Skip to content

Commit fffe486

Browse files
committed
Make audio, motion, regular procedures
1 parent f585e29 commit fffe486

File tree

3 files changed

+123
-124
lines changed

3 files changed

+123
-124
lines changed

auto_editor/analyze.py

-82
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,10 @@
2121
from auto_editor.lib.data_structs import Sym
2222
from auto_editor.render.subtitle import SubtitleParser
2323
from auto_editor.utils.cmdkw import (
24-
ParserError,
2524
Required,
26-
parse_with_palet,
2725
pAttr,
2826
pAttrs,
2927
)
30-
from auto_editor.utils.func import boolop
3128
from auto_editor.wavfile import read
3229

3330
if TYPE_CHECKING:
@@ -38,7 +35,6 @@
3835
from numpy.typing import NDArray
3936

4037
from auto_editor.ffwrapper import FileInfo
41-
from auto_editor.lib.data_structs import Env
4238
from auto_editor.output import Ensure
4339
from auto_editor.utils.bar import Bar
4440
from auto_editor.utils.log import Log
@@ -412,81 +408,3 @@ def motion(self, s: int, blur: int, width: int) -> NDArray[np.float64]:
412408

413409
self.bar.end()
414410
return self.cache("motion", mobj, threshold_list[:index])
415-
416-
417-
def edit_method(val: str, filesetup: FileSetup, env: Env) -> NDArray[np.bool_]:
418-
assert isinstance(filesetup, FileSetup)
419-
src = filesetup.src
420-
tb = filesetup.tb
421-
ensure = filesetup.ensure
422-
strict = filesetup.strict
423-
bar = filesetup.bar
424-
temp = filesetup.temp
425-
log = filesetup.log
426-
427-
if ":" in val:
428-
method, attrs = val.split(":", 1)
429-
else:
430-
method, attrs = val, ""
431-
432-
levels = Levels(ensure, src, tb, bar, temp, log)
433-
434-
if method == "none":
435-
return levels.none()
436-
if method == "all/e":
437-
return levels.all()
438-
439-
try:
440-
obj = parse_with_palet(attrs, builder_map[method], env)
441-
except ParserError as e:
442-
log.error(e)
443-
444-
try:
445-
if method == "audio":
446-
s = obj["stream"]
447-
if s == "all" or s == Sym("all"):
448-
total_list: NDArray[np.bool_] | None = None
449-
for s in range(len(src.audios)):
450-
audio_list = to_threshold(levels.audio(s), obj["threshold"])
451-
if total_list is None:
452-
total_list = audio_list
453-
else:
454-
total_list = boolop(total_list, audio_list, np.logical_or)
455-
456-
if total_list is None:
457-
if strict:
458-
log.error("Input has no audio streams.")
459-
stream_data = levels.all()
460-
else:
461-
stream_data = total_list
462-
else:
463-
assert isinstance(s, int)
464-
stream_data = to_threshold(levels.audio(s), obj["threshold"])
465-
466-
assert isinstance(obj["minclip"], int)
467-
assert isinstance(obj["mincut"], int)
468-
469-
mut_remove_small(stream_data, obj["minclip"], replace=1, with_=0)
470-
mut_remove_small(stream_data, obj["mincut"], replace=0, with_=1)
471-
472-
return stream_data
473-
474-
if method == "motion":
475-
return to_threshold(
476-
levels.motion(obj["stream"], obj["blur"], obj["width"]),
477-
obj["threshold"],
478-
)
479-
480-
if method == "subtitle":
481-
return levels.subtitle(
482-
obj["pattern"],
483-
obj["stream"],
484-
obj["ignore_case"],
485-
obj["max_count"],
486-
)
487-
except LevelError as e:
488-
if strict:
489-
log.error(e)
490-
491-
return levels.all()
492-
raise ValueError("Unreachable")

auto_editor/lang/palet.py

+90-31
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
import numpy as np
1919
from numpy import logical_and, logical_not, logical_or, logical_xor
2020

21-
from auto_editor.analyze import edit_method, mut_remove_large, mut_remove_small
21+
from auto_editor.analyze import (
22+
LevelError,
23+
mut_remove_large,
24+
mut_remove_small,
25+
to_threshold,
26+
)
2227
from auto_editor.lib.contracts import *
2328
from auto_editor.lib.data_structs import *
2429
from auto_editor.lib.err import MyError
@@ -49,7 +54,6 @@ class ClosingError(MyError):
4954
LPAREN, RPAREN, LBRAC, RBRAC, LCUR, RCUR, EOF = "(", ")", "[", "]", "{", "}", "EOF"
5055
VAL, QUOTE, SEC, DB, DOT, VLIT = "VAL", "QUOTE", "SEC", "DB", "DOT", "VLIT"
5156
SEC_UNITS = ("s", "sec", "secs", "second", "seconds")
52-
METHODS = ("audio:", "motion:", "subtitle:")
5357
brac_pairs = {LPAREN: RPAREN, LBRAC: RBRAC, LCUR: RCUR}
5458

5559
str_escape = {
@@ -315,7 +319,6 @@ def get_next_token(self) -> Token:
315319

316320
result = ""
317321
has_illegal = False
318-
is_method = False
319322

320323
def normal() -> bool:
321324
return (
@@ -334,21 +337,14 @@ def handle_strings() -> bool:
334337

335338
while normal():
336339
result += self.char
337-
if (result + ":") in METHODS:
338-
is_method = True
339-
normal = handle_strings
340+
# if (result + ":") in METHODS:
341+
# is_method = True
342+
# normal = handle_strings
340343

341344
if self.char in "'`|\\":
342345
has_illegal = True
343346
self.advance()
344347

345-
if is_method:
346-
return Token(VAL, Method(result))
347-
348-
for method in METHODS:
349-
if result == method[:-1]:
350-
return Token(VAL, Method(result))
351-
352348
if self.char == ".": # handle `object.method` syntax
353349
self.advance()
354350
return Token(DOT, (Sym(result), self.get_next_token()))
@@ -368,16 +364,6 @@ def handle_strings() -> bool:
368364
###############################################################################
369365

370366

371-
@dataclass(slots=True)
372-
class Method:
373-
val: str
374-
375-
def __str__(self) -> str:
376-
return f"#<method:{self.val}>"
377-
378-
__repr__ = __str__
379-
380-
381367
class Parser:
382368
def __init__(self, lexer: Lexer):
383369
self.lexer = lexer
@@ -807,7 +793,7 @@ def __call__(self, *args: Any) -> Any:
807793

808794

809795
@dataclass(slots=True)
810-
class KeywordProc:
796+
class KeywordUserProc:
811797
env: Env
812798
name: str
813799
parms: list[str]
@@ -952,7 +938,7 @@ def syn_define(env: Env, node: Node) -> None:
952938
raise MyError(f"{node[0]}: must be an identifier")
953939

954940
if kw_only:
955-
env[n] = KeywordProc(env, n, parms, kparms, body, (len(parms), None))
941+
env[n] = KeywordUserProc(env, n, parms, kparms, body, (len(parms), None))
956942
else:
957943
env[n] = UserProc(env, n, parms, (), body)
958944
return None
@@ -1481,6 +1467,60 @@ def edit_all() -> np.ndarray:
14811467
return env["@levels"].all()
14821468

14831469

1470+
def edit_audio(
1471+
threshold: float = 0.04, stream: object = "all", mincut: int = 6 , minclip: int = 3
1472+
) -> np.ndarray:
1473+
if "@levels" not in env or "@filesetup" not in env:
1474+
raise MyError("Can't use `audio` if there's no input media")
1475+
1476+
levels = env["@levels"]
1477+
src = env["@filesetup"].src
1478+
1479+
stream_data: NDArray[np.bool_] | None = None
1480+
if stream == "all" or stream == Sym("all"):
1481+
stream_range = range(0, len(src.audios))
1482+
else:
1483+
assert isinstance(stream, int)
1484+
stream_range = range(stream, stream + 1)
1485+
1486+
try:
1487+
for s in stream_range:
1488+
audio_list = to_threshold(levels.audio(s), threshold)
1489+
if stream_data is None:
1490+
stream_data = audio_list
1491+
else:
1492+
stream_data = boolop(stream_data, audio_list, np.logical_or)
1493+
except LevelError as e:
1494+
raise MyError(e)
1495+
1496+
if stream_data is not None:
1497+
mut_remove_small(stream_data, minclip, replace=1, with_=0)
1498+
mut_remove_small(stream_data, mincut, replace=0, with_=1)
1499+
1500+
return stream_data
1501+
1502+
return levels.all()
1503+
1504+
1505+
def edit_motion(
1506+
threshold: float = 0.02,
1507+
stream: int = 0,
1508+
blur: int = 9,
1509+
width: int = 400,
1510+
) -> np.ndarray:
1511+
if "@levels" not in env:
1512+
raise MyError("Can't use `motion` if there's no input media")
1513+
1514+
return to_threshold(env["@levels"].motion(stream, blur, width), threshold)
1515+
1516+
1517+
def edit_subtitle(pattern, stream=0, ignore_case=False, max_count=None):
1518+
if "@levels" not in env:
1519+
raise MyError("Can't use `subtitle` if there's no input media")
1520+
1521+
return env["@levels"].subtitle(pattern, stream, ignore_case, max_count)
1522+
1523+
14841524
def my_eval(env: Env, node: object) -> Any:
14851525
if type(node) is Sym:
14861526
val = env.get(node.val)
@@ -1494,11 +1534,6 @@ def my_eval(env: Env, node: object) -> Any:
14941534
)
14951535
return val
14961536

1497-
if isinstance(node, Method):
1498-
if "@filesetup" not in env:
1499-
raise MyError("Can't use edit methods if there's no input files")
1500-
return edit_method(node.val, env["@filesetup"], env)
1501-
15021537
if type(node) is list:
15031538
return [my_eval(env, item) for item in node]
15041539

@@ -1530,7 +1565,21 @@ def my_eval(env: Env, node: object) -> Any:
15301565
if type(oper) is Syntax:
15311566
return oper(env, node)
15321567

1533-
return oper(*(my_eval(env, c) for c in node[1:]))
1568+
i = 1
1569+
args: list[Any] = []
1570+
kwargs: dict[str, Any] = {}
1571+
while i < len(node):
1572+
result = my_eval(env, node[i])
1573+
if type(result) is Keyword:
1574+
i += 1
1575+
if i >= len(node):
1576+
raise MyError("todo: write good error message")
1577+
kwargs[result.val] = my_eval(env, node[i])
1578+
else:
1579+
args.append(result)
1580+
i += 1
1581+
1582+
return oper(*args, **kwargs)
15341583

15351584
return node
15361585

@@ -1545,6 +1594,16 @@ def my_eval(env: Env, node: object) -> Any:
15451594
# edit procedures
15461595
"none": Proc("none", edit_none, (0, 0)),
15471596
"all/e": Proc("all/e", edit_all, (0, 0)),
1597+
"audio": Proc("audio", edit_audio, (0, 4),
1598+
is_threshold, orc(is_nat, Sym("all"), "all"), is_nat,
1599+
{"threshold": 0, "stream": 1, "minclip": 2, "mincut": 2}
1600+
),
1601+
"motion": Proc("motion", edit_motion, (0, 4),
1602+
is_threshold, is_nat, is_nat, is_nat1
1603+
),
1604+
"subtitle": Proc("subtitle", edit_subtitle, (1, 4),
1605+
is_str, is_nat, is_bool, orc(is_nat, is_void)
1606+
),
15481607
# syntax
15491608
"lambda": Syntax(syn_lambda),
15501609
"λ": Syntax(syn_lambda),

0 commit comments

Comments
 (0)