优化 任务列表显示流程

优化 下载资源流程插件
This commit is contained in:
小肥羊 2024-11-14 15:11:35 +08:00
parent f3932cfaef
commit 79d024f9f6
18 changed files with 402 additions and 141 deletions

View File

@ -5,18 +5,72 @@
@using SqlSugar
@using VideoAnalysisCore.Model
@using VideoAnalysisCore.Model.Dto
@using VideoAnalysisCore.Enum
<Table @ref="_table" Loading="tableLoading" TItem="VideoTaskDto" PageSize="15" Total="_total" DataSource="_dataSource" @bind-SelectedRows="_selectedRows" OnChange="OnChange">
<Table @ref="_table" Loading="tableLoading" TItem="VideoTaskDto" PageSize="15" Total="_total" DataSource="_dataSource" @bind-SelectedRows="_selectedRows" OnChange="OnChange"
OnExpand="OnExpand">
<TitleTemplate>
<Flex Justify="end" Gap="10"> </Flex>
</TitleTemplate>
<ColumnDefinitions Context="row">
<Selection />
<ActionColumn Title="操作列">
<Button Type="@ButtonType.Link" Danger @onclick="() => ReStart(row)">
<PropertyColumn Property="c=>c.Id" Width="100" Filterable="true" Sortable="true"/>
<PropertyColumn Property="c=>c.TagId" Width="100" />
<PropertyColumn Property="c=>c.LastEnum" Width="120" />
<PropertyColumn Property="c=>c.ApiToken" Width="120" />
<PropertyColumn Property="c=>c.ComeFrom" Width="120" />
<PropertyColumn Property="c=>c.MediaUrl" />
<PropertyColumn Property="c=>c.TotalTokens" Width="100" />
<PropertyColumn Property="c=>c.CreateTime" Width="100" />
</ColumnDefinitions>
<ExpandTemplate Context="rowData" >
<Descriptions Title="任务详情" Bordered>
<DescriptionsItem Title="@rowData.Data.LastEnum.ToString()">
@rowData.Data.Progress%
</DescriptionsItem>
<DescriptionsItem Title="操作" Span="2">
<Button Type="@ButtonType.Primary"
Loading="rowRestartLoading"
OnClick="()=>RowRestart(rowData)">
刷新数据
</Button>
<Button Type="@ButtonType.Primary" Danger @onclick="() => ReStart(rowData.Data)">
重试
</Button>
</ActionColumn>
<GenerateColumns Definitions="@((n,c) => { c.Filterable = true; c.Sortable = true; })" />
</ColumnDefinitions>
</DescriptionsItem>
<DescriptionsItem Title="任务时间轴" Span="5">
<Steps Current="@RowSTIndex(rowData)" Status="@RowSTStatus(rowData)">
<Step Title="下载文件"
Description="@RowST(rowData,RedisChannelEnum.DownloadFile)" />
<Step Title="分离音频"
Description="@RowST(rowData,RedisChannelEnum.SeparateAudio)" />
<Step Title="解析字幕"
Description="@RowST(rowData,RedisChannelEnum.ParsingCaptions)" />
<Step Title="解析说话人"
Description="@RowST(rowData,RedisChannelEnum.ParsingSpeaker)" />
<Step Title="Chat模型分析"
Description="@RowST(rowData,RedisChannelEnum.ChatModelAnalysis)" />
<Step Title="结束任务"
Description="@RowST(rowData,RedisChannelEnum.EndTask)" />
</Steps>
</DescriptionsItem>
@if (!string.IsNullOrEmpty( @rowData.Data.ErrorMessage))
{
<DescriptionsItem Title="任务异常" Span="3">
@rowData.Data.ErrorMessage
</DescriptionsItem>
}
</Descriptions>
</ExpandTemplate>
</Table>

View File

@ -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<VideoTask> taskDB { get; set; } = default!;
[Inject] private INotificationService _notice { get; set; } = default!;
IEnumerable<VideoTaskDto> _selectedRows = [];
ITable _table;
@ -26,6 +28,8 @@ namespace Learn.VideoAnalysis.Components.Pages
RefAsync<int> _total = 0;
bool tableLoading = false;
private VideoTaskDto selectData;
private bool rowRestartLoading=false;
/// <summary>
/// 重试
@ -34,16 +38,18 @@ namespace Learn.VideoAnalysis.Components.Pages
async void ReStart(VideoTaskDto query)
{
var lastEnum = (await RedisExpand.Redis.HMGetAsync<RedisChannelEnum>(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<VideoTaskDto> lastQuery = null;
/// <summary>
/// 分页 查询 筛选 时
/// </summary>
/// <param name="query"></param>
/// <param name="changed"></param>
async void OnChange(QueryModel<VideoTaskDto> query)
{
lastQuery= query;
tableLoading = true;
List<IConditionalModel> where = default!;
if (query.FilterModel != null && ((query.FilterModel?.Count() ?? 0) > 0))
@ -55,39 +61,72 @@ namespace Learn.VideoAnalysis.Components.Pages
.Select<VideoTaskDto>()
.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<double>(RedisExpandKey.Task(item.Id), "Progress").FirstOrDefault();
}
tableLoading = false;
StateHasChanged();
}
private static System.Timers.Timer _timer;
public void StartTimer(Object source, System.Timers.ElapsedEventArgs e)
public void RowRestart(RowData<VideoTaskDto> 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<double>(RedisExpandKey.Task(item.Id), "Progress").FirstOrDefault();
}
var data = RedisExpand.Redis.HMGet<string>(RedisExpandKey.Task(item.Id),
"Progress", "LastEnum", "StartTime", "ErrorMessage");
item.Progress = double.Parse(data[0]);
item.LastEnum = data[1].ToEnum<RedisChannelEnum>() ?? default;
item.StartTimeDic = System.Text.Json.JsonSerializer.Deserialize<Dictionary<RedisChannelEnum, DateTime>>(data[2]) ?? null;
item.ErrorMessage = data[3];
rowRestartLoading = false;
StateHasChanged();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
private string RowST(RowData<VideoTaskDto> rowData, RedisChannelEnum e)
{
var dic = rowData.Data.StartTimeDic;
if (dic is null || !dic.ContainsKey(e))
return "--";
return dic[e].ToString();
}
private string RowSTStatus(RowData<VideoTaskDto> 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<VideoTaskDto> rowData)
{
var dic = rowData.Data.StartTimeDic;
if (dic is null )
return 0;
return (int)dic.LastOrDefault().Key;
}
private void OnExpand(RowData<VideoTaskDto> rowData)
{
RowRestart(rowData);
}
/// <summary>
/// 在渲染页面之后
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
}
/// <summary>
/// 初始化
/// </summary>
protected override void OnInitialized()
{
_timer = new System.Timers.Timer(2000);
_timer.Elapsed += StartTimer;
_timer.Enabled = true;
}
private async Task<bool> Comfirm(string message)

View File

@ -1,3 +1,8 @@
input[aria-hidden="true"] {
display: none !important;
}
.task_status_tag {
display:flex;
}

View File

@ -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);
}
}

View File

@ -51,8 +51,8 @@ namespace Learn.VideoAnalysis
builder.Configuration.GetSection("AppConfig").Bind(AppCommon.Config);
//初始化 插件
//Speaker.Init();
//RedisExpand.Init();
RedisExpand.Init();
Speaker.Init();
//SenseVoice.Init();

View File

@ -7,6 +7,7 @@
},
"AllowedHosts": "*",
"AppConfig": {
"ID": "APP0001",//
"Redis": {
"ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10"
},

View File

@ -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<int>(),
Prompt = criteriaDic[s.].Flaw,
}).ToArray(),
@ -137,7 +140,7 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
};
//高频词汇
gptRes.Hotwords = arr2[(int)QuestionTypeEnum.]..ToObject<string[]>()??["暂无数据"];
gptRes.Hotwords = arr2[(int)QuestionTypeEnum.]..ToObject<string[]>() ?? ["暂无数据"];
//时间段概览
gptRes.TimeOverview = arr2[(int)QuestionTypeEnum.]
@ -148,27 +151,33 @@ namespace VideoAnalysisCore.AICore.ChatGPT.KIMI
//分析上课时间段情况 分析 独立学习 小组合作 随堂练习等情况
var extraTimeBase = arr2[(int)QuestionTypeEnum.]
..ToObject<Dictionary<TimeBaseTypeEnum,string?>>()?.Select(s =>
new TimeBase(s.Value,s.Key.ToString()));
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
..ToObject<TimeBase[]>();
if (extraTimeBase is not null)
foreach (var item in extraTimeBase)
{
it.ChatAnalysis,
it.ChatAnalysisScore,
it.ErrorMessage,
it.TotalTokens,
it.LastEnum,
}).ExecuteCommandAsync();
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<TimeBaseTypeEnum>();
}
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);
return gptRes;
}
}

View File

@ -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<ChatRes>();
}

View File

@ -78,6 +78,8 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
/// <returns></returns>
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<SenseVoiceRes>(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);
}

View File

@ -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";

View File

@ -303,6 +303,10 @@ namespace VideoAnalysisCore.Common
/// </summary>
public class AppConfig
{
/// <summary>
/// 程序ID
/// </summary>
public string ID { get; set; } = string.Empty;
/// <summary>
/// redis
/// </summary>

View File

@ -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
/// </summary>
public class DownloadFile
{
static DownloadConfiguration Opt { get; set; } = default!;
/// <summary>
/// 初始化下载器
/// </summary>
/// <param name="DownloadSpeed">下载速度mb/s 默认8</param>
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
/// <returns></returns>
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);
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) =>
{
var progress = (double)totalBytesRead / totalBytes.Value * 100;
RedisExpand.SetTaskProgress(task, progress);
if (download.Status == DownloadStatus.Failed && e.Error!=null)
{
await RedisExpand.SetTaskErrorMessage(long.Parse(task), e.Error)
.ConfigureAwait(false);//不切回上下文
return;
}
}
else if (download.Status == DownloadStatus.Completed)
{
//加入下一队列
RedisExpand.InsertChannel(Enum.RedisChannelEnum.SeparateAudio, task);
return;
}
};
await download.StartAsync();
}
}
}

