Skip to content

Commit 3f4ae6e

Browse files
committed
Added error returns, removed panics
1 parent 5de0d20 commit 3f4ae6e

File tree

7 files changed

+232
-98
lines changed

7 files changed

+232
-98
lines changed

README.md

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ go get github.com/AlexEidt/Vidio
1515
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.
1616

1717
```go
18+
vidio.NewVideo() (*Video, error) // Create a new Video struct
19+
1820
FileName() string
1921
Width() int
2022
Height() int
@@ -26,33 +28,45 @@ FPS() float64
2628
Codec() string
2729
AudioCodec() string
2830
FrameBuffer() []byte
31+
32+
Read() bool // Read a frame of video and store it in the frame buffer
33+
Close()
2934
```
3035

3136
```go
32-
video := vidio.NewVideo("input.mp4")
37+
video, err := vidio.NewVideo("input.mp4")
38+
// Error handling...
3339
for video.Read() {
3440
// "frame" stores the video frame as a flattened RGB image in row-major order
3541
frame := video.FrameBuffer() // stored as: RGBRGBRGBRGB...
3642
// Video processing here...
3743
}
44+
// If all frames have been read, "video" will be closed automatically.
45+
// If not all frames are read, call "video.Close()" to close the video.
3846
```
3947

4048
## `Camera`
4149

4250
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.
4351

4452
```go
53+
vidio.NewCamera(stream int) (*Camera, error) // Create a new Camera struct
54+
4555
Name() string
4656
Width() int
4757
Height() int
4858
Depth() int
4959
FPS() float64
5060
Codec() string
5161
FrameBuffer() []byte
62+
63+
Read() bool // Read a frame of video and store it in the frame buffer
64+
Close()
5265
```
5366

5467
```go
55-
camera := vidio.NewCamera(0) // Get Webcam
68+
camera, err := vidio.NewCamera(0) // Get Webcam
69+
// Error handling...
5670
defer camera.Close()
5771

5872
// Stream the webcam
@@ -67,6 +81,8 @@ for camera.Read() {
6781
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.
6882

6983
```go
84+
vidio.NewVideoWriter() (*VideoWriter, error) // Create a new VideoWriter struct
85+
7086
FileName() string
7187
Width() int
7288
Height() int
@@ -78,6 +94,9 @@ FPS() float64
7894
Quality() float64
7995
Codec() string
8096
AudioCodec() string
97+
98+
Write(frame []byte) error // Write a frame to the video file
99+
Close()
81100
```
82101

83102
```go
@@ -89,7 +108,7 @@ type Options struct {
89108
FPS float64 // Frames per second. Default 25
90109
Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst
91110
Codec string // Codec for video. Default libx264
92-
Audio string // File path for audio for the video. If no audio, audio="".
111+
Audio string // File path for audio for the video. If no audio, audio=""
93112
AudioCodec string // Codec for audio. Default aac
94113
}
95114
```
@@ -98,11 +117,13 @@ type Options struct {
98117
w, h, c := 1920, 1080, 3
99118
options := vidio.Options{} // Will fill in defaults if empty
100119

101-
writer := vidio.NewVideoWriter("output.mp4", w, h, &options)
120+
writer, err := vidio.NewVideoWriter("output.mp4", w, h, &options)
121+
// Error handling...
102122
defer writer.Close()
103123

104124
frame := make([]byte, w*h*c) // Create Frame as RGB Image and modify
105-
writer.Write(frame) // Write Frame to video
125+
err := writer.Write(frame) // Write Frame to video
126+
// Error handling...
106127
```
107128

108129
## Images
@@ -111,44 +132,51 @@ Vidio provides some convenience functions for reading and writing to images usin
111132

112133
```go
113134
// Read png image
114-
w, h, img := vidio.Read("input.png")
135+
w, h, img, err := vidio.Read("input.png")
136+
// Error handling...
115137

116138
// w - width of image
117139
// h - height of image
118140
// img - byte array in RGB format. RGBRGBRGBRGB...
119141

120-
vidio.Write("output.jpg", w, h, img)
142+
err := vidio.Write("output.jpg", w, h, img)
143+
// Error handling...
121144
```
122145

123146
## Examples
124147

125148
Copy `input.mp4` to `output.mp4`. Copy the audio from `input.mp4` to `output.mp4` as well.
126149

