From c3cfc62973b3e5aa86f8fe80890e4ae3592911f0 Mon Sep 17 00:00:00 2001 From: Paul H Choi Date: Tue, 11 Jun 2024 21:28:26 -0400 Subject: [PATCH 1/5] refactor: 56min run --- .vscode/settings.json | 7 ++++ process-video.py | 85 +++++++++++++++++++++---------------------- utils/file.py | 11 ++++++ utils/media.py | 46 ++++++++++++++++++++++- utils/time.py | 7 ++++ utils/types.py | 14 ++----- 6 files changed, 114 insertions(+), 56 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6e4698d..993427b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,9 +11,11 @@ "Bitwarden", "compand", "crossfade", + "crossfadein", "crossfading", "dotenv", "escription", + "fadein", "ffprobe", "httpx", "lavfi", @@ -21,9 +23,14 @@ "loudnorm", "lxml", "mdfind", + "perfcounter", + "strerror", "studylight", + "subclip", "udio", "versetxt", + "videoclips", + "videofile", "xfade" ] } diff --git a/process-video.py b/process-video.py index 99a68a3..8cd4015 100755 --- a/process-video.py +++ b/process-video.py @@ -1,25 +1,27 @@ import os -import subprocess +import time from utils.config import parse_video_parameters +from utils.constants import OUTPUT_BASE_DIR from utils.file import ( create_and_change_directory, + delete_dir, ensure_dir_exists, is_valid_file, ) from utils.helpers import print_error, print_info, print_success from utils.media import ( - apply_video_compression, check_and_download, - crossfade_videos, + crossfade_videos_with_pymovie, download_media_from_youtube, - get_video_settings, get_video_upload_date, ) -from utils.types import PathsDict +from utils.time import format_seconds_to_readable from utils.ui import confirm_parameters def main() -> None: + warmup_perfcounter = time.perf_counter() + print_info("Processing video...") ensure_dir_exists("tmp") @@ -36,68 +38,63 @@ def main() -> None: ) try: - check_and_download(intro_url, "./tmp/intro.mp4") - check_and_download(outro_url, "./tmp/outro.mp4") + start_perfcounter = time.perf_counter() + # set the config vars and paths upload_date = get_video_upload_date(youtube_url) output_dir = create_and_change_directory(upload_date) + + intro_clip_path = os.path.join(OUTPUT_BASE_DIR, "intro.mp4") + outro_clip_path = os.path.join(OUTPUT_BASE_DIR, "outro.mp4") + final_path = os.path.join(output_dir, f"{upload_date}_final.mp4") + + # download the video clips + check_and_download(intro_url, intro_clip_path) + check_and_download(outro_url, outro_clip_path) downloaded_video_file = download_media_from_youtube( youtube_url, start_time, end_time, "video" ) - paths: PathsDict = { - "intro": { - "raw": "./tmp/intro.mp4", - "compressed": "./tmp/01-intro_compressed.mp4", - "crossfaded": "./tmp/04-intro-base_crossfaded.mp4", - }, - "base": { - "raw": f"./tmp/base_downloaded_raw.mp4", - "compressed": "./tmp/02-base_compressed.mp4", - "crossfaded": "./tmp/05-base-output_crossfaded.mp4", - }, - "outro": { - "raw": "./tmp/outro.mp4", - "compressed": "./tmp/03-outro_compressed.mp4", - }, - } - final_path = os.path.join(output_dir, f"{upload_date}_final.mp4") - if not is_valid_file(downloaded_video_file): raise ValueError( f"The file {downloaded_video_file} was not created or is too small. Please check for errors." ) - # apply "standard loudness" to all clips individually - for key in paths.keys(): - apply_video_compression(paths[key]["raw"], paths[key]["compressed"]) - - base_settings = get_video_settings(paths["base"]["compressed"]) + downloading_perfcounter = time.perf_counter() - # crossfade the intro to base - crossfade_videos( - paths["intro"]["compressed"], - paths["base"]["compressed"], - 1, - paths["intro"]["crossfaded"], - base_settings, - ) - - # crossfade the intro/base result to outro - crossfade_videos( - paths["intro"]["crossfaded"], - paths["outro"]["compressed"], + # stitch the clips together + crossfade_videos_with_pymovie( + [ + intro_clip_path, + downloaded_video_file, + outro_clip_path, + ], 1, final_path, - base_settings, ) + crossfade_perfcounter = time.perf_counter() print_success("Video processing complete.") + end_perfcounter = time.perf_counter() + + # print the performance timing + print_success( + f"Downloading took : {format_seconds_to_readable(downloading_perfcounter - start_perfcounter)} seconds." + ) + print_success( + f"Crossfading took : {format_seconds_to_readable(crossfade_perfcounter - downloading_perfcounter)} seconds." + ) + print_success( + f"Total process took: {format_seconds_to_readable(end_perfcounter - start_perfcounter)} seconds." + ) + except ValueError as ve: print_error(f"File validation error: {ve}") except Exception as e: print_error(f"An unexpected error occurred: {e}") + finally: + delete_dir("tmp") if __name__ == "__main__": diff --git a/utils/file.py b/utils/file.py index 53a5483..e7aa107 100644 --- a/utils/file.py +++ b/utils/file.py @@ -1,4 +1,5 @@ import os +import shutil import sys from utils.constants import OUTPUT_BASE_DIR @@ -13,6 +14,16 @@ def ensure_dir_exists(path: str): return directory +def delete_dir(path: str): + directory = os.path.join(".", path) + try: + shutil.rmtree(directory) + print(f"Directory '{directory}' has been deleted.") + except OSError as e: + print(f"Error deleting filepath {path}: {e.strerror} - {e.filename}") + return directory + + def ensure_file_exists(filename): if not os.path.exists(filename): print(f"Error: The file '{filename}' does not exist. Exiting.") diff --git a/utils/media.py b/utils/media.py index 67deb7b..36b558c 100644 --- a/utils/media.py +++ b/utils/media.py @@ -3,11 +3,18 @@ import subprocess import requests import os -from moviepy.editor import AudioFileClip, concatenate_audioclips, AudioClip +from moviepy.editor import ( + AudioFileClip, + concatenate_audioclips, + AudioClip, + VideoFileClip, + CompositeVideoClip, + afx, +) from typing import Literal from utils.file import ensure_dir_exists -from utils.helpers import run_command +from utils.helpers import print_info, run_command from utils.time import is_valid_time from utils.constants import SCRIPT_DIR @@ -318,6 +325,40 @@ def crossfade_videos( ) +def crossfade_videos_with_pymovie(video_paths, crossfade_duration, output_path): + # Ensure there are at least two videos to crossfade + if len(video_paths) < 2: + raise ValueError("At least two video paths are required for crossfading.") + + # Initialize list to hold video clips with their start times + clips = [] + total_duration = 0 # Keep track of the total duration after each clip is added + + # Load video clips and set their start times + for i, path in enumerate(video_paths): + clip = VideoFileClip(path) + # If it's not the first clip, set it to start where the last one starts to fade out + if i > 0: + start_time = max(0, total_duration - crossfade_duration) + clip = clip.set_start(start_time).crossfadein(crossfade_duration) + else: + # First clip starts at 0 + clip = clip.set_start(0) + + # Normalize the audio of each clip + clip = clip.fx(afx.audio_normalize) + + clips.append(clip) + # Update total_duration to the end of the current clip, not including the crossfade + total_duration += clip.duration - crossfade_duration if i > 0 else clip.duration + + # Create the crossfade clip by overlaying the clips + final_clip = CompositeVideoClip(clips, size=clips[0].size) + + # Write the output video file with crossfade + final_clip.write_videofile(output_path) + + def trim_base_video(video_path, segment_duration, base_settings): tmp_dir = ensure_dir_exists("tmp") @@ -442,6 +483,7 @@ def download_video(s3_url, local_path): def check_and_download(video_url, file_path): if not os.path.exists(file_path): + print_info(f"File {file_path} doesn't exist, downloading...") download_video(video_url, file_path) else: print(f"File {file_path} already exists. Skipping download.") diff --git a/utils/time.py b/utils/time.py index 4910b67..ed0554f 100644 --- a/utils/time.py +++ b/utils/time.py @@ -21,3 +21,10 @@ def get_formatted_date(input_date): formatted_date = date_obj.strftime("%A, %B %d, %Y") return formatted_date + + +def format_seconds_to_readable(seconds): + """Converts a number of seconds to a formatted string showing hours, minutes, and seconds.""" + hours, remainder = divmod(seconds, 3600) + minutes, seconds = divmod(remainder, 60) + return f"{int(hours)} hours, {int(minutes)} minutes, {seconds:.2f} seconds" diff --git a/utils/types.py b/utils/types.py index 41cfece..5189c79 100644 --- a/utils/types.py +++ b/utils/types.py @@ -1,13 +1,7 @@ -from typing import Optional, TypedDict - - -class VideoPaths(TypedDict): - raw: str - compressed: str - crossfaded: Optional[str] +from typing import TypedDict class PathsDict(TypedDict, total=False): - intro: VideoPaths - base: VideoPaths - outro: VideoPaths + intro: str + base: str + outro: str From fc88205b358e6dec64016005b1b6d54e2cd045b4 Mon Sep 17 00:00:00 2001 From: Paul H Choi Date: Tue, 11 Jun 2024 22:34:18 -0400 Subject: [PATCH 2/5] testing re-encoding --- process-video-pymovie.py | 104 +++++++++++++++++++++++++++++++++++++++ process-video.py | 6 +-- utils/media.py | 51 +++++++------------ 3 files changed, 125 insertions(+), 36 deletions(-) create mode 100644 process-video-pymovie.py diff --git a/process-video-pymovie.py b/process-video-pymovie.py new file mode 100644 index 0000000..99a68a3 --- /dev/null +++ b/process-video-pymovie.py @@ -0,0 +1,104 @@ +import os +import subprocess +from utils.config import parse_video_parameters +from utils.file import ( + create_and_change_directory, + ensure_dir_exists, + is_valid_file, +) +from utils.helpers import print_error, print_info, print_success +from utils.media import ( + apply_video_compression, + check_and_download, + crossfade_videos, + download_media_from_youtube, + get_video_settings, + get_video_upload_date, +) +from utils.types import PathsDict +from utils.ui import confirm_parameters + + +def main() -> None: + print_info("Processing video...") + + ensure_dir_exists("tmp") + ensure_dir_exists("data") + + youtube_url, start_time, end_time, intro_url, outro_url = parse_video_parameters() + + confirm_parameters( + { + "URL": youtube_url, + "Start": start_time, + "End": end_time, + } + ) + + try: + check_and_download(intro_url, "./tmp/intro.mp4") + check_and_download(outro_url, "./tmp/outro.mp4") + + upload_date = get_video_upload_date(youtube_url) + output_dir = create_and_change_directory(upload_date) + downloaded_video_file = download_media_from_youtube( + youtube_url, start_time, end_time, "video" + ) + + paths: PathsDict = { + "intro": { + "raw": "./tmp/intro.mp4", + "compressed": "./tmp/01-intro_compressed.mp4", + "crossfaded": "./tmp/04-intro-base_crossfaded.mp4", + }, + "base": { + "raw": f"./tmp/base_downloaded_raw.mp4", + "compressed": "./tmp/02-base_compressed.mp4", + "crossfaded": "./tmp/05-base-output_crossfaded.mp4", + }, + "outro": { + "raw": "./tmp/outro.mp4", + "compressed": "./tmp/03-outro_compressed.mp4", + }, + } + final_path = os.path.join(output_dir, f"{upload_date}_final.mp4") + + if not is_valid_file(downloaded_video_file): + raise ValueError( + f"The file {downloaded_video_file} was not created or is too small. Please check for errors." + ) + + # apply "standard loudness" to all clips individually + for key in paths.keys(): + apply_video_compression(paths[key]["raw"], paths[key]["compressed"]) + + base_settings = get_video_settings(paths["base"]["compressed"]) + + # crossfade the intro to base + crossfade_videos( + paths["intro"]["compressed"], + paths["base"]["compressed"], + 1, + paths["intro"]["crossfaded"], + base_settings, + ) + + # crossfade the intro/base result to outro + crossfade_videos( + paths["intro"]["crossfaded"], + paths["outro"]["compressed"], + 1, + final_path, + base_settings, + ) + + print_success("Video processing complete.") + + except ValueError as ve: + print_error(f"File validation error: {ve}") + except Exception as e: + print_error(f"An unexpected error occurred: {e}") + + +if __name__ == "__main__": + main() diff --git a/process-video.py b/process-video.py index 8cd4015..b1b0408 100755 --- a/process-video.py +++ b/process-video.py @@ -69,7 +69,7 @@ def main() -> None: downloaded_video_file, outro_clip_path, ], - 1, + 2, final_path, ) crossfade_perfcounter = time.perf_counter() @@ -86,15 +86,13 @@ def main() -> None: f"Crossfading took : {format_seconds_to_readable(crossfade_perfcounter - downloading_perfcounter)} seconds." ) print_success( - f"Total process took: {format_seconds_to_readable(end_perfcounter - start_perfcounter)} seconds." + f"Total process took: {format_seconds_to_readable(end_perfcounter - start_perfcounter)}." ) except ValueError as ve: print_error(f"File validation error: {ve}") except Exception as e: print_error(f"An unexpected error occurred: {e}") - finally: - delete_dir("tmp") if __name__ == "__main__": diff --git a/utils/media.py b/utils/media.py index 36b558c..d40aee0 100644 --- a/utils/media.py +++ b/utils/media.py @@ -94,46 +94,31 @@ def download_media_from_youtube( if not is_valid_time(start_time) or not is_valid_time(end_time): raise ValueError("Invalid time format") - # Define filename based on media type media_filename = os.path.join( "tmp", f"base_downloaded_raw.{'mp3' if media_type == 'audio' else 'mp4'}", ) - # Configuring command for different media types - if media_type == "audio": - command = [ - "yt-dlp", - "--progress", - "-x", - "--audio-format", - "mp3", - "-o", - media_filename, - youtube_url, - ] - elif media_type == "video": - command = [ - "yt-dlp", - "--progress", - "--format", - "bestvideo+bestaudio", - "--merge-output-format", - "mp4", - "-o", - media_filename, - youtube_url, - ] - else: - raise ValueError("Invalid media type specified") + command = [ + "yt-dlp", + "--progress", + "-o", + media_filename, + "--format", + "bestaudio" if media_type == "audio" else "bestvideo+bestaudio", + "--merge-output-format", + "mp4" if media_type == "video" else "mp3", + youtube_url, + ] - # Apply trimming if necessary if start_time != "00:00:00" or end_time != "00:00:00": - command.extend( - ["--postprocessor-args", f"ffmpeg:-ss {start_time} -to {end_time}"] + postprocessor_args = ( + f"ffmpeg:-ss {start_time} -to {end_time} -avoid_negative_ts make_zero" ) + if media_type == "video": + postprocessor_args += " -c:v libx264 -c:a aac" # Re-encoding for video + command.extend(["--postprocessor-args", postprocessor_args]) - # Execute download command try: subprocess.run(command, check=True) except subprocess.CalledProcessError as e: @@ -356,7 +341,9 @@ def crossfade_videos_with_pymovie(video_paths, crossfade_duration, output_path): final_clip = CompositeVideoClip(clips, size=clips[0].size) # Write the output video file with crossfade - final_clip.write_videofile(output_path) + final_clip.write_videofile( + output_path, codec="libx264", audio_codec="aac", audio_fps=48000 + ) def trim_base_video(video_path, segment_duration, base_settings): From 26ea00fa49969d42f059893c0b041368b70090f9 Mon Sep 17 00:00:00 2001 From: Paul H Choi Date: Tue, 11 Jun 2024 23:36:03 -0400 Subject: [PATCH 3/5] re-add ffmpog audio normalization and compression --- process-video copy.py | 104 ++++++++++++++++++++++++++++++++++++++++++ process-video.py | 13 +++++- utils/media.py | 11 +++-- 3 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 process-video copy.py diff --git a/process-video copy.py b/process-video copy.py new file mode 100644 index 0000000..99a68a3 --- /dev/null +++ b/process-video copy.py @@ -0,0 +1,104 @@ +import os +import subprocess +from utils.config import parse_video_parameters +from utils.file import ( + create_and_change_directory, + ensure_dir_exists, + is_valid_file, +) +from utils.helpers import print_error, print_info, print_success +from utils.media import ( + apply_video_compression, + check_and_download, + crossfade_videos, + download_media_from_youtube, + get_video_settings, + get_video_upload_date, +) +from utils.types import PathsDict +from utils.ui import confirm_parameters + + +def main() -> None: + print_info("Processing video...") + + ensure_dir_exists("tmp") + ensure_dir_exists("data") + + youtube_url, start_time, end_time, intro_url, outro_url = parse_video_parameters() + + confirm_parameters( + { + "URL": youtube_url, + "Start": start_time, + "End": end_time, + } + ) + + try: + check_and_download(intro_url, "./tmp/intro.mp4") + check_and_download(outro_url, "./tmp/outro.mp4") + + upload_date = get_video_upload_date(youtube_url) + output_dir = create_and_change_directory(upload_date) + downloaded_video_file = download_media_from_youtube( + youtube_url, start_time, end_time, "video" + ) + + paths: PathsDict = { + "intro": { + "raw": "./tmp/intro.mp4", + "compressed": "./tmp/01-intro_compressed.mp4", + "crossfaded": "./tmp/04-intro-base_crossfaded.mp4", + }, + "base": { + "raw": f"./tmp/base_downloaded_raw.mp4", + "compressed": "./tmp/02-base_compressed.mp4", + "crossfaded": "./tmp/05-base-output_crossfaded.mp4", + }, + "outro": { + "raw": "./tmp/outro.mp4", + "compressed": "./tmp/03-outro_compressed.mp4", + }, + } + final_path = os.path.join(output_dir, f"{upload_date}_final.mp4") + + if not is_valid_file(downloaded_video_file): + raise ValueError( + f"The file {downloaded_video_file} was not created or is too small. Please check for errors." + ) + + # apply "standard loudness" to all clips individually + for key in paths.keys(): + apply_video_compression(paths[key]["raw"], paths[key]["compressed"]) + + base_settings = get_video_settings(paths["base"]["compressed"]) + + # crossfade the intro to base + crossfade_videos( + paths["intro"]["compressed"], + paths["base"]["compressed"], + 1, + paths["intro"]["crossfaded"], + base_settings, + ) + + # crossfade the intro/base result to outro + crossfade_videos( + paths["intro"]["crossfaded"], + paths["outro"]["compressed"], + 1, + final_path, + base_settings, + ) + + print_success("Video processing complete.") + + except ValueError as ve: + print_error(f"File validation error: {ve}") + except Exception as e: + print_error(f"An unexpected error occurred: {e}") + + +if __name__ == "__main__": + main() diff --git a/process-video.py b/process-video.py index b1b0408..a470c5c 100755 --- a/process-video.py +++ b/process-video.py @@ -10,6 +10,7 @@ ) from utils.helpers import print_error, print_info, print_success from utils.media import ( + apply_video_compression, check_and_download, crossfade_videos_with_pymovie, download_media_from_youtube, @@ -62,11 +63,16 @@ def main() -> None: downloading_perfcounter = time.perf_counter() + downloaded_video_compressed = "./tmp/base_compressed.mp4" + apply_video_compression(downloaded_video_file, downloaded_video_compressed) + + compression_perfcounter = time.perf_counter() + # stitch the clips together crossfade_videos_with_pymovie( [ intro_clip_path, - downloaded_video_file, + downloaded_video_compressed, outro_clip_path, ], 2, @@ -83,7 +89,10 @@ def main() -> None: f"Downloading took : {format_seconds_to_readable(downloading_perfcounter - start_perfcounter)} seconds." ) print_success( - f"Crossfading took : {format_seconds_to_readable(crossfade_perfcounter - downloading_perfcounter)} seconds." + f"Compressing took : {format_seconds_to_readable(compression_perfcounter - downloading_perfcounter)} seconds." + ) + print_success( + f"Crossfading took : {format_seconds_to_readable(crossfade_perfcounter - compression_perfcounter)} seconds." ) print_success( f"Total process took: {format_seconds_to_readable(end_perfcounter - start_perfcounter)}." diff --git a/utils/media.py b/utils/media.py index d40aee0..9ad8a3f 100644 --- a/utils/media.py +++ b/utils/media.py @@ -10,6 +10,7 @@ VideoFileClip, CompositeVideoClip, afx, + vfx, ) from typing import Literal @@ -327,12 +328,8 @@ def crossfade_videos_with_pymovie(video_paths, crossfade_duration, output_path): start_time = max(0, total_duration - crossfade_duration) clip = clip.set_start(start_time).crossfadein(crossfade_duration) else: - # First clip starts at 0 clip = clip.set_start(0) - # Normalize the audio of each clip - clip = clip.fx(afx.audio_normalize) - clips.append(clip) # Update total_duration to the end of the current clip, not including the crossfade total_duration += clip.duration - crossfade_duration if i > 0 else clip.duration @@ -342,7 +339,11 @@ def crossfade_videos_with_pymovie(video_paths, crossfade_duration, output_path): # Write the output video file with crossfade final_clip.write_videofile( - output_path, codec="libx264", audio_codec="aac", audio_fps=48000 + output_path, + codec="libx264", + audio_codec="aac", + audio_fps=48000, + threads=8, ) From 7b0e24e071e190da8e0ab66c4a065400d4734e15 Mon Sep 17 00:00:00 2001 From: Paul H Choi Date: Sat, 15 Jun 2024 11:53:35 -0400 Subject: [PATCH 4/5] make base downloads in tmp unique --- process-video copy.py | 104 --------------------------------------- process-video-pymovie.py | 104 --------------------------------------- process-video.py | 4 +- utils/media.py | 3 +- 4 files changed, 4 insertions(+), 211 deletions(-) delete mode 100644 process-video copy.py delete mode 100644 process-video-pymovie.py diff --git a/process-video copy.py b/process-video copy.py deleted file mode 100644 index 99a68a3..0000000 --- a/process-video copy.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import subprocess -from utils.config import parse_video_parameters -from utils.file import ( - create_and_change_directory, - ensure_dir_exists, - is_valid_file, -) -from utils.helpers import print_error, print_info, print_success -from utils.media import ( - apply_video_compression, - check_and_download, - crossfade_videos, - download_media_from_youtube, - get_video_settings, - get_video_upload_date, -) -from utils.types import PathsDict -from utils.ui import confirm_parameters - - -def main() -> None: - print_info("Processing video...") - - ensure_dir_exists("tmp") - ensure_dir_exists("data") - - youtube_url, start_time, end_time, intro_url, outro_url = parse_video_parameters() - - confirm_parameters( - { - "URL": youtube_url, - "Start": start_time, - "End": end_time, - } - ) - - try: - check_and_download(intro_url, "./tmp/intro.mp4") - check_and_download(outro_url, "./tmp/outro.mp4") - - upload_date = get_video_upload_date(youtube_url) - output_dir = create_and_change_directory(upload_date) - downloaded_video_file = download_media_from_youtube( - youtube_url, start_time, end_time, "video" - ) - - paths: PathsDict = { - "intro": { - "raw": "./tmp/intro.mp4", - "compressed": "./tmp/01-intro_compressed.mp4", - "crossfaded": "./tmp/04-intro-base_crossfaded.mp4", - }, - "base": { - "raw": f"./tmp/base_downloaded_raw.mp4", - "compressed": "./tmp/02-base_compressed.mp4", - "crossfaded": "./tmp/05-base-output_crossfaded.mp4", - }, - "outro": { - "raw": "./tmp/outro.mp4", - "compressed": "./tmp/03-outro_compressed.mp4", - }, - } - final_path = os.path.join(output_dir, f"{upload_date}_final.mp4") - - if not is_valid_file(downloaded_video_file): - raise ValueError( - f"The file {downloaded_video_file} was not created or is too small. Please check for errors." - ) - - # apply "standard loudness" to all clips individually - for key in paths.keys(): - apply_video_compression(paths[key]["raw"], paths[key]["compressed"]) - - base_settings = get_video_settings(paths["base"]["compressed"]) - - # crossfade the intro to base - crossfade_videos( - paths["intro"]["compressed"], - paths["base"]["compressed"], - 1, - paths["intro"]["crossfaded"], - base_settings, - ) - - # crossfade the intro/base result to outro - crossfade_videos( - paths["intro"]["crossfaded"], - paths["outro"]["compressed"], - 1, - final_path, - base_settings, - ) - - print_success("Video processing complete.") - - except ValueError as ve: - print_error(f"File validation error: {ve}") - except Exception as e: - print_error(f"An unexpected error occurred: {e}") - - -if __name__ == "__main__": - main() diff --git a/process-video-pymovie.py b/process-video-pymovie.py deleted file mode 100644 index 99a68a3..0000000 --- a/process-video-pymovie.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import subprocess -from utils.config import parse_video_parameters -from utils.file import ( - create_and_change_directory, - ensure_dir_exists, - is_valid_file, -) -from utils.helpers import print_error, print_info, print_success -from utils.media import ( - apply_video_compression, - check_and_download, - crossfade_videos, - download_media_from_youtube, - get_video_settings, - get_video_upload_date, -) -from utils.types import PathsDict -from utils.ui import confirm_parameters - - -def main() -> None: - print_info("Processing video...") - - ensure_dir_exists("tmp") - ensure_dir_exists("data") - - youtube_url, start_time, end_time, intro_url, outro_url = parse_video_parameters() - - confirm_parameters( - { - "URL": youtube_url, - "Start": start_time, - "End": end_time, - } - ) - - try: - check_and_download(intro_url, "./tmp/intro.mp4") - check_and_download(outro_url, "./tmp/outro.mp4") - - upload_date = get_video_upload_date(youtube_url) - output_dir = create_and_change_directory(upload_date) - downloaded_video_file = download_media_from_youtube( - youtube_url, start_time, end_time, "video" - ) - - paths: PathsDict = { - "intro": { - "raw": "./tmp/intro.mp4", - "compressed": "./tmp/01-intro_compressed.mp4", - "crossfaded": "./tmp/04-intro-base_crossfaded.mp4", - }, - "base": { - "raw": f"./tmp/base_downloaded_raw.mp4", - "compressed": "./tmp/02-base_compressed.mp4", - "crossfaded": "./tmp/05-base-output_crossfaded.mp4", - }, - "outro": { - "raw": "./tmp/outro.mp4", - "compressed": "./tmp/03-outro_compressed.mp4", - }, - } - final_path = os.path.join(output_dir, f"{upload_date}_final.mp4") - - if not is_valid_file(downloaded_video_file): - raise ValueError( - f"The file {downloaded_video_file} was not created or is too small. Please check for errors." - ) - - # apply "standard loudness" to all clips individually - for key in paths.keys(): - apply_video_compression(paths[key]["raw"], paths[key]["compressed"]) - - base_settings = get_video_settings(paths["base"]["compressed"]) - - # crossfade the intro to base - crossfade_videos( - paths["intro"]["compressed"], - paths["base"]["compressed"], - 1, - paths["intro"]["crossfaded"], - base_settings, - ) - - # crossfade the intro/base result to outro - crossfade_videos( - paths["intro"]["crossfaded"], - paths["outro"]["compressed"], - 1, - final_path, - base_settings, - ) - - print_success("Video processing complete.") - - except ValueError as ve: - print_error(f"File validation error: {ve}") - except Exception as e: - print_error(f"An unexpected error occurred: {e}") - - -if __name__ == "__main__": - main() diff --git a/process-video.py b/process-video.py index a470c5c..d9535f9 100755 --- a/process-video.py +++ b/process-video.py @@ -53,7 +53,7 @@ def main() -> None: check_and_download(intro_url, intro_clip_path) check_and_download(outro_url, outro_clip_path) downloaded_video_file = download_media_from_youtube( - youtube_url, start_time, end_time, "video" + youtube_url, start_time, end_time, upload_date, "video" ) if not is_valid_file(downloaded_video_file): @@ -63,7 +63,7 @@ def main() -> None: downloading_perfcounter = time.perf_counter() - downloaded_video_compressed = "./tmp/base_compressed.mp4" + downloaded_video_compressed = f"./tmp/{upload_date}_base_compressed.mp4" apply_video_compression(downloaded_video_file, downloaded_video_compressed) compression_perfcounter = time.perf_counter() diff --git a/utils/media.py b/utils/media.py index 9ad8a3f..dd387be 100644 --- a/utils/media.py +++ b/utils/media.py @@ -90,6 +90,7 @@ def download_media_from_youtube( youtube_url: str, start_time: str, end_time: str, + upload_date: str, media_type: Literal["audio", "video"] = "audio", ) -> str: if not is_valid_time(start_time) or not is_valid_time(end_time): @@ -97,7 +98,7 @@ def download_media_from_youtube( media_filename = os.path.join( "tmp", - f"base_downloaded_raw.{'mp3' if media_type == 'audio' else 'mp4'}", + f"{upload_date}_base_downloaded_raw.{'mp3' if media_type == 'audio' else 'mp4'}", ) command = [ From a574f4a3d8af2196de07d2cf7997659bd696817d Mon Sep 17 00:00:00 2001 From: Paul H Choi Date: Sun, 16 Jun 2024 21:03:30 -0400 Subject: [PATCH 5/5] fix audio process --- utils/media.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/utils/media.py b/utils/media.py index dd387be..7b60e96 100644 --- a/utils/media.py +++ b/utils/media.py @@ -96,9 +96,9 @@ def download_media_from_youtube( if not is_valid_time(start_time) or not is_valid_time(end_time): raise ValueError("Invalid time format") + file_extension = "mp3" if media_type == "audio" else "mp4" media_filename = os.path.join( - "tmp", - f"{upload_date}_base_downloaded_raw.{'mp3' if media_type == 'audio' else 'mp4'}", + "tmp", f"{upload_date}_base_downloaded_raw.{file_extension}" ) command = [ @@ -108,11 +108,14 @@ def download_media_from_youtube( media_filename, "--format", "bestaudio" if media_type == "audio" else "bestvideo+bestaudio", - "--merge-output-format", - "mp4" if media_type == "video" else "mp3", - youtube_url, ] + if media_type == "audio": + command.extend(["-x", "--audio-format", "mp3"]) + elif media_type == "video": + command.append("--merge-output-format") + command.append("mp4") + if start_time != "00:00:00" or end_time != "00:00:00": postprocessor_args = ( f"ffmpeg:-ss {start_time} -to {end_time} -avoid_negative_ts make_zero" @@ -121,6 +124,8 @@ def download_media_from_youtube( postprocessor_args += " -c:v libx264 -c:a aac" # Re-encoding for video command.extend(["--postprocessor-args", postprocessor_args]) + command.append(youtube_url) + try: subprocess.run(command, check=True) except subprocess.CalledProcessError as e: