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")
|
return 0, fmt.Errorf("no videos loaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usedOutputs := make(map[string]struct{})
|
||||||
count := 0
|
count := 0
|
||||||
for _, src := range s.loadedVideos {
|
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)
|
return count, fmt.Errorf("failed to add %s: %w", filepath.Base(src.Path), err)
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
|
|
@ -2006,6 +2007,113 @@ func (s *appState) addAllConvertToQueue() (int, error) {
|
||||||
return count, nil
|
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() {
|
func (s *appState) showBenchmark() {
|
||||||
s.stopPreview()
|
s.stopPreview()
|
||||||
s.stopPlayer()
|
s.stopPlayer()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user