예제 #1
0
    def _copy_music_changed(self):
        for x in self.music_changed:
            ext_left = self.music_left[x]['ext']
            full_path_left = os.path.join(self.base_path_left, f'{x}{ext_left}')
            if ext_left.lower() == '.flac':
                music_thing_left = flac.FLAC(full_path_left)
                tags_left = music_thing_left.tags.as_dict()
            elif ext_left.lower() == '.mp3':
                music_thing_left = mp3.MP3(full_path_left)
                tags_left = id3_tags_as_dict(music_thing_left.tags)
            else:
                raise Exception('What is this thing?')

            ext_right = self.music_right[x]['ext']
            assert ext_right == '.mp3', 'Target file is not .mp3'
            full_path_right = os.path.join(self.base_path_right, f'{x}{ext_right}')
            music_thing_right = mp3.MP3(full_path_right)
            tags_right = id3_tags_as_dict(music_thing_right.tags)

            if tags_left != tags_right:
                print_if_not_silent(f'copying tags {full_path_left}')
                copy_tag_dict_to_mp3(music_thing_right, tags_left)

            else:
                if ext_left.lower() == '.flac':
                    print_if_not_silent(f'encoding {full_path_left} to mp3')
                    flac_to_mp3(full_path_left, full_path_right, tag_dict=tags_left)
                else:
                    print_if_not_silent(f'copying {full_path_left} to right')
                    shutil.copy2(full_path_left, full_path_right)
예제 #2
0
 def load_from_filepath(self, filepath: str):
     self.filepath = filepath
     self._filesize = os.stat(filepath).st_size
     file = File(filepath)
     mime = file.mime
     tags: Optional[TagsInfo] = None
     info: InfoBlock = InfoBlock()
     if 'audio/mp3' in mime:
         mp3_data = mp3.EasyMP3(filepath)
         tags = cast(TagsInfo, mp3_data)
         info = cast(InfoBlock, mp3_data.info)
     elif 'audio/flac' in mime:
         flac_data = flac.FLAC(filepath)
         tags = cast(TagsInfo, flac_data)
         info = cast(InfoBlock, flac_data.info)
     else:
         raise TypeError
     self._bitrate = info.bitrate
     tag_name = None
     try:
         tag_name = 'title'
         self._tags.titles = tags[tag_name]
         tag_name = 'album'
         self._tags.albums = tags[tag_name]
         self.title = self._tags.titles[0]
     except KeyError as err:
         self._logger.warning('Not found tag "{0}" in file "{1}"'.format(
             tag_name, filepath))
         self._logger.debug(err)
     self.is_found = True
     self._filename = os.path.basename(filepath)
예제 #3
0
def processflac(filename):
    '''Reads metadata from FLAC files'''

    f = flac.FLAC(filename)
    title, anime, role, rolequal = part(f.get('title', _blank)[0])
    label = f.get('description', _blank)[0]
    if not label:
        label = f.get('subtitle', _blank)[0]
    album = f.get('album', _blank)[0]
    artist = f.get('artist', _blank)[0]
    fn_artist = artist if artist else 'Unknown Artist'
    id = random_id()

    return [
        metadata(
            ID=id,
            OriginalFileName=filename,
            Filename=canonicalFileName(id, artist, title, 'flac'),
            Tracktitle=title,
            Album=album,
            Length=int(f.info.length * 1000),
            Anime=anime,
            Role=role,
            Rolequalifier=rolequal,
            Artist=artist,
            Composer=f.get('composer', _blank)[0],
            Label=label,
            InMyriad="NO",
            Dateadded=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M"))
    ]
예제 #4
0
def identify_filetype(file):
    """Identify the given file as either MP3 or FLAC and return a Mutagen object."""
    if file.endswith('.mp3'):
        audio_file = mp3.MP3(file, ID3=easyid3.EasyID3)

    elif file.endswith('.flac'):
        audio_file = flac.FLAC(file)

    return audio_file
예제 #5
0
def tag_scanner(music_path):
    if music_path.suffix == ".aiff" or music_path.suffix == ".aif":
        return aiff.AIFF(music_path)
    elif music_path.suffix == ".flac":
        return flac.FLAC(music_path)
    elif music_path.suffix == ".mp3":
        return easyid3.EasyID3(music_path)
    else:
        print("Invalid FileType")
예제 #6
0
def acquireFlacInfo(src):
    mu = flac.FLAC(src)
    trackNum = mu.get("TRCK").text[0]  # 得到音轨号
    performer = mu.get("TPE1").text[0]  # 得到表演者
    songName = songTitleFormat(mu.get("TIT2").text[0])  # 得到歌曲名称
    albumName = mu.get("TALB").text[0]
    if int(trackNum) < 10:
        trackNum = '0' + trackNum
    # print(trackNum, performer, songName, albumName)
    return trackNum, performer, songName, albumName
예제 #7
0
    def flac_art(self, flac_file):

        song = mflac.FLAC(flac_file)

        pic = mflac.Picture()
        pic.data = self.art_data
        pic.type = 3
        pic.mime = 'image/jpeg'

        song.add_picture(pic)
        song.save()
예제 #8
0
def tags_flac(name, infer=True):
    try:
        tags = flac.FLAC(name)
    except (mutagen.flac.FLACNoHeaderError, mutagen.flac.error):
        if infer:
            uprint(
                'WARN: Corrupted or mistagged, inferring artist and album: %s ...'
                % name,
                end=' ')
            return infer_album_artist(name)
        return None
    if not tags:
        return None, None
    return tags['albumartist'][0] if 'albumartist' in tags.keys() else tags['artist'][0] if 'artist' in tags.keys() \
        else None, tags['album'][0] if 'album' in tags.keys() else None
예제 #9
0
def extract(path):
    if open(path, 'rb').read(4) == binascii.a2b_hex('664C6143'):
        audio = flac.FLAC(path)
        identifier = audio['description']
    else:
        audio = mp3.MP3(path)
        identifier = [
            text for item in audio.tags.getall('COMM') for text in item.text
        ]
    identifier = max(identifier, key=len)

    identifier = base64.b64decode(identifier[22:])
    cryptor = AES.new(key, AES.MODE_ECB)
    meta = unpad(cryptor.decrypt(identifier), 16).decode('utf8')
    return json.loads(meta[6:])
예제 #10
0
    async def detecter(self, songdir):
        #リストアップ
        files = os.listdir(songdir)
        files_file = [
            f for f in files if os.path.isfile(os.path.join(songdir, f))
        ]
        #osuファイル検出
        osu_detect = [l for l in files_file if l.endswith('.osu')]
        osu_osup = os.path.join(songdir, osu_detect[0])
        osu_osuo = open(osu_osup, encoding='utf_8')
        osu_raw = osu_osuo.readlines()
        osu_osuo.close()
        AFn = None
        Tit = None
        UTit = None
        dosz_id = None

        for line in osu_raw:
            line = line.replace('\n', '')
            if line.startswith("AudioFilename:"):
                AFn = line[15:]
                continue
            elif line.startswith("Title:"):
                Tit = line[6:]
                continue
            elif line.startswith("TitleUnicode:"):
                UTit = line[13:]
                break
            elif line.startswith("[Difficulty]"):
                break

        if UTit:
            Tit = UTit
        AudioFname = os.path.join(songdir, AFn)
        if AudioFname.endswith(".mp3") or AudioFname.endswith(".MP3"):
            af = MP3(AudioFname)
        elif AudioFname.endswith(".ogg") or AudioFname.endswith(".OGG"):
            af = oggvorbis.OggVorbis(AudioFname)
        elif AudioFname.endswith(".m4a") or AudioFname.endswith(".M4A"):
            af = MP4(AudioFname)
        elif AudioFname.endswith(".flac") or AudioFname.endswith(".FLAC"):
            af = flac.FLAC(AudioFname)
        duration = int(af.info.length) + 1
        dosz_idd = os.path.dirname(songdir)
        dosz_ids = songdir.replace(dosz_idd, '').split(' ')[0]
        dosz_id = dosz_ids.replace('\\', '')
        print("検出ID:{}".format(dosz_id))
        return Tit, AudioFname, duration, dosz_id
예제 #11
0
def check(file,parent_folder_path):
    
    fl = os.path.join(parent_folder_path, file)
    
    if file.endswith('.flac'):
        f = flac.FLAC(fl).info
        rate = f.sample_rate
    else:
        f = wave.open(fl)
        rate = f.getframerate()
        
    print(rate)
    if(16000 <= rate <= 48000):
        return '🙂'
    else:
        return '�'
예제 #12
0
def flac_to_mp3(input_path, output_path, tag_dict=None):
    """
    Converts flac to mp3 and copies tags.

    :param input_path: str.
    :param output_path: str.
    :param tag_dict: flac_like dict {'tag_name': ['tag_value', ...]}
    """
    flac_options = '-d -s --force-raw-format --endian=little --sign=signed -c'
    lame_options = '-r --quiet --add-id3v2 --noreplaygain'

    cp1 = subprocess.run(f'"{flac_prog}" {flac_options} "{input_path}"', capture_output=True)
    subprocess.run(f'"{lame_prog}" -{lame_quality} {lame_options} "-" "{output_path}"',
                   input=cp1.stdout, capture_output=False)

    mp3_thing = mp3.MP3(output_path)
    if not tag_dict:
        tag_dict = flac.FLAC(input_path).tags.as_dict()
    copy_tag_dict_to_mp3(mp3_thing, tag_dict)
 def __init__(self, filename):
     from mutagen import flac
     self.obj = flac.FLAC(filename)
     self.map = {
         'desc': 'desc',
         'description': 'desc',
         'album': 'album',
         'comment': 'comment',
         'artist': 'artist',
         'title': 'title',
         'tracknumber': 'tracknumber',
         'discnumber': 'discnumber',
         'albumartist': 'albumartist',
         'composer': 'composer',
         'disccount': 'disccount',
         'tracktotal': 'tracktotal',
         'date': 'date',
         'genre': 'genre',
         'website': 'www'}
예제 #14
0
    def metadata(self):
        #dictionary
        track_data = {}

        # default name
        track_data['title'] = self.path[self.path.rfind('/') +
                                        1:self.path.rfind('.')]
        track_data['album'] = None
        track_data['genre'] = None
        track_data['artist'] = None

        #detect the current file type
        file_type = self.path[self.path.rfind('.'):]

        # handle FLAC files
        if file_type == ".flac":
            metadata = flac.FLAC(self.path)
            # print metadata
            try:
                track_data['title'] = metadata['title'][0]
                track_data['album'] = metadata['album'][0]
                track_data['genre'] = metadata['genre'][0]
                track_data['artist'] = metadata['artist'][0]
            except:
                pass
        #handle MP3 files
        if file_type == ".mp3":
            metadata = mp3.MP3(self.path)
            # print metadata
            try:
                if (metadata.has_key('TIT2')):
                    track_data['title'] = metadata['TIT2'].text[0]
                if (metadata.has_key('TALB')):
                    track_data['album'] = metadata['TALB'].text[0]
                if (metadata.has_key('TCON')):
                    track_data['genre'] = metadata['TCON'].text[0]
                if (metadata.has_key('TPE2')):
                    track_data['artist'] = metadata['TPE2'].text[0]
            except:
                pass

        return track_data
예제 #15
0
    def flacinfo(self):
        """ FLACの曲情報を取得 """
        self.tags = flac.FLAC(self.filepath)

        # 各項目取得
        # キーが存在しなかった場合: 半角空白に置き換え
        self.title = self.tags.get('TITLE', ' ')[0].strip()
        self.album = self.tags.get('ALBUM', ' ')[0].strip()
        self.artist = self.tags.get('ARTIST', ' ')[0].strip()
        self.genre = self.tags.get('GENRE', ' ')[0].strip()
        self.comment = self.tags.get('COMMENT', ' ')[0].strip()

        artworks = self.tags.pictures
        artwork = None
        for artwork in artworks:    # 抽出(最後に登録されている画像のみ)
            pass
        if artwork:     # アートワーク画像が存在するか
            self.artwork = artwork.data  # type: bytes
        else:
            self.artwork = None
예제 #16
0
    def flac_tag(self):

        song = mflac.FLAC(self.audio_file)

        info_dict = {
            'album': 'ALBUM',
            'album_artist': 'ALBUMARTIST',
            'artist': 'ARTIST',
            'date': 'DATE',
            'label': 'LABEL',
            'genre': 'GENRE',
            'title': 'TITLE',
            'track_number': 'TRACKNUMBER',
            'total_tracks': 'TOTALTRACKS',
            'disc_number': 'DISCNUMBER',
            'total_discs': 'TOTALDISCS'
        }

        metadata_dictionary = self.create_tag_dict(song, info_dict)

        return metadata_dictionary
예제 #17
0
    def flac_refresh(self, track_path: str) -> bool:

        try:
            audio = flac.FLAC(track_path)
            tag_dict = {}

            for key, value in audio.tags:
                tag_dict[key.lower()] = value

            artist, title = tag_dict['artist'], tag_dict['title']
            title = title_prettify(title)

            lyrics = self.get_lyrics(artist, title)
            if lyrics:
                audio.tags['Lyrics'] = lyrics
                audio.save()
            return True

        except Exception as e:
            logging.warning(f"EXCEPTION at {track_path}: {e}")
            return False
예제 #18
0
def insert_flac(abs_audio_f):
    cover = cut_suffix(abs_audio_f) + '.jpg'
    mime = u"image/jpeg"
    if not os.path.exists(cover):
        cover = cut_suffix(abs_audio_f) + '.png'
        mime = u"image/png"
        if not os.path.exists(cover):
            # print('Cover for [' + abs_audio_f + '] not found.')
            return
    audio = flac.FLAC(abs_audio_f)
    audio.clear_pictures()
    with open(cover, 'rb') as img_f:
        pic = flac.Picture()
        pic.data = img_f.read()
        pic.type = id3.PictureType.COVER_FRONT
        pic.mime = mime
        # pic.width = 500
        # pic.height = 500
        audio.add_picture(pic)
        try:
            audio.save()
        except MutagenError:
            MutagenErrors.append(abs_audio_f)
예제 #19
0
    def vorbis_tag(self, a_file):
        """ This function takes a metadata dictionary and uses Mutagen to
        apply it to a FLAC or OGG Vorbis file.
        """

        title, extension = os.path.splitext(a_file)

        if extension == '.flac':
            song = mflac.FLAC(a_file)
        elif extension == '.ogg':
            song = mogg.OggVorbis(a_file)
        else:
            print("%s format not permitted by flac_paste" % extension)

        # Remove any pre-existing tags
        song.delete()

        info_dict = self.info_dict
        transfer_dictionary = {
            'ALBUM': info_dict['album'],
            'ALBUMARTIST': info_dict['album_artist'],
            'ARTIST': info_dict['artist'],
            'DATE': info_dict['date'][:4],
            'LABEL': info_dict['label'],
            'GENRE': info_dict['genre'],
            'TITLE': self.track_title,
            'TRACKNUMBER': self.track_number.zfill(2),
            'TOTALTRACKS': str(info_dict['total_tracks']).zfill(2),
            'DISCNUMBER': info_dict['disc'],
            'TOTALDISCS': info_dict['total_discs']
        }

        for tag in transfer_dictionary:
            song[tag] = transfer_dictionary[tag]

        song.save()
예제 #20
0
def mark(path, song, id=None):
    def streamify(file):
        with file:
            return file.read()

    def embed(item, content, type):
        item.encoding = 0
        item.type = type
        item.mime = 'image/png' if content[0:4] == binascii.a2b_hex(
            '89504E47') else 'image/jpeg'
        item.data = content

    format = 'flac' if open(
        path, 'rb').read(4) == binascii.a2b_hex('664C6143') else 'mp3'
    audio = mp3.EasyMP3(path) if format == 'mp3' else flac.FLAC(path)

    meta = {
        'album':
        song['album']['name'],
        'albumId':
        song['album']['id'],
        'albumPic':
        song['album']['picUrl'],
        'albumPicDocId':
        str(song['album']['pic'] if 'pic' in song['album'] else re.
            search(r'/(\d+)\.\w+$', song['album']['picUrl']).group(1)),
        'alias':
        song['alias'] if 'alias' in song else [],
        'artist':
        [[artist['name'], artist['id']] for artist in song['artists']],
        'musicId':
        id if id else song['id'],
        'musicName':
        song['name'] if 'name' in song else audio['title'][0],
        'mvId':
        song['mvid'] if 'mvid' in song else 0,
        'transNames': [],
        'format':
        format,
        'bitrate':
        audio.info.bitrate,
        'duration':
        int(audio.info.length * 1000),
        'mp3DocId':
        hashlib.md5(streamify(open(path, 'rb'))).hexdigest()
    }

    cryptor = AES.new(key, AES.MODE_ECB)
    identifier = 'music:' + json.dumps(meta)
    identifier = '163 key(Don\'t modify):' + base64.b64encode(
        cryptor.encrypt(pad(identifier.encode('utf8'), 16))).decode('utf8')

    audio.delete()
    audio['title'] = meta['musicName']
    audio['album'] = meta['album']
    audio['artist'] = '/'.join([artist[0] for artist in meta['artist']])

    if format == 'flac':
        audio['description'] = identifier
    else:
        audio.tags.RegisterTextKey('comment', 'COMM')
        audio['comment'] = identifier
    audio.save()

    data = requests.get(meta['albumPic'] + '?param=300y300').content
    if format == 'flac':
        audio = flac.FLAC(path)
        image = flac.Picture()
        embed(image, data, 3)
        audio.clear_pictures()
        audio.add_picture(image)
    elif format == 'mp3':
        audio = mp3.MP3(path)
        image = id3.APIC()
        embed(image, data, 6)
        audio.tags.add(image)
    audio.save()
예제 #21
0
    def __update_tag(self, download_dir, audio_file, image_file,
                     song_title=None, album_title=None, album_artist=None, album_composer=None,
                     track_number=-1, process_index=-1, process_total=-1):
        """
        The function that update audio metadata for each song.

        :param str download_dir: Download directory
        :param str audio_file: Path to audio file
        :param str image_file: Path to image file
        :param str song_title: Song title
        :param str album_title: Album title to be saved in metadata
        :param str album_artist: Album artist to be saved in metadata
        :param str album_composer: Album composer to be saved in metadata
        :param int track_number: track number to be saved in metadata
        :param int process_index: Current process index displayed in log message
        :param int process_total: Total number of process displayed in log message
        """

        if audio_file is None:
            logger.warning('[Process:{}/{}][Track:{}] Could not update metadata because there is no data found on the playlist. The video may be private or deleted.'.format(process_index, process_total, track_number))
            return

        if process_index > 0 and process_total > 0:

            if track_number > 0:
                log_prefix = '[Process:{}/{}][Track:{}]'.format(process_index, process_total, track_number)

            else:
                log_prefix = '[Process:{}/{}]'.format(process_index, process_total)

        else:
            log_prefix = ''

        audio_filename = os.path.basename(audio_file)

        try:
            # Validate audio data
            if not os.path.isfile(audio_file):
                raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), audio_file)

            audio_mime_type = mimetypes.guess_type(audio_file)

            if contains_at_least(audio_mime_type, ['audio/x-mp4', 'audio/x-m4a', 'audio/mp4a-latm']):

                # For more info about mp4 tag is available at
                # https://github.com/quodlibet/mutagen/blob/cf399dc58940fb1356f672809d763be9e2af0033/mutagen/mp4/__init__.py
                # http://atomicparsley.sourceforge.net/mpeg-4files.html
                mp4_data = mp4.MP4(audio_file)
                # Track Number
                if not self.no_track_number and track_number > 0:
                    mp4_data['trkn'] = [(track_number, 0)]
                # Cover image
                if not self.no_artwork:
                    image_data = self.__get_tag_image(image_file, audio_mime_type)
                    if image_data:
                        mp4_data['covr'] = [image_data]
                # Album title
                if not self.no_album_title and album_title is not None:
                    mp4_data['\xa9alb'] = album_title
                # Album artist
                if not self.no_album_artist and album_artist is not None:
                    mp4_data['aART'] = album_artist
                # Composer
                if not self.no_composer and album_composer is not None:
                    mp4_data['\xa9wrt'] = album_composer
                # Part of compilation
                if not self.no_compilation:
                    mp4_data['cpil'] = True
                # Save
                mp4_data.save()

            elif contains_at_least(audio_mime_type, ['audio/x-mp3', 'audio/mpeg']):

                # For more info about ID3v2 tag is available at
                # https://github.com/quodlibet/mutagen/blob/4a5d7d17f1a611280cc52d229aa70b77ca3c55dd/mutagen/id3/_frames.py
                # https://help.mp3tag.de/main_tags.html
                mp3_data = id3.ID3(audio_file)
                # Cover image
                if not self.no_artwork:
                    image_data = self.__get_tag_image(image_file, audio_mime_type)
                    if image_data:
                        mp3_data['APIC'] = image_data
                # Track number
                if not self.no_track_number and track_number > 0:
                    mp3_data.add(id3.TRCK(encoding=3, text=['{}/{}'.format(track_number, 0)]))
                # Album title
                if not self.no_album_title and album_title is not None:
                    mp3_data["TALB"] = id3.TALB(encoding=0, text=album_title)
                # Album artist
                if not self.no_album_artist and album_artist is not None:
                    mp3_data["TPE2"] = id3.TPE2(encoding=0, text=album_artist)
                # Composer
                if not self.no_composer and album_composer is not None:
                    mp3_data["TCOM"] = id3.TCOM(encoding=0, text=album_composer)
                # Part of compilation
                if not self.no_compilation:
                    mp3_data['TCMP'] = id3.TCMP(encoding=0, text=['1'])
                # Save
                mp3_data.save()

            elif contains_at_least(audio_mime_type, ['audio/x-aac']):

                # TODO: Add AAC support
                pass
                # image_data = __get_tag_image(image_file, audio_mime_type)
                # aac_data = aac.AAC(audio_file)
                # if not self.no_track_number:
                #     if track_number > 0 and track_total > 0:
                #         aac_data.add_tags(id3.TRCK(encoding=3, text=['{}/{}'.format(track_number, track_total)]))
                #         # mp3_data['TRCK'] = id3.TRCK(encoding=3, text=[str(track_number)])
                # if image_data:
                #     mp3_data['APIC'] = image_data
                #     aac_data.save()

            elif contains_at_least(audio_mime_type, ['audio/x-flac']):

                # https://github.com/quodlibet/mutagen/blob/a1db79ece62c4e86259f15825e360d1ce0986a22/mutagen/flac.py
                # https://github.com/quodlibet/mutagen/blob/4a5d7d17f1a611280cc52d229aa70b77ca3c55dd/tests/test_flac.py

                flac_data = flac.FLAC(audio_file)
                # Artwork
                if not self.no_artwork:
                    image_data = self.__get_tag_image(image_file, audio_mime_type)
                    if image_data:
                        flac_data.add_picture(image_data)
                # Save
                flac_data.save()

                flac_data = File(audio_file)
                # Track number
                if not self.no_track_number and track_number > 0:
                    flac_data.tags['tracknumber'] = str(track_number)
                # Album title
                if not self.no_album_title and album_title is not None:
                    flac_data.tags['album'] = album_title
                # Album artist
                if not self.no_album_artist and album_artist is not None:
                    flac_data.tags['albumartist'] = album_artist
                # Composer
                if not self.no_composer and album_composer is not None:
                    flac_data.tags['composer'] = album_composer
                # Part of compilation
                if not self.no_compilation:
                    pass
                # Save
                flac_data.save()
                # audio = File(audio_file, easy=True)

            else:
                raise InvalidMimeTypeException("Invalid audio format.", audio_mime_type)

            # Remove artwork if succeeded
            if os.path.exists(image_file):
                os.remove(image_file)

            # Rename filename from id to title
            dest_audio_file = os.path.join(download_dir, '{}.{}'.format(song_title, self.audio_codec))
            os.rename(audio_file, dest_audio_file)

            dest_audio_filename = os.path.basename(dest_audio_file)
            logger.info('{}[File:{}] Updated.'.format(log_prefix, dest_audio_filename))

        except FileNotFoundError:
            message = 'File not found. Skipped.'
            logger.warning('{}[File:{}] {}'.format(log_prefix, audio_filename, message))

        except InvalidDataException as e:
            message = e.message + ' Skipped.'
            logger.warning('{}[File:{}] {}'.format(log_prefix, audio_filename, message))

        except InvalidMimeTypeException as e:
            message = e.message + ' Skipped.'
            logger.warning('{}[File:{}] {}'.format(log_prefix, audio_filename, message))

        except Exception as e:
            message = 'Error {}: {} Skipped.'.format(type(e), str(e))
            logger.error('{}[File:{}] {}'.format(log_prefix, audio_filename, message))
