Compare commits

..

No commits in common. "e3aebdcbb73e979de99232b85d9bd17f677d5176" and "7a82542f91bb033cc84294c582972a8f4b36e765" have entirely different histories.

6 changed files with 63 additions and 163 deletions

View File

@ -997,7 +997,6 @@ type appState struct {
subtitleBurnOutput string
subtitleBurnEnabled bool
subtitleCuesRefresh func()
subtitleTimeOffset float64
}
type mergeClip struct {

View File

@ -9,21 +9,21 @@ alias VideoTools="bash $PROJECT_ROOT/scripts/run.sh"
# Also create a rebuild function for quick rebuilds
VideoToolsRebuild() {
echo "Rebuilding VideoTools..."
echo "🔨 Rebuilding VideoTools..."
bash "$PROJECT_ROOT/scripts/build.sh"
}
# Create a clean function
VideoToolsClean() {
echo "Cleaning VideoTools build artifacts..."
echo "🧹 Cleaning VideoTools build artifacts..."
cd "$PROJECT_ROOT"
go clean -cache -modcache -testcache
rm -f "$PROJECT_ROOT/VideoTools"
echo "Clean complete"
echo "Clean complete"
}
echo "════════════════════════════════════════════════════════════════"
echo "VideoTools Commands Available"
echo "VideoTools Commands Available"
echo "════════════════════════════════════════════════════════════════"
echo ""
echo "Commands:"

View File

@ -17,49 +17,49 @@ echo ""
# Check if go is installed
if ! command -v go &> /dev/null; then
echo "ERROR: Go is not installed. Please install Go 1.21 or later."
echo "ERROR: Go is not installed. Please install Go 1.21 or later."
exit 1
fi
echo "Go version:"
echo "📦 Go version:"
go version
echo ""
# Change to project directory
cd "$PROJECT_ROOT"
echo "Cleaning previous builds and cache..."
echo "🧹 Cleaning previous builds and cache..."
go clean -cache -testcache 2>/dev/null || true
rm -f "$BUILD_OUTPUT" 2>/dev/null || true
# Also clear build cache directory to avoid permission issues
rm -rf "${GOCACHE:-$HOME/.cache/go-build}" 2>/dev/null || true
echo "Cache cleaned"
echo "Cache cleaned"
echo ""
echo "Downloading and verifying dependencies (skips if already cached)..."
echo "⬇️ Downloading and verifying dependencies (skips if already cached)..."
if go list -m all >/dev/null 2>&1; then
echo "Dependencies already present"
echo "Dependencies already present"
else
if go mod download && go mod verify; then
echo "Dependencies downloaded and verified"
echo "Dependencies downloaded and verified"
else
echo "Failed to download/verify modules. Check network/GOPROXY or try again."
echo "Failed to download/verify modules. Check network/GOPROXY or try again."
exit 1
fi
fi
echo ""
echo "Building VideoTools..."
echo "🔨 Building VideoTools..."
# Fyne needs cgo for GLFW/OpenGL bindings; build with CGO enabled.
export CGO_ENABLED=1
export GOCACHE="$PROJECT_ROOT/.cache/go-build"
export GOMODCACHE="$PROJECT_ROOT/.cache/go-mod"
mkdir -p "$GOCACHE" "$GOMODCACHE"
if go build -o "$BUILD_OUTPUT" .; then
echo "Build successful! (VideoTools $APP_VERSION)"
echo "Build successful! (VideoTools $APP_VERSION)"
echo ""
echo "════════════════════════════════════════════════════════════════"
echo "BUILD COMPLETE - $APP_VERSION"
echo "BUILD COMPLETE - $APP_VERSION"
echo "════════════════════════════════════════════════════════════════"
echo ""
echo "Output: $BUILD_OUTPUT"
@ -74,7 +74,7 @@ if go build -o "$BUILD_OUTPUT" .; then
echo " VideoTools"
echo ""
else
echo "Build failed! (VideoTools $APP_VERSION)"
echo "Build failed! (VideoTools $APP_VERSION)"
echo "Diagnostics: version=$APP_VERSION os=$(uname -s) arch=$(uname -m) go=$(go version | awk '{print $3}')"
echo ""
echo "Help: check the Go error messages above."

View File

@ -15,17 +15,17 @@ echo ""
# Check if go is installed
if ! command -v go &> /dev/null; then
echo "ERROR: Go is not installed. Please install Go 1.21 or later."
echo "ERROR: Go is not installed. Please install Go 1.21 or later."
exit 1
fi
echo "Go version:"
echo "📦 Go version:"
go version
echo ""
# Check if MinGW-w64 is installed
if ! command -v x86_64-w64-mingw32-gcc &> /dev/null; then
echo "ERROR: MinGW-w64 cross-compiler not found!"
echo "ERROR: MinGW-w64 cross-compiler not found!"
echo ""
echo "To install on Fedora/RHEL:"
echo " sudo dnf install mingw64-gcc mingw64-winpthreads-static"
@ -36,26 +36,26 @@ if ! command -v x86_64-w64-mingw32-gcc &> /dev/null; then
exit 1
fi
echo "MinGW-w64 detected:"
echo "🔧 MinGW-w64 detected:"
x86_64-w64-mingw32-gcc --version | head -1
echo ""
# Change to project directory
cd "$PROJECT_ROOT"
echo "Cleaning previous Windows builds..."
echo "🧹 Cleaning previous Windows builds..."
rm -f "$BUILD_OUTPUT" 2>/dev/null || true
rm -rf "$DIST_DIR" 2>/dev/null || true
echo "Previous builds cleaned"
echo "Previous builds cleaned"
echo ""
echo "Downloading and verifying dependencies..."
echo "⬇️ Downloading and verifying dependencies..."
go mod download
go mod verify
echo "Dependencies verified"
echo "Dependencies verified"
echo ""
echo "Cross-compiling for Windows (amd64)..."
echo "🔨 Cross-compiling for Windows (amd64)..."
echo " Target: windows/amd64"
echo " Compiler: x86_64-w64-mingw32-gcc"
echo ""
@ -73,27 +73,27 @@ export CXX=x86_64-w64-mingw32-g++
LDFLAGS="-H windowsgui -s -w"
if go build -ldflags="$LDFLAGS" -o "$BUILD_OUTPUT" .; then
echo "Cross-compilation successful!"
echo "Cross-compilation successful!"
echo ""
else
echo "Build failed!"
echo "Build failed!"
exit 1
fi
echo "Creating distribution package..."
echo "📦 Creating distribution package..."
mkdir -p "$DIST_DIR"
# Copy executable
cp "$BUILD_OUTPUT" "$DIST_DIR/"
echo "Copied VideoTools.exe"
echo "Copied VideoTools.exe"
# Copy documentation
cp README.md "$DIST_DIR/" 2>/dev/null || echo "WARNING: README.md not found"
cp LICENSE "$DIST_DIR/" 2>/dev/null || echo "WARNING: LICENSE not found"
cp README.md "$DIST_DIR/" 2>/dev/null || echo "⚠️ README.md not found"
cp LICENSE "$DIST_DIR/" 2>/dev/null || echo "⚠️ LICENSE not found"
# Download and bundle FFmpeg automatically
if [ ! -f "ffmpeg.exe" ]; then
echo "FFmpeg not found locally, downloading..."
echo "📥 FFmpeg not found locally, downloading..."
FFMPEG_URL="https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip"
FFMPEG_ZIP="$PROJECT_ROOT/ffmpeg-windows.zip"
@ -102,14 +102,14 @@ if [ ! -f "ffmpeg.exe" ]; then
elif command -v curl &> /dev/null; then
curl -L "$FFMPEG_URL" -o "$FFMPEG_ZIP" --progress-bar
else
echo "WARNING: wget or curl not found. Cannot download FFmpeg automatically."
echo "⚠️ wget or curl not found. Cannot download FFmpeg automatically."
echo " Please download manually from: $FFMPEG_URL"
echo " Extract ffmpeg.exe and ffprobe.exe to project root"
echo ""
fi
if [ -f "$FFMPEG_ZIP" ]; then
echo "Extracting FFmpeg..."
echo "📦 Extracting FFmpeg..."
unzip -q "$FFMPEG_ZIP" "*/bin/ffmpeg.exe" "*/bin/ffprobe.exe" -d "$PROJECT_ROOT/ffmpeg-temp"
# Find and copy the executables (they're nested in a versioned directory)
@ -118,28 +118,28 @@ if [ ! -f "ffmpeg.exe" ]; then
# Cleanup
rm -rf "$PROJECT_ROOT/ffmpeg-temp" "$FFMPEG_ZIP"
echo "FFmpeg downloaded and extracted"
echo "FFmpeg downloaded and extracted"
fi
fi
# Bundle FFmpeg with the distribution
if [ -f "ffmpeg.exe" ]; then
cp ffmpeg.exe "$DIST_DIR/"
echo "Bundled ffmpeg.exe"
echo "Bundled ffmpeg.exe"
else
echo "WARNING: ffmpeg.exe not found - distribution will require separate FFmpeg installation"
echo "⚠️ ffmpeg.exe not found - distribution will require separate FFmpeg installation"
fi
if [ -f "ffprobe.exe" ]; then
cp ffprobe.exe "$DIST_DIR/"
echo "Bundled ffprobe.exe"
echo "Bundled ffprobe.exe"
else
echo "WARNING: ffprobe.exe not found"
echo "⚠️ ffprobe.exe not found"
fi
echo ""
echo "════════════════════════════════════════════════════════════════"
echo "WINDOWS BUILD COMPLETE"
echo "WINDOWS BUILD COMPLETE"
echo "════════════════════════════════════════════════════════════════"
echo ""
echo "Output directory: $DIST_DIR"

View File

@ -15,23 +15,23 @@ case "$PLATFORM" in
Linux*) OS="Linux" ;;
Darwin*) OS="macOS" ;;
CYGWIN*|MINGW*|MSYS*) OS="Windows" ;;
*) echo "Unknown platform: $PLATFORM"; exit 1 ;;
*) echo "Unknown platform: $PLATFORM"; exit 1 ;;
esac
echo "════════════════════════════════════════════════════════════════"
echo " VideoTools ${OS} Build"
echo "════════════════════════════════════════════════════════════════"
echo ""
echo "Detected platform: $OS"
echo "🔍 Detected platform: $OS"
echo ""
# Go check
if ! command -v go >/dev/null 2>&1; then
echo "ERROR: Go is not installed. Please install Go 1.21+ (go version currently missing)."
echo "ERROR: Go is not installed. Please install Go 1.21+ (go version currently missing)."
exit 1
fi
echo "Go version:"
echo "📦 Go version:"
go version
echo ""
@ -50,26 +50,26 @@ case "$OS" in
echo ""
cd "$PROJECT_ROOT"
echo "Cleaning previous builds..."
echo "🧹 Cleaning previous builds..."
rm -f VideoTools.exe 2>/dev/null || true
# Clear Go cache to avoid permission issues
go clean -cache -modcache -testcache 2>/dev/null || true
echo "Cache cleaned"
echo "Cache cleaned"
echo ""
echo "Downloading dependencies..."
echo "⬇️ Downloading dependencies..."
go mod download
echo "Dependencies downloaded"
echo "Dependencies downloaded"
echo ""
echo "Building VideoTools $APP_VERSION for Windows..."
echo "🔨 Building VideoTools $APP_VERSION for Windows..."
export CGO_ENABLED=1
if go build -ldflags="-H windowsgui -s -w" -o VideoTools.exe .; then
echo "Build successful! (VideoTools $APP_VERSION)"
echo "Build successful! (VideoTools $APP_VERSION)"
echo ""
if [ -f "setup-windows.bat" ]; then
echo "════════════════════════════════════════════════════════════════"
echo "BUILD COMPLETE - $APP_VERSION"
echo "BUILD COMPLETE - $APP_VERSION"
echo "════════════════════════════════════════════════════════════════"
echo ""
echo "Output: VideoTools.exe"
@ -93,11 +93,11 @@ case "$OS" in
echo "You can skip if FFmpeg is already installed elsewhere."
fi
else
echo "Build complete: VideoTools.exe"
echo "Build complete: VideoTools.exe"
diagnostics
fi
else
echo "Build failed! (VideoTools $APP_VERSION)"
echo "Build failed! (VideoTools $APP_VERSION)"
diagnostics
echo ""
echo "Help: check the Go error messages above."

