ffprobe different results video duration using pipe and reading a file from the file system

77 views Asked by At

I have a method to convert a video file, after processing the file I use pipe to pass bytes to a method to get meta information about the file using pipe. But in this case I get wrong duration of video file, 8.22, but if I save the file on file system and read it to get meta information I get result 15.85. Why is this happening?

Video Convert method:

// ConvertVideoWithPath converts a video file specified by its path using FFmpeg.
// It returns the converted video data and any error that occurred during conversion.
func (f *FFmpeg) ConvertVideoWithPath(filePath string) (bytes []byte, err error) {
    if filePath == "" {
        return nil, ErrEmptyPath
    }

    // Create a CmdRunner instance for executing FFmpeg.
    commander := &CmdRunner{}
    commander.Command = "ffmpeg"
    args := []string{
        "-loglevel", "fatal",
        "-i", filePath,
        "-y",
        "-filter:v", "crop=trunc(iw/2)*2:trunc(ih/2)*2",
        "-c:v", f.videoCodec, // libx264
        "-c:a", f.audioCodec, // aac
        "-pix_fmt", "yuv420p",
        "-movflags", "frag_keyframe+faststart",
        "-map_metadata", "-1",
        "-crf", "5",
        "-vsync", "2",
        "-bufsize", "15000000",
        "-maxrate", "5000000",
        "-preset", "medium",
        "-f", "mp4",
        "pipe:1",
    }
    commander.Args = args

    // Initialize output pipe.
    reader := commander.InitStdOutPipe()

    // Use WaitGroup to synchronize goroutines.
    wg := &sync.WaitGroup{}
    wg.Add(1)

    // Goroutine for reading data from the output pipe.
    go func() {
        defer reader.Close()
        defer wg.Done()

        // Read data from the output pipe.
        data, _ := io.ReadAll(reader)
        // Safely update the 'bytes' variable.
        f.mutex.Lock()
        bytes = data
        f.mutex.Unlock()
    }()

    // Run the FFmpeg command with pipes and wait for completion.
    err = <-commander.RunWithPipe()
    wg.Wait()

    return
}
// MetadataWithReader retrieves metadata from media data provided by an io.Reader using FFprobe.
// It returns the metadata and any error that occurred during metadata retrieval.
func (f *FFmpeg) MetadataWithReader(fileBytes io.Reader) (*Metadata, error) {
    if fileBytes == nil {
        return nil, ErrInvalidArgument
    }

    // Create a CmdRunner instance for executing FFprobe.
    commander := &CmdRunner{}
    commander.Command = "ffprobe"
    args := []string{
        "-loglevel", "fatal",
        "-i", "pipe:0",
        "-print_format", "json",
        "-show_format", "-show_streams",
        "-show_error",
    }
    commander.Args = args

    // Get output data from FFprobe with pipes.
    err := commander.GetOutputWithPipe(fileBytes)
    if err != nil {
        return nil, err
    }

    // Unmarshal JSON output into a Metadata struct.
    output := &Metadata{}
    err = json.Unmarshal(commander.GetOutput(), output)
    if err != nil {
        return nil, err
    }

    return output, err
}
// MetadataWithPath extracts metadata of a file using FFprobe.
// It returns a Metadata struct or an error if the operation fails.
func (f *FFmpeg) MetadataWithPath(filePath string) (*Metadata, error) {
    if filePath == "" {
        return nil, ErrEmptyPath
    }

    // Create a CmdRunner instance for executing FFprobe.
    commander := &CmdRunner{}
    commander.Command = "ffprobe"
    args := []string{
        "-loglevel", "fatal",
        "-i", filePath,
        "-loglevel",
        "fatal",
        "-print_format", "json",
        "-show_format", "-show_streams", "-show_error",
    }
    commander.Args = args
    buffer := bytes.NewBuffer([]byte{})
    commander.StdOutWriter = buffer

    err := commander.Run()
    if err != nil {
        return nil, err
    }

    // Unmarshal JSON output into a Metadata struct.
    output := &Metadata{}
    err = json.Unmarshal(buffer.Bytes(), output)
    if err != nil {
        return nil, err
    }

    return output, nil
}

The source code of the CmdRunner biblio library can be found here link , so as not to overload the question with a large piece of code.

Unit test code

t.Run("convert video", func(t *testing.T) {
        ffmpeg := NewFFmpeg("aac", "libx264", "24M", "12M")

        filePath := "../../test/testdata/input_video_ts.mp4"
        firstMeta, err := ffmpeg.MetadataWithPath(filePath)
        assert.NoError(t, err)
        fmt.Print("first meta duration: ", firstMeta.Format.DurationSeconds) // 15.75

        outFile := "../../test/testdata/output_mp4.mp4"
        newVideoOut, err := ffmpeg.ConvertVideoWithPath(filePath)
        assert.NoError(t, err)
        assert.NotEmpty(t, newVideoOut)

        meta, err := ffmpeg.MetadataWithReader(bytes.NewBuffer(newVideoOut))
        assert.NoError(t, err)
        assert.NotEmpty(t, meta)

        err = os.WriteFile(outFile, newVideoOut, 0644)
        assert.NoError(t, err)
        assert.FileExists(t, outFile)

        fmt.Print("meta duration: ", meta.Format.DurationSeconds) // 8.22

        secondMeta, err := ffmpeg.MetadataWithPath(outFile)
        assert.NoError(t, err)
        fmt.Print("second meta duration: ", secondMeta.Format.DurationSeconds) //15.85

        err = os.Remove(outFile)
        assert.NoError(t, err)
    })
0

There are 0 answers