diff --git a/VideoAnalysis/Program.cs b/VideoAnalysis/Program.cs index 9d61e40..5d1ef8f 100644 --- a/VideoAnalysis/Program.cs +++ b/VideoAnalysis/Program.cs @@ -66,6 +66,7 @@ namespace Learn.VideoAnalysis builder.Services.AddSimpleTexOcrClient(); builder.Services.AddDownloadFileExpand(); builder.Services.AddAlibabaCloudVod(); + builder.Services.AddAliyunOSS(); builder.Services.AddRedisExpand(); builder.Services.AddSpeakerAI(); builder.Services.AddCoravel(); diff --git a/VideoAnalysis/appsettings.json b/VideoAnalysis/appsettings.json index b48376c..fb0733a 100644 --- a/VideoAnalysis/appsettings.json +++ b/VideoAnalysis/appsettings.json @@ -58,13 +58,21 @@ "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", "SqlType": "MySql", - "UpdateTable": false + "UpdateTable": true }, "AlibabaCloudVod": { "AccessKeyId": "LTAI5tDC6p9h747B7FHbgwkH", "AccessKeySecret": "vRKgmbp1LB05LaGOjh3ZrZxbHSLYLF", "EndPoint": "vod.cn-shanghai.aliyuncs.com" //上传节点 }, + "AliyunOSS": { + "AccessKeyId": "LTAI5tDC6p9h747B7FHbgwkH", + "AccessKeySecret": "vRKgmbp1LB05LaGOjh3ZrZxbHSLYLF", + "BucketDomain": "https://learn-videoanalysis.oss-cn-chengdu.aliyuncs.com", + "Region": "cn-chengdu", + "BucketName": "learn-videoanalysis", + "EndPoint": "oss-cn-chengdu.aliyuncs.com" //上传节点 + }, "OtherDBArr": [ { "ConfigId": 1001, //ResourceBank diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs index 6623516..2e95453 100644 --- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs +++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs @@ -16,6 +16,8 @@ using System.Linq; using System.Security.Cryptography; using static System.Collections.Specialized.BitVector32; using FFmpeg.NET.Services; +using Aliyun.OSS; +using Yitter.IdGenerator; namespace VideoAnalysisCore.AICore.GPT.DeepSeek { @@ -29,14 +31,16 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek private readonly Repository videoTaskDB; private readonly Repository videoKonwPointDB; private readonly Repository videoQuestionDB; + private readonly Repository videoQuestionKonwDB; private readonly Repository knowledgeInfoDB; private readonly SimpLetexClient simpLetexClient; + private readonly OssClient ossClient; /// /// 初始化 /// /// /// - public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository criteria, Repository videoTaskDB, Repository knowledgeInfoDB, Repository videoKonwPointDB, SimpLetexClient simpLetexClient, Repository videoQuestionDB) + public DeepSeek_GPT(DeepSeekGPTClient moonshotClient, Repository criteria, Repository videoTaskDB, Repository knowledgeInfoDB, Repository videoKonwPointDB, SimpLetexClient simpLetexClient, Repository videoQuestionDB, OssClient ossClient, Repository videoQuestionKonwDB) { chatClient = moonshotClient; criteriaDB = criteria; @@ -45,6 +49,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek this.videoKonwPointDB = videoKonwPointDB; this.simpLetexClient = simpLetexClient; this.videoQuestionDB = videoQuestionDB; + this.ossClient = ossClient; + this.videoQuestionKonwDB = videoQuestionKonwDB; } /// /// 获取内容对应的章节 @@ -95,6 +101,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek s => { var ks = s.KnowPoint.Split(",").Distinct(); + var StageId=Yitter.IdGenerator.YitIdHelper.NextId(); return ks.Where(x => knowDic.ContainsKey(x)) .Select(x => new VideoKonwPoint() { @@ -102,6 +109,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek Theme = s.Theme, StartTime = s.StartTime, EndTime = s.EndTime, + StageId = StageId, KnowPoint = x, KnowPointId = knowDic[x].ToString(), TagId = taskInfo.TagId, @@ -445,11 +453,12 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek private async Task AnalysisVideoQuestions(VideoTask taskInfo) { - if (taskInfo is null || !string.IsNullOrEmpty(taskInfo.PPTKeyFrame)) + if (taskInfo is null || string.IsNullOrEmpty(taskInfo.PPTKeyFrame)) return null; var farmeArr = JsonSerializer.Deserialize(taskInfo.PPTKeyFrame); var videoKnowArr = await videoKonwPointDB.GetListAsync(s => s.VideoTaskId == taskInfo.Id); - var insertData =new List(); + var insertData =new List(); + var insertQuestionKonw = new List(); foreach (var item in farmeArr) { var knowInfoArr = videoKnowArr @@ -457,33 +466,47 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek .ToArray(); if (knowInfoArr is null || knowInfoArr.Count() ==0) return null; - while (true) + var tryCount = 50; + while (tryCount>1) { + tryCount--; try { + var filePath = taskInfo.FramePath(item); var sRes = await simpLetexClient - .ProcessImageAsync(new SimpleTexOcrRequest(taskInfo.FramePath(item))); + .ProcessImageAsync(new SimpleTexOcrRequest(filePath)); if (!sRes.Success) continue; var knowArr=string.Join(',', knowInfoArr.Select(s => s.KnowPoint + "|"+s.KnowPointId)); - var resFormat = """[{"TopicStem":string(试题题干),"Question:string(问题)","KnowPointId":(string)知识点ID(多个使用逗号拼接)}]"""; + var resFormat = """[{"TopicStem":string(试题题干),"Question:string(问题)","KnowPointId":(string)知识点ID}]"""; var postMessages = $"提供一段内容是md格式的试题内容字符串。" + - $"请提取出其中的试题内容。并且为他们关联上在我限定范围内的知识点。" + + $"请提取出其中的试题内容。并且为每个试题关联上在我限定范围内的知识点(多个则用逗号分割)。" + $"知识点范围[{knowArr}]。" + $"排除不是试题内容的文字,优化公式排版并且去除题号。" + $"如果存在多道大题,请帮忙拆分开!" + $"输出内容只返回json格式为({resFormat})" + $"以下是试题内容" + $"`{sRes.Result.res.info.markdown}`"; - var resData = await ChatAsync(taskInfo.Id.ToString(), postMessages, "提取试题", "deepseek-chat"); + var resData = await ChatAsync(taskInfo.Id.ToString(), postMessages, "提取试题", "deepseek-chat"); foreach (var q in resData) { + q.FilePath = filePath; q.VideoTaskId = taskInfo.Id; - q.VideoKonwPoint = knowInfoArr.First().Id; + q.StageId = knowInfoArr.First().StageId; + foreach (var kid in q.KnowPointId.Split(",")) + { + insertQuestionKonw.Add(new VideoQuestionKonw() + { + KnowPoint = knowInfoArr.First(s => s.KnowPointId == kid).KnowPoint, + KnowPointId= kid, + StageId =q.StageId, + VideoTaskId = q.VideoTaskId, + }); + } } - //处理知识点 insertData.AddRange(resData); + break; } catch (Exception) { @@ -491,9 +514,17 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek } } } - - - await videoQuestionDB.InsertRangeAsync(insertData); + //分组共同题干试题ID + foreach (var item in insertData.GroupBy(x => x.TopicStem)) + { + var keyId=YitIdHelper.NextId(); + foreach (var s in item) + s.TopicId = keyId; + } + //上传oss + ossClient.AddVideoQuestionUrl(insertData); + await videoQuestionDB.InsertRangeAsync(insertData.Adapt()); + await videoQuestionKonwDB.InsertRangeAsync(insertQuestionKonw); return null; } diff --git a/VideoAnalysisCore/AntDeploy.json b/VideoAnalysisCore/AntDeploy.json new file mode 100644 index 0000000..c8a7e49 --- /dev/null +++ b/VideoAnalysisCore/AntDeploy.json @@ -0,0 +1,57 @@ +{ + "Env": [], + "IIsConfig": { + "SdkType": null, + "WebSiteName": "", + "LastEnvName": null, + "EnvPairList": [] + }, + "WindowsServiveConfig": { + "ServiceName": "", + "SdkType": null, + "LastEnvName": null, + "EnvPairList": [] + }, + "LinuxServiveConfig": { + "ServiceName": "", + "EnvParam": "", + "LastEnvName": null, + "EnvPairList": [] + }, + "DockerConfig": { + "Prot": "", + "AspNetCoreEnv": "", + "LastEnvName": null, + "RemoveDaysFromPublished": "10", + "WorkDir": "", + "Volume": "", + "Other": "", + "EnvPairList": [] + }, + "DockerImageConfig": { + "BaseHttpProxy": "", + "BaseImage": "", + "BaseImageCredential": { + "UserName": "", + "Password": "" + }, + "TargetImage": "", + "TargetHttpProxy": "", + "TargetTags": [ + "" + ], + "TargetImageCredential": { + "UserName": "", + "Password": "" + }, + "ImageFormat": "Docker", + "Entrypoint": [ + "" + ], + "Cmd": [ + "" + ], + "IgnoreList": [], + "SkipExistingImages": false + } +} \ No newline at end of file diff --git a/VideoAnalysisCore/Common/AppCommon.cs b/VideoAnalysisCore/Common/AppCommon.cs index 334d1cb..0b7ee2f 100644 --- a/VideoAnalysisCore/Common/AppCommon.cs +++ b/VideoAnalysisCore/Common/AppCommon.cs @@ -99,7 +99,7 @@ namespace VideoAnalysisCore.Common /// public static string FramePath(this VideoTask task, int fTime) { - return Path.Combine(task.Id.ToString().LocalPath(), $"{FrameName}{fTime / 5}.jpg"); + return Path.Combine(task.Id.ToString().LocalPath(), $"{FrameName}{(fTime / 5).ToString().PadLeft(3,'0')}.jpg"); } /// diff --git a/VideoAnalysisCore/Common/AppConfig.cs b/VideoAnalysisCore/Common/AppConfig.cs index d2d53fd..acbca26 100644 --- a/VideoAnalysisCore/Common/AppConfig.cs +++ b/VideoAnalysisCore/Common/AppConfig.cs @@ -44,6 +44,7 @@ namespace VideoAnalysisCore.Common /// 阿里云视频点播配置 /// public AlibabaCloudVodConfig AlibabaCloudVod { get; set; } = new AlibabaCloudVodConfig(); + public AliyunOSSConfig AliyunOSS { get; set; } = new AliyunOSSConfig(); /// diff --git a/VideoAnalysisCore/Common/Expand/AliyunOSSExpand.cs b/VideoAnalysisCore/Common/Expand/AliyunOSSExpand.cs new file mode 100644 index 0000000..38705f5 --- /dev/null +++ b/VideoAnalysisCore/Common/Expand/AliyunOSSExpand.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using AlibabaCloud.OpenApiClient.Models; +using AlibabaCloud.SDK.Vod20170321; +using AlibabaCloud.SDK.Vod20170321.Models; +using AlibabaCloud.TeaUtil.Models; +using Aliyun.OSS.Common; +using Aliyun.OSS; +using Microsoft.Extensions.DependencyInjection; +using VideoAnalysisCore.Job; +using VideoAnalysisCore.Model; +using System.Security.AccessControl; +using Aliyun.Credentials.Models; +using VideoAnalysisCore.Model.Dto; +using System.IO; + +namespace VideoAnalysisCore.Common +{ + public class AliyunOSSConfig + { + /// + /// id + /// + public string AccessKeyId { get; set; } + /// + ///密钥 + /// + public string AccessKeySecret { get; set; } + /// + /// 区域Url + /// + public string Region { get; set; } + /// + /// 筒域名 + /// + public string BucketDomain { get; set; } + /// + /// 桶名称 + /// + public string BucketName { get; set; } + public string Endpoint { get; set; } = "oss-cn-chengdu.aliyuncs.com"; + } + + /// + /// 阿里云 视频点播拓展 + /// + public static class AliyunOSSExpand + { + /// + /// 使用阿里云 vod拓展 + /// + /// + /// + public static void AddAliyunOSS(this IServiceCollection service) + { + Console.WriteLine($"{DateTime.Now}=>初始化 阿里云OSS"); + AliyunOSSConfig config = new() + { + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 + AccessKeyId = AppCommon.Config.AliyunOSS.AccessKeyId, + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 + AccessKeySecret = AppCommon.Config.AliyunOSS.AccessKeySecret, + Endpoint = AppCommon.Config.AliyunOSS.Endpoint + };// 创建ClientConfiguration实例,按照您的需要修改默认参数。 + var conf = new ClientConfiguration(); + // 设置v4签名。 + conf.SignatureVersion = SignatureVersion.V4; + // 创建OssClient实例。 + var oss = new OssClient(config.Endpoint, config.AccessKeyId, config.AccessKeySecret, conf); + oss.SetRegion(config.Region); + + service.AddSingleton(oss); + } + /// + /// 上传文件 + /// + /// + /// 视频尸体片段 + public static void AddVideoQuestionUrl(this OssClient oss, List fileArr ) + { + var cached = new HashSet(); + foreach (var item in fileArr) + { + try + { + var path = item.VideoTaskId.ToString() + "/" + Path.GetFileName(item.FilePath); + if (cached.Contains(item.FilePath)) + { + item.ImageUrl = AppCommon.Config.AliyunOSS.BucketDomain + "/" + path; + continue; + } + using var file = File.OpenRead(item.FilePath); + var result = oss + .PutObject( + AppCommon.Config.AliyunOSS.BucketName, + path, + file); + item.ImageUrl = AppCommon.Config.AliyunOSS.BucketDomain + "/" + path; + cached.Add(item.FilePath); + continue; + } + catch (Exception) + { + + } + } + } + } +} diff --git a/VideoAnalysisCore/Common/Expand/SimpLetexExpand.cs b/VideoAnalysisCore/Common/Expand/SimpLetexExpand.cs index 1046c44..08685bb 100644 --- a/VideoAnalysisCore/Common/Expand/SimpLetexExpand.cs +++ b/VideoAnalysisCore/Common/Expand/SimpLetexExpand.cs @@ -58,7 +58,7 @@ namespace VideoAnalysisCore.Common /// 用以指定识别图片的类型,如果使用auto则会自动检测,使用document会返回markdown文档结果,使用formula会返回LaTeX结果 /// "auto", "document", "formula" /// - public string rec_mode { get; set; } = "auto"; + public string rec_mode { get; set; } = "document"; /// /// 开启后,模型将基于0°,90°, 180°, 270°自动矫正上传图片的方向,默认不开启 /// @@ -159,8 +159,9 @@ namespace VideoAnalysisCore.Common try { var response = await client.SendAsync(requestMessage); + var resStr = await response.Content.ReadAsStringAsync(); var responseContent = await response.Content.ReadFromJsonAsync(); - + request.file.Dispose(); return new SimpleTexOcrResponse { Success = response.IsSuccessStatusCode, diff --git a/VideoAnalysisCore/Model/Dto/VideoQuestionOSSDto.cs b/VideoAnalysisCore/Model/Dto/VideoQuestionOSSDto.cs new file mode 100644 index 0000000..fe73a63 --- /dev/null +++ b/VideoAnalysisCore/Model/Dto/VideoQuestionOSSDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VideoAnalysisCore.Model.Dto +{ + public class VideoQuestionOSSDto: VideoQuestion + { + public string FilePath { get; set; } + public string KnowPointId { get; set; } + } +} diff --git a/VideoAnalysisCore/Model/VideoKonwPoint.cs b/VideoAnalysisCore/Model/VideoKonwPoint.cs index 5d7a840..f8a1b46 100644 --- a/VideoAnalysisCore/Model/VideoKonwPoint.cs +++ b/VideoAnalysisCore/Model/VideoKonwPoint.cs @@ -20,13 +20,17 @@ namespace VideoAnalysisCore.Model /// /// id /// - [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + [SugarColumn(IsPrimaryKey = true)] public long Id { get; set; } /// /// 视频任务id /// public long VideoTaskId { get; set; } /// + /// 视频阶段id + /// + public long StageId { get; set; } + /// /// 自定义Id [任务视频自定义id] /// /// @@ -67,6 +71,7 @@ namespace VideoAnalysisCore.Model /// /// 课程阶段 /// + [SugarColumn(IsIgnore = true)] public virtual StageEnum? Stage { get; set; } } } diff --git a/VideoAnalysisCore/Model/VideoQuestion.cs b/VideoAnalysisCore/Model/VideoQuestion.cs index 9fc66da..d6ec0f2 100644 --- a/VideoAnalysisCore/Model/VideoQuestion.cs +++ b/VideoAnalysisCore/Model/VideoQuestion.cs @@ -27,22 +27,26 @@ namespace VideoAnalysisCore.Model /// public long VideoTaskId { get; set; } /// - /// 视频片段ID - /// 隶属于 + /// 视频阶段id + /// 隶属于 /// - public long VideoKonwPoint { get; set; } + public long StageId { get; set; } /// - /// 知识点 + /// 题干id + /// [用于多个一个题干多个问题] /// - public string? KnowPoint { get; set; } + public long TopicId { get; set; } + /// - /// 知识点ID + /// 图片地址 /// - public string? KnowPointId { get; set; } + [SugarColumn(Length =100)] + public string? ImageUrl { get; set; } /// /// 题干 /// + [SugarColumn(ColumnDataType ="text")] public string? TopicStem { get; set; } /// /// 问题(设问) diff --git a/VideoAnalysisCore/Model/VideoQuestionKonw.cs b/VideoAnalysisCore/Model/VideoQuestionKonw.cs new file mode 100644 index 0000000..85d0e3b --- /dev/null +++ b/VideoAnalysisCore/Model/VideoQuestionKonw.cs @@ -0,0 +1,43 @@ +using SqlSugar; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Text.Json; +using UserCenter.Model.Enum; +using VideoAnalysisCore.AICore.GPT.Dto; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Model.Enum; +using VideoAnalysisCore.Model.Interface; +using Whisper.net; + +namespace VideoAnalysisCore.Model +{ + /// + /// 视频片段试题知识点关系表 + /// + [SugarTable("videoquestionkonw")] + public class VideoQuestionKonw : IDB + { + /// + /// id + /// + [SugarColumn(IsPrimaryKey = true)] + public long Id { get; set; } + /// + /// 视频任务id + /// + public long VideoTaskId { get; set; } + /// + /// 知识点名称 + /// + public string KnowPoint { get; set; } + /// + /// 知识点ID + /// + public string KnowPointId { get; set; } + /// + /// 视频阶段id + /// 隶属于 + /// + public long StageId { get; set; } + } +} diff --git a/VideoAnalysisCore/VideoAnalysisCore.csproj b/VideoAnalysisCore/VideoAnalysisCore.csproj index 89946ab..5039bcf 100644 --- a/VideoAnalysisCore/VideoAnalysisCore.csproj +++ b/VideoAnalysisCore/VideoAnalysisCore.csproj @@ -59,6 +59,7 @@ +