Files
photoprism/pkg/fs/mime.go
2025-11-22 16:14:43 +01:00

140 lines
3.7 KiB
Go

package fs
import (
"errors"
"path/filepath"
"strings"
"github.com/gabriel-vasile/mimetype"
"github.com/photoprism/photoprism/pkg/http/header"
)
const (
// MimeTypeUnknown represents an unknown mime type.
MimeTypeUnknown = ""
)
// 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 DetectMimeType(filename string) (mimeType string, err error) {
// Abort if no filename was specified.
if filename == "" {
return MimeTypeUnknown, errors.New("missing filename")
}
// Detect file type based on the filename extension.
fileType := Extensions[strings.ToLower(filepath.Ext(filename))]
// Determine mime type based on the extension for the following
// formats, which otherwise cannot be reliably distinguished:
switch fileType {
// MPEG-2 Transport Stream
case VideoM2TS, VideoAVCHD:
return header.ContentTypeM2TS, nil
// Apple QuickTime Container
case VideoMov:
return header.ContentTypeMov, nil
// MPEG-4 AVC Video
case VideoAvc:
return header.ContentTypeMp4Avc, nil
// MPEG-4 HEVC Video
case VideoHvc:
return header.ContentTypeMp4Hvc, nil
// MPEG-4 HEVC Bitstream
case VideoHev:
return header.ContentTypeMp4Hev, nil
// Adobe Digital Negative
case ImageDng:
return header.ContentTypeDng, nil
// Adobe Illustrator
case VectorAI:
return header.ContentTypeAI, nil
// Adobe PostScript
case VectorPS:
return header.ContentTypePS, nil
// Adobe Embedded PostScript
case VectorEPS:
return header.ContentTypeEPS, nil
// Scalable Vector Graphics
case VectorSVG:
return header.ContentTypeSVG, nil
}
// Use "gabriel-vasile/mimetype" to automatically detect the MIME type.
detectedType, err := mimetype.DetectFile(filename)
// 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.
if mimeType == header.ContentTypeBinary {
mimeType = MimeTypeUnknown
}
// If it could be detected, try to determine mime type from extension:
if mimeType == MimeTypeUnknown {
switch fileType {
// MPEG-4 Multimedia Container
case VideoMp4:
return header.ContentTypeMp4, nil
// AV1 Image File
case ImageAvif:
return header.ContentTypeAvif, nil
// AV1 Image File Sequence
case ImageAvifS:
return header.ContentTypeAvifS, nil
// High Efficiency Image Container
case ImageHeic, ImageHeif:
return header.ContentTypeHeic, nil
// High Efficiency Image Container Sequence
case ImageHeicS:
return header.ContentTypeHeicS, nil
// ZIP Archive File:
case ArchiveZip:
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
}
// 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)
}
// SameType tests if the specified mime types are matching, except for any optional parameters.
func SameType(mime1, mime2 string) bool {
if mime1 == mime2 {
return true
}
return BaseType(mime1) == BaseType(mime2)
}