From b65b2b68c232d377c924fed77606c19e43b09eab Mon Sep 17 00:00:00 2001 From: Wacheee Date: Wed, 26 Feb 2025 20:06:02 -0300 Subject: [PATCH 1/2] allow raw formats and pixel motion photos to move --- lib/utils.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/utils.dart b/lib/utils.dart index 41ee7e4d..b98ea5ff 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -30,12 +30,14 @@ extension X on Iterable { /// Easy extension allowing you to filter for files that are photo or video Iterable wherePhotoVideo() => whereType().where((e) { final mime = lookupMimeType(e.path) ?? ""; + final fileExtension = p.extension(e.path).toLowerCase(); return mime.startsWith('image/') || mime.startsWith('video/') || // https://github.com/TheLastGimbus/GooglePhotosTakeoutHelper/issues/223 // https://github.com/dart-lang/mime/issues/102 // 🙃🙃 - mime == 'model/vnd.mts'; + mime == 'model/vnd.mts'|| + _moreExtensions.contains(fileExtension); }); } @@ -43,15 +45,20 @@ extension Y on Stream { /// Easy extension allowing you to filter for files that are photo or video Stream wherePhotoVideo() => whereType().where((e) { final mime = lookupMimeType(e.path) ?? ""; + final fileExtension = p.extension(e.path).toLowerCase(); return mime.startsWith('image/') || mime.startsWith('video/') || // https://github.com/TheLastGimbus/GooglePhotosTakeoutHelper/issues/223 // https://github.com/dart-lang/mime/issues/102 // 🙃🙃 - mime == 'model/vnd.mts'; + mime == 'model/vnd.mts'|| + _moreExtensions.contains(fileExtension); }); } +//Support raw formats (dng, cr2) and Pixel motion photos (mp, mv) +const _moreExtensions = ['.mp', '.mv', '.dng', '.cr2']; + extension Util on Stream { Stream whereType() => where((e) => e is T).cast(); } From 583cc555d9064cca2bc5a2a0cacf015a67c99b8d Mon Sep 17 00:00:00 2001 From: Wacheee Date: Wed, 26 Feb 2025 23:39:21 -0300 Subject: [PATCH 2/2] added option to change .MV and .MP extensions to .mp4 --- bin/gpth.dart | 16 +++++++++++++++- lib/interactive.dart | 22 ++++++++++++++++++++++ lib/utils.dart | 29 +++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/bin/gpth.dart b/bin/gpth.dart index 0e2f2e0f..92edcf74 100644 --- a/bin/gpth.dart +++ b/bin/gpth.dart @@ -61,7 +61,10 @@ void main(List arguments) async { help: "Copy files instead of moving them.\n" "This is usually slower, and uses extra space, " "but doesn't break your input folder", - ); + ) + ..addFlag( + 'transform-pixel-mp', + help: 'Transform Pixel .MP or .MV extensions to ".mp4"'); final args = {}; try { final res = parser.parse(arguments); @@ -109,6 +112,8 @@ void main(List arguments) async { print(''); args['albums'] = await interactive.askAlbums(); print(''); + args['transform-pixel-mp'] = await interactive.askTransformPixelMP(); + print(''); // @Deprecated('Interactive unzipping is suspended for now!') // // calculate approx space required for everything @@ -355,6 +360,15 @@ void main(List arguments) async { print('Finding albums (this may take some time, dont worry :) ...'); findAlbums(media); + // Change Pixel Motion Photos extension to .mp4 using a list of Medias. + // This is done after the dates of files have been defined, and before + // the files are moved to the output folder, to avoid shortcuts/symlinks problems + if (args['transform-pixel-mp']) { + print('Changing .MP or .MV extensions to .mp4 (this may take some time) ...'); + await changeMPExtensions(media, ".mp4"); + } + print(''); + /// ####################### /// ##### Copy/move files to actual output folder ##### diff --git a/lib/interactive.dart b/lib/interactive.dart index 97198790..5113ee10 100644 --- a/lib/interactive.dart +++ b/lib/interactive.dart @@ -230,6 +230,28 @@ Future askForCleanOutput() async { } } +Future askTransformPixelMP() async { + print('Pixel Motion Pictures are saved with the .MP or .MV ' + 'extensions. Do you want to change them to .mp4 ' + 'for better compatibility?'); + print('[1] (default) - no, keep original extension'); + print('[2] - yes, change extension to .mp4'); + print('(Type 1 or 2 or press enter for default):'); + final answer = await askForInt(); + switch (answer) { + case '1': + case '': + print('Okay, will keep original extension'); + return false; + case '2': + print('Okay, will change to mp4!'); + return true; + default: + error('Invalid answer - try again'); + return askTransformPixelMP(); + } +} + /// Checks free space on disk and notifies user accordingly @Deprecated('Interactive unzipping is suspended for now!') Future freeSpaceNotice(int required, Directory dir) async { diff --git a/lib/utils.dart b/lib/utils.dart index b98ea5ff..1b730c9a 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -5,6 +5,7 @@ import 'package:gpth/interactive.dart' as interactive; import 'package:mime/mime.dart'; import 'package:path/path.dart' as p; import 'package:proper_filesize/proper_filesize.dart'; +import 'package:unorm_dart/unorm_dart.dart' as unorm; import 'media.dart'; @@ -140,3 +141,31 @@ extension Z on String { return replaceRange(lastIndex, lastIndex + from.length, to); } } + +Future changeMPExtensions(List allMedias, String finalExtension) async { + int renamedCount = 0; + for (final m in allMedias) { + for (final entry in m.files.entries) { + final file = entry.value; + final ext = p.extension(file.path).toLowerCase(); + if (ext == '.mv' || ext == '.mp') { + final originalName = p.basenameWithoutExtension(file.path); + final normalizedName = unorm.nfc(originalName); + + final newName = '$normalizedName$finalExtension'; + if (newName != normalizedName) { + final newPath = p.join(p.dirname(file.path), newName); + // Rename file and update reference in map + try { + final newFile = await file.rename(newPath); + m.files[entry.key] = newFile; + renamedCount++; + } on FileSystemException catch (e) { + print('[Error] Error changing extension to $finalExtension -> ${file.path}: ${e.message}'); + } + } + } + } + } + print('Successfully changed Pixel Motion Photos files extensions (change it to $finalExtension): $renamedCount'); +}