diff --git a/Learn.VideoAnalysis.API/appsettings.json b/Learn.VideoAnalysis.API/appsettings.json index 11991ec..7738f9c 100644 --- a/Learn.VideoAnalysis.API/appsettings.json +++ b/Learn.VideoAnalysis.API/appsettings.json @@ -61,8 +61,8 @@ "UpdateTable": false }, "AlibabaCloudVod": { - "AccessKeyId": "LTAI5tDC6p9h747B7FHbgwkH", - "AccessKeySecret": "vRKgmbp1LB05LaGOjh3ZrZxbHSLYLF", + "AccessKeyId": "LTAI5tFLXyC3ixVdxhxLih8K", + "AccessKeySecret": "dlGu3WMoW0XQaoAYxiCPpnxry6qLhB", "EndPoint": "vod.cn-shanghai.aliyuncs.com" //上传节点 }, "AliyunOSS": { diff --git a/VideoAnalysis/appsettings.json b/VideoAnalysis/appsettings.json index ab07d01..3b73f57 100644 --- a/VideoAnalysis/appsettings.json +++ b/VideoAnalysis/appsettings.json @@ -79,8 +79,8 @@ "UpdateTable": false }, "AlibabaCloudVod": { - "AccessKeyId": "LTAI5tDC6p9h747B7FHbgwkH", - "AccessKeySecret": "vRKgmbp1LB05LaGOjh3ZrZxbHSLYLF", + "AccessKeyId": "LTAI5tFLXyC3ixVdxhxLih8K", + "AccessKeySecret": "dlGu3WMoW0XQaoAYxiCPpnxry6qLhB", "EndPoint": "vod.cn-shanghai.aliyuncs.com" //上传节点 }, "AliyunOSS": { diff --git a/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs b/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs index 4ce85dc..abb56d5 100644 --- a/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs +++ b/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs @@ -1,4 +1,4 @@ -using VideoAnalysisCore.Common; +using VideoAnalysisCore.Common; using System.Text.Json; using VideoAnalysisCore.Model; using System.Text; diff --git a/VideoAnalysisCore/AICore/SherpaOnnx/SherpaVad.cs b/VideoAnalysisCore/AICore/SherpaOnnx/SherpaVad.cs index 378054a..6219bb1 100644 --- a/VideoAnalysisCore/AICore/SherpaOnnx/SherpaVad.cs +++ b/VideoAnalysisCore/AICore/SherpaOnnx/SherpaVad.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using SherpaOnnx; using SqlSugar; diff --git a/VideoAnalysisCore/Common/DownloadFile.cs b/VideoAnalysisCore/Common/DownloadFile.cs index 58157a0..d721d5a 100644 --- a/VideoAnalysisCore/Common/DownloadFile.cs +++ b/VideoAnalysisCore/Common/DownloadFile.cs @@ -148,21 +148,34 @@ namespace VideoAnalysisCore.Common public async Task RunTask(string task, string workflowName = "VideoSliceWorkflow") { var taskId = long.Parse(task); - //获取资源文件 地址 + var taskInfo = await GetTaskInfoAsync(taskId); + var fileUrl = await GetMediaUrlAsync(taskInfo); + + // 准备本地目录 + var localPath = task.LocalPath(); + await PrepareDirectoryAndDbAsync(taskId, localPath); + + // 处理 PPT 视频 + await ProcessPPTVideoAsync(taskInfo, localPath, taskId, workflowName, task); + + // 下载主视频 + await DownloadWithCacheCheckAsync(taskId, fileUrl, localPath, taskVideoName, workflowName, task, "主视频"); + } + + private async Task GetTaskInfoAsync(long taskId) + { var taskInfo = await videoTaskDB.CopyNew().AsQueryable() .Where(s => s.Id == taskId).FirstAsync(); - if (taskInfo is null ) + + if (taskInfo is null) throw new Exception($"任务为null/是教研视频/没有视频课程名称"); + + return taskInfo; + } + + private async Task GetMediaUrlAsync(VideoTask taskInfo) + { var fileUrl = taskInfo.MediaUrl; - switch (taskInfo?.VideoType) - { - case AttachmentsInfoType.无: - case AttachmentsInfoType.新课: - case AttachmentsInfoType.复习: - break; - default: - throw new Exception("无效的课程类型"); - } if (string.IsNullOrEmpty(fileUrl)) { var videoInfo = await vodClient.GetPlayInfoAsync(new GetPlayInfoRequest() @@ -176,57 +189,70 @@ namespace VideoAnalysisCore.Common throw new Exception($"{DateTime.Now} 视频订阅=>获取阿里云视频信息失败 VideoCode {taskInfo.TagId} StatusCode {videoInfo?.StatusCode}"); fileUrl = videoInfo.Body.PlayInfoList.PlayInfo.First().PlayURL; } - if (string.IsNullOrEmpty(fileUrl)) - throw new Exception($"任务id[{task}] 资源地址无效 {fileUrl}"); - // 尝试从 URL 中获取文件后缀 + if (string.IsNullOrEmpty(fileUrl)) + throw new Exception($"任务id[{taskInfo.Id}] 资源地址无效 {fileUrl}"); + + // 尝试从 URL 中获取文件后缀 (保留原有校验逻辑) string fileExtension = Path.GetExtension(new Uri(fileUrl).AbsolutePath); - //否则 获取 从Content-Type 获取文件后缀 if (string.IsNullOrEmpty(fileExtension)) throw new Exception($"未能从资源路径中获取文件后缀"); - //创建下载文件缓存路径 - if (!Directory.Exists(AppCommon.TaskCachedFile)) Directory.CreateDirectory(AppCommon.TaskCachedFile); + return fileUrl; + } - var localPath = task.LocalPath(); - var outputPath = Path.Combine(localPath, taskVideoName); + private async Task PrepareDirectoryAndDbAsync(long taskId, string localPath) + { + if (!Directory.Exists(AppCommon.TaskCachedFile)) Directory.CreateDirectory(AppCommon.TaskCachedFile); if (!Directory.Exists(localPath)) Directory.CreateDirectory(localPath); + var outputPath = Path.Combine(localPath, taskVideoName); await videoTaskDB.CopyNew() .AsUpdateable() .SetColumns(it => it.LocalMediaPath == outputPath) - .Where(it => it.Id == long.Parse(task)) + .Where(it => it.Id == taskId) .ExecuteCommandAsync(); - //下载PPT视频 - if (string.IsNullOrEmpty(taskInfo.PPTVideoUrl)&& - !string.IsNullOrEmpty(taskInfo.PPTVideoCode)) + } + + private async Task ProcessPPTVideoAsync(VideoTask taskInfo, string localPath, long taskId, string workflowName, string taskStr) + { + // 更新 PPT 视频 URL + if (string.IsNullOrEmpty(taskInfo.PPTVideoUrl) && !string.IsNullOrEmpty(taskInfo.PPTVideoCode)) { taskInfo.PPTVideoUrl = await packageInfoTaskDB.AsQueryable() .Where(s => s.VideoCode == taskInfo.PPTVideoCode) .Select(s => s.VideoUrl) .FirstAsync(); + if (!string.IsNullOrEmpty(taskInfo.PPTVideoUrl)) + { await videoTaskDB.CopyNew().AsUpdateable(taskInfo) .UpdateColumns(it => new { it.PPTVideoUrl }) .ExecuteCommandAsync(); - + } } + + // 下载 PPT 视频 if (!string.IsNullOrEmpty(taskInfo.PPTVideoUrl)) { - await Download(taskInfo.PPTVideoUrl, localPath, taskPPTVideoName, - (s, e) => GetWorkflowManager(workflowName).SetTaskProgress(task, "PPT->" + Math.Round(e.ProgressPercentage, 1)) - ); + await DownloadWithCacheCheckAsync(taskId, taskInfo.PPTVideoUrl, localPath, taskPPTVideoName, workflowName, taskStr, "PPT视频", true); } - try + } + + private async Task DownloadWithCacheCheckAsync(long taskId, string url, string localPath, string fileName, string workflowName, string taskStr, string logPrefix, bool isPPT = false) + { + var filePath = Path.Combine(localPath, fileName); + if (File.Exists(filePath) && new FileInfo(filePath).Length > 10 * 1024 * 1024) { - //下载原视频 - await Download(fileUrl, localPath, taskVideoName, - (s, e) => GetWorkflowManager(workflowName).SetTaskProgress(task, Math.Round(e.ProgressPercentage, 1)) - ); + await GetWorkflowManager(workflowName).AddTaskLog(taskId, $"{logPrefix}命中本地缓存"); } - catch + else { - throw; + await Download(url, localPath, fileName, (s, e) => + { + var progressPrefix = isPPT ? "PPT->" : ""; + GetWorkflowManager(workflowName).SetTaskProgress(taskStr, $"{progressPrefix}" + Math.Round(e.ProgressPercentage, 1)); + }); } } @@ -265,7 +291,7 @@ namespace VideoAnalysisCore.Common try { using var scope = serviceProvider.CreateScope(); - var clearJob = scope.ServiceProvider.GetRequiredService(); + var clearJob = scope.ServiceProvider.GetRequiredService(); await clearJob.Invoke(); } catch (Exception ex) diff --git a/VideoAnalysisCore/Common/RedisExpand.cs b/VideoAnalysisCore/Common/RedisExpand.cs index bc90fdc..7b54446 100644 --- a/VideoAnalysisCore/Common/RedisExpand.cs +++ b/VideoAnalysisCore/Common/RedisExpand.cs @@ -1,4 +1,4 @@ -using FreeRedis; +using FreeRedis; using FreeRedis.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; @@ -140,25 +140,13 @@ namespace VideoAnalysisCore.Common public class RedisManager { public static bool StopTask { get; set; } = false; - public static Dictionary> SubscribeList = new Dictionary>(); - - /// - /// 正在后台运行的任务集合 - /// - public static ConcurrentDictionary RunningTasks = new ConcurrentDictionary(); - - private static CancellationTokenSource? _cts; private static Task? _workerTask; public RedisClient Redis { get; set; } - public Repository videoTaskDB { get; set; } - public Repository taskLogDB { get; set; } - public RedisManager(RedisClient redis, Repository videoTaskDB, Repository taskLogDB) + public RedisManager(RedisClient redis) { Redis = redis; - this.videoTaskDB = videoTaskDB; - this.taskLogDB = taskLogDB; } @@ -170,37 +158,5 @@ namespace VideoAnalysisCore.Common { Redis.Set(RedisExpandKey.TaskGPT(taskId) + ":" + time, data, timeoutSeconds: 3600 * 24); } - /// - /// 加入到消费队列 - /// - /// - public void JoinQueue(params long[] taskIds) - { //事务 - if (taskIds is null || taskIds.Length == 0) - return; - using (var tran = Redis.Multi()) - { - foreach (var item in taskIds) - tran.LPush(RedisExpandKey.ChannelKey, item); - tran.Exec(); - } - } - - // AddTaskLog 已迁移至 WorkflowBase - - /// - /// 获取任务进度 - /// - /// - /// 工作流名称(可选) - public string GetTaskProgress(object taskId, string workflowName = "VideoSliceWorkflow") - { - var fieldName = workflowName == "VideoSliceWorkflow" ? "Progress" : $"Progress:{workflowName}"; - return Redis.HMGet(RedisExpandKey.Task(taskId), fieldName)[0] ?? ""; - } - - // 注意:SetTaskProgress, TaskEnd, SetTaskErrorMessage, ClearTaskError, SetTaskError - // 已迁移至 WorkflowBase,此处移除或标记为已废弃 - } } diff --git a/VideoAnalysisCore/Common/VideoSliceWorkflowManager.cs b/VideoAnalysisCore/Common/VideoSliceWorkflowManager.cs index 94d869c..d297727 100644 --- a/VideoAnalysisCore/Common/VideoSliceWorkflowManager.cs +++ b/VideoAnalysisCore/Common/VideoSliceWorkflowManager.cs @@ -39,13 +39,15 @@ namespace VideoAnalysisCore.Common private readonly IServiceProvider _serviceProvider; private readonly FFMPGEHandle _ffmpeg; private readonly SenseVoice _senseVoice; + private readonly TidySlideWorkflowManager _tidySlideWorkflowManager; - public VideoSliceWorkflowInit(VideoSliceWorkflowManager manager, IServiceProvider serviceProvider, FFMPGEHandle ffmpeg, SenseVoice senseVoice) + public VideoSliceWorkflowInit(VideoSliceWorkflowManager manager, IServiceProvider serviceProvider, FFMPGEHandle ffmpeg, SenseVoice senseVoice, TidySlideWorkflowManager tidySlideWorkflowManager) { _manager = manager; _serviceProvider = serviceProvider; _ffmpeg = ffmpeg; _senseVoice = senseVoice; + _tidySlideWorkflowManager = tidySlideWorkflowManager; Init(); _manager.InitChannel(); } @@ -62,7 +64,17 @@ namespace VideoAnalysisCore.Common await downloadService.RunTask(task, "VideoSliceWorkflow"); }); SubscribeList.Add(RedisChannelEnum.分离音频, _ffmpeg.RunAsync); - SubscribeList.Add(RedisChannelEnum.解析字幕, _senseVoice.RunTask); + SubscribeList.Add(RedisChannelEnum.解析字幕, async (task) => + { + await _senseVoice.RunTask(task); + // 触发 TidySlide 工作流 + if (AppCommon.Config.Workflow.TidySlide.Enabled) + { + await _tidySlideWorkflowManager.AddTaskLog(long.Parse(task), "由解析字幕完成触发 TidySlide 工作流"); + //不阻塞 + _=_tidySlideWorkflowManager.InsertChannel(RedisTidySlideChannelEnum.下载文件, task); + } + }); SubscribeList.Add(RedisChannelEnum.AI课程类型, async (task) => { diff --git a/VideoAnalysisCore/Common/WorkflowBase.cs b/VideoAnalysisCore/Common/WorkflowBase.cs index 43029ce..50e72b4 100644 --- a/VideoAnalysisCore/Common/WorkflowBase.cs +++ b/VideoAnalysisCore/Common/WorkflowBase.cs @@ -461,5 +461,19 @@ namespace VideoAnalysisCore.Common RunningTasks.TryAdd(tId, bgTask); await Task.CompletedTask; } + + + /// + /// 加入到消费队列 + /// + /// + public void JoinQueue(params long[] taskIds) + { + if (taskIds is null || taskIds.Length == 0) + return; + // 直接批量推入,避免循环和事务开销 + var d = taskIds.Select(s => (object)s).ToArray(); + Redis.LPush(ChannelKey, d); + } } } diff --git a/VideoAnalysisCore/Controllers/LJZK_Controller.cs b/VideoAnalysisCore/Controllers/LJZK_Controller.cs index d283e13..cdad991 100644 --- a/VideoAnalysisCore/Controllers/LJZK_Controller.cs +++ b/VideoAnalysisCore/Controllers/LJZK_Controller.cs @@ -39,11 +39,12 @@ namespace VideoAnalysisCore.Controllers private readonly Repository nodePackageInfoDB; private readonly Repository videoQuestionDB; private readonly Repository videoQuestionKonwDB; - private readonly RedisManager redisManager; + private readonly RedisManager redisManager; + readonly VideoSliceWorkflowManager videoSliceWorkflowManager; public LJZK_Controller(IMapper mp, Repository nodesubscriptionDB, Repository videoTaskDB = null, Repository videoKonwPointDB = null - , Repository nodePackageInfoDB = null, Repository videoQuestionDB = null, Repository videoQuestionKonwDB = null, Repository courseInfoDB = null, RedisManager redisManager = null, Repository videoTaskStageDB = null) + , Repository nodePackageInfoDB = null, Repository videoQuestionDB = null, Repository videoQuestionKonwDB = null, Repository courseInfoDB = null, RedisManager redisManager = null, Repository videoTaskStageDB = null, VideoSliceWorkflowManager videoSliceWorkflowManager = null) { this.mp = mp; this.nodesubscriptionDB = nodesubscriptionDB; @@ -55,6 +56,7 @@ namespace VideoAnalysisCore.Controllers this.courseInfoDB = courseInfoDB; this.redisManager = redisManager; this.videoTaskStageDB = videoTaskStageDB; + this.videoSliceWorkflowManager = videoSliceWorkflowManager; } @@ -158,7 +160,7 @@ namespace VideoAnalysisCore.Controllers if (videos is null || videos.Count == 0) return Ok(); var ids = videos.Select(s => s.Id).ToArray(); - redisManager.JoinQueue(ids); + videoSliceWorkflowManager.JoinQueue(ids); return Ok(); } diff --git a/VideoAnalysisCore/Controllers/VideoTaskController.cs b/VideoAnalysisCore/Controllers/VideoTaskController.cs index 896456d..cee4ec6 100644 --- a/VideoAnalysisCore/Controllers/VideoTaskController.cs +++ b/VideoAnalysisCore/Controllers/VideoTaskController.cs @@ -110,16 +110,30 @@ namespace VideoAnalysisCore.Controllers #endif /// - /// 插入批量任务id + /// 添加任务到 TidySlide队列 /// /// 是否执行任务 /// - [HttpPost(Name = "JoinQueue")] - public IActionResult JoinQueue(long[] ids) + [HttpPost(Name = "JoinQueueVideoSlice")] + public IActionResult JoinQueue( long[] ids) { - if (ids == null || ids.Count() == 0) + if (ids == null || ids.Length == 0) return BadRequest("录入数据无效"); - redisManager.JoinQueue(ids); + videoSliceWorkflowManager.JoinQueue(ids); + return Ok(); + } + + /// + /// 添加任务到 TidySlide队列 + /// + /// + /// + [HttpPost(Name = "JoinQueueTidySlide")] + public IActionResult JoinTidySlideQueue( long[] ids) + { + if (ids == null || ids.Length == 0) + return BadRequest("录入数据无效"); + tidySlideWorkflowManager.JoinQueue(ids); return Ok(); } diff --git a/VideoAnalysisCore/VideoAnalysisCore.csproj b/VideoAnalysisCore/VideoAnalysisCore.csproj index a9fc04c..0ad094d 100644 --- a/VideoAnalysisCore/VideoAnalysisCore.csproj +++ b/VideoAnalysisCore/VideoAnalysisCore.csproj @@ -16,7 +16,7 @@ - +