mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Library: Add support for indexing PDF documents #4600
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -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":
|
||||
|
||||
@@ -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')"
|
||||
|
||||
@@ -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`) }}
|
||||
|
||||
@@ -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
@@ -59,6 +59,7 @@ export class File extends RestModel {
|
||||
Duration: 0,
|
||||
FPS: 0.0,
|
||||
Frames: 0,
|
||||
Pages: 0,
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
Orientation: 0,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -387,6 +387,10 @@ export const PhotoTypes = () => [
|
||||
text: $gettext("Vector"),
|
||||
value: media.Vector,
|
||||
},
|
||||
{
|
||||
text: $gettext("Document"),
|
||||
value: media.Document,
|
||||
},
|
||||
];
|
||||
|
||||
export const Timeouts = () => [
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ExtPDF = ".pdf"
|
||||
ExtJpeg = ".jpg"
|
||||
ExtPng = ".png"
|
||||
ExtDng = ".dng"
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user