"""Produce pinned local Qwen Korean voice assets; validation never loads a model.""" import argparse from datetime import datetime, timezone import hashlib import importlib.metadata import json from pathlib import Path import re import subprocess import sys import time import uuid import numpy as np import soundfile as sf MODEL_ID = 'Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice' def digest(value): return hashlib.sha256(json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode('utf-8')).hexdigest() def file_hash(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest() def load_json(path): return json.loads(Path(path).read_text(encoding='utf-8')) def write_json(path, value): Path(path).write_text(json.dumps(value, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') def validate_inputs(catalog, profile): if catalog.get('schemaVersion') != 1 or profile.get('schemaVersion') != 1: raise ValueError('catalog/profile schemaVersion must be 1') clips = catalog.get('clips') if not isinstance(clips, list) or not clips: raise ValueError('catalog must contain clips') ids = set() for clip in clips: clip_id = clip.get('id', '') if not re.fullmatch(r'[a-z][a-z0-9_]*', clip_id) or clip_id in ids: raise ValueError(f'{clip_id}: invalid or duplicate ID') ids.add(clip_id) if not isinstance(clip.get('text'), str) or not clip['text'].strip(): raise ValueError(f'{clip_id}: text is empty') if not re.search('[가-힣]', clip['text']) or re.search(r'[0-9]', clip['text']): raise ValueError(f'{clip_id}: use a complete Korean utterance, not numeral digits') if not isinstance(clip.get('required'), bool) or not clip.get('usage'): raise ValueError(f'{clip_id}: required/usage missing') if not isinstance(clip.get('stageIds'), list) or not clip['stageIds'] or any( type(n) is not int or n < 1 or n > 30 for n in clip['stageIds']): raise ValueError(f'{clip_id}: stageIds must be within 1..30') for field in ('modelRevision', 'instruct', 'device', 'dtype', 'attention', 'generationParameters', 'seed'): if field not in profile: raise ValueError(f'profile: missing {field}') if profile.get('modelId') != MODEL_ID or profile.get('language') != 'Korean' or profile.get('speaker') != 'Sohee': raise ValueError('profile must retain approved model/Korean/Sohee') if not re.fullmatch('[0-9a-f]{40}', profile['modelRevision']): raise ValueError('profile must pin a full model revision') if profile['device'] != 'cuda:0' or profile['dtype'] not in ('bfloat16', 'float16') or profile['attention'] not in ('sdpa', 'eager'): raise ValueError('profile requires approved CUDA configuration') if not isinstance(profile['generationParameters'], dict) or type(profile['seed']) is not int: raise ValueError('invalid generation settings') def select_clips(catalog, ids=None, required_only=False): by_id = {c['id']: c for c in catalog['clips']} if ids is None: return [c for c in catalog['clips'] if not required_only or c['required']] missing = set(ids) - by_id.keys() if missing: raise ValueError('unknown IDs: ' + ', '.join(sorted(missing))) if not ids or len(ids) != len(set(ids)): raise ValueError('requested IDs must be nonempty and unique') return [by_id[key] for key in ids] def waveform_metadata(path): samples, sample_rate = sf.read(path, dtype='float32', always_2d=True) if sample_rate <= 0 or samples.size == 0 or not np.isfinite(samples).all(): raise ValueError('empty or nonfinite waveform') peak = float(np.max(np.abs(samples))) return {'sha256': file_hash(path), 'sampleRate': sample_rate, 'channels': samples.shape[1], 'frames': samples.shape[0], 'duration': samples.shape[0] / sample_rate, 'peak': peak, 'rms': float(np.sqrt(np.mean(samples ** 2))), 'qualityWarnings': (['near silence'] if peak < .001 else []) + (['clipping candidate'] if peak >= .999 else [])} def load_run_inputs(manifest, root): run_id = manifest.get('runId', '') if not isinstance(run_id, str) or not re.fullmatch(r'[A-Za-z0-9_]+', run_id): raise ValueError('invalid input snapshot runId') references = manifest.get('inputSnapshots', {'catalog': 'catalog.json', 'profile': 'profile.json'}) if references != {'catalog': 'catalog.json', 'profile': 'profile.json'}: raise ValueError('input snapshots must reside in their immutable run directory') run_dir = Path(root) / 'generated' / run_id catalog = load_json(run_dir / references['catalog']) profile = load_json(run_dir / references['profile']) if not isinstance(catalog, dict) or not isinstance(profile, dict): raise ValueError('input snapshots must contain JSON objects') if digest(catalog) != manifest.get('catalogHash') or digest(profile) != manifest.get('profileHash'): raise ValueError('input snapshot hash differs from manifest') validate_inputs(catalog, profile) if profile['modelRevision'] != manifest.get('modelRevision'): raise ValueError('input snapshot model revision differs from manifest') clips = {clip['id']: clip for clip in catalog['clips']} for entry in manifest.get('clips', []): clip = clips.get(entry.get('id')) if not clip or entry.get('textHash') != digest(clip['text']) or entry.get('settingsHash') != digest(profile): raise ValueError(f"{entry.get('id')}: input snapshot differs from clip text/settings hashes") return catalog, profile def clip_errors(entry, clip, profile, root): errors = [] if entry.get('result') not in ('generated', 'reused'): return [f"{clip['id']}: no successful generation"] if entry.get('textHash') != digest(clip['text']) or entry.get('settingsHash') != digest(profile): errors.append(f"{clip['id']}: current text/profile differs") try: path = (root / entry['file']).resolve() if not path.is_relative_to(root.resolve()): raise ValueError('waveform escapes source directory') actual = waveform_metadata(path) for key in ('sha256', 'sampleRate', 'channels', 'frames', 'duration'): if actual[key] != entry.get(key): errors.append(f"{clip['id']}: {key} mismatch") if actual['channels'] != 1: errors.append(f"{clip['id']}: expected mono") run_id = entry['originalRunId'] if not isinstance(run_id, str) or not re.fullmatch(r'[A-Za-z0-9_]+', run_id): raise ValueError('invalid original generation runId') origin = load_json(root / 'generated' / run_id / 'manifest.json') if origin.get('runId') != run_id: raise ValueError('original manifest runId mismatch') load_run_inputs(origin, root) original_entries = [c for c in origin['clips'] if c.get('id') == clip['id']] if len(original_entries) != 1 or original_entries[0].get('result') != 'generated': raise ValueError('missing successful original generation entry') for key in ('file', 'sha256', 'textHash', 'settingsHash'): if original_entries[0].get(key) != entry.get(key): raise ValueError(f'original generation {key} differs') except (KeyError, OSError, ValueError, sf.LibsndfileError) as error: errors.append(f"{clip['id']}: invalid source artifact: {error}") return errors def validate_manifest(manifest, catalog, profile, root, ids=None): validate_inputs(catalog, profile) requested = select_clips(catalog, ids, required_only=ids is None) errors = [] try: load_run_inputs(manifest, root) except (KeyError, OSError, ValueError, TypeError) as error: errors.append(f'input snapshots invalid: {error}') if manifest.get('schemaVersion') != 1 or manifest.get('modelRevision') != profile['modelRevision']: errors.append('manifest schema/revision differs') if manifest.get('catalogHash') != digest(catalog) or manifest.get('profileHash') != digest(profile): errors.append('manifest does not describe current catalog/profile') entries = manifest.get('clips', []) by_id = {c.get('id'): c for c in entries} if len(by_id) != len(entries): errors.append('manifest contains duplicate IDs') for clip in requested: if clip['id'] not in by_id: errors.append(f"{clip['id']}: missing required/requested ID") else: errors.extend(clip_errors(by_id[clip['id']], clip, profile, root)) return errors def reusable_entries(root, catalog, profile): result = {} by_id = {c['id']: c for c in catalog['clips']} for path in sorted((root / 'generated').glob('*/manifest.json'), reverse=True): try: manifest = load_json(path) load_run_inputs(manifest, root) for entry in manifest.get('clips', []): clip = by_id.get(entry.get('id')) if clip and clip['id'] not in result and not clip_errors(entry, clip, profile, root): result[clip['id']] = entry except (OSError, ValueError, TypeError): continue return result def reuse_environment(profile, entries, root): environment = {'python': sys.version, 'packages': {name: importlib.metadata.version(name) for name in ('qwen-tts', 'torch', 'torchaudio', 'transformers', 'soundfile', 'huggingface-hub')}, 'modelLoad': 'not_needed', 'requestedDevice': profile['device'], 'sourceGenerations': []} try: probe = subprocess.run(['nvidia-smi', '-i', '0', '--query-gpu=name', '--format=csv,noheader'], check=True, capture_output=True, text=True, timeout=5) environment['device'] = probe.stdout.strip() environment['deviceProbe'] = 'nvidia-smi; no model or CUDA inference executed' except (OSError, subprocess.SubprocessError) as error: environment['deviceProbeError'] = str(error) for run_id in sorted({entry['originalRunId'] for entry in entries.values()}): manifest_path = Path(root) / 'generated' / run_id / 'manifest.json' source = load_json(manifest_path) environment['sourceGenerations'].append({'runId': run_id, 'manifest': manifest_path.relative_to(root).as_posix(), 'manifestSha256': file_hash(manifest_path), 'environment': source['environment']}) return environment def generate(catalog, profile, root, ids=None, synthesize=None, environment=None, regenerate=False): validate_inputs(catalog, profile) selected = select_clips(catalog, ids) root = Path(root) previous = {} if regenerate else reusable_entries(root, catalog, profile) run_id = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ_') + uuid.uuid4().hex[:8] run_dir = root / 'generated' / run_id run_dir.mkdir(parents=True) write_json(run_dir / 'catalog.json', catalog) write_json(run_dir / 'profile.json', profile) manifest = {'schemaVersion': 1, 'runId': run_id, 'createdUtc': datetime.now(timezone.utc).isoformat(), 'modelRevision': profile['modelRevision'], 'catalogHash': digest(catalog), 'profileHash': digest(profile), 'scope': 'selected' if ids else 'all', 'regenerateRequested': regenerate, 'inputSnapshots': {'catalog': 'catalog.json', 'profile': 'profile.json'}, 'requestedIds': [c['id'] for c in selected], 'environment': environment or {}, 'clips': []} try: for clip in selected: clip_id = clip['id'] if clip_id in previous: entry = dict(previous[clip_id], result='reused') else: entry = {'id': clip_id, 'textHash': digest(clip['text']), 'settingsHash': digest(profile), 'originalRunId': run_id, 'file': f'generated/{run_id}/{clip_id}.wav', 'review': 'not_listened'} started = time.perf_counter() try: if synthesize is None: raise ValueError('no synthesizer available') samples, rate = synthesize(clip) samples = np.asarray(samples, dtype=np.float32) if samples.ndim != 1 or not samples.size or not np.isfinite(samples).all() or rate <= 0: raise ValueError('synthesizer returned empty/nonfinite/nonmono audio') path = root / entry['file'] sf.write(path, samples, rate, subtype='FLOAT') entry.update(waveform_metadata(path), result='generated', generationSeconds=time.perf_counter() - started) except Exception as error: entry.update(result='failed', error=f'{type(error).__name__}: {error}') manifest['clips'].append(entry) write_json(run_dir / 'manifest.json', manifest) print(f"{clip_id}: {entry['result']}", flush=True) finally: write_json(run_dir / 'manifest.json', manifest) return manifest def load_synthesizer(profile): import torch from huggingface_hub import snapshot_download from qwen_tts import Qwen3TTSModel if not torch.cuda.is_available(): raise RuntimeError('CUDA unavailable; refusing an unapproved CPU fallback') probe = torch.arange(8, device=profile['device'], dtype=torch.float32).square().sum().item() if probe != 140: raise RuntimeError('CUDA arithmetic probe failed') snapshot = snapshot_download(repo_id=profile['modelId'], revision=profile['modelRevision']) started = time.perf_counter() torch.cuda.reset_peak_memory_stats() model = Qwen3TTSModel.from_pretrained(snapshot, device_map=profile['device'], dtype=getattr(torch, profile['dtype']), attn_implementation=profile['attention']) model_device = str(next(model.model.parameters()).device) if model_device != profile['device']: raise RuntimeError(f'loaded model device {model_device} differs from requested CUDA device') if 'sohee' not in [s.lower() for s in model.get_supported_speakers()] or 'korean' not in [s.lower() for s in model.get_supported_languages()]: raise RuntimeError('Sohee/Korean not supported by loaded model') environment = {'python': sys.version, 'packages': {name: importlib.metadata.version(name) for name in ('qwen-tts', 'torch', 'torchaudio', 'transformers', 'soundfile', 'huggingface-hub')}, 'device': torch.cuda.get_device_name(0), 'cuda': torch.version.cuda, 'modelDevice': model_device, 'cudaProbe': probe, 'modelLoadSeconds': time.perf_counter() - started, 'modelPeakVramBytes': torch.cuda.max_memory_allocated(), 'modelDefaults': model.generate_defaults, 'effectiveGenerationParameters': model._merge_generate_kwargs(**profile['generationParameters'])} def synthesize(clip): torch.manual_seed(profile['seed']) torch.cuda.manual_seed_all(profile['seed']) waves, rate = model.generate_custom_voice(text=clip['text'], language=profile['language'], speaker=profile['speaker'], instruct=profile['instruct'], non_streaming_mode=True, **profile['generationParameters']) torch.cuda.synchronize() environment['peakVramBytes'] = torch.cuda.max_memory_allocated() return waves[0], rate return synthesize, environment def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--catalog', required=True, type=Path) parser.add_argument('--profile', required=True, type=Path) parser.add_argument('--ids', help='comma separated IDs; absent means all catalog clips') parser.add_argument('--validate-only', action='store_true') parser.add_argument('--manifest', type=Path) parser.add_argument('--regenerate', action='store_true', help='create a new take for selected IDs, preserving original runs') args = parser.parse_args() ids = args.ids.split(',') if args.ids else None try: catalog, profile = load_json(args.catalog), load_json(args.profile) validate_inputs(catalog, profile) root = args.catalog.parent if args.validate_only: if not args.manifest: raise ValueError('--validate-only requires --manifest') manifest = load_json(args.manifest) errors = validate_manifest(manifest, catalog, profile, root, ids) else: selected = select_clips(catalog, ids) reusable = reusable_entries(root, catalog, profile) if not args.regenerate and all(c['id'] in reusable for c in selected): synthesize = None environment = reuse_environment(profile, {c['id']: reusable[c['id']] for c in selected}, root) else: synthesize, environment = load_synthesizer(profile) manifest = generate(catalog, profile, root, ids, synthesize, environment, args.regenerate) print('Manifest: ' + str(root / 'generated' / manifest['runId'] / 'manifest.json')) errors = validate_manifest(manifest, catalog, profile, root, ids) print(json.dumps({'validationScope': 'selected' if ids else 'all_required', 'ids': ids, 'errors': errors}, ensure_ascii=False)) return 1 if errors else 0 except Exception as error: print(f'{type(error).__name__}: {error}', file=sys.stderr) return 1 if __name__ == '__main__': sys.exit(main())