Added getters for structs

This commit is contained in:
Alex Eidt 2022-04-16 10:45:14 -07:00
parent fb2c0b201d
commit 4fef48055a
6 changed files with 257 additions and 126 deletions

123
README.md
View file

@ -15,29 +15,24 @@ go get github.com/AlexEidt/Vidio
The `Video` struct stores data about a video file you give it. The code below shows an example of sequentially reading the frames of the given video. The `Video` struct stores data about a video file you give it. The code below shows an example of sequentially reading the frames of the given video.
```go ```go
type Video struct { FileName() string
filename string // Video Filename Width() int
width int // Width of Frames Height() int
height int // Height of Frames Depth() int
depth int // Depth of Frames Bitrate() int
bitrate int // Bitrate for video encoding Frames() int
frames int // Total number of frames Duration() float64
duration float64 // Duration in seconds FPS() float64
fps float64 // Frames per second Codec() string
codec string // Codec used to encode video AudioCodec() string
audio_codec string // Codec used for audio encoding FrameBuffer() string
pix_fmt string // Pixel format video is stored in
framebuffer []byte // Raw frame data
pipe *io.ReadCloser // Stdout pipe for ffmpeg process
cmd *exec.Cmd // ffmpeg command
}
``` ```
```go ```go
video := vidio.NewVideo("input.mp4") video := vidio.NewVideo("input.mp4")
for video.Read() { for video.Read() {
// "frame" stores the video frame as a flattened RGB image // "frame" stores the video frame as a flattened RGB image in row-major order
frame := video.framebuffer // stored as: RGBRGBRGBRGB... frame := video.FrameBuffer() // stored as: RGBRGBRGBRGB...
// Video processing here... // Video processing here...
} }
``` ```
@ -47,17 +42,13 @@ for video.Read() {
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. Note that audio retrieval from the microphone is not yet supported. 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. Note that audio retrieval from the microphone is not yet supported.
```go ```go
type Camera struct { Name() string
name string // Camera device name Width() int
width int // Camera frame width Height() int
height int // Camera frame height Depth() int
depth int // Camera frame depth FPS() float64
fps float64 // Camera frames per second Codec() string
codec string // Camera codec FrameBuffer() string
framebuffer []byte // Raw frame data
pipe *io.ReadCloser // Stdout pipe for ffmpeg process streaming webcam
cmd *exec.Cmd // ffmpeg command
}
``` ```
```go ```go
@ -67,7 +58,7 @@ defer camera.Close()
// Stream the webcam // Stream the webcam
for camera.Read() { for camera.Read() {
// "frame" stores the video frame as a flattened RGB image // "frame" stores the video frame as a flattened RGB image
frame := camera.framebuffer // stored as: RGBRGBRGBRGB... frame := camera.FrameBuffer() // stored as: RGBRGBRGBRGB...
// Video processing here... // Video processing here...
} }
``` ```
@ -77,35 +68,30 @@ for camera.Read() {
The `VideoWriter` is used to write frames to a video file. The only required parameters are the output file name, the width and height of the frames being written, and an `Options` struct. This contains all the desired properties of the new video you want to create. The `VideoWriter` is used to write frames to a video file. The only required parameters are the output file name, the width and height of the frames being written, and an `Options` struct. This contains all the desired properties of the new video you want to create.
```go ```go
type Options struct { FileName() string
bitrate int // Bitrate Width() int
loop int // For GIFs only. -1=no loop, 0=loop forever, >0=loop n times Height() int
delay int // Delay for Final Frame of GIFs. Default -1 (Use same delay as previous frame) Bitrate() int
macro int // macro size for determining how to resize frames for codecs. Default 16 Loop() int
fps float64 // Frames per second. Default 25 Delay() int
quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst Macro() int
codec string // Codec for video. Default libx264 FPS() float64
audio string // File path for audio for the video. If no audio, audio=nil. Quality() float64
audio_codec string // Codec for audio. Default aac Codec() string
} AudioCodec() string
``` ```
```go ```go
type VideoWriter struct { type Options struct {
filename string // Output filename Bitrate int // Bitrate
audio string // Audio filename Loop int // For GIFs only. -1=no loop, 0=loop forever, >0=loop n times
width int // Frame width Delay int // Delay for Final Frame of GIFs. Default -1 (Use same delay as previous frame)
height int // Frame height Macro int // macro size for determining how to resize frames for codecs. Default 16
bitrate int // Output video bitrate FPS float64 // Frames per second. Default 25
loop int // Number of times for GIF to loop Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst
delay int // Delay of final frame of GIF. Default -1 (same delay as previous frame) Codec string // Codec for video. Default libx264
macro int // Macroblock size for determining how to resize frames for codecs Audio string // File path for audio for the video. If no audio, audio=nil.
fps float64 // Frames per second for output video. Default 25 AudioCodec string // Codec for audio. Default aac
quality float64 // Used if bitrate not given. Default 0.5
codec string // Codec to encode video with. Default libx264
audio_codec string // Codec to encode audio with. Default aac
pipe *io.WriteCloser // Stdout pipe of ffmpeg process
cmd *exec.Cmd // ffmpeg command
} }
``` ```
@ -142,16 +128,16 @@ Copy `input.mp4` to `output.mp4`. Copy the audio from `input.mp4` to `output.mp4
```go ```go
video := vidio.NewVideo("input.mp4") video := vidio.NewVideo("input.mp4")
options := vidio.Options{ options := vidio.Options{
fps: video.fps, FPS: video.FPS(),
bitrate: video.bitrate, Bitrate: video.Bitrate(),
audio: "input.mp4", Audio: "input.mp4",
} }
writer := vidio.NewVideoWriter("output.mp4", video.width, video.height, &options) writer := vidio.NewVideoWriter("output.mp4", video.width, video.height, &options)
defer writer.Close() defer writer.Close()
for video.Read() { for video.Read() {
writer.Write(video.framebuffer) writer.Write(video.FrameBuffer())
} }
``` ```
@ -161,22 +147,23 @@ Grayscale 1000 frames of webcam stream and store in `output.mp4`.
webcam := vidio.NewCamera(0) webcam := vidio.NewCamera(0)
defer webcam.Close() defer webcam.Close()
options := vidio.Options{fps: webcam.fps} options := vidio.Options{FPS: webcam.FPS()}
writer := vidio.NewVideoWriter("output.mp4", webcam.width, webcam.height, &options) writer := vidio.NewVideoWriter("output.mp4", webcam.width, webcam.height, &options)
defer writer.Close() defer writer.Close()
count := 0 count := 0
for webcam.Read() { for webcam.Read() {
for i := 0; i < len(webcam.framebuffer); i += 3 { frame := webcam.FrameBuffer()
rgb := webcam.framebuffer[i : i+3] for i := 0; i < len(frame); i += 3 {
rgb := frame[i : i+3]
r, g, b := int(rgb[0]), int(rgb[1]), int(rgb[2]) r, g, b := int(rgb[0]), int(rgb[1]), int(rgb[2])
gray := uint8((3*r + 4*g + b) / 8) gray := uint8((3*r + 4*g + b) / 8)
webcam.framebuffer[i] = gray frame[i] = gray
webcam.framebuffer[i+1] = gray frame[i+1] = gray
webcam.framebuffer[i+2] = gray frame[i+2] = gray
} }
writer.Write(webcam.framebuffer) writer.Write(frame)
count++ count++
if count > 1000 { if count > 1000 {
break break
@ -189,7 +176,7 @@ Create a gif from a series of `png` files enumerated from 1 to 10 that loops con
```go ```go
w, h, _ := vidio.Read("1.png") // Get frame dimensions from first image w, h, _ := vidio.Read("1.png") // Get frame dimensions from first image
options := vidio.Options{fps: 1, loop: -1, delay: 1000} options := vidio.Options{FPS: 1, Loop: -1, Delay: 1000}
gif := vidio.NewVideoWriter("output.gif", w, h, &options) gif := vidio.NewVideoWriter("output.gif", w, h, &options)
defer gif.Close() defer gif.Close()

View file

@ -22,6 +22,34 @@ type Camera struct {
cmd *exec.Cmd // ffmpeg command. cmd *exec.Cmd // ffmpeg command.
} }
func (camera *Camera) Name() string {
return camera.name
}
func (camera *Camera) Width() int {
return camera.width
}
func (camera *Camera) Height() int {
return camera.height
}
func (camera *Camera) Depth() int {
return camera.depth
}
func (camera *Camera) FPS() float64 {
return camera.fps
}
func (camera *Camera) Codec() string {
return camera.codec
}
func (camera *Camera) FrameBuffer() []byte {
return camera.framebuffer
}
// Returns the webcam device name. // Returns the webcam device name.
// On windows, ffmpeg output from the -list_devices command is parsed to find the device name. // On windows, ffmpeg output from the -list_devices command is parsed to find the device name.
func getDevicesWindows() []string { func getDevicesWindows() []string {

View file

@ -116,8 +116,8 @@ func addVideoData(data map[string]string, video *Video) {
if codec, ok := data["codec_name"]; ok { if codec, ok := data["codec_name"]; ok {
video.codec = codec video.codec = codec
} }
if pix_fmt, ok := data["pix_fmt"]; ok { if pixfmt, ok := data["pix_fmt"]; ok {
video.pix_fmt = pix_fmt video.pixfmt = pixfmt
} }
} }

View file

@ -18,13 +18,60 @@ type Video struct {
duration float64 // Duration of video in seconds. duration float64 // Duration of video in seconds.
fps float64 // Frames per second. fps float64 // Frames per second.
codec string // Codec used for video encoding. codec string // Codec used for video encoding.
audio_codec string // Codec used for audio encoding. audioCodec string // Codec used for audio encoding.
pix_fmt string // Pixel format video is stored in. pixfmt string // Pixel format video is stored in.
framebuffer []byte // Raw frame data. framebuffer []byte // Raw frame data.
pipe *io.ReadCloser // Stdout pipe for ffmpeg process. pipe *io.ReadCloser // Stdout pipe for ffmpeg process.
cmd *exec.Cmd // ffmpeg command. cmd *exec.Cmd // ffmpeg command.
} }
func (video *Video) FileName() string {
return video.filename
}
func (video *Video) Width() int {
return video.width
}
func (video *Video) Height() int {
return video.height
}
// Channels of video frames.
func (video *Video) Depth() int {
return video.depth
}
// Bitrate of video.
func (video *Video) Bitrate() int {
return video.bitrate
}
// Total number of frames in video.
func (video *Video) Frames() int {
return video.frames
}
func (video *Video) Duration() float64 {
return video.duration
}
func (video *Video) FPS() float64 {
return video.fps
}
func (video *Video) Codec() string {
return video.codec
}
func (video *Video) AudioCodec() string {
return video.audioCodec
}
func (video *Video) FrameBuffer() []byte {
return video.framebuffer
}
// Creates a new Video struct. // Creates a new Video struct.
// Uses ffprobe to get video information and fills in the Video struct with this data. // Uses ffprobe to get video information and fills in the Video struct with this data.
func NewVideo(filename string) *Video { func NewVideo(filename string) *Video {
@ -41,8 +88,8 @@ func NewVideo(filename string) *Video {
video := &Video{filename: filename, depth: 3} video := &Video{filename: filename, depth: 3}
addVideoData(videoData, video) addVideoData(videoData, video)
if audio_codec, ok := audioData["codec_name"]; ok { if audioCodec, ok := audioData["codec_name"]; ok {
video.audio_codec = audio_codec video.audioCodec = audioCodec
} }
return video return video

View file

@ -3,6 +3,7 @@ package vidio
import ( import (
"fmt" "fmt"
"io" "io"
"math"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
@ -22,22 +23,66 @@ type VideoWriter struct {
fps float64 // Frames per second for output video. Default 25. fps float64 // Frames per second for output video. Default 25.
quality float64 // Used if bitrate not given. Default 0.5. quality float64 // Used if bitrate not given. Default 0.5.
codec string // Codec to encode video with. Default libx264. codec string // Codec to encode video with. Default libx264.
audio_codec string // Codec to encode audio with. Default aac. audioCodec string // Codec to encode audio with. Default aac.
pipe *io.WriteCloser // Stdout pipe of ffmpeg process. pipe *io.WriteCloser // Stdout pipe of ffmpeg process.
cmd *exec.Cmd // ffmpeg command. cmd *exec.Cmd // ffmpeg command.
} }
// Optional parameters for VideoWriter. // Optional parameters for VideoWriter.
type Options struct { type Options struct {
bitrate int // Bitrate. Bitrate int // Bitrate.
loop int // For GIFs only. -1=no loop, 0=infinite loop, >0=number of loops. Loop int // For GIFs only. -1=no loop, 0=infinite loop, >0=number of loops.
delay int // Delay for final frame of GIFs. Delay int // Delay for final frame of GIFs.
macro int // Macroblock size for determining how to resize frames for codecs. Macro int // Macroblock size for determining how to resize frames for codecs.
fps float64 // Frames per second for output video. 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. Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst.
codec string // Codec for video. Codec string // Codec for video.
audio string // File path for audio. If no audio, audio=nil. Audio string // File path for audio. If no audio, audio=nil.
audio_codec string // Codec for audio. AudioCodec string // Codec for audio.
}
func (writer *VideoWriter) FileName() string {
return writer.filename
}
func (writer *VideoWriter) Width() int {
return writer.width
}
func (writer *VideoWriter) Height() int {
return writer.height
}
func (writer *VideoWriter) Bitrate() int {
return writer.bitrate
}
func (writer *VideoWriter) Loop() int {
return writer.loop
}
func (writer *VideoWriter) Delay() int {
return writer.delay
}
func (writer *VideoWriter) Macro() int {
return writer.macro
}
func (writer *VideoWriter) FPS() float64 {
return writer.fps
}
func (writer *VideoWriter) Quality() float64 {
return writer.quality
}
func (writer *VideoWriter) Codec() string {
return writer.codec
}
func (writer *VideoWriter) AudioCodec() string {
return writer.audioCodec
} }
// Creates a new VideoWriter struct with default values from the Options struct. // Creates a new VideoWriter struct with default values from the Options struct.
@ -49,38 +94,38 @@ func NewVideoWriter(filename string, width, height int, options *Options) *Video
writer.width = width writer.width = width
writer.height = height writer.height = height
writer.bitrate = options.bitrate writer.bitrate = options.Bitrate
// Default Parameter options logic from: // Default Parameter options logic from:
// https://github.com/imageio/imageio-ffmpeg/blob/master/imageio_ffmpeg/_io.py#L268. // https://github.com/imageio/imageio-ffmpeg/blob/master/imageio_ffmpeg/_io.py#L268.
// GIF settings // GIF settings
writer.loop = options.loop // Default to infinite loop. writer.loop = options.Loop // Default to infinite loop.
if options.delay == 0 { if options.Delay == 0 {
writer.delay = -1 // Default to frame delay of previous frame. writer.delay = -1 // Default to frame delay of previous frame.
} else { } else {
writer.delay = options.delay writer.delay = options.Delay
} }
if options.macro == 0 { if options.Macro == 0 {
writer.macro = 16 writer.macro = 16
} else { } else {
writer.macro = options.macro writer.macro = options.Macro
} }
if options.fps == 0 { if options.FPS == 0 {
writer.fps = 25 writer.fps = 25
} else { } else {
writer.fps = options.fps writer.fps = options.FPS
} }
if options.quality == 0 { if options.Quality == 0 {
writer.quality = 0.5 writer.quality = 0.5
} else { } else {
writer.quality = options.quality writer.quality = math.Max(0, math.Min(options.Quality, 1))
} }
if options.codec == "" { if options.Codec == "" {
if strings.HasSuffix(strings.ToLower(filename), ".wmv") { if strings.HasSuffix(strings.ToLower(filename), ".wmv") {
writer.codec = "msmpeg4" writer.codec = "msmpeg4"
} else if strings.HasSuffix(strings.ToLower(filename), ".gif") { } else if strings.HasSuffix(strings.ToLower(filename), ".gif") {
@ -89,24 +134,24 @@ func NewVideoWriter(filename string, width, height int, options *Options) *Video
writer.codec = "libx264" writer.codec = "libx264"
} }
} else { } else {
writer.codec = options.codec writer.codec = options.Codec
} }
if options.audio != "" { if options.Audio != "" {
if !exists(options.audio) { if !exists(options.Audio) {
panic("Audio file " + options.audio + " does not exist.") panic("Audio file " + options.Audio + " does not exist.")
} }
if len(ffprobe(options.audio, "a")) == 0 { if len(ffprobe(options.Audio, "a")) == 0 {
panic("Given \"audio\" file " + options.audio + " has no audio.") panic("Given \"audio\" file " + options.Audio + " has no audio.")
} }
writer.audio = options.audio writer.audio = options.Audio
if options.audio_codec == "" { if options.AudioCodec == "" {
writer.audio_codec = "aac" writer.audioCodec = "aac"
} else { } else {
writer.audio_codec = options.audio_codec writer.audioCodec = options.AudioCodec
} }
} }
@ -195,7 +240,7 @@ func initVideoWriter(writer *VideoWriter) {
if writer.audio != "" && !gif { if writer.audio != "" && !gif {
command = append( command = append(
command, command,
"-acodec", writer.audio_codec, "-acodec", writer.audioCodec,
"-map", "0:v:0", "-map", "0:v:0",
"-map", "1:a:0", "-map", "1:a:0",
) )

View file

@ -25,8 +25,8 @@ func TestVideoMetaData(t *testing.T) {
assertEquals(video.duration, 3.366667) assertEquals(video.duration, 3.366667)
assertEquals(video.fps, float64(30)) assertEquals(video.fps, float64(30))
assertEquals(video.codec, "h264") assertEquals(video.codec, "h264")
assertEquals(video.audio_codec, "aac") assertEquals(video.audioCodec, "aac")
assertEquals(video.pix_fmt, "yuv420p") assertEquals(video.pixfmt, "yuv420p")
assertEquals(len(video.framebuffer), 0) assertEquals(len(video.framebuffer), 0)
if video.pipe != nil { if video.pipe != nil {
@ -63,17 +63,17 @@ func TestVideoWriting(t *testing.T) {
testWriting := func(input, output string, audio bool) { testWriting := func(input, output string, audio bool) {
video := NewVideo(input) video := NewVideo(input)
options := Options{ options := Options{
fps: video.fps, FPS: video.FPS(),
bitrate: video.bitrate, Bitrate: video.Bitrate(),
codec: video.codec, Codec: video.Codec(),
} }
if audio { if audio {
options.audio = input options.Audio = input
} }
writer := NewVideoWriter(output, video.width, video.height, &options) writer := NewVideoWriter(output, video.width, video.height, &options)
for video.Read() { for video.Read() {
writer.Write(video.framebuffer) writer.Write(video.FrameBuffer())
} }
writer.Close() writer.Close()
@ -86,6 +86,30 @@ func TestVideoWriting(t *testing.T) {
fmt.Println("Video Writing (without Audio) Test Passed") fmt.Println("Video Writing (without Audio) Test Passed")
} }
func TestCameraIO(t *testing.T) {
webcam := NewCamera(0)
options := Options{FPS: webcam.FPS()}
writer := NewVideoWriter("test/camera.mp4", webcam.width, webcam.height, &options)
count := 0
for webcam.Read() {
frame := webcam.FrameBuffer()
writer.Write(frame)
count++
if count > 100 {
break
}
}
webcam.Close()
writer.Close()
//os.Remove("test/camera.mp4")
fmt.Println("Camera IO Test Passed")
}
func TestFFprobe(t *testing.T) { func TestFFprobe(t *testing.T) {
koalaVideo := ffprobe("test/koala.mp4", "v") koalaVideo := ffprobe("test/koala.mp4", "v")
assertEquals(koalaVideo["width"], "480") assertEquals(koalaVideo["width"], "480")