auto-editor 26.3.3__py3-none-any.whl → 27.1.0__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/edit.py CHANGED
@@ -8,13 +8,11 @@ from os.path import splitext
8
8
  from subprocess import run
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
- import av
12
- from av import AudioResampler, Codec
11
+ import bv
13
12
 
14
- from auto_editor.ffwrapper import FileInfo, initFileInfo
13
+ from auto_editor.ffwrapper import FileInfo
15
14
  from auto_editor.lib.contracts import is_int, is_str
16
15
  from auto_editor.make_layers import clipify, make_av, make_timeline
17
- from auto_editor.output import Ensure, parse_bitrate
18
16
  from auto_editor.render.audio import make_new_audio
19
17
  from auto_editor.render.subtitle import make_new_subtitles
20
18
  from auto_editor.render.video import render_av
@@ -83,21 +81,13 @@ def set_video_codec(
83
81
  return ctr.default_vid
84
82
  return codec
85
83
 
86
- if codec == "copy":
87
- log.deprecated("The `copy` codec is deprecated. auto-editor always re-encodes")
88
- if src is None:
89
- log.error("No input to copy its codec from.")
90
- if not src.videos:
91
- log.error("Input file does not have a video stream to copy codec from.")
92
- codec = src.videos[0].codec
93
-
94
84
  if ctr.vcodecs is not None and codec not in ctr.vcodecs:
95
85
  try:
96
- cobj = Codec(codec, "w")
97
- except av.codec.codec.UnknownCodecError:
86
+ cobj = bv.Codec(codec, "w")
87
+ except bv.codec.codec.UnknownCodecError:
98
88
  log.error(f"Unknown encoder: {codec}")
99
89
  # Normalize encoder names
100
- if cobj.id not in (Codec(x, "w").id for x in ctr.vcodecs):
90
+ if cobj.id not in (bv.Codec(x, "w").id for x in ctr.vcodecs):
101
91
  log.error(codec_error.format(codec, out_ext))
102
92
 
103
93
  return codec
@@ -111,7 +101,7 @@ def set_audio_codec(
111
101
  codec = "aac"
112
102
  else:
113
103
  codec = src.audios[0].codec
114
- if av.Codec(codec, "w").audio_formats is None:
104
+ if bv.Codec(codec, "w").audio_formats is None:
115
105
  codec = "aac"
116
106
  if codec not in ctr.acodecs and ctr.default_aud != "none":
117
107
  codec = ctr.default_aud
@@ -119,21 +109,13 @@ def set_audio_codec(
119
109
  codec = "aac"
120
110
  return codec
121
111
 
122
- if codec == "copy":
123
- log.deprecated("The `copy` codec is deprecated. auto-editor always re-encodes")
124
- if src is None:
125
- log.error("No input to copy its codec from.")
126
- if not src.audios:
127
- log.error("Input file does not have an audio stream to copy codec from.")
128
- codec = src.audios[0].codec
129
-
130
112
  if ctr.acodecs is None or codec not in ctr.acodecs:
131
113
  try:
132
- cobj = Codec(codec, "w")
133
- except av.codec.codec.UnknownCodecError:
114
+ cobj = bv.Codec(codec, "w")
115
+ except bv.codec.codec.UnknownCodecError:
134
116
  log.error(f"Unknown encoder: {codec}")
135
117
  # Normalize encoder names
136
- if cobj.id not in (Codec(x, "w").id for x in ctr.acodecs):
118
+ if cobj.id not in (bv.Codec(x, "w").id for x in ctr.acodecs):
137
119
  log.error(codec_error.format(codec, out_ext))
138
120
 
139
121
  return codec
@@ -177,6 +159,11 @@ def parse_export(export: str, log: Log) -> dict[str, Any]:
177
159
  def edit_media(paths: list[str], args: Args, log: Log) -> None:
178
160
  bar = initBar(args.progress)
179
161
  tl = None
162
+ src = None
163
+
164
+ if args.keep_tracks_separate:
165
+ log.deprecated("--keep-tracks-separate is deprecated.")
166
+ args.keep_tracks_separate = False
180
167
 
181
168
  if paths:
182
169
  path_ext = splitext(paths[0])[1].lower()
@@ -184,30 +171,18 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
184
171
  from auto_editor.formats.fcp7 import fcp7_read_xml
185
172
 
186
173
  tl = fcp7_read_xml(paths[0], log)
187
- assert tl.src is not None
188
- sources: list[FileInfo] = [tl.src]
189
- src: FileInfo | None = tl.src
190
-
191
174
  elif path_ext == ".mlt":
192
175
  from auto_editor.formats.shotcut import shotcut_read_mlt
193
176
 
194
177
  tl = shotcut_read_mlt(paths[0], log)
195
- assert tl.src is not None
196
- sources = [tl.src]
197
- src = tl.src
198
-
199
178
  elif path_ext == ".json":
200
179
  from auto_editor.formats.json import read_json
201
180
 
202
181
  tl = read_json(paths[0], log)
203
- sources = [] if tl.src is None else [tl.src]
204
- src = tl.src
205
182
  else:
206
- sources = [initFileInfo(path, log) for path in paths]
183
+ sources = [FileInfo.init(path, log) for path in paths]
207
184
  src = None if not sources else sources[0]
208
185
 
209
- del paths
210
-
211
186
  output, export_ops = set_output(args.output, args.export, src, log)
212
187
  assert "export" in export_ops
213
188
  export = export_ops["export"]
@@ -268,7 +243,8 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
268
243
  from auto_editor.formats.fcp11 import fcp11_write_xml
269
244
  from auto_editor.timeline import set_stream_to_0
270
245
 
271
- set_stream_to_0(tl, log)
246
+ assert src is not None
247
+ set_stream_to_0(src, tl, log)
272
248
  fcp11_write_xml(export_ops["name"], 10, output, True, tl, log)
273
249
  return
274
250
 
@@ -281,7 +257,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
281
257
  out_ext = splitext(output)[1].replace(".", "")
282
258
 
283
259
  # Check if export options make sense.
284
- ctr = container_constructor(out_ext.lower())
260
+ ctr = container_constructor(out_ext.lower(), log)
285
261
 
286
262
  if ctr.samplerate is not None and args.sample_rate not in ctr.samplerate:
287
263
  log.error(f"'{out_ext}' container only supports samplerates: {ctr.samplerate}")
@@ -289,12 +265,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
289
265
  args.video_codec = set_video_codec(args.video_codec, src, out_ext, ctr, log)
290
266
  args.audio_codec = set_audio_codec(args.audio_codec, src, out_ext, ctr, log)
291
267
 
292
- if args.keep_tracks_separate and ctr.max_audios == 1:
293
- log.warning(f"'{out_ext}' container doesn't support multiple audio tracks.")
294
-
295
268
  def make_media(tl: v3, output_path: str) -> None:
296
- assert src is not None
297
-
298
269
  options = {}
299
270
  mov_flags = []
300
271
  if args.fragmented and not args.no_fragmented:
@@ -307,76 +278,59 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
307
278
  if mov_flags:
308
279
  options["movflags"] = "+".join(mov_flags)
309
280
 
310
- output = av.open(output_path, "w", container_options=options)
311
-
312
- if ctr.default_sub != "none" and not args.sn:
313
- sub_paths = make_new_subtitles(tl, log)
314
- else:
315
- sub_paths = []
316
-
317
- if ctr.default_aud != "none":
318
- ensure = Ensure(bar, samplerate, log)
319
- audio_paths = make_new_audio(tl, ctr, ensure, args, bar, log)
320
- else:
321
- audio_paths = []
281
+ output = bv.open(output_path, "w", container_options=options)
322
282
 
323
283
  # Setup video
324
284
  if ctr.default_vid != "none" and tl.v:
325
285
  vframes = render_av(output, tl, args, log)
326
- output_stream = next(vframes)
286
+ output_stream: bv.VideoStream | None
287
+ output_stream = next(vframes) # type: ignore
327
288
  else:
328
289
  output_stream, vframes = None, iter([])
329
290
 
330
291
  # Setup audio
331
- if audio_paths:
332
- try:
333
- audio_encoder = av.Codec(args.audio_codec, "w")
334
- except av.FFmpegError as e:
335
- log.error(e)
336
- if audio_encoder.audio_formats is None:
337
- log.error(f"{args.audio_codec}: No known audio formats avail.")
338
- audio_format = audio_encoder.audio_formats[0]
339
- resampler = AudioResampler(format=audio_format, layout="stereo", rate=tl.sr)
340
-
341
- audio_streams: list[av.AudioStream] = []
342
- audio_inputs = []
343
- audio_gen_frames = []
344
- for i, audio_path in enumerate(audio_paths):
345
- audio_stream = output.add_stream(
346
- args.audio_codec,
347
- format=audio_format,
348
- rate=tl.sr,
349
- time_base=Fraction(1, tl.sr),
350
- )
351
- if not isinstance(audio_stream, av.AudioStream):
352
- log.error(f"Not a known audio codec: {args.audio_codec}")
292
+ try:
293
+ audio_encoder = bv.Codec(args.audio_codec, "w")
294
+ except bv.FFmpegError as e:
295
+ log.error(e)
296
+ if audio_encoder.audio_formats is None:
297
+ log.error(f"{args.audio_codec}: No known audio formats avail.")
298
+ fmt = audio_encoder.audio_formats[0]
353
299
 
354
- if args.audio_bitrate != "auto":
355
- audio_stream.bit_rate = parse_bitrate(args.audio_bitrate, log)
356
- log.debug(f"audio bitrate: {audio_stream.bit_rate}")
357
- else:
358
- log.debug(f"[auto] audio bitrate: {audio_stream.bit_rate}")
359
- if i < len(src.audios) and src.audios[i].lang is not None:
360
- audio_stream.metadata["language"] = src.audios[i].lang # type: ignore
300
+ audio_streams: list[bv.AudioStream] = []
301
+
302
+ if ctr.default_aud == "none":
303
+ while len(tl.a) > 0:
304
+ tl.a.pop()
305
+ elif len(tl.a) > 1 and ctr.max_audios == 1:
306
+ log.warning("Dropping extra audio streams (container only allows one)")
361
307
 
362
- audio_streams.append(audio_stream)
363
- audio_input = av.open(audio_path)
364
- audio_inputs.append(audio_input)
365
- audio_gen_frames.append(audio_input.decode(audio=0))
308
+ while len(tl.a) > 1:
309
+ tl.a.pop()
310
+
311
+ if len(tl.a) > 0:
312
+ audio_streams, audio_gen_frames = make_new_audio(output, fmt, tl, args, log)
313
+ else:
314
+ audio_streams, audio_gen_frames = [], [iter([])]
366
315
 
367
316
  # Setup subtitles
317
+ if ctr.default_sub != "none" and not args.sn:
318
+ sub_paths = make_new_subtitles(tl, log)
319
+ else:
320
+ sub_paths = []
321
+
368
322
  subtitle_streams = []
369
323
  subtitle_inputs = []
370
324
  sub_gen_frames = []
371
325
 
372
326
  for i, sub_path in enumerate(sub_paths):
373
- subtitle_input = av.open(sub_path)
327
+ subtitle_input = bv.open(sub_path)
374
328
  subtitle_inputs.append(subtitle_input)
375
329
  subtitle_stream = output.add_stream_from_template(
376
330
  subtitle_input.streams.subtitles[0]
377
331
  )
378
- if i < len(src.subtitles) and src.subtitles[i].lang is not None:
379
- subtitle_stream.metadata["language"] = src.subtitles[i].lang # type: ignore
332
+ if i < len(tl.T.subtitles) and (lang := tl.T.subtitles[i].lang) is not None:
333
+ subtitle_stream.metadata["language"] = lang
380
334
 
381
335
  subtitle_streams.append(subtitle_stream)
382
336
  sub_gen_frames.append(subtitle_input.demux(subtitles=0))
@@ -473,13 +427,11 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
473
427
  break
474
428
 
475
429
  if should_get_audio:
476
- for audio_stream, audio_frame in zip(audio_streams, audio_frames):
477
- for reframe in resampler.resample(audio_frame):
478
- assert reframe.pts is not None
479
- heappush(
480
- frame_queue,
481
- Priority(reframe.pts, reframe, audio_stream),
482
- )
430
+ for audio_stream, aframe in zip(audio_streams, audio_frames):
431
+ if aframe is None:
432
+ continue
433
+ assert aframe.pts is not None
434
+ heappush(frame_queue, Priority(aframe.pts, aframe, audio_stream))
483
435
  if should_get_sub:
484
436
  for subtitle_stream, packet in zip(subtitle_streams, subtitle_frames):
485
437
  if packet and packet.pts is not None:
@@ -499,14 +451,14 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
499
451
  output.mux(item.stream.encode(item.frame))
500
452
  elif frame_type == "subtitle":
501
453
  output.mux(item.frame)
502
- except av.error.ExternalError:
454
+ except bv.error.ExternalError:
503
455
  log.error(
504
456
  f"Generic error for encoder: {item.stream.name}\n"
505
457
  f"at {item.index} time_base\nPerhaps video quality settings are too low?"
506
458
  )
507
- except av.FileNotFoundError:
459
+ except bv.FileNotFoundError:
508
460
  log.error(f"File not found: {output_path}")
509
- except av.FFmpegError as e:
461
+ except bv.FFmpegError as e:
510
462
  log.error(e)
511
463
 
512
464
  if bar_index:
@@ -521,8 +473,6 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
521
473
  bar.end()
522
474
 
523
475
  # Close resources
524
- for audio_input in audio_inputs:
525
- audio_input.close()
526
476
  for subtitle_input in subtitle_inputs:
527
477
  subtitle_input.close()
528
478
  output.close()
@@ -552,11 +502,9 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
552
502
  tl.v1.source, [clipify(padded_chunks, tl.v1.source)]
553
503
  )
554
504
  my_timeline = v3(
555
- tl.v1.source,
556
505
  tl.tb,
557
- tl.sr,
558
- tl.res,
559
506
  "#000",
507
+ tl.template,
560
508
  vspace,
561
509
  aspace,
562
510
  v1(tl.v1.source, padded_chunks),
auto_editor/ffwrapper.py CHANGED
@@ -4,14 +4,14 @@ from dataclasses import dataclass
4
4
  from fractions import Fraction
5
5
  from pathlib import Path
6
6
 
7
- import av
7
+ import bv
8
8
 
9
9
  from auto_editor.utils.log import Log
10
10
 
11
11
 
12
12
  def mux(input: Path, output: Path, stream: int) -> None:
13
- input_container = av.open(input, "r")
14
- output_container = av.open(output, "w")
13
+ input_container = bv.open(input, "r")
14
+ output_container = bv.open(output, "w")
15
15
 
16
16
  input_audio_stream = input_container.streams.audio[stream]
17
17
  output_audio_stream = output_container.add_stream("pcm_s16le")
@@ -86,89 +86,93 @@ class FileInfo:
86
86
  return self.audios[0].samplerate
87
87
  return 48000
88
88
 
89
+ @classmethod
90
+ def init(self, path: str, log: Log) -> FileInfo:
91
+ try:
92
+ cont = bv.open(path, "r")
93
+ except bv.error.FileNotFoundError:
94
+ log.error(f"Input file doesn't exist: {path}")
95
+ except bv.error.IsADirectoryError:
96
+ log.error(f"Expected a media file, but got a directory: {path}")
97
+ except bv.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
+ desc = cont.metadata.get("description", None)
170
+ bitrate = 0 if cont.bit_rate is None else cont.bit_rate
171
+ dur = 0 if cont.duration is None else cont.duration / bv.time_base
172
+
173
+ cont.close()
174
+
175
+ return FileInfo(Path(path), bitrate, dur, desc, videos, audios, subtitles)
176
+
89
177
  def __repr__(self) -> str:
90
178
  return f"@{self.path.name}"
91
-
92
-
93
- def initFileInfo(path: str, log: Log) -> FileInfo:
94
- try:
95
- cont = av.open(path, "r")
96
- except av.error.FileNotFoundError:
97
- log.error(f"Input file doesn't exist: {path}")
98
- except av.error.IsADirectoryError:
99
- log.error(f"Expected a media file, but got a directory: {path}")
100
- except av.error.InvalidDataError:
101
- log.error(f"Invalid data when processing: {path}")
102
-
103
- videos: tuple[VideoStream, ...] = ()
104
- audios: tuple[AudioStream, ...] = ()
105
- subtitles: tuple[SubtitleStream, ...] = ()
106
-
107
- for v in cont.streams.video:
108
- if v.duration is not None and v.time_base is not None:
109
- vdur = float(v.duration * v.time_base)
110
- else:
111
- vdur = 0.0
112
-
113
- fps = v.average_rate
114
- if (fps is None or fps < 1) and v.name in {"png", "mjpeg", "webp"}:
115
- fps = Fraction(25)
116
- if fps is None or fps == 0:
117
- fps = Fraction(30)
118
-
119
- sar = Fraction(1) if v.sample_aspect_ratio is None else v.sample_aspect_ratio
120
- cc = v.codec_context
121
-
122
- if v.name is None:
123
- log.error(f"Can't detect codec for video stream {v}")
124
-
125
- videos += (
126
- VideoStream(
127
- v.width,
128
- v.height,
129
- v.name,
130
- fps,
131
- vdur,
132
- sar,
133
- v.time_base,
134
- getattr(v.format, "name", None),
135
- cc.color_range,
136
- cc.colorspace,
137
- cc.color_primaries,
138
- cc.color_trc,
139
- 0 if v.bit_rate is None else v.bit_rate,
140
- v.language,
141
- ),
142
- )
143
-
144
- for a in cont.streams.audio:
145
- adur = 0.0
146
- if a.duration is not None and a.time_base is not None:
147
- adur = float(a.duration * a.time_base)
148
-
149
- a_cc = a.codec_context
150
- audios += (
151
- AudioStream(
152
- a_cc.codec.canonical_name,
153
- 0 if a_cc.sample_rate is None else a_cc.sample_rate,
154
- a.layout.name,
155
- a_cc.channels,
156
- adur,
157
- 0 if a_cc.bit_rate is None else a_cc.bit_rate,
158
- a.language,
159
- ),
160
- )
161
-
162
- for s in cont.streams.subtitles:
163
- codec = s.codec_context.name
164
- sub_exts = {"mov_text": "srt", "ass": "ass", "webvtt": "vtt"}
165
- ext = sub_exts.get(codec, "vtt")
166
- subtitles += (SubtitleStream(codec, ext, s.language),)
167
-
168
- desc = cont.metadata.get("description", None)
169
- bitrate = 0 if cont.bit_rate is None else cont.bit_rate
170
- dur = 0 if cont.duration is None else cont.duration / av.time_base
171
-
172
- cont.close()
173
-
174
- return FileInfo(Path(path), bitrate, dur, desc, videos, audios, subtitles)
@@ -59,13 +59,6 @@ def fcp11_write_xml(
59
59
  return "0s"
60
60
  return f"{val * tl.tb.denominator}/{tl.tb.numerator}s"
61
61
 
62
- src = tl.src
63
- assert src is not None
64
-
65
- proj_name = src.path.stem
66
- src_dur = int(src.duration * tl.tb)
67
- tl_dur = src_dur if resolve else tl.out_len()
68
-
69
62
  if version == 11:
70
63
  ver_str = "1.11"
71
64
  elif version == 10:
@@ -76,7 +69,16 @@ def fcp11_write_xml(
76
69
  fcpxml = Element("fcpxml", version=ver_str)
77
70
  resources = SubElement(fcpxml, "resources")
78
71
 
72
+ src_dur = 0
73
+ tl_dur = 0 if resolve else tl.out_len()
74
+
79
75
  for i, one_src in enumerate(tl.unique_sources()):
76
+ if i == 0:
77
+ proj_name = one_src.path.stem
78
+ src_dur = int(one_src.duration * tl.tb)
79
+ if resolve:
80
+ tl_dur = src_dur
81
+
80
82
  SubElement(
81
83
  resources,
82
84
  "format",
@@ -113,7 +115,7 @@ def fcp11_write_xml(
113
115
  format="r1",
114
116
  tcStart="0s",
115
117
  tcFormat="NDF",
116
- audioLayout="mono" if src.audios and src.audios[0].channels == 1 else "stereo",
118
+ audioLayout=tl.T.layout,
117
119
  audioRate="44.1k" if tl.sr == 44100 else "48k",
118
120
  )
119
121
  spine = SubElement(sequence, "spine")
@@ -7,8 +7,8 @@ from math import ceil
7
7
  from typing import TYPE_CHECKING
8
8
  from xml.etree.ElementTree import Element
9
9
 
10
- from auto_editor.ffwrapper import FileInfo, initFileInfo
11
- from auto_editor.timeline import ASpace, TlAudio, TlVideo, VSpace, v3
10
+ from auto_editor.ffwrapper import FileInfo
11
+ from auto_editor.timeline import ASpace, Template, TlAudio, TlVideo, VSpace, v3
12
12
 
13
13
  from .utils import Validator, show
14
14
 
@@ -282,7 +282,7 @@ def fcp7_read_xml(path: str, log: Log) -> v3:
282
282
  fileobj = valid.parse(clipitem["file"], {"pathurl": str})
283
283
 
284
284
  if "pathurl" in fileobj:
285
- sources[file_id] = initFileInfo(
285
+ sources[file_id] = FileInfo.init(
286
286
  uri_to_path(fileobj["pathurl"]),
287
287
  log,
288
288
  )
@@ -317,7 +317,7 @@ def fcp7_read_xml(path: str, log: Log) -> v3:
317
317
  file_id = clipitem["file"].attrib["id"]
318
318
  if file_id not in sources:
319
319
  fileobj = valid.parse(clipitem["file"], {"pathurl": str})
320
- sources[file_id] = initFileInfo(
320
+ sources[file_id] = FileInfo.init(
321
321
  uri_to_path(fileobj["pathurl"]), log
322
322
  )
323
323
 
@@ -336,10 +336,8 @@ def fcp7_read_xml(path: str, log: Log) -> v3:
336
336
  )
337
337
  )
338
338
 
339
- primary_src = sources[next(iter(sources))]
340
- assert type(primary_src) is FileInfo
341
-
342
- return v3(primary_src, tb, sr, res, "#000", vobjs, aobjs, v1=None)
339
+ T = Template.init(sources[next(iter(sources))], sr, res=res)
340
+ return v3(tb, "#000", T, vobjs, aobjs, v1=None)
343
341
 
344
342
 
345
343
  def media_def(
@@ -424,13 +422,14 @@ def resolve_write_audio(audio: Element, make_filedef, tl: v3) -> None:
424
422
  clipitem.append(speedup(aclip.speed * 100))
425
423
 
426
424
 
427
- def premiere_write_audio(audio: Element, make_filedef, src: FileInfo, tl: v3) -> None:
425
+ def premiere_write_audio(audio: Element, make_filedef, tl: v3) -> None:
428
426
  ET.SubElement(audio, "numOutputChannels").text = "2"
429
427
  aformat = ET.SubElement(audio, "format")
430
428
  aschar = ET.SubElement(aformat, "samplecharacteristics")
431
429
  ET.SubElement(aschar, "depth").text = DEPTH
432
430
  ET.SubElement(aschar, "samplerate").text = f"{tl.sr}"
433
431
 
432
+ has_video = tl.v and tl.v[0]
434
433
  t = 0
435
434
  for aclips in tl.a:
436
435
  for channelcount in range(0, 2): # Because "stereo" is hardcoded.
@@ -442,7 +441,7 @@ def premiere_write_audio(audio: Element, make_filedef, src: FileInfo, tl: v3) ->
442
441
  premiereTrackType="Stereo",
443
442
  )
444
443
 
445
- if src.videos:
444
+ if has_video:
446
445
  ET.SubElement(track, "outputchannelindex").text = f"{channelcount + 1}"
447
446
 
448
447
  for j, aclip in enumerate(aclips):
@@ -453,7 +452,7 @@ def premiere_write_audio(audio: Element, make_filedef, src: FileInfo, tl: v3) ->
453
452
  _in = f"{aclip.offset}"
454
453
  _out = f"{aclip.offset + aclip.dur}"
455
454
 
456
- if not src.videos:
455
+ if not has_video:
457
456
  clip_item_num = j + 1
458
457
  else:
459
458
  clip_item_num = len(aclips) + 1 + j + (t * len(aclips))
@@ -579,7 +578,7 @@ def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
579
578
  if resolve:
580
579
  resolve_write_audio(audio, make_filedef, tl)
581
580
  else:
582
- premiere_write_audio(audio, make_filedef, src, tl)
581
+ premiere_write_audio(audio, make_filedef, tl)
583
582
 
584
583
  tree = ET.ElementTree(xmeml)
585
584
  ET.indent(tree, space=" ", level=0)