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)
})