package config import ( "encoding/json" "errors" "fmt" "live-streamer/model" "live-streamer/utils" "log" "os" "path/filepath" "strings" ) type OutputConfig struct { RTMPServer string `json:"rtmp_server"` StreamKey string `json:"stream_key"` } type LogConfig struct { Level string `json:"level"` ShowFFmpegOutput bool `json:"show_ffmpeg_output"` } type ServerConfig struct { Addr string `json:"addr"` Token string `json:"token"` } type Config struct { Input []any `json:"input"` InputItems []model.VideoItem `json:"-"` // contains video file or dir VideoList []model.VideoItem `json:"-"` // only contains video file Play map[string]any `json:"play"` Output OutputConfig `json:"output"` Log LogConfig `json:"log"` Server ServerConfig `json:"server"` } var GlobalConfig *Config func init() { GlobalConfig = &Config{} err := readConfig("./config.json") if err != nil { if os.IsNotExist(err) { log.Fatal("Config not exists") } else { log.Fatal(err) } } if len(GlobalConfig.Input) == 0 { log.Fatal("no input video found") } } func readConfig(configPath string) error { stat, err := os.Stat(configPath) if err != nil { return fmt.Errorf("config read failed: %v", err) } if stat.IsDir() { return os.ErrNotExist } databytes, err := os.ReadFile(configPath) if err != nil { return fmt.Errorf("config read failed: %v", err) } if err = json.Unmarshal(databytes, GlobalConfig); err != nil { return fmt.Errorf("config unmarshal failed: %v", err) } err = validateConfig() if err != nil { return fmt.Errorf("config validate failed: %v", err) } return nil } func validateConfig() error { if err := validateInputConfig(); err != nil { return err } if err := validateOutputConfig(); err != nil { return err } if err := validateServerConfig(); err != nil { return err } validateLogConfig() return nil } func validateLogConfig() { if GlobalConfig.Log.Level == "" { GlobalConfig.Log.Level = "info" } } func validateInputConfig() error { if GlobalConfig.Input == nil { return errors.New("video_path is nil") } GlobalConfig.InputItems = make([]model.VideoItem, 0, len(GlobalConfig.Input)) GlobalConfig.VideoList = []model.VideoItem{} for i, item := range GlobalConfig.Input { var inputItem model.VideoItem switch v := item.(type) { case string: inputItem = model.VideoItem{Path: v} case map[string]any: data, err := json.Marshal(v) if err != nil { return fmt.Errorf("failed to marshal input item[%d]: %v", i, err) } if err := json.Unmarshal(data, &inputItem); err != nil { return fmt.Errorf("failed to unmarshal input item[%d]: %v", i, err) } // more efficient, but coupled // if path, ok := v["path"].(string); ok { // inputItem.Path = path // } // if start, ok := v["start"].(string); ok { // inputItem.Start = start // } // if end, ok := v["end"].(string); ok { // inputItem.End = end // } default: return fmt.Errorf("invalid input type for item[%d]: %T", i, item) } if inputItem.Path == "" { return fmt.Errorf("video_path[%d] is empty", i) } stat, err := os.Stat(inputItem.Path) if err != nil { return fmt.Errorf("video_path[%d] stat failed: %v", i, err) } if stat.IsDir() { inputItem.ItemType = "dir" videos, err := getAllVideos(inputItem.Path) if err != nil { return fmt.Errorf("video_path[%d] get videos error: %v", i, err) } GlobalConfig.VideoList = append(GlobalConfig.VideoList, videos...) } else { inputItem.ItemType = "file" if !utils.IsSupportedVideo(inputItem.Path) { return fmt.Errorf("video_path[%d] is not supported", i) } GlobalConfig.VideoList = append(GlobalConfig.VideoList, inputItem) } GlobalConfig.InputItems = append(GlobalConfig.InputItems, inputItem) } return nil } func validateOutputConfig() error { if GlobalConfig.Output.RTMPServer == "" { return errors.New("rtmp_server is empty") } else if !strings.HasPrefix(GlobalConfig.Output.RTMPServer, "rtmp://") && !strings.HasPrefix(GlobalConfig.Output.RTMPServer, "rtmps://") { return errors.New("rtmp_server is not a valid rtmp server") } else { GlobalConfig.Output.RTMPServer = strings.TrimSuffix(GlobalConfig.Output.RTMPServer, "/") } if GlobalConfig.Output.StreamKey == "" { return errors.New("stream_key is empty") } else { GlobalConfig.Output.StreamKey = strings.TrimPrefix(GlobalConfig.Output.StreamKey, "/") } return nil } func validateServerConfig() error { if GlobalConfig.Server.Addr == "" { GlobalConfig.Server.Addr = ":8080" } return nil } func getAllVideos(dirPath string) ([]model.VideoItem, error) { res := []model.VideoItem{} err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && utils.IsSupportedVideo(path) { res = append(res, model.VideoItem{Path: path}) } return nil }) if err != nil { return nil, err } return res, nil }