import { stringify } from 'querystring' ; import { spawn } from 'child_process' ; import fetch from 'node-fetch' ; import { Stream } from 'stream' ; import es from 'event-stream' ; import concat from 'concat-stream' ; import filter from 'stream-filter' ; import reduce from 'stream-reduce' ; import path from 'path' ; import { inject , injectable } from 'inversify' ; import Config from '../config' ; import Platform from '../platform' ; see https see https @ injectable ( ) class AcousticId { constructor ( @ inject ( Config ) private config , @ inject ( Platform ) private platform ) { } /**
     * Fetch acousticId metadata, with a fingerprint of the sound obtained with fpcalc
     */ async getMetadata ( filePath ) { const { duration , fingerprint } = await this . getFingerPrint ( filePath ) ; const query = { format : 'json' , meta : 'recordings' , client : this . config . acousticId . key , duration , fingerprint } ; const res = await fetch ( `${ this . config . acousticId . url }?${ stringify ( query ) }` ) ; return res . json ( ) ; } private parseData ( buffer ) { const data = buffer . toString ( ) ; const index = data . indexOf ( '=' ) ; return { name : data . slice ( 0 , index ) . toLowerCase ( ) , value : data . slice ( index + 1 ) } ; } /**
     * run fpCalc and extract sound fingerprint
     */ async getFingerPrint ( file ) { return new Promise ( ( resolve , reject ) => { this . runFpCalc ( file ) . on ( 'error' , reject ) . pipe ( es . pipeline ( es . split ( ) , filter ( Boolean ) , es . mapSync ( this . parseData ) , reduce ( ( result , data ) => ( { [ data . name ] : data . value , ... result } ) , { } ) ) ) . on ( 'data' , resolve ) ; } ) ; } /**
     * run fpcalc binaries
     */ runFpCalc ( file ) { const command = this . platform . isWindows ( ) ? 'fpcalc.exe' : 'fpcalc' ; const commandPath = path . join ( this . platform . getBinaryPath ( ) , command ) ; const cp = spawn ( commandPath , [ file ] ) ; const stream = es . through ( null , ( ) => { } ) ; cp . stdout . pipe ( stream ) ; cp . stderr . pipe ( concat ( ( data ) => { if ( data && data . toString ( ) . slice ( 0 , 6 ) === 'ERROR:' ) { stream . emit ( 'error' , new Error ( data . toString ( ) ) ) ; } } ) ) ; cp . on ( 'close' , ( code ) => { if ( code !== 0 ) { stream . emit ( 'error' , new Error ( 'fpcalc failed' ) ) ; } stream . queue ( null ) ; } ) ; return stream ; } } export default AcousticId ;