summaryrefslogtreecommitdiff
path: root/files/bandleader.py
diff options
context:
space:
mode:
Diffstat (limited to 'files/bandleader.py')
-rw-r--r--files/bandleader.py271
1 files changed, 271 insertions, 0 deletions
diff --git a/files/bandleader.py b/files/bandleader.py
new file mode 100644
index 0000000..16e167d
--- /dev/null
+++ b/files/bandleader.py
@@ -0,0 +1,271 @@
+from selenium.webdriver import Firefox
+from selenium.webdriver.firefox.options import Options
+from collections import namedtuple, Counter
+from threading import Thread
+from time import sleep, ctime
+from os.path import isfile
+import csv
+
+
+BANDCAMP_FRONTPAGE='https://bandcamp.com/'
+
+TrackRec = namedtuple('TrackRec',
+ ['track_url', # if available
+ 'title',
+ 'artist',
+ 'artist_url',
+ 'album',
+ 'album_url',
+ 'timestamp' # when you played it
+ ])
+
+class BandLeader():
+
+ def __init__(self,csvpath=None):
+
+ self.database_path=csvpath
+ self.database = [] # using a list b/c its simple
+
+ # load database from disk if possible
+ if isfile(self.database_path):
+ with open(self.database_path, newline='') as dbfile:
+ dbreader = csv.reader(dbfile)
+ next(dbreader) # to ignore the header line
+ self.database = [TrackRec._make(rec) for rec in dbreader]
+
+ # create a headless browser
+ opts = Options()
+ opts.set_headless()
+ self.browser = Firefox(options=opts)
+ self.browser.get(BANDCAMP_FRONTPAGE)
+ self.paused = True
+
+ self.autoplay_on = False
+ self._current_track_record = None
+ self._current_track_number = 1
+ # create a thread that periodically maintains our database by
+ # consulting the browser's current state
+ self._has_quit = False
+ self.thread = Thread(target=self._maintain)
+ self.thread.daemon = True # kills the thread when the main process dies
+ self.thread.start()
+
+ self.tracks()
+
+ def _maintain(self):
+ while not self._has_quit:
+ self._update_db()
+ self._check_auto_advance()
+ sleep(1)
+
+
+ def log(self,where, err):
+ print(where, err)
+
+
+ def save_db(self):
+ with open(self.database_path,'w',newline='') as dbfile:
+ dbwriter = csv.writer(dbfile)
+ dbwriter.writerow(list(TrackRec._fields))
+ for entry in self.database:
+ dbwriter.writerow(list(entry))
+
+
+ def _update_db(self):
+ try:
+ check = (self._current_track_record is not None
+ and self._current_track_record is not None
+ and (len(self.database) == 0
+ or self.database[-1] != self._current_track_record)
+ and self.is_playing())
+ if check:
+ self.database.append(self._current_track_record)
+ self.save_db()
+
+ except Exception as e:
+ self.log('_update_db',e)
+
+
+ def _check_auto_advance(self):
+ try:
+ if self.autoplay_on and not self.is_playing():
+ self.play_next()
+ except Exception as e:
+ self.log('_check_auto_advance', e)
+
+
+ def toggle_autoplay(self):
+ self.autoplay_on = not self.autoplay_on
+ if self.autoplay_on:
+ print('autoplay is ON')
+ else:
+ print('autoplay is OFF')
+
+
+ def currently_playing(self):
+ '''
+ returns the record for the currently playing track,
+ or None if nothing is playing
+ '''
+ try:
+ if self.is_playing():
+ track_title = self.browser.find_element_by_class_name('title').text
+ album_detail = self.browser.find_element_by_css_selector('.detail-album > a')
+ album_title = album_detail.text
+ album_url = album_detail.get_attribute('href').split('?')[0]
+ artist_detail = self.browser.find_element_by_css_selector('.detail-artist > a')
+ artist = artist_detail.text
+ artist_url = artist_detail.get_attribute('href').split('?')[0]
+ return TrackRec('',track_title,artist,artist_url,album_title,album_url,ctime())
+ except Exception as e:
+ print('there was an error: {}'.format(e))
+
+ return None
+
+
+ def tracks(self):
+ '''
+ lists the tracks that are presently available for play and
+ associates a track number with each one. You may use these
+ track numbers to as arguments to the `play` method.
+ '''
+ sleep(1)
+ discover_section = self.browser.find_element_by_class_name('discover-results')
+ left_x = discover_section.location['x']
+ right_x = left_x + discover_section.size['width']
+
+ discover_items = self.browser.find_elements_by_class_name('discover-item')
+ self.track_list = [t for t in discover_items
+ if t.location['x'] >= left_x and t.location['x'] < right_x]
+
+ for (i,track) in enumerate(self.track_list):
+ print('[{}]'.format(i+1))
+ lines = track.text.split('\n')
+ print('Album : {}'.format(lines[0]))
+ print('Artist : {}'.format(lines[1]))
+ if len(lines) > 2:
+ print('Genre : {}'.format(lines[2]))
+
+
+ def play(self,track=None):
+ '''
+ plays a track. If `track` is not supplied, this method
+ simulates pressing the play button somewhere on bandcamp's site.
+
+ If `track` is an `int`, this method plays the track with that
+ track number. See the `tracks` method.
+ '''
+
+ if track is None:
+ self.browser.find_element_by_class_name('playbutton').click()
+ elif type(track) is int and track <= len(self.track_list) and track >= 1:
+ self._current_track_number = track
+ self.track_list[self._current_track_number - 1].click()
+
+ sleep(0.5)
+ if self.is_playing():
+ self._current_track_record = self.currently_playing()
+ print("CURRENTLY PLAYING")
+ self.print_track()
+
+ def pause(self):
+ self.play()
+ self.paused = True
+
+
+ def resume(self):
+ if self.paused:
+ self.play()
+ self.paused = False
+
+
+ def is_playing(self):
+ '''
+ returns `True` if a track is presently playing
+ '''
+ playbtn = self.browser.find_element_by_class_name('playbutton')
+ return playbtn.get_attribute('class').find('playing') > -1
+
+
+ def play_next(self):
+ '''
+ plays the next available track
+ '''
+ if self._current_track_number < len(self.track_list):
+ self.play(self._current_track_number+1)
+ else:
+ self.more_tracks()
+ self.play(1)
+
+
+ def play_prev(self):
+ '''
+ plays the previous available track
+ '''
+ if (self._current_track_number - 1) >= 0:
+ self.play(self._current_track_number -1)
+
+
+ def top_tracks(self,num=10):
+ '''
+ lists the top `num` tracks in order of frequency of listening
+ '''
+ c = Counter(t.title for t in self.database)
+ return c.most_common(num)
+
+ def top_albums(self,num=10):
+ c = Counter(t.album for t in self.database)
+ return c.most_common(num)
+
+ def top_artists(self,num=10):
+ c = Counter(t.artist for t in self.database)
+ return c.most_common(num)
+
+ def catalogue_pages(self):
+ '''
+ print the available pages in the catalogue that are presently
+ accessible
+ '''
+ print('PAGES')
+ for e in self.browser.find_elements_by_class_name('item-page'):
+ print(e.text)
+ print('')
+
+
+ def more_tracks(self,page='next'):
+ '''
+ finds more tracks in a contextual way.
+
+ If on the main page, advances the listing in the 'Discover'
+ section.
+
+ If on an album page, looks to the next album in the
+ 'Discography' section for the present artist.
+ '''
+ next_btn = [e for e in self.browser.find_elements_by_class_name('item-page')
+ if e.text.lower().strip() == str(page)]
+
+ if next_btn:
+ next_btn[0].click()
+ self.tracks()
+
+ def explore(self):
+ '''
+ visits the start page and lists tracks
+ '''
+ self.browser.get(BANDCAMP_FRONTPAGE)
+ self.tracks()
+
+
+ def quit(self):
+ self._has_quit = True
+ self.browser.close()
+ # flush db to disk
+
+ def print_track(self,tr=None):
+ if tr is None:
+ tr = self._current_track_record
+
+ if tr is not None:
+ print('{}\n by {}\n on the album {}'.format(tr.title,tr.artist,tr.album))
+