View File

@ -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
/// <summary>
/// 基础Channel key
/// </summary>
public const string ChannelKey = BaseKey + "Channel:";
public const string ChannelKey = BaseKey + "TaskChannel";
/// <summary>
/// 下载文件
/// </summary>
@ -55,20 +59,13 @@ namespace VideoAnalysisCore.Common
/// </summary>
public const string TaskArr = BaseKey + "TaskArr";
/// <summary>
/// 获取枚举RedisKey
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public static string EnumKey(RedisChannelEnum e)
{
return ChannelKey + e.ToString();
}
/// <summary>
/// 任务对象地址
/// </summary>
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";
}
/// <summary>
@ -80,6 +77,12 @@ namespace VideoAnalysisCore.Common
/// redis 连接
/// </summary>
public static RedisClient Redis = new RedisClient(AppCommon.Config.Redis.ConnectionString);
public static Dictionary<RedisChannelEnum, Action<string>> SubscribeList = new Dictionary<RedisChannelEnum, Action<string>>();
/// <summary>
/// 队列池
/// </summary>
static SubscribeListObject? Subscribe;
/// <summary>
/// 初始化 redis
/// <para>需要在初始化配置文件时候调用</para>
@ -92,6 +95,14 @@ namespace VideoAnalysisCore.Common
InitChannel();
}
/// <summary>
/// 获取任务进度
/// </summary>
/// <param name="taskId"></param>
public static void SetTaskGPTCached(object taskId, object data)
{
Redis.Set(RedisExpandKey.TaskGPT(taskId), data, 3600);
}
/// <summary>
/// 获取任务进度
/// </summary>
@ -105,9 +116,9 @@ namespace VideoAnalysisCore.Common
/// </summary>
/// <param name="p">进度百分比</param>
/// <param name="taskId"></param>
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));
}
/// <summary>
@ -117,6 +128,7 @@ namespace VideoAnalysisCore.Common
/// <param name="taskId">任务id</param>
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<Dictionary<RedisChannelEnum, DateTime>>(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<CallGPTRes>(RedisExpandKey.Task(task), "ChatAnalysis")).FirstOrDefault();
if (gptRes is null)
throw new Exception("未能读取到GPT处理结果");
var taskData = await DbScoped.SugarScope.Queryable<VideoTask>()
.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();
}
/// <summary>
/// 初始化 队列 任务
/// </summary>
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<IBserGPT>() is null)
throw new Exception("IBserGPT 未注入");
else
return scope.ServiceProvider.GetService<IBserGPT>()?.CallGPT(task)??Task.CompletedTask;
return scope.ServiceProvider.GetService<IBserGPT>()?.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();
}
/// <summary>
/// 重新接收新任务
/// </summary>
public static async Task ReceivingTaskAsync()
{
var oldTask = await Redis.GetAsync(RedisExpandKey.IDTask);
if (!string.IsNullOrEmpty(oldTask))
{
var lastEnum = (await Redis.HMGetAsync<RedisChannelEnum>(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);
});
}
/// <summary>
@ -173,10 +244,22 @@ namespace VideoAnalysisCore.Common
/// <param name="taskID"></param>
/// <param name="errorMessage"></param>
/// <returns></returns>
public static async Task<bool> SetTaskErrorMessage(long taskID, string errorMessage)
public static async Task<bool> 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<VideoTask>()
.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<string, Task> 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<VideoTask>()
@ -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

View File

@ -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,
}
}

View File

@ -25,10 +25,10 @@
/// Chat模型分析
/// </summary>
ChatModelAnalysis,
/// <summary>
/// 回调三方系统
/// </summary>
CallBackSystem,
///// <summary>
///// 回调三方系统
///// </summary>
//CallBackSystem,
/// <summary>
/// 结束任务
/// </summary>

View File

@ -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
/// </summary>
[DisplayName("创建时间")]
public DateTime CreateTime { get; set; } = DateTime.Now;
/// <summary>
/// 开始时间轴
/// <para>逻辑字段</para>
/// </summary>
public Dictionary<RedisChannelEnum, DateTime>? StartTimeDic {get; set;}
}
}

View File

@ -97,6 +97,7 @@ namespace VideoAnalysisCore.Model
/// <summary>
/// AI模型评分
/// </summary>
[SugarColumn(IsNullable =true)]
public decimal? ChatAnalysisScore { get; set; }
/// <summary>
/// 消耗token

View File

@ -46,6 +46,7 @@
<ItemGroup>
<PackageReference Include="FreeRedis" Version="1.3.2" />
<PackageReference Include="Downloader" Version="3.2.1" />
<PackageReference Include="Mapster" Version="7.4.1-pre01" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />