diff --git a/.gitignore b/.gitignore index e57570e..c5b221d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ backend/nsfwapp.exe backend/web/dist backend/recorder_settings.json backend/data/pending-autostart/__global__.json +backend/.gocache diff --git a/backend/analyze.go b/backend/analyze.go index 9e5f9fc..856e1c9 100644 --- a/backend/analyze.go +++ b/backend/analyze.go @@ -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 { diff --git a/backend/assets_sprite.go b/backend/assets_sprite.go index 64c24b0..2ea8a49 100644 --- a/backend/assets_sprite.go +++ b/backend/assets_sprite.go @@ -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 diff --git a/backend/build.bat b/backend/build.bat index 36408c8..061f2cb 100644 --- a/backend/build.bat +++ b/backend/build.bat @@ -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 \ No newline at end of file +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 diff --git a/backend/exec_hidden_other.go b/backend/exec_hidden_other.go new file mode 100644 index 0000000..d9d7877 --- /dev/null +++ b/backend/exec_hidden_other.go @@ -0,0 +1,9 @@ +//go:build !windows + +package main + +import "os/exec" + +func hideCommandWindow(cmd *exec.Cmd) { + _ = cmd +} diff --git a/backend/exec_hidden_windows.go b/backend/exec_hidden_windows.go new file mode 100644 index 0000000..e9f4528 --- /dev/null +++ b/backend/exec_hidden_windows.go @@ -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 + } +} diff --git a/backend/file_ops_other.go b/backend/file_ops_other.go new file mode 100644 index 0000000..3476523 --- /dev/null +++ b/backend/file_ops_other.go @@ -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) +} diff --git a/backend/live.go b/backend/live.go index 47af5d8..326093e 100644 --- a/backend/live.go +++ b/backend/live.go @@ -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 { diff --git a/backend/main.go b/backend/main.go index f3cc8d8..6600fb6 100644 --- a/backend/main.go +++ b/backend/main.go @@ -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 diff --git a/backend/nsfwapp-linux-amd64 b/backend/nsfwapp-linux-amd64 new file mode 100644 index 0000000..636f4b2 Binary files /dev/null and b/backend/nsfwapp-linux-amd64 differ diff --git a/backend/preview.go b/backend/preview.go index 5d2ce22..2dee809 100644 --- a/backend/preview.go +++ b/backend/preview.go @@ -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 diff --git a/backend/record_stream_hls.go b/backend/record_stream_hls.go index 2c46def..783072c 100644 --- a/backend/record_stream_hls.go +++ b/backend/record_stream_hls.go @@ -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 diff --git a/backend/server.go b/backend/server.go index e743876..c17f496 100644 --- a/backend/server.go +++ b/backend/server.go @@ -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 { diff --git a/backend/split.go b/backend/split.go index a3ad486..af34fb0 100644 --- a/backend/split.go +++ b/backend/split.go @@ -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 { diff --git a/backend/teaser.go b/backend/teaser.go index c12be03..ddfdcd2 100644 --- a/backend/teaser.go +++ b/backend/teaser.go @@ -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) diff --git a/backend/training.go b/backend/training.go index fb4f5e3..4314ec2 100644 --- a/backend/training.go +++ b/backend/training.go @@ -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 { diff --git a/backend/training_videomae.go b/backend/training_videomae.go index 7c95d45..19fc08e 100644 --- a/backend/training_videomae.go +++ b/backend/training_videomae.go @@ -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 {