同步代码

引入 知识点数据库
重写 AI分析视频片段功能
This commit is contained in:
小肥羊 2025-01-09 11:53:48 +08:00
parent 6981314257
commit 59c8171ce8
25 changed files with 603 additions and 35 deletions

View File

@ -56,6 +56,13 @@ namespace VideoAnalysisRazor.Layouts
Name = "登录页",
Key = "Login",
HideInMenu = true,
},
new MenuDataItem
{
Path = "/VideoTaskShow",
Name = "视频任务预览",
Key = "VideoTaskShow",
HideInMenu = true,
}
];
}

View File

@ -45,6 +45,10 @@
<Button Type="@ButtonType.Primary" Danger @onclick="() => ReStartClick(rowData.Data)">
重试
</Button>
<Button Type="@ButtonType.Primary" Icon="@IconType.Outline.Search" @onclick="() => PreviewTask(rowData.Data)">
预览任务
</Button>
</DescriptionsItem>
<DescriptionsItem Title="任务时间轴" Span="5">

View File

@ -20,6 +20,7 @@ namespace Learn.VideoAnalysis.Components.Pages
[Inject] private ConfirmService ComfirmService { get; set; } = default!;
[Inject] private Repository<VideoTask> taskDB { get; set; } = default!;
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private INotificationService _notice { get; set; } = default!;
@ -56,6 +57,10 @@ namespace Learn.VideoAnalysis.Components.Pages
reStartTask = query;
modalShow = true;
}
void PreviewTask(VideoTaskDto task)
{
NavigationManager.NavigateTo("/VideoTaskShow/"+task.Id);
}
/// <summary>
/// 重试
/// </summary>

View File

@ -0,0 +1,80 @@
@page "/VideoTaskShow/{tid}"
<div>
<input type="file" id="videoFile" accept="video/*">
<input type="file" id="subtitleFile" accept=".json">
<input type="file" id="segmentsFile" accept=".json">
</div>
<div id="video-container">
<video id="videoPlayer" controls></video>
<div id="subtitleArea" class="subtitles"></div>
</div>
<div id="segmentsContainer"></div>
<script>
const
const videoPlayer = document.getElementById('videoPlayer');
const subtitleArea = document.getElementById('subtitleArea');
let subtitles = @captionsArr
let segments = @videoKnows
let displayButton = []
let lastSegments = null;
window.onload(() => {
videoPlayer.src = @videoPath
})
videoPlayer.addEventListener('timeupdate', function () {
const currentTime = videoPlayer.currentTime;
subtitleArea.textContent = '';
subtitles.forEach(subtitle => {
let textContent = subtitle.Text || subtitle.Content;
if (currentTime >= subtitle.Start
&& currentTime <= subtitle.End
&& subtitleArea.textContent != textContent) {
subtitleArea.textContent = textContent;
}
});
let segment = displayButton.find(s => currentTime >= s.开始秒
&& currentTime <= s.结束秒)
if (segment) {
segment.button.style.backgroundColor = "rgb(238, 200, 118)";
if (lastSegments && lastSegments != segment) lastSegments.button.style.backgroundColor = "rgb(240, 249, 235)";
lastSegments = segment
}
});
function getF(segment) {
let sf = parseInt(segment.开始秒 / 60)
let sm = parseInt(segment.开始秒 % 60)
let ef = parseInt(segment.结束秒 / 60)
let em = parseInt(segment.结束秒 % 60)
return `${sf}:${sm} - ${ef}:${em}`
}
function displaySegments() {
const segmentsContainer = document.getElementById('segmentsContainer');
segmentsContainer.innerHTML = '';
segments.forEach(segment => {
const button = document.createElement('button');
displayButton.push(segment);
segment.button = button;
button.textContent =
`${getF(segment)} ${segment.主题}`;
button.addEventListener('click', () => {
videoPlayer.currentTime = segment.开始秒;
videoPlayer.play();
});
segmentsContainer.appendChild(button);
});
}
</script>
@code {
}

View File

@ -0,0 +1,72 @@
using AntDesign;
using AntDesign.TableModels;
using FFmpeg.NET.Services;
using Learn.VideoAnalysis.Controllers.Dto;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using SqlSugar;
using System.Linq.Expressions;
using System.Threading.Tasks;
using VideoAnalysisCore.AICore.GPT.Dto;
using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Dto;
namespace Learn.VideoAnalysis.Components.Pages
{
public partial class VideoTaskShow : ComponentBase
{
[Inject] private ConfirmService ComfirmService { get; set; } = default!;
[Inject] private IHttpContextAccessor HttpContext { get; set; } = default!;
[Inject] private Repository<VideoTask> taskDB { get; set; } = default!;
private VideoTask nowTask { get; set; } = default!;
private string videoPath { get; set; } = default!;
/// <summary>
/// 字幕
/// </summary>
private SenseVoiceRes[] captionsArr { get; set; } = default!;
/// <summary>
/// 分段
/// </summary>
private VideoKnowRes[] videoKnows { get; set; } = default!;
/// <summary>
/// 在渲染页面之后
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
}
/// <summary>
/// 初始化
/// </summary>
protected override async void OnInitialized()
{
var routeData = HttpContext.HttpContext.GetRouteData();
if (routeData is null)
return;
long taskId = (long)routeData.Values["id"];
nowTask = await taskDB.GetFirstAsync(s => s.Id == taskId);
if(nowTask is null)
return;
captionsArr = RedisExpand.Redis.HMGet<SenseVoiceRes[]>(RedisExpandKey.Task(taskId), "Captions").FirstOrDefault();
videoKnows = RedisExpand.Redis.HMGet<VideoKnowRes[]>(RedisExpandKey.Task(taskId), "VideoKnows").FirstOrDefault();
videoPath = AppCommon.GetVideoPath(nowTask.Id.ToString());
}
private async Task<bool> Comfirm(string message)
{
return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes;
}
}
}

View File

@ -0,0 +1,59 @@

* {
padding: 0;
margin: 0;
}
#video-container {
position: relative;
width: 1600px;
height: 850px;
float: left;
}
video {
width: 100%;
height: 100%;
}
.subtitles {
position: absolute;
bottom: -40px;
width: 100%;
text-align: center;
color: white;
background-color: rgba(0, 0, 0, 0.7);
font-size: 18px;
}
#segmentsContainer {
display: flex;
flex-direction: column;
width: 265px;
height: 850px;
gap: 10px;
overflow: hidden;
overflow-y: scroll;
float: left;
flex-wrap: nowrap;
padding: 10px;
align-content: flex-start;
justify-content: flex-start;
align-items: center;
}
#segmentsContainer button {
width: 100%;
height: 60px;
font-size: 1.3rem;
text-align: left;
cursor: pointer;
color: rgb(103, 194, 58);
background-color: rgb(240, 249, 235);
border: 1px solid rgb(179, 225, 157);
}
#segmentsContainer button:hover {
background-color: rgb(248, 230, 191) !important;
border: 1px solid rgb(206, 187, 81);
}

View File

@ -83,8 +83,7 @@ namespace Learn.VideoAnalysis.Controllers
public async Task<IActionResult> Test(long taskId)
{
//重新开始执行GPT分析
RedisExpand.InsertChannel(RedisChannelEnum.ChatModelAnalysis
, taskId);
chatGPT.GetKnow(taskId.ToString());
return Ok();
}
@ -206,6 +205,7 @@ namespace Learn.VideoAnalysis.Controllers
Subject = req.Subject,
Tag = req.Tag,
TagId = req.TagId,
MediaName = req.MediaName
};
//入库
task.Id = await videoTaskDB.InsertReturnBigIdentityAsync(task);

View File

@ -18,6 +18,12 @@ namespace Learn.VideoAnalysis.Controllers.Dto
[Url(ErrorMessage = "请输入有效的 URL")]
public string MediaUrl { get; set; } = string.Empty;
/// <summary>
/// 资源名称
/// </summary>
[Required(ErrorMessage = "资源名称是必要的")]
[Url(ErrorMessage = "请输入有效的 资源名称")]
public string MediaName { get; set; } = string.Empty;
/// <summary>
/// ApiKey
/// </summary>
[Required(ErrorMessage = "接口Token是必填项")]

View File

@ -7,6 +7,7 @@ using Mapster;
using VideoAnalysisCore.AICore.GPT;
using VideoAnalysisCore.AICore.GPT.KIMI;
using VideoAnalysisCore.AICore.GPT.ChatGPT;
using Microsoft.Extensions.FileProviders;
@ -101,7 +102,15 @@ namespace Learn.VideoAnalysis
//}
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(AppCommon.TaskCachedFile),
RequestPath = "/video",
//OnPrepareResponse = ctx => //»º´æ
//{
// ctx.Context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.CacheControl] = "public,max - age = 31536000";
//}
});
app.UseAntiforgery();
app.MapRazorComponents<App>()

View File

@ -27,14 +27,19 @@
"ApiKey": "sk-8BvvhESZIkgUbiaaJhglPxFa4o2X9H3xEv9lXELrWWwGxHWY"
},
"ChatGpt": {
"Host": "https://api.oaibest.com/",
"Host": "https://api.g4f.icu/",
//"Host": "https://api.oaibest.com/",
"ApiKey": "sk-D15tBln31N7dI9Fi7lds7OySFv5tOEK7DMNsG5rY2E6DCr4s"
}
},
"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": true,
"UpdateTable": true
},
"KnowsDB": {
"ConnectionString": "Server=47.109.35.116;Database=ResourceBank;UID=live;Password=Woshiren^&*();MultipleActiveResultSets=true;Encrypt=True;TrustServerCertificate=True;",
"SqlType": "SqlServer"
}
}
}

View File

@ -16,5 +16,6 @@ namespace VideoAnalysisCore.AICore.GPT
/// <param name="task">任务id</param>
/// <returns></returns>
public Task<TaskRes> CallGPT(string task);
public Task<TaskRes> GetKnow(string task);
}
}

View File

@ -8,6 +8,7 @@ using System.Reflection;
using VideoAnalysisCore.Model.Dto;
using VideoAnalysisCore.AICore.GPT.Dto;
using VideoAnalysisCore.AICore.GPT;
using System.Threading.Tasks;
namespace VideoAnalysisCore.AICore.GPT.ChatGPT
{
@ -19,16 +20,184 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
private readonly ChatGPTClient chatClient;
private readonly Repository<CourseGradingCriteria> criteriaDB;
private readonly Repository<VideoTask> videoTaskDB;
private readonly Repository<KnowledgeInfo> knowledgeInfoDB;
/// <summary>
/// 初始化
/// </summary>
/// <param name="moonshotClient"></param>
/// <param name="logger"></param>
public Chat_GPT(ChatGPTClient moonshotClient, Repository<CourseGradingCriteria> criteria, Repository<VideoTask> videoTaskDB)
public Chat_GPT(ChatGPTClient moonshotClient, Repository<CourseGradingCriteria> criteria, Repository<VideoTask> videoTaskDB, Repository<KnowledgeInfo> knowledgeInfoDB)
{
this.chatClient = moonshotClient;
criteriaDB = criteria;
this.videoTaskDB = videoTaskDB;
this.knowledgeInfoDB = knowledgeInfoDB;
}
private static List<VideoKnowRes> MergeRes(VideoKnowRes[] timeBases)
{
if (timeBases == null || timeBases.Count() == 0)
{
return new List<VideoKnowRes>();
}
var mergedList = new List<VideoKnowRes>();
// 初始化合并段
var current = timeBases.First();
foreach (var next in timeBases)
{
// 如果类型相同,则扩展时间段
if (current. == next.)
{
current. = Math.Max(current..Value, next..Value);
current. += next.;
}
else
{
// 类型不同,将当前时间段加入结果列表,并开始新时间段
mergedList.Add(current);
current = next;
}
}
// 添加最后的时间段
mergedList.Add(current);
return mergedList;
}
/// <summary>
/// 获取知识点
/// </summary>
/// <param name="task">任务id</param>
/// <returns></returns>
public async Task<TaskRes> GetKnow(string task)
{
var taskId = long.Parse(task);
var taskInfo = await videoTaskDB.AsQueryable()
.Where(s => s.Id == taskId)
.FirstAsync();
var subject = "数学";
var xkwKnows = await knowledgeInfoDB.AsQueryable()
.Where(s => s.Course_Id == 27
&& s.Depth == 2)
.Select(s => s.Name).ToArrayAsync();
string title = "周三1.3)《第八章——统计与概率:超几何分布的极限为二项分布》";
var fileNameResFormat = "{授课章节: string|null, 授课内容:string}";
var fileNamePostMessages = title +
" 这是一堂课的标题,请你帮我分析一些关于课堂方面的内容." +
$"1.分析出高中{subject}课堂授课的主要章节(例如 章节: 数列),章节范围限定在[{xkwKnows}]范围." +
$"2.分析出这堂课的主要授课内容." +
$"输出格式 json字符串 对象格式{fileNameResFormat}";
var fileNameInfoRes = await ChatAsync<FileNameInfo>(task, fileNamePostMessages, fileNameResFormat);
var captions = ExpandFunction.GetSpeakerCaptions(task);
var criteriaBuilder = new StringBuilder();
var resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Section":章节(string),"Theme":主题(string),"Content":内容总结(string)}]""";
var know = await knowledgeInfoDB.GetFirstAsync(s => s.Name == fileNameInfoRes.);
var knowledgeInfos = await knowledgeInfoDB.AsQueryable().ToChildListAsync(s => s.Parent_Id, know.Id);
var knows = "数列的概念,数列的定义,项的表示,数列的表示方法,通项公式,递推公式,图像表示,数列的类型,等差数列,等比数列,其他特殊数列,数列的性质,单调性,有限性,数列的求和,等差数列求和公式,等比数列求和公式,数列极限,递推关系";
knows = string.Join(',', knowledgeInfos.Select(s => s.Name));
var postMessages =
$"你的任务是分析视频字幕内容并提取出中国高考考试试题方法点,然后根据步骤分析出内容片段" +
$"按以下步骤完成:" +
$"1.识别方法点:提取字幕内容中与{subject}考试相关的方法点。" +
$"2.分类方法点:按学科方法点,细化到具体章节与主题(例如“章节:数列 主题:数列的基本概念”)。" +
$"3.分析总结:基于提取出的方法点名称来匹配我提供的方法点名称" +
$"提供的方法点名称(基本概念,课堂练习,{knows},例题讲解)。" +
$"4.关联合并相似的知识点来合并为内容片段。" +
$"内容片段使用关联知识点中的最小(开始秒)和(最大)结束秒,主题为关联知识点的主题分析,内容总结为关联知识点的内容总结分析。" +
$"延长内容片段时间区间来获取更加详细的上下文。" +
$"输入:包含时间戳的视频字幕文本。" +
$"以下是包含时间的视频字幕文本。" +
$"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" +
$"输出格式({resFormat})";
//var postMessages =
// $"你是一名专业的教育视频内容分析助手,主要任务是分析视频字幕内容,精准提取出与中国高考数学考试相关的试题方法点。" +
// $"按以下步骤完成:" +
// $"1.准确识别方法点:从字幕中提取与{subject}考试紧密相关的方法点,尤其关注与给定方法点类别相关的内容" +
// $"2.深入分析总结:依据给定的方法点类别进行约束,确定提取出的方法点所属类别。" +
// $"给定方法点包括(基本概念,课堂练习,例题讲解,解题技巧,{gjz})。" +
// $"3.细致分类方法点:按照学科方法点进行分类,具体细化到特定章节与主题,格式为 “章节:具体章节名称 主题:具体主题名称" +
// $"4.合理合并相似方法点为内容片段,确保内容的连贯性和逻辑性" +
// $"5.关联每个内容片段的方法点所有的时间并结构化输出。" +
// $"尽可能延内容片段时间区间,以获取详细的方法点上下文。" +
// $"输入:包含时间戳的视频字幕文本。输出格式:开始秒,结束秒,主题,内容总结" +
// $"以下是包含时间的视频字幕文本。" +
// $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" +
// $"返回固定的JSON格式({resFormat})";
var questionRes =await ChatAsync<VideoKnowRes[]>(task, postMessages, resFormat);
await RedisExpand.Redis
.HMSetAsync(RedisExpandKey.Task(task), "VideoKnows", questionRes);
var postMessages1 =
$"你的任务是分析json内容并合并含义相似的主题为新的主题" +
$"按以下步骤完成:" +
$"1.合理合并主题字段重复相似的对象为新的json对象确保内容的连贯性和逻辑性。" +
$"2.合并对象属性持续时间低于60秒的对象" +
$"3.结构化输出。" +
$"输入json对象 包含总结开始秒,结束秒,持续时间,主题,章节,内容总结" +
$"以下是包含json内容的文本。" +
$" {JsonSerializer.Serialize(questionRes)}" +
$"返回固定的JSON格式({resFormat})";
var questionRes1 = await ChatAsync<VideoKnowRes[]>(task, postMessages1, resFormat);
//questionRes1 = MergeRes(questionRes1).ToArray();
var gptRes = new TaskRes(captions);
await RedisExpand.Redis
.HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes);
RedisExpand.InsertChannel(RedisChannelEnum.EndTask, task);
return gptRes;
}
public async Task<T> ChatAsync<T>(string task,string postMessages,string resFormat)
{
var maxTokens = 4000;
var chatRep = new ChatRequest
{
max_tokens = maxTokens,
temperature = 0.3f,
messages = [
new Message(postMessages,"system"),
new Message(resFormat,"assistant"),
]
};
RedisExpand.SetTaskGPTReqCached(task, chatRep);
var chatResp = await chatClient.Chat(chatRep);
var chatResContent = chatResp?.res;
if (string.IsNullOrEmpty(chatResContent))
throw new Exception("GPT返回message无效结果");
if (chatResp != null)
RedisExpand.SetTaskGPTCached(task, new object[] { chatResp.Value.res, chatResp.Value.u });
chatResContent = chatResContent?.Replace("字幕内容", "课堂情况");
chatResContent = chatResContent?.Replace("\n", "");
chatResContent = chatResContent?.Replace("```json", "");
chatResContent = chatResContent?.Replace("```", "");
chatResContent = chatResContent?.Replace("}{", "},{");
chatResContent = chatResContent?.Replace("}|{", "},{");
chatResContent = chatResContent?.Trim();
var startsStr = typeof(T).IsArray? "[" :"{";
var endStr = typeof(T).IsArray? "]" : "}";
if (!chatResContent.StartsWith(startsStr))
chatResContent = startsStr + chatResContent;
if (!chatResContent.EndsWith(endStr))
chatResContent = chatResContent + endStr;
var questionRes = JsonSerializer.Deserialize<T>(chatResContent);
if (questionRes is null)
throw new Exception("ChatGPT返回无效结果");
//var totalTokens = chatResp?.u.total_tokens ?? 0;
//if (totalTokens > 1)
//{
// var tid = long.Parse(task);
// await videoTaskDB.AsUpdateable()
// .SetColumns(it => it.TotalTokens == totalTokens)//SetColumns是可以叠加的 写2个就2个字段赋值
// .Where(it => it.Id == tid)
// .ExecuteCommandAsync();
//}
return questionRes;
}
/// <summary>
/// 访问GPT

View File

@ -10,6 +10,28 @@ using System.Threading.Tasks;
namespace VideoAnalysisCore.AICore.GPT.Dto
{
public class VideoKnowRes
{
/// <summary>
/// 问题解释
/// </summary>
public float? { get; set; }
public float? { get; set; }
public float? => ( ?? 0) - ??0;
public string? { get; set; }
public string? { get; set; }
public string? { get; set; }
}
public class FileNameInfo
{
/// <summary>
/// 问题解释
/// </summary>
public string? { get; set; }
public string? { get; set; }
}
public class QuestionRes
{
/// <summary>

View File

@ -23,7 +23,7 @@ namespace VideoAnalysisCore.AICore.GPT.KIMI
/// <summary>
/// kimi 文本模型
/// </summary>
public class KIMI_GPT : IBserGPT
public class KIMI_GPT // : IBserGPT
{
private readonly MoonshotClient moonshotClient;
private readonly Repository<CourseGradingCriteria> criteriaDB;

View File

@ -233,7 +233,8 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
}
await RedisExpand.Redis.HMSetAsync(RedisExpandKey.Task(task), "Captions", res);
RedisExpand.InsertChannel(Enum.RedisChannelEnum.ParsingSpeaker, task);
//RedisExpand.InsertChannel(Enum.RedisChannelEnum.ParsingSpeaker, task);
RedisExpand.InsertChannel(Enum.RedisChannelEnum.ChatModelAnalysis, task);
}
}

