-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Faster rfs implementation #60
Faster rfs implementation #60
Conversation
Do you have some numbers on that? Theoretically it produces less |
@DJATOM Not any hard numbers yet. I was having issues earlier with the tonnes of splicing that lvf.rfs used originally, which initialises hundreds, if not thousands of |
Note: I do also need to add some checks in for OOB frame and clip accesses within |
Doesn't satisfy mypy - can fix, but adds excess complexity that I disagree with. There's probably a cleverer fix that doesn't do this, but I don't really know mypy, and I'm also too tired for this nonsense
* Add select_frames function borrowed from Jaded-Encoding-Thaumaturgy/lvsfunc#60 * replace_ranges and adjust_clip_frames use it * Add Numpy dependency
Closing this in favour of the vardefunc commit above |
Unclosing since it turns out people are still interested. |
if len(clips) > 1 and not all(clip.format == clips[0].format for clip in clips[1:]): | ||
raise ValueError("All input clips must be of the same format.") | ||
|
||
def select_frames_func(n: int, clips: List[vs.VideoNode], indicies: List[Tuple[int, int]]) -> vs.VideoNode: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't need the clips and indices arguments, the nested function has access to the parent function's scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course - I missed this as it was originally written as a seperate function.
@@ -1,7 +1,8 @@ | |||
""" | |||
Helper functions for the main functions in the script. | |||
""" | |||
from typing import Any, Callable, List, Optional, Sequence, Type, TypeVar, Tuple, Union | |||
from functools import partial | |||
from typing import Any, Callable, List, Optional, Sequence, Type, TypeVar, Tuple, Union, cast |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cast imported but unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above
@@ -1,7 +1,8 @@ | |||
""" | |||
Helper functions for the main functions in the script. | |||
""" | |||
from typing import Any, Callable, List, Optional, Sequence, Type, TypeVar, Tuple, Union | |||
from functools import partial |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
partial can be removed, see below
Select frames from one or more clips at specified indices. | ||
|
||
:param clips: A clip or a list of clips to select the frames from | ||
:param indexes: Indices of frames to select. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you want indices
, not indicies
or indexes
(misspelled throughout the function)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Writing code while drunk is a bad idea
def select_frames(clips: Union[vs.VideoNode, List[vs.VideoNode]], | ||
indicies: Union[List[int], List[Tuple[int, int]]]) -> vs.VideoNode: | ||
""" | ||
Select frames from one or more clips at specified indices. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be more understandable to say something like Builds a clip from a list of clips and frame numbers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I intended for this to be rewritten before merging. I still don't really like the description here, but I'm unsure how to nicely describe what this does in a short sentence.
@@ -102,6 +103,45 @@ def get_prop(frame: vs.VideoFrame, key: str, t: Type[T]) -> T: | |||
return prop | |||
|
|||
|
|||
def select_frames(clips: Union[vs.VideoNode, List[vs.VideoNode]], | |||
indicies: Union[List[int], List[Tuple[int, int]]]) -> vs.VideoNode: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm not really sure what the point of unioning indices with List[int]
is, it does make the function a bit more ergonomic but in reality indices should almost always be generated not directly user-entered
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree with this point - The function is not intended to be called by the end user, however the flexibility of the function is part of the point of it's design. Why be less flexible if I don't have to? In any case, rfs ect. are intended to be the external functions, but if the user wishes to do their own thing then they can. If you have a better idea of what types these arguments could be feel free to suggest, though I think that writing even dataclasses for this would be over the top for something so simple.
:return: The selected frames in a single clip | ||
""" | ||
clips = [clips] if isinstance(clips, vs.VideoNode) else clips | ||
for pos, index in enumerate(indicies): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removing the union removes the issues with this code, otherwise
for index in indices:
if isinstance(index, int):
clip_idx, frame_idx = (0, index)
else:
clip_idx, frame_idx = index
then
if isinstance(index, int):
clip_idx, frame_idx = (0, indices[n])
else:
clip_idx, frame_idx = indices[n]
return clips[clip_idx][frame_idx]
in the frameeval
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather not have any more logic than is strictly necessary within the frame eval. However I agree that expanding this into multiple lines would help with both readability and mypy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that makes sense for VS filters, but doing a typecheck and conditional tuple construction is virtually free in the context of video processing
clips = [clips] if isinstance(clips, vs.VideoNode) else clips | ||
for pos, index in enumerate(indicies): | ||
if isinstance(index, int): | ||
indicies[pos] = (0, index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in addition to the mypy issues, this is bad because mutating user input is bad
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whilst mutating the userinput is probably not the best idea, I still don't like mypy telling me what I can and can't do >:(
Creating a copy of the list would probably a solution
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
honestly i'd just throw this entire logic into the frameeval, it's not going to impact performance at all
@@ -174,19 +214,20 @@ def replace_ranges(clip_a: vs.VideoNode, | |||
if ranges is None: | |||
return clip_a | |||
|
|||
out = clip_a | |||
out_indicies = list(zip([0] * clip_a.num_frames, range(clip_a.num_frames))) | |||
clip_b_indices = list(zip([1] * clip_b.num_frames, range(clip_b.num_frames))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably better to just generate the indices we need for clip_b inside the loop instead of slicing a precomputed list
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inside the loop meaning inside the frameeval? Not sure I understand this point.
@@ -174,19 +214,20 @@ def replace_ranges(clip_a: vs.VideoNode, | |||
if ranges is None: | |||
return clip_a | |||
|
|||
out = clip_a | |||
out_indicies = list(zip([0] * clip_a.num_frames, range(clip_a.num_frames))) | |||
clip_b_indices = list(zip([1] * clip_b.num_frames, range(clip_b.num_frames))) | |||
|
|||
nranges = normalize_ranges(clip_b, ranges) | |||
|
|||
for start, end in nranges: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we're now using a normal List
instead of a VideoNode
, we can just do slice assignment
for start, end in nranges:
out_indices[start:end+1] = list(zip([1] * (end + 1 - start), range(start, end+1)))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup you've lost me now, please post the full block that this is relevant to so I can better understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
out_indicies = [(0, i) for i in range(clip_a.num_frames)]
for start, end in nranges:
out_indices[start:end+1] = [(1, i) for i in range(start, end+1)]
can replace the entire for loop
Apologies for the delay in replying here - I've been busy being dead/on holiday. If I miss any further comments please ping me in IEW or drop me a dm. With regards to the FrameEval vs ModifyFrame discussion - calling get_frame within a ModifyFrame is not a good idea, however there is an overhead (if minor) to using FrameEval. I have had an idea on how to merge the best of both of these into one plugin, which I intend to have an implementation for finished within a week or so. For this function though, the difference between the two is minimal enough that it can be safely ignored. |
Oh and I will make some changes over the following days. I am on holiday though so may be slow, as I do not have easy access to a proper environment to work in. |
As this PR hasn't seen any movement in over half a year, I'm closing it. I'll see if I can update rfs at some point to use faster implementations. |
I haven't tested this yet, but creating PR for review.