Skip to content

Commit

Permalink
refactor: handle ImageProcessor chaining in TransformedImageReader
Browse files Browse the repository at this point in the history
  • Loading branch information
shaksternano committed Sep 29, 2024
1 parent 2aa5535 commit 0cecbf0
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ interface ImageProcessor<T : Any> : SuspendCloseable {
override suspend fun close() = Unit
}

infix fun <T : Any, U : Any> ImageProcessor<T>?.then(after: ImageProcessor<U>?): ImageProcessor<*> {
return if (this == null && after != null) {
infix fun <T : Any, U : Any> ImageProcessor<T>.then(after: ImageProcessor<U>): ImageProcessor<*> {
return if (this is IdentityImageProcessor) {
after
} else if (this != null && after == null) {
} else if (after is IdentityImageProcessor) {
this
} else if (this != null && after != null) {
ChainedImageProcessor(this, after)
} else {
throw IllegalArgumentException("Both processors are null")
ChainedImageProcessor(this, after)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ private suspend fun processMedia(
outputFormat: String,
maxFileSize: Long,
): Path = useAllIgnored(imageReader, audioReader) {
println(imageReader)
var outputSize: Long
var resizeRatio = 1.0
val maxResizeAttempts = 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,6 @@ open class SimpleMediaProcessingConfig(
return imageReader.transform(processor, outputFormat)
}

override fun then(after: MediaProcessingConfig): MediaProcessingConfig {
return if (after is SimpleMediaProcessingConfig) {
val newOutputName: String = after.outputName.ifBlank {
outputName
}
SimpleMediaProcessingConfig(
processor then after.processor,
newOutputName,
)
} else {
super.then(after)
}
}

override fun toString(): String {
return "SimpleMediaProcessingConfig(processor=$processor, outputName='$outputName')"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ private class ReversedReader<T : VideoFrame<*>>(
override suspend fun reversed(): MediaReader<T> = reader

override suspend fun close() = reader.close()

override fun toString(): String {
return "ReversedReader(" +
"reader=$reader" +
", reversedFrameInfo=$reversedFrameInfo" +
")"
}
}

private data class ReversedFrameInfo(
Expand Down Expand Up @@ -126,6 +133,13 @@ open class ChangedSpeedReader<T : VideoFrame<*>>(
}

override suspend fun close() = reader.close()

override fun toString(): String {
return "ChangedSpeedReader(" +
"reader=$reader" +
", speedMultiplier=$speedMultiplier" +
")"
}
}

abstract class BaseImageReader : BaseMediaReader<ImageFrame>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,12 @@ class ConstantFrameDurationMediaReader<T : VideoFrame<*>>(
}

override suspend fun close() = reader.close()

override fun toString(): String {
return "ConstantFrameDurationMediaReader(" +
"reader=$reader" +
", frameDuration=$frameDuration" +
", frameCount=$frameCount" +
")"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class LimitedDurationMediaReader<T : VideoFrame<*>> internal constructor(
}

override suspend fun close() = reader.close()

override fun toString(): String {
return "LimitedDurationMediaReader(" +
"reader=$reader" +
", frameCount=$frameCount" +
", duration=$duration" +
")"
}
}

@Suppress("FunctionName")
Expand Down Expand Up @@ -69,7 +77,10 @@ private suspend fun readerInfo(reader: MediaReader<*>, maxDuration: Duration): R
return ReaderInfo(frameCount, totalDuration)
}

internal data class ReaderInfo(val frameCount: Int, val duration: Duration) {
data class ReaderInfo(
val frameCount: Int,
val duration: Duration
) {
constructor(reader: MediaReader<*>) : this(
reader.frameCount,
reader.duration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import io.github.shaksternano.gifcodec.AsyncExecutor
import io.github.shaksternano.gifcodec.use
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.time.Duration

interface MediaReader<T : VideoFrame<*>> : SuspendCloseable {
Expand Down Expand Up @@ -74,6 +76,8 @@ suspend fun <E, T : VideoFrame<E>> MediaReader<T>.readContent(timestamp: Duratio
fun ImageReader.transform(processor: ImageProcessor<*>, outputFormat: String): ImageReader {
return if (processor is IdentityImageProcessor) {
this
} else if (this is TransformedImageReader<*>) {
this then processor
} else {
TransformedImageReader(this, processor, outputFormat)
}
Expand All @@ -94,6 +98,7 @@ private class TransformedImageReader<T : Any>(
override val loopCount: Int = reader.loopCount

private val flow: Flow<ImageFrame> = reader.asFlow()
private val mutex: Mutex = Mutex()
private lateinit var constantData: T

override suspend fun readFrame(timestamp: Duration): ImageFrame {
Expand Down Expand Up @@ -122,13 +127,37 @@ private class TransformedImageReader<T : Any>(
}

private suspend fun initConstantData(frame: ImageFrame) {
if (!::constantData.isInitialized) {
constantData = processor.constantData(frame, flow, outputFormat)
if (::constantData.isInitialized) return
mutex.withLock {
if (!::constantData.isInitialized) {
constantData = processor.constantData(frame, flow, outputFormat)
}
}
}

infix fun then(after: ImageProcessor<*>): TransformedImageReader<*> {
return if (after is IdentityImageProcessor) {
this
} else {
TransformedImageReader(reader, processor then after, outputFormat)
}
}

override suspend fun close() = closeAll(
reader,
processor,
)

override fun toString(): String {
return "TransformedImageReader(" +
"reader=$reader" +
", processor=$processor" +
", outputFormat='$outputFormat'" +
", mutex=$mutex" +
", constantData=${
if (::constantData.isInitialized) constantData.toString()
else "[not initialized]"
}" +
")"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,12 @@ class ZippedImageReader(
ZippedImageReader(firstReader.changeSpeed(speedMultiplier), secondReader.changeSpeed(speedMultiplier))

override suspend fun close() = closeAll(firstReader, secondReader)

override fun toString(): String {
return "ZippedImageReader(" +
"firstReader=$firstReader" +
", secondReader=$secondReader" +
", firstControlling=$firstControlling" +
")"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ private const val BASE_FRAMES_PER_ROTATION = 150.0
private data class SpinConfig(
private val spinSpeed: Double,
private val backgroundColor: Color?,
private val afterProcessor: ImageProcessor<*>? = null,
) : MediaProcessingConfig {

private val rotationDuration: Duration = run {
Expand All @@ -49,23 +48,16 @@ private data class SpinConfig(
spinSpeed,
rotationDuration,
backgroundColor,
) then afterProcessor
)
return ConstantFrameDurationMediaReader(imageReader, SPIN_FRAME_DURATION, totalDuration).transform(
processor,
outputFormat,
)
}

override fun transformOutputFormat(inputFormat: String): String =
if (isStaticOnly(inputFormat)) "gif"
override fun transformOutputFormat(inputFormat: String): String {
return if (isStaticOnly(inputFormat)) "gif"
else inputFormat

override fun then(after: MediaProcessingConfig): MediaProcessingConfig {
return if (after is SimpleMediaProcessingConfig) {
copy(afterProcessor = afterProcessor then after.processor)
} else {
super.then(after)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ private data class TemplateConfig(
private val template: Template,
private val text: String?,
private val nonTextParts: Map<String, Drawable>,
private val afterProcessor: ImageProcessor<*>? = null,
) : MediaProcessingConfig {

override val outputName: String = template.resultName
Expand All @@ -41,10 +40,10 @@ private data class TemplateConfig(
if (text.isNullOrBlank()) {
val templateReader = template.getImageReader()
val zipped = ZippedImageReader(imageReader, templateReader)
val processor = TemplateImageContentProcessor(template) then afterProcessor
val processor = TemplateImageContentProcessor(template)
zipped.transform(processor, outputFormat)
} else {
val processor = TemplateTextContentProcessor(text, nonTextParts, template) then afterProcessor
val processor = TemplateTextContentProcessor(text, nonTextParts, template)
imageReader.transform(processor, outputFormat)
}

Expand All @@ -56,14 +55,6 @@ private data class TemplateConfig(
} else {
inputFormat
}

override fun then(after: MediaProcessingConfig): MediaProcessingConfig {
return if (after is SimpleMediaProcessingConfig) {
copy(afterProcessor = afterProcessor then after.processor)
} else {
super.then(after)
}
}
}

private class TemplateImageContentProcessor(
Expand Down

0 comments on commit 0cecbf0

Please sign in to comment.