From a57ca16bb32db6126a208954b0c89899c796c16a Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Sat, 30 May 2026 21:50:55 +0000 Subject: [PATCH] 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. --- internal/commands/download.go | 4 +-- internal/commands/download_impl.go | 4 +-- internal/commands/video_remux.go | 4 +-- internal/commands/video_transcode.go | 6 ++++ internal/photoprism/convert_image_jpeg.go | 3 ++ internal/photoprism/convert_image_png.go | 3 ++ internal/photoprism/convert_video_avc.go | 5 +--- pkg/media/video/formats.go | 35 ++++++++++++++--------- pkg/media/video/formats_test.go | 21 ++++++++++++++ 9 files changed, 62 insertions(+), 23 deletions(-) diff --git a/internal/commands/download.go b/internal/commands/download.go index 412c4c66e..18952dac5 100644 --- a/internal/commands/download.go +++ b/internal/commands/download.go @@ -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 } diff --git a/internal/commands/download_impl.go b/internal/commands/download_impl.go index 7a99d489f..b6e4daca5 100644 --- a/internal/commands/download_impl.go +++ b/internal/commands/download_impl.go @@ -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 } diff --git a/internal/commands/video_remux.go b/internal/commands/video_remux.go index 3f7a66792..6a5307e0e 100644 --- a/internal/commands/video_remux.go +++ b/internal/commands/video_remux.go @@ -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 } diff --git a/internal/commands/video_transcode.go b/internal/commands/video_transcode.go index e68f784e9..1ea2bf742 100644 --- a/internal/commands/video_transcode.go +++ b/internal/commands/video_transcode.go @@ -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 } diff --git a/internal/photoprism/convert_image_jpeg.go b/internal/photoprism/convert_image_jpeg.go index 29c0b92b0..a033febe1 100644 --- a/internal/photoprism/convert_image_jpeg.go +++ b/internal/photoprism/convert_image_jpeg.go @@ -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()) } diff --git a/internal/photoprism/convert_image_png.go b/internal/photoprism/convert_image_png.go index f63791db9..ac2558dfa 100644 --- a/internal/photoprism/convert_image_png.go +++ b/internal/photoprism/convert_image_png.go @@ -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()) } diff --git a/internal/photoprism/convert_video_avc.go b/internal/photoprism/convert_video_avc.go index a6a9d1f8a..6ac13538d 100644 --- a/internal/photoprism/convert_video_avc.go +++ b/internal/photoprism/convert_video_avc.go @@ -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) } diff --git a/pkg/media/video/formats.go b/pkg/media/video/formats.go index 4437fe739..2005ac2cd 100644 --- a/pkg/media/video/formats.go +++ b/pkg/media/video/formats.go @@ -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. diff --git a/pkg/media/video/formats_test.go b/pkg/media/video/formats_test.go index 80758a44f..6cbc71cde 100644 --- a/pkg/media/video/formats_test.go +++ b/pkg/media/video/formats_test.go @@ -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)