diff --git a/README.md b/README.md index 4f59ee2..ba571c5 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Connect to the device's access point (e.g., FTYC811847AGFDZ) and run `node dist/ ## Running +### HTTP Server To execute the HTTP server, run `node dist/bin.cjs http_server`; you can access the JPEG stream at http://localhost:5000/. The roundtrip delay when using MJPEG is [~350ms](pics/delay.jpg?raw=true). @@ -38,9 +39,9 @@ There's a basic UI which can display multiple cameras: Clicking on the image will take you to a page that has audio streaming. Click the button below the image to mute/unmute the audio. -## Settings +#### Settings -You can provide a config file in `yml` format, check `config.example.yml` for an updated example: +You can provide a config file in `yml` format, then pass it as an argument: `node bin.cjs http_server --config_file ` ```yml http_server: @@ -82,6 +83,12 @@ All keys are optional You must restart the HTTP server for changes to the settings file to take effect. +### Single capture mode + +```bash +node bin.cjs frame --discovery_ip 192.168.40.104 --out out.jpg +``` + ---- ## Protocol diff --git a/capture_single.ts b/capture_single.ts new file mode 100644 index 0000000..d13be17 --- /dev/null +++ b/capture_single.ts @@ -0,0 +1,39 @@ +import fs from "node:fs"; +import { DevSerial } from "./impl.js"; +import { RemoteInfo } from "dgram"; +import { logger } from "./logger.js"; +import { startVideoStream } from "./session.js"; +import { discoverDevices } from "./discovery.js"; +import { Session } from "./session.js"; +import { Handlers, makeSession } from "./session.js"; +import { config } from "./settings.js"; + +const sessions: Record = {}; +export const captureSingle = ({ discovery_ip, out_file }: { discovery_ip: string; out_file: string }) => { + let devEv = discoverDevices([discovery_ip]); + + const startSession = (s: Session) => { + startVideoStream(s); + logger.info(`Camera ${s.devName} is now ready to stream`); + }; + + devEv.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => { + if (dev.devId in sessions) { + logger.info(`Camera ${dev.devId} at ${rinfo.address} already discovered, ignoring`); + return; + } + + logger.info(`Discovered camera ${dev.devId} at ${rinfo.address}`); + const s = makeSession(Handlers, dev, rinfo, startSession, 5000); + sessions[dev.devId] = s; + config.cameras[dev.devId] = { fix_packet_loss: false }; + + s.eventEmitter.on("frame", () => { + const assembled = Buffer.concat(s.curImage); + fs.writeFileSync(out_file, assembled); + logger.info(`Got frame. Exiting`); + devEv.emit("close"); + s.close(); + }); + }); +}; diff --git a/cmd/bin.ts b/cmd/bin.ts index 2837757..4838927 100644 --- a/cmd/bin.ts +++ b/cmd/bin.ts @@ -2,6 +2,7 @@ import process from "node:process"; import { hideBin } from "yargs/helpers"; import yargs from "yargs/yargs"; +import { captureSingle } from "../capture_single.js"; import { serveHttp } from "../http_server.js"; import { pair } from "../pair.js"; import { loadConfig, config } from "../settings.js"; @@ -70,5 +71,24 @@ yargs(hideBin(process.argv)) pair({ ssid: argv.ssid, password: argv.password }); }, ) + .command( + "frame", + "capture a single frame from the first discovered camera", + (yargs) => { + return yargs + .option("log_level", { describe: "Set log level", default: "info" }) + .option("discovery_ip", { describe: "Camera discovery IP address", default: "192.168.1.255" }) + .option("out", { describe: "Path for output file" }) + .demandOption(["out"]) + .string(["out", "discovery_ip"]); + }, + (argv) => { + buildLogger(argv.log_level, undefined); + if (majorVersion < 16) { + logger.error(`Node version ${majorVersion} is not supported, may malfunction`); + } + captureSingle({ discovery_ip: argv.discovery_ip, out_file: argv.out }); + }, + ) .demandCommand() .parseSync(); diff --git a/handlers.ts b/handlers.ts index 5b57ce4..663bf0a 100644 --- a/handlers.ts +++ b/handlers.ts @@ -249,7 +249,7 @@ const deal_with_data = (session: Session, dv: DataView) => { } // this should always be enabled but currently it seems to cause more // visual distortion than just missing some frames - if (!config.cameras[session.devName].fix_packet_loss) { + if (!config.cameras[session.devName]?.fix_packet_loss) { return; }