cysox 0.1.4__cp311-cp311-macosx_11_0_arm64.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.
- cysox/__init__.py +61 -0
- cysox/__init__.pyi +223 -0
- cysox/__main__.py +102 -0
- cysox/audio.py +528 -0
- cysox/fx/__init__.py +113 -0
- cysox/fx/base.py +168 -0
- cysox/fx/convert.py +128 -0
- cysox/fx/eq.py +94 -0
- cysox/fx/filter.py +131 -0
- cysox/fx/reverb.py +215 -0
- cysox/fx/time.py +248 -0
- cysox/fx/volume.py +98 -0
- cysox/sox.cpython-311-darwin.so +0 -0
- cysox/utils.py +47 -0
- cysox/utils.pyi +21 -0
- cysox-0.1.4.dist-info/METADATA +339 -0
- cysox-0.1.4.dist-info/RECORD +21 -0
- cysox-0.1.4.dist-info/WHEEL +6 -0
- cysox-0.1.4.dist-info/entry_points.txt +2 -0
- cysox-0.1.4.dist-info/licenses/LICENSE +21 -0
- cysox-0.1.4.dist-info/top_level.txt +1 -0
cysox/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""cysox - A Cython wrapper for libsox.
|
|
2
|
+
|
|
3
|
+
High-level API (recommended):
|
|
4
|
+
>>> import cysox
|
|
5
|
+
>>> from cysox import fx
|
|
6
|
+
>>>
|
|
7
|
+
>>> # Get file info
|
|
8
|
+
>>> info = cysox.info('audio.wav')
|
|
9
|
+
>>>
|
|
10
|
+
>>> # Convert with effects
|
|
11
|
+
>>> cysox.convert('input.wav', 'output.mp3', effects=[
|
|
12
|
+
... fx.Volume(db=3),
|
|
13
|
+
... fx.Reverb(),
|
|
14
|
+
... ])
|
|
15
|
+
>>>
|
|
16
|
+
>>> # Stream samples
|
|
17
|
+
>>> for chunk in cysox.stream('audio.wav'):
|
|
18
|
+
... process(chunk)
|
|
19
|
+
>>>
|
|
20
|
+
>>> # Play audio
|
|
21
|
+
>>> cysox.play('audio.wav')
|
|
22
|
+
|
|
23
|
+
Low-level API (power users):
|
|
24
|
+
>>> from cysox import sox
|
|
25
|
+
>>> sox.init()
|
|
26
|
+
>>> f = sox.Format('audio.wav')
|
|
27
|
+
>>> # ... low-level operations ...
|
|
28
|
+
>>> sox.quit()
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# High-level API (auto-initializing)
|
|
32
|
+
from .audio import (
|
|
33
|
+
info,
|
|
34
|
+
convert,
|
|
35
|
+
stream,
|
|
36
|
+
play,
|
|
37
|
+
concat,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Effects module
|
|
41
|
+
from . import fx
|
|
42
|
+
|
|
43
|
+
# Low-level API (explicit init/quit required)
|
|
44
|
+
from . import sox
|
|
45
|
+
|
|
46
|
+
# get libsox version via cysox.sox.version()
|
|
47
|
+
__version__ = "0.1.4"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
# High-level functions
|
|
52
|
+
"info",
|
|
53
|
+
"convert",
|
|
54
|
+
"stream",
|
|
55
|
+
"play",
|
|
56
|
+
# Modules
|
|
57
|
+
"fx",
|
|
58
|
+
"sox",
|
|
59
|
+
# Version
|
|
60
|
+
"__version__",
|
|
61
|
+
]
|
cysox/__init__.pyi
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Type stubs for cysox
|
|
2
|
+
# This file provides type hints for IDEs and type checkers
|
|
3
|
+
|
|
4
|
+
from typing import Optional, List, Union, Tuple
|
|
5
|
+
import array
|
|
6
|
+
|
|
7
|
+
# Exception classes
|
|
8
|
+
class SoxError(Exception): ...
|
|
9
|
+
class SoxInitError(SoxError): ...
|
|
10
|
+
class SoxFormatError(SoxError): ...
|
|
11
|
+
class SoxEffectError(SoxError): ...
|
|
12
|
+
class SoxIOError(SoxError): ...
|
|
13
|
+
class SoxMemoryError(SoxError): ...
|
|
14
|
+
|
|
15
|
+
# Core classes
|
|
16
|
+
class SignalInfo:
|
|
17
|
+
rate: float
|
|
18
|
+
channels: int
|
|
19
|
+
precision: int
|
|
20
|
+
length: int
|
|
21
|
+
mult: Optional[float]
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
rate: float = 0.0,
|
|
26
|
+
channels: int = 0,
|
|
27
|
+
precision: int = 0,
|
|
28
|
+
length: int = 0,
|
|
29
|
+
mult: float = 0.0
|
|
30
|
+
) -> None: ...
|
|
31
|
+
|
|
32
|
+
class EncodingInfo:
|
|
33
|
+
encoding: int
|
|
34
|
+
bits_per_sample: int
|
|
35
|
+
compression: float
|
|
36
|
+
reverse_bytes: int
|
|
37
|
+
reverse_nibbles: int
|
|
38
|
+
reverse_bits: int
|
|
39
|
+
opposite_endian: bool
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
encoding: int = 0,
|
|
44
|
+
bits_per_sample: int = 0,
|
|
45
|
+
compression: float = 0.0,
|
|
46
|
+
reverse_bytes: int = 0,
|
|
47
|
+
reverse_nibbles: int = 0,
|
|
48
|
+
reverse_bits: int = 0,
|
|
49
|
+
opposite_endian: bool = False
|
|
50
|
+
) -> None: ...
|
|
51
|
+
|
|
52
|
+
class Format:
|
|
53
|
+
filename: str
|
|
54
|
+
signal: SignalInfo
|
|
55
|
+
encoding: EncodingInfo
|
|
56
|
+
filetype: str
|
|
57
|
+
seekable: bool
|
|
58
|
+
mode: str
|
|
59
|
+
olength: int
|
|
60
|
+
clips: int
|
|
61
|
+
sox_errno: int
|
|
62
|
+
sox_errstr: str
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
filename: str,
|
|
67
|
+
signal: Optional[SignalInfo] = None,
|
|
68
|
+
encoding: Optional[EncodingInfo] = None,
|
|
69
|
+
filetype: Optional[str] = None,
|
|
70
|
+
mode: str = 'r'
|
|
71
|
+
) -> None: ...
|
|
72
|
+
|
|
73
|
+
def __enter__(self) -> Format: ...
|
|
74
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool: ...
|
|
75
|
+
|
|
76
|
+
def read(self, length: int) -> List[int]: ...
|
|
77
|
+
def read_buffer(self, length: int) -> memoryview: ...
|
|
78
|
+
def read_into(self, buffer) -> int: ...
|
|
79
|
+
def write(self, samples: Union[List[int], array.array, memoryview]) -> int: ...
|
|
80
|
+
def seek(self, offset: int, whence: int = 0) -> int: ...
|
|
81
|
+
def close(self) -> int: ...
|
|
82
|
+
|
|
83
|
+
class EffectHandler:
|
|
84
|
+
name: str
|
|
85
|
+
usage: str
|
|
86
|
+
flags: int
|
|
87
|
+
priv_size: int
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def find(name: str, ignore_devices: bool = False) -> Optional[EffectHandler]: ...
|
|
91
|
+
|
|
92
|
+
class Effect:
|
|
93
|
+
handler: EffectHandler
|
|
94
|
+
in_signal: SignalInfo
|
|
95
|
+
out_signal: SignalInfo
|
|
96
|
+
in_encoding: Optional[EncodingInfo]
|
|
97
|
+
out_encoding: Optional[EncodingInfo]
|
|
98
|
+
clips: int
|
|
99
|
+
flows: int
|
|
100
|
+
flow: int
|
|
101
|
+
|
|
102
|
+
def __init__(self, handler: EffectHandler) -> None: ...
|
|
103
|
+
def set_options(self, options: List[str]) -> int: ...
|
|
104
|
+
def stop(self) -> int: ...
|
|
105
|
+
def trim_get_start(self) -> int: ...
|
|
106
|
+
def trim_clear_start(self) -> None: ...
|
|
107
|
+
|
|
108
|
+
class EffectsChain:
|
|
109
|
+
effects: List[Effect]
|
|
110
|
+
length: int
|
|
111
|
+
table_size: int
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
in_encoding: Optional[EncodingInfo] = None,
|
|
116
|
+
out_encoding: Optional[EncodingInfo] = None
|
|
117
|
+
) -> None: ...
|
|
118
|
+
|
|
119
|
+
def add_effect(
|
|
120
|
+
self,
|
|
121
|
+
effect: Effect,
|
|
122
|
+
in_signal: SignalInfo,
|
|
123
|
+
out_signal: SignalInfo
|
|
124
|
+
) -> int: ...
|
|
125
|
+
|
|
126
|
+
def flow_effects(self, callback=None, client_data=None) -> int: ...
|
|
127
|
+
def get_clips(self) -> int: ...
|
|
128
|
+
def push_effect_last(self, effect: Effect) -> None: ...
|
|
129
|
+
def pop_effect_last(self) -> Optional[Effect]: ...
|
|
130
|
+
def delete_effect_last(self) -> None: ...
|
|
131
|
+
def delete_effects(self) -> None: ...
|
|
132
|
+
|
|
133
|
+
class FormatHandler:
|
|
134
|
+
sox_lib_version_code: int
|
|
135
|
+
description: str
|
|
136
|
+
names: List[str]
|
|
137
|
+
flags: int
|
|
138
|
+
priv_size: int
|
|
139
|
+
|
|
140
|
+
def __init__(self, path: str) -> None: ...
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def find(name: str, ignore_devices: bool = False) -> Optional[FormatHandler]: ...
|
|
144
|
+
|
|
145
|
+
class Globals:
|
|
146
|
+
verbosity: int
|
|
147
|
+
repeatable: bool
|
|
148
|
+
bufsiz: int
|
|
149
|
+
input_bufsiz: int
|
|
150
|
+
ranqd1: int
|
|
151
|
+
stdin_in_use_by: Optional[str]
|
|
152
|
+
stdout_in_use_by: Optional[str]
|
|
153
|
+
subsystem: Optional[str]
|
|
154
|
+
tmp_path: Optional[str]
|
|
155
|
+
use_magic: bool
|
|
156
|
+
use_threads: bool
|
|
157
|
+
log2_dft_min_size: int
|
|
158
|
+
|
|
159
|
+
def as_dict(self) -> dict: ...
|
|
160
|
+
|
|
161
|
+
# Functions
|
|
162
|
+
def init() -> None: ...
|
|
163
|
+
def quit() -> None: ...
|
|
164
|
+
def format_init() -> None: ...
|
|
165
|
+
def format_quit() -> None: ...
|
|
166
|
+
def version() -> str: ...
|
|
167
|
+
def version_info() -> dict: ...
|
|
168
|
+
def strerror(sox_errno: int) -> str: ...
|
|
169
|
+
def find_effect(name: str) -> Optional[EffectHandler]: ...
|
|
170
|
+
def find_format(name: str, ignore_devices: bool = False) -> Optional[FormatHandler]: ...
|
|
171
|
+
def is_playlist(filename: str) -> bool: ...
|
|
172
|
+
def basename(filename: str) -> str: ...
|
|
173
|
+
def precision(encoding: int, bits_per_sample: int) -> int: ...
|
|
174
|
+
def format_supports_encoding(path: str, encoding: EncodingInfo) -> bool: ...
|
|
175
|
+
def get_encodings() -> List: ...
|
|
176
|
+
def get_effects_globals() -> dict: ...
|
|
177
|
+
def get_format_fns() -> List[dict]: ...
|
|
178
|
+
def get_effect_fns() -> List[int]: ...
|
|
179
|
+
|
|
180
|
+
# Memory I/O
|
|
181
|
+
def open_mem_read(
|
|
182
|
+
buffer: bytes,
|
|
183
|
+
signal: Optional[SignalInfo] = None,
|
|
184
|
+
encoding: Optional[EncodingInfo] = None,
|
|
185
|
+
filetype: Optional[str] = None
|
|
186
|
+
) -> Format: ...
|
|
187
|
+
|
|
188
|
+
def open_mem_write(
|
|
189
|
+
buffer: bytearray,
|
|
190
|
+
signal: SignalInfo,
|
|
191
|
+
encoding: EncodingInfo,
|
|
192
|
+
filetype: Optional[str] = None,
|
|
193
|
+
oob = None
|
|
194
|
+
) -> Format: ...
|
|
195
|
+
|
|
196
|
+
def open_memstream_write(
|
|
197
|
+
signal: SignalInfo,
|
|
198
|
+
encoding: EncodingInfo,
|
|
199
|
+
filetype: Optional[str] = None,
|
|
200
|
+
oob = None
|
|
201
|
+
) -> Tuple[Format, int, int]: ...
|
|
202
|
+
|
|
203
|
+
# Sample I/O
|
|
204
|
+
def read_samples(format: Format, buffer: List, length: int) -> int: ...
|
|
205
|
+
def write_samples(format: Format, samples: List) -> int: ...
|
|
206
|
+
def seek_samples(format: Format, offset: int, whence: int = 0) -> int: ...
|
|
207
|
+
|
|
208
|
+
# Callback exception handling
|
|
209
|
+
def get_last_callback_exception() -> Optional[Tuple[type, BaseException, object]]: ...
|
|
210
|
+
|
|
211
|
+
# Constants
|
|
212
|
+
SUCCESS: int
|
|
213
|
+
EOF: int
|
|
214
|
+
EHDR: int
|
|
215
|
+
EFMT: int
|
|
216
|
+
ENOMEM: int
|
|
217
|
+
EPERM: int
|
|
218
|
+
ENOTSUP: int
|
|
219
|
+
EINVAL: int
|
|
220
|
+
|
|
221
|
+
SOX_SEEK_SET: int
|
|
222
|
+
|
|
223
|
+
ENCODINGS: List[Tuple[str, str]]
|
cysox/__main__.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Command-line interface for cysox."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import cysox
|
|
7
|
+
from cysox import sox
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
"""Main entry point for the cysox CLI."""
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
prog="cysox",
|
|
14
|
+
description="A Pythonic audio processing library wrapping libsox",
|
|
15
|
+
)
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"--version", "-V",
|
|
18
|
+
action="store_true",
|
|
19
|
+
help="Show version information",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
23
|
+
|
|
24
|
+
# info command
|
|
25
|
+
info_parser = subparsers.add_parser("info", help="Get audio file information")
|
|
26
|
+
info_parser.add_argument("file", help="Path to audio file")
|
|
27
|
+
|
|
28
|
+
# convert command
|
|
29
|
+
convert_parser = subparsers.add_parser("convert", help="Convert audio file")
|
|
30
|
+
convert_parser.add_argument("input", help="Input audio file")
|
|
31
|
+
convert_parser.add_argument("output", help="Output audio file")
|
|
32
|
+
convert_parser.add_argument(
|
|
33
|
+
"--rate", "-r", type=int, help="Target sample rate in Hz"
|
|
34
|
+
)
|
|
35
|
+
convert_parser.add_argument(
|
|
36
|
+
"--channels", "-c", type=int, help="Target number of channels"
|
|
37
|
+
)
|
|
38
|
+
convert_parser.add_argument(
|
|
39
|
+
"--bits", "-b", type=int, help="Target bits per sample"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# play command
|
|
43
|
+
play_parser = subparsers.add_parser("play", help="Play audio file")
|
|
44
|
+
play_parser.add_argument("file", help="Path to audio file")
|
|
45
|
+
|
|
46
|
+
# concat command
|
|
47
|
+
concat_parser = subparsers.add_parser("concat", help="Concatenate audio files")
|
|
48
|
+
concat_parser.add_argument("inputs", nargs="+", help="Input audio files")
|
|
49
|
+
concat_parser.add_argument("-o", "--output", required=True, help="Output file")
|
|
50
|
+
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
|
|
53
|
+
if args.version:
|
|
54
|
+
sox.init()
|
|
55
|
+
print(f"cysox {cysox.__version__}")
|
|
56
|
+
print(f"libsox {sox.version()}")
|
|
57
|
+
sox.quit()
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
if args.command is None:
|
|
61
|
+
parser.print_help()
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
if args.command == "info":
|
|
65
|
+
file_info = cysox.info(args.file)
|
|
66
|
+
print(f"File: {file_info['path']}")
|
|
67
|
+
print(f"Format: {file_info['format']}")
|
|
68
|
+
print(f"Duration: {file_info['duration']:.2f}s")
|
|
69
|
+
print(f"Sample rate: {file_info['sample_rate']} Hz")
|
|
70
|
+
print(f"Channels: {file_info['channels']}")
|
|
71
|
+
print(f"Bits per sample: {file_info['bits_per_sample']}")
|
|
72
|
+
print(f"Encoding: {file_info['encoding']}")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
if args.command == "convert":
|
|
76
|
+
cysox.convert(
|
|
77
|
+
args.input,
|
|
78
|
+
args.output,
|
|
79
|
+
sample_rate=args.rate,
|
|
80
|
+
channels=args.channels,
|
|
81
|
+
bits=args.bits,
|
|
82
|
+
)
|
|
83
|
+
print(f"Converted: {args.input} -> {args.output}")
|
|
84
|
+
return 0
|
|
85
|
+
|
|
86
|
+
if args.command == "play":
|
|
87
|
+
cysox.play(args.file)
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
if args.command == "concat":
|
|
91
|
+
if len(args.inputs) < 2:
|
|
92
|
+
print("Error: concat requires at least 2 input files", file=sys.stderr)
|
|
93
|
+
return 1
|
|
94
|
+
cysox.concat(args.inputs, args.output)
|
|
95
|
+
print(f"Concatenated {len(args.inputs)} files -> {args.output}")
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
sys.exit(main())
|