2025-10-16 17:08:51 -04:00
package main
import (
"bytes"
2025-10-21 16:01:28 -04:00
"flag"
2025-10-16 17:08:51 -04:00
"fmt"
"image/jpeg"
"log"
"net/http"
2025-10-23 02:15:20 -04:00
"os"
"os/user"
2025-10-21 16:01:28 -04:00
"strconv"
2025-10-23 02:15:20 -04:00
"strings"
2025-10-16 17:08:51 -04:00
"syscall"
"github.com/google/gousb"
2025-10-21 16:01:28 -04:00
"github.com/hybridgroup/mjpeg"
2025-10-16 17:08:51 -04:00
"github.com/kevmo314/go-uvc"
"github.com/kevmo314/go-uvc/pkg/descriptors"
)
2025-10-23 02:15:20 -04:00
const udevrule = ` # Bigscreen Bigeye
2025-10-29 15:35:29 -04:00
KERNEL == "hidraw*" , SUBSYSTEM == "hidraw" , ATTRS { idVendor } == "35bd" , ATTRS { idProduct } == "0202" , MODE = "0660" , GROUP = "users" , TAG += "uaccess"
SUBSYSTEM == "usb" , ATTRS { idVendor } == "35bd" , ATTRS { idProduct } == "0202" , MODE = "0660" , GROUP = "users" , TAG += "uaccess"
2025-10-23 02:15:20 -04:00
`
2025-10-29 15:35:29 -04:00
const udevfilename = "99-bsb-cams.rules"
2025-10-23 02:15:20 -04:00
2025-10-16 17:08:51 -04:00
func getdevice ( ) ( device string ) {
ctx := gousb . NewContext ( )
defer ctx . Close ( )
dev , err := ctx . OpenDeviceWithVIDPID ( 0x35bd , 0x0202 )
2025-10-23 02:15:20 -04:00
user , _ := user . Current ( )
if user . Username == "root" && ! * sudo {
log . Print ( "Running As Root Isnt Reccomended For Safety, Creating A UDEV Rule To Allow Rootless Access " )
2025-10-29 15:35:29 -04:00
if _ , err := os . Stat ( udevfilename ) ; err == nil {
2025-10-23 02:15:20 -04:00
log . Print ( "File Already Exists" )
} else {
2025-10-29 15:35:29 -04:00
err := os . WriteFile ( udevfilename , [ ] byte ( udevrule ) , 0644 )
2025-10-23 02:15:20 -04:00
if err != nil {
log . Fatalf ( "Could not Create File: %v" , err )
}
}
var response string
2025-10-29 15:35:29 -04:00
log . Print ( "Would You Like The UDEV Rule Automatically Moved And Put In Place? (Y/N)" )
2025-10-23 02:15:20 -04:00
fmt . Scan ( & response )
switch strings . ToLower ( response ) {
case "yes" , "ye" , "y" :
2025-10-29 15:35:29 -04:00
log . Printf ( "Would You Like The Rule Moved To The Userspace (/usr/lib/udev/rules.d/%v) Or The Linux OS Space (/etc/udev/rules.d/%v) (Good For Atomic Operating Systems) " , udevfilename )
log . Print ( "0/Userspace 1/OS Space" )
fmt . Scan ( & response )
switch strings . ToLower ( response ) {
case "0" , "userspace" :
log . Printf ( "Putting File In /usr/lib/udev/rules.d/%v !!" , udevfilename )
err := os . Rename ( udevfilename , "/usr/lib/udev/rules.d/" + udevfilename )
if err != nil {
log . Fatalf ( "Could Not Move File: %v" , err )
2025-10-23 02:15:20 -04:00
}
2025-10-29 15:35:29 -04:00
case "1" , "os space" :
log . Printf ( "Putting File In /etc/udev/rules.d/%v !!" , udevfilename )
err := os . Rename ( udevfilename , "/etc/udev/rules.d/" + udevfilename )
if err != nil {
log . Fatalf ( "Could Not Move File: %v" , err )
}
default :
log . Fatal ( "Invalid Response" )
2025-10-23 02:15:20 -04:00
}
log . Print ( "Please Reboot Your PC For Changes To Take Effect!!" )
os . Exit ( 0 )
case "n" , "no" :
2025-10-29 15:35:29 -04:00
log . Printf ( "Please move the file (%v) into your udev rule directory and reboot for it to take effect, or if you REALLLLY want to run this program as sudo append --sudo to your run command" , udevfilename )
2025-10-23 02:15:20 -04:00
os . Exit ( 0 )
default :
log . Fatal ( "Invalid Answer" )
}
}
2025-10-16 17:08:51 -04:00
if err != nil {
2025-10-23 02:15:20 -04:00
if err == gousb . ErrorAccess {
log . Print ( "It looks like the cameras cannot be accessed, udev file being created in this directory" )
2025-10-29 15:35:29 -04:00
log . Printf ( "Creating UDEV Rule At %v" , udevfilename )
err := os . WriteFile ( udevfilename , [ ] byte ( udevrule ) , 0644 )
if err != nil {
log . Fatalf ( "Could not Create File: %v" , err )
2025-10-23 02:15:20 -04:00
}
log . Print ( "File Created ! Please copy to your udev directory, chown to root, and reboot for it to take effect" )
os . Exit ( 0 )
2025-10-29 15:35:29 -04:00
} else {
log . Fatal ( err )
2025-10-23 02:15:20 -04:00
}
2025-10-16 17:08:51 -04:00
}
2025-10-29 15:35:29 -04:00
if dev == nil {
log . Fatal ( "Could Not Find Device, Please Make Sure It Is On And Plugged In !" )
}
2025-10-16 17:08:51 -04:00
defer dev . Close ( )
return fmt . Sprintf ( "/dev/bus/usb/%03v/%03v" , dev . Desc . Bus , dev . Desc . Address )
}
2025-10-23 02:15:20 -04:00
var gitVersion string
2025-10-21 16:01:28 -04:00
var verbosePtr = flag . Bool ( "verbose" , false , "Whether or not to show libusb errors" )
2025-10-23 02:17:45 -04:00
var port = flag . Int ( "port" , 8080 , "What Port To Output Frames To" )
2025-10-23 02:15:20 -04:00
var version = flag . Bool ( "version" , false , "Flag To Show Current Version" )
var sudo = flag . Bool ( "sudo" , false , "Force Program To Run As Sudo" )
2025-10-21 16:01:28 -04:00
2025-10-16 17:08:51 -04:00
func main ( ) {
2025-10-21 16:01:28 -04:00
flag . Parse ( )
2025-10-23 02:15:20 -04:00
if * version {
log . Print ( "go-bsb-cams " + gitVersion )
os . Exit ( 0 )
}
2025-10-21 16:01:28 -04:00
stream := mjpeg . NewLiveStream ( )
2025-10-16 17:08:51 -04:00
device := getdevice ( )
// Pass your jpegBuffer frames using stream.UpdateJPEG(<your-buffer>)
go imagestreamer ( stream , device )
mux := http . NewServeMux ( )
mux . Handle ( "/stream" , stream )
2025-10-21 16:01:28 -04:00
log . Print ( "Server Is Running And Can Be Accessed At: http://localhost:" + strconv . Itoa ( * port ) + "/stream" )
log . Print ( "Make Sure You Have No Ending / When Inputting The Url Into Baballonia !!!" )
2025-10-29 15:35:29 -04:00
log . Print ( "If You Are Here And Cannot See The Cams, Please Close This Program, Unplug And Replug Your BSB, And Try Again :)" )
2025-10-21 16:01:28 -04:00
log . Fatal ( http . ListenAndServe ( ":" + strconv . Itoa ( * port ) , mux ) )
2025-10-16 17:08:51 -04:00
}
2025-10-21 16:01:28 -04:00
func imagestreamer ( stream * mjpeg . Stream , device string ) {
2025-10-20 00:19:14 -04:00
frame :
2025-10-16 17:08:51 -04:00
fd , err := syscall . Open ( device , syscall . O_RDWR , 0 )
2025-10-20 00:19:14 -04:00
var deviceFd = fd
2025-10-16 17:08:51 -04:00
if err != nil {
panic ( err )
}
ctx , err := uvc . NewUVCDevice ( uintptr ( fd ) )
if err != nil {
panic ( err )
}
info , err := ctx . DeviceInfo ( )
if err != nil {
panic ( err )
}
for _ , iface := range info . StreamingInterfaces {
for i , desc := range iface . Descriptors {
fd , ok := desc . ( * descriptors . MJPEGFormatDescriptor )
if ! ok {
continue
}
frd := iface . Descriptors [ i + 1 ] . ( * descriptors . MJPEGFrameDescriptor )
resp , err := iface . ClaimFrameReader ( fd . Index ( ) , frd . Index ( ) )
if err != nil {
2025-10-20 00:19:14 -04:00
log . Print ( "Yes" )
2025-10-16 17:08:51 -04:00
panic ( err )
}
2025-10-16 18:08:56 -04:00
for {
2025-10-16 17:08:51 -04:00
fr , err := resp . ReadFrame ( )
if err != nil {
2025-10-21 16:01:28 -04:00
if * verbosePtr {
log . Print ( err )
log . Print ( "Reclaiming Frame Reader and continuing to get frames... " )
}
2025-10-20 00:19:14 -04:00
syscall . Close ( deviceFd )
2025-10-16 18:15:17 -04:00
goto frame
2025-10-16 17:08:51 -04:00
}
2025-10-16 18:08:56 -04:00
2025-10-16 17:08:51 -04:00
img , err := jpeg . Decode ( fr )
if err != nil {
continue
}
jpegbuf := new ( bytes . Buffer )
if err = jpeg . Encode ( jpegbuf , img , nil ) ; err != nil {
log . Printf ( "failed to encode: %v" , err )
}
// boundry := ("--frame-boundary\r\nContent-Type: image/jpeg\r\nContent-Length: " + strconv.Itoa(len(jpegbuf.Bytes())) + "\r\n\r\n")
// stream.UpdateJPEG(append([]byte(boundry), jpegbuf.Bytes()...))
stream . UpdateJPEG ( jpegbuf . Bytes ( ) )
}
}
}
}