Skip to content

Commit

Permalink
Merge pull request #139 from rakuri255/0.0.10
Browse files Browse the repository at this point in the history
0.0.10
  • Loading branch information
rakuri255 authored Apr 30, 2024
2 parents baeb3d2 + ff764d9 commit 40168a9
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 19 deletions.
7 changes: 7 additions & 0 deletions ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Version: 0.0.10
Date: 2024.05.01
- Fix:
- remove whitespace from the beginning and end of title and artist
- image conversion to jpeg for transparent or RGBA
- index out of range error in when list is empty from musicbrainz

# Version: 0.0.9
Date: 2024.02.06
- Fix:
Expand Down
57 changes: 57 additions & 0 deletions pytest/modules/Audio/test_youtube.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import unittest
from unittest.mock import patch, MagicMock
from src.modules.Audio.youtube import get_youtube_title
from src.modules.Audio.youtube import download_and_convert_thumbnail

class TestGetYoutubeTitle(unittest.TestCase):
@patch("yt_dlp.YoutubeDL")
def test_get_youtube_title(self, mock_youtube_dl):
# Arrange
# Also test leading and trailing whitespaces
mock_youtube_dl.return_value.__enter__.return_value.extract_info.return_value = {
"artist": " Test Artist ",
"track": " Test Track ",
"title": " Test Artist - Test Track ",
"channel": " Test Channel "
}
url = " https://www.youtube.com/watch?v=dQw4w9WgXcQ "

# Act
result = get_youtube_title(url)

# Assert
self.assertEqual(result, ("Test Artist", "Test Track"))
mock_youtube_dl.assert_called_once()

@patch("src.modules.Audio.youtube.yt_dlp.YoutubeDL")
@patch("src.modules.Audio.youtube.Image.open")
@patch("src.modules.Audio.youtube.os.path.join")
@patch("src.modules.Audio.youtube.crop_image_to_square")
def test_download_and_convert_thumbnail(self, mock_crop_image_to_square, mock_os_path_join, mock_image_open, mock_youtube_dl):
# Arrange
mock_youtube_dl.return_value.__enter__.return_value.extract_info.return_value = {"thumbnail": "test_thumbnail_url"}
mock_youtube_dl.return_value.__enter__.return_value.urlopen.return_value.read.return_value = b"test_image_data"
mock_image = MagicMock()
mock_image.convert.return_value = mock_image
mock_image_open.return_value = mock_image
mock_os_path_join.return_value = "/path/to/output/test.jpg"

ydl_opts = {}
url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
clear_filename = "test"
output_path = "/path/to/output"

# Act
download_and_convert_thumbnail(ydl_opts, url, clear_filename, output_path)

# Assert
mock_youtube_dl.assert_called_once_with(ydl_opts)
mock_youtube_dl.return_value.__enter__.return_value.extract_info.assert_called_once_with(url, download=False)
mock_youtube_dl.return_value.__enter__.return_value.urlopen.assert_called_once_with("test_thumbnail_url")
mock_image.convert.assert_called_once_with('RGB')
mock_os_path_join.assert_called_once_with(output_path, clear_filename + " [CO].jpg")
mock_image.save.assert_called_once_with("/path/to/output/test.jpg", "JPEG")
mock_crop_image_to_square.assert_called_once_with("/path/to/output/test.jpg")

