mirror of
https://github.com/KevinMidboe/spotify-downloader.git
synced 2025-10-29 18:00:15 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d18663c0e7 | ||
|
|
4db1dcc9b8 | ||
|
|
98d21bbad9 | ||
|
|
3812de3a43 | ||
|
|
9068f6008e | ||
|
|
f9e85d3796 | ||
|
|
97a8c21eb9 | ||
|
|
17e6d1fa2e | ||
|
|
31b91bd6da | ||
|
|
633fb9b1bd | ||
|
|
c73c650383 | ||
|
|
6b3dafa99b | ||
|
|
ddd57eda88 | ||
|
|
0178307b20 | ||
|
|
9d25197a5f | ||
|
|
7e0fdfbce3 | ||
|
|
ed630823b0 | ||
|
|
8997455e4c | ||
|
|
c23be97619 | ||
|
|
6ff72b0495 | ||
|
|
01c0501f46 | ||
|
|
fa5ec719f4 | ||
|
|
d1ae0c05a6 | ||
|
|
93f45badbf | ||
|
|
624f9d95f3 | ||
|
|
67f594c7b0 | ||
|
|
606e55bb1e | ||
|
|
dbaa890976 | ||
|
|
6401eca454 | ||
|
|
5d4f2d5148 | ||
|
|
05023b90c9 | ||
|
|
146f70c8a7 | ||
|
|
6ac25bda0c | ||
|
|
dc6019a1a5 | ||
|
|
0607003cfa | ||
|
|
1b56888cb0 | ||
|
|
4a2bbb2535 | ||
|
|
4cf8a210bf | ||
|
|
9564a71035 | ||
|
|
ce3a6c3d5a | ||
|
|
d28ff08a69 | ||
|
|
75be7285c0 | ||
|
|
e1ef35b6b9 | ||
|
|
cc7260dfa6 | ||
|
|
5add1fd0f8 | ||
|
|
b73d0b1049 | ||
|
|
c80f2996fb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ __pycache__/
|
||||
.cache/
|
||||
Music/
|
||||
*.txt
|
||||
.python-version
|
||||
|
||||
18
.travis.yml
18
.travis.yml
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
92
README.md
92
README.md
@@ -1,5 +1,7 @@
|
||||
# Spotify-Downloader
|
||||
|
||||
[](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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
92
core/misc.py
92
core/misc.py
@@ -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
300
spotdl.py
@@ -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
72
test/test_simple.py
Normal 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
|
||||
@@ -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
77
test/test_spotify.py
Normal 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
|
||||
Reference in New Issue
Block a user