package comicanimator import ( "errors" "os" "strconv" "time" ) type Config struct { OpenRouterAPIKey, OpenRouterBaseURL, OpenRouterSiteURL, OpenRouterAppName, PromptModel, PromptFile, VideoModel, VideoResolution, PublicBaseURL, SigningSecret, UploadDirectory, OutputDirectory string VideoDuration int GenerateAudio bool PollInterval, JobTimeout, HTTPTimeout, SignedURLTTL time.Duration MaxUploadBytes, MaxGeneratedVideoBytes int64 QueueCapacity int } func LoadConfigFromEnv() (Config, error) { c := Config{OpenRouterAPIKey: os.Getenv("COMIC_ANIMATOR_OPENROUTER_API_KEY"), OpenRouterBaseURL: get("COMIC_ANIMATOR_OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1"), OpenRouterSiteURL: os.Getenv("COMIC_ANIMATOR_OPENROUTER_SITE_URL"), OpenRouterAppName: get("COMIC_ANIMATOR_OPENROUTER_APP_NAME", "Preface Tools - Comic Animator"), PromptModel: os.Getenv("COMIC_ANIMATOR_PROMPT_MODEL"), PromptFile: get("COMIC_ANIMATOR_PROMPT_FILE", "prompts/comic-animator-system.txt"), VideoModel: os.Getenv("COMIC_ANIMATOR_VIDEO_MODEL"), VideoResolution: get("COMIC_ANIMATOR_VIDEO_RESOLUTION", "720p"), PublicBaseURL: os.Getenv("PUBLIC_BASE_URL"), SigningSecret: os.Getenv("COMIC_ANIMATOR_SIGNING_SECRET"), UploadDirectory: get("COMIC_ANIMATOR_UPLOAD_DIR", "data/comic-animator/uploads"), OutputDirectory: get("COMIC_ANIMATOR_OUTPUT_DIR", "data/comic-animator/outputs")} var err error c.VideoDuration, err = intenv("COMIC_ANIMATOR_VIDEO_DURATION", 6) if err != nil { return c, err } c.GenerateAudio, err = strconv.ParseBool(get("COMIC_ANIMATOR_GENERATE_AUDIO", "false")) if err != nil { return c, err } c.PollInterval, err = dur("COMIC_ANIMATOR_POLL_INTERVAL", "30s") if err != nil { return c, err } c.JobTimeout, err = dur("COMIC_ANIMATOR_JOB_TIMEOUT", "15m") if err != nil { return c, err } c.HTTPTimeout, err = dur("COMIC_ANIMATOR_HTTP_TIMEOUT", "60s") if err != nil { return c, err } c.SignedURLTTL, err = dur("COMIC_ANIMATOR_SIGNED_URL_TTL", "30m") if err != nil { return c, err } c.MaxUploadBytes, err = int64env("COMIC_ANIMATOR_MAX_UPLOAD_BYTES", 20971520) if err != nil { return c, err } c.MaxGeneratedVideoBytes, err = int64env("COMIC_ANIMATOR_MAX_VIDEO_BYTES", 536870912) if err != nil { return c, err } c.QueueCapacity, err = intenv("COMIC_ANIMATOR_QUEUE_CAPACITY", 100) if err != nil { return c, err } if c.OpenRouterAPIKey == "" || c.PromptModel == "" || c.VideoModel == "" || c.PublicBaseURL == "" || len(c.SigningSecret) < 32 { return c, errors.New("Comic Animator OpenRouter key/models, PUBLIC_BASE_URL, and a 32+ character signing secret are required") } if c.VideoDuration < 1 || c.QueueCapacity < 1 || c.MaxUploadBytes < 1 || c.MaxGeneratedVideoBytes < 1 || c.PollInterval <= 0 || c.JobTimeout <= 0 || c.HTTPTimeout <= 0 || c.SignedURLTTL <= 0 { return c, errors.New("Comic Animator numeric configuration is invalid") } return c, nil } func get(k, d string) string { if v := os.Getenv(k); v != "" { return v } return d } func dur(k, d string) (time.Duration, error) { return time.ParseDuration(get(k, d)) } func intenv(k string, d int) (int, error) { if os.Getenv(k) == "" { return d, nil } return strconv.Atoi(os.Getenv(k)) } func int64env(k string, d int64) (int64, error) { if os.Getenv(k) == "" { return d, nil } return strconv.ParseInt(os.Getenv(k), 10, 64) }