Library Usage Guide

This guide provides comprehensive documentation for using mkv2cast as a Python library in your applications.

Overview

mkv2cast exposes a clean Python API that allows you to:

  • Convert video files programmatically

  • Monitor conversion progress via callbacks

  • Process multiple files in parallel

  • Integrate with your existing applications

Architecture

The mkv2cast API is organized into several modules:

mkv2cast/
├── Config          # Configuration dataclass
├── convert_file()  # Single file conversion
├── convert_batch() # Parallel batch conversion
├── decide_for()    # Codec analysis
├── pick_backend()  # Backend selection
└── HistoryDB       # Conversion history

Data Flow

The conversion process follows this flow:

Input File
    │
    ▼
┌─────────────────┐
│ decide_for()    │ ─── Analyze codecs, determine if transcoding needed
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ pick_backend()  │ ─── Select best encoder (NVENC, QSV, VAAPI, CPU)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ convert_file()  │ ─── Build FFmpeg command, run encoding
└────────┬────────┘
         │
         ▼
   Output File

Quick Start

Minimal Example

from mkv2cast import convert_file, Config
from pathlib import Path

config = Config.for_library()
success, output, msg = convert_file(Path("movie.mkv"), cfg=config)

With Progress Tracking

from mkv2cast import convert_file, Config
from pathlib import Path

def on_progress(filepath, progress):
    print(f"{progress['stage']}: {progress['progress_percent']:.1f}%")

config = Config.for_library(hw="vaapi")
convert_file(Path("movie.mkv"), cfg=config, progress_callback=on_progress)

Configuration

Using Config.for_library()

The recommended way to create a configuration for library usage:

from mkv2cast import Config

# Basic usage - automatically disables UI features
config = Config.for_library()

# With custom options
config = Config.for_library(
    hw="vaapi",           # Hardware acceleration
    crf=20,               # Quality (lower = better)
    preset="slow",        # Encoding preset
    container="mkv",      # Output format
    encode_workers=2,     # Parallel encoders for batch
)

What Config.for_library() Does

Automatically sets:

  • progress=False - No progress bars

  • notify=False - No desktop notifications

  • pipeline=False - No Rich UI

This ensures clean operation when running as a library.

Manual Configuration

You can also configure manually:

from mkv2cast import Config

config = Config(
    # Output
    suffix=".cast",
    container="mkv",

    # Hardware
    hw="auto",            # "auto", "nvenc", "qsv", "vaapi", "cpu"
    vaapi_device="/dev/dri/renderD128",
    vaapi_qp=23,
    qsv_quality=23,
    nvenc_cq=23,

    # Encoding
    crf=20,               # CPU CRF (18-28)
    preset="slow",        # "ultrafast" to "veryslow"
    abr="192k",           # Audio bitrate

    # Behavior
    skip_when_ok=True,    # Skip compatible files
    force_h264=False,     # Force H264 transcoding
    allow_hevc=False,     # Allow HEVC passthrough

    # UI (disable for library)
    progress=False,
    notify=False,
    pipeline=False,

    # Workers
    encode_workers=2,
)

Progress Callbacks

Callback Function Signature

def progress_callback(filepath: Path, progress: dict) -> None:
    """
    Called during conversion with progress updates.

    Args:
        filepath: Path to the file being processed
        progress: Dictionary with progress information
    """
    pass

Progress Dictionary

The progress dictionary contains:

{
    "stage": "encoding",        # "checking", "encoding", "done", "skipped", "failed"
    "progress_percent": 45.2,   # 0.0 to 100.0
    "fps": 120.5,               # Current FPS
    "eta_seconds": 30.5,        # Estimated time remaining
    "bitrate": "2500kbits/s",   # Current bitrate
    "speed": "2.5x",            # Encoding speed
    "current_time_ms": 1620000, # Current position
    "duration_ms": 3600000,     # Total duration
    "error": None,              # Error message if failed
}

Stage Transitions

checking ──► encoding ──► done
    │            │
    │            └──► failed
    │
    └──► skipped (if compatible)
    │
    └──► failed (if integrity check fails)

Example: GUI Integration

import tkinter as tk
from tkinter import ttk
from mkv2cast import convert_file, Config
from pathlib import Path
import threading

