2022-11-10 15:58:30 +00:00
|
|
|
module utils;
|
|
|
|
|
|
|
|
import std.typecons;
|
2022-11-11 10:40:35 +00:00
|
|
|
import ingest;
|
|
|
|
|
|
|
|
const DEFAULT_MEDIA_DIR = "/media";
|
|
|
|
const DEFAULT_OUTPUT_DIR = "raw";
|
|
|
|
|
|
|
|
public enum CliResultType {
|
|
|
|
OK,
|
|
|
|
NO_CONTENT,
|
|
|
|
MISSING_MEDIA
|
|
|
|
}
|
|
|
|
|
|
|
|
public struct CliResult {
|
|
|
|
CliResultType type;
|
|
|
|
IngestConfig config;
|
|
|
|
}
|
|
|
|
|
|
|
|
public CliResult parseArgs(string[] args) {
|
|
|
|
import std.path;
|
|
|
|
import std.file;
|
|
|
|
import std.getopt;
|
|
|
|
import std.string;
|
|
|
|
import std.stdio;
|
|
|
|
import filesizes;
|
|
|
|
CliResult result;
|
|
|
|
IngestConfig config;
|
|
|
|
config.outputDir = buildPath(getcwd(), DEFAULT_OUTPUT_DIR);
|
|
|
|
string mediaSearchDir = DEFAULT_MEDIA_DIR;
|
|
|
|
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."(config.outputDir),
|
|
|
|
&config.outputDir,
|
|
|
|
"force|f",
|
|
|
|
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."(config.dryRun),
|
|
|
|
&config.dryRun,
|
|
|
|
"bufferSize|b",
|
|
|
|
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) {
|
|
|
|
defaultGetoptPrinter("Ingestion tool for importing data from GoPro media cards.", helpInfo.options);
|
|
|
|
result.type = CliResultType.NO_CONTENT;
|
|
|
|
return result;
|
|
|
|
} else {
|
|
|
|
auto nullableGoProDir = getGoProDir(mediaSearchDir);
|
|
|
|
if (nullableGoProDir.isNull) {
|
|
|
|
writeln("Couldn't find GoPro directory.");
|
|
|
|
result.type = CliResultType.MISSING_MEDIA;
|
|
|
|
} else {
|
|
|
|
config.inputDir = nullableGoProDir.get();
|
|
|
|
writefln!"Found GoPro media at %s."(config.inputDir);
|
|
|
|
result.type = CliResultType.OK;
|
|
|
|
result.config = config;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
unittest {
|
|
|
|
assert(parseArgs(["app", "-h"]).type == CliResultType.NO_CONTENT);
|
|
|
|
// TODO: Expand tests.
|
|
|
|
}
|
|
|
|
|
|
|
|
public void printBanner() {
|
|
|
|
import std.stdio : writeln;
|
|
|
|
writeln(
|
|
|
|
"+---------------------------------+\n" ~
|
|
|
|
"| |\n" ~
|
|
|
|
"| GoPro Ingester |\n" ~
|
|
|
|
"| v1.0.0 |\n" ~
|
|
|
|
"| by Andrew Lalis |\n" ~
|
|
|
|
"| |\n" ~
|
|
|
|
"+---------------------------------+\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool endsWithAny(string s, string[] suffixes ...) {
|
|
|
|
import std.algorithm : endsWith;
|
|
|
|
foreach (string suffix; suffixes) {
|
|
|
|
if (s.endsWith(suffix)) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
unittest {
|
|
|
|
assert(endsWithAny("abc", "a", "b", "c"));
|
|
|
|
assert(!endsWithAny("abc", "d"));
|
|
|
|
assert(!endsWithAny("abc", "A", "B", "C"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copies all files from the given source directory, to the given destination
|
|
|
|
* directory. Will create the destination directory if it doesn't exist yet.
|
|
|
|
* Overwrites any files that already exist in the destination directory.
|
|
|
|
* Params:
|
|
|
|
* sourceDir = The source directory to copy from.
|
|
|
|
* destDir = The destination directory to copy to.
|
|
|
|
*/
|
|
|
|
public void copyDir(string sourceDir, string destDir) {
|
|
|
|
import std.file;
|
|
|
|
if (!isDir(sourceDir)) return;
|
|
|
|
if (exists(destDir) && !isDir(destDir)) return;
|
|
|
|
if (!exists(destDir)) mkdirRecurse(destDir);
|
|
|
|
import std.path : buildPath, baseName;
|
|
|
|
foreach (DirEntry entry; dirEntries(sourceDir, SpanMode.shallow)) {
|
|
|
|
string destPath = buildPath(destDir, baseName(entry.name));
|
|
|
|
if (entry.isDir) {
|
|
|
|
copyDir(entry.name, destPath);
|
|
|
|
} else if (entry.isFile) {
|
|
|
|
copy(entry.name, destPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-10 15:58:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Tries to find a GoPro's media directory.
|
|
|
|
* Params:
|
|
|
|
* baseDir = The base directory to start the search from.
|
|
|
|
* Returns: A nullable string that, if present, refers to the GoPro's media
|
|
|
|
* directory.
|
|
|
|
*/
|
2022-11-11 10:40:35 +00:00
|
|
|
private Nullable!string getGoProDir(string baseDir) {
|
|
|
|
import std.file;
|
|
|
|
import std.path;
|
2022-11-10 15:58:30 +00:00
|
|
|
if (!exists(baseDir) || !isDir(baseDir)) return Nullable!string.init;
|
|
|
|
foreach (dir; std.file.dirEntries(baseDir, SpanMode.breadth)) {
|
|
|
|
// We know that a GoPro contains DCIM/100GOPRO in it.
|
|
|
|
string mediaPath = buildPath(dir.name, "DCIM", "100GOPRO");
|
|
|
|
if (exists(mediaPath) && isDir(mediaPath)) {
|
|
|
|
return nullable(mediaPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Nullable!string.init;
|
2022-11-11 10:40:35 +00:00
|
|
|
}
|