refactor: asset media endpoints (#9831)

* refactor: asset media endpoints

* refactor: mobile upload livePhoto as separate request

* refactor: change mobile backup flow to use new asset upload endpoints

* chore: format and analyze dart code

* feat: mark motion as hidden when linked

* feat: upload video portion of live photo before image portion

* fix: incorrect assetApi calls in mobile code

* fix: download asset

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
This commit is contained in:
Jason Rasmussen 2024-05-31 13:44:04 -04:00 committed by GitHub
parent 66fced40e7
commit 69d2fcb43e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
91 changed files with 1932 additions and 2456 deletions

View file

@ -1295,7 +1295,7 @@
]
}
},
"/asset": {
"/assets": {
"delete": {
"operationId": "deleteAssets",
"parameters": [],
@ -1329,6 +1329,65 @@
"Assets"
]
},
"post": {
"operationId": "uploadAsset",
"parameters": [
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "x-immich-checksum",
"in": "header",
"description": "sha1 checksum that can be used for duplicate detection before the file is uploaded",
"required": false,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/AssetMediaCreateDto"
}
}
},
"description": "Asset Upload Information",
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetMediaResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Assets"
]
},
"put": {
"operationId": "updateAssets",
"parameters": [],
@ -1363,7 +1422,7 @@
]
}
},
"/asset/bulk-upload-check": {
"/assets/bulk-upload-check": {
"post": {
"description": "Checks if assets exist by checksums",
"operationId": "checkBulkUpload",
@ -1406,7 +1465,7 @@
]
}
},
"/asset/device/{deviceId}": {
"/assets/device/{deviceId}": {
"get": {
"description": "Get all asset of a device that are in the database, ID only.",
"operationId": "getAllUserAssetsByDeviceId",
@ -1451,7 +1510,7 @@
]
}
},
"/asset/exist": {
"/assets/exist": {
"post": {
"description": "Checks if multiple assets exist on the server and returns all existing - used by background backup",
"operationId": "checkExistingAssets",
@ -1494,76 +1553,7 @@
]
}
},
"/asset/file/{id}": {
"get": {
"operationId": "serveFile",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "isThumb",
"required": false,
"in": "query",
"schema": {
"title": "Is serve thumbnail (resize) file",
"type": "boolean"
}
},
{
"name": "isWeb",
"required": false,
"in": "query",
"schema": {
"title": "Is request made from web",
"type": "boolean"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/octet-stream": {
"schema": {
"format": "binary",
"type": "string"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Assets"
]
}
},
"/asset/jobs": {
"/assets/jobs": {
"post": {
"operationId": "runAssetJobs",
"parameters": [],
@ -1598,7 +1588,7 @@
]
}
},
"/asset/memory-lane": {
"/assets/memory-lane": {
"get": {
"operationId": "getMemoryLane",
"parameters": [
@ -1654,7 +1644,7 @@
]
}
},
"/asset/random": {
"/assets/random": {
"get": {
"operationId": "getRandom",
"parameters": [
@ -1699,7 +1689,7 @@
]
}
},
"/asset/stack/parent": {
"/assets/stack/parent": {
"put": {
"operationId": "updateStackParent",
"parameters": [],
@ -1734,7 +1724,7 @@
]
}
},
"/asset/statistics": {
"/assets/statistics": {
"get": {
"operationId": "getAssetStatistics",
"parameters": [
@ -1791,127 +1781,7 @@
]
}
},
"/asset/thumbnail/{id}": {
"get": {
"operationId": "getAssetThumbnail",
"parameters": [
{
"name": "format",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/ThumbnailFormat"
}
},
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/octet-stream": {
"schema": {
"format": "binary",
"type": "string"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Assets"
]
}
},
"/asset/upload": {
"post": {
"operationId": "uploadFile",
"parameters": [
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "x-immich-checksum",
"in": "header",
"description": "sha1 checksum that can be used for duplicate detection before the file is uploaded",
"required": false,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/CreateAssetDto"
}
}
},
"description": "Asset Upload Information",
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetFileUploadResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Assets"
]
}
},
"/asset/{id}": {
"/assets/{id}": {
"get": {
"operationId": "getAssetInfo",
"parameters": [
@ -2011,7 +1881,56 @@
]
}
},
"/asset/{id}/file": {
"/assets/{id}/original": {
"get": {
"operationId": "downloadAsset",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/octet-stream": {
"schema": {
"format": "binary",
"type": "string"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Assets"
]
},
"put": {
"description": "Replace the asset with new file, without changing its id",
"operationId": "replaceAsset",
@ -2075,6 +1994,116 @@
}
}
},
"/assets/{id}/thumbnail": {
"get": {
"operationId": "viewAsset",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "size",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/AssetMediaSize"
}
}
],
"responses": {
"200": {
"content": {
"application/octet-stream": {
"schema": {
"format": "binary",
"type": "string"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Assets"
]
}
},
"/assets/{id}/video/playback": {
"get": {
"operationId": "playAssetVideo",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/octet-stream": {
"schema": {
"format": "binary",
"type": "string"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Assets"
]
}
},
"/audit/deletes": {
"get": {
"operationId": "getAuditDeletes",
@ -2354,57 +2383,6 @@
]
}
},
"/download/asset/{id}": {
"post": {
"operationId": "downloadFile",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/octet-stream": {
"schema": {
"format": "binary",
"type": "string"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Download"
]
}
},
"/download/info": {
"post": {
"operationId": "getDownloadInfo",
@ -7417,21 +7395,6 @@
],
"type": "object"
},
"AssetFileUploadResponseDto": {
"properties": {
"duplicate": {
"type": "boolean"
},
"id": {
"type": "string"
}
},
"required": [
"duplicate",
"id"
],
"type": "object"
},
"AssetFullSyncDto": {
"properties": {
"lastCreationDate": {
@ -7526,6 +7489,59 @@
],
"type": "object"
},
"AssetMediaCreateDto": {
"properties": {
"assetData": {
"format": "binary",
"type": "string"
},
"deviceAssetId": {
"type": "string"
},
"deviceId": {
"type": "string"
},
"duration": {
"type": "string"
},
"fileCreatedAt": {
"format": "date-time",
"type": "string"
},
"fileModifiedAt": {
"format": "date-time",
"type": "string"
},
"isArchived": {
"type": "boolean"
},
"isFavorite": {
"type": "boolean"
},
"isOffline": {
"type": "boolean"
},
"isVisible": {
"type": "boolean"
},
"livePhotoVideoId": {
"format": "uuid",
"type": "string"
},
"sidecarData": {
"format": "binary",
"type": "string"
}
},
"required": [
"assetData",
"deviceAssetId",
"deviceId",
"fileCreatedAt",
"fileModifiedAt"
],
"type": "object"
},
"AssetMediaReplaceDto": {
"properties": {
"assetData": {
@ -7574,8 +7590,16 @@
],
"type": "object"
},
"AssetMediaSize": {
"enum": [
"preview",
"thumbnail"
],
"type": "string"
},
"AssetMediaStatus": {
"enum": [
"created",
"replaced",
"duplicate"
],
@ -7963,59 +7987,6 @@
],
"type": "object"
},
"CreateAssetDto": {
"properties": {
"assetData": {
"format": "binary",
"type": "string"
},
"deviceAssetId": {
"type": "string"
},
"deviceId": {
"type": "string"
},
"duration": {
"type": "string"
},
"fileCreatedAt": {
"format": "date-time",
"type": "string"
},
"fileModifiedAt": {
"format": "date-time",
"type": "string"
},
"isArchived": {
"type": "boolean"
},
"isFavorite": {
"type": "boolean"
},
"isOffline": {
"type": "boolean"
},
"isVisible": {
"type": "boolean"
},
"livePhotoData": {
"format": "binary",
"type": "string"
},
"sidecarData": {
"format": "binary",
"type": "string"
}
},
"required": [
"assetData",
"deviceAssetId",
"deviceId",
"fileCreatedAt",
"fileModifiedAt"
],
"type": "object"
},
"CreateLibraryDto": {
"properties": {
"exclusionPatterns": {
@ -10872,13 +10843,6 @@
],
"type": "string"
},
"ThumbnailFormat": {
"enum": [
"JPEG",
"WEBP"
],
"type": "string"
},
"TimeBucketResponseDto": {
"properties": {
"count": {

View file

@ -264,6 +264,24 @@ export type AssetBulkDeleteDto = {
force?: boolean;
ids: string[];
};
export type AssetMediaCreateDto = {
assetData: Blob;
deviceAssetId: string;
deviceId: string;
duration?: string;
fileCreatedAt: string;
fileModifiedAt: string;
isArchived?: boolean;
isFavorite?: boolean;
isOffline?: boolean;
isVisible?: boolean;
livePhotoVideoId?: string;
sidecarData?: Blob;
};
export type AssetMediaResponseDto = {
id: string;
status: AssetMediaStatus;
};
export type AssetBulkUpdateDto = {
dateTimeOriginal?: string;
duplicateId?: string | null;
@ -316,24 +334,6 @@ export type AssetStatsResponseDto = {
total: number;
videos: number;
};
export type CreateAssetDto = {
assetData: Blob;
deviceAssetId: string;
deviceId: string;
duration?: string;
fileCreatedAt: string;
fileModifiedAt: string;
isArchived?: boolean;
isFavorite?: boolean;
isOffline?: boolean;
isVisible?: boolean;
livePhotoData?: Blob;
sidecarData?: Blob;
};
export type AssetFileUploadResponseDto = {
duplicate: boolean;
id: string;
};
export type UpdateAssetDto = {
dateTimeOriginal?: string;
description?: string;
@ -350,10 +350,6 @@ export type AssetMediaReplaceDto = {
fileCreatedAt: string;
fileModifiedAt: string;
};
export type AssetMediaResponseDto = {
id: string;
status: AssetMediaStatus;
};
export type AuditDeletesResponseDto = {
ids: string[];
needsFullSync: boolean;
@ -1434,16 +1430,35 @@ export function updateApiKey({ id, apiKeyUpdateDto }: {
export function deleteAssets({ assetBulkDeleteDto }: {
assetBulkDeleteDto: AssetBulkDeleteDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText("/asset", oazapfts.json({
return oazapfts.ok(oazapfts.fetchText("/assets", oazapfts.json({
...opts,
method: "DELETE",
body: assetBulkDeleteDto
})));
}
export function uploadAsset({ key, xImmichChecksum, assetMediaCreateDto }: {
key?: string;
xImmichChecksum?: string;
assetMediaCreateDto: AssetMediaCreateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: AssetMediaResponseDto;
}>(`/assets${QS.query(QS.explode({
key
}))}`, oazapfts.multipart({
...opts,
method: "POST",
body: assetMediaCreateDto,
headers: oazapfts.mergeHeaders(opts?.headers, {
"x-immich-checksum": xImmichChecksum
})
})));
}
export function updateAssets({ assetBulkUpdateDto }: {
assetBulkUpdateDto: AssetBulkUpdateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText("/asset", oazapfts.json({
return oazapfts.ok(oazapfts.fetchText("/assets", oazapfts.json({
...opts,
method: "PUT",
body: assetBulkUpdateDto
@ -1458,7 +1473,7 @@ export function checkBulkUpload({ assetBulkUploadCheckDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetBulkUploadCheckResponseDto;
}>("/asset/bulk-upload-check", oazapfts.json({
}>("/assets/bulk-upload-check", oazapfts.json({
...opts,
method: "POST",
body: assetBulkUploadCheckDto
@ -1473,7 +1488,7 @@ export function getAllUserAssetsByDeviceId({ deviceId }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: string[];
}>(`/asset/device/${encodeURIComponent(deviceId)}`, {
}>(`/assets/device/${encodeURIComponent(deviceId)}`, {
...opts
}));
}
@ -1486,33 +1501,16 @@ export function checkExistingAssets({ checkExistingAssetsDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: CheckExistingAssetsResponseDto;
}>("/asset/exist", oazapfts.json({
}>("/assets/exist", oazapfts.json({
...opts,
method: "POST",
body: checkExistingAssetsDto
})));
}
export function serveFile({ id, isThumb, isWeb, key }: {
id: string;
isThumb?: boolean;
isWeb?: boolean;
key?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/asset/file/${encodeURIComponent(id)}${QS.query(QS.explode({
isThumb,
isWeb,
key
}))}`, {
...opts
}));
}
export function runAssetJobs({ assetJobsDto }: {
assetJobsDto: AssetJobsDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText("/asset/jobs", oazapfts.json({
return oazapfts.ok(oazapfts.fetchText("/assets/jobs", oazapfts.json({
...opts,
method: "POST",
body: assetJobsDto
@ -1525,7 +1523,7 @@ export function getMemoryLane({ day, month }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: MemoryLaneResponseDto[];
}>(`/asset/memory-lane${QS.query(QS.explode({
}>(`/assets/memory-lane${QS.query(QS.explode({
day,
month
}))}`, {
@ -1538,7 +1536,7 @@ export function getRandom({ count }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetResponseDto[];
}>(`/asset/random${QS.query(QS.explode({
}>(`/assets/random${QS.query(QS.explode({
count
}))}`, {
...opts
@ -1547,7 +1545,7 @@ export function getRandom({ count }: {
export function updateStackParent({ updateStackParentDto }: {
updateStackParentDto: UpdateStackParentDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText("/asset/stack/parent", oazapfts.json({
return oazapfts.ok(oazapfts.fetchText("/assets/stack/parent", oazapfts.json({
...opts,
method: "PUT",
body: updateStackParentDto
@ -1561,7 +1559,7 @@ export function getAssetStatistics({ isArchived, isFavorite, isTrashed }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetStatsResponseDto;
}>(`/asset/statistics${QS.query(QS.explode({
}>(`/assets/statistics${QS.query(QS.explode({
isArchived,
isFavorite,
isTrashed
@ -1569,40 +1567,6 @@ export function getAssetStatistics({ isArchived, isFavorite, isTrashed }: {
...opts
}));
}
export function getAssetThumbnail({ format, id, key }: {
format?: ThumbnailFormat;
id: string;
key?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/asset/thumbnail/${encodeURIComponent(id)}${QS.query(QS.explode({
format,
key
}))}`, {
...opts
}));
}
export function uploadFile({ key, xImmichChecksum, createAssetDto }: {
key?: string;
xImmichChecksum?: string;
createAssetDto: CreateAssetDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: AssetFileUploadResponseDto;
}>(`/asset/upload${QS.query(QS.explode({
key
}))}`, oazapfts.multipart({
...opts,
method: "POST",
body: createAssetDto,
headers: oazapfts.mergeHeaders(opts?.headers, {
"x-immich-checksum": xImmichChecksum
})
})));
}
export function getAssetInfo({ id, key }: {
id: string;
key?: string;
@ -1610,7 +1574,7 @@ export function getAssetInfo({ id, key }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetResponseDto;
}>(`/asset/${encodeURIComponent(id)}${QS.query(QS.explode({
}>(`/assets/${encodeURIComponent(id)}${QS.query(QS.explode({
key
}))}`, {
...opts
@ -1623,12 +1587,25 @@ export function updateAsset({ id, updateAssetDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetResponseDto;
}>(`/asset/${encodeURIComponent(id)}`, oazapfts.json({
}>(`/assets/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PUT",
body: updateAssetDto
})));
}
export function downloadAsset({ id, key }: {
id: string;
key?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/assets/${encodeURIComponent(id)}/original${QS.query(QS.explode({
key
}))}`, {
...opts
}));
}
/**
* Replace the asset with new file, without changing its id
*/
@ -1640,7 +1617,7 @@ export function replaceAsset({ id, key, assetMediaReplaceDto }: {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetMediaResponseDto;
}>(`/asset/${encodeURIComponent(id)}/file${QS.query(QS.explode({
}>(`/assets/${encodeURIComponent(id)}/original${QS.query(QS.explode({
key
}))}`, oazapfts.multipart({
...opts,
@ -1648,6 +1625,34 @@ export function replaceAsset({ id, key, assetMediaReplaceDto }: {
body: assetMediaReplaceDto
})));
}
export function viewAsset({ id, key, size }: {
id: string;
key?: string;
size?: AssetMediaSize;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/assets/${encodeURIComponent(id)}/thumbnail${QS.query(QS.explode({
key,
size
}))}`, {
...opts
}));
}
export function playAssetVideo({ id, key }: {
id: string;
key?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/assets/${encodeURIComponent(id)}/video/playback${QS.query(QS.explode({
key
}))}`, {
...opts
}));
}
export function getAuditDeletes({ after, entityType, userId }: {
after: string;
entityType: EntityType;
@ -1733,20 +1738,6 @@ export function downloadArchive({ key, assetIdsDto }: {
body: assetIdsDto
})));
}
export function downloadFile({ id, key }: {
id: string;
key?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchBlob<{
status: 200;
data: Blob;
}>(`/download/asset/${encodeURIComponent(id)}${QS.query(QS.explode({
key
}))}`, {
...opts,
method: "POST"
}));
}
export function getDownloadInfo({ key, downloadInfoDto }: {
key?: string;
downloadInfoDto: DownloadInfoDto;
@ -2929,6 +2920,11 @@ export enum Error {
NotFound = "not_found",
Unknown = "unknown"
}
export enum AssetMediaStatus {
Created = "created",
Replaced = "replaced",
Duplicate = "duplicate"
}
export enum Action {
Accept = "accept",
Reject = "reject"
@ -2942,13 +2938,9 @@ export enum AssetJobName {
RefreshMetadata = "refresh-metadata",
TranscodeVideo = "transcode-video"
}
export enum ThumbnailFormat {
Jpeg = "JPEG",
Webp = "WEBP"
}
export enum AssetMediaStatus {
Replaced = "replaced",
Duplicate = "duplicate"
export enum AssetMediaSize {
Preview = "preview",
Thumbnail = "thumbnail"
}
export enum EntityType {
Asset = "ASSET",

View file

@ -24,9 +24,12 @@ export const setApiKey = (apiKey: string) => {
defaults.headers['x-api-key'] = apiKey;
};
export const getAssetOriginalPath = (id: string) => `/asset/file/${id}`;
export const getAssetOriginalPath = (id: string) => `/assets/${id}/original`;
export const getAssetThumbnailPath = (id: string) => `/asset/thumbnail/${id}`;
export const getAssetThumbnailPath = (id: string) => `/assets/${id}/thumbnail`;
export const getAssetPlaybackPath = (id: string) =>
`/assets/${id}/video/playback`;
export const getUserProfileImagePath = (userId: string) =>
`/users/${userId}/profile-image`;