Compare commits
5 Commits
63804f7475
...
804d27a0b5
| Author | SHA1 | Date | |
|---|---|---|---|
| 804d27a0b5 | |||
| d566a085d1 | |||
| e22eae8207 | |||
| 834d6b5517 | |||
| aa659b80f5 |
|
|
@ -308,6 +308,9 @@ func buildJobItem(
|
|||
if job.Status == queue.JobStatusRunning {
|
||||
progress.SetActivity(job.Progress <= 0.01)
|
||||
progress.StartAnimation()
|
||||
} else {
|
||||
progress.SetActivity(false)
|
||||
progress.StopAnimation()
|
||||
}
|
||||
progressWidget := progress
|
||||
|
||||
|
|
|
|||
128
main.go
128
main.go
|
|
@ -1767,9 +1767,12 @@ func (s *appState) refreshQueueView() {
|
|||
},
|
||||
func() { // onClearAll
|
||||
s.jobQueue.ClearAll()
|
||||
s.clearVideo()
|
||||
// Always return to main menu after clearing all
|
||||
s.showMainMenu()
|
||||
// Return to the module we were working on if possible
|
||||
if s.lastModule != "" && s.lastModule != "queue" && s.lastModule != "menu" {
|
||||
s.showModule(s.lastModule)
|
||||
} else {
|
||||
s.showMainMenu()
|
||||
}
|
||||
},
|
||||
func(id string) { // onCopyError
|
||||
job, err := s.jobQueue.Get(id)
|
||||
|
|
@ -1992,9 +1995,10 @@ func (s *appState) addAllConvertToQueue() (int, error) {
|
|||
return 0, fmt.Errorf("no videos loaded")
|
||||
}
|
||||
|
||||
usedOutputs := make(map[string]struct{})
|
||||
count := 0
|
||||
for _, src := range s.loadedVideos {
|
||||
if err := s.addConvertToQueueForSource(src); err != nil {
|
||||
if err := s.addConvertToQueueForSourceWithOutputs(src, usedOutputs); err != nil {
|
||||
return count, fmt.Errorf("failed to add %s: %w", filepath.Base(src.Path), err)
|
||||
}
|
||||
count++
|
||||
|
|
@ -2003,6 +2007,113 @@ func (s *appState) addAllConvertToQueue() (int, error) {
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func (s *appState) addConvertToQueueForSourceWithOutputs(src *videoSource, used map[string]struct{}) error {
|
||||
outputBase := s.resolveOutputBase(src, false)
|
||||
cfg := s.convert
|
||||
cfg.OutputBase = outputBase
|
||||
|
||||
outDir := filepath.Dir(src.Path)
|
||||
outName := cfg.OutputFile()
|
||||
if outName == "" {
|
||||
outName = "converted" + cfg.SelectedFormat.Ext
|
||||
}
|
||||
outPath := filepath.Join(outDir, outName)
|
||||
if outPath == src.Path {
|
||||
outPath = filepath.Join(outDir, "converted-"+outName)
|
||||
}
|
||||
|
||||
// Ensure unique output path within batch to avoid overwrites.
|
||||
ext := filepath.Ext(outPath)
|
||||
base := strings.TrimSuffix(outPath, ext)
|
||||
candidate := outPath
|
||||
for i := 2; ; i++ {
|
||||
if _, ok := used[candidate]; !ok {
|
||||
if _, err := os.Stat(candidate); os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
candidate = fmt.Sprintf("%s-%d%s", base, i, ext)
|
||||
}
|
||||
outPath = candidate
|
||||
used[outPath] = struct{}{}
|
||||
|
||||
// Align codec choice with the selected format when the preset implies a codec change.
|
||||
adjustedCodec := s.convert.VideoCodec
|
||||
if preset := s.convert.SelectedFormat.VideoCodec; preset != "" {
|
||||
if friendly := friendlyCodecFromPreset(preset); friendly != "" {
|
||||
if adjustedCodec == "" ||
|
||||
(strings.EqualFold(adjustedCodec, "H.264") && friendly == "H.265") ||
|
||||
(strings.EqualFold(adjustedCodec, "H.265") && friendly == "H.264") {
|
||||
adjustedCodec = friendly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create job config map
|
||||
config := map[string]interface{}{
|
||||
"inputPath": src.Path,
|
||||
"outputPath": outPath,
|
||||
"outputBase": cfg.OutputBase,
|
||||
"selectedFormat": cfg.SelectedFormat,
|
||||
"quality": cfg.Quality,
|
||||
"mode": cfg.Mode,
|
||||
"preserveChapters": cfg.PreserveChapters,
|
||||
"videoCodec": adjustedCodec,
|
||||
"encoderPreset": cfg.EncoderPreset,
|
||||
"crf": cfg.CRF,
|
||||
"bitrateMode": cfg.BitrateMode,
|
||||
"bitratePreset": cfg.BitratePreset,
|
||||
"videoBitrate": cfg.VideoBitrate,
|
||||
"targetFileSize": cfg.TargetFileSize,
|
||||
"targetResolution": cfg.TargetResolution,
|
||||
"frameRate": cfg.FrameRate,
|
||||
"pixelFormat": cfg.PixelFormat,
|
||||
"hardwareAccel": cfg.HardwareAccel,
|
||||
"twoPass": cfg.TwoPass,
|
||||
"h264Profile": cfg.H264Profile,
|
||||
"h264Level": cfg.H264Level,
|
||||
"deinterlace": cfg.Deinterlace,
|
||||
"deinterlaceMethod": cfg.DeinterlaceMethod,
|
||||
"autoCrop": cfg.AutoCrop,
|
||||
"cropWidth": cfg.CropWidth,
|
||||
"cropHeight": cfg.CropHeight,
|
||||
"cropX": cfg.CropX,
|
||||
"cropY": cfg.CropY,
|
||||
"flipHorizontal": cfg.FlipHorizontal,
|
||||
"flipVertical": cfg.FlipVertical,
|
||||
"rotation": cfg.Rotation,
|
||||
"audioCodec": cfg.AudioCodec,
|
||||
"audioBitrate": cfg.AudioBitrate,
|
||||
"audioChannels": cfg.AudioChannels,
|
||||
"audioSampleRate": cfg.AudioSampleRate,
|
||||
"normalizeAudio": cfg.NormalizeAudio,
|
||||
"inverseTelecine": cfg.InverseTelecine,
|
||||
"coverArtPath": cfg.CoverArtPath,
|
||||
"aspectHandling": cfg.AspectHandling,
|
||||
"outputAspect": cfg.OutputAspect,
|
||||
"sourceWidth": src.Width,
|
||||
"sourceHeight": src.Height,
|
||||
"sourceDuration": src.Duration,
|
||||
"sourceBitrate": src.Bitrate,
|
||||
"fieldOrder": src.FieldOrder,
|
||||
"autoCompare": s.autoCompare,
|
||||
}
|
||||
|
||||
job := &queue.Job{
|
||||
Type: queue.JobTypeConvert,
|
||||
Title: fmt.Sprintf("Convert %s", filepath.Base(src.Path)),
|
||||
Description: fmt.Sprintf("Output: %s → %s", utils.ShortenMiddle(filepath.Base(src.Path), 40), utils.ShortenMiddle(filepath.Base(outPath), 40)),
|
||||
InputFile: src.Path,
|
||||
OutputFile: outPath,
|
||||
Config: config,
|
||||
}
|
||||
|
||||
s.jobQueue.Add(job)
|
||||
logging.Debug(logging.CatSystem, "added convert job to queue: %s", job.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *appState) showBenchmark() {
|
||||
s.stopPreview()
|
||||
s.stopPlayer()
|
||||
|
|
@ -6040,10 +6151,17 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
outputEntry := widget.NewEntry()
|
||||
outputEntry.SetText(state.convert.OutputBase)
|
||||
var updatingOutput bool
|
||||
var autoNameCheck *widget.Check
|
||||
outputEntry.OnChanged = func(val string) {
|
||||
if updatingOutput {
|
||||
return
|
||||
}
|
||||
if state.convert.UseAutoNaming {
|
||||
state.convert.UseAutoNaming = false
|
||||
if autoNameCheck != nil {
|
||||
autoNameCheck.SetChecked(false)
|
||||
}
|
||||
}
|
||||
state.convert.OutputBase = val
|
||||
outputHint.SetText(fmt.Sprintf("Output file: %s", state.convert.OutputFile()))
|
||||
}
|
||||
|
|
@ -6060,7 +6178,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
|||
outputHint.SetText(fmt.Sprintf("Output file: %s", state.convert.OutputFile()))
|
||||
}
|
||||
|
||||
autoNameCheck := widget.NewCheck("Auto-name from metadata", func(checked bool) {
|
||||
autoNameCheck = widget.NewCheck("Auto-name from metadata", func(checked bool) {
|
||||
state.convert.UseAutoNaming = checked
|
||||
applyAutoName(true)
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user