Further split up stuff.

This commit is contained in:
Andrew Lalis 2022-11-10 17:26:24 +01:00
parent 5b24b3380b
commit 3a1587be5e
3 changed files with 60 additions and 38 deletions

View File

@ -22,4 +22,15 @@ This tool comes with a variety of options, which you can view by running `gopro-
- `--force` - Forcibly overwrite existing files. - `--force` - Forcibly overwrite existing files.
- `--dryRun` - Perform a dry-run (and don't actually copy anything). - `--dryRun` - Perform a dry-run (and don't actually copy anything).
- `--bufferSize` - The size of the memory buffer for copying. - `--bufferSize` - The size of the memory buffer for copying.
- `--clean` - Delete copied items from the GoPro's media card afterwards.
- `--help` - Shows help information. - `--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.

View File

@ -11,9 +11,8 @@ import progress;
import utils; import utils;
import ingest; import ingest;
const DEFAULT_OUTPUT_DIR = "raw";
const DEFAULT_MEDIA_DIR = "/media"; const DEFAULT_MEDIA_DIR = "/media";
const DEFAULT_BUFFER_SIZE = 1024 * 1024; const DEFAULT_OUTPUT_DIR = "raw";
int main(string[] args) { int main(string[] args) {
writeln( writeln(
@ -25,30 +24,29 @@ int main(string[] args) {
"| |\n" ~ "| |\n" ~
"+---------------------------------+\n" "+---------------------------------+\n"
); );
IngestConfig config;
config.outputDir = buildPath(getcwd(), DEFAULT_OUTPUT_DIR);
string mediaSearchDir = DEFAULT_MEDIA_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( auto helpInfo = getopt(
args, args,
"mediaDir|i", "mediaDir|i",
format!"The base directory from which to search for the GoPro media. Defaults to \"%s\"."(mediaSearchDir), format!"The base directory from which to search for the GoPro media. Defaults to \"%s\"."(mediaSearchDir),
&mediaSearchDir, &mediaSearchDir,
"outputDir|o", "outputDir|o",
format!"The directory to copy data to. Defaults to \"%s\". Will create the directory if it doesn't exist yet."(outputDir), format!"The directory to copy data to. Defaults to \"%s\". Will create the directory if it doesn't exist yet."(config.outputDir),
&outputDir, &config.outputDir,
"force|f", "force|f",
format!"Whether to forcibly overwrite existing files. Defaults to %s."(force), format!"Whether to forcibly overwrite existing files. Defaults to %s."(config.force),
&force, &config.force,
"dryRun|d", "dryRun|d",
format!"Whether to perform a dry-run (don't actually copy anything). Defaults to %s."(dryRun), format!"Whether to perform a dry-run (don't actually copy anything). Defaults to %s."(config.dryRun),
&dryRun, &config.dryRun,
"bufferSize|b", "bufferSize|b",
format!"The size of the buffer for copying files, in bytes. Defaults to %s."(formatFilesize(DEFAULT_BUFFER_SIZE)), format!"The size of the buffer for copying files, in bytes. Defaults to %s."(formatFilesize(config.bufferSize)),
&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) { if (helpInfo.helpWanted) {
@ -61,7 +59,7 @@ int main(string[] args) {
writeln("Couldn't find GoPro directory."); writeln("Couldn't find GoPro directory.");
return 1; return 1;
} }
string goProDir = nullableGoProDir.get(); config.inputDir = nullableGoProDir.get();
writefln!"Found GoPro media at %s."(goProDir); writefln!"Found GoPro media at %s."(config.inputDir);
return copyFiles(goProDir, outputDir, bufferSize, force, dryRun); return copyFiles(config);
} }

View File

@ -9,6 +9,8 @@ import std.typecons;
import filesizes; import filesizes;
import progress; import progress;
const DEFAULT_BUFFER_SIZE = 1024 * 1024;
private struct IngestData { private struct IngestData {
DirEntry[] filesToCopy; 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 * Determines if we should copy a given file from the media card to the local
* directory. * directory.
@ -36,11 +47,17 @@ private bool shouldCopyFile(DirEntry entry, string targetFile, bool force) {
(!exists(targetFile) || getSize(targetFile) != entry.size || 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; IngestData data;
foreach (DirEntry entry; dirEntries(sourceDir, SpanMode.shallow)) { foreach (DirEntry entry; dirEntries(config.inputDir, SpanMode.shallow)) {
string targetFile = buildPath(targetDir, baseName(entry.name)); string targetFile = buildPath(config.outputDir, baseName(entry.name));
if (shouldCopyFile(entry, targetFile, force)) { if (shouldCopyFile(entry, targetFile, config.force)) {
data.filesToCopy ~= entry; 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. * Copies files from a source to a target directory.
* Params: * Params:
* sourceDir = The source directory. * config = The configuration for the ingest operation.
* 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.
* Returns: An exit code. * Returns: An exit code.
*/ */
public int copyFiles(string sourceDir, string targetDir, size_t bufferSize, bool force, bool dryRun) { public int copyFiles(IngestConfig config) {
IngestData ingestData = discoverIngestData(sourceDir, targetDir, force); IngestData ingestData = discoverIngestData(config);
if (ingestData.fileCount == 0) { if (ingestData.fileCount == 0) {
writeln("No new files to copy."); writeln("No new files to copy.");
return 0; 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."( writefln!"Not enough disk space to copy all files: %s available, %s needed."(
formatFilesize(getAvailableDiskSpace(targetDir)), formatFilesize(getAvailableDiskSpace(config.outputDir)),
formatFilesize(ingestData.totalFileSize) formatFilesize(ingestData.totalFileSize)
); );
return 1; return 1;
} }
writefln!"Copying %d files (%s) to %s."(ingestData.fileCount, formatFilesize(ingestData.totalFileSize), targetDir); writefln!"Copying %d files (%s) to %s."(ingestData.fileCount, formatFilesize(ingestData.totalFileSize), config.outputDir);
if (dryRun) writeln("(Dry Run)"); 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(); Bar progressBar = new FillingSquaresBar();
progressBar.width = 80; progressBar.width = 80;
progressBar.max = ingestData.totalFileSize; progressBar.max = ingestData.totalFileSize;
progressBar.start(); progressBar.start();
ubyte[] buffer = new ubyte[bufferSize]; ubyte[] buffer = new ubyte[config.bufferSize];
foreach (DirEntry entry; ingestData.filesToCopy) { foreach (DirEntry entry; ingestData.filesToCopy) {
string filename = baseName(entry.name); string filename = baseName(entry.name);
string targetFile = buildPath(targetDir, filename); string targetFile = buildPath(config.outputDir, filename);
string verb = exists(targetFile) ? "Overwriting" : "Copying"; string verb = exists(targetFile) ? "Overwriting" : "Copying";
progressBar.message = { return std.string.format!"%s %s (%s)"(verb, filename, formatFilesize(entry.size)); }; 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 inputFile = File(entry.name, "rb");
File outputFile = File(targetFile, "wb"); File outputFile = File(targetFile, "wb");
foreach (ubyte[] localBuffer; inputFile.byChunk(buffer)) { foreach (ubyte[] localBuffer; inputFile.byChunk(buffer)) {