package main import ( "context" "errors" "flag" "fmt" "log" "os" "path/filepath" "time" "git.meatbag.se/varl/subsyt/internal/config" "git.meatbag.se/varl/subsyt/internal/dl" "git.meatbag.se/varl/subsyt/internal/format" "git.meatbag.se/varl/subsyt/internal/metadata" "git.meatbag.se/varl/subsyt/internal/scheduler" "git.meatbag.se/varl/subsyt/internal/server" ) func run(cfg config.Config) { provider := cfg.Provider["youtube"] if err := dl.UpgradeYtDlp(provider.Cmd); err != nil { log.Fatalf("failed to ensure yt-dlp is up to date: %v", err) } opml, err := format.OpmlLoad(provider.Opml_file) if err != nil { panic(err) } for _, outlines := range opml.Body.Outline { log.Printf("Archiving videos from OPML: %s\n", outlines.Title) for _, outline := range outlines.Outlines { rssData, err := dl.RssDownloader(outline.XmlUrl) if err != nil { log.Printf("Failed to download RSS for %s: %v", outline.Title, err) continue } feed, err := format.RssLoad(rssData) if err != nil { log.Printf("Failed to parse RSS for %s: %v", feed.Title, err) continue } dl.Youtube(dl.Download{ Url: feed.Author.Uri, OutDir: filepath.Join(cfg.Out_dir, outline.Title), DryRun: cfg.Dry_run, Metadata: true, }, provider) log.Printf("Downloaded RSS feed for %s with %d entries", feed.Title, len(feed.Entries)) for _, entry := range feed.Entries { url := fmt.Sprintf("%s/watch?v=%s", provider.Url, entry.VideoId) log.Printf("Entry: %#v", entry) dl.Youtube(dl.Download{ Url: url, OutDir: filepath.Join(cfg.Out_dir, feed.Title), DryRun: cfg.Dry_run, Metadata: false, }, provider) } metadata.Generate(cfg.Out_dir, feed.Title, cfg.Dry_run) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() configPtr := flag.String("config", "", "path to config file") flag.Parse() configEnv := os.Getenv("CONFIG") var configPath string if *configPtr != "" { configPath = *configPtr } else if configEnv != "" { configPath = configEnv } else { configPath = "./config.json" } log.Println("resolved config file", configPath) cfg, err := config.Load(configPath) if err != nil { panic(err) } provider, ok := cfg.Provider["youtube"] if !ok { log.Fatal("youtube provider configuration missing") } if cfg.Http_api.Enable { if err := setupAPIServer(ctx, cfg, provider); err != nil { log.Fatalf("failed to start http api: %v", err) } } if cfg.Daemon { log.Println("running with scheduler") s := scheduler.Scheduler{} s.Start(run, cfg) } else { log.Println("running standalone") run(cfg) } } func setupAPIServer(ctx context.Context, cfg config.Config, provider config.Provider) error { queue, err := server.NewQueue(cfg.Http_api.Queue_file) if err != nil { return fmt.Errorf("load queue: %w", err) } go queueWorker(ctx, queue, cfg, provider) srv := server.NewServer(cfg.Http_api, cfg.Out_dir, queue) go func() { if err := srv.Start(ctx); err != nil { log.Printf("http api server stopped with error: %v", err) } }() return nil } func queueWorker(ctx context.Context, q *server.Queue, cfg config.Config, provider config.Provider) { for { item, err := q.Next(ctx) if err != nil { if errors.Is(err, context.Canceled) { return } log.Printf("queue wait error: %v", err) select { case <-time.After(5 * time.Second): case <-ctx.Done(): return } continue } outPath := filepath.Join(cfg.Out_dir, "_misc") dl.Youtube(dl.Download{ Url: item.Request.URL, OutDir: outPath, DryRun: cfg.Dry_run, Metadata: false, }, provider) metadata.Generate(cfg.Out_dir, "_misc", cfg.Dry_run) if err := q.MarkDone(item.ID); err != nil { log.Printf("queue mark done failed: %v", err) } } }