-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathvsutil.py
168 lines (136 loc) · 5.52 KB
/
vsutil.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""
VSUtil. A collection of general-purpose Vapoursynth functions to be reused in modules and scripts.
"""
from functools import reduce
from typing import Callable, TypeVar, Union, List, Tuple, Optional
import vapoursynth as vs
import mimetypes
import os
core = vs.core
T = TypeVar("T")
def get_subsampling(clip: vs.VideoNode) -> str:
"""
Returns the subsampling of a clip in human-readable format.
"""
if clip.format.subsampling_w == 1 and clip.format.subsampling_h == 1:
css = '420'
elif clip.format.subsampling_w == 1 and clip.format.subsampling_h == 0:
css = '422'
elif clip.format.subsampling_w == 0 and clip.format.subsampling_h == 0:
css = '444'
elif clip.format.subsampling_w == 2 and clip.format.subsampling_h == 2:
css = '410'
elif clip.format.subsampling_w == 2 and clip.format.subsampling_h == 0:
css = '411'
elif clip.format.subsampling_w == 0 and clip.format.subsampling_h == 1:
css = '440'
else:
raise ValueError('Unknown subsampling')
return css
def get_depth(clip: vs.VideoNode) -> int:
"""
Returns the bitdepth of a clip as an integer.
"""
return clip.format.bits_per_sample
def get_plane_size(frame: Union[vs.VideoFrame, vs.VideoNode], planeno: int) -> Tuple[int, int]:
"""
Calculates the size of the plane
:param frame: The frame
:param planeno: The plane
:return: (width, height)
"""
# Add additional checks on Video Nodes as their size and format can be variable.
if isinstance(frame, vs.VideoNode):
if frame.width == 0:
raise ValueError("Cannot calculate plane size of variable size clip. Pass a frame instead.")
if frame.format is None:
raise ValueError("Cannot calculate plane size of variable format clip. Pass a frame instead.")
width, height = frame.width, frame.height
if planeno != 0:
width >>= frame.format.subsampling_w
height >>= frame.format.subsampling_h
return width, height
def iterate(base: T, function: Callable[[T], T], count: int) -> T:
"""
Utility function that executes a given function for a given number of times.
"""
return reduce(lambda v, _: function(v), range(count), base)
def insert_clip(clip: vs.VideoNode, insert: vs.VideoNode, start_frame: int) -> vs.VideoNode:
"""
Convenience method to insert a shorter clip into a longer one.
The inserted clip cannot go beyond the last frame of the source clip or an exception is raised.
"""
if start_frame == 0:
return insert + clip[insert.num_frames:]
pre = clip[:start_frame]
frame_after_insert = start_frame + insert.num_frames
if frame_after_insert > clip.num_frames:
raise ValueError('Inserted clip is too long')
if frame_after_insert == clip.num_frames:
return pre + insert
post = clip[start_frame + insert.num_frames:]
return pre + insert + post
def fallback(value: Optional[T], fallback_value: T) -> T:
"""
Utility function that returns a value or a fallback if the value is None.
"""
return fallback_value if value is None else value
def plane(clip: vs.VideoNode, planeno: int) -> vs.VideoNode:
"""
Extract the plane with the given index from the clip.
:param clip: The clip to extract the plane from.
:param planeno: The planeno that specifies which plane to extract.
:return: A grayscale clip that only contains the given plane.
"""
return core.std.ShufflePlanes(clip, planeno, vs.GRAY)
def get_y(clip: vs.VideoNode) -> vs.VideoNode:
"""
Helper to get the luma of a VideoNode.
"""
return plane(clip, 0)
def split(clip: vs.VideoNode) -> List[vs.VideoNode]:
"""
Returns a list of planes for the given input clip.
"""
return [plane(clip, x) for x in range(clip.format.num_planes)]
def join(planes: List[vs.VideoNode], family=vs.YUV) -> vs.VideoNode:
"""
Joins the supplied list of planes into a YUV video node.
"""
return core.std.ShufflePlanes(clips=planes, planes=[0], colorfamily=family)
def frame2clip(frame: vs.VideoFrame, *, enforce_cache=True) -> vs.VideoNode:
"""
Converts a vapoursynth frame to a clip.
:param frame: The frame to wrap.
:param enforce_cache: Always add a cache. (Even if the vapoursynth module has this feature disabled)
:returns: A one-frame VideoNode that yields the frame passed to the function.
"""
bc = core.std.BlankClip(
width=frame.width,
height=frame.height,
length=1,
fpsnum=1,
fpsden=1,
format=frame.format.id
)
frame = frame.copy()
result = bc.std.ModifyFrame([bc], lambda n, f: frame.copy())
# Forcefully add a cache to Modify-Frame if caching is disabled on the core.
# This will ensure that the filter will not include GIL characteristics.
if not core.add_cache and enforce_cache:
result = result.std.Cache(size=1, fixed=True)
return result
def get_w(height: int, aspect_ratio: float = 16 / 9, only_even: bool = True) -> int:
"""
Calculates the width for a clip with the given height and aspect ratio.
only_even is True by default because it imitates the math behind most standard resolutions (e.g. 854x480).
"""
width = height * aspect_ratio
if only_even:
width = round(width / 2) * 2
return int(round(width))
def is_image(filename: str) -> bool:
"""
Returns true if a filename refers to an image.
"""
return mimetypes.types_map.get(os.path.splitext(filename)[-1], "").startswith("image/")