@@ -11,6 +11,7 @@ const contentDisposition = require('content-disposition')
1111const { WebSocketServer } = require ( 'ws' )
1212const { runconvert } = require ( './convert/convert' )
1313const { wer, readBaseUrls } = require ( './utils' )
14+ const { getAudioLengthSeconds } = require ( './soxi' )
1415const debug = require ( 'debug' ) ( 'botium-speech-processing-routes' )
1516
1617const cachePathStt = ( process . env . BOTIUM_SPEECH_CACHE_DIR && path . join ( process . env . BOTIUM_SPEECH_CACHE_DIR , 'stt' ) ) || './resources/.cache/stt'
@@ -75,6 +76,24 @@ const extractMultipartContent = (req, res) => new Promise((resolve, reject) => {
7576 } )
7677} )
7778
79+ const _addContentDurationHeadersForFile = async ( name , filenameOrBuffer , headers = { } ) => {
80+ try {
81+ const outputDuration = await getAudioLengthSeconds ( filenameOrBuffer )
82+ return _addContentDurationHeaders ( outputDuration , headers )
83+ } catch ( err ) {
84+ debug ( `no audio length readable for ${ name } : ${ err . message } ` )
85+ return headers
86+ }
87+ }
88+
89+ const _addContentDurationHeaders = ( outputDuration , headers = { } ) => {
90+ if ( outputDuration >= 0 ) {
91+ headers [ 'Content-Duration' ] = outputDuration . toFixed ( 0 )
92+ headers [ 'X-Content-Duration' ] = outputDuration . toFixed ( 3 )
93+ }
94+ return headers
95+ }
96+
7897const router = express . Router ( )
7998
8099/**
@@ -416,10 +435,12 @@ router.post('/api/stt/:language', async (req, res, next) => {
416435 const name = fs . readFileSync ( cacheFileName ) . toString ( )
417436 const buffer = fs . readFileSync ( cacheFileBuffer )
418437 debug ( `Reading tts result ${ cacheFileName } from cache: ${ name } ` )
419- res . writeHead ( 200 , {
438+ const headers = {
420439 'Content-disposition' : `${ contentDisposition ( name ) } ` ,
421440 'Content-Length' : buffer . length
422- } )
441+ }
442+ await _addContentDurationHeadersForFile ( name , cacheFileBuffer , headers )
443+ res . writeHead ( 200 , headers )
423444 return res . end ( buffer )
424445 } catch ( err ) {
425446 debug ( `Failed reading tts result ${ cacheFileName } from cache: ${ err . message } ` )
@@ -435,10 +456,12 @@ router.post('/api/stt/:language', async (req, res, next) => {
435456 voice : req . query . voice ,
436457 text : req . query . text
437458 } )
438- res . writeHead ( 200 , {
459+ const headers = {
439460 'Content-disposition' : `${ contentDisposition ( name ) } ` ,
440461 'Content-Length' : buffer . length
441- } )
462+ }
463+ await _addContentDurationHeadersForFile ( name , buffer , headers )
464+ res . writeHead ( 200 , headers )
442465 res . end ( buffer )
443466
444467 if ( ! skipCache && cachePathTts ) {
@@ -550,11 +573,13 @@ router.post('/api/convert/:profile', async (req, res, next) => {
550573 const envVarOutput = `BOTIUM_SPEECH_CONVERT_PROFILE_${ req . params . profile . toUpperCase ( ) } _OUTPUT`
551574
552575 try {
553- const { outputName, outputBuffer } = await runconvert ( process . env [ envVarCmd ] , process . env [ envVarOutput ] , { inputBuffer : buffer , start : req . query . start , end : req . query . end } )
554- res . writeHead ( 200 , {
576+ const { outputName, outputBuffer, outputDuration } = await runconvert ( process . env [ envVarCmd ] , process . env [ envVarOutput ] , { inputBuffer : buffer , start : req . query . start , end : req . query . end } )
577+ const headers = {
555578 'Content-disposition' : `attachment; filename="${ outputName } "` ,
556579 'Content-Length' : outputBuffer . length
557- } )
580+ }
581+ _addContentDurationHeaders ( outputDuration , headers )
582+ res . writeHead ( 200 , headers )
558583 res . end ( outputBuffer )
559584 } catch ( err ) {
560585 return next ( err )
@@ -624,6 +649,7 @@ router.post('/api/convert', async (req, res, next) => {
624649 const profiles = _ . isString ( req . query . profile ) ? [ req . query . profile ] : _ . isArray ( req . query . profile ) ? req . query . profile : [ ]
625650 let transformBuffer = buffer
626651 let transformName = null
652+ let transformDuration = null
627653 for ( const profile of profiles ) {
628654 const envVarCmd = `BOTIUM_SPEECH_CONVERT_PROFILE_${ profile . toUpperCase ( ) } _CMD`
629655 if ( ! process . env [ envVarCmd ] ) {
@@ -632,17 +658,20 @@ router.post('/api/convert', async (req, res, next) => {
632658 const envVarOutput = `BOTIUM_SPEECH_CONVERT_PROFILE_${ profile . toUpperCase ( ) } _OUTPUT`
633659
634660 try {
635- const { outputName, outputBuffer } = await runconvert ( process . env [ envVarCmd ] , process . env [ envVarOutput ] , { inputBuffer : transformBuffer , start : req . query . start , end : req . query . end } )
661+ const { outputName, outputBuffer, outputDuration } = await runconvert ( process . env [ envVarCmd ] , process . env [ envVarOutput ] , { inputBuffer : transformBuffer , start : req . query . start , end : req . query . end } )
636662 transformBuffer = outputBuffer
637663 transformName = outputName
664+ transformDuration = outputDuration
638665 } catch ( err ) {
639666 return next ( err )
640667 }
641668 }
642- res . writeHead ( 200 , {
669+ const headers = {
643670 'Content-disposition' : `attachment; filename="${ transformName } "` ,
644671 'Content-Length' : transformBuffer . length
645- } )
672+ }
673+ _addContentDurationHeaders ( transformDuration , headers )
674+ res . writeHead ( 200 , headers )
646675 res . end ( transformBuffer )
647676} )
648677
@@ -726,6 +755,7 @@ const wssStreams = {}
726755 const streamId = uuidv1 ( )
727756 const stream = await stt . stt_OpenStream ( req , { language : req . params . language } )
728757 stream . events . on ( 'close' , ( ) => delete wssStreams [ streamId ] )
758+ stream . dateTimeStart = new Date ( )
729759 wssStreams [ streamId ] = stream
730760
731761 const baseUrls = readBaseUrls ( req )
@@ -764,7 +794,8 @@ const wssStreams = {}
764794; [ router . get . bind ( router ) , router . post . bind ( router ) ] . forEach ( m => m ( '/api/sttstatus/:streamId' , async ( req , res , next ) => {
765795 const stream = wssStreams [ req . params . streamId ]
766796 if ( stream ) {
767- res . status ( 200 ) . json ( { status : 'OK' , streamId : req . params . streamId } )
797+ const streamDuration = ( ( new Date ( ) - stream . dateTimeStart ) / 1000 ) . toFixed ( 3 )
798+ res . status ( 200 ) . json ( { status : 'OK' , streamId : req . params . streamId , streamDuration } )
768799 } else {
769800 res . status ( 404 ) . json ( { status : 'NOTFOUND' , streamId : req . params . streamId } )
770801 }
@@ -810,6 +841,7 @@ const wssUpgrade = (req, socket, head) => {
810841 const wss1 = new WebSocketServer ( { noServer : true } )
811842 wss1 . on ( 'connection' , async ( ws ) => {
812843 stream . events . on ( 'data' , ( data ) => {
844+ data . streamDuration = ( ( new Date ( ) - stream . dateTimeStart ) / 1000 ) . toFixed ( 3 )
813845 ws . send ( JSON . stringify ( data ) )
814846 } )
815847 stream . events . on ( 'close' , ( ) => {
0 commit comments