예제 #22
0
def dump(input_path, output_path=None, skip=True):

    output_path = (lambda path, meta: os.path.splitext(path)[0] + '.' + meta[
        'format']) if not output_path else output_path
    output_path = (
        lambda path, meta: path) if not callable(output_path) else output_path

    core_key = binascii.a2b_hex('687A4852416D736F356B496E62617857')
    meta_key = binascii.a2b_hex('2331346C6A6B5F215C5D2630553C2728')
    unpad = lambda s: s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))]

    f = open(input_path, 'rb')

    # magic header
    header = f.read(8)
    assert binascii.b2a_hex(header) == b'4354454e4644414d'

    # key data
    f.seek(2, 1)
    key_length = f.read(4)
    key_length = struct.unpack('<I', bytes(key_length))[0]

    key_data = bytearray(f.read(key_length))
    key_data = bytes(bytearray([byte ^ 0x64 for byte in key_data]))

    cryptor = AES.new(core_key, AES.MODE_ECB)
    key_data = unpad(cryptor.decrypt(key_data))[17:]
    key_length = len(key_data)

    # S-box (standard RC4 Key-scheduling algorithm)
    key = bytearray(key_data)
    S = bytearray(range(256))
    j = 0

    for i in range(256):
        j = (j + S[i] + key[i % key_length]) & 0xFF
        S[i], S[j] = S[j], S[i]

    # meta data
    meta_length = f.read(4)
    meta_length = struct.unpack('<I', bytes(meta_length))[0]

    if meta_length:
        meta_data = bytearray(f.read(meta_length))
        meta_data = bytes(bytearray([byte ^ 0x63 for byte in meta_data]))
        identification = meta_data.decode('utf-8')
        meta_data = base64.b64decode(meta_data[22:])

        cryptor = AES.new(meta_key, AES.MODE_ECB)
        meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:]
        meta_data = json.loads(meta_data)
    else:
        meta_data = {
            'format':
            'flac' if os.fstat(f.fileno()).st_size > 1024**2 * 16 else 'mp3'
        }

    # crc32
    crc32 = f.read(4)
    crc32 = struct.unpack('<I', bytes(crc32))[0]

    # album cover
    f.seek(5, 1)
    image_size = f.read(4)
    image_size = struct.unpack('<I', bytes(image_size))[0]
    image_data = f.read(image_size) if image_size else None

    # media data
    output_path = output_path(input_path, meta_data)
    if skip and os.path.exists(output_path): return

    data = f.read()
    f.close()

    # stream cipher (modified RC4 Pseudo-random generation algorithm)
    stream = [S[(S[i] + S[(i + S[i]) & 0xFF]) & 0xFF] for i in range(256)]
    stream = bytes(bytearray(stream * (len(data) // 256 + 1))[1:1 + len(data)])
    data = XOR(data, stream)

    m = open(output_path, 'wb')
    m.write(data)
    m.close()

    # media tag
    def embed(item, content, type):
        item.encoding = 0
        item.type = type
        item.mime = 'image/png' if content[0:4] == binascii.a2b_hex(
            '89504E47') else 'image/jpeg'
        item.data = content

    if image_data:
        if meta_data['format'] == 'flac':
            audio = flac.FLAC(output_path)
            image = flac.Picture()
            embed(image, image_data, 3)
            audio.clear_pictures()
            audio.add_picture(image)
        elif meta_data['format'] == 'mp3':
            audio = mp3.MP3(output_path)
            image = id3.APIC()
            embed(image, image_data, 6)
            audio.tags.add(image)
        audio.save()

    if meta_length:
        if meta_data['format'] == 'flac':
            audio = flac.FLAC(output_path)
            audio['description'] = identification
        else:
            audio = mp3.EasyMP3(output_path)
            audio.tags.RegisterTextKey('comment', 'COMM')
            audio['comment'] = identification
        audio['title'] = meta_data['musicName']
        audio['album'] = meta_data['album']
        audio['artist'] = '/'.join(
            [artist[0] for artist in meta_data['artist']])
        audio.save()

    return output_path
예제 #23
0
def set_metadata_tags(args, audio_file, idx, track, ripper):
    # log completed file
    print(Fore.GREEN + Style.BRIGHT + os.path.basename(audio_file) +
          Style.NORMAL + "\t[ " + format_size(os.stat(audio_file)[ST_SIZE]) +
          " ]" + Fore.RESET)

    if args.output_type == "wav" or args.output_type == "pcm":
        print(Fore.YELLOW + "Skipping metadata tagging for " +
              args.output_type + " encoding...")
        return

    # ensure everything is loaded still
    if not track.is_loaded:
        track.load()
    if not track.album.is_loaded:
        track.album.load()
    album_browser = track.album.browse()
    album_browser.load()

    # calculate num of tracks on disc and num of dics
    num_discs = 0
    num_tracks = 0
    for track_browse in album_browser.tracks:
        if (track_browse.disc == track.disc and
                track_browse.index > track.index):
            num_tracks = track_browse.index
        if track_browse.disc > num_discs:
            num_discs = track_browse.disc

    # try to get genres from Spotify's Web API
    genres = None
    if args.genres is not None:
        genres = ripper.web.get_genres(args.genres[0], track)

    # use mutagen to update id3v2 tags and vorbis comments
    try:
        audio = None
        on_error = 'replace' if args.ascii_path_only else 'ignore'
        album = to_ascii(track.album.name, on_error)
        artist = to_ascii(track.artists[0].name, on_error)
        title = to_ascii(track.name, on_error)

        # the comment tag can be formatted
        if args.comment is not None:
            comment = \
                format_track_string(ripper, args.comment[0], idx, track)
            comment_ascii = to_ascii(comment, on_error)

        if args.grouping is not None:
            grouping = \
                format_track_string(ripper, args.grouping[0], idx, track)
            grouping_ascii = to_ascii(grouping, on_error)

        if genres is not None and genres:
            genres_ascii = [to_ascii(genre) for genre in genres]

        # cover art image
        def get_cover_image(image_link):
            image_link = 'http://i.scdn.co%s' % (
                image_link[len('spotify'):].replace(':', '/'))
            cover_file = urllib.request.urlretrieve(image_link)[0]

            with open(cover_file, "rb") as f:
                if f.mode == "rb":
                    return f.read()
                else:
                    return None

        image_link = str(track.album.cover(0).link)
        image = get_cover_image(image_link)
        
        def tag_to_ascii(_str, _str_ascii):
            return _str if args.ascii_path_only else _str_ascii

        def idx_of_total_str(_idx, _total):
            if _total > 0:
                return "%d/%d" % (_idx, _total)
            else:
                return "%d" % (_idx)

        def save_cover_image(embed_image_func):
            if image is not None:
                def write_image(file_name):
                    cover_path = os.path.dirname(audio_file)
                    cover_file = os.path.join(cover_path, file_name)
                    if not path_exists(cover_file):
                        with open(cover_file, "wb") as f:
                            f.write(image)

                if args.cover_file is not None:
                    write_image(args.cover_file[0])
                elif args.cover_file_and_embed is not None:
                    write_image(args.cover_file_and_embed[0])
                    embed_image_func()
                else:
                    embed_image_func()

        def set_id3_tags(audio):
            # add ID3 tag if it doesn't exist
            audio.add_tags()

            def embed_image():
                audio.tags.add(
                    id3.APIC(
                        encoding=3,
                        mime='image/jpeg',
                        type=3,
                        desc='Front Cover',
                        data=image
                    )
                )

            save_cover_image(embed_image)

            if album is not None:
                audio.tags.add(
                    id3.TALB(text=[tag_to_ascii(track.album.name, album)],
                             encoding=3))
            audio.tags.add(
                id3.TIT2(text=[tag_to_ascii(track.name, title)],
                         encoding=3))
            audio.tags.add(
                id3.TPE1(text=[tag_to_ascii(track.artists[0].name, artist)],
                         encoding=3))
            audio.tags.add(id3.TDRC(text=[str(track.album.year)],
                                    encoding=3))
            audio.tags.add(
                id3.TPOS(text=[idx_of_total_str(track.disc, num_discs)],
                         encoding=3))
            audio.tags.add(
                id3.TRCK(text=[idx_of_total_str(track.index, num_tracks)],
                         encoding=3))
            if args.comment is not None:
                audio.tags.add(
                    id3.COMM(text=[tag_to_ascii(comment, comment_ascii)],
                             encoding=3))
            if args.grouping is not None:
                audio.tags.add(
                    id3.TIT1(text=[tag_to_ascii(grouping, grouping_ascii)],
                             encoding=3))
            if genres is not None and genres:
                tcon_tag = id3.TCON(encoding=3)
                tcon_tag.genres = genres if args.ascii_path_only \
                    else genres_ascii
                audio.tags.add(tcon_tag)

            if args.id3_v23:
                audio.tags.update_to_v23()
                audio.save(v2_version=3, v23_sep='/')
                audio.tags.version = (2, 3, 0)
            else:
                audio.save()

        # aac is not well supported
        def set_id3_tags_raw(audio, audio_file):
            try:
                id3_dict = id3.ID3(audio_file)
            except id3.ID3NoHeaderError:
                id3_dict = id3.ID3()

            def embed_image():
                id3_dict.add(
                    id3.APIC(
                        encoding=3,
                        mime='image/jpeg',
                        type=3,
                        desc='Front Cover',
                        data=image
                    )
                )

            save_cover_image(embed_image)

            if album is not None:
                id3_dict.add(
                    id3.TALB(text=[tag_to_ascii(track.album.name, album)],
                             encoding=3))
            id3_dict.add(
                id3.TIT2(text=[tag_to_ascii(track.name, title)],
                         encoding=3))
            id3_dict.add(
                id3.TPE1(text=[tag_to_ascii(track.artists[0].name, artist)],
                         encoding=3))
            id3_dict.add(id3.TDRC(text=[str(track.album.year)],
                                  encoding=3))
            id3_dict.add(
                id3.TPOS(text=[idx_of_total_str(track.disc, num_discs)],
                         encoding=3))
            id3_dict.add(
                id3.TRCK(text=[idx_of_total_str(track.index, num_tracks)],
                         encoding=3))
            if args.comment is not None:
                id3_dict.add(
                    id3.COMM(text=[tag_to_ascii(comment, comment_ascii)],
                             encoding=3))
            if args.grouping is not None:
                audio.tags.add(
                    id3.TIT1(text=[tag_to_ascii(grouping, grouping_ascii)],
                             encoding=3))
            if genres is not None and genres:
                tcon_tag = id3.TCON(encoding=3)
                tcon_tag.genres = genres if args.ascii_path_only \
                    else genres_ascii
                id3_dict.add(tcon_tag)

            if args.id3_v23:
                id3_dict.update_to_v23()
                id3_dict.save(audio_file, v2_version=3, v23_sep='/')
                id3_dict.version = (2, 3, 0)
            else:
                id3_dict.save(audio_file)
            audio.tags = id3_dict

        def set_vorbis_comments(audio):
            # add Vorbis comment block if it doesn't exist
            if audio.tags is None:
                audio.add_tags()

            def embed_image():
                pic = flac.Picture()
                pic.type = 3
                pic.mime = "image/jpeg"
                pic.desc = "Front Cover"
                pic.data = image
                if args.output_type == "flac":
                    audio.add_picture(pic)
                else:
                    data = base64.b64encode(pic.write())
                    audio["METADATA_BLOCK_PICTURE"] = [data.decode("ascii")]

            save_cover_image(embed_image)

            if album is not None:
                audio.tags["ALBUM"] = tag_to_ascii(track.album.name, album)
            audio.tags["TITLE"] = tag_to_ascii(track.name, title)
            audio.tags["ARTIST"] = tag_to_ascii(track.artists[0].name, artist)
            audio.tags["DATE"] = str(track.album.year)
            audio.tags["YEAR"] = str(track.album.year)
            audio.tags["DISCNUMBER"] = str(track.disc)
            audio.tags["DISCTOTAL"] = str(num_discs)
            audio.tags["TRACKNUMBER"] = str(track.index)
            audio.tags["TRACKTOTAL"] = str(num_tracks)
            if args.comment is not None:
                audio.tags["COMMENT"] = tag_to_ascii(comment, comment_ascii)
            if args.grouping is not None:
                audio.tags["GROUPING"] = \
                    tag_to_ascii(grouping, grouping_ascii)

            if genres is not None and genres:
                _genres = genres if args.ascii_path_only else genres_ascii
                audio.tags["GENRE"] = ", ".join(_genres)

            audio.save()

        # only called by Python 3
        def set_mp4_tags(audio):
            # add MP4 tags if it doesn't exist
            if audio.tags is None:
                audio.add_tags()

            def embed_image():
                audio.tags["covr"] = mp4.MP4Cover(image)

            save_cover_image(embed_image)

            if album is not None:
                audio.tags["\xa9alb"] = tag_to_ascii(track.album.name, album)
            audio["\xa9nam"] = tag_to_ascii(track.name, title)
            audio.tags["\xa9ART"] = tag_to_ascii(track.artists[0].name, artist)
            audio.tags["\xa9day"] = str(track.album.year)
            audio.tags["disk"] = [(track.disc, num_discs)]
            audio.tags["trkn"] = [(track.index, num_tracks)]
            if args.comment is not None:
                audio.tags["\xa9cmt"] = tag_to_ascii(comment, comment_ascii)
            if args.grouping is not None:
                audio.tags["\xa9grp"] = tag_to_ascii(grouping, grouping_ascii)

            if genres is not None and genres:
                _genres = genres if args.ascii_path_only else genres_ascii
                audio.tags["\xa9gen"] = ", ".join(_genres)

            audio.save()

        def set_m4a_tags(audio):
            # add M4A tags if it doesn't exist
            audio.add_tags()

            def embed_image():
                audio.tags[str("covr")] = m4a.M4ACover(image)

            save_cover_image(embed_image)

            if album is not None:
                audio.tags[b"\xa9alb"] = tag_to_ascii(track.album.name, album)
            audio[b"\xa9nam"] = tag_to_ascii(track.name, title)
            audio.tags[b"\xa9ART"] = tag_to_ascii(
                track.artists[0].name, artist)
            audio.tags[b"\xa9day"] = str(track.album.year)
            audio.tags[str("disk")] = (track.disc, num_discs)
            audio.tags[str("trkn")] = (track.index, num_tracks)
            if args.comment is not None:
                audio.tags[b"\xa9cmt"] = tag_to_ascii(comment, comment_ascii)
            if args.grouping is not None:
                audio.tags[b"\xa9grp"] = tag_to_ascii(grouping, grouping_ascii)

            if genres is not None and genres:
                _genres = genres if args.ascii_path_only else genres_ascii
                audio.tags[b"\xa9gen"] = ", ".join(_genres)

            audio.save()

        if args.output_type == "flac":
            audio = flac.FLAC(audio_file)
            set_vorbis_comments(audio)
        elif args.output_type == "ogg":
            audio = oggvorbis.OggVorbis(audio_file)
            set_vorbis_comments(audio)
        elif args.output_type == "opus":
            audio = oggopus.OggOpus(audio_file)
            set_vorbis_comments(audio)
        elif args.output_type == "aac":
            audio = aac.AAC(audio_file)
            set_id3_tags_raw(audio, audio_file)
        elif args.output_type == "m4a":
            if sys.version_info >= (3, 0):
                from mutagen import mp4

                audio = mp4.MP4(audio_file)
                set_mp4_tags(audio)
            else:
                from mutagen import m4a, mp4

                audio = m4a.M4A(audio_file)
                set_m4a_tags(audio)
                audio = mp4.MP4(audio_file)
        elif args.output_type == "alac.m4a":
            if sys.version_info >= (3, 0):
                from mutagen import mp4

                audio = mp4.MP4(audio_file)
                set_mp4_tags(audio)
            else:
                from mutagen import m4a, mp4

                audio = m4a.M4A(audio_file)
                set_m4a_tags(audio)
                audio = mp4.MP4(audio_file)
        elif args.output_type == "mp3":
            audio = mp3.MP3(audio_file, ID3=id3.ID3)
            set_id3_tags(audio)

        def bit_rate_str(bit_rate):
            brs = "%d kb/s" % bit_rate
            if not args.cbr:
                brs = "~" + brs
            return brs

        def mode_str(mode):
            modes = ["Stereo", "Joint Stereo", "Dual Channel", "Mono"]
            if mode < len(modes):
                return modes[mode]
            else:
                return ""

        def channel_str(num):
            channels = ["", "Mono", "Stereo"]
            if num < len(channels):
                return channels[num]
            else:
                return ""

        # log id3 tags
        print("-" * 79)
        print(Fore.YELLOW + "Setting artist: " + artist + Fore.RESET)
        if album is not None:
            print(Fore.YELLOW + "Setting album: " + album + Fore.RESET)
        print(Fore.YELLOW + "Setting title: " + title + Fore.RESET)
        print(Fore.YELLOW + "Setting track info: (" +
              str(track.index) + ", " + str(num_tracks) + ")" + Fore.RESET)
        print(Fore.YELLOW + "Setting disc info: (" + str(track.disc) +
              ", " + str(num_discs) + ")" + Fore.RESET)
        print(Fore.YELLOW + "Setting release year: " +
              str(track.album.year) + Fore.RESET)
        if genres is not None and genres:
            print(Fore.YELLOW + "Setting genres: " +
                  " / ".join(genres_ascii) + Fore.RESET)
        if image is not None:
            print(Fore.YELLOW + "Adding cover image" + Fore.RESET)
        if args.comment is not None:
            print(Fore.YELLOW + "Adding comment: " + comment_ascii +
                  Fore.RESET)
        if args.grouping is not None:
            print(Fore.YELLOW + "Adding grouping: " + grouping_ascii +
                  Fore.RESET)
        if args.output_type == "flac":
            bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) *
                        audio.info.channels)
            print("Time: " + format_time(audio.info.length) +
                  "\tFree Lossless Audio Codec" +
                  "\t[ " + bit_rate_str(bit_rate / 1000) + " @ " +
                  str(audio.info.sample_rate) +
                  " Hz - " + channel_str(audio.info.channels) + " ]")
            print("-" * 79)
            print(Fore.YELLOW + "Writing Vorbis comments - " +
                  audio.tags.vendor + Fore.RESET)
            print("-" * 79)
        if args.output_type == "alac.m4a":
            bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) *
                        audio.info.channels)
            print("Time: " + format_time(audio.info.length) +
                  "\tApple Lossless" +
                  "\t[ " + bit_rate_str(bit_rate / 1000) + " @ " +
                  str(audio.info.sample_rate) +
                  " Hz - " + channel_str(audio.info.channels) + " ]")
            print("-" * 79)
            print(Fore.YELLOW + "Writing Apple iTunes metadata - " +
                  Fore.RESET)
            print("-" * 79)
        elif args.output_type == "ogg":
            print("Time: " + format_time(audio.info.length) +
                  "\tOgg Vorbis Codec" +
                  "\t[ " + bit_rate_str(audio.info.bitrate / 1000) + " @ " +
                  str(audio.info.sample_rate) +
                  " Hz - " + channel_str(audio.info.channels) + " ]")
            print("-" * 79)
            print(Fore.YELLOW + "Writing Vorbis comments - " +
                  audio.tags.vendor + Fore.RESET)
            print("-" * 79)
        elif args.output_type == "opus":
            print("Time: " + format_time(audio.info.length) + "\tOpus Codec" +
                  "\t[ " + channel_str(audio.info.channels) + " ]")
            print("-" * 79)
            print(Fore.YELLOW + "Writing Vorbis comments - " +
                  audio.tags.vendor + Fore.RESET)
            print("-" * 79)
        elif args.output_type == "mp3":
            print("Time: " + format_time(audio.info.length) + "\tMPEG" +
                  str(audio.info.version) +
                  ", Layer " + ("I" * audio.info.layer) + "\t[ " +
                  bit_rate_str(audio.info.bitrate / 1000) +
                  " @ " + str(audio.info.sample_rate) + " Hz - " +
                  mode_str(audio.info.mode) + " ]")
            print("-" * 79)
            id3_version = "v%d.%d" % (
                audio.tags.version[0], audio.tags.version[1])
            print("ID3 " + id3_version + ": " +
                  str(len(audio.tags.values())) + " frames")
            print(
                Fore.YELLOW + "Writing ID3 version " +
                id3_version + Fore.RESET)
            print("-" * 79)
        elif args.output_type == "aac":
            print("Time: " + format_time(audio.info.length) +
                  "\tAdvanced Audio Coding" +
                  "\t[ " + bit_rate_str(audio.info.bitrate / 1000) +
                  " @ " + str(audio.info.sample_rate) + " Hz - " +
                  channel_str(audio.info.channels) + " ]")
            print("-" * 79)
            id3_version = "v%d.%d" % (
                audio.tags.version[0], audio.tags.version[1])
            print("ID3 " + id3_version + ": " +
                  str(len(audio.tags.values())) + " frames")
            print(
                Fore.YELLOW + "Writing ID3 version " +
                id3_version + Fore.RESET)
            print("-" * 79)
        elif args.output_type == "m4a":
            bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) *
                        audio.info.channels)
            print("Time: " + format_time(audio.info.length) +
                  "\tMPEG-4 Part 14 Audio" +
                  "\t[ " + bit_rate_str(bit_rate / 1000) +
                  " @ " + str(audio.info.sample_rate) + " Hz - " +
                  channel_str(audio.info.channels) + " ]")
            print("-" * 79)
            print(Fore.YELLOW + "Writing Apple iTunes metadata - " +
                  str(audio.info.codec) + Fore.RESET)
            print("-" * 79)

    except id3.error:
        print(Fore.YELLOW + "Warning: exception while saving id3 tag: " +
              str(id3.error) + Fore.RESET)
