#!/usr/bin/env python # -*- coding: utf-8 -*- # import sys, os import http.client import xml.etree.ElementTree as ET from flask import Flask, render_template, request, make_response, jsonify from waitress import serve app = Flask(__name__) DEBUG = 0 video_folder = "/home/pi" media_ext = [ "mp4", "avi", "mkv" ] # ~ video_folder = "/media/" ## base64 encoded ":secret"" # import base64 # passwd = "foo" # passswd64 = str(base64.b64encode(passwd.encode('utf-8')), 'utf-8') auth = "OnNlY3JldA==" cmd_auth = "OnNlY3JldA==" hosts = [ "10.42.0.135", "10.42.0.156" ] # VLC http LUA port port = 8080 # Clients cmd port cmd_port = 5000 hosts_available, hosts_unavailable = [],[] # Map vlc cmds # See https://github.com/videolan/vlc/blob/1336447566c0190c42a1926464fa1ad2e59adc4f/share/lua/http/requests/README.txt cmd = { "play" : "pl_play", "resume" : "pl_forceresume", "pause" : "pl_forcepause", "tpause" : "pl_pause", "previous" : "pl_previous", "next" : "pl_next", "stop" : "pl_stop", "enqueue" : "in_enqueue", "add" : "in_play", "clear" : "pl_empty", "delete" : "pl_delete", "loop" : "pl_loop", "repeat" : "pl_repeat", "random" : "pl_random", "move" : "pl_move", "sort" : "pl_sort", "seek" : "seek", "sync" : "sync", "status" : "status.xml", "list" : "playlist.xml", #"volume" : "volume", #"ratio" : "aspectratio", #"dir" : "?dir=", #"command" : "?command=", #"key" : "key=", #"browse" : "browse.xml?uri=file://~" # System commands : "rssi" : "rssi", "blink" : "blink", "poweroff" : "poweroff", "reboot" : "reboot", } # Network/link utilities # https://www.metageek.com/training/resources/understanding-rssi/ def isup(host_l, port): global DEBUG import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.settimeout(3.0) s.connect((host_l, port)) if DEBUG: print( "Port " + str(port) + " reachable") return 1 except (socket.error, socket.timeout) as e: if DEBUG: print("Error on connect to " + host_l + ": %s" % e) return 0 finally: s.close() def checkHosts(host_l): hostdown, hostup = [], [] hosts_number = str(len(host_l)) for lhost in host_l: if not isup(lhost, 8080): hostdown.append(lhost) else: hostup.append(lhost) if DEBUG: print( str(len(hostup)) + " of " + hosts_number + " hosts found.") return hostup, hostdown # File utilities def listMediaFiles(folder): files = os.listdir(folder); medias = [] for fd in files: if len(fd.split('.')) > 1: if fd.split('.')[1] in media_ext: medias.append(fd) return medias # /requests/status.xml?command=in_enqueue&input=file:///home/pi/tst1.mp4 def writeM3U(m3u_content : str, host : str): filename = host.replace(".", "_") + ".m3u" fd = open(filename, "w") fd.write(m3u_content) fd.close() return 1 # String utilities def escapeStr(uri): return uri.replace(" ", "%20") def sec2min(duration): return('%02d:%02d' % (duration / 60, duration % 60)) # VLC lua utilities def sendCommand(host, arg0, arg1, arg2): #conn = http.client.HTTPConnection( host + ":" + str(port), timeout=3 ) portl = port # Build request req = "/requests/status.xml" if arg0 == "list" : req = "/requests/playlist.xml" elif arg0 == "rssi": req = "/rssi" portl = cmd_port elif arg0 == "reboot": req = "/reboot" portl = cmd_port elif arg0 == "poweroff": req = "/poweroff" portl = cmd_port elif arg0 != "status" : req = req + "?command=" + cmd[arg0] if arg1 != "null" : if (arg0 == "play") or (arg0 == "delete") or (arg0 == "sort") or (arg0 == "move"): req = req + "&id=" + arg1 if (arg0 == "sort") or (arg0 == "move") : # val possible values : id, title, title nodes first, artist, genre, random, duration, title numeric, album # https://github.com/videolan/vlc/blob/3.0.17.4/modules/lua/libs/playlist.c#L353-L362 req = req + "&val=" + escapeStr(arg2) elif arg0 == "seek" : req = req + "&val=" + arg1 elif (arg0 == "enqueue") or (arg0 == "add") : req = req + "&input=file://" + video_folder + "/" + arg1 # Send request conn = http.client.HTTPConnection( host + ":" + str(portl), timeout=3 ) try: conn.request( "GET", req, headers={"Authorization":"Basic " + auth} ) except: return "Connection to " + host + " was refused on port " + str(portl) resp = conn.getresponse() # Parse response data = resp.read() if arg0 == "rssi": if DEBUG: print(data) response_dict = { 'host': host, 'rssi': str(data, 'UTF-8') } return response_dict xml = ET.fromstring(data) if arg0 != "list" : meta = xml.findall("./information/category/") if meta: for leaf in meta: if leaf.get("name") == "filename": filename = leaf.text else: filename = "N/A" cur_length = int(xml.find('length').text) cur_time = int(xml.find('time').text) cur_length_fmtd = sec2min(cur_length) cur_time_fmtd = sec2min(cur_time) response_dict = { 'host': host, 'file': filename, 'time': cur_time_fmtd, 'leng': cur_length_fmtd, 'pos': xml.find('position').text, 'loop': xml.find('loop').text, 'repeat': xml.find('repeat').text, } return response_dict else: # Build M3U file from playlist playlist = [] playlist_duration = 0 m3u_hdr = "#EXTM3U\n" m3u_prefix = "#EXTINF:" item_list = [] if xml.find("./node") and xml.find("./node").get('name') == "Playlist": m3u_content = m3u_hdr playlist = xml.findall("./node/leaf") #item_list = [] for item in playlist: m3u_content += m3u_prefix + item.get("duration") + ", " + item.get("name") + "\n" + item.get("uri") + "\n" playlist_duration += int(item.get("duration")) # ~ item_info = item.get("id") + " : " + item.get("name") + " - " + sec2min( int( item.get("duration") ) ) item_info = item.get("id") + ";" + item.get("name") + ";" + sec2min( int( item.get("duration") ) ) + ";" if "current" in item.keys(): item_info += item.get("current") item_list.append(item_info) playlist_overview = { "host" : host, "leng" : str(len(playlist)), "duration" : sec2min(playlist_duration), "items" : item_list } return playlist_overview status_message = "Idle" @app.route("/") def main(): global hosts status_message = "Searching network for live hosts..." # ~ hosts_available, hosts_unavailable = checkHosts(hosts) templateData = { 'hosts' : hosts, 'status_message' : status_message } return render_template('main.html', **templateData) @app.route("/scan") def scan(): global hosts_available, hosts_unavailable hosts_available, hosts_unavailable = checkHosts(hosts) hosts_status = [hosts_available, hosts_unavailable] return hosts_status @app.route("/browse") def browse(): files = listMediaFiles("../"); return files; @app.route("///", defaults = { "arg1": "null", "arg2": "null" }) @app.route("////", defaults = { "arg2": "null" }) @app.route("////") def action(host, arg0, arg1, arg2): status_message = "Idle" if arg0 not in cmd: # ~ return "

Wrong command

" status_message = "

Wrong command

" elif host == "all": resp = [] for hostl in hosts_available: resp.append( sendCommand(hostl, arg0, arg1, arg2) ) status_message = resp elif host not in hosts_available: status_message = "

Host is not reachable

" else: status_message = sendCommand(host, arg0, arg1, arg2) if DEBUG: print(status_message) return status_message if __name__ == '__main__': app.run() # ~ serve(app, host='127.0.0.1', port=8080)