--- /dev/null
+.PHONY: _subtrees
+
+_subtrees: _cgilite
+
+cgilite:
+ git subtree add --squash -P $@ https://git.plutz.net/git/$@ master
+
+_cgilite: cgilite
+ git subtree pull --squash -P $< https://git.plutz.net/git/$< master
+
--- /dev/null
+#!/bin/sh
+
+f=''
+order="$(POST order |grep -m1 -xE 'Name|Date|Length|Group' || printf Name)"
+
+for n in 1 2 3 4 5 6 7 8 9; do
+ [ "$(POST pol_$n)" = neg ] \
+ && f="$f~"
+ cat="$(POST cat_$n)"
+ for m in $(seq 1 $(POST_COUNT tag_$n)); do
+ tag="$(POST tag_$n $m)"
+ [ ! "${tag##${cat}:*}" ] || [ ! "${tag##-${cat}:*}" ] || [ "$cat" = '*' -a "${tag##*:*}" ] \
+ && f="${f}${tag}|"
+ [ "$cat" = \$ ] && f="${f}\$:${tag}|"
+ done
+ f="${f%[|^]}^"
+done
+f="$(printf '%s' "$f" |sed -E 's;[~|^]+$;;; s;\|\^;^;g;')"
+#f="${f%^}"
+
+REDIRECT "$(URL "${ITEM}")?o=${order}&f=${f}"
--- /dev/null
+#!/bin/sh
+
+exec 2>/dev/null
+for n in "$@"; do case ${n%%=*} in
+ data) _DATA="${n#data=}";;
+ exec) _exec="${n#exec=}";;
+ noerr) exec 2>&-;;
+esac; done
+
+[ -z "${_EXEC%/}" ] && _EXEC="$(realpath "${0%/*}")" || _EXEC="${_EXEC%/}"
+[ -z "${_DATA%/}" ] && _DATA=. || _DATA="${_DATA%/}"
+
+file_pattern='^.*\.(mov|ts|mpg|mpeg|mp4|m4v|avi|mkv|flv|sfv|wmv|ogm|ogv|webm|iso|rmvb)$'
+
+. "$_EXEC/cgilite/cgilite.sh"
+
+FILTER="$(GET f)"
+SEARCH="$(GET s)"
+ORDER="$(GET o |grep -m1 -axE 'Date|Name|Length|Group' || printf Name)"
+LISTSIZE="$(COOKIE pagesize |grep -m1 -axE '[1-9][0-9]*' || printf 60)"
+ITEM="${PATH_INFO%/}"
+ACTION="$(GET a)"
+
+case $ACTION in
+ setprefs)
+ SET_COOKIE +$((86400 * 90)) pagesize="$(POST pagesize |grep -m1 -axE '[1-9][0-9]*' || printf 60)"
+ SET_COOKIE +$((86400 * 90)) mode="$(POST mode |grep -m1 -axE 'browse|index' || printf browse)"
+ SET_COOKIE +$((86400 * 90)) fakemp4="$(POST fakemp4 |grep -m1 -axE 'yes' || printf no)"
+ SET_COOKIE +$((86400 * 90)) downscale="$(POST downscale |grep -m1 -axE 'yes' || printf no)"
+ [ "$(POST index)" = "update" ] && touch -cd @0 "${_DATA}/.index/meta.time"
+ REDIRECT "$(POST ref)"
+ ;;
+ bookmark)
+ bm="$_DATA/.index/bookmarks"
+ . "$_EXEC/cgilite/storage.sh"
+
+ s="$(POST search |STRING)"; f="$(POST filter |STRING)"
+ if LOCK "$bm"; then
+ grep -avF " search=$s filter=$f${CR}" "$bm" >"$bm.tmp"
+ [ ! "$(POST delete)" ] \
+ && printf '%s search=%s filter=%s\r\n' \
+ "$(POST name |STRING)" "$s" "$f" >>"$bm.tmp"
+ mv "$bm.tmp" "$bm"
+ RELEASE "$bm"
+ fi
+ REDIRECT "$(POST ref)"
+ ;;
+ multitag)
+ . "$_EXEC/multitag.sh"
+ REDIRECT "$(POST ref)"
+ ;;
+ thumbnail) if [ -f "$_DATA/$PATH_INFO" ]; then
+ . "$_EXEC/cgilite/file.sh"
+ index="$_DATA/${PATH_INFO%/*}/.index"
+ thumb="$index/${PATH_INFO##*/}"; thumb="${thumb%.*}.jpg"
+ if [ -d "$index" -a ! -f "$thumb" ] && { printf %s "$PATH_INFO" |grep -qE -e "${file_pattern}" ;}; then
+ . "$_EXEC/thumbnail.sh"
+ gen_thumb "$_DATA/$PATH_INFO" "$thumb"
+ fi
+ FILE "$thumb"
+ return 0
+ fi;;
+ download) if [ -f "$_DATA/$PATH_INFO" ]; then
+ . "$_EXEC/cgilite/file.sh"
+ fakemp4="$(COOKIE fakemp4)"
+ downscale="$(COOKIE downscale)"
+ downfile="$_DATA/${PATH_INFO%/*}/.transcode/${PATH_INFO%.*}.480p.webm"
+ if [ "$downscale" = yes -a -f "$downfile" ]; then
+ FILE "$downfile" "$([ "$fakemp4" = yes ] && printf 'video/mp4')"
+ else
+ FILE "$_DATA/$PATH_INFO" "$([ "$fakemp4" = yes ] && printf 'video/mp4')"
+ fi
+ return 0
+ fi;;
+ delete) if [ -f "$_DATA/$PATH_INFO" ]; then
+ :
+ fi;;
+ advsearch) if [ -d "$_DATA/$PATH_INFO" ]; then
+ . "$_EXEC/advsearch.sh"
+ return 0
+ fi;;
+ spawnindex) if [ -d "$_DATA/$PATH_INFO" ]; then
+ if [ "$(POST recursive)" = yes ]; then
+ find "$_DATA/$PATH_INFO" -depth -type d \! -name .index \
+ -exec mkdir -p '{}'/.index \;
+ else
+ mkdir -p "$_DATA/$PATH_INFO/.index"
+ fi
+ REDIRECT "$(POST ref)"
+ fi;;
+esac
+
+# case $PATH_INFO in
+# /style.css)
+# . "$_EXEC/cgilite/file.sh"
+# [ -r "$_DATA/$PATH_INFO" ] \
+# && FILE "$_DATA/$PATH_INFO" \
+# || FILE "$_EXEC/style.css"
+# return 0
+# ;;
+# /stereoview.js)
+# . "$_EXEC/cgilite/file.sh"
+# FILE "$_EXEC/stereoview.js"
+# return 0
+# ;;
+# esac
+
+if [ -f "$_EXEC/$PATH_INFO" ]; then
+ . "$_EXEC/cgilite/file.sh"
+ FILE "$_EXEC/$PATH_INFO"
+ return 0
+elif [ -f "$_DATA/$PATH_INFO" ]; then
+ . "$_EXEC/view.sh"
+ return 0
+elif [ -d "$_DATA/$PATH_INFO" ]; then
+ . "$_EXEC/list.sh"
+ return 0
+else
+ printf 'Status: 404 Not Found\r\nContent-Length 0:\r\n\r\n'
+fi
--- /dev/null
+#!/bin/sh
+
+[ -n "$include_indexmeta" ] && return 0
+include_indexmeta="$0"
+
+. "$_EXEC/cgilite/storage.sh"
+file_pattern='^.*\.(mov|ts|mpg|mpeg|mp4|m4v|avi|mkv|flv|sfv|wmv|ogm|ogv|webm|iso|rmvb)$'
+
+meta_name() {
+ local fn
+ fn="$1"; fn="${fn##*/}"; fn="${fn%.*}"
+ STRING "$fn"; printf '\r'
+}
+
+meta_line() {
+ local video l w h
+ video="$1"
+ [ "${video%.part}" = "$video" -a -s "$video" ] || return 0
+
+ read l h w <<__EOF
+ $(printf '' \
+ | mplayer -input nodefault-bindings -nosound -vo null -identify -frames 0 "$video" 2>&- \
+ | sort | sed -rn '
+ s:ID_LENGTH=(.*)(\..*)$:\1:p;
+ s:ID_VIDEO_HEIGHT=(.*):\1:p;
+ s:ID_VIDEO_WIDTH=(.*):\1:p;' \
+ | tr '\n' ' '
+ )
+__EOF
+ printf '%i\t%i\t%i\ttags=\tcomment=\t%s\n' \
+ "${l-0}" "${w-0}" "${h-0}" "$(meta_name "$video")"
+}
+
+meta_file(){
+ local file meta name
+ file="$1"
+ meta="${file%/*}/.index/meta"
+ name="$(meta_name "$file")"
+
+ if [ -d "${meta%/meta}" ] && LOCK "$meta"; then
+ grep -avF " ${name}" "$meta" >"$meta.tmp"
+ meta_line "$file" \
+ | tee -a "$meta.tmp"
+ mv "$meta.tmp" "$meta"
+ RELEASE "$meta"
+ fi
+}
+
+meta_purge(){
+ local file meta name
+ file="$1"
+ meta="${file%/*}/.index/meta"
+ name="$(meta_name "$file")"
+
+ if [ -d "${meta%/meta}" ] && LOCK "$meta"; then
+ grep -avF " ${name}" "$meta" >"${meta}.tmp"
+ grep -aF " ${name}" "$meta" >>"${meta}.trash"
+ mv "${meta}.tmp" "$meta"
+ RELEASE "$meta"
+ fi
+}
+
+meta_info(){
+ local file meta
+ file="$1"; meta="${file%/*}/.index/meta"
+
+ if [ -d "${meta%/meta}" ]; then
+ grep -aF " $(meta_name "$file")" "$meta" \
+ | grep -m1 -axE '[0-9]+ [0-9]+ [0-9]+ tags=[^ ]* comment=[^ ]* .+' \
+ || meta_file "$file"
+ else
+ printf '0\t0\t0\ttags=\tcomment=\t%s\r' "$(meta_name "$file")"
+ fi
+}
+
+meta_dir(){
+ local dir meta v
+ dir="${1}"
+ meta="${dir}/.index/meta"
+ metat="${dir}/.index/meta.time"
+
+ [ -f "$metat" ] || touch -d @0 "$metat"
+
+ if [ -d "$dir/.index" -a \! -f "$meta" ] && LOCK "$meta"; then
+ touch "$meta" # preliminary touch to prevent concurrent generators
+ find -L "$dir" -type f -mindepth 1 -maxdepth 1 \
+ | grep -aE "$file_pattern" \
+ | while read -r v; do
+ meta_line "$v"
+ done >"$meta"
+ touch "$metat"
+
+ RELEASE "$meta"
+ elif [ -d "$dir/.index" -a "$dir" -nt "$metat" ] && LOCK "$meta"; then
+ touch "$meta"
+ find -L "$dir" -type f -newer "$metat" \
+ -mindepth 1 -maxdepth 1 \
+ | grep -aE "$file_pattern" \
+ | while read -r v; do
+ grep -qF " $(meta_name "$v")" "$meta" \
+ || meta_line "$v"
+ done >>"$meta"
+ touch "$metat"
+
+ RELEASE "$meta"
+ fi
+}
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/indexmeta.sh"
+. "$_EXEC/widgets.sh"
+
+list_item() {
+ local meta type length width height tags comment name display link
+ meta="${1}"; type="${meta%% *}"; meta="${meta#* }"
+
+ if [ "$type" = dir ]; then
+ name="${meta%% *}";
+ display="$(HTML "$name")"; link="$(URL "${PATH_INFO%/}/$name")"
+ printf '[a .list .dir href="%s" %s]' "${link}?${w_refuri#*\?}" "$name"
+ return 0
+ fi
+
+ length="${meta%% *}"; meta="${meta#* }"
+ width="${meta%% *}"; meta="${meta#* }"
+ height="${meta%% *}"; meta="${meta#* }"
+ tags="${meta%% *}"; meta="${meta#* }"
+ comment="${meta%% *}"; meta="${meta#* }"
+ name="${meta%% *}"; meta="${meta#* }"
+
+ if [ "$type" = metashort ]; then
+ name="$(list_fullname "$(UNSTRING "${name%${CR}}")")"
+ fi
+ if [ -f "$_DATA/${PATH_INFO%/}/$name" ]; then
+ link="$(URL "${PATH_INFO%/}/${name#./}")"
+ name="$(HTML "${PATH_INFO%/}/${name#./}")"
+ printf '[div .list .file
+ [a href="%s" [img src="%s?a=thumbnail"]][label . %s]
+ [span .time %i:%02imin] [span .dim %ix%i] %s
+ [checkbox "select" "%s" id="select_%s"][label for="select_%s" +]
+ ]' \
+ "$link" "$link" "${name##/}" \
+ "$((length / 60))" "$((length % 60))" \
+ "$width" "$height" \
+ "$(UNSTRING "${tags#tags=}" \
+ | sed -r 's;^;,;; s;,+;,;g; s;,$;;;
+ :X s;,-?([^,]+)(,|$); [span .tag\n \1]\2;; tX;'
+ )" "$name" "$link" "$link"
+ else
+ debug "Canning record for nonexist file: $name"
+ meta_purge "$_DATA/$ITEM/$name"
+ fi
+}
+
+
+[ "$FILTER" ] && list_fex="$(
+ fex='p'
+ STRING "$FILTER^" \
+ | sed -E 's;\^;\n;g; s;[]\/\(\)\\\^\$\?\.\+\*\;\[\{\}];\\&;g' \
+ | while read -r f; do
+ [ "${f##*[A-Z]*}" ] && tl="y;ABCDEFGHIJKLMNOPQRSTUVWXYZ;abcdefghijklmnopqrstuvwxyz;;"
+ case $f in
+ ''|~) continue;;
+ ~\\\$:*) fex="h; ${tl} /${f#~\\\$:}/d; g;${fex}";;
+ \\\$:*) fex="h; ${tl} /${f#\\\$:}/{g;${fex}}";;
+ ~*) fex="/(\ttags=([^\t]*,)?)(${f#\~})((,[^\t]*)?\t)/d; ${fex}";;
+ *) fex="/(\ttags=([^\t]*,)?)(${f})((,[^\t]*)?\t)/{${fex}}";;
+ esac
+ printf '%s\n' "${fex}"
+ done \
+ | tail -n1
+)"
+
+list_fullname(){
+ sn="$1"
+ [ ! "${sn%%*/*}" ] && base="${sn%/*}" || base=.
+ file="$(printf '%s' "$_DATA/$ITEM/$sn".*)"
+ file="${file##*/}"
+ [ -e "$_DATA/$ITEM/$base/${file}" ] \
+ && printf '%s\n' "${base}/${file}"
+}
+
+list_filter(){
+ if [ "$FILTER" ]; then
+ debug "FEX:" "$list_fex"
+ sed -nE "$list_fex"
+ elif [ "${SEARCH#!}" != "${SEARCH}" ]; then
+ grep -aviEe "$(STRING "${SEARCH}" \
+ | sed -E ':x s;((^|[^\\])(\\\\)*)\+;\1 ;g; tx;
+ s;((^|[^\\])(\\\\)*)\\\+;\1+;g;
+ s; ;\\+;g;')"
+ elif [ "${SEARCH}" ]; then
+ grep -aiEe "$(STRING "${SEARCH}" \
+ | sed -E ':x s;((^|[^\\])(\\\\)*)\+;\1 ;g; tx;
+ s;((^|[^\\])(\\\\)*)\\\+;\1+;g;
+ s; ;\\+;g;')"
+ else
+ cat
+ fi
+}
+
+list_order(){
+ local fm fn fn al length ln h w t c name buffer l
+
+ if [ $ORDER = Name ]; then
+ sort -k6 |sed 's;^;metashort\t;;'
+ elif [ $ORDER = Group ]; then
+ sed -E '
+ :X
+ s;^([^\t]+\t[^\t]+\t[^\t]+\t[^\t]+\t[^\t]+\t[^ 0-9]*)-([0-9a-zA-Z_-]{11}\r|ph[0-9a-f]{13}\r|[0-9]{8}\r)(.*)$;\1-\r\3 \2\n;;
+ t;
+ s;^([^\t]+\t[^\t]+\t[^\t]+\t[^\t]+\t[^\t]+\t[^ 0-9]*)([0-9]+)(.*)$;\1\r\3 \2;;
+ tX;' \
+ | { sort -n -k7 -k8 -k9 -k10 -k11 |sort -s -k6,6 ; echo '0 0 0 tags= comment= _'; } \
+ | while read -r length h w t c name; do
+ if [ "${ln%% *}" = "${name%% *}" ]; then
+ al=$((al + length))
+ buffer="${buffer}${BR}$length $h $w $t $c $name"
+ else
+ printf '%s\n' "$buffer" |while read -r l; do
+ [ "$l" ] && printf '%s %s\n' "$al" "$l"
+ done
+ al="$length"
+ buffer="$length $h $w $t $c $name"
+ fi
+ ln="$name"
+ done \
+ | sort -s -n -k1,1 |sed -E 's;^[0-9]+\t;metashort\t;;' \
+ | sed -E ':X s;^([^\t]+\t[^\t]+\t[^\t]+\t[^\t]+\t[^\t]+\t[^\r]*)\r([^ ]*) ([^ ]+)( .*)?$;\1\3\2\4;; tX'
+ elif [ $ORDER = Length ]; then
+ sort -sn -k1 |sed 's;^;metashort\t;;'
+ elif [ $ORDER = Date ]; then
+ while read -r fm; do
+ sn="${fm##* }"
+ fn="$(list_fullname "$(UNSTRING "${sn%${CR}}")")"
+ printf '%i %s %s\n' \
+ "$(stat -c %Y "$fn")" "${fm% *}" "$fn"
+ done \
+ | sort -srn -k1 |sed -E 's;^[0-9]+\t;metalong\t;;'
+ fi
+}
+
+list_filemeta(){
+ local meta base cbase fm cachename
+ base="$1"
+ meta="$_DATA/$ITEM/$base/.index/meta"
+ meta_dir "$_DATA/$ITEM/$base"
+
+ cachename="$(printf '%s\n' "$mode" "$FILTER" "$SEARCH" "$ORDER" |sha1sum)"
+ cachename="$_DATA/$ITEM/.index/${cachename% -}.cache"
+
+ if [ "$cachename" -nt "$meta" ] 2>&-; then
+ cat "$cachename"
+ else
+ cbase="$(STRING "$base")"
+ grep -axE '[0-9]+ [0-9]+ [0-9]+ tags=[^ ]* comment=[^ ]* .+' "$meta" \
+ | while read -r fm; do
+ printf '%s %s/%s\n' "${fm% *}" "$cbase" "${fm##* }"
+ done \
+ | list_filter \
+ | list_order \
+ | { [ -d "${meta%meta}" ] && tee "$cachename" || cat; }
+ fi
+}
+
+list_items() {
+ local mode meta
+ mode="$(COOKIE mode |grep -m1 -axE 'index|browse' || printf index )"
+
+ if [ "$mode" = browse ]; then
+ [ "$ITEM" ] && printf 'dir\t..\n'
+ (cd "$_DATA/$ITEM";
+ find ./ -type d \! -name .index -mindepth 1 -maxdepth 1 \
+ ) | cut -d/ -f2- | sort |sed 's;^;dir\t;;'
+ list_filemeta .
+ elif [ "$mode" = index ]; then
+ (cd "$_DATA/$ITEM";
+ find ./ -path '*/.index/meta'
+ ) | while read -r meta; do
+ list_filemeta "${meta%/.index/meta}"
+ done
+ fi
+}
+
+list_paginate() {
+ local page i c n end qry
+ page="$(GET p |grep -axE '[0-9]+' || printf 1)"; c=1
+ end=$((page + LISTSIZE))
+ qry="${w_refuri#*\?}"; qry="${qry#p=*&}"
+
+ printf '[div .itemlist '
+ while read -r i; do
+ c=$((c + 1))
+ [ $c -gt $page -a $c -le $end ] && list_item "$i"
+ done
+ printf ']'
+
+ [ $(( c % LISTSIZE )) -gt 0 ] \
+ && end=$((c / LISTSIZE + 1)) \
+ || end=$((c / LISTSIZE))
+
+ printf '[div .pagination'
+ for n in $( seq 1 $end ); do
+ c=$(( (n - 1) * LISTSIZE + 1 ))
+ [ $c = $page ] \
+ && printf '[a .page .current href="%s" %s]' "?p=${c}&${qry}" "$n" \
+ || printf '[a .page href="%s" %s]' "?p=${c}&${qry}" "$n"
+ done
+ printf ']'
+}
+
+printf 'Content-Type: text/html;charset=utf-8\r\n\r\n'
+
+{ printf '
+[!DOCTYPE HTML]
+[html [head [title '
+ w_bmname
+ printf ' by %s]' "$ORDER"
+ printf '
+ [meta name="viewport" content="width=device-width"]
+ [link rel=stylesheet href="/cgilite/common.css" ]
+ [link rel=stylesheet href="/style.css" ]
+] [body
+ [div #navigation
+ [a #t_bookmarks href="#bookmarks" ★]'
+ w_search
+ printf '
+ [a #t_prefs href="#prefs" ⚙]
+ ]'
+ w_bookmarks
+ w_advsearch
+ w_prefs
+ printf '
+ [form method=POST action="?a=multitag"'
+ list_items \
+ | list_paginate
+ [ -d "$_DATA/$ITEM/.index" ] && { printf '
+ [div #editing'
+ w_tagging
+ printf '
+ ]'; }
+ printf '
+ ]'
+ [ ! -d "$_DATA/$ITEM/.index" ] && { printf '
+ [div #editing'
+ w_index
+ printf '
+ ]'; }
+ printf '
+] ]
+'; } | "$_EXEC/cgilite/html-sh.sed"
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/cgilite/storage.sh"
+. "$_EXEC/indexmeta.sh"
+
+newtags=''
+for tn in $(seq 1 $(POST_COUNT tag)); do
+ newtags="$(POST tag $tn)${BR}${newtags}"
+done
+newtags="$(POST newtag |tr -d '\r' |tr , '\n')${BR}${newtags}"
+
+while [ "${newtags#${BR}}" != "${newtags}" ]; do newtags="${newtags#${BR}}"; done
+while [ "${newtags%${BR}}" != "${newtags}" ]; do newtags="${newtags%${BR}}"; done
+
+[ "$(POST op)" = del ] && deltags="$newtags"
+
+for select in $(seq 1 $(POST_COUNT select)); do
+ file="$_DATA/$ITEM/$(POST select $select |PATH)"
+ meta="${file%/*}/.index/meta"
+
+ read -r length width height tags comment fn <<-EOF
+ $(meta_info "$file")
+ EOF
+ tags="$(UNSTRING "${tags#tags=}" |tr , '\n')"
+
+ if [ ! "$deltags" ]; then
+ extags="$(printf '%s' "${newtags}" |grep -e "^-" |cut -d: -f1 )"
+ [ "$extags" ] && tags="$(printf %s\\n "$tags" |grep -vwFe "$extags")"
+ if printf %s "${newtags}" |grep -e "^-" |grep -qEe "^-[^:]+$"; then
+ tags="$(printf %s\\n "$tags" |grep -vEe '^-[^:]+$')"
+ fi
+ tags="$(printf '%s\n' "${tags},${newtags}" \
+ | tr , '\n' |sort -u |tr '\n' , \
+ | STRING)"
+ else
+ detag="${deltags}${BR}"; while [ "$detag" ]; do
+ tags="$(printf '%s\n' "$tags" |grep -vxFe "${detag%%${BR}*}")"
+ detag="${detag#*${BR}}"
+ done
+ tags="$(printf '%s\n' "$tags" |sort -u |tr '\n' , |STRING)"
+ fi
+ tags="${tags#,}"; tags="${tags%,}"
+
+ if LOCK "$meta"; then
+ grep -avF " $fn" "$meta" >"${meta}.tmp"
+
+ printf '%i %i %i tags=%s comment=%s %s\n' \
+ "$length" "$width" "$height" "$tags" \
+ "${comment#comment=}" "$fn" \
+ >>"${meta}.tmp"
+ mv "${meta}.tmp" "$meta"
+
+ RELEASE "$meta"
+ fi
+done
--- /dev/null
+/* Copyright 2018 Paul Hänsch
+
+ This file is part of Serve0
+
+ Serve0 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Serve0 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Serve0 If not, see <http://www.gnu.org/licenses/>.
+*/
+
+var render, video, controlTimeout = 0;
+var pitch = 0, roll = 0, yaw = 0;
+var w, h, hdeg, vdeg, scale, fov = 90;
+var lv = document.createElement("canvas");
+var rv = document.createElement("canvas");
+var debug = document.createElement("p");
+var gp;
+
+function draw() {
+ sw = fov * hdeg |0;
+ sh = h / 2 |0;
+ dh = h / 2 * scale |0;
+
+ if ( layout == "180" ) {
+ sx = (w / 2 - fov * hdeg) / 2 + yaw * hdeg |0;
+ sy = ( h - fov * vdeg) / 2 + pitch * vdeg |0;
+ if (sx + sw > w / 2) { sx = w / 2 - sw; } else if (sx < 0) { sx = 0; }
+ lc.drawImage(video, sx, sy, sw, sh, 0, 0, lv.width, dh);
+ rc.drawImage(video, w / 2 + sx, sy, sw, sh, 0, 0, rv.width, dh);
+ } else {
+ sx = (w - fov * hdeg) / 2 + yaw * hdeg |0;
+ sy = (h - fov * vdeg) / 4 + pitch * vdeg |0;
+ lc.drawImage(video, sx, sy, sw, sh, 0, 0, lv.width, dh);
+ rc.drawImage(video, sx, h/2 + sy, sw, sh, 0, 0, rv.width, dh);
+ if (sx < 0) {
+ lc.drawImage(video, sx + w, sy, sw, sh, 0, 0, lv.width, dh);
+ rc.drawImage(video, sx + w, h/2 + sy, sw, sh, 0, 0, rv.width, dh);
+ } else if ( sx + fov * hdeg > w) {
+ lc.drawImage(video, sx - w, sy, sw, sh, 0, 0, lv.width, dh);
+ rc.drawImage(video, sx - w, h/2 + sy, sw, sh, 0, 0, rv.width, dh);
+ }
+ }
+
+ lv.style.transform = "rotate(" + roll + "deg)";
+ rv.style.transform = "rotate(" + roll + "deg)";
+
+ requestAnimationFrame(draw);
+
+ gp = navigator.getGamepads()[0];
+ if ( gp && Date.now() > controlTimeout ) {
+ if ( gp.axes[0] > .3) { video.currentTime += 10; controlTimeout = Date.now() + 500; }
+ if ( gp.axes[0] < -.3) { video.currentTime -= 10; controlTimeout = Date.now() + 500; }
+ if ( gp.axes[1] < -.3) { video.currentTime += 60; controlTimeout = Date.now() + 500; }
+ if ( gp.axes[1] > .3) { video.currentTime -= 60; controlTimeout = Date.now() + 500; }
+ if ( gp.buttons[0].pressed ) { video.currentTime += 1/30; video.pause(); }
+ if ( gp.buttons[1].pressed ) { video.play(); }
+ if ( gp.buttons[2].pressed ) { fov -= 10; controlTimeout = Date.now() + 500; }
+ if ( gp.buttons[3].pressed ) { fov += 10; controlTimeout = Date.now() + 500; }
+ }
+
+ // debug.textContent = "" + video.currentTime + " " + controlTimeout + " " + tx + " " + ty + " " + tz;
+};
+
+function stereoview(layout, video) {
+ this.layout = layout; this.video = video;
+ document.body.appendChild( lv );
+ document.body.appendChild( rv );
+ document.body.appendChild( debug );
+
+ lv.setAttribute( "style", "position: fixed; top: 0; left: 0 ; width: 50%; height: 100%; z-index: 100;");
+ rv.setAttribute( "style", "position: fixed; top: 0; left: 50%; width: 50%; height: 100%; z-index: 100;");
+ debug.setAttribute( "style", "position: fixed; top: 0; left: 0; z-index: 101; background: #000;");
+
+ lv.setAttribute( "width", "" + lv.offsetWidth);
+ rv.setAttribute( "width", "" + rv.offsetWidth);
+ lv.setAttribute("height", "" + lv.offsetHeight);
+ rv.setAttribute("height", "" + rv.offsetHeight);
+
+ lc = lv.getContext("2d");
+ rc = rv.getContext("2d");
+
+ mpuevent = new EventSource("http://localhost:314");
+ var x = [], y = [], z = [], cnt = -1, inertia = 6;
+
+ mpuevent.addEventListener("bearing", function(e) {
+ bearing = e.data.split(" ");
+ yaw = -parseFloat(bearing[0]);
+ }, false);
+ mpuevent.addEventListener("motion", function(e) {
+ motion = e.data.split(" ");
+
+ cnt = (cnt + 1) % inertia;
+ x[cnt] = parseFloat(motion[0]);
+ y[cnt] = parseFloat(motion[1]);
+ z[cnt] = parseFloat(motion[2]);
+
+ // tx = 0; x.forEach( function(n, i){ tx += n; } ); tx /= inertia;
+ ty = 0; y.forEach( function(n, i){ ty += n; } ); ty /= inertia;
+ tz = 0; z.forEach( function(n, i){ tz += n; } ); tz /= inertia;
+
+ pitch = Math.asin((tz / 9.81 > 1)?1:(tz/9.81)) / Math.PI * 180 + 22.5;
+ roll = - Math.asin((ty / 9.81 > 1)?1:(ty/9.81)) / Math.PI * 180;
+ // yaw = (yaw + ty) % 360;
+ }, false );
+
+ window.addEventListener("devicemotion", (function() {
+ var x = [], y = [], z = [], cnt = -1, inertia = 6;
+ return function(event) {
+ cnt = (cnt + 1) % inertia;
+
+ x[cnt] = event.accelerationIncludingGravity.x;
+ y[cnt] = event.accelerationIncludingGravity.y;
+ z[cnt] = event.accelerationIncludingGravity.z;
+
+ tx = 0; x.forEach( function(n, i){ tx += n; } ); tx /= inertia;
+ ty = 0; y.forEach( function(n, i){ ty += n; } ); ty /= inertia;
+ tz = 0; z.forEach( function(n, i){ tz += n; } ); tz /= inertia;
+
+ pitch = Math.asin((tz / 9.81 > 1)?1:(tz/9.81)) / Math.PI * 180 + 22.5;
+ roll = - Math.asin((ty / 9.81 > 1)?1:(ty/9.81)) / Math.PI * 180;
+ yaw = (yaw + ty) % 360;
+ };
+ })());
+
+ window.addEventListener("click", function(event) {
+ (lv.parentElement)?lv.parentElement.removeChild(lv):{};
+ (rv.parentElement)?rv.parentElement.removeChild(rv):{};
+ video.style.display = "block";
+ video.pause();
+ }, true);
+
+ w = video.videoWidth; h = video.videoHeight;
+ hdeg = w / 360; vdeg = h / 180;
+ scale = lv.width / (fov * hdeg |0);
+ // scale = lv.width / (w / 4);
+ video.play();
+ video.style.display = "none";
+ draw();
+};
--- /dev/null
+body {
+ color: #EEE;
+ background-color: #000;
+ padding-bottom: 2.5em;
+}
+
+/* ====== TOP CONTROL BAR ====== */
+
+#navigation {
+ text-align: center;
+ margin-bottom: 1em; padding: 0 2em;
+ background-color: #333;
+ box-shadow: .125em .125em .25em #000;
+}
+#navigation > a {
+ position: absolute; bottom: .25em;
+ padding: 0 .125em;
+ font-size: 1.5em;
+ text-decoration: none;
+}
+#navigation > a[href="#bookmarks"] { left: 0; }
+#navigation > a[href="#prefs"] { right: 0; }
+
+#bookmarks, #advsearch, #prefs, #multitag {
+ -display: none;
+ height: 0;
+ overflow: hidden;
+}
+
+#editing {
+ position: fixed;
+ bottom: 0; width: 100%;
+ padding: 0 .5em;
+ background-color: #333;
+}
+
+:target a[href="#"] {
+ position: absolute;
+ top: 0; right: 0;
+ font-size: 1.5em;
+ font-weight: bold;
+ text-decoration: none;
+ padding: 0 .25em;
+ z-index: 1;
+}
+
+/* ====== MAIN LIST VIEW ====== */
+
+.itemlist { text-align: center; }
+.itemlist > * { text-align: left; }
+.itemlist .list {
+ display: inline-block;
+ vertical-align: top;
+ width: 99%;
+ -padding: 0 .25em;
+ margin: 0 .5%;
+ margin-bottom: 1em;
+ overflow: hidden;
+}
+
+.itemlist .list img {
+ -width: 1000%; height: 11em;
+ max-width: unset;
+ background-color: #111;
+ object-fit: cover;
+ transform: translate(-05%, 0);
+ margin-left: 50%;
+}
+.itemlist .list:hover img {
+ animation: thumbscroll 8s steps(10, end) infinite;
+}
+@keyframes thumbscroll {
+ from { transform: translate(-05%, 0);}
+ to { transform: translate(-105%, 0);}
+}
+
+.itemlist .list label {
+ display: block;
+ font-weight: bolder;
+ word-break: break-word;
+}
+.itemlist .list .time,
+.itemlist .list .dim {
+ position: absolute; top: 9.75em;
+ background-color: rgba(0,0,0,.5);
+ padding: .125em .25em;
+}
+.itemlist .list .time { right: 0; }
+.itemlist .list .dim { left: 0; }
+.itemlist .list input[type=checkbox] { display: none; }
+.itemlist .list .tag,
+.itemlist .list input[type=checkbox] + label {
+ display: inline-block;
+ background-color: #333;
+ margin-top: .125em;
+ margin-left: 0;
+ padding: 0 .25em;
+ border-radius: 1pt;
+}
+.itemlist .list input[type=checkbox]:checked + label {
+ background-color: #383;
+}
+
+
+/* ====== PAGINATION LIST ====== */
+
+.pagination {
+ display: block;
+ font-size: 1.25em;
+ max-width: 98%;
+ margin: 0 auto;
+ padding: .25em 0;
+ background-color: #333;
+ border-radius: 2pt;
+}
+.pagination a {
+ display: inline-block;
+ padding: 0 .5em;
+ margin: 0 .5em;
+ border-radius: 2pt;
+}
+.pagination a.current {
+ background-color: #BBB;
+}
+
+/* ====== BOOKMARK PANEL ====== */
+
+#bookmarks:target,
+#prefs:target {
+ display: block; position: fixed;
+ top: 50%; left: 50%;
+ transform: translate( -50%, -50% );
+ width: 40em; max-width: 90%;
+ height: 30em; max-height: 90vh;
+ background-color: #333;
+ padding: 0 .5em;
+ z-index: 1;
+ box-shadow: .25em .25em .5em #000;
+ overflow-y: auto;
+}
+
+#bookmarks label {
+ display: block;
+ font-weight: bold;
+ font-size: 1.125em;
+ margin-left: 0;
+ margin-top: .75em;
+ word-break: break-word;
+}
+
+/* ====== ADVSEARCH / FILTER PANEL ====== */
+
+#advsearch:target {
+ display: block; position: fixed;
+ top: 0; width: 100%;
+ height: 30em; max-height: 90vh;
+ background-color: #333;
+ padding: 0 .5em;
+ z-index: 1;
+ box-shadow: .25em .25em .5em #000;
+ overflow-y: auto;
+}
+
+-#advsearch { text-align: center; }
+-#advsearch > * { text-align: left; }
+
+#advsearch .help {
+ width: 95%;
+ margin: 1em auto; padding: 0 .5em;
+ background-color: #444;
+ white-space: pre-line;
+}
+
+#advsearch input.and + label {
+ display: inline-block;
+ vertical-align: top;
+ font-weight: bold;
+}
+#advsearch fieldset.select {
+ display: inline-block;
+ width: 99%;
+ margin: 0 .5%; margin-bottom: .75em; padding: 0 .375em;
+ box-shadow: .125em .125em .25em #000;
+}
+
+#advsearch fieldset.select > label.head {
+ display: none;
+ width: 40%;
+ text-align: right;
+}
+#advsearch fieldset.select > input.cat { display: none; }
+#advsearch fieldset.select > input.cat + label + .catselect { display: none; }
+#advsearch fieldset.select > input.cat + label {
+ display: block;
+ width: 40%;
+ margin: 0; padding: 0 .5em;
+ text-align: right;
+}
+#advsearch fieldset.select > input.cat:checked + label { background-color: #444; }
+#advsearch fieldset.select > input.cat:checked + label + .catselect {
+ display: block; position: absolute;
+ top: 1.5em; bottom: 0; right: 0;
+ width: 60%;
+ padding: 0 .25em;
+ background-color: #444;
+ overflow-y: auto;
+}
+#advsearch fieldset.select > input.cat + label + .catselect > * {
+ display: block;
+ white-space: pre;
+}
+
+#advsearch input.and { display: none; }
+#advsearch input.and + label { display: none; }
+#advsearch input.and + label + fieldset { display: none; }
+#advsearch input.and:checked + label + fieldset,
+#advsearch input.and:first-of-type + label + fieldset { display: inline-block; }
+#advsearch input.and:checked + label + fieldset + input + label,
+#advsearch input.and:first-of-type + label + fieldset + input + label { display: inline-block; }
+#advsearch input.and:checked + label + fieldset + input:checked + label { display: none; }
+#advsearch input.and:first-of-type + label + fieldset + input:checked + label { display: none; }
+
+
+/* ====== MULTITAG DIALOG ====== */
+
+#multitag:target {
+ display: block; position: fixed;
+ bottom: 0; left: 0; width: 100%;
+ height: 30em; max-height: 90vh;
+ background-color: #333;
+ padding: 0 .5em;
+ z-index: 1;
+ box-shadow: .25em .25em .5em #000;
+ overflow-y: auto;
+}
+
+-#multitag { text-align: center; }
+-#multitag > * { text-align: left; }
+
+#multitag fieldset {
+ display: inline-block;
+ width: 99%;
+ margin: 0 .5%; margin-top: 1em;
+}
+
+#multitag fieldset select {
+ width: 100%; height: 10em;
+}
+#multitag fieldset .tagselect {
+ height: 10em;
+ background-color: #444;
+ overflow-y: auto;
+}
+#multitag fieldset .tagselect > label {
+ display: block;
+ white-space: pre;
+}
+
+
+/* ====== VIEW PAGE ====== */
+
+body#view video {
+ display: block;
+ max-height: 80vh;
+ margin: 0 auto;
+}
+
+body#view .tag {
+ display: inline-block;
+ background-color: #333;
+ margin-top: .125em;
+ margin-left: 0;
+ padding: 0 .25em;
+ border-radius: 1pt;
+}
+
+
+/* ====== SCALE BLOCK ELEMENTS ====== */
+
+@media(min-width: 20em) { .itemlist .list, #advsearch fieldset.select, #multitag fieldset { max-width: 49%; } }
+@media(min-width: 40em) { .itemlist .list, #advsearch fieldset.select, #multitag fieldset { max-width: 32%; } }
+@media(min-width: 60em) { .itemlist .list, #advsearch fieldset.select, #multitag fieldset { max-width: 24%; } }
+@media(min-width: 80em) { .itemlist .list, #advsearch fieldset.select, #multitag fieldset { max-width: 19%; } }
+@media(min-width: 100em) { .itemlist .list, #advsearch fieldset.select, #multitag fieldset { max-width: 19em; } }
--- /dev/null
+#!/bin/sh
+
+[ -n "$include_thumbnails" ] && return 0
+include_thumbnails="$0"
+
+gen_thumb(){
+ file="$1"; thumb="$2";
+
+ if [ "${file%.part}" = "${file}" ] && [ ! -s "$thumb" ] && [ -s "$file" ]; then
+ l="$(
+ printf '' \
+ | mplayer -input nodefault-bindings -nosound -vo null -identify -frames 0 "$file" 2>&- \
+ | sed -rn 's:ID_LENGTH=(.*)(\..*)$:\1:p;' \
+ )"
+
+ chunk="$((${l:-10} / 11))"
+
+ tmp="$(mktemp -d)"
+ for cnt in 1 2 3 4 5 6 7 8 9 10; do
+ printf '' \
+ | mplayer -input nodefault-bindings -nosound -benchmark \
+ -noconfig all -really-quiet \
+ -frames 1 -ss "$((cnt * chunk))" \
+ -vf framestep=I \
+ -vo jpeg:quality=100:outdir="${tmp}" \
+ "$file" 2>&-
+ mv "${tmp}/00000001.jpg" "${tmp}/_${cnt}.jpg"
+ done
+ printf '' \
+ | mplayer -input nodefault-bindings -nosound -benchmark \
+ -noconfig all -really-quiet \
+ -vf expand=:::::16/9,scale=320:-2,tile=10:1:10:0:0 \
+ -vo jpeg:quality=100:outdir="${tmp}"\
+ "mf://$tmp/_*.jpg" 2>&-
+
+ #mv "${tmp}/00000001.jpg" "$thumb"
+ convert "${tmp}/00000001.jpg" -interlace line -quality 85 "$thumb"
+ rm -r "${tmp}"
+ fi
+}
--- /dev/null
+#!/bin/sh
+
+. "$_EXEC/indexmeta.sh"
+. "$_EXEC/widgets.sh"
+
+read length width height tags comment short <<-EOF
+ $(meta_info "$_DATA/$ITEM")
+ EOF
+
+printf 'Content-Type: text/html;charset=utf-8\r\n\r\n'
+
+{ printf '
+[!DOCTYPE HTML]
+[html [head [title \n'
+ HTML "${ITEM##*/}"
+ printf ']
+ [meta name="viewport" content="width=device-width"]
+ [link rel=stylesheet href="/cgilite/common.css" ]
+ [link rel=stylesheet href="/style.css" ]
+] [body #view
+ [script type="text/javascript" src="/stereoview.js"\n]
+ [div #navigation
+ [a #t_bookmarks href="#bookmarks" ★]'
+ w_search
+ printf '
+ [a #t_prefs href="#prefs" ⚙]
+ ]'
+ w_bookmarks
+ w_advsearch
+ w_prefs
+ printf '
+ [video #mainvideo controls="controls" preload="auto" [source src="?a=download" type="video/mp4"]]
+ [a "?a=download" Download]
+ [a "javascript:stereoview(180, document.getElementById("mainvideo"));" View 180° Stereoscopic]
+ [a "javascript:stereoview(360, document.getElementById("mainvideo"));" View 360° Stereoscopic]
+ [h1\n %s]
+ [span .time %i:%02imin] [span .dim %ix%i] %s
+ ' "$(HTML "${ITEM##*/}" |sed -r "$w_ascii"' s;[^0-9a-zA-Z&#];&[wbr];g')" \
+ "$((length / 60))" "$((length % 60))" "$width" "$height" \
+ "$(UNSTRING "${tags#tags=}" |sed -r '
+ s;^;,;; s;,+;,;g; s;,$;;; :X s;,-?([^,]+)(,|$); [span .tag\n \1]\2;; tX;'
+ )"
+ printf '
+ [div #editing
+ [form method=POST action="/?a=multitag"
+ [hidden "select" "%s"]' "$(HTML "${ITEM}")"
+ [ -d "$_DATA/${ITEM%/*}/.index/" ] && w_tagging
+ printf '
+ ]
+ ]
+] ]
+'; } | "$_EXEC/cgilite/html-sh.sed"
--- /dev/null
+#!/bin/sh
+
+[ -n "$include_widgets" ] && return 0
+include_widgets="$0"
+
+. "$_EXEC/cgilite/storage.sh"
+
+w_refuri="$(URL "$PATH_INFO")?$(HTML "$QUERY_STRING")"
+
+w_str_s="$(STRING "$SEARCH")"
+w_str_f="$(STRING "$FILTER")"
+
+c_tags="$_DATA/.index/tags.cache"; c_tagcategories="$_DATA/.index/tagcategories.cache"
+if [ ! -s "$c_tags" -o ! -s "$c_tagcategories" ] \
+ || [ "$(find "$_DATA/" -path '*/.index/meta' -newer "$c_tags")" ]
+ then
+ w_tags="$( cn=1
+ find "$_DATA/" -path '*/.index/meta' -print0 \
+ | xargs -r0 sed -r '
+ s;^.*\t.*\t.*\ttags=(.*)\tcomment=.*\t.*\r$;\1;;
+ s;,;\n;g;'"$UNSTRING" \
+ | { sort; printf '\n'; } \
+ | while read -r tag; do
+ [ "$tag" = "$otag" ] \
+ && cn=$((cn + 1)) \
+ || {
+ printf "%i %s\n" "$cn" "$otag"
+ cn=1
+ }
+ otag="$tag"
+ done \
+ |sort -rn |cut -f2- |HTML \
+ |sed "s-
-\n-g; s;\n\n;\n;g;"
+ )"
+ w_tagcategories="$(printf %s "$w_tags" | sed -rn '/:/s;^-?([^:]+):.*$;\1;p' |sort -u )"
+ printf %s "$w_tags" >"$c_tags"
+ printf %s "$w_tagcategories" >"$c_tagcategories"
+else
+ w_tags="$(cat "$c_tags")"
+ w_tagcategories="$(cat "$c_tagcategories")"
+fi
+
+
+[ "$ORDER" = Name ] && w_coname=checked
+[ "$ORDER" = Date ] && w_codate=checked
+[ "$ORDER" = Length ] && w_colength=checked
+[ "$ORDER" = Group ] && w_cogroup=checked
+
+w_bmname=
+w_bmname(){
+ [ "$w_bmname" ] || w_bmname="$(
+ bm="$_DATA/.index/bookmarks"
+ name="$(grep -m1 -aF " search=${w_str_s} filter=${w_str_f}${CR}" "$bm" 2>&-)"
+
+ if [ "$name" ]; then
+ printf '%s' "$name" |cut -f1 |UNSTRING |HTML
+ else
+ printf '%s\t%s' "$SEARCH" "$FILTER" \
+ | sed -r '/^\t$/{ s;\t;All;; q;}
+ /.*\t$/{ s;\t$;;; q;}
+ /^\t.*/{ s;^\t;;;
+ :x; s;(^|[~^|])([^|^~:]+):;\1;; tx;
+ s;\^; and ;g; s;\|;,;g; s;~;not ;g; q;}' \
+ | HTML
+ fi
+ )"
+ printf '%s' "$w_bmname"
+}
+
+w_bookmarks(){
+ local bm="$_DATA/.index/bookmarks" name='' search='' filter=''
+ [ ! -d "${bm%/*}" ] && return 0
+ [ ! -f "$bm" ] && touch "$bm"
+
+ grep -qaF " search=$w_str_s filter=${w_str_f}${CR}" "$bm" && name=Update || name=Add
+
+ printf '[form #bookmarks action=?a=bookmark method=POST
+ [a href="#" x]
+ [hidden "ref" "%s"]
+ [hidden "search" "%s"][hidden "filter" "%s"]
+ [label Name for current page:]
+ [input name="name" value="%s" placeholder="Name" ]
+ [button type="submit" %s]' \
+ "$w_refuri" \
+ "$(HTML "$SEARCH")" "$(HTML "$FILTER")" \
+ "$(w_bmname)" "${name}"
+ [ "$name" ] && printf ' [submit "delete" "delete" Delete]'
+
+ sort "$bm" |while read -r name search filter; do
+ search="${search#search=}" filter="${filter#filter=}" filter="${filter%${CR}}"
+ [ "$search" = "${w_str_s}" -a "$filter" = "${w_str_f}" ] && continue
+
+ name="$(UNSTRING "$name")";
+ search="$(UNSTRING "${search}" |URL)";
+ filter="$(UNSTRING "${filter}" |URL)";
+ printf '[label .link %s]
+ [a .link target=blank href="/?o=Name&s=%s&f=%s" by Name]
+ [a .link target=blank href="/?o=Date&s=%s&f=%s" by Date]
+ [a .link target=blank href="/?o=Length&s=%s&f=%s" by Length]
+ [a .link target=blank href="/?o=Group&s=%s&f=%s" by Group]
+ ' \
+ "$(HTML "$name" |sed 's;,\;;&[wbr];g;')" \
+ "$search" "$filter" \
+ "$search" "$filter" \
+ "$search" "$filter" \
+ "$search" "$filter"
+ done
+ printf ']'
+}
+
+w_search(){
+ printf '
+ [form #search method=GET action=./?
+ [select name=o size=1
+ [option disabled=disabled Order By]
+ [option value=Name %s Name]
+ [option value=Date %s Date]
+ [option value=Length %s Length]
+ [option value=Group %s Group]
+ ]
+ [input name=s placeholder=Search value="%s"]
+ [a #t_avsearch href="#advsearch" Advanced]
+ ]
+ ' \
+ "$w_coname" "$w_codate" "$w_colength" "$w_cogroup" \
+ "$(HTML "$SEARCH")"
+}
+
+w_prefs(){
+ local tm tf td
+
+ tm=''; [ "$(COOKIE mode)" = index ] && tm=' '
+ tf=''; [ "$(COOKIE fakemp4)" = yes ] && tf=checked
+ td=''; [ "$(COOKIE downscale)" = yes ] && td=checked
+
+ printf '
+ [form #prefs method="POST" action="?a=setprefs"
+ [a href="#" x]
+ [hidden "ref" "%s"]
+ [label for=prefs_ps Pagesize]
+ [input #prefs_ps type=number name=pagesize value="%s"][br]
+ [radio "mode" "browse" %s #prefs_modebrowse] [label for=prefs_modebrowse Browse Folders][br]
+ [radio "mode" "index" %s #prefs_modeindex ] [label for=prefs_modeindex View Full Index][br]
+ [checkbox "fakemp4" "yes" %s #prefs_fmp4] [label for=prefs_fmp4 Fake .MP4 file type][br]
+ [checkbox "downscale" "yes" %s #prefs_downscale] [label for=prefs_downscale Prefer downscale to 480p][br]
+ [submit "index" "update" Force Index Update][br]
+ [submit "store" "store" Set Cookie]
+ ]
+ ' \
+ "$w_refuri" "$LISTSIZE" \
+ "${tm:-checked}" "${tm:+checked}" "$tf" "$td"
+}
+
+w_index(){
+ printf '
+ [form #index method="POST" action="?a=spawnindex"
+ [hidden "ref" "%s"]
+ [label Set up for Index view: ]
+ [checkbox "recursive" "yes" #spawn_recursive] [label for=spawn_recursive Include subdirectories]
+ [submit "spawn" "spawn" Set up]
+ ]
+ ' "$w_refuri"
+ return 0
+}
+
+w_advsearch(){
+ local n lbid tag category filter f t d
+ filter="$(HTML "${FILTER}^")"
+
+ printf '[form #advsearch action=./?a=advsearch method=POST
+ [a href="#" X]
+ [p .help Select multiple tags from each category by holding down the [strong Ctrl] key on your keyboard.
+ Refine the search further by setting additional search tags using the [strong "+and"] button.]'
+
+ for n in 1 2 3 4 5 6 7 8 9 10; do
+ f="${filter%%^*}"; filter="${filter#*^}"
+
+ t=''; [ "$f" -a ! "${f%%~*}" ] && t=" "
+
+ lbid="cat_${n}_(none)"
+ printf '[input .and type=checkbox name=and id="and_%i" %s][label for="and_%i" +and
+ ][fieldset .select
+ [radio "pol_%i" "pos" .pol %s #pol_pos_%i"][label for=pol_pos_%i Any]
+ [radio "pol_%i" "neg" .pol %s #pol_neg_%i"][label for=pol_neg_%i None]
+ [label .head Category:]' \
+ $n "${f:+checked}" $n \
+ $n "${t:-checked}" $n $n \
+ $n "${t:+checked}" $n $n
+
+ f="|${f#\~}|"
+ printf '*\n%s\n$\n' "$w_tagcategories" \
+ | while read -r category; do
+ lbid="cat_${n}_${category}"
+
+ t=''
+ [ "$category" = '*' -a "${f%%|${category}:*}" ] && t=checked
+ [ "$category" != '*' -a ! "${f%%|${category}:*}" ] && t=checked
+ [ "$category" != '*' -a ! "${f%%|-${category}:*}" ] && t=checked
+
+ # printf '[radio "cat_%i" "%s" .cat %s id="%s"][label for="%s" %s]
+ # [select name=tag_%s size=10 multiple' \
+ # $n "$category" "$t" "$lbid" "$lbid" "$category" $n
+ printf '[radio "cat_%i" "%s" .cat %s id="%s"][label for="%s" %s]
+ [div .catselect\n' \
+ $n "$category" "$t" "$lbid" "$lbid" "$category"
+
+ printf '%s\n' "$w_tags" \
+ | { [ "$category" = '*' ] && grep -avF ':' || grep -awF "${category}"; } \
+ | { for n in 1 2 3 4 5 6 7 8 9 0; do
+ read -r line && printf '%s\n' "$line" || break;
+ done; # pass 10 lines through without modification
+ sort; # and sort remaining lines
+ } | while read -r tag; do
+ [ "$tag" ] || continue
+ t=''; [ ! "${f%%*|${tag}|*}" ] && t=checked
+ d="${tag#-}"; d="${d#*:}"
+ # printf '[option %s value="%s"\n%s]' "$t" "$tag" "$d"
+ printf '[label [checkbox "tag_%s" "%s" %s] %s]' "$n" "$tag" "$t" "$d"
+ done
+ d="${f##*\$:}" d="${d%%\|*}"
+ [ "$category" = \$ ] && printf '[input name="tag_%i" value="%s"]' "$n" "$(HTML "$d")"
+ printf '\n]'
+ done
+ printf ']'
+ done
+
+ printf '[fieldset .submit [select name=order
+ [option disabled=disabled Order By]
+ [option value=Name %s Name]
+ [option value=Date %s Date]
+ [option value=Length %s Length]
+ [option value=Group %s Group]
+ ][button type=submit Apply Filter]]
+ ]' \
+ "$w_coname" "$w_codate" \
+ "$w_colength" "$w_cogroup"
+}
+
+w_delete(){
+ printf '[a href="#multitag" Add Tags / Remove Tags]
+ [div #multitag [input type="hidden" name="ref" value="%s"]
+ [a href="#" X]
+ [fieldset [legend New:]
+ [submit "op" "filedelete" Delete Files]
+ ]]' "$w_refuri"
+}
+
+w_tagging(){
+ local tag category d
+ printf '[a href="#multitag" Add Tags / Remove Tags]
+ [div #multitag [input type="hidden" name="ref" value="%s"]' "$w_refuri"
+ printf '[a href="#" X]'
+
+ printf 'Tags\n%s\n' "$w_tagcategories" \
+ | while read -r category; do
+ [ "$category" ] || continue
+ # printf '[fieldset [legend %s:][select name=tag size=4 multiple\n' "$category"
+ printf '[fieldset [legend %s:][div .tagselect\n' "$category"
+ printf %s "$w_tags" \
+ | { [ "$category" = 'Tags' ] && grep -avF ':' || grep -awF "${category}"; } \
+ | { for n in 1 2 3 4 5 6 7 8 9 0; do
+ read -r line && printf '%s\n' "$line" || break
+ done;
+ sort;
+ } | while read -r tag; do
+ [ "$tag" ] || continue
+ d="${tag#-}"; d="${d#*:}"
+ # printf '[option value="%s"\n%s]' "$tag" "$d"
+ printf '[label [checkbox "tag" "%s"] %s]\n' "$tag" "$d"
+ done
+ printf ']]'
+ done
+
+ printf '[fieldset [legend New:][textarea name=newtag\n]
+ [submit "op" "del" Remove Tags][submit "op" "add" Add Tags]
+ ]]'
+}