auto-editor 24.7.1__py3-none-any.whl → 24.13.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
auto_editor/lang/palet.py CHANGED
@@ -3,7 +3,6 @@ Palet is a light-weight scripting languge. It handles `--edit` and the `repl`.
3
3
  The syntax is inspired by the Racket Programming language.
4
4
  """
5
5
 
6
-
7
6
  from __future__ import annotations
8
7
 
9
8
  from cmath import sqrt as complex_sqrt
@@ -19,7 +18,12 @@ from typing import TYPE_CHECKING
19
18
  import numpy as np
20
19
  from numpy import logical_and, logical_not, logical_or, logical_xor
21
20
 
22
- 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
+ )
23
27
  from auto_editor.lib.contracts import *
24
28
  from auto_editor.lib.data_structs import *
25
29
  from auto_editor.lib.err import MyError
@@ -48,9 +52,8 @@ class ClosingError(MyError):
48
52
  ###############################################################################
49
53
 
50
54
  LPAREN, RPAREN, LBRAC, RBRAC, LCUR, RCUR, EOF = "(", ")", "[", "]", "{", "}", "EOF"
51
- VAL, QUOTE, SEC, DB, DOT, VLIT = "VAL", "QUOTE", "SEC", "DB", "DOT", "VLIT"
55
+ VAL, QUOTE, SEC, DB, DOT, VLIT, M = "VAL", "QUOTE", "SEC", "DB", "DOT", "VLIT", "M"
52
56
  SEC_UNITS = ("s", "sec", "secs", "second", "seconds")
53
- METHODS = ("audio:", "motion:", "subtitle:")
54
57
  brac_pairs = {LPAREN: RPAREN, LBRAC: RBRAC, LCUR: RCUR}
55
58
 
56
59
  str_escape = {
@@ -316,7 +319,6 @@ class Lexer:
316
319
 
317
320
  result = ""
318
321
  has_illegal = False
319
- is_method = False
320
322
 
321
323
  def normal() -> bool:
322
324
  return (
@@ -333,22 +335,24 @@ class Lexer:
333
335
  else:
334
336
  return self.char_is_norm()
335
337
 
338
+ is_method = False
336
339
  while normal():
337
- result += self.char
338
- if (result + ":") in METHODS:
340
+ if self.char == ":":
341
+ name = result
342
+ result = ""
339
343
  is_method = True
340
344
  normal = handle_strings
345
+ else:
346
+ result += self.char
341
347
 
342
348
  if self.char in "'`|\\":
343
349
  has_illegal = True
344
350
  self.advance()
345
351
 
346
352
  if is_method:
347
- return Token(VAL, Method(result))
353
+ from auto_editor.utils.cmdkw import parse_method
348
354
 
349
- for method in METHODS:
350
- if result == method[:-1]:
351
- return Token(VAL, Method(result))
355
+ return Token(M, parse_method(name, result, env))
352
356
 
353
357
  if self.char == ".": # handle `object.method` syntax
354
358
  self.advance()
@@ -369,16 +373,6 @@ class Lexer:
369
373
  ###############################################################################
370
374
 
371
375
 
372
- @dataclass(slots=True)
373
- class Method:
374
- val: str
375
-
376
- def __str__(self) -> str:
377
- return f"#<method:{self.val}>"
378
-
379
- __repr__ = __str__
380
-
381
-
382
376
  class Parser:
383
377
  def __init__(self, lexer: Lexer):
384
378
  self.lexer = lexer
@@ -413,6 +407,16 @@ class Parser:
413
407
  self.eat()
414
408
  return (Sym("pow"), 10, (Sym("/"), token.value, 20))
415
409
 
410
+ if token.type == M:
411
+ self.eat()
412
+ name, args, kwargs = token.value
413
+ _result = [Sym(name)] + args
414
+ for key, val in kwargs.items():
415
+ _result.append(Keyword(key))
416
+ _result.append(val)
417
+
418
+ return tuple(_result)
419
+
416
420
  if token.type == DOT:
417
421
  self.eat()
418
422
  if type(token.value[1].value) is not Sym:
@@ -506,7 +510,7 @@ def initOutPort(name: str) -> OutputPort | Literal[False]:
506
510
  return OutputPort(name, port, port.write, False)
507
511
 
508
512
 
509
- def raise_(msg: str) -> None:
513
+ def raise_(msg: str | Exception) -> NoReturn:
510
514
  raise MyError(msg)
511
515
 
512
516
 
@@ -808,7 +812,7 @@ class UserProc(Proc):
808
812
 
809
813
 
810
814
  @dataclass(slots=True)
811
- class KeywordProc:
815
+ class KeywordUserProc:
812
816
  env: Env
813
817
  name: str
814
818
  parms: list[str]
@@ -817,38 +821,21 @@ class KeywordProc:
817
821
  arity: tuple[int, None]
818
822
  contracts: list[Any] | None = None
819
823
 
820
- def __call__(self, *args: Any) -> Any:
824
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
821
825
  env = {}
822
-
823
- for i, parm in enumerate(self.parms):
824
- if type(args[i]) is Keyword:
825
- raise MyError(f"Invalid keyword `{args[i]}`")
826
- env[parm] = args[i]
827
-
828
- remain_args = args[len(self.parms) :]
829
-
830
- allow_pos = True
831
- pos_index = 0
832
- key = ""
833
- for arg in remain_args:
834
- if type(arg) is Keyword:
835
- if key:
836
- raise MyError("Expected value for keyword but got another keyword")
837
- key = arg.val
838
- allow_pos = False
839
- elif key:
840
- env[key] = arg
841
- key = ""
826
+ all_parms = self.parms + self.kw_parms
827
+ for i, arg in enumerate(args):
828
+ if i >= len(all_parms):
829
+ raise MyError("Too many arguments")
830
+ env[all_parms[i]] = arg
831
+
832
+ for key, val in kwargs.items():
833
+ if key in env:
834
+ raise MyError(
835
+ f"Keyword: {key} already fulfilled by positional argument."
836
+ )
842
837
  else:
843
- if not allow_pos:
844
- raise MyError("Positional argument not allowed here")
845
- if pos_index >= len(self.kw_parms):
846
- base = f"`{self.name}` has an arity mismatch. Expected"
847
- upper = len(self.parms) + len(self.kw_parms)
848
- raise MyError(f"{base} at most {upper}")
849
-
850
- env[self.kw_parms[pos_index]] = arg
851
- pos_index += 1
838
+ env[key] = val
852
839
 
853
840
  inner_env = Env(env, self.env)
854
841
 
@@ -953,7 +940,7 @@ def syn_define(env: Env, node: Node) -> None:
953
940
  raise MyError(f"{node[0]}: must be an identifier")
954
941
 
955
942
  if kw_only:
956
- env[n] = KeywordProc(env, n, parms, kparms, body, (len(parms), None))
943
+ env[n] = KeywordUserProc(env, n, parms, kparms, body, (len(parms), None))
957
944
  else:
958
945
  env[n] = UserProc(env, n, parms, (), body)
959
946
  return None
@@ -1482,6 +1469,81 @@ def edit_all() -> np.ndarray:
1482
1469
  return env["@levels"].all()
1483
1470
 
1484
1471
 
1472
+ def edit_audio(
1473
+ threshold: float = 0.04,
1474
+ stream: object = Sym("all"),
1475
+ mincut: int = 6,
1476
+ minclip: int = 3,
1477
+ ) -> np.ndarray:
1478
+ if "@levels" not in env or "@filesetup" not in env:
1479
+ raise MyError("Can't use `audio` if there's no input media")
1480
+
1481
+ levels = env["@levels"]
1482
+ src = env["@filesetup"].src
1483
+ strict = env["@filesetup"].strict
1484
+
1485
+ stream_data: NDArray[np.bool_] | None = None
1486
+ if stream == Sym("all"):
1487
+ stream_range = range(0, len(src.audios))
1488
+ else:
1489
+ assert isinstance(stream, int)
1490
+ stream_range = range(stream, stream + 1)
1491
+
1492
+ try:
1493
+ for s in stream_range:
1494
+ audio_list = to_threshold(levels.audio(s), threshold)
1495
+ if stream_data is None:
1496
+ stream_data = audio_list
1497
+ else:
1498
+ stream_data = boolop(stream_data, audio_list, np.logical_or)
1499
+ except LevelError as e:
1500
+ raise_(e) if strict else levels.all()
1501
+
1502
+ if stream_data is not None:
1503
+ mut_remove_small(stream_data, minclip, replace=1, with_=0)
1504
+ mut_remove_small(stream_data, mincut, replace=0, with_=1)
1505
+
1506
+ return stream_data
1507
+
1508
+ stream = 0 if stream == Sym("all") else stream
1509
+ return raise_(f"audio stream '{stream}' does not exist") if strict else levels.all()
1510
+
1511
+
1512
+ def edit_motion(
1513
+ threshold: float = 0.02,
1514
+ stream: int = 0,
1515
+ blur: int = 9,
1516
+ width: int = 400,
1517
+ ) -> np.ndarray:
1518
+ if "@levels" not in env:
1519
+ raise MyError("Can't use `motion` if there's no input media")
1520
+
1521
+ levels = env["@levels"]
1522
+ strict = env["@filesetup"].strict
1523
+ try:
1524
+ return to_threshold(levels.motion(stream, blur, width), threshold)
1525
+ except LevelError as e:
1526
+ return raise_(e) if strict else levels.all()
1527
+
1528
+
1529
+ def edit_subtitle(pattern, stream=0, **kwargs):
1530
+ if "@levels" not in env:
1531
+ raise MyError("Can't use `subtitle` if there's no input media")
1532
+
1533
+ levels = env["@levels"]
1534
+ strict = env["@filesetup"].strict
1535
+ if "ignore-case" not in kwargs:
1536
+ kwargs["ignore-case"] = False
1537
+ if "max-count" not in kwargs:
1538
+ kwargs["max-count"] = None
1539
+ ignore_case = kwargs["ignore-case"]
1540
+ max_count = kwargs["max-count"]
1541
+ try:
1542
+ return levels.subtitle(pattern, stream, ignore_case, max_count)
1543
+ except LevelError as e:
1544
+ return raise_(e) if strict else levels.all()
1545
+
1546
+
1485
1547
  def my_eval(env: Env, node: object) -> Any:
1486
1548
  if type(node) is Sym:
1487
1549
  val = env.get(node.val)
@@ -1495,11 +1557,6 @@ def my_eval(env: Env, node: object) -> Any:
1495
1557
  )
1496
1558
  return val
1497
1559
 
1498
- if isinstance(node, Method):
1499
- if "@filesetup" not in env:
1500
- raise MyError("Can't use edit methods if there's no input files")
1501
- return edit_method(node.val, env["@filesetup"], env)
1502
-
1503
1560
  if type(node) is list:
1504
1561
  return [my_eval(env, item) for item in node]
1505
1562
 
@@ -1531,7 +1588,21 @@ def my_eval(env: Env, node: object) -> Any:
1531
1588
  if type(oper) is Syntax:
1532
1589
  return oper(env, node)
1533
1590
 
1534
- return oper(*(my_eval(env, c) for c in node[1:]))
1591
+ i = 1
1592
+ args: list[Any] = []
1593
+ kwargs: dict[str, Any] = {}
1594
+ while i < len(node):
1595
+ result = my_eval(env, node[i])
1596
+ if type(result) is Keyword:
1597
+ i += 1
1598
+ if i >= len(node):
1599
+ raise MyError("Keyword need argument")
1600
+ kwargs[result.val] = my_eval(env, node[i])
1601
+ else:
1602
+ args.append(result)
1603
+ i += 1
1604
+
1605
+ return oper(*args, **kwargs)
1535
1606
 
1536
1607
  return node
1537
1608
 
@@ -1546,6 +1617,18 @@ env.update({
1546
1617
  # edit procedures
1547
1618
  "none": Proc("none", edit_none, (0, 0)),
1548
1619
  "all/e": Proc("all/e", edit_all, (0, 0)),
1620
+ "audio": Proc("audio", edit_audio, (0, 4),
1621
+ is_threshold, orc(is_nat, Sym("all")), is_nat,
1622
+ {"threshold": 0, "stream": 1, "minclip": 2, "mincut": 2}
1623
+ ),
1624
+ "motion": Proc("motion", edit_motion, (0, 4),
1625
+ is_threshold, is_nat, is_nat1,
1626
+ {"threshold": 0, "stream": 1, "blur": 1, "width": 2}
1627
+ ),
1628
+ "subtitle": Proc("subtitle", edit_subtitle, (1, 4),
1629
+ is_str, is_nat, is_bool, orc(is_nat, is_void),
1630
+ {"pattern": 0, "stream": 1, "ignore-case": 2, "max-count": 3}
1631
+ ),
1549
1632
  # syntax
1550
1633
  "lambda": Syntax(syn_lambda),
1551
1634
  "λ": Syntax(syn_lambda),
@@ -47,7 +47,7 @@ def check_contract(c: object, val: object) -> bool:
47
47
 
48
48
 
49
49
  def check_args(
50
- o: str,
50
+ name: str,
51
51
  values: list | tuple,
52
52
  arity: tuple[int, int | None],
53
53
  cont: tuple[Any, ...],
@@ -56,7 +56,7 @@ def check_args(
56
56
  amount = len(values)
57
57
 
58
58
  assert not (upper is not None and lower > upper)
59
- base = f"`{o}` has an arity mismatch. Expected "
59
+ base = f"`{name}` has an arity mismatch. Expected "
60
60
 
61
61
  if lower == upper and len(values) != lower:
62
62
  raise MyError(f"{base}{lower}, got {amount}")
@@ -72,11 +72,11 @@ def check_args(
72
72
  check = cont[-1] if i >= len(cont) else cont[i]
73
73
  if not check_contract(check, val):
74
74
  exp = f"{check}" if callable(check) else print_str(check)
75
- raise MyError(f"`{o}` expected a {exp}, got {print_str(val)}")
75
+ raise MyError(f"`{name}` expected {exp}, but got {print_str(val)}")
76
76
 
77
77
 
78
78
  class Proc:
79
- __slots__ = ("name", "proc", "arity", "contracts")
79
+ __slots__ = ("name", "proc", "arity", "contracts", "kw_contracts")
80
80
 
81
81
  def __init__(
82
82
  self, n: str, p: Callable, a: tuple[int, int | None] = (1, None), *c: Any
@@ -84,11 +84,52 @@ class Proc:
84
84
  self.name = n
85
85
  self.proc = p
86
86
  self.arity = a
87
- self.contracts: tuple[Any, ...] = c
88
87
 
89
- def __call__(self, *args: Any) -> Any:
90
- check_args(self.name, args, self.arity, self.contracts)
91
- return self.proc(*args)
88
+ if c and type(c[-1]) is dict:
89
+ self.kw_contracts: dict[str, int] | None = c[-1]
90
+ self.contracts: tuple[Any, ...] = c[:-1]
91
+ else:
92
+ self.kw_contracts = None
93
+ self.contracts = c
94
+
95
+ def __call__(self, *args: Any, **kwargs: Any):
96
+ lower, upper = self.arity
97
+ amount = len(args)
98
+ cont = self.contracts
99
+ kws = self.kw_contracts
100
+
101
+ assert not (upper is not None and lower > upper)
102
+ base = f"`{self.name}` has an arity mismatch. Expected "
103
+
104
+ if lower == upper and len(args) != lower:
105
+ raise MyError(f"{base}{lower}, got {amount}")
106
+ if upper is None and amount < lower:
107
+ raise MyError(f"{base}at least {lower}, got {amount}")
108
+ if upper is not None and (amount > upper or amount < lower):
109
+ raise MyError(f"{base}between {lower} and {upper}, got {amount}")
110
+
111
+ if not cont:
112
+ return self.proc(*args)
113
+
114
+ if kws is not None:
115
+ for key, val in kwargs.items():
116
+ check = cont[-1] if kws[key] >= len(cont) else cont[kws[key]]
117
+ if not check_contract(check, val):
118
+ exp = f"{check}" if callable(check) else print_str(check)
119
+ raise MyError(
120
+ f"`{self.name} #:{key}` expected {exp}, but got {print_str(val)}"
121
+ )
122
+
123
+ elif len(kwargs) > 0:
124
+ raise MyError("Keyword arguments are not allowed here")
125
+
126
+ for i, val in enumerate(args):
127
+ check = cont[-1] if i >= len(cont) else cont[i]
128
+ if not check_contract(check, val):
129
+ exp = f"{check}" if callable(check) else print_str(check)
130
+ raise MyError(f"`{self.name}` expected {exp}, but got {print_str(val)}")
131
+
132
+ return self.proc(*args, **kwargs)
92
133
 
93
134
  def __str__(self) -> str:
94
135
  return self.name
@@ -137,13 +178,19 @@ is_threshold = Contract(
137
178
  is_proc = Contract("procedure?", lambda v: isinstance(v, Proc | Contract))
138
179
 
139
180
 
181
+ def contract_printer(cs) -> str:
182
+ return " ".join(
183
+ c.name if isinstance(c, Proc | Contract) else print_str(c) for c in cs
184
+ )
185
+
186
+
140
187
  def andc(*cs: object) -> Proc:
141
- name = "(and/c " + " ".join(f"{c}" for c in cs) + ")"
188
+ name = f"(and/c {contract_printer(cs)})"
142
189
  return Proc(name, lambda v: all(check_contract(c, v) for c in cs), (1, 1), any_p)
143
190
 
144
191
 
145
192
  def orc(*cs: object) -> Proc:
146
- name = "(or/c " + " ".join(f"{c}" for c in cs) + ")"
193
+ name = f"(or/c {contract_printer(cs)})"
147
194
  return Proc(name, lambda v: any(check_contract(c, v) for c in cs), (1, 1), any_p)
148
195
 
149
196
 
@@ -57,6 +57,7 @@ class Sym:
57
57
  __slots__ = ("val", "hash")
58
58
 
59
59
  def __init__(self, val: str):
60
+ assert isinstance(val, str)
60
61
  self.val = val
61
62
  self.hash = hash(val)
62
63
 
auto_editor/output.py CHANGED
@@ -182,18 +182,25 @@ def mux_quality_media(
182
182
  cmd += _ffset("-c:a", args.audio_codec) + _ffset("-b:a", args.audio_bitrate)
183
183
 
184
184
  if same_container and v_tracks > 0:
185
- cmd += (
186
- _ffset("-color_range", src.videos[0].color_range)
187
- + _ffset("-colorspace", src.videos[0].color_space)
188
- + _ffset("-color_primaries", src.videos[0].color_primaries)
189
- + _ffset("-color_trc", src.videos[0].color_transfer)
190
- )
185
+ color_range = src.videos[0].color_range
186
+ colorspace = src.videos[0].color_space
187
+ color_prim = src.videos[0].color_primaries
188
+ color_trc = src.videos[0].color_transfer
189
+
190
+ if color_range == 1 or color_range == 2:
191
+ cmd.extend(["-color_range", f"{color_range}"])
192
+ if colorspace in (0, 1) or (colorspace >= 3 and colorspace < 16):
193
+ cmd.extend(["-colorspace", f"{colorspace}"])
194
+ if color_prim in (0, 1) or (color_prim >= 4 and color_prim < 17):
195
+ cmd.extend(["-color_primaries", f"{color_prim}"])
196
+ if color_trc == 1 or (color_trc >= 4 and color_trc < 22):
197
+ cmd.extend(["-color_trc", f"{color_trc}"])
191
198
 
192
199
  if args.extras is not None:
193
200
  cmd.extend(args.extras.split(" "))
194
201
  cmd.extend(["-strict", "-2"]) # Allow experimental codecs.
195
202
 
196
- if not args.sn:
203
+ if s_tracks > 0:
197
204
  cmd.extend(["-map", "0:t?"]) # Add input attachments to output.
198
205
 
199
206
  # This was causing a crash for 'example.mp4 multi-track.mov'
@@ -168,15 +168,26 @@ def render_av(
168
168
  log.debug(f"Target pix_fmt: {target_pix_fmt}")
169
169
 
170
170
  apply_video_later = True
171
-
172
- if args.scale != 1:
173
- apply_video_later = False
174
- elif args.video_codec in encoders:
171
+ if args.video_codec in encoders:
175
172
  apply_video_later = set(encoders[args.video_codec]).isdisjoint(allowed_pix_fmt)
176
173
 
177
174
  log.debug(f"apply video quality settings now: {not apply_video_later}")
178
175
 
179
- width, height = tl.res
176
+ if args.scale == 1.0:
177
+ target_width, target_height = tl.res
178
+ scale_graph = None
179
+ else:
180
+ target_width = max(round(tl.res[0] * args.scale), 2)
181
+ target_height = max(round(tl.res[1] * args.scale), 2)
182
+ scale_graph = av.filter.Graph()
183
+ link_nodes(
184
+ scale_graph.add(
185
+ "buffer", video_size="1x1", time_base="1/1", pix_fmt=target_pix_fmt
186
+ ),
187
+ scale_graph.add("scale", f"{target_width}:{target_height}"),
188
+ scale_graph.add("buffersink"),
189
+ )
190
+
180
191
  spedup = os.path.join(temp, "spedup0.mp4")
181
192
 
182
193
  cmd = [
@@ -189,7 +200,7 @@ def render_av(
189
200
  "-pix_fmt",
190
201
  target_pix_fmt,
191
202
  "-s",
192
- f"{width}*{height}",
203
+ f"{target_width}*{target_height}",
193
204
  "-framerate",
194
205
  f"{tl.tb}",
195
206
  "-i",
@@ -224,7 +235,7 @@ def render_av(
224
235
  bar.start(tl.end, "Creating new video")
225
236
 
226
237
  bg = color(args.background)
227
- null_frame = make_solid(width, height, target_pix_fmt, bg)
238
+ null_frame = make_solid(target_width, target_height, target_pix_fmt, bg)
228
239
  frame_index = -1
229
240
  try:
230
241
  for index in range(tl.end):
@@ -282,7 +293,8 @@ def render_av(
282
293
  if frame.key_frame:
283
294
  log.debug(f"Keyframe {frame_index} {frame.pts}")
284
295
 
285
- if frame.width != width or frame.height != height:
296
+ if (frame.width, frame.height) != tl.res:
297
+ width, height = tl.res
286
298
  graph = av.filter.Graph()
287
299
  link_nodes(
288
300
  graph.add_buffer(template=my_stream),
@@ -345,6 +357,10 @@ def render_av(
345
357
 
346
358
  frame = av.VideoFrame.from_ndarray(array, format="rgb24")
347
359
 
360
+ if scale_graph is not None and frame.width != target_width:
361
+ scale_graph.push(frame)
362
+ frame = scale_graph.pull()
363
+
348
364
  if frame.format.name != target_pix_fmt:
349
365
  frame = frame.reformat(format=target_pix_fmt)
350
366
  bar.tick(index)
@@ -363,19 +379,4 @@ def render_av(
363
379
 
364
380
  log.debug(f"Total frames saved seeking: {frames_saved}")
365
381
 
366
- if args.scale != 1:
367
- sped_input = os.path.join(temp, "spedup0.mp4")
368
- spedup = os.path.join(temp, "scale0.mp4")
369
- scale_filter = f"scale=iw*{args.scale}:ih*{args.scale}"
370
-
371
- cmd = ["-i", sped_input, "-vf", scale_filter, spedup]
372
-
373
- check_errors = ffmpeg.pipe(cmd)
374
- if "Error" in check_errors or "failed" in check_errors:
375
- if "-allow_sw 1" in check_errors:
376
- cmd.insert(-1, "-allow_sw")
377
- cmd.insert(-1, "1")
378
- # Run again to show errors even if it might not work.
379
- ffmpeg.run(cmd)
380
-
381
382
  return spedup, apply_video_later
@@ -3,28 +3,26 @@ from __future__ import annotations
3
3
  import sys
4
4
  from dataclasses import dataclass, field
5
5
 
6
- from auto_editor.ffwrapper import FFmpeg, initFileInfo
6
+ from auto_editor.ffwrapper import initFileInfo
7
7
  from auto_editor.utils.log import Log
8
8
  from auto_editor.vanparse import ArgumentParser
9
9
 
10
10
 
11
11
  @dataclass(slots=True)
12
12
  class DescArgs:
13
- ffmpeg_location: str | None = None
14
13
  help: bool = False
15
14
  input: list[str] = field(default_factory=list)
16
15
 
17
16
 
18
17
  def desc_options(parser: ArgumentParser) -> ArgumentParser:
19
18
  parser.add_required("input", nargs="*")
20
- parser.add_argument("--ffmpeg-location", help="Point to your custom ffmpeg file")
21
19
  return parser
22
20
 
23
21
 
24
22
  def main(sys_args: list[str] = sys.argv[1:]) -> None:
25
23
  args = desc_options(ArgumentParser("desc")).parse_args(DescArgs, sys_args)
26
24
  for path in args.input:
27
- src = initFileInfo(path, FFmpeg(args.ffmpeg_location), Log())
25
+ src = initFileInfo(path, Log())
28
26
  if src.description is not None:
29
27
  sys.stdout.write(f"\n{src.description}\n\n")
30
28
  else: