mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix!: set duration to null when not present (#26982)
This commit is contained in:
@@ -332,7 +332,7 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
|
|||||||
isArchived: false,
|
isArchived: false,
|
||||||
isTrashed: asset.isTrashed,
|
isTrashed: asset.isTrashed,
|
||||||
visibility: asset.visibility,
|
visibility: asset.visibility,
|
||||||
duration: asset.duration || '0:00:00.00000',
|
duration: asset.duration,
|
||||||
exifInfo,
|
exifInfo,
|
||||||
livePhotoVideoId: asset.livePhotoVideoId,
|
livePhotoVideoId: asset.livePhotoVideoId,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
|
|||||||
isArchived: false,
|
isArchived: false,
|
||||||
isTrashed: false,
|
isTrashed: false,
|
||||||
visibility: AssetVisibility.Timeline,
|
visibility: AssetVisibility.Timeline,
|
||||||
duration: '0:00:00.00000',
|
duration: null,
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
make: null,
|
make: null,
|
||||||
model: null,
|
model: null,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationInSeconds: duration.toDuration()?.inSeconds ?? 0,
|
durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
@@ -36,7 +36,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationInSeconds: duration.toDuration()?.inSeconds ?? 0,
|
durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
|
|||||||
+8
-4
@@ -57,8 +57,8 @@ class AssetResponseDto {
|
|||||||
/// Duplicate group ID
|
/// Duplicate group ID
|
||||||
String? duplicateId;
|
String? duplicateId;
|
||||||
|
|
||||||
/// Video duration (for videos)
|
/// Video/gif duration in hh:mm:ss.SSS format (null for static images)
|
||||||
String duration;
|
String? duration;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@@ -209,7 +209,7 @@ class AssetResponseDto {
|
|||||||
(checksum.hashCode) +
|
(checksum.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(duplicateId == null ? 0 : duplicateId!.hashCode) +
|
(duplicateId == null ? 0 : duplicateId!.hashCode) +
|
||||||
(duration.hashCode) +
|
(duration == null ? 0 : duration!.hashCode) +
|
||||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||||
(fileCreatedAt.hashCode) +
|
(fileCreatedAt.hashCode) +
|
||||||
(fileModifiedAt.hashCode) +
|
(fileModifiedAt.hashCode) +
|
||||||
@@ -252,7 +252,11 @@ class AssetResponseDto {
|
|||||||
} else {
|
} else {
|
||||||
// json[r'duplicateId'] = null;
|
// json[r'duplicateId'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.duration != null) {
|
||||||
json[r'duration'] = this.duration;
|
json[r'duration'] = this.duration;
|
||||||
|
} else {
|
||||||
|
// json[r'duration'] = null;
|
||||||
|
}
|
||||||
if (this.exifInfo != null) {
|
if (this.exifInfo != null) {
|
||||||
json[r'exifInfo'] = this.exifInfo;
|
json[r'exifInfo'] = this.exifInfo;
|
||||||
} else {
|
} else {
|
||||||
@@ -337,7 +341,7 @@ class AssetResponseDto {
|
|||||||
checksum: mapValueOfType<String>(json, r'checksum')!,
|
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||||
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
||||||
duration: mapValueOfType<String>(json, r'duration')!,
|
duration: mapValueOfType<String>(json, r'duration'),
|
||||||
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
||||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
|
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
|
||||||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,
|
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class TimeBucketAssetResponseDto {
|
|||||||
/// Array of country names extracted from EXIF GPS data
|
/// Array of country names extracted from EXIF GPS data
|
||||||
List<String?> country;
|
List<String?> country;
|
||||||
|
|
||||||
/// Array of video durations in HH:MM:SS format (null for images)
|
/// Array of video/gif durations in hh:mm:ss.SSS format (null for static images)
|
||||||
List<String?> duration;
|
List<String?> duration;
|
||||||
|
|
||||||
/// Array of file creation timestamps in UTC
|
/// Array of file creation timestamps in UTC
|
||||||
|
|||||||
@@ -16640,7 +16640,8 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"duration": {
|
"duration": {
|
||||||
"description": "Video duration (for videos)",
|
"description": "Video/gif duration in hh:mm:ss.SSS format (null for static images)",
|
||||||
|
"nullable": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"exifInfo": {
|
"exifInfo": {
|
||||||
@@ -24911,7 +24912,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"duration": {
|
"duration": {
|
||||||
"description": "Array of video durations in HH:MM:SS format (null for images)",
|
"description": "Array of video/gif durations in hh:mm:ss.SSS format (null for static images)",
|
||||||
"items": {
|
"items": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
@@ -856,8 +856,8 @@ export type AssetResponseDto = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
/** Duplicate group ID */
|
/** Duplicate group ID */
|
||||||
duplicateId?: string | null;
|
duplicateId?: string | null;
|
||||||
/** Video duration (for videos) */
|
/** Video/gif duration in hh:mm:ss.SSS format (null for static images) */
|
||||||
duration: string;
|
duration: string | null;
|
||||||
exifInfo?: ExifResponseDto;
|
exifInfo?: ExifResponseDto;
|
||||||
/** The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. */
|
/** The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. */
|
||||||
fileCreatedAt: string;
|
fileCreatedAt: string;
|
||||||
@@ -2671,7 +2671,7 @@ export type TimeBucketAssetResponseDto = {
|
|||||||
city: (string | null)[];
|
city: (string | null)[];
|
||||||
/** Array of country names extracted from EXIF GPS data */
|
/** Array of country names extracted from EXIF GPS data */
|
||||||
country: (string | null)[];
|
country: (string | null)[];
|
||||||
/** Array of video durations in HH:MM:SS format (null for images) */
|
/** Array of video/gif durations in hh:mm:ss.SSS format (null for static images) */
|
||||||
duration: (string | null)[];
|
duration: (string | null)[];
|
||||||
/** Array of file creation timestamps in UTC */
|
/** Array of file creation timestamps in UTC */
|
||||||
fileCreatedAt: string[];
|
fileCreatedAt: string[];
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ const makeUploadDto = (options?: { omit: string }): Record<string, any> => {
|
|||||||
fileCreatedAt: new Date().toISOString(),
|
fileCreatedAt: new Date().toISOString(),
|
||||||
fileModifiedAt: new Date().toISOString(),
|
fileModifiedAt: new Date().toISOString(),
|
||||||
isFavorite: 'false',
|
isFavorite: 'false',
|
||||||
duration: '0:00:00.000000',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const omit = options?.omit;
|
const omit = options?.omit;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const SanitizedAssetResponseSchema = z
|
|||||||
.describe(
|
.describe(
|
||||||
'The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer\'s local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months.',
|
'The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer\'s local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months.',
|
||||||
),
|
),
|
||||||
duration: z.string().describe('Video duration (for videos)'),
|
duration: z.string().nullable().describe('Video/gif duration in hh:mm:ss.SSS format (null for static images)'),
|
||||||
livePhotoVideoId: z.string().nullish().describe('Live photo video ID'),
|
livePhotoVideoId: z.string().nullish().describe('Live photo video ID'),
|
||||||
hasMetadata: z.boolean().describe('Whether asset has metadata'),
|
hasMetadata: z.boolean().describe('Whether asset has metadata'),
|
||||||
width: z.number().min(0).nullable().describe('Asset width'),
|
width: z.number().min(0).nullable().describe('Asset width'),
|
||||||
@@ -221,7 +221,7 @@ export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOpt
|
|||||||
originalMimeType: mimeTypes.lookup(entity.originalFileName),
|
originalMimeType: mimeTypes.lookup(entity.originalFileName),
|
||||||
thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null,
|
thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null,
|
||||||
localDateTime: asDateString(entity.localDateTime),
|
localDateTime: asDateString(entity.localDateTime),
|
||||||
duration: entity.duration ?? '0:00:00.00000',
|
duration: entity.duration,
|
||||||
livePhotoVideoId: entity.livePhotoVideoId,
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
hasMetadata: false,
|
hasMetadata: false,
|
||||||
width: entity.width,
|
width: entity.width,
|
||||||
@@ -251,7 +251,7 @@ export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOpt
|
|||||||
isArchived: entity.visibility === AssetVisibility.Archive,
|
isArchived: entity.visibility === AssetVisibility.Archive,
|
||||||
isTrashed: !!entity.deletedAt,
|
isTrashed: !!entity.deletedAt,
|
||||||
visibility: entity.visibility,
|
visibility: entity.visibility,
|
||||||
duration: entity.duration ?? '0:00:00.00000',
|
duration: entity.duration,
|
||||||
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
||||||
livePhotoVideoId: entity.livePhotoVideoId,
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
tags: entity.tags?.map((tag) => mapTag(tag)),
|
tags: entity.tags?.map((tag) => mapTag(tag)),
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ const TimeBucketAssetResponseSchema = z
|
|||||||
.describe(
|
.describe(
|
||||||
"Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective.",
|
"Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective.",
|
||||||
),
|
),
|
||||||
duration: z.array(z.string().nullable()).describe('Array of video durations in HH:MM:SS format (null for images)'),
|
duration: z
|
||||||
|
.array(z.string().nullable())
|
||||||
|
.describe('Array of video/gif durations in hh:mm:ss.SSS format (null for static images)'),
|
||||||
stack: z
|
stack: z
|
||||||
.array(stackTupleSchema)
|
.array(stackTupleSchema)
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ const createDto = Object.freeze({
|
|||||||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
duration: '0:00:00.000000',
|
|
||||||
}) as AssetMediaCreateDto;
|
}) as AssetMediaCreateDto;
|
||||||
|
|
||||||
const assetEntity = Object.freeze({
|
const assetEntity = Object.freeze({
|
||||||
@@ -160,7 +159,7 @@ const assetEntity = Object.freeze({
|
|||||||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||||
updatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
updatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
duration: '0:00:00.000000',
|
duration: null,
|
||||||
files: [] as AssetFile[],
|
files: [] as AssetFile[],
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
latitude: 49.533_547,
|
latitude: 49.533_547,
|
||||||
|
|||||||
@@ -291,7 +291,7 @@
|
|||||||
playbackOnIconHover={!$playVideoThumbnailOnHover}
|
playbackOnIconHover={!$playVideoThumbnailOnHover}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else if asset.isImage && asset.duration && !asset.duration.includes('0:00:00.000') && mouseOver}
|
{:else if asset.isImage && asset.duration && mouseOver}
|
||||||
<!-- GIF -->
|
<!-- GIF -->
|
||||||
<div class="absolute h-full w-full pointer-events-none">
|
<div class="absolute h-full w-full pointer-events-none">
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
@@ -369,7 +369,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if asset.isImage && asset.duration && !asset.duration.includes('0:00:00.000')}
|
{#if asset.isImage && asset.duration}
|
||||||
<div class="z-2 absolute inset-e-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
<div class="z-2 absolute inset-e-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
||||||
<span class="pe-2 pt-2">
|
<span class="pe-2 pt-2">
|
||||||
<Icon icon={mouseOver ? mdiMotionPauseOutline : mdiFileGifBox} size="24" />
|
<Icon icon={mouseOver ? mdiMotionPauseOutline : mdiFileGifBox} size="24" />
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ export function getAssetUrls(asset: AssetResponseDto, sharedLink?: SharedLinkRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
const forceUseOriginal = (asset: AssetResponseDto) => {
|
const forceUseOriginal = (asset: AssetResponseDto) => {
|
||||||
return asset.type === AssetTypeEnum.Image && asset.duration && !asset.duration.includes('0:00:00.000');
|
return asset.type === AssetTypeEnum.Image && asset.duration;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const targetImageSize = (asset: AssetResponseDto, forceOriginal: boolean) => {
|
export const targetImageSize = (asset: AssetResponseDto, forceOriginal: boolean) => {
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ async function fileUploader({
|
|||||||
fileCreatedAt,
|
fileCreatedAt,
|
||||||
fileModifiedAt: new Date(assetFile.lastModified).toISOString(),
|
fileModifiedAt: new Date(assetFile.lastModified).toISOString(),
|
||||||
isFavorite: 'false',
|
isFavorite: 'false',
|
||||||
duration: '0:00:00.000000',
|
|
||||||
assetData: new File([assetFile], assetFile.name),
|
assetData: new File([assetFile], assetFile.name),
|
||||||
})) {
|
})) {
|
||||||
formData.append(key, value);
|
formData.append(key, value);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const assetFactory = Sync.makeFactory<AssetResponseDto>({
|
|||||||
isFavorite: Sync.each(() => faker.datatype.boolean()),
|
isFavorite: Sync.each(() => faker.datatype.boolean()),
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
isTrashed: false,
|
isTrashed: false,
|
||||||
duration: '0:00:00.00000',
|
duration: null,
|
||||||
checksum: Sync.each(() => faker.string.alphanumeric(28)),
|
checksum: Sync.each(() => faker.string.alphanumeric(28)),
|
||||||
isOffline: Sync.each(() => faker.datatype.boolean()),
|
isOffline: Sync.each(() => faker.datatype.boolean()),
|
||||||
hasMetadata: Sync.each(() => faker.datatype.boolean()),
|
hasMetadata: Sync.each(() => faker.datatype.boolean()),
|
||||||
@@ -44,7 +44,7 @@ export const timelineAssetFactory = Sync.makeFactory<TimelineAsset>({
|
|||||||
isTrashed: false,
|
isTrashed: false,
|
||||||
isImage: true,
|
isImage: true,
|
||||||
isVideo: false,
|
isVideo: false,
|
||||||
duration: '0:00:00.00000',
|
duration: null,
|
||||||
stack: null,
|
stack: null,
|
||||||
projectionType: null,
|
projectionType: null,
|
||||||
livePhotoVideoId: Sync.each(() => faker.string.uuid()),
|
livePhotoVideoId: Sync.each(() => faker.string.uuid()),
|
||||||
|
|||||||
Reference in New Issue
Block a user