photoprism/frontend/src/model/photo.js
Michael Mayer f510ac994c XMP: Group files based on DocumentID and Instance ID #335
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
2020-05-27 13:40:21 +02:00

508 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import RestModel from "model/rest";
import Api from "common/api";
import {DateTime} from "luxon";
import Util from "common/util";
export const SrcManual = "manual";
export const CodecAvc1 = "avc1";
export const TypeMP4 = "mp4";
export const TypeJpeg = "jpg";
export const TypeImage = "image";
export const YearUnknown = -1;
export const MonthUnknown = -1;
export class Photo extends RestModel {
getDefaults() {
return {
DocumentID: "",
UID: "",
Type: TypeImage,
Favorite: false,
Private: false,
TakenAt: "",
TakenAtLocal: "",
TakenSrc: "",
TimeZone: "",
Path: "",
Color: "",
Name: "",
Title: "",
TitleSrc: "",
Description: "",
DescriptionSrc: "",
Resolution: 0,
Quality: 0,
Lat: 0.0,
Lng: 0.0,
Altitude: 0,
Iso: 0,
FocalLength: 0,
FNumber: 0.0,
Exposure: "",
Views: 0,
Camera: {},
CameraID: 0,
CameraSrc: "",
Lens: {},
LensID: 0,
Country: "",
Year: YearUnknown,
Month: MonthUnknown,
Details: {
Keywords: "",
Notes: "",
Subject: "",
Artist: "",
Copyright: "",
License: "",
},
Files: [],
Labels: [],
Keywords: [],
Albums: [],
Links: [],
Location: null,
Place: null,
PlaceUID: "",
LocUID: "",
LocSrc: "",
// Additional data in result lists.
LocLabel: "",
LocCity: "",
LocState: "",
LocCountry: "",
FileUID: "",
FileRoot: "",
FileName: "",
Hash: "",
Width: "",
Height: "",
// Date fields.
CreatedAt: "",
UpdatedAt: "",
DeletedAt: null,
};
}
baseName(truncate) {
let result = this.fileBase(this.FileName ? this.FileName : this.mainFile().Name);
if (truncate) {
result = Util.truncate(result, truncate, "...");
}
return result;
}
fileBase(name) {
let result = name;
const slash = result.lastIndexOf("/");
if (slash >= 0) {
result = name.substring(slash + 1);
}
return result;
}
getEntityName() {
return this.Title;
}
getId() {
return this.UID;
}
getTitle() {
return this.Title;
}
getGoogleMapsLink() {
return "https://www.google.com/maps/place/" + this.Lat + "," + this.Lng;
}
refreshFileAttr() {
const file = this.mainFile();
if (!file || !file.Hash) {
return;
}
this.Hash = file.Hash;
this.Width = file.Width;
this.Height = file.Height;
}
isPlayable() {
if (!this.Files) {
return false;
}
return this.Files.findIndex(f => f.Codec === CodecAvc1) !== -1 || this.Files.findIndex(f => f.Type === TypeMP4) !== -1;
}
videoFile() {
if (!this.Files) {
return false;
}
let file = this.Files.find(f => f.Codec === CodecAvc1);
if (!file) {
file = this.Files.find(f => f.Type === TypeMP4);
}
if (!file) {
file = this.Files.find(f => !!f.Video);
}
return file;
}
videoUrl() {
const file = this.videoFile();
if (!file) {
return "";
}
return "/api/v1/videos/" + file.Hash + "/" + TypeMP4;
}
mainFile() {
if (!this.Files) {
return false;
}
let file = this.Files.find(f => !!f.Primary);
if (!file) {
file = this.Files.find(f => f.Type === TypeJpeg);
}
return file;
}
mainFileHash() {
if (this.Files) {
let file = this.mainFile();
if (file && file.Hash) {
return file.Hash;
}
} else if (this.Hash) {
return this.Hash;
}
return "";
}
thumbnailUrl(type) {
let hash = this.mainFileHash();
if (!hash) {
let video = this.videoFile();
if (video && video.Hash) {
return "/api/v1/thumbnails/" + video.Hash + "/" + type;
}
return "/api/v1/svg/photo";
}
return "/api/v1/thumbnails/" + hash + "/" + type;
}
getDownloadUrl() {
return "/api/v1/download/" + this.mainFileHash();
}
downloadAll() {
if (!this.Files) {
let link = document.createElement('a')
link.href = "/api/v1/download/" + this.mainFileHash();
link.download = this.baseName(false);
link.click();
return;
}
this.Files.forEach((file) => {
if (!file || !file.Hash) {
console.warn("no file hash found for download", file)
return
}
let link = document.createElement('a')
link.href = "/api/v1/download/" + file.Hash;
link.download = this.fileBase(file.Name);
link.click();
});
}
thumbnailSrcset() {
const result = [];
result.push(this.thumbnailUrl("fit_720") + " 720w");
result.push(this.thumbnailUrl("fit_1280") + " 1280w");
result.push(this.thumbnailUrl("fit_1920") + " 1920w");
result.push(this.thumbnailUrl("fit_2560") + " 2560w");
result.push(this.thumbnailUrl("fit_3840") + " 3840w");
return result.join(", ");
}
calculateSize(width, height) {
if (width >= this.Width && height >= this.Height) { // Smaller
return {width: this.Width, height: this.Height};
}
const srcAspectRatio = this.Width / this.Height;
const maxAspectRatio = width / height;
let newW, newH;
if (srcAspectRatio > maxAspectRatio) {
newW = width;
newH = Math.round(newW / srcAspectRatio);
} else {
newH = height;
newW = Math.round(newH * srcAspectRatio);
}
return {width: newW, height: newH};
}
thumbnailSizes() {
const result = [];
result.push("(min-width: 2560px) 3840px");
result.push("(min-width: 1920px) 2560px");
result.push("(min-width: 1280px) 1920px");
result.push("(min-width: 720px) 1280px");
result.push("720px");
return result.join(", ");
}
getDateString() {
if (!this.TakenAt || this.Year === YearUnknown) {
return "Unknown";
}
if (this.TimeZone) {
return DateTime.fromISO(this.TakenAt).setZone(this.TimeZone).toLocaleString(DateTime.DATETIME_FULL);
}
return DateTime.fromISO(this.TakenAt).setZone("UTC").toLocaleString(DateTime.DATE_HUGE);
}
shortDateString() {
if (!this.TakenAt || this.Year === YearUnknown) {
return "Unknown";
}
if (this.TimeZone) {
return DateTime.fromISO(this.TakenAt).setZone(this.TimeZone).toLocaleString(DateTime.DATE_MED);
}
return DateTime.fromISO(this.TakenAt).setZone("UTC").toLocaleString(DateTime.DATE_MED);
}
hasLocation() {
return this.Lat !== 0 || this.Lng !== 0;
}
locationInfo() {
if (this.LocLabel) {
return this.LocLabel;
}
return "Unknown";
}
addSizeInfo(file, info) {
if (!file) {
return;
}
if (file.Width && file.Height) {
info.push(file.Width + " × " + file.Height);
} else if (!file.Primary) {
let main = this.mainFile();
if (main && main.Width && main.Height) {
info.push(main.Width + " × " + main.Height);
}
}
if (file.Size > 102400) {
const size = Number.parseFloat(file.Size) / 1048576;
info.push(size.toFixed(1) + " MB");
} else if (file.Size) {
const size = Number.parseFloat(file.Size) / 1024;
info.push(size.toFixed(1) + " KB");
}
}
getVideoInfo() {
let info = [];
let file = this.videoFile();
if (!file) {
file = this.mainFile();
}
if (!file) {
return "Video";
}
if (file.Duration > 0) {
info.push(Util.duration(file.Duration));
}
this.addSizeInfo(file, info);
if (!info) {
return "Video";
}
return info.join(", ");
}
getPhotoInfo() {
let info = [];
if (this.Camera) {
info.push(this.Camera.Make + " " + this.Camera.Model);
} else if (this.CameraModel && this.CameraMake) {
info.push(this.CameraMake + " " + this.CameraModel);
}
let file = this.mainFile();
this.addSizeInfo(file, info);
if (!info) {
return "Unknown";
}
return info.join(", ");
}
getCamera() {
if (this.Camera) {
return this.Camera.Make + " " + this.Camera.Model;
} else if (this.CameraModel) {
return this.CameraMake + " " + this.CameraModel;
}
return "Unknown";
}
toggleLike() {
this.Favorite = !this.Favorite;
if (this.Favorite) {
return Api.post(this.getEntityResource() + "/like");
} else {
return Api.delete(this.getEntityResource() + "/like");
}
}
togglePrivate() {
this.Private = !this.Private;
return Api.put(this.getEntityResource(), {Private: this.Private});
}
setPrimary(uid) {
return Api.post(this.getEntityResource() + "/primary/" + uid).then((r) => Promise.resolve(this.setValues(r.data)));
}
like() {
this.Favorite = true;
return Api.post(this.getEntityResource() + "/like");
}
unlike() {
this.Favorite = false;
return Api.delete(this.getEntityResource() + "/like");
}
addLabel(name) {
return Api.post(this.getEntityResource() + "/label", {Name: name, Priority: 10})
.then((r) => Promise.resolve(this.setValues(r.data)));
}
activateLabel(id) {
return Api.put(this.getEntityResource() + "/label/" + id, {Uncertainty: 0})
.then((r) => Promise.resolve(this.setValues(r.data)));
}
renameLabel(id, name) {
return Api.put(this.getEntityResource() + "/label/" + id, {Label: {Name: name}})
.then((r) => Promise.resolve(this.setValues(r.data)));
}
removeLabel(id) {
return Api.delete(this.getEntityResource() + "/label/" + id)
.then((r) => Promise.resolve(this.setValues(r.data)));
}
update() {
const values = this.getValues(true);
if (values.Title) {
values.TitleSrc = SrcManual;
}
if (values.Description) {
values.DescriptionSrc = SrcManual;
}
if (values.Lat || values.Lng) {
values.LocSrc = SrcManual;
}
if (values.TakenAt || values.TimeZone) {
values.TakenSrc = SrcManual;
}
if (values.CameraID || values.LensID || values.FocalLength || values.FNumber || values.Iso || values.Exposure) {
values.CameraSrc = SrcManual;
}
return Api.put(this.getEntityResource(), values).then((response) => Promise.resolve(this.setValues(response.data)));
}
static getCollectionResource() {
return "photos";
}
static getModelName() {
return "Photo";
}
static mergeResponse(results, response) {
if (response.offset === 0 || results.length === 0) {
return response.models;
}
if (response.models.length > 0) {
let i = results.length - 1;
if (results[i].UID === response.models[0].UID) {
const first = response.models.shift();
results[i].Files = results[i].Files.concat(first.Files);
}
}
return results.concat(response.models);
}
}
export default Photo;