Initial Commit
This commit is contained in:
commit
fdeba8fd41
17 changed files with 2290 additions and 0 deletions
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
*
|
||||||
|
!src
|
||||||
|
!package.json
|
||||||
|
!pnpm-lock.yaml
|
||||||
|
!build.mjs
|
||||||
|
!tsconfig.json
|
||||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
dist/
|
||||||
40
.github/workflows/docker.yml
vendored
Normal file
40
.github/workflows/docker.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
name: Build Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository != 'lillithkt/template'
|
||||||
|
env:
|
||||||
|
REPO: ${{ github.repository.name }}
|
||||||
|
steps:
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3.6.0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3.10.0
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3.4.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: downcase REPO
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "REPO=${GITHUB_REPOSITORY##*/}" >>${GITHUB_ENV}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6.15.0
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ghcr.io/lillithkt/${{ env.REPO }}:latest
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
25
.github/workflows/test.yml
vendored
Normal file
25
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4.2.0
|
||||||
|
with:
|
||||||
|
node-version: 22.13.1
|
||||||
|
cache: 'pnpm'
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
- name: Test
|
||||||
|
run: pnpm test
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
output/
|
||||||
13
.vscode/launch.json
vendored
Normal file
13
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Program",
|
||||||
|
"program": "${workspaceFolder}/dist/index.js",
|
||||||
|
"request": "launch",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"type": "node",
|
||||||
|
"preLaunchTask": "${defaultBuildTask}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
.vscode/settings.json
vendored
Normal file
12
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"files.eol": "\n",
|
||||||
|
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": "explicit",
|
||||||
|
"source.organizeImports": "explicit"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
.vscode/tasks.json
vendored
Normal file
15
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "pnpm build",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
FROM node:22.13.1-alpine
|
||||||
|
|
||||||
|
ENV NODE_ENV=development
|
||||||
|
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
WORKDIR /src
|
||||||
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
RUN pnpm prune --prod
|
||||||
|
|
||||||
|
FROM node:22.13.1-alpine
|
||||||
|
|
||||||
|
RUN apk add ffmpeg
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
WORKDIR /dist
|
||||||
|
COPY --from=0 /src/dist/* ./
|
||||||
|
|
||||||
|
CMD ["node", "."]
|
||||||
12
README.md
Normal file
12
README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Lillith's Template
|
||||||
|
|
||||||
|
My template for server projects
|
||||||
|
|
||||||
|
## Contains:
|
||||||
|
|
||||||
|
- Typescript
|
||||||
|
- Esbuild Single-File output
|
||||||
|
- ESLint
|
||||||
|
- Two Stage Docker build for minimum download size
|
||||||
|
- GHCR Upload for the docker image
|
||||||
|
- c/i For linting and testing compiling
|
||||||
13
build.mjs
Normal file
13
build.mjs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import * as esbuild from "esbuild";
|
||||||
|
|
||||||
|
esbuild.build({
|
||||||
|
entryPoints: ["src/index.ts"],
|
||||||
|
bundle: true,
|
||||||
|
outfile: "dist/index.js",
|
||||||
|
platform: "node",
|
||||||
|
target: ["node21"],
|
||||||
|
sourcemap: true,
|
||||||
|
loader: {
|
||||||
|
".node": "copy",
|
||||||
|
},
|
||||||
|
});
|
||||||
34
eslint.config.mjs
Normal file
34
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||||
|
import prettier from "eslint-plugin-prettier";
|
||||||
|
import globals from "globals";
|
||||||
|
import tsParser from "@typescript-eslint/parser";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig([globalIgnores(["**/dist/"]), {
|
||||||
|
extends: compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"),
|
||||||
|
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": typescriptEslint,
|
||||||
|
prettier,
|
||||||
|
},
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: tsParser,
|
||||||
|
},
|
||||||
|
}]);
|
||||||
35
package.json
Normal file
35
package.json
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "template",
|
||||||
|
"description": "Starter template for lillith's projects",
|
||||||
|
"homepage": "https://github.com/lillithkt/template",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=21.5.0"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@9.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.mjs",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"test": "tsc --noEmit && eslint .",
|
||||||
|
"lint": "eslint ."
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.3.0",
|
||||||
|
"@eslint/js": "^9.22.0",
|
||||||
|
"@types/fluent-ffmpeg": "^2.1.27",
|
||||||
|
"@types/node": "^22.13.10",
|
||||||
|
"esbuild": "^0.25.1",
|
||||||
|
"eslint": "^9.22.0",
|
||||||
|
"eslint-config-prettier": "^10.1.1",
|
||||||
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
|
"globals": "^16.0.0",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"prettier-eslint": "^16.3.0",
|
||||||
|
"typescript": "^5.8.2",
|
||||||
|
"typescript-eslint": "^8.26.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
1947
pnpm-lock.yaml
generated
Normal file
1947
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
54
src/index.ts
Normal file
54
src/index.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import ffmpeg from "fluent-ffmpeg";
|
||||||
|
import { Event, getCurrentEvent, loadEvents } from "setlist";
|
||||||
|
|
||||||
|
const commandBase = ffmpeg()
|
||||||
|
.input("rtsp://stream.furality.online/live/club")
|
||||||
|
.seekInput("0:01")
|
||||||
|
.audioCodec("copy")
|
||||||
|
.videoCodec("copy")
|
||||||
|
.outputOption("-y");
|
||||||
|
|
||||||
|
async function runCommand(event: Event) {
|
||||||
|
const eventEnd = new Date(event.end);
|
||||||
|
const now = new Date();
|
||||||
|
const secondsUntilEventEnd = Math.max(
|
||||||
|
0,
|
||||||
|
Math.floor((eventEnd.getTime() - now.getTime()) / 1000)
|
||||||
|
);
|
||||||
|
const command = commandBase.clone().duration(5);
|
||||||
|
|
||||||
|
const filename = event.name.replace(/ /g, "_");
|
||||||
|
|
||||||
|
console.log(`Saving ${secondsUntilEventEnd}s of 1440p to ${filename}.mkv`);
|
||||||
|
console.log(process.cwd());
|
||||||
|
command.save(`./output/${filename}.mkv`);
|
||||||
|
await new Promise<void>((res) => {
|
||||||
|
command.on("start", console.log);
|
||||||
|
command.on("error", (err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
command.on("end", (cmd) => {
|
||||||
|
console.log(cmd);
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log("Command Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await loadEvents();
|
||||||
|
while (true) {
|
||||||
|
// find next
|
||||||
|
const event = getCurrentEvent();
|
||||||
|
if (!event?.name) {
|
||||||
|
console.error("No more sets!");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
await runCommand(event);
|
||||||
|
|
||||||
|
// sleep 5s
|
||||||
|
await new Promise((res) => setTimeout(res, 5000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
43
src/setlist.ts
Normal file
43
src/setlist.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
export interface Event {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
description?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
type: string;
|
||||||
|
imageUrl: string;
|
||||||
|
questCompatible: boolean;
|
||||||
|
pcCompatible: boolean;
|
||||||
|
livestream: boolean;
|
||||||
|
status: string;
|
||||||
|
supporter: boolean;
|
||||||
|
rsvp: boolean;
|
||||||
|
vote: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let events: Event[] | null = null;
|
||||||
|
|
||||||
|
export async function loadEvents() {
|
||||||
|
console.log("Loading Events");
|
||||||
|
const { data }: { data: Event[] } = await fetch(
|
||||||
|
"https://api.fynn.ai/v2/event"
|
||||||
|
).then((i) => i.json());
|
||||||
|
events = data.filter((i) => i.type === "dj");
|
||||||
|
console.log(`Loaded ${events.length} DJ events`);
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentEvent(): Event | null {
|
||||||
|
if (!events) return null;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
return (
|
||||||
|
events.find((event) => {
|
||||||
|
const start = new Date(event.start);
|
||||||
|
const end = new Date(event.end);
|
||||||
|
return now >= start && now <= end;
|
||||||
|
}) || null
|
||||||
|
);
|
||||||
|
}
|
||||||
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue