def fill_track_info(cls, filepath): """Given the path and contents of a track, return a filled locker_pb2.Track. On problems, raise ValueError.""" track = locker_pb2.Track() track.client_id = cls.get_track_clientid(filepath) extension = os.path.splitext(filepath)[1].upper() if extension: #Trim leading period if it exists (ie extension not empty). extension = extension[1:] if extension.upper() == 'M4B': #M4B are supported by the music manager, and transcoded like normal. extension = 'M4A' if not hasattr(locker_pb2.Track, extension): raise ValueError("unsupported filetype") track.original_content_type = getattr(locker_pb2.Track, extension) track.estimated_size = os.path.getsize(filepath) track.last_modified_timestamp = int(os.path.getmtime(filepath)) #These are typically zeroed in my examples. track.play_count = 0 track.client_date_added = 0 track.recent_timestamp = 0 track.rating = locker_pb2.Track.NOT_RATED # star rating #Populate information about the encoding. audio = mutagen.File(filepath, easy=True) if audio is None: raise ValueError("could not open to read metadata") elif isinstance(audio, mutagen.asf.ASF): #WMA entries store more info than just the value. #Monkeypatch in a dict {key: value} to keep interface the same for all filetypes. asf_dict = dict((k, [ve.value for ve in v]) for (k, v) in audio.tags.as_dict().items()) audio.tags = asf_dict track.duration_millis = int(audio.info.length * 1000) try: bitrate = int(audio.info.bitrate / 1000) except AttributeError: #mutagen doesn't provide bitrate for some lossless formats (eg FLAC), so # provide an estimation instead. This shouldn't matter too much; # the bitrate will always be > 320, which is the highest scan and match quality. bitrate = (track.estimated_size * 8) / track.duration_millis track.original_bit_rate = bitrate #Populate metadata. def track_set(field_name, val, msg=track): """Returns result of utils.pb_set and logs on failures. Should be used when setting directly from metadata.""" success = utils.pb_set(msg, field_name, val) if not success: log.info("could not pb_set track.%s = %r for '%s'", field_name, val, filepath) return success #Title is required. #If it's not in the metadata, the filename will be used. if "title" in audio: title = audio['title'][0] if isinstance(title, mutagen.asf.ASFUnicodeAttribute): title = title.value track_set('title', title) else: #Assume ascii or unicode. track.title = os.path.basename(filepath) if "date" in audio: date_val = str(audio['date'][0]) try: datetime = dateutil.parser.parse(date_val, fuzzy=True) except ValueError as e: log.warning("could not parse date md for '%s': (%s)", filepath, e) else: track_set('year', datetime.year) #Mass-populate the rest of the simple fields. #Merge shared and unshared fields into {mutagen: Track}. fields = dict( dict((shared, shared) for shared in cls.shared_fields).items() + cls.field_map.items() ) for mutagen_f, track_f in fields.items(): if mutagen_f in audio: track_set(track_f, audio[mutagen_f][0]) for mutagen_f, (track_f, track_total_f) in cls.count_fields.items(): if mutagen_f in audio: numstrs = str(audio[mutagen_f][0]).split("/") track_set(track_f, numstrs[0]) if len(numstrs) == 2 and numstrs[1]: track_set(track_total_f, numstrs[1]) return track
def fill_track_info(cls, filepath): """Given the path and contents of a track, return a filled locker_pb2.Track. On problems, raise ValueError.""" track = locker_pb2.Track() # The track protobuf message supports an additional metadata list field. # ALBUM_ART_HASH has been observed being sent in this field so far. # Append locker_pb2.AdditionalMetadata objects to additional_metadata. # AdditionalMetadata objects consist of two fields, 'tag_name' and 'value'. additional_metadata = [] track.client_id = cls.get_track_clientid(filepath) audio = mutagen.File(filepath, easy=True) if audio is None: raise ValueError("could not open to read metadata") elif isinstance(audio, mutagen.asf.ASF): # WMA entries store more info than just the value. # Monkeypatch in a dict {key: value} to keep interface the same for all filetypes. asf_dict = dict((k, [ve.value for ve in v]) for (k, v) in audio.tags.as_dict().items()) audio.tags = asf_dict extension = os.path.splitext(filepath)[1].upper() if isinstance(extension, bytes): extension = extension.decode('utf8') if extension: # Trim leading period if it exists (ie extension not empty). extension = extension[1:] if isinstance(audio, mutagen.mp4.MP4) and ( audio.info.codec == 'alac' or audio.info.codec_description == 'ALAC'): extension = 'ALAC' elif isinstance(audio, mutagen.mp4.MP4 ) and audio.info.codec_description.startswith('AAC'): extension = 'AAC' if extension.upper() == 'M4B': # M4B are supported by the music manager, and transcoded like normal. extension = 'M4A' if not hasattr(locker_pb2.Track, extension): raise ValueError("unsupported filetype: {0} for file {1}".format( extension, filepath)) track.original_content_type = getattr(locker_pb2.Track, extension) track.estimated_size = os.path.getsize(filepath) track.last_modified_timestamp = int(os.path.getmtime(filepath)) # These are typically zeroed in my examples. track.play_count = 0 track.client_date_added = 0 track.recent_timestamp = 0 track.rating = locker_pb2.Track.NOT_RATED # star rating track.duration_millis = int(audio.info.length * 1000) try: bitrate = audio.info.bitrate // 1000 except AttributeError: # mutagen doesn't provide bitrate for some lossless formats (eg FLAC), so # provide an estimation instead. This shouldn't matter too much; # the bitrate will always be > 320, which is the highest scan and match quality. bitrate = (track.estimated_size * 8) // track.duration_millis track.original_bit_rate = bitrate # Populate metadata. def track_set(field_name, val, msg=track): """Returns result of utils.pb_set and logs on failures. Should be used when setting directly from metadata.""" success = utils.pb_set(msg, field_name, val) if not success: log.info("could not pb_set track.%s = %r for '%r'", field_name, val, filepath) return success # Title is required. # If it's not in the metadata, the filename will be used. if "title" in audio: title = audio['title'][0] if isinstance(title, mutagen.asf.ASFUnicodeAttribute): title = title.value track_set('title', title) else: # Assume ascii or unicode. track.title = os.path.basename(filepath) if "date" in audio: date_val = str(audio['date'][0]) try: datetime = dateutil.parser.parse(date_val, fuzzy=True) except (ValueError, TypeError) as e: # TypeError provides compatibility with: # https://bugs.launchpad.net/dateutil/+bug/1247643 log.warning("could not parse date md for '%r': (%s)", filepath, e) else: track_set('year', datetime.year) for null_field in ['artist', 'album']: # If these fields aren't provided, they'll render as "undefined" in the web interface; # see https://github.com/simon-weber/gmusicapi/issues/236. # Defaulting them to an empty string fixes this. if null_field not in audio: track_set(null_field, '') # Mass-populate the rest of the simple fields. # Merge shared and unshared fields into {mutagen: Track}. fields = dict( itertools.chain(((shared, shared) for shared in cls.shared_fields), cls.field_map.items())) for mutagen_f, track_f in fields.items(): if mutagen_f in audio: track_set(track_f, audio[mutagen_f][0]) for mutagen_f, (track_f, track_total_f) in cls.count_fields.items(): if mutagen_f in audio: numstrs = str(audio[mutagen_f][0]).split("/") track_set(track_f, numstrs[0]) if len(numstrs) == 2 and numstrs[1]: track_set(track_total_f, numstrs[1]) if additional_metadata: track.track_extras.additional_metadata.extend(additional_metadata) return track
def fill_track_info(cls, filepath, file_contents): """Given the path and contents of a track, return a filled locker_pb2.Track. On problems, raise ValueError.""" track = locker_pb2.Track() track.client_id = cls.get_track_clientid(file_contents) extension = os.path.splitext(filepath)[1].upper() if extension: #Trim leading period if it exists (ie extension not empty). extension = extension[1:] if not hasattr(locker_pb2.Track, extension): raise ValueError("unsupported filetype") track.original_content_type = getattr(locker_pb2.Track, extension) track.estimated_size = os.path.getsize(filepath) track.last_modified_timestamp = int(os.path.getmtime(filepath)) #These are typically zeroed in my examples. track.play_count = 0 track.client_date_added = 0 track.recent_timestamp = 0 track.rating = locker_pb2.Track.NOT_RATED # star rating #Populate information about the encoding. audio = mutagen.File(filepath, easy=True) if audio is None: raise ValueError("could not open to read metadata") elif isinstance(audio, mutagen.asf.ASF): #WMA entries store more info than just the value. #Monkeypatch in a dict {key: value} to keep interface the same for all filetypes. asf_dict = { k: [ve.value for ve in v] for (k, v) in audio.tags.as_dict().items() } audio.tags = asf_dict track.duration_millis = int(audio.info.length * 1000) try: bitrate = int(audio.info.bitrate / 1000) except AttributeError: #mutagen doesn't provide bitrate for some lossless formats (eg FLAC), so # provide an estimation instead. This shouldn't matter too much; # the bitrate will always be > 320, which is the highest scan and match quality. bitrate = (track.estimated_size * 8) / track.duration_millis track.original_bit_rate = bitrate #Populate metadata. #Title is required. #If it's not in the metadata, the filename will be used. if "title" in audio: title = audio['title'][0] if isinstance(title, mutagen.asf.ASFUnicodeAttribute): title = title.value track.title = title else: #handle non-ascii path encodings. if not isinstance(filepath, unicode): #not sure if this is possible enc = utils.guess_str_encoding(filepath)[0] filepath = filepath.decode(enc) track.title = os.path.basename(filepath) if "date" in audio: try: track.year = int(audio["date"][0].split("-")[0]) except ValueError: #TODO log pass #Mass-populate the rest of the simple fields. #Merge shared and unshared fields into {mutagen: Track}. fields = dict({shared: shared for shared in cls.shared_fields}.items() + cls.field_map.items()) for mutagen_f, track_f in fields.items(): if mutagen_f in audio: setattr(track, track_f, audio[mutagen_f][0]) for mutagen_f, (track_f, track_total_f) in cls.count_fields.items(): if mutagen_f in audio: numstrs = audio[mutagen_f][0].split("/") setattr(track, track_f, int(numstrs[0])) if len(numstrs) == 2 and numstrs[1]: setattr(track, track_total_f, int(numstrs[1])) return track