Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
feat(nks): adding empty black frames at handle start if missing
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubjezek001 committed May 11, 2020
1 parent 7ac7600 commit d4899b6
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 23 deletions.
21 changes: 21 additions & 0 deletions pype/plugins/nukestudio/publish/collect_clip_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pyblish.api


class CollectClipResolution(pyblish.api.InstancePlugin):
"""Collect clip geometry resolution"""

order = pyblish.api.CollectorOrder + 0.101
label = "Collect Clip Resoluton"
hosts = ["nukestudio"]

def process(self, instance):
sequence = instance.context.data['activeSequence']
resolution_width = int(sequence.format().width())
resolution_height = int(sequence.format().height())
pixel_aspect = sequence.format().pixelAspect()

instance.data.update({
"resolutionWidth": resolution_width,
"resolutionHeight": resolution_height,
"pixelAspect": pixel_aspect
})
2 changes: 1 addition & 1 deletion pype/plugins/nukestudio/publish/collect_clips.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def process(self, context):
"effects": effects,
"sourceIn": int(item.sourceIn()),
"sourceOut": int(item.sourceOut()),
"mediaDuration": source.duration(),
"mediaDuration": int(source.duration()),
"clipIn": clip_in,
"clipOut": clip_out,
"clipDuration": (
Expand Down
10 changes: 5 additions & 5 deletions pype/plugins/nukestudio/publish/collect_hierarchy_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ def process(self, context):
clip = instance.data["item"]
asset = instance.data["asset"]
sequence = context.data['activeSequence']
width = int(sequence.format().width())
height = int(sequence.format().height())
pixel_aspect = sequence.format().pixelAspect()
resolution_width = instance.data["resolutionWidth"]
resolution_height = instance.data["resolutionHeight"]
pixel_aspect = instance.data["pixelAspect"]
clip_in = instance.data["clipIn"]
clip_out = instance.data["clipOut"]
fps = context.data["fps"]
Expand Down Expand Up @@ -187,8 +187,8 @@ def process(self, context):
"asset": asset,
"hierarchy": hierarchy,
"parents": parents,
"resolutionWidth": width,
"resolutionHeight": height,
"resolutionWidth": resolution_width,
"resolutionHeight": resolution_height,
"pixelAspect": pixel_aspect,
"fps": fps,
"tasks": instance.data["tasks"]
Expand Down
10 changes: 5 additions & 5 deletions pype/plugins/nukestudio/publish/collect_plates.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ def process(self, instance):
# adding SourceResolution if Tag was present
if instance.data.get("sourceResolution") and instance.data.get("main"):
item = instance.data["item"]
width = int(item.source().mediaSource().width())
height = int(item.source().mediaSource().height())
resolution_width = int(item.source().mediaSource().width())
resolution_height = int(item.source().mediaSource().height())
pixel_aspect = int(item.source().mediaSource().pixelAspect())

self.log.info("Source Width and Height are: `{0} x {1} : {2}`".format(
width, height, pixel_aspect))
resolution_width, resolution_height, pixel_aspect))
data.update({
"width": width,
"height": height,
"resolutionWidth": resolution_width,
"resolutionHeight": resolution_height,
"pixelAspect": pixel_aspect
})

Expand Down
12 changes: 10 additions & 2 deletions pype/plugins/nukestudio/publish/collect_reviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,15 @@ def process(self, instance):
representation.update({
"frameStart": instance.data.get("sourceInH"),
"frameEnd": instance.data.get("sourceOutH"),
"tags": ["cut-up", "delete"]
"tags": ["_cut-bigger", "delete"]
})
elif media_duration < clip_duration_h:
self.log.debug("Media duration higher: {}".format(
(media_duration - clip_duration_h)))
representation.update({
"frameStart": instance.data.get("sourceInH"),
"frameEnd": instance.data.get("sourceOutH"),
"tags": ["_cut-smaller", "delete"]
})

instance.data["representations"].append(representation)
Expand All @@ -133,7 +141,7 @@ def create_thumbnail(self, instance):
self.log.debug("__ thumb_path: {}".format(thumb_path))

thumb_frame = instance.data["sourceIn"] + ((instance.data["sourceOut"] - instance.data["sourceIn"])/2)

self.log.debug("__ thumb_frame: {}".format(thumb_frame))
thumbnail = item.thumbnail(thumb_frame).save(
thumb_path,
format='png'
Expand Down
87 changes: 77 additions & 10 deletions pype/plugins/nukestudio/publish/extract_review_cutup_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
class ExtractReviewCutUpVideo(pype.api.Extractor):
"""Cut up clips from long video file"""

order = api.ExtractorOrder
# order = api.CollectorOrder + 0.1023
# order = api.ExtractorOrder
order = api.CollectorOrder + 0.1023
label = "Extract Review CutUp Video"
hosts = ["nukestudio"]
families = ["review"]
Expand All @@ -22,7 +22,16 @@ def process(self, instance):
# get representation and loop them
representations = inst_data["representations"]

# resolution data
resolution_width = inst_data["resolutionWidth"]

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 11, 2020

Member

This is just my opinion but it would be safer to use input's width and height get from ffprobe, instead of resolution stored in instance.data.

This comment has been minimized.

Copy link
@mkolar

mkolar May 12, 2020

Member

I agree. at this point we just want to cut the input so everything should be only dependent on it's data. Or as much as possible

resolution_height = inst_data["resolutionHeight"]
pixel_aspect = inst_data["pixelAspect"]

# frame range data
media_duration = inst_data["mediaDuration"]

ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe")

# filter out mov and img sequences
representations_new = representations[:]
Expand All @@ -32,7 +41,9 @@ def process(self, instance):

tags = repre.get("tags", [])

if "cut-up" not in tags:
if not next(
(t for t in tags
if t in ["_cut-bigger", "_cut-smaller"]), None):

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 11, 2020

Member

This is just a note: It took me a while to understand that is just check in "_cut-bigger" or "_cut-smaller" are in tags. This is really messy and hard to read what are you doing here...

This comment has been minimized.

Copy link
@mkolar

mkolar May 12, 2020

Member

List comprehensions, in general, are super hard to read and maintain later on. can we just re-write this to a simple:

filter_tag= False
for tag in ["_cut-bigger", "_cut-smaller"]:
    if tag in tags:
        filter_tag= True
        break
if not filter_tag:
    continue
continue

self.log.debug("__ repre: {}".format(repre))
Expand All @@ -49,21 +60,77 @@ def process(self, instance):
full_input_path = os.path.join(
staging_dir, file)

full_output_dir = os.path.join(
staging_dir, "cuts")

os.path.isdir(full_output_dir) or os.makedirs(full_output_dir)

full_output_path = os.path.join(
staging_dir, new_file_name)
full_output_dir, new_file_name)

