Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to the Video Selections Process #239

Merged
merged 6 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions DifferenceGenerator/src/main/kotlin/AcceptedCodecs.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.bytedeco.javacv.FFmpegFrameGrabber

/**
* A globally accessible object that contains a list of all accepted codecs. This list can be
* expanded to include more codecs.
Expand All @@ -17,5 +19,20 @@ class AcceptedCodecs {
"Uncompressed YUV 422 8-bit",
"FFV1 YUV 422 8-bit",
)

/**
* Checks if the given file is in an accepted codec.
*/
public fun checkFile(path: String): Boolean {
val grabber = FFmpegFrameGrabber(path)
grabber.start()
val codecName = grabber.videoMetadata["encoder"] ?: grabber.videoCodecName
for (codec in ACCEPTED_CODECS) {
if (codecName.contains(codec, ignoreCase = true)) {
return true
}
}
return false
}
}
}
16 changes: 1 addition & 15 deletions DifferenceGenerator/src/main/kotlin/DifferenceGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class DifferenceGenerator(
* @throws DifferenceGeneratorDimensionException if the videos' dimensions don't match.
*/
init {
if (!isLosslessCodec(videoReferenceGrabber) || !isLosslessCodec(videoCurrentGrabber)) {
if (!AcceptedCodecs.checkFile(videoReferencePath) || !AcceptedCodecs.checkFile(videoCurrentPath)) {
throw DifferenceGeneratorCodecException("Videos must be in a lossless codec")
}

Expand All @@ -73,20 +73,6 @@ class DifferenceGenerator(
avutil.av_log_set_level(avutil.AV_LOG_QUIET)
}

/**
* Determines whether the given video file is encoded using one of the
* [AcceptedCodecs.ACCEPTED_CODECS].
*
* @param grabber [MaskedImageGrabber] of the video to check
* @return true if the video file is encoded using one of the [AcceptedCodecs.ACCEPTED_CODECS],
* false otherwise
*/
private fun isLosslessCodec(grabber: MaskedImageGrabber): Boolean {
grabber.start()
val codecName = grabber.videoMetadata["encoder"] ?: grabber.videoCodecName
return codecName in AcceptedCodecs.ACCEPTED_CODECS
}

/**
* Generates a difference video from the two videos given in the constructor.
*
Expand Down
7 changes: 7 additions & 0 deletions GUI/src/main/kotlin/ui/components/general/FileSelector.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ui.components.general

import javax.swing.JFileChooser
import javax.swing.filechooser.FileNameExtensionFilter

/**
* Opens a file chooser dialog and returns the selected file path.
Expand All @@ -12,10 +13,16 @@ import javax.swing.JFileChooser
fun openFileChooserAndGetPath(
directoryPath: String?,
onResult: (String) -> Unit,
allowedFileExtensions: Array<String>? = null,
) {
val fileChooser = JFileChooser()
// set the current directory to the given directory path or if null the user's home directory
fileChooser.currentDirectory = java.io.File(directoryPath ?: System.getProperty("user.home"))
if (allowedFileExtensions != null) {
fileChooser.isAcceptAllFileFilterUsed = false
val filter = FileNameExtensionFilter(allowedFileExtensions.joinToString(", "), *allowedFileExtensions)
fileChooser.addChoosableFileFilter(filter)
}
val result = fileChooser.showOpenDialog(null)
if (JFileChooser.APPROVE_OPTION == result) {
onResult(fileChooser.selectedFile.absolutePath)
Expand Down
8 changes: 7 additions & 1 deletion GUI/src/main/kotlin/ui/components/general/ProjectManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ fun ProjectMenu(
onClick = {
openScope.launch(
Dispatchers.IO,
) { openFileChooserAndGetPath(state.value.openProjectPath) { path -> handleOpenProject(state, path, errorDialogText) } }
) {
openFileChooserAndGetPath(
directoryPath = state.value.openProjectPath,
onResult = { path -> handleOpenProject(state, path, errorDialogText) },
allowedFileExtensions = arrayOf("mkv"),
)
}
expanded = false
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,19 @@ fun RowScope.FileSelectorButton(
onUpdateResult: (String) -> Unit,
tooltipText: String? = null,
directoryPath: String? = null,
buttonDescription: String? = null,
allowedFileExtensions: Array<String>? = null,
) {
val scope = rememberCoroutineScope()
Button(
modifier = Modifier.weight(1f).padding(8.dp).fillMaxHeight(1f),
onClick = {
scope.launch(Dispatchers.IO) {
openFileChooserAndGetPath(directoryPath) { path -> onUpdateResult(path) }
openFileChooserAndGetPath(
directoryPath,
{ path -> onUpdateResult(path) },
allowedFileExtensions,
)
}
},
shape = MaterialTheme.shapes.medium,
Expand All @@ -66,10 +72,17 @@ fun RowScope.FileSelectorButton(
InfoIconWithHover(tooltipText)
}
}
// row to display the button text
Row(modifier = Modifier.weight(0.15f)) {
AutoSizeText(text = buttonText, minimalFontSize = 27)

if (buttonDescription != null) {
// row to display the button text
Row(modifier = Modifier.weight(0.1f)) { AutoSizeText(text = buttonText, minimalFontSize = 27) }
// row to display the button description
Row(modifier = Modifier.weight(0.05f)) { AutoSizeText(text = buttonDescription) }
} else {
// row to display the button text
Row(modifier = Modifier.weight(0.15f)) { AutoSizeText(text = buttonText, minimalFontSize = 27) }
}

// row to display the selected file path
Row(modifier = Modifier.weight(0.1f)) {
AutoSizeText(text = buttonPath ?: "No file selected", minimalFontSize = 25)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fun RowScope.MaskSelectorButton(
) {
val scope = rememberCoroutineScope()
Button(
onClick = { scope.launch(Dispatchers.IO) { openFileChooserAndGetPath(directoryPath) { path -> onUpdateResult(path) } } },
onClick = { scope.launch(Dispatchers.IO) { openFileChooserAndGetPath(directoryPath, { path -> onUpdateResult(path) }) } },
shape = MaterialTheme.shapes.medium,
modifier = Modifier.padding(16.dp).weight(0.6f).fillMaxHeight(0.9f),
) {
Expand Down
63 changes: 54 additions & 9 deletions GUI/src/main/kotlin/ui/screens/SelectVideoScreen.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package ui.screens

import AcceptedCodecs
import algorithms.AlgorithmExecutionState
import androidx.compose.foundation.layout.*
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import models.AppState
import ui.components.general.ErrorDialog
import ui.components.general.HelpMenu
import ui.components.general.ProjectMenu
import ui.components.selectVideoScreen.AdvancedSettingsButton
Expand All @@ -29,6 +27,8 @@ fun SelectVideoScreen(state: MutableState<AppState>) {
val scope = rememberCoroutineScope()
val showLoadingDialog = remember { mutableStateOf(false) }

val errorDialogText = remember { mutableStateOf<String?>(null) }

Column(modifier = Modifier.fillMaxSize()) {
// menu bar
CenterAlignedTopAppBar(
Expand All @@ -48,27 +48,46 @@ fun SelectVideoScreen(state: MutableState<AppState>) {
HelpMenu()
},
)

// video selection
Row(modifier = Modifier.weight(0.85f)) {
FileSelectorButton(
buttonText = "Select Reference Video",
buttonPath = state.value.videoReferencePath,
onUpdateResult = { selectedFilePath ->
state.value = state.value.copy(videoReferencePath = selectedFilePath)
checkVideoFormatAndCodec(
selectedFilePath,
state,
errorDialogText,
true,
)
},
directoryPath = state.value.videoReferencePath,
buttonDescription = "Please upload a video with format mkv or mov.",
allowedFileExtensions = arrayOf("mkv", "mov"),
)

if (errorDialogText.value != null) {
ErrorDialog(
onCloseRequest = { errorDialogText.value = null },
text = errorDialogText.value!!,
)
}
FileSelectorButton(
buttonText = "Select Current Video",
buttonPath = state.value.videoCurrentPath,
onUpdateResult = { selectedFilePath ->
state.value = state.value.copy(videoCurrentPath = selectedFilePath)
checkVideoFormatAndCodec(
selectedFilePath,
state,
errorDialogText,
false,
)
},
directoryPath = state.value.videoCurrentPath,
buttonDescription = "Please upload a video with format mkv or mov.",
allowedFileExtensions = arrayOf("mkv", "mov"),
)
}

// screen switch buttons
Row(modifier = Modifier.weight(0.15f)) {
ComputeDifferencesButton(state, scope, showLoadingDialog)
Expand All @@ -82,3 +101,29 @@ fun SelectVideoScreen(state: MutableState<AppState>) {
})
}
}

/**
* Checks if the selected file is in the correct format and codec.
*/
private fun checkVideoFormatAndCodec(
akriese marked this conversation as resolved.
Show resolved Hide resolved
selectedFilePath: String,
state: MutableState<AppState>,
errorDialogText: MutableState<String?>,
isReference: Boolean,
) {
if (!selectedFilePath.endsWith(".mkv") && !selectedFilePath.endsWith(".mov")) {
errorDialogText.value =
"Uploaded Video is not in the correct format. Please upload a video with format mkv or mov."
return
}
if (!AcceptedCodecs.checkFile(selectedFilePath)) {
errorDialogText.value =
"Uploaded Video is not in the correct codec. Please upload a video encoded with ffv1."
return
}
if (isReference) {
state.value = state.value.copy(videoReferencePath = selectedFilePath)
} else {
state.value = state.value.copy(videoCurrentPath = selectedFilePath)
}
}
Loading