diff --git a/dev/creating-a-service/index.html b/dev/creating-a-service/index.html index 6fb58759..414a0865 100644 --- a/dev/creating-a-service/index.html +++ b/dev/creating-a-service/index.html @@ -2334,10 +2334,11 @@ downloading, decryption, muxing, naming) is handled by the unshackle core.

the CLI. Service code is deliberately kept separate from the core so it can live in its own private repository.

-

Unshackle ships one reference service, EXAMPLE, at -unshackle/services/EXAMPLE/. It is a deliberately exhaustive, non-runnable -showcase of every framework feature in a single file. Read it alongside this -page; most snippets below are drawn from it.

+

unshackle ships two reference services. EXAMPLE, at +unshackle/services/EXAMPLE/, is a non-runnable showcase of every framework +feature in a single file. Read it alongside this page. Most snippets below come +from it. MUSIC_EXAMPLE, at unshackle/services/MUSIC_EXAMPLE/, is the same +kind of showcase for a music service. If your platform has songs, read that one too.


How a service is discovered

At startup unshackle scans every path in the directories.services config key @@ -2687,10 +2688,18 @@ service splits one episode into several videos, see Split episodes.

-
Song(id_, service, name, artist, album, track, disc, year,
-     language=None, data=None)
+
Song(id_, service, name, artist, album, track, disc=1, year=None,
+     language=None, data=None, ...)
 
-

Return an Album([...]) of songs.

+

Return an Album([...]) of songs. disc defaults to 1 and year is optional. +Optional metadata keywords such as album_artist, genre, isrc, label, +lyrics and artwork_url feed the tagger and the naming templates. Read the +class for the current list.

+

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.

