123 lines
4.0 KiB
Python
123 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Batch FLP generator — produces 50 unique reggaeton FLP+JSON pairs.
|
|
|
|
Usage:
|
|
python scripts/batch_generate.py [--count 50] [--out-dir output/batch]
|
|
|
|
Output structure:
|
|
output/batch_{timestamp}/
|
|
reggaeton_000_95bpm_Am_i-VII-VI-VII.json
|
|
reggaeton_000_95bpm_Am_i-VII-VI-VII.flp
|
|
reggaeton_001_90bpm_Dm_i-iv-VII-III.json
|
|
...
|
|
manifest.json ← list of all generated songs with metadata
|
|
"""
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parents[1]))
|
|
|
|
from src.composer.variation import generate_batch
|
|
from src.flp_builder.builder import FLPBuilder
|
|
from src.flp_builder.schema import SongDefinition
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Filename helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_UNSAFE_RE = re.compile(r'[^\w\-]')
|
|
|
|
|
|
def sanitize_filename(s: str) -> str:
|
|
"""Replace unsafe filename chars with _."""
|
|
return _UNSAFE_RE.sub('_', s)
|
|
|
|
|
|
def make_filename(idx: int, song: SongDefinition) -> str:
|
|
"""Build stem like ``reggaeton_000_95bpm_Am_i_VII_VI_VII`` (no extension)."""
|
|
prog_safe = sanitize_filename(song.progression_name)
|
|
return f"reggaeton_{idx:03d}_{song.meta.bpm}bpm_{song.meta.key}_{prog_safe}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Manifest
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def build_manifest(songs: list[SongDefinition], filenames: list[str]) -> dict:
|
|
"""Build manifest dict with per-song metadata."""
|
|
entries = []
|
|
for idx, (song, stem) in enumerate(zip(songs, filenames)):
|
|
bar_count = int(max(item.bar + item.bars for item in song.items))
|
|
entries.append({
|
|
"idx": idx,
|
|
"filename": stem,
|
|
"bpm": song.meta.bpm,
|
|
"key": song.meta.key,
|
|
"progression": song.progression_name,
|
|
"title": song.meta.title,
|
|
"bars": bar_count,
|
|
})
|
|
return {
|
|
"generated_at": datetime.now().isoformat(),
|
|
"count": len(songs),
|
|
"songs": entries,
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Batch FLP generator")
|
|
parser.add_argument("--count", type=int, default=50,
|
|
help="Number of songs to generate (default: 50)")
|
|
parser.add_argument("--out-dir", default="",
|
|
help="Output directory (default: output/batch_{timestamp})")
|
|
args = parser.parse_args()
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
out_dir = Path(args.out_dir) if args.out_dir else Path("output") / f"batch_{timestamp}"
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
print(f"Generating {args.count} songs -> {out_dir}")
|
|
|
|
songs = generate_batch(args.count)
|
|
builder = FLPBuilder()
|
|
filenames: list[str] = []
|
|
|
|
for idx, song in enumerate(songs):
|
|
stem = make_filename(idx, song)
|
|
filenames.append(stem)
|
|
|
|
# Write JSON
|
|
json_path = out_dir / f"{stem}.json"
|
|
json_path.write_text(song.to_json(), encoding="utf-8")
|
|
|
|
# Write FLP
|
|
flp_path = out_dir / f"{stem}.flp"
|
|
flp_bytes = builder.build(song)
|
|
flp_path.write_bytes(flp_bytes)
|
|
|
|
bar_count = int(max(item.bar + item.bars for item in song.items))
|
|
print(f" [{idx+1:>3}/{args.count}] {stem}.flp {len(flp_bytes):>9,}b {bar_count}bars")
|
|
|
|
# Write manifest
|
|
manifest = build_manifest(songs, filenames)
|
|
(out_dir / "manifest.json").write_text(
|
|
json.dumps(manifest, indent=2), encoding="utf-8"
|
|
)
|
|
|
|
total_size = sum((out_dir / f"{f}.flp").stat().st_size for f in filenames)
|
|
print(f"\nDone. {args.count} FLPs in {out_dir}")
|
|
print(f" Total size: {total_size:,} bytes")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|