Repository navigation
feat(locallibrary): give a set's recording chapters, one per track - #273
Merged
Merged
Conversation
`sets export SET --format chapters` writes Podcasting 2.0 JSON chapters (version 1.2.0: the set's name and a startTime and title for each track with a start time, in the order they play), and `-o FILE.json` picks it. It's a publishing format, so a track marked unnamed or hidden by the profile is `ID - ID`, and a note names each track left out. `sets publish SET --upload` puts the same chapters in the file it sends, named as the set's record names them, `--hide` included, each ending where the next starts and the last where the recording ends. ffmpeg gets them as an FFMETADATA file (`-f ffmetadata -i FILE -map_chapters 1`, values escaped as its docs say), so the M4A it encodes has MP4 chapters and keeps the recording's own tags. An MP3 that goes up as it is goes up as a temporary copy whose ID3v2 tag gains a CHAP frame per chapter and a CTOC listing them (written with lofty, the other frames kept); if the tag tips it over the limit, the MP3 goes as it is with a warning. An AAC or M4A file that goes up as it is gets no chapters, since adding them would need ffmpeg. The plan lists the chapters and why any track has none, and reads the profile's choices along with the sets. sets::chapters builds the chapters for both: tracks without a start time, starting with an earlier track or after the recording ends get none.
bnb
previously approved these changes
Oct 29, 2026
bnb
approved these changes
Oct 29, 2026
botandbang
marked this pull request as ready for review
October 29, 2026 19:54
SizeCompared with main. KiB are 1,024 bytes.
|
Rust coverageFrom run 537. Both are held to the thresholds in .cargo/config.toml.
|
3 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Builds on the
--uploadchange. A set's recording now carries its tracklist as chapters, so podcast apps and audio players show what's playing and can jump between tracks.Added:
crates/locallibrary/src/sets/chapters.rsturns a set's tracks into chapters: one for each track with a start time, sorted by start, each ending where the next begins and the last where the recording ends. A track without a start time, starting at the same second as an earlier track, or starting after the recording ends gets no chapter, and a note says which and why.sets export SET --format chapterswrites Podcasting 2.0 JSON chapters (version 1.2.0, with the set's name astitle).-o FILE.jsonpicks the format from the extension. As a format for publishing, it names a track marked unnamed or hidden by the profileID - ID.sets publish SET --uploadputs the same chapters in the file it sends, naming tracks the way the set's record does (--hideincluded). When ffmpeg encodes the recording, it's given the chapters as an FFMETADATA file (-f ffmetadata -i FILE -map_chapters 1, values escaped as ffmpeg's docs require), so the M4A has MP4 chapters and keeps the recording's own tags. An MP3 that goes up as it is goes up as a temporary copy whose ID3v2 tag gains a CHAP frame per chapter and a CTOC listing them in order, written with lofty with the tag's other frames kept; the copy is deleted after the upload, and the MP3 itself isn't touched. If the tag would push the file over the size limit, the MP3 goes up as it is, with a warning. An AAC or M4A file that goes up as it is gets no chapters, since adding them would need ffmpeg.chaptersin its JSON, aChaptersline in the text) and notes the tracks that have none.Changed:
SetUploadRequesttakeshide. The upload plan also watches the profile's preferences, since chapter names depend on them.sets::export::shown_namenames a track or writesID - ID, shared by the export and the upload.SetFormatgainschapters, soapps/locallibrary/docs/rpc/is regenerated.apps/locallibrary/docs/sets.md,apps/locallibrary/docs/commands.md, thesets exporthelp andapps/locallibrary/CHANGELOG.mddescribe the format and the chapters in uploads.How it was tested
sets::chaptershas a unit test and a Hegel property over drawn tracklists and lengths: chapters are ordered and don't overlap, each ends at the next start or the recording's end, each timed track before the end has a chapter named for the first track at that start, and every other track is in a note.sets::exportchecks the exact JSON for a set, including a track left out without a start time, and for an empty set.publish::recordingchecks the exact FFMETADATA output with every escape, and a Hegel property that escaping round-trips. The stand-in ffmpeg test draws chapters: ffmpeg gets the metadata file with-map_chapters 1, an MP3 that goes up as it is goes as a copy whose ID3 chapters read back correctly with the same audio after the tag, and a copy pushed over the limit falls back to the original with a warning.crates/locallibrary/tests/it/publish/recordings.rsuploads an MP3 to the mock data server with a hidden track and reads the chapters back from the uploaded blob.--hideincluded), ffmpeg's arguments, and thechaptersformat and.jsoninprops_cli_sets.rs.ffprobe -show_chaptersread the expected chapters, titles and tags from both.-D warnings, nextest for locallibrary (2140) and locallibrary-cli (1071).Checklist
apps/locallibrary/CHANGELOG.mdhas a line under Unreleased, if people using it will noticedocs/and--helpstill say what the code does (apps/locallibrary/docs/rpc/too, for a sidecar change)