Example #1
0
def _audio_tpub(atuple):
    audio, atag, advanced, _, _ = atuple
    if advanced:
        param = ast.literal_eval(atag)
        audio.add(TPUB(3, param[1]))
    else:
        audio.add(TPUB(3, atag))
Example #2
0
def tag_resulting_track(out_file_path, track_info):
    try:
        track_to_tag = ID3(out_file_path)
    except mutagen.id3.error:
        track_to_tag = ID3()
        track_to_tag.save(out_file_path)

    track_to_tag.add(TPE1(encoding=3,
                          text=track_info['track_artist']))  # Artist

    track_to_tag.add(TIT2(encoding=3, text=track_info['track_title']))  # Title
    track_to_tag.add(TSRC(encoding=3, text=track_info['ISRC']))  # ISRC

    track_to_tag.add(TRCK(encoding=3,
                          text=track_info['track_number']))  # Track Number
    track_to_tag.add(TPOS(encoding=3,
                          text=track_info['disc_number']))  # Disc Number

    track_to_tag.add(TALB(encoding=3,
                          text=track_info['album_title']))  # Album Title
    track_to_tag.add(TDRC(encoding=3, text=track_info['album_year']))  # Year
    track_to_tag.add(TPUB(encoding=3, text=track_info['label']))  # Label
    track_to_tag.add(TPE2(encoding=3,
                          text=track_info['album_artist']))  # Album artist
    track_to_tag.add(TCON(encoding=3, text=track_info['genre']))  # Genre

    track_to_tag.save(out_file_path)
    def as_mp3(self):
        """ Embed metadata to MP3 files. """
        music_file = self.music_file
        #print(music_file)
        meta_tags = self.meta_tags
        #print(meta_tags)
        # EasyID3 is fun to use ;)
        # For supported easyid3 tags:
        # https://github.com/quodlibet/mutagen/blob/master/mutagen/easyid3.py
        # Check out somewhere at end of above linked file
        ''' mera code adding dummy tags'''
        try:
            tags = ID3(music_file)
        except ID3NoHeaderError:
            tags = ID3()

        tags.save(music_file)

        audiofile = EasyID3(music_file)
        self._embed_basic_metadata(audiofile, preset=TAG_PRESET)
        audiofile['media'] = meta_tags['type']
        audiofile['author'] = meta_tags['artists'][0]['name']
        audiofile['lyricist'] = meta_tags['artists'][0]['name']
        audiofile['arranger'] = meta_tags['artists'][0]['name']
        audiofile['performer'] = meta_tags['artists'][0]['name']
        audiofile['website'] = meta_tags['external_urls']['spotify']
        audiofile['length'] = str(meta_tags['duration'])
        if meta_tags['publisher']:
            audiofile['encodedby'] = meta_tags['publisher']
        if meta_tags['external_ids']['isrc']:
            audiofile['isrc'] = meta_tags['external_ids']['isrc']
        audiofile.save(v2_version=3)

        # For supported id3 tags:
        # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py
        # Each class represents an id3 tag
        audiofile = ID3(music_file)
        audiofile['TORY'] = TORY(encoding=3, text=meta_tags['year'])
        audiofile['TYER'] = TYER(encoding=3, text=meta_tags['year'])
        audiofile['TPUB'] = TPUB(encoding=3, text=meta_tags['publisher'])
        audiofile['COMM'] = COMM(encoding=3,
                                 text=meta_tags['external_urls']['spotify'])
        if meta_tags['lyrics']:
            audiofile['USLT'] = USLT(encoding=3,
                                     desc=u'Lyrics',
                                     text=meta_tags['lyrics'])
        try:
            albumart = urllib.request.urlopen(
                meta_tags['album']['images'][0]['url'])
            audiofile['APIC'] = APIC(encoding=3,
                                     mime='image/jpeg',
                                     type=3,
                                     desc=u'Cover',
                                     data=albumart.read())
            albumart.close()
        except IndexError:
            pass

        audiofile.save(v2_version=3)
        return True
Example #4
0
    def to_id3_tags(self, audio_path):
        """Loads an MP3 file and adds ID3v2.4 tags based on the given discogs entry"""
        audio = ID3(audio_path, v2_version=4)

        # Set album art
        album_art_url = self.get_album_art_url()
        if album_art_url:
            r = requests.get(album_art_url)
            audio.add(APIC(
                encoding=3,
                mime='image/jpeg',
                type=3,
                desc='Cover',
                data=r.content
            ))
            del r

        # Set title
        audio.add(TIT2(encoding=3, text=[self.track.title]))

        # Set artists
        audio.add(TPE1(encoding=3, text=self.get_artists()))

        # Set album
        audio.add(TALB(encoding=3, text=[self.get_release_title()]))

        # Set track number
        audio.add(TRCK(encoding=3, text=[self.track.position]))

        # Set labels
        labels = list(self.get_labels())
        if len(labels) > 0:
            audio.add(TPUB(encoding=3, text=labels[0:1]))

        # Set genres
        audio.add(TCON(encoding=3, text=self.get_genres()))

        # Set year
        audio.add(TYER(encoding=3, text=[self.get_year()])) # for backwards compatibility with v2.3
        # The timestamp fields are based on a subset of ISO 8601. When being as
        # precise as possible the format of a time string is
        # yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of
        # 24), ":", minutes, ":", seconds), but the precision may be reduced by
        # removing as many time indicators as wanted. Hence valid timestamps
        # are
        # yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and
        # yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use
        # the slash character as described in 8601, and for multiple non-
        # contiguous dates, use multiple strings, if allowed by the frame
        # definition.
        audio.add(TDRL(encoding=3, text=[self.get_year()]))
        audio.add(TDOR(encoding=3, text=[self.get_year()]))

        # Set tagging time
        # aka right now
        # for completeness sake
        audio.add(TDTG(encoding=3, text=[datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M')]))

        # Write to disk
        audio.save()
    def as_mp3(self, path, metadata, cached_albumart=None):
        """ Embed metadata to MP3 files. """
        logger.debug('Writing MP3 metadata to "{path}".'.format(path=path))
        # EasyID3 is fun to use ;)
        # For supported easyid3 tags:
        # https://github.com/quodlibet/mutagen/blob/master/mutagen/easyid3.py
        # Check out somewhere at end of above linked file
        audiofile = EasyID3(path)
        self._embed_basic_metadata(audiofile,
                                   metadata,
                                   "mp3",
                                   preset=TAG_PRESET)
        audiofile["media"] = metadata["type"]
        audiofile["author"] = metadata["artists"][0]["name"]
        audiofile["lyricist"] = metadata["artists"][0]["name"]
        audiofile["arranger"] = metadata["artists"][0]["name"]
        audiofile["performer"] = metadata["artists"][0]["name"]
        provider = metadata["provider"]
        audiofile["website"] = metadata["external_urls"][provider]
        audiofile["length"] = str(metadata["duration"])
        if metadata["publisher"]:
            audiofile["encodedby"] = metadata["publisher"]
        if metadata["external_ids"]["isrc"]:
            audiofile["isrc"] = metadata["external_ids"]["isrc"]
        audiofile.save(v2_version=3)

        # For supported id3 tags:
        # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py
        # Each class in the linked source file represents an id3 tag
        audiofile = ID3(path)
        if metadata["year"]:
            audiofile["TORY"] = TORY(encoding=3, text=metadata["year"])
            audiofile["TYER"] = TYER(encoding=3, text=metadata["year"])
        if metadata["publisher"]:
            audiofile["TPUB"] = TPUB(encoding=3, text=metadata["publisher"])
        provider = metadata["provider"]
        audiofile["COMM"] = COMM(encoding=3,
                                 text=metadata["external_urls"][provider])
        if metadata["lyrics"]:
            audiofile["USLT"] = USLT(encoding=3,
                                     desc=u"Lyrics",
                                     text=metadata["lyrics"])
        if cached_albumart is None:
            cached_albumart = urllib.request.urlopen(
                metadata["album"]["images"][0]["url"]).read()
            albumart.close()
        try:
            audiofile["APIC"] = APIC(
                encoding=3,
                mime="image/jpeg",
                type=3,
                desc=u"Cover",
                data=cached_albumart,
            )
        except IndexError:
            pass

        audiofile.save(v2_version=3)
    def as_mp3(self):
        """ Embed metadata to MP3 files. """
        music_file = self.music_file
        meta_tags = self.meta_tags
        # EasyID3 is fun to use ;)
        # For supported easyid3 tags:
        # https://github.com/quodlibet/mutagen/blob/master/mutagen/easyid3.py
        # Check out somewhere at end of above linked file
        audiofile = EasyID3(music_file)
        self._embed_basic_metadata(audiofile, preset=TAG_PRESET)
        audiofile["media"] = meta_tags["type"]
        audiofile["author"] = meta_tags["artists"][0]["name"]
        audiofile["lyricist"] = meta_tags["artists"][0]["name"]
        audiofile["arranger"] = meta_tags["artists"][0]["name"]
        audiofile["performer"] = meta_tags["artists"][0]["name"]
        audiofile["website"] = meta_tags["external_urls"][self.provider]
        audiofile["length"] = str(meta_tags["duration"])
        if meta_tags["publisher"]:
            audiofile["encodedby"] = meta_tags["publisher"]
        if meta_tags["external_ids"]["isrc"]:
            audiofile["isrc"] = meta_tags["external_ids"]["isrc"]
        audiofile.save(v2_version=3)

        # For supported id3 tags:
        # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py
        # Each class represents an id3 tag
        audiofile = ID3(music_file)
        if meta_tags["year"]:
            audiofile["TORY"] = TORY(encoding=3, text=meta_tags["year"])
            audiofile["TYER"] = TYER(encoding=3, text=meta_tags["year"])
        if meta_tags["publisher"]:
            audiofile["TPUB"] = TPUB(encoding=3, text=meta_tags["publisher"])
        audiofile["COMM"] = COMM(
            encoding=3, text=meta_tags["external_urls"][self.provider])
        if meta_tags["lyrics"]:
            audiofile["USLT"] = USLT(encoding=3,
                                     desc=u"Lyrics",
                                     text=meta_tags["lyrics"])
        try:
            albumart = urllib.request.urlopen(
                meta_tags["album"]["images"][0]["url"])
            audiofile["APIC"] = APIC(
                encoding=3,
                mime="image/jpeg",
                type=3,
                desc=u"Cover",
                data=albumart.read(),
            )
            albumart.close()
        except IndexError:
            pass

        audiofile.save(v2_version=3)
        return True
Example #7
0
    def write_tag(self, tag, value, save=True):
        text = [normalize_tag_text(value)]

        if tag in self.id3.keys():
            self.id3[tag].text = text

        elif tag in TRACK_MD_ID3_TAGS:
            if tag == ID3Tag.TITLE.value:
                self.id3[tag] = TIT2(text=text)
            elif tag == ID3Tag.GENRE.value:
                self.id3[tag] = TCON(text=text)
            elif tag == ID3Tag.BPM.value:
                self.id3[tag] = TBPM(text=text)
            elif tag == ID3Tag.KEY.value:
                self.id3[tag] = TKEY(text=text)
            elif tag == ID3Tag.LABEL.value:
                self.id3[tag] = TPUB(text=text)
            elif tag == ID3Tag.COMMENT.value or tag == ID3Tag.COMMENT_ENG.value or tag == ID3Tag.COMMENT_XXX.value:
                self.id3[ID3Tag.COMMENT.value] = COMM(text=text)
                self.id3[ID3Tag.COMMENT_ENG.value] = COMM(text=text)
                self.id3[ID3Tag.COMMENT_XXX.value] = COMM(text=text)

        if save:
            self.id3.save()
Example #8
0
def setID3(baseDIR, filename, artist, title, lyric, albumID, cover_img_path):
  file_path = os.path.join(baseDIR, filename)
  audio_file = MP3(file_path, ID3=ID3)
  encoding=3   # 3 is for utf-8
  # add CoverPicture
  audio_file.tags.add(
    APIC(
      encoding=encoding,
      mime='image/jpg', # image/jpeg or image/png
      type=3, # 3 is for the cover image
      desc=u'Cover',
      data=open(cover_img_path, 'rb').read()
    )
  )
  audio_file.tags.add(
    USLT(
      encoding=encoding,
      desc=u'Lyric',
      text=lyric
    )
  )
  audio_file.tags.add(
    TOPE(
      encoding=encoding,
      text=artist
    )
  )
  audio_file.tags.add(
    TPE1(
      encoding=encoding,
      text=artist
    )
  )
  audio_file.tags.add(
    TIT1(
      encoding=encoding,
      text=title
    )
  )
  audio_file.tags.add(
    TIT2(
      encoding=encoding,
      text=title
    )
  )
  audio_file.tags.add(
    TIPL(
      encoding=encoding,
      text=[artist]
    )
  )
  albumInfo = cc.getAlbumInfoFromMelon(albumID)
  if not albumInfo == None:
    audio_file.tags.add(
      TALB(
        encoding=encoding,
        text=[albumInfo['album_name']]
      )
    )
    audio_file.tags.add(
      TPRO(
        encoding=encoding,
        text=[albumInfo['copyright']]
      )
    )
    audio_file.tags.add(
      TCON(
        encoding=encoding,
        text=[albumInfo['genre']]
      )
    )
    audio_file.tags.add(
      TPUB(
        encoding=encoding,
        text=[albumInfo['publisher']]
      )
    )
    audio_file.tags.add(
      TDOR(
        encoding=encoding,
        text=[albumInfo['pub_date']]
      )
    )
    audio_file.tags.add(
      TDRL(
        encoding=encoding,
        text=[albumInfo['pub_date']]
      )
    )
  audio_file.save()
Example #9
0
 def __init_id3_tags(id3, major=3):
     """
     Attributes:
         id3 ID3 Tag object
         major ID3 major version, e.g.: 3 for ID3v2.3
     """
     from mutagen.id3 import TRCK, TPOS, TXXX, TPUB, TALB, UFID, TPE2, \
         TSO2, TMED, TIT2, TPE1, TSRC, IPLS, TORY, TDAT, TYER
     id3.add(TRCK(encoding=major, text="1/10"))
     id3.add(TPOS(encoding=major, text="1/1"))
     id3.add(
         TXXX(encoding=major,
              desc="MusicBrainz Release Group Id",
              text="e00305af-1c72-469b-9a7c-6dc665ca9adc"))
     id3.add(TXXX(encoding=major, desc="originalyear", text="2011"))
     id3.add(
         TXXX(encoding=major, desc="MusicBrainz Album Type", text="album"))
     id3.add(
         TXXX(encoding=major,
              desc="MusicBrainz Album Id",
              text="e7050302-74e6-42e4-aba0-09efd5d431d8"))
     id3.add(TPUB(encoding=major, text="J&R Adventures"))
     id3.add(TXXX(encoding=major, desc="CATALOGNUMBER", text="PRAR931391"))
     id3.add(TALB(encoding=major, text="Don\'t Explain"))
     id3.add(
         TXXX(encoding=major,
              desc="MusicBrainz Album Status",
              text="official"))
     id3.add(TXXX(encoding=major, desc="SCRIPT", text="Latn"))
     id3.add(
         TXXX(encoding=major,
              desc="MusicBrainz Album Release Country",
              text="US"))
     id3.add(TXXX(encoding=major, desc="BARCODE", text="804879313915"))
     id3.add(
         TXXX(encoding=major,
              desc="MusicBrainz Album Artist Id",
              text=[
                  "3fe817fc-966e-4ece-b00a-76be43e7e73c",
                  "984f8239-8fe1-4683-9c54-10ffb14439e9"
              ]))
     id3.add(TPE2(encoding=major, text="Beth Hart & Joe Bonamassa"))
     id3.add(TSO2(encoding=major, text="Hart, Beth & Bonamassa, Joe"))
     id3.add(TXXX(encoding=major, desc="ASIN", text="B005NPEUB2"))
     id3.add(TMED(encoding=major, text="CD"))
     id3.add(
         UFID(encoding=major,
              owner="http://musicbrainz.org",
              data=b"f151cb94-c909-46a8-ad99-fb77391abfb8"))
     id3.add(TIT2(encoding=major, text="Sinner's Prayer"))
     id3.add(
         TXXX(encoding=major,
              desc="MusicBrainz Artist Id",
              text=[
                  "3fe817fc-966e-4ece-b00a-76be43e7e73c",
                  "984f8239-8fe1-4683-9c54-10ffb14439e9"
              ]))
     id3.add(TPE1(encoding=major, text=["Beth Hart & Joe Bonamassa"]))
     id3.add(
         TXXX(encoding=major,
              desc="Artists",
              text=["Beth Hart", "Joe Bonamassa"]))
     id3.add(TSRC(encoding=major, text=["NLB931100460", "USMH51100098"]))
     id3.add(
         TXXX(encoding=major,
              desc="MusicBrainz Release Track Id",
              text="d062f484-253c-374b-85f7-89aab45551c7"))
     id3.add(
         IPLS(encoding=major,
              people=[["engineer", "James McCullagh"],
                      ["engineer",
                       "Jared Kvitka"], ["arranger", "Jeff Bova"],
                      ["producer", "Roy Weisman"], ["piano", "Beth Hart"],
                      ["guitar", "Blondie Chaplin"],
                      ["guitar", "Joe Bonamassa"],
                      ["percussion", "Anton Fig"], ["drums", "Anton Fig"],
                      ["keyboard", "Arlan Schierbaum"],
                      ["bass guitar", "Carmine Rojas"],
                      ["orchestra", "The Bovaland Orchestra"],
                      ["vocals", "Beth Hart"], ["vocals",
                                                "Joe Bonamassa"]])),
     id3.add(TORY(encoding=major, text="2011"))
     id3.add(TYER(encoding=major, text="2011"))
     id3.add(TDAT(encoding=major, text="2709"))
Example #10
0
    def MP3Tagger(self):
        Caminho = self.lineEdit.text()
        if Caminho == "":
            QMessageBox.about(self, "ERRO", "Nenhum caminho especificado.")
        else:
            File = glob.glob(Caminho + "/*.mp3")
            for i in File:  # Caminho completo do arquivo com extensão
                self.label_10.setText(_translate("MainWindow", ""))
                QtWidgets.QApplication.processEvents()
                Path = os.path.dirname(i)  # Caninho completo da pasta do arquivo
                Name1 = os.path.basename(i)  # Nome do arquivo completo com extensão
                Name2 = os.path.splitext(Name1)  # Nome do arquivo dividido na extensão
                Name3 = Name2[0].split(" - ")  # Nome do arquivo divido no artista e musica
                Name4 = ''.join(i for i in Name3[0] if not i.isdigit())  # Nome da música sem os números
                print("Caminho: " + i)
                print("Name1: " + str(Name1))
                print("Name2: " + str(Name2))
                print("Name3: " + str(Name3))
                print("Name4: " + str(Name4))
                self.label_10.setText(_translate("MainWindow", "Renomeando arquivo..."))
                QtWidgets.QApplication.processEvents()
                os.rename(i, Path + "\\" + Name3[1] + " -" + Name4 + ".mp3")  # Renomeia o arquivo
                self.label_10.setText(_translate("MainWindow", "Buscando metadados no banco de dados do Spotify..."))
                QtWidgets.QApplication.processEvents()
                meta_tags = spotify_tools.generate_metadata(Name3[1] + " -" + Name4)  # Gera as tags do mp3
                if meta_tags == None:
                    continue
                else:
                    self.label_6.setText(_translate("MainWindow", str(meta_tags['artists'][0]['name'])))
                    self.label_7.setText(_translate("MainWindow", str(meta_tags['name'])))
                    self.label_8.setText(_translate("MainWindow", str(meta_tags['album']['name'])))
                    self.label_10.setText(_translate("MainWindow", "Aplicando tags..."))
                    ScriptFolder = os.path.dirname(os.path.realpath(sys.argv[0]))
                    IMG = open(ScriptFolder + "\\" + 'cover2.jpg', 'wb')
                    IMG.write(urllib.request.urlopen(meta_tags['album']['images'][0]['url']).read())
                    IMG.close()
                    time.sleep(1)
                    self.GenericCover3 = QPixmap('cover2.jpg')
                    self.GenericCover4 = self.GenericCover3.scaled(141, 141)
                    self.graphicsView.setPixmap(self.GenericCover4)
                    QtWidgets.QApplication.processEvents()
                    audiofile = MP3(Path + "\\" + Name3[1] + " -" + Name4 + ".mp3")
                    audiofile.tags = None  # Exclui qualquer tag antes de aplicar as novas (previne erro)
                    audiofile.add_tags(ID3=EasyID3)
                    audiofile['artist'] = meta_tags['artists'][0]['name']
                    audiofile['albumartist'] = meta_tags['artists'][0]['name']
                    audiofile['album'] = meta_tags['album']['name']
                    audiofile['title'] = meta_tags['name']
                    audiofile['tracknumber'] = [meta_tags['track_number'],
                                                meta_tags['total_tracks']]
                    audiofile['discnumber'] = [meta_tags['disc_number'], 0]
                    audiofile['date'] = meta_tags['release_date']
                    audiofile['originaldate'] = meta_tags['release_date']
                    audiofile['media'] = meta_tags['type']
                    audiofile['author'] = meta_tags['artists'][0]['name']
                    audiofile['lyricist'] = meta_tags['artists'][0]['name']
                    audiofile['arranger'] = meta_tags['artists'][0]['name']
                    audiofile['performer'] = meta_tags['artists'][0]['name']
                    audiofile['website'] = meta_tags['external_urls']['spotify']
                    audiofile['length'] = str(meta_tags['duration_ms'] / 1000.0)
                    if meta_tags['publisher']:
                        audiofile['encodedby'] = meta_tags['publisher']
                    if meta_tags['genre']:
                        audiofile['genre'] = meta_tags['genre']
                    if meta_tags['copyright']:
                        audiofile['copyright'] = meta_tags['copyright']
                    if meta_tags['external_ids']['isrc']:
                        audiofile['isrc'] = meta_tags['external_ids']['isrc']
                    audiofile.save(v2_version=3)

                    # For supported id3 tags:
                    # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py
                    # Each class represents an id3 tag
                    audiofile = ID3(Path + "\\" + Name3[1] + " -" + Name4 + ".mp3")
                    year, *_ = meta_tags['release_date'].split('-')
                    audiofile['TORY'] = TORY(encoding=3, text=year)
                    audiofile['TYER'] = TYER(encoding=3, text=year)
                    audiofile['TPUB'] = TPUB(encoding=3, text=meta_tags['publisher'])
                    audiofile['COMM'] = COMM(encoding=3, text=meta_tags['external_urls']['spotify'])
                    if meta_tags['lyrics']:
                        audiofile['USLT'] = USLT(encoding=3, desc=u'Lyrics', text=meta_tags['lyrics'])
                    try:
                        albumart = urllib.request.urlopen(meta_tags['album']['images'][0]['url'])
                        audiofile['APIC'] = APIC(encoding=3, mime='image/jpeg', type=3,
                                                 desc=u'Cover', data=albumart.read())
                        albumart.close()
                    except IndexError:
                        pass
                    audiofile.save(v2_version=3)
                    self.label_10.setText(_translate("MainWindow", "Concluído."))
                    QtWidgets.QApplication.processEvents()
                    time.sleep(2)  # pausa dramática
                    # Some com os textos:
                    self.label_10.setText(_translate("MainWindow", ""))
                    QtWidgets.QApplication.processEvents()
            QMessageBox.about(self, "Concluído", "Operação concluída.")
Example #11
0
def CopyTagsToTranscodedFileMp3(losslessFile, lossyFile):  
    # Because the input flac file is decoded to wav, all metadata is lost. We have to extract this metadata from 
    # the flac file and put it directly into the generated mp3 file.
    from mutagen.flac import FLAC
    from mutagen.id3 import ID3   
    
    flacFile = FLAC(losslessFile)
    flacFileTags = flacFile.tags 
          
    mp3File = ID3(lossyFile)    
    mp3File.delete()    
        
    for key,value in flacFileTags.items():
        if key == 'title': 
            from mutagen.id3 import TIT2
            mp3File.add(TIT2(encoding=3, text=value)) 
        elif key == 'album': 
            from mutagen.id3 import TALB
            mp3File.add(TALB(encoding=3, text=value))
        elif key == 'artist': 
            from mutagen.id3 import TPE1
            mp3File.add(TPE1(encoding=3, text=value)) 
        elif key == 'tracknumber': 
            from mutagen.id3 import TRCK
            mp3File.add(TRCK(encoding=3, text=value))
        elif key == 'date': 
            from mutagen.id3 import TDRC
            mp3File.add(TDRC(encoding=3, text=value))
        elif key == 'genre': 
            from mutagen.id3 import TCON
            mp3File.add(TCON(encoding=3, text=value))
        elif key == 'discnumber': 
            from mutagen.id3 import TPOS
            mp3File.add(TPOS(encoding=3, text=value))
        elif key == 'composer': 
            from mutagen.id3 import TCOM
            mp3File.add(TCOM(encoding=3, text=value))
        elif key == 'conductor': 
            from mutagen.id3 import TPE3
            mp3File.add(TPE3(encoding=3, text=value))
        elif key == 'ensemble': 
            from mutagen.id3 import TPE2
            mp3File.add(TPE2(encoding=3, text=value))      
        elif key == 'comment': 
            from mutagen.id3 import COMM
            mp3File.add(COMM(encoding=3, text=value))
        elif key == 'publisher': 
            from mutagen.id3 import TPUB
            mp3File.add(TPUB(encoding=3, text=value))
        elif key == 'opus': 
            from mutagen.id3 import TIT3
            mp3File.add(TIT3(encoding=3, text=value))
        elif key == 'sourcemedia': 
            from mutagen.id3 import TMED
            mp3File.add(TMED(encoding=3, text=value))
        elif key == 'isrc': 
            from mutagen.id3 import TSRC
            mp3File.add(TSRC(encoding=3, text=value))
        elif key == 'license': 
            from mutagen.id3 import TOWN
            mp3File.add(TOWN(encoding=3, text=value))
        elif key == 'copyright': 
            from mutagen.id3 import WCOP
            mp3File.add(WCOP(encoding=3, text=value))
        elif key == 'encoded-by': 
            from mutagen.id3 import TENC
            mp3File.add(TENC(encoding=3, text=value))
        elif (key == 'part' or key == 'partnumber'): 
            from mutagen.id3 import TIT3
            mp3File.add(TIT3(encoding=3, text=value))
        elif (key == 'lyricist' or key == 'textwriter'): 
            from mutagen.id3 import TIT3
            mp3File.add(TIT3(encoding=3, text=value))
        else: 
            from mutagen.id3 import TXXX
            mp3File.add(TXXX(encoding=3, text=value, desc=key))        
      
        mp3File.update_to_v24()
        mp3File.save() 
      
    return
Example #12
0
    def metadata_mp3_mutagen(self, path, media):

        from mutagen.mp3 import MP3
        from mutagen.id3 import ID3, TRCK, TIT2, TPE1, TALB, TCON, TXXX, UFID, TSRC, TPUB, TMED, TRCK, TDRC, APIC

        try:
            tags = ID3(path)
        except Exception:
            """
            kindf of hackish - mutagen does complain if no id3 headers - so just create some
            """
            audio = MP3(path)
            audio["TIT2"] = TIT2(encoding=3, text=["Empty Title"])
            audio.save()
            tags = ID3(path)

        # reset tags
        tags.delete()

        # user data
        if INCLUDE_USER and self.user:
            tags.add(
                TXXX(encoding=3,
                     desc='open broadcast user',
                     text=u'%s' % self.user.email))

        # track-level metadata
        tags.add(TIT2(encoding=3, text=u'%s' % media.name))
        tags.add(
            UFID(encoding=3,
                 owner='https://openbroadcast.org',
                 data=u'%s' % media.uuid))

        tags.add(
            TXXX(encoding=3,
                 desc='open broadcast API',
                 text=u'https://%s%s' %
                 (self.current_site.domain, media.get_api_url())))
        # remove genre
        tags.add(TCON(encoding=3, text=u''))
        tags.add(TMED(encoding=3, text=u'Digital Media'))
        if media.tracknumber:
            tags.add(TRCK(encoding=3, text=u'%s' % media.tracknumber))
        if media.isrc:
            tags.add(TSRC(encoding=3, text=u'%s' % media.isrc))

        if uuid_by_object(media, 'musicbrainz'):
            tags.add(
                UFID(encoding=3,
                     owner='http://musicbrainz.org',
                     data=u'%s' % uuid_by_object(media, 'musicbrainz')))

        # release-level metadata
        if media.release:
            tags.add(TALB(encoding=3, text=u'%s' % media.release.name))
            if media.release.catalognumber:
                tags.add(
                    TXXX(encoding=3,
                         desc='CATALOGNUMBER',
                         text=u'%s' % media.release.catalognumber))
            if media.release.releasedate:
                tags.add(
                    TDRC(encoding=3,
                         text=u'%s' % media.release.releasedate.year))
            if media.release.release_country:
                tags.add(
                    TXXX(encoding=3,
                         desc='MusicBrainz Album Release Country',
                         text=u'%s' % media.release.release_country.iso2_code))
            if media.release.totaltracks and media.tracknumber:
                tags.add(
                    TRCK(encoding=3,
                         text=u'%s/%s' %
                         (media.tracknumber, media.release.totaltracks)))
            if media.release.releasedate:
                tags.add(
                    TDRC(encoding=3,
                         text=u'%s' % media.release.releasedate.year))
            if uuid_by_object(media.release, 'musicbrainz'):
                tags.add(
                    TXXX(encoding=3,
                         desc='MusicBrainz Album Id',
                         text=u'%s' %
                         uuid_by_object(media.release, 'musicbrainz')))

            if media.release and media.release.main_image and os.path.exists(
                    media.release.main_image.path):

                opt = dict(size=(300, 300), crop=True, bw=False, quality=80)

                try:
                    image = get_thumbnailer(
                        media.release.main_image).get_thumbnail(opt)
                    tags.add(
                        APIC(encoding=3,
                             mime='image/jpeg',
                             type=3,
                             desc=u'Cover',
                             data=open(image.path).read()))
                except:
                    pass

        # artist-level metadata
        if media.artist:
            tags.add(TPE1(encoding=3, text=u'%s' % media.artist.name))
            if uuid_by_object(media.artist, 'musicbrainz'):
                tags.add(
                    TXXX(encoding=3,
                         desc='MusicBrainz Artist Id',
                         text=u'%s' %
                         uuid_by_object(media.artist, 'musicbrainz')))

        # label-level metadata
        if media.release and media.release.label:
            tags.add(TPUB(encoding=3, text=u'%s' % media.release.label.name))

        tags.save(v1=0)

        return
Example #13
0
def copyTagsToTranscodedFileMp3(losslessFile, lossyFile):
    #
    # Copy the tags from the losslessFile (.flac) to the lossyFile.
    # All previous tags from the lossyFile will be deleted before the
    # tags from the losslessFile are copied.
    #
    from mutagen.flac import FLAC
    from mutagen.id3 import ID3

    # Read all tags from the flac file
    flacFile = FLAC(losslessFile)
    flacFileTags = flacFile.tags  # Returns a dictionary containing the flac tags

    # Only mp3 files with ID3 headers can be openend.
    # So be sure to add some tags during encoding .wav. to mp3

    # Mapping from Vorbis comments field recommendations to id3v2_4_0
    # For more information about vorbis field recommendations: http://reactor-core.org/ogg-tagging.html
    # For more information about id3v2_4_0 frames: http://www.id3.org/id3v2.4.0-frames
    #
    # Single value tags:
    #  ALBUM              -> TALB
    #  ARTIST             -> TPE1
    #  PUBLISHER          -> TPUB
    #  COPYRIGHT          -> WCOP
    #  DISCNUMBER         -> TPOS
    #  ISRC               -> TSRC
    #  EAN/UPN
    #  LABEL
    #  LABELNO
    #  LICENSE             -> TOWN
    #  OPUS                -> TIT3
    #  SOURCEMEDIA         -> TMED
    #  TITLE               -> TIT2
    #  TRACKNUMBER         -> TRCK
    #  VERSION
    #  ENCODED-BY          -> TENC
    #  ENCODING
    # Multiple value tags:
    #  COMPOSER            -> TCOM
    #  ARRANGER
    #  LYRICIST            -> TEXT
    #  AUTHOR              -> TEXT
    #  CONDUCTOR           -> TPE3
    #  PERFORMER           ->
    #  ENSEMBLE            -> TPE2
    #  PART                -> TIT1
    #  PARTNUMBER          -> TIT1
    #  GENRE               -> TCON
    #  DATE                -> TDRC
    #  LOCATION
    #  COMMENT             -> COMM
    # Other vorbis tags are mapped to TXXX tags

    mp3File = ID3(lossyFile)
    mp3File.delete()

    for key, value in flacFileTags.items():
        if key == 'title':
            # Map to TIT2 frame
            from mutagen.id3 import TIT2
            mp3File.add(TIT2(encoding=3, text=value))
        elif key == 'album':
            # Map to TALB frame
            from mutagen.id3 import TALB
            mp3File.add(TALB(encoding=3, text=value))
        elif key == 'artist':
            # Map to TPE1 frame
            from mutagen.id3 import TPE1
            mp3File.add(TPE1(encoding=3, text=value))
        elif key == 'tracknumber':
            # Map to TRCK frame
            from mutagen.id3 import TRCK
            mp3File.add(TRCK(encoding=3, text=value))
        elif key == 'date':
            # Map to TDRC frame
            from mutagen.id3 import TDRC
            mp3File.add(TDRC(encoding=3, text=value))
        elif key == 'genre':
            # Map to TCON frame
            from mutagen.id3 import TCON
            mp3File.add(TCON(encoding=3, text=value))
        elif key == 'discnumber':
            # Map to TPOS frame
            from mutagen.id3 import TPOS
            mp3File.add(TPOS(encoding=3, text=value))
        elif key == 'composer':
            # Map to TCOM frame
            from mutagen.id3 import TCOM
            mp3File.add(TCOM(encoding=3, text=value))
        elif key == 'conductor':
            # Map to TPE3 frame
            from mutagen.id3 import TPE3
            mp3File.add(TPE3(encoding=3, text=value))
        elif key == 'ensemble':
            # Map to TPE2 frame
            from mutagen.id3 import TPE2
            mp3File.add(TPE2(encoding=3, text=value))
        elif key == 'comment':
            # Map to COMM frame
            from mutagen.id3 import COMM
            mp3File.add(COMM(encoding=3, text=value))
        elif key == 'publisher':
            # Map to TPUB frame
            from mutagen.id3 import TPUB
            mp3File.add(TPUB(encoding=3, text=value))
        elif key == 'opus':
            # Map to TIT3 frame
            from mutagen.id3 import TIT3
            mp3File.add(TIT3(encoding=3, text=value))
        elif key == 'sourcemedia':
            # Map to TMED frame
            from mutagen.id3 import TMED
            mp3File.add(TMED(encoding=3, text=value))
        elif key == 'isrc':
            # Map to TSRC frame
            from mutagen.id3 import TSRC
            mp3File.add(TSRC(encoding=3, text=value))
        elif key == 'license':
            # Map to TOWN frame
            from mutagen.id3 import TOWN
            mp3File.add(TOWN(encoding=3, text=value))
        elif key == 'copyright':
            # Map to WCOP frame
            from mutagen.id3 import WCOP
            mp3File.add(WCOP(encoding=3, text=value))
        elif key == 'encoded-by':
            # Map to TENC frame
            from mutagen.id3 import TENC
            mp3File.add(TENC(encoding=3, text=value))
        elif (key == 'part' or key == 'partnumber'):
            # Map to TIT3 frame
            from mutagen.id3 import TIT3
            mp3File.add(TIT3(encoding=3, text=value))
        elif (key == 'lyricist' or key == 'textwriter'):
            # Map to TEXT frame
            from mutagen.id3 import TIT3
            mp3File.add(TIT3(encoding=3, text=value))
        else:  #all other tags are mapped to TXXX frames
            # Map to TXXX frame
            from mutagen.id3 import TXXX
            mp3File.add(TXXX(encoding=3, text=value, desc=key))

        mp3File.update_to_v24()
        mp3File.save()

    return
Example #14
0
def run():
    parser = argparse.ArgumentParser(
        prog='odmpy',
        description='Download/return an Overdrive loan audiobook',
        epilog=
        'Version {version}. [Python {py_major}.{py_minor}.{py_micro}-{platform}] '
        'Source at https://github.com/ping/odmpy/'.format(
            version=__version__,
            py_major=sys.version_info.major,
            py_minor=sys.version_info.minor,
            py_micro=sys.version_info.micro,
            platform=sys.platform,
        ))
    parser.add_argument('-v',
                        '--verbose',
                        dest='verbose',
                        action='store_true',
                        help='Enable more verbose messages for debugging')
    parser.add_argument(
        '-t',
        '--timeout',
        dest='timeout',
        type=int,
        default=10,
        help='Timeout (seconds) for network requests. Default 10.')

    subparsers = parser.add_subparsers(
        title='Available commands',
        dest='subparser_name',
        help='To get more help, use the -h option with the command.')
    parser_info = subparsers.add_parser(
        'info',
        description='Get information about a loan file.',
        help='Get information about a loan file')
    parser_info.add_argument('odm_file', type=str, help='ODM file path')

    parser_dl = subparsers.add_parser('dl',
                                      description='Download from a loan file.',
                                      help='Download from a loan file')
    parser_dl.add_argument('-d',
                           '--downloaddir',
                           dest='download_dir',
                           default='.',
                           help='Download folder path')
    parser_dl.add_argument('-c',
                           '--chapters',
                           dest='add_chapters',
                           action='store_true',
                           help='Add chapter marks (experimental)')
    parser_dl.add_argument(
        '-m',
        '--merge',
        dest='merge_output',
        action='store_true',
        help='Merge into 1 file (experimental, requires ffmpeg)')
    parser_dl.add_argument(
        '--mergeformat',
        dest='merge_format',
        choices=['mp3', 'm4b'],
        default='mp3',
        help='Merged file format (m4b is slow, experimental, requires ffmpeg)')
    parser_dl.add_argument(
        '-k',
        '--keepcover',
        dest='always_keep_cover',
        action='store_true',
        help='Always generate the cover image file (cover.jpg)')
    parser_dl.add_argument('-f',
                           '--keepmp3',
                           dest='keep_mp3',
                           action='store_true',
                           help='Keep downloaded mp3 files (after merging)')
    parser_dl.add_argument('-j',
                           '--writejson',
                           dest='write_json',
                           action='store_true',
                           help='Generate a meta json file (for debugging)')
    parser_dl.add_argument('odm_file', type=str, help='ODM file path')

    parser_ret = subparsers.add_parser('ret',
                                       description='Return a loan file.',
                                       help='Return a loan file.')

    parser_ret.add_argument('odm_file', type=str, help='ODM file path')

    args = parser.parse_args()
    if args.verbose:
        logger.setLevel(logging.DEBUG)

    try:
        # test for odm file
        args.odm_file
    except AttributeError:
        parser.print_help()
        exit(0)

    xml_doc = xml.etree.ElementTree.parse(args.odm_file)
    root = xml_doc.getroot()

    # Return Book
    if args.subparser_name == 'ret':
        logger.info('Returning {} ...'.format(args.odm_file))
        early_return_url = root.find('EarlyReturnURL').text
        try:
            early_return_res = requests.get(early_return_url,
                                            headers={'User-Agent': UA_LONG},
                                            timeout=10)
            early_return_res.raise_for_status()
            logger.info('Loan returned successfully: {}'.format(args.odm_file))
        except HTTPError as he:
            if he.response.status_code == 403:
                logger.warning('Loan is probably already returned.')
                sys.exit()
            logger.error(
                'Unexpected HTTPError while trying to return loan {}'.format(
                    args.odm_file))
            logger.error('HTTPError: {}'.format(str(he)))
            logger.debug(he.response.content)
            sys.exit(1)
        except ConnectionError as ce:
            logger.error('ConnectionError: {}'.format(str(ce)))
            sys.exit(1)

        sys.exit()

    metadata = None
    for t in root.itertext():
        if not t.startswith('<Metadata>'):
            continue
        metadata = xml.etree.ElementTree.fromstring(
            # remove invalid & char
            re.sub(r'\s&\s', ' &amp; ', t))
        break

    debug_meta = {}

    title = metadata.find('Title').text
    cover_url = metadata.find(
        'CoverUrl').text if metadata.find('CoverUrl') != None else ''
    authors = [
        unescape_html(c.text) for c in metadata.find('Creators')
        if 'Author' in c.attrib.get('role', '')
    ]
    if not authors:
        authors = [
            unescape_html(c.text) for c in metadata.find('Creators')
            if 'Editor' in c.attrib.get('role', '')
        ]
    if not authors:
        authors = [unescape_html(c.text) for c in metadata.find('Creators')]
    publisher = metadata.find('Publisher').text
    description = metadata.find('Description').text if metadata.find(
        'Description') is not None else ''

    debug_meta['meta'] = {
        'title': title,
        'coverUrl': cover_url,
        'authors': authors,
        'publisher': publisher,
        'description': description,
    }

    # View Book Info
    if args.subparser_name == 'info':
        logger.info(u'{:10} {}'.format('Title:', colored.blue(title)))
        logger.info(u'{:10} {}'.format(
            'Creators:',
            colored.blue(u', '.join([
                u'{} ({})'.format(c.text, c.attrib['role'])
                for c in metadata.find('Creators')
            ]))))
        logger.info(u'{:10} {}'.format('Publisher:',
                                       metadata.find('Publisher').text))
        logger.info(u'{:10} {}'.format(
            'Subjects:',
            u', '.join([c.text for c in metadata.find('Subjects')])))
        logger.info(u'{:10} {}'.format(
            'Languages:',
            u', '.join([c.text for c in metadata.find('Languages')])))
        logger.info(u'{:10} \n{}'.format('Description:',
                                         metadata.find('Description').text))

        for formats in root.findall('Formats'):
            for f in formats:
                logger.info(u'\n{:10} {}'.format('Format:', f.attrib['name']))
                parts = f.find('Parts')
                for p in parts:
                    logger.info('* {} - {} ({:,.0f}kB)'.format(
                        p.attrib['name'], p.attrib['duration'],
                        math.ceil(1.0 * int(p.attrib['filesize']) / 1024)))
        sys.exit()

    # Download Book
    download_baseurl = ''
    download_parts = []
    for formats in root.findall('Formats'):
        for f in formats:
            protocols = f.find('Protocols')
            for p in protocols:
                if p.attrib.get('method', '') != 'download':
                    continue
                download_baseurl = p.attrib['baseurl']
                break
            parts = f.find('Parts')
            for p in parts:
                download_parts.append(p.attrib)
    debug_meta['download_parts'] = download_parts

    logger.info('Downloading "{}" by "{}" in {} parts...'.format(
        colored.blue(title, bold=True), colored.blue(', '.join(authors)),
        len(download_parts)))

    # declare book folder/file names here together so we can catch problems from too long names
    book_folder = os.path.join(
        args.download_dir,
        u'{} - {}'.format(title.replace(os.sep, '-'),
                          u', '.join(authors).replace(os.sep, '-')))

    # for merged mp3
    book_filename = os.path.join(
        book_folder,
        u'{} - {}.mp3'.format(title.replace(os.sep, '-'),
                              u', '.join(authors).replace(os.sep, '-')))
    # for merged m4b
    book_m4b_filename = os.path.join(
        book_folder,
        u'{} - {}.m4b'.format(title.replace(os.sep, '-'),
                              u', '.join(authors).replace(os.sep, '-')))

    if not os.path.exists(book_folder):
        try:
            os.makedirs(book_folder)
        except OSError as exc:
            if exc.errno not in (
                    36, 63):  # ref http://www.ioplex.com/~miallen/errcmpp.html
                raise

            # Ref OSError: [Errno 36] File name too long https://github.com/ping/odmpy/issues/5
            # create book folder, file with just the title
            book_folder = os.path.join(
                args.download_dir, u'{}'.format(title.replace(os.sep, '-')))
            os.makedirs(book_folder)

            book_filename = os.path.join(
                book_folder, u'{}.mp3'.format(title.replace(os.sep, '-')))
            book_m4b_filename = os.path.join(
                book_folder, u'{}.m4b'.format(title.replace(os.sep, '-')))

    cover_filename = os.path.join(book_folder, 'cover.jpg')
    debug_filename = os.path.join(book_folder, 'debug.json')
    if not os.path.isfile(cover_filename) and cover_url:
        cover_res = requests.get(cover_url, headers={'User-Agent': UA})
        cover_res.raise_for_status()
        with open(cover_filename, 'wb') as outfile:
            outfile.write(cover_res.content)

    acquisition_url = root.find('License').find('AcquisitionUrl').text
    media_id = root.attrib['id']

    client_id = str(uuid.uuid1()).upper()
    raw_hash = '{client_id}|{omc}|{os}|ELOSNOC*AIDEM*EVIRDREVO'.format(
        client_id=client_id, omc=OMC, os=OS)
    m = hashlib.sha1(raw_hash.encode('utf-16-le'))
    license_hash = base64.b64encode(m.digest())

    # Extract license
    # License file is downloadable only once per odm
    # so we keep it in case downloads fail
    _, odm_filename = os.path.split(args.odm_file)
    license_file = os.path.join(args.download_dir,
                                odm_filename.replace('.odm', '.license'))

    if os.path.isfile(license_file):
        logger.warning(
            'Already downloaded license file: {}'.format(license_file))
    else:
        # download license file
        params = OrderedDict([('MediaID', media_id), ('ClientID', client_id),
                              ('OMC', OMC), ('OS', OS),
                              ('Hash', license_hash)])

        license_res = requests.get(acquisition_url,
                                   params=params,
                                   headers={'User-Agent': UA},
                                   timeout=args.timeout,
                                   stream=True)
        try:
            license_res.raise_for_status()
            with open(license_file, 'wb') as outfile:
                for chunk in license_res.iter_content(1024):
                    outfile.write(chunk)
            logger.debug('Saved license file {}'.format(license_file))

        except HTTPError as he:
            if he.response.status_code == 404:
                # odm file has expired
                logger.error('The loan file "{}" has expired.'
                             'Please download again.'.format(args.odm_file))
            else:
                logger.error(he.response.content)
            sys.exit(1)
        except ConnectionError as ce:
            logger.error('ConnectionError: {}'.format(str(ce)))
            sys.exit(1)

    license_xml_doc = xml.etree.ElementTree.parse(license_file)
    license_root = license_xml_doc.getroot()

    ns = '{http://license.overdrive.com/2008/03/License.xsd}'

    license_client = license_root.find('{}SignedInfo'.format(ns)).find(
        '{}ClientID'.format(ns))
    license_client_id = license_client.text

    lic_file_contents = ''
    with open(license_file, 'r') as lic_file:
        lic_file_contents = lic_file.read()

    cover_bytes = None
    if os.path.isfile(cover_filename):
        with open(cover_filename, 'rb') as f:
            cover_bytes = f.read()

    track_count = 0
    file_tracks = []
    keep_cover = args.always_keep_cover
    audio_lengths_ms = []
    for p in download_parts:
        part_number = int(p['number'])
        part_filename = os.path.join(
            book_folder, u'{}.mp3'.format(
                slugify(u'{} - Part {:02d}'.format(title, part_number),
                        allow_unicode=True)))
        part_tmp_filename = u'{}.part'.format(part_filename)
        part_file_size = int(p['filesize'])
        part_url_filename = p['filename']
        part_download_url = '{}/{}'.format(download_baseurl, part_url_filename)
        part_markers = []

        if os.path.isfile(part_filename):
            logger.warning('Already saved {}'.format(
                colored.magenta(part_filename)))
        else:
            try:
                part_download_res = requests.get(part_download_url,
                                                 headers={
                                                     'User-Agent': UA,
                                                     'ClientID':
                                                     license_client_id,
                                                     'License':
                                                     lic_file_contents
                                                 },
                                                 timeout=args.timeout,
                                                 stream=True)

                part_download_res.raise_for_status()

                chunk_size = 1024 * 1024
                expected_chunk_count = math.ceil(1.0 * part_file_size /
                                                 chunk_size)
                with open(part_tmp_filename, 'wb') as outfile:
                    for chunk in progress.bar(
                            part_download_res.iter_content(
                                chunk_size=chunk_size),
                            label='Part {}'.format(part_number),
                            expected_size=expected_chunk_count):
                        if chunk:
                            outfile.write(chunk)

                # try to remux file to remove mp3 lame tag errors
                cmd = [
                    'ffmpeg', '-y', '-nostdin', '-hide_banner', '-loglevel',
                    'info' if logger.level == logging.DEBUG else 'error', '-i',
                    part_tmp_filename, '-c:a', 'copy', '-c:v', 'copy',
                    part_filename
                ]
                try:
                    exit_code = subprocess.call(cmd)
                    if exit_code:
                        logger.warning(
                            'ffmpeg exited with the code: {0!s}'.format(
                                exit_code))
                        logger.warning('Command: {0!s}'.format(' '.join(cmd)))
                        os.rename(part_tmp_filename, part_filename)
                    else:
                        os.remove(part_tmp_filename)
                except Exception as ffmpeg_ex:
                    logger.warning('Error executing ffmpeg: {}'.format(
                        str(ffmpeg_ex)))
                    os.rename(part_tmp_filename, part_filename)

            except HTTPError as he:
                logger.error('HTTPError: {}'.format(str(he)))
                logger.debug(he.response.content)
                sys.exit(1)

            except ConnectionError as ce:
                logger.error('ConnectionError: {}'.format(str(ce)))
                sys.exit(1)

        try:
            # Fill id3 info for mp3 part
            mutagen_audio = MP3(part_filename, ID3=ID3)
            if not mutagen_audio.tags:
                mutagen_audio.tags = ID3()
            if 'TIT2' not in mutagen_audio.tags:
                mutagen_audio.tags.add(
                    TIT2(encoding=3, text=u'{}'.format(title)))
            if 'TIT3' not in mutagen_audio.tags:
                mutagen_audio.tags.add(
                    TIT3(encoding=3, text=u'{}'.format(description)))
            if 'TALB' not in mutagen_audio.tags:
                mutagen_audio.tags.add(
                    TALB(encoding=3, text=u'{}'.format(title)))
            if 'TPE1' not in mutagen_audio.tags:
                mutagen_audio.tags.add(
                    TPE1(encoding=3, text=u'{}'.format(authors[0])))
            if 'TPE2' not in mutagen_audio.tags:
                mutagen_audio.tags.add(
                    TPE2(encoding=3, text=u'{}'.format(authors[0])))
            if 'TRCK' not in mutagen_audio.tags:
                mutagen_audio.tags.add(
                    TRCK(encoding=3, text=u'{}'.format(part_number)))
            if 'TPUB' not in mutagen_audio.tags:
                mutagen_audio.tags.add(
                    TPUB(encoding=3, text=u'{}'.format(publisher)))
            if 'COMM' not in mutagen_audio.tags:
                mutagen_audio.tags.add(
                    COMM(encoding=3,
                         desc=u'Description',
                         text=u'{}'.format(description)))
            if cover_bytes:
                mutagen_audio.tags.add(
                    APIC(encoding=3,
                         mime=u'image/jpeg',
                         type=3,
                         desc=u'Cover',
                         data=cover_bytes))
            mutagen_audio.save()

            audio_lengths_ms.append(
                int(round(mutagen_audio.info.length * 1000)))

            # Extract OD chapter info from mp3s for use in merged file
            if 'TXXX:OverDrive MediaMarkers' in mutagen_audio.tags \
                    and mutagen_audio.tags['TXXX:OverDrive MediaMarkers'].text:
                marker_text = mutagen_audio.tags[
                    'TXXX:OverDrive MediaMarkers'].text[0]
                try:
                    tree = xml.etree.ElementTree.fromstring(marker_text)
                except UnicodeEncodeError:
                    tree = xml.etree.ElementTree.fromstring(
                        marker_text.encode('ascii', 'ignore').decode('ascii'))

                for m in tree.iter('Marker'):
                    marker_name = m.find('Name').text.strip()
                    marker_timestamp = m.find('Time').text
                    timestamp = None
                    ts_mark = 0
                    # 2 timestamp formats found
                    for r in ('%M:%S.%f', '%H:%M:%S.%f'):
                        try:
                            timestamp = time.strptime(marker_timestamp, r)
                            ts = datetime.timedelta(hours=timestamp.tm_hour,
                                                    minutes=timestamp.tm_min,
                                                    seconds=timestamp.tm_sec)
                            ts_mark = int(1000 * ts.total_seconds())
                            break
                        except ValueError:
                            pass

                    if not timestamp:
                        # some invalid timestamp string, e.g. 60:15.00
                        mobj = re.match(MARKER_TIMESTAMP_HHMMSS,
                                        marker_timestamp)
                        if mobj:
                            ts_mark = int(mobj.group('hr')) * 60 * 60 * 1000 + \
                                      int(mobj.group('min')) * 60 * 1000 + \
                                      int(mobj.group('sec')) * 1000 + \
                                      int(mobj.group('ms'))
                        else:
                            mobj = re.match(MARKER_TIMESTAMP_MMSS,
                                            marker_timestamp)
                            if mobj:
                                ts_mark = int(mobj.group('min')) * 60 * 1000 + \
                                          int(mobj.group('sec')) * 1000 + \
                                          int(mobj.group('ms'))
                            else:
                                raise ValueError(
                                    'Invalid marker timestamp: {}'.format(
                                        marker_timestamp))

                    track_count += 1
                    part_markers.append((u'ch{:02d}'.format(track_count),
                                         marker_name, ts_mark))

            if args.add_chapters and not args.merge_output:
                # set the chapter marks
                generated_markers = []
                for j, file_marker in enumerate(part_markers):
                    generated_markers.append({
                        'id':
                        file_marker[0],
                        'text':
                        file_marker[1],
                        'start_time':
                        int(file_marker[2]),
                        'end_time':
                        int(
                            round(mutagen_audio.info.length *
                                  1000) if j == (len(part_markers) -
                                                 1) else part_markers[j +
                                                                      1][2]),
                    })

                mutagen_audio.tags.add(
                    CTOC(element_id=u'toc',
                         flags=CTOCFlags.TOP_LEVEL | CTOCFlags.ORDERED,
                         child_element_ids=[
                             m['id'].encode('ascii') for m in generated_markers
                         ],
                         sub_frames=[TIT2(text=[u'Table of Contents'])]))
                for i, m in enumerate(generated_markers):
                    mutagen_audio.tags.add(
                        CHAP(element_id=m['id'].encode('ascii'),
                             start_time=m['start_time'],
                             end_time=m['end_time'],
                             sub_frames=[TIT2(text=[u'{}'.format(m['text'])])
                                         ]))
                    start_time = datetime.timedelta(
                        milliseconds=m['start_time'])
                    end_time = datetime.timedelta(milliseconds=m['end_time'])
                    logger.debug(
                        u'Added chap tag => {}: {}-{} "{}" to "{}"'.format(
                            colored.cyan(m['id']), start_time, end_time,
                            colored.cyan(m['text']),
                            colored.blue(part_filename)))

                if len(generated_markers) == 1:
                    # Weird player problem on voice where title is shown instead of chapter title
                    mutagen_audio.tags.add(
                        TIT2(encoding=3, text=u'{}'.format(title)))

                mutagen_audio.save()

        except Exception as e:
            logger.warning('Error saving ID3: {}'.format(
                colored.red(str(e), bold=True)))
            keep_cover = True

        logger.info('Saved "{}"'.format(colored.magenta(part_filename)))

        file_tracks.append({
            'file': part_filename,
            'markers': part_markers,
        })
    # end loop: for p in download_parts:

    debug_meta['audio_lengths_ms'] = audio_lengths_ms
    debug_meta['file_tracks'] = file_tracks

    if args.merge_output:
        if os.path.isfile(book_filename if args.merge_format ==
                          'mp3' else book_m4b_filename):
            logger.warning('Already saved "{}"'.format(
                colored.magenta(book_filename if args.merge_format ==
                                'mp3' else book_m4b_filename)))
            sys.exit(0)

        logger.info('Generating "{}"...'.format(
            colored.magenta(book_filename if args.merge_format ==
                            'mp3' else book_m4b_filename)))

        # We can't directly generate a m4b here even if specified because eyed3 doesn't support m4b/mp4
        temp_book_filename = '{}.part'.format(book_filename)
        cmd = [
            'ffmpeg',
            '-y',
            '-nostdin',
            '-hide_banner',
            '-loglevel',
            'info' if logger.level == logging.DEBUG else 'error',
            '-stats',
            '-i',
            'concat:{}'.format('|'.join([ft['file'] for ft in file_tracks])),
            '-acodec',
            'copy',
            '-b:a',
            '64k',  # explicitly set audio bitrate
            '-f',
            'mp3',
            temp_book_filename
        ]
        exit_code = subprocess.call(cmd)

        if exit_code:
            logger.error(
                'ffmpeg exited with the code: {0!s}'.format(exit_code))
            logger.error('Command: {0!s}'.format(' '.join(cmd)))
            exit(exit_code)
        os.rename(temp_book_filename, book_filename)

        mutagen_audio = MP3(book_filename, ID3=ID3)
        if not mutagen_audio.tags:
            mutagen_audio.tags = ID3()
        # Overwrite title since it gets picked up from the first track by default
        mutagen_audio.tags.add(TIT2(encoding=3, text=u'{}'.format(title)))
        if 'TALB' not in mutagen_audio.tags:
            mutagen_audio.tags.add(TALB(encoding=3, text=u'{}'.format(title)))
        if 'TPE1' not in mutagen_audio.tags:
            mutagen_audio.tags.add(
                TPE1(encoding=3, text=u'{}'.format(authors[0])))
        if 'TPE2' not in mutagen_audio.tags:
            mutagen_audio.tags.add(
                TPE2(encoding=3, text=u'{}'.format(authors[0])))
        if 'TPUB' not in mutagen_audio.tags:
            mutagen_audio.tags.add(
                TPUB(encoding=3, text=u'{}'.format(publisher)))
        if 'COMM' not in mutagen_audio.tags:
            mutagen_audio.tags.add(
                COMM(encoding=3,
                     desc=u'Description',
                     text=u'{}'.format(description)))
        mutagen_audio.save()

        if args.add_chapters:
            merged_markers = []
            for i, f in enumerate(file_tracks):
                prev_tracks_len_ms = 0 if i == 0 else reduce(
                    lambda x, y: x + y, audio_lengths_ms[0:i])
                this_track_endtime_ms = int(
                    reduce(lambda x, y: x + y, audio_lengths_ms[0:i + 1]))
                file_markers = f['markers']
                for j, file_marker in enumerate(file_markers):
                    merged_markers.append({
                        'id':
                        file_marker[0],
                        'text':
                        u'{}'.format(file_marker[1]),
                        'start_time':
                        int(file_marker[2]) + prev_tracks_len_ms,
                        'end_time':
                        int(this_track_endtime_ms if j == (
                            len(file_markers) -
                            1) else file_markers[j + 1][2] +
                            prev_tracks_len_ms),
                    })
            debug_meta['merged_markers'] = merged_markers

            mutagen_audio.tags.add(
                CTOC(element_id=u'toc',
                     flags=CTOCFlags.TOP_LEVEL | CTOCFlags.ORDERED,
                     child_element_ids=[
                         m['id'].encode('ascii') for m in merged_markers
                     ],
                     sub_frames=[TIT2(text=[u"Table of Contents"])]))
            for i, m in enumerate(merged_markers):
                mutagen_audio.tags.add(
                    CHAP(element_id=m['id'].encode('ascii'),
                         start_time=m['start_time'],
                         end_time=m['end_time'],
                         sub_frames=[TIT2(text=[u'{}'.format(m['text'])])]))
                start_time = datetime.timedelta(milliseconds=m['start_time'])
                end_time = datetime.timedelta(milliseconds=m['end_time'])
                logger.debug(
                    u'Added chap tag => {}: {}-{} "{}" to "{}"'.format(
                        colored.cyan(m['id']), start_time, end_time,
                        colored.cyan(m['text']), colored.blue(book_filename)))

        mutagen_audio.save()

        if args.merge_format == 'mp3':
            logger.info('Merged files into "{}"'.format(
                colored.magenta(book_filename if args.merge_format ==
                                'mp3' else book_m4b_filename)))

        if args.merge_format == 'm4b':
            temp_book_m4b_filename = '{}.part'.format(book_m4b_filename)
            cmd = [
                'ffmpeg',
                '-y',
                '-nostdin',
                '-hide_banner',
                '-loglevel',
                'info' if logger.level == logging.DEBUG else 'error',
                '-stats',
                '-i',
                book_filename,
            ]
            if os.path.isfile(cover_filename):
                cmd.extend(['-i', cover_filename])

            cmd.extend([
                '-map',
                '0:a',
                '-c:a',
                'aac',
                '-b:a',
                '64k',  # explicitly set audio bitrate
            ])
            if os.path.isfile(cover_filename):
                cmd.extend([
                    '-map',
                    '1:v',
                    '-c:v',
                    'copy',
                    '-disposition:v:0',
                    'attached_pic',
                ])

            cmd.extend(['-f', 'mp4', temp_book_m4b_filename])
            exit_code = subprocess.call(cmd)

            if exit_code:
                logger.error(
                    'ffmpeg exited with the code: {0!s}'.format(exit_code))
                logger.error('Command: {0!s}'.format(' '.join(cmd)))
                exit(exit_code)

            os.rename(temp_book_m4b_filename, book_m4b_filename)
            logger.info('Merged files into "{}"'.format(
                colored.magenta(book_m4b_filename)))
            try:
                os.remove(book_filename)
            except Exception as e:
                logger.warning('Error deleting "{}": {}'.format(
                    book_filename, str(e)))

        if not args.keep_mp3:
            for f in file_tracks:
                try:
                    os.remove(f['file'])
                except Exception as e:
                    logger.warning('Error deleting "{}": {}'.format(
                        f['file'], str(e)))

    if not keep_cover:
        try:
            os.remove(cover_filename)
        except Exception as e:
            logger.warning('Error deleting "{}": {}'.format(
                cover_filename, str(e)))

    if args.write_json:
        with open(debug_filename, 'w') as outfile:
            json.dump(debug_meta, outfile, indent=2)
Example #15
0
    def update_id3(self, path: str, track: beatport.Track):
        #AIFF Check
        aiff = None
        if path.endswith('.aiff') or path.endswith('.aif'):
            aiff = AIFF(path)
            f = aiff.tags
        else:
            f = ID3()
            f.load(path, v2_version=3, translate=True)

        #Update tags
        if UpdatableTags.title in self.config.update_tags and self.config.overwrite:
            f.setall('TIT2', [TIT2(text=track.title)])
        if UpdatableTags.artist in self.config.update_tags and self.config.overwrite:
            f.setall('TPE1', [
                TPE1(text=self.config.artist_separator.join(
                    [a.name for a in track.artists]))
            ])
        if UpdatableTags.album in self.config.update_tags and (
                self.config.overwrite or len(f.getall('TALB')) == 0):
            f.setall('TALB', [TALB(text=track.album.name)])
        if UpdatableTags.label in self.config.update_tags and (
                self.config.overwrite or len(f.getall('TPUB')) == 0):
            f.setall('TPUB', [TPUB(text=track.label.name)])
        if UpdatableTags.bpm in self.config.update_tags and (
                self.config.overwrite or len(f.getall('TBPM')) == 0):
            f.setall('TBPM', [TBPM(text=str(track.bpm))])
        if UpdatableTags.genre in self.config.update_tags and (
                self.config.overwrite or len(f.getall('TCON')) == 0):
            f.setall('TCON',
                     [TCON(text=', '.join([g.name for g in track.genres]))])

        #Dates
        if UpdatableTags.date in self.config.update_tags:
            #ID3 v2.3
            if self.config.id3v23 and (self.config.overwrite or
                                       (len(f.getall('TYER')) == 0
                                        and len(f.getall('TDAT')) == 0)):
                date = track.release_date.strftime('%d%m')
                f.setall('TDRC', [])
                f.setall('TDAT', [TDAT(text=date)])
                f.setall('TYER', [TYER(text=str(track.release_date.year))])
            #ID3 v2.4
            if not self.config.id3v23 and (self.config.overwrite
                                           or len(f.getall('TDRC')) == 0):
                date = track.release_date.strftime('%Y-%m-%d')
                f.setall('TDAT', [])
                f.setall('TYER', [])
                f.setall('TDRC', [TDRC(text=date)])

        if UpdatableTags.key in self.config.update_tags and (
                self.config.overwrite or len(f.getall('TKEY')) == 0):
            f.setall('TKEY', [TKEY(text=track.id3key())])
        if UpdatableTags.publishdate in self.config.update_tags and (
                self.config.overwrite or len(f.getall('TDRL')) == 0):
            # f.setall('TORY', [TORY(text=str(track.publish_date.year))])
            if not self.config.id3v23:
                date = track.publish_date.strftime('%Y-%m-%d')
                f.setall('TDRL', [TDRL(text=date)])

        #Other keys
        if UpdatableTags.other in self.config.update_tags:
            f.add(TXXX(desc='WWWAUDIOFILE', text=track.url()))
            f.add(TXXX(desc='WWWPUBLISHER', text=track.label.url('label')))

        #Redownlaod cover
        if self.config.replace_art:
            try:
                url = track.art(self.config.art_resolution)
                r = requests.get(url)
                data = APIC(encoding=3,
                            mime='image/jpeg',
                            type=3,
                            desc=u'Cover',
                            data=r.content)
                f.delall('APIC')
                f['APIC:cover.jpg'] = data

            except Exception:
                logging.warning('Error downloading cover for file: ' + path)

        if aiff == None:
            if self.config.id3v23:
                f.save(path, v2_version=3, v1=0)
            else:
                f.save(path, v2_version=4, v1=0)
        else:
            aiff.save()
Example #16
0
from segevmusic.applemusic import AMSong
from mutagen.id3 import ID3, TXXX, TIT2, TPE1, TALB, TPE2, TCON, TPUB, TSRC, APIC, TCOP, TDRC, TRCK, TPOS
from os import replace
from os.path import realpath, join
from typing import List

TAGS = {
    "song_name":
    lambda amsong: TIT2(text=amsong.name),
    "album_name":
    lambda amsong: TALB(text=amsong.album_name),
    "isrc":
    lambda amsong: TSRC(text=amsong.isrc),
    "record_label":
    lambda amsong: TPUB(text=amsong.album.record_label)
    if amsong.album.record_label else None,
    "copyright":
    lambda amsong: TCOP(text=amsong.album.copyright),
    "genre":
    lambda amsong: TCON(text=amsong.genres[0]),
    "album_artist":
    lambda amsong: TPE2(text=amsong.album.artist_name),
    "song_artist":
    lambda amsong: TPE1(text=amsong.artist_name),
    "itunes_advisory":
    lambda amsong: TXXX(desc="ITUNESADVISORY", text="1")
    if amsong.is_explicit else None,
    "release_date":
    lambda amsong: TDRC(text=amsong.release_date),
    "artwork":
    lambda amsong: APIC(mime='image/jpeg',
Example #17
0
    def save_mp3_metadata(self, file_path, data):
        """ Saves the given metadata for an MP3 file.

		Metadata.save_mp3_metadata(str, dict)
		"""
        audio = ID3(file_path)  # Writing MP3 tags needs ID3
        mp3_audio, format = self.get_mutagen_parser(file_path)

        if data.get("title"):
            audio.add(TIT2(encoding=3, text=unicode(data['title'])))
        if data.get("artist"):
            audio.add(TPE1(encoding=3, text=unicode(data['artist'])))
        if data.get("album"):
            audio.add(TALB(encoding=3, text=unicode(data['album'])))
        if data.get("genre"):
            audio.add(TCON(encoding=3, text=unicode(data['genre'])))
        if data.get("year"):
            audio.add(TDRC(encoding=3, text=unicode(data['year'])))
        if data.get("album_artist"):
            audio.add(TPE2(encoding=3, text=unicode(data['album_artist'])))

        if data.get("track_number", False) and data.get("total_tracks", False):
            audio.add(
                TRCK(encoding=3,
                     text=unicode(data['track_number'] + '/' +
                                  data['total_tracks'])))
        elif data.get("track_number"):
            total_tracks = self.get_total_tracks(mp3_audio, format)
            if total_tracks == None:
                audio.add(TRCK(encoding=3, text=unicode(data['track_number'])))
            else:
                audio.add(
                    TRCK(encoding=3,
                         text=unicode(data['track_number'] + '/' +
                                      total_tracks)))
        elif data.get("total_tracks"):
            t_no = self.get_track_number(mp3_audio, format)
            if t_no == None:
                audio.add(
                    TRCK(encoding=3,
                         text=unicode(" /" + data["total_tracks"])))
            else:
                audio.add(
                    TRCK(encoding=3,
                         text=unicode(t_no + '/' + data["total_tracks"])))

        if data.get("disc_number", False) and data.get("total_discs", False):
            audio.add(
                TPOS(encoding=3,
                     text=unicode(data['disc_number'] + '/' +
                                  data['total_discs'])))
        elif data.get("disc_number"):
            total_discs = self.get_total_discs(mp3_audio, format)
            if total_discs == None:
                audio.add(TPOS(encoding=3, text=unicode(data['disc_number'])))
            else:
                audio.add(
                    TPOS(encoding=3,
                         text=unicode(data['disc_number'] + '/' +
                                      total_discs)))
        elif data.get("total_discs"):
            d_no = self.get_disc_number(mp3_audio, format)
            if d_no == None:
                audio.add(
                    TPOS(encoding=3, text=unicode(" /" + data["total_discs"])))
            else:
                audio.add(
                    TPOS(encoding=3,
                         text=unicode(d_no + '/' + data["total_discs"])))

        if data.get("composer"):
            audio.add(TCOM(encoding=3, text=unicode(data['composer'])))
        if data.get("publisher"):
            audio.add(TPUB(encoding=3, text=unicode(data['publisher'])))
        if data.get("comments"):
            audio.add(
                COMM(encoding=3,
                     lang="eng",
                     desc="",
                     text=unicode(data['comments'])))
        audio.save()
Example #18
0
    def as_mp3(self):
        """ Embed metadata to MP3 files. """
        music_file = self.music_file
        meta_tags = self.meta_tags
        # EasyID3 is fun to use ;)
        # For supported easyid3 tags:
        # https://github.com/quodlibet/mutagen/blob/master/mutagen/easyid3.py
        # Check out somewhere at end of above linked file
        audiofile = EasyID3(music_file)
        audiofile['artist'] = meta_tags['artists'][0]['name']
        audiofile['albumartist'] = meta_tags['artists'][0]['name']
        audiofile['album'] = meta_tags['album']['name']
        audiofile['title'] = meta_tags['name']
        audiofile['tracknumber'] = [
            meta_tags['track_number'], meta_tags['total_tracks']
        ]
        audiofile['discnumber'] = [meta_tags['disc_number'], 0]
        audiofile['date'] = meta_tags['release_date']
        audiofile['originaldate'] = meta_tags['release_date']
        audiofile['media'] = meta_tags['type']
        audiofile['author'] = meta_tags['artists'][0]['name']
        audiofile['lyricist'] = meta_tags['artists'][0]['name']
        audiofile['arranger'] = meta_tags['artists'][0]['name']
        audiofile['performer'] = meta_tags['artists'][0]['name']
        audiofile['website'] = meta_tags['external_urls']['spotify']
        audiofile['length'] = str(meta_tags['duration'])
        if meta_tags['publisher']:
            audiofile['encodedby'] = meta_tags['publisher']
        if meta_tags['genre']:
            audiofile['genre'] = meta_tags['genre']
        if meta_tags['copyright']:
            audiofile['copyright'] = meta_tags['copyright']
        if meta_tags['external_ids']['isrc']:
            audiofile['isrc'] = meta_tags['external_ids']['isrc']
        audiofile.save(v2_version=3)

        # For supported id3 tags:
        # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py
        # Each class represents an id3 tag
        audiofile = ID3(music_file)
        audiofile['TORY'] = TORY(encoding=3, text=meta_tags['year'])
        audiofile['TYER'] = TYER(encoding=3, text=meta_tags['year'])
        audiofile['TPUB'] = TPUB(encoding=3, text=meta_tags['publisher'])
        audiofile['COMM'] = COMM(encoding=3,
                                 text=meta_tags['external_urls']['spotify'])
        if meta_tags['lyrics']:
            audiofile['USLT'] = USLT(encoding=3,
                                     desc=u'Lyrics',
                                     text=meta_tags['lyrics'])
        try:
            albumart = urllib.request.urlopen(
                meta_tags['album']['images'][0]['url'])
            audiofile['APIC'] = APIC(encoding=3,
                                     mime='image/jpeg',
                                     type=3,
                                     desc=u'Cover',
                                     data=albumart.read())
            albumart.close()
        except IndexError:
            pass

        audiofile.save(v2_version=3)
        return True
    fd.tags[u'ARTIST'] = u"Waatea"
    fd.tags[u'ORGANIZATION'] = u"*** NEWS ***"
    fd.tags[u'LABEL'] = u"*** NEWS *** Updated-%s" % (
        datetime.now().strftime('%H:%M-%d-%m-%Y'))
    fd.tags[u'PUBLISHER'] = u"*** NEWS ***"
    fd.tags[u'UFID'] = u"1840-WAATEA-NEWS-%02d%s-MP3" % (hour, ampm.upper())
    fd.tags[u'OWNER'] = u"admin"
    fd.tags[u'LENGTH'] = u"%d:%02d.%d" % (min, sec, hund)
    fd.tags[u'TLEN'] = u"%d:%02d.%d" % (min, sec, hund)
    retval = fd.save()
    print(retval)
    print(fd.tags)

    from mutagen.id3 import ID3, TPUB
    audio = ID3(f_name)
    audio.add(TPUB(encoding=3, text=u"*** NEWS ***"))
    audio.save()

    f_name_abs = os.path.join(file_path, f_name)
    os.rename(f_name, f_name_abs)

    cmd = 'sudo chown www-data {0}'.format(f_name_abs)
    p = subprocess.Popen(cmd.split(' '))
    p.communicate()

    cmd = 'sudo chgrp www-data {0}'.format(f_name_abs)
    p = subprocess.Popen(cmd.split(' '))
    p.communicate()

    td = (datetime.now() - start_time)
    print('elapsed time = %s' % (td.seconds))