├── LICENSE ├── README.md ├── requirements.txt └── steeb ├── .gitignore ├── __init__.py ├── helpers ├── __init__.py ├── musicbrainz.py └── pleer.py ├── preference.py ├── steeb.py └── util └── comparator.py /LICENSE: -------------------------------------------------------------------------------- 1 | this is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #steeb 2 | steeb is a way to download music based on the search of an album and artist 3 | 4 | **Demo:** 5 | [Click here for a demo gif](http://i.imgur.com/bulRcP1.gif) 6 | 7 | 8 | **How does it work?** 9 | Steeb searches the musicbrainz library for the tracklists of your chosen artist and thereafter it does single queries to third party music services to get the mp3 files you want. Awesome, right? 10 | 11 | TODO: 12 | - [x] Make a search on musicbrainz 13 | - [x] Get all the tracks of an album 14 | - [x] Loop through the tracks and search on various online music sources for it 15 | - [x] Set download locations 16 | - [x] Download all the tracks one by one 17 | - [ ] Make progress bar work 18 | - [ ] Reorganize code 19 | - [ ] By pleer search make sure length match by song for better matches. 20 | - [ ] Force 320kbs option 21 | - [ ] Run beets on the downloaded tracks to format them immediately 22 | - [ ] Make an exe for windows 23 | - [ ] Making a settings window 24 | 25 | Optional: 26 | - [ ] Migrate to TkInter 27 | 28 | **Build:** 29 | Make sure you have `wxpython` installed and you're using `python2` and `python2-pip` OR python2.7 30 | 31 | ```bash 32 | # get requirements 33 | $ pip install -r requirements.txt 34 | 35 | # run with steeb with: 36 | $ python2 steeb/steeb.pyw 37 | $ python2.7 steeb/steeb.pyw 38 | $ python2.8 steeb/steeb.pyw 39 | ``` 40 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | args==0.1.0 2 | musicbrainzngs 3 | requests==2.4.3 4 | clint 5 | gui2py 6 | -------------------------------------------------------------------------------- /steeb/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | -------------------------------------------------------------------------------- /steeb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeizerDev/steeb/636daa632f0114a42c5e6cb6a117c420eecdd72a/steeb/__init__.py -------------------------------------------------------------------------------- /steeb/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeizerDev/steeb/636daa632f0114a42c5e6cb6a117c420eecdd72a/steeb/helpers/__init__.py -------------------------------------------------------------------------------- /steeb/helpers/musicbrainz.py: -------------------------------------------------------------------------------- 1 | # Do the search stuff here -------------------------------------------------------------------------------- /steeb/helpers/pleer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | import requests 4 | 5 | class Pleer: 6 | 7 | def search(self, keywords, track): 8 | pleer_qry = requests.get("http://pleer.com/browser-extension/search?q=%s" % keywords) 9 | pleer_tracks = pleer_qry.json()['tracks'] 10 | 11 | print(pleer_tracks) 12 | if len(pleer_tracks) > 0: 13 | # Create something like a magically selection algorithm 14 | return [track["number"], track["recording"]["title"], "✓".decode('utf-8'), pleer_tracks[0]["id"]] 15 | else: 16 | # TODO: Do another search for the track 17 | return [track["number"], track["recording"]["title"], "×".decode('utf-8'), ""] 18 | -------------------------------------------------------------------------------- /steeb/preference.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | download_dir = os.path.join(os.path.expanduser("~"), os.path.join("Downloads", "Music")) 4 | force_hq = False #TODO: implement 5 | -------------------------------------------------------------------------------- /steeb/steeb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | 4 | from __future__ import with_statement # for python 2.5 compatibility 5 | 6 | __author__ = "KeizerDev (robertjankeizer@gmail.com)" 7 | __copyright__ = "Copyright (C) 2015- KeizerDev" 8 | __license__ = "LGPL 3.0" 9 | 10 | import gui, requests, sys, os, argparse, time, urllib2 11 | import preference as pref 12 | import musicbrainzngs as m 13 | import helpers.pleer as pleer 14 | from clint.textui import colored, puts, progress, indent 15 | 16 | # --- here goes your event handlers --- 17 | def search_artist(evt): 18 | result = m.search_artists(artist=mainwin['searchfield'].value) 19 | resultList = [] 20 | print("steeb found %s artists" % colored.cyan(len(result["artist-list"]))) 21 | for idx, artist in enumerate(result["artist-list"]): 22 | resultList.append([artist["name"], artist["id"]]) 23 | 24 | lv = mainwin['artistslist'] 25 | lv.items = resultList 26 | 27 | 28 | def get_albums(evt): 29 | result = m.get_artist_by_id(evt.target.get_selected_items()[0]['id'], includes=["release-groups"], release_type=["album", "ep"]) 30 | resultList = [] 31 | print(result) 32 | for idx, album in enumerate(result["artist"]["release-group-list"]): 33 | resultList.append([album["title"], album["id"]]) 34 | 35 | lv = mainwin['albumslist'] 36 | lv.items = resultList 37 | 38 | 39 | def get_tracks(evt): 40 | albumslist = m.get_release_group_by_id(evt.target.get_selected_items()[0]['id'], includes="releases") 41 | resultList = [] 42 | 43 | print("=========================") 44 | print("=========================") 45 | print(albumslist) 46 | print("=========================") 47 | print("=========================") 48 | tracks = m.get_release_by_id(albumslist['release-group']['release-list'][0]['id'], includes=["artists", "recordings"]) 49 | 50 | album_id = tracks["release"]["id"] 51 | for idx, track in enumerate(tracks["release"]["medium-list"][0]["track-list"]): 52 | resultList.append([track["recording"]["title"], album_id]) 53 | 54 | lv = mainwin['trackslist'] 55 | lv.items = resultList 56 | 57 | 58 | def clickevt_album(evt): 59 | window_name = mainwin['artistslist'].get_selected_items()[0]["artist"] + " - " + mainwin['albumslist'].get_selected_items()[0]["albums"]; 60 | 61 | with gui.Window(name='downwin', title=u'' + window_name, height=down_win_height, width=down_win_width, left='323', top='137', bgcolor=u'#F0F0F0', fgcolor=u'#555555', ): 62 | gui.TextBox(name='downloadpath', value=pref.download_dir, height=form_height, left='5', top='0', width=down_input_width, parent='downwin', ) 63 | gui.Button(label=u'Download all!', name='btn_down_all', height='35px', width=down_btn_width, left=down_input_width, top='5', default=True, fgcolor=u'#EEEEEE', bgcolor=u'#C0392B', parent='downwin', ) 64 | gui.Button(label=u'Download selected!', name='button_down', height='35px', width=down_btn_width, left=down_btn_left, top='5', default=True, fgcolor=u'#EEEEEE', bgcolor=u'#C0392B', parent='downwin', ) 65 | with gui.ListView(name='downloadlist', height=down_lv_songs_height, width=down_win_width, left='0', top=form_height, item_count=10, sort_column=0, onitemselected="print ('sel %s' % event.target.get_selected_items())", ): 66 | gui.ListColumn(name='trackposition', text='Nr.', width=50) 67 | gui.ListColumn(name='tracks', text='Tracks', width=300) 68 | gui.ListColumn(name='tracksfound', text='Tracks found', width=150) 69 | gui.ListColumn(name='id', text='', width=0) 70 | gui.Gauge(name='progressbar', height=down_gauge_height, left=0, top=down_gauge_top, width=down_win_width, value=0, ) 71 | 72 | downwin = gui.get("downwin") 73 | downwin['btn_down_all'].onclick = download_all_songs 74 | plr = pleer.Pleer() 75 | # print(pleer.Pleer()) 76 | 77 | 78 | tracksList = [] 79 | (oldtracks_position, oldtracks_json) = mainwin["trackslist"].items()[0] 80 | 81 | tracks = m.get_release_by_id(oldtracks_json["id"], includes=["artists", "recordings"]) 82 | print(tracks) 83 | for idx, track in enumerate(tracks["release"]["medium-list"][0]["track-list"]): 84 | print(idx) 85 | 86 | tracksList.append(plr.search(mainwin['artistslist'].get_selected_items()[0]["artist"] + " " + track["recording"]["title"], track)) 87 | 88 | lv = downwin["downloadlist"] 89 | lv.items = tracksList 90 | 91 | 92 | def download_all_songs(self): 93 | downwin = gui.get("downwin") 94 | for track in downwin["downloadlist"].items: 95 | if (track["id"] != ""): 96 | song_url = "http://pleer.com/browser-extension/files/%s.mp3" % track["id"] 97 | song_title = "%s - %s" % (mainwin['artistslist'].get_selected_items()[0]["artist"], track["tracks"]) 98 | download(downwin, song_url, song_title) 99 | 100 | def download(downwin, fileurl, file_name): 101 | downwin['progressbar'].value = 30 102 | 103 | u = urllib2.urlopen(fileurl) 104 | if not os.path.exists(pref.download_dir): 105 | os.makedirs(pref.download_dir) 106 | 107 | if not os.path.exists(pref.download_dir + "/" + file_name + ".mp3"): 108 | 109 | f = open(os.path.join(pref.download_dir, file_name + ".mp3"), 'wb') 110 | meta = u.info() 111 | file_size = int(meta.getheaders("Content-Length")[0]) 112 | 113 | print "Downloading: %s Bytes: %s" % (file_name, file_size) 114 | 115 | file_size_dl = 0 116 | block_sz = 8192 117 | while True: 118 | buffer = u.read(block_sz) 119 | if not buffer: 120 | break 121 | 122 | file_size_dl += len(buffer) 123 | f.write(buffer) 124 | status = r"%10d [%3.2f%%]" % (file_size_dl, file_size_dl * 100. / file_size) 125 | downwin['progressbar'].value = int(round(file_size_dl * 100. / file_size)) 126 | # downwin["progressbar"].value = int(round(file_size_dl * 100. / file_size)) 127 | status = status + chr(8)*(len(status)+1) 128 | print status, 129 | 130 | f.close() 131 | else: 132 | print ("File " + file_name + " already downloaded") 133 | 134 | def load(evt): 135 | m.set_useragent("steeb", "0.1", "KeizerDev@github.com") 136 | mainwin['button_search'].onclick = search_artist 137 | mainwin['artistslist'].onitemselected = get_albums 138 | mainwin['albumslist'].onitemselected = get_tracks 139 | mainwin['button_down'].onclick = clickevt_album 140 | lv = mainwin['artistslist'] 141 | 142 | # Layout styles 143 | #downwin 144 | down_win_height = '400px' 145 | down_win_width = '500px' 146 | 147 | down_input_width = '260px' 148 | down_btn_width = '120px' 149 | down_btn_left = '380px' 150 | 151 | down_lv_songs_height = '335px' 152 | down_gauge_top = '380px' 153 | down_gauge_height = '20px' 154 | 155 | #mainwin 156 | main_win_height = '500px' 157 | main_win_width = '600px' 158 | 159 | main_search_width = '400px' 160 | main_songs_width = '200px' 161 | main_input_width = '320px' 162 | main_btn_width = '60px' 163 | 164 | lv_artist_height = '235px' 165 | lv_albums_height = '220px' 166 | lv_songs_height = '455px' 167 | lv_artist_top = '280px' 168 | 169 | #global 170 | btn_download_width = '100px' 171 | form_height = '45px' 172 | 173 | 174 | with gui.Window(name='mainwin', title=u'Steeb', height=main_win_height, width=main_win_width, left='323', top='137', bgcolor=u'#F0F0F0', fgcolor=u'#555555', image='', ): 175 | gui.TextBox(name='searchfield', height=form_height, left='5', top='0', width=main_input_width, parent='mainwin', ) 176 | gui.Button(label=u'Crawl songs!', name='button_down', height='35px', width=btn_download_width, left=main_search_width, top='5', default=True, fgcolor=u'#EEEEEE', bgcolor=u'#C0392B', parent='mainwin', ) 177 | gui.Button(label=u'Search!', name='button_search', height='35px', width=main_btn_width, left='333px', top='5', default=True, fgcolor=u'#EEEEEE', bgcolor=u'#C0392B', parent='mainwin', ) 178 | with gui.ListView(name='artistslist', height=lv_artist_height, left='0', top=form_height, width=main_search_width, item_count=10, sort_column=0, onitemselected="print ('sel %s' % event.target.get_selected_items())", ): 179 | gui.ListColumn(name='artist', text='Artist', width=400) 180 | gui.ListColumn(name='id', text='Id', width=0) 181 | with gui.ListView(name='albumslist', height=lv_albums_height, left='0', top=lv_artist_top, width=main_search_width, item_count=10, sort_column=0, onitemselected="print ('sel %s' % event.target.get_selected_items())", ): 182 | gui.ListColumn(name='albums', text='Albums', width=400) 183 | gui.ListColumn(name='id', text='Id', width=0) 184 | with gui.ListView(name='trackslist', height=lv_songs_height, left=main_search_width, top=form_height, width=main_songs_width, item_count=10, sort_column=0, onitemselected="print ('sel %s' % event.target.get_selected_items())", ): 185 | gui.ListColumn(name='songs', text='Songs', width=200) 186 | gui.ListColumn(name='id', text='Id', width=0, ) 187 | # gui.Gauge(name='gauge', height='18', left='13', top='130', width='50', value=50, ) 188 | 189 | 190 | 191 | mainwin = gui.get("mainwin") 192 | 193 | mainwin.onload = load 194 | 195 | if __name__ == "__main__": 196 | mainwin.show() 197 | gui.main_loop() 198 | -------------------------------------------------------------------------------- /steeb/util/comparator.py: -------------------------------------------------------------------------------- 1 | # Create some method for comparing tracks like: 2 | # comparing(searching track obj, founded tracks array) --------------------------------------------------------------------------------