diff --git a/script.html b/script.html index 27198b7..bf9c0c8 100644 --- a/script.html +++ b/script.html @@ -996,4 +996,202 @@ function pzPaint(lines) { : '')) + (over.length ? '
Shot' + (over.length > 1 ? 's ' : ' ') + + over.map(function (x) { return x.index; }).join(", ") + + ' will not fit in eight seconds once filmed: ' + + over.map(function (x) { return x.index + " would run " + (x.seconds * FILMED).toFixed(1) + "s"; }).join(", ") + + '. Trim those lines before filming.
' + : '') + + '
' + + PZ.map(function (x) { + var f = (x.seconds || 0) * FILMED; + return '
' + + 'shot ' + x.index + ' · ' + x.seconds.toFixed(1) + 's spoken, ~' + + f.toFixed(1) + 's filmed' + + (f > 8 ? ' over' : '') + + ' · ' + esc(x.line.slice(0, 60)) + '
'; + }).join("") + + '
'; + + var img = document.getElementById("pzimg"), + aud = document.getElementById("pzaud"), + btn = document.getElementById("pzplay"), + where = document.getElementById("pzwhere"); + + function stop() { + aud.pause(); + if (PZTIMER) { clearTimeout(PZTIMER); PZTIMER = null; } + btn.innerHTML = "▶ Play the run-through"; + } + + function playFrom(i) { + if (i >= PZ.length) { stop(); return; } + var x = PZ[i]; + img.src = x.plate || ""; + where.textContent = "shot " + x.index + " of " + PZ.length + " · " + x.seconds.toFixed(1) + "s"; + aud.src = x.url; + aud.onended = function () { playFrom(i + 1); }; + aud.play().catch(function () { stop(); }); + } + + btn.onclick = function () { + if (!aud.paused) { stop(); return; } + btn.innerHTML = "▮▮ Stop"; + playFrom(0); + }; +} + +function bindAlign() { + var b = document.getElementById("alignbtn"); + if (!b) return; + var out = document.getElementById("alignout"); + + function paint(r) { + var bought = ((DATA && DATA.script && DATA.script.segments) || []).length * 8; + var filmed = r.filmed || 0; + out.innerHTML = '

Measured: ' + + (r.spoken || 0).toFixed(1) + 's spoken, about ' + filmed.toFixed(1) + + 's filmed, against ' + bought + 's of shots.

' + + (r.audio ? '' : '') + + (r.shots || []).map(function (s) { + var fit = Math.abs((s.filmed || 0) - 8) <= 1.2; + return '
' + + 'shot ' + s.index + ' · ' + (s.start || 0).toFixed(1) + '-' + + (s.end || 0).toFixed(1) + 's spoken · ~' + (s.filmed || 0).toFixed(1) + 's filmed' + + (fit ? '' : ' off the clock') + + '
' + esc(s.line || "") + '
'; + }).join("") + + (filmed < bought - 1.5 + ? '
The whole piece only runs about ' + + filmed.toFixed(1) + ' seconds filmed, but ' + bought + ' seconds are being shot. ' + + 'No arrangement of these words fills the video — it needs more written, or a ' + + 'shorter length.
' + : '') + + (filmed > bought + 1.5 + ? '
The whole piece runs about ' + + filmed.toFixed(1) + ' seconds filmed against ' + bought + ' seconds of video, so the ' + + 'end would be cut off. Trim it, or buy more length.
' + : ''); + } + + b.onclick = async function () { + hideMsg("msg"); + b.disabled = true; b.textContent = "Measuring…"; + var r = await api("align_script", { email: email, project_id: pid, refresh: true }); + if (r.ok && r.pending) { + var waited = 0; + r = await new Promise(function (done) { + var t = setInterval(async function () { + waited += 4; + var g = await api("align_script", { email: email, project_id: pid }); + if (g.ok && !g.pending) { clearInterval(t); done(g); return; } + if (waited > 240) { clearInterval(t); done({ ok: false, error: "That is taking unusually long." }); } + }, 4000); + }); + } + b.disabled = false; b.textContent = "Cut it to the clock"; + if (!r.ok) { showMsg("msg", r.error || "Could not measure that."); return; } + if (r.note) { showMsg("msg", r.note); return; } + paint(r); + showMsg("msg", "Shots re-cut at real word endings.", "ok"); + load(); + }; + + api("align_script", { email: email, project_id: pid }).then(function (r) { + if (r && r.ok && !r.pending && r.audio) paint(r); + }); +} + +function bindPreviz() { + var b = document.getElementById("pzmake"); + if (!b) return; + var note = document.getElementById("pznote"); + + async function poll() { + var waited = 0; + return await new Promise(function (done) { + var t = setInterval(async function () { + waited += 4; + var g = await api("make_previz", { email: email, project_id: pid }); + if (g.ok && !g.pending) { clearInterval(t); done(g); return; } + if (g.ok && g.pending && note) note.textContent = g.status_text || ""; + if (waited > 240) { clearInterval(t); done({ ok: false, error: "That is taking unusually long." }); } + }, 4000); + }); + } + + b.onclick = async function () { + hideMsg("msg"); + b.disabled = true; b.textContent = "Speaking…"; + var r = await api("make_previz", { email: email, project_id: pid }); + if (r.ok && r.pending) { note.textContent = r.status_text || ""; r = await poll(); } + b.disabled = false; b.textContent = "Speak it again"; + if (!r.ok) { note.textContent = ""; showMsg("msg", r.error || "Could not speak that."); return; } + note.textContent = r.status_text || ""; + pzPaint(r.lines || []); + }; + + // Already spoken? Show it without asking again. + api("make_previz", { email: email, project_id: pid }).then(function (r) { + if (r && r.ok && !r.pending && (r.lines || []).length) { + b.textContent = "Speak it again"; + note.textContent = r.status_text || ""; + pzPaint(r.lines); + } + }); +} + +// The picture is the hook; the link carries the video. Never the video itself +// over MMS - carriers transcode it into mush. +function bindShare() { + var b = document.getElementById("sharebtn"); + if (!b) return; + var note = document.getElementById("sharenote"), + out = document.getElementById("shareout"); + + // The file URL ends in /public with no filename. The headers are already + // correct (video/mp4, filename=final.mp4) so anything that reads them is + // fine, but clients that sniff the extension from the URL string are not. + // A filename query is accepted and ignored by the file service. + // Filled in by share_link, which owns the address. A link straight to the + // file would be invisible to us; this one is counted server side. + var SHARE_LINK = ""; + function named(url) { return SHARE_LINK || ""; } + + function paint(r) { + var kb = Math.round((r.bytes || 0) / 1024); + var link = named(r.video || ""); + var msg = ((DATA && DATA.script && DATA.script.title) + ? DATA.script.title + " — " : "") + + ((DATA && DATA.project && DATA.project.pet_name) || "your pet") + + " has something to say. " + link; + out.innerHTML = '
' + + 'share picture' + + '
' + + '

' + esc(r.size || "") + ' · ' + kb + ' KB' + + ' · well inside the 600KB most carriers allow, so it will not be ' + + 'downsampled.

' + + '' + + '' + + '' + + '' + + 'Download the picture' + + '

' + esc(link) + + '

' + + '
'; + api("share_link", { email: email, project_id: pid }).then(function (s) { + if (!(s && s.ok)) return; + SHARE_LINK = s.url || ""; + var el = document.getElementById("viewcount"); + if (el) { + el.innerHTML = '
' + esc(SHARE_LINK) + '
Opened ' + (s.views || 0) + + ' time' + ((s.views === 1) ? '' : 's') + '.'; + } + var t = document.getElementById("sharemsg"); + if (t && SHARE_LINK) t.value = t.value.replace(/https?:\/\/\S+/, SHARE_LINK); + }); + var sl = document.getElementById("sharelink"); + if (sl) sl.onclick = async function () { + var title = (DATA && DATA.script && DATA.script.title) || "A video"; \ No newline at end of file