mirror of
https://github.com/cooperhammond/irs.git
synced 2024-12-31 18:55:28 +00:00
Merge pull request #28 from kepoorhampond/random-unicode-character-errors
fixed random unicode character error and removed the need to install ffmpeg.
This commit is contained in:
commit
573bc7c656
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,6 +4,7 @@
|
||||||
/*.egg-info/
|
/*.egg-info/
|
||||||
/build/
|
/build/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
.eggs
|
||||||
|
|
||||||
# For easy updating of stuff.
|
# For easy updating of stuff.
|
||||||
update_pypi_and_github.py
|
update_pypi_and_github.py
|
||||||
|
@ -21,4 +22,4 @@ update_pypi_and_github.py
|
||||||
.coverage
|
.coverage
|
||||||
|
|
||||||
# Temporarily built binaries
|
# Temporarily built binaries
|
||||||
ffmpeg binaries/
|
ffmpeg binaries/
|
||||||
|
|
|
@ -4,13 +4,8 @@ python:
|
||||||
- "3.5"
|
- "3.5"
|
||||||
- "3.6"
|
- "3.6"
|
||||||
|
|
||||||
before_script:
|
|
||||||
- sudo aptitude -y -q install ffmpeg libavcodec-extra-53 lame libmp3lame0
|
|
||||||
# These dependencies are necessary for ffmpeg. I currently hate all things
|
|
||||||
# doing with Travis and ffmpeg because I have no direct access to test stuff.
|
|
||||||
# I've gone through 25 seperate commits to get it working.
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
- pip install -r requirements.txt
|
||||||
- python setup.py install
|
- python setup.py install
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
|
14
README.md
14
README.md
|
@ -1,7 +1,6 @@
|
||||||
<div align="center"><img src ="http://i.imgur.com/VbsyTe7.png" /></div>
|
<div align="center"><img src ="http://i.imgur.com/VbsyTe7.png" /></div>
|
||||||
|
|
||||||
Ironic Redistribution System
|
# Ironic Redistribution System
|
||||||
===
|
|
||||||
|
|
||||||
[![License: GNU](https://img.shields.io/badge/license-gnu-yellow.svg?style=flat-square)](http://www.gnu.org/licenses/gpl.html)
|
[![License: GNU](https://img.shields.io/badge/license-gnu-yellow.svg?style=flat-square)](http://www.gnu.org/licenses/gpl.html)
|
||||||
[![Stars](https://img.shields.io/github/stars/kepoorhampond/irs.svg?style=flat-square)](https://github.com/kepoorhampond/irs/stargazers)
|
[![Stars](https://img.shields.io/github/stars/kepoorhampond/irs.svg?style=flat-square)](https://github.com/kepoorhampond/irs/stargazers)
|
||||||
|
@ -14,10 +13,9 @@ Ironic Redistribution System
|
||||||
|
|
||||||
> A music downloader that understands your metadata needs.
|
> A music downloader that understands your metadata needs.
|
||||||
|
|
||||||
A tool to download your music with metadata. It uses [Spotify](https://www.spotify.com/) for finding metadata and [Youtube](https://www.youtube.com/) for the actual audio source.
|
A tool to download your music with metadata. It uses [Spotify](https://www.spotify.com/) for finding metadata and [Youtube](https://www.youtube.com/) for the actual audio source. You will need to have some Spotify tokens, the instructions to set them up are [here](https://github.com/kepoorhampond/irs#spotify-tokens).
|
||||||
|
|
||||||
Works with Python 2 and 3.
|
Works with Python 2 and 3.
|
||||||
___
|
|
||||||
|
|
||||||
## Demo and Usages
|
## Demo and Usages
|
||||||
|
|
||||||
|
@ -59,7 +57,7 @@ $ irs -s "Bohemian Rhapsody"
|
||||||
$ irs -p "Best Nirvana"
|
$ irs -p "Best Nirvana"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install & The Dependencies <sub><sup>(my new band name)</sub></sup>
|
## Install & The Dependencies <sub><sup>(my new band name <sub><sup><sup><sub>\s</sub></sup></sup></sub>)</sub></sup>
|
||||||
|
|
||||||
Really there's only one actual external dependency: `ffmpeg`. For windows, you'll want to follow [this](http://www.wikihow.com/Install-FFmpeg-on-Windows) guide. For OSX, you'll want to install it through [`brew`](https://brew.sh/) with this command:
|
Really there's only one actual external dependency: `ffmpeg`. For windows, you'll want to follow [this](http://www.wikihow.com/Install-FFmpeg-on-Windows) guide. For OSX, you'll want to install it through [`brew`](https://brew.sh/) with this command:
|
||||||
```
|
```
|
||||||
|
@ -76,9 +74,11 @@ Other than `ffmpeg` though, all other dependencies are automatically installed w
|
||||||
$ sudo pip install irs
|
$ sudo pip install irs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Spotify Playlists
|
**You will need to have some Spotify tokens, the instructions to set them up are [here](https://github.com/kepoorhampond/irs#spotify-tokens).**
|
||||||
|
|
||||||
To download spotify playlists, you'll want to head to their Dev Apps page, [here](https://developer.spotify.com/my-applications/). After doing that you'll want to create a new app. Name it whatever you want and then once you've done that, find the `Client ID` and `Client Secret` keys. You'll want to take those keys and paste them into your system's environment variables as `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET`, correspondingly. Viola! You can now download playlists!
|
## Spotify Tokens
|
||||||
|
|
||||||
|
To download metadata through spotify, you'll want to head to their Dev Apps page, [here](https://developer.spotify.com/my-applications/). After doing that you'll want to create a new app. Name it whatever you want and then once you've done that, find the `Client ID` and `Client Secret` keys. You'll want to take those keys and paste them into your system's environment variables as `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET`, correspondingly. Viola! You can now download metadata with IRS!
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ directory to place files in.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Combiner args from argparse and the ripper_args as above and then
|
# Combine args from argparse and the ripper_args as above and then
|
||||||
# remove all keys with the value of "None"
|
# remove all keys with the value of "None"
|
||||||
ripper_args.update(vars(args))
|
ripper_args.update(vars(args))
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,9 @@
|
||||||
CONFIG = dict(
|
import sys
|
||||||
|
from os import path
|
||||||
|
|
||||||
default_flags = ['-o'],
|
|
||||||
# For default flags. Right now, it organizes your files into an
|
|
||||||
# artist/album/song structure.
|
|
||||||
# To add a flag or argument, add an element to the index:
|
|
||||||
# default_flags = ['-o', '-l', '~/Music']
|
|
||||||
|
|
||||||
SPOTIFY_CLIENT_ID = '',
|
sys.path.append(path.expanduser("~/.irs")) # Add config to path
|
||||||
SPOTIFY_CLIENT_SECRET = '',
|
|
||||||
# You can either specify Spotify keys here, or in environment variables.
|
|
||||||
|
|
||||||
additional_search_terms = 'lyrics',
|
import config_ # from "~/.irs/config_.py"
|
||||||
# Search terms for youtube
|
|
||||||
|
|
||||||
organize = True,
|
CONFIG = config_.CONFIG
|
||||||
# True always forces organization.
|
|
||||||
# False always forces non-organization.
|
|
||||||
# None allows options and flags to determine if the files
|
|
||||||
# will be organized.
|
|
||||||
|
|
||||||
custom_directory = "",
|
|
||||||
# Defaults to '~/Music'
|
|
||||||
)
|
|
||||||
|
|
24
irs/config_preset
Normal file
24
irs/config_preset
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
CONFIG = dict(
|
||||||
|
|
||||||
|
default_flags = ['-o'],
|
||||||
|
# For default flags. Right now, it organizes your files into an
|
||||||
|
# artist/album/song structure.
|
||||||
|
# To add a flag or argument, add an element to the index:
|
||||||
|
# default_flags = ['-o', '-l', '~/Music']
|
||||||
|
|
||||||
|
SPOTIFY_CLIENT_ID = '',
|
||||||
|
SPOTIFY_CLIENT_SECRET = '',
|
||||||
|
# You can either specify Spotify keys here, or in environment variables.
|
||||||
|
|
||||||
|
additional_search_terms = 'lyrics',
|
||||||
|
# Search terms for youtube
|
||||||
|
|
||||||
|
organize = True,
|
||||||
|
# True always forces organization.
|
||||||
|
# False always forces non-organization.
|
||||||
|
# None allows options and flags to determine if the files
|
||||||
|
# will be organized.
|
||||||
|
|
||||||
|
custom_directory = "",
|
||||||
|
# When blank, defaults to '~/Music'
|
||||||
|
)
|
|
@ -56,9 +56,9 @@ class Metadata:
|
||||||
mp3.save()
|
mp3.save()
|
||||||
|
|
||||||
|
|
||||||
def find_album_and_track(song, artist):
|
def find_album_and_track(song, artist, spotify=spotipy.Spotify()):
|
||||||
tracks = spotipy.Spotify().search(q=song, type="track"
|
tracks = spotify.search(q=song, type="track")["tracks"]["items"]
|
||||||
)["tracks"]["items"]
|
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
if om.blank_include(track["name"], song):
|
if om.blank_include(track["name"], song):
|
||||||
if om.blank_include(track["artists"][0]["name"], artist):
|
if om.blank_include(track["artists"][0]["name"], artist):
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
# Powered by:
|
|
||||||
import youtube_dl
|
|
||||||
import spotipy
|
|
||||||
from spotipy.oauth2 import SpotifyClientCredentials
|
|
||||||
|
|
||||||
# System
|
# System
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
# Add youtube-dl binary to path
|
||||||
|
sys.path.append(os.path.expanduser("~/.irs/bin/youtube-dl"))
|
||||||
|
|
||||||
|
# Powered by:
|
||||||
|
import youtube_dl # Locally imported from the binary
|
||||||
|
import spotipy
|
||||||
|
from spotipy.oauth2 import SpotifyClientCredentials
|
||||||
|
|
||||||
|
|
||||||
# Local utilities
|
# Local utilities
|
||||||
from .utils import YdlUtils, ObjManip, Config
|
from .utils import YdlUtils, ObjManip, Config
|
||||||
from .metadata import Metadata
|
from .metadata import Metadata
|
||||||
|
@ -80,7 +85,7 @@ class Ripper:
|
||||||
# those flaws for being exclusive to them.
|
# those flaws for being exclusive to them.
|
||||||
# And if those flaws are really enough to turn you off of them,
|
# And if those flaws are really enough to turn you off of them,
|
||||||
# then you *probably* don't really want to be with them anyways.
|
# then you *probably* don't really want to be with them anyways.
|
||||||
# Either way, it's up to you.
|
# Either way, it's up to you. (I'd just ignore this)
|
||||||
|
|
||||||
if Config.parse_organize(self):
|
if Config.parse_organize(self):
|
||||||
if self.type in ("album", "song"):
|
if self.type in ("album", "song"):
|
||||||
|
@ -125,7 +130,7 @@ class Ripper:
|
||||||
def find_yt_url(self, song=None, artist=None, additional_search=None):
|
def find_yt_url(self, song=None, artist=None, additional_search=None):
|
||||||
if additional_search is None:
|
if additional_search is None:
|
||||||
additional_search = Config.parse_search_terms(self)
|
additional_search = Config.parse_search_terms(self)
|
||||||
print(self.args["hook-text"].get("youtube"))
|
print(str(self.args["hook-text"].get("youtube")))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not song:
|
if not song:
|
||||||
|
@ -136,7 +141,8 @@ class Ripper:
|
||||||
raise ValueError("Must specify song_title/artist in `args` with \
|
raise ValueError("Must specify song_title/artist in `args` with \
|
||||||
init, or in method arguments.")
|
init, or in method arguments.")
|
||||||
|
|
||||||
search_terms = song + " " + artist + " " + additional_search
|
search_terms = str(song) + " " + str(artist
|
||||||
|
) + " " + str(additional_search)
|
||||||
query_string = urlencode({"search_query": (
|
query_string = urlencode({"search_query": (
|
||||||
search_terms.encode('utf-8'))})
|
search_terms.encode('utf-8'))})
|
||||||
link = "http://www.youtube.com/results?" + query_string
|
link = "http://www.youtube.com/results?" + query_string
|
||||||
|
@ -194,7 +200,7 @@ album".split(" ")
|
||||||
|
|
||||||
return ("https://youtube.com" + self.code["href"], self.code["title"])
|
return ("https://youtube.com" + self.code["href"], self.code["title"])
|
||||||
|
|
||||||
def album(self, title, artist=None): # Alias for `spotify_list("album", ...)`
|
def album(self, title, artist=None): # Alias for spotify_list("album", ..)
|
||||||
return self.spotify_list("album", title=title, artist=artist)
|
return self.spotify_list("album", title=title, artist=artist)
|
||||||
|
|
||||||
def playlist(self, title, username):
|
def playlist(self, title, username):
|
||||||
|
@ -321,7 +327,7 @@ with init, or in method arguments.")
|
||||||
return locations
|
return locations
|
||||||
|
|
||||||
def parse_song_data(self, song, artist):
|
def parse_song_data(self, song, artist):
|
||||||
album, track = find_album_and_track(song, artist)
|
album, track = find_album_and_track(song, artist, self.spotify)
|
||||||
if album is False:
|
if album is False:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -372,8 +378,7 @@ init, or in method arguments.")
|
||||||
|
|
||||||
print(self.args["hook-text"].get("song").format(song, artist))
|
print(self.args["hook-text"].get("song").format(song, artist))
|
||||||
|
|
||||||
file_name = str(data["file_prefix"] + ObjManip.blank(song, False) +
|
file_name = data["file_prefix"] + ObjManip.blank(song, False) + ".mp3"
|
||||||
".mp3")
|
|
||||||
|
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
'format': 'bestaudio/best',
|
'format': 'bestaudio/best',
|
||||||
|
@ -386,6 +391,7 @@ init, or in method arguments.")
|
||||||
'progress_hooks': [YdlUtils.my_hook],
|
'progress_hooks': [YdlUtils.my_hook],
|
||||||
'output': "tmp_file",
|
'output': "tmp_file",
|
||||||
'prefer-ffmpeg': True,
|
'prefer-ffmpeg': True,
|
||||||
|
'ffmpeg_location': os.path.expanduser("~/.irs/bin/")
|
||||||
}
|
}
|
||||||
|
|
||||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
|
31
irs/utils.py
31
irs/utils.py
|
@ -15,7 +15,11 @@ from time import sleep
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
# Config File and Flags
|
# Config File and Flags
|
||||||
from .config import CONFIG
|
if sys.version_info[0] == 2:
|
||||||
|
import config
|
||||||
|
CONFIG = config.CONFIG
|
||||||
|
else:
|
||||||
|
from irs.config import CONFIG
|
||||||
|
|
||||||
|
|
||||||
# ==================
|
# ==================
|
||||||
|
@ -56,6 +60,22 @@ class YdlUtils:
|
||||||
# Object Manipulation and Checking
|
# Object Manipulation and Checking
|
||||||
# ================================
|
# ================================
|
||||||
|
|
||||||
|
def set_encoding(ld, encoding): # ld => list or dictionary with strings in it
|
||||||
|
if type(ld) == dict:
|
||||||
|
for k in ld:
|
||||||
|
if type(ld[k]) == dict or type(ld[k]) == list:
|
||||||
|
ld[k] = set_encoding(ld[k], encoding)
|
||||||
|
elif type(ld[k]) == str:
|
||||||
|
ld[k] = encoding(ld[k])
|
||||||
|
elif type(ld) == list:
|
||||||
|
for index, datum in enumerate(ld):
|
||||||
|
if type(datum) == str:
|
||||||
|
ld[index] = encoding(datum)
|
||||||
|
elif type(ld[k]) == dict or type(ld[k]) == list:
|
||||||
|
ld[k] = set_encoding(ld[k], encoding)
|
||||||
|
return ld
|
||||||
|
|
||||||
|
|
||||||
@staticmethods
|
@staticmethods
|
||||||
class ObjManip: # Object Manipulation
|
class ObjManip: # Object Manipulation
|
||||||
def limit_song_name(song):
|
def limit_song_name(song):
|
||||||
|
@ -77,7 +97,7 @@ class ObjManip: # Object Manipulation
|
||||||
def blank(string, downcase=True):
|
def blank(string, downcase=True):
|
||||||
import re
|
import re
|
||||||
regex = re.compile('[^a-zA-Z0-9\ ]')
|
regex = re.compile('[^a-zA-Z0-9\ ]')
|
||||||
string = regex.sub('', string)
|
string = regex.sub('', str(string))
|
||||||
if downcase:
|
if downcase:
|
||||||
string = string.lower()
|
string = string.lower()
|
||||||
return ' '.join(string.split())
|
return ' '.join(string.split())
|
||||||
|
@ -118,11 +138,18 @@ class ObjManip: # Object Manipulation
|
||||||
del new_d[x]
|
del new_d[x]
|
||||||
return new_d
|
return new_d
|
||||||
|
|
||||||
|
# ld => a list or dictionary with strings in it
|
||||||
|
def set_utf8_encoding(ld):
|
||||||
|
return set_encoding(ld, lambda x: x.encode('utf-8'))
|
||||||
|
|
||||||
|
def set_encoding(*args):
|
||||||
|
return set_encoding(*args)
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# Download Log Reading/Updating/Formatting
|
# Download Log Reading/Updating/Formatting
|
||||||
# ========================================
|
# ========================================
|
||||||
|
|
||||||
|
|
||||||
@staticmethods
|
@staticmethods
|
||||||
class DLog:
|
class DLog:
|
||||||
def format_download_log_line(t, download_status="not downloaded"):
|
def format_download_log_line(t, download_status="not downloaded"):
|
||||||
|
|
7
requirements.txt
Normal file
7
requirements.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
--index-url https://pypi.python.org/simple/
|
||||||
|
|
||||||
|
bs4
|
||||||
|
mutagen
|
||||||
|
requests
|
||||||
|
spotipy
|
||||||
|
ydl-binaries
|
43
setup.py
43
setup.py
|
@ -1,4 +1,39 @@
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
from setuptools.command.develop import develop
|
||||||
|
from setuptools.command.install import install
|
||||||
|
|
||||||
|
|
||||||
|
class PostDevelopCommand(develop):
|
||||||
|
"""Post-installation for development mode."""
|
||||||
|
def run(self):
|
||||||
|
# PUT YOUR PRE-INSTALL SCRIPT HERE or CALL A FUNCTION
|
||||||
|
develop.run(self)
|
||||||
|
# PUT YOUR POST-INSTALL SCRIPT HERE or CALL A FUNCTION
|
||||||
|
|
||||||
|
|
||||||
|
class PostInstallCommand(install):
|
||||||
|
"""Post-installation for installation mode."""
|
||||||
|
def run(self):
|
||||||
|
install.run(self) # Actually install the module and dependencies
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ydl_binaries
|
||||||
|
except ImportError:
|
||||||
|
import pip
|
||||||
|
pip.main(['install', "ydl-binaries"])
|
||||||
|
|
||||||
|
import ydl_binaries
|
||||||
|
from os import path
|
||||||
|
from shutil import copyfile
|
||||||
|
|
||||||
|
print("\n\nDownloading Dependencies:\n")
|
||||||
|
ydl_binaries.download_ffmpeg("~/.irs/bin")
|
||||||
|
ydl_binaries.update_ydl("~/.irs/bin")
|
||||||
|
|
||||||
|
config_file = path.expanduser("~/.irs/config_.py")
|
||||||
|
if not path.isfile(config_file):
|
||||||
|
copyfile("irs/config_preset", config_file)
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = 'irs',
|
name = 'irs',
|
||||||
|
@ -9,14 +44,18 @@ setup(
|
||||||
author_email = 'kepoorh@gmail.com',
|
author_email = 'kepoorh@gmail.com',
|
||||||
license = 'GPL',
|
license = 'GPL',
|
||||||
packages = ['irs'],
|
packages = ['irs'],
|
||||||
install_requires=[
|
install_requires = [
|
||||||
'bs4',
|
'bs4',
|
||||||
'mutagen',
|
'mutagen',
|
||||||
'youtube-dl',
|
|
||||||
'requests',
|
'requests',
|
||||||
'spotipy',
|
'spotipy',
|
||||||
|
'ydl-binaries'
|
||||||
],
|
],
|
||||||
entry_points = {
|
entry_points = {
|
||||||
'console_scripts': ['irs = irs.cli:main'],
|
'console_scripts': ['irs = irs.cli:main'],
|
||||||
},
|
},
|
||||||
|
cmdclass = {
|
||||||
|
'develop': PostDevelopCommand,
|
||||||
|
'install': PostInstallCommand,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,4 +2,4 @@ from irs.ripper import Ripper
|
||||||
|
|
||||||
print ("[*] Testing `song.py`")
|
print ("[*] Testing `song.py`")
|
||||||
Ripper().song("Da Frame 2R", "Arctic Monkeys")
|
Ripper().song("Da Frame 2R", "Arctic Monkeys")
|
||||||
print ("[+] Passed!")
|
print ("[+] Passed!")
|
||||||
|
|
Loading…
Reference in a new issue