Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/gpth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:gpth/media.dart';
import 'package:gpth/moving.dart';
import 'package:gpth/utils.dart';
import 'package:path/path.dart' as p;
import 'package:gpth/date_extractors/heic_video_extractor.dart';

const helpText = """GooglePhotosTakeoutHelper v$version - The Dart successor

Expand Down Expand Up @@ -125,6 +126,7 @@ void main(List<String> arguments) async {
jsonExtractor,
exifExtractor,
if (args['guess-from-name']) guessExtractor,
heicVideoExtractor,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this extractor perhaps come before the guessExtractor? If it exits quickly when conditions are not met, it should be safe.

// this is potentially *dangerous* - see:
// https://github.com/TheLastGimbus/GooglePhotosTakeoutHelper/issues/175
(f) => jsonExtractor(f, tryhard: true),
Expand Down
6 changes: 6 additions & 0 deletions lib/date_extractors/guess_extractor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ final _commonDatetimePatterns = [
r'(?<date>(20|19|18)\d{2}_(01|02|03|04|05|06|07|08|09|10|11|12)_[0-3]\d_\d{2}_\d{2}_\d{2})'),
'YYYY_MM_DD_hh_mm_ss',
],
// example: 20190620_184109.jpg

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this format of photos when going through my old photos 20190620_184109.jpg, it seems to be used by older cameras.

[
RegExp(
r'(20|19|18)\d{2}(01|02|03|04|05|06|07|08|09|10|11|12)[0-3]\d_\d{6}'),
'YYYYMMDD_hhmmss'
]
];

/// Guesses DateTime from [file]s name
Expand Down
44 changes: 44 additions & 0 deletions lib/date_extractors/heic_video_extractor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'dart:io';
import 'package:gpth/date_extractors/json_extractor.dart';
import "package:path/path.dart" as p;

/// Finds corresponding json file with info and gets 'photoTakenTime' from it
Future<DateTime?> heicVideoExtractor(File file, {bool tryhard = false}) async {
final fileNameWithoutExtension = p.basenameWithoutExtension(file.path);

if (fileNameWithoutExtension.startsWith("IMG") &&
p.extension(file.path).toLowerCase() == ".mp4") {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Photo from iPhone with Live Photo enabled when uploading to Google Photos will create three files:

  • IMG_1845.HEIC
  • IMG_1845.MP4
  • IMG_1845.HEIC.json

The .MP4 file always comes in a pair with the .HEIC file when the live photo is enabled, however, there is no JSON file for the video. We can also use the JSON file for the image to set the date for the video. This will ensure that the image and the video are stored in the same directory.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that it doesn't always start with IMG, for example, one of mine is like this:

97E2514D-F5BB-4314-B099-9A5FAE5EEA22.JPG
97E2514D-F5BB-4314-B099-9A5FAE5EEA22.JPG.json
97E2514D-F5BB-4314-B099-9A5FAE5EEA22.MP4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I too see other files. Perhaps instead try to probe whether the img file exists next to it by doing an exists check on the "possibleFileExtensions".

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to andrewchen5678's comment. I had a single file named 51750CEF-B61E-43C7-B4C9-00C19ED09E55 that fit this criteria, so the strict rule of IMG prefix needs rework

final String parentPath = file.parent.path;
// Is a valid iOS Live Photo video
return tryGetDateTimeFromPossibleFileExtensions(
parentPath, fileNameWithoutExtension);
}

return null;
}

Future<DateTime?> tryGetDateTimeFromPossibleFileExtensions(
String parentPath, String fileNameWithoutExtension,
{bool tryhard = false}) async {
final possibleFileExtensions = <String>[
"HEIC",
"heic",
"JPG",
"jpg",
"JPEG",
"jpeg"
];

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have come across a few situations when the original image in the live photo pair is saved as a .jpg or .jpeg, they seem to use both upper and lower case. I suspect this is either because the HEIC format is disabled or the photo with the live photo feature was sent over iMessage which converted the image into a .jpeg format.

Sample directory of the files:

  • IMG_3984.jpeg
  • IMG_3984.MP4
  • IMG_3984.jpeg.json

and...

  • IMG_7334.JPG
  • IMG_7334.MP4
  • IMG_7334.JPG.json


for (final fileExtension in possibleFileExtensions) {
final String possiblePhotoPath =
p.join(parentPath, "$fileNameWithoutExtension.$fileExtension");
final result =
await jsonExtractor(File(possiblePhotoPath), tryhard: tryhard);

if (result != null) {
return result;
}
}

return null;
}