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/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()