-
Notifications
You must be signed in to change notification settings - Fork 0
limelight-recorder #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
7425783
e231a77
e7137a0
da00e95
4a8b4e1
c01f5ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| FROM node:20-alpine | ||
| WORKDIR /usr/src/app | ||
| COPY ./dist /usr/src/app | ||
| EXPOSE 4590 | ||
| CMD ["node","bundle.js"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // בס"ד | ||
| import ping from "ping"; | ||
|
|
||
| async function pingRobot(robotIp: string) { | ||
| const result = await ping.promise.probe(robotIp, { timeout: 10 }); | ||
| return result; | ||
| } | ||
|
|
||
| export async function pingCameras(): Promise<boolean> { | ||
| const robotIp = "10.45.90.2"; | ||
| const isUp = await pingRobot(robotIp); | ||
|
|
||
| if (isUp.alive) { | ||
| console.log(`Robot at ${robotIp} is online.`); | ||
| return true; | ||
| } | ||
| console.log(`Robot at ${robotIp} is offline.`); | ||
| return false; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| //בס"ד | ||
| import type { ChildProcess } from "child_process"; | ||
| import { spawn } from "child_process"; | ||
| import { timeStamp } from "console"; | ||
| import ffmpegPath from "ffmpeg-static"; | ||
|
|
||
| export class RecordingProcess { | ||
| public ffmpegProcess: ChildProcess | null = null; | ||
| public cameraUrl: string; | ||
| public outputFile: string; | ||
|
|
||
| // --- CONSTRUCTOR --- | ||
| public constructor(cameraUrl: string, outputFile: string) { | ||
| const time: Date = new Date(); | ||
| this.outputFile = outputFile | ||
| + "/timestamp_" | ||
| + time.getHours() | ||
| + ":" | ||
| + time.getMinutes() | ||
| + ".mp4"; | ||
|
|
||
| this.cameraUrl = | ||
| cameraUrl === "right" ? "http://limelight.local:5800" | ||
| : `http://limelight-${cameraUrl}.local:5800`; | ||
| } | ||
|
|
||
|
|
||
| // --- START RECORDING --- | ||
| public startRecording(): string { | ||
| if (this.ffmpegProcess) { | ||
| return "Recording already running"; | ||
| } | ||
| console.log(ffmpegPath); | ||
|
|
||
| // Process initiations | ||
| this.ffmpegProcess = spawn(ffmpegPath as unknown as string, [ | ||
Check warningCode scanning / ESLint Disallow type assertions that narrow a type Warning
Unsafe type assertion: type 'string' is more narrow than the original type.
|
||
| "-i", | ||
| this.cameraUrl, | ||
| "-c:v", | ||
| "copy", | ||
| this.outputFile, | ||
| ]); | ||
|
|
||
| // Logging | ||
| this.ffmpegProcess.stderr?.on("data", (data) => { | ||
emmr253 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| console.log(data.toString()); | ||
|
||
| }); | ||
|
|
||
| // Send response | ||
| return "Recording started"; | ||
| } | ||
|
|
||
| // --- STOP RECORDING --- | ||
| public stopRecording(): string { | ||
| if (!this.ffmpegProcess) { | ||
| return "No recording running"; | ||
| } | ||
|
|
||
| this.ffmpegProcess.stdin?.write("q"); | ||
emmr253 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| this.ffmpegProcess.stdin?.end(); | ||
| this.ffmpegProcess = null; | ||
|
|
||
| return "Recording stopped" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // בס"ד | ||
| import { build, context } from "esbuild"; | ||
| import { spawn } from "child_process"; | ||
|
|
||
| const isDev = process.env.NODE_ENV === "DEV"; | ||
|
|
||
| const bundlePath = "dist/bundle.js"; | ||
|
|
||
| const buildSettings = { | ||
| entryPoints: ["src/main.ts"], | ||
| outfile: "dist/bundle.js", | ||
| bundle: true, | ||
| plugins: [], | ||
| minify: true, | ||
| platform: "node", | ||
| target: ["ES2022"], | ||
| format: "cjs", | ||
| external: ["@repo/config-env"], | ||
| } satisfies Parameters<typeof build>[0]; | ||
|
|
||
| const buildDev = async () => | ||
| context(buildSettings) | ||
| .then(async (ctx) => ctx.watch()) | ||
| .then(() => { | ||
| console.log("Starting nodemon to manage execution of bundle.js"); | ||
| spawn( | ||
| "nodemon", | ||
| [bundlePath, "--watch", bundlePath, "--ext", "js", "--exec", "node"], | ||
| { stdio: "inherit", shell: true } | ||
| ); | ||
| }); | ||
|
|
||
| const buildedProject = isDev ? buildDev() : build(buildSettings); | ||
|
|
||
| buildedProject.catch((error: unknown) => { | ||
| console.warn(error); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| services: | ||
| backend: | ||
| build: | ||
| context: . | ||
| dockerfile: Dockerfile | ||
|
|
||
| env_file: | ||
| - ../../../.public.env | ||
| - ../../../.secret.env | ||
| ports: | ||
| - "${BACKEND_PORT}:4590" # Maps host:${FRONTEND_PORT} to container:4590 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "gb-limelight-recorder-backend", | ||
| "version": "1.0.0", | ||
| "description": "Backend for the application", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "test": "echo Backend Test Succeeded && exit 0", | ||
| "build": "tsx build.ts", | ||
| "serve": "node dist/bundle.js", | ||
| "dev": "tsx build.ts" | ||
| }, | ||
| "author": "", | ||
| "license": "ISC", | ||
| "dependencies": { | ||
| "process": "^0.11.10" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^24.8.1" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| //בס"ד | ||
| import express from "express"; | ||
| import { RecordingProcess } from "./RecordingProcess.js"; | ||
| import cors from "cors"; | ||
| import { pingCameras } from "./PingRobot.js"; | ||
| import { useEffect } from "react"; | ||
| import { join } from "path"; | ||
|
|
||
| const app = express(); | ||
| const port = 5000; | ||
| app.use(cors()); | ||
Check warningCode scanning / ESLint Disallow calling a value with type `any` Warning
Unsafe call of a(n) error type typed value.
|
||
|
|
||
| type cameraObj = { | ||
| name: string | ||
| camURL: string | ||
| ffmpegProcess: RecordingProcess | null | ||
| }; | ||
|
|
||
| const leftCamObj: cameraObj = { | ||
| name: "left", | ||
| camURL: "http://limelight-left.local:5800", | ||
| ffmpegProcess: null | ||
| }; | ||
| const objectCamObj: cameraObj = { | ||
| name: "object", | ||
| camURL: "http://limelight-object.local:5800", | ||
| ffmpegProcess: null | ||
| }; | ||
| const rightCamObj: cameraObj = { | ||
| name: "right", | ||
| camURL: "http://limelight.local:5800", | ||
| ffmpegProcess: null | ||
| }; | ||
| const cameras: cameraObj[] = [leftCamObj, objectCamObj, rightCamObj]; | ||
|
|
||
| // --- HELLO --- | ||
emmr253 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| app.get("/", (req, res) => { | ||
| console.log("GET / route hit"); | ||
| res.send("Welcome to the Limelight Recorder API"); | ||
| }); | ||
|
Comment on lines
+37
to
+40
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider using the built in
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ha? |
||
|
|
||
| // --- START THE SERVER --- | ||
| app.listen(port, () => { | ||
| console.log(`Server listening on http://localhost:${port}`); | ||
| }); | ||
|
|
||
| // for general use and stuff | ||
| const zero = 0; | ||
| const one = 1; | ||
| const two = 2; | ||
| const three = 3; | ||
|
|
||
| function startRecording() { | ||
| if (cameras[zero].ffmpegProcess | ||
| || cameras[one].ffmpegProcess | ||
| || cameras[two].ffmpegProcess | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| for (let i = 0; i < three; i++) { | ||
| cameras[i].ffmpegProcess = new RecordingProcess( | ||
| cameras[i].name, | ||
| join(process.env.USERPROFILE ?? "", "Downloads") | ||
| ); | ||
| cameras[i].ffmpegProcess?.startRecording(); | ||
| } | ||
| } | ||
|
|
||
| function stopRecording() { | ||
| for (let i = 0; i < three; i++) { | ||
| if (cameras[i].ffmpegProcess) { | ||
| cameras[i].ffmpegProcess?.stopRecording(); | ||
| cameras[i].ffmpegProcess = null; | ||
| console.log(`Stopped recording: ${cameras[i].name}`); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const oneSecond = 1000; | ||
| useEffect(() => { | ||
| const intervalId = setInterval(() => { | ||
| pingCameras() | ||
| .catch(console.error); | ||
| }, oneSecond); | ||
|
|
||
| return () => {clearInterval(intervalId)}; | ||
| }, []); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // בס"ד | ||
| import express from "express"; | ||
| import { apiRouter } from "./routes"; | ||
|
|
||
| const app = express(); | ||
|
|
||
| const defaultPort = 4590; | ||
| const port = process.env.BACKEND_PORT ?? defaultPort; | ||
|
|
||
| app.use("/api/v1", apiRouter); | ||
|
|
||
| app.listen(port, () => { | ||
| console.log(`Production server running at http://localhost:${port}`); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // בס"ד | ||
| import { Router } from "express"; | ||
| import { StatusCodes } from "http-status-codes"; | ||
|
|
||
| export const apiRouter = Router(); | ||
|
|
||
| apiRouter.get("/health", (req, res) => { | ||
| res.status(StatusCodes.OK).send({ message: "Healthy!" }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "extends": [ | ||
| "//" | ||
| ], | ||
| "tasks": { | ||
| "build": { | ||
| "outputs": [ | ||
| "dist/**" | ||
| ] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <link rel="icon" type="image/svg+xml" href="./src/assets/greenblitz.svg" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>GreenBlitz 4590</title> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="/src/main.tsx"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| { | ||
| "name": "gb-limelight-recorder-frontend", | ||
| "version": "1.0.0", | ||
| "description": "Frontend for the application", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "test": "echo Frontend Test Succeeded && exit 0", | ||
| "dev": "vite", | ||
| "build": "tsc -b && vite build", | ||
| "serve": " tsx start.ts", | ||
| "lint": "eslint .", | ||
| "preview": "vite preview" | ||
| }, | ||
| "author": "", | ||
| "license": "ISC", | ||
| "dependencies": { | ||
| "react": "^19.1.1", | ||
| "react-dom": "^19.1.1", | ||
| "tailwindcss": "^4.1.16", | ||
| "@tailwindcss/vite": "^4.1.16" | ||
| }, | ||
| "devDependencies": { | ||
| "@eslint/js": "^9.36.0", | ||
| "@types/node": "^24.6.0", | ||
| "@types/react": "^19.1.16", | ||
| "@types/react-dom": "^19.1.9", | ||
| "@vitejs/plugin-react": "^5.0.4", | ||
| "babel-plugin-react-compiler": "^19.1.0-rc.3", | ||
| "globals": "^16.4.0", | ||
| "vite": "^7.1.7" | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.