ka9q-python 3.2.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.
ka9q/types.py ADDED
@@ -0,0 +1,161 @@
1
+ """
2
+ ka9q-radio protocol types and constants
3
+
4
+ Status types from ka9q-radio/src/status.h
5
+ These MUST match the enum values in status.h exactly!
6
+ Verified against https://github.com/ka9q/ka9q-radio (official repository)
7
+ """
8
+
9
+ class StatusType:
10
+ """TLV type identifiers for radiod status/control protocol"""
11
+
12
+ EOL = 0
13
+ COMMAND_TAG = 1
14
+ CMD_CNT = 2
15
+ GPS_TIME = 3
16
+
17
+ DESCRIPTION = 4
18
+ STATUS_DEST_SOCKET = 5
19
+ SETOPTS = 6
20
+ CLEAROPTS = 7
21
+ RTP_TIMESNAP = 8
22
+ UNUSED4 = 9
23
+ INPUT_SAMPRATE = 10
24
+ UNUSED6 = 11
25
+ UNUSED7 = 12
26
+ INPUT_SAMPLES = 13
27
+ UNUSED8 = 14
28
+ UNUSED9 = 15
29
+
30
+ OUTPUT_DATA_SOURCE_SOCKET = 16
31
+ OUTPUT_DATA_DEST_SOCKET = 17
32
+ OUTPUT_SSRC = 18
33
+ OUTPUT_TTL = 19
34
+ OUTPUT_SAMPRATE = 20
35
+ OUTPUT_METADATA_PACKETS = 21
36
+ OUTPUT_DATA_PACKETS = 22
37
+ OUTPUT_ERRORS = 23
38
+
39
+ # Hardware
40
+ CALIBRATE = 24
41
+ LNA_GAIN = 25
42
+ MIXER_GAIN = 26
43
+ IF_GAIN = 27
44
+
45
+ DC_I_OFFSET = 28
46
+ DC_Q_OFFSET = 29
47
+ IQ_IMBALANCE = 30
48
+ IQ_PHASE = 31
49
+ DIRECT_CONVERSION = 32
50
+
51
+ # Tuning
52
+ RADIO_FREQUENCY = 33
53
+ FIRST_LO_FREQUENCY = 34
54
+ SECOND_LO_FREQUENCY = 35
55
+ SHIFT_FREQUENCY = 36
56
+ DOPPLER_FREQUENCY = 37
57
+ DOPPLER_FREQUENCY_RATE = 38
58
+
59
+ # Filtering
60
+ LOW_EDGE = 39
61
+ HIGH_EDGE = 40
62
+ KAISER_BETA = 41
63
+ FILTER_BLOCKSIZE = 42
64
+ FILTER_FIR_LENGTH = 43
65
+ FILTER2 = 44
66
+
67
+ # Signals
68
+ IF_POWER = 45
69
+ BASEBAND_POWER = 46
70
+ NOISE_DENSITY = 47
71
+
72
+ # Demodulation configuration
73
+ DEMOD_TYPE = 48 # 0 = linear (default), 1 = FM, 2 = WFM/Stereo, 3 = spectrum
74
+ OUTPUT_CHANNELS = 49 # 1 or 2 in Linear, otherwise 1
75
+ INDEPENDENT_SIDEBAND = 50 # Linear only
76
+ PLL_ENABLE = 51
77
+ PLL_LOCK = 52
78
+ PLL_SQUARE = 53
79
+ PLL_PHASE = 54
80
+ PLL_BW = 55
81
+ ENVELOPE = 56
82
+ SNR_SQUELCH = 57
83
+
84
+ # Demodulation status
85
+ PLL_SNR = 58 # FM, PLL linear
86
+ FREQ_OFFSET = 59
87
+ PEAK_DEVIATION = 60
88
+ PL_TONE = 61
89
+
90
+ # Settable gain parameters
91
+ AGC_ENABLE = 62 # Boolean, linear modes only
92
+ HEADROOM = 63
93
+ AGC_HANGTIME = 64
94
+ AGC_RECOVERY_RATE = 65
95
+ FM_SNR = 66
96
+ AGC_THRESHOLD = 67
97
+
98
+ GAIN = 68 # AM, Linear only
99
+ OUTPUT_LEVEL = 69
100
+ OUTPUT_SAMPLES = 70
101
+
102
+ OPUS_BIT_RATE = 71
103
+ MINPACKET = 72
104
+ FILTER2_BLOCKSIZE = 73
105
+ FILTER2_FIR_LENGTH = 74
106
+ FILTER2_KAISER_BETA = 75
107
+ SPECTRUM_FFT_N = 76
108
+
109
+ FILTER_DROPS = 77
110
+ LOCK = 78
111
+
112
+ TP1 = 79 # Test points
113
+ TP2 = 80
114
+
115
+ GAINSTEP = 81
116
+ AD_BITS_PER_SAMPLE = 82
117
+ SQUELCH_OPEN = 83
118
+ SQUELCH_CLOSE = 84
119
+ PRESET = 85 # Mode/preset name (e.g., "iq", "usb", "lsb")
120
+ DEEMPH_TC = 86
121
+ DEEMPH_GAIN = 87
122
+ CONVERTER_OFFSET = 88
123
+ PL_DEVIATION = 89
124
+ THRESH_EXTEND = 90
125
+
126
+ # Spectral analysis
127
+ SPECTRUM_KAISER_BETA = 91
128
+ COHERENT_BIN_SPACING = 92
129
+ NONCOHERENT_BIN_BW = 93
130
+ BIN_COUNT = 94
131
+ CROSSOVER = 95
132
+ BIN_DATA = 96
133
+
134
+ RF_ATTEN = 97
135
+ RF_GAIN = 98
136
+ RF_AGC = 99
137
+ FE_LOW_EDGE = 100
138
+ FE_HIGH_EDGE = 101
139
+ FE_ISREAL = 102
140
+ BLOCKS_SINCE_POLL = 103
141
+ AD_OVER = 104
142
+ RTP_PT = 105
143
+ STATUS_INTERVAL = 106
144
+ OUTPUT_ENCODING = 107
145
+ SAMPLES_SINCE_OVER = 108
146
+ PLL_WRAPS = 109
147
+ RF_LEVEL_CAL = 110
148
+
149
+
150
+ # Command packet type
151
+ CMD = 1
152
+
153
+ # Encoding types (from ka9q-radio)
154
+ class Encoding:
155
+ """Output encoding types"""
156
+ NO_ENCODING = 0
157
+ S16BE = 1 # Signed 16-bit big-endian
158
+ S16LE = 2 # Signed 16-bit little-endian
159
+ F32 = 3 # 32-bit float
160
+ F16 = 4 # 16-bit float
161
+ OPUS = 5 # Opus codec
ka9q/utils.py ADDED
@@ -0,0 +1,202 @@
1
+ """
2
+ Shared utility functions for ka9q-python
3
+
4
+ This module contains common utilities used across the package, primarily
5
+ for mDNS address resolution and network operations.
6
+ """
7
+
8
+ import socket
9
+ import subprocess
10
+ import re
11
+ import logging
12
+ from typing import Optional
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def resolve_multicast_address(address: str, timeout: float = 5.0) -> str:
18
+ """
19
+ Resolve hostname or mDNS address to IP for multicast operations
20
+
21
+ This function tries multiple resolution methods for cross-platform compatibility:
22
+ 1. Check if already an IP address (no resolution needed)
23
+ 2. Try avahi-resolve (Linux)
24
+ 3. Try dns-sd (macOS)
25
+ 4. Fallback to getaddrinfo (works everywhere)
26
+
27
+ Args:
28
+ address: Hostname, .local mDNS name, or IP address
29
+ timeout: Resolution timeout in seconds (default: 5.0)
30
+
31
+ Returns:
32
+ Resolved IP address as string (e.g., "239.251.200.193")
33
+
34
+ Raises:
35
+ Exception: If resolution fails after trying all methods
36
+
37
+ Example:
38
+ >>> resolve_multicast_address("radiod.local")
39
+ '239.251.200.193'
40
+
41
+ >>> resolve_multicast_address("192.168.1.100")
42
+ '192.168.1.100'
43
+ """
44
+ # Check if already an IP address
45
+ if re.match(r'^\d+\.\d+\.\d+\.\d+$', address):
46
+ logger.debug(f"Address {address} is already an IP")
47
+ return address
48
+
49
+ # Try avahi-resolve (Linux)
50
+ try:
51
+ result = subprocess.run(
52
+ ['avahi-resolve', '-n', address],
53
+ capture_output=True,
54
+ text=True,
55
+ timeout=timeout
56
+ )
57
+ if result.returncode == 0:
58
+ # Parse output: "hostname ip_address"
59
+ parts = result.stdout.strip().split()
60
+ if len(parts) >= 2:
61
+ resolved = parts[1]
62
+ logger.debug(f"Resolved via avahi-resolve: {address} -> {resolved}")
63
+ return resolved
64
+ except (subprocess.TimeoutExpired, FileNotFoundError) as e:
65
+ logger.debug(f"avahi-resolve not available: {e}")
66
+
67
+ # Try dns-sd (macOS)
68
+ try:
69
+ result = subprocess.run(
70
+ ['dns-sd', '-G', 'v4', address],
71
+ capture_output=True,
72
+ text=True,
73
+ timeout=timeout
74
+ )
75
+ if result.returncode == 0:
76
+ # Parse dns-sd output for IP address
77
+ for line in result.stdout.split('\n'):
78
+ # Look for lines containing the address and an IP
79
+ match = re.search(r'(\d+\.\d+\.\d+\.\d+)', line)
80
+ if match and address in line:
81
+ resolved = match.group(1)
82
+ logger.debug(f"Resolved via dns-sd: {address} -> {resolved}")
83
+ return resolved
84
+ except (subprocess.TimeoutExpired, FileNotFoundError) as e:
85
+ logger.debug(f"dns-sd not available: {e}")
86
+
87
+ # Fallback to getaddrinfo (works everywhere)
88
+ # Note: getaddrinfo doesn't support timeout, so we set socket default timeout
89
+ try:
90
+ old_timeout = socket.getdefaulttimeout()
91
+ socket.setdefaulttimeout(timeout)
92
+ try:
93
+ addr_info = socket.getaddrinfo(address, None, socket.AF_INET, socket.SOCK_DGRAM)
94
+ resolved = addr_info[0][4][0]
95
+ logger.debug(f"Resolved via getaddrinfo: {address} -> {resolved}")
96
+ return resolved
97
+ finally:
98
+ socket.setdefaulttimeout(old_timeout)
99
+ except Exception as e:
100
+ raise Exception(f"Failed to resolve {address}: {e}") from e
101
+
102
+
103
+ def create_multicast_socket(multicast_addr: str, port: int = 5006,
104
+ bind_addr: str = '0.0.0.0',
105
+ interface: Optional[str] = None) -> socket.socket:
106
+ """
107
+ Create and configure a UDP socket for multicast operations
108
+
109
+ This is a convenience function that sets up all the necessary socket options
110
+ for sending to or receiving from a multicast group.
111
+
112
+ Args:
113
+ multicast_addr: Multicast group IP address
114
+ port: Port number (default: 5006 for radiod)
115
+ bind_addr: Address to bind to (default: '0.0.0.0' for all interfaces)
116
+ interface: IP address of network interface for multicast membership
117
+ (e.g., '192.168.1.100'). Required on multi-homed systems.
118
+ If None, uses INADDR_ANY (0.0.0.0).
119
+
120
+ Returns:
121
+ Configured socket ready for multicast operations
122
+
123
+ Raises:
124
+ OSError: If socket creation or configuration fails
125
+
126
+ Example:
127
+ >>> sock = create_multicast_socket('239.251.200.193')
128
+ >>> sock.sendto(data, ('239.251.200.193', 5006))
129
+
130
+ >>> # Multi-homed system
131
+ >>> sock = create_multicast_socket('239.251.200.193', interface='192.168.1.100')
132
+ """
133
+ import struct
134
+
135
+ # Create UDP socket
136
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
137
+
138
+ # Allow multiple sockets to bind to the same port
139
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
140
+
141
+ # Set SO_REUSEPORT if available (allows multiple processes)
142
+ if hasattr(socket, 'SO_REUSEPORT'):
143
+ try:
144
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
145
+ logger.debug("SO_REUSEPORT enabled")
146
+ except OSError as e:
147
+ logger.debug(f"Could not set SO_REUSEPORT: {e}")
148
+
149
+ # Bind to specified port
150
+ try:
151
+ sock.bind((bind_addr, port))
152
+ logger.debug(f"Bound to {bind_addr}:{port}")
153
+ except OSError as e:
154
+ logger.error(f"Failed to bind socket to {bind_addr}:{port}: {e}")
155
+ sock.close()
156
+ raise
157
+
158
+ # Join multicast group on specified interface
159
+ interface_addr = interface if interface else '0.0.0.0'
160
+ mreq = struct.pack('=4s4s',
161
+ socket.inet_aton(multicast_addr), # multicast group
162
+ socket.inet_aton(interface_addr)) # interface to use
163
+ try:
164
+ sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
165
+ logger.debug(f"Joined multicast group {multicast_addr} on interface {interface_addr}")
166
+ except OSError as e:
167
+ # EADDRINUSE is not fatal - group already joined
168
+ if e.errno != 48: # EADDRINUSE on macOS
169
+ logger.warning(f"Failed to join multicast group: {e}")
170
+
171
+ return sock
172
+
173
+
174
+ def validate_multicast_address(address: str) -> bool:
175
+ """
176
+ Validate that an address is a valid multicast address
177
+
178
+ Multicast addresses are in the range 224.0.0.0 to 239.255.255.255
179
+
180
+ Args:
181
+ address: IP address string to validate
182
+
183
+ Returns:
184
+ True if valid multicast address, False otherwise
185
+
186
+ Example:
187
+ >>> validate_multicast_address('239.251.200.193')
188
+ True
189
+
190
+ >>> validate_multicast_address('192.168.1.1')
191
+ False
192
+ """
193
+ try:
194
+ parts = address.split('.')
195
+ if len(parts) != 4:
196
+ return False
197
+
198
+ first_octet = int(parts[0])
199
+ # Multicast range: 224.0.0.0 to 239.255.255.255
200
+ return 224 <= first_octet <= 239
201
+ except (ValueError, AttributeError):
202
+ return False
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.4
2
+ Name: ka9q-python
3
+ Version: 3.2.0
4
+ Summary: Python interface for ka9q-radio control and monitoring
5
+ Home-page: https://github.com/mijahauan/ka9q-python
6
+ Author: Michael Hauan AC0G
7
+ Author-email: Michael Hauan AC0G <ac0g@hauan.org>
8
+ License-Expression: MIT
9
+ Project-URL: Homepage, https://github.com/mijahauan/ka9q-python
10
+ Project-URL: Documentation, https://github.com/mijahauan/ka9q-python/blob/main/README.md
11
+ Project-URL: Repository, https://github.com/mijahauan/ka9q-python
12
+ Project-URL: Issues, https://github.com/mijahauan/ka9q-python/issues
13
+ Keywords: ka9q-radio,sdr,ham-radio,radio-control
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Intended Audience :: Telecommunications Industry
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Communications :: Ham Radio
23
+ Classifier: Topic :: Scientific/Engineering
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: numpy>=1.24.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
31
+ Dynamic: author
32
+ Dynamic: home-page
33
+ Dynamic: license-file
34
+ Dynamic: requires-python
35
+
36
+ # ka9q-python
37
+
38
+ [![PyPI version](https://badge.fury.io/py/ka9q-python.svg)](https://badge.fury.io/py/ka9q-python)
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
+
41
+ **General-purpose Python library for controlling [ka9q-radio](https://github.com/ka9q/ka9q-radio)**
42
+
43
+ Control radiod channels for any application: AM/FM/SSB radio, WSPR monitoring, SuperDARN radar, CODAR oceanography, HF fax, satellite downlinks, and more.
44
+
45
+ **Note:** Package name is `ka9q-python` out of respect for KA9Q (Phil Karn's callsign). Import as `import ka9q`.
46
+
47
+ ## Table of Contents
48
+
49
+ - [Features](#features)
50
+ - [Installation](#installation)
51
+ - [Quick Start](#quick-start)
52
+ - [Documentation](#documentation)
53
+ - [Examples](#examples)
54
+ - [Use Cases](#use-cases)
55
+ - [License](#license)
56
+
57
+ ## Features
58
+
59
+ ✅ **Zero assumptions** - Works for any SDR application
60
+ ✅ **Complete API** - All 85+ radiod parameters exposed
61
+ ✅ **Channel control** - Create, configure, discover channels
62
+ ✅ **RTP recording** - Generic recorder with timing support and state machine
63
+ ✅ **Precise timing** - GPS_TIME/RTP_TIMESNAP for accurate timestamps
64
+ ✅ **Multi-homed support** - Works on systems with multiple network interfaces
65
+ ✅ **Pure Python** - No compiled dependencies
66
+ ✅ **Well tested** - Comprehensive test coverage
67
+ ✅ **Documented** - Comprehensive examples and API reference included
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ pip install ka9q-python
73
+ ```
74
+
75
+ Or install from source:
76
+
77
+ ```bash
78
+ git clone https://github.com/mijahauan/ka9q-python.git
79
+ cd ka9q-python
80
+ pip install -e .
81
+ ```
82
+
83
+ ## Quick Start
84
+
85
+ ### Listen to AM Broadcast
86
+
87
+ ```python
88
+ from ka9q import RadiodControl
89
+
90
+ # Connect to radiod
91
+ control = RadiodControl("radiod.local")
92
+
93
+ # Create AM channel on 10 MHz WWV
94
+ control.create_channel(
95
+ ssrc=10000000,
96
+ frequency_hz=10.0e6,
97
+ preset="am",
98
+ sample_rate=12000
99
+ )
100
+
101
+ # RTP stream now available with SSRC 10000000
102
+ ```
103
+
104
+ ### Monitor WSPR Bands
105
+
106
+ ```python
107
+ from ka9q import RadiodControl
108
+
109
+ control = RadiodControl("radiod.local")
110
+
111
+ wspr_bands = [
112
+ (1.8366e6, "160m"),
113
+ (3.5686e6, "80m"),
114
+ (7.0386e6, "40m"),
115
+ (10.1387e6, "30m"),
116
+ (14.0956e6, "20m"),
117
+ ]
118
+
119
+ for freq, band in wspr_bands:
120
+ control.create_channel(
121
+ ssrc=int(freq),
122
+ frequency_hz=freq,
123
+ preset="usb",
124
+ sample_rate=12000
125
+ )
126
+ print(f"{band} WSPR channel created")
127
+ ```
128
+
129
+ ### Discover Existing Channels
130
+
131
+ ```python
132
+ from ka9q import discover_channels
133
+
134
+ channels = discover_channels("radiod.local")
135
+ for ssrc, info in channels.items():
136
+ print(f"{ssrc}: {info.frequency/1e6:.3f} MHz, {info.preset}, {info.sample_rate} Hz")
137
+ ```
138
+
139
+ ### Record RTP Stream with Precise Timing
140
+
141
+ ```python
142
+ from ka9q import discover_channels, RTPRecorder
143
+ import time
144
+
145
+ # Get channel with timing info
146
+ channels = discover_channels("radiod.local")
147
+ channel = channels[14074000]
148
+
149
+ # Define packet handler
150
+ def handle_packet(header, payload, wallclock):
151
+ print(f"Packet at {wallclock}: {len(payload)} bytes")
152
+
153
+ # Create and start recorder
154
+ recorder = RTPRecorder(channel=channel, on_packet=handle_packet)
155
+ recorder.start()
156
+ recorder.start_recording()
157
+ time.sleep(60) # Record for 60 seconds
158
+ recorder.stop_recording()
159
+ recorder.stop()
160
+ ```
161
+
162
+ ### Multi-Homed Systems
163
+
164
+ For systems with multiple network interfaces, specify which interface to use:
165
+
166
+ ```python
167
+ from ka9q import RadiodControl, discover_channels
168
+
169
+ # Specify your interface IP address
170
+ my_interface = "192.168.1.100"
171
+
172
+ # Create control with specific interface
173
+ control = RadiodControl("radiod.local", interface=my_interface)
174
+
175
+ # Discovery on specific interface
176
+ channels = discover_channels("radiod.local", interface=my_interface)
177
+ ```
178
+
179
+ ## Documentation
180
+
181
+ For detailed information, please refer to the documentation in the `docs/` directory:
182
+
183
+ - **[API Reference](docs/API_REFERENCE.md)**: Full details on all classes, methods, and functions.
184
+ - **[RTP Timing Support](docs/RTP_TIMING_SUPPORT.md)**: Guide to RTP timing and synchronization.
185
+ - **[Architecture](docs/ARCHITECTURE.md)**: Overview of the library's design and structure.
186
+ - **[Installation Guide](docs/INSTALLATION.md)**: Detailed installation instructions.
187
+ - **[Testing Guide](docs/TESTING_GUIDE.md)**: Information on how to run the test suite.
188
+ - **[Security Considerations](docs/SECURITY.md)**: Important security information regarding the ka9q-radio protocol.
189
+ - **[Changelog](docs/CHANGELOG.md)**: A log of all changes for each version.
190
+ - **[Release Notes](docs/releases/)**: Release-specific notes and instructions.
191
+
192
+ ## Examples
193
+
194
+ See the `examples/` directory for complete applications:
195
+
196
+ - **`discover_example.py`** - Channel discovery methods (native Python and control utility)
197
+ - **`tune.py`** - Interactive channel tuning utility (Python implementation of ka9q-radio's tune)
198
+ - **`tune_example.py`** - Programmatic examples of using the tune() method
199
+ - **`rtp_recorder_example.py`** - Complete RTP recorder with timing and state machine
200
+ - **`test_timing_fields.py`** - Verify GPS_TIME/RTP_TIMESNAP timing fields
201
+ - **`simple_am_radio.py`** - Minimal AM broadcast listener
202
+ - **`superdarn_recorder.py`** - Ionospheric radar monitoring
203
+ - **`codar_oceanography.py`** - Ocean current radar
204
+ - **`hf_band_scanner.py`** - Dynamic frequency scanner
205
+ - **`wspr_monitor.py`** - Weak signal propagation reporter
206
+
207
+ ## Use Cases
208
+
209
+ ### AM/FM/SSB Radio
210
+ - Broadcast monitoring
211
+ - Ham radio operation
212
+ - Shortwave listening
213
+
214
+ ### Scientific Research
215
+ - WSPR propagation studies
216
+ - SuperDARN ionospheric radar
217
+ - CODAR ocean current mapping
218
+ - Meteor scatter
219
+ - EME (moonbounce)
220
+
221
+ ### Digital Modes
222
+ - FT8/FT4 monitoring
223
+ - RTTY/PSK decoding
224
+ - DRM digital radio
225
+ - HF fax reception
226
+
227
+ ### Satellite Operations
228
+ - Downlink reception
229
+ - Doppler tracking
230
+ - Multi-frequency monitoring
231
+
232
+ ### Custom Applications
233
+ **No assumptions!** Use for anything SDR-related.
234
+
235
+ ## License
236
+
237
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,15 @@
1
+ ka9q/__init__.py,sha256=Hj9-RXpUlYCKCaS8s_mCI2E_rT1Tb9j04fT94PSpqg8,2272
2
+ ka9q/control.py,sha256=gte-8p8W_l76OJbaUu3pi4gqwztS1Kc1NzdMvgkoAlw,88910
3
+ ka9q/discovery.py,sha256=gURq2GlV49vArYCcExarsKtyD1x6Is2LuDfLWveYM5c,18707
4
+ ka9q/exceptions.py,sha256=_P8rok_tTmRMu-lLRhS95kv8FRK5PEY80_6jIux2mWM,474
5
+ ka9q/resequencer.py,sha256=o31Hrt1m12jFyfIOtpHlICK0EQm5wm2Bo3yJNgr8FgQ,14084
6
+ ka9q/rtp_recorder.py,sha256=J9cWygxe7oKG5nQu4tIO6Ba5gaY5iK5PahvfdpcZbo4,16171
7
+ ka9q/stream.py,sha256=fc2yPQanVEA3FO7-jWfhEuN9UHpE9sFyybaIOOfSJHY,13822
8
+ ka9q/stream_quality.py,sha256=yklim5rx2U_y1ElgYrLtftSxYxPItoOwIHJsw17xujA,7589
9
+ ka9q/types.py,sha256=LtqDQIG6N89iAKuqwdwJEtupcJeLqpqRCjpRtdK0pW4,3524
10
+ ka9q/utils.py,sha256=fFR_sYIpeDyPaFlXzN6jCiGhYlssvrMMVx4z6vyhqcc,7107
11
+ ka9q_python-3.2.0.dist-info/licenses/LICENSE,sha256=mjz-l5TVsO9j7Jqt1gfLvTWtZK_HL9sZRRpsHaq4Qv8,1074
12
+ ka9q_python-3.2.0.dist-info/METADATA,sha256=Rx3b6-JleiGIYvLQc7MBTE3vgIyogrdLCggj0OPA-t0,7334
13
+ ka9q_python-3.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ ka9q_python-3.2.0.dist-info/top_level.txt,sha256=FUTLZDtQpJGenGPPlN34E5QpQi_oaiLB6LuzgHblBNE,5
15
+ ka9q_python-3.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Michael James Hauan
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 OF THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ka9q