diff --git a/cineast-core/src/main/java/org/vitrivr/cineast/core/features/abstracts/AbstractSegmentExporter.java b/cineast-core/src/main/java/org/vitrivr/cineast/core/features/abstracts/AbstractSegmentExporter.java
new file mode 100644
index 000000000..577c0e652
--- /dev/null
+++ b/cineast-core/src/main/java/org/vitrivr/cineast/core/features/abstracts/AbstractSegmentExporter.java
@@ -0,0 +1,149 @@
+package org.vitrivr.cineast.core.features.abstracts;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.vitrivr.cineast.core.data.segments.SegmentContainer;
+import org.vitrivr.cineast.core.db.PersistencyWriterSupplier;
+import org.vitrivr.cineast.core.db.setup.EntityCreator;
+import org.vitrivr.cineast.core.features.extractor.Extractor;
+import org.vitrivr.cineast.core.util.LogHelper;
+
+abstract public class AbstractSegmentExporter implements Extractor {
+
+ protected static final Logger LOGGER = LogManager.getLogger();
+
+ private static final String PROPERTY_NAME_DESTINATION = "destination";
+
+ /**
+ * Destination path for the audio-segment.
+ */
+ private final Path destination;
+
+ /**
+ * Returns the file extension for the exported data.
+ *
+ * @return String containing the file extension without the dot.
+ */
+ protected abstract String getFileExtension();
+
+ /**
+ * Returns the data-url prefix for the exported data.
+ *
+ * @return String containing the data-url prefix.
+ */
+ protected abstract String getDataUrlPrefix();
+
+ /**
+ * Default constructor
+ */
+ public AbstractSegmentExporter() {
+ this(new HashMap<>());
+ }
+
+ /**
+ * Default constructor. A segment exporter can be configured via named properties in the provided HashMap.
+ *
+ * Supported parameters:
+ *
+ *
+ * - destination: Path where files should be stored.
+ *
+ *
+ * @param properties HashMap containing named properties
+ */
+ public AbstractSegmentExporter(HashMap properties) {
+ this.destination = Path.of(properties.getOrDefault(PROPERTY_NAME_DESTINATION, "./export"));
+ }
+
+
+ /**
+ * Exports the data of a segment container to an output stream.
+ *
+ * @param sc SegmentContainer to export.
+ * @param stream OutputStream to write to.
+ */
+ abstract public void exportToStream(SegmentContainer sc, OutputStream stream);
+
+ /**
+ * Determines whether a segment can be exported or not, i.e. if there is enough data to create an export. For example, a segment without audio cannot be exported as audio. Does not check if the segment is already exported.
+ *
+ * @param sc The segment to be checked.
+ */
+ abstract public boolean isExportable(SegmentContainer sc);
+
+ @Override
+ public void processSegment(SegmentContainer shot) {
+ if (!isExportable(shot)) {
+ return;
+ }
+ try {
+ /* Prepare folder and OutputStream. */
+ Path folder = this.destination.resolve(shot.getSuperId());
+ if (!folder.toFile().exists()) {
+ folder.toFile().mkdirs();
+ }
+ Path path = folder.resolve(shot.getId() +'.' + this.getFileExtension());
+ OutputStream os = Files.newOutputStream(path);
+
+ exportToStream(shot, os);
+
+ /* Close OutputStream. */
+ os.close();
+ } catch (Exception e) {
+ LOGGER.error("Could not write data to file for segment {} due to {}.", shot.getId(), LogHelper.getStackTrace(e));
+ }
+ }
+
+ /**
+ * Exports a segment to a data-url.
+ *
+ * @param shot The segment to be exported.
+ * @return A String containing the data-url.
+ */
+ public String exportToDataUrl(SegmentContainer shot) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ exportToStream(shot, baos);
+ byte[] bytes = baos.toByteArray();
+ baos.close();
+ String base64 = Base64.getEncoder().encodeToString(bytes);
+ return this.getDataUrlPrefix() + base64;
+ }
+
+ /**
+ * Exports a segment to a byte array.
+ *
+ * @param shot The segment to be exported.
+ * @return A byte array containing the exported data.
+ */
+ public byte[] exportToBinary(SegmentContainer shot) {
+ // create a ByteArrayOutputStream
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ // call the exportToStream method with the ByteArrayOutputStream
+ exportToStream(shot, baos);
+
+ // convert the ByteArrayOutputStream's data to a byte array
+ return baos.toByteArray();
+ }
+
+ @Override
+ public void init(PersistencyWriterSupplier phandlerSupply) { /* Nothing to init. */ }
+
+ @Override
+ public void finish() { /* Nothing to finish. */}
+
+ @Override
+ public void initalizePersistentLayer(Supplier supply) { /* Nothing to init. */ }
+
+ @Override
+ public void dropPersistentLayer(Supplier supply) { /* Nothing to drop. */}
+
+}
diff --git a/cineast-core/src/main/java/org/vitrivr/cineast/core/features/exporter/AudioSegmentExporter.java b/cineast-core/src/main/java/org/vitrivr/cineast/core/features/exporter/AudioSegmentExporter.java
index 9c08ed0dd..e89782956 100644
--- a/cineast-core/src/main/java/org/vitrivr/cineast/core/features/exporter/AudioSegmentExporter.java
+++ b/cineast-core/src/main/java/org/vitrivr/cineast/core/features/exporter/AudioSegmentExporter.java
@@ -1,41 +1,30 @@
package org.vitrivr.cineast.core.features.exporter;
-import static java.nio.file.StandardOpenOption.CREATE;
-import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
-
import java.io.IOException;
import java.io.OutputStream;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.HashMap;
-import java.util.function.Supplier;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
import org.vitrivr.cineast.core.data.frames.AudioFrame;
import org.vitrivr.cineast.core.data.segments.SegmentContainer;
-import org.vitrivr.cineast.core.db.PersistencyWriterSupplier;
-import org.vitrivr.cineast.core.db.setup.EntityCreator;
-import org.vitrivr.cineast.core.features.extractor.Extractor;
+import org.vitrivr.cineast.core.features.abstracts.AbstractSegmentExporter;
import org.vitrivr.cineast.core.util.LogHelper;
/**
* Exports the audio in a given segment as mono WAV file.
*/
-public class AudioSegmentExporter implements Extractor {
-
-
- private static final Logger LOGGER = LogManager.getLogger();
+public class AudioSegmentExporter extends AbstractSegmentExporter {
- private static final String PROPERTY_NAME_DESTINATION = "destination";
+ @Override
+ protected String getFileExtension() {
+ return "wav";
+ }
- /**
- * Destination path for the audio-segment.
- */
- private Path destination;
+ @Override
+ protected String getDataUrlPrefix() {
+ return "data:audio/wav;base64,";
+ }
/**
* Default constructor
@@ -56,21 +45,19 @@ public AudioSegmentExporter() {
* @param properties HashMap containing named properties
*/
public AudioSegmentExporter(HashMap properties) {
- this.destination = Paths.get(properties.getOrDefault(PROPERTY_NAME_DESTINATION, "."));
+ super(properties);
}
+
+
/**
* Processes a SegmentContainer: Extract audio-data and writes to a WAVE file.
*
* @param shot SegmentContainer to process.
*/
@Override
- public void processSegment(SegmentContainer shot) {
+ public void exportToStream(SegmentContainer shot, OutputStream stream) {
try {
- /* Prepare folder and OutputStream. */
- Path directory = this.destination.resolve(shot.getSuperId());
- Files.createDirectories(directory);
- OutputStream stream = Files.newOutputStream(directory.resolve(shot.getId() + ".wav"), CREATE, TRUNCATE_EXISTING);
/* Extract mean samples and perpare byte buffer. */
short[] data = shot.getMeanSamplesAsShort();
@@ -85,12 +72,16 @@ public void processSegment(SegmentContainer shot) {
}
stream.write(buffer.array());
- stream.close();
} catch (IOException | BufferOverflowException e) {
LOGGER.fatal("Could not export audio segment {} due to a serious IO error ({}).", shot.getId(), LogHelper.getStackTrace(e));
}
}
+ @Override
+ public boolean isExportable(SegmentContainer sc) {
+ return sc.getNumberOfSamples() > 0;
+ }
+
/**
* Writes the WAV header to the ByteBuffer (1 channel).
*
@@ -123,16 +114,4 @@ private void writeWaveHeader(ByteBuffer buffer, float samplingrate, short channe
buffer.putInt(subChunk2Length); /* Length of the data chunk. */
}
-
- @Override
- public void init(PersistencyWriterSupplier phandlerSupply) { /* Nothing to init. */ }
-
- @Override
- public void finish() { /* Nothing to finish. */}
-
- @Override
- public void initalizePersistentLayer(Supplier supply) { /* Nothing to init. */ }
-
- @Override
- public void dropPersistentLayer(Supplier supply) { /* Nothing to drop. */}
}
diff --git a/cineast-core/src/main/java/org/vitrivr/cineast/core/features/exporter/AudioSpectogramExporter.java b/cineast-core/src/main/java/org/vitrivr/cineast/core/features/exporter/AudioSpectogramExporter.java
index 7185576af..59e72b79a 100644
--- a/cineast-core/src/main/java/org/vitrivr/cineast/core/features/exporter/AudioSpectogramExporter.java
+++ b/cineast-core/src/main/java/org/vitrivr/cineast/core/features/exporter/AudioSpectogramExporter.java
@@ -2,19 +2,14 @@
import java.awt.image.BufferedImage;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
-import java.util.function.Supplier;
import javax.imageio.ImageIO;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.vitrivr.cineast.core.data.segments.SegmentContainer;
-import org.vitrivr.cineast.core.db.PersistencyWriterSupplier;
-import org.vitrivr.cineast.core.db.setup.EntityCreator;
-import org.vitrivr.cineast.core.features.extractor.Extractor;
+import org.vitrivr.cineast.core.features.abstracts.AbstractSegmentExporter;
import org.vitrivr.cineast.core.util.LogHelper;
import org.vitrivr.cineast.core.util.dsp.fft.STFT;
import org.vitrivr.cineast.core.util.dsp.fft.Spectrum;
@@ -24,7 +19,7 @@
/**
* Visualizes and exporst the power spectogram (time vs. frequency vs. power) of the provided AudioSegment.
*/
-public class AudioSpectogramExporter implements Extractor {
+public class AudioSpectogramExporter extends AbstractSegmentExporter {
private static final Logger LOGGER = LogManager.getLogger();
@@ -32,15 +27,10 @@ public class AudioSpectogramExporter implements Extractor {
/**
* Property names that can be used in the configuration hash map.
*/
- private static final String PROPERTY_NAME_DESTINATION = "destination";
private static final String PROPERTY_NAME_WIDTH = "width";
private static final String PROPERTY_NAME_HEIGHT = "height";
private static final String PROPERTY_NAME_FORMAT = "format";
- /**
- * Destination path; can be set in the AudioWaveformExporter properties.
- */
- private final Path destination;
/**
* Width of the resulting image in pixels.
@@ -53,10 +43,20 @@ public class AudioSpectogramExporter implements Extractor {
private final int height;
/**
- * Output format for thumbnails. Defaults to PNG.
+ * Output format for thumbnails. Defaults to JPG.
*/
private final String format;
+ @Override
+ protected String getFileExtension() {
+ return this.format.toLowerCase();
+ }
+
+ @Override
+ protected String getDataUrlPrefix() {
+ return "data:image/" + format.toLowerCase() + ";base64,";
+ }
+
/**
* Default constructor
*/
@@ -78,21 +78,15 @@ public AudioSpectogramExporter() {
* @param properties HashMap containing named properties
*/
public AudioSpectogramExporter(HashMap properties) {
- this.destination = Paths.get(properties.getOrDefault(PROPERTY_NAME_DESTINATION, "."));
+ super(properties);
this.width = Integer.parseInt(properties.getOrDefault(PROPERTY_NAME_WIDTH, "800"));
this.height = Integer.parseInt(properties.getOrDefault(PROPERTY_NAME_HEIGHT, "600"));
this.format = properties.getOrDefault(PROPERTY_NAME_FORMAT, "JPG");
}
@Override
- public void processSegment(SegmentContainer shot) {
- /* If shot has no samples, this step is skipped. */
- if (shot.getNumberOfSamples() == 0) {
- return;
- }
-
+ public void exportToStream(SegmentContainer shot, OutputStream stream) {
/* Prepare STFT and Spectrum for the segment. */
- final Path directory = this.destination.resolve(shot.getSuperId());
final STFT stft = shot.getSTFT(2048, 512, new HanningWindow());
final List spectrums = stft.getPowerSpectrum();
@@ -100,8 +94,7 @@ public void processSegment(SegmentContainer shot) {
try {
BufferedImage image = AudioSignalVisualizer.visualizeSpectogram(spectrums, this.width, this.height);
if (image != null) {
- Files.createDirectories(directory);
- ImageIO.write(image, format, directory.resolve(shot.getId() + "." + format.toLowerCase()).toFile());
+ ImageIO.write(image, format, stream);
} else {
LOGGER.warn("Spectrum could not be visualized!");
}
@@ -111,14 +104,8 @@ public void processSegment(SegmentContainer shot) {
}
@Override
- public void init(PersistencyWriterSupplier phandlerSupplier) { /* Noting to init. */}
-
- @Override
- public void finish() { /* Nothing to finish. */}
-
- @Override
- public void initalizePersistentLayer(Supplier supply) {/* Nothing to initialize. */}
+ public boolean isExportable(SegmentContainer sc) {
+ return sc.getNumberOfSamples() > 0;
+ }
- @Override
- public void dropPersistentLayer(Supplier supply) {/* Nothing to drop. */}
}