Files
av1dae/MANUAL.md
T
2026-05-16 19:42:25 +03:00

8.7 KiB
Raw Blame History

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:


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 .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,000DVD 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 128kaudio.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 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, …)