Spaces:
Running on Zero
Running on Zero
| """Build background-music loops from FreePD (public-domain / CC0). | |
| FreePD (Kevin MacLeod) releases its catalogue as 100% public-domain music, mirrored on the | |
| Internet Archive (item `freepd`). We pull four mood tracks, trim each to a ~60s segment, | |
| normalise, fade, and save a compact mono OGG loop used by tts/music.py. No attribution is | |
| required (public domain), but FreePD is credited in the README. | |
| Run from the repo root: python scripts/build_music_loops.py | |
| OGG files are tracked via Git LFS (see .gitattributes). | |
| """ | |
| from __future__ import annotations | |
| import io | |
| import os | |
| import urllib.parse | |
| import urllib.request | |
| import numpy as np | |
| import soundfile as sf | |
| IA_BASE = "https://archive.org/download/freepd" | |
| TARGET_SR = 44100 | |
| SEG_START = 8.0 # skip any intro | |
| SEG_LEN = 60.0 | |
| OUT_DIR = os.path.join("tts", "music_loops") | |
| # bed id -> FreePD track (path within the IA item) | |
| TRACKS = { | |
| "ambient_drift": "Page2/Ambient C Motion.mp3", | |
| "lofi_pulse": "Page2/Chill Beat.mp3", | |
| "cinematic_rise": "Page2/Wonder Flow.mp3", | |
| "newsroom_bed": "Page2/Action Investigation.mp3", | |
| } | |
| def _fetch(track: str) -> bytes: | |
| url = IA_BASE + "/" + urllib.parse.quote(track) | |
| req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) | |
| with urllib.request.urlopen(req, timeout=120) as r: | |
| return r.read() | |
| def _resample(x: np.ndarray, sr: int, target: int) -> np.ndarray: | |
| if sr == target: | |
| return x | |
| n = int(round(len(x) * target / sr)) | |
| xp = np.linspace(0, 1, len(x), dtype=np.float64) | |
| fp = np.linspace(0, 1, n, dtype=np.float64) | |
| return np.interp(fp, xp, x).astype(np.float32) | |
| def build(): | |
| os.makedirs(OUT_DIR, exist_ok=True) | |
| for bed_id, track in TRACKS.items(): | |
| audio, sr = sf.read(io.BytesIO(_fetch(track)), dtype="float32") | |
| if audio.ndim > 1: | |
| audio = audio.mean(axis=1) | |
| audio = _resample(audio, sr, TARGET_SR) | |
| start = int(SEG_START * TARGET_SR) | |
| if start + int(5 * TARGET_SR) >= len(audio): | |
| start = 0 | |
| seg = audio[start:start + int(SEG_LEN * TARGET_SR)] | |
| peak = float(np.abs(seg).max()) or 1.0 | |
| seg = (seg / peak * 0.9).astype(np.float32) | |
| f = int(1.0 * TARGET_SR) | |
| if len(seg) > 2 * f: | |
| seg[:f] *= np.linspace(0, 1, f, dtype=np.float32) | |
| seg[-f:] *= np.linspace(1, 0, f, dtype=np.float32) | |
| # FLAC: stable libsndfile write + lossless/compact. Loops are read server-side | |
| # only (mixed into the speech), so browser codec support is irrelevant. | |
| out = os.path.join(OUT_DIR, f"{bed_id}.flac") | |
| sf.write(out, seg, TARGET_SR, format="FLAC", subtype="PCM_16") | |
| print(f" {bed_id:16s} <- {track} {len(seg)/TARGET_SR:.1f}s {os.path.getsize(out)//1024}KB") | |
| if __name__ == "__main__": | |
| build() | |
| print("done.") | |