From 79d024f9f63ba54787694e880a1d1a64e060aba7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com>
Date: Thu, 14 Nov 2024 15:11:35 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20=E4=BB=BB=E5=8A=A1?=
=?UTF-8?q?=E5=88=97=E8=A1=A8=E6=98=BE=E7=A4=BA=E6=B5=81=E7=A8=8B=20?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=20=E4=B8=8B=E8=BD=BD=E8=B5=84=E6=BA=90?=
=?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=8F=92=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Components/Pages/VideoTaskPage.razor | 68 +++++++-
.../Components/Pages/VideoTaskPage.razor.cs | 85 +++++++---
.../Components/Pages/VideoTaskPage.razor.css | 5 +
VideoAnalysis/Controllers/ApiController.cs | 7 +-
VideoAnalysis/Program.cs | 4 +-
VideoAnalysis/appsettings.json | 1 +
.../AICore/ChatGPT/KIMI/KIMI_GPT.cs | 55 ++++---
.../AICore/ChatGPT/KIMI/MoonshotClient.cs | 1 -
.../AICore/SherpaOnnx/SenseVoice.cs | 6 +-
.../AICore/SherpaOnnx/Speaker.cs | 1 +
VideoAnalysisCore/Common/AppCommon.cs | 4 +
VideoAnalysisCore/Common/DownloadFile.cs | 132 +++++++++++-----
VideoAnalysisCore/Common/RedisExpand.cs | 146 +++++++++++++-----
VideoAnalysisCore/Enum/QuestionTypeEnum.cs | 12 +-
VideoAnalysisCore/Enum/RedisChannelEnum.cs | 8 +-
VideoAnalysisCore/Model/Dto/VideoTaskDto.cs | 6 +
VideoAnalysisCore/Model/VideoTask.cs | 1 +
VideoAnalysisCore/VideoAnalysisCore.csproj | 1 +
18 files changed, 402 insertions(+), 141 deletions(-)
diff --git a/VideoAnalysis/Components/Pages/VideoTaskPage.razor b/VideoAnalysis/Components/Pages/VideoTaskPage.razor
index d298a67..b93a5b5 100644
--- a/VideoAnalysis/Components/Pages/VideoTaskPage.razor
+++ b/VideoAnalysis/Components/Pages/VideoTaskPage.razor
@@ -5,18 +5,72 @@
@using SqlSugar
@using VideoAnalysisCore.Model
@using VideoAnalysisCore.Model.Dto
+@using VideoAnalysisCore.Enum
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ @rowData.Data.Progress%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (!string.IsNullOrEmpty( @rowData.Data.ErrorMessage))
+ {
+
+ @rowData.Data.ErrorMessage
+
+ }
+
+
+
+
diff --git a/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs b/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs
index 41c1867..676c712 100644
--- a/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs
+++ b/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs
@@ -1,5 +1,6 @@
using AntDesign;
using AntDesign.TableModels;
+using FFmpeg.NET.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using SqlSugar;
@@ -19,6 +20,7 @@ namespace Learn.VideoAnalysis.Components.Pages
[Inject] private Repository taskDB { get; set; } = default!;
+ [Inject] private INotificationService _notice { get; set; } = default!;
IEnumerable _selectedRows = [];
ITable _table;
@@ -26,6 +28,8 @@ namespace Learn.VideoAnalysis.Components.Pages
RefAsync _total = 0;
bool tableLoading = false;
+ private VideoTaskDto selectData;
+ private bool rowRestartLoading=false;
///
/// 重试
@@ -34,16 +38,18 @@ namespace Learn.VideoAnalysis.Components.Pages
async void ReStart(VideoTaskDto query)
{
var lastEnum = (await RedisExpand.Redis.HMGetAsync(RedisExpandKey.Task(query.Id), "LastEnum")).FirstOrDefault() ;
- await taskDB.UpdateAsync(s => new VideoTask()
- { ErrorMessage = string.Empty },s => s.Id == query.Id);
+ await RedisExpand.SetTaskErrorMessage(query.Id, string.Empty);
RedisExpand.InsertChannel(lastEnum, query.Id);
}
+ private QueryModel lastQuery = null;
///
/// 分页 查询 筛选 时
///
///
+ ///
async void OnChange(QueryModel query)
{
+ lastQuery= query;
tableLoading = true;
List where = default!;
if (query.FilterModel != null && ((query.FilterModel?.Count() ?? 0) > 0))
@@ -55,39 +61,72 @@ namespace Learn.VideoAnalysis.Components.Pages
.Select()
.ToPageListAsync(query.PageIndex - 1, query.PageSize, _total);
- foreach (var item in _dataSource)
- {
- if (!string.IsNullOrEmpty(item.ErrorMessage) || item.LastEnum == RedisChannelEnum.EndTask)
- continue;
- item.Progress = RedisExpand.Redis.HMGet(RedisExpandKey.Task(item.Id), "Progress").FirstOrDefault();
- }
-
- tableLoading = false;
+ tableLoading = false;
StateHasChanged();
}
- private static System.Timers.Timer _timer;
- public void StartTimer(Object source, System.Timers.ElapsedEventArgs e)
+ public void RowRestart(RowData rowData)
{
- if (_dataSource is null)
+ rowRestartLoading = true;
+ var item = rowData.Data;
+ if (item is null)
return;
- foreach (var item in _dataSource)
- {
- if (!string.IsNullOrEmpty(item.ErrorMessage) || item.LastEnum == RedisChannelEnum.EndTask)
- continue;
- item.Progress = RedisExpand.Redis.HMGet(RedisExpandKey.Task(item.Id), "Progress").FirstOrDefault();
- }
-
+ var data = RedisExpand.Redis.HMGet(RedisExpandKey.Task(item.Id),
+ "Progress", "LastEnum", "StartTime", "ErrorMessage");
+ item.Progress = double.Parse(data[0]);
+ item.LastEnum = data[1].ToEnum() ?? default;
+ item.StartTimeDic = System.Text.Json.JsonSerializer.Deserialize>(data[2]) ?? null;
+ item.ErrorMessage = data[3];
+ rowRestartLoading = false;
StateHasChanged();
+ }
+ ///
+ ///
+ ///
+ ///
+ private string RowST(RowData rowData, RedisChannelEnum e)
+ {
+ var dic = rowData.Data.StartTimeDic;
+ if (dic is null || !dic.ContainsKey(e))
+ return "--";
+ return dic[e].ToString();
+ }
+ private string RowSTStatus(RowData rowData)
+ {
+ var dic = rowData.Data.StartTimeDic;
+ if (dic is null)
+ return "wait";
+ if(!string.IsNullOrEmpty(rowData.Data.ErrorMessage))
+ return "error";
+ if(dic.ContainsKey(RedisChannelEnum.EndTask))
+ return "finish";
+ return "wait";
+ }
+ private int RowSTIndex(RowData rowData)
+ {
+ var dic = rowData.Data.StartTimeDic;
+ if (dic is null )
+ return 0;
+ return (int)dic.LastOrDefault().Key;
+ }
+ private void OnExpand(RowData rowData)
+ {
+ RowRestart(rowData);
+ }
+ ///
+ /// 在渲染页面之后
+ ///
+ ///
+ ///
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+
}
///
/// 初始化
///
protected override void OnInitialized()
{
- _timer = new System.Timers.Timer(2000);
- _timer.Elapsed += StartTimer;
- _timer.Enabled = true;
}
private async Task Comfirm(string message)
diff --git a/VideoAnalysis/Components/Pages/VideoTaskPage.razor.css b/VideoAnalysis/Components/Pages/VideoTaskPage.razor.css
index aca1ed1..0003041 100644
--- a/VideoAnalysis/Components/Pages/VideoTaskPage.razor.css
+++ b/VideoAnalysis/Components/Pages/VideoTaskPage.razor.css
@@ -1,3 +1,8 @@
input[aria-hidden="true"] {
display: none !important;
}
+
+.task_status_tag {
+ display:flex;
+
+}
diff --git a/VideoAnalysis/Controllers/ApiController.cs b/VideoAnalysis/Controllers/ApiController.cs
index 6db61d3..5585689 100644
--- a/VideoAnalysis/Controllers/ApiController.cs
+++ b/VideoAnalysis/Controllers/ApiController.cs
@@ -8,6 +8,8 @@ using VideoAnalysisCore.Model;
using VideoAnalysisCore.AICore.FFMPGE;
using VideoAnalysisCore.Model.Dto;
using VideoAnalysisCore.AICore.ChatGPT.Dto;
+using AntDesign;
+using System.Threading.Tasks;
namespace Learn.VideoAnalysis.Controllers
{
@@ -47,6 +49,8 @@ namespace Learn.VideoAnalysis.Controllers
.WhereIF(taskId!=0, s => s.Id == taskId)
.WhereIF(string.IsNullOrEmpty(tagId), s => s.TagId == tagId)
.FirstAsync();
+ if(task is null)
+ return BadRequest();
if (task.LastEnum != RedisChannelEnum.EndTask)
return BadRequest(new { Enum = task.LastEnum ,Task = task.ChatAnalysis});
return Ok(new { Enum = task.LastEnum, Task = task.ChatAnalysis });
@@ -90,8 +94,7 @@ namespace Learn.VideoAnalysis.Controllers
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(s => s.Name, s => s.GetValue(task));
RedisExpand.Redis.HMSet(RedisExpandKey.Task(task.Id), hashEntries);
-
- RedisExpand.InsertChannel(RedisChannelEnum.DownloadFile,task.Id);
+ RedisExpand.Redis.Set(RedisExpandKey.ChannelKey, task.Id);
return Ok(task.Id);
}
}
diff --git a/VideoAnalysis/Program.cs b/VideoAnalysis/Program.cs
index cda2602..c3ca055 100644
--- a/VideoAnalysis/Program.cs
+++ b/VideoAnalysis/Program.cs
@@ -51,8 +51,8 @@ namespace Learn.VideoAnalysis
builder.Configuration.GetSection("AppConfig").Bind(AppCommon.Config);
//ʼ
- //Speaker.Init();
- //RedisExpand.Init();
+ RedisExpand.Init();
+ Speaker.Init();
//SenseVoice.Init();
diff --git a/VideoAnalysis/appsettings.json b/VideoAnalysis/appsettings.json
index 5bc6be3..c38de8d 100644
--- a/VideoAnalysis/appsettings.json
+++ b/VideoAnalysis/appsettings.json
@@ -7,6 +7,7 @@
},
"AllowedHosts": "*",
"AppConfig": {
+ "ID": "APP0001",//程序唯一值
"Redis": {
"ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10"
},
diff --git a/VideoAnalysisCore/AICore/ChatGPT/KIMI/KIMI_GPT.cs b/VideoAnalysisCore/AICore/ChatGPT/KIMI/KIMI_GPT.cs
index a3ce0b3..d3dcc87 100644
--- a/VideoAnalysisCore/AICore/ChatGPT/KIMI/KIMI_GPT.cs
+++ b/VideoAnalysisCore/AICore/ChatGPT/KIMI/KIMI_GPT.cs
@@ -15,6 +15,8 @@ using System.Reflection;
using FreeRedis;
using VideoAnalysisCore.Model.Dto;
using Newtonsoft.Json;
+using AntDesign;
+using SqlSugar.IOC;
namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
{
@@ -55,7 +57,7 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
criteriaBuilder.Append(item.Id);
criteriaBuilder.Append(":");
criteriaBuilder.Append(item.NamePrompt);
- criteriaBuilder.Append("请基于解释打分(0-10分 6分为及格线) 结果类型 int |");
+ criteriaBuilder.Append("请基于解释打分(0-10分 6分为及格) 结果类型 int |");
}
//拼接枚举提问
foreach (var value in System.Enum.GetValues(typeof(QuestionTypeEnum)))
@@ -71,7 +73,7 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
criteriaBuilder.Append("|");
}
- var resFormat = "[{问题编号:int,结果:array|bool,问题解释:string}]";
+ var resFormat = "[{问题编号:int,结果:array|bool|object,问题解释:string}]";
var postMessages =
$"以下是一段音频的字幕,分析这段字幕(格式 说话人:开始秒:结束秒:内容|下一段字幕)." +
$"来简明的回答提出的问题 问题列表 {criteriaBuilder} " +
@@ -95,6 +97,7 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
}
};
var chatResp = await moonshotClient.Chat(chatRep);
+ RedisExpand.SetTaskGPTCached(task, chatResp);
if (chatResp is null || chatResp.error != null)
throw new Exception($"KIMI模型返回异常 Chat 请求参数: {System.Text.Json.JsonSerializer.Serialize(chatRep)} " +
$" chatResp {System.Text.Json.JsonSerializer.Serialize(chatResp)}");
@@ -121,7 +124,7 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
{
Id = criteriaDic[s.问题编号].Id,
ImprovedMethods = criteriaDic[s.问题编号].ImprovedMethods,
- Analyze = s.问题解释??string.Empty,
+ Analyze = s.问题解释 ?? string.Empty,
Score = s.结果.ToObject(),
Prompt = criteriaDic[s.问题编号].Flaw,
}).ToArray(),
@@ -137,7 +140,7 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
};
//高频词汇
- gptRes.Hotwords = arr2[(int)QuestionTypeEnum.高频词].结果.ToObject()??["暂无数据"];
+ gptRes.Hotwords = arr2[(int)QuestionTypeEnum.高频词].结果.ToObject() ?? ["暂无数据"];
//时间段概览
gptRes.TimeOverview = arr2[(int)QuestionTypeEnum.时间段概览]
@@ -148,27 +151,33 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
//分析上课时间段情况 分析 独立学习 小组合作 随堂练习等情况
var extraTimeBase = arr2[(int)QuestionTypeEnum.额外课堂情况]
- .结果.ToObject>()?.Select(s =>
- new TimeBase(s.Value,s.Key.ToString()));
+ .结果.ToObject();
+ if (extraTimeBase is not null)
+ foreach (var item in extraTimeBase)
+ {
+ if (item is null)
+ continue;
+ var arr = gptRes.TimeBase?
+ .Where(s => s.Start >= item.Start && s.End <= item.End);
+ if (arr is null)
+ continue;
+ foreach (var s in arr)
+ s.TimeBaseType = item.Content.ToEnum();
+ }
+ var totalTokens = chatResp?.usage.total_tokens ?? 0;
+ if (totalTokens > 1)
+ {
+ var tid = long.Parse(task);
+ await videoTaskDB.AsUpdateable()
+ .SetColumns(it => it.TotalTokens == totalTokens)//SetColumns是可以叠加的 写2个就2个字段赋值
+ .Where(it => it.Id == tid)
+ .ExecuteCommandAsync();
+ }
+ await RedisExpand.Redis
+ .HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes);
+ RedisExpand.InsertChannel(Enum.RedisChannelEnum.EndTask, task);
-
- var tId = long.Parse(task);
- var taskData = await videoTaskDB.GetFirstAsync(s => s.Id == tId);
- taskData.ChatAnalysis = gptRes;
- taskData.ChatAnalysisScore = gptRes.Assessment.Merit?.Sum(s=>s.Score)??0;
- taskData.ErrorMessage = string.Empty;
- taskData.TotalTokens = chatResp.usage.total_tokens;
- taskData.LastEnum = RedisChannelEnum.EndTask;
- await videoTaskDB.AsUpdateable(taskData)
- .UpdateColumns(it => new
- {
- it.ChatAnalysis,
- it.ChatAnalysisScore,
- it.ErrorMessage,
- it.TotalTokens,
- it.LastEnum,
- }).ExecuteCommandAsync();
return gptRes;
}
}
diff --git a/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs b/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs
index dfbd14f..6cceda1 100644
--- a/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs
+++ b/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs
@@ -57,7 +57,6 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
{
var requestBody = Newtonsoft.Json.JsonConvert.SerializeObject(chatReq);
var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody);
- var resStr = chatResp.Content.ReadAsStringAsync();
return await chatResp.Content.ReadFromJsonAsync();
}
diff --git a/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs b/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs
index 02c20cd..49166de 100644
--- a/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs
+++ b/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs
@@ -78,6 +78,8 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
///
public static async Task RunTask(string task)
{
+ if (OR is null)
+ Init();
var filePath = Path.Combine(task.LocalPath(), task + ".wav");
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
throw new Exception("task 音频路径未找到");
@@ -87,7 +89,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
int windowSize = VADModelConfig.SileroVad.WindowSize;
int sampleRate = VADModelConfig.SampleRate;
int numIter = numSamples / windowSize;
-
+ var totalSecond = numSamples / (double)sampleRate;
var res = new List(500);
for (int i = 0; i != numIter; ++i)
{
@@ -115,7 +117,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
Start= startTime,
End = startTime + duration
});
- var progress = (double)(startTime + duration) / numSamples * 100;
+ var progress = (double)(startTime + duration) / (totalSecond) * 100;
RedisExpand.SetTaskProgress(task, progress);
}
diff --git a/VideoAnalysisCore/AICore/SherpaOnnx/Speaker.cs b/VideoAnalysisCore/AICore/SherpaOnnx/Speaker.cs
index d60e450..535b10f 100644
--- a/VideoAnalysisCore/AICore/SherpaOnnx/Speaker.cs
+++ b/VideoAnalysisCore/AICore/SherpaOnnx/Speaker.cs
@@ -31,6 +31,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
config.Embedding.Model = Path.Combine(AppCommon.AIModelFile, "wespeaker", "wespeaker_zh_cnceleb_resnet34_LM.onnx");
//说话人数量
config.Clustering.NumClusters = speakerNumber;
+ config.Embedding.NumThreads = 4;
//需要使用GPU
if (!useGPU)
config.Embedding.Provider = "cuda";
diff --git a/VideoAnalysisCore/Common/AppCommon.cs b/VideoAnalysisCore/Common/AppCommon.cs
index bfb0b47..f4406bc 100644
--- a/VideoAnalysisCore/Common/AppCommon.cs
+++ b/VideoAnalysisCore/Common/AppCommon.cs
@@ -303,6 +303,10 @@ namespace VideoAnalysisCore.Common
///
public class AppConfig
{
+ ///
+ /// 程序ID
+ ///
+ public string ID { get; set; } = string.Empty;
///
/// redis
///
diff --git a/VideoAnalysisCore/Common/DownloadFile.cs b/VideoAnalysisCore/Common/DownloadFile.cs
index 221b567..f40ed51 100644
--- a/VideoAnalysisCore/Common/DownloadFile.cs
+++ b/VideoAnalysisCore/Common/DownloadFile.cs
@@ -1,5 +1,10 @@
-using System;
+using AntDesign;
+using Downloader;
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
using System.IO;
+using System.Net;
using System.Threading.Tasks;
namespace VideoAnalysisCore.Common
@@ -9,6 +14,67 @@ namespace VideoAnalysisCore.Common
///
public class DownloadFile
{
+ static DownloadConfiguration Opt { get; set; } = default!;
+ ///
+ /// 初始化下载器
+ ///
+ /// 下载速度mb/s 默认8
+ static void Init(int DownloadSpeed = 8)
+ {
+ Opt = new DownloadConfiguration()
+ {
+ // 通常,主机支持的最大值为8000字节,默认值是8000
+ BufferBlockSize = 10240,
+ // 要下载的文件部分数量,默认值是1
+ ChunkCount = 8,
+ // 下载速度限制为2MB/秒,默认值为零(即无限制)
+ MaximumBytesPerSecond = 1024 * 1024 * DownloadSpeed,
+ // 故障转移时的最大重试次数
+ MaxTryAgainOnFailover = 5,
+ // 每50MB后释放内存缓冲区
+ MaximumMemoryBufferBytes = 1024 * 1024 * 50,
+ // 是否并行下载文件的各个部分。默认值为false
+ ParallelDownload = true,
+ // 并行下载的数量。默认值与分块数量相同
+ ParallelCount = 4,
+ // 每个流块读取器的超时时间(毫秒),默认值是1000
+ Timeout = 1000,
+ // 如果只想下载大型文件的特定字节范围,则设置为true
+ RangeDownload = false,
+ // 大型文件下载范围的起始偏移量
+ RangeLow = 0,
+ // 大型文件下载范围的结束偏移量
+ RangeHigh = 0,
+ // 下载失败完成时清除包块数据,默认值为false
+ ClearPackageOnCompletionWithFailure = true,
+ // 将文件分多个部分下载时的最小分块大小,默认值是512
+ MinimumSizeOfChunking = 1024,
+ // 在开始下载之前,按照文件大小预留文件的存储空间,默认值为false
+ ReserveStorageSpaceBeforeStartingDownload = true,
+ // 在下载进度改变事件中,通过ReceivedBytes获取按需下载的数据
+ EnableLiveStreaming = false,
+ // 配置和自定义请求头
+ RequestConfiguration =
+ {
+ Accept = "*/*",
+ //CookieContainer = cookies,
+ Headers = new WebHeaderCollection(), // { 你的自定义头部信息 }
+ KeepAlive = true, // 默认值为false
+ ProtocolVersion = HttpVersion.Version11, // 默认值是HTTP 1.1
+ UseDefaultCredentials = false,
+ // 你的自定义用户代理或你的应用名称/应用版本。
+ UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
+ //Proxy = new WebProxy()//使用代理
+ //{
+ // Address = new Uri("http://Learn.VideoAnalysis"),
+ // UseDefaultCredentials = false,
+ // Credentials = System.Net.CredentialCache.DefaultNetworkCredentials,
+ // BypassProxyOnLocal = true
+ //}
+ }
+ };
+ }
+
// 根据 Content-Type 映射文件后缀
static string GetExtensionFromContentType(HttpResponseMessage res)
{
@@ -35,62 +101,56 @@ namespace VideoAnalysisCore.Common
///
public static async Task RunTask(string task)
{
+ if (Opt is null)
+ Init();
//获取资源文件 地址
var fileUrl = RedisExpand.Redis.HMGet(RedisExpandKey.Task(task), "MediaUrl")
.FirstOrDefault();
if (string.IsNullOrEmpty(fileUrl))
throw new Exception($"任务id[{task}] 资源地址无效 {fileUrl}");
- using HttpClient client = new HttpClient();
- // 发送 GET 请求以下载文件
- var response = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead);
- // 确保响应是成功的
- response.EnsureSuccessStatusCode();
-
// 尝试从 URL 中获取文件后缀
string fileExtension = Path.GetExtension(new Uri(fileUrl).AbsolutePath);
//否则 获取 从Content-Type 获取文件后缀
if (string.IsNullOrEmpty(fileExtension))
- fileExtension = GetExtensionFromContentType(response);
+ throw new Exception($"未能从资源路径中获取文件后缀");
//创建下载文件缓存路径
if (!Directory.Exists(AppCommon.TaskCachedFile)) Directory.CreateDirectory(AppCommon.TaskCachedFile);
-
- // 获取文件大小
- var totalBytes = response.Content.Headers.ContentLength;
- if (!totalBytes.HasValue)
- throw new Exception(task+" 未能获取到下载文件的 ContentLength ");
+
var localPath = task.LocalPath();
- var outputPath = Path.Combine(localPath, task + fileExtension);
+ var outputPath = Path.Combine(localPath, task + fileExtension);
if (!Directory.Exists(localPath)) Directory.CreateDirectory(localPath);
-
- //同步到redis
RedisExpand.Redis.HSet(RedisExpandKey.Task(task), "LocalMediaPath", outputPath);
- // 打开本地文件流以写入文件
- using var fs = new FileStream(outputPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
- // 读取响应内容流
- using var contentStream = await response.Content.ReadAsStreamAsync();
- var buffer = new byte[512 * 1024]; // 512KB 缓冲区
- long totalBytesRead = 0;
- var count = 0;
- int bytesRead;
- while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
+ IDownload download = DownloadBuilder.New()
+ .WithUrl(fileUrl)
+ .WithDirectory(localPath)
+ .WithFileName(task + fileExtension)
+ .WithConfiguration(Opt)
+ .Build();
+ download.DownloadProgressChanged += (object? sender, Downloader.DownloadProgressChangedEventArgs e) =>
{
- count++;
- await fs.WriteAsync(buffer, 0, bytesRead);
- totalBytesRead += bytesRead;
-
- // 计算下载进度
- if (count % 30 == 0)
+ RedisExpand.SetTaskProgress(task, e.ProgressPercentage);
+ };
+ download.DownloadFileCompleted +=async (object? sender, AsyncCompletedEventArgs e) =>
+ {
+ if (download.Status == DownloadStatus.Failed && e.Error!=null)
{
- var progress = (double)totalBytesRead / totalBytes.Value * 100;
- RedisExpand.SetTaskProgress(task, progress);
+ await RedisExpand.SetTaskErrorMessage(long.Parse(task), e.Error)
+ .ConfigureAwait(false);//不切回上下文
+ return;
+ }
+ else if (download.Status == DownloadStatus.Completed)
+ {
+ //加入下一队列
+ RedisExpand.InsertChannel(Enum.RedisChannelEnum.SeparateAudio, task);
+ return;
}
- }
- //加入下一队列
- RedisExpand.InsertChannel(Enum.RedisChannelEnum.SeparateAudio, task);
+ };
+ await download.StartAsync();
+
}
}
}
diff --git a/VideoAnalysisCore/Common/RedisExpand.cs b/VideoAnalysisCore/Common/RedisExpand.cs
index 56f97d1..b10f999 100644
--- a/VideoAnalysisCore/Common/RedisExpand.cs
+++ b/VideoAnalysisCore/Common/RedisExpand.cs
@@ -1,4 +1,6 @@
-using FreeRedis;
+using AntDesign;
+using FreeRedis;
+using FreeRedis.Internal;
using Microsoft.Extensions.DependencyInjection;
using SqlSugar.IOC;
using System;
@@ -6,6 +8,7 @@ using System.Threading.Channels;
using System.Threading.Tasks;
using System.Xml.Linq;
using VideoAnalysisCore.AICore.ChatGPT;
+using VideoAnalysisCore.AICore.ChatGPT.Dto;
using VideoAnalysisCore.AICore.FFMPGE;
//using VideoAnalysisCore.AICore.FFMPGE;
@@ -13,6 +16,7 @@ using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.AICore.Whisper;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Model;
+using VideoAnalysisCore.Model.Dto;
namespace VideoAnalysisCore.Common
{
@@ -28,7 +32,7 @@ namespace VideoAnalysisCore.Common
///
/// 基础Channel key
///
- public const string ChannelKey = BaseKey + "Channel:";
+ public const string ChannelKey = BaseKey + "TaskChannel";
///
/// 下载文件
///
@@ -55,20 +59,13 @@ namespace VideoAnalysisCore.Common
///
public const string TaskArr = BaseKey + "TaskArr";
- ///
- /// 获取枚举RedisKey
- ///
- ///
- ///
- public static string EnumKey(RedisChannelEnum e)
- {
- return ChannelKey + e.ToString();
- }
///
/// 任务对象地址
///
public static string Task(object taskId) => BaseKey + "Task:" + taskId;
+ public static string IDTask => BaseKey + AppCommon.Config.ID;
+ public static string TaskGPT(object taskId) => Task(taskId) + ":GPTCached";
}
///
@@ -80,6 +77,12 @@ namespace VideoAnalysisCore.Common
/// redis 连接
///
public static RedisClient Redis = new RedisClient(AppCommon.Config.Redis.ConnectionString);
+ public static Dictionary> SubscribeList = new Dictionary>();
+ ///
+ /// 队列池
+ ///
+ static SubscribeListObject? Subscribe;
+
///
/// 初始化 redis
/// 需要在初始化配置文件时候调用
@@ -92,6 +95,14 @@ namespace VideoAnalysisCore.Common
InitChannel();
}
+ ///
+ /// 获取任务进度
+ ///
+ ///
+ public static void SetTaskGPTCached(object taskId, object data)
+ {
+ Redis.Set(RedisExpandKey.TaskGPT(taskId), data, 3600);
+ }
///
/// 获取任务进度
///
@@ -105,9 +116,9 @@ namespace VideoAnalysisCore.Common
///
/// 进度百分比
///
- public static void SetTaskProgress(object taskId,double p)
+ public static void SetTaskProgress(object taskId, double p)
{
- Redis.HMSet(RedisExpandKey.Task(taskId), "Progress", Math.Round(p,2));
+ Redis.HMSet(RedisExpandKey.Task(taskId), "Progress", Math.Round(p, 2));
}
///
@@ -117,6 +128,7 @@ namespace VideoAnalysisCore.Common
/// 任务id
public static void InsertChannel(RedisChannelEnum @enum, object taskId)
{
+ if (taskId is null) throw new Exception("taskId为空");
if (Redis is null) throw new Exception("redis未初始化");
var startTime = Redis.HMGet>(RedisExpandKey.Task(taskId), "StartTime").FirstOrDefault();
@@ -128,28 +140,61 @@ namespace VideoAnalysisCore.Common
startTime[@enum] = DateTime.Now;
Redis.HMSet(RedisExpandKey.Task(taskId), "StartTime", startTime);
- Redis.LPush(RedisExpandKey.EnumKey(@enum), taskId);
+ if(!SubscribeList.ContainsKey(@enum))
+ throw new Exception(@enum+" 未实现");
+
+ SubscribeList[@enum].Invoke(taskId.ToString());
+ }
+
+ public static async Task TaskEnd(string task)
+ {
+ var tId = long.Parse(task);
+ var gptRes = (await RedisExpand.Redis
+ .HMGetAsync(RedisExpandKey.Task(task), "ChatAnalysis")).FirstOrDefault();
+ if (gptRes is null)
+ throw new Exception("未能读取到GPT处理结果");
+
+ var taskData = await DbScoped.SugarScope.Queryable()
+ .FirstAsync(s => s.Id == tId);
+ taskData.ChatAnalysis = gptRes;
+ taskData.ChatAnalysisScore = gptRes.Assessment.Merit?.Sum(s => s.Score) ?? 0;
+ taskData.ErrorMessage = string.Empty;
+ taskData.LastEnum = RedisChannelEnum.EndTask;
+ await DbScoped.SugarScope.Updateable(taskData)
+ .UpdateColumns(it => new
+ {
+ it.ChatAnalysis,
+ it.ChatAnalysisScore,
+ it.ErrorMessage,
+ it.TotalTokens,
+ it.LastEnum,
+ }).ExecuteCommandAsync();
+
+
+ await Redis.DelAsync(RedisExpandKey.IDTask);
+ await ReceivingTaskAsync();
}
///
/// 初始化 队列 任务
///
- public static void InitChannel()
+ public static async void InitChannel()
{
if (Redis is null) throw new Exception("redis未初始化");
- Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.DownloadFile),
+ SubscribeList.Add(RedisChannelEnum.DownloadFile,
(msg) => { TouchChannel(RedisChannelEnum.DownloadFile, msg, DownloadFile.RunTask); });
- Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.SeparateAudio),
+
+ SubscribeList.Add(RedisChannelEnum.SeparateAudio,
(msg) => { TouchChannel(RedisChannelEnum.SeparateAudio, msg, FFMPGEHandle.Audio2WAV16KAsync); });
- Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.ParsingCaptions),
+ SubscribeList.Add(RedisChannelEnum.ParsingCaptions,
(msg) => { TouchChannel(RedisChannelEnum.ParsingCaptions, msg, SenseVoice.RunTask); });
- Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.ParsingSpeaker),
+ SubscribeList.Add(RedisChannelEnum.ParsingSpeaker,
(msg) => { TouchChannel(RedisChannelEnum.ParsingSpeaker, msg, Speaker.Run); });
- Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.ChatModelAnalysis),
+ SubscribeList.Add(RedisChannelEnum.ChatModelAnalysis,
(msg) =>
{
TouchChannel(RedisChannelEnum.ChatModelAnalysis, msg,
@@ -159,12 +204,38 @@ namespace VideoAnalysisCore.Common
if (scope is null || scope.ServiceProvider.GetService() is null)
throw new Exception("IBserGPT 未注入");
else
- return scope.ServiceProvider.GetService()?.CallGPT(task)??Task.CompletedTask;
+ return scope.ServiceProvider.GetService()?.CallGPT(task) ?? Task.CompletedTask;
});
});
- Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.CallBackSystem),
- (msg) => { TouchChannel(RedisChannelEnum.ParsingSpeaker, msg); });
+ SubscribeList.Add(RedisChannelEnum.EndTask,
+ (msg) => { TouchChannel(RedisChannelEnum.EndTask, msg, TaskEnd); });
+ await ReceivingTaskAsync();
+
+ }
+ ///
+ /// 重新接收新任务
+ ///
+ public static async Task ReceivingTaskAsync()
+ {
+ var oldTask = await Redis.GetAsync(RedisExpandKey.IDTask);
+ if (!string.IsNullOrEmpty(oldTask))
+ {
+ var lastEnum = (await Redis.HMGetAsync(RedisExpandKey.Task(oldTask), "LastEnum")).FirstOrDefault();
+ await SetTaskErrorMessage(long.Parse(oldTask), null);
+ InsertChannel(lastEnum, oldTask);
+ return;
+ }
+
+ Subscribe = Redis.SubscribeList(RedisExpandKey.ChannelKey, (taskId) =>
+ {
+ if (taskId is null) return;
+ Subscribe?.Dispose();
+ //存储当前机器的任务
+ Redis.Set(RedisExpandKey.IDTask, taskId);
+ Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 接收到任务 " + taskId);
+ InsertChannel(RedisChannelEnum.DownloadFile, taskId);
+ });
}
///
@@ -173,10 +244,22 @@ namespace VideoAnalysisCore.Common
///
///
///
- public static async Task SetTaskErrorMessage(long taskID, string errorMessage)
+ public static async Task SetTaskErrorMessage(long taskID, Exception? ex)
{
+ var error = string.Empty;
+ if (ex != null)
+ {
+ //执行任务时出现异常
+ error = ex.Message + ex.StackTrace;
+ Console.WriteLine("====================[出现异常]====================");
+ Console.WriteLine(ex.Message);
+ Console.WriteLine(ex.StackTrace);
+ Console.WriteLine("==============================================");
+ }
+
+ Redis.HMSet(RedisExpandKey.Task(taskID), "ErrorMessage", error);
return await DbScoped.SugarScope.Updateable()
- .SetColumns(it => it.ErrorMessage == errorMessage)//SetColumns是可以叠加的 写2个就2个字段赋值
+ .SetColumns(it => it.ErrorMessage == error)//SetColumns是可以叠加的 写2个就2个字段赋值
.Where(it => it.Id == taskID)
.ExecuteCommandAsync() == 1;
}
@@ -189,12 +272,12 @@ namespace VideoAnalysisCore.Common
public static async void TouchChannel(RedisChannelEnum key, string taskId, Func action = null)
{
if (taskId is null) return;
+ var tID = long.Parse(taskId);
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> " + key + " " + taskId);
if (action is not null)
{
try
{
- var tID = long.Parse(taskId);
Redis.HMSet(RedisExpandKey.Task(taskId), "LastEnum", key);
Redis.HMSet(RedisExpandKey.Task(taskId), "Progress", 0);
await DbScoped.SugarScope.Updateable()
@@ -206,15 +289,8 @@ namespace VideoAnalysisCore.Common
}
catch (Exception ex)
{
- //执行任务时出现异常
- var error = ex.Message + ex.StackTrace;
- await SetTaskErrorMessage(long.Parse(taskId), error);
-
- Console.WriteLine("====================[出现异常]====================");
- Console.WriteLine(ex.Message);
- Console.WriteLine(ex.StackTrace);
- Console.WriteLine("==============================================");
-
+ await SetTaskErrorMessage(tID, ex)
+ .ConfigureAwait(false);//不切回上下文
}
}
else
diff --git a/VideoAnalysisCore/Enum/QuestionTypeEnum.cs b/VideoAnalysisCore/Enum/QuestionTypeEnum.cs
index 8dd19a6..5dd928e 100644
--- a/VideoAnalysisCore/Enum/QuestionTypeEnum.cs
+++ b/VideoAnalysisCore/Enum/QuestionTypeEnum.cs
@@ -13,14 +13,14 @@ namespace VideoAnalysisCore.Enum
[Display(Prompt = "分析授课中使用的高频词" +
"10个频率从高到低 结果类型[]")]
高频词 = 100,
- [Display(Prompt = "总结字幕内容划分成10个时间段" +
- "并且提取它们的内容概览 结果类型[{Start:开始时间,End:结束时间,Content:概览}]")]
+ [Display(Prompt = "基于字幕内容知识点精准的划分成最多10个片段" +
+ "提取片段的内容概览,字幕开始秒,结束秒 结果类型[{Start:开始秒,End:结束秒,Content:概览}]")]
时间段概览 = 101,
- [Display(Prompt = "统计授课中教师提问类型的次数 提问类型" +
- "[重复回答,老师追问,简单性表扬,老师补充答案,表扬并补充答案] 结果类型{提问类型:次数}")]
+ [Display(Prompt = "统计授课中教师回答类型的次数 回答类型" +
+ "[重复回答,老师追问,简单性表扬,老师补充答案,表扬并补充答案] 结果类型{回答类型:次数}")]
提问类型 = 102,
- [Display(Prompt = " 分析授课中教师提到 类型" +
- "[独立学习,小组合作,随堂练习]的时间段 结果类型[{Start:开始时间/null,End:结束时间/null,Content:类型}]")]
+ [Display(Prompt = " 分析授课中教师提到 以下类型" +
+ "[独立学习,小组合作,随堂练习]的时间段,提取出其中字幕开始秒,结束秒 结果类型[{Start:开始秒,End:结束秒,Content:类型}/null]")]
额外课堂情况 = 103,
}
}
diff --git a/VideoAnalysisCore/Enum/RedisChannelEnum.cs b/VideoAnalysisCore/Enum/RedisChannelEnum.cs
index 187f0cc..281d95d 100644
--- a/VideoAnalysisCore/Enum/RedisChannelEnum.cs
+++ b/VideoAnalysisCore/Enum/RedisChannelEnum.cs
@@ -25,10 +25,10 @@
/// Chat模型分析
///
ChatModelAnalysis,
- ///
- /// 回调三方系统
- ///
- CallBackSystem,
+ /////
+ ///// 回调三方系统
+ /////
+ //CallBackSystem,
///
/// 结束任务
///
diff --git a/VideoAnalysisCore/Model/Dto/VideoTaskDto.cs b/VideoAnalysisCore/Model/Dto/VideoTaskDto.cs
index 4a912fa..3503180 100644
--- a/VideoAnalysisCore/Model/Dto/VideoTaskDto.cs
+++ b/VideoAnalysisCore/Model/Dto/VideoTaskDto.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoAnalysisCore.Enum;
+using System.Text.Json;
namespace VideoAnalysisCore.Model.Dto
{
@@ -64,5 +65,10 @@ namespace VideoAnalysisCore.Model.Dto
///
[DisplayName("创建时间")]
public DateTime CreateTime { get; set; } = DateTime.Now;
+ ///
+ /// 开始时间轴
+ /// 逻辑字段
+ ///
+ public Dictionary? StartTimeDic {get; set;}
}
}
diff --git a/VideoAnalysisCore/Model/VideoTask.cs b/VideoAnalysisCore/Model/VideoTask.cs
index 07c3d67..5e8c71f 100644
--- a/VideoAnalysisCore/Model/VideoTask.cs
+++ b/VideoAnalysisCore/Model/VideoTask.cs
@@ -97,6 +97,7 @@ namespace VideoAnalysisCore.Model
///
/// AI模型评分
///
+ [SugarColumn(IsNullable =true)]
public decimal? ChatAnalysisScore { get; set; }
///
/// 消耗token
diff --git a/VideoAnalysisCore/VideoAnalysisCore.csproj b/VideoAnalysisCore/VideoAnalysisCore.csproj
index 79861de..6aaf56a 100644
--- a/VideoAnalysisCore/VideoAnalysisCore.csproj
+++ b/VideoAnalysisCore/VideoAnalysisCore.csproj
@@ -46,6 +46,7 @@
+