Video: Name the matched exclude entry and apply it to transcode #5617

Add Formats.Match to report which exclude-list entry matched, and use it in the
remux, download, transcode, and index skip messages instead of always printing
the codec (which could be empty or differ from the entry the user configured).

Also apply the exclude list when building "videos transcode" plans so excluded
files are skipped during dry runs and real runs, and report exclusion clearly
when no preview image can be generated for an excluded video.
This commit is contained in:
Michael Mayer 2026-05-30 21:50:55 +00:00
parent c691b8cbe0
commit a57ca16bb3
9 changed files with 62 additions and 23 deletions

View file

@ -261,8 +261,8 @@ func downloadAction(ctx *cli.Context) error {
continue
}
if ffmpeg.Exclude().Contains(result.Info.VCodec, result.Info.Ext, result.Info.Container) {
log.Warnf("skipping %s because format %s is on the FFmpeg exclude list", clean.Log(u.String()), clean.Log(result.Info.VCodec))
if matched := ffmpeg.Exclude().Match(result.Info.VCodec, result.Info.Ext, result.Info.Container); matched != "" {
log.Warnf("skipping %s because format %s is on the FFmpeg exclude list", clean.Log(u.String()), clean.Log(matched))
failures++
continue
}

View file

@ -165,8 +165,8 @@ func runDownload(conf *config.Config, opts DownloadOpts, inputURLs []string) err
continue
}
if ffmpeg.Exclude().Contains(result.Info.VCodec, result.Info.Ext, result.Info.Container) {
log.Warnf("skipping %s because format %s is on the FFmpeg exclude list", clean.Log(u.String()), clean.Log(result.Info.VCodec))
if matched := ffmpeg.Exclude().Match(result.Info.VCodec, result.Info.Ext, result.Info.Container); matched != "" {
log.Warnf("skipping %s because format %s is on the FFmpeg exclude list", clean.Log(u.String()), clean.Log(matched))
failures++
continue
}

View file

@ -160,8 +160,8 @@ func videoBuildRemuxPlans(conf *config.Config, results []search.Photo, force boo
}
}
if ffmpeg.Exclude().Contains(videoFile.FileCodec, fs.FileType(srcPath).String()) {
log.Warnf("remux: skipping %s because format %s is on the FFmpeg exclude list", clean.Log(videoFile.FileName), clean.Log(videoFile.FileCodec))
if matched := ffmpeg.Exclude().Match(videoFile.FileCodec, fs.FileType(srcPath).String()); matched != "" {
log.Warnf("remux: skipping %s because format %s is on the FFmpeg exclude list", clean.Log(videoFile.FileName), clean.Log(matched))
skipped++
continue
}

View file

@ -10,6 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/entity/search"
"github.com/photoprism/photoprism/internal/ffmpeg"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/clean"
@ -154,6 +155,11 @@ func videoBuildTranscodePlans(conf *config.Config, results []search.Photo, force
continue
}
if matched := ffmpeg.Exclude().Match(videoFile.FileCodec, fs.FileType(srcPath).String()); matched != "" {
log.Warnf("transcode: skipping %s because format %s is on the FFmpeg exclude list", clean.Log(videoFile.FileName), clean.Log(matched))
continue
}
if !conf.SidecarWritable() || !fs.PathWritable(conf.SidecarPath()) {
return nil, nil, config.ErrReadOnly
}

View file

@ -153,6 +153,9 @@ func (w *Convert) JpegConvertCmds(f *MediaFile, jpegName string, xmpName string)
// No suitable converter found?
if len(result) == 0 {
if f.IsAnimated() && w.conf.FFmpegEnabled() && !w.FFmpegAllowed(f) {
return result, useMutex, fmt.Errorf("format %s is on the FFmpeg exclude list", w.ffmpegExclude.Match(f.MetaData().Codec, f.VideoInfo().VideoCodec, f.FileType().String()))
}
return result, useMutex, fmt.Errorf("file type %s not supported", f.FileType())
}

View file

@ -91,6 +91,9 @@ func (w *Convert) PngConvertCmds(f *MediaFile, pngName string) (result ConvertCm
// No suitable converter found?
if len(result) == 0 {
if f.IsAnimated() && w.conf.FFmpegEnabled() && !w.FFmpegAllowed(f) {
return result, useMutex, fmt.Errorf("format %s is on the FFmpeg exclude list", w.ffmpegExclude.Match(f.MetaData().Codec, f.VideoInfo().VideoCodec, f.FileType().String()))
}
return result, useMutex, fmt.Errorf("file type %s not supported", f.FileType())
}

View file

@ -37,10 +37,7 @@ func (w *Convert) ToAvc(f *MediaFile, encoder encode.Encoder, noMutex, force boo
// Skip files whose codec or container is on the FFmpeg exclude list.
if !w.FFmpegAllowed(f) {
format := clean.Log(f.MetaData().Codec)
if format == "" {
format = clean.Log(f.FileType().String())
}
format := clean.Log(w.ffmpegExclude.Match(f.MetaData().Codec, f.VideoInfo().VideoCodec, f.FileType().String()))
log.Warnf("convert: skipping %s because format %s is on the FFmpeg exclude list", logFileName, format)
return nil, fmt.Errorf("convert: format %s is excluded from FFmpeg processing", format)
}

View file

@ -24,25 +24,34 @@ func NewFormats(formats ...string) Formats {
return list
}
// Match returns the first of the given format names that is in the list,
// reported as its canonical codec alias, or "" if none match. Empty values are
// skipped so callers can pass both a codec and a container even when only one
// is known. Use it to name the matched entry when reporting why a file was
// excluded.
func (b Formats) Match(formats ...string) string {
if len(b) == 0 {
return ""
}
for _, format := range formats {
if format = clean.Format(format); format == "" {
continue
} else if canonical := Canonical(format); b[canonical] {
return canonical
}
}
return ""
}
// Contains reports whether any of the given format names is in the list.
// Empty values are skipped so callers can pass both a codec and a container
// even when only one is known. Names are matched by their canonical codec
// alias, so the original name (e.g. "m8ra") and its mapped name (e.g. "magy")
// are treated as equivalent.
func (b Formats) Contains(formats ...string) bool {
if len(b) == 0 || len(formats) == 0 {
return false
}
for _, format := range formats {
if format = clean.Format(format); format == "" {
continue
} else if _, ok := b[Canonical(format)]; ok {
return true
}
}
return false
return b.Match(formats...) != ""
}
// Allow reports whether the given format is NOT in the list.

View file

@ -120,6 +120,27 @@ func TestFormats_Allow(t *testing.T) {
assert.True(t, list.Allow(""))
}
func TestFormats_Match(t *testing.T) {
list := NewFormats("magy, mov")
t.Run("ReturnsCanonicalEntry", func(t *testing.T) {
// The matched entry is reported, not the first argument.
assert.Equal(t, "mov", list.Match("avc1", "mov"))
assert.Equal(t, "magy", list.Match("m8ra", "avi"))
})
t.Run("SkipsEmptyArgs", func(t *testing.T) {
assert.Equal(t, "magy", list.Match("", "magicyuv"))
})
t.Run("NoMatch", func(t *testing.T) {
assert.Equal(t, "", list.Match("avc1", "mp4"))
assert.Equal(t, "", list.Match())
assert.Equal(t, "", list.Match(""))
})
t.Run("EmptyList", func(t *testing.T) {
assert.Equal(t, "", NewFormats("").Match("magy"))
})
}
func TestFormats_Set(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
list := make(Formats)