Skip to content

Commit 54cfa77

Browse files
committed
Refactoring and Cleanup
1 parent af983f0 commit 54cfa77

File tree

5 files changed

+144
-142
lines changed

5 files changed

+144
-142
lines changed

camera.go

Lines changed: 78 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"os"
77
"os/exec"
88
"os/signal"
9+
"regexp"
910
"runtime"
1011
"strconv"
12+
"strings"
1113
"syscall"
1214
)
1315

@@ -61,41 +63,86 @@ func (camera *Camera) SetFrameBuffer(buffer []byte) error {
6163
return nil
6264
}
6365

64-
// Returns the webcam device name.
65-
// On windows, ffmpeg output from the -list_devices command is parsed to find the device name.
66-
func getDevicesWindows() ([]string, error) {
67-
// Run command to get list of devices.
68-
cmd := exec.Command(
69-
"ffmpeg",
70-
"-hide_banner",
71-
"-list_devices", "true",
72-
"-f", "dshow",
73-
"-i", "dummy",
74-
)
75-
pipe, err := cmd.StderrPipe()
76-
if err != nil {
66+
// Creates a new camera struct that can read from the device with the given stream index.
67+
func NewCamera(stream int) (*Camera, error) {
68+
// Check if ffmpeg is installed on the users machine.
69+
if err := checkExists("ffmpeg"); err != nil {
7770
return nil, err
7871
}
79-
if err := cmd.Start(); err != nil {
72+
73+
var device string
74+
switch runtime.GOOS {
75+
case "linux":
76+
device = "/dev/video" + strconv.Itoa(stream)
77+
case "darwin":
78+
device = strconv.Itoa(stream)
79+
case "windows":
80+
// If OS is windows, we need to parse the listed devices to find which corresponds to the
81+
// given "stream" index.
82+
devices, err := getDevicesWindows()
83+
if err != nil {
84+
return nil, err
85+
}
86+
if stream >= len(devices) {
87+
return nil, fmt.Errorf("could not find device with index: %d", stream)
88+
}
89+
device = "video=" + devices[stream]
90+
default:
91+
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
92+
}
93+
94+
camera := &Camera{name: device, depth: 3}
95+
if err := camera.getCameraData(device); err != nil {
8096
return nil, err
8197
}
82-
// Read list devices from Stdout.
83-
buffer := make([]byte, 2<<10)
84-
total := 0
85-
for {
86-
n, err := pipe.Read(buffer[total:])
87-
total += n
88-
if err == io.EOF {
89-
break
98+
return camera, nil
99+
}
100+
101+
// Parses the webcam metadata (width, height, fps, codec) from ffmpeg output.
102+
func (camera *Camera) parseWebcamData(buffer []byte) {
103+
bufferstr := string(buffer)
104+
index := strings.Index(bufferstr, "Stream #")
105+
if index == -1 {
106+
index++
107+
}
108+
bufferstr = bufferstr[index:]
109+
// Dimensions. widthxheight.
110+
regex := regexp.MustCompile(`\d{2,}x\d{2,}`)
111+
match := regex.FindString(bufferstr)
112+
if len(match) > 0 {
113+
split := strings.Split(match, "x")
114+
camera.width = int(parse(split[0]))
115+
camera.height = int(parse(split[1]))
116+
}
117+
// FPS.
118+
regex = regexp.MustCompile(`\d+(.\d+)? fps`)
119+
match = regex.FindString(bufferstr)
120+
if len(match) > 0 {
121+
index = strings.Index(match, " fps")
122+
if index != -1 {
123+
match = match[:index]
90124
}
125+
camera.fps = parse(match)
126+
}
127+
// Codec.
128+
regex = regexp.MustCompile("Video: .+,")
129+
match = regex.FindString(bufferstr)
130+
if len(match) > 0 {
131+
match = match[len("Video: "):]
132+
index = strings.Index(match, "(")
133+
if index != -1 {
134+
match = match[:index]
135+
}
136+
index = strings.Index(match, ",")
137+
if index != -1 {
138+
match = match[:index]
139+
}
140+
camera.codec = strings.TrimSpace(match)
91141
}
92-
cmd.Wait()
93-
devices := parseDevices(buffer)
94-
return devices, nil
95142
}
96143

97144
// Get camera meta data such as width, height, fps and codec.
98-
func getCameraData(device string, camera *Camera) error {
145+
func (camera *Camera) getCameraData(device string) error {
99146
// Run command to get camera data.
100147
// Webcam will turn on and then off in quick succession.
101148
webcamDeviceName, err := webcam()
@@ -131,48 +178,13 @@ func getCameraData(device string, camera *Camera) error {
131178
// Wait for the command to finish.
132179
cmd.Wait()
133180

134-
parseWebcamData(buffer[:total], camera)
181+
camera.parseWebcamData(buffer[:total])
135182
return nil
136183
}
137184

138-
// Creates a new camera struct that can read from the device with the given stream index.
139-
func NewCamera(stream int) (*Camera, error) {
140-
// Check if ffmpeg is installed on the users machine.
141-
if err := checkExists("ffmpeg"); err != nil {
142-
return nil, err
143-
}
144-
145-
var device string
146-
switch runtime.GOOS {
147-
case "linux":
148-
device = "/dev/video" + strconv.Itoa(stream)
149-
case "darwin":
150-
device = strconv.Itoa(stream)
151-
case "windows":
152-
// If OS is windows, we need to parse the listed devices to find which corresponds to the
153-
// given "stream" index.
154-
devices, err := getDevicesWindows()
155-
if err != nil {
156-
return nil, err
157-
}
158-
if stream >= len(devices) {
159-
return nil, fmt.Errorf("could not find device with index: %d", stream)
160-
}
161-
device = "video=" + devices[stream]
162-
default:
163-
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
164-
}
165-
166-
camera := Camera{name: device, depth: 3}
167-
if err := getCameraData(device, &camera); err != nil {
168-
return nil, err
169-
}
170-
return &camera, nil
171-
}
172-
173185
// Once the user calls Read() for the first time on a Camera struct,
174186
// the ffmpeg command which is used to read the camera device is started.
175-
func initCamera(camera *Camera) error {
187+
func (camera *Camera) init() error {
176188
// If user exits with Ctrl+C, stop ffmpeg process.
177189
camera.cleanup()
178190

@@ -190,7 +202,8 @@ func initCamera(camera *Camera) error {
190202
"-i", camera.name,
191203
"-f", "image2pipe",
192204
"-pix_fmt", "rgb24",
193-
"-vcodec", "rawvideo", "-",
205+
"-vcodec", "rawvideo",
206+
"-",
194207
)
195208

196209
camera.cmd = cmd
@@ -215,7 +228,7 @@ func initCamera(camera *Camera) error {
215228
func (camera *Camera) Read() bool {
216229
// If cmd is nil, video reading has not been initialized.
217230
if camera.cmd == nil {
218-
if err := initCamera(camera); err != nil {
231+
if err := camera.init(); err != nil {
219232
return false
220233
}
221234
}

utils.go

Lines changed: 29 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -91,36 +91,6 @@ func parseFFprobe(input []byte) map[string]string {
9191
return data
9292
}
9393

94-
// Adds Video data to the video struct from the ffprobe output.
95-
func addVideoData(data map[string]string, video *Video) {
96-
if width, ok := data["width"]; ok {
97-
video.width = int(parse(width))
98-
}
99-
if height, ok := data["height"]; ok {
100-
video.height = int(parse(height))
101-
}
102-
if duration, ok := data["duration"]; ok {
103-
video.duration = float64(parse(duration))
104-
}
105-
if frames, ok := data["nb_frames"]; ok {
106-
video.frames = int(parse(frames))
107-
}
108-
109-
if fps, ok := data["r_frame_rate"]; ok {
110-
split := strings.Split(fps, "/")
111-
if len(split) == 2 && split[0] != "" && split[1] != "" {
112-
video.fps = parse(split[0]) / parse(split[1])
113-
}
114-
}
115-
116-
if bitrate, ok := data["bit_rate"]; ok {
117-
video.bitrate = int(parse(bitrate))
118-
}
119-
if codec, ok := data["codec_name"]; ok {
120-
video.codec = codec
121-
}
122-
}
123-
12494
// Parses the given data into a float64.
12595
func parse(data string) float64 {
12696
n, err := strconv.ParseFloat(data, 64)
@@ -195,7 +165,6 @@ func parseDevices(buffer []byte) []string {
195165
return devices
196166
}
197167

198-
// Helper function. Array contains function.
199168
func contains(list []string, item string) bool {
200169
for _, i := range list {
201170
if i == item {
@@ -205,45 +174,35 @@ func contains(list []string, item string) bool {
205174
return false
206175
}
207176

208-
// Parses the webcam metadata (width, height, fps, codec) from ffmpeg output.
209-
func parseWebcamData(buffer []byte, camera *Camera) {
210-
bufferstr := string(buffer)
211-
index := strings.Index(bufferstr, "Stream #")
212-
if index == -1 {
213-
index++
214-
}
215-
bufferstr = bufferstr[index:]
216-
// Dimensions. widthxheight.
217-
regex := regexp.MustCompile(`\d{2,}x\d{2,}`)
218-
match := regex.FindString(bufferstr)
219-
if len(match) > 0 {
220-
split := strings.Split(match, "x")
221-
camera.width = int(parse(split[0]))
222-
camera.height = int(parse(split[1]))
223-
}
224-
// FPS.
225-
regex = regexp.MustCompile(`\d+(.\d+)? fps`)
226-
match = regex.FindString(bufferstr)
227-
if len(match) > 0 {
228-
index = strings.Index(match, " fps")
229-
if index != -1 {
230-
match = match[:index]
231-
}
232-
camera.fps = parse(match)
233-
}
234-
// Codec.
235-
regex = regexp.MustCompile("Video: .+,")
236-
match = regex.FindString(bufferstr)
237-
if len(match) > 0 {
238-
match = match[len("Video: "):]
239-
index = strings.Index(match, "(")
240-
if index != -1 {
241-
match = match[:index]
242-
}
243-
index = strings.Index(match, ",")
244-
if index != -1 {
245-
match = match[:index]
177+
// Returns the webcam device name.
178+
// On windows, ffmpeg output from the -list_devices command is parsed to find the device name.
179+
func getDevicesWindows() ([]string, error) {
180+
// Run command to get list of devices.
181+
cmd := exec.Command(
182+
"ffmpeg",
183+
"-hide_banner",
184+
"-list_devices", "true",
185+
"-f", "dshow",
186+
"-i", "dummy",
187+
)
188+
pipe, err := cmd.StderrPipe()
189+
if err != nil {
190+
return nil, err
191+
}
192+
if err := cmd.Start(); err != nil {
193+
return nil, err
194+
}
195+
// Read list devices from Stdout.
196+
buffer := make([]byte, 2<<10)
197+
total := 0
198+
for {
199+
n, err := pipe.Read(buffer[total:])
200+
total += n
201+
if err == io.EOF {
202+
break
246203
}
247-
camera.codec = strings.TrimSpace(match)
248204
}
205+
cmd.Wait()
206+
devices := parseDevices(buffer)
207+
return devices, nil
249208
}

video.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"os/exec"
88
"os/signal"
9+
"strings"
910
"syscall"
1011
)
1112

@@ -107,17 +108,47 @@ func NewVideo(filename string) (*Video, error) {
107108

108109
video := &Video{filename: filename, depth: 3}
109110

110-
addVideoData(videoData, video)
111+
video.addVideoData(videoData)
111112
if audioCodec, ok := audioData["codec_name"]; ok {
112113
video.audioCodec = audioCodec
113114
}
114115

115116
return video, nil
116117
}
117118

119+
// Adds Video data to the video struct from the ffprobe output.
120+
func (video *Video) addVideoData(data map[string]string) {
121+
if width, ok := data["width"]; ok {
122+
video.width = int(parse(width))
123+
}
124+
if height, ok := data["height"]; ok {
125+
video.height = int(parse(height))
126+
}
127+
if duration, ok := data["duration"]; ok {
128+
video.duration = float64(parse(duration))
129+
}
130+
if frames, ok := data["nb_frames"]; ok {
131+
video.frames = int(parse(frames))
132+
}
133+
134+
if fps, ok := data["r_frame_rate"]; ok {
135+
split := strings.Split(fps, "/")
136+
if len(split) == 2 && split[0] != "" && split[1] != "" {
137+
video.fps = parse(split[0]) / parse(split[1])
138+
}
139+
}
140+
141+
if bitrate, ok := data["bit_rate"]; ok {
142+
video.bitrate = int(parse(bitrate))
143+
}
144+
if codec, ok := data["codec_name"]; ok {
145+
video.codec = codec
146+
}
147+
}
148+
118149
// Once the user calls Read() for the first time on a Video struct,
119150
// the ffmpeg command which is used to read the video is started.
120-
func initVideo(video *Video) error {
151+
func (video *Video) init() error {
121152
// If user exits with Ctrl+C, stop ffmpeg process.
122153
video.cleanup()
123154
// ffmpeg command to pipe video data to stdout in 8-bit RGB format.
@@ -153,7 +184,7 @@ func initVideo(video *Video) error {
153184
func (video *Video) Read() bool {
154185
// If cmd is nil, video reading has not been initialized.
155186
if video.cmd == nil {
156-
if err := initVideo(video); err != nil {
187+
if err := video.init(); err != nil {
157188
return false
158189
}
159190
}

videowriter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ func NewVideoWriter(filename string, width, height int, options *Options) (*Vide
185185

186186
// Once the user calls Write() for the first time on a VideoWriter struct,
187187
// the ffmpeg command which is used to write to the video file is started.
188-
func initVideoWriter(writer *VideoWriter) error {
188+
func (writer *VideoWriter) init() error {
189189
// If user exits with Ctrl+C, stop ffmpeg process.
190190
writer.cleanup()
191191
// ffmpeg command to write to video file. Takes in bytes from Stdin and encodes them.
@@ -291,7 +291,7 @@ func initVideoWriter(writer *VideoWriter) error {
291291
func (writer *VideoWriter) Write(frame []byte) error {
292292
// If cmd is nil, video writing has not been set up.
293293
if writer.cmd == nil {
294-
if err := initVideoWriter(writer); err != nil {
294+
if err := writer.init(); err != nil {
295295
return err
296296
}
297297
}

0 commit comments

Comments
 (0)