class ConversionGUI:
    def __init__(self):
        self.root = tk.Tk()
        self.progress = ttk.Progressbar(self.root, length=300)
        self.progress.pack(pady=20)
        self.label = tk.Label(self.root, text="Ready")
        self.label.pack()

    def on_progress(self, filepath, progress):
        # Update GUI from main thread
        self.root.after(0, self._update_gui, progress)

    def _update_gui(self, progress):
        self.progress["value"] = progress["progress_percent"]
        self.label["text"] = f"{progress['stage']}: {progress['progress_percent']:.1f}%"

    def convert(self, filepath):
        config = Config.for_library()
        threading.Thread(
            target=convert_file,
            args=(Path(filepath),),
            kwargs={"cfg": config, "progress_callback": self.on_progress}
        ).start()

Batch Processing

Using convert_batch()

Process multiple files in parallel:

from mkv2cast import convert_batch, Config
from pathlib import Path

config = Config.for_library(encode_workers=2)

files = [
    Path("movie1.mkv"),
    Path("movie2.mkv"),
    Path("movie3.mkv"),
]

results = convert_batch(files, cfg=config)

for filepath, (success, output, msg) in results.items():
    print(f"{filepath.name}: {msg}")

Thread-Safe Callbacks

When using convert_batch(), callbacks may be called from multiple threads. The library handles thread safety internally, but your callback code should also be safe:

import threading
from mkv2cast import convert_batch, Config

progress_lock = threading.Lock()
all_progress = {}

def thread_safe_callback(filepath, progress):
    with progress_lock:
        all_progress[str(filepath)] = progress
        # Safe to access/modify shared state here

config = Config.for_library(encode_workers=4)
convert_batch(files, cfg=config, progress_callback=thread_safe_callback)

Controlling Parallelism

config = Config.for_library(
    encode_workers=4,    # 4 parallel encoding jobs
)
  • encode_workers=0: Auto-detect (usually 1)

  • encode_workers=1: Sequential processing

  • encode_workers=N: N parallel jobs

Error Handling

Convert File Errors

from mkv2cast import convert_file, Config
from pathlib import Path

config = Config.for_library()

try:
    success, output, msg = convert_file(Path("movie.mkv"), cfg=config)

    if not success:
        print(f"Conversion failed: {msg}")
    elif output is None:
        print(f"File skipped: {msg}")
    else:
        print(f"Converted to: {output}")

except Exception as e:
    print(f"Unexpected error: {e}")

Batch Error Handling

from mkv2cast import convert_batch, Config

results = convert_batch(files, cfg=config)

failed_files = [
    (path, msg)
    for path, (success, _, msg) in results.items()
    if not success
]

if failed_files:
    print("Failed files:")
    for path, msg in failed_files:
        print(f"  {path}: {msg}")

Callback Errors

Callback errors are caught and ignored to prevent affecting the conversion:

def buggy_callback(filepath, progress):
    raise Exception("Bug!")  # Won't stop conversion

# Conversion continues despite callback error
convert_file(path, cfg=config, progress_callback=buggy_callback)

Best Practices

1. Always Use Config.for_library()

This ensures UI features are disabled:

# Good
config = Config.for_library(hw="vaapi")

# Avoid (may produce unwanted output)
config = Config(hw="vaapi")

2. Handle All Result Cases

success, output, msg = convert_file(path, cfg=config)

if success and output:
    # File was converted
    pass
elif success and not output:
    # File was skipped (already compatible or dry run)
    pass
else:
    # Conversion failed
    pass

3. Use Thread-Safe Callbacks for Batch

import threading

lock = threading.Lock()

def callback(filepath, progress):
    with lock:
        # Safe operations here
        pass

4. Set Appropriate Worker Count

import os

# Don't use more workers than CPU cores
config = Config.for_library(
    encode_workers=min(4, os.cpu_count() or 1)
)

Troubleshooting

No Output When Expected

Check if file is already compatible:

from mkv2cast import decide_for

decision = decide_for(path)
print(f"Needs video transcode: {decision.need_v}")
print(f"Needs audio transcode: {decision.need_a}")
print(f"Reason: {decision.reason_v}")

Progress Callback Not Called

Ensure you’re passing the callback correctly:

# Correct
convert_file(path, cfg=config, progress_callback=my_callback)

# Wrong (callback as positional arg)
convert_file(path, config, my_callback)

Backend Not Detected

Test backend availability:

from mkv2cast import pick_backend, Config

config = Config(hw="auto")
backend = pick_backend(config)
print(f"Selected backend: {backend}")

API Reference

For complete API documentation, see API Reference.