mirror of
https://github.com/cooperhammond/irs.git
synced 2025-01-08 20:05:27 +00:00
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:
parent
d7dc97eb68
commit
96ddf3be3f
12
.idea/irs.iml
Normal file
12
.idea/irs.iml
Normal 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
4
.idea/misc.xml
Normal 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
8
.idea/modules.xml
Normal 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
6
.idea/vcs.xml
Normal 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
144
.idea/workspace.xml
Normal 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
9
flexx-app/center.py
Normal 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
87
flexx-app/chat.py
Normal 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
51
flexx-app/circles.py
Normal 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
28
flexx-app/form.py
Normal 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
22
flexx-app/irs-app.py
Normal 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()
|
|
@ -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__":
|
||||
|
|
265
irs/manage.py
265
irs/manage.py
|
@ -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
312
irs/manager.py
Normal 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)
|
237
irs/metadata.py
237
irs/metadata.py
|
@ -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))
|
||||
|
|
34
irs/utils.py
34
irs/utils.py
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue