From 2d8480a563046a91c20094cb94e88b112f424fa7 Mon Sep 17 00:00:00 2001 From: Ralph Gasser Date: Thu, 7 Nov 2024 16:37:30 +0100 Subject: [PATCH] A single DiskResolver is now capable of creating output of different types (e.g., thumbnail and audio clip). --- .../vitrivr/engine/core/resolver/Resolver.kt | 3 +- .../engine/core/resolver/impl/DiskResolver.kt | 20 +++++----- .../engine/core/source/file/MimeType.kt | 8 +--- .../index/exporters/ThumbnailExporter.kt | 2 +- .../index/exporters/VideoPreviewExporter.kt | 3 +- .../engine/model3d/ModelPreviewExporter.kt | 39 +++++++++++-------- .../api/rest/handlers/FetchExportData.kt | 2 +- 7 files changed, 38 insertions(+), 39 deletions(-) diff --git a/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/resolver/Resolver.kt b/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/resolver/Resolver.kt index c0bb50044..3150f4939 100644 --- a/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/resolver/Resolver.kt +++ b/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/resolver/Resolver.kt @@ -13,8 +13,9 @@ interface Resolver { * Attempts to resolve the provided [RetrievableId] to a [Resolvable] using this [Resolver]. * * @param id The [RetrievableId] to resolve. + * @param suffix The suffix of the filename. * @return [Resolvable] or null, if [RetrievableId] could not be resolved. */ - fun resolve(id: RetrievableId) : Resolvable? + fun resolve(id: RetrievableId, suffix: String) : Resolvable? } \ No newline at end of file diff --git a/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/resolver/impl/DiskResolver.kt b/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/resolver/impl/DiskResolver.kt index 4343c9e7d..f5a6a527b 100644 --- a/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/resolver/impl/DiskResolver.kt +++ b/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/resolver/impl/DiskResolver.kt @@ -28,14 +28,13 @@ class DiskResolver : ResolverFactory { */ override fun newResolver(schema: Schema, parameters: Map): Resolver { val location = Paths.get(parameters["location"] ?: "./thumbnails/${schema.name}") - val mimeType = MimeType.valueOf(parameters["mimeType"] ?: "JPG") - return Instance(location, mimeType) + return Instance(location) } /** * The [Resolver] generated by this [DiskResolver]. */ - private class Instance(private val location: Path, private val mimeType: MimeType) : Resolver { + private class Instance(private val location: Path) : Resolver { init { /* Make sure, directory exists. */ if (!Files.exists(this.location)) { @@ -47,20 +46,19 @@ class DiskResolver : ResolverFactory { * Resolves the provided [RetrievableId] to a [Resolvable] using this [Resolver]. * * @param id The [RetrievableId] to resolve. + * @param suffix The suffix of the filename. * @return [Resolvable] or null, if [RetrievableId] could not be resolved. */ - override fun resolve(id: RetrievableId): Resolvable = DiskResolvable(id) - + override fun resolve(id: RetrievableId, suffix: String): Resolvable = DiskResolvable(id, suffix) /** * A [Resolvable] generated by this [DiskResolver]. */ - inner class DiskResolvable(override val retrievableId: RetrievableId) : Resolvable { - val path: Path - get() = this@Instance.location.resolve("$retrievableId.${this@Instance.mimeType.fileExtension}") - override val mimeType: MimeType - get() = this@Instance.mimeType - + inner class DiskResolvable(override val retrievableId: RetrievableId, suffix: String) : Resolvable { + val path: Path = this@Instance.location.resolve("${retrievableId}.$suffix") + override val mimeType: MimeType by lazy { + MimeType.getMimeType(this.path) ?: MimeType.UNKNOWN + } override fun exists(): Boolean = Files.exists(this.path) override fun openInputStream(): InputStream = Files.newInputStream(this.path, StandardOpenOption.READ) override fun openOutputStream(): OutputStream = Files.newOutputStream(this.path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE) diff --git a/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/source/file/MimeType.kt b/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/source/file/MimeType.kt index 5669a31bc..ece74d09f 100644 --- a/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/source/file/MimeType.kt +++ b/vitrivr-engine-core/src/main/kotlin/org/vitrivr/engine/core/source/file/MimeType.kt @@ -59,12 +59,8 @@ enum class MimeType(val fileExtension: String, val mimeType: String, val mediaTy OFF("off", "application/3d-off", MediaType.MESH), GLTF("gltf", "model/gltf+json", MediaType.MESH), - - - //Unknown type - UNKNOWN("", "", MediaType.NONE) - ; + UNKNOWN("", "", MediaType.NONE); companion object { fun getMimeType(fileName: String): MimeType? = try { @@ -80,6 +76,6 @@ enum class MimeType(val fileExtension: String, val mimeType: String, val mediaTy null } - val allValid = MimeType.values().filter { it != UNKNOWN }.toSet() + val allValid = entries.filter { it != UNKNOWN }.toSet() } } \ No newline at end of file diff --git a/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/exporters/ThumbnailExporter.kt b/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/exporters/ThumbnailExporter.kt index f82818d32..591522e02 100644 --- a/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/exporters/ThumbnailExporter.kt +++ b/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/exporters/ThumbnailExporter.kt @@ -64,7 +64,7 @@ class ThumbnailExporter : ExporterFactory { override fun toFlow(scope: CoroutineScope): Flow = this.input.toFlow(scope).onEach { retrievable -> try { - val resolvable = this.context.resolver.resolve(retrievable.id) + val resolvable = this.context.resolver.resolve(retrievable.id, ".${this.mimeType.fileExtension}") val contentIds = this.contentSources?.let { retrievable.filteredAttribute(ContentAuthorAttribute::class.java)?.getContentIds(it) diff --git a/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/exporters/VideoPreviewExporter.kt b/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/exporters/VideoPreviewExporter.kt index ee80caec2..faa993fcc 100644 --- a/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/exporters/VideoPreviewExporter.kt +++ b/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/exporters/VideoPreviewExporter.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.onEach import org.bytedeco.javacpp.PointerScope import org.bytedeco.javacv.FFmpegFrameGrabber import org.bytedeco.javacv.Java2DFrameConverter -import org.bytedeco.javacv.Java2DFrameUtils import org.vitrivr.engine.core.context.IndexContext import org.vitrivr.engine.core.model.retrievable.Retrievable import org.vitrivr.engine.core.model.retrievable.attributes.SourceAttribute @@ -78,7 +77,7 @@ class VideoPreviewExporter : ExporterFactory { override fun toFlow(scope: CoroutineScope): Flow = this.input.toFlow(scope).onEach { retrievable -> val source = retrievable.filteredAttribute(SourceAttribute::class.java)?.source ?: return@onEach if (source.type == MediaType.VIDEO) { - val resolvable = this.context.resolver.resolve(retrievable.id) + val resolvable = this.context.resolver.resolve(retrievable.id, ".${this.mimeType.fileExtension}") if (resolvable != null) { val writer = when (mimeType) { MimeType.JPEG, diff --git a/vitrivr-engine-module-m3d/src/main/kotlin/org/vitrivr/engine/model3d/ModelPreviewExporter.kt b/vitrivr-engine-module-m3d/src/main/kotlin/org/vitrivr/engine/model3d/ModelPreviewExporter.kt index 237ebca52..a3cbd207f 100644 --- a/vitrivr-engine-module-m3d/src/main/kotlin/org/vitrivr/engine/model3d/ModelPreviewExporter.kt +++ b/vitrivr-engine-module-m3d/src/main/kotlin/org/vitrivr/engine/model3d/ModelPreviewExporter.kt @@ -12,17 +12,17 @@ import org.vitrivr.engine.core.model.mesh.texturemodel.Model3d import org.vitrivr.engine.core.model.mesh.texturemodel.util.entropyoptimizer.EntopyCalculationMethod import org.vitrivr.engine.core.model.mesh.texturemodel.util.entropyoptimizer.EntropyOptimizerStrategy import org.vitrivr.engine.core.model.mesh.texturemodel.util.entropyoptimizer.OptimizerOptions +import org.vitrivr.engine.core.model.mesh.texturemodel.util.types.Vec3f import org.vitrivr.engine.core.model.retrievable.Retrievable import org.vitrivr.engine.core.model.retrievable.attributes.SourceAttribute -import org.vitrivr.engine.core.model.mesh.texturemodel.util.types.Vec3f import org.vitrivr.engine.core.operators.Operator import org.vitrivr.engine.core.operators.general.Exporter import org.vitrivr.engine.core.operators.general.ExporterFactory import org.vitrivr.engine.core.source.MediaType import org.vitrivr.engine.core.source.file.MimeType import org.vitrivr.engine.model3d.lwjglrender.render.RenderOptions -import org.vitrivr.engine.model3d.lwjglrender.window.WindowOptions import org.vitrivr.engine.model3d.lwjglrender.util.texturemodel.entroopyoptimizer.ModelEntropyOptimizer +import org.vitrivr.engine.model3d.lwjglrender.window.WindowOptions import org.vitrivr.engine.model3d.renderer.ExternalRenderer import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream @@ -45,10 +45,10 @@ private val logger: KLogger = KotlinLogging.logger {} */ class ModelPreviewExporter : ExporterFactory { companion object { - val SUPPORTED = setOf(MimeType.GLTF) + val SUPPORTED_INPUT = setOf(MimeType.GLTF) /** Set of supported output formats. */ - val OUTPUT_FORMAT = setOf("gif", "jpg") + val SUPPORTED_OUTPUT = setOf(MimeType.GIF, MimeType.JPG, MimeType.JPEG) /** * Renders a preview of the given model as a JPEG image. @@ -186,7 +186,7 @@ class ModelPreviewExporter : ExporterFactory { } } ?: MimeType.GLTF val distance = context[name, "distance"]?.toFloatOrNull() ?: 1f - val format = context[name, "format"] ?: "gif" + val format = MimeType.valueOf(context[name, "format"]?.uppercase() ?: "GIF") val views = context[name, "views"]?.toIntOrNull() ?: 30 logger.debug { @@ -202,12 +202,12 @@ class ModelPreviewExporter : ExporterFactory { private val maxResolution: Int, mimeType: MimeType, private val distance: Float, - private val format: String, + private val format: MimeType, private val views: Int ) : Exporter { init { - require(mimeType in SUPPORTED) { "ModelPreviewExporter only supports models of format GLTF." } - require(format in OUTPUT_FORMAT) { "ModelPreviewExporter only supports exporting a gif of jpg." } + require(mimeType in SUPPORTED_INPUT) { "ModelPreviewExporter only supports models of format GLTF." } + require(this.format in SUPPORTED_OUTPUT) { "ModelPreviewExporter only supports exporting a gif of jpg." } } override fun toFlow(scope: CoroutineScope): Flow { @@ -216,7 +216,7 @@ class ModelPreviewExporter : ExporterFactory { val source = retrievable.filteredAttribute(SourceAttribute::class.java)?.source ?: return@onEach if (source.type == MediaType.MESH) { - val resolvable = this.context.resolver.resolve(retrievable.id) + val resolvable = this.context.resolver.resolve(retrievable.id, ".${this.format.fileExtension}") val model = retrievable.content[0].content as Model3d if (resolvable != null) { @@ -225,15 +225,20 @@ class ModelPreviewExporter : ExporterFactory { } source.newInputStream().use { input -> - if (format == "jpg") { - val preview: BufferedImage = renderPreviewJPEG(model, renderer, this.distance) - resolvable.openOutputStream().use { output -> - ImageIO.write(preview, "jpg", output) + when (format) { + MimeType.JPG, + MimeType.JPEG -> { + val preview: BufferedImage = renderPreviewJPEG(model, renderer, this.distance) + resolvable.openOutputStream().use { output -> + ImageIO.write(preview, "jpg", output) + } + } + MimeType.GIF -> { + val frames = createFramesForGif(model, renderer, this.views, this.distance) + val gif = createGif(frames, 50) + resolvable.openOutputStream().use { output -> output.write(gif!!.toByteArray()) } } - } else { // format == "gif" - val frames = createFramesForGif(model, renderer, this.views, this.distance) - val gif = createGif(frames, 50) - resolvable.openOutputStream().use { output -> output.write(gif!!.toByteArray()) } + else -> throw IllegalArgumentException("Unsupported mime type $format") } } } diff --git a/vitrivr-engine-server/src/main/kotlin/org/vitrivr/engine/server/api/rest/handlers/FetchExportData.kt b/vitrivr-engine-server/src/main/kotlin/org/vitrivr/engine/server/api/rest/handlers/FetchExportData.kt index 1f070dfbb..c44de7ab9 100644 --- a/vitrivr-engine-server/src/main/kotlin/org/vitrivr/engine/server/api/rest/handlers/FetchExportData.kt +++ b/vitrivr-engine-server/src/main/kotlin/org/vitrivr/engine/server/api/rest/handlers/FetchExportData.kt @@ -37,7 +37,7 @@ fun fetchExportData(ctx: Context, schema: Schema) { } /* Try to resolve resolvable for retrievable ID. */ - val resolvable = schema.getExporter(exporterName)?.resolver?.resolve(retrievableId) + val resolvable = schema.getExporter(exporterName)?.resolver?.resolve(retrievableId, ".jpg") if (resolvable == null) { ctx.status(404) ctx.json(ErrorStatus("Failed to resolve data."))