Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
VLCxcallbacker: generate and host a web page of vlc-x-callback links to every video file in a given directory (for #VLC clients on iOS)
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals
import os
import socket
from datetime import datetime
from urllib import parse
from klein import Klein
from twisted.web.static import File
from twisted.python.filepath import FilePath
'''
Synthesize and host vlc-x-callback stream links
Tired of using the 'open network stream' UI in VLC for iOS to view
large videos hosted elsewhere on your LAN? Me too. Here's the medicine.
The idea is that iOS clients should use a separate route distinct from the
video file URIs (e.g. /vlc/, if the files are in /vids/) to get the
synthesized vlc-x-callbacks, and all requests outside that route
are served as vanila twisted.web.static.File() resources
We'll build a single html page that recursively lists all files under
VIDEO_ROOT that have an extension in the valid_extensions array (so...
if your VIDEO_ROOT is super huge, this might be a bad idea)
'''
# Customize as needed. As you can see, I run this on Windows, so I hope I didn't
# screw up the path handling for other platforms...
VIDEO_ROOT = 'D:\\'
#HOSTNAME = socket.gethostname()
HOSTNAME = "0.0.0.0"
valid_extensions = ['.mp4', '.mkv', '.ts', '.avi', '.mpeg']
S_PORT = 8080
route = '/vlc/'
APP = Klein()
def sizeof_fmt(num, suffix='B'):
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
@APP.route(route)
def list_videos(request):
"""Synthesize x-callback-urls for every video file under VIDEO_ROOT"""
header = '''
<html><head><title>the videos, yo</title></head><body><div><table cellpadding=3,
cellspacing=3, border=1>
'''
videos = [] # list of tuples (uri, size_string, date_created_string)
def descend(file):
base, ext = file.splitext()
if "recycle".lower() in base.lower(): # hack to ignore the windows recycle bin :/
return False
try:
os.chdir(file.path)
except NotADirectoryError:
return False
except WindowsError:
return False
return True
file_path = FilePath(VIDEO_ROOT)
for file in file_path.walk(descend=descend):
_unused, ext = file.splitext()
if ext in valid_extensions and file.isfile:
stats = os.stat(file.path)
fsize = stats.st_size
fstring = sizeof_fmt(fsize)
created = datetime.fromtimestamp(stats.st_ctime)
rel_uri = file.path.replace(VIDEO_ROOT, '').lstrip("/").lstrip("\\")
rel_uri = rel_uri.replace("\\", "/")
videos.append((rel_uri, fstring, created))
# sort by created date, the third tuple element of 'videos' array items
sorted_videos = sorted(videos, key=lambda tup: tup[2])
main_div = []
host = request.host.host
port = request.host.port
for i in reversed(sorted_videos):
enc = parse.urlencode({'url': 'http://{}:{}/{}'.format(host, port, i[0])})
tr_html = """
\n<tr>
<td>{}</td>
<td>{:10}</td>
<td><a href='vlc-x-callback://x-callback-url/stream?{}'>{}</a></td>
</tr>
""".format(i[2], i[1], enc, i[0])
main_div.append(tr_html)
footer = '</table></div></body></html>'
content = header + ''.join(main_div) + footer
return content
@APP.route('/', branch=True)
def pg_index(request):
success = request.uri
if success:
return File(VIDEO_ROOT)
print("Starting up with hostname {}".format(HOSTNAME))
APP.run(HOSTNAME, S_PORT)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.