Commit 977d444a authored by Michael Murtaugh's avatar Michael Murtaugh

stuff

parents
node_modules/
package-lock.json
Goal: to build a simple (as possible) in-page directory listing media player. This will be deployable as a "mixin" to an existing apache directory listing via the appropriate directives.
## Deployment on Apache
```
IndexHeadInsert "<script type=\"text/javascript\" src=\"/lib/directory/directory
.js\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\"/lib/directory/directory.css\">"
HeaderName /include/HEADER.shtml
ReadmeName README.html
```
## Plan
Re-enable on this directory:
<https://video.constantvzw.org/diversions/2016-10-05/>
* Store derived files in a hidden subdirectory
.aa/FILENAME/derived files
* Enable New "Formats" & "Thumbnails" to be uploaded and placed in these directories.
* Map "open" clicks to player
Player features
* Links to versions (drop down)
* Editable description (markdown?!)
* "Share" link with embed code
* Download links
* Collect icon (to perform evt. editorial work on groups of items ?!)
* Audio
* Video
* PDF
Make viewer respond to "formats" available including "original".
#!/usr/bin/env python
from __future__ import print_function
import cgitb; cgitb.enable()
import cgi, os, sys, json
env = os.environ
method = os.environ.get("REQUEST_METHOD", "GET").upper()
UPLOADS = os.environ.get("DOCUMENT_ROOT", "/var/www/html")
fs = cgi.FieldStorage()
referer = env.get("HTTP_REFERER")
this_url = os.environ.get("SCRIPT_NAME", "")
request_uri = env.get("REQUEST_URI", "").lower()
serve_script = "javascript" in request_uri
# with open ("/usr/lib/cgi-bin/droptoupload.log", "a") as f:
# print ("="*40, file=f)
# print ("ACCESS", file=f)
# print ("="*40, file=f)
# for x in env:
# print ("{0} = {1}".format(x, env.get(x)), file=f)
# print (file=f)
# print (file=f)
def upload (form, inputname, base_upload_dir, pathname=None):
path = None
if (pathname != None) and form.getvalue(pathname):
path = form.getvalue(pathname)
if not form.has_key(inputname): return
fileitem = form[inputname]
if not fileitem.file: return
upload_dir = base_upload_dir
if path:
upload_dir = os.path.join(upload_dir, path.strip("/"))
fp = os.path.join(upload_dir, fileitem.filename.replace(" ", "_"))
# log.write("uploading to path:{0} fp:{1}\n".format(upload_dir, fp))
fout = file (fp, 'wb')
bytes = 0
while 1:
chunk = fileitem.file.read(100000)
if not chunk: break
bytes += len(chunk)
fout.write (chunk)
fout.close()
return bytes, fileitem.filename
if method == "POST":
# with open("upload.log", "a") as log:
result = upload(fs, "thefile", UPLOADS, "path")
if result:
src = fs.getvalue("_src")
bytes, filename = result
if src != "form":
resp = {}
resp["code"] = 0
resp["bytes"] = bytes
print ("Content-type: application/json")
print ()
print (json.dumps(resp))
else:
print ("Content-type: text/html;charset=utf8")
print ()
print ("{0} bytes written to {1}<br />".format(bytes, filename))
print ("")
else:
# form = fs.getvalue("form")
# this_url = os.environ.get("SCRIPT_NAME", "")
print ("Set-Cookie: aa_auth=true;max-age=0;path=/")
if serve_script:
print ("Content-type: application/javascript;charset=utf8")
print ()
print ("""
(function () {
// http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
// https://hacks.mozilla.org/2009/12/uploading-files-with-xmlhttprequest/
// https://developer.mozilla.org/en-US/docs/Web/API/FormData
// http://wabism.com/html5-file-api-how-to-upload-files-dynamically-using-ajax/
var defaults = {
drop_style: "background: gray;",
dropzone: "document"
},
opts = defaults;
var sheet = (function() {
// http://davidwalsh.name/add-rules-stylesheets
// Create the <style> tag
var style = document.createElement("style");
// Add a media (and/or media query) here if you'd like!
// style.setAttribute("media", "screen")
// style.setAttribute("media", "only screen and (max-width : 1024px)")
// WebKit hack :(
style.appendChild(document.createTextNode(""));
// Add the <style> element to the page
document.head.appendChild(style);
return style.sheet;
})();
sheet.insertRule(".dd_drop { " + opts.drop_style + " }", 0);
function dragelt (elt) {
while (elt.nodeType == 3 && elt.parentNode) {
elt = elt.parentNode;
}
return elt;
}
var dropzone = (opts.dropzone == "document") ? document : document.querySelector(opts.dropzone);
dropzone.addEventListener("dragover", function (event) {
event.preventDefault();
}, true);
dropzone.addEventListener("drop", function (event) {
var elt = dragelt(event.target),
files = event.dataTransfer.files;
event.preventDefault();
elt.classList.remove("dd_drop");
var total_files = files.length,
total_bytes = 0,
uploaded_bytes = 0,
uploaded_files = 0;
for (var i=0; i<total_files; i++) {
total_bytes += files[i].size;
}
// figure out path to place files
var path = window.location.pathname;
// 1. if possible, adjust element to be the link inside a table cell
if (elt.nodeName == "TD" && elt.querySelector("a")) {
elt = elt.querySelector("a");
}
// 2. Extend path with a link when it's href ends with "/"
if (elt.nodeName == "A" && elt.getAttribute("href").match(/\/$/) != null) {
path += elt.getAttribute("href");
}
// ask for confirmation (maybe)
// var do_it = confirm("Upload "+files.length+" file"+((files.length > 1)?"s":"")+" to "+path);
// alert("You've just dropped " + files.length + " files");
// console.log(do_it);
var do_it = true;
function upload () {
var formData = new FormData();
formData.append("path", path);
formData.append("thefile", files[uploaded_files]);
// console.log(files);
var xhr = new XMLHttpRequest();
xhr.open("POST", 'THISURL');
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var adjusted_bytes = (e.loaded / e.total) * files[uploaded_files].size;
// console.log(e.loaded, "loaded", e.total, "total");
// var percentage = Math.round((e.loaded * 100) / e.total);
var percentage = (uploaded_bytes + adjusted_bytes) / total_bytes;
// feedback
console.log((uploaded_files+1)+"/"+total_files+", progress", percentage);
var cc = Math.floor(255*percentage),
bg = rgbToHex(cc, cc, cc);
document.body.style = "background: "+bg;
}
}, false);
xhr.onreadystatechange = function () {
if (this.readyState == this.DONE && this.status == 200) {
// console.log("done", this.status);
uploaded_files += 1;
if (uploaded_files < total_files) {
upload();
} else {
// all done
console.log("all done");
window.location.reload(true);
}
}
};
xhr.send(formData);
}
if (do_it && total_files > 0) { upload(); }
}, true);
dropzone.addEventListener("dragenter", function (event) {
var elt = dragelt(event.target);
elt.classList.add("dd_drop");
});
dropzone.addEventListener("dragleave", function (event) {
var elt = dragelt(event.target);
if (elt && elt.classList) {
elt.classList.remove("dd_drop");
}
});
})();
""".replace("THISURL", this_url))
else:
print ("Content-type: text/html; charset=utf8")
print ()
print ("""<!DOCTYPE html>
<html>
<head>
</head>
<body>
UPLOADS: {0}<br>
this_url: {1}<br>
<form method="post" action="" enctype="multipart/form-data">
<input type="file" name="thefile" /><input type="submit" />
<input type="hidden" name="_src" value="form" />
</form>
</body>
</html>
""".format(UPLOADS, this_url))
\ No newline at end of file
#!/usr/bin/env python3
import cgi
# import cgitb; cgitb.enable()
from os import environ
from base64 import b64encode as encode, b64decode as decode
import binascii
PASSWORD = "archivingstartsnow"
def read_cookie(env, name):
cookiestr = env.get("HTTP_COOKIE", "")
if cookiestr:
for cookie in cookiestr.split(';'):
(key, value ) = cookie.strip().split('=');
if key == name:
return value
def authenticate (env):
aa_auth = read_cookie(env, "aa_auth")
if aa_auth:
# attempt to authorize
try:
if decode(aa_auth).decode("utf-8") == PASSWORD:
return True
except binascii.Error:
return False
if __name__ == "__main__":
method = environ.get("REQUEST_METHOD", "GET").upper()
fs = cgi.FieldStorage()
authorized = authenticate(environ)
formclass = ""
if method == "POST":
logout = fs.getvalue("logout")
if logout:
print ("Set-Cookie: aa_auth=; path=/; max-age:0")
authorized = False
elif not authorized:
# attempt to login
password = fs.getvalue("door")
if password == PASSWORD:
# Set the cookie and redirect to next (if present)
password = encode(PASSWORD.encode("utf-8")).decode("utf-8")
print ("Set-Cookie: aa_auth={0};path=/".format(password))
authorized = True
else:
formclass="error"
if not authorized:
print ("Content-type:text/html; charset=utf-8")
print ()
print ("""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>login</title>
<style>
p {{
max-width: 32em;
text-align: center;
}}
form {{
margin-top: 1em;
text-align: center;
}}
form.error input {{
border: 2px solid red;
}}
</style>
</head>
<body>
<p>
<form method="post" action="" class="{0[class]}">
<input id="door" type="password" name="door" value="" placeholder="login with password" autofocus>
</form>
</p>
</body>
</html>
""".format({
"class": formclass
}))
else:
print ("Content-type:text/html; charset=utf-8")
print ()
print ("""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>login</title>
</head>
<body>
<p><b>You are logged in.</b></p>
<form method="post" action="">
<input type="submit" name="logout" value="logout" />
</form>
</body>
</html>
""")
const path = require('path');
// const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry: "./src/directory.js",
mode: "development",
output: {
filename: "directory.js",
path: path.resolve(__dirname, 'dist'),
library: "directory"
}
// plugins: [
// new UglifyJSPlugin()
// ]
}
\ No newline at end of file
const path = require('path');
// const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry: "./src/feedplayer.js",
mode: "development",
output: {
filename: "feedplayer.js",
path: path.resolve(__dirname, 'dist'),
library: "feedplayer"
}
// plugins: [
// new UglifyJSPlugin()
// ]
}
\ No newline at end of file
dist/directory.js: src/directory.js src/*.js
node_modules/.bin/webpack-cli --config directory.config.js
dist/feedplayer.js: src/feedplayer.js src/*.js
node_modules/.bin/webpack-cli --config feedplayer.config.js
upload: dist/directory.js dist/directory.css
cp dist/directory.js dist/directory.css ~/mnt/1/public_html/lib/directory/
cp dist/directory.js dist/directory.css /var/www/html/lib/directory/
{
"name": "directory",
"version": "1.0.0",
"description": "Goal: to build a simple (as possible) in-page directory listing media player. This will be deployable as a \"mixin\" to an existing apache directory listing via the appropriate directives.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"d3": "^5.14.2",
"video.js": "^7.6.6",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10"
}
}
import os
import json
from urllib.parse import quote as urlquote
files = os.listdir(".")
files = [x for x in sorted(files) if not x.startswith(".")]
data = {}
data['children'] = children = []
def decorate_link (path):
mtype = ""
rel = "alternate"
if path.endswith(".thumb.jpg"):
mtype = "image/jpeg"
rel = "thumb"
elif path.endswith(".gif"):
mtype = "image/gif"
return {'url': urlquote(path), 'type': mtype, 'rel': rel}
for f in files:
d = {}
d['url'] = f
base, ext = os.path.splitext(f)
t = os.path.join(".aa", f)
if os.path.exists(t) and os.path.isdir(t):
d['links'] = [decorate_link(os.path.join(".aa", f, x)) for x in os.listdir(t)]
children.append(d)
print (json.dumps(data, indent=2))
import os
files = os.listdir(".")
files.sort()
def mv (a, b):
print ("Move {0} to {1}".format(a, b))
try:
os.makedirs(os.path.split(b)[0])
except OSError as e:
pass
os.rename(a, b)
def migrate (f, ext):
base, fext = os.path.splitext(f)
t = base + ext
if os.path.exists(t):
newpath = os.path.join(".aa", f, base + ext)
mv(t, newpath)
for f in files:
print (f)
if f.endswith(".webm"):
migrate(f, ".thumb.gif")
migrate(f, ".thumb.jpg")
else:
t = os.path.join(".thumbs", f+".jpg")
if os.path.exists(t):
base, fext = os.path.splitext(f)
newpath = os.path.join(".aa", f, base + ".thumb.jpg")
mv(t, newpath)
function controller () {
var div = document.createElement("div"),
playbutton = document.createElement("button"),
nextbutton = document.createElement("button"),
prevbutton = document.createElement("button"),
timeslider = document.createElement("input"),
source = document.createElement("select"),
login = document.createElement("button");
playbutton.textContent = "play";
prevbutton.textContent = "prev";
nextbutton.textContent = "next";
timeslider.setAttribute("type", "range");
login.textContent = "login";
div.appendChild(playbutton);
div.appendChild(prevbutton);
div.appendChild(nextbutton);
div.appendChild(timeslider);
div.appendChild(source);
div.appendChild(login);
login.addEventListener("click", x=> {
loadscript.loadscript("/cgi-bin/aa/droptoupload.cgi?javascript").then(x=> {
console.log("loaded droptoupload");
});
});
document.body.appendChild(div);
div.classList.add("aacontroller");
// console.log("controller", div);
return div;
}
module.exports = controller;
function getCookie (name) {
var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
if (match) return match[2];
}
module.exports = {
get: getCookie
}
\ No newline at end of file
var d3 = require("d3-selection"),
loadscript = require("./loadscript.js"),
droptoupload = require("./droptoupload.js"),
controller = require("./controller.js"),
cookie = require("./cookie.js");
// polyfills
require("./trim.js");
function player (parent, data) {}
/* Binds initial data items to each tr in the table with url == a[href] */
function init_table_data() {
var tr = d3.selectAll("table tbody tr");
tr = tr.filter( (d, i) => (i>=3 && i < tr.size()-1) );
var data = tr.nodes().map(x => ({
'url' : x.querySelector("td:nth-child(2) a[href]").getAttribute("href"),
'last-modified': x.querySelector("td:nth-child(3)").textContent.trim(),
'size': x.querySelector("td:nth-child(4)").textContent.trim()
}));
tr.data(data);
console.log("init_table_data", data);
}
async function read_metadata(json_url) {
function get_thumb_url (d) {
if (!d.links) return;
let links = d.links;
for (let i=0, l=links.length; i<l; i++) {
let link = links[i];
if (link.rel == "thumb") {
return link.url;
}
}
}
try {
var resp = await fetch(json_url),
data = await resp.json();
console.log("got index.json", data);
var update = tr.data(data.children, d => d.url);
console.log("update", update, update.size());
console.log("enter", update.enter().size());
update.select("img").attr("src", d=>get_thumb_url(d));
update.select("a").on("click", function(d) {
d3.event.preventDefault();
console.log("play", this);
var tr = this.parentNode.parentNode,
td = tr.querySelector("td"),
img = td.querySelector("img"),
video = document.createElement("video-js"),
source = document.createElement("source");
console.log("tr", tr);
console.log("img", img);
source.setAttribute("src", d.url);
video.setAttribute("data-setup", "{}");
video.setAttribute("controls", "controls");
video.appendChild(source);
video.setAttribute("class", "video-js");
td.replaceChild(video, img);
videojs(video);
});
return data;
} catch (error) {
console.log("error", error);
return;
}
}
async function init () {
var aa_auth = cookie.get("aa_auth");
console.log("aa_auth", aa_auth, aa_auth=="true");
controller()
// linkplayer({ selection: "a" });
// Initialize data, prepare for join
init_table_data();
// read + join with stored metadata
var data = await read_metadata(".aa/index.json");
if (!data) {
console.log("no data, writing init_table_data to .aa/index.json");
}
//loadscript.loadscript("/lib/directory/video.js").then(x => {
// console.log("loaded video.js", videojs);
//});
//loadscript.loadstylesheet("/lib/directory/video-js.css", x => {
// console.log("loaded videojs.css");
//});
}
document.addEventListener("DOMContentLoaded", init);
\ No newline at end of file
function droptoupload (posturl) {
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
dropzone = document;
dropzone.addEventListener("dragover", function (event) {
event.preventDefault();
}, true);
dropzone.addEventListener("drop", function (event) {
var elt = dragelt(event.target),
files = event.dataTransfer.files;
event.preventDefault();
elt.classList.remove("dd_drop");
var total_files = files.length,
total_bytes = 0,
uploaded_bytes = 0,
uploaded_files = 0;
for (var i=0; i<total_files; i++) {
total_bytes += files[i].size;
}
// figure out path to place files
var path = "v/vj10" ; // window.location.pathname;
// 1. if possible, adjust element to be the link inside a table cell
if (elt.nodeName == "TD" && elt.querySelector("a")) {
elt = elt.querySelector("a");
}
// 2. Extend path with a link when it's href ends with "/"
if (elt.nodeName == "A" && elt.getAttribute("href").match(/\/$/) != null) {
path += elt.getAttribute("href");
}
// ask for confirmation (maybe)
// var do_it = confirm("Upload "+files.length+" file"+((files.length > 1)?"s":"")+" to "+path);
// alert("You've just dropped " + files.length + " files");
// console.log(do_it);
var do_it = true;
function dragelt (elt) {
while (elt.nodeType == 3 && elt.parentNode) {
elt = elt.parentNode;
}
return elt;
}
function upload () {
var formData = new FormData();
formData.append("path", path);
formData.append("file", files[uploaded_files]);
// console.log(files);
var xhr = new XMLHttpRequest();
xhr.open("POST", posturl);
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var adjusted_bytes = (e.loaded / e.total) * files[uploaded_files].size;
// console.log(e.loaded, "loaded", e.total, "total");
// var percentage = Math.round((e.loaded * 100) / e.total);
var percentage = (uploaded_bytes + adjusted_bytes) / total_bytes;
// feedback
console.log((uploaded_files+1)+"/"+total_files+", progress", percentage);
var cc = Math.floor(255*percentage),
bg = rgbToHex(cc, cc, cc);
document.body.style = "background: "+bg;
}
}, false);
xhr.onreadystatechange = function () {
if (this.readyState == this.DONE && this.status == 200) {
// console.log("done", this.status);
uploaded_files += 1;
if (uploaded_files < total_files) {
upload();
} else {
// all done
console.log("all done");
window.location.reload(true);
}
}
};
xhr.send(formData);
}
if (do_it && total_files > 0) { upload(); }
}, true);
dropzone.addEventListener("dragenter", function (event) {
var elt = dragelt(event.target);
elt.classList.add("dd_drop");
});
dropzone.addEventListener("dragleave", function (event) {
var elt = dragelt(event.target);
if (elt && elt.classList) {
elt.classList.remove("dd_drop");
}
});
}
module.exports = droptoupload;
function linkplayer (elt, opts) {