From 6c3bc4d789c34b8ea2c8e890095d527c799a1edf Mon Sep 17 00:00:00 2001 From: lenafx Date: Thu, 1 Jan 2026 18:16:18 +0100 Subject: [PATCH] fixed video chapters when downloading an anime episode --- desktop/src/api/local/download.service.ts | 47 +++++++++++++++++++++-- docker/src/api/local/download.service.ts | 47 +++++++++++++++++++++-- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/desktop/src/api/local/download.service.ts b/desktop/src/api/local/download.service.ts index b17d14c..72e5b28 100644 --- a/desktop/src/api/local/download.service.ts +++ b/desktop/src/api/local/download.service.ts @@ -175,14 +175,53 @@ export async function downloadAnimeEpisode(params: AnimeDownloadParams) { subFiles.forEach(f => args.push('-i', f)); let chaptersInputIndex = -1; + if (chapters?.length) { const meta = path.join(tempDir, 'chapters.txt'); - let txt = ';FFMETADATA1\n'; - for (const c of chapters) { - txt += `[CHAPTER]\nTIMEBASE=1/1000\nSTART=${c.start_time * 1000 | 0}\nEND=${c.end_time * 1000 | 0}\ntitle=${c.title}\n`; + + const sorted = [...chapters].sort((a, b) => a.start_time - b.start_time); + const lines: string[] = [';FFMETADATA1']; + + for (let i = 0; i < sorted.length; i++) { + const c = sorted[i]; + + const start = Math.floor(c.start_time * 1000); + const end = Math.floor(c.end_time * 1000); + const title = (c.title || 'chapter').toUpperCase(); + + lines.push( + '[CHAPTER]', + 'TIMEBASE=1/1000', + `START=${start}`, + `END=${end}`, + `title=${title}` + ); + + if (i < sorted.length - 1) { + const nextStart = Math.floor(sorted[i + 1].start_time * 1000); + if (nextStart - end > 1000) { + lines.push( + '[CHAPTER]', + 'TIMEBASE=1/1000', + `START=${end}`, + `END=${nextStart}`, + 'title=Episode' + ); + } + } else { + lines.push( + '[CHAPTER]', + 'TIMEBASE=1/1000', + `START=${end}`, + 'title=Episode' + ); + } } - fs.writeFileSync(meta, txt); + + fs.writeFileSync(meta, lines.join('\n')); args.push('-i', meta); + + // índice correcto del metadata input chaptersInputIndex = 1 + audioInputs.length + subFiles.length; } diff --git a/docker/src/api/local/download.service.ts b/docker/src/api/local/download.service.ts index b17d14c..72e5b28 100644 --- a/docker/src/api/local/download.service.ts +++ b/docker/src/api/local/download.service.ts @@ -175,14 +175,53 @@ export async function downloadAnimeEpisode(params: AnimeDownloadParams) { subFiles.forEach(f => args.push('-i', f)); let chaptersInputIndex = -1; + if (chapters?.length) { const meta = path.join(tempDir, 'chapters.txt'); - let txt = ';FFMETADATA1\n'; - for (const c of chapters) { - txt += `[CHAPTER]\nTIMEBASE=1/1000\nSTART=${c.start_time * 1000 | 0}\nEND=${c.end_time * 1000 | 0}\ntitle=${c.title}\n`; + + const sorted = [...chapters].sort((a, b) => a.start_time - b.start_time); + const lines: string[] = [';FFMETADATA1']; + + for (let i = 0; i < sorted.length; i++) { + const c = sorted[i]; + + const start = Math.floor(c.start_time * 1000); + const end = Math.floor(c.end_time * 1000); + const title = (c.title || 'chapter').toUpperCase(); + + lines.push( + '[CHAPTER]', + 'TIMEBASE=1/1000', + `START=${start}`, + `END=${end}`, + `title=${title}` + ); + + if (i < sorted.length - 1) { + const nextStart = Math.floor(sorted[i + 1].start_time * 1000); + if (nextStart - end > 1000) { + lines.push( + '[CHAPTER]', + 'TIMEBASE=1/1000', + `START=${end}`, + `END=${nextStart}`, + 'title=Episode' + ); + } + } else { + lines.push( + '[CHAPTER]', + 'TIMEBASE=1/1000', + `START=${end}`, + 'title=Episode' + ); + } } - fs.writeFileSync(meta, txt); + + fs.writeFileSync(meta, lines.join('\n')); args.push('-i', meta); + + // índice correcto del metadata input chaptersInputIndex = 1 + audioInputs.length + subFiles.length; }