323 lines
12 KiB
C#
323 lines
12 KiB
C#
using AntDesign;
|
|
using FreeRedis;
|
|
using FreeRedis.Internal;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using SqlSugar.IOC;
|
|
using System;
|
|
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;
|
|
using VideoAnalysisCore.AICore.SherpaOnnx;
|
|
using VideoAnalysisCore.AICore.Whisper;
|
|
using VideoAnalysisCore.Enum;
|
|
using VideoAnalysisCore.Model;
|
|
using VideoAnalysisCore.Model.Dto;
|
|
|
|
namespace VideoAnalysisCore.Common
|
|
{
|
|
/// <summary>
|
|
/// redis key
|
|
/// </summary>
|
|
public static class RedisExpandKey
|
|
{
|
|
/// <summary>
|
|
/// 基础key
|
|
/// </summary>
|
|
public const string BaseKey = "VideoAnalysis:";
|
|
/// <summary>
|
|
/// 基础Channel key
|
|
/// </summary>
|
|
public const string ChannelKey = BaseKey + "TaskChannel";
|
|
/// <summary>
|
|
/// 下载文件
|
|
/// </summary>
|
|
public const string DownloadFile = ChannelKey + "DownloadFile";
|
|
/// <summary>
|
|
/// 分离音频
|
|
/// </summary>
|
|
public const string SeparateAudio = ChannelKey + "SeparateAudio";
|
|
/// <summary>
|
|
/// 解析字幕
|
|
/// </summary>
|
|
public const string ParsingCaptions = ChannelKey + "ParsingCaptions";
|
|
/// <summary>
|
|
/// 解析说话人
|
|
/// </summary>
|
|
public const string ParsingSpeaker = ChannelKey + "ParsingSpeaker";
|
|
/// <summary>
|
|
/// Chat模型分析
|
|
/// </summary>
|
|
public const string ChatModelAnalysis = ChannelKey + "ChatModelAnalysis";
|
|
|
|
/// <summary>
|
|
/// 任务数组
|
|
/// </summary>
|
|
public const string TaskArr = BaseKey + "TaskArr";
|
|
|
|
|
|
/// <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>
|
|
/// redis拓展
|
|
/// </summary>
|
|
public class RedisExpand
|
|
{
|
|
/// <summary>
|
|
/// 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>
|
|
/// </summary>
|
|
public static void Init()
|
|
{
|
|
Console.WriteLine("初始化 redis");
|
|
Redis.Serialize = obj => System.Text.Json.JsonSerializer.Serialize(obj);
|
|
Redis.Deserialize = (json, type) => System.Text.Json.JsonSerializer.Deserialize(json, type);
|
|
Task.Run(() =>
|
|
{
|
|
Task.Delay(1000 * 10);
|
|
InitChannel();
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 缓存GPT任务缓存
|
|
/// </summary>
|
|
/// <param name="taskId"></param>
|
|
public static void SetTaskGPTCached(object taskId, object? data)
|
|
{
|
|
Redis.Set(RedisExpandKey.TaskGPT(taskId), data, 3600);
|
|
}
|
|
/// <summary>
|
|
/// 获取任务进度
|
|
/// </summary>
|
|
/// <param name="taskId"></param>
|
|
public static float GetTaskProgress(object taskId)
|
|
{
|
|
return Redis.HMGet<float>(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", Math.Round(p, 2));
|
|
|
|
}
|
|
/// <summary>
|
|
/// 将任务 插入 队列
|
|
/// </summary>
|
|
/// <param name="enum">枚举</param>
|
|
/// <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();
|
|
if (startTime is null)
|
|
startTime = new Dictionary<RedisChannelEnum, DateTime>();
|
|
if (!startTime.ContainsKey(@enum))
|
|
startTime.Add(@enum, DateTime.Now);
|
|
else
|
|
startTime[@enum] = DateTime.Now;
|
|
|
|
Redis.HMSet(RedisExpandKey.Task(taskId), "StartTime", startTime);
|
|
|
|
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<TaskRes>(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 async void InitChannel()
|
|
{
|
|
if (Redis is null) throw new Exception("redis未初始化");
|
|
|
|
SubscribeList.Add(RedisChannelEnum.DownloadFile,
|
|
(msg) => { TouchChannel(RedisChannelEnum.DownloadFile, msg, DownloadFile.RunTask); });
|
|
|
|
|
|
SubscribeList.Add(RedisChannelEnum.SeparateAudio,
|
|
(msg) => { TouchChannel(RedisChannelEnum.SeparateAudio, msg, FFMPGEHandle.Audio2WAV16KAsync); });
|
|
|
|
SubscribeList.Add(RedisChannelEnum.ParsingCaptions,
|
|
(msg) => { TouchChannel(RedisChannelEnum.ParsingCaptions, msg, SenseVoice.RunTask); });
|
|
SubscribeList.Add(RedisChannelEnum.ParsingSpeaker,
|
|
(msg) => { TouchChannel(RedisChannelEnum.ParsingSpeaker, msg, Speaker.Run); });
|
|
SubscribeList.Add(RedisChannelEnum.ChatModelAnalysis,
|
|
(msg) =>
|
|
{
|
|
TouchChannel(RedisChannelEnum.ChatModelAnalysis, msg,
|
|
(task) =>
|
|
{
|
|
using var scope = AppCommon.Services?.CreateScope();
|
|
if (scope is null || scope.ServiceProvider.GetService<IBserGPT>() is null)
|
|
throw new Exception("IBserGPT 未注入");
|
|
else
|
|
return scope.ServiceProvider.GetService<IBserGPT>()?.CallGPT(task) ?? Task.CompletedTask;
|
|
});
|
|
});
|
|
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;
|
|
}
|
|
if (Subscribe?.IsUnsubscribed == false)//排除重试机制后 多次接收任务导致内存泄露
|
|
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>
|
|
/// 写入任务异常
|
|
/// </summary>
|
|
/// <param name="taskID"></param>
|
|
/// <param name="errorMessage"></param>
|
|
/// <returns></returns>
|
|
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("==============================================");
|
|
//清除失败任务 重新接收任务
|
|
await Redis.DelAsync(RedisExpandKey.IDTask);
|
|
await ReceivingTaskAsync();
|
|
}
|
|
|
|
Redis.HMSet(RedisExpandKey.Task(taskID), "ErrorMessage", error);
|
|
|
|
return await DbScoped.SugarScope.Updateable<VideoTask>()
|
|
.SetColumns(it => it.ErrorMessage == error)//SetColumns是可以叠加的 写2个就2个字段赋值
|
|
.Where(it => it.Id == taskID)
|
|
.ExecuteCommandAsync() == 1;
|
|
}
|
|
/// <summary>
|
|
/// 触发
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <param name="taskId"></param>
|
|
/// <param name="action"></param>
|
|
public static async void TouchChannel(RedisChannelEnum key, string taskId, Func<string, Task> action = null)
|
|
{
|
|
if (taskId is null) return;
|
|
var tID = long.Parse(taskId);
|
|
if (action is not null)
|
|
{
|
|
var errArr = new Exception[3];
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 开始执行 " + key + " " + taskId);
|
|
try
|
|
{
|
|
Redis.HMSet(RedisExpandKey.Task(taskId), "LastEnum", key);
|
|
Redis.HMSet(RedisExpandKey.Task(taskId), "Progress", 0);
|
|
await DbScoped.SugarScope.Updateable<VideoTask>()
|
|
.SetColumns(it => it.LastEnum == key)
|
|
.Where(it => it.Id == tID)
|
|
.ExecuteCommandAsync();
|
|
await action(taskId);
|
|
return;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errArr[i] = ex;
|
|
Console.WriteLine("====================[出现异常]====================");
|
|
Console.WriteLine(ex.Message);
|
|
Console.WriteLine(ex.StackTrace);
|
|
Console.WriteLine("==============================================");
|
|
Thread.Sleep(1000);
|
|
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 稍后后重试." + key + " " + taskId );
|
|
}
|
|
}
|
|
await SetTaskErrorMessage(tID, errArr.First());
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(key + " 任务函数 未实现");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|