asset-utils.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { api, AddAssetsResponseDto, AssetResponseDto } from '@api';
  2. import {
  3. notificationController,
  4. NotificationType
  5. } from '$lib/components/shared-components/notification/notification';
  6. import { downloadAssets } from '$lib/stores/download';
  7. export const addAssetsToAlbum = async (
  8. albumId: string,
  9. assetIds: Array<string>,
  10. key: string | undefined = undefined
  11. ): Promise<AddAssetsResponseDto> =>
  12. api.albumApi
  13. .addAssetsToAlbum({ id: albumId, addAssetsDto: { assetIds }, key })
  14. .then(({ data: dto }) => {
  15. if (dto.successfullyAdded > 0) {
  16. // This might be 0 if the user tries to add an asset that is already in the album
  17. notificationController.show({
  18. message: `Added ${dto.successfullyAdded} to ${dto.album?.albumName}`,
  19. type: NotificationType.Info
  20. });
  21. }
  22. return dto;
  23. });
  24. export async function bulkDownload(
  25. fileName: string,
  26. assets: AssetResponseDto[],
  27. onDone?: () => void,
  28. key?: string
  29. ) {
  30. const assetIds = assets.map((asset) => asset.id);
  31. try {
  32. // let skip = 0;
  33. let count = 0;
  34. let done = false;
  35. while (!done) {
  36. count++;
  37. const downloadFileName = fileName + `${count === 1 ? '' : count}.zip`;
  38. downloadAssets.set({ [downloadFileName]: 0 });
  39. let total = 0;
  40. const { data, status, headers } = await api.assetApi.downloadFiles(
  41. { downloadFilesDto: { assetIds }, key },
  42. {
  43. responseType: 'blob',
  44. onDownloadProgress: function (progressEvent) {
  45. const request = this as XMLHttpRequest;
  46. if (!total) {
  47. total = Number(request.getResponseHeader('X-Immich-Content-Length-Hint')) || 0;
  48. }
  49. if (total) {
  50. const current = progressEvent.loaded;
  51. downloadAssets.set({ [downloadFileName]: Math.floor((current / total) * 100) });
  52. }
  53. }
  54. }
  55. );
  56. const isNotComplete = headers['x-immich-archive-complete'] === 'false';
  57. const fileCount = Number(headers['x-immich-archive-file-count']) || 0;
  58. if (isNotComplete && fileCount > 0) {
  59. // skip += fileCount;
  60. } else {
  61. onDone?.();
  62. done = true;
  63. }
  64. if (!(data instanceof Blob)) {
  65. return;
  66. }
  67. if (status === 201) {
  68. const fileUrl = URL.createObjectURL(data);
  69. const anchor = document.createElement('a');
  70. anchor.href = fileUrl;
  71. anchor.download = downloadFileName;
  72. document.body.appendChild(anchor);
  73. anchor.click();
  74. document.body.removeChild(anchor);
  75. URL.revokeObjectURL(fileUrl);
  76. // Remove item from download list
  77. setTimeout(() => {
  78. downloadAssets.set({});
  79. }, 2000);
  80. }
  81. }
  82. } catch (e) {
  83. console.error('Error downloading file ', e);
  84. notificationController.show({
  85. type: NotificationType.Error,
  86. message: 'Error downloading file, check console for more details.'
  87. });
  88. }
  89. }
  90. /**
  91. * Returns the lowercase filename extension without a dot (.) and
  92. * an empty string when not found.
  93. */
  94. export function getFilenameExtension(filename: string): string {
  95. const lastIndex = Math.max(0, filename.lastIndexOf('.'));
  96. const startIndex = (lastIndex || Infinity) + 1;
  97. return filename.slice(startIndex).toLowerCase();
  98. }
  99. /**
  100. * Returns the filename of an asset including file extension
  101. */
  102. export function getAssetFilename(asset: AssetResponseDto): string {
  103. const fileExtension = getFilenameExtension(asset.originalPath);
  104. return `${asset.originalFileName}.${fileExtension}`;
  105. }
  106. /**
  107. * Returns the MIME type of the file and an empty string when not found.
  108. */
  109. export function getFileMimeType(file: File): string {
  110. const mimeTypes: Record<string, string> = {
  111. '3gp': 'video/3gpp',
  112. arw: 'image/x-sony-arw',
  113. dng: 'image/dng',
  114. heic: 'image/heic',
  115. heif: 'image/heif',
  116. insp: 'image/jpeg',
  117. insv: 'video/mp4',
  118. nef: 'image/x-nikon-nef',
  119. raf: 'image/x-fuji-raf',
  120. srw: 'image/x-samsung-srw'
  121. };
  122. // Return the MIME type determined by the browser or the MIME type based on the file extension.
  123. return file.type || (mimeTypes[getFilenameExtension(file.name)] ?? '');
  124. }
  125. /**
  126. * Returns aspect ratio for the asset
  127. */
  128. export function getAssetRatio(asset: AssetResponseDto) {
  129. let height = asset.exifInfo?.exifImageHeight || 235;
  130. let width = asset.exifInfo?.exifImageWidth || 235;
  131. const orientation = Number(asset.exifInfo?.orientation);
  132. if (orientation) {
  133. if (orientation == 6 || orientation == -90) {
  134. [width, height] = [height, width];
  135. }
  136. }
  137. return { width, height };
  138. }