✅ AUDIO REORGANIZATION COMPLETE: **Files Moved:** - Moved 2 SFX WAV: footstep_grass.wav, wood_chop.wav → /sfx/ - Moved 1 Music MP3: forest_ambient.mp3 → /music/ - Moved 43 Voiceover WAV: prologue_sl files → /voiceover/ - Existing 20 Voice MP3: Ana, Kai, Mayor, Narrator, Teacher → /voices/ **Folder Structure Created:** /assets/audio/ ├── sfx/ │ ├── farming/ (8 placeholder .txt) │ ├── combat/ (8 placeholder .txt) │ ├── building/ (5 placeholder .txt) │ └── misc/ (4 placeholder .txt) ├── music/ (8 placeholder .txt + 1 MP3 ✅) ├── voices/ (20 MP3 ✅) └── voiceover/ (43 WAV ✅) 📋 CREATED DOCUMENTATION: 1. **AUDIO_GENERATION_MANIFEST.md** - Complete list of 33 missing audio files - Detailed specifications (duration, format, mood) - SFX: 25 files (farming, combat, building, misc) - Music: 8 files (themes, ambients, victory) - Generation instructions (AI tools, free libraries) 2. **scripts/convert_audio_to_ogg.py** - Auto-remove .txt placeholders - Convert MP3/WAV → OGG (ffmpeg) - Verify file integrity - Generate audioManifest.json for Phaser ❌ STILL MISSING: - 25 SFX .ogg files (placeholders only) - 7 Music .ogg files (placeholders only) 🎯 NEXT STEPS: 1. Generate audio using AI tools (ElevenLabs, Suno, etc.) 2. Run: python3 scripts/convert_audio_to_ogg.py 3. Verify all 33 files present **Current Audio Status:** 66/99 files (67% complete)
204 lines
6.6 KiB
Python
Executable File
204 lines
6.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Audio Conversion & Cleanup Script
|
|
Converts audio files to .ogg, removes placeholders, verifies integrity
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path("/Users/davidkotnik/repos/novafarma")
|
|
AUDIO_ROOT = REPO_ROOT / "assets" / "audio"
|
|
|
|
def remove_txt_placeholders():
|
|
"""Remove all .txt placeholder files"""
|
|
print("\n🗑️ Removing .txt placeholders...")
|
|
|
|
txt_files = list(AUDIO_ROOT.rglob("*.txt"))
|
|
for txt_file in txt_files:
|
|
txt_file.unlink()
|
|
print(f" ✅ Removed: {txt_file.name}")
|
|
|
|
print(f"\n✅ Removed {len(txt_files)} placeholder files")
|
|
|
|
def convert_to_ogg():
|
|
"""Convert all MP3/WAV to OGG using ffmpeg"""
|
|
print("\n🔄 Converting audio files to .ogg...")
|
|
|
|
# Find all MP3 and WAV files
|
|
audio_files = []
|
|
audio_files.extend(AUDIO_ROOT.rglob("*.mp3"))
|
|
audio_files.extend(AUDIO_ROOT.rglob("*.wav"))
|
|
|
|
converted = 0
|
|
for audio_file in audio_files:
|
|
ogg_file = audio_file.with_suffix(".ogg")
|
|
|
|
# Skip if .ogg already exists
|
|
if ogg_file.exists():
|
|
print(f" ⏭️ Skip: {ogg_file.name} (exists)")
|
|
continue
|
|
|
|
# Convert using ffmpeg
|
|
try:
|
|
cmd = [
|
|
"ffmpeg",
|
|
"-i", str(audio_file),
|
|
"-c:a", "libvorbis",
|
|
"-q:a", "6", # Quality 6 (good balance)
|
|
str(ogg_file)
|
|
]
|
|
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
print(f" ✅ Converted: {audio_file.name} → {ogg_file.name}")
|
|
converted += 1
|
|
else:
|
|
print(f" ❌ Failed: {audio_file.name}")
|
|
print(f" Error: {result.stderr[:100]}")
|
|
|
|
except FileNotFoundError:
|
|
print("\n❌ ERROR: ffmpeg not found!")
|
|
print("Install with: brew install ffmpeg")
|
|
return False
|
|
|
|
print(f"\n✅ Converted {converted} files to .ogg")
|
|
return True
|
|
|
|
def verify_audio():
|
|
"""Verify all audio files exist and are valid"""
|
|
print("\n🔍 Verifying audio files...")
|
|
|
|
# Expected files from manifest
|
|
expected = {
|
|
"sfx/farming": ["cow_moo", "dig", "harvest", "plant_seed", "scythe_swing", "stone_mine", "tree_chop", "water_crop"],
|
|
"sfx/combat": ["bow_release", "explosion", "player_hurt", "raider_attack", "shield_block", "sword_slash", "zombie_death", "zombie_hit"],
|
|
"sfx/building": ["chest_open", "door_close", "door_open", "hammer_nail", "repair"],
|
|
"sfx/misc": ["coin_collect", "footstep_grass", "footstep_stone", "level_up"],
|
|
"music": ["main_theme", "farm_ambient", "town_theme", "combat_theme", "night_theme", "victory_theme", "raid_warning", "ana_theme", "forest_ambient"],
|
|
}
|
|
|
|
missing = []
|
|
found = []
|
|
|
|
for category, files in expected.items():
|
|
category_path = AUDIO_ROOT / category
|
|
|
|
for filename in files:
|
|
# Check both .ogg and original formats
|
|
ogg_file = category_path / f"{filename}.ogg"
|
|
mp3_file = category_path / f"{filename}.mp3"
|
|
wav_file = category_path / f"{filename}.wav"
|
|
|
|
if ogg_file.exists():
|
|
size = ogg_file.stat().st_size
|
|
if size > 1000: # At least 1KB
|
|
found.append(str(ogg_file.relative_to(AUDIO_ROOT)))
|
|
else:
|
|
missing.append(f"{category}/{filename}.ogg (too small: {size}B)")
|
|
elif mp3_file.exists() or wav_file.exists():
|
|
found.append(f"{category}/{filename} (needs conversion)")
|
|
else:
|
|
missing.append(f"{category}/{filename}.ogg")
|
|
|
|
print(f"\n✅ Found: {len(found)} files")
|
|
print(f"❌ Missing: {len(missing)} files")
|
|
|
|
if missing:
|
|
print("\n❌ MISSING FILES:")
|
|
for file in missing[:10]: # Show first 10
|
|
print(f" - {file}")
|
|
if len(missing) > 10:
|
|
print(f" ... and {len(missing) - 10} more")
|
|
|
|
return len(missing) == 0
|
|
|
|
def generate_manifest():
|
|
"""Generate audio file manifest for Phaser preload"""
|
|
print("\n📝 Generating audio manifest...")
|
|
|
|
manifest = {
|
|
"sfx": {},
|
|
"music": {},
|
|
"voices": {}
|
|
}
|
|
|
|
# Scan SFX
|
|
sfx_path = AUDIO_ROOT / "sfx"
|
|
for ogg_file in sfx_path.rglob("*.ogg"):
|
|
category = ogg_file.parent.name
|
|
filename = ogg_file.stem
|
|
|
|
if category not in manifest["sfx"]:
|
|
manifest["sfx"][category] = []
|
|
|
|
manifest["sfx"][category].append({
|
|
"key": f"{category}_{filename}",
|
|
"path": str(ogg_file.relative_to(AUDIO_ROOT.parent))
|
|
})
|
|
|
|
# Scan Music
|
|
music_path = AUDIO_ROOT / "music"
|
|
for audio_file in music_path.glob("*"):
|
|
if audio_file.suffix in [".ogg", ".mp3"]:
|
|
manifest["music"][audio_file.stem] = {
|
|
"key": audio_file.stem,
|
|
"path": str(audio_file.relative_to(AUDIO_ROOT.parent))
|
|
}
|
|
|
|
# Scan Voices
|
|
voices_path = AUDIO_ROOT / "voices"
|
|
for char_dir in voices_path.iterdir():
|
|
if char_dir.is_dir():
|
|
manifest["voices"][char_dir.name] = []
|
|
for mp3_file in char_dir.glob("*.mp3"):
|
|
manifest["voices"][char_dir.name].append({
|
|
"key": f"{char_dir.name}_{mp3_file.stem}",
|
|
"path": str(mp3_file.relative_to(AUDIO_ROOT.parent))
|
|
})
|
|
|
|
# Write manifest
|
|
manifest_file = REPO_ROOT / "src" / "data" / "audioManifest.json"
|
|
import json
|
|
|
|
manifest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(manifest_file, 'w') as f:
|
|
json.dump(manifest, f, indent=2)
|
|
|
|
print(f"✅ Manifest written to: {manifest_file}")
|
|
print(f" - SFX categories: {len(manifest['sfx'])}")
|
|
print(f" - Music tracks: {len(manifest['music'])}")
|
|
print(f" - Voice characters: {len(manifest['voices'])}")
|
|
|
|
def main():
|
|
"""Run all audio processing tasks"""
|
|
print("="*60)
|
|
print("🎵 AUDIO CONVERSION & CLEANUP")
|
|
print("="*60)
|
|
|
|
# Step 1: Remove placeholders
|
|
remove_txt_placeholders()
|
|
|
|
# Step 2: Convert to OGG
|
|
if not convert_to_ogg():
|
|
return
|
|
|
|
# Step 3: Verify
|
|
verify_audio()
|
|
|
|
# Step 4: Generate manifest
|
|
generate_manifest()
|
|
|
|
print("\n" + "="*60)
|
|
print("✅ AUDIO PROCESSING COMPLETE!")
|
|
print("="*60)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|