-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: ffmpeg code into utility file
- Loading branch information
Showing
3 changed files
with
111 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import logging | ||
import subprocess | ||
|
||
import ffmpeg | ||
|
||
from pikaraoke.lib.get_platform import supports_hardware_h264_encoding | ||
|
||
|
||
def build_ffmpeg_cmd(fr, semitones=0, normalize_audio=True): | ||
# use h/w acceleration on pi | ||
default_vcodec = "h264_v4l2m2m" if supports_hardware_h264_encoding() else "libx264" | ||
# just copy the video stream if it's an mp4 or webm file, since they are supported natively in html5 | ||
# otherwise use the default h264 codec | ||
vcodec = ( | ||
"copy" if fr.file_extension == ".mp4" or fr.file_extension == ".webm" else default_vcodec | ||
) | ||
vbitrate = "5M" # seems to yield best results w/ h264_v4l2m2m on pi, recommended for 720p. | ||
|
||
# copy the audio stream if no transposition/normalization, otherwise reincode with the aac codec | ||
is_transposed = semitones != 0 | ||
acodec = "aac" if is_transposed or normalize_audio else "copy" | ||
input = ffmpeg.input(fr.file_path) | ||
|
||
# The pitch value is (2^x/12), where x represents the number of semitones | ||
pitch = 2 ** (semitones / 12) | ||
|
||
audio = input.audio.filter("rubberband", pitch=pitch) if is_transposed else input.audio | ||
# normalize the audio | ||
audio = audio.filter("loudnorm", i=-16, tp=-1.5, lra=11) if normalize_audio else audio | ||
|
||
if fr.cdg_file_path != None: # handle CDG files | ||
logging.info("Playing CDG/MP3 file: " + fr.file_path) | ||
# copyts helps with sync issues, fps=25 prevents ffmpeg from needlessly encoding cdg at 300fps | ||
cdg_input = ffmpeg.input(fr.cdg_file_path, copyts=None) | ||
video = cdg_input.video.filter("fps", fps=25) | ||
# cdg is very fussy about these flags. | ||
# pi ffmpeg needs to encode to aac and cant just copy the mp3 stream | ||
# It alse appears to have memory issues with hardware acceleration h264_v4l2m2m | ||
output = ffmpeg.output( | ||
audio, | ||
video, | ||
fr.output_file, | ||
vcodec="libx264", | ||
acodec="aac", | ||
preset="ultrafast", | ||
pix_fmt="yuv420p", | ||
listen=1, | ||
f="mp4", | ||
video_bitrate="500k", | ||
movflags="frag_keyframe+default_base_moof", | ||
) | ||
else: | ||
video = input.video | ||
output = ffmpeg.output( | ||
audio, | ||
video, | ||
fr.output_file, | ||
vcodec=vcodec, | ||
acodec=acodec, | ||
preset="ultrafast", | ||
listen=1, | ||
f="mp4", | ||
video_bitrate=vbitrate, | ||
movflags="frag_keyframe+default_base_moof", | ||
) | ||
|
||
args = output.get_args() | ||
logging.debug(f"COMMAND: ffmpeg " + " ".join(args)) | ||
return output | ||
|
||
|
||
def get_ffmpeg_version(): | ||
try: | ||
# Execute the command 'ffmpeg -version' | ||
result = subprocess.run( | ||
["ffmpeg", "-version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True | ||
) | ||
# Parse the first line to get the version | ||
first_line = result.stdout.split("\n")[0] | ||
version_info = first_line.split(" ")[2] # Assumes the version info is the third element | ||
return version_info | ||
except FileNotFoundError: | ||
return "FFmpeg is not installed" | ||
except IndexError: | ||
return "Unable to parse FFmpeg version" | ||
|
||
|
||
def is_transpose_enabled(): | ||
try: | ||
filters = subprocess.run(["ffmpeg", "-filters"], capture_output=True) | ||
except FileNotFoundError: | ||
# FFmpeg is not installed | ||
return False | ||
except IndexError: | ||
# Unable to parse FFmpeg filters | ||
return False | ||
return "rubberband" in filters.stdout.decode() |