package main import ( "context" "flag" "fmt" "log" "os" "os/signal" "syscall" "time" "github.com/0x524a/onvif-go/server" ) var ( version = "1.0.0" ) const ( defaultPort = 8080 maxWorkers = 3 defaultTimeout = 30 ptzStepSize = 5 ptzMaxPan = 180 ptzMaxTilt = 90 ptzSpeed = 0.5 ) func main() { // Define command-line flags host := flag.String("host", "0.0.0.0", "Server host address") port := flag.Int("port", defaultPort, "Server port") username := flag.String("username", "admin", "Authentication username") password := flag.String("password", "admin", "Authentication password") manufacturer := flag.String("manufacturer", "onvif-go", "Device manufacturer") model := flag.String("model", "Virtual Multi-Lens Camera", "Device model") firmware := flag.String("firmware", "1.0.0", "Firmware version") serial := flag.String("serial", "SN-12345678", "Serial number") profiles := flag.Int( "profiles", maxWorkers, "Number of camera profiles (1-10)", ) ptz := flag.Bool("ptz", true, "Enable PTZ support") imaging := flag.Bool("imaging", true, "Enable Imaging support") events := flag.Bool("events", false, "Enable Events support") info := flag.Bool("info", false, "Show server info and exit") showVersion := flag.Bool("version", false, "Show version and exit") flag.Usage = func() { fmt.Fprintf(os.Stderr, "ONVIF Server - Virtual IP Camera Simulator\n\n") fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Options:\n") flag.PrintDefaults() fmt.Fprintf(os.Stderr, "\nExamples:\n") fmt.Fprintf(os.Stderr, " # Start with default settings (3 profiles, PTZ enabled)\n") fmt.Fprintf(os.Stderr, " %s\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, " # Start with custom credentials and 5 profiles\n") fmt.Fprintf(os.Stderr, " %s -username myuser -password mypass -profiles 5\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, " # Start on specific port without PTZ\n") fmt.Fprintf(os.Stderr, " %s -port 9000 -ptz=false\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, " # Show server information\n") fmt.Fprintf(os.Stderr, " %s -info\n\n", os.Args[0]) } flag.Parse() // Handle version flag if *showVersion { fmt.Printf("onvif-server version %s\n", version) os.Exit(0) } // Validate profiles count if *profiles < 1 || *profiles > 10 { log.Fatal("Number of profiles must be between 1 and 10") } // Create server configuration config := buildConfig(*host, *port, *username, *password, *manufacturer, *model, *firmware, *serial, *profiles, *ptz, *imaging, *events) // Create server srv, err := server.New(config) if err != nil { log.Fatalf("Failed to create server: %v", err) } // Handle info flag if *info { fmt.Println(srv.ServerInfo()) os.Exit(0) } // Print banner printBanner() // Create context that listens for interrupt signals ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Setup signal handler sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) // Start server in goroutine go func() { if err := srv.Start(ctx); err != nil { log.Printf("Server error: %v", err) cancel() } }() // Wait for interrupt signal <-sigChan fmt.Println("\nšŸ›‘ Received interrupt signal, shutting down...") cancel() // Give the server a moment to shut down gracefully time.Sleep(1 * time.Second) fmt.Println("āœ… Server stopped") } // buildConfig creates a server configuration from command-line arguments. func buildConfig(host string, port int, username, password, manufacturer, model, firmware, serial string, numProfiles int, ptz, imaging, events bool) *server.Config { config := &server.Config{ Host: host, Port: port, BasePath: "/onvif", Timeout: defaultTimeout * time.Second, DeviceInfo: server.DeviceInfo{ Manufacturer: manufacturer, Model: model, FirmwareVersion: firmware, SerialNumber: serial, HardwareID: "HW-87654321", }, Username: username, Password: password, SupportPTZ: ptz, SupportImaging: imaging, SupportEvents: events, Profiles: make([]server.ProfileConfig, numProfiles), } // Define profile templates templates := []struct { name string width int height int framerate int bitrate int quality float64 hasPTZ bool ptzZoomMax float64 }{ {"Main Camera - High Quality", 1920, 1080, 30, 4096, 80, true, 1}, {"Wide Angle Camera", 1280, 720, 30, 2048, 75, false, 0}, {"Telephoto Camera", 1920, 1080, 25, 6144, 85, true, 3}, {"Low Light Camera", 1920, 1080, 30, 4096, 80, false, 0}, {"Ultra HD Camera", 3840, 2160, 30, 16384, 90, true, 2}, {"Compact Camera", 640, 480, 30, 512, 70, false, 0}, {"PTZ Dome Camera", 1920, 1080, 30, 4096, 80, true, 2}, {"Fisheye Camera", 1920, 1080, 30, 4096, 80, false, 0}, {"Thermal Camera", 640, 480, 30, 1024, 75, true, 1}, {"License Plate Camera", 1920, 1080, 60, 8192, 90, true, 5}, } // Generate profiles for i := 0; i < numProfiles; i++ { template := templates[i%len(templates)] profile := server.ProfileConfig{ Token: fmt.Sprintf("profile_%d", i), Name: template.name, VideoSource: server.VideoSourceConfig{ Token: fmt.Sprintf("video_source_%d", i), Name: template.name, Resolution: server.Resolution{Width: template.width, Height: template.height}, Framerate: template.framerate, Bounds: server.Bounds{X: 0, Y: 0, Width: template.width, Height: template.height}, }, VideoEncoder: server.VideoEncoderConfig{ Encoding: "H264", Resolution: server.Resolution{Width: template.width, Height: template.height}, Quality: template.quality, Framerate: template.framerate, Bitrate: template.bitrate, GovLength: template.framerate, }, Snapshot: server.SnapshotConfig{ Enabled: true, Resolution: server.Resolution{Width: template.width, Height: template.height}, Quality: template.quality + 5, //nolint:mnd // Quality offset }, } // Add PTZ if enabled and template supports it if ptz && template.hasPTZ { profile.PTZ = &server.PTZConfig{ NodeToken: fmt.Sprintf("ptz_node_%d", i), PanRange: server.Range{Min: -ptzMaxPan, Max: ptzMaxPan}, TiltRange: server.Range{Min: -ptzMaxTilt, Max: ptzMaxTilt}, ZoomRange: server.Range{Min: 0, Max: template.ptzZoomMax}, DefaultSpeed: server.PTZSpeed{Pan: ptzSpeed, Tilt: ptzSpeed, Zoom: ptzSpeed}, SupportsContinuous: true, SupportsAbsolute: true, SupportsRelative: true, Presets: []server.Preset{ { Token: fmt.Sprintf("preset_%d_0", i), Name: "Home", Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}, }, { Token: fmt.Sprintf("preset_%d_1", i), Name: "Entrance", Position: server.PTZPosition{ Pan: -45, Tilt: -10, Zoom: template.ptzZoomMax * ptzSpeed, }, }, }, } } config.Profiles[i] = profile } return config } // printBanner prints the application banner. func printBanner() { banner := ` ╔═══════════════════════════════════════════════════════════╗ ā•‘ ā•‘ ā•‘ šŸŽ„ ONVIF Virtual Camera Server šŸŽ„ ā•‘ ā•‘ ā•‘ ā•‘ Simulate multi-lens IP cameras with ONVIF support ā•‘ ā•‘ Version: ` + version + ` ā•‘ ā•‘ ā•‘ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• ` fmt.Println(banner) }