auto-editor 27.0.0__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/__init__.py +1 -1
- auto_editor/__main__.py +8 -0
- auto_editor/cmds/desc.py +2 -2
- auto_editor/cmds/info.py +2 -2
- auto_editor/cmds/levels.py +2 -2
- auto_editor/cmds/repl.py +3 -8
- auto_editor/cmds/test.py +36 -2
- auto_editor/edit.py +37 -79
- auto_editor/ffwrapper.py +88 -84
- auto_editor/formats/fcp11.py +10 -8
- auto_editor/formats/fcp7.py +11 -12
- auto_editor/formats/json.py +8 -9
- auto_editor/lang/stdenv.py +1 -0
- auto_editor/make_layers.py +18 -8
- auto_editor/render/audio.py +219 -84
- auto_editor/render/video.py +1 -2
- auto_editor/timeline.py +60 -10
- auto_editor/utils/container.py +19 -12
- auto_editor/utils/func.py +21 -0
- {auto_editor-27.0.0.dist-info → auto_editor-27.1.0.dist-info}/METADATA +2 -2
- {auto_editor-27.0.0.dist-info → auto_editor-27.1.0.dist-info}/RECORD +25 -27
- {auto_editor-27.0.0.dist-info → auto_editor-27.1.0.dist-info}/WHEEL +1 -1
- auto_editor/output.py +0 -86
- auto_editor/wavfile.py +0 -310
- {auto_editor-27.0.0.dist-info → auto_editor-27.1.0.dist-info}/entry_points.txt +0 -0
- {auto_editor-27.0.0.dist-info → auto_editor-27.1.0.dist-info}/licenses/LICENSE +0 -0
- {auto_editor-27.0.0.dist-info → auto_editor-27.1.0.dist-info}/top_level.txt +0 -0
auto_editor/wavfile.py
DELETED
@@ -1,310 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import io
|
4
|
-
import struct
|
5
|
-
import sys
|
6
|
-
from typing import TYPE_CHECKING, Literal
|
7
|
-
|
8
|
-
import numpy as np
|
9
|
-
|
10
|
-
PCM = 0x0001
|
11
|
-
IEEE_FLOAT = 0x0003
|
12
|
-
EXTENSIBLE = 0xFFFE
|
13
|
-
|
14
|
-
AudioData = np.memmap | np.ndarray
|
15
|
-
Endian = Literal[">", "<"] # Big Endian, Little Endian
|
16
|
-
ByteOrd = Literal["big", "little"]
|
17
|
-
|
18
|
-
if TYPE_CHECKING:
|
19
|
-
Reader = io.BufferedReader | io.BytesIO
|
20
|
-
Writer = io.BufferedWriter | io.BytesIO
|
21
|
-
|
22
|
-
|
23
|
-
class WavError(Exception):
|
24
|
-
pass
|
25
|
-
|
26
|
-
|
27
|
-
def _read_fmt_chunk(
|
28
|
-
fid: Reader, bytes_order: ByteOrd
|
29
|
-
) -> tuple[int, int, int, int, int]:
|
30
|
-
size = int.from_bytes(fid.read(4), bytes_order)
|
31
|
-
|
32
|
-
if size < 16:
|
33
|
-
raise WavError("Binary structure of wave file is not compliant")
|
34
|
-
|
35
|
-
format_tag = int.from_bytes(fid.read(2), bytes_order)
|
36
|
-
channels = int.from_bytes(fid.read(2), bytes_order)
|
37
|
-
sr = int.from_bytes(fid.read(4), bytes_order)
|
38
|
-
fid.read(4) # This is bitrate but we don't need it
|
39
|
-
block_align = int.from_bytes(fid.read(2), bytes_order)
|
40
|
-
bit_depth = int.from_bytes(fid.read(2), bytes_order)
|
41
|
-
bytes_read = 16
|
42
|
-
|
43
|
-
if format_tag == EXTENSIBLE and size >= 18:
|
44
|
-
ext_chunk_size = int.from_bytes(fid.read(2), bytes_order)
|
45
|
-
bytes_read += 2
|
46
|
-
if ext_chunk_size >= 22:
|
47
|
-
extensible_chunk_data = fid.read(22)
|
48
|
-
bytes_read += 22
|
49
|
-
raw_guid = extensible_chunk_data[6:22]
|
50
|
-
|
51
|
-
if bytes_order == "big":
|
52
|
-
tail = b"\x00\x00\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71"
|
53
|
-
else:
|
54
|
-
tail = b"\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71"
|
55
|
-
if raw_guid.endswith(tail):
|
56
|
-
format_tag = int.from_bytes(raw_guid[:4], bytes_order)
|
57
|
-
else:
|
58
|
-
raise WavError("Binary structure of wave file is not compliant")
|
59
|
-
|
60
|
-
if format_tag not in {PCM, IEEE_FLOAT}:
|
61
|
-
raise WavError(
|
62
|
-
f"Encountered unknown format tag: {format_tag:#06x}, while reading fmt chunk."
|
63
|
-
)
|
64
|
-
|
65
|
-
# move file pointer to next chunk
|
66
|
-
if size > bytes_read:
|
67
|
-
fid.read(size - bytes_read)
|
68
|
-
|
69
|
-
# fmt should always be 16, 18 or 40, but handle it just in case
|
70
|
-
_handle_pad_byte(fid, size)
|
71
|
-
|
72
|
-
return format_tag, channels, sr, block_align, bit_depth
|
73
|
-
|
74
|
-
|
75
|
-
def _read_data_chunk(
|
76
|
-
fid: Reader,
|
77
|
-
format_tag: int,
|
78
|
-
channels: int,
|
79
|
-
bit_depth: int,
|
80
|
-
en: Endian,
|
81
|
-
block_align: int,
|
82
|
-
data_size: int | None,
|
83
|
-
) -> AudioData:
|
84
|
-
bytes_order: ByteOrd = "big" if en == ">" else "little"
|
85
|
-
size = int.from_bytes(fid.read(4), bytes_order)
|
86
|
-
|
87
|
-
if data_size is not None:
|
88
|
-
# size is only 32-bits here, so get real size from header.
|
89
|
-
size = data_size
|
90
|
-
|
91
|
-
bytes_per_sample = block_align // channels
|
92
|
-
|
93
|
-
if bytes_per_sample in {3, 5, 6, 7}:
|
94
|
-
raise WavError(f"Unsupported bytes per sample: {bytes_per_sample}")
|
95
|
-
|
96
|
-
if format_tag == PCM:
|
97
|
-
if 1 <= bit_depth <= 8:
|
98
|
-
dtype = "u1" # WAVs of 8-bit integer or less are unsigned
|
99
|
-
elif bit_depth <= 64:
|
100
|
-
dtype = f"{en}i{bytes_per_sample}"
|
101
|
-
else:
|
102
|
-
raise WavError(
|
103
|
-
f"Unsupported bit depth: the WAV file has {bit_depth}-bit integer data."
|
104
|
-
)
|
105
|
-
elif format_tag == IEEE_FLOAT:
|
106
|
-
if bit_depth in {32, 64}:
|
107
|
-
dtype = f"{en}f{bytes_per_sample}"
|
108
|
-
else:
|
109
|
-
raise WavError(
|
110
|
-
f"Unsupported bit depth: the WAV file has {bit_depth}-bit floating-point data."
|
111
|
-
)
|
112
|
-
else:
|
113
|
-
raise WavError(
|
114
|
-
f"Unknown wave file format: {format_tag:#06x}. Supported formats: PCM, IEEE_FLOAT"
|
115
|
-
)
|
116
|
-
if size % 2 == 0:
|
117
|
-
n_samples = size // block_align
|
118
|
-
else:
|
119
|
-
n_samples = (size - 1) // block_align
|
120
|
-
|
121
|
-
if isinstance(fid, io.BufferedReader):
|
122
|
-
data: AudioData = np.memmap(
|
123
|
-
fid, dtype=dtype, mode="c", offset=fid.tell(), shape=(n_samples, channels)
|
124
|
-
)
|
125
|
-
fid.seek(size, 1)
|
126
|
-
else:
|
127
|
-
bytes_per_sample = np.dtype(dtype).itemsize
|
128
|
-
buffer = fid.read(n_samples * channels * bytes_per_sample)
|
129
|
-
data = np.frombuffer(buffer, dtype=dtype).reshape((n_samples, channels))
|
130
|
-
|
131
|
-
_handle_pad_byte(fid, size)
|
132
|
-
|
133
|
-
return data
|
134
|
-
|
135
|
-
|
136
|
-
def _skip_unknown_chunk(fid: Reader, en: Endian) -> None:
|
137
|
-
data = fid.read(4)
|
138
|
-
|
139
|
-
if len(data) == 4:
|
140
|
-
size = struct.unpack(f"{en}I", data)[0]
|
141
|
-
if size == 0:
|
142
|
-
raise WavError("Unknown chunk size is 0")
|
143
|
-
fid.seek(size, 1)
|
144
|
-
_handle_pad_byte(fid, size)
|
145
|
-
elif len(data) == 0:
|
146
|
-
pass # It's okay, we've hit EOF
|
147
|
-
else:
|
148
|
-
raise WavError(
|
149
|
-
f"Unknown chunk size has wrong length, expected 4, got {len(data)}"
|
150
|
-
)
|
151
|
-
|
152
|
-
|
153
|
-
def _read_rf64_chunk(fid: Reader) -> tuple[int, int, Endian]:
|
154
|
-
# https://tech.ebu.ch/docs/tech/tech3306v1_0.pdf
|
155
|
-
# https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf
|
156
|
-
|
157
|
-
heading = fid.read(12)
|
158
|
-
if heading != b"\xff\xff\xff\xffWAVEds64":
|
159
|
-
raise WavError(f"Invalid heading for rf64 chunk: {heading!r}")
|
160
|
-
|
161
|
-
chunk_size = fid.read(4)
|
162
|
-
|
163
|
-
bw_size_low = fid.read(4)
|
164
|
-
bw_size_high = fid.read(4)
|
165
|
-
|
166
|
-
en: Endian = ">" if (bw_size_high > bw_size_low) else "<"
|
167
|
-
bytes_order: ByteOrd = "big" if en == ">" else "little"
|
168
|
-
|
169
|
-
data_size_low = fid.read(4)
|
170
|
-
data_size_high = fid.read(4)
|
171
|
-
|
172
|
-
file_size = int.from_bytes(bw_size_low + bw_size_high, "little")
|
173
|
-
data_size = int.from_bytes(data_size_low + data_size_high, "little")
|
174
|
-
|
175
|
-
int_chunk_size = int.from_bytes(chunk_size, bytes_order)
|
176
|
-
if type(int_chunk_size) is not int or int_chunk_size > 40:
|
177
|
-
raise WavError("Invalid chunk size in RF64 chunk")
|
178
|
-
|
179
|
-
fid.read(40 - int_chunk_size)
|
180
|
-
|
181
|
-
return data_size, file_size, en
|
182
|
-
|
183
|
-
|
184
|
-
def _read_riff_chunk(sig: bytes, fid: Reader) -> tuple[None, int, Endian]:
|
185
|
-
en: Endian = "<" if sig == b"RIFF" else ">"
|
186
|
-
bytes_order: ByteOrd = "big" if en == ">" else "little"
|
187
|
-
|
188
|
-
file_size = int.from_bytes(fid.read(4), bytes_order) + 8
|
189
|
-
|
190
|
-
form = fid.read(4)
|
191
|
-
if form != b"WAVE":
|
192
|
-
raise WavError(f"Not a WAV file. RIFF form type is {form!r}.")
|
193
|
-
|
194
|
-
return None, file_size, en
|
195
|
-
|
196
|
-
|
197
|
-
def _handle_pad_byte(fid: Reader, size: int) -> None:
|
198
|
-
if size % 2 == 1:
|
199
|
-
fid.seek(1, 1)
|
200
|
-
|
201
|
-
|
202
|
-
def read(fid: Reader) -> tuple[int, AudioData]:
|
203
|
-
file_sig = fid.read(4)
|
204
|
-
if file_sig in {b"RIFF", b"RIFX"}:
|
205
|
-
data_size, file_size, en = _read_riff_chunk(file_sig, fid)
|
206
|
-
elif file_sig == b"RF64":
|
207
|
-
data_size, file_size, en = _read_rf64_chunk(fid)
|
208
|
-
else:
|
209
|
-
raise WavError(f"File format {file_sig!r} not supported.")
|
210
|
-
|
211
|
-
bytes_order: ByteOrd = "big" if en == ">" else "little"
|
212
|
-
fmt_chunk_received = False
|
213
|
-
|
214
|
-
while fid.tell() < file_size:
|
215
|
-
chunk_id = fid.read(4)
|
216
|
-
|
217
|
-
if not chunk_id:
|
218
|
-
raise WavError("Unexpected end of file.")
|
219
|
-
if len(chunk_id) < 4:
|
220
|
-
raise WavError(f"Incomplete chunk ID: {chunk_id!r}")
|
221
|
-
|
222
|
-
if chunk_id == b"fmt ":
|
223
|
-
fmt_chunk_received = True
|
224
|
-
format_tag, channels, sr, block_align, bit_depth = _read_fmt_chunk(
|
225
|
-
fid, bytes_order
|
226
|
-
)
|
227
|
-
elif chunk_id == b"data":
|
228
|
-
if not fmt_chunk_received:
|
229
|
-
raise WavError("No fmt chunk before data")
|
230
|
-
|
231
|
-
data = _read_data_chunk(
|
232
|
-
fid,
|
233
|
-
format_tag,
|
234
|
-
channels,
|
235
|
-
bit_depth,
|
236
|
-
en,
|
237
|
-
block_align,
|
238
|
-
data_size,
|
239
|
-
)
|
240
|
-
|
241
|
-
fid.seek(0)
|
242
|
-
return sr, data
|
243
|
-
|
244
|
-
elif chunk_id == b"\x00\x00\x00\x00":
|
245
|
-
raise WavError("Invalid chunk ID")
|
246
|
-
else:
|
247
|
-
_skip_unknown_chunk(fid, en)
|
248
|
-
|
249
|
-
raise WavError("Found no data")
|
250
|
-
|
251
|
-
|
252
|
-
def write(fid: Writer, sr: int, arr: np.ndarray) -> None:
|
253
|
-
channels = 1 if arr.ndim == 1 else arr.shape[1]
|
254
|
-
bit_depth = arr.dtype.itemsize * 8
|
255
|
-
block_align = channels * (bit_depth // 8)
|
256
|
-
data_size = arr.nbytes
|
257
|
-
total_size = 44 + data_size # Basic WAV header size + data size
|
258
|
-
|
259
|
-
if is_rf64 := total_size > 0xFFFFFFFF:
|
260
|
-
fid.write(b"RF64\xff\xff\xff\xffWAVE")
|
261
|
-
ds64_size = 28
|
262
|
-
ds64_chunk_data = (0).to_bytes(ds64_size, "little") # placeholder values
|
263
|
-
fid.write(b"ds64" + struct.pack("<I", ds64_size) + ds64_chunk_data)
|
264
|
-
else:
|
265
|
-
fid.write(b"RIFF" + struct.pack("<I", total_size - 8) + b"WAVE")
|
266
|
-
|
267
|
-
dkind = arr.dtype.kind
|
268
|
-
format_tag = IEEE_FLOAT if dkind == "f" else PCM
|
269
|
-
|
270
|
-
fmt_chunk_data = struct.pack(
|
271
|
-
"<HHIIHH", format_tag, channels, sr, 0, block_align, bit_depth
|
272
|
-
)
|
273
|
-
fid.write(b"fmt " + struct.pack("<I", len(fmt_chunk_data)) + fmt_chunk_data)
|
274
|
-
|
275
|
-
# Data chunk
|
276
|
-
fid.write(b"data")
|
277
|
-
fid.write(struct.pack("<I", 0xFFFFFFFF if is_rf64 else data_size))
|
278
|
-
|
279
|
-
if arr.dtype.byteorder == ">" or (
|
280
|
-
arr.dtype.byteorder == "=" and sys.byteorder == "big"
|
281
|
-
):
|
282
|
-
arr = arr.byteswap()
|
283
|
-
fid.write(arr.ravel().view("b").data)
|
284
|
-
|
285
|
-
if is_rf64:
|
286
|
-
end_position = fid.tell()
|
287
|
-
fid.seek(16) # Position at the start of 'ds64' chunk size
|
288
|
-
|
289
|
-
file_size = end_position - 20
|
290
|
-
fid.write(struct.pack("<I", ds64_size))
|
291
|
-
fid.write(file_size.to_bytes(8, "little") + data_size.to_bytes(8, "little"))
|
292
|
-
|
293
|
-
fid.seek(end_position)
|
294
|
-
|
295
|
-
|
296
|
-
def main() -> None:
|
297
|
-
data = np.random.rand(48000, 2)
|
298
|
-
with open("test.wav", "wb") as file:
|
299
|
-
write(file, 48_000, data)
|
300
|
-
|
301
|
-
with open("test.wav", "rb") as file:
|
302
|
-
read_sr, read_data = read(file)
|
303
|
-
|
304
|
-
assert read_sr == 48_000
|
305
|
-
assert np.array_equal(data, read_data)
|
306
|
-
print("success")
|
307
|
-
|
308
|
-
|
309
|
-
if __name__ == "__main__":
|
310
|
-
main()
|
File without changes
|
File without changes
|
File without changes
|