Add more controls (#71)
* add more ducking command * fix current music command * provide more controls, like pause, resume, clear. * add more controls in the web interface * refactored and improved: 1. move get_music_tag_info to util, and 2. refined logic related to it. 3. now playlist will check for tag info thus update_music_tag_info is useless and was removed 4. add "add folder" feature to !file asked in #65, 5. fix bugs related to !file * truncate file list if too long * fixed several tiny bugs * fixed several tiny bugs continue * fixed several tiny bugs continue continue * fixed several tiny bugs continue**3 * fixed several tiny bugs continue**4 * added !filematch command to add files that match a regex pattern. * truncate long message * fix web interface delete file issue * refresh after delete file * add regex to listfile command * refactored command part, added partial match support for commands * organized * added random command * typo * typo * Fixed many bugs. * Added workaround for azlux/pymumble#44 to fix the memory leak. * changed logging style. * fixed bugs related to random and resume * fix now playing * fixed issue related to download * fixed issue related to download 2 * fixed thumbnail issue * fixed add url problem in web interface * REFACTORED, turned db.ini into sqlite3 database. * fixed remove song problem * fixed radio get title problem. auto download if tmp file is deleted * fixed current index not loaded from database * fixed: order of songs loaded from the database * fixed: some obscure bugs. beautified error of commands * added a workaround for TerryGeng/botamusique#1. * beautified * fixed: channel not loaded in the config * fixed: auto checked for updates * fixed: mysterious bug: sometimes "now playing" string cannot be properly displayed. The real reason is: do use <br />, do not use <br>. I tried hours to find out this. * chore: unified debug messages that refer to music items * feav: fetch ffmpeg stderr mentioned in #72, reformatted logs. * fix: async download not working * fix: async download not working, still * fix: async download not working, finished * feat: queue command: ▶current playing item◀ * feat: support more than one command prefix * chore: added some WARNINGs into default config file to avoid people to touch it. * refactor: packed all string contants into constants.py, just to avoid people messing it around. * refactor: required by azlux. Added a configuration.example.ini to keep people away from configuration.default.ini
This commit is contained in:
238
util.py
238
util.py
@ -1,15 +1,26 @@
|
||||
#!/usr/bin/python3
|
||||
# coding=utf-8
|
||||
|
||||
import hashlib
|
||||
import magic
|
||||
import os
|
||||
import sys
|
||||
import variables as var
|
||||
import constants
|
||||
import zipfile
|
||||
import urllib.request
|
||||
import mutagen
|
||||
import re
|
||||
import subprocess as sp
|
||||
import logging
|
||||
import youtube_dl
|
||||
from importlib import reload
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
from sys import platform
|
||||
import base64
|
||||
import media
|
||||
import media.radio
|
||||
|
||||
def get_recursive_filelist_sorted(path):
|
||||
filelist = []
|
||||
@ -35,6 +46,156 @@ def get_recursive_filelist_sorted(path):
|
||||
return filelist
|
||||
|
||||
|
||||
def get_music_tag_info(music, uri = ""):
|
||||
|
||||
if "path" in music:
|
||||
if not uri:
|
||||
uri = var.config.get('bot', 'music_folder') + music["path"]
|
||||
|
||||
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 = "[radio] {title} from {url} by {user}".format(
|
||||
title=media.radio.get_radio_title(music["url"]),
|
||||
url=music["url"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "url" and 'from_playlist' in music:
|
||||
display = "[url] {title} (from playlist <a href=\"{url}\">{playlist}</a>) by {user}".format(
|
||||
title=title,
|
||||
url=music["playlist_url"],
|
||||
playlist=music["playlist_title"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "url":
|
||||
display = "[url] <a href=\"{url}\">{title}</a> by {user}".format(
|
||||
title=title,
|
||||
url=music["url"],
|
||||
user=music["user"]
|
||||
)
|
||||
elif source == "file":
|
||||
display = "[file] {artist} - {title} by {user}".format(
|
||||
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] {url} by {user}".format(
|
||||
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)
|
||||
|
||||
thumbnail_html = ''
|
||||
if 'thumbnail' in music:
|
||||
thumbnail_html = '<img width="80" src="data:image/jpge;base64,' + \
|
||||
music['thumbnail'] + '"/>'
|
||||
|
||||
display = (constants.strings('now_playing', item=display, thumb=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)
|
||||
@ -70,52 +231,47 @@ def zipdir(zippath, zipname_prefix=None):
|
||||
return zipname
|
||||
|
||||
|
||||
def write_db():
|
||||
with open(var.dbfile, 'w') as f:
|
||||
var.db.write(f)
|
||||
|
||||
|
||||
def get_user_ban():
|
||||
res = "List of ban hash"
|
||||
for i in var.db.items("user_ban"):
|
||||
res += "<br/>" + i[0]
|
||||
return res
|
||||
|
||||
def new_release_version():
|
||||
v = int(urllib.request.urlopen(urllib.request.Request("https://azlux.fr/botamusique/version")).read())
|
||||
return v
|
||||
|
||||
def update(version):
|
||||
v = int(urllib.request.urlopen(urllib.request.Request("https://azlux.fr/botamusique/version")).read())
|
||||
v = new_release_version()
|
||||
if v > version:
|
||||
logging.info('New version, starting update')
|
||||
logging.info('update: new version, start updating...')
|
||||
tp = sp.check_output(['/usr/bin/env', 'bash', 'update.sh']).decode()
|
||||
logging.debug(tp)
|
||||
logging.info('Update pip librairies dependancies')
|
||||
logging.info('update: update pip librairies dependancies')
|
||||
tp = sp.check_output([var.config.get('bot', 'pip3_path'), 'install', '--upgrade', '-r', 'requirements.txt']).decode()
|
||||
msg = "New version installed"
|
||||
msg = "New version installed, please restart the bot."
|
||||
|
||||
else:
|
||||
logging.info('Starting update youtube-dl via pip3')
|
||||
logging.info('update: starting update youtube-dl via pip3')
|
||||
tp = sp.check_output([var.config.get('bot', 'pip3_path'), 'install', '--upgrade', 'youtube-dl']).decode()
|
||||
msg = ""
|
||||
if "Requirement already up-to-date" in tp:
|
||||
msg += "Youtube-dl is up-to-date"
|
||||
else:
|
||||
msg += "Update done : " + tp.split('Successfully installed')[1]
|
||||
msg += "Update done: " + tp.split('Successfully installed')[1]
|
||||
reload(youtube_dl)
|
||||
msg += "<br/> Youtube-dl reloaded"
|
||||
return msg
|
||||
|
||||
|
||||
def user_ban(user):
|
||||
var.db.set("user_ban", user, None)
|
||||
res = "User " + user + " banned"
|
||||
write_db()
|
||||
return res
|
||||
|
||||
|
||||
def user_unban(user):
|
||||
var.db.remove_option("user_ban", user)
|
||||
res = "Done"
|
||||
write_db()
|
||||
return res
|
||||
|
||||
|
||||
@ -129,16 +285,53 @@ def get_url_ban():
|
||||
def url_ban(url):
|
||||
var.db.set("url_ban", url, None)
|
||||
res = "url " + url + " banned"
|
||||
write_db()
|
||||
return res
|
||||
|
||||
|
||||
def url_unban(url):
|
||||
var.db.remove_option("url_ban", url)
|
||||
res = "Done"
|
||||
write_db()
|
||||
return res
|
||||
|
||||
def pipe_no_wait(pipefd):
|
||||
''' Used to fetch the STDERR of ffmpeg. pipefd is the file descriptor returned from os.pipe()'''
|
||||
if platform == "linux" or platform == "linux2" or platform == "darwin":
|
||||
import fcntl
|
||||
import os
|
||||
try:
|
||||
fl = fcntl.fcntl(pipefd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(pipefd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
except:
|
||||
print(sys.exc_info()[1])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
elif platform == "win32":
|
||||
# https://stackoverflow.com/questions/34504970/non-blocking-read-on-os-pipe-on-windows
|
||||
import msvcrt
|
||||
import os
|
||||
|
||||
from ctypes import windll, byref, wintypes, GetLastError, WinError
|
||||
from ctypes.wintypes import HANDLE, DWORD, POINTER, BOOL
|
||||
|
||||
LPDWORD = POINTER(DWORD)
|
||||
PIPE_NOWAIT = wintypes.DWORD(0x00000001)
|
||||
ERROR_NO_DATA = 232
|
||||
|
||||
SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
|
||||
SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
|
||||
SetNamedPipeHandleState.restype = BOOL
|
||||
|
||||
h = msvcrt.get_osfhandle(pipefd)
|
||||
|
||||
res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
|
||||
if res == 0:
|
||||
print(WinError())
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
class Dir(object):
|
||||
def __init__(self, path):
|
||||
@ -225,3 +418,16 @@ class Dir(object):
|
||||
val.render_text(ident + 1)
|
||||
for file in self.files:
|
||||
print('{}{}'.format(' ' * (ident + 1) * 4, file))
|
||||
|
||||
|
||||
# Parse the html from the message to get the URL
|
||||
|
||||
def get_url_from_input(string):
|
||||
if string.startswith('http'):
|
||||
return string
|
||||
p = re.compile('href="(.+?)"', re.IGNORECASE)
|
||||
res = re.search(p, string)
|
||||
if res:
|
||||
return res.group(1)
|
||||
else:
|
||||
return False
|
Reference in New Issue
Block a user