Skip to content

Commit

Permalink
Handle missing ffmpeg better (#38)
Browse files Browse the repository at this point in the history
* check for ffmpeg/ffprobe before attempting call

* use logging properly

* missed logging update

* lint and ignore unused imports in init
  • Loading branch information
philipqueen authored Aug 26, 2024
1 parent 5d04a98 commit da8b147
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 47 deletions.
4 changes: 3 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -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

Expand Down
10 changes: 6 additions & 4 deletions skelly_synchronize/core_processes/audio_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
)
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"""
Expand Down Expand Up @@ -44,13 +46,13 @@ def extract_audio_files(
audio_file_path = audio_folder_path / audio_name

if not audio_file_path.is_file():
logging.error("Error loading audio file, verify video has audio track")
logger.error("Error loading audio file, verify video has audio track")
raise FileNotFoundError(f"Audio file not found: {audio_file_path}")

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,
Expand All @@ -64,7 +66,7 @@ def extract_audio_files(
def trim_audio_files(
audio_folder_path: Path, lag_dictionary: dict, synced_video_length: float
):
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)
Expand All @@ -83,7 +85,7 @@ def trim_audio_files(

audio_filename = str(audio_filepath.stem) + ".wav"

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")

Expand Down
12 changes: 7 additions & 5 deletions skelly_synchronize/core_processes/correlation_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from skelly_synchronize.system.paths_and_file_names import BRIGHTNESS_SUFFIX

logger = logging.getLogger(__name__)


def cross_correlate(audio1, audio2):
"""Take two audio files, synchronize them using cross correlation, and trim them to the same length.
Expand All @@ -27,7 +29,7 @@ def cross_correlate(audio1, audio2):
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(
Expand All @@ -41,12 +43,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}"
)

Expand Down Expand Up @@ -96,7 +98,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}"
)

Expand All @@ -111,7 +113,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}"
)

Expand Down
10 changes: 6 additions & 4 deletions skelly_synchronize/core_processes/debugging/debug_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
TRIMMED_AUDIO_FOLDER_NAME,
)

logger = logging.getLogger(__name__)


def create_brightness_debug_plots(
raw_video_folder_path: Path, synchronized_video_folder_path: Path
Expand All @@ -25,7 +27,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,
Expand All @@ -41,7 +43,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,
Expand Down Expand Up @@ -90,7 +92,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)


Expand Down Expand Up @@ -123,5 +125,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)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
check_if_video_has_reversed_metadata,
)

logger = logging.getLogger(__name__)


def trim_single_video_deffcode(
input_video_pathstring: str,
Expand All @@ -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 = {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import logging
import subprocess
import shutil
from pathlib import Path

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,
Expand All @@ -10,9 +31,10 @@ def extract_audio_from_video_ffmpeg(
):
"""Run a subprocess call to extract the audio from a video file using ffmpeg"""

check_for_ffmpeg()
subprocess.run(
[
"ffmpeg",
ffmpeg_string,
"-y",
"-i",
file_pathstring,
Expand All @@ -26,9 +48,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",
Expand All @@ -48,9 +71,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",
Expand All @@ -76,9 +100,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",
Expand Down Expand Up @@ -110,9 +135,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",
Expand All @@ -138,9 +164,10 @@ def trim_single_video_ffmpeg(
):
"""Run a subprocess call to trim a video from start time to last as long as the desired duration"""

check_for_ffmpeg()
subprocess.run(
[
"ffmpeg",
ffmpeg_string,
"-i",
f"{input_video_pathstring}",
"-ss",
Expand All @@ -162,9 +189,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",
Expand All @@ -180,4 +208,6 @@ def attach_audio_to_video_ffmpeg(
)

if attach_audio_subprocess.returncode != 0:
print(f"Error occurred: {attach_audio_subprocess.stderr.decode('utf-8')}")
logger.error(
f"Error occurred: {attach_audio_subprocess.stderr.decode('utf-8')}"
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
name_synced_video,
)

logger = logging.getLogger(__name__)


def create_video_info_dict(
video_filepath_list: list, video_handler: str = "ffmpeg"
Expand Down Expand Up @@ -60,7 +62,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"]
)
Expand All @@ -72,8 +74,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,
Expand All @@ -82,12 +84,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(
Expand All @@ -97,7 +99,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}"
)

Expand Down Expand Up @@ -152,7 +154,7 @@ def attach_audio_to_videos(
Path(temp_dir) / f"{video_name}_with_audio_temp.mp4"
)

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(
Expand Down
1 change: 0 additions & 1 deletion skelly_synchronize/gui/skelly_synchronize_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit da8b147

Please sign in to comment.