Videos: Transcode supported formats if bitrate exceeds limit #2461
Implements Option (1) as described in the issue comments.
This commit is contained in:
parent
1a6f108bbe
commit
b09112058e
6 changed files with 87 additions and 9 deletions
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/video"
|
||||
|
||||
|
@ -64,6 +65,14 @@ func GetVideo(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
fileBitrate := f.Bitrate()
|
||||
|
||||
// File format supported by the client/browser?
|
||||
supported := f.FileCodec != "" && f.FileCodec == string(format.Codec) || format.Codec == video.UnknownCodec && f.FileType == string(format.File)
|
||||
|
||||
// File bitrate too high (for streaming)?
|
||||
conf := service.Config()
|
||||
transcode := !supported || conf.FFmpegEnabled() && conf.FFmpegBitrateExceeded(fileBitrate)
|
||||
|
||||
if mf, err := photoprism.NewMediaFile(fileName); err != nil {
|
||||
// Set missing flag so that the file doesn't show up in search results anymore.
|
||||
|
@ -73,15 +82,13 @@ func GetVideo(router *gin.RouterGroup) {
|
|||
log.Errorf("video: file %s is missing", clean.Log(f.FileName))
|
||||
fileName = service.Config().StaticFile("video/404.mp4")
|
||||
AddContentTypeHeader(c, ContentTypeAvc)
|
||||
} else if f.FileCodec != "" && f.FileCodec == string(format.Codec) || format.Codec == video.UnknownCodec && f.FileType == string(format.File) {
|
||||
if f.FileCodec != "" && f.FileCodec != f.FileType {
|
||||
log.Debugf("video: %s has matching codec %s", clean.Log(f.FileName), clean.Log(f.FileCodec))
|
||||
AddContentTypeHeader(c, fmt.Sprintf("%s; codecs=\"%s\"", f.FileMime, clean.Codec(f.FileCodec)))
|
||||
} else if transcode {
|
||||
if f.FileCodec != "" {
|
||||
log.Debugf("video: %s is %s compressed and cannot be streamed directly, average bitrate %.1f MBit/s", clean.Log(f.FileName), clean.Log(strings.ToUpper(f.FileCodec)), fileBitrate)
|
||||
} else {
|
||||
log.Debugf("video: %s has matching type %s", clean.Log(f.FileName), clean.Log(f.FileType))
|
||||
AddContentTypeHeader(c, f.FileMime)
|
||||
log.Debugf("video: %s cannot be streamed directly, average bitrate %.1f MBit/s", clean.Log(f.FileName), fileBitrate)
|
||||
}
|
||||
} else {
|
||||
|
||||
conv := service.Convert()
|
||||
|
||||
if avcFile, err := conv.ToAvc(mf, service.Config().FFmpegEncoder(), false, false); err != nil {
|
||||
|
@ -93,6 +100,14 @@ func GetVideo(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
AddContentTypeHeader(c, ContentTypeAvc)
|
||||
} else {
|
||||
if f.FileCodec != "" && f.FileCodec != f.FileType {
|
||||
log.Debugf("video: %s is %s compressed and requires no transcoding, average bitrate %.1f MBit/s", clean.Log(f.FileName), clean.Log(strings.ToUpper(f.FileCodec)), fileBitrate)
|
||||
AddContentTypeHeader(c, fmt.Sprintf("%s; codecs=\"%s\"", f.FileMime, clean.Codec(f.FileCodec)))
|
||||
} else {
|
||||
log.Debugf("video: %s is streamed directly, average bitrate %.1f MBit/s", clean.Log(f.FileName), fileBitrate)
|
||||
AddContentTypeHeader(c, f.FileMime)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Query("download") != "" {
|
||||
|
|
|
@ -28,3 +28,14 @@ func (c *Config) FFmpegBitrate() int {
|
|||
return c.options.FFmpegBitrate
|
||||
}
|
||||
}
|
||||
|
||||
// FFmpegBitrateExceeded tests if the ffmpeg bitrate limit is exceeded.
|
||||
func (c *Config) FFmpegBitrateExceeded(mbit float64) bool {
|
||||
if mbit <= 0 {
|
||||
return false
|
||||
} else if max := c.FFmpegBitrate(); max <= 0 {
|
||||
return false
|
||||
} else {
|
||||
return mbit > float64(max)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,3 +42,24 @@ func TestConfig_FFmpegBitrate(t *testing.T) {
|
|||
c.options.FFmpegBitrate = 800
|
||||
assert.Equal(t, 800, c.FFmpegBitrate())
|
||||
}
|
||||
|
||||
func TestConfig_FFmpegBitrateExceeded(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
c.options.FFmpegBitrate = 0
|
||||
assert.False(t, c.FFmpegBitrateExceeded(0.95))
|
||||
assert.False(t, c.FFmpegBitrateExceeded(1.05))
|
||||
assert.False(t, c.FFmpegBitrateExceeded(2.05))
|
||||
c.options.FFmpegBitrate = 1
|
||||
assert.False(t, c.FFmpegBitrateExceeded(0.95))
|
||||
assert.False(t, c.FFmpegBitrateExceeded(1.0))
|
||||
assert.True(t, c.FFmpegBitrateExceeded(1.05))
|
||||
assert.True(t, c.FFmpegBitrateExceeded(2.05))
|
||||
c.options.FFmpegBitrate = 50
|
||||
assert.False(t, c.FFmpegBitrateExceeded(0.95))
|
||||
assert.False(t, c.FFmpegBitrateExceeded(1.05))
|
||||
assert.False(t, c.FFmpegBitrateExceeded(2.05))
|
||||
c.options.FFmpegBitrate = -5
|
||||
assert.False(t, c.FFmpegBitrateExceeded(0.95))
|
||||
assert.False(t, c.FFmpegBitrateExceeded(1.05))
|
||||
assert.False(t, c.FFmpegBitrateExceeded(2.05))
|
||||
}
|
||||
|
|
|
@ -661,6 +661,17 @@ func (m *File) SetDuration(d time.Duration) {
|
|||
}
|
||||
}
|
||||
|
||||
// Bitrate returns the average bitrate in MBit/s if the file has a duration.
|
||||
func (m *File) Bitrate() float64 {
|
||||
// Make sure size and duration have a positive value.
|
||||
if m.FileSize <= 0 || m.FileDuration <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Divide number of bits through the duration in seconds.
|
||||
return ((float64(m.FileSize) * 8) / m.FileDuration.Seconds()) / 1e6
|
||||
}
|
||||
|
||||
// SetFPS sets the average number of frames per second.
|
||||
func (m *File) SetFPS(frameRate float64) {
|
||||
if frameRate <= 0 {
|
||||
|
|
|
@ -780,3 +780,21 @@ func TestFile_SetDuration(t *testing.T) {
|
|||
assert.Equal(t, 216000, m.FileFrames)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFile_Bitrate(t *testing.T) {
|
||||
t.Run("HasDuration", func(t *testing.T) {
|
||||
m := File{FileDuration: 1e9 * 20.302, FileSize: 1826192}
|
||||
|
||||
assert.InEpsilon(t, 0.719, m.Bitrate(), 0.01)
|
||||
})
|
||||
t.Run("NoDuration", func(t *testing.T) {
|
||||
m := File{FileDuration: 0, FileSize: 1826192}
|
||||
|
||||
assert.Equal(t, float64(0), m.Bitrate())
|
||||
})
|
||||
t.Run("NoSize", func(t *testing.T) {
|
||||
m := File{FileDuration: 1e9 * 20.302, FileSize: 0}
|
||||
|
||||
assert.Equal(t, float64(0), m.Bitrate())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -139,9 +139,11 @@ func (c *Convert) AvcConvertCommand(f *MediaFile, avcName string, encoder ffmpeg
|
|||
case bitrate == "":
|
||||
return nil, false, fmt.Errorf("convert: transcoding bitrate is empty - possible bug")
|
||||
case ffmpegBin == "":
|
||||
return nil, false, fmt.Errorf("convert: ffmpeg must be installed to transcode %s to avc", clean.Log(f.BaseName()))
|
||||
return nil, false, fmt.Errorf("convert: ffmpeg must be installed to transcode %s", clean.Log(f.BaseName()))
|
||||
case c.conf.DisableFFmpeg():
|
||||
return nil, false, fmt.Errorf("convert: ffmpeg must be enabled to transcode %s", clean.Log(f.BaseName()))
|
||||
case !f.IsAnimated():
|
||||
return nil, false, fmt.Errorf("convert: file type %s of %s cannot be transcoded to avc", f.FileType(), clean.Log(f.BaseName()))
|
||||
return nil, false, fmt.Errorf("convert: file type %s of %s cannot be transcoded", f.FileType(), clean.Log(f.BaseName()))
|
||||
}
|
||||
|
||||
return ffmpeg.AvcConvertCommand(fileName, avcName, ffmpegBin, c.AvcBitrate(f), encoder)
|
||||
|
|
Loading…
Reference in a new issue