Refactoring and Cleanup
This commit is contained in:
parent
af983f09e4
commit
54cfa7783a
5 changed files with 144 additions and 142 deletions
141
camera.go
141
camera.go
|
@ -6,8 +6,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -61,41 +63,86 @@ func (camera *Camera) SetFrameBuffer(buffer []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the webcam device name.
|
// Creates a new camera struct that can read from the device with the given stream index.
|
||||||
// On windows, ffmpeg output from the -list_devices command is parsed to find the device name.
|
func NewCamera(stream int) (*Camera, error) {
|
||||||
func getDevicesWindows() ([]string, error) {
|
// Check if ffmpeg is installed on the users machine.
|
||||||
// Run command to get list of devices.
|
if err := checkExists("ffmpeg"); err != nil {
|
||||||
cmd := exec.Command(
|
return nil, err
|
||||||
"ffmpeg",
|
}
|
||||||
"-hide_banner",
|
|
||||||
"-list_devices", "true",
|
var device string
|
||||||
"-f", "dshow",
|
switch runtime.GOOS {
|
||||||
"-i", "dummy",
|
case "linux":
|
||||||
)
|
device = "/dev/video" + strconv.Itoa(stream)
|
||||||
pipe, err := cmd.StderrPipe()
|
case "darwin":
|
||||||
|
device = strconv.Itoa(stream)
|
||||||
|
case "windows":
|
||||||
|
// If OS is windows, we need to parse the listed devices to find which corresponds to the
|
||||||
|
// given "stream" index.
|
||||||
|
devices, err := getDevicesWindows()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := cmd.Start(); err != nil {
|
if stream >= len(devices) {
|
||||||
|
return nil, fmt.Errorf("could not find device with index: %d", stream)
|
||||||
|
}
|
||||||
|
device = "video=" + devices[stream]
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
camera := &Camera{name: device, depth: 3}
|
||||||
|
if err := camera.getCameraData(device); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Read list devices from Stdout.
|
return camera, nil
|
||||||
buffer := make([]byte, 2<<10)
|
|
||||||
total := 0
|
|
||||||
for {
|
|
||||||
n, err := pipe.Read(buffer[total:])
|
|
||||||
total += n
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parses the webcam metadata (width, height, fps, codec) from ffmpeg output.
|
||||||
|
func (camera *Camera) parseWebcamData(buffer []byte) {
|
||||||
|
bufferstr := string(buffer)
|
||||||
|
index := strings.Index(bufferstr, "Stream #")
|
||||||
|
if index == -1 {
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
bufferstr = bufferstr[index:]
|
||||||
|
// Dimensions. widthxheight.
|
||||||
|
regex := regexp.MustCompile(`\d{2,}x\d{2,}`)
|
||||||
|
match := regex.FindString(bufferstr)
|
||||||
|
if len(match) > 0 {
|
||||||
|
split := strings.Split(match, "x")
|
||||||
|
camera.width = int(parse(split[0]))
|
||||||
|
camera.height = int(parse(split[1]))
|
||||||
|
}
|
||||||
|
// FPS.
|
||||||
|
regex = regexp.MustCompile(`\d+(.\d+)? fps`)
|
||||||
|
match = regex.FindString(bufferstr)
|
||||||
|
if len(match) > 0 {
|
||||||
|
index = strings.Index(match, " fps")
|
||||||
|
if index != -1 {
|
||||||
|
match = match[:index]
|
||||||
|
}
|
||||||
|
camera.fps = parse(match)
|
||||||
|
}
|
||||||
|
// Codec.
|
||||||
|
regex = regexp.MustCompile("Video: .+,")
|
||||||
|
match = regex.FindString(bufferstr)
|
||||||
|
if len(match) > 0 {
|
||||||
|
match = match[len("Video: "):]
|
||||||
|
index = strings.Index(match, "(")
|
||||||
|
if index != -1 {
|
||||||
|
match = match[:index]
|
||||||
|
}
|
||||||
|
index = strings.Index(match, ",")
|
||||||
|
if index != -1 {
|
||||||
|
match = match[:index]
|
||||||
|
}
|
||||||
|
camera.codec = strings.TrimSpace(match)
|
||||||
}
|
}
|
||||||
cmd.Wait()
|
|
||||||
devices := parseDevices(buffer)
|
|
||||||
return devices, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get camera meta data such as width, height, fps and codec.
|
// Get camera meta data such as width, height, fps and codec.
|
||||||
func getCameraData(device string, camera *Camera) error {
|
func (camera *Camera) getCameraData(device string) error {
|
||||||
// Run command to get camera data.
|
// Run command to get camera data.
|
||||||
// Webcam will turn on and then off in quick succession.
|
// Webcam will turn on and then off in quick succession.
|
||||||
webcamDeviceName, err := webcam()
|
webcamDeviceName, err := webcam()
|
||||||
|
@ -131,48 +178,13 @@ func getCameraData(device string, camera *Camera) error {
|
||||||
// Wait for the command to finish.
|
// Wait for the command to finish.
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
|
|
||||||
parseWebcamData(buffer[:total], camera)
|
camera.parseWebcamData(buffer[:total])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new camera struct that can read from the device with the given stream index.
|
|
||||||
func NewCamera(stream int) (*Camera, error) {
|
|
||||||
// Check if ffmpeg is installed on the users machine.
|
|
||||||
if err := checkExists("ffmpeg"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var device string
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux":
|
|
||||||
device = "/dev/video" + strconv.Itoa(stream)
|
|
||||||
case "darwin":
|
|
||||||
device = strconv.Itoa(stream)
|
|
||||||
case "windows":
|
|
||||||
// If OS is windows, we need to parse the listed devices to find which corresponds to the
|
|
||||||
// given "stream" index.
|
|
||||||
devices, err := getDevicesWindows()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if stream >= len(devices) {
|
|
||||||
return nil, fmt.Errorf("could not find device with index: %d", stream)
|
|
||||||
}
|
|
||||||
device = "video=" + devices[stream]
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
camera := Camera{name: device, depth: 3}
|
|
||||||
if err := getCameraData(device, &camera); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &camera, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once the user calls Read() for the first time on a Camera struct,
|
// Once the user calls Read() for the first time on a Camera struct,
|
||||||
// the ffmpeg command which is used to read the camera device is started.
|
// the ffmpeg command which is used to read the camera device is started.
|
||||||
func initCamera(camera *Camera) error {
|
func (camera *Camera) init() error {
|
||||||
// If user exits with Ctrl+C, stop ffmpeg process.
|
// If user exits with Ctrl+C, stop ffmpeg process.
|
||||||
camera.cleanup()
|
camera.cleanup()
|
||||||
|
|
||||||
|
@ -190,7 +202,8 @@ func initCamera(camera *Camera) error {
|
||||||
"-i", camera.name,
|
"-i", camera.name,
|
||||||
"-f", "image2pipe",
|
"-f", "image2pipe",
|
||||||
"-pix_fmt", "rgb24",
|
"-pix_fmt", "rgb24",
|
||||||
"-vcodec", "rawvideo", "-",
|
"-vcodec", "rawvideo",
|
||||||
|
"-",
|
||||||
)
|
)
|
||||||
|
|
||||||
camera.cmd = cmd
|
camera.cmd = cmd
|
||||||
|
@ -215,7 +228,7 @@ func initCamera(camera *Camera) error {
|
||||||
func (camera *Camera) Read() 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 {
|
||||||
if err := initCamera(camera); err != nil {
|
if err := camera.init(); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
97
utils.go
97
utils.go
|
@ -91,36 +91,6 @@ func parseFFprobe(input []byte) map[string]string {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds Video data to the video struct from the ffprobe output.
|
|
||||||
func addVideoData(data map[string]string, video *Video) {
|
|
||||||
if width, ok := data["width"]; ok {
|
|
||||||
video.width = int(parse(width))
|
|
||||||
}
|
|
||||||
if height, ok := data["height"]; ok {
|
|
||||||
video.height = int(parse(height))
|
|
||||||
}
|
|
||||||
if duration, ok := data["duration"]; ok {
|
|
||||||
video.duration = float64(parse(duration))
|
|
||||||
}
|
|
||||||
if frames, ok := data["nb_frames"]; ok {
|
|
||||||
video.frames = int(parse(frames))
|
|
||||||
}
|
|
||||||
|
|
||||||
if fps, ok := data["r_frame_rate"]; ok {
|
|
||||||
split := strings.Split(fps, "/")
|
|
||||||
if len(split) == 2 && split[0] != "" && split[1] != "" {
|
|
||||||
video.fps = parse(split[0]) / parse(split[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bitrate, ok := data["bit_rate"]; ok {
|
|
||||||
video.bitrate = int(parse(bitrate))
|
|
||||||
}
|
|
||||||
if codec, ok := data["codec_name"]; ok {
|
|
||||||
video.codec = codec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses the given data into a float64.
|
// Parses the given data into a float64.
|
||||||
func parse(data string) float64 {
|
func parse(data string) float64 {
|
||||||
n, err := strconv.ParseFloat(data, 64)
|
n, err := strconv.ParseFloat(data, 64)
|
||||||
|
@ -195,7 +165,6 @@ func parseDevices(buffer []byte) []string {
|
||||||
return devices
|
return devices
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function. Array contains function.
|
|
||||||
func contains(list []string, item string) bool {
|
func contains(list []string, item string) bool {
|
||||||
for _, i := range list {
|
for _, i := range list {
|
||||||
if i == item {
|
if i == item {
|
||||||
|
@ -205,45 +174,35 @@ func contains(list []string, item string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses the webcam metadata (width, height, fps, codec) from ffmpeg output.
|
// Returns the webcam device name.
|
||||||
func parseWebcamData(buffer []byte, camera *Camera) {
|
// On windows, ffmpeg output from the -list_devices command is parsed to find the device name.
|
||||||
bufferstr := string(buffer)
|
func getDevicesWindows() ([]string, error) {
|
||||||
index := strings.Index(bufferstr, "Stream #")
|
// Run command to get list of devices.
|
||||||
if index == -1 {
|
cmd := exec.Command(
|
||||||
index++
|
"ffmpeg",
|
||||||
|
"-hide_banner",
|
||||||
|
"-list_devices", "true",
|
||||||
|
"-f", "dshow",
|
||||||
|
"-i", "dummy",
|
||||||
|
)
|
||||||
|
pipe, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
bufferstr = bufferstr[index:]
|
if err := cmd.Start(); err != nil {
|
||||||
// Dimensions. widthxheight.
|
return nil, err
|
||||||
regex := regexp.MustCompile(`\d{2,}x\d{2,}`)
|
|
||||||
match := regex.FindString(bufferstr)
|
|
||||||
if len(match) > 0 {
|
|
||||||
split := strings.Split(match, "x")
|
|
||||||
camera.width = int(parse(split[0]))
|
|
||||||
camera.height = int(parse(split[1]))
|
|
||||||
}
|
}
|
||||||
// FPS.
|
// Read list devices from Stdout.
|
||||||
regex = regexp.MustCompile(`\d+(.\d+)? fps`)
|
buffer := make([]byte, 2<<10)
|
||||||
match = regex.FindString(bufferstr)
|
total := 0
|
||||||
if len(match) > 0 {
|
for {
|
||||||
index = strings.Index(match, " fps")
|
n, err := pipe.Read(buffer[total:])
|
||||||
if index != -1 {
|
total += n
|
||||||
match = match[:index]
|
if err == io.EOF {
|
||||||
}
|
break
|
||||||
camera.fps = parse(match)
|
|
||||||
}
|
|
||||||
// Codec.
|
|
||||||
regex = regexp.MustCompile("Video: .+,")
|
|
||||||
match = regex.FindString(bufferstr)
|
|
||||||
if len(match) > 0 {
|
|
||||||
match = match[len("Video: "):]
|
|
||||||
index = strings.Index(match, "(")
|
|
||||||
if index != -1 {
|
|
||||||
match = match[:index]
|
|
||||||
}
|
|
||||||
index = strings.Index(match, ",")
|
|
||||||
if index != -1 {
|
|
||||||
match = match[:index]
|
|
||||||
}
|
|
||||||
camera.codec = strings.TrimSpace(match)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cmd.Wait()
|
||||||
|
devices := parseDevices(buffer)
|
||||||
|
return devices, nil
|
||||||
|
}
|
||||||
|
|
37
video.go
37
video.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,7 +108,7 @@ func NewVideo(filename string) (*Video, error) {
|
||||||
|
|
||||||
video := &Video{filename: filename, depth: 3}
|
video := &Video{filename: filename, depth: 3}
|
||||||
|
|
||||||
addVideoData(videoData, video)
|
video.addVideoData(videoData)
|
||||||
if audioCodec, ok := audioData["codec_name"]; ok {
|
if audioCodec, ok := audioData["codec_name"]; ok {
|
||||||
video.audioCodec = audioCodec
|
video.audioCodec = audioCodec
|
||||||
}
|
}
|
||||||
|
@ -115,9 +116,39 @@ func NewVideo(filename string) (*Video, error) {
|
||||||
return video, nil
|
return video, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds Video data to the video struct from the ffprobe output.
|
||||||
|
func (video *Video) addVideoData(data map[string]string) {
|
||||||
|
if width, ok := data["width"]; ok {
|
||||||
|
video.width = int(parse(width))
|
||||||
|
}
|
||||||
|
if height, ok := data["height"]; ok {
|
||||||
|
video.height = int(parse(height))
|
||||||
|
}
|
||||||
|
if duration, ok := data["duration"]; ok {
|
||||||
|
video.duration = float64(parse(duration))
|
||||||
|
}
|
||||||
|
if frames, ok := data["nb_frames"]; ok {
|
||||||
|
video.frames = int(parse(frames))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fps, ok := data["r_frame_rate"]; ok {
|
||||||
|
split := strings.Split(fps, "/")
|
||||||
|
if len(split) == 2 && split[0] != "" && split[1] != "" {
|
||||||
|
video.fps = parse(split[0]) / parse(split[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bitrate, ok := data["bit_rate"]; ok {
|
||||||
|
video.bitrate = int(parse(bitrate))
|
||||||
|
}
|
||||||
|
if codec, ok := data["codec_name"]; ok {
|
||||||
|
video.codec = codec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Once the user calls Read() for the first time on a Video struct,
|
// Once the user calls Read() for the first time on a Video struct,
|
||||||
// the ffmpeg command which is used to read the video is started.
|
// the ffmpeg command which is used to read the video is started.
|
||||||
func initVideo(video *Video) 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 RGB format.
|
||||||
|
@ -153,7 +184,7 @@ func initVideo(video *Video) error {
|
||||||
func (video *Video) Read() 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 {
|
||||||
if err := initVideo(video); err != nil {
|
if err := video.init(); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ func NewVideoWriter(filename string, width, height int, options *Options) (*Vide
|
||||||
|
|
||||||
// Once the user calls Write() for the first time on a VideoWriter struct,
|
// Once the user calls Write() for the first time on a VideoWriter struct,
|
||||||
// the ffmpeg command which is used to write to the video file is started.
|
// the ffmpeg command which is used to write to the video file is started.
|
||||||
func initVideoWriter(writer *VideoWriter) error {
|
func (writer *VideoWriter) init() error {
|
||||||
// If user exits with Ctrl+C, stop ffmpeg process.
|
// If user exits with Ctrl+C, stop ffmpeg process.
|
||||||
writer.cleanup()
|
writer.cleanup()
|
||||||
// ffmpeg command to write to video file. Takes in bytes from Stdin and encodes them.
|
// ffmpeg command to write to video file. Takes in bytes from Stdin and encodes them.
|
||||||
|
@ -291,7 +291,7 @@ func initVideoWriter(writer *VideoWriter) error {
|
||||||
func (writer *VideoWriter) Write(frame []byte) error {
|
func (writer *VideoWriter) Write(frame []byte) error {
|
||||||
// If cmd is nil, video writing has not been set up.
|
// If cmd is nil, video writing has not been set up.
|
||||||
if writer.cmd == nil {
|
if writer.cmd == nil {
|
||||||
if err := initVideoWriter(writer); err != nil {
|
if err := writer.init(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,12 +223,11 @@ dummy: Immediate exit requested`,
|
||||||
|
|
||||||
func TestWebcamParsing(t *testing.T) {
|
func TestWebcamParsing(t *testing.T) {
|
||||||
camera := &Camera{}
|
camera := &Camera{}
|
||||||
err := getCameraData(
|
err := camera.getCameraData(
|
||||||
`Input #0, dshow, from 'video=Integrated Camera':
|
`Input #0, dshow, from 'video=Integrated Camera':
|
||||||
Duration: N/A, start: 1367309.442000, bitrate: N/A
|
Duration: N/A, start: 1367309.442000, bitrate: N/A
|
||||||
Stream #0:0: Video: mjpeg (Baseline) (MJPG / 0x47504A4D), yuvj422p(pc, bt470bg/unknown/unknown), 1280x720, 30 fps, 30 tbr, 10000k tbn
|
Stream #0:0: Video: mjpeg (Baseline) (MJPG / 0x47504A4D), yuvj422p(pc, bt470bg/unknown/unknown), 1280x720, 30 fps, 30 tbr, 10000k tbn
|
||||||
At least one output file must be specified`,
|
At least one output file must be specified`,
|
||||||
camera,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue