def build_transaction(cls, songs): """:param songs: a list of dictionary representations of songs.""" #Warn about metadata changes that may cause problems. #If you change the interface in api, you can warn about changing bad categories, too. #Something like safelychange(song, entries) where entries are only those you want to change. for song in songs: for key in song: allowed_values = MetadataExpectations.get_expectation(key).allowed_values if allowed_values and song[key] not in allowed_values: LogController.get_logger("modifyentries").warning( "setting key %s to unallowed value %s for id " "%s. Check metadata expectations in " "protocol.py" % (key, song[key], song["id"])) req = {"entries": songs} res = {"type": "object", "properties":{ "success": {"type":"boolean"}, "songs":WC_Protocol.song_array }, "additionalProperties":False } return (req, res)
def get_expectation(cls, key, warn_on_unknown=True): """Get the Expectation associated with the given key name. If no Expectation exists for that name, an immutable Expectation of any type is returned.""" try: expt = getattr(cls, key) if not issubclass(expt, _MetadataExpectation): raise TypeError return expt except (AttributeError, TypeError): if warn_on_unknown: LogController.get_logger("get_expectation").warning("unknown metadata type '%s'", key) return UnknownExpectation
def make_metadata_request(self, filenames): """Returns (Metadata protobuff, dictionary mapping ClientId to filename) for the given mp3s.""" filemap = {} #map clientid -> filename metadata = self.make_pb("metadata_request") for filename in filenames: if not filename.split(".")[-1].lower() == "mp3": LogController.get_logger("make_metadata_request").error( "cannot upload '%s' because it is not an mp3.", filename) continue track = metadata.tracks.add() #Eventually pull this to supported_filetypes audio = MP3(filename, ID3 = EasyID3) #The id is a 22 char hash of the file. It is found by: # stripping tags # getting an md5 sum # converting sum to base64 # removing trailing === #My implementation is _not_ the same hash the music manager will send; # they strip tags first. But files are differentiated across accounts, # so this shouldn't cause problems. #This will reupload files if their tags change. with open(filename, mode="rb") as f: file_contents = f.read() h = hashlib.md5(file_contents).digest() h = base64.encodestring(h)[:-3] id = h filemap[id] = filename track.id = id filesize = os.path.getsize(filename) track.fileSize = filesize track.bitrate = audio.info.bitrate / 1000 track.duration = int(audio.info.length * 1000) #GM requires at least a title. if "title" in audio: track.title = audio["title"][0] else: #attempt to handle unicode filenames. enc = utils.guess_str_encoding(filename)[0] track.title = filename.decode(enc).split(r'/')[-1] #TODO refactor if "album" in audio: track.album = audio["album"][0] if "artist" in audio: track.artist = audio["artist"][0] if "composer" in audio: track.composer = audio["composer"][0] #albumartist is 'performer' according to this guy: # https://github.com/plexinc-plugins/Scanners.bundle/commit/95cc0b9eeb7fa8fa77c36ffcf0ec51644a927700 if "performer" in audio: track.albumArtist = audio["performer"][0] if "genre" in audio: track.genre = audio["genre"][0] if "date" in audio: track.year = int(audio["date"][0].split("-")[0]) #this looks like an assumption if "bpm" in audio: track.beatsPerMinute = int(audio["bpm"][0]) #think these are assumptions: if "tracknumber" in audio: tracknumber = audio["tracknumber"][0].split("/") track.track = int(tracknumber[0]) if len(tracknumber) == 2 and tracknumber[1]: track.totalTracks = int(tracknumber[1]) if "discnumber" in audio: discnumber = audio["discnumber"][0].split("/") track.disc = int(discnumber[0]) if len(discnumber) == 2 and discnumber[1]: track.totalDiscs = int(discnumber[1]) return (metadata, filemap)
# -*- coding: utf-8 -*- """Utilities used in testing.""" import numbers import unittest import random import inspect from getpass import getpass import re from gmusicapi.api import Api from gmusicapi.exceptions import CallFailure, NotLoggedIn from gmusicapi.protocol.metadata import md_expectations from gmusicapi.utils.apilogging import LogController log = LogController.get_logger("utils") #A regex for the gm id format, eg: #c293dd5a-9aa9-33c4-8b09-0c865b56ce46 hex_set = "[0-9a-f]" gm_id_regex = re.compile( ("{h}{{8}}-" + ("{h}{{4}}-" * 3) + "{h}{{12}}").format(h=hex_set)) def init(): """Makes an instance of the unit-tested api and attempts to login with it. Returns the authenticated api. """ api = UnitTestedApi()
"""Utilities used in testing.""" import numbers import unittest import random import inspect from getpass import getpass import re from gmusicapi.api import Api from gmusicapi.exceptions import CallFailure, NotLoggedIn from gmusicapi.protocol.metadata import md_expectations from gmusicapi.utils.apilogging import LogController log = LogController.get_logger("utils") #A regex for the gm id format, eg: #c293dd5a-9aa9-33c4-8b09-0c865b56ce46 hex_set = "[0-9a-f]" gm_id_regex = re.compile(("{h}{{8}}-" + ("{h}{{4}}-" * 3) + "{h}{{12}}").format(h=hex_set)) def init(): """Makes an instance of the unit-tested api and attempts to login with it. Returns the authenticated api. """ api = UnitTestedApi()