shrivel 0.1.0

Cross-platform FFmpeg orchestrator that batch re-encodes videos to H.265/HEVC
shrivel-0.1.0 is not a library.

shrivel

A small cross-platform command-line tool that batch re-encodes videos to H.265/HEVC by orchestrating FFmpeg. Point it at a folder of videos and it shrinks them, picking a quality target per file based on the source bitrate.

Requirements

  • ffmpeg and ffprobe available in your PATH.
  • Rust (2024 edition) to build from source.

Installation

cargo install --path .

Or build without installing:

cargo build --release
# binary: target/release/shrivel

Usage

# Convert every .mp4 in ./input into ./output
shrivel

# Custom folders and quality
shrivel --input ~/Videos/raw --output ~/Videos/small --cq 28

# Resample to 30 fps, denoise, and skip files that are already HEVC
shrivel --fps 30 --denoise --skip-hevc

# Process several container types, forcing the CPU encoder
shrivel --ext mp4,mkv,mov --encoder software

# Preview the FFmpeg commands without running them
shrivel --dry-run

Subdirectories of the input folder are not searched. Output files keep the name of their source file.

Options

Option Default Description
-i, --input <DIR> input Directory containing the source videos.
-o, --output <DIR> output Directory for converted videos (created if missing). Must differ from the input directory.
--cq <N> 30 Base constant-quality value, 0-51. Lower means better quality and larger files.
--fps <FPS> source Target frame rate. The source frame rate is kept when omitted.
--skip-hevc off Skip files whose video stream is already HEVC.
--denoise off Apply a light hqdn3d=3:3:8:8 denoise filter.
-e, --encoder <NAME> auto One of auto, nvenc, qsv, amf, videotoolbox, software.
--ext <EXT,...> mp4 Comma-separated input extensions, matched case-insensitively.
--skip-existing off Skip files whose output already exists instead of overwriting them.
--dry-run off Print the FFmpeg commands without running them.

The process exits with a non-zero status if at least one file failed to convert.

How it works

For each input file shrivel:

  1. Runs ffprobe to read the video codec, bitrate and frame rate. If the video stream has no bitrate (common in Matroska files), the container bitrate is used instead.

  2. Optionally skips the file when it is already HEVC (--skip-hevc).

  3. Adapts the quality value to the source bitrate. Heavily compressed sources gain little from a strict target, so the value is relaxed to avoid inflating the output:

    Source bitrate Effective CQ
    unknown base
    below 3000 kbit/s base + 4
    3000 to 5999 kbit/s base + 2
    6000 kbit/s or more base

    The result is capped at 51.

  4. Builds the video filter chain (denoise, then frame-rate change). A frame-rate change is applied only when the source differs from the target by more than 0.5 fps, and it forces constant-frame-rate output to avoid leftover variable-frame-rate drift.

  5. Runs FFmpeg, mapping the first video stream and all audio streams. Audio is copied without re-encoding. Other streams (subtitles, data) are dropped.

  6. Reports the input and output sizes. If FFmpeg fails, the partial output file is removed and the first lines of FFmpeg's error output are shown.

Files in .mp4, .m4v and .mov outputs are tagged hvc1 so they play on Apple devices.

Encoders

With --encoder auto (the default), shrivel tries each hardware encoder in turn by encoding one tiny synthetic frame, and uses the first one that works. If none works it uses software x265. An encoder being listed by ffmpeg -encoders is not enough, because hardware encoders are often compiled in but unusable on the current machine.

Encoder FFmpeg codec Notes
nvenc hevc_nvenc NVIDIA GPUs.
videotoolbox hevc_videotoolbox macOS. The CQ value is mapped approximately onto VideoToolbox's -q:v scale.
qsv hevc_qsv Intel Quick Sync.
amf hevc_amf AMD GPUs.
software libx265 CPU encoding.

Quality values are not directly comparable between encoders. A given --cq is a starting point, so check the output of a short clip when switching backends.