Jump to content

Themes and Trailers python script


Recommended Posts

josenightbreed
Posted

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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...