self.log.debug("__ full_input_path: {}".format(full_input_path))
self.log.debug("__ full_output_path: {}".format(full_output_path))

input_args.append("-y")
input_args.append("-i {}".format(full_input_path))
# check if audio stream is in input video file
ffprob_cmd = (
"{ffprobe_path} -i {full_input_path} -show_streams "
"-select_streams a -loglevel error"
).format(**locals())

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 11, 2020

Member

I would prefer to enter real dictionary instead of locals. It's hard to determine where variables are used if they are not in code...

This comment has been minimized.

Copy link
@mkolar

mkolar May 12, 2020

Member

Agreed too. This hides a lot of stuff that should be explicit

self.log.debug("ffprob_cmd: {}".format(ffprob_cmd))
audio_check_output = pype.api.subprocess(ffprob_cmd)
self.log.debug("audio_check_output: {}".format(audio_check_output))

# translate frame to sec
start_sec = float(frame_start) / fps
duration_sec = float(frame_end - frame_start + 1) / fps

input_args.append("-y")

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 12, 2020

Member

"-y" is actually output argument (and the best place is just before output file path).


if start_sec < 0:
audio_empty = ""
audio_output = ""
audio_layer = ""
v_inp_idx = 0
black_duration = abs(start_sec)
start_sec = 0
duration_sec = float(frame_end - (
frame_start + (black_duration * fps)) + 1) / fps

if audio_check_output:
# adding input for empty audio
input_args.append("-f lavfi -i anullsrc")
audio_empty = (
"[0]atrim=duration={black_duration}[ga0];"
).format(**locals())
audio_output = ":a=1"
audio_layer = "[ga0]"
v_inp_idx = 1

# adding input for video black frame
input_args.append((
"-f lavfi -i \"color=c=black:"
"s={resolution_width}x{resolution_height}:r={fps}\""
).format(**locals()))

# concutting black frame togather
output_args.append((
"-filter_complex \""
"{audio_empty}"
"[{v_inp_idx}]trim=duration={black_duration}[gv0];"
"[gv0]{audio_layer}[1:v]"
"concat=n=2:v=1{audio_output}\""
).format(**locals()))

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 11, 2020

Member

Same note about local as before.

Plus it would be better to use frames instead of duration because python is not good at float math. (this can be done via select="between(n\,start_frame_num\,end_frame_num),setpts=PTS-STARTPTS" but is not required just would be better to do.

This comment has been minimized.

Copy link
@mkolar

mkolar May 12, 2020

Member

@jezscha have you ever tried with frames? Don't know whether that would introduce problems at some point, Maybe worth trying and see if it might be more precise? Don't know myself

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 12, 2020

Member

To be honest I didn't try either just know it can work. And I think with 24fps or 25fps it's not real problem at all. But with crazy framerates like 24000/1001 it may be over python's abilities.


input_args.append("-ss {:0.2f}".format(start_sec))
input_args.append("-t {:0.2f}".format(duration_sec))

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 11, 2020

Member

You know you can use round(duration_sec, 2) instead of {:0.2f}?

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 11, 2020

Member

BTW: you can use -frames {frame length} in output args instead

input_args.append("-i {}".format(full_input_path))

output_args.append("-c copy")
duration_sec = float(frame_end - frame_start + 1) / fps
output_args.append("-t {:0.2f}".format(duration_sec))
# check if not missing frames at the end
self.log.debug("media_duration: {}".format(media_duration))
self.log.debug("frame_end: {}".format(frame_end))

# make sure it is having no frame to frame comprassion
output_args.append("-intra")

This comment has been minimized.

Copy link
@iLLiCiTiT

iLLiCiTiT May 11, 2020

Member

Out of context question: Is good idea to force this? (Yes/No answer is enough)

This comment has been minimized.

Copy link
@mkolar

mkolar May 12, 2020

Member

For these intermediates, I'd say that it is better to force it.


# output filename
output_args.append(full_output_path)
Expand All @@ -90,7 +157,7 @@ def process(self, instance):
"step": 1,
"fps": fps,
"name": "cut_up_preview",
"tags": ["cut-up", "review", "delete"] + self.tags_addition,
"tags": ["review", "delete"] + self.tags_addition,
"ext": ext,
"anatomy_template": "publish"
}
Expand Down

0 comments on commit d4899b6

Please sign in to comment.