auto-editor 27.1.1__py3-none-any.whl → 28.0.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.
Files changed (37) hide show
  1. auto_editor/__init__.py +1 -1
  2. auto_editor/__main__.py +0 -8
  3. auto_editor/cmds/cache.py +1 -1
  4. auto_editor/cmds/desc.py +8 -9
  5. auto_editor/cmds/info.py +4 -17
  6. auto_editor/cmds/palet.py +1 -1
  7. auto_editor/cmds/subdump.py +2 -4
  8. auto_editor/cmds/test.py +50 -31
  9. auto_editor/edit.py +52 -52
  10. auto_editor/{formats → exports}/fcp11.py +11 -7
  11. auto_editor/{formats → exports}/fcp7.py +7 -239
  12. auto_editor/exports/json.py +32 -0
  13. auto_editor/{formats → exports}/shotcut.py +8 -17
  14. auto_editor/ffwrapper.py +1 -3
  15. auto_editor/help.py +6 -17
  16. auto_editor/imports/__init__.py +0 -0
  17. auto_editor/imports/fcp7.py +275 -0
  18. auto_editor/{formats → imports}/json.py +16 -43
  19. auto_editor/json.py +2 -2
  20. auto_editor/lang/palet.py +6 -43
  21. auto_editor/lang/stdenv.py +8 -20
  22. auto_editor/lib/contracts.py +4 -6
  23. auto_editor/lib/data_structs.py +0 -3
  24. auto_editor/make_layers.py +13 -13
  25. auto_editor/preview.py +1 -2
  26. auto_editor/render/audio.py +9 -6
  27. auto_editor/render/video.py +3 -2
  28. auto_editor/timeline.py +35 -52
  29. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/METADATA +2 -2
  30. auto_editor-28.0.1.dist-info/RECORD +56 -0
  31. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/WHEEL +1 -1
  32. auto_editor/formats/utils.py +0 -56
  33. auto_editor-27.1.1.dist-info/RECORD +0 -54
  34. /auto_editor/{formats → exports}/__init__.py +0 -0
  35. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/entry_points.txt +0 -0
  36. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/licenses/LICENSE +0 -0
  37. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/top_level.txt +0 -0
@@ -3,16 +3,10 @@ from __future__ import annotations
3
3
  import xml.etree.ElementTree as ET
4
4
  from fractions import Fraction
5
5
  from math import ceil
6
- from typing import TYPE_CHECKING
7
6
  from xml.etree.ElementTree import Element
8
7
 
9
8
  from auto_editor.ffwrapper import FileInfo
10
- from auto_editor.timeline import ASpace, Template, TlAudio, TlVideo, VSpace, v3
11
-
12
- from .utils import Validator, show
13
-
14
- if TYPE_CHECKING:
15
- from auto_editor.utils.log import Log
9
+ from auto_editor.timeline import Clip, v3
16
10
 
17
11
  """
18
12
  Premiere Pro uses the Final Cut Pro 7 XML Interchange Format
@@ -27,28 +21,6 @@ come back the way they started.
27
21
  DEPTH = "16"
28
22
 
29
23
 
30
- def uri_to_path(uri: str) -> str:
31
- urllib = __import__("urllib.parse", fromlist=["parse"])
32
-
33
- if uri.startswith("file://localhost/"):
34
- uri = uri[16:]
35
- elif uri.startswith("file://"):
36
- # Windows-style paths
37
- if len(uri) > 8 and uri[9] == ":":
38
- uri = uri[8:]
39
- else:
40
- uri = uri[7:]
41
- else:
42
- return uri
43
-
44
- return urllib.parse.unquote(uri)
45
-
46
- # /Users/wyattblue/projects/auto-editor/example.mp4
47
- # file:///Users/wyattblue/projects/auto-editor/example.mp4
48
- # file:///C:/Users/WyattBlue/projects/auto-editor/example.mp4
49
- # file://localhost/Users/wyattblue/projects/auto-editor/example.mp4
50
-
51
-
52
24
  def set_tb_ntsc(tb: Fraction) -> tuple[int, str]:
53
25
  # See chart: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/FinalCutPro_XML/FrameRate/FrameRate.html#//apple_ref/doc/uid/TP30001158-TPXREF103
54
26
  if tb == Fraction(24000, 1001):
@@ -65,19 +37,6 @@ def set_tb_ntsc(tb: Fraction) -> tuple[int, str]:
65
37
  return int(tb), "FALSE"
66
38
 
67
39
 
68
- def read_tb_ntsc(tb: int, ntsc: bool) -> Fraction:
69
- if ntsc:
70
- if tb == 24:
71
- return Fraction(24000, 1001)
72
- if tb == 30:
73
- return Fraction(30000, 1001)
74
- if tb == 60:
75
- return Fraction(60000, 1001)
76
- return tb * Fraction(999, 1000)
77
-
78
- return Fraction(tb)
79
-
80
-
81
40
  def speedup(speed: float) -> Element:
82
41
  fil = Element("filter")
83
42
  effect = ET.SubElement(fil, "effect")
@@ -106,200 +65,6 @@ def speedup(speed: float) -> Element:
106
65
  return fil
107
66
 
108
67
 
109
- SUPPORTED_EFFECTS = ("timeremap",)
110
-
111
-
112
- def read_filters(clipitem: Element, log: Log) -> float:
113
- for effect_tag in clipitem:
114
- if effect_tag.tag in {"enabled", "start", "end"}:
115
- continue
116
- if len(effect_tag) < 3:
117
- log.error("<effect> requires: <effectid> <name> and one <parameter>")
118
- for i, effects in enumerate(effect_tag):
119
- if i == 0 and effects.tag != "name":
120
- log.error("<effect>: <name> must be first tag")
121
- if i == 1 and effects.tag != "effectid":
122
- log.error("<effect>: <effectid> must be second tag")
123
- if effects.text not in SUPPORTED_EFFECTS:
124
- log.error(f"`{effects.text}` is not a supported effect.")
125
-
126
- if i > 1:
127
- for j, parms in enumerate(effects):
128
- if j == 0:
129
- if parms.tag != "parameterid":
130
- log.error("<parameter>: <parameterid> must be first tag")
131
- if parms.text != "speed":
132
- break
133
-
134
- if j > 0 and parms.tag == "value":
135
- if parms.text is None:
136
- log.error("<value>: number required")
137
- return float(parms.text) / 100
138
-
139
- return 1.0
140
-
141
-
142
- def fcp7_read_xml(path: str, log: Log) -> v3:
143
- def xml_bool(val: str) -> bool:
144
- if val == "TRUE":
145
- return True
146
- if val == "FALSE":
147
- return False
148
- raise TypeError("Value must be 'TRUE' or 'FALSE'")
149
-
150
- try:
151
- tree = ET.parse(path)
152
- except FileNotFoundError:
153
- log.error(f"Could not find '{path}'")
154
-
155
- root = tree.getroot()
156
-
157
- valid = Validator(log)
158
-
159
- valid.check(root, "xmeml")
160
- valid.check(root[0], "sequence")
161
- result = valid.parse(
162
- root[0],
163
- {
164
- "name": str,
165
- "duration": int,
166
- "rate": {
167
- "timebase": Fraction,
168
- "ntsc": xml_bool,
169
- },
170
- "media": None,
171
- },
172
- )
173
-
174
- tb = read_tb_ntsc(result["rate"]["timebase"], result["rate"]["ntsc"])
175
-
176
- av = valid.parse(
177
- result["media"],
178
- {
179
- "video": None,
180
- "audio": None,
181
- },
182
- )
183
-
184
- sources: dict[str, FileInfo] = {}
185
- vobjs: VSpace = []
186
- aobjs: ASpace = []
187
-
188
- vclip_schema = {
189
- "format": {
190
- "samplecharacteristics": {
191
- "width": int,
192
- "height": int,
193
- },
194
- },
195
- "track": {
196
- "__arr": "",
197
- "clipitem": {
198
- "__arr": "",
199
- "start": int,
200
- "end": int,
201
- "in": int,
202
- "out": int,
203
- "file": None,
204
- "filter": None,
205
- },
206
- },
207
- }
208
-
209
- aclip_schema = {
210
- "format": {"samplecharacteristics": {"samplerate": int}},
211
- "track": {
212
- "__arr": "",
213
- "clipitem": {
214
- "__arr": "",
215
- "start": int,
216
- "end": int,
217
- "in": int,
218
- "out": int,
219
- "file": None,
220
- "filter": None,
221
- },
222
- },
223
- }
224
-
225
- sr = 48000
226
- res = (1920, 1080)
227
-
228
- if "video" in av:
229
- tracks = valid.parse(av["video"], vclip_schema)
230
-
231
- if "format" in tracks:
232
- width = tracks["format"]["samplecharacteristics"]["width"]
233
- height = tracks["format"]["samplecharacteristics"]["height"]
234
- res = width, height
235
-
236
- for t, track in enumerate(tracks["track"]):
237
- if len(track["clipitem"]) > 0:
238
- vobjs.append([])
239
- for i, clipitem in enumerate(track["clipitem"]):
240
- file_id = clipitem["file"].attrib["id"]
241
- if file_id not in sources:
242
- fileobj = valid.parse(clipitem["file"], {"pathurl": str})
243
-
244
- if "pathurl" in fileobj:
245
- sources[file_id] = FileInfo.init(
246
- uri_to_path(fileobj["pathurl"]),
247
- log,
248
- )
249
- else:
250
- show(clipitem["file"], 3)
251
- log.error(
252
- f"'pathurl' child element not found in {clipitem['file'].tag}"
253
- )
254
-
255
- if "filter" in clipitem:
256
- speed = read_filters(clipitem["filter"], log)
257
- else:
258
- speed = 1.0
259
-
260
- start = clipitem["start"]
261
- dur = clipitem["end"] - start
262
- offset = clipitem["in"]
263
-
264
- vobjs[t].append(
265
- TlVideo(start, dur, sources[file_id], offset, speed, stream=0)
266
- )
267
-
268
- if "audio" in av:
269
- tracks = valid.parse(av["audio"], aclip_schema)
270
- if "format" in tracks:
271
- sr = tracks["format"]["samplecharacteristics"]["samplerate"]
272
-
273
- for t, track in enumerate(tracks["track"]):
274
- if len(track["clipitem"]) > 0:
275
- aobjs.append([])
276
- for i, clipitem in enumerate(track["clipitem"]):
277
- file_id = clipitem["file"].attrib["id"]
278
- if file_id not in sources:
279
- fileobj = valid.parse(clipitem["file"], {"pathurl": str})
280
- sources[file_id] = FileInfo.init(
281
- uri_to_path(fileobj["pathurl"]), log
282
- )
283
-
284
- if "filter" in clipitem:
285
- speed = read_filters(clipitem["filter"], log)
286
- else:
287
- speed = 1.0
288
-
289
- start = clipitem["start"]
290
- dur = clipitem["end"] - start
291
- offset = clipitem["in"]
292
-
293
- aobjs[t].append(
294
- TlAudio(
295
- start, dur, sources[file_id], offset, speed, volume=1, stream=0
296
- )
297
- )
298
-
299
- T = Template.init(sources[next(iter(sources))], sr, res=res)
300
- return v3(tb, "#000", T, vobjs, aobjs, v1=None)
301
-
302
-
303
68
  def media_def(
304
69
  filedef: Element, url: str, src: FileInfo, tl: v3, tb: int, ntsc: str
305
70
  ) -> None:
@@ -472,7 +237,7 @@ def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
472
237
  sequence = ET.SubElement(xmeml, "sequence", explodedTracks="true")
473
238
 
474
239
  ET.SubElement(sequence, "name").text = name
475
- ET.SubElement(sequence, "duration").text = f"{int(tl.out_len())}"
240
+ ET.SubElement(sequence, "duration").text = f"{len(tl)}"
476
241
  rate = ET.SubElement(sequence, "rate")
477
242
  ET.SubElement(rate, "timebase").text = f"{timebase}"
478
243
  ET.SubElement(rate, "ntsc").text = ntsc
@@ -493,7 +258,7 @@ def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
493
258
  track = ET.SubElement(video, "track")
494
259
 
495
260
  for j, clip in enumerate(tl.v[0]):
496
- assert isinstance(clip, TlVideo)
261
+ assert isinstance(clip, Clip)
497
262
 
498
263
  _start = f"{clip.start}"
499
264
  _end = f"{clip.start + clip.dur}"
@@ -542,4 +307,7 @@ def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
542
307
 
543
308
  tree = ET.ElementTree(xmeml)
544
309
  ET.indent(tree, space=" ", level=0)
545
- tree.write(output, xml_declaration=True, encoding="utf-8")
310
+ if output == "-":
311
+ print(ET.tostring(xmeml, encoding="unicode"))
312
+ else:
313
+ tree.write(output, xml_declaration=True, encoding="utf-8")
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from typing import TYPE_CHECKING
5
+
6
+ from auto_editor.json import dump
7
+ from auto_editor.timeline import v3
8
+
9
+ if TYPE_CHECKING:
10
+ from auto_editor.utils.log import Log
11
+
12
+
13
+ def make_json_timeline(ver: str, out: str, tl: v3, log: Log) -> None:
14
+ if ver not in {"v1", "v3"}:
15
+ log.error(f"Unknown timeline version: {ver}")
16
+
17
+ if out == "-":
18
+ outfile = sys.stdout
19
+ else:
20
+ outfile = open(out, "w")
21
+
22
+ if ver == "v3":
23
+ dump(tl.as_dict(), outfile, indent=2)
24
+ else:
25
+ if tl.v1 is None:
26
+ log.error("Timeline can't be converted to v1 format")
27
+ dump(tl.v1.as_dict(), outfile, indent=2)
28
+
29
+ if out == "-":
30
+ print("") # Flush stdout
31
+ else:
32
+ outfile.close()
@@ -1,17 +1,9 @@
1
- from __future__ import annotations
2
-
3
1
  import xml.etree.ElementTree as ET
4
- from typing import TYPE_CHECKING, Any, cast
2
+ from typing import cast
5
3
 
6
- from auto_editor.timeline import v3
4
+ from auto_editor.timeline import Clip, v3
7
5
  from auto_editor.utils.func import aspect_ratio, to_timecode
8
6
 
9
- if TYPE_CHECKING:
10
- from collections.abc import Sequence
11
-
12
- from auto_editor.timeline import TlAudio, TlVideo
13
- from auto_editor.utils.log import Log
14
-
15
7
  """
16
8
  Shotcut uses the MLT timeline format
17
9
 
@@ -21,10 +13,6 @@ https://mltframework.org/docs/mltxml/
21
13
  """
22
14
 
23
15
 
24
- def shotcut_read_mlt(path: str, log: Log) -> v3:
25
- raise NotImplementedError
26
-
27
-
28
16
  def shotcut_write_mlt(output: str, tl: v3) -> None:
29
17
  mlt = ET.Element(
30
18
  "mlt",
@@ -61,7 +49,7 @@ def shotcut_write_mlt(output: str, tl: v3) -> None:
61
49
  playlist_bin = ET.SubElement(mlt, "playlist", id="main_bin")
62
50
  ET.SubElement(playlist_bin, "property", name="xml_retain").text = "1"
63
51
 
64
- global_out = to_timecode(tl.out_len() / tb, "standard")
52
+ global_out = to_timecode(len(tl) / tb, "standard")
65
53
 
66
54
  producer = ET.SubElement(mlt, "producer", id="bg")
67
55
 
@@ -83,7 +71,7 @@ def shotcut_write_mlt(output: str, tl: v3) -> None:
83
71
  producers = 0
84
72
 
85
73
  if tl.v:
86
- clips: Sequence[TlVideo | TlAudio] = cast(Any, tl.v[0])
74
+ clips = cast(list[Clip], tl.v[0])
87
75
  elif tl.a:
88
76
  clips = tl.a[0]
89
77
  else:
@@ -154,4 +142,7 @@ def shotcut_write_mlt(output: str, tl: v3) -> None:
154
142
 
155
143
  ET.indent(tree, space="\t", level=0)
156
144
 
157
- tree.write(output, xml_declaration=True, encoding="utf-8")
145
+ if output == "-":
146
+ print(ET.tostring(mlt, encoding="unicode"))
147
+ else:
148
+ tree.write(output, xml_declaration=True, encoding="utf-8")
auto_editor/ffwrapper.py CHANGED
@@ -66,7 +66,6 @@ class FileInfo:
66
66
  path: Path
67
67
  bitrate: int
68
68
  duration: float
69
- description: str | None
70
69
  videos: tuple[VideoStream, ...]
71
70
  audios: tuple[AudioStream, ...]
72
71
  subtitles: tuple[SubtitleStream, ...]
@@ -166,13 +165,12 @@ class FileInfo:
166
165
  ext = sub_exts.get(codec, "vtt")
167
166
  subtitles += (SubtitleStream(codec, ext, s.language),)
168
167
 
169
- desc = cont.metadata.get("description", None)
170
168
  bitrate = 0 if cont.bit_rate is None else cont.bit_rate
171
169
  dur = 0 if cont.duration is None else cont.duration / bv.time_base
172
170
 
173
171
  cont.close()
174
172
 
175
- return FileInfo(Path(path), bitrate, dur, desc, videos, audios, subtitles)
173
+ return FileInfo(Path(path), bitrate, dur, videos, audios, subtitles)
176
174
 
177
175
  def __repr__(self) -> str:
178
176
  return f"@{self.path.name}"
auto_editor/help.py CHANGED
@@ -71,26 +71,15 @@ This option controls how timelines are exported.
71
71
 
72
72
  Export Methods:
73
73
  - default ; Export as a regular media file
74
-
75
74
  - premiere ; Export as an XML timeline file for Adobe Premiere Pro
76
- - name string? : "Auto-Editor Media Group"
77
-
75
+ - name : "Auto-Editor Media Group"
78
76
  - resolve ; Export as an XML timeline file for DaVinci Resolve
79
- - name string? : "Auto-Editor Media Group"
80
-
77
+ - name : "Auto-Editor Media Group"
81
78
  - final-cut-pro ; Export as an XML timeline file for Final Cut Pro
82
- - name string? : "Auto-Editor Media Group"
83
-
84
- - shotcut ; Export as an XML timeline file for Shotcut
85
-
86
- - json ; Export as an auto-editor JSON timeline file
87
- - api string? : "3"
88
-
89
- - timeline ; Print the auto-editor timeline to stdout
90
- - api string? : "3"
91
-
92
- - audio ; Export as a WAV audio file
93
-
79
+ - name : "Auto-Editor Media Group"
80
+ - shotcut ; Export as an XML timeline file for Shotcut
81
+ - v3 ; Export as an auto-editor v3 timeline file
82
+ - v1 ; Export as an auto-editor v1 timeline file
94
83
  - clip-sequence ; Export as multiple numbered media files
95
84
 
96
85
  """.strip(),
File without changes