Avoid batch remux output collisions
This commit is contained in:
parent
834d6b5517
commit
e22eae8207
110
main.go
110
main.go
|
|
@ -1995,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++
|
||||
|
|
@ -2006,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, true)
|
||||
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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user