Initial commit

This commit is contained in:
2025-10-26 16:22:50 +01:00
commit 4d6c354436
12 changed files with 1193 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
package tasks
import (
"bufio"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/roemer/goext"
)
type BlackBorderAnalyzeTask struct {
logFilePath string
cropDetectOutputRegex *regexp.Regexp
}
func NewBlackBorderAnalyzeTask(logFilePath string) *BlackBorderAnalyzeTask {
return &BlackBorderAnalyzeTask{
logFilePath: logFilePath,
cropDetectOutputRegex: regexp.MustCompile(`.*Parsed_cropdetect_0.* x1:([0-9]+) x2:([0-9]+) y1:([0-9]+) y2:([0-9]+) w:([0-9]+) h:([0-9]+) x:([0-9]+) y:([0-9]+) pts:([0-9]+) t:([0-9\.]+) limit:([0-9\.]+) crop=([0-9\:]+)`),
}
}
func (t *BlackBorderAnalyzeTask) Run(folderPath string) error {
files, err := os.ReadDir(folderPath)
if err != nil {
return fmt.Errorf("failed to read directory: %w", err)
}
for _, file := range files {
// Skip directories
if file.IsDir() {
continue
}
// Skip reencoded files
if strings.HasSuffix(file.Name(), "_2.mkv") {
continue
}
t.output(fmt.Sprintf("=== %s", file.Name()))
if err := t.processFile(filepath.Join(folderPath, file.Name())); err != nil {
return fmt.Errorf("failed to process file %s: %w", file.Name(), err)
}
}
return nil
}
func (t *BlackBorderAnalyzeTask) processFile(filePath string) error {
// Get the resolution of the video
resOutput, _, err := goext.CmdRunners.Default.RunGetOutput("ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of", "json", filePath)
if err != nil {
return fmt.Errorf("failed to run ffprobe: %w", err)
}
decoder := json.NewDecoder(strings.NewReader(resOutput))
var result struct {
Streams []struct {
Width int `json:"width"`
Height int `json:"height"`
} `json:"streams"`
}
if err := decoder.Decode(&result); err != nil {
return fmt.Errorf("failed to decode ffprobe output: %w", err)
}
videoWidth := result.Streams[0].Width
videoHeight := result.Streams[0].Height
// Execute a command and process output in real time
cmd := exec.Command("ffmpeg", "-v", "debug", "-i", filePath, "-vf", "cropdetect", "-f", "null", "-")
// Create a single pipe that combines both stdout and stderr
combinedOutput, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to create stdout pipe: %w", err)
}
// Redirect stderr to stdout so both streams are handled together
cmd.Stderr = cmd.Stdout
// Start the command
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start command: %w", err)
}
// Process combined output in real time with a single goroutine
go func() {
lastValue := &blackBorderAnalyzeData{}
scanner := bufio.NewScanner(combinedOutput)
for scanner.Scan() {
line := scanner.Text()
// Process each line of combined output here
value := t.processOutputLine(line)
if value != nil {
if value.x1 != lastValue.x1 || value.x2 != lastValue.x2 || value.y1 != lastValue.y1 || value.y2 != lastValue.y2 || value.w != lastValue.w || value.h != lastValue.h || value.x != lastValue.x || value.y != lastValue.y {
// output(value.String())
t.output(fmt.Sprintf("left=%d, right=%d, top=%d, bottom=%d, time:%s",
value.x1, videoWidth-value.x2, value.y1, videoHeight-value.y2, value.timeStamp))
}
lastValue = value
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading combined output: %v\n", err)
}
}()
// Wait for the command to complete
return cmd.Wait()
}
func (t *BlackBorderAnalyzeTask) processOutputLine(line string) *blackBorderAnalyzeData {
match := t.cropDetectOutputRegex.FindStringSubmatch(line)
if match != nil {
//fmt.Println(line)
// Extracted values can be processed further
x1 := match[1]
x2 := match[2]
y1 := match[3]
y2 := match[4]
w := match[5]
h := match[6]
x := match[7]
y := match[8]
//pts := match[9]
t := match[10]
//limit := match[11]
//crop := match[12]
x1v, _ := strconv.Atoi(x1)
x2v, _ := strconv.Atoi(x2)
y1v, _ := strconv.Atoi(y1)
y2v, _ := strconv.Atoi(y2)
wv, _ := strconv.Atoi(w)
hv, _ := strconv.Atoi(h)
xv, _ := strconv.Atoi(x)
yv, _ := strconv.Atoi(y)
timeStamp, _ := strconv.ParseFloat(t, 64)
return &blackBorderAnalyzeData{
x1: x1v,
x2: x2v,
y1: y1v,
y2: y2v,
w: wv,
h: hv,
x: xv,
y: yv,
timeStamp: time.Duration(timeStamp * float64(time.Second)),
}
}
return nil
}
func (t *BlackBorderAnalyzeTask) output(value string) {
fmt.Println(value)
// Append to file
f, err := os.OpenFile(t.logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening file for writing: %v\n", err)
return
}
defer f.Close()
if _, err := f.WriteString(value + "\n"); err != nil {
fmt.Fprintf(os.Stderr, "Error writing to file: %v\n", err)
}
}
type blackBorderAnalyzeData struct {
x1 int
x2 int
y1 int
y2 int
w int
h int
x int
y int
timeStamp time.Duration
}
func (c blackBorderAnalyzeData) String() string {
return fmt.Sprintf("x1: %d, x2: %d, y1: %d, y2: %d, w: %d, h: %d, x: %d, y: %d, timeStamp: %s", c.x1, c.x2, c.y1, c.y2, c.w, c.h, c.x, c.y, c.timeStamp)
}