changeset 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
files create.py
diffstat 1 files changed, 16 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/create.py	Fri Jan 02 10:35:03 2026 -0500
+++ b/create.py	Sat Jan 03 20:25:38 2026 -0500
@@ -7,6 +7,12 @@
 import json
 import subprocess
 
+def build_vf(blur) -> list[str]:
+	if blur:
+		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"]
+
+	return ["-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2"]
+
 # Build filter string
 def build_filter_string(length: int, cover: bool) -> str:
     f = ""
@@ -16,7 +22,7 @@
     f += ("concat=n=%d:v=0:a=1[out]" % length)
     return f
 
-def build_arguments_list(audio_files: list[str], cover, output) -> list:
+def build_arguments_list(audio_files: list[str], cover, output, blur) -> list:
     args = ["ffmpeg", "-y"]
     if not cover is None:
         args.extend(["-r", "1", "-loop", "1", "-i", cover])
@@ -26,15 +32,16 @@
     args.extend(["-filter_complex", build_filter_string(len(audio_files), False if cover is None else True), "-map", "[out]"])
     if not cover is None:
         # Letterbox to 1920x1080
-        args.extend(["-map", "0:v", "-tune", "stillimage", "-c:v", "libx264", "-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2"])
+        args.extend(["-map", "0:v", "-tune", "stillimage", "-c:v", "libx264"])
+        args.extend(build_vf(blur))
     args.extend(["-shortest"])
     args.append(output + ".mkv")
     return args
 
 
 # Returns a Popen object that should be waited on
-def run_ffmpeg(audio_files: list[str], cover, output) -> subprocess.Popen:
-    args = build_arguments_list(audio_files, cover, output)
+def run_ffmpeg(audio_files: list[str], cover, output, blur) -> subprocess.Popen:
+    args = build_arguments_list(audio_files, cover, output, blur)
     print(args)
     return subprocess.Popen(args)
 
@@ -54,9 +61,9 @@
     # Ugh
     return default
 
-def doit(audio_files: list[str], cover, output) -> int:
+def doit(audio_files: list[str], cover, output, blur) -> int:
     ffprobes = [run_ffprobe(x) for x in audio_files]
-    ffmpeg = run_ffmpeg(audio_files, cover, output)
+    ffmpeg = run_ffmpeg(audio_files, cover, output, blur)
 
     # Wait for all ffprobe processes  to finish their jobs
     for f in ffprobes:
@@ -69,7 +76,7 @@
         dur = 0.0
         for f in ffprobes:
             j = json.load(f.stdout)
-            fw.write("%d:%02d %s\n" % (int(dur) // 60, int(dur) % 60, get_title_from_tags(j["format"]["tags"], "OOPS....")))
+            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....")))
             dur += float(j["format"]["duration"])
 
     # Finally, wait on the big ffmpeg process
@@ -79,9 +86,10 @@
     parser = argparse.ArgumentParser(prog='create', description='Creates album videos', epilog='(c) paper 2025 all rights reversed')
     parser.add_argument('-c', '--cover', required=True)
     parser.add_argument('-o', '--output', required=True)
+    parser.add_argument('-b', '--blur', action='store_true')
     parser.add_argument('audio', action='extend', nargs='+', type=str)
     args = parser.parse_args()
-    doit(args.audio, args.cover, args.output)
+    doit(args.audio, args.cover, args.output, args.blur)
 
 if __name__ == "__main__":
     exit(main())