예제 #24
0
def dump(input_path, output_path=None, skip=True):

    output_path = (lambda path, meta: os.path.splitext(path)[0] + '.' + meta[
        'format']) if not output_path else output_path
    core_key = binascii.a2b_hex('687A4852416D736F356B496E62617857')
    meta_key = binascii.a2b_hex('2331346C6A6B5F215C5D2630553C2728')
    unpad = lambda s: s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))]

    f = open(input_path, 'rb')

    # magic header
    header = f.read(8)
    assert binascii.b2a_hex(header) == b'4354454e4644414d'

    # key data
    f.seek(2, 1)
    key_length = f.read(4)
    key_length = struct.unpack('<I', bytes(key_length))[0]

    key_data = bytearray(f.read(key_length))
    key_data = bytes(bytearray([byte ^ 0x64 for byte in key_data]))

    cryptor = AES.new(core_key, AES.MODE_ECB)
    key_data = unpad(cryptor.decrypt(key_data))[17:]
    key_length = len(key_data)

    # S-box (standard RC4 Key-scheduling algorithm)
    key = bytearray(key_data)
    S = bytearray(range(256))
    j = 0

    for i in range(256):
        j = (j + S[i] + key[i % key_length]) % 256
        S[i], S[j] = S[j], S[i]

    # meta data
    meta_length = f.read(4)
    meta_length = struct.unpack('<I', bytes(meta_length))[0]

    meta_data = bytearray(f.read(meta_length))
    meta_data = bytes(bytearray([byte ^ 0x63 for byte in meta_data]))
    meta_data = base64.b64decode(meta_data[22:])

    cryptor = AES.new(meta_key, AES.MODE_ECB)
    meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:]
    meta_data = json.loads(meta_data)

    # crc32
    crc32 = f.read(4)
    crc32 = struct.unpack('<I', bytes(crc32))[0]

    # album cover
    f.seek(5, 1)
    image_size = f.read(4)
    image_size = struct.unpack('<I', bytes(image_size))[0]
    image_data = f.read(image_size)

    # media data
    output_path = output_path(input_path, meta_data)
    if skip and os.path.exists(output_path): return
    m = open(output_path, 'wb')
    data = bytearray(f.read())

    # stream cipher (modified RC4 Pseudo-random generation algorithm)
    i = 0
    j = 0
    for k, _ in enumerate(data):
        i = (i + 1) % 256
        j = (i + S[i]) % 256  # in RC4, is j = (j + S[i]) % 256
        # S[i], S[j] = S[j], S[i] # no swapping
        data[k] ^= S[(S[i] + S[j]) % 256]

    m.write(data)
    m.close()
    f.close()

    # media tag
    if meta_data['format'] == 'flac':
        audio = flac.FLAC(output_path)
        # audio.delete()
        image = flac.Picture()
        image.encoding = 0
        image.type = 3
        image.mime = 'image/jpeg'
        image.data = image_data
        audio.clear_pictures()
        audio.add_picture(image)
    elif meta_data['format'] == 'mp3':
        audio = mp3.MP3(output_path)
        # audio.delete()
        image = id3.APIC()
        image.encoding = 0
        image.type = 3
        image.mime = 'image/jpeg'
        image.data = image_data
        audio.tags.add(image)
        audio.save()
        audio = mp3.EasyMP3(output_path)

    audio['title'] = meta_data['musicName']
    audio['album'] = meta_data['album']
    audio['artist'] = '/'.join([artist[0] for artist in meta_data['artist']])
    audio.save()

    return output_path
