feat(books): add an audiobook chapter editor

Add a Chapters tab to edit chapter timing and titles, look chapters up on Audible or in the ebook,
snap starts to pauses, keep a version history and write chapters into M4B, M4A and MP3 files. Add a
chapter health tool with a bulk Audible lookup queue. Editing needs Editor access to the book's
library. Chapter edits keep released iOS playback working by accepting a stale manifest revision
while the track layout is unchanged.

Ref #1120
This commit is contained in:
neon
2026-10-22 03:25:48 +00:00
parent 5ba5352e3e
commit d21c68d13c
180 changed files with 48279 additions and 299 deletions
+74
View File
@@ -401,6 +401,80 @@ export const E2E_SUITES = Object.freeze({
...SHARED_DB_AND_HELPER_PATHS,
],
},
"audiobook-chapter-revision": {
id: "audiobook-chapter-revision",
name: "Audiobook Chapter Revision",
timeout: 60,
lane: "full",
description: "Audiobook chapter edits keep released-client playback writes and locks working",
vitestTarget: "test/audiobook-chapter-revision.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/audiobook-chapter-revision-e2e-junit.xml`,
prepareDedicatedDatabase: true,
useDedicatedDatabase: true,
changedPaths: [
"server/src/modules/audiobook/**",
"server/src/modules/audiobook-chapters/**",
"server/src/modules/chapter-editor/**",
"server/src/modules/metadata/metadata.service.ts",
"server/src/modules/book/**",
"server/src/modules/book-metadata-lock/**",
"server/src/common/utils/audiobook-chapters.utils.ts",
"server/test/audiobook-chapter-revision.e2e-spec.ts",
"server/test/e2e/authorization-matrix/**",
".github/workflows/e2e-runner.yml",
"packages/types/src/audiobook.ts",
"packages/types/src/metadata-lock.ts",
...SHARED_DB_AND_HELPER_PATHS,
],
},
"audiobook-chapter-write": {
id: "audiobook-chapter-write",
name: "Audiobook Chapter Write",
timeout: 120,
lane: "full",
description: "Edited audiobook chapters are written into real audio files and read back unchanged",
vitestTarget: "test/audiobook-chapter-write.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/audiobook-chapter-write-e2e-junit.xml`,
prepareDedicatedDatabase: true,
useDedicatedDatabase: true,
changedPaths: [
"server/src/modules/chapter-editor/**",
"server/src/modules/file-write/**",
"server/src/modules/metadata/extractors/audio-chapter-merge.ts",
"server/src/common/utils/audiobook-chapters.utils.ts",
"server/test/audiobook-chapter-write.e2e-spec.ts",
"server/test/e2e/authorization-matrix/**",
".github/workflows/e2e-runner.yml",
"packages/types/src/audiobook.ts",
"packages/types/src/file-write.ts",
...SHARED_DB_AND_HELPER_PATHS,
],
},
"chapter-health": {
id: "chapter-health",
name: "Chapter Health",
timeout: 60,
lane: "full",
description: "Chapter health categories, library scoping, and the bulk Audible lookup queue and worker",
vitestTarget: "test/chapter-health.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/chapter-health-e2e-junit.xml`,
prepareDedicatedDatabase: true,
useDedicatedDatabase: true,
changedPaths: [
"server/src/modules/chapter-health/**",
"server/src/modules/chapter-editor/**",
"server/src/modules/audiobook-chapters/**",
"server/src/modules/book-metadata-lock/**",
"server/src/modules/library/**",
"server/src/common/utils/audiobook-chapters.utils.ts",
"server/test/chapter-health.e2e-spec.ts",
"server/test/e2e/authorization-matrix/**",
".github/workflows/e2e-runner.yml",
"packages/types/src/chapter-health.ts",
"packages/types/src/audiobook.ts",
...SHARED_DB_AND_HELPER_PATHS,
],
},
"dashboard-continue-listening": {
id: "dashboard-continue-listening",
name: "Dashboard Continue Listening",