Library: Add support for indexing PDF documents #4600

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-03-12 04:26:04 +01:00
parent a3914d6098
commit f747211017
26 changed files with 274 additions and 121 deletions

View File

@@ -316,6 +316,8 @@ export default class $util {
}
switch (value) {
case "pdf":
return "PDF";
case "jpg":
return "JPEG";
case media.FormatJpegXL:
@@ -391,8 +393,6 @@ export default class $util {
return "Windows Media";
case "svg":
return "SVG";
case "pdf":
return "PDF";
case "ai":
return "Adobe Illustrator";
case "ps":

View File

@@ -168,6 +168,18 @@
</v-list-item-title>
</v-list-item>
<v-list-item
:to="{ name: 'photos', query: { q: 'stacks' } }"
:exact="true"
variant="text"
class="nav-stacks"
@click.stop=""
>
<v-list-item-title :class="`nav-menu-item menu-item`">
{{ $gettext(`Stacks`) }}
</v-list-item-title>
</v-list-item>
<v-list-item
v-show="isSponsor"
:to="{ name: 'browse', query: { q: 'vectors' } }"
@@ -181,18 +193,6 @@
</v-list-item-title>
</v-list-item>
<v-list-item
:to="{ name: 'photos', query: { q: 'stacks' } }"
:exact="true"
variant="text"
class="nav-stacks"
@click.stop=""
>
<v-list-item-title :class="`nav-menu-item menu-item`">
{{ $gettext(`Stacks`) }}
</v-list-item-title>
</v-list-item>
<v-list-item
:to="{ name: 'photos', query: { q: 'scans' } }"
:exact="true"
@@ -205,6 +205,19 @@
</v-list-item-title>
</v-list-item>
<v-list-item
v-show="isSponsor"
:to="{ name: 'browse', query: { q: 'documents' } }"
:exact="true"
variant="text"
class="nav-documents"
@click.stop=""
>
<v-list-item-title :class="`nav-menu-item menu-item`">
{{ $gettext(`Documents`) }}
</v-list-item-title>
</v-list-item>
<v-list-item
v-if="canManagePhotos"
v-show="$config.feature('review')"

View File

@@ -172,6 +172,14 @@
}}</span>
</td>
</tr>
<tr v-if="file.FileType">
<td>
{{ $gettext(`Type`) }}
</td>
<td class="text-break">
<span v-tooltip="file?.Mime">{{ file.typeInfo() }}</span>
</td>
</tr>
<tr>
<td>
{{ $gettext(`Size`) }}
@@ -182,20 +190,18 @@
}}</span>
</td>
</tr>
<tr v-if="file.Pages">
<td>
{{ $gettext(`Pages`) }}
</td>
<td>{{ file.Pages }}</td>
</tr>
<tr v-if="file.Software">
<td>
{{ $gettext(`Software`) }}
</td>
<td class="text-break">{{ file.Software }}</td>
</tr>
<tr v-if="file.FileType">
<td>
{{ $gettext(`Type`) }}
</td>
<td class="text-break">
<span v-tooltip="file?.Mime">{{ file.typeInfo() }}</span>
</td>
</tr>
<tr v-if="file.isAnimated()">
<td>
{{ $gettext(`Animated`) }}

View File

@@ -88,8 +88,8 @@
<i class="mdi mdi-file-gif-box" />
{{ m.getVideoInfo() }}
</button>
<button v-else-if="m.Type === 'vector'" class="meta-vector text-truncate">
<i class="mdi mdi-vector-polyline" />
<button v-else-if="m.Type === 'document' || m.Type === 'vector'" class="meta-vector text-truncate">
<i class="mdi" :class="m.Type === 'document' ? 'mdi-text-box' : 'mdi-vector-polyline'" />
{{ m.getVectorInfo() }}
</button>
<button v-else class="meta-image text-truncate">
@@ -300,12 +300,12 @@
{{ m.getVideoInfo() }}
</button>
<button
v-else-if="m.Type === 'vector'"
:title="$gettext('Vector')"
v-else-if="m.Type === 'document' || m.Type === 'vector'"
:title="m.Type === 'document' ? $gettext('Document') : $gettext('Vector')"
class="meta-vector text-truncate"
@click.exact="editPhoto(index)"
>
<i class="mdi mdi-vector-polyline" />
<i class="mdi" :class="m.Type === 'document' ? 'mdi-text-box' : 'mdi-vector-polyline'" />
{{ m.getVectorInfo() }}
</button>
<button

File diff suppressed because one or more lines are too long

View File

@@ -59,6 +59,7 @@ export class File extends RestModel {
Duration: 0,
FPS: 0.0,
Frames: 0,
Pages: 0,
Width: 0,
Height: 0,
Orientation: 0,

View File

@@ -862,6 +862,11 @@ export class Photo extends RestModel {
return;
}
if (file?.Pages > 0) {
info.push(file.Pages + " " + $gettext("Pages"));
}
if (file?.MediaType !== media.Document) {
if (file.Width && file.Height) {
info.push(file.Width + " × " + file.Height);
} else if (!file.Primary) {
@@ -870,6 +875,7 @@ export class Photo extends RestModel {
info.push(primary.Width + " × " + primary.Height);
}
}
}
if (!file.Size) {
return;
@@ -883,7 +889,7 @@ export class Photo extends RestModel {
return this;
}
return this.Files.find((f) => f.MediaType === media.Vector || f.FileType === media.FormatSVG);
return this.Files.find((f) => f.MediaType === media.Document || f.MediaType === media.Vector || f.FileType === media.FormatSVG);
}
getVectorInfo = () => {
@@ -893,15 +899,15 @@ export class Photo extends RestModel {
generateVectorInfo = memoizeOne((file) => {
if (!file) {
return $gettext("Vector");
return $gettext("Unknown");
}
const info = [];
if (file.MediaType === media.Vector) {
if (file.MediaType === media.Vector || file.MediaType === media.Document) {
info.push($util.fileType(file.FileType));
} else {
info.push($gettext("Vector"));
info.push($gettext("Unknown"));
}
this.addSizeInfo(file, info);

View File

@@ -387,6 +387,10 @@ export const PhotoTypes = () => [
text: $gettext("Vector"),
value: media.Vector,
},
{
text: $gettext("Document"),
value: media.Document,
},
];
export const Timeouts = () => [

View File

@@ -66,6 +66,7 @@ type File struct {
FileDuration time.Duration `json:"Duration" yaml:"Duration,omitempty"`
FileFPS float64 `gorm:"column:file_fps;" json:"FPS" yaml:"FPS,omitempty"`
FileFrames int `gorm:"column:file_frames;" json:"Frames" yaml:"Frames,omitempty"`
FilePages int `gorm:"column:file_pages;default:0;" json:"Pages" yaml:"Pages,omitempty"`
FileWidth int `gorm:"column:file_width;" json:"Width" yaml:"Width,omitempty"`
FileHeight int `gorm:"column:file_height;" json:"Height" yaml:"Height,omitempty"`
FileOrientation int `gorm:"column:file_orientation;" json:"Orientation" yaml:"Orientation,omitempty"`
@@ -759,6 +760,15 @@ func (m *File) SetFrames(n int) {
}
}
// SetPages sets the number of document pages.
func (m *File) SetPages(n int) {
if n <= 0 {
return
}
m.FilePages = n
}
// SetMediaUTC sets the media creation date from metadata as unix time in ms.
func (m *File) SetMediaUTC(taken time.Time) {
if taken.IsZero() {

View File

@@ -31,6 +31,7 @@ func (m *File) MarshalJSON() ([]byte, error) {
Duration time.Duration `json:",omitempty"`
FPS float64 `json:",omitempty"`
Frames int `json:",omitempty"`
Pages int `json:",omitempty"`
Width int `json:",omitempty"`
Height int `json:",omitempty"`
Orientation int `json:",omitempty"`
@@ -78,6 +79,7 @@ func (m *File) MarshalJSON() ([]byte, error) {
Duration: m.FileDuration,
FPS: m.FileFPS,
Frames: m.FileFrames,
Pages: m.FilePages,
Width: m.FileWidth,
Height: m.FileHeight,
Orientation: m.FileOrientation,

View File

@@ -776,6 +776,26 @@ func TestFile_SetFrames(t *testing.T) {
})
}
func TestFile_SetPages(t *testing.T) {
t.Run("Success", func(t *testing.T) {
m := File{FilePages: 4}
assert.Equal(t, 4, m.FilePages)
m.SetPages(120)
assert.Equal(t, 120, m.FilePages)
m.SetPages(30)
assert.Equal(t, 30, m.FilePages)
m.SetPages(0)
assert.Equal(t, 30, m.FilePages)
})
}
func TestFile_SetDuration(t *testing.T) {
t.Run("FileFPS", func(t *testing.T) {
m := File{FileFPS: 20}

View File

@@ -64,7 +64,7 @@ func (m *Photo) UpdateQuality() error {
// IsNonPhotographic checks whether the image appears to be non-photographic.
func (m *Photo) IsNonPhotographic() (result bool) {
if m.PhotoType == MediaUnknown || m.PhotoType == MediaVector || m.PhotoType == MediaAnimated {
if m.PhotoType == MediaUnknown || m.PhotoType == MediaVector || m.PhotoType == MediaAnimated || m.PhotoType == MediaDocument {
return true
}

View File

@@ -322,6 +322,12 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
case terms["video"]:
frm.Query = strings.ReplaceAll(frm.Query, "video", "")
frm.Video = true
case terms["documents"]:
frm.Query = strings.ReplaceAll(frm.Query, "documents", "")
frm.Document = true
case terms["document"]:
frm.Query = strings.ReplaceAll(frm.Query, "document", "")
frm.Document = true
case terms["vectors"]:
frm.Query = strings.ReplaceAll(frm.Query, "vectors", "")
frm.Vector = true
@@ -640,7 +646,7 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
} else if frm.Video {
s = s.Where("photos.photo_type = ?", media.Video)
} else if frm.Photo {
s = s.Where("photos.photo_type IN ('image','live','animated','vector','raw')")
s = s.Where("photos.photo_type IN ('image','raw','live','animated','vector')")
}
// Filter by storage path.

View File

@@ -86,6 +86,7 @@ type Photo struct {
FileDuration time.Duration `json:"-" select:"files.file_duration"`
FileFPS float64 `json:"-" select:"files.file_fps"`
FileFrames int `json:"-" select:"files.file_frames"`
FilePages int `json:"-" select:"files.file_pages"`
FileCodec string `json:"-" select:"files.file_codec"`
FileType string `json:"-" select:"files.file_type"`
MediaType string `json:"-" select:"files.media_type"`

View File

@@ -32,6 +32,7 @@ type Data struct {
Duration time.Duration `meta:"Duration,MediaDuration,TrackDuration,PreviewDuration"`
FPS float64 `meta:"VideoFrameRate,VideoAvgFrameRate"`
Frames int `meta:"FrameCount,AnimationFrames"`
Pages int `meta:"PageCount,NPages,Pages"`
Codec string `meta:"CompressorID,VideoCodecID,CodecID,OtherFormat,FileType"`
Title string `meta:"Title,Headline" xmp:"dc:title" dc:"title,title.Alt"`
Caption string `meta:"Description,ImageDescription,Caption,Caption-Abstract" xmp:"Description,Description.Alt"`
@@ -50,7 +51,7 @@ type Data struct {
CameraSerial string `meta:"SerialNumber"`
LensMake string `meta:"LensMake"`
LensModel string `meta:"LensModel,Lens,LensID" xmp:"LensModel,Lens"`
Software string `meta:"Software,CreatorTool,HistorySoftwareAgent,ProcessingSoftware"`
Software string `meta:"Software,Producer,CreatorTool,Creator,CreatorSubTool,HistorySoftwareAgent,ProcessingSoftware"`
Flash bool `meta:"FlashFired"`
FocalLength int `meta:"FocalLength,FocalLengthIn35mmFormat"`
FocalDistance float64 `meta:"HyperfocalDistance"`

View File

@@ -109,14 +109,22 @@ func (w *Convert) JpegConvertCmds(f *MediaFile, jpegName string, xmpName string)
}
// Try ImageMagick for other image file formats if allowed.
if w.conf.ImageMagickEnabled() && w.imageMagickExclude.Allow(fileExt) &&
(f.IsImage() && !f.IsJpegXL() && !f.IsRaw() && !f.IsHeif() || f.IsVector() && w.conf.VectorEnabled()) {
if w.conf.ImageMagickEnabled() && w.imageMagickExclude.Allow(fileExt) {
if f.IsImage() && !f.IsJpegXL() && !f.IsRaw() && !f.IsHeif() || f.IsVector() && w.conf.VectorEnabled() {
quality := fmt.Sprintf("%d", w.conf.JpegQuality())
resize := fmt.Sprintf("%dx%d>", w.conf.JpegSize(), w.conf.JpegSize())
args := []string{f.FileName(), "-flatten", "-resize", resize, "-quality", quality, jpegName}
result = append(result, NewConvertCmd(
exec.Command(w.conf.ImageMagickBin(), args...)),
)
} else if f.IsDocument() {
quality := fmt.Sprintf("%d", w.conf.JpegQuality())
resize := fmt.Sprintf("%dx%d>", w.conf.JpegSize(), w.conf.JpegSize())
args := []string{f.FileName() + "[0]", "-background", "white", "-alpha", "remove", "-alpha", "off", "-resize", resize, "-quality", quality, jpegName}
result = append(result, NewConvertCmd(
exec.Command(w.conf.ImageMagickBin(), args...)),
)
}
}
// No suitable converter found?

View File

@@ -58,13 +58,20 @@ func (w *Convert) PngConvertCmds(f *MediaFile, pngName string) (result ConvertCm
result = append(result, NewConvertCmd(
exec.Command(w.conf.RsvgConvertBin(), args...)),
)
} else if w.conf.ImageMagickEnabled() && w.imageMagickExclude.Allow(fileExt) &&
(f.IsImage() && !f.IsJpegXL() && !f.IsRaw() && !f.IsHeif() || f.IsVector() && w.conf.VectorEnabled()) {
} else if w.conf.ImageMagickEnabled() && w.imageMagickExclude.Allow(fileExt) {
if f.IsImage() && !f.IsJpegXL() && !f.IsRaw() && !f.IsHeif() || f.IsVector() && w.conf.VectorEnabled() {
resize := fmt.Sprintf("%dx%d>", w.conf.PngSize(), w.conf.PngSize())
args := []string{f.FileName(), "-flatten", "-resize", resize, pngName}
result = append(result, NewConvertCmd(
exec.Command(w.conf.ImageMagickBin(), args...)),
)
} else if f.IsDocument() {
resize := fmt.Sprintf("%dx%d>", w.conf.PngSize(), w.conf.PngSize())
args := []string{f.FileName() + "[0]", "-background", "white", "-alpha", "remove", "-alpha", "off", "-resize", resize, pngName}
result = append(result, NewConvertCmd(
exec.Command(w.conf.ImageMagickBin(), args...)),
)
}
}
// No suitable converter found?

View File

@@ -521,6 +521,7 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
file.FileAspectRatio = m.AspectRatio()
file.FilePortrait = m.Portrait()
file.SetMediaUTC(data.TakenAt)
file.SetPages(data.Pages)
file.SetProjection(data.Projection)
file.SetHDR(data.IsHDR())
file.SetColorProfile(data.ColorProfile)
@@ -610,9 +611,7 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
file.FileAspectRatio = m.AspectRatio()
file.FilePortrait = m.Portrait()
file.SetMediaUTC(data.TakenAt)
file.SetDuration(data.Duration)
file.SetFPS(data.FPS)
file.SetFrames(data.Frames)
file.SetPages(data.Pages)
file.SetProjection(data.Projection)
file.SetHDR(data.IsHDR())
file.SetColorProfile(data.ColorProfile)
@@ -628,6 +627,60 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
if photo.TypeSrc == entity.SrcAuto {
photo.PhotoType = entity.MediaVector
}
case m.IsDocument():
if data := m.MetaData(); data.Error == nil {
photo.SetTitle(data.Title, entity.SrcMeta)
photo.SetCaption(data.Caption, entity.SrcMeta)
photo.SetTakenAt(data.TakenAt, data.TakenAtLocal, data.TimeZone, entity.SrcMeta)
// Update metadata details.
details.SetKeywords(data.Keywords.String(), entity.SrcMeta)
details.SetNotes(data.Notes, entity.SrcMeta)
details.SetSubject(data.Subject, entity.SrcMeta)
details.SetArtist(data.Artist, entity.SrcMeta)
details.SetCopyright(data.Copyright, entity.SrcMeta)
details.SetLicense(data.License, entity.SrcMeta)
details.SetSoftware(data.Software, entity.SrcMeta)
if data.HasDocumentID() && photo.UUID == "" {
log.Infof("index: %s has document_id %s", logName, clean.Log(data.DocumentID))
photo.UUID = data.DocumentID
}
if data.HasInstanceID() {
log.Infof("index: %s has instance_id %s", logName, clean.Log(data.InstanceID))
file.InstanceID = data.InstanceID
}
if file.OriginalName == "" && filepath.Base(file.FileName) != data.FileName {
file.OriginalName = data.FileName
if photo.OriginalName == "" {
photo.OriginalName = fs.StripKnownExt(data.FileName)
}
}
file.FileCodec = data.Codec
file.FileWidth = m.Width()
file.FileHeight = m.Height()
file.FileAspectRatio = m.AspectRatio()
file.FilePortrait = m.Portrait()
file.SetMediaUTC(data.TakenAt)
file.SetPages(data.Pages)
file.SetColorProfile(data.ColorProfile)
file.SetSoftware(data.Software)
// Set photo resolution based on the largest media file.
if res := m.Megapixels(); res > photo.PhotoResolution {
photo.PhotoResolution = res
}
}
// Update photo type if not manually modified.
if photo.TypeSrc == entity.SrcAuto {
photo.PhotoType = entity.MediaDocument
}
case m.IsVideo():
if data := m.MetaData(); data.Error == nil {
photo.SetTitle(data.Title, entity.SrcMeta)

View File

@@ -960,9 +960,9 @@ func (m *MediaFile) NotAnimated() bool {
return !m.IsAnimated()
}
// IsVideo returns true if this is a video file.
func (m *MediaFile) IsVideo() bool {
return m.HasMediaType(media.Video)
// IsDocument returns true if this is a document file.
func (m *MediaFile) IsDocument() bool {
return m.HasMediaType(media.Document)
}
// IsVector returns true if this is a vector graphics.
@@ -970,6 +970,11 @@ func (m *MediaFile) IsVector() bool {
return m.HasMediaType(media.Vector) || m.IsSVG()
}
// IsVideo returns true if this is a video file.
func (m *MediaFile) IsVideo() bool {
return m.HasMediaType(media.Video)
}
// IsSidecar checks if the file is a metadata sidecar file, independent of the storage location.
func (m *MediaFile) IsSidecar() bool {
return !m.Media().Main()
@@ -1055,7 +1060,7 @@ func (m *MediaFile) ExifSupported() bool {
// IsMedia returns true if this is a media file (photo or video, not sidecar or other).
func (m *MediaFile) IsMedia() bool {
return !m.IsThumb() && (m.IsImage() || m.IsRaw() || m.IsVideo() || m.IsVector())
return !m.IsThumb() && (m.IsImage() || m.IsRaw() || m.IsVideo() || m.IsVector() || m.IsDocument())
}
// PreviewImage returns a PNG or JPEG version of the media file, if exists.

View File

@@ -89,6 +89,8 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e
result.Main = f
} else if f.IsVector() {
result.Main = f
} else if f.IsDocument() {
result.Main = f
} else if f.IsHeic() {
isHeic = true
result.Main = f

View File

@@ -6,6 +6,7 @@ import (
)
const (
ExtPDF = ".pdf"
ExtJpeg = ".jpg"
ExtPng = ".png"
ExtDng = ".dng"

View File

@@ -10,6 +10,7 @@ type FileExtensions map[string]Type
// Extensions contains the filename extensions of file formats known to PhotoPrism.
var Extensions = FileExtensions{
ExtPDF: DocumentPDF, // .pdf
ExtJpeg: ImageJpeg, // .jpg
".jpeg": ImageJpeg,
".jpe": ImageJpeg,
@@ -80,6 +81,28 @@ var Extensions = FileExtensions{
".srw": ImageRaw,
".sr2": ImageRaw,
".x3f": ImageRaw,
ExtXMP: SidecarXMP,
".aae": SidecarAppleXml,
ExtXml: SidecarXml,
ExtYaml: SidecarYaml, // .yml
".yaml": SidecarYaml,
ExtJson: SidecarJson,
ExtTxt: SidecarText,
".nfo": SidecarInfo,
ExtMd: SidecarMarkdown,
".markdown": SidecarMarkdown,
".svg": VectorSVG,
".ai": VectorAI,
".ps": VectorPS,
".ps2": VectorPS,
".ps3": VectorPS,
".eps": VectorEPS,
".eps2": VectorEPS,
".eps3": VectorEPS,
".epi": VectorEPS,
".ept": VectorEPS,
".epsf": VectorEPS,
".epsi": VectorEPS,
ExtMov: VideoMov, // Apple QuickTime Video Container
ExtQT: VideoMov, // .qt
ExtMp4: VideoMp4, // MPEG-4 Part 14 Multimedia Container
@@ -141,28 +164,6 @@ var Extensions = FileExtensions{
".avi": VideoAVI,
".wmv": VideoWMV,
".dv": VideoDV,
".svg": VectorSVG,
".ai": VectorAI,
".ps": VectorPS,
".ps2": VectorPS,
".ps3": VectorPS,
".eps": VectorEPS,
".eps2": VectorEPS,
".eps3": VectorEPS,
".epi": VectorEPS,
".ept": VectorEPS,
".epsf": VectorEPS,
".epsi": VectorEPS,
ExtXMP: SidecarXMP,
".aae": SidecarAppleXml,
ExtXml: SidecarXml,
ExtYaml: SidecarYaml, // .yml
".yaml": SidecarYaml,
ExtJson: SidecarJson,
ExtTxt: SidecarText,
".nfo": SidecarInfo,
ExtMd: SidecarMarkdown,
".markdown": SidecarMarkdown,
}
// Known tests if the file extension is known (supported).

View File

@@ -4,8 +4,7 @@ type TypeMap map[Type]string
// TypeInfo contains human-readable descriptions for supported file formats
var TypeInfo = TypeMap{
ImageRaw: "Unprocessed Sensor Data",
ImageDng: "Adobe Digital Negative",
DocumentPDF: "Portable Document Format (PDF)",
ImageJpeg: "Joint Photographic Experts Group (JPEG)",
ImageJpegXL: "JPEG XL",
ImageThumb: "Thumbnail Image",
@@ -21,6 +20,12 @@ var TypeInfo = TypeMap{
ImageHeic: "High Efficiency Image Container",
ImageHeicS: "HEIC Image Sequence",
ImageWebp: "Google WebP",
ImageDng: "Adobe Digital Negative",
ImageRaw: "Unprocessed Sensor Data",
VectorSVG: "Scalable Vector Graphics",
VectorAI: "Adobe Illustrator",
VectorPS: "Adobe PostScript",
VectorEPS: "Encapsulated PostScript",
VideoWebm: "Google WebM",
VideoMp2: "MPEG 2 (H.262)",
VideoAvc: "Advanced Video Coding (H.264, MPEG-4 Part 10)",
@@ -48,10 +53,6 @@ var TypeInfo = TypeMap{
VideoAvcHD: "Advanced Video Coding High Definition (AVCHD)",
VideoBDAV: "Blu-ray MPEG-2 Transport Stream",
VideoTheora: "Ogg Media (OGG)",
VectorSVG: "Scalable Vector Graphics",
VectorAI: "Adobe Illustrator",
VectorPS: "Adobe PostScript",
VectorEPS: "Encapsulated PostScript",
SidecarXMP: "Adobe Extensible Metadata Platform",
SidecarAppleXml: "Apple Image Edits XML",
SidecarXml: "Extensible Markup Language",

View File

@@ -10,13 +10,9 @@ import (
_ "golang.org/x/image/webp"
)
// TypeUnknown is the default type used when a file cannot be classified.
const TypeUnknown Type = ""
// Supported media.Raw file types:
// Supported media.Document file types:
const (
ImageRaw Type = "raw" // RAW Image
ImageDng Type = "dng" // Adobe Digital Negative
DocumentPDF Type = "pdf" // Portable Document Format (PDF)
)
// Supported media.Image file types:
@@ -38,6 +34,32 @@ const (
ImageWebp Type = "webp" // Google WebP Image
)
// Supported media.Raw file types:
const (
ImageRaw Type = "raw" // RAW Image
ImageDng Type = "dng" // Adobe Digital Negative
)
// Supported media.Sidecar file types:
const (
SidecarYaml Type = "yml" // YAML metadata / config / sidecar file
SidecarJson Type = "json" // JSON metadata / config / sidecar file
SidecarXml Type = "xml" // XML metadata / config / sidecar file
SidecarAppleXml Type = "aae" // Apple image edits sidecar file (based on XML)
SidecarXMP Type = "xmp" // Adobe XMP sidecar file (XML)
SidecarText Type = "txt" // Text config / sidecar file
SidecarInfo Type = "nfo" // Info text file as used by e.g. Plex Media Server
SidecarMarkdown Type = "md" // Markdown text sidecar file
)
// Supported media.Vector file types:
const (
VectorAI Type = "ai" // Adobe Illustrator
VectorPS Type = "ps" // Adobe PostScript
VectorEPS Type = "eps" // Encapsulated PostScript
VectorSVG Type = "svg" // Scalable Vector Graphics
)
// Supported media.Video file types, see https://tools.woolyss.com/html5-canplaytype-tester/:
const (
VideoWebm Type = "webm" // Google WebM Video
@@ -69,23 +91,5 @@ const (
VideoDV Type = "dv" // DV Video (https://en.wikipedia.org/wiki/DV)
)
// Supported media.Vector file types:
const (
VectorAI Type = "ai" // Adobe Illustrator
VectorPS Type = "ps" // Adobe PostScript
VectorEPS Type = "eps" // Encapsulated PostScript
VectorPDF Type = "pdf" // Encapsulated PostScript
VectorSVG Type = "svg" // Scalable Vector Graphics
)
// Supported media.Sidecar file types:
const (
SidecarYaml Type = "yml" // YAML metadata / config / sidecar file
SidecarJson Type = "json" // JSON metadata / config / sidecar file
SidecarXml Type = "xml" // XML metadata / config / sidecar file
SidecarAppleXml Type = "aae" // Apple image edits sidecar file (based on XML)
SidecarXMP Type = "xmp" // Adobe XMP sidecar file (XML)
SidecarText Type = "txt" // Text config / sidecar file
SidecarInfo Type = "nfo" // Info text file as used by e.g. Plex Media Server
SidecarMarkdown Type = "md" // Markdown text sidecar file
)
// TypeUnknown is the default type used when a file cannot be classified.
const TypeUnknown Type = ""

View File

@@ -54,7 +54,7 @@ func MimeType(filename string) (mimeType string) {
case VectorEPS:
return header.ContentTypeEPS
// Adobe PDF
case VectorPDF:
case DocumentPDF:
return header.ContentTypePDF
// Scalable Vector Graphics
case VectorSVG:

View File

@@ -4,8 +4,7 @@ import "github.com/photoprism/photoprism/pkg/fs"
// Formats maps file formats to general media types.
var Formats = map[fs.Type]Type{
fs.ImageRaw: Raw,
fs.ImageDng: Raw,
fs.DocumentPDF: Document,
fs.ImageJpeg: Image,
fs.ImageJpegXL: Image,
fs.ImageThumb: Image,
@@ -21,6 +20,8 @@ var Formats = map[fs.Type]Type{
fs.ImageHeic: Image,
fs.ImageHeicS: Image,
fs.ImageWebp: Image,
fs.ImageRaw: Raw,
fs.ImageDng: Raw,
fs.SidecarXMP: Sidecar,
fs.SidecarXml: Sidecar,
fs.SidecarAppleXml: Sidecar,