def _set_id3_opus(converted_file_path: str, song_object: SongObject): audio_file = OggOpus(converted_file_path) audio_file = _embed_basic_metadata(audio_file, song_object, "opus") audio_file = _embed_ogg_metadata(audio_file, song_object) audio_file = _embed_cover(audio_file, song_object, "opus") audio_file.save()
def _set_id3_opus(converted_file_path, song_obj): audio_file = OggOpus(converted_file_path) audio_file = _embed_basic_metadata(audio_file, song_obj, "opus") audio_file = _embed_ogg_metadata(audio_file, song_obj) audio_file = _embed_cover(audio_file, song_obj, "opus") audio_file.save()
def as_opus(self, path, metadata, cached_albumart=None): logger.debug('Writing Opus metadata to "{path}".'.format(path=path)) audiofile = OggOpus(path) self._embed_basic_metadata(audiofile, metadata, "opus") self._embed_ogg_metadata(audiofile, metadata) self._embed_mbp_picture(audiofile, metadata, cached_albumart, "opus") audiofile.save()
def encodeOpus(self, wavf, dstf, cover, meta): # TODO: bitrate 160/128 FNULL=open(os.devnull, 'w') args = ['opusenc', '--bitrate', '192', '--quiet' ] if cover: args.append('--picture') args.append(cover) args.append(wavf) args.append(dstf) subprocess.call(args, stdout=FNULL, stderr=FNULL) FNULL.close() # tag the file opus = OggOpus(dstf) # no need to save r128_track_gain for c in sorted(meta.keys()): opus[c] = meta[c] opus.save()
def clean(self): file = self.cleaned_data.get('file', False) acceptedMime = ['audio/mpeg', 'audio/mpeg3', 'audio/x-mpeg-3', 'audio/ogg', 'application/ogg', 'audio/x-ogg', 'application/x-ogg', 'video/ogg', 'audio/wav', 'audio/x-wav', 'audio/wave', 'audio/x-pn-wav'] type = "none" if file: if file._size > 10*1024*1024: raise forms.ValidationError(file.name + ': File too large. Max Limit: 10MB') elif not file.content_type in acceptedMime: raise forms.ValidationError(file.name + ': It is not a mp3, ogg, or wav file') try: OggVorbis(file.temporary_file_path()) type = 'OGG' except: pass try: OggFLAC(file.temporary_file_path()) type = 'OGG' except: pass try: OggOpus(file.temporary_file_path()) type = 'OGG' except: pass try: OggSpeex(file.temporary_file_path()) type = 'OGG' except: pass try: OggTheora(file.temporary_file_path()) type = 'OGG' except: pass try: MP3(file.temporary_file_path()) type = 'MP3' except: pass try: WavPack(file.temporary_file_path()) type = 'WAV' except: pass if not type in ['OGG', 'MP3', 'WAV']: raise forms.ValidationError(file.name + ': Unsupported file type') return file else: raise forms.ValidationError(file.name + ': File cannot be opened')
def add_opus(self, path='', filename='file.opus', artist='', album='', title='', tracknum=None, year=None, group='', conductor='', composer='', basefile='silence.opus', apply_tags=True): """ Adds a new ogg opus file with the given parameters to our library. Pass in ``False`` for ``apply_tags`` to only use whatever tags happen to be present in the source basefile. """ full_filename = self.add_file(basefile, filename, path=path) # Finish here if we've been told to. if not apply_tags: return # Apply the tags as specified tags = OggOpus(full_filename) tags['ARTIST'] = artist tags['ALBUM'] = album tags['TITLE'] = title if group != '': tags['ENSEMBLE'] = group if conductor != '': tags['CONDUCTOR'] = conductor if composer != '': tags['COMPOSER'] = composer if tracknum is not None: tags['TRACKNUMBER'] = str(tracknum) if year is not None: tags['DATE'] = str(year) # Save to our filename tags.save()
def write_to_file(music_file): if music_file.audioformat == AudioFormat.OGGOPUS: audio = OggOpus(music_file.file_path) write_to_audio_dict.write(audio, music_file) elif music_file.audioformat == AudioFormat.OGGVORBIS: audio = OggVorbis(music_file.file_path) write_to_audio_dict.write(audio, music_file) else: logging.warning("Neither ogg vorbis nor opus. Can't write tags")
def set_vorbis_data(self, convertedFilePath, songObj): # embed song details audioFile = OggOpus(convertedFilePath) # ! Get rid of all existing ID3 tags (if any exist) audioFile.delete() # ! song name audioFile['title'] = songObj.get_song_name() audioFile['titlesort'] = songObj.get_song_name() # ! track number audioFile['tracknumber'] = str(songObj.get_track_number()) # ! genres (pretty pointless if you ask me) # ! we only apply the first available genre as ID3 v2.3 doesn't support multiple # ! genres and ~80% of the world PC's run Windows - an OS with no ID3 v2.4 support genres = songObj.get_genres() if len(genres) > 0: audioFile['genre'] = genres[0] # ! all involved artists audioFile['artist'] = songObj.get_contributing_artists() # ! album name audioFile['album'] = songObj.get_album_name() # ! album artist (all of 'em) audioFile['albumartist'] = songObj.get_album_artists() # ! album release date (to what ever precision available) audioFile['date'] = songObj.get_album_release() audioFile['originaldate'] = songObj.get_album_release() audioFile.save() # ! setting the album art audioFile = OggOpus(convertedFilePath) rawAlbumArt = urlopen(songObj.get_album_cover_url()).read() AlbumCover = Picture() AlbumCover.mime=u'image/jpeg' AlbumCover.type=3 AlbumCover.desc=u'Cover' AlbumCover.data=rawAlbumArt rawAlbumCover = AlbumCover.write() encodedAlbumArt = base64.b64encode(rawAlbumCover) vcommentAlbumArt = encodedAlbumArt.decode("ascii") audioFile['metadata_block_picture'] = [vcommentAlbumArt] audioFile.save()
def write_cover_opus(afile, cover): file_ = OggOpus(afile) with open(cover, "rb") as h: data = h.read() picture = Picture() picture.data = data picture.type = 17 picture.desc = u"cover art" picture.mime = u"image/" + os.path.splitext(cover)[1][1:] dim = [int(x) for x in COVER_DIMENSION_INTERN.split("x")] picture.width = dim[0] picture.height = dim[1] picture.depth = 24 picture_data = picture.write() encoded_data = base64.b64encode(picture_data) vcomment_value = encoded_data.decode("ascii") file_["metadata_block_picture"] = [vcomment_value] file_.save()
def test_preserve_non_padding(self): self.audio["FOO"] = ["BAR"] self.audio.save() extra_data = b"\xde\xad\xbe\xef" with open(self.filename, "r+b") as fobj: OggPage(fobj) # header page = OggPage(fobj) data = OggPage.to_packets([page])[0] data = data.rstrip(b"\x00") + b"\x01" + extra_data new_pages = OggPage.from_packets([data], page.sequence) OggPage.replace(fobj, [page], new_pages) OggOpus(self.filename).save() with open(self.filename, "rb") as fobj: OggPage(fobj) # header page = OggPage(fobj) data = OggPage.to_packets([page])[0] self.assertTrue(data.endswith(b"\x01" + extra_data)) self.assertEqual(OggOpus(self.filename).tags._padding, 0)
def isAMusicFile(filePath): type = "none" try: OggVorbis(filePath) type = 'OGG' except: pass try: OggFLAC(filePath) type = 'OGG' except: pass try: OggOpus(filePath) type = 'OGG' except: pass try: OggSpeex(filePath) type = 'OGG' except: pass try: OggTheora(filePath) type = 'OGG' except: pass try: MP3(filePath) type = 'MP3' except: pass try: WavPack(filePath) type = 'WAV' except: pass if not type in ['OGG', 'MP3', 'WAV']: return False return True
def test_combine_returns_opus_with_metadata(self, processor, opus_audio_asset, tmpdir): essence = opus_audio_asset.essence metadata = dict(ffmetadata=dict(artist='Frédéric Chopin')) essence_with_metadata = processor.combine(essence, metadata) essence_file = tmpdir.join('essence_with_metadata') essence_file.write(essence_with_metadata.read(), 'wb') ogg = OggOpus(str(essence_file)) for key, actual in metadata['ffmetadata'].items(): expected = ogg.tags.get(key) if isinstance(expected, list): assert actual in expected else: assert ogg.tags[key] == actual
def get_ogg_or_flac(path): from mutagen.oggflac import OggFLAC from mutagen.oggopus import OggOpus from mutagen.oggvorbis import OggVorbis from mutagen.flac import FLAC try: return OggVorbis(path) except: pass try: return FLAC(path) except: pass try: return OggFLAC(path) except: pass try: return OggOpus(path) except: pass return None
def get_tags(self): file_extension = self.audio_filepath[self.audio_filepath.rindex('.'):] if file_extension == '.flac': reader = FLAC(self.audio_filepath) tags, info = reader.tags, reader.info self.title = tags.get('TITLE', ['UNKNOWN'])[0] self.artist = tags.get('ARTIST', ['UNKNOWN'])[0] self.album = tags.get('ALBUM', ['UNKNOWN'])[0] self.duration = float(info.length) elif file_extension == '.opus': reader = OggOpus(self.audio_filepath) tags, info = reader.tags, reader.info self.title = tags.get('TITLE', ['UNKNOWN'])[0] self.artist = tags.get('ARTIST', ['UNKNOWN'])[0] self.album = tags.get('ALBUM', ['UNKNOWN'])[0] self.duration = float(info.length) elif file_extension == '.mp3': reader = MP3(self.audio_filepath) tags, info = reader.tags, reader.info self.title = tags.get('TIT2', ['UNKNOWN'])[0] self.artist = tags.get('TPE1', ['UNKNOWN'])[0] self.album = tags.get('TALB', ['UNKNOWN'])[0] self.duration = float(info.length) elif file_extension == '.m4a': reader = MP4(self.audio_filepath) tags, info = reader.tags, reader.info self.title = tags.get('\xa9nam', ['UNKNOWN'])[0] self.artist = tags.get('\xa9ART', ['UNKNOWN'])[0] self.album = tags.get('\xa9alb', ['UNKNOWN'])[0] self.duration = float(info.length) else: return
def _get_opus_tags(track, path): """ Tries to load embedded tags from given file. :return: TrackData object """ track_data = TrackData(path) log.debug("Importing ogg " + track.path) try: track.mutagen = OggOpus(path) except Exception as e: log.warning( "Track " + track.path + " has no valid tags. Now guessing from file and folder name…") return track_data track_data.disk = int(__get_common_disk_number(track)) track_data.length = float(__get_common_track_length(track)) track_data.cover = __get_ogg_cover(track) track_data.author = __get_common_tag(track, "composer") track_data.reader = __get_common_tag(track, "artist") track_data.book_name = __get_common_tag(track, "album") track_data.name = __get_common_tag(track, "title") return track_data
def isSongValid(file): acceptedMime = [ 'audio/mp3', 'audio/mpeg', 'audio/mpeg3', 'audio/x-mpeg-3', 'audio/ogg', 'application/ogg', 'audio/x-ogg', 'application/x-ogg', 'video/ogg', 'audio/wav', 'audio/x-wav', 'audio/wave', 'audio/x-pn-wav' ] type = "none" if file: if file._size > 10 * 1024 * 1024: return ('File too large. Max Limit: 10MB') elif not file.content_type in acceptedMime: if settings.DEBUG: return ('MIMETYPE: ' + file.content_type + ' Not a mp3, ogg, or wav file') else: logger.error('MIMETYPE: ' + file.content_type + ' Not a mp3, ogg, or wav file') return ('It is not a mp3, ogg, or wav file') try: OggVorbis(file.temporary_file_path()) type = 'OGG' except: pass try: OggFLAC(file.temporary_file_path()) type = 'OGG' except: pass try: OggOpus(file.temporary_file_path()) type = 'OGG' except: pass try: OggSpeex(file.temporary_file_path()) type = 'OGG' except: pass try: OggTheora(file.temporary_file_path()) type = 'OGG' except: pass try: MP3(file.temporary_file_path()) type = 'MP3' except: pass try: WavPack(file.temporary_file_path()) type = 'WAV' except: pass if not type in ['OGG', 'MP3', 'WAV']: return ('Unsupported file type') return True else: return ('File cannot be opened')
def detect_file_codec(path): """ Try to detect codec for file """ if not os.path.isfile(path): return None name, extension = os.path.splitext(path) if extension in ('.m4a', 'caf'): from mutagen.mp4 import MP4 try: codec = MP4(path).info.codec if codec == 'alac': return codec elif codec[:4] == 'mp4a': return 'aac' except Exception as e: raise ValueError('Error detecting file {} codec: {}'.format( path, e)) if extension == '.aif': from mutagen.aiff import AIFF try: AIFF(path).info return 'aif' except Exception as e: raise ValueError('Error opening {} as aif: {}'.format(path, e)) if extension == '.flac': from mutagen.flac import FLAC try: FLAC(path).info return 'flac' except Exception as e: raise ValueError('Error opening {} as flac: {}'.format(path, e)) if extension == '.mp3': from mutagen.mp3 import MP3 try: MP3(path).info return 'mp3' except Exception as e: raise ValueError('Error opening {} as mp3: {}'.format(path, e)) if extension == '.opus': from mutagen.oggopus import OggOpus try: OggOpus(path).info return 'opus' except Exception as e: raise ValueError('Error opening {} as mp3: {}'.format(path, e)) if extension == '.ogg': from mutagen.oggvorbis import OggVorbis try: OggVorbis(path).info return 'vorbis' except Exception as e: raise ValueError('Error opening {} as ogg vorbis: {}'.format( path, e)) if extension == '.wv': from mutagen.wavpack import WavPack try: WavPack(path).info return 'wavpack' except Exception as e: raise ValueError('Error opening {} as wavpack: {}'.format(path, e)) return None
def getInfoFromTag(filename, language): compil = '0' tag = None if filename.lower().endswith("mp3"): try: tag = EasyID3(filename) except: return (None, None, None, None, None, None, None) artist = mp3tagGrabber(tag, filename, 'artist', language, force=True) album = mp3tagGrabber(tag, filename, 'album', language, force=True) title = mp3tagGrabber(tag, filename, 'title', language, force=True) track = cleanTrackAndDisk( mp3tagGrabber(tag, filename, 'track', language, 'tracknumber')) disc = cleanTrackAndDisk( mp3tagGrabber(tag, filename, 'disk', language, 'discnumber')) TPE2 = mp3tagGrabber(tag, filename, 'TPE2', language, 'performer') try: compil = tag['compilation'][0] except: pass #print artist, album, title, track, disc, TPE2, compil return (artist, album, title, track, disc, TPE2, compil) elif filename.lower().endswith("m4a") or filename.lower().endswith( "m4b") or filename.lower().endswith("m4p"): try: tag = EasyMP4(filename) except: return (None, None, None, None, None, None, None) elif filename.lower().endswith("flac"): try: tag = FLAC(filename) except: return (None, None, None, None, None, None, None) elif filename.lower().endswith("ogg"): try: tag = OggVorbis(filename) except: return (None, None, None, None, None, None, None) elif filename.lower().endswith("opus"): try: tag = OggOpus(filename) except: return (None, None, None, None, None, None, None) elif filename.lower().endswith("wma"): try: tag = ASF(filename) except: return (None, None, None, None, None, None, None) artist = getWMAstring(mutagenGrabber(tag, 'Author', language)) album = getWMAstring(mutagenGrabber(tag, 'WM/AlbumTitle', language)) title = getWMAstring(mutagenGrabber(tag, 'Title', language)) track = cleanTrackAndDisk( mutagenGrabber(tag, 'WM/TrackNumber', language)) disc = cleanTrackAndDisk(mutagenGrabber(tag, 'WM/PartOfSet', language)) TPE2 = getWMAstring(mutagenGrabber(tag, 'WM/AlbumArtist', language)) return (artist, album, title, track, disc, TPE2, compil) else: #unsupported filetype return (None, None, None, None, None, None, None) artist = mutagenGrabber(tag, 'artist', language) album = mutagenGrabber(tag, 'album', language) title = mutagenGrabber(tag, 'title', language) track = cleanTrackAndDisk(mutagenGrabber(tag, 'tracknumber', language)) disc = cleanTrackAndDisk(mutagenGrabber(tag, 'discnumber', language)) TPE2 = mutagenGrabber(tag, 'albumartist', language) or mutagenGrabber( tag, 'album artist', language) try: compil = tag['compilation'][0] if tag['compilation'][0] == True: compil = '1' except: pass return (artist, album, title, track, disc, TPE2, compil)
async def download_song(self, songObj: SongObj) -> None: ''' `songObj` `songObj` : song to be downloaded RETURNS `~` Downloads, Converts, Normalizes song & embeds metadata as ID3 tags. ''' #! all YouTube downloads are to .\Temp; they are then converted and put into .\ and #! finally followed up with ID3 metadata tags #! we explicitly use the os.path.join function here to ensure download is #! platform agnostic # Create a .\Temp folder if not present tempFolder = Path('.', 'Temp') if not tempFolder.exists(): tempFolder.mkdir() # build file name of converted file artistStr = '' #! we eliminate contributing artist names that are also in the song name, else we #! would end up with things like 'Jetta, Mastubs - I'd love to change the world #! (Mastubs REMIX).mp3' which is kinda an odd file name. for artist in songObj.get_contributing_artists(): if artist.lower() not in songObj.get_song_name().lower(): artistStr += artist + ', ' #! the ...[:-2] is to avoid the last ', ' appended to artistStr convertedFileName = artistStr[:-2] + ' - ' + songObj.get_song_name() #! this is windows specific (disallowed chars) for disallowedChar in ['/', '?', '\\', '*', '|', '<', '>']: if disallowedChar in convertedFileName: convertedFileName = convertedFileName.replace( disallowedChar, '') #! double quotes (") and semi-colons (:) are also disallowed characters but we would #! like to retain their equivalents, so they aren't removed in the prior loop convertedFileName = convertedFileName.replace( '"', "'").replace(': ', ' - ') convertedFilePath = Path(".", f"{convertedFileName}.opus") # if a song is already downloaded skip it if convertedFilePath.is_file(): if self.displayManager: self.displayManager.notify_download_skip() if self.downloadTracker: self.downloadTracker.notify_download_completion(songObj) #! None is the default return value of all functions, we just explicitly define #! it here as a continent way to avoid executing the rest of the function. return None # download Audio from YouTube if self.displayManager: youtubeHandler = YouTube( url=songObj.get_youtube_link(), on_progress_callback=self.displayManager.pytube_progress_hook ) else: youtubeHandler = YouTube(songObj.get_youtube_link()) trackAudioStream = youtubeHandler.streams.filter( only_audio=True).order_by('bitrate').last() if not trackAudioStream: print(f"Unable to get audio stream for \"{songObj.get_song_name()}\" " f"by \"{songObj.get_contributing_artists()[0]}\" " f"from video \"{songObj.get_youtube_link()}\"") return None downloadedFilePathString = await self._download_from_youtube(convertedFileName, tempFolder, trackAudioStream) if downloadedFilePathString is None: return None downloadedFilePath = Path(downloadedFilePathString) # convert downloaded file command = 'ffmpeg -v quiet -y -i "%s" -acodec libopus -b:a 80K -vbr on -compression_level 10 "%s"' #! bash/ffmpeg on Unix systems need to have excape char (\) for special characters: \$ #! alternatively the quotes could be reversed (single <-> double) in the command then #! the windows special characters needs escaping (^): ^\ ^& ^| ^> ^< ^^ if sys.platform == 'win32': formattedCommand = command % ( str(downloadedFilePath), str(convertedFilePath) ) else: formattedCommand = command % ( str(downloadedFilePath).replace('$', '\$'), str(convertedFilePath).replace('$', '\$') ) process = await asyncio.subprocess.create_subprocess_shell(formattedCommand) _ = await process.communicate() #! Wait till converted file is actually created while True: if convertedFilePath.is_file(): break if self.displayManager: self.displayManager.notify_conversion_completion() # embed song details audioFile = OggOpus(convertedFilePath) #! Get rid of all existing tags (if any exist) audioFile.delete() #! song name audioFile['title'] = songObj.get_song_name() audioFile['titlesort'] = songObj.get_song_name() #! track number audioFile['tracknumber'] = str(songObj.get_track_number()) #! genres audioFile['genre'] = songObj.get_genres() #! all involved artists audioFile['artist'] = songObj.get_contributing_artists() #! album name audioFile['album'] = songObj.get_album_name() #! album artist (all of 'em) audioFile['albumartist'] = songObj.get_album_artists() #! album release date (to what ever precision available) audioFile['date'] = songObj.get_album_release() audioFile['originaldate'] = songObj.get_album_release() audioFile.save() #! setting the album art audioFile = OggOpus(convertedFilePath) rawAlbumArt = urlopen(songObj.get_album_cover_url()).read() picture = Picture() picture.data = rawAlbumArt picture.type = 3 picture.desc = u"Cover" picture.mime = u"image/jpeg" picture_data = picture.write() encoded_data = base64.b64encode(picture_data) vcomment_value = encoded_data.decode("ascii") audioFile["metadata_block_picture"] = [vcomment_value] audioFile.save() # Do the necessary cleanup if self.displayManager: self.displayManager.notify_download_completion() if self.downloadTracker: self.downloadTracker.notify_download_completion(songObj) # delete the unnecessary YouTube download File if downloadedFilePath and downloadedFilePath.is_file(): downloadedFilePath.unlink()
def update_opus(self, filename, artist=None, album=None, title=None, tracknum=None, year=None, group=None, conductor=None, composer=None): """ Updates an on-disk ogg opus file with the given tag data. Any passed-in variable set to None will be ignored. If group/conductor/composer is a blank string, those fields will be completely removed from the file. Any of the other fields set to blank will leave the tag in place. Will ensure that the file's mtime is updated. """ full_filename = self.check_library_filename(filename) self.assertEqual(os.path.exists(full_filename), True) starting_mtime = int(os.stat(full_filename).st_mtime) tags = OggOpus(full_filename) if artist is not None: try: del tags['ARTIST'] except KeyError: pass tags['ARTIST'] = artist if album is not None: try: del tags['ALBUM'] except KeyError: pass tags['ALBUM'] = album if title is not None: try: del tags['TITLE'] except KeyError: pass tags['TITLE'] = title if group is not None: try: del tags['ENSEMBLE'] except KeyError: pass if group != '': tags['ENSEMBLE'] = group if conductor is not None: try: del tags['CONDUCTOR'] except KeyError: pass if conductor != '': tags['CONDUCTOR'] = conductor if composer is not None: try: del tags['COMPOSER'] except KeyError: pass if composer != '': tags['COMPOSER'] = composer if tracknum is not None: try: del tags['TRACKNUMBER'] except KeyError: pass tags['TRACKNUMBER'] = str(tracknum) if year is not None: try: del tags['DATE'] except KeyError: pass try: del tags['YEAR'] except KeyError: pass tags['DATE'] = str(year) # Save tags.save() # Check on mtime update and manually fix it if it's not updated stat_result = os.stat(full_filename) ending_mtime = int(stat_result.st_mtime) if starting_mtime == ending_mtime: new_mtime = ending_mtime + 1 os.utime(full_filename, times=(stat_result.st_atime, new_mtime))