新增 文件包未处理的视频

优化 下载视频时基于任务类型实时获取资源地址
This commit is contained in:
小肥羊 2025-03-21 16:49:39 +08:00
parent 374c582cd1
commit 8847ae987b
13 changed files with 350 additions and 42 deletions

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using SqlSugar;
using System.Data.Common;
using System.Linq.Expressions;
using System.Threading.Tasks;
using VideoAnalysisCore.Common;
@ -68,7 +69,9 @@ namespace Learn.VideoAnalysis.Components.Pages
async void ReStart()
{
await RedisExpand.SetTaskErrorMessage(reStartTask.Id, null);
RedisExpand.InsertChannel((RedisChannelEnum)selectEnum, reStartTask.Id);
_=Task.Run(() =>
RedisExpand.InsertChannel((RedisChannelEnum)selectEnum, reStartTask.Id)
);
modalShow = false;
}
private QueryModel<VideoTaskDto> lastQuery = null;

View File

@ -10,6 +10,7 @@ using VideoAnalysisCore.AICore.GPT.ChatGPT;
using VideoAnalysisCore.AICore.GPT;
using System.Text.Json;
using VideoAnalysisCore.Model.Enum;
using Yitter.IdGenerator;
namespace Learn.VideoAnalysis.Controllers
{
@ -157,6 +158,7 @@ namespace Learn.VideoAnalysis.Controllers
// 自动映射属性到哈希
var task = new VideoTask()
{
Id=YitIdHelper.NextId(),
ComeFrom = GetClientIpAddress(),
MediaUrl = req.MediaUrl,
ApiToken = req.ApiToken,
@ -167,7 +169,6 @@ namespace Learn.VideoAnalysis.Controllers
MediaName = req.Name
};
//入库
task.Id = await videoTaskDB.InsertReturnBigIdentityAsync(task);
var hashEntries = task.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(s => s.Name, s => s.GetValue(task));

View File

@ -7,6 +7,51 @@ using VideoAnalysisCore.Model.Enum;
namespace Learn.VideoAnalysis.Controllers.Dto
{
/// <summary>
/// 视频列表项
/// </summary>
public class StructurePageContentAnalyzeItem
{
/// <summary>
/// 录播内容编号
/// </summary>
public long StructurePageContentId { get; set; }
/// <summary>
/// 素材ID
/// </summary>
public long MaterialId { get; set; }
/// <summary>
/// 视频编码
/// </summary>
public string VideoCode { get; set; }
/// <summary>
/// 视频文件名称
/// </summary>
public string VideoName { get; set; }
}
public class NodePackageReq
{
/// <summary>
/// 媒体路径
/// </summary>
[Required(ErrorMessage = "文件节点ID是必填项")]
public long NodeId { get; set; }
/// <summary>
/// 任务类型
/// </summary>
public TaskTypeEnum? TaskType { get; set; }
/// <summary>
/// 学科类型
/// </summary>
public SubjectEnum? SubjectType { get; set; }
/// <summary>
/// 视频列表
/// </summary>
[Required(ErrorMessage = "文件节点ID是必填项")]
public List<StructurePageContentAnalyzeItem> AnalyzeItems { get; set; }
}
/// <summary>
/// 视频处理 请求
/// </summary>

View File

@ -11,6 +11,10 @@ using VideoAnalysisCore.AICore.GPT;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using VideoAnalysisCore.Model.Enum;
using FFmpeg.NET.Services;
using static FFmpeg.NET.MetaData;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Yitter.IdGenerator;
namespace Learn.VideoAnalysis.Controllers
{
@ -26,9 +30,12 @@ namespace Learn.VideoAnalysis.Controllers
private readonly Repository<NodeSubscription> nodesubscriptionDB;
private readonly Repository<VideoTask> videoTaskDB;
private readonly Repository<VideoKonwPoint> videoKonwPointDB;
private readonly Repository<NodePackageInfo> nodePackageInfoDB;
private readonly IBserGPT chatGPT;
public LJZK_Controller(ILogger<LJZK_Controller> logger,
IMapper mp, IBserGPT chatGPT, Repository<NodeSubscription> nodesubscriptionDB, Repository<VideoTask> videoTaskDB = null, Repository<VideoKonwPoint> videoKonwPointDB = null)
IMapper mp, IBserGPT chatGPT, Repository<NodeSubscription> nodesubscriptionDB,
Repository<VideoTask> videoTaskDB = null, Repository<VideoKonwPoint> videoKonwPointDB = null
, Repository<NodePackageInfo> nodePackageInfoDB = null)
{
_logger = logger;
this.mp = mp;
@ -36,6 +43,7 @@ namespace Learn.VideoAnalysis.Controllers
this.nodesubscriptionDB = nodesubscriptionDB;
this.videoTaskDB = videoTaskDB;
this.videoKonwPointDB = videoKonwPointDB;
this.nodePackageInfoDB = nodePackageInfoDB;
}
@ -47,7 +55,7 @@ namespace Learn.VideoAnalysis.Controllers
[HttpPost(Name = "NodeSubscription")]
public async Task<IActionResult> NodeSubscription(NodeMonitoringReq req)
{
if(req is null || req.NodeId ==0)
if (req is null || req.NodeId == 0)
return BadRequest("无效的提交数据");
if (nodesubscriptionDB.IsAny(s => s.NodeId == req.NodeId))
return BadRequest("重复添加了节点监控任务" + req.NodeId);
@ -55,12 +63,64 @@ namespace Learn.VideoAnalysis.Controllers
{
NodeId = req.NodeId,
TaskType = req.Type ?? default,
Subject = req.Subject?? default,
Subject = req.Subject ?? default,
});
return Ok(res);
}
/// <summary>
/// 蓝鲸智库_文件包订阅
/// </summary>
/// <param name="req">请求体</param>
/// <returns></returns>
[HttpPost(Name = "NodePackage")]
public async Task<IActionResult> NodePackage(NodePackageReq req)
{
if (req is null || req.NodeId == 0)
return BadRequest("无效的提交数据");
if (req.AnalyzeItems is null || req.AnalyzeItems.Count() == 0)
return BadRequest("无效视频列表数据");
var videos = new List<VideoTask>(req.AnalyzeItems.Count);
var nodePackages = new List<NodePackageInfo>(req.AnalyzeItems.Count);
var videoIdArr = videoTaskDB.AsQueryable().Select(v => v.TagId).Distinct().ToArray();
foreach (var s in req.AnalyzeItems)
{
var np = new NodePackageInfo()
{
MaterialId = s.MaterialId,
VideoCode = s.VideoCode,
NodeId = req.NodeId,
StructurePageContentId = s.StructurePageContentId,
};
nodePackages.Add(np);
if (videoIdArr.Contains(s.VideoCode))
{
Console.WriteLine($"重复任务");
continue;
}
videos.Add(new VideoTask()
{
Id = YitIdHelper.NextId(),
ComeFrom = "127.0.0.1",
ApiToken = "",
Type = req.TaskType,
Subject = req.SubjectType,
Tag = req.NodeId.ToString(),
TagId = s.VideoCode,
MediaUrl =string.Empty,
MediaName = s.VideoName
});
}
await nodePackageInfoDB.InsertRangeAsync(nodePackages);
await videoTaskDB.InsertRangeAsync(videos);
var ids = videos.Select(s => s.Id).ToArray();
RedisExpand.JoinQueue(ids);
return Ok();
}
/// <summary>
/// 获取任务类型
/// </summary>

View File

@ -20,6 +20,7 @@ namespace Learn.VideoAnalysis.Expand
service.AddScheduler();
service.AddTransient<NodeSubscriptionJob>();
service.AddTransient<TaskFileClearJob>();
service.AddTransient<NodePackageJob>();
}
public static void UseCoravelExpand(this IServiceProvider provider)
{
@ -27,7 +28,9 @@ namespace Learn.VideoAnalysis.Expand
{
//每5分钟执行一次 未处理视频扫描
scheduler.Schedule<NodeSubscriptionJob>().EveryFiveMinutes();
//每天02:10
//文件包分析
scheduler.Schedule<NodePackageJob>().HourlyAt(20);
//任务缓存清理
// scheduler.Schedule<TaskFileClearJob>().HourlyAt(10);
});
}

