mirror of
https://github.com/cooperhammond/irs.git
synced 2024-12-22 17:35: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/
|
||||
/build/
|
||||
__pycache__/
|
||||
.eggs
|
||||
|
||||
# For easy updating of stuff.
|
||||
update_pypi_and_github.py
|
||||
|
@ -21,4 +22,4 @@ update_pypi_and_github.py
|
|||
.coverage
|
||||
|
||||
# Temporarily built binaries
|
||||
ffmpeg binaries/
|
||||
ffmpeg binaries/
|
||||
|
|
|
@ -4,13 +4,8 @@ python:
|
|||
- "3.5"
|
||||
- "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:
|
||||
- pip install -r requirements.txt
|
||||
- python setup.py install
|
||||
|
||||
script:
|
||||
|
|
14
README.md
14
README.md
|
@ -1,7 +1,6 @@
|
|||
<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)
|
||||
[![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 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.
|
||||
___
|
||||
|
||||
## Demo and Usages
|
||||
|
||||
|
@ -59,7 +57,7 @@ $ irs -s "Bohemian Rhapsody"
|
|||
$ 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:
|
||||
```
|
||||
|
@ -76,9 +74,11 @@ Other than `ffmpeg` though, all other dependencies are automatically installed w
|
|||
$ 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
|
||||
|
||||
|
|
|
@ -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"
|
||||
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 = '',
|
||||
SPOTIFY_CLIENT_SECRET = '',
|
||||
# You can either specify Spotify keys here, or in environment variables.
|
||||
sys.path.append(path.expanduser("~/.irs")) # Add config to path
|
||||
|
||||
additional_search_terms = 'lyrics',
|
||||
# Search terms for youtube
|
||||
import config_ # from "~/.irs/config_.py"
|
||||
|
||||
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 = "",
|
||||
# Defaults to '~/Music'
|
||||
)
|
||||
CONFIG = config_.CONFIG
|
||||
|
|
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()
|
||||
|
||||
|
||||
def find_album_and_track(song, artist):
|
||||
tracks = spotipy.Spotify().search(q=song, type="track"
|
||||
)["tracks"]["items"]
|
||||
def find_album_and_track(song, artist, spotify=spotipy.Spotify()):
|
||||
tracks = spotify.search(q=song, type="track")["tracks"]["items"]
|
||||
|
||||
for track in tracks:
|
||||
if om.blank_include(track["name"], song):
|
||||
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
|
||||
import sys
|
||||
import os
|
||||
import glob
|
||||
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
|
||||
from .utils import YdlUtils, ObjManip, Config
|
||||
from .metadata import Metadata
|
||||
|
@ -80,7 +85,7 @@ class Ripper:
|
|||
# those flaws for being exclusive to 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.
|
||||
# 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 self.type in ("album", "song"):
|
||||
|
@ -125,7 +130,7 @@ class Ripper:
|
|||
def find_yt_url(self, song=None, artist=None, additional_search=None):
|
||||
if additional_search is None:
|
||||
additional_search = Config.parse_search_terms(self)
|
||||
print(self.args["hook-text"].get("youtube"))
|
||||
print(str(self.args["hook-text"].get("youtube")))
|
||||
|
||||
try:
|
||||
if not song:
|
||||
|
@ -136,7 +141,8 @@ class Ripper:
|
|||
raise ValueError("Must specify song_title/artist in `args` with \
|
||||
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": (
|
||||
search_terms.encode('utf-8'))})
|
||||
link = "http://www.youtube.com/results?" + query_string
|
||||
|
@ -194,7 +200,7 @@ album".split(" ")
|
|||
|
||||
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)
|
||||
|
||||
def playlist(self, title, username):
|
||||
|
@ -321,7 +327,7 @@ with init, or in method arguments.")
|
|||
return locations
|
||||
|
||||
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:
|
||||
return {}
|
||||
|
||||
|
@ -372,8 +378,7 @@ init, or in method arguments.")
|
|||
|
||||
print(self.args["hook-text"].get("song").format(song, artist))
|
||||
|
||||
file_name = str(data["file_prefix"] + ObjManip.blank(song, False) +
|
||||
".mp3")
|
||||
file_name = data["file_prefix"] + ObjManip.blank(song, False) + ".mp3"
|
||||
|
||||
ydl_opts = {
|
||||
'format': 'bestaudio/best',
|
||||
|
@ -386,6 +391,7 @@ init, or in method arguments.")
|
|||
'progress_hooks': [YdlUtils.my_hook],
|
||||
'output': "tmp_file",
|
||||
'prefer-ffmpeg': True,
|
||||
'ffmpeg_location': os.path.expanduser("~/.irs/bin/")
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
# ================================
|
||||
|
||||
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
|
||||
class ObjManip: # Object Manipulation
|
||||
def limit_song_name(song):
|
||||
|
@ -77,7 +97,7 @@ class ObjManip: # Object Manipulation
|
|||
def blank(string, downcase=True):
|
||||
import re
|
||||
regex = re.compile('[^a-zA-Z0-9\ ]')
|
||||
string = regex.sub('', string)
|
||||
string = regex.sub('', str(string))
|
||||
if downcase:
|
||||
string = string.lower()
|
||||
return ' '.join(string.split())
|
||||
|
@ -118,11 +138,18 @@ class ObjManip: # Object Manipulation
|
|||
del new_d[x]
|
||||
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
|
||||
# ========================================
|
||||
|
||||
|
||||
@staticmethods
|
||||
class DLog:
|
||||
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.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(
|
||||
name = 'irs',
|
||||
|
@ -9,14 +44,18 @@ setup(
|
|||
author_email = 'kepoorh@gmail.com',
|
||||
license = 'GPL',
|
||||
packages = ['irs'],
|
||||
install_requires=[
|
||||
install_requires = [
|
||||
'bs4',
|
||||
'mutagen',
|
||||
'youtube-dl',
|
||||
'requests',
|
||||
'spotipy',
|
||||
'ydl-binaries'
|
||||
],
|
||||
entry_points = {
|
||||
'console_scripts': ['irs = irs.cli:main'],
|
||||
},
|
||||
cmdclass = {
|
||||
'develop': PostDevelopCommand,
|
||||
'install': PostInstallCommand,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -2,4 +2,4 @@ from irs.ripper import Ripper
|
|||
|
||||
print ("[*] Testing `song.py`")
|
||||
Ripper().song("Da Frame 2R", "Arctic Monkeys")
|
||||
print ("[+] Passed!")
|
||||
print ("[+] Passed!")
|
||||
|
|
Loading…
Reference in a new issue