mirror of
https://github.com/photoprism/photoprism.git
synced 2025-09-26 21:01:58 +08:00
Media: Log underlying error when MIME type detection fails #5149
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -43,6 +43,7 @@ type MediaFile struct {
|
||||
fileNameResolved string
|
||||
fileRoot string
|
||||
statErr error
|
||||
mimeErr error
|
||||
modTime time.Time
|
||||
fileSize int64
|
||||
fileType fs.Type
|
||||
@@ -518,24 +519,36 @@ func (m *MediaFile) Root() string {
|
||||
// since media types have become used in contexts unrelated to email, such as HTTP:
|
||||
// https://en.wikipedia.org/wiki/Media_type#Structure
|
||||
func (m *MediaFile) MimeType() string {
|
||||
if m.mimeType != "" {
|
||||
// Do not detect the MIME type again if it is already known,
|
||||
// or if the detection failed.
|
||||
if m.mimeType != "" || m.mimeErr != nil {
|
||||
return m.mimeType
|
||||
}
|
||||
|
||||
var err error
|
||||
fileName := m.FileName()
|
||||
|
||||
// Resolve symlinks.
|
||||
// Get the filename and resolve symbolic links, if necessary.
|
||||
fileName := m.FileName()
|
||||
if fileName, err = fs.Resolve(fileName); err != nil {
|
||||
return m.mimeType
|
||||
}
|
||||
|
||||
m.mimeType = fs.MimeType(fileName)
|
||||
// Detect the file's MIME type based on its content and file extension.
|
||||
m.mimeType, err = fs.DetectMimeType(fileName)
|
||||
|
||||
// Log and remember the error if the MIME type detection has failed.
|
||||
if err != nil {
|
||||
log.Errorf("media: failed to detect mime type of %s (%s)", clean.Log(m.RootRelName()), clean.Error(err))
|
||||
m.mimeErr = err
|
||||
return m.mimeType
|
||||
}
|
||||
|
||||
// Adjust the MIME type for MP4 files containing MPEG-2 transport streams.
|
||||
if m.mimeType == header.ContentTypeMp4 && m.MetaData().Codec == video.CodecM2TS {
|
||||
m.mimeType = header.ContentTypeM2TS
|
||||
}
|
||||
|
||||
// Return MIME type.
|
||||
return m.mimeType
|
||||
}
|
||||
|
||||
@@ -930,9 +943,9 @@ func (m *MediaFile) CheckType() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exclude mime type from the error message if it could not be detected.
|
||||
// If the MIME type is empty, it is usually because the file could not be read.
|
||||
if mimeType == fs.MimeTypeUnknown {
|
||||
return fmt.Errorf("has an invalid extension (unknown media type)")
|
||||
return fmt.Errorf("could not be identified")
|
||||
}
|
||||
|
||||
return fmt.Errorf("has an invalid extension for media type %s", clean.LogQuote(mimeType))
|
||||
|
@@ -149,7 +149,7 @@ func (m *MediaFile) GenerateThumbnails(thumbPath string, force bool) (err error)
|
||||
msg := imgErr.Error()
|
||||
|
||||
// Non-repairable file error?
|
||||
if !(strings.Contains(msg, "EOF") ||
|
||||
if !(strings.Contains(msg, fs.EOF.Error()) ||
|
||||
strings.HasPrefix(msg, "invalid JPEG")) {
|
||||
log.Debugf("media: %s in %s", msg, clean.Log(m.RootRelName()))
|
||||
return imgErr
|
||||
|
@@ -32,7 +32,7 @@ func TestCollage(t *testing.T) {
|
||||
err = imaging.Save(preview, saveName)
|
||||
|
||||
assert.NoError(t, err)
|
||||
mimeType := fs.MimeType(saveName)
|
||||
mimeType, _ := fs.DetectMimeType(saveName)
|
||||
assert.Equal(t, header.ContentTypeJpeg, mimeType)
|
||||
|
||||
_ = os.Remove(saveName)
|
||||
@@ -56,7 +56,7 @@ func TestCollage(t *testing.T) {
|
||||
err = imaging.Save(preview, saveName)
|
||||
|
||||
assert.NoError(t, err)
|
||||
mimeType := fs.MimeType(saveName)
|
||||
mimeType, _ := fs.DetectMimeType(saveName)
|
||||
assert.Equal(t, header.ContentTypeJpeg, mimeType)
|
||||
|
||||
_ = os.Remove(saveName)
|
||||
@@ -73,7 +73,7 @@ func TestCollage(t *testing.T) {
|
||||
err = imaging.Save(preview, saveName)
|
||||
|
||||
assert.NoError(t, err)
|
||||
mimeType := fs.MimeType(saveName)
|
||||
mimeType, _ := fs.DetectMimeType(saveName)
|
||||
assert.Equal(t, header.ContentTypeJpeg, mimeType)
|
||||
|
||||
_ = os.Remove(saveName)
|
||||
@@ -100,7 +100,7 @@ func TestCollage(t *testing.T) {
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
mimeType := fs.MimeType(saveName)
|
||||
mimeType, _ := fs.DetectMimeType(saveName)
|
||||
assert.Equal(t, header.ContentTypeJpeg, mimeType)
|
||||
|
||||
_ = os.Remove(saveName)
|
||||
|
@@ -26,7 +26,7 @@ func TestImage(t *testing.T) {
|
||||
err = imaging.Save(out, saveName)
|
||||
|
||||
assert.NoError(t, err)
|
||||
mimeType := fs.MimeType(saveName)
|
||||
mimeType, _ := fs.DetectMimeType(saveName)
|
||||
assert.Equal(t, header.ContentTypePng, mimeType)
|
||||
|
||||
_ = os.Remove(saveName)
|
||||
@@ -46,7 +46,7 @@ func TestImage(t *testing.T) {
|
||||
err = imaging.Save(out, saveName)
|
||||
|
||||
assert.NoError(t, err)
|
||||
mimeType := fs.MimeType(saveName)
|
||||
mimeType, _ := fs.DetectMimeType(saveName)
|
||||
assert.Equal(t, header.ContentTypePng, mimeType)
|
||||
|
||||
_ = os.Remove(saveName)
|
||||
|
@@ -26,7 +26,7 @@ func TestPolaroid(t *testing.T) {
|
||||
err = imaging.Save(out, saveName)
|
||||
|
||||
assert.NoError(t, err)
|
||||
mimeType := fs.MimeType(saveName)
|
||||
mimeType, _ := fs.DetectMimeType(saveName)
|
||||
assert.Equal(t, header.ContentTypePng, mimeType)
|
||||
|
||||
_ = os.Remove(saveName)
|
||||
|
17
pkg/fs/errors.go
Normal file
17
pkg/fs/errors.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Generic errors that may occur when accessing files and folders:
|
||||
var (
|
||||
EOF = io.EOF
|
||||
ErrUnexpectedEOF = io.ErrUnexpectedEOF
|
||||
ErrShortWrite = io.ErrShortWrite
|
||||
ErrShortBuffer = io.ErrShortBuffer
|
||||
ErrNoProgress = io.ErrNoProgress
|
||||
ErrInvalidWrite = errors.New("invalid write result")
|
||||
ErrPermissionDenied = errors.New("permission denied")
|
||||
)
|
@@ -1,6 +1,7 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -13,14 +14,16 @@ const (
|
||||
MimeTypeUnknown = ""
|
||||
)
|
||||
|
||||
// MimeType returns the mimetype of a file, or an empty string if it could not be determined.
|
||||
// DetectMimeType returns the MIME type of the specified file,
|
||||
// or an error if the type could not be detected.
|
||||
//
|
||||
// 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) {
|
||||
func DetectMimeType(filename string) (mimeType string, err error) {
|
||||
// Abort if no filename was specified.
|
||||
if filename == "" {
|
||||
return MimeTypeUnknown
|
||||
return MimeTypeUnknown, errors.New("missing filename")
|
||||
}
|
||||
|
||||
// Detect file type based on the filename extension.
|
||||
@@ -31,44 +34,51 @@ func MimeType(filename string) (mimeType string) {
|
||||
switch fileType {
|
||||
// MPEG-2 Transport Stream
|
||||
case VideoM2TS, VideoAVCHD:
|
||||
return header.ContentTypeM2TS
|
||||
return header.ContentTypeM2TS, nil
|
||||
// Apple QuickTime Container
|
||||
case VideoMov:
|
||||
return header.ContentTypeMov
|
||||
return header.ContentTypeMov, nil
|
||||
// MPEG-4 AVC Video
|
||||
case VideoAvc:
|
||||
return header.ContentTypeMp4Avc
|
||||
return header.ContentTypeMp4Avc, nil
|
||||
// MPEG-4 HEVC Video
|
||||
case VideoHvc:
|
||||
return header.ContentTypeMp4Hvc
|
||||
return header.ContentTypeMp4Hvc, nil
|
||||
// MPEG-4 HEVC Bitstream
|
||||
case VideoHev:
|
||||
return header.ContentTypeMp4Hev
|
||||
return header.ContentTypeMp4Hev, nil
|
||||
// Adobe Digital Negative
|
||||
case ImageDng:
|
||||
return header.ContentTypeDng
|
||||
return header.ContentTypeDng, nil
|
||||
// Adobe Illustrator
|
||||
case VectorAI:
|
||||
return header.ContentTypeAI
|
||||
return header.ContentTypeAI, nil
|
||||
// Adobe PostScript
|
||||
case VectorPS:
|
||||
return header.ContentTypePS
|
||||
return header.ContentTypePS, nil
|
||||
// Adobe Embedded PostScript
|
||||
case VectorEPS:
|
||||
return header.ContentTypeEPS
|
||||
return header.ContentTypeEPS, nil
|
||||
// Adobe PDF
|
||||
case DocumentPDF:
|
||||
return header.ContentTypePDF
|
||||
return header.ContentTypePDF, nil
|
||||
// Scalable Vector Graphics
|
||||
case VectorSVG:
|
||||
return header.ContentTypeSVG
|
||||
return header.ContentTypeSVG, nil
|
||||
}
|
||||
|
||||
// Detect mime type based on the file content.
|
||||
// Use "gabriel-vasile/mimetype" to automatically detect the MIME type.
|
||||
detectedType, err := mimetype.DetectFile(filename)
|
||||
|
||||
if detectedType != nil && err == nil {
|
||||
mimeType = detectedType.String()
|
||||
// Check if type could be successfully detected.
|
||||
if err == nil {
|
||||
if detectedType != nil {
|
||||
mimeType = detectedType.String()
|
||||
}
|
||||
} else if e := err.Error(); strings.HasSuffix(e, ErrPermissionDenied.Error()) {
|
||||
return MimeTypeUnknown, ErrPermissionDenied
|
||||
} else if strings.Contains(e, EOF.Error()) {
|
||||
return MimeTypeUnknown, ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// Treat "application/octet-stream" as unknown.
|
||||
@@ -81,25 +91,32 @@ func MimeType(filename string) (mimeType string) {
|
||||
switch fileType {
|
||||
// MPEG-4 Multimedia Container
|
||||
case VideoMp4:
|
||||
return header.ContentTypeMp4
|
||||
return header.ContentTypeMp4, nil
|
||||
// AV1 Image File
|
||||
case ImageAvif:
|
||||
return header.ContentTypeAvif
|
||||
return header.ContentTypeAvif, nil
|
||||
// AV1 Image File Sequence
|
||||
case ImageAvifS:
|
||||
return header.ContentTypeAvifS
|
||||
return header.ContentTypeAvifS, nil
|
||||
// High Efficiency Image Container
|
||||
case ImageHeic, ImageHeif:
|
||||
return header.ContentTypeHeic
|
||||
return header.ContentTypeHeic, nil
|
||||
// High Efficiency Image Container Sequence
|
||||
case ImageHeicS:
|
||||
return header.ContentTypeHeicS
|
||||
return header.ContentTypeHeicS, nil
|
||||
// ZIP Archive File:
|
||||
case ArchiveZip:
|
||||
return header.ContentTypeZip
|
||||
return header.ContentTypeZip, nil
|
||||
}
|
||||
}
|
||||
|
||||
return mimeType, err
|
||||
}
|
||||
|
||||
// MimeType returns the MIME type of the specified file,
|
||||
// or an empty string if the type could not be detected.
|
||||
func MimeType(filename string) (mimeType string) {
|
||||
mimeType, _ = DetectMimeType(filename)
|
||||
return mimeType
|
||||
}
|
||||
|
||||
|
@@ -8,147 +8,158 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/media/http/header"
|
||||
)
|
||||
|
||||
func TestMimeType(t *testing.T) {
|
||||
t.Run("Mp4", func(t *testing.T) {
|
||||
func TestDetectMimeType(t *testing.T) {
|
||||
t.Run("MP4", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.mp4")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "video/mp4", mimeType)
|
||||
})
|
||||
t.Run("MOV", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.mov")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "video/quicktime", mimeType)
|
||||
assert.Equal(t, "video/quicktime", MimeType(filename))
|
||||
})
|
||||
t.Run("JPEG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.jpg")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/jpeg", mimeType)
|
||||
assert.Equal(t, "image/jpeg", MimeType(filename))
|
||||
})
|
||||
t.Run("InvalidFilename", func(t *testing.T) {
|
||||
filename := Abs("./testdata/xxx.jpg")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "", mimeType)
|
||||
assert.Equal(t, "", MimeType(filename))
|
||||
})
|
||||
t.Run("EmptyFilename", func(t *testing.T) {
|
||||
mimeType := MimeType("")
|
||||
mimeType, _ := DetectMimeType("")
|
||||
assert.Equal(t, "", mimeType)
|
||||
assert.Equal(t, "", MimeType(""))
|
||||
})
|
||||
t.Run("AVIF", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.avif")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/avif", mimeType)
|
||||
assert.Equal(t, "image/avif", MimeType(filename))
|
||||
})
|
||||
t.Run("AVIFS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.avifs")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/avif-sequence", mimeType)
|
||||
assert.Equal(t, "image/avif-sequence", MimeType(filename))
|
||||
})
|
||||
t.Run("HEIC", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.heic")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/heic", mimeType)
|
||||
assert.Equal(t, "image/heic", MimeType(filename))
|
||||
})
|
||||
t.Run("HEICS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.heics")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/heic-sequence", mimeType)
|
||||
})
|
||||
t.Run("DNG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.dng")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/dng", mimeType)
|
||||
})
|
||||
t.Run("SVG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.svg")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/svg+xml", mimeType)
|
||||
assert.Equal(t, "image/svg+xml", MimeType(filename))
|
||||
})
|
||||
t.Run("AI", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.ai")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "application/vnd.adobe.illustrator", mimeType)
|
||||
assert.Equal(t, "application/vnd.adobe.illustrator", MimeType(filename))
|
||||
})
|
||||
t.Run("PS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.ps")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "application/postscript", mimeType)
|
||||
assert.Equal(t, "application/postscript", MimeType(filename))
|
||||
})
|
||||
t.Run("EPS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.eps")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/eps", mimeType)
|
||||
assert.Equal(t, "image/eps", MimeType(filename))
|
||||
})
|
||||
}
|
||||
|
||||
func TestBaseType(t *testing.T) {
|
||||
t.Run("Mp4", func(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)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "video/mp4", result)
|
||||
})
|
||||
t.Run("MOV", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.mov")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "video/quicktime", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "video/quicktime", result)
|
||||
})
|
||||
t.Run("JPEG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.jpg")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/jpeg", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/jpeg", result)
|
||||
})
|
||||
t.Run("InvalidFilename", func(t *testing.T) {
|
||||
filename := Abs("./testdata/xxx.jpg")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "", result)
|
||||
})
|
||||
t.Run("EmptyFilename", func(t *testing.T) {
|
||||
mimeType := BaseType("")
|
||||
assert.Equal(t, "", mimeType)
|
||||
assert.Equal(t, "", BaseType(""))
|
||||
})
|
||||
t.Run("AVIF", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.avif")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/avif", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/avif", result)
|
||||
})
|
||||
t.Run("AVIFS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.avifs")
|
||||
mimeType := MimeType(filename)
|
||||
mimeType, _ := DetectMimeType(filename)
|
||||
assert.Equal(t, "image/avif-sequence", mimeType)
|
||||
assert.Equal(t, "image/avif-sequence", BaseType(mimeType))
|
||||
})
|
||||
t.Run("HEIC", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.heic")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/heic", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/heic", result)
|
||||
})
|
||||
t.Run("HEICS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.heics")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/heic-sequence", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/heic-sequence", result)
|
||||
})
|
||||
t.Run("DNG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.dng")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/dng", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/dng", result)
|
||||
})
|
||||
t.Run("SVG", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.svg")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/svg+xml", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/svg+xml", result)
|
||||
})
|
||||
t.Run("AI", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.ai")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "application/vnd.adobe.illustrator", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "application/vnd.adobe.illustrator", result)
|
||||
})
|
||||
t.Run("PS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.ps")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "application/postscript", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "application/postscript", result)
|
||||
})
|
||||
t.Run("EPS", func(t *testing.T) {
|
||||
filename := Abs("./testdata/test.eps")
|
||||
mimeType := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/eps", mimeType)
|
||||
result := BaseType(MimeType(filename))
|
||||
assert.Equal(t, "image/eps", result)
|
||||
})
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user