From de31b8d1df9458f1daa1ac9a385e648f588f4cf6 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Tue, 10 May 2022 20:14:02 +0200 Subject: [PATCH 1/6] add silent audio to slate --- .../plugins/publish/extract_review_slate.py | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 832799601c..2ef5308225 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,5 +1,4 @@ import os -import shutil import openpype.api import pyblish from openpype.lib import ( @@ -11,6 +10,7 @@ from openpype.lib import ( get_ffmpeg_format_args, ) + class ExtractReviewSlate(openpype.api.Extractor): """ Will add slate frame at the start of the video files @@ -122,10 +122,14 @@ class ExtractReviewSlate(openpype.api.Extractor): if stream["channel_layout"]: audio_channel_layout = str( stream.get("channel_layout")) + if stream["codec_name"]: + audio_codec = str( + stream.get("codec_name")) if ( audio_channels and audio_sample_rate and audio_channel_layout + and audio_codec ): input_audio = True break @@ -200,16 +204,6 @@ class ExtractReviewSlate(openpype.api.Extractor): input_args.append("-loop 1 -i {}".format( openpype.lib.path_to_subprocess_arg(slate_path))) - # if input has an audio, add silent audio to the slate - if input_audio: - input_args.extend( - ["-f lavfi -i anullsrc=r={}:cl={}:d={}".format( - audio_sample_rate, - audio_channel_layout, - one_frame_duration - )] - ) - input_args.extend(["-r {}".format(input_frame_rate)]) input_args.extend(["-frames:v 1"]) # add timecode from source to the slate, substract one frame @@ -314,6 +308,41 @@ class ExtractReviewSlate(openpype.api.Extractor): slate_subprocess_cmd, shell=True, logger=self.log ) + # Create slate with silent audio track + if input_audio: + # silent slate output path + slate_silent_path = slate_path.replace(".png", "Silent" + ext) + _remove_at_end.append(slate_silent_path) + + slate_silent_args = [ + ffmpeg_path, + "-i", slate_v_path, + "-f", "lavfi", "-i", + "anullsrc=r={}:cl={}:d={}".format( + audio_sample_rate, + audio_channel_layout, + one_frame_duration + ), + "-c:v", "copy", + "-c:a", audio_codec, + "-map", "0:v", + "-map", "1:a", + "-shortest", + "-y", + slate_silent_path + ] + slate_silent_subprocess_cmd = " ".join(slate_silent_args) + # run slate generation subprocess + self.log.debug( + "Silent Slate Executing: {}".format(slate_silent_subprocess_cmd) + ) + openpype.api.run_subprocess( + slate_silent_subprocess_cmd, shell=True, logger=self.log + ) + + # replace slate with silent slate for concat + slate_v_path = slate_silent_path + # create ffmpeg concat text file path conc_text_file = input_file.replace(ext, "") + "_concat" + ".txt" conc_text_path = os.path.join( @@ -361,21 +390,13 @@ class ExtractReviewSlate(openpype.api.Extractor): # add final output path concat_args.append(output_path) - if not input_audio: - # ffmpeg concat subprocess - self.log.debug( - "Executing concat: {}".format(" ".join(concat_args)) - ) - openpype.api.run_subprocess( - concat_args, logger=self.log - ) - else: - self.log.warning( - "Audio found. Creating slate with audio" - " is not supported at this time. Outputing slate-less" - ":\n{}".format(input_file)) - # skip concatenating slate, use slate-less file instead - shutil.copyfile(input_path, output_path) + # ffmpeg concat subprocess + self.log.debug( + "Executing concat: {}".format(" ".join(concat_args)) + ) + openpype.api.run_subprocess( + concat_args, logger=self.log + ) self.log.debug("__ repre[tags]: {}".format(repre["tags"])) repre_update = { @@ -438,7 +459,6 @@ class ExtractReviewSlate(openpype.api.Extractor): return vf_back - def _get_format_codec_args(self, repre): """Detect possible codec arguments from representation.""" codec_args = [] From b0c9e8a29d4c16d3276989fee37c8d8762d67809 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Tue, 10 May 2022 20:35:04 +0200 Subject: [PATCH 2/6] hound --- openpype/plugins/publish/extract_review_slate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 2ef5308225..d3f8934620 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -334,8 +334,8 @@ class ExtractReviewSlate(openpype.api.Extractor): slate_silent_subprocess_cmd = " ".join(slate_silent_args) # run slate generation subprocess self.log.debug( - "Silent Slate Executing: {}".format(slate_silent_subprocess_cmd) - ) + "Silent Slate Executing: {}".format( + slate_silent_subprocess_cmd)) openpype.api.run_subprocess( slate_silent_subprocess_cmd, shell=True, logger=self.log ) From ba1587efb6faf0e8cf21bf949fa36dbc5061d780 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Wed, 11 May 2022 12:22:34 +0200 Subject: [PATCH 3/6] safer code --- .../plugins/publish/extract_review_slate.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index d3f8934620..b84dbbc2ac 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -79,7 +79,7 @@ class ExtractReviewSlate(openpype.api.Extractor): ) # Get video metadata for stream in streams: - input_timecode = None + input_timecode = "" input_width = None input_height = None input_frame_rate = None @@ -89,18 +89,20 @@ class ExtractReviewSlate(openpype.api.Extractor): tags = stream.get("tags") or {} input_timecode = tags.get("timecode") or "" if "width" in stream and "height" in stream: - input_width = int(stream.get("width")) - input_height = int(stream.get("height")) + input_width = stream.get("width") + input_height = stream.get("height") if "r_frame_rate" in stream: # get frame rate in a form of # x/y, like 24000/1001 for 23.976 - input_frame_rate = str(stream.get("r_frame_rate")) + input_frame_rate = stream.get("r_frame_rate") if ( - input_timecode - and input_width + input_width and input_height and input_frame_rate ): + input_width = int(input_width) + input_height = int(input_height) + input_frame_rate = str(input_frame_rate) break # Raise exception of any stream didn't define input resolution if input_width is None: @@ -131,7 +133,10 @@ class ExtractReviewSlate(openpype.api.Extractor): and audio_channel_layout and audio_codec ): - input_audio = True + input_audio = str(input_audio) + audio_sample_rate = str(audio_sample_rate) + audio_channel_layout = str(audio_channel_layout) + audio_codec = str(audio_codec) break # Get duration of one frame in micro seconds one_frame_duration = "40000us" @@ -207,7 +212,7 @@ class ExtractReviewSlate(openpype.api.Extractor): input_args.extend(["-r {}".format(input_frame_rate)]) input_args.extend(["-frames:v 1"]) # add timecode from source to the slate, substract one frame - offset_timecode = "00:00:00:00" + offset_timecode = "" if input_timecode: offset_timecode = str(input_timecode) offset_timecode = self._tc_offset( @@ -311,7 +316,8 @@ class ExtractReviewSlate(openpype.api.Extractor): # Create slate with silent audio track if input_audio: # silent slate output path - slate_silent_path = slate_path.replace(".png", "Silent" + ext) + slate_silent_path = "_silent".join( + os.path.splitext(slate_v_path)) _remove_at_end.append(slate_silent_path) slate_silent_args = [ @@ -331,13 +337,12 @@ class ExtractReviewSlate(openpype.api.Extractor): "-y", slate_silent_path ] - slate_silent_subprocess_cmd = " ".join(slate_silent_args) # run slate generation subprocess self.log.debug( "Silent Slate Executing: {}".format( - slate_silent_subprocess_cmd)) + " ".join(slate_silent_args))) openpype.api.run_subprocess( - slate_silent_subprocess_cmd, shell=True, logger=self.log + slate_silent_args, logger=self.log ) # replace slate with silent slate for concat @@ -367,8 +372,9 @@ class ExtractReviewSlate(openpype.api.Extractor): "-safe", "0", "-i", conc_text_path, "-c", "copy", - "-timecode", offset_timecode, ] + if offset_timecode: + concat_args.extend(["-timecode", offset_timecode]) # NOTE: Added because of OP Atom demuxers # Add format arguments if there are any # - keep format of output From 82e62230fa78520d6cf167b0eaaec8095e5d18a6 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Wed, 11 May 2022 14:35:38 +0200 Subject: [PATCH 4/6] int back --- openpype/plugins/publish/extract_review_slate.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index b84dbbc2ac..7b2df4dc5f 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -89,8 +89,8 @@ class ExtractReviewSlate(openpype.api.Extractor): tags = stream.get("tags") or {} input_timecode = tags.get("timecode") or "" if "width" in stream and "height" in stream: - input_width = stream.get("width") - input_height = stream.get("height") + input_width = int(stream.get("width")) + input_height = int(stream.get("height")) if "r_frame_rate" in stream: # get frame rate in a form of # x/y, like 24000/1001 for 23.976 @@ -100,8 +100,6 @@ class ExtractReviewSlate(openpype.api.Extractor): and input_height and input_frame_rate ): - input_width = int(input_width) - input_height = int(input_height) input_frame_rate = str(input_frame_rate) break # Raise exception of any stream didn't define input resolution From 73a0d6fc70c847ab6114b1985dd0013282fb9a9a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 25 May 2022 12:08:01 +0200 Subject: [PATCH 5/6] extracted some functionality into separated functions --- .../plugins/publish/extract_review_slate.py | 275 +++++++++++------- 1 file changed, 171 insertions(+), 104 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 7b2df4dc5f..a42fbbbfe1 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -78,76 +78,26 @@ class ExtractReviewSlate(openpype.api.Extractor): input_path, self.log ) # Get video metadata - for stream in streams: - input_timecode = "" - input_width = None - input_height = None - input_frame_rate = None - if "codec_type" in stream: - if stream["codec_type"] == "video": - self.log.debug("__Ffprobe Video: {}".format(stream)) - tags = stream.get("tags") or {} - input_timecode = tags.get("timecode") or "" - if "width" in stream and "height" in stream: - input_width = int(stream.get("width")) - input_height = int(stream.get("height")) - if "r_frame_rate" in stream: - # get frame rate in a form of - # x/y, like 24000/1001 for 23.976 - input_frame_rate = stream.get("r_frame_rate") - if ( - input_width - and input_height - and input_frame_rate - ): - input_frame_rate = str(input_frame_rate) - break + ( + input_width, + input_height, + input_timecode, + input_frame_rate + ) = self._get_video_metadata(streams) + # Raise exception of any stream didn't define input resolution if input_width is None: raise AssertionError(( "FFprobe couldn't read resolution from input file: \"{}\"" ).format(input_path)) - # Get audio metadata - for stream in streams: - audio_channels = None - audio_sample_rate = None - audio_channel_layout = None - input_audio = False - if stream["codec_type"] == "audio": - self.log.debug("__Ffprobe Audio: {}".format(stream)) - if stream["channels"]: - audio_channels = str(stream.get("channels")) - if stream["sample_rate"]: - audio_sample_rate = str(stream.get("sample_rate")) - if stream["channel_layout"]: - audio_channel_layout = str( - stream.get("channel_layout")) - if stream["codec_name"]: - audio_codec = str( - stream.get("codec_name")) - if ( - audio_channels - and audio_sample_rate - and audio_channel_layout - and audio_codec - ): - input_audio = str(input_audio) - audio_sample_rate = str(audio_sample_rate) - audio_channel_layout = str(audio_channel_layout) - audio_codec = str(audio_codec) - break - # Get duration of one frame in micro seconds - one_frame_duration = "40000us" - if input_frame_rate: - items = input_frame_rate.split("/") - if len(items) == 1: - one_frame_duration = float(1.0) / float(items[0]) - elif len(items) == 2: - one_frame_duration = float(items[1]) / float(items[0]) - one_frame_duration *= 1000000 - one_frame_duration = str(int(one_frame_duration)) + "us" - self.log.debug( - "One frame duration is {}".format(one_frame_duration)) + + ( + audio_codec, + audio_channels, + audio_sample_rate, + audio_channel_layout, + input_audio + ) = self._get_audio_metadata(streams) # values are set in ExtractReview if use_legacy_code: @@ -205,14 +155,16 @@ class ExtractReviewSlate(openpype.api.Extractor): else: input_args.extend(repre["outputDef"].get('input', [])) - input_args.append("-loop 1 -i {}".format( - openpype.lib.path_to_subprocess_arg(slate_path))) - input_args.extend(["-r {}".format(input_frame_rate)]) - input_args.extend(["-frames:v 1"]) + input_args.extend([ + "-loop", "1", + "-i", openpype.lib.path_to_subprocess_arg(slate_path), + "-r", str(input_frame_rate), + "-frames:v", "1", + ]) + # add timecode from source to the slate, substract one frame offset_timecode = "" if input_timecode: - offset_timecode = str(input_timecode) offset_timecode = self._tc_offset( str(input_timecode), framerate=fps, @@ -221,7 +173,8 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug("Slate Timecode: `{}`".format( offset_timecode )) - input_args.extend(["-timecode {}".format(offset_timecode)]) + input_args.extend(["-timecode", str(offset_timecode)]) + if use_legacy_code: format_args = [] codec_args = repre["_profile"].get('codec', []) @@ -236,10 +189,10 @@ class ExtractReviewSlate(openpype.api.Extractor): # make sure colors are correct output_args.extend([ - "-vf scale=out_color_matrix=bt709", - "-color_primaries bt709", - "-color_trc bt709", - "-colorspace bt709" + "-vf", "scale=out_color_matrix=bt709", + "-color_primaries", "bt709", + "-color_trc", "bt709", + "-colorspace", "bt709", ]) # scaling none square pixels and 1920 width @@ -275,13 +228,20 @@ class ExtractReviewSlate(openpype.api.Extractor): "__ height_half_pad: `{}`".format(height_half_pad) ) - scaling_arg = ("scale={0}x{1}:flags=lanczos," - "pad={2}:{3}:{4}:{5}:black,setsar=1").format( - width_scale, height_scale, to_width, to_height, - width_half_pad, height_half_pad + scaling_arg = ( + "scale={0}x{1}:flags=lanczos" + ",pad={2}:{3}:{4}:{5}:black" + ",setsar=1" + ",fps={6}" + ).format( + width_scale, + height_scale, + to_width, + to_height, + width_half_pad, + height_half_pad, + input_frame_rate ) - # add output frame rate as a filter, just in case - scaling_arg += ",fps={}".format(input_frame_rate) vf_back = self.add_video_filter_args(output_args, scaling_arg) # add it to output_args @@ -317,30 +277,14 @@ class ExtractReviewSlate(openpype.api.Extractor): slate_silent_path = "_silent".join( os.path.splitext(slate_v_path)) _remove_at_end.append(slate_silent_path) - - slate_silent_args = [ + self._create_silent_slate( ffmpeg_path, - "-i", slate_v_path, - "-f", "lavfi", "-i", - "anullsrc=r={}:cl={}:d={}".format( - audio_sample_rate, - audio_channel_layout, - one_frame_duration - ), - "-c:v", "copy", - "-c:a", audio_codec, - "-map", "0:v", - "-map", "1:a", - "-shortest", - "-y", - slate_silent_path - ] - # run slate generation subprocess - self.log.debug( - "Silent Slate Executing: {}".format( - " ".join(slate_silent_args))) - openpype.api.run_subprocess( - slate_silent_args, logger=self.log + slate_v_path, + slate_silent_path, + audio_codec, + audio_channels, + audio_sample_rate, + audio_channel_layout, ) # replace slate with silent slate for concat @@ -426,6 +370,129 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug(inst_data["representations"]) + def _get_video_metadata(self, streams): + input_timecode = "" + input_width = None + input_height = None + input_frame_rate = None + for stream in streams: + if stream.get("codec_type") != "video": + continue + self.log.debug("FFprobe Video: {}".format(stream)) + + if "width" not in stream or "height" not in stream: + continue + width = int(stream["width"]) + height = int(stream["height"]) + if not width or not height: + continue + + # Make sure that width and height are captured even if frame rate + # is not available + input_width = width + input_height = height + + tags = stream.get("tags") or {} + input_timecode = tags.get("timecode") or "" + + input_frame_rate = stream.get("r_frame_rate") + if input_frame_rate is not None: + break + return ( + input_width, + input_height, + input_timecode, + input_frame_rate + ) + + def _get_audio_metadata(self, streams): + # Get audio metadata + audio_codec = None + audio_channels = None + audio_sample_rate = None + audio_channel_layout = None + input_audio = False + + for stream in streams: + if stream.get("codec_type") != "audio": + continue + self.log.debug("__Ffprobe Audio: {}".format(stream)) + + if all( + stream.get(key) + for key in ( + "codec_name", + "channels", + "sample_rate", + "channel_layout", + ) + ): + audio_codec = stream["codec_name"] + audio_channels = stream["channels"] + audio_sample_rate = stream["sample_rate"] + audio_channel_layout = stream["channel_layout"] + input_audio = True + break + + return ( + audio_codec, + audio_channels, + audio_sample_rate, + audio_channel_layout, + input_audio, + ) + + def _create_silent_slate( + self, + ffmpeg_path, + src_path, + dst_path, + audio_codec, + audio_channels, + audio_sample_rate, + audio_channel_layout, + ): + # Get duration of one frame in micro seconds + items = audio_sample_rate.split("/") + if len(items) == 1: + one_frame_duration = 1.0 / float(items[0]) + elif len(items) == 2: + one_frame_duration = float(items[1]) / float(items[0]) + else: + one_frame_duration = None + + if one_frame_duration is None: + one_frame_duration = "40000us" + else: + one_frame_duration *= 1000000 + one_frame_duration = str(int(one_frame_duration)) + "us" + self.log.debug("One frame duration is {}".format(one_frame_duration)) + + slate_silent_args = [ + ffmpeg_path, + "-i", src_path, + "-f", "lavfi", "-i", + "anullsrc=r={}:cl={}:d={}".format( + audio_sample_rate, + audio_channel_layout, + one_frame_duration + ), + "-c:v", "copy", + "-c:a", audio_codec, + "-map", "0:v", + "-map", "1:a", + "-shortest", + "-y", + dst_path + ] + # run slate generation subprocess + self.log.debug("Silent Slate Executing: {}".format( + " ".join(slate_silent_args) + )) + openpype.api.run_subprocess( + slate_silent_args, logger=self.log + ) + def add_video_filter_args(self, args, inserting_arg): """ Fixing video filter argumets to be one long string From 4c3914c6c263efe68d466a542c26908a1e12739d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 25 May 2022 12:12:46 +0200 Subject: [PATCH 6/6] fix double space --- openpype/plugins/publish/extract_review_slate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index a42fbbbfe1..a2cbc1b704 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -159,7 +159,7 @@ class ExtractReviewSlate(openpype.api.Extractor): "-loop", "1", "-i", openpype.lib.path_to_subprocess_arg(slate_path), "-r", str(input_frame_rate), - "-frames:v", "1", + "-frames:v", "1", ]) # add timecode from source to the slate, substract one frame