예제 #25
0
def test_identify_flac_filetype(flac_file):
    """Test the identify_filetype function on a FLAC file.

    Asserts that the identify_filetype function returns a FLAC object created by mutagen."""
    assert metadata.identify_filetype(flac_file) == flac.FLAC(flac_file)
예제 #26
0
파일: dump.py 프로젝트: akiakise/NCM_dump
def dumpfile(file_path):
    # 预定义key
    core_key = binascii.a2b_hex("687A4852416D736F356B496E62617857")
    meta_key = binascii.a2b_hex("2331346C6A6B5F215C5D2630553C2728")
    unpad = lambda s: s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))]

    f = open(file_path, 'rb')

    # magic header
    header = f.read(8)
    assert binascii.b2a_hex(header) == b'4354454e4644414d'

    # key data
    f.seek(2, 1)
    key_length = f.read(4)
    key_length = struct.unpack('<I', bytes(key_length))[0]

    key_data = bytearray(f.read(key_length))
    key_data = bytes(bytearray([byte ^ 0x64 for byte in key_data]))

    cryptor = AES.new(core_key, AES.MODE_ECB)
    key_data = unpad(cryptor.decrypt(key_data))[17:]
    key_length = len(key_data)

    # key box
    key_data = bytearray(key_data)
    key_box = bytearray(range(256))
    j = 0

    for i in range(256):
        j = (key_box[i] + j + key_data[i % key_length]) & 0xff
        key_box[i], key_box[j] = key_box[j], key_box[i]

    # meta data
    meta_length = f.read(4)
    meta_length = struct.unpack('<I', bytes(meta_length))[0]

    meta_data = bytearray(f.read(meta_length))
    meta_data = bytes(bytearray([byte ^ 0x63 for byte in meta_data]))
    meta_data = base64.b64decode(meta_data[22:])

    cryptor = AES.new(meta_key, AES.MODE_ECB)
    meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:]
    meta_data = json.loads(meta_data)

    # crc32
    crc32 = f.read(4)
    crc32 = struct.unpack('<I', bytes(crc32))[0]

    # album cover
    f.seek(5, 1)
    image_size = f.read(4)
    image_size = struct.unpack('<I', bytes(image_size))[0]
    image_data = f.read(image_size)

    author_name = ""
    # media data
    if len(meta_data['artist']) == 1:
        author_name = meta_data['artist'][0][0]
    else:
        for i in range(len(meta_data['artist'])):
            if i == len(meta_data['artist']) - 1:
                author_name += meta_data['artist'][i][0]
            else:
                author_name += meta_data['artist'][i][0]
                author_name += ','
    file_name = author_name + ' - ' + meta_data['musicName'] + '.' + meta_data['format']
    music_path = os.path.join(os.path.split(file_path)[0], file_name)
    m = open(music_path, 'wb')

    while True:
        chunk = bytearray(f.read(0x8000))
        chunk_length = len(chunk)
        if not chunk:
            break

        for i in range(chunk_length):
            j = (i + 1) & 0xff
            chunk[i] ^= key_box[(key_box[j] + key_box[(key_box[j] + j) & 0xff]) & 0xff]

        m.write(chunk)

    m.close()
    f.close()

    # media tag
    if meta_data['format'] == 'flac':
        audio = flac.FLAC(music_path)
        # audio.delete()
        image = flac.Picture()
        image.type = 3
        image.mime = 'image/jpeg'
        image.data = image_data
        audio.clear_pictures()
        audio.add_picture(image)
    elif meta_data['format'] == 'mp3':
        audio = mp3.MP3(music_path)
        # audio.delete()
        image = id3.APIC()
        image.type = 3
        image.mime = 'image/jpeg'
        image.data = image_data
        audio.tags.add(image)
        audio.save()
        audio = mp3.EasyMP3(music_path)

    audio['title'] = meta_data['musicName']
    audio['album'] = meta_data['album']
    audio['artist'] = '/'.join([artist[0] for artist in meta_data['artist']])
    audio.save()