127150
```go
128-
video := vidio.NewVideo("input.mp4")
151+
video, err := vidio.NewVideo("input.mp4")
152+
// Error handling...
129153
options := vidio.Options{
130154
FPS: video.FPS(),
131155
Bitrate: video.Bitrate(),
132156
Audio: "input.mp4",
133157
}
134158

135-
writer := vidio.NewVideoWriter("output.mp4", video.Width(), video.Height(), &options)
159+
writer, err := vidio.NewVideoWriter("output.mp4", video.Width(), video.Height(), &options)
160+
// Error handling...
136161
defer writer.Close()
137162

138163
for video.Read() {
139-
writer.Write(video.FrameBuffer())
164+
err := writer.Write(video.FrameBuffer())
165+
// Error handling...
140166
}
141167
```
142168

143169
Grayscale 1000 frames of webcam stream and store in `output.mp4`.
144170

145171
```go
146-
webcam := vidio.NewCamera(0)
172+
webcam, err := vidio.NewCamera(0)
173+
// Error handling...
147174
defer webcam.Close()
148175

149176
options := vidio.Options{FPS: webcam.FPS()}
150177

151-
writer := vidio.NewVideoWriter("output.mp4", webcam.Width(), webcam.Height(), &options)
178+
writer, err := vidio.NewVideoWriter("output.mp4", webcam.Width(), webcam.Height(), &options)
179+
// Error handling...
152180
defer writer.Close()
153181

154182
count := 0
@@ -162,7 +190,8 @@ for webcam.Read() {
162190
frame[i+1] = gray
163191
frame[i+2] = gray
164192
}
165-
writer.Write(frame)
193+
err := writer.Write(frame)
194+
// Error handling...
166195
count++
167196
if count > 1000 {
168197
break
@@ -173,16 +202,20 @@ for webcam.Read() {
173202
Create a gif from a series of `png` files enumerated from 1 to 10 that loops continuously with a final frame delay of 1000 centiseconds.
174203

175204
```go
176-
w, h, _ := vidio.Read("1.png") // Get frame dimensions from first image
205+
w, h, _, err := vidio.Read("1.png") // Get frame dimensions from first image
206+
// Error handling...
177207

178208
options := vidio.Options{FPS: 1, Loop: 0, Delay: 1000}
179209

180-
gif := vidio.NewVideoWriter("output.gif", w, h, &options)
210+
gif, err := vidio.NewVideoWriter("output.gif", w, h, &options)
211+
// Error handling...
181212
defer gif.Close()
182213

