From b54cf700aa797eb3823059b53355ff1d83ade911 Mon Sep 17 00:00:00 2001 From: Esa Kataja Date: Sat, 4 Apr 2026 18:07:38 +0300 Subject: [PATCH] add: add main CLI entrypoint --- cmd/videnc/main.go | 174 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 cmd/videnc/main.go diff --git a/cmd/videnc/main.go b/cmd/videnc/main.go new file mode 100644 index 0000000..416765a --- /dev/null +++ b/cmd/videnc/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "flag" + "fmt" + "os" + "os/signal" + "path/filepath" + "syscall" + + "videnc-vibe/internal/config" + "videnc-vibe/internal/encoder" + "videnc-vibe/internal/logger" + "videnc-vibe/internal/metadata" + "videnc-vibe/internal/mover" + "videnc-vibe/internal/watcher" + "videnc-vibe/pkg/types" +) + +var ( + deleteOrigin bool + configPath string +) + +func init() { + flag.BoolVar(&deleteOrigin, "d", false, "Delete original after successful encode") + flag.StringVar(&configPath, "c", "", "Config file path (default: ~/.config/videnc-vibe/config.yaml)") +} + +func main() { + flag.Parse() + + cfg, err := config.Load(configPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err) + os.Exit(1) + } + + if err := config.EnsureDirs(cfg); err != nil { + fmt.Fprintf(os.Stderr, "Failed to create directories: %v\n", err) + os.Exit(1) + } + + log, err := logger.New(".") + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err) + os.Exit(1) + } + defer log.Close() + + enc := encoder.New() + if err := enc.CheckDeps(); err != nil { + log.Error("Dependency check failed", err.Error()) + fmt.Fprintf(os.Stderr, "Dependency check failed: %v\n", err) + os.Exit(1) + } + + metaClient := metadata.NewClient(cfg.OMDBAPIKey) + + done := make(chan struct{}) + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + <-sigChan + close(done) + }() + + w := watcher.New(cfg.Paths.Input, 15) + w.Start(func(inputPath string) error { + return processFile(inputPath, cfg, enc, metaClient, log) + }, done) + + log.Info("videnc-vibe started") +} + +func processFile(inputPath string, cfg *types.Config, enc *encoder.Encoder, metaClient *metadata.Client, log *logger.Logger) error { + log.Info(fmt.Sprintf("Processing: %s", inputPath)) + + filename := filepath.Base(inputPath) + isSeries, imdbID, tvmazeID, season, episode := metadata.ParseFilename(filename) + + width, height, err := enc.GetMediaInfo(inputPath) + if err != nil { + log.ErrorFile(inputPath, "Getting media info", err.Error()) + mover.MoveToFailed(inputPath, cfg.Paths.Failed) + return err + } + + mediaType := watcher.DetectMediaType(width, height) + crf := cfg.Encoding.DVD.CRF + preset := cfg.Encoding.DVD.Preset + if mediaType == types.MediaTypeBluRay { + crf = cfg.Encoding.Bluray.CRF + preset = cfg.Encoding.Bluray.Preset + } + + var meta *types.Metadata + if isSeries && tvmazeID != "" && season != "" && episode != "" { + meta, err = metaClient.FetchSeriesMetadata(tvmazeID, season, episode) + if err != nil { + log.ErrorFile(inputPath, "Fetching series metadata", err.Error()) + } + } else if imdbID != "" { + meta, err = metaClient.FetchMovieMetadata(imdbID) + if err != nil { + log.ErrorFile(inputPath, "Fetching movie metadata", err.Error()) + } + } + + if meta == nil { + meta = &types.Metadata{ + Title: "Unknown", + DateReleased: "", + IMDBID: "", + OriginalMedia: mediaType, + } + } + meta.OriginalMedia = mediaType + + job := &types.Job{ + InputPath: inputPath, + MediaType: mediaType, + CRF: crf, + Preset: preset, + DeleteOrigin: deleteOrigin, + } + + if err := enc.Transcode(inputPath, job, meta); err != nil { + log.ErrorFile(inputPath, "Transcoding", err.Error()) + mover.MoveToFailed(inputPath, cfg.Paths.Failed) + outputPath := filepath.Join(filepath.Dir(inputPath), "output.mkv") + mover.MoveToFailed(outputPath, cfg.Paths.Failed) + return err + } + + outputDir := filepath.Dir(inputPath) + outputPath := filepath.Join(outputDir, "output.mkv") + + var outFilename string + if meta.IMDBID == "" || meta.Title == "Unknown" { + outFilename = fmt.Sprintf("%s.nometadata.mkv", generateRandomString(8)) + } else if isSeries { + outFilename = fmt.Sprintf("%s.S%sE%s.mkv", meta.Collection, season, episode) + } else { + outFilename = fmt.Sprintf("%s.%s.mkv", meta.Title, meta.IMDBID) + } + + finalOutput := filepath.Join(cfg.Paths.Output, outFilename) + if err := mover.Rename(outputPath, finalOutput); err != nil { + log.ErrorFile(inputPath, "Moving output", err.Error()) + mover.MoveToFailed(outputPath, cfg.Paths.Failed) + return err + } + + if deleteOrigin { + mover.Delete(inputPath) + log.Info(fmt.Sprintf("Deleted original: %s", inputPath)) + } else { + mover.MoveToOriginals(inputPath, cfg.Paths.Originals) + log.Info(fmt.Sprintf("Moved original to originals: %s", inputPath)) + } + + log.Info(fmt.Sprintf("Completed: %s -> %s", inputPath, finalOutput)) + return nil +} + +func generateRandomString(length int) string { + bytes := make([]byte, length/2+1) + rand.Read(bytes) + return hex.EncodeToString(bytes)[:length] +}