Videos: Don't skip transcoding QuickTime files, only MP4 AVC #3525

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2023-07-18 21:23:56 +02:00
parent 3b7b551cca
commit 08070978cf
5 changed files with 125 additions and 32 deletions

View file

@ -6,13 +6,12 @@ import (
const CodecUnknown = ""
const CodecAv1 = string(video.CodecAV1)
const CodecVP9 = string(video.CodecVP9)
const CodecAvc1 = string(video.CodecAVC)
const CodecJpeg = "jpeg"
const CodecHeic = "heic"
const CodecXMP = "xmp"
// CodecAvc returns true if the video format is MPEG-4 AVC.
// CodecAvc returns true if the video codec is AVC.
func (data Data) CodecAvc() bool {
return data.Codec == CodecAvc1
}

View file

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestData_CodecAvc1(t *testing.T) {
func TestData_CodecAvc(t *testing.T) {
t.Run("true", func(t *testing.T) {
data := Data{
Codec: "avc1",
@ -14,7 +14,6 @@ func TestData_CodecAvc1(t *testing.T) {
assert.Equal(t, true, data.CodecAvc())
})
t.Run("false", func(t *testing.T) {
data := Data{
Codec: "heic",

View file

@ -19,24 +19,37 @@ func ConvertWorker(jobs <-chan ConvertJob) {
}
for job := range jobs {
switch {
case job.file == nil:
// File and convert service must not be nil.
if job.file == nil || job.convert == nil {
continue
case job.convert == nil:
continue
case job.file.IsAnimated():
_, _ = job.convert.ToJson(job.file, false)
}
// Create JPEG preview and AVC encoded version for videos.
if _, err := job.convert.ToImage(job.file, job.force); err != nil {
// f is the media file to be converted.
f := job.file
switch {
case f.IsAnimated():
// Extract metadata.
_, _ = job.convert.ToJson(f, false)
// Create cover image.
if _, err := job.convert.ToImage(f, job.force); err != nil {
logError(err, job)
} else if metaData := job.file.MetaData(); metaData.CodecAvc() {
}
// Check if the file has a playable format or has already been transcoded.
if f.SkipTranscoding() {
log.Debugf("convert: %s does not require transcoding", clean.Log(f.RelName(job.convert.conf.OriginalsPath())))
continue
} else if _, err := job.convert.ToAvc(job.file, job.convert.conf.FFmpegEncoder(), false, false); err != nil {
}
// Transcode to MP4 AVC.
if _, err := job.convert.ToAvc(f, job.convert.conf.FFmpegEncoder(), false, false); err != nil {
logError(err, job)
}
default:
if _, err := job.convert.ToImage(job.file, job.force); err != nil {
// Create preview image.
if _, err := job.convert.ToImage(f, job.force); err != nil {
logError(err, job)
}
}

View file

@ -810,6 +810,11 @@ func (m *MediaFile) IsAnimated() bool {
return m.IsVideo() || m.IsAnimatedImage()
}
// NotAnimated checks if the file is not a video or an animated image.
func (m *MediaFile) NotAnimated() bool {
return !m.IsAnimated()
}
// IsVideo returns true if this is a video file.
func (m *MediaFile) IsVideo() bool {
return m.HasMediaType(media.Video)
@ -845,9 +850,24 @@ func (m *MediaFile) InSidecar() bool {
return m.Root() == entity.RootSidecar
}
// IsPlayableVideo checks if the file is a video in playable format.
func (m *MediaFile) IsPlayableVideo() bool {
return m.IsVideo() && (m.HasFileType(fs.VideoMP4) || m.HasFileType(fs.VideoAVC))
// NeedsTranscoding checks whether the media file is a video or an animated image and should be transcoded to a playable format.
func (m *MediaFile) NeedsTranscoding() bool {
if m.NotAnimated() {
return false
} else if m.HasFileType(fs.VideoAVC) || m.HasFileType(fs.VideoMP4) && m.MetaData().CodecAvc() {
return false
}
if m.IsAnimatedImage() {
return fs.VideoMP4.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false) == ""
}
return fs.VideoAVC.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false) == ""
}
// SkipTranscoding checks if the media file is not animated or has already been transcoded to a playable format.
func (m *MediaFile) SkipTranscoding() bool {
return !m.NeedsTranscoding()
}
// IsImageOther returns true if this is a PNG, GIF, BMP, TIFF, or WebP file.

View file

@ -1293,7 +1293,7 @@ func TestMediaFile_IsImageOther(t *testing.T) {
assert.Equal(t, true, mediaFile.IsImageNative())
assert.Equal(t, true, mediaFile.IsImageOther())
assert.Equal(t, false, mediaFile.IsVideo())
assert.Equal(t, false, mediaFile.IsPlayableVideo())
assert.Equal(t, true, mediaFile.SkipTranscoding())
})
t.Run("yellow_rose-small.bmp", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/yellow_rose-small.bmp")
@ -1310,7 +1310,7 @@ func TestMediaFile_IsImageOther(t *testing.T) {
assert.Equal(t, true, mediaFile.IsImageNative())
assert.Equal(t, true, mediaFile.IsImageOther())
assert.Equal(t, false, mediaFile.IsVideo())
assert.Equal(t, false, mediaFile.IsPlayableVideo())
assert.Equal(t, true, mediaFile.SkipTranscoding())
})
t.Run("preloader.gif", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/preloader.gif")
@ -1328,7 +1328,7 @@ func TestMediaFile_IsImageOther(t *testing.T) {
assert.Equal(t, true, mediaFile.IsImageNative())
assert.Equal(t, true, mediaFile.IsImageOther())
assert.Equal(t, false, mediaFile.IsVideo())
assert.Equal(t, false, mediaFile.IsPlayableVideo())
assert.Equal(t, true, mediaFile.SkipTranscoding())
})
t.Run("norway-kjetil-moe.webp", func(t *testing.T) {
mediaFile, err := NewMediaFile("testdata/norway-kjetil-moe.webp")
@ -1347,7 +1347,7 @@ func TestMediaFile_IsImageOther(t *testing.T) {
assert.Equal(t, true, mediaFile.IsImageNative())
assert.Equal(t, true, mediaFile.IsImageOther())
assert.Equal(t, false, mediaFile.IsVideo())
assert.Equal(t, false, mediaFile.IsPlayableVideo())
assert.Equal(t, true, mediaFile.SkipTranscoding())
})
}
@ -1503,6 +1503,7 @@ func TestMediaFile_IsAnimated(t *testing.T) {
assert.Equal(t, true, f.IsImage())
assert.Equal(t, true, f.IsAVIFS())
assert.Equal(t, true, f.IsAnimated())
assert.Equal(t, false, f.NotAnimated())
assert.Equal(t, true, f.IsAnimatedImage())
assert.Equal(t, true, f.ExifSupported())
assert.Equal(t, false, f.IsVideo())
@ -1521,6 +1522,7 @@ func TestMediaFile_IsAnimated(t *testing.T) {
assert.Equal(t, true, f.IsImage())
assert.Equal(t, true, f.IsWebP())
assert.Equal(t, true, f.IsAnimated())
assert.Equal(t, false, f.NotAnimated())
assert.Equal(t, true, f.IsAnimatedImage())
assert.Equal(t, false, f.ExifSupported())
assert.Equal(t, false, f.IsVideo())
@ -1539,6 +1541,7 @@ func TestMediaFile_IsAnimated(t *testing.T) {
assert.Equal(t, true, f.IsImage())
assert.Equal(t, false, f.IsVideo())
assert.Equal(t, false, f.IsAnimated())
assert.Equal(t, true, f.NotAnimated())
assert.Equal(t, true, f.IsGIF())
assert.Equal(t, false, f.IsAnimatedImage())
assert.Equal(t, false, f.IsSidecar())
@ -1551,6 +1554,7 @@ func TestMediaFile_IsAnimated(t *testing.T) {
assert.Equal(t, true, f.IsImage())
assert.Equal(t, false, f.IsVideo())
assert.Equal(t, true, f.IsAnimated())
assert.Equal(t, false, f.NotAnimated())
assert.Equal(t, true, f.IsGIF())
assert.Equal(t, true, f.IsAnimatedImage())
assert.Equal(t, false, f.IsSidecar())
@ -1563,6 +1567,7 @@ func TestMediaFile_IsAnimated(t *testing.T) {
assert.Equal(t, false, f.IsImage())
assert.Equal(t, true, f.IsVideo())
assert.Equal(t, true, f.IsAnimated())
assert.Equal(t, false, f.NotAnimated())
assert.Equal(t, false, f.IsGIF())
assert.Equal(t, false, f.IsAnimatedImage())
assert.Equal(t, false, f.IsSidecar())
@ -2376,28 +2381,85 @@ func TestMediaFile_IsJson(t *testing.T) {
})
}
func TestMediaFile_IsPlayableVideo(t *testing.T) {
t.Run("false", func(t *testing.T) {
conf := config.TestConfig()
func TestMediaFile_NeedsTranscoding(t *testing.T) {
c := config.TestConfig()
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/blue-go-video.json")
t.Run("json", func(t *testing.T) {
f, err := NewMediaFile(c.ExamplesPath() + "/blue-go-video.json")
if err != nil {
t.Fatal(err)
}
assert.False(t, mediaFile.IsPlayableVideo())
assert.False(t, f.NeedsTranscoding())
})
t.Run("true", func(t *testing.T) {
conf := config.TestConfig()
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/blue-go-video.mp4")
t.Run("mp4", func(t *testing.T) {
f, err := NewMediaFile(c.ExamplesPath() + "/blue-go-video.mp4")
if err != nil {
t.Fatal(err)
}
assert.True(t, mediaFile.IsPlayableVideo())
assert.False(t, f.NeedsTranscoding())
})
t.Run("mov", func(t *testing.T) {
f, err := NewMediaFile(c.ExamplesPath() + "/earth.mov")
if err != nil {
t.Fatal(err)
}
assert.True(t, f.NeedsTranscoding())
})
t.Run("gif", func(t *testing.T) {
f, err := NewMediaFile(c.ExamplesPath() + "/pythagoras.gif")
if err != nil {
t.Fatal(err)
}
assert.True(t, f.NeedsTranscoding())
})
}
func TestMediaFile_SkipTranscoding(t *testing.T) {
c := config.TestConfig()
t.Run("json", func(t *testing.T) {
f, err := NewMediaFile(c.ExamplesPath() + "/blue-go-video.json")
if err != nil {
t.Fatal(err)
}
assert.True(t, f.SkipTranscoding())
})
t.Run("mp4", func(t *testing.T) {
f, err := NewMediaFile(c.ExamplesPath() + "/blue-go-video.mp4")
if err != nil {
t.Fatal(err)
}
assert.True(t, f.SkipTranscoding())
})
t.Run("mov", func(t *testing.T) {
f, err := NewMediaFile(c.ExamplesPath() + "/earth.mov")
if err != nil {
t.Fatal(err)
}
assert.False(t, f.SkipTranscoding())
})
t.Run("gif", func(t *testing.T) {
f, err := NewMediaFile(c.ExamplesPath() + "/pythagoras.gif")
if err != nil {
t.Fatal(err)
}
assert.False(t, f.SkipTranscoding())
})
}