parent
f3932cfaef
commit
79d024f9f6
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
input[aria-hidden="true"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.task_status_tag {
|
||||
display:flex;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ namespace Learn.VideoAnalysis
|
|||
builder.Configuration.GetSection("AppConfig").Bind(AppCommon.Config);
|
||||
|
||||
//初始化 插件
|
||||
//Speaker.Init();
|
||||
//RedisExpand.Init();
|
||||
RedisExpand.Init();
|
||||
Speaker.Init();
|
||||
//SenseVoice.Init();
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
},
|
||||
"AllowedHosts": "*",
|
||||
"AppConfig": {
|
||||
"ID": "APP0001",//程序唯一值
|
||||
"Redis": {
|
||||
"ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)}");
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -303,6 +303,10 @@ namespace VideoAnalysisCore.Common
|
|||
/// </summary>
|
||||
public class AppConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 程序ID
|
||||
/// </summary>
|
||||
public string ID { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// redis
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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,
|
||||
|
|
@ -162,9 +207,35 @@ namespace VideoAnalysisCore.Common
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@
|
|||
/// Chat模型分析
|
||||
/// </summary>
|
||||
ChatModelAnalysis,
|
||||
/// <summary>
|
||||
/// 回调三方系统
|
||||
/// </summary>
|
||||
CallBackSystem,
|
||||
///// <summary>
|
||||
///// 回调三方系统
|
||||
///// </summary>
|
||||
//CallBackSystem,
|
||||
/// <summary>
|
||||
/// 结束任务
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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;}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ namespace VideoAnalysisCore.Model
|
|||
/// <summary>
|
||||
/// AI模型评分
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable =true)]
|
||||
public decimal? ChatAnalysisScore { get; set; }
|
||||
/// <summary>
|
||||
/// 消耗token
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue