Add configurable temp directory with SSD hint
This commit is contained in:
parent
91d38a1b3f
commit
2f9995d8f1
1
DONE.md
1
DONE.md
|
|
@ -833,6 +833,7 @@ This file tracks completed features, fixes, and milestones.
|
||||||
- ✅ Hide quality presets when bitrate mode is not CRF
|
- ✅ Hide quality presets when bitrate mode is not CRF
|
||||||
- ✅ Snippet UI now shows Convert Snippet + batch + options with context-sensitive controls
|
- ✅ Snippet UI now shows Convert Snippet + batch + options with context-sensitive controls
|
||||||
- ✅ Reduced module video pane minimum sizes to allow GNOME window snapping
|
- ✅ Reduced module video pane minimum sizes to allow GNOME window snapping
|
||||||
|
- ✅ Added cache/temp directory setting with SSD recommendation and override
|
||||||
- ✅ Snippet defaults now use conversion settings (not Match Source)
|
- ✅ Snippet defaults now use conversion settings (not Match Source)
|
||||||
- ✅ Stabilized video seeking and embedded rendering
|
- ✅ Stabilized video seeking and embedded rendering
|
||||||
- ✅ Improved player window positioning
|
- ✅ Improved player window positioning
|
||||||
|
|
|
||||||
1
TODO.md
1
TODO.md
|
|
@ -66,6 +66,7 @@ This file tracks upcoming features, improvements, and known issues.
|
||||||
- Quality presets hidden when bitrate mode is not CRF
|
- Quality presets hidden when bitrate mode is not CRF
|
||||||
- Snippet UI rearranged into Convert Snippet / Batch / Options with context-sensitive visibility
|
- Snippet UI rearranged into Convert Snippet / Batch / Options with context-sensitive visibility
|
||||||
- Reduce module video pane min sizes to allow GNOME snapping
|
- Reduce module video pane min sizes to allow GNOME snapping
|
||||||
|
- Cache/temp directory setting with SSD recommendation
|
||||||
|
|
||||||
*Last Updated: 2025-12-20*
|
*Last Updated: 2025-12-20*
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ func ProbeVideo(path string) (*VideoSource, error) {
|
||||||
|
|
||||||
// Extract embedded cover art if present
|
// Extract embedded cover art if present
|
||||||
if coverArtStreamIndex >= 0 {
|
if coverArtStreamIndex >= 0 {
|
||||||
coverPath := filepath.Join(os.TempDir(), fmt.Sprintf("videotools-embedded-cover-%d.png", time.Now().UnixNano()))
|
coverPath := filepath.Join(utils.TempDir(), fmt.Sprintf("videotools-embedded-cover-%d.png", time.Now().UnixNano()))
|
||||||
extractCmd := exec.CommandContext(ctx, FFmpegPath,
|
extractCmd := exec.CommandContext(ctx, FFmpegPath,
|
||||||
"-i", path,
|
"-i", path,
|
||||||
"-map", fmt.Sprintf("0:%d", coverArtStreamIndex),
|
"-map", fmt.Sprintf("0:%d", coverArtStreamIndex),
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
|
@ -271,3 +272,25 @@ func LoadAppIcon() fyne.Resource {
|
||||||
logging.Debug(logging.CatUI, "no app icon found in search paths")
|
logging.Debug(logging.CatUI, "no app icon found in search paths")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tempDirOverride atomic.Value
|
||||||
|
|
||||||
|
// SetTempDir overrides the app temp directory (empty string resets to system temp).
|
||||||
|
func SetTempDir(path string) {
|
||||||
|
trimmed := strings.TrimSpace(path)
|
||||||
|
if trimmed == "" {
|
||||||
|
tempDirOverride.Store("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tempDirOverride.Store(trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TempDir returns the app temp directory, falling back to the system temp dir.
|
||||||
|
func TempDir() string {
|
||||||
|
if v := tempDirOverride.Load(); v != nil {
|
||||||
|
if s, ok := v.(string); ok && s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.TempDir()
|
||||||
|
}
|
||||||
|
|
|
||||||
51
main.go
51
main.go
|
|
@ -489,6 +489,7 @@ type convertConfig struct {
|
||||||
AspectHandling string
|
AspectHandling string
|
||||||
OutputAspect string
|
OutputAspect string
|
||||||
AspectUserSet bool // Tracks if user explicitly set OutputAspect
|
AspectUserSet bool // Tracks if user explicitly set OutputAspect
|
||||||
|
TempDir string // Optional temp/cache directory override
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c convertConfig) OutputFile() string {
|
func (c convertConfig) OutputFile() string {
|
||||||
|
|
@ -553,6 +554,7 @@ func defaultConvertConfig() convertConfig {
|
||||||
AspectHandling: "Auto",
|
AspectHandling: "Auto",
|
||||||
OutputAspect: "Source",
|
OutputAspect: "Source",
|
||||||
AspectUserSet: false,
|
AspectUserSet: false,
|
||||||
|
TempDir: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1792,7 +1794,7 @@ func (s *appState) showBenchmark() {
|
||||||
logging.Debug(logging.CatSystem, "detected hardware for benchmark: %s", hwInfo.Summary())
|
logging.Debug(logging.CatSystem, "detected hardware for benchmark: %s", hwInfo.Summary())
|
||||||
|
|
||||||
// Create benchmark suite
|
// Create benchmark suite
|
||||||
tmpDir := filepath.Join(os.TempDir(), "videotools-benchmark")
|
tmpDir := filepath.Join(utils.TempDir(), "videotools-benchmark")
|
||||||
_ = os.MkdirAll(tmpDir, 0o755)
|
_ = os.MkdirAll(tmpDir, 0o755)
|
||||||
|
|
||||||
suite := benchmark.NewSuite(platformConfig.FFmpegPath, tmpDir)
|
suite := benchmark.NewSuite(platformConfig.FFmpegPath, tmpDir)
|
||||||
|
|
@ -3079,7 +3081,7 @@ func (s *appState) executeMergeJob(ctx context.Context, job *queue.Job, progress
|
||||||
return fmt.Errorf("need at least two clips to merge")
|
return fmt.Errorf("need at least two clips to merge")
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDir := os.TempDir()
|
tmpDir := utils.TempDir()
|
||||||
listFile, err := os.CreateTemp(tmpDir, "vt-merge-list-*.txt")
|
listFile, err := os.CreateTemp(tmpDir, "vt-merge-list-*.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -4935,6 +4937,7 @@ func runGUI() {
|
||||||
} else if !errors.Is(err, os.ErrNotExist) {
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
logging.Debug(logging.CatSystem, "failed to load persisted convert config: %v", err)
|
logging.Debug(logging.CatSystem, "failed to load persisted convert config: %v", err)
|
||||||
}
|
}
|
||||||
|
utils.SetTempDir(state.convert.TempDir)
|
||||||
|
|
||||||
// Initialize conversion history
|
// Initialize conversion history
|
||||||
if historyCfg, err := loadHistoryConfig(); err == nil {
|
if historyCfg, err := loadHistoryConfig(); err == nil {
|
||||||
|
|
@ -5809,6 +5812,33 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
settingsInfoLabel := widget.NewLabel("Settings persist across videos. Change them anytime to affect all subsequent videos.")
|
settingsInfoLabel := widget.NewLabel("Settings persist across videos. Change them anytime to affect all subsequent videos.")
|
||||||
settingsInfoLabel.Alignment = fyne.TextAlignCenter
|
settingsInfoLabel.Alignment = fyne.TextAlignCenter
|
||||||
|
|
||||||
|
cacheDirLabel := widget.NewLabelWithStyle("Cache/Temp Directory", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||||
|
cacheDirEntry := widget.NewEntry()
|
||||||
|
cacheDirEntry.SetPlaceHolder("System temp (recommended SSD)")
|
||||||
|
cacheDirEntry.SetText(state.convert.TempDir)
|
||||||
|
cacheDirHint := widget.NewLabel("Use an SSD for best performance. Leave blank to use system temp.")
|
||||||
|
cacheDirHint.Wrapping = fyne.TextWrapWord
|
||||||
|
cacheDirEntry.OnChanged = func(val string) {
|
||||||
|
state.convert.TempDir = strings.TrimSpace(val)
|
||||||
|
utils.SetTempDir(state.convert.TempDir)
|
||||||
|
}
|
||||||
|
cacheBrowseBtn := widget.NewButton("Browse...", func() {
|
||||||
|
dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) {
|
||||||
|
if err != nil || uri == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cacheDirEntry.SetText(uri.Path())
|
||||||
|
state.convert.TempDir = uri.Path()
|
||||||
|
utils.SetTempDir(state.convert.TempDir)
|
||||||
|
}, state.window)
|
||||||
|
})
|
||||||
|
cacheUseSystemBtn := widget.NewButton("Use System Temp", func() {
|
||||||
|
cacheDirEntry.SetText("")
|
||||||
|
state.convert.TempDir = ""
|
||||||
|
utils.SetTempDir("")
|
||||||
|
})
|
||||||
|
cacheUseSystemBtn.Importance = widget.LowImportance
|
||||||
|
|
||||||
resetSettingsBtn := widget.NewButton("Reset to Defaults", func() {
|
resetSettingsBtn := widget.NewButton("Reset to Defaults", func() {
|
||||||
if resetConvertDefaults != nil {
|
if resetConvertDefaults != nil {
|
||||||
resetConvertDefaults()
|
resetConvertDefaults()
|
||||||
|
|
@ -5818,6 +5848,11 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
|
|
||||||
settingsContent := container.NewVBox(
|
settingsContent := container.NewVBox(
|
||||||
settingsInfoLabel,
|
settingsInfoLabel,
|
||||||
|
widget.NewSeparator(),
|
||||||
|
cacheDirLabel,
|
||||||
|
container.NewBorder(nil, nil, nil, cacheBrowseBtn, cacheDirEntry),
|
||||||
|
cacheUseSystemBtn,
|
||||||
|
cacheDirHint,
|
||||||
resetSettingsBtn,
|
resetSettingsBtn,
|
||||||
)
|
)
|
||||||
settingsContent.Hide()
|
settingsContent.Hide()
|
||||||
|
|
@ -6993,6 +7028,8 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
audioCodecSelect.SetSelected(state.convert.AudioCodec)
|
audioCodecSelect.SetSelected(state.convert.AudioCodec)
|
||||||
audioBitrateSelect.SetSelected(state.convert.AudioBitrate)
|
audioBitrateSelect.SetSelected(state.convert.AudioBitrate)
|
||||||
audioChannelsSelect.SetSelected(state.convert.AudioChannels)
|
audioChannelsSelect.SetSelected(state.convert.AudioChannels)
|
||||||
|
cacheDirEntry.SetText(state.convert.TempDir)
|
||||||
|
utils.SetTempDir(state.convert.TempDir)
|
||||||
inverseCheck.SetChecked(state.convert.InverseTelecine)
|
inverseCheck.SetChecked(state.convert.InverseTelecine)
|
||||||
inverseHint.SetText(state.convert.InverseAutoNotes)
|
inverseHint.SetText(state.convert.InverseAutoNotes)
|
||||||
coverLabel.SetText(state.convert.CoverLabel())
|
coverLabel.SetText(state.convert.CoverLabel())
|
||||||
|
|
@ -7913,7 +7950,7 @@ Metadata: %s`,
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Generate preview at 10 seconds into the video
|
// Generate preview at 10 seconds into the video
|
||||||
previewPath := filepath.Join(os.TempDir(), fmt.Sprintf("deinterlace_preview_%d.png", time.Now().Unix()))
|
previewPath := filepath.Join(utils.TempDir(), fmt.Sprintf("deinterlace_preview_%d.png", time.Now().Unix()))
|
||||||
err := detector.GenerateComparisonPreview(ctx, state.source.Path, 10.0, previewPath)
|
err := detector.GenerateComparisonPreview(ctx, state.source.Path, 10.0, previewPath)
|
||||||
|
|
||||||
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
||||||
|
|
@ -8760,7 +8797,7 @@ func (s *appState) showFrameManual(path string, img *canvas.Image) {
|
||||||
func (s *appState) captureCoverFromCurrent() (string, error) {
|
func (s *appState) captureCoverFromCurrent() (string, error) {
|
||||||
// If we have a play session active, capture the current playing frame
|
// If we have a play session active, capture the current playing frame
|
||||||
if s.playSess != nil && s.playSess.img != nil && s.playSess.img.Image != nil {
|
if s.playSess != nil && s.playSess.img != nil && s.playSess.img.Image != nil {
|
||||||
dest := filepath.Join(os.TempDir(), fmt.Sprintf("videotools-cover-%d.png", time.Now().UnixNano()))
|
dest := filepath.Join(utils.TempDir(), fmt.Sprintf("videotools-cover-%d.png", time.Now().UnixNano()))
|
||||||
f, err := os.Create(dest)
|
f, err := os.Create(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -8780,7 +8817,7 @@ func (s *appState) captureCoverFromCurrent() (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
dest := filepath.Join(os.TempDir(), fmt.Sprintf("videotools-cover-%d.png", time.Now().UnixNano()))
|
dest := filepath.Join(utils.TempDir(), fmt.Sprintf("videotools-cover-%d.png", time.Now().UnixNano()))
|
||||||
if err := os.WriteFile(dest, data, 0o644); err != nil {
|
if err := os.WriteFile(dest, data, 0o644); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -8792,7 +8829,7 @@ func (s *appState) importCoverImage(path string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
dest := filepath.Join(os.TempDir(), fmt.Sprintf("videotools-cover-import-%d%s", time.Now().UnixNano(), filepath.Ext(path)))
|
dest := filepath.Join(utils.TempDir(), fmt.Sprintf("videotools-cover-import-%d%s", time.Now().UnixNano(), filepath.Ext(path)))
|
||||||
if err := os.WriteFile(dest, data, 0o644); err != nil {
|
if err := os.WriteFile(dest, data, 0o644); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -11089,7 +11126,7 @@ func probeVideo(path string) (*videoSource, error) {
|
||||||
|
|
||||||
// Extract embedded cover art if present
|
// Extract embedded cover art if present
|
||||||
if coverArtStreamIndex >= 0 {
|
if coverArtStreamIndex >= 0 {
|
||||||
coverPath := filepath.Join(os.TempDir(), fmt.Sprintf("videotools-embedded-cover-%d.png", time.Now().UnixNano()))
|
coverPath := filepath.Join(utils.TempDir(), fmt.Sprintf("videotools-embedded-cover-%d.png", time.Now().UnixNano()))
|
||||||
extractCmd := exec.CommandContext(ctx, platformConfig.FFmpegPath,
|
extractCmd := exec.CommandContext(ctx, platformConfig.FFmpegPath,
|
||||||
"-i", path,
|
"-i", path,
|
||||||
"-map", fmt.Sprintf("0:%d", coverArtStreamIndex),
|
"-map", fmt.Sprintf("0:%d", coverArtStreamIndex),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user