Skip to content

Commit f747211

Browse files
committed
Library: Add support for indexing PDF documents photoprism#4600
Signed-off-by: Michael Mayer <michael@photoprism.app>
1 parent a3914d6 commit f747211

26 files changed

+268
-115
lines changed

frontend/src/common/util.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ export default class $util {
316316
}
317317

318318
switch (value) {
319+
case "pdf":
320+
return "PDF";
319321
case "jpg":
320322
return "JPEG";
321323
case media.FormatJpegXL:
@@ -391,8 +393,6 @@ export default class $util {
391393
return "Windows Media";
392394
case "svg":
393395
return "SVG";
394-
case "pdf":
395-
return "PDF";
396396
case "ai":
397397
return "Adobe Illustrator";
398398
case "ps":

frontend/src/component/navigation.vue

+19-6
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@
168168
</v-list-item-title>
169169
</v-list-item>
170170

171+
<v-list-item
172+
:to="{ name: 'photos', query: { q: 'stacks' } }"
173+
:exact="true"
174+
variant="text"
175+
class="nav-stacks"
176+
@click.stop=""
177+
>
178+
<v-list-item-title :class="`nav-menu-item menu-item`">
179+
{{ $gettext(`Stacks`) }}
180+
</v-list-item-title>
181+
</v-list-item>
182+
171183
<v-list-item
172184
v-show="isSponsor"
173185
:to="{ name: 'browse', query: { q: 'vectors' } }"
@@ -182,26 +194,27 @@
182194
</v-list-item>
183195

184196
<v-list-item
185-
:to="{ name: 'photos', query: { q: 'stacks' } }"
197+
:to="{ name: 'photos', query: { q: 'scans' } }"
186198
:exact="true"
187199
variant="text"
188-
class="nav-stacks"
200+
class="nav-scans"
189201
@click.stop=""
190202
>
191203
<v-list-item-title :class="`nav-menu-item menu-item`">
192-
{{ $gettext(`Stacks`) }}
204+
{{ $gettext(`Scans`) }}
193205
</v-list-item-title>
194206
</v-list-item>
195207

196208
<v-list-item
197-
:to="{ name: 'photos', query: { q: 'scans' } }"
209+
v-show="isSponsor"
210+
:to="{ name: 'browse', query: { q: 'documents' } }"
198211
:exact="true"
199212
variant="text"
200-
class="nav-scans"
213+
class="nav-documents"
201214
@click.stop=""
202215
>
203216
<v-list-item-title :class="`nav-menu-item menu-item`">
204-
{{ $gettext(`Scans`) }}
217+
{{ $gettext(`Documents`) }}
205218
</v-list-item-title>
206219
</v-list-item>
207220

frontend/src/component/photo/edit/files.vue

+14-8
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@
172172
}}</span>
173173
</td>
174174
</tr>
175+
<tr v-if="file.FileType">
176+
<td>
177+
{{ $gettext(`Type`) }}
178+
</td>
179+
<td class="text-break">
180+
<span v-tooltip="file?.Mime">{{ file.typeInfo() }}</span>
181+
</td>
182+
</tr>
175183
<tr>
176184
<td>
177185
{{ $gettext(`Size`) }}
@@ -182,19 +190,17 @@
182190
}}</span>
183191
</td>
184192
</tr>
185-
<tr v-if="file.Software">
193+
<tr v-if="file.Pages">
186194
<td>
187-
{{ $gettext(`Software`) }}
195+
{{ $gettext(`Pages`) }}
188196
</td>
189-
<td class="text-break">{{ file.Software }}</td>
197+
<td>{{ file.Pages }}</td>
190198
</tr>
191-
<tr v-if="file.FileType">
199+
<tr v-if="file.Software">
192200
<td>
193-
{{ $gettext(`Type`) }}
194-
</td>
195-
<td class="text-break">
196-
<span v-tooltip="file?.Mime">{{ file.typeInfo() }}</span>
201+
{{ $gettext(`Software`) }}
197202
</td>
203+
<td class="text-break">{{ file.Software }}</td>
198204
</tr>
199205
<tr v-if="file.isAnimated()">
200206
<td>

frontend/src/component/photo/view/cards.vue

+5-5
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@
8888
<i class="mdi mdi-file-gif-box" />
8989
{{ m.getVideoInfo() }}
9090
</button>
91-
<button v-else-if="m.Type === 'vector'" class="meta-vector text-truncate">
92-
<i class="mdi mdi-vector-polyline" />
91+
<button v-else-if="m.Type === 'document' || m.Type === 'vector'" class="meta-vector text-truncate">
92+
<i class="mdi" :class="m.Type === 'document' ? 'mdi-text-box' : 'mdi-vector-polyline'" />
9393
{{ m.getVectorInfo() }}
9494
</button>
9595
<button v-else class="meta-image text-truncate">
@@ -300,12 +300,12 @@
300300
{{ m.getVideoInfo() }}
301301
</button>
302302
<button
303-
v-else-if="m.Type === 'vector'"
304-
:title="$gettext('Vector')"
303+
v-else-if="m.Type === 'document' || m.Type === 'vector'"
304+
:title="m.Type === 'document' ? $gettext('Document') : $gettext('Vector')"
305305
class="meta-vector text-truncate"
306306
@click.exact="editPhoto(index)"
307307
>
308-
<i class="mdi mdi-vector-polyline" />
308+
<i class="mdi" :class="m.Type === 'document' ? 'mdi-text-box' : 'mdi-vector-polyline'" />
309309
{{ m.getVectorInfo() }}
310310
</button>
311311
<button

frontend/src/locales/translations.json

+1-1
Large diffs are not rendered by default.

frontend/src/model/file.js

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export class File extends RestModel {
5959
Duration: 0,
6060
FPS: 0.0,
6161
Frames: 0,
62+
Pages: 0,
6263
Width: 0,
6364
Height: 0,
6465
Orientation: 0,

frontend/src/model/photo.js

+16-10
Original file line numberDiff line numberDiff line change
@@ -862,12 +862,18 @@ export class Photo extends RestModel {
862862
return;
863863
}
864864

865-
if (file.Width && file.Height) {
866-
info.push(file.Width + " × " + file.Height);
867-
} else if (!file.Primary) {
868-
let primary = this.primaryFile();
869-
if (primary && primary.Width && primary.Height) {
870-
info.push(primary.Width + " × " + primary.Height);
865+
if (file?.Pages > 0) {
866+
info.push(file.Pages + " " + $gettext("Pages"));
867+
}
868+
869+
if (file?.MediaType !== media.Document) {
870+
if (file.Width && file.Height) {
871+
info.push(file.Width + " × " + file.Height);
872+
} else if (!file.Primary) {
873+
let primary = this.primaryFile();
874+
if (primary && primary.Width && primary.Height) {
875+
info.push(primary.Width + " × " + primary.Height);
876+
}
871877
}
872878
}
873879

@@ -883,7 +889,7 @@ export class Photo extends RestModel {
883889
return this;
884890
}
885891

886-
return this.Files.find((f) => f.MediaType === media.Vector || f.FileType === media.FormatSVG);
892+
return this.Files.find((f) => f.MediaType === media.Document || f.MediaType === media.Vector || f.FileType === media.FormatSVG);
887893
}
888894

889895
getVectorInfo = () => {
@@ -893,15 +899,15 @@ export class Photo extends RestModel {
893899

894900
generateVectorInfo = memoizeOne((file) => {
895901
if (!file) {
896-
return $gettext("Vector");
902+
return $gettext("Unknown");
897903
}
898904

899905
const info = [];
900906

901-
if (file.MediaType === media.Vector) {
907+
if (file.MediaType === media.Vector || file.MediaType === media.Document) {
902908
info.push($util.fileType(file.FileType));
903909
} else {
904-
info.push($gettext("Vector"));
910+
info.push($gettext("Unknown"));
905911
}
906912

907913
this.addSizeInfo(file, info);

frontend/src/options/options.js

+4
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ export const PhotoTypes = () => [
387387
text: $gettext("Vector"),
388388
value: media.Vector,
389389
},
390+
{
391+
text: $gettext("Document"),
392+
value: media.Document,
393+
},
390394
];
391395

392396
export const Timeouts = () => [

internal/entity/file.go

+10
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type File struct {
6666
FileDuration time.Duration `json:"Duration" yaml:"Duration,omitempty"`
6767
FileFPS float64 `gorm:"column:file_fps;" json:"FPS" yaml:"FPS,omitempty"`
6868
FileFrames int `gorm:"column:file_frames;" json:"Frames" yaml:"Frames,omitempty"`
69+
FilePages int `gorm:"column:file_pages;default:0;" json:"Pages" yaml:"Pages,omitempty"`
6970
FileWidth int `gorm:"column:file_width;" json:"Width" yaml:"Width,omitempty"`
7071
FileHeight int `gorm:"column:file_height;" json:"Height" yaml:"Height,omitempty"`
7172
FileOrientation int `gorm:"column:file_orientation;" json:"Orientation" yaml:"Orientation,omitempty"`
@@ -759,6 +760,15 @@ func (m *File) SetFrames(n int) {
759760
}
760761
}
761762

763+
// SetPages sets the number of document pages.
764+
func (m *File) SetPages(n int) {
765+
if n <= 0 {
766+
return
767+
}
768+
769+
m.FilePages = n
770+
}
771+
762772
// SetMediaUTC sets the media creation date from metadata as unix time in ms.
763773
func (m *File) SetMediaUTC(taken time.Time) {
764774
if taken.IsZero() {

internal/entity/file_json.go

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func (m *File) MarshalJSON() ([]byte, error) {
3131
Duration time.Duration `json:",omitempty"`
3232
FPS float64 `json:",omitempty"`
3333
Frames int `json:",omitempty"`
34+
Pages int `json:",omitempty"`
3435
Width int `json:",omitempty"`
3536
Height int `json:",omitempty"`
3637
Orientation int `json:",omitempty"`
@@ -78,6 +79,7 @@ func (m *File) MarshalJSON() ([]byte, error) {
7879
Duration: m.FileDuration,
7980
FPS: m.FileFPS,
8081
Frames: m.FileFrames,
82+
Pages: m.FilePages,
8183
Width: m.FileWidth,
8284
Height: m.FileHeight,
8385
Orientation: m.FileOrientation,

internal/entity/file_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,26 @@ func TestFile_SetFrames(t *testing.T) {
776776
})
777777
}
778778

779+
func TestFile_SetPages(t *testing.T) {
780+
t.Run("Success", func(t *testing.T) {
781+
m := File{FilePages: 4}
782+
783+
assert.Equal(t, 4, m.FilePages)
784+
785+
m.SetPages(120)
786+
787+
assert.Equal(t, 120, m.FilePages)
788+
789+
m.SetPages(30)
790+
791+
assert.Equal(t, 30, m.FilePages)
792+
793+
m.SetPages(0)
794+
795+
assert.Equal(t, 30, m.FilePages)
796+
})
797+
}
798+
779799
func TestFile_SetDuration(t *testing.T) {
780800
t.Run("FileFPS", func(t *testing.T) {
781801
m := File{FileFPS: 20}

internal/entity/photo_quality.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (m *Photo) UpdateQuality() error {
6464

6565
// IsNonPhotographic checks whether the image appears to be non-photographic.
6666
func (m *Photo) IsNonPhotographic() (result bool) {
67-
if m.PhotoType == MediaUnknown || m.PhotoType == MediaVector || m.PhotoType == MediaAnimated {
67+
if m.PhotoType == MediaUnknown || m.PhotoType == MediaVector || m.PhotoType == MediaAnimated || m.PhotoType == MediaDocument {
6868
return true
6969
}
7070

internal/entity/search/photos.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,12 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
322322
case terms["video"]:
323323
frm.Query = strings.ReplaceAll(frm.Query, "video", "")
324324
frm.Video = true
325+
case terms["documents"]:
326+
frm.Query = strings.ReplaceAll(frm.Query, "documents", "")
327+
frm.Document = true
328+
case terms["document"]:
329+
frm.Query = strings.ReplaceAll(frm.Query, "document", "")
330+
frm.Document = true
325331
case terms["vectors"]:
326332
frm.Query = strings.ReplaceAll(frm.Query, "vectors", "")
327333
frm.Vector = true
@@ -640,7 +646,7 @@ func searchPhotos(frm form.SearchPhotos, sess *entity.Session, resultCols string
640646
} else if frm.Video {
641647
s = s.Where("photos.photo_type = ?", media.Video)
642648
} else if frm.Photo {
643-
s = s.Where("photos.photo_type IN ('image','live','animated','vector','raw')")
649+
s = s.Where("photos.photo_type IN ('image','raw','live','animated','vector')")
644650
}
645651

646652
// Filter by storage path.

internal/entity/search/photos_results.go

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ type Photo struct {
8686
FileDuration time.Duration `json:"-" select:"files.file_duration"`
8787
FileFPS float64 `json:"-" select:"files.file_fps"`
8888
FileFrames int `json:"-" select:"files.file_frames"`
89+
FilePages int `json:"-" select:"files.file_pages"`
8990
FileCodec string `json:"-" select:"files.file_codec"`
9091
FileType string `json:"-" select:"files.file_type"`
9192
MediaType string `json:"-" select:"files.media_type"`

internal/meta/data.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Data struct {
3232
Duration time.Duration `meta:"Duration,MediaDuration,TrackDuration,PreviewDuration"`
3333
FPS float64 `meta:"VideoFrameRate,VideoAvgFrameRate"`
3434
Frames int `meta:"FrameCount,AnimationFrames"`
35+
Pages int `meta:"PageCount,NPages,Pages"`
3536
Codec string `meta:"CompressorID,VideoCodecID,CodecID,OtherFormat,FileType"`
3637
Title string `meta:"Title,Headline" xmp:"dc:title" dc:"title,title.Alt"`
3738
Caption string `meta:"Description,ImageDescription,Caption,Caption-Abstract" xmp:"Description,Description.Alt"`
@@ -50,7 +51,7 @@ type Data struct {
5051
CameraSerial string `meta:"SerialNumber"`
5152
LensMake string `meta:"LensMake"`
5253
LensModel string `meta:"LensModel,Lens,LensID" xmp:"LensModel,Lens"`
53-
Software string `meta:"Software,CreatorTool,HistorySoftwareAgent,ProcessingSoftware"`
54+
Software string `meta:"Software,Producer,CreatorTool,Creator,CreatorSubTool,HistorySoftwareAgent,ProcessingSoftware"`
5455
Flash bool `meta:"FlashFired"`
5556
FocalLength int `meta:"FocalLength,FocalLengthIn35mmFormat"`
5657
FocalDistance float64 `meta:"HyperfocalDistance"`

internal/photoprism/convert_image_jpeg.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,22 @@ func (w *Convert) JpegConvertCmds(f *MediaFile, jpegName string, xmpName string)
109109
}
110110

111111
// Try ImageMagick for other image file formats if allowed.
112-
if w.conf.ImageMagickEnabled() && w.imageMagickExclude.Allow(fileExt) &&
113-
(f.IsImage() && !f.IsJpegXL() && !f.IsRaw() && !f.IsHeif() || f.IsVector() && w.conf.VectorEnabled()) {
114-
quality := fmt.Sprintf("%d", w.conf.JpegQuality())
115-
resize := fmt.Sprintf("%dx%d>", w.conf.JpegSize(), w.conf.JpegSize())
116-
args := []string{f.FileName(), "-flatten", "-resize", resize, "-quality", quality, jpegName}
117-
result = append(result, NewConvertCmd(
118-
exec.Command(w.conf.ImageMagickBin(), args...)),
119-
)
112+
if w.conf.ImageMagickEnabled() && w.imageMagickExclude.Allow(fileExt) {
113+
if f.IsImage() && !f.IsJpegXL() && !f.IsRaw() && !f.IsHeif() || f.IsVector() && w.conf.VectorEnabled() {
114+
quality := fmt.Sprintf("%d", w.conf.JpegQuality())
115+
resize := fmt.Sprintf("%dx%d>", w.conf.JpegSize(), w.conf.JpegSize())
116+
args := []string{f.FileName(), "-flatten", "-resize", resize, "-quality", quality, jpegName}
117+
result = append(result, NewConvertCmd(
118+
exec.Command(w.conf.ImageMagickBin(), args...)),
119+
)
120+
} else if f.IsDocument() {
121+
quality := fmt.Sprintf("%d", w.conf.JpegQuality())
122+
resize := fmt.Sprintf("%dx%d>", w.conf.JpegSize(), w.conf.JpegSize())
123+
args := []string{f.FileName() + "[0]", "-background", "white", "-alpha", "remove", "-alpha", "off", "-resize", resize, "-quality", quality, jpegName}
124+
result = append(result, NewConvertCmd(
125+
exec.Command(w.conf.ImageMagickBin(), args...)),
126+
)
127+
}
120128
}
121129

122130
// No suitable converter found?

internal/photoprism/convert_image_png.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,20 @@ func (w *Convert) PngConvertCmds(f *MediaFile, pngName string) (result ConvertCm
5858
result = append(result, NewConvertCmd(
5959
exec.Command(w.conf.RsvgConvertBin(), args...)),
6060
)
61-
} else if w.conf.ImageMagickEnabled() && w.imageMagickExclude.Allow(fileExt) &&
62-
(f.IsImage() && !f.IsJpegXL() && !f.IsRaw() && !f.IsHeif() || f.IsVector() && w.conf.VectorEnabled()) {
63-
resize := fmt.Sprintf("%dx%d>", w.conf.PngSize(), w.conf.PngSize())
64-
args := []string{f.FileName(), "-flatten", "-resize", resize, pngName}
65-
result = append(result, NewConvertCmd(
66-
exec.Command(w.conf.ImageMagickBin(), args...)),
67-
)
61+
} else if w.conf.ImageMagickEnabled() && w.imageMagickExclude.Allow(fileExt) {
62+
if f.IsImage() && !f.IsJpegXL() && !f.IsRaw() && !f.IsHeif() || f.IsVector() && w.conf.VectorEnabled() {
63+
resize := fmt.Sprintf("%dx%d>", w.conf.PngSize(), w.conf.PngSize())
64+
args := []string{f.FileName(), "-flatten", "-resize", resize, pngName}
65+
result = append(result, NewConvertCmd(
66+
exec.Command(w.conf.ImageMagickBin(), args...)),
67+
)
68+
} else if f.IsDocument() {
69+
resize := fmt.Sprintf("%dx%d>", w.conf.PngSize(), w.conf.PngSize())
70+
args := []string{f.FileName() + "[0]", "-background", "white", "-alpha", "remove", "-alpha", "off", "-resize", resize, pngName}
71+
result = append(result, NewConvertCmd(
72+
exec.Command(w.conf.ImageMagickBin(), args...)),
73+
)
74+
}
6875
}
6976

7077
// No suitable converter found?

0 commit comments

Comments
 (0)