]> git.0d.be Git - botaradio.git/commitdiff
REFACTOR: ITEM REVOLUTION #91
authorTerry Geng <gengyanda@gmail.com>
Thu, 5 Mar 2020 08:28:08 +0000 (16:28 +0800)
committerTerry Geng <gengyanda@gmail.com>
Thu, 5 Mar 2020 08:28:08 +0000 (16:28 +0800)
15 files changed:
command.py
configuration.default.ini
interface.py
media/file.py
media/item.py [new file with mode: 0644]
media/playlist.py
media/radio.py
media/url.py
media/url_from_playlist.py [new file with mode: 0644]
mumbleBot.py
playlist.py [deleted file]
playlist.txt [deleted file]
templates/playlist.html
util.py
variables.py

index a073df52291d11eec9e0f832db4dfec4754d6ffa..d0fa110681e16d287ebbc074c72177dac1c04013 100644 (file)
@@ -5,15 +5,16 @@ import pymumble.pymumble_py3 as pymumble
 import re
 
 import constants
-import media.file
-import media.playlist
-import media.radio
 import media.system
-import media.url
 import util
 import variables as var
 from librb import radiobrowser
 from database import Database
+from media.playlist import PlaylistItemWrapper
+from media.file import FileItem
+from media.url_from_playlist import URLFromPlaylistItem, get_playlist_info
+from media.url import URLItem
+from media.radio import RadioItem
 
 log = logging.getLogger("bot")
 
@@ -57,8 +58,8 @@ def register_all_commands(bot):
 
     # Just for debug use
     bot.register_command('rtrms', cmd_real_time_rms)
-    bot.register_command('loop', cmd_loop_state)
-    bot.register_command('item', cmd_item)
+    bot.register_command('loop', cmd_loop_state)
+    bot.register_command('item', cmd_item)
 
 def send_multi_lines(bot, lines, text):
     global log
@@ -138,16 +139,16 @@ def cmd_play(bot, user, text, command, parameter):
 
     if var.playlist.length() > 0:
         if parameter:
-            if parameter.isdigit() and int(parameter) > 0 and int(parameter) <= len(var.playlist):
+            if parameter.isdigit() and 0 <= int(parameter) <= len(var.playlist):
+                var.playlist.point_to(int(parameter) - 1)
                 bot.interrupt_playing()
-                bot.launch_music(int(parameter) - 1)
             else:
                 bot.send_msg(constants.strings('invalid_index', index=parameter), text)
 
         elif bot.is_pause:
             bot.resume()
         else:
-            bot.send_msg(util.format_current_playing(), text)
+            bot.send_msg(var.playlist.current_item().format_current_playing(), text)
     else:
         bot.is_pause = False
         bot.send_msg(constants.strings('queue_empty'), text)
@@ -168,12 +169,11 @@ def cmd_play_file(bot, user, text, command, parameter):
         files = util.get_recursive_file_list_sorted(var.music_folder)
         if int(parameter) < len(files):
             filename = files[int(parameter)].replace(var.music_folder, '')
-            music = {'type': 'file',
-                     'path': filename,
-                     'user': user}
-            music = var.playlist.append(music)
-            log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
-            bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
+            music_wrapper = PlaylistItemWrapper(FileItem(bot, filename), user)
+            var.playlist.append(music_wrapper)
+            music = music_wrapper.item
+            log.info("cmd: add to playlist: " + music.format_debug_string())
+            bot.send_msg(constants.strings('file_added', item=music.format_song_string(user)), text)
 
     # if parameter is {path}
     else:
@@ -184,12 +184,11 @@ def cmd_play_file(bot, user, text, command, parameter):
             return
 
         if os.path.isfile(path):
-            music = {'type': 'file',
-                     'path': parameter,
-                     'user': user}
-            music = var.playlist.append(music)
-            log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
-            bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
+            music_wrapper = PlaylistItemWrapper(FileItem(bot, parameter), user)
+            var.playlist.append(music_wrapper)
+            music = music_wrapper.item
+            log.info("cmd: add to playlist: " + music.format_debug_string())
+            bot.send_msg(constants.strings('file_added', item=music.format_song_string(user)), text)
             return
 
         # if parameter is {folder}
@@ -211,12 +210,11 @@ def cmd_play_file(bot, user, text, command, parameter):
 
             for file in files:
                 count += 1
-                music = {'type': 'file',
-                         'path': file,
-                         'user': user}
-                music = var.playlist.append(music)
-                log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
-                msgs.append("{} ({})".format(music['title'], music['path']))
+                music_wrapper = PlaylistItemWrapper(FileItem(bot, file), user)
+                var.playlist.append(music_wrapper)
+                music = music_wrapper.item
+                log.info("cmd: add to playlist: " + music.format_debug_string())
+                msgs.append("{} ({})".format(music.title, music.path))
 
             if count != 0:
                 send_multi_lines(bot, msgs, text)
@@ -230,12 +228,12 @@ def cmd_play_file(bot, user, text, command, parameter):
             if len(matches) == 0:
                 bot.send_msg(constants.strings('no_file'), text)
             elif len(matches) == 1:
-                music = {'type': 'file',
-                         'path': matches[0][1],
-                         'user': user}
-                music = var.playlist.append(music)
-                log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
-                bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
+                file = matches[0][1]
+                music_wrapper = PlaylistItemWrapper(FileItem(bot, file), user)
+                var.playlist.append(music_wrapper)
+                music = music_wrapper.item
+                log.info("cmd: add to playlist: " + music.format_debug_string())
+                bot.send_msg(constants.strings('file_added', item=music.format_song_string(user)), text)
             else:
                 msgs = [ constants.strings('multiple_matches')]
                 for match in matches:
@@ -256,13 +254,11 @@ def cmd_play_file_match(bot, user, text, command, parameter):
                 match = re.search(parameter, file)
                 if match:
                     count += 1
-                    music = {'type': 'file',
-                             'path': file,
-                             'user': user}
-                    music = var.playlist.append(music)
-                    log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
-
-                    msgs.append("{} ({})".format(music['title'], music['path']))
+                    music_wrapper = PlaylistItemWrapper(FileItem(bot, file), user)
+                    var.playlist.append(music_wrapper)
+                    music = music_wrapper.item
+                    log.info("cmd: add to playlist: " + music.format_debug_string())
+                    msgs.append("{} ({})".format(music.title, music.path))
 
             if count != 0:
                 send_multi_lines(bot, msgs, text)
@@ -279,22 +275,15 @@ def cmd_play_file_match(bot, user, text, command, parameter):
 def cmd_play_url(bot, user, text, command, parameter):
     global log
 
-    music = {'type': 'url',
-             # grab the real URL
-             'url': util.get_url_from_input(parameter),
-             'user': user,
-             'ready': 'validation'}
+    url = util.get_url_from_input(parameter)
+    music_wrapper = PlaylistItemWrapper(URLItem(bot, url), user)
+    var.playlist.append(music_wrapper)
 
-    music = bot.validate_music(music)
-    if music:
-        music = var.playlist.append(music)
-        log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
-        bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
-        if var.playlist.length() == 2:
-            # If I am the second item on the playlist. (I am the next one!)
-            bot.async_download_next()
-    else:
-        bot.send_msg(constants.strings('unable_download'), text)
+    log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
+    bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text)
+    if var.playlist.length() == 2:
+        # If I am the second item on the playlist. (I am the next one!)
+        bot.async_download_next()
 
 
 def cmd_play_playlist(bot, user, text, command, parameter):
@@ -308,11 +297,11 @@ def cmd_play_playlist(bot, user, text, command, parameter):
 
     url = util.get_url_from_input(parameter)
     log.debug("cmd: fetching media info from playlist url %s" % url)
-    items = media.playlist.get_playlist_info(url=url, start_index=offset, user=user)
+    items = get_playlist_info(bot, url=url, start_index=offset, user=user)
     if len(items) > 0:
         var.playlist.extend(items)
         for music in items:
-            log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
+            log.info("cmd: add to playlist: " + music.format_debug_string())
     else:
         bot.send_msg(constants.strings("playlist_fetching_failed"), text)
 
@@ -335,16 +324,10 @@ def cmd_play_radio(bot, user, text, command, parameter):
             parameter = parameter.split()[0]
         url = util.get_url_from_input(parameter)
         if url:
-            music = {'type': 'radio',
-                     'url': url,
-                     'user': user}
-
-            log.info("bot: fetching radio server description")
-            music["name"] = media.radio.get_radio_server_description(url)
+            music_wrapper = PlaylistItemWrapper(RadioItem(bot, url), user)
 
-            var.playlist.append(music)
-            log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
-            bot.async_download_next()
+            var.playlist.append(music_wrapper)
+            log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
         else:
             bot.send_msg(constants.strings('bad_url'))
 
@@ -440,13 +423,9 @@ def cmd_rb_play(bot, user, text, command, parameter):
         url = radiobrowser.geturl_byid(parameter)
         if url != "-1":
             log.info('cmd: Found url: ' + url)
-            music = {'type': 'radio',
-                     'name': stationname,
-                     'artist': homepage,
-                     'url': url,
-                     'user': user}
-            var.playlist.append(music)
-            log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
+            music_wrapper = PlaylistItemWrapper(RadioItem(bot, url, stationname), user)
+            var.playlist.append(music_wrapper)
+            log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
             bot.async_download_next()
         else:
             log.info('cmd: No playable url found.')
@@ -637,7 +616,7 @@ def cmd_current_music(bot, user, text, command, parameter):
 
     reply = ""
     if var.playlist.length() > 0:
-        bot.send_msg(util.format_current_playing())
+        bot.send_msg(var.playlist.current_item().format_current_playing())
     else:
         reply = constants.strings('not_playing')
     bot.send_msg(reply, text)
@@ -647,9 +626,7 @@ def cmd_skip(bot, user, text, command, parameter):
     global log
 
     if var.playlist.length() > 0:
-        bot.stop()
-        bot.launch_music()
-        bot.async_download_next()
+        bot.interrupt_playing()
     else:
         bot.send_msg(constants.strings('queue_empty'), text)
 
@@ -668,10 +645,6 @@ def cmd_last(bot, user, text, command, parameter):
 def cmd_remove(bot, user, text, command, parameter):
     global log
 
-    if bot.download_in_progress:
-        bot.send_msg(constants.strings("cannot_change_when_download"))
-        return
-
     # Allow to remove specific music into the queue with a number
     if parameter and parameter.isdigit() and int(parameter) > 0 \
             and int(parameter) <= var.playlist.length():
@@ -695,11 +668,10 @@ def cmd_remove(bot, user, text, command, parameter):
         else:
             removed = var.playlist.remove(index)
 
-        # the Title isn't here if the music wasn't downloaded
         bot.send_msg(constants.strings('removing_item',
-            item=removed['title'] if 'title' in removed else removed['url']), text)
+            item=removed.format_song_string()), text)
 
-        log.info("cmd: delete from playlist: " + str(removed['path'] if 'path' in removed else removed['url']))
+        log.info("cmd: delete from playlist: " + removed.format_debug_string())
     else:
         bot.send_msg(constants.strings('bad_parameter', command=command))
 
@@ -772,9 +744,9 @@ def cmd_repeat(bot, user, text, command, parameter):
             var.playlist.current_index + 1,
             music
         )
-        log.info("bot: add to playlist: " + util.format_debug_song_string(music))
+        log.info("bot: add to playlist: " + music.format_debug_string)
 
-    bot.send_msg(constants.strings("repeat", song=util.format_song_string(music), n=str(repeat)), text)
+    bot.send_msg(constants.strings("repeat", song=music.format_song_string, n=str(repeat)), text)
 
 def cmd_mode(bot, user, text, command, parameter):
     global log
@@ -811,4 +783,4 @@ def cmd_loop_state(bot, user, text, command, parameter):
 
 def cmd_item(bot, user, text, command, parameter):
     print(bot.wait_for_downloading)
-    print(var.playlist.current_item())
+    print(var.playlist.current_item().item.to_dict())
index ef38a99e949780a0effd98d29142a6b9cdf39f27..0ea29da6efc8201a0791438a7510b1fdf47591e6 100644 (file)
@@ -182,15 +182,19 @@ multiple_matches = Track not found! Possible candidates:
 queue_contents = Items on the playlist:
 queue_empty = Playlist is empty!
 invalid_index = Invalid index <i>{index}</i>. Use '!queue' to see your playlist.
-now_playing_radio = Now Playing Radio: <br /> <a href="{url}">{title}</a> <i>from</i> {name} <i>added by</i> {user}
-now_playing_file = Now Playing File:<br /> {artist} - {title} <i>added by</i> {user}
-now_playing_from_playlist = Now Playing URL:<br /> <a href="{url}">{title}</a> <i>from playlist</i> <a href="{playlist_url}">{playlist}</a> <i>added by</i> {user}
-now_playing_url = Now Playing URL: <br /> <a href="{url}">{title}</a> <i>added by</i> {user}
+now_playing = Playing <br />{item}
+radio = Radio
+file = File
+url_from_playlist = URL
+url = URL
+radio_item = <a href="{url}">{title}</a> <i>from</i> {name} <i>added by</i> {user}
+file_item = {artist} - {title} <i>added by</i> {user}
+url_from_playlist_item = <a href="{url}">{title}</a> <i>from playlist</i> <a href="{playlist_url}">{playlist}</a> <i>added by</i> {user}
+url_item = <a href="{url}">{title}</a> <i>added by</i> {user}
 not_in_my_channel = You're not in my channel, command refused!
 pm_not_allowed = Private message aren't allowed.
 too_long = This music is too long, skip!
 download_in_progress = Download of {item} in progress...
-cannot_change_when_download = Downloading songs, please wait until the download completes.
 removing_item = Removed entry {item} from playlist.
 user_ban = You are banned, not allowed to do that!
 url_ban = This url is banned!
index 576e80103a41ddb617cc212fdf34c018bcbfd01d..bf4d235b1ea79ec0b26b0c59351e4ae804b7c3f6 100644 (file)
@@ -12,7 +12,11 @@ import random
 from werkzeug.utils import secure_filename
 import errno
 import media
-import media.radio
+from media.playlist import PlaylistItemWrapper
+from media.file import FileItem
+from media.url_from_playlist import URLFromPlaylistItem, get_playlist_info
+from media.url import URLItem
+from media.radio import RadioItem
 import logging
 import time
 import constants
@@ -58,6 +62,7 @@ class ReverseProxied(object):
 
 web = Flask(__name__)
 log = logging.getLogger("bot")
+user = 'Remote Control'
 
 def init_proxy():
     global web
@@ -109,7 +114,7 @@ def index():
                            os=os,
                            playlist=var.playlist,
                            user=var.user,
-                           paused=var.botamusique.is_pause
+                           paused=var.bot.is_pause
                            )
 
 @web.route("/playlist", methods=['GET'])
@@ -124,10 +129,10 @@ def playlist():
 
     items = []
 
-    for index, item in enumerate(var.playlist):
+    for index, item_wrapper in enumerate(var.playlist):
          items.append(render_template('playlist.html',
                                      index=index,
-                                     m=item,
+                                     m=item_wrapper.item,
                                      playlist=var.playlist
                                      )
                      )
@@ -138,7 +143,7 @@ def status():
     if (var.playlist.length() > 0):
         return jsonify({'ver': var.playlist.version,
                         'empty': False,
-                        'play': not var.botamusique.is_pause,
+                        'play': not var.bot.is_pause,
                         'mode': var.playlist.mode})
     else:
         return jsonify({'ver': var.playlist.version,
@@ -159,25 +164,16 @@ def post():
         if 'add_file_bottom' in request.form and ".." not in request.form['add_file_bottom']:
             path = var.music_folder + request.form['add_file_bottom']
             if os.path.isfile(path):
-                item = {'type': 'file',
-                        'path' : request.form['add_file_bottom'],
-                        'title' : '',
-                        'user' : 'Remote Control'}
-                item = var.playlist.append(util.attach_music_tag_info(item))
-                log.info('web: add to playlist(bottom): ' + util.format_debug_song_string(item))
+                music_wrapper = PlaylistItemWrapper(FileItem(var.bot, request.form['add_file_bottom']), user)
+                var.playlist.append(music_wrapper)
+                log.info('web: add to playlist(bottom): ' + music_wrapper.format_debug_string())
 
         elif 'add_file_next' in request.form and ".." not in request.form['add_file_next']:
             path = var.music_folder + request.form['add_file_next']
             if os.path.isfile(path):
-                item = {'type': 'file',
-                        'path' : request.form['add_file_next'],
-                        'title' : '',
-                        'user' : 'Remote Control'}
-                item = var.playlist.insert(
-                    var.playlist.current_index + 1,
-                    item
-                )
-                log.info('web: add to playlist(next): ' + util.format_debug_song_string(item))
+                music_wrapper = PlaylistItemWrapper(FileItem(var.bot, request.form['add_file_next']), user)
+                var.playlist.insert(var.playlist.current_index + 1, music_wrapper)
+                log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string())
 
         elif ('add_folder' in request.form and ".." not in request.form['add_folder']) or ('add_folder_recursively' in request.form and ".." not in request.form['add_folder_recursively']):
             try:
@@ -202,42 +198,35 @@ def post():
                 else:
                     files = music_library.get_files(folder)
 
-                files = list(map(lambda file:
-                    {'type':'file',
-                     'path': os.path.join(folder, file),
-                     'user':'Remote Control'}, files))
+                music_wrappers = list(map(
+                    lambda file: PlaylistItemWrapper(FileItem(var.bot, file), user),
+                    files))
 
-                files = var.playlist.extend(files)
+                var.playlist.extend(files)
 
-                for file in files:
-                    log.info("web: add to playlist: %s" %  util.format_debug_song_string(file))
+                for music_wrapper in music_wrappers:
+                    log.info('web: add to playlist: ' + music_wrapper.format_debug_string())
 
 
         elif 'add_url' in request.form:
-            music = {'type':'url',
-                                 'url': request.form['add_url'],
-                                 'user': 'Remote Control',
-                                 'ready': 'validation'}
-            music = var.botamusique.validate_music(music)
-            if music:
-                var.playlist.append(music)
-                log.info("web: add to playlist: " + util.format_debug_song_string(music))
-                if var.playlist.length() == 2:
-                    # If I am the second item on the playlist. (I am the next one!)
-                    var.botamusique.async_download_next()
+            music_wrapper = PlaylistItemWrapper(URLItem(var.bot, request.form['add_url']), user)
+            var.playlist.append(music_wrapper)
+
+            log.info("web: add to playlist: " + music_wrapper.format_debug_string())
+            if var.playlist.length() == 2:
+                # If I am the second item on the playlist. (I am the next one!)
+                var.bot.async_download_next()
 
         elif 'add_radio' in request.form:
             url = request.form['add_radio']
-            music = var.playlist.append({'type': 'radio',
-                                 'url': url,
-                                 'user': "Remote Control"})
-            log.info("web: fetching radio server description")
-            music["name"] = media.radio.get_radio_server_description(url)
-            log.info("web: add to playlist: " + util.format_debug_song_string(music))
+            music_wrapper = PlaylistItemWrapper(RadioItem(var.bot, url), user)
+            var.playlist.append(music_wrapper)
+
+            log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
 
         elif 'delete_music' in request.form:
-            music = var.playlist[int(request.form['delete_music'])]
-            log.info("web: delete from playlist: " + util.format_debug_song_string(music))
+            music_wrapper = var.playlist[int(request.form['delete_music'])]
+            log.info("web: delete from playlist: " + music_wrapper.format_debug_string())
 
             if var.playlist.length() >= int(request.form['delete_music']):
                 index = int(request.form['delete_music'])
@@ -246,26 +235,26 @@ def post():
                     var.playlist.remove(index)
 
                     if index < len(var.playlist):
-                        if not var.botamusique.is_pause:
-                            var.botamusique.interrupt_playing()
+                        if not var.bot.is_pause:
+                            var.bot.interrupt_playing()
                             var.playlist.current_index -= 1
                             # then the bot will move to next item
 
                     else:  # if item deleted is the last item of the queue
                         var.playlist.current_index -= 1
-                        if not var.botamusique.is_pause:
-                            var.botamusique.interrupt_playing()
+                        if not var.bot.is_pause:
+                            var.bot.interrupt_playing()
                 else:
                     var.playlist.remove(index)
 
 
         elif 'play_music' in request.form:
-            music = var.playlist[int(request.form['play_music'])]
-            log.info("web: jump to: " + util.format_debug_song_string(music))
+            music_wrapper = var.playlist[int(request.form['play_music'])]
+            log.info("web: jump to: " + music_wrapper.format_debug_string())
 
             if len(var.playlist) >= int(request.form['play_music']):
-                var.botamusique.interrupt_playing()
-                var.botamusique.launch_music(int(request.form['play_music']))
+                var.playlist.point_to(int(request.form['play_music']) - 1)
+                var.bot.interrupt_playing()
 
         elif 'delete_music_file' in request.form and ".." not in request.form['delete_music_file']:
             path = var.music_folder + request.form['delete_music_file']
@@ -283,7 +272,7 @@ def post():
         elif 'action' in request.form:
             action = request.form['action']
             if action == "randomize":
-                var.botamusique.interrupt_playing()
+                var.bot.interrupt_playing()
                 var.playlist.set_mode("random")
                 var.db.set('playlist', 'playback_mode', "random")
                 log.info("web: playback mode changed to random.")
@@ -296,27 +285,27 @@ def post():
                 var.db.set('playlist', 'playback_mode', "repeat")
                 log.info("web: playback mode changed to repeat.")
             elif action == "stop":
-                var.botamusique.stop()
+                var.bot.stop()
             elif action == "pause":
-                var.botamusique.pause()
+                var.bot.pause()
             elif action == "resume":
-                var.botamusique.resume()
+                var.bot.resume()
             elif action == "clear":
-                var.botamusique.clear()
+                var.bot.clear()
             elif action == "volume_up":
-                if var.botamusique.volume_set + 0.03 < 1.0:
-                    var.botamusique.volume_set = var.botamusique.volume_set + 0.03
+                if var.bot.volume_set + 0.03 < 1.0:
+                    var.bot.volume_set = var.bot.volume_set + 0.03
                 else:
-                    var.botamusique.volume_set = 1.0
-                var.db.set('bot', 'volume', str(var.botamusique.volume_set))
-                log.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
+                    var.bot.volume_set = 1.0
+                var.db.set('bot', 'volume', str(var.bot.volume_set))
+                log.info("web: volume up to %d" % (var.bot.volume_set * 100))
             elif action == "volume_down":
-                if var.botamusique.volume_set - 0.03 > 0:
-                    var.botamusique.volume_set = var.botamusique.volume_set - 0.03
+                if var.bot.volume_set - 0.03 > 0:
+                    var.bot.volume_set = var.bot.volume_set - 0.03
                 else:
-                    var.botamusique.volume_set = 0
-                var.db.set('bot', 'volume', str(var.botamusique.volume_set))
-                log.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
+                    var.bot.volume_set = 0
+                var.db.set('bot', 'volume', str(var.bot.volume_set))
+                log.info("web: volume up to %d" % (var.bot.volume_set * 100))
 
     return status()
 
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bcde63d76b0b81cf1d082edd5cb2a59e2d56d7cd 100644 (file)
@@ -0,0 +1,158 @@
+import logging
+import os
+import re
+from io import BytesIO
+import base64
+import hashlib
+import mutagen
+from PIL import Image
+import json
+
+import util
+import variables as var
+from media.item import BaseItem
+import constants
+
+'''
+type : file
+    id
+    path
+    title
+    artist
+    duration
+    thumbnail
+    user
+'''
+
+class FileItem(BaseItem):
+    def __init__(self, bot, path, from_dict=None):
+        if not from_dict:
+            super().__init__(bot)
+            self.path = path
+            self.title = ""
+            self.artist = "??"
+            self.thumbnail = None
+            if self.path:
+                self.id = hashlib.md5(path.encode()).hexdigest()
+                if os.path.exists(self.uri()):
+                    self._get_info_from_tag()
+                    self.ready = "yes"
+        else:
+            super().__init__(bot, from_dict)
+            self.path = from_dict['path']
+            self.title = from_dict['title']
+            self.artist = from_dict['artist']
+            self.thumbnail = from_dict['thumbnail']
+            if not self.validate():
+                self.ready = "failed"
+
+        self.type = "file"
+
+    def uri(self):
+        return var.music_folder + self.path
+
+    def is_ready(self):
+        return True
+
+    def validate(self):
+        if not os.path.exists(self.uri()):
+            self.log.info(
+                "file: music file missed for %s" % self.format_debug_string())
+            self.send_client_message(constants.strings('file_missed', file=self.path))
+            return False
+
+        self.ready = "yes"
+        return True
+
+    def _get_info_from_tag(self):
+        match = re.search("(.+)\.(.+)", self.uri())
+        assert match is not None
+
+        file_no_ext = match[1]
+        ext = match[2]
+
+        try:
+            im = None
+            path_thumbnail = file_no_ext + ".jpg"
+            if os.path.isfile(path_thumbnail):
+                im = Image.open(path_thumbnail)
+
+            if ext == "mp3":
+                # title: TIT2
+                # artist: TPE1, TPE2
+                # album: TALB
+                # cover artwork: APIC:
+                tags = mutagen.File(self.uri())
+                if 'TIT2' in tags:
+                    self.title = tags['TIT2'].text[0]
+                if 'TPE1' in tags:  # artist
+                    self.artist = tags['TPE1'].text[0]
+
+                if im is None:
+                    if "APIC:" in tags:
+                        im = Image.open(BytesIO(tags["APIC:"].data))
+
+            elif ext == "m4a" or ext == "m4b" or ext == "mp4" or ext == "m4p":
+                # title: ©nam (\xa9nam)
+                # artist: ©ART
+                # album: ©alb
+                # cover artwork: covr
+                tags = mutagen.File(self.uri())
+                if '©nam' in tags:
+                    self.title = tags['©nam'][0]
+                if '©ART' in tags:  # artist
+                    self.artist = tags['©ART'][0]
+
+                if im is None:
+                    if "covr" in tags:
+                        im = Image.open(BytesIO(tags["covr"][0]))
+
+            if im:
+                self.thumbnail = self._prepare_thumbnail(im)
+        except:
+            pass
+
+        if not self.title:
+            self.title = os.path.basename(file_no_ext)
+
+    def _prepare_thumbnail(self, im):
+        im.thumbnail((100, 100), Image.ANTIALIAS)
+        buffer = BytesIO()
+        im = im.convert('RGB')
+        im.save(buffer, format="JPEG")
+        return base64.b64encode(buffer.getvalue()).decode('utf-8')
+
+    def to_dict(self):
+        dict = super().to_dict()
+        dict['type'] = 'file'
+        dict['path'] = self.path
+        dict['title'] = self.title
+        dict['artist'] = self.artist
+        dict['thumbnail'] = self.thumbnail
+        return dict
+
+    def format_debug_string(self):
+        return "[file] {artist} - {title} ({path})".format(
+            title=self.title,
+            artist=self.artist,
+            path=self.path
+        )
+
+    def format_song_string(self, user):
+        return constants.strings("file_item",
+                                    title=self.title,
+                                    artist=self.artist,
+                                    user=user
+                                    )
+
+    def format_current_playing(self, user):
+        display = constants.strings("now_playing", item=self.format_song_string(user))
+        if self.thumbnail:
+            thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
+                             self.thumbnail + '"/>'
+            display += "<br />" +  thumbnail_html
+
+        return display
+
+    def display_type(self):
+        return constants.strings("file")
diff --git a/media/item.py b/media/item.py
new file mode 100644 (file)
index 0000000..5e1b3b5
--- /dev/null
@@ -0,0 +1,98 @@
+import logging
+import threading
+import os
+import re
+from io import BytesIO
+import base64
+import hashlib
+import mutagen
+from PIL import Image
+
+import util
+import variables as var
+
+"""
+FORMAT OF A MUSIC INTO THE PLAYLIST
+type : url
+    id
+    url
+    title
+    path
+    duration
+    artist
+    thumbnail
+    user
+    ready (validation, no, downloading, yes, failed)
+    from_playlist (yes,no)
+    playlist_title
+    playlist_url
+
+type : radio
+    id
+    url
+    name
+    current_title
+    user
+
+"""
+
+class BaseItem:
+    def __init__(self, bot, from_dict=None):
+        self.bot = bot
+        self.log = logging.getLogger("bot")
+        self.type = "base"
+
+        if from_dict is None:
+            self.id = ""
+            self.ready = "pending" # pending - is_valid() -> validated - prepare() -> yes, failed
+        else:
+            self.id = from_dict['id']
+            self.ready = from_dict['ready']
+
+    def is_ready(self):
+        return True if self.ready == "yes" else False
+
+    def is_failed(self):
+        return True if self.ready == "failed" else False
+
+    def validate(self):
+        return False
+
+    def uri(self):
+        raise
+
+    def async_prepare(self):
+        th = threading.Thread(
+            target=self.prepare, name="Prepare-" + self.id[:7])
+        self.log.info(
+            "%s: start preparing item in thread: " % self.type + self.format_debug_string())
+        th.daemon = True
+        th.start()
+        #self.download_threads.append(th)
+        return th
+
+    def prepare(self):
+        return True
+
+    def play(self):
+        pass
+
+    def format_song_string(self, user):
+        return self.id
+
+    def format_current_playing(self, user):
+        return self.id
+
+    def format_debug_string(self):
+        return self.id
+
+    def display_type(self):
+        return ""
+
+    def send_client_message(self, msg):
+        self.bot.send_msg(msg)
+
+    def to_dict(self):
+        return {"type" : "base", "id": self.id, "ready": self.ready}
+
+
index 78cb870cfbc36bce212660ae9c041df9f05286b3..d2d17904a50b6d2aa4df17a6bfb438147be61404 100644 (file)
-import youtube_dl
+import json
+import random
+import hashlib
+import threading
+import logging
+
+import util
 import variables as var
+from media.item import BaseItem
+from media.file import FileItem
+from media.url import URLItem
+
+
+class PlaylistItemWrapper:
+    def __init__(self, item, user):
+        self.item = item
+        self.user = user
+
+    def to_dict(self):
+        dict = self.item.to_dict()
+        dict['user'] = self.user
+        return dict
+
+    def format_current_playing(self):
+        return self.item.format_current_playing(self.user)
+
+    def format_song_string(self):
+        return self.item.format_song_string(self.user)
+
+    def format_debug_string(self):
+        return self.item.format_debug_string()
+
+
+def dict_to_item(dict):
+    if dict['type'] == 'file':
+        return PlaylistItemWrapper(FileItem(var.bot, "", dict), dict['user'])
+    elif dict['type'] == 'url':
+        return PlaylistItemWrapper(URLItem(var.bot, "", dict), dict['user'])
+
+
+class PlayList(list):
+    def __init__(self, *args):
+        super().__init__(*args)
+        self.current_index = -1
+        self.version = 0  # increase by one after each change
+        self.mode = "one-shot"  # "repeat", "random"
+        self.pending_items = []
+        self.log = logging.getLogger("bot")
+        self.validating_thread_lock = threading.Lock()
+
+    def is_empty(self):
+        return True if len(self) == 0 else False
+
+    def set_mode(self, mode):
+        # modes are "one-shot", "repeat", "random"
+        self.mode = mode
+
+        if mode == "random":
+            self.randomize()
+
+        elif mode == "one-shot" and self.current_index > 0:
+            # remove items before current item
+            self.version += 1
+            for i in range(self.current_index):
+                super().__delitem__(0)
+            self.current_index = 0
+
+    def append(self, item: PlaylistItemWrapper):
+        self.version += 1
+        super().append(item)
+        self.pending_items.append(item)
+        self.start_async_validating()
+
+        return item
+
+    def insert(self, index, item):
+        self.version += 1
+
+        if index == -1:
+            index = self.current_index
+
+        item = util.attach_music_tag_info(item)
+        super().insert(index, item)
+
+        if index <= self.current_index:
+            self.current_index += 1
+
+        self.pending_items.append(item)
+        self.start_async_validating()
+
+        return item
+
+    def length(self):
+        return len(self)
+
+    def extend(self, items):
+        self.version += 1
+        items = list(map(
+            lambda item: item,
+            items))
+        super().extend(items)
+        self.pending_items.extend(items)
+        self.start_async_validating()
+        return items
+
+    def next(self):
+        if len(self) == 0:
+            return False
+
+        self.version += 1
+        #logging.debug("playlist: Next into the queue")
+
+        if self.current_index < len(self) - 1:
+            if self.mode == "one-shot" and self.current_index != -1:
+                super().__delitem__(self.current_index)
+            else:
+                self.current_index += 1
+
+            return self[self.current_index]
+        else:
+            self.current_index = 0
+            if self.mode == "one-shot":
+                self.clear()
+                return False
+            elif self.mode == "repeat":
+                return self[0]
+            elif self.mode == "random":
+                self.randomize()
+                return self[0]
+            else:
+                raise TypeError("Unknown playlist mode '%s'." % self.mode)
+
+    def point_to(self, index):
+        if -1 <= index < len(self):
+            self.current_index = index
+
+    def find(self, id):
+        for index, wrapper in enumerate(self):
+            if wrapper.item.id == id:
+                return index
+        return None
+
+    def update(self, item, id):
+        self.version += 1
+        index = self.find(id)
+        if index:
+            self[index] = item
+            return True
+        return False
+
+    def __delitem__(self, key):
+        return self.remove(key)
+
+    def remove(self, index=-1):
+        self.version += 1
+        if index > len(self) - 1:
+            return False
+
+        if index == -1:
+            index = self.current_index
+
+        removed = self[index]
+        super().__delitem__(index)
+
+        if self.current_index > index:
+            self.current_index -= 1
+
+        return removed
+
+    def remove_by_id(self, id):
+        to_be_removed = []
+        for index, item in enumerate(self):
+            if item.id == id:
+                to_be_removed.append(index)
+
+        for index in to_be_removed:
+            self.remove(index)
+
+    def current_item(self):
+        if len(self) == 0:
+            return False
+
+        return self[self.current_index]
+
+    def next_index(self):
+        if len(self) == 0 or (len(self) == 1 and self.mode == 'one_shot'):
+            return False
+
+        if self.current_index < len(self) - 1:
+            return self.current_index + 1
+        else:
+            return 0
+
+    def next_item(self):
+        if len(self) == 0 or (len(self) == 1 and self.mode == 'one_shot'):
+            return False
+
+        return self[self.next_index()]
+
+    def jump(self, index):
+        if self.mode == "one-shot":
+            for i in range(index):
+                super().__delitem__(0)
+            self.current_index = 0
+        else:
+            self.current_index = index
+
+        self.version += 1
+        return self[self.current_index]
+
+    def randomize(self):
+        # current_index will lose track after shuffling, thus we take current music out before shuffling
+        #current = self.current_item()
+        #del self[self.current_index]
+
+        random.shuffle(self)
+
+        #self.insert(0, current)
+        self.current_index = -1
+        self.version += 1
+
+    def clear(self):
+        self.version += 1
+        self.current_index = -1
+        super().clear()
+
+    def save(self):
+        var.db.remove_section("playlist_item")
+        var.db.set("playlist", "current_index", self.current_index)
+
+        for index, music in enumerate(self):
+            var.db.set("playlist_item", str(index), json.dumps(music.to_dict()))
+
+    def load(self):
+        current_index = var.db.getint("playlist", "current_index", fallback=-1)
+        if current_index == -1:
+            return
+
+        items = list(var.db.items("playlist_item"))
+        items.sort(key=lambda v: int(v[0]))
+        self.extend(list(map(lambda v: dict_to_item(json.loads(v[1])), items)))
+
+        self.current_index = current_index
+
+    def _debug_print(self):
+        print("===== Playlist(%d)=====" % self.current_index)
+        for index, item_wrapper in enumerate(self):
+            if index == self.current_index:
+                print("-> %d %s" % (index, item_wrapper.item.title))
+            else:
+                print("%d %s" % (index, item_wrapper.item.title))
+        print("=====     End     =====")
+
+    def start_async_validating(self):
+        if not self.validating_thread_lock.locked():
+            th = threading.Thread(target=self._check_valid, name="Validating")
+            th.daemon = True
+            th.start()
 
+    def _check_valid(self):
+        self.log.debug("playlist: start validating...")
+        self.validating_thread_lock.acquire()
+        while len(self.pending_items) > 0:
+            item = self.pending_items.pop().item
+            self.log.debug("playlist: validating %s" % item.format_debug_string())
+            if not item.validate() or item.ready == 'failed':
+                # TODO: logging
+                self.remove_by_id(item.id)
 
-def get_playlist_info(url, start_index=0, user=""):
-    items = []
-    ydl_opts = {
-        'extract_flat': 'in_playlist'
-    }
-    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
-        attempts = var.config.getint('bot', 'download_attempts', fallback=2)
-        for i in range(attempts):
-            try:
-                info = ydl.extract_info(url, download=False)
-                # # if url is not a playlist but a video
-                # if 'entries' not in info and 'webpage_url' in info:
-                #     music = {'type': 'url',
-                #              'title': info['title'],
-                #              'url': info['webpage_url'],
-                #              'user': user,
-                #              'ready': 'validation'}
-                #     items.append(music)
-                #     return items
-
-                playlist_title = info['title']
-                for j in range(start_index, min(len(info['entries']), start_index + var.config.getint('bot', 'max_track_playlist'))):
-                    # Unknow String if No title into the json
-                    title = info['entries'][j]['title'] if 'title' in info['entries'][j] else "Unknown Title"
-                    # Add youtube url if the url in the json isn't a full url
-                    url = info['entries'][j]['url'] if info['entries'][j]['url'][0:4] == 'http' else "https://www.youtube.com/watch?v=" + info['entries'][j]['url']
-
-                    music = {'type': 'url',
-                             'title': title,
-                             'url': url,
-                             'user': user,
-                             'from_playlist': True,
-                             'playlist_title': playlist_title,
-                             'playlist_url': url,
-                             'ready': 'validation'}
-                    items.append(music)
-            except:
-                pass
-
-    return items
+        self.log.debug("playlist: validating finished.")
+        self.validating_thread_lock.release()
index c4fc6487186b3de65e84ca11203ec0fd785952cc..ebc2a4cef63847350278be96c6b234ad5923bae2 100644 (file)
@@ -1,16 +1,19 @@
 import re
 import logging
-import json
-import http.client
 import struct
 import requests
 import traceback
+import hashlib
+
+from media.item import BaseItem
+import constants
 
 log = logging.getLogger("bot")
 
 def get_radio_server_description(url):
     global log
 
+    log.debug("radio: fetching radio server description")
     p = re.compile('(https?\:\/\/[^\/]*)', re.IGNORECASE)
     res = re.search(p, url)
     base_url = res.group(1)
@@ -50,6 +53,9 @@ def get_radio_server_description(url):
 
 
 def get_radio_title(url):
+    global log
+
+    log.debug("radio: fetching radio server description")
     try:
         r = requests.get(url, headers={'Icy-MetaData': '1'}, stream=True, timeout=5)
         icy_metaint_header = int(r.headers['icy-metaint'])
@@ -67,3 +73,57 @@ def get_radio_title(url):
     except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as e:
         pass
     return url
+
+class RadioItem(BaseItem):
+    def __init__(self, bot, url, name="", from_dict=None):
+        if from_dict is None:
+            super().__init__(bot)
+            self.url = url
+            if not name:
+                self.title = get_radio_server_description(self.url) # The title of the radio station
+            else:
+                self.title = name
+            self.id = hashlib.md5(url.encode()).hexdigest()
+        else:
+            super().__init__(bot, from_dict)
+            self.url = from_dict['url']
+            self.title = from_dict['title']
+
+        self.type = "radio"
+
+    def validate(self):
+        return True
+
+    def is_ready(self):
+        return True
+
+    def uri(self):
+        return self.url
+
+    def to_dict(self):
+        dict = super().to_dict()
+        dict['url'] = self.url
+        dict['title'] = self.title
+
+    def format_debug_string(self):
+        return "[radio] {name} ({url})".format(
+            name=self.title,
+            url=self.url
+        )
+
+    def format_song_string(self, user):
+        return constants.strings("radio_item",
+                                 url=self.url,
+                                 title=get_radio_title(self.url), # the title of current song
+                                 name=self.title, # the title of radio station
+                                 user=user
+                                 )
+
+    def format_current_playing(self, user):
+        return constants.strings("now_playing", item=self.format_song_string(user))
+
+    def display_type(self):
+        return constants.strings("radio")
+
+
+
index 830a313f4082c3776646411b118446eb223626bd..8fcdbd6c8e97780b6240637302884cfffc95b24b 100644 (file)
+import threading
+import logging
+import os
+import hashlib
+import traceback
+from PIL import Image
 import youtube_dl
+import glob
+
+import constants
+import media
 import variables as var
+from media.file import FileItem
+import media.system
+
+log = logging.getLogger("bot")
+
 
+class URLItem(FileItem):
+    def __init__(self, bot, url, from_dict=None):
+        self.validating_lock = threading.Lock()
+        if from_dict is None:
+            self.url = url
+            self.title = ''
+            self.duration = 0
+            self.ready = 'pending'
+            super().__init__(bot, "")
+            self.id = hashlib.md5(url.encode()).hexdigest()
+            path = var.tmp_folder + self.id + ".mp3"
 
-def get_url_info(music):
-    ydl_opts = {
-        'noplaylist': True
-    }
-    music['duration'] = 0
-    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
-        for i in range(2):
-            try:
-                info = ydl.extract_info(music['url'], download=False)
-                music['duration'] = info['duration'] / 60
-                music['title'] = info['title']
-            except youtube_dl.utils.DownloadError:
+            if os.path.isfile(path):
+                self.log.info("url: file existed for url %s " % self.url)
+                self.ready = 'yes'
+                self.path = path
+                self._get_info_from_tag()
+            else:
+                # self._get_info_from_url()
                 pass
-            except KeyError:
-                return music
+        else:
+            super().__init__(bot, "", from_dict)
+            self.url = from_dict['url']
+            self.duration = from_dict['duration']
+
+        self.downloading = False
+        self.type = "url"
+
+    def uri(self):
+        return self.path
+
+    def is_ready(self):
+        if self.downloading or self.ready != 'yes':
+            return False
+        if self.ready == 'yes' and not os.path.exists(self.path):
+            self.log.info(
+                "url: music file missed for %s" % self.format_debug_string())
+            self.ready = 'validated'
+            return False
+
+        return True
+
+    def validate(self):
+        if self.ready in ['yes', 'validated']:
+            return True
+
+        if os.path.exists(self.path):
+            self.ready = "yes"
+            return True
+
+        # avoid multiple process validating in the meantime
+        self.validating_lock.acquire()
+        info = self._get_info_from_url()
+        self.validating_lock.release()
+
+        if self.duration == 0 and not info:
+            return False
+
+        if self.duration > var.config.getint('bot', 'max_track_duration') != 0:
+            # Check the length, useful in case of playlist, it wasn't checked before)
+            log.info(
+                "url: " + self.url + " has a duration of " + str(self.duration) + " min -- too long")
+            self.send_client_message(constants.strings('too_long'))
+            return False
+        else:
+            self.ready = "validated"
+            return True
+
+    # Run in a other thread
+    def prepare(self):
+        if not self.downloading:
+            assert self.ready == 'validated'
+            return self._download()
+        else:
+            assert self.ready == 'yes'
+            return True
+
+    def _get_info_from_url(self):
+        self.log.info("url: fetching metadata of url %s " % self.url)
+        ydl_opts = {
+            'noplaylist': True
+        }
+        succeed = False
+        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
+            attempts = var.config.getint('bot', 'download_attempts', fallback=2)
+            for i in range(attempts):
+                try:
+                    info = ydl.extract_info(self.url, download=False)
+                    self.duration = info['duration'] / 60
+                    self.title = info['title']
+                    succeed = True
+                    return True
+                except youtube_dl.utils.DownloadError:
+                    pass
+
+        if not succeed:
+            self.ready = 'failed'
+            self.log.error("url: error while fetching info from the URL")
+            self.send_client_message(constants.strings('unable_download'))
+            return False
+
+    def _download(self):
+        media.system.clear_tmp_folder(var.tmp_folder, var.config.getint('bot', 'tmp_folder_max_size'))
+
+        self.downloading = True
+        base_path = var.tmp_folder + self.id
+        save_path = base_path + ".%(ext)s"
+        mp3_path = base_path + ".mp3"
+
+        # Download only if music is not existed
+        self.ready = "preparing"
+
+        self.log.info("bot: downloading url (%s) %s " % (self.title, self.url))
+        ydl_opts = ""
+
+        ydl_opts = {
+            'format': 'bestaudio/best',
+            'outtmpl': save_path,
+            'noplaylist': True,
+            'writethumbnail': True,
+            'updatetime': False,
+            'postprocessors': [{
+                'key': 'FFmpegExtractAudio',
+                'preferredcodec': 'mp3',
+                'preferredquality': '192'},
+                {'key': 'FFmpegMetadata'}]
+        }
+        # TODO
+        self.send_client_message(constants.strings('download_in_progress', item=self.url))
+
+        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
+            attempts = var.config.getint('bot', 'download_attempts', fallback=2)
+            download_succeed = False
+            for i in range(attempts):
+                self.log.info("bot: download attempts %d / %d" % (i+1, attempts))
+                try:
+                    info = ydl.extract_info(self.url)
+                    download_succeed = True
+                    break
+                except:
+                    error_traceback = traceback.format_exc().split("During")[0]
+                    error = error_traceback.rstrip().split("\n")[-1]
+                    self.log.error("bot: download failed with error:\n %s" % error)
+
+            if download_succeed:
+                self.path = mp3_path
+                self.ready = "yes"
+                self.log.info(
+                    "bot: finished downloading url (%s) %s, saved to %s." % (self.title, self.url, self.path))
+                self.downloading = False
+                return True
             else:
-                return music
-    return False
+                for f in glob.glob(base_path + "*"):
+                    os.remove(f)
+                self.send_client_message(constants.strings('unable_download'))
+                self.ready = "failed"
+                self.downloading = False
+                return False
+
+    def _read_thumbnail_from_file(self, path_thumbnail):
+        if os.path.isfile(path_thumbnail):
+            im = Image.open(path_thumbnail)
+            self.thumbnail = self._prepare_thumbnail(im)
+
+    def to_dict(self):
+        dict = super().to_dict()
+        dict['type'] = 'url'
+        dict['url'] = self.url
+        dict['duration'] = self.duration
+
+        return dict
+
+
+    def format_debug_string(self):
+        return "[url] {title} ({url})".format(
+            title=self.title,
+            url=self.url
+        )
+
+    def format_song_string(self, user):
+        return constants.strings("url_item",
+                                    title=self.title,
+                                    url=self.url,
+                                    user=user)
+
+    def format_current_playing(self, user):
+        display = constants.strings("now_playing", item=self.format_song_string(user))
+
+        if self.thumbnail:
+            thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
+                             self.thumbnail + '"/>'
+            display += "<br />" +  thumbnail_html
+
+        return display
+
+    def display_type(self):
+        return constants.strings("url")
diff --git a/media/url_from_playlist.py b/media/url_from_playlist.py
new file mode 100644 (file)
index 0000000..33b0cdd
--- /dev/null
@@ -0,0 +1,99 @@
+import youtube_dl
+import constants
+import media
+import variables as var
+from media.url import URLItem
+from media.playlist import PlaylistItemWrapper
+
+def get_playlist_info(bot, url, start_index=0, user=""):
+    items = []
+    ydl_opts = {
+        'extract_flat': 'in_playlist'
+    }
+    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
+        attempts = var.config.getint('bot', 'download_attempts', fallback=2)
+        for i in range(attempts):
+            try:
+                info = ydl.extract_info(url, download=False)
+                # # if url is not a playlist but a video
+                # if 'entries' not in info and 'webpage_url' in info:
+                #     music = {'type': 'url',
+                #              'title': info['title'],
+                #              'url': info['webpage_url'],
+                #              'user': user,
+                #              'ready': 'validation'}
+                #     items.append(music)
+                #     return items
+
+                playlist_title = info['title']
+                for j in range(start_index, min(len(info['entries']),
+                                                start_index + var.config.getint('bot', 'max_track_playlist'))):
+                    # Unknow String if No title into the json
+                    title = info['entries'][j]['title'] if 'title' in info['entries'][j] else "Unknown Title"
+                    # Add youtube url if the url in the json isn't a full url
+                    item_url = info['entries'][j]['url'] if info['entries'][j]['url'][0:4] == 'http' \
+                        else "https://www.youtube.com/watch?v=" + info['entries'][j]['url']
+
+                    music = PlaylistItemWrapper(
+                        URLFromPlaylistItem(
+                            bot,
+                            item_url,
+                            title,
+                            url,
+                            playlist_title
+                        ), user)
+
+                    items.append(music)
+            except:
+                pass
+
+    return items
+
+class URLFromPlaylistItem(URLItem):
+    def __init__(self, bot, url, title, playlist_url, playlist_title, from_dict=None):
+        if from_dict is None:
+            super().__init__(bot, url)
+            self.title = title
+            self.playlist_url = playlist_url
+            self.playlist_title = playlist_title
+        else:
+            super().__init__(bot, "", from_dict)
+            self.playlist_title = from_dict['playlist_title']
+            self.playlist_url = from_dict['playlist_url']
+
+        self.type = "url_from_playlist"
+
+    def to_dict(self):
+        dict = super().to_dict()
+        dict['playlist_url'] = self.playlist_url
+        dict['playlist_title'] = self.playlist_title
+
+        return dict
+
+    def format_debug_string(self):
+        return "[url] {title} ({url}) from playlist {playlist}".format(
+            title=self.title,
+            url=self.url,
+            playlist=self.playlist_title
+        )
+
+    def format_song_string(self, user):
+        return constants.strings("url_from_playlist_item",
+                                    title=self.title,
+                                    url=self.url,
+                                    playlist_url=self.playlist_url,
+                                    playlist=self.playlist_title,
+                                    user=user)
+
+    def format_current_playing(self, user):
+        display = constants.strings("now_playing", item=self.format_song_string(user))
+
+        if self.thumbnail:
+            thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
+                             self.thumbnail + '"/>'
+            display += "<br />" +  thumbnail_html
+
+        return display
+
+    def display_type(self):
+        return constants.strings("url_from_playlist")
index 54cdd1954d439be1752c033e06735ec9173c31a1..67a326cead9959ff61fa0a9d1aaf14e6fe0a317f 100644 (file)
@@ -5,7 +5,6 @@ import threading
 import time
 import sys
 import math
-import re
 import signal
 import configparser
 import audioop
@@ -28,11 +27,9 @@ import constants
 from database import Database
 import media.url
 import media.file
-import media.playlist
 import media.radio
 import media.system
-from librb import radiobrowser
-from playlist import PlayList
+from media.playlist import PlayList
 
 
 class MumbleBot:
@@ -71,6 +68,7 @@ class MumbleBot:
         self.thread = None
         self.thread_stderr = None
         self.is_pause = False
+        self.pause_at_id = ""
         self.playhead = -1
         self.song_start_at = -1
         #self.download_threads = []
@@ -221,7 +219,8 @@ class MumbleBot:
             self.log.info('bot: received command ' + command + ' - ' + parameter + ' by ' + user)
 
             # Anti stupid guy function
-            if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_other_channel_message') and self.mumble.users[text.actor]['channel_id'] != self.mumble.users.myself['channel_id']:
+            if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_other_channel_message') \
+                    and self.mumble.users[text.actor]['channel_id'] != self.mumble.users.myself['channel_id']:
                 self.mumble.users[text.actor].send_text_message(
                     constants.strings('not_in_my_channel'))
                 return
@@ -294,56 +293,18 @@ class MumbleBot:
     #   Launch and Download
     # =======================
 
-    def launch_music(self, index=-1):
-        uri = ""
-        music = None
+    def launch_music(self):
         if var.playlist.is_empty():
             return
+        assert self.wait_for_downloading == False
 
-        if index == -1:
-            music = var.playlist.current_item()
-        else:
-            music = var.playlist.jump(index)
-
-        self.wait_for_downloading = False
-
-        self.log.info("bot: play music " + util.format_debug_song_string(music))
-        if music["type"] == "url":
-            # Delete older music is the tmp folder is too big
-            media.system.clear_tmp_folder(var.tmp_folder, var.config.getint('bot', 'tmp_folder_max_size'))
-
-            if music['ready'] == 'downloading':
-                self.wait_for_downloading = True
-                self.log.info("bot: current music isn't ready, other thread is downloading.")
-                return
+        music_wrapper = var.playlist.current_item()
+        uri = music_wrapper.item.uri()
 
-            # Check if the music is ready to be played
-            if music["ready"] != "yes" or not os.path.exists(music['path']):
-                self.wait_for_downloading = True
-                self.log.info("bot: current music isn't ready, start downloading.")
-                self.async_download(index)
-                return
-
-            if music['ready'] == 'failed':
-                self.log.info("bot: removing music from the playlist: %s" % util.format_debug_song_string(music))
-                var.playlist.remove(index)
-                return
-            uri = music['path']
-
-        elif music["type"] == "file":
-            if not self.check_item_path_or_remove():
-                return
-            uri = var.music_folder + var.playlist.current_item()["path"]
-
-        elif music["type"] == "radio":
-            uri = music["url"]
-            if 'name' not in music:
-                self.log.info("bot: fetching radio server description")
-                title = media.radio.get_radio_server_description(uri)
-                music["name"] = title
+        self.log.info("bot: play music " + music_wrapper.item.format_debug_string())
 
         if var.config.getboolean('bot', 'announce_current_music'):
-            self.send_msg(util.format_current_playing())
+            self.send_msg(music_wrapper.format_current_playing())
 
         if var.config.getboolean('debug', 'ffmpeg'):
             ffmpeg_debug = "debug"
@@ -365,172 +326,22 @@ class MumbleBot:
         self.playhead = 0
         self.last_volume_cycle_time = time.time()
 
-    def validate_music(self, music):
-        url = music['url']
-
-        url_hash = hashlib.md5(url.encode()).hexdigest()
-
-        path = var.tmp_folder + url_hash + ".%(ext)s"
-        mp3 = path.replace(".%(ext)s", ".mp3")
-        music['path'] = mp3
-
-        # Download only if music is not existed
-        if os.path.isfile(mp3):
-            self.log.info("bot: file existed for url %s " % music['url'])
-            music['ready'] = 'yes'
-            return music
-
-        music = media.url.get_url_info(music)
-
-        self.log.info("bot: verifying the duration of url %s " % music['url'])
-
-        if music:
-            if music['duration'] > var.config.getint('bot', 'max_track_duration'):
-                # Check the length, useful in case of playlist, it wasn't checked before)
-                self.log.info(
-                    "the music " + music["url"] + " has a duration of " + str(music['duration']) + "s -- too long")
-                self.send_msg(constants.strings('too_long'))
-                return False
-            else:
-                music['ready'] = "no"
-
-            return music
-        else:
-            self.log.error("bot: error while fetching info from the URL")
-            self.send_msg(constants.strings('unable_download'))
-            return False
-
-    def download_music(self, index=-1):
-        if index == -1:
-            index = var.playlist.current_index
-        music = var.playlist[index]
-
-        if music['type'] != 'url':
-            # then no need to download
-            return music
-
-        self.download_in_progress = True
-
-        url = music['url']
-
-        url_hash = hashlib.md5(url.encode()).hexdigest()
-
-        path = var.tmp_folder + url_hash + ".%(ext)s"
-        mp3 = path.replace(".%(ext)s", ".mp3")
-        music['path'] = mp3
-
-        # Download only if music is not existed
-        if not os.path.isfile(mp3):
-            # download the music
-            music['ready'] = "downloading"
-            var.playlist.update(music, music['id'])
-
-            self.log.info("bot: downloading url (%s) %s " % (music['title'], url))
-            ydl_opts = ""
-
-            ydl_opts = {
-                'format': 'bestaudio/best',
-                'outtmpl': path,
-                'noplaylist': True,
-                'writethumbnail': True,
-                'updatetime': False,
-                'postprocessors': [{
-                    'key': 'FFmpegExtractAudio',
-                    'preferredcodec': 'mp3',
-                    'preferredquality': '192'},
-                    {'key': 'FFmpegMetadata'}]
-            }
-            self.send_msg(constants.strings('download_in_progress', item=music['title']))
-
-            with youtube_dl.YoutubeDL(ydl_opts) as ydl:
-                attempts = var.config.getint('bot', 'download_attempts', fallback=2)
-                download_succeed = False
-                for i in range(attempts):
-                    self.log.info("bot: download attempts %d / %d" % (i+1, attempts))
-                    try:
-                        ydl.extract_info(url)
-                        download_succeed = True
-                        break
-                    except:
-                        error_traceback = traceback.format_exc().split("During")[0]
-                        error = error_traceback.rstrip().split("\n")[-1]
-                        self.log.error("bot: download failed with error:\n %s" % error)
-
-                if download_succeed:
-                    music['ready'] = "yes"
-                    self.log.info(
-                        "bot: finished downloading url (%s) %s, saved to %s." % (music['title'], url, music['path']))
-                else:
-                    for f in [mp3, path.replace(".%(ext)s", ".jpg"), path.replace(".%(ext)s", ".m4a")]:
-                        if os.path.exists(f):
-                            os.remove(f)
-                    self.send_msg(constants.strings('unable_download'))
-                    music['ready'] = "failed"
-        else:
-            self.log.info("bot: music file existed, skip downloading " + mp3)
-            music['ready'] = "yes"
-
-        music = util.attach_music_tag_info(music)
-
-        var.playlist.update(music, music['id'])
-        self.download_in_progress = False
-        return music
-
     def async_download_next(self):
         # Function start if the next music isn't ready
         # Do nothing in case the next music is already downloaded
         self.log.debug("bot: Async download next asked ")
-        if var.playlist.next_item() and var.playlist.next_item()['type'] == 'url':
+        while var.playlist.next_item() and var.playlist.next_item().item.type == 'url':
             # usually, all validation will be done when adding to the list.
             # however, for performance consideration, youtube playlist won't be validate when added.
             # the validation has to be done here.
-            while var.playlist.next_item() and var.playlist.next_item()['ready'] == "validation":
-                music = self.validate_music(var.playlist.next_item())
-                if music:
-                    var.playlist.update(music, music['id'])
-                    break
-                else:
-                    var.playlist.remove(var.playlist.next_index())
+            next = var.playlist.next_item().item
+            if next.validate():
+                if not next.is_ready():
+                    next.async_prepare()
+                break
+            else:
+                var.playlist.remove_by_id(next.id)
 
-            if var.playlist.next_item() and var.playlist.next_item()['ready'] == "no":
-                self.async_download(var.playlist.next_index())
-
-    def async_download(self, index):
-        th = threading.Thread(
-            target=self.download_music, name="DownloadThread-" + var.playlist[index]['id'][:5], args=(index,))
-        self.log.info(
-            "bot: start downloading item in thread: " + util.format_debug_song_string(var.playlist[index]))
-        th.daemon = True
-        th.start()
-        #self.download_threads.append(th)
-        return th
-
-    def check_item_path_or_remove(self, index = -1):
-        if index == -1:
-            index = var.playlist.current_index
-        music = var.playlist[index]
-
-        if music['type'] == 'radio':
-            return True
-
-        if not 'path' in music:
-            return False
-        else:
-            if music["type"] == "url":
-                uri = music['path']
-                if not os.path.exists(uri):
-                    music['ready'] = 'validation'
-                    return False
-
-            elif music["type"] == "file":
-                uri = var.music_folder + music["path"]
-                if not os.path.exists(uri):
-                    self.log.info("bot: music file missed. removing music from the playlist: %s" % util.format_debug_song_string(music))
-                    self.send_msg(constants.strings('file_missed', file=music["path"]))
-                    var.playlist.remove(index)
-                    return False
-
-        return True
 
     # =======================
     #          Loop
@@ -577,17 +388,30 @@ class MumbleBot:
                 # ffmpeg thread has gone. indicate that last song has finished. move to the next song.
                 if not self.wait_for_downloading:
                     if var.playlist.next():
-                    # if downloading in the other thread
-                        self.launch_music()
-                        self.async_download_next()
+                        current = var.playlist.current_item().item
+                        if current.validate():
+                            print("validate")
+                            if current.is_ready():
+                                print("ready")
+                                self.launch_music()
+                                self.async_download_next()
+                            else:
+                                self.log.info("bot: current music isn't ready, start downloading.")
+                                self.wait_for_downloading = True
+                                current.async_prepare()
+                        else:
+                            var.playlist.remove_by_id(current.id)
                     else:
                         self._loop_status = 'Empty queue'
                 else:
-                    if var.playlist.current_item():
-                        if var.playlist.current_item()["ready"] != "downloading":
+                    current = var.playlist.current_item().item
+                    if current:
+                        if current.is_ready():
                             self.wait_for_downloading = False
                             self.launch_music()
                             self.async_download_next()
+                        elif current.is_failed():
+                            var.playlist.remove_by_id(current.id)
                         else:
                             self._loop_status = 'Wait for downloading'
                     else:
@@ -666,6 +490,7 @@ class MumbleBot:
     def pause(self):
         # Kill the ffmpeg thread
         if self.thread:
+            self.pause_at_id = var.playlist.current_item().item.id
             self.thread.kill()
             self.thread = None
         self.is_pause = True
@@ -678,10 +503,10 @@ class MumbleBot:
         if var.playlist.current_index == -1:
             var.playlist.next()
 
-        music = var.playlist.current_item()
+        music_wrapper = var.playlist.current_item()
 
-        if music['type'] == 'radio' or self.playhead == 0 or not self.check_item_path_or_remove():
-            self.launch_music()
+        if not music_wrapper or not music_wrapper.item.id == self.pause_at_id or not music_wrapper.item.is_ready():
+            self.playhead = 0
             return
 
         if var.config.getboolean('debug', 'ffmpeg'):
@@ -691,12 +516,7 @@ class MumbleBot:
 
         self.log.info("bot: resume music at %.2f seconds" % self.playhead)
 
-        uri = ""
-        if music["type"] == "url":
-            uri = music['path']
-
-        elif music["type"] == "file":
-            uri = var.music_folder + var.playlist.current_item()["path"]
+        uri = music_wrapper.item.uri()
 
         command = ("ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-ss', "%f" % self.playhead, '-i',
                    uri, '-ac', '1', '-f', 's16le', '-ar', '48000', '-')
@@ -713,6 +533,7 @@ class MumbleBot:
         self.thread_stderr = os.fdopen(pipe_rd)
         self.thread = sp.Popen(command, stdout=sp.PIPE, stderr=pipe_wd, bufsize=480)
         self.last_volume_cycle_time = time.time()
+        self.pause_at_id = ""
 
 
     # TODO: this is a temporary workaround for issue #44 of pymumble.
@@ -808,8 +629,8 @@ if __name__ == '__main__':
     var.bot_logger = bot_logger
 
     var.playlist = PlayList() # playlist should be initialized after the database
-    var.botamusique = MumbleBot(args)
-    command.register_all_commands(var.botamusique)
+    var.bot = MumbleBot(args)
+    command.register_all_commands(var.bot)
 
     # load playlist
     if var.config.getboolean('bot', 'save_playlist', fallback=True):
@@ -827,4 +648,4 @@ if __name__ == '__main__':
         var.playlist.set_mode(playback_mode)
 
     # Start the main loop.
-    var.botamusique.loop()
+    var.bot.loop()
diff --git a/playlist.py b/playlist.py
deleted file mode 100644 (file)
index 8986399..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-import json
-import random
-import hashlib
-
-import util
-import variables as var
-
-"""
-FORMAT OF A MUSIC INTO THE PLAYLIST
-type : url
-    id
-    url
-    title
-    path
-    duration
-    artist
-    thumbnail
-    user
-    ready (validation, no, downloading, yes, failed)
-    from_playlist (yes,no)
-    playlist_title
-    playlist_url
-
-type : radio
-    id
-    url
-    name
-    current_title
-    user
-
-type : file
-    id
-    path
-    title
-    artist
-    duration
-    thumbnail
-    user
-"""
-
-
-class PlayList(list):
-    current_index = -1
-    version = 0 # increase by one after each change
-    mode = "one-shot" # "repeat", "random"
-
-
-    def __init__(self, *args):
-        super().__init__(*args)
-
-    def is_empty(self):
-        return True if len(self) == 0 else False
-
-    def set_mode(self, mode):
-        # modes are "one-shot", "repeat", "random"
-        self.mode = mode
-
-        if mode == "random":
-            self.randomize()
-
-        elif mode == "one-shot" and self.current_index > 0:
-            # remove items before current item
-            self.version += 1
-            for i in range(self.current_index):
-                super().__delitem__(0)
-            self.current_index = 0
-
-    def append(self, item):
-        self.version += 1
-        item = util.attach_music_tag_info(item)
-        super().append(item)
-
-        return item
-
-    def insert(self, index, item):
-        self.version += 1
-
-        if index == -1:
-            index = self.current_index
-
-        item = util.attach_music_tag_info(item)
-        super().insert(index, item)
-
-        if index <= self.current_index:
-            self.current_index += 1
-
-        return item
-
-    def length(self):
-        return len(self)
-
-    def extend(self, items):
-        self.version += 1
-        items = list(map(
-            lambda item: util.attach_music_tag_info(item),
-            items))
-        super().extend(items)
-        return items
-
-    def next(self):
-        if len(self) == 0:
-            return False
-
-        self.version += 1
-        #logging.debug("playlist: Next into the queue")
-
-        if self.current_index < len(self) - 1:
-            if self.mode == "one-shot" and self.current_index != -1:
-                super().__delitem__(self.current_index)
-            else:
-                self.current_index += 1
-
-            return self[self.current_index]
-        else:
-            self.current_index = 0
-            if self.mode == "one-shot":
-                self.clear()
-                return False
-            elif self.mode == "repeat":
-                return self[0]
-            elif self.mode == "random":
-                self.randomize()
-                return self[0]
-            else:
-                raise TypeError("Unknown playlist mode '%s'." % self.mode)
-
-    def find(self, id):
-        for index, item in enumerate(self):
-            if item['id'] == id:
-                return index
-        return None
-
-    def update(self, item, id):
-        self.version += 1
-        index = self.find(id)
-        if index:
-            self[index] = item
-            return True
-        return False
-
-    def __delitem__(self, key):
-        return self.remove(key)
-
-    def remove(self, index=-1):
-        self.version += 1
-        if index > len(self) - 1:
-            return False
-
-        if index == -1:
-            index = self.current_index
-
-        removed = self[index]
-        super().__delitem__(index)
-
-        if self.current_index > index:
-            self.current_index -= 1
-
-        return removed
-
-    def current_item(self):
-        if len(self) == 0:
-            return False
-
-        return self[self.current_index]
-
-    def current_item_downloading(self):
-        if len(self) == 0:
-            return False
-
-        if self[self.current_index]['type'] == 'url' and self[self.current_index]['ready'] == 'downloading':
-            return True
-        return False
-
-    def next_index(self):
-        if len(self) == 0 or (len(self) == 1 and self.mode == 'one_shot'):
-            return False
-
-        if self.current_index < len(self) - 1:
-            return self.current_index + 1
-        else:
-            return 0
-
-    def next_item(self):
-        if len(self) == 0 or (len(self) == 1 and self.mode == 'one_shot'):
-            return False
-
-        return self[self.next_index()]
-
-    def jump(self, index):
-        if self.mode == "one-shot":
-            for i in range(index):
-                super().__delitem__(0)
-            self.current_index = 0
-        else:
-            self.current_index = index
-
-        self.version += 1
-        return self[self.current_index]
-
-    def randomize(self):
-        # current_index will lose track after shuffling, thus we take current music out before shuffling
-        #current = self.current_item()
-        #del self[self.current_index]
-
-        random.shuffle(self)
-
-        #self.insert(0, current)
-        self.current_index = -1
-        self.version += 1
-
-    def clear(self):
-        self.version += 1
-        self.current_index = -1
-        super().clear()
-
-    def save(self):
-        var.db.remove_section("playlist_item")
-        var.db.set("playlist", "current_index", self.current_index)
-
-        for index, music in enumerate(self):
-            if music['type'] == 'url' and music['ready'] == 'downloading':
-                music['ready'] = 'no'
-
-            var.db.set("playlist_item", str(index), json.dumps(music))
-
-    def load(self):
-        current_index = var.db.getint("playlist", "current_index", fallback=-1)
-        if current_index == -1:
-            return
-
-        items = list(var.db.items("playlist_item"))
-        items.sort(key=lambda v: int(v[0]))
-        self.extend(list(map(lambda v: json.loads(v[1]), items)))
-
-        self.current_index = current_index
-
-    def _debug_print(self):
-        print("===== Playlist(%d) ====" % self.current_index)
-        for index, item in enumerate(self):
-            if index == self.current_index:
-                print("-> %d %s" % (index, item['title']))
-            else:
-                print("%d %s" % (index, item['title']))
-        print("=====      End     ====")
\ No newline at end of file
diff --git a/playlist.txt b/playlist.txt
deleted file mode 100644 (file)
index dca9629..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-type : url
-    url
-    title
-    path
-    duration
-    thundnail
-    user
-    ready (validation, no, downloading, yes)
-    from_playlist (yes,no)
-    playlist_title
-    playlist_url
-
-type : radio
-    url
-    name
-    current_title
-    user
-
-type : file
-    path
-    title
-    duration
-    user
-
index f0adb8c255665ef95c306407089d338e1870a480..b677ac941e7577d067a2b604176be83df8972d31 100644 (file)
     <th scope="row">{{ index + 1 }}</th>
     <td>
         <div class="playlist-title">
-            {% if 'thumbnail' in m %}
-                <img width="80" src="data:image/PNG;base64,{{ m['thumbnail'] }}"/>
+            {% if m.type != 'radio' and m.thumbnail %}
+                <img width="80" src="data:image/PNG;base64,{{ m.thumbnail }}"/>
                 {% else %}
                 <img width="80" src="static/image/unknown-album.png"/>
             {% endif %}
         </div>
         <div class="playlist-artwork">
-            {% if 'title' in m and m['title'].strip() %}
-                <b>{{ m['title']|truncate(45) }}</b>
-            {% elif 'url' in m %}
-                <b>{{ m['url']|truncate(45) }}</b>
+            {% if m.title.strip() %}
+                <b>{{ m.title|truncate(45) }}</b>
+            {% elif m.url %}
+                <b>{{ m.url|truncate(45) }}</b>
             {% endif %}
-            <span class="badge badge-secondary">{{ m['type'].capitalize() }}</span>
+            <span class="badge badge-secondary">{{ m.display_type() }}</span>
             <br>
-            {% if 'artist' in m %}
-                {{ m['artist'] }}
+            {% if m.type == 'file' %}
+                {{ m.artist }}
+            {% elif m.type == 'url_from_playlist' %}
+                <a href="{{ m.playlist_url }}"><i>{{ m.playlist_title|truncate(50) }}</i></a>
             {% else %}
                 Unknown Artist
             {% endif %}
         </div>
     </td>
     <td>
-        {% if 'url' in m %}
-            <small><a href="{{ m['url'] }}"><i>{{ m['url']|truncate(50) }}</i></a></small>
-        {% elif 'path' in m %}
-        <small>{{ m['path']|truncate(50) }}</small>
+        {% if m.type == 'url' or m.type == 'radio' or m.type == 'url_from_playlist' %}
+            <small><a href="{{ m.url }}"><i>{{ m.url|truncate(50) }}</i></a></small>
+        {% elif m.type == 'file' %}
+        <small>{{ m.path|truncate(50) }}</small>
         {% endif %}
     </td>
     <td>
diff --git a/util.py b/util.py
index a9886372567871388589e59f2eed6cad8555d8f7..da1f7faab5462007cc1e336cf316d01f6ff8d480 100644 (file)
--- a/util.py
+++ b/util.py
@@ -62,181 +62,6 @@ def get_recursive_file_list_sorted(path):
     filelist.sort()
     return filelist
 
-
-def get_music_path(music):
-    uri = ''
-    if music["type"] == "url":
-        uri = music['path']
-    elif music["type"] == "file":
-        uri = var.music_folder + music["path"]
-    elif music["type"] == "radio":
-        uri = music['url']
-
-    return uri
-
-def attach_item_id(item):
-    if item['type'] == 'url':
-        item['id'] = hashlib.md5(item['url'].encode()).hexdigest()
-    elif item['type'] == 'file':
-        item['id'] = hashlib.md5(item['path'].encode()).hexdigest()
-    elif item['type'] == 'radio':
-        item['id'] = hashlib.md5(item['url'].encode()).hexdigest()
-    return item
-
-def attach_music_tag_info(music):
-    music = attach_item_id(music)
-
-    if "path" in music:
-        uri = get_music_path(music)
-
-        if os.path.isfile(uri):
-            match = re.search("(.+)\.(.+)", uri)
-            if match is None:
-                return music
-
-            file_no_ext = match[1]
-            ext = match[2]
-
-            try:
-                im = None
-                path_thumbnail = file_no_ext + ".jpg"
-                if os.path.isfile(path_thumbnail):
-                    im = Image.open(path_thumbnail)
-
-                if ext == "mp3":
-                    # title: TIT2
-                    # artist: TPE1, TPE2
-                    # album: TALB
-                    # cover artwork: APIC:
-                    tags = mutagen.File(uri)
-                    if 'TIT2' in tags:
-                        music['title'] = tags['TIT2'].text[0]
-                    if 'TPE1' in tags:  # artist
-                        music['artist'] = tags['TPE1'].text[0]
-
-                    if im is None:
-                        if "APIC:" in tags:
-                            im = Image.open(BytesIO(tags["APIC:"].data))
-
-                elif ext == "m4a" or ext == "m4b" or ext == "mp4" or ext == "m4p":
-                    # title: ©nam (\xa9nam)
-                    # artist: ©ART
-                    # album: ©alb
-                    # cover artwork: covr
-                    tags = mutagen.File(uri)
-                    if '©nam' in tags:
-                        music['title'] = tags['©nam'][0]
-                    if '©ART' in tags:  # artist
-                        music['artist'] = tags['©ART'][0]
-
-                        if im is None:
-                            if "covr" in tags:
-                                im = Image.open(BytesIO(tags["covr"][0]))
-
-                if im:
-                    im.thumbnail((100, 100), Image.ANTIALIAS)
-                    buffer = BytesIO()
-                    im = im.convert('RGB')
-                    im.save(buffer, format="JPEG")
-                    music['thumbnail'] = base64.b64encode(buffer.getvalue()).decode('utf-8')
-            except:
-                pass
-    else:
-        uri = music['url']
-
-    # if nothing found
-    if 'title' not in music:
-        match = re.search("([^\.]+)\.?.*", os.path.basename(uri))
-        music['title'] = match[1]
-
-    return music
-
-
-def format_song_string(music):
-    display = ''
-    source = music["type"]
-    title = music["title"] if "title" in music else "Unknown title"
-    artist = music["artist"] if "artist" in music else "Unknown artist"
-
-    if source == "radio":
-        display = constants.strings("now_playing_radio",
-            url=music["url"],
-            title=media.radio.get_radio_title(music["url"]),
-            name=music["name"],
-            user=music["user"]
-        )
-    elif source == "url" and 'from_playlist' in music:
-        display = constants.strings("now_playing_from_playlist",
-                                    title=title,
-                                    url=music['url'],
-                                    playlist_url=music["playlist_url"],
-                                    playlist=music["playlist_title"],
-                                    user=music["user"]
-        )
-    elif source == "url":
-        display = constants.strings("now_playing_url",
-                                    title=title,
-                                    url=music["url"],
-                                    user=music["user"]
-        )
-    elif source == "file":
-        display = constants.strings("now_playing_file",
-                                    title=title,
-                                    artist=artist,
-                                    user=music["user"]
-        )
-
-    return display
-
-
-def format_debug_song_string(music):
-    display = ''
-    source = music["type"]
-    title = music["title"] if "title" in music else "??"
-    artist = music["artist"] if "artist" in music else "??"
-
-    if source == "radio":
-        display = "[radio] {name} ({url}) by {user}".format(
-            name=music["name"],
-            url=music["url"],
-            user=music["user"]
-        )
-    elif source == "url" and 'from_playlist' in music:
-        display = "[url] {title} ({url}) from playlist {playlist} by {user}".format(
-            title=title,
-            url=music["url"],
-            playlist=music["playlist_title"],
-            user=music["user"]
-        )
-    elif source == "url":
-        display = "[url] {title} ({url}) by {user}".format(
-            title=title,
-            url=music["url"],
-            user=music["user"]
-        )
-    elif source == "file":
-        display = "[file] {artist} - {title} ({path}) by {user}".format(
-            title=title,
-            artist=artist,
-            path=music["path"],
-            user=music["user"]
-        )
-
-    return display
-
-
-def format_current_playing():
-    music = var.playlist.current_item()
-    display = format_song_string(music)
-
-    if 'thumbnail' in music:
-        thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
-                         music['thumbnail'] + '"/>'
-        return display + "<br />" + thumbnail_html
-
-    return display
-
-
 # - zips all files of the given zippath (must be a directory)
 # - returns the absolute path of the created zip file
 # - zip file will be in the applications tmp folder (according to configuration)
index 473d99c804960e4a9ea3956c72c3abd081b9afa6..188aeb9b089e096e63bf7dd4ef7d10a05dd85cb5 100644 (file)
@@ -1,4 +1,4 @@
-botamusique = None
+bot = None
 playlist = None
 
 user = ""