add build library functions

This commit is contained in:
iou1name 2024-12-19 15:00:38 -05:00
parent 4f8edc0b43
commit a6c43fe468
3 changed files with 122 additions and 1 deletions

View File

@ -3,7 +3,7 @@ A naturally occurring iron disulfide mineral. The name comes from the Greek word
## Requirements ## Requirements
Python 3.12+ Python 3.12+
Python packages: `fastapi uvicorn[standard] httpx jinja2 asyncpg mutagen` Python packages: `fastapi uvicorn[standard] httpx jinja2 asyncpg tinytag`
## Install ## Install
``` ```

108
build_db.py Normal file
View File

@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
Builds the music library database.
"""
import os
import asyncio
import multiprocessing
import asyncpg
import tinytag
import config
MUSIC_EXT = ['.flac', '.mp3', '.opus']
def read_track(filepath):
"""
Reads the specified file and extracts relevant information from it.
"""
t = tinytag.TinyTag.get(filepath)
d = {
'filepath': filepath,
'artist': t.artist,
'albumartist': t.albumartist,
'album': t.album,
'title': t.title,
'date': t.year,
'discnumber': str(t.disc),
'tracknumber': str(t.track),
'genre': t.genre,
'duration': t.duration,
'last_modified': os.path.getmtime(filepath)
}
return d
async def build_library(root_dir):
"""Walks the directory and builds a library from tracks discovered."""
print("Building library")
db_pool = await asyncpg.create_pool(**config.db)
async with db_pool.acquire() as conn:
with open('pyrite.sql', 'r') as file:
await conn.execute(file.read())
filepaths = []
for dir_name, sub_dirs, files in os.walk(root_dir):
for file in files:
if not os.path.splitext(file)[1] in MUSIC_EXT:
continue
filepath = os.path.join(root_dir, dir_name, file)
last_modified = os.path.getmtime(filepath)
filepaths.append((filepath, last_modified))
async with db_pool.acquire() as conn:
tracks_prev = await conn.fetch("SELECT filepath, last_modified FROM track")
tracks_prev = {track['filepath']: track for track in tracks_prev}
global worker
def worker(args):
"""Worker for multi-processing tracks."""
filepath, last_modified = args
track_prev = tracks_prev.get(filepath)
if track_prev:
if track_prev['last_modified'] >= last_modified:
return
data = read_track(filepath)
return data
with multiprocessing.Pool() as pool:
mapping = pool.imap(worker, filepaths)
tracks = []
prev_percent = 0
while True:
try:
track = mapping.next()
if track:
tracks.append(track)
except StopIteration:
break
percent = round(len(tracks) / len(filepaths) * 100, 2)
if percent >= prev_percent + 2.5:
print(f"{percent}%")
prev_percent = percent
if not tracks:
print("No new tracks found!")
return
cols = ', '.join(tracks[0].keys())
vals = ', '.join(['$'+str(i) for i in range(1, len(tracks[0])+1)])
tracks_data = [list(track.values()) for track in tracks]
async with db_pool.acquire() as conn:
p = f"INSERT INTO track ({cols}) VALUES ({vals}) "
p += "ON CONFLICT(filepath) DO UPDATE SET "
for col in tracks[0].keys():
p += col + " = EXCLUDED." + col + ", "
p = p[:-2]
cur = await conn.prepare(p)
await cur.executemany(tracks_data)
print("Done")
if __name__ == "__main__":
asyncio.run(build_library(config.music_dir))

13
pyrite.sql Normal file
View File

@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS track (
filepath TEXT PRIMARY KEY,
artist TEXT,
albumartist TEXT,
album TEXT,
title TEXT,
date TEXT,
discnumber TEXT,
tracknumber TEXT,
genre TEXT,
duration FLOAT,
last_modified FLOAT
)