59 lines
3.4 KiB
Python
59 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Iterator
|
|
|
|
from .constants import MUSIC_EXTENSIONS
|
|
from .db import connect
|
|
from .hashing import fast_hash
|
|
from .metadata import read_metadata
|
|
|
|
|
|
def iter_music(root: Path) -> Iterator[Path]:
|
|
for path in root.rglob("*"):
|
|
if not path.is_file() or path.is_symlink() or path.name.startswith(".") or ".dedupe" in path.parts:
|
|
continue
|
|
if path.suffix.lower() in MUSIC_EXTENSIONS:
|
|
yield path
|
|
|
|
|
|
def index_file(conn, path: Path) -> None:
|
|
st = path.stat()
|
|
fh = fast_hash(path)
|
|
meta = read_metadata(path)
|
|
flags = ",".join(meta.get("version_flags", []))
|
|
old = conn.execute("SELECT file_size,fast_hash,inode,device FROM audio_tracks WHERE file_path=?", (str(path),)).fetchone()
|
|
identity_changed = old is not None and (old["file_size"], old["fast_hash"], old["inode"], old["device"]) != (st.st_size, fh, st.st_ino, st.st_dev)
|
|
conn.execute("""
|
|
INSERT INTO audio_tracks(file_path,file_size,fast_hash,duration,artist,title,album,format,bitrate,sample_rate,bit_depth,version_flags,inode,device,status)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,'active')
|
|
ON CONFLICT(file_path) DO UPDATE SET file_size=excluded.file_size, fast_hash=excluded.fast_hash,
|
|
duration=excluded.duration,artist=excluded.artist,title=excluded.title,album=excluded.album,format=excluded.format,
|
|
bitrate=excluded.bitrate,sample_rate=excluded.sample_rate,bit_depth=excluded.bit_depth,version_flags=excluded.version_flags,
|
|
inode=excluded.inode,device=excluded.device,updated_at=CURRENT_TIMESTAMP,status='active',
|
|
sha256=CASE WHEN excluded.file_size!=audio_tracks.file_size OR excluded.fast_hash!=audio_tracks.fast_hash
|
|
OR excluded.inode!=audio_tracks.inode OR excluded.device!=audio_tracks.device
|
|
THEN NULL ELSE audio_tracks.sha256 END,
|
|
chromaprint=CASE WHEN excluded.file_size!=audio_tracks.file_size OR excluded.fast_hash!=audio_tracks.fast_hash
|
|
OR excluded.inode!=audio_tracks.inode OR excluded.device!=audio_tracks.device
|
|
THEN NULL ELSE audio_tracks.chromaprint END,
|
|
review_status=CASE WHEN excluded.file_size!=audio_tracks.file_size OR excluded.fast_hash!=audio_tracks.fast_hash
|
|
OR excluded.inode!=audio_tracks.inode OR excluded.device!=audio_tracks.device
|
|
THEN NULL ELSE audio_tracks.review_status END
|
|
""", (str(path),st.st_size,fh,meta.get("duration"),meta.get("artist"),meta.get("title"),meta.get("album"),meta.get("format"),meta.get("bitrate"),meta.get("sample_rate"),meta.get("bit_depth"),flags,st.st_ino,st.st_dev))
|
|
if identity_changed:
|
|
conn.execute("UPDATE audio_tracks SET updated_at=CURRENT_TIMESTAMP WHERE file_path=?", (str(path),))
|
|
|
|
|
|
def scan(root: Path, db_path: Path) -> int:
|
|
count = 0
|
|
seen = set()
|
|
with connect(db_path) as conn:
|
|
for path in iter_music(root):
|
|
index_file(conn, path); seen.add(str(path)); count += 1
|
|
rows = conn.execute("SELECT id,file_path,status FROM audio_tracks WHERE status!='missing'").fetchall()
|
|
for row in rows:
|
|
if row["file_path"] not in seen and not Path(row["file_path"]).exists():
|
|
conn.execute("UPDATE audio_tracks SET status='missing',updated_at=CURRENT_TIMESTAMP WHERE id=?", (row["id"],))
|
|
return count
|