Hide ffmpeg console windows on Windows and fix inspect clear button
This commit is contained in:
parent
30146295b1
commit
f900f6804d
|
|
@ -98,6 +98,7 @@ func ProbeVideo(path string) (*VideoSource, error) {
|
||||||
"-show_streams",
|
"-show_streams",
|
||||||
path,
|
path,
|
||||||
)
|
)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -155,6 +156,13 @@ func ProbeVideo(path string) (*VideoSource, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(result.Format.Tags) > 0 {
|
||||||
|
src.Metadata = normalizeTags(result.Format.Tags)
|
||||||
|
if len(src.Metadata) > 0 {
|
||||||
|
src.HasMetadata = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for chapters
|
// Check for chapters
|
||||||
src.HasChapters = len(result.Chapters) > 0
|
src.HasChapters = len(result.Chapters) > 0
|
||||||
|
|
||||||
|
|
@ -252,6 +260,7 @@ func ProbeVideo(path string) (*VideoSource, error) {
|
||||||
"-y",
|
"-y",
|
||||||
coverPath,
|
coverPath,
|
||||||
)
|
)
|
||||||
|
utils.ApplyNoWindow(extractCmd)
|
||||||
if err := extractCmd.Run(); err != nil {
|
if err := extractCmd.Run(); err != nil {
|
||||||
logging.Debug(logging.CatFFMPEG, "failed to extract embedded cover art: %v", err)
|
logging.Debug(logging.CatFFMPEG, "failed to extract embedded cover art: %v", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -271,6 +280,21 @@ func ProbeVideo(path string) (*VideoSource, error) {
|
||||||
return src, nil
|
return src, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeTags(tags map[string]interface{}) map[string]string {
|
||||||
|
normalized := make(map[string]string, len(tags))
|
||||||
|
for k, v := range tags {
|
||||||
|
key := strings.ToLower(strings.TrimSpace(k))
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val := strings.TrimSpace(fmt.Sprint(v))
|
||||||
|
if val != "" {
|
||||||
|
normalized[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
// detectGOPSize attempts to detect GOP size by examining key frames
|
// detectGOPSize attempts to detect GOP size by examining key frames
|
||||||
func detectGOPSize(ctx context.Context, path string) int {
|
func detectGOPSize(ctx context.Context, path string) int {
|
||||||
// Use ffprobe to show frames and look for key_frame markers
|
// Use ffprobe to show frames and look for key_frame markers
|
||||||
|
|
@ -283,6 +307,7 @@ func detectGOPSize(ctx context.Context, path string) int {
|
||||||
"-print_format", "json",
|
"-print_format", "json",
|
||||||
path,
|
path,
|
||||||
)
|
)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
|
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
10
internal/utils/proc_other.go
Normal file
10
internal/utils/proc_other.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "os/exec"
|
||||||
|
|
||||||
|
// ApplyNoWindow is a no-op on non-Windows platforms.
|
||||||
|
func ApplyNoWindow(cmd *exec.Cmd) {
|
||||||
|
_ = cmd
|
||||||
|
}
|
||||||
16
internal/utils/proc_windows.go
Normal file
16
internal/utils/proc_windows.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyNoWindow hides the console window for spawned processes on Windows.
|
||||||
|
func ApplyNoWindow(cmd *exec.Cmd) {
|
||||||
|
if cmd == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
|
}
|
||||||
299
main.go
299
main.go
|
|
@ -128,6 +128,7 @@ type convertConfig struct {
|
||||||
EncoderPreset string // ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
|
EncoderPreset string // ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
|
||||||
CRF string // Manual CRF value (0-51, or empty to use Quality preset)
|
CRF string // Manual CRF value (0-51, or empty to use Quality preset)
|
||||||
BitrateMode string // CRF, CBR, VBR, "Target Size"
|
BitrateMode string // CRF, CBR, VBR, "Target Size"
|
||||||
|
BitratePreset string // Friendly bitrate presets (codec-aware recommendations)
|
||||||
VideoBitrate string // For CBR/VBR modes (e.g., "5000k")
|
VideoBitrate string // For CBR/VBR modes (e.g., "5000k")
|
||||||
TargetFileSize string // Target file size (e.g., "25MB", "100MB") - requires BitrateMode="Target Size"
|
TargetFileSize string // Target file size (e.g., "25MB", "100MB") - requires BitrateMode="Target Size"
|
||||||
TargetResolution string // Source, 720p, 1080p, 1440p, 4K, or custom
|
TargetResolution string // Source, 720p, 1080p, 1440p, 4K, or custom
|
||||||
|
|
@ -682,6 +683,7 @@ func (s *appState) addConvertToQueue() error {
|
||||||
"encoderPreset": cfg.EncoderPreset,
|
"encoderPreset": cfg.EncoderPreset,
|
||||||
"crf": cfg.CRF,
|
"crf": cfg.CRF,
|
||||||
"bitrateMode": cfg.BitrateMode,
|
"bitrateMode": cfg.BitrateMode,
|
||||||
|
"bitratePreset": cfg.BitratePreset,
|
||||||
"videoBitrate": cfg.VideoBitrate,
|
"videoBitrate": cfg.VideoBitrate,
|
||||||
"targetFileSize": cfg.TargetFileSize,
|
"targetFileSize": cfg.TargetFileSize,
|
||||||
"targetResolution": cfg.TargetResolution,
|
"targetResolution": cfg.TargetResolution,
|
||||||
|
|
@ -955,6 +957,7 @@ func (s *appState) batchAddToQueue(paths []string) {
|
||||||
"encoderPreset": s.convert.EncoderPreset,
|
"encoderPreset": s.convert.EncoderPreset,
|
||||||
"crf": s.convert.CRF,
|
"crf": s.convert.CRF,
|
||||||
"bitrateMode": s.convert.BitrateMode,
|
"bitrateMode": s.convert.BitrateMode,
|
||||||
|
"bitratePreset": s.convert.BitratePreset,
|
||||||
"videoBitrate": s.convert.VideoBitrate,
|
"videoBitrate": s.convert.VideoBitrate,
|
||||||
"targetResolution": s.convert.TargetResolution,
|
"targetResolution": s.convert.TargetResolution,
|
||||||
"frameRate": s.convert.FrameRate,
|
"frameRate": s.convert.FrameRate,
|
||||||
|
|
@ -1534,6 +1537,7 @@ func (s *appState) executeConvertJob(ctx context.Context, job *queue.Job, progre
|
||||||
|
|
||||||
// Execute FFmpeg
|
// Execute FFmpeg
|
||||||
cmd := exec.CommandContext(ctx, platformConfig.FFmpegPath, args...)
|
cmd := exec.CommandContext(ctx, platformConfig.FFmpegPath, args...)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create stdout pipe: %w", err)
|
return fmt.Errorf("failed to create stdout pipe: %w", err)
|
||||||
|
|
@ -1798,6 +1802,7 @@ func runGUI() {
|
||||||
EncoderPreset: "medium",
|
EncoderPreset: "medium",
|
||||||
CRF: "", // Empty means use Quality preset
|
CRF: "", // Empty means use Quality preset
|
||||||
BitrateMode: "CRF",
|
BitrateMode: "CRF",
|
||||||
|
BitratePreset: "Manual",
|
||||||
VideoBitrate: "5000k",
|
VideoBitrate: "5000k",
|
||||||
TargetResolution: "Source",
|
TargetResolution: "Source",
|
||||||
FrameRate: "Source",
|
FrameRate: "Source",
|
||||||
|
|
@ -2085,11 +2090,57 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
// Placeholder for updateDVDOptions - will be defined after resolution/framerate selects are created
|
// Placeholder for updateDVDOptions - will be defined after resolution/framerate selects are created
|
||||||
var updateDVDOptions func()
|
var updateDVDOptions func()
|
||||||
|
|
||||||
qualitySelect := widget.NewSelect([]string{"Draft (CRF 28)", "Standard (CRF 23)", "High (CRF 18)", "Lossless"}, func(value string) {
|
// Forward declarations for encoding controls (used in reset/update callbacks)
|
||||||
logging.Debug(logging.CatUI, "quality preset %s", value)
|
var (
|
||||||
|
bitrateModeSelect *widget.Select
|
||||||
|
bitratePresetSelect *widget.Select
|
||||||
|
crfEntry *widget.Entry
|
||||||
|
videoBitrateEntry *widget.Entry
|
||||||
|
targetFileSizeSelect *widget.Select
|
||||||
|
targetFileSizeEntry *widget.Entry
|
||||||
|
qualitySelectSimple *widget.Select
|
||||||
|
qualitySelectAdv *widget.Select
|
||||||
|
qualitySectionSimple fyne.CanvasObject
|
||||||
|
qualitySectionAdv fyne.CanvasObject
|
||||||
|
)
|
||||||
|
|
||||||
|
qualityOptions := []string{"Draft (CRF 28)", "Standard (CRF 23)", "High (CRF 18)", "Lossless"}
|
||||||
|
var syncingQuality bool
|
||||||
|
|
||||||
|
qualitySelectSimple = widget.NewSelect(qualityOptions, func(value string) {
|
||||||
|
if syncingQuality {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
syncingQuality = true
|
||||||
|
logging.Debug(logging.CatUI, "quality preset %s (simple)", value)
|
||||||
state.convert.Quality = value
|
state.convert.Quality = value
|
||||||
|
if qualitySelectAdv != nil {
|
||||||
|
qualitySelectAdv.SetSelected(value)
|
||||||
|
}
|
||||||
|
if updateEncodingControls != nil {
|
||||||
|
updateEncodingControls()
|
||||||
|
}
|
||||||
|
syncingQuality = false
|
||||||
})
|
})
|
||||||
qualitySelect.SetSelected(state.convert.Quality)
|
|
||||||
|
qualitySelectAdv = widget.NewSelect(qualityOptions, func(value string) {
|
||||||
|
if syncingQuality {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
syncingQuality = true
|
||||||
|
logging.Debug(logging.CatUI, "quality preset %s (advanced)", value)
|
||||||
|
state.convert.Quality = value
|
||||||
|
if qualitySelectSimple != nil {
|
||||||
|
qualitySelectSimple.SetSelected(value)
|
||||||
|
}
|
||||||
|
if updateEncodingControls != nil {
|
||||||
|
updateEncodingControls()
|
||||||
|
}
|
||||||
|
syncingQuality = false
|
||||||
|
})
|
||||||
|
|
||||||
|
qualitySelectSimple.SetSelected(state.convert.Quality)
|
||||||
|
qualitySelectAdv.SetSelected(state.convert.Quality)
|
||||||
|
|
||||||
outputEntry := widget.NewEntry()
|
outputEntry := widget.NewEntry()
|
||||||
outputEntry.SetText(state.convert.OutputBase)
|
outputEntry.SetText(state.convert.OutputBase)
|
||||||
|
|
@ -2123,6 +2174,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
autoNameTemplate := widget.NewEntry()
|
autoNameTemplate := widget.NewEntry()
|
||||||
autoNameTemplate.SetPlaceHolder("<actress> - <studio> - <scene>")
|
autoNameTemplate.SetPlaceHolder("<actress> - <studio> - <scene>")
|
||||||
autoNameTemplate.SetText(state.convert.AutoNameTemplate)
|
autoNameTemplate.SetText(state.convert.AutoNameTemplate)
|
||||||
|
|
||||||
autoNameTemplate.OnChanged = func(val string) {
|
autoNameTemplate.OnChanged = func(val string) {
|
||||||
state.convert.AutoNameTemplate = val
|
state.convert.AutoNameTemplate = val
|
||||||
if state.convert.UseAutoNaming {
|
if state.convert.UseAutoNaming {
|
||||||
|
|
@ -2306,6 +2358,9 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
videoCodecSelect := widget.NewSelect([]string{"H.264", "H.265", "VP9", "AV1", "Copy"}, func(value string) {
|
videoCodecSelect := widget.NewSelect([]string{"H.264", "H.265", "VP9", "AV1", "Copy"}, func(value string) {
|
||||||
state.convert.VideoCodec = value
|
state.convert.VideoCodec = value
|
||||||
logging.Debug(logging.CatUI, "video codec set to %s", value)
|
logging.Debug(logging.CatUI, "video codec set to %s", value)
|
||||||
|
if updateQualityVisibility != nil {
|
||||||
|
updateQualityVisibility()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
videoCodecSelect.SetSelected(state.convert.VideoCodec)
|
videoCodecSelect.SetSelected(state.convert.VideoCodec)
|
||||||
|
|
||||||
|
|
@ -2328,6 +2383,8 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updateQualityVisibility func()
|
||||||
|
|
||||||
formatSelect := widget.NewSelect(formatLabels, func(value string) {
|
formatSelect := widget.NewSelect(formatLabels, func(value string) {
|
||||||
for _, opt := range formatOptions {
|
for _, opt := range formatOptions {
|
||||||
if opt.Label == value {
|
if opt.Label == value {
|
||||||
|
|
@ -2344,6 +2401,9 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
state.convert.VideoCodec = newCodec
|
state.convert.VideoCodec = newCodec
|
||||||
videoCodecSelect.SetSelected(newCodec)
|
videoCodecSelect.SetSelected(newCodec)
|
||||||
}
|
}
|
||||||
|
if updateQualityVisibility != nil {
|
||||||
|
updateQualityVisibility()
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2412,6 +2472,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
VideoCodec: "H.264",
|
VideoCodec: "H.264",
|
||||||
EncoderPreset: "medium",
|
EncoderPreset: "medium",
|
||||||
BitrateMode: "CRF",
|
BitrateMode: "CRF",
|
||||||
|
BitratePreset: "Manual",
|
||||||
CRF: "",
|
CRF: "",
|
||||||
VideoBitrate: "",
|
VideoBitrate: "",
|
||||||
TargetResolution: "Source",
|
TargetResolution: "Source",
|
||||||
|
|
@ -2427,11 +2488,24 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
logging.Debug(logging.CatUI, "settings reset to defaults")
|
logging.Debug(logging.CatUI, "settings reset to defaults")
|
||||||
formatSelect.SetSelected(state.convert.SelectedFormat.Label)
|
formatSelect.SetSelected(state.convert.SelectedFormat.Label)
|
||||||
videoCodecSelect.SetSelected(state.convert.VideoCodec)
|
videoCodecSelect.SetSelected(state.convert.VideoCodec)
|
||||||
qualitySelect.SetSelected(state.convert.Quality)
|
qualitySelectSimple.SetSelected(state.convert.Quality)
|
||||||
|
qualitySelectAdv.SetSelected(state.convert.Quality)
|
||||||
simplePresetSelect.SetSelected(state.convert.EncoderPreset)
|
simplePresetSelect.SetSelected(state.convert.EncoderPreset)
|
||||||
|
bitrateModeSelect.SetSelected(state.convert.BitrateMode)
|
||||||
|
bitratePresetSelect.SetSelected(state.convert.BitratePreset)
|
||||||
|
crfEntry.SetText(state.convert.CRF)
|
||||||
|
videoBitrateEntry.SetText(state.convert.VideoBitrate)
|
||||||
|
targetFileSizeSelect.SetSelected("Manual")
|
||||||
|
targetFileSizeEntry.SetText(state.convert.TargetFileSize)
|
||||||
autoNameCheck.SetChecked(state.convert.UseAutoNaming)
|
autoNameCheck.SetChecked(state.convert.UseAutoNaming)
|
||||||
autoNameTemplate.SetText(state.convert.AutoNameTemplate)
|
autoNameTemplate.SetText(state.convert.AutoNameTemplate)
|
||||||
outputEntry.SetText(state.convert.OutputBase)
|
outputEntry.SetText(state.convert.OutputBase)
|
||||||
|
if updateEncodingControls != nil {
|
||||||
|
updateEncodingControls()
|
||||||
|
}
|
||||||
|
if updateQualityVisibility != nil {
|
||||||
|
updateQualityVisibility()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
resetSettingsBtn.Importance = widget.LowImportance
|
resetSettingsBtn.Importance = widget.LowImportance
|
||||||
|
|
||||||
|
|
@ -2461,15 +2535,21 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Shared updater for bitrate/quality UI state; defined later alongside controls
|
||||||
|
var updateEncodingControls func()
|
||||||
|
|
||||||
// Bitrate Mode
|
// Bitrate Mode
|
||||||
bitrateModeSelect := widget.NewSelect([]string{"CRF", "CBR", "VBR", "Target Size"}, func(value string) {
|
bitrateModeSelect = widget.NewSelect([]string{"CRF", "CBR", "VBR", "Target Size"}, func(value string) {
|
||||||
state.convert.BitrateMode = value
|
state.convert.BitrateMode = value
|
||||||
logging.Debug(logging.CatUI, "bitrate mode set to %s", value)
|
logging.Debug(logging.CatUI, "bitrate mode set to %s", value)
|
||||||
|
if updateEncodingControls != nil {
|
||||||
|
updateEncodingControls()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
bitrateModeSelect.SetSelected(state.convert.BitrateMode)
|
bitrateModeSelect.SetSelected(state.convert.BitrateMode)
|
||||||
|
|
||||||
// Manual CRF entry
|
// Manual CRF entry
|
||||||
crfEntry := widget.NewEntry()
|
crfEntry = widget.NewEntry()
|
||||||
crfEntry.SetPlaceHolder("Auto (from Quality preset)")
|
crfEntry.SetPlaceHolder("Auto (from Quality preset)")
|
||||||
crfEntry.SetText(state.convert.CRF)
|
crfEntry.SetText(state.convert.CRF)
|
||||||
crfEntry.OnChanged = func(val string) {
|
crfEntry.OnChanged = func(val string) {
|
||||||
|
|
@ -2477,18 +2557,52 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Video Bitrate entry (for CBR/VBR)
|
// Video Bitrate entry (for CBR/VBR)
|
||||||
videoBitrateEntry := widget.NewEntry()
|
videoBitrateEntry = widget.NewEntry()
|
||||||
videoBitrateEntry.SetPlaceHolder("5000k")
|
videoBitrateEntry.SetPlaceHolder("5000k")
|
||||||
videoBitrateEntry.SetText(state.convert.VideoBitrate)
|
videoBitrateEntry.SetText(state.convert.VideoBitrate)
|
||||||
videoBitrateEntry.OnChanged = func(val string) {
|
videoBitrateEntry.OnChanged = func(val string) {
|
||||||
state.convert.VideoBitrate = val
|
state.convert.VideoBitrate = val
|
||||||
}
|
}
|
||||||
|
|
||||||
// Target File Size with smart presets + manual entry
|
type bitratePreset struct {
|
||||||
targetFileSizeEntry := widget.NewEntry()
|
Label string
|
||||||
targetFileSizeEntry.SetPlaceHolder("e.g., 25MB, 100MB, 8MB")
|
Bitrate string
|
||||||
|
Codec string
|
||||||
|
}
|
||||||
|
|
||||||
var targetFileSizeSelect *widget.Select
|
presets := []bitratePreset{
|
||||||
|
{Label: "Manual", Bitrate: "", Codec: ""},
|
||||||
|
{Label: "AV1 1080p - 1200k (smallest)", Bitrate: "1200k", Codec: "AV1"},
|
||||||
|
{Label: "AV1 1080p - 1400k (sweet spot)", Bitrate: "1400k", Codec: "AV1"},
|
||||||
|
{Label: "AV1 1080p - 1800k (headroom)", Bitrate: "1800k", Codec: "AV1"},
|
||||||
|
{Label: "H.265 1080p - 2000k (balanced)", Bitrate: "2000k", Codec: "H.265"},
|
||||||
|
{Label: "H.265 1080p - 2400k (noisy sources)", Bitrate: "2400k", Codec: "H.265"},
|
||||||
|
{Label: "AV1 4K - 7M (archive)", Bitrate: "7000k", Codec: "AV1"},
|
||||||
|
{Label: "H.265 4K - 9M (fast/Topaz)", Bitrate: "9000k", Codec: "H.265"},
|
||||||
|
}
|
||||||
|
|
||||||
|
bitratePresetLookup := make(map[string]bitratePreset)
|
||||||
|
var bitratePresetLabels []string
|
||||||
|
for _, p := range presets {
|
||||||
|
bitratePresetLookup[p.Label] = p
|
||||||
|
bitratePresetLabels = append(bitratePresetLabels, p.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
var applyBitratePreset func(string)
|
||||||
|
|
||||||
|
bitratePresetSelect = widget.NewSelect(bitratePresetLabels, func(value string) {
|
||||||
|
if applyBitratePreset != nil {
|
||||||
|
applyBitratePreset(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if state.convert.BitratePreset == "" || bitratePresetLookup[state.convert.BitratePreset].Label == "" {
|
||||||
|
state.convert.BitratePreset = "Manual"
|
||||||
|
}
|
||||||
|
bitratePresetSelect.SetSelected(state.convert.BitratePreset)
|
||||||
|
|
||||||
|
// Target File Size with smart presets + manual entry
|
||||||
|
targetFileSizeEntry = widget.NewEntry()
|
||||||
|
targetFileSizeEntry.SetPlaceHolder("e.g., 25MB, 100MB, 8MB")
|
||||||
|
|
||||||
updateTargetSizeOptions := func() {
|
updateTargetSizeOptions := func() {
|
||||||
if src == nil {
|
if src == nil {
|
||||||
|
|
@ -2558,6 +2672,97 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
state.convert.TargetFileSize = val
|
state.convert.TargetFileSize = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encodingHint := widget.NewLabel("")
|
||||||
|
encodingHint.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
|
applyBitratePreset = func(label string) {
|
||||||
|
preset, ok := bitratePresetLookup[label]
|
||||||
|
if !ok {
|
||||||
|
label = "Manual"
|
||||||
|
preset = bitratePresetLookup[label]
|
||||||
|
}
|
||||||
|
|
||||||
|
state.convert.BitratePreset = label
|
||||||
|
|
||||||
|
// Move to CBR for predictable output when a preset is chosen
|
||||||
|
if preset.Bitrate != "" && state.convert.BitrateMode != "CBR" && state.convert.BitrateMode != "VBR" {
|
||||||
|
state.convert.BitrateMode = "CBR"
|
||||||
|
bitrateModeSelect.SetSelected("CBR")
|
||||||
|
}
|
||||||
|
|
||||||
|
if preset.Bitrate != "" {
|
||||||
|
state.convert.VideoBitrate = preset.Bitrate
|
||||||
|
videoBitrateEntry.SetText(preset.Bitrate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust codec to match the preset intent (user can change back)
|
||||||
|
if preset.Codec != "" && state.convert.VideoCodec != preset.Codec {
|
||||||
|
state.convert.VideoCodec = preset.Codec
|
||||||
|
videoCodecSelect.SetSelected(preset.Codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateEncodingControls != nil {
|
||||||
|
updateEncodingControls()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEncodingControls = func() {
|
||||||
|
mode := state.convert.BitrateMode
|
||||||
|
isLossless := state.convert.Quality == "Lossless"
|
||||||
|
|
||||||
|
// Default: enable everything
|
||||||
|
crfEntry.Enable()
|
||||||
|
videoBitrateEntry.Enable()
|
||||||
|
targetFileSizeEntry.Enable()
|
||||||
|
targetFileSizeSelect.Enable()
|
||||||
|
bitratePresetSelect.Enable()
|
||||||
|
|
||||||
|
hint := ""
|
||||||
|
|
||||||
|
if isLossless {
|
||||||
|
// Lossless forces CRF 0; ignore bitrate/preset/target size to reduce confusion
|
||||||
|
if mode != "CRF" {
|
||||||
|
state.convert.BitrateMode = "CRF"
|
||||||
|
bitrateModeSelect.SetSelected("CRF")
|
||||||
|
mode = "CRF"
|
||||||
|
}
|
||||||
|
if crfEntry.Text != "0" {
|
||||||
|
crfEntry.SetText("0")
|
||||||
|
}
|
||||||
|
state.convert.CRF = "0"
|
||||||
|
crfEntry.Disable()
|
||||||
|
videoBitrateEntry.Disable()
|
||||||
|
targetFileSizeEntry.Disable()
|
||||||
|
targetFileSizeSelect.Disable()
|
||||||
|
bitratePresetSelect.Disable()
|
||||||
|
hint = "Lossless forces CRF 0 for H.265/AV1; bitrate and target size are ignored."
|
||||||
|
} else {
|
||||||
|
switch mode {
|
||||||
|
case "CRF", "":
|
||||||
|
videoBitrateEntry.Disable()
|
||||||
|
targetFileSizeEntry.Disable()
|
||||||
|
targetFileSizeSelect.Disable()
|
||||||
|
bitratePresetSelect.Disable()
|
||||||
|
hint = "CRF mode uses the quality preset/CRF only."
|
||||||
|
case "CBR", "VBR":
|
||||||
|
crfEntry.Disable()
|
||||||
|
targetFileSizeEntry.Disable()
|
||||||
|
targetFileSizeSelect.Disable()
|
||||||
|
hint = "Bitrate mode uses the value above; presets auto-fill common choices."
|
||||||
|
case "Target Size":
|
||||||
|
crfEntry.Disable()
|
||||||
|
videoBitrateEntry.Disable()
|
||||||
|
bitratePresetSelect.Disable()
|
||||||
|
targetFileSizeEntry.Enable()
|
||||||
|
targetFileSizeSelect.Enable()
|
||||||
|
hint = "Target size calculates bitrate automatically from duration."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encodingHint.SetText(hint)
|
||||||
|
}
|
||||||
|
updateEncodingControls()
|
||||||
|
|
||||||
// Target Resolution
|
// Target Resolution
|
||||||
resolutionSelect := widget.NewSelect([]string{"Source", "720p", "1080p", "1440p", "4K", "NTSC (720×480)", "PAL (720×576)"}, func(value string) {
|
resolutionSelect := widget.NewSelect([]string{"Source", "720p", "1080p", "1440p", "4K", "NTSC (720×480)", "PAL (720×576)"}, func(value string) {
|
||||||
state.convert.TargetResolution = value
|
state.convert.TargetResolution = value
|
||||||
|
|
@ -2703,6 +2908,35 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
}
|
}
|
||||||
updateDVDOptions()
|
updateDVDOptions()
|
||||||
|
|
||||||
|
qualitySectionSimple = container.NewVBox(
|
||||||
|
widget.NewLabelWithStyle("═══ QUALITY ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||||
|
qualitySelectSimple,
|
||||||
|
)
|
||||||
|
qualitySectionAdv = container.NewVBox(
|
||||||
|
widget.NewLabelWithStyle("Quality Preset", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
|
qualitySelectAdv,
|
||||||
|
)
|
||||||
|
|
||||||
|
updateQualityVisibility = func() {
|
||||||
|
hide := strings.Contains(strings.ToLower(state.convert.SelectedFormat.Label), "h.265") ||
|
||||||
|
strings.EqualFold(state.convert.VideoCodec, "H.265")
|
||||||
|
|
||||||
|
if qualitySectionSimple != nil {
|
||||||
|
if hide {
|
||||||
|
qualitySectionSimple.Hide()
|
||||||
|
} else {
|
||||||
|
qualitySectionSimple.Show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if qualitySectionAdv != nil {
|
||||||
|
if hide {
|
||||||
|
qualitySectionAdv.Hide()
|
||||||
|
} else {
|
||||||
|
qualitySectionAdv.Show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simple mode options - minimal controls, aspect locked to Source
|
// Simple mode options - minimal controls, aspect locked to Source
|
||||||
simpleOptions := container.NewVBox(
|
simpleOptions := container.NewVBox(
|
||||||
widget.NewLabelWithStyle("═══ OUTPUT ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("═══ OUTPUT ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||||
|
|
@ -2711,13 +2945,9 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
dvdAspectBox, // DVD options appear here when DVD format selected
|
dvdAspectBox, // DVD options appear here when DVD format selected
|
||||||
widget.NewLabelWithStyle("Output Name", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Output Name", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
outputEntry,
|
outputEntry,
|
||||||
autoNameCheck,
|
|
||||||
autoNameTemplate,
|
|
||||||
autoNameHint,
|
|
||||||
outputHint,
|
outputHint,
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
widget.NewLabelWithStyle("═══ QUALITY ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
qualitySectionSimple,
|
||||||
qualitySelect,
|
|
||||||
widget.NewLabelWithStyle("Encoder Speed/Quality", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Encoder Speed/Quality", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||||
widget.NewLabel("Choose slower for better compression, faster for speed"),
|
widget.NewLabel("Choose slower for better compression, faster for speed"),
|
||||||
widget.NewLabelWithStyle("Encoder Preset", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Encoder Preset", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
|
|
@ -2734,9 +2964,6 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
dvdAspectBox, // DVD options appear here when DVD format selected
|
dvdAspectBox, // DVD options appear here when DVD format selected
|
||||||
widget.NewLabelWithStyle("Output Name", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Output Name", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
outputEntry,
|
outputEntry,
|
||||||
autoNameCheck,
|
|
||||||
autoNameTemplate,
|
|
||||||
autoNameHint,
|
|
||||||
outputHint,
|
outputHint,
|
||||||
coverDisplay,
|
coverDisplay,
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
|
|
@ -2747,14 +2974,16 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
widget.NewLabelWithStyle("Encoder Preset (speed vs quality)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Encoder Preset (speed vs quality)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
encoderPresetSelect,
|
encoderPresetSelect,
|
||||||
encoderPresetHint,
|
encoderPresetHint,
|
||||||
widget.NewLabelWithStyle("Quality Preset", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
qualitySectionAdv,
|
||||||
qualitySelect,
|
|
||||||
widget.NewLabelWithStyle("Bitrate Mode", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Bitrate Mode", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
bitrateModeSelect,
|
bitrateModeSelect,
|
||||||
widget.NewLabelWithStyle("Manual CRF (overrides Quality preset)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Manual CRF (overrides Quality preset)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
crfEntry,
|
crfEntry,
|
||||||
widget.NewLabelWithStyle("Video Bitrate (for CBR/VBR)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Video Bitrate (for CBR/VBR)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
videoBitrateEntry,
|
videoBitrateEntry,
|
||||||
|
widget.NewLabelWithStyle("Recommended Bitrate Preset", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
|
bitratePresetSelect,
|
||||||
|
encodingHint,
|
||||||
widget.NewLabelWithStyle("Target File Size", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Target File Size", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
targetFileSizeSelect,
|
targetFileSizeSelect,
|
||||||
targetFileSizeEntry,
|
targetFileSizeEntry,
|
||||||
|
|
@ -2815,6 +3044,10 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
advancedScrollBox := container.NewVScroll(advancedOptions)
|
advancedScrollBox := container.NewVScroll(advancedOptions)
|
||||||
advancedScrollBox.SetMinSize(fyne.NewSize(0, 0))
|
advancedScrollBox.SetMinSize(fyne.NewSize(0, 0))
|
||||||
|
|
||||||
|
if updateQualityVisibility != nil {
|
||||||
|
updateQualityVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
tabs := container.NewAppTabs(
|
tabs := container.NewAppTabs(
|
||||||
container.NewTabItem("Simple", simpleScrollBox),
|
container.NewTabItem("Simple", simpleScrollBox),
|
||||||
container.NewTabItem("Advanced", advancedScrollBox),
|
container.NewTabItem("Advanced", advancedScrollBox),
|
||||||
|
|
@ -2881,10 +3114,18 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
tabs.SelectIndex(0) // Select Simple tab
|
tabs.SelectIndex(0) // Select Simple tab
|
||||||
state.convert.Mode = "Simple"
|
state.convert.Mode = "Simple"
|
||||||
formatSelect.SetSelected("MP4 (H.264)")
|
formatSelect.SetSelected("MP4 (H.264)")
|
||||||
qualitySelect.SetSelected("Standard (CRF 23)")
|
state.convert.Quality = "Standard (CRF 23)"
|
||||||
|
qualitySelectSimple.SetSelected("Standard (CRF 23)")
|
||||||
|
qualitySelectAdv.SetSelected("Standard (CRF 23)")
|
||||||
aspectOptions.SetSelected("Auto")
|
aspectOptions.SetSelected("Auto")
|
||||||
targetAspectSelect.SetSelected("Source")
|
targetAspectSelect.SetSelected("Source")
|
||||||
updateAspectBoxVisibility()
|
updateAspectBoxVisibility()
|
||||||
|
if updateEncodingControls != nil {
|
||||||
|
updateEncodingControls()
|
||||||
|
}
|
||||||
|
if updateQualityVisibility != nil {
|
||||||
|
updateQualityVisibility()
|
||||||
|
}
|
||||||
logging.Debug(logging.CatUI, "convert settings reset to defaults")
|
logging.Debug(logging.CatUI, "convert settings reset to defaults")
|
||||||
})
|
})
|
||||||
statusLabel := widget.NewLabel("")
|
statusLabel := widget.NewLabel("")
|
||||||
|
|
@ -3790,6 +4031,7 @@ func (p *playSession) runVideo(offset float64) {
|
||||||
"-",
|
"-",
|
||||||
}
|
}
|
||||||
cmd := exec.Command(platformConfig.FFmpegPath, args...)
|
cmd := exec.Command(platformConfig.FFmpegPath, args...)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -3877,6 +4119,7 @@ func (p *playSession) runAudio(offset float64) {
|
||||||
"-f", "s16le",
|
"-f", "s16le",
|
||||||
"-",
|
"-",
|
||||||
)
|
)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -4610,6 +4853,7 @@ func detectBestH264Encoder() string {
|
||||||
|
|
||||||
for _, encoder := range encoders {
|
for _, encoder := range encoders {
|
||||||
cmd := exec.Command(platformConfig.FFmpegPath, "-hide_banner", "-encoders")
|
cmd := exec.Command(platformConfig.FFmpegPath, "-hide_banner", "-encoders")
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Check if encoder is in the output
|
// Check if encoder is in the output
|
||||||
|
|
@ -4622,6 +4866,7 @@ func detectBestH264Encoder() string {
|
||||||
|
|
||||||
// Fallback: check if libx264 is available
|
// Fallback: check if libx264 is available
|
||||||
cmd := exec.Command(platformConfig.FFmpegPath, "-hide_banner", "-encoders")
|
cmd := exec.Command(platformConfig.FFmpegPath, "-hide_banner", "-encoders")
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err == nil && (strings.Contains(string(output), " libx264 ") || strings.Contains(string(output), " libx264\n")) {
|
if err == nil && (strings.Contains(string(output), " libx264 ") || strings.Contains(string(output), " libx264\n")) {
|
||||||
logging.Debug(logging.CatFFMPEG, "using software encoder: libx264")
|
logging.Debug(logging.CatFFMPEG, "using software encoder: libx264")
|
||||||
|
|
@ -4638,6 +4883,7 @@ func detectBestH265Encoder() string {
|
||||||
|
|
||||||
for _, encoder := range encoders {
|
for _, encoder := range encoders {
|
||||||
cmd := exec.Command(platformConfig.FFmpegPath, "-hide_banner", "-encoders")
|
cmd := exec.Command(platformConfig.FFmpegPath, "-hide_banner", "-encoders")
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if strings.Contains(string(output), " "+encoder+" ") || strings.Contains(string(output), " "+encoder+"\n") {
|
if strings.Contains(string(output), " "+encoder+" ") || strings.Contains(string(output), " "+encoder+"\n") {
|
||||||
|
|
@ -4648,6 +4894,7 @@ func detectBestH265Encoder() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(platformConfig.FFmpegPath, "-hide_banner", "-encoders")
|
cmd := exec.Command(platformConfig.FFmpegPath, "-hide_banner", "-encoders")
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err == nil && (strings.Contains(string(output), " libx265 ") || strings.Contains(string(output), " libx265\n")) {
|
if err == nil && (strings.Contains(string(output), " libx265 ") || strings.Contains(string(output), " libx265\n")) {
|
||||||
logging.Debug(logging.CatFFMPEG, "using software encoder: libx265")
|
logging.Debug(logging.CatFFMPEG, "using software encoder: libx265")
|
||||||
|
|
@ -5147,6 +5394,7 @@ func (s *appState) startConvert(status *widget.Label, btn, cancelBtn *widget.But
|
||||||
|
|
||||||
started := time.Now()
|
started := time.Now()
|
||||||
cmd := exec.CommandContext(ctx, platformConfig.FFmpegPath, args...)
|
cmd := exec.CommandContext(ctx, platformConfig.FFmpegPath, args...)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Debug(logging.CatFFMPEG, "convert stdout pipe failed: %v", err)
|
logging.Debug(logging.CatFFMPEG, "convert stdout pipe failed: %v", err)
|
||||||
|
|
@ -5625,6 +5873,7 @@ func (s *appState) generateSnippet() {
|
||||||
args = append(args, outPath)
|
args = append(args, outPath)
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, platformConfig.FFmpegPath, args...)
|
cmd := exec.CommandContext(ctx, platformConfig.FFmpegPath, args...)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
logging.Debug(logging.CatFFMPEG, "snippet command: %s", strings.Join(cmd.Args, " "))
|
logging.Debug(logging.CatFFMPEG, "snippet command: %s", strings.Join(cmd.Args, " "))
|
||||||
|
|
||||||
// Show progress dialog for snippets that need re-encoding (WMV, filters, etc.)
|
// Show progress dialog for snippets that need re-encoding (WMV, filters, etc.)
|
||||||
|
|
@ -5672,6 +5921,7 @@ func capturePreviewFrames(path string, duration float64) ([]string, error) {
|
||||||
"-vf", "scale=640:-1:flags=lanczos,fps=8",
|
"-vf", "scale=640:-1:flags=lanczos,fps=8",
|
||||||
pattern,
|
pattern,
|
||||||
)
|
)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.RemoveAll(dir)
|
os.RemoveAll(dir)
|
||||||
|
|
@ -5776,6 +6026,7 @@ func probeVideo(path string) (*videoSource, error) {
|
||||||
"-show_streams",
|
"-show_streams",
|
||||||
path,
|
path,
|
||||||
)
|
)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -5894,6 +6145,7 @@ func probeVideo(path string) (*videoSource, error) {
|
||||||
"-y",
|
"-y",
|
||||||
coverPath,
|
coverPath,
|
||||||
)
|
)
|
||||||
|
utils.ApplyNoWindow(extractCmd)
|
||||||
if err := extractCmd.Run(); err != nil {
|
if err := extractCmd.Run(); err != nil {
|
||||||
logging.Debug(logging.CatFFMPEG, "failed to extract embedded cover art: %v", err)
|
logging.Debug(logging.CatFFMPEG, "failed to extract embedded cover art: %v", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -5958,6 +6210,7 @@ func detectCrop(path string, duration float64) *CropValues {
|
||||||
"-f", "null",
|
"-f", "null",
|
||||||
"-",
|
"-",
|
||||||
)
|
)
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -6561,7 +6814,7 @@ func buildInspectView(state *appState) fyne.CanvasObject {
|
||||||
})
|
})
|
||||||
clearBtn.Importance = widget.LowImportance
|
clearBtn.Importance = widget.LowImportance
|
||||||
|
|
||||||
instructionsRow := container.NewBorder(nil, nil, nil, clearBtn, instructions)
|
instructionsRow := container.NewBorder(nil, nil, nil, nil, instructions)
|
||||||
|
|
||||||
// File label
|
// File label
|
||||||
fileLabel := widget.NewLabel("No file loaded")
|
fileLabel := widget.NewLabel("No file loaded")
|
||||||
|
|
|
||||||
20
platform.go
20
platform.go
|
|
@ -10,19 +10,20 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
|
"git.leaktechnologies.dev/stu/VideoTools/internal/logging"
|
||||||
|
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PlatformConfig holds platform-specific configuration
|
// PlatformConfig holds platform-specific configuration
|
||||||
type PlatformConfig struct {
|
type PlatformConfig struct {
|
||||||
FFmpegPath string
|
FFmpegPath string
|
||||||
FFprobePath string
|
FFprobePath string
|
||||||
TempDir string
|
TempDir string
|
||||||
HWEncoders []string
|
HWEncoders []string
|
||||||
ExeExtension string
|
ExeExtension string
|
||||||
PathSeparator string
|
PathSeparator string
|
||||||
IsWindows bool
|
IsWindows bool
|
||||||
IsLinux bool
|
IsLinux bool
|
||||||
IsDarwin bool
|
IsDarwin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectPlatform detects the current platform and returns configuration
|
// DetectPlatform detects the current platform and returns configuration
|
||||||
|
|
@ -167,6 +168,7 @@ func detectHardwareEncoders(cfg *PlatformConfig) []string {
|
||||||
|
|
||||||
// Get list of available encoders from ffmpeg
|
// Get list of available encoders from ffmpeg
|
||||||
cmd := exec.Command(cfg.FFmpegPath, "-hide_banner", "-encoders")
|
cmd := exec.Command(cfg.FFmpegPath, "-hide_banner", "-encoders")
|
||||||
|
utils.ApplyNoWindow(cmd)
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Debug(logging.CatSystem, "Failed to query ffmpeg encoders: %v", err)
|
logging.Debug(logging.CatSystem, "Failed to query ffmpeg encoders: %v", err)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user