Customize Website

x
Color Swatches
Theme
Font Size
100%

YouTube AI Quiz Generator

100% FREE

Turn any YouTube video into a captivating quiz in seconds! Customize the format and difficulty, then share, embed, or download as images.

`; const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = fileSafe(quizData.quiz_title) + '-qa.xls'; document.body.appendChild(a); a.click(); setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 1000); } function wrapCanvasText(ctx, text, x, y, maxWidth, lineHeight, maxLines) { const words = String(text || '').split(/\s+/); let line = ''; let lines = []; words.forEach(function (word) { const test = line ? line + ' ' + word : word; if (ctx.measureText(test).width > maxWidth && line) { lines.push(line); line = word; } else { line = test; } }); if (line) lines.push(line); if (maxLines && lines.length > maxLines) { lines = lines.slice(0, maxLines); lines[lines.length - 1] = lines[lines.length - 1].replace(/\s+\S*$/, '') + '...'; } lines.forEach(function (ln, idx) { ctx.fillText(ln, x, y + idx * lineHeight); }); return y + lines.length * lineHeight; } function renderQuestionCanvas(q, index, title) { const canvas = document.createElement('canvas'); canvas.width = 1080; canvas.height = 1080; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#f8fafc'; ctx.fillRect(0, 0, 1080, 1080); ctx.fillStyle = '#4c1d95'; ctx.fillRect(0, 0, 1080, 138); ctx.fillStyle = '#ffffff'; ctx.font = '800 34px Arial, sans-serif'; wrapCanvasText(ctx, title, 70, 60, 940, 42, 2); ctx.fillStyle = '#7c3aed'; ctx.font = '800 26px Arial, sans-serif'; ctx.fillText('Question ' + (index + 1) + ' of ' + quizData.questions.length, 70, 210); ctx.fillStyle = '#0f172a'; ctx.font = '800 48px Arial, sans-serif'; let y = wrapCanvasText(ctx, stripHtml(escapeHtml(q.question).replace(/___/g, '_____')), 70, 285, 940, 62, 7) + 34; ctx.font = '600 32px Arial, sans-serif'; ctx.fillStyle = '#334155'; if (q.type === 'multiple_choice') { (q.options || []).forEach(function (opt, optionIndex) { y = wrapCanvasText(ctx, String.fromCharCode(65 + optionIndex) + '. ' + opt, 92, y, 870, 44, 2) + 18; }); } else if (q.type === 'true_false') { ctx.fillText('True Or False', 92, y); } ctx.fillStyle = '#ede9fe'; ctx.fillRect(0, 1000, 1080, 80); ctx.fillStyle = '#6d28d9'; ctx.font = '700 25px Arial, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Answer provided in the separate answer image', 540, 1050); ctx.textAlign = 'left'; return canvas; } function renderAnswerCanvas(q, index, title) { const canvas = document.createElement('canvas'); canvas.width = 1080; canvas.height = 1080; const ctx = canvas.getContext('2d'); const questionText = stripHtml(escapeHtml(q.question).replace(/___/g, '_____')); const answerText = stripHtml(getCorrectAnswer(q)); const explanationText = stripHtml(q.explanation || 'No explanation provided.'); ctx.fillStyle = '#f8fafc'; ctx.fillRect(0, 0, 1080, 1080); ctx.fillStyle = '#4c1d95'; ctx.fillRect(0, 0, 1080, 138); ctx.fillStyle = '#ffffff'; ctx.font = '800 34px Arial, sans-serif'; wrapCanvasText(ctx, title, 70, 60, 940, 42, 2); ctx.fillStyle = '#059669'; ctx.font = '800 26px Arial, sans-serif'; ctx.fillText('Answer ' + (index + 1) + ' of ' + quizData.questions.length, 70, 210); ctx.fillStyle = '#0f172a'; ctx.font = '800 38px Arial, sans-serif'; wrapCanvasText(ctx, questionText, 70, 280, 940, 50, 5); ctx.fillStyle = '#d1fae5'; ctx.fillRect(70, 535, 940, 210); ctx.fillStyle = '#10b981'; ctx.fillRect(70, 535, 8, 210); ctx.fillStyle = '#047857'; ctx.font = '800 27px Arial, sans-serif'; ctx.fillText('Correct Answer', 110, 595); ctx.fillStyle = '#064e3b'; ctx.font = '800 40px Arial, sans-serif'; wrapCanvasText(ctx, answerText, 110, 660, 840, 50, 2); ctx.fillStyle = '#7c3aed'; ctx.fillRect(70, 800, 940, 4); ctx.fillStyle = '#4c1d95'; ctx.font = '800 27px Arial, sans-serif'; ctx.fillText('Explanation', 70, 855); ctx.fillStyle = '#334155'; ctx.font = '600 29px Arial, sans-serif'; wrapCanvasText(ctx, explanationText, 70, 910, 940, 39, 4); return canvas; } function canvasToPng(canvas) { return new Promise(function (resolve, reject) { canvas.toBlob(function (blob) { if (blob) resolve(blob); else reject(new Error('Could not create a quiz image.')); }, 'image/png'); }); } const zipCrcTable = (function () { const table = new Uint32Array(256); for (let n = 0; n < 256; n++) { let value = n; for (let bit = 0; bit < 8; bit++) { value = (value & 1) ? (0xedb88320 ^ (value >>> 1)) : (value >>> 1); } table[n] = value >>> 0; } return table; })(); function zipCrc32(bytes) { let crc = 0xffffffff; for (let i = 0; i < bytes.length; i++) { crc = zipCrcTable[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8); } return (crc ^ 0xffffffff) >>> 0; } function zipWrite16(view, offset, value) { view.setUint16(offset, value, true); } function zipWrite32(view, offset, value) { view.setUint32(offset, value >>> 0, true); } function zipDateParts(date) { const year = Math.max(1980, date.getFullYear()); return { time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2), date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate() }; } async function createZipBlob(files) { const encoder = new TextEncoder(); const now = zipDateParts(new Date()); const parts = []; const centralParts = []; let localOffset = 0; let centralSize = 0; for (const file of files) { const name = encoder.encode(file.name); const data = new Uint8Array(await file.blob.arrayBuffer()); const crc = zipCrc32(data); const local = new Uint8Array(30 + name.length); const localView = new DataView(local.buffer); zipWrite32(localView, 0, 0x04034b50); zipWrite16(localView, 4, 20); zipWrite16(localView, 6, 0x0800); zipWrite16(localView, 8, 0); zipWrite16(localView, 10, now.time); zipWrite16(localView, 12, now.date); zipWrite32(localView, 14, crc); zipWrite32(localView, 18, data.length); zipWrite32(localView, 22, data.length); zipWrite16(localView, 26, name.length); zipWrite16(localView, 28, 0); local.set(name, 30); parts.push(local, data); const central = new Uint8Array(46 + name.length); const centralView = new DataView(central.buffer); zipWrite32(centralView, 0, 0x02014b50); zipWrite16(centralView, 4, 20); zipWrite16(centralView, 6, 20); zipWrite16(centralView, 8, 0x0800); zipWrite16(centralView, 10, 0); zipWrite16(centralView, 12, now.time); zipWrite16(centralView, 14, now.date); zipWrite32(centralView, 16, crc); zipWrite32(centralView, 20, data.length); zipWrite32(centralView, 24, data.length); zipWrite16(centralView, 28, name.length); zipWrite16(centralView, 30, 0); zipWrite16(centralView, 32, 0); zipWrite16(centralView, 34, 0); zipWrite16(centralView, 36, 0); zipWrite32(centralView, 38, 0); zipWrite32(centralView, 42, localOffset); central.set(name, 46); centralParts.push(central); centralSize += central.length; localOffset += local.length + data.length; } const end = new Uint8Array(22); const endView = new DataView(end.buffer); zipWrite32(endView, 0, 0x06054b50); zipWrite16(endView, 4, 0); zipWrite16(endView, 6, 0); zipWrite16(endView, 8, files.length); zipWrite16(endView, 10, files.length); zipWrite32(endView, 12, centralSize); zipWrite32(endView, 16, localOffset); zipWrite16(endView, 20, 0); return new Blob(parts.concat(centralParts, [end]), { type: 'application/zip' }); } async function downloadImagePack() { if (!quizData || !quizData.questions || !quizData.questions.length) return; const title = quizData.quiz_title || 'YouTube Quiz'; const baseName = fileSafe(title); const $buttons = $('.ytq-images-btn'); $buttons.each(function () { const $button = $(this); if (!$button.data('ytq-original-text')) $button.data('ytq-original-text', $button.text()); }).prop('disabled', true).text('Creating ZIP…'); try { const files = []; for (let i = 0; i < quizData.questions.length; i++) { $buttons.text('Creating ' + (i + 1) + '/' + quizData.questions.length + '…'); const questionCanvas = renderQuestionCanvas(quizData.questions[i], i, title); const answerCanvas = renderAnswerCanvas(quizData.questions[i], i, title); const number = String(i + 1).padStart(2, '0'); files.push({ name: 'questions/' + baseName + '-question-' + number + '.png', blob: await canvasToPng(questionCanvas) }, { name: 'answers/' + baseName + '-answer-' + number + '.png', blob: await canvasToPng(answerCanvas) }); } const zipBlob = await createZipBlob(files); const a = document.createElement('a'); a.href = URL.createObjectURL(zipBlob); a.download = baseName + '-images.zip'; document.body.appendChild(a); a.click(); setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 1000); } catch (error) { console.error('Quiz image ZIP failed:', error); alert('Could not create the image ZIP. Please try again.'); } finally { $buttons.each(function () { const $button = $(this); $button.text($button.data('ytq-original-text') || 'Images'); }).prop('disabled', false); } } function recordSharedSubmission() { if (!activeShareToken || sharedSubmissionRecorded) return; sharedSubmissionRecorded = true; $.ajax({ type: 'POST', url: ajaxUrl(), data: { action: 'yt_quiz_share_submit', token: activeShareToken } }); } $(document).off('click.ytqshare').on('click.ytqshare', '.ytq-share-btn', function () { createShareLink($(this)); }); $(document).off('click.ytqexcel').on('click.ytqexcel', '.ytq-excel-btn', downloadExcel); $(document).off('click.ytqimages').on('click.ytqimages', '.ytq-images-btn', downloadImagePack); $(document).off('click.ytqedit').on('click.ytqedit', '.ytq-edit-btn', renderQuizEditor); $(document).off('click.ytqdeletequestion').on('click.ytqdeletequestion', '.ytq-delete-question', function () { const index = Number($(this).data('index')); if (!quizData || quizData.questions.length <= 1) { alert('A quiz needs at least one question. Regenerate this question instead.'); return; } quizData.questions.splice(index, 1); resetQuizProgress(); renderQuizEditor(); }); $(document).off('click.ytqregeneratequestion').on('click.ytqregeneratequestion', '.ytq-regenerate-question', function () { regenerateQuestion(Number($(this).data('index')), $(this)); }); $(document).off('click.ytqfinishediting').on('click.ytqfinishediting', '#ytq-finish-editing', function () { resetQuizProgress(); renderQuestion(0); }); function createResultMount() { $('.result-container').remove(); newResultContainer = $('
'); newResultDiv = $('
'); newResultContainer.append(newResultDiv); if ($('.tool-box').length) $('.tool-box').after(newResultContainer); else $('#ytq_html_anchors').after(newResultContainer); } function loadSharedQuiz(token) { activeShareToken = token; generationConfig = null; sharedSubmissionRecorded = false; createResultMount(); $('.tool-box').hide(); newResultDiv.html(createProgressBlock()); startProgress(); $.ajax({ type: 'POST', url: ajaxUrl(), data: { action: 'yt_quiz_share_get', token: token }, success: function (raw) { clearInterval(progressInt); let d; try { d = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch (ex) { showError('Could not load this shared quiz.'); return; } if (!d.success || !d.data || !d.data.quiz) { showError(d.error || 'Share link not found or expired.'); return; } quizData = d.data.quiz; currentQ = 0; score = 0; answers = []; renderQuizShell({ video_id: d.data.video_id, video_title: d.data.video_title, difficulty: d.data.difficulty, source_mode: d.data.source_mode }, true); renderQuestion(0); $('html,body').animate({ scrollTop: newResultDiv.offset().top - 80 }, 300); }, error: function () { clearInterval(progressInt); showError('Could not load this shared quiz. Please try again later.'); } }); } // ── URL preview overlay (retrieve video name + X to clear) ── const ytqUrlInput = $('#form-field-video_url'); function ytqExtractVideoId(url) { const m = url.match(/(?:v=|youtu\.be\/|\/shorts\/|\/embed\/)([a-zA-Z0-9_-]{11})/); return m ? m[1] : (/^[a-zA-Z0-9_-]{11}$/.test(url) ? url : null); } function ytqShowOverlay(label, vid) { $('#youtube-overlay').remove(); const ov = $('
').attr('data-vid', vid); ov.append($('').text(label)).append($('

Tool Comments