auto-editor 28.0.1__tar.gz → 28.0.2__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.
- {auto_editor-28.0.1 → auto_editor-28.0.2}/PKG-INFO +1 -1
- auto_editor-28.0.2/auto_editor/__init__.py +1 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/levels.py +17 -9
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/edit.py +29 -25
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/exports/fcp11.py +40 -14
- auto_editor-28.0.2/auto_editor/exports/json.py +63 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/exports/shotcut.py +1 -2
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/ffwrapper.py +12 -1
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/preview.py +12 -21
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/timeline.py +0 -35
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor.egg-info/PKG-INFO +1 -1
- auto_editor-28.0.1/auto_editor/__init__.py +0 -1
- auto_editor-28.0.1/auto_editor/exports/json.py +0 -32
- {auto_editor-28.0.1 → auto_editor-28.0.2}/LICENSE +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/README.md +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/__main__.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/analyze.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/__init__.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/cache.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/desc.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/info.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/palet.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/repl.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/subdump.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/cmds/test.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/exports/__init__.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/exports/fcp7.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/help.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/imports/__init__.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/imports/fcp7.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/imports/json.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/json.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lang/__init__.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lang/libintrospection.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lang/libmath.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lang/palet.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lang/stdenv.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lib/__init__.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lib/contracts.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lib/data_structs.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/lib/err.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/make_layers.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/render/__init__.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/render/audio.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/render/subtitle.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/render/video.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/utils/__init__.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/utils/bar.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/utils/chunks.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/utils/cmdkw.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/utils/container.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/utils/func.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/utils/log.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/utils/types.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor/vanparse.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor.egg-info/SOURCES.txt +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor.egg-info/dependency_links.txt +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor.egg-info/entry_points.txt +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor.egg-info/requires.txt +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/auto_editor.egg-info/top_level.txt +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/docs/build.py +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/pyproject.toml +0 -0
- {auto_editor-28.0.1 → auto_editor-28.0.2}/setup.cfg +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "28.0.2"
|
@@ -36,6 +36,7 @@ class LevelArgs:
|
|
36
36
|
input: list[str] = field(default_factory=list)
|
37
37
|
edit: str = "audio"
|
38
38
|
timebase: Fraction | None = None
|
39
|
+
no_cache: bool = False
|
39
40
|
help: bool = False
|
40
41
|
|
41
42
|
|
@@ -53,16 +54,14 @@ def levels_options(parser: ArgumentParser) -> ArgumentParser:
|
|
53
54
|
type=frame_rate,
|
54
55
|
help="Set custom timebase",
|
55
56
|
)
|
57
|
+
parser.add_argument("--no-cache", flag=True)
|
56
58
|
return parser
|
57
59
|
|
58
60
|
|
59
61
|
def print_arr(arr: NDArray) -> None:
|
60
62
|
print("")
|
61
63
|
print("@start")
|
62
|
-
if arr.dtype
|
63
|
-
for a in arr:
|
64
|
-
sys.stdout.write(f"{a:.20f}\n")
|
65
|
-
elif arr.dtype == np.bool_:
|
64
|
+
if arr.dtype == np.bool_:
|
66
65
|
for a in arr:
|
67
66
|
sys.stdout.write(f"{1 if a else 0}\n")
|
68
67
|
else:
|
@@ -76,7 +75,7 @@ def print_arr_gen(arr: Iterator[float | np.float32]) -> None:
|
|
76
75
|
print("")
|
77
76
|
print("@start")
|
78
77
|
for a in arr:
|
79
|
-
print(f"{a
|
78
|
+
print(f"{a}")
|
80
79
|
print("")
|
81
80
|
|
82
81
|
|
@@ -131,7 +130,11 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
131
130
|
levels = initLevels(src, tb, bar, False, log)
|
132
131
|
try:
|
133
132
|
if method == "audio":
|
134
|
-
if (
|
133
|
+
if (
|
134
|
+
not args.no_cache
|
135
|
+
and (arr := levels.read_cache("audio", (obj["stream"],)))
|
136
|
+
is not None
|
137
|
+
):
|
135
138
|
print_arr(arr)
|
136
139
|
else:
|
137
140
|
container = bv.open(src.path, "r")
|
@@ -148,11 +151,15 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
148
151
|
container.close()
|
149
152
|
|
150
153
|
cache_array = np.array(values, dtype=np.float32)
|
151
|
-
|
154
|
+
if not args.no_cache:
|
155
|
+
levels.cache(cache_array, "audio", (obj["stream"],))
|
152
156
|
|
153
157
|
elif method == "motion":
|
154
158
|
mobj = (obj["stream"], obj["width"], obj["blur"])
|
155
|
-
if (
|
159
|
+
if (
|
160
|
+
not args.no_cache
|
161
|
+
and (arr := levels.read_cache("motion", mobj)) is not None
|
162
|
+
):
|
156
163
|
print_arr(arr)
|
157
164
|
else:
|
158
165
|
container = bv.open(src.path, "r")
|
@@ -171,7 +178,8 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
171
178
|
container.close()
|
172
179
|
|
173
180
|
cache_array = np.array(values, dtype=np.float32)
|
174
|
-
|
181
|
+
if not args.no_cache:
|
182
|
+
levels.cache(cache_array, "motion", mobj)
|
175
183
|
|
176
184
|
elif method == "subtitle":
|
177
185
|
print_arr(levels.subtitle(**obj))
|
@@ -29,8 +29,8 @@ if TYPE_CHECKING:
|
|
29
29
|
|
30
30
|
|
31
31
|
def set_output(
|
32
|
-
out: str | None,
|
33
|
-
) -> tuple[str,
|
32
|
+
out: str | None, export: str | None, path: Path | None, log: Log
|
33
|
+
) -> tuple[str, str]:
|
34
34
|
if out is None or out == "-":
|
35
35
|
if path is None:
|
36
36
|
log.error("`--output` must be set.") # When a timeline file is the input.
|
@@ -42,33 +42,33 @@ def set_output(
|
|
42
42
|
# Use `mp4` as the default, because it is most compatible.
|
43
43
|
ext = ".mp4" if path is None else path.suffix
|
44
44
|
|
45
|
-
if
|
45
|
+
if export is None:
|
46
46
|
match ext:
|
47
47
|
case ".xml":
|
48
|
-
export
|
48
|
+
export = "premiere"
|
49
49
|
case ".fcpxml":
|
50
|
-
export =
|
50
|
+
export = "final-cut-pro"
|
51
51
|
case ".mlt":
|
52
|
-
export =
|
52
|
+
export = "shotcut"
|
53
53
|
case ".json" | ".v1":
|
54
|
-
export =
|
54
|
+
export = "v1"
|
55
55
|
case ".v3":
|
56
|
-
export =
|
56
|
+
export = "v3"
|
57
57
|
case _:
|
58
|
-
export =
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
"
|
64
|
-
|
65
|
-
"
|
66
|
-
|
67
|
-
"
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
58
|
+
export = "default"
|
59
|
+
|
60
|
+
match export:
|
61
|
+
case "premiere" | "resolve-fcp7":
|
62
|
+
ext = ".xml"
|
63
|
+
case "final-cut-pro" | "resolve":
|
64
|
+
ext = ".fcpxml"
|
65
|
+
case "shotcut":
|
66
|
+
ext = ".mlt"
|
67
|
+
case "v1":
|
68
|
+
if ext != ".json":
|
69
|
+
ext = ".v1"
|
70
|
+
case "v3":
|
71
|
+
ext = ".v3"
|
72
72
|
|
73
73
|
if out == "-":
|
74
74
|
return "-", export
|
@@ -184,9 +184,13 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
184
184
|
src = sources[0]
|
185
185
|
use_path = src.path
|
186
186
|
|
187
|
-
|
188
|
-
|
189
|
-
|
187
|
+
if args.export is None:
|
188
|
+
output, export = set_output(args.output, args.export, use_path, log)
|
189
|
+
export_ops: dict[str, Any] = {"export": export}
|
190
|
+
else:
|
191
|
+
export_ops = parse_export(args.export, log)
|
192
|
+
export = export_ops["export"]
|
193
|
+
output, _ = set_output(args.output, export, use_path, log)
|
190
194
|
|
191
195
|
if output == "-":
|
192
196
|
# When printing to stdout, silence all logs.
|
@@ -1,17 +1,10 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
1
|
import xml.etree.ElementTree as ET
|
4
|
-
from
|
2
|
+
from fractions import Fraction
|
5
3
|
from xml.etree.ElementTree import Element, ElementTree, SubElement, indent
|
6
4
|
|
5
|
+
from auto_editor.ffwrapper import FileInfo
|
7
6
|
from auto_editor.timeline import Clip, v3
|
8
|
-
|
9
|
-
if TYPE_CHECKING:
|
10
|
-
from fractions import Fraction
|
11
|
-
|
12
|
-
from auto_editor.ffwrapper import FileInfo
|
13
|
-
from auto_editor.utils.log import Log
|
14
|
-
|
7
|
+
from auto_editor.utils.log import Log
|
15
8
|
|
16
9
|
"""
|
17
10
|
Export a FCPXML 11 file readable with Final Cut Pro 10.6.8 or later.
|
@@ -52,6 +45,35 @@ def make_name(src: FileInfo, tb: Fraction) -> str:
|
|
52
45
|
return "FFVideoFormatRateUndefined"
|
53
46
|
|
54
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
|
+
|
55
77
|
def fcp11_write_xml(
|
56
78
|
group_name: str, version: int, output: str, resolve: bool, tl: v3, log: Log
|
57
79
|
) -> None:
|
@@ -90,12 +112,14 @@ def fcp11_write_xml(
|
|
90
112
|
height=f"{tl.res[1]}",
|
91
113
|
colorSpace=get_colorspace(one_src),
|
92
114
|
)
|
115
|
+
|
116
|
+
startPoint = parseSMPTE(one_src.timecode, tl.tb, log)
|
93
117
|
r2 = SubElement(
|
94
118
|
resources,
|
95
119
|
"asset",
|
96
120
|
id=f"r{i * 2 + 2}",
|
97
121
|
name=one_src.path.stem,
|
98
|
-
start=
|
122
|
+
start=fraction(startPoint),
|
99
123
|
hasVideo="1" if one_src.videos else "0",
|
100
124
|
format=f"r{i * 2 + 1}",
|
101
125
|
hasAudio="1" if one_src.audios else "0",
|
@@ -122,12 +146,14 @@ def fcp11_write_xml(
|
|
122
146
|
spine = SubElement(sequence, "spine")
|
123
147
|
|
124
148
|
def make_clip(ref: str, clip: Clip) -> None:
|
149
|
+
startPoint = parseSMPTE(clip.src.timecode, tl.tb, log)
|
150
|
+
|
125
151
|
clip_properties = {
|
126
152
|
"name": proj_name,
|
127
153
|
"ref": ref,
|
128
|
-
"offset": fraction(clip.start),
|
154
|
+
"offset": fraction(clip.start + startPoint),
|
129
155
|
"duration": fraction(clip.dur),
|
130
|
-
"start": fraction(clip.offset),
|
156
|
+
"start": fraction(clip.offset + startPoint),
|
131
157
|
"tcFormat": "NDF",
|
132
158
|
}
|
133
159
|
asset = SubElement(spine, "asset-clip", clip_properties)
|
@@ -146,7 +172,7 @@ def fcp11_write_xml(
|
|
146
172
|
)
|
147
173
|
|
148
174
|
if tl.v and tl.v[0]:
|
149
|
-
clips =
|
175
|
+
clips = [clip for clip in tl.v[0] if isinstance(clip, Clip)]
|
150
176
|
elif tl.a and tl.a[0]:
|
151
177
|
clips = tl.a[0]
|
152
178
|
else:
|
@@ -0,0 +1,63 @@
|
|
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()
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import xml.etree.ElementTree as ET
|
2
|
-
from typing import cast
|
3
2
|
|
4
3
|
from auto_editor.timeline import Clip, v3
|
5
4
|
from auto_editor.utils.func import aspect_ratio, to_timecode
|
@@ -71,7 +70,7 @@ def shotcut_write_mlt(output: str, tl: v3) -> None:
|
|
71
70
|
producers = 0
|
72
71
|
|
73
72
|
if tl.v:
|
74
|
-
clips =
|
73
|
+
clips = [clip for clip in tl.v[0] if isinstance(clip, Clip)]
|
75
74
|
elif tl.a:
|
76
75
|
clips = tl.a[0]
|
77
76
|
else:
|
@@ -66,6 +66,7 @@ class FileInfo:
|
|
66
66
|
path: Path
|
67
67
|
bitrate: int
|
68
68
|
duration: float
|
69
|
+
timecode: str # in SMPTE
|
69
70
|
videos: tuple[VideoStream, ...]
|
70
71
|
audios: tuple[AudioStream, ...]
|
71
72
|
subtitles: tuple[SubtitleStream, ...]
|
@@ -165,12 +166,22 @@ class FileInfo:
|
|
165
166
|
ext = sub_exts.get(codec, "vtt")
|
166
167
|
subtitles += (SubtitleStream(codec, ext, s.language),)
|
167
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()
|
168
179
|
bitrate = 0 if cont.bit_rate is None else cont.bit_rate
|
169
180
|
dur = 0 if cont.duration is None else cont.duration / bv.time_base
|
170
181
|
|
171
182
|
cont.close()
|
172
183
|
|
173
|
-
return FileInfo(Path(path), bitrate, dur, videos, audios, subtitles)
|
184
|
+
return FileInfo(Path(path), bitrate, dur, timecode, videos, audios, subtitles)
|
174
185
|
|
175
186
|
def __repr__(self) -> str:
|
176
187
|
return f"@{self.path.name}"
|
@@ -28,23 +28,24 @@ def time_frame(
|
|
28
28
|
def all_cuts(tl: v3, in_len: int) -> list[int]:
|
29
29
|
# Calculate cuts
|
30
30
|
tb = tl.tb
|
31
|
-
|
31
|
+
clip_spans: list[tuple[int, int]] = []
|
32
32
|
|
33
33
|
for clip in tl.a[0]:
|
34
34
|
old_offset = clip.offset * clip.speed
|
35
|
-
|
35
|
+
clip_spans.append((round(old_offset), round(old_offset + clip.dur)))
|
36
36
|
|
37
37
|
cut_lens = []
|
38
38
|
i = 0
|
39
|
-
while i < len(
|
40
|
-
if i == 0 and
|
41
|
-
cut_lens.append(
|
39
|
+
while i < len(clip_spans) - 1:
|
40
|
+
if i == 0 and clip_spans[i][0] != 0:
|
41
|
+
cut_lens.append(clip_spans[i][0])
|
42
42
|
|
43
|
-
cut_lens.append(
|
43
|
+
cut_lens.append(clip_spans[i + 1][0] - clip_spans[i][1])
|
44
44
|
i += 1
|
45
45
|
|
46
|
-
if len(
|
47
|
-
cut_lens.append(in_len -
|
46
|
+
if len(clip_spans) > 0 and clip_spans[-1][1] < round(in_len / tb):
|
47
|
+
cut_lens.append(in_len - clip_spans[-1][1])
|
48
|
+
|
48
49
|
return cut_lens
|
49
50
|
|
50
51
|
|
@@ -53,19 +54,9 @@ def preview(tl: v3, log: Log) -> None:
|
|
53
54
|
tb = tl.tb
|
54
55
|
|
55
56
|
# Calculate input videos length
|
56
|
-
all_sources = set()
|
57
|
-
for vlayer in tl.v:
|
58
|
-
for vclip in vlayer:
|
59
|
-
if hasattr(vclip, "src"):
|
60
|
-
all_sources.add(vclip.src)
|
61
|
-
for alayer in tl.a:
|
62
|
-
for aclip in alayer:
|
63
|
-
if hasattr(aclip, "src"):
|
64
|
-
all_sources.add(aclip.src)
|
65
|
-
|
66
57
|
in_len = 0
|
67
58
|
bar = initBar("none")
|
68
|
-
for src in
|
59
|
+
for src in tl.unique_sources():
|
69
60
|
in_len += initLevels(src, tb, bar, False, log).media_length
|
70
61
|
|
71
62
|
out_len = len(tl)
|
@@ -77,7 +68,7 @@ def preview(tl: v3, log: Log) -> None:
|
|
77
68
|
time_frame(fp, "output", out_len, tb, f"{round((out_len / in_len) * 100, 2)}%")
|
78
69
|
time_frame(fp, "diff", diff, tb, f"{round((diff / in_len) * 100, 2)}%")
|
79
70
|
|
80
|
-
clip_lens = [clip.dur
|
71
|
+
clip_lens = [clip.dur for clip in tl.a[0]]
|
81
72
|
log.debug(clip_lens)
|
82
73
|
|
83
74
|
fp.write(f"clips:\n - amount: {len(clip_lens)}\n")
|
@@ -90,7 +81,7 @@ def preview(tl: v3, log: Log) -> None:
|
|
90
81
|
|
91
82
|
cut_lens = all_cuts(tl, in_len)
|
92
83
|
log.debug(cut_lens)
|
93
|
-
fp.write(f"cuts:\n - amount: {len(
|
84
|
+
fp.write(f"cuts:\n - amount: {len(cut_lens)}\n")
|
94
85
|
if len(cut_lens) > 0:
|
95
86
|
time_frame(fp, "smallest", min(cut_lens), tb)
|
96
87
|
time_frame(fp, "largest", max(cut_lens), tb)
|
@@ -282,41 +282,6 @@ video\n"""
|
|
282
282
|
|
283
283
|
return result
|
284
284
|
|
285
|
-
def as_dict(self) -> dict:
|
286
|
-
def aclip_to_dict(self: Clip) -> dict:
|
287
|
-
return {
|
288
|
-
"name": "audio",
|
289
|
-
"src": self.src,
|
290
|
-
"start": self.start,
|
291
|
-
"dur": self.dur,
|
292
|
-
"offset": self.offset,
|
293
|
-
"speed": self.speed,
|
294
|
-
"volume": self.volume,
|
295
|
-
"stream": self.stream,
|
296
|
-
}
|
297
|
-
|
298
|
-
v = []
|
299
|
-
a = []
|
300
|
-
for vlayer in self.v:
|
301
|
-
vb = [vobj.as_dict() for vobj in vlayer]
|
302
|
-
if vb:
|
303
|
-
v.append(vb)
|
304
|
-
for layer in self.a:
|
305
|
-
ab = [aclip_to_dict(clip) for clip in layer]
|
306
|
-
if ab:
|
307
|
-
a.append(ab)
|
308
|
-
|
309
|
-
return {
|
310
|
-
"version": "3",
|
311
|
-
"timebase": f"{self.tb.numerator}/{self.tb.denominator}",
|
312
|
-
"background": self.background,
|
313
|
-
"resolution": self.T.res,
|
314
|
-
"samplerate": self.T.sr,
|
315
|
-
"layout": self.T.layout,
|
316
|
-
"v": v,
|
317
|
-
"a": a,
|
318
|
-
}
|
319
|
-
|
320
285
|
@property
|
321
286
|
def T(self) -> Template:
|
322
287
|
return self.template
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "28.0.1"
|
@@ -1,32 +0,0 @@
|
|
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()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|