BEDO97 commited on
Commit
125bae1
·
verified ·
1 Parent(s): e323edf

Add transcribe.py (app pack entry point)

Browse files
Files changed (1) hide show
  1. transcribe.py +108 -0
transcribe.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Egystormy Tools — portable local MuScriptor transcription pack.
3
+
4
+ Usage:
5
+ runtime/python transcribe.py <input_audio> <output.mid>
6
+
7
+ Fully offline and self-contained: the interpreter is ./runtime, all
8
+ third-party libraries live in ./libs, and the model weights in ./weights.
9
+ No Python installation, no setup, no network, no account.
10
+
11
+ Design note — no beat detection, on purpose: this entry point uses
12
+ ``TranscriptionModel.transcribe`` + ``events_to_midi_bytes`` directly and
13
+ never calls the beat-grid path (``transcribe_and_postprocess`` /
14
+ ``detect_tempo``). MuScriptor's beat detection hard-imports
15
+ beat_this -> torchaudio at call time, and a mismatched torchaudio build
16
+ kills the whole transcription. Skipping it costs nothing that matters
17
+ here: note timings stay true (seconds-accurate); the MIDI simply carries
18
+ no tempo map. (In this pack torch is 2.15.0+cpu while the newest
19
+ torchaudio on the CPU index is 2.11.1+cpu — no matched 2.15 pair exists,
20
+ which is exactly the hazard avoided.)
21
+ """
22
+
23
+ import os
24
+ import sys
25
+ import time
26
+ from pathlib import Path
27
+
28
+ PACK = Path(__file__).resolve().parent
29
+ sys.path.insert(0, str(PACK / "libs"))
30
+ # Hard-offline: never let a library try to phone home.
31
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
32
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
33
+
34
+ T0 = time.perf_counter()
35
+
36
+
37
+ def stamp(label: str) -> None:
38
+ print(f"[pack] {label}: {time.perf_counter() - T0:.2f}s elapsed", flush=True)
39
+
40
+
41
+ def main() -> int:
42
+ if len(sys.argv) != 3:
43
+ print("usage: runtime/python transcribe.py <input_audio> <output.mid>",
44
+ file=sys.stderr)
45
+ return 2
46
+ src = Path(sys.argv[1])
47
+ dst = Path(sys.argv[2])
48
+ weights = PACK / "weights" / "model.safetensors"
49
+ if not src.exists():
50
+ print(f"input audio not found: {src}", file=sys.stderr)
51
+ return 2
52
+ if not weights.exists():
53
+ print(f"weights not found: {weights}", file=sys.stderr)
54
+ return 2
55
+
56
+ from muscriptor import TranscriptionModel
57
+ from muscriptor.events import NoteStartEvent, ProgressEvent
58
+
59
+ stamp("imports done")
60
+
61
+ # libsndfile cannot decode m4a/AAC and some other containers; if an
62
+ # ffmpeg happens to be available, transcode those to a temp WAV first.
63
+ # (mp3/wav/flac/ogg decode natively and never reach this branch.)
64
+ audio_arg = src
65
+ tmp_wav = None
66
+ if src.suffix.lower() in {".m4a", ".aac", ".opus", ".wma"}:
67
+ import shutil
68
+ import subprocess
69
+ import tempfile
70
+
71
+ ffmpeg = shutil.which("ffmpeg")
72
+ if ffmpeg:
73
+ fd, tmp_wav = tempfile.mkstemp(suffix=".wav")
74
+ os.close(fd)
75
+ subprocess.run(
76
+ [ffmpeg, "-y", "-v", "error", "-i", str(src), tmp_wav],
77
+ check=True,
78
+ )
79
+ audio_arg = Path(tmp_wav)
80
+
81
+ model = TranscriptionModel.load_model(weights_path=weights, device="cpu")
82
+ stamp("model loaded (cold start)")
83
+
84
+ t_transcribe = time.perf_counter()
85
+ events = []
86
+ notes = 0
87
+ for ev in model.transcribe(str(audio_arg)):
88
+ if isinstance(ev, ProgressEvent):
89
+ print(f"[pack] progress: chunk {ev.completed}/{ev.total}", flush=True)
90
+ else:
91
+ events.append(ev)
92
+ if isinstance(ev, NoteStartEvent):
93
+ notes += 1
94
+
95
+ midi_bytes = model.events_to_midi_bytes(iter(events), beat_grid=None)
96
+ dst.parent.mkdir(parents=True, exist_ok=True)
97
+ dst.write_bytes(midi_bytes)
98
+ if tmp_wav:
99
+ os.unlink(tmp_wav)
100
+
101
+ print(f"[pack] transcribe wall time: {time.perf_counter() - t_transcribe:.2f}s",
102
+ flush=True)
103
+ print(f"DONE notes={notes} output={dst} bytes={len(midi_bytes)}", flush=True)
104
+ return 0
105
+
106
+
107
+ if __name__ == "__main__":
108
+ raise SystemExit(main())