新增 VideoTaskPage.razor 页面

优化 任务执行逻辑
This commit is contained in:
小肥羊 2024-11-05 17:19:20 +08:00
parent c9ef1d230c
commit 489aff9087
17 changed files with 329 additions and 29 deletions

View File

@ -19,15 +19,22 @@ namespace VideoAnalysisRazor.Layouts
} }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_menuData = new[] { _menuData = [
new MenuDataItem
{
Path = "/Project",
Name = "课堂指标",
Key = "EvaluationProject",
Icon = "question-circle",
},
new MenuDataItem new MenuDataItem
{ {
Path = "/", Path = "/",
Name = "课堂指标", Name = "任务队列",
Key = "EvaluationProject", Key = "VideoTaskPage",
Icon = "EvaluationProject", Icon = "unordered-list",
} }
}; ];
} }
void Reload() void Reload()

View File

@ -1,4 +1,4 @@
@page "/" @page "/Project"
@using AntDesign @using AntDesign
@using AntDesign.TableModels @using AntDesign.TableModels
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations

View File

@ -0,0 +1,22 @@
@page "/"
@using AntDesign
@using AntDesign.TableModels
@using System.ComponentModel.DataAnnotations
@using SqlSugar
@using VideoAnalysisCore.Model
@using VideoAnalysisCore.Model.Dto
<Table @ref="_table" Loading="tableLoading" TItem="VideoTaskDto" PageSize="15" Total="_total" DataSource="_dataSource" @bind-SelectedRows="_selectedRows" OnChange="OnChange">
<TitleTemplate>
<Flex Justify="end" Gap="10"> </Flex>
</TitleTemplate>
<ColumnDefinitions Context="row">
<Selection />
<ActionColumn Title="操作列">
<Button Type="@ButtonType.Link" Danger @onclick="() => ReStart(row)">
重试
</Button>
</ActionColumn>
<GenerateColumns Definitions="@((n,c) => { c.Filterable = true; c.Sortable = true; })" />
</ColumnDefinitions>
</Table>

View File

@ -0,0 +1,94 @@
using AntDesign;
using AntDesign.TableModels;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using SqlSugar;
using System.Linq.Expressions;
using System.Threading.Tasks;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Dto;
namespace Learn.VideoAnalysis.Components.Pages
{
public partial class VideoTaskPage : ComponentBase
{
[Inject] private ConfirmService ComfirmService { get; set; } = default!;
[Inject] private Repository<VideoTask> taskDB { get; set; } = default!;
IEnumerable<VideoTaskDto> _selectedRows = [];
ITable _table;
List<VideoTaskDto> _dataSource = null;
RefAsync<int> _total = 0;
bool tableLoading = false;
/// <summary>
/// 重试
/// </summary>
/// <param name="query"></param>
async void ReStart(VideoTaskDto query)
{
var lastEnum = (await RedisExpand.Redis.HMGetAsync<RedisChannelEnum>(RedisExpandKey.Task(query.Id), "LastEnum")).FirstOrDefault() ;
RedisExpand.InsertChannel(lastEnum, query.Id);
}
/// <summary>
/// 分页 查询 筛选 时
/// </summary>
/// <param name="query"></param>
async void OnChange(QueryModel<VideoTaskDto> query)
{
tableLoading = true;
List<IConditionalModel> where = default!;
if (query.FilterModel != null && ((query.FilterModel?.Count() ?? 0) > 0))
{
where = query.ToSqlSugerWhere();
}
_dataSource = await taskDB.AsQueryable()
.Where(where)
.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();
}
/// <summary>
/// 初始化
/// </summary>
protected override void OnInitialized()
{
var timer = new Timer(_ =>
{
InvokeAsync(() =>
{
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();
}
} );
InvokeAsync(StateHasChanged);
}, null, dueTime: 500, period: 3000);
}
private async Task<bool> Comfirm(string message)
{
return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes;
}
}
}

View File

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

View File

@ -17,6 +17,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\VideoAnalysisCore\VideoAnalysisCore.csproj" /> <ProjectReference Include="..\VideoAnalysisCore\VideoAnalysisCore.csproj" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.2-pre01" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.5" />

View File

