Skip to content

Commit 944be41

Browse files
committed
Display motion levels in real time
1 parent f497099 commit 944be41

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

auto_editor/analyze.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,5 @@ def motion(self, stream: int, blur: int, width: int) -> NDArray[np.float64]:
354354
prev_frame = current_frame
355355

356356
self.bar.end()
357+
container.close()
357358
return self.cache("motion", mobj, threshold_list[:index])

auto_editor/subcommands/levels.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from auto_editor.vanparse import ArgumentParser
2626

2727
if TYPE_CHECKING:
28+
from collections.abc import Iterator
2829
from fractions import Fraction
2930

3031
from numpy.typing import NDArray
@@ -79,6 +80,68 @@ def print_arr(arr: NDArray) -> None:
7980
print("")
8081

8182

83+
def print_arr_gen(arr: Iterator[int | float | bool]) -> None:
84+
print("")
85+
print("@start")
86+
for a in arr:
87+
if isinstance(a, float):
88+
print(f"{a:.20f}")
89+
if isinstance(a, bool):
90+
print("1" if a else "0")
91+
if isinstance(a, int):
92+
print(a)
93+
print("")
94+
95+
96+
def iter_motion(src, tb, stream: int, blur: int, width: int) -> Iterator[float]:
97+
import av
98+
99+
container = av.open(f"{src.path}", "r")
100+
101+
video = container.streams.video[stream]
102+
video.thread_type = "AUTO"
103+
104+
prev_frame = None
105+
current_frame = None
106+
total_pixels = src.videos[0].width * src.videos[0].height
107+
index = 0
108+
prev_index = 0
109+
110+
graph = av.filter.Graph()
111+
graph.link_nodes(
112+
graph.add_buffer(template=video),
113+
graph.add("scale", f"{width}:-1"),
114+
graph.add("format", "gray"),
115+
graph.add("gblur", f"sigma={blur}"),
116+
graph.add("buffersink"),
117+
).configure()
118+
119+
for unframe in container.decode(video):
120+
if unframe.pts is None:
121+
continue
122+
123+
graph.push(unframe)
124+
frame = graph.pull()
125+
assert frame.time is not None
126+
index = round(frame.time * tb)
127+
128+
current_frame = frame.to_ndarray()
129+
if prev_frame is None:
130+
value = 0.0
131+
else:
132+
# Use `int16` to avoid underflow with `uint8` datatype
133+
diff = np.abs(prev_frame.astype(np.int16) - current_frame.astype(np.int16))
134+
value = np.count_nonzero(diff) / total_pixels
135+
136+
for _ in range(index - prev_index):
137+
yield value
138+
139+
prev_frame = current_frame
140+
prev_index = index
141+
142+
container.close()
143+
144+
82145
def main(sys_args: list[str] = sys.argv[1:]) -> None:
83146
parser = levels_options(ArgumentParser("levels"))
84147
args = parser.parse_args(LevelArgs, sys_args)
@@ -136,7 +199,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
136199
if method == "audio":
137200
print_arr(levels.audio(**obj))
138201
elif method == "motion":
139-
print_arr(levels.motion(**obj))
202+
print_arr_gen(iter_motion(src, tb, **obj))
140203
elif method == "subtitle":
141204
print_arr(levels.subtitle(**obj))
142205
elif method == "none":

0 commit comments

Comments
 (0)