0

I want to call ffmpeg to get the duration of a video file. When using the command in OSX Terminal everything works fine:

ffmpeg -i MyVideo.MOV 2>&1 | grep "Duration"

I get this:

  Duration: 00:01:23.53, start: 0.000000, bitrate: 39822 kb/s

It is perfect for me. But now I tried this call from within my code:

func shell(launchPath: String, arguments: [String]) -> String
{
    let task = Process()
    task.launchPath = launchPath
    task.arguments  = arguments

    let pipe = Pipe()
    task.standardOutput = pipe
    
    do {
        try task.run()
        // task.launch() till 10.12, but now catchable!
    } catch let error as NSError {
        print(error.localizedDescription)
        return ""
    }
    
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String

    return output
}

This code works fine for all other external commands. But here I get error:

[NULL @ 0x107808800] Unable to find a suitable output format for '2>&1'
2>&1: Invalid argument

I defined the arguments for ffmpeg like this:

let arguments = ["-i", video.path, "2>&1", "|", "grep \"Duration\"" ]

Even if I put them all in one argument as a larger string, it doesn't work. Using "pipe:1" instead of "2>&1" and rest of arguments results also in an error.

Any idea, how I get it to work?

Peter Silie
  • 825
  • 1
  • 11
  • 16
  • `2>&1` that means to redirect error output into standard output, no? Do you really need it? You know you can manually do it, by creating a new `Pipe`, and writing the `task.standardError = newPipe` – Larme Aug 12 '20 at 12:55
  • Yes, I know of "2>&1", but without that trailing stuff it won't work in terminal. That is the place I check all the commands first. – Peter Silie Aug 12 '20 at 13:18
  • According to https://stackoverflow.com/questions/6239350/how-to-extract-duration-time-from-ffmpeg-output, it's outputting in the sdterror, so why don't you do do the grep yourself? `task.standardOutput = pipe` => `task.standardError = pipe` and `let durationLine = output.components(separatedBy: .whitespacesAndNewlines).filter{ $0.contains("Duration") }` – Larme Aug 12 '20 at 13:26

1 Answers1

1

2>&1 means everything in standardError(Output) goes into standardOutput.

Well, you don't really need that, yes?

Why not reading the standardError(Output) directly and do the grep yourself?

let pipe = Pipe()
task.standardError = pipe

... //task.run()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = String(data: data, encoding: .utf8)

By the way, let's avoid NSString and double re-casting into String.

Let's do the grep ourselves too.

let durationLine = output.components(separatedBy: .newlines).first(where: { $0.contains("Duration") }) 

Steps: Let's get an array of new lines, and find the first line with "Duration".

Larme
  • 24,190
  • 6
  • 51
  • 81
  • Great! That's it. I had to redirect standardError, because the information I need were not from stdout but from stderr! Thanks a lot, you saved my day! :-) – Peter Silie Aug 12 '20 at 14:33
  • BTW: "let durationLine ..." delivers the leading text "Duration", not the time. But I solved it by my own with a little more code. – Peter Silie Aug 12 '20 at 15:10
  • `.whitespacesAndNewlines` I meant `.newlines` * – Larme Aug 12 '20 at 15:11