View File

@ -4,12 +4,16 @@ using FreeRedis;
using Microsoft.Extensions.DependencyModel;
using SqlSugar;
using SqlSugar.IOC;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using UserCenter.Model.Interface;
using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Interface;
using VideoAnalysisCore.Model.Dto;
using Whisper.net;
@ -29,22 +33,26 @@ namespace VideoAnalysisCore.Common
/// 主库数据库表类型
/// </summary>
public static readonly IEnumerable<Type> DbMatserType;
public static readonly IEnumerable<Type> KnowsType;
static AppCommon()
{
try
{
Assemblies = ExpandFunction.GetAssemblies();
var assembliesType = Assemblies.Where(s => s.FullName.Contains("VideoAnalysis")).SelectMany(s => s.ExportedTypes
.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false)));
DbMatserType = assembliesType;
DbMatserType = assembliesType
.Where(u => u.GetInterfaces().Contains(typeof(IDB)));
KnowsType = assembliesType
.Where(u =>u.GetInterfaces().Contains(typeof(IKnowsDB)));
}
catch
{
throw;
}
//.Where(u => !u.IsDefined(typeof(SplitTableAttribute), false))
//.Where(u => !typeof(Model.DataCenterYH.IDataCenterYHModel).IsAssignableFrom(u))
//.Where(u => !u.IsSubclassOf(typeof(YQ_BaseEntity)));
//.Where(u => !u.IsDefined(typeof(SplitTableAttribute), false))
//.Where(u => !typeof(Model.DataCenterYH.IDataCenterYHModel).IsAssignableFrom(u))
//.Where(u => !u.IsSubclassOf(typeof(YQ_BaseEntity)));
}
/// <summary>
@ -66,6 +74,13 @@ namespace VideoAnalysisCore.Common
public static string AIModelFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AICore", "_Static");
/// <summary>
/// 获取视频路径
/// </summary>
/// <param name="tid"></param>
/// <returns></returns>
public static string GetVideoPath(string tid) =>
Path.Combine(TaskCachedFile, tid, tid + ".mp4");
}
/// <summary>
@ -140,35 +155,46 @@ namespace VideoAnalysisCore.Common
{
var captionsArr = RedisExpand.Redis.HMGet<SenseVoiceRes[]>(RedisExpandKey.Task(task), "Captions").FirstOrDefault();
var speakerArr = RedisExpand.Redis.HMGet<OfflineSpeakerRes[]>(RedisExpandKey.Task(task), "Speaker").FirstOrDefault();
if (captionsArr is null || captionsArr.Length == 0
|| speakerArr is null || speakerArr.Length == 0)
if (captionsArr is null || captionsArr.Length == 0)
//|| speakerArr is null || speakerArr.Length == 0)
throw new Exception("音频解析数据异常");
// 教师说话人Id
var techerId = speakerArr.GroupBy(s=>s.SpeakerIndex).Select(s => (s.Key,s.Sum(x=>x.Total)))
var techerId = speakerArr is null || !speakerArr.Any()
? 0
:speakerArr.GroupBy(s=>s.SpeakerIndex).Select(s => (s.Key,s.Sum(x=>x.Total)))
.OrderByDescending(s=>s.Item2).First().Key;
var teacherSpeaking = 0f;
var studentSpeaking = 0f;
var results = new Dictionary<SenseVoiceRes, List<int>>();
foreach (var segment in captionsArr)
if (speakerArr is null || speakerArr.Count() == 0)
{
var spList = new List<int>();
foreach (var speakerRes in speakerArr)
var ss = new List<int> { 1 };
results = captionsArr.ToDictionary(s => s, s=> ss);
}
else
{
foreach (var segment in captionsArr)
{
if (speakerRes.Start > segment.End)
break;
if (segment.Start <= speakerRes.End
&& segment.End >= speakerRes.Start)
var spList = new List<int>();
foreach (var speakerRes in speakerArr)
{
if (speakerRes.SpeakerIndex == techerId)
teacherSpeaking += speakerRes.Total;
else
studentSpeaking += speakerRes.Total;
spList.Add(speakerRes.SpeakerIndex);
if (speakerRes.Start > segment.End)
break;
if (segment.Start <= speakerRes.End
&& segment.End >= speakerRes.Start)
{
if (speakerRes.SpeakerIndex == techerId)
teacherSpeaking += speakerRes.Total;
else
studentSpeaking += speakerRes.Total;
spList.Add(speakerRes.SpeakerIndex);
}
}
var sp = spList.Distinct().ToList();
if (sp.Count > 0)
results.Add(segment, sp);
}
var sp = spList.Distinct().ToList();
if(sp.Count>0)
results.Add(segment, sp);
}
//拼接 提示词字幕源
var stringBuilder = new StringBuilder();
@ -352,6 +378,10 @@ namespace VideoAnalysisCore.Common
/// 数据库配置
/// </summary>
public DBConfig DB { get; set; } = new DBConfig();
/// <summary>
/// 知识点数据库
/// </summary>
public DBConfig KnowsDB { get; set; } = new DBConfig();
}
}

View File

@ -213,7 +213,7 @@ namespace VideoAnalysisCore.Common
if (scope is null || scope.ServiceProvider.GetService<IBserGPT>() is null)
throw new Exception("IBserGPT 未注入");
else
return scope.ServiceProvider.GetService<IBserGPT>()?.CallGPT(task) ?? Task.CompletedTask;
return scope.ServiceProvider.GetService<IBserGPT>()?.GetKnow(task) ?? Task.CompletedTask;
});
});
SubscribeList.Add(RedisChannelEnum.EndTask,
@ -277,6 +277,7 @@ namespace VideoAnalysisCore.Common
.Where(it => it.Id == taskID)
.ExecuteCommandAsync() == 1;
}
/// <summary>
/// 触发
/// </summary>

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoAnalysisCore.Interface;
using VideoAnalysisCore.Model;
namespace VideoAnalysisCore.Common
@ -13,7 +14,10 @@ namespace VideoAnalysisCore.Common
{
public Repository()
{
base.Context = DbScoped.SugarScope;
if(typeof(T).GetInterfaces().Contains(typeof(IKnowsDB)))
base.Context = DbScoped.SugarScope.GetConnection(1001);
else
base.Context = DbScoped.SugarScope;
}
}

View File

@ -27,6 +27,13 @@ namespace VideoAnalysisCore.Common
ConnectionString = AppCommon.Config.DB.ConnectionString,
DbType =AppCommon.Config.DB.SqlType,
IsAutoCloseConnection = true//自动释放
},
new IocConfig()
{
ConfigId =1001,
ConnectionString = AppCommon.Config.KnowsDB.ConnectionString,
DbType =AppCommon.Config.KnowsDB.SqlType,
IsAutoCloseConnection = true//自动释放
},
};
services.AddSingleton(typeof(Repository<>));

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VideoAnalysisCore.Interface
{
/// <summary>
/// 表属于IDB
/// </summary>
interface IDB
{
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VideoAnalysisCore.Interface
{
/// <summary>
/// 表属于KnowsDB
/// </summary>
interface IKnowsDB
{
}
}

View File

@ -6,6 +6,7 @@ using System.Net;
using UserCenter.Model.Enum;
using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Interface;
using Whisper.net;
namespace VideoAnalysisCore.Model
@ -14,7 +15,7 @@ namespace VideoAnalysisCore.Model
/// 课堂评分标准
/// </summary>
[SugarTable("coursegradingcriteria")]
public class CourseGradingCriteria
public class CourseGradingCriteria: IDB
{
/// <summary>
/// Id

View File

@ -57,6 +57,11 @@ namespace VideoAnalysisCore.Model.Dto
[DisplayName("媒体路径")]
public string MediaUrl { get; set; } = string.Empty;
/// <summary>
/// 媒体名称
/// </summary>
[DisplayName("媒体名称")]
public string MediaName { get; set; } = string.Empty;
/// <summary>
/// 自定义ID
/// </summary>
[DisplayName("自定义ID")]

View File

@ -0,0 +1,45 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoAnalysisCore.Interface;
namespace VideoAnalysisCore.Model.Dto
{
[SugarTable("knowledgeinfo")]
public class KnowledgeInfo: IKnowsDB
{
[SugarColumn(IsPrimaryKey = true, ColumnDescription = "Id 主键", ColumnName = "id")]
public long Id { get; set; }
[SugarColumn(ColumnDescription = "自己数据库Id", ColumnName = "thisid")]
public long ThisId { get; set; }
[SugarColumn(InsertServerTime = true, ColumnDescription = "创建时间", ColumnName = "createtime")]
public DateTime CreateTime { get; set; }
[SugarColumn(UpdateServerTime = true, InsertServerTime = true, IsNullable = true, ColumnDescription = "更新时间", ColumnName = "updatetime")]
public DateTime UpdateTime { get; set; }
[SugarColumn(ColumnDescription = "是否删除 true=删除", DefaultValue = "false", ColumnName = "deletestate")]
public bool DeleteState { get; set; }
[SugarColumn(ColumnDescription = "排序", ColumnName = "ordinal", DefaultValue = "0")]
public int Ordinal { get; set; }
[SugarColumn(ColumnDataType = "nvarchar(200)", ColumnDescription = "知识点名称", ColumnName = "name", IsNullable = true)]
public string Name { get; set; }
[SugarColumn(ColumnDescription = "节点深度一级节点的深度为1二级节点的深度为2以此类推。", ColumnName = "depth", IsNullable = true)]
public long Depth { get; set; }
[SugarColumn(ColumnDescription = "适用于精简版", ColumnName = "for_lite", IsNullable = true)]
public bool For_Lite { get; set; }
[SugarColumn(ColumnDescription = "父节点ID", ColumnName = "parent_id", IsNullable = true)]
public long Parent_Id { get; set; }
[SugarColumn(ColumnDescription = "root节点的ID", ColumnName = "root_id", IsNullable = true)]
public long Root_Id { get; set; }
[SugarColumn(ColumnDataType = "nvarchar(100)", ColumnDescription = "节点类型可用值NODE、KNOWLEDGE_POINT、TESTING_POINT分别代表普通节点、知识点、考点。", ColumnName = "type", IsNullable = true)]
public string Type { get; set; }
[SugarColumn(ColumnDescription = "课程ID", ColumnName = "course_id", IsNullable = true)]
public long Course_Id { get; set; }
[SugarColumn(InsertServerTime = true, ColumnDescription = "学科网创建时间", ColumnName = "create_time", IsNullable = true)]
public DateTime Create_Time { get; set; }
}
}

View File

@ -6,6 +6,7 @@ using UserCenter.Model.Enum;
using VideoAnalysisCore.AICore.GPT.Dto;
using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Interface;
using Whisper.net;
namespace VideoAnalysisCore.Model
@ -14,7 +15,7 @@ namespace VideoAnalysisCore.Model
/// 视频任务模型
/// </summary>
[SugarTable("videotask")]
public class VideoTask
public class VideoTask: IDB
{
/// <summary>
/// 任务id
@ -27,6 +28,10 @@ namespace VideoAnalysisCore.Model
/// </summary>
public string MediaUrl { get; set; } = string.Empty;
/// <summary>
/// 媒体文件名称
/// </summary>
public string MediaName { get; set; } = string.Empty;
/// <summary>
/// 下载后本地媒体目录
/// </summary>
public string LocalMediaPath { get; set; } = string.Empty;