def test_that_get_lyrics_works(self): """ Test that get_lyrics function works """ self.assertEqual(get_lyrics("Faded", "Alan Walker")[:9], "[Verse 1]") self.assertEqual(get_lyrics("Radioactive", "Imagine Dragons")[:7], "[Intro]") self.assertEqual(get_lyrics("Battle Symphony", "Linkin Park")[:9], "[Verse 1]")
def test_database_for_unsupported_song(self): """ test that the database set on pythonanywhere is working and giving strippers for unsupported songs """ self.assertEqual( get_lyrics("Bitch Lasagna", "Party in Backyard")[:7], "[Intro]") self.assertEqual( get_lyrics("Let Me Hold You (Turn Me On)", "Cheat Codes")[:9], "[Verse 1]")
def test_that_get_lyrics_does_not_break_with_wrong_data(self, fake_get): """ Test that get_lyrics function does not break with wrong data """ fake_resp = requests.Response() fake_resp.status_code = 404 fake_get.return_value = fake_resp self.assertEqual(get_lyrics("xyzzy", "Yeet"), None) self.assertEqual(get_lyrics("aifone", "Muhmello"), None) self.assertEqual(get_lyrics("Pixel2XL", "Goog-el"), None)
def test_that_get_lyrics_does_not_break_with_wrong_data(self): """ Test that get_lyrics function does not break with wrong data """ self.assertEqual(get_lyrics( "Battle Symphony", "One Direction", False), "Couldn't get lyrics for Battle Symphony by One Direction.\n") self.assertEqual(get_lyrics("Faded", "Muhmello", False), "Couldn't get lyrics for Faded by Muhmello.\n") self.assertEqual(get_lyrics("Battle Symphony", "Drake", False), "Couldn't get lyrics for Battle Symphony by Drake.\n") # Deleting above songs and artists from unsupported.txt with open("unsupported.txt", "r") as f: lines = f.readlines() with open("unsupported.txt", "w") as f: for line in lines: if line not in [" Battle Symphony by One Direction \n", " Faded by Muhmello \n", " Battle Symphony by Drake \n"]: f.write(line)
def test_that_get_lyrics_does_not_break_with_wrong_data(self): """ Test that get_lyrics function does not break with wrong data """ self.assertEqual(get_lyrics( "xyzzy", "Yeet", False), "Couldn't get lyrics for xyzzy by Yeet.\n") self.assertEqual(get_lyrics("wiuegi", "Muhmello", False), "Couldn't get lyrics for wiuegi by Muhmello.\n") self.assertEqual(get_lyrics("Pixel2XL", "Elgoog", False), "Couldn't get lyrics for Pixel2XL by Elgoog.\n") # Deleting above songs and artists from unsupported.txt with open("unsupported.txt", "r") as f: lines = f.readlines() with open("unsupported.txt", "w") as f: for line in lines: if line not in ["xyzzy by Yeet \n", "wiuegi by Muhmello \n", "Pixel2XL by Elgoog \n"]: f.write(line)
def get_lyrics(song, artist): """ Fetches lyrics using the swaglyrics library """ lyrics = swaglyrics.get_lyrics(song, artist) if not lyrics: raise LyricsNotFound(f"Lyrics for {song} by {artist} not found on Genius.") return lyrics
def test_that_get_lyrics_works(self): """ Test that get_lyrics function works """ self.assertEqual(get_lyrics('果てるまで', 'ハゼ馳せる'), None) # song and artist non-latin self.assertEqual(get_lyrics('Hello', 'ハゼ馳せる'), None) # artist non-latin self.assertEqual(get_lyrics('ハゼ馳せる果てるまで', 'ZUTOMAYO'), None) # song non-latin self.assertEqual(get_lyrics("Faded", "Alan Walker")[:9], "[Verse 1]") self.assertEqual( get_lyrics("Radioactive", "Imagine Dragons")[:7], "[Intro]") self.assertEqual( get_lyrics("Battle Symphony", "Linkin Park")[:9], "[Verse 1]")
def process(self, statement, additional_response_selection_parameters=None): if ( self.normalized[0] == "get" and self.normalized[1] == "lyrics" and self.normalized[2] == "for" ): self.normalized[0:3] = [] elif self.normalized[0] == "lyrics" and self.normalized[1] == "for": self.normalized[0:2] = [] elif self.normalized[0] == "lyrics": self.normalized[0:1] = [] stripped_message = " ".join(self.normalized).strip() try: song, artist = stripped_message.split("$ by") except Exception as e: selected_statement = SugaroidStatement( "Usage: _Hello $by Adele_ or " "_get lyrics for The Nights $by Avicii_", chatbot=True, ) selected_statement.confidence = 1 emotion = Emotion.lol selected_statement.emotion = emotion return selected_statement try: lyrics = get_lyrics(song, artist) if not lyrics or not lyrics.strip(): raise LyricsNotFound except LyricsNotFound: lyrics = "I couldn't find the lyrics for '{}' by '{}'.".format(song, artist) selected_statement = SugaroidStatement(lyrics, chatbot=True) selected_statement.confidence = 1 emotion = Emotion.lol selected_statement.emotion = emotion return selected_statement
def test_that_lyrics_works_for_unsupported_songs(self): """ Test that lyrics function gives 'unsupported' message to unsupported files """ get_lyrics("Hello", "World", False) self.assertEqual(lyrics("Hello", "World"), "Lyrics unavailable for Hello by World.\n") get_lyrics("Foo", "Bar", False) self.assertEqual(lyrics("Foo", "Bar"), "Lyrics unavailable for Foo by Bar.\n") get_lyrics("Fantastic", "Beasts", False) self.assertEqual(lyrics("Fantastic", "Beasts"), "Lyrics unavailable for Fantastic by Beasts.\n") # Deleting above songs and artists from unsupported.txt with open("unsupported.txt", "r") as f: lines = f.readlines() with open("unsupported.txt", "w") as f: for line in lines: if line not in [" Hello by World \n", " Foo by Bar \n", " Fantastic by Beasts \n"]: f.write(line)
def test_that_lyrics_works_for_unsupported_songs(self): """ Test that lyrics function gives 'unsupported' message to unsupported files """ get_lyrics("xyzzy", "Yeet", False) self.assertEqual(lyrics("xyzzy", "Yeet"), "Lyrics unavailable for xyzzy by Yeet.\n") get_lyrics("wiuegi", "Muhmello", False) self.assertEqual(lyrics("wiuegi", "Muhmello"), "Lyrics unavailable for wiuegi by Muhmello.\n") get_lyrics("Pixel2XL", "Elgoog", False) self.assertEqual(lyrics("Pixel2XL", "Elgoog"), "Lyrics unavailable for Pixel2XL by Elgoog.\n") # Deleting above songs and artists from unsupported.txt with open("unsupported.txt", "r") as f: lines = f.readlines() with open("unsupported.txt", "w") as f: for line in lines: if line not in ["xyzzy by Yeet \n", "wiuegi by Muhmello \n", "Pixel2XL by Elgoog \n"]: f.write(line)
def test_that_get_lyrics_does_not_break_with_request_giving_wrong_status_code(self, mock_requests): """ Test the get_lyrics does not break with requests giving wrong status code """ self.assertEqual(get_lyrics("Ki", "Ki", True), "Couldn\'t get lyrics for Ki by Ki.\n")
def test_that_get_lyrics_calls_requests(self, mock_requests): """ Test that get_lyrics calls requests """ self.assertEqual(get_lyrics( "Pixel2XL", "Elgoog", True), "Couldn't get lyrics for Pixel2XL by Elgoog.\nPhone is dope")
def test_that_wrong_song_or_artist_does_not_break_stuff(self): self.assertEqual(get_lyrics('Get Schwifty', 'lol'), 'Couldn\'t get lyrics for Get Schwifty by lol.') self.assertFalse( get_lyrics('Get Schwifty', 'Rick Sanchez') == 'Couldn\'t get lyrics for Get Schwifty by Rick Sanchez.')
def test_that_get_lyrics_calls_requests(self, mock_requests): """ Test that get lyrics calls requests """ self.assertEqual(get_lyrics("River", "Dale", True), "Couldn't get lyrics for River by Dale.\nSeason 3 is supernatural")
def test_that_get_lyrics_do_not_break_with_error_in_request(self, mock_requests): """ Test the get_lyrics does not break with error in requests """ self.assertEqual(get_lyrics("Ki", "Ki", True), "Couldn\'t get lyrics for Ki by Ki.\n")
def test_database_for_unsupported_song(self): """ test that the database set on pythonanywhere is working and giving strippers for unsupported songs """ self.assertEqual(get_lyrics("Bitch Lasagna", "Party in Backyard")[:7], "[Intro]")
def line_align(songs, dump_dir, boundary_algorithm='olda', label_algorithm='fmc2d', do_twinnet=False): """ Aligns given audio with lyrics by line. If dump_dir is None, no timestamp yml is created. :param songs: Song metadata in dict with keys 'song', 'artist', 'path' and \ 'genre'. Key 'path' is audio file path. Key 'genre' optional. :type songs: list[dict{}] | dict{} :param dump_dir: Directory to store timestamp ymls. :type dump_dir: file-like | None :param boundary_algorithm: Segmentation algorithm for MSAF. :type boundary_algorithm: str :param label_algorithm: Labelling algorithm for MSAF. :type label_algorithm: str :param do_twinnet: Flag for performing vocal isolation. :type do_twinnet: bool :return align_data: List of alignment data. See below for formatting. :rtype: list[dict{}] """ logging.info('Beginning alignment...') if isinstance(songs, dict): songs = [songs] # Module initializations snd = SND(silencedb=-15) sc = SyllableCounter() # Perform MaD TwinNet in one batch if do_twinnet: paths = [song['path'] for song in songs] twinnet.twinnet_process(paths) else: #logging.info('Skipping MaD TwinNet') print('Performing source separation using spleeter..') audio_path = songs[0]['path'] destination = os.path.splitext(audio_path)[0] if not os.path.exists(destination): separator = Separator('spleeter:2stems') separator.separate_to_file(audio_descriptor=audio_path, destination=destination) total_align_data = [] for song in songs: logging.info('Processing {} by {}'.format(song['song'], song['artist'])) start_time = time.time() # Get file names mixed_path = song['path'] voice_path = os.path.splitext(song['path'])[0] + '_voice.wav' if not do_twinnet: voice_path = os.path.join(destination, 'vocals.wav') # Get lyrics from Genius lyrics = get_lyrics(song['song'], song['artist']) # Get syllable count from lyrics formatted_lyrics = sc.build_lyrics(lyrics) syl_lyrics = sc.get_syllable_count_lyrics(formatted_lyrics) sc_syllables = sc.get_syllable_count_per_section(syl_lyrics) # Get syllable count from SND snd_syllables = snd.run(voice_path) # Structural segmentation analysis on original audio sections, labels = msaf.process(mixed_path, boundaries_id=boundary_algorithm, labels_id=label_algorithm) # Save instrumental section indices instrumentals = [] # Get SND counts, densities per label max_count = 0 labels_density = {} i_s = 0 for i, section in enumerate(zip(labels, sections[:-1], sections[1:])): count = 0 while i_s < len(snd_syllables) and snd_syllables[i_s] < section[2]: count += 1 i_s += 1 max_count = max(max_count, count) duration = section[2] - section[1] density = count / duration # TODO: Improve instrumental categorization if density < 0.4: instrumentals.append(i) else: if section[0] not in labels_density: labels_density[section[0]] = [[], []] labels_density[section[0]][0].append(count) labels_density[section[0]][1].append(density) # if section[0] not in labels_density: # labels_density[section[0]] = [[], []] # labels_density[section[0]][0].append(count) # labels_density[section[0]][1].append(density) # Normalize SND syllable counts for label in labels_density: labels_density[label][0] = [ count / max_count for count in labels_density[label][0] ] # Normalize SSA syllable counts gt_max_syl = max(section[1] for section in sc_syllables) gt_chorus_syl = mean(section[1] / gt_max_syl for section in sc_syllables if section[0] == 'chorus') # Find label most similar to chorus min_label = labels[0] min_distance = float('inf') for label in labels_density: if len(labels_density[label][0]) < 2: continue # TODO: Fix distance scales mean_syl = mean(labels_density[label][0]) std_den = stdev(labels_density[label][1]) distance = sqrt(((mean_syl - gt_chorus_syl) / gt_chorus_syl)**2 + std_den**2) if distance < min_distance: min_distance = distance min_label = label # Relabel relabels = [''] * len(labels) temp = defaultdict(list) for i, label in enumerate(labels): temp[label].append(i) for label in temp: for i in temp[label]: if i in instrumentals: continue elif label == min_label: relabels[i] = 'chorus' elif len(temp[label]) > 1: relabels[i] = 'verse' else: relabels[i] = 'other' del temp relabels = [label for label in relabels if label] if not relabels: logging.error('Whole song tagged as instrumental! Skipping...') continue # Calculate accumulated error matrix dp = [[-1 for j in range(len(relabels))] for i in range(len(sc_syllables))] for i in range(len(sc_syllables)): for j in range(len(relabels)): dp[i][j] = dp_err_matrix[sc_syllables[i][0]][relabels[j]] if i == 0 and j == 0: pass elif i == 0: dp[i][j] += dp[i][j - 1] elif j == 0: dp[i][j] += dp[i - 1][j] else: dp[i][j] += min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) # Backtrack i, j = len(sc_syllables) - 1, len(relabels) - 1 path = [] while True: path.append((i, j)) if (i, j) == (0, 0): break elif i == 0: j -= 1 elif j == 0: i -= 1 else: min_dir = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) if dp[i - 1][j] == min_dir: i -= 1 elif dp[i][j - 1] == min_dir: j -= 1 else: i -= 1 j -= 1 path.reverse() # Process alignment and write to file alignment = [[] for i in range(len(labels))] for i in instrumentals: alignment[i].append('instrumental') section_id = 0 j_prev = 0 for (i, j) in path: if j != j_prev: section_id += 1 j_prev = j while 'instrumental' in alignment[section_id]: section_id += 1 alignment[section_id].append(i) end_time = time.time() align_data = { 'song': song['song'], 'artist': song['artist'], 'process time': end_time - start_time, 'duration': round((sections[-1] - sections[0]).item(), 2), 'align': [] } if 'genre' in song: align_data['genre'] = song['genre'] cur_lyric_section = -1 for i, section in enumerate(alignment): for n, lyric_section in enumerate(section): if lyric_section != cur_lyric_section: break_point = round(( sections[i] + n * (sections[i + 1] - sections[i]) / len(section)).item(), 2) if cur_lyric_section != 'instrumental' and align_data[ 'align']: align_data['align'][-1]['end'] = break_point if lyric_section != 'instrumental': align_data['align'].append({ 'label': sc_syllables[lyric_section][0], 'syllables': sc_syllables[lyric_section][1], 'start': break_point, 'lines': [] }) cur_lyric_section = lyric_section if 'end' not in align_data['align'][-1]: align_data['align'][-1]['end'] = break_point for i, section in enumerate(align_data['align']): duration = section['end'] - section['start'] line_start = section['start'] for j, line in enumerate(formatted_lyrics[i][1]): line_text = ' '.join(line) line_syls = sum(syl_lyrics[i][1][j]) line_duration = line_syls / align_data['align'][i][ 'syllables'] * duration align_data['align'][i]['lines'].append({ 'end': line_start + line_duration, 'text': line_text }) line_start += line_duration if dump_dir is not None: file_name = '{}_{}.yml'.format(song['artist'], song['song']).replace(' ', '') file_path = os.path.join(dump_dir, file_name) with open(file_path, 'w') as f: yaml.dump(align_data, f, default_flow_style=False) total_align_data.append(align_data) return total_align_data