Skip to content

Instantly share code, notes, and snippets.

@indivisible
Created August 5, 2019 11:36
Show Gist options
  • Save indivisible/13f043f6596c5a30b42ea815b7c6c91e to your computer and use it in GitHub Desktop.
Save indivisible/13f043f6596c5a30b42ea815b7c6c91e to your computer and use it in GitHub Desktop.
Shuffle plugin with (mostly) preserving previous / next order for Quod Libet
#!/usr/bin/env python3
import random
from quodlibet import _
from quodlibet.plugins.playorder import ShufflePlugin
from quodlibet.qltk import Icons
from quodlibet.util import print_d
class OrderRememberedHack(ShufflePlugin):
PLUGIN_ID = "order_remembered_hack"
PLUGIN_NAME = _("Order remembered random")
PLUGIN_ICON = Icons.MEDIA_SKIP_FORWARD
PLUGIN_DESC = _("Preserve the play order of songs on forward / backward skip.")
def __init__(self):
super(OrderRememberedHack, self).__init__()
self.no_repeat_window_size = 20
self.no_repeat_tries = 5
self.window_size = 100
self._forward_window = []
self._backward_window = []
def _trim_windows(self):
if len(self._forward_window) > self.window_size:
self._forward_window = self._forward_window[-self.window_size:]
if len(self._backward_window) > self.window_size:
self._backward_window = self._backward_window[-self.window_size:]
def add_to_window(self, playlist, iter, window):
if not iter:
return
item = (iter, playlist.get_value(iter, 0))
window.append(item)
self._trim_windows()
def find_in_playlist(self, playlist, audiofile):
pass
def pop_window(self, playlist, window):
if not window:
return None
iter, audiofile = window.pop()
try:
new_af = playlist.get_value(iter, 0)
if new_af == audiofile:
return iter
except TypeError:
# the old iter is invalid for some reason
pass
i = playlist.get_iter_first()
while i:
value = playlist.get_value(i, 0)
if value == audiofile:
print_d('Found new entry with same audiofile!')
return i
i = playlist.iter_next(i)
print_d("Couldn't find entry for {}".format(audiofile))
return None
def next(self, playlist, iter):
self.add_to_window(playlist, iter, self._backward_window)
next_iter = self.pop_window(playlist, self._forward_window)
if next_iter:
return next_iter
songs = set(range(len(playlist)))
if not songs:
return None
# For best results we'd have to try to find all recently played songs
# in the current playlist. That is expensive, so we just try rerolling
# a few times
size = self.no_repeat_window_size
for __ in range(self.no_repeat_tries):
if len(songs) == 1:
break
played = set(i[1] for i in self._backward_window[-size:])
next_iter = playlist.get_iter((random.choice(list(songs)),))
next_af = playlist.get_value(next_iter, 0)
if next_af not in played or len(songs) == 1:
return next_iter
# give up after a few tries
return playlist.get_iter((random.choice(list(songs)),))
def previous(self, playlist, iter):
self.add_to_window(playlist, iter, self._forward_window)
return self.pop_window(playlist, self._backward_window)
def set_explicit(self, playlist, iter):
if playlist.current_iter:
self.add_to_window(
playlist,
playlist.current_iter,
self._backward_window)
return iter
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment