add build library functions
This commit is contained in:
parent
4f8edc0b43
commit
a6c43fe468
|
@ -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
108
build_db.py
Normal 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
13
pyrite.sql
Normal 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
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user