Created
August 3, 2018 22:43
Revisions
-
danielrbradley created this gist
Aug 3, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,8 @@ My method for storing and backing up photos is as follows: 1. All photos get downloaded into the `Archive` folder, in a folder for the current year, with the batch folder name starting with the date. 2. The photos get reviewed, the ones which get picked are edited, exported as JPEGs, and the edit metadata saved alongside. 3. Once editing is complete, the processed JPEGs are uploaded to wherever they're being shared (e.g. Google Photos). 4. Run the script below which copies only files which have been picked and edited into the `Best` folder using the same folder names, but not grouped into years. 5. The `Best` folder is backed up to a second local disk, and an offsite location (AWS Glacier). 6. The `Archive` folder is only backed up onto a second local replicated disk. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,135 @@ open System open System.IO let cd = __SOURCE_DIRECTORY__ let path parts = Path.Combine(parts) let archive = path [|cd; "Archive"|] let best = path [|cd; "Best"|] let isJpg filename = let ext = Path.GetExtension(filename) match ext.ToLowerInvariant() with | ".jpg" | ".jpeg" -> true | _ -> false let isMeta filename = let ext = Path.GetExtension(filename) match ext.ToLowerInvariant() with | ".xmp" | ".bib" -> true | _ -> false let isPhotoRelated filename = let ext = Path.GetExtension(filename) match ext.ToLowerInvariant() with | ".jpg" | ".jpeg" | ".nef" | ".xmp" | ".bib" -> true | _ -> false let getPhotosToMove folder = Directory.EnumerateFiles(folder, "*", SearchOption.AllDirectories) |> Seq.groupBy(Path.GetFileNameWithoutExtension) |> Seq.map(fun (key, files) -> key, (files |> Seq.filter isPhotoRelated |> Seq.toList) ) |> Seq.filter(fun (withoutExt, files) -> (not (files |> List.forall isJpg)) && (files |> List.exists isJpg) ) |> Seq.collect snd |> Seq.toArray type PhotoFolder = { FolderName: string Files: string[] } let describePhotoFolder path = { FolderName = Path.GetFileName(path) Files = getPhotosToMove path } let describeArchive () = Directory.GetDirectories(archive) |> Array.collect ( Directory.GetDirectories >> Array.map describePhotoFolder ) type FileCopy = { Source: string Destination: string } type Duplicate = { Filename: string Paths: string[] } type Operation = | NewDirectory of folderName:string * path:string * files:FileCopy[] | NewFilesInDirectory of folderName:string * newFiles:FileCopy[] * skippedFiles:FileCopy[] | NoChangeDirectory of folderName:string | UnprocessedDirectory of folderName:string | DuplicateFiles of folderName:string * duplicates:Duplicate[] let planFolderOperation destination folder = let fileCount = folder.Files.Length let duplicates = folder.Files |> Seq.groupBy Path.GetFileName |> Seq.choose (fun (key, files) -> let filesArray = Seq.toArray files if filesArray.Length > 1 then Some { Filename = key; Paths = filesArray } else None ) |> Seq.toArray if fileCount = 0 then UnprocessedDirectory folder.FolderName elif duplicates.Length > 0 then DuplicateFiles(folder.FolderName, duplicates) else let destFolder = path [| destination; folder.FolderName |] let potentialFileCopies = folder.Files |> Array.map (fun file -> { Source = file Destination = path [| destFolder; Path.GetFileName file |] } ) if not <| Directory.Exists destFolder then NewDirectory(folder.FolderName, destFolder, potentialFileCopies) else let existing, newFileCopies = potentialFileCopies |> Array.partition (fun fileCopy -> File.Exists(fileCopy.Destination)) if newFileCopies.Length = 0 then NoChangeDirectory folder.FolderName else NewFilesInDirectory(folder.FolderName, newFileCopies, existing) let planOperations destination folders = folders |> Array.map (planFolderOperation destination) let copyFiles files = for file in files do printfn "%s\t-> %s" file.Source file.Destination File.Copy (file.Source, file.Destination) let run () = let archive = describeArchive () let operations = planOperations best archive for operation in operations do match operation with | NewDirectory(folderName, path,fileCopies) -> printfn "+ %s" folderName Directory.CreateDirectory path |> ignore copyFiles fileCopies | NewFilesInDirectory(folderName, newFiles, existing) -> printfn "~ %s (%i existing files)" folderName existing.Length copyFiles newFiles | NoChangeDirectory(_) -> () | UnprocessedDirectory(folderName) -> printfn "TODO: %s" folderName | DuplicateFiles(folder, files) -> printfn "Duplicates in %s" folder // Not covered: // Folders with only .NEF files // Folders with renamed .jpg files