diff --git a/.flake8 b/.flake8 index 21c8158..f810726 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,8 @@ [flake8] -per-file-ignores = ./skelly_synchronize/tests/*:S101 +per-file-ignores = + ./skelly_synchronize/tests/*:S101 + ./skelly_synchronize/__init__.py:F401 max-line-length = 88 diff --git a/skelly_synchronize/core_processes/audio_utilities.py b/skelly_synchronize/core_processes/audio_utilities.py index 64319c3..6bb1c4e 100644 --- a/skelly_synchronize/core_processes/audio_utilities.py +++ b/skelly_synchronize/core_processes/audio_utilities.py @@ -12,6 +12,8 @@ from skelly_synchronize.system.file_extensions import AudioExtension from skelly_synchronize.system.paths_and_file_names import TRIMMED_AUDIO_FOLDER_NAME +logger = logging.getLogger(__name__) + def get_audio_sample_rates(video_info_dict: Dict[str, dict]) -> list: """Get the sample rates of each audio file and return them in a list""" @@ -53,7 +55,7 @@ def extract_audio_files( audio_signal, sample_rate = librosa.load(path=audio_file_path, sr=None) audio_duration = librosa.get_duration(y=audio_signal, sr=sample_rate) - logging.info(f"audio file {audio_name} is {audio_duration} seconds long") + logger.info(f"audio file {audio_name} is {audio_duration} seconds long") audio_signal_dict[audio_name] = { "audio file": audio_signal, "sample rate": sample_rate, @@ -70,7 +72,7 @@ def trim_audio_files( synced_video_length: float, audio_extension: AudioExtension = AudioExtension.WAV, ): - logging.info("Trimming audio files to match synchronized video length") + logger.info("Trimming audio files to match synchronized video length") trimmed_audio_folder_path = Path(audio_folder_path) / TRIMMED_AUDIO_FOLDER_NAME trimmed_audio_folder_path.mkdir(parents=True, exist_ok=True) @@ -89,7 +91,7 @@ def trim_audio_files( audio_filename = f"{audio_filepath.stem}.{AudioExtension.WAV.value}" - logging.info(f"Saving audio {audio_filename}") + logger.info(f"Saving audio {audio_filename}") output_path = trimmed_audio_folder_path / audio_filename sf.write(output_path, shortened_audio_signal, sr, subtype="PCM_24") diff --git a/skelly_synchronize/core_processes/correlation_functions.py b/skelly_synchronize/core_processes/correlation_functions.py index b019328..085db56 100644 --- a/skelly_synchronize/core_processes/correlation_functions.py +++ b/skelly_synchronize/core_processes/correlation_functions.py @@ -8,6 +8,8 @@ from skelly_synchronize.system.file_extensions import NUMPY_EXTENSION from skelly_synchronize.system.paths_and_file_names import BRIGHTNESS_SUFFIX +logger = logging.getLogger(__name__) + def cross_correlate(audio1: np.ndarray, audio2: np.ndarray): """Take two audio files, synchronize them using cross correlation, and trim them to the same length. @@ -28,7 +30,7 @@ def cross_correlate(audio1: np.ndarray, audio2: np.ndarray): def find_first_brightness_change( video_pathstring: str, brightness_ratio_threshold: float = 1000 ) -> int: - logging.info(f"Detecting first brightness change in {video_pathstring}") + logger.info(f"Detecting first brightness change in {video_pathstring}") brightness_array = find_brightness_across_frames(video_pathstring) brightness_difference = np.diff(brightness_array, prepend=brightness_array[0]) brightness_double_difference = np.diff( @@ -42,12 +44,12 @@ def find_first_brightness_change( ) if first_brightness_change == 0: - logging.info( + logger.info( "No brightness change exceeded threshold, defaulting to frame with fastest detected brightness change" ) first_brightness_change = np.argmax(brightness_double_difference) else: - logging.info( + logger.info( f"First brightness change detected at frame number {first_brightness_change}" ) @@ -95,7 +97,7 @@ def find_cross_correlation_lags( The lag dict is normalized so that the lag of the latest video to start in time is 0, and all other lags are positive. """ comparison_file_key = next(iter(audio_signal_dict)) - logging.info( + logger.info( f"comparison file is: {comparison_file_key}, sample rate is: {sample_rate}" ) @@ -110,7 +112,7 @@ def find_cross_correlation_lags( normalized_lag_dict = normalize_lag_dictionary(lag_dictionary=lag_dict) - logging.info( + logger.info( f"original lag dict: {lag_dict} normalized lag dict: {normalized_lag_dict}" ) diff --git a/skelly_synchronize/core_processes/debugging/debug_plots.py b/skelly_synchronize/core_processes/debugging/debug_plots.py index 837de3f..819fcfc 100644 --- a/skelly_synchronize/core_processes/debugging/debug_plots.py +++ b/skelly_synchronize/core_processes/debugging/debug_plots.py @@ -13,6 +13,8 @@ TRIMMED_AUDIO_FOLDER_NAME, ) +logger = logging.getLogger(__name__) + def create_brightness_debug_plots( raw_video_folder_path: Path, synchronized_video_folder_path: Path @@ -26,7 +28,7 @@ def create_brightness_debug_plots( folder_path=synchronized_video_folder_path ) - logging.info("Creating debug plots") + logger.info("Creating debug plots") plot_brightness_across_frames( raw_brightness_npys=list_of_raw_brightness_paths, trimmed_brightness_npys=list_of_trimmed_brightness_paths, @@ -42,7 +44,7 @@ def create_audio_debug_plots(synchronized_video_folder_path: Path): list_of_raw_audio_paths = get_audio_paths_from_folder(raw_audio_folder_path) list_of_trimmed_audio_paths = get_audio_paths_from_folder(trimmed_audio_folder_path) - logging.info("Creating debug plots") + logger.info("Creating debug plots") plot_audio_waveforms( raw_audio_filepath_list=list_of_raw_audio_paths, trimmed_audio_filepath_list=list_of_trimmed_audio_paths, @@ -91,7 +93,7 @@ def plot_brightness_across_frames( axs[1].plot(time, brightness_array, alpha=0.5) - logging.info(f"Saving debug plots to: {output_filepath}") + logger.info(f"Saving debug plots to: {output_filepath}") plt.savefig(output_filepath) @@ -124,5 +126,5 @@ def plot_audio_waveforms( axs[1].plot(time, audio_signal, alpha=0.4) - logging.info(f"Saving debug plots to: {output_filepath}") + logger.info(f"Saving debug plots to: {output_filepath}") plt.savefig(output_filepath) diff --git a/skelly_synchronize/core_processes/video_functions/deffcode_functions.py b/skelly_synchronize/core_processes/video_functions/deffcode_functions.py index e97bf0f..d271d5b 100644 --- a/skelly_synchronize/core_processes/video_functions/deffcode_functions.py +++ b/skelly_synchronize/core_processes/video_functions/deffcode_functions.py @@ -7,6 +7,8 @@ check_if_video_has_reversed_metadata, ) +logger = logging.getLogger(__name__) + def trim_single_video_deffcode( input_video_pathstring: str, @@ -18,7 +20,7 @@ def trim_single_video_deffcode( ) if vertical_video_bool: - logging.info("Video has reversed metadata, changing FFmpeg transpose argument") + logger.info("Video has reversed metadata, changing FFmpeg transpose argument") ffparams = {"-ffprefixes": ["-noautorotate"], "-vf": "transpose=1"} else: ffparams = {} diff --git a/skelly_synchronize/core_processes/video_functions/ffmpeg_functions.py b/skelly_synchronize/core_processes/video_functions/ffmpeg_functions.py index d45b2db..b72625e 100644 --- a/skelly_synchronize/core_processes/video_functions/ffmpeg_functions.py +++ b/skelly_synchronize/core_processes/video_functions/ffmpeg_functions.py @@ -1,15 +1,36 @@ +import logging import subprocess +import shutil from pathlib import Path from typing import Union from skelly_synchronize.system.file_extensions import AudioExtension +logger = logging.getLogger(__name__) + +ffmpeg_string = "ffmpeg" +ffprobe_string = "ffprobe" + + +def check_for_ffmpeg(): + if shutil.which(ffmpeg_string) is None: + raise FileNotFoundError( + "ffmpeg not found, please install ffmpeg and add it to your PATH" + ) + + +def check_for_ffprobe(): + if shutil.which(ffprobe_string) is None: + raise FileNotFoundError( + "ffprobe not found, please install ffmpeg and add it to your PATH" + ) + def extract_audio_from_video_ffmpeg( file_pathstring: str, output_file_path: Union[Path, str] ): """Run a subprocess call to extract the audio from a video file using ffmpeg""" - + check_for_ffmpeg() if str(Path(output_file_path).suffix).strip(".") not in { extension.value for extension in AudioExtension }: @@ -19,7 +40,7 @@ def extract_audio_from_video_ffmpeg( extract_audio_subprocess = subprocess.run( [ - "ffmpeg", + ffmpeg_string, "-y", "-i", file_pathstring, @@ -38,9 +59,10 @@ def extract_audio_from_video_ffmpeg( def extract_video_duration_ffmpeg(file_pathstring: str): """Run a subprocess call to get the duration from a video file using ffmpeg""" + check_for_ffprobe() extract_duration_subprocess = subprocess.run( [ - "ffprobe", + ffprobe_string, "-v", "error", "-show_entries", @@ -66,9 +88,10 @@ def extract_video_duration_ffmpeg(file_pathstring: str): def extract_video_fps_ffmpeg(file_pathstring: str): """Run a subprocess call to get the fps of a video file using ffmpeg""" + check_for_ffprobe() extract_fps_subprocess = subprocess.run( [ - "ffprobe", + ffprobe_string, "-v", "error", "-select_streams", @@ -99,9 +122,10 @@ def extract_video_fps_ffmpeg(file_pathstring: str): def extract_audio_sample_rate_ffmpeg(file_pathstring: str): """Run a subprocess call to get the audio sample rate of a video file using ffmpeg""" + check_for_ffprobe() extract_sample_rate_subprocess = subprocess.run( [ - "ffprobe", + ffprobe_string, "-v", "error", "-select_streams", @@ -138,9 +162,10 @@ def normalize_framerates_in_video_ffmpeg( ): """Run a subprocess call to normalize the framerate and audio sample rate of a video file using ffmpeg""" + check_for_ffmpeg() normalize_framerates_subprocess = subprocess.run( [ - "ffmpeg", + ffmpeg_string, "-i", f"{input_video_pathstring}", "-r", @@ -165,10 +190,10 @@ def trim_single_video_ffmpeg( output_video_pathstring: str, ): """Run a subprocess call to trim a video from start time to last as long as the desired duration""" - + check_for_ffmpeg() trim_video_subprocess = subprocess.run( [ - "ffmpeg", + ffmpeg_string, "-i", f"{input_video_pathstring}", "-ss", @@ -195,9 +220,10 @@ def attach_audio_to_video_ffmpeg( ): """Run a subprocess call to attach audio file back to the video""" + check_for_ffmpeg() attach_audio_subprocess = subprocess.run( [ - "ffmpeg", + ffmpeg_string, "-i", f"{input_video_pathstring}", "-i", diff --git a/skelly_synchronize/core_processes/video_functions/video_utilities.py b/skelly_synchronize/core_processes/video_functions/video_utilities.py index fabfd88..e0cfb68 100644 --- a/skelly_synchronize/core_processes/video_functions/video_utilities.py +++ b/skelly_synchronize/core_processes/video_functions/video_utilities.py @@ -20,6 +20,8 @@ name_synced_video, ) +logger = logging.getLogger(__name__) + def create_video_info_dict( video_filepath_list: list, video_handler: str = "ffmpeg" @@ -61,7 +63,7 @@ def trim_videos( minimum_frames = int(minimum_duration * fps) for video_dict in video_info_dict.values(): - logging.debug(f"trimming video file {video_dict['camera name']}") + logger.debug(f"trimming video file {video_dict['camera name']}") synced_video_name = name_synced_video( raw_video_filename=video_dict["camera name"] ) @@ -73,8 +75,8 @@ def trim_videos( ) if video_handler == "ffmpeg": - logging.info(f"Saving video - Cam name: {video_dict['camera name']}") - logging.info(f"desired saving duration is: {minimum_duration} seconds") + logger.info(f"Saving video - Cam name: {video_dict['camera name']}") + logger.info(f"desired saving duration is: {minimum_duration} seconds") trim_single_video_ffmpeg( input_video_pathstring=video_dict["video pathstring"], start_time=start_time, @@ -83,12 +85,12 @@ def trim_videos( synchronized_folder_path / synced_video_name ), ) - logging.info( + logger.info( f"Video Saved - Cam name: {video_dict['camera name']}, Video Duration in Seconds: {minimum_duration}" ) if video_handler == "deffcode": - logging.info(f"Saving video - Cam name: {video_dict['camera name']}") - logging.info( + logger.info(f"Saving video - Cam name: {video_dict['camera name']}") + logger.info( f"start frame is: {start_frame} desired saving duration is: {minimum_frames} frames" ) trim_single_video_deffcode( @@ -98,7 +100,7 @@ def trim_videos( synchronized_folder_path / synced_video_name ), ) - logging.info( + logger.info( f"Video Saved - Cam name: {video_dict['camera name']}, Video Duration in Frames: {minimum_frames}" ) @@ -154,7 +156,7 @@ def attach_audio_to_videos( / f"{video_name}_with_audio_temp.{VideoExtension.MP4.value}" ) - logging.info(f"Attaching audio to video {video_name}") + logger.info(f"Attaching audio to video {video_name}") attach_audio_to_video_ffmpeg( input_video_pathstring=str(video), audio_file_pathstring=str( diff --git a/skelly_synchronize/gui/skelly_synchronize_gui.py b/skelly_synchronize/gui/skelly_synchronize_gui.py index 202d040..3883d93 100644 --- a/skelly_synchronize/gui/skelly_synchronize_gui.py +++ b/skelly_synchronize/gui/skelly_synchronize_gui.py @@ -12,7 +12,6 @@ QHBoxLayout, ) -from gui.widgets.run_button_widget import RunButtonWidget from skelly_synchronize.skelly_synchronize import ( synchronize_videos_from_audio, synchronize_videos_from_brightness, diff --git a/skelly_synchronize/skelly_synchronize.py b/skelly_synchronize/skelly_synchronize.py index db24acb..7a36a37 100644 --- a/skelly_synchronize/skelly_synchronize.py +++ b/skelly_synchronize/skelly_synchronize.py @@ -49,7 +49,7 @@ ) from skelly_synchronize.system.file_extensions import AudioExtension -logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) def synchronize_videos_from_audio( @@ -131,7 +131,7 @@ def synchronize_videos_from_audio( synchronized_video_framecounts = get_number_of_frames_of_videos_in_a_folder( folder_path=synchronized_video_folder_path ) - logging.info( + logger.info( f"All videos are {check_list_values_are_equal(synchronized_video_framecounts)} frames long" ) @@ -166,7 +166,7 @@ def synchronize_videos_from_audio( end_timer = time.time() - logging.info(f"Elapsed processing time in seconds: {end_timer - start_timer}") + logger.info(f"Elapsed processing time in seconds: {end_timer - start_timer}") return synchronized_video_folder_path @@ -185,7 +185,7 @@ def synchronize_videos_from_brightness( """ start_timer = time.time() - logging.info( + logger.info( f"Synchronizing videos with a brightness ratio threshold of {brightness_ratio_threshold}" ) @@ -241,7 +241,7 @@ def synchronize_videos_from_brightness( synchronized_video_framecounts = get_number_of_frames_of_videos_in_a_folder( folder_path=synchronized_video_folder_path ) - logging.info( + logger.info( f"All videos are {check_list_values_are_equal(synchronized_video_framecounts)} frames long" ) @@ -273,6 +273,6 @@ def synchronize_videos_from_brightness( end_timer = time.time() - logging.info(f"Elapsed processing time in seconds: {end_timer - start_timer}") + logger.info(f"Elapsed processing time in seconds: {end_timer - start_timer}") return synchronized_video_folder_path diff --git a/skelly_synchronize/tests/utilities/check_list_values_are_equal.py b/skelly_synchronize/tests/utilities/check_list_values_are_equal.py index a7aab4e..3a35408 100644 --- a/skelly_synchronize/tests/utilities/check_list_values_are_equal.py +++ b/skelly_synchronize/tests/utilities/check_list_values_are_equal.py @@ -1,6 +1,8 @@ import logging from typing import Any, List +logger = logging.getLogger(__name__) + def check_list_values_are_equal(input_list: List[Any]) -> Any: """Check if values in list are all equal, throw an exception if not (or if list is empty).""" @@ -11,7 +13,7 @@ def check_list_values_are_equal(input_list: List[Any]) -> Any: if len(unique_values) == 1: unique_value = unique_values.pop() - logging.debug(f"all values in list are equal to {unique_value}") + logger.debug(f"all values in list are equal to {unique_value}") return unique_value else: raise Exception(f"list values are not equal, list is {input_list}") diff --git a/skelly_synchronize/utils/check_if_video_has_reversed_metadata.py b/skelly_synchronize/utils/check_if_video_has_reversed_metadata.py index 77078b9..7c6c6df 100644 --- a/skelly_synchronize/utils/check_if_video_has_reversed_metadata.py +++ b/skelly_synchronize/utils/check_if_video_has_reversed_metadata.py @@ -1,9 +1,6 @@ -import logging import cv2 from deffcode import Sourcer -logging.basicConfig(level=logging.INFO) - def check_if_video_has_reversed_metadata(video_pathstring: str): """ diff --git a/skelly_synchronize/utils/get_video_files.py b/skelly_synchronize/utils/get_video_files.py index 21d079f..07589ac 100644 --- a/skelly_synchronize/utils/get_video_files.py +++ b/skelly_synchronize/utils/get_video_files.py @@ -3,6 +3,8 @@ from skelly_synchronize.system.file_extensions import VideoExtension +logger = logging.getLogger(__name__) + def get_video_file_list(folder_path: Path) -> list: """Return a list of all video files in the base_path folder that match a video file type""" @@ -19,7 +21,7 @@ def get_video_file_list(folder_path: Path) -> list: # because glob behaves differently on windows vs. mac/linux, we collect all files both upper and lowercase, and remove redundant files that appear on windows unique_video_filepath_list = get_unique_list(video_filepath_list) - logging.info(f"{len(unique_video_filepath_list)} videos found in folder") + logger.info(f"{len(unique_video_filepath_list)} videos found in folder") return unique_video_filepath_list diff --git a/skelly_synchronize/utils/path_handling_utilities.py b/skelly_synchronize/utils/path_handling_utilities.py index 6eedd44..079db08 100644 --- a/skelly_synchronize/utils/path_handling_utilities.py +++ b/skelly_synchronize/utils/path_handling_utilities.py @@ -4,7 +4,7 @@ from skelly_synchronize.system.file_extensions import VideoExtension from skelly_synchronize.system.paths_and_file_names import SYNCED_VIDEO_PRECURSOR -logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) def create_directory(parent_directory: Path, directory_name: str) -> Path: @@ -14,9 +14,9 @@ def create_directory(parent_directory: Path, directory_name: str) -> Path: try: new_directory_path.mkdir(parents=True, exist_ok=True) - logging.info(f"Created directory: {new_directory_path}") + logger.info(f"Created directory: {new_directory_path}") except Exception as e: - logging.error(f"Error creating directory: {new_directory_path}. Exception: {e}") + logger.error(f"Error creating directory: {new_directory_path}. Exception: {e}") raise return new_directory_path