diff --git a/README.md b/README.md index b98937e..bd891aa 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,15 @@ This tool comes with a variety of options, which you can view by running `gopro- - `--force` - Forcibly overwrite existing files. - `--dryRun` - Perform a dry-run (and don't actually copy anything). - `--bufferSize` - The size of the memory buffer for copying. +- `--clean` - Delete copied items from the GoPro's media card afterwards. - `--help` - Shows help information. + +### System Compatibility +Currently, this software is known to work with the following GoPro models: +- Hero 9 + +And the following operating systems: +- Linux/Ubuntu +- MacOS + +If you've got the time and hardware, I'd greatly appreciate if you could test this software on your own device, and make a PR to add it to the list here, or an issue if something doesn't work. diff --git a/source/app.d b/source/app.d index f9d0c15..6790fec 100644 --- a/source/app.d +++ b/source/app.d @@ -11,9 +11,8 @@ import progress; import utils; import ingest; -const DEFAULT_OUTPUT_DIR = "raw"; const DEFAULT_MEDIA_DIR = "/media"; -const DEFAULT_BUFFER_SIZE = 1024 * 1024; +const DEFAULT_OUTPUT_DIR = "raw"; int main(string[] args) { writeln( @@ -25,30 +24,29 @@ int main(string[] args) { "| |\n" ~ "+---------------------------------+\n" ); - + IngestConfig config; + config.outputDir = buildPath(getcwd(), DEFAULT_OUTPUT_DIR); string mediaSearchDir = DEFAULT_MEDIA_DIR; - string outputDir = buildPath(getcwd(), DEFAULT_OUTPUT_DIR); - size_t bufferSize = DEFAULT_BUFFER_SIZE; - bool force = false; - bool dryRun = false; - auto helpInfo = getopt( args, "mediaDir|i", format!"The base directory from which to search for the GoPro media. Defaults to \"%s\"."(mediaSearchDir), &mediaSearchDir, "outputDir|o", - format!"The directory to copy data to. Defaults to \"%s\". Will create the directory if it doesn't exist yet."(outputDir), - &outputDir, + format!"The directory to copy data to. Defaults to \"%s\". Will create the directory if it doesn't exist yet."(config.outputDir), + &config.outputDir, "force|f", - format!"Whether to forcibly overwrite existing files. Defaults to %s."(force), - &force, + format!"Whether to forcibly overwrite existing files. Defaults to %s."(config.force), + &config.force, "dryRun|d", - format!"Whether to perform a dry-run (don't actually copy anything). Defaults to %s."(dryRun), - &dryRun, + format!"Whether to perform a dry-run (don't actually copy anything). Defaults to %s."(config.dryRun), + &config.dryRun, "bufferSize|b", - format!"The size of the buffer for copying files, in bytes. Defaults to %s."(formatFilesize(DEFAULT_BUFFER_SIZE)), - &bufferSize + format!"The size of the buffer for copying files, in bytes. Defaults to %s."(formatFilesize(config.bufferSize)), + &config.bufferSize, + "clean|c", + format!"Whether to remove files from the GoPro media card after copying. Defaults to %s."(config.clean), + &config.clean ); if (helpInfo.helpWanted) { @@ -61,7 +59,7 @@ int main(string[] args) { writeln("Couldn't find GoPro directory."); return 1; } - string goProDir = nullableGoProDir.get(); - writefln!"Found GoPro media at %s."(goProDir); - return copyFiles(goProDir, outputDir, bufferSize, force, dryRun); + config.inputDir = nullableGoProDir.get(); + writefln!"Found GoPro media at %s."(config.inputDir); + return copyFiles(config); } diff --git a/source/ingest.d b/source/ingest.d index 65189c8..8b0c49e 100644 --- a/source/ingest.d +++ b/source/ingest.d @@ -9,6 +9,8 @@ import std.typecons; import filesizes; import progress; +const DEFAULT_BUFFER_SIZE = 1024 * 1024; + private struct IngestData { DirEntry[] filesToCopy; @@ -21,6 +23,15 @@ private struct IngestData { } } +public struct IngestConfig { + string inputDir; + string outputDir; + size_t bufferSize = DEFAULT_BUFFER_SIZE; + bool force = false; + bool dryRun = false; + bool clean = false; +} + /** * Determines if we should copy a given file from the media card to the local * directory. @@ -36,11 +47,17 @@ private bool shouldCopyFile(DirEntry entry, string targetFile, bool force) { (!exists(targetFile) || getSize(targetFile) != entry.size || force); } -private IngestData discoverIngestData(string sourceDir, string targetDir, bool force) { +/** + * Searches for relevant files to ingest. + * Params: + * config = The ingest config. + * Returns: Data about what to ingest. + */ +private IngestData discoverIngestData(IngestConfig config) { IngestData data; - foreach (DirEntry entry; dirEntries(sourceDir, SpanMode.shallow)) { - string targetFile = buildPath(targetDir, baseName(entry.name)); - if (shouldCopyFile(entry, targetFile, force)) { + foreach (DirEntry entry; dirEntries(config.inputDir, SpanMode.shallow)) { + string targetFile = buildPath(config.outputDir, baseName(entry.name)); + if (shouldCopyFile(entry, targetFile, config.force)) { data.filesToCopy ~= entry; } } @@ -50,42 +67,38 @@ private IngestData discoverIngestData(string sourceDir, string targetDir, bool f /** * Copies files from a source to a target directory. * Params: - * sourceDir = The source directory. - * targetDir = The target directory. - * bufferSize = The buffer size to use when copying. - * force = Whether to overwrite existing files. - * dryRun = Whether to perform a dry-run. + * config = The configuration for the ingest operation. * Returns: An exit code. */ -public int copyFiles(string sourceDir, string targetDir, size_t bufferSize, bool force, bool dryRun) { - IngestData ingestData = discoverIngestData(sourceDir, targetDir, force); +public int copyFiles(IngestConfig config) { + IngestData ingestData = discoverIngestData(config); if (ingestData.fileCount == 0) { writeln("No new files to copy."); return 0; } - if (getAvailableDiskSpace(targetDir) < ingestData.totalFileSize) { + if (getAvailableDiskSpace(config.outputDir) < ingestData.totalFileSize) { writefln!"Not enough disk space to copy all files: %s available, %s needed."( - formatFilesize(getAvailableDiskSpace(targetDir)), + formatFilesize(getAvailableDiskSpace(config.outputDir)), formatFilesize(ingestData.totalFileSize) ); return 1; } - writefln!"Copying %d files (%s) to %s."(ingestData.fileCount, formatFilesize(ingestData.totalFileSize), targetDir); - if (dryRun) writeln("(Dry Run)"); + writefln!"Copying %d files (%s) to %s."(ingestData.fileCount, formatFilesize(ingestData.totalFileSize), config.outputDir); + if (config.dryRun) writeln("(Dry Run)"); - if (!exists(targetDir) && !dryRun) mkdirRecurse(targetDir); + if (!exists(config.outputDir) && !config.dryRun) mkdirRecurse(config.outputDir); Bar progressBar = new FillingSquaresBar(); progressBar.width = 80; progressBar.max = ingestData.totalFileSize; progressBar.start(); - ubyte[] buffer = new ubyte[bufferSize]; + ubyte[] buffer = new ubyte[config.bufferSize]; foreach (DirEntry entry; ingestData.filesToCopy) { string filename = baseName(entry.name); - string targetFile = buildPath(targetDir, filename); + string targetFile = buildPath(config.outputDir, filename); string verb = exists(targetFile) ? "Overwriting" : "Copying"; progressBar.message = { return std.string.format!"%s %s (%s)"(verb, filename, formatFilesize(entry.size)); }; - if (!dryRun) { + if (!config.dryRun) { File inputFile = File(entry.name, "rb"); File outputFile = File(targetFile, "wb"); foreach (ubyte[] localBuffer; inputFile.byChunk(buffer)) {