183214
for i := 1; i <= 10; i++ {
184-
_, _, img := vidio.Read(strconv.Itoa(i)+".png")
185-
gif.Write(img)
215+
_, _, img, err := vidio.Read(strconv.Itoa(i)+".png")
216+
// Error handling...
217+
err := gif.Write(img)
218+
// Error handling...
186219
}
187220
```
188221

camera.go

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package vidio
22

33
import (
4+
"errors"
45
"io"
56
"os"
67
"os/exec"
@@ -52,7 +53,7 @@ func (camera *Camera) FrameBuffer() []byte {
5253

5354
// Returns the webcam device name.
5455
// On windows, ffmpeg output from the -list_devices command is parsed to find the device name.
55-
func getDevicesWindows() []string {
56+
func getDevicesWindows() ([]string, error) {
5657
// Run command to get list of devices.
5758
cmd := exec.Command(
5859
"ffmpeg",
@@ -63,10 +64,12 @@ func getDevicesWindows() []string {
6364
)
6465
pipe, err := cmd.StderrPipe()
6566
if err != nil {
66-
panic(err)
67+
pipe.Close()
68+
return nil, err
6769
}
6870
if err := cmd.Start(); err != nil {
69-
panic(err)
71+
cmd.Process.Kill()
72+
return nil, err
7073
}
7174
// Read list devices from Stdout.
7275
buffer := make([]byte, 2<<10)
@@ -80,28 +83,34 @@ func getDevicesWindows() []string {
8083
}
8184
cmd.Wait()
8285
devices := parseDevices(buffer)
83-
return devices
86+
return devices, nil
8487
}
8588

8689
// Get camera meta data such as width, height, fps and codec.
87-
func getCameraData(device string, camera *Camera) {
90+
func getCameraData(device string, camera *Camera) error {
8891
// Run command to get camera data.
8992
// Webcam will turn on and then off in quick succession.
93+
webcamDeviceName, err := webcam()
94+
if err != nil {
95+
return err
96+
}
9097
cmd := exec.Command(
9198
"ffmpeg",
9299
"-hide_banner",
93-
"-f", webcam(),
100+
"-f", webcamDeviceName,
94101
"-i", device,
95102
)
96103
// The command will fail since we do not give a file to write to, therefore
97104
// it will write the meta data to Stderr.
98105
pipe, err := cmd.StderrPipe()
99106
if err != nil {
100-
panic(err)
107+
pipe.Close()
108+
return err
101109
}
102110
// Start the command.
103111
if err := cmd.Start(); err != nil {
104-
panic(err)
112+
cmd.Process.Kill()
113+
return err
105114
}
106115
// Read ffmpeg output from Stdout.
107116
buffer := make([]byte, 2<<11)
@@ -117,51 +126,61 @@ func getCameraData(device string, camera *Camera) {
117126
cmd.Wait()
118127

119128
parseWebcamData(buffer[:total], camera)
129+
return nil
120130
}
121131

122132
// Creates a new camera struct that can read from the device with the given stream index.
123-
func NewCamera(stream int) *Camera {
133+
func NewCamera(stream int) (*Camera, error) {
124134
// Check if ffmpeg is installed on the users machine.
125-
checkExists("ffmpeg")
135+
if err := checkExists("ffmpeg"); err != nil {
136+
return nil, err
137+
}
126138

127139
var device string
128140
switch runtime.GOOS {
129141
case "linux":
130142
device = "/dev/video" + strconv.Itoa(stream)
131-
break
132143
case "darwin":
133144
device = strconv.Itoa(stream)
134-
break
135145
case "windows":
136146
// If OS is windows, we need to parse the listed devices to find which corresponds to the
137147
// given "stream" index.
138-
devices := getDevicesWindows()
148+
devices, err := getDevicesWindows()
149+
if err != nil {
150+
return nil, err
151+
}
139152
if stream >= len(devices) {
140-
panic("Could not find devices with index: " + strconv.Itoa(stream))
153+
return nil, errors.New("Could not find device with index: " + strconv.Itoa(stream))
141154
}
142155
device = "video=" + devices[stream]
143-
break
144156
default:
145-
panic("Unsupported OS: " + runtime.GOOS)
157+
return nil, errors.New("Unsupported OS: " + runtime.GOOS)
146158
}
147159

148160
camera := Camera{name: device, depth: 3}
149-
getCameraData(device, &camera)
150-
return &camera
161+
if err := getCameraData(device, &camera); err != nil {
162+
return nil, err
163+
}
164+
return &camera, nil
151165
}
152166

153167
// Once the user calls Read() for the first time on a Camera struct,
154168
// the ffmpeg command which is used to read the camera device is started.
155-
func initCamera(camera *Camera) {
169+
func initCamera(camera *Camera) error {
156170
// If user exits with Ctrl+C, stop ffmpeg process.
157171
camera.cleanup()
158172

173+
webcamDeviceName, err := webcam()
174+
if err != nil {
175+
return err
176+
}
177+
159178
// Use ffmpeg to pipe webcam to stdout.
160179
cmd := exec.Command(
161180
"ffmpeg",
162181
"-hide_banner",
163182
"-loglevel", "quiet",
164-
"-f", webcam(),
183+
"-f", webcamDeviceName,
165184
"-i", camera.name,
166185
"-f", "image2pipe",
167186
"-pix_fmt", "rgb24",
@@ -171,21 +190,27 @@ func initCamera(camera *Camera) {
171190
camera.cmd = cmd
172191
pipe, err := cmd.StdoutPipe()
173192
if err != nil {
174-
panic(err)
193+
pipe.Close()
194+
return err
175195
}
176196

177197
camera.pipe = &pipe
178198
if err := cmd.Start(); err != nil {
179-
panic(err)
199+
cmd.Process.Kill()
200+
return err
180201
}
202+
181203
camera.framebuffer = make([]byte, camera.width*camera.height*camera.depth)
204+
return nil
182205
}
183206

184207
// Reads the next frame from the webcam and stores in the framebuffer.
185208
func (camera *Camera) Read() bool {
186209
// If cmd is nil, video reading has not been initialized.
187210
if camera.cmd == nil {
188-
initCamera(camera)
211+
if err := initCamera(camera); err != nil {
212+
return false
213+
}
189214
}
190215
total := 0
191216
for total < camera.width*camera.height*camera.depth {

0 commit comments

Comments
 (0)