mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
8c3ec7435e
commit
420fa9946c
37 changed files with 770 additions and 393 deletions
39
pkg/clean/content_type.go
Normal file
39
pkg/clean/content_type.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package clean
|
||||
|
||||
import (
|
||||
"github.com/photoprism/photoprism/pkg/header"
|
||||
)
|
||||
|
||||
// ContentType normalizes media content type strings, see https://en.wikipedia.org/wiki/Media_type.
|
||||
func ContentType(s string) string {
|
||||
if s == "" {
|
||||
return header.ContentTypeBinary
|
||||
}
|
||||
|
||||
s = Type(s)
|
||||
|
||||
switch s {
|
||||
case "":
|
||||
return header.ContentTypeBinary
|
||||
case "text/json", "application/json":
|
||||
return header.ContentTypeJsonUtf8
|
||||
case "text/htm", "text/html":
|
||||
return header.ContentTypeHtml
|
||||
case "text/plain":
|
||||
return header.ContentTypeText
|
||||
case "text/pdf", "text/x-pdf", "application/x-pdf", "application/acrobat":
|
||||
return header.ContentTypePDF
|
||||
case "image/svg":
|
||||
return header.ContentTypeSVG
|
||||
case "image/jpe", "image/jpg":
|
||||
return header.ContentTypeJPEG
|
||||
case "video/mp4; codecs=\"avc\"":
|
||||
return header.ContentTypeAVC
|
||||
case "video/mp4; codecs=\"hvc1\"", "video/mp4; codecs=\"hvc\"", "video/mp4; codecs=\"hevc\"":
|
||||
return header.ContentTypeHEVC
|
||||
case "video/webm; codecs=\"av01\"":
|
||||
return header.ContentTypeAV1
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
|
@ -31,8 +31,10 @@ func Log(s string) string {
|
|||
case ' ':
|
||||
spaces = true
|
||||
return r
|
||||
case '`', '"':
|
||||
case '`':
|
||||
return '\''
|
||||
case '"':
|
||||
return '"'
|
||||
case '\\', '$', '<', '>', '{', '}':
|
||||
return '?'
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func TestLog(t *testing.T) {
|
|||
assert.Equal(t, "?", Log("User-Agent: {jndi:ldap://<host>:<port>/<path>}"))
|
||||
})
|
||||
t.Run("SpecialChars", func(t *testing.T) {
|
||||
assert.Equal(t, "' The ?quick? ''brown 'fox. '", Log(" The <quick>\n\r ''brown \"fox. \t "))
|
||||
assert.Equal(t, "' The ?quick? ''brown \"fox. '", Log(" The <quick>\n\r ''brown \"fox. \t "))
|
||||
})
|
||||
t.Run("LoremIpsum", func(t *testing.T) {
|
||||
assert.Equal(t, "'It is a long established fact that a reader will be distracted by the readable "+
|
||||
|
|
|
|||
|
|
@ -29,11 +29,16 @@ const (
|
|||
MimeTypeAI = "application/vnd.adobe.illustrator"
|
||||
MimeTypePS = "application/postscript"
|
||||
MimeTypeEPS = "image/eps"
|
||||
MimeTypeText = "text/plain"
|
||||
MimeTypeXML = "text/xml"
|
||||
MimeTypeJSON = "application/json"
|
||||
)
|
||||
|
||||
// MimeType returns the mime type of a file, or an empty string if it could not be detected.
|
||||
// MimeType returns the mimetype of a file, or an empty string if it could not be determined.
|
||||
//
|
||||
// The IANA and IETF use the term "media type", and consider the term "MIME type" to be obsolete,
|
||||
// since media types have become used in contexts unrelated to email, such as HTTP:
|
||||
// https://en.wikipedia.org/wiki/Media_type#Structure
|
||||
func MimeType(filename string) (mimeType string) {
|
||||
if filename == "" {
|
||||
return MimeTypeUnknown
|
||||
|
|
@ -69,7 +74,7 @@ func MimeType(filename string) (mimeType string) {
|
|||
detectedType, err := mimetype.DetectFile(filename)
|
||||
|
||||
if detectedType != nil && err == nil {
|
||||
mimeType, _, _ = strings.Cut(detectedType.String(), ";")
|
||||
mimeType = detectedType.String()
|
||||
}
|
||||
|
||||
// Treat "application/octet-stream" as unknown.
|
||||
|
|
@ -100,3 +105,23 @@ func MimeType(filename string) (mimeType string) {
|
|||
|
||||
return mimeType
|
||||
}
|
||||
|
||||
// BaseType returns the media type string without any optional parameters.
|
||||
func BaseType(mimeType string) string {
|
||||
if mimeType == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
mimeType, _, _ = strings.Cut(mimeType, ";")
|
||||
|
||||
return strings.ToLower(mimeType)
|
||||
}
|
||||
|
||||
// IsType tests if the specified mime types are matching, except for any optional parameters.
|
||||
func IsType(mime1, mime2 string) bool {
|
||||
if mime1 == mime2 {
|
||||
return true
|
||||
}
|
||||
|
||||
return BaseType(mime1) == BaseType(mime2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,3 +77,93 @@ func TestMimeType(t *testing.T) {
|
|||
assert.Equal(t, "image/eps", mimeType)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBaseType(t *testing.T) {
|
||||
t.Run("MP4", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.mp4")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "video/mp4", mimeType)
|
||||
})
|
||||
t.Run("MOV", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.mov")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "video/quicktime", mimeType)
|
||||
})
|
||||
t.Run("JPEG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.jpg")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/jpeg", mimeType)
|
||||
})
|
||||
t.Run("InvalidFilename", func(t *testing.T) {
|
||||
filename := Abs("./testdata/xxx.jpg")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "", mimeType)
|
||||
})
|
||||
t.Run("EmptyFilename", func(t *testing.T) {
|
||||
mimeType := BaseType("")
|
||||
assert.Equal(t, "", mimeType)
|
||||
})
|
||||
t.Run("AVIF", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.avif")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/avif", mimeType)
|
||||
})
|
||||
t.Run("AVIFS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.avifs")
|
||||
mimeType := MimeType(filename)
|
||||
assert.Equal(t, "image/avif-sequence", mimeType)
|
||||
})
|
||||
t.Run("HEIC", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.heic")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/heic", mimeType)
|
||||
})
|
||||
t.Run("HEICS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.heics")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/heic-sequence", mimeType)
|
||||
})
|
||||
t.Run("DNG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.dng")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/dng", mimeType)
|
||||
})
|
||||
t.Run("SVG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.svg")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/svg+xml", mimeType)
|
||||
})
|
||||
t.Run("AI", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.ai")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "application/vnd.adobe.illustrator", mimeType)
|
||||
})
|
||||
t.Run("PS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.ps")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "application/postscript", mimeType)
|
||||
})
|
||||
t.Run("EPS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.eps")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/eps", mimeType)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsType(t *testing.T) {
|
||||
t.Run("True", func(t *testing.T) {
|
||||
assert.True(t, IsType("", MimeTypeUnknown))
|
||||
assert.True(t, IsType("video/jpg", "video/jpg"))
|
||||
assert.True(t, IsType("video/jpeg", "video/jpeg"))
|
||||
assert.True(t, IsType("video/mp4", "video/mp4"))
|
||||
assert.True(t, IsType("video/mp4", MimeTypeMP4))
|
||||
assert.True(t, IsType("video/mp4", "video/MP4"))
|
||||
assert.True(t, IsType("video/mp4", "video/MP4; codecs=\"avc1\""))
|
||||
})
|
||||
t.Run("False", func(t *testing.T) {
|
||||
assert.False(t, IsType("", MimeTypeMP4))
|
||||
assert.False(t, IsType("video/jpeg", "video/jpg"))
|
||||
assert.False(t, IsType("video/mp4", MimeTypeUnknown))
|
||||
assert.False(t, IsType(MimeTypeMP4, MimeTypeJPEG))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,22 @@ const (
|
|||
|
||||
// Standard ContentType header values.
|
||||
const (
|
||||
ContentTypeBinary = "application/octet-stream"
|
||||
ContentTypeForm = "application/x-www-form-urlencoded"
|
||||
ContentTypeMultipart = "multipart/form-data"
|
||||
ContentTypeJson = "application/json"
|
||||
ContentTypeJsonUtf8 = "application/json; charset=utf-8"
|
||||
ContentTypeHtml = "text/html; charset=utf-8"
|
||||
ContentTypeText = "text/plain; charset=utf-8"
|
||||
ContentTypePDF = "application/pdf"
|
||||
ContentTypePNG = "image/png"
|
||||
ContentTypeJPEG = "image/jpeg"
|
||||
ContentTypeSVG = "image/svg+xml"
|
||||
ContentTypeAVC = "video/mp4; codecs=\"avc1\""
|
||||
ContentTypeHEVC = "video/mp4; codecs=\"hvc1.1.6.L93.90\""
|
||||
ContentTypeOGG = "video/ogg"
|
||||
ContentTypeWebM = "video/webm"
|
||||
ContentTypeVP8 = "video/webm; codecs=\"vp8\""
|
||||
ContentTypeVP9 = "video/webm; codecs=\"vp9\""
|
||||
ContentTypeAV1 = "video/webm; codecs=\"av01.0.08M.08\""
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
package video
|
||||
|
||||
type Codec string
|
||||
|
||||
// String returns the codec name as string.
|
||||
func (c Codec) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
// Check browser support: https://cconcolato.github.io/media-mime-support/
|
||||
type Codec = string
|
||||
|
||||
// Video codecs supported by web browsers:
|
||||
// https://cconcolato.github.io/media-mime-support/
|
||||
const (
|
||||
CodecUnknown Codec = ""
|
||||
CodecAVC Codec = "avc1"
|
||||
CodecHVC Codec = "hvc1"
|
||||
CodecHEVC Codec = "hvc1"
|
||||
CodecVVC Codec = "vvc"
|
||||
CodecEVC Codec = "evc"
|
||||
CodecAV1 Codec = "av01"
|
||||
|
|
@ -34,11 +29,14 @@ var Codecs = StandardCodecs{
|
|||
"iso/avc": CodecAVC,
|
||||
"v_mpeg4/avc": CodecAVC,
|
||||
"v_mpeg4/iso/avc": CodecAVC,
|
||||
"hevc": CodecHVC,
|
||||
"hvc": CodecHVC,
|
||||
"hvc1": CodecHVC,
|
||||
"v_hvc": CodecHVC,
|
||||
"v_hvc1": CodecHVC,
|
||||
"hevc": CodecHEVC,
|
||||
"hevC": CodecHEVC,
|
||||
"hvc": CodecHEVC,
|
||||
"hvc1": CodecHEVC,
|
||||
"v_hvc": CodecHEVC,
|
||||
"v_hvc1": CodecHEVC,
|
||||
"hev": CodecHEVC,
|
||||
"hev1": CodecHEVC,
|
||||
"evc": CodecEVC,
|
||||
"evc1": CodecEVC,
|
||||
"evcC": CodecEVC,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func TestContentType(t *testing.T) {
|
|||
assert.Equal(t, fs.MimeTypeMOV, ContentType(fs.MimeTypeMOV, ""))
|
||||
})
|
||||
t.Run("QuickTime_HVC", func(t *testing.T) {
|
||||
assert.Equal(t, `video/quicktime; codecs="hvc1"`, ContentType(fs.MimeTypeMOV, CodecHVC))
|
||||
assert.Equal(t, `video/quicktime; codecs="hvc1"`, ContentType(fs.MimeTypeMOV, CodecHEVC))
|
||||
})
|
||||
t.Run("MP4", func(t *testing.T) {
|
||||
assert.Equal(t, fs.MimeTypeMP4, ContentType(fs.MimeTypeMP4, ""))
|
||||
|
|
@ -22,6 +22,6 @@ func TestContentType(t *testing.T) {
|
|||
assert.Equal(t, ContentTypeAVC, ContentType(fs.MimeTypeMP4, CodecAVC))
|
||||
})
|
||||
t.Run("MP4_HVC", func(t *testing.T) {
|
||||
assert.Equal(t, `video/mp4; codecs="hvc1"`, ContentType(fs.MimeTypeMP4, CodecHVC))
|
||||
assert.Equal(t, `video/mp4; codecs="hvc1"`, ContentType(fs.MimeTypeMP4, CodecHEVC))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ func Probe(file io.ReadSeeker) (info Info, err error) {
|
|||
// Detect codec by searching for matching chunks.
|
||||
if info.VideoCodec == "" {
|
||||
if found, _ := ChunkHVC1.DataOffset(file); found > 0 {
|
||||
info.VideoCodec = CodecHVC
|
||||
info.VideoCodec = CodecHEVC
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func TestProbeFile(t *testing.T) {
|
|||
assert.Equal(t, int64(0), info.VideoOffset)
|
||||
assert.Equal(t, int64(-1), info.ThumbOffset)
|
||||
assert.Equal(t, media.Video, info.MediaType)
|
||||
assert.Equal(t, CodecHVC, info.VideoCodec)
|
||||
assert.Equal(t, CodecHEVC, info.VideoCodec)
|
||||
assert.Equal(t, fs.MimeTypeMOV, info.VideoMimeType)
|
||||
assert.Equal(t, ContentTypeMOV+`; codecs="hvc1"`, info.VideoContentType())
|
||||
assert.Equal(t, "1.166666666s", info.Duration.String())
|
||||
|
|
|
|||
|
|
@ -2,30 +2,34 @@ package video
|
|||
|
||||
// Types maps identifiers to standards.
|
||||
var Types = Standards{
|
||||
"": AVC,
|
||||
"mp4": MP4,
|
||||
"mpeg4": MP4,
|
||||
"avc": AVC,
|
||||
"avc1": AVC,
|
||||
"hvc": HEVC,
|
||||
"hvc1": HEVC,
|
||||
"hevc": HEVC,
|
||||
"hevC": HEVC,
|
||||
"evc": EVC,
|
||||
"evc1": EVC,
|
||||
"evcC": EVC,
|
||||
"vvc": VVC,
|
||||
"vvc1": VVC,
|
||||
"vvcC": VVC,
|
||||
"vp8": VP8,
|
||||
"vp80": VP8,
|
||||
"vp9": VP9,
|
||||
"vp90": VP9,
|
||||
"av1": AV1,
|
||||
"av01": AV1,
|
||||
"ogg": OGV,
|
||||
"ogv": OGV,
|
||||
"webm": WebM,
|
||||
"": AVC,
|
||||
"mp4": MP4,
|
||||
"mpeg4": MP4,
|
||||
"avc": AVC,
|
||||
"avc1": AVC,
|
||||
"hevc": HEVC,
|
||||
"hevC": HEVC,
|
||||
"hvc": HEVC,
|
||||
"hvc1": HEVC,
|
||||
"v_hvc": HEVC,
|
||||
"v_hvc1": HEVC,
|
||||
"hev": HEVC,
|
||||
"hev1": HEVC,
|
||||
"evc": EVC,
|
||||
"evc1": EVC,
|
||||
"evcC": EVC,
|
||||
"vvc": VVC,
|
||||
"vvc1": VVC,
|
||||
"vvcC": VVC,
|
||||
"vp8": VP8,
|
||||
"vp80": VP8,
|
||||
"vp9": VP9,
|
||||
"vp90": VP9,
|
||||
"av1": AV1,
|
||||
"av01": AV1,
|
||||
"ogg": OGV,
|
||||
"ogv": OGV,
|
||||
"webm": WebM,
|
||||
}
|
||||
|
||||
// Standards maps names to standardized formats.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ var AVC = Type{
|
|||
|
||||
// HEVC aka High Efficiency Video Coding (H.265).
|
||||
var HEVC = Type{
|
||||
Codec: CodecHVC,
|
||||
Codec: CodecHEVC,
|
||||
FileType: fs.VideoHEVC,
|
||||
WidthLimit: 0,
|
||||
HeightLimit: 0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue