photoprism/frontend/src/model/photo.js
Michael Mayer f9b6952947 Support for Live Photos #177
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
2020-05-21 13:26:28 +02:00

463 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";
const SrcManual = "manual";
const CodecAvc1 = "avc1";
const TypeMP4 = "mp4";
const TypeJpeg = "jpg";
const YearUnknown = -1;
const MonthUnknown = -1;
class Photo extends RestModel {
getDefaults() {
return {
ID: 0,
PhotoUUID: "",
PhotoType: "",
PhotoFavorite: false,
PhotoPrivate: false,
TakenAt: "",
TakenAtLocal: "",
TakenSrc: "",
TimeZone: "",
PhotoPath: "",
PhotoName: "",
PhotoTitle: "",
TitleSrc: "",
PhotoDescription: "",
DescriptionSrc: "",
PhotoResolution: 0,
PhotoQuality: 0,
PhotoLat: 0.0,
PhotoLng: 0.0,
PhotoAltitude: 0,
PhotoIso: 0,
PhotoFocalLength: 0,
PhotoFNumber: 0.0,
PhotoExposure: "",
PhotoViews: 0,
Camera: {},
CameraID: 0,
CameraSrc: "",
Lens: {},
LensID: 0,
Location: null,
LocationID: "",
LocationSrc: "",
Place: null,
PlaceID: "",
PhotoCountry: "",
PhotoYear: YearUnknown,
PhotoMonth: MonthUnknown,
Details: {
Keywords: "",
Notes: "",
Subject: "",
Artist: "",
Copyright: "",
License: "",
},
Files: [],
Labels: [],
Keywords: [],
Albums: [],
Links: [],
CreatedAt: "",
UpdatedAt: "",
DeletedAt: null,
};
}
getEntityName() {
return this.PhotoTitle;
}
getId() {
return this.PhotoUUID;
}
getTitle() {
return this.PhotoTitle;
}
getColor() {
switch (this.PhotoColor) {
case "brown":
case "black":
case "white":
case "grey":
return "grey lighten-2";
default:
return this.PhotoColor + " lighten-4";
}
}
getGoogleMapsLink() {
return "https://www.google.com/maps/place/" + this.PhotoLat + "," + this.PhotoLng;
}
refreshFileAttr() {
const file = this.mainFile();
if (!file || !file.FileHash) {
return;
}
this.FileHash = file.FileHash;
this.FileWidth = file.FileWidth;
this.FileHeight = file.FileHeight;
}
isPlayable() {
if (!this.Files) {
return false;
}
return this.Files.findIndex(f => f.FileCodec === CodecAvc1) !== -1 || this.Files.findIndex(f => f.FileType === TypeMP4) !== -1;
}
videoFile() {
if (!this.Files) {
return false;
}
let file = this.Files.find(f => f.FileCodec === CodecAvc1);
if (!file) {
file = this.Files.find(f => f.FileType === TypeMP4);
}
if (!file) {
file = this.Files.find(f => !!f.FileVideo);
}
return file;
}
videoUrl() {
const file = this.videoFile();
if (!file) {
return "";
}
return "/api/v1/videos/" + file.FileHash + "/" + TypeMP4;
}
mainFile() {
if (!this.Files) {
return false;
}
let file = this.Files.find(f => !!f.FilePrimary);
if (!file) {
file = this.Files.find(f => f.FileType === TypeJpeg);
}
return file;
}
mainFileHash() {
if (this.Files) {
let file = this.mainFile();
if (file && file.FileHash) {
return file.FileHash;
}
} else if (this.FileHash) {
return this.FileHash;
}
return "";
}
thumbnailUrl(type) {
let hash = this.mainFileHash();
if (!hash) {
let video = this.videoFile();
if (video && video.FileHash) {
return "/api/v1/thumbnails/" + video.FileHash + "/" + type;
}
return "/api/v1/svg/photo";
}
return "/api/v1/thumbnails/" + hash + "/" + type;
}
getDownloadUrl() {
return "/api/v1/download/" + this.mainFileHash();
}
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.FileWidth && height >= this.FileHeight) { // Smaller
return {width: this.FileWidth, height: this.FileHeight};
}
const srcAspectRatio = this.FileWidth / this.FileHeight;
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.PhotoYear === 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.PhotoYear === 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.PhotoLat !== 0 || this.PhotoLng !== 0;
}
getLocation() {
if (this.LocLabel) {
return this.LocLabel;
}
return "Unknown";
}
addSizeInfo(file, info) {
if (!file) {
return;
}
if (file.FileWidth && file.FileHeight) {
info.push(file.FileWidth + " × " + file.FileHeight);
} else if (!file.FilePrimary) {
let main = this.mainFile();
if (main && main.FileWidth && main.FileHeight) {
info.push(main.FileWidth + " × " + main.FileHeight);
}
}
if (file.FileSize > 102400) {
const size = Number.parseFloat(file.FileSize) / 1048576;
info.push(size.toFixed(1) + " MB");
} else if (file.FileSize) {
const size = Number.parseFloat(file.FileSize) / 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.FileDuration > 0) {
info.push(Util.duration(file.FileDuration));
}
this.addSizeInfo(file, info);
if (!info) {
return "Video";
}
return info.join(", ");
}
getPhotoInfo() {
let info = [];
if (this.Camera) {
info.push(this.Camera.CameraMake + " " + this.Camera.CameraModel);
} 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.CameraMake + " " + this.Camera.CameraModel;
} else if (this.CameraModel) {
return this.CameraMake + " " + this.CameraModel;
}
return "Unknown";
}
toggleLike() {
this.PhotoFavorite = !this.PhotoFavorite;
if (this.PhotoFavorite) {
return Api.post(this.getEntityResource() + "/like");
} else {
return Api.delete(this.getEntityResource() + "/like");
}
}
togglePrivate() {
this.PhotoPrivate = !this.PhotoPrivate;
return Api.put(this.getEntityResource(), {PhotoPrivate: this.PhotoPrivate});
}
setPrimary(fileUUID) {
return Api.post(this.getEntityResource() + "/primary/" + fileUUID).then((r) => Promise.resolve(this.setValues(r.data)));
}
like() {
this.PhotoFavorite = true;
return Api.post(this.getEntityResource() + "/like");
}
unlike() {
this.PhotoFavorite = false;
return Api.delete(this.getEntityResource() + "/like");
}
addLabel(name) {
return Api.post(this.getEntityResource() + "/label", {LabelName: name, LabelPriority: 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: {LabelName: 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.PhotoTitle) {
values.TitleSrc = SrcManual;
}
if (values.PhotoDescription) {
values.DescriptionSrc = SrcManual;
}
if (values.PhotoLat || values.PhotoLng) {
values.LocationSrc = SrcManual;
}
if (values.TakenAt || values.TimeZone) {
values.TakenSrc = SrcManual;
}
if (values.CameraID || values.LensID || values.PhotoFocalLength || values.PhotoFNumber || values.PhotoIso || values.PhotoExposure) {
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].PhotoUUID === response.models[0].PhotoUUID) {
const first = response.models.shift();
results[i].Files = results[i].Files.concat(first.Files);
}
}
return results.concat(response.models);
}
}
export default Photo;