def append_song(self, song): """ Append song `song` to the playlist. """ ps = PlaylistSong(playlist=self, song=song) self.playlist_songs.append(ps) session.flush()
def delete_song(self, position): """ Delete the song at position `position`. """ ps = self.playlist_songs.pop(position) session.delete(ps) session.flush()
def insert_song(self, position, song): """ Insert song `song` at position `position` """ ps = PlaylistSong(playlist=self, song=song) self.playlist_songs.insert(position, ps) session.flush()
def delete_segment(self, position): """ Delete the segment at position `position`. """ seg = self.segments.pop(position) session.delete(seg) session.flush()
def insert_segment(self, position, pace, length): """ Insert a segment with pace `pace` and length `length` at position `position`. """ self.segments.insert(position, Segment(pace=pace, length=length)) session.flush()
def update_segment(self, position, pace=None, length=None): """ Update the segment at position `position` with pace `pace` and/or length `length`. """ if not (pace or length): # noop return segment = self.segments[position] segment.pace = pace or segment.pace segment.length = length or segment.length self.segments[position] = segment session.flush()
def playlist_example(): slow, steady, fast, sprint = Pace(speed='Slow'), Pace(speed='Steady'), \ Pace(speed='Fast'), Pace(speed='Sprint') # Create an activity plan plan = ActivityPlan(name='test') session.add(plan) session.flush() # Add 4 segments to the plan plan.append_segment(pace=steady, length=60) plan.append_segment(pace=steady, length=60) plan.append_segment(pace=slow, length=10) plan.append_segment(pace=sprint, length=60) # Create a playlist playlist = Playlist(name='test') session.add(playlist) session.flush() playlist.generate(plan)
def music_example(): # Create artists kanye = Artist(name='Kanye West') mozart = Artist(name='Wolfgang Amadeus Mozart') # Add artists to the DB session and flush to generate their primary key IDs session.add(kanye) session.add(mozart) session.flush() # Create songs stronger = Song( filename='stronger.mp3', title='Stronger', date_added=datetime.utcnow(), artist=kanye, # Assign the artist object directly ) love_lockdown = Song( filename='love_lockdown.mp3', title='Love Lockdown', date_added=datetime.utcnow(), artist=kanye, ) requiem = Song( filename='requiem.mp3', title='Requiem', date_added=datetime.utcnow(), artist=mozart, ) # Add songs to the DB session and flush to generate their primary key IDs # as well as populating the artist_id field session.add(stronger) session.add(love_lockdown) session.add(requiem) session.flush() # Create a playlist playlist = Playlist(name='test') session.add(playlist) session.flush() # Add some songs to the playlist playlist.append_song(stronger) playlist.append_song(love_lockdown) playlist.append_song(requiem) # Delete a song from the playlist playlist.delete_song(1) # Insert a song at number 3 in the playlist playlist.insert_song(2, love_lockdown) session.commit() print "Artists: %s, %s" % (kanye, mozart) print "Each artist's songs: Kanye: %s, Mozart: %s" % (kanye.songs, mozart.songs) print "Playlist songs: %s" % playlist.songs
def activity_example(): # Create pace objects and add to session slow, steady, fast, sprint = Pace(speed='Slow'), Pace(speed='Steady'), \ Pace(speed='Fast'), Pace(speed='Sprint') session.add(slow) session.add(steady) session.add(fast) session.add(sprint) # Create an activity plan plan = ActivityPlan(name='test') session.add(plan) session.flush() # Add 4 segments to the plan plan.append_segment(pace=steady, length=60) plan.append_segment(pace=steady, length=60) plan.append_segment(pace=slow, length=10) plan.append_segment(pace=sprint, length=60) # Whoops - I made a mistake and want to delete a segment! plan.delete_segment(1) # Fix the second segment plan.update_segment(position=1, pace=fast, length=60) # Add a segment to the beginning plan.insert_segment(position=0, pace=slow, length=60) session.flush() session.commit() # Clean up orphaned segments Segment.remove_orphans() session.commit() print "Plan segments: %s" % plan.segments
def csv_import(): """ Import the song metadata from song_dict.csv into the database. """ print "Importing CSV song data into the database..." csvfile = open('song_dict.csv', 'r') csvreader = csv.reader(csvfile, delimiter=',', quotechar='\"') artist_dict = {} for title, artist_name, bpm, duration, filename in csvreader: # Create or fetch artist artist_name = artist_name.decode('utf-8') artist = artist_dict.get(artist_name) if not artist: # Create new artist artist = Artist(name=artist_name) # Add artists to the DB session and flush to generate their # primary key IDs session.add(artist) session.flush() # Remember the artist via the artists dict artist_dict[artist_name] = artist # Create songs song = Song( filename=os.path.abspath(filename.decode('utf-8')), title=title.decode('utf-8'), date_added=datetime.utcnow(), artist=artist, # Assign the artist object directly ) # Add songs to the DB session and flush to generate their # primary key IDs as well as populating the artist_id field print "Adding song %s..." % song session.add(song) session.flush() # Create Metadata meta = SongMeta( duration=int(float(duration)), bpm=int(bpm), song=song, ) session.add(meta) session.flush() session.commit() print "CSV import finished successfully."
def csv_import(): """ Import the song metadata from song_dict.csv into the database. """ print "Importing CSV song data into the database..." csvfile = open('song_dict.csv', 'r') csvreader = csv.reader(csvfile, delimiter=',', quotechar = '\"') artist_dict = {} for title, artist_name, bpm, duration, filename in csvreader: # Create or fetch artist artist_name = artist_name.decode('utf-8') artist = artist_dict.get(artist_name) if not artist: # Create new artist artist = Artist(name=artist_name) # Add artists to the DB session and flush to generate their # primary key IDs session.add(artist) session.flush() # Remember the artist via the artists dict artist_dict[artist_name] = artist # Create songs song = Song( filename = os.path.abspath(filename.decode('utf-8')), title = title.decode('utf-8'), date_added = datetime.utcnow(), artist = artist, # Assign the artist object directly ) # Add songs to the DB session and flush to generate their # primary key IDs as well as populating the artist_id field print "Adding song %s..." % song session.add(song) session.flush() # Create Metadata meta = SongMeta( duration = int(float(duration)), bpm = int(bpm), song = song, ) session.add(meta) session.flush() session.commit() print "CSV import finished successfully."
def generate(self, plan): """ Generate a playlist of songs given a plan. """ def get_current_set(sets, pace): """ Return the set of songs for the given Pace if non-empty, otherwise choose sets in increasing order of speed until a non-empty set is found. If all sets are empty, return `None`. Nested because it only makes sense to use this within generate(). """ if sets[pace]: # Current set is non-empty - guard statement returns early return sets[pace] # The set for the current segment pace is empty - choose the # next fastest set print " NO SONGS LEFT IN CURRENT SET %s! Choosing a new set" % pace paces = PACE_ENUM.keys() for _ in xrange(4): # Get new pace using the PACE_ENUM. # Choose the next paces in increasing speed order, unless the # current pace is a Sprint, in which case we wrap around to Slow. pace = paces[(paces.index(pace) + 1) % 4] # modulo to wrap around to Slow if sets[pace]: # We have found a non-empty set - continue with this print " Found nonempty set %s" % pace return sets[pace] else: # At this point NONE of the pace sets had any songs left, # which means we have exhausted our corpus of songs. # Break here - the playlist is finished as there's nothing # left to add. print " ALL SETS EXHAUSTED" return None # Reset the playlist, just in case! for _ in xrange(len(self.playlist_songs)): ps = self.playlist_songs.pop() session.delete(ps) self.playlist_songs = [] session.flush() slow_set, steady_set, fast_set, sprint_set = self.divide_songs_into_sets() # Add sets to a dict to easily access by hash sets = { 'Slow': slow_set, 'Steady': steady_set, 'Fast': fast_set, 'Sprint': sprint_set, } segments = list(plan.segments) remaining_segments = len(segments) remaining_time = segments[0].length skips = 0 for seg in segments: if skips > 0: # Skip this segment if a song overlaps it completely skips -= 1 continue print "NEW SEGMENT: %s" % seg remaining_segments -= 1 pace = seg.pace.speed current_set = get_current_set(sets, pace) if not current_set: # We have exhausted the corpus of songs! Our playlist is finished break # Pick subset of songs that can fit in the time remaining subset = [ song for song in current_set if song.meta.duration <= remaining_time ] while subset: # Pick a random song song = random.choice(subset) # Add the song to our playlist print " Adding song from subset: %s" % song self.append_song(song) # Remove the song from relevant sets subset.remove(song) current_set.remove(song) # Recalculate time remaining remaining_time -= song.meta.duration # Remove any songs that are greater than the remaining time subset = [ song for song in subset if song.meta.duration <= remaining_time ] # At this point the segment cannot fit any more segments without # overlapping with the next segment. print " Cannot fit any more whole songs in this segment." # It's possible at this point that the current set has been exhausted # of songs, so we check to see if we need to get the next nonempty set current_set = get_current_set(sets, pace) if not current_set: # No more songs break if remaining_time == 0 and remaining_segments > 0: # Just go to the next seg - set remaining_time to that segment's # duration remaining_time = segments[seg.position + 1].length print " Moving to next segment" elif remaining_segments == 0: # Our run is almost done - pick a random song at the current # segment pace song = random.choice(current_set) self.append_song(song) current_set.remove(song) print " THIS IS THE LAST SEGMENT. Adding random song" else: # We will (potentially) have to overlap with the next segment. next_seg = segments[seg.position + 1] if PACE_ENUM[next_seg.pace.speed] > PACE_ENUM[seg.pace.speed] \ and current_set[0].meta.duration / 2 > remaining_time: # The next segment is at a faster pace. # Here we choose a song from the current segment's set iff # 50% or more of the song will take place in the current # segment. # Otherwise, pick the shortest song from the current pace's # set, so we minimize the amount of time the song cuts into # the next segment current_set = get_current_set(sets, next_seg.pace.speed) if not current_set: # No more songs break song = current_set[0] self.append_song(song) current_set.remove(song) print " Add song from curr pace." # Calculate the overlap between the last song of this segment # and the next segment overlap = (song.meta.duration - remaining_time) next_seg_position = seg.position + 1 remaining_time = segments[next_seg_position].length - overlap while remaining_time <= 0: # If the song completely overlaps the next segment, skip it # and consider the segment after that, until we no longer # completely overlap any segments. print "SKIPPING %s" % segments[next_seg_position] skips += 1 remaining_segments -= 1 if remaining_segments == 0: # We have already overlapped the last segment, so no more # songs are needed return next_seg_position += 1 overlap = abs(remaining_time) remaining_time = segments[next_seg_position].length - overlap self.write_playlist_to_file()
def generate(self, plan): """ Generate a playlist of songs given a plan. """ def get_current_set(sets, pace): """ Return the set of songs for the given Pace if non-empty, otherwise choose sets in increasing order of speed until a non-empty set is found. If all sets are empty, return `None`. Nested because it only makes sense to use this within generate(). """ if sets[pace]: # Current set is non-empty - guard statement returns early return sets[pace] # The set for the current segment pace is empty - choose the # next fastest set print " NO SONGS LEFT IN CURRENT SET %s! Choosing a new set" % pace paces = PACE_ENUM.keys() for _ in xrange(4): # Get new pace using the PACE_ENUM. # Choose the next paces in increasing speed order, unless the # current pace is a Sprint, in which case we wrap around to Slow. pace = paces[(paces.index(pace) + 1) % 4] # modulo to wrap around to Slow if sets[pace]: # We have found a non-empty set - continue with this print " Found nonempty set %s" % pace return sets[pace] else: # At this point NONE of the pace sets had any songs left, # which means we have exhausted our corpus of songs. # Break here - the playlist is finished as there's nothing # left to add. print " ALL SETS EXHAUSTED" return None # Reset the playlist, just in case! for _ in xrange(len(self.playlist_songs)): ps = self.playlist_songs.pop() session.delete(ps) self.playlist_songs = [] session.flush() slow_set, steady_set, fast_set, sprint_set = self.divide_songs_into_sets( ) # Add sets to a dict to easily access by hash sets = { 'Slow': slow_set, 'Steady': steady_set, 'Fast': fast_set, 'Sprint': sprint_set, } segments = list(plan.segments) remaining_segments = len(segments) remaining_time = segments[0].length skips = 0 for seg in segments: if skips > 0: # Skip this segment if a song overlaps it completely skips -= 1 continue print "NEW SEGMENT: %s" % seg remaining_segments -= 1 pace = seg.pace.speed current_set = get_current_set(sets, pace) if not current_set: # We have exhausted the corpus of songs! Our playlist is finished break # Pick subset of songs that can fit in the time remaining subset = [ song for song in current_set if song.meta.duration <= remaining_time ] while subset: # Pick a random song song = random.choice(subset) # Add the song to our playlist print " Adding song from subset: %s" % song self.append_song(song) # Remove the song from relevant sets subset.remove(song) current_set.remove(song) # Recalculate time remaining remaining_time -= song.meta.duration # Remove any songs that are greater than the remaining time subset = [ song for song in subset if song.meta.duration <= remaining_time ] # At this point the segment cannot fit any more segments without # overlapping with the next segment. print " Cannot fit any more whole songs in this segment." # It's possible at this point that the current set has been exhausted # of songs, so we check to see if we need to get the next nonempty set current_set = get_current_set(sets, pace) if not current_set: # No more songs break if remaining_time == 0 and remaining_segments > 0: # Just go to the next seg - set remaining_time to that segment's # duration remaining_time = segments[seg.position + 1].length print " Moving to next segment" elif remaining_segments == 0: # Our run is almost done - pick a random song at the current # segment pace song = random.choice(current_set) self.append_song(song) current_set.remove(song) print " THIS IS THE LAST SEGMENT. Adding random song" else: # We will (potentially) have to overlap with the next segment. next_seg = segments[seg.position + 1] if PACE_ENUM[next_seg.pace.speed] > PACE_ENUM[seg.pace.speed] \ and current_set[0].meta.duration / 2 > remaining_time: # The next segment is at a faster pace. # Here we choose a song from the current segment's set iff # 50% or more of the song will take place in the current # segment. # Otherwise, pick the shortest song from the current pace's # set, so we minimize the amount of time the song cuts into # the next segment current_set = get_current_set(sets, next_seg.pace.speed) if not current_set: # No more songs break song = current_set[0] self.append_song(song) current_set.remove(song) print " Add song from curr pace." # Calculate the overlap between the last song of this segment # and the next segment overlap = (song.meta.duration - remaining_time) next_seg_position = seg.position + 1 remaining_time = segments[next_seg_position].length - overlap while remaining_time <= 0: # If the song completely overlaps the next segment, skip it # and consider the segment after that, until we no longer # completely overlap any segments. print "SKIPPING %s" % segments[next_seg_position] skips += 1 remaining_segments -= 1 if remaining_segments == 0: # We have already overlapped the last segment, so no more # songs are needed return next_seg_position += 1 overlap = abs(remaining_time) remaining_time = segments[ next_seg_position].length - overlap self.write_playlist_to_file()
def append_segment(self, pace, length): """ Append a segment to the list with pace `pace` and length `length`. """ self.segments.append(Segment(pace=pace, length=length)) session.flush()