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.
- auto_editor/__init__.py +3 -1
- auto_editor/__main__.py +31 -497
- auto_editor/cli.py +12 -0
- {auto_editor-28.1.0.dist-info → auto_editor-29.0.1.dist-info}/METADATA +5 -6
- auto_editor-29.0.1.dist-info/RECORD +9 -0
- auto_editor-29.0.1.dist-info/entry_points.txt +2 -0
- {auto_editor-28.1.0.dist-info → auto_editor-29.0.1.dist-info}/top_level.txt +0 -1
- auto_editor/analyze.py +0 -393
- auto_editor/cmds/__init__.py +0 -0
- auto_editor/cmds/cache.py +0 -69
- auto_editor/cmds/desc.py +0 -32
- auto_editor/cmds/info.py +0 -213
- auto_editor/cmds/levels.py +0 -199
- auto_editor/cmds/palet.py +0 -29
- auto_editor/cmds/repl.py +0 -113
- auto_editor/cmds/subdump.py +0 -72
- auto_editor/cmds/test.py +0 -816
- auto_editor/edit.py +0 -560
- auto_editor/exports/__init__.py +0 -0
- auto_editor/exports/fcp11.py +0 -195
- auto_editor/exports/fcp7.py +0 -313
- auto_editor/exports/json.py +0 -63
- auto_editor/exports/kdenlive.py +0 -322
- auto_editor/exports/shotcut.py +0 -147
- auto_editor/ffwrapper.py +0 -187
- auto_editor/help.py +0 -224
- auto_editor/imports/__init__.py +0 -0
- auto_editor/imports/fcp7.py +0 -275
- auto_editor/imports/json.py +0 -234
- auto_editor/json.py +0 -297
- auto_editor/lang/__init__.py +0 -0
- auto_editor/lang/libintrospection.py +0 -10
- auto_editor/lang/libmath.py +0 -23
- auto_editor/lang/palet.py +0 -724
- auto_editor/lang/stdenv.py +0 -1179
- auto_editor/lib/__init__.py +0 -0
- auto_editor/lib/contracts.py +0 -235
- auto_editor/lib/data_structs.py +0 -278
- auto_editor/lib/err.py +0 -2
- auto_editor/make_layers.py +0 -315
- auto_editor/preview.py +0 -93
- auto_editor/render/__init__.py +0 -0
- auto_editor/render/audio.py +0 -517
- auto_editor/render/subtitle.py +0 -205
- auto_editor/render/video.py +0 -307
- auto_editor/timeline.py +0 -331
- auto_editor/utils/__init__.py +0 -0
- auto_editor/utils/bar.py +0 -142
- auto_editor/utils/chunks.py +0 -2
- auto_editor/utils/cmdkw.py +0 -206
- auto_editor/utils/container.py +0 -101
- auto_editor/utils/func.py +0 -128
- auto_editor/utils/log.py +0 -126
- auto_editor/utils/types.py +0 -277
- auto_editor/vanparse.py +0 -313
- auto_editor-28.1.0.dist-info/RECORD +0 -57
- auto_editor-28.1.0.dist-info/entry_points.txt +0 -6
- docs/build.py +0 -70
- {auto_editor-28.1.0.dist-info → auto_editor-29.0.1.dist-info}/WHEEL +0 -0
- {auto_editor-28.1.0.dist-info → auto_editor-29.0.1.dist-info}/licenses/LICENSE +0 -0
auto_editor/exports/fcp11.py
DELETED
@@ -1,195 +0,0 @@
|
|
1
|
-
import xml.etree.ElementTree as ET
|
2
|
-
from fractions import Fraction
|
3
|
-
from xml.etree.ElementTree import Element, ElementTree, SubElement, indent
|
4
|
-
|
5
|
-
from auto_editor.ffwrapper import FileInfo
|
6
|
-
from auto_editor.timeline import Clip, v3
|
7
|
-
from auto_editor.utils.log import Log
|
8
|
-
|
9
|
-
"""
|
10
|
-
Export a FCPXML 11 file readable with Final Cut Pro 10.6.8 or later.
|
11
|
-
|
12
|
-
See docs here:
|
13
|
-
https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference
|
14
|
-
|
15
|
-
"""
|
16
|
-
|
17
|
-
|
18
|
-
def get_colorspace(src: FileInfo) -> str:
|
19
|
-
# See: https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference/asset#3686496
|
20
|
-
|
21
|
-
if not src.videos:
|
22
|
-
return "1-1-1 (Rec. 709)"
|
23
|
-
|
24
|
-
s = src.videos[0]
|
25
|
-
if s.pix_fmt == "rgb24":
|
26
|
-
return "sRGB IEC61966-2.1"
|
27
|
-
if s.color_space == 5: # "bt470bg"
|
28
|
-
return "5-1-6 (Rec. 601 PAL)"
|
29
|
-
if s.color_space == 6: # "smpte170m"
|
30
|
-
return "6-1-6 (Rec. 601 NTSC)"
|
31
|
-
if s.color_primaries == 9: # "bt2020"
|
32
|
-
# See: https://video.stackexchange.com/questions/22059/how-to-identify-hdr-video
|
33
|
-
if s.color_transfer in {16, 18}: # "smpte2084" "arib-std-b67"
|
34
|
-
return "9-18-9 (Rec. 2020 HLG)"
|
35
|
-
return "9-1-9 (Rec. 2020)"
|
36
|
-
|
37
|
-
return "1-1-1 (Rec. 709)"
|
38
|
-
|
39
|
-
|
40
|
-
def make_name(src: FileInfo, tb: Fraction) -> str:
|
41
|
-
if src.get_res()[1] == 720 and tb == 30:
|
42
|
-
return "FFVideoFormat720p30"
|
43
|
-
if src.get_res()[1] == 720 and tb == 25:
|
44
|
-
return "FFVideoFormat720p25"
|
45
|
-
return "FFVideoFormatRateUndefined"
|
46
|
-
|
47
|
-
|
48
|
-
def parseSMPTE(val: str, fps: Fraction, log: Log) -> int:
|
49
|
-
if len(val) == 0:
|
50
|
-
return 0
|
51
|
-
try:
|
52
|
-
parts = val.split(":")
|
53
|
-
if len(parts) != 4:
|
54
|
-
raise ValueError(f"Invalid SMPTE format: {val}")
|
55
|
-
|
56
|
-
hours, minutes, seconds, frames = map(int, parts)
|
57
|
-
|
58
|
-
if (
|
59
|
-
hours < 0
|
60
|
-
or minutes < 0
|
61
|
-
or minutes >= 60
|
62
|
-
or seconds < 0
|
63
|
-
or seconds >= 60
|
64
|
-
or frames < 0
|
65
|
-
):
|
66
|
-
raise ValueError(f"Invalid SMPTE values: {val}")
|
67
|
-
|
68
|
-
if frames >= fps:
|
69
|
-
raise ValueError(f"Frame count {frames} exceeds fps {fps}")
|
70
|
-
|
71
|
-
total_frames = (hours * 3600 + minutes * 60 + seconds) * fps + frames
|
72
|
-
return int(round(total_frames))
|
73
|
-
except (ValueError, ZeroDivisionError) as e:
|
74
|
-
log.error(f"Cannot parse SMPTE timecode '{val}': {e}")
|
75
|
-
|
76
|
-
|
77
|
-
def fcp11_write_xml(
|
78
|
-
group_name: str, version: int, output: str, resolve: bool, tl: v3, log: Log
|
79
|
-
) -> None:
|
80
|
-
def fraction(val: int) -> str:
|
81
|
-
if val == 0:
|
82
|
-
return "0s"
|
83
|
-
return f"{val * tl.tb.denominator}/{tl.tb.numerator}s"
|
84
|
-
|
85
|
-
if version == 11:
|
86
|
-
ver_str = "1.11"
|
87
|
-
elif version == 10:
|
88
|
-
ver_str = "1.10"
|
89
|
-
else:
|
90
|
-
log.error(f"Unknown final cut pro version: {version}")
|
91
|
-
|
92
|
-
fcpxml = Element("fcpxml", version=ver_str)
|
93
|
-
resources = SubElement(fcpxml, "resources")
|
94
|
-
|
95
|
-
src_dur = 0
|
96
|
-
tl_dur = 0 if resolve else len(tl)
|
97
|
-
|
98
|
-
for i, one_src in enumerate(tl.unique_sources()):
|
99
|
-
if i == 0:
|
100
|
-
proj_name = one_src.path.stem
|
101
|
-
src_dur = int(one_src.duration * tl.tb)
|
102
|
-
if resolve:
|
103
|
-
tl_dur = src_dur
|
104
|
-
|
105
|
-
SubElement(
|
106
|
-
resources,
|
107
|
-
"format",
|
108
|
-
id=f"r{i * 2 + 1}",
|
109
|
-
name=make_name(one_src, tl.tb),
|
110
|
-
frameDuration=fraction(1),
|
111
|
-
width=f"{tl.res[0]}",
|
112
|
-
height=f"{tl.res[1]}",
|
113
|
-
colorSpace=get_colorspace(one_src),
|
114
|
-
)
|
115
|
-
|
116
|
-
startPoint = parseSMPTE(one_src.timecode, tl.tb, log)
|
117
|
-
r2 = SubElement(
|
118
|
-
resources,
|
119
|
-
"asset",
|
120
|
-
id=f"r{i * 2 + 2}",
|
121
|
-
name=one_src.path.stem,
|
122
|
-
start=fraction(startPoint),
|
123
|
-
hasVideo="1" if one_src.videos else "0",
|
124
|
-
format=f"r{i * 2 + 1}",
|
125
|
-
hasAudio="1" if one_src.audios else "0",
|
126
|
-
audioSources="1",
|
127
|
-
audioChannels=f"{2 if not one_src.audios else one_src.audios[0].channels}",
|
128
|
-
duration=fraction(tl_dur),
|
129
|
-
)
|
130
|
-
SubElement(
|
131
|
-
r2, "media-rep", kind="original-media", src=one_src.path.resolve().as_uri()
|
132
|
-
)
|
133
|
-
|
134
|
-
lib = SubElement(fcpxml, "library")
|
135
|
-
evt = SubElement(lib, "event", name=group_name)
|
136
|
-
proj = SubElement(evt, "project", name=proj_name)
|
137
|
-
sequence = SubElement(
|
138
|
-
proj,
|
139
|
-
"sequence",
|
140
|
-
format="r1",
|
141
|
-
tcStart="0s",
|
142
|
-
tcFormat="NDF",
|
143
|
-
audioLayout=tl.T.layout,
|
144
|
-
audioRate="44.1k" if tl.sr == 44100 else "48k",
|
145
|
-
)
|
146
|
-
spine = SubElement(sequence, "spine")
|
147
|
-
|
148
|
-
def make_clip(ref: str, clip: Clip) -> None:
|
149
|
-
startPoint = parseSMPTE(clip.src.timecode, tl.tb, log)
|
150
|
-
|
151
|
-
clip_properties = {
|
152
|
-
"name": proj_name,
|
153
|
-
"ref": ref,
|
154
|
-
"offset": fraction(clip.start + startPoint),
|
155
|
-
"duration": fraction(clip.dur),
|
156
|
-
"start": fraction(clip.offset + startPoint),
|
157
|
-
"tcFormat": "NDF",
|
158
|
-
}
|
159
|
-
asset = SubElement(spine, "asset-clip", clip_properties)
|
160
|
-
if clip.speed != 1:
|
161
|
-
# See the "Time Maps" section.
|
162
|
-
# https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference/story_elements/timemap/
|
163
|
-
|
164
|
-
timemap = SubElement(asset, "timeMap")
|
165
|
-
SubElement(timemap, "timept", time="0s", value="0s", interp="smooth2")
|
166
|
-
SubElement(
|
167
|
-
timemap,
|
168
|
-
"timept",
|
169
|
-
time=fraction(int(src_dur // clip.speed)),
|
170
|
-
value=fraction(src_dur),
|
171
|
-
interp="smooth2",
|
172
|
-
)
|
173
|
-
|
174
|
-
if tl.v and tl.v[0]:
|
175
|
-
clips = [clip for clip in tl.v[0] if isinstance(clip, Clip)]
|
176
|
-
elif tl.a and tl.a[0]:
|
177
|
-
clips = tl.a[0]
|
178
|
-
else:
|
179
|
-
clips = []
|
180
|
-
|
181
|
-
all_refs: list[str] = ["r2"]
|
182
|
-
if resolve:
|
183
|
-
for i in range(1, len(tl.a)):
|
184
|
-
all_refs.append(f"r{(i + 1) * 2}")
|
185
|
-
|
186
|
-
for my_ref in reversed(all_refs):
|
187
|
-
for clip in clips:
|
188
|
-
make_clip(my_ref, clip)
|
189
|
-
|
190
|
-
tree = ElementTree(fcpxml)
|
191
|
-
indent(tree, space="\t", level=0)
|
192
|
-
if output == "-":
|
193
|
-
print(ET.tostring(fcpxml, encoding="unicode"))
|
194
|
-
else:
|
195
|
-
tree.write(output, xml_declaration=True, encoding="utf-8")
|
auto_editor/exports/fcp7.py
DELETED
@@ -1,313 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import xml.etree.ElementTree as ET
|
4
|
-
from fractions import Fraction
|
5
|
-
from math import ceil
|
6
|
-
from xml.etree.ElementTree import Element
|
7
|
-
|
8
|
-
from auto_editor.ffwrapper import FileInfo
|
9
|
-
from auto_editor.timeline import Clip, v3
|
10
|
-
|
11
|
-
"""
|
12
|
-
Premiere Pro uses the Final Cut Pro 7 XML Interchange Format
|
13
|
-
|
14
|
-
See docs here:
|
15
|
-
https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/FinalCutPro_XML/Elements/Elements.html
|
16
|
-
|
17
|
-
Also, Premiere itself will happily output subtlety incorrect XML files that don't
|
18
|
-
come back the way they started.
|
19
|
-
"""
|
20
|
-
|
21
|
-
DEPTH = "16"
|
22
|
-
|
23
|
-
|
24
|
-
def set_tb_ntsc(tb: Fraction) -> tuple[int, str]:
|
25
|
-
# See chart: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/FinalCutPro_XML/FrameRate/FrameRate.html#//apple_ref/doc/uid/TP30001158-TPXREF103
|
26
|
-
if tb == Fraction(24000, 1001):
|
27
|
-
return 24, "TRUE"
|
28
|
-
if tb == Fraction(30000, 1001):
|
29
|
-
return 30, "TRUE"
|
30
|
-
if tb == Fraction(60000, 1001):
|
31
|
-
return 60, "TRUE"
|
32
|
-
|
33
|
-
ctb = ceil(tb)
|
34
|
-
if ctb not in {24, 30, 60} and ctb * Fraction(999, 1000) == tb:
|
35
|
-
return ctb, "TRUE"
|
36
|
-
|
37
|
-
return int(tb), "FALSE"
|
38
|
-
|
39
|
-
|
40
|
-
def speedup(speed: float) -> Element:
|
41
|
-
fil = Element("filter")
|
42
|
-
effect = ET.SubElement(fil, "effect")
|
43
|
-
ET.SubElement(effect, "name").text = "Time Remap"
|
44
|
-
ET.SubElement(effect, "effectid").text = "timeremap"
|
45
|
-
|
46
|
-
para = ET.SubElement(effect, "parameter", authoringApp="PremierePro")
|
47
|
-
ET.SubElement(para, "parameterid").text = "variablespeed"
|
48
|
-
ET.SubElement(para, "name").text = "variablespeed"
|
49
|
-
ET.SubElement(para, "valuemin").text = "0"
|
50
|
-
ET.SubElement(para, "valuemax").text = "1"
|
51
|
-
ET.SubElement(para, "value").text = "0"
|
52
|
-
|
53
|
-
para2 = ET.SubElement(effect, "parameter", authoringApp="PremierePro")
|
54
|
-
ET.SubElement(para2, "parameterid").text = "speed"
|
55
|
-
ET.SubElement(para2, "name").text = "speed"
|
56
|
-
ET.SubElement(para2, "valuemin").text = "-100000"
|
57
|
-
ET.SubElement(para2, "valuemax").text = "100000"
|
58
|
-
ET.SubElement(para2, "value").text = str(speed)
|
59
|
-
|
60
|
-
para3 = ET.SubElement(effect, "parameter", authoringApp="PremierePro")
|
61
|
-
ET.SubElement(para3, "parameterid").text = "frameblending"
|
62
|
-
ET.SubElement(para3, "name").text = "frameblending"
|
63
|
-
ET.SubElement(para3, "value").text = "FALSE"
|
64
|
-
|
65
|
-
return fil
|
66
|
-
|
67
|
-
|
68
|
-
def media_def(
|
69
|
-
filedef: Element, url: str, src: FileInfo, tl: v3, tb: int, ntsc: str
|
70
|
-
) -> None:
|
71
|
-
ET.SubElement(filedef, "name").text = src.path.stem
|
72
|
-
ET.SubElement(filedef, "pathurl").text = url
|
73
|
-
|
74
|
-
timecode = ET.SubElement(filedef, "timecode")
|
75
|
-
ET.SubElement(timecode, "string").text = "00:00:00:00"
|
76
|
-
ET.SubElement(timecode, "displayformat").text = "NDF"
|
77
|
-
rate = ET.SubElement(timecode, "rate")
|
78
|
-
ET.SubElement(rate, "timebase").text = f"{tb}"
|
79
|
-
ET.SubElement(rate, "ntsc").text = ntsc
|
80
|
-
|
81
|
-
rate = ET.SubElement(filedef, "rate")
|
82
|
-
ET.SubElement(rate, "timebase").text = f"{tb}"
|
83
|
-
ET.SubElement(rate, "ntsc").text = ntsc
|
84
|
-
|
85
|
-
# DaVinci Resolve needs this tag even though it's blank
|
86
|
-
ET.SubElement(filedef, "duration").text = ""
|
87
|
-
|
88
|
-
mediadef = ET.SubElement(filedef, "media")
|
89
|
-
|
90
|
-
if len(src.videos) > 0:
|
91
|
-
videodef = ET.SubElement(mediadef, "video")
|
92
|
-
|
93
|
-
vschar = ET.SubElement(videodef, "samplecharacteristics")
|
94
|
-
rate = ET.SubElement(vschar, "rate")
|
95
|
-
ET.SubElement(rate, "timebase").text = f"{tb}"
|
96
|
-
ET.SubElement(rate, "ntsc").text = ntsc
|
97
|
-
ET.SubElement(vschar, "width").text = f"{tl.res[0]}"
|
98
|
-
ET.SubElement(vschar, "height").text = f"{tl.res[1]}"
|
99
|
-
ET.SubElement(vschar, "pixelaspectratio").text = "square"
|
100
|
-
|
101
|
-
for aud in src.audios:
|
102
|
-
audiodef = ET.SubElement(mediadef, "audio")
|
103
|
-
aschar = ET.SubElement(audiodef, "samplecharacteristics")
|
104
|
-
ET.SubElement(aschar, "depth").text = DEPTH
|
105
|
-
ET.SubElement(aschar, "samplerate").text = f"{tl.sr}"
|
106
|
-
ET.SubElement(audiodef, "channelcount").text = f"{aud.channels}"
|
107
|
-
|
108
|
-
|
109
|
-
def resolve_write_audio(audio: Element, make_filedef, tl: v3) -> None:
|
110
|
-
for t, aclips in enumerate(tl.a):
|
111
|
-
track = ET.SubElement(audio, "track")
|
112
|
-
for j, aclip in enumerate(aclips):
|
113
|
-
src = aclip.src
|
114
|
-
|
115
|
-
_start = f"{aclip.start}"
|
116
|
-
_end = f"{aclip.start + aclip.dur}"
|
117
|
-
_in = f"{aclip.offset}"
|
118
|
-
_out = f"{aclip.offset + aclip.dur}"
|
119
|
-
|
120
|
-
if not src.videos:
|
121
|
-
clip_item_num = j + 1
|
122
|
-
else:
|
123
|
-
clip_item_num = len(aclips) + 1 + j
|
124
|
-
|
125
|
-
clipitem = ET.SubElement(track, "clipitem", id=f"clipitem-{clip_item_num}")
|
126
|
-
ET.SubElement(clipitem, "name").text = src.path.stem
|
127
|
-
ET.SubElement(clipitem, "start").text = _start
|
128
|
-
ET.SubElement(clipitem, "end").text = _end
|
129
|
-
ET.SubElement(clipitem, "enabled").text = "TRUE"
|
130
|
-
ET.SubElement(clipitem, "in").text = _in
|
131
|
-
ET.SubElement(clipitem, "out").text = _out
|
132
|
-
|
133
|
-
make_filedef(clipitem, aclip.src)
|
134
|
-
|
135
|
-
sourcetrack = ET.SubElement(clipitem, "sourcetrack")
|
136
|
-
ET.SubElement(sourcetrack, "mediatype").text = "audio"
|
137
|
-
ET.SubElement(sourcetrack, "trackindex").text = f"{t + 1}"
|
138
|
-
|
139
|
-
if src.videos:
|
140
|
-
link = ET.SubElement(clipitem, "link")
|
141
|
-
ET.SubElement(link, "linkclipref").text = f"clipitem-{j + 1}"
|
142
|
-
ET.SubElement(link, "mediatype").text = "video"
|
143
|
-
link = ET.SubElement(clipitem, "link")
|
144
|
-
ET.SubElement(link, "linkclipref").text = f"clipitem-{clip_item_num}"
|
145
|
-
|
146
|
-
if aclip.speed != 1:
|
147
|
-
clipitem.append(speedup(aclip.speed * 100))
|
148
|
-
|
149
|
-
|
150
|
-
def premiere_write_audio(audio: Element, make_filedef, tl: v3) -> None:
|
151
|
-
ET.SubElement(audio, "numOutputChannels").text = "2"
|
152
|
-
aformat = ET.SubElement(audio, "format")
|
153
|
-
aschar = ET.SubElement(aformat, "samplecharacteristics")
|
154
|
-
ET.SubElement(aschar, "depth").text = DEPTH
|
155
|
-
ET.SubElement(aschar, "samplerate").text = f"{tl.sr}"
|
156
|
-
|
157
|
-
has_video = tl.v and tl.v[0]
|
158
|
-
t = 0
|
159
|
-
for aclips in tl.a:
|
160
|
-
for channelcount in range(0, 2): # Because "stereo" is hardcoded.
|
161
|
-
t += 1
|
162
|
-
track = ET.Element(
|
163
|
-
"track",
|
164
|
-
currentExplodedTrackIndex=f"{channelcount}",
|
165
|
-
totalExplodedTrackCount="2", # Because "stereo" is hardcoded.
|
166
|
-
premiereTrackType="Stereo",
|
167
|
-
)
|
168
|
-
|
169
|
-
if has_video:
|
170
|
-
ET.SubElement(track, "outputchannelindex").text = f"{channelcount + 1}"
|
171
|
-
|
172
|
-
for j, aclip in enumerate(aclips):
|
173
|
-
src = aclip.src
|
174
|
-
|
175
|
-
_start = f"{aclip.start}"
|
176
|
-
_end = f"{aclip.start + aclip.dur}"
|
177
|
-
_in = f"{aclip.offset}"
|
178
|
-
_out = f"{aclip.offset + aclip.dur}"
|
179
|
-
|
180
|
-
if not has_video:
|
181
|
-
clip_item_num = j + 1
|
182
|
-
else:
|
183
|
-
clip_item_num = len(aclips) + 1 + j + (t * len(aclips))
|
184
|
-
|
185
|
-
clipitem = ET.SubElement(
|
186
|
-
track,
|
187
|
-
"clipitem",
|
188
|
-
id=f"clipitem-{clip_item_num}",
|
189
|
-
premiereChannelType="stereo",
|
190
|
-
)
|
191
|
-
ET.SubElement(clipitem, "name").text = src.path.stem
|
192
|
-
ET.SubElement(clipitem, "enabled").text = "TRUE"
|
193
|
-
ET.SubElement(clipitem, "start").text = _start
|
194
|
-
ET.SubElement(clipitem, "end").text = _end
|
195
|
-
ET.SubElement(clipitem, "in").text = _in
|
196
|
-
ET.SubElement(clipitem, "out").text = _out
|
197
|
-
|
198
|
-
make_filedef(clipitem, aclip.src)
|
199
|
-
|
200
|
-
sourcetrack = ET.SubElement(clipitem, "sourcetrack")
|
201
|
-
ET.SubElement(sourcetrack, "mediatype").text = "audio"
|
202
|
-
ET.SubElement(sourcetrack, "trackindex").text = f"{t}"
|
203
|
-
labels = ET.SubElement(clipitem, "labels")
|
204
|
-
ET.SubElement(labels, "label2").text = "Iris"
|
205
|
-
|
206
|
-
if aclip.speed != 1:
|
207
|
-
clipitem.append(speedup(aclip.speed * 100))
|
208
|
-
|
209
|
-
audio.append(track)
|
210
|
-
|
211
|
-
|
212
|
-
def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
|
213
|
-
width, height = tl.res
|
214
|
-
timebase, ntsc = set_tb_ntsc(tl.tb)
|
215
|
-
|
216
|
-
src_to_url: dict[FileInfo, str] = {}
|
217
|
-
src_to_id: dict[FileInfo, str] = {}
|
218
|
-
|
219
|
-
file_defs: set[str] = set() # Contains urls
|
220
|
-
|
221
|
-
for src in set(tl.sources):
|
222
|
-
the_id = f"file-{len(src_to_id) + 1}"
|
223
|
-
src_to_url[src] = f"{src.path.resolve()}"
|
224
|
-
src_to_id[src] = the_id
|
225
|
-
|
226
|
-
def make_filedef(clipitem: Element, src: FileInfo) -> None:
|
227
|
-
pathurl = src_to_url[src]
|
228
|
-
filedef = ET.SubElement(clipitem, "file", id=src_to_id[src])
|
229
|
-
if pathurl not in file_defs:
|
230
|
-
media_def(filedef, pathurl, src, tl, timebase, ntsc)
|
231
|
-
file_defs.add(pathurl)
|
232
|
-
|
233
|
-
xmeml = ET.Element("xmeml", version="5")
|
234
|
-
if resolve:
|
235
|
-
sequence = ET.SubElement(xmeml, "sequence")
|
236
|
-
else:
|
237
|
-
sequence = ET.SubElement(xmeml, "sequence", explodedTracks="true")
|
238
|
-
|
239
|
-
ET.SubElement(sequence, "name").text = name
|
240
|
-
ET.SubElement(sequence, "duration").text = f"{len(tl)}"
|
241
|
-
rate = ET.SubElement(sequence, "rate")
|
242
|
-
ET.SubElement(rate, "timebase").text = f"{timebase}"
|
243
|
-
ET.SubElement(rate, "ntsc").text = ntsc
|
244
|
-
media = ET.SubElement(sequence, "media")
|
245
|
-
video = ET.SubElement(media, "video")
|
246
|
-
vformat = ET.SubElement(video, "format")
|
247
|
-
vschar = ET.SubElement(vformat, "samplecharacteristics")
|
248
|
-
|
249
|
-
ET.SubElement(vschar, "width").text = f"{width}"
|
250
|
-
ET.SubElement(vschar, "height").text = f"{height}"
|
251
|
-
ET.SubElement(vschar, "pixelaspectratio").text = "square"
|
252
|
-
|
253
|
-
rate = ET.SubElement(vschar, "rate")
|
254
|
-
ET.SubElement(rate, "timebase").text = f"{timebase}"
|
255
|
-
ET.SubElement(rate, "ntsc").text = ntsc
|
256
|
-
|
257
|
-
if len(tl.v) > 0 and len(tl.v[0]) > 0:
|
258
|
-
track = ET.SubElement(video, "track")
|
259
|
-
|
260
|
-
for j, clip in enumerate(tl.v[0]):
|
261
|
-
assert isinstance(clip, Clip)
|
262
|
-
|
263
|
-
_start = f"{clip.start}"
|
264
|
-
_end = f"{clip.start + clip.dur}"
|
265
|
-
_in = f"{clip.offset}"
|
266
|
-
_out = f"{clip.offset + clip.dur}"
|
267
|
-
|
268
|
-
this_clipid = f"clipitem-{j + 1}"
|
269
|
-
clipitem = ET.SubElement(track, "clipitem", id=this_clipid)
|
270
|
-
ET.SubElement(clipitem, "name").text = src.path.stem
|
271
|
-
ET.SubElement(clipitem, "enabled").text = "TRUE"
|
272
|
-
ET.SubElement(clipitem, "start").text = _start
|
273
|
-
ET.SubElement(clipitem, "end").text = _end
|
274
|
-
ET.SubElement(clipitem, "in").text = _in
|
275
|
-
ET.SubElement(clipitem, "out").text = _out
|
276
|
-
|
277
|
-
make_filedef(clipitem, clip.src)
|
278
|
-
|
279
|
-
ET.SubElement(clipitem, "compositemode").text = "normal"
|
280
|
-
if clip.speed != 1:
|
281
|
-
clipitem.append(speedup(clip.speed * 100))
|
282
|
-
|
283
|
-
if resolve:
|
284
|
-
link = ET.SubElement(clipitem, "link")
|
285
|
-
ET.SubElement(link, "linkclipref").text = this_clipid
|
286
|
-
link = ET.SubElement(clipitem, "link")
|
287
|
-
ET.SubElement(
|
288
|
-
link, "linkclipref"
|
289
|
-
).text = f"clipitem-{(len(tl.v[0])) + j + 1}"
|
290
|
-
continue
|
291
|
-
|
292
|
-
for i in range(1 + len(src.audios) * 2): # `2` because stereo.
|
293
|
-
link = ET.SubElement(clipitem, "link")
|
294
|
-
ET.SubElement(
|
295
|
-
link, "linkclipref"
|
296
|
-
).text = f"clipitem-{(i * (len(tl.v[0]))) + j + 1}"
|
297
|
-
ET.SubElement(link, "mediatype").text = "video" if i == 0 else "audio"
|
298
|
-
ET.SubElement(link, "trackindex").text = f"{max(i, 1)}"
|
299
|
-
ET.SubElement(link, "clipindex").text = f"{j + 1}"
|
300
|
-
|
301
|
-
# Audio definitions and clips
|
302
|
-
audio = ET.SubElement(media, "audio")
|
303
|
-
if resolve:
|
304
|
-
resolve_write_audio(audio, make_filedef, tl)
|
305
|
-
else:
|
306
|
-
premiere_write_audio(audio, make_filedef, tl)
|
307
|
-
|
308
|
-
tree = ET.ElementTree(xmeml)
|
309
|
-
ET.indent(tree, space=" ", level=0)
|
310
|
-
if output == "-":
|
311
|
-
print(ET.tostring(xmeml, encoding="unicode"))
|
312
|
-
else:
|
313
|
-
tree.write(output, xml_declaration=True, encoding="utf-8")
|
auto_editor/exports/json.py
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
|
3
|
-
from auto_editor.json import dump
|
4
|
-
from auto_editor.timeline import Clip, v3
|
5
|
-
from auto_editor.utils.log import Log
|
6
|
-
|
7
|
-
|
8
|
-
def as_dict(self: v3) -> dict:
|
9
|
-
def aclip_to_dict(self: Clip) -> dict:
|
10
|
-
return {
|
11
|
-
"name": "audio",
|
12
|
-
"src": self.src,
|
13
|
-
"start": self.start,
|
14
|
-
"dur": self.dur,
|
15
|
-
"offset": self.offset,
|
16
|
-
"speed": self.speed,
|
17
|
-
"volume": self.volume,
|
18
|
-
"stream": self.stream,
|
19
|
-
}
|
20
|
-
|
21
|
-
v = []
|
22
|
-
a = []
|
23
|
-
for vlayer in self.v:
|
24
|
-
vb = [vobj.as_dict() for vobj in vlayer]
|
25
|
-
if vb:
|
26
|
-
v.append(vb)
|
27
|
-
for layer in self.a:
|
28
|
-
ab = [aclip_to_dict(clip) for clip in layer]
|
29
|
-
if ab:
|
30
|
-
a.append(ab)
|
31
|
-
|
32
|
-
return {
|
33
|
-
"version": "3",
|
34
|
-
"timebase": f"{self.tb.numerator}/{self.tb.denominator}",
|
35
|
-
"background": self.background,
|
36
|
-
"resolution": self.T.res,
|
37
|
-
"samplerate": self.T.sr,
|
38
|
-
"layout": self.T.layout,
|
39
|
-
"v": v,
|
40
|
-
"a": a,
|
41
|
-
}
|
42
|
-
|
43
|
-
|
44
|
-
def make_json_timeline(ver: str, out: str, tl: v3, log: Log) -> None:
|
45
|
-
if ver not in {"v1", "v3"}:
|
46
|
-
log.error(f"Unknown timeline version: {ver}")
|
47
|
-
|
48
|
-
if out == "-":
|
49
|
-
outfile = sys.stdout
|
50
|
-
else:
|
51
|
-
outfile = open(out, "w")
|
52
|
-
|
53
|
-
if ver == "v3":
|
54
|
-
dump(as_dict(tl), outfile, indent=2)
|
55
|
-
else:
|
56
|
-
if tl.v1 is None:
|
57
|
-
log.error("Timeline can't be converted to v1 format")
|
58
|
-
dump(tl.v1.as_dict(), outfile, indent=2)
|
59
|
-
|
60
|
-
if out == "-":
|
61
|
-
print("") # Flush stdout
|
62
|
-
else:
|
63
|
-
outfile.close()
|