Videos: Extract still image after 3 seconds if duration > 3100ms #1241

This way, still images of live photos remain unchanged, while other
videos might get better preview images, especially if the first
few frames are only black or white.

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2022-12-23 01:33:35 +01:00
parent fdd758ace5
commit 709683ef59
5 changed files with 39 additions and 11 deletions

View file

@ -173,7 +173,11 @@ func (c *Convert) JpegConvertCommands(f *MediaFile, jpegName string, xmpName str
// Video thumbnails can be created with FFmpeg.
if f.IsVideo() && c.conf.FFmpegEnabled() {
result = append(result, exec.Command(c.conf.FFmpegBin(), "-y", "-i", f.FileName(), "-ss", "00:00:00.001", "-vframes", "1", jpegName))
if f.Duration() > LivePhotoDurationLimit {
result = append(result, exec.Command(c.conf.FFmpegBin(), "-y", "-i", f.FileName(), "-ss", "00:00:03.000", "-vframes", "1", jpegName))
} else {
result = append(result, exec.Command(c.conf.FFmpegBin(), "-y", "-i", f.FileName(), "-ss", "00:00:00.001", "-vframes", "1", jpegName))
}
}
// RAW files may be concerted with Darktable and Rawtherapee.

View file

@ -566,7 +566,7 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
if photo.TypeSrc == entity.SrcAuto {
// Update photo type only if not manually modified.
if file.FileDuration == 0 || file.FileDuration > time.Millisecond*3100 {
if file.FileDuration == 0 || file.FileDuration > LivePhotoDurationLimit {
photo.PhotoType = entity.MediaVideo
} else {
photo.PhotoType = entity.MediaLive

View file

@ -0,0 +1,6 @@
package photoprism
import "time"
// LivePhotoDurationLimit is the maximum duration of a live photo.
var LivePhotoDurationLimit = time.Millisecond * 3100

View file

@ -763,6 +763,15 @@ func (m *MediaFile) IsVideo() bool {
return strings.HasPrefix(m.MimeType(), "video/") || m.Media() == media.Video
}
// Duration returns the duration if the file is a video.
func (m *MediaFile) Duration() time.Duration {
if !m.IsVideo() {
return 0
}
return m.MetaData().Duration
}
// IsAnimatedGif returns true if it is an animated GIF.
func (m *MediaFile) IsAnimatedGif() bool {
return m.IsGif() && m.MetaData().Frames > 1

View file

@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
@ -2428,55 +2429,63 @@ func TestMediaFile_RemoveSidecarFiles(t *testing.T) {
}
func TestMediaFile_ColorProfile(t *testing.T) {
c := config.TestConfig()
t.Run("iphone_7.json", func(t *testing.T) {
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.json")
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.json")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "", mediaFile.ColorProfile())
})
t.Run("iphone_7.xmp", func(t *testing.T) {
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.xmp")
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.xmp")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "", mediaFile.ColorProfile())
})
t.Run("iphone_7.heic", func(t *testing.T) {
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.heic")
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "", mediaFile.ColorProfile())
})
t.Run("canon_eos_6d.dng", func(t *testing.T) {
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/canon_eos_6d.dng")
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "", mediaFile.ColorProfile())
})
t.Run("elephants.jpg", func(t *testing.T) {
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg")
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "Adobe RGB (1998)", mediaFile.ColorProfile())
})
t.Run("/beach_wood.jpg", func(t *testing.T) {
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/beach_wood.jpg")
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/beach_wood.jpg")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "", mediaFile.ColorProfile())
})
t.Run("/peacock_blue.jpg", func(t *testing.T) {
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/peacock_blue.jpg")
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/peacock_blue.jpg")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "sRGB IEC61966-2.1", mediaFile.ColorProfile())
})
}
func TestMediaFile_Duration(t *testing.T) {
t.Run("earth.mov", func(t *testing.T) {
if f, err := NewMediaFile(filepath.Join(conf.ExamplesPath(), "blue-go-video.mp4")); err != nil {
t.Fatal(err)
} else {
assert.Equal(t, time.Duration(2000000000), f.Duration())
}
})
}