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 @@ + + + + + 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 @@