优化文件

This commit is contained in:
BBIT-Kai
2025-06-18 10:21:32 +08:00
parent 617cc3162e
commit f02173c667
6 changed files with 219 additions and 249 deletions
@@ -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))
//}