94 lines
2.5 KiB
Python
94 lines
2.5 KiB
Python
|
#!/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())
|