@ -7,6 +7,7 @@ using VideoAnalysisCore.AICore.ChatGPT;
using VideoAnalysisCore.AICore.ChatGPT.KIMI; using VideoAnalysisCore.AICore.ChatGPT.KIMI;
using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.AICore.SherpaOnnx;
using SqlSugar; using SqlSugar;
using Mapster;
@ -69,6 +70,7 @@ namespace Learn.VideoAnalysis
builder.Services.AddAntDesign(); builder.Services.AddAntDesign();
builder.Services.InitSqlSugar(); builder.Services.InitSqlSugar();
builder.Services.AddMapster();
builder.Services.Configure<ProSettings>(builder.Configuration.GetSection("ProSettings")); builder.Services.Configure<ProSettings>(builder.Configuration.GetSection("ProSettings"));

View File

@ -26,7 +26,7 @@
"DB": { "DB": {
"ConnectionString": "AllowLoadLocalInfile=true;Server=192.168.2.9;User ID=root;Password=qwe123!@#;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None", "ConnectionString": "AllowLoadLocalInfile=true;Server=192.168.2.9;User ID=root;Password=qwe123!@#;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None",
"SqlType": "MySql", "SqlType": "MySql",
"UpdateTable": false, "UpdateTable": true,
} }
} }
} }

View File

@ -77,9 +77,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
throw new Exception("task 音频路径未找到"); throw new Exception("task 音频路径未找到");
string testWaveFilename = filePath; WaveReader reader = new WaveReader(filePath);
WaveReader reader = new WaveReader(testWaveFilename);
int numSamples = reader.Samples.Length; int numSamples = reader.Samples.Length;
int windowSize = VADModelConfig.SileroVad.WindowSize; int windowSize = VADModelConfig.SileroVad.WindowSize;
int sampleRate = VADModelConfig.SampleRate; int sampleRate = VADModelConfig.SampleRate;
@ -110,7 +108,11 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
{ {
Text = stream.Result.Text, Text = stream.Result.Text,
Start= startTime, Start= startTime,
End = startTime + duration }); End = startTime + duration
});
var progress = (double)(startTime + duration) / numSamples * 100;
RedisExpand.SetTaskProgress(task, progress);
} }
VAD.Pop(); VAD.Pop();
} }

View File

@ -50,11 +50,15 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
if (SD.SampleRate != waveReader.SampleRate) if (SD.SampleRate != waveReader.SampleRate)
throw new Exception($"预期采样率:{SD.SampleRate}. 传入: {waveReader.SampleRate}"); throw new Exception($"预期采样率:{SD.SampleRate}. 传入: {waveReader.SampleRate}");
var i = 0;
var segments = SD.ProcessWithCallback(waveReader.Samples, var segments = SD.ProcessWithCallback(waveReader.Samples,
(numProcessedChunks, numTotalChunks, arg) => (numProcessedChunks, numTotalChunks, arg) =>
{ {
i++;
if(i%50 !=0)
return 1;
var progress = (double)numProcessedChunks / numTotalChunks * 100; var progress = (double)numProcessedChunks / numTotalChunks * 100;
Console.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}=> {task} 说话人日志: {progress:F2}%"); RedisExpand.SetTaskProgress(task, progress);
return 1; return 1;
}, nint.Zero); }, nint.Zero);
var res = segments.Select(s => new OfflineSpeakerRes(s)); var res = segments.Select(s => new OfflineSpeakerRes(s));

View File

@ -58,6 +58,8 @@ namespace VideoAnalysisCore.Common
// 获取文件大小 // 获取文件大小
var totalBytes = response.Content.Headers.ContentLength; var totalBytes = response.Content.Headers.ContentLength;
if (!totalBytes.HasValue)
throw new Exception(task+" 未能获取到下载文件的 ContentLength ");
var localPath = task.LocalPath(); 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); if (!Directory.Exists(localPath)) Directory.CreateDirectory(localPath);
@ -80,10 +82,10 @@ namespace VideoAnalysisCore.Common
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
// 计算下载进度 // 计算下载进度
if (totalBytes.HasValue && count % 3 == 0) if (count % 50 == 0)
{ {
var progress = (double)totalBytesRead / totalBytes.Value * 100; var progress = (double)totalBytesRead / totalBytes.Value * 100;
Console.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}=> {task} 下载进度: {progress:F2}%"); RedisExpand.SetTaskProgress(task, progress);
} }
} }

View File

