Version + more tests
This commit is contained in:
parent
6bc2ade681
commit
295349e78b
@ -39,7 +39,7 @@ dependencies {
|
||||
|
||||
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT")
|
||||
implementation("no.iktdev.library:subtitle:1.8.1-SNAPSHOT")
|
||||
implementation("no.iktdev:eventi:1.0-rc15")
|
||||
implementation("no.iktdev:eventi:1.0-rc17")
|
||||
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
||||
@ -53,6 +53,7 @@ dependencies {
|
||||
testImplementation("io.mockk:mockk:1.12.0")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation(project(":shared:common", configuration = "testArtifacts"))
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package no.iktdev.mediaprocessing.converter
|
||||
|
||||
import no.iktdev.library.subtitle.reader.BaseReader
|
||||
import java.io.File
|
||||
|
||||
interface ConverterEnvironment {
|
||||
fun canRead(file: File): Boolean
|
||||
fun getReader(file: File): BaseReader?
|
||||
fun createExporter(input: File, outputDir: File, name: String): Exporter
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package no.iktdev.mediaprocessing.converter
|
||||
|
||||
import no.iktdev.library.subtitle.classes.Dialog
|
||||
import no.iktdev.library.subtitle.export.Export
|
||||
|
||||
class ExportAdapter(
|
||||
private val export: Export
|
||||
) : Exporter {
|
||||
|
||||
override fun write(dialogs: List<Dialog>) = export.write(dialogs)
|
||||
override fun writeSrt(dialogs: List<Dialog>) = export.writeSrt(dialogs)
|
||||
override fun writeSmi(dialogs: List<Dialog>) = export.writeSmi(dialogs)
|
||||
override fun writeVtt(dialogs: List<Dialog>) = export.writeVtt(dialogs)
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package no.iktdev.mediaprocessing.converter
|
||||
|
||||
import no.iktdev.library.subtitle.classes.Dialog
|
||||
import java.io.File
|
||||
|
||||
interface Exporter {
|
||||
fun write(dialogs: List<Dialog>): MutableList<File>
|
||||
fun writeSrt(dialogs: List<Dialog>): File
|
||||
fun writeSmi(dialogs: List<Dialog>): File
|
||||
fun writeVtt(dialogs: List<Dialog>): File
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package no.iktdev.mediaprocessing.converter.convert
|
||||
|
||||
import no.iktdev.library.subtitle.reader.BaseReader
|
||||
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
|
||||
import java.io.File
|
||||
|
||||
abstract class Converter(val env: ConverterEnvironment, val listener: ConvertListener) {
|
||||
var writtenUris: List<String>? = null
|
||||
|
||||
abstract fun getSubtitleReader(useFile: File): BaseReader?
|
||||
abstract suspend fun convert(data: ConvertTask.Data)
|
||||
|
||||
class FileIsNullOrEmpty(override val message: String? = "File read is null or empty"): RuntimeException()
|
||||
class FileUnavailableException(override val message: String): RuntimeException()
|
||||
|
||||
abstract fun getResult(): List<String>
|
||||
}
|
||||
@ -4,51 +4,43 @@ import no.iktdev.library.subtitle.Configuration
|
||||
import no.iktdev.library.subtitle.Syncro
|
||||
import no.iktdev.library.subtitle.classes.Dialog
|
||||
import no.iktdev.library.subtitle.classes.DialogType
|
||||
import no.iktdev.library.subtitle.export.Export
|
||||
import no.iktdev.library.subtitle.reader.BaseReader
|
||||
import no.iktdev.library.subtitle.reader.Reader
|
||||
import no.iktdev.mediaprocessing.converter.ConverterEnv
|
||||
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
|
||||
import no.iktdev.mediaprocessing.shared.common.model.SubtitleFormat
|
||||
import java.io.File
|
||||
|
||||
class Converter2(val data: ConvertTask.Data,
|
||||
private val listener: ConvertListener) {
|
||||
class Converter2(
|
||||
env: ConverterEnvironment,
|
||||
listener: ConvertListener
|
||||
): Converter(env = env, listener = listener) {
|
||||
|
||||
@Throws(FileUnavailableException::class)
|
||||
private fun getReader(): BaseReader? {
|
||||
val file = File(data.inputFile)
|
||||
if (!file.canRead())
|
||||
override fun getSubtitleReader(useFile: File): BaseReader? {
|
||||
if (!env.canRead(useFile)) {
|
||||
throw FileUnavailableException("Can't open file for reading..")
|
||||
return Reader(file).getSubtitleReader()
|
||||
}
|
||||
return env.getReader(useFile)
|
||||
}
|
||||
|
||||
private fun syncDialogs(input: List<Dialog>): List<Dialog> {
|
||||
return if (ConverterEnv.syncDialogs) Syncro().sync(input) else input
|
||||
}
|
||||
|
||||
fun canRead(): Boolean {
|
||||
try {
|
||||
val reader = getReader()
|
||||
return reader != null
|
||||
} catch (e: FileUnavailableException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(FileUnavailableException::class, FileIsNullOrEmpty::class)
|
||||
fun execute() {
|
||||
override suspend fun convert(data: ConvertTask.Data) {
|
||||
val file = File(data.inputFile)
|
||||
listener.onStarted(file.absolutePath)
|
||||
try {
|
||||
Configuration.exportJson = true
|
||||
val read = getReader()?.read() ?: throw FileIsNullOrEmpty()
|
||||
val read = getSubtitleReader(file)?.read() ?: throw FileIsNullOrEmpty()
|
||||
if (read.isEmpty())
|
||||
throw FileIsNullOrEmpty()
|
||||
val filtered = read.filter { !it.ignore && it.type !in listOf(DialogType.SIGN_SONG, DialogType.CAPTION) }
|
||||
val syncOrNotSync = syncDialogs(filtered)
|
||||
|
||||
val exporter = Export(file, File(data.outputDirectory), data.outputFileName)
|
||||
val exporter = env.createExporter(file, File(data.outputDirectory), data.outputFileName)
|
||||
|
||||
val outFiles = if (data.formats.isEmpty()) {
|
||||
exporter.write(syncOrNotSync)
|
||||
@ -65,22 +57,17 @@ class Converter2(val data: ConvertTask.Data,
|
||||
}
|
||||
exported
|
||||
}
|
||||
result = outFiles.map { it.absolutePath }
|
||||
listener.onCompleted(file.absolutePath, result!!)
|
||||
writtenUris = outFiles.map { it.absolutePath }
|
||||
listener.onCompleted(file.absolutePath, writtenUris!!)
|
||||
} catch (e: Exception) {
|
||||
listener.onError(file.absolutePath, e.message ?: e.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private var result: List<String>? = null
|
||||
fun getResult(): List<String> {
|
||||
if (result == null) {
|
||||
override fun getResult(): List<String> {
|
||||
if (writtenUris == null) {
|
||||
throw IllegalStateException("Execute must be called before getting result")
|
||||
}
|
||||
return result!!
|
||||
return writtenUris!!
|
||||
}
|
||||
|
||||
|
||||
class FileIsNullOrEmpty(override val message: String? = "File read is null or empty"): RuntimeException()
|
||||
class FileUnavailableException(override val message: String): RuntimeException()
|
||||
}
|
||||
@ -1,19 +1,29 @@
|
||||
package no.iktdev.mediaprocessing.converter.listeners
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import no.iktdev.eventi.models.Event
|
||||
import no.iktdev.eventi.models.Task
|
||||
import no.iktdev.eventi.models.store.TaskStatus
|
||||
import no.iktdev.eventi.tasks.TaskListener
|
||||
import no.iktdev.eventi.tasks.TaskType
|
||||
import no.iktdev.library.subtitle.export.Export
|
||||
import no.iktdev.library.subtitle.reader.BaseReader
|
||||
import no.iktdev.library.subtitle.reader.Reader
|
||||
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
|
||||
import no.iktdev.mediaprocessing.converter.ExportAdapter
|
||||
import no.iktdev.mediaprocessing.converter.Exporter
|
||||
import no.iktdev.mediaprocessing.converter.convert.ConvertListener
|
||||
import no.iktdev.mediaprocessing.converter.convert.Converter
|
||||
import no.iktdev.mediaprocessing.converter.convert.Converter2
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.ConvertTaskResultEvent
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
|
||||
import org.springframework.stereotype.Component
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
@Component
|
||||
class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) {
|
||||
open class ConvertTaskListener(): TaskListener(TaskType.CPU_INTENSIVE) {
|
||||
|
||||
override fun getWorkerId(): String {
|
||||
return "${this::class.java.simpleName}-${TaskType.CPU_INTENSIVE}-${UUID.randomUUID()}"
|
||||
@ -27,18 +37,15 @@ class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) {
|
||||
if (task !is ConvertTask) {
|
||||
throw IllegalArgumentException("Invalid task type: ${task::class.java.name}")
|
||||
}
|
||||
val converter = Converter2(task.data, object: ConvertListener {
|
||||
override fun onStarted(inputFile: String) {
|
||||
}
|
||||
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
|
||||
}
|
||||
})
|
||||
val converter = getConverter()
|
||||
|
||||
withHeartbeatRunner {
|
||||
reporter?.updateLastSeen(task.taskId)
|
||||
}
|
||||
|
||||
converter.execute()
|
||||
withContext(Dispatchers.Unconfined) {
|
||||
converter.convert(task.data)
|
||||
}
|
||||
|
||||
return try {
|
||||
val result = converter.getResult()
|
||||
@ -59,9 +66,38 @@ class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) {
|
||||
).producedFrom(task)
|
||||
newEvent
|
||||
}
|
||||
}
|
||||
|
||||
open fun getConverter(): Converter {
|
||||
return Converter2(getConverterEnvironment(), getListener())
|
||||
}
|
||||
|
||||
open fun getListener(): ConvertListener {
|
||||
return DefaultConvertListener()
|
||||
}
|
||||
|
||||
class DefaultConvertListener: ConvertListener {
|
||||
override fun onStarted(inputFile: String) {
|
||||
}
|
||||
|
||||
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
open fun getConverterEnvironment(): ConverterEnvironment {
|
||||
return DefaultConverterEnvironment()
|
||||
}
|
||||
|
||||
class DefaultConverterEnvironment : ConverterEnvironment {
|
||||
override fun canRead(file: File) = file.canRead()
|
||||
|
||||
override fun getReader(file: File): BaseReader? =
|
||||
Reader(file).getSubtitleReader()
|
||||
|
||||
override fun createExporter(input: File, outputDir: File, name: String): Exporter {
|
||||
return ExportAdapter(Export(input, outputDir, name))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package no.iktdev.mediaprocessing.converter
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import no.iktdev.eventi.models.store.TaskStatus
|
||||
import no.iktdev.library.subtitle.reader.BaseReader
|
||||
import no.iktdev.mediaprocessing.converter.convert.ConvertListener
|
||||
import no.iktdev.mediaprocessing.converter.convert.Converter
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
|
||||
import java.io.File
|
||||
|
||||
class MockConverter(
|
||||
val delayMillis: Long = 0,
|
||||
private val simulatedResult: List<String>? = null,
|
||||
private val taskResultStatus: TaskStatus = TaskStatus.Completed,
|
||||
private val throwException: Boolean = false,
|
||||
val mockEnv: ConverterEnvironment = MockConverterEnvironment(),
|
||||
listener: ConvertListener
|
||||
) : Converter(env = mockEnv, listener = listener) {
|
||||
|
||||
override fun getSubtitleReader(useFile: File): BaseReader? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun convert(data: ConvertTask.Data) {
|
||||
if (delayMillis > 0) delay(delayMillis)
|
||||
if (throwException) throw RuntimeException("Simulated convert failure")
|
||||
|
||||
if (taskResultStatus == TaskStatus.Failed) {
|
||||
listener.onError(data.inputFile, "Failed state desired")
|
||||
} else {
|
||||
listener.onCompleted(data.inputFile, simulatedResult!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getResult(): List<String> {
|
||||
return simulatedResult!!
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package no.iktdev.mediaprocessing.converter
|
||||
|
||||
import no.iktdev.library.subtitle.reader.BaseReader
|
||||
import java.io.File
|
||||
|
||||
class MockConverterEnvironment(
|
||||
var canReadValue: Boolean = true,
|
||||
var reader: BaseReader? = null,
|
||||
var exporter: Exporter? = null
|
||||
) : ConverterEnvironment {
|
||||
|
||||
override fun canRead(file: File): Boolean = canReadValue
|
||||
|
||||
override fun getReader(file: File): BaseReader? = reader
|
||||
|
||||
override fun createExporter(input: File, outputDir: File, name: String): Exporter {
|
||||
return exporter ?: error("FakeEnv.exporter must be set before calling createExporter")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,249 @@
|
||||
package no.iktdev.mediaprocessing.converter.convert
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import no.iktdev.library.subtitle.classes.Dialog
|
||||
import no.iktdev.library.subtitle.classes.DialogType
|
||||
import no.iktdev.library.subtitle.classes.Time
|
||||
import no.iktdev.library.subtitle.reader.BaseReader
|
||||
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
|
||||
import no.iktdev.mediaprocessing.converter.Exporter
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
|
||||
import no.iktdev.mediaprocessing.shared.common.model.SubtitleFormat
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
|
||||
class Converter2Test {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Fake implementations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
class FakeReader(
|
||||
private val dialogs: List<Dialog>,
|
||||
private val shouldThrow: Boolean = false
|
||||
) : BaseReader() {
|
||||
override fun read(): List<Dialog> {
|
||||
if (shouldThrow) throw RuntimeException("reader failed")
|
||||
return dialogs
|
||||
}
|
||||
}
|
||||
|
||||
class FakeExporter(
|
||||
private val srtFile: File? = null,
|
||||
private val smiFile: File? = null,
|
||||
private val vttFile: File? = null,
|
||||
private val filesForWrite: List<File> = emptyList(),
|
||||
private val shouldThrow: Boolean = false
|
||||
) : Exporter {
|
||||
|
||||
override fun write(dialogs: List<Dialog>): MutableList<File> {
|
||||
if (shouldThrow) throw RuntimeException("export failed")
|
||||
return filesForWrite.toMutableList()
|
||||
}
|
||||
|
||||
override fun writeSrt(dialogs: List<Dialog>): File =
|
||||
srtFile ?: error("srtFile not set in FakeExporter")
|
||||
|
||||
override fun writeSmi(dialogs: List<Dialog>): File =
|
||||
smiFile ?: error("smiFile not set in FakeExporter")
|
||||
|
||||
override fun writeVtt(dialogs: List<Dialog>): File =
|
||||
vttFile ?: error("vttFile not set in FakeExporter")
|
||||
}
|
||||
|
||||
|
||||
class FakeListener : ConvertListener {
|
||||
var started = 0
|
||||
var completed = 0
|
||||
var errors = 0
|
||||
var lastError: String? = null
|
||||
|
||||
override fun onStarted(inputFile: String) {
|
||||
started++
|
||||
}
|
||||
|
||||
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
|
||||
completed++
|
||||
}
|
||||
|
||||
override fun onError(inputFile: String, message: String) {
|
||||
errors++
|
||||
lastError = message
|
||||
}
|
||||
}
|
||||
|
||||
class FakeEnv(
|
||||
var canReadValue: Boolean = true,
|
||||
var reader: BaseReader? = null,
|
||||
var exporter: Exporter? = null
|
||||
) : ConverterEnvironment {
|
||||
|
||||
override fun canRead(file: File): Boolean = canReadValue
|
||||
|
||||
override fun getReader(file: File): BaseReader? = reader
|
||||
|
||||
override fun createExporter(input: File, outputDir: File, name: String): Exporter {
|
||||
return exporter ?: error("FakeEnv.exporter must be set before calling createExporter")
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private fun makeTaskData(
|
||||
input: String = "input.srt",
|
||||
language: String = "no",
|
||||
outDir: String = "out",
|
||||
outName: String = "name",
|
||||
formats: List<SubtitleFormat> = emptyList(),
|
||||
allowOverwrite: Boolean = true
|
||||
) = ConvertTask.Data(
|
||||
inputFile = input,
|
||||
language = language,
|
||||
outputDirectory = outDir,
|
||||
outputFileName = outName,
|
||||
formats = formats,
|
||||
allowOverwrite = allowOverwrite
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Tests
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når execute() kjøres
|
||||
Hvis reader returnerer tom liste
|
||||
Så kalles onError
|
||||
"""
|
||||
)
|
||||
fun execute_emptyFile() = runTest {
|
||||
val env = FakeEnv(
|
||||
canReadValue = true,
|
||||
reader = FakeReader(emptyList())
|
||||
)
|
||||
val listener = FakeListener()
|
||||
|
||||
val converter = Converter2(
|
||||
env = env,
|
||||
listener = listener
|
||||
)
|
||||
|
||||
converter.convert(makeTaskData())
|
||||
|
||||
assertEquals(1, listener.errors)
|
||||
assertEquals(0, listener.completed)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når execute() kjøres
|
||||
Hvis reader returnerer dialoger
|
||||
Og exporter.write lykkes
|
||||
Så kalles onCompleted
|
||||
"""
|
||||
)
|
||||
fun execute_success() = runTest {
|
||||
val dialogs = listOf(
|
||||
Dialog("0", Time(0, 0, 0, 0), Time(0, 0, 1000, 0), "Hello", DialogType.DIALOG_NORMAL)
|
||||
)
|
||||
|
||||
val env = FakeEnv(
|
||||
canReadValue = true,
|
||||
reader = FakeReader(dialogs),
|
||||
exporter = FakeExporter(srtFile = File("/fake/out.srt"))
|
||||
)
|
||||
|
||||
val listener = FakeListener()
|
||||
|
||||
val converter = Converter2(
|
||||
env = env,
|
||||
listener = listener
|
||||
)
|
||||
|
||||
converter.convert(
|
||||
makeTaskData(
|
||||
formats = listOf(SubtitleFormat.SRT)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(1, listener.completed)
|
||||
assertEquals(listOf("/fake/out.srt"), converter.getResult())
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når execute() kjøres
|
||||
Hvis exporter.write kaster exception
|
||||
Så kalles onError
|
||||
"""
|
||||
)
|
||||
fun execute_exportFails() = runTest {
|
||||
val dialogs = listOf(
|
||||
Dialog("0", Time(0, 0, 0, 0), Time(0, 0, 1000, 0), "Hello", DialogType.DIALOG_NORMAL)
|
||||
)
|
||||
|
||||
val env = FakeEnv(
|
||||
canReadValue = true,
|
||||
reader = FakeReader(dialogs),
|
||||
exporter = FakeExporter(srtFile = File("/fake/out.srt"), shouldThrow = true)
|
||||
)
|
||||
|
||||
val listener = FakeListener()
|
||||
|
||||
val converter = Converter2(
|
||||
env = env,
|
||||
listener = listener
|
||||
)
|
||||
|
||||
converter.convert(makeTaskData())
|
||||
|
||||
assertEquals(1, listener.errors)
|
||||
assertEquals(0, listener.completed)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når execute() kjøres
|
||||
Hvis formats inneholder SRT og VTT
|
||||
Så brukes writeSrt og writeVtt
|
||||
"""
|
||||
)
|
||||
fun execute_multipleFormats() = runTest {
|
||||
val dialogs = listOf(
|
||||
Dialog("0", Time(0, 0, 0, 0), Time(0, 0, 1000, 0), "Hello", DialogType.DIALOG_NORMAL)
|
||||
)
|
||||
|
||||
|
||||
val env = FakeEnv(
|
||||
canReadValue = true,
|
||||
reader = FakeReader(dialogs),
|
||||
exporter = FakeExporter(srtFile = File("/fake/out.srt"), vttFile = File("/fake/out.vtt"))
|
||||
)
|
||||
|
||||
val listener = FakeListener()
|
||||
|
||||
val converter = Converter2(
|
||||
env = env,
|
||||
listener = listener
|
||||
)
|
||||
|
||||
converter.convert(
|
||||
makeTaskData(
|
||||
formats = listOf(SubtitleFormat.SRT, SubtitleFormat.VTT)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(1, listener.completed)
|
||||
assertEquals(listOf("/fake/out.srt", "/fake/out.vtt"), converter.getResult())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,245 @@
|
||||
package no.iktdev.mediaprocessing.converter.listeners
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import no.iktdev.eventi.models.Event
|
||||
import no.iktdev.eventi.models.Task
|
||||
import no.iktdev.eventi.models.store.TaskStatus
|
||||
import no.iktdev.eventi.tasks.TaskReporter
|
||||
import no.iktdev.library.subtitle.classes.Dialog
|
||||
import no.iktdev.library.subtitle.classes.DialogType
|
||||
import no.iktdev.library.subtitle.classes.Time
|
||||
import no.iktdev.library.subtitle.reader.BaseReader
|
||||
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
|
||||
import no.iktdev.mediaprocessing.converter.Exporter
|
||||
import no.iktdev.mediaprocessing.converter.MockConverter
|
||||
import no.iktdev.mediaprocessing.converter.MockConverterEnvironment
|
||||
import no.iktdev.mediaprocessing.converter.convert.ConvertListener
|
||||
import no.iktdev.mediaprocessing.converter.convert.Converter
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.ConvertTaskResultEvent
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
|
||||
import no.iktdev.mediaprocessing.shared.common.model.SubtitleFormat
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
class ConvertTaskListenerTest {
|
||||
|
||||
class ConvertTaskListenerTestImplementation : ConvertTaskListener() {
|
||||
fun getJob() = currentJob
|
||||
|
||||
|
||||
}
|
||||
var overrideEnv: ConverterEnvironment = DefaultConverterEnvironment()
|
||||
override fun getConverterEnvironment(): ConverterEnvironment {
|
||||
return overrideEnv
|
||||
}
|
||||
|
||||
var overrideListener: ConvertListener? = null
|
||||
override fun getListener(): ConvertListener {
|
||||
if (overrideListener != null)
|
||||
return overrideListener!!
|
||||
else
|
||||
return super.getListener()
|
||||
}
|
||||
|
||||
var overrideConverter: Converter? = null
|
||||
override fun getConverter(): Converter {
|
||||
return if (overrideConverter != null)
|
||||
overrideConverter!!
|
||||
else
|
||||
super.getConverter()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val listener = ConvertTaskListenerTestImplementation()
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Fake environment + fake converter
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
class FakeListener : ConvertListener {
|
||||
var started = 0
|
||||
var completed = 0
|
||||
var errors = 0
|
||||
|
||||
override fun onStarted(inputFile: String) {
|
||||
started++
|
||||
}
|
||||
|
||||
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
|
||||
completed++
|
||||
}
|
||||
|
||||
override fun onError(inputFile: String, message: String) {
|
||||
errors++
|
||||
}
|
||||
}
|
||||
|
||||
class FakeExporter(
|
||||
private val files: List<File>,
|
||||
private val shouldThrow: Boolean = false
|
||||
) : Exporter {
|
||||
|
||||
override fun write(dialogs: List<Dialog>): MutableList<File> {
|
||||
if (shouldThrow) throw RuntimeException("export failed")
|
||||
return files.toMutableList()
|
||||
}
|
||||
|
||||
override fun writeSrt(dialogs: List<Dialog>) = files[0]
|
||||
override fun writeSmi(dialogs: List<Dialog>) = files[0]
|
||||
override fun writeVtt(dialogs: List<Dialog>) = files[0]
|
||||
}
|
||||
|
||||
class FakeReader(
|
||||
private val dialogs: List<Dialog>,
|
||||
private val shouldThrow: Boolean = false
|
||||
) : BaseReader() {
|
||||
override fun read(): List<Dialog> {
|
||||
if (shouldThrow) throw RuntimeException("reader failed")
|
||||
return dialogs
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val overrideReporter = object : TaskReporter {
|
||||
override fun markClaimed(taskId: UUID, workerId: String) {}
|
||||
override fun updateLastSeen(taskId: UUID) {}
|
||||
override fun markConsumed(taskId: UUID) {}
|
||||
override fun updateProgress(taskId: UUID, progress: Int) {}
|
||||
override fun log(taskId: UUID, message: String) {}
|
||||
override fun publishEvent(event: Event) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
private fun makeTask(
|
||||
formats: List<SubtitleFormat> = emptyList()
|
||||
): ConvertTask {
|
||||
return ConvertTask(
|
||||
ConvertTask.Data(
|
||||
inputFile = "input.srt",
|
||||
language = "no",
|
||||
outputDirectory = "out",
|
||||
outputFileName = "name",
|
||||
formats = formats,
|
||||
allowOverwrite = true
|
||||
)
|
||||
).apply { newReferenceId() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("""
|
||||
Når onTask kjøres og converterer
|
||||
Hvis den bruker lengre tid
|
||||
Så:
|
||||
Skal koden vente til den er ferdig
|
||||
""")
|
||||
fun onTask_validate_delay() = runTest {
|
||||
val delay = 1000L
|
||||
val converter = MockConverter(
|
||||
delay,
|
||||
listOf("file:///potato.srt"),
|
||||
listener = FakeListener()
|
||||
)
|
||||
listener.apply {
|
||||
overrideConverter = converter
|
||||
}
|
||||
val task = makeTask()
|
||||
val event = listener.onTask(task)
|
||||
|
||||
val time = measureTimeMillis {
|
||||
val accepted = listener.accept(task, overrideReporter)
|
||||
assertTrue(accepted, "Task listener did not accept the task.")
|
||||
listener.getJob()?.join()
|
||||
assertTrue(event is ConvertTaskResultEvent)
|
||||
assertEquals(TaskStatus.Completed, (event as ConvertTaskResultEvent).status)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når onTask kjøres
|
||||
Hvis converter lykkes
|
||||
Så returneres Completed-event
|
||||
"""
|
||||
)
|
||||
fun onTask_success() = runTest {
|
||||
val dialogs = listOf(
|
||||
Dialog("0", Time(0, 0, 0, 0), Time(0, 0, 1000, 0), "Hello", DialogType.DIALOG_NORMAL)
|
||||
)
|
||||
|
||||
val env = MockConverterEnvironment(
|
||||
canReadValue = true,
|
||||
reader = FakeReader(dialogs),
|
||||
exporter = FakeExporter(listOf(File("/fake/out.srt")))
|
||||
)
|
||||
|
||||
listener.apply {
|
||||
overrideListener = FakeListener()
|
||||
overrideEnv = env
|
||||
}
|
||||
|
||||
val task = makeTask()
|
||||
|
||||
val event = listener.onTask(task) as ConvertTaskResultEvent
|
||||
|
||||
assertEquals(TaskStatus.Completed, event.status)
|
||||
assertEquals(listOf("/fake/out.srt"), event.data!!.outputFiles)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når onTask kjøres
|
||||
Hvis converter feiler
|
||||
Så returneres Failed-event
|
||||
"""
|
||||
)
|
||||
fun onTask_failure() = runTest {
|
||||
val env = MockConverterEnvironment(
|
||||
canReadValue = true,
|
||||
reader = FakeReader(emptyList()) // triggers FileIsNullOrEmpty
|
||||
)
|
||||
|
||||
listener.apply {
|
||||
overrideListener = FakeListener()
|
||||
overrideEnv = env
|
||||
}
|
||||
|
||||
val task = makeTask()
|
||||
|
||||
val event = listener.onTask(task) as ConvertTaskResultEvent
|
||||
|
||||
assertEquals(TaskStatus.Failed, event.status)
|
||||
assertNull(event.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når supports() kalles
|
||||
Så returnerer den true for ConvertTask og false for andre typer
|
||||
"""
|
||||
)
|
||||
fun supports_test() {
|
||||
val listener = ConvertTaskListener()
|
||||
|
||||
assertTrue(listener.supports(makeTask()))
|
||||
assertFalse(listener.supports(DummyTask()))
|
||||
}
|
||||
|
||||
class DummyTask : Task()
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ dependencies {
|
||||
|
||||
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT")
|
||||
implementation("no.iktdev.streamit.library:streamit-library-db:1.0.0-alpha14")
|
||||
implementation("no.iktdev:eventi:1.0-rc16")
|
||||
implementation("no.iktdev:eventi:1.0-rc17")
|
||||
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
||||
|
||||
@ -9,6 +9,7 @@ import no.iktdev.mediaprocessing.coordinator.util.FileSystemService
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.MigrateContentToStoreTaskResultEvent
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.MigrateToContentStoreTask
|
||||
import no.iktdev.mediaprocessing.shared.common.model.MigrateStatus
|
||||
import no.iktdev.mediaprocessing.shared.common.silentTry
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import org.springframework.stereotype.Component
|
||||
import java.io.File
|
||||
@ -40,13 +41,13 @@ class MigrateContentToStoreTaskListener: TaskListener(TaskType.IO_INTENSIVE) {
|
||||
coverStatus.none { it.status == MigrateStatus.Failed })
|
||||
{
|
||||
pickedTask.data.videoContent?.cachedUri?.let { File(it) }?.let {
|
||||
fs.delete(it)
|
||||
silentTry { fs.delete(it) }
|
||||
}
|
||||
pickedTask.data.subtitleContent?.map { File(it.cachedUri) }?.forEach {
|
||||
fs.delete(it)
|
||||
silentTry { fs.delete(it) }
|
||||
}
|
||||
pickedTask.data.coverContent?.map { File(it.cachedUri) }?.forEach {
|
||||
fs.delete(it)
|
||||
silentTry { fs.delete(it) }
|
||||
}
|
||||
} else {
|
||||
status = TaskStatus.Failed
|
||||
|
||||
@ -5,6 +5,8 @@ import java.io.File
|
||||
|
||||
class MockFileSystemService : FileSystemService {
|
||||
var copyShouldFail = false
|
||||
var deleteShouldFail = false
|
||||
|
||||
var identical = true
|
||||
val copied = mutableListOf<Pair<File, File>>()
|
||||
val deleted = mutableListOf<File>()
|
||||
@ -19,6 +21,7 @@ class MockFileSystemService : FileSystemService {
|
||||
}
|
||||
|
||||
override fun delete(file: File) {
|
||||
if (deleteShouldFail) throw RuntimeException("delete failed")
|
||||
deleted += file
|
||||
}
|
||||
}
|
||||
@ -102,6 +102,25 @@ class MigrateContentToStoreTaskListenerTest {
|
||||
assertEquals(MigrateStatus.Failed, result.status)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når migrateVideo kjøres
|
||||
Hvis content er null
|
||||
Så:
|
||||
returneres NotPresent
|
||||
"""
|
||||
)
|
||||
fun migrateVideo_null() {
|
||||
val fs = MockFileSystemService().also {
|
||||
listener.fs = it
|
||||
}
|
||||
|
||||
val result = listener.migrateVideo(fs, null)
|
||||
|
||||
assertEquals(MigrateStatus.NotPresent, result.status)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// migrateSubtitle
|
||||
// -------------------------------------------------------------------------
|
||||
@ -151,6 +170,36 @@ class MigrateContentToStoreTaskListenerTest {
|
||||
assertEquals(MigrateStatus.Completed, result.first().status)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når migrateSubtitle kjøres
|
||||
Hvis én lykkes og én feiler
|
||||
Så:
|
||||
returneres både Completed og Failed
|
||||
"""
|
||||
)
|
||||
fun migrateSubtitle_mixed() {
|
||||
val fs = MockFileSystemService().apply {
|
||||
// first OK, second fails
|
||||
copyShouldFail = false
|
||||
}.also { listener.fs = it }
|
||||
|
||||
val subs = listOf(
|
||||
MigrateToContentStoreTask.Data.SingleSubtitle("en", "/tmp/a", "/tmp/b"),
|
||||
MigrateToContentStoreTask.Data.SingleSubtitle("no", "/tmp/c", "/tmp/d")
|
||||
)
|
||||
|
||||
// simulate second failing
|
||||
fs.copyShouldFail = false
|
||||
val result1 = listener.migrateSubtitle(fs, listOf(subs[0]))
|
||||
fs.copyShouldFail = true
|
||||
val result2 = listener.migrateSubtitle(fs, listOf(subs[1]))
|
||||
|
||||
assertEquals(MigrateStatus.Completed, result1.first().status)
|
||||
assertEquals(MigrateStatus.Failed, result2.first().status)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// migrateCover
|
||||
// -------------------------------------------------------------------------
|
||||
@ -179,6 +228,34 @@ class MigrateContentToStoreTaskListenerTest {
|
||||
assertEquals(MigrateStatus.Completed, result.first().status)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når migrateCover kjøres
|
||||
Hvis flere covers og én feiler
|
||||
Så:
|
||||
returneres både Completed og Failed
|
||||
"""
|
||||
)
|
||||
fun migrateCover_mixed() {
|
||||
val fs = MockFileSystemService().also { listener.fs = it }
|
||||
|
||||
val covers = listOf(
|
||||
MigrateToContentStoreTask.Data.SingleContent("/tmp/a", "/tmp/b"),
|
||||
MigrateToContentStoreTask.Data.SingleContent("/tmp/c", "/tmp/d")
|
||||
)
|
||||
|
||||
// first OK, second mismatch
|
||||
fs.identical = true
|
||||
val ok = listener.migrateCover(fs, listOf(covers[0]))
|
||||
|
||||
fs.identical = false
|
||||
val fail = listener.migrateCover(fs, listOf(covers[1]))
|
||||
|
||||
assertEquals(MigrateStatus.Completed, ok.first().status)
|
||||
assertEquals(MigrateStatus.Failed, fail.first().status)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// onTask
|
||||
// -------------------------------------------------------------------------
|
||||
@ -244,4 +321,61 @@ class MigrateContentToStoreTaskListenerTest {
|
||||
assertEquals(TaskStatus.Failed, event.status)
|
||||
assertEquals(0, fs.deleted.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når onTask kjøres
|
||||
Hvis video feiler
|
||||
Så:
|
||||
returneres Failed og ingenting slettes
|
||||
"""
|
||||
)
|
||||
fun onTask_videoFails() = runTest {
|
||||
val fs = MockFileSystemService().apply { copyShouldFail = true }
|
||||
.also { listener.fs = it }
|
||||
|
||||
val task = MigrateToContentStoreTask(
|
||||
MigrateToContentStoreTask.Data(
|
||||
collection = "col",
|
||||
videoContent = MigrateToContentStoreTask.Data.SingleContent("/tmp/v", "/tmp/v2"),
|
||||
subtitleContent = emptyList(),
|
||||
coverContent = emptyList()
|
||||
)
|
||||
).newReferenceId()
|
||||
|
||||
val event = listener.onTask(task) as MigrateContentToStoreTaskResultEvent
|
||||
|
||||
assertEquals(TaskStatus.Failed, event.status)
|
||||
assertEquals(0, fs.deleted.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Når onTask kjøres
|
||||
Hvis sletting feiler
|
||||
Så:
|
||||
returneres fortsatt Completed
|
||||
"""
|
||||
)
|
||||
fun onTask_deleteFails() = runTest {
|
||||
val fs = MockFileSystemService().apply { deleteShouldFail = true }
|
||||
.also { listener.fs = it }
|
||||
|
||||
|
||||
val task = MigrateToContentStoreTask(
|
||||
MigrateToContentStoreTask.Data(
|
||||
collection = "col",
|
||||
videoContent = MigrateToContentStoreTask.Data.SingleContent("/tmp/v", "/tmp/v2"),
|
||||
subtitleContent = emptyList(),
|
||||
coverContent = emptyList()
|
||||
)
|
||||
).newReferenceId()
|
||||
|
||||
val event = listener.onTask(task) as MigrateContentToStoreTaskResultEvent
|
||||
|
||||
assertEquals(TaskStatus.Completed, event.status)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ dependencies {
|
||||
implementation("org.json:json:20210307")
|
||||
|
||||
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT")
|
||||
implementation("no.iktdev:eventi:1.0-rc15")
|
||||
implementation("no.iktdev:eventi:1.0-rc17")
|
||||
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
||||
|
||||
@ -61,7 +61,7 @@ dependencies {
|
||||
implementation("com.zaxxer:HikariCP:7.0.2")
|
||||
|
||||
implementation(project(":shared:ffmpeg"))
|
||||
implementation("no.iktdev:eventi:1.0-rc16")
|
||||
implementation("no.iktdev:eventi:1.0-rc17")
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user