Пример #1
0
def update_mp3_with_tags(file, tags):
    """
    Update a mp3 file's ID3 with provided tags
    file: Path to a mp3 file
    tags: Provided tags
    """
    try:
        id3 = ID3(file)
        print "updating file", file.rsplit('/', 1)[-1]
        for key in tags.iterkeys():
            values = tags[key]
            new_values = [
                single_tag_dict['preferred'] for single_tag_dict in values
            ]
            f = lambda value: value if type(
                value) is unicode else value.decode('utf-8')
            new_tag_value = map(f, new_values)
            print "    updating key", key, 'from', id3[
                key], 'to', new_tag_value
            id3[key] = new_tag_value

        id3.save()

    except Exception as e:
        print 'Error', e
        pass
Пример #2
0
 def findID3Tags(self):
     try:
         audio = ID3(self.mp3)
     except:
         if debug:
             print("Could not get ID3 tags for {0}".format(self.mp3))
         audio = None
     self.tagsID3 = audio
Пример #3
0
def _embed_mp3_cover(audio_file, song_obj, converted_file_path):
    # ! setting the album art
    audio_file = ID3(converted_file_path)
    rawAlbumArt = urlopen(song_obj.get_album_cover_url()).read()
    audio_file['APIC'] = AlbumCover(encoding=3,
                                    mime='image/jpeg',
                                    type=3,
                                    desc='Cover',
                                    data=rawAlbumArt)

    return audio_file
def _embed_mp3_cover(audio_file, song_object, converted_file_path):
    # ! setting the album art
    audio_file = ID3(converted_file_path)
    rawAlbumArt = urlopen(song_object.album_cover_url).read()
    audio_file["APIC"] = AlbumCover(encoding=3,
                                    mime="image/jpeg",
                                    type=3,
                                    desc="Cover",
                                    data=rawAlbumArt)

    return audio_file
Пример #5
0
    def set_id3_data(self, convertedFilePath, songObj):
        # embed song details
        # ! we save tags as both ID3 v2.3 and v2.4
        # ! The simple ID3 tags
        audioFile = EasyID3(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())
        # ! disc number
        audioFile['discnumber'] = str(songObj.get_disc_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)
        try:
            audioFile['albumartist'] = songObj.get_album_artists()
        except EasyID3KeyError:
            pass
        # ! album release date (to what ever precision available)
        audioFile['date'] = songObj.get_album_release()
        audioFile['originaldate'] = songObj.get_album_release()
        # ! save as both ID3 v2.3 & v2.4 as v2.3 isn't fully features and
        # ! windows doesn't support v2.4 until later versions of Win10
        audioFile.save(v2_version=3)
        # ! setting the album art
        audioFile = ID3(convertedFilePath)
        rawAlbumArt = urlopen(songObj.get_album_cover_url()).read()
        audioFile['APIC'] = AlbumCover(
            encoding=3,
            mime='image/jpeg',
            type=3,
            desc='Cover',
            data=rawAlbumArt
        )
        # ! setting the lyrics
        lyrics = songObj.get_lyrics()
        USLTOutput = USLT(encoding=3, lang=u'eng', desc=u'desc', text=lyrics)
        audioFile["USLT::'eng'"] = USLTOutput

        audioFile.save(v2_version=3)
Пример #6
0
def directorychooser():

    directory = askdirectory()
    os.chdir(directory)

    for files in os.listdir(directory):
        if files.endswith(".mp4"):

            realdir = os.path.realpath(files)
            audio = ID3(realdir)
            realnames.append(audio['TIT2'].text[0])

            listofsongs.append(files)

    pygame.mixer.init()
    pygame.mixer.music.load(listofsongs[0])
Пример #7
0
def info(filename):
    song_details = {
        'title': '',
        'artist': '',
        'album': '',
    }
    music = ID3(filename)
    for k, v in music.items():
        if k == 'TALB':
            song_details['album'] = v
        if k == 'TPE1':
            song_details['artist'] = v
        if k == 'TIT2':
            song_details['title'] = v
        if search('APIC', str(k)):
            with open('img1.jpg', 'wb') as img:
                img.write(v.data)

    return song_details
Пример #8
0
def read_tags(file):
    """
    Read all the tags in one mp3 file and return in a JSON object
    file: a MP3 file
    """
    id3 = ID3(file)
    tags = {}
    for key in id3.keys():
        tag_value = id3[key]
        tag_value_array = []
        for value in tag_value:
            encoded_value = []
            try:
                encoded = value.encode('ascii')
                encoded_value.extend(all_possible_decode(encoded))
            except Exception as e:
                encoded = value.encode('raw_unicode_escape')
                encoded_value.extend(all_possible_decode(encoded))

                encoded = value.encode('utf-8')
                encoded_value.extend(all_possible_decode(encoded))

            preferred = preferred_value(encoded_value, file.rsplit('/', 1)[-1])

            if preferred != encoded_value[-1]:
                tag_value_dict = {
                    'value': encoded_value,
                    'preferred': preferred
                }
                tag_value_array.append(tag_value_dict)
        if tag_value_array:
            tags[key] = tag_value_array
    if tags:
        dict = {'path': file}
        dict['tags'] = tags
        return dict
    else:
        return None
