Rewrote it all so that it's much much more organized. Now uses spotipy to find album art and album contents.

This commit is contained in:
Kepoor Hampond 2016-12-31 16:47:45 -08:00
parent d7dc97eb68
commit 96ddf3be3f
16 changed files with 841 additions and 469 deletions

12
.idea/irs.iml Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="projectConfiguration" value="py.test" />
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>

4
.idea/misc.xml Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.5.2 (/usr/bin/python3.5)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/irs.iml" filepath="$PROJECT_DIR$/.idea/irs.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

144
.idea/workspace.xml Normal file
View file

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="584c38f8-fe62-4de0-bab7-ab5fd4513619" name="Default" comment="">
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/irs/metadata.py" afterPath="$PROJECT_DIR$/irs/metadata.py" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="CreatePatchCommitExecutor">
<option name="PATCH_PATH" value="" />
</component>
<component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
<component name="FileEditorManager">
<leaf>
<file leaf-file-name="metadata.py" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/irs/metadata.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectFrameBounds">
<option name="x" value="260" />
<option name="y" value="40" />
<option name="width" value="1400" />
<option name="height" value="1000" />
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
<manualOrder />
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="Scratches" />
<pane id="ProjectPane">
<subPane>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="irs" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="irs" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="irs" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="irs" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="irs" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
</PATH_ELEMENT>
</PATH>
</subPane>
</pane>
</panes>
</component>
<component name="PropertiesComponent">
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="584c38f8-fe62-4de0-bab7-ab5fd4513619" name="Default" comment="" />
<created>1482431898415</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1482431898415</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="260" y="40" width="1400" height="1000" extended-state="0" />
<editor active="false" />
<layout>
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Python Console" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/irs/metadata.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</component>
</project>

9
flexx-app/center.py Normal file
View file

@ -0,0 +1,9 @@
def center(lst):
length = len(lst)
center = -1
for num in range(0, length):
if not (num % 2):
center += 1
return lst[center]
print (center(center([1, 2, [1, 2, 3], 4, 5])))

87
flexx-app/chat.py Normal file
View file

@ -0,0 +1,87 @@
"""
Simple chat web app in less than 80 lines.
This app might be running at the demo server: http://flexx1.zoof.io
"""
from flexx import app, ui, event
class Relay(event.HasEvents):
""" Global object to relay messages to all participants.
"""
@event.emitter
def new_message(self, msg):
return dict(msg=msg + '<br />')
class MessageBox(ui.Label):
CSS = """
.flx-MessageBox {
overflow-y:scroll;
background: #e8e8e8;
border: 1px solid #444;
margin: 3px;
}
"""
class ChatRoom(ui.Widget):
""" Despite the name, this represents one connection to the chat room."""
def init(self):
with ui.HBox():
ui.Widget(flex=1)
with ui.VBox():
self.name = ui.LineEdit(placeholder_text='your name')
self.people = ui.Label(flex=1, base_size=(250, 0))
with ui.VBox():
self.messages = MessageBox(flex=1)
with ui.HBox():
self.message = ui.LineEdit(flex=1, placeholder_text='enter message')
self.ok = ui.Button(text='Send')
ui.Widget(flex=1)
# Pipe messages send by the relay into this app
relay.connect(self._push_info, 'new_message:' + self.id)
self._update_participants()
def _push_info(self, *events):
if self.session.status:
for ev in events:
self.emit('new_message', ev)
def _update_participants(self):
if not self.session.status:
relay.disconnect('new_message:' + self.id)
return # and dont't invoke a new call
proxies = app.manager.get_connections(self.__class__.__name__)
names = [p.app.name.text for p in proxies]
del proxies
text = '<br />%i persons in this chat:<br /><br />' % len(names)
text += '<br />'.join([name or 'anonymous' for name in sorted(names)])
self.people.text = text
app.call_later(3, self._update_participants)
@event.connect('ok.mouse_down', 'message.submit')
def _send_message(self, *events):
text = self.message.text
if text:
name = self.name.text or 'anonymous'
relay.new_message('<i>%s</i>: %s' % (name, text))
self.message.text = ''
class JS:
@event.connect('new_message')
def _update_total_text(self, *events):
self.messages.text += ''.join([ev.msg for ev in events])
# Create global relay
relay = Relay()
if __name__ == '__main__':
m = app.launch(ChatRoom) # for use during development
app.run()

51
flexx-app/circles.py Normal file
View file

@ -0,0 +1,51 @@
"""
Example that shows animated circles. The animation is run from Python.
Doing that in JS would be more efficient, but we have not implemented timers
yet.
"""
import math
from flexx import app, ui
class Circle(ui.Label):
CSS = """
.flx-Circle {
background: #f00;
border-radius: 10px;
width: 10px;
height: 10px;
}
"""
class Circles(ui.Widget):
def init(self):
self._circles = []
with ui.PinboardLayout():
for i in range(32):
x = math.sin(i*0.2)*0.3 + 0.5
y = math.cos(i*0.2)*0.3 + 0.5
w = Circle(pos=(x, y))
self._circles.append(w)
self.tick()
# todo: animate in JS!
def tick(self):
if not self.session.status:
return
import time
t = time.time()
for i, circle in enumerate(self._circles):
x = math.sin(i*0.2 + t)*0.3 + 0.5
y = math.cos(i*0.2 + t)*0.3 + 0.5
circle.pos = x, y
app.call_later(0.03, self.tick)
if __name__ == '__main__':
m = app.launch(Circles)
app.run()

28
flexx-app/form.py Normal file
View file

@ -0,0 +1,28 @@
"""
Simple example that shows two forms, one which is stretched, and one
in which we use a dummy Widget to fill up space so that the form is
more compact.
"""
from flexx import app, ui
class Form(ui.Widget):
def init(self):
with ui.BoxPanel():
with ui.FormLayout() as self.form:
self.b1 = ui.Button(title='Name:', text='Hola')
self.b2 = ui.Button(title='Age:', text='Hello world')
self.b3 = ui.Button(title='Favorite color:', text='Foo bar')
with ui.FormLayout() as self.form:
self.b4 = ui.Button(title='Name:', text='Hola')
self.b5 = ui.Button(title='Age:', text='Hello world')
self.b6 = ui.Button(title='Favorite color:', text='Foo bar')
ui.Widget(flex=1) # Add a flexer
if __name__ == '__main__':
m = app.launch(Form)
app.run()

22
flexx-app/irs-app.py Normal file
View file

@ -0,0 +1,22 @@
from flexx import app, ui, event
import os
class IRS(ui.Widget):
def init(self):
with ui.FormLayout() as self.form:
self.song = ui.LineEdit(placeholder_text="Song Name")
self.artist = ui.LineEdit(placeholder_text="Artist Name")
self.submit = ui.Button(text="Submit")
self.output = ui.Label(text="")
ui.Widget(flex=2)
"""@event.connect("submit.mouse_click", "artist.submit")
def _button_clicked(self, *events):
self.output.text = os.system('irs -a "%s" -s "%s"' % (self.artist.text, self.song.text))
"""
if __name__ == '__main__':
m = app.launch(IRS)
app.run()

View file

