123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- #!/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 = 3
- #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 plt:
- announcepos = 0
- else:
- announcepos = getlastannounce()
- if len(plt) < res:
- for x in listfallback():
- if announcepos == 0:
- if os.path.exists("announce/repeat.mp3"):
- plt.append(["announce/repeat.mp3"])
- announcepos = ANNOUNCEREPEAT - 1
- else:
- announcepos = (announcepos - 1)
- plt.append(x)
- return plt[:res]
- else:
- return plt
- 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(str(announcepos))
- def consume(song):
- if os.path.exists(song):
- print ("Consumo la canzone " + 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(ANNOUNCEREPEAT)
-
- 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)
- announcepos = getlastannounce()
- print("Annuncio da " + str(announcepos) + " a " + str(announcepos - 1))
- setlastannounce(announcepos - 1)
-
- elif song.split("/")[0] == "announce":
- setlastannounce(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 = 10
- if client.status()['state'] != "play":
- 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")
- os.makedirs("mpd")
- client.update()
- #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.update()
- while 'updating_db' in client.status():
- pass
- for f in plt:
- client.add(f[0])
- #consuma il primo e fa play
- consume(plt[0][0])
- #mpdadd(client, listlen)
- client.play(0)
- 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))
- mpdclean(client)
- mpdadd(client, listlen)
- time.sleep(looptime)
- def mpdclean(client):
- #cancella le precedenti
- for x in range(int(client.status()['song'])):
- song = client.playlistinfo()[0]['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 mpdadd(client, listlen):
- print("Rigenero la playlist")
- plt = listtot(listlen)
- #copia i file
- for f in plt:
- #print(f[0])
- copyfile(f[0], "mpd/" + f[0])
- client.update()
- while 'updating_db' in client.status():
- pass
- # #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])
- # time.sleep(0.5)
- print("------------------")
- playlist=client.playlistinfo()
- for f in plt[:len(playlist)-1]:
- i = plt.index(f) + 1
- #print(f[0] +" - "+ playlist[i]['file'])
- if f[0] != playlist[i]['file']:
- break
- else:
- print("Mantengo " + f[0])
-
- #print (i)
- for x in client.playlistinfo()[i:]:
- print("Cancello " + x['file'])
- client.delete(i)
- #e rifa la playlist
- for f in plt[i-1:]:
- print("Aggiungo " + f[0])
- client.add(f[0])
- #consumo la prima
- consume(plt[0][0])
- if __name__ == '__main__':
- print ("This is a package, use other commands to run it")
|