@ -1,5 +1,6 @@
using FreeRedis; using FreeRedis;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using SqlSugar.IOC;
using System; using System;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,6 +12,7 @@ using VideoAnalysisCore.AICore.FFMPGE;
using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.AICore.Whisper; using VideoAnalysisCore.AICore.Whisper;
using VideoAnalysisCore.Enum; using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Model;
namespace VideoAnalysisCore.Common namespace VideoAnalysisCore.Common
{ {
@ -89,6 +91,25 @@ namespace VideoAnalysisCore.Common
Redis.Deserialize = (json, type) => System.Text.Json.JsonSerializer.Deserialize(json, type); Redis.Deserialize = (json, type) => System.Text.Json.JsonSerializer.Deserialize(json, type);
InitChannel(); InitChannel();
} }
/// <summary>
/// 获取任务进度
/// </summary>
/// <param name="taskId"></param>
public static double SetTaskProgress(object taskId)
{
return Redis.HMGet<double>(RedisExpandKey.Task(taskId), "Progress")[0];
}
/// <summary>
/// 设置任务进度
/// </summary>
/// <param name="p">进度百分比</param>
/// <param name="taskId"></param>
public static void SetTaskProgress(object taskId,double p)
{
Redis.HMSet(RedisExpandKey.Task(taskId), "Progress", p);
}
/// <summary> /// <summary>
/// 将任务 插入 队列 /// 将任务 插入 队列
/// </summary> /// </summary>
@ -106,7 +127,6 @@ namespace VideoAnalysisCore.Common
else else
startTime[@enum] = DateTime.Now; startTime[@enum] = DateTime.Now;
Redis.HMSet(RedisExpandKey.Task(taskId), "StartTime", startTime); Redis.HMSet(RedisExpandKey.Task(taskId), "StartTime", startTime);
Redis.LPush(RedisExpandKey.EnumKey(@enum), taskId); Redis.LPush(RedisExpandKey.EnumKey(@enum), taskId);
@ -148,19 +168,47 @@ namespace VideoAnalysisCore.Common
} }
/// <summary> /// <summary>
/// /// 写入任务异常
/// </summary>
/// <param name="taskID"></param>
/// <param name="errorMessage"></param>
/// <returns></returns>
public static async Task<bool> SetTaskErrorMessage(long taskID, string errorMessage)
{
return await DbScoped.SugarScope.Updateable<VideoTask>()
.SetColumns(it => it.ErrorMessage == errorMessage)//SetColumns是可以叠加的 写2个就2个字段赋值
.Where(it => it.Id == taskID)
.ExecuteCommandAsync() == 1;
}
/// <summary>
/// 触发
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="msg"></param> /// <param name="taskId"></param>
/// <param name="action"></param> /// <param name="action"></param>
public static async void TouchChannel(RedisChannelEnum key, string msg, Func<string, Task> action = null) public static async void TouchChannel(RedisChannelEnum key, string taskId, Func<string, Task> action = null)
{ {
if (msg is null) return; if (taskId is null) return;
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> " + key + " " + msg); Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> " + key + " " + taskId);
if (action is not null) if (action is not null)
{ {
Redis.HMSet(RedisExpandKey.Task(msg), "LastEnum", key); var tID = long.Parse(taskId);
await action(msg); Redis.HMSet(RedisExpandKey.Task(taskId), "LastEnum", key);
await DbScoped.SugarScope.Updateable<VideoTask>()
.SetColumns(it => it.LastEnum == key)
.Where(it => it.Id == tID)
.ExecuteCommandAsync();
try
{
await action(taskId);
}
catch (Exception ex)
{
//执行任务时出现异常
var error = ex.Message + ex.StackTrace;
await SetTaskErrorMessage(long.Parse(taskId), error);
Redis.HMSet(RedisExpandKey.Task(taskId), "Error", error);
}
} }
else else
{ {

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using VideoAnalysisCore.Model;
namespace VideoAnalysisCore.Common namespace VideoAnalysisCore.Common
{ {

View File

@ -28,6 +28,10 @@
/// <summary> /// <summary>
/// 回调三方系统 /// 回调三方系统
/// </summary> /// </summary>
CallBackSystem = 6 CallBackSystem = 6,
/// <summary>
/// 结束任务
/// </summary>
EndTask = 6,
} }
} }

View File

@ -0,0 +1,73 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoAnalysisCore.Enum;
namespace VideoAnalysisCore.Model.Dto
{
public class VideoTaskDto
{
/// <summary>
/// 任务id
/// <para>视频音频文件地址都使用taskID能获取</para>
/// </summary>
[DisplayName("任务id")]
public long Id { get; set; }
/// <summary>
/// ApiKey
/// </summary>
[DisplayName("ApiKey")]
public string ApiToken { get; set; } = string.Empty;
/// <summary>
/// 请求来自哪个ip地址
/// </summary>
[DisplayName("请求IP")]
public string ComeFrom { get; set; } = string.Empty;
/// <summary>
/// 上一次执行的枚举
/// </summary>
[DisplayName("最后执行")]
public RedisChannelEnum LastEnum { get; set; }
/// <summary>
/// 错误信息
/// </summary>
[DisplayName("错误信息")]
public string? ErrorMessage { get; set; }
/// <summary>
/// 执行进度
/// </summary>
[DisplayName("进度")]
public double Progress { get; set; }
/// <summary>
/// 媒体路径
/// </summary>
[DisplayName("媒体路径")]
public string MediaUrl { get; set; } = string.Empty;
/// <summary>
/// 自定义值 任务完成后附带通知
/// </summary>
[DisplayName("自定义值")]
public string? Tag { get; set; }
/// <summary>
/// 消耗token
/// </summary>
[DisplayName("消耗Token")]
public int TotalTokens { get; set; }
/// <summary>
///回调Api地址
/// </summary>
[DisplayName("回调地址")]
public string? CallBackUrl { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[DisplayName("创建时间")]
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}

View File

@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations;
using System.Net; using System.Net;
using System.Text.Json; using System.Text.Json;
using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.AICore.Whisper;
using VideoAnalysisCore.Enum; using VideoAnalysisCore.Enum;
using Whisper.net; using Whisper.net;
@ -53,23 +54,58 @@ namespace VideoAnalysisCore.Model
/// <summary> /// <summary>
/// 字幕缓存 /// 字幕缓存
/// </summary> /// </summary>
[SugarColumn(ColumnDataType = "longtext", IsNullable = true)] [SugarColumn(ColumnName = "Captions", ColumnDataType = "longtext", IsNullable = true)]
public SegmentData[]? Captions { get; set; } public string _Captions { get; set; } = "[]";
/// <summary>
/// 字幕缓存
/// </summary>
[SugarColumn(IsIgnore = true)]
public SenseVoiceRes[]? Captions
{
get=> JsonSerializer.Deserialize<SenseVoiceRes[]>(_Captions ?? "[]");
set => _Captions = JsonSerializer.Serialize(value);
}
/// <summary> /// <summary>
/// 说话人日志解析缓存 /// 说话人日志解析缓存
/// </summary> /// </summary>
[SugarColumn(ColumnDataType = "longtext", IsNullable = true)] [SugarColumn(ColumnName = "Speaker", ColumnDataType = "longtext", IsNullable = true)]
public OfflineSpeakerRes[]? Speaker { get; set; } public string _Speaker { get; set; } = "[]";
/// <summary>
/// 说话人日志解析缓存
/// </summary>
[SugarColumn(IsIgnore = true)]
public OfflineSpeakerRes[]? Speaker
{
get => JsonSerializer.Deserialize<OfflineSpeakerRes[]>(_Speaker ?? "[]");
set => _Speaker = JsonSerializer.Serialize(value);
}
/// <summary> /// <summary>
/// Chat模型分析缓存 /// Chat模型分析缓存
/// </summary> /// </summary>
[SugarColumn(ColumnDataType = "longtext", IsNullable = true)] [SugarColumn(ColumnName = "ChatAnalysis", ColumnDataType = "longtext", IsNullable = true)]
public object[]? ChatAnalysis { get; set; } public string _ChatAnalysis { get; set; } = "[]";
/// <summary>
/// Chat模型分析缓存
/// </summary>
[SugarColumn(IsIgnore = true)]
public object[]? ChatAnalysis
{
get => JsonSerializer.Deserialize<object[]>(_ChatAnalysis ?? "[]");
set => _ChatAnalysis = JsonSerializer.Serialize(value);
}
/// <summary> /// <summary>
/// 消耗token /// 消耗token
/// </summary> /// </summary>
public int TotalTokens { get; set; } public int TotalTokens { get; set; }
/// <summary> /// <summary>
/// 错误信息
/// </summary>
[SugarColumn(ColumnDataType = "text", IsNullable = true)]
public string? ErrorMessage { get; set; }
/// <summary>
/// 创建时间 /// 创建时间
/// </summary> /// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now; public DateTime CreateTime { get; set; } = DateTime.Now;

View File

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