orbit-watermark 1.0.0__tar.gz
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.
- orbit_watermark-1.0.0/PKG-INFO +20 -0
- orbit_watermark-1.0.0/README.md +3 -0
- orbit_watermark-1.0.0/orbit_watermark/__init__.py +0 -0
- orbit_watermark-1.0.0/orbit_watermark/silentcipher_watermark.py +426 -0
- orbit_watermark-1.0.0/orbit_watermark.egg-info/PKG-INFO +20 -0
- orbit_watermark-1.0.0/orbit_watermark.egg-info/SOURCES.txt +10 -0
- orbit_watermark-1.0.0/orbit_watermark.egg-info/dependency_links.txt +1 -0
- orbit_watermark-1.0.0/orbit_watermark.egg-info/entry_points.txt +2 -0
- orbit_watermark-1.0.0/orbit_watermark.egg-info/requires.txt +5 -0
- orbit_watermark-1.0.0/orbit_watermark.egg-info/top_level.txt +1 -0
- orbit_watermark-1.0.0/pyproject.toml +30 -0
- orbit_watermark-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orbit-watermark
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: High-robustness neural audio watermarking using SilentCipher (Sony AI) & Spread Spectrum fallback
|
|
5
|
+
License: ISC
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: ISC License (ISCL)
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: torch<=2.0.0
|
|
13
|
+
Requires-Dist: silentcipher
|
|
14
|
+
Requires-Dist: librosa>=0.10.0
|
|
15
|
+
Requires-Dist: soundfile>=0.12.0
|
|
16
|
+
Requires-Dist: numpy>=1.20.0
|
|
17
|
+
|
|
18
|
+
# orbit_watermark
|
|
19
|
+
|
|
20
|
+
Standalone Python package for ORBIT WATERMARK analysis.
|
|
File without changes
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ORBIT SilentCipher Watermarking Script
|
|
4
|
+
|
|
5
|
+
Session 22 - Neural audio watermarking with SilentCipher (Sony AI)
|
|
6
|
+
|
|
7
|
+
This script provides embed and extract functionality for neural watermarking
|
|
8
|
+
using the SilentCipher model from Sony AI.
|
|
9
|
+
|
|
10
|
+
License: MIT (commercially licensable)
|
|
11
|
+
Source: https://github.com/sony/silentcipher
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
# Embed a message into audio
|
|
15
|
+
python scripts/silentcipher_watermark.py embed <audio_path> <output_path> --message "0,1,2,3,4"
|
|
16
|
+
|
|
17
|
+
# Extract message from audio
|
|
18
|
+
python scripts/silentcipher_watermark.py extract <audio_path>
|
|
19
|
+
|
|
20
|
+
Output (JSON):
|
|
21
|
+
Embed: {"success": true, "sdr": 25.5, "message": [0,1,2,3,4]}
|
|
22
|
+
Extract: {"success": true, "message": [0,1,2,3,4], "confidence": 0.98}
|
|
23
|
+
|
|
24
|
+
Requirements:
|
|
25
|
+
pip install silentcipher librosa soundfile numpy
|
|
26
|
+
|
|
27
|
+
Model:
|
|
28
|
+
SilentCipher (Sony AI) - ~100MB, downloaded on first use
|
|
29
|
+
Supports 44.1kHz and 16kHz audio
|
|
30
|
+
Message capacity: 5 x 8-bit integers = 40 bits
|
|
31
|
+
|
|
32
|
+
See: ORBIT_ENHANCEMENTS.md Section 1 (Neural Watermarking)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
import sys
|
|
36
|
+
import os
|
|
37
|
+
import json
|
|
38
|
+
import argparse
|
|
39
|
+
import warnings
|
|
40
|
+
import logging
|
|
41
|
+
|
|
42
|
+
# Suppress all warnings and verbose logging for cleaner JSON output
|
|
43
|
+
warnings.filterwarnings('ignore')
|
|
44
|
+
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
|
|
45
|
+
os.environ['TRANSFORMERS_VERBOSITY'] = 'error'
|
|
46
|
+
os.environ['HF_HUB_DISABLE_PROGRESS_BARS'] = '1'
|
|
47
|
+
logging.getLogger('silentcipher').setLevel(logging.ERROR)
|
|
48
|
+
logging.getLogger('huggingface_hub').setLevel(logging.ERROR)
|
|
49
|
+
logging.getLogger('tqdm').setLevel(logging.ERROR)
|
|
50
|
+
|
|
51
|
+
def check_dependencies():
|
|
52
|
+
"""Check if required packages are installed."""
|
|
53
|
+
missing = []
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
import silentcipher
|
|
57
|
+
except ImportError:
|
|
58
|
+
missing.append('silentcipher')
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
import librosa
|
|
62
|
+
except ImportError:
|
|
63
|
+
missing.append('librosa')
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
import soundfile
|
|
67
|
+
except ImportError:
|
|
68
|
+
missing.append('soundfile')
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
import numpy
|
|
72
|
+
except ImportError:
|
|
73
|
+
missing.append('numpy')
|
|
74
|
+
|
|
75
|
+
if missing:
|
|
76
|
+
print(json.dumps({
|
|
77
|
+
'error': 'missing_dependencies',
|
|
78
|
+
'message': f'Missing Python packages: {", ".join(missing)}',
|
|
79
|
+
'install': f'pip install {" ".join(missing)}'
|
|
80
|
+
}))
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SuppressStderr:
|
|
85
|
+
"""Context manager to suppress stderr output during model operations."""
|
|
86
|
+
def __init__(self):
|
|
87
|
+
self._original_stderr = None
|
|
88
|
+
self._devnull = None
|
|
89
|
+
|
|
90
|
+
def __enter__(self):
|
|
91
|
+
self._original_stderr = sys.stderr
|
|
92
|
+
self._devnull = open(os.devnull, 'w')
|
|
93
|
+
sys.stderr = self._devnull
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def __exit__(self, *args):
|
|
97
|
+
sys.stderr = self._original_stderr
|
|
98
|
+
if self._devnull:
|
|
99
|
+
self._devnull.close()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class CaptureOutput:
|
|
103
|
+
"""Context manager to capture stdout and stderr into buffers instead of /dev/null.
|
|
104
|
+
Prevents model output from corrupting JSON on stdout while preserving
|
|
105
|
+
error messages for diagnostics."""
|
|
106
|
+
def __init__(self):
|
|
107
|
+
self._original_stdout = None
|
|
108
|
+
self._original_stderr = None
|
|
109
|
+
self._stdout_buf = None
|
|
110
|
+
self._stderr_buf = None
|
|
111
|
+
|
|
112
|
+
def __enter__(self):
|
|
113
|
+
import io
|
|
114
|
+
self._original_stdout = sys.stdout
|
|
115
|
+
self._original_stderr = sys.stderr
|
|
116
|
+
self._stdout_buf = io.StringIO()
|
|
117
|
+
self._stderr_buf = io.StringIO()
|
|
118
|
+
sys.stdout = self._stdout_buf
|
|
119
|
+
sys.stderr = self._stderr_buf
|
|
120
|
+
return self
|
|
121
|
+
|
|
122
|
+
def __exit__(self, *args):
|
|
123
|
+
sys.stdout = self._original_stdout
|
|
124
|
+
sys.stderr = self._original_stderr
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def stdout(self):
|
|
128
|
+
return self._stdout_buf.getvalue() if self._stdout_buf else ''
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def stderr(self):
|
|
132
|
+
return self._stderr_buf.getvalue() if self._stderr_buf else ''
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_model(sample_rate=44100):
|
|
136
|
+
"""Load SilentCipher model for the given sample rate."""
|
|
137
|
+
import silentcipher
|
|
138
|
+
import torch
|
|
139
|
+
|
|
140
|
+
# Determine device
|
|
141
|
+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
|
142
|
+
|
|
143
|
+
# Select model type based on sample rate
|
|
144
|
+
if sample_rate == 44100:
|
|
145
|
+
model_type = '44.1k'
|
|
146
|
+
elif sample_rate == 16000:
|
|
147
|
+
model_type = '16k'
|
|
148
|
+
else:
|
|
149
|
+
# Resample to 44.1k if other sample rate
|
|
150
|
+
model_type = '44.1k'
|
|
151
|
+
|
|
152
|
+
# Load model (cached after first download) - capture output
|
|
153
|
+
cap = CaptureOutput()
|
|
154
|
+
with cap:
|
|
155
|
+
model = silentcipher.get_model(
|
|
156
|
+
model_type=model_type,
|
|
157
|
+
device=device
|
|
158
|
+
)
|
|
159
|
+
if cap.stderr:
|
|
160
|
+
print(f"[SilentCipher model load stderr]: {cap.stderr}", file=sys.stderr)
|
|
161
|
+
|
|
162
|
+
return model, device, model_type
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def cleanup_gpu():
|
|
166
|
+
"""Explicitly release GPU memory to prevent memory leaks between calls."""
|
|
167
|
+
import gc
|
|
168
|
+
import torch
|
|
169
|
+
gc.collect()
|
|
170
|
+
if torch.cuda.is_available():
|
|
171
|
+
torch.cuda.empty_cache()
|
|
172
|
+
torch.cuda.synchronize()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def embed_watermark(audio_path, output_path, message, target_sr=44100):
|
|
176
|
+
"""
|
|
177
|
+
Embed a watermark message into an audio file.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
audio_path: Path to input audio file
|
|
181
|
+
output_path: Path to save watermarked audio
|
|
182
|
+
message: List of 5 integers (0-255 each)
|
|
183
|
+
target_sr: Target sample rate (44100 or 16000)
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
dict with success status, SDR, and message
|
|
187
|
+
"""
|
|
188
|
+
import librosa
|
|
189
|
+
import soundfile as sf
|
|
190
|
+
import numpy as np
|
|
191
|
+
import torch
|
|
192
|
+
|
|
193
|
+
if not os.path.exists(audio_path):
|
|
194
|
+
raise FileNotFoundError(f'Audio file not found: {audio_path}')
|
|
195
|
+
|
|
196
|
+
# Validate message
|
|
197
|
+
if len(message) != 5:
|
|
198
|
+
raise ValueError(f'Message must be exactly 5 integers, got {len(message)}')
|
|
199
|
+
for i, val in enumerate(message):
|
|
200
|
+
if not 0 <= val <= 255:
|
|
201
|
+
raise ValueError(f'Message[{i}] must be 0-255, got {val}')
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
# Load audio
|
|
205
|
+
audio, sr = librosa.load(audio_path, sr=target_sr, mono=True)
|
|
206
|
+
duration = len(audio) / sr
|
|
207
|
+
|
|
208
|
+
# Minimum duration check (SilentCipher needs at least ~1 second)
|
|
209
|
+
if duration < 1.0:
|
|
210
|
+
raise ValueError(f'Audio too short: {duration:.2f}s (minimum 1.0s)')
|
|
211
|
+
|
|
212
|
+
# Load model
|
|
213
|
+
model, device, model_type = get_model(target_sr)
|
|
214
|
+
|
|
215
|
+
# Embed watermark - capture stdout/stderr from silentcipher library
|
|
216
|
+
# SilentCipher expects message as list of 5 integers [0-255]
|
|
217
|
+
embed_cap = CaptureOutput()
|
|
218
|
+
with embed_cap:
|
|
219
|
+
encoded_audio, sdr = model.encode_wav(audio, sr, message)
|
|
220
|
+
if embed_cap.stderr:
|
|
221
|
+
print(f"[SilentCipher embed stderr]: {embed_cap.stderr}", file=sys.stderr)
|
|
222
|
+
if embed_cap.stdout:
|
|
223
|
+
print(f"[SilentCipher embed stdout]: {embed_cap.stdout}", file=sys.stderr)
|
|
224
|
+
|
|
225
|
+
# Save output
|
|
226
|
+
sf.write(output_path, encoded_audio, sr)
|
|
227
|
+
|
|
228
|
+
# Clean up model and GPU memory
|
|
229
|
+
del model
|
|
230
|
+
del audio
|
|
231
|
+
del encoded_audio
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
'success': True,
|
|
235
|
+
'sdr': float(sdr), # Signal-to-Distortion Ratio (higher = better quality)
|
|
236
|
+
'message': message,
|
|
237
|
+
'duration': duration,
|
|
238
|
+
'sample_rate': sr,
|
|
239
|
+
'device': device,
|
|
240
|
+
'model_type': model_type
|
|
241
|
+
}
|
|
242
|
+
finally:
|
|
243
|
+
# Always clean up GPU memory
|
|
244
|
+
cleanup_gpu()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def extract_watermark(audio_path, target_sr=44100, phase_shift_decoding=True):
|
|
248
|
+
"""
|
|
249
|
+
Extract watermark message from an audio file.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
audio_path: Path to watermarked audio file
|
|
253
|
+
target_sr: Target sample rate for processing
|
|
254
|
+
phase_shift_decoding: Enable for better robustness to crops (slower)
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
dict with success status, message, and confidence
|
|
258
|
+
"""
|
|
259
|
+
import librosa
|
|
260
|
+
import numpy as np
|
|
261
|
+
|
|
262
|
+
if not os.path.exists(audio_path):
|
|
263
|
+
raise FileNotFoundError(f'Audio file not found: {audio_path}')
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
# Load audio
|
|
267
|
+
audio, sr = librosa.load(audio_path, sr=target_sr, mono=True)
|
|
268
|
+
duration = len(audio) / sr
|
|
269
|
+
|
|
270
|
+
# Load model
|
|
271
|
+
model, device, model_type = get_model(target_sr)
|
|
272
|
+
|
|
273
|
+
# Extract watermark - capture stdout/stderr from silentcipher library
|
|
274
|
+
# phase_shift_decoding=True makes decoder more robust to audio crops
|
|
275
|
+
extract_cap = CaptureOutput()
|
|
276
|
+
with extract_cap:
|
|
277
|
+
result = model.decode_wav(audio, sr, phase_shift_decoding=phase_shift_decoding)
|
|
278
|
+
if extract_cap.stderr:
|
|
279
|
+
print(f"[SilentCipher extract stderr]: {extract_cap.stderr}", file=sys.stderr)
|
|
280
|
+
if extract_cap.stdout:
|
|
281
|
+
print(f"[SilentCipher extract stdout]: {extract_cap.stdout}", file=sys.stderr)
|
|
282
|
+
|
|
283
|
+
# Clean up model
|
|
284
|
+
del model
|
|
285
|
+
del audio
|
|
286
|
+
|
|
287
|
+
# Status can be True, 'success', or truthy value depending on version
|
|
288
|
+
status_ok = result.get('status') in (True, 'success', 'True') or result.get('status') == True
|
|
289
|
+
|
|
290
|
+
if status_ok and result.get('messages'):
|
|
291
|
+
# Get the first (and typically only) message
|
|
292
|
+
message = result['messages'][0] if result['messages'] else None
|
|
293
|
+
confidence = result['confidences'][0] if result['confidences'] else 0.0
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
'success': True,
|
|
297
|
+
'detected': True,
|
|
298
|
+
'message': list(message) if message is not None else None,
|
|
299
|
+
'confidence': float(confidence),
|
|
300
|
+
'duration': duration,
|
|
301
|
+
'sample_rate': sr,
|
|
302
|
+
'device': device,
|
|
303
|
+
'model_type': model_type
|
|
304
|
+
}
|
|
305
|
+
else:
|
|
306
|
+
return {
|
|
307
|
+
'success': True,
|
|
308
|
+
'detected': False,
|
|
309
|
+
'message': None,
|
|
310
|
+
'confidence': 0.0,
|
|
311
|
+
'duration': duration,
|
|
312
|
+
'sample_rate': sr,
|
|
313
|
+
'device': device,
|
|
314
|
+
'status_detail': str(result.get('status', 'unknown'))
|
|
315
|
+
}
|
|
316
|
+
finally:
|
|
317
|
+
# Always clean up GPU memory
|
|
318
|
+
cleanup_gpu()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def check_environment():
|
|
322
|
+
"""Check if SilentCipher environment is properly set up."""
|
|
323
|
+
check_dependencies()
|
|
324
|
+
|
|
325
|
+
import torch
|
|
326
|
+
import silentcipher
|
|
327
|
+
|
|
328
|
+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
'available': True,
|
|
332
|
+
'message': 'SilentCipher environment ready',
|
|
333
|
+
'details': {
|
|
334
|
+
'device': device,
|
|
335
|
+
'cuda_available': torch.cuda.is_available(),
|
|
336
|
+
'silentcipher_version': getattr(silentcipher, '__version__', 'unknown')
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def main():
|
|
342
|
+
parser = argparse.ArgumentParser(description='ORBIT SilentCipher Watermarking')
|
|
343
|
+
subparsers = parser.add_subparsers(dest='command', help='Command to run')
|
|
344
|
+
|
|
345
|
+
# Embed command
|
|
346
|
+
embed_parser = subparsers.add_parser('embed', help='Embed watermark into audio')
|
|
347
|
+
embed_parser.add_argument('audio_path', help='Path to input audio file')
|
|
348
|
+
embed_parser.add_argument('output_path', help='Path to save watermarked audio')
|
|
349
|
+
embed_parser.add_argument('--message', required=True,
|
|
350
|
+
help='Message as comma-separated integers (5 values, 0-255 each)')
|
|
351
|
+
embed_parser.add_argument('--sample-rate', type=int, default=44100,
|
|
352
|
+
help='Target sample rate (default: 44100)')
|
|
353
|
+
|
|
354
|
+
# Extract command
|
|
355
|
+
extract_parser = subparsers.add_parser('extract', help='Extract watermark from audio')
|
|
356
|
+
extract_parser.add_argument('audio_path', help='Path to watermarked audio file')
|
|
357
|
+
extract_parser.add_argument('--sample-rate', type=int, default=44100,
|
|
358
|
+
help='Target sample rate (default: 44100)')
|
|
359
|
+
extract_parser.add_argument('--no-phase-shift', action='store_true',
|
|
360
|
+
help='Disable phase shift decoding (faster but less robust)')
|
|
361
|
+
|
|
362
|
+
# Check command
|
|
363
|
+
check_parser = subparsers.add_parser('check', help='Check environment')
|
|
364
|
+
|
|
365
|
+
args = parser.parse_args()
|
|
366
|
+
|
|
367
|
+
if args.command is None:
|
|
368
|
+
parser.print_help()
|
|
369
|
+
sys.exit(1)
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
if args.command == 'check':
|
|
373
|
+
result = check_environment()
|
|
374
|
+
print(json.dumps(result))
|
|
375
|
+
|
|
376
|
+
elif args.command == 'embed':
|
|
377
|
+
check_dependencies()
|
|
378
|
+
|
|
379
|
+
# Parse message
|
|
380
|
+
message = [int(x.strip()) for x in args.message.split(',')]
|
|
381
|
+
|
|
382
|
+
result = embed_watermark(
|
|
383
|
+
args.audio_path,
|
|
384
|
+
args.output_path,
|
|
385
|
+
message,
|
|
386
|
+
target_sr=args.sample_rate
|
|
387
|
+
)
|
|
388
|
+
print(json.dumps(result))
|
|
389
|
+
|
|
390
|
+
elif args.command == 'extract':
|
|
391
|
+
check_dependencies()
|
|
392
|
+
|
|
393
|
+
result = extract_watermark(
|
|
394
|
+
args.audio_path,
|
|
395
|
+
target_sr=args.sample_rate,
|
|
396
|
+
phase_shift_decoding=not args.no_phase_shift
|
|
397
|
+
)
|
|
398
|
+
print(json.dumps(result))
|
|
399
|
+
|
|
400
|
+
except FileNotFoundError as e:
|
|
401
|
+
print(json.dumps({
|
|
402
|
+
'error': 'file_not_found',
|
|
403
|
+
'message': str(e)
|
|
404
|
+
}))
|
|
405
|
+
sys.exit(1)
|
|
406
|
+
except ValueError as e:
|
|
407
|
+
print(json.dumps({
|
|
408
|
+
'error': 'validation_error',
|
|
409
|
+
'message': str(e)
|
|
410
|
+
}))
|
|
411
|
+
sys.exit(1)
|
|
412
|
+
except Exception as e:
|
|
413
|
+
print(json.dumps({
|
|
414
|
+
'error': 'processing_error',
|
|
415
|
+
'message': str(e),
|
|
416
|
+
'type': type(e).__name__
|
|
417
|
+
}))
|
|
418
|
+
sys.exit(1)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
if __name__ == '__main__':
|
|
422
|
+
main()
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orbit-watermark
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: High-robustness neural audio watermarking using SilentCipher (Sony AI) & Spread Spectrum fallback
|
|
5
|
+
License: ISC
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: ISC License (ISCL)
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: torch<=2.0.0
|
|
13
|
+
Requires-Dist: silentcipher
|
|
14
|
+
Requires-Dist: librosa>=0.10.0
|
|
15
|
+
Requires-Dist: soundfile>=0.12.0
|
|
16
|
+
Requires-Dist: numpy>=1.20.0
|
|
17
|
+
|
|
18
|
+
# orbit_watermark
|
|
19
|
+
|
|
20
|
+
Standalone Python package for ORBIT WATERMARK analysis.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
orbit_watermark/__init__.py
|
|
4
|
+
orbit_watermark/silentcipher_watermark.py
|
|
5
|
+
orbit_watermark.egg-info/PKG-INFO
|
|
6
|
+
orbit_watermark.egg-info/SOURCES.txt
|
|
7
|
+
orbit_watermark.egg-info/dependency_links.txt
|
|
8
|
+
orbit_watermark.egg-info/entry_points.txt
|
|
9
|
+
orbit_watermark.egg-info/requires.txt
|
|
10
|
+
orbit_watermark.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
orbit_watermark
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "orbit-watermark"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "High-robustness neural audio watermarking using SilentCipher (Sony AI) & Spread Spectrum fallback"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "ISC" }
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"License :: OSI Approved :: ISC License (ISCL)",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis"
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"torch<=2.0.0",
|
|
20
|
+
"silentcipher",
|
|
21
|
+
"librosa>=0.10.0",
|
|
22
|
+
"soundfile>=0.12.0",
|
|
23
|
+
"numpy>=1.20.0"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
packages = ["orbit_watermark"]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
orbit-watermark = "orbit_watermark.silentcipher_watermark:main"
|