diff --git a/backend/pkg/utils/exec.go b/backend/pkg/utils/exec.go new file mode 100644 index 0000000..5c9207f --- /dev/null +++ b/backend/pkg/utils/exec.go @@ -0,0 +1,67 @@ +package utils + +import ( + "bufio" + "context" + "os/exec" + + "github.com/go-pay/errgroup" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +// ExecCommand executes a command and streams its output in real-time +func ExecCommand(name string, args ...string) error { + cmd := exec.Command(name, args...) + + stdout, err := cmd.StdoutPipe() + if err != nil { + return errors.Wrap(err, "stdout pipe error") + } + + stderr, err := cmd.StderrPipe() + if err != nil { + return errors.Wrap(err, "stderr pipe error") + } + + if err := cmd.Start(); err != nil { + return errors.Wrap(err, "command start error") + } + + var eg errgroup.Group + eg.Go(func(ctx context.Context) error { + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + log.Info(scanner.Text()) + } + return nil + }) + + eg.Go(func(ctx context.Context) error { + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + log.Error(scanner.Text()) + } + return nil + }) + + if err := cmd.Wait(); err != nil { + return errors.Wrap(err, "command wait error") + } + + if err := eg.Wait(); err != nil { + return errors.Wrap(err, "command wait error") + } + + return nil +} + +// ExecCommandOutput executes a command and returns its output +func ExecCommandOutput(name string, args ...string) ([]byte, error) { + cmd := exec.Command(name, args...) + output, err := cmd.Output() + if err != nil { + return nil, errors.Wrapf(err, "failed to execute command: %s", name) + } + return output, nil +} diff --git a/backend/pkg/utils/ffmpeg.go b/backend/pkg/utils/ffmpeg.go index 310a627..5916a49 100644 --- a/backend/pkg/utils/ffmpeg.go +++ b/backend/pkg/utils/ffmpeg.go @@ -1,120 +1,56 @@ package utils import ( - "bufio" - "context" - "os/exec" "strconv" "strings" - "github.com/go-pay/errgroup" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) func GetMediaDuration(path string) (int64, error) { - // use ffprobe to get media duration - // ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 - cmd := exec.Command("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", path) - durationOutput, err := cmd.Output() + args := []string{ + "-v", "error", + "-show_entries", "format=duration", + "-of", "default=noprint_wrappers=1:nokey=1", + path, + } + + output, err := ExecCommandOutput("ffprobe", args...) if err != nil { return 0, errors.Wrap(err, "ffprobe error") } - duration := string(durationOutput) - duration = strings.TrimSpace(duration) + + duration := strings.TrimSpace(string(output)) durationFloat, err := strconv.ParseFloat(duration, 64) if err != nil { return 0, errors.Wrap(err, "duration conversion error") } + return int64(durationFloat), nil } func CutMedia(input, output string, start, end int64) error { - // ffmpeg -ss 00:00:00 -i input.mp4 -to 00:01:00 -c copy output.mp4 - cmd := exec.Command("ffmpeg", "-y", "-ss", strconv.FormatInt(start, 10), "-i", input, "-t", strconv.FormatInt(end, 10), "-c", "copy", output) - - stdout, err := cmd.StdoutPipe() - if err != nil { - log.Errorf("Error creating stdout pipe: %v", err) - return err + args := []string{ + "-y", + "-ss", strconv.FormatInt(start, 10), + "-i", input, + "-t", strconv.FormatInt(end, 10), + "-c", "copy", + output, } - stderr, err := cmd.StderrPipe() - if err != nil { - log.Errorf("Error creating stderr pipe: %v", err) - return err - } - - if err := cmd.Start(); err != nil { - log.Errorf("Error starting command: %v", err) - return err - } - - var eg errgroup.Group - eg.Go(func(ctx context.Context) error { - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - log.Info(scanner.Text()) - } - return nil - }) - - eg.Go(func(ctx context.Context) error { - scanner := bufio.NewScanner(stderr) - for scanner.Scan() { - log.Error(scanner.Text()) - } - return nil - }) - - if err := cmd.Wait(); err != nil { - log.Errorf("Error waiting for command: %v", err) - return err - } - - if err := eg.Wait(); err != nil { - log.Errorf("Error waiting for command: %v", err) - return err - } - return nil + return ExecCommand("ffmpeg", args...) } // GetFrameImageFromVideo extracts target time frame from a video file and saves it as an image. func GetFrameImageFromVideo(input, output string, time int64) error { - // ffmpeg -y -i input.mp4 -ss 00:00:01 -vframes 1 output.jpg - cmd := exec.Command("ffmpeg", "-y", "-i", input, "-ss", strconv.FormatInt(time, 10), "-vframes", "1", output) - stdout, err := cmd.StdoutPipe() - if err != nil { - return errors.Wrap(err, "stdout pipe error") + args := []string{ + "-y", + "-i", input, + "-ss", strconv.FormatInt(time, 10), + "-vframes", "1", + output, } - stderr, err := cmd.StderrPipe() - if err != nil { - return errors.Wrap(err, "stderr pipe error") - } - if err := cmd.Start(); err != nil { - return errors.Wrap(err, "command start error") - } - var eg errgroup.Group - eg.Go(func(ctx context.Context) error { - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - log.Info(scanner.Text()) - } - return nil - }) - eg.Go(func(ctx context.Context) error { - scanner := bufio.NewScanner(stderr) - for scanner.Scan() { - log.Error(scanner.Text()) - } - return nil - }) - if err := cmd.Wait(); err != nil { - return errors.Wrap(err, "command wait error") - } - if err := eg.Wait(); err != nil { - return errors.Wrap(err, "command wait error") - } - return nil + return ExecCommand("ffmpeg", args...) }