Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iOS Live Photo support and another format for guess_extractor #224

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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

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

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") {

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.

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

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".

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"
];

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;
}