View File

@ -37,11 +37,10 @@ type subtitleCue struct {
}
type subtitlesConfig struct {
OutputMode string `json:"outputMode"`
ModelPath string `json:"modelPath"`
BackendPath string `json:"backendPath"`
BurnOutput string `json:"burnOutput"`
TimeOffset float64 `json:"timeOffset"`
OutputMode string `json:"outputMode"`
ModelPath string `json:"modelPath"`
BackendPath string `json:"backendPath"`
BurnOutput string `json:"burnOutput"`
}
func defaultSubtitlesConfig() subtitlesConfig {
@ -86,7 +85,6 @@ func (s *appState) applySubtitlesConfig(cfg subtitlesConfig) {
s.subtitleModelPath = cfg.ModelPath
s.subtitleBackendPath = cfg.BackendPath
s.subtitleBurnOutput = cfg.BurnOutput
s.subtitleTimeOffset = cfg.TimeOffset
}
func (s *appState) persistSubtitlesConfig() {
@ -95,7 +93,6 @@ func (s *appState) persistSubtitlesConfig() {
ModelPath: s.subtitleModelPath,
BackendPath: s.subtitleBackendPath,
BurnOutput: s.subtitleBurnOutput,
TimeOffset: s.subtitleTimeOffset,
}
if err := savePersistedSubtitlesConfig(cfg); err != nil {
logging.Debug(logging.CatSystem, "failed to persist subtitles config: %v", err)
@ -138,14 +135,14 @@ func buildSubtitlesView(state *appState) fyne.CanvasObject {
bottomBar := moduleFooter(subtitlesColor, layout.NewSpacer(), state.statsBar)
videoEntry := widget.NewEntry()
videoEntry.SetPlaceHolder("Video file path")
videoEntry.SetPlaceHolder("Video file path (drag and drop works here)")
videoEntry.SetText(state.subtitleVideoPath)
videoEntry.OnChanged = func(val string) {
state.subtitleVideoPath = strings.TrimSpace(val)
}
subtitleEntry := widget.NewEntry()
subtitleEntry.SetPlaceHolder("Subtitle file (.srt or .vtt)")
subtitleEntry.SetPlaceHolder("Subtitle file path (.srt or .vtt)")
subtitleEntry.SetText(state.subtitleFilePath)
subtitleEntry.OnChanged = func(val string) {
state.subtitleFilePath = strings.TrimSpace(val)
@ -344,71 +341,11 @@ func buildSubtitlesView(state *appState) fyne.CanvasObject {
})
applyBtn.Importance = widget.HighImportance
browseVideoBtn := widget.NewButton("Browse", func() {
dialog.ShowFileOpen(func(file fyne.URIReadCloser, err error) {
if err != nil || file == nil {
return
}
defer file.Close()
path := file.URI().Path()
state.subtitleVideoPath = path
videoEntry.SetText(path)
}, state.window)
})
browseSubtitleBtn := widget.NewButton("Browse", func() {
dialog.ShowFileOpen(func(file fyne.URIReadCloser, err error) {
if err != nil || file == nil {
return
}
defer file.Close()
path := file.URI().Path()
if err := state.loadSubtitleFile(path); err != nil {
state.setSubtitleStatus(err.Error())
return
}
subtitleEntry.SetText(path)
rebuildCues()
}, state.window)
})
offsetEntry := widget.NewEntry()
offsetEntry.SetPlaceHolder("0.0")
offsetEntry.SetText(fmt.Sprintf("%.2f", state.subtitleTimeOffset))
offsetEntry.OnChanged = func(val string) {
if offset, err := strconv.ParseFloat(strings.TrimSpace(val), 64); err == nil {
state.subtitleTimeOffset = offset
state.persistSubtitlesConfig()
}
}
applyOffsetBtn := widget.NewButton("Apply Offset", func() {
state.applySubtitleTimeOffset(state.subtitleTimeOffset)
})
applyOffsetBtn.Importance = widget.HighImportance
offsetPlus1Btn := widget.NewButton("+1s", func() {
state.applySubtitleTimeOffset(1.0)
})
offsetMinus1Btn := widget.NewButton("-1s", func() {
state.applySubtitleTimeOffset(-1.0)
})
offsetPlus01Btn := widget.NewButton("+0.1s", func() {
state.applySubtitleTimeOffset(0.1)
})
offsetMinus01Btn := widget.NewButton("-0.1s", func() {
state.applySubtitleTimeOffset(-0.1)
})
applyControls := func() {
outputModeSelect.SetSelected(state.subtitleOutputMode)
backendEntry.SetText(state.subtitleBackendPath)
modelEntry.SetText(state.subtitleModelPath)
outputEntry.SetText(state.subtitleBurnOutput)
offsetEntry.SetText(fmt.Sprintf("%.2f", state.subtitleTimeOffset))
}
loadCfgBtn := widget.NewButton("Load Config", func() {
@ -431,7 +368,6 @@ func buildSubtitlesView(state *appState) fyne.CanvasObject {
ModelPath: state.subtitleModelPath,
BackendPath: state.subtitleBackendPath,
BurnOutput: state.subtitleBurnOutput,
TimeOffset: state.subtitleTimeOffset,
}
if err := savePersistedSubtitlesConfig(cfg); err != nil {
dialog.ShowError(fmt.Errorf("failed to save config: %w", err), state.window)
@ -449,25 +385,16 @@ func buildSubtitlesView(state *appState) fyne.CanvasObject {
left := container.NewVBox(
widget.NewLabelWithStyle("Sources", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
container.NewBorder(nil, nil, nil, browseVideoBtn, ui.NewDroppable(videoEntry, handleDrop)),
container.NewBorder(nil, nil, nil, browseSubtitleBtn, ui.NewDroppable(subtitleEntry, handleDrop)),
widget.NewSeparator(),
widget.NewLabelWithStyle("Timing Adjustment", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel("Shift all subtitle times by offset (seconds):"),
offsetEntry,
container.NewHBox(offsetMinus1Btn, offsetMinus01Btn, offsetPlus01Btn, offsetPlus1Btn),
applyOffsetBtn,
widget.NewSeparator(),
ui.NewDroppable(videoEntry, handleDrop),
ui.NewDroppable(subtitleEntry, handleDrop),
widget.NewLabelWithStyle("Offline Speech-to-Text (whisper.cpp)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
backendEntry,
modelEntry,
container.NewHBox(generateBtn),
widget.NewSeparator(),
widget.NewLabelWithStyle("Output", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
outputModeSelect,
outputEntry,
applyBtn,
widget.NewSeparator(),
widget.NewLabelWithStyle("Status", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
statusLabel,
widget.NewSeparator(),
@ -488,8 +415,7 @@ func buildSubtitlesView(state *appState) fyne.CanvasObject {
rebuildCues()
content := container.NewGridWithColumns(2, left, right)
droppableContent := ui.NewDroppable(content, handleDrop)
return container.NewBorder(topBar, bottomBar, nil, nil, droppableContent)
return container.NewBorder(topBar, bottomBar, nil, nil, content)
}
func (s *appState) setSubtitleStatus(msg string) {
@ -536,11 +462,7 @@ func (s *appState) handleSubtitlesModuleDrop(items []fyne.URI) {
s.setSubtitleStatus(err.Error())
}
}
// Refresh the view to show the loaded files
if s.active == "subtitles" {
s.showSubtitlesView()
}
s.showSubtitlesView()
}
func (s *appState) loadSubtitleFile(path string) error {
@ -577,27 +499,6 @@ func (s *appState) saveSubtitleFile(path string) error {
return nil
}
func (s *appState) applySubtitleTimeOffset(offsetSeconds float64) {
if len(s.subtitleCues) == 0 {
s.setSubtitleStatus("No subtitle cues to adjust")
return
}
for i := range s.subtitleCues {
s.subtitleCues[i].Start += offsetSeconds
s.subtitleCues[i].End += offsetSeconds
if s.subtitleCues[i].Start < 0 {
s.subtitleCues[i].Start = 0
}
if s.subtitleCues[i].End < 0 {
s.subtitleCues[i].End = 0
}
}
if s.subtitleCuesRefresh != nil {
s.subtitleCuesRefresh()
}
s.setSubtitleStatus(fmt.Sprintf("Applied %.2fs offset to %d subtitle cues", offsetSeconds, len(s.subtitleCues)))
}
func (s *appState) generateSubtitlesFromSpeech() {
videoPath := strings.TrimSpace(s.subtitleVideoPath)
if videoPath == "" {