예제 #27
0
import os
import sys
import mutagen.flac as mflac
import mutagen.id3 as mid3

input_song = sys.argv[1]
proc_song = ()
title, extension = os.path.splitext(input_song)
true_extension = extension.lower()

if true_extension == '.mp3':
    proc_song = mid3.ID3(input_song)
elif true_extension == '.flac':
    proc_song = mflac.FLAC(input_song)
else:
    print("Extension not supported")
    sys.exit(1)

output = proc_song.pprint()

print(output)
예제 #28
0
def dump(input_path):

    f = open(input_path, 'rb')
    # 开头都一样,不一样解不了密
    header = f.read(8)
    assert binascii.b2a_hex(header) == b'4354454e4644414d'

    f.seek(2, 1)

    # key data, 要翻译成小端的unsigned int
    key_length = f.read(4)
    key_length = struct.unpack('<I', bytes(key_length))[0]

    # AES-ECB 密文
    key_data = bytearray(f.read(key_length))
    key_data = bytes(bytearray([byte ^ 0x64 for byte in key_data]))

    # AES-ECB 解密
    core_key = binascii.a2b_hex('687A4852416D736F356B496E62617857') # 神奇的key,hashcat还是有内鬼?
    cryptor = AES.new(core_key, AES.MODE_ECB)
    # decrypt 前17位是 neteasecloudmusic,采用pkcs7 padding方式
    key_data = unpad(cryptor.decrypt(key_data), 16)[17:] 
    key_length = len(key_data)

    # S-box (标准 RC4 KSA)
    key = bytearray(key_data)
    S = bytearray(range(256))
    j = 0

    for i in range(256):
        j = (j + S[i] + key[i % key_length]) & 0xFF
        S[i], S[j] = S[j], S[i]

    # meta data,要翻译成小端的unsigned int
    meta_length = f.read(4)
    meta_length = struct.unpack('<I', bytes(meta_length))[0]

    if meta_length:
        meta_data = bytearray(f.read(meta_length))
        meta_data = bytes(bytearray([byte ^ 0x63 for byte in meta_data]))
        identifier = meta_data.decode('utf-8') # 惊现 '163 key(Don't modify):' 内鬼?
        # base64 解码
        meta_data = base64.b64decode(meta_data[22:])
        # 第二次 AES-ECB 解密
        meta_key = binascii.a2b_hex('2331346C6A6B5F215C5D2630553C2728')
        cryptor = AES.new(meta_key, AES.MODE_ECB)
        meta_data = unpad(cryptor.decrypt(meta_data), 16).decode('utf-8')
        # 解密出来一个 json 格式文件
        meta_data = json.loads(meta_data[6:])
    else:
        # 没有 json 文件确定格式的话,就用文件大小区分(>16m)
        meta_data = {'format': 'flac' if os.fstat(f.fileno()).st_size > 16777216 else 'mp3'}

    f.seek(5, 1)

    # 专辑封面图片
    image_space = f.read(4)
    image_space = struct.unpack('<I', bytes(image_space))[0]
    image_size = f.read(4)
    image_size = struct.unpack('<I', bytes(image_size))[0]
    image_data = f.read(image_size) if image_size else None

    f.seek(image_space - image_size, 1)

    # 音乐输出地址
    output_path = os.path.splitext(input_path)[0] + '.' + meta_data['format']
    # 已转换过的不做无用功
    if os.path.exists(output_path): return
    # 剩下全是音乐文件
    data = f.read()
    f.close()

    # 音乐文件主体部分 (修改的RC4-PRGA,没有用 j 加和随机化)
    # 直接循环的话会丢失最后几秒
    stream = [S[(S[i] + S[(i + S[i]) & 0xFF]) & 0xFF] for i in range(256)]
    stream = bytes(bytearray(stream * (len(data) // 256 + 1))[1:1 + len(data)])
    data = strxor(data, stream)

    m = open(output_path, 'wb')
    m.write(data)
    m.close()

    # 处理专辑封面
    if image_data:
        if meta_data['format'] == 'flac':
            audio = flac.FLAC(output_path)
            image = flac.Picture()
            image.encoding = 0
            image.type = 3
            image.mime = 'image/png' if image_data[0:4] == binascii.a2b_hex('89504E47') else 'image/jpeg' # png开头是\x89 P N G
            image.data = image_data
            audio.clear_pictures()
            audio.add_picture(image)
            audio.save()
        elif meta_data['format'] == 'mp3':
            audio = mp3.MP3(output_path)
            image = id3.APIC()
            image.encoding = 0
            image.type = 6
            image.mime = 'image/png' if image_data[0:4] == binascii.a2b_hex('89504E47') else 'image/jpeg' # png开头是\x89 P N G
            image.data = image_data
            audio.tags.add(image)
            audio.save()
    
    # 添加音乐相关信息
    if meta_length:
        if meta_data['format'] == 'flac':
            audio = flac.FLAC(output_path)
            audio['description'] = identifier
        else:
            audio = mp3.EasyMP3(output_path)
            audio['title'] = 'placeholder'
            audio.tags.RegisterTextKey('comment', 'COMM')
            audio['comment'] = identifier
        audio['title'] = meta_data['musicName']
        audio['album'] = meta_data['album']
        audio['artist'] = '/'.join([artist[0] for artist in meta_data['artist']])
        audio.save()

    return output_path
예제 #29
0
def get_tags(folder,addformat=False):
    flacs = []
    mp3s = []
    images = []
    tracks = []
    for root, dirnames, filenames in os.walk(folder):
        for filename in [f for f in filenames if f.endswith(('.flac'))]:
            flacs.append(os.path.join(root, filename))
        for filename in [f for f in filenames if f.endswith('.mp3')]:
            mp3s.append(os.path.join(root, filename))
        for filename in [f for f in filenames if f.endswith('cover.jpg')]:
            images.append(os.path.join(root, filename))
    tracks = []
    tracks_tags = []
    total_length = 0
    if flacs:
        format = 'FLAC'
        flacfile = flac.FLAC(flacs[0])
        if flacfile.info.bits_per_sample == 16:
            bitrate = 'Lossless'
        elif flacfile.info.bits_per_sample == 24:
            bitrate = '24bit Lossless'
        ## past here is irrelevant if addformat == True
        artist = flacfile['artist'][0]
        album = flacfile['album'][0]
        year = flacfile['date'][0]
        for filename in flacs:
            flacfile = flac.FLAC(filename)
            track_num = int(flacfile['tracknumber'][0])
            track_title = flacfile['title'][0]
            length = int(flacfile.info.length)
            tracks.append((track_num, track_title, length))
            total_length += length
            tracks_tags.append(flacfile.tags)
    elif mp3s:
        format = 'MP3'
        mp3file = mp3.MP3(mp3s[0])
        if mp3file.info.VBR_preset:     ## only for LAME V*
            bitrate = mp3file.info.VBR_preset
        else:
            bitrate = mp3file.info.bitrate / 1000
        ## past here is irrelevant if addformat == True
        artist = str(mp3file['TPE1'])
        album = str(mp3file['TALB'])
        year = str(mp3file['TDRC'])
        for filename in mp3s:
            mp3file = mp3.MP3(filename)
            track_num = int(mp3file['TRCK'].text[0])
            track_title = str(mp3file['TIT2'].text[0])
            length = int(mp3file.info.length)
            tracks.append((track_num, track_title, length))
            total_length += length
    else:
        raise Exception('No music files found!')
    tracks.sort()
    if images and not addformat:
        image = to_imgur(images[0])
    else:
        image = ''
    pprint('Tags extracted from files...')
    return (artist,album,year,format,bitrate,image,tracks,total_length)
예제 #30
0
def convert_perfect_three(folder=None,outfolder=None):
    #folders are created within outfolder for each bitrate
    #e.g., if outfolder = /files/, three folders would be created
    #/files/whatever [V2] , /files/whatever [V0] , /files/whatever [320]
    if not folder:
        folder = select_folder(msg='Select the folder to be converted')
    if not outfolder:
        outfolder = select_folder(msg='Select the folder where the new\
folders should be placed')
    #get files
    flacs = []      #contains tuples: (abspath, rel_path, filename)
    logs_cues = []  #same. we don't copy these
    etc = []        #same. we do copy these
    for root, dirs, filenames in os.walk(folder):
        for filename in filenames:
            abspath = os.path.join(root,filename)
            if filename.endswith('.flac'):
                flacs.append((abspath,relpath(abspath,folder),filename))
            elif filename.endswith('.log') or filename.endswith('.cue'):
                logs_cues.append((abspath,relpath(abspath,folder),filename))
            else:
                etc.append((abspath,relpath(abspath,folder),filename))
    #get metadata
    files = []          ## tuples: ((abspath,rel_path,filename),metadata)
    if not flacs:
        raise Exception('No flacs found for conversion!')
    for f in flacs:
        flacfile = flac.FLAC(f[0])
        artist = flacfile['artist'][0].encode('utf-8')
        title = flacfile['title'][0].encode('utf-8')
        album = flacfile['album'][0].encode('utf-8')
        year = flacfile['date'][0].encode('utf-8')
        track_num = flacfile['tracknumber'][0].encode('utf-8')
        metadata = (artist,title,album,year,track_num)
        files.append((f,metadata))
    for f in flacs:
        reldir = os.path.split(f[1])[0]
##        print(reldir)         #for debugging
        if reldir and not os.path.isdir(reldir):
            os.makedirs(reldir)
    #prepare the different convert bathes
    converts = []
    return_dirs = []
    global _bitrates
    try:
        bitrates = _bitrates
    except NameError:
        bitrates = ('V0','V2','320')
    for bitrate in bitrates:
        #using values of 'artist' and 'album' left over from the above for loop
        makedir = os.path.normpath(
            os.path.join(outfolder,'%s - %s (%s) [%s]' % (
                                    artist,album,year,bitrate)))
        return_dirs.append(makedir)
        converts.append([(f[0][0],
                          os.path.join(
                              str(makedir),str(f[0][1][:-5])), #-5 = '.flac'
                          make_lame_params(bitrate),
                          make_lame_metadata(f[1])) for f in files])
        
        try:
            os.mkdir(makedir)
        except OSError:
            pass        #folder already exists - that's fine
        #copy over 'etc' (cover art, anything else)
        for f in etc:
            reldir = os.path.join(makedir,os.path.split(f[1])[0])
            if not os.path.isdir(reldir):
                os.makedirs(reldir)
            filecopy(f[0],os.path.join(makedir,f[1]))
        #make subdirectories, if any exist
        for convert in converts:
            for job in convert:
                reldir = os.path.split(job[1])[0]
                if not os.path.isdir(reldir):
                    os.makedirs(reldir)
##    return converts                   ##for debugging
    convert_id = 0
    for convert in converts:
        pprint('converting to %s' % return_dirs[convert_id])
        convert_id += 1
        jobid = 0
        for job in convert:
            jobid += 1
            while actives() == threads_number:
                time.sleep(1)
            pprint('%s of %s' % (jobid,len(convert)))
            threading.Thread(
                target = flac_to_mp3,
                args = (job[0],job[1],job[2],job[3])).start()
    #wait for all threads to finish
    while actives() != default_actives:
        time.sleep(1)
    return return_dirs