Init
This commit is contained in:
commit
2f80222293
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
**/.idea/*
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
42
CommonCode/.gitignore
vendored
Normal file
42
CommonCode/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
26
CommonCode/build.gradle.kts
Normal file
26
CommonCode/build.gradle.kts
Normal file
@ -0,0 +1,26 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.8.21"
|
||||
}
|
||||
|
||||
group = "no.iktdev.streamit.content"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.pgreze:kotlin-process:1.3.1")
|
||||
implementation("io.github.microutils:kotlin-logging-jvm:2.0.11")
|
||||
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.1")
|
||||
testImplementation("org.assertj:assertj-core:3.4.1")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
BIN
CommonCode/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
CommonCode/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
CommonCode/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
CommonCode/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Sat Jul 15 17:55:49 CEST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
234
CommonCode/gradlew
vendored
Normal file
234
CommonCode/gradlew
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
CommonCode/gradlew.bat
vendored
Normal file
89
CommonCode/gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
2
CommonCode/settings.gradle.kts
Normal file
2
CommonCode/settings.gradle.kts
Normal file
@ -0,0 +1,2 @@
|
||||
rootProject.name = "CommonCode"
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package no.iktdev.streamit.content.common
|
||||
|
||||
import java.io.File
|
||||
|
||||
object CommonConfig {
|
||||
var kafkaConsumerId: String = System.getenv("KAFKA_TOPIC") ?: "contentEvents"
|
||||
var incomingContent: File? = if (!System.getenv("DIRECTORY_CONTENT_INCOMING").isNullOrEmpty()) File(System.getenv("DIRECTORY_CONTENT_INCOMING")) else null
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package no.iktdev.streamit.content.common
|
||||
|
||||
import mu.KotlinLogging
|
||||
import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
class FileAccess {
|
||||
companion object {
|
||||
fun isFileAvailable(file: File): Boolean {
|
||||
if (!file.exists()) return false
|
||||
var stream: RandomAccessFile? = null
|
||||
try {
|
||||
stream = RandomAccessFile(file, "rw")
|
||||
stream.close()
|
||||
logger.info { "File ${file.name} is read and writable" }
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
stream?.close()
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
package no.iktdev.streamit.content.common
|
||||
|
||||
class Naming(val fileName: String) {
|
||||
var cleanedFileName: String
|
||||
private set
|
||||
|
||||
init {
|
||||
cleanedFileName = fileName.apply {
|
||||
removeBracketedText(this)
|
||||
removeParenthesizedText(this)
|
||||
removeResolutionAndTags(this)
|
||||
removeInBetweenCharacters(this)
|
||||
|
||||
removeExtraWhiteSpace(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun guessDesiredFileName(): String {
|
||||
val parts = fileName.split(" - ")
|
||||
return when {
|
||||
parts.size == 2 && parts[1].matches(Regex("\\d{4}")) -> {
|
||||
val title = parts[0]
|
||||
val year = parts[1]
|
||||
"$title ($year)"
|
||||
}
|
||||
|
||||
parts.size >= 3 && parts[1].matches(Regex("S\\d+")) && parts[2].matches(Regex("\\d+[vV]\\d+")) -> {
|
||||
val title = parts[0]
|
||||
val episodeWithRevision = parts[2]
|
||||
val episodeParts = episodeWithRevision.split("v", "V")
|
||||
val episodeNumber = episodeParts[0].toInt()
|
||||
val revisionNumber = episodeParts[1].toInt()
|
||||
val seasonEpisode =
|
||||
"S${episodeNumber.toString().padStart(2, '0')}E${revisionNumber.toString().padStart(2, '0')}"
|
||||
val episodeTitle = if (parts.size > 3) parts[3] else ""
|
||||
"$title - $seasonEpisode - $episodeTitle"
|
||||
}
|
||||
|
||||
else -> fileName
|
||||
}
|
||||
}
|
||||
|
||||
fun guessDesiredTitle(): String {
|
||||
val desiredFileName = guessDesiredFileName()
|
||||
return if (desiredFileName.contains(" - ")) {
|
||||
return desiredFileName.split(" - ").firstOrNull() ?: desiredFileName
|
||||
} else desiredFileName
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the filename contains the keyword movie, if so, default to movie
|
||||
*/
|
||||
fun doesContainMovieKeywords(): Boolean {
|
||||
return getMatch("[(](?<=\\()movie(?=\\))[)]")?.isBlank() ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* @return not null if matches "S01E01"
|
||||
*/
|
||||
fun isSeasonEpisodeDefined(): String? {
|
||||
return getMatch("(?i)S[0-9]+E[0-9]+(?i)")
|
||||
}
|
||||
|
||||
/**
|
||||
* @return not null if matches " 2020 " or ".2020."
|
||||
*/
|
||||
fun isDefinedWithYear(): String? {
|
||||
return getMatch("[ .][0-9]{4}[ .]")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the input value and removes "[Text]"
|
||||
* @param text "[TEST] Dummy - 01 [AZ 1080p] "
|
||||
*/
|
||||
fun removeBracketedText(text: String): String {
|
||||
return Regex("\\[.*?]").replace(text, " ")
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fun removeParenthesizedText(text: String): String {
|
||||
return Regex("\\(.*?\\)").replace(text, " ")
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fun removeResolutionAndTags(text: String): String {
|
||||
return Regex("(.*?)(?=\\d+[pk]\\b)").replace(text, " ")
|
||||
}
|
||||
|
||||
fun removeInBetweenCharacters(text: String): String {
|
||||
return Regex("[.]").replace(text, " ")
|
||||
}
|
||||
|
||||
/**
|
||||
* @param text "example text with extra spaces"
|
||||
* @return example text with extra spaces
|
||||
*/
|
||||
fun removeExtraWhiteSpace(text: String): String {
|
||||
return Regex("\\s{2,}").replace(text, " ")
|
||||
}
|
||||
|
||||
|
||||
private fun getMatch(regex: String): String? {
|
||||
return Regex(regex).find(fileName)?.value ?: return null
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package no.iktdev.streamit.content.common.deamon
|
||||
|
||||
import com.github.pgreze.process.ProcessResult
|
||||
import com.github.pgreze.process.Redirect
|
||||
import com.github.pgreze.process.process
|
||||
import mu.KotlinLogging
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
class Daemon(open val executable: String, val parameters: List<String>, val daemonInterface: IDaemon) {
|
||||
var executor: ProcessResult? = null
|
||||
suspend fun run(): Int {
|
||||
daemonInterface.onStarted()
|
||||
executor = process(executable, *parameters.toTypedArray(),
|
||||
stdout = Redirect.CAPTURE,
|
||||
stderr = Redirect.CAPTURE,
|
||||
consumer = {
|
||||
daemonInterface.onOutputChanged(it)
|
||||
})
|
||||
val resultCode = executor?.resultCode ?: -1
|
||||
if (resultCode == 0) {
|
||||
daemonInterface.onEnded()
|
||||
} else daemonInterface.onError()
|
||||
return resultCode
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package no.iktdev.streamit.content.common.deamon
|
||||
|
||||
interface IDaemon {
|
||||
|
||||
fun onStarted() {}
|
||||
|
||||
fun onOutputChanged(line: String) {}
|
||||
|
||||
fun onEnded() {}
|
||||
|
||||
fun onError()
|
||||
|
||||
}
|
||||
@ -0,0 +1,184 @@
|
||||
package no.iktdev.streamit.content.common.streams
|
||||
|
||||
data class MediaStreams(
|
||||
val streams: List<Stream>
|
||||
)
|
||||
|
||||
sealed class Stream(
|
||||
@Transient open val index: Int,
|
||||
@Transient open val codec_name: String,
|
||||
@Transient open val codec_long_name: String,
|
||||
@Transient open val codec_type: String,
|
||||
@Transient open val codec_tag_string: String,
|
||||
@Transient open val codec_tag: String,
|
||||
@Transient open val r_frame_rate: String,
|
||||
@Transient open val avg_frame_rate: String,
|
||||
@Transient open val time_base: String,
|
||||
@Transient open val start_pts: Long,
|
||||
@Transient open val start_time: String,
|
||||
@Transient open val duration_ts: Long? = null,
|
||||
@Transient open val duration: String? = null,
|
||||
@Transient open val disposition: Disposition,
|
||||
@Transient open val tags: Tags
|
||||
)
|
||||
|
||||
data class VideoStream(
|
||||
override val index: Int,
|
||||
override val codec_name: String,
|
||||
override val codec_long_name: String,
|
||||
override val codec_type: String,
|
||||
override val codec_tag_string: String,
|
||||
override val codec_tag: String,
|
||||
override val r_frame_rate: String,
|
||||
override val avg_frame_rate: String,
|
||||
override val time_base: String,
|
||||
override val start_pts: Long,
|
||||
override val start_time: String,
|
||||
override val disposition: Disposition,
|
||||
override val tags: Tags,
|
||||
override val duration: String?,
|
||||
override val duration_ts: Long?,
|
||||
val profile: String,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val coded_width: Int,
|
||||
val coded_height: Int,
|
||||
val closed_captions: Int,
|
||||
val has_b_frames: Int,
|
||||
val sample_aspect_ratio: String,
|
||||
val display_aspect_ratio: String,
|
||||
val pix_fmt: String,
|
||||
val level: Int,
|
||||
val color_range: String,
|
||||
val color_space: String,
|
||||
val color_transfer: String,
|
||||
val color_primaries: String,
|
||||
val chroma_location: String,
|
||||
val refs: Int
|
||||
) : Stream(
|
||||
index,
|
||||
codec_name,
|
||||
codec_long_name,
|
||||
codec_type,
|
||||
codec_tag_string,
|
||||
codec_tag,
|
||||
r_frame_rate,
|
||||
avg_frame_rate,
|
||||
time_base,
|
||||
start_pts,
|
||||
start_time,
|
||||
duration_ts,
|
||||
duration,
|
||||
disposition,
|
||||
tags
|
||||
)
|
||||
|
||||
data class AudioStream(
|
||||
override val index: Int,
|
||||
override val codec_name: String,
|
||||
override val codec_long_name: String,
|
||||
override val codec_type: String,
|
||||
override val codec_tag_string: String,
|
||||
override val codec_tag: String,
|
||||
override val r_frame_rate: String,
|
||||
override val avg_frame_rate: String,
|
||||
override val time_base: String,
|
||||
override val start_pts: Long,
|
||||
override val start_time: String,
|
||||
override val duration: String?,
|
||||
override val duration_ts: Long?,
|
||||
override val disposition: Disposition,
|
||||
override val tags: Tags,
|
||||
val profile: String,
|
||||
val sample_fmt: String,
|
||||
val sample_rate: String,
|
||||
val channels: Int,
|
||||
val channel_layout: String,
|
||||
val bits_per_sample: Int
|
||||
) : Stream(
|
||||
index,
|
||||
codec_name,
|
||||
codec_long_name,
|
||||
codec_type,
|
||||
codec_tag_string,
|
||||
codec_tag,
|
||||
r_frame_rate,
|
||||
avg_frame_rate,
|
||||
time_base,
|
||||
start_pts,
|
||||
start_time,
|
||||
duration_ts,
|
||||
duration,
|
||||
disposition,
|
||||
tags
|
||||
)
|
||||
|
||||
data class SubtitleStream(
|
||||
override val index: Int,
|
||||
override val codec_name: String,
|
||||
override val codec_long_name: String,
|
||||
override val codec_type: String,
|
||||
override val codec_tag_string: String,
|
||||
override val codec_tag: String,
|
||||
override val r_frame_rate: String,
|
||||
override val avg_frame_rate: String,
|
||||
override val time_base: String,
|
||||
override val start_pts: Long,
|
||||
override val start_time: String,
|
||||
override val duration: String?,
|
||||
override val duration_ts: Long?,
|
||||
override val disposition: Disposition,
|
||||
override val tags: Tags,
|
||||
val subtitle_tags: SubtitleTags
|
||||
) : Stream(
|
||||
index,
|
||||
codec_name,
|
||||
codec_long_name,
|
||||
codec_type,
|
||||
codec_tag_string,
|
||||
codec_tag,
|
||||
r_frame_rate,
|
||||
avg_frame_rate,
|
||||
time_base,
|
||||
start_pts,
|
||||
start_time,
|
||||
duration_ts,
|
||||
duration,
|
||||
disposition,
|
||||
tags
|
||||
)
|
||||
|
||||
data class Disposition(
|
||||
val default: Int,
|
||||
val dub: Int,
|
||||
val original: Int,
|
||||
val comment: Int,
|
||||
val lyrics: Int,
|
||||
val karaoke: Int,
|
||||
val forced: Int,
|
||||
val hearing_impaired: Int,
|
||||
val visual_impaired: Int,
|
||||
val clean_effects: Int,
|
||||
val attached_pic: Int,
|
||||
val timed_thumbnails: Int
|
||||
)
|
||||
|
||||
data class Tags(
|
||||
val title: String?,
|
||||
val BPS: String?,
|
||||
val DURATION: String?,
|
||||
val NUMBER_OF_FRAMES: String?,
|
||||
val NUMBER_OF_BYTES: String?,
|
||||
val _STATISTICS_WRITING_APP: String?,
|
||||
val _STATISTICS_WRITING_DATE_UTC: String?,
|
||||
val _STATISTICS_TAGS: String?,
|
||||
val language: String?,
|
||||
val filename: String?,
|
||||
val mimetype: String?
|
||||
)
|
||||
|
||||
data class SubtitleTags(
|
||||
val language: String?,
|
||||
val filename: String?,
|
||||
val mimetype: String?
|
||||
)
|
||||
@ -0,0 +1,41 @@
|
||||
package no.iktdev.streamit.content.common
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Named
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
class NamingTest {
|
||||
/*
|
||||
@ParameterizedTest
|
||||
@MethodSource("serieOnlyTest")
|
||||
fun ensureOnlySerieAndDecodedCorrectly(testData: TestData) {
|
||||
val naming = Naming(testData.input).getName() ?: throw NullPointerException("Named is null")
|
||||
assertThat(naming.type).isEqualTo("serie")
|
||||
assertThat(naming.season).isEqualTo(testData.expected.season)
|
||||
assertThat(naming.episode).isEqualTo(testData.expected.episode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTest() {
|
||||
val tmp = TestData(Naming.Name(title = "Demo", season = 1, episode = 1, type = "serie"), "[Kametsu] Ghost in the Shell Arise - 05 - Pyrophoric Cult (BD 1080p Hi10 FLAC) [13FF85A7]")
|
||||
val naming = Naming(tmp.input).getName()
|
||||
assertThat(naming).isNotNull()
|
||||
}
|
||||
|
||||
|
||||
fun serieOnlyTest(): List<Named<TestData>> {
|
||||
return listOf(
|
||||
Named.of("Is defined", TestData(Naming.Name(title = "Demo", season = 1, episode = 1, type = "serie"), "Demo - S01E01")),
|
||||
Named.of("Is decoded", TestData(Naming.Name("Demo!", "serie", season = 1, episode = 1), "[TMP] Demo! - 03")),
|
||||
Named.of("Is only Episode", TestData(Naming.Name("Demo", "serie", 1, 1), "Demo E1"))
|
||||
)
|
||||
}*/
|
||||
|
||||
/*
|
||||
data class TestData(
|
||||
val expected: Naming.Name,
|
||||
val input: String
|
||||
)*/
|
||||
}
|
||||
42
Encode/.gitignore
vendored
Normal file
42
Encode/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
19
Encode/build.gradle.kts
Normal file
19
Encode/build.gradle.kts
Normal file
@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.8.21"
|
||||
}
|
||||
|
||||
group = "no.iktdev.streamit.content"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(platform("org.junit:junit-bom:5.9.1"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
BIN
Encode/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
Encode/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
Encode/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
Encode/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Tue Jul 11 02:14:45 CEST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
234
Encode/gradlew
vendored
Normal file
234
Encode/gradlew
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
Encode/gradlew.bat
vendored
Normal file
89
Encode/gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
2
Encode/settings.gradle.kts
Normal file
2
Encode/settings.gradle.kts
Normal file
@ -0,0 +1,2 @@
|
||||
rootProject.name = "Encode"
|
||||
|
||||
42
Reader/.gitignore
vendored
Normal file
42
Reader/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
49
Reader/build.gradle.kts
Normal file
49
Reader/build.gradle.kts
Normal file
@ -0,0 +1,49 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.8.21"
|
||||
id("org.springframework.boot") version "2.5.5"
|
||||
id("io.spring.dependency-management") version "1.0.11.RELEASE"
|
||||
kotlin("plugin.spring") version "1.5.31"
|
||||
}
|
||||
|
||||
group = "no.iktdev.streamit.content"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://jitpack.io")
|
||||
maven {
|
||||
url = uri("https://reposilite.iktdev.no/releases")
|
||||
}
|
||||
maven {
|
||||
url = uri("https://reposilite.iktdev.no/snapshots")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("no.iktdev.streamit.library:streamit-library-kafka:0.0.2-alpha10")
|
||||
implementation("no.iktdev:exfl:0.0.4-SNAPSHOT")
|
||||
|
||||
implementation("com.github.pgreze:kotlin-process:1.3.1")
|
||||
implementation("com.github.vishna:watchservice-ktx:master-SNAPSHOT")
|
||||
implementation("io.github.microutils:kotlin-logging-jvm:2.0.11")
|
||||
|
||||
implementation("com.google.code.gson:gson:2.8.9")
|
||||
implementation("org.json:json:20210307")
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter:2.7.0")
|
||||
implementation("org.springframework.kafka:spring-kafka:2.8.5")
|
||||
|
||||
implementation(project(":CommonCode"))
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.1")
|
||||
testImplementation("org.assertj:assertj-core:3.4.1")
|
||||
|
||||
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
BIN
Reader/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
Reader/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
Reader/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
Reader/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Tue Jul 11 02:16:45 CEST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
234
Reader/gradlew
vendored
Normal file
234
Reader/gradlew
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
Reader/gradlew.bat
vendored
Normal file
89
Reader/gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
5
Reader/settings.gradle.kts
Normal file
5
Reader/settings.gradle.kts
Normal file
@ -0,0 +1,5 @@
|
||||
rootProject.name = "Reader"
|
||||
|
||||
include(":CommonCode")
|
||||
project(":CommonCode").projectDir = File("../CommonCode")
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
package no.iktdev.streamit.content.reader
|
||||
|
||||
import no.iktdev.streamit.content.reader.analyzer.PreferenceReader
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class ReaderApplication
|
||||
|
||||
val preference = PreferenceReader().getPreference()
|
||||
|
||||
fun main(array: Array<String>) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package no.iktdev.streamit.content.reader
|
||||
|
||||
import java.io.File
|
||||
|
||||
class ReaderEnv {
|
||||
companion object {
|
||||
val ffprobe: String = System.getenv("SUPPORTING_EXECUTABLE_FFPROBE") ?: "ffprobe"
|
||||
val encodePreference: String? = System.getenv("ENCODE_PREFERENCE") ?: null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer
|
||||
|
||||
import no.iktdev.streamit.content.common.streams.AudioStream
|
||||
import no.iktdev.streamit.content.common.streams.MediaStreams
|
||||
import no.iktdev.streamit.content.common.streams.SubtitleStream
|
||||
import no.iktdev.streamit.content.common.streams.VideoStream
|
||||
import no.iktdev.streamit.content.reader.analyzer.encoding.AudioEncodeArguments
|
||||
import no.iktdev.streamit.content.reader.analyzer.encoding.EncodeInformation
|
||||
import no.iktdev.streamit.content.reader.analyzer.encoding.SubtitleEncodeArguments
|
||||
import no.iktdev.streamit.content.reader.analyzer.encoding.VideoEncodeArguments
|
||||
import no.iktdev.streamit.content.reader.preference
|
||||
|
||||
class EncodeArgumentSelector(val inputFile: String, val streams: MediaStreams, val outFileName: String) {
|
||||
var defaultSelectedVideo: VideoStream? = getDefaultSelectedVideo()
|
||||
var defaultSelectedAudio: AudioStream? = getDefaultSelectedAudio()
|
||||
|
||||
private fun getAudioStreams() = streams.streams.filterIsInstance<AudioStream>()
|
||||
private fun getVideoStreams() = streams.streams.filterIsInstance<VideoStream>()
|
||||
|
||||
|
||||
private fun getDefaultSelectedVideo(): VideoStream? {
|
||||
return getVideoStreams().filter { (it.duration_ts ?: 0) > 0 }.maxByOrNull { it.duration_ts!! } ?: getVideoStreams().minByOrNull { it.index }
|
||||
}
|
||||
|
||||
private fun getDefaultSelectedAudio(): AudioStream? {
|
||||
return getAudioStreams().filter { (it.duration_ts ?: 0) > 0 }.maxByOrNull { it.duration_ts!! } ?: getAudioStreams().minByOrNull { it.index }
|
||||
}
|
||||
|
||||
/**
|
||||
* @return VideoStream based on preference or defaultSelectedVideo
|
||||
*/
|
||||
/*private fun getSelectedVideoBasedOnPreference(): VideoStream {
|
||||
val
|
||||
}*/
|
||||
|
||||
/**
|
||||
* @return AudioStrem based on preference or defaultSelectedAudio
|
||||
*/
|
||||
private fun getSelectedAudioBasedOnPreference(): AudioStream? {
|
||||
val languageFiltered = getAudioStreams().filter { it.tags.language == preference.audio.language }
|
||||
val channeledAndCodec = languageFiltered.find { it.channels >= (preference.audio.channels ?: 2) && it.codec_name == preference.audio.codec.lowercase() }
|
||||
return channeledAndCodec ?: return languageFiltered.minByOrNull { it.index } ?: defaultSelectedAudio
|
||||
}
|
||||
|
||||
|
||||
fun getVideoAndAudioArguments(): EncodeInformation? {
|
||||
val selectedVideo = defaultSelectedVideo
|
||||
val selectedAudio = getSelectedAudioBasedOnPreference() ?: defaultSelectedAudio
|
||||
return if (selectedVideo == null || selectedAudio == null) return null
|
||||
else {
|
||||
EncodeInformation(
|
||||
inputFile = inputFile,
|
||||
outFileName = "$outFileName.mp4",
|
||||
language = selectedAudio.tags.language ?: "eng",
|
||||
arguments = VideoEncodeArguments(selectedVideo).getVideoArguments() +
|
||||
AudioEncodeArguments(selectedAudio).getAudioArguments()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSubtitleArguments(): List<EncodeInformation> {
|
||||
return streams.streams.filterIsInstance<SubtitleStream>().map {
|
||||
val subArgs = SubtitleEncodeArguments(it)
|
||||
EncodeInformation(
|
||||
inputFile = inputFile,
|
||||
outFileName = "$outFileName.${subArgs.getFormatToCodec()}",
|
||||
language = it.tags.language ?: "eng",
|
||||
arguments = subArgs.getSubtitleArguments()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import no.iktdev.streamit.content.common.streams.*
|
||||
import no.iktdev.streamit.content.reader.fileWatcher.FileWatcher
|
||||
import no.iktdev.streamit.library.kafka.KnownEvents
|
||||
import no.iktdev.streamit.library.kafka.Message
|
||||
import no.iktdev.streamit.library.kafka.StatusType
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
import java.io.File
|
||||
|
||||
class EncodeStreamsMessageParser {
|
||||
fun getFileNameFromEvent(records: MutableList<ConsumerRecord<String, Message>>): FileWatcher.FileResult? {
|
||||
val file = records.find { it.key() == KnownEvents.EVENT_READER_RECEIVED_FILE.event } ?: return null
|
||||
if (file.value().status.statusType != StatusType.SUCCESS) return null
|
||||
return if (file.value().data is String) {
|
||||
return Gson().fromJson(file.value().data as String, FileWatcher.FileResult::class.java)
|
||||
} else null
|
||||
}
|
||||
|
||||
fun getMediaStreamsFromEvent(records: MutableList<ConsumerRecord<String, Message>>): MediaStreams? {
|
||||
val streams = records.find { it.key() == KnownEvents.EVENT_READER_RECEIVED_STREAMS.event } ?: return null
|
||||
if (streams.value().status.statusType != StatusType.SUCCESS || streams.value().data !is String) return null
|
||||
val json = streams.value().data as String
|
||||
val gson = Gson()
|
||||
/*return gson.fromJson(streams.value().data as String, MediaStreams::class.java)*/
|
||||
|
||||
val jsonObject = gson.fromJson(json, JsonObject::class.java)
|
||||
|
||||
val streamsJsonArray = jsonObject.getAsJsonArray("streams")
|
||||
|
||||
val rstreams = streamsJsonArray.mapNotNull { streamJson ->
|
||||
val streamObject = streamJson.asJsonObject
|
||||
|
||||
val codecType = streamObject.get("codec_type").asString
|
||||
if (streamObject.get("codec_name").asString == "mjpeg") {
|
||||
null
|
||||
} else {
|
||||
when (codecType) {
|
||||
"video" -> gson.fromJson(streamObject, VideoStream::class.java)
|
||||
"audio" -> gson.fromJson(streamObject, AudioStream::class.java)
|
||||
"subtitle" -> gson.fromJson(streamObject, SubtitleStream::class.java)
|
||||
else -> null //throw IllegalArgumentException("Unknown stream type: $codecType")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MediaStreams(rstreams)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer
|
||||
|
||||
import com.google.gson.Gson
|
||||
import no.iktdev.streamit.content.common.CommonConfig
|
||||
import no.iktdev.streamit.content.common.streams.MediaStreams
|
||||
import no.iktdev.streamit.content.reader.analyzer.encoding.EncodeInformation
|
||||
import no.iktdev.streamit.content.reader.fileWatcher.FileWatcher
|
||||
import no.iktdev.streamit.library.kafka.KnownEvents
|
||||
import no.iktdev.streamit.library.kafka.Message
|
||||
import no.iktdev.streamit.library.kafka.Status
|
||||
import no.iktdev.streamit.library.kafka.StatusType
|
||||
import no.iktdev.streamit.library.kafka.consumers.DefaultConsumer
|
||||
import no.iktdev.streamit.library.kafka.listener.pooled.IPooledEvents
|
||||
import no.iktdev.streamit.library.kafka.listener.pooled.PooledEventMessageListener
|
||||
import no.iktdev.streamit.library.kafka.producer.DefaultProducer
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
import org.springframework.stereotype.Service
|
||||
import java.io.File
|
||||
|
||||
@Service
|
||||
class EncodeStreamsProducer: IPooledEvents.OnEventsReceived {
|
||||
|
||||
val messageProducer = DefaultProducer(CommonConfig.kafkaConsumerId)
|
||||
|
||||
val defaultConsumer = DefaultConsumer().apply {
|
||||
autoCommit = false
|
||||
}
|
||||
|
||||
init {
|
||||
val ackListener = PooledEventMessageListener(
|
||||
topic = CommonConfig.kafkaConsumerId, consumer = defaultConsumer,
|
||||
mainFilter = KnownEvents.EVENT_READER_RECEIVED_FILE.event,
|
||||
subFilter = listOf(KnownEvents.EVENT_READER_RECEIVED_STREAMS.event),
|
||||
event = this
|
||||
)
|
||||
ackListener.listen()
|
||||
}
|
||||
|
||||
override fun areAllMessagesReceived(recordedEvents: MutableMap<String, StatusType>): Boolean {
|
||||
val expected = listOf(KnownEvents.EVENT_READER_RECEIVED_FILE.event, KnownEvents.EVENT_READER_RECEIVED_STREAMS.event)
|
||||
return expected.containsAll(recordedEvents.keys)
|
||||
}
|
||||
|
||||
private fun produceErrorMessage(referenceId: String, reason: String) {
|
||||
val message = Message(referenceId = referenceId,
|
||||
Status(statusType = StatusType.ERROR, errorMessage = reason)
|
||||
)
|
||||
messageProducer.sendMessage(KnownEvents.EVENT_READER_ENCODE_GENERATED.event, message)
|
||||
}
|
||||
|
||||
private fun produceEncodeMessage(referenceId: String, data: EncodeInformation?) {
|
||||
val message = Message(referenceId = referenceId,
|
||||
Status(statusType = if (data != null) StatusType.SUCCESS else StatusType.IGNORED),
|
||||
data = data
|
||||
)
|
||||
messageProducer.sendMessage(KnownEvents.EVENT_READER_ENCODE_GENERATED.event, message)
|
||||
}
|
||||
|
||||
override fun onAllEventsConsumed(referenceId: String, records: MutableList<ConsumerRecord<String, Message>>) {
|
||||
val parser = EncodeStreamsMessageParser()
|
||||
val fileResult = parser.getFileNameFromEvent(records)
|
||||
if (fileResult == null) {
|
||||
produceErrorMessage(referenceId, "FileResult is either null or not deserializable!")
|
||||
return
|
||||
}
|
||||
val outFileName = fileResult.desiredNewName.ifBlank { File(fileResult.file).nameWithoutExtension }
|
||||
val streams = parser.getMediaStreamsFromEvent(records)
|
||||
if (streams == null) {
|
||||
produceErrorMessage(referenceId, "No streams received!")
|
||||
return
|
||||
}
|
||||
|
||||
val encodeInformation = EncodeArgumentSelector(inputFile = fileResult.file, streams = streams, outFileName = outFileName)
|
||||
produceEncodeMessage(referenceId, encodeInformation.getVideoAndAudioArguments())
|
||||
encodeInformation.getSubtitleArguments().forEach { s ->
|
||||
produceEncodeMessage(referenceId, s)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer
|
||||
|
||||
import com.google.gson.Gson
|
||||
import no.iktdev.streamit.content.reader.ReaderEnv
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
data class EncodingPreference(
|
||||
val video: VideoPreference,
|
||||
val audio: AudioPreference
|
||||
)
|
||||
|
||||
data class VideoPreference(
|
||||
val codec: String = "h264",
|
||||
val pixelFormat: String = "yuv420p",
|
||||
val pixelFormatPassthrough: List<String> = listOf<String>("yuv420p", "yuv420p10le"),
|
||||
val threshold: Int = 16
|
||||
)
|
||||
|
||||
data class AudioPreference(
|
||||
val codec: String = "aac",
|
||||
val sample_rate: Int? = null,
|
||||
val channels: Int? = null,
|
||||
val language: String = "eng", //ISO3 format
|
||||
val preserveChannels: Boolean = true,
|
||||
val defaultToEAC3OnSurroundDetected: Boolean = true,
|
||||
val forceStereo: Boolean = false
|
||||
)
|
||||
|
||||
|
||||
class PreferenceReader {
|
||||
fun getPreference(): EncodingPreference {
|
||||
val defaultPreference = EncodingPreference(video = VideoPreference(), audio = AudioPreference())
|
||||
val preferenceText = readPreference() ?: return defaultPreference
|
||||
val configured = deserialize(preferenceText)
|
||||
|
||||
printConfiguration("Audio", "Codec", configured?.audio?.codec, defaultPreference.audio.codec)
|
||||
printConfiguration("Audio", "Language", configured?.audio?.language, defaultPreference.audio.language)
|
||||
printConfiguration("Audio", "Channels", configured?.audio?.channels.toString(), defaultPreference.audio.channels.toString())
|
||||
printConfiguration("Audio", "Sample rate", configured?.audio?.sample_rate.toString(), defaultPreference.audio.sample_rate.toString())
|
||||
printConfiguration("Audio", "Override to EAC3 for surround", configured?.audio?.defaultToEAC3OnSurroundDetected.toString(), defaultPreference.audio.defaultToEAC3OnSurroundDetected.toString())
|
||||
|
||||
|
||||
printConfiguration("Video", "Codec", configured?.video?.codec, defaultPreference.video.codec)
|
||||
printConfiguration("Video", "Pixel format", configured?.video?.pixelFormat, defaultPreference.video.pixelFormat)
|
||||
printConfiguration("Video", "Threshold", configured?.video?.threshold.toString(), defaultPreference.video.threshold.toString())
|
||||
|
||||
|
||||
return configured ?: defaultPreference
|
||||
}
|
||||
|
||||
fun printConfiguration(sourceType: String, key: String, value: String?, default: String?) {
|
||||
val usedValue = if (!value.isNullOrEmpty()) value else if (!default.isNullOrEmpty()) "$default (default)" else "no changes will be made"
|
||||
LoggerFactory.getLogger(javaClass.simpleName).info("$sourceType: $key => $usedValue")
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun readPreference(): String? {
|
||||
val prefFile = File(ReaderEnv.encodePreference)
|
||||
if (!prefFile.exists()) {
|
||||
LoggerFactory.getLogger(javaClass.simpleName).info("Preference file: ${prefFile.absolutePath} does not exists...")
|
||||
LoggerFactory.getLogger(javaClass.simpleName).info("Using default configuration")
|
||||
return null
|
||||
}
|
||||
else {
|
||||
LoggerFactory.getLogger(javaClass.simpleName).info("Preference file: ${prefFile.absolutePath} found")
|
||||
}
|
||||
|
||||
try {
|
||||
val instr = prefFile.inputStream()
|
||||
return instr.bufferedReader().use { it.readText() }
|
||||
}
|
||||
catch (e: Exception) {
|
||||
LoggerFactory.getLogger(javaClass.simpleName).error("Failed to read preference file: ${prefFile.absolutePath}.. Will use default configuration")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun deserialize(value: String?): EncodingPreference? {
|
||||
value ?: return null
|
||||
return Gson().fromJson(value, EncodingPreference::class.java) ?: null
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer.encoding
|
||||
|
||||
import no.iktdev.streamit.content.common.streams.AudioStream
|
||||
import no.iktdev.streamit.content.reader.preference
|
||||
|
||||
class AudioEncodeArguments(val audio: AudioStream) {
|
||||
|
||||
fun isAudioCodecEqual() = audio.codec_name.lowercase() == preference.audio.codec.lowercase()
|
||||
|
||||
fun shouldUseEAC3(): Boolean {
|
||||
return (preference.audio.defaultToEAC3OnSurroundDetected && audio.channels > 2 && audio.codec_name.lowercase() != "eac3")
|
||||
}
|
||||
|
||||
fun getAudioArguments(): MutableList<String> {
|
||||
val result = mutableListOf<String>()
|
||||
if (shouldUseEAC3()) {
|
||||
result.addAll(listOf("-c:a", "eac3"))
|
||||
} else if (!isAudioCodecEqual()) {
|
||||
result.addAll(listOf("-c:a", preference.audio.codec))
|
||||
} else result.addAll(listOf("-acodec", "copy"))
|
||||
result.addAll(listOf("-map", "0:a:${audio.index}"))
|
||||
return result
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer.encoding
|
||||
|
||||
data class EncodeInformation(
|
||||
val inputFile: String,
|
||||
val outFileName: String,
|
||||
val language: String,
|
||||
val arguments: List<String>
|
||||
)
|
||||
@ -0,0 +1,25 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer.encoding
|
||||
|
||||
import no.iktdev.streamit.content.common.streams.SubtitleStream
|
||||
|
||||
class SubtitleEncodeArguments(val subtitle: SubtitleStream) {
|
||||
|
||||
fun getSubtitleArguments(): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
result.addAll(listOf("-c:s", "copy"))
|
||||
result.addAll(listOf("-map", "0:s:${subtitle.index}"))
|
||||
return result
|
||||
}
|
||||
|
||||
fun getFormatToCodec(): String? {
|
||||
return when(subtitle.codec_name) {
|
||||
"ass" -> "ass"
|
||||
"subrip" -> "srt"
|
||||
"webvtt", "vtt" -> "vtt"
|
||||
"smi" -> "smi"
|
||||
"hdmv_pgs_subtitle" -> null
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer.encoding
|
||||
|
||||
import no.iktdev.streamit.content.common.streams.AudioStream
|
||||
import no.iktdev.streamit.content.common.streams.VideoStream
|
||||
import no.iktdev.streamit.content.reader.preference
|
||||
|
||||
class VideoEncodeArguments(val video: VideoStream) {
|
||||
|
||||
fun isVideoCodecEqual() = video.codec_name == getCorrectCodec()
|
||||
|
||||
|
||||
fun getVideoArguments(): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
if (isVideoCodecEqual()) result.addAll(listOf(
|
||||
"-vcodec", "copy"
|
||||
)) else {
|
||||
result.addAll(listOf("-c:v", getCorrectCodec()))
|
||||
result.addAll(listOf("-crf", preference.video.threshold.toString()))
|
||||
}
|
||||
if (preference.video.pixelFormatPassthrough.none { it == video.pix_fmt }) {
|
||||
result.addAll(listOf("-pix_fmt", preference.video.pixelFormat))
|
||||
}
|
||||
result.addAll(listOf("-map", "0:v:${video.index}"))
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
protected fun getCorrectCodec(): String {
|
||||
return when(preference.video.codec.lowercase()) {
|
||||
"hevc" -> "libx265"
|
||||
"h265" -> "libx265"
|
||||
"h.265" -> "libx265"
|
||||
|
||||
"h.264" -> "libx264"
|
||||
"h264" -> "libx264"
|
||||
|
||||
else -> preference.video.codec.lowercase()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package no.iktdev.streamit.content.reader.fileWatcher
|
||||
|
||||
import dev.vishna.watchservice.KWatchEvent
|
||||
import dev.vishna.watchservice.asWatchChannel
|
||||
import kotlinx.coroutines.channels.consumeEach
|
||||
import kotlinx.coroutines.launch
|
||||
import mu.KotlinLogging
|
||||
import no.iktdev.exfl.coroutines.Coroutines
|
||||
import no.iktdev.streamit.content.common.CommonConfig
|
||||
import no.iktdev.streamit.content.common.Naming
|
||||
|
||||
import no.iktdev.streamit.content.reader.ReaderEnv
|
||||
import no.iktdev.streamit.library.kafka.KnownEvents
|
||||
import no.iktdev.streamit.library.kafka.Message
|
||||
import no.iktdev.streamit.library.kafka.Status
|
||||
import no.iktdev.streamit.library.kafka.StatusType
|
||||
import no.iktdev.streamit.library.kafka.producer.DefaultProducer
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
@Service
|
||||
class FileWatcher: FileWatcherEvents {
|
||||
val messageProducer = DefaultProducer(CommonConfig.kafkaConsumerId)
|
||||
|
||||
val queue = FileWatcherQueue()
|
||||
|
||||
|
||||
val watcherChannel = CommonConfig.incomingContent?.asWatchChannel()
|
||||
init {
|
||||
Coroutines.io().launch {
|
||||
if (watcherChannel == null) {
|
||||
logger.error { "Can't start watcherChannel on null!" }
|
||||
}
|
||||
watcherChannel?.consumeEach {
|
||||
when (it.kind) {
|
||||
KWatchEvent.Kind.Deleted -> {
|
||||
queue.removeFromQueue(it.file, this@FileWatcher::onFileRemoved)
|
||||
}
|
||||
KWatchEvent.Kind.Created, KWatchEvent.Kind.Initialized -> {
|
||||
queue.addToQueue(it.file, this@FileWatcher::onFilePending, this@FileWatcher::onFileAvailable)
|
||||
}
|
||||
else -> {
|
||||
logger.info { "Ignoring event kind: ${it.kind.name} for file ${it.file.name}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onFileAvailable(file: PendingFile) {
|
||||
val naming = Naming(file.file.nameWithoutExtension)
|
||||
val message = Message(
|
||||
referenceId = file.id,
|
||||
status = Status(StatusType.SUCCESS),
|
||||
data = FileResult(file = file.file.absolutePath, title = naming.guessDesiredTitle(), desiredNewName = naming.guessDesiredFileName())
|
||||
)
|
||||
messageProducer.sendMessage(KnownEvents.EVENT_READER_RECEIVED_FILE.event, message)
|
||||
}
|
||||
|
||||
override fun onFilePending(file: PendingFile) {
|
||||
val message = Message(
|
||||
status = Status(StatusType.PENDING),
|
||||
data = FileResult(file = file.file.absolutePath)
|
||||
)
|
||||
messageProducer.sendMessage(KnownEvents.EVENT_READER_RECEIVED_FILE.event , message)
|
||||
}
|
||||
|
||||
override fun onFileFailed(file: PendingFile) {
|
||||
val message = Message(
|
||||
status = Status(StatusType.ERROR),
|
||||
data = file.file.absolutePath
|
||||
)
|
||||
messageProducer.sendMessage(KnownEvents.EVENT_READER_RECEIVED_FILE.event , message)
|
||||
}
|
||||
|
||||
override fun onFileRemoved(file: PendingFile) {
|
||||
val message = Message(
|
||||
status = Status(StatusType.IGNORED),
|
||||
data = file.file.absolutePath
|
||||
)
|
||||
messageProducer.sendMessage(KnownEvents.EVENT_READER_RECEIVED_FILE.event , message)
|
||||
}
|
||||
|
||||
data class FileResult(
|
||||
val file: String,
|
||||
val title: String = "",
|
||||
val desiredNewName: String = ""
|
||||
)
|
||||
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package no.iktdev.streamit.content.reader.fileWatcher
|
||||
|
||||
import java.io.File
|
||||
|
||||
interface FileWatcherEvents {
|
||||
fun onFileAvailable(file: PendingFile)
|
||||
|
||||
/**
|
||||
* If the file is being copied or incomplete, or in case a process currently owns the file, pending should be issued
|
||||
*/
|
||||
fun onFilePending(file: PendingFile)
|
||||
|
||||
/**
|
||||
* If the file is either removed or is not a valid file
|
||||
*/
|
||||
fun onFileFailed(file: PendingFile)
|
||||
|
||||
|
||||
fun onFileRemoved(file: PendingFile)
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package no.iktdev.streamit.content.reader.fileWatcher
|
||||
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import no.iktdev.exfl.coroutines.Coroutines
|
||||
import no.iktdev.streamit.content.common.FileAccess
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
data class PendingFile(val id: String = UUID.randomUUID().toString(), val file: File, var time: Long = 0)
|
||||
class FileWatcherQueue {
|
||||
private val fileChannel = Channel<PendingFile>()
|
||||
|
||||
fun addToQueue(file: File, onFilePending: (PendingFile) -> Unit, onFileAccessible: (PendingFile) -> Unit) {
|
||||
// Check if the file is accessible
|
||||
if (FileAccess.isFileAvailable(file)) {
|
||||
// If accessible, run the function immediately and return
|
||||
onFileAccessible(PendingFile(file = file))
|
||||
return
|
||||
}
|
||||
|
||||
// Add the file to the channel for processing
|
||||
fileChannel.trySend(PendingFile(file = file))
|
||||
|
||||
// Coroutine to process the file and remove it from the queue when accessible
|
||||
Coroutines.default().launch {
|
||||
while (true) {
|
||||
delay(500)
|
||||
val currentFile = fileChannel.receive()
|
||||
if (FileAccess.isFileAvailable(currentFile.file)) {
|
||||
onFileAccessible(currentFile)
|
||||
// File is accessible, remove it from the queue
|
||||
removeFromQueue(currentFile.file) { /* Do nothing here as the operation is not intended to be performed here */ }
|
||||
} else {
|
||||
// File is not accessible, put it back in the channel for later processing
|
||||
fileChannel.send(currentFile.apply { time += 500 })
|
||||
onFilePending(currentFile)
|
||||
}
|
||||
}
|
||||
} // https://chat.openai.com/share/f3c8f6ea-603a-40d6-a811-f8fea5067501
|
||||
}
|
||||
|
||||
fun removeFromQueue(file: File, onFileRemoved: (PendingFile) -> Unit) {
|
||||
val removedFile = fileChannel.findAndRemove { it.file == file }
|
||||
removedFile?.let {
|
||||
onFileRemoved(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Extension function to find and remove an element from the channel
|
||||
fun <T> Channel<T>.findAndRemove(predicate: (T) -> Boolean): T? {
|
||||
val items = mutableListOf<T>()
|
||||
while (true) {
|
||||
val item = poll() ?: break
|
||||
if (predicate(item)) {
|
||||
return item
|
||||
}
|
||||
items.add(item)
|
||||
}
|
||||
for (item in items) {
|
||||
offer(item)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package no.iktdev.streamit.content.reader.streams
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import mu.KotlinLogging
|
||||
import no.iktdev.streamit.content.common.CommonConfig
|
||||
import no.iktdev.streamit.content.common.deamon.Daemon
|
||||
import no.iktdev.streamit.content.common.deamon.IDaemon
|
||||
import no.iktdev.streamit.content.reader.ReaderEnv
|
||||
import no.iktdev.streamit.library.kafka.KnownEvents
|
||||
import no.iktdev.streamit.library.kafka.KnownEvents.EVENT_READER_RECEIVED_FILE
|
||||
import no.iktdev.streamit.library.kafka.Message
|
||||
import no.iktdev.streamit.library.kafka.Status
|
||||
import no.iktdev.streamit.library.kafka.StatusType
|
||||
import no.iktdev.streamit.library.kafka.consumers.DefaultConsumer
|
||||
import no.iktdev.streamit.library.kafka.listener.EventMessageListener
|
||||
import no.iktdev.streamit.library.kafka.producer.DefaultProducer
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
@Service
|
||||
class StreamsReader {
|
||||
|
||||
val messageProducer = DefaultProducer(CommonConfig.kafkaConsumerId)
|
||||
val defaultConsumer = DefaultConsumer().apply {
|
||||
// autoCommit = false
|
||||
}
|
||||
init {
|
||||
object: EventMessageListener(CommonConfig.kafkaConsumerId, defaultConsumer, listOf(EVENT_READER_RECEIVED_FILE.event)) {
|
||||
override fun onMessage(data: ConsumerRecord<String, Message>) {
|
||||
if (data.value().status.statusType != StatusType.SUCCESS) {
|
||||
logger.info { "Ignoring event: ${data.key()} as status is not Success!" }
|
||||
return
|
||||
} else if (data.value().data !is String) {
|
||||
logger.info { "Ignoring event: ${data.key()} as values is not of expected type!" }
|
||||
return
|
||||
}
|
||||
logger.info { "Preparing Probe for ${data.value().data}" }
|
||||
val output = mutableListOf<String>()
|
||||
val d = Daemon(executable = ReaderEnv.ffprobe, parameters = listOf("-v", "quiet", "-print_format", "json", "-show_streams", data.value().data as String), daemonInterface = object:
|
||||
IDaemon {
|
||||
override fun onOutputChanged(line: String) {
|
||||
output.add(line)
|
||||
}
|
||||
|
||||
override fun onStarted() {
|
||||
logger.info { "Probe started for ${data.value().data}" }
|
||||
}
|
||||
|
||||
override fun onError() {
|
||||
logger.error { "An error occurred for ${data.value().data}" }
|
||||
}
|
||||
|
||||
override fun onEnded() {
|
||||
logger.info { "Probe ended for ${data.value().data}" }
|
||||
}
|
||||
|
||||
})
|
||||
val resultCode = runBlocking {
|
||||
d.run()
|
||||
}
|
||||
|
||||
val message = Message(status = Status( statusType = if (resultCode == 0) StatusType.SUCCESS else StatusType.ERROR), data = output.joinToString("\n"))
|
||||
messageProducer.sendMessage(KnownEvents.EVENT_READER_RECEIVED_STREAMS.event, message)
|
||||
}
|
||||
}.listen()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package no.iktdev.streamit.content.reader
|
||||
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
|
||||
open class Resources {
|
||||
|
||||
fun getText(path: String): String? {
|
||||
return this.javaClass.classLoader.getResource(path)?.readText()
|
||||
}
|
||||
|
||||
open class Streams(): Resources() {
|
||||
fun all(): List<String> {
|
||||
return listOf<String>(
|
||||
getSample(0),
|
||||
getSample(1),
|
||||
getSample(2),
|
||||
getSample(3),
|
||||
getSample(4),
|
||||
getSample(5),
|
||||
getSample(6),
|
||||
)
|
||||
}
|
||||
|
||||
fun getSample(number: Int): String {
|
||||
return getText("streams/sample$number.json")!!
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any?> getConsumerRecord(event: String, data: T): ConsumerRecord<String, T> {
|
||||
return ConsumerRecord("testTopic", 0, 0L, event, data)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package no.iktdev.streamit.content.reader.analyzer
|
||||
|
||||
import no.iktdev.streamit.content.reader.Resources
|
||||
import no.iktdev.streamit.library.kafka.KnownEvents
|
||||
import no.iktdev.streamit.library.kafka.Message
|
||||
import no.iktdev.streamit.library.kafka.Status
|
||||
import no.iktdev.streamit.library.kafka.StatusType
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
|
||||
class EncodeStreamsMessageParserTest {
|
||||
val parser = EncodeStreamsMessageParser()
|
||||
val baseEvent = Message(status = Status( statusType = StatusType.SUCCESS))
|
||||
|
||||
@Test
|
||||
fun getFileNameFromEvent() {
|
||||
val payload = Resources.Streams().getSample(3)
|
||||
assertDoesNotThrow {
|
||||
val msg = baseEvent.copy(data = payload)
|
||||
val result = parser.getMediaStreamsFromEvent(mutableListOf(
|
||||
Resources().getConsumerRecord(
|
||||
KnownEvents.EVENT_READER_RECEIVED_STREAMS.event,
|
||||
msg
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getMediaStreamsFromEvent() {
|
||||
}
|
||||
}
|
||||
97
Reader/src/test/resources/streams/sample1.json
Normal file
97
Reader/src/test/resources/streams/sample1.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"streams": [
|
||||
{
|
||||
"index": 0,
|
||||
"codec_name": "hevc",
|
||||
"codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
|
||||
"profile": "Main 10",
|
||||
"codec_type": "video",
|
||||
"codec_time_base": "1/25",
|
||||
"codec_tag_string": "hev1",
|
||||
"codec_tag": "0x31766568",
|
||||
"width": 1920,
|
||||
"height": 960,
|
||||
"coded_width": 1920,
|
||||
"coded_height": 960,
|
||||
"has_b_frames": 2,
|
||||
"sample_aspect_ratio": "1:1",
|
||||
"display_aspect_ratio": "2:1",
|
||||
"pix_fmt": "yuv420p10le",
|
||||
"level": 120,
|
||||
"color_range": "tv",
|
||||
"refs": 1,
|
||||
"r_frame_rate": "25/1",
|
||||
"avg_frame_rate": "25/1",
|
||||
"time_base": "1/25000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 70902000,
|
||||
"duration": "2836.080000",
|
||||
"bit_rate": "1999184",
|
||||
"nb_frames": "70902",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"creation_time": "2022-01-04T07:01:48.000000Z",
|
||||
"language": "und",
|
||||
"handler_name": "VideoHandler"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"codec_name": "aac",
|
||||
"codec_long_name": "AAC (Advanced Audio Coding)",
|
||||
"profile": "LC",
|
||||
"codec_type": "audio",
|
||||
"codec_time_base": "1/48000",
|
||||
"codec_tag_string": "mp4a",
|
||||
"codec_tag": "0x6134706d",
|
||||
"sample_fmt": "fltp",
|
||||
"sample_rate": "48000",
|
||||
"channels": 6,
|
||||
"channel_layout": "5.1",
|
||||
"bits_per_sample": 0,
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/48000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 136131024,
|
||||
"duration": "2836.063000",
|
||||
"bit_rate": "224000",
|
||||
"max_bit_rate": "224000",
|
||||
"nb_frames": "132943",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"creation_time": "2022-01-04T07:01:48.000000Z",
|
||||
"language": "nor",
|
||||
"handler_name": "SoundHandler"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
118
Reader/src/test/resources/streams/sample2.json
Normal file
118
Reader/src/test/resources/streams/sample2.json
Normal file
@ -0,0 +1,118 @@
|
||||
{
|
||||
"streams": [
|
||||
{
|
||||
"index": 0,
|
||||
"codec_name": "hevc",
|
||||
"codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
|
||||
"profile": "Main 10",
|
||||
"codec_type": "video",
|
||||
"codec_time_base": "1/24",
|
||||
"codec_tag_string": "hev1",
|
||||
"codec_tag": "0x31766568",
|
||||
"width": 1920,
|
||||
"height": 960,
|
||||
"coded_width": 1920,
|
||||
"coded_height": 960,
|
||||
"has_b_frames": 2,
|
||||
"sample_aspect_ratio": "1:1",
|
||||
"display_aspect_ratio": "2:1",
|
||||
"pix_fmt": "yuv420p10le",
|
||||
"level": 120,
|
||||
"color_range": "tv",
|
||||
"refs": 1,
|
||||
"r_frame_rate": "24/1",
|
||||
"avg_frame_rate": "24/1",
|
||||
"time_base": "1/24000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 58857000,
|
||||
"duration": "2452.375000",
|
||||
"bit_rate": "1999262",
|
||||
"nb_frames": "58857",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"creation_time": "2021-12-03T08:59:16.000000Z",
|
||||
"language": "und",
|
||||
"handler_name": "VideoHandler"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"codec_name": "aac",
|
||||
"codec_long_name": "AAC (Advanced Audio Coding)",
|
||||
"profile": "LC",
|
||||
"codec_type": "audio",
|
||||
"codec_time_base": "1/48000",
|
||||
"codec_tag_string": "mp4a",
|
||||
"codec_tag": "0x6134706d",
|
||||
"sample_fmt": "fltp",
|
||||
"sample_rate": "48000",
|
||||
"channels": 6,
|
||||
"channel_layout": "5.1",
|
||||
"bits_per_sample": 0,
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/48000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 117714384,
|
||||
"duration": "2452.383000",
|
||||
"bit_rate": "224003",
|
||||
"max_bit_rate": "224003",
|
||||
"nb_frames": "114958",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"creation_time": "2021-12-03T08:59:16.000000Z",
|
||||
"language": "eng",
|
||||
"handler_name": "SoundHandler"
|
||||
}
|
||||
}
|
||||
],
|
||||
"format": {
|
||||
"filename": "Alex.Rider.S02E01.1080p.WEBRip.x265-RARBG.mp4",
|
||||
"nb_streams": 2,
|
||||
"nb_programs": 0,
|
||||
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
|
||||
"format_long_name": "QuickTime / MOV",
|
||||
"start_time": "0.000000",
|
||||
"duration": "2452.426000",
|
||||
"size": "683226674",
|
||||
"bit_rate": "2228737",
|
||||
"probe_score": 100,
|
||||
"tags": {
|
||||
"major_brand": "isom",
|
||||
"minor_version": "512",
|
||||
"compatible_brands": "isomiso2mp41",
|
||||
"creation_time": "2021-12-03T08:59:16.000000Z",
|
||||
"title": "Alex.Rider.S02E01.1080p.WEBRip.x265-RARBG",
|
||||
"encoder": "Lavf58.20.100",
|
||||
"comment": "Alex.Rider.S02E01.1080p.WEBRip.x265-RARBG"
|
||||
}
|
||||
}
|
||||
}
|
||||
550
Reader/src/test/resources/streams/sample3.json
Normal file
550
Reader/src/test/resources/streams/sample3.json
Normal file
@ -0,0 +1,550 @@
|
||||
{
|
||||
"streams": [
|
||||
{
|
||||
"index": 0,
|
||||
"codec_name": "hevc",
|
||||
"codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
|
||||
"profile": "Main 10",
|
||||
"codec_type": "video",
|
||||
"codec_time_base": "1001/24000",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"width": 1920,
|
||||
"height": 804,
|
||||
"coded_width": 1920,
|
||||
"coded_height": 808,
|
||||
"has_b_frames": 2,
|
||||
"sample_aspect_ratio": "1:1",
|
||||
"display_aspect_ratio": "160:67",
|
||||
"pix_fmt": "yuv420p10le",
|
||||
"level": 123,
|
||||
"color_range": "tv",
|
||||
"color_space": "bt709",
|
||||
"color_transfer": "bt709",
|
||||
"color_primaries": "bt709",
|
||||
"refs": 1,
|
||||
"r_frame_rate": "24000/1001",
|
||||
"avg_frame_rate": "24000/1001",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"title": "Presented By EMBER",
|
||||
"BPS": "3796879",
|
||||
"DURATION": "02:01:28.782000000",
|
||||
"NUMBER_OF_FRAMES": "174756",
|
||||
"NUMBER_OF_BYTES": "3459328516",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-05-24 07:43:44",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"codec_name": "ac3",
|
||||
"codec_long_name": "ATSC A/52A (AC-3)",
|
||||
"codec_type": "audio",
|
||||
"codec_time_base": "1/48000",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"sample_fmt": "fltp",
|
||||
"sample_rate": "48000",
|
||||
"channels": 6,
|
||||
"channel_layout": "5.1(side)",
|
||||
"bits_per_sample": 0,
|
||||
"dmix_mode": "-1",
|
||||
"ltrt_cmixlev": "-1.000000",
|
||||
"ltrt_surmixlev": "-1.000000",
|
||||
"loro_cmixlev": "-1.000000",
|
||||
"loro_surmixlev": "-1.000000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"bit_rate": "448000",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "eng",
|
||||
"BPS": "448000",
|
||||
"DURATION": "02:01:28.832000000",
|
||||
"NUMBER_OF_FRAMES": "227776",
|
||||
"NUMBER_OF_BYTES": "408174592",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-05-24 07:43:44",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"codec_name": "ac3",
|
||||
"codec_long_name": "ATSC A/52A (AC-3)",
|
||||
"codec_type": "audio",
|
||||
"codec_time_base": "1/48000",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"sample_fmt": "fltp",
|
||||
"sample_rate": "48000",
|
||||
"channels": 6,
|
||||
"channel_layout": "5.1(side)",
|
||||
"bits_per_sample": 0,
|
||||
"dmix_mode": "-1",
|
||||
"ltrt_cmixlev": "-1.000000",
|
||||
"ltrt_surmixlev": "-1.000000",
|
||||
"loro_cmixlev": "-1.000000",
|
||||
"loro_surmixlev": "-1.000000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"bit_rate": "448000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "jpn",
|
||||
"BPS": "448000",
|
||||
"DURATION": "02:01:28.832000000",
|
||||
"NUMBER_OF_FRAMES": "227776",
|
||||
"NUMBER_OF_BYTES": "408174592",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-05-24 07:43:44",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"codec_name": "ass",
|
||||
"codec_long_name": "ASS (Advanced SSA) subtitle",
|
||||
"codec_type": "subtitle",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 7288832,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "eng",
|
||||
"title": "Signs & Songs@EMBER",
|
||||
"BPS": "5",
|
||||
"DURATION": "01:54:41.630000000",
|
||||
"NUMBER_OF_FRAMES": "90",
|
||||
"NUMBER_OF_BYTES": "4696",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-05-24 07:43:44",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"codec_name": "ass",
|
||||
"codec_long_name": "ASS (Advanced SSA) subtitle",
|
||||
"codec_type": "subtitle",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 7288832,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "eng",
|
||||
"title": "Dialogue@EMBER",
|
||||
"BPS": "78",
|
||||
"DURATION": "01:56:48.150000000",
|
||||
"NUMBER_OF_FRAMES": "1434",
|
||||
"NUMBER_OF_BYTES": "69001",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-05-24 07:43:44",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"codec_name": "hdmv_pgs_subtitle",
|
||||
"codec_long_name": "HDMV Presentation Graphic Stream subtitles",
|
||||
"codec_type": "subtitle",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 7288832,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "eng",
|
||||
"title": "Signs & Songs@USBD",
|
||||
"BPS": "402",
|
||||
"DURATION": "01:50:49.111000000",
|
||||
"NUMBER_OF_FRAMES": "56",
|
||||
"NUMBER_OF_BYTES": "334551",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-05-24 07:43:44",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 6,
|
||||
"codec_name": "hdmv_pgs_subtitle",
|
||||
"codec_long_name": "HDMV Presentation Graphic Stream subtitles",
|
||||
"codec_type": "subtitle",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 7288832,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "eng",
|
||||
"title": "Dialogue@USBD",
|
||||
"BPS": "21019",
|
||||
"DURATION": "02:00:56.802000000",
|
||||
"NUMBER_OF_FRAMES": "2829",
|
||||
"NUMBER_OF_BYTES": "19067149",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-05-24 07:43:44",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 7,
|
||||
"codec_name": "hdmv_pgs_subtitle",
|
||||
"codec_long_name": "HDMV Presentation Graphic Stream subtitles",
|
||||
"codec_type": "subtitle",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 7288832,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "eng",
|
||||
"title": "CC@USBD",
|
||||
"BPS": "34179",
|
||||
"DURATION": "01:58:57.850000000",
|
||||
"NUMBER_OF_FRAMES": "4338",
|
||||
"NUMBER_OF_BYTES": "30495881",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-05-24 07:43:44",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 8,
|
||||
"codec_type": "attachment",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/90000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 655994880,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"filename": "GandhiSans-BoldItalic.otf",
|
||||
"mimetype": "font/otf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 9,
|
||||
"codec_type": "attachment",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/90000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 655994880,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"filename": "HAPPYHELL.TTF",
|
||||
"mimetype": "font/ttf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 10,
|
||||
"codec_type": "attachment",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/90000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 655994880,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"filename": "AVERIALIBRE-BOLD.TTF",
|
||||
"mimetype": "font/ttf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 11,
|
||||
"codec_type": "attachment",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/90000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 655994880,
|
||||
"duration": "7288.832000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"filename": "GandhiSans-Bold.otf",
|
||||
"mimetype": "font/otf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 12,
|
||||
"codec_name": "mjpeg",
|
||||
"codec_long_name": "Motion JPEG",
|
||||
"profile": "Baseline",
|
||||
"codec_type": "video",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"width": 640,
|
||||
"height": 360,
|
||||
"coded_width": 640,
|
||||
"coded_height": 360,
|
||||
"has_b_frames": 0,
|
||||
"sample_aspect_ratio": "1:1",
|
||||
"display_aspect_ratio": "16:9",
|
||||
"pix_fmt": "yuvj420p",
|
||||
"level": -99,
|
||||
"color_range": "pc",
|
||||
"color_space": "bt470bg",
|
||||
"chroma_location": "center",
|
||||
"refs": 1,
|
||||
"r_frame_rate": "90000/1",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/90000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 655994880,
|
||||
"duration": "7288.832000",
|
||||
"bits_per_raw_sample": "8",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 1,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"filename": "cover.jpg",
|
||||
"mimetype": "image/jpeg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"format": {
|
||||
"filename": "[EMBER] Belle - Ryuu to Sobakasu no Hime (2021) (Movie) [BDRip] [804p Dual Audio HEVC 10 bits DD].mkv",
|
||||
"nb_streams": 13,
|
||||
"nb_programs": 0,
|
||||
"format_name": "matroska,webm",
|
||||
"format_long_name": "Matroska / WebM",
|
||||
"start_time": "0.000000",
|
||||
"duration": "7288.832000",
|
||||
"size": "4333518626",
|
||||
"bit_rate": "4756338",
|
||||
"probe_score": 100,
|
||||
"tags": {
|
||||
"title": "Belle.1080p.Dual.Audio.BDRip.10.bits.DD.x265-EMBER",
|
||||
"encoder": "libebml v1.4.2 + libmatroska v1.6.4",
|
||||
"creation_time": "2022-05-24T07:43:44.000000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
1093
Reader/src/test/resources/streams/sample4.json
Normal file
1093
Reader/src/test/resources/streams/sample4.json
Normal file
File diff suppressed because it is too large
Load Diff
98
Reader/src/test/resources/streams/sample5.json
Normal file
98
Reader/src/test/resources/streams/sample5.json
Normal file
@ -0,0 +1,98 @@
|
||||
{
|
||||
"streams": [
|
||||
{
|
||||
"index": 0,
|
||||
"codec_name": "hevc",
|
||||
"codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
|
||||
"profile": "Main 10",
|
||||
"codec_type": "video",
|
||||
"codec_tag_string": "hev1",
|
||||
"codec_tag": "0x31766568",
|
||||
"width": 1920,
|
||||
"height": 960,
|
||||
"coded_width": 1920,
|
||||
"coded_height": 960,
|
||||
"closed_captions": 0,
|
||||
"has_b_frames": 2,
|
||||
"sample_aspect_ratio": "1:1",
|
||||
"display_aspect_ratio": "2:1",
|
||||
"pix_fmt": "yuv420p10le",
|
||||
"level": 120,
|
||||
"color_range": "tv",
|
||||
"chroma_location": "left",
|
||||
"refs": 1,
|
||||
"r_frame_rate": "24/1",
|
||||
"avg_frame_rate": "24/1",
|
||||
"time_base": "1/24000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 84883000,
|
||||
"duration": "3536.791667",
|
||||
"bit_rate": "1998078",
|
||||
"nb_frames": "84883",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"creation_time": "2022-05-19T19:59:17.000000Z",
|
||||
"language": "und",
|
||||
"handler_name": "VideoHandler",
|
||||
"vendor_id": "[0][0][0][0]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"codec_name": "aac",
|
||||
"codec_long_name": "AAC (Advanced Audio Coding)",
|
||||
"profile": "LC",
|
||||
"codec_type": "audio",
|
||||
"codec_tag_string": "mp4a",
|
||||
"codec_tag": "0x6134706d",
|
||||
"sample_fmt": "fltp",
|
||||
"sample_rate": "48000",
|
||||
"channels": 6,
|
||||
"channel_layout": "5.1",
|
||||
"bits_per_sample": 0,
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/48000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 169766400,
|
||||
"duration": "3536.800000",
|
||||
"bit_rate": "224001",
|
||||
"nb_frames": "165790",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"creation_time": "2022-05-19T19:59:17.000000Z",
|
||||
"language": "eng",
|
||||
"handler_name": "SoundHandler",
|
||||
"vendor_id": "[0][0][0][0]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
193
Reader/src/test/resources/streams/sample6.json
Normal file
193
Reader/src/test/resources/streams/sample6.json
Normal file
@ -0,0 +1,193 @@
|
||||
{
|
||||
"streams": [
|
||||
{
|
||||
"index": 0,
|
||||
"codec_name": "hevc",
|
||||
"codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
|
||||
"profile": "Main 10",
|
||||
"codec_type": "video",
|
||||
"codec_time_base": "1001/24000",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"coded_width": 1920,
|
||||
"coded_height": 1080,
|
||||
"has_b_frames": 2,
|
||||
"sample_aspect_ratio": "1:1",
|
||||
"display_aspect_ratio": "16:9",
|
||||
"pix_fmt": "yuv420p10le",
|
||||
"level": 120,
|
||||
"color_range": "tv",
|
||||
"color_space": "bt709",
|
||||
"color_transfer": "bt709",
|
||||
"color_primaries": "bt709",
|
||||
"refs": 1,
|
||||
"r_frame_rate": "24000/1001",
|
||||
"avg_frame_rate": "24000/1001",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"title": "Presented By EMBER",
|
||||
"BPS": "2438576",
|
||||
"DURATION": "00:23:42.004000000",
|
||||
"NUMBER_OF_FRAMES": "34094",
|
||||
"NUMBER_OF_BYTES": "433458227",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-07-06 20:30:37",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"codec_name": "eac3",
|
||||
"codec_long_name": "ATSC A/52B (AC-3, E-AC-3)",
|
||||
"codec_type": "audio",
|
||||
"codec_time_base": "1/48000",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"sample_fmt": "fltp",
|
||||
"sample_rate": "48000",
|
||||
"channels": 2,
|
||||
"bits_per_sample": 0,
|
||||
"dmix_mode": "-1",
|
||||
"ltrt_cmixlev": "-1.000000",
|
||||
"ltrt_surmixlev": "-1.000000",
|
||||
"loro_cmixlev": "-1.000000",
|
||||
"loro_surmixlev": "-1.000000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "jpn",
|
||||
"BPS": "128000",
|
||||
"DURATION": "00:23:42.112000000",
|
||||
"NUMBER_OF_FRAMES": "44441",
|
||||
"NUMBER_OF_BYTES": "22753792",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-07-06 20:30:37",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"codec_name": "subrip",
|
||||
"codec_long_name": "SubRip subtitle",
|
||||
"codec_type": "subtitle",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 1422112,
|
||||
"duration": "1422.112000",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "eng",
|
||||
"BPS": "65",
|
||||
"DURATION": "00:23:25.487000000",
|
||||
"NUMBER_OF_FRAMES": "342",
|
||||
"NUMBER_OF_BYTES": "11595",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v65.0.0 ('Too Much') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2022-07-06 20:30:37",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"codec_name": "mjpeg",
|
||||
"codec_long_name": "Motion JPEG",
|
||||
"profile": "Progressive",
|
||||
"codec_type": "video",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"width": 400,
|
||||
"height": 564,
|
||||
"coded_width": 400,
|
||||
"coded_height": 564,
|
||||
"has_b_frames": 0,
|
||||
"sample_aspect_ratio": "1:1",
|
||||
"display_aspect_ratio": "100:141",
|
||||
"pix_fmt": "yuvj420p",
|
||||
"level": -99,
|
||||
"color_range": "pc",
|
||||
"color_space": "bt470bg",
|
||||
"chroma_location": "center",
|
||||
"refs": 1,
|
||||
"r_frame_rate": "90000/1",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/90000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 127990080,
|
||||
"duration": "1422.112000",
|
||||
"bits_per_raw_sample": "8",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 1,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"filename": "cover.jpg",
|
||||
"mimetype": "image/jpeg"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
205
Reader/src/test/resources/streams/sample7.json
Normal file
205
Reader/src/test/resources/streams/sample7.json
Normal file
@ -0,0 +1,205 @@
|
||||
{
|
||||
"streams": [
|
||||
{
|
||||
"index": 0,
|
||||
"codec_name": "hevc",
|
||||
"codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
|
||||
"profile": "Main 10",
|
||||
"codec_type": "video",
|
||||
"codec_time_base": "1/25",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"width": 1920,
|
||||
"height": 952,
|
||||
"coded_width": 1920,
|
||||
"coded_height": 952,
|
||||
"has_b_frames": 2,
|
||||
"sample_aspect_ratio": "1:1",
|
||||
"display_aspect_ratio": "240:119",
|
||||
"pix_fmt": "yuv420p10le",
|
||||
"level": 120,
|
||||
"color_range": "tv",
|
||||
"refs": 1,
|
||||
"r_frame_rate": "25/1",
|
||||
"avg_frame_rate": "25/1",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"BPS": "3698552",
|
||||
"BPS-eng": "3698552",
|
||||
"DURATION": "00:43:59.240000000",
|
||||
"DURATION-eng": "00:43:59.240000000",
|
||||
"NUMBER_OF_FRAMES": "65981",
|
||||
"NUMBER_OF_FRAMES-eng": "65981",
|
||||
"NUMBER_OF_BYTES": "1220170846",
|
||||
"NUMBER_OF_BYTES-eng": "1220170846",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v17.0.0 ('Be Ur Friend') 64-bit",
|
||||
"_STATISTICS_WRITING_APP-eng": "mkvmerge v17.0.0 ('Be Ur Friend') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2019-05-21 18:17:28",
|
||||
"_STATISTICS_WRITING_DATE_UTC-eng": "2019-05-21 18:17:28",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES",
|
||||
"_STATISTICS_TAGS-eng": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"codec_name": "aac",
|
||||
"codec_long_name": "AAC (Advanced Audio Coding)",
|
||||
"profile": "LC",
|
||||
"codec_type": "audio",
|
||||
"codec_time_base": "1/48000",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"sample_fmt": "fltp",
|
||||
"sample_rate": "48000",
|
||||
"channels": 2,
|
||||
"channel_layout": "stereo",
|
||||
"bits_per_sample": 0,
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 20,
|
||||
"start_time": "0.020000",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "nor",
|
||||
"BPS": "152584",
|
||||
"BPS-eng": "152584",
|
||||
"DURATION": "00:43:58.250000000",
|
||||
"DURATION-eng": "00:43:58.250000000",
|
||||
"NUMBER_OF_FRAMES": "123668",
|
||||
"NUMBER_OF_FRAMES-eng": "123668",
|
||||
"NUMBER_OF_BYTES": "50319602",
|
||||
"NUMBER_OF_BYTES-eng": "50319602",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v17.0.0 ('Be Ur Friend') 64-bit",
|
||||
"_STATISTICS_WRITING_APP-eng": "mkvmerge v17.0.0 ('Be Ur Friend') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2019-05-21 18:17:28",
|
||||
"_STATISTICS_WRITING_DATE_UTC-eng": "2019-05-21 18:17:28",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES",
|
||||
"_STATISTICS_TAGS-eng": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"codec_name": "subrip",
|
||||
"codec_long_name": "SubRip subtitle",
|
||||
"codec_type": "subtitle",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 2639240,
|
||||
"duration": "2639.240000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "eng",
|
||||
"BPS": "21",
|
||||
"BPS-eng": "21",
|
||||
"DURATION": "00:43:00.840000000",
|
||||
"DURATION-eng": "00:43:00.840000000",
|
||||
"NUMBER_OF_FRAMES": "197",
|
||||
"NUMBER_OF_FRAMES-eng": "197",
|
||||
"NUMBER_OF_BYTES": "6798",
|
||||
"NUMBER_OF_BYTES-eng": "6798",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v17.0.0 ('Be Ur Friend') 64-bit",
|
||||
"_STATISTICS_WRITING_APP-eng": "mkvmerge v17.0.0 ('Be Ur Friend') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2019-05-21 18:17:28",
|
||||
"_STATISTICS_WRITING_DATE_UTC-eng": "2019-05-21 18:17:28",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES",
|
||||
"_STATISTICS_TAGS-eng": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"codec_name": "subrip",
|
||||
"codec_long_name": "SubRip subtitle",
|
||||
"codec_type": "subtitle",
|
||||
"codec_time_base": "0/1",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 2639240,
|
||||
"duration": "2639.240000",
|
||||
"disposition": {
|
||||
"default": 0,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "dan",
|
||||
"BPS": "37",
|
||||
"BPS-eng": "37",
|
||||
"DURATION": "00:43:20.306000000",
|
||||
"DURATION-eng": "00:43:20.306000000",
|
||||
"NUMBER_OF_FRAMES": "276",
|
||||
"NUMBER_OF_FRAMES-eng": "276",
|
||||
"NUMBER_OF_BYTES": "12172",
|
||||
"NUMBER_OF_BYTES-eng": "12172",
|
||||
"_STATISTICS_WRITING_APP": "mkvmerge v17.0.0 ('Be Ur Friend') 64-bit",
|
||||
"_STATISTICS_WRITING_APP-eng": "mkvmerge v17.0.0 ('Be Ur Friend') 64-bit",
|
||||
"_STATISTICS_WRITING_DATE_UTC": "2019-05-21 18:17:28",
|
||||
"_STATISTICS_WRITING_DATE_UTC-eng": "2019-05-21 18:17:28",
|
||||
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES",
|
||||
"_STATISTICS_TAGS-eng": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
42
UI/.gitignore
vendored
Normal file
42
UI/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
19
UI/build.gradle.kts
Normal file
19
UI/build.gradle.kts
Normal file
@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
id("java")
|
||||
}
|
||||
|
||||
group = "no.iktdev.streamit.content"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(platform("org.junit:junit-bom:5.9.1"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
BIN
UI/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
UI/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
UI/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
UI/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Sat Jul 15 22:33:37 CEST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
234
UI/gradlew
vendored
Normal file
234
UI/gradlew
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
UI/gradlew.bat
vendored
Normal file
89
UI/gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
2
UI/settings.gradle.kts
Normal file
2
UI/settings.gradle.kts
Normal file
@ -0,0 +1,2 @@
|
||||
rootProject.name = "UI"
|
||||
|
||||
7
UI/src/main/java/no/iktdev/streamit/content/Main.java
Normal file
7
UI/src/main/java/no/iktdev/streamit/content/Main.java
Normal file
@ -0,0 +1,7 @@
|
||||
package no.iktdev.streamit.content;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello world!");
|
||||
}
|
||||
}
|
||||
178
pyMetadata/app.py
Normal file
178
pyMetadata/app.py
Normal file
@ -0,0 +1,178 @@
|
||||
import signal
|
||||
import sys, os, uuid
|
||||
import threading
|
||||
import json
|
||||
from kafka import KafkaConsumer, KafkaProducer
|
||||
from fuzzywuzzy import fuzz
|
||||
from sources.result import Result, Metadata
|
||||
from sources.anii import metadata as AniiMetadata
|
||||
from sources.imdb import metadata as ImdbMetadata
|
||||
|
||||
# Konfigurer Kafka-forbindelsen
|
||||
bootstrap_servers = os.environ.get("KAFKA_BOOTSTRAP_SERVER") if os.environ.get("KAFKA_BOOTSTRAP_SERVER") != None else "127.0.0.1:9092"
|
||||
consumer_group = os.environ.get("KAFKA_CONSUMER_ID") if os.environ.get("KAFKA_CONSUMER_ID") != None else f"Metadata-{uuid.uuid4()}"
|
||||
kafka_topic = os.environ.get("KAFKA_BOOTSTRAP_SERVER") if os.environ.get("KAFKA_BOOTSTRAP_SERVER") != None else "127.0.0.1:9092"
|
||||
|
||||
class ProducerDataValueSchema:
|
||||
def __init__(self, referenceId, statusType, errorMessage, data):
|
||||
self.referenceId = referenceId
|
||||
self.statusType = statusType
|
||||
self.errorMessage = errorMessage
|
||||
self.data = data
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'referenceId': self.referenceId,
|
||||
'status': {
|
||||
'statusType': self.statusType,
|
||||
'errorMessage': self.errorMessage
|
||||
},
|
||||
'data': self.data.to_dict() if self.data else None
|
||||
}
|
||||
|
||||
def to_json(self):
|
||||
data_dict = self.to_dict()
|
||||
return json.dumps(data_dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data_dict):
|
||||
referenceId = data_dict.get('referenceId')
|
||||
statusType = data_dict['status'].get('statusType')
|
||||
errorMessage = data_dict['status'].get('errorMessage')
|
||||
data = data_dict.get('data')
|
||||
|
||||
return cls(referenceId, statusType, errorMessage, data)
|
||||
|
||||
|
||||
|
||||
# Kafka consumer-klasse
|
||||
class KafkaConsumerThread(threading.Thread):
|
||||
def __init__(self, bootstrap_servers, topic):
|
||||
super().__init__()
|
||||
self.bootstrap_servers = bootstrap_servers
|
||||
self.topic = topic
|
||||
self.shutdown = threading.Event()
|
||||
|
||||
def run(self):
|
||||
consumer = KafkaConsumer(self.topic, bootstrap_servers=self.bootstrap_servers)
|
||||
|
||||
while not self.shutdown.is_set():
|
||||
for message in consumer:
|
||||
if self.shutdown.is_set():
|
||||
break
|
||||
|
||||
# Sjekk om meldingen har målnøkkelen
|
||||
if message.key == "request:metadata:obtain" or message.key == "event:reader:received-file":
|
||||
# Opprett en ny tråd for å håndtere meldingen
|
||||
handler_thread = MessageHandlerThread(message)
|
||||
handler_thread.start()
|
||||
|
||||
consumer.close()
|
||||
|
||||
def stop(self):
|
||||
self.shutdown.set()
|
||||
|
||||
# Kafka message handler-klasse
|
||||
class MessageHandlerThread(threading.Thread):
|
||||
def __init__(self, message):
|
||||
super().__init__()
|
||||
self.message = message
|
||||
|
||||
def run(self):
|
||||
# Deserialiser meldingsverdien fra JSON til et Python-dictionary
|
||||
message_value = json.loads(self.message.value)
|
||||
|
||||
# Sjekk om meldingen har en Status
|
||||
if 'status' in message_value:
|
||||
status_type = message_value['status']['statusType']
|
||||
|
||||
# Sjekk om statusen er SUCCESS
|
||||
if status_type == 'SUCCESS':
|
||||
data_value = message_value['data']["title"]
|
||||
|
||||
# Utfør handlingen basert på verdien
|
||||
result = self.perform_action(title=data_value)
|
||||
|
||||
producerMessage = self.compose_message(referenceId=message_value["referenceId"], result=result)
|
||||
|
||||
# Serialiser resultatet til JSON
|
||||
result_json = json.dumps(producerMessage.to_json())
|
||||
|
||||
# Send resultatet tilbake ved hjelp av Kafka-producer
|
||||
producer = KafkaProducer(bootstrap_servers=bootstrap_servers)
|
||||
producer.send(kafka_topic, key="event:metadata:obtained", value=result_json)
|
||||
producer.close()
|
||||
|
||||
def perform_action(self, title) -> Result:
|
||||
anii = AniiMetadata(title)
|
||||
imdb = ImdbMetadata(title)
|
||||
|
||||
anii_result = anii.lookup()
|
||||
imdb_result = imdb.lookup()
|
||||
|
||||
# Sammenlign resultater basert på likheter og sammenhenger med tittelen
|
||||
if anii_result.statusType == "SUCCESS" and imdb_result.statusType == "SUCCESS":
|
||||
# Begge registrene ga suksessresultater, bruk fuzzy matching for å gjøre en vurdering
|
||||
title_similarity_anii = fuzz.ratio(title.lower(), anii_result.data.title.lower())
|
||||
title_similarity_imdb = fuzz.ratio(title.lower(), imdb_result.data.title.lower())
|
||||
|
||||
# Sammenlign likheter mellom tittel og registertitler
|
||||
if title_similarity_anii > title_similarity_imdb:
|
||||
most_likely_result = anii_result
|
||||
else:
|
||||
most_likely_result = imdb_result
|
||||
|
||||
elif anii_result.statusType == "SUCCESS":
|
||||
# AniList ga suksessresultat, bruk det som det mest sannsynlige
|
||||
most_likely_result = anii_result
|
||||
|
||||
elif imdb_result.statusType == "SUCCESS":
|
||||
# IMDb ga suksessresultat, bruk det som det mest sannsynlige
|
||||
most_likely_result = imdb_result
|
||||
|
||||
else:
|
||||
# Begge registrene feilet, håndter etter eget behov
|
||||
most_likely_result = Result(statusType="ERROR", errorMessage="No Result")
|
||||
|
||||
# Returner det mest sannsynlige resultatet
|
||||
return most_likely_result
|
||||
|
||||
|
||||
def compose_message(self, referenceId: str, result: Result) -> ProducerDataValueSchema:
|
||||
""""""
|
||||
return ProducerDataValueSchema(
|
||||
referenceId=referenceId,
|
||||
statusType=result.statusType,
|
||||
errorMessage=result.errorMessage,
|
||||
data=result.data
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Global variabel for å indikere om applikasjonen skal avsluttes
|
||||
should_stop = False
|
||||
|
||||
# Signalhåndteringsfunksjon
|
||||
def signal_handler(sig, frame):
|
||||
global should_stop
|
||||
should_stop = True
|
||||
|
||||
# Hovedprogrammet
|
||||
def main():
|
||||
# Angi signalhåndterer for å fange opp SIGINT (Ctrl+C)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
# Opprett og start consumer-tråden
|
||||
consumer_thread = KafkaConsumerThread(bootstrap_servers, kafka_topic)
|
||||
consumer_thread.start()
|
||||
|
||||
# Vent til should_stop er satt til True for å avslutte applikasjonen
|
||||
while not should_stop:
|
||||
pass
|
||||
|
||||
# Stopp consumer-tråden
|
||||
consumer_thread.stop()
|
||||
consumer_thread.join()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
4
pyMetadata/requirments.txt
Normal file
4
pyMetadata/requirments.txt
Normal file
@ -0,0 +1,4 @@
|
||||
cinemagoer>=2023.5.1
|
||||
AnilistPython>=0.1.3
|
||||
kafka-python>=2.0.2
|
||||
fuzzywuzzy>=0.18.0
|
||||
33
pyMetadata/sources/anii.py
Normal file
33
pyMetadata/sources/anii.py
Normal file
@ -0,0 +1,33 @@
|
||||
from AnilistPython import Anilist
|
||||
from result import Metadata, Result
|
||||
|
||||
class metadata():
|
||||
name: str = None
|
||||
anilist = Anilist()
|
||||
|
||||
def __init__(self, name) -> None:
|
||||
self.name = name
|
||||
|
||||
def lookup(self) -> Result:
|
||||
""""""
|
||||
try:
|
||||
result = self.anilist.get_anime(self.name)
|
||||
meta = Metadata()
|
||||
meta.title = result.get("name_english", None)
|
||||
meta.altTitle = result.get("name_romaji", None)
|
||||
meta.cover = result.get("cover_image", None)
|
||||
meta.summary = result.get("desc", None)
|
||||
|
||||
airing_format = result.get('airing_format', '').lower()
|
||||
if airing_format == 'movie':
|
||||
meta.type = 'movie'
|
||||
else:
|
||||
meta.type = 'serie'
|
||||
meta.genres = result.get('genres', [])
|
||||
return Result("SUCCESS", None, meta)
|
||||
|
||||
except IndexError as ingore:
|
||||
return Result(statusType="IGNORE", errorMessage=f"No result for {self.name}")
|
||||
except Exception as e:
|
||||
return Result(statusType="ERROR", errorMessage=str(e))
|
||||
|
||||
34
pyMetadata/sources/imdb.py
Normal file
34
pyMetadata/sources/imdb.py
Normal file
@ -0,0 +1,34 @@
|
||||
import imdb
|
||||
from result import Metadata, Result
|
||||
|
||||
class metadata():
|
||||
name: str = None
|
||||
imdb = imdb.Cinemagoer()
|
||||
|
||||
def __init__(self, name) -> None:
|
||||
self.name = name
|
||||
|
||||
|
||||
def lookup(self) -> Result:
|
||||
""""""
|
||||
try:
|
||||
query = self.imdb.search_movie(self.name)
|
||||
imdbId = query[0].movieID
|
||||
result = self.imdb.get_movie(imdbId)
|
||||
meta = Metadata()
|
||||
meta.title = result.get("title", None)
|
||||
meta.altTitle = result.get("localized title", None)
|
||||
meta.cover = result.get("cover url", None)
|
||||
meta.summary = result.get("plot outline", None)
|
||||
|
||||
airing_format = result.get('kind', '').lower()
|
||||
if airing_format == 'movie':
|
||||
meta.type = 'movie'
|
||||
else:
|
||||
meta.type = 'serie'
|
||||
|
||||
meta.genres = result.get('genres', [])
|
||||
|
||||
return Result("SUCCESS", None, meta)
|
||||
except Exception as e:
|
||||
return Result(statusType="ERROR", errorMessage=str(e))
|
||||
30
pyMetadata/sources/result.py
Normal file
30
pyMetadata/sources/result.py
Normal file
@ -0,0 +1,30 @@
|
||||
from typing import List, Optional
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
@dataclass
|
||||
class Metadata:
|
||||
title: str
|
||||
altTitle: str
|
||||
cover: str
|
||||
type: str # Serie/Movie
|
||||
summary: str
|
||||
genres: List[str]
|
||||
|
||||
@dataclass
|
||||
class Result:
|
||||
statusType: str
|
||||
errorMessage: str
|
||||
data: Metadata
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data_dict):
|
||||
metadata_dict = data_dict.get('data')
|
||||
metadata = Metadata(**metadata_dict) if metadata_dict else None
|
||||
return cls(
|
||||
statusType=data_dict['statusType'],
|
||||
errorMessage=data_dict['errorMessage'],
|
||||
data=metadata
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user