View File

@ -6,6 +6,12 @@
}
},
"AppConfig": {
"Subsystem": {
"蓝鲸智库": {
"APIUrl": "https://zyapi.23544.com",
"Token": ""
}
},
"Redis": {
"ConnectionString": "172.28.0.1,password=qwe123!@#,defaultDatabase=3"
},

View File

@ -12,6 +12,12 @@
"Account": "admin",
"Password": "q1w2e3!@#"
},
"Subsystem": {
"蓝鲸智库": {
"APIUrl": "http://192.168.2.117:6400",
"Token": ""
}
},
"Redis": {
"ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10"
},

View File

@ -12,6 +12,7 @@ using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.Model.;
using VideoAnalysisCore.Model.Enum;
using Mapster;
using System.Linq;
namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{
@ -77,14 +78,20 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
// " 这是一堂课的标题,请你基于标题帮我分析出这堂课所讲授的内容与最恰当的授课章节(关联最贴切的章节,保留一个章节!)." +
// $"章节范围限定在[{string.Join(',', xkwKnows)}]范围内." +
// $"输出格式 json字符串 对象格式{fileNameResFormat}";
var rCaptionArr = string.Join(',', captionsArr
.Where((s,i)=>i%3==0)
.Take((int)(captionsArr?.Length??0 / 2.2))
.Select(s => s.Text));
var fileNamePostMessages = title +
" 这是一堂课的部分授课字幕,请你基于字幕内容帮我分析出这堂课所讲授的内容与最恰当的授课章节(关联最贴切的章节,保留一个章节!)." +
var fileNamePostMessages =
"这是一堂课的部分授课字幕,请你基于字幕内容帮我分析出这堂课所讲授的内容与最恰当的授课章节(关联最贴切的章节,保留一个章节!)." +
$"章节范围限定在[{string.Join(',', xkwKnows)}]范围内." +
$"以下是包含时间的视频字幕文本。" +
$"字幕列表 {rCaptionArr}。" +
$"输出格式 json字符串 对象格式{fileNameResFormat}";
var fileNameInfoRes = await ChatAsync<FileNameInfo>
(task, fileNamePostMessages, null);//, "deepseek-chat");
(task, fileNamePostMessages, null);
var captions = ExpandFunction.GetSpeakerCaptions(captionsArr, speakerArr);

View File

@ -8,6 +8,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Runtime.Loader;
using System.Text;
using System.Text.Json;
@ -414,6 +415,24 @@ namespace VideoAnalysisCore.Common
public long ConfigId { get; set; }
}
/// <summary>
/// 子系统配置
/// </summary>
public class SubsystemInfo
{
public string APIUrl { get; set; } = string.Empty;
public string Token { get; set; } = string.Empty;
}
/// <summary>
/// 子系统配置
/// </summary>
public class SubsystemConfig
{
public SubsystemInfo { get; set; } = new SubsystemInfo();
}
/// <summary>
/// 应用程序配置
/// </summary>
@ -428,6 +447,10 @@ namespace VideoAnalysisCore.Common
/// </summary>
public AdminConfig Admin { get; set; } = new AdminConfig();
/// <summary>
/// 子系统
/// </summary>
public SubsystemConfig Subsystem { get; set; } = new SubsystemConfig();
/// <summary>
/// redis
/// </summary>
public RedisConfig Redis { get; set; } = new RedisConfig();

