lvthn-codec 1.0.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.
codec.py ADDED
@@ -0,0 +1,149 @@
1
+ import sys
2
+ import time
3
+ from pathlib import Path
4
+ import numpy as np
5
+ from scipy.io import wavfile
6
+
7
+ from core import container, audio, image
8
+
9
+ PROFILES = {
10
+ 'default': {'delta': 0.18, 'snr': 24.0},
11
+ 'compact': {'delta': 0.25, 'snr': 20.0},
12
+ 'detail': {'delta': 0.05, 'snr': 24.0},
13
+ 'speech': {'delta': 0.10, 'snr': 20.0},
14
+ 'master': {'delta': 0.03, 'snr': 30.0},
15
+ }
16
+
17
+ IMG_PROFILES = {
18
+ 'fast': {'quality': 85, 'level': 1},
19
+ 'default': {'quality': 92, 'level': 12},
20
+ 'max': {'quality': 98, 'level': 19},
21
+ }
22
+
23
+ def encode_pcm(pcm, sr, ch, delta, target_snr_db):
24
+ payload = audio.encode_audio(pcm, sr, ch, delta, target_snr_db)
25
+ return container.wrap(container.MEDIA_AUDIO, payload)
26
+
27
+ def decode_lvn(data):
28
+ media_type, payload = container.unwrap(data)
29
+ if media_type == container.MEDIA_AUDIO:
30
+ return audio.decode_audio(payload)
31
+ elif media_type == container.MEDIA_IMAGE:
32
+ return image.decode_image(payload)
33
+ else:
34
+ raise ValueError(f'Unsupported media_type: 0x{media_type:02x}')
35
+
36
+ def decode_lvn_info(data):
37
+ media_type, payload = container.unwrap(data)
38
+ if media_type == container.MEDIA_AUDIO:
39
+ info = audio.get_audio_info(payload)
40
+ info['media_type'] = media_type
41
+ info['total'] = len(data)
42
+ return info
43
+ elif media_type == container.MEDIA_IMAGE:
44
+ info = image.get_image_info(payload)
45
+ info['media_type'] = media_type
46
+ info['total'] = len(data)
47
+ return info
48
+ return dict(media_type=media_type, total=len(data))
49
+
50
+ def read_wav(path):
51
+ sr, d = wavfile.read(path)
52
+ if d.dtype == np.int16: d = d.astype(np.float64) / 32768.0
53
+ elif d.dtype == np.int32: d = d.astype(np.float64) / 2147483648.0
54
+ elif d.dtype == np.uint8: d = (d.astype(np.float64) - 128.0) / 128.0
55
+ else: d = d.astype(np.float64)
56
+ if d.ndim == 1: d = d[:, None]
57
+ return d, sr
58
+
59
+ def write_wav(path, pcm, sr):
60
+ wavfile.write(path, sr, np.clip(pcm * 32767, -32768, 32767).astype(np.int16))
61
+
62
+ def read_image(path):
63
+ from PIL import Image
64
+ img = Image.open(path)
65
+ if img.mode == 'RGBA':
66
+ return np.array(img), 4
67
+ else:
68
+ return np.array(img.convert('RGB')), 3
69
+
70
+ def write_image(path, pixels, channels):
71
+ from PIL import Image
72
+ img = Image.fromarray(pixels)
73
+ img.save(path)
74
+
75
+ def main():
76
+ if len(sys.argv) < 2:
77
+ print('LVTHN-CODEC')
78
+ print('Commands: encode | decode | info | encode-img | decode-img | profiles')
79
+ sys.exit(1)
80
+ cmd, args = sys.argv[1], sys.argv[2:]
81
+
82
+ if cmd == 'encode':
83
+ if len(args) < 2: sys.exit('usage: encode in.wav out.lvn [profile|delta] [target_snr_db]')
84
+ pcm, sr = read_wav(args[0])
85
+ arg2 = args[2] if len(args) > 2 else 'default'
86
+ if arg2 in PROFILES:
87
+ delta = PROFILES[arg2]['delta']
88
+ tsnr = PROFILES[arg2]['snr']
89
+ print(f'[audio profile: {arg2}] delta={delta} snr={tsnr}')
90
+ else:
91
+ delta = float(arg2)
92
+ tsnr = float(args[3]) if len(args) > 3 else 24.0
93
+
94
+ t0 = time.time()
95
+ data = encode_pcm(pcm, sr, pcm.shape[1], delta, target_snr_db=tsnr)
96
+ Path(args[1]).write_bytes(data)
97
+ print(f'OK {args[1]} {len(data)} bytes ({pcm.size * 2 / len(data):.2f}x vs PCM) encode={time.time() - t0:.2f}s')
98
+
99
+ elif cmd == 'decode':
100
+ if len(args) < 2: sys.exit('usage: decode in.lvn out.wav')
101
+ pcm, sr, ch, n = decode_lvn(Path(args[0]).read_bytes())
102
+ write_wav(args[1], pcm, sr)
103
+ print(f'OK {args[1]} {n} samples, {ch} ch, {sr} Hz')
104
+
105
+ elif cmd == 'encode-img':
106
+ if len(args) < 2: sys.exit('usage: encode-img in.png out.lvn [profile|quality] [level]')
107
+ pixels, ch = read_image(args[0])
108
+ arg2 = args[2] if len(args) > 2 else 'default'
109
+ if arg2 in IMG_PROFILES:
110
+ q = IMG_PROFILES[arg2]['quality']
111
+ lvl = IMG_PROFILES[arg2]['level']
112
+ print(f'[image profile: {arg2}] quality={q} level={lvl}')
113
+ else:
114
+ q = int(arg2)
115
+ lvl = int(args[3]) if len(args) > 3 else 19
116
+
117
+ t0 = time.time()
118
+ payload = image.encode_image(pixels, pixels.shape[1], pixels.shape[0], ch, q, lvl)
119
+ data = container.wrap(container.MEDIA_IMAGE, payload)
120
+ Path(args[1]).write_bytes(data)
121
+ print(f'OK {args[1]} {len(data)} bytes encode={time.time() - t0:.2f}s')
122
+
123
+ elif cmd == 'decode-img':
124
+ if len(args) < 2: sys.exit('usage: decode-img in.lvn out.png')
125
+ mt, payload = container.unwrap(Path(args[0]).read_bytes())
126
+ if mt != container.MEDIA_IMAGE: sys.exit('Not an image')
127
+ pixels = image.decode_image(payload)
128
+ write_image(args[1], pixels, pixels.shape[2] if len(pixels.shape) > 2 else 1)
129
+ print(f'OK {args[1]}')
130
+
131
+ elif cmd == 'info':
132
+ if not args: sys.exit('usage: info file.lvn')
133
+ info = decode_lvn_info(Path(args[0]).read_bytes())
134
+ if info['media_type'] == container.MEDIA_AUDIO:
135
+ print(f"LVTHN v1 audio {info['sr']} Hz {info['ch']} ch {info['n_samples'] / info['sr']:.2f}s delta={info['delta']:.4f}")
136
+ print(f" target_snr_db: {info['target_snr_db']:.1f} use_ms: {info['use_ms']} total: {info['total']}")
137
+ elif info['media_type'] == container.MEDIA_IMAGE:
138
+ print(f"LVTHN v1 image {info['width']}x{info['height']} alpha={info['has_alpha']} quality={info['quality']:.0f} total: {info['total']}")
139
+
140
+ elif cmd == 'profiles':
141
+ print('Audio profiles:')
142
+ for name, p in PROFILES.items():
143
+ print(f' {name:10s} delta={p["delta"]:<6} snr={p["snr"]}')
144
+ print('Image profiles:')
145
+ for name, p in IMG_PROFILES.items():
146
+ print(f' {name:10s} quality={p["quality"]:<4} level={p["level"]}')
147
+
148
+ if __name__ == '__main__':
149
+ main()
core/__init__.py ADDED
File without changes
core/audio.py ADDED
@@ -0,0 +1,330 @@
1
+ import struct
2
+ import numpy as np
3
+ from numba import njit
4
+
5
+ FRAME_SIZE = 1024
6
+ HOP = FRAME_SIZE // 2
7
+ N_COEFFS = FRAME_SIZE // 2
8
+
9
+ DEFAULT_DELTA = 0.18
10
+ DEFAULT_TARGET_SNR_DB = 24.0
11
+ DEFAULT_DELTA_CURVE = np.array([1.0, 1.0, 1.1, 1.3, 1.6, 2.0, 2.5, 3.0], dtype=np.float32)
12
+ MIN_BAND_SIZE = 3
13
+ SCALE_MIN = -32
14
+ SCALE_MAX = 31
15
+ SCALE_BITS = 6
16
+ ATH_OFFSET_DB = 96.0
17
+
18
+ AUDIO_HDR_FMT = '<IBHQf8ffBI'
19
+ AUDIO_HDR_SIZE = struct.calcsize(AUDIO_HDR_FMT)
20
+
21
+ _WINDOW = np.sin(np.pi * (np.arange(FRAME_SIZE) + 0.5) / FRAME_SIZE).astype(np.float64)
22
+ _n = np.arange(FRAME_SIZE, dtype=np.float64)[:, None]
23
+ _k = np.arange(N_COEFFS, dtype=np.float64)[None, :]
24
+ _MDCT_MATRIX = np.cos(np.pi / N_COEFFS * (_n + 0.5 + N_COEFFS / 2) * (_k + 0.5))
25
+ _IMDCT_MATRIX = (_MDCT_MATRIX.T * (2.0 / N_COEFFS)).astype(np.float64)
26
+
27
+ @njit(cache=True, inline='always')
28
+ def _get_bit(buf, pos):
29
+ return (buf[pos >> 3] >> (7 - (pos & 7))) & 1
30
+
31
+ @njit(cache=True, inline='always')
32
+ def _set_bit(buf, pos, val):
33
+ if val:
34
+ buf[pos >> 3] |= (1 << (7 - (pos & 7)))
35
+
36
+ @njit(cache=True)
37
+ def _encode_frame_channel(quantized, band_starts, band_ends, scales, out_buf, bit_pos):
38
+ n_bands = len(band_starts)
39
+ n_coeffs = len(quantized)
40
+ last_nz = 0
41
+ for i in range(n_coeffs):
42
+ if quantized[i] != 0:
43
+ last_nz = i + 1
44
+ for i in range(9, -1, -1):
45
+ _set_bit(out_buf, bit_pos, (last_nz >> i) & 1)
46
+ bit_pos += 1
47
+ if last_nz == 0:
48
+ return bit_pos
49
+
50
+ band_present = np.zeros(n_bands, dtype=np.int32)
51
+ band_rice_k = np.zeros(n_bands, dtype=np.int32)
52
+ for b in range(n_bands):
53
+ s = band_starts[b]
54
+ e = band_ends[b]
55
+ if s >= last_nz:
56
+ break
57
+ be = min(e, last_nz)
58
+ any_nz = False
59
+ total_zz = 0
60
+ for i in range(s, be):
61
+ v = quantized[i]
62
+ if v != 0:
63
+ any_nz = True
64
+ total_zz += (v << 1) if v >= 0 else ((-v) << 1) - 1
65
+ if any_nz:
66
+ band_present[b] = 1
67
+ mean = total_zz / (be - s)
68
+ k = 0
69
+ m = mean
70
+ while m > 2.0 and k < 15:
71
+ m /= 2.0
72
+ k += 1
73
+ band_rice_k[b] = k
74
+
75
+ for b in range(n_bands):
76
+ s = band_starts[b]
77
+ if s >= last_nz:
78
+ break
79
+ _set_bit(out_buf, bit_pos, band_present[b])
80
+ bit_pos += 1
81
+
82
+ for b in range(n_bands):
83
+ s = band_starts[b]
84
+ e = band_ends[b]
85
+ if s >= last_nz:
86
+ break
87
+ if band_present[b] == 0:
88
+ continue
89
+ scale_enc = scales[b] - SCALE_MIN
90
+ for i in range(SCALE_BITS - 1, -1, -1):
91
+ _set_bit(out_buf, bit_pos, (scale_enc >> i) & 1)
92
+ bit_pos += 1
93
+ k = band_rice_k[b]
94
+ for i in range(3, -1, -1):
95
+ _set_bit(out_buf, bit_pos, (k >> i) & 1)
96
+ bit_pos += 1
97
+ be = min(e, last_nz)
98
+ for i in range(s, be):
99
+ v = quantized[i]
100
+ zz = (v << 1) if v >= 0 else ((-v) << 1) - 1
101
+ q = zz >> k
102
+ r = zz - (q << k)
103
+ for _ in range(q):
104
+ bit_pos += 1
105
+ _set_bit(out_buf, bit_pos, 1)
106
+ bit_pos += 1
107
+ for i2 in range(k - 1, -1, -1):
108
+ _set_bit(out_buf, bit_pos, (r >> i2) & 1)
109
+ bit_pos += 1
110
+ return bit_pos
111
+
112
+ @njit(cache=True)
113
+ def _decode_frame_channel(in_buf, bit_pos, quantized, scales, band_starts, band_ends):
114
+ n_bands = len(band_starts)
115
+ n_coeffs = len(quantized)
116
+ for i in range(n_coeffs):
117
+ quantized[i] = 0
118
+ for i in range(n_bands):
119
+ scales[i] = 0
120
+
121
+ last_nz = 0
122
+ for i in range(10):
123
+ last_nz = (last_nz << 1) | _get_bit(in_buf, bit_pos)
124
+ bit_pos += 1
125
+ if last_nz == 0:
126
+ return bit_pos
127
+
128
+ present = np.zeros(n_bands, dtype=np.int32)
129
+ for b in range(n_bands):
130
+ s = band_starts[b]
131
+ if s >= last_nz:
132
+ break
133
+ present[b] = _get_bit(in_buf, bit_pos)
134
+ bit_pos += 1
135
+
136
+ for b in range(n_bands):
137
+ s = band_starts[b]
138
+ e = band_ends[b]
139
+ if s >= last_nz:
140
+ break
141
+ if present[b] == 0:
142
+ continue
143
+ scale_enc = 0
144
+ for i in range(SCALE_BITS):
145
+ scale_enc = (scale_enc << 1) | _get_bit(in_buf, bit_pos)
146
+ bit_pos += 1
147
+ scales[b] = scale_enc + SCALE_MIN
148
+ k = 0
149
+ for i in range(4):
150
+ k = (k << 1) | _get_bit(in_buf, bit_pos)
151
+ bit_pos += 1
152
+ be = min(e, last_nz)
153
+ for i in range(s, be):
154
+ q = 0
155
+ while _get_bit(in_buf, bit_pos) == 0:
156
+ q += 1
157
+ bit_pos += 1
158
+ bit_pos += 1
159
+ r = 0
160
+ for i2 in range(k):
161
+ r = (r << 1) | _get_bit(in_buf, bit_pos)
162
+ bit_pos += 1
163
+ zz = (q << k) | r
164
+ quantized[i] = (zz >> 1) if (zz & 1) == 0 else -((zz + 1) >> 1)
165
+ return bit_pos
166
+
167
+ @njit(cache=True)
168
+ def _encode_body_njit(q_flat, band_starts, band_ends, scales_flat, out_buf):
169
+ bit_pos = 0
170
+ for fc in range(q_flat.shape[0]):
171
+ bit_pos = _encode_frame_channel(q_flat[fc], band_starts, band_ends, scales_flat[fc], out_buf, bit_pos)
172
+ return bit_pos
173
+
174
+ @njit(cache=True)
175
+ def _decode_body_njit(body_arr, q_flat, scales_flat, band_starts, band_ends):
176
+ bit_pos = 0
177
+ for fc in range(q_flat.shape[0]):
178
+ bit_pos = _decode_frame_channel(body_arr, bit_pos, q_flat[fc], scales_flat[fc], band_starts, band_ends)
179
+ return bit_pos
180
+
181
+ def _make_bark_bands(sr, n_coeffs, min_size=MIN_BAND_SIZE):
182
+ bark_hz = np.array([0, 100, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720, 2000, 2320, 2700, 3150, 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500, sr / 2], dtype=np.float64)
183
+ bin_centers = (np.arange(n_coeffs) + 0.5) * (sr / (2 * n_coeffs))
184
+ raw = []
185
+ for i in range(len(bark_hz) - 1):
186
+ start = int(np.searchsorted(bin_centers, bark_hz[i], side='left'))
187
+ end = int(np.searchsorted(bin_centers, bark_hz[i + 1], side='left'))
188
+ if end > start:
189
+ raw.append((start, end))
190
+ if raw:
191
+ if raw[0][0] > 0: raw[0] = (0, raw[0][1])
192
+ if raw[-1][1] < n_coeffs: raw[-1] = (raw[-1][0], n_coeffs)
193
+ merged = []
194
+ acc = None
195
+ for s, e in raw:
196
+ if acc is None:
197
+ acc = [s, e]
198
+ elif acc[1] - acc[0] < min_size:
199
+ acc[1] = e
200
+ else:
201
+ merged.append((acc[0], acc[1]))
202
+ acc = [s, e]
203
+ if acc is not None:
204
+ merged.append((acc[0], acc[1]))
205
+ return merged
206
+
207
+ def _ath_thresholds(sr, n_coeffs, offset_db=ATH_OFFSET_DB):
208
+ freqs = np.maximum(np.arange(n_coeffs) * sr / FRAME_SIZE, 20.0)
209
+ f_khz = freqs / 1000.0
210
+ ath_db = np.minimum(3.64 * f_khz ** (-0.8) - 6.5 * np.exp(-0.6 * (f_khz - 3.3) ** 2) + 1e-3 * f_khz ** 4, 75.0)
211
+ return (10.0 ** ((ath_db - offset_db) / 10.0)).astype(np.float64)
212
+
213
+ def _encode_body(coeffs, sr, delta, curve_bands, band_starts, band_ends, target_snr_db):
214
+ n_frames, _, ch = coeffs.shape
215
+ n_bands = len(band_starts)
216
+
217
+ ath_band = np.array([np.mean(_ath_thresholds(sr, N_COEFFS)[s:e]) for s, e in zip(band_starts, band_ends)])
218
+
219
+ energies = np.zeros((n_frames, n_bands, ch), dtype=np.float64)
220
+ for b in range(n_bands):
221
+ energies[:, b, :] = np.mean(coeffs[:, band_starts[b]:band_ends[b], :] ** 2, axis=1)
222
+
223
+ mask_smooth = energies.copy()
224
+ for shift in (1, 2, 3):
225
+ mask_smooth = np.maximum(mask_smooth, np.roll(energies, shift, axis=0))
226
+ mask_smooth = np.maximum(mask_smooth, np.roll(energies, -shift, axis=0))
227
+
228
+ spread = np.zeros_like(mask_smooth)
229
+ for b in range(n_bands):
230
+ acc = np.zeros((n_frames, ch), dtype=np.float64)
231
+ for db in range(-4, 5):
232
+ b2 = b + db
233
+ if 0 <= b2 < n_bands:
234
+ acc += mask_smooth[:, b2, :] * (1.0 - abs(db) / 5.0)
235
+ spread[:, b, :] = acc
236
+
237
+ mask = spread + ath_band[None, :, None]
238
+ base_deltas = delta * curve_bands.astype(np.float64)
239
+ delta_max = np.sqrt(mask / 20.0)
240
+ delta_floor = base_deltas[None, :, None] / np.geomspace(4.0, 0.5, n_bands)[None, :, None]
241
+ deltas = np.maximum(delta_floor, np.minimum(base_deltas[None, :, None], delta_max))
242
+
243
+ with np.errstate(divide='ignore', invalid='ignore'):
244
+ scale = np.round(2 * np.log2(deltas / base_deltas[None, :, None])).astype(np.int32)
245
+ np.clip(scale, SCALE_MIN, SCALE_MAX, out=scale)
246
+ deltas = base_deltas[None, :, None] * (2.0 ** (scale / 2.0))
247
+
248
+ quantized = np.zeros_like(coeffs, dtype=np.int32)
249
+ for b in range(n_bands):
250
+ d = deltas[:, b, :]
251
+ quantized[:, band_starts[b]:band_ends[b], :] = np.round(coeffs[:, band_starts[b]:band_ends[b], :] / d[:, None, :])
252
+ np.clip(quantized, -32767, 32767, out=quantized)
253
+
254
+ q_flat = np.ascontiguousarray(quantized.transpose(0, 2, 1).reshape(n_frames * ch, N_COEFFS))
255
+ scales_flat = np.ascontiguousarray(scale.transpose(0, 2, 1).reshape(n_frames * ch, n_bands))
256
+
257
+ out_buf = np.zeros(n_frames * ch * 3072, dtype=np.uint8)
258
+ bit_pos = _encode_body_njit(q_flat, band_starts, band_ends, scales_flat, out_buf)
259
+ return out_buf[:(bit_pos + 7) // 8].tobytes(), bit_pos
260
+
261
+ def encode_audio(pcm, sr, ch, delta, target_snr_db):
262
+ if pcm.ndim == 1:
263
+ pcm = pcm[:, None]
264
+ n_samples = pcm.shape[0]
265
+ n_frames = (n_samples + HOP - 1) // HOP + 1
266
+
267
+ pcm_padded = np.zeros(((n_frames + 1) * HOP, ch), dtype=np.float64)
268
+ pcm_padded[HOP:HOP + n_samples] = pcm
269
+
270
+ idx = np.arange(n_frames)[:, None] * HOP + np.arange(FRAME_SIZE)[None, :]
271
+ coeffs_L = (pcm_padded[idx, 0] * _WINDOW[None, :]) @ _MDCT_MATRIX
272
+
273
+ if ch == 2:
274
+ coeffs_R = (pcm_padded[idx, 1] * _WINDOW[None, :]) @ _MDCT_MATRIX
275
+ coeffs_lr = np.stack([coeffs_L, coeffs_R], axis=2)
276
+ coeffs_ms = np.stack([(coeffs_L + coeffs_R) * 0.5, (coeffs_L - coeffs_R) * 0.5], axis=2)
277
+ else:
278
+ coeffs_lr, coeffs_ms = coeffs_L[:, :, None], None
279
+
280
+ bark_bands = _make_bark_bands(sr, N_COEFFS)
281
+ band_starts = np.array([s for s, _ in bark_bands], dtype=np.int32)
282
+ band_ends = np.array([e for _, e in bark_bands], dtype=np.int32)
283
+ curve_bands = np.interp(np.linspace(0, 1, len(bark_bands)), np.linspace(0, 1, 8), DEFAULT_DELTA_CURVE).astype(np.float32)
284
+
285
+ body_lr, bits_lr = _encode_body(coeffs_lr, sr, delta, curve_bands, band_starts, band_ends, target_snr_db)
286
+
287
+ if ch == 2:
288
+ body_ms, bits_ms = _encode_body(coeffs_ms, sr, delta, curve_bands, band_starts, band_ends, target_snr_db)
289
+ use_ms, body = (1, body_ms) if bits_ms < bits_lr else (0, body_lr)
290
+ else:
291
+ use_ms, body = 0, body_lr
292
+
293
+ hdr = struct.pack(AUDIO_HDR_FMT, sr, ch, FRAME_SIZE, n_samples, float(delta), *[float(x) for x in DEFAULT_DELTA_CURVE], float(target_snr_db), use_ms, len(body))
294
+ return hdr + body
295
+
296
+ def decode_audio(payload):
297
+ (sr, ch, fs, n_samples, delta, c0, c1, c2, c3, c4, c5, c6, c7, target_snr_db, use_ms, body_len) = struct.unpack_from(AUDIO_HDR_FMT, payload, 0)
298
+
299
+ body = payload[AUDIO_HDR_SIZE:AUDIO_HDR_SIZE + body_len]
300
+ n_frames = (n_samples + HOP - 1) // HOP + 1
301
+
302
+ bark_bands = _make_bark_bands(sr, N_COEFFS)
303
+ band_starts = np.array([s for s, _ in bark_bands], dtype=np.int32)
304
+ band_ends = np.array([e for _, e in bark_bands], dtype=np.int32)
305
+
306
+ curve_orig = np.array([c0, c1, c2, c3, c4, c5, c6, c7], dtype=np.float32)
307
+ curve_bands = np.interp(np.linspace(0, 1, len(bark_bands)), np.linspace(0, 1, 8), curve_orig).astype(np.float32)
308
+
309
+ q_flat = np.zeros((n_frames * ch, N_COEFFS), dtype=np.int32)
310
+ scales_flat = np.zeros((n_frames * ch, len(bark_bands)), dtype=np.int32)
311
+ _decode_body_njit(np.frombuffer(body, dtype=np.uint8), q_flat, scales_flat, band_starts, band_ends)
312
+
313
+ coeffs = np.zeros((n_frames, N_COEFFS, ch), dtype=np.float64)
314
+ for b in range(len(bark_bands)):
315
+ d = delta * curve_bands[b] * (2.0 ** (scales_flat[:, b].reshape(n_frames, ch) / 2.0))
316
+ q_band = q_flat[:, band_starts[b]:band_ends[b]].reshape(n_frames, ch, band_ends[b] - band_starts[b]).transpose(0, 2, 1)
317
+ coeffs[:, band_starts[b]:band_ends[b], :] = q_band * d[:, None, :]
318
+
319
+ frames = (coeffs.transpose(0, 2, 1).reshape(-1, N_COEFFS) @ _IMDCT_MATRIX).reshape(n_frames, ch, FRAME_SIZE).transpose(0, 2, 1) * _WINDOW[None, :, None]
320
+
321
+ pcm_enc = np.zeros(((n_frames + 1) * HOP, ch), dtype=np.float64)
322
+ for f in range(n_frames):
323
+ pcm_enc[f * HOP : f * HOP + FRAME_SIZE] += frames[f]
324
+
325
+ pcm_padded = np.stack([pcm_enc[:, 0] + pcm_enc[:, 1], pcm_enc[:, 0] - pcm_enc[:, 1]], axis=1) if use_ms and ch == 2 else pcm_enc
326
+ return np.clip(pcm_padded[HOP:HOP + n_samples], -1.0, 1.0), sr, ch, n_samples
327
+
328
+ def get_audio_info(payload):
329
+ (sr, ch, _, n_samples, delta, _, _, _, _, _, _, _, _, target_snr_db, use_ms, body_len) = struct.unpack_from(AUDIO_HDR_FMT, payload, 0)
330
+ return dict(sr=sr, ch=ch, n_samples=n_samples, delta=delta, target_snr_db=target_snr_db, use_ms=use_ms, body_len=body_len)
core/container.py ADDED
@@ -0,0 +1,25 @@
1
+ import struct
2
+
3
+ MAGIC = b'LVTHN'
4
+ VERSION = 1
5
+
6
+ MEDIA_AUDIO = 0x01
7
+ MEDIA_IMAGE = 0x02
8
+ MEDIA_VIDEO = 0x03
9
+
10
+ HDR_FMT = '<5sHB'
11
+ HDR_SIZE = struct.calcsize(HDR_FMT)
12
+
13
+ def wrap(media_type, payload):
14
+ header = struct.pack(HDR_FMT, MAGIC, VERSION, media_type)
15
+ return header + payload
16
+
17
+ def unwrap(data):
18
+ if len(data) < HDR_SIZE:
19
+ raise ValueError('File too small')
20
+ magic, version, media_type = struct.unpack_from(HDR_FMT, data, 0)
21
+ if magic != MAGIC:
22
+ raise ValueError('Not a Leviathan file')
23
+ if version != VERSION:
24
+ raise ValueError(f'Unsupported version {version}')
25
+ return media_type, data[HDR_SIZE:]
core/image.py ADDED
@@ -0,0 +1,267 @@
1
+ import struct
2
+ import numpy as np
3
+ from scipy.fft import dctn, idctn
4
+ import zstandard as zstd
5
+ from numba import njit
6
+
7
+ IMG_HDR_FMT = '<IIBBf'
8
+ IMG_HDR_SIZE = struct.calcsize(IMG_HDR_FMT)
9
+
10
+ ZIGZAG_ORDER = np.array([
11
+ 0, 1, 8, 16, 9, 2, 3, 10,
12
+ 17, 24, 32, 25, 18, 11, 4, 5,
13
+ 12, 19, 26, 33, 40, 48, 41, 34,
14
+ 27, 20, 13, 6, 7, 14, 21, 28,
15
+ 35, 42, 49, 56, 57, 50, 43, 36,
16
+ 29, 22, 15, 23, 30, 37, 44, 51,
17
+ 58, 59, 52, 45, 38, 31, 39, 46,
18
+ 53, 60, 61, 54, 47, 55, 62, 63
19
+ ], dtype=np.int32)
20
+
21
+ @njit(cache=True)
22
+ def _rgb_to_oklab_njit(rgb, out):
23
+ h, w = rgb.shape[0], rgb.shape[1]
24
+ for i in range(h):
25
+ for j in range(w):
26
+ r = rgb[i, j, 0] / 255.0
27
+ g = rgb[i, j, 1] / 255.0
28
+ b = rgb[i, j, 2] / 255.0
29
+
30
+ if r > 0.04045: r = ((r + 0.055) / 1.055) ** 2.4
31
+ else: r = r / 12.92
32
+ if g > 0.04045: g = ((g + 0.055) / 1.055) ** 2.4
33
+ else: g = g / 12.92
34
+ if b > 0.04045: b = ((b + 0.055) / 1.055) ** 2.4
35
+ else: b = b / 12.92
36
+
37
+ l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b
38
+ m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b
39
+ s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b
40
+
41
+ l = (l ** (1.0/3.0)) if l > 0 else 0.0
42
+ m = (m ** (1.0/3.0)) if m > 0 else 0.0
43
+ s = (s ** (1.0/3.0)) if s > 0 else 0.0
44
+
45
+ L = 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s
46
+ a = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s
47
+ bb = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s
48
+
49
+ out[i, j, 0] = L * 255.0
50
+ out[i, j, 1] = a * 255.0 + 128.0
51
+ out[i, j, 2] = bb * 255.0 + 128.0
52
+
53
+ @njit(cache=True)
54
+ def _oklab_to_rgb_njit(oklab, out):
55
+ h, w = oklab.shape[0], oklab.shape[1]
56
+ for i in range(h):
57
+ for j in range(w):
58
+ L = oklab[i, j, 0] / 255.0
59
+ a = (oklab[i, j, 1] - 128.0) / 255.0
60
+ b = (oklab[i, j, 2] - 128.0) / 255.0
61
+
62
+ l = L + 0.3963377774 * a + 0.2158037573 * b
63
+ m = L - 0.1055613458 * a - 0.0638541728 * b
64
+ s = L - 0.0894841775 * a - 1.2914855480 * b
65
+
66
+ l = l ** 3
67
+ m = m ** 3
68
+ s = s ** 3
69
+
70
+ r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s
71
+ g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s
72
+ bb = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
73
+
74
+ if r > 0.0031308: r = 1.055 * (r ** (1.0/2.4)) - 0.055
75
+ else: r = 12.92 * r
76
+ if g > 0.0031308: g = 1.055 * (g ** (1.0/2.4)) - 0.055
77
+ else: g = 12.92 * g
78
+ if bb > 0.0031308: bb = 1.055 * (bb ** (1.0/2.4)) - 0.055
79
+ else: bb = 12.92 * bb
80
+
81
+ r = max(0.0, min(1.0, r))
82
+ g = max(0.0, min(1.0, g))
83
+ bb = max(0.0, min(1.0, bb))
84
+
85
+ out[i, j, 0] = int(r * 255.0 + 0.5)
86
+ out[i, j, 1] = int(g * 255.0 + 0.5)
87
+ out[i, j, 2] = int(bb * 255.0 + 0.5)
88
+
89
+ @njit(cache=True)
90
+ def _rle_encode_all_fast(zigzag_blocks, out_pairs_1d):
91
+ n_blocks = zigzag_blocks.shape[0]
92
+ out_pos = 0
93
+ for b in range(n_blocks):
94
+ run = 0
95
+ for k in range(64):
96
+ v = zigzag_blocks[b, k]
97
+ if v == 0:
98
+ run += 1
99
+ else:
100
+ while run > 15:
101
+ out_pairs_1d[out_pos] = 15
102
+ out_pairs_1d[out_pos+1] = 0
103
+ out_pos += 2
104
+ run -= 16
105
+ out_pairs_1d[out_pos] = run
106
+ out_pairs_1d[out_pos+1] = v
107
+ out_pos += 2
108
+ run = 0
109
+ if run > 0:
110
+ out_pairs_1d[out_pos] = 0
111
+ out_pairs_1d[out_pos+1] = 0
112
+ out_pos += 2
113
+ return out_pos // 2
114
+
115
+ @njit(cache=True)
116
+ def _rle_decode_all_fast(pairs_1d, n_pairs, n_blocks, out_zigzag):
117
+ idx = 0
118
+ for b in range(n_blocks):
119
+ for k in range(64):
120
+ out_zigzag[b, k] = 0
121
+ pos = 0
122
+ while pos < 64 and idx < n_pairs:
123
+ run = pairs_1d[idx*2]
124
+ val = pairs_1d[idx*2+1]
125
+ idx += 1
126
+ if val == 0 and run == 0:
127
+ break
128
+ pos += run
129
+ if pos < 64:
130
+ out_zigzag[b, pos] = val
131
+ pos += 1
132
+ return idx
133
+
134
+ def _get_q_matrix(quality, is_chroma):
135
+ q = np.clip(quality, 1, 100)
136
+ step = (100 - q) / 2.0 + 1.0
137
+ if is_chroma:
138
+ step *= 1.5
139
+ return np.ones((8, 8), dtype=np.float32) * step
140
+
141
+ def _encode_channel_fast(channel, q_matrix):
142
+ h, w = channel.shape
143
+ pad_h = (8 - h % 8) % 8
144
+ pad_w = (8 - w % 8) % 8
145
+ padded = np.pad(channel, ((0, pad_h), (0, pad_w)), mode='edge') if (pad_h or pad_w) else channel
146
+
147
+ h_blocks = padded.shape[0] // 8
148
+ w_blocks = padded.shape[1] // 8
149
+
150
+ blocks = np.ascontiguousarray(padded.reshape(h_blocks, 8, w_blocks, 8).transpose(0, 2, 1, 3))
151
+ dct_blocks = dctn(blocks, type=2, norm='ortho', axes=(-2, -1))
152
+ quantized = np.round(dct_blocks / q_matrix).astype(np.int32)
153
+
154
+ flat = quantized.reshape(-1, 64)
155
+ zigzag = np.ascontiguousarray(flat[:, ZIGZAG_ORDER])
156
+
157
+ n_blocks = zigzag.shape[0]
158
+ max_pairs = n_blocks * 65
159
+ out_pairs = np.zeros(max_pairs * 2, dtype=np.int32)
160
+ n_pairs = _rle_encode_all_fast(zigzag, out_pairs)
161
+
162
+ return out_pairs[:n_pairs * 2]
163
+
164
+ def _decode_channel_fast(pairs_1d, q_matrix, h, w):
165
+ pad_h = (8 - h % 8) % 8
166
+ pad_w = (8 - w % 8) % 8
167
+ h_blocks = (h + pad_h) // 8
168
+ w_blocks = (w + pad_w) // 8
169
+ n_blocks = h_blocks * w_blocks
170
+
171
+ zigzag = np.zeros((n_blocks, 64), dtype=np.int32)
172
+ n_pairs = len(pairs_1d) // 2
173
+ _rle_decode_all_fast(pairs_1d, n_pairs, n_blocks, zigzag)
174
+
175
+ flat = np.zeros((n_blocks, 64), dtype=np.int32)
176
+ flat[:, ZIGZAG_ORDER] = zigzag
177
+
178
+ blocks = flat.reshape(h_blocks, w_blocks, 8, 8).astype(np.float32) * q_matrix
179
+ spatial_blocks = idctn(blocks, type=2, norm='ortho', axes=(-2, -1))
180
+
181
+ padded = spatial_blocks.transpose(0, 2, 1, 3).reshape(h_blocks * 8, w_blocks * 8)
182
+ return padded[:h, :w]
183
+
184
+ def encode_image(pixels, width, height, channels=3, quality=90, level=19):
185
+ has_alpha = (channels == 4)
186
+ flags = 1 if has_alpha else 0
187
+
188
+ if has_alpha:
189
+ rgb_pixels = pixels[:, :, :3]
190
+ alpha_channel = pixels[:, :, 3].astype(np.float32)
191
+ else:
192
+ rgb_pixels = pixels
193
+
194
+ oklab = np.zeros((height, width, 3), dtype=np.float32)
195
+ _rgb_to_oklab_njit(rgb_pixels, oklab)
196
+
197
+ q_luma = _get_q_matrix(quality, is_chroma=False)
198
+ q_chroma = _get_q_matrix(quality, is_chroma=True)
199
+ q_alpha = _get_q_matrix(quality, is_chroma=False)
200
+
201
+ raw_pairs = []
202
+ raw_pairs.append(_encode_channel_fast(oklab[:, :, 0], q_luma))
203
+ raw_pairs.append(_encode_channel_fast(oklab[:, :, 1], q_chroma))
204
+ raw_pairs.append(_encode_channel_fast(oklab[:, :, 2], q_chroma))
205
+ if has_alpha:
206
+ raw_pairs.append(_encode_channel_fast(alpha_channel, q_alpha))
207
+
208
+ compressed_channels = []
209
+ cctx = zstd.ZstdCompressor(level=level, threads=-1, write_content_size=True)
210
+ for pairs in raw_pairs:
211
+ compressed_channels.append(cctx.compress(pairs.tobytes()))
212
+
213
+ comp_lens = [len(c) for c in compressed_channels]
214
+
215
+ header = struct.pack(IMG_HDR_FMT, width, height, channels, flags, float(quality))
216
+ meta = struct.pack(f'<{channels}I', *comp_lens)
217
+
218
+ body = b''.join(compressed_channels)
219
+ return header + meta + body
220
+
221
+ def decode_image(payload):
222
+ if len(payload) < IMG_HDR_SIZE:
223
+ raise ValueError("Image payload too small")
224
+
225
+ width, height, channels, flags, quality = struct.unpack_from(IMG_HDR_FMT, payload, 0)
226
+ has_alpha = bool(flags & 1)
227
+
228
+ pos = IMG_HDR_SIZE
229
+ comp_lens = struct.unpack_from(f'<{channels}I', payload, pos)
230
+ pos += channels * 4
231
+
232
+ compressed_channels = []
233
+ for l in comp_lens:
234
+ compressed_channels.append(payload[pos:pos+l])
235
+ pos += l
236
+
237
+ raw_pairs = []
238
+ dctx = zstd.ZstdDecompressor()
239
+ for comp in compressed_channels:
240
+ packed = dctx.decompress(comp, max_output_size=width*height*64*4)
241
+ raw_pairs.append(np.frombuffer(packed, dtype='<i4').astype(np.int32))
242
+
243
+ q_luma = _get_q_matrix(quality, is_chroma=False)
244
+ q_chroma = _get_q_matrix(quality, is_chroma=True)
245
+ q_alpha = _get_q_matrix(quality, is_chroma=False)
246
+
247
+ oklab = np.zeros((height, width, 3), dtype=np.float32)
248
+ oklab[:, :, 0] = _decode_channel_fast(raw_pairs[0], q_luma, height, width)
249
+ oklab[:, :, 1] = _decode_channel_fast(raw_pairs[1], q_chroma, height, width)
250
+ oklab[:, :, 2] = _decode_channel_fast(raw_pairs[2], q_chroma, height, width)
251
+
252
+ rgb = np.zeros((height, width, 3), dtype=np.uint8)
253
+ _oklab_to_rgb_njit(oklab, rgb)
254
+
255
+ if has_alpha:
256
+ alpha_channel = _decode_channel_fast(raw_pairs[3], q_alpha, height, width)
257
+ alpha = np.clip(alpha_channel, 0, 255).astype(np.uint8)
258
+ return np.dstack((rgb, alpha))
259
+
260
+ return rgb
261
+
262
+ def get_image_info(payload):
263
+ width, height, channels, flags, quality = struct.unpack_from(IMG_HDR_FMT, payload, 0)
264
+ return dict(
265
+ width=width, height=height, channels=channels,
266
+ has_alpha=bool(flags & 1), quality=quality
267
+ )
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: lvthn-codec
3
+ Version: 1.0.0
4
+ Summary: Universal LVTHN media codec: audio (MDCT + Bark + Psy) and images (Oklab + DCT + zstd)
5
+ Author: FreshGame
6
+ Maintainer: FreshLend Studio
7
+ License-Expression: MIT
8
+ Keywords: audio,codec,image,compression,mdct,dct,psychoacoustics,oklab,lvn,patent-free
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Multimedia :: Sound/Audio
16
+ Classifier: Topic :: Multimedia :: Graphics
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ License-File: PATENTS
21
+ Requires-Dist: numpy==2.2.6
22
+ Requires-Dist: scipy==1.15.3
23
+ Requires-Dist: numba==0.68.1
24
+ Requires-Dist: zstandard==0.25.0
25
+ Requires-Dist: Pillow==12.3.0
26
+ Dynamic: license-file
27
+
28
+ # LVTHN-CODEC [RU](README.md) | **EN**
29
+
30
+ A modular, patent-clean, and highly efficient media codec written in Python.
31
+ It uses a universal container (`.lvn`) that supports both **Audio** and **Images**, applying modern psychoacoustic and psychovisual models to outperform industry standards like MP3 and PNG.
32
+
33
+ ## Features
34
+
35
+ * **Universal container (`.lvn`):** One format for everything. The decoder automatically detects what is inside.
36
+ * **Audio codec:** MDCT + Mid/Side stereo + Bark scale bands + Absolute Threshold of Hearing (ATH) + Adaptive quantization.
37
+ * **Image codec:** Oklab color space + Block DCT + RLE + Zstandard.
38
+ * **Performance:**
39
+ * Audio outperforms MP3 in size by **~15%** at the same perceived quality.
40
+ * Images outperform PNG in size by **6 to 7 times** with visually indistinguishable quality.
41
+ * High speed: Audio RTF < 0.02, 4K image decoding takes about 3 seconds.
42
+
43
+ ## Installation
44
+
45
+ Ensure you have Python 3.10+ installed.
46
+
47
+ ```bash
48
+ pip install numpy scipy numba zstandard Pillow sounddevice PySide6
49
+ ```
50
+ *(Note: For `converter.py` to work with formats like MP3 or JPG, you need to install `ffmpeg` and add it to the system PATH environment variable).*
51
+
52
+ ## Usage
53
+
54
+ ### 1. Codec CLI (`codec.py`)
55
+ Direct encoding and decoding to and from the `.lvn` format.
56
+ ```bash
57
+ # Audio
58
+ python codec.py encode track.wav track.lvn compact
59
+ python codec.py decode track.lvn out.wav
60
+
61
+ # Images
62
+ python codec.py encode-img image.png image.lvn fast
63
+ python codec.py decode-img image.lvn out.png
64
+
65
+ # File info
66
+ python codec.py info track.lvn
67
+ ```
68
+
69
+ ### 2. Converter (`converter.py`)
70
+ Converts any supported format (via ffmpeg) to `.lvn` and back.
71
+ ```bash
72
+ python converter.py to-lvn track.mp3 track.lvn --profile default
73
+ python converter.py from-lvn track.lvn track_out.mp3 --bitrate 192k
74
+ ```
75
+
76
+ ### 3. Player (`player.py`)
77
+ Modern GUI player and image viewer based on PySide6.
78
+ ```bash
79
+ python player.py track.lvn
80
+ python player.py image.lvn
81
+ ```
82
+
83
+ ### 4. Benchmark (`benchmark.py`)
84
+ Testing speed and compression ratio on your files.
85
+ ```bash
86
+ python benchmark.py --audio audio.wav --image image.png --image-profiles fast max
87
+ ```
88
+
89
+ ## Profiles
90
+
91
+ ### Audio Profiles
92
+ | Profile | Description | SNR | Best for |
93
+ |---|---|---|---|
94
+ | `compact` | Maximum compression | 20.0 | Everyday listening, saving space |
95
+ | `default` | Balanced | 24.0 | High-quality music |
96
+ | `speech` | Voice optimized | 20.0 | Podcasts, audiobooks |
97
+ | `detail` | Preserves quiet details | 24.0 | Lo-Fi, acoustic, vinyl |
98
+ | `master` | Archival quality | 30.0 | Mastering, studio archives |
99
+
100
+ ### Image Profiles
101
+ | Profile | Quality | Zstd Level | Best for |
102
+ |---|---|---|---|
103
+ | `fast` | 85 | 1 | Quick preview, game textures |
104
+ | `default` | 92 | 12 | Web images, everyday use |
105
+ | `max` | 98 | 19 | Archiving, professional photography |
106
+
107
+ ## Benchmarks
108
+
109
+ ### Audio (179 sec track)
110
+ | File | Profile | Bitrate | SNR | Enc RTF | Dec RTF | Compression |
111
+ |---|---|---|---|---|---|---|
112
+ | acoustic.wav | compact | 113 kbps | 27.5 dB | 0.022 | 0.009 | 13.5x |
113
+ | acoustic.wav | master | 225 kbps | 40.8 dB | 0.020 | 0.010 | 6.8x |
114
+
115
+ ### Images (4K 8.29 MP)
116
+ | File | Profile | PSNR | Enc Time | Dec Time | Compression |
117
+ |---|---|---|---|---|---|
118
+ | 4k_image.png | fast | 42.1 dB | 4.10s | 3.14s | 7.1x |
119
+ | 4k_image.png | max | 48.3 dB | 29.32s | 3.14s | 4.3x |
120
+
121
+ *(PSNR > 40 dB is considered visually indistinguishable from the original. RTF < 1.0 means faster than real time).*
122
+
123
+ ## Architecture
124
+
125
+ * `codec.py` - Main CLI and API.
126
+ * `core/container.py` - Universal LVTHN wrapper.
127
+ * `core/audio.py` - Audio math (MDCT, Bark, Psy).
128
+ * `core/image.py` - Image math (Oklab, DCT).
129
+ * `player.py` - GUI (PySide6).
130
+ * `converter.py` - FFmpeg wrapper.
131
+ * `benchmark.py` - Performance testing tool.
132
+
133
+ ## License and Patents
134
+
135
+ The project is distributed under the **MIT** license.
136
+ It is built exclusively on patent-free algorithms (MDCT, Oklab, DCT, Rice coding, Zstandard). See the `PATENTS` file for a detailed breakdown.
@@ -0,0 +1,11 @@
1
+ codec.py,sha256=qBczZSDoOPzLqAMz49N1lIM6PHV7GBkhh_qaBBp1s3c,6001
2
+ core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ core/audio.py,sha256=5CBFQAiHg9zUQRjVkMbf9RcWzEYXmAoblXdlsq7lkYM,13277
4
+ core/container.py,sha256=tis5Y-BpfwwAcmD5_HlXI6NQJ98UhvMUY8QalO0GxUc,660
5
+ core/image.py,sha256=LgVaA_VCtEoEpVvDg8mtmlkiSQFKHydxBuZSQBVtJjQ,9684
6
+ lvthn_codec-1.0.0.dist-info/licenses/LICENSE,sha256=oydlZ-tVSj8Dhk5n6xZTAesffEyD4P5mZCW0-gpOaKQ,1092
7
+ lvthn_codec-1.0.0.dist-info/licenses/PATENTS,sha256=9Y191lC6dM6RvaraUMc8nust5l-j9V_tyzs793OslRQ,1300
8
+ lvthn_codec-1.0.0.dist-info/METADATA,sha256=xQGTOE82E1TeHKkGsrdfEvweebdDxtduC8gter98IEQ,5091
9
+ lvthn_codec-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
10
+ lvthn_codec-1.0.0.dist-info/top_level.txt,sha256=ns1lgx8tWumjOWKwOow_jNeT-5onvR86t_2TJLGw8zo,11
11
+ lvthn_codec-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FreshLend Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,30 @@
1
+ LVTHN-CODEC — Patent Notice
2
+
3
+ This software is released under the MIT License.
4
+
5
+ The author is not aware of any patents that cover the algorithms implemented
6
+ here. The core techniques used are:
7
+
8
+ Audio:
9
+ - MDCT / IMDCT with sine window (Princen-Bradley, 1986–87; expired)
10
+ - TDAC (Time-Domain Aliasing Cancellation, 1986; expired)
11
+ - Mid/Side stereo coding (Blumlein, 1930s; classic technique)
12
+ - Bark-scale band partitioning (Barkhausen 1960s, public domain)
13
+ - Absolute Threshold of Hearing (Terhardt 1979, public domain)
14
+ - Rice coding (Golomb 1966, Rice 1979; expired)
15
+ - Adaptive per-band quantization (basic idea, expired)
16
+
17
+ Images:
18
+ - Oklab color space (Björn Ottosson, 2020; public domain)
19
+ - Block DCT (discrete cosine transform; expired)
20
+ - Run-Length Encoding (RLE; public domain)
21
+ - Zstandard compression (Yann Collet, 2015; BSD/GPLv2, patent-free)
22
+
23
+ This is NOT a legal guarantee of patent non-infringement. No warranty is
24
+ provided. If you distribute binaries based on this code commercially,
25
+ consult a patent attorney about your jurisdiction.
26
+
27
+ The author has intentionally avoided:
28
+ - Full psychoacoustic masking models identical to MP3/AAC
29
+ - SBR / PS / HE-AAC extensions
30
+ - Any code from FFmpeg, LAME, Opus, Vorbis, or other existing codecs
@@ -0,0 +1,2 @@
1
+ codec
2
+ core