@@ -3061,8 +3070,9 @@ flagged, banned, or downgraded.

  • URLs, user agents, and certificates live in config.yaml, read via self.config[...].
  • Multi-file services import their own modules relatively (from .helpers import x), so they work when loaded from a repo.
  • -

    Read unshackle/services/EXAMPLE/ end to end; it annotates every feature -touched above in one place.

    +

    Read unshackle/services/EXAMPLE/ end to end. It annotates every feature +touched above in one place. For a music service, read +unshackle/services/MUSIC_EXAMPLE/ too.

    diff --git a/dev/rest-api/endpoints/index.html b/dev/rest-api/endpoints/index.html index 19cce673..20dab35e 100644 --- a/dev/rest-api/endpoints/index.html +++ b/dev/rest-api/endpoints/index.html @@ -3230,6 +3230,14 @@

    Each serialized title carries type ("episode", "movie", or "other"), name, id, language, description, date, and cover_url. Episodes and movies add year; episodes additionally add series_title, season, and number.

    +
    +

    A song serializes as "other"

    +

    A music track has no type of its own. It is sent as "other", and the response +carries none of its music fields: no artist, no album, no track or disc number, and no +year. name holds the track name. To read the disc and track numbers of a release, +run the CLI with --list-titles. Selecting music tracks over the API still works, +through the wanted parameter.

    +

    These keys are sent only when the title carries them, so a title without them serializes exactly as before:

    @@ -3352,9 +3360,9 @@ is unaffected. See Split epis - + - + @@ -3568,9 +3576,9 @@ is unaffected. See Split epis - + - + diff --git a/dev/rest-api/quickstart/index.html b/dev/rest-api/quickstart/index.html index b2132b94..d1d3dcf1 100644 --- a/dev/rest-api/quickstart/index.html +++ b/dev/rest-api/quickstart/index.html @@ -2486,7 +2486,7 @@ - + diff --git a/dev/service-architecture/index.html b/dev/service-architecture/index.html index f10fc0c2..0f8bb05b 100644 --- a/dev/service-architecture/index.html +++ b/dev/service-architecture/index.html @@ -2618,7 +2618,8 @@ your service, in order:

    │ DRM license + decrypt → service license hooks → CDM → decrypt tool │ -Tracks.mux → mkvmerge → tagged .mkv +Tracks.mux → mkvmerge → tagged .mkv (movie / episode) + │ a Song skips the muxer and keeps its own container
    1. get_titles() returns a Titles collection: the movies, episodes, or songs @@ -2823,10 +2824,40 @@ An air_date is normalized to a date so the {date and a placeholder episode name like "Episode 4" is discarded automatically.

      -
      Song(id_, service, name, artist, album, track, disc, year,
      -     language=None, data=None)
      +
      Song(id_, service, name, artist, album, track, disc=1, year=None,
      +     language=None, data=None, ...)
       
      -

      name, artist, album, track, disc, and year are all required and validated.

      +

      name, artist, album, and track are required and validated. disc defaults to +1, and year is optional. A single therefore needs no disc, and an undated release +needs no year.

      +

      A Song also takes optional metadata keywords that the tagger and the naming templates +read: album_artist, release_type, total_tracks, total_discs, genre, +explicit, isrc, upc, copyright, label, lyrics, and artwork_url. Read the +class for the current list.

      +

      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.

      +

      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 +options, -a/--acodec and --abitrate, and its usual audio sorting. A movie or an +episode uses the same options. -q/--quality sets the video height only and does not +apply to music. Audio has no bit_depth or sample_rate field. Therefore bitrate +separates a hi-res FLAC from a CD FLAC, and the audio sort already ranks it.

      +

      A Song is an ordinary Title on the one generic download path. unshackle has no +music-specific service API and no separate music download loop. The +GROUP_AUDIO_DOWNLOADS flag, get_music_track_options and get_music_collection_label +no longer exist, and a music service does not need them.

      +
      +

      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.

      +
      +

      The MUSIC_EXAMPLE reference service, at unshackle/services/MUSIC_EXAMPLE/, shows +the full music surface.

      @@ -2900,8 +2931,8 @@ closed-caption descriptors. The relevant enums:

      Bitrate units

      -

      Both Video and Audio store bitrate in bytes per second (rounded up), even -though the string form displays kb/s.

      +

      Both Video and Audio store bitrate in bits per second (rounded up). The +string form displays kb/s, and --vbitrate and --abitrate match on kb/s.

      Audio

      Audio adds bitrate, channels, joc (Joint Object Coding, the Atmos indicator), diff --git a/getting-started/quickstart/index.html b/getting-started/quickstart/index.html index f00cf1eb..8268992b 100644 --- a/getting-started/quickstart/index.html +++ b/getting-started/quickstart/index.html @@ -2392,7 +2392,7 @@ keys through your CDM (and any key vaults), then decrypt, mux, and tag the resul

    - + diff --git a/guide/cli-reference/index.html b/guide/cli-reference/index.html index c54f3c5e..1b8642ae 100644 --- a/guide/cli-reference/index.html +++ b/guide/cli-reference/index.html @@ -3100,7 +3100,7 @@ configuration file.

    - + diff --git a/guide/downloading/index.html b/guide/downloading/index.html index 193b766e..4482687d 100644 --- a/guide/downloading/index.html +++ b/guide/downloading/index.html @@ -891,6 +891,17 @@ + + +
  • + + + + Music quality + + + +
  • @@ -1010,15 +1021,15 @@
  • - + - Selecting episodes + Selecting episodes and tracks -
  • - + - Selecting episodes + Selecting episodes and tracks -
  • -al / --a-lang: language for audio only (overrides -l for audio).
  • -
    English audio over the original video
    unshackle dl -al en EXAMPLE 81234567
    +
    English audio over the original video
    unshackle dl -al en EXAMPLE 81234567
     

    Excluding languages

    Put a - in front of a language to remove it from the selection. This works on -l, -vl, -al, -sl, and -fsl, in the same way as it works on -w / --wanted.

    -
    Every subtitle language except Spanish
    unshackle dl -sl all,-es EXAMPLE 81234567
    +
    Every subtitle language except Spanish
    unshackle dl -sl all,-es EXAMPLE 81234567
     

    The exclusions are subtracted from the languages the flag would otherwise select, and the order of the values does not matter. If you give only exclusions, the flag keeps its own default: -sl -es is the same as -sl all,-es, and -l -es is the same as -l orig,-es.

    -
    Only exclusions
    # every subtitle language except Spanish and French
    -unshackle dl -sl -es,-fr EXAMPLE 81234567
    -
    -# the original audio language, unless it is Spanish
    -unshackle dl -l -es EXAMPLE 81234567
    +
    Only exclusions
    # every subtitle language except Spanish and French
    +unshackle dl -sl -es,-fr EXAMPLE 81234567
    +
    +# the original audio language, unless it is Spanish
    +unshackle dl -l -es EXAMPLE 81234567
     

    -vl and -al have no default of their own: they cascade to -l. An override that names a language replaces -l for that stream type, exclusions included. An override that gives only exclusions adds them to the ones from -l.

    -
    Cascade
    # video keeps the Japanese track; only the audio drops Spanish
    -unshackle dl -l en,-es -vl ja EXAMPLE 81234567
    +
    Cascade
    # video keeps the Japanese track; only the audio drops Spanish
    +unshackle dl -l en,-es -vl ja EXAMPLE 81234567
     

    Rules for exclusion tokens

    @@ -3360,11 +3419,11 @@ only exclusions adds them to the ones from -l.

    Sort order

    -l and -sl select languages: naming some removes the rest. To keep every language but decide which ones come first, set a priority list in your configuration file:

    -
    unshackle.yaml
    audio:
    -  language_priority: [orig, en]
    -subtitle:
    -  group_by: language
    -  language_priority: [en, es, fr]
    +
    unshackle.yaml
    audio:
    +  language_priority: [orig, en]
    +subtitle:
    +  group_by: language
    +  language_priority: [en, es, fr]
     

    Subtitles then start with English, Spanish, and French. The languages you leave out follow alphabetically, after the title's original language. Nothing is removed. Audio works the @@ -3385,7 +3444,7 @@ keeps every subtitle language.

    Selecting subtitle languages

    -sl / --s-lang sets the wanted subtitle language(s). The default is all, meaning every available subtitle language is downloaded.

    -
    Only English and Spanish subtitles
    unshackle dl -sl en,es EXAMPLE 81234567
    +
    Only English and Spanish subtitles
    unshackle dl -sl en,es EXAMPLE 81234567
     

    To keep the default and drop one language, use the - prefix described in Excluding languages. -sl all,-es keeps every subtitle language @@ -3444,7 +3503,7 @@ download on the presence of a specific subtitle track.

    wantedstringstring / string[] noEpisode/season range (e.g. "S01E01-S01E03").Episode/season range (e.g. "S01E01-S01E03"), or a music track selector (e.g. "1-5", "1,3,7", or "2x3" for disc 2 track 3). A list of selectors is accepted too.
    season
    wantedstring[]string / string[] []Episode/season selectors. Accepts the part form, "S01E01.2", and the air-date form, "2026-08-11" or "2026-08-01:2026-08-31".Episode/season selectors, as a list or as one comma-separated string. Accepts the part form, "S01E01.2", and the air-date form, "2026-08-11" or "2026-08-01:2026-08-31". For a music release, a selector is a track number, "1-5" or "1,3,7", or "{disc}x{track}" such as "2x3".
    latest_episode
    wanted []Episodes to fetch, e.g. ["S01E01"]; empty means allEpisodes to fetch, e.g. ["S01E01"], or music tracks, e.g. ["1-5"]. Empty means all
    lang
    -w, --wantedWhich episodes, e.g. -w S01 or -w S01E01-S01E03.Which episodes, e.g. -w S01 or -w S01E01-S01E03. For music, which tracks, e.g. -w 1-5 or -w 2x3 for disc 2 track 3.
    -o, --output
    -w, --wantedWanted episodes, e.g. S01-S05,S07, S01E01-S02E03. Supports exclusions with a leading - (e.g. -S03). For a split episode, .N picks one part (S01E01.2), a range must stay inside the episode (S01E01.1-S01E01.3), and S01E01 on its own takes every part. For dated content, a token can also be an ISO air date (2026-08-11) or a date range with a colon (2026-08-01:2026-08-31).Wanted episodes, e.g. S01-S05,S07, S01E01-S02E03. Supports exclusions with a leading - (e.g. -S03). For a split episode, .N picks one part (S01E01.2), a range must stay inside the episode (S01E01.1-S01E01.3), and S01E01 on its own takes every part. For dated content, a token can also be an ISO air date (2026-08-11) or a date range with a colon (2026-08-01:2026-08-31). For a music release, a token is a track number (1-5, 1,3,7), or {disc}x{track} (2x3) when the release has more than one disc.
    --select-titles
    -
    Convert subtitles to SRT
    unshackle dl --sub-format srt EXAMPLE 81234567
    +
    Convert subtitles to SRT
    unshackle dl --sub-format srt EXAMPLE 81234567
     

    SDH stripping happens by default

    @@ -3453,55 +3512,96 @@ plain same-language subtitle, unshackle produces a stripped, non-SDH variant automatically. This behaviour is governed by the subtitle.strip_sdh config option (default on).

    -

    Selecting episodes

    -

    For series, several flags control which episodes are downloaded. By default, all -episodes are downloaded.

    +

    Selecting episodes and tracks

    +

    For series, several flags control which episodes are downloaded. The same flags select the +tracks of a music release. By default, all episodes and all tracks are downloaded.

    Wanted ranges

    -

    -w / --wanted accepts flexible season/episode ranges, comma-separated. Prefix a token -with - to exclude it.

    +

    -w / --wanted accepts season and episode ranges, comma-separated. Prefix a token +with - to exclude it. For a music release, the same option takes track numbers. Read +music tracks below.

    -
    unshackle dl -w S01 EXAMPLE 81234567
    -unshackle dl -w S01-S03 EXAMPLE 81234567
    -unshackle dl -w S01,S03,S05 EXAMPLE 81234567
    +
    unshackle dl -w S01 EXAMPLE 81234567
    +unshackle dl -w S01-S03 EXAMPLE 81234567
    +unshackle dl -w S01,S03,S05 EXAMPLE 81234567
     
    -
    unshackle dl -w S01E01 EXAMPLE 81234567
    -unshackle dl -w S01E01-S01E05 EXAMPLE 81234567
    -unshackle dl -w S01E01-S02E03 EXAMPLE 81234567
    +
    unshackle dl -w S01E01 EXAMPLE 81234567
    +unshackle dl -w S01E01-S01E05 EXAMPLE 81234567
    +unshackle dl -w S01E01-S02E03 EXAMPLE 81234567
     
    -
    # Seasons 1 through 5, but not season 3
    -unshackle dl -w S01-S05,-S03 EXAMPLE 81234567
    +
    # Seasons 1 through 5, but not season 3
    +unshackle dl -w S01-S05,-S03 EXAMPLE 81234567
     
    -
    # Part 2 of episode 1 only
    -unshackle dl -w S01E01.2 EXAMPLE 81234567
    -
    -# Parts 1 through 3 of episode 1
    -unshackle dl -w S01E01.1-S01E01.3 EXAMPLE 81234567
    -
    -# All of season 1 except episode 1 part 2
    -unshackle dl -w S01,-S01E01.2 EXAMPLE 81234567
    +
    # Part 2 of episode 1 only
    +unshackle dl -w S01E01.2 EXAMPLE 81234567
    +
    +# Parts 1 through 3 of episode 1
    +unshackle dl -w S01E01.1-S01E01.3 EXAMPLE 81234567
    +
    +# All of season 1 except episode 1 part 2
    +unshackle dl -w S01,-S01E01.2 EXAMPLE 81234567
     
    -
    # One day
    -unshackle dl -w 2026-08-11 EXAMPLE 81234567
    -
    -# A range of days, colon separated
    -unshackle dl -w 2026-08-01:2026-08-31 EXAMPLE 81234567
    -
    -# August, but not the 15th
    -unshackle dl -w 2026-08-01:2026-08-31,-2026-08-15 EXAMPLE 81234567
    +
    # One day
    +unshackle dl -w 2026-08-11 EXAMPLE 81234567
    +
    +# A range of days, colon separated
    +unshackle dl -w 2026-08-01:2026-08-31 EXAMPLE 81234567
    +
    +# August, but not the 15th
    +unshackle dl -w 2026-08-01:2026-08-31,-2026-08-15 EXAMPLE 81234567
     
    +

    Music tracks

    +

    -w selects the tracks of an album, EP, single, or playlist. Give a track by its number. +A release with more than one disc uses the {disc}x{track} form. A number on its own is a +track on disc 1, which is the number the track list shows for a single-disc release.

    +
    +
    +
    +
    # Track 3
    +unshackle dl -w 3 EXAMPLE 81234567
    +
    +# Tracks 1 through 5
    +unshackle dl -w 1-5 EXAMPLE 81234567
    +
    +# Tracks 1, 3 and 7
    +unshackle dl -w 1,3,7 EXAMPLE 81234567
    +
    +
    +
    +
    # Disc 2, track 3
    +unshackle dl -w 2x3 EXAMPLE 81234567
    +
    +# Disc 2, tracks 1 through 4
    +unshackle dl -w 2x1-2x4 EXAMPLE 81234567
    +
    +# Disc 1 track 3, through disc 2 track 2
    +unshackle dl -w 1x3-2x2 EXAMPLE 81234567
    +
    +
    +
    +
    # Tracks 1 through 8, but not track 4
    +unshackle dl -w 1-8,-4 EXAMPLE 81234567
    +
    +
    +
    +
    +
    +

    Read the track list first

    +

    --list-titles prints the release with its disc and track numbers, so you can see +which numbers to give -w.

    +

    Daily and date-based content

    Talk shows, news and sports have no official episode numbering, so unshackle names them by air date. An episode that carries an air date is written as Show.YYYY.MM.DD instead of @@ -3509,7 +3609,7 @@ by air date. An episode that carries an air date is written as Show.YYYY.M

    A service that only carries this kind of content sets DAILY = True on its class, and a service can set air_date on each episode itself. Add --daily to mark any other title as date-based:

    -
    unshackle dl --daily --tvdb 73871 --enrich EXAMPLE 81234567
    +
    unshackle dl --daily --tvdb 73871 --enrich EXAMPLE 81234567
     

    With --enrich and a TVDB ID, --daily fills in the air date of every episode that has none. An air date the service already set is kept. Dates before 1970 and dates in the @@ -3586,7 +3686,7 @@ the episode has no parts, rather than quietly handing you the whole episode. Run -

    Grab just the newest episode
    unshackle dl --latest-episode EXAMPLE 81234567
    +
    Grab just the newest episode
    unshackle dl --latest-episode EXAMPLE 81234567
     

    Including and excluding track types

    You can restrict a download to certain track categories, either positively (only these) @@ -3612,9 +3712,9 @@ or negatively (everything but these).

  • --skip-subtitle-errors: if a subtitle fails to download, skip it and continue rather than aborting the whole title. Video and audio failures remain fatal.
  • -
    Subtitles only
    unshackle dl -S -sl en EXAMPLE 81234567
    +
    Subtitles only
    unshackle dl -S -sl en EXAMPLE 81234567
     
    -
    Everything except chapters
    unshackle dl -nc EXAMPLE 81234567
    +
    Everything except chapters
    unshackle dl -nc EXAMPLE 81234567
     

    Note

    @@ -3646,7 +3746,7 @@ are always kept.

    -
    See the track selection without downloading
    unshackle dl -q 1080 -v H.265 -r HDR10 --list EXAMPLE 81234567
    +
    See the track selection without downloading
    unshackle dl -q 1080 -v H.265 -r HDR10 --list EXAMPLE 81234567
     

    Output and muxing

    Output location and folders

    @@ -3656,11 +3756,11 @@ are always kept.

  • --no-folder: do not create a per-show folder for TV downloads.
  • --no-source: remove the service source tag from the filename and path.
  • -
    Send this download somewhere specific
    unshackle dl -o ~/Videos/incoming EXAMPLE 81234567
    +
    Send this download somewhere specific
    unshackle dl -o ~/Videos/incoming EXAMPLE 81234567
     

    Muxing behaviour

    -

    By default, tracks are muxed into a single Matroska (.mkv) file with mkvmerge. These -flags change how the output is assembled:

    +

    By default, the tracks of a movie or an episode are muxed into a single Matroska (.mkv) +file with mkvmerge. These flags change how the output is assembled:

    @@ -3687,12 +3787,18 @@ flags change how the output is assembled:

    +
    +

    Music is never muxed

    +

    A music track skips the muxer. unshackle keeps the container that the service +delivered, such as .flac, .m4a, or .mp3, and writes the metadata into that file. +Read Music output files.

    +

    After the download

    --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. Nothing runs under --no-mux, since no output is -written.

    -
    Hand each finished file to an uploader
    unshackle dl --postscript "python /opt/upload.py {filepath} --service={service}" EXAMPLE 81234567
    +post_scripts config for that run. No success hook runs under --no-mux, because that
    +run writes no muxed output. A failure hook still runs if the download itself fails.

    +
    Hand each finished file to an uploader
    unshackle dl --postscript "python /opt/upload.py {filepath} --service={service}" EXAMPLE 81234567
     

    For hooks that persist across runs, for the season and run modes, for failure hooks and for the full variable list, see @@ -3702,14 +3808,14 @@ and for the full variable list, see

  • --tag: set the release group tag (overrides the configured tag).
  • --repack: add a REPACK tag to the output filename.
  • -
    Custom group tag and a REPACK label
    unshackle dl --tag MYGRP --repack EXAMPLE 81234567
    +
    Custom group tag and a REPACK label
    unshackle dl --tag MYGRP --repack EXAMPLE 81234567
     

    Proxies

    --proxy accepts a full proxy URI, a 2-letter country code (resolved through your configured proxy providers), or a provider:region form.

    -
    Proxy forms
    unshackle dl --proxy us EXAMPLE 81234567
    -unshackle dl --proxy nordvpn:ca EXAMPLE 81234567
    -unshackle dl --proxy 'http://user:pass@host:8080' EXAMPLE 81234567
    +
    Proxy forms
    unshackle dl --proxy us EXAMPLE 81234567
    +unshackle dl --proxy nordvpn:ca EXAMPLE 81234567
    +unshackle dl --proxy 'http://user:pass@host:8080' EXAMPLE 81234567
     

    Two related flags:

      @@ -3761,9 +3867,9 @@ configured proxy providers), or a provider:region form.

      -
      Two tracks at a time, eight threads each
      unshackle dl --downloads 2 --workers 8 EXAMPLE 81234567
      +
      Two tracks at a time, eight threads each
      unshackle dl --downloads 2 --workers 8 EXAMPLE 81234567
       
      -
      Space out a season download
      unshackle dl -w S01 --slow 30-60 EXAMPLE 81234567
      +
      Space out a season download
      unshackle dl -w S01 --slow 30-60 EXAMPLE 81234567
       

      Keys, vaults, and export

      By default, unshackle checks your key vaults first and only asks a CDM to license @@ -3772,12 +3878,12 @@ a key when the vault misses. You can force one side or the other:

    • --cdm-only: only use the CDM (skip vaults).
    • --vaults-only: only use key vaults (never license via the CDM); a missing key fails.
    -
    Fetch keys only, no download
    unshackle dl --skip-dl EXAMPLE 81234567
    +
    Fetch keys only, no download
    unshackle dl --skip-dl EXAMPLE 81234567
     

    --export writes a JSON file, into the configured exports directory, containing track info and the acquired content keys for each title. This is the format consumed by unshackle import to reconstruct a download later.

    -
    Export track info and keys
    unshackle dl --skip-dl --export EXAMPLE 81234567
    +
    Export track info and keys
    unshackle dl --skip-dl --export EXAMPLE 81234567
     

    Region is recorded only with a proxy

    @@ -3841,9 +3947,9 @@ lookups but keep the IDs you give, set -
    Force the right IMDb match, and keep the service's own naming
    unshackle dl --imdb tt1375666 EXAMPLE 81234567
    +
    Force the right IMDb match, and keep the service's own naming
    unshackle dl --imdb tt1375666 EXAMPLE 81234567
     
    -
    Force the match and take IMDb's title, year and language too
    unshackle dl --imdb tt1375666 --enrich EXAMPLE 81234567
    +
    Force the match and take IMDb's title, year and language too
    unshackle dl --imdb tt1375666 --enrich EXAMPLE 81234567
     

    What each metadata provider supplies

    Providers differ in what they answer with, so the fields --enrich can replace depend on @@ -3957,7 +4063,7 @@ wrong ID can pick the wrong audio track and not only write a wrong name. Without Futurama in TVDB's alternate (Streaming) order, for example, while TVDB's official order holds back four season-one episodes to the start of season two. --tvdb-order works out which order the service used, then renumbers the episodes into the order you asked for:

    -
    Download Futurama in DVD order
    unshackle dl --tvdb-order dvd EXAMPLE 81234567
    +
    Download Futurama in DVD order
    unshackle dl --tvdb-order dvd EXAMPLE 81234567
     

    Available orders are official (aired), dvd, absolute, alternate, and regional. Set tvdb_order in your config to apply one by default.

    @@ -4059,7 +4165,7 @@ authoritative list.

    --wanted -w -Episode/season range, or an air date. +Episode/season range, an air date, or a music track (1-5, 2x3). --daily diff --git a/guide/output-and-naming/index.html b/guide/output-and-naming/index.html index 1979652e..1aa80305 100644 --- a/guide/output-and-naming/index.html +++ b/guide/output-and-naming/index.html @@ -1002,6 +1002,34 @@
    + + +
  • + + + + Music output files + + + + + +
  • @@ -2301,6 +2329,34 @@ +
  • + +
  • + + + + Music output files + + + + + +
  • @@ -2432,14 +2488,14 @@

    This page explains where unshackle writes finished downloads and how it names them. You control naming with output templates (the filename) and folder templates (the directory the file lands in), using a set of {variable} placeholders that unshackle fills in from the media it just downloaded. It also covers muxing options, release-group tagging, Unicode filenames, and how to relocate unshackle's working directories.

    If you have not configured unshackle.yaml yet, read Configuration File first. Everything below lives in that file.

    How an output path is assembled

    -

    When a download finishes muxing, unshackle builds the final path from three pieces:

    +

    When a download finishes, unshackle builds the final path from three pieces:

    <output directory> / <folder template> / <filename template><extension>
     
    • Output directory: directories.downloads from your config, or whatever you pass to -o/--output on the command line for a single run.
    • Folder template: an optional per-title subfolder. TV episodes and music tracks are always placed in a folder; movies only get one if you define a movies folder template, or a single-string folder template that applies to all title kinds (see Folder templates).
    • Filename template: the output_template for the title kind (movie, series, or song).
    • -
    • Extension: chosen by the muxer: .mkv for video, .mka for audio-only, .mks for subtitle-only.
    • +
    • Extension: chosen by the muxer for a movie or an episode: .mkv for video, .mka for audio-only, .mks for subtitle-only. A music track is not muxed. It keeps the container the service delivered, such as .flac, .m4a, or .mp3. Read Music output files.

    A typical episode path

    @@ -2823,21 +2879,50 @@ For an episode with no on-screen name, {episode_name?} and its adja

    Fallback behavior:

    • A per-kind folder template wins if present; otherwise the single folder string is used; otherwise unshackle falls back to a built-in default.
    • -
    • Built-in defaults when no folder template is set: movies get no folder at all unless a movies folder template (or a single-string folder template) exists; series fall back to a folder derived from the series output template (stripping {episode}, {episode_name}, and collapsing {season_episode} down to {season}); music albums fall back to {artist} - {album} ({year}).
    • +
    • Built-in defaults when no folder template is set. Movies get no folder at all unless a movies folder template (or a single-string folder template) exists. Series fall back to a folder derived from the series output template (stripping {episode}, {episode_name}, and collapsing {season_episode} down to {season}). Music albums fall back to {artist} - {album} ({year}). If the song has no year, the folder name is only {artist} - {album}.

    Movies are flat by default

    Episodes and songs are always foldered. Movies are written directly into the output directory unless you define a movies folder template (or a single-string folder template, which folders every kind). Set one if you want each movie in its own directory.

    +

    Music output files

    +

    A music track does not go through the muxer. unshackle moves the downloaded audio file to +its final path. It keeps the container that the service delivered. A lossless download +arrives as a .flac file. An AAC download arrives as a .m4a file. unshackle writes no +.mkv file for music, and the muxing options below do not apply.

    +

    The songs output template names the file. The albums folder template names the folder +around it.

    +
    /home/user/Downloads/
    +  └─ The Artist - The Album (2024)/
    +     ├─ 01. First Track.flac
    +     └─ 02. Second Track.flac
    +
    +

    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.

    +

    Cover art is embedded when the service gives an artwork URL. unshackle downloads the image +and puts it in the file.

    +

    Lyrics are written 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 +FLAC. unshackle does not write synchronized lyrics (SYLT).

    +

    A metadata fault does not stop the run. The file is already in place, so unshackle writes a +warning and continues.

    +
    +

    --no-mux leaves music files untagged

    +

    --no-mux 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.

    +

    Muxing options

    -

    Muxing (combining video, audio, subtitle, chapter and attachment tracks into a single Matroska file with mkvmerge) is configured under the muxing key.

    -
    unshackle.yaml
    muxing:
    -  set_title: true
    -  merge_video: false
    -  merge_audio: true
    -  default_language:
    -    audio: en
    -    subtitle: en
    +

    Muxing (combining video, audio, subtitle, chapter and attachment tracks into a single Matroska file with mkvmerge) is configured under the muxing key. Only movies and episodes are muxed. Read Music output files for what a music download does instead.

    +
    unshackle.yaml
    muxing:
    +  set_title: true
    +  merge_video: false
    +  merge_audio: true
    +  default_language:
    +    audio: en
    +    subtitle: en
     
    @@ -2897,9 +2982,9 @@ It can be swapped per release with reference page for the exact list.

    -
    unshackle.yaml
    tag: "MYGROUP"
    -tag_group_name: true
    -tag_imdb_tmdb: true
    +
    unshackle.yaml
    tag: "MYGROUP"
    +tag_group_name: true
    +tag_imdb_tmdb: true
     
    @@ -2935,10 +3020,10 @@ cannot be matched. See the Matroska tagging spec requires: series/73871 for a show, movies/113 for a film.

    You can override the tag per run without editing config:

    -
    unshackle dl --tag OTHERGROUP EXAMPLE "..."
    +
    unshackle dl --tag OTHERGROUP EXAMPLE "..."
     

    The --repack flag adds the REPACK marker (surfaced through the {repack} variable) to the filename for that run:

    -
    unshackle dl --repack EXAMPLE "..."
    +
    unshackle dl --repack EXAMPLE "..."
     

    Empty tags collapse cleanly

    @@ -2946,7 +3031,7 @@ requires: series/73871 for a show, movies/113 for a fi

    Unicode filenames

    By default unshackle transliterates non-ASCII characters (Korean, Japanese, Chinese, accented Latin, etc.) into ASCII equivalents, which is safest for older tools, DDL, and P2P sharing. Set unicode_filenames to keep the native characters.

    -
    unshackle.yaml
    unicode_filenames: true
    +
    unshackle.yaml
    unicode_filenames: true
     
    @@ -2969,12 +3054,12 @@ requires: series/73871 for a show, movies/113 for a fi

    Regardless of this setting, a set of filesystem-unsafe and structural characters is always removed or replaced during sanitization (for example / and ; become &, and characters like \ * ! ? , ' " < > | $ # ~ are stripped). This is why you should never put path-unsafe characters directly in a template. unshackle warns about templates containing < > : " / \ | ? * at startup (folder templates are checked per path segment, so / is allowed there as a directory separator).

    Directory & filename configuration

    The directories key relocates unshackle's working folders. Only the folders listed here can be moved; core package paths are protected and silently ignored if you try to override them.

    -
    unshackle.yaml
    directories:
    -  downloads: "~/Media/unshackle"
    -  temp: "/mnt/fast/unshackle-temp"
    -  cache: "~/.cache/unshackle"
    -  cookies: "~/.config/unshackle/cookies"
    -  logs: "~/.config/unshackle/logs"
    +
    unshackle.yaml
    directories:
    +  downloads: "~/Media/unshackle"
    +  temp: "/mnt/fast/unshackle-temp"
    +  cache: "~/.cache/unshackle"
    +  cookies: "~/.config/unshackle/cookies"
    +  logs: "~/.config/unshackle/logs"
     
    @@ -3021,10 +3106,10 @@ requires: series/73871 for a show, movies/113 for a fi

    Paths support ~ expansion. The names app_dirs, core_dir, namespace_dir, user_configs, and data are protected and cannot be changed.

    Filename patterns

    The filenames key overrides the naming patterns for a handful of internal files. These are not your media output names. They are logs, temp files, and per-service configs.

    -
    unshackle.yaml
    filenames:
    -  log: "unshackle_{name}_{time}.log"
    -  chapters: "Chapters_{title}_{random}.txt"
    -  subtitle: "Subtitle_{id}_{language}.srt"
    +
    unshackle.yaml
    filenames:
    +  log: "unshackle_{name}_{time}.log"
    +  chapters: "Chapters_{title}_{random}.txt"
    +  subtitle: "Subtitle_{id}_{language}.srt"
     
    @@ -3069,7 +3154,7 @@ requires: series/73871 for a show, movies/113 for a fi

    Chapters naming

    When a title has chapters, they are written to an OGM chapters file (filenames.chapters) and passed to the muxer. Unnamed chapters use the chapter_fallback_name template:

    -
    unshackle.yaml
    chapter_fallback_name: "Chapter {i:02}"
    +
    unshackle.yaml
    chapter_fallback_name: "Chapter {i:02}"
     

    The fallback supports two placeholders: {i} (chapter number, starting at 1) and {j} (which increments only for unnamed chapters). Standard format directives like {i:02} are allowed.

    Removed & migrated options

    @@ -3083,61 +3168,61 @@ how the optional (?) variables appear and disappear.

    -
    output_template:
    -  movies: "{title}.{year}.{edition?}.{quality}.{source}.WEB-DL.{audio_full}.{atmos?}.{video}.{hdr?}-{tag?}"
    -  folder:
    -    movies: "{title} ({year})"
    +
    output_template:
    +  movies: "{title}.{year}.{edition?}.{quality}.{source}.WEB-DL.{audio_full}.{atmos?}.{video}.{hdr?}-{tag?}"
    +  folder:
    +    movies: "{title} ({year})"
     

    A 4K Atmos HDR download fills every placeholder:

    -
    The Movie (2024)/
    -  └─ The.Movie.2024.2160p.EXAMPLE.WEB-DL.DDP5.1.Atmos.H.265.HDR-TAG.mkv
    +
    The Movie (2024)/
    +  └─ The.Movie.2024.2160p.EXAMPLE.WEB-DL.DDP5.1.Atmos.H.265.HDR-TAG.mkv
     

    The same template on a plain 1080p SDR stereo download, where {edition?}, {atmos?} and {hdr?} collapse along with their dots:

    -
    The Movie (2024)/
    -  └─ The.Movie.2024.1080p.EXAMPLE.WEB-DL.AAC2.0.H.264-TAG.mkv
    +
    The Movie (2024)/
    +  └─ The.Movie.2024.1080p.EXAMPLE.WEB-DL.AAC2.0.H.264-TAG.mkv
     

    A director's cut fills {edition?}:

    -
    The Movie (2024)/
    -  └─ The.Movie.2024.Directors.Cut.2160p.EXAMPLE.WEB-DL.DDP5.1.Atmos.H.265.DV.HDR-TAG.mkv
    +
    The Movie (2024)/
    +  └─ The.Movie.2024.Directors.Cut.2160p.EXAMPLE.WEB-DL.DDP5.1.Atmos.H.265.DV.HDR-TAG.mkv
     
    -

    output_template:
    -  series: "{title}.{season_episode}.{episode_name?}.{quality}.{source}.WEB-DL.{audio_full}.{video}-{tag}"
    -  folder:
    -    series: "{title} ({year})/Season {season}"
    +

    output_template:
    +  series: "{title}.{season_episode}.{episode_name?}.{quality}.{source}.WEB-DL.{audio_full}.{video}-{tag}"
    +  folder:
    +    series: "{title} ({year})/Season {season}"
     
    -
    The Show (2023)/Season S02/
    -  └─ The.Show.S02E04.The.Reckoning.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG.mkv
    +
    The Show (2023)/Season S02/
    +  └─ The.Show.S02E04.The.Reckoning.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG.mkv
     

    unshackle decides the separator style once for the whole folder template, and every segment then follows it. Spaces outnumber dots in {title} ({year})/Season {season}, so Season {season} keeps its space even though it holds only one variable.

    When an episode has no on-screen name, {episode_name?} disappears cleanly:

    -
    The Show (2023)/Season S02/
    -  └─ The.Show.S02E05.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG.mkv
    +
    The Show (2023)/Season S02/
    +  └─ The.Show.S02E05.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG.mkv
     

    Place {dual?}, {multi?} and {dubbed?} next to each other. At most one of them is ever set (see dual_multi_mode), and the empty ones collapse:

    -
    output_template:
    -  movies: "{title}.{year}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{dubbed?}.{audio_full}.{video}-{tag}"
    +
    output_template:
    +  movies: "{title}.{year}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{dubbed?}.{audio_full}.{video}-{tag}"
     

    Original English audio plus a French dub (-l en,fr):

    -
    The.Movie.2024.1080p.EXAMPLE.WEB-DL.DUAL.DDP5.1.H.264-TAG.mkv
    +
    The.Movie.2024.1080p.EXAMPLE.WEB-DL.DUAL.DDP5.1.H.264-TAG.mkv
     

    Original plus several dubs (-l en,fr,de,es):

    -
    The.Movie.2024.1080p.EXAMPLE.WEB-DL.MULTi.DDP5.1.H.264-TAG.mkv
    +
    The.Movie.2024.1080p.EXAMPLE.WEB-DL.MULTi.DDP5.1.H.264-TAG.mkv
     

    Only a dub, original audio left out (-l de on an English-original title):

    -
    The.Movie.2024.1080p.EXAMPLE.WEB-DL.DUBBED.DDP5.1.H.264-TAG.mkv
    +
    The.Movie.2024.1080p.EXAMPLE.WEB-DL.DUBBED.DDP5.1.H.264-TAG.mkv
     

    Two dialects of one language (en-US + en-GB) count as a single language, so none of the three variables is set:

    -
    The.Movie.2024.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
    +
    The.Movie.2024.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
     

    For finer control (e.g. a SUBBED tag driven by subtitle languages), use {lang_tag?} with language_tags rules instead.

    @@ -3145,12 +3230,12 @@ with langu

    Dated content needs no special template. When an episode carries an air date, {season_episode} becomes the date automatically and {episode} and {year} clear:

    -
    output_template:
    -  series: "{title}.{season_episode}.{episode_name?}.{quality}.{source}.WEB-DL.{audio_full}.{video}-{tag}"
    +
    output_template:
    +  series: "{title}.{season_episode}.{episode_name?}.{quality}.{source}.WEB-DL.{audio_full}.{video}-{tag}"
     

    A normal episode and a dated one, same template:

    -
    The.Daily.Show.S28E101.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
    -The.Daily.Show.2024.06.01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
    +
    The.Daily.Show.S28E101.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
    +The.Daily.Show.2024.06.01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
     

    The date separator follows your template style: dots here, spaces if your series template uses spaces.

    @@ -3158,26 +3243,26 @@ template uses spaces.

    {year?} in the folder template is skipped when it would just repeat that year.

    -

    output_template:
    -  songs: "{track_number}. {title}"
    -  folder:
    -    albums: "{album_artist} - {album} ({year})"
    +

    output_template:
    +  songs: "{track_number}. {title}"
    +  folder:
    +    albums: "{album_artist} - {album} ({year})"
     
    -
    The Artist - The Album (2024)/
    -  └─ 01.Opening.Track.mka
    +
    The Artist - The Album (2024)/
    +  └─ 01.Opening.Track.mka
     

    In "{track_number}. {title}" dots and spaces are tied, and ties go to dot style; add more spaces between variables (as below) if you want space-separated names.

    A multi-disc album with explicit tracks, using more of the music variables. {disc} is empty on disc 1, so {disc?} and its separator only appear from disc 2 onward:

    -

    output_template:
    -  songs: "{disc?}-{track_number}. {title} {explicit?}"
    -  folder:
    -    albums: "{album_artist} - {album} ({year}) [{label?}]"
    +

    output_template:
    +  songs: "{disc?}-{track_number}. {title} {explicit?}"
    +  folder:
    +    albums: "{album_artist} - {album} ({year}) [{label?}]"
     
    -
    The Artist - The Album (2024) [The Label]/
    -  ├─ 01. Opening Track.mka
    -  └─ 02-03. Closing Track Explicit.mka
    +
    The Artist - The Album (2024) [The Label]/
    +  ├─ 01. Opening Track.mka
    +  └─ 02-03. Closing Track Explicit.mka
     

    diff --git a/objects.inv b/objects.inv index 1d17c7e8..1d020b93 100644 Binary files a/objects.inv and b/objects.inv differ diff --git a/reference/api/titles/index.html b/reference/api/titles/index.html index 0fd2a138..60e23be2 100644 --- a/reference/api/titles/index.html +++ b/reference/api/titles/index.html @@ -2018,6 +2018,17 @@