import { LameStatus , Options } from "./LameTypings" ; import { LameOptions } from "./LameOptions" ; import { existsSync as fsExistsSync , readFile as fsReadFile , writeFile as fsWriteFile , unlinkSync as fsUnlinkSync } from "fs" ; import { isBuffer as utilIsBuffer } from "util" ; import { spawn } from "child_process" ; import { EventEmitter } from "events" ; class class Lame { private status : LameStatus = { started : false , finished : false , progress : undefined , eta : undefined } ; private emitter : EventEmitter = new EventEmitter ( ) ; private options : Options ; private args : string [ ] ; private filePath : string ; private fileBuffer : Buffer ; private fileBufferTempFilePath : string ; private progressedFilePath : string ; private progressedBuffer : Buffer ; private progressedBufferTempFilePath : string ; param { Options } options constructor ( options ) { this . options = options ; this . args = new LameOptions ( this . options ) . getArguments ( ) ; } param { string } filePath public setFile ( path ) { if ( ! fsExistsSync ( path ) ) { throw new Error ( "Audio file (path) does not exist" ) ; } this . filePath = path ; this . fileBuffer = undefined ; return this ; } param { Buffer } file public setBuffer ( file ) { if ( ! utilIsBuffer ( file ) ) { throw new Error ( "Audio file (buffer) dose not exist" ) ; } this . fileBuffer = file ; this . filePath = undefined ; return this ; } returns { string } public getFile ( ) { if ( this . progressedFilePath == undefined ) { throw new Error ( "Audio is not yet decoded/encoded" ) ; } return this . progressedFilePath ; } returns { Buffer } public getBuffer ( ) { if ( this . progressedBuffer == undefined ) { throw new Error ( "Audio is not yet decoded/encoded" ) ; } return this . progressedBuffer ; } returns { EventEmitter } public getEmitter ( ) { return this . emitter ; } returns { LameStatus } public getStatus ( ) { return this . status ; } return { Promise } public encode ( ) { return this . progress ( "encode" ) ; } return { Promise } public decode ( ) { return this . progress ( "decode" ) ; } return { Promise } private progress ( type ) { if ( this . filePath == undefined && this . fileBuffer == undefined ) { throw new Error ( "Audio file to encode is not set" ) ; } const args = this . args ; if ( type == "decode" ) { args . push ( "--decode" ) ; } if ( this . fileBuffer != undefined ) { this . fileBufferTempFilePath = this . tempFilePathGenerator ( "raw" , type ) ; return new Promise ( ( resolve , reject ) => { fsWriteFile ( this . fileBufferTempFilePath , this . fileBuffer , err => { if ( err ) { reject ( err ) ; return ; } resolve ( this . fileBufferTempFilePath ) ; } ) ; } ) . then ( ( file ) => { return this . execProgress ( file , args , type ) ; } ) . catch ( ( error ) => { this . removeTempFilesOnError ( ) ; throw error ; } ) ; } else { return this . execProgress ( this . filePath , args , type ) . catch ( ( error ) => { this . removeTempFilesOnError ( ) ; throw error ; } ) ; } } private param { string } inputFilePath private execProgress ( inputFilePath , args , type ) { args . push ( "--disptime" ) ; args . push ( "1" ) ; if ( this . options . output == "buffer" ) { const tempOutPath = this . tempFilePathGenerator ( "encoded" , type ) ; args . unshift ( `${ tempOutPath }` ) ; this . progressedBufferTempFilePath = tempOutPath ; } else { this . progressedFilePath = this . options . output ; args . unshift ( this . progressedFilePath ) ; } args . unshift ( inputFilePath ) ; this . status . started = true ; this . status . finished = false ; this . status . progress = 0 ; this . status . eta = undefined ; param { ( String | Buffer ) } data const progressStdout = ( data ) => { data = data . toString ( ) . trim ( ) ; if ( data . length > 6 ) { if ( type == "encode" && data . search ( "Writing LAME Tag...done" ) > - 1 ) { this . status . finished = true ; this . status . progress = 100 ; this . status . eta = "00:00" ; this . emitter . emit ( "finish" ) ; this . emitter . emit ( "progress" , [ this . status . progress , this . status . eta ] ) ; } else if ( type == "encode" && data . search ( /\((( [0-9])|([0-9]{2})|(100))%\)\|/ ) > - 1 ) { const progressMatch = data . match ( /\((( [0-9])|([0-9]{2})|(100))%\)\|/ ) ; const etaMatch = data . match ( /[0-9]{1,2}:[0-9][0-9] / ) ; const progress = String ( progressMatch [ 1 ] ) ; let eta = null ; if ( etaMatch != null ) { eta = etaMatch [ 0 ] . trim ( ) ; } if ( progress != null && Number ( progress ) > this . status . progress ) { this . status . progress = Number ( progress ) ; } if ( eta != null ) { this . status . eta = eta ; } this . emitter . emit ( "progress" , [ this . status . progress , this . status . eta ] ) ; } else if ( type == "decode" && data . search ( /[0-9]{1,10}\/[0-9]{1,10}/ ) > - 1 ) { const progressMatch = data . match ( /[0-9]{1,10}\/[0-9]{1,10}/ ) ; const progressAbsolute = progressMatch [ 0 ] . split ( "/" ) ; const progress = Math . floor ( ( Number ( progressAbsolute [ 0 ] ) / Number ( progressAbsolute [ 1 ] ) ) * 100 ) ; if ( ! isNaN ( progress ) && Number ( progress ) > this . status . progress ) { this . status . progress = Number ( progress ) ; } this . emitter . emit ( "progress" , [ this . status . progress , this . status . eta ] ) ; } else if ( data . search ( /^lame: / ) > - 1 || data . search ( /^Warning: / ) > - 1 || data . search ( /Error / ) > - 1 ) { if ( data . search ( /^lame: / ) == - 1 ) { this . emitter . emit ( "error" , new Error ( "lame: " + data ) ) ; } else { this . emitter . emit ( "error" , new Error ( String ( data ) ) ) ; } } } } ; const progressOnClose = code => { if ( code == 0 ) { if ( ! this . status . finished ) { this . emitter . emit ( "finish" ) ; } this . status . finished = true ; this . status . progress = 100 ; this . status . eta = "00:00" ; } if ( code === 255 ) { this . emitter . emit ( "error" , new Error ( "Unexpected termination of the process, possibly directly after the start. Please check if the input and/or output does not exist." ) ) ; } } ; param { Error } error const progressError = ( error ) => { this . emitter . emit ( "error" , error ) ; } ; const instance = spawn ( "lame" , args ) ; instance . stdout . on ( "data" , progressStdout ) ; instance . stderr . on ( "data" , progressStdout ) ; instance . on ( "close" , progressOnClose ) ; instance . on ( "error" , progressError ) ; return new Promise ( ( resolve , reject ) => { this . emitter . on ( "finish" , ( ) => { if ( this . fileBufferTempFilePath != undefined ) { fsUnlinkSync ( this . fileBufferTempFilePath ) ; } if ( this . options . output == "buffer" ) { fsReadFile ( this . progressedBufferTempFilePath , null , ( error , data ) => { fsUnlinkSync ( this . progressedBufferTempFilePath ) ; if ( error ) { reject ( error ) ; return ; } this . progressedBuffer = Buffer . from ( data ) ; this . progressedBufferTempFilePath = undefined ; resolve ( this ) ; } ) ; } else { resolve ( this ) ; } } ) ; this . emitter . on ( "error" , error => { reject ( error ) ; } ) ; } ) ; } param { ( "raw" | "encoded" ) } type returns { string } private tempFilePathGenerator ( type , progressType ) { const prefix = `${ __dirname }/../.` ; let path = `${ prefix }./temp/${ type }/` ; let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ; for ( let i = 0 ; i < 32 ; i ++ ) { path += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) ; } if ( type == "raw" && progressType == "decode" ) { path += `.mp3` ; } if ( ! fsExistsSync ( `${ prefix }./temp/${ path }` ) ) { return path ; } else { return this . tempFilePathGenerator ( type , progressType ) ; } } /**
     * Remove temp files, if error occurred
     */ private removeTempFilesOnError ( ) { if ( this . fileBufferTempFilePath != undefined && fsExistsSync ( this . fileBufferTempFilePath ) ) { fsUnlinkSync ( this . fileBufferTempFilePath ) ; } if ( this . progressedBufferTempFilePath != undefined && fsExistsSync ( this . progressedBufferTempFilePath ) ) { fsUnlinkSync ( this . progressedBufferTempFilePath ) ; } } } export { Lame } ;