优化文件
This commit is contained in:
@@ -9,7 +9,6 @@ import ink.snowflake.server.route.ImageAnalytics
|
|||||||
import ink.snowflake.server.route.RemoteDebug
|
import ink.snowflake.server.route.RemoteDebug
|
||||||
import ink.snowflake.server.route.VideoAnalytics
|
import ink.snowflake.server.route.VideoAnalytics
|
||||||
import ink.snowflake.server.route.VideoAnalyticsJetson
|
import ink.snowflake.server.route.VideoAnalyticsJetson
|
||||||
import ink.snowflake.server.route.mainFunc
|
|
||||||
import ink.snowflake.server.utils.AppConfig
|
import ink.snowflake.server.utils.AppConfig
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.tomcat.jakarta.*
|
import io.ktor.server.tomcat.jakarta.*
|
||||||
@@ -54,8 +53,6 @@ fun Application.module() {
|
|||||||
// 设置-WebSocket
|
// 设置-WebSocket
|
||||||
configureSockets()
|
configureSockets()
|
||||||
|
|
||||||
// 业务-首页导航
|
|
||||||
mainFunc()
|
|
||||||
// 业务-用户信息相关操作
|
// 业务-用户信息相关操作
|
||||||
User(appConfig)
|
User(appConfig)
|
||||||
// 业务-聊天
|
// 业务-聊天
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ object ImageDao {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getImageList(name: String): List<ImageAnalyticsRequest> {
|
fun getImageList(name: String): List<ImageAnalyticsRequest> {
|
||||||
return transaction {
|
return transaction {
|
||||||
ImageTable.selectAll()
|
ImageTable.selectAll()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import io.ktor.server.auth.*
|
|||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
|
import kotlin.text.isNullOrEmpty
|
||||||
|
|
||||||
fun Application.ImageAnalytics() {
|
fun Application.ImageAnalytics() {
|
||||||
routing {
|
routing {
|
||||||
@@ -18,16 +19,6 @@ fun Application.ImageAnalytics() {
|
|||||||
call.respond(BaseResponse(data = ImageDao.insertImageAnalyticsData(request)))
|
call.respond(BaseResponse(data = ImageDao.insertImageAnalyticsData(request)))
|
||||||
}
|
}
|
||||||
authenticate {
|
authenticate {
|
||||||
// 拍照保存为图片 并且调用Python程序进行分析
|
|
||||||
get("/takePhoto") {
|
|
||||||
val camera = call.request.queryParameters["cameraId"]
|
|
||||||
if (camera.isNullOrEmpty()) {
|
|
||||||
call.respond(BaseResponse(status = false, message = "摄像头名称不能为空", data = null))
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
stopHLSStream(camera)
|
|
||||||
call.respond(BaseResponse(message = "摄像头流已停止", data = null))
|
|
||||||
}
|
|
||||||
// 获取已分析图片列表
|
// 获取已分析图片列表
|
||||||
get("/getImageList") {
|
get("/getImageList") {
|
||||||
val name = call.parameters["name"]
|
val name = call.parameters["name"]
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package ink.snowflake.server.route
|
|
||||||
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import ink.snowflake.server.model.response.BaseResponse
|
|
||||||
import ink.snowflake.server.model.response.DeviceItem
|
|
||||||
import ink.snowflake.server.utils.runCommand
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.engine.cio.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
import io.ktor.client.plugins.auth.*
|
|
||||||
import io.ktor.client.plugins.auth.providers.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.http.content.*
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
fun Application.mainFunc() {
|
|
||||||
routing {
|
|
||||||
get("/") {
|
|
||||||
// call.respondFile(File("src/main/resources/page/html/login.html"))
|
|
||||||
// call.respondRedirect("html/login.html")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,244 +34,211 @@ import java.time.format.DateTimeFormatter
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
val clients = Collections.synchronizedList<WebSocketServerSession>(ArrayList()) // 线程安全的客户端列表
|
val clients = Collections.synchronizedList<WebSocketServerSession>(ArrayList()) // 线程安全的客户端列表
|
||||||
var aiState = "等待分析任务中"
|
|
||||||
|
|
||||||
fun Application.VideoAnalytics() {
|
fun Application.VideoAnalytics() {
|
||||||
routing {
|
routing {
|
||||||
// 实时发送AI状态
|
|
||||||
webSocket("/handleState") { // WebSocket 路由
|
|
||||||
clients.add(this) // 添加当前连接的客户端
|
|
||||||
send(aiState) // 向客户端发送连接成功消息
|
|
||||||
try {
|
|
||||||
incoming.consumeEach { frame -> // 持续接收消息
|
|
||||||
when (frame) {
|
|
||||||
is Frame.Text -> {
|
|
||||||
aiState = frame.readText() // 更新状态
|
|
||||||
broadcastMessage(aiState) // 使用封装的方法广播消息
|
|
||||||
}
|
|
||||||
|
|
||||||
is Frame.Close -> {
|
|
||||||
println("Closed")
|
|
||||||
clients.remove(this)
|
|
||||||
close() // 确保关闭 WebSocket 连接
|
|
||||||
return@consumeEach
|
|
||||||
}
|
|
||||||
// 其他消息类型的处理
|
|
||||||
is Frame.Binary -> TODO() // 处理二进制消息
|
|
||||||
is Frame.Ping -> TODO() // 处理 Ping 消息
|
|
||||||
is Frame.Pong -> TODO() // 处理 Pong 消息
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// 处理接收消息时的异常
|
|
||||||
close(CloseReason(CloseReason.Codes.NORMAL, "Client disconnected"))
|
|
||||||
e.printStackTrace()
|
|
||||||
} finally {
|
|
||||||
clients.remove(this) // 确保在连接关闭时移除客户端
|
|
||||||
}
|
|
||||||
}
|
|
||||||
route("/api/iva") {
|
route("/api/iva") {
|
||||||
// 上传分析结果
|
// 上传分析结果
|
||||||
post("/saveVideoAnalyticsData") {
|
post("/saveVideoAnalyticsData") {
|
||||||
val request = call.receive<VideoAnalyticsRequest>()
|
val request = call.receive<VideoAnalyticsRequest>()
|
||||||
// todo 上传这里未做测试
|
// todo 上传这里未做测试
|
||||||
call.respond(BaseResponse(data = VideoDao.insertVideoAnalyticsData(request)))
|
call.respond(BaseResponse(data = VideoDao.insertVideoAnalyticsData(request)))
|
||||||
}
|
}
|
||||||
authenticate {
|
authenticate {
|
||||||
post("/createVideoTask") {
|
post("/createVideoTask") {
|
||||||
val multipart = call.receiveMultipart() //1G
|
val multipart = call.receiveMultipart() //1G
|
||||||
// 确保 uploads 目录存在
|
// 确保 uploads 目录存在
|
||||||
val uploadDir = File(VIDEO_INPUT_PATH)
|
val uploadDir = File(VIDEO_INPUT_PATH)
|
||||||
if (!uploadDir.exists()) {
|
if (!uploadDir.exists()) {
|
||||||
if (!uploadDir.mkdirs()) {
|
if (!uploadDir.mkdirs()) {
|
||||||
println("无法创建目录")
|
println("无法创建目录")
|
||||||
throw IOException("Failed to create upload directory.")
|
throw IOException("Failed to create upload directory.")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var fileName = ""
|
}
|
||||||
var name = ""
|
var fileName = ""
|
||||||
var datetime = ""
|
var name = ""
|
||||||
broadcastMessage("正在上传数据")
|
var datetime = ""
|
||||||
|
broadcastMessage("正在上传数据")
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
multipart.forEachPart { part ->
|
multipart.forEachPart { part ->
|
||||||
when (part) {
|
when (part) {
|
||||||
is PartData.FileItem -> {
|
is PartData.FileItem -> {
|
||||||
fileName = part.originalFileName ?: "unknown"
|
fileName = part.originalFileName ?: "unknown"
|
||||||
val file = File("$VIDEO_INPUT_PATH$fileName") // 保存路径
|
val file = File("$VIDEO_INPUT_PATH$fileName") // 保存路径
|
||||||
//ktor3
|
//ktor3
|
||||||
// file.outputStream().use { outputStream ->
|
// file.outputStream().use { outputStream ->
|
||||||
// val writableChannel = Channels.newChannel(outputStream)
|
// val writableChannel = Channels.newChannel(outputStream)
|
||||||
// part.provider().copyTo(writableChannel) // 复制到 WritableByteChannel
|
// part.provider().copyTo(writableChannel) // 复制到 WritableByteChannel
|
||||||
// }
|
// }
|
||||||
//ktor2
|
//ktor2
|
||||||
part.streamProvider().use { inputStream ->
|
part.streamProvider().use { inputStream ->
|
||||||
file.outputStream().buffered().use { outputStream ->
|
file.outputStream().buffered().use { outputStream ->
|
||||||
inputStream.copyTo(outputStream)
|
inputStream.copyTo(outputStream)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is PartData.FormItem -> {
|
|
||||||
when (part.name) {
|
|
||||||
"projectName" -> name = part.value
|
|
||||||
"projectDatetime" -> datetime = part.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> part.dispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is PartData.FormItem -> {
|
||||||
|
when (part.name) {
|
||||||
|
"projectName" -> name = part.value
|
||||||
|
"projectDatetime" -> datetime = part.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> part.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
call.respond(BaseResponse(message = "上传成功", data = null))
|
}
|
||||||
broadcastMessage("上传完成,开始启动AI引擎")
|
call.respond(BaseResponse(message = "上传成功", data = null))
|
||||||
val command = listOf(
|
broadcastMessage("上传完成,开始启动AI引擎")
|
||||||
"/usr/bin/python3",
|
val command = listOf(
|
||||||
"/home/xhcp/mine/IntelligentVideoAnalytics/AI_Project/DeepStream_Action_Recognition/core/final.py",
|
"/usr/bin/python3",
|
||||||
"$VIDEO_INPUT_PATH$fileName",
|
"/home/xhcp/mine/IntelligentVideoAnalytics/AI_Project/DeepStream_Action_Recognition/core/final.py",
|
||||||
datetime,
|
"$VIDEO_INPUT_PATH$fileName",
|
||||||
name
|
datetime,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
println("-----------------" + command.joinToString(" "))
|
||||||
|
runCommand(command) {
|
||||||
|
println(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 获取已分析视频列表
|
||||||
|
get("/getVideoList") {
|
||||||
|
val name = call.parameters["name"]
|
||||||
|
val res = VideoDao.getVideoList(name)
|
||||||
|
call.respond(BaseResponse(data = res))
|
||||||
|
}
|
||||||
|
// 获取某视频分析详情
|
||||||
|
get("/getAnalyticsDetailByVideoId") {
|
||||||
|
// 获取 vId 参数
|
||||||
|
val vIdParam = call.parameters["vId"]
|
||||||
|
val vId = vIdParam?.toIntOrNull() // 将 vId 转换为 Int,确保安全
|
||||||
|
if (vId == null) {
|
||||||
|
call.respond(BaseResponse(status = true, message = "Invalid vId", data = null))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
// 查询视频信息
|
||||||
|
val video = VideoDao.getAnalyticsDetailByVideoId(vId)
|
||||||
|
if (video == null) {
|
||||||
|
call.respond(BaseResponse(status = false, message = "不存在该视频", data = null))
|
||||||
|
} else {
|
||||||
|
// 查询相关的分析详情
|
||||||
|
val details = VideoDao.selectVideoDetailByVid(vId)
|
||||||
|
|
||||||
|
// 用于返回的数据
|
||||||
|
val yTotalData = mutableListOf<Pair<String, Int>>() // (时间, 总人数)
|
||||||
|
val yMaskedData = mutableListOf<Pair<String, Int>>() // (时间, 佩戴口罩人数)
|
||||||
|
val areaData = mutableListOf<List<Area>>()
|
||||||
|
val detailList = mutableListOf<DetailItem>()
|
||||||
|
// 颜色映射
|
||||||
|
val colors = mapOf(
|
||||||
|
"feed" to "rgba(0, 255, 0, 0.4)", // 淡绿色
|
||||||
|
"disinfection" to "rgba(0, 0, 255, 0.4)", // 淡蓝色
|
||||||
|
"other" to "rgba(255, 0, 0, 0.4)" // 淡红色
|
||||||
)
|
)
|
||||||
println("-----------------" + command.joinToString(" "))
|
// 生成 yTotalData 和 yMaskedData,同时计算 areaData
|
||||||
runCommand(command) {
|
var lastAction: String? = null
|
||||||
println(it)
|
var areaStartTime: String? = null
|
||||||
}
|
var currentColor: String? = null
|
||||||
}
|
|
||||||
// 获取已分析视频列表
|
|
||||||
get("/getVideoList") {
|
|
||||||
val name = call.parameters["name"]
|
|
||||||
val res = VideoDao.getVideoList(name)
|
|
||||||
call.respond(BaseResponse(data = res))
|
|
||||||
}
|
|
||||||
// 获取某视频分析详情
|
|
||||||
get("/getAnalyticsDetailByVideoId") {
|
|
||||||
// 获取 vId 参数
|
|
||||||
val vIdParam = call.parameters["vId"]
|
|
||||||
val vId = vIdParam?.toIntOrNull() // 将 vId 转换为 Int,确保安全
|
|
||||||
if (vId == null) {
|
|
||||||
call.respond(BaseResponse(status = true, message = "Invalid vId", data = null))
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
// 查询视频信息
|
|
||||||
val video = VideoDao.getAnalyticsDetailByVideoId(vId)
|
|
||||||
if (video == null) {
|
|
||||||
call.respond(BaseResponse(status = false, message = "不存在该视频", data = null))
|
|
||||||
} else {
|
|
||||||
// 查询相关的分析详情
|
|
||||||
val details = VideoDao.selectVideoDetailByVid(vId)
|
|
||||||
|
|
||||||
// 用于返回的数据
|
for (detail in details) {
|
||||||
val yTotalData = mutableListOf<Pair<String, Int>>() // (时间, 总人数)
|
// 获取视频开始时间
|
||||||
val yMaskedData = mutableListOf<Pair<String, Int>>() // (时间, 佩戴口罩人数)
|
val vStartDatetime: LocalDateTime = video[vStartDateTime]!!
|
||||||
val areaData = mutableListOf<List<Area>>()
|
val timeStr = vStartDatetime.toJavaLocalDateTime()
|
||||||
val detailList = mutableListOf<DetailItem>()
|
.plusSeconds(detail.a_time_stamp.toLong())
|
||||||
// 颜色映射
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||||
val colors = mapOf(
|
|
||||||
"feed" to "rgba(0, 255, 0, 0.4)", // 淡绿色
|
|
||||||
"disinfection" to "rgba(0, 0, 255, 0.4)", // 淡蓝色
|
|
||||||
"other" to "rgba(255, 0, 0, 0.4)" // 淡红色
|
|
||||||
)
|
|
||||||
// 生成 yTotalData 和 yMaskedData,同时计算 areaData
|
|
||||||
var lastAction: String? = null
|
|
||||||
var areaStartTime: String? = null
|
|
||||||
var currentColor: String? = null
|
|
||||||
|
|
||||||
for (detail in details) {
|
// 添加总人数和口罩佩戴人数
|
||||||
// 获取视频开始时间
|
yTotalData.add(timeStr to detail.a_total_people)
|
||||||
val vStartDatetime: LocalDateTime = video[vStartDateTime]!!
|
yMaskedData.add(timeStr to detail.a_total_people_masked)
|
||||||
val timeStr = vStartDatetime.toJavaLocalDateTime()
|
|
||||||
.plusSeconds(detail.a_time_stamp.toLong())
|
|
||||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
|
||||||
|
|
||||||
// 添加总人数和口罩佩戴人数
|
// 处理 areaData,根据 a_action 判断是否需要创建新的区域
|
||||||
yTotalData.add(timeStr to detail.a_total_people)
|
if (detail.a_action != "--") { // 只处理非 "--" 动作
|
||||||
yMaskedData.add(timeStr to detail.a_total_people_masked)
|
if (lastAction == null || lastAction != detail.a_action) {
|
||||||
|
// 如果上一个动作和当前动作不同,并且 areaStartTime 已经存在,创建新的区域
|
||||||
// 处理 areaData,根据 a_action 判断是否需要创建新的区域
|
|
||||||
if (detail.a_action != "--") { // 只处理非 "--" 动作
|
|
||||||
if (lastAction == null || lastAction != detail.a_action) {
|
|
||||||
// 如果上一个动作和当前动作不同,并且 areaStartTime 已经存在,创建新的区域
|
|
||||||
if (areaStartTime != null) {
|
|
||||||
areaData.add(
|
|
||||||
listOf(
|
|
||||||
Area(areaStartTime, ItemStyle(currentColor ?: "#FF0000")),
|
|
||||||
Area(timeStr, ItemStyle(currentColor ?: "#FF0000"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// 添加到 detailList,记录动作开始的时刻
|
|
||||||
detailList.add(
|
|
||||||
DetailItem(getFriendlyActionName(detail.a_action), time = timeStr)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 更新为新的动作
|
|
||||||
lastAction = detail.a_action
|
|
||||||
areaStartTime = timeStr
|
|
||||||
currentColor = colors[detail.a_action] ?: "#FF0000" // 默认红色
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果当前动作为 "--",则结束当前区域,并重置状态
|
|
||||||
if (areaStartTime != null) {
|
if (areaStartTime != null) {
|
||||||
// 添加到 detailList,记录动作开始的时刻
|
|
||||||
detailList.add(
|
|
||||||
DetailItem(getFriendlyActionName(detail.a_action), timeStr)
|
|
||||||
)
|
|
||||||
// 结束当前区域
|
|
||||||
areaData.add(
|
areaData.add(
|
||||||
listOf(
|
listOf(
|
||||||
Area(areaStartTime, ItemStyle(currentColor ?: "#FF0000")),
|
Area(areaStartTime, ItemStyle(currentColor ?: "#FF0000")),
|
||||||
Area(timeStr, ItemStyle(currentColor ?: "#FF0000"))
|
Area(timeStr, ItemStyle(currentColor ?: "#FF0000"))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// 重置状态
|
|
||||||
lastAction = null
|
|
||||||
areaStartTime = null
|
|
||||||
currentColor = null
|
|
||||||
}
|
}
|
||||||
|
// 添加到 detailList,记录动作开始的时刻
|
||||||
|
detailList.add(
|
||||||
|
DetailItem(getFriendlyActionName(detail.a_action), time = timeStr)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 更新为新的动作
|
||||||
|
lastAction = detail.a_action
|
||||||
|
areaStartTime = timeStr
|
||||||
|
currentColor = colors[detail.a_action] ?: "#FF0000" // 默认红色
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果当前动作为 "--",则结束当前区域,并重置状态
|
||||||
|
if (areaStartTime != null) {
|
||||||
|
// 添加到 detailList,记录动作开始的时刻
|
||||||
|
detailList.add(
|
||||||
|
DetailItem(getFriendlyActionName(detail.a_action), timeStr)
|
||||||
|
)
|
||||||
|
// 结束当前区域
|
||||||
|
areaData.add(
|
||||||
|
listOf(
|
||||||
|
Area(areaStartTime, ItemStyle(currentColor ?: "#FF0000")),
|
||||||
|
Area(timeStr, ItemStyle(currentColor ?: "#FF0000"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// 重置状态
|
||||||
|
lastAction = null
|
||||||
|
areaStartTime = null
|
||||||
|
currentColor = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 处理最后一个区域的结束时间
|
}
|
||||||
if (areaStartTime != null && lastAction != null) {
|
// 处理最后一个区域的结束时间
|
||||||
areaData.add(
|
if (areaStartTime != null && lastAction != null) {
|
||||||
listOf(
|
areaData.add(
|
||||||
Area(areaStartTime, ItemStyle(currentColor ?: "#FF0000")),
|
listOf(
|
||||||
Area(yTotalData.last().first, ItemStyle(currentColor ?: "#FF0000"))
|
Area(areaStartTime, ItemStyle(currentColor ?: "#FF0000")),
|
||||||
)
|
Area(yTotalData.last().first, ItemStyle(currentColor ?: "#FF0000"))
|
||||||
)
|
|
||||||
}
|
|
||||||
val analyticsData = VideoAnalyticsData(
|
|
||||||
yTotalData = yTotalData,
|
|
||||||
yMaskedData = yMaskedData,
|
|
||||||
areaData = areaData
|
|
||||||
)
|
|
||||||
// 返回数据
|
|
||||||
call.respond(
|
|
||||||
BaseResponse(
|
|
||||||
data = VideoAnalyticsDetail(
|
|
||||||
v_id = vId,
|
|
||||||
v_name = video[VideoTable.vName],
|
|
||||||
v_video_play_path = "http://${SERVER_PATH_OSS}:9000/video/" + video[VideoTable.vObjectName],
|
|
||||||
v_file_name = video[VideoTable.vFileName],
|
|
||||||
v_duration = video[VideoTable.vDuration],
|
|
||||||
v_size = video[VideoTable.vSize],
|
|
||||||
v_start_datetime = video[vStartDateTime]?.toString() ?: "",
|
|
||||||
v_video_codec = video[VideoTable.vVideoCodec],
|
|
||||||
v_audio_codec = video[VideoTable.vAudioCodec],
|
|
||||||
v_overall_bit_rate = video[VideoTable.vOverallBitRate],
|
|
||||||
v_resolution = video[VideoTable.vResolution],
|
|
||||||
v_a_time = video[VideoTable.vATime].toString(),
|
|
||||||
v_a_total_people = video[vATotalPeople], // 总人数
|
|
||||||
v_a_count_people = video[vACountPeople], // 佩戴口罩人数
|
|
||||||
v_a_max_stay_time = video[vAMaxStayTime], // 最大停留时间
|
|
||||||
v_a_max_action = getFriendlyActionName(video[vAMaxAction]), // 最大动作
|
|
||||||
v_a_average_masked_ratio = video[vAAverageMaskedRatio], // 平均佩戴口罩比例
|
|
||||||
v_a_details = analyticsData, // 这里是计算得来的 VideoAnalyticsData
|
|
||||||
v_details_list = detailList
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val analyticsData = VideoAnalyticsData(
|
||||||
|
yTotalData = yTotalData,
|
||||||
|
yMaskedData = yMaskedData,
|
||||||
|
areaData = areaData
|
||||||
|
)
|
||||||
|
// 返回数据
|
||||||
|
call.respond(
|
||||||
|
BaseResponse(
|
||||||
|
data = VideoAnalyticsDetail(
|
||||||
|
v_id = vId,
|
||||||
|
v_name = video[VideoTable.vName],
|
||||||
|
v_video_play_path = "http://${SERVER_PATH_OSS}:9000/video/" + video[VideoTable.vObjectName],
|
||||||
|
v_file_name = video[VideoTable.vFileName],
|
||||||
|
v_duration = video[VideoTable.vDuration],
|
||||||
|
v_size = video[VideoTable.vSize],
|
||||||
|
v_start_datetime = video[vStartDateTime]?.toString() ?: "",
|
||||||
|
v_video_codec = video[VideoTable.vVideoCodec],
|
||||||
|
v_audio_codec = video[VideoTable.vAudioCodec],
|
||||||
|
v_overall_bit_rate = video[VideoTable.vOverallBitRate],
|
||||||
|
v_resolution = video[VideoTable.vResolution],
|
||||||
|
v_a_time = video[VideoTable.vATime].toString(),
|
||||||
|
v_a_total_people = video[vATotalPeople], // 总人数
|
||||||
|
v_a_count_people = video[vACountPeople], // 佩戴口罩人数
|
||||||
|
v_a_max_stay_time = video[vAMaxStayTime], // 最大停留时间
|
||||||
|
v_a_max_action = getFriendlyActionName(video[vAMaxAction]), // 最大动作
|
||||||
|
v_a_average_masked_ratio = video[vAAverageMaskedRatio], // 平均佩戴口罩比例
|
||||||
|
v_a_details = analyticsData, // 这里是计算得来的 VideoAnalyticsData
|
||||||
|
v_details_list = detailList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
var aiState = "等待分析任务中"
|
||||||
|
// 实时发送AI状态
|
||||||
|
webSocket("/handleState") { // WebSocket 路由
|
||||||
|
clients.add(this) // 添加当前连接的客户端
|
||||||
|
send(aiState) // 向客户端发送连接成功消息
|
||||||
|
try {
|
||||||
|
incoming.consumeEach { frame -> // 持续接收消息
|
||||||
|
when (frame) {
|
||||||
|
is Frame.Text -> {
|
||||||
|
aiState = frame.readText() // 更新状态
|
||||||
|
broadcastMessage(aiState) // 使用封装的方法广播消息
|
||||||
|
}
|
||||||
|
|
||||||
|
is Frame.Close -> {
|
||||||
|
println("Closed")
|
||||||
|
clients.remove(this)
|
||||||
|
close() // 确保关闭 WebSocket 连接
|
||||||
|
return@consumeEach
|
||||||
|
}
|
||||||
|
// 其他消息类型的处理
|
||||||
|
is Frame.Binary -> TODO() // 处理二进制消息
|
||||||
|
is Frame.Ping -> TODO() // 处理 Ping 消息
|
||||||
|
is Frame.Pong -> TODO() // 处理 Pong 消息
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 处理接收消息时的异常
|
||||||
|
close(CloseReason(CloseReason.Codes.NORMAL, "Client disconnected"))
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
clients.remove(this) // 确保在连接关闭时移除客户端
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 拍照保存为图片 并且调用Python程序进行分析
|
||||||
|
//get("/takePhoto") {
|
||||||
|
// val camera = call.request.queryParameters["cameraId"]
|
||||||
|
// if (camera.isNullOrEmpty()) {
|
||||||
|
// call.respond(BaseResponse(status = false, message = "摄像头名称不能为空", data = null))
|
||||||
|
// return@get
|
||||||
|
// }
|
||||||
|
// stopHLSStream(camera)
|
||||||
|
// call.respond(BaseResponse(message = "摄像头流已停止", data = null))
|
||||||
|
//}
|
||||||
Reference in New Issue
Block a user