paywall the extras
Some checks failed
ci / build (push) Failing after 2s
ci / Tests & Checks (push) Failing after 1s

This commit is contained in:
CJ_Clippy 2025-08-11 22:32:34 -08:00
parent afc9e2d1c8
commit 93d67045a1
7 changed files with 402 additions and 92 deletions

View File

@ -50,6 +50,13 @@ COPY . .
# Build the app
RUN npm run build
# Setup Python virtualenv
RUN python3 -m venv /app/venv
ENV PATH="/app/venv/bin:$PATH"
# Install torrentfile & other python deps
RUN ./venv/pip install -r requirements.txt
# Expose the port
EXPOSE 5000

View File

@ -1,12 +1,12 @@
{
"name": "futureporn",
"version": "2.0.1",
"version": "2.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "futureporn",
"version": "2.0.1",
"version": "2.2.1",
"dependencies": {
"@aws-sdk/client-s3": "3.726.1",
"@aws-sdk/s3-request-presigner": "^3.844.0",
@ -32,6 +32,7 @@
"canvas": "^3.1.2",
"chokidar-cli": "^3.0.0",
"concurrently": "^9.2.0",
"create-torrent": "^6.1.0",
"date-fns": "^4.1.0",
"fastify": "^5.4.0",
"fastify-plugin": "^5.0.1",
@ -58,6 +59,7 @@
"sharp": "^0.34.3",
"slugify": "^1.6.6",
"slvtt": "^0.3.4",
"ssh2-sftp-client": "^12.0.1",
"ts-node": "^10.9.2",
"tsup": "^8.5.0",
"tsx": "^4.20.3",
@ -5065,6 +5067,15 @@
"node": ">= 0.4"
}
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@ -5244,6 +5255,15 @@
"bare-path": "^3.0.0"
}
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@ -5264,6 +5284,27 @@
],
"license": "MIT"
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"license": "BSD-3-Clause",
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/bencode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/bencode/-/bencode-4.0.0.tgz",
"integrity": "sha512-AERXw18df0pF3ziGOCyUjqKZBVNH8HV3lBxnx5w0qtgMIk4a1wb9BkcCQbkp9Zstfrn/dzRwl7MmUHHocX3sRQ==",
"license": "MIT",
"dependencies": {
"uint8-util": "^2.2.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -5311,6 +5352,12 @@
"ieee754": "^1.1.13"
}
},
"node_modules/block-iterator": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/block-iterator/-/block-iterator-1.1.1.tgz",
"integrity": "sha512-DrjdVWZemVO4iBf4tiOXjUrY5cNesjzy0t7sIiu2rdl8cOCHRxAgKjSJFc3vBZYYMMmshUAxajl8QQh/uxXTKQ==",
"license": "MIT"
},
"node_modules/boolean": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
@ -5371,6 +5418,21 @@
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/buildcheck": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz",
"integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==",
"optional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/bundle-require": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz",
@ -5768,6 +5830,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"engines": [
"node >= 6.0"
],
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"node_modules/concurrently": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz",
@ -5963,12 +6040,66 @@
}
}
},
"node_modules/cpu-features": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"buildcheck": "~0.0.6",
"nan": "^2.19.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"license": "MIT"
},
"node_modules/create-torrent": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/create-torrent/-/create-torrent-6.1.0.tgz",
"integrity": "sha512-War593HCsg4TotHgMGWTJqnDHN0pmEU2RM13xUzzSZ78TpRNOC2bbcsC5yMO3pqIkedHEWFzYNqH1yhwuuBYTg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"bencode": "^4.0.0",
"block-iterator": "^1.1.1",
"fast-readable-async-iterator": "^2.0.0",
"is-file": "^1.0.0",
"join-async-iterator": "^1.1.1",
"junk": "^4.0.1",
"minimist": "^1.2.8",
"once": "^1.4.0",
"piece-length": "^2.0.1",
"queue-microtask": "^1.2.3",
"run-parallel": "^1.2.0",
"uint8-util": "^2.2.5"
},
"bin": {
"create-torrent": "bin/cmd.js"
},
"engines": {
"node": ">=12"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -6795,6 +6926,12 @@
"fast-decode-uri-component": "^1.0.1"
}
},
"node_modules/fast-readable-async-iterator": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-readable-async-iterator/-/fast-readable-async-iterator-2.0.0.tgz",
"integrity": "sha512-8Sld+DuyWRIftl86ZguJxR2oXCBccOiJxrY/Rj9/7ZBynW8pYMWzIcqxFL1da+25jaWJZVa+HHX/8SsA21JdTA==",
"license": "MIT"
},
"node_modules/fast-redact": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
@ -7891,6 +8028,12 @@
"node": ">=0.10.0"
}
},
"node_modules/is-file": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-file/-/is-file-1.0.0.tgz",
"integrity": "sha512-ZGMuc+xA8mRnrXtmtf2l/EkIW2zaD2LSBWlaOVEF6yH4RTndHob65V4SwWWdtGKVthQfXPVKsXqw4TDUjbVxVQ==",
"license": "MIT"
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@ -8036,6 +8179,12 @@
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
"license": "BSD-3-Clause"
},
"node_modules/join-async-iterator": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/join-async-iterator/-/join-async-iterator-1.1.1.tgz",
"integrity": "sha512-ATse+nuNeKZ9K1y27LKdvPe/GCe9R/u9dw9vI248e+vILeRK3IcJP4JUPAlSmKRCDK0cKhEwfmiw4Skqx7UnGQ==",
"license": "MIT"
},
"node_modules/joycon": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
@ -8155,6 +8304,18 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/junk": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz",
"integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==",
"license": "MIT",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -8563,6 +8724,13 @@
"thenify-all": "^1.0.0"
}
},
"node_modules/nan": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
"integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
"license": "MIT",
"optional": true
},
"node_modules/nano-spawn": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz",
@ -9127,6 +9295,12 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/piece-length": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/piece-length/-/piece-length-2.0.1.tgz",
"integrity": "sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug==",
"license": "MIT"
},
"node_modules/pino": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz",
@ -9563,7 +9737,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [
{
"type": "github",
@ -9822,7 +9995,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [
{
"type": "github",
@ -9899,6 +10071,12 @@
"node": ">=10"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/secure-json-parse": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.2.tgz",
@ -10224,6 +10402,40 @@
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
},
"node_modules/ssh2": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz",
"integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
"bcrypt-pbkdf": "^1.0.2"
},
"engines": {
"node": ">=10.16.0"
},
"optionalDependencies": {
"cpu-features": "~0.0.10",
"nan": "^2.20.0"
}
},
"node_modules/ssh2-sftp-client": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-12.0.1.tgz",
"integrity": "sha512-ICJ1L2PmBel2Q2ctbyxzTFZCPKSHYYD6s2TFZv7NXmZDrDNGk8lHBb/SK2WgXLMXNANH78qoumeJzxlWZqSqWg==",
"license": "Apache-2.0",
"dependencies": {
"concat-stream": "^2.0.0",
"ssh2": "^1.16.0"
},
"engines": {
"node": ">=18.20.4"
},
"funding": {
"type": "individual",
"url": "https://square.link/u/4g7sPflL"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@ -11054,6 +11266,12 @@
"node": "*"
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"license": "Unlicense"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -11067,6 +11285,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@ -11122,6 +11346,15 @@
"node": ">=0.8.0"
}
},
"node_modules/uint8-util": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/uint8-util/-/uint8-util-2.2.5.tgz",
"integrity": "sha512-/QxVQD7CttWpVUKVPz9znO+3Dd4BdTSnFQ7pv/4drVhC9m4BaL2LFHTkJn6EsYoxT79VDq/2Gg8L0H22PrzyMw==",
"license": "MIT",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",

