Compare commits

...

2 Commits

Author SHA1 Message Date
Mees Frensel
b5d1ca9357 Merge branch 'main' into fix/nullable-duration 2026-03-17 14:11:40 +01:00
Mees Frensel
9c5357422e fix: set duration to null when not present 2025-12-05 12:22:04 +01:00
15 changed files with 29 additions and 27 deletions

View File

@@ -334,7 +334,7 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
isArchived: false,
isTrashed: asset.isTrashed,
visibility: asset.visibility,
duration: asset.duration || '0:00:00.00000',
duration: asset.duration,
exifInfo,
livePhotoVideoId: asset.livePhotoVideoId,
tags: [],

View File

@@ -69,7 +69,7 @@ extension on AssetResponseDto {
api.AssetVisibility.locked => AssetVisibility.locked,
_ => AssetVisibility.timeline,
},
durationInSeconds: duration.toDuration()?.inSeconds ?? 0,
durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
height: height?.toInt(),
width: width?.toInt(),
isFavorite: isFavorite,

View File

@@ -24,7 +24,7 @@ class Asset {
fileCreatedAt = remote.fileCreatedAt,
fileModifiedAt = remote.fileModifiedAt,
updatedAt = remote.updatedAt,
durationInSeconds = remote.duration.toDuration()?.inSeconds ?? 0,
durationInSeconds = remote.duration?.toDuration()?.inSeconds ?? 0,
type = remote.type.toAssetType(),
fileName = remote.originalFileName,
height = remote.exifInfo?.exifImageHeight?.toInt(),

View File

@@ -65,8 +65,8 @@ class AssetResponseDto {
/// Duplicate group ID
String? duplicateId;
/// Video duration (for videos)
String duration;
/// Video/gif duration in hh:mm:ss.SSS format (null for static images)
String? duration;
///
/// Please note: This property should have been non-nullable! Since the specification file
@@ -219,7 +219,7 @@ class AssetResponseDto {
(deviceAssetId.hashCode) +
(deviceId.hashCode) +
(duplicateId == null ? 0 : duplicateId!.hashCode) +
(duration.hashCode) +
(duration == null ? 0 : duration!.hashCode) +
(exifInfo == null ? 0 : exifInfo!.hashCode) +
(fileCreatedAt.hashCode) +
(fileModifiedAt.hashCode) +
@@ -264,7 +264,11 @@ class AssetResponseDto {
} else {
// json[r'duplicateId'] = null;
}
if (this.duration != null) {
json[r'duration'] = this.duration;
} else {
// json[r'duration'] = null;
}
if (this.exifInfo != null) {
json[r'exifInfo'] = this.exifInfo;
} else {
@@ -351,7 +355,7 @@ class AssetResponseDto {
deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
deviceId: mapValueOfType<String>(json, r'deviceId')!,
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
duration: mapValueOfType<String>(json, r'duration')!,
duration: mapValueOfType<String>(json, r'duration'),
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,

View File

@@ -39,7 +39,7 @@ class TimeBucketAssetResponseDto {
/// Array of country names extracted from EXIF GPS data
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;
/// Array of file creation timestamps in UTC

View File

@@ -16962,7 +16962,8 @@
"type": "string"
},
"duration": {
"description": "Video duration (for videos)",
"description": "Video/gif duration in hh:mm:ss.SSS format (null for static images)",
"nullable": true,
"type": "string"
},
"exifInfo": {
@@ -25036,7 +25037,7 @@
"type": "array"
},
"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": {
"nullable": true,
"type": "string"

View File

@@ -573,8 +573,8 @@ export type AssetResponseDto = {
deviceId: string;
/** Duplicate group ID */
duplicateId?: string | null;
/** Video duration (for videos) */
duration: string;
/** Video/gif duration in hh:mm:ss.SSS format (null for static images) */
duration: string | null;
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. */
fileCreatedAt: string;
@@ -2756,7 +2756,7 @@ export type TimeBucketAssetResponseDto = {
city: (string | null)[];
/** Array of country names extracted from EXIF GPS data */
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)[];
/** Array of file creation timestamps in UTC */
fileCreatedAt: string[];

View File

@@ -14,7 +14,6 @@ const makeUploadDto = (options?: { omit: string }): Record<string, any> => {
fileCreatedAt: new Date().toISOString(),
fileModifiedAt: new Date().toISOString(),
isFavorite: 'false',
duration: '0:00:00.000000',
};
const omit = options?.omit;

View File

@@ -41,8 +41,8 @@ export class SanitizedAssetResponseDto {
example: '2024-01-15T14:30:00.000Z',
})
localDateTime!: string;
@ApiProperty({ description: 'Video duration (for videos)' })
duration!: string;
@ApiProperty({ description: 'Video/gif duration in hh:mm:ss.SSS format (null for static images)' })
duration!: string | null;
@ApiPropertyOptional({ description: 'Live photo video ID' })
livePhotoVideoId?: string | null;
@ApiProperty({ description: 'Whether asset has metadata' })
@@ -247,7 +247,7 @@ export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOpt
originalMimeType: mimeTypes.lookup(entity.originalFileName),
thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null,
localDateTime: asDateString(entity.localDateTime),
duration: entity.duration ?? '0:00:00.00000',
duration: entity.duration,
livePhotoVideoId: entity.livePhotoVideoId,
hasMetadata: false,
width: entity.width,
@@ -279,7 +279,7 @@ export function mapAsset(entity: MaybeDehydrated<MapAsset>, options: AssetMapOpt
isArchived: entity.visibility === AssetVisibility.Archive,
isTrashed: !!entity.deletedAt,
visibility: entity.visibility,
duration: entity.duration ?? '0:00:00.00000',
duration: entity.duration,
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
livePhotoVideoId: entity.livePhotoVideoId,
tags: entity.tags?.map((tag) => mapTag(tag)),

View File

@@ -151,7 +151,7 @@ export class TimeBucketAssetResponseDto {
@ApiProperty({
type: 'array',
items: { type: 'string', nullable: true },
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)',
})
duration!: (string | null)[];

View File

@@ -149,7 +149,6 @@ const createDto = Object.freeze({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
isFavorite: false,
duration: '0:00:00.000000',
}) as AssetMediaCreateDto;
const assetEntity = Object.freeze({
@@ -163,7 +162,7 @@ const assetEntity = Object.freeze({
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
updatedAt: new Date('2022-06-19T23:41:36.910Z'),
isFavorite: false,
duration: '0:00:00.000000',
duration: null,
files: [] as AssetFile[],
exifInfo: {
latitude: 49.533_547,

View File

@@ -281,7 +281,7 @@
playbackOnIconHover={!$playVideoThumbnailOnHover}
/>
</div>
{:else if asset.isImage && asset.duration && !asset.duration.includes('0:00:00.000') && mouseOver}
{:else if asset.isImage && asset.duration && mouseOver}
<!-- GIF -->
<div class="absolute h-full w-full pointer-events-none">
<ImageThumbnail
@@ -361,7 +361,7 @@
</div>
{/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">
<span class="pe-2 pt-2">
<Icon icon={mouseOver ? mdiMotionPauseOutline : mdiFileGifBox} size="24" />

View File

@@ -195,7 +195,7 @@ export function getAssetUrls(asset: AssetResponseDto, sharedLink?: SharedLinkRes
}
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) => {

View File

@@ -157,7 +157,6 @@ async function fileUploader({
fileCreatedAt,
fileModifiedAt: new Date(assetFile.lastModified).toISOString(),
isFavorite: 'false',
duration: '0:00:00.000000',
assetData: new File([assetFile], assetFile.name),
})) {
formData.append(key, value);

View File

@@ -23,7 +23,7 @@ export const assetFactory = Sync.makeFactory<AssetResponseDto>({
isFavorite: Sync.each(() => faker.datatype.boolean()),
isArchived: false,
isTrashed: false,
duration: '0:00:00.00000',
duration: null,
checksum: Sync.each(() => faker.string.alphanumeric(28)),
isOffline: Sync.each(() => faker.datatype.boolean()),
hasMetadata: Sync.each(() => faker.datatype.boolean()),
@@ -46,7 +46,7 @@ export const timelineAssetFactory = Sync.makeFactory<TimelineAsset>({
isTrashed: false,
isImage: true,
isVideo: false,
duration: '0:00:00.00000',
duration: null,
stack: null,
projectionType: null,
livePhotoVideoId: Sync.each(() => faker.string.uuid()),