mirror of
https://github.com/KevinMidboe/spotify-downloader.git
synced 2026-07-23 18:21:53 +00:00
Write tests for YouTube metadata
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
[tool:pytest]
|
||||
addopts = --strict-markers -m "not network"
|
||||
markers =
|
||||
network: marks test which rely on external network resources (select with '-m network' or run all with '-m "network, not network"')
|
||||
|
||||
@@ -81,7 +81,7 @@ class EncoderBase(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _generate_encoding_arguments(self, input_encoding, output_encoding):
|
||||
def _generate_encoding_arguments(self, input_encoding, target_encoding):
|
||||
"""
|
||||
This method must return the core arguments for the defined
|
||||
encoder such as defining the sample rate, audio bitrate,
|
||||
|
||||
@@ -26,7 +26,7 @@ class EncoderAvconv(EncoderBase):
|
||||
def get_encoding(self, filename):
|
||||
return super().get_encoding(filename)
|
||||
|
||||
def _generate_encoding_arguments(self, input_encoding, output_encoding):
|
||||
def _generate_encoding_arguments(self, input_encoding, target_encoding):
|
||||
initial_arguments = self._rules.get(input_encoding)
|
||||
if initial_arguments is None:
|
||||
raise TypeError(
|
||||
@@ -35,7 +35,7 @@ class EncoderAvconv(EncoderBase):
|
||||
)
|
||||
)
|
||||
|
||||
arguments = initial_arguments.get(output_encoding)
|
||||
arguments = initial_arguments.get(target_encoding)
|
||||
if arguments is None:
|
||||
raise TypeError(
|
||||
'The output format ("{}") is not supported.'.format(
|
||||
@@ -45,19 +45,19 @@ class EncoderAvconv(EncoderBase):
|
||||
|
||||
return arguments
|
||||
|
||||
def _generate_encoding_arguments(self, input_encoding, output_encoding):
|
||||
def _generate_encoding_arguments(self, input_encoding, target_encoding):
|
||||
return ""
|
||||
|
||||
def set_debuglog(self):
|
||||
self._loglevel = "-loglevel debug"
|
||||
|
||||
def _generate_encode_command(self, input_file, output_file):
|
||||
def _generate_encode_command(self, input_file, target_file):
|
||||
input_encoding = self.get_encoding(input_file)
|
||||
output_encoding = self.get_encoding(output_file)
|
||||
target_encoding = self.get_encoding(target_file)
|
||||
|
||||
arguments = self._generate_encoding_arguments(
|
||||
input_encoding,
|
||||
output_encoding
|
||||
target_encoding
|
||||
)
|
||||
|
||||
command = [self.encoder_path] \
|
||||
@@ -65,14 +65,14 @@ class EncoderAvconv(EncoderBase):
|
||||
+ self._loglevel.split() \
|
||||
+ ["-i", input_file] \
|
||||
+ self._additional_arguments \
|
||||
+ [output_file]
|
||||
+ [target_file]
|
||||
|
||||
return command
|
||||
|
||||
def re_encode(self, input_file, output_file, delete_original=False):
|
||||
def re_encode(self, input_file, target_file, delete_original=False):
|
||||
encode_command = self._generate_encode_command(
|
||||
input_file,
|
||||
output_file
|
||||
target_file
|
||||
)
|
||||
|
||||
returncode = subprocess.call(encode_command)
|
||||
|
||||
@@ -37,18 +37,18 @@ class EncoderFFmpeg(EncoderBase):
|
||||
def get_encoding(self, path):
|
||||
return super().get_encoding(path)
|
||||
|
||||
def _generate_encoding_arguments(self, input_encoding, output_encoding):
|
||||
def _generate_encoding_arguments(self, input_encoding, target_encoding):
|
||||
initial_arguments = self._rules.get(input_encoding)
|
||||
if initial_arguments is None:
|
||||
raise TypeError(
|
||||
'The input format ("{}") is not supported.'.format(
|
||||
input_encoding,
|
||||
))
|
||||
arguments = initial_arguments.get(output_encoding)
|
||||
arguments = initial_arguments.get(target_encoding)
|
||||
if arguments is None:
|
||||
raise TypeError(
|
||||
'The output format ("{}") is not supported.'.format(
|
||||
output_encoding,
|
||||
target_encoding,
|
||||
))
|
||||
return arguments
|
||||
|
||||
@@ -56,14 +56,14 @@ class EncoderFFmpeg(EncoderBase):
|
||||
self._loglevel = "-loglevel debug"
|
||||
|
||||
def _generate_encode_command(self, input_path, target_path,
|
||||
input_encoding=None, output_encoding=None):
|
||||
input_encoding=None, target_encoding=None):
|
||||
if input_encoding is None:
|
||||
input_encoding = self.get_encoding(input_path)
|
||||
if output_encoding is None:
|
||||
output_encoding = self.get_encoding(target_path)
|
||||
if target_encoding is None:
|
||||
target_encoding = self.get_encoding(target_path)
|
||||
arguments = self._generate_encoding_arguments(
|
||||
input_encoding,
|
||||
output_encoding
|
||||
target_encoding
|
||||
)
|
||||
command = [self.encoder_path] \
|
||||
+ ["-y", "-nostdin"] \
|
||||
@@ -88,7 +88,7 @@ class EncoderFFmpeg(EncoderBase):
|
||||
return process
|
||||
|
||||
def re_encode_from_stdin(self, input_encoding, target_path):
|
||||
output_encoding = self.get_encoding(target_path)
|
||||
target_encoding = self.get_encoding(target_path)
|
||||
encode_command = self._generate_encode_command(
|
||||
"-",
|
||||
target_path,
|
||||
|
||||
@@ -15,12 +15,12 @@ class TestEncoderAvconv:
|
||||
|
||||
|
||||
class TestEncodingDefaults:
|
||||
def encode_command(input_file, output_file):
|
||||
def encode_command(input_file, target_file):
|
||||
command = [
|
||||
'avconv', '-y', '-loglevel', '0',
|
||||
'-i', input_file,
|
||||
'-ab', '192k',
|
||||
output_file,
|
||||
target_file,
|
||||
]
|
||||
return command
|
||||
|
||||
@@ -36,12 +36,12 @@ class TestEncodingDefaults:
|
||||
|
||||
|
||||
class TestEncodingInDebugMode:
|
||||
def debug_encode_command(input_file, output_file):
|
||||
def debug_encode_command(input_file, target_file):
|
||||
command = [
|
||||
'avconv', '-y', '-loglevel', 'debug',
|
||||
'-i', input_file,
|
||||
'-ab', '192k',
|
||||
output_file,
|
||||
target_file,
|
||||
]
|
||||
return command
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from spotdl.encode.encoders import EncoderFFmpeg
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestEncoderFFmpeg:
|
||||
def test_subclass(self):
|
||||
assert issubclass(EncoderFFmpeg, EncoderBase)
|
||||
|
||||
@@ -2,19 +2,19 @@ class EncoderNotFoundError(Exception):
|
||||
__module__ = Exception.__module__
|
||||
|
||||
def __init__(self, message=None):
|
||||
super(EncoderNotFoundError, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class FFmpegNotFoundError(EncoderNotFoundError):
|
||||
__module__ = Exception.__module__
|
||||
|
||||
def __init__(self, message=None):
|
||||
super(FFmpegNotFoundError, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class AvconvNotFoundError(EncoderNotFoundError):
|
||||
__module__ = Exception.__module__
|
||||
|
||||
def __init__(self, message=None):
|
||||
super(AvconvNotFoundError, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from spotdl.encode.exceptions import EncoderNotFoundError
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestAbstractBaseClass:
|
||||
def test_error_abstract_base_class_encoderbase(self):
|
||||
encoder_path = "ffmpeg"
|
||||
@@ -27,15 +26,9 @@ class TestAbstractBaseClass:
|
||||
def _generate_encoding_arguments(self):
|
||||
pass
|
||||
|
||||
def get_encoding(self):
|
||||
pass
|
||||
|
||||
def re_encode(self):
|
||||
pass
|
||||
|
||||
def set_argument(self):
|
||||
pass
|
||||
|
||||
def set_debuglog(self):
|
||||
pass
|
||||
|
||||
@@ -52,21 +45,15 @@ class TestMethods:
|
||||
def __init__(self, encoder_path, _loglevel, _additional_arguments):
|
||||
super().__init__(encoder_path, _loglevel, _additional_arguments)
|
||||
|
||||
def _generate_encode_command(self, input_file, output_file):
|
||||
def _generate_encode_command(self, input_file, target_file):
|
||||
pass
|
||||
|
||||
def _generate_encoding_arguments(self, input_encoding, output_encoding):
|
||||
def _generate_encoding_arguments(self, input_encoding, target_encoding):
|
||||
pass
|
||||
|
||||
def get_encoding(self, filename):
|
||||
return super().get_encoding(filename)
|
||||
|
||||
def re_encode(self, input_encoding, output_encoding):
|
||||
def re_encode(self, input_encoding, target_encoding):
|
||||
pass
|
||||
|
||||
def set_argument(self, argument):
|
||||
super().set_argument(argument)
|
||||
|
||||
def set_debuglog(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ from spotdl.encode.exceptions import EncoderNotFoundError
|
||||
from spotdl.encode.exceptions import FFmpegNotFoundError
|
||||
from spotdl.encode.exceptions import AvconvNotFoundError
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestEncoderNotFoundSubclass:
|
||||
def test_encoder_not_found_subclass(self):
|
||||
|
||||
@@ -2,4 +2,4 @@ class LyricsNotFoundError(Exception):
|
||||
__module__ = Exception.__module__
|
||||
|
||||
def __init__(self, message=None):
|
||||
super(LyricsNotFoundError, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from spotdl.metadata.provider_base import ProviderBase
|
||||
from spotdl.metadata.provider_base import StreamsBase
|
||||
|
||||
from spotdl.metadata.exceptions import MetadataNotFoundError
|
||||
from spotdl.metadata.exceptions import SpotifyMetadataNotFoundError
|
||||
from spotdl.metadata.exceptions import YouTubeMetadataNotFoundError
|
||||
|
||||
from spotdl.metadata.embedder_base import EmbedderBase
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from abc import abstractmethod
|
||||
|
||||
class EmbedderBase(ABC):
|
||||
"""
|
||||
The class must define the supported media file encoding
|
||||
The subclass must define the supported media file encoding
|
||||
formats here using a static variable - such as:
|
||||
|
||||
>>> supported_formats = ("mp3", "opus", "flac")
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
class MetadataNotFoundError(Exception):
|
||||
__module__ = Exception.__module__
|
||||
|
||||
def __init__(self, message=None):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class SpotifyMetadataNotFoundError(MetadataNotFoundError):
|
||||
__module__ = Exception.__module__
|
||||
|
||||
def __init__(self, message=None):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class YouTubeMetadataNotFoundError(MetadataNotFoundError):
|
||||
__module__ = Exception.__module__
|
||||
|
||||
def __init__(self, message=None):
|
||||
super().__init__(message)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class StreamsBase(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self, streams):
|
||||
@@ -17,7 +16,6 @@ class StreamsBase(ABC):
|
||||
"""
|
||||
self.all = streams
|
||||
|
||||
@abstractmethod
|
||||
def getbest(self):
|
||||
"""
|
||||
This method must return the audio stream with the
|
||||
@@ -25,7 +23,6 @@ class StreamsBase(ABC):
|
||||
"""
|
||||
return self.all[0]
|
||||
|
||||
@abstractmethod
|
||||
def getworst(self):
|
||||
"""
|
||||
This method must return the audio stream with the
|
||||
@@ -51,13 +48,12 @@ class ProviderBase(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def from_query(self, query):
|
||||
"""
|
||||
This method must return track metadata from the
|
||||
corresponding search query.
|
||||
"""
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def metadata_to_standard_form(self, metadata):
|
||||
@@ -67,3 +63,4 @@ class ProviderBase(ABC):
|
||||
providers, for easy utilization.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import spotipy
|
||||
import spotipy.oauth2 as oauth2
|
||||
|
||||
from spotdl.metadata import ProviderBase
|
||||
|
||||
from spotdl.metadata.exceptions import SpotifyMetadataNotFoundError
|
||||
|
||||
class ProviderSpotify(ProviderBase):
|
||||
def __init__(self, spotify=None):
|
||||
@@ -17,8 +17,14 @@ class ProviderSpotify(ProviderBase):
|
||||
return self.metadata_to_standard_form(metadata)
|
||||
|
||||
def from_query(self, query):
|
||||
metadata = self.spotify.search(query, limit=1)["tracks"]["items"][0]
|
||||
return self.metadata_to_standard_form(metadata)
|
||||
tracks = self.spotify.search(query, limit=1)["tracks"]["items"]
|
||||
if tracks is None:
|
||||
raise SpotifyMetadataNotFoundError(
|
||||
'Could not find any tracks matching the given search query ("{}")'.format(
|
||||
query,
|
||||
)
|
||||
)
|
||||
return self.metadata_to_standard_form(tracks[0])
|
||||
|
||||
def _generate_token(self, client_id, client_secret):
|
||||
""" Generate the token. """
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
|
||||
from spotdl.metadata import ProviderBase
|
||||
from spotdl.metadata.exceptions import SpotifyMetadataNotFoundError
|
||||
from spotdl.metadata.providers import ProviderSpotify
|
||||
|
||||
class TestProviderSpotify:
|
||||
def test_subclass(self):
|
||||
assert issubclass(ProviderSpotify, ProviderBase)
|
||||
|
||||
# def test_metadata_not_found_error(self):
|
||||
# provider = ProviderSpotify(spotify=spotify)
|
||||
# with pytest.raises(SpotifyMetadataNotFoundError):
|
||||
# provider.from_query("This track doesn't exist on Spotify.")
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
from spotdl.metadata.providers.youtube import YouTubeSearch
|
||||
from spotdl.metadata.providers.youtube import YouTubeStreams
|
||||
from spotdl.metadata.providers import youtube
|
||||
from spotdl.metadata.providers import ProviderYouTube
|
||||
from spotdl.metadata.exceptions import YouTubeMetadataNotFoundError
|
||||
|
||||
import pytube
|
||||
import urllib.request
|
||||
import pickle
|
||||
import sys
|
||||
import os
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def track():
|
||||
return "selena gomez wolves"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def no_result_track():
|
||||
return "n0 v1d305 3x157 f0r 7h15 53arc4 qu3ry"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def expect_search_results():
|
||||
return [
|
||||
"https://www.youtube.com/watch?v=cH4E_t3m3xM",
|
||||
"https://www.youtube.com/watch?v=xrbY9gDVms0",
|
||||
"https://www.youtube.com/watch?v=jX0n2rSmDbE",
|
||||
"https://www.youtube.com/watch?v=nVzA1uWTydQ",
|
||||
"https://www.youtube.com/watch?v=rQ6jcpwzQZU",
|
||||
"https://www.youtube.com/watch?v=-grLLLTza6k",
|
||||
"https://www.youtube.com/watch?v=j0AxZ4V5WQw",
|
||||
"https://www.youtube.com/watch?v=zbWsb36U0uo",
|
||||
"https://www.youtube.com/watch?v=3B1aY9Ob8r0",
|
||||
"https://www.youtube.com/watch?v=hd2SGk90r9k",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def expect_mock_search_results():
|
||||
return [
|
||||
"https://www.youtube.com/watch?v=cH4E_t3m3xM",
|
||||
"https://www.youtube.com/watch?v=xrbY9gDVms0",
|
||||
"https://www.youtube.com/watch?v=jX0n2rSmDbE",
|
||||
"https://www.youtube.com/watch?v=rQ6jcpwzQZU",
|
||||
"https://www.youtube.com/watch?v=nVzA1uWTydQ",
|
||||
"https://www.youtube.com/watch?v=-grLLLTza6k",
|
||||
"https://www.youtube.com/watch?v=zbWsb36U0uo",
|
||||
"https://www.youtube.com/watch?v=rykH1BkGwTo",
|
||||
"https://www.youtube.com/watch?v=j0AxZ4V5WQw",
|
||||
"https://www.youtube.com/watch?v=RyxsaKfu-ZY",
|
||||
]
|
||||
|
||||
|
||||
class TestYouTubeSearch:
|
||||
@pytest.fixture(scope="module")
|
||||
def youtube_searcher(self):
|
||||
return YouTubeSearch()
|
||||
|
||||
def test_generate_search_url(self, track, youtube_searcher):
|
||||
url = youtube_searcher.generate_search_url(track)
|
||||
expect_url = "https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q=selena%20gomez%20wolves"
|
||||
assert url == expect_url
|
||||
|
||||
@pytest.mark.network
|
||||
def test_search(self, track, youtube_searcher, expect_search_results):
|
||||
results = youtube_searcher.search(track)
|
||||
assert results == expect_search_results
|
||||
|
||||
class MockHTTPResponse:
|
||||
response_file = ""
|
||||
|
||||
def __init__(self, url):
|
||||
pass
|
||||
|
||||
def read(self):
|
||||
module_directory = os.path.dirname(__file__)
|
||||
mock_html = os.path.join(module_directory, "data", self.response_file)
|
||||
with open(mock_html, "r") as fin:
|
||||
html = fin.read()
|
||||
return html
|
||||
|
||||
# @pytest.mark.mock
|
||||
def test_mock_search(self, track, youtube_searcher, expect_mock_search_results, monkeypatch):
|
||||
self.MockHTTPResponse.response_file = "youtube_search_results.html"
|
||||
monkeypatch.setattr(urllib.request, "urlopen", self.MockHTTPResponse)
|
||||
self.test_search(track, youtube_searcher, expect_mock_search_results)
|
||||
|
||||
@pytest.mark.network
|
||||
def test_no_videos_search(self, no_result_track, youtube_searcher):
|
||||
results = youtube_searcher.search(no_result_track)
|
||||
assert results == []
|
||||
|
||||
def test_mock_no_videos_search(self, no_result_track, youtube_searcher, monkeypatch):
|
||||
self.MockHTTPResponse.response_file = "youtube_no_search_results.html"
|
||||
monkeypatch.setattr(urllib.request, "urlopen", self.MockHTTPResponse)
|
||||
self.test_no_videos_search(no_result_track, youtube_searcher)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def content():
|
||||
return pytube.YouTube("https://www.youtube.com/watch?v=cH4E_t3m3xM")
|
||||
|
||||
|
||||
class MockYouTube:
|
||||
def __init__(self, url):
|
||||
self.watch_html = '\\"category\\":\\"Music\\",\\"publishDate\\":\\"2017-11-18\\",\\"ownerChannelName\\":\\"SelenaGomezVEVO\\",'
|
||||
self.title = "Selena Gomez, Marshmello - Wolves"
|
||||
self.author = "SelenaGomezVEVO"
|
||||
self.length = 213
|
||||
self.watch_url = "https://youtube.com/watch?v=cH4E_t3m3xM"
|
||||
self.thumbnail_url = "https://i.ytimg.com/vi/cH4E_t3m3xM/maxresdefault.jpg"
|
||||
|
||||
@property
|
||||
def streams(self):
|
||||
module_directory = os.path.dirname(__file__)
|
||||
mock_streams = os.path.join(module_directory, "data", "streams.dump")
|
||||
with open(mock_streams, "rb") as fin:
|
||||
streams_dump = pickle.load(fin)
|
||||
return streams_dump
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def mock_content():
|
||||
return MockYouTube("https://www.youtube.com/watch?v=cH4E_t3m3xM")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def expect_formatted_streams():
|
||||
return [
|
||||
{"bitrate": 160, "download_url": None, "encoding": "opus", "filesize": 3614184},
|
||||
{"bitrate": 128, "download_url": None, "encoding": "mp4a.40.2", "filesize": 3444850},
|
||||
{"bitrate": 70, "download_url": None, "encoding": "opus", "filesize": 1847626},
|
||||
{"bitrate": 50, "download_url": None, "encoding": "opus", "filesize": 1407962}
|
||||
]
|
||||
|
||||
|
||||
class TestYouTubeStreams:
|
||||
@pytest.mark.network
|
||||
def test_streams(self, content, expect_formatted_streams):
|
||||
formatted_streams = YouTubeStreams(content.streams)
|
||||
for index in range(len(formatted_streams.all)):
|
||||
assert isinstance(formatted_streams.all[index]["download_url"], str)
|
||||
formatted_streams.all[index]["download_url"] = None
|
||||
|
||||
assert formatted_streams.all == expect_formatted_streams
|
||||
|
||||
# @pytest.mark.mock
|
||||
def test_mock_streams(self, mock_content, expect_formatted_streams):
|
||||
self.test_streams(mock_content, expect_formatted_streams)
|
||||
|
||||
@pytest.mark.network
|
||||
def test_getbest(self, content):
|
||||
formatted_streams = YouTubeStreams(content.streams)
|
||||
best_stream = formatted_streams.getbest()
|
||||
best_stream["download_url"] = None
|
||||
assert best_stream == {
|
||||
"bitrate": 160,
|
||||
"download_url": None,
|
||||
"encoding": "opus",
|
||||
"filesize": 3614184
|
||||
}
|
||||
|
||||
# @pytest.mark.mock
|
||||
def test_mock_getbest(self, mock_content):
|
||||
self.test_getbest(mock_content)
|
||||
|
||||
@pytest.mark.network
|
||||
def test_getworst(self, content):
|
||||
formatted_streams = YouTubeStreams(content.streams)
|
||||
worst_stream = formatted_streams.getworst()
|
||||
worst_stream["download_url"] = None
|
||||
assert worst_stream == {
|
||||
"bitrate": 50,
|
||||
"download_url": None,
|
||||
"encoding": 'opus',
|
||||
"filesize": 1407962
|
||||
}
|
||||
|
||||
# @pytest.mark.mock
|
||||
def test_mock_getworst(self, mock_content):
|
||||
self.test_getworst(mock_content)
|
||||
|
||||
|
||||
class TestProviderYouTube:
|
||||
@pytest.fixture(scope="module")
|
||||
def youtube_provider(self):
|
||||
return ProviderYouTube()
|
||||
|
||||
class MockYouTubeSearch:
|
||||
watch_urls = []
|
||||
def search(self, query):
|
||||
return self.watch_urls
|
||||
|
||||
@pytest.mark.network
|
||||
def test_from_query(self, track, youtube_provider):
|
||||
metadata = youtube_provider.from_query(track)
|
||||
assert isinstance(metadata["streams"], YouTubeStreams)
|
||||
|
||||
metadata["streams"] = []
|
||||
assert metadata == {
|
||||
'album': {'artists': [{'name': None}],
|
||||
'images': [{'url': 'https://i.ytimg.com/vi/cH4E_t3m3xM/maxresdefault.jpg'}],
|
||||
'name': None},
|
||||
'artists': [{'name': 'SelenaGomezVEVO'}],
|
||||
'copyright': None,
|
||||
'disc_number': 1,
|
||||
'duration': 213,
|
||||
'external_ids': {'isrc': None},
|
||||
'external_urls': {'youtube': 'https://youtube.com/watch?v=cH4E_t3m3xM'},
|
||||
'genre': None,
|
||||
'lyrics': None,
|
||||
'name': 'Selena Gomez, Marshmello - Wolves',
|
||||
'provider': 'youtube',
|
||||
'publisher': None,
|
||||
'release_date': '2017-11-1',
|
||||
'streams': [],
|
||||
'total_tracks': 1,
|
||||
'track_number': 1,
|
||||
'type': 'track',
|
||||
'year': '2017'
|
||||
}
|
||||
|
||||
def test_mock_from_query(self, track, youtube_provider, expect_mock_search_results, monkeypatch):
|
||||
self.MockYouTubeSearch.watch_urls = expect_mock_search_results
|
||||
monkeypatch.setattr(youtube, "YouTubeSearch", self.MockYouTubeSearch)
|
||||
monkeypatch.setattr(pytube, "YouTube", MockYouTube)
|
||||
self.test_from_query(track, youtube_provider)
|
||||
|
||||
@pytest.mark.network
|
||||
def test_error_exception_from_query(self, no_result_track, youtube_provider):
|
||||
with pytest.raises(YouTubeMetadataNotFoundError):
|
||||
youtube_provider.from_query(no_result_track)
|
||||
|
||||
def test_mock_error_exception_from_query(self, no_result_track, youtube_provider, monkeypatch):
|
||||
self.MockYouTubeSearch.watch_urls = []
|
||||
monkeypatch.setattr(youtube, "YouTubeSearch", self.MockYouTubeSearch)
|
||||
monkeypatch.setattr(pytube, "YouTube", MockYouTube)
|
||||
self.test_error_exception_from_query(no_result_track, youtube_provider)
|
||||
|
||||
@@ -5,6 +5,7 @@ import urllib.request
|
||||
|
||||
from spotdl.metadata import StreamsBase
|
||||
from spotdl.metadata import ProviderBase
|
||||
from spotdl.metadata.exceptions import YouTubeMetadataNotFoundError
|
||||
|
||||
BASE_URL = "https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q={}"
|
||||
|
||||
@@ -77,6 +78,9 @@ class YouTubeStreams(StreamsBase):
|
||||
def __init__(self, streams):
|
||||
audiostreams = streams.filter(only_audio=True).order_by("abr").desc()
|
||||
self.all = [{
|
||||
# Store only the integer part. For example the given
|
||||
# bitrate would be "192kbps", we store only the integer
|
||||
# part here and drop the rest.
|
||||
"bitrate": int(stream.abr[:-4]),
|
||||
"download_url": stream.url,
|
||||
"encoding": stream.audio_codec,
|
||||
@@ -93,6 +97,11 @@ class YouTubeStreams(StreamsBase):
|
||||
class ProviderYouTube(ProviderBase):
|
||||
def from_query(self, query):
|
||||
watch_urls = YouTubeSearch().search(query)
|
||||
if not watch_urls:
|
||||
raise YouTubeMetadataNotFoundError(
|
||||
'YouTube returned nothing for the given search '
|
||||
'query ("{}")'.format(query)
|
||||
)
|
||||
return self.from_url(watch_urls[0])
|
||||
|
||||
def from_url(self, url):
|
||||
@@ -111,7 +120,6 @@ class ProviderYouTube(ProviderBase):
|
||||
|
||||
def metadata_to_standard_form(self, content):
|
||||
""" Fetch a song's metadata from YouTube. """
|
||||
streams = []
|
||||
publish_date = self._fetch_publish_date(content)
|
||||
metadata = {
|
||||
"name": content.title,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from spotdl.metadata.exceptions import MetadataNotFoundError
|
||||
from spotdl.metadata.exceptions import SpotifyMetadataNotFoundError
|
||||
from spotdl.metadata.exceptions import YouTubeMetadataNotFoundError
|
||||
|
||||
|
||||
class TestMetadataNotFoundSubclass:
|
||||
def test_metadata_not_found_subclass(self):
|
||||
assert issubclass(MetadataNotFoundError, Exception)
|
||||
|
||||
def test_spotify_metadata_not_found(self):
|
||||
assert issubclass(SpotifyMetadataNotFoundError, MetadataNotFoundError)
|
||||
|
||||
def test_youtube_metadata_not_found(self):
|
||||
assert issubclass(YouTubeMetadataNotFoundError, MetadataNotFoundError)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
from spotdl.metadata import ProviderBase
|
||||
from spotdl.metadata import StreamsBase
|
||||
|
||||
import pytest
|
||||
|
||||
class TestStreamsBaseABC:
|
||||
def test_error_abstract_base_class_streamsbase(self):
|
||||
with pytest.raises(TypeError):
|
||||
# This abstract base class must be inherited from
|
||||
# for instantiation
|
||||
StreamsBase()
|
||||
|
||||
def test_inherit_abstract_base_class_streamsbase(self):
|
||||
class StreamsKid(StreamsBase):
|
||||
def __init__(self, streams):
|
||||
super().__init__(streams)
|
||||
|
||||
streams = ("stream1", "stream2", "stream3")
|
||||
kid = StreamsKid(streams)
|
||||
assert kid.all == streams
|
||||
|
||||
|
||||
class TestMethods:
|
||||
class StreamsKid(StreamsBase):
|
||||
def __init__(self, streams):
|
||||
super().__init__(streams)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def streamskid(self):
|
||||
streams = ("stream1", "stream2", "stream3")
|
||||
streamskid = self.StreamsKid(streams)
|
||||
return streamskid
|
||||
|
||||
def test_getbest(self, streamskid):
|
||||
best_stream = streamskid.getbest()
|
||||
assert best_stream == "stream1"
|
||||
|
||||
def test_getworst(self, streamskid):
|
||||
worst_stream = streamskid.getworst()
|
||||
assert worst_stream == "stream3"
|
||||
|
||||
|
||||
class TestProviderBaseABC:
|
||||
def test_error_abstract_base_class_providerbase(self):
|
||||
with pytest.raises(TypeError):
|
||||
# This abstract base class must be inherited from
|
||||
# for instantiation
|
||||
ProviderBase()
|
||||
|
||||
def test_inherit_abstract_base_class_providerbase(self):
|
||||
class ProviderKid(ProviderBase):
|
||||
def from_url(self, query):
|
||||
pass
|
||||
|
||||
def metadata_to_standard_form(self, metadata):
|
||||
pass
|
||||
|
||||
ProviderKid()
|
||||
|
||||
Reference in New Issue
Block a user