View File

@ -4,11 +4,12 @@
"version": "2.2.1",
"type": "module",
"scripts": {
"dev": "concurrently npm:dev:serve npm:dev:build npm:dev:worker npm:dev:compose",
"dev": "concurrently npm:dev:serve npm:dev:build npm:dev:worker npm:dev:compose npm:dev:sftp",
"dev:serve": "npx @dotenvx/dotenvx run -f ../../.env.development.local -- tsx watch ./src/index.ts",
"dev:compose": "docker compose -f compose.development.yaml up",
"dev:worker": "npx @dotenvx/dotenvx run -e GRAPHILE_LOGGER_DEBUG=1 -f ../../.env.development.local -- tsx watch ./src/worker.ts",
"dev:build": "chokidar 'src/**/*.{js,ts}' -c tsup --clean",
"dev:sftp": "docker run -p 2222:22 --rm atmoz/sftp user:pass:::watch",
"start": "echo please use either start:server or start:worker; exit 1",
"start:server": "tsx ./src/index.ts",
"start:worker": "tsx ./src/worker.ts",
@ -62,6 +63,7 @@
"canvas": "^3.1.2",
"chokidar-cli": "^3.0.0",
"concurrently": "^9.2.0",
"create-torrent": "^6.1.0",
"date-fns": "^4.1.0",
"fastify": "^5.4.0",
"fastify-plugin": "^5.0.1",
@ -88,6 +90,7 @@
"sharp": "^0.34.3",
"slugify": "^1.6.6",
"slvtt": "^0.3.4",
"ssh2-sftp-client": "^12.0.1",
"ts-node": "^10.9.2",
"tsup": "^8.5.0",
"tsx": "^4.20.3",

View File

@ -1,2 +1,3 @@
ultralytics
vcsi
torrentfile

View File

@ -324,8 +324,8 @@ export default async function vtubersRoutes(
const items = vtuber.vods.map(vod => ({
title: `Stream on ${vod.streamDate.toDateString()}`,
link: `${env.ORIGIN}/vod/${vod.id}`,
guid: `${env.ORIGIN}/vod/${vod.id}`,
link: `${env.ORIGIN}/vods/${vod.id}`,
guid: `${env.ORIGIN}/vods/${vod.id}`,
pubDate: vod.streamDate.toUTCString(),
description: vod.notes || 'No description available',
}));
@ -337,7 +337,7 @@ export default async function vtubersRoutes(
.view('/feed.hbs', {
title,
description: vtuber.description || title,
link: `${env.ORIGIN}/vtuber/${vtuber.slug || vtuber.id}`,
link: `${env.ORIGIN}/vtubers/${vtuber.slug || vtuber.id}`,
items,
}, { layout: 'layouts/xml.hbs' });
});

View File

@ -49,7 +49,8 @@
<video id="player" class="video-js vjs-fluid" controls preload="auto" poster="{{getCdnUrl vod.thumbnail}}"
data-setup='{}' data-playlist="{{signedHlsUrl vod.hlsPlaylist}}">
<source src="/hls/{{vod.id}}/master.m3u8" type="application/x-mpegURL">
<track kind="captions" src="{{getCdnUrl vod.asrVttKey}}" srclang="en" label="English" default>
{{#if (hasRole "supporterTier1" user)}}
<track kind="captions" src="{{getCdnUrl vod.asrVttKey}}" srclang="en" label="English" default>{{/if}}
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a
web browser that
@ -99,9 +100,9 @@
<h1>
{{#each vod.vtubers}}
{{this.displayName}}{{#unless @last}}, {{/unless}}
<a href="/vtubers/{{this.slug}}">{{this.displayName}}</a>{{#unless @last}}, {{/unless}}
{{/each}}
{{formatDate vod.streamDate}}
- {{formatDate vod.streamDate}}
</h1>
<div class="overflow-auto">
@ -119,21 +120,7 @@
<h2>Downloads</h2>
<h3>Raw Segments</h3>
{{#if vod.segmentKeys}}
<ul>
{{#each vod.segmentKeys}}
<li><a data-source-video="{{getCdnUrl this.key}}" data-file-name="{{this.name}}" target="_blank"
download="{{this.name}}" x-on:click.prevent="download($el.dataset.sourceVideo, $el.dataset.fileName)"
href="{{getCdnUrl this.key}}">{{icon "download" 24}} {{this.name}}</a>
</li>
{{/each}}
</ul>
{{else}}
<article>
This VOD has no file segments.
</article>
{{/if}}
<h3>VOD</h3>
{{#if vod.sourceVideo}}
@ -168,25 +155,67 @@
Magnet Link is processing.
</article>
{{/if}}
<h4>Raw Segments</h4>
{{#if vod.segmentKeys}}
{{#if (hasRole "supporterTier1" user)}}
<ul>
{{#each vod.segmentKeys}}
<li><a data-source-video="{{getCdnUrl this.key}}" data-file-name="{{this.name}}" target="_blank"
download="{{this.name}}" x-on:click.prevent="download($el.dataset.sourceVideo, $el.dataset.fileName)"
href="{{getCdnUrl this.key}}">{{icon "download" 24}} {{this.name}}</a>
</li>
{{/each}}
</ul>
{{else}}
<p>
<a href="/perks">{{icon "patreon" 24}}</a>
<del>
Raw Segments CDN Download
</del>
</p>
{{/if}}
{{else}}
<article>
This VOD has no file segments.
</article>
{{/if}} {{!-- end of raw segments --}}
{{else}}
<article>
Video Source is processing.
</article>
{{/if}}
{{/if}} {{!-- end of vod.sourceVideo --}}
<h3>HLS Playlist</h3>
<h4>HLS Playlist</h4>
{{#if vod.hlsPlaylist}}
{{#if (hasRole "supporterTier1" user)}}
<a href="{{signedHlsUrl vod.hlsPlaylist}}">{{signedHlsUrl vod.hlsPlaylist}}</a>
{{else}}
<p>
<a href="/perks">{{icon "patreon" 24}}</a>
<del>
HLS Playlist CDN Download
</del>
</p>
{{/if}}
{{else}}
<article>
HLS Playlist is processing.
</article>
{{/if}}
<h3>Thumbnail Image</h3>
<h4>Thumbnail Image</h4>
{{#if vod.thumbnail}}
<img src="{{getCdnUrl vod.thumbnail}}" alt="{{this.vtuber.displayName}} thumbnail">
<div class="mx-5"></div>
@ -198,13 +227,29 @@
<h3>Funscript (sex toy sync)</h3>
<h4>Funscripts (sex toy sync)</h4>
{{#if vod.funscript}}
<a id="funscript" data-url="{{getCdnUrl vod.funscript}}" data-file-name="{{basename vod.funscript}}"
x-on:click.prevent="download($el.dataset.url, $el.dataset.fileName)" href="{{getCdnUrl vod.funscript}}"
alt="{{this.vtuber.displayName}} funscript file">{{icon "download" 24}}
{{this.vtuber.displayName}}
Funscript</a>
{{#if (hasRole "supporterTier1" user)}}
<p>
{{!-- @todo change this id to funscript-vibrate --}}
<a id="funscript" data-url="{{getCdnUrl vod.funscript}}" data-file-name="{{basename vod.funscript}}"
x-on:click.prevent="download($el.dataset.url, $el.dataset.fileName)" href="{{getCdnUrl vod.funscript}}"
alt="{{this.vtuber.displayName}} funscript file">{{icon "download" 24}}
{{this.vtuber.displayName}}
vibrate.funscript</a>
</p>
<p>
<s><a id="funscript-thrust">{{icon "download" 24}} thrust.funscript</a></s> (coming soon)
</p>
{{else}}
<p>
<a href="/perks">{{icon "patreon" 24}}</a>
<del>
Funscripts
</del>
</p>
{{/if}}
<div class="mx-5"></div>
{{else}}
<article>
@ -213,11 +258,25 @@
{{/if}}
<h3>Closed Captions / Subtitles</h3>
<h4>Closed Captions / Subtitles</h4>
{{#if vod.asrVttKey}}
{{#if (hasRole "supporterTier1" user)}}
<a id="asr-vtt" data-url="{{getCdnUrl vod.asrVttKey}}" data-file-name="{{basename vod.asrVttKey}}"
x-on:click.prevent="download($el.dataset.url, $el.dataset.fileName)" href="{{getCdnUrl vod.asrVttKey}}"
alt="Closed Captions VTT file">{{icon "download" 24}} Closed Captions</a>
{{else}}
<p>
<a href="/perks">{{icon "patreon" 24}}</a>
<del>
Closed Captions / Subtitles
</del>
</p>
{{/if}}
{{else}}
<article>
Closed captions are processing.
@ -229,21 +288,24 @@
{{#if (isModerator user)}}
<h3>Storyboard Images</h3>
{{#if vod.slvttVTTKey}}
<a id="slvtt" data-url="{{getCdnUrl vod.slvttVTTKey}}" data-file-name="{{basename vod.slvttVTTKey}}"
x-on:click.prevent="download($el.dataset.url, $el.dataset.fileName)" href="{{getCdnUrl vod.slvttVTTKey}}"
alt="slvttVTTKey">{{icon "download" 24}} slvttVTTKey</a>
{{else}}
<article>
Storyboard Images are processing
</article>
{{/if}}
<h2>Moderator section</h2>
<h2>Moderator Controls</h2>
<button hx-post="/vods/{{vod.id}}/process" hx-target="body">{{icon "processing" 24}} Re-Schedule Vod
Processing</button>
<h3>Storyboard Images</h3>
{{#if vod.slvttVTTKey}}
<a id="slvtt" data-url="{{getCdnUrl vod.slvttVTTKey}}" data-file-name="{{basename vod.slvttVTTKey}}"
x-on:click.prevent="download($el.dataset.url, $el.dataset.fileName)" href="{{getCdnUrl vod.slvttVTTKey}}"
alt="slvttVTTKey">{{icon "download" 24}} slvttVTTKey</a>
{{else}}
<article>
Storyboard Images are processing
</article>
{{/if}}
<h3>Controls</h3>
<button hx-post="/vods/{{vod.id}}/process" hx-target="body">{{icon "processing" 24}} Re-Schedule Vod
Processing</button>
</article>
{{/if}}
@ -287,7 +349,7 @@
</script>
<script src="https://cdn.jsdelivr.net/gh/Teyuto/videojs-vtt-thumbnails@main/src/videojs-vtt-thumbnails.min.js"></script>
{{!-- <script src="https://cdn.jsdelivr.net/gh/Teyuto/videojs-vtt-thumbnails@main/src/videojs-vtt-thumbnails.min.js"></script> --}}
{{!-- <script src="https://cdn.jsdelivr.net/npm/video.js@8.23.3/dist/video.min.js"></script> --}}
{{!-- <script src="https://cdn.jsdelivr.net/npm/videojs-vtt-thumbnails@0.0.13/dist/videojs-vtt-thumbnails.min.js"></script> --}}
{{!-- <script type="module">
@ -297,58 +359,25 @@
caca()
</script> --}}
{{!--
Script 4: Initialize the buttplug plugin and pass in the funscript URL from the DOM
--}}
<script type="module">
//import videojs from 'https://cdn.jsdelivr.net/npm/video.js@8/+esm'
//import 'https://esm.sh/gh/mayeaux/videojs-vtt-thumbnails';
const player = videojs('#player');
const funscriptElement = document.querySelector('#funscript');
//if (funscriptElement) {
// player.buttplug({
// funscriptUrl: funscriptElement.dataset.url
// });
//} else {
// console.error('Element with id "funscript" not found.');
//}
//console.log('vtt-thumbnails')
//console.log(player.vttThumbnails)
// Initialize videojs-vtt-thumbnails
//player.vttThumbnails({
// src: "{{getCdnUrl vod.slvttVTTKey}}",
// showTimestamp: true
//});
player.vttThumbnails({
//spriteUrl: 'path/to/sprite.jpg',
vttData: {
url: '{{getCdnUrl vod.slvttVTTKey}}'
}
});
</script>
{{!--
Script 1: Load Buttplug.js from Skypack CDN and expose it to window.buttplug
--}}
{{!-- <script type="module">
<script type="module">
import {
ButtplugClient,
ButtplugBrowserWebsocketClientConnector
} from 'https://cdn.skypack.dev/buttplug';
window.buttplug = { ButtplugClient, ButtplugBrowserWebsocketClientConnector };
</script> --}}
</script>
{{!--
Script 2: Define reusable utility components for funscript and buttplug indicators
--}}
{{!-- <script type="module">
<script type="module">
const Plugin = videojs.getPlugin('plugin');
const createIndicator = (Component, className, defaultText) => {
@ -370,13 +399,13 @@
'ButtplugIndicator',
createIndicator(videojs.getComponent('Component'), 'vjs-buttplug-indicator', 'Buttplug.js not connected')
);
</script> --}}
</script>
{{!--
Script 3: Main ButtplugPlugin class — handles connection, syncing, and device control
--}}
{{!-- <script type="module">
<script type="module">
class ButtplugPlugin extends videojs.getPlugin('plugin') {
constructor(player, options) {
super(player, options);
@ -568,9 +597,42 @@
}
videojs.registerPlugin('buttplug', ButtplugPlugin);
</script> --}}
</script>
{{!--
Script 4: Initialize the buttplug plugin and pass in the funscript URL from the DOM
--}}
<script type="module">
//import videojs from 'https://cdn.jsdelivr.net/npm/video.js@8/+esm'
//import 'https://esm.sh/gh/mayeaux/videojs-vtt-thumbnails';
const player = videojs('#player');
const funscriptElement = document.querySelector('#funscript');
if (funscriptElement) {
player.buttplug({
funscriptUrl: funscriptElement?.dataset.url || null
});
} else {
console.error('Element with id "funscript" not found.');
}
//console.log('vtt-thumbnails')
//console.log(player.vttThumbnails)
// Initialize videojs-vtt-thumbnails
//player.vttThumbnails({
// src: "{{getCdnUrl vod.slvttVTTKey}}",
// showTimestamp: true
//});
//player.vttThumbnails({
// //spriteUrl: 'path/to/sprite.jpg',
// vttData: {
// url: '{{getCdnUrl vod.slvttVTTKey}}'
// }
//});
</script>

View File

@ -40,8 +40,7 @@
</video> --}}
<h1>
{{vtuber.displayName}} <a href="/vtubers/{{vtuber.slug}}/rss"
alt="RSS feed for {{vtuber.displayName}}">{{icon "rss" 32}}</a>
{{vtuber.displayName}}
</h1>
<div class="grid">
@ -288,6 +287,11 @@
<h2><s>🚧Toys</s></h2>
<p>🚧</p>
<h2>Feeds</h2>
<p>
<a href="/vtubers/{{vtuber.slug}}/rss" alt="RSS feed for {{vtuber.displayName}}">RSS {{icon "rss" 32}}</a>
</p>
<div class="overflow-auto">