updated for multi platform

This commit is contained in:
Linrador 2026-06-23 08:49:37 +02:00
parent 0ac5f369f1
commit 0411025384
17 changed files with 197 additions and 141 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ backend/nsfwapp.exe
backend/web/dist
backend/recorder_settings.json
backend/data/pending-autostart/__global__.json
backend/.gocache

View File

@ -19,7 +19,6 @@ import (
"sort"
"strings"
"sync"
"syscall"
"time"
)
@ -617,10 +616,7 @@ func extractVideoFrameAt(ctx context.Context, outPath string, atSec float64) (im
)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
if out, err := cmd.CombinedOutput(); err != nil {
return nil, appErrorf("ffmpeg fehlgeschlagen: %v: %s", err, strings.TrimSpace(string(out)))
@ -756,10 +752,7 @@ func extractVideoFramesBatch(
pattern,
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
expectedFrames := int(math.Ceil(durationSec / float64(intervalSeconds)))
if expectedFrames < 1 {

View File

@ -10,7 +10,6 @@ import (
"os/exec"
"path/filepath"
"strings"
"syscall"
)
const (
@ -152,10 +151,7 @@ func generatePreviewSpriteJPG(
)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
var stderr bytes.Buffer
cmd.Stderr = &stderr

View File

@ -1,8 +1,16 @@
@echo off
setlocal
cd /d "%~dp0"
set "LOCAL_GOCACHE=%CD%\.gocache-build"
if exist "%LOCAL_GOCACHE%" rmdir /s /q "%LOCAL_GOCACHE%" >nul 2>nul
set "GOCACHE=%LOCAL_GOCACHE%"
echo Baue Windows x64...
if exist nsfwapp.exe del /q nsfwapp.exe >nul 2>nul
set GOOS=windows
set GOARCH=amd64
set CGO_ENABLED=1
@ -13,10 +21,25 @@ if errorlevel 1 goto error
echo.
echo Baue Debian/Linux x64 via WSL...
for /f "delims=" %%i in ('wsl wslpath -a "%CD%"') do set "WSL_CWD=%%i"
if exist nsfwapp-linux-amd64 del /q nsfwapp-linux-amd64 >nul 2>nul
wsl --cd "%WSL_CWD%" bash -lc "GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o nsfwapp-linux-amd64"
where wsl >nul 2>nul
if errorlevel 1 goto wsl_missing
set "WSL_CWD="
for /f "delims=" %%i in ('wsl wslpath -a "%CD%" 2^>nul') do set "WSL_CWD=%%i"
if "%WSL_CWD%"=="" goto wsl_path_error
if not "%WSL_CWD:~0,1%"=="/" goto wsl_missing
wsl bash -lc "command -v go >/dev/null 2>&1"
if errorlevel 1 goto wsl_go_missing
wsl bash -lc "command -v gcc >/dev/null 2>&1 && command -v pkg-config >/dev/null 2>&1"
if errorlevel 1 goto wsl_build_deps_missing
wsl bash -lc "cd ""%WSL_CWD%"" && mkdir -p .gocache-linux && GOOS=linux GOARCH=amd64 CGO_ENABLED=1 GOCACHE=""$PWD/.gocache-linux"" go build -o nsfwapp-linux-amd64"
if errorlevel 1 goto error
if not exist nsfwapp-linux-amd64 goto linux_output_missing
echo.
echo Build erfolgreich.
@ -27,8 +50,72 @@ goto end
:error
echo.
echo Build fehlgeschlagen.
call :cleanup_cache
exit /b 1
:wsl_missing
echo.
echo Debian/Linux-Build braucht eine installierte WSL-Linux-Distribution.
echo Aktuell kann WSL nicht gestartet werden.
echo.
echo Einmalig installieren, z.B.:
echo wsl --install -d Debian
echo.
echo In Debian danach die Build-Abhaengigkeiten installieren:
echo sudo apt update
echo sudo apt install -y build-essential pkg-config libgtk-3-dev libayatana-appindicator3-dev
echo.
call :cleanup_cache
exit /b 1
:wsl_go_missing
echo.
echo In WSL ist Go nicht installiert oder nicht im PATH.
echo.
echo In Debian installieren:
echo sudo apt update
echo sudo apt install -y golang-go
echo.
echo Falls Debian eine zu alte Go-Version installiert, installiere Go passend zu go.mod von go.dev
echo und setze PATH in ~/.profile oder ~/.bashrc, z.B.:
echo export PATH=/usr/local/go/bin:$PATH
echo.
call :cleanup_cache
exit /b 1
:wsl_build_deps_missing
echo.
echo In WSL fehlen Build-Abhaengigkeiten fuer den Linux-Build.
echo.
echo In Debian installieren:
echo sudo apt update
echo sudo apt install -y build-essential pkg-config libgtk-3-dev libayatana-appindicator3-dev
echo.
call :cleanup_cache
exit /b 1
:wsl_path_error
echo.
echo WSL-Pfad konnte nicht aus dem Windows-Pfad erzeugt werden:
echo %CD%
call :cleanup_cache
exit /b 1
:linux_output_missing
echo.
echo Debian/Linux-Build wurde nicht erzeugt:
echo nsfwapp-linux-amd64
echo.
echo Pruefe, ob in WSL eine Debian/Ubuntu-Distribution mit Go und den Build-Abhaengigkeiten installiert ist.
call :cleanup_cache
exit /b 1
:end
call :cleanup_cache
endlocal
pause
exit /b 0
:cleanup_cache
if defined LOCAL_GOCACHE if exist "%LOCAL_GOCACHE%" rmdir /s /q "%LOCAL_GOCACHE%" >nul 2>nul
if exist "%CD%\.gocache-linux" rmdir /s /q "%CD%\.gocache-linux" >nul 2>nul
exit /b 0

View File

@ -0,0 +1,9 @@
//go:build !windows
package main
import "os/exec"
func hideCommandWindow(cmd *exec.Cmd) {
_ = cmd
}

View File

@ -0,0 +1,19 @@
//go:build windows
package main
import (
"os/exec"
"syscall"
)
func hideCommandWindow(cmd *exec.Cmd) {
if cmd == nil {
return
}
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
}

46
backend/file_ops_other.go Normal file
View File

@ -0,0 +1,46 @@
//go:build !windows
package main
import (
"io"
"os"
)
func moveFile(src, dst string) error {
if err := os.Rename(src, dst); err == nil {
return nil
} else {
in, err2 := os.Open(src)
if err2 != nil {
return err
}
defer in.Close()
out, err2 := os.Create(dst)
if err2 != nil {
return err
}
if _, err2 := io.Copy(out, in); err2 != nil {
_ = out.Close()
return err2
}
if err2 := out.Close(); err2 != nil {
return err2
}
return os.Remove(src)
}
}
func isSharingViolation(err error) bool {
_ = err
return false
}
func removeWithRetry(path string) error {
return os.Remove(path)
}
func renameWithRetry(oldPath, newPath string) error {
return os.Rename(oldPath, newPath)
}

View File

@ -17,7 +17,6 @@ import (
"regexp"
"strings"
"sync"
"syscall"
"time"
)
@ -571,10 +570,7 @@ func startPreviewHLS(ctx context.Context, job *RecordJob, m3u8URL, previewDir, h
)
cmd := exec.CommandContext(ctx, ffmpegPath, hqArgs...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
var stderr bytes.Buffer
cmd.Stderr = &stderr
@ -868,10 +864,7 @@ func recordPreviewLiveFMP4(w http.ResponseWriter, r *http.Request) {
)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {

View File

@ -21,7 +21,6 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/grafov/m3u8"
@ -319,10 +318,7 @@ func probeVideoProps(ctx context.Context, filePath string) (w int, h int, fps fl
"-of", "json",
filePath,
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
out, err := cmd.Output()
if err != nil {
@ -543,10 +539,7 @@ func durationSecondsCached(ctx context.Context, path string) (float64, error) {
"-of", "default=noprint_wrappers=1:nokey=1",
path,
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
out, err := cmd.Output()
if err == nil {
@ -562,10 +555,7 @@ func durationSecondsCached(ctx context.Context, path string) (float64, error) {
// 2) Fallback: ffmpeg -i "Duration: HH:MM:SS.xx" parsen
cmd2 := exec.CommandContext(ctx, ffmpegPath, "-i", path)
cmd2.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd2)
b, _ := cmd2.CombinedOutput() // ffmpeg liefert hier oft ExitCode!=0, Output ist trotzdem da
text := string(b)
@ -1040,10 +1030,7 @@ func runRemuxTSToMP4AttemptWithProgress(
)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {
@ -1257,10 +1244,7 @@ func ensureFastStartMP4(path string) error {
"-movflags", "+faststart",
tmp,
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
var stderr bytes.Buffer
cmd.Stderr = &stderr
@ -1877,10 +1861,7 @@ func reencodeTSToMP4(ctx context.Context, tsPath, mp4Path string) error {
)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
var stderr bytes.Buffer
cmd.Stdout = io.Discard

BIN
backend/nsfwapp-linux-amd64 Normal file

Binary file not shown.

View File

@ -30,7 +30,6 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"time"
"golang.org/x/image/font"
@ -1256,10 +1255,7 @@ func extractLastFrameJPG(path string) ([]byte, error) {
"-f", "image2pipe",
"pipe:1",
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
var out bytes.Buffer
var stderr bytes.Buffer
@ -1298,10 +1294,7 @@ func extractFrameAtTimeJPG(path string, seconds float64) ([]byte, error) {
"-f", "image2pipe",
"pipe:1",
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
var out bytes.Buffer
var stderr bytes.Buffer
@ -1345,10 +1338,7 @@ func extractLastFrameJPGScaled(path string, width int, quality int) ([]byte, err
"-f", "image2pipe",
"pipe:1",
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
var out bytes.Buffer
var stderr bytes.Buffer
@ -1386,10 +1376,7 @@ func extractFirstFrameJPGScaled(path string, width int, quality int) ([]byte, er
"-f", "image2pipe",
"pipe:1",
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
var out bytes.Buffer
var stderr bytes.Buffer
@ -1441,10 +1428,7 @@ func extractLiveFrameFromM3U8ThumbJPG(ctx context.Context, m3u8URL, cookie, user
)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
var out bytes.Buffer
var stderr bytes.Buffer

View File

@ -15,7 +15,6 @@ import (
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/grafov/m3u8"
@ -1030,10 +1029,7 @@ func muxAudioVideoSegmentFiles(
}
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
var stderr bytes.Buffer
cmd.Stdout = io.Discard
@ -1578,10 +1574,7 @@ func handleM3U8Mode(
}
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
var stderr bytes.Buffer
cmd.Stdout = io.Discard

View File

@ -71,10 +71,7 @@ func showAppNotification(title, message string) error {
"-Command", ps,
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
return cmd.Run()
@ -577,10 +574,7 @@ func startAIServer(ctx context.Context) (*aiServerProcess, error) {
cmd.Stderr = logWriter
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
}
if err := cmd.Start(); err != nil {

View File

@ -15,7 +15,6 @@ import (
"sort"
"strconv"
"strings"
"syscall"
"time"
)
@ -603,10 +602,7 @@ func splitSingleSegment(
}
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {

View File

@ -14,7 +14,6 @@ import (
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
)
@ -123,10 +122,7 @@ func videoHasAudioStream(ctx context.Context, srcPath string) bool {
"-of", "csv=p=0",
srcPath,
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
out, err := cmd.Output()
if err != nil {
@ -238,10 +234,7 @@ func generateTeaserChunkMP4(
args = append(args, "-movflags", "+faststart", tmp)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
var stderr bytes.Buffer
cmd.Stderr = &stderr
@ -409,10 +402,7 @@ func generateTeaserPreviewMP4WithProgress(
args = append(args, "-movflags", "+faststart", tmp)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
}
hideCommandWindow(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {
@ -571,10 +561,7 @@ func generateTeaserMP4(ctx context.Context, srcPath, outPath string, startSec, d
)
cmd := exec.CommandContext(ctx, ffmpegPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
if out, err := cmd.CombinedOutput(); err != nil {
_ = os.Remove(tmp)

View File

@ -22,7 +22,6 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"time"
)
@ -966,10 +965,7 @@ func trainingRunCommandStreaming(
cmdArgs := append([]string{script}, args...)
cmd := exec.CommandContext(ctx, python, cmdArgs...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {
@ -1150,10 +1146,7 @@ func trainingRunCommand(python string, script string, args ...string) (string, e
cmdArgs := append([]string{script}, args...)
cmd := exec.Command(python, cmdArgs...)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
out, err := cmd.CombinedOutput()
return strings.TrimSpace(string(out)), err
@ -4583,10 +4576,7 @@ func trainingExtractFrame(videoPath string, framePath string, second float64) er
framePath,
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
@ -4766,10 +4756,7 @@ func trainingPredictDetector(root string, framePath string) TrainingDetectorPred
"--imgsz", "640",
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
var stdout strings.Builder
var stderr strings.Builder
@ -4942,10 +4929,7 @@ func trainingPredictPose(root string, framePath string) TrainingPosePrediction {
"--imgsz", "640",
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
var stdout strings.Builder
var stderr strings.Builder
@ -6771,10 +6755,7 @@ func trainingProbeDurationSeconds(videoPath string) float64 {
videoPath,
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
out, err := cmd.Output()
if err != nil {

View File

@ -13,7 +13,6 @@ import (
"sort"
"strconv"
"strings"
"syscall"
)
const (
@ -377,10 +376,7 @@ func trainingExtractVideoMAEClipFrames(
filepath.Join(clipDir, "frame_%03d.jpg"),
)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: 0x08000000,
}
hideCommandWindow(cmd)
out, err := cmd.CombinedOutput()
if err != nil {