aboutsummaryrefslogtreecommitdiff
path: root/src/Commands
diff options
context:
space:
mode:
Diffstat (limited to 'src/Commands')
-rw-r--r--src/Commands/ExtractCommand.cs158
-rw-r--r--src/Commands/ICommand.cs7
-rw-r--r--src/Commands/PreviewCommand.cs149
-rw-r--r--src/Commands/RootCommand.cs34
4 files changed, 348 insertions, 0 deletions
diff --git a/src/Commands/ExtractCommand.cs b/src/Commands/ExtractCommand.cs
new file mode 100644
index 0000000..5872fd6
--- /dev/null
+++ b/src/Commands/ExtractCommand.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Extensions.CommandLineUtils;
+
+namespace iPhotoExtractor.Commands
+{
+ public class ExtractCommand : ICommand
+ {
+ public static void Configure(CommandLineApplication command)
+ {
+ command.HelpOption("-h|--help");
+ command.Description = "Copy photos from their original locations to a directory " +
+ "structure mirroring the event albums within iPhoto.";
+
+ var libPathArgument = command.Argument(
+ "library_dir",
+ "Path to the iPhoto library directory.");
+
+ var outputDirArgument = command.Argument(
+ "[output_dir]",
+ "Path to the directory where the extracted photos will be copied.");
+
+ command.OnExecute(() =>
+ {
+ (new ExtractCommand(libPathArgument.Value, outputDirArgument.Value)).Run();
+ return 0;
+ });
+ }
+
+ private readonly string _libraryDir;
+ private readonly string _outputDir;
+
+ public ExtractCommand(string libraryDir, string outputDir)
+ {
+ _libraryDir = libraryDir;
+ _outputDir = outputDir;
+ }
+
+ public void Run()
+ {
+ var libraryDir = String.IsNullOrWhiteSpace(_libraryDir) ?
+ Directory.GetCurrentDirectory() :
+ _libraryDir;
+
+ var outputDir = String.IsNullOrWhiteSpace(_outputDir) ?
+ Path.Combine(Directory.GetCurrentDirectory(), "Extracted Photos") :
+ _outputDir;
+
+ var dbPath = Path.Combine(libraryDir, "iPhotoMain.db");
+
+ if (!File.Exists(dbPath))
+ {
+ Console.WriteLine($"File '{dbPath}' not found.");
+ return;
+ }
+
+ if (Directory.Exists(outputDir))
+ {
+ Console.Write($"Warning: directory '{outputDir}' already exists. Continue (y/n)? ");
+ var response = Console.ReadLine().Trim().ToLower();
+
+ if (response != "y" && response != "yes")
+ return;
+ }
+
+ Directory.CreateDirectory(outputDir);
+
+ var photoStore = new PhotoStore(dbPath);
+ List<Photo> photos = photoStore.GetAllPhotos();
+
+ Dictionary<string, List<Photo>> albums = photos
+ .GroupBy(p => p.AlbumName)
+ .ToDictionary(g => g.Key, g => g.ToList());
+
+ Console.WriteLine($"Found {albums.Keys.Count} albums.");
+ var counter = 1;
+
+ foreach (string album in albums.Keys)
+ {
+ var albumPhotos = albums[album];
+ var s = albumPhotos.Count == 1 ? "" : "s";
+
+ Console.WriteLine($"[{counter}/{albums.Keys.Count}] Extracting album '{album}' " +
+ $"({albumPhotos.Count} photo{s})...");
+
+ ExtractAlbum(libraryDir, outputDir, album, albumPhotos);
+ counter++;
+ }
+
+ Console.WriteLine("Done.");
+ }
+
+ private void ExtractAlbum(
+ string libraryDir,
+ string outputDir,
+ string albumName,
+ List<Photo> photos)
+ {
+ string albumDir = String.IsNullOrWhiteSpace(albumName) ?
+ Path.Combine(outputDir, "Untitled Album") :
+ Path.Combine(outputDir, albumName);
+
+ if (!Directory.Exists(albumDir))
+ Directory.CreateDirectory(albumDir);
+
+ var hasModified = photos.Any(p => p.HasModifiedVersion());
+ string originalsDir = Path.Combine(albumDir, "Originals");
+
+ if (hasModified && !Directory.Exists(originalsDir))
+ Directory.CreateDirectory(originalsDir);
+
+ foreach (var photo in photos)
+ {
+ List<string> paths = photo.GetUniquePaths(PhotoPathType.Modified);
+ bool isModified = false;
+
+ if (paths.Any())
+ {
+ isModified = true;
+
+ foreach (string path in paths)
+ {
+ CopyPhoto(libraryDir, albumDir, path);
+ }
+ }
+
+ paths = photo.GetUniquePaths(PhotoPathType.Original);
+ string destDir = isModified ? originalsDir : albumDir;
+
+ foreach (string path in paths)
+ {
+ CopyPhoto(libraryDir, destDir, path);
+ }
+ }
+ }
+
+ private void CopyPhoto(string libraryDir, string destDir, string relativePhotoPath)
+ {
+ string fileName = Path.GetFileNameWithoutExtension(relativePhotoPath);
+ string extension = Path.GetExtension(relativePhotoPath);
+ string destFileName = $"{fileName}{extension}";
+ var suffix = 2;
+
+ while (File.Exists(Path.Combine(destDir, destFileName)))
+ {
+ destFileName = $"{fileName} ({suffix}){extension}";
+ suffix++;
+ }
+
+ string sourcePath = Path.Combine(libraryDir, relativePhotoPath);
+ string destPath = Path.Combine(destDir, destFileName);
+
+ File.Copy(sourcePath, destPath);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Commands/ICommand.cs b/src/Commands/ICommand.cs
new file mode 100644
index 0000000..8515d48
--- /dev/null
+++ b/src/Commands/ICommand.cs
@@ -0,0 +1,7 @@
+namespace iPhotoExtractor.Commands
+{
+ public interface ICommand
+ {
+ void Run();
+ }
+} \ No newline at end of file
diff --git a/src/Commands/PreviewCommand.cs b/src/Commands/PreviewCommand.cs
new file mode 100644
index 0000000..aea4afc
--- /dev/null
+++ b/src/Commands/PreviewCommand.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Extensions.CommandLineUtils;
+
+namespace iPhotoExtractor.Commands
+{
+ public class PreviewCommand : ICommand
+ {
+ private const char TEE = '\u251c';
+ private const char ELBOW = '\u2514';
+ private const char V_BAR = '\u2502';
+ private const char H_BAR = '\u2500';
+
+ public static void Configure(CommandLineApplication command)
+ {
+ command.HelpOption("-h|--help");
+ command.Description = "Display the directory structure that would result from " +
+ "running 'iPhotoExtractor extract'.";
+
+ var libPathArgument = command.Argument(
+ "library_dir",
+ "Path to the iPhoto library directory.");
+
+ var outputDirArgument = command.Argument(
+ "[output_dir]",
+ "Path to the directory where extracted photos will be copied.");
+
+ command.OnExecute(() =>
+ {
+ (new PreviewCommand(libPathArgument.Value, outputDirArgument.Value)).Run();
+ return 0;
+ });
+ }
+
+ private readonly string _libraryDir;
+ private readonly string _outputDir;
+
+ public PreviewCommand(string libraryDir, string outputDir)
+ {
+ _libraryDir = libraryDir;
+ _outputDir = outputDir;
+ }
+
+ public void Run()
+ {
+ var libraryDir = String.IsNullOrWhiteSpace(_libraryDir) ?
+ Directory.GetCurrentDirectory() :
+ _libraryDir;
+
+ var outputDir = String.IsNullOrWhiteSpace(_outputDir) ?
+ Directory.GetCurrentDirectory() :
+ _outputDir;
+
+ var dbPath = Path.Combine(libraryDir, "iPhotoMain.db");
+
+ if (!File.Exists(dbPath))
+ {
+ Console.WriteLine($"File '{dbPath}' not found.");
+ return;
+ }
+
+ var photoStore = new PhotoStore(dbPath);
+ List<Photo> photos = photoStore.GetAllPhotos();
+
+ Dictionary<string, List<Photo>> albums = photos
+ .GroupBy(p => p.AlbumName)
+ .ToDictionary(g => g.Key, g => g.ToList());
+
+ Console.WriteLine(_outputDir);
+ string album;
+
+ for (int i = 0; i < albums.Keys.Count - 1; i++)
+ {
+ album = albums.Keys.ElementAt(i);
+ DrawAlbumTree(album, albums[album], false);
+ }
+
+ album = albums.Keys.Last();
+ DrawAlbumTree(album, albums[album], true);
+ }
+
+ private void DrawAlbumTree(string albumName, List<Photo> photos, bool isLastChild)
+ {
+ var modified = photos
+ .Where(p => p.HasModifiedVersion() && p.HasOriginalVersion())
+ .ToList();
+
+ var hasModified = modified.Any();
+ string prefix = isLastChild ? " " : $"{V_BAR} ";
+
+ Console.WriteLine($"{GetCurrLevelTreeString(isLastChild)}{albumName}");
+ DrawPhotos(photos, prefix, hasModified);
+
+ if (!hasModified)
+ return;
+
+ Console.WriteLine($"{prefix}{GetCurrLevelTreeString(true)}Originals");
+ DrawPhotos(modified, prefix + " ", false);
+ }
+
+ private void DrawPhotos(List<Photo> photos, string prefix, bool hasModified)
+ {
+ for (int i = 0; i < photos.Count; i++)
+ {
+ var photo = photos.ElementAt(i);
+ var isLastChild = i == photos.Count - 1 && !hasModified;
+
+ List<string> paths = new List<string>();
+
+ if (hasModified)
+ paths.AddRange(photo.GetUniquePaths(PhotoPathType.Modified));
+
+ if (!paths.Any())
+ paths.AddRange(photo.GetUniquePaths(PhotoPathType.Original));
+
+ if (!paths.Any())
+ paths.Add("");
+
+ for (int j = 0; j < paths.Count; j++)
+ {
+ var path = paths.ElementAt(j);
+
+ if (j < paths.Count - 1)
+ DrawPhoto(path, prefix, false);
+ else
+ DrawPhoto(path, prefix, isLastChild);
+ }
+ }
+ }
+
+ private void DrawPhoto(string relativePath, string prefix, bool isLastChild)
+ {
+ string fileName = Path.GetFileName(relativePath);
+ string currLevel = GetCurrLevelTreeString(isLastChild);
+
+ Console.WriteLine($"{prefix}{currLevel}{fileName} ({relativePath})");
+ }
+
+ private string GetCurrLevelTreeString(bool isLastChild)
+ {
+ if (isLastChild)
+ return $"{ELBOW}{H_BAR}{H_BAR} ";
+ else
+ return $"{TEE}{H_BAR}{H_BAR} ";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Commands/RootCommand.cs b/src/Commands/RootCommand.cs
new file mode 100644
index 0000000..1abab5a
--- /dev/null
+++ b/src/Commands/RootCommand.cs
@@ -0,0 +1,34 @@
+using Microsoft.Extensions.CommandLineUtils;
+
+namespace iPhotoExtractor.Commands
+{
+ public class RootCommand : ICommand
+ {
+ public static void Configure(CommandLineApplication app)
+ {
+ app.Name = "iPhotoExtractor";
+ app.HelpOption("-h|--help");
+
+ app.Command("preview", PreviewCommand.Configure);
+ app.Command("extract", ExtractCommand.Configure);
+
+ app.OnExecute(() =>
+ {
+ (new RootCommand(app)).Run();
+ return 0;
+ });
+ }
+
+ private readonly CommandLineApplication _app;
+
+ public RootCommand(CommandLineApplication app)
+ {
+ _app = app;
+ }
+
+ public void Run()
+ {
+ _app.ShowHelp();
+ }
+ }
+} \ No newline at end of file