Cleanup and updated README
This commit is contained in:
parent
0cc5ffe925
commit
723de2fa8a
9 changed files with 254 additions and 130 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
||||||
main.go
|
|
||||||
*.gif
|
*.gif
|
||||||
*.mp4
|
*.mp4
|
192
README.md
192
README.md
|
@ -2,134 +2,194 @@
|
||||||
|
|
||||||
A simple Video I/O library written in Go. This library relies on [FFmpeg](https://www.ffmpeg.org/), and [FFProbe](https://www.ffmpeg.org/) which must be downloaded before usage.
|
A simple Video I/O library written in Go. This library relies on [FFmpeg](https://www.ffmpeg.org/), and [FFProbe](https://www.ffmpeg.org/) which must be downloaded before usage.
|
||||||
|
|
||||||
|
All frames are encoded and decoded in 8-bit RGB format.
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/AlexEidt/Vidio
|
||||||
|
```
|
||||||
|
|
||||||
## `Video`
|
## `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.
|
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
|
||||||
video := NewVideo("input.mp4")
|
video := vidio.NewVideo("input.mp4")
|
||||||
for video.NextFrame() {
|
for video.Read() {
|
||||||
// "frame" stores the video frame as a flattened RGB image.
|
// "frame" stores the video frame as a flattened RGB image
|
||||||
frame := video.framebuffer // stored as: RGBRGBRGBRGB...
|
frame := video.framebuffer // stored as: RGBRGBRGBRGB...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Video struct {
|
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
|
||||||
pix_fmt string
|
pix_fmt string // Pixel format video is stored in
|
||||||
framebuffer []byte
|
framebuffer []byte // Raw frame data
|
||||||
pipe *io.ReadCloser
|
pipe *io.ReadCloser // Stdout pipe for ffmpeg process
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd // ffmpeg command
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `Camera`
|
## `Camera`
|
||||||
|
|
||||||
The `Camera` can read from any cameras on the device running Vidio.
|
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
|
```go
|
||||||
type Camera struct {
|
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 []byte
|
framebuffer []byte // Raw frame data
|
||||||
pipe *io.ReadCloser
|
pipe *io.ReadCloser // Stdout pipe for ffmpeg process streaming webcam
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd // ffmpeg command
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
camera := NewCamera(0) // Get Webcam
|
camera := vidio.NewCamera(0) // Get Webcam
|
||||||
defer camera.Close()
|
defer camera.Close()
|
||||||
|
|
||||||
// Stream the webcam.
|
// Stream the webcam
|
||||||
for camera.NextFrame() {
|
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...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `VideoWriter`
|
## `VideoWriter`
|
||||||
|
|
||||||
The `VideoWriter` is used to write frames to a video file. You first need to create a `Video` struct with all the desired properties of the new video you want to create such as width, height and framerate.
|
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 such as width, height and framerate.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Options struct {
|
type Options struct {
|
||||||
width int // Width of Output Frames
|
bitrate int // Bitrate
|
||||||
height int // Height of Output Frames
|
loop int // For GIFs only. -1=no loop, 0=loop forever, >0=loop n times
|
||||||
bitrate int // Bitrate
|
delay int // Delay for Final Frame of GIFs. Default -1 (Use same delay as previous frame)
|
||||||
loop int // For GIFs only. -1=no loop, 0=loop forever, >0=loop n times
|
macro int // macro size for determining how to resize frames for codecs. Default 16
|
||||||
delay int // Delay for Final Frame of GIFs
|
fps float64 // Frames per second. Default 25
|
||||||
macro int // macro size for determining how to resize frames for codecs
|
quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst
|
||||||
fps float64 // Frames per second
|
codec string // Codec for video. Default libx264
|
||||||
codec string // Codec for video
|
|
||||||
in_pix_fmt string // Pixel Format of incoming bytes
|
|
||||||
out_pix_fmt string // Pixel Format for video being written
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type VideoWriter struct {
|
type VideoWriter struct {
|
||||||
filename string
|
filename string // Output video filename
|
||||||
width int
|
width int // Frame width
|
||||||
height int
|
height int // Frame height
|
||||||
bitrate int
|
bitrate int // Output video bitrate for encoding
|
||||||
loop int
|
loop int // Number of times for GIF to loop
|
||||||
delay int
|
delay int // Delay of final frame of GIF
|
||||||
macro int
|
macro int // macro size for determining how to resize frames for codecs
|
||||||
fps float64
|
fps float64 // Frames per second for output video
|
||||||
codec string
|
quality float64 // Used if bitrate not given
|
||||||
in_pix_fmt string
|
codec string // Codec to encode video with
|
||||||
out_pix_fmt string
|
pipe *io.WriteCloser // Stdout pipe of ffmpeg process
|
||||||
pipe *io.WriteCloser
|
cmd *exec.Cmd // ffmpeg command
|
||||||
cmd *exec.Cmd
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
w, h, c := 1920, 1080, 3
|
w, h, c := 1920, 1080, 3
|
||||||
options = Options{width: w, height: w, bitrate: 100000}
|
options = vidio.Options{} // Will fill in defaults if empty
|
||||||
|
|
||||||
writer := NewVideoWriter("output.mp4", &options)
|
writer := vidio.NewVideoWriter("output.mp4", w, h, &options)
|
||||||
defer writer.Close() // Make sure to close writer.
|
defer writer.Close()
|
||||||
|
|
||||||
frame = make([]byte, w*h*c) // Create Frame as RGB Image and modify.
|
frame := make([]byte, w*h*c) // Create Frame as RGB Image and modify
|
||||||
writer.Write(frame) // Write Frame to video.
|
writer.Write(frame) // Write Frame to video
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Read png image
|
||||||
|
w, h, img := vidio.Read("input.png")
|
||||||
|
|
||||||
|
// w - width of image
|
||||||
|
// h - height of image
|
||||||
|
// img - byte array in RGB format. RGBRGBRGBRGB...
|
||||||
|
|
||||||
|
vidio.Write("output.jpg", w, h, img)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Copy `input` to `output`.
|
Copy `input.mp4` to `output.mp4`.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
video := NewVideo(input)
|
video := vidio.NewVideo("input.mp4")
|
||||||
options := Options{
|
options := vidio.Options{
|
||||||
width: video.width,
|
|
||||||
height: video.height,
|
|
||||||
fps: video.fps,
|
fps: video.fps,
|
||||||
bitrate: video.bitrate
|
bitrate: video.bitrate
|
||||||
}
|
}
|
||||||
|
|
||||||
writer := NewVideoWriter(output, &options)
|
writer := vidio.NewVideoWriter("output.mp4", video.width, video.height, &options)
|
||||||
defer writer.Close()
|
defer writer.Close()
|
||||||
|
|
||||||
for video.NextFrame() {
|
for video.Read() {
|
||||||
writer.Write(video.framebuffer)
|
writer.Write(video.framebuffer)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Grayscale 1000 frames of webcam stream and store in `output.mp4`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
webcam := vidio.NewCamera(0)
|
||||||
|
defer webcam.Close()
|
||||||
|
|
||||||
|
options := vidio.Options{fps: webcam.fps}
|
||||||
|
|
||||||
|
writer := vidio.NewVideoWriter("output.mp4", webcam.width, webcam.height, &options)
|
||||||
|
defer writer.Close()
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for webcam.Read() {
|
||||||
|
for i := 0; i < len(webcam.framebuffer); i += 3 {
|
||||||
|
rgb := webcam.framebuffer[i : i+3]
|
||||||
|
r, g, b := int(rgb[0]), int(rgb[1]), int(rgb[2])
|
||||||
|
gray := uint8((3*r + 4*g + b) / 8)
|
||||||
|
writer.framebuffer[i] = gray
|
||||||
|
writer.framebuffer[i+1] = gray
|
||||||
|
writer.framebuffer[i+2] = gray
|
||||||
|
}
|
||||||
|
writer.Write(webcam.framebuffer)
|
||||||
|
if count > 1000 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a gif from a series of `png` files enumerated from 1 to 10 that loops continously with a final frame delay of 1000 centiseconds.
|
||||||
|
|
||||||
|
```go
|
||||||
|
w, h, _ := vidio.Read("1.png") // Get frame dimensions from first image
|
||||||
|
|
||||||
|
options := vidio.Options{fps: 1, loop: -1, delay: 1000}
|
||||||
|
|
||||||
|
gif := vidio.NewVideoWriter("output.gif", w, h, &options)
|
||||||
|
defer gif.Close()
|
||||||
|
|
||||||
|
for i := 1; i <= 10; i++ {
|
||||||
|
_, _, img := vidio.Read(strconv.Itoa(i)+".png")
|
||||||
|
gif.Write(img)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Acknowledgements
|
# Acknowledgements
|
||||||
|
|
||||||
* Special thanks to [Zulko](http://zulko.github.io/) and his [blog post](http://zulko.github.io/blog/2013/09/27/read-and-write-video-frames-in-python-using-ffmpeg/) about using FFmpeg to process video.
|
* Special thanks to [Zulko](http://zulko.github.io/) and his [blog post](http://zulko.github.io/blog/2013/09/27/read-and-write-video-frames-in-python-using-ffmpeg/) about using FFmpeg to process video.
|
||||||
|
|
13
camera.go
13
camera.go
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package vidio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -55,7 +55,7 @@ func getDevicesWindows() []string {
|
||||||
|
|
||||||
func getCameraData(device string, camera *Camera) {
|
func getCameraData(device string, camera *Camera) {
|
||||||
// Run command to get camera data.
|
// Run command to get camera data.
|
||||||
// On windows the webcam will turn on and off again.
|
// Webcam will turn on and then off in quick succession.
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"-hide_banner",
|
"-hide_banner",
|
||||||
|
@ -88,11 +88,10 @@ func getCameraData(device string, camera *Camera) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCamera(stream int) *Camera {
|
func NewCamera(stream int) *Camera {
|
||||||
|
// Check if ffmpeg is installed on the users machine.
|
||||||
checkExists("ffmpeg")
|
checkExists("ffmpeg")
|
||||||
|
|
||||||
var device string
|
var device string
|
||||||
// If OS is windows, we need to parse the listed devices to find which corresponds to the
|
|
||||||
// given "stream" index.
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
device = "/dev/video" + strconv.Itoa(stream)
|
device = "/dev/video" + strconv.Itoa(stream)
|
||||||
|
@ -101,6 +100,8 @@ func NewCamera(stream int) *Camera {
|
||||||
device = strconv.Itoa(stream)
|
device = strconv.Itoa(stream)
|
||||||
break
|
break
|
||||||
case "windows":
|
case "windows":
|
||||||
|
// If OS is windows, we need to parse the listed devices to find which corresponds to the
|
||||||
|
// given "stream" index.
|
||||||
devices := getDevicesWindows()
|
devices := getDevicesWindows()
|
||||||
if stream >= len(devices) {
|
if stream >= len(devices) {
|
||||||
panic("Could not find devices with index: " + strconv.Itoa(stream))
|
panic("Could not find devices with index: " + strconv.Itoa(stream))
|
||||||
|
@ -123,10 +124,10 @@ func initCamera(camera *Camera) {
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"-hide_banner",
|
"-hide_banner",
|
||||||
|
"-loglevel", "quiet",
|
||||||
"-f", webcam(),
|
"-f", webcam(),
|
||||||
"-i", camera.name,
|
"-i", camera.name,
|
||||||
"-f", "image2pipe",
|
"-f", "image2pipe",
|
||||||
"-loglevel", "quiet",
|
|
||||||
"-pix_fmt", "rgb24",
|
"-pix_fmt", "rgb24",
|
||||||
"-vcodec", "rawvideo", "-",
|
"-vcodec", "rawvideo", "-",
|
||||||
)
|
)
|
||||||
|
@ -144,7 +145,7 @@ func initCamera(camera *Camera) {
|
||||||
camera.framebuffer = make([]byte, camera.width*camera.height*camera.depth)
|
camera.framebuffer = make([]byte, camera.width*camera.height*camera.depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (camera *Camera) NextFrame() bool {
|
func (camera *Camera) Read() bool {
|
||||||
// If cmd is nil, video reading has not been initialized.
|
// If cmd is nil, video reading has not been initialized.
|
||||||
if camera.cmd == nil {
|
if camera.cmd == nil {
|
||||||
initCamera(camera)
|
initCamera(camera)
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -1,3 +1,3 @@
|
||||||
module github.com/AlexEidt/Video-IO
|
module github.com/AlexEidt/Vidio
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1,4 +0,0 @@
|
||||||
github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e/go.mod h1:eagM805MRKrioHYuU7iKLUyFPVKqVV6um5DAvCkUtXs=
|
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
gocv.io/x/gocv v0.28.0 h1:hweRS9Js60YEZPZzjhU5I+0E2ngazquLlO78zwnrFvY=
|
|
||||||
gocv.io/x/gocv v0.28.0/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU=
|
|
69
imageio.go
Normal file
69
imageio.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package vidio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"image/color"
|
||||||
|
"image/jpeg"
|
||||||
|
_ "image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
_ "image/png"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reads an image from a file. Currently only supports png and jpeg.
|
||||||
|
func Read(filename string) (int, int, []byte) {
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
image, _, err := image.Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
bounds := image.Bounds().Max
|
||||||
|
data := make([]byte, bounds.Y*bounds.X*3)
|
||||||
|
index := 0
|
||||||
|
for h := 0; h < bounds.Y; h++ {
|
||||||
|
for w := 0; w < bounds.X; w++ {
|
||||||
|
r, g, b, _ := image.At(w, h).RGBA()
|
||||||
|
r, g, b = r/256, g/256, b/256
|
||||||
|
data[index] = byte(r)
|
||||||
|
index++
|
||||||
|
data[index] = byte(g)
|
||||||
|
index++
|
||||||
|
data[index] = byte(b)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bounds.X, bounds.Y, data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes an image to a file. Currently only supports png and jpeg.
|
||||||
|
func Write(filename string, width, height int, data []byte) {
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
image := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
index := 0
|
||||||
|
for h := 0; h < height; h++ {
|
||||||
|
for w := 0; w < width; w++ {
|
||||||
|
r, g, b := data[index], data[index+1], data[index+2]
|
||||||
|
image.Set(w, h, color.RGBA{r, g, b, 255})
|
||||||
|
index += 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(filename, ".png") {
|
||||||
|
if err := png.Encode(f, image); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else if strings.HasSuffix(filename, ".jpg") || strings.HasSuffix(filename, ".jpeg") {
|
||||||
|
if err := jpeg.Encode(f, image, nil); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
utils.go
2
utils.go
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package vidio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package vidio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -28,6 +28,7 @@ func NewVideo(filename string) *Video {
|
||||||
if !exists(filename) {
|
if !exists(filename) {
|
||||||
panic("File: " + filename + " does not exist")
|
panic("File: " + filename + " does not exist")
|
||||||
}
|
}
|
||||||
|
// Check if ffmpeg and ffprobe are installed on the users machine.
|
||||||
checkExists("ffmpeg")
|
checkExists("ffmpeg")
|
||||||
checkExists("ffprobe")
|
checkExists("ffprobe")
|
||||||
// Extract video information with ffprobe.
|
// Extract video information with ffprobe.
|
||||||
|
@ -71,8 +72,6 @@ func initVideoStream(video *Video) {
|
||||||
// If user exits with Ctrl+C, stop ffmpeg process.
|
// If user exits with Ctrl+C, stop ffmpeg process.
|
||||||
video.cleanup()
|
video.cleanup()
|
||||||
|
|
||||||
// map = {1: "gray", 2: "gray8a", 3: "rgb24", 4: "rgba"}
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"-i", video.filename,
|
"-i", video.filename,
|
||||||
|
@ -94,7 +93,7 @@ func initVideoStream(video *Video) {
|
||||||
video.framebuffer = make([]byte, video.width*video.height*video.depth)
|
video.framebuffer = make([]byte, video.width*video.height*video.depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (video *Video) NextFrame() bool {
|
func (video *Video) Read() bool {
|
||||||
// If cmd is nil, video reading has not been initialized.
|
// If cmd is nil, video reading has not been initialized.
|
||||||
if video.cmd == nil {
|
if video.cmd == nil {
|
||||||
initVideoStream(video)
|
initVideoStream(video)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package vidio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -11,43 +11,35 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type VideoWriter struct {
|
type VideoWriter struct {
|
||||||
filename string
|
filename string
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
bitrate int
|
bitrate int
|
||||||
loop int // For GIFs. -1=no loop, 0=loop forever, >0=loop n times
|
loop int // For GIFs. -1=no loop, 0=loop forever, >0=loop n times
|
||||||
delay int // Delay for Final Frame of GIFs.
|
delay int // Delay for Final Frame of GIFs.
|
||||||
macro int
|
macro int
|
||||||
fps float64
|
fps float64
|
||||||
codec string
|
quality float64
|
||||||
in_pix_fmt string
|
codec string
|
||||||
out_pix_fmt string
|
pipe *io.WriteCloser
|
||||||
pipe *io.WriteCloser
|
cmd *exec.Cmd
|
||||||
cmd *exec.Cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
width int
|
bitrate int
|
||||||
height int
|
loop int
|
||||||
bitrate int
|
delay int
|
||||||
loop int
|
macro int
|
||||||
delay int
|
fps float64
|
||||||
macro int
|
quality float64
|
||||||
fps float64
|
codec string
|
||||||
codec string
|
|
||||||
in_pix_fmt string
|
|
||||||
out_pix_fmt string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVideoWriter(filename string, options *Options) *VideoWriter {
|
func NewVideoWriter(filename string, width, height int, options *Options) *VideoWriter {
|
||||||
writer := VideoWriter{filename: filename}
|
writer := VideoWriter{filename: filename}
|
||||||
|
|
||||||
if options.width == 0 || options.height == 0 {
|
writer.width = width
|
||||||
panic("width and height must be greater than 0.")
|
writer.height = height
|
||||||
} else {
|
|
||||||
writer.width = options.width
|
|
||||||
writer.height = options.height
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -72,6 +64,12 @@ func NewVideoWriter(filename string, options *Options) *VideoWriter {
|
||||||
writer.fps = options.fps
|
writer.fps = options.fps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.quality == 0 {
|
||||||
|
writer.quality = 0.5
|
||||||
|
} else {
|
||||||
|
writer.quality = options.quality
|
||||||
|
}
|
||||||
|
|
||||||
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"
|
||||||
|
@ -84,18 +82,6 @@ func NewVideoWriter(filename string, options *Options) *VideoWriter {
|
||||||
writer.codec = options.codec
|
writer.codec = options.codec
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.in_pix_fmt == "" {
|
|
||||||
writer.in_pix_fmt = "rgb24"
|
|
||||||
} else {
|
|
||||||
writer.in_pix_fmt = options.in_pix_fmt
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.out_pix_fmt == "" {
|
|
||||||
writer.out_pix_fmt = "yuv420p"
|
|
||||||
} else {
|
|
||||||
writer.out_pix_fmt = options.out_pix_fmt
|
|
||||||
}
|
|
||||||
|
|
||||||
return &writer
|
return &writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,13 +95,27 @@ func initVideoWriter(writer *VideoWriter) {
|
||||||
"-f", "rawvideo",
|
"-f", "rawvideo",
|
||||||
"-vcodec", "rawvideo",
|
"-vcodec", "rawvideo",
|
||||||
"-s", fmt.Sprintf("%dx%d", writer.width, writer.height), // frame w x h
|
"-s", fmt.Sprintf("%dx%d", writer.width, writer.height), // frame w x h
|
||||||
"-pix_fmt", writer.in_pix_fmt,
|
"-pix_fmt", "rgb24",
|
||||||
"-r", fmt.Sprintf("%.02f", writer.fps), // frames per second
|
"-r", fmt.Sprintf("%.02f", writer.fps), // frames per second
|
||||||
"-i", "-", // The input comes from stdin
|
"-i", "-", // The input comes from stdin
|
||||||
"-an", // Tells ffmpeg not to expect any audio
|
"-an", // Tells ffmpeg not to expect any audio
|
||||||
"-vcodec", writer.codec,
|
"-vcodec", writer.codec,
|
||||||
"-pix_fmt", writer.out_pix_fmt,
|
"-pix_fmt", "yuv420p", // Output is 8-bit RGB, no alpha
|
||||||
"-b:v", fmt.Sprintf("%d", writer.bitrate), // bitrate
|
}
|
||||||
|
|
||||||
|
// Code from the imageio-ffmpeg project.
|
||||||
|
// https://github.com/imageio/imageio-ffmpeg/blob/master/imageio_ffmpeg/_io.py#L399
|
||||||
|
// If bitrate not given, use a default.
|
||||||
|
if writer.bitrate == 0 {
|
||||||
|
if writer.codec == "libx264" {
|
||||||
|
// Quality between 0 an 51. 51 is worst.
|
||||||
|
command = append(command, "-crf", fmt.Sprintf("%d", int(writer.quality*51)))
|
||||||
|
} else {
|
||||||
|
// Quality between 1 and 31. 31 is worst.
|
||||||
|
command = append(command, "-qscale:v", fmt.Sprintf("%d", int(writer.quality*30)+1))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
command = append(command, "-b:v", fmt.Sprintf("%d", writer.bitrate))
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(strings.ToLower(writer.filename), ".gif") {
|
if strings.HasSuffix(strings.ToLower(writer.filename), ".gif") {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue