Files
LinedanceAfspiller/linedance-app/local/scanner.py
2026-04-19 23:45:59 +02:00

154 lines
5.0 KiB
Python

"""
scanner.py — Scanning af musikbiblioteker i baggrunden. v0.9
Skriver til files-tabellen og finder/opretter sange i songs-tabellen.
"""
import os
import logging
import time
from pathlib import Path
logger = logging.getLogger(__name__)
SUPPORTED = {'.mp3', '.flac', '.m4a', '.ogg', '.wav', '.aiff', '.wma'}
def is_supported(path) -> bool:
return Path(path).suffix.lower() in SUPPORTED
def get_file_mtime(path: Path) -> str:
try:
return str(os.path.getmtime(str(path)))
except Exception:
return ""
def scan_library(library_id: int, library_path: str, db_path: str,
overwrite_bpm: bool = False,
progress_callback=None) -> int:
"""
Scan ét bibliotek og upsert til files + songs tabellerne.
Returnerer antal scannede filer.
"""
import sqlite3
from local.tag_reader import read_tags
from local.local_db import find_or_create_song, upsert_file
base = Path(library_path)
if not base.exists():
return 0
# Byg indeks over kendte filer (path → mtime)
conn = sqlite3.connect(db_path, timeout=10)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
known = {}
for row in conn.execute(
"SELECT local_path, file_modified_at FROM files WHERE file_missing=0"
).fetchall():
known[row["local_path"]] = row["file_modified_at"]
# Find alle musikfiler
all_files = []
for dirpath, _, filenames in os.walk(str(base), followlinks=False):
for fn in filenames:
fp = Path(dirpath) / fn
if is_supported(fp):
all_files.append(fp)
total = len(all_files)
done = 0
for fp in all_files:
path_str = str(fp)
mtime = get_file_mtime(fp)
if progress_callback:
progress_callback(done, total, fp.name)
# Spring over uændrede filer
if path_str in known and known[path_str] == mtime:
done += 1
time.sleep(0.005)
continue
try:
tags = read_tags(str(fp))
title = tags.get("title", "") or fp.stem
artist = tags.get("artist", "")
album = tags.get("album", "")
bpm = tags.get("bpm", 0)
mbid = tags.get("mbid", "")
acoustid = tags.get("acoustid", "")
duration_sec = tags.get("duration_sec", 0)
file_format = tags.get("file_format", fp.suffix.lstrip(".").lower())
extra_tags = tags.get("extra_tags", "{}")
# Find eller opret sang i global katalog
song_id = find_or_create_song(
title=title, artist=artist, album=album,
bpm=bpm, duration_sec=duration_sec,
mbid=mbid, acoustid=acoustid,
)
# Opdater BPM på sangen hvis vi har bedre data
if bpm and bpm > 0:
conn.execute(
"UPDATE songs SET bpm=? WHERE id=? AND (bpm=0 OR bpm IS NULL)",
(bpm, song_id)
)
# Opret eller opdater fil-post
upsert_file(
song_id=song_id,
local_path=path_str,
file_format=file_format,
file_modified_at=mtime,
extra_tags=extra_tags,
)
# Dans-tags fra fil
file_dances = tags.get("dances", [])
if file_dances:
existing_count = conn.execute(
"SELECT COUNT(*) FROM song_dances WHERE song_id=?", (song_id,)
).fetchone()[0]
if existing_count == 0:
import uuid
for order, dance_name in enumerate(file_dances, start=1):
dance_row = conn.execute(
"SELECT id FROM dances WHERE name=? COLLATE NOCASE LIMIT 1",
(dance_name,)
).fetchone()
if not dance_row:
cur = conn.execute(
"INSERT INTO dances (name) VALUES (?)", (dance_name,)
)
dance_id = cur.lastrowid
else:
dance_id = dance_row["id"]
conn.execute(
"INSERT OR IGNORE INTO song_dances (id, song_id, dance_id, dance_order) VALUES (?,?,?,?)",
(str(uuid.uuid4()), song_id, dance_id, order)
)
conn.commit()
except Exception as e:
logger.warning(f"Scan fejl {fp.name}: {e}")
done += 1
time.sleep(0.02)
# Marker manglende filer
for path_str in known:
if not Path(path_str).exists():
conn.execute(
"UPDATE files SET file_missing=1 WHERE local_path=?", (path_str,)
)
conn.commit()
conn.close()
logger.info(f"Scan færdig: {done} filer i {library_path}")
return done