~minus/stewdio-api

b06ddec968561e28a1b659ae0b5151c427d6334c — minus 5 years ago beca4b7
Initial work on getting MusicBrainz data

More specifically: fetch releases and recordings on adding/updating the
library. Does not adhere to the AcousticBrainz API rate limit of 3 req/s
yet.
A alembic/versions/9468e73ed926_add_musicbrainz_metadata.py => alembic/versions/9468e73ed926_add_musicbrainz_metadata.py +22 -0
@@ 0,0 1,22 @@
"""Add MusicBrainz metadata

Revision ID: 9468e73ed926
Revises: 74eaf7f47b52
Create Date: 2018-03-29 23:37:07.888458

"""

# revision identifiers, used by Alembic.
revision = '9468e73ed926'
down_revision = '74eaf7f47b52'

from alembic import op
import sqlalchemy as sa


def upgrade():
    op.add_column('songs', sa.Column('mb_metadata', sa.JSON(), nullable=True))


def downgrade():
    op.drop_column('songs', 'mb_metadata')

M requirements.txt => requirements.txt +1 -0
@@ 1,3 1,4 @@
pyacoustid
flask
flask-sockets
gevent

M stewdio-api.conf => stewdio-api.conf +5 -2
@@ 11,7 11,10 @@ host = localhost
port = 5432

[search]
off-vocal-regex=[^\w](w/o|without)[^\w].*[\)\]-]$|(instrumental|off vocal|カラオケ)[・\s]*(カラオケ|バージョン)?(.*ver(sion|.)?)?\s*[->~~\]))]?[)\]]?\s*$
off-vocal-regex = [^\w](w/o|without)[^\w].*[\)\]-]$|(instrumental|off vocal|カラオケ)[・\s]*(カラオケ|バージョン)?(.*ver(sion|.)?)?\s*[->~~\]))]?[)\]]?\s*$

[storage-status]
test=/var/music/some.mp3
test = /var/music/some.mp3

[acoustid]
api-key = nrUnHqXcsAE

M stewdio/config.py => stewdio/config.py +4 -0
@@ 42,3 42,7 @@ if off_vocal_regex:

cfg_storage_status: configparser.SectionProxy = config['storage-status']
storage_status = dict(cfg_storage_status)


cfg_acoustid: configparser.SectionProxy = config['acoustid']
acoustid_api_key = cfg_acoustid.get('api-key')

M stewdio/database.py => stewdio/database.py +5 -0
@@ 33,3 33,8 @@ class Database:
        Base.query = session.query_property()

        return session

if __name__ == '__main__':
    from .config import db
    from .types import *
    session = db.create_session()

M stewdio/library.py => stewdio/library.py +21 -2
@@ 5,6 5,8 @@ from functools import partial
from pathlib import Path
from typing import NamedTuple

import acoustid
from acoustid import WebServiceError
from tinytag import TinyTag

from stewdio.types.song import SongStatus


@@ 28,14 30,28 @@ def compute_hash(file):
	return h.hexdigest()


def augment_with_musicbrainz_metadata(song):
	meta = 'recordings releases'
	try:
		data = acoustid.match(config.acoustid_api_key, song.path, meta=meta, parse=False)
		if data.get('status') != 'ok':
			raise WebServiceError("status not ok")
		if not isinstance(data.get('results'), list):
			raise WebServiceError("invalid results")
		song.mb_metadata = data['results']
		L.debug(f"Augmented {song} with MusicBrainz metadata")
	except WebServiceError as e:
		L.exception("Failed to fetch MusicBrainz metadata")


def update(session, scan_dir):
	songs = []
	scan_dir = Path(scan_dir)
	L.info(f"Scanning directory {scan_dir} for new files")
	for root, dirs, files in os.walk(scan_dir):
		root = Path(root)
		for file in sorted(files):
			path = root / file
		for path in sorted(files):
			path = root / path
			path = path.absolute()
			if path.suffix not in ('.flac', '.mp3', '.aac', '.opus', '.ogg'):
				continue


@@ 43,6 59,8 @@ def update(session, scan_dir):
			hash = compute_hash(path)
			song = session.query(Song).filter_by(hash=hash).one_or_none()
			if song:
				if not song.mb_metadata:
					augment_with_musicbrainz_metadata(song)
				L.info(f"Song {song} (path: {song.path}) already exists in database (new path: {path}), skipping")
				continue
			metadata = TinyTag.get(str(path))


@@ 65,6 83,7 @@ def update(session, scan_dir):
				artist=artist,
				album=album,
			)
			augment_with_musicbrainz_metadata(song)
			session.add(song)
			session.flush()
			L.info(f"Added song {song}")

M stewdio/types/song.py => stewdio/types/song.py +2 -0
@@ 46,6 46,8 @@ class Song(Base):
            collection_class=set,
            back_populates="songs")

    mb_metadata = sa.Column(sa.JSON)

    def json(self):
        return dict(
            **{attr: getattr(self, attr)