Compare commits
3 Commits
f309374ff5
...
ce3b89d776
| Author | SHA1 | Date | |
|---|---|---|---|
| ce3b89d776 | |||
| 820815e1a5 | |||
| 5b07de56da |
140
AUDIO_GENERATION_MANIFEST.md
Normal file
140
AUDIO_GENERATION_MANIFEST.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# 🎵 **AUDIO GENERATION MANIFEST - JAN 8, 2026**
|
||||
|
||||
**COMPLETE LIST OF ALL MISSING AUDIO FILES**
|
||||
|
||||
---
|
||||
|
||||
## 📊 **SUMMARY:**
|
||||
|
||||
- **SFX Needed:** 25 files (.ogg format)
|
||||
- **Music Needed:** 8 files (.ogg format)
|
||||
- **Total:** 33 audio files to generate
|
||||
|
||||
---
|
||||
|
||||
## 🔊 **1. SOUND EFFECTS (SFX) - 25 FILES**
|
||||
|
||||
### **FARMING SOUNDS (8 files)** 🌾
|
||||
**Path:** `/assets/audio/sfx/farming/`
|
||||
|
||||
| Filename | Description | Duration | Notes |
|
||||
|----------|-------------|----------|-------|
|
||||
| `cow_moo.ogg` | Cow mooing sound | 2-3s | Friendly, farm animal |
|
||||
| `dig.ogg` | Digging/hoeing soil | 1s | Shovel into dirt |
|
||||
| `harvest.ogg` | Crop harvest/pickup | 0.5s | Satisfying pop/snap |
|
||||
| `plant_seed.ogg` | Planting seed in soil | 0.5s | Soft thud |
|
||||
| `scythe_swing.ogg` | Scythe swinging through air | 0.8s | Whoosh sound |
|
||||
| `stone_mine.ogg` | Pickaxe hitting stone | 1s | Clink/chip sound |
|
||||
| `tree_chop.ogg` | Axe chopping wood | 1s | Thunk/chop |
|
||||
| `water_crop.ogg` | Watering can pouring | 1.5s | Water splash/trickle |
|
||||
|
||||
---
|
||||
|
||||
### **COMBAT SOUNDS (8 files)** ⚔️
|
||||
**Path:** `/assets/audio/sfx/combat/`
|
||||
|
||||
| Filename | Description | Duration | Notes |
|
||||
|----------|-------------|----------|-------|
|
||||
| `bow_release.ogg` | Arrow release from bow | 0.3s | Twang sound |
|
||||
| `explosion.ogg` | Explosion/bomb | 2s | Boom + debris |
|
||||
| `player_hurt.ogg` | Player damage grunt | 0.5s | Oof/ugh |
|
||||
| `raider_attack.ogg` | Enemy attack yell | 1s | Aggressive shout |
|
||||
| `shield_block.ogg` | Shield blocking hit | 0.5s | Metallic clang |
|
||||
| `sword_slash.ogg` | Sword swing | 0.5s | Whoosh + metal |
|
||||
| `zombie_death.ogg` | Zombie dies | 1.5s | Groan + thud |
|
||||
| `zombie_hit.ogg` | Zombie takes damage | 0.5s | Hurt groan |
|
||||
|
||||
---
|
||||
|
||||
### **BUILDING SOUNDS (5 files)** 🏗️
|
||||
**Path:** `/assets/audio/sfx/building/`
|
||||
|
||||
| Filename | Description | Duration | Notes |
|
||||
|----------|-------------|----------|-------|
|
||||
| `chest_open.ogg` | Chest opening | 1s | Creaky wood |
|
||||
| `door_close.ogg` | Door closing | 0.8s | Wood door slam |
|
||||
| `door_open.ogg` | Door opening | 0.8s | Creaky hinges |
|
||||
| `hammer_nail.ogg` | Hammering nail | 0.5s | Metallic bang |
|
||||
| `repair.ogg` | Building repair | 1.5s | Construction sounds |
|
||||
|
||||
---
|
||||
|
||||
### **MISC SOUNDS (4 files)** ✨
|
||||
**Path:** `/assets/audio/sfx/misc/`
|
||||
|
||||
| Filename | Description | Duration | Notes |
|
||||
|----------|-------------|----------|-------|
|
||||
| `coin_collect.ogg` | Picking up coin | 0.3s | Bright ching! |
|
||||
| `footstep_grass.ogg` | Footstep on grass | 0.3s | Soft rustle (HAVE .wav, convert!) |
|
||||
| `footstep_stone.ogg` | Footstep on stone | 0.3s | Hard tap |
|
||||
| `level_up.ogg` | Level up/achievement | 2s | Triumphant chime |
|
||||
|
||||
---
|
||||
|
||||
## 🎶 **2. MUSIC TRACKS - 8 FILES**
|
||||
|
||||
### **BACKGROUND MUSIC (.ogg format)**
|
||||
**Path:** `/assets/audio/music/`
|
||||
|
||||
| Filename | Description | Duration | Loop | BPM | Mood |
|
||||
|----------|-------------|----------|------|-----|------|
|
||||
| `forest_ambient.mp3` | **✅ HAVE!** Forest sounds | - | Yes | - | Peaceful |
|
||||
| `main_theme.ogg` | Main menu theme | 2-3min | Yes | 90-110 | Epic/Adventure |
|
||||
| `farm_ambient.ogg` | Farm/grassland loop | 2-3min | Yes | 70-90 | Calm/Peaceful |
|
||||
| `town_theme.ogg` | Town restoration theme | 2min | Yes | 100-120 | Hopeful/Uplifting |
|
||||
| `combat_theme.ogg` | Battle music | 2min | Yes | 130-150 | Intense/Action |
|
||||
| `night_theme.ogg` | Nighttime ambient | 3min | Yes | 60-80 | Mysterious/Calm |
|
||||
| `victory_theme.ogg` | Quest complete | 30s | No | 120 | Triumphant |
|
||||
| `raid_warning.ogg` | Raid approaching | 1min | No | 140-160 | Tense/Urgent |
|
||||
| `ana_theme.ogg` | Ana's memory theme | 2min | No | 80 | Emotional/Sad |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ **GENERATION INSTRUCTIONS:**
|
||||
|
||||
### **Option 1: AI Sound Generation (Recommended)**
|
||||
Use services like:
|
||||
- **ElevenLabs Sound Effects** - AI SFX generation
|
||||
- **Suno AI** or **Udio** - Music generation
|
||||
- **Soundraw** - Royalty-free music generator
|
||||
|
||||
### **Option 2: Free Sound Libraries**
|
||||
Download from:
|
||||
- **Freesound.org** - Community sound library
|
||||
- **OpenGameArt.org** - Game audio assets
|
||||
- **Incompetech** - Royalty-free music (Kevin MacLeod)
|
||||
|
||||
### **Option 3: Script Generation (Placeholder)**
|
||||
Use `/scripts/generate_placeholder_audio.py` to create:
|
||||
- Simple tone beeps (SFX placeholders)
|
||||
- White noise loops (ambient placeholders)
|
||||
|
||||
---
|
||||
|
||||
## 📋 **CONVERSION CHECKLIST:**
|
||||
|
||||
After generating, run:
|
||||
```bash
|
||||
python3 /Users/davidkotnik/repos/novafarma/scripts/convert_audio_to_ogg.py
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Convert all .mp3/.wav to .ogg
|
||||
2. Remove .txt placeholders
|
||||
3. Verify file sizes
|
||||
4. Generate audio manifest
|
||||
|
||||
---
|
||||
|
||||
## ✅ **COMPLETION CRITERIA:**
|
||||
|
||||
- [ ] All 25 SFX .ogg files present
|
||||
- [ ] All 8 music .ogg files present
|
||||
- [ ] Each file is 5KB+ (not empty)
|
||||
- [ ] Audio plays correctly in Phaser 3
|
||||
- [ ] Volume normalized (-14 LUFS)
|
||||
|
||||
---
|
||||
|
||||
**Status:** 📝 Manifest ready, awaiting audio generation
|
||||
**Last Updated:** 2026-01-08 15:48 CET
|
||||
35
COMPLETE_ASSET_CHECK_JAN8.md
Normal file
35
COMPLETE_ASSET_CHECK_JAN8.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 🔍 **COMPLETE ASSET & SYSTEMS CHECK - JAN 8, 2026 (15:41 CET)**
|
||||
|
||||
**SYSTEMATIČNI PREGLED OD ZAČETKA DO KONCA**
|
||||
|
||||
---
|
||||
|
||||
## 📋 **METODOLOGIJA:**
|
||||
|
||||
1. ✅ Pregledam DEMO_FAZA1_FAZA2_OVERVIEW.md (kaj MORA bit)
|
||||
2. ✅ Preverim vse /assets/references/ folders (kaj IMO)
|
||||
3. ✅ Primerjam dokumentacijo vs realnost
|
||||
4. ✅ Naredim seznam manjkajočih elementov
|
||||
5. ✅ Prioritiziram kaj dodat
|
||||
|
||||
---
|
||||
|
||||
## 📊 **CATEGORY 1: CHARACTER ANIMATIONS**
|
||||
|
||||
### **Kaj MORA bit (iz docs):**
|
||||
- Kai: idle (5), walk (6), dig (5), swing (5) = 21 ✅
|
||||
- Ana: idle (4), walk (6) = 10 ✅
|
||||
- Gronk: idle (4), walk (6) = 10 ✅
|
||||
- Susi: idle (4), run (6), bark (2) = 12 ✅
|
||||
|
||||
**TOTAL NEEDS:** 53 sprites
|
||||
|
||||
### **Kaj IMO v /references:**
|
||||
46
|
||||
|
||||
|
||||
**STATUS:** ✅ CHECKING...
|
||||
|
||||
---
|
||||
|
||||
## 📊 **RUNNING SYSTEMATIC CHECK...**
|
||||
1446
assets/audio/music/forest_ambient.mp3
Normal file
1446
assets/audio/music/forest_ambient.mp3
Normal file
File diff suppressed because one or more lines are too long
1446
assets/audio/sfx/footstep_grass.wav
Normal file
1446
assets/audio/sfx/footstep_grass.wav
Normal file
File diff suppressed because one or more lines are too long
1446
assets/audio/sfx/wood_chop.wav
Normal file
1446
assets/audio/sfx/wood_chop.wav
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/audio/voiceover/prologue/prologue_01.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_01.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_02.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_02.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_03.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_03.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_04.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_04.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_05.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_05.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_06.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_06.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_07.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_07.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_08.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_08.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_09.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_09.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_10.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_10.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_11.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_11.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_12.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_12.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_13.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_13.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_14.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_14.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_15.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_15.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_16.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_16.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_17.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_17.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_18.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_18.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue/prologue_19.wav
Normal file
BIN
assets/audio/voiceover/prologue/prologue_19.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_01.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_01.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_02.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_02.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_03.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_03.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_04.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_04.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_05.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_05.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_06.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_06.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_07.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_07.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_08.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_08.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_09.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_09.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_10.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_10.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_11.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_11.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_en/prologue_12.wav
Normal file
BIN
assets/audio/voiceover/prologue_en/prologue_12.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_01.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_01.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_02.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_02.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_03.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_03.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_04.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_04.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_05.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_05.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_06.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_06.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_07.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_07.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_08.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_08.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_09.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_09.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_10.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_10.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_11.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_11.wav
Normal file
Binary file not shown.
BIN
assets/audio/voiceover/prologue_sl/prologue_12.wav
Normal file
BIN
assets/audio/voiceover/prologue_sl/prologue_12.wav
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/audio/voices/kai/kai_test_01.mp3
Normal file
BIN
assets/audio/voices/kai/kai_test_01.mp3
Normal file
Binary file not shown.
BIN
assets/audio/voices/narrator/narrator_01.mp3
Normal file
BIN
assets/audio/voices/narrator/narrator_01.mp3
Normal file
Binary file not shown.
BIN
assets/audio/voices/narrator/narrator_02.mp3
Normal file
BIN
assets/audio/voices/narrator/narrator_02.mp3
Normal file
Binary file not shown.
BIN
assets/audio/voices/narrator/narrator_03.mp3
Normal file
BIN
assets/audio/voices/narrator/narrator_03.mp3
Normal file
Binary file not shown.
221
scripts/complete_asset_audit.py
Executable file
221
scripts/complete_asset_audit.py
Executable 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
203
scripts/convert_audio_to_ogg.py
Executable 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()
|
||||
147
scripts/generate_voices_edge_tts.py
Executable file
147
scripts/generate_voices_edge_tts.py
Executable file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Edge TTS Voice Generator
|
||||
Generate voice-over audio using Microsoft Edge TTS
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import edge_tts
|
||||
from pathlib import Path
|
||||
|
||||
# Output directory
|
||||
OUTPUT_DIR = Path("/Users/davidkotnik/repos/novafarma/assets/audio/voices")
|
||||
|
||||
# Voice configurations
|
||||
VOICES = {
|
||||
"kai_en": "en-US-AvaNeural", # English - female, young
|
||||
"kai_sl": "sl-SI-PetraNeural", # Slovenian
|
||||
"ana_en": "en-US-JennyNeural", # English - warm, friendly
|
||||
"ana_sl": "sl-SI-PetraNeural",
|
||||
"narrator_en": "en-US-GuyNeural", # English - deep, storytelling
|
||||
"narrator_sl": "sl-SI-RokNeural", # Slovenian male
|
||||
}
|
||||
|
||||
async def generate_voice(text: str, voice: str, output_path: Path, rate: str = "+0%", pitch: str = "+0Hz"):
|
||||
"""
|
||||
Generate voice audio from text
|
||||
|
||||
Args:
|
||||
text: Text to convert to speech
|
||||
voice: Voice ID (e.g. "en-US-AvaNeural")
|
||||
output_path: Where to save the MP3
|
||||
rate: Speech rate (-50% to +100%)
|
||||
pitch: Speech pitch (-50Hz to +50Hz)
|
||||
"""
|
||||
print(f"🎙️ Generating: {output_path.name}")
|
||||
print(f" Voice: {voice}")
|
||||
print(f" Text: {text[:50]}...")
|
||||
|
||||
# Create output directory
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Generate speech
|
||||
communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
|
||||
await communicate.save(str(output_path))
|
||||
|
||||
# Check file size
|
||||
size = output_path.stat().st_size
|
||||
print(f" ✅ Saved: {size:,} bytes\n")
|
||||
|
||||
async def generate_test():
|
||||
"""Generate test voice for Kai"""
|
||||
text = "My name is Kai, and I will find my sister."
|
||||
output = OUTPUT_DIR / "kai" / "kai_test_01.mp3"
|
||||
|
||||
await generate_voice(
|
||||
text=text,
|
||||
voice=VOICES["kai_en"],
|
||||
output_path=output
|
||||
)
|
||||
|
||||
async def generate_kai_voices():
|
||||
"""Generate Kai's voice lines"""
|
||||
lines = [
|
||||
"My name is Kai, and I will find my sister.",
|
||||
"Ana, where are you? I won't give up.",
|
||||
"This farm... it reminds me of home.",
|
||||
"I need to keep farming. For Ana.",
|
||||
"Another day, another harvest. But I won't forget."
|
||||
]
|
||||
|
||||
for i, text in enumerate(lines, 1):
|
||||
output = OUTPUT_DIR / "kai" / f"kai_{i:02d}.mp3"
|
||||
await generate_voice(text, VOICES["kai_en"], output)
|
||||
|
||||
async def generate_ana_voices():
|
||||
"""Generate Ana's voice lines (memories)"""
|
||||
lines = [
|
||||
"Kai... can you hear me?",
|
||||
"Remember the farm... remember our home.",
|
||||
"I'm still here, Kai. Don't forget me.",
|
||||
"The valley holds secrets... find them."
|
||||
]
|
||||
|
||||
for i, text in enumerate(lines, 1):
|
||||
output = OUTPUT_DIR / "ana" / f"ana_{i:02d}.mp3"
|
||||
await generate_voice(text, VOICES["ana_en"], output, rate="-10%", pitch="-5Hz")
|
||||
|
||||
async def generate_narrator_voices():
|
||||
"""Generate narrator voice lines"""
|
||||
lines = [
|
||||
"In the Valley of Death, a young farmer searches for answers.",
|
||||
"Long ago, this valley was green and full of life.",
|
||||
"But the dead walk now, and the living must survive."
|
||||
]
|
||||
|
||||
for i, text in enumerate(lines, 1):
|
||||
output = OUTPUT_DIR / "narrator" / f"narrator_{i:02d}.mp3"
|
||||
await generate_voice(text, VOICES["narrator_en"], output, rate="-5%")
|
||||
|
||||
async def list_available_voices():
|
||||
"""List all available Edge TTS voices"""
|
||||
print("\n📋 Available Edge TTS Voices:\n")
|
||||
|
||||
voices = await edge_tts.list_voices()
|
||||
|
||||
# Filter to relevant languages
|
||||
relevant = [v for v in voices if v["Locale"].startswith(("en-", "sl-"))]
|
||||
|
||||
for voice in relevant[:20]: # Show first 20
|
||||
print(f" {voice['ShortName']}")
|
||||
print(f" Language: {voice['Locale']}")
|
||||
print(f" Gender: {voice['Gender']}")
|
||||
print()
|
||||
|
||||
async def main():
|
||||
"""Main execution"""
|
||||
print("="*60)
|
||||
print("🎙️ EDGE TTS VOICE GENERATOR")
|
||||
print("="*60)
|
||||
|
||||
# Generate test first
|
||||
print("\n🧪 GENERATING TEST VOICE:\n")
|
||||
await generate_test()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ TEST COMPLETE! Check: assets/audio/voices/kai/kai_test_01.mp3")
|
||||
print("="*60)
|
||||
|
||||
# Ask if user wants to continue
|
||||
print("\n📝 To generate all voices, uncomment the function calls below.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run async main
|
||||
asyncio.run(main())
|
||||
|
||||
# Generate all voices
|
||||
print("\n" + "="*60)
|
||||
print("🎬 GENERATING ALL CHARACTER VOICES...")
|
||||
print("="*60)
|
||||
|
||||
asyncio.run(generate_kai_voices())
|
||||
asyncio.run(generate_ana_voices())
|
||||
asyncio.run(generate_narrator_voices())
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ ALL VOICES GENERATED!")
|
||||
print("="*60)
|
||||
Reference in New Issue
Block a user