mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
feat(titles): add optional Episode.part, selected with -w s1e1.2
Opt-in and additive: an episode without a part behaves exactly as before.
This commit is contained in:
@@ -304,11 +304,14 @@ Common constructor fields:
|
||||
|
||||
```python
|
||||
Episode(id_, service, title, season, number, name=None, year=None,
|
||||
language=None, data=None, description=None, air_date=None)
|
||||
language=None, data=None, description=None, air_date=None,
|
||||
part=None)
|
||||
```
|
||||
|
||||
Pass `air_date` for daily/sports content to name by date instead of
|
||||
`SxxExx`. Return a `Series([...])` of episodes.
|
||||
`SxxExx`. Return a `Series([...])` of episodes. Pass `part` only when the
|
||||
service splits one episode into several videos, see
|
||||
[Split episodes](#split-episodes).
|
||||
|
||||
=== "Song"
|
||||
|
||||
@@ -328,6 +331,52 @@ Common constructor fields:
|
||||
framework path to reuse cached results and gracefully fall back to stale data
|
||||
when the API errors. It also applies the per-service `title_map` config.
|
||||
|
||||
#### Split episodes {#split-episodes}
|
||||
|
||||
Some services split one logical episode into several separately playable videos, for
|
||||
example a variety show that ships episode 1 as *Episode 1 (Part 1)*, *(Part 2)* and
|
||||
*(Part 3)*. `Episode.number` stays an `int`. The optional `part` carries the index.
|
||||
|
||||
```python
|
||||
Episode(..., season=1, number=1, part=1, name="The Reckoning")
|
||||
Episode(..., season=1, number=1, part=2, name="The Reckoning")
|
||||
```
|
||||
|
||||
1. `part` counts from 1, contiguous. `part=0` is rejected, and `-w` can only address
|
||||
parts up to 99.
|
||||
2. Every part of one episode shares the same `season` and `number`.
|
||||
3. Each part is a separate `Episode` with its own unique `id_`.
|
||||
4. Unsplit episode: leave `part` unset. Never pass `part=1` for a whole episode.
|
||||
5. Do not put the part in `name` or `number`. Pass a clean `name` (or `None`) and set
|
||||
`part`.
|
||||
6. `part` is a split *episode*, not a split *season*. A service listing *Season 1 Part 2*
|
||||
describes a group of episodes. Keep numbering those in sequence, the way the shipped
|
||||
services do.
|
||||
|
||||
`part` accepts an `int` or a digit string. A value that is not an integer raises
|
||||
`TypeError`. A value of `0` or less raises `ValueError`.
|
||||
|
||||
!!! warning "The framework never infers a part from the episode name"
|
||||
Rule 6 is the reason. Shipped services already use the word "Part" to mean a *season*
|
||||
part, so name-sniffing would misread every one of them. The part has to arrive
|
||||
structurally, as the `part` argument.
|
||||
|
||||
!!! tip "Clean the part out of the name"
|
||||
`Episode` discards any `name` that starts with `Episode <number>`, so a raw
|
||||
`"Episode 1 (Part 2): The Reckoning"` leaves you with no episode name at all. Strip
|
||||
the prefix before you pass it, or pass `None`.
|
||||
|
||||
Once `part` is set, the filename gains a part token (`S01E01.Part.2`, see
|
||||
[Output & naming](../guide/output-and-naming.md#split-episodes)), `-w S01E01.2` selects a
|
||||
single part while a bare `-w S01E01` selects them all, and the `--list-titles` tree labels
|
||||
the episode `01.2`. The REST API reports it as an extra `part` key on the serialized
|
||||
title, described in [Endpoints](rest-api/endpoints.md#post-apilist-titles).
|
||||
|
||||
!!! note "Opt-in and additive"
|
||||
Leaving `part` unset costs you nothing: filenames, sort order, `-w` matching and the
|
||||
API JSON are identical to a plain episode, so a service that does not split episodes
|
||||
has nothing to adopt.
|
||||
|
||||
### `get_tracks`: required
|
||||
|
||||
Given one title, return a `Tracks` object holding `Video`, `Audio`, and
|
||||
|
||||
@@ -223,6 +223,12 @@ Service-specific CLI options may also be passed as additional keys.
|
||||
|
||||
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`.
|
||||
|
||||
!!! info "`part` on a split episode"
|
||||
A service that splits one episode into several separately playable videos reports each
|
||||
one as its own episode. Those titles share a `season` and `number` and add a `part`
|
||||
integer counting from 1. No other title carries the key, so a client that ignores it
|
||||
is unaffected. See [Split episodes](../creating-a-service.md#split-episodes).
|
||||
|
||||
| Status | Error code | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `200` | - | Titles returned. |
|
||||
@@ -248,9 +254,10 @@ List the video, audio, and subtitle tracks for a title. For series, you can scop
|
||||
| `wanted` | string | no | Episode/season range (e.g. `"S01E01-S01E03"`). |
|
||||
| `season` | int/string | no | Season number (combined with `episode`). |
|
||||
| `episode` | int/string | no | Episode number (combined with `season`). |
|
||||
| `part` | int/string | no | Part index of a split episode (combined with `season` and `episode`). |
|
||||
| `profile`, `proxy`, `no_proxy`, `cdm_type` | - | no | As on `list-titles`. |
|
||||
|
||||
When both `season` and `episode` are given, they are combined into a `"{season}x{episode}"` selector.
|
||||
When both `season` and `episode` are given, they are combined into a `"{season}x{episode}"` selector. Adding `part` narrows that to `"{season}x{episode}.{part}"`, one part of a [split episode](../creating-a-service.md#split-episodes); without it you get every part of the episode. `part` is read only when `season` and `episode` are both given, and `wanted` takes precedence over all three. To scope a part by range, put it in `wanted` instead (`"S01E01.2"`).
|
||||
|
||||
=== "Single title / movie: Response `200`"
|
||||
|
||||
@@ -361,7 +368,7 @@ Create a download job. Requires `service` and `title_id`; every other field is a
|
||||
| `range` | string[] | `["SDR"]` | Dynamic range(s). |
|
||||
| `channels` | number | `null` | Audio channel count. |
|
||||
| `no_atmos` | boolean | `false` | Exclude Atmos tracks. |
|
||||
| `wanted` | string[] | `[]` | Episode/season selectors. |
|
||||
| `wanted` | string[] | `[]` | Episode/season selectors. Accepts the part form, `"S01E01.2"`. |
|
||||
| `latest_episode` | boolean | `false` | Only the newest episode. |
|
||||
| `lang` / `v_lang` / `a_lang` / `s_lang` | string[] | `["orig"]` / `[]` / `[]` / `["all"]` | Language filters. |
|
||||
| `require_subs` | string[] | `[]` | Required subtitle languages. |
|
||||
|
||||
Reference in New Issue
Block a user