新模块功能:蚕茧视频识别
This commit is contained in:
+4
-5
@@ -15,7 +15,6 @@ from routers.RabbitMQ import rqRouter
|
|||||||
from routers.Report import reportRouter
|
from routers.Report import reportRouter
|
||||||
from routers.Service import serviceRouter
|
from routers.Service import serviceRouter
|
||||||
from routers.Vision import visionRouter
|
from routers.Vision import visionRouter
|
||||||
from service.Analyze import mq_pull_analysis_async
|
|
||||||
|
|
||||||
|
|
||||||
async def ai_lab():
|
async def ai_lab():
|
||||||
@@ -47,7 +46,7 @@ async def ai_lab():
|
|||||||
app.include_router(r, prefix="/llm", tags=["llm"])
|
app.include_router(r, prefix="/llm", tags=["llm"])
|
||||||
app.include_router(visionRouter, prefix="/cv", tags=["cv"])
|
app.include_router(visionRouter, prefix="/cv", tags=["cv"])
|
||||||
app.include_router(publicRouter, prefix="/api/public", tags=["api"])
|
app.include_router(publicRouter, prefix="/api/public", tags=["api"])
|
||||||
config = Config(app=app, host="0.0.0.0", port=13011, log_level="info")
|
config = Config(app=app, host="0.0.0.0", port=13011, log_level="debug")
|
||||||
server = Server(config)
|
server = Server(config)
|
||||||
await server.serve()
|
await server.serve()
|
||||||
|
|
||||||
@@ -59,7 +58,7 @@ async def main():
|
|||||||
task_api = asyncio.create_task(ai_lab())
|
task_api = asyncio.create_task(ai_lab())
|
||||||
|
|
||||||
# MCP服务-ailab
|
# MCP服务-ailab
|
||||||
endpoint_url_ai_lab = "wss://ai.ronsunny.cn:8090/aimcp/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
# endpoint_url_ai_lab = "wss://ai.ronsunny.cn:8090/aimcp/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||||
# endpoint_url_ai_lab = "ws://ce_bot_mcp:8004/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
# endpoint_url_ai_lab = "ws://ce_bot_mcp:8004/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||||
# task_mcp1 = asyncio.create_task(init_mcp_server(endpoint_url_ai_lab))
|
# task_mcp1 = asyncio.create_task(init_mcp_server(endpoint_url_ai_lab))
|
||||||
|
|
||||||
@@ -68,10 +67,10 @@ async def main():
|
|||||||
task_mcp2 = asyncio.create_task(init_mcp_server(endpoint_url_ql))
|
task_mcp2 = asyncio.create_task(init_mcp_server(endpoint_url_ql))
|
||||||
|
|
||||||
# RabbitMQ服务
|
# RabbitMQ服务
|
||||||
task_mq = asyncio.create_task(mq_pull_analysis_async())
|
# task_mq = asyncio.create_task(mq_pull_analysis_async())
|
||||||
|
|
||||||
# await asyncio.gather(task_api, task_mcp1, task_mcp2, task_mq)
|
# await asyncio.gather(task_api, task_mcp1, task_mcp2, task_mq)
|
||||||
await asyncio.gather(task_api, task_mcp2, task_mq)
|
await asyncio.gather(task_api, task_mcp2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -22,11 +22,10 @@ def push_file(bucket_name, object_name, file_bytes, contents, content_type):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_upload_token(bucket_name, object_name, xpires=timedelta(hours=1)):
|
def get_upload_token(user_id, bucket_name, object_name, xpires=timedelta(minutes=15)):
|
||||||
upload_url = minio_client.presigned_put_object(
|
return minio_client.presigned_put_object(
|
||||||
bucket_name=bucket_name, object_name=object_name, expires=xpires
|
bucket_name=bucket_name, object_name=object_name, expires=xpires
|
||||||
)
|
)
|
||||||
return {"upload_url": upload_url, "object_name": object_name}
|
|
||||||
|
|
||||||
|
|
||||||
def get_temp_url(bucket_name, object_name):
|
def get_temp_url(bucket_name, object_name):
|
||||||
|
|||||||
@@ -753,3 +753,97 @@ def get_sca_image_list(user_id, name, page=1, page_size=10):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return total, result
|
return total, result
|
||||||
|
|
||||||
|
|
||||||
|
def get_sca_video_list(name, page=1, page_size=10):
|
||||||
|
"""
|
||||||
|
获取用户已分析视频列表,带分页
|
||||||
|
"""
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
|
||||||
|
with pg_pool.getConn() as conn:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
# 1️⃣ 查询总条数
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM sca_videos
|
||||||
|
WHERE (%s = '' OR name LIKE '%%' || %s || '%%')
|
||||||
|
""",
|
||||||
|
(name, name),
|
||||||
|
)
|
||||||
|
total = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
# 2️⃣ 查询当前页数据
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT id, name, raw_object_name, ai_object_name, duration, size, video_codec, audio_codec,
|
||||||
|
overall_bit_rate, resolution, sc_analysis_time, sc_analysis_total_count, sc_analysis_max_count,
|
||||||
|
sc_analysis_primary_type, sc_analysis_secondary_type, other_info, created_at
|
||||||
|
FROM sca_videos
|
||||||
|
WHERE (%s = '' OR name LIKE '%%' || %s || '%%')
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT %s OFFSET %s
|
||||||
|
""",
|
||||||
|
(name, name, page_size, offset),
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for row in rows:
|
||||||
|
result.append(
|
||||||
|
{
|
||||||
|
"id": row[0],
|
||||||
|
"name": row[1],
|
||||||
|
"raw_video_url": get_temp_url("video-sca", "raw/" + row[2]),
|
||||||
|
"ai_video_url": get_temp_url("video-sca", "ai/" + row[3]),
|
||||||
|
"duration": MyUtils.safe_round(row[4], 2),
|
||||||
|
"size_kb": MyUtils.safe_round(row[5] / 1024, 2),
|
||||||
|
"video_codec": row[6],
|
||||||
|
"audio_codec": row[7],
|
||||||
|
"overall_bit_rate": row[8],
|
||||||
|
"resolution": row[9],
|
||||||
|
"sc_analysis_time": MyUtils.safe_round(row[10], 2),
|
||||||
|
"sc_analysis_total_count": row[11],
|
||||||
|
"sc_analysis_max_count": row[12],
|
||||||
|
"sc_analysis_primary_type": row[13],
|
||||||
|
"sc_analysis_secondary_type": row[14],
|
||||||
|
"other_info": json.loads(row[15]),
|
||||||
|
"created_at": MyUtils.format_datetime(row[16]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return total, result
|
||||||
|
|
||||||
|
|
||||||
|
def get_sca_video_details(v_id):
|
||||||
|
"""
|
||||||
|
获取指定视频的分析明细列表
|
||||||
|
"""
|
||||||
|
with pg_pool.getConn() as conn:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT id, v_id, time_stamp, other_info
|
||||||
|
FROM sca_video_details
|
||||||
|
WHERE v_id = %s
|
||||||
|
ORDER BY time_stamp ASC
|
||||||
|
""",
|
||||||
|
(v_id,),
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
result = []
|
||||||
|
for row in rows:
|
||||||
|
# other_info 从 JSON 字符串解析回字典
|
||||||
|
result.append(
|
||||||
|
{
|
||||||
|
"id": row[0],
|
||||||
|
"v_id": row[1],
|
||||||
|
"time_stamp": row[2],
|
||||||
|
"other_info": row[3],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
@@ -153,8 +153,41 @@ def getSilkwormCocoonAnalysisTasks(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@visionRouter.post("/getIVASCUploadToken")
|
# ————————————————————————————————蚕茧视频识别任务————————————————————————————————————————————————
|
||||||
def getIVASCUploadToken():
|
|
||||||
|
|
||||||
|
@visionRouter.get("/getIVASCUploadToken")
|
||||||
|
def getIVASCUploadToken(
|
||||||
|
user_id: UUID = Depends(get_user_id_from_token),
|
||||||
|
):
|
||||||
# 生成唯一文件名,避免覆盖
|
# 生成唯一文件名,避免覆盖
|
||||||
object_name = f"raw/{uuid.uuid4()}"
|
object_name = f"raw/{uuid.uuid4()}"
|
||||||
return get_upload_token("video-sca", object_name)
|
return BaseResponse(data=get_upload_token(user_id, "video-sca", object_name))
|
||||||
|
|
||||||
|
|
||||||
|
@visionRouter.get("/getScVideoList")
|
||||||
|
def getScVideoList(
|
||||||
|
user_id: UUID = Depends(get_user_id_from_token),
|
||||||
|
name: str = "",
|
||||||
|
page: int = Query(1, ge=1),
|
||||||
|
page_size: int = Query(10, ge=1, le=100),
|
||||||
|
):
|
||||||
|
if not user_id:
|
||||||
|
return {"error": "userId is required"}
|
||||||
|
total, items = pg.get_sca_video_list(name, page=page, page_size=page_size)
|
||||||
|
return BaseResponse(
|
||||||
|
data={
|
||||||
|
"total": total,
|
||||||
|
"items": items,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@visionRouter.get("/getAnalyticsDetailBySCVideoId")
|
||||||
|
def getAnalyticsDetailBySCVideoId(
|
||||||
|
user_id: UUID = Depends(get_user_id_from_token),
|
||||||
|
vId: str = "",
|
||||||
|
):
|
||||||
|
if not user_id:
|
||||||
|
return {"error": "userId is required"}
|
||||||
|
return BaseResponse(data=pg.get_sca_video_details(vId))
|
||||||
|
|||||||
@@ -132,18 +132,6 @@ def process_silkworm_cocoon_image(
|
|||||||
# YOLO检测
|
# YOLO检测
|
||||||
img_bytes_out, results_json = YOLOSingleton.detect(img_bytes)
|
img_bytes_out, results_json = YOLOSingleton.detect(img_bytes)
|
||||||
|
|
||||||
# results_json = {
|
|
||||||
# "total_objects": "",
|
|
||||||
# "max_confidence": "",
|
|
||||||
# "min_confidence": "",
|
|
||||||
# "avg_confidence": "",
|
|
||||||
# "class_counts": "",
|
|
||||||
# "speed_ms": {
|
|
||||||
# "preprocess": "",
|
|
||||||
# "inference": "",
|
|
||||||
# "postprocess": "",
|
|
||||||
# },
|
|
||||||
# }
|
|
||||||
speed_json = results_json.get("speed_ms")
|
speed_json = results_json.get("speed_ms")
|
||||||
|
|
||||||
file_bytes_out = BytesIO(img_bytes_out)
|
file_bytes_out = BytesIO(img_bytes_out)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
"ant-design-vue": "catalog:",
|
"ant-design-vue": "catalog:",
|
||||||
|
"axios": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"handsontable": "^16.0.1",
|
"handsontable": "^16.0.1",
|
||||||
"js-sha256": "^0.11.0",
|
"js-sha256": "^0.11.0",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './iva';
|
export * from './iva';
|
||||||
|
export * from './iva-sc';
|
||||||
export * from './license';
|
export * from './license';
|
||||||
export * from './sca';
|
export * from './sca';
|
||||||
export * from './sca2';
|
export * from './sca2';
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { pyRequestClient } from '#/api/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已分析的视频列表
|
||||||
|
*/
|
||||||
|
export async function refreshSCVideoList(name = '', page = 1, pageSize = 9) {
|
||||||
|
return pyRequestClient.get('/cv/getScVideoList', {
|
||||||
|
params: { name, page, page_size: pageSize },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取已分析的视频
|
||||||
|
*/
|
||||||
|
export async function refreshSCVideoDetail(vId = '') {
|
||||||
|
return pyRequestClient.get('/cv/getAnalyticsDetailBySCVideoId', {
|
||||||
|
params: { vId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传视频分析任务
|
||||||
|
*/
|
||||||
|
export async function getIVASCUploadToken() {
|
||||||
|
return pyRequestClient.get('/cv/getIVASCUploadToken');
|
||||||
|
}
|
||||||
@@ -22,11 +22,12 @@ import {
|
|||||||
} from '@vben/icons';
|
} from '@vben/icons';
|
||||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { Button, Form, Input, message } from 'ant-design-vue';
|
import { Button, Form, message } from 'ant-design-vue';
|
||||||
|
import axios from 'axios';
|
||||||
import videojs from 'video.js';
|
import videojs from 'video.js';
|
||||||
|
|
||||||
import * as api from '#/api';
|
import * as api from '#/api';
|
||||||
import { createImageTaskV2 } from '#/api';
|
import { getIVASCUploadToken } from '#/api';
|
||||||
|
|
||||||
import 'video.js/dist/video-js.css';
|
import 'video.js/dist/video-js.css';
|
||||||
|
|
||||||
@@ -37,11 +38,15 @@ const activeTab = ref<'detail' | 'video'>('detail');
|
|||||||
const selectedItem = ref<any>(null);
|
const selectedItem = ref<any>(null);
|
||||||
const detailList = ref<any[]>([]);
|
const detailList = ref<any[]>([]);
|
||||||
const videoEl = ref<HTMLVideoElement | null>(null);
|
const videoEl = ref<HTMLVideoElement | null>(null);
|
||||||
|
const originalVideoEl = ref<HTMLVideoElement | null>(null);
|
||||||
|
const originalPlayer = ref<null | Player>(null);
|
||||||
|
|
||||||
const player = ref<null | Player>(null);
|
const player = ref<null | Player>(null);
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
error.value = null;
|
error.value = null;
|
||||||
const res = await api.refreshVideoList(filterKeyword.value);
|
const res = await api.refreshSCVideoList(filterKeyword.value);
|
||||||
list.value = res || [];
|
list.value = res.items || [];
|
||||||
|
total.value = res.total;
|
||||||
}
|
}
|
||||||
function refreshList() {
|
function refreshList() {
|
||||||
filterKeyword.value = '';
|
filterKeyword.value = '';
|
||||||
@@ -53,15 +58,18 @@ function createTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function selectItem(item: any) {
|
async function selectItem(item: any) {
|
||||||
const res = await api.refreshVideoDetail(item.v_id);
|
const res = await api.refreshSCVideoDetail(item.id);
|
||||||
selectedItem.value = res;
|
detailList.value = res;
|
||||||
|
selectedItem.value = item;
|
||||||
refreshLineChart();
|
refreshLineChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: 'detail', label: '分析详情' },
|
{ key: 'detail', label: '分析详情' },
|
||||||
|
{ key: 'original', label: '原视频' }, // 新增
|
||||||
{ key: 'video', label: '分析视频' },
|
{ key: 'video', label: '分析视频' },
|
||||||
];
|
];
|
||||||
|
|
||||||
let overviewItems: AnalysisOverviewItem[];
|
let overviewItems: AnalysisOverviewItem[];
|
||||||
|
|
||||||
// 监听关键词变化,调用防抖接口
|
// 监听关键词变化,调用防抖接口
|
||||||
@@ -72,45 +80,50 @@ watch(selectedItem, () => {
|
|||||||
overviewItems = [
|
overviewItems = [
|
||||||
{
|
{
|
||||||
icon: SvgDownloadIcon,
|
icon: SvgDownloadIcon,
|
||||||
title: '出现的蚕茧数量',
|
title: '出现的蚕茧总数',
|
||||||
totalTitle: '',
|
totalTitle: '',
|
||||||
totalValue: 0,
|
totalValue: 0,
|
||||||
value: selectedItem.value.v_a_count_people,
|
value: selectedItem.value.sc_analysis_total_count,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgCardIcon,
|
icon: SvgCardIcon,
|
||||||
title: '出现最多的种类',
|
title: '出现最多的种类',
|
||||||
totalTitle: '',
|
totalTitle: '',
|
||||||
totalValue: 0,
|
totalValue: 0,
|
||||||
value: selectedItem.value.v_a_max_action,
|
value: selectedItem.value.sc_analysis_primary_type,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgCakeIcon,
|
icon: SvgCakeIcon,
|
||||||
title: '最多同框蚕茧数量',
|
title: '出现次多的种类',
|
||||||
totalTitle: '',
|
totalTitle: '',
|
||||||
totalValue: 0,
|
totalValue: 0,
|
||||||
value: selectedItem.value.v_a_total_people,
|
value: selectedItem.value.sc_analysis_secondary_type,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: SvgBellIcon,
|
icon: SvgBellIcon,
|
||||||
title: '时间',
|
title: '最多同框蚕茧数量',
|
||||||
totalTitle: '最长停留时间',
|
totalTitle: '',
|
||||||
totalValue: 0,
|
totalValue: 0,
|
||||||
value: selectedItem.value.v_a_max_stay_time,
|
value: selectedItem.value.sc_analysis_max_count,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
watch([activeTab, selectedItem], async ([tab]) => {
|
watch([activeTab, selectedItem], async ([tab]) => {
|
||||||
if (tab === 'video' && selectedItem.value?.v_video_play_path) {
|
if (tab === 'video' && selectedItem.value?.ai_video_url) {
|
||||||
refreshVideoPlayer();
|
refreshVideoPlayer();
|
||||||
|
} else if (tab === 'original' && selectedItem.value?.raw_video_url) {
|
||||||
|
refreshOriginalPlayer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadList();
|
loadList();
|
||||||
});
|
});
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
player.value?.dispose();
|
player.value?.dispose();
|
||||||
|
originalPlayer.value?.dispose();
|
||||||
player.value = null;
|
player.value = null;
|
||||||
|
originalPlayer.value = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// ✅ 切换视频项时销毁并重建
|
// ✅ 切换视频项时销毁并重建
|
||||||
@@ -119,7 +132,7 @@ function refreshVideoPlayer() {
|
|||||||
if (player.value) {
|
if (player.value) {
|
||||||
player.value.src([
|
player.value.src([
|
||||||
{
|
{
|
||||||
src: selectedItem.value.v_video_play_path,
|
src: selectedItem.value.ai_video_url,
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -131,7 +144,7 @@ function refreshVideoPlayer() {
|
|||||||
preload: 'auto',
|
preload: 'auto',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: selectedItem.value.v_video_play_path,
|
src: selectedItem.value.ai_video_url,
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -139,6 +152,32 @@ function refreshVideoPlayer() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function refreshOriginalPlayer() {
|
||||||
|
nextTick(() => {
|
||||||
|
if (originalPlayer.value) {
|
||||||
|
originalPlayer.value.src([
|
||||||
|
{
|
||||||
|
src: selectedItem.value.raw_video_url,
|
||||||
|
type: 'video/mp4',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
if (!originalVideoEl.value) return;
|
||||||
|
originalPlayer.value = videojs(originalVideoEl.value, {
|
||||||
|
controls: true,
|
||||||
|
autoplay: false,
|
||||||
|
preload: 'auto',
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: selectedItem.value.raw_video_url,
|
||||||
|
type: 'video/mp4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const showInfoStr = ref<Record<string, number | string>>({});
|
const showInfoStr = ref<Record<string, number | string>>({});
|
||||||
|
|
||||||
const chartRef1 = ref<EchartsUIType>();
|
const chartRef1 = ref<EchartsUIType>();
|
||||||
@@ -150,73 +189,63 @@ const { renderEcharts: renderEcharts2 } = useEcharts(chartRef2);
|
|||||||
function refreshLineChart() {
|
function refreshLineChart() {
|
||||||
const data = selectedItem.value;
|
const data = selectedItem.value;
|
||||||
showInfoStr.value = {
|
showInfoStr.value = {
|
||||||
项目名: data.v_name,
|
项目名: data.name,
|
||||||
文件名: data.v_file_name,
|
文件大小: `${data.size_kb} KB`,
|
||||||
文件大小: `${data.v_size} MB`,
|
总时长: `${data.duration} 秒`,
|
||||||
总时长: `${data.v_duration} 秒`,
|
分辨率: data.resolution,
|
||||||
分辨率: data.v_resolution,
|
视频编码格式: data.video_codec,
|
||||||
视频编码格式: data.v_video_codec,
|
音频编码格式: data.audio_codec,
|
||||||
音频编码格式: data.v_audio_codec,
|
总体比特率: data.overall_bit_rate,
|
||||||
总体比特率: data.v_overall_bit_rate,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
detailList.value = data.v_details_list || [];
|
const temp = detailList.value;
|
||||||
|
// 1. X轴
|
||||||
|
const xAxisData = temp.map((item) => item.time_stamp);
|
||||||
|
|
||||||
const detail = selectedItem.value.v_a_details;
|
// 2. 获取类别(other_info 的 key)
|
||||||
let yTotalData = Array.isArray(detail.yTotalData)
|
const categories = Object.keys(temp[0]?.other_info || {});
|
||||||
? detail.yTotalData.map((item: any) => [item.first, item.second])
|
|
||||||
: []; // 默认值为空数组
|
|
||||||
let yMaskedData = Array.isArray(detail.yMaskedData)
|
|
||||||
? detail.yMaskedData.map((item: any) => [item.first, item.second])
|
|
||||||
: []; // 默认值为空数组
|
|
||||||
const areaData = Array.isArray(detail.areaData)
|
|
||||||
? detail.areaData.map((actionGroup: any) => {
|
|
||||||
return actionGroup.map((action: any) => {
|
|
||||||
return { xAxis: action.xAxis, itemStyle: action.itemStyle };
|
|
||||||
});
|
|
||||||
})
|
|
||||||
: []; // 默认值为空数组
|
|
||||||
yTotalData = yTotalData.map((item: any) => [
|
|
||||||
new Date(item[0]).getTime(),
|
|
||||||
item[1],
|
|
||||||
]);
|
|
||||||
yMaskedData = yMaskedData.map((item: any) => [
|
|
||||||
new Date(item[0]).getTime(),
|
|
||||||
item[1],
|
|
||||||
]);
|
|
||||||
|
|
||||||
renderEcharts1({
|
// 3. 构造 series
|
||||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
const series = categories.map((key) => ({
|
||||||
series: [
|
name: key,
|
||||||
{
|
|
||||||
name: '总人数',
|
|
||||||
type: 'line',
|
type: 'line',
|
||||||
step: 'end',
|
stack: 'Total',
|
||||||
data: yTotalData,
|
data: temp.map((item) => item.other_info[key]),
|
||||||
markArea: {
|
}));
|
||||||
itemStyle: { color: 'rgba(255, 173, 177, 0.4)' },
|
|
||||||
data: areaData,
|
const option = {
|
||||||
},
|
tooltip: { trigger: 'axis' },
|
||||||
},
|
legend: { data: categories },
|
||||||
{ name: '口罩佩戴人数', type: 'line', step: 'end', data: yMaskedData },
|
grid: {
|
||||||
],
|
left: '3%',
|
||||||
tooltip: {
|
right: '4%',
|
||||||
axisPointer: {
|
bottom: '3%',
|
||||||
lineStyle: {
|
containLabel: true,
|
||||||
color: '#019680',
|
},
|
||||||
width: 1,
|
xAxis: {
|
||||||
},
|
type: 'category',
|
||||||
},
|
boundaryGap: false,
|
||||||
trigger: 'axis',
|
data: xAxisData,
|
||||||
},
|
},
|
||||||
xAxis: { type: 'time' },
|
yAxis: {
|
||||||
yAxis: { type: 'value' },
|
type: 'value',
|
||||||
});
|
},
|
||||||
|
series,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderEcharts1(option);
|
||||||
|
|
||||||
|
const chartData = data.other_info;
|
||||||
|
|
||||||
|
// 将对象转换为 echarts 的 [{value, name}] 数组
|
||||||
|
const seriesData = Object.entries(chartData).map(([name, value]) => ({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
|
||||||
const maskedRatio = data.v_a_average_masked_ratio * 100;
|
|
||||||
const noMaskedRatio = 100 - maskedRatio;
|
|
||||||
renderEcharts2({
|
renderEcharts2({
|
||||||
legend: { top: '5%', left: 'center' },
|
legend: { top: '5%', left: 'center' },
|
||||||
|
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
animationDelay() {
|
animationDelay() {
|
||||||
@@ -225,17 +254,12 @@ function refreshLineChart() {
|
|||||||
animationEasing: 'exponentialInOut',
|
animationEasing: 'exponentialInOut',
|
||||||
animationType: 'scale',
|
animationType: 'scale',
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: false,
|
||||||
|
|
||||||
|
// 你原来的颜色保留
|
||||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
||||||
data: [
|
|
||||||
{
|
data: seriesData, // 关键改动!!!
|
||||||
value: maskedRatio,
|
|
||||||
name: '非正茧(%)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: noMaskedRatio,
|
|
||||||
name: '正茧(%)',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
emphasis: {
|
emphasis: {
|
||||||
label: {
|
label: {
|
||||||
fontSize: '12',
|
fontSize: '12',
|
||||||
@@ -255,11 +279,11 @@ function refreshLineChart() {
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
padAngle: 5,
|
padAngle: 5,
|
||||||
name: '正茧平均占比',
|
|
||||||
radius: ['40%', '70%'],
|
radius: ['40%', '70%'],
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
},
|
},
|
||||||
@@ -270,7 +294,6 @@ function refreshLineChart() {
|
|||||||
refreshVideoPlayer();
|
refreshVideoPlayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const projectName = ref('');
|
|
||||||
const fileName = ref('');
|
const fileName = ref('');
|
||||||
const selectedFile = ref<File | null>(null);
|
const selectedFile = ref<File | null>(null);
|
||||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||||
@@ -293,15 +316,19 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
async function uploadFile() {
|
async function uploadFile() {
|
||||||
// 先关闭弹窗
|
// 先关闭弹窗
|
||||||
modalApi.close();
|
modalApi.close();
|
||||||
const formData = new FormData();
|
const uploadUrl = await getIVASCUploadToken();
|
||||||
formData.append('file', selectedFile.value!);
|
// 2. 使用 presigned URL 上传文件
|
||||||
formData.append('projectName', projectName.value);
|
const file = selectedFile.value;
|
||||||
await createImageTaskV2(formData).then(() => {
|
message.success('正在上传视频');
|
||||||
|
await axios.put(uploadUrl, file, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': file.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
message.success('正在分析,请稍后刷新列表查看');
|
||||||
// 清空表单
|
// 清空表单
|
||||||
projectName.value = '';
|
|
||||||
fileName.value = '';
|
fileName.value = '';
|
||||||
selectedFile.value = null;
|
selectedFile.value = null;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectFile() {
|
function selectFile() {
|
||||||
@@ -334,10 +361,6 @@ function changePage(newPage) {
|
|||||||
<BaseModal />
|
<BaseModal />
|
||||||
<Modal>
|
<Modal>
|
||||||
<Form layout="vertical">
|
<Form layout="vertical">
|
||||||
<Form.Item label="任务名称">
|
|
||||||
<Input v-model:value="projectName" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="上传视频*" required>
|
<Form.Item label="上传视频*" required>
|
||||||
<div
|
<div
|
||||||
@click="selectFile"
|
@click="selectFile"
|
||||||
@@ -380,13 +403,13 @@ function changePage(newPage) {
|
|||||||
<div class="flex-1 space-y-2 overflow-auto">
|
<div class="flex-1 space-y-2 overflow-auto">
|
||||||
<div
|
<div
|
||||||
v-for="item in list"
|
v-for="item in list"
|
||||||
:key="item.v_id"
|
:key="item.id"
|
||||||
@click="selectItem(item)"
|
@click="selectItem(item)"
|
||||||
class="cursor-pointer rounded border p-3 hover:bg-gray-100"
|
class="cursor-pointer rounded border p-3 hover:bg-gray-100"
|
||||||
:class="{ 'bg-gray-100': item.v_id === selectedItem?.v_id }"
|
:class="{ 'bg-gray-100': item.id === selectedItem?.id }"
|
||||||
>
|
>
|
||||||
<div class="text-base font-medium">{{ item.v_name }}</div>
|
<div class="text-base font-medium">{{ item.name }}</div>
|
||||||
<div class="text-sm text-gray-400">{{ item.v_a_time }}</div>
|
<div class="text-sm text-gray-400">{{ item.created_at }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
@@ -494,6 +517,17 @@ function changePage(newPage) {
|
|||||||
controls
|
controls
|
||||||
></video>
|
></video>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="activeTab === 'original'"
|
||||||
|
class="flex h-full space-x-4"
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
ref="originalVideoEl"
|
||||||
|
class="video-js vjs-default-skin h-full w-full"
|
||||||
|
preload="auto"
|
||||||
|
controls
|
||||||
|
></video>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Generated
+3
@@ -689,6 +689,9 @@ importers:
|
|||||||
ant-design-vue:
|
ant-design-vue:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.2.6(vue@3.5.16(typescript@5.8.3))
|
version: 4.2.6(vue@3.5.16(typescript@5.8.3))
|
||||||
|
axios:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 1.9.0
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
|
|||||||
Reference in New Issue
Block a user