chore: bring MANUAL.md in line with current encoder behavior
§6 step 2: drop the codec-name allowlist, describe the bounded idet run and the Multi-frame summary parse. §6 step 6/7: per-stream audio.<n>.wav / audio.<n>.opus now. §6 step 8/9: zscale is conditional and width rounds to mod-2; -vf is omitted entirely when no filter applies. Audio language tags are indexed by output position. TITLE/COLLECTION values are unquoted. §6 step 11: series filename no longer requires an IMDb mapping. §7: added a token example and notes about what the token does and does not influence in the output filename. §8: noted the known log-rotation limitation (filename frozen at start; restart to rotate; structured.json does not rotate).
This commit is contained in:
@@ -170,33 +170,33 @@ For every `.mkv` found in `paths.input`, the program runs these steps in order.
|
|||||||
|
|
||||||
1. **Parse filename** → determines whether this is a movie or series, and what IDs to use.
|
1. **Parse filename** → determines whether this is a movie or series, and what IDs to use.
|
||||||
2. **Probe video** with `ffprobe`:
|
2. **Probe video** with `ffprobe`:
|
||||||
- Picks the first video stream whose codec is `mpeg2video`, `h264`, or `hevc`.
|
- Picks the first stream with `codec_type=video`, regardless of codec name. Errors out if there isn't one.
|
||||||
- Records width, height, and sample aspect ratio (SAR).
|
- Records width, height, and sample aspect ratio (SAR).
|
||||||
- Runs `ffmpeg -vf idet` to detect interlacing (presence of `TFF`/`BFF` in stderr).
|
- Detects interlacing by running `ffmpeg -vf idet -frames:v 400 -an -sn -f null -` and parsing the `Multi frame detection: TFF: a BFF: b Progressive: c Undetermined: d` summary line. The source is treated as interlaced only when `a+b > c`; undetermined frames are ignored, and a missing summary line defaults to progressive.
|
||||||
3. **Probe stream languages** with `ffprobe` — collects `language` tags for every audio/subtitle stream so they can be re-applied after encoding (FFmpeg's `-map_metadata -1` strips them otherwise).
|
3. **Probe stream languages** with `ffprobe` — collects `language` tags for every audio/subtitle stream so they can be re-applied after encoding (FFmpeg's `-map_metadata -1` strips them otherwise).
|
||||||
4. **Detect media type**:
|
4. **Detect media type**:
|
||||||
- First, check the filename for a `dvd` / `bluray` / `webdl` / `tvrip` token (case-insensitive). If present, that wins.
|
- First, check the filename for a `dvd` / `bluray` / `webdl` / `tvrip` token (case-insensitive). If present, that wins.
|
||||||
- Otherwise, fall back to pixel count: `width × height < 600,000` → DVD, else Blu-ray.
|
- Otherwise, fall back to pixel count: `width × height < 600,000` → DVD, else Blu-ray.
|
||||||
The chosen profile selects which `encoding.<type>.crf` / `encoding.<type>.preset` pair from the config to use, and is written into the `ORIGINAL_MEDIA_TYPE` metadata tag.
|
The chosen profile selects which `encoding.<type>.crf` / `encoding.<type>.preset` pair from the config to use, and is written into the `ORIGINAL_MEDIA_TYPE` metadata tag.
|
||||||
5. **Fetch metadata** from OMDb or TVmaze depending on the parsed filename. Failures here are logged but do not abort the encode — the file is just encoded without metadata.
|
5. **Fetch metadata** from OMDb or TVmaze depending on the parsed filename. Failures here are logged but do not abort the encode — the file is just encoded without metadata.
|
||||||
6. **Extract audio** to `audio.wav` (PCM s16le, 48 kHz) in the input directory.
|
6. **Extract audio** — one PCM wav per source audio stream, written to the input directory as `audio.0.wav`, `audio.1.wav`, … in source order (PCM s16le, 48 kHz). Errors out if the source has no audio streams.
|
||||||
7. **Encode audio** with `opusenc --bitrate 128k` → `audio.opus`.
|
7. **Encode audio** — each wav is converted with `opusenc --bitrate 128k` to a matching `audio.<n>.opus`.
|
||||||
8. **Calculate display width** from SAR. If SAR ≠ `1:1`, the width is rescaled so the output has square pixels, using a `zscale` filter (`spline36`). Height is preserved.
|
8. **Calculate display width** from SAR. The width is rescaled so the output has square pixels only when there's actually work to do — `zscale` is skipped entirely for square-pixel sources (SAR `1:1`, `N/A`, empty, `0:N`), and for any SAR whose calculated width rounds to the source width. When rescaling, the width is rounded to the nearest even number (mod-2, preferred by AV1).
|
||||||
9. **Encode video** with FFmpeg:
|
9. **Encode video** with FFmpeg:
|
||||||
- Video filter chain: `bwdif=mode=0:par=-1:-1,zscale=w=W:h=H:filter=spline36` if interlaced, else just the `zscale` step.
|
- Video filter chain is built conditionally. `bwdif=mode=0:par=-1:-1` is prepended when the source is interlaced; the `zscale` step is appended only when a rescale is actually needed (see step 8). If neither applies, `-vf` is omitted entirely.
|
||||||
- Codec: `libsvtav1`, `-pix_fmt yuv420p10le`.
|
- Codec: `libsvtav1`, `-pix_fmt yuv420p10le`.
|
||||||
- `-crf` and `-preset` from the selected profile.
|
- `-crf` and `-preset` from the selected profile (step 4).
|
||||||
- `-svtav1-params film-grain=10:film-grain-denoise=1:scd=1:qm-min=4:qm-max=15:keyint=10s`.
|
- `-svtav1-params film-grain=10:film-grain-denoise=1:scd=1:qm-min=4:qm-max=15:keyint=10s`.
|
||||||
- Streams mapped: video from input, subtitles from input (`0:s?` — optional), audio from the re-encoded Opus files.
|
- Streams mapped: video from input (`0:v`), subtitles from input (`0:s?` — optional), and one audio stream per Opus file (`1:a`, `2:a`, …).
|
||||||
- Audio copied (`-c:a copy`), subtitles copied (`-c:s copy`).
|
- Audio re-muxed (`-c:a copy` — copies the already-Opus-encoded streams), subtitles copied (`-c:s copy`).
|
||||||
- `-map_metadata -1` strips global metadata; per-stream language tags are then re-applied from the ffprobe pass.
|
- `-map_metadata -1` strips global metadata; per-stream language tags are then re-applied. Audio language tags are indexed by **output position** (the position in the opus-file list), not by counting source streams, so missing-language streams don't shift the index.
|
||||||
- Container metadata written: `TITLE`, `DATE_RELEASED`, `IMDBID`, `ORIGINAL_MEDIA_TYPE`. For series: also `COLLECTION`, `SEASON`, `EPISODE`, `TVMAZE_ID`.
|
- Container metadata written: `TITLE`, `DATE_RELEASED`, `IMDBID`, `ORIGINAL_MEDIA_TYPE`. For series: also `COLLECTION`, `SEASON`, `EPISODE`, `TVMAZE_ID`. Values are unquoted (literal value, no wrapping `"…"`).
|
||||||
- Output written to `output.mkv` in the input directory.
|
- Output written to `output.mkv` in the input directory.
|
||||||
10. **Clean up** intermediate `.wav` and `.opus` files.
|
10. **Clean up** intermediate `audio.*.wav` and `audio.*.opus` files.
|
||||||
11. **Rename and move** `output.mkv` to `paths.output` with a final name:
|
11. **Rename and move** `output.mkv` to `paths.output` with a final name:
|
||||||
- Series → `<sanitized-show-name>.S<NN>E<NN>.mkv`
|
- Series with a known show name → `<sanitized-show-name>.S<NN>E<NN>.mkv` (the series no longer needs a populated IMDb mapping — a TVmaze show with no external IMDb link still gets a useful filename).
|
||||||
- Movie → `<sanitized-title>.<imdbID>.mkv`
|
- Movie with a known title and IMDb ID → `<sanitized-title>.<imdbID>.mkv`.
|
||||||
- No metadata → `<8-hex-chars>.nometadata.mkv`
|
- Anything else → `<8-hex-chars>.nometadata.mkv`.
|
||||||
Sanitization keeps `a–z A–Z 0–9 - ä ö Ä Ö` only.
|
Sanitization keeps `a–z A–Z 0–9 - ä ö Ä Ö` only.
|
||||||
12. **Dispose of the source**:
|
12. **Dispose of the source**:
|
||||||
- `-d` flag set → delete the original `.mkv`.
|
- `-d` flag set → delete the original `.mkv`.
|
||||||
@@ -206,13 +206,18 @@ For every `.mkv` found in `paths.input`, the program runs these steps in order.
|
|||||||
|
|
||||||
## 7. Output naming examples
|
## 7. Output naming examples
|
||||||
|
|
||||||
| Input filename | Result in `paths.output` |
|
| Input filename | Result in `paths.output` | Notes |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| `Heat.tt0113277.mkv` | `Heat.tt0113277.mkv` |
|
| `Heat.tt0113277.mkv` | `Heat.tt0113277.mkv` | Pixel-count fallback → Blu-ray profile + tag |
|
||||||
| `Breaking.Bad.tvm169.S01E01.mkv` | `BreakingBad.S01E01.mkv` |
|
| `Heat.tt0113277.bluray.mkv` | `Heat.tt0113277.mkv` | Same output filename; muxed `ORIGINAL_MEDIA_TYPE=Blu-ray` is now from the token, not the guess |
|
||||||
| `unrecognized-rip.mkv` | `a1b2c3d4.nometadata.mkv` |
|
| `Breaking.Bad.tvm169.S01E01.webdl.mkv` | `BreakingBad.S01E01.mkv` | WebDL profile + tag |
|
||||||
|
| `unrecognized-rip.mkv` | `a1b2c3d4.nometadata.mkv` | No IDs at all |
|
||||||
|
|
||||||
The IMDb ID returned by the API is used, not the one from the filename, so a typo in the filename would surface there.
|
A few things worth knowing:
|
||||||
|
|
||||||
|
- The media-type token affects the muxed `ORIGINAL_MEDIA_TYPE` tag and the CRF/preset profile, but **not** the output filename.
|
||||||
|
- The IMDb ID written into the filename is the one returned by the API, not the one in the input filename, so a typo in the source filename will surface in the output name.
|
||||||
|
- A TVmaze show with no IMDb mapping still gets a `<Collection>.S<NN>E<NN>.mkv` filename (only the `IMDBID` metadata tag is left empty).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -228,6 +233,8 @@ Run the program from the directory where you want the logs to land.
|
|||||||
|
|
||||||
Note: a number of `DEBUG` lines are printed to stdout/stderr (ffprobe output, calculated zscale width, the full ffmpeg command, etc.). These are intentional but not written to the log files.
|
Note: a number of `DEBUG` lines are printed to stdout/stderr (ffprobe output, calculated zscale width, the full ffmpeg command, etc.). These are intentional but not written to the log files.
|
||||||
|
|
||||||
|
**Known limitation:** the date in `info_*.log` / `error_*.log` filenames is computed when the daemon starts and does not roll over at midnight. If the daemon is left running across days, all writes continue into the start-day's file. Restart the daemon to rotate. `structured.json` does not rotate at all.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Failure handling
|
## 9. Failure handling
|
||||||
|
|||||||
Reference in New Issue
Block a user