Transcripts: say why a show's own transcript was not found; read Podgrab fields whatever their capitals
build-container / build (push) Successful in 4m2s
build-container / build (push) Successful in 4m2s
This commit is contained in:
1 parent
ae00f61711
commit
747002924d
5 files changed
+27
-16
No files matched your search
+21
-11
@@ -50,18 +50,23 @@ function transcriptLinks(feedXml, guid, fileUrl) {
|
||||
const unwrap = x => String(x || '').replace(/<!\[CDATA\[|\]\]>/g, '').trim();
|
||||
const item = items.find(b => { const g = /<guid[^>]*>([\s\S]*?)<\/guid>/i.exec(b); return g && guid && unwrap(g[1]) === guid; })
|
||||
|| items.find(b => { const e = /<enclosure[^>]*>/i.exec(b); return e && fileUrl && attr(e[0], 'url') === fileUrl; });
|
||||
if (!item) return [];
|
||||
if (!item) return null;
|
||||
return [...item.matchAll(/<podcast:transcript\s+([^>]*?)\/?>/gi)].map(m => ({ url: attr(m[1], 'url'), type: attr(m[1], 'type').toLowerCase() })).filter(x => x.url);
|
||||
}
|
||||
/** { transcript } when the show publishes one we can read, otherwise { why }: the reason, so a missing transcript can be explained. */
|
||||
async function nativeTranscript(info) {
|
||||
if (!info || !info.feedUrl) return null;
|
||||
const links = transcriptLinks(await getText(info.feedUrl), info.guid, info.fileUrl);
|
||||
if (!info) return { why: 'Could not read this episode from the podcast service.' };
|
||||
if (!info.feedUrl) return { why: 'The podcast service did not give the show\'s feed address.' };
|
||||
let xml; try { xml = await getText(info.feedUrl); } catch (e) { return { why: 'Could not download the show\'s feed (' + e.message + ').' }; }
|
||||
const links = transcriptLinks(xml, info.guid, info.fileUrl);
|
||||
if (!links) return { why: 'Could not find this episode in the show\'s feed (looked for ' + (info.guid ? 'id ' + String(info.guid).slice(0, 60) : 'its audio address') + ').' };
|
||||
if (!links.length) return { why: 'The show\'s feed lists no transcript for this episode.' };
|
||||
const rank = l => /json/.test(l.type) ? 0 : /vtt/.test(l.type) ? 1 : /subrip|srt/.test(l.type) ? 2 : 9;
|
||||
for (const l of links.sort((a, b) => rank(a) - rank(b))) {
|
||||
if (rank(l) > 2) continue; // (plain HTML or text has no times: not worth showing as a transcript)
|
||||
try { const t = await getText(l.url), lines = /json/.test(l.type) ? parseJson(t) : parseCues(t); const r = shape(lines, 'Published by the show'); if (r) return r; } catch {}
|
||||
try { const t = await getText(l.url), lines = /json/.test(l.type) ? parseJson(t) : parseCues(t); const r = shape(lines, 'Published by the show'); if (r) return { transcript: r }; } catch {}
|
||||
}
|
||||
return null;
|
||||
return { why: 'The show lists a transcript (' + links.map(l => l.type || 'unknown type').join(', ') + ') but not in a timed format this can follow, or it could not be downloaded.' };
|
||||
}
|
||||
|
||||
// ---- speech to text on this server
|
||||
@@ -110,7 +115,7 @@ function startJob(key, src, cookie) {
|
||||
}
|
||||
|
||||
const SRC_OK = [/^\/api\/podcast\/podcastitems\/[\w-]+\/file$/, /^\/api\/ab\/books\/[^?#\s]+$/]; // only audio this hub already serves to the person
|
||||
const nativeMiss = new Map(); // key -> time: a show with no transcript is not asked again for a day
|
||||
const nativeMiss = new Map(); // key -> { t, why }: a show with no transcript is not asked again for a day
|
||||
async function handle(req, res) {
|
||||
const u = new URL(req.url, 'http://x'), q = u.searchParams, key0 = String(q.get('key') || ''), src = String(q.get('src') || '');
|
||||
const kind = key0.startsWith('pod:') ? 'pod' : key0.startsWith('ab:') ? 'ab' : '';
|
||||
@@ -124,12 +129,17 @@ async function handle(req, res) {
|
||||
if (!cfg.api) return json(res, 400, { error: 'Transcribing is not set up on this server (see the whisper service in docker-compose.yml)' });
|
||||
startJob(key, src, req.headers.cookie); return json(res, 200, { state: 'running', since: Date.now(), ...base });
|
||||
}
|
||||
if (kind === 'pod' && !(nativeMiss.get(key) > Date.now() - 864e5)) {
|
||||
let r = null; try { r = await nativeTranscript(await podcast.feedInfo(me, key0.slice(4))); } catch {}
|
||||
if (r) { atomicWrite(fileOf(key), JSON.stringify(r)); return json(res, 200, { state: 'ready', ...base, transcript: r }); }
|
||||
nativeMiss.set(key, Date.now());
|
||||
let why = '';
|
||||
if (kind === 'pod') {
|
||||
const miss = nativeMiss.get(key);
|
||||
if (miss && miss.t > Date.now() - 6 * 3600e3) why = miss.why;
|
||||
else {
|
||||
let r; try { r = await nativeTranscript(await podcast.feedInfo(me, key0.slice(4))); } catch (e) { r = { why: 'Checking the show\'s feed failed (' + e.message + ').' }; }
|
||||
if (r.transcript) { atomicWrite(fileOf(key), JSON.stringify(r.transcript)); return json(res, 200, { state: 'ready', ...base, transcript: r.transcript }); }
|
||||
why = r.why; nativeMiss.set(key, { t: Date.now(), why });
|
||||
}
|
||||
}
|
||||
return json(res, 200, { state: 'none', ...base });
|
||||
return json(res, 200, { state: 'none', why, ...base });
|
||||
} catch (e) { json(res, e.status || 500, { error: e.message }); }
|
||||
}
|
||||
module.exports = { init, handle, parseCues, parseJson, transcriptLinks, shape, whisper, getText, nativeTranscript, enabled: () => true };
|
||||
Reference in new issue
Block a user