if __name__ == "__main__":
unittest.main()
101 changes: 101 additions & 0 deletions pytest/modules/test_musicbrainz_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,107 @@ def test_get_music_infos_when_title_and_artist_are_the_same(self, mock_search_re
self.assertEqual(year, None)
self.assertEqual(genre, None)

@patch('musicbrainzngs.search_artists')
@patch('musicbrainzngs.search_release_groups')
def test_get_music_infos(self, mock_search_release_groups, mock_search_artists):
# Arrange
artist = 'UltraSinger'
title = 'That\'s Rocking!'
search = f'{artist} - {title} (UltrStar 2023) FULL HD'

# Set up mock return values for the MusicBrainz API calls
mock_search_artists.return_value = {
'artist-list': [
{'name': f' {artist} '} # Also test leading and trailing whitespaces
]
}

mock_search_release_groups.return_value = {
'release-group-list': [
{
'title': f' {title} ', # Also test leading and trailing whitespaces
'artist-credit-phrase': f' {artist} ', # Also test leading and trailing whitespaces
'first-release-date': ' 2023-01-01 ', # Also test leading and trailing whitespaces
'tag-list': [
{'name': ' Genre 1 '}, # Also test leading and trailing whitespaces
{'name': ' Genre 2 '} # Also test leading and trailing whitespaces
]
}
]
}

# Act
title, artist, year, genre = get_music_infos(search)

# Assert
self.assertEqual(title, 'That\'s Rocking!')
self.assertEqual(artist, 'UltraSinger')
self.assertEqual(year, '2023-01-01')
self.assertEqual(genre, 'Genre 1,Genre 2,')

@patch('musicbrainzngs.search_artists')
@patch('musicbrainzngs.search_release_groups')
def test_get_empty_artist_music_infos(self, mock_search_release_groups, mock_search_artists):
# Arrange
artist = 'UltraSinger'
title = 'That\'s Rocking!'
search = f'{artist} - {title} (UltrStar 2023) FULL HD'

# Set up mock return values for the MusicBrainz API calls
mock_search_artists.return_value = {
'artist-list': []
}

mock_search_release_groups.return_value = {
'release-group-list': [
{
'title': f' {title} ', # Also test leading and trailing whitespaces
'artist-credit-phrase': f' {artist} ', # Also test leading and trailing whitespaces
'first-release-date': ' 2023-01-01 ', # Also test leading and trailing whitespaces
'tag-list': [
{'name': ' Genre 1 '}, # Also test leading and trailing whitespaces
{'name': ' Genre 2 '} # Also test leading and trailing whitespaces
]
}
]
}

# Act
title, artist, year, genre = get_music_infos(search)

# Assert
self.assertEqual(title, None)
self.assertEqual(artist, None)
self.assertEqual(year, None)
self.assertEqual(genre, None)

@patch('musicbrainzngs.search_artists')
@patch('musicbrainzngs.search_release_groups')
def test_get_empty_release_music_infos(self, mock_search_release_groups, mock_search_artists):
# Arrange
artist = 'UltraSinger'
title = 'That\'s Rocking!'
search = f'{artist} - {title} (UltrStar 2023) FULL HD'

# Set up mock return values for the MusicBrainz API calls
mock_search_artists.return_value = {
'artist-list': [
{'name': f' {artist} '} # Also test leading and trailing whitespaces
]
}

mock_search_release_groups.return_value = {
'release-group-list': []
}

# Act
title, artist, year, genre = get_music_infos(search)

# Assert
self.assertEqual(title, None)
self.assertEqual(artist, None)
self.assertEqual(year, None)
self.assertEqual(genre, None)

if __name__ == '__main__':
unittest.main()
35 changes: 35 additions & 0 deletions pytest/test_UltraSinger.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Tests for UltraSinger.py"""

import unittest
from unittest.mock import patch, MagicMock
from src.UltraSinger import format_separated_string
from src.UltraSinger import extract_year
from src.UltraSinger import parse_ultrastar_txt

class TestUltraSinger(unittest.TestCase):
def test_format_separated_string(self):
Expand All @@ -22,3 +24,36 @@ def test_extract_year(self):

for year in years:
self.assertEqual(year, "2023")

@patch("src.UltraSinger.ultrastar_parser.parse_ultrastar_txt")
@patch("src.UltraSinger.ultrastar_converter.ultrastar_bpm_to_real_bpm")
@patch("src.UltraSinger.os.path.dirname")
@patch("src.UltraSinger.get_unused_song_output_dir")
@patch("src.UltraSinger.os_helper.create_folder")
def test_parse_ultrastar_txt(self, mock_create_folder, mock_get_unused_song_output_dir,
mock_dirname, mock_ultrastar_bpm_to_real_bpm,
mock_parse_ultrastar_txt):
# Arrange
mock_parse_ultrastar_txt.return_value = MagicMock(mp3="test.mp3",
artist=" Test Artist ", # Also test leading and trailing whitespaces
title=" Test Title ") # Also test leading and trailing whitespaces
mock_ultrastar_bpm_to_real_bpm.return_value = 120.0
mock_dirname.return_value = "\\path\\to\\input"
mock_get_unused_song_output_dir.return_value = "\\path\\to\\output\\Test Artist - Test Title"
mock_create_folder.return_value = None

# Act
result = parse_ultrastar_txt()

# Assert
self.assertEqual(result, ("test",
120.0,
"\\path\\to\\output\\Test Artist - Test Title",
"\\path\\to\\input\\test.mp3",
mock_parse_ultrastar_txt.return_value))

mock_parse_ultrastar_txt.assert_called_once()
mock_ultrastar_bpm_to_real_bpm.assert_called_once()
mock_dirname.assert_called_once()
mock_get_unused_song_output_dir.assert_called_once()
mock_create_folder.assert_called_once()
9 changes: 5 additions & 4 deletions src/UltraSinger.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,15 +787,16 @@ def parse_ultrastar_txt() -> tuple[str, float, str, str, UltrastarTxtValue]:
ultrastar_audio_input_path = os.path.join(dirname, ultrastar_mp3_name)
song_output = os.path.join(
settings.output_file_path,
ultrastar_class.artist + " - " + ultrastar_class.title,
ultrastar_class.artist.strip() + " - " + ultrastar_class.title.strip(),
)
song_output = get_unused_song_output_dir(song_output)
song_output = get_unused_song_output_dir(str(song_output))
os_helper.create_folder(song_output)

return (
basename_without_ext,
str(basename_without_ext),
real_bpm,
song_output,
ultrastar_audio_input_path,
str(ultrastar_audio_input_path),
ultrastar_class,
)

Expand Down
8 changes: 5 additions & 3 deletions src/modules/Audio/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from modules.console_colors import ULTRASINGER_HEAD
from modules.Image.image_helper import crop_image_to_square


def get_youtube_title(url: str) -> tuple[str, str]:
"""Get the title of the YouTube video"""

Expand All @@ -19,10 +20,10 @@ def get_youtube_title(url: str) -> tuple[str, str]:
)

if "artist" in result:
return result["artist"], result["track"]
return result["artist"].strip(), result["track"].strip()
if "-" in result["title"]:
return result["title"].split("-")[0], result["title"].split("-")[1]
return result["channel"], result["title"]
return result["title"].split("-")[0].strip(), result["title"].split("-")[1].strip()
return result["channel"].strip(), result["title"].strip()


def download_youtube_audio(url: str, clear_filename: str, output_path: str):
Expand Down Expand Up @@ -62,6 +63,7 @@ def download_and_convert_thumbnail(ydl_opts, url: str, clear_filename: str, outp
response = ydl.urlopen(thumbnail_url)
image_data = response.read()
image = Image.open(io.BytesIO(image_data))
image = image.convert('RGB') # Convert to RGB to avoid transparency or RGBA issues
image_path = os.path.join(output_path, clear_filename + " [CO].jpg")
image.save(image_path, "JPEG")
crop_image_to_square(image_path)
Expand Down
32 changes: 20 additions & 12 deletions src/modules/musicbrainz_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,35 @@ def get_music_infos(search_string: str) -> tuple[str, str, str, str]:
musicbrainzngs.set_useragent("UltraSinger", "0.1", "https://github.com/rakuri255/UltraSinger")

# search for artist and titel to get release on the first place
artist = None
artists = musicbrainzngs.search_artists(search_string)
artist = artists['artist-list'][0]['name']
if len(artists['artist-list']) != 0:
artist = artists['artist-list'][0]['name'].strip()
else:
print(f"{ULTRASINGER_HEAD} {red_highlighted('No match found')}")
return None, None, None, None

release = None
release_groups = musicbrainzngs.search_release_groups(search_string, artist=artist)
release = release_groups['release-group-list'][0]
if len(release_groups['release-group-list']) != 0:
release = release_groups['release-group-list'][0]

if 'artist-credit-phrase' in release:
artist = release['artist-credit-phrase']
if release is not None and 'artist-credit-phrase' in release:
artist = release['artist-credit-phrase'].strip()

title = None
if 'title' in release:
clean_search_string = search_string.translate(str.maketrans('', '', string.punctuation)).lower()
clean_release_title = release['title'].translate(str.maketrans('', '', string.punctuation)).lower()
clean_artist = artist.translate(str.maketrans('', '', string.punctuation)).lower()
if release is not None and 'title' in release:
clean_search_string = search_string.translate(str.maketrans('', '', string.punctuation)).lower().strip()
clean_release_title = release['title'].translate(str.maketrans('', '', string.punctuation)).lower().strip()
clean_artist = artist.translate(str.maketrans('', '', string.punctuation)).lower().strip()

# prepare search string when title and artist are the same
if clean_release_title == clean_artist:
# remmove the first acurance of the artist
# remove the first appearance of the artist
clean_search_string = clean_search_string.replace(clean_artist, "", 1)

if clean_release_title in clean_search_string:
title = release['title']
title = release['title'].strip()
else:
print(
f"{ULTRASINGER_HEAD} cant find title {red_highlighted(clean_release_title)} in {red_highlighted(clean_search_string)}")
Expand All @@ -43,14 +51,14 @@ def get_music_infos(search_string: str) -> tuple[str, str, str, str]:

year = None
if 'first-release-date' in release:
year = release['first-release-date']
year = release['first-release-date'].strip()
print(f"{ULTRASINGER_HEAD} Found release year: {blue_highlighted(year)}")

genres = None
if 'tag-list' in release:
genres = ""
for tag in release['tag-list']:
genres += f"{tag['name']},"
genres += f"{tag['name'].strip()},"
print(f"{ULTRASINGER_HEAD} Found genres: {blue_highlighted(genres)}")

return title, artist, year, genres

0 comments on commit 40168a9

Please sign in to comment.