Adding very basic library to emit image events from ffmpeg rtsp stream

This commit is contained in:
watsonb8 2020-11-01 16:50:24 -05:00
commit de4cd7b572
10 changed files with 468 additions and 0 deletions

116
.gitignore vendored Normal file
View File

@ -0,0 +1,116 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

21
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/lib/index.js",
"preLaunchTask": "build",
"console": "internalConsole",
"internalConsoleOptions": "openOnSessionStart",
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/**/*.js"
]
}
]
}

15
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "typescript",
"tsconfig": "tsconfig.json",
"problemMatcher": [
"$tsc"
]
}
]
}

110
lib/index.js Normal file
View File

@ -0,0 +1,110 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var child_process_1 = require("child_process");
var events_1 = require("events");
var fs_1 = __importDefault(require("fs"));
var Rtsp = /** @class */ (function (_super) {
__extends(Rtsp, _super);
function Rtsp(connectionString) {
var _this = _super.call(this) || this;
_this.onError = function (err) {
console.error('Failed to start stream: ' + err.message);
};
_this.onStdErrorData = function (data) {
if (!_this._started) {
_this._started = true;
}
data.toString().split(/\n/).forEach(function (line) {
console.debug(line);
});
};
_this.onExit = function (code, signal) {
var message = 'FFmpeg exited with code: ' + code + ' and signal: ' + signal;
if (code == null || code === 255) {
if (_this._childProcess && _this._childProcess.killed) {
console.debug(message + ' (Expected)');
}
else {
console.error(message + ' (Unexpected)');
}
}
else {
console.error(message + ' (Error)');
}
};
_this.onData = function (data) {
if (data.length > 1) {
_this._buffer = _this._buffer ? Buffer.concat([_this._buffer, data]) : _this._buffer = Buffer.from(data);
//End of image
if (data[data.length - 2].toString(16) == "ff" && data[data.length - 1].toString(16) == "d9") {
_this.emit('data', _this._buffer);
_this._buffer = Buffer.from('');
console.log();
}
}
};
_this._started = false;
_this._connecteionString = connectionString;
_this._childProcess = undefined;
_this._buffer = Buffer.from('');
return _this;
}
Object.defineProperty(Rtsp.prototype, "isStarted", {
get: function () {
return this._started;
},
enumerable: true,
configurable: true
});
Rtsp.prototype.start = function () {
var _a;
var args = "-i " + this._connecteionString + " -r 30 -f image2 -update 1 -";
this._childProcess = child_process_1.spawn("ffmpeg", args.split(/\s+/));
if (!this._childProcess) {
return;
}
(_a = this._childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', this.onData);
this._childProcess.on('exit', this.onExit);
this._childProcess.on('error', this.onError);
if (this._childProcess.stderr) {
this._childProcess.stderr.on('data', this.onStdErrorData);
}
};
Rtsp.prototype.close = function () {
this._childProcess && this._childProcess.kill('SIGKILL');
this.emit('closed');
};
Rtsp.prototype.getStdin = function () {
return this._childProcess ? this._childProcess.stdin : null;
};
return Rtsp;
}(events_1.EventEmitter));
exports.Rtsp = Rtsp;
var rtsp = new Rtsp("rtsp://brandon:asdf1234@192.168.1.229/live");
rtsp.on('data', function (data) {
rtsp.close();
fs_1.default.writeFile("/Users/brandonwatson/Documents/Git/Gitea/asdf.jpeg", data, 'base64', function (err) {
if (err) {
console.log(err);
process.exit(1);
}
});
});
rtsp.start();
//# sourceMappingURL=index.js.map

1
lib/index.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}

19
package-lock.json generated Normal file
View File

@ -0,0 +1,19 @@
{
"name": "rtsp-stream",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "14.14.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz",
"integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==",
"dev": true
},
"child_process": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
"integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o="
}
}
}

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "rtsp-stream",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc --build"
},
"repository": {
"type": "git",
"url": "ssh://git@thebword.ddns.net:3122/watsonb8/rtsp-stream.git"
},
"keywords": [
"rtsp",
"typescript"
],
"author": "Brandon Watson",
"license": "ISC",
"dependencies": {
"child_process": "^1.0.2"
},
"devDependencies": {
"@types/node": "^14.14.6"
}
}

94
src/index.ts Normal file
View File

@ -0,0 +1,94 @@
import { ChildProcess, spawn } from "child_process"
import { EventEmitter } from "events";
import { Writable } from "stream";
import { IOptions } from "./options";
const ef1 = "ff";
const ef2 = "d9";
export class Rtsp extends EventEmitter {
private _connecteionString: string;
private _childProcess: ChildProcess | undefined;
private _started: boolean;
private _buffer: Buffer;
private _options: IOptions;
constructor(connectionString: string, options: IOptions) {
super();
this._started = false;
this._connecteionString = connectionString;
this._childProcess = undefined;
this._buffer = Buffer.from('');
this._options = options;
}
public get isStarted(): boolean {
return this._started;
}
public start(): void {
const argStrings = [
`-i ${this._connecteionString}`,
`-r ${this._options.rate ?? 10}`,
`-f image2`,
`-codec:v ${this._options.codec ?? 'libx264'}`,
`-update 1 -`
];
const args = argStrings.join(" ");
this._childProcess = spawn("ffmpeg", args.split(/\s+/));
if(!this._childProcess){
return;
}
this._childProcess.stdout?.on('data', this.onData)
this._childProcess.on('exit', this.onExit);
this._childProcess.on('error', this.onError);
if (this._childProcess.stderr) {
this._childProcess.stderr.on('data', this.onStdErrorData);
}
}
public close(): void {
this._childProcess && this._childProcess.kill('SIGKILL');
this.emit('closed');
}
public getStdin(): Writable | null {
return this._childProcess ? this._childProcess.stdin : null;
}
private onError = (err: Error): void => {
this.emit('error', new Error('Failed to start stream: ' + err.message));
}
private onStdErrorData = (data: any): void => {
if (!this._started) {
this._started = true;
}
let msg = "";
data.toString().split(/\n/).forEach((line: string) => {
msg += `line\n`;
});
this.emit("error", msg);
}
private onExit = (code: number, signal: NodeJS.Signals): void => {
const message = 'FFmpeg exited with code: ' + code + ' and signal: ' + signal;
this.emit('closed', message);
}
private onData = (data: Buffer): void => {
if (data.length > 1) {
this._buffer = this._buffer ? Buffer.concat([this._buffer, data]) : this._buffer = Buffer.from(data);
//End of image
if(data[data.length - 2].toString(16) == ef1 && data[data.length -1].toString(16) == ef2){
this.emit('data', this._buffer)
this._buffer = Buffer.from('');
}
}
}
}

6
src/options.ts Normal file
View File

@ -0,0 +1,6 @@
export interface IOptions {
rate?: number,
quality?: number,
resolution?: string,
codec?: string,
}

60
tsconfig.json Normal file
View File

@ -0,0 +1,60 @@
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./lib", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}