@ -1,5 +1,4 @@
#!/usr/bin python
HELP = \
"""
usage:
@ -22,67 +21,22 @@ Options:
-s SONG, --song SONG Specify song name of the artist.
-A ALBUM, --album ALBUM
Specify album name of the artist.
-st SEARCH_TERMS, --search-terms SEARCH_TERMS
Only use if calling -A/--album. Acts as extra search
terms when looking for the album.
-l, --choose-link If supplied, will bring up a console choice for what
link you want to download based off a list of titles.
-ng, --no-organize Only use if calling -p/--playlist. Forces all files
downloaded to be organized normally.
"""
import argparse
from os import system
# For exiting
from sys import exit
from .manage import *
# Parsing args
import argparse
# Import the manager
from .manager import Manager
from .utils import *
def console(args):
system("clear")
media = None
while type(media) is not int:
print (bc.HEADER)
print ("What type of media would you like to download?")
print ("\t1) Song")
print ("\t2) Album")
print ("\t3) Playlist")
try:
media = int(input(bc.YELLOW + bc.BOLD + ":: " + bc.ENDC))
if media not in (1, 2, 3):
raise ValueError
except ValueError:
print (bc.FAIL + "\nPlease enter a valid number." + bc.ENDC)
if media in (1, 2):
print (bc.HEADER + "Artist of song/album ", end="")
artist = input(bc.BOLD + bc.YELLOW + ": " + bc.ENDC)
if media == 1:
print (bc.HEADER + "Song you would like to download ", end="")
song = input(bc.BOLD + bc.YELLOW + ": " + bc.ENDC)
rip_mp3(song, artist, command=args.command, choose_link=args.link)
elif media == 2:
print (bc.HEADER + "Album you would like to download ", end="")
album = input(bc.BOLD + bc.YELLOW + ": " + bc.ENDC)
rip_album(album, artist, command=args.command, choose_link=args.link)
elif media == 3:
print (bc.HEADER + "Playlist file name ", end="")
playlist = input(bc.BOLD + bc.YELLOW + ": " + bc.ENDC)
organize = ""
while organize not in ("y", "n", "yes", "no", ""):
print (bc.HEADER + "Would you like to place all songs into a single folder? (Y/n)", end="")
organize = input(bc.BOLD + bc.YELLOW + ": " + bc.ENDC).lower()
if organize in ("y", "yes", ""):
rip_playlist(playlist, command=args.command, choose_link=args.link, \
no_organize=True)
elif organize in ("n", "no"):
rip_playlist(playlist, command=args.command, choose_link=args.link)
def main():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-h', '--help', action='store_true', dest='help')
@ -95,15 +49,13 @@ def main():
parser.add_argument('-p', '--playlist', dest="playlist", \
help="Specify playlist filename. Each line should be formatted like so: SONGNAME - ARTIST")
parser.add_argument('-ng', '--no-organize', action="store_false", dest="no_organize", \
parser.add_argument('-ng', '--no-organize', action="store_false", dest="organize", \
help="Only use if calling -p/--playlist. Forces all files downloaded to be organizes normally.")
media = parser.add_mutually_exclusive_group()
media.add_argument('-s', '--song', dest="song", help="Specify song name of the artist.")
media.add_argument('-A', '--album', dest="album", help="Specify album name of the artist.")
parser.add_argument('-st', '--search-terms', dest="search_terms", \
help="Only use if calling -A/--album. Acts as extra search terms for the album.")
parser.add_argument('-o', '--order-files', action='store_true', dest="order_files",\
help="Only use if callign with -p/--playlist or -A/--album. Adds a digit to front of each file specifying order.")
@ -111,9 +63,15 @@ def main():
args = parser.parse_args()
if args.organize == None:
args.organize = True
manager = Manager(args)
if args.help:
global HELP
print (HELP)
exit(1)
elif args.version:
import pkg_resources
@ -121,32 +79,31 @@ def main():
print ("Homepage: " + color("https://github.com/kepoorhampond/irs", ["OKGREEN"]))
print ("License: " + color("The GNU", ["YELLOW"]) + " (http://www.gnu.org/licenses/gpl.html)")
print ("Version: " + pkg_resources.get_distribution("irs").version)
print ("\n")
exit(0)
elif not args.album and args.search_terms:
parser.error("error: must supply -A/--album if you are going to supply -st/--search-terms")
elif not args.organize and not args.playlist:
parser.error("error: must supply -p/--playlist if specifying -ng/--no-organize")
exit(1)
elif args.artist and not (args.album or args.song):
print ("error: must specify -A/--album or -s/--song if specifying -a/--artist")
parser.error("error: must supply -A/--album or -s/--song if specifying -a/--artist")
exit(1)
elif not args.artist and not args.playlist:
console(args)
manager.console()
elif args.playlist:
rip_playlist(args.playlist, args.command, choose_link=args.link, no_organize=args.no_organize)
manager.rip_playlist()
elif args.artist:
if args.album:
rip_album(args.album, args.artist, command=args.command, \
search=args.search_terms, choose_link=args.link)
manager.rip_album()
elif args.song:
rip_mp3(args.song, args.artist, command=args.command, choose_link=args.link)
manager.rip_mp3()
if __name__ == "__main__":

View file

@ -1,265 +0,0 @@
# Powered by:
import youtube_dl
# Info getting
from urllib.request import urlopen
from urllib.parse import urlencode
# Info parsing
from re import findall
import os, json
from bs4 import BeautifulSoup
# Local utils
from .utils import *
from .metadata import *
def find_mp3(song, artist,
choose_link=False, # Whether to allow the user to choose the link.
):
os.system("clear")
print (color(song, ["BOLD", "UNDERLINE"]) + ' by ' + color(artist, ["BOLD", "UNDERLINE"]))
search_terms = song + " " + artist + " lyrics"
query_string = urlencode({"search_query" : (search_terms)})
html_content = urlopen("http://www.youtube.com/results?" + query_string)
search_results = findall(r'href=\"\/watch\?v=(.{11})', html_content.read().decode())
in_title = False
i = -1
given_up_score = 0
if not choose_link:
print (bc.YELLOW + "\nFinding youtube link ...", end="\r")
while in_title == False:
i += 1
given_up_score += 1
if given_up_score >= 10:
in_title = True
audio_url = ("http://www.youtube.com/watch?v=" + search_results[i])
title = strip_special_chars((BeautifulSoup(urlopen(audio_url), 'html.parser')).title.string.lower())
song_title = song.lower().split("/")
for song in song_title:
song = strip_special_chars(song)
if song in title and "full album" not in title:
in_title = True
print (bc.OKGREEN + "Found youtube link! \n" + bc.ENDC)
else:
results = []
print (bc.YELLOW + "Finding links ... " + bc.ENDC, end="\r")
for key in search_results[:10]:
results.append(BeautifulSoup(urlopen(("http://www.youtube.com/watch?v="\
+ key)), 'html.parser').title.string.replace(" - YouTube" , ""))
valid_choice = False
while valid_choice == False:
print (bc.HEADER + "What song would you like to download?")
index = 0
for result in results:
index += 1
print (" %s) %s" % (index, result))
i = int(input(bc.YELLOW + bc.BOLD + ":: " + bc.ENDC))
if i in tuple(range(1, 11)):
i -= 1
valid_choice = True
return search_results[i]
def rip_playlist(file_name,
command=None, # Whether to run a special user-supplied command.
choose_link=False, # Whether to allow the user to choose the link.
no_organize=True, # Whether to organize the file downloaded.
):
try:
file = open(file_name, 'r')
except Exception:
print (file_name + bc.FAIL + " could not be found." + bc.ENDC)
exit(1)
errors = []
song_number = 0
for line in file:
if line.strip() == "":
pass
try:
arr = line.strip("\n").split(" - ")
song = arr[0]
artist = arr[1]
if os.path.isdir(artist):
remove = False
else:
remove = True
location = rip_mp3(song, artist, command=command)
song_number += 1
locations = location.split("/")
# Enter... the reorganizing...
if no_organize:
folder_name = ("playlist - " + file_name)[:40]
if not os.path.isdir(folder_name):
os.makedirs(folder_name)
os.rename(location, "%s/%s - %s" % (folder_name, song_number, locations[-1]))
if remove:
import shutil # Only import this if I have to.
shutil.rmtree(locations[0])
if os.path.isfile(filename):
os.rename(filename, folder_name + "/" + filename)
os.rename(folder_name, folder_name.replace("playlist"))
except Exception as e:
errors.append(line + color(" : ", ["YELLOW"]) + bc.FAIL + str(e) + bc.ENDC)
if len(errors) > 0:
print (bc.FAIL + "Something was wrong with the formatting of the following lines:" + bc.ENDC)
for i in errors:
print ("\t%s" % i)
def rip_album(album, artist,
tried=False, # for if it can't find the album the first time
search="album", # ditto
command=None, # For running a command with the song's location
choose_link=False # Whether to allow the user to choose the link.
):
if search in (None, False):
search = "album"
visible_texts = search_google(album, artist, search)
errors = []
try:
songs = []
num = True
for i, j in enumerate(visible_texts):
if 'Songs' in j:
if visible_texts[i + 1] == "1":
indexed = i
while num == True:
try:
if type(int(visible_texts[indexed])) is int:
a = visible_texts[indexed + 1]
songs.append(a)
indexed += 1
except:
indexed += 1
if indexed >= 1000:
num = False
else:
pass
print ("")
print (bc.HEADER + "Album Contents:" + bc.ENDC)
for i, j in enumerate(songs):
print (bc.OKBLUE + " - " + j + bc.ENDC)
print (bc.YELLOW + "\nFinding album cover ... " + bc.ENDC, end="\r")
album_art_url = get_albumart_url(album, artist)
print (bc.OKGREEN + "Album cover found: " + bc.ENDC + album_art_url)
for i, j in enumerate(songs):
song = j
print (color("\n%s/%s - " % (i + 1, len(songs)), ["UNDERLINE"]), end="")
rip_mp3(j, artist, part_of_album=True, album=album, tracknum=i + 1, \
album_art_url=album_art_url, command=command, choose_link=choose_link)
if len(errors) > 0:
for error in errors: print (error)
else:
print (bc.BOLD + bc.UNDERLINE + album + bc.ENDC + bc.OKGREEN + " downloaded successfully!\n")
except Exception as e:
if str(e) == "local variable 'indexed' referenced before assignment" or str(e) == 'list index out of range':
if tried != True:
print (bc.OKBLUE + "Trying to find album ..." + bc.ENDC)
rip_album(album, artist, tried=True, search="", choose_link=choose_link)
else:
print (bc.FAIL + 'Could not find album "%s"' % album + bc.ENDC)
else:
errors.append(bc.FAIL + "There was a problem with downloading: " + bc.ENDC + song + "\n" + str(e))
pass
def rip_mp3(song, artist,
part_of_album=False, # neccessary for creating folders.
album=None, # if you want to specify an album and save a bit of time.
tracknum=None, # to specify the tracknumber in the album.
album_art_url=None, # if you want to save a lot of time trying to find album cover.
command=None, # For running a command with the song's location.
choose_link=False, # Whether to allow the user to choose the link.
):
audio_code = find_mp3(song, artist)
filename = strip_special_chars(song) + ".mp3"
ydl_opts = {
'format': 'bestaudio/best',
#'quiet': True,
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
}],
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
ydl.download(["http://www.youtube.com/watch?v=" + audio_code])
artist_folder = artist
if not os.path.isdir(artist_folder):
os.makedirs(artist_folder)
if not part_of_album:
location = artist_folder
if album and part_of_album:
album_folder = artist + "/" + album
if not os.path.isdir(album_folder):
os.makedirs(album_folder)
location = album_folder
for file in os.listdir("."):
if audio_code in file:
os.rename(file, location + "/" + filename)
parse_metadata(song, artist, location, filename, tracknum=tracknum, album=album, album_art_url=album_art_url)
print (color(song, ["BOLD", "UNDERLINE"]) + bc.OKGREEN + ' downloaded successfully!'+ bc.ENDC)
print ("")
if command:
loc = location + "/" + filename
os.system((command.replace("%(loc)s", '"%s"' % loc) + " &"))
return (location + "/" + filename)

312
irs/manager.py Normal file
View file

@ -0,0 +1,312 @@
# Powered by:
import youtube_dl
import spotipy
# Info getting
from urllib.request import urlopen
from urllib.parse import urlencode
# Info parsing
from re import findall
import os, json
from bs4 import BeautifulSoup
# Local utils
from .utils import *
from .metadata import *
class Manager:
def __init__(self, args):
self.args = args
def console(self):
args = self.args
os.system("clear")
media = None
while type(media) is not int:
print (bc.HEADER)
print ("What type of media would you like to download?")
print ("\t1) Song")
print ("\t2) Album")
print ("\t3) Playlist")
try:
media = int(input(bc.YELLOW + bc.BOLD + ":: " + bc.ENDC))
if media not in (1, 2, 3):
raise ValueError
except ValueError:
print (bc.FAIL + "\nPlease enter a valid number." + bc.ENDC)
if media in (1, 2):
self.args.artist = color_input("Artist of song/album")
if media == 1:
self.args.song = color_input("Song you would like to download")
self.rip_mp3()
elif media == 2:
self.args.album = color_input("Album you would like to download")
self.rip_album()
elif media == 3:
self.args.playlist = color_input("Playlist file name")
organize = ""
while organize not in ("y", "n", "yes", "no", ""):
print (bc.HEADER + "Would you like to place all songs into a single folder? (Y/n)", end="")
organize = input(bc.BOLD + bc.YELLOW + ": " + bc.ENDC).lower()
if organize in ("y", "yes", ""):
self.args.organize = True
elif organize in ("n", "no"):
self.args.organize = False
self.rip_playlist()
def find_mp3(self, song=None, artist=None):
if not song:
song = self.args.song
if not artist:
artist = self.args.artist
os.system("clear")
print (color(song, ["BOLD", "UNDERLINE"]) + ' by ' + color(artist, ["BOLD", "UNDERLINE"]))
search_terms = song + " " + artist + " lyrics"
query_string = urlencode({"search_query" : (search_terms)})
html_content = urlopen("http://www.youtube.com/results?" + query_string)
search_results = findall(r'href=\"\/watch\?v=(.{11})', html_content.read().decode())
in_title = False
i = -1
given_up_score = 0
if not self.args.link:
print (bc.YELLOW + "\nFinding youtube link ...", end="\r")
while in_title == False:
i += 1
given_up_score += 1
if given_up_score >= 10:
in_title = True
audio_url = ("http://www.youtube.com/watch?v=" + search_results[i])
title = strip_special_chars((BeautifulSoup(urlopen(audio_url), 'html.parser')).title.string.lower())
song_title = song.lower().split("/")
for song in song_title:
song = strip_special_chars(song)
if song in title and "full album" not in title:
in_title = True
print (bc.OKGREEN + "Found youtube link! \n" + bc.ENDC)
else:
results = []
print (bc.YELLOW + "\nFinding links ... " + bc.ENDC, end="\r")
for key in search_results[:10]:
results.append(BeautifulSoup(urlopen(("http://www.youtube.com/watch?v="\
+ key)), 'html.parser').title.string.replace(" - YouTube" , ""))
valid_choice = False
while valid_choice == False:
print (bc.HEADER + "What song would you like to download?")
index = 0
for result in results:
index += 1
print (" %s) %s" % (index, result))
i = int(input(bc.YELLOW + bc.BOLD + ":: " + bc.ENDC))
if i in tuple(range(1, 11)):
i -= 1
valid_choice = True
return search_results[i]
def rip_playlist(self):
file_name = self.args.playlist
organize = self.args.organize
try:
file = open(file_name, 'r')
except Exception:
print (file_name + bc.FAIL + " could not be found." + bc.ENDC)
exit(1)
errors = []
song_number = 0
for line in file:
if line.strip() == "":
pass
#try:
arr = line.strip("\n").split(" - ")
self.args.song = arr[0]
self.args.artist = arr[1]
if os.path.isdir(self.args.artist):
remove = False
else:
remove = True
location = self.rip_mp3()
locations = location.split("/")
song_number += 1
# Enter... the reorganizing...
if organize:
folder_name = ("playlist - " + file_name)[:40]
if not os.path.isdir(folder_name):
os.makedirs(folder_name)
os.rename(location, "%s/%s - %s" % (folder_name, song_number, locations[-1]))
if remove:
import shutil # Only import this if I have to.
shutil.rmtree(locations[0])
if organize:
os.rename(file_name, folder_name + "/" + file_name)
os.rename(folder_name, folder_name.replace("playlist - ", ""))
#except Exception as e:
# errors.append(line + color(" : ", ["YELLOW"]) + bc.FAIL + str(e) + bc.ENDC)
if len(errors) > 0:
print (bc.FAIL + "Something was wrong with the formatting of the following lines:" + bc.ENDC)
for i in errors:
print ("\t%s" % i)
def get_album_contents(self, search):
spotify = spotipy.Spotify()
results = spotify.search(q=search, type='album')
items = results['albums']['items']
if len(items) > 0:
album = items[0]
album_id = (album['uri'])
contents = spotify.album_tracks(album_id)["items"]
contents = contents[0:-1]
names = []
for song in contents:
names.append(song["name"])
return names
def get_album_art(self, artist, album):
spotify = spotipy.Spotify()
results = spotify.search(q="album:" + album, type='album')
items = results['albums']['items']
if len(items) > 0:
album = items[0]['images'][0]['url']
return album
def rip_album(self):
search = self.args.artist + " " + self.args.album
songs = self.get_album_contents(search)
print ("")
print (bc.HEADER + "Album Contents:" + bc.ENDC)
for song in songs:
print (bc.OKBLUE + " - " + song + bc.ENDC)
print (bc.YELLOW + "\nFinding album cover ... " + bc.ENDC, end="\r")
album_art_url = self.get_album_art(self.args.artist, self.args.album)
print (bc.OKGREEN + "Album cover found: " + bc.ENDC + album_art_url)
for track_number, song in enumerate(songs):
print (color("\n%s/%s - " % (track_number + 1, len(songs)), ["UNDERLINE"]), end="")
self.rip_mp3(song, album=self.args.album, tracknum=track_number + 1, album_art_url=album_art_url)
else:
print (bc.BOLD + bc.UNDERLINE + self.args.album + bc.ENDC + bc.OKGREEN + " downloaded successfully!\n")
def rip_mp3(self, song=None, artist=None,
album=None, # if you want to specify an album and save a bit of time.
tracknum=None, # to specify the tracknumber in the album.
album_art_url=None, # if you want to save a lot of time trying to find album cover.
):
if not song:
song = self.args.song
if not artist:
artist = self.args.artist
audio_code = self.find_mp3(song=song, artist=artist)
filename = strip_special_chars(song) + ".mp3"
ydl_opts = {
'format': 'bestaudio/best',
#'quiet': True,
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
}],
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
ydl.download(["http://www.youtube.com/watch?v=" + audio_code])
artist_folder = artist
if not os.path.isdir(artist_folder):
os.makedirs(artist_folder)
if album:
album_folder = artist + "/" + album
if not os.path.isdir(album_folder):
os.makedirs(album_folder)
location = album_folder
elif not album:
location = artist_folder
for file in os.listdir("."):
if audio_code in file:
os.rename(file, location + "/" + filename)
# Metadata
mp3 = Metadata(location + "/" + filename, song, artist)
mp3.add_title()
exclaim_good("Title added: ", song)
mp3.add_artist()
exclaim_good("Artist added: ", artist)
test_goodness(mp3.add_album(album), "Album", "album", mp3)
test_goodness(mp3.add_release_date(), "Release Date", "date", mp3)
if tracknum:
mp3.add_track_number(tracknum)
image_url = mp3.add_album_art(self.get_album_art(artist, mp3.get_attr('album')))
exclaim_good("Album art added: ", image_url)
print (color(song, ["BOLD", "UNDERLINE"]) + bc.OKGREEN + ' downloaded successfully!'+ bc.ENDC)
print ("")
if self.args.command:
loc = location + "/" + filename
os.system((self.args.command.replace("%(loc)s", '"%s"' % loc) + " &"))
return (location + "/" + filename)

View file

@ -3,7 +3,7 @@ from mutagen.mp3 import MP3, EasyMP3
from mutagen.easyid3 import EasyID3
from mutagen.id3 import ID3, APIC
# Info getting
# Info finding
from urllib.parse import quote_plus, quote
from urllib.request import urlopen, Request
@ -15,152 +15,115 @@ from bs4 import BeautifulSoup
# Local utils
from .utils import *
def search_google(song, artist, search_terms=""):
# Powered by...
import spotipy
def visible(element):
if element.parent.name in ['style', 'script', '[document]', 'head', 'title']:
return False
elif match('<!--.*-->', str(element)):
return False
return True
class Metadata:
def __init__(self, location, song, artist):
self.spotify = spotipy.Spotify()
string = "%s %s %s" % (song, artist, search_terms)
filename = 'http://www.google.com/search?q=' + quote_plus(string)
hdr = {
'User-Agent':'Mozilla/5.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
self.song = song
self.artist = artist
self.location = location
texts = BeautifulSoup(urlopen(Request(filename, \
headers=hdr)).read(), 'html.parser').findAll(text=True)
self.info = self.search_google()
self.mp3 = MP3(self.location, ID3=EasyID3)
return list(filter(visible, texts))
def parse_metadata(song, artist, location, filename,
tracknum="",
album="",
album_art_url=""
):
googled = search_google(song, artist)
mp3file = MP3("%s/%s" % (location, filename), ID3=EasyID3)
# Song title
mp3file['title'] = song
mp3file.save()
print("")
print (bc.OKGREEN + "Title parsed: " + bc.ENDC + mp3file['title'][0])
# Artist
mp3file['artist'] = artist
mp3file.save()
print (bc.OKGREEN + "Artist parsed: " + bc.ENDC + mp3file['artist'][0])
# Album
try:
if not album:
for i, j in enumerate(googled):
if "Album:" in j:
album = (googled[i + 1])
except Exception as e:
album = None
if album:
mp3file['album'] = album
print (bc.OKGREEN + "Album parsed: " + bc.ENDC + mp3file['album'][0])
else:
print (bc.FAIL + "Album not parsed.")
mp3file.save()
# Release date
for i, j in enumerate(googled):
if "Released:" in j:
date = (googled[i + 1])
try:
mp3file['date'] = date
print (bc.OKGREEN + "Release date parsed: " + bc.ENDC + mp3file['date'][0])
except Exception:
mp3file['date'] = ""
pass
mp3file.save()
# Track number
if tracknum:
mp3file['tracknumber'] = str(tracknum)
mp3file.save()
# Album art
try:
if album:
if not album_art_url:
print (bc.YELLOW + "Parsing album art ..." + bc.ENDC, end="\r")
temp_url = get_albumart_url(album, artist)
embed_mp3(temp_url, location + "/" + filename)
print (bc.OKGREEN + "Album art parsed: " + bc.ENDC + temp_url)
else: # If part of an album, it should do this.
embed_mp3(album_art_url, location + "/" + filename)
print (bc.OKGREEN + "Album art parsed." + bc.ENDC)
except Exception as e:
print (bc.FAIL + "Album art not parsed: " + bc.ENDC + str(e))
def embed_mp3(albumart_url, song_location):
image = urlopen(albumart_url)
audio = EasyMP3(song_location, ID3=ID3)
try:
audio.add_tags()
except Exception as e:
pass
audio.tags.add(
APIC(
encoding = 3,
mime = 'image/png',
type = 3,
desc = 'Cover',
data = image.read()
)
)
audio.save()
def get_albumart_url(album, artist):
def test_404(url):
def get_attr(self, attr):
try:
urlopen(albumart).read()
return self.mp3[attr][0]
except Exception:
return False
def add_title(self):
self.mp3['title'] = self.song
self.mp3.save()
return True
tries = 0
album = "%s %s Album Art" % (artist, album)
url = ("https://www.google.com/search?q=" + quote(album.encode('utf-8')) + "&source=lnms&tbm=isch")
header = {
'User-Agent':
'''
Mozilla/5.0 (Windows NT 6.1; WOW64)
AppleWebKit/537.36 (KHTML,like Gecko)
Chrome/43.0.2357.134 Safari/537.36
'''
}
soup = BeautifulSoup(urlopen(Request(url, headers=header)), "html.parser")
def add_artist(self):
self.mp3['artist'] = self.artist
self.mp3.save()
return True
albumart_divs = soup.findAll("div", {"class": "rg_meta"})
albumart = json.loads(albumart_divs[tries].text)["ou"]
while not test_404(albumart):
tries += 1
albumart = json.loads(albumart_divs[tries].text)["ou"]
def add_album(self, album=None):
try:
if not album:
for i, j in enumerate(self.info):
if "Album:" in j:
album = (self.info[i + 1])
return albumart
self.mp3['album'] = album
self.mp3.save()
return True
except Exception:
self.mp3['album'] = self.song
self.mp3.save()
return False
def add_release_date(self, release_date=None):
try:
if not release_date:
for i, j in enumerate(self.info):
if "Released:" in j:
date = (self.info[i + 1])
self.mp3['date'] = date
self.mp3.save()
return release_date
except UnboundLocalError:
return False
def add_track_number(self, track_number):
self.mp3['tracknumber'] = str(track_number)
self.mp3.save()
return True
def add_album_art(self, image_url):
mp3 = EasyMP3(self.location, ID3=ID3)
try:
mp3.add_tags()
except Exception as e:
pass
if not image_url:
image_url = self.get_albumart_url(album)
mp3.tags.add(
APIC(
encoding = 3,
mime = 'image/png',
type = 3,
desc = 'cover',
data = urlopen(image_url).read()
)
)
mp3.save()
return image_url
def search_google(self, search_terms=""):
def visible(element):
if element.parent.name in ['style', 'script', '[document]', 'head', 'title']:
return False
elif match('<!--.*-->', str(element)):
return False
return True
search_terms = "%s %s %s" % (self.song, self.artist, search_terms)
url = 'http://www.google.com/search?q=' + quote_plus(search_terms)
hdr = {
'User-Agent':'Mozilla/5.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
texts = BeautifulSoup(urlopen(Request(url, \
headers=hdr)).read(), 'html.parser').findAll(text=True)
return list(filter(visible, texts))

View file

@ -53,3 +53,37 @@ def color(text, colors=[]):
color_string += "bc.%s + " % color
color_string = color_string[:-2]
return (bc.ENDC + eval(color_string) + text + bc.ENDC)
def color_input(text):
print (bc.HEADER + text, end=" ")
return input(bc.BOLD + bc.YELLOW + ": " + bc.ENDC)
def exclaim_good(text, item):
print (bc.OKGREEN + text + bc.ENDC + item)
def test_goodness(test, word, metadata_id, mp3):
if test:
exclaim_good(word + " added: ", mp3.get_attr(metadata_id))
else:
print (bc.FAIL + word + " not added." + bc.ENDC)
def search_google(self, search_terms=""):
def visible(element):
if element.parent.name in ['style', 'script', '[document]', 'head', 'title']:
return False
elif match('<!--.*-->', str(element)):
return False
return True
search_terms = "%s %s %s" % (self.song, self.artist, search_terms)
url = 'http://www.google.com/search?q=' + quote_plus(search_terms)
hdr = {
'User-Agent':'Mozilla/5.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
texts = BeautifulSoup(urlopen(Request(url, \
headers=hdr)).read(), 'html.parser').findAll(text=True)
return list(filter(visible, texts))

View file

@ -2,7 +2,7 @@ from setuptools import setup
setup(
name='irs',
version='1.6.12',
version='2.6.12',
description='A music downloader that just gets metadata.',
url='https://github.com/kepoorhampond/irs',
author='Kepoor Hampond',