mirror of
https://github.com/KevinMidboe/spotify-downloader.git
synced 2025-10-29 18:00:15 +00:00
[WIP] Monkeypatch tests (#448)
* Parameterize test_internals.py * Create test_spotify_tools.py * Monkeypatch pafy.download * Monkeypatch YouTube search page * Replace globals with fixtures * Add missing urllib import, re-ordering and rename test_with_metadata.py * Avoid creating temp directory in current working directory during test * Update CHANGES.md
This commit is contained in:
@@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||||||
- Ability to pass multiple tracks with `-s` option ([@ritiek](https://github.com/ritiek)) (#442)
|
- Ability to pass multiple tracks with `-s` option ([@ritiek](https://github.com/ritiek)) (#442)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Change FFmpeg to use the built-in encoder `aac` instead of 3rd party `libfdk-aac` which does not
|
||||||
|
ship with the apt package ([@ritiek](https://github.com/ritiek)) (#448)
|
||||||
|
- Monkeypatch ever-changing network-relying tests ([@ritiek](https://github.com/ritiek)) (#448)
|
||||||
- Correct `.m4a` container before writing metadata so metadata fields shows up properly in
|
- Correct `.m4a` container before writing metadata so metadata fields shows up properly in
|
||||||
media players (especially iTunes) ([@ritiek](https://github.com/ritiek) with thanks to [@Amit-L](https://github.com/Amit-L)!) (#453)
|
media players (especially iTunes) ([@ritiek](https://github.com/ritiek) with thanks to [@Amit-L](https://github.com/Amit-L)!) (#453)
|
||||||
- Refactored core downloading module ([@ritiek](https://github.com/ritiek)) (#410)
|
- Refactored core downloading module ([@ritiek](https://github.com/ritiek)) (#410)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import os
|
|||||||
from logzero import logger as log
|
from logzero import logger as log
|
||||||
|
|
||||||
|
|
||||||
"""What are the differences and similarities between ffmpeg, libav, and avconv?
|
"""
|
||||||
|
What are the differences and similarities between ffmpeg, libav, and avconv?
|
||||||
https://stackoverflow.com/questions/9477115
|
https://stackoverflow.com/questions/9477115
|
||||||
|
|
||||||
ffmeg encoders high to lower quality
|
ffmeg encoders high to lower quality
|
||||||
@@ -25,10 +26,10 @@ def song(input_song, output_song, folder, avconv=False, trim_silence=False):
|
|||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
if avconv:
|
if avconv:
|
||||||
exit_code = convert.with_avconv()
|
exit_code, command = convert.with_avconv()
|
||||||
else:
|
else:
|
||||||
exit_code = convert.with_ffmpeg()
|
exit_code, command = convert.with_ffmpeg()
|
||||||
return exit_code
|
return exit_code, command
|
||||||
|
|
||||||
|
|
||||||
class Converter:
|
class Converter:
|
||||||
@@ -59,7 +60,7 @@ class Converter:
|
|||||||
log.warning("--trim-silence not supported with avconv")
|
log.warning("--trim-silence not supported with avconv")
|
||||||
|
|
||||||
log.debug(command)
|
log.debug(command)
|
||||||
return subprocess.call(command)
|
return subprocess.call(command), command
|
||||||
|
|
||||||
def with_ffmpeg(self):
|
def with_ffmpeg(self):
|
||||||
ffmpeg_pre = "ffmpeg -y "
|
ffmpeg_pre = "ffmpeg -y "
|
||||||
@@ -84,7 +85,7 @@ class Converter:
|
|||||||
if output_ext == ".mp3":
|
if output_ext == ".mp3":
|
||||||
ffmpeg_params = "-codec:a libmp3lame -ar 44100 "
|
ffmpeg_params = "-codec:a libmp3lame -ar 44100 "
|
||||||
elif output_ext == ".m4a":
|
elif output_ext == ".m4a":
|
||||||
ffmpeg_params = "-cutoff 20000 -codec:a libfdk_aac -ar 44100 "
|
ffmpeg_params = "-cutoff 20000 -codec:a aac -ar 44100 "
|
||||||
|
|
||||||
if output_ext == ".flac":
|
if output_ext == ".flac":
|
||||||
ffmpeg_params = "-codec:a flac -ar 44100 "
|
ffmpeg_params = "-codec:a flac -ar 44100 "
|
||||||
@@ -104,4 +105,4 @@ class Converter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
log.debug(command)
|
log.debug(command)
|
||||||
return subprocess.call(command)
|
return subprocess.call(command), command
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import spotipy
|
||||||
|
import urllib
|
||||||
|
import os
|
||||||
|
from logzero import logger as log
|
||||||
|
|
||||||
from spotdl import const
|
from spotdl import const
|
||||||
from spotdl import metadata
|
from spotdl import metadata
|
||||||
from spotdl import convert
|
from spotdl import convert
|
||||||
@@ -5,10 +10,6 @@ from spotdl import internals
|
|||||||
from spotdl import spotify_tools
|
from spotdl import spotify_tools
|
||||||
from spotdl import youtube_tools
|
from spotdl import youtube_tools
|
||||||
|
|
||||||
import spotipy
|
|
||||||
from logzero import logger as log
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class CheckExists:
|
class CheckExists:
|
||||||
def __init__(self, music_file, meta_tags=None):
|
def __init__(self, music_file, meta_tags=None):
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import appdirs
|
|
||||||
from spotdl import internals
|
|
||||||
from logzero import logger as log
|
from logzero import logger as log
|
||||||
|
import appdirs
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import yaml
|
import yaml
|
||||||
import argparse
|
import argparse
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from spotdl import internals
|
||||||
|
|
||||||
|
|
||||||
_LOG_LEVELS_STR = ["INFO", "WARNING", "ERROR", "DEBUG"]
|
_LOG_LEVELS_STR = ["INFO", "WARNING", "ERROR", "DEBUG"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
from logzero import logger as log
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from logzero import logger as log
|
|
||||||
|
|
||||||
from spotdl import const
|
from spotdl import const
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ from mutagen.easyid3 import EasyID3
|
|||||||
from mutagen.id3 import ID3, TORY, TYER, TPUB, APIC, USLT, COMM
|
from mutagen.id3 import ID3, TORY, TYER, TPUB, APIC, USLT, COMM
|
||||||
from mutagen.mp4 import MP4, MP4Cover
|
from mutagen.mp4 import MP4, MP4Cover
|
||||||
from mutagen.flac import Picture, FLAC
|
from mutagen.flac import Picture, FLAC
|
||||||
from logzero import logger as log
|
|
||||||
from spotdl.const import TAG_PRESET, M4A_TAG_PRESET
|
|
||||||
|
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
from logzero import logger as log
|
||||||
|
|
||||||
|
from spotdl.const import TAG_PRESET, M4A_TAG_PRESET
|
||||||
|
|
||||||
|
|
||||||
def compare(music_file, metadata):
|
def compare(music_file, metadata):
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import platform
|
||||||
|
import pprint
|
||||||
|
import logzero
|
||||||
|
from logzero import logger as log
|
||||||
|
|
||||||
from spotdl import __version__
|
from spotdl import __version__
|
||||||
from spotdl import const
|
from spotdl import const
|
||||||
from spotdl import handle
|
from spotdl import handle
|
||||||
@@ -7,11 +13,6 @@ from spotdl import internals
|
|||||||
from spotdl import spotify_tools
|
from spotdl import spotify_tools
|
||||||
from spotdl import youtube_tools
|
from spotdl import youtube_tools
|
||||||
from spotdl import downloader
|
from spotdl import downloader
|
||||||
from logzero import logger as log
|
|
||||||
import logzero
|
|
||||||
import sys
|
|
||||||
import platform
|
|
||||||
import pprint
|
|
||||||
|
|
||||||
|
|
||||||
def debug_sys_info():
|
def debug_sys_info():
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import spotipy
|
import spotipy
|
||||||
import spotipy.oauth2 as oauth2
|
import spotipy.oauth2 as oauth2
|
||||||
import lyricwikia
|
import lyricwikia
|
||||||
from logzero import logger as log
|
|
||||||
|
|
||||||
from spotdl import internals
|
|
||||||
|
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from titlecase import titlecase
|
from titlecase import titlecase
|
||||||
|
from logzero import logger as log
|
||||||
import pprint
|
import pprint
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from spotdl import internals
|
||||||
|
|
||||||
|
|
||||||
def generate_token():
|
def generate_token():
|
||||||
""" Generate the token. Please respect these credentials :) """
|
""" Generate the token. Please respect these credentials :) """
|
||||||
@@ -29,8 +29,8 @@ def refresh_token():
|
|||||||
|
|
||||||
# token is mandatory when using Spotify's API
|
# token is mandatory when using Spotify's API
|
||||||
# https://developer.spotify.com/news-stories/2017/01/27/removing-unauthenticated-calls-to-the-web-api/
|
# https://developer.spotify.com/news-stories/2017/01/27/removing-unauthenticated-calls-to-the-web-api/
|
||||||
token = generate_token()
|
_token = generate_token()
|
||||||
spotify = spotipy.Spotify(auth=token)
|
spotify = spotipy.Spotify(auth=_token)
|
||||||
|
|
||||||
|
|
||||||
def generate_metadata(raw_song):
|
def generate_metadata(raw_song):
|
||||||
@@ -87,12 +87,6 @@ def generate_metadata(raw_song):
|
|||||||
return meta_tags
|
return meta_tags
|
||||||
|
|
||||||
|
|
||||||
def write_user_playlist(username, text_file=None):
|
|
||||||
links = get_playlists(username=username)
|
|
||||||
playlist = internals.input_link(links)
|
|
||||||
return write_playlist(playlist, text_file)
|
|
||||||
|
|
||||||
|
|
||||||
def get_playlists(username):
|
def get_playlists(username):
|
||||||
""" Fetch user playlists when using the -u option. """
|
""" Fetch user playlists when using the -u option. """
|
||||||
playlists = spotify.user_playlists(username)
|
playlists = spotify.user_playlists(username)
|
||||||
@@ -121,6 +115,12 @@ def get_playlists(username):
|
|||||||
return links
|
return links
|
||||||
|
|
||||||
|
|
||||||
|
def write_user_playlist(username, text_file=None):
|
||||||
|
links = get_playlists(username=username)
|
||||||
|
playlist = internals.input_link(links)
|
||||||
|
return write_playlist(playlist, text_file)
|
||||||
|
|
||||||
|
|
||||||
def fetch_playlist(playlist):
|
def fetch_playlist(playlist):
|
||||||
try:
|
try:
|
||||||
playlist_id = internals.extract_spotify_id(playlist)
|
playlist_id = internals.extract_spotify_id(playlist)
|
||||||
@@ -154,7 +154,7 @@ def fetch_album(album):
|
|||||||
return album
|
return album
|
||||||
|
|
||||||
|
|
||||||
def fetch_album_from_artist(artist_url, album_type="album"):
|
def fetch_albums_from_artist(artist_url, album_type="album"):
|
||||||
"""
|
"""
|
||||||
This funcction returns all the albums from a give artist_url using the US
|
This funcction returns all the albums from a give artist_url using the US
|
||||||
market
|
market
|
||||||
@@ -191,7 +191,7 @@ def write_all_albums_from_artist(artist_url, text_file=None):
|
|||||||
album_base_url = "https://open.spotify.com/album/"
|
album_base_url = "https://open.spotify.com/album/"
|
||||||
|
|
||||||
# fetching all default albums
|
# fetching all default albums
|
||||||
albums = fetch_album_from_artist(artist_url)
|
albums = fetch_albums_from_artist(artist_url)
|
||||||
|
|
||||||
# if no file if given, the default save file is in the current working
|
# if no file if given, the default save file is in the current working
|
||||||
# directory with the name of the artist
|
# directory with the name of the artist
|
||||||
@@ -204,7 +204,7 @@ def write_all_albums_from_artist(artist_url, text_file=None):
|
|||||||
write_album(album_base_url + album["id"], text_file=text_file)
|
write_album(album_base_url + album["id"], text_file=text_file)
|
||||||
|
|
||||||
# fetching all single albums
|
# fetching all single albums
|
||||||
singles = fetch_album_from_artist(artist_url, album_type="single")
|
singles = fetch_albums_from_artist(artist_url, album_type="single")
|
||||||
|
|
||||||
for single in singles:
|
for single in singles:
|
||||||
log.info("Fetching single: " + single["name"])
|
log.info("Fetching single: " + single["name"])
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import urllib
|
import urllib
|
||||||
import pafy
|
import pafy
|
||||||
|
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from logzero import logger as log
|
from logzero import logger as log
|
||||||
|
import os
|
||||||
|
|
||||||
from spotdl import spotify_tools
|
from spotdl import spotify_tools
|
||||||
from spotdl import internals
|
from spotdl import internals
|
||||||
from spotdl import const
|
from spotdl import const
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Fix download speed throttle on short duration tracks
|
# Fix download speed throttle on short duration tracks
|
||||||
# Read more on mps-youtube/pafy#199
|
# Read more on mps-youtube/pafy#199
|
||||||
pafy.g.opener.addheaders.append(("Range", "bytes=0-"))
|
pafy.g.opener.addheaders.append(("Range", "bytes=0-"))
|
||||||
@@ -75,6 +75,8 @@ def generate_m3u(track_file):
|
|||||||
log.info("Generating {0} from {1} YouTube URLs".format(target_file, total_tracks))
|
log.info("Generating {0} from {1} YouTube URLs".format(target_file, total_tracks))
|
||||||
with open(target_file, "w") as output_file:
|
with open(target_file, "w") as output_file:
|
||||||
output_file.write("#EXTM3U\n\n")
|
output_file.write("#EXTM3U\n\n")
|
||||||
|
|
||||||
|
videos = []
|
||||||
for n, track in enumerate(tracks, 1):
|
for n, track in enumerate(tracks, 1):
|
||||||
content, _ = match_video_and_metadata(track)
|
content, _ = match_video_and_metadata(track)
|
||||||
if content is None:
|
if content is None:
|
||||||
@@ -94,6 +96,9 @@ def generate_m3u(track_file):
|
|||||||
log.debug(m3u_key)
|
log.debug(m3u_key)
|
||||||
with open(target_file, "a") as output_file:
|
with open(target_file, "a") as output_file:
|
||||||
output_file.write(m3u_key)
|
output_file.write(m3u_key)
|
||||||
|
videos.append(content.watchv_url)
|
||||||
|
|
||||||
|
return videos
|
||||||
|
|
||||||
|
|
||||||
def download_song(file_name, content):
|
def download_song(file_name, content):
|
||||||
@@ -240,7 +245,7 @@ class GenerateYouTubeURL:
|
|||||||
search_url = generate_search_url(self.search_query)
|
search_url = generate_search_url(self.search_query)
|
||||||
log.debug("Opening URL: {0}".format(search_url))
|
log.debug("Opening URL: {0}".format(search_url))
|
||||||
|
|
||||||
item = urllib.request.urlopen(search_url).read()
|
item = self._fetch_response(search_url).read()
|
||||||
items_parse = BeautifulSoup(item, "html.parser")
|
items_parse = BeautifulSoup(item, "html.parser")
|
||||||
|
|
||||||
videos = []
|
videos = []
|
||||||
@@ -319,3 +324,11 @@ class GenerateYouTubeURL:
|
|||||||
return self._best_match(videos)
|
return self._best_match(videos)
|
||||||
|
|
||||||
return videos
|
return videos
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _fetch_response(url):
|
||||||
|
# XXX: This method exists only because it helps us indirectly
|
||||||
|
# monkey patch `urllib.request.open`, directly monkey patching
|
||||||
|
# `urllib.request.open` causes us to end up in an infinite recursion
|
||||||
|
# during the test since `urllib.request.open` would monkeypatch itself.
|
||||||
|
return urllib.request.urlopen(url)
|
||||||
|
|||||||
195
test/test_download_with_metadata.py
Normal file
195
test/test_download_with_metadata.py
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import urllib
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
from spotdl import const
|
||||||
|
from spotdl import internals
|
||||||
|
from spotdl import spotify_tools
|
||||||
|
from spotdl import youtube_tools
|
||||||
|
from spotdl import convert
|
||||||
|
from spotdl import metadata
|
||||||
|
from spotdl import downloader
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import loader
|
||||||
|
|
||||||
|
loader.load_defaults()
|
||||||
|
|
||||||
|
SPOTIFY_TRACK_URL = "https://open.spotify.com/track/3SipFlNddvL0XNZRLXvdZD"
|
||||||
|
EXPECTED_YOUTUBE_TITLE = "Janji - Heroes Tonight (feat. Johnning) [NCS Release]"
|
||||||
|
EXPECTED_SPOTIFY_TITLE = "Janji - Heroes Tonight"
|
||||||
|
EXPECTED_YOUTUBE_URL = "http://youtube.com/watch?v=3nQNiWdeH2Q"
|
||||||
|
# GIST_URL is the monkeypatched version of: https://www.youtube.com/results?search_query=janji+-+heroes
|
||||||
|
# so that we get same results even if YouTube changes the list/order of videos on their page.
|
||||||
|
GIST_URL = "https://gist.githubusercontent.com/ritiek/e731338e9810e31c2f00f13c249a45f5/raw/c11a27f3b5d11a8d082976f1cdd237bd605ec2c2/search_results.html"
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_namespace():
|
||||||
|
# XXX: We override the value of `content_fixture` later in the tests.
|
||||||
|
# We do not use an acutal @pytest.fixture because it does not accept
|
||||||
|
# the monkeypatch parameter and we need to monkeypatch the network
|
||||||
|
# request before creating the Pafy object.
|
||||||
|
return {"content_fixture": None}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def metadata_fixture():
|
||||||
|
meta_tags = spotify_tools.generate_metadata(SPOTIFY_TRACK_URL)
|
||||||
|
return meta_tags
|
||||||
|
|
||||||
|
|
||||||
|
def test_metadata(metadata_fixture):
|
||||||
|
expect_number = 23
|
||||||
|
assert len(metadata_fixture) == expect_number
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileFormat:
|
||||||
|
def test_with_spaces(self, metadata_fixture):
|
||||||
|
title = internals.format_string(const.args.file_format, metadata_fixture)
|
||||||
|
assert title == EXPECTED_SPOTIFY_TITLE
|
||||||
|
|
||||||
|
def test_without_spaces(self, metadata_fixture):
|
||||||
|
const.args.no_spaces = True
|
||||||
|
title = internals.format_string(const.args.file_format, metadata_fixture)
|
||||||
|
assert title == EXPECTED_SPOTIFY_TITLE.replace(" ", "_")
|
||||||
|
|
||||||
|
|
||||||
|
def monkeypatch_youtube_search_page(*args, **kwargs):
|
||||||
|
fake_urlopen = urllib.request.urlopen(GIST_URL)
|
||||||
|
return fake_urlopen
|
||||||
|
|
||||||
|
|
||||||
|
def test_youtube_url(metadata_fixture, monkeypatch):
|
||||||
|
monkeypatch.setattr(youtube_tools.GenerateYouTubeURL, "_fetch_response", monkeypatch_youtube_search_page)
|
||||||
|
url = youtube_tools.generate_youtube_url(SPOTIFY_TRACK_URL, metadata_fixture)
|
||||||
|
assert url == EXPECTED_YOUTUBE_URL
|
||||||
|
|
||||||
|
|
||||||
|
def test_youtube_title(metadata_fixture, monkeypatch):
|
||||||
|
monkeypatch.setattr(youtube_tools.GenerateYouTubeURL, "_fetch_response", monkeypatch_youtube_search_page)
|
||||||
|
content = youtube_tools.go_pafy(SPOTIFY_TRACK_URL, metadata_fixture)
|
||||||
|
pytest.content_fixture = content
|
||||||
|
title = youtube_tools.get_youtube_title(content)
|
||||||
|
assert title == EXPECTED_YOUTUBE_TITLE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def filename_fixture(metadata_fixture):
|
||||||
|
songname = internals.format_string(const.args.file_format, metadata_fixture)
|
||||||
|
filename = internals.sanitize_title(songname)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_track_exists_before_download(tmpdir, metadata_fixture, filename_fixture):
|
||||||
|
expect_check = False
|
||||||
|
const.args.folder = str(tmpdir)
|
||||||
|
# prerequisites for determining filename
|
||||||
|
track_existence = downloader.CheckExists(filename_fixture, metadata_fixture)
|
||||||
|
check = track_existence.already_exists(SPOTIFY_TRACK_URL)
|
||||||
|
assert check == expect_check
|
||||||
|
|
||||||
|
|
||||||
|
class TestDownload:
|
||||||
|
def blank_audio_generator(self, filepath):
|
||||||
|
if filepath.endswith(".m4a"):
|
||||||
|
cmd = "ffmpeg -f lavfi -i anullsrc -t 1 -c:a aac {}".format(filepath)
|
||||||
|
elif filepath.endswith(".webm"):
|
||||||
|
cmd = "ffmpeg -f lavfi -i anullsrc -t 1 -c:a libopus {}".format(filepath)
|
||||||
|
subprocess.call(cmd.split(" "))
|
||||||
|
|
||||||
|
def test_m4a(self, monkeypatch, filename_fixture):
|
||||||
|
expect_download = True
|
||||||
|
monkeypatch.setattr("pafy.backend_shared.BaseStream.download", self.blank_audio_generator)
|
||||||
|
download = youtube_tools.download_song(filename_fixture + ".m4a", pytest.content_fixture)
|
||||||
|
assert download == expect_download
|
||||||
|
|
||||||
|
def test_webm(self, monkeypatch, filename_fixture):
|
||||||
|
expect_download = True
|
||||||
|
monkeypatch.setattr("pafy.backend_shared.BaseStream.download", self.blank_audio_generator)
|
||||||
|
download = youtube_tools.download_song(filename_fixture + ".webm", pytest.content_fixture)
|
||||||
|
assert download == expect_download
|
||||||
|
|
||||||
|
|
||||||
|
class TestFFmpeg:
|
||||||
|
def test_convert_from_webm_to_mp3(self, filename_fixture, monkeypatch):
|
||||||
|
expect_command = "ffmpeg -y -i {0}.webm -codec:a libmp3lame -ar 44100 -b:a 192k -vn {0}.mp3".format(os.path.join(const.args.folder, filename_fixture))
|
||||||
|
_, command = convert.song(
|
||||||
|
filename_fixture + ".webm", filename_fixture + ".mp3", const.args.folder
|
||||||
|
)
|
||||||
|
assert ' '.join(command) == expect_command
|
||||||
|
|
||||||
|
def test_convert_from_webm_to_m4a(self, filename_fixture, monkeypatch):
|
||||||
|
expect_command = "ffmpeg -y -i {0}.webm -cutoff 20000 -codec:a aac -ar 44100 -b:a 192k -vn {0}.m4a".format(os.path.join(const.args.folder, filename_fixture))
|
||||||
|
_, command = convert.song(
|
||||||
|
filename_fixture + ".webm", filename_fixture + ".m4a", const.args.folder
|
||||||
|
)
|
||||||
|
assert ' '.join(command) == expect_command
|
||||||
|
|
||||||
|
def test_convert_from_m4a_to_mp3(self, filename_fixture, monkeypatch):
|
||||||
|
expect_command = "ffmpeg -y -i {0}.m4a -codec:v copy -codec:a libmp3lame -ar 44100 -b:a 192k -vn {0}.mp3".format(os.path.join(const.args.folder, filename_fixture))
|
||||||
|
_, command = convert.song(
|
||||||
|
filename_fixture + ".m4a", filename_fixture + ".mp3", const.args.folder
|
||||||
|
)
|
||||||
|
assert ' '.join(command) == expect_command
|
||||||
|
|
||||||
|
def test_convert_from_m4a_to_webm(self, filename_fixture, monkeypatch):
|
||||||
|
expect_command = "ffmpeg -y -i {0}.m4a -codec:a libopus -vbr on -b:a 192k -vn {0}.webm".format(os.path.join(const.args.folder, filename_fixture))
|
||||||
|
_, command = convert.song(
|
||||||
|
filename_fixture + ".m4a", filename_fixture + ".webm", const.args.folder
|
||||||
|
)
|
||||||
|
assert ' '.join(command) == expect_command
|
||||||
|
|
||||||
|
def test_convert_from_m4a_to_flac(self, filename_fixture, monkeypatch):
|
||||||
|
expect_command = "ffmpeg -y -i {0}.m4a -codec:a flac -ar 44100 -b:a 192k -vn {0}.flac".format(os.path.join(const.args.folder, filename_fixture))
|
||||||
|
_, command = convert.song(
|
||||||
|
filename_fixture + ".m4a", filename_fixture + ".flac", const.args.folder
|
||||||
|
)
|
||||||
|
assert ' '.join(command) == expect_command
|
||||||
|
|
||||||
|
|
||||||
|
class TestAvconv:
|
||||||
|
def test_convert_from_m4a_to_mp3(self, filename_fixture):
|
||||||
|
expect_command = "avconv -loglevel debug -i {0}.m4a -ab 192k {0}.mp3 -y".format(os.path.join(const.args.folder, filename_fixture))
|
||||||
|
_, command = convert.song(
|
||||||
|
filename_fixture + ".m4a", filename_fixture + ".mp3", const.args.folder, avconv=True
|
||||||
|
)
|
||||||
|
assert ' '.join(command) == expect_command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def trackpath_fixture(filename_fixture):
|
||||||
|
trackpath = os.path.join(const.args.folder, filename_fixture)
|
||||||
|
return trackpath
|
||||||
|
|
||||||
|
|
||||||
|
class TestEmbedMetadata:
|
||||||
|
def test_embed_in_mp3(self, metadata_fixture, trackpath_fixture):
|
||||||
|
expect_embed = True
|
||||||
|
embed = metadata.embed(trackpath_fixture + ".mp3", metadata_fixture)
|
||||||
|
assert embed == expect_embed
|
||||||
|
|
||||||
|
def test_embed_in_m4a(self, metadata_fixture, trackpath_fixture):
|
||||||
|
expect_embed = True
|
||||||
|
embed = metadata.embed(trackpath_fixture + ".m4a", metadata_fixture)
|
||||||
|
os.remove(trackpath_fixture + ".m4a")
|
||||||
|
assert embed == expect_embed
|
||||||
|
|
||||||
|
def test_embed_in_webm(self, metadata_fixture, trackpath_fixture):
|
||||||
|
expect_embed = False
|
||||||
|
embed = metadata.embed(trackpath_fixture + ".webm", metadata_fixture)
|
||||||
|
os.remove(trackpath_fixture + ".webm")
|
||||||
|
assert embed == expect_embed
|
||||||
|
|
||||||
|
def test_embed_in_flac(self, metadata_fixture, trackpath_fixture):
|
||||||
|
expect_embed = True
|
||||||
|
embed = metadata.embed(trackpath_fixture + ".flac", metadata_fixture)
|
||||||
|
os.remove(trackpath_fixture + ".flac")
|
||||||
|
assert embed == expect_embed
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_track_exists_after_download(metadata_fixture, filename_fixture, trackpath_fixture):
|
||||||
|
expect_check = True
|
||||||
|
track_existence = downloader.CheckExists(filename_fixture, metadata_fixture)
|
||||||
|
check = track_existence.already_exists(SPOTIFY_TRACK_URL)
|
||||||
|
os.remove(trackpath_fixture + ".mp3")
|
||||||
|
assert check == expect_check
|
||||||
@@ -25,28 +25,37 @@ def test_log_str_to_int():
|
|||||||
assert levels == expect_levels
|
assert levels == expect_levels
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def config_path_fixture(tmpdir_factory):
|
||||||
|
config_path = os.path.join(str(tmpdir_factory.mktemp("config")),
|
||||||
|
"config.yml")
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def modified_config_fixture():
|
||||||
|
modified_config = dict(handle.default_conf)
|
||||||
|
return modified_config
|
||||||
|
|
||||||
|
|
||||||
class TestConfig:
|
class TestConfig:
|
||||||
def test_default_config(self, tmpdir):
|
def test_default_config(self, config_path_fixture):
|
||||||
expect_config = handle.default_conf["spotify-downloader"]
|
expect_config = handle.default_conf["spotify-downloader"]
|
||||||
global config_path
|
config = handle.get_config(config_path_fixture)
|
||||||
config_path = os.path.join(str(tmpdir), "config.yml")
|
|
||||||
config = handle.get_config(config_path)
|
|
||||||
assert config == expect_config
|
assert config == expect_config
|
||||||
|
|
||||||
def test_modified_config(self):
|
def test_modified_config(self, modified_config_fixture):
|
||||||
global modified_config
|
modified_config_fixture["spotify-downloader"]["file-format"] = "just_a_test"
|
||||||
modified_config = dict(handle.default_conf)
|
merged_config = handle.merge(handle.default_conf, modified_config_fixture)
|
||||||
modified_config["spotify-downloader"]["file-format"] = "just_a_test"
|
assert merged_config == modified_config_fixture
|
||||||
merged_config = handle.merge(handle.default_conf, modified_config)
|
|
||||||
assert merged_config == modified_config
|
|
||||||
|
|
||||||
def test_custom_config_path(self, tmpdir):
|
def test_custom_config_path(self, config_path_fixture, modified_config_fixture):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
with open(config_path, "w") as config_file:
|
with open(config_path_fixture, "w") as config_file:
|
||||||
yaml.dump(modified_config, config_file, default_flow_style=False)
|
yaml.dump(modified_config_fixture, config_file, default_flow_style=False)
|
||||||
overridden_config = handle.override_config(config_path, parser, raw_args="")
|
overridden_config = handle.override_config(config_path_fixture, parser, raw_args="")
|
||||||
modified_values = [
|
modified_values = [
|
||||||
str(value) for value in modified_config["spotify-downloader"].values()
|
str(value) for value in modified_config_fixture["spotify-downloader"].values()
|
||||||
]
|
]
|
||||||
overridden_config.folder = os.path.realpath(overridden_config.folder)
|
overridden_config.folder = os.path.realpath(overridden_config.folder)
|
||||||
overridden_values = [
|
overridden_values = [
|
||||||
|
|||||||
@@ -80,6 +80,26 @@ STRING_IDS_TEST_TABLE = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
FROM_SECONDS_TEST_TABLE = [
|
||||||
|
(35, "35"),
|
||||||
|
(23, "23"),
|
||||||
|
(158, "2:38"),
|
||||||
|
(263, "4:23"),
|
||||||
|
(4562, "1:16:02"),
|
||||||
|
(26762, "7:26:02")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
TO_SECONDS_TEST_TABLE = [
|
||||||
|
("0:23", 23),
|
||||||
|
("0:45", 45),
|
||||||
|
("2:19", 139),
|
||||||
|
("3:33", 213),
|
||||||
|
("7:38", 458),
|
||||||
|
("1:30:05", 5405),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_default_music_directory():
|
def test_default_music_directory():
|
||||||
if sys.platform.startswith("linux"):
|
if sys.platform.startswith("linux"):
|
||||||
output = subprocess.check_output(["xdg-user-dir", "MUSIC"])
|
output = subprocess.check_output(["xdg-user-dir", "MUSIC"])
|
||||||
@@ -92,68 +112,39 @@ def test_default_music_directory():
|
|||||||
assert directory == expect_directory
|
assert directory == expect_directory
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def directory_fixture(tmpdir_factory):
|
||||||
|
dir_path = os.path.join(str(tmpdir_factory.mktemp("tmpdir")),
|
||||||
|
"filter_this_folder")
|
||||||
|
return dir_path
|
||||||
|
|
||||||
|
|
||||||
class TestPathFilterer:
|
class TestPathFilterer:
|
||||||
def test_create_directory(self, tmpdir):
|
def test_create_directory(self, directory_fixture):
|
||||||
expect_path = True
|
expect_path = True
|
||||||
global folder_path
|
internals.filter_path(directory_fixture)
|
||||||
folder_path = os.path.join(str(tmpdir), "filter_this_folder")
|
is_path = os.path.isdir(directory_fixture)
|
||||||
internals.filter_path(folder_path)
|
|
||||||
is_path = os.path.isdir(folder_path)
|
|
||||||
assert is_path == expect_path
|
assert is_path == expect_path
|
||||||
|
|
||||||
def test_remove_temp_files(self, tmpdir):
|
def test_remove_temp_files(self, directory_fixture):
|
||||||
expect_file = False
|
expect_file = False
|
||||||
file_path = os.path.join(folder_path, "pesky_file.temp")
|
file_path = os.path.join(directory_fixture, "pesky_file.temp")
|
||||||
open(file_path, "a")
|
open(file_path, "a")
|
||||||
internals.filter_path(folder_path)
|
internals.filter_path(directory_fixture)
|
||||||
is_file = os.path.isfile(file_path)
|
is_file = os.path.isfile(file_path)
|
||||||
assert is_file == expect_file
|
assert is_file == expect_file
|
||||||
|
|
||||||
|
|
||||||
class TestVideoTimeFromSeconds:
|
@pytest.mark.parametrize("sec_duration, str_duration", FROM_SECONDS_TEST_TABLE)
|
||||||
def test_from_seconds(self):
|
def test_video_time_from_seconds(sec_duration, str_duration):
|
||||||
expect_duration = "35"
|
duration = internals.videotime_from_seconds(sec_duration)
|
||||||
duration = internals.videotime_from_seconds(35)
|
assert duration == str_duration
|
||||||
assert duration == expect_duration
|
|
||||||
|
|
||||||
def test_from_minutes(self):
|
|
||||||
expect_duration = "2:38"
|
|
||||||
duration = internals.videotime_from_seconds(158)
|
|
||||||
assert duration == expect_duration
|
|
||||||
|
|
||||||
def test_from_hours(self):
|
|
||||||
expect_duration = "1:16:02"
|
|
||||||
duration = internals.videotime_from_seconds(4562)
|
|
||||||
assert duration == expect_duration
|
|
||||||
|
|
||||||
|
|
||||||
class TestGetSeconds:
|
@pytest.mark.parametrize("str_duration, sec_duration", TO_SECONDS_TEST_TABLE)
|
||||||
def test_from_seconds(self):
|
def test_get_seconds_from_video_time(str_duration, sec_duration):
|
||||||
expect_secs = 45
|
secs = internals.get_sec(str_duration)
|
||||||
secs = internals.get_sec("0:45")
|
assert secs == sec_duration
|
||||||
assert secs == expect_secs
|
|
||||||
secs = internals.get_sec("0.45")
|
|
||||||
assert secs == expect_secs
|
|
||||||
|
|
||||||
def test_from_minutes(self):
|
|
||||||
expect_secs = 213
|
|
||||||
secs = internals.get_sec("3.33")
|
|
||||||
assert secs == expect_secs
|
|
||||||
secs = internals.get_sec("3:33")
|
|
||||||
assert secs == expect_secs
|
|
||||||
|
|
||||||
def test_from_hours(self):
|
|
||||||
expect_secs = 5405
|
|
||||||
secs = internals.get_sec("1.30.05")
|
|
||||||
assert secs == expect_secs
|
|
||||||
secs = internals.get_sec("1:30:05")
|
|
||||||
assert secs == expect_secs
|
|
||||||
|
|
||||||
def test_raise_error(self):
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
internals.get_sec("10*05")
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
internals.get_sec("02,28,46")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("duplicates, expected", DUPLICATE_TRACKS_TEST_TABLE)
|
@pytest.mark.parametrize("duplicates, expected", DUPLICATE_TRACKS_TEST_TABLE)
|
||||||
@@ -170,3 +161,21 @@ def test_get_unique_tracks(tmpdir, duplicates, expected):
|
|||||||
def test_extract_spotify_id(input_str, expected_spotify_id):
|
def test_extract_spotify_id(input_str, expected_spotify_id):
|
||||||
spotify_id = internals.extract_spotify_id(input_str)
|
spotify_id = internals.extract_spotify_id(input_str)
|
||||||
assert spotify_id == expected_spotify_id
|
assert spotify_id == expected_spotify_id
|
||||||
|
|
||||||
|
|
||||||
|
def test_trim(tmpdir):
|
||||||
|
text_file = os.path.join(str(tmpdir), "test_trim.txt")
|
||||||
|
with open(text_file, "w") as track_file:
|
||||||
|
track_file.write("ncs - spectre\nncs - heroes\nncs - hope")
|
||||||
|
|
||||||
|
with open(text_file, "r") as track_file:
|
||||||
|
tracks = track_file.readlines()
|
||||||
|
|
||||||
|
expect_number = len(tracks) - 1
|
||||||
|
expect_track = tracks[0]
|
||||||
|
track = internals.trim_song(text_file)
|
||||||
|
|
||||||
|
with open(text_file, "r") as track_file:
|
||||||
|
number = len(track_file.readlines())
|
||||||
|
|
||||||
|
assert expect_number == number and expect_track == track
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
import builtins
|
|
||||||
import os
|
|
||||||
|
|
||||||
from spotdl import spotify_tools
|
|
||||||
from spotdl import youtube_tools
|
|
||||||
from spotdl import const
|
|
||||||
from spotdl import spotdl
|
|
||||||
|
|
||||||
import loader
|
|
||||||
|
|
||||||
|
|
||||||
USERNAME = "uqlakumu7wslkoen46s5bulq0"
|
|
||||||
PLAYLIST_URL = "https://open.spotify.com/playlist/0fWBMhGh38y0wsYWwmM9Kt"
|
|
||||||
ALBUM_URL = "https://open.spotify.com/album/499J8bIsEnU7DSrosFDJJg"
|
|
||||||
ARTIST_URL = "https://open.spotify.com/artist/4dpARuHxo51G3z768sgnrY"
|
|
||||||
|
|
||||||
loader.load_defaults()
|
|
||||||
|
|
||||||
|
|
||||||
def test_user_playlists(tmpdir, monkeypatch):
|
|
||||||
expect_tracks = 17
|
|
||||||
text_file = os.path.join(str(tmpdir), "test_us.txt")
|
|
||||||
monkeypatch.setattr("builtins.input", lambda x: 1)
|
|
||||||
spotify_tools.write_user_playlist(USERNAME, text_file)
|
|
||||||
with open(text_file, "r") as f:
|
|
||||||
tracks = len(f.readlines())
|
|
||||||
assert tracks == expect_tracks
|
|
||||||
|
|
||||||
|
|
||||||
def test_playlist(tmpdir):
|
|
||||||
expect_tracks = 14
|
|
||||||
text_file = os.path.join(str(tmpdir), "test_pl.txt")
|
|
||||||
spotify_tools.write_playlist(PLAYLIST_URL, text_file)
|
|
||||||
with open(text_file, "r") as f:
|
|
||||||
tracks = len(f.readlines())
|
|
||||||
assert tracks == expect_tracks
|
|
||||||
|
|
||||||
|
|
||||||
def test_album(tmpdir):
|
|
||||||
expect_tracks = 15
|
|
||||||
text_file = os.path.join(str(tmpdir), "test_al.txt")
|
|
||||||
spotify_tools.write_album(ALBUM_URL, text_file)
|
|
||||||
with open(text_file, "r") as f:
|
|
||||||
tracks = len(f.readlines())
|
|
||||||
assert tracks == expect_tracks
|
|
||||||
|
|
||||||
|
|
||||||
def test_m3u(tmpdir):
|
|
||||||
expect_m3u = (
|
|
||||||
"#EXTM3U\n\n"
|
|
||||||
"#EXTINF:31,Eminem - 01 - Eminem - Curtains Up (Skit)\n"
|
|
||||||
"http://www.youtube.com/watch?v=qk13SFlwG9A\n"
|
|
||||||
"#EXTINF:226,Alan Walker - Spectre [NCS Release]\n"
|
|
||||||
"http://www.youtube.com/watch?v=AOeY-nDp7hI\n"
|
|
||||||
)
|
|
||||||
m3u_track_file = os.path.join(str(tmpdir), "m3u_test.txt")
|
|
||||||
with open(m3u_track_file, "w") as track_file:
|
|
||||||
track_file.write("\nhttps://open.spotify.com/track/2nT5m433s95hvYJH4S7ont")
|
|
||||||
track_file.write("\nhttp://www.youtube.com/watch?v=AOeY-nDp7hI")
|
|
||||||
youtube_tools.generate_m3u(m3u_track_file)
|
|
||||||
m3u_file = "{}.m3u".format(m3u_track_file.split(".")[0])
|
|
||||||
with open(m3u_file, "r") as m3u_in:
|
|
||||||
m3u = m3u_in.readlines()
|
|
||||||
assert "".join(m3u) == expect_m3u
|
|
||||||
|
|
||||||
|
|
||||||
def test_all_albums(tmpdir):
|
|
||||||
# current number of tracks on spotify since as of 10/10/2018
|
|
||||||
# in US market only
|
|
||||||
expect_tracks = 49
|
|
||||||
global text_file
|
|
||||||
text_file = os.path.join(str(tmpdir), "test_ab.txt")
|
|
||||||
spotify_tools.write_all_albums_from_artist(ARTIST_URL, text_file)
|
|
||||||
with open(text_file, "r") as f:
|
|
||||||
tracks = len(f.readlines())
|
|
||||||
assert tracks == expect_tracks
|
|
||||||
|
|
||||||
|
|
||||||
def test_trim():
|
|
||||||
with open(text_file, "r") as track_file:
|
|
||||||
tracks = track_file.readlines()
|
|
||||||
|
|
||||||
expect_number = len(tracks) - 1
|
|
||||||
expect_track = tracks[0]
|
|
||||||
track = spotdl.internals.trim_song(text_file)
|
|
||||||
|
|
||||||
with open(text_file, "r") as track_file:
|
|
||||||
number = len(track_file.readlines())
|
|
||||||
|
|
||||||
assert expect_number == number and expect_track == track
|
|
||||||
137
test/test_spotify_tools.py
Normal file
137
test/test_spotify_tools.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
from spotdl import spotify_tools
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_token():
|
||||||
|
token = spotify_tools.generate_token()
|
||||||
|
assert len(token) == 83
|
||||||
|
|
||||||
|
|
||||||
|
def test_refresh_token():
|
||||||
|
old_instance = spotify_tools.spotify
|
||||||
|
spotify_tools.refresh_token()
|
||||||
|
new_instance = spotify_tools.spotify
|
||||||
|
assert not old_instance == new_instance
|
||||||
|
|
||||||
|
|
||||||
|
class TestGenerateMetadata:
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def metadata_fixture(self):
|
||||||
|
metadata = spotify_tools.generate_metadata("ncs - spectre")
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
def test_len(self, metadata_fixture):
|
||||||
|
assert len(metadata_fixture) == 23
|
||||||
|
|
||||||
|
def test_trackname(self, metadata_fixture):
|
||||||
|
assert metadata_fixture["name"] == "Spectre"
|
||||||
|
|
||||||
|
def test_artist(self, metadata_fixture):
|
||||||
|
assert metadata_fixture["artists"][0]["name"] == "Alan Walker"
|
||||||
|
|
||||||
|
def test_duration(self, metadata_fixture):
|
||||||
|
assert metadata_fixture["duration"] == 230.634
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_playlists():
|
||||||
|
expect_playlist_ids = [ "34gWCK8gVeYDPKcctB6BQJ",
|
||||||
|
"04wTU2c2WNQG9XE5oSLYfj",
|
||||||
|
"0fWBMhGh38y0wsYWwmM9Kt" ]
|
||||||
|
|
||||||
|
expect_playlists = [ "https://open.spotify.com/playlist/" + playlist_id
|
||||||
|
for playlist_id in expect_playlist_ids ]
|
||||||
|
|
||||||
|
playlists = spotify_tools.get_playlists("uqlakumu7wslkoen46s5bulq0")
|
||||||
|
assert playlists == expect_playlists
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_user_playlist(tmpdir, monkeypatch):
|
||||||
|
expect_tracks = 17
|
||||||
|
text_file = os.path.join(str(tmpdir), "test_us.txt")
|
||||||
|
monkeypatch.setattr("builtins.input", lambda x: 1)
|
||||||
|
spotify_tools.write_user_playlist("uqlakumu7wslkoen46s5bulq0", text_file)
|
||||||
|
with open(text_file, "r") as f:
|
||||||
|
tracks = len(f.readlines())
|
||||||
|
assert tracks == expect_tracks
|
||||||
|
|
||||||
|
|
||||||
|
class TestFetchPlaylist:
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def playlist_fixture(self):
|
||||||
|
playlist = spotify_tools.fetch_playlist("https://open.spotify.com/playlist/0fWBMhGh38y0wsYWwmM9Kt")
|
||||||
|
return playlist
|
||||||
|
|
||||||
|
def test_name(self, playlist_fixture):
|
||||||
|
assert playlist_fixture["name"] == "special_test_playlist"
|
||||||
|
|
||||||
|
def test_tracks(self, playlist_fixture):
|
||||||
|
assert playlist_fixture["tracks"]["total"] == 14
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_playlist(tmpdir):
|
||||||
|
expect_tracks = 14
|
||||||
|
text_file = os.path.join(str(tmpdir), "test_pl.txt")
|
||||||
|
spotify_tools.write_playlist("https://open.spotify.com/playlist/0fWBMhGh38y0wsYWwmM9Kt", text_file)
|
||||||
|
with open(text_file, "r") as f:
|
||||||
|
tracks = len(f.readlines())
|
||||||
|
assert tracks == expect_tracks
|
||||||
|
|
||||||
|
|
||||||
|
# XXX: Mock this test off if it fails in future
|
||||||
|
class TestFetchAlbum:
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def album_fixture(self):
|
||||||
|
album = spotify_tools.fetch_album("https://open.spotify.com/album/499J8bIsEnU7DSrosFDJJg")
|
||||||
|
return album
|
||||||
|
|
||||||
|
def test_name(self, album_fixture):
|
||||||
|
assert album_fixture["name"] == "NCS: Infinity"
|
||||||
|
|
||||||
|
def test_tracks(self, album_fixture):
|
||||||
|
assert album_fixture["tracks"]["total"] == 15
|
||||||
|
|
||||||
|
|
||||||
|
# XXX: Mock this test off if it fails in future
|
||||||
|
class TestFetchAlbumsFromArtist:
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def albums_from_artist_fixture(self):
|
||||||
|
albums = spotify_tools.fetch_albums_from_artist("https://open.spotify.com/artist/7oPftvlwr6VrsViSDV7fJY")
|
||||||
|
return albums
|
||||||
|
|
||||||
|
def test_len(self, albums_from_artist_fixture):
|
||||||
|
assert len(albums_from_artist_fixture) == 18
|
||||||
|
|
||||||
|
def test_zeroth_album_name(self, albums_from_artist_fixture):
|
||||||
|
assert albums_from_artist_fixture[0]["name"] == "Revolution Radio"
|
||||||
|
|
||||||
|
def test_zeroth_album_tracks(self, albums_from_artist_fixture):
|
||||||
|
assert albums_from_artist_fixture[0]["total_tracks"] == 12
|
||||||
|
|
||||||
|
def test_fist_album_name(self, albums_from_artist_fixture):
|
||||||
|
assert albums_from_artist_fixture[1]["name"] == "Demolicious"
|
||||||
|
|
||||||
|
def test_first_album_tracks(self, albums_from_artist_fixture):
|
||||||
|
assert albums_from_artist_fixture[0]["total_tracks"] == 12
|
||||||
|
|
||||||
|
|
||||||
|
# XXX: Mock this test off if it fails in future
|
||||||
|
def test_write_all_albums_from_artist(tmpdir):
|
||||||
|
# current number of tracks on spotify since as of 10/10/2018
|
||||||
|
# in US market only
|
||||||
|
expect_tracks = 49
|
||||||
|
text_file = os.path.join(str(tmpdir), "test_ab.txt")
|
||||||
|
spotify_tools.write_all_albums_from_artist("https://open.spotify.com/artist/4dpARuHxo51G3z768sgnrY", text_file)
|
||||||
|
with open(text_file, "r") as f:
|
||||||
|
tracks = len(f.readlines())
|
||||||
|
assert tracks == expect_tracks
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_album(tmpdir):
|
||||||
|
expect_tracks = 15
|
||||||
|
text_file = os.path.join(str(tmpdir), "test_al.txt")
|
||||||
|
spotify_tools.write_album("https://open.spotify.com/album/499J8bIsEnU7DSrosFDJJg", text_file)
|
||||||
|
with open(text_file, "r") as f:
|
||||||
|
tracks = len(f.readlines())
|
||||||
|
assert tracks == expect_tracks
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from spotdl import const
|
|
||||||
from spotdl import internals
|
|
||||||
from spotdl import spotify_tools
|
|
||||||
from spotdl import youtube_tools
|
|
||||||
from spotdl import convert
|
|
||||||
from spotdl import metadata
|
|
||||||
from spotdl import downloader
|
|
||||||
|
|
||||||
import loader
|
|
||||||
|
|
||||||
loader.load_defaults()
|
|
||||||
|
|
||||||
TRACK_URL = "https://open.spotify.com/track/2nT5m433s95hvYJH4S7ont"
|
|
||||||
EXPECTED_TITLE = "Eminem - Curtains Up"
|
|
||||||
EXPECTED_YT_TITLE = "Eminem - 01 - Eminem - Curtains Up (Skit)"
|
|
||||||
EXPECTED_YT_URL = "http://youtube.com/watch?v=qk13SFlwG9A"
|
|
||||||
|
|
||||||
|
|
||||||
def test_metadata():
|
|
||||||
expect_number = 23
|
|
||||||
global meta_tags
|
|
||||||
meta_tags = spotify_tools.generate_metadata(TRACK_URL)
|
|
||||||
assert len(meta_tags) == expect_number
|
|
||||||
|
|
||||||
|
|
||||||
class TestFileFormat:
|
|
||||||
def test_with_spaces(self):
|
|
||||||
title = internals.format_string(const.args.file_format, meta_tags)
|
|
||||||
assert title == EXPECTED_TITLE
|
|
||||||
|
|
||||||
def test_without_spaces(self):
|
|
||||||
const.args.no_spaces = True
|
|
||||||
title = internals.format_string(const.args.file_format, meta_tags)
|
|
||||||
assert title == EXPECTED_TITLE.replace(" ", "_")
|
|
||||||
|
|
||||||
|
|
||||||
def test_youtube_url():
|
|
||||||
url = youtube_tools.generate_youtube_url(TRACK_URL, meta_tags)
|
|
||||||
assert url == EXPECTED_YT_URL
|
|
||||||
|
|
||||||
|
|
||||||
def test_youtube_title():
|
|
||||||
global content
|
|
||||||
content = youtube_tools.go_pafy(TRACK_URL, meta_tags)
|
|
||||||
title = youtube_tools.get_youtube_title(content)
|
|
||||||
assert title == EXPECTED_YT_TITLE
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_track_exists_before_download(tmpdir):
|
|
||||||
expect_check = False
|
|
||||||
const.args.folder = str(tmpdir)
|
|
||||||
# prerequisites for determining filename
|
|
||||||
songname = internals.format_string(const.args.file_format, meta_tags)
|
|
||||||
global file_name
|
|
||||||
file_name = internals.sanitize_title(songname)
|
|
||||||
track_existence = downloader.CheckExists(file_name, meta_tags)
|
|
||||||
check = track_existence.already_exists(TRACK_URL)
|
|
||||||
assert check == expect_check
|
|
||||||
|
|
||||||
|
|
||||||
class TestDownload:
|
|
||||||
def test_m4a(self):
|
|
||||||
expect_download = True
|
|
||||||
download = youtube_tools.download_song(file_name + ".m4a", content)
|
|
||||||
assert download == expect_download
|
|
||||||
|
|
||||||
def test_webm(self):
|
|
||||||
expect_download = True
|
|
||||||
download = youtube_tools.download_song(file_name + ".webm", content)
|
|
||||||
assert download == expect_download
|
|
||||||
|
|
||||||
|
|
||||||
class TestFFmpeg:
|
|
||||||
def test_convert_from_webm_to_mp3(self):
|
|
||||||
expect_return_code = 0
|
|
||||||
return_code = convert.song(
|
|
||||||
file_name + ".webm", file_name + ".mp3", const.args.folder
|
|
||||||
)
|
|
||||||
assert return_code == expect_return_code
|
|
||||||
|
|
||||||
def test_convert_from_webm_to_m4a(self):
|
|
||||||
expect_return_code = 0
|
|
||||||
return_code = convert.song(
|
|
||||||
file_name + ".webm", file_name + ".m4a", const.args.folder
|
|
||||||
)
|
|
||||||
assert return_code == expect_return_code
|
|
||||||
|
|
||||||
def test_convert_from_m4a_to_mp3(self):
|
|
||||||
expect_return_code = 0
|
|
||||||
return_code = convert.song(
|
|
||||||
file_name + ".m4a", file_name + ".mp3", const.args.folder
|
|
||||||
)
|
|
||||||
assert return_code == expect_return_code
|
|
||||||
|
|
||||||
def test_convert_from_m4a_to_webm(self):
|
|
||||||
expect_return_code = 0
|
|
||||||
return_code = convert.song(
|
|
||||||
file_name + ".m4a", file_name + ".webm", const.args.folder
|
|
||||||
)
|
|
||||||
assert return_code == expect_return_code
|
|
||||||
|
|
||||||
def test_convert_from_m4a_to_flac(self):
|
|
||||||
expect_return_code = 0
|
|
||||||
return_code = convert.song(
|
|
||||||
file_name + ".m4a", file_name + ".flac", const.args.folder
|
|
||||||
)
|
|
||||||
assert return_code == expect_return_code
|
|
||||||
|
|
||||||
|
|
||||||
class TestAvconv:
|
|
||||||
def test_convert_from_m4a_to_mp3(self):
|
|
||||||
expect_return_code = 0
|
|
||||||
return_code = convert.song(
|
|
||||||
file_name + ".m4a", file_name + ".mp3", const.args.folder, avconv=True
|
|
||||||
)
|
|
||||||
assert return_code == expect_return_code
|
|
||||||
|
|
||||||
|
|
||||||
class TestEmbedMetadata:
|
|
||||||
def test_embed_in_mp3(self):
|
|
||||||
expect_embed = True
|
|
||||||
global track_path
|
|
||||||
track_path = os.path.join(const.args.folder, file_name)
|
|
||||||
embed = metadata.embed(track_path + ".mp3", meta_tags)
|
|
||||||
assert embed == expect_embed
|
|
||||||
|
|
||||||
def test_embed_in_m4a(self):
|
|
||||||
expect_embed = True
|
|
||||||
embed = metadata.embed(track_path + ".m4a", meta_tags)
|
|
||||||
os.remove(track_path + ".m4a")
|
|
||||||
assert embed == expect_embed
|
|
||||||
|
|
||||||
def test_embed_in_webm(self):
|
|
||||||
expect_embed = False
|
|
||||||
embed = metadata.embed(track_path + ".webm", meta_tags)
|
|
||||||
os.remove(track_path + ".webm")
|
|
||||||
assert embed == expect_embed
|
|
||||||
|
|
||||||
def test_embed_in_flac(self):
|
|
||||||
expect_embed = True
|
|
||||||
embed = metadata.embed(track_path + ".flac", meta_tags)
|
|
||||||
os.remove(track_path + ".flac")
|
|
||||||
assert embed == expect_embed
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_track_exists_after_download():
|
|
||||||
expect_check = True
|
|
||||||
track_existence = downloader.CheckExists(file_name, meta_tags)
|
|
||||||
check = track_existence.already_exists(TRACK_URL)
|
|
||||||
os.remove(track_path + ".mp3")
|
|
||||||
assert check == expect_check
|
|
||||||
@@ -8,6 +8,7 @@ from spotdl import youtube_tools
|
|||||||
from spotdl import downloader
|
from spotdl import downloader
|
||||||
|
|
||||||
import loader
|
import loader
|
||||||
|
import pytest
|
||||||
|
|
||||||
loader.load_defaults()
|
loader.load_defaults()
|
||||||
|
|
||||||
@@ -38,11 +39,15 @@ class TestYouTubeAPIKeys:
|
|||||||
assert key == EXPECTED_YT_API_KEY
|
assert key == EXPECTED_YT_API_KEY
|
||||||
|
|
||||||
|
|
||||||
def test_metadata():
|
@pytest.fixture(scope="module")
|
||||||
expect_metadata = None
|
def metadata_fixture():
|
||||||
global metadata
|
|
||||||
metadata = spotify_tools.generate_metadata(TRACK_SEARCH)
|
metadata = spotify_tools.generate_metadata(TRACK_SEARCH)
|
||||||
assert metadata == expect_metadata
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
|
def test_metadata(metadata_fixture):
|
||||||
|
expect_metadata = None
|
||||||
|
assert metadata_fixture == expect_metadata
|
||||||
|
|
||||||
|
|
||||||
class TestArgsManualResultCount:
|
class TestArgsManualResultCount:
|
||||||
@@ -63,68 +68,82 @@ class TestArgsManualResultCount:
|
|||||||
|
|
||||||
|
|
||||||
class TestYouTubeURL:
|
class TestYouTubeURL:
|
||||||
def test_only_music_category(self):
|
def test_only_music_category(self, metadata_fixture):
|
||||||
const.args.music_videos_only = True
|
const.args.music_videos_only = True
|
||||||
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata)
|
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata_fixture)
|
||||||
# YouTube keeps changing its results
|
# YouTube keeps changing its results
|
||||||
assert url in EXPECTED_YT_URLS
|
assert url in EXPECTED_YT_URLS
|
||||||
|
|
||||||
def test_all_categories(self):
|
def test_all_categories(self, metadata_fixture):
|
||||||
const.args.music_videos_only = False
|
const.args.music_videos_only = False
|
||||||
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata)
|
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata_fixture)
|
||||||
assert url == EXPECTED_YT_URL
|
assert url == EXPECTED_YT_URL
|
||||||
|
|
||||||
def test_args_manual(self, monkeypatch):
|
def test_args_manual(self, metadata_fixture, monkeypatch):
|
||||||
const.args.manual = True
|
const.args.manual = True
|
||||||
monkeypatch.setattr("builtins.input", lambda x: "1")
|
monkeypatch.setattr("builtins.input", lambda x: "1")
|
||||||
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata)
|
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata_fixture)
|
||||||
assert url == EXPECTED_YT_URL
|
assert url == EXPECTED_YT_URL
|
||||||
|
|
||||||
def test_args_manual_none(self, monkeypatch):
|
def test_args_manual_none(self, metadata_fixture, monkeypatch):
|
||||||
expect_url = None
|
expect_url = None
|
||||||
monkeypatch.setattr("builtins.input", lambda x: "0")
|
monkeypatch.setattr("builtins.input", lambda x: "0")
|
||||||
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata)
|
url = youtube_tools.generate_youtube_url(TRACK_SEARCH, metadata_fixture)
|
||||||
const.args.manual = False
|
const.args.manual = False
|
||||||
assert url == expect_url
|
assert url == expect_url
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def content_fixture(metadata_fixture):
|
||||||
|
content = youtube_tools.go_pafy(TRACK_SEARCH, metadata_fixture)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def title_fixture(content_fixture):
|
||||||
|
title = youtube_tools.get_youtube_title(content_fixture)
|
||||||
|
return title
|
||||||
|
|
||||||
|
|
||||||
class TestYouTubeTitle:
|
class TestYouTubeTitle:
|
||||||
def test_single_download_with_youtube_api(self):
|
def test_single_download_with_youtube_api(self, title_fixture):
|
||||||
global content
|
|
||||||
global title
|
|
||||||
const.args.youtube_api_key = YT_API_KEY
|
const.args.youtube_api_key = YT_API_KEY
|
||||||
youtube_tools.set_api_key()
|
youtube_tools.set_api_key()
|
||||||
content = youtube_tools.go_pafy(TRACK_SEARCH, metadata)
|
assert title_fixture == EXPECTED_TITLE
|
||||||
title = youtube_tools.get_youtube_title(content)
|
|
||||||
assert title == EXPECTED_TITLE
|
|
||||||
|
|
||||||
def test_download_from_list_without_youtube_api(self):
|
def test_download_from_list_without_youtube_api(self, metadata_fixture, content_fixture):
|
||||||
const.args.youtube_api_key = None
|
const.args.youtube_api_key = None
|
||||||
youtube_tools.set_api_key()
|
youtube_tools.set_api_key()
|
||||||
content = youtube_tools.go_pafy(TRACK_SEARCH, metadata)
|
content_fixture = youtube_tools.go_pafy(TRACK_SEARCH, metadata_fixture)
|
||||||
title = youtube_tools.get_youtube_title(content, 1)
|
title = youtube_tools.get_youtube_title(content_fixture, 1)
|
||||||
assert title == "1. {0}".format(EXPECTED_TITLE)
|
assert title == "1. {0}".format(EXPECTED_TITLE)
|
||||||
|
|
||||||
|
|
||||||
def test_check_exists(tmpdir):
|
@pytest.fixture(scope="module")
|
||||||
|
def filename_fixture(title_fixture):
|
||||||
|
filename = internals.sanitize_title(title_fixture)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_exists(metadata_fixture, filename_fixture, tmpdir):
|
||||||
expect_check = False
|
expect_check = False
|
||||||
const.args.folder = str(tmpdir)
|
const.args.folder = str(tmpdir)
|
||||||
# prerequisites for determining filename
|
# prerequisites for determining filename
|
||||||
global file_name
|
track_existence = downloader.CheckExists(filename_fixture, metadata_fixture)
|
||||||
file_name = internals.sanitize_title(title)
|
|
||||||
track_existence = downloader.CheckExists(file_name, metadata)
|
|
||||||
check = track_existence.already_exists(TRACK_SEARCH)
|
check = track_existence.already_exists(TRACK_SEARCH)
|
||||||
assert check == expect_check
|
assert check == expect_check
|
||||||
|
|
||||||
|
|
||||||
class TestDownload:
|
class TestDownload:
|
||||||
def test_webm(self):
|
def test_webm(self, content_fixture, filename_fixture, monkeypatch):
|
||||||
# content does not have any .webm audiostream
|
# content_fixture does not have any .webm audiostream
|
||||||
expect_download = False
|
expect_download = False
|
||||||
download = youtube_tools.download_song(file_name + ".webm", content)
|
monkeypatch.setattr("pafy.backend_shared.BaseStream.download", lambda x: None)
|
||||||
|
download = youtube_tools.download_song(filename_fixture + ".webm", content_fixture)
|
||||||
assert download == expect_download
|
assert download == expect_download
|
||||||
|
|
||||||
def test_other(self):
|
def test_other(self, content_fixture, filename_fixture, monkeypatch):
|
||||||
expect_download = False
|
expect_download = False
|
||||||
download = youtube_tools.download_song(file_name + ".fake_extension", content)
|
monkeypatch.setattr("pafy.backend_shared.BaseStream.download", lambda x: None)
|
||||||
|
download = youtube_tools.download_song(filename_fixture + ".fake_extension", content_fixture)
|
||||||
assert download == expect_download
|
assert download == expect_download
|
||||||
Reference in New Issue
Block a user