77 AccessorOnPackage ,
88 LoggerInstance ,
99 escapeFilePath ,
10+ stringifyError ,
1011} from '@sofie-package-manager/api'
1112import {
1213 isQuantelClipAccessorHandle ,
@@ -33,7 +34,7 @@ import { HTTPProxyAccessorHandle } from '../../../../accessorHandlers/httpProxy'
3334import { HTTPAccessorHandle } from '../../../../accessorHandlers/http'
3435import { MAX_EXEC_BUFFER } from '../../../../lib/lib'
3536import { getFFMpegExecutable , getFFProbeExecutable } from './ffmpeg'
36- import { GenericAccessorHandle } from '../../../../accessorHandlers/genericHandle'
37+ import { GenericAccessorHandle , PackageReadStream } from '../../../../accessorHandlers/genericHandle'
3738import { FTPAccessorHandle } from '../../../../accessorHandlers/ftp'
3839
3940export interface FFProbeScanResultStream {
@@ -68,6 +69,7 @@ export function scanWithFFProbe(
6869 ) {
6970 let inputPath : string
7071 let filePath : string
72+ let pipeStdin = false
7173 if ( isLocalFolderAccessorHandle ( sourceHandle ) ) {
7274 inputPath = sourceHandle . fullPath
7375 filePath = sourceHandle . filePath
@@ -82,8 +84,14 @@ export function scanWithFFProbe(
8284 inputPath = sourceHandle . fullUrl
8385 filePath = sourceHandle . filePath
8486 } else if ( isFTPAccessorHandle ( sourceHandle ) ) {
85- inputPath = sourceHandle . ftpUrl . url
86- filePath = sourceHandle . filePath
87+ if ( sourceHandle . ftpUrl . url . startsWith ( 'ftps://' ) || sourceHandle . ftpUrl . url . startsWith ( 'sftp://' ) ) {
88+ // ffmpeg doesn't support ftps protocol, stream instead
89+ pipeStdin = true
90+ inputPath = '-' // stream on stdin
91+ } else {
92+ inputPath = sourceHandle . ftpUrl . url
93+ filePath = sourceHandle . filePath
94+ }
8795 } else {
8896 assertNever ( sourceHandle )
8997 throw new Error ( 'Unknown handle' )
@@ -98,38 +106,71 @@ export function scanWithFFProbe(
98106 '-print_format' ,
99107 'json' ,
100108 ]
109+
101110 let ffProbeProcess : ChildProcess | undefined = undefined
111+ let sourceStream : PackageReadStream | undefined = undefined
102112 onCancel ( ( ) => {
103113 ffProbeProcess ?. stdin ?. write ( 'q' ) // send "q" to quit, because .kill() doesn't quite do it.
104114 ffProbeProcess ?. kill ( )
115+ sourceStream ?. cancel ( )
105116 reject ( 'Cancelled' )
106117 } )
107118
108- ffProbeProcess = execFile (
109- getFFProbeExecutable ( ) ,
110- args ,
111- {
112- maxBuffer : MAX_EXEC_BUFFER ,
113- windowsVerbatimArguments : true , // To fix an issue with ffprobe.exe on Windows
114- } ,
115- ( err , stdout , _stderr ) => {
116- // this.logger.debug(`Worker: metadata generate: output (stdout, stderr)`, stdout, stderr)
117- ffProbeProcess = undefined
118- if ( err ) {
119- reject ( err )
120- return
121- }
122- const json : FFProbeScanResult = JSON . parse ( stdout )
123- if ( ! json . streams || ! json . streams [ 0 ] ) {
124- reject ( new Error ( `File doesn't seem to be a media file` ) )
125- return
126- }
127- json . filePath = filePath
119+ ffProbeProcess = spawn ( getFFProbeExecutable ( ) , args , {
120+ // maxBuffer: MAX_EXEC_BUFFER,
121+ windowsVerbatimArguments : true , // To fix an issue with ffprobe.exe on Windows
122+ } )
123+ let stdoutBuffer = ''
124+ ffProbeProcess . stdout ?. on ( 'data' , ( data ) => {
125+ stdoutBuffer += data . toString ( )
126+ } )
128127
129- fixJSONResult ( json )
130- resolve ( json )
128+ ffProbeProcess . on ( 'error' , ( err ) => {
129+ ffProbeProcess = undefined
130+ reject ( err )
131+ } )
132+ ffProbeProcess . on ( 'close' , ( code ) => {
133+ ffProbeProcess = undefined
134+
135+ if ( code !== 0 ) {
136+ reject ( new Error ( `FFProbe exited with code ${ code } ` ) )
137+ return
131138 }
132- )
139+
140+ const json : FFProbeScanResult = JSON . parse ( stdoutBuffer )
141+ if ( ! json . streams || ! json . streams [ 0 ] ) {
142+ reject ( new Error ( `File doesn't seem to be a media file` ) )
143+ return
144+ }
145+ json . filePath = filePath
146+ fixJSONResult ( json )
147+ resolve ( json )
148+ } )
149+ if ( pipeStdin ) {
150+ sourceStream = await sourceHandle . getPackageReadStream ( )
151+ if ( ffProbeProcess . stdin === null ) {
152+ throw new Error ( 'ffprobeProcess.stdin is null, cant pipe to it' )
153+ }
154+ sourceStream . readStream . on ( 'error' , ( err ) => {
155+ // wait just a little bit before throwing, perhaps ffprobe is already done?
156+ setTimeout ( ( ) => {
157+ if ( ffProbeProcess ) {
158+ // still going, so this is a real error
159+ reject ( new Error ( 'Error reading source stream: ' + stringifyError ( err ) ) )
160+ }
161+ } , 10 )
162+ } )
163+ ffProbeProcess . stdin . on ( 'error' , ( err ) => {
164+ // wait just a little bit before throwing, perhaps ffprobe is already done?
165+ setTimeout ( ( ) => {
166+ if ( ffProbeProcess ) {
167+ // still going, so this is a real error
168+ reject ( new Error ( 'Error writing stream: ' + stringifyError ( err ) ) )
169+ }
170+ } , 10 )
171+ } )
172+ sourceStream . readStream . pipe ( ffProbeProcess . stdin )
173+ }
133174 } else if ( isQuantelClipAccessorHandle ( sourceHandle ) ) {
134175 // Because we have no good way of using ffprobe to generate the info we want,
135176 // we resort to faking it:
0 commit comments