Skip to content

Commit a6d8400

Browse files
committed
Media: Log underlying error when MIME type detection fails photoprism#5149
Signed-off-by: Michael Mayer <michael@photoprism.app>
1 parent 0d24ec5 commit a6d8400

File tree

8 files changed

+139
-81
lines changed

8 files changed

+139
-81
lines changed

internal/photoprism/mediafile.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type MediaFile struct {
4343
fileNameResolved string
4444
fileRoot string
4545
statErr error
46+
mimeErr error
4647
modTime time.Time
4748
fileSize int64
4849
fileType fs.Type
@@ -518,24 +519,36 @@ func (m *MediaFile) Root() string {
518519
// since media types have become used in contexts unrelated to email, such as HTTP:
519520
// https://en.wikipedia.org/wiki/Media_type#Structure
520521
func (m *MediaFile) MimeType() string {
521-
if m.mimeType != "" {
522+
// Do not detect the MIME type again if it is already known,
523+
// or if the detection failed.
524+
if m.mimeType != "" || m.mimeErr != nil {
522525
return m.mimeType
523526
}
524527

525528
var err error
526-
fileName := m.FileName()
527529

528-
// Resolve symlinks.
530+
// Get the filename and resolve symbolic links, if necessary.
531+
fileName := m.FileName()
529532
if fileName, err = fs.Resolve(fileName); err != nil {
530533
return m.mimeType
531534
}
532535

533-
m.mimeType = fs.MimeType(fileName)
536+
// Detect the file's MIME type based on its content and file extension.
537+
m.mimeType, err = fs.DetectMimeType(fileName)
538+
539+
// Log and remember the error if the MIME type detection has failed.
540+
if err != nil {
541+
log.Errorf("media: failed to detect mime type of %s (%s)", clean.Log(m.RootRelName()), clean.Error(err))
542+
m.mimeErr = err
543+
return m.mimeType
544+
}
534545

546+
// Adjust the MIME type for MP4 files containing MPEG-2 transport streams.
535547
if m.mimeType == header.ContentTypeMp4 && m.MetaData().Codec == video.CodecM2TS {
536548
m.mimeType = header.ContentTypeM2TS
537549
}
538550

551+
// Return MIME type.
539552
return m.mimeType
540553
}
541554

@@ -930,9 +943,9 @@ func (m *MediaFile) CheckType() error {
930943
return nil
931944
}
932945

933-
// Exclude mime type from the error message if it could not be detected.
946+
// If the MIME type is empty, it is usually because the file could not be read.
934947
if mimeType == fs.MimeTypeUnknown {
935-
return fmt.Errorf("has an invalid extension (unknown media type)")
948+
return fmt.Errorf("could not be identified")
936949
}
937950

938951
return fmt.Errorf("has an invalid extension for media type %s", clean.LogQuote(mimeType))

internal/photoprism/mediafile_thumbs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (m *MediaFile) GenerateThumbnails(thumbPath string, force bool) (err error)
149149
msg := imgErr.Error()
150150

151151
// Non-repairable file error?
152-
if !(strings.Contains(msg, "EOF") ||
152+
if !(strings.Contains(msg, fs.EOF.Error()) ||
153153
strings.HasPrefix(msg, "invalid JPEG")) {
154154
log.Debugf("media: %s in %s", msg, clean.Log(m.RootRelName()))
155155
return imgErr

internal/thumb/frame/collage_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestCollage(t *testing.T) {
3232
err = imaging.Save(preview, saveName)
3333

3434
assert.NoError(t, err)
35-
mimeType := fs.MimeType(saveName)
35+
mimeType, _ := fs.DetectMimeType(saveName)
3636
assert.Equal(t, header.ContentTypeJpeg, mimeType)
3737

3838
_ = os.Remove(saveName)
@@ -56,7 +56,7 @@ func TestCollage(t *testing.T) {
5656
err = imaging.Save(preview, saveName)
5757

5858
assert.NoError(t, err)
59-
mimeType := fs.MimeType(saveName)
59+
mimeType, _ := fs.DetectMimeType(saveName)
6060
assert.Equal(t, header.ContentTypeJpeg, mimeType)
6161

6262
_ = os.Remove(saveName)
@@ -73,7 +73,7 @@ func TestCollage(t *testing.T) {
7373
err = imaging.Save(preview, saveName)
7474

7575
assert.NoError(t, err)
76-
mimeType := fs.MimeType(saveName)
76+
mimeType, _ := fs.DetectMimeType(saveName)
7777
assert.Equal(t, header.ContentTypeJpeg, mimeType)
7878

7979
_ = os.Remove(saveName)
@@ -100,7 +100,7 @@ func TestCollage(t *testing.T) {
100100

101101
assert.NoError(t, err)
102102

103-
mimeType := fs.MimeType(saveName)
103+
mimeType, _ := fs.DetectMimeType(saveName)
104104
assert.Equal(t, header.ContentTypeJpeg, mimeType)
105105

106106
_ = os.Remove(saveName)

internal/thumb/frame/image_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func TestImage(t *testing.T) {
2626
err = imaging.Save(out, saveName)
2727

2828
assert.NoError(t, err)
29-
mimeType := fs.MimeType(saveName)
29+
mimeType, _ := fs.DetectMimeType(saveName)
3030
assert.Equal(t, header.ContentTypePng, mimeType)
3131

3232
_ = os.Remove(saveName)
@@ -46,7 +46,7 @@ func TestImage(t *testing.T) {
4646
err = imaging.Save(out, saveName)
4747

4848
assert.NoError(t, err)
49-
mimeType := fs.MimeType(saveName)
49+
mimeType, _ := fs.DetectMimeType(saveName)
5050
assert.Equal(t, header.ContentTypePng, mimeType)
5151

5252
_ = os.Remove(saveName)

internal/thumb/frame/polaroid_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func TestPolaroid(t *testing.T) {
2626
err = imaging.Save(out, saveName)
2727

2828
assert.NoError(t, err)
29-
mimeType := fs.MimeType(saveName)
29+
mimeType, _ := fs.DetectMimeType(saveName)
3030
assert.Equal(t, header.ContentTypePng, mimeType)
3131

3232
_ = os.Remove(saveName)

pkg/fs/errors.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package fs
2+
3+
import (
4+
"errors"
5+
"io"
6+
)
7+
8+
// Generic errors that may occur when accessing files and folders:
9+
var (
10+
EOF = io.EOF
11+
ErrUnexpectedEOF = io.ErrUnexpectedEOF
12+
ErrShortWrite = io.ErrShortWrite
13+
ErrShortBuffer = io.ErrShortBuffer
14+
ErrNoProgress = io.ErrNoProgress
15+
ErrInvalidWrite = errors.New("invalid write result")
16+
ErrPermissionDenied = errors.New("permission denied")
17+
)

pkg/fs/mime.go

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fs
22

33
import (
4+
"errors"
45
"path/filepath"
56
"strings"
67

@@ -13,14 +14,16 @@ const (
1314
MimeTypeUnknown = ""
1415
)
1516

16-
// MimeType returns the mimetype of a file, or an empty string if it could not be determined.
17+
// DetectMimeType returns the MIME type of the specified file,
18+
// or an error if the type could not be detected.
1719
//
1820
// The IANA and IETF use the term "media type", and consider the term "MIME type" to be obsolete,
1921
// since media types have become used in contexts unrelated to email, such as HTTP:
2022
// https://en.wikipedia.org/wiki/Media_type#Structure
21-
func MimeType(filename string) (mimeType string) {
23+
func DetectMimeType(filename string) (mimeType string, err error) {
24+
// Abort if no filename was specified.
2225
if filename == "" {
23-
return MimeTypeUnknown
26+
return MimeTypeUnknown, errors.New("missing filename")
2427
}
2528

2629
// Detect file type based on the filename extension.
@@ -31,44 +34,51 @@ func MimeType(filename string) (mimeType string) {
3134
switch fileType {
3235
// MPEG-2 Transport Stream
3336
case VideoM2TS, VideoAVCHD:
34-
return header.ContentTypeM2TS
37+
return header.ContentTypeM2TS, nil
3538
// Apple QuickTime Container
3639
case VideoMov:
37-
return header.ContentTypeMov
40+
return header.ContentTypeMov, nil
3841
// MPEG-4 AVC Video
3942
case VideoAvc:
40-
return header.ContentTypeMp4Avc
43+
return header.ContentTypeMp4Avc, nil
4144
// MPEG-4 HEVC Video
4245
case VideoHvc:
43-
return header.ContentTypeMp4Hvc
46+
return header.ContentTypeMp4Hvc, nil
4447
// MPEG-4 HEVC Bitstream
4548
case VideoHev:
46-
return header.ContentTypeMp4Hev
49+
return header.ContentTypeMp4Hev, nil
4750
// Adobe Digital Negative
4851
case ImageDng:
49-
return header.ContentTypeDng
52+
return header.ContentTypeDng, nil
5053
// Adobe Illustrator
5154
case VectorAI:
52-
return header.ContentTypeAI
55+
return header.ContentTypeAI, nil
5356
// Adobe PostScript
5457
case VectorPS:
55-
return header.ContentTypePS
58+
return header.ContentTypePS, nil
5659
// Adobe Embedded PostScript
5760
case VectorEPS:
58-
return header.ContentTypeEPS
61+
return header.ContentTypeEPS, nil
5962
// Adobe PDF
6063
case DocumentPDF:
61-
return header.ContentTypePDF
64+
return header.ContentTypePDF, nil
6265
// Scalable Vector Graphics
6366
case VectorSVG:
64-
return header.ContentTypeSVG
67+
return header.ContentTypeSVG, nil
6568
}
6669

67-
// Detect mime type based on the file content.
70+
// Use "gabriel-vasile/mimetype" to automatically detect the MIME type.
6871
detectedType, err := mimetype.DetectFile(filename)
6972

70-
if detectedType != nil && err == nil {
71-
mimeType = detectedType.String()
73+
// Check if type could be successfully detected.
74+
if err == nil {
75+
if detectedType != nil {
76+
mimeType = detectedType.String()
77+
}
78+
} else if e := err.Error(); strings.HasSuffix(e, ErrPermissionDenied.Error()) {
79+
return MimeTypeUnknown, ErrPermissionDenied
80+
} else if strings.Contains(e, EOF.Error()) {
81+
return MimeTypeUnknown, ErrUnexpectedEOF
7282
}
7383

7484
// Treat "application/octet-stream" as unknown.
@@ -81,25 +91,32 @@ func MimeType(filename string) (mimeType string) {
8191
switch fileType {
8292
// MPEG-4 Multimedia Container
8393
case VideoMp4:
84-
return header.ContentTypeMp4
94+
return header.ContentTypeMp4, nil
8595
// AV1 Image File
8696
case ImageAvif:
87-
return header.ContentTypeAvif
97+
return header.ContentTypeAvif, nil
8898
// AV1 Image File Sequence
8999
case ImageAvifS:
90-
return header.ContentTypeAvifS
100+
return header.ContentTypeAvifS, nil
91101
// High Efficiency Image Container
92102
case ImageHeic, ImageHeif:
93-
return header.ContentTypeHeic
103+
return header.ContentTypeHeic, nil
94104
// High Efficiency Image Container Sequence
95105
case ImageHeicS:
96-
return header.ContentTypeHeicS
106+
return header.ContentTypeHeicS, nil
97107
// ZIP Archive File:
98108
case ArchiveZip:
99-
return header.ContentTypeZip
109+
return header.ContentTypeZip, nil
100110
}
101111
}
102112

113+
return mimeType, err
114+
}
115+
116+
// MimeType returns the MIME type of the specified file,
117+
// or an empty string if the type could not be detected.
118+
func MimeType(filename string) (mimeType string) {
119+
mimeType, _ = DetectMimeType(filename)
103120
return mimeType
104121
}
105122

0 commit comments

Comments
 (0)