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
Example #3
0
    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