pilpil-server/app.py

278 lines
8.9 KiB
Python
Executable File

#!/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=<uri>",
#"command" : "?command=<cmd>",
#"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("/<host>/<arg0>/", defaults = { "arg1": "null", "arg2": "null" })
@app.route("/<host>/<arg0>/<arg1>/", defaults = { "arg2": "null" })
@app.route("/<host>/<arg0>/<arg1>/<arg2>")
def action(host, arg0, arg1, arg2):
status_message = "Idle"
if arg0 not in cmd:
# ~ return "<p>Wrong command</p>"
status_message = "<p>Wrong command</p>"
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 = "<p>Host is not reachable</p>"
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)