#!/usr/bin/env python3 """ Database operations for Scorch. """ import os import multiprocessing from datetime import datetime, timezone import asyncpg import mutagen import mutagen.mp3 import config MUSIC_EXT = ['flac', 'mp3'] async def build_library(root_dir): """Walks the music directory and builds a library of tracks.""" print("Building library") filepaths = [] for dir_name, sub_dirs, files in os.walk(root_dir): for file in files: if not os.path.splitext(file)[1][1:] in MUSIC_EXT: continue filepath = os.path.join(root_dir, dir_name, file) last_modified = datetime.fromtimestamp( os.path.getmtime(filepath), timezone.utc) filepaths.append((filepath, last_modified)) db_pool = await asyncpg.create_pool(**config.db) async with db_pool.acquire() as conn: tracks_prev = await conn.fetch("SELECT * FROM track") tracks_prev = {track['filepath']: track for track in tracks_prev} global worker def worker(tup): """Worker for multi-processing tracks.""" filepath, last_modified = tup track_prev = tracks_prev.get(filepath) if track_prev: if track_prev['last_modified'] >= last_modified: return tuple(track_prev) data = read_track(filepath) return data with multiprocessing.Pool() as pool: mapping = pool.imap(worker, filepaths) tracks = [] prev_percent = 0 while True: try: tracks.append(mapping.next()) except StopIteration: break percent = round(len(tracks) / len(filepaths) * 100, 2) if percent >= prev_percent + 2.5: print(f"{percent}%") prev_percent = percent async with db_pool.acquire() as conn: await conn.execute("DELETE FROM track") await conn.executemany( "INSERT INTO track VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", tracks ) print("Done") def read_track(filepath): """ Reads the specified file and extracts relevant information from it. Returns a tuple. """ if filepath.endswith("mp3"): m = mutagen.mp3.EasyMP3(filepath) else: m = mutagen.File(filepath) artist = m.get('artist', [''])[0] if m.get('albumartist'): albumartist = m.get('albumartist', [''])[0] else: albumartist = m.get('artist', [''])[0] date = m.get('date', [''])[0] album = m.get('album', [''])[0] discnumber = m.get('discnumber', [''])[0] tracknumber = m.get('tracknumber', [''])[0] title = m.get('title', [''])[0] genre = m.get('genre', [''])[0] length = m.info.length last_modified = datetime.fromtimestamp( os.path.getmtime(filepath), timezone.utc) d = locals() d.pop('m') return tuple(d.values())