From 0bce47ccae8e32de3de7a3e8984617fc76baf52c Mon Sep 17 00:00:00 2001 From: Alex Eidt Date: Thu, 15 Sep 2022 14:14:45 -0700 Subject: [PATCH] HasAudio() generalized to HasStreams() --- README.md | 34 +++++++++++++-------------- video.go | 31 +++++++++++++++--------- videowriter.go | 64 +++++++++++++++++++++++++------------------------- vidio_test.go | 5 ++-- 4 files changed, 72 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index c4f37fa..40cc973 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Stream() int Duration() float64 FPS() float64 Codec() string -HasAudio() bool +HasStreams() bool FrameBuffer() []byte MetaData() map[string]string SetFrameBuffer(buffer []byte) error @@ -45,7 +45,7 @@ If all frames have been read, `video` will be closed automatically. If not all f ## `Camera` -The `Camera` can read from any cameras on the device running Vidio. It takes in the stream index. On most machines the webcam device has index 0. +The `Camera` can read from any cameras on the device running `Vidio`. It takes in the stream index. On most machines the webcam device has index 0. ```go vidio.NewCamera(stream int) (*vidio.Camera, error) @@ -71,7 +71,7 @@ The `VideoWriter` is used to write frames to a video file. The only required par vidio.NewVideoWriter(filename string, width, height int, options *vidio.Options) (*vidio.VideoWriter, error) FileName() string -Audio() string +StreamFile() string Width() int Height() int Bitrate() int @@ -89,25 +89,25 @@ Close() ```go type Options struct { - Bitrate int // Bitrate. - Loop int // For GIFs only. -1=no loop, 0=infinite loop, >0=number of loops. - Delay int // Delay for final frame of GIFs. - Macro int // Macroblock size for determining how to resize frames for codecs. - FPS float64 // Frames per second for output video. - Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst. - Codec string // Codec for video. - Format string // Pixel Format for video. Default "rgb24". - Audio string // File path for extra stream data. + Bitrate int // Bitrate. + Loop int // For GIFs only. -1=no loop, 0=infinite loop, >0=number of loops. + Delay int // Delay for final frame of GIFs. + Macro int // Macroblock size for determining how to resize frames for codecs. + FPS float64 // Frames per second for output video. + Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst. + Codec string // Codec for video. + Format string // Pixel Format for video. Default "rgb24". + StreamFile string // File path for extra stream data. } ``` -The `Options.Audio` parameter is intended for users who wish to process a video stream and keep the audio. Instead of having to process the video and store in a file and then combine with the original audio later, the user can simply pass in the original file path via the `Options.Video` parameter. This will combine the video with all other streams in the given file (Audio, Subtitle, Data, and Attachments Streams) and will cut all streams to be the same length. Note that `vidio` is not a audio/video editing library. +The `Options.StreamFile` parameter is intended for users who wish to process a video stream and keep the audio (or other streams). Instead of having to process the video and store in a file and then combine with the original audio later, the user can simply pass in the original file path via the `Options.StreamFile` parameter. This will combine the video with all other streams in the given file (Audio, Subtitle, Data, and Attachments Streams) and will cut all streams to be the same length. **Note that `Vidio` is not a audio/video editing library.** -Note that this means that adding extra stream data from a file will only work if the filename being written to is a container format. +This means that adding extra stream data from a file will only work if the filename being written to is a container format. ## Images -Vidio provides some convenience functions for reading and writing to images using an array of bytes. Currently, only `png` and `jpeg` formats are supported. When reading images, an optional `buffer` can be passed in to avoid array reallocation. +`Vidio` provides some convenience functions for reading and writing to images using an array of bytes. Currently, only `png` and `jpeg` formats are supported. When reading images, an optional `buffer` can be passed in to avoid array reallocation. ```go Read(filename string, buffer ...[]byte) (int, int, []byte, error) @@ -124,8 +124,8 @@ options := vidio.Options{ FPS: video.FPS(), Bitrate: video.Bitrate(), } -if video.HasAudio() { - options.Audio = video.FileName() +if video.HasStreams() { + options.StreamFile = video.FileName() } writer, _ := vidio.NewVideoWriter("output.mp4", video.Width(), video.Height(), &options) diff --git a/video.go b/video.go index 46e2d58..97fa739 100644 --- a/video.go +++ b/video.go @@ -21,7 +21,7 @@ type Video struct { duration float64 // Duration of video in seconds. fps float64 // Frames per second. codec string // Codec used for video encoding. - hasaudio bool // Flag storing whether file has Audio. + hasstreams bool // Flag storing whether file has additional data streams. framebuffer []byte // Raw frame data. metadata map[string]string // Video metadata. pipe *io.ReadCloser // Stdout pipe for ffmpeg process. @@ -72,8 +72,9 @@ func (video *Video) Codec() string { return video.codec } -func (video *Video) HasAudio() bool { - return video.hasaudio +// Returns true if file has any audio, subtitle, data or attachment streams. +func (video *Video) HasStreams() bool { + return video.hasstreams } func (video *Video) FrameBuffer() []byte { @@ -125,19 +126,27 @@ func NewVideoStreams(filename string) ([]*Video, error) { return nil, fmt.Errorf("no video data found in %s", filename) } - audioData, err := ffprobe(filename, "a") - if err != nil { - return nil, err + // Loop over all stream types. a: Audio, s: Subtitle, d: Data, t: Attachments + hasstream := false + for _, c := range "asdt" { + data, err := ffprobe(filename, string(c)) + if err != nil { + return nil, err + } + if len(data) > 0 { + hasstream = true + break + } } streams := make([]*Video, len(videoData)) for i, data := range videoData { video := &Video{ - filename: filename, - depth: 3, - stream: i, - hasaudio: len(audioData) > 0, - metadata: data, + filename: filename, + depth: 3, + stream: i, + hasstreams: hasstream, + metadata: data, } video.addVideoData(data) diff --git a/videowriter.go b/videowriter.go index 617323e..5b15f17 100644 --- a/videowriter.go +++ b/videowriter.go @@ -12,33 +12,33 @@ import ( ) type VideoWriter struct { - filename string // Output filename. - audio string // Extra stream data filename. - width int // Frame width. - height int // Frame height. - bitrate int // Output video bitrate. - loop int // Number of times for GIF to loop. - delay int // Delay of final frame of GIF. Default -1 (same delay as previous frame). - macro int // Macroblock size for determining how to resize frames for codecs. - fps float64 // Frames per second for output video. Default 25. - quality float64 // Used if bitrate not given. Default 0.5. - codec string // Codec to encode video with. Default libx264. - format string // Output format. Default rgb24. - pipe *io.WriteCloser // Stdout pipe of ffmpeg process. - cmd *exec.Cmd // ffmpeg command. + filename string // Output filename. + streamfile string // Extra stream data filename. + width int // Frame width. + height int // Frame height. + bitrate int // Output video bitrate. + loop int // Number of times for GIF to loop. + delay int // Delay of final frame of GIF. Default -1 (same delay as previous frame). + macro int // Macroblock size for determining how to resize frames for codecs. + fps float64 // Frames per second for output video. Default 25. + quality float64 // Used if bitrate not given. Default 0.5. + codec string // Codec to encode video with. Default libx264. + format string // Output format. Default rgb24. + pipe *io.WriteCloser // Stdout pipe of ffmpeg process. + cmd *exec.Cmd // ffmpeg command. } // Optional parameters for VideoWriter. type Options struct { - Bitrate int // Bitrate. - Loop int // For GIFs only. -1=no loop, 0=infinite loop, >0=number of loops. - Delay int // Delay for final frame of GIFs. - Macro int // Macroblock size for determining how to resize frames for codecs. - FPS float64 // Frames per second for output video. - Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst. - Codec string // Codec for video. - Format string // Pixel Format for video. Default "rgb24". - Audio string // File path for extra stream data. + Bitrate int // Bitrate. + Loop int // For GIFs only. -1=no loop, 0=infinite loop, >0=number of loops. + Delay int // Delay for final frame of GIFs. + Macro int // Macroblock size for determining how to resize frames for codecs. + FPS float64 // Frames per second for output video. + Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst. + Codec string // Codec for video. + Format string // Pixel Format for video. Default "rgb24". + StreamFile string // File path for extra stream data. } func (writer *VideoWriter) FileName() string { @@ -46,8 +46,8 @@ func (writer *VideoWriter) FileName() string { } // File used to fill in extra stream data. -func (writer *VideoWriter) Audio() string { - return writer.audio +func (writer *VideoWriter) StreamFile() string { + return writer.streamfile } func (writer *VideoWriter) Width() int { @@ -155,11 +155,11 @@ func NewVideoWriter(filename string, width, height int, options *Options) (*Vide writer.format = options.Format } - if options.Audio != "" { - if !exists(options.Audio) { - return nil, fmt.Errorf("file %s does not exist", options.Audio) + if options.StreamFile != "" { + if !exists(options.StreamFile) { + return nil, fmt.Errorf("file %s does not exist", options.StreamFile) } - writer.audio = options.Audio + writer.streamfile = options.StreamFile } return writer, nil @@ -184,12 +184,12 @@ func (writer *VideoWriter) init() error { gif := strings.HasSuffix(strings.ToLower(writer.filename), ".gif") - // Assumes "writer.file" is a container format. + // Assumes "writer.streamfile" is a container format. // gif check is included since they are a common format. - if writer.audio != "" && !gif { + if writer.streamfile != "" && !gif { command = append( command, - "-i", writer.audio, + "-i", writer.streamfile, "-map", "0:v:0", "-map", "1:a?", // Add Audio streams if present. "-c:a", "copy", diff --git a/vidio_test.go b/vidio_test.go index 343e766..3fb5876 100644 --- a/vidio_test.go +++ b/vidio_test.go @@ -46,6 +46,7 @@ func TestVideoMetaData(t *testing.T) { assertEquals(video.fps, float64(30)) assertEquals(video.codec, "h264") assertEquals(video.stream, 0) + assertEquals(video.hasstreams, true) assertEquals(len(video.framebuffer), 0) if video.pipe != nil { @@ -92,8 +93,8 @@ func TestVideoWriting(t *testing.T) { Bitrate: video.Bitrate(), Codec: video.Codec(), } - if video.HasAudio() { - options.Audio = video.FileName() + if video.HasStreams() { + options.StreamFile = video.FileName() } writer, err := NewVideoWriter(output, video.width, video.height, &options)