557 line
16 KiB
Python
Executable file
557 line
16 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
#Playlistalo - simpatico script che legge le cartelle e genera la playlist
|
|
#Requirements: youtube_dl Mastodon.py python-telegram-bot validators python-musicpd
|
|
|
|
import youtube_dl
|
|
import shutil
|
|
import sys
|
|
import re
|
|
import os
|
|
import validators
|
|
from glob import glob
|
|
import json
|
|
import time
|
|
import subprocess
|
|
import random
|
|
import configparser
|
|
import hashlib
|
|
|
|
from musicpd import MPDClient
|
|
|
|
from telegram.ext import Updater, MessageHandler, Filters
|
|
from mastodon import Mastodon, StreamListener
|
|
|
|
scriptpath = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
#Crea le cartelle
|
|
os.makedirs("playlist", exist_ok=True)
|
|
os.makedirs("fallback", exist_ok=True)
|
|
|
|
SHUFFLEUSERS = False
|
|
SHUFFLESONGS = False
|
|
SHUFFLEFALLBACK = False
|
|
ARCHIVE = True
|
|
TELEGRAM_TOKEN = ""
|
|
MASTODON_TOKEN = ""
|
|
MASTODON_URL = ""
|
|
ANNOUNCEREPEAT = 4
|
|
|
|
#Scrivi la prima configurazione
|
|
configfile = 'playlistalo.conf'
|
|
if not os.path.exists(configfile):
|
|
config = configparser.ConfigParser()
|
|
config['playlistalo'] = {'ShuffleUsers': SHUFFLEUSERS, 'ShuffleSongs': SHUFFLESONGS, 'ShuffleFallback': SHUFFLEFALLBACK, 'Archive': ARCHIVE, 'Telegram_token': TELEGRAM_TOKEN, 'Mastodon_token': MASTODON_TOKEN, 'Mastodon_url': MASTODON_URL}
|
|
with open(configfile, 'w') as f:
|
|
config.write(f)
|
|
|
|
#Leggi la configurazione
|
|
config = configparser.ConfigParser()
|
|
config.read(configfile)
|
|
playlistaloconf = config['playlistalo']
|
|
SHUFFLEUSERS = playlistaloconf.getboolean('ShuffleUsers', SHUFFLEUSERS)
|
|
SHUFFLESONGS = playlistaloconf.getboolean('ShuffleSongs', SHUFFLESONGS)
|
|
SHUFFLEFALLBACK = playlistaloconf.getboolean('ShuffleFallback', SHUFFLEFALLBACK)
|
|
ARCHIVE = playlistaloconf.getboolean('Archive', ARCHIVE)
|
|
TELEGRAM_TOKEN = playlistaloconf.get('Telegram_token', TELEGRAM_TOKEN)
|
|
MASTODON_TOKEN = playlistaloconf.get('Mastodon_token',MASTODON_TOKEN)
|
|
MASTODON_URL = playlistaloconf.get('Mastodon_url', MASTODON_URL)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def addurl(url, user = "-unknown-"):
|
|
#print ('--- Inizio ---')
|
|
|
|
os.makedirs("cache", exist_ok=True)
|
|
|
|
ydl_opts = {
|
|
'format': 'bestaudio[ext=m4a]',
|
|
'outtmpl': 'cache/%(id)s.m4a',
|
|
'no-cache-dir': True,
|
|
'noplaylist': True,
|
|
'quiet': True,
|
|
}
|
|
|
|
url = url.strip()
|
|
print ("url: " + url)
|
|
print ("user: " + user)
|
|
|
|
if not validators.url(url):
|
|
print ('--- URL malformato ---')
|
|
return ("Err: url non valido")
|
|
|
|
|
|
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
|
try:
|
|
meta = ydl.extract_info(url, download = False)
|
|
except youtube_dl.DownloadError as detail:
|
|
print ('--- Errore video non disponibile ---')
|
|
print(str(detail))
|
|
return ("Err: " + str(detail))
|
|
|
|
id = meta.get('id').strip()
|
|
title = __normalizetext(meta.get('title'))
|
|
|
|
print ('id: %s' %(id))
|
|
print ('title: %s' %(title))
|
|
|
|
#scrivo il json
|
|
with open(os.path.join("cache", id + ".json"), 'w') as outfile:
|
|
json.dump(meta, outfile, indent=4)
|
|
|
|
#ho letto le info, ora controllo se il file esiste altrimenti lo scarico
|
|
#miglioria: controllare se upload_date e' uguale a quella del json gia' esistente
|
|
filetemp = os.path.join("cache", id + ".m4a")
|
|
if not glob(filetemp):
|
|
print ('--- Scarico ---')
|
|
ydl.download([url]) #non ho capito perche' ma senza [] fa un carattere per volta
|
|
|
|
if not os.path.isfile(filetemp):
|
|
return("Err: file non scaricato")
|
|
|
|
return(add(filetemp, user, title, id))
|
|
|
|
|
|
def md5(fname):
|
|
hash_md5 = hashlib.md5()
|
|
with open(fname, "rb") as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
hash_md5.update(chunk)
|
|
return hash_md5.hexdigest()
|
|
|
|
def add(filetemp, user = "-unknown-", title = None, id = None):
|
|
if not id:
|
|
id = md5(filetemp)
|
|
if not title:
|
|
title = os.path.splitext(os.path.basename(filetemp))[0]
|
|
|
|
#se il file esiste gia' in playlist salto (potrebbe esserci, anche rinominato)
|
|
if glob("playlist/**/*|" + id + ".*"):
|
|
print ('--- File già presente ---')
|
|
return ("Err: %s [%s] già presente" %(title, id))
|
|
|
|
os.makedirs("playlist/" + user, exist_ok=True)
|
|
#qui compone il nome del file
|
|
if SHUFFLESONGS:
|
|
fileout = str(random.randrange(10**6)).zfill(14) + "|" + title + "|" + id + ".m4a"
|
|
else:
|
|
fileout = time.strftime("%Y%m%d%H%M%S") + "|" + title + "|" + id + ".m4a"
|
|
fileout = os.path.join("playlist/" + user, fileout)
|
|
|
|
print ('--- Converto ---')
|
|
print (fileout)
|
|
subprocess.call([scriptpath + "/trimaudio.sh", filetemp, fileout])
|
|
if not os.path.isfile(fileout):
|
|
return("Err: file non convertito")
|
|
|
|
if ARCHIVE:
|
|
os.makedirs("archive", exist_ok=True)
|
|
if not glob("archive/*|" + id + ".*"):
|
|
shutil.copy2(fileout, "archive")
|
|
|
|
#cerca la posizione del pezzo appena inserito
|
|
pos = getposition(fileout)
|
|
|
|
print ('--- Fine ---')
|
|
print ("")
|
|
|
|
return ("OK: %s [%s] aggiunto alla playlist in posizione #%s" %(title, id, pos))
|
|
|
|
|
|
|
|
|
|
|
|
def __normalizetext(s):
|
|
if s is None:
|
|
return None
|
|
else:
|
|
s = re.sub(r'[\\|/|:|*|?|"|<|>|\|]',r'',s)
|
|
s = " ".join(s.split())
|
|
return s
|
|
|
|
|
|
def listplaylist():
|
|
pl = []
|
|
pl2 = []
|
|
for udir in sorted(glob("playlist/*/")):
|
|
#print (udir)
|
|
user = os.path.basename(os.path.dirname(udir))
|
|
|
|
#cerca il file last
|
|
last = ""
|
|
if os.path.exists(udir + "/last"):
|
|
f = open(udir + "/last", "r")
|
|
last = f.readline().rstrip()
|
|
else:
|
|
files = [x for x in sorted(glob(udir + "/*")) if not os.path.basename(x) == "last"]
|
|
if files:
|
|
last = os.path.basename(files[0]).split("|")[0]
|
|
#print ("LAST: " + last)
|
|
|
|
#leggi i file nella cartella
|
|
files = [x for x in sorted(glob(udir + "/*")) if not os.path.basename(x) == "last"]
|
|
seq = 0
|
|
for file in files:
|
|
bn = os.path.splitext(os.path.basename(file))[0]
|
|
#print ("BASENAME: " + bn)
|
|
seq = seq + 1
|
|
dat = bn.split("|")[0]
|
|
nam = bn.split("|")[1]
|
|
cod = bn.split("|")[2]
|
|
key = "-".join([str(seq).zfill(5), last, dat])
|
|
#print ("KEY: " + key)
|
|
|
|
plsong = [key, file, user, nam, cod]
|
|
pl.append(plsong)
|
|
pl.sort()
|
|
#rimuove la prima colonna, che serve solo per l'ordinamento
|
|
pl2 = [x[1:] for x in pl]
|
|
|
|
#print (pl)
|
|
#print ('\n'.join([", ".join(x) for x in pl]))
|
|
#print ('\n'.join([x[0] for x in pl]))
|
|
return pl2
|
|
|
|
def listfallback():
|
|
pl = []
|
|
pl2 = []
|
|
#leggi i file nella cartella
|
|
files = [x for x in sorted(glob("fallback/*")) if not os.path.basename(x) == "last"]
|
|
seq = 0
|
|
for file in files:
|
|
bn = os.path.splitext(os.path.basename(file))[0]
|
|
seq = seq + 1
|
|
dat = bn.split("|")[0]
|
|
nam = bn.split("|")[1]
|
|
cod = bn.split("|")[2]
|
|
key = "-".join([str(seq).zfill(5), dat])
|
|
|
|
plsong = [key, file, "fallback", nam, cod]
|
|
pl.append(plsong)
|
|
pl.sort()
|
|
#rimuove la prima colonna, che serve solo per l'ordinamento
|
|
pl2 = [x[1:] for x in pl]
|
|
return pl2
|
|
|
|
|
|
def playlocal():
|
|
if SHUFFLEUSERS:
|
|
shuffleusers()
|
|
if SHUFFLEFALLBACK:
|
|
shufflefallback()
|
|
|
|
while True:
|
|
plt = listtot(1)
|
|
if plt:
|
|
song = plt[0][0]
|
|
print(song)
|
|
#qui fa play
|
|
subprocess.call(["mplayer", "-nolirc", "-msglevel", "all=0:statusline=5", song])
|
|
consume(song)
|
|
|
|
def clean():
|
|
#cancella tutto dalla playlist
|
|
shutil.rmtree("playlist")
|
|
os.makedirs("playlist")
|
|
|
|
def shuffleusers():
|
|
#scrivere un numero casuale dentro a tutti i file last
|
|
for udir in sorted(glob("playlist/*/")):
|
|
#print (udir)
|
|
with open(udir + "/last", "w") as f:
|
|
f.write(str(random.randrange(10**6)).zfill(14))
|
|
|
|
def shufflefallback():
|
|
#rinominare con un numero casuale i file in fallback
|
|
files = [x for x in glob("fallback/*") if not os.path.basename(x) == "last"]
|
|
for file in files:
|
|
fname = str(random.randrange(10**6)).zfill(14) + "|" + "|".join(os.path.basename(file).split("|")[1:])
|
|
fname = os.path.dirname(file) + "/" + fname
|
|
os.rename(file, fname)
|
|
|
|
def getposition(file):
|
|
pl = listplaylist()
|
|
try:
|
|
return([x[0] for x in pl].index(file) + 1)
|
|
except:
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def telegram_msg_parser(bot, update):
|
|
print("Messaggio ricevuto")
|
|
msg = update.message.text
|
|
id = str(update.message.from_user.id)
|
|
username = update.message.from_user.username
|
|
|
|
urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', msg)
|
|
user = "t_" + "-".join([i for i in [id, username] if i])
|
|
#print (urls)
|
|
#print (user)
|
|
|
|
if not urls:
|
|
update.message.reply_text("Non ho trovato indirizzi validi...")
|
|
return()
|
|
|
|
update.message.reply_text("Messaggio ricevuto. Elaboro...")
|
|
|
|
for url in urls:
|
|
#update.message.reply_text("Scarico %s" %(url))
|
|
# start the download
|
|
dl = addurl(url, user)
|
|
update.message.reply_text(dl)
|
|
|
|
|
|
def telegram_bot():
|
|
print ("Bot avviato")
|
|
# Create the EventHandler and pass it your bot's token.
|
|
updater = Updater(TELEGRAM_TOKEN)
|
|
# Get the dispatcher to register handlers
|
|
dp = updater.dispatcher
|
|
# parse message
|
|
dp.add_handler(MessageHandler(Filters.text, telegram_msg_parser))
|
|
# Start the Bot
|
|
updater.start_polling()
|
|
# Run the bot until you press Ctrl-C
|
|
updater.idle()
|
|
|
|
|
|
class MastodonListener(StreamListener):
|
|
# andiamo a definire il metodo __init__, che prende una istanza di Mastodon come parametro opzionale e lo setta nella prop. self.mastodon
|
|
def __init__(self, mastodonInstance=None):
|
|
self.mastodon = mastodonInstance
|
|
def on_notification(self, notification):
|
|
print("Messaggio ricevuto")
|
|
#try:
|
|
msg = notification["status"]["content"]
|
|
id = str(notification["account"]["id"])
|
|
username = notification["account"]["acct"]
|
|
#except KeyError:
|
|
# return
|
|
|
|
msg = msg.replace("<br>", "\n")
|
|
msg = re.compile(r'<.*?>').sub('', msg)
|
|
|
|
urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', msg)
|
|
user = "m_" + "-".join([i for i in [id, username] if i])
|
|
#print (urls)
|
|
#print (user)
|
|
|
|
#visibility = notification['status']['visibility']
|
|
statusid = notification['status']['id']
|
|
if not urls:
|
|
self.mastodon.status_post("@" + username + " " + "Non ho trovato indirizzi validi...", in_reply_to_id = statusid, visibility="direct")
|
|
return()
|
|
|
|
self.mastodon.status_post("@" + username + " " + "Messaggio ricevuto. Elaboro...", in_reply_to_id = statusid, visibility="direct")
|
|
|
|
for url in urls:
|
|
# start the download
|
|
dl = addurl(url, user)
|
|
self.mastodon.status_post("@" + username + " " + dl, in_reply_to_id = statusid, visibility="direct")
|
|
|
|
|
|
def mastodon_bot():
|
|
print ("Bot avviato")
|
|
mastodon = Mastodon(access_token = MASTODON_TOKEN, api_base_url = MASTODON_URL)
|
|
listener = MastodonListener(mastodon)
|
|
mastodon.stream_user(listener)
|
|
|
|
|
|
def listtot(res = sys.maxsize):
|
|
plt = listplaylist()
|
|
if len(plt) < res:
|
|
announcepos = getlastannounce()
|
|
for x in listfallback():
|
|
if announcepos == 0:
|
|
if os.path.exists("announce/repeat.mp3"):
|
|
plt.append(["announce/repeat.mp3"])
|
|
announcepos = (announcepos + 1) % ANNOUNCEREPEAT
|
|
|
|
plt.append(x)
|
|
return plt[:res]
|
|
|
|
|
|
def getlastannounce():
|
|
announcepos = ANNOUNCEREPEAT
|
|
try:
|
|
with open("announce/last","r") as f:
|
|
announcepos=int(f.readline().rstrip())
|
|
except:
|
|
pass
|
|
return announcepos
|
|
|
|
def setlastannounce(announcepos):
|
|
with open("announce/last","w") as f:
|
|
f.write(announcepos = (announcepos + 1) % ANNOUNCEREPEAT)
|
|
|
|
def consume(song):
|
|
if os.path.exists(song):
|
|
if song.split("/")[0] == "playlist":
|
|
os.remove(song)
|
|
if not [x for x in glob(os.path.dirname(song) + "/*") if not os.path.basename(x) == "last"]:
|
|
shutil.rmtree(os.path.dirname(song))
|
|
else:
|
|
with open(os.path.dirname(song) + "/last", "w") as f:
|
|
f.write(time.strftime("%Y%m%d%H%M%S"))
|
|
#resetta il contatore announcelast
|
|
setlastannounce(0)
|
|
|
|
elif song.split("/")[0] == "fallback":
|
|
fname = time.strftime("%Y%m%d%H%M%S") + "|" + "|".join(os.path.basename(song).split("|")[1:])
|
|
fname = os.path.dirname(song) + "/" + fname
|
|
os.rename(song, fname)
|
|
|
|
elif song.split("/")[0] == "announce":
|
|
announcepos = getlastannounce()
|
|
setlastannounce(announcepos = (announcepos + 1) % ANNOUNCEREPEAT)
|
|
|
|
|
|
def addstartannounce():
|
|
#aggiunge l'annuncio iniziale
|
|
if os.path.exists("announce/start.mp3"):
|
|
fileout = "playlist/announce/00000000000000|start|start.mp3"
|
|
copyfile("announce/start.mp3", fileout)
|
|
|
|
def copyfile(source, dest):
|
|
if not os.path.exists(dest):
|
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
shutil.copy2(source, dest)
|
|
|
|
|
|
|
|
|
|
def plaympd():
|
|
client = MPDClient()
|
|
|
|
client.timeout = 10 # network timeout in seconds (floats allowed), default: None
|
|
client.idletimeout = None # timeout for fetching the result of the idle command is handled seperately, default: None
|
|
client.connect("localhost", 6600) # connect to localhost:6600
|
|
#print(client.mpd_version)
|
|
|
|
looptime = 10
|
|
synctime = 5
|
|
listlen = 5
|
|
|
|
if SHUFFLEUSERS:
|
|
shuffleusers()
|
|
if SHUFFLEFALLBACK:
|
|
shufflefallback()
|
|
|
|
#stoppa e svuota
|
|
client.stop()
|
|
client.clear()
|
|
#cancella la cartella mpd
|
|
if os.path.exists("mpd"):
|
|
shutil.rmtree("mpd")
|
|
|
|
#aggiunge l'annuncio iniziale
|
|
addstartannounce()
|
|
#resetta il lastannounce
|
|
setlastannounce(ANNOUNCEREPEAT)
|
|
|
|
#riempe la playlist
|
|
plt = listtot(listlen)
|
|
for f in plt:
|
|
print(f[0])
|
|
copyfile(f[0], "mpd/" + f[0])
|
|
client.rescan()
|
|
time.sleep(0.5)
|
|
for f in plt:
|
|
client.add(f[0])
|
|
|
|
#consuma il primo e fa play
|
|
consume(plt[0][0])
|
|
client.play()
|
|
|
|
while True:
|
|
# print("Current")
|
|
# print(client.currentsong())
|
|
# print()
|
|
# print("Status")
|
|
# print(client.status())
|
|
# print()
|
|
# print("playlist")
|
|
# print(client.playlistinfo())
|
|
# print()
|
|
|
|
#controlla se il pezzo e' il primo e consuma le precedenti
|
|
status = client.status()
|
|
|
|
if int(status['song']) > 0:
|
|
#consuma la canzone attuale
|
|
song = client.playlistinfo()[int(status['song'])]['file']
|
|
consume(song)
|
|
mpdclean(client)
|
|
|
|
if len(client.playlistinfo()) < listlen:
|
|
mpdsync(client, listlen)
|
|
status = client.status()
|
|
|
|
#controlla se mancano meno di 15 secondi
|
|
#timeleft = float(status['duration']) - float(status['elapsed']) #new mpd
|
|
timeleft = float(client.currentsong()['time']) - float(status['elapsed']) #old mpd
|
|
|
|
|
|
if timeleft <= looptime + synctime:
|
|
time.sleep(max(timeleft - synctime, 0))
|
|
print ("Mancano %d secondi" % (synctime))
|
|
mpdsync(client, listlen)
|
|
|
|
time.sleep(looptime)
|
|
|
|
def mpdclean(client):
|
|
#cancella le precedenti
|
|
for x in range(int(client.status()['song'])):
|
|
song = client.playlistinfo()[x]['file']
|
|
consume(song)
|
|
client.delete(0)
|
|
#e pulisce anche in mpd
|
|
if os.path.exists("mpd/" + song):
|
|
os.remove("mpd/" + song)
|
|
#se non ci sono + file cancella la cartella
|
|
if not glob(os.path.dirname("mpd/" + song) + "/*"):
|
|
shutil.rmtree(os.path.dirname("mpd/" + song))
|
|
|
|
|
|
|
|
def mpdsync(client, listlen):
|
|
print("Rigenero la playlist")
|
|
mpdclean(client)
|
|
|
|
|
|
plt = listtot(listlen)
|
|
#copia i file
|
|
for f in plt:
|
|
print(f[0])
|
|
copyfile(f[0], "mpd/" + f[0])
|
|
client.rescan()
|
|
#cancella tutto tranne la prima
|
|
for x in client.playlistinfo()[1:]:
|
|
client.delete(1)
|
|
time.sleep(0.5)
|
|
#e rifa la playlist
|
|
for f in plt:
|
|
client.add(f[0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print ("This is a package, use other commands to run it")
|
|
|
|
|