script: rebuild part 5 of 6

This commit is contained in:
gator 2026-09-14 13:34:44 +00:00
parent 7ce21f6621
commit dd1ad70fb7

View file

@ -996,4 +996,202 @@ function pzPaint(lines) {
: ''))
+ (over.length
? '<div class="warn" style="margin-top:12px">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.</div>'
: '')
+ '<div style="margin-top:14px">'
+ PZ.map(function (x) {
var f = (x.seconds || 0) * FILMED;
return '<div class="foot" style="padding:6px 0;border-top:1px solid var(--line)">'
+ 'shot ' + x.index + ' &middot; ' + x.seconds.toFixed(1) + 's spoken, ~'
+ f.toFixed(1) + 's filmed'
+ (f > 8 ? ' <span style="color:var(--accent2)">over</span>' : '')
+ ' &middot; ' + esc(x.line.slice(0, 60)) + '</div>';
}).join("")
+ '</div>';
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 = "&#9654; 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 = "&#9646;&#9646; 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 = '<p class="foot" style="margin:0 0 8px">Measured: '
+ (r.spoken || 0).toFixed(1) + 's spoken, about ' + filmed.toFixed(1)
+ 's filmed, against ' + bought + 's of shots.</p>'
+ (r.audio ? '<audio controls src="' + esc(r.audio) + '" style="width:100%;max-width:420px"></audio>' : '')
+ (r.shots || []).map(function (s) {
var fit = Math.abs((s.filmed || 0) - 8) <= 1.2;
return '<div class="foot" style="padding:6px 0;border-top:1px solid var(--line)">'
+ 'shot ' + s.index + ' &middot; ' + (s.start || 0).toFixed(1) + '-'
+ (s.end || 0).toFixed(1) + 's spoken &middot; ~' + (s.filmed || 0).toFixed(1) + 's filmed'
+ (fit ? '' : ' <span style="color:var(--accent2)">off the clock</span>')
+ '<br>' + esc(s.line || "") + '</div>';
}).join("")
+ (filmed < bought - 1.5
? '<div class="warn" style="margin-top:12px">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 &mdash; it needs more written, or a '
+ 'shorter length.</div>'
: '')
+ (filmed > bought + 1.5
? '<div class="warn" style="margin-top:12px">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.</div>'
: '');
}
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 = '<div style="display:flex;gap:18px;flex-wrap:wrap;align-items:flex-start">'
+ '<img src="' + esc(r.image) + '" alt="share picture" '
+ 'style="width:190px;border-radius:10px;border:1px solid var(--line);display:block">'
+ '<div style="flex:1 1 260px">'
+ '<p class="foot" style="margin:0 0 10px">' + esc(r.size || "") + ' &middot; ' + kb + ' KB'
+ ' &middot; well inside the 600KB most carriers allow, so it will not be '
+ 'downsampled.</p>'
+ '<label class="mini">The message</label>'
+ '<textarea id="sharemsg" style="min-height:70px">' + esc(msg) + '</textarea>'
+ '<button type="button" class="btn" id="sharelink" style="margin-top:8px">Share the video</button>'
+ '<button type="button" class="btn ghost" id="sharecopy" style="margin-left:8px;margin-top:8px">Copy the message</button>'
+ '<a class="btn ghost" style="margin-left:8px;display:inline-block;text-decoration:none" '
+ 'href="' + esc(r.image) + '" download>Download the picture</a>'
+ '<p class="foot" style="margin:12px 0 0;word-break:break-all">' + esc(link)
+ '<span id="viewcount"></span></p>'
+ '</div></div>';
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 = '<br>' + esc(SHARE_LINK) + '<br>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";
<!--PTLCHUNK-->