feat: initial commit
This commit is contained in:
commit
392555d587
13 changed files with 428 additions and 0 deletions
22
.drone.yml
Normal file
22
.drone.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Build and deploy site
|
||||||
|
image: nixos/nix
|
||||||
|
commands:
|
||||||
|
- nix --experimental-features 'nix-command flakes' build
|
||||||
|
- cd result/ && nix --experimental-features 'nix-command flakes' run nixpkgs#zip -- -r $DRONE_WORKSPACE/deploy.zip * && cd ..
|
||||||
|
- 'nix --experimental-features "nix-command flakes" run nixpkgs#curl -- --fail --upload-file deploy.zip -H "Authorization: token $GITEA_TOKEN" https://git.ashhhleyyy.dev/api/packages/ash/generic/wfm/$DRONE_COMMIT_SHA/wfm.zip'
|
||||||
|
environment:
|
||||||
|
GITEA_TOKEN:
|
||||||
|
from_secret: GITEA_TOKEN
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event: [push]
|
||||||
|
branch: [main]
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/videos/
|
66
css/main.css
Normal file
66
css/main.css
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plyr {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-list li {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 320px;
|
||||||
|
font-weight: normal
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-card h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 4px;
|
||||||
|
/* opacity: 0; */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .video-card:hover h2 {
|
||||||
|
opacity: 1;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.thumbnail-container {
|
||||||
|
position: relative;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-duration {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 4px;
|
||||||
|
color: white;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(0, 0, 0, .45);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-card:hover .thumbnail-duration {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
max-width: 320px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
43
flake.lock
Normal file
43
flake.lock
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1667395993,
|
||||||
|
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1671268780,
|
||||||
|
"narHash": "sha256-9Okbivo10bcXEGCtmAQNfJt1Zpk6B3tjkSQ2CIXmTCg=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "80c24eeb9ff46aa99617844d0c4168659e35175f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
30
flake.nix
Normal file
30
flake.nix
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
|
flake-utils.lib.eachDefaultSystem(system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
};
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages.default = pkgs.stdenv.mkDerivation rec {
|
||||||
|
name = "wfm";
|
||||||
|
src = ./.;
|
||||||
|
phases = "installPhase";
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/
|
||||||
|
cp -r $src/css/ $out/
|
||||||
|
cp -r $src/js/ $out/
|
||||||
|
cp -r $src/vendor/ $out/
|
||||||
|
cp $src/index.html $out/index.html
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
45
index-media.sh
Executable file
45
index-media.sh
Executable file
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
# I have I feeling I will hate this script by the time I finish writing it
|
||||||
|
|
||||||
|
# Some cursed JSON header
|
||||||
|
echo '{'
|
||||||
|
echo '"videos":['
|
||||||
|
|
||||||
|
isfirst="1"
|
||||||
|
|
||||||
|
for filename in *.mp4; do
|
||||||
|
if [ -f "$filename" ]; then
|
||||||
|
if [ $isfirst -ne "1" ]; then
|
||||||
|
echo ','
|
||||||
|
fi
|
||||||
|
isfirst="0"
|
||||||
|
name="${filename%.mp4}"
|
||||||
|
echo '{'
|
||||||
|
echo '"filename":"'"$filename"'",'
|
||||||
|
echo '"subtitles":['
|
||||||
|
sub_isfirst="1"
|
||||||
|
IFS=$'\n'
|
||||||
|
for subtitleName in "$name".*.vtt; do
|
||||||
|
if [ $sub_isfirst -ne 1 ]; then
|
||||||
|
echo ','
|
||||||
|
fi
|
||||||
|
echo '"'$subtitleName'"'
|
||||||
|
sub_isfirst="0"
|
||||||
|
done
|
||||||
|
echo '],'
|
||||||
|
ffmpeg -n -ss 00:00:01.00 -i "$filename" -vf 'scale=320:320:force_original_aspect_ratio=decrease' -vframes 1 "$name.jpg"
|
||||||
|
echo '"thumbnail":"'"$name"'.jpg",'
|
||||||
|
dimensions=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "$filename")
|
||||||
|
echo '"dimensions":"'"$dimensions"'",'
|
||||||
|
duration=$(ffprobe -v error -sexagesimal -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "$filename")
|
||||||
|
echo '"duration":"'"$duration"'"'
|
||||||
|
echo '}'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Close everything off :)
|
||||||
|
echo ']'
|
||||||
|
echo '}'
|
15
index.html
Normal file
15
index.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Watch</title>
|
||||||
|
<link rel="stylesheet" href="vendor/plyr-3.7.3.css">
|
||||||
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container"></div>
|
||||||
|
<script type="module" src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
181
js/main.js
Normal file
181
js/main.js
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
import Plyr from '../vendor/plyr-3.7.3.mjs';
|
||||||
|
|
||||||
|
const container = document.getElementById('container');
|
||||||
|
|
||||||
|
function displayError(message) {
|
||||||
|
const errorBox = document.createElement('div');
|
||||||
|
errorBox.classList.add('error');
|
||||||
|
errorBox.innerText = message;
|
||||||
|
container.appendChild(errorBox);
|
||||||
|
throw new Error(); // abort
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPlaybackState(videoUrl) {
|
||||||
|
const item = localStorage.getItem('wfm__playerstate');
|
||||||
|
if (item) {
|
||||||
|
const data = JSON.parse(item);
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data, videoUrl)) {
|
||||||
|
return data[videoUrl];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
time: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePlaybackState(videoUrl, state) {
|
||||||
|
const item = localStorage.getItem('wfm__playerstate');
|
||||||
|
let data;
|
||||||
|
if (item) {
|
||||||
|
data = JSON.parse(item);
|
||||||
|
} else {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
data[videoUrl] = state;
|
||||||
|
localStorage.setItem('wfm__playerstate', JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseQuery() {
|
||||||
|
if (window.location.hash.length > 1) {
|
||||||
|
const qs = window.location.hash.substring(1);
|
||||||
|
const query = new URLSearchParams(qs);
|
||||||
|
const source = query.get('source');
|
||||||
|
const sourceType = query.get('source_type');
|
||||||
|
const captions = query.getAll('captions');
|
||||||
|
const captionLanguages = query.getAll('caption_langs');
|
||||||
|
const captionLabels = query.getAll('caption_labels');
|
||||||
|
if (!source || !sourceType) {
|
||||||
|
displayError('missing source or sourceType parameter');
|
||||||
|
}
|
||||||
|
if (captions.length !== captionLanguages.length || captions.length !== captionLabels.length) {
|
||||||
|
displayError('mismatch between captions, caption_langs and caption_labels length');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
source, sourceType,
|
||||||
|
captions, captionLanguages, captionLabels,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTimestamp(timestamp) {
|
||||||
|
const results = /^(0|[1-9]+):([0-9]{2}):([0-9]{2}).([0-9]+)$/g.exec(timestamp);
|
||||||
|
const [_, hours, mins, secs, frac] = results;
|
||||||
|
return {
|
||||||
|
hours, mins, secs, frac,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimestamp({ hours, mins, secs, frac }) {
|
||||||
|
const ms = `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
if (hours !== 0) {
|
||||||
|
return `${hours}:${ms}`;
|
||||||
|
} else {
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = parseQuery();
|
||||||
|
|
||||||
|
if (query !== null) {
|
||||||
|
const {
|
||||||
|
source, sourceType,
|
||||||
|
captions, captionLanguages, captionLabels
|
||||||
|
} = query;
|
||||||
|
const video = document.createElement('video');
|
||||||
|
video.controls = true;
|
||||||
|
video.crossOrigin = 'anonymous';
|
||||||
|
|
||||||
|
const sourceEle = document.createElement('source');
|
||||||
|
sourceEle.src = source;
|
||||||
|
sourceEle.type = sourceType;
|
||||||
|
video.appendChild(sourceEle);
|
||||||
|
|
||||||
|
for (let i = 0; i < captions.length; i++) {
|
||||||
|
const url = captions[i];
|
||||||
|
const lang = captionLanguages[i];
|
||||||
|
const label = captionLabels[i];
|
||||||
|
const track = document.createElement('track');
|
||||||
|
track.kind = 'subtitles';
|
||||||
|
track.label = label;
|
||||||
|
track.srclang = lang;
|
||||||
|
track.src = url;
|
||||||
|
video.appendChild(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
video.autoplay = true;
|
||||||
|
|
||||||
|
container.appendChild(video);
|
||||||
|
|
||||||
|
const player = new Plyr(video, {
|
||||||
|
captions: {
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
iconUrl: 'vendor/plyr-3.7.3.svg',
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on('ready', () => {
|
||||||
|
const state = loadPlaybackState(source);
|
||||||
|
console.log('setting start time', state);
|
||||||
|
player.currentTime = state.time;
|
||||||
|
console.log(player.currentTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on('timeupdate', () => {
|
||||||
|
savePlaybackState(source, {
|
||||||
|
time: player.currentTime,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const BASE = '/videos/';
|
||||||
|
|
||||||
|
fetch(BASE+'media.json')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(({ videos }) => {
|
||||||
|
const ul = document.createElement('ul');
|
||||||
|
ul.classList.add('video-list');
|
||||||
|
for (const video of videos) {
|
||||||
|
//if (!video.filename.includes('telescope')) continue;
|
||||||
|
const query = [
|
||||||
|
['source', BASE+video.filename],
|
||||||
|
['source_type', 'video/mp4'],
|
||||||
|
...video.subtitles.map(subName => ['captions', BASE+subName]),
|
||||||
|
...video.subtitles.map(subName => {
|
||||||
|
const s = subName.split('.');
|
||||||
|
return ['caption_langs', s[s.length - 2]];
|
||||||
|
}),
|
||||||
|
...video.subtitles.map(subName => ['caption_labels', subName]),
|
||||||
|
];
|
||||||
|
const duration = parseTimestamp(video.duration);
|
||||||
|
const thumbnail = video.thumbnail;
|
||||||
|
const thumbnailContainer = document.createElement('div');
|
||||||
|
thumbnailContainer.classList.add('thumbnail-container');
|
||||||
|
const durationEle = document.createElement('p');
|
||||||
|
durationEle.classList.add('thumbnail-duration');
|
||||||
|
durationEle.innerText = formatTimestamp(duration);
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = BASE+thumbnail;
|
||||||
|
img.classList.add('thumbnail');
|
||||||
|
const qs = new URLSearchParams(query).toString();
|
||||||
|
const ele = document.createElement('li');
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.classList.add('video-card');
|
||||||
|
a.href = '#' + qs;
|
||||||
|
const title = document.createElement('h2');
|
||||||
|
title.innerText = video.filename;
|
||||||
|
thumbnailContainer.appendChild(img);
|
||||||
|
thumbnailContainer.appendChild(durationEle);
|
||||||
|
a.appendChild(thumbnailContainer);
|
||||||
|
a.appendChild(title);
|
||||||
|
ele.appendChild(a);
|
||||||
|
ul.appendChild(ele);
|
||||||
|
}
|
||||||
|
container.appendChild(ul);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('hashchange', (e) => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
1
result
Symbolic link
1
result
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/dhcsck44wqr3jr3ir38r6a15rgsmlhfx-wfm
|
21
vendor/plyr-3.7.3.LICENSE.md
vendored
Normal file
21
vendor/plyr-3.7.3.LICENSE.md
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Sam Potts
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
1
vendor/plyr-3.7.3.css
vendored
Normal file
1
vendor/plyr-3.7.3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
vendor/plyr-3.7.3.mjs
vendored
Normal file
1
vendor/plyr-3.7.3.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
1
vendor/plyr-3.7.3.svg
vendored
Normal file
1
vendor/plyr-3.7.3.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.6 KiB |
Loading…
Reference in a new issue