From 2d21d425be008267ac709124e361c18af9794bb9 Mon Sep 17 00:00:00 2001
From: Brandon Davis
Date: Fri, 19 Mar 2021 16:36:05 -0400
Subject: [PATCH 1/4] Work in progress on adding import dialog
---
client/platform/desktop/backend/ipcService.ts | 16 +-
.../desktop/backend/native/common.spec.ts | 44 +++---
.../platform/desktop/backend/native/common.ts | 40 +++--
client/platform/desktop/constants.ts | 5 +
client/platform/desktop/frontend/api.ts | 12 +-
.../frontend/components/ImportDialog.vue | 148 ++++++++++++++++++
.../desktop/frontend/components/Recent.vue | 67 ++++++--
.../desktop/frontend/store/dataset.ts | 8 +
8 files changed, 281 insertions(+), 59 deletions(-)
create mode 100644 client/platform/desktop/frontend/components/ImportDialog.vue
diff --git a/client/platform/desktop/backend/ipcService.ts b/client/platform/desktop/backend/ipcService.ts
index 99182c109..db7c1a9d2 100644
--- a/client/platform/desktop/backend/ipcService.ts
+++ b/client/platform/desktop/backend/ipcService.ts
@@ -3,7 +3,7 @@ import http from 'http';
import { ipcMain } from 'electron';
import {
- DesktopJobUpdate, RunPipeline, RunTraining, Settings, ExportDatasetArgs,
+ DesktopJobUpdate, RunPipeline, RunTraining, Settings, ExportDatasetArgs, MediaImportPayload,
} from 'platform/desktop/constants';
import linux from './native/linux';
@@ -59,13 +59,19 @@ export default function register() {
});
ipcMain.handle('import-media', async (event, { path }: { path: string }) => {
+ const ret = await common.beginMediaImport(
+ settings.get(), path, currentPlatform.checkMedia,
+ );
+ return ret;
+ });
+
+ ipcMain.handle('finalize-import', async (event, args: MediaImportPayload) => {
const updater = (update: DesktopJobUpdate) => {
event.sender.send('job-update', update);
};
- const ret = await common.importMedia(settings.get(), path, updater, {
- checkMedia: currentPlatform.checkMedia,
- convertMedia: currentPlatform.convertMedia,
- });
+ const ret = await common.finalizeMediaImport(
+ settings.get(), args, updater, currentPlatform.convertMedia,
+ );
return ret;
});
diff --git a/client/platform/desktop/backend/native/common.spec.ts b/client/platform/desktop/backend/native/common.spec.ts
index 382e68edf..b8fa43461 100644
--- a/client/platform/desktop/backend/native/common.spec.ts
+++ b/client/platform/desktop/backend/native/common.spec.ts
@@ -308,44 +308,48 @@ describe('native.common', () => {
});
it('importMedia image sequence success', async () => {
- const meta = await common.importMedia(settings, '/home/user/data/imageSuccess', updater, { checkMedia, convertMedia });
- expect(meta.name).toBe('imageSuccess');
- expect(meta.originalImageFiles.length).toBe(2);
- expect(meta.originalVideoFile).toBe('');
- expect(meta.originalBasePath).toBe('/home/user/data/imageSuccess');
+ const payload = await common.beginMediaImport(settings, '/home/user/data/imageSuccess', checkMedia);
+ expect(payload.jsonMeta.name).toBe('imageSuccess');
+ expect(payload.jsonMeta.originalImageFiles.length).toBe(2);
+ expect(payload.jsonMeta.originalVideoFile).toBe('');
+ expect(payload.jsonMeta.originalBasePath).toBe('/home/user/data/imageSuccess');
});
it('importMedia video success', async () => {
- const meta = await common.importMedia(settings, '/home/user/data/videoSuccess/video1.mp4', updater, { checkMedia, convertMedia });
- expect(meta.name).toBe('video1');
- expect(meta.originalImageFiles.length).toBe(0);
- expect(meta.originalVideoFile).toBe('video1.mp4');
- expect(meta.originalBasePath).toBe('/home/user/data/videoSuccess');
+ const payload = await common.beginMediaImport(settings, '/home/user/data/videoSuccess/video1.mp4', checkMedia);
+ expect(payload.jsonMeta.name).toBe('video1');
+ expect(payload.jsonMeta.originalImageFiles.length).toBe(0);
+ expect(payload.jsonMeta.originalVideoFile).toBe('video1.mp4');
+ expect(payload.jsonMeta.originalBasePath).toBe('/home/user/data/videoSuccess');
});
it('importMedia empty json file success', async () => {
- const meta = await common.importMedia(settings, '/home/user/data/annotationEmptySuccess/video1.mp4', updater, { checkMedia, convertMedia });
- const tracks = await common.loadDetections(settings, meta.id);
+ const payload = await common.beginMediaImport(settings, '/home/user/data/annotationEmptySuccess/video1.mp4', checkMedia);
+ await common.finalizeMediaImport(settings, payload, updater, convertMedia);
+ const tracks = await common.loadDetections(settings, payload.jsonMeta.id);
expect(tracks).toEqual({});
});
it('importMedia various failure modes', async () => {
- await expect(common.importMedia(settings, '/fake/path', updater, { checkMedia, convertMedia }))
+ await expect(common.beginMediaImport(settings, '/fake/path', checkMedia))
.rejects.toThrow('file or directory not found');
- await expect(common.importMedia(settings, '/home/user/data/imageSuccess/foo.png', updater, { checkMedia, convertMedia }))
+ await expect(common.beginMediaImport(settings, '/home/user/data/imageSuccess/foo.png', checkMedia))
.rejects.toThrow('chose image file for video import option');
- await expect(common.importMedia(settings, '/home/user/data/videoSuccess/otherfile.txt', updater, { checkMedia, convertMedia }))
+ await expect(common.beginMediaImport(settings, '/home/user/data/videoSuccess/otherfile.txt', checkMedia))
.rejects.toThrow('unsupported MIME type');
- await expect(common.importMedia(settings, '/home/user/data/videoSuccess/nomime', updater, { checkMedia, convertMedia }))
+ await expect(common.beginMediaImport(settings, '/home/user/data/videoSuccess/nomime', checkMedia))
.rejects.toThrow('could not determine video MIME');
- await expect(common.importMedia(settings, '/home/user/data/annotationFail/video1.mp4', updater, { checkMedia, convertMedia }))
+
+ const payload = await common.beginMediaImport(settings, '/home/user/data/annotationFail/video1.mp4', checkMedia);
+ await expect(common.finalizeMediaImport(settings, payload, updater, convertMedia))
.rejects.toThrow('too many CSV');
});
it('importMedia video, start conversion', async () => {
- const meta = await common.importMedia(settings, '/home/user/data/videoSuccess/video1.avi', updater, { checkMedia, convertMedia });
- expect(meta.transcodingJobKey).toBe('jobKey');
- expect(meta.type).toBe('video');
+ const payload = await common.beginMediaImport(settings, '/home/user/data/videoSuccess/video1.avi', checkMedia);
+ await common.finalizeMediaImport(settings, payload, updater, convertMedia);
+ expect(payload.jsonMeta.transcodingJobKey).toBe('jobKey');
+ expect(payload.jsonMeta.type).toBe('video');
});
it('processing good Trained Pipeline folder', async () => {
diff --git a/client/platform/desktop/backend/native/common.ts b/client/platform/desktop/backend/native/common.ts
index 124bea038..cd1f562e7 100644
--- a/client/platform/desktop/backend/native/common.ts
+++ b/client/platform/desktop/backend/native/common.ts
@@ -17,7 +17,7 @@ import * as viameSerializers from 'platform/desktop/backend/serializers/viame';
import {
websafeImageTypes, websafeVideoTypes, otherImageTypes, otherVideoTypes,
JsonMeta, Settings, JsonMetaCurrentVersion, DesktopMetadata, DesktopJobUpdater,
- ConvertMedia, RunTraining, ExportDatasetArgs,
+ ConvertMedia, RunTraining, ExportDatasetArgs, MediaImportPayload,
} from 'platform/desktop/constants';
import { Attribute, Attributes } from 'vue-media-annotator/use/useAttributes';
import processTrackAttributes from './attributeProcessor';
@@ -501,21 +501,13 @@ async function _initializeProjectDir(settings: Settings, jsonMeta: JsonMeta): Pr
}
/**
- * importMedia locates as much information as possible
- * about a dataset using only the directory structure.
- * @param settings user settings
- * @param path path to import dir/file
- * @returns datasetId
+ * Begin a dataset import.
*/
-async function importMedia(
+async function beginMediaImport(
settings: Settings,
path: string,
- updater: DesktopJobUpdater,
- { checkMedia, convertMedia }: {
- checkMedia: (settings: Settings, path: string) => Promise;
- convertMedia: ConvertMedia;
-},
-): Promise {
+ checkMedia: (settings: Settings, path: string) => Promise,
+): Promise {
let datasetType: DatasetType;
const exists = fs.existsSync(path);
@@ -604,6 +596,25 @@ async function importMedia(
throw new Error('only video and image-sequence types are supported');
}
+ return {
+ jsonMeta,
+ mediaConvertList,
+ };
+}
+
+/**
+ * Finalize a dataset import.
+ */
+async function finalizeMediaImport(
+ settings: Settings,
+ args: MediaImportPayload,
+ updater: DesktopJobUpdater,
+ convertMedia: ConvertMedia,
+) {
+ const { jsonMeta, mediaConvertList } = args;
+ const { type: datasetType, id: dsId } = jsonMeta;
+
+ const contents = await fs.readdir(jsonMeta.originalBasePath);
const projectDirAbsPath = await _initializeProjectDir(settings, jsonMeta);
//Now we will kick off any conversions that are necessary
@@ -712,13 +723,14 @@ async function exportDataset(
export {
ProjectsFolderName,
JobsFolderName,
+ beginMediaImport,
createKwiverRunWorkingDir,
exportDataset,
+ finalizeMediaImport,
getPipelineList,
getTrainingConfigs,
getProjectDir,
getValidatedProjectDir,
- importMedia,
loadMetadata,
loadJsonMetadata,
loadJsonTracks,
diff --git a/client/platform/desktop/constants.ts b/client/platform/desktop/constants.ts
index e6f1fcf9c..90ce6e57d 100644
--- a/client/platform/desktop/constants.ts
+++ b/client/platform/desktop/constants.ts
@@ -186,6 +186,11 @@ export interface DesktopJob {
endTime?: Date;
}
+export interface MediaImportPayload {
+ jsonMeta: JsonMeta;
+ mediaConvertList: string[];
+}
+
export interface DesktopJobUpdate extends DesktopJob {
// body contents of update payload
body: string[];
diff --git a/client/platform/desktop/frontend/api.ts b/client/platform/desktop/frontend/api.ts
index 872448b18..bca29b614 100644
--- a/client/platform/desktop/frontend/api.ts
+++ b/client/platform/desktop/frontend/api.ts
@@ -11,7 +11,7 @@ import type {
import {
DesktopJob, DesktopMetadata, JsonMeta, NvidiaSmiReply,
- RunPipeline, RunTraining, fileVideoTypes, ExportDatasetArgs,
+ RunPipeline, RunTraining, fileVideoTypes, ExportDatasetArgs, MediaImportPayload,
} from 'platform/desktop/constants';
/**
@@ -72,9 +72,12 @@ async function runTraining(
return ipcRenderer.invoke('run-training', args);
}
-async function importMedia(path: string): Promise {
- const data: JsonMeta = await ipcRenderer.invoke('import-media', { path });
- return data;
+function importMedia(path: string): Promise {
+ return ipcRenderer.invoke('import-media', { path });
+}
+
+function finalizeImport(args: MediaImportPayload): Promise {
+ return ipcRenderer.invoke('finalize-import', args);
}
async function exportDataset(id: string, exclude: boolean): Promise {
@@ -143,6 +146,7 @@ export {
saveAttributes,
/* Nonstandard APIs */
exportDataset,
+ finalizeImport,
importMedia,
openFromDisk,
openLink,
diff --git a/client/platform/desktop/frontend/components/ImportDialog.vue b/client/platform/desktop/frontend/components/ImportDialog.vue
new file mode 100644
index 000000000..2e85226b2
--- /dev/null
+++ b/client/platform/desktop/frontend/components/ImportDialog.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
+ Importing new {{ argCopy.jsonMeta.type }}
+
+
+
+ Found {{ duplicates.length }} possible duplicates
+
+ {{ i + 1 }}: {{ duplicate.name }} @
{{ duplicate.id }}
+
+
Abort
+ to return to the recents list.
+
Continue
+ to ignore warning.
+
+
+ Found {{ argCopy.mediaConvertList.length }}
+ item(s) in this dataset that require transcoding. Dataset will not be
+ available until transcoding is complete.
+
+
+
+
+ | New ID |
+
+ {{ argCopy.jsonMeta.id }}
+ |
+
+
+ | Data Path |
+
+ {{ settings.dataPath }}
+ |
+
+
+ | Source |
+
+ {{ argCopy.jsonMeta.originalBasePath }}
+ |
+
+
+ | FPS |
+ {{ argCopy.jsonMeta.fps }} |
+
+
+ | Image Count |
+ {{ argCopy.jsonMeta.originalImageFiles.length }} |
+
+
+
+
+
+ Abort
+
+
+ Proceed
+
+
+
+
+
+
+
diff --git a/client/platform/desktop/frontend/components/Recent.vue b/client/platform/desktop/frontend/components/Recent.vue
index f3a063776..e19bd7b96 100644
--- a/client/platform/desktop/frontend/components/Recent.vue
+++ b/client/platform/desktop/frontend/components/Recent.vue
@@ -1,40 +1,38 @@