Пример #9
0
    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}.mp3")

        # 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 to MP3 with normalization

        #! -af loudnorm=I=-7:LRA applies EBR 128 loudness normalization algorithm with
        #! intergrated loudness target (I) set to -17, using values lower than -15
        #! causes 'pumping' i.e. rhythmic variation in loudness that should not
        #! exist -loud parts exaggerate, soft parts left alone.
        #!
        #! dynaudnorm applies dynamic non-linear RMS based normalization, this is what
        #! actually normalized the audio. The loudnorm filter just makes the apparent
        #! loudness constant
        #!
        #! apad=pad_dur=2 adds 2 seconds of silence toward the end of the track, this is
        #! done because the loudnorm filter clips/cuts/deletes the last 1-2 seconds on
        #! occasion especially if the song is EDM-like, so we add a few extra seconds to
        #! combat that.
        #!
        #! -acodec libmp3lame sets the encoded to 'libmp3lame' which is far better
        #! than the default 'mp3_mf', '-abr true' automatically determines and passes the
        #! audio encoding bitrate to the filters and encoder. This ensures that the
        #! sampled length of songs matches the actual length (i.e. a 5 min song won't display
        #! as 47 seconds long in your music player, yeah that was an issue earlier.)

        command = 'ffmpeg -v quiet -y -i "%s" -acodec libmp3lame -abr true ' \
            f'-b:a {trackAudioStream.bitrate} ' \
                  '-af "apad=pad_dur=2, dynaudnorm, loudnorm=I=-17" "%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
        #! we save tags as both ID3 v2.3 and v2.4

        #! The simple ID3 tags
        audioFile = EasyID3(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()

        #! save as both ID3 v2.3 & v2.4 as v2.3 isn't fully features and
        #! windows doesn't support v2.4 until later versions of Win10
        audioFile.save(v2_version=3)

        #! setting the album art
        audioFile = ID3(convertedFilePath)

        rawAlbumArt = urlopen(songObj.get_album_cover_url()).read()

        audioFile['APIC'] = AlbumCover(encoding=3,
                                       mime='image/jpeg',
                                       type=3,
                                       desc='Cover',
                                       data=rawAlbumArt)

        audioFile.save(v2_version=3)

        # 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()
Пример #10
0
def download_song(songObj: SongObj,
                  displayManager: DisplayManager = None,
                  downloadTracker: DownloadTracker = None) -> None:
    '''
    `songObj` `songObj` : song to be downloaded

    `AutoProxy` `displayManager` : autoproxy reference to a `DisplayManager`

    `AutoProxy` `downloadTracker`: autoproxy reference to a `DownloadTracker`

    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 = join('.', 'Temp')

    if not exists(tempFolder):
        mkdir(tempFolder)

    # 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(': ', ' - ')
    #! if a songObj's playlistIndex is not None then we prepend it to keep the playlist/album order
    playlistIndex = songObj.get_playlist_index()
    if playlistIndex is not None:
        convertedFileName = '{:04d}'.format(
            playlistIndex) + ' - ' + convertedFileName

    convertedFilePath = join('.', convertedFileName) + '.mp3'

    # if a song is already downloaded skip it
    if exists(convertedFilePath):
        if displayManager:
            displayManager.notify_download_skip()
        if downloadTracker:
            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 displayManager:
        youtubeHandler = YouTube(
            url=songObj.get_youtube_link(),
            on_progress_callback=displayManager.pytube_progress_hook)
    else:
        youtubeHandler = YouTube(songObj.get_youtube_link())

    trackAudioStream = youtubeHandler.streams.get_audio_only()

    #! The actual download, if there is any error, it'll be here,
    try:
        #! pyTube will save the song in .\Temp\$songName.mp4, it doesn't save as '.mp3'
        downloadedFilePath = trackAudioStream.download(
            output_path=tempFolder,
            filename=convertedFileName,
            skip_existing=False)
    except:
        #! This is equivalent to a failed download, we do nothing, the song remains on
        #! downloadTrackers download queue and all is well...
        #!
        #! None is again used as a convenient exit
        remove(join(tempFolder, convertedFileName) + '.mp4')
        return None

    # convert downloaded file to MP3 with normalization

    #! -af loudnorm=I=-7:LRA applies EBR 128 loudness normalization algorithm with
    #! intergrated loudness target (I) set to -17, using values lower than -15
    #! causes 'pumping' i.e. rhythmic variation in loudness that should not
    #! exist -loud parts exaggerate, soft parts left alone.
    #!
    #! apad=pad_dur=2 adds 2 seconds of silence toward the end of the track, this is
    #! done because the loudnorm filter clips/cuts/deletes the last 1-2 seconds on
    #! occasion especially if the song is EDM-like, so we add a few extra seconds to
    #! combat that.
    #!
    #! -acodec libmp3lame sets the encoded to 'libmp3lame' which is far better
    #! than the default 'mp3_mf', '-abr true' automatically determines and passes the
    #! audio encoding bitrate to the filters and encoder. This ensures that the
    #! sampled length of songs matches the actual length (i.e. a 5 min song won't display
    #! as 47 seconds long in your music player, yeah that was an issue earlier.)

    command = 'ffmpeg -v quiet -y -i "%s" -acodec libmp3lame -abr true -af loudnorm=I=-17 "%s"'
    formattedCommand = command % (downloadedFilePath, convertedFilePath)

    run_in_shell(formattedCommand)

    #! Wait till converted file is actually created
    while True:
        if exists(convertedFilePath):
            break

    if displayManager:
        displayManager.notify_conversion_completion()

    # embed song details
    #! we save tags as both ID3 v2.3 and v2.4

    #! The simple ID3 tags
    audioFile = EasyID3(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()

    #! spotify link: in case you wanna re-download your whole offline library,
    #! you can just read the links from the tags and redownload the songs.
    audioFile['website'] = songObj.get_spotify_link()

    #! save as both ID3 v2.3 & v2.4 as v2.3 isn't fully features and
    #! windows doesn't support v2.4 until later versions of Win10
    audioFile.save(v2_version=3)

    #! setting the album art
    audioFile = ID3(convertedFilePath)

    rawAlbumArt = urlopen(songObj.get_album_cover_url()).read()

    audioFile['APIC'] = AlbumCover(encoding=3,
                                   mime='image/jpeg',
                                   type=3,
                                   desc='Cover',
                                   data=rawAlbumArt)

    #! adding lyrics
    try:
        lyrics = songObj.get_song_lyrics()
        USLTOutput = USLT(encoding=3, lang=u'eng', desc=u'desc', text=lyrics)
        audioFile["USLT::'eng'"] = USLTOutput
    except:
        pass

    audioFile.save(v2_version=3)

    # Do the necessary cleanup
    if displayManager:
        displayManager.notify_download_completion()

    if downloadTracker:
        downloadTracker.notify_download_completion(songObj)

    # delete the unnecessary YouTube download File
    remove(downloadedFilePath)
    f for f in os.listdir(filepath)
    if os.path.isfile(os.path.join(filepath, f))
]

if not files:  # if the library is empty return NAK
    payload = '<NAK>'
else:  # otherwise send all the information at one song per packet
    payload = ('<ACK>')
    print(payload)
    # phone_client_sock.send(payload.encode('utf-8'))

    tags = ['title', 'artist', 'album']

    for f in files:
        # create ID3 tag reader
        mf = ID3(filepath + f)

        # add filename to payload
        payload = f

        for t in tags:
            payload = payload + '<US>'

            try:
                # add tag to payload if it exists
                payload = payload + mf[t][0]
            except:
                # otherwise it's <NUL>
                payload = payload + '<NUL>'
            #endexcept
        #endfor
Пример #12
0
def getSongList(client):
    print('Phone - Sending song list...')

    try:
        if(os.name == 'nt'): # if windows
            filepath = os.getcwd() + '/library/'
        elif(platform.system() == 'Darwin'):
            filepath = os.getcwd() + '/library'
        else: #otherwise debian server
            filepath = '/home/linaro/Desktop/library/'

        # get a list of all the filenames in the library
        files = [f for f in os.listdir(filepath) if os.path.isfile(os.path.join(filepath, f))]

        if not files: # if the library is empty return NAK
            payload = NAK
        else: # otherwise send all the information, one song per packet
            payload = ACK
            client.send(payload.encode('utf-8'))

            tags = ['title', 'artist', 'album']
            mf = None
            for f in files:
                try:
                    # create ID3 tag reader
                    mf = ID3(filepath + f)
                    validID3 = True
                except:
                    validID3 = False
                #endexcept

                if(validID3):
                    # add filename to payload
                    payload = f
                    for t in tags:
                        payload = payload + US # mark new attribute by unit seperator
                        try:
                            # add tag to payload if it exists
                            payload = payload + mf[t][0]
                        except:
                            # otherwise it's <NUL>
                            payload = payload + NUL
                        #endexcept
                    #endfor
                else:
                    payload = f
                    for t in tags: payload = payload + NUL + US
                #endelse

                # group seperator to show end of current song
                payload = payload + GS

                #send the payload, move to next song
                # print(payload.decode())
                client.send(payload.encode('utf-8'))
            #endfor

            returnPayload = EOT
        #endelse
    except Exception as e:
            print('Phone - Error getSongList')
            print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno))
            print(type(e).__name__)
            print(e)
            returnPayload = NAK
    #endexcept
    return returnPayload
Пример #13
0
def download_song(
    songObj: SongObj,
    displayManager: DisplayManager = None,
    downloadTracker: DownloadTracker = None,
) -> None:
    """
    `songObj` `songObj` : song to be downloaded

    `AutoProxy` `displayManager` : autoproxy reference to a `DisplayManager`

    `AutoProxy` `downloadTracker`: autoproxy reference to a `DownloadTracker`

    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 = join(".", "Temp")

    if not exists(tempFolder):
        mkdir(tempFolder)

    # 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 = join(".", convertedFileName) + ".mp3"

    # if a song is already downloaded skip it
    if exists(convertedFilePath):
        if displayManager:
            displayManager.notify_download_skip()
        if downloadTracker:
            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

    Lyrics = songObj.get_lyrics()

    # download Audio from YouTube
    if displayManager:
        youtubeHandler = YouTube(
            url=songObj.get_youtube_link(),
            on_progress_callback=displayManager.pytube_progress_hook,
        )
    else:
        youtubeHandler = YouTube(songObj.get_youtube_link())

    trackAudioStream = youtubeHandler.streams.get_audio_only()

    #! The actual download, if there is any error, it'll be here,
    try:
        #! pyTube will save the song in .\Temp\$songName.mp4, it doesn't save as '.mp3'
        downloadedFilePath = trackAudioStream.download(
            output_path=tempFolder,
            filename=convertedFileName,
            skip_existing=False)
    except:
        #! This is equivalent to a failed download, we do nothing, the song remains on
        #! downloadTrackers download queue and all is well...
        #!
        #! None is again used as a convenient exit
        remove(join(tempFolder, convertedFileName) + ".mp4")
        return None

    # convert downloaded file to MP3 with normalization

    #! -af loudnorm=I=-7:LRA applies EBR 128 loudness normalization algorithm with
    #! intergrated loudness target (I) set to -17, using values lower than -15
    #! causes 'pumping' i.e. rhythmic variation in loudness that should not
    #! exist -loud parts exaggerate, soft parts left alone.
    #!
    #! dynaudnorm applies dynamic non-linear RMS based normalization, this is what
    #! actually normalized the audio. The loudnorm filter just makes the apparent
    #! loudness constant
    #!
    #! apad=pad_dur=2 adds 2 seconds of silence toward the end of the track, this is
    #! done because the loudnorm filter clips/cuts/deletes the last 1-2 seconds on
    #! occasion especially if the song is EDM-like, so we add a few extra seconds to
    #! combat that.
    #!
    #! -acodec libmp3lame sets the encoded to 'libmp3lame' which is far better
    #! than the default 'mp3_mf', '-abr true' automatically determines and passes the
    #! audio encoding bitrate to the filters and encoder. This ensures that the
    #! sampled length of songs matches the actual length (i.e. a 5 min song won't display
    #! as 47 seconds long in your music player, yeah that was an issue earlier.)

    command = 'ffmpeg  -v quiet -hwaccel_output_format cuda -y -i "%s" -acodec libmp3lame -abr true  "%s"'
    formattedCommand = command % (downloadedFilePath, convertedFilePath)

    # run_in_shell(formattedCommand)
    return_code = None
    return_code = call(formattedCommand)

    if return_code != 0:
        raise ("Error occurred during conversion, ffmpeg issue probably")

    if displayManager:
        displayManager.notify_conversion_completion()

    # embed song details
    #! we save tags as both ID3 v2.3 and v2.4

    #! The simple ID3 tags
    audioFile = EasyID3(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()
    #! save as both ID3 v2.3 & v2.4 as v2.3 isn't fully features and
    #! windows doesn't support v2.4 until later versions of Win10
    audioFile.save(v2_version=3)

    #! setting the album art
    audioFile = ID3(convertedFilePath)

    rawAlbumArt = get(songObj.get_album_cover_url()).content

    audioFile["APIC"] = AlbumCover(encoding=3,
                                   mime="image/jpeg",
                                   type=3,
                                   desc="Cover",
                                   data=rawAlbumArt)

    audioFile["USLT"] = USLT(encoding=3, desc=u"Lyrics", text=Lyrics)

    audioFile.save(v2_version=3)

    # Do the necessary cleanup
    if displayManager:
        displayManager.notify_download_completion()

    if downloadTracker:
        downloadTracker.notify_download_completion(songObj)

    # delete the unnecessary YouTube download File
    remove(downloadedFilePath)