Skip to content

Instantly share code, notes, and snippets.

@ph33nx
Last active May 27, 2025 22:38
Show Gist options
  • Save ph33nx/0c320ae1c8d4dfb29c560d8a9f9088ad to your computer and use it in GitHub Desktop.
Save ph33nx/0c320ae1c8d4dfb29c560d8a9f9088ad to your computer and use it in GitHub Desktop.
Python script to recursively download missing subtitles for video files in formats like .mp4, .mkv, .avi, and .mov. Ideal for Jellyfin, Plex, Emby, and other media servers in homelab setups. Automatically fetches the best-matched .srt subtitles in your preferred language using subliminal. Boosts your media library with accurate subtitles for mov…
#!/usr/bin/env python3
"""
Author: ph33nx
URL: https://github.com/ph33nx
Description:
This script automatically scans a directory (recursively) for video files and downloads the best matching subtitles using the `subliminal` library.
It avoids duplicate work by checking for existing `.srt` subtitle files and is suitable for organizing media libraries in homelab setups.
Key Features:
- Supports popular video formats: MP4, MKV, AVI, MOV
- Downloads subtitles in the preferred language (default: English)
- Skips hidden/system files and already-subtitled videos
- Useful for Jellyfin, Plex, Emby, and other media server setups
- Ideal for homelab automation scripts and post-processing media workflows
Keywords:
jellyfin, plex, media server, subtitles, movies, homelab, subliminal, automation, python
Requirements:
- Python >= 3.8
- `subliminal` and `babelfish` Python packages
Installation:
pip install subliminal babelfish
Usage:
python download_subtitles.py <directory> [language]
Arguments:
<directory> Path to the root folder where your media files are stored.
[language] Optional subtitle language code (default: "eng" for English).
Example:
python download_subtitles.py /mnt/media/movies
python download_subtitles.py /mnt/media/tvshows spa
Note:
- Subtitle files are saved alongside the video with `.srt` extension.
- The script uses a local DBM cache for faster repeated scans.
"""
import os
import sys
from babelfish import Language
from subliminal import download_best_subtitles, region, save_subtitles, scan_video
# Configure subliminal cache
region.configure('dogpile.cache.dbm', arguments={'filename': 'cachefile.dbm'})
# Supported video extensions
VIDEO_EXTENSIONS = ('.mp4', '.mkv', '.avi', '.mov')
def download_subtitles_for_directory(directory, languages=['eng']):
"""
Recursively scan a directory for video files and download subtitles if missing.
:param directory: Path to the directory to scan
:param languages: List of languages for subtitles (default: ['eng'])
"""
for root, _, files in os.walk(directory):
for file in files:
# Skip files starting with dot (e.g., ".file" or "._file")
if file.startswith('.') or file.startswith('._'):
print(f"Skipping hidden or system file: {file}")
continue
if file.endswith(VIDEO_EXTENSIONS):
video_path = os.path.join(root, file)
subtitle_path = os.path.splitext(video_path)[0] + '.srt'
# Skip if subtitles already exist
if os.path.exists(subtitle_path):
print(f"Subtitle already exists for: {video_path}")
continue
print(f"Processing video: {video_path}")
try:
# Scan the video
video = scan_video(video_path)
if not video:
print(f"Could not process video: {video_path}")
continue
# Download subtitles
subtitles = download_best_subtitles([video], {Language(lang) for lang in languages})
if video in subtitles:
save_subtitles(video, subtitles[video])
print(f"Subtitle downloaded and saved for: {video_path}")
else:
print(f"No subtitles found for: {video_path}")
except Exception as e:
print(f"Error processing {video_path}: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python download_subtitles.py <directory> [language]")
sys.exit(1)
directory = sys.argv[1]
language = sys.argv[2] if len(sys.argv) > 2 else 'eng'
if not os.path.isdir(directory):
print(f"Invalid directory: {directory}")
sys.exit(1)
print(f"Scanning directory: {directory} for language: {language}")
download_subtitles_for_directory(directory, languages=[language])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment