videopython 0.25.3__tar.gz → 0.25.4__tar.gz

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 (54) hide show
  1. {videopython-0.25.3 → videopython-0.25.4}/.gitignore +3 -0
  2. {videopython-0.25.3 → videopython-0.25.4}/PKG-INFO +1 -1
  3. {videopython-0.25.3 → videopython-0.25.4}/pyproject.toml +1 -1
  4. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/editing/__init__.py +2 -0
  5. videopython-0.25.4/src/videopython/editing/premiere_xml.py +313 -0
  6. {videopython-0.25.3 → videopython-0.25.4}/LICENSE +0 -0
  7. {videopython-0.25.3 → videopython-0.25.4}/README.md +0 -0
  8. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/__init__.py +0 -0
  9. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/__init__.py +0 -0
  10. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/_device.py +0 -0
  11. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/dubbing/__init__.py +0 -0
  12. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/dubbing/dubber.py +0 -0
  13. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/dubbing/models.py +0 -0
  14. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/dubbing/pipeline.py +0 -0
  15. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/dubbing/timing.py +0 -0
  16. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/generation/__init__.py +0 -0
  17. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/generation/audio.py +0 -0
  18. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/generation/image.py +0 -0
  19. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/generation/translation.py +0 -0
  20. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/generation/video.py +0 -0
  21. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/registry.py +0 -0
  22. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/swapping/__init__.py +0 -0
  23. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/swapping/inpainter.py +0 -0
  24. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/swapping/models.py +0 -0
  25. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/swapping/segmenter.py +0 -0
  26. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/swapping/swapper.py +0 -0
  27. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/transforms.py +0 -0
  28. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/understanding/__init__.py +0 -0
  29. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/understanding/audio.py +0 -0
  30. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/understanding/image.py +0 -0
  31. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/understanding/separation.py +0 -0
  32. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/understanding/temporal.py +0 -0
  33. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/ai/video_analysis.py +0 -0
  34. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/__init__.py +0 -0
  35. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/audio/__init__.py +0 -0
  36. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/audio/analysis.py +0 -0
  37. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/audio/audio.py +0 -0
  38. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/combine.py +0 -0
  39. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/description.py +0 -0
  40. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/effects.py +0 -0
  41. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/exceptions.py +0 -0
  42. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/progress.py +0 -0
  43. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/registry.py +0 -0
  44. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/scene.py +0 -0
  45. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/text/__init__.py +0 -0
  46. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/text/overlay.py +0 -0
  47. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/text/transcription.py +0 -0
  48. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/transforms.py +0 -0
  49. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/transitions.py +0 -0
  50. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/utils.py +0 -0
  51. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/base/video.py +0 -0
  52. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/editing/multicam.py +0 -0
  53. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/editing/video_edit.py +0 -0
  54. {videopython-0.25.3 → videopython-0.25.4}/src/videopython/py.typed +0 -0
@@ -1,3 +1,6 @@
1
+ # Project notes
2
+ TODO.md
3
+
1
4
  # Byte-compiled / optimized / DLL files
2
5
  __pycache__/
3
6
  *.py[cod]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: videopython
3
- Version: 0.25.3
3
+ Version: 0.25.4
4
4
  Summary: Minimal video generation and processing library.
5
5
  Project-URL: Homepage, https://videopython.com
6
6
  Project-URL: Repository, https://github.com/bartwojtowicz/videopython/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "videopython"
3
- version = "0.25.3"
3
+ version = "0.25.4"
4
4
  description = "Minimal video generation and processing library."
5
5
  authors = [
6
6
  { name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
@@ -1,4 +1,5 @@
1
1
  from .multicam import CutPoint, MultiCamEdit
2
+ from .premiere_xml import to_premiere_xml
2
3
  from .video_edit import SegmentConfig, VideoEdit
3
4
 
4
5
  __all__ = [
@@ -6,4 +7,5 @@ __all__ = [
6
7
  "SegmentConfig",
7
8
  "MultiCamEdit",
8
9
  "CutPoint",
10
+ "to_premiere_xml",
9
11
  ]
@@ -0,0 +1,313 @@
1
+ """Export MultiCamEdit plans to FCP7 XML (xmeml) for Adobe Premiere Pro.
2
+
3
+ Generates xmeml version 5 with a flat sequence of clipitems on one video
4
+ track and stereo audio tracks. Each cut becomes a clipitem that directly
5
+ references its source file.
6
+
7
+ Known limitations:
8
+ - BlurTransition has no xmeml equivalent and is exported as a hard cut.
9
+ - Source file paths are absolute file://localhost/ URLs; the xmeml is not
10
+ portable across machines without relinking media in Premiere.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+ from typing import TYPE_CHECKING
17
+ from urllib.parse import quote
18
+ from xml.dom.minidom import parseString
19
+ from xml.etree.ElementTree import Element, SubElement, tostring
20
+
21
+ if TYPE_CHECKING:
22
+ from videopython.editing.multicam import MultiCamEdit
23
+
24
+ __all__ = ["to_premiere_xml"]
25
+
26
+ _XMEML_VERSION = "5"
27
+
28
+ # Integer timebase for common NTSC rates (xmeml uses <timebase>/<ntsc>).
29
+ _NTSC_TIMEBASES: dict[float, int] = {
30
+ 23.976: 24,
31
+ 23.98: 24,
32
+ 29.97: 30,
33
+ 59.94: 60,
34
+ }
35
+
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Time and path helpers
39
+ # ---------------------------------------------------------------------------
40
+
41
+
42
+ def _fps_to_rate_info(fps: float) -> tuple[int, bool]:
43
+ """Return ``(timebase, ntsc)`` for a frame rate."""
44
+ rounded = round(fps, 3)
45
+ if rounded in _NTSC_TIMEBASES:
46
+ return _NTSC_TIMEBASES[rounded], True
47
+ return round(fps), False
48
+
49
+
50
+ def _seconds_to_frames(seconds: float, fps: float) -> int:
51
+ """Convert seconds to an integer frame count."""
52
+ timebase, ntsc = _fps_to_rate_info(fps)
53
+ actual_fps = timebase / 1.001 if ntsc else timebase
54
+ return round(seconds * actual_fps)
55
+
56
+
57
+ def _path_to_url(path: Path) -> str:
58
+ """Convert a local path to a ``file://localhost/...`` URL."""
59
+ absolute = str(path.resolve())
60
+ if not absolute.startswith("/"):
61
+ absolute = "/" + absolute
62
+ return "file://localhost" + quote(absolute)
63
+
64
+
65
+ def _serialize(root: Element, doctype: str) -> str:
66
+ """Pretty-print an ElementTree *root* with XML declaration and DOCTYPE."""
67
+ rough = tostring(root, encoding="unicode", xml_declaration=False)
68
+ dom = parseString(rough)
69
+ lines = dom.toprettyxml(indent=" ", encoding="utf-8").decode("utf-8").splitlines(True)
70
+ return lines[0] + doctype + "\n" + "".join(lines[1:])
71
+
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Element builders
75
+ # ---------------------------------------------------------------------------
76
+
77
+
78
+ def _add_rate(parent: Element, fps: float) -> Element:
79
+ timebase, ntsc = _fps_to_rate_info(fps)
80
+ rate = SubElement(parent, "rate")
81
+ SubElement(rate, "timebase").text = str(timebase)
82
+ SubElement(rate, "ntsc").text = "TRUE" if ntsc else "FALSE"
83
+ return rate
84
+
85
+
86
+ def _add_text(parent: Element, tag: str, text: str) -> Element:
87
+ el = SubElement(parent, tag)
88
+ el.text = text
89
+ return el
90
+
91
+
92
+ def _build_file_element(
93
+ parent: Element,
94
+ file_id: str,
95
+ name: str,
96
+ path: Path,
97
+ fps: float,
98
+ duration_frames: int,
99
+ width: int | None = None,
100
+ height: int | None = None,
101
+ has_video: bool = True,
102
+ has_audio: bool = True,
103
+ ) -> Element:
104
+ """Build a full ``<file>`` definition (first occurrence)."""
105
+ f = SubElement(parent, "file", id=file_id)
106
+ _add_text(f, "name", name)
107
+ _add_text(f, "pathurl", _path_to_url(path))
108
+ _add_rate(f, fps)
109
+ _add_text(f, "duration", str(duration_frames))
110
+
111
+ tc = SubElement(f, "timecode")
112
+ _add_text(tc, "string", "00:00:00:00")
113
+ _add_text(tc, "frame", "0")
114
+ _add_text(tc, "displayformat", "NDF")
115
+ _add_rate(tc, fps)
116
+
117
+ media = SubElement(f, "media")
118
+ if has_video:
119
+ vid = SubElement(media, "video")
120
+ _add_text(vid, "duration", str(duration_frames))
121
+ sc = SubElement(vid, "samplecharacteristics")
122
+ _add_text(sc, "width", str(width))
123
+ _add_text(sc, "height", str(height))
124
+ if has_audio:
125
+ aud = SubElement(media, "audio")
126
+ asc = SubElement(aud, "samplecharacteristics")
127
+ _add_text(asc, "depth", "16")
128
+ _add_text(asc, "samplerate", "48000")
129
+ _add_text(aud, "channelcount", "2")
130
+
131
+ return f
132
+
133
+
134
+ def _build_video_transition(parent: Element, fps: float, start_frame: int, end_frame: int) -> Element:
135
+ ti = SubElement(parent, "transitionitem")
136
+ _add_rate(ti, fps)
137
+ _add_text(ti, "start", str(start_frame))
138
+ _add_text(ti, "end", str(end_frame))
139
+ _add_text(ti, "alignment", "center")
140
+
141
+ effect = SubElement(ti, "effect")
142
+ _add_text(effect, "name", "Cross Dissolve")
143
+ _add_text(effect, "effectid", "Cross Dissolve")
144
+ _add_text(effect, "effectcategory", "Dissolve")
145
+ _add_text(effect, "effecttype", "transition")
146
+ _add_text(effect, "mediatype", "video")
147
+ _add_text(effect, "wipecode", "0")
148
+ _add_text(effect, "wipeaccuracy", "100")
149
+ _add_text(effect, "startratio", "0")
150
+ _add_text(effect, "endratio", "1")
151
+ _add_text(effect, "reverse", "FALSE")
152
+ return ti
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # Main export function
157
+ # ---------------------------------------------------------------------------
158
+
159
+
160
+ def to_premiere_xml(edit: MultiCamEdit) -> str:
161
+ """Export a *MultiCamEdit* plan to FCP7 XML (xmeml) for Premiere Pro.
162
+
163
+ Generates a flat sequence with one video track containing clipitems
164
+ that directly reference source files, plus stereo audio tracks for
165
+ the external audio source.
166
+
167
+ Args:
168
+ edit: A validated ``MultiCamEdit`` instance.
169
+
170
+ Returns:
171
+ A UTF-8 XML string with ``<?xml ...?>`` declaration and
172
+ ``<!DOCTYPE xmeml>``.
173
+ """
174
+ from videopython.base.transitions import FadeTransition
175
+
176
+ meta = edit._source_meta
177
+ fps = meta.fps
178
+ source_duration = edit._source_duration
179
+ total_frames = _seconds_to_frames(source_duration, fps)
180
+
181
+ def frames(s: float) -> int:
182
+ return _seconds_to_frames(s, fps)
183
+
184
+ root = Element("xmeml", version=_XMEML_VERSION)
185
+
186
+ # -- sequence --------------------------------------------------------------
187
+ seq = SubElement(root, "sequence", id="MultiCamEdit")
188
+ _add_text(seq, "name", "MultiCamEdit")
189
+ _add_text(seq, "duration", str(total_frames))
190
+ _add_rate(seq, fps)
191
+
192
+ tc = SubElement(seq, "timecode")
193
+ _add_text(tc, "string", "00:00:00:00")
194
+ _add_text(tc, "frame", "0")
195
+ _add_text(tc, "displayformat", "NDF")
196
+ _add_rate(tc, fps)
197
+
198
+ media = SubElement(seq, "media")
199
+
200
+ # -- video -----------------------------------------------------------------
201
+ video = SubElement(media, "video")
202
+ fmt = SubElement(video, "format")
203
+ sc = SubElement(fmt, "samplecharacteristics")
204
+ _add_text(sc, "width", str(meta.width))
205
+ _add_text(sc, "height", str(meta.height))
206
+ _add_text(sc, "anamorphic", "FALSE")
207
+ _add_text(sc, "pixelaspectratio", "square")
208
+ _add_text(sc, "fielddominance", "none")
209
+ _add_rate(sc, fps)
210
+ _add_text(sc, "colordepth", "24")
211
+
212
+ v_track = SubElement(video, "track")
213
+ defined_file_ids: set[str] = set()
214
+ cuts = edit.cuts
215
+
216
+ for i, cut in enumerate(cuts):
217
+ start_time = cut.time
218
+ end_time = cuts[i + 1].time if i + 1 < len(cuts) else source_duration
219
+ start_frame = frames(start_time)
220
+ end_frame = frames(end_time)
221
+
222
+ camera = cut.camera
223
+ offset = edit.source_offsets.get(camera, 0.0)
224
+ in_frame = frames(start_time - offset)
225
+ out_frame = frames(end_time - offset)
226
+
227
+ # Transition
228
+ if i > 0:
229
+ transition = cut.transition or edit.default_transition
230
+ effect_time = getattr(transition, "effect_time_seconds", 0.0)
231
+ if isinstance(transition, FadeTransition) and effect_time > 0:
232
+ t_half = effect_time / 2
233
+ _build_video_transition(v_track, fps, frames(start_time - t_half), frames(start_time + t_half))
234
+
235
+ ci = SubElement(v_track, "clipitem", id=f"clipitem-v-{i + 1}")
236
+ _add_text(ci, "name", camera)
237
+ _add_text(ci, "duration", str(total_frames))
238
+ _add_rate(ci, fps)
239
+ _add_text(ci, "in", str(in_frame))
240
+ _add_text(ci, "out", str(out_frame))
241
+ _add_text(ci, "start", str(start_frame))
242
+ _add_text(ci, "end", str(end_frame))
243
+ _add_text(ci, "enabled", "TRUE")
244
+
245
+ file_id = f"file-{camera}"
246
+ if file_id not in defined_file_ids:
247
+ src_meta = edit._source_metas[camera]
248
+ src_dur_frames = _seconds_to_frames(src_meta.total_seconds, fps)
249
+ _build_file_element(
250
+ ci,
251
+ file_id,
252
+ edit.sources[camera].name,
253
+ edit.sources[camera],
254
+ fps,
255
+ src_dur_frames,
256
+ width=src_meta.width,
257
+ height=src_meta.height,
258
+ )
259
+ defined_file_ids.add(file_id)
260
+ else:
261
+ SubElement(ci, "file", id=file_id)
262
+
263
+ # -- audio -----------------------------------------------------------------
264
+ if edit.audio_source:
265
+ audio = SubElement(media, "audio")
266
+ afmt = SubElement(audio, "format")
267
+ asc = SubElement(afmt, "samplecharacteristics")
268
+ _add_text(asc, "depth", "16")
269
+ _add_text(asc, "samplerate", "48000")
270
+
271
+ outputs = SubElement(audio, "outputs")
272
+ group = SubElement(outputs, "group")
273
+ _add_text(group, "index", "1")
274
+ _add_text(group, "numchannels", "2")
275
+ _add_text(group, "downmix", "0")
276
+ for ch in (1, 2):
277
+ channel = SubElement(group, "channel")
278
+ _add_text(channel, "index", str(ch))
279
+
280
+ audio_file_id = "file-audio"
281
+
282
+ for track_idx in (1, 2):
283
+ a_track = SubElement(audio, "track")
284
+
285
+ aci = SubElement(a_track, "clipitem", id=f"clipitem-a{track_idx}")
286
+ _add_text(aci, "name", "Audio")
287
+ _add_text(aci, "duration", str(total_frames))
288
+ _add_rate(aci, fps)
289
+ _add_text(aci, "in", "0")
290
+ _add_text(aci, "out", str(total_frames))
291
+ _add_text(aci, "start", "0")
292
+ _add_text(aci, "end", str(total_frames))
293
+ _add_text(aci, "enabled", "TRUE")
294
+
295
+ if audio_file_id not in defined_file_ids:
296
+ _build_file_element(
297
+ aci,
298
+ audio_file_id,
299
+ edit.audio_source.name,
300
+ edit.audio_source,
301
+ fps,
302
+ total_frames,
303
+ has_video=False,
304
+ )
305
+ defined_file_ids.add(audio_file_id)
306
+ else:
307
+ SubElement(aci, "file", id=audio_file_id)
308
+
309
+ st = SubElement(aci, "sourcetrack")
310
+ _add_text(st, "mediatype", "audio")
311
+ _add_text(st, "trackindex", str(track_idx))
312
+
313
+ return _serialize(root, "<!DOCTYPE xmeml>")
File without changes
File without changes