mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
chore!: duration in milliseconds (#28003)
* server changes * openapi * web changes * mobile changes * assume 3.0 client * deprecate * review feedback * update medium tests * linting
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte';
|
||||
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
||||
import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
|
||||
import { timeToSeconds } from '$lib/utils/date-time';
|
||||
import { moveFocus } from '$lib/utils/focus-util';
|
||||
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
@@ -274,7 +273,7 @@
|
||||
url={getAssetPlaybackUrl({ id: asset.id, cacheKey: asset.thumbhash })}
|
||||
enablePlayback={mouseOver && $playVideoThumbnailOnHover}
|
||||
curve={selected}
|
||||
durationInSeconds={asset.duration ? timeToSeconds(asset.duration) : 0}
|
||||
durationInSeconds={asset.duration ? asset.duration / 1000 : 0}
|
||||
playbackOnIconHover={!$playVideoThumbnailOnHover}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ export type TimelineAsset = {
|
||||
isVideo: boolean;
|
||||
isImage: boolean;
|
||||
stack: AssetStackResponseDto | null;
|
||||
duration: string | null;
|
||||
duration: number | null;
|
||||
projectionType: string | null;
|
||||
livePhotoVideoId: string | null;
|
||||
city: string | null;
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('utils', () => {
|
||||
originalPath: 'image.gif',
|
||||
originalMimeType: 'image/gif',
|
||||
type: AssetTypeEnum.Image,
|
||||
duration: '2.0',
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
const url = getAssetUrl({ asset });
|
||||
@@ -65,7 +65,7 @@ describe('utils', () => {
|
||||
originalPath: 'image.webp',
|
||||
originalMimeType: 'image/webp',
|
||||
type: AssetTypeEnum.Image,
|
||||
duration: '2.0',
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
const url = getAssetUrl({ asset });
|
||||
@@ -119,7 +119,7 @@ describe('utils', () => {
|
||||
originalPath: 'image.gif',
|
||||
originalMimeType: 'image/gif',
|
||||
type: AssetTypeEnum.Image,
|
||||
duration: '2.0',
|
||||
duration: 2000,
|
||||
});
|
||||
const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] });
|
||||
|
||||
@@ -134,7 +134,7 @@ describe('utils', () => {
|
||||
originalPath: 'image.gif',
|
||||
originalMimeType: 'image/gif',
|
||||
type: AssetTypeEnum.Image,
|
||||
duration: '2.0',
|
||||
duration: 2000,
|
||||
});
|
||||
const sharedLink = sharedLinkFactory.build({ allowDownload: false, assets: [asset] });
|
||||
|
||||
@@ -150,7 +150,7 @@ describe('utils', () => {
|
||||
originalPath: 'image.gif',
|
||||
originalMimeType: 'image/gif',
|
||||
type: AssetTypeEnum.Image,
|
||||
duration: '2.0',
|
||||
duration: 2000,
|
||||
});
|
||||
const sharedLink = sharedLinkFactory.build({ showMetadata: false, assets: [asset] });
|
||||
|
||||
|
||||
@@ -1,53 +1,5 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { getAlbumDateRange, getShortDateRange, timeToSeconds } from './date-time';
|
||||
|
||||
describe('converting time to seconds', () => {
|
||||
it('parses hh:mm:ss correctly', () => {
|
||||
expect(timeToSeconds('01:02:03')).toBeCloseTo(3723);
|
||||
});
|
||||
|
||||
it('parses hh:mm:ss.SSS correctly', () => {
|
||||
expect(timeToSeconds('01:02:03.456')).toBeCloseTo(3723.456);
|
||||
});
|
||||
|
||||
it('parses h:m:s.S correctly', () => {
|
||||
expect(timeToSeconds('1:2:3.4')).toBe(0); // Non-standard format, Luxon returns NaN
|
||||
});
|
||||
|
||||
it('parses hhh:mm:ss.SSS correctly', () => {
|
||||
expect(timeToSeconds('100:02:03.456')).toBe(0); // Non-standard format, Luxon returns NaN
|
||||
});
|
||||
|
||||
it('ignores ignores double milliseconds hh:mm:ss.SSS.SSSSSS', () => {
|
||||
expect(timeToSeconds('01:02:03.456.123456')).toBe(0); // Non-standard format, Luxon returns NaN
|
||||
});
|
||||
|
||||
// Test edge cases that can cause crashes
|
||||
it('handles "0" string input', () => {
|
||||
expect(timeToSeconds('0')).toBe(0);
|
||||
});
|
||||
|
||||
it('handles empty string input', () => {
|
||||
expect(timeToSeconds('')).toBe(0);
|
||||
});
|
||||
|
||||
it('parses HH:MM format correctly', () => {
|
||||
expect(timeToSeconds('01:02')).toBe(3720); // 1 hour 2 minutes = 3720 seconds
|
||||
});
|
||||
|
||||
it('handles malformed time strings', () => {
|
||||
expect(timeToSeconds('invalid')).toBe(0);
|
||||
});
|
||||
|
||||
it('parses single hour format correctly', () => {
|
||||
expect(timeToSeconds('01')).toBe(3600); // Luxon interprets "01" as 1 hour
|
||||
});
|
||||
|
||||
it('handles time strings with invalid numbers', () => {
|
||||
expect(timeToSeconds('aa:bb:cc')).toBe(0);
|
||||
expect(timeToSeconds('01:bb:03')).toBe(0);
|
||||
});
|
||||
});
|
||||
import { getAlbumDateRange, getShortDateRange } from './date-time';
|
||||
|
||||
describe('getShortDateRange', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import { DateTime } from 'luxon';
|
||||
import { get } from 'svelte/store';
|
||||
import { dateFormats } from '$lib/constants';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
|
||||
/**
|
||||
* Convert time like `01:02:03.456` to seconds.
|
||||
*/
|
||||
export function timeToSeconds(time: string) {
|
||||
if (!time || time === '0') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const seconds = Duration.fromISOTime(time).as('seconds');
|
||||
|
||||
return Number.isNaN(seconds) ? 0 : seconds;
|
||||
}
|
||||
export function parseUtcDate(date: string) {
|
||||
return DateTime.fromISO(date, { zone: 'UTC' }).toUTC();
|
||||
}
|
||||
|
||||
@@ -95,18 +95,10 @@
|
||||
};
|
||||
|
||||
const setProgressDuration = (asset: TimelineAsset) => {
|
||||
if (asset.isVideo) {
|
||||
const timeParts = asset.duration!.split(':').map(Number);
|
||||
const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000;
|
||||
progressBarController = new Tween<number>(0, {
|
||||
duration: (from: number, to: number) => (to ? durationInMilliseconds * (to - from) : 0),
|
||||
});
|
||||
} else {
|
||||
progressBarController = new Tween<number>(0, {
|
||||
duration: (from: number, to: number) =>
|
||||
to ? authManager.preferences.memories.duration * 1000 * (to - from) : 0,
|
||||
});
|
||||
}
|
||||
progressBarController = new Tween<number>(0, {
|
||||
duration: (from: number, to: number) =>
|
||||
to ? (asset.isVideo ? asset.duration! : authManager.preferences.memories.duration * 1000) * (to - from) : 0,
|
||||
});
|
||||
};
|
||||
|
||||
const handleNextAsset = () => handleNavigate(current?.next?.asset);
|
||||
|
||||
Reference in New Issue
Block a user