package main import ( "fmt" "io" "log" "os" "os/exec" "runtime" "strings" ) var ( // Logger is the global logger Logger *log.Logger // LogFileHandle keeps the file handle to close it later LogFileHandle *os.File ) // SetupLogging configures logging to both console and file func SetupLogging(logPath string) error { // Close previous log file if open if LogFileHandle != nil { LogFileHandle.Close() } f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return err } LogFileHandle = f // MultiWriter to write to both stdout and file mw := io.MultiWriter(os.Stdout, f) Logger = log.New(mw, "", log.LstdFlags) return nil } // LogInfo logs a message func LogInfo(format string, v ...interface{}) { if Logger != nil { Logger.Printf(format, v...) } else { log.Printf(format, v...) } } // RunCmd executes a command. // If captureOutput is true, it returns the stdout as string. // If captureOutput is false, it streams stdout/stderr to the console (and log file if possible). func RunCmd(name string, args []string, captureOutput bool) (string, error) { cmdStr := fmt.Sprintf("%s %s", name, strings.Join(args, " ")) LogInfo("Executing: %s", cmdStr) cmd := exec.Command(name, args...) if captureOutput { output, err := cmd.Output() if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { return "", fmt.Errorf("command failed: %s\nstderr: %s", err, string(exitErr.Stderr)) } return "", err } return string(output), nil } // Passthrough mode // We want to stream to both stdout/stderr AND the log file if possible. // However, for interactive tools or progress bars, simple MultiWriter might mess up formatting. // For simplicity and safety with tools like av1an/ffmpeg showing progress bars, // we will just pipe to Stdout/Stderr of the parent process. // If we want to capture logs, we'd need a pipe that writes to file and stdout. if LogFileHandle != nil { cmd.Stdout = io.MultiWriter(os.Stdout, LogFileHandle) cmd.Stderr = io.MultiWriter(os.Stderr, LogFileHandle) } else { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } err := cmd.Run() if err != nil { return "", err } return "", nil } // CheckTools verifies that all required tools are in PATH func CheckTools(tools []string) { missing := []string{} for _, tool := range tools { _, err := exec.LookPath(tool) if err != nil { missing = append(missing, tool) } } if len(missing) > 0 { fmt.Printf("Error: The following required tools are missing from PATH:\n") for _, m := range missing { fmt.Printf(" - %s\n", m) } os.Exit(1) } } // GetTotalCores returns the number of logical CPUs func GetTotalCores() int { return runtime.NumCPU() }