josenightbreed 4 Posted 55 minutes ago Posted 55 minutes ago I want to share a script I made in python that creates theme videos and trailers from the existing movie library completely offline. Requirements ======================== ## Requirements ### Linux Install FFmpeg: ```bash sudo pacman -S ffmpeg '`` or use your distribution's package manager. ### Python Python 3.8+ recommended. Verify: ```bash python3 --version ``` ### Dependencies No external Python packages are required. The script only uses Python standard library modules and FFmpeg/FFprobe. ## Script was tested on 1,899 movies including: MP4 MKV HEVC/H.265 H.264 Multi-audio releases Anime releases with PGS subtitles NTFS media drives mounted under Linux in a intel n150 mini pc running linux mint. I use filebot for renaming and posters and stuff as it works great on movies, series and animes. The pc i run the script in is a AMD Ryzen™ 9 6900HX with Radeon™ Graphics × 16 Minis Forums mini pc and emby is running in a intel n150 mini pc running linux mint, drives i use for media on that server are all external usb formatted in ntfs and i mount all the media drives from the emby server via CIFS on my pc where i run the script in. As you can see i battle tested this lol I've only used it for movies, i have not tried it in series fearing that it will mess the library up. the output of the script is: Movies/ └── Ad Astra (2019) ├── Ad Astra (2019).mkv ├── Ad Astra (2019)-trailer.mp4 └── backdrops ├── theme.mp4 └── .processed I do the folder structure and renaming with filebot and the script generates the ├── Ad Astra (2019)-trailer.mp4 └── backdrops ├── theme.mp4 └── .processed The .processed file is to prevent duplication, and it tells the script to skip the movies already done. You can run it as many times as need it (I ran it multiple times after adding new additions to my library) It ran on 1880+ movies ≈10.5 hours runtime, and it did every single one them. If run locally on the server it should be faster. This is the script, I named mine backdrops.py, but you can name it what ever it makes sense to you: ********************************************************************************************************* #!/usr/bin/env python3 """ Emby Theme + Trailer Generator Creates: backdrops/theme.mp4 Movie Name-trailer.mp4 for every movie folder in a library. Features: - Multi-process - Resume support - MP4 / MKV support - Emby-compatible trailer naming - Offline operation - Handles anime and multi-audio releases Author: Jose Cortes License: MIT """ import os import subprocess from concurrent.futures import ProcessPoolExecutor # ===================================================== # CONFIG # ===================================================== VERSION = "1.0" MOVIE_ROOTS = [ "/mnt/media/NucBoxG9/disk1/Movies", "/mnt/media/NucBoxG9/disk2/Movies", ] WORKERS = 1 THEME_LENGTH = 12 TRAILER_CLIP_LENGTH = 15 TRAILER_POSITIONS = [ 0.10, 0.18, 0.26, 0.34, 0.42, 0.50, 0.58, 0.66, 0.74, ] # ===================================================== # UTILITIES # ===================================================== def get_duration(movie_path): output = subprocess.check_output([ "ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", movie_path ]) return float(output.decode().strip()) # ===================================================== # THEME VIDEO # ===================================================== def create_theme(movie_path, output_path, duration): start = int(duration * 0.30) cmd = [ "ffmpeg", "-y", "-ss", str(start), "-i", movie_path, "-map", "0:v:0", "-map", "0:a:0?", "-t", str(THEME_LENGTH), "-c:v", "libx264", "-preset", "veryfast", "-crf", "23", "-c:a", "aac", "-ac", "2", output_path ] subprocess.run( cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True ) # ===================================================== # TRAILER GENERATION # ===================================================== def create_trailer(movie_path, trailer_path, duration): temp_files = [] for idx, pct in enumerate(TRAILER_POSITIONS): start = int(duration * pct) temp_clip = f"/tmp/trailer_clip_{os.getpid()}_{idx}.mp4" cmd = [ "ffmpeg", "-y", "-ss", str(start), "-i", movie_path, "-map", "0:v:0", "-map", "0:a:0?", "-t", str(TRAILER_CLIP_LENGTH), "-c:v", "libx264", "-preset", "veryfast", "-crf", "23", "-c:a", "aac", "-ac", "2", temp_clip ] subprocess.run( cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True ) temp_files.append(temp_clip) concat_file = f"/tmp/trailer_concat_{os.getpid()}.txt" with open(concat_file, "w") as f: for clip in temp_files: f.write(f"file '{clip}'\n") cmd = [ "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", concat_file, "-c", "copy", trailer_path ] subprocess.run( cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True ) for clip in temp_files: try: os.remove(clip) except: pass try: os.remove(concat_file) except: pass # ===================================================== # FIND MOVIES # ===================================================== def find_movies(): movies = [] for root in MOVIE_ROOTS: if not os.path.exists(root): continue for folder in os.listdir(root): full = os.path.join(root, folder) if not os.path.isdir(full): continue candidates = [] for f in os.listdir(full): if f.lower().endswith(( ".mkv", ".mp4", ".avi", ".mov", ".m4v", ".webm" )): path = os.path.join(full, f) try: candidates.append( (os.path.getsize(path), path) ) except: pass if candidates: movies.append( (full, max(candidates)[1]) ) return movies # ===================================================== # PROCESS MOVIE # ===================================================== def process_movie(task): folder, movie_path = task backdrop_dir = os.path.join(folder, "backdrops") os.makedirs(backdrop_dir, exist_ok=True) processed_marker = os.path.join( backdrop_dir, ".processed" ) if os.path.exists(processed_marker): return movie_name = os.path.splitext( os.path.basename(movie_path) )[0] trailer_path = os.path.join( folder, f"{movie_name}-trailer.mp4" ) theme_path = os.path.join( backdrop_dir, "theme.mp4" ) try: print(f" Processing: {movie_name}") duration = get_duration(movie_path) create_theme( movie_path, theme_path, duration ) create_trailer( movie_path, trailer_path, duration ) if ( os.path.exists(theme_path) and os.path.exists(trailer_path) ): open(processed_marker, "w").close() print(f" Done: {movie_name}") else: print(f" Missing output: {movie_name}") except Exception as e: print( f" Failed: {movie_name} -> {e}" ) # ===================================================== # MAIN # ===================================================== if __name__ == "__main__": movies = find_movies() # TEST MODE # movies = movies[:20] print(f"\n Found {len(movies)} movies\n") with ProcessPoolExecutor( max_workers=WORKERS ) as executor: executor.map( process_movie, movies ) print("\n Library processing complete.\n") ************************************************************************************************************************ Change: MOVIE_ROOTS = [ "/mnt/media/NucBoxG9/disk1/Movies", "/mnt/media/NucBoxG9/disk2/Movies", ] To wherever you're disks are mounted. I would only modify: WORKERS = 1 to a higher value only if your pc can handle it. THEME_LENGTH = 12 TRAILER_CLIP_LENGTH = 15 Yields good file size results as is making the theme.mp4 around 12 secs and the *-trailer.mp4 around 2:15 minutes. They turned out to be pretty decent might I add specially compared to YouTube downloaded trailers that have branding and crappy resolution most of the time. I really hope it's useful for many of you, and please modify it as you see fit and share the results and mods so we can all benefit. I almost forgot, I'm not a programmer, I'm a retired pc tech and programming languages have always been my bane. This is the result of throwing ideas into ChatGPT and testing and troubleshooting the results. I finally found a way to use AI for something other than making cute cat girls. backdrops.py
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now