speechflow
Version:
Speech Processing Flow Graph
135 lines (124 loc) • 5.71 kB
text/typescript
/*
** SpeechFlow - Speech Processing Flow Graph
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
*/
/* standard dependencies */
import Stream from "node:stream"
/* internal dependencies */
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
import * as util from "./speechflow-util"
/* SpeechFlow node for data flow filtering (based on meta information) */
export default class SpeechFlowNodeX2XFilter extends SpeechFlowNode {
/* declare official node name */
public static name = "x2x-filter"
/* cached regular expression instance */
private cachedRegExp = new util.CachedRegExp()
/* construct node */
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
super(id, cfg, opts, args)
/* declare node configuration parameters */
this.configure({
type: { type: "string", pos: 0, val: "audio", match: /^(?:audio|text)$/ },
name: { type: "string", pos: 1, val: "filter", match: /^.+?$/ },
var: { type: "string", pos: 2, val: "", match: /^(?:meta:.+|payload:(?:length|text)|time:(?:start|end)|kind|type)$/ },
op: { type: "string", pos: 3, val: "==", match: /^(?:<|<=|==|!=|~~|!~|>=|>)$/ },
val: { type: "string", pos: 4, val: "", match: /^.*$/ }
})
/* declare node input/output format */
this.input = this.params.type
this.output = this.params.type
}
/* open node */
async open () {
/* helper function for comparing two values */
const comparison = (val1: any, op: string, val2: any) => {
if (op === "==" || op === "!=") {
/* equal comparison */
const str1 = (typeof val1 === "string" ? val1 : val1.toString()) as string
const str2 = (typeof val2 === "string" ? val2 : val2.toString()) as string
return (op === "==" ? (str1 === str2) : (str1 !== str2))
}
else if (op === "~~" || op === "!~") {
/* regular expression comparison */
const str = (typeof val1 === "string" ? val1 : val1.toString()) as string
const regexp = (
val2 instanceof RegExp ?
val2 :
typeof val2 === "string" ?
this.cachedRegExp.compile(val2) :
this.cachedRegExp.compile(val2.toString()))
if (regexp === null) {
/* fallback to literal string comparison on invalid regex */
this.log("warning", `invalid regular expression: "${val2}"`)
return (op === "~~" ? (str === val2) : (str !== val2))
}
return (op === "~~" ? regexp.test(str) : !regexp.test(str))
}
else {
/* non-equal comparison */
const coerceNum = (val: any) =>
typeof val === "number" ? val : (
typeof val === "string" && val.match(/^[\d+-]+$/) ? Number.parseInt(val, 10) : (
typeof val === "string" && val.match(/^[\d.+-]+$/) ?
Number.parseFloat(val) :
Number(val)
)
)
const num1 = coerceNum(val1)
const num2 = coerceNum(val2)
return (
op === "<" ? (num1 < num2) :
op === "<=" ? (num1 <= num2) :
op === ">=" ? (num1 >= num2) :
op === ">" ? (num1 > num2) :
false
)
}
}
/* provide Transform stream */
const self = this
this.stream = new Stream.Transform({
writableObjectMode: true,
readableObjectMode: true,
decodeStrings: false,
highWaterMark: 1,
transform (chunk: SpeechFlowChunk, encoding, callback) {
let val1: any
const val2: any = self.params.val
const m = self.params.var.match(/^meta:(.+)$/)
if (m !== null)
val1 = chunk.meta.get(m[1]) ?? ""
else if (self.params.var === "kind")
val1 = chunk.kind
else if (self.params.var === "type")
val1 = chunk.type
else if (self.params.var === "payload:length")
val1 = chunk.payload.length
else if (self.params.var === "payload:text")
val1 = (self.params.type === "text" ? chunk.payload as string : "")
else if (self.params.var === "time:start")
val1 = chunk.timestampStart.toMillis()
else if (self.params.var === "time:end")
val1 = chunk.timestampEnd.toMillis()
if (comparison(val1, self.params.op, val2)) {
self.log("info", `[${self.params.name}]: passing through ${chunk.type} chunk`)
this.push(chunk)
}
callback()
},
final (callback) {
this.push(null)
callback()
}
})
}
/* close node */
async close () {
/* close stream */
if (this.stream !== null) {
this.stream.destroy()
this.stream = null
}
}
}