Skip to content

Commit 883f5f5

Browse files
committed
feat(gui): down scaled video files for fast thumbnail loading
Signed-off-by: Anton Kriese <[email protected]>
1 parent 6e12929 commit 883f5f5

File tree

5 files changed

+84
-13
lines changed

5 files changed

+84
-13
lines changed

GUI/src/main/kotlin/logic/FrameGrabber.kt

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@ import java.awt.image.BufferedImage
1818
*
1919
* @param state [MutableState]<[AppState]> containing the global state. Needed to get the file paths to the videos.
2020
*/
21-
class FrameGrabber(state: MutableState<AppState>) {
21+
class FrameGrabber(
22+
referencePath: String,
23+
currentPath: String,
24+
diffPath: String? = null,
25+
private val diffSequence: Array<AlignmentElement>,
26+
) {
2227
// create the grabbers
2328
private val videoReferenceGrabber: FFmpegFrameGrabber =
24-
FFmpegFrameGrabber(state.value.videoReferencePath)
29+
FFmpegFrameGrabber(referencePath)
2530
private val videoCurrentGrabber: FFmpegFrameGrabber =
26-
FFmpegFrameGrabber(state.value.videoCurrentPath)
27-
private val grabberDiff: FFmpegFrameGrabber = FFmpegFrameGrabber(state.value.outputPath)
31+
FFmpegFrameGrabber(currentPath)
32+
private val grabberDiff: FFmpegFrameGrabber? = if (diffPath == null) null else FFmpegFrameGrabber(diffPath)
2833

2934
// create the sequences
30-
private val diffSequence: Array<AlignmentElement> = state.value.sequenceObj
3135
private var videoReferenceFrames: MutableList<Int> = mutableListOf()
3236
private var videoCurrentFrames: MutableList<Int> = mutableListOf()
3337

@@ -44,14 +48,14 @@ class FrameGrabber(state: MutableState<AppState>) {
4448
// start the grabbers
4549
videoReferenceGrabber.start()
4650
videoCurrentGrabber.start()
47-
grabberDiff.start()
51+
grabberDiff?.start()
4852

4953
// generate the sequences for video 1 and video 2
5054
// diffSequence is already generated
5155
generateSequences()
5256

53-
width = grabberDiff.imageWidth
54-
height = grabberDiff.imageHeight
57+
width = grabberDiff?.imageWidth ?: videoReferenceGrabber.imageWidth
58+
height = grabberDiff?.imageHeight ?: videoReferenceGrabber.imageHeight
5559

5660
val coloredFrameGenerator = ColoredFrameGenerator(width, height)
5761
insertionBitmap =
@@ -98,6 +102,10 @@ class FrameGrabber(state: MutableState<AppState>) {
98102
* @return [ImageBitmap] containing the bitmap of the frame.
99103
*/
100104
fun getDiffVideoFrame(index: Int): ImageBitmap {
105+
if (grabberDiff == null) {
106+
throw IllegalStateException("No difference video was provided.")
107+
}
108+
101109
grabberDiff.setVideoFrameNumber(index)
102110
return getBitmap(grabberDiff)
103111
}
@@ -203,9 +211,9 @@ class FrameGrabber(state: MutableState<AppState>) {
203211
fun close() {
204212
videoReferenceGrabber.stop()
205213
videoCurrentGrabber.stop()
206-
grabberDiff.stop()
214+
grabberDiff?.stop()
207215
videoReferenceGrabber.close()
208216
videoCurrentGrabber.close()
209-
grabberDiff.close()
217+
grabberDiff?.close()
210218
}
211219
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package logic
2+
3+
import org.bytedeco.javacv.FFmpegFrameRecorder
4+
import wrappers.IterableFrameGrabber
5+
import wrappers.Resettable2DFrameConverter
6+
import java.awt.image.BufferedImage
7+
import java.io.File
8+
9+
fun createThumbnailVideo(
10+
inputPath: String,
11+
downScaling: Float = 0.5f,
12+
): String {
13+
assert(downScaling > 0.0f && downScaling <= 1.0f) { "Downscaling factor must be between 0 and 1" }
14+
val outputPath = kotlin.io.path.createTempFile(prefix = "gui_thumbnail_video", suffix = ".mkv").toString()
15+
16+
val grabber = IterableFrameGrabber(File(inputPath))
17+
18+
val inWidth = grabber.imageWidth
19+
val inHeight = grabber.imageHeight
20+
21+
val outWidth = (inWidth * downScaling).toInt()
22+
val outHeight = (inHeight * downScaling).toInt()
23+
24+
val recorder = FFmpegFrameRecorder(outputPath, outWidth, outHeight)
25+
26+
val converter = Resettable2DFrameConverter()
27+
28+
recorder.frameRate = grabber.frameRate
29+
recorder.videoCodec = grabber.videoCodec
30+
31+
recorder.start()
32+
33+
for (image in grabber) {
34+
// do something with the frame
35+
val scaledImage = BufferedImage(outWidth, outHeight, BufferedImage.TYPE_3BYTE_BGR)
36+
scaledImage.createGraphics().drawImage(image.getScaledInstance(outWidth, outHeight, 0), 0, 0, null)
37+
recorder.record(converter.convert(scaledImage))
38+
}
39+
40+
recorder.close()
41+
grabber.close()
42+
43+
return outputPath
44+
}

GUI/src/main/kotlin/models/AppState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ data class AppState(
4040
var hasUnsavedChanges: Boolean = false,
4141
var saveFramePath: String? = null,
4242
var saveInsertionsPath: String? = null,
43+
var thumbnailVideoPathReference: String? = null,
44+
var thumbnailVideoPathCurrent: String? = null,
4345
var maskPath: String? = null,
4446
var sequenceObj: Array<AlignmentElement> = arrayOf(),
4547
var gapOpenPenalty: Double = 0.2,

GUI/src/main/kotlin/ui/components/selectVideoScreen/ComputeDifferencesButton.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
1313
import androidx.compose.ui.text.style.TextAlign
1414
import androidx.compose.ui.unit.dp
1515
import kotlinx.coroutines.*
16+
import logic.createThumbnailVideo
1617
import logic.differenceGeneratorWrapper.DifferenceGeneratorWrapper
1718
import logic.getVideoMetadata
1819
import models.AppState
@@ -45,12 +46,13 @@ fun RowScope.ComputeDifferencesButton(
4546
onClick = {
4647
try {
4748
if (referenceIsOlderThanCurrent(state)) {
49+
createThumbnailVideos(state)
4850
calculateVideoDifferences(scope, state, errorDialogText, showDialog)
4951
} else {
5052
showConfirmDialog.value = true
5153
}
5254
} catch (e: Exception) {
53-
errorDialogText.value = "An unexpected exception was thrown when checking" +
55+
errorDialogText.value = "An unexpected exception was thrown when checking " +
5456
"video creation timestamps:\n\n${e.message}"
5557
}
5658
},
@@ -78,6 +80,7 @@ fun RowScope.ComputeDifferencesButton(
7880
text = "The reference video is newer than the current video. Are you sure you want to continue?",
7981
showDialog = showConfirmDialog.value,
8082
onConfirm = {
83+
createThumbnailVideos(state)
8184
calculateVideoDifferences(scope, state, errorDialogText, showDialog)
8285
showConfirmDialog.value = false
8386
},
@@ -87,6 +90,14 @@ fun RowScope.ComputeDifferencesButton(
8790
)
8891
}
8992

93+
fun createThumbnailVideos(state: MutableState<AppState>) {
94+
// create the thumbnail videos
95+
val tempReference = createThumbnailVideo(state.value.videoReferencePath!!, 0.25f)
96+
val tempCurrent = createThumbnailVideo(state.value.videoCurrentPath!!, 0.25f)
97+
98+
state.value = state.value.copy(thumbnailVideoPathReference = tempReference, thumbnailVideoPathCurrent = tempCurrent)
99+
}
100+
90101
private fun calculateVideoDifferences(
91102
scope: CoroutineScope,
92103
state: MutableState<AppState>,

GUI/src/main/kotlin/ui/screens/DiffScreen.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,14 @@ fun DiffScreen(state: MutableState<AppState>) {
4646
// create the navigator, which implements the jumping logic
4747
val navigator = remember { FrameNavigation(state) }
4848
val showConfirmationDialog = remember { mutableStateOf(false) }
49-
val frameGrabber = FrameGrabber(state)
50-
val thumbnailGrabber = FrameGrabber(state)
49+
val frameGrabber =
50+
FrameGrabber(state.value.videoReferencePath!!, state.value.videoCurrentPath!!, state.value.outputPath!!, state.value.sequenceObj)
51+
val thumbnailGrabber =
52+
FrameGrabber(
53+
state.value.thumbnailVideoPathReference!!,
54+
state.value.thumbnailVideoPathCurrent!!,
55+
diffSequence = state.value.sequenceObj,
56+
)
5157

5258
DisposableEffect(Unit) {
5359
onDispose {

0 commit comments

Comments
 (0)