Fixes to timeline thumbnails

This commit is contained in:
WardPearce
2026-02-11 06:10:03 +13:00
parent 37fb50c1f8
commit 0875214183
3 changed files with 69 additions and 50 deletions
+13 -9
View File
@@ -523,13 +523,13 @@
}
if (data.video.storyboards && data.video.storyboards.length > 2) {
storyboardThumbnails(
data.video.storyboards[2],
playerTimelineThumbnailsCache,
playerMaxKnownTime
).then((thumbnails) => {
playerTimelineThumbnails = thumbnails;
});
try {
storyboardThumbnails(data.video).then((thumbnails) => {
playerTimelineThumbnails = thumbnails;
});
} catch {
// Continue regardless of error.
}
}
if (
@@ -1374,9 +1374,13 @@
></canvas>
{/if}
{#if playerCloestSponsor}
<p class="no-margin">{sponsorSegments[playerCloestSponsor.category]}</p>
<p class="no-margin" style="padding: 0 0.5rem;">
{sponsorSegments[playerCloestSponsor.category]}
</p>
{:else if playerCloestTimestamp}
<p class="no-margin">{truncate(playerCloestTimestamp.title, 22)}</p>
<p class="no-margin" style="padding: 0 0.5rem;">
{truncate(playerCloestTimestamp.title, 22)}
</p>
{/if}
{/snippet}
<div class="track">
+55 -40
View File
@@ -1,5 +1,8 @@
import type { StoryBoard } from './api/model';
import { get } from 'svelte/store';
import type { VideoPlay } from './api/model';
import { ImageCache } from './images';
import { instanceStore } from './store';
import { parseText } from 'media-captions';
export interface TimelineThumbnail {
url: string;
@@ -9,58 +12,70 @@ export interface TimelineThumbnail {
index: number;
}
export async function storyboardThumbnails(
storyboard: StoryBoard,
imageCache: ImageCache,
videoLength: number
): Promise<TimelineThumbnail[]> {
const { count, storyboardCount, width, height, templateUrl } = storyboard;
export async function storyboardThumbnails(video: VideoPlay): Promise<TimelineThumbnail[]> {
if (!video.storyboards || video.storyboards.length < 2) return [];
// Our ytjs implementation doesn't do this right,
// so just assume its incorrect and fetch manually.
// Doesn't actually add any extra overhead.
const firstSheetUrl = templateUrl.replace('M$M', `M0`);
const firstSheetImage = await imageCache.load(firstSheetUrl);
if (!firstSheetImage) {
return [];
}
const storyboardWidth = firstSheetImage.width;
const storyboardHeight = firstSheetImage.height;
const cols = Math.floor(storyboardWidth / width);
const rows = Math.floor(storyboardHeight / height);
const thumbnails: TimelineThumbnail[] = [];
const totalThumbnails = storyboardCount * count;
const videoDurationMs = videoLength * 1000;
if (video.fallbackPatch === 'youtubejs') {
const { count, storyboardCount, width, height, templateUrl } = video.storyboards[2];
let globalIndex = 0;
const totalThumbnails = storyboardCount * count;
const videoDurationMs = video.lengthSeconds * 1000;
const timeInterval = videoDurationMs / totalThumbnails;
for (let sheetIndex = 0; sheetIndex < storyboardCount; sheetIndex++) {
const url = templateUrl.replace('M$M', `M${sheetIndex}`);
// Pre-cache sheet
imageCache.load(url);
for (let i = 0; i < count; i++) {
const localIndex = i % (cols * rows);
const row = Math.floor(localIndex / cols);
if (row >= rows) break;
const time = Math.floor((globalIndex / totalThumbnails) * videoDurationMs);
for (let sheetIndex = 0; sheetIndex < storyboardCount; sheetIndex++) {
const url = templateUrl.replace('M$M', `M${sheetIndex}`);
const time = Math.floor(sheetIndex * count * timeInterval);
thumbnails.push({
url,
time,
width,
height,
index: globalIndex
index: sheetIndex * count
});
globalIndex++;
}
} else {
const WebVTTResp = await fetch(`${get(instanceStore)}${video.storyboards[2].url}`);
if (!WebVTTResp.ok) return [];
const thumbnailsSheets = await parseText(await WebVTTResp.text(), {
strict: true,
type: 'vtt'
});
const processedUrls = new Set<string>();
let index = 0;
thumbnailsSheets.cues.forEach((cue) => {
const urlParts = cue.text.split('#');
const cleanedUrl = urlParts[0];
if (!processedUrls.has(cleanedUrl)) {
const xywh = urlParts[1];
const xywhValues = xywh.split(',');
if (xywhValues.length !== 4) {
return [];
}
const [, , thumbWidth, thumbHeight] = xywhValues.map(Number);
processedUrls.add(cleanedUrl);
thumbnails.push({
url: cleanedUrl,
time: cue.startTime * 1000,
width: thumbWidth,
height: thumbHeight,
index
});
index++;
}
});
}
return thumbnails;
File diff suppressed because one or more lines are too long