📊 Jan 8 Audio System Complete Organization + Generation Manifest

 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)
This commit is contained in:
2026-01-08 15:50:30 +01:00
parent f309374ff5
commit 5b07de56da
50 changed files with 4937 additions and 0 deletions

221
scripts/complete_asset_audit.py Executable file
View File

@@ -0,0 +1,221 @@
#!/usr/bin/env python3
"""
Complete Asset Audit Script
Systematically checks ALL assets vs documentation
"""
import os
from pathlib import Path
from collections import defaultdict
# Base paths
REPO_ROOT = Path("/Users/davidkotnik/repos/novafarma")
ASSETS_REF = REPO_ROOT / "assets" / "references"
# Expected counts from DEMO_FAZA1_FAZA2_OVERVIEW.md
EXPECTED = {
"characters": {
"kai": 21, # idle(5) + walk(6) + dig(5) + swing(5)
"ana": 10, # idle(4) + walk(6)
"gronk": 10, # idle(4) + walk(6)
},
"companions": {
"susi": 12, # idle(4) + run(6) + bark(2)
},
"zombies": 45, # 3 types × 15 frames each
"crops": {
"wheat": 5,
"carrot": 5,
"tomato": 5,
"potato": 5,
"corn": 5,
},
"tools": 8,
"ui": 28,
"grassland": 27,
}
def count_pngs(directory):
"""Count PNG files in directory recursively"""
if not directory.exists():
return 0
return len(list(directory.rglob("*.png")))
def audit_characters():
"""Audit main characters"""
print("\n## 1. MAIN CHARACTERS")
print("=" * 60)
char_path = ASSETS_REF / "main_characters"
results = {}
for char in ["kai", "ana", "gronk"]:
char_dir = char_path / char
actual = count_pngs(char_dir)
expected = EXPECTED["characters"][char]
status = "" if actual >= expected else ""
gap = actual - expected
results[char] = {
"expected": expected,
"actual": actual,
"status": status,
"gap": gap
}
print(f"\n{char.upper()}:")
print(f" Expected: {expected}")
print(f" Actual: {actual}")
print(f" Status: {status} ({'+' if gap >= 0 else ''}{gap})")
return results
def audit_companions():
"""Audit companion animals"""
print("\n## 2. COMPANIONS")
print("=" * 60)
comp_path = ASSETS_REF / "companions"
results = {}
for comp in ["susi"]:
comp_dir = comp_path / comp
actual = count_pngs(comp_dir)
expected = EXPECTED["companions"][comp]
status = "" if actual >= expected else ""
gap = actual - expected
results[comp] = {
"expected": expected,
"actual": actual,
"status": status,
"gap": gap
}
print(f"\n{comp.upper()}:")
print(f" Expected: {expected}")
print(f" Actual: {actual}")
print(f" Status: {status} ({'+' if gap >= 0 else ''}{gap})")
return results
def audit_crops():
"""Audit crop growth stages"""
print("\n## 3. CROPS")
print("=" * 60)
crops_path = ASSETS_REF / "crops"
results = {}
for crop in ["wheat", "carrot", "tomato", "potato", "corn"]:
crop_dir = crops_path / crop / "growth_stages"
actual = count_pngs(crop_dir)
expected = EXPECTED["crops"][crop]
status = "" if actual >= expected else ""
gap = actual - expected
results[crop] = {
"expected": expected,
"actual": actual,
"status": status,
"gap": gap
}
print(f"\n{crop.upper()}:")
print(f" Expected: {expected}")
print(f" Actual: {actual}")
print(f" Status: {status} ({'+' if gap >= 0 else ''}{gap})")
return results
def audit_audio():
"""Audit audio files"""
print("\n## 4. AUDIO")
print("=" * 60)
audio_path = ASSETS_REF.parent / "audio"
# Count voice files
voices = audio_path / "voices"
voice_count = len(list(voices.rglob("*.mp3"))) if voices.exists() else 0
# Count sound effects
sfx = audio_path / "sfx"
sfx_count = len(list(sfx.rglob("*.wav"))) if sfx.exists() else 0
# Count music
music_path = REPO_ROOT / "music"
music_count = len(list(music_path.rglob("*.mp3"))) if music_path.exists() else 0
music_count += len(list(music_path.rglob("*.wav"))) if music_path.exists() else 0
music_count += len(list(music_path.rglob("*.ogg"))) if music_path.exists() else 0
print(f"\nVOICES (MP3): {voice_count}")
print(f"SOUND EFFECTS (WAV): {sfx_count}")
print(f"MUSIC: {music_count} {'❌ MISSING!' if music_count == 0 else ''}")
return {
"voices": voice_count,
"sfx": sfx_count,
"music": music_count
}
def generate_report():
"""Generate complete audit report"""
print("\n" + "="*60)
print("🔍 COMPLETE ASSET AUDIT - JAN 8, 2026")
print("="*60)
# Run all audits
chars = audit_characters()
comps = audit_companions()
crops = audit_crops()
audio = audit_audio()
# Summary
print("\n" + "="*60)
print("📊 SUMMARY")
print("="*60)
total_expected = sum(EXPECTED["characters"].values())
total_expected += sum(EXPECTED["companions"].values())
total_expected += sum(EXPECTED["crops"].values())
total_expected += EXPECTED["zombies"]
total_expected += EXPECTED["tools"]
total_expected += EXPECTED["ui"]
total_actual = count_pngs(ASSETS_REF)
print(f"\nTOTAL PNG FILES: {total_actual}")
print(f"AUDIO FILES: {audio['voices'] + audio['sfx'] + audio['music']}")
print(f" - Voices: {audio['voices']}")
print(f" - SFX: {audio['sfx']}")
print(f" - Music: {audio['music']} {'' if audio['music'] == 0 else ''}")
# Missing items
print("\n" + "="*60)
print("❌ MISSING / NEEDS ATTENTION")
print("="*60)
missing = []
for char, data in chars.items():
if data['gap'] < 0:
missing.append(f" - {char.upper()}: {abs(data['gap'])} sprites short")
for crop, data in crops.items():
if data['gap'] < 0:
missing.append(f" - {crop.upper()}: {abs(data['gap'])} sprites short")
if audio['music'] == 0:
missing.append(f" - MUSIC: Need 3+ background tracks")
if missing:
for item in missing:
print(item)
else:
print(" ✅ ALL ASSETS COMPLETE!")
print("\n" + "="*60)
if __name__ == "__main__":
generate_report()

203
scripts/convert_audio_to_ogg.py Executable file
View File

@@ -0,0 +1,203 @@
#!/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()