47 Commits

Author SHA1 Message Date
ritiek
d18663c0e7 Join threads before exitting 2017-10-08 13:06:51 +05:30
ritiek
4db1dcc9b8 Implement basic threading 2017-10-08 12:30:43 +05:30
Ritiek Malhotra
98d21bbad9 Fix NoneType not scriptable if song not on Spotify (#140) 2017-09-29 12:38:15 +05:30
Linus Groh
3812de3a43 Update README.md to avoid more py2 related issues 2017-09-29 00:23:46 +02:00
ritiek
9068f6008e Fix albumart exception in .M4A downloads 2017-09-27 10:26:03 +05:30
ritiek
f9e85d3796 albumart out of range exception 2017-09-27 10:22:25 +05:30
Ritiek Malhotra
97a8c21eb9 Download songs using YouTube URL (#135)
* Download from YouTubr URL

* Slugify title only for YouTube URL's
2017-09-27 09:56:47 +05:30
Linus Groh
17e6d1fa2e Update README.md to include a note about pip2/3 2017-09-18 00:03:25 +02:00
Ritiek Malhotra
31b91bd6da Use Python 3.6+ on Windows to avoid Unicode errors 2017-09-05 21:41:22 +05:30
Pierre Gérard
633fb9b1bd [fix] add wait to avoid infinite retry looping (#126) 2017-08-30 20:01:23 +05:30
Ritiek Malhotra
c73c650383 Fix typo in an example command 2017-08-29 23:05:28 +05:30
Linus Groh
6b3dafa99b Merge pull request #121 from lkgarrison/master
Improves accuracy of selected youtube video
2017-08-26 17:28:38 +02:00
Linus Groh
ddd57eda88 Fix wrong command line option in README.md 2017-08-25 14:20:32 +02:00
ritiek
0178307b20 Use tinydownload to download ffmpeg binary 2017-08-12 19:33:38 +05:30
Luke Garrison
9d25197a5f Improves accuracy of selected youtube video
Finds a balance between viewcount (Youtube's relevancy) and proper song
duration based on Spotify duration. Thus, a Youtube video that is 30
seconds longer than the Spotify track will not be considered

Takes the first result from Youtube (using Youtube's original ordering
based on relevance) that has a similar duration to the Spotify song

Fixes a bug where if there were no suitable videos for a song, the
program would infinitely loop. The program will now retry to find a song
up to 5 times before moving on (this is necessary because occasionally the
song isn't properly fetched or parsed from Youtube)

Fixes bug where songs that are retried were appended to the playlist
file without being separated by a newline
2017-08-09 21:42:31 -04:00
Ritiek Malhotra
7e0fdfbce3 Update .travis.yml 2017-08-03 11:01:10 +00:00
Ritiek Malhotra
ed630823b0 Update .travis.yml 2017-08-03 10:51:14 +00:00
Ritiek Malhotra
8997455e4c Update .travis.yml 2017-08-03 10:48:56 +00:00
Ritiek Malhotra
c23be97619 Update .travis.yml 2017-08-03 10:43:25 +00:00
Ritiek Malhotra
6ff72b0495 Update ffmpeg_bin.py 2017-08-03 10:40:32 +00:00
Ritiek Malhotra
01c0501f46 Update .travis.yml 2017-08-03 10:38:29 +00:00
Ritiek Malhotra
fa5ec719f4 Download FFmpeg binary instead of compiling 2017-08-03 10:31:36 +00:00
Ritiek Malhotra
d1ae0c05a6 Delete ffmeg_bin.py 2017-08-03 10:31:07 +00:00
Ritiek Malhotra
93f45badbf Download ffmpeg binary instead of compiling 2017-08-03 10:27:24 +00:00
Ritiek Malhotra
624f9d95f3 Update .travis.yml 2017-08-03 10:26:09 +00:00
ritiek
67f594c7b0 Don't view sort results if metadata not found 2017-08-02 17:49:16 +05:30
Linus Groh
606e55bb1e Merge pull request #118 from I-Al-Istannen/master
Added handling for when there simply is no video
2017-07-24 23:13:29 +02:00
I-Al-Istannen
dbaa890976 Added handling for when there simply is no video
Just return None if no video could be found. Makes it skip instead of error out.
2017-07-24 23:01:05 +02:00
ritiek
6401eca454 Optimize and add non-spotify tests 2017-07-25 01:13:22 +05:30
ritiek
5d4f2d5148 Fix traceback when metadata not found 2017-07-25 00:37:37 +05:30
Ritiek Malhotra
05023b90c9 Test Python 3.2 and 3.3 2017-07-22 16:24:47 +05:30
ritiek
146f70c8a7 Sort search results by viewcount 2017-07-21 11:24:56 +05:30
ritiek
6ac25bda0c Fix KeyError when ISRC info unavailable 2017-07-20 05:18:52 +05:30
Ritiek Malhotra
dc6019a1a5 Reduce Travis CI build time by more than 90% (#115)
Travis CI now downloads a pre-compiled FFmpeg binary instead of building one on every run. Test duration dropped from 10 mins to about 50 secs.
2017-07-18 14:43:33 +05:30
Ritiek Malhotra
0607003cfa Revert 2017-07-18 13:42:56 +05:30
Ritiek Malhotra
1b56888cb0 Use pre-compiled ffmpeg binary 2017-07-18 13:40:18 +05:30
Ritiek Malhotra
4a2bbb2535 Expect Python to be already in PATH (#114)
* Expect python to already be in PATH

* Expect python to be in PATH

* Fix special handlling of conversion on windows

* Some cleaning

* Update docs
2017-07-17 20:25:11 +05:30
WMP
4cf8a210bf Change for automatical download video with this same duration as spotify song (#111)
* Changes in .gitignore file:
 - added .python-version
This file is used in pyenv to select good python version.

Changes in core/misc.py:
 - added function get_sec to convert HH:mm:ss to seconds

Changes in spotdl.py:
 - in function generate_songname
     change function to receive generate_metadata, this is optymalization, becouse in oldest version metadata from spotify api is downlaoded 2 times
 - in function generate_youtube_url
     song variable use changed function generate_songname
     function now looking for songs in while, and save data to dict. Dictionary is used in manual and auto mode. In dictionary keep is youtube link, title, videotime (in format HH:mm:ss) and videotime converted to seconds.
     For now in automatic downloading is selected video with least difference betwen youtube video time and time from spotify. This is important, becouse in youtube a lot of movies has scenes before/after without musics.

* Fix parameter in generate_songname() and dual calls

* Fix tests

* Skip tests that depend on the location (for the moment)

* Remove unnecessary code
2017-07-17 20:20:29 +05:30
Ritiek Malhotra
9564a71035 Add -p option to directly download playlists with URL (#110)
* Rough implementation of playlist link

* Clean code

* Add information about playlist URL

* Update README.md

* Fix bug where script selects wrong playlist

* Minor improvements

* Minor improvements
2017-07-12 13:37:15 +05:30
ritiek
ce3a6c3d5a Replace '/' with '_' instead of deleting it 2017-07-12 00:03:38 +05:30
Rutger Rauws
d28ff08a69 Use a Spotify song's title instead of a YouTube video's title (#99)
* Use a Spotify song's title instead of a YouTube video's title

* Added fallback to YouTube title if song's metadata cannot be fetched from Spotify

* Removed duplicate generation of metadata

* Fix test cases that use download_song(..) and generate_filename(..)

* Fix conflicting function

* Fix conflict in check_exists()

* Fix filenames for non spotify songs

* Fix some bugs

* Some documentation changes

* Remove unnecessary determine_filename()
2017-07-11 23:14:56 +05:30
Linus
75be7285c0 Add description of the new -f option in README.md; Fix minor issues 2017-07-11 18:48:02 +02:00
Ritiek Malhotra
e1ef35b6b9 Fix avconv conversion for -f option 2017-07-11 22:02:13 +05:30
Linus
cc7260dfa6 Add command line option for target folder (-f) 2017-07-11 18:16:41 +02:00
Ritiek Malhotra
5add1fd0f8 Drop python 2 compatibility (#107) 2017-07-11 20:32:26 +05:30
Linus Groh
b73d0b1049 Fix rendering issue in README.md 2017-07-11 16:14:17 +02:00
Linus Groh
c80f2996fb Add and fix some information in README.md 2017-07-11 15:36:15 +02:00
11 changed files with 514 additions and 331 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ __pycache__/
.cache/
Music/
*.txt
.python-version

View File

@@ -1,23 +1,15 @@
dist: trusty
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
before_install:
install:
- sudo apt-get -qq update
- sudo apt-get -y install autoconf automake build-essential libass-dev libfreetype6-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo wget zlib1g-dev
- sudo apt-get -y install yasm nasm libmp3lame-dev
- mkdir ~/ffmpeg_sources
- cd ~/ffmpeg_sources
- wget http://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2
- tar jxf ffmpeg-snapshot.tar.bz2
- cd ffmpeg
- PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --pkg-config-flags="--static" --extra-cflags="-I$HOME/ffmpeg_build/include" --extra-ldflags="-L$HOME/ffmpeg_build/lib" --bindir="$HOME/bin" --enable-libmp3lame
- PATH="$HOME/bin:$PATH" make -s -j
- make install
- hash -r
install:
- cd $TRAVIS_BUILD_DIR
- pip install -r requirements.txt
- pip install tinydownload
- tinydownload 05861434675432854607 -o ~/bin/ffmpeg
- chmod 755 ~/bin/ffmpeg
script: python -m pytest test

View File

@@ -17,7 +17,7 @@ Please follow the guide below
- [ ] Other
#### System information
- Your `python` version: `python 2.7`
- Your `python` version: `python 3.x`
- Your operating system: `Ubuntu 16.04`
### Description

View File

@@ -1,5 +1,7 @@
# Spotify-Downloader
[![Build Status](https://travis-ci.org/ritiek/spotify-downloader.svg?branch=master)](https://travis-ci.org/ritiek/spotify-downloader)
- Downloads songs from YouTube in an MP3 format by using Spotify's HTTP link.
- Can also download a song by entering its artist and song name (in case if you don't have the Spotify's HTTP link for some song).
@@ -15,19 +17,17 @@
- Track number
- Disc number
- Release date
- and some more..
- And some more...
- Works straight out of the box and does not require to generate or mess with your API keys.
<br>
That's how your Music library will look like!
<img src="http://i.imgur.com/Gpch7JI.png" width="290"><img src="http://i.imgur.com/5vhk3HY.png" width="290"><img src="http://i.imgur.com/RDTCCST.png" width="290">
## Reporting Issues
- Search for your problem in the [issues section](https://github.com/Ritiek/spotify-downloader/issues?utf8=%E2%9C%93&q=) before opening a new ticket. It might be already answered and save us time :D.
- Search for your problem in the [issues section](https://github.com/Ritiek/spotify-downloader/issues?utf8=%E2%9C%93&q=) before opening a new ticket. It might be already answered and save us time. :smile:
- Provide as much information possible when opening your ticket.
@@ -35,7 +35,7 @@ That's how your Music library will look like!
<img src="http://i.imgur.com/Dg8p9up.png" width="600">
- This version supports both Python2 and Python3.
- **This tool supports only Python 3**, Python 2 compatibility was dropped because of the way it deals with unicode. If you need to use Python 2 though, check out the (old) `python2` branch.
- Note: `play` and `lyrics` commands have been deprecated in the current brach since they were not of much use and created unnecessary clutter. You can still get them back by using `old` branch though.
@@ -48,6 +48,8 @@ cd spotify-downloader
pip install -U -r requirements.txt
```
**Important:** if you have installed both Python 2 and 3, the `pip` command could invoke an installation for Python 2. To see which Python version `pip` refers to, try `pip -V`. If it turns out `pip` is your Python 2 pip, try `pip3 install -U -r requirements.txt` instead.
You'll also need to install FFmpeg for conversion (use `--avconv` if you'd like to use that instead):
Linux: `sudo apt-get install ffmpeg`
@@ -58,25 +60,24 @@ If it does not install correctly, you may have to build it from source. For more
### Windows
Assuming you have Python already installed..
Assuming you have Python 3 ([preferably v3.6 or above to stay away from Unicode errors](https://stackoverflow.com/questions/30539882/whats-the-deal-with-python-3-4-unicode-different-languages-and-windows)) already installed and in PATH.
- Download FFmpeg for Windows from [here](http://ffmpeg.zeranoe.com/builds/). Copy `ffmpeg.exe` from `ffmpeg-xxx-winxx-static\bin\ffmpeg.exe` to `Scripts` folder (in your Python's installation directory: `Pythonxx\Scripts\ffmpeg.exe`)
- Download and extract the [zip file](https://github.com/ritiek/spotify-downloader/archive/master.zip) from master branch.
- Download the [zip file](https://github.com/ritiek/spotify-downloader/archive/master.zip) of this repository and copy the folder contained in the archive into your Python's installation folder (`\Python36\spotify-downloader-master`).
- Download FFmpeg for Windows from [here](http://ffmpeg.zeranoe.com/builds/). Copy `ffmpeg.exe` from `ffmpeg-xxx-winxx-static\bin\ffmpeg.exe` to PATH (usually C:\Windows\System32\) or just place it in the root directory extracted from the above step.
- Open the folder from last step. Shift+right-click on empty area, open `cmd` and type:
`"Scripts/pip.exe" install -U -r requirements.txt`
- If you do not want to naviagte to your Python folder from the command-line everytime you want to run the script, you can have your Python 'PATH' environment variables set and then you can run the script from any directory.
- Open `cmd` and type `pip install -U -r requirements.txt` to install dependencies. The same note about `pip` as for Debian, Ubuntu, Linux & Mac applies.
## Instructions for Downloading Songs
- For all available options, run `python spotdl.py --help` (or for Windows run `python.exe spotdl.py --help`).
**Important:** as like with `pip`, there might be no `python3` command. This is most likely the case when you have only Python 3 but not 2 installed. In this case try the `python` command instead of `python3`, but make sure `python -V` gives you a `Python 3.x.x`!
- For all available options, run `python3 spotdl.py --help`.
```
usage: spotdl.py [-h] (-s SONG | -l LIST | -u USERNAME) [-n] [-m] [-f] [-v]
[-i INPUT_EXT] [-o OUTPUT_EXT]
usage: spotdl.py [-h] (-s SONG | -l LIST | -p PLAYLIST | -u USERNAME) [-m]
[-nm] [-a] [-f FOLDER] [-v] [-i INPUT_EXT] [-o OUTPUT_EXT]
Download and convert songs from Spotify, Youtube etc.
@@ -84,12 +85,19 @@ optional arguments:
-h, --help show this help message and exit
-s SONG, --song SONG download song by spotify link or name (default: None)
-l LIST, --list LIST download songs from a file (default: None)
-u USERNAME, --username USERNAME
load user's playlists into <playlist_name>.txt
-p PLAYLIST, --playlist PLAYLIST
load songs from playlist URL into <playlist_name>.txt
(default: None)
-u USERNAME, --username USERNAME
load songs from user's playlist into
<playlist_name>.txt (default: None)
-m, --manual choose the song to download manually (default: False)
-a, --avconv Use avconv for conversion. If not set
defaults to FFmpeg (default: False)
-nm, --no-metadata do not embed metadata in songs (default: False)
-a, --avconv Use avconv for conversion otherwise set defaults to
ffmpeg (default: False)
-f FOLDER, --folder FOLDER
path to folder where files will be stored in (default:
Music/)
-v, --verbose show debug output (default: False)
-i INPUT_EXT, --input_ext INPUT_EXT
prefered input format .m4a or .webm (Opus) (default:
@@ -99,23 +107,23 @@ optional arguments:
.mp3)
```
#### Downloading by Name
#### Download by Name
For example
- We want to download Hello by Adele, simply run `python spotdl.py --song "adele hello"`.
- We want to download Hello by Adele, simply run `python3 spotdl.py --song "adele hello"`.
- The script will automatically look for the best matching song and download it in the folder `Music/` placed in the root directory of the code base.
- It will now convert the song to an mp3 and try to fix meta-tags and album-art by looking up on Spotify.
#### Downloading by Spotify Link (Recommended)
#### Download by Spotify Link (Recommended)
For example
- We want to download the same song (i.e: Hello by Adele) but using Spotify Link this time that looks like `http://open.spotify.com/track/1MDoll6jK4rrk2BcFRP5i7`, you can copy it from your Spotify desktop or mobile app by right clicking or long tap on the song and copy HTTP link.
- Run `python spotdl.py --song http://open.spotify.com/track/1MDoll6jK4rrk2BcFRP5i7`, it should download Hello by Adele.
- Run `python3 spotdl.py --song http://open.spotify.com/track/1MDoll6jK4rrk2BcFRP5i7`, it should download Hello by Adele.
- Just like before, it will again convert the song to an mp3 but since we used a Spotify HTTP link, the script is guaranteed to fetch the correct meta-tags and album-art.
@@ -131,7 +139,7 @@ No problem!
- Just make a `list.txt` in the same folder as the script and add all the songs you want to download, in our case it is
(if you are on windows, just edit `list.txt` - i.e `C:\Python27\list.txt`)
(if you are on Windows, just edit `list.txt` - i.e `C:\Python36\spotify-downloader-master\list.txt`)
```
https://open.spotify.com/track/1MDoll6jK4rrk2BcFRP5i7
@@ -139,25 +147,45 @@ the nights avicci
http://open.spotify.com/track/64yrDBpcdwEdNY9loyEGbX
```
- Now pass `--list=list.txt` to the script, i.e `python spotdl.py --list=list.txt` and it will start downloading songs mentioned in `list.txt`.
- Now pass `--list=list.txt` to the script, i.e `python3 spotdl.py --list=list.txt` and it will start downloading songs mentioned in `list.txt`.
- You can stop downloading songs by hitting `ctrl+c`, the script will automatically resume from the song where you stopped it the next time you want to download the songs present in `list.txt`.
- Songs that are already downloaded will be skipped and not be downloaded again.
#### Downloading playlists
#### Download playlists
- You can also load songs from any playlist provided you have a Spotify username or user id of that user. (Open profile in Spotify, click on the three little dots below name, "Share", "Copy to clipboard", paste last numbers into command-line: `https://open.spotify.com/user/0123456790`)
- You can copy the Spotify URL of the playlist and pass it in `--playlist` option.
- Try running `python spotdl.py -u <your_username>`, it will show all your public playlists.
For example
- Once you select the one you want to download, the script will load all the tracks from the playlist into `<playlist_name>.txt`
- `python3 spotdl.py --playlist https://open.spotify.com/user/camillazi/playlist/71MXqcSOKCxsLNtRvONkhF`
- Then you can simply run `python spotdl.py --list=<playlist_name>.txt` to download them all!
- The script will load all the tracks from the playlist into `<playlist_name>.txt`
- Then you can simply run `python3 spotdl.py --list=<playlist_name>.txt` to download all the tracks.
#### Download playlists by username
- You can also load songs using Spotify username if you don't have the playlist URL. (Open profile in Spotify, click on the three little dots below name, "Share", "Copy to clipboard", paste last numbers into command-line: `https://open.spotify.com/user/0123456790`)
- Try running `python3 spotdl.py -u <your_username>`, it will show all your public playlists.
- Once you select the one you want to download, the script will load all the tracks from the playlist into `<playlist_name>.txt`.
- Run `python3 spotdl.py --list=<playlist_name>.txt` to download all the tracks.
#### Specify the target directory
If you don't want to download all the songs to the `Music/` folder relative to the `spotdl.py` script, you can use the `-f`/`--folder` option. E.g. `python3 spotdl.py -s "adele hello" -f "/home/user/Music/"`. This works with both relative and absolute paths.
## Running tests
`python -m pytest test`
```
python3 -m pytest test
```
Obviously this requires the `pytest` module to be installed.
## Disclaimer

View File

@@ -1,67 +1,52 @@
import subprocess
import os
import sys
def song(input_song, output_song, avconv=False, verbose=False):
"""
What are the differences and similarities between ffmpeg, libav, and avconv?
https://stackoverflow.com/questions/9477115
ffmeg encoders high to lower quality
libopus > libvorbis >= libfdk_aac > aac > libmp3lame
libfdk_aac due to copyrights needs to be compiled by end user
on MacOS brew install ffmpeg --with-fdk-aac will do just that. Other OS?
https://trac.ffmpeg.org/wiki/Encode/AAC
"""
def song(input_song, output_song, folder, avconv=False, verbose=False):
"""Do the audio format conversion."""
if not input_song == output_song:
if sys.version_info < (3, 0):
input_song = input_song.encode('utf-8')
output_song = output_song.encode('utf-8')
print('Converting {0} to {1}'.format(
input_song, output_song.split('.')[-1]))
if avconv:
exit_code = convert_with_avconv(input_song, output_song, verbose)
exit_code = convert_with_avconv(input_song, output_song, folder, verbose)
else:
exit_code = convert_with_ffmpeg(input_song, output_song, verbose)
exit_code = convert_with_ffmpeg(input_song, output_song, folder, verbose)
return exit_code
return 0
def convert_with_avconv(input_song, output_song, verbose):
def convert_with_avconv(input_song, output_song, folder, verbose):
"""Convert the audio file using avconv."""
if os.name == 'nt':
avconv_path = '..\\Scripts\\avconv.exe'
else:
avconv_path = 'avconv'
if verbose:
level = 'debug'
else:
level = '0'
command = [avconv_path,
command = ['avconv',
'-loglevel', level,
'-i', 'Music/' + input_song,
'-i', os.path.join(folder, input_song),
'-ab', '192k',
'Music/' + output_song]
os.path.join(folder, output_song)]
return subprocess.call(command)
def convert_with_ffmpeg(input_song, output_song, verbose):
"""Convert the audio file using FFMpeg.
What are the differences and similarities between ffmpeg, libav, and avconv?
https://stackoverflow.com/questions/9477115
ffmeg encoders high to lower quality
libopus > libvorbis >= libfdk_aac > aac > libmp3lame
libfdk_aac due to copyrights needs to be compiled by end user
on MacOS brew install ffmpeg --with-fdk-aac will do just that. Other OS?
https://trac.ffmpeg.org/wiki/Encode/AAC
"""
if os.name == "nt":
ffmpeg_pre = '..\\Scripts\\ffmpeg.exe '
else:
ffmpeg_pre = 'ffmpeg '
ffmpeg_pre += '-y '
def convert_with_ffmpeg(input_song, output_song, folder, verbose):
"""Convert the audio file using FFmpeg."""
ffmpeg_pre = 'ffmpeg -y '
if not verbose:
ffmpeg_pre += '-hide_banner -nostats -v panic '
ffmpeg_params = ''
input_ext = input_song.split('.')[-1]
output_ext = output_song.split('.')[-1]
@@ -77,7 +62,7 @@ def convert_with_ffmpeg(input_song, output_song, verbose):
elif output_ext == 'm4a':
ffmpeg_params = '-cutoff 20000 -c:a libfdk_aac -b:a 192k -vn '
command = '{0}-i Music/{1} {2}Music/{3}'.format(
ffmpeg_pre, input_song, ffmpeg_params, output_song).split(' ')
command = '{0}-i {1} {2}{3}'.format(
ffmpeg_pre, os.path.join(folder, input_song), ffmpeg_params, os.path.join(folder, output_song)).split(' ')
return subprocess.call(command)

View File

@@ -1,26 +1,21 @@
from mutagen.easyid3 import EasyID3
from mutagen.id3 import ID3, APIC
from mutagen.mp4 import MP4, MP4Cover
import sys
# urllib2 is urllib.request in python3
try:
import urllib2
except ImportError:
import urllib.request as urllib2
import urllib.request
def compare(file, metadata):
"""Check if the input file title matches the expected title."""
def compare(music_file, metadata):
"""Check if the input music file title matches the expected title."""
already_tagged = False
try:
if file.endswith('.mp3'):
audiofile = EasyID3('Music/' + file)
if music_file.endswith('.mp3'):
audiofile = EasyID3(music_file)
# fetch track title metadata
already_tagged = audiofile['title'][0] == metadata['name']
elif file.endswith('.m4a'):
elif music_file.endswith('.m4a'):
tags = {'title': '\xa9nam'}
audiofile = MP4('Music/' + file)
audiofile = MP4(music_file)
# fetch track title metadata
already_tagged = audiofile[tags['title']] == metadata['name']
except (KeyError, TypeError):
@@ -30,8 +25,6 @@ def compare(file, metadata):
def embed(music_file, meta_tags):
"""Embed metadata."""
if sys.version_info < (3, 0):
music_file = music_file.encode('utf-8')
if meta_tags is None:
print('Could not find meta-tags')
return None
@@ -49,7 +42,7 @@ def embed(music_file, meta_tags):
def embed_mp3(music_file, meta_tags):
"""Embed metadata to MP3 files."""
# EasyID3 is fun to use ;)
audiofile = EasyID3('Music/' + music_file)
audiofile = EasyID3(music_file)
audiofile['artist'] = meta_tags['artists'][0]['name']
audiofile['albumartist'] = meta_tags['artists'][0]['name']
audiofile['album'] = meta_tags['album']['name']
@@ -65,19 +58,23 @@ def embed_mp3(music_file, meta_tags):
audiofile['arranger'] = meta_tags['artists'][0]['name']
audiofile['performer'] = meta_tags['artists'][0]['name']
audiofile['encodedby'] = meta_tags['publisher']
audiofile['isrc'] = meta_tags['external_ids']['isrc']
audiofile['website'] = meta_tags['external_urls']['spotify']
audiofile['length'] = str(meta_tags['duration_ms'] / 1000)
if meta_tags['genre']:
audiofile['genre'] = meta_tags['genre']
if meta_tags['copyright']:
audiofile['copyright'] = meta_tags['copyright']
if meta_tags['isrc']:
audiofile['isrc'] = meta_tags['external_ids']['isrc']
audiofile.save(v2_version=3)
audiofile = ID3('Music/' + music_file)
albumart = urllib2.urlopen(meta_tags['album']['images'][0]['url'])
audiofile["APIC"] = APIC(encoding=3, mime='image/jpeg', type=3,
desc=u'Cover', data=albumart.read())
albumart.close()
audiofile = ID3(music_file)
try:
albumart = urllib.request.urlopen(meta_tags['album']['images'][0]['url'])
audiofile["APIC"] = APIC(encoding=3, mime='image/jpeg', type=3,
desc=u'Cover', data=albumart.read())
albumart.close()
except IndexError:
pass
audiofile.save(v2_version=3)
return True
@@ -103,7 +100,7 @@ def embed_m4a(music_file, meta_tags):
'copyright': 'cprt',
'tempo': 'tmpo'}
audiofile = MP4('Music/' + music_file)
audiofile = MP4(music_file)
audiofile[tags['artist']] = meta_tags['artists'][0]['name']
audiofile[tags['albumartist']] = meta_tags['artists'][0]['name']
audiofile[tags['album']] = meta_tags['album']['name']
@@ -117,9 +114,12 @@ def embed_m4a(music_file, meta_tags):
audiofile[tags['genre']] = meta_tags['genre']
if meta_tags['copyright']:
audiofile[tags['copyright']] = meta_tags['copyright']
albumart = urllib2.urlopen(meta_tags['album']['images'][0]['url'])
audiofile[tags['albumart']] = [MP4Cover(
albumart.read(), imageformat=MP4Cover.FORMAT_JPEG)]
albumart.close()
try:
albumart = urllib.request.urlopen(meta_tags['album']['images'][0]['url'])
audiofile[tags['albumart']] = [MP4Cover(
albumart.read(), imageformat=MP4Cover.FORMAT_JPEG)]
albumart.close()
except IndexError:
pass
audiofile.save()
return True

View File

@@ -1,20 +1,16 @@
import argparse
import sys
import os
from slugify import slugify
import argparse
import spotipy.oauth2 as oauth2
try:
from urllib2 import quote
except ImportError:
from urllib.request import quote
from urllib.request import quote
from slugify import slugify
def input_link(links):
"""Let the user input a number."""
while True:
try:
the_chosen_one = int(user_input('>> Choose your number: '))
the_chosen_one = int(input('>> Choose your number: '))
if 1 <= the_chosen_one <= len(links):
return links[the_chosen_one - 1]
elif the_chosen_one == 0:
@@ -25,14 +21,6 @@ def input_link(links):
print('Choose a valid number!')
def user_input(string=''):
"""Take input correctly for both Python 2 & 3."""
if sys.version_info > (3, 0):
return input(string)
else:
return raw_input(string)
def trim_song(file):
"""Remove the first song from file."""
with open(file, 'r') as file_in:
@@ -51,9 +39,11 @@ def get_arguments():
'-s', '--song', help='download song by spotify link or name')
group.add_argument(
'-l', '--list', help='download songs from a file')
group.add_argument(
'-p', '--playlist', help='load songs from playlist URL into <playlist_name>.txt')
group.add_argument(
'-u', '--username',
help="load user's playlists into <playlist_name>.txt")
help="load songs from user's playlist into <playlist_name>.txt")
parser.add_argument(
'-m', '--manual', default=False,
help='choose the song to download manually', action='store_true')
@@ -64,6 +54,9 @@ def get_arguments():
'-a', '--avconv', default=False,
help='Use avconv for conversion otherwise set defaults to ffmpeg',
action='store_true')
parser.add_argument(
'-f', '--folder', default='Music/',
help='path to folder where files will be stored in')
parser.add_argument(
'-v', '--verbose', default=False, help='show debug output',
action='store_true')
@@ -79,23 +72,26 @@ def get_arguments():
def is_spotify(raw_song):
"""Check if the input song is a Spotify link."""
if (len(raw_song) == 22 and raw_song.replace(" ", "%20") == raw_song) or \
(raw_song.find('spotify') > -1):
return True
else:
return False
status = len(raw_song) == 22 and raw_song.replace(" ", "%20") == raw_song
status = status or raw_song.find('spotify') > -1
return status
def is_youtube(raw_song):
"""Check if the input song is a YouTube link."""
status = len(raw_song) == 11 and raw_song.replace(" ", "%20") == raw_song
status = status and not raw_song.lower() == raw_song
status = status or 'youtube.com/watch?v=' in raw_song
return status
def generate_filename(title):
def sanitize_title(title):
"""Generate filename of the song to be downloaded."""
# IMO python2 sucks dealing with unicode
title = fix_encoding(title)
title = fix_decoding(title)
title = title.replace(' ', '_')
title = title.replace('/', '_')
# slugify removes any special characters
filename = slugify(title, ok='-_()[]{}', lower=False)
return fix_encoding(filename)
title = slugify(title, ok='-_()[]{}', lower=False)
return title
def generate_token():
@@ -107,37 +103,39 @@ def generate_token():
return token
def generate_search_url(song):
def generate_search_url(song, viewsort=False):
"""Generate YouTube search URL for the given song."""
# urllib2.quote() encodes URL with special characters
url = u"https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q={0}".format(
quote(song))
# urllib.request.quote() encodes URL with special characters
song = quote(song)
if viewsort:
url = u"https://www.youtube.com/results?q={0}".format(song)
else:
url = u"https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q={0}".format(song)
return url
def fix_encoding(query):
"""Fix encoding issues in Python 2."""
if sys.version_info < (3, 0):
query = query.encode('utf-8')
return query
def fix_decoding(query):
"""Fix decoding issues in Python 2."""
if sys.version_info < (3, 0):
query = query.decode('utf-8')
return query
def filter_path(path):
os.chdir(sys.path[0])
if not os.path.exists(path):
os.makedirs(path)
for temp in os.listdir(path):
if temp.endswith('.temp'):
os.remove('{0}/{1}'.format(path, temp))
os.remove(os.path.join(path, temp))
def grace_quit():
print('\n\nExiting.')
sys.exit()
def get_sec(time_str):
v = time_str.split(':', 3)
v.reverse()
sec = 0
if len(v) > 0: #seconds
sec += int(v[0])
if len(v) > 1: # minutes
sec += int(v[1]) * 60
if len(v) > 2: # hours
sec += int(v[2]) * 3600
return sec

300
spotdl.py
View File

@@ -9,22 +9,17 @@ from titlecase import titlecase
from slugify import slugify
import spotipy
import pafy
import urllib.request
import sys
import os
# urllib2 is urllib.request in python3
try:
import urllib2
except ImportError:
import urllib.request as urllib2
import time
import threading
def generate_songname(raw_song):
"""Generate a string of the format '[artist] - [song]' for the given song."""
if misc.is_spotify(raw_song):
tags = generate_metadata(raw_song)
raw_song = u'{0} - {1}'.format(tags['artists'][0]['name'], tags['name'])
return misc.fix_encoding(raw_song)
def generate_songname(tags):
"""Generate a string of the format '[artist] - [song]' for the given spotify song."""
raw_song = u'{0} - {1}'.format(tags['artists'][0]['name'], tags['name'])
return raw_song
def generate_metadata(raw_song):
@@ -49,70 +44,127 @@ def generate_metadata(raw_song):
meta_tags[u'copyright'] = album['copyrights'][0]['text']
except IndexError:
meta_tags[u'copyright'] = None
try:
meta_tags['isrc']
except KeyError:
meta_tags['isrc'] = None
meta_tags[u'release_date'] = album['release_date']
meta_tags[u'publisher'] = album['label']
meta_tags[u'total_tracks'] = album['tracks']['total']
# import pprint
# pprint.pprint(meta_tags)
# pprint.pprint(spotify.album(meta_tags['album']['id']))
return meta_tags
def generate_youtube_url(raw_song):
"""Search for the song on YouTube and generate an URL to its video."""
song = generate_songname(raw_song)
search_url = misc.generate_search_url(song)
item = urllib2.urlopen(search_url).read()
def generate_youtube_url(raw_song, tries_remaining=5):
"""Search for the song on YouTube and generate a URL to its video."""
# prevents an infinite loop but allows for a few retries
if tries_remaining == 0:
return
meta_tags = generate_metadata(raw_song)
if meta_tags is None:
song = raw_song
search_url = misc.generate_search_url(song, viewsort=False)
else:
song = generate_songname(meta_tags)
search_url = misc.generate_search_url(song, viewsort=True)
item = urllib.request.urlopen(search_url).read()
# item = unicode(item, 'utf-8')
items_parse = BeautifulSoup(item, "html.parser")
check = 1
videos = []
for x in items_parse.find_all('div', {'class': 'yt-lockup-dismissable yt-uix-tile'}):
# ensure result is not a channel
if x.find('channel') is not None or 'yt-lockup-channel' in x.parent.attrs['class'] or 'yt-lockup-channel' in x.attrs['class']:
continue
# ensure result is not a mix/playlist
if 'yt-lockup-playlist' in x.parent.attrs['class']:
continue
# confirm the video result is not an advertisement
if x.find('googleads') is not None:
continue
y = x.find('div', class_='yt-lockup-content')
link = y.find('a')['href']
title = y.find('a')['title']
try:
videotime = x.find('span', class_="video-time").get_text()
except AttributeError:
return generate_youtube_url(raw_song, tries_remaining - 1)
youtubedetails = {'link': link, 'title': title, 'videotime': videotime, 'seconds':misc.get_sec(videotime)}
videos.append(youtubedetails)
if meta_tags is None:
break
if not videos:
return None
if args.manual:
links = []
print(song)
print('')
print('0. Skip downloading this song')
# fetch all video links on first page on YouTube
for x in items_parse.find_all('h3', {'class': 'yt-lockup-title'}):
# confirm the video result is not an advertisement
if x.find('channel') is None and x.find('googleads') is None:
link = x.find('a')['href']
links.append(link)
print(u'{0}. {1} {2}'.format(check, x.get_text(), "http://youtube.com"+link))
check += 1
for i, v in enumerate(videos):
print(u'{0}. {1} {2} {3}'.format(i+1, v['title'], v['videotime'], "http://youtube.com"+v['link']))
print('')
# let user select the song to download
result = misc.input_link(links)
result = misc.input_link(videos)
if result is None:
return None
else:
# get video link of the first YouTube result
result = items_parse.find_all(
attrs={'class': 'yt-uix-tile-link'})[0]['href']
if meta_tags is not None:
# filter out videos that do not have a similar length to the Spotify song
duration_tolerance = 10
max_duration_tolerance = 20
possible_videos_by_duration = list()
# confirm the video result is not an advertisement
# otherwise keep iterating until it is not
while result.find('channel') > -1 or result.find('googleads') > -1:
result = items_parse.find_all(
attrs={'class': 'yt-uix-tile-link'})[check]['href']
check += 1
'''
start with a reasonable duration_tolerance, and increment duration_tolerance
until one of the Youtube results falls within the correct duration or
the duration_tolerance has reached the max_duration_tolerance
'''
while len(possible_videos_by_duration) == 0:
possible_videos_by_duration = list(filter(lambda x: abs(x['seconds'] - (int(meta_tags['duration_ms'])/1000)) <= duration_tolerance, videos))
duration_tolerance += 1
if duration_tolerance > max_duration_tolerance:
print(meta_tags['name'], 'by', meta_tags['artists'][0]['name'], 'was not found')
return None
result = possible_videos_by_duration[0]
else:
# if the metadata could not be acquired, take the first result from Youtube because the proper song length is unknown
result = videos[0]
full_link = None
if result:
full_link = u'youtube.com{0}'.format(result['link'])
full_link = u'youtube.com{0}'.format(result)
return full_link
def go_pafy(raw_song):
"""Parse track from YouTube."""
track_url = generate_youtube_url(raw_song)
if track_url is None:
return None
if misc.is_youtube(raw_song):
track_info = pafy.new(raw_song)
else:
return pafy.new(track_url)
track_url = generate_youtube_url(raw_song)
if track_url is None:
track_info = None
else:
track_info = pafy.new(track_url)
return track_info
def get_youtube_title(content, number=None):
"""Get the YouTube video's title."""
title = misc.fix_encoding(content.title)
title = content.title
if number is None:
return title
else:
@@ -130,8 +182,8 @@ def feed_playlist(username):
# in rare cases, playlists may not be found, so playlists['next']
# is None. Skip these. Also see Issue #91.
if playlist['name'] is not None:
print(u'{0:>5}.| {1:<30} | ({2} tracks)'.format(
check, misc.fix_encoding(playlist['name']),
print(u'{0:>5}. {1:<30} ({2} tracks)'.format(
check, playlist['name'],
playlist['tracks']['total']))
links.append(playlist)
check += 1
@@ -142,14 +194,18 @@ def feed_playlist(username):
print('')
playlist = misc.input_link(links)
print('')
write_tracks(playlist)
def write_tracks(playlist):
results = spotify.user_playlist(
playlist['owner']['id'], playlist['id'], fields='tracks,next')
print('')
file = u'{0}.txt'.format(slugify(playlist['name'], ok='-_()[]{}'))
print(u'Feeding {0} tracks to {1}'.format(playlist['tracks']['total'], file))
text_file = u'{0}.txt'.format(slugify(playlist['name'], ok='-_()[]{}'))
print(u'Feeding {0} tracks to {1}'.format(playlist['tracks']['total'], text_file))
tracks = results['tracks']
with open(file, 'a') as file_out:
with open(text_file, 'a') as file_out:
while True:
for item in tracks['items']:
track = item['track']
@@ -166,7 +222,7 @@ def feed_playlist(username):
break
def download_song(content):
def download_song(file_name, content):
"""Download the audio file from YouTube."""
if args.input_ext == '.webm':
link = content.getbestaudio(preftype='webm')
@@ -178,53 +234,51 @@ def download_song(content):
if link is None:
return False
else:
music_file = misc.generate_filename(content.title)
link.download(
filepath='Music/{0}{1}'.format(music_file, args.input_ext))
filepath='{0}{1}'.format(os.path.join(args.folder, file_name), args.input_ext))
return True
def check_exists(music_file, raw_song, islist=True):
"""Check if the input song already exists in the 'Music' folder."""
files = os.listdir('Music')
for file in files:
if file.endswith('.temp'):
os.remove(u'Music/{0}'.format(file))
"""Check if the input song already exists in the given folder."""
songs = os.listdir(args.folder)
for song in songs:
if song.endswith('.temp'):
os.remove(os.path.join(args.folder, song))
continue
# check if any file with similar name is already present in Music/
dfile = misc.fix_decoding(file)
umfile = misc.fix_decoding(misc.generate_filename(music_file))
if dfile.startswith(umfile):
# check if any song with similar name is already present in the given folder
file_name = misc.sanitize_title(music_file)
if song.startswith(file_name):
# check if the already downloaded song has correct metadata
already_tagged = metadata.compare(file, generate_metadata(raw_song))
already_tagged = metadata.compare(os.path.join(args.folder, song), generate_metadata(raw_song))
# if not, remove it and download again without prompt
if misc.is_spotify(raw_song) and not already_tagged:
os.remove('Music/{0}'.format(file))
os.remove(os.path.join(args.folder, song))
return False
# do not prompt and skip the current song
# if already downloaded when using list
if islist:
print('Song already exists')
return True
# if downloading only single song, prompt to re-download
else:
prompt = misc.user_input(
'Song with same name has already been downloaded. '
'Re-download? (y/n): ').lower()
prompt = input('Song with same name has already been downloaded. '
'Re-download? (y/n): ').lower()
if prompt == 'y':
os.remove('Music/{0}'.format(file))
os.remove(os.path.join(args.folder, song))
return False
else:
return True
return False
def grab_list(file):
def grab_list(text_file):
"""Download all songs from the list."""
with open(file, 'r') as listed:
with open(text_file, 'r') as listed:
lines = (listed.read()).splitlines()
# ignore blank lines in file (if any)
# ignore blank lines in text_file (if any)
try:
lines.remove('')
except ValueError:
@@ -244,75 +298,131 @@ def grab_list(file):
spotify = spotipy.Spotify(auth=new_token)
grab_single(raw_song, number=number)
# detect network problems
except (urllib2.URLError, TypeError, IOError):
except (urllib.request.URLError, TypeError, IOError):
lines.append(raw_song)
# remove the downloaded song from .txt
misc.trim_song(file)
misc.trim_song(text_file)
# and append it to the last line in .txt
with open(file, 'a') as myfile:
myfile.write(raw_song)
with open(text_file, 'a') as myfile:
myfile.write(raw_song + '\n')
print('Failed to download song. Will retry after other songs.')
# wait 0.5 sec to avoid infinite looping
time.sleep(0.5)
continue
except KeyboardInterrupt:
misc.grace_quit()
finally:
print('')
misc.trim_song(file)
misc.trim_song(text_file)
number += 1
def grab_playlist(playlist):
if '/' in playlist:
if playlist.endswith('/'):
playlist = playlist[:-1]
splits = playlist.split('/')
else:
splits = playlist.split(':')
username = splits[-3]
playlist_id = splits[-1]
playlists = spotify.user_playlists(username)
while True:
for playlist in playlists['items']:
if not playlist['name'] == None:
if playlist['id'] == playlist_id:
playlists['next'] = None
break
if playlists['next']:
playlists = spotify.next(playlists)
else:
break
write_tracks(playlist)
def grab_single(raw_song, number=None):
"""Logic behind downloading a song."""
if number:
islist = True
else:
islist = False
content = go_pafy(raw_song)
if content is None:
return
if misc.is_youtube(raw_song):
raw_song = slugify(content.title).replace('-', ' ')
# print '[number]. [artist] - [song]' if downloading from list
# otherwise print '[artist] - [song]'
print(get_youtube_title(content, number))
# generate file name of the song to download
music_file = misc.generate_filename(content.title)
music_file = misc.fix_decoding(music_file)
if not check_exists(music_file, raw_song, islist=islist):
if download_song(content):
meta_tags = generate_metadata(raw_song)
songname = content.title
if meta_tags is not None:
refined_songname = generate_songname(meta_tags)
if not refined_songname == ' - ':
songname = refined_songname
file_name = misc.sanitize_title(songname)
if not check_exists(file_name, raw_song, islist=islist):
if download_song(file_name, content):
print('')
input_song = music_file + args.input_ext
output_song = music_file + args.output_ext
convert.song(input_song, output_song, avconv=args.avconv,
verbose=args.verbose)
if not args.input_ext == args.output_ext:
os.remove('Music/{0}'.format(misc.fix_encoding(input_song)))
meta_tags = generate_metadata(raw_song)
if not args.no_metadata:
metadata.embed(output_song, meta_tags)
thread = threading.Thread(target=finalize, args=(file_name, meta_tags))
threads.append(thread)
thread.start()
else:
print('No audio streams available')
class Args(object):
def finalize(file_name, meta_tags):
input_song = file_name + args.input_ext
output_song = file_name + args.output_ext
convert.song(input_song, output_song, args.folder,
avconv=args.avconv, verbose=args.verbose)
if not args.input_ext == args.output_ext:
os.remove(os.path.join(args.folder, input_song))
if not args.no_metadata:
metadata.embed(os.path.join(args.folder, output_song), meta_tags)
class TestArgs(object):
manual = False
input_ext = '.m4a'
output_ext = '.mp3'
folder = 'Music/'
args = Args()
# token is mandatory when using Spotify's API
# https://developer.spotify.com/news-stories/2017/01/27/removing-unauthenticated-calls-to-the-web-api/
token = misc.generate_token()
spotify = spotipy.Spotify(auth=token)
misc.filter_path('Music')
if __name__ == '__main__':
os.chdir(sys.path[0])
args = misc.get_arguments()
misc.filter_path(args.folder)
threads = []
if args.song:
grab_single(raw_song=args.song)
elif args.list:
grab_list(file=args.list)
grab_list(text_file=args.list)
elif args.playlist:
grab_playlist(playlist=args.playlist)
elif args.username:
feed_playlist(username=args.username)
for thread in threads:
thread.join()
else:
misc.filter_path('Music')
args = TestArgs()

72
test/test_simple.py Normal file
View File

@@ -0,0 +1,72 @@
# -*- coding: UTF-8 -*-
import spotdl
import os
raw_song = "Tony's Videos VERY SHORT VIDEO 28.10.2016"
for x in os.listdir(spotdl.args.folder):
os.remove(os.path.join(spotdl.args.folder, x))
def test_youtube_url():
expect_url = 'youtube.com/watch?v=qOOcy2-tmbk'
url = spotdl.generate_youtube_url(raw_song)
assert url == expect_url
def test_youtube_title():
expect_title = "Tony's Videos VERY SHORT VIDEO 28.10.2016"
global content
content = spotdl.go_pafy(raw_song)
global title
title = spotdl.get_youtube_title(content)
assert title == expect_title
def test_check_exists():
expect_check = False
# prerequisites for determining filename
file_name = spotdl.misc.sanitize_title(title)
check = spotdl.check_exists(file_name, raw_song, islist=True)
assert check == expect_check
def test_download():
expect_download = True
# prerequisites for determining filename
file_name = spotdl.misc.sanitize_title(title)
download = spotdl.download_song(file_name, content)
assert download == expect_download
def test_convert():
# exit code 0 = success
expect_convert = 0
# prerequisites for determining filename
file_name = spotdl.misc.sanitize_title(title)
input_song = file_name + spotdl.args.input_ext
output_song = file_name + spotdl.args.output_ext
convert = spotdl.convert.song(input_song, output_song, spotdl.args.folder)
assert convert == expect_convert
def test_metadata():
expect_metadata = None
# prerequisites for determining filename
meta_tags = spotdl.generate_metadata(raw_song)
meta_tags = spotdl.generate_metadata(raw_song)
file_name = spotdl.misc.sanitize_title(title)
output_song = file_name + spotdl.args.output_ext
metadata_output = spotdl.metadata.embed(os.path.join(spotdl.args.folder, output_song), meta_tags)
input_song = file_name + spotdl.args.input_ext
metadata_input = spotdl.metadata.embed(os.path.join(spotdl.args.folder, input_song), meta_tags)
assert (metadata_output == expect_metadata) and (metadata_input == expect_metadata)
def test_check_exists2():
expect_check = True
# prerequisites for determining filename
file_name = spotdl.misc.sanitize_title(title)
input_song = file_name + spotdl.args.input_ext
os.remove(os.path.join(spotdl.args.folder, input_song))
check = spotdl.check_exists(file_name, raw_song, islist=True)
assert check == expect_check

View File

@@ -1,80 +0,0 @@
# -*- coding: UTF-8 -*-
import spotdl
import os
raw_song = 'http://open.spotify.com/track/0JlS7BXXD07hRmevDnbPDU'
for x in os.listdir('Music'):
os.remove('Music/' + x)
def test_spotify_title():
expect_title = 'David André Østby - Intro'
title = spotdl.generate_songname(raw_song)
assert title == expect_title
def test_youtube_url():
expect_url = 'youtube.com/watch?v=rg1wfcty0BA'
url = spotdl.generate_youtube_url(raw_song)
assert url == expect_url
def test_youtube_title():
expect_title = 'Intro - David André Østby'
content = spotdl.go_pafy(raw_song)
title = spotdl.get_youtube_title(content)
assert title == expect_title
def test_check_exists():
expect_check = False
content = spotdl.go_pafy(raw_song)
music_file = spotdl.misc.generate_filename(content.title)
music_file = spotdl.misc.fix_decoding(music_file)
check = spotdl.check_exists(music_file, raw_song, islist=True)
assert check == expect_check
def test_download():
expect_download = True
content = spotdl.go_pafy(raw_song)
download = spotdl.download_song(content)
assert download == expect_download
def test_convert():
# exit code 0 = success
expect_convert = 0
content = spotdl.go_pafy(raw_song)
music_file = spotdl.misc.generate_filename(content.title)
music_file = spotdl.misc.fix_decoding(music_file)
input_song = music_file + spotdl.args.input_ext
output_song = music_file + spotdl.args.output_ext
convert = spotdl.convert.song(input_song, output_song)
assert convert == expect_convert
def test_metadata():
expect_metadata = True
content = spotdl.go_pafy(raw_song)
music_file = spotdl.misc.generate_filename(content.title)
music_file = spotdl.misc.fix_decoding(music_file)
meta_tags = spotdl.generate_metadata(raw_song)
output_song = music_file + spotdl.args.output_ext
metadata_output = spotdl.metadata.embed(output_song, meta_tags)
input_song = music_file + spotdl.args.input_ext
metadata_input = spotdl.metadata.embed(input_song, meta_tags)
assert metadata_output == (metadata_input == expect_metadata)
def test_check_exists2():
expect_check = True
content = spotdl.go_pafy(raw_song)
music_file = spotdl.misc.generate_filename(content.title)
music_file = spotdl.misc.fix_decoding(music_file)
input_song = music_file + spotdl.args.input_ext
os.remove('Music/' + spotdl.misc.fix_encoding(input_song))
check = spotdl.check_exists(music_file, raw_song, islist=True)
assert check == expect_check

77
test/test_spotify.py Normal file
View File

@@ -0,0 +1,77 @@
# -*- coding: UTF-8 -*-
import spotdl
import os
raw_song = 'http://open.spotify.com/track/0JlS7BXXD07hRmevDnbPDU'
for x in os.listdir(spotdl.args.folder):
os.remove(os.path.join(spotdl.args.folder, x))
def test_spotify_title():
expect_title = 'David André Østby - Intro'
global meta_tags
meta_tags = spotdl.generate_metadata(raw_song)
title = spotdl.generate_songname(meta_tags)
assert title == expect_title
def youtube_url():
expect_url = 'youtube.com/watch?v=rg1wfcty0BA'
url = spotdl.generate_youtube_url(raw_song)
assert url == expect_url
def youtube_title():
expect_title = 'Intro - David André Østby'
content = spotdl.go_pafy(raw_song)
title = spotdl.get_youtube_title(content)
assert title == expect_title
def test_check_exists():
expect_check = False
# prerequisites for determining filename
songname = spotdl.generate_songname(meta_tags)
global file_name
file_name = spotdl.misc.sanitize_title(songname)
check = spotdl.check_exists(file_name, raw_song, islist=True)
assert check == expect_check
def test_download():
expect_download = True
# prerequisites for determining filename
content = spotdl.go_pafy(raw_song)
download = spotdl.download_song(file_name, content)
assert download == expect_download
def test_convert():
# exit code 0 = success
expect_convert = 0
# prerequisites for determining filename
input_song = file_name + spotdl.args.input_ext
output_song = file_name + spotdl.args.output_ext
convert = spotdl.convert.song(input_song, output_song, spotdl.args.folder)
assert convert == expect_convert
def test_metadata():
expect_metadata = True
# prerequisites for determining filename
output_song = file_name + spotdl.args.output_ext
metadata_output = spotdl.metadata.embed(os.path.join(spotdl.args.folder, output_song), meta_tags)
input_song = file_name + spotdl.args.input_ext
metadata_input = spotdl.metadata.embed(os.path.join(spotdl.args.folder, input_song), meta_tags)
assert metadata_output == (metadata_input == expect_metadata)
def test_check_exists2():
expect_check = True
# prerequisites for determining filename
input_song = file_name + spotdl.args.input_ext
os.remove(os.path.join(spotdl.args.folder, input_song))
check = spotdl.check_exists(file_name, raw_song, islist=True)
assert check == expect_check