rgb to rgba
This commit is contained in:
parent
2f07344cb8
commit
f6eaee9f1f
6 changed files with 51 additions and 59 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
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 and added to the system path.
|
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 and added to the system path.
|
||||||
|
|
||||||
All frames are encoded and decoded in 8-bit RGB format.
|
All frames are encoded and decoded in 8-bit RGBA format.
|
||||||
|
|
||||||
For Audio I/O using FFmpeg, see the [`aio`](https://github.com/AlexEidt/aio) project.
|
For Audio I/O using FFmpeg, see the [`aio`](https://github.com/AlexEidt/aio) project.
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ 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.
|
||||||
|
|
||||||
Calling the `Read()` function will fill in the `Video` struct `framebuffer` with the next frame data as 8-bit RGB data, stored in a flattened byte array in row-major order where each pixel is represented by three consecutive bytes representing the R, G and B component of that pixel.
|
Calling the `Read()` function will fill in the `Video` struct `framebuffer` with the next frame data as 8-bit RGBA data, stored in a flattened byte array in row-major order where each pixel is represented by four consecutive bytes representing the R, G, B and A components of that pixel. Note that the A (alpha) component will always be 255.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
vidio.NewVideo(filename string) (*vidio.Video, error)
|
vidio.NewVideo(filename string) (*vidio.Video, error)
|
||||||
|
@ -81,7 +81,6 @@ Macro() int
|
||||||
FPS() float64
|
FPS() float64
|
||||||
Quality() float64
|
Quality() float64
|
||||||
Codec() string
|
Codec() string
|
||||||
Format() string
|
|
||||||
|
|
||||||
Write(frame []byte) error
|
Write(frame []byte) error
|
||||||
Close()
|
Close()
|
||||||
|
@ -96,7 +95,6 @@ type Options struct {
|
||||||
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.
|
||||||
Format string // Pixel Format for video. Default "rgb24".
|
|
||||||
StreamFile string // File path for extra stream data.
|
StreamFile string // File path for extra stream data.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -90,7 +90,7 @@ func NewCamera(stream int) (*Camera, error) {
|
||||||
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
camera := &Camera{name: device, depth: 3}
|
camera := &Camera{name: device, depth: 4}
|
||||||
if err := camera.getCameraData(device); err != nil {
|
if err := camera.getCameraData(device); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ func (camera *Camera) init() error {
|
||||||
"-f", webcamDeviceName,
|
"-f", webcamDeviceName,
|
||||||
"-i", camera.name,
|
"-i", camera.name,
|
||||||
"-f", "image2pipe",
|
"-f", "image2pipe",
|
||||||
"-pix_fmt", "rgb24",
|
"-pix_fmt", "rgba",
|
||||||
"-vcodec", "rawvideo",
|
"-vcodec", "rawvideo",
|
||||||
"-",
|
"-",
|
||||||
)
|
)
|
||||||
|
|
13
imageio.go
13
imageio.go
|
@ -11,7 +11,7 @@ import (
|
||||||
"image/png"
|
"image/png"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reads an image from a file. Currently only supports png and jpeg.
|
// Reads an image into an rgba byte buffer from a file. Currently only supports png and jpeg.
|
||||||
func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
|
func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
|
||||||
f, err := os.Open(filename)
|
f, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -25,7 +25,7 @@ func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds := image.Bounds().Max
|
bounds := image.Bounds().Max
|
||||||
size := bounds.X * bounds.Y * 3
|
size := bounds.X * bounds.Y * 4
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
if len(buffer) > 0 {
|
if len(buffer) > 0 {
|
||||||
|
@ -45,13 +45,14 @@ func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
|
||||||
data[index+0] = byte(r)
|
data[index+0] = byte(r)
|
||||||
data[index+1] = byte(g)
|
data[index+1] = byte(g)
|
||||||
data[index+2] = byte(b)
|
data[index+2] = byte(b)
|
||||||
index += 3
|
data[index+3] = 255
|
||||||
|
index += 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bounds.X, bounds.Y, data, nil
|
return bounds.X, bounds.Y, data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes an image to a file. Currently only supports png and jpeg.
|
// Writes a rgba byte buffer to a file. Currently only supports png and jpeg.
|
||||||
func Write(filename string, width, height int, buffer []byte) error {
|
func Write(filename string, width, height int, buffer []byte) error {
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -63,9 +64,9 @@ func Write(filename string, width, height int, buffer []byte) error {
|
||||||
index := 0
|
index := 0
|
||||||
for h := 0; h < height; h++ {
|
for h := 0; h < height; h++ {
|
||||||
for w := 0; w < width; w++ {
|
for w := 0; w < width; w++ {
|
||||||
r, g, b := buffer[index], buffer[index+1], buffer[index+2]
|
r, g, b := buffer[index+0], buffer[index+1], buffer[index+2]
|
||||||
image.Set(w, h, color.RGBA{r, g, b, 255})
|
image.Set(w, h, color.RGBA{r, g, b, 255})
|
||||||
index += 3
|
index += 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
6
video.go
6
video.go
|
@ -143,7 +143,7 @@ func NewVideoStreams(filename string) ([]*Video, error) {
|
||||||
for i, data := range videoData {
|
for i, data := range videoData {
|
||||||
video := &Video{
|
video := &Video{
|
||||||
filename: filename,
|
filename: filename,
|
||||||
depth: 3,
|
depth: 4,
|
||||||
stream: i,
|
stream: i,
|
||||||
hasstreams: hasstream,
|
hasstreams: hasstream,
|
||||||
metadata: data,
|
metadata: data,
|
||||||
|
@ -190,13 +190,13 @@ func (video *Video) addVideoData(data map[string]string) {
|
||||||
func (video *Video) init() error {
|
func (video *Video) init() error {
|
||||||
// If user exits with Ctrl+C, stop ffmpeg process.
|
// If user exits with Ctrl+C, stop ffmpeg process.
|
||||||
video.cleanup()
|
video.cleanup()
|
||||||
// ffmpeg command to pipe video data to stdout in 8-bit RGB format.
|
// ffmpeg command to pipe video data to stdout in 8-bit RGBA format.
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"-i", video.filename,
|
"-i", video.filename,
|
||||||
"-f", "image2pipe",
|
"-f", "image2pipe",
|
||||||
"-loglevel", "quiet",
|
"-loglevel", "quiet",
|
||||||
"-pix_fmt", "rgb24",
|
"-pix_fmt", "rgba",
|
||||||
"-vcodec", "rawvideo",
|
"-vcodec", "rawvideo",
|
||||||
"-map", fmt.Sprintf("0:v:%d", video.stream),
|
"-map", fmt.Sprintf("0:v:%d", video.stream),
|
||||||
"-",
|
"-",
|
||||||
|
|
|
@ -23,7 +23,6 @@ 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.
|
||||||
format string // Output format. Default rgb24.
|
|
||||||
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.
|
||||||
}
|
}
|
||||||
|
@ -37,7 +36,6 @@ type Options struct {
|
||||||
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.
|
||||||
Format string // Pixel Format for video. Default "rgb24".
|
|
||||||
StreamFile string // File path for extra stream data.
|
StreamFile string // File path for extra stream data.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,10 +84,6 @@ func (writer *VideoWriter) Codec() string {
|
||||||
return writer.codec
|
return writer.codec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (writer *VideoWriter) Format() string {
|
|
||||||
return writer.format
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new VideoWriter struct with default values from the Options struct.
|
// Creates a new VideoWriter struct with default values from the Options struct.
|
||||||
func NewVideoWriter(filename string, width, height int, options *Options) (*VideoWriter, error) {
|
func NewVideoWriter(filename string, width, height int, options *Options) (*VideoWriter, error) {
|
||||||
// Check if ffmpeg is installed on the users machine.
|
// Check if ffmpeg is installed on the users machine.
|
||||||
|
@ -149,12 +143,6 @@ func NewVideoWriter(filename string, width, height int, options *Options) (*Vide
|
||||||
writer.codec = options.Codec
|
writer.codec = options.Codec
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Format == "" {
|
|
||||||
writer.format = "rgb24"
|
|
||||||
} else {
|
|
||||||
writer.format = options.Format
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.StreamFile != "" {
|
if options.StreamFile != "" {
|
||||||
if !exists(options.StreamFile) {
|
if !exists(options.StreamFile) {
|
||||||
return nil, fmt.Errorf("file %s does not exist", options.StreamFile)
|
return nil, fmt.Errorf("file %s does not exist", options.StreamFile)
|
||||||
|
@ -177,7 +165,7 @@ func (writer *VideoWriter) init() error {
|
||||||
"-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.format,
|
"-pix_fmt", "rgba",
|
||||||
"-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.
|
||||||
}
|
}
|
||||||
|
@ -206,7 +194,7 @@ func (writer *VideoWriter) init() error {
|
||||||
command = append(
|
command = append(
|
||||||
command,
|
command,
|
||||||
"-vcodec", writer.codec,
|
"-vcodec", writer.codec,
|
||||||
"-pix_fmt", "yuv420p", // Output is 8-bit RGB, no alpha.
|
"-pix_fmt", "yuv420p", // Output is 8-bit RGB, ignore alpha.
|
||||||
)
|
)
|
||||||
|
|
||||||
// Code from the imageio-ffmpeg project.
|
// Code from the imageio-ffmpeg project.
|
||||||
|
@ -246,6 +234,8 @@ func (writer *VideoWriter) init() error {
|
||||||
if writer.height%writer.macro > 0 {
|
if writer.height%writer.macro > 0 {
|
||||||
height += writer.macro - (writer.height % writer.macro)
|
height += writer.macro - (writer.height % writer.macro)
|
||||||
}
|
}
|
||||||
|
writer.width = width
|
||||||
|
writer.height = height
|
||||||
command = append(
|
command = append(
|
||||||
command,
|
command,
|
||||||
"-vf", fmt.Sprintf("scale=%d:%d", width, height),
|
"-vf", fmt.Sprintf("scale=%d:%d", width, height),
|
||||||
|
|
|
@ -39,7 +39,7 @@ func TestVideoMetaData(t *testing.T) {
|
||||||
assertEquals(video.filename, "test/koala.mp4")
|
assertEquals(video.filename, "test/koala.mp4")
|
||||||
assertEquals(video.width, 480)
|
assertEquals(video.width, 480)
|
||||||
assertEquals(video.height, 270)
|
assertEquals(video.height, 270)
|
||||||
assertEquals(video.depth, 3)
|
assertEquals(video.depth, 4)
|
||||||
assertEquals(video.bitrate, 170549)
|
assertEquals(video.bitrate, 170549)
|
||||||
assertEquals(video.frames, 101)
|
assertEquals(video.frames, 101)
|
||||||
assertEquals(video.duration, 3.366667)
|
assertEquals(video.duration, 3.366667)
|
||||||
|
@ -67,17 +67,23 @@ func TestVideoFrame(t *testing.T) {
|
||||||
defer video.Close()
|
defer video.Close()
|
||||||
|
|
||||||
video.Read()
|
video.Read()
|
||||||
// [203 222 134 203 222 134 203 222 134 203]
|
// [203 222 134 255 203 222 134 255 203 222 134 255 203]
|
||||||
assertEquals(video.framebuffer[0], uint8(203))
|
assertEquals(video.framebuffer[0], uint8(203))
|
||||||
assertEquals(video.framebuffer[1], uint8(222))
|
assertEquals(video.framebuffer[1], uint8(222))
|
||||||
assertEquals(video.framebuffer[2], uint8(134))
|
assertEquals(video.framebuffer[2], uint8(134))
|
||||||
assertEquals(video.framebuffer[3], uint8(203))
|
assertEquals(video.framebuffer[3], uint8(255))
|
||||||
assertEquals(video.framebuffer[4], uint8(222))
|
|
||||||
assertEquals(video.framebuffer[5], uint8(134))
|
assertEquals(video.framebuffer[4], uint8(203))
|
||||||
assertEquals(video.framebuffer[6], uint8(203))
|
assertEquals(video.framebuffer[5], uint8(222))
|
||||||
assertEquals(video.framebuffer[7], uint8(222))
|
assertEquals(video.framebuffer[6], uint8(134))
|
||||||
assertEquals(video.framebuffer[8], uint8(134))
|
assertEquals(video.framebuffer[7], uint8(255))
|
||||||
assertEquals(video.framebuffer[9], uint8(203))
|
|
||||||
|
assertEquals(video.framebuffer[8], uint8(203))
|
||||||
|
assertEquals(video.framebuffer[9], uint8(222))
|
||||||
|
assertEquals(video.framebuffer[10], uint8(134))
|
||||||
|
assertEquals(video.framebuffer[11], uint8(255))
|
||||||
|
|
||||||
|
assertEquals(video.framebuffer[12], uint8(203))
|
||||||
|
|
||||||
fmt.Println("Video Frame Test Passed")
|
fmt.Println("Video Frame Test Passed")
|
||||||
}
|
}
|
||||||
|
@ -90,8 +96,8 @@ func TestVideoWriting(t *testing.T) {
|
||||||
}
|
}
|
||||||
options := Options{
|
options := Options{
|
||||||
FPS: video.FPS(),
|
FPS: video.FPS(),
|
||||||
Bitrate: video.Bitrate(),
|
|
||||||
Codec: video.Codec(),
|
Codec: video.Codec(),
|
||||||
|
Bitrate: video.Bitrate(),
|
||||||
}
|
}
|
||||||
if video.HasStreams() {
|
if video.HasStreams() {
|
||||||
options.StreamFile = video.FileName()
|
options.StreamFile = video.FileName()
|
||||||
|
@ -101,12 +107,11 @@ func TestVideoWriting(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for video.Read() {
|
for video.Read() {
|
||||||
err := writer.Write(video.FrameBuffer())
|
writer.Write(video.FrameBuffer())
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Close()
|
writer.Close()
|
||||||
|
|
||||||
os.Remove(output)
|
os.Remove(output)
|
||||||
|
@ -134,14 +139,6 @@ func TestCameraIO(t *testing.T) {
|
||||||
count := 0
|
count := 0
|
||||||
for webcam.Read() {
|
for webcam.Read() {
|
||||||
frame := webcam.FrameBuffer()
|
frame := webcam.FrameBuffer()
|
||||||
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])
|
|
||||||
gray := uint8((3*r + 4*g + b) / 8)
|
|
||||||
frame[i] = gray
|
|
||||||
frame[i+1] = gray
|
|
||||||
frame[i+2] = gray
|
|
||||||
}
|
|
||||||
err := writer.Write(frame)
|
err := writer.Write(frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -250,18 +247,24 @@ func TestImageRead(t *testing.T) {
|
||||||
|
|
||||||
assertEquals(w, 200)
|
assertEquals(w, 200)
|
||||||
assertEquals(h, 133)
|
assertEquals(h, 133)
|
||||||
assertEquals(len(img), 200*133*3)
|
assertEquals(len(img), 200*133*4)
|
||||||
// [255 221 189 255 221 189 255 222 186 255]
|
// [255 221 189 255 255 221 189 255 255 222 186 255 255]
|
||||||
assertEquals(img[0], uint8(255))
|
assertEquals(img[0], uint8(255))
|
||||||
assertEquals(img[1], uint8(221))
|
assertEquals(img[1], uint8(221))
|
||||||
assertEquals(img[2], uint8(189))
|
assertEquals(img[2], uint8(189))
|
||||||
assertEquals(img[3], uint8(255))
|
assertEquals(img[3], uint8(255))
|
||||||
assertEquals(img[4], uint8(221))
|
|
||||||
assertEquals(img[5], uint8(189))
|
assertEquals(img[4], uint8(255))
|
||||||
assertEquals(img[6], uint8(255))
|
assertEquals(img[5], uint8(221))
|
||||||
assertEquals(img[7], uint8(222))
|
assertEquals(img[6], uint8(189))
|
||||||
assertEquals(img[8], uint8(186))
|
assertEquals(img[7], uint8(255))
|
||||||
assertEquals(img[9], uint8(255))
|
|
||||||
|
assertEquals(img[8], uint8(255))
|
||||||
|
assertEquals(img[9], uint8(222))
|
||||||
|
assertEquals(img[10], uint8(186))
|
||||||
|
assertEquals(img[11], uint8(255))
|
||||||
|
|
||||||
|
assertEquals(img[12], uint8(255))
|
||||||
|
|
||||||
fmt.Println("Image Reading Test Passed")
|
fmt.Println("Image Reading Test Passed")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue