mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-15 05:25:12 +00:00
feat(music): pick the song container from the codec in the downloaded file
FFprobe sets the codec; DTS goes to .mka, PCM to .flac or .wav, AC-4/MPEG-H/DTS-UHD to MP4Box. The remux also cuts --sample, --no-mux no longer applies to a Song, and the tagger adds cover fallback, WebP, ARTISTS and ReplayGain. Song tag data now reaches a --remote client.
This commit is contained in:
@@ -370,9 +370,9 @@ Common constructor fields:
|
||||
|
||||
From `get_tracks()`, give one `Audio` track for each codec and bitrate you offer
|
||||
for the song. The framework then selects one with its usual audio options.
|
||||
A `Song` skips the muxer, so restore the real container suffix on the track path
|
||||
in `on_track_downloaded`. The **MUSIC_EXAMPLE** service shows the full music
|
||||
surface.
|
||||
A `Song` skips the muxer: the framework reads the codec from the downloaded file
|
||||
and gives the file its container, so a service does not rename the track path.
|
||||
The **MUSIC_EXAMPLE** service shows the full music surface.
|
||||
|
||||
!!! note "The ID must be unique and stable"
|
||||
`id_` must be truthy and, if it has a length, at least 4 characters, to
|
||||
|
||||
@@ -202,7 +202,9 @@ Each subclass adds its own required fields on top of `id_` and `service`:
|
||||
|
||||
The tagger also falls back to `Song.data` for fields the constructor did not get, such
|
||||
as the composer or a full release date. It reads the top level of that dict and a
|
||||
nested `metadata` sub-dict, with the nested keys winning, so either shape works.
|
||||
nested `metadata` sub-dict, with the nested keys winning, so either shape works. The keys
|
||||
that a remote client gets back are `MUSIC_DATA_KEYS` in `unshackle/core/titles/music.py`,
|
||||
for example `artists` (a list) and `replaygain_track_gain`.
|
||||
|
||||
From `get_tracks()`, a music service gives **one `Audio` track for each codec and
|
||||
bitrate it offers for a `Song`**. The framework then selects one with its usual audio
|
||||
@@ -216,13 +218,9 @@ Each subclass adds its own required fields on top of `id_` and `service`:
|
||||
`GROUP_AUDIO_DOWNLOADS` flag, `get_music_track_options` and `get_music_collection_label`
|
||||
no longer exist, and a music service does not need them.
|
||||
|
||||
!!! warning "Restore the container in `on_track_downloaded`"
|
||||
Every track downloads to a temp file named `Audio_<id>.mp4` whatever it holds, and
|
||||
the final move keeps that suffix. A `Song` skips the muxer, so its download **is**
|
||||
the delivered file, and the tagger picks its metadata format from the suffix.
|
||||
Rename the track path to the true container in `on_track_downloaded`, or a FLAC
|
||||
lands as an untaggable `.mp4`. The event fires twice, once before decryption and
|
||||
once at the end, so wait until `track.drm` is clear before you rename.
|
||||
Core picks the container of a `Song` from the codec in the downloaded file, so a music
|
||||
service does not rename the track path. Read
|
||||
[Music output files](../guide/output-and-naming.md#music-output-files).
|
||||
|
||||
The **MUSIC_EXAMPLE** reference service, at `unshackle/services/MUSIC_EXAMPLE/`, shows
|
||||
the full music surface.
|
||||
|
||||
@@ -268,7 +268,7 @@ download. For example, `dovi_tool` only matters if you download Dolby Vision vid
|
||||
| Tool | Binary looked up | Why it is needed |
|
||||
| --- | --- | --- |
|
||||
| Bento4 (`mp4decrypt`) | `mp4decrypt` | Alternative DRM decryptor to shaka-packager |
|
||||
| MP4Box (GPAC) | `MP4Box` | Muxing DTS:X Profile 2 (DTS-UHD) and MPEG-H audio, and 3D video |
|
||||
| MP4Box (GPAC) | `MP4Box` | Muxing DTS:X Profile 2 (DTS-UHD) and MPEG-H audio, and 3D video; remuxing AC-4, MPEG-H and DTS-UHD music |
|
||||
| dovi_tool | `dovi_tool` | Dolby Vision metadata handling |
|
||||
| HDR10Plus_tool | `hdr10plus_tool` | HDR10+ metadata handling |
|
||||
| SubtitleEdit | `seconv`, `SubtitleEdit` | Subtitle conversion (the `SeConv` CLI from 5.x) |
|
||||
@@ -280,7 +280,7 @@ download. For example, `dovi_tool` only matters if you download Dolby Vision vid
|
||||
| Docker | `docker` | Gluetun VPN proxy support |
|
||||
| Git | `git` | Fetching and updating remote service repositories |
|
||||
|
||||
!!! info "MP4Box is only used for DTS:X Profile 2, MPEG-H and 3D video"
|
||||
!!! info "MP4Box is only used for DTS:X Profile 2, MPEG-H, AC-4 music and 3D video"
|
||||
Titles with DTS:X Profile 2 (DTS-UHD) or MPEG-H audio, or a stereoscopic 3D video track, mux to
|
||||
MP4; everything else muxes to MKV with `mkvmerge`. Install with `apt install gpac`.
|
||||
|
||||
|
||||
@@ -865,8 +865,8 @@ By default, `mkvmerge` muxes the tracks of a movie or an episode into a single M
|
||||
window, or the whole file when the track is one file.
|
||||
|
||||
!!! note "Music is never muxed"
|
||||
A song skips the muxer. unshackle keeps the container that the service
|
||||
delivered, such as `.flac`, `.m4a`, or `.mp3`, and writes the metadata into that file.
|
||||
A song skips the muxer. unshackle remuxes it into the container of its codec,
|
||||
such as `.flac`, `.m4a`, or `.mp3`, and writes the metadata into that file.
|
||||
Read [Music output files](output-and-naming.md#music-output-files).
|
||||
|
||||
### After the download
|
||||
@@ -874,7 +874,8 @@ By default, `mkvmerge` muxes the tracks of a movie or an episode into a single M
|
||||
`--postscript "<command>"` runs your own command once per output file, with unshackle's
|
||||
metadata substituted into `{variable}` placeholders. It is repeatable, and it replaces the
|
||||
`post_scripts` config for that run. No `success` post-script operates under `--no-mux`, because
|
||||
that run writes no muxed output. A `failure` post-script still operates if the download fails.
|
||||
that run writes no muxed output; a song is the exception, because `--no-mux` does not apply to it.
|
||||
A `failure` post-script still operates if the download fails.
|
||||
|
||||
```shell title="Hand each finished file to an uploader"
|
||||
unshackle dl --postscript "python /opt/upload.py {filepath} --service={service}" EXAMPLE 81234567
|
||||
|
||||
@@ -262,22 +262,37 @@ The per-kind folder config keys are `movies`, `series`, `sports`, `songs`, and `
|
||||
|
||||
## Music output files
|
||||
|
||||
A song does not go through the muxer. Music services deliver audio in a fragmented MP4, which is
|
||||
not a music file. Its header states a length of zero, so a player stops after the first fragment.
|
||||
FLAC inside an MP4 is also not a FLAC stream. A rename cannot correct either fault, so unshackle
|
||||
remuxes each downloaded audio track into the container of its codec, then moves the file to its
|
||||
final path.
|
||||
A song does not go through the muxer. Music services deliver audio as a fragmented MP4, a WebM
|
||||
file or a plain audio file, and every download has a `.mp4` name. A fragmented MP4 header states a
|
||||
length of zero, so a player stops after the first fragment. A rename cannot correct this, so
|
||||
unshackle remuxes each downloaded audio track into the container of its codec, then moves the file
|
||||
to its final path.
|
||||
|
||||
FFprobe reads the codec from the downloaded file. The codec that the service declares is used only
|
||||
when FFprobe cannot read the file.
|
||||
|
||||
| Codec | Extension |
|
||||
|---|---|
|
||||
| FLAC | `.flac` |
|
||||
| PCM, integer, 24-bit or less | `.flac` |
|
||||
| PCM, more than 24-bit or floating point | `.wav` |
|
||||
| Opus | `.opus` |
|
||||
| Vorbis | `.ogg` |
|
||||
| MP3 | `.mp3` |
|
||||
| AAC, ALAC, and all other codecs | `.m4a` |
|
||||
| DTS, DTS-HD | `.mka` |
|
||||
| AAC, ALAC, Dolby Digital, Dolby Digital Plus, and all other codecs | `.m4a` |
|
||||
|
||||
unshackle re-encodes FLAC in this step. The audio does not change, but the new file states its
|
||||
true length. A plain copy cannot, because it keeps the source header and the zero in it.
|
||||
unshackle encodes FLAC and PCM to FLAC in this step. The audio does not change, but the new file
|
||||
states its true length. A plain copy cannot, because it keeps the source header and the zero in it.
|
||||
FLAC cannot hold floating-point PCM, and FFmpeg writes 32-bit FLAC only as an experimental feature,
|
||||
so that audio goes to `.wav`.
|
||||
|
||||
FFmpeg writes DTS into MP4 with the wrong codec entry, so DTS goes to Matroska. AC-4, MPEG-H and
|
||||
DTS:X Profile 2 (DTS-UHD) go through MP4Box, because FFmpeg cannot write them into MP4. MP4Box
|
||||
needs [GPAC](../getting-started/installation.md#recommended-and-optional-tools).
|
||||
|
||||
With `--sample`, the same step cuts the song to the sample window. If the window starts after the
|
||||
end of the song, unshackle shows a warning and keeps the full download.
|
||||
|
||||
unshackle writes no `.mkv` file for music, and the [muxing options](#muxing-options) below do not
|
||||
apply.
|
||||
@@ -295,13 +310,18 @@ around it.
|
||||
### Music metadata
|
||||
|
||||
After the file moves, unshackle writes the metadata into the audio file. It uses Vorbis
|
||||
comments for FLAC and Ogg, ID3 frames for MP3, and MP4 atoms for M4A. The tags hold the
|
||||
track title, artist, album artist, track and disc numbers, date, genre, ISRC, UPC,
|
||||
copyright, and record label. When `tag` and `tag_group_name` are set, unshackle also writes the group
|
||||
name into a `GROUP` tag.
|
||||
comments for FLAC and Ogg, ID3 frames for MP3 and WAV, MP4 atoms for M4A, and Matroska tags for
|
||||
MKA. The tags hold the track title, artist, album artist, track and disc numbers, date, genre,
|
||||
ISRC, UPC, copyright, and record label. When the service gives them, unshackle also writes each
|
||||
artist of a song with several artists into `ARTISTS`, and the ReplayGain values into
|
||||
`REPLAYGAIN_*` tags. An Opus file gets no `REPLAYGAIN_*` tags, because RFC 7845 advises against
|
||||
them in Opus files. When `tag` and `tag_group_name` are set, unshackle also writes the group name
|
||||
into a `GROUP` tag.
|
||||
|
||||
unshackle embeds cover art when the service gives an artwork URL. It downloads the image
|
||||
and puts it in the file.
|
||||
and puts it in the file. With no artwork URL, it uses the first image attachment of the song. It
|
||||
converts a WebP image to JPEG. It skips an image larger than 16 MiB, the FLAC limit, with a
|
||||
warning. An MKA file gets no cover art.
|
||||
|
||||
unshackle writes lyrics when the service supplies them: a `LYRICS` Vorbis comment for FLAC, a
|
||||
`USLT` frame for MP3, and a `©lyr` atom for M4A. Ogg uses the same `LYRICS` field as
|
||||
@@ -314,8 +334,8 @@ the timed lines from that file. The file also comes back to the client from a re
|
||||
A metadata fault does not stop the run. The file is already in place, so unshackle writes a
|
||||
warning and continues.
|
||||
|
||||
!!! warning "`--no-mux` leaves music files untagged"
|
||||
[`--no-mux`](cli-reference.md#output-muxing-files) moves each downloaded track file to the output path without the tagging step. A music file from such a run holds only the metadata that the service put in it.
|
||||
!!! note "`--no-mux` does not change music output"
|
||||
A song has no mux step, so [`--no-mux`](cli-reference.md#output-muxing-files) does not apply to it. The song gets the same file name, metadata, cover art and lyrics file as in a run without the flag.
|
||||
|
||||
## Muxing options
|
||||
|
||||
@@ -486,7 +506,7 @@ A few muxing behaviors are automatic and not configurable:
|
||||
- Descriptive audio gets the visually-impaired flag. SDH subtitles get the hearing-impaired flag. Forced subtitles get the forced flag.
|
||||
- unshackle writes the chapters from the title's chapter list (see [Chapters](#chapters-naming) below).
|
||||
|
||||
The `--no-mux` flag skips muxing entirely and writes the individual track files, each with a track-type suffix, into the same output/folder structure. A track that shaka-packager decrypts has its date set to 1970-01-01; other tracks keep the date of the source file.
|
||||
The `--no-mux` flag skips muxing entirely and writes the individual track files, each with a track-type suffix, into the same output/folder structure. An audio track in an MP4 container gets the `.m4a` extension. A track that shaka-packager decrypts has its date set to 1970-01-01; other tracks keep the date of the source file.
|
||||
|
||||
## Sample files
|
||||
|
||||
|
||||
@@ -328,7 +328,8 @@ credentials and `token=` / `api_key=` query parameters are also masked.
|
||||
## Limitations
|
||||
|
||||
- No `success` post-script operates under `--no-mux` or `--skip-dl`, because neither writes a
|
||||
muxed output. A `failure` post-script still runs under `--no-mux` if the download itself fails.
|
||||
muxed output. A song is the exception: `--no-mux` does not apply to it, so its post-scripts
|
||||
operate. A `failure` post-script still runs under `--no-mux` if the download itself fails.
|
||||
- `failure` post-scripts only operate in `file` mode.
|
||||
- There is no webhook sender. Call `curl` from a script instead.
|
||||
- Script output is not captured. Redirect it inside your own script if you want a log.
|
||||
|
||||
Reference in New Issue
Block a user