コード例 #1
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
コード例 #2
0
    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)
コード例 #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