Last active
February 15, 2023 16:46
-
-
Save dreness/8aa50d14926641ff7d7c2d3367bbc0ae to your computer and use it in GitHub Desktop.
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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