From e5459b68ffb3a43b24bb21ac4bb8122410a732cc Mon Sep 17 00:00:00 2001 From: Alex <alex.tran1502@gmail.com> Date: Thu, 22 Sep 2022 15:58:17 -0500 Subject: [PATCH] fix(server,web,mobile): Incorrectly record and show timestamp and time zone of the asset (#706) Implemented a mechanism to extract the correct time zone from the GPS coordinate if presented in the file's EXIF, and to convert the timestamp to the correct UTC time so that the time will show correctly based on the mobile/web local time zone. --- docker/.env.example | 21 -- docker/docker-compose.yml | 2 - .../asset_viewer/ui/exif_bottom_sheet.dart | 2 +- .../backup/views/backup_controller_page.dart | 2 +- .../views/failed_backup_status_page.dart | 2 +- .../lib/modules/home/ui/daily_title_text.dart | 4 +- .../modules/home/ui/monthly_title_text.dart | 2 +- .../search_result_page.provider.dart | 4 +- .../lib/shared/providers/asset.provider.dart | 8 +- .../immich/src/api-v1/user/user.controller.ts | 5 +- server/apps/immich/src/app.module.ts | 3 +- .../metadata-extraction.processor.ts | 55 ++- server/package-lock.json | 334 +++++++++++++++++- server/package.json | 2 + .../asset-viewer/detail-panel.svelte | 8 - web/src/routes/photos/+page.server.ts | 1 + 16 files changed, 392 insertions(+), 63 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index c91f12ae3d..fdeecc91aa 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -10,9 +10,6 @@ DB_DATABASE_NAME=immich # Optional Database settings: # DB_PORT=5432 - - - ################################################################################### # Redis ################################################################################### @@ -25,33 +22,24 @@ REDIS_HOSTNAME=immich_redis # REDIS_PASSWORD= # REDIS_SOCKET= - - - - ################################################################################### # Upload File Config ################################################################################### UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup - ################################################################################### # Log message level - [simple|verbose] ################################################################################### LOG_LEVEL=simple - ################################################################################### # JWT SECRET ################################################################################### JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess - - - ################################################################################### # MAPBOX #################################################################################### @@ -60,7 +48,6 @@ JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess ENABLE_MAPBOX=false MAPBOX_KEY= - #################################################################################### # WEB - Optional #################################################################################### @@ -69,11 +56,3 @@ MAPBOX_KEY= # For example PUBLIC_LOGIN_PAGE_MESSAGE="This is a demo instance of Immich.<br><br>Email: <i>demo@demo.de</i><br>Password: <i>demo</i>" PUBLIC_LOGIN_PAGE_MESSAGE= - -# For correctly display your local time zone on the web, you can set the time zone here. -# Should work fine by default value, however, in case of incorrect timezone in EXIF, this value -# should be set to the correct timezone. -# Command to get timezone: -# - Linux: curl -s http://ip-api.com/json/ | grep -oP '(?<=timezone":")(.*?)(?=")' - -# TZ=Etc/UTC \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index abbf86069c..87bcb958d5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -47,8 +47,6 @@ services: entrypoint: ["/bin/sh", "./entrypoint.sh"] env_file: - .env - environment: - - PUBLIC_TZ=${TZ} restart: always redis: diff --git a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart index 952be9448e..1136f3970d 100644 --- a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart +++ b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart @@ -81,7 +81,7 @@ class ExifBottomSheet extends ConsumerWidget { if (assetDetail.exifInfo?.dateTimeOriginal != null) Text( DateFormat('date_format'.tr()).format( - assetDetail.exifInfo!.dateTimeOriginal!, + assetDetail.exifInfo!.dateTimeOriginal!.toLocal(), ), style: TextStyle( color: Colors.grey[400], diff --git a/mobile/lib/modules/backup/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart index eec0277421..34fdc591d7 100644 --- a/mobile/lib/modules/backup/views/backup_controller_page.dart +++ b/mobile/lib/modules/backup/views/backup_controller_page.dart @@ -508,7 +508,7 @@ class BackupControllerPage extends HookConsumerWidget { DateTime.parse( backupState.currentUploadAsset.createdAt .toString(), - ), + ).toLocal(), ) ], ), diff --git a/mobile/lib/modules/backup/views/failed_backup_status_page.dart b/mobile/lib/modules/backup/views/failed_backup_status_page.dart index c78592fa2c..056af2ecf2 100644 --- a/mobile/lib/modules/backup/views/failed_backup_status_page.dart +++ b/mobile/lib/modules/backup/views/failed_backup_status_page.dart @@ -90,7 +90,7 @@ class FailedBackupStatusPage extends HookConsumerWidget { DateFormat.yMMMMd('en_US').format( DateTime.parse( errorAsset.createdAt.toString(), - ), + ).toLocal(), ), style: TextStyle( fontSize: 12, diff --git a/mobile/lib/modules/home/ui/daily_title_text.dart b/mobile/lib/modules/home/ui/daily_title_text.dart index ce12a4b793..f083cc6e6a 100644 --- a/mobile/lib/modules/home/ui/daily_title_text.dart +++ b/mobile/lib/modules/home/ui/daily_title_text.dart @@ -21,8 +21,8 @@ class DailyTitleText extends ConsumerWidget { var formatDateTemplate = currentYear == groupYear ? "daily_title_text_date".tr() : "daily_title_text_date_year".tr(); - var dateText = - DateFormat(formatDateTemplate).format(DateTime.parse(isoDate)); + var dateText = DateFormat(formatDateTemplate) + .format(DateTime.parse(isoDate).toLocal()); var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable; var selectedDateGroup = ref.watch(homePageStateProvider).selectedDateGroup; diff --git a/mobile/lib/modules/home/ui/monthly_title_text.dart b/mobile/lib/modules/home/ui/monthly_title_text.dart index a68ea69ee5..e4e626e3ae 100644 --- a/mobile/lib/modules/home/ui/monthly_title_text.dart +++ b/mobile/lib/modules/home/ui/monthly_title_text.dart @@ -12,7 +12,7 @@ class MonthlyTitleText extends StatelessWidget { @override Widget build(BuildContext context) { var monthTitleText = DateFormat("monthly_title_text_date_format".tr()) - .format(DateTime.parse(isoDate)); + .format(DateTime.parse(isoDate).toLocal()); return SliverToBoxAdapter( child: Padding( diff --git a/mobile/lib/modules/search/providers/search_result_page.provider.dart b/mobile/lib/modules/search/providers/search_result_page.provider.dart index d6984a4b89..66c7a44199 100644 --- a/mobile/lib/modules/search/providers/search_result_page.provider.dart +++ b/mobile/lib/modules/search/providers/search_result_page.provider.dart @@ -62,7 +62,7 @@ final searchResultGroupByDateTimeProvider = StateProvider((ref) { (a, b) => b.compareTo(a), ); return assets.groupListsBy( - (element) => - DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)), + (element) => DateFormat('y-MM-dd') + .format(DateTime.parse(element.createdAt).toLocal()), ); }); diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 84ddc2dc98..6329995ade 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -81,8 +81,8 @@ final assetGroupByDateTimeProvider = StateProvider((ref) { (a, b) => b.compareTo(a), ); return assets.groupListsBy( - (element) => - DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)), + (element) => DateFormat('y-MM-dd') + .format(DateTime.parse(element.createdAt).toLocal()), ); }); @@ -95,7 +95,7 @@ final assetGroupByMonthYearProvider = StateProvider((ref) { ); return assets.groupListsBy( - (element) => - DateFormat('MMMM, y').format(DateTime.parse(element.createdAt)), + (element) => DateFormat('MMMM, y') + .format(DateTime.parse(element.createdAt).toLocal()), ); }); diff --git a/server/apps/immich/src/api-v1/user/user.controller.ts b/server/apps/immich/src/api-v1/user/user.controller.ts index 4ed0b1dd49..a534154ad7 100644 --- a/server/apps/immich/src/api-v1/user/user.controller.ts +++ b/server/apps/immich/src/api-v1/user/user.controller.ts @@ -11,7 +11,6 @@ import { UseInterceptors, UploadedFile, Response, - Request, ParseBoolPipe, } from '@nestjs/common'; import { UserService } from './user.service'; @@ -22,7 +21,7 @@ import { AdminRolesGuard } from '../../middlewares/admin-role-guard.middleware'; import { UpdateUserDto } from './dto/update-user.dto'; import { FileInterceptor } from '@nestjs/platform-express'; import { profileImageUploadOption } from '../../config/profile-image-upload.config'; -import { Response as Res, Request as Req } from 'express'; +import { Response as Res } from 'express'; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; import { UserResponseDto } from './response-dto/user-response.dto'; import { UserCountResponseDto } from './response-dto/user-count-response.dto'; @@ -93,9 +92,7 @@ export class UserController { async createProfileImage( @GetAuthUser() authUser: AuthUserDto, @UploadedFile() fileInfo: Express.Multer.File, - @Request() req: Req, ): Promise<CreateProfileImageResponseDto> { - console.log(req.body, req.file); return await this.userService.createProfileImage(authUser, fileInfo); } diff --git a/server/apps/immich/src/app.module.ts b/server/apps/immich/src/app.module.ts index ae9172bc72..16f644c030 100644 --- a/server/apps/immich/src/app.module.ts +++ b/server/apps/immich/src/app.module.ts @@ -15,7 +15,6 @@ import { AppController } from './app.controller'; import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; import { DatabaseModule } from '@app/database'; -import { AppLoggerMiddleware } from './middlewares/app-logger.middleware'; @Module({ imports: [ @@ -65,7 +64,7 @@ export class AppModule implements NestModule { // eslint-disable-next-line @typescript-eslint/no-unused-vars configure(consumer: MiddlewareConsumer): void { if (process.env.NODE_ENV == 'development') { - consumer.apply(AppLoggerMiddleware).forRoutes('*'); + // consumer.apply(AppLoggerMiddleware).forRoutes('*'); } } } diff --git a/server/apps/microservices/src/processors/metadata-extraction.processor.ts b/server/apps/microservices/src/processors/metadata-extraction.processor.ts index d0e48df820..7de409e01b 100644 --- a/server/apps/microservices/src/processors/metadata-extraction.processor.ts +++ b/server/apps/microservices/src/processors/metadata-extraction.processor.ts @@ -26,6 +26,8 @@ import ffmpeg from 'fluent-ffmpeg'; import path from 'path'; import sharp from 'sharp'; import { Repository } from 'typeorm/repository/Repository'; +import { find } from 'geo-tz'; +import * as luxon from 'luxon'; @Processor(metadataExtractionQueueName) export class MetadataExtractionProcessor { @@ -75,6 +77,8 @@ export class MetadataExtractionProcessor { throw new Error(`can not parse exif data from file ${asset.originalPath}`); } + const createdAt = new Date(exifData.DateTimeOriginal || exifData.CreateDate || new Date(asset.createdAt)); + const newExif = new ExifEntity(); newExif.assetId = asset.id; newExif.make = exifData['Make'] || null; @@ -84,7 +88,7 @@ export class MetadataExtractionProcessor { newExif.exifImageWidth = exifData['ExifImageWidth'] || exifData['ImageWidth'] || null; newExif.fileSizeInByte = fileSize || null; newExif.orientation = exifData['Orientation'] || null; - newExif.dateTimeOriginal = new Date(asset.createdAt) || null; + newExif.dateTimeOriginal = createdAt; newExif.modifyDate = exifData['ModifyDate'] || null; newExif.lensModel = exifData['LensModel'] || null; newExif.fNumber = exifData['FNumber'] || null; @@ -94,7 +98,49 @@ export class MetadataExtractionProcessor { newExif.latitude = exifData['latitude'] || null; newExif.longitude = exifData['longitude'] || null; - // Reverse GeoCoding + /** + * Correctly store UTC time based on timezone + * The timestamp being extracted from EXIF is based on the timezone + * of the container. We need to correct it to UTC time based on the + * timezone of the location. + * + * The timezone of the location can be exracted from the lat/lon + * GPS coordinates. + * + * Any assets that doesn't have this information will used the + * createdAt timestamp of the asset instead. + * + * The updated/corrected timestamp will be used to update the + * createdAt timestamp in the asset table. So that the information + * is consistent across the database. + * */ + if (newExif.longitude && newExif.latitude) { + const tz = find(newExif.latitude, newExif.longitude)[0]; + const localTimeWithTimezone = createdAt.toISOString(); + + if (localTimeWithTimezone.length == 24) { + // Remove the last character + const localTimeWithoutTimezone = localTimeWithTimezone.slice(0, -1); + const correctUTCTime = luxon.DateTime.fromISO(localTimeWithoutTimezone, { zone: tz }).toUTC().toISO(); + newExif.dateTimeOriginal = new Date(correctUTCTime); + await this.assetRepository.save({ + id: asset.id, + createdAt: correctUTCTime, + }); + } + } else { + await this.assetRepository.save({ + id: asset.id, + createdAt: createdAt.toISOString(), + }); + } + + /** + * Reverse Geocoding + * + * Get the city, state or region name of the asset + * based on lat/lon GPS coordinates. + */ if (this.geocodingClient && exifData['longitude'] && exifData['latitude']) { const geoCodeInfo: MapiResponse = await this.geocodingClient .reverseGeocode({ @@ -126,7 +172,10 @@ export class MetadataExtractionProcessor { newExif.country = country || null; } - // Enrich metadata + /** + * IF the EXIF doesn't contain the width and height of the image, + * We will use Sharpjs to get the information. + */ if (!newExif.exifImageHeight || !newExif.exifImageWidth || !newExif.orientation) { const metadata = await sharp(asset.originalPath).metadata(); diff --git a/server/package-lock.json b/server/package-lock.json index 6c8d7ef267..97158ae939 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -34,8 +34,10 @@ "dotenv": "^14.2.0", "exifr": "^7.1.3", "fluent-ffmpeg": "^2.1.2", + "geo-tz": "^7.0.2", "joi": "^17.5.0", "lodash": "^4.17.21", + "luxon": "^3.0.3", "passport": "^0.6.0", "passport-jwt": "^4.0.0", "pg": "^8.7.1", @@ -2307,6 +2309,37 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "devOptional": true }, + "node_modules/@turf/boolean-point-in-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz", + "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/babel__core": { "version": "7.1.18", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", @@ -3426,6 +3459,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/array-source": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/array-source/-/array-source-0.0.4.tgz", + "integrity": "sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw==" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -4198,8 +4236,7 @@ "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/compare-versions": { "version": "4.1.3", @@ -4466,6 +4503,22 @@ "node": ">=0.8" } }, + "node_modules/cron-parser/node_modules/luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==", + "engines": { + "node": "*" + } + }, + "node_modules/cron/node_modules/luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==", + "engines": { + "node": "*" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5601,6 +5654,14 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-source": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-source/-/file-source-0.6.1.tgz", + "integrity": "sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==", + "dependencies": { + "stream-source": "0.3" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5927,6 +5988,49 @@ "node": ">=6.9.0" } }, + "node_modules/geo-tz": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-7.0.2.tgz", + "integrity": "sha512-H/loC6hQCSsns7VOrk8dIaHXtE0LzI/+239enbTrFYpWcM0DQxdBopKgnijC1yYXtiz5PDALdYAwTs1rgG2aiQ==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "geobuf": "^3.0.2", + "pbf": "^3.2.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/geobuf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/geobuf/-/geobuf-3.0.2.tgz", + "integrity": "sha512-ASgKwEAQQRnyNFHNvpd5uAwstbVYmiTW0Caw3fBb509tNTqXyAAPMyFs5NNihsLZhLxU1j/kjFhkhLWA9djuVg==", + "dependencies": { + "concat-stream": "^2.0.0", + "pbf": "^3.2.1", + "shapefile": "~0.6.6" + }, + "bin": { + "geobuf2json": "bin/geobuf2json", + "json2geobuf": "bin/json2geobuf", + "shp2geobuf": "bin/shp2geobuf" + } + }, + "node_modules/geobuf/node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -7797,11 +7901,11 @@ } }, "node_modules/luxon": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", - "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.0.3.tgz", + "integrity": "sha512-+EfHWnF+UT7GgTnq5zXg3ldnTKL2zdv7QJgsU5bjjpbH17E3qi/puMhQyJVYuCq+FRkogvB5WB6iVvUr+E4a7w==", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/macos-release": { @@ -8691,6 +8795,15 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-source": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/path-source/-/path-source-0.1.3.tgz", + "integrity": "sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==", + "dependencies": { + "array-source": "0.0", + "file-source": "0.6" + } + }, "node_modules/path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -8710,6 +8823,18 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, + "node_modules/pbf": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", + "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/pg": { "version": "8.7.1", "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", @@ -8970,6 +9095,11 @@ "node": ">= 6" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -9329,6 +9459,14 @@ "node": ">=4" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/resolve.exports": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", @@ -9599,6 +9737,23 @@ "sha.js": "bin.js" } }, + "node_modules/shapefile": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/shapefile/-/shapefile-0.6.6.tgz", + "integrity": "sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==", + "dependencies": { + "array-source": "0.0", + "commander": "2", + "path-source": "0.1", + "slice-source": "0.4", + "stream-source": "0.3", + "text-encoding": "^0.6.4" + }, + "bin": { + "dbf2json": "bin/dbf2json", + "shp2json": "bin/shp2json" + } + }, "node_modules/sharp": { "version": "0.28.3", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.28.3.tgz", @@ -9729,6 +9884,11 @@ "node": ">=8" } }, + "node_modules/slice-source": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/slice-source/-/slice-source-0.4.1.tgz", + "integrity": "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==" + }, "node_modules/socket.io": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", @@ -9905,6 +10065,11 @@ "node": ">= 0.8" } }, + "node_modules/stream-source": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", + "integrity": "sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==" + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -10273,6 +10438,12 @@ "node": ">=8" } }, + "node_modules/text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", + "deprecated": "no longer maintained" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -12927,6 +13098,28 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "devOptional": true }, + "@turf/boolean-point-in-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz", + "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==" + }, + "@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, "@types/babel__core": { "version": "7.1.18", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", @@ -13898,6 +14091,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "array-source": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/array-source/-/array-source-0.0.4.tgz", + "integrity": "sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw==" + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -14481,8 +14679,7 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "compare-versions": { "version": "4.1.3", @@ -14702,6 +14899,13 @@ "integrity": "sha512-RPeRunBCFr/WEo7WLp8Jnm45F/ziGJiHVvVQEBSDTSGu6uHW49b2FOP2O14DcXlGJRLhwE7TIoDzHHK4KmlL6g==", "requires": { "luxon": "^1.23.x" + }, + "dependencies": { + "luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==" + } } }, "cron-parser": { @@ -14710,6 +14914,13 @@ "integrity": "sha512-5sJBwDYyCp+0vU5b7POl8zLWfgV5fOHxlc45FWoWdHecGC7MQHCjx0CHivCMRnGFovghKhhyYM+Zm9DcY5qcHg==", "requires": { "luxon": "^1.28.0" + }, + "dependencies": { + "luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==" + } } }, "cross-spawn": { @@ -15578,6 +15789,14 @@ "flat-cache": "^3.0.4" } }, + "file-source": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-source/-/file-source-0.6.1.tgz", + "integrity": "sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==", + "requires": { + "stream-source": "0.3" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -15821,6 +16040,40 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, + "geo-tz": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-7.0.2.tgz", + "integrity": "sha512-H/loC6hQCSsns7VOrk8dIaHXtE0LzI/+239enbTrFYpWcM0DQxdBopKgnijC1yYXtiz5PDALdYAwTs1rgG2aiQ==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "geobuf": "^3.0.2", + "pbf": "^3.2.1" + } + }, + "geobuf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/geobuf/-/geobuf-3.0.2.tgz", + "integrity": "sha512-ASgKwEAQQRnyNFHNvpd5uAwstbVYmiTW0Caw3fBb509tNTqXyAAPMyFs5NNihsLZhLxU1j/kjFhkhLWA9djuVg==", + "requires": { + "concat-stream": "^2.0.0", + "pbf": "^3.2.1", + "shapefile": "~0.6.6" + }, + "dependencies": { + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + } + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -17250,9 +17503,9 @@ } }, "luxon": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", - "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.0.3.tgz", + "integrity": "sha512-+EfHWnF+UT7GgTnq5zXg3ldnTKL2zdv7QJgsU5bjjpbH17E3qi/puMhQyJVYuCq+FRkogvB5WB6iVvUr+E4a7w==" }, "macos-release": { "version": "2.5.0", @@ -17920,6 +18173,15 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "path-source": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/path-source/-/path-source-0.1.3.tgz", + "integrity": "sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==", + "requires": { + "array-source": "0.0", + "file-source": "0.6" + } + }, "path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -17936,6 +18198,15 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, + "pbf": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", + "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "requires": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + } + }, "pg": { "version": "8.7.1", "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", @@ -18122,6 +18393,11 @@ "sisteransi": "^1.0.5" } }, + "protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -18381,6 +18657,14 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "requires": { + "protocol-buffers-schema": "^3.3.1" + } + }, "resolve.exports": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", @@ -18574,6 +18858,19 @@ "safe-buffer": "^5.0.1" } }, + "shapefile": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/shapefile/-/shapefile-0.6.6.tgz", + "integrity": "sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==", + "requires": { + "array-source": "0.0", + "commander": "2", + "path-source": "0.1", + "slice-source": "0.4", + "stream-source": "0.3", + "text-encoding": "^0.6.4" + } + }, "sharp": { "version": "0.28.3", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.28.3.tgz", @@ -18665,6 +18962,11 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-source": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/slice-source/-/slice-source-0.4.1.tgz", + "integrity": "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==" + }, "socket.io": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", @@ -18821,6 +19123,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "stream-source": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", + "integrity": "sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==" + }, "streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -19073,6 +19380,11 @@ "minimatch": "^3.0.4" } }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/server/package.json b/server/package.json index 15de424966..4420084c3f 100644 --- a/server/package.json +++ b/server/package.json @@ -53,8 +53,10 @@ "dotenv": "^14.2.0", "exifr": "^7.1.3", "fluent-ffmpeg": "^2.1.2", + "geo-tz": "^7.0.2", "joi": "^17.5.0", "lodash": "^4.17.21", + "luxon": "^3.0.3", "passport": "^0.6.0", "passport-jwt": "^4.0.0", "pg": "^8.7.1", diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 3586a55d53..bbe942d243 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -7,7 +7,6 @@ import moment from 'moment'; import { createEventDispatcher, onMount } from 'svelte'; import { browser } from '$app/environment'; - import { env } from '$env/dynamic/public'; import { AssetResponseDto, AlbumResponseDto } from '@api'; type Leaflet = typeof import('leaflet'); @@ -31,13 +30,6 @@ if (asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null) { await drawMap(asset.exifInfo.latitude, asset.exifInfo.longitude); } - - // remove timezone when user not config PUBLIC_TZ var. Etc/UTC is used in default. - if (asset.exifInfo?.dateTimeOriginal && !env.PUBLIC_TZ) { - const dateTimeOriginal = asset.exifInfo.dateTimeOriginal; - - asset.exifInfo.dateTimeOriginal = dateTimeOriginal.slice(0, dateTimeOriginal.length - 1); - } } }); diff --git a/web/src/routes/photos/+page.server.ts b/web/src/routes/photos/+page.server.ts index 82ac30b303..afee519995 100644 --- a/web/src/routes/photos/+page.server.ts +++ b/web/src/routes/photos/+page.server.ts @@ -12,6 +12,7 @@ export const load: PageServerLoad = async ({ parent }) => { user }; } catch (e) { + console.log('Photo page load error', e); throw redirect(302, '/auth/login'); } };