add: user manual
This commit is contained in:
@@ -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 `a–z A–Z 0–9 - ä ö Ä Ö` 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 10–30 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, …)
|
||||
```
|
||||
Reference in New Issue
Block a user