!7 sclass VidFile { File file; double length = -1; S id() { ret md5FromFilePathSizeAndDate(file); } File audioFile() { ret prepareCacheProgramFile("preview-" + id() + ".wav"); } } /*c*/module AJCBatch > DynObjectTable { transient ReliableSingleThread rstScan = dm_rst(this, r scan); int volumeThresholdPercent = 15; S minSilence = "0.2", leadIn = "0.2", leadOut = "0.2"; transient double profileSamplingInterval = 0.05; // 50 ms transient int audioWindowSize = iround(profileSamplingInterval*16000); visualize { JComponent sup = super.visualize(); ret jHandleMultiFileDrop(vf> onFileDrop, withCenteredTitle("Input videos (you can drag&drop files here):", withRightAlignedButtons(sup, tableDependentButton(table, "Remove selected", r removeSelected) )); } void onFileDrop(L files) enter { print("Have file drop"); Set haveFiles = collectAsSet file(data()); addAll(map(listMinusSet(files, haveFiles), f -> nu VidFile(file := f))); } start { itemToMap = func(VidFile v) -> Map { litorderedmap( "Video" := fileName(v.file), "Folder" := dirPath(v.file), "Duration" := !fileExists(v.file) ? "File not found" : v.length < 0 ? "[calculating]" : formatMinuteAndSeconds(iceil(v.length)) ) }; rstScan.trigger(); } void scan { bool change; for (VidFile v : clonedList()) pcall { if (v.length < 0 && fileExists(v.file)) { File audio = v.audioFile(); ffmpeg_toMonoAudio_16k(v.file, audio); v.length = lengthOfWAVInSeconds(audio); set change; } } if (change) fireDataChanged(); } }