7 import variables as var
8 from media.file import FileItem
9 from media.url import URLItem
10 from media.url_from_playlist import PlaylistURLItem
11 from media.radio import RadioItem
12 from database import MusicDatabase
13 from media.library import MusicLibrary
15 class PlaylistItemWrapper:
16 def __init__(self, lib, id, type, user):
21 self.log = logging.getLogger("bot")
25 return self.lib[self.id]
28 dict = self.item().to_dict()
29 dict['user'] = self.user
33 ret = self.item().validate()
34 if ret and self.item().version > self.version:
35 self.version = self.item().version
36 self.lib.save(self.id)
40 ret = self.item().prepare()
41 if ret and self.item().version > self.version:
42 self.version = self.item().version
43 self.lib.save(self.id)
46 def async_prepare(self):
47 th = threading.Thread(
48 target=self.prepare, name="Prepare-" + self.id[:7])
50 "%s: start preparing item in thread: " % self.item().type + self.format_debug_string())
56 return self.item().uri()
58 def add_tags(self, tags):
59 self.item().add_tags(tags)
60 if self.item().version > self.version:
61 self.version = self.item().version
62 self.lib.save(self.id)
64 def remove_tags(self, tags):
65 self.item().remove_tags(tags)
66 if self.item().version > self.version:
67 self.version = self.item().version
68 self.lib.save(self.id)
71 self.item().clear_tags()
72 if self.item().version > self.version:
73 self.version = self.item().version
74 self.lib.save(self.id)
77 return self.item().is_ready()
80 return self.item().is_failed()
82 def format_current_playing(self):
83 return self.item().format_current_playing(self.user)
85 def format_song_string(self):
86 return self.item().format_song_string(self.user)
88 def format_short_string(self):
89 return self.item().format_short_string()
91 def format_debug_string(self):
92 return self.item().format_debug_string()
94 def display_type(self):
95 return self.item().display_type()
98 def get_item_wrapper(bot, **kwargs):
99 item = var.library.get_item(bot, **kwargs)
100 if 'user' not in kwargs:
101 raise KeyError("Which user added this song?")
102 return PlaylistItemWrapper(var.library, item.id, kwargs['type'], kwargs['user'])
104 def get_item_wrapper_by_id(bot, id, user):
105 item = var.library.get_item_by_id(bot, id)
107 return PlaylistItemWrapper(var.library, item.id, item.type, user)
111 def get_item_wrappers_by_tags(bot, tags, user):
112 items = var.library.get_items_by_tags(bot, tags)
115 ret.append(PlaylistItemWrapper(var.library, item.id, item.type, user))
118 def get_playlist(mode, _list=None, index=None):
119 if _list and index is None:
120 index = _list.current_index
123 if mode == "one-shot":
124 return OneshotPlaylist()
125 elif mode == "repeat":
126 return RepeatPlaylist()
127 elif mode == "random":
128 return RandomPlaylist()
129 elif mode == "autoplay":
130 return AutoPlaylist()
132 if mode == "one-shot":
133 return OneshotPlaylist().from_list(_list, index)
134 elif mode == "repeat":
135 return RepeatPlaylist().from_list(_list, index)
136 elif mode == "random":
137 return RandomPlaylist().from_list(_list, index)
138 elif mode == "autoplay":
139 return AutoPlaylist().from_list(_list, index)
142 class BasePlaylist(list):
145 self.current_index = -1
146 self.version = 0 # increase by one after each change
147 self.mode = "base" # "repeat", "random"
148 self.pending_items = []
149 self.log = logging.getLogger("bot")
150 self.validating_thread_lock = threading.Lock()
153 return True if len(self) == 0 else False
155 def from_list(self, _list, current_index):
159 self.current_index = current_index
163 def append(self, item: PlaylistItemWrapper):
166 self.pending_items.append(item)
167 self.start_async_validating()
171 def insert(self, index, item):
175 index = self.current_index
177 super().insert(index, item)
179 if index <= self.current_index:
180 self.current_index += 1
182 self.pending_items.append(item)
183 self.start_async_validating()
187 def extend(self, items):
189 super().extend(items)
190 self.pending_items.extend(items)
191 self.start_async_validating()
200 if self.current_index < len(self) - 1:
201 self.current_index += 1
202 return self[self.current_index]
206 def point_to(self, index):
208 if -1 <= index < len(self):
209 self.current_index = index
212 for index, wrapper in enumerate(self):
213 if wrapper.item.id == id:
217 def __delitem__(self, key):
218 return self.remove(key)
220 def remove(self, index):
222 if index > len(self) - 1:
225 removed = self[index]
226 super().__delitem__(index)
228 if self.current_index > index:
229 self.current_index -= 1
234 if wrapper.id == removed.id:
238 var.library.free(removed.id)
241 def remove_by_id(self, id):
244 for index, wrapper in enumerate(self):
246 to_be_removed.append(index)
248 for index in to_be_removed:
251 def current_item(self):
255 return self[self.current_index]
257 def next_index(self):
258 if self.current_index < len(self) - 1:
259 return self.current_index + 1
264 if self.current_index < len(self) - 1:
265 return self[self.current_index + 1]
270 # current_index will lose track after shuffling, thus we take current music out before shuffling
271 #current = self.current_item()
272 #del self[self.current_index]
276 #self.insert(0, current)
277 self.current_index = -1
282 self.current_index = -1
283 var.library.free_all()
287 var.db.remove_section("playlist_item")
288 assert self.current_index is not None
289 var.db.set("playlist", "current_index", self.current_index)
291 for index, music in enumerate(self):
292 var.db.set("playlist_item", str(index), json.dumps({'id': music.id, 'user': music.user }))
295 current_index = var.db.getint("playlist", "current_index", fallback=-1)
296 if current_index == -1:
299 items = var.db.items("playlist_item")
302 items.sort(key=lambda v: int(v[0]))
304 item = json.loads(item[1])
305 music_wrapper = get_item_wrapper_by_id(var.bot, item['id'], item['user'])
307 music_wrappers.append(music_wrapper)
308 self.from_list(music_wrappers, current_index)
310 def _debug_print(self):
311 print("===== Playlist(%d)=====" % self.current_index)
312 for index, item_wrapper in enumerate(self):
313 if index == self.current_index:
314 print("-> %d %s" % (index, item_wrapper.format_debug_string()))
316 print("%d %s" % (index, item_wrapper.format_debug_string()))
317 print("===== End =====")
319 def start_async_validating(self):
320 if not self.validating_thread_lock.locked():
321 th = threading.Thread(target=self._check_valid, name="Validating")
325 def _check_valid(self):
326 self.log.debug("playlist: start validating...")
327 self.validating_thread_lock.acquire()
328 while len(self.pending_items) > 0:
329 item = self.pending_items.pop()
330 self.log.debug("playlist: validating %s" % item.format_debug_string())
331 if not item.validate() or item.is_failed():
332 self.log.debug("playlist: validating failed.")
333 var.library.delete(item.id)
334 self.remove_by_id(item.id)
336 self.log.debug("playlist: validating finished.")
337 self.validating_thread_lock.release()
340 class OneshotPlaylist(BasePlaylist):
343 self.mode = "one-shot"
344 self.current_index = -1
346 def from_list(self, _list, current_index):
348 if current_index > -1:
349 for i in range(current_index):
351 return super().from_list(_list, 0)
352 return super().from_list(_list, -1)
362 if self.current_index != -1:
363 super().__delitem__(self.current_index)
367 self.current_index = 0
374 def next_index(self):
386 def point_to(self, index):
388 self.current_index = -1
389 for i in range(index + 1):
390 super().__delitem__(0)
393 class RepeatPlaylist(BasePlaylist):
404 if self.current_index < len(self) - 1:
405 self.current_index += 1
406 return self[self.current_index]
408 self.current_index = 0
411 def next_index(self):
412 if self.current_index < len(self) - 1:
413 return self.current_index + 1
418 return self[self.next_index()]
421 class RandomPlaylist(BasePlaylist):
426 def from_list(self, _list, current_index):
428 random.shuffle(_list)
429 return super().from_list(_list, -1)
437 if self.current_index < len(self) - 1:
438 self.current_index += 1
439 return self[self.current_index]
442 self.current_index = 0
446 class AutoPlaylist(BasePlaylist):
449 self.mode = "autoplay"
453 ids = var.music_db.query_all_ids()
455 _list.append(get_item_wrapper_by_id(var.bot, ids[random.randint(0, len(ids)-1)], 'AutoPlay'))
456 self.from_list(_list, -1)
458 # def from_list(self, _list, current_index):
474 if self.current_index < len(self) - 1:
475 self.current_index += 1
476 return self[self.current_index]
479 self.current_index = 0