openvibers/openvibe.mediahub Commits
Chapters: no chapter for a moment with no title, or past the video's end (VOD 6026 listed "0:16 " and 41:16 in 32:01) (#142)
Changes
@@@ -383,14 +383,22 @@ function chapterTitle(description) {return `${cut.slice(0, Math.max(cut.lastIndexOf(' '), 50))}…`;}--/** The AI moments as chapters: the first one, then one at least a twelfth of the stream later, at most twelve. */++/**++ * The AI moments as chapters: the first one, then one at least a twelfth of the stream later, at most twelve. A moment++ * with nothing seen to title it (only what was heard), or past the video's end (a stream's moments read for one part of++ * it, before Live kept parts), is no chapter: VOD 6026 listed "0:16" with no title and 41:16 in a 32-minute video.++ */function chapters(mems, durationSeconds) {-- const gap = Math.max(0, Number(durationSeconds) || 0) / MAX_CHAPTERS;++ const length = Math.max(0, Number(durationSeconds) || 0);++ const gap = length / MAX_CHAPTERS;const out = [];for (const m of mems) {if (out.length >= MAX_CHAPTERS) break;++ if (length && m.offset_seconds >= length) break;++ const title = chapterTitle(m.description);++ if (!title) continue;if (out.length && m.offset_seconds - out[out.length - 1].offset_seconds < gap) continue;-- out.push({ offset_seconds: m.offset_seconds, description: chapterTitle(m.description) });++ out.push({ offset_seconds: m.offset_seconds, description: title });}return out;}@@@ -15,6 +15,10 @@ assert.strictEqual(out[0].description, 'Moment 0', 'the heard part is dropped');// A short clip-length recording keeps close moments (the gap is a twelfth of 45 s).assert.deepStrictEqual(chapters([{ offset_seconds: 10, description: 'Intro' }, { offset_seconds: 30, description: 'Main' }], 45).map((c) => c.offset_seconds), [10, 30]);++// A moment with only what was heard (no title), or past the video's end, is no chapter (VOD 6026: "0:16 " and 41:16 in 32:01).++assert.deepStrictEqual(chapters([{ offset_seconds: 16, description: ' — heard: "hey"' }, { offset_seconds: 287, description: 'A campsite' }, { offset_seconds: 2476, description: 'Later' }], 1921)++ .map((c) => [c.offset_seconds, c.description]), [[287, 'A campsite']]);++// Titles: a trailing period goes; a long one is cut at a word with an ellipsis.assert.strictEqual(chapterTitle('A streamer waves.'), 'A streamer waves');const long = chapterTitle('word '.repeat(40));
@@ -383,14 +383,22 @@ function chapterTitle(description) {
@@ -383,14 +383,22 @@ function chapterTitle(description) {
return `${cut.slice(0, Math.max(cut.lastIndexOf(' '), 50))}…`;
return `${cut.slice(0, Math.max(cut.lastIndexOf(' '), 50))}…`;
}
}
-/** The AI moments as chapters: the first one, then one at least a twelfth of the stream later, at most twelve. */
+/**
+ * The AI moments as chapters: the first one, then one at least a twelfth of the stream later, at most twelve. A moment
+ * with nothing seen to title it (only what was heard), or past the video's end (a stream's moments read for one part of
+ * it, before Live kept parts), is no chapter: VOD 6026 listed "0:16" with no title and 41:16 in a 32-minute video.
+ */
function chapters(mems, durationSeconds) {
function chapters(mems, durationSeconds) {
- const gap = Math.max(0, Number(durationSeconds) || 0) / MAX_CHAPTERS;
+ const length = Math.max(0, Number(durationSeconds) || 0);
+ const gap = length / MAX_CHAPTERS;
const out = [];
const out = [];
for (const m of mems) {
for (const m of mems) {
if (out.length >= MAX_CHAPTERS) break;
if (out.length >= MAX_CHAPTERS) break;
+ if (length && m.offset_seconds >= length) break;
+ const title = chapterTitle(m.description);
+ if (!title) continue;
if (out.length && m.offset_seconds - out[out.length - 1].offset_seconds < gap) continue;
if (out.length && m.offset_seconds - out[out.length - 1].offset_seconds < gap) continue;
- out.push({ offset_seconds: m.offset_seconds, description: chapterTitle(m.description) });
+ out.push({ offset_seconds: m.offset_seconds, description: title });
}
}
return out;
return out;
}
}
@@ -15,6 +15,10 @@ assert.strictEqual(out[0].description, 'Moment 0', 'the heard part is dropped');
@@ -15,6 +15,10 @@ assert.strictEqual(out[0].description, 'Moment 0', 'the heard part is dropped');
// A short clip-length recording keeps close moments (the gap is a twelfth of 45 s).
// A short clip-length recording keeps close moments (the gap is a twelfth of 45 s).
assert.deepStrictEqual(chapters([{ offset_seconds: 10, description: 'Intro' }, { offset_seconds: 30, description: 'Main' }], 45).map((c) => c.offset_seconds), [10, 30]);
assert.deepStrictEqual(chapters([{ offset_seconds: 10, description: 'Intro' }, { offset_seconds: 30, description: 'Main' }], 45).map((c) => c.offset_seconds), [10, 30]);
+// A moment with only what was heard (no title), or past the video's end, is no chapter (VOD 6026: "0:16 " and 41:16 in 32:01).
+assert.deepStrictEqual(chapters([{ offset_seconds: 16, description: ' — heard: "hey"' }, { offset_seconds: 287, description: 'A campsite' }, { offset_seconds: 2476, description: 'Later' }], 1921)
+ .map((c) => [c.offset_seconds, c.description]), [[287, 'A campsite']]);
+
// Titles: a trailing period goes; a long one is cut at a word with an ellipsis.
// Titles: a trailing period goes; a long one is cut at a word with an ellipsis.
assert.strictEqual(chapterTitle('A streamer waves.'), 'A streamer waves');
assert.strictEqual(chapterTitle('A streamer waves.'), 'A streamer waves');
const long = chapterTitle('word '.repeat(40));
const long = chapterTitle('word '.repeat(40));