View File

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using VideoAnalysisCore.Job;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Enum;
using AlibabaCloud.SDK.Vod20170321;
namespace VideoAnalysisCore.Common
{
@ -24,22 +25,9 @@ namespace VideoAnalysisCore.Common
public static void AddDownloadFileExpand(this IServiceCollection services, int DownloadSpeed)
{
DownloadFile.DownloadSpeed = DownloadSpeed;
services.AddSingleton<DownloadFile>();
}
services.AddTransient<DownloadFile>();
}
/// <summary>
///
/// </summary>
public class DownloadFile
{
static DownloadConfiguration Opt { get; set; } = default!;
public static int DownloadSpeed { get; set; } = default!;
private readonly Repository<VideoTask> videoTaskDB;
public DownloadFile(Repository<VideoTask> videoTaskDB)
{
Opt = new DownloadConfiguration()
DownloadFile.Opt = new DownloadConfiguration()
{
// 通常主机支持的最大值为8000字节默认值是8000
BufferBlockSize = 10240,
@ -91,7 +79,23 @@ namespace VideoAnalysisCore.Common
//}
}
};
}
}
/// <summary>
///
/// </summary>
public class DownloadFile
{
public static DownloadConfiguration Opt { get; set; } = default!;
public static int DownloadSpeed { get; set; } = default!;
private readonly Repository<VideoTask> videoTaskDB;
private readonly Client vodClient;
public DownloadFile(Repository<VideoTask> videoTaskDB, Client vodClient)
{
this.videoTaskDB = videoTaskDB;
this.vodClient = vodClient;
}
// 根据 Content-Type 映射文件后缀
@ -122,11 +126,35 @@ namespace VideoAnalysisCore.Common
{
var taskId = long.Parse(task);
//获取资源文件 地址
var taskInfo =await videoTaskDB.AsQueryable()
var taskInfo = await videoTaskDB.AsQueryable()
.Where(s => s.Id == taskId).FirstAsync();
if (taskInfo is null || string.IsNullOrEmpty(taskInfo.MediaName) || taskInfo.MediaName.Contains("教研"))
throw new Exception($"任务为null/是教研视频/没有视频课程名称");
var fileUrl = taskInfo.MediaUrl;
if (string.IsNullOrEmpty(fileUrl))
{
switch (taskInfo.Type)
{
case TaskTypeEnum._视频分段:
case TaskTypeEnum._中职视频分段:
var videoInfo = await vodClient.GetPlayInfoAsync(new AlibabaCloud.SDK.Vod20170321.Models.GetPlayInfoRequest()
{
VideoId = taskInfo.TagId,
Formats = "mp4",
OutputType = "cdn",
AuthTimeout = 3600 * 24 * 12,
});
if (videoInfo is null || videoInfo.StatusCode != 200 && !videoInfo.Body.PlayInfoList.PlayInfo.Any())
throw new Exception($"{DateTime.Now} 视频订阅=>获取阿里云视频信息失败 VideoCode {taskInfo.TagId} StatusCode {videoInfo?.StatusCode}");
fileUrl = videoInfo.Body.PlayInfoList.PlayInfo.First().PlayURL;
break;
case TaskTypeEnum._视频分析:
break;
default:
break;
}
}
if (string.IsNullOrEmpty(fileUrl))
throw new Exception($"任务id[{task}] 资源地址无效 {fileUrl}");

View File

@ -0,0 +1,77 @@
using AlibabaCloud.SDK.Vod20170321;
using Coravel.Invocable;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http.Json;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using UserCenter.Model.Enum;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Dto;
using VideoAnalysisCore.Model.;
using Yitter.IdGenerator;
namespace VideoAnalysisCore.Job
{
/// <summary>
/// [蓝鲸智库] 查找文件包未处理的视频
/// </summary>
public class NodePackageJob : IInvocable
{
/// <summary>
/// 每个扫描文件包每次取出{20}个
/// </summary>
private readonly int TopLength = 20;
private readonly Repository<NodePackageInfo> nodePackageInfoDB;
private readonly Repository<Attachments> attachmentsDB;
private readonly Repository<VideoTask> videoTaskDB;
public NodePackageJob(Repository<Attachments> videoTaskDB,
Repository<NodePackageInfo> nodePackageInfoDB, Repository<VideoTask> videotaskDB)
{
this.attachmentsDB = videoTaskDB;
this.nodePackageInfoDB = nodePackageInfoDB;
this.videoTaskDB = videotaskDB;
}
public async Task Invoke()
{
Console.WriteLine($"{DateTime.Now} Invoke=>文件包任务");
var taskArr = await nodePackageInfoDB.AsQueryable()
.Where(s => s.SuccessTime == null)
.ToArrayAsync();
var videoIdArr = await videoTaskDB.AsQueryable()
.Where(s => s.EndTime != null)
.Select(s => s.TagId)
.ToArrayAsync();
var postData = new List<NodePackageInfo>();
foreach (var item in taskArr)
{
if (videoIdArr.Contains(item.VideoCode))
{
postData.Add(item);
item.SuccessTime = DateTime.Now;
}
}
Console.WriteLine($"{DateTime.Now} Invoke=>文件包任务 已完成任务回调 数量{postData.Count}");
if (postData.Count() == 0)
return;
var responseMessage = await new HttpClient()
.PostAsJsonAsync(AppCommon.Config.Subsystem..APIUrl + "/api/callback/platform/videosAnalyze", postData);
if (responseMessage.IsSuccessStatusCode)
{
var res = await responseMessage.Content.ReadAsStringAsync();
Console.WriteLine($"{DateTime.Now} Invoke=>文件包任务 回调结果 {res}");
await nodePackageInfoDB.AsUpdateable(postData)
.UpdateColumns(it => new { it.SuccessTime })
.ExecuteCommandAsync();
}
}
}
}

View File

@ -1,5 +1,4 @@
using AlibabaCloud.SDK.Vod20170321;
using Coravel.Invocable;
using Coravel.Invocable;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
@ -28,13 +27,11 @@ namespace VideoAnalysisCore.Job
private readonly Repository<NodeSubscription> nodesubscriptionDB;
private readonly Repository<Attachments> attachmentsDB;
private readonly Repository<VideoTask> videotaskDB;
private readonly Client vodClient;
public NodeSubscriptionJob(Repository<Attachments> videoTaskDB, Repository<NodeSubscription> nodesubscriptionDB, Repository<VideoTask> videotaskDB, Client vodClient)
public NodeSubscriptionJob(Repository<Attachments> videoTaskDB, Repository<NodeSubscription> nodesubscriptionDB, Repository<VideoTask> videotaskDB)
{
this.attachmentsDB = videoTaskDB;
this.nodesubscriptionDB = nodesubscriptionDB;
this.videotaskDB = videotaskDB;
this.vodClient = vodClient;
}
public async Task Invoke()
{
@ -81,15 +78,6 @@ namespace VideoAnalysisCore.Job
var videos = new List<VideoTask>(data.Count);
foreach (var s in data)
{
var videoInfo = await vodClient.GetPlayInfoAsync(new AlibabaCloud.SDK.Vod20170321.Models.GetPlayInfoRequest()
{
VideoId = s.VideoCode, Formats="mp4",OutputType = "cdn", AuthTimeout = 3600 * 24 * 3,
});
if (videoInfo is null || videoInfo.StatusCode != 200 && !videoInfo.Body.PlayInfoList.PlayInfo.Any())
{
Console.WriteLine($"{DateTime.Now} 视频订阅=>获取阿里云视频信息失败 VideoCode {s.VideoCode} StatusCode {videoInfo?.StatusCode}");
continue;
}
videos.Add(new VideoTask()
{
Id = YitIdHelper.NextId(),
@ -99,7 +87,7 @@ namespace VideoAnalysisCore.Job
Subject = item.Subject,
Tag = item.NodeId.ToString(),
TagId = s.VideoCode,
MediaUrl = videoInfo.Body.PlayInfoList.PlayInfo.First().PlayURL,
MediaUrl = string.Empty,
MediaName = s.Name
});
}

View File

@ -0,0 +1,61 @@
using AntDesign;
using SqlSugar;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Net;
using UserCenter.Model.Enum;
using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.Model.Enum;
using VideoAnalysisCore.Model.Interface;
using Whisper.net;
namespace VideoAnalysisCore.Model
{
/// <summary>
/// [蓝鲸智库]文件节点订阅
/// </summary>
[SugarTable("nodepackageinfo")]
public class NodePackageInfo : IDB
{
/// <summary>
/// Id
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
[DisplayName("编号")]
public long Id { get; set; }
/// <summary>
/// 文件节点Id
/// </summary>
[DisplayName("文件节点Id"), Required]
public long NodeId { get; set; }
/// <summary>
/// 文件节点存储ID
/// </summary>
[DisplayName("文件节点存储ID"), Required]
[SugarColumn(Length = 32)]
public string VideoCode { get; set; }
/// <summary>
/// 录播内容编号
/// </summary>
public long StructurePageContentId { get; set; }
/// <summary>
/// 素材ID
/// </summary>
public long MaterialId { get; set; }
/// <summary>
/// 完成时间
/// </summary>
[SugarColumn(IsNullable = true)]
public DateTime? SuccessTime { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[DisplayName("创建时间")]
public DateTime CreateTime { get; set; } =DateTime.Now;
}
}