add: user manual

This commit is contained in:
Esa Kataja
2026-05-16 19:42:25 +03:00
parent 30c7d9ae27
commit 1dab2befbd
+234
View File
@@ -0,0 +1,234 @@
# videnc-vibe — User Manual
A Go CLI that watches a folder for `.mkv` rips, transcodes them to SVT-AV1 video + Opus audio, embeds metadata fetched from OMDb (movies) or TVmaze (series), and files the results into output/originals/failed directories.
---
## 1. Requirements
External binaries must be on `$PATH` (the program checks them at startup and exits if any are missing):
- `ffmpeg`
- `ffprobe`
- `opusenc`
Go (only to build):
- Go 1.x with module support.
API keys:
- OMDb API key (free at https://www.omdbapi.com/). Required for movie metadata. TVmaze is unauthenticated.
---
## 2. Build
```bash
go build -o videnc-vibe ./cmd/videnc/
```
This produces a single static binary `./videnc-vibe`.
---
## 3. Configuration
By default the config is loaded from `~/.config/videnc-vibe/config.yaml`. Pass `-c /path/to/config.yaml` to override.
Example (`config.example.yaml`):
```yaml
omdb_api_key: "YOUR_API_KEY_HERE"
encoding:
dvd:
crf: 30
preset: 2
bluray:
crf: 29
preset: 3
paths:
input: "./input"
output: "./output"
originals: "./originals"
failed: "./failed"
```
### Field reference
| Field | Meaning | Default |
|---|---|---|
| `omdb_api_key` | OMDb API key for movie metadata lookup | (none — movies won't get metadata without it) |
| `encoding.dvd.crf` | SVT-AV1 CRF for SD sources | `30` |
| `encoding.dvd.preset` | SVT-AV1 preset for SD sources | `2` |
| `encoding.bluray.crf` | SVT-AV1 CRF for HD sources | `29` |
| `encoding.bluray.preset` | SVT-AV1 preset for HD sources | `3` |
| `paths.input` | Folder polled for new `.mkv` files | `./input` |
| `paths.output` | Destination for finished encodes | `./output` |
| `paths.originals` | Where source files are moved on success (unless `-d`) | `./originals` |
| `paths.failed` | Where source + partial output go on failure | `./failed` |
All four directories are created on startup if they don't exist.
---
## 4. Running
```bash
./videnc-vibe # default config path, keep originals
./videnc-vibe -d # delete originals after successful encode
./videnc-vibe -c /etc/videnc.yaml # custom config
./videnc-vibe -c /etc/videnc.yaml -d
```
Flags:
- `-d` — delete the source `.mkv` after a successful encode instead of moving it to `originals/`.
- `-c PATH` — path to config file.
Stop with `Ctrl+C` (SIGINT) or `SIGTERM`. A signal triggers a clean shutdown after the current poll cycle.
The program runs as a foreground daemon. It scans the input directory on startup and every 15 seconds thereafter.
---
## 5. Input filename convention
The base name of each `.mkv` in `paths.input` is parsed to decide what metadata to fetch. Three forms are recognized:
### 5.1 Movies — IMDb ID
Filename must contain an IMDb tag of the form `tt<digits>`.
Examples:
```
Heat.tt0113277.mkv
some-rip-tt0114369.mkv
```
→ OMDb is queried for that IMDb ID. Title, release date, and IMDb ID are embedded.
### 5.2 Series — TVmaze ID + season/episode
Filename must contain BOTH:
- `tvm<digits>` — the TVmaze show ID (case-insensitive).
- `s<digits>e<digits>` — season and episode (case-insensitive).
Examples:
```
Breaking.Bad.tvm169.S01E01.mkv
the-wire.TVM75.s2e5.mkv
```
→ TVmaze is queried for that show/season/episode. Show name (Collection), episode title, season, episode, airdate, and the show's IMDb ID are embedded.
### 5.3 No recognizable tags
If neither pattern matches, encoding still proceeds but the file is treated as having no metadata. The output is named with a random hex string and an `.nometadata.mkv` suffix.
---
## 6. The processing pipeline
For every `.mkv` found in `paths.input`, the program runs these steps in order. Any error sends the source file (and the partial `output.mkv`, if any) to `paths.failed`.
1. **Parse filename** → determines whether this is a movie or series, and what IDs to use.
2. **Probe video** with `ffprobe`:
- Picks the first video stream whose codec is `mpeg2video`, `h264`, or `hevc`.
- Records width, height, and sample aspect ratio (SAR).
- Runs `ffmpeg -vf idet` to detect interlacing (presence of `TFF`/`BFF` in stderr).
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** from pixel count:
- `width × height < 600,000`**DVD** profile.
- Otherwise → **Blu-ray** profile.
The chosen profile selects which CRF/preset pair from the config to use.
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.
7. **Encode audio** with `opusenc --bitrate 128k``audio.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.
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.
- Codec: `libsvtav1`, `-pix_fmt yuv420p10le`.
- `-crf` and `-preset` from the selected profile.
- `-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.
- Audio copied (`-c:a copy`), subtitles copied (`-c:s copy`).
- `-map_metadata -1` strips global metadata; per-stream language tags are then re-applied from the ffprobe pass.
- Container metadata written: `TITLE`, `DATE_RELEASED`, `IMDBID`, `ORIGINAL_MEDIA_TYPE`. For series: also `COLLECTION`, `SEASON`, `EPISODE`, `TVMAZE_ID`.
- Output written to `output.mkv` in the input directory.
10. **Clean up** intermediate `.wav` and `.opus` files.
11. **Rename and move** `output.mkv` to `paths.output` with a final name:
- Series → `<sanitized-show-name>.S<NN>E<NN>.mkv`
- Movie → `<sanitized-title>.<imdbID>.mkv`
- No metadata → `<8-hex-chars>.nometadata.mkv`
Sanitization keeps `az AZ 09 - ä ö Ä Ö` only.
12. **Dispose of the source**:
- `-d` flag set → delete the original `.mkv`.
- Otherwise → move it to `paths.originals`.
---
## 7. Output naming examples
| Input filename | Result in `paths.output` |
|---|---|
| `Heat.tt0113277.mkv` | `Heat.tt0113277.mkv` |
| `Breaking.Bad.tvm169.S01E01.mkv` | `BreakingBad.S01E01.mkv` |
| `unrecognized-rip.mkv` | `a1b2c3d4.nometadata.mkv` |
The IMDb ID returned by the API is used, not the one from the filename, so a typo in the filename would surface there.
---
## 8. Logs
Three log files are written to the **current working directory** (not the config paths):
- `info_YYYY-MM-DD.log` — INFO messages, dated.
- `error_YYYY-MM-DD.log` — ERROR messages, dated.
- `structured.json` — one JSON object per line, every entry (info + error), with `timestamp`, `level`, `message`, optional `error` and `file` fields.
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.
---
## 9. Failure handling
If any step from probing through encoding through renaming fails:
- The source `.mkv` is moved to `paths.failed`.
- Any partial `output.mkv` left in the input directory is also moved to `paths.failed`.
- The error is logged to `error_*.log` and `structured.json` with the source file path.
The watcher continues with the next file; one bad rip won't stop the daemon.
---
## 10. Polling behavior
- Input directory is scanned every **15 seconds** (clamped to a 1030 s range).
- Only files matching `*.mkv` directly in `paths.input` are picked up — no recursion.
- Files are processed **sequentially**, one at a time, in the order `filepath.Glob` returns them (alphabetical on Linux).
- There is no atomic-write detection. If you're copying a large file into `paths.input`, copy it to a different name first and `mv` it into place once complete, otherwise the watcher may try to encode a half-written file.
---
## 11. Project layout
```
cmd/videnc/main.go CLI entrypoint and per-file orchestration
internal/config/ YAML config load + defaults + mkdir
internal/watcher/ Polling loop, media-type detection by pixel count
internal/encoder/ ffprobe/ffmpeg/opusenc wrapper, transcode pipeline
internal/metadata/ Filename parsing, OMDb + TVmaze clients
internal/mover/ File rename/move/delete helpers
internal/logger/ Plain + JSON logging
pkg/types/types.go Shared structs (Config, Job, Metadata, …)
```