auto-editor 28.1.0__py3-none-any.whl → 29.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 (60) hide show
  1. auto_editor/__init__.py +3 -1
  2. auto_editor/__main__.py +31 -497
  3. auto_editor/cli.py +12 -0
  4. {auto_editor-28.1.0.dist-info → auto_editor-29.0.1.dist-info}/METADATA +5 -6
  5. auto_editor-29.0.1.dist-info/RECORD +9 -0
  6. auto_editor-29.0.1.dist-info/entry_points.txt +2 -0
  7. {auto_editor-28.1.0.dist-info → auto_editor-29.0.1.dist-info}/top_level.txt +0 -1
  8. auto_editor/analyze.py +0 -393
  9. auto_editor/cmds/__init__.py +0 -0
  10. auto_editor/cmds/cache.py +0 -69
  11. auto_editor/cmds/desc.py +0 -32
  12. auto_editor/cmds/info.py +0 -213
  13. auto_editor/cmds/levels.py +0 -199
  14. auto_editor/cmds/palet.py +0 -29
  15. auto_editor/cmds/repl.py +0 -113
  16. auto_editor/cmds/subdump.py +0 -72
  17. auto_editor/cmds/test.py +0 -816
  18. auto_editor/edit.py +0 -560
  19. auto_editor/exports/__init__.py +0 -0
  20. auto_editor/exports/fcp11.py +0 -195
  21. auto_editor/exports/fcp7.py +0 -313
  22. auto_editor/exports/json.py +0 -63
  23. auto_editor/exports/kdenlive.py +0 -322
  24. auto_editor/exports/shotcut.py +0 -147
  25. auto_editor/ffwrapper.py +0 -187
  26. auto_editor/help.py +0 -224
  27. auto_editor/imports/__init__.py +0 -0
  28. auto_editor/imports/fcp7.py +0 -275
  29. auto_editor/imports/json.py +0 -234
  30. auto_editor/json.py +0 -297
  31. auto_editor/lang/__init__.py +0 -0
  32. auto_editor/lang/libintrospection.py +0 -10
  33. auto_editor/lang/libmath.py +0 -23
  34. auto_editor/lang/palet.py +0 -724
  35. auto_editor/lang/stdenv.py +0 -1179
  36. auto_editor/lib/__init__.py +0 -0
  37. auto_editor/lib/contracts.py +0 -235
  38. auto_editor/lib/data_structs.py +0 -278
  39. auto_editor/lib/err.py +0 -2
  40. auto_editor/make_layers.py +0 -315
  41. auto_editor/preview.py +0 -93
  42. auto_editor/render/__init__.py +0 -0
  43. auto_editor/render/audio.py +0 -517
  44. auto_editor/render/subtitle.py +0 -205
  45. auto_editor/render/video.py +0 -307
  46. auto_editor/timeline.py +0 -331
  47. auto_editor/utils/__init__.py +0 -0
  48. auto_editor/utils/bar.py +0 -142
  49. auto_editor/utils/chunks.py +0 -2
  50. auto_editor/utils/cmdkw.py +0 -206
  51. auto_editor/utils/container.py +0 -101
  52. auto_editor/utils/func.py +0 -128
  53. auto_editor/utils/log.py +0 -126
  54. auto_editor/utils/types.py +0 -277
  55. auto_editor/vanparse.py +0 -313
  56. auto_editor-28.1.0.dist-info/RECORD +0 -57
  57. auto_editor-28.1.0.dist-info/entry_points.txt +0 -6
  58. docs/build.py +0 -70
  59. {auto_editor-28.1.0.dist-info → auto_editor-29.0.1.dist-info}/WHEEL +0 -0
  60. {auto_editor-28.1.0.dist-info → auto_editor-29.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,322 +0,0 @@
1
- import json
2
- import xml.etree.ElementTree as ET
3
- from os import getcwd
4
- from uuid import uuid4
5
-
6
- from auto_editor.timeline import Clip, v3
7
- from auto_editor.utils.func import aspect_ratio, to_timecode
8
-
9
- """
10
- kdenlive uses the MLT timeline format
11
-
12
- See docs here:
13
- https://mltframework.org/docs/mltxml/
14
-
15
- kdenlive specifics:
16
- https://github.com/KDE/kdenlive/blob/master/dev-docs/fileformat.md
17
- """
18
-
19
-
20
- def kdenlive_write(output: str, tl: v3) -> None:
21
- mlt = ET.Element(
22
- "mlt",
23
- attrib={
24
- "LC_NUMERIC": "C",
25
- "version": "7.22.0",
26
- "producer": "main_bin",
27
- "root": f"{getcwd()}",
28
- },
29
- )
30
-
31
- width, height = tl.res
32
- num, den = aspect_ratio(width, height)
33
- tb = tl.tb
34
- seq_uuid = uuid4()
35
-
36
- ET.SubElement(
37
- mlt,
38
- "profile",
39
- attrib={
40
- "description": "automatic",
41
- "width": f"{width}",
42
- "height": f"{height}",
43
- "progressive": "1",
44
- "sample_aspect_num": "1",
45
- "sample_aspect_den": "1",
46
- "display_aspect_num": f"{num}",
47
- "display_aspect_den": f"{den}",
48
- "frame_rate_num": f"{tb.numerator}",
49
- "frame_rate_den": f"{tb.denominator}",
50
- "colorspace": "709",
51
- },
52
- )
53
-
54
- # Reserved producer0
55
- global_out = to_timecode(len(tl) / tb, "standard")
56
- producer = ET.SubElement(mlt, "producer", id="producer0")
57
- ET.SubElement(producer, "property", name="length").text = global_out
58
- ET.SubElement(producer, "property", name="eof").text = "continue"
59
- ET.SubElement(producer, "property", name="resource").text = "black"
60
- ET.SubElement(producer, "property", name="mlt_service").text = "color"
61
- ET.SubElement(producer, "property", name="kdenlive:playlistid").text = "black_track"
62
- ET.SubElement(producer, "property", name="mlt_image_format").text = "rgba"
63
- ET.SubElement(producer, "property", name="aspect_ratio").text = "1"
64
-
65
- # Get all clips
66
- if tl.v:
67
- clips = [clip for clip in tl.v[0] if isinstance(clip, Clip)]
68
- elif tl.a:
69
- clips = tl.a[0]
70
- else:
71
- clips = []
72
-
73
- source_ids = {}
74
- source_id = 4
75
- clip_playlists = []
76
- chains = 0
77
- playlists = 0
78
- producers = 1
79
- a_channels = len(tl.a)
80
- v_channels = len(tl.v)
81
- warped_clips = [i for i, clip in enumerate(clips) if clip.speed != 1]
82
-
83
- # create all producers for warped clips
84
- for clip_idx in warped_clips:
85
- for i in range(a_channels + v_channels):
86
- clip = clips[clip_idx]
87
- path = str(clip.src.path)
88
-
89
- if path not in source_ids:
90
- source_ids[path] = str(source_id)
91
- source_id += 1
92
-
93
- prod = ET.SubElement(
94
- mlt,
95
- "producer",
96
- attrib={
97
- "id": f"producer{producers}",
98
- "in": "00:00:00.000",
99
- "out": global_out,
100
- },
101
- )
102
- ET.SubElement(
103
- prod, "property", name="resource"
104
- ).text = f"{clip.speed}:{path}"
105
- ET.SubElement(prod, "property", name="warp_speed").text = str(clip.speed)
106
- ET.SubElement(prod, "property", name="warp_resource").text = path
107
- ET.SubElement(prod, "property", name="warp_pitch").text = "0"
108
- ET.SubElement(prod, "property", name="mlt_service").text = "timewarp"
109
- ET.SubElement(prod, "property", name="kdenlive:id").text = source_ids[path]
110
-
111
- if i < a_channels:
112
- ET.SubElement(prod, "property", name="vstream").text = "0"
113
- ET.SubElement(prod, "property", name="astream").text = str(
114
- a_channels - 1 - i
115
- )
116
- ET.SubElement(prod, "property", name="set.test_audio").text = "0"
117
- ET.SubElement(prod, "property", name="set.test_video").text = "1"
118
- else:
119
- ET.SubElement(prod, "property", name="vstream").text = str(
120
- v_channels - 1 - (i - a_channels)
121
- )
122
- ET.SubElement(prod, "property", name="astream").text = "0"
123
- ET.SubElement(prod, "property", name="set.test_audio").text = "1"
124
- ET.SubElement(prod, "property", name="set.test_video").text = "0"
125
-
126
- producers += 1
127
-
128
- # create chains, playlists and tractors for audio channels
129
- for i, audio in enumerate(tl.a):
130
- path = str(audio[0].src.path)
131
-
132
- if path not in source_ids:
133
- source_ids[path] = str(source_id)
134
- source_id += 1
135
-
136
- chain = ET.SubElement(mlt, "chain", attrib={"id": f"chain{chains}"})
137
- ET.SubElement(chain, "property", name="resource").text = path
138
- ET.SubElement(
139
- chain, "property", name="mlt_service"
140
- ).text = "avformat-novalidate"
141
- ET.SubElement(chain, "property", name="vstream").text = "0"
142
- ET.SubElement(chain, "property", name="astream").text = str(a_channels - 1 - i)
143
- ET.SubElement(chain, "property", name="set.test_audio").text = "0"
144
- ET.SubElement(chain, "property", name="set.test_video").text = "1"
145
- ET.SubElement(chain, "property", name="kdenlive:id").text = source_ids[path]
146
-
147
- for _i in range(2):
148
- playlist = ET.SubElement(mlt, "playlist", id=f"playlist{playlists}")
149
- clip_playlists.append(playlist)
150
- ET.SubElement(playlist, "property", name="kdenlive:audio_track").text = "1"
151
- playlists += 1
152
-
153
- tractor = ET.SubElement(
154
- mlt,
155
- "tractor",
156
- attrib={"id": f"tractor{chains}", "in": "00:00:00.000", "out": global_out},
157
- )
158
- ET.SubElement(tractor, "property", name="kdenlive:audio_track").text = "1"
159
- ET.SubElement(tractor, "property", name="kdenlive:timeline_active").text = "1"
160
- ET.SubElement(tractor, "property", name="kdenlive:audio_rec")
161
- ET.SubElement(
162
- tractor,
163
- "track",
164
- attrib={"hide": "video", "producer": f"playlist{playlists - 2}"},
165
- )
166
- ET.SubElement(
167
- tractor,
168
- "track",
169
- attrib={"hide": "video", "producer": f"playlist{playlists - 1}"},
170
- )
171
- chains += 1
172
-
173
- # create chains, playlists and tractors for video channels
174
- for i, video in enumerate(tl.v):
175
- path = f"{video[0].src.path}" # type: ignore
176
-
177
- if path not in source_ids:
178
- source_ids[path] = str(source_id)
179
- source_id += 1
180
-
181
- chain = ET.SubElement(mlt, "chain", attrib={"id": f"chain{chains}"})
182
- ET.SubElement(chain, "property", name="resource").text = path
183
- ET.SubElement(
184
- chain, "property", name="mlt_service"
185
- ).text = "avformat-novalidate"
186
- ET.SubElement(chain, "property", name="vstream").text = str(v_channels - 1 - i)
187
- ET.SubElement(chain, "property", name="astream").text = "0"
188
- ET.SubElement(chain, "property", name="set.test_audio").text = "1"
189
- ET.SubElement(chain, "property", name="set.test_video").text = "0"
190
- ET.SubElement(chain, "property", name="kdenlive:id").text = source_ids[path]
191
-
192
- for _i in range(2):
193
- playlist = ET.SubElement(mlt, "playlist", id=f"playlist{playlists}")
194
- clip_playlists.append(playlist)
195
- playlists += 1
196
-
197
- tractor = ET.SubElement(
198
- mlt,
199
- "tractor",
200
- attrib={"id": f"tractor{chains}", "in": "00:00:00.000", "out": global_out},
201
- )
202
- ET.SubElement(tractor, "property", name="kdenlive:timeline_active").text = "1"
203
- ET.SubElement(
204
- tractor,
205
- "track",
206
- attrib={"hide": "audio", "producer": f"playlist{playlists - 2}"},
207
- )
208
- ET.SubElement(
209
- tractor,
210
- "track",
211
- attrib={"hide": "audio", "producer": f"playlist{playlists - 1}"},
212
- )
213
- chains += 1
214
-
215
- # final chain for the project bin
216
- path = str(clips[0].src.path)
217
- chain = ET.SubElement(mlt, "chain", attrib={"id": f"chain{chains}"})
218
- ET.SubElement(chain, "property", name="resource").text = path
219
- ET.SubElement(chain, "property", name="mlt_service").text = "avformat-novalidate"
220
- ET.SubElement(chain, "property", name="audio_index").text = "1"
221
- ET.SubElement(chain, "property", name="video_index").text = "0"
222
- ET.SubElement(chain, "property", name="vstream").text = "0"
223
- ET.SubElement(chain, "property", name="astream").text = "0"
224
- ET.SubElement(chain, "property", name="kdenlive:id").text = source_ids[path]
225
-
226
- groups = []
227
- group_counter = 0
228
- producers = 1
229
-
230
- for clip in clips:
231
- group_children: list[object] = []
232
- _in = to_timecode(clip.offset / tb, "standard")
233
- _out = to_timecode((clip.offset + clip.dur) / tb, "standard")
234
- path = str(clip.src.path)
235
-
236
- for i, playlist in enumerate(clip_playlists[::2]):
237
- # adding 1 extra frame for each previous group to the start time works but feels hacky?
238
- group_children.append(
239
- {
240
- "data": f"{i}:{clip.start + group_counter}",
241
- "leaf": "clip",
242
- "type": "Leaf",
243
- }
244
- )
245
- clip_prod = ""
246
-
247
- if clip.speed == 1:
248
- clip_prod = f"chain{i}"
249
- else:
250
- clip_prod = f"producer{producers}"
251
- producers += 1
252
-
253
- entry = ET.SubElement(
254
- playlist,
255
- "entry",
256
- attrib={"producer": f"{clip_prod}", "in": _in, "out": _out},
257
- )
258
- ET.SubElement(entry, "property", name="kdenlive:id").text = source_ids[path]
259
-
260
- groups.append({"children": group_children[:], "type": "Normal"})
261
- group_counter += 1
262
-
263
- # default sequence tractor
264
- sequence = ET.SubElement(
265
- mlt,
266
- "tractor",
267
- attrib={"id": f"{{{seq_uuid}}}", "in": "00:00:00.000", "out": "00:00:00.000"},
268
- )
269
- ET.SubElement(sequence, "property", name="kdenlive:uuid").text = f"{{{seq_uuid}}}"
270
- ET.SubElement(sequence, "property", name="kdenlive:clipname").text = "Sequence 1"
271
- ET.SubElement(
272
- sequence, "property", name="kdenlive:sequenceproperties.groups"
273
- ).text = json.dumps(groups, indent=4)
274
- ET.SubElement(sequence, "track", producer="producer0")
275
-
276
- for i in range(chains):
277
- ET.SubElement(sequence, "track", producer=f"tractor{i}")
278
-
279
- # main bin
280
- playlist_bin = ET.SubElement(mlt, "playlist", id="main_bin")
281
- ET.SubElement(
282
- playlist_bin, "property", name="kdenlive:docproperties.uuid"
283
- ).text = f"{{{seq_uuid}}}"
284
- ET.SubElement(
285
- playlist_bin, "property", name="kdenlive:docproperties.version"
286
- ).text = "1.1"
287
- ET.SubElement(playlist_bin, "property", name="xml_retain").text = "1"
288
- ET.SubElement(
289
- playlist_bin,
290
- "entry",
291
- attrib={
292
- "producer": f"{{{seq_uuid}}}",
293
- "in": "00:00:00.000",
294
- "out": "00:00:00.000",
295
- },
296
- )
297
- ET.SubElement(
298
- playlist_bin,
299
- "entry",
300
- attrib={"producer": f"chain{chains}", "in": "00:00:00.000"},
301
- )
302
-
303
- # reserved last tractor for project
304
- tractor = ET.SubElement(
305
- mlt,
306
- "tractor",
307
- attrib={"id": f"tractor{chains}", "in": "00:00:00.000", "out": global_out},
308
- )
309
- ET.SubElement(tractor, "property", name="kdenlive:projectTractor").text = "1"
310
- ET.SubElement(
311
- tractor,
312
- "track",
313
- attrib={"producer": f"{{{seq_uuid}}}", "in": "00:00:00.000", "out": global_out},
314
- )
315
- tree = ET.ElementTree(mlt)
316
-
317
- ET.indent(tree, space="\t", level=0)
318
-
319
- if output == "-":
320
- print(ET.tostring(mlt, encoding="unicode"))
321
- else:
322
- tree.write(output, xml_declaration=True, encoding="utf-8")
@@ -1,147 +0,0 @@
1
- import xml.etree.ElementTree as ET
2
-
3
- from auto_editor.timeline import Clip, v3
4
- from auto_editor.utils.func import aspect_ratio, to_timecode
5
-
6
- """
7
- Shotcut uses the MLT timeline format
8
-
9
- See docs here:
10
- https://mltframework.org/docs/mltxml/
11
-
12
- """
13
-
14
-
15
- def shotcut_write_mlt(output: str, tl: v3) -> None:
16
- mlt = ET.Element(
17
- "mlt",
18
- attrib={
19
- "LC_NUMERIC": "C",
20
- "version": "7.9.0",
21
- "title": "Shotcut version 22.09.23",
22
- "producer": "main_bin",
23
- },
24
- )
25
-
26
- width, height = tl.res
27
- num, den = aspect_ratio(width, height)
28
- tb = tl.tb
29
-
30
- ET.SubElement(
31
- mlt,
32
- "profile",
33
- attrib={
34
- "description": "automatic",
35
- "width": f"{width}",
36
- "height": f"{height}",
37
- "progressive": "1",
38
- "sample_aspect_num": "1",
39
- "sample_aspect_den": "1",
40
- "display_aspect_num": f"{num}",
41
- "display_aspect_den": f"{den}",
42
- "frame_rate_num": f"{tb.numerator}",
43
- "frame_rate_den": f"{tb.denominator}",
44
- "colorspace": "709",
45
- },
46
- )
47
-
48
- playlist_bin = ET.SubElement(mlt, "playlist", id="main_bin")
49
- ET.SubElement(playlist_bin, "property", name="xml_retain").text = "1"
50
-
51
- global_out = to_timecode(len(tl) / tb, "standard")
52
-
53
- producer = ET.SubElement(mlt, "producer", id="bg")
54
-
55
- ET.SubElement(producer, "property", name="length").text = global_out
56
- ET.SubElement(producer, "property", name="eof").text = "pause"
57
- ET.SubElement(producer, "property", name="resource").text = "#000" # background
58
- ET.SubElement(producer, "property", name="mlt_service").text = "color"
59
- ET.SubElement(producer, "property", name="mlt_image_format").text = "rgba"
60
- ET.SubElement(producer, "property", name="aspect_ratio").text = "1"
61
-
62
- playlist = ET.SubElement(mlt, "playlist", id="background")
63
- ET.SubElement(
64
- playlist,
65
- "entry",
66
- attrib={"producer": "bg", "in": "00:00:00.000", "out": global_out},
67
- ).text = "1"
68
-
69
- chains = 0
70
- producers = 0
71
-
72
- if tl.v:
73
- clips = [clip for clip in tl.v[0] if isinstance(clip, Clip)]
74
- elif tl.a:
75
- clips = tl.a[0]
76
- else:
77
- clips = []
78
-
79
- for clip in clips:
80
- src = clip.src
81
- length = to_timecode((clip.offset + clip.dur) / tb, "standard")
82
-
83
- if clip.speed == 1:
84
- resource = f"{src.path}"
85
- caption = f"{src.path.stem}"
86
- chain = ET.SubElement(
87
- mlt, "chain", attrib={"id": f"chain{chains}", "out": length}
88
- )
89
- else:
90
- chain = ET.SubElement(
91
- mlt, "producer", attrib={"id": f"producer{producers}", "out": length}
92
- )
93
- resource = f"{clip.speed}:{src.path}"
94
- caption = f"{src.path.stem} ({clip.speed}x)"
95
-
96
- producers += 1
97
-
98
- ET.SubElement(chain, "property", name="length").text = length
99
- ET.SubElement(chain, "property", name="resource").text = resource
100
-
101
- if clip.speed != 1:
102
- ET.SubElement(chain, "property", name="warp_speed").text = f"{clip.speed}"
103
- ET.SubElement(chain, "property", name="warp_pitch").text = "1"
104
- ET.SubElement(chain, "property", name="mlt_service").text = "timewarp"
105
-
106
- ET.SubElement(chain, "property", name="caption").text = caption
107
-
108
- chains += 1
109
-
110
- main_playlist = ET.SubElement(mlt, "playlist", id="playlist0")
111
- ET.SubElement(main_playlist, "property", name="shotcut:video").text = "1"
112
- ET.SubElement(main_playlist, "property", name="shotcut:name").text = "V1"
113
-
114
- producers = 0
115
- for i, clip in enumerate(clips):
116
- _in = to_timecode(clip.offset / tb, "standard")
117
- _out = to_timecode((clip.offset + clip.dur) / tb, "standard")
118
-
119
- tag_name = f"chain{i}"
120
- if clip.speed != 1:
121
- tag_name = f"producer{producers}"
122
- producers += 1
123
-
124
- ET.SubElement(
125
- main_playlist,
126
- "entry",
127
- attrib={"producer": tag_name, "in": _in, "out": _out},
128
- )
129
-
130
- tractor = ET.SubElement(
131
- mlt,
132
- "tractor",
133
- attrib={"id": "tractor0", "in": "00:00:00.000", "out": global_out},
134
- )
135
- ET.SubElement(tractor, "property", name="shotcut").text = "1"
136
- ET.SubElement(tractor, "property", name="shotcut:projectAudioChannels").text = "2"
137
- ET.SubElement(tractor, "track", producer="background")
138
- ET.SubElement(tractor, "track", producer="playlist0")
139
-
140
- tree = ET.ElementTree(mlt)
141
-
142
- ET.indent(tree, space="\t", level=0)
143
-
144
- if output == "-":
145
- print(ET.tostring(mlt, encoding="unicode"))
146
- else:
147
- tree.write(output, xml_declaration=True, encoding="utf-8")
auto_editor/ffwrapper.py DELETED
@@ -1,187 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from fractions import Fraction
5
- from pathlib import Path
6
-
7
- import av
8
-
9
- from auto_editor.utils.log import Log
10
-
11
-
12
- def mux(input: Path, output: Path, stream: int) -> None:
13
- input_container = av.open(input, "r")
14
- output_container = av.open(output, "w")
15
-
16
- input_audio_stream = input_container.streams.audio[stream]
17
- output_audio_stream = output_container.add_stream("pcm_s16le")
18
-
19
- for frame in input_container.decode(input_audio_stream):
20
- output_container.mux(output_audio_stream.encode(frame))
21
-
22
- output_container.mux(output_audio_stream.encode(None))
23
-
24
- output_container.close()
25
- input_container.close()
26
-
27
-
28
- @dataclass(slots=True, frozen=True)
29
- class VideoStream:
30
- width: int
31
- height: int
32
- codec: str
33
- fps: Fraction
34
- duration: float
35
- sar: Fraction
36
- time_base: Fraction | None
37
- pix_fmt: str | None
38
- color_range: int
39
- color_space: int
40
- color_primaries: int
41
- color_transfer: int
42
- bitrate: int
43
- lang: str | None
44
-
45
-
46
- @dataclass(slots=True, frozen=True)
47
- class AudioStream:
48
- codec: str
49
- samplerate: int
50
- layout: str
51
- channels: int
52
- duration: float
53
- bitrate: int
54
- lang: str | None
55
-
56
-
57
- @dataclass(slots=True, frozen=True)
58
- class SubtitleStream:
59
- codec: str
60
- ext: str
61
- lang: str | None
62
-
63
-
64
- @dataclass(slots=True, frozen=True)
65
- class FileInfo:
66
- path: Path
67
- bitrate: int
68
- duration: float
69
- timecode: str # in SMPTE
70
- videos: tuple[VideoStream, ...]
71
- audios: tuple[AudioStream, ...]
72
- subtitles: tuple[SubtitleStream, ...]
73
-
74
- def get_res(self) -> tuple[int, int]:
75
- if self.videos:
76
- return self.videos[0].width, self.videos[0].height
77
- return 1920, 1080
78
-
79
- def get_fps(self) -> Fraction:
80
- if self.videos:
81
- return self.videos[0].fps
82
- return Fraction(30)
83
-
84
- def get_sr(self) -> int:
85
- if self.audios:
86
- return self.audios[0].samplerate
87
- return 48000
88
-
89
- @classmethod
90
- def init(self, path: str, log: Log) -> FileInfo:
91
- try:
92
- cont = av.open(path, "r")
93
- except av.error.FileNotFoundError:
94
- log.error(f"Input file doesn't exist: {path}")
95
- except av.error.IsADirectoryError:
96
- log.error(f"Expected a media file, but got a directory: {path}")
97
- except av.error.InvalidDataError:
98
- log.error(f"Invalid data when processing: {path}")
99
-
100
- videos: tuple[VideoStream, ...] = ()
101
- audios: tuple[AudioStream, ...] = ()
102
- subtitles: tuple[SubtitleStream, ...] = ()
103
-
104
- for v in cont.streams.video:
105
- if v.duration is not None and v.time_base is not None:
106
- vdur = float(v.duration * v.time_base)
107
- else:
108
- vdur = 0.0
109
-
110
- fps = v.average_rate
111
- if (fps is None or fps < 1) and v.name in {"png", "mjpeg", "webp"}:
112
- fps = Fraction(25)
113
- if fps is None or fps == 0:
114
- fps = Fraction(30)
115
-
116
- if v.sample_aspect_ratio is None:
117
- sar = Fraction(1)
118
- else:
119
- sar = v.sample_aspect_ratio
120
-
121
- cc = v.codec_context
122
-
123
- if v.name is None:
124
- log.error(f"Can't detect codec for video stream {v}")
125
-
126
- videos += (
127
- VideoStream(
128
- v.width,
129
- v.height,
130
- v.codec.canonical_name,
131
- fps,
132
- vdur,
133
- sar,
134
- v.time_base,
135
- getattr(v.format, "name", None),
136
- cc.color_range,
137
- cc.colorspace,
138
- cc.color_primaries,
139
- cc.color_trc,
140
- 0 if v.bit_rate is None else v.bit_rate,
141
- v.language,
142
- ),
143
- )
144
-
145
- for a in cont.streams.audio:
146
- adur = 0.0
147
- if a.duration is not None and a.time_base is not None:
148
- adur = float(a.duration * a.time_base)
149
-
150
- a_cc = a.codec_context
151
- audios += (
152
- AudioStream(
153
- a_cc.codec.canonical_name,
154
- 0 if a_cc.sample_rate is None else a_cc.sample_rate,
155
- a.layout.name,
156
- a_cc.channels,
157
- adur,
158
- 0 if a_cc.bit_rate is None else a_cc.bit_rate,
159
- a.language,
160
- ),
161
- )
162
-
163
- for s in cont.streams.subtitles:
164
- codec = s.codec_context.name
165
- sub_exts = {"mov_text": "srt", "ass": "ass", "webvtt": "vtt"}
166
- ext = sub_exts.get(codec, "vtt")
167
- subtitles += (SubtitleStream(codec, ext, s.language),)
168
-
169
- def get_timecode() -> str:
170
- for d in cont.streams.data:
171
- if (result := d.metadata.get("timecode")) is not None:
172
- return result
173
- for v in cont.streams.video:
174
- if (result := v.metadata.get("timecode")) is not None:
175
- return result
176
- return "00:00:00:00"
177
-
178
- timecode = get_timecode()
179
- bitrate = 0 if cont.bit_rate is None else cont.bit_rate
180
- dur = 0 if cont.duration is None else cont.duration / av.time_base
181
-
182
- cont.close()
183
-
184
- return FileInfo(Path(path), bitrate, dur, timecode, videos, audios, subtitles)
185
-
186
- def __repr__(self) -> str:
187
- return f"@{self.path.name}"