handle live photos

This commit is contained in:
mertalev 2025-10-09 16:12:47 -04:00
parent a3d10ff46a
commit 883eb15ecb
No known key found for this signature in database
GPG key ID: DF6ABC77AAD98C95
8 changed files with 34 additions and 11 deletions

View file

@ -293,7 +293,7 @@ class UploadApi {
/// Indicates the version of the RUFH protocol supported by the client.
///
/// * [String] xImmichAssetData (required):
/// RFC 9651 structured dictionary containing asset metadata with the following keys: - device-asset-id (string, required): Unique device asset identifier - device-id (string, required): Device identifier - file-created-at (string/date, required): ISO 8601 date string or Unix timestamp - file-modified-at (string/date, required): ISO 8601 date string or Unix timestamp - filename (string, required): Original filename - is-favorite (boolean, optional): Favorite status - icloud-id (string, optional): iCloud identifier for assets from iOS devices
/// RFC 9651 structured dictionary containing asset metadata with the following keys: - device-asset-id (string, required): Unique device asset identifier - device-id (string, required): Device identifier - file-created-at (string/date, required): ISO 8601 date string or Unix timestamp - file-modified-at (string/date, required): ISO 8601 date string or Unix timestamp - filename (string, required): Original filename - is-favorite (boolean, optional): Favorite status - live-photo-video-id (string, optional): Live photo ID for assets from iOS devices - icloud-id (string, optional): iCloud identifier for assets from iOS devices
///
/// * [String] key:
///
@ -353,7 +353,7 @@ class UploadApi {
/// Indicates the version of the RUFH protocol supported by the client.
///
/// * [String] xImmichAssetData (required):
/// RFC 9651 structured dictionary containing asset metadata with the following keys: - device-asset-id (string, required): Unique device asset identifier - device-id (string, required): Device identifier - file-created-at (string/date, required): ISO 8601 date string or Unix timestamp - file-modified-at (string/date, required): ISO 8601 date string or Unix timestamp - filename (string, required): Original filename - is-favorite (boolean, optional): Favorite status - icloud-id (string, optional): iCloud identifier for assets from iOS devices
/// RFC 9651 structured dictionary containing asset metadata with the following keys: - device-asset-id (string, required): Unique device asset identifier - device-id (string, required): Device identifier - file-created-at (string/date, required): ISO 8601 date string or Unix timestamp - file-modified-at (string/date, required): ISO 8601 date string or Unix timestamp - filename (string, required): Original filename - is-favorite (boolean, optional): Favorite status - live-photo-video-id (string, optional): Live photo ID for assets from iOS devices - icloud-id (string, optional): iCloud identifier for assets from iOS devices
///
/// * [String] key:
///

View file

@ -9296,7 +9296,7 @@
{
"name": "x-immich-asset-data",
"in": "header",
"description": "RFC 9651 structured dictionary containing asset metadata with the following keys:\n- device-asset-id (string, required): Unique device asset identifier\n- device-id (string, required): Device identifier\n- file-created-at (string/date, required): ISO 8601 date string or Unix timestamp\n- file-modified-at (string/date, required): ISO 8601 date string or Unix timestamp\n- filename (string, required): Original filename\n- is-favorite (boolean, optional): Favorite status\n- icloud-id (string, optional): iCloud identifier for assets from iOS devices",
"description": "RFC 9651 structured dictionary containing asset metadata with the following keys:\n- device-asset-id (string, required): Unique device asset identifier\n- device-id (string, required): Device identifier\n- file-created-at (string/date, required): ISO 8601 date string or Unix timestamp\n- file-modified-at (string/date, required): ISO 8601 date string or Unix timestamp\n- filename (string, required): Original filename\n- is-favorite (boolean, optional): Favorite status\n- live-photo-video-id (string, optional): Live photo ID for assets from iOS devices\n- icloud-id (string, optional): iCloud identifier for assets from iOS devices",
"required": true,
"schema": {
"type": "string"

View file

@ -14,7 +14,13 @@ import {
} from '@nestjs/common';
import { ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { GetUploadStatusDto, ResumeUploadDto, StartUploadDto, UploadHeader, UploadOkDto } from 'src/dtos/asset-upload';
import {
GetUploadStatusDto,
ResumeUploadDto,
StartUploadDto,
UploadHeader,
UploadOkDto,
} from 'src/dtos/asset-upload.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { ImmichHeader, Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
@ -60,6 +66,7 @@ export class AssetUploadController {
- file-modified-at (string/date, required): ISO 8601 date string or Unix timestamp
- filename (string, required): Original filename
- is-favorite (boolean, optional): Favorite status
- live-photo-video-id (string, optional): Live photo ID for assets from iOS devices
- icloud-id (string, optional): iCloud identifier for assets from iOS devices`,
required: true,
example:

View file

@ -28,6 +28,11 @@ export class UploadAssetDataDto {
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@Optional()
@IsString()
@IsNotEmpty()
livePhotoVideoId?: string;
@Optional()
@IsString()
@IsNotEmpty()
@ -101,6 +106,7 @@ export class StartUploadDto extends BaseUploadHeadersDto {
fileCreatedAt: dict.get('file-created-at')?.[0],
fileModifiedAt: dict.get('file-modified-at')?.[0],
isFavorite: dict.get('is-favorite')?.[0],
livePhotoVideoId: dict.get('live-photo-video-id')?.[0],
iCloudId: dict.get('icloud-id')?.[0],
});
} catch {
@ -126,7 +132,7 @@ export class StartUploadDto extends BaseUploadHeadersDto {
@Expose({ name: UploadHeader.UploadLength })
@Min(0)
@IsInt()
@Type(() => Number)
@Transform(({ obj, value }) => Number(value === undefined ? obj['x-upload-length'] : value))
uploadLength!: number;
@Expose({ name: UploadHeader.UploadOffset })

View file

@ -64,10 +64,16 @@ where
-- AssetRepository.setComplete
update "asset"
set
"status" = $1,
"visibility" = $2
"status" = 'active',
"visibility" = (
case
when type = 'VIDEO'
and "livePhotoVideoId" is not null then 'hidden'
else 'timeline'
end
)::asset_visibility_enum
where
"id" = $3
"id" = $1
and "status" = 'partial'
-- AssetRepository.removeAndDecrementQuota

View file

@ -292,7 +292,10 @@ export class AssetRepository {
async setComplete(assetId: string) {
await this.db
.updateTable('asset')
.set({ status: AssetStatus.Active, visibility: AssetVisibility.Timeline })
.set({
status: sql.lit(AssetStatus.Active),
visibility: sql`(case when type = 'VIDEO' and "livePhotoVideoId" is not null then 'hidden' else 'timeline' end)::asset_visibility_enum`,
})
.where('id', '=', assetId)
.where('status', '=', sql.lit(AssetStatus.Partial))
.execute();

View file

@ -1,5 +1,5 @@
import { BadRequestException, InternalServerErrorException } from '@nestjs/common';
import { StructuredBoolean } from 'src/dtos/asset-upload';
import { StructuredBoolean } from 'src/dtos/asset-upload.dto';
import { AssetMetadataKey, AssetStatus, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum';
import { AssetUploadService } from 'src/services/asset-upload.service';
import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database';

View file

@ -7,7 +7,7 @@ import { Readable } from 'node:stream';
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants';
import { StorageCore } from 'src/cores/storage.core';
import { OnEvent, OnJob } from 'src/decorators';
import { GetUploadStatusDto, ResumeUploadDto, StartUploadDto } from 'src/dtos/asset-upload';
import { GetUploadStatusDto, ResumeUploadDto, StartUploadDto } from 'src/dtos/asset-upload.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import {
AssetMetadataKey,
@ -269,6 +269,7 @@ export class AssetUploadService extends BaseService {
localDateTime: assetData.fileCreatedAt,
type,
isFavorite: assetData.isFavorite,
livePhotoVideoId: assetData.livePhotoVideoId,
visibility: AssetVisibility.Hidden,
originalFileName: assetData.filename,
status: AssetStatus.Partial,