From e5459b68ffb3a43b24bb21ac4bb8122410a732cc Mon Sep 17 00:00:00 2001 From: Alex 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 c91f12ae3..fdeecc91a 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.

Email: demo@demo.de
Password: demo" 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 abbf86069..87bcb958d 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 952be9448..1136f3970 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 eec027742..34fdc591d 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 c78592fa2..056af2ecf 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 ce12a4b79..f083cc6e6 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 a68ea69ee..e4e626e3a 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 d6984a4b8..66c7a4419 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 84ddc2dc9..6329995ad 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 4ed0b1dd4..a534154ad 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 { - 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 ae9172bc7..16f644c03 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 d0e48df82..7de409e01 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 6c8d7ef26..97158ae93 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 15de42496..4420084c3 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 3586a55d5..bbe942d24 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 82ac30b30..afee51999 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'); } };