effeffmpeg - cleverdevil/squishy GitHub Wiki

effeffmpeg Documentation

FFmpeg Hardware-Aware Transcoder is a Python wrapper around FFmpeg that powers Squishy's transcoding functionality. It simplifies video transcoding with hardware acceleration, automatically detecting hardware capabilities and falling back to software encoding when needed.

Features

  • Automatic hardware acceleration detection (currently supports VAAPI)
  • Smart fallback to software encoding when hardware acceleration isn't available
  • Resolution scaling with common presets (360p, 480p, 720p, 1080p, 2160p)
  • Support for various video and audio codecs with container compatibility validation
  • Quality control using CRF or bitrate settings
  • Preset system for common encoding configurations
  • Command-line interface
  • Python API for integration into other applications
  • Non-blocking operation with live progress tracking
  • Real-time progress reporting callback function

Installation

No special installation is required as effeffmpeg is embedded within Squishy. The module consists of a single Python file: effeffmpeg.py.

Requirements:

  • Python 3.6 or later
  • FFmpeg installed on your system
  • ffprobe command (usually installed alongside FFmpeg)

Command-line Usage

Detect hardware capabilities

python3 effeffmpeg.py detect capabilities.json

List available presets

python3 effeffmpeg.py presets --file presets.json

Transcode a video using command-line options

python3 effeffmpeg.py transcode --to h264 --scale 720p --audio aac --run input.mp4 output.mp4

Transcode using a preset

python3 effeffmpeg.py transcode --preset 720p-high --run input.mp4 output.mp4

Preset System

effeffmpeg uses a flexible preset system for defining common encoding configurations:

  1. JSON files: Store presets in JSON files and load them at runtime
  2. Python dictionaries: Define presets directly in your Python code

Preset Format

Each preset is a dictionary with the following keys:

  • container: The container format (e.g., ".mp4", ".mkv", ".webm")
  • codec: The video codec to use (e.g., "h264", "hevc", "vp9")
  • scale: The target resolution (e.g., "360p", "720p", "1080p")
  • audio_codec: The audio codec to use (e.g., "aac", "opus", "flac")
  • audio_bitrate: The audio bitrate (e.g., "128k", "192k")
  • bitrate or crf: The video quality setting (bitrate-based or quality-based)
  • allow_fallback: Whether to allow fallback to software encoding

Preset Collections

effeffmpeg comes with two preset collections:

Compatible Presets (presets-compatible.json)

These presets focus on compatibility using H.264/MP4 with different bitrates:

{
  "presets": {
    "360p-low": {
      "container": ".mp4",
      "codec": "h264",
      "scale": "360p",
      "audio_codec": "aac",
      "audio_bitrate": "64k",
      "bitrate": "500k",
      "allow_fallback": true
    },
    "720p-medium": {
      "container": ".mp4",
      "codec": "h264",
      "scale": "720p",
      "audio_codec": "aac",
      "audio_bitrate": "128k",
      "bitrate": "2.5M",
      "allow_fallback": true
    },
    "1080p-high": {
      "container": ".mp4",
      "codec": "h264",
      "scale": "1080p",
      "audio_codec": "aac",
      "audio_bitrate": "192k",
      "bitrate": "8M",
      "allow_fallback": true
    }
  }
}

Quality Presets (presets-quality.json)

These presets focus on quality using HEVC/MKV with CRF-based encoding:

{
  "presets": {
    "360p-low": {
      "container": ".mkv",
      "codec": "hevc",
      "scale": "360p",
      "audio_codec": "aac",
      "audio_bitrate": "64k",
      "crf": 28,
      "allow_fallback": true
    },
    "720p-medium": {
      "container": ".mkv",
      "codec": "hevc",
      "scale": "720p",
      "audio_codec": "aac",
      "audio_bitrate": "128k",
      "crf": 24,
      "allow_fallback": true
    },
    "1080p-high": {
      "container": ".mkv",
      "codec": "hevc",
      "scale": "1080p",
      "audio_codec": "aac",
      "audio_bitrate": "192k",
      "crf": 20,
      "allow_fallback": true
    }
  }
}

Python API Usage

Basic Transcoding

import effeffmpeg

# Transcode a video with hardware acceleration if available
effeffmpeg.transcode(
    input_file="input.mp4",
    output_file="output.mp4",
    codec="h264",
    scale="720p",
    audio_codec="aac",
    audio_bitrate="128k",
    allow_fallback=True,  # Allow fallback to software encoding if needed
    overwrite=True        # Overwrite output file if it exists
)

Quality-Based Encoding

import effeffmpeg

# Use CRF for quality-based encoding (software encoding only)
effeffmpeg.transcode(
    input_file="input.mp4",
    output_file="output.mkv",
    codec="hevc",
    scale="1080p",
    audio_codec="libopus",
    audio_bitrate="192k",
    crf=22,               # Lower CRF = higher quality
    force_software=True,  # Force software encoding
    overwrite=True
)

Using Presets

import effeffmpeg

# Using a preset from a JSON file
effeffmpeg.transcode(
    input_file="input.mp4",
    output_file="output.mp4",
    preset_name="720p-high",
    presets_file="presets/presets-compatible.json",
    overwrite=True
)

# Using a quality-focused preset
effeffmpeg.transcode(
    input_file="input.mp4",
    output_file="output.mkv",
    preset_name="1080p-high",
    presets_file="presets/presets-quality.json",
    overwrite=True
)

Non-Blocking Transcoding with Progress Tracking

import effeffmpeg
import time

# Define a progress callback function
def progress_handler(line, progress):
    if progress is not None:
        print(f"Progress: {progress*100:.1f}% - {line}")

# Start a non-blocking transcode
process = effeffmpeg.transcode(
    input_file="input.mp4",
    output_file="output.mkv",
    codec="hevc",
    scale="1080p",
    audio_codec="libopus",
    audio_bitrate="192k",
    overwrite=True,
    non_blocking=True,  # Enable non-blocking mode
    progress_callback=progress_handler  # Register progress callback
)

# Do other work while transcoding is in progress
while not process.finished:
    print(f"Elapsed time: {process.get_elapsed_time():.1f} seconds")
    time.sleep(1)

    # Check if the process has completed
    if process.process.poll() is not None:
        process.finished = True
        process.returncode = process.process.returncode

# Get the result when done
print(f"Transcoding completed with return code: {process.returncode}")

Hardware Capability Detection

import effeffmpeg
import json

# Detect hardware acceleration capabilities
capabilities = effeffmpeg.detect_capabilities()

# Print available hardware encoders
if capabilities["hwaccel"]:
    print(f"Hardware acceleration available: {capabilities['hwaccel']}")
    print(f"Available encoders: {list(capabilities['encoders'].keys())}")
else:
    print("No hardware acceleration detected, using software encoding")

# Save capabilities to a file for future use
with open("capabilities.json", "w") as f:
    json.dump(capabilities, f, indent=2)

API Reference

transcode()

def transcode(
    input_file,             # Path to input video file
    output_file,            # Path to output video file
    codec=None,             # Target video codec (h264, hevc, vp9, av1)
    scale=None,             # Target resolution (360p, 480p, 720p, 1080p, 2160p)
    audio_codec=None,       # Target audio codec (copy, aac, flac, opus, libopus)
    allow_fallback=True,    # Allow software fallback if hardware encoding fails
    force_software=False,   # Force software encoding
    crf=None,               # Constant Rate Factor (0-51, lower is better)
    bitrate=None,           # Target video bitrate (e.g. "2M")
    audio_bitrate=None,     # Target audio bitrate (e.g. "128k")
    flac_compression=None,  # FLAC compression level (0-8)
    capabilities_file=None, # Path to capabilities JSON file
    dry_run=False,          # Return command without executing
    overwrite=False,        # Force overwrite of output file
    quiet=False,            # Suppress informational output
    non_blocking=False,     # Enable non-blocking operation
    progress_callback=None, # Function to call with progress updates
    preset_name=None,       # Name of the preset to use
    presets_data=None,      # Python dictionary containing preset configurations
    presets_file=None       # Path to JSON file containing preset configurations
)

Returns:

  • If dry_run=True: Returns the FFmpeg command as a list of strings
  • If non_blocking=True: Returns a TranscodeProcess object to manage the process
  • Otherwise: Returns a subprocess.CompletedProcess object with the result

Integration with Squishy

In Squishy, effeffmpeg is used to power the transcoding functionality:

  1. Squishy detects hardware capabilities during startup
  2. The onboarding wizard helps configure hardware acceleration
  3. Transcoding jobs are managed through a queue system
  4. Each job uses effeffmpeg to perform the actual transcoding
  5. Progress is tracked in real-time and displayed in the web interface

The integration can be seen in the transcoder.py file, where Squishy creates a custom progress callback function to update the job status and emits events to the web interface.