NavidromeApp/companion-api/diagnose.py

176 lines
6.6 KiB
Python
Raw Normal View History

2026-04-06 11:57:16 -07:00
"""
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()