154 lines
5.0 KiB
Python
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 |