playlistalo.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. #!/usr/bin/env python3
  2. #Playlistalo - simpatico script che legge le cartelle e genera la playlist
  3. import youtube_dl
  4. import shutil
  5. import sys
  6. import re
  7. import os
  8. import validators
  9. from glob import glob
  10. import json
  11. import time
  12. import subprocess
  13. import random
  14. import configparser
  15. import hashlib
  16. from musicpd import MPDClient
  17. from telegram.ext import Updater, MessageHandler, Filters
  18. from mastodon import Mastodon, StreamListener
  19. scriptpath = os.path.dirname(os.path.realpath(__file__))
  20. #Crea le cartelle
  21. os.makedirs("playlist", exist_ok=True)
  22. os.makedirs("fallback", exist_ok=True)
  23. #Scrivi la prima configurazione
  24. configfile = 'playlistalo.conf'
  25. if not os.path.exists(configfile):
  26. config = configparser.ConfigParser()
  27. config['playlistalo'] = {'ShuffleUsers': False, 'ShuffleSongs': False, 'ShuffleFallback': False, 'Archive':True, 'Telegram_token': "", 'Mastodon_token': "", 'Mastodon_url': ""}
  28. with open(configfile, 'w') as f:
  29. config.write(f)
  30. #Leggi la configurazione
  31. config = configparser.ConfigParser()
  32. config.read(configfile)
  33. playlistaloconf = config['playlistalo']
  34. SHUFFLEUSERS = playlistaloconf.getboolean('ShuffleUsers')
  35. SHUFFLESONGS = playlistaloconf.getboolean('ShuffleSongs')
  36. SHUFFLEFALLBACK = playlistaloconf.getboolean('ShuffleFallback')
  37. ARCHIVE = playlistaloconf.getboolean('Archive')
  38. TELEGRAM_TOKEN = playlistaloconf.get('Telegram_token')
  39. MASTODON_TOKEN = playlistaloconf.get('Mastodon_token')
  40. MASTODON_URL = playlistaloconf.get('Mastodon_url')
  41. def addurl(url, user = "-unknown-"):
  42. #print ('--- Inizio ---')
  43. os.makedirs("cache", exist_ok=True)
  44. ydl_opts = {
  45. 'format': 'bestaudio[ext=m4a]',
  46. 'outtmpl': 'cache/%(id)s.m4a',
  47. 'noplaylist': True,
  48. 'quiet': True,
  49. }
  50. url = url.strip()
  51. print ("url: " + url)
  52. print ("user: " + user)
  53. if not validators.url(url):
  54. print ('--- URL malformato ---')
  55. return ("Err: url non valido")
  56. with youtube_dl.YoutubeDL(ydl_opts) as ydl:
  57. try:
  58. meta = ydl.extract_info(url, download = False)
  59. except youtube_dl.DownloadError as detail:
  60. print ('--- Errore video non disponibile ---')
  61. print(str(detail))
  62. return ("Err: " + str(detail))
  63. id = meta.get('id').strip()
  64. title = __normalizetext(meta.get('title'))
  65. print ('id: %s' %(id))
  66. print ('title: %s' %(title))
  67. #scrivo il json
  68. with open(os.path.join("cache", id + ".json"), 'w') as outfile:
  69. json.dump(meta, outfile, indent=4)
  70. #ho letto le info, ora controllo se il file esiste altrimenti lo scarico
  71. #miglioria: controllare se upload_date e' uguale a quella del json gia' esistente
  72. filetemp = os.path.join("cache", id + ".m4a")
  73. if not glob(filetemp):
  74. print ('--- Scarico ---')
  75. ydl.download([url]) #non ho capito perche' ma senza [] fa un carattere per volta
  76. if not os.path.isfile(filetemp):
  77. return("Err: file non scaricato")
  78. return(add(filetemp, user, title, id))
  79. def md5(fname):
  80. hash_md5 = hashlib.md5()
  81. with open(fname, "rb") as f:
  82. for chunk in iter(lambda: f.read(4096), b""):
  83. hash_md5.update(chunk)
  84. return hash_md5.hexdigest()
  85. def add(filetemp, user = "-unknown-", title = None, id = None):
  86. if not id:
  87. id = md5(filetemp)
  88. if not title:
  89. title = os.path.splitext(os.path.basename(filetemp))[0]
  90. #se il file esiste gia' in playlist salto (potrebbe esserci, anche rinominato)
  91. if glob("playlist/**/*|" + id + ".*"):
  92. print ('--- File già presente ---')
  93. return ("Err: %s [%s] già presente" %(title, id))
  94. os.makedirs("playlist/" + user, exist_ok=True)
  95. #qui compone il nome del file
  96. if SHUFFLESONGS:
  97. fileout = str(random.randrange(10**6)).zfill(14) + "|" + title + "|" + id + ".m4a"
  98. else:
  99. fileout = time.strftime("%Y%m%d%H%M%S") + "|" + title + "|" + id + ".m4a"
  100. fileout = os.path.join("playlist/" + user, fileout)
  101. print ('--- Converto ---')
  102. print (fileout)
  103. subprocess.call([scriptpath + "/trimaudio.sh", filetemp, fileout])
  104. if not os.path.isfile(fileout):
  105. return("Err: file non convertito")
  106. if ARCHIVE:
  107. os.makedirs("archive", exist_ok=True)
  108. if not glob("archive/*|" + id + ".*"):
  109. shutil.copy2(fileout, "archive")
  110. #cerca la posizione del pezzo appena inserito
  111. pos = getposition(fileout)
  112. print ('--- Fine ---')
  113. print ("")
  114. return ("OK: %s [%s] aggiunto alla playlist in posizione #%s" %(title, id, pos))
  115. def __normalizetext(s):
  116. if s is None:
  117. return None
  118. else:
  119. s = re.sub(r'[\\|/|:|*|?|"|<|>|\|]',r'',s)
  120. s = " ".join(s.split())
  121. return s
  122. def listplaylist():
  123. pl = []
  124. pl2 = []
  125. for udir in sorted(glob("playlist/*/")):
  126. #print (udir)
  127. user = os.path.basename(os.path.dirname(udir))
  128. #cerca il file last
  129. last = ""
  130. if os.path.exists(udir + "/last"):
  131. f = open(udir + "/last", "r")
  132. last = f.readline().rstrip()
  133. else:
  134. last = os.path.basename(sorted(glob(udir + "/*.m4a"))[0]).split("|")[0]
  135. #print ("LAST: " + last)
  136. #leggi i file nella cartella
  137. files = sorted(glob(udir + "/*.m4a"))
  138. seq = 0
  139. for file in files:
  140. bn = os.path.splitext(os.path.basename(file))[0]
  141. #print ("BASENAME: " + bn)
  142. seq = seq + 1
  143. dat = bn.split("|")[0]
  144. nam = bn.split("|")[1]
  145. cod = bn.split("|")[2]
  146. key = "-".join([str(seq).zfill(5), last, dat])
  147. #print ("KEY: " + key)
  148. plsong = [key, file, user, nam, cod]
  149. pl.append(plsong)
  150. pl.sort()
  151. #rimuove la prima colonna, che serve solo per l'ordinamento
  152. pl2 = [x[1:] for x in pl]
  153. #print (pl)
  154. #print ('\n'.join([", ".join(x) for x in pl]))
  155. #print ('\n'.join([x[0] for x in pl]))
  156. return pl2
  157. def listfallback():
  158. pl = []
  159. pl2 = []
  160. #leggi i file nella cartella
  161. files = sorted(glob("fallback/*.m4a"))
  162. seq = 0
  163. for file in files:
  164. bn = os.path.splitext(os.path.basename(file))[0]
  165. seq = seq + 1
  166. dat = bn.split("|")[0]
  167. nam = bn.split("|")[1]
  168. cod = bn.split("|")[2]
  169. key = "-".join([str(seq).zfill(5), dat])
  170. plsong = [key, file, "fallback", nam, cod]
  171. pl.append(plsong)
  172. pl.sort()
  173. #rimuove la prima colonna, che serve solo per l'ordinamento
  174. pl2 = [x[1:] for x in pl]
  175. return pl2
  176. def playloop():
  177. while True:
  178. plt = listtot(1)
  179. if plt:
  180. song = plt[0][0]
  181. print(song)
  182. #qui fa play
  183. subprocess.call(["mplayer", "-nolirc", "-msglevel", "all=0:statusline=5", song])
  184. consume(song)
  185. def clean():
  186. #cancella tutto dalla playlist
  187. shutil.rmtree("playlist")
  188. os.makedirs("playlist")
  189. def shuffleusers():
  190. #scrivere un numero casuale dentro a tutti i file last
  191. for udir in sorted(glob("playlist/*/")):
  192. #print (udir)
  193. with open(udir + "/last", "w") as f:
  194. f.write(str(random.randrange(10**6)).zfill(14))
  195. def shufflefallback():
  196. #rinominare con un numero casuale i file in fallback
  197. files = sorted(glob("fallback/*.m4a"))
  198. for file in files:
  199. fname = str(random.randrange(10**6)).zfill(14) + "|" + "|".join(os.path.basename(file).split("|")[1:])
  200. fname = os.path.dirname(file) + "/" + fname
  201. os.rename(file, fname)
  202. def getposition(file):
  203. pl = listplaylist()
  204. try:
  205. return([x[0] for x in pl].index(file) + 1)
  206. except:
  207. pass
  208. def telegram_msg_parser(bot, update):
  209. print("Messaggio ricevuto")
  210. msg = update.message.text
  211. id = str(update.message.from_user.id)
  212. username = update.message.from_user.username
  213. urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', msg)
  214. user = "t_" + "-".join([i for i in [id, username] if i])
  215. #print (urls)
  216. #print (user)
  217. if not urls:
  218. update.message.reply_text("Non ho trovato indirizzi validi...")
  219. return()
  220. update.message.reply_text("Messaggio ricevuto. Elaboro...")
  221. for url in urls:
  222. #update.message.reply_text("Scarico %s" %(url))
  223. # start the download
  224. dl = addurl(url, user)
  225. update.message.reply_text(dl)
  226. def telegram_bot():
  227. print ("Bot avviato")
  228. # Create the EventHandler and pass it your bot's token.
  229. updater = Updater(TELEGRAM_TOKEN)
  230. # Get the dispatcher to register handlers
  231. dp = updater.dispatcher
  232. # parse message
  233. dp.add_handler(MessageHandler(Filters.text, telegram_msg_parser))
  234. # Start the Bot
  235. updater.start_polling()
  236. # Run the bot until you press Ctrl-C
  237. updater.idle()
  238. class MastodonListener(StreamListener):
  239. # andiamo a definire il metodo __init__, che prende una istanza di Mastodon come parametro opzionale e lo setta nella prop. self.mastodon
  240. def __init__(self, mastodonInstance=None):
  241. self.mastodon = mastodonInstance
  242. def on_notification(self, notification):
  243. print("Messaggio ricevuto")
  244. #try:
  245. msg = notification["status"]["content"]
  246. id = str(notification["account"]["id"])
  247. username = notification["account"]["acct"]
  248. #except KeyError:
  249. # return
  250. msg = msg.replace("<br>", "\n")
  251. msg = re.compile(r'<.*?>').sub('', msg)
  252. urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', msg)
  253. user = "m_" + "-".join([i for i in [id, username] if i])
  254. #print (urls)
  255. #print (user)
  256. #visibility = notification['status']['visibility']
  257. statusid = notification['status']['id']
  258. if not urls:
  259. self.mastodon.status_post("@" + username + " " + "Non ho trovato indirizzi validi...", in_reply_to_id = statusid, visibility="direct")
  260. return()
  261. self.mastodon.status_post("@" + username + " " + "Messaggio ricevuto. Elaboro...", in_reply_to_id = statusid, visibility="direct")
  262. for url in urls:
  263. # start the download
  264. dl = addurl(url, user)
  265. self.mastodon.status_post("@" + username + " " + dl, in_reply_to_id = statusid, visibility="direct")
  266. def mastodon_bot():
  267. print ("Bot avviato")
  268. mastodon = Mastodon(access_token = MASTODON_TOKEN, api_base_url = MASTODON_URL)
  269. listener = MastodonListener(mastodon)
  270. mastodon.stream_user(listener)
  271. def listtot(res = sys.maxsize):
  272. plt = listplaylist()
  273. if len(plt) < res:
  274. [plt.append(x) for x in listfallback()]
  275. return plt[:res]
  276. def consume(song):
  277. if song.split("/")[0] == "playlist":
  278. os.remove(song)
  279. if not [x for x in glob(os.path.dirname(song) + "/*") if not os.path.basename(x) == "last"]:
  280. shutil.rmtree(os.path.dirname(song))
  281. else:
  282. with open(os.path.dirname(song) + "/last", "w") as f:
  283. f.write(time.strftime("%Y%m%d%H%M%S"))
  284. elif os.path.split(song)[0] == "fallback":
  285. fname = time.strftime("%Y%m%d%H%M%S") + "|" + "|".join(os.path.basename(song).split("|")[1:])
  286. fname = os.path.dirname(song) + "/" + fname
  287. os.rename(song, fname)
  288. if __name__ == '__main__':
  289. print ("This is a package, use other commands to run it")