Skip to content

feat(audio): embed cover image in audiobook files#197

Merged
neonsolstice merged 3 commits into
bookorbit:mainfrom
chrismansell26:BO-145-embed-audiobook-cover
Jun 2, 2026
Merged

feat(audio): embed cover image in audiobook files#197
neonsolstice merged 3 commits into
bookorbit:mainfrom
chrismansell26:BO-145-embed-audiobook-cover

Conversation

@chrismansell26

@chrismansell26 chrismansell26 commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

This PR introduces the ability to embed cover image files directly into audiobook files.

Key additions and features:

  • Adds an AudioCoverEmbedder to write cover art bytes to audio files using ffmpeg.
  • Implements specific AudioFormatWriter implementations to support M4B, M4A, MP3, and FLAC audio formats.
  • Updates the library creation/edit UI (LibraryCreatorFileWrite) to include new settings for enabling audio file writes.
  • Updates the database schema and introduces a new migration (0012_add_audio_file_write_settings.sql) to persist audio file write settings for libraries.
  • Integrates the new audio format writers into the existing file-write module and service architecture.

Closes #145

How did you test this?

  • Added comprehensive unit tests for AudioCoverEmbedder and all supported AudioFormatWriter subclasses.
  • Tested LibraryCreatorFileWrite UI changes to ensure audio settings toggle correctly.
  • Confirmed that lint and tests pass locally.
  • Spun up the app locally and manually tested updating an audiobook's cover to verify ffmpeg successfully embeds the image into the target files without corrupting metadata.

Screenshots

Screenshot 2026-06-02 at 8 44 07 AM

Anything non-obvious in the diff?

  • Included a database migration (0012_add_audio_file_write_settings.sql) and a snapshot update to handle the new audio write settings per library.
  • We rely on ffmpeg under the hood (execFile) for safe manipulation of audio metadata across multiple varying formats. Tests mock this interaction to verify arguments.

Checklist

  • I've read through my own diff
  • This PR was discussed in an issue and has maintainer approval (for new features)
  • Lint and tests pass locally
  • No unintended files included (build artifacts, .env, personal configs)

Summary by CodeRabbit

  • New Features
    • Audio file writing: embed cover art into M4B/M4A/MP3/FLAC audio files during library operations.
    • Library-level audio settings: per-library toggle and max-file-size control with sensible defaults; UI controls added to the library creation modal and file-write section.
  • Tests
    • Expanded test coverage for audio write behavior, validation, and UI interactions.

- Add audio cover embedder
- Implement format writers for M4B, M4A, MP3, and FLAC using ffmpeg
- Add library settings for audio file writes

Closes bookorbit#145
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: af5fbf6a-b9df-429c-800c-8c728426c190

📥 Commits

Reviewing files that changed from the base of the PR and between 94c8a01 and 0c49e76.

📒 Files selected for processing (7)
  • client/src/features/library/components/LibraryCreatorFileWrite.vue
  • client/src/features/library/components/__tests__/LibraryCreatorFileWrite.spec.ts
  • server/src/db/migrations/0012_add_audio_file_write_settings.sql
  • server/src/db/migrations/meta/0012_snapshot.json
  • server/src/db/schema/libraries.ts
  • server/src/modules/file-write/formats/audio/audio-cover-embedder.test.ts
  • server/src/modules/file-write/formats/audio/audio-cover-embedder.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/src/modules/file-write/formats/audio/audio-cover-embedder.ts
  • server/src/modules/file-write/formats/audio/audio-cover-embedder.test.ts

📝 Walkthrough

Walkthrough

Adds audio cover embedding end-to-end: DB fields and migration, DTOs and service defaults, audio format writers and ffmpeg-based embedder, refactored file-write service for multi-target writes, client UI for audio settings, and comprehensive tests.

Changes

Audio cover embedding for audiobooks

Layer / File(s) Summary
Type definitions and database schema
packages/types/src/library.ts, server/src/db/migrations/0012_add_audio_file_write_settings.sql, server/src/db/migrations/meta/_journal.json, server/src/db/schema/libraries.ts
Library type gains fileWriteAudioEnabled and fileWriteAudioMaxFileSizeMb; migration and schema add corresponding columns with defaults and a minimum-size check.
Library DTOs, validation and service defaults
server/src/modules/library/dto/create-library.dto.ts, server/src/modules/library/dto/update-library.dto.ts, server/src/modules/library/library.dto.test.ts, server/src/modules/library/library.service.ts, server/src/modules/library/library.service.test.ts
Create/Update DTOs accept optional audio write settings (validated); service inserts audio defaults when creating libraries; tests cover DTO validation and service behavior.
File-write constants, module wiring, repository
server/src/modules/file-write/file-write.constants.ts, server/src/modules/file-write/file-write.module.ts, server/src/modules/file-write/file-write.module.test.ts, server/src/modules/file-write/file-write.repository.ts, server/src/modules/file-write/file-write.repository.test.ts
Adds audio format constants and AUDIO_WRITE_FORMATS; module registers AudioCoverEmbedder and audio writers; repository queries include audio fields and a method to list book files.
AudioCoverEmbedder (ffmpeg) and tests
server/src/modules/file-write/formats/audio/audio-cover-embedder.ts, server/src/modules/file-write/formats/audio/audio-cover-embedder.test.ts
Implements JPEG normalization, ffmpeg argument construction and invocation, atomic replacement, cleanup, and MP3-specific metadata handling; tests cover success, failure, PATH fallback, args, and cleanup behavior.
Format-specific audio writers and tests
server/src/modules/file-write/formats/audio/audio-format-writer.ts, server/src/modules/file-write/formats/audio/audio-format-writer.test.ts
AudioFormatWriter implements write/skip/dryRun semantics; four injectable subclasses target specific formats and are tested for control flow and format identity.
FileWriteService multi-target refactor & tests
server/src/modules/file-write/file-write.service.ts, server/src/modules/file-write/file-write.service.test.ts
writeToFile refactored to resolve multiple targets, compute per-target skips, iterate and aggregate results, and update hashes/logs; adds helpers and extensive audio-focused tests.
Client UI, composable state, and component tests
client/src/features/library/components/LibraryCreatorFileWrite.vue, client/src/features/library/components/LibraryCreatorModal.vue, client/src/features/library/composables/useLibraryCreator.ts, client/src/features/library/composables/__tests__/useLibraryCreator.spec.ts, client/src/features/library/components/__tests__/LibraryCreatorFileWrite.spec.ts, client/src/features/settings/__tests__/LibrariesSettings.spec.ts
Adds audio toggle and max-size input to file-write UI, propagates props/emits through modal, includes composable defaults/hydration for audio fields, and updates tests/fixtures to validate behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FileWriteService
  participant AudioCoverEmbedder
  participant FFmpeg
  participant DB
  Client->>FileWriteService: request writeToFile(bookId)
  FileWriteService->>DB: findFilesForBook(bookId) / findLibraryWriteSettingsForBook
  FileWriteService->>FileWriteService: resolveWriteTargets (primary + audio tracks)
  FileWriteService->>AudioCoverEmbedder: embedCover(tempAudioPath, coverBytes, format)
  AudioCoverEmbedder->>FFmpeg: exec ffmpeg args
  FFmpeg-->>AudioCoverEmbedder: exit status
  AudioCoverEmbedder-->>FileWriteService: success / error
  FileWriteService->>DB: insertTargetLog / updateTargetHash / updateLastWrittenAt
  FileWriteService-->>Client: aggregated WriteResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

"🐰
I hop with joy through ffmpeg's song,
Embedding covers where they belong;
m4b, m4a, mp3, flac — a tidy crew,
Library art and players now match too. 🥕"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(audio): embed cover image in audiobook files' directly reflects the main feature introduced: audio cover embedding functionality across multiple audio formats.
Description check ✅ Passed The PR description comprehensively covers what the PR does, testing approach, and includes manual verification. All template sections are completed with meaningful content.
Linked Issues check ✅ Passed The PR successfully implements all objectives from issue #145: embedding cover images in audiobook files (M4B, M4A, MP3, FLAC formats) with customizable settings, UI controls, and comprehensive test coverage for consistency between stored metadata and playback clients.
Out of Scope Changes check ✅ Passed All changes directly support the core objective of audio cover embedding: database schema updates, UI components for audio settings, ffmpeg-based cover embedder, format-specific writers, service integration, and comprehensive tests. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added server Changes affecting server-side code, APIs, or backend behavior. client Changes affecting the client application or frontend behavior. packages labels Jun 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/src/features/library/components/LibraryCreatorFileWrite.vue`:
- Around line 245-275: The audio embedding controls (fileWriteAudioEnabled
toggle and its Max file size input) must be gated by the cover-write setting to
avoid a no-op configuration; update the template so the audio toggle and the
conditional size input are only visible/usable when fileWriteWriteCover is true
— e.g. wrap the entire audio block or the button+input in a
v-if="fileWriteWriteCover" (or disable the toggle via
:disabled="!fileWriteWriteCover" and similarly disable the size input) and
ensure you reference the existing symbols fileWriteAudioEnabled,
handleAudioToggle, fileWriteAudioMaxFileSizeMb, and onAudioMaxSizeInput so
behavior is preserved while preventing enabling audio when fileWriteWriteCover
is off.

In `@server/src/db/migrations/0012_add_audio_file_write_settings.sql`:
- Around line 1-2: The migration currently adds
libraries.file_write_audio_enabled with DEFAULT true NOT NULL which backfills
existing rows to enabled; change that column's default to DEFAULT false NOT NULL
so existing libraries are not auto-opted-in (keep the
file_write_audio_max_file_size_mb addition as-is), and instead ensure any
desired default-for-new-libraries behavior is implemented in the library
creation flow/UI rather than this migration.

In `@server/src/modules/file-write/formats/audio/audio-cover-embedder.ts`:
- Line 25: The execFile call invoking resolveFfmpegPath() with buildFfmpegArgs
currently only sets maxBuffer and can hang; add a timeout option (e.g., 60000
ms) to the options object passed to execFile so the child process is killed
after the timeout and the promise rejects; update the call that uses
FFMPEG_OUTPUT_MAX_BUFFER_BYTES to pass { maxBuffer:
FFMPEG_OUTPUT_MAX_BUFFER_BYTES, timeout: 60000 } (optionally include killSignal
if desired) so ffmpeg won't block indefinitely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 991a2df7-b21c-4ae2-bd20-38dcff5dd8dc

📥 Commits

Reviewing files that changed from the base of the PR and between 28ff196 and 94c8a01.

📒 Files selected for processing (27)
  • client/src/features/library/components/LibraryCreatorFileWrite.vue
  • client/src/features/library/components/LibraryCreatorModal.vue
  • client/src/features/library/components/__tests__/LibraryCreatorFileWrite.spec.ts
  • client/src/features/library/composables/__tests__/useLibraryCreator.spec.ts
  • client/src/features/library/composables/useLibraryCreator.ts
  • client/src/features/settings/__tests__/LibrariesSettings.spec.ts
  • packages/types/src/library.ts
  • server/src/db/migrations/0012_add_audio_file_write_settings.sql
  • server/src/db/migrations/meta/0012_snapshot.json
  • server/src/db/migrations/meta/_journal.json
  • server/src/db/schema/libraries.ts
  • server/src/modules/file-write/file-write.constants.ts
  • server/src/modules/file-write/file-write.module.test.ts
  • server/src/modules/file-write/file-write.module.ts
  • server/src/modules/file-write/file-write.repository.test.ts
  • server/src/modules/file-write/file-write.repository.ts
  • server/src/modules/file-write/file-write.service.test.ts
  • server/src/modules/file-write/file-write.service.ts
  • server/src/modules/file-write/formats/audio/audio-cover-embedder.test.ts
  • server/src/modules/file-write/formats/audio/audio-cover-embedder.ts
  • server/src/modules/file-write/formats/audio/audio-format-writer.test.ts
  • server/src/modules/file-write/formats/audio/audio-format-writer.ts
  • server/src/modules/library/dto/create-library.dto.ts
  • server/src/modules/library/dto/update-library.dto.ts
  • server/src/modules/library/library.dto.test.ts
  • server/src/modules/library/library.service.test.ts
  • server/src/modules/library/library.service.ts

Comment thread client/src/features/library/components/LibraryCreatorFileWrite.vue Outdated
Comment thread server/src/db/migrations/0012_add_audio_file_write_settings.sql Outdated
Comment thread server/src/modules/file-write/formats/audio/audio-cover-embedder.ts Outdated
chrismansell26 and others added 2 commits June 2, 2026 15:08
Change the default value of file_write_audio_enabled from true to false in the database schema and migrations. Automatically disable audio writing in the UI if cover writing is toggled off. Add a 60-second timeout to the ffmpeg child process to prevent hanging when embedding covers.
@codecov

codecov Bot commented Jun 2, 2026

Copy link
Copy Markdown

@neonsolstice
neonsolstice merged commit 935fce9 into bookorbit:main Jun 2, 2026
42 of 46 checks passed
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.9.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions github-actions Bot added the released Issue or PR is included in a released version. label Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client Changes affecting the client application or frontend behavior. packages released Issue or PR is included in a released version. server Changes affecting server-side code, APIs, or backend behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] embed cover image in audiobook

2 participants