From 2b87e1d2ec58c10586bf473abe64ed740b42c627 Mon Sep 17 00:00:00 2001 From: dave Date: Sun, 13 Aug 2017 23:54:37 -0700 Subject: [PATCH] add id3 scanner and support more apis --- pysonic/api.py | 86 +++++++++++++++++++++++++++++++++++++++------- pysonic/daemon.py | 1 + pysonic/library.py | 4 +++ pysonic/scanner.py | 47 ++++++++++++++++++++++++- 4 files changed, 124 insertions(+), 14 deletions(-) diff --git a/pysonic/api.py b/pysonic/api.py index 43f1be5..403bfb7 100644 --- a/pysonic/api.py +++ b/pysonic/api.py @@ -3,6 +3,7 @@ import logging import cherrypy import subprocess from time import time +from random import shuffle from bs4 import BeautifulSoup from pysonic.library import LETTER_GROUPS from pysonic.types import MUSIC_TYPES @@ -85,6 +86,58 @@ class PysonicApi(object): index.append(artist_tag) yield doc.prettify() + @cherrypy.expose + def savePlayQueue_view(self, id, current, position, **kwargs): + # /rest/savePlayQueue.view? + # u=dave& + # s=h7vcg97gm2vbb7m4133pavs1ot& + # t=355f45124d9d3a75fe681c11d94ed066& + # v=1.2.0& + # c=DSub& + # id=296& + # id=289& + # id=292&id=287&id=288&id=290&id=293&id=294&id=297&id=298&id=291& + # current=297& + # position=0 + print("TODO save playlist with items {} current {} position {}".format(id, current, position)) + + @cherrypy.expose + def getAlbumList_view(self, type, size=50, offset=0, **kwargs): + albums = self.library.get_albums() + if type == "random": + shuffle(albums) + elif type == "alphabeticalByName": + albums.sort(key=lambda item: item.get("id3_album", item["album"])) + else: + raise NotImplemented() + albumset = albums[0 + int(offset):int(size) + int(offset)] + + cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8' + doc, root = self.response() + albumlist = doc.new_tag("albumList") + doc.append(albumlist) + + for album in albumset: + album_meta = self.library.db.decode_metadata(album['metadata']) + tag = doc.new_tag("album", + id=album["id"], + parent=album["parent"], + isDir="true" if album['isdir'] else "false", + title=album_meta.get("id3_title", album["name"]), + album=album_meta.get("id3_album", album["album"]), + artist=album_meta.get("id3_artist", album["artist"]), + # X year="2014" + # X coverArt="3228" + # playCount="0" + # created="2016-05-08T05:31:31.000Z"/> + ) + if 'cover' in album_meta: + tag.attrs["coverArt"] = album_meta["cover"] + if 'id3_year' in album_meta: + tag.attrs["year"] = album_meta['id3_year'] + albumlist.append(tag) + yield doc.prettify() + @cherrypy.expose def getMusicDirectory_view(self, id, **kwargs): """ @@ -108,34 +161,41 @@ class PysonicApi(object): # omit not dirs and media in browser if not item["isdir"] and item["type"] not in MUSIC_TYPES: continue + item_meta = self.db.decode_metadata(item['metadata']) child = doc.new_tag("child", id=item["id"], parent=directory["id"], isDir="true" if item['isdir'] else "false", - title=item["name"], - album=item["name"], - artist=directory["name"], + title=item_meta.get("id3_title", item["name"]), + album=item_meta.get("id3_album", item["album"]), + artist=item_meta.get("id3_artist", item["artist"]), # playCount="5", # created="2016-04-25T07:31:33.000Z" - # track="3", - # year="2012", + # X track="3", + # X year="2012", + # X coverArt="12835", + # X contentType="audio/mpeg" + # X suffix="mp3" # genre="Other", - # coverArt="12835", - # contentType="audio/mpeg" - # suffix="mp3" # size="15838864" # duration="395" # bitRate="320" # path="Cosmic Gate/Sign Of The Times/03 Flatline (featuring Kyler England).mp3" - # albumId="933" - # artistId="353" - # type="music"/> - ) - item_meta = self.db.decode_metadata(item['metadata']) + albumId=directory["id"], + artistId=directory["parent"], + type="music") + if "." in item["name"]: + child.attrs["suffix"] = item["name"].split(".")[-1] + if item_meta["type"]: + child.attrs["contentType"] = item_meta["type"] if 'cover' in item_meta: child.attrs["coverArt"] = item_meta["cover"] elif 'cover' in dir_meta: child.attrs["coverArt"] = dir_meta["cover"] + if 'track' in item_meta: + child.attrs["track"] = item_meta['track'] + if 'id3_year' in item_meta: + child.attrs["year"] = item_meta['id3_year'] dirtag.append(child) yield doc.prettify() diff --git a/pysonic/daemon.py b/pysonic/daemon.py index 34e370a..8101c45 100644 --- a/pysonic/daemon.py +++ b/pysonic/daemon.py @@ -37,6 +37,7 @@ def main(): logging.warning("Libraries: {}".format([i["name"] for i in library.get_libraries()])) logging.warning("Artists: {}".format([i["name"] for i in library.get_artists()])) + logging.warning("Albums: {}".format(len(library.get_albums()))) cherrypy.tree.mount(PysonicApi(db, library, args), '/rest/', {'/': {}}) cherrypy.config.update({ diff --git a/pysonic/library.py b/pysonic/library.py index d0e55f2..086df7b 100644 --- a/pysonic/library.py +++ b/pysonic/library.py @@ -68,6 +68,10 @@ class PysonicLibrary(object): def get_dir_children(self, dirid): return self.db.getnodes(dirid) + @memoize + def get_albums(self): + return self.db.getnodes(*[item["id"] for item in self.get_artists()]) + @memoize def get_filepath(self, nodeid): parents = [self.db.getnode(nodeid)] diff --git a/pysonic/scanner.py b/pysonic/scanner.py index 1eb7378..3da79d8 100644 --- a/pysonic/scanner.py +++ b/pysonic/scanner.py @@ -4,7 +4,9 @@ import logging import mimetypes from time import time from threading import Thread -from pysonic.types import KNOWN_MIMES +from pysonic.types import KNOWN_MIMES, MUSIC_TYPES +from mutagen.id3 import ID3 +from mutagen.id3._util import ID3NoHeaderError logging = logging.getLogger("scanner") @@ -93,5 +95,48 @@ class PysonicFilesystemScanner(object): else: logging.warning("Ignoring unreadable file at {}, unknown ftype ({}, {})" .format(fpath, ftype, extra)) + # + # + # + # Add advanced id3 metadata + for artist_dir in self.library.db.getnodes(parent["id"]): + artist = artist_dir["name"] + for album_dir in self.library.db.getnodes(artist_dir["id"]): + album = album_dir["name"] + album_meta = self.library.db.get_metadata(album_dir["id"]) + for track_file in self.library.db.getnodes(album_dir["id"]): + track_meta = self.library.db.decode_metadata(track_file['metadata']) + title = track_file["name"] + fpath = self.library.get_filepath(track_file["id"]) + if track_meta.get('id3_done', False) or track_file.get("type", "x") not in MUSIC_TYPES: + continue + print("Mutagening", fpath) + tags = {'id3_done': True} + try: + id3 = ID3(fpath) + # print(id3.pprint()) + try: + tags["track"] = int(''.join(id3['TRCK'].text).split("/")[0]) + except KeyError: + pass + try: + tags["id3_artist"] = ''.join(id3['TPE1'].text) + except KeyError: + pass + try: + tags["id3_album"] = ''.join(id3['TALB'].text) + except KeyError: + pass + try: + tags["id3_title"] = ''.join(id3['TIT2'].text) + except KeyError: + pass + try: + tags["id3_year"] = id3['TDRC'].text[0].year + except (KeyError, IndexError): + pass + except ID3NoHeaderError: + pass + self.library.db.update_metadata(track_file["id"], **tags) logging.warning("Library scan complete in {}s".format(int(time() - start)))