176 lines
6.6 KiB
Python
176 lines
6.6 KiB
Python
|
|
"""
|
||
|
|
Diagnostic script — run inside the music-companion container:
|
||
|
|
docker exec -it music-companion python diagnose.py
|
||
|
|
|
||
|
|
Checks:
|
||
|
|
1. What MUSIC_DIR points to and what's in it
|
||
|
|
2. What paths are stored in smart_dj.db
|
||
|
|
3. Whether those DB paths actually exist on disk
|
||
|
|
4. Tests resolve_path() with sample paths
|
||
|
|
"""
|
||
|
|
import os, sqlite3, hashlib
|
||
|
|
|
||
|
|
MUSIC_DIR = os.getenv("MUSIC_DIR", "/music")
|
||
|
|
DB_PATH = os.getenv("DB_PATH", "/app/data/smart_dj.db")
|
||
|
|
VIS_CACHE_DIR = os.getenv("VIS_CACHE_DIR", "/app/data/vis_cache")
|
||
|
|
|
||
|
|
print("=" * 60)
|
||
|
|
print("COMPANION API DIAGNOSTICS")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
# 1. Check MUSIC_DIR
|
||
|
|
print(f"\n1. MUSIC_DIR = {MUSIC_DIR}")
|
||
|
|
print(f" Exists: {os.path.isdir(MUSIC_DIR)}")
|
||
|
|
|
||
|
|
if os.path.isdir(MUSIC_DIR):
|
||
|
|
top = os.listdir(MUSIC_DIR)
|
||
|
|
dirs = sorted([d for d in top if os.path.isdir(os.path.join(MUSIC_DIR, d))])
|
||
|
|
files = [f for f in top if os.path.isfile(os.path.join(MUSIC_DIR, f))]
|
||
|
|
print(f" Top-level: {len(dirs)} folders, {len(files)} files")
|
||
|
|
|
||
|
|
# Show first 10 folders
|
||
|
|
for d in dirs[:10]:
|
||
|
|
print(f" 📁 {d}")
|
||
|
|
if len(dirs) > 10:
|
||
|
|
print(f" ... and {len(dirs) - 10} more")
|
||
|
|
|
||
|
|
# Check if there's a nested "music" folder (common misconfiguration)
|
||
|
|
if "music" in dirs:
|
||
|
|
nested = os.path.join(MUSIC_DIR, "music")
|
||
|
|
nested_contents = os.listdir(nested)
|
||
|
|
print(f"\n ⚠️ FOUND nested 'music/' folder inside MUSIC_DIR!")
|
||
|
|
print(f" {nested} has {len(nested_contents)} items")
|
||
|
|
print(f" This usually means docker-compose mounts the parent instead of the music dir")
|
||
|
|
print(f" Your files are probably at /music/music/Artist/Album/song.flac")
|
||
|
|
print(f" Fix: change volume mount from '/home/pi/navidrome:/music' to '/home/pi/navidrome/music:/music'")
|
||
|
|
|
||
|
|
# Count total audio files
|
||
|
|
audio_count = 0
|
||
|
|
sample_paths = []
|
||
|
|
for root, _, fnames in os.walk(MUSIC_DIR):
|
||
|
|
for f in fnames:
|
||
|
|
if f.lower().endswith(('.mp3', '.flac', '.m4a', '.ogg', '.opus', '.wav')):
|
||
|
|
audio_count += 1
|
||
|
|
fp = os.path.join(root, f)
|
||
|
|
rel = os.path.relpath(fp, MUSIC_DIR)
|
||
|
|
if len(sample_paths) < 5:
|
||
|
|
sample_paths.append((fp, rel))
|
||
|
|
|
||
|
|
print(f"\n Total audio files: {audio_count}")
|
||
|
|
print(f"\n Sample file paths (absolute → relative):")
|
||
|
|
for abs_path, rel_path in sample_paths:
|
||
|
|
print(f" ABS: {abs_path}")
|
||
|
|
print(f" REL: {rel_path}")
|
||
|
|
print()
|
||
|
|
else:
|
||
|
|
print(" ❌ MUSIC_DIR does not exist!")
|
||
|
|
|
||
|
|
# 2. Check DB
|
||
|
|
print(f"\n2. DATABASE = {DB_PATH}")
|
||
|
|
print(f" Exists: {os.path.isfile(DB_PATH)}")
|
||
|
|
|
||
|
|
db_paths = []
|
||
|
|
if os.path.isfile(DB_PATH):
|
||
|
|
with sqlite3.connect(DB_PATH) as c:
|
||
|
|
count = c.execute("SELECT COUNT(*) FROM dj_profiles").fetchone()[0]
|
||
|
|
print(f" Profiles: {count}")
|
||
|
|
|
||
|
|
rows = c.execute("SELECT file_path FROM dj_profiles LIMIT 10").fetchall()
|
||
|
|
db_paths = [r[0] for r in rows]
|
||
|
|
|
||
|
|
print(f"\n Sample DB paths:")
|
||
|
|
for p in db_paths:
|
||
|
|
exists = os.path.isfile(p)
|
||
|
|
icon = "✓" if exists else "✗"
|
||
|
|
print(f" {icon} {p}")
|
||
|
|
if not exists:
|
||
|
|
# Try to find what DOES exist
|
||
|
|
basename = os.path.basename(p)
|
||
|
|
for root, _, fnames in os.walk(MUSIC_DIR):
|
||
|
|
if basename in fnames:
|
||
|
|
actual = os.path.join(root, basename)
|
||
|
|
print(f" → FOUND at: {actual}")
|
||
|
|
break
|
||
|
|
|
||
|
|
# Check how many DB paths actually exist
|
||
|
|
all_paths = c.execute("SELECT file_path FROM dj_profiles").fetchall()
|
||
|
|
existing = sum(1 for (p,) in all_paths if os.path.isfile(p))
|
||
|
|
missing = len(all_paths) - existing
|
||
|
|
print(f"\n DB path health: {existing} exist, {missing} broken")
|
||
|
|
if missing > 0:
|
||
|
|
print(f" ⚠️ {missing} profiles point to files that don't exist!")
|
||
|
|
print(f" This means the DB was built with a different mount path")
|
||
|
|
else:
|
||
|
|
print(" ❌ Database does not exist! Run pre_analyze.py first")
|
||
|
|
|
||
|
|
# 3. Check vis cache
|
||
|
|
print(f"\n3. VIS CACHE = {VIS_CACHE_DIR}")
|
||
|
|
if os.path.isdir(VIS_CACHE_DIR):
|
||
|
|
vis_files = os.listdir(VIS_CACHE_DIR)
|
||
|
|
print(f" Cached: {len(vis_files)} files")
|
||
|
|
else:
|
||
|
|
print(f" Does not exist")
|
||
|
|
|
||
|
|
# 4. Test resolve_path with the sample paths
|
||
|
|
print(f"\n4. RESOLVE_PATH TESTS")
|
||
|
|
print(f" Testing with various path formats...")
|
||
|
|
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
def resolve_path(relative):
|
||
|
|
cleaned = relative.lstrip("/")
|
||
|
|
direct = os.path.join(MUSIC_DIR, cleaned)
|
||
|
|
if os.path.isfile(direct):
|
||
|
|
return ("direct", direct)
|
||
|
|
parts = Path(cleaned).parts
|
||
|
|
for i in range(1, len(parts)):
|
||
|
|
sub = os.path.join(MUSIC_DIR, *parts[i:])
|
||
|
|
if os.path.isfile(sub):
|
||
|
|
return (f"strip-{i}", sub)
|
||
|
|
target = os.path.basename(cleaned)
|
||
|
|
if target:
|
||
|
|
for root, _, files in os.walk(MUSIC_DIR):
|
||
|
|
if target in files:
|
||
|
|
return ("filename-search", os.path.join(root, target))
|
||
|
|
return ("FAILED", None)
|
||
|
|
|
||
|
|
if sample_paths:
|
||
|
|
for abs_path, rel_path in sample_paths:
|
||
|
|
# Test various formats the iOS app might send
|
||
|
|
tests = [
|
||
|
|
("exact relative", rel_path),
|
||
|
|
("with music/ prefix", f"music/{rel_path}"),
|
||
|
|
("with leading /", f"/{rel_path}"),
|
||
|
|
("absolute", abs_path),
|
||
|
|
]
|
||
|
|
print(f"\n File: {os.path.basename(rel_path)}")
|
||
|
|
for label, test_path in tests:
|
||
|
|
strategy, result = resolve_path(test_path)
|
||
|
|
icon = "✓" if result else "✗"
|
||
|
|
print(f" {icon} {label}: '{test_path}' → {strategy}")
|
||
|
|
break # Just test one file
|
||
|
|
|
||
|
|
# 5. Summary
|
||
|
|
print(f"\n{'=' * 60}")
|
||
|
|
print("SUMMARY")
|
||
|
|
print(f"{'=' * 60}")
|
||
|
|
|
||
|
|
if os.path.isdir(MUSIC_DIR):
|
||
|
|
if "music" in [d for d in os.listdir(MUSIC_DIR) if os.path.isdir(os.path.join(MUSIC_DIR, d))]:
|
||
|
|
print("❌ LIKELY ISSUE: Nested 'music' folder detected.")
|
||
|
|
print(" Your docker-compose probably mounts /home/pi/navidrome:/music")
|
||
|
|
print(" but should mount /home/pi/navidrome/music:/music")
|
||
|
|
elif audio_count == 0:
|
||
|
|
print("❌ No audio files found in MUSIC_DIR!")
|
||
|
|
elif db_paths and not os.path.isfile(db_paths[0]):
|
||
|
|
print("❌ DB paths don't match filesystem. The DB was built with")
|
||
|
|
print(" a different mount configuration. Fix mount and re-run:")
|
||
|
|
print(" docker exec -it music-companion python pre_analyze.py --force")
|
||
|
|
else:
|
||
|
|
print("✓ Configuration looks correct")
|
||
|
|
print(f" {audio_count} audio files, {count if os.path.isfile(DB_PATH) else 0} profiles")
|
||
|
|
else:
|
||
|
|
print("❌ MUSIC_DIR doesn't exist — check docker-compose volumes")
|
||
|
|
|
||
|
|
print()
|