Mees Frensel 2025-12-10 18:13:11 +07:00 committed by GitHub
commit 3c98141d33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 31 additions and 26 deletions

@ -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: [],

@ -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: exifInfo?.exifImageHeight?.toInt(),
width: exifInfo?.exifImageWidth?.toInt(),
isFavorite: isFavorite,

@ -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(),

@ -59,7 +59,8 @@ class AssetResponseDto {
String? duplicateId;
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
@ -184,7 +185,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) +
@ -226,7 +227,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 {
@ -302,7 +307,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'')!,

@ -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 (ISO 8601 format, without timezone)

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

@ -342,7 +342,8 @@ export type AssetResponseDto = {
deviceAssetId: string;
deviceId: string;
duplicateId?: string | null;
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;
@ -1665,7 +1666,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 (ISO 8601 format, without timezone) */
fileCreatedAt: string[];

@ -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;

@ -31,7 +31,8 @@ export class SanitizedAssetResponseDto {
example: '2024-01-15T14:30:00.000Z',
})
localDateTime!: Date;
duration!: string;
@ApiProperty({ description: 'Video/gif duration in hh:mm:ss.SSS format (null for static images)' })
duration!: string | null;
livePhotoVideoId?: string | null;
hasMetadata!: boolean;
}
@ -187,7 +188,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
originalMimeType: mimeTypes.lookup(entity.originalFileName),
thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null,
localDateTime: entity.localDateTime,
duration: entity.duration ?? '0:00:00.00000',
duration: entity.duration,
livePhotoVideoId: entity.livePhotoVideoId,
hasMetadata: false,
};
@ -215,7 +216,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
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)),

@ -147,7 +147,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)[];

@ -145,7 +145,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 replaceDto = Object.freeze({
@ -167,7 +166,7 @@ const assetEntity = Object.freeze({
updatedAt: new Date('2022-06-19T23:41:36.910Z'),
isFavorite: false,
encodedVideoPath: '',
duration: '0:00:00.000000',
duration: null,
files: [] as AssetFile[],
exifInfo: {
latitude: 49.533_547,

@ -62,7 +62,7 @@ const assetResponse: AssetResponseDto = {
updatedAt: today,
isFavorite: false,
isArchived: false,
duration: '0:00:00.00000',
duration: null,
exifInfo: assetInfo,
livePhotoVideoId: null,
tags: [],
@ -80,7 +80,7 @@ const assetResponseWithoutMetadata = {
originalMimeType: 'image/jpeg',
thumbhash: null,
localDateTime: today,
duration: '0:00:00.00000',
duration: null,
livePhotoVideoId: null,
hasMetadata: false,
} as AssetResponseDto;

@ -157,8 +157,7 @@
// when true, will force loading of the original image
let forceUseOriginal: boolean = $derived(
(asset.type === AssetTypeEnum.Image && asset.duration && !asset.duration.includes('0:00:00.000')) ||
$photoZoomState.currentZoom > 1,
(asset.type === AssetTypeEnum.Image && !!asset.duration) || $photoZoomState.currentZoom > 1,
);
const targetImageSize = $derived.by(() => {

@ -295,7 +295,7 @@
</div>
{/if}
{#if asset.isImage && asset.duration && !asset.duration.includes('0:00:00.000')}
{#if asset.isImage && !!asset.duration}
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
<span class="pe-2 pt-2">
<Icon data-icon-playable icon={mdiFileGifBox} size="24" />
@ -364,7 +364,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 top-0 h-full w-full pointer-events-none">
<div class="absolute h-full w-full bg-linear-to-b from-black/25 via-[transparent_25%]"></div>

@ -136,7 +136,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);

@ -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()),
@ -42,7 +42,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()),