colabcola 0.1.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.
Files changed (81) hide show
  1. colabcola/__init__.py +195 -0
  2. colabcola/__main__.py +3 -0
  3. colabcola/_hifigan/__init__.py +1 -0
  4. colabcola/_hifigan/audio_diagnostic.py +292 -0
  5. colabcola/_hifigan/extract_audio_diagnostic_input.py +117 -0
  6. colabcola/_hifigan/measure_full_transport.py +128 -0
  7. colabcola/_hifigan/profile.json +127 -0
  8. colabcola/_hifigan/resume_state.py +218 -0
  9. colabcola/_hifigan/train_colabcola.py +709 -0
  10. colabcola/_vendor/__init__.py +1 -0
  11. colabcola/_vendor/colab_cli/LICENSE +202 -0
  12. colabcola/_vendor/colab_cli/__init__.py +1 -0
  13. colabcola/_vendor/colab_cli/client.py +361 -0
  14. colabcola/_vendor/colab_cli/consumption.py +56 -0
  15. colabcola/_vendor/colab_cli/oauth_config.json +11 -0
  16. colabcola/_vendor/colab_cli/runtime.py +279 -0
  17. colabcola/_vendor/colab_cli/state.py +159 -0
  18. colabcola/_vendor/colab_cli/utils.py +104 -0
  19. colabcola/_vendor/jupyter_kernel_client/LICENSE +29 -0
  20. colabcola/_vendor/jupyter_kernel_client/__init__.py +27 -0
  21. colabcola/_vendor/jupyter_kernel_client/_version.py +7 -0
  22. colabcola/_vendor/jupyter_kernel_client/client.py +624 -0
  23. colabcola/_vendor/jupyter_kernel_client/constants.py +7 -0
  24. colabcola/_vendor/jupyter_kernel_client/konsoleapp.py +227 -0
  25. colabcola/_vendor/jupyter_kernel_client/log.py +37 -0
  26. colabcola/_vendor/jupyter_kernel_client/manager.py +336 -0
  27. colabcola/_vendor/jupyter_kernel_client/models.py +13 -0
  28. colabcola/_vendor/jupyter_kernel_client/shell.py +64 -0
  29. colabcola/_vendor/jupyter_kernel_client/snippets.py +221 -0
  30. colabcola/_vendor/jupyter_kernel_client/utils.py +171 -0
  31. colabcola/_vendor/jupyter_kernel_client/wsclient.py +1361 -0
  32. colabcola/_vendor/provenance.json +124 -0
  33. colabcola/_version.py +3 -0
  34. colabcola/bundle.py +637 -0
  35. colabcola/checkpoint_runtime.py +615 -0
  36. colabcola/cli.py +470 -0
  37. colabcola/client_downloads.py +276 -0
  38. colabcola/client_handoff.py +129 -0
  39. colabcola/cloud_project.py +351 -0
  40. colabcola/compiler.py +202 -0
  41. colabcola/console.py +63 -0
  42. colabcola/controller.py +330 -0
  43. colabcola/drive.py +564 -0
  44. colabcola/drive_oauth.py +208 -0
  45. colabcola/errors.py +14 -0
  46. colabcola/google_drive.py +575 -0
  47. colabcola/gpu_check.py +30 -0
  48. colabcola/hifigan.py +467 -0
  49. colabcola/invocation.py +60 -0
  50. colabcola/isolation_runtime.py +195 -0
  51. colabcola/lifecycle.py +637 -0
  52. colabcola/lifecycle_auth.py +166 -0
  53. colabcola/lifecycle_io.py +459 -0
  54. colabcola/lifecycle_kernel.py +189 -0
  55. colabcola/lifecycle_provider.py +417 -0
  56. colabcola/lifecycle_rest.py +213 -0
  57. colabcola/lifecycle_worker.py +462 -0
  58. colabcola/ljspeech.py +786 -0
  59. colabcola/managed.py +397 -0
  60. colabcola/notebook.py +286 -0
  61. colabcola/onboarding.py +497 -0
  62. colabcola/package_guide.py +379 -0
  63. colabcola/packager.py +390 -0
  64. colabcola/preflight.py +270 -0
  65. colabcola/profiles.py +145 -0
  66. colabcola/project_setup.py +395 -0
  67. colabcola/publisher_oauth.json +1 -0
  68. colabcola/python_profiles.py +8 -0
  69. colabcola/runtime.py +1515 -0
  70. colabcola/spec.py +750 -0
  71. colabcola/transport.py +188 -0
  72. colabcola/workflow.py +227 -0
  73. colabcola-0.1.0.dist-info/METADATA +133 -0
  74. colabcola-0.1.0.dist-info/RECORD +81 -0
  75. colabcola-0.1.0.dist-info/WHEEL +5 -0
  76. colabcola-0.1.0.dist-info/entry_points.txt +2 -0
  77. colabcola-0.1.0.dist-info/licenses/LICENSE +202 -0
  78. colabcola-0.1.0.dist-info/licenses/NOTICE +24 -0
  79. colabcola-0.1.0.dist-info/licenses/src/colabcola/_vendor/colab_cli/LICENSE +202 -0
  80. colabcola-0.1.0.dist-info/licenses/src/colabcola/_vendor/jupyter_kernel_client/LICENSE +29 -0
  81. colabcola-0.1.0.dist-info/top_level.txt +1 -0
colabcola/__init__.py ADDED
@@ -0,0 +1,195 @@
1
+ """Compile trusted batch jobs into deterministic Google Colab notebooks."""
2
+
3
+ from ._version import __version__
4
+ from .bundle import (
5
+ ARCHIVE_WARNING_BYTES,
6
+ Bundle,
7
+ FileRecord,
8
+ archive_size_warnings,
9
+ build_bundle,
10
+ canonical_json_bytes,
11
+ verify_bundle,
12
+ )
13
+ from .compiler import (
14
+ CompilationResult,
15
+ DriveControllerResult,
16
+ StagedBundleResult,
17
+ compile_drive_controller,
18
+ compile_drive_controller_invocation,
19
+ compile_invocation,
20
+ compile_notebook,
21
+ prepare_drive_bundle,
22
+ prepare_drive_invocation,
23
+ write_notebook,
24
+ )
25
+ from .controller import (
26
+ DriveControllerAuth,
27
+ DriveControllerArtifact,
28
+ build_drive_controller,
29
+ controller_auth_from_settings,
30
+ verify_drive_controller,
31
+ write_drive_controller,
32
+ )
33
+ from .drive import (
34
+ DRIVE_ROOT_FOLDER_NAME,
35
+ DRIVE_STAGING_FOLDER_NAME,
36
+ DriveBackend,
37
+ DriveFile,
38
+ DriveFolder,
39
+ DriveStagingReceipt,
40
+ VerifiedResult,
41
+ authorize_abandoned_cleanup,
42
+ cleanup_drive_staging,
43
+ drive_receipt_for_observed_upload,
44
+ drive_receipt_json_bytes,
45
+ load_drive_receipt,
46
+ record_verified_result,
47
+ stage_drive_bundle,
48
+ verify_drive_receipt,
49
+ write_drive_receipt,
50
+ )
51
+ from .errors import BundleError, DriveTransportError, NotebookError, SpecError
52
+ from .google_drive import (
53
+ DEFAULT_COLAB_SECRET_NAME,
54
+ GOOGLE_DRIVE_SCOPE,
55
+ GoogleDriveApiBackend,
56
+ GoogleDriveSettings,
57
+ GoogleOAuthClient,
58
+ create_google_drive_backend,
59
+ default_google_drive_settings_path,
60
+ google_drive_settings_from_clients,
61
+ load_google_drive_settings,
62
+ load_google_oauth_client,
63
+ verify_google_drive_settings,
64
+ write_google_drive_settings,
65
+ )
66
+ from .hifigan import (
67
+ HiFiGanOverlay,
68
+ HiFiGanProfile,
69
+ HiFiGanRuntimeLock,
70
+ HiFiGanSourceFile,
71
+ default_hifigan_profile_path,
72
+ hifigan_source_payload,
73
+ load_hifigan_profile,
74
+ verify_hifigan_checkout,
75
+ verify_hifigan_profile_assets,
76
+ )
77
+ from .invocation import JobInvocation, bind_job_arguments, load_job_invocation
78
+ from .ljspeech import (
79
+ LJSpeechSelection,
80
+ LJSpeechSubset,
81
+ build_ljspeech_subset,
82
+ ljspeech_subset_payload,
83
+ select_ljspeech_ids,
84
+ verify_ljspeech_subset,
85
+ )
86
+ from .notebook import (
87
+ NotebookArtifact,
88
+ build_notebook,
89
+ encode_archive_chunks,
90
+ verify_notebook,
91
+ )
92
+ from .spec import JobSpec, RemoteInputSpec, RuntimeSpec, load_job_spec
93
+ from .transport import (
94
+ STAGED_BUNDLE_HEADER_BYTES,
95
+ STAGED_BUNDLE_MAGIC,
96
+ STAGED_BUNDLE_SUFFIX,
97
+ StagedBundleArtifact,
98
+ build_staged_bundle,
99
+ verify_staged_bundle,
100
+ verify_staged_bundle_file,
101
+ write_staged_bundle,
102
+ )
103
+
104
+ __all__ = [
105
+ "ARCHIVE_WARNING_BYTES",
106
+ "Bundle",
107
+ "BundleError",
108
+ "CompilationResult",
109
+ "DRIVE_ROOT_FOLDER_NAME",
110
+ "DRIVE_STAGING_FOLDER_NAME",
111
+ "DEFAULT_COLAB_SECRET_NAME",
112
+ "DriveBackend",
113
+ "DriveControllerAuth",
114
+ "DriveControllerArtifact",
115
+ "DriveControllerResult",
116
+ "DriveFile",
117
+ "DriveFolder",
118
+ "DriveStagingReceipt",
119
+ "DriveTransportError",
120
+ "FileRecord",
121
+ "JobInvocation",
122
+ "JobSpec",
123
+ "LJSpeechSelection",
124
+ "LJSpeechSubset",
125
+ "HiFiGanOverlay",
126
+ "HiFiGanProfile",
127
+ "HiFiGanRuntimeLock",
128
+ "HiFiGanSourceFile",
129
+ "NotebookArtifact",
130
+ "NotebookError",
131
+ "RemoteInputSpec",
132
+ "STAGED_BUNDLE_HEADER_BYTES",
133
+ "STAGED_BUNDLE_MAGIC",
134
+ "STAGED_BUNDLE_SUFFIX",
135
+ "SpecError",
136
+ "StagedBundleArtifact",
137
+ "StagedBundleResult",
138
+ "VerifiedResult",
139
+ "authorize_abandoned_cleanup",
140
+ "bind_job_arguments",
141
+ "archive_size_warnings",
142
+ "build_bundle",
143
+ "build_drive_controller",
144
+ "build_notebook",
145
+ "build_staged_bundle",
146
+ "build_ljspeech_subset",
147
+ "canonical_json_bytes",
148
+ "cleanup_drive_staging",
149
+ "compile_drive_controller",
150
+ "compile_drive_controller_invocation",
151
+ "compile_invocation",
152
+ "compile_notebook",
153
+ "default_hifigan_profile_path",
154
+ "drive_receipt_for_observed_upload",
155
+ "drive_receipt_json_bytes",
156
+ "encode_archive_chunks",
157
+ "load_job_invocation",
158
+ "load_job_spec",
159
+ "load_drive_receipt",
160
+ "hifigan_source_payload",
161
+ "load_hifigan_profile",
162
+ "ljspeech_subset_payload",
163
+ "prepare_drive_bundle",
164
+ "prepare_drive_invocation",
165
+ "record_verified_result",
166
+ "select_ljspeech_ids",
167
+ "stage_drive_bundle",
168
+ "verify_bundle",
169
+ "verify_drive_controller",
170
+ "verify_drive_receipt",
171
+ "verify_notebook",
172
+ "verify_staged_bundle",
173
+ "verify_staged_bundle_file",
174
+ "verify_hifigan_checkout",
175
+ "verify_hifigan_profile_assets",
176
+ "verify_ljspeech_subset",
177
+ "write_drive_controller",
178
+ "write_drive_receipt",
179
+ "write_notebook",
180
+ "write_staged_bundle",
181
+ "__version__",
182
+ "GOOGLE_DRIVE_SCOPE",
183
+ "GoogleDriveApiBackend",
184
+ "GoogleDriveSettings",
185
+ "GoogleOAuthClient",
186
+ "RuntimeSpec",
187
+ "controller_auth_from_settings",
188
+ "create_google_drive_backend",
189
+ "default_google_drive_settings_path",
190
+ "google_drive_settings_from_clients",
191
+ "load_google_drive_settings",
192
+ "load_google_oauth_client",
193
+ "verify_google_drive_settings",
194
+ "write_google_drive_settings",
195
+ ]
colabcola/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ """Checked-in assets for the pinned HiFi-GAN Phase 2-3 workload."""
@@ -0,0 +1,292 @@
1
+ """Bounded HiFi-GAN generated-audio diagnostic for a trusted checkpoint.
2
+
3
+ Derived from ``inference.py`` and ``meldataset.py`` in jik876/hifi-gan commit
4
+ 4769534d45265d52a904b850da5a622601885777. The bundled upstream LICENSE
5
+ applies to the derived portions of this file.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import hashlib
12
+ import json
13
+ import os
14
+ import pathlib
15
+ import shutil
16
+
17
+ import numpy as np
18
+ import torch
19
+ from scipy.io.wavfile import read, write
20
+
21
+ import meldataset as upstream_meldataset
22
+ from env import AttrDict
23
+ from models import Generator
24
+
25
+
26
+ MAX_WAV_VALUE = 32768.0
27
+ UPSTREAM_COMMIT = "4769534d45265d52a904b850da5a622601885777"
28
+ EXPECTED_INPUTS = {
29
+ "LJ001-0028.wav": (
30
+ 261_478,
31
+ "58831256095edbcc72346a2db9be39efd39662f442f8115f19da4d55876cfa0e",
32
+ ),
33
+ "LJ001-0068.wav": (
34
+ 402_278,
35
+ "9ec4f7944fdf753a62792aef59da5f2145c0a69d8877fc07c4c77c7189dcdc0e",
36
+ ),
37
+ "LJ002-0149.wav": (
38
+ 349_030,
39
+ "d71743df2a5842ae0130920afe910e53516374dae97e8f24da403efdc4cc9c60",
40
+ ),
41
+ }
42
+
43
+
44
+ def mel_spectrogram(
45
+ y,
46
+ n_fft,
47
+ num_mels,
48
+ sampling_rate,
49
+ hop_size,
50
+ win_size,
51
+ fmin,
52
+ fmax,
53
+ center=False,
54
+ ):
55
+ """Pinned upstream transform adapted to current librosa and PyTorch APIs."""
56
+
57
+ cache_key = f"{fmax}_{y.device}"
58
+ if cache_key not in upstream_meldataset.mel_basis:
59
+ mel = upstream_meldataset.librosa_mel_fn(
60
+ sr=sampling_rate,
61
+ n_fft=n_fft,
62
+ n_mels=num_mels,
63
+ fmin=fmin,
64
+ fmax=fmax,
65
+ )
66
+ upstream_meldataset.mel_basis[cache_key] = torch.from_numpy(mel).float().to(
67
+ y.device
68
+ )
69
+ upstream_meldataset.hann_window[str(y.device)] = torch.hann_window(win_size).to(
70
+ y.device
71
+ )
72
+
73
+ padded = torch.nn.functional.pad(
74
+ y.unsqueeze(1),
75
+ (int((n_fft - hop_size) / 2), int((n_fft - hop_size) / 2)),
76
+ mode="reflect",
77
+ ).squeeze(1)
78
+ spectrum = torch.stft(
79
+ padded,
80
+ n_fft,
81
+ hop_length=hop_size,
82
+ win_length=win_size,
83
+ window=upstream_meldataset.hann_window[str(y.device)],
84
+ center=center,
85
+ pad_mode="reflect",
86
+ normalized=False,
87
+ onesided=True,
88
+ return_complex=True,
89
+ ).abs()
90
+ spectrum = torch.matmul(upstream_meldataset.mel_basis[cache_key], spectrum)
91
+ return upstream_meldataset.spectral_normalize_torch(spectrum)
92
+
93
+
94
+ def _sha256(path: pathlib.Path) -> str:
95
+ digest = hashlib.sha256()
96
+ with path.open("rb") as stream:
97
+ while block := stream.read(1024 * 1024):
98
+ digest.update(block)
99
+ return digest.hexdigest()
100
+
101
+
102
+ def _audio_stats(samples: np.ndarray) -> dict[str, object]:
103
+ normalized = samples.astype(np.float64) / MAX_WAV_VALUE
104
+ return {
105
+ "frames": int(samples.size),
106
+ "peak_absolute": float(np.max(np.abs(normalized))) if samples.size else 0.0,
107
+ "rms": float(np.sqrt(np.mean(np.square(normalized)))) if samples.size else 0.0,
108
+ }
109
+
110
+
111
+ def _atomic_json(path: pathlib.Path, value: object) -> None:
112
+ content = (
113
+ json.dumps(
114
+ value,
115
+ allow_nan=False,
116
+ ensure_ascii=False,
117
+ separators=(",", ":"),
118
+ sort_keys=True,
119
+ ).encode("utf-8")
120
+ + b"\n"
121
+ )
122
+ path.parent.mkdir(parents=True, exist_ok=True)
123
+ temporary = path.with_name("." + path.name + ".tmp")
124
+ try:
125
+ with temporary.open("xb") as stream:
126
+ stream.write(content)
127
+ stream.flush()
128
+ os.fsync(stream.fileno())
129
+ os.replace(temporary, path)
130
+ finally:
131
+ temporary.unlink(missing_ok=True)
132
+
133
+
134
+ def run(args: argparse.Namespace) -> dict[str, object]:
135
+ checkpoint = pathlib.Path(args.checkpoint)
136
+ checkpoint_sha256 = _sha256(checkpoint)
137
+ if checkpoint_sha256 != args.expected_checkpoint_sha256:
138
+ raise RuntimeError("generator checkpoint SHA-256 does not match")
139
+ if not torch.cuda.is_available():
140
+ raise RuntimeError("audio diagnostic requires one CUDA GPU")
141
+
142
+ config_value = json.loads(pathlib.Path(args.config).read_text(encoding="utf-8"))
143
+ config = AttrDict(config_value)
144
+ device = torch.device("cuda")
145
+ torch.manual_seed(config.seed)
146
+ torch.cuda.manual_seed_all(config.seed)
147
+ torch.backends.cudnn.benchmark = False
148
+
149
+ generator = Generator(config).to(device)
150
+ checkpoint_value = torch.load(checkpoint, map_location=device, weights_only=False)
151
+ if not isinstance(checkpoint_value, dict) or "generator" not in checkpoint_value:
152
+ raise RuntimeError("generator checkpoint has an unexpected structure")
153
+ generator.load_state_dict(checkpoint_value["generator"], strict=True)
154
+ generator.eval()
155
+ generator.remove_weight_norm()
156
+
157
+ output_root = pathlib.Path(args.output_root)
158
+ reference_root = output_root / "reference"
159
+ generated_root = output_root / "generated"
160
+ reference_root.mkdir(parents=True, exist_ok=False)
161
+ generated_root.mkdir(parents=True, exist_ok=False)
162
+
163
+ records: list[dict[str, object]] = []
164
+ if [pathlib.Path(value).name for value in args.input_wav] != list(EXPECTED_INPUTS):
165
+ raise RuntimeError("audio diagnostic inputs do not match the pinned ordered clip set")
166
+ with torch.no_grad():
167
+ for input_value in args.input_wav:
168
+ source = pathlib.Path(input_value)
169
+ expected_size, expected_sha256 = EXPECTED_INPUTS[source.name]
170
+ if source.stat().st_size != expected_size or _sha256(source) != expected_sha256:
171
+ raise RuntimeError(f"validation WAV identity does not match for {source.name}")
172
+ sampling_rate, samples = read(source)
173
+ if sampling_rate != config.sampling_rate:
174
+ raise RuntimeError(f"unexpected sampling rate for {source.name}")
175
+ if samples.dtype != np.int16 or samples.ndim != 1 or samples.size == 0:
176
+ raise RuntimeError(f"unexpected WAV format for {source.name}")
177
+
178
+ reference_path = reference_root / source.name
179
+ generated_path = generated_root / f"{source.stem}_generated.wav"
180
+ shutil.copyfile(source, reference_path)
181
+
182
+ reference_tensor = (
183
+ torch.from_numpy(samples.astype(np.float32)).to(device) / MAX_WAV_VALUE
184
+ ).unsqueeze(0)
185
+ input_mel = mel_spectrogram(
186
+ reference_tensor,
187
+ config.n_fft,
188
+ config.num_mels,
189
+ config.sampling_rate,
190
+ config.hop_size,
191
+ config.win_size,
192
+ config.fmin,
193
+ config.fmax,
194
+ )
195
+ generated_tensor = generator(input_mel).squeeze(0).squeeze(0)
196
+ if not bool(torch.isfinite(generated_tensor).all().item()):
197
+ raise RuntimeError(f"generated non-finite audio for {source.name}")
198
+
199
+ generated_float = generated_tensor.detach().cpu().numpy()
200
+ clipped_samples = int(np.count_nonzero(np.abs(generated_float) > 1.0))
201
+ generated_samples = np.rint(
202
+ np.clip(generated_float, -1.0, 1.0) * 32767.0
203
+ ).astype(np.int16)
204
+ write(generated_path, config.sampling_rate, generated_samples)
205
+
206
+ generated_for_mel = torch.from_numpy(generated_float).to(device).unsqueeze(0)
207
+ reference_loss_mel = mel_spectrogram(
208
+ reference_tensor,
209
+ config.n_fft,
210
+ config.num_mels,
211
+ config.sampling_rate,
212
+ config.hop_size,
213
+ config.win_size,
214
+ config.fmin,
215
+ config.fmax_for_loss,
216
+ )
217
+ generated_loss_mel = mel_spectrogram(
218
+ generated_for_mel,
219
+ config.n_fft,
220
+ config.num_mels,
221
+ config.sampling_rate,
222
+ config.hop_size,
223
+ config.win_size,
224
+ config.fmin,
225
+ config.fmax_for_loss,
226
+ )
227
+ aligned_frames = min(reference_loss_mel.shape[-1], generated_loss_mel.shape[-1])
228
+ mel_l1 = torch.nn.functional.l1_loss(
229
+ reference_loss_mel[..., :aligned_frames],
230
+ generated_loss_mel[..., :aligned_frames],
231
+ ).item()
232
+
233
+ records.append(
234
+ {
235
+ "clip_id": source.stem,
236
+ "generated": {
237
+ **_audio_stats(generated_samples),
238
+ "clipped_samples_before_clamp": clipped_samples,
239
+ "path": generated_path.as_posix(),
240
+ "sha256": _sha256(generated_path),
241
+ "size": generated_path.stat().st_size,
242
+ },
243
+ "mel_l1_aligned": float(mel_l1),
244
+ "mel_l1_frames": int(aligned_frames),
245
+ "reference": {
246
+ **_audio_stats(samples),
247
+ "path": reference_path.as_posix(),
248
+ "sha256": _sha256(reference_path),
249
+ "size": reference_path.stat().st_size,
250
+ },
251
+ "sampling_rate": int(sampling_rate),
252
+ }
253
+ )
254
+
255
+ report = {
256
+ "checkpoint": {
257
+ "path": checkpoint.as_posix(),
258
+ "sha256": checkpoint_sha256,
259
+ "size": checkpoint.stat().st_size,
260
+ },
261
+ "clips": records,
262
+ "diagnostic_boundary": (
263
+ "Operational generated-audio evidence only; not a convergence, perceptual-quality, "
264
+ "MOS, or paper-reproduction result."
265
+ ),
266
+ "gpu": {
267
+ "count": torch.cuda.device_count(),
268
+ "memory_bytes": torch.cuda.get_device_properties(0).total_memory,
269
+ "name": torch.cuda.get_device_name(0),
270
+ },
271
+ "sample_count": len(records),
272
+ "state": "audio_diagnostic_complete",
273
+ "upstream_commit": UPSTREAM_COMMIT,
274
+ }
275
+ _atomic_json(pathlib.Path(args.report), report)
276
+ print(json.dumps(report, allow_nan=False, separators=(",", ":"), sort_keys=True))
277
+ return report
278
+
279
+
280
+ def _parser() -> argparse.ArgumentParser:
281
+ parser = argparse.ArgumentParser()
282
+ parser.add_argument("--config", required=True)
283
+ parser.add_argument("--checkpoint", required=True)
284
+ parser.add_argument("--expected-checkpoint-sha256", required=True)
285
+ parser.add_argument("--input-wav", action="append", required=True)
286
+ parser.add_argument("--output-root", required=True)
287
+ parser.add_argument("--report", required=True)
288
+ return parser
289
+
290
+
291
+ if __name__ == "__main__":
292
+ run(_parser().parse_args())
@@ -0,0 +1,117 @@
1
+ """Extract the one verified HiFi-GAN checkpoint needed by the audio diagnostic."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import hashlib
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ import tempfile
11
+ import zipfile
12
+
13
+
14
+ CHECKPOINT_MEMBER = "outputs/cp_hifigan/g_00005000"
15
+ CHECKPOINT_SIZE = 55_825_339
16
+ CHECKPOINT_SHA256 = "836627cc247ec688589593d2324aae2cfadc8958eb17304c9426c44de56b46e3"
17
+
18
+
19
+ def _sha256(path: Path) -> str:
20
+ digest = hashlib.sha256()
21
+ with path.open("rb") as stream:
22
+ for block in iter(lambda: stream.read(1024 * 1024), b""):
23
+ digest.update(block)
24
+ return digest.hexdigest()
25
+
26
+
27
+ def extract_checkpoint(
28
+ artifacts_zip: Path,
29
+ output_path: Path,
30
+ *,
31
+ member: str = CHECKPOINT_MEMBER,
32
+ expected_size: int = CHECKPOINT_SIZE,
33
+ expected_sha256: str = CHECKPOINT_SHA256,
34
+ ) -> dict[str, object]:
35
+ artifacts_zip = artifacts_zip.resolve(strict=True)
36
+ if not artifacts_zip.is_file() or artifacts_zip.is_symlink():
37
+ raise ValueError(f"artifacts path is not a regular file: {artifacts_zip}")
38
+
39
+ output_path = output_path.resolve(strict=False)
40
+ output_path.parent.mkdir(parents=True, exist_ok=True)
41
+ if output_path.exists():
42
+ if not output_path.is_file() or output_path.is_symlink():
43
+ raise ValueError(f"output path is not a regular file: {output_path}")
44
+ actual_size = output_path.stat().st_size
45
+ actual_sha256 = _sha256(output_path)
46
+ if actual_size != expected_size or actual_sha256 != expected_sha256:
47
+ raise ValueError(
48
+ "existing output does not match the verified checkpoint: "
49
+ f"size={actual_size}, sha256={actual_sha256}"
50
+ )
51
+ return {
52
+ "artifacts_zip": artifacts_zip.as_posix(),
53
+ "checkpoint_path": output_path.as_posix(),
54
+ "checkpoint_size": actual_size,
55
+ "checkpoint_sha256": actual_sha256,
56
+ "member": member,
57
+ "reused": True,
58
+ }
59
+
60
+ with zipfile.ZipFile(artifacts_zip) as archive:
61
+ matching = [info for info in archive.infolist() if info.filename == member]
62
+ if len(matching) != 1:
63
+ raise ValueError(f"expected exactly one {member!r} member; found {len(matching)}")
64
+ info = matching[0]
65
+ if info.is_dir() or info.file_size != expected_size:
66
+ raise ValueError(
67
+ f"checkpoint member size mismatch: expected {expected_size}, got {info.file_size}"
68
+ )
69
+
70
+ handle, temporary_name = tempfile.mkstemp(
71
+ dir=output_path.parent,
72
+ prefix=f".{output_path.name}.",
73
+ suffix=".tmp",
74
+ )
75
+ os.close(handle)
76
+ temporary_path = Path(temporary_name)
77
+ try:
78
+ digest = hashlib.sha256()
79
+ with archive.open(info, "r") as source, temporary_path.open("wb") as destination:
80
+ while block := source.read(1024 * 1024):
81
+ destination.write(block)
82
+ digest.update(block)
83
+ destination.flush()
84
+ os.fsync(destination.fileno())
85
+ actual_size = temporary_path.stat().st_size
86
+ actual_sha256 = digest.hexdigest()
87
+ if actual_size != expected_size or actual_sha256 != expected_sha256:
88
+ raise ValueError(
89
+ "extracted checkpoint failed verification: "
90
+ f"size={actual_size}, sha256={actual_sha256}"
91
+ )
92
+ os.replace(temporary_path, output_path)
93
+ finally:
94
+ temporary_path.unlink(missing_ok=True)
95
+
96
+ return {
97
+ "artifacts_zip": artifacts_zip.as_posix(),
98
+ "checkpoint_path": output_path.as_posix(),
99
+ "checkpoint_size": expected_size,
100
+ "checkpoint_sha256": expected_sha256,
101
+ "member": member,
102
+ "reused": False,
103
+ }
104
+
105
+
106
+ def main() -> int:
107
+ parser = argparse.ArgumentParser(description=__doc__)
108
+ parser.add_argument("artifacts_zip", type=Path)
109
+ parser.add_argument("output_path", type=Path)
110
+ args = parser.parse_args()
111
+ result = extract_checkpoint(args.artifacts_zip, args.output_path)
112
+ print(json.dumps(result, sort_keys=True, separators=(",", ":")))
113
+ return 0
114
+
115
+
116
+ if __name__ == "__main__":
117
+ raise SystemExit(main())