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: + * + *

    + *
  1. destination: Path where files should be stored.
  2. + *
+ * + * @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. */} }