annotate create.py @ 133:0d8eabdd12ab default tip

create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black many albums are longer than one hour so writing H:MM:SS is a necessity. if anything there will just be verbose info that isn't important for my use-case. however the gaussian-blur is simply broken. It works, and it plays locally just fine, but YouTube in particular elongates the video to fit the full width. I'm not entirely sure why it does this, but it makes it useless and ugly.
author Paper <paper@tflc.us>
date Sat, 03 Jan 2026 20:25:38 -0500
parents 71df0cf3aa05
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
1 #!/usr/bin/env python3
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
2 # Simple python script to create a video from a bunch of wav files
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
3 # and an image. Also creates a cue-like track listing that contains
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
4 # track numbers etc.
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
5
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
6 import argparse
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
7 import json
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
8 import subprocess
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
9
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
10 def build_vf(blur) -> list[str]:
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
11 if blur:
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
12 return ["-filter_complex", "[0:v]split=2[bg][fg];[bg]scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,boxblur=luma_radius=10:chroma_radius=10[bg_final];[fg]scale=1920:1080:force_original_aspect_ratio=decrease[fg_final];[bg_final][fg_final]overlay=(W-w)/2:(H-h)/2"]
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
13
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
14 return ["-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2"]
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
15
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
16 # Build filter string
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
17 def build_filter_string(length: int, cover: bool) -> str:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
18 f = ""
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
19 i = 1 if cover else 0
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
20 for x in range(length):
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
21 f += ("[%d:0]" % (x + i))
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
22 f += ("concat=n=%d:v=0:a=1[out]" % length)
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
23 return f
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
24
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
25 def build_arguments_list(audio_files: list[str], cover, output, blur) -> list:
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
26 args = ["ffmpeg", "-y"]
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
27 if not cover is None:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
28 args.extend(["-r", "1", "-loop", "1", "-i", cover])
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
29 # I want you to play that song again
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
30 for f in audio_files:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
31 args.extend(["-i", f])
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
32 args.extend(["-filter_complex", build_filter_string(len(audio_files), False if cover is None else True), "-map", "[out]"])
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
33 if not cover is None:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
34 # Letterbox to 1920x1080
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
35 args.extend(["-map", "0:v", "-tune", "stillimage", "-c:v", "libx264"])
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
36 args.extend(build_vf(blur))
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
37 args.extend(["-shortest"])
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
38 args.append(output + ".mkv")
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
39 return args
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
40
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
41
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
42 # Returns a Popen object that should be waited on
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
43 def run_ffmpeg(audio_files: list[str], cover, output, blur) -> subprocess.Popen:
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
44 args = build_arguments_list(audio_files, cover, output, blur)
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
45 print(args)
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
46 return subprocess.Popen(args)
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
47
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
48
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
49 # Returns a Popen object that should be waited on.
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
50 # stdout is also piped...
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
51 def run_ffprobe(audio_file: str) -> subprocess.Popen:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
52 return subprocess.Popen(["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", audio_file], stdout=subprocess.PIPE, text=True)
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
53
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
54
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
55 def get_title_from_tags(j, default: str) -> str:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
56 # Is there an easier way to do this...?
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
57 for tag in j:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
58 if tag.lower() == "title":
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
59 return j[tag]
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
60
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
61 # Ugh
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
62 return default
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
63
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
64 def doit(audio_files: list[str], cover, output, blur) -> int:
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
65 ffprobes = [run_ffprobe(x) for x in audio_files]
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
66 ffmpeg = run_ffmpeg(audio_files, cover, output, blur)
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
67
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
68 # Wait for all ffprobe processes to finish their jobs
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
69 for f in ffprobes:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
70 f.wait()
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
71
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
72 # Iterate the list again, and add up all the times.
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
73 # This also accounts for milliseconds etc. to be
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
74 # more accurate
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
75 with open(output + ".txt", "w", encoding="utf-8") as fw:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
76 dur = 0.0
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
77 for f in ffprobes:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
78 j = json.load(f.stdout)
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
79 fw.write("%d:%02d:%02d %s\n" % (int(dur) // 3600, int(dur) % 3600 // 60, int(dur) % 60, get_title_from_tags(j["format"]["tags"], "OOPS....")))
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
80 dur += float(j["format"]["duration"])
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
81
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
82 # Finally, wait on the big ffmpeg process
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
83 ffmpeg.wait()
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
84
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
85 def main() -> int:
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
86 parser = argparse.ArgumentParser(prog='create', description='Creates album videos', epilog='(c) paper 2025 all rights reversed')
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
87 parser.add_argument('-c', '--cover', required=True)
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
88 parser.add_argument('-o', '--output', required=True)
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
89 parser.add_argument('-b', '--blur', action='store_true')
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
90 parser.add_argument('audio', action='extend', nargs='+', type=str)
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
91 args = parser.parse_args()
133
0d8eabdd12ab create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black
Paper <paper@tflc.us>
parents: 132
diff changeset
92 doit(args.audio, args.cover, args.output, args.blur)
132
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
93
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
94 if __name__ == "__main__":
71df0cf3aa05 add create.py
Paper <paper@tflc.us>
parents:
diff changeset
95 exit(main())