8.7 KiB
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):
ffmpegffprobeopusenc
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
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):
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
./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.mkvafter a successful encode instead of moving it tooriginals/.-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.
- Parse filename → determines whether this is a movie or series, and what IDs to use.
- Probe video with
ffprobe:- Picks the first video stream whose codec is
mpeg2video,h264, orhevc. - Records width, height, and sample aspect ratio (SAR).
- Runs
ffmpeg -vf idetto detect interlacing (presence ofTFF/BFFin stderr).
- Picks the first video stream whose codec is
- Probe stream languages with
ffprobe— collectslanguagetags for every audio/subtitle stream so they can be re-applied after encoding (FFmpeg's-map_metadata -1strips them otherwise). - 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.
- 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.
- Extract audio to
audio.wav(PCM s16le, 48 kHz) in the input directory. - Encode audio with
opusenc --bitrate 128k→audio.opus. - Calculate display width from SAR. If SAR ≠
1:1, the width is rescaled so the output has square pixels, using azscalefilter (spline36). Height is preserved. - Encode video with FFmpeg:
- Video filter chain:
bwdif=mode=0:par=-1:-1,zscale=w=W:h=H:filter=spline36if interlaced, else just thezscalestep. - Codec:
libsvtav1,-pix_fmt yuv420p10le. -crfand-presetfrom 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 -1strips 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: alsoCOLLECTION,SEASON,EPISODE,TVMAZE_ID. - Output written to
output.mkvin the input directory.
- Video filter chain:
- Clean up intermediate
.wavand.opusfiles. - Rename and move
output.mkvtopaths.outputwith a final name:- Series →
<sanitized-show-name>.S<NN>E<NN>.mkv - Movie →
<sanitized-title>.<imdbID>.mkv - No metadata →
<8-hex-chars>.nometadata.mkvSanitization keepsa–z A–Z 0–9 - ä ö Ä Öonly.
- Series →
- Dispose of the source:
-dflag 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), withtimestamp,level,message, optionalerrorandfilefields.
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
.mkvis moved topaths.failed. - Any partial
output.mkvleft in the input directory is also moved topaths.failed. - The error is logged to
error_*.logandstructured.jsonwith 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
*.mkvdirectly inpaths.inputare picked up — no recursion. - Files are processed sequentially, one at a time, in the order
filepath.Globreturns 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 andmvit 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, …)