From d8224e68d202ce140da54f52ba275701ad41f6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Wed, 30 Oct 2024 17:38:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 63 +++ .gitignore | 367 ++++++++++++++++++ Learn.VideoAnalysis.sln | 31 ++ VideoAnalysis/Components/App.razor | 24 ++ VideoAnalysis/Components/Error.razor | 36 ++ .../Components/Layouts/BasicLayout.razor | 32 ++ .../Components/Layouts/BasicLayout.razor.cs | 44 +++ .../Components/Pages/EvaluationProject.razor | 75 ++++ .../Pages/EvaluationProject.razor.cs | 70 ++++ .../Pages/EvaluationProject.razor.css | 3 + VideoAnalysis/Components/Resources/I18n.cs | 6 + VideoAnalysis/Components/Resources/I18n.resx | 129 ++++++ .../Components/Resources/I18n.zh-CN.resx | 129 ++++++ VideoAnalysis/Components/Routes.razor | 10 + VideoAnalysis/Components/_Imports.razor | 16 + VideoAnalysis/Controllers/ApiController.cs | 87 +++++ VideoAnalysis/Controllers/Dto/ApiDto.cs | 34 ++ VideoAnalysis/GlobalUsings.cs | 2 + VideoAnalysis/Learn.VideoAnalysis.csproj | 34 ++ VideoAnalysis/Learn.VideoAnalysis.http | 6 + VideoAnalysis/Program.cs | 109 ++++++ VideoAnalysis/Properties/launchSettings.json | 31 ++ VideoAnalysis/appsettings.Development.json | 8 + VideoAnalysis/appsettings.json | 31 ++ VideoAnalysis/wwwroot/app.css | 54 +++ VideoAnalysisCore/AICore/ChatGPT/BserGPT.cs | 19 + .../AICore/ChatGPT/KIMI/KIMI_GPT.cs | 58 +++ .../AICore/ChatGPT/KIMI/MoonshotClient.cs | 244 ++++++++++++ .../AICore/ChatGPT/KIMI/MoonshotModel.cs | 277 +++++++++++++ .../AICore/FFMPGE/FFMPGEHandle.cs | 78 ++++ .../AICore/SherpaOnnx/Speaker.cs | 100 +++++ .../AICore/SherpaOnnx/WaveHeader.cs | 167 ++++++++ .../AICore/Whisper/WhisperDto.cs | 35 ++ .../AICore/Whisper/WhisperHandle.cs | 122 ++++++ VideoAnalysisCore/Common/AppCommon.cs | 281 ++++++++++++++ VideoAnalysisCore/Common/DownloadFile.cs | 94 +++++ VideoAnalysisCore/Common/RedisExpand.cs | 169 ++++++++ VideoAnalysisCore/Common/Repository.cs | 19 + VideoAnalysisCore/Common/SqlSugarExpand.cs | 154 ++++++++ VideoAnalysisCore/Enum/RedisChannelEnum.cs | 33 ++ VideoAnalysisCore/Enum/TimeBaseEnum.cs | 29 ++ .../Model/CourseGradingCriteria.cs | 53 +++ .../Model/Dto/SpeakerCaptionsDto.cs | 49 +++ VideoAnalysisCore/Model/VideoTask.cs | 83 ++++ VideoAnalysisCore/VideoAnalysisCore.csproj | 59 +++ 45 files changed, 3554 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Learn.VideoAnalysis.sln create mode 100644 VideoAnalysis/Components/App.razor create mode 100644 VideoAnalysis/Components/Error.razor create mode 100644 VideoAnalysis/Components/Layouts/BasicLayout.razor create mode 100644 VideoAnalysis/Components/Layouts/BasicLayout.razor.cs create mode 100644 VideoAnalysis/Components/Pages/EvaluationProject.razor create mode 100644 VideoAnalysis/Components/Pages/EvaluationProject.razor.cs create mode 100644 VideoAnalysis/Components/Pages/EvaluationProject.razor.css create mode 100644 VideoAnalysis/Components/Resources/I18n.cs create mode 100644 VideoAnalysis/Components/Resources/I18n.resx create mode 100644 VideoAnalysis/Components/Resources/I18n.zh-CN.resx create mode 100644 VideoAnalysis/Components/Routes.razor create mode 100644 VideoAnalysis/Components/_Imports.razor create mode 100644 VideoAnalysis/Controllers/ApiController.cs create mode 100644 VideoAnalysis/Controllers/Dto/ApiDto.cs create mode 100644 VideoAnalysis/GlobalUsings.cs create mode 100644 VideoAnalysis/Learn.VideoAnalysis.csproj create mode 100644 VideoAnalysis/Learn.VideoAnalysis.http create mode 100644 VideoAnalysis/Program.cs create mode 100644 VideoAnalysis/Properties/launchSettings.json create mode 100644 VideoAnalysis/appsettings.Development.json create mode 100644 VideoAnalysis/appsettings.json create mode 100644 VideoAnalysis/wwwroot/app.css create mode 100644 VideoAnalysisCore/AICore/ChatGPT/BserGPT.cs create mode 100644 VideoAnalysisCore/AICore/ChatGPT/KIMI/KIMI_GPT.cs create mode 100644 VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs create mode 100644 VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotModel.cs create mode 100644 VideoAnalysisCore/AICore/FFMPGE/FFMPGEHandle.cs create mode 100644 VideoAnalysisCore/AICore/SherpaOnnx/Speaker.cs create mode 100644 VideoAnalysisCore/AICore/SherpaOnnx/WaveHeader.cs create mode 100644 VideoAnalysisCore/AICore/Whisper/WhisperDto.cs create mode 100644 VideoAnalysisCore/AICore/Whisper/WhisperHandle.cs create mode 100644 VideoAnalysisCore/Common/AppCommon.cs create mode 100644 VideoAnalysisCore/Common/DownloadFile.cs create mode 100644 VideoAnalysisCore/Common/RedisExpand.cs create mode 100644 VideoAnalysisCore/Common/Repository.cs create mode 100644 VideoAnalysisCore/Common/SqlSugarExpand.cs create mode 100644 VideoAnalysisCore/Enum/RedisChannelEnum.cs create mode 100644 VideoAnalysisCore/Enum/TimeBaseEnum.cs create mode 100644 VideoAnalysisCore/Model/CourseGradingCriteria.cs create mode 100644 VideoAnalysisCore/Model/Dto/SpeakerCaptionsDto.cs create mode 100644 VideoAnalysisCore/Model/VideoTask.cs create mode 100644 VideoAnalysisCore/VideoAnalysisCore.csproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c2149d --- /dev/null +++ b/.gitignore @@ -0,0 +1,367 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + + +VideoAnalysis/AICore/_Static/ +VideoAnalysisCore/AICore/_Static/ \ No newline at end of file diff --git a/Learn.VideoAnalysis.sln b/Learn.VideoAnalysis.sln new file mode 100644 index 0000000..713ed78 --- /dev/null +++ b/Learn.VideoAnalysis.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Learn.VideoAnalysis", "VideoAnalysis\Learn.VideoAnalysis.csproj", "{0CE488B7-F766-4083-86A6-6848A300E44E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoAnalysisCore", "VideoAnalysisCore\VideoAnalysisCore.csproj", "{69F4243A-B22E-431B-8F0B-ECD8729B8665}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0CE488B7-F766-4083-86A6-6848A300E44E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CE488B7-F766-4083-86A6-6848A300E44E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CE488B7-F766-4083-86A6-6848A300E44E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CE488B7-F766-4083-86A6-6848A300E44E}.Release|Any CPU.Build.0 = Release|Any CPU + {69F4243A-B22E-431B-8F0B-ECD8729B8665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69F4243A-B22E-431B-8F0B-ECD8729B8665}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69F4243A-B22E-431B-8F0B-ECD8729B8665}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69F4243A-B22E-431B-8F0B-ECD8729B8665}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {37C1B9DE-28AE-46BC-9975-D1201D7F962A} + EndGlobalSection +EndGlobal diff --git a/VideoAnalysis/Components/App.razor b/VideoAnalysis/Components/App.razor new file mode 100644 index 0000000..d07604b --- /dev/null +++ b/VideoAnalysis/Components/App.razor @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/VideoAnalysis/Components/Error.razor b/VideoAnalysis/Components/Error.razor new file mode 100644 index 0000000..576cc2d --- /dev/null +++ b/VideoAnalysis/Components/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/VideoAnalysis/Components/Layouts/BasicLayout.razor b/VideoAnalysis/Components/Layouts/BasicLayout.razor new file mode 100644 index 0000000..2c28310 --- /dev/null +++ b/VideoAnalysis/Components/Layouts/BasicLayout.razor @@ -0,0 +1,32 @@ +@namespace VideoAnalysisRazor.Layouts +@inherits LayoutComponentBase + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VideoAnalysis/Components/Layouts/BasicLayout.razor.cs b/VideoAnalysis/Components/Layouts/BasicLayout.razor.cs new file mode 100644 index 0000000..6387c3c --- /dev/null +++ b/VideoAnalysis/Components/Layouts/BasicLayout.razor.cs @@ -0,0 +1,44 @@ +using AntDesign.Extensions.Localization; +using AntDesign.ProLayout; +using Microsoft.AspNetCore.Components; +using System.Globalization; +using System.Net.Http.Json; + +namespace VideoAnalysisRazor.Layouts +{ + public partial class BasicLayout : LayoutComponentBase, IDisposable + { + private MenuDataItem[] _menuData; + + [Inject] private ReuseTabsService TabService { get; set; } + + bool collapsed; + void Toggle() + { + collapsed = !collapsed; + } + protected override async Task OnInitializedAsync() + { + _menuData = new[] { + new MenuDataItem + { + Path = "/", + Name = "首页", + Key = "hone", + Icon = "home", + } + }; + } + + void Reload() + { + TabService.ReloadPage(); + } + + public void Dispose() + { + + } + + } +} diff --git a/VideoAnalysis/Components/Pages/EvaluationProject.razor b/VideoAnalysis/Components/Pages/EvaluationProject.razor new file mode 100644 index 0000000..09d1f3b --- /dev/null +++ b/VideoAnalysis/Components/Pages/EvaluationProject.razor @@ -0,0 +1,75 @@ +@page "/" +@using AntDesign +@using AntDesign.TableModels +@using System.ComponentModel.DataAnnotations +@using SqlSugar +@using VideoAnalysisCore.Model + + + + + + @* *@ + + + + + + 修改 + + + + +
+ +@inject ModalService ModalService +@code +{ + /// + /// 新增或者修改 + /// + /// + void StartEdit(CourseGradingCriteria row) + { + var data = row == null ? new() : row; + IForm? form = default; + ModalRef modalRef = default; + modalRef = ModalService.CreateModal(new() + { + Title = data.Id > 0 ? "修改" : "新增", + Content = + @
+ + + , + OkText = "确定", + CancelText = "取消", + OnOk = async (e) => + { + if (!form.Validate()) + return; + // save db and refresh + modalRef.SetConfirmLoading(true); + + if (data.Id > 0) + await criteria.UpdateAsync(data); + else + data.Id = await criteria.InsertReturnBigIdentityAsync(data); + //弹窗按钮 show + modalRef.SetConfirmLoading(false); + + await modalRef.CloseAsync(); + _table.ReloadData(); + StateHasChanged(); + }, + OnCancel = async (e) => + { + if (form.IsModified && (!await Comfirm("表格已经更新,您确定要退出吗?"))) + return; + await modalRef.CloseAsync(); + } + }); + } +} \ No newline at end of file diff --git a/VideoAnalysis/Components/Pages/EvaluationProject.razor.cs b/VideoAnalysis/Components/Pages/EvaluationProject.razor.cs new file mode 100644 index 0000000..0290b01 --- /dev/null +++ b/VideoAnalysis/Components/Pages/EvaluationProject.razor.cs @@ -0,0 +1,70 @@ +using AntDesign.TableModels; +using Microsoft.AspNetCore.Components; +using SqlSugar; +using System.Linq.Expressions; +using VideoAnalysisCore.Common; +using VideoAnalysisCore.Model; + +namespace Learn.VideoAnalysis.Components.Pages +{ + public partial class EvaluationProject : ComponentBase + { + + [Inject] private ConfirmService ComfirmService { get; set; } = default!; + [Inject] private Repository criteria { get; set; } = default!; + + + IEnumerable _selectedRows = []; + ITable _table; + + List _dataSource = null; + RefAsync _total = 0; + + bool tableLoading = false; + + /// + /// 分页 查询 筛选 时 + /// + /// + async void OnChange(QueryModel query) + { + tableLoading = true; + Expression> where = null; + if (query.FilterModel != null && ((query.FilterModel?.Count() ?? 0) > 0)) + { + where = query.GetFilterExpression(); + } + _dataSource = await criteria.AsQueryable() + .WhereIF(where != null, where) + .ToPageListAsync(query.PageIndex - 1, query.PageSize, _total); + tableLoading = false; + StateHasChanged(); + + } + /// + /// 删除行 + /// + /// + /// + async Task Delete(CourseGradingCriteria row) + { + if (!await Comfirm($"确定要删除这条数据吗? [{row.NamePrompt}]?")) + return; + await criteria.DeleteByIdAsync(row.Id); + _table.ReloadData(); + } + /// + /// 初始化 + /// + protected override void OnInitialized() + { + } + + private async Task Comfirm(string message) + { + return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes; + } + + } + +} diff --git a/VideoAnalysis/Components/Pages/EvaluationProject.razor.css b/VideoAnalysis/Components/Pages/EvaluationProject.razor.css new file mode 100644 index 0000000..aca1ed1 --- /dev/null +++ b/VideoAnalysis/Components/Pages/EvaluationProject.razor.css @@ -0,0 +1,3 @@ +input[aria-hidden="true"] { + display: none !important; +} diff --git a/VideoAnalysis/Components/Resources/I18n.cs b/VideoAnalysis/Components/Resources/I18n.cs new file mode 100644 index 0000000..b0fb331 --- /dev/null +++ b/VideoAnalysis/Components/Resources/I18n.cs @@ -0,0 +1,6 @@ +namespace VideoAnalysisRazor.Resources; + + +internal class I18n +{ +} \ No newline at end of file diff --git a/VideoAnalysis/Components/Resources/I18n.resx b/VideoAnalysis/Components/Resources/I18n.resx new file mode 100644 index 0000000..5a932a1 --- /dev/null +++ b/VideoAnalysis/Components/Resources/I18n.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Account Center + + + Logout + + + Settings + + \ No newline at end of file diff --git a/VideoAnalysis/Components/Resources/I18n.zh-CN.resx b/VideoAnalysis/Components/Resources/I18n.zh-CN.resx new file mode 100644 index 0000000..d938141 --- /dev/null +++ b/VideoAnalysis/Components/Resources/I18n.zh-CN.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 个人中心 + + + 退出登录 + + + 个人设置 + + \ No newline at end of file diff --git a/VideoAnalysis/Components/Routes.razor b/VideoAnalysis/Components/Routes.razor new file mode 100644 index 0000000..919a0de --- /dev/null +++ b/VideoAnalysis/Components/Routes.razor @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/VideoAnalysis/Components/_Imports.razor b/VideoAnalysis/Components/_Imports.razor new file mode 100644 index 0000000..83cab43 --- /dev/null +++ b/VideoAnalysis/Components/_Imports.razor @@ -0,0 +1,16 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using AntDesign +@using AntDesign.Charts +@using AntDesign.ProLayout +@using AntDesign.Extensions.Localization + +@using Learn.VideoAnalysis +@using VideoAnalysisRazor +@using Learn.VideoAnalysis.Components diff --git a/VideoAnalysis/Controllers/ApiController.cs b/VideoAnalysis/Controllers/ApiController.cs new file mode 100644 index 0000000..1e30681 --- /dev/null +++ b/VideoAnalysis/Controllers/ApiController.cs @@ -0,0 +1,87 @@ + +using VideoAnalysisCore.Common; +using Learn.VideoAnalysis.Controllers.Dto; +using Microsoft.AspNetCore.Mvc; +using System.Reflection; +using VideoAnalysisCore.Enum; +using VideoAnalysisCore.Model; +using VideoAnalysisCore.AICore.FFMPGE; + +namespace Learn.VideoAnalysis.Controllers +{ + [ApiController] + [Route("[controller]/[action]")] + public class ApiController : ControllerBase + { + private readonly ILogger _logger; + public ApiController(ILogger logger) + { + _logger = logger; + } + /// + /// Ƶתwav_16k + /// + /// ļ· + /// + [HttpGet(Name = "Audio2WAV16K")] + public async Task Audio2WAV16K(string filePath) + { + await FFMPGEHandle.Audio2WAV16KAsync(filePath); + return Ok(); + } + + private string GetClientIpAddress() + { + // X-Forwarded-For ͷ + if (HttpContext.Request.Headers.ContainsKey("X-Forwarded-For") + && !string.IsNullOrEmpty(HttpContext.Request.Headers["X-Forwarded-For"])) + return HttpContext.Request.Headers["X-Forwarded-For"].ToString(); + if (HttpContext.Connection.RemoteIpAddress != null) + return HttpContext.Connection.RemoteIpAddress.ToString(); + throw new Exception("δܻȡͻipַ"); + } + + + /// + /// + /// + /// + /// + /// + [HttpPost(Name = "TestInsertChannel")] + public IActionResult TestInsertChannel(int @enum=1, string msg= "test_0001") + { + RedisExpand.InsertChannel(@enum.ToEnum().Value + , msg); + return Ok(); + } + /// + /// Ƶ + /// + /// + /// + [HttpPost(Name = "VideoAnalysis")] + public IActionResult VideoAnalysis(VideoAnalysisReq req) + { + if (!ModelState.IsValid) return BadRequest(ModelState); + + // ԶӳԵϣ + var task = new VideoTask() + { + ComeFrom = GetClientIpAddress(), + MediaUrl = req.MediaUrl, + ApiToken = req.ApiToken, + Tag = req.Tag, + CallBackUrl = req.CallBackUrl, + }; + var hashEntries = task.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .ToDictionary(s => s.Name, s => s.GetValue(task)); + RedisExpand.Redis.HMSet(RedisExpandKey.Task(task.Id), hashEntries); + + RedisExpand.InsertChannel(RedisChannelEnum.DownloadFile + , task.Id); + return Ok(); + } + } +} diff --git a/VideoAnalysis/Controllers/Dto/ApiDto.cs b/VideoAnalysis/Controllers/Dto/ApiDto.cs new file mode 100644 index 0000000..6938d22 --- /dev/null +++ b/VideoAnalysis/Controllers/Dto/ApiDto.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; + +namespace Learn.VideoAnalysis.Controllers.Dto +{ + /// + /// 视频处理 请求 + /// + public class VideoAnalysisReq + { + /// + /// 媒体路径 + /// + [Required(ErrorMessage = "资源URL是必填项")] + [Url(ErrorMessage = "请输入有效的 URL")] + public string MediaUrl { get; set; } = string.Empty; + /// + /// ApiKey + /// + [Required(ErrorMessage = "接口Token是必填项")] + public string ApiToken { get; set; } = string.Empty; + /// + /// 自定义值 任务完成后附带通知 + /// + public string Tag { get; set; } = string.Empty; + /// + ///回调Api地址 + /// + [Required(ErrorMessage = "回调Api地址是必填项")] + [Url(ErrorMessage = "请输入有效的 URL")] + public string CallBackUrl { get; set; } = string.Empty; + + + } +} diff --git a/VideoAnalysis/GlobalUsings.cs b/VideoAnalysis/GlobalUsings.cs new file mode 100644 index 0000000..a967599 --- /dev/null +++ b/VideoAnalysis/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using VideoAnalysisRazor.Resources; +global using AntDesign; \ No newline at end of file diff --git a/VideoAnalysis/Learn.VideoAnalysis.csproj b/VideoAnalysis/Learn.VideoAnalysis.csproj new file mode 100644 index 0000000..11594ac --- /dev/null +++ b/VideoAnalysis/Learn.VideoAnalysis.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + false + True + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/VideoAnalysis/Learn.VideoAnalysis.http b/VideoAnalysis/Learn.VideoAnalysis.http new file mode 100644 index 0000000..c1e6a9e --- /dev/null +++ b/VideoAnalysis/Learn.VideoAnalysis.http @@ -0,0 +1,6 @@ +@Learn.VideoAnalysis_HostAddress = http://localhost:5238 + +GET {{Learn.VideoAnalysis_HostAddress}}/Audio2Text?modelType=LargeV3 +Accept: application/json + +### diff --git a/VideoAnalysis/Program.cs b/VideoAnalysis/Program.cs new file mode 100644 index 0000000..19d389c --- /dev/null +++ b/VideoAnalysis/Program.cs @@ -0,0 +1,109 @@ +using VideoAnalysisCore.Common; +using VideoAnalysisRazor; +using Learn.VideoAnalysis.Components; +using Microsoft.OpenApi.Models; +using AntDesign.ProLayout; +using VideoAnalysisCore.AICore.ChatGPT; +using VideoAnalysisCore.AICore.ChatGPT.KIMI; +using VideoAnalysisCore.AICore.SherpaOnnx; +using SqlSugar; + +namespace Learn.VideoAnalysis +{ + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + //.AddInteractiveWebAssemblyComponents(); + + builder.Services.AddHttpContextAccessor(); + + + builder.Services.AddControllers(); + + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "Learn.VideoAnalysis", + Version = "v1", + Description = "ѧƵƽ̨v1" + }); + var file = Path.Combine(AppContext.BaseDirectory, "Learn.VideoAnalysis.xml"); // xmlĵ· + c.IncludeXmlComments(file, true); // true : ʾע + c.OrderActionsBy(o => o.RelativePath); // actionƽжͿԿЧˡ + }); + + // appsetting + builder.Configuration.GetSection("AppConfig").Bind(AppCommon.Config); + + //ʼ + Speaker.Init(); + RedisExpand.Init(); + + + builder.Services.AddScoped(sp => + { + var httpContext = sp.GetRequiredService().HttpContext; + if (httpContext != null) + { + return new HttpClient + { + BaseAddress = new Uri(httpContext.Request.Scheme + "://" + httpContext.Request.Host) + }; + } + return new HttpClient(); + }); + + //VideoAnalysisRazor.Program.AddClientServices(builder.Services); + + builder.Services.AddAntDesign(); + builder.Services.InitSqlSugar(); + + + builder.Services.Configure(builder.Configuration.GetSection("ProSettings")); + + builder.Services.AddHttpClient(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + + + + var app = builder.Build(); + + AppCommon.Services = app.Services; + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + app.UseExceptionHandler("/Error"); + } + + app.UseAuthorization(); + + app.UseStaticFiles(); + app.UseAntiforgery(); + + app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + //.AddInteractiveWebAssemblyRenderMode() + //.AddAdditionalAssemblies(typeof(VideoAnalysisRazor._Imports).Assembly); + + app.MapControllers(); + + SqlSugarExpand.InitDB(); + + app.Run(); + + } + } +} diff --git a/VideoAnalysis/Properties/launchSettings.json b/VideoAnalysis/Properties/launchSettings.json new file mode 100644 index 0000000..12a2a37 --- /dev/null +++ b/VideoAnalysis/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:9624", + "sslPort": 0 + } + }, + "profiles": { + "http:5238": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "", + "applicationUrl": "http://localhost:5238", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/VideoAnalysis/appsettings.Development.json b/VideoAnalysis/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/VideoAnalysis/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/VideoAnalysis/appsettings.json b/VideoAnalysis/appsettings.json new file mode 100644 index 0000000..1e3df8d --- /dev/null +++ b/VideoAnalysis/appsettings.json @@ -0,0 +1,31 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "AppConfig": { + "Redis": { + "ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10" + }, + "Whisper": { + "ModelName": "ggml-small.bin" + }, + "FFmpeg": { + " TimeSlice": 600 + }, + "ChatGpt": { + "KIMI": { + "Host": "https://api.moonshot.cn", + "ApiKey": "sk-CNYJdRHgJsgtgw1Q8GhQ5ayXuFPVLSk5bduOF4l2FMvI5lUo" + } + }, + "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, + } + } +} diff --git a/VideoAnalysis/wwwroot/app.css b/VideoAnalysis/wwwroot/app.css new file mode 100644 index 0000000..e05030d --- /dev/null +++ b/VideoAnalysis/wwwroot/app.css @@ -0,0 +1,54 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.ant-modal div[aria-hidden="true"] { + display: none !important; +} +a, .btn-link { + color: #006bb7; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} diff --git a/VideoAnalysisCore/AICore/ChatGPT/BserGPT.cs b/VideoAnalysisCore/AICore/ChatGPT/BserGPT.cs new file mode 100644 index 0000000..7fe6c27 --- /dev/null +++ b/VideoAnalysisCore/AICore/ChatGPT/BserGPT.cs @@ -0,0 +1,19 @@ +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Common; +using Whisper.net; + +namespace VideoAnalysisCore.AICore.ChatGPT +{ + /// + /// GPT 接口 + /// + public interface IBserGPT + { + /// + /// 访问GPT + /// + /// 任务id + /// + public Task CallGPT(string task); + } +} diff --git a/VideoAnalysisCore/AICore/ChatGPT/KIMI/KIMI_GPT.cs b/VideoAnalysisCore/AICore/ChatGPT/KIMI/KIMI_GPT.cs new file mode 100644 index 0000000..6ec29a2 --- /dev/null +++ b/VideoAnalysisCore/AICore/ChatGPT/KIMI/KIMI_GPT.cs @@ -0,0 +1,58 @@ +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Common; +using System.Threading.Tasks; +using Whisper.net; +using VideoAnalysisCore.AICore.ChatGPT; +using System.Text.Json; + +namespace VideoAnalysisCore.AICore.ChatGPT.KIMI +{ + /// + /// kimi 文本模型 + /// + public class KIMI_GPT : IBserGPT + { + private readonly MoonshotClient moonshotClient; + /// + /// 初始化 + /// + /// + /// + public KIMI_GPT(MoonshotClient moonshotClient) + { + MoonshotClient.Host = AppCommon.Config.ChatGpt.KIMI.Host; + MoonshotClient.ApiKey = AppCommon.Config.ChatGpt.KIMI.ApiKey; + + this.moonshotClient = moonshotClient; + } + /// + /// 访问GPT + /// + /// 任务id + /// + public async Task CallGPT(string task) + { + var captions = ExpandFunction.GetSpeakerCaptions(task); + + var modelsResp = await moonshotClient.ListModels(); + if (modelsResp is null || modelsResp.data.Count == 0) + throw new Exception("未获取KIMI模型类型"); + var chatRep = new ChatReq + { + max_tokens = 1048 * 16, + temperature = 0.3, + frequency_penalty = 0, + presence_penalty = 0, + model = modelsResp.data.First().id, + messages = new List(){ + new MessagesItem("以下是和一个AI助手的对话。这位助手乐于助人,富有创造力,聪明,而且非常友好。","system"), + new MessagesItem("美国的人类预期寿命是多少?") + } + }; + Console.WriteLine($"Chat 请求参数: {JsonSerializer.Serialize(chatRep)}"); + var chatResp = await moonshotClient.Chat(chatRep); + var chatRespBody = await chatResp.Content.ReadAsStringAsync(); + Console.WriteLine($"Chat 成功返回值: {chatRespBody}"); + } + } +} diff --git a/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs b/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs new file mode 100644 index 0000000..d08c622 --- /dev/null +++ b/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotClient.cs @@ -0,0 +1,244 @@ +using VideoAnalysisCore.Common; +using System.Net.Http.Headers; +using System.Text; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using System.Net.Http; + +namespace VideoAnalysisCore.AICore.ChatGPT.KIMI +{ + /// + /// https://platform.moonshot.cn/docs/api-reference + /// + public class MoonshotClient + { + private readonly ILogger _logger; + + private readonly IHttpClientFactory _httpClientFactory; + + public MoonshotClient(ILogger logger, IHttpClientFactory httpClientFactory) + { + _logger = logger; + _httpClientFactory = httpClientFactory; + } + + /// + /// list models + /// + /// + public async Task ListModels() + { + var response = await GetAsync("/v1/models"); + return await ParseResp(response); + } + + /// + /// Chat + /// + /// + /// Return HttpResponseMessage for SSE + public async Task Chat(string requestBody) + { + return await PostJsonStreamAsync("/v1/chat/completions", requestBody); + } + + /// + /// Chat + /// + /// + /// Return HttpResponseMessage for SSE + public async Task Chat(ChatReq chatReq) + { + var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq); + return await PostJsonStreamAsync("/v1/chat/completions", requestBody); + } + + /// + /// Get as timate token count + /// + public async Task GetAsTiMateTokenCount(string chatReqText) + { + var response = await PostJsonAsync("/v1/tokenizers/estimate-token-count", chatReqText); + var responseText = await response.Content.ReadAsStringAsync(); + if (response.IsSuccessStatusCode) + { + var responseObj = JToken.Parse(responseText); + return responseObj?["data"]?["total_tokens"]?.ToObject(); + } + var error = System.Text.Json.JsonSerializer.Deserialize(responseText); + _logger.LogError($"{error?.error?.type}: {error?.error?.message}"); + throw new Exception($"{error?.error.type}: {error?.error.message}"); + } + + + /// + /// Get as timate token count + /// + /// + /// + public async Task GetAsTiMateTokenCount(ChatReq chatReq) + { + var chatReqText =System.Text.Json.JsonSerializer.Serialize(chatReq); + return await GetAsTiMateTokenCount(chatReqText); + } + + + /// + /// List files + /// + public virtual async Task ListFiles() + { + var response = await GetAsync("/v1/files"); + return await ParseResp(response); + } + + + + + + /// + /// Upload file + /// + public virtual async Task UploadFile(string filePath) + { + if (!File.Exists(filePath)) + { + throw new FileNotFoundException($"{filePath} not found"); + } + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey); + var request = new HttpRequestMessage(HttpMethod.Post, $"{Host}/v1/files"); + var content = new MultipartFormDataContent + { + { new StreamContent(File.OpenRead(filePath)), "file", filePath } + }; + request.Content = content; + var response = await client.SendAsync(request); + return await ParseResp(response); + } + + + + /// + /// Upload file stream + /// + public virtual async Task UploadFileStream(Stream stream, string fileName) + { + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey); + var request = new HttpRequestMessage(HttpMethod.Post, $"{Host}/v1/files"); + var content = new MultipartFormDataContent + { + { new StreamContent(stream), "file", fileName } + }; + request.Content = content; + var response = await client.SendAsync(request); + return await ParseResp(response); + } + + + /// + /// Get file content + /// + + public virtual async Task GetFileContent(string fileId) + { + var response = await GetAsync($"/v1/files/{fileId}/content"); + return await ParseResp(response); + } + + + private async Task GetAsync(string path) + { + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey); + return await client.GetAsync(Host + path); + } + + private async Task PostJsonAsync(string path, string json) + { + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey); + return await client.PostAsync(Host + path, new StringContent(json, Encoding.UTF8, "application/json")); + } + + private async Task PostJsonStreamAsync(string path, string json) + { + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey); + var request = ToHttpRequest(path); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + } + + private HttpRequestMessage ToHttpRequest(string path) + { + var request = new HttpRequestMessage(); + var uriBuilder = new UriBuilder(Host + path); + request.RequestUri = uriBuilder.Uri; + request.Method = new HttpMethod("POST"); + request.Headers.Host = new Uri(Host).Host; + return request; + } + + + + /// + /// Parse response + /// + private async Task ParseResp(HttpResponseMessage response) + { + var responseText = await response.Content.ReadAsStringAsync(); + if (response.IsSuccessStatusCode) + { + return System.Text.Json.JsonSerializer.Deserialize(responseText) ?? default; + } + var error = System.Text.Json.JsonSerializer.Deserialize(responseText); + _logger.LogError($"{error?.error.type}: {error?.error.message}"); + throw new Exception($"{error?.error.type}: {error?.error.message}"); + } + + + + private static string _host = "https://api.moonshot.cn"; + + public static string Host + { + get + { + if (string.IsNullOrEmpty(_host) && !string.IsNullOrEmpty(AppCommon.Config.ChatGpt.KIMI.Host)) + { + _host = AppCommon.Config.ChatGpt.KIMI.Host ?? ""; + } + + return _host; + } + set + { + + _host = value; + } + } + + + private static string _apiKey = "sk_"; + + public static string ApiKey + { + get + { + if (string.IsNullOrEmpty(_apiKey) && !string.IsNullOrEmpty(AppCommon.Config.ChatGpt.KIMI.ApiKey)) + { + _apiKey = AppCommon.Config.ChatGpt.KIMI.ApiKey ?? ""; + } + + return _apiKey; + } + set + { + _apiKey = value; + } + } + + } +} diff --git a/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotModel.cs b/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotModel.cs new file mode 100644 index 0000000..d045c7d --- /dev/null +++ b/VideoAnalysisCore/AICore/ChatGPT/KIMI/MoonshotModel.cs @@ -0,0 +1,277 @@ +namespace VideoAnalysisCore.AICore.ChatGPT.KIMI +{ + + public class MessagesItem + { + public MessagesItem() + { + + } + public MessagesItem(string content, string role = "user") + { + this.content = content; + this.role = role; + } + /// + /// + /// + public string role { get; set; } + + /// + /// + /// + public string content { get; set; } + } + /// + /// chat请求参数 + /// + public class ChatReq + { + /// + /// 使用的模型 + /// 例如[ moonshot-v1-8k ] + /// + public string model { get; set; } = "moonshot-v1-8k"; + /// + /// 消息主体 + /// + public List messages { get; set; } + /// + /// 使用什么采样温度,介于 0 和 1 之间。较高的值(如 0.7)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定性 + /// 默认为 0,如果设置,值域须为 [0, 1] 我们推荐 0.3,以达到较合适的效果 + /// + public double temperature { get; set; } + + /// + /// 聊天完成时生成的最大 token 数。如果到生成了最大 token 数个结果仍然没有结束,finish reason 会是 "length", 否则会是 "stop" + /// 这个值建议按需给个合理的值,如果不给的话,我们会给一个不错的整数比如 1024。特别要注意的是,这个 max_tokens 是指您期待我们返回的 token 长度,而不是输入 + 输出的总长度。比如对一个 moonshot-v1-8k 模型,它的最大输入 + 输出总长度是 8192,当输入 messages 总长度为 4096 的时候,您最多只能设置为 4096,否则我们服务会返回不合法的输入参数( invalid_request_error ),并拒绝回答。如果您希望获得“输入的精确 token 数”,可以使用下面的“计算 Token” API 使用我们的计算器获得计数 + /// + public int? max_tokens { get; set; } + /// + /// 另一种采样方法,即模型考虑概率质量为 top_p 的标记的结果。因此,0.1 意味着只考虑概率质量最高的 10% 的标记。一般情况下,我们建议改变这一点或温度,但不建议 同时改变 + /// + public float? top_p { get; set; } = 1.0f; + /// + /// 为每条输入消息生成多少个结果 + /// 默认为 1,不得大于 5。特别的,当 temperature 非常小靠近 0 的时候,我们只能返回 1 个结果,如果这个时候 n 已经设置并且 > 1,我们的服务会返回不合法的输入参数(invalid_request_error) + /// + public int? n { get; set; } = 1; + /// + /// 存在惩罚,介于-2.0到2.0之间的数字。正值会根据新生成的词汇是否出现在文本中来进行惩罚,增加模型讨论新话题的可能性 + /// 默认为 0 + /// + public float? presence_penalty { get; set; } = 0; + /// + /// 频率惩罚,介于-2.0到2.0之间的数字。正值会根据新生成的词汇在文本中现有的频率来进行惩罚,减少模型一字不差重复同样话语的可能性 + /// 默认为 0 + /// + public float? frequency_penalty { get; set; } = 0; + /// + /// 停止词,当全匹配这个(组)词后会停止输出,这个(组)词本身不会输出。最多不能超过 5 个字符串,每个字符串不得超过 32 字节 + /// 默认 null + /// + public List? stop { get; set; } + /// + /// 是否流式返回 + /// false + /// + public bool stream { get; set; } = false; + + + } + + + public class PermissionItem + { + /// + /// + /// + public int created { get; set; } + /// + /// + /// + public string id { get; set; } + /// + /// + /// + public string @object { get; set; } + /// + /// + /// + public string allow_create_engine { get; set; } + /// + /// + /// + public string allow_sampling { get; set; } + /// + /// + /// + public string allow_logprobs { get; set; } + /// + /// + /// + public string allow_search_indices { get; set; } + /// + /// + /// + public string allow_view { get; set; } + /// + /// + /// + public string allow_fine_tuning { get; set; } + /// + /// + /// + public string organization { get; set; } + /// + /// + /// + public string @group { get; set; } + /// + /// + /// + public string is_blocking { get; set; } + } + + public class ModelInfo + { + /// + /// + /// + public int created { get; set; } + /// + /// + /// + public string id { get; set; } + /// + /// + /// + public string @object { get; set; } + /// + /// + /// + public string owned_by { get; set; } + /// + /// + /// + public List permission { get; set; } + /// + /// + /// + public string root { get; set; } + /// + /// + /// + public string parent { get; set; } + } + + public class ModelListResp + { + /// + /// + /// + public string @object { get; set; } + /// + /// + /// + public List data { get; set; } + } + + + public class FileListResp + { + /// + /// + /// + public string @object { get; set; } + /// + /// + /// + public List data { get; set; } + } + + public class FileContent + { + /// + /// + /// + public string content { get; set; } + /// + /// + /// + public string file_type { get; set; } + /// + /// + /// + public string filename { get; set; } + /// + /// + /// + public string title { get; set; } + /// + /// + /// + public string type { get; set; } + } + + + public class FileItem + { + /// + /// + /// + public string id { get; set; } + /// + /// + /// + public string @object { get; set; } + /// + /// + /// + public int bytes { get; set; } + /// + /// + /// + public int created_at { get; set; } + /// + /// + /// + public string filename { get; set; } + /// + /// + /// + public string purpose { get; set; } + /// + /// + /// + public string status { get; set; } + /// + /// + /// + public string status_details { get; set; } + } + + + + public class ErrorMsg + { + /// + /// + /// + public string message { get; set; } + /// + /// + /// + public string type { get; set; } + } + + public class ErrorResponse + { + /// + /// + /// + public ErrorMsg error { get; set; } + } + + +} diff --git a/VideoAnalysisCore/AICore/FFMPGE/FFMPGEHandle.cs b/VideoAnalysisCore/AICore/FFMPGE/FFMPGEHandle.cs new file mode 100644 index 0000000..37bf74f --- /dev/null +++ b/VideoAnalysisCore/AICore/FFMPGE/FFMPGEHandle.cs @@ -0,0 +1,78 @@ +using FFmpeg.NET.Events; +using FFmpeg.NET; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Common; +using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSType; +using System.Xml.Linq; + +namespace VideoAnalysisCore.AICore.FFMPGE +{ + /// + /// Ffmpeg处理程序 + /// + public class FFMPGEHandle + { + /// + /// + /// + public static string FFmpegPath = Path.Combine(AppCommon.AIModelFile, "ffmpeg.exe"); + /// + /// 音频转码为 wav_16k + /// + /// 任务id + /// + public static async Task Audio2WAV16KAsync(string task) + { + var filePath = RedisExpand.Redis.HGet(RedisExpandKey.Task(task), "LocalMediaPath"); + if (string.IsNullOrEmpty(filePath)) + throw new Exception($"任务id[{task}] 无效"); + + // 打开输入文件 + var inputFile = new InputFile(filePath); + var outputFile = new OutputFile(Path.Combine(task.LocalPath(), Path.GetFileNameWithoutExtension(filePath) + ".wav")); + var ffmpeg = new Engine(FFmpegPath); + + ffmpeg.Progress += OnProgress; + ffmpeg.Data += OnData; + ffmpeg.Complete += OnComplete; + ffmpeg.Error += (sender, e) => + { + throw new Exception($"[{e.Input.Name} => {e.Output.Name}]: 错误: {e.Exception.Message}"); + }; + + var conversionOptions = new ConversionOptions + { + ExtraArguments = "-ar 16000 -ac 1" + //+ (AppCommon.AppSetting.FFmpeg.TimeSlice == 0 + //?string.Empty + //: $"-f segment -reset_timestamps 1 -segment_time {AppCommon.AppSetting.FFmpeg.TimeSlice}") + }; + + var res = await ffmpeg.ConvertAsync(inputFile, outputFile, conversionOptions); + //加入下一队列 + RedisExpand.InsertChannel(Enum.RedisChannelEnum.ParsingCaptions, task); + + } + private static void OnProgress(object sender, ConversionProgressEventArgs e) + { + Console.WriteLine("[{0} => {1}]", e.Input.MetaData.FileInfo.Name, e.Output.Name); + Console.WriteLine("比特率: {0}", e.Bitrate); + Console.WriteLine("Fps: {0}", e.Fps); + Console.WriteLine("基本框架: {0}", e.Frame); + Console.WriteLine("处理持续时间: {0}", e.ProcessedDuration); + Console.WriteLine("Size: {0} kb", e.SizeKb); + Console.WriteLine("总持续时间: {0}\n", e.TotalDuration); + } + + private static void OnData(object sender, ConversionDataEventArgs e) + { + Console.WriteLine(e.Data); + } + + private static void OnComplete(object sender, ConversionCompleteEventArgs e) + { + Console.WriteLine("转换完成=>" + e.Output.Name); + } + } +} diff --git a/VideoAnalysisCore/AICore/SherpaOnnx/Speaker.cs b/VideoAnalysisCore/AICore/SherpaOnnx/Speaker.cs new file mode 100644 index 0000000..4be39f8 --- /dev/null +++ b/VideoAnalysisCore/AICore/SherpaOnnx/Speaker.cs @@ -0,0 +1,100 @@ + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using VideoAnalysisCore.Common; +using SherpaOnnx; + +namespace VideoAnalysisCore.AICore.SherpaOnnx +{ + + /// + /// 说话人模型 + /// pyannote + /// + public class Speaker + { + private static OfflineSpeakerDiarization? SD; + /// + /// 初始化 Speaker + /// + /// + /// + public static void Init(int speakerNumber = 0, double threshold = 0.6) + { + var config = new OfflineSpeakerDiarizationConfig(); + //Pyannote模型地址 + config.Segmentation.Pyannote.Model = Path.Combine(AppCommon.AIModelFile, "sherpa-onnx-pyannote-segmentation-3-0", "model.onnx"); + //验证模型 + config.Embedding.Model = Path.Combine(AppCommon.AIModelFile, "wespeaker", "wespeaker_zh_cnceleb_resnet34_LM.onnx"); + //说话人数量 + config.Clustering.NumClusters = speakerNumber; + //说话人判定阈值 + config.Clustering.Threshold = (float)threshold; + SD = new OfflineSpeakerDiarization(config); + } + /// + /// 运行 说话人日志 + /// + /// + public static async Task Run(string task) + { + var filePath = Path.Combine(task.LocalPath(), task + ".wav"); + if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) + throw new Exception("task 音频路径未找到"); + + if (SD is null) + throw new Exception("Speaker 未进行初始化"); + var waveReader = new WaveReader(filePath); + if (SD.SampleRate != waveReader.SampleRate) + throw new Exception($"预期采样率:{SD.SampleRate}. 传入: {waveReader.SampleRate}"); + + var segments = SD.ProcessWithCallback(waveReader.Samples, + (numProcessedChunks, numTotalChunks, arg) => + { + var progress = (double)numProcessedChunks / numTotalChunks * 100; + Console.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}=> {task} 说话人日志: {progress:F2}%"); + return 1; + }, nint.Zero); + var res = segments.Select(s => new OfflineSpeakerRes(s)); + await RedisExpand.Redis.HSetAsync(RedisExpandKey.Task(task), "Speaker", res); + //加入下一队列 + RedisExpand.InsertChannel(Enum.RedisChannelEnum.ChatModelAnalysis, task); + + } + } + /// + /// 讲话人日志结果 + /// + public class OfflineSpeakerRes + { + /// + /// 总持续时间 + /// + [JsonIgnore] + public decimal Total => End - Start; + /// + /// 开始时间 + /// + public decimal Start { get; set; } + /// + /// 结束时间 + /// + public decimal End { get; set; } + /// + /// 讲话人索引 + /// + public int SpeakerIndex { get; set; } + /// + /// 讲话人日志结果 + /// + /// + public OfflineSpeakerRes(OfflineSpeakerDiarizationSegment sds) + { + Start = (decimal)sds.Start; + End = (decimal)sds.End; + SpeakerIndex = sds.Speaker; + } + } +} + diff --git a/VideoAnalysisCore/AICore/SherpaOnnx/WaveHeader.cs b/VideoAnalysisCore/AICore/SherpaOnnx/WaveHeader.cs new file mode 100644 index 0000000..24084c0 --- /dev/null +++ b/VideoAnalysisCore/AICore/SherpaOnnx/WaveHeader.cs @@ -0,0 +1,167 @@ +// Copyright (c) 2023 Xiaomi Corporation (authors: Fangjun Kuang) +using System; +using System.IO; + +using System.Runtime.InteropServices; + +namespace VideoAnalysisCore.AICore.SherpaOnnx +{ + + [StructLayout(LayoutKind.Sequential)] + public struct WaveHeader + { + public int ChunkID; + public int ChunkSize; + public int Format; + public int SubChunk1ID; + public int SubChunk1Size; + public short AudioFormat; + public short NumChannels; + public int SampleRate; + public int ByteRate; + public short BlockAlign; + public short BitsPerSample; + public int SubChunk2ID; + public int SubChunk2Size; + + public bool Validate() + { + if (ChunkID != 0x46464952) + { + Console.WriteLine($"Invalid chunk ID: 0x{ChunkID:X}. Expect 0x46464952"); + return false; + } + + // E V A W + if (Format != 0x45564157) + { + Console.WriteLine($"Invalid format: 0x{Format:X}. Expect 0x45564157"); + return false; + } + + // t m f + if (SubChunk1ID != 0x20746d66) + { + Console.WriteLine($"Invalid SubChunk1ID: 0x{SubChunk1ID:X}. Expect 0x20746d66"); + return false; + } + + if (SubChunk1Size != 16) + { + Console.WriteLine($"Invalid SubChunk1Size: {SubChunk1Size}. Expect 16"); + return false; + } + + if (AudioFormat != 1) + { + Console.WriteLine($"Invalid AudioFormat: {AudioFormat}. Expect 1"); + return false; + } + + if (NumChannels != 1) + { + Console.WriteLine($"Invalid NumChannels: {NumChannels}. Expect 1"); + return false; + } + + if (ByteRate != SampleRate * NumChannels * BitsPerSample / 8) + { + Console.WriteLine($"Invalid byte rate: {ByteRate}."); + return false; + } + + if (BlockAlign != NumChannels * BitsPerSample / 8) + { + Console.WriteLine($"Invalid block align: {ByteRate}."); + return false; + } + + if (BitsPerSample != 16) + { // we support only 16 bits per sample + Console.WriteLine($"Invalid bits per sample: {BitsPerSample}. Expect 16"); + return false; + } + + return true; + } + } + + // It supports only 16-bit, single channel WAVE format. + // The sample rate can be any value. + public class WaveReader + { + public WaveReader(string fileName) + { + if (!File.Exists(fileName)) + { + throw new ApplicationException($"{fileName} does not exist!"); + } + + using (var stream = File.Open(fileName, FileMode.Open)) + { + using (var reader = new BinaryReader(stream)) + { + _header = ReadHeader(reader); + + if (!_header.Validate()) + { + throw new ApplicationException($"Invalid wave file ${fileName}"); + } + + SkipMetaData(reader); + + // now read samples + // _header.SubChunk2Size contains number of bytes in total. + // we assume each sample is of type int16 + byte[] buffer = reader.ReadBytes(_header.SubChunk2Size); + short[] samples_int16 = new short[_header.SubChunk2Size / 2]; + Buffer.BlockCopy(buffer, 0, samples_int16, 0, buffer.Length); + + _samples = new float[samples_int16.Length]; + + for (var i = 0; i < samples_int16.Length; ++i) + { + _samples[i] = samples_int16[i] / 32768.0F; + } + } + } + } + + private static WaveHeader ReadHeader(BinaryReader reader) + { + byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(WaveHeader))); + + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + WaveHeader header = (WaveHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(WaveHeader))!; + handle.Free(); + + return header; + } + + private void SkipMetaData(BinaryReader reader) + { + var bs = reader.BaseStream; + + int subChunk2ID = _header.SubChunk2ID; + int subChunk2Size = _header.SubChunk2Size; + + while (bs.Position != bs.Length && subChunk2ID != 0x61746164) + { + bs.Seek(subChunk2Size, SeekOrigin.Current); + subChunk2ID = reader.ReadInt32(); + subChunk2Size = reader.ReadInt32(); + } + _header.SubChunk2ID = subChunk2ID; + _header.SubChunk2Size = subChunk2Size; + } + + private WaveHeader _header; + + // Samples are normalized to the range [-1, 1] + private float[] _samples; + + public int SampleRate => _header.SampleRate; + public float[] Samples => _samples; + } + +} \ No newline at end of file diff --git a/VideoAnalysisCore/AICore/Whisper/WhisperDto.cs b/VideoAnalysisCore/AICore/Whisper/WhisperDto.cs new file mode 100644 index 0000000..0524c39 --- /dev/null +++ b/VideoAnalysisCore/AICore/Whisper/WhisperDto.cs @@ -0,0 +1,35 @@ +using Whisper.net; + +namespace VideoAnalysisCore.AICore.Whisper +{ + /// + /// 字幕识别 结果 + /// + public class WhisperResDto + { + /// + /// + /// + /// + public WhisperResDto(SegmentData sd) + { + Text = sd.Text; + Start = sd.Start; + End = sd.End; + } + /// + /// 文本 + /// + public string Text { get; } = string.Empty; + /// + /// 开始时间 + /// + + public TimeSpan Start { get; } + /// + /// 结束时间 + /// + + public TimeSpan End { get; } + } +} diff --git a/VideoAnalysisCore/AICore/Whisper/WhisperHandle.cs b/VideoAnalysisCore/AICore/Whisper/WhisperHandle.cs new file mode 100644 index 0000000..8ab2f15 --- /dev/null +++ b/VideoAnalysisCore/AICore/Whisper/WhisperHandle.cs @@ -0,0 +1,122 @@ +using VideoAnalysisCore.Common; +using System; +using System.Linq; +using Whisper.net; +using Whisper.net.Ggml; +using Whisper.net.Wave; + +namespace VideoAnalysisCore.AICore.Whisper +{ + public static class WhisperHandle + { + + /// + /// 获取语音字幕 + /// + /// + /// + public static async Task RunTask(string task) + { + var filePath = Path.Combine(task.LocalPath(), task + ".wav"); + if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) + throw new Exception("task 音频路径未找到"); + var opt = new WhisperOption(filePath) { ModelName = AppCommon.Config.Whisper.ModelName }; + var modPath = Path.Combine(AppCommon.AIModelFile, opt.ModelName); + // 从给定的模型名称路径创建一个 WhisperFactory 实例 + using var factory = WhisperFactory.FromPath(modPath); + var builder = factory.CreateBuilder() + //自定义提示词 + .WithPrompt("以下是普通话的句子") + //设置语言 + .WithLanguage(opt.Language); + + // 如果值为 "translate",则在构建器中启用翻译功能 + //if (opt.Command == "translate") + //{ + // builder.WithTranslate(); + //} + using var processor = builder.Build(); + // 打开一个文件流来读取由 opt.FileName 指定的音频文件 + using var fileStream = File.OpenRead(filePath); + var res = new List(200); + // 使用处理器异步处理音频文件。对于处理器返回的每个段(segment),它将段的开始时间、结束时间和文本打印到控制台。 + await foreach (var segment in processor.ProcessAsync(fileStream, CancellationToken.None)) + { + res.Add(new WhisperResDto(segment)); + } + RedisExpand.Redis.HMSet(RedisExpandKey.Task(task), "Captions", res); + RedisExpand.InsertChannel(Enum.RedisChannelEnum.ParsingSpeaker, task); + } + /// + /// 检测语言的方法 + /// + /// + static void LanguageIdentification(WhisperOption opt) + { + var modPath = Path.Combine(AppCommon.AIModelFile, opt.ModelName); + // 使用 File.ReadAllBytes 方法将模型文件读取到内存中。 + var bufferedModel = File.ReadAllBytes(modPath); + + // 多个任务可以使用同一个工厂来创建处理器 + using var factory = WhisperFactory.FromBuffer(bufferedModel); + + // 使用工厂创建一个新的构建器,并设置其语言 + var builder = factory.CreateBuilder() + .WithLanguage(opt.Language); + + using var processor = builder.Build(); + + // 打开一个文件流来读取由 opt.FileName 指定的音频文件 + using var fileStream = File.OpenRead(opt.FilePath); + + // 使用 WaveParser 类来解析音频文件 + var wave = new WaveParser(fileStream); + + // 使用 WaveParser 的 GetAvgSamples 方法获取音频文件的平均样本 + var samples = wave.GetAvgSamples(); + + // 使用处理器的 DetectLanguage 方法检测音频样本中的语言 + var language = processor.DetectLanguage(samples, speedUp: true); + + Console.WriteLine("Language is " + language); + } + } + /// + /// 音频处理选项 + /// + public class WhisperOption + { + /// + /// 传入目标文件路径 + /// + /// + public WhisperOption(string file) + { + FilePath = file; + } + /// + /// 指令类型 + /// + public string Command { get; set; } = "transcribe"; + /// + /// 音频文件,默认要存放bin目录下 + /// + public string FilePath { get; set; } + /// + /// 语言,默认自动选择 + /// + public string Language { get; set; } = "chinese"; + /// + /// 模型文件名称 + /// + public string ModelName { get; set; } = "ggml-base.bin"; + /// + /// 模型文件路径 + /// + public string ModPath => Path.Combine(AppCommon.AIModelFile, ModelName); + /// + /// 模型类型 + /// + //public GgmlType ModelType { get; set; } = GgmlType.Base; + } +} diff --git a/VideoAnalysisCore/Common/AppCommon.cs b/VideoAnalysisCore/Common/AppCommon.cs new file mode 100644 index 0000000..9855ad8 --- /dev/null +++ b/VideoAnalysisCore/Common/AppCommon.cs @@ -0,0 +1,281 @@ +using FreeRedis; +using Microsoft.Extensions.DependencyModel; +using SqlSugar; +using SqlSugar.IOC; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using System.Threading.Tasks; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Enum; +using VideoAnalysisCore.Model.Dto; +using Whisper.net; + +namespace VideoAnalysisCore.Common +{ + + /// + /// 程序 公共变量 + /// + public static class AppCommon + { + /// + /// 应用有效程序集 + /// + public static readonly IEnumerable Assemblies; + /// + /// 主库数据库表类型 + /// + public static readonly IEnumerable DbMatserType; + 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; + } + catch + { + } + //.Where(u => !u.IsDefined(typeof(SplitTableAttribute), false)) + //.Where(u => !typeof(Model.DataCenterYH.IDataCenterYHModel).IsAssignableFrom(u)) + //.Where(u => !u.IsSubclassOf(typeof(YQ_BaseEntity))); + } + + /// + /// 程序配置 + /// + public static AppConfig Config = new AppConfig(); + /// + /// ServiceProvider + /// + public static IServiceProvider? Services; + /// + /// 文件下载路径 + /// + public static string TaskCachedFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TaskCachedFile"); + + /// + /// 模型地址 + /// + public static string AIModelFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AICore", "_Static"); + + + } + + /// + /// 拓展函数 + /// + public static class ExpandFunction + { + + /// + /// 获取应用有效程序集 + /// + /// IEnumerable + public static List GetAssemblies() + { + // 获取当前解决方案的所有程序集 + var assembliesStr = DependencyContext.Default.RuntimeLibraries + .Where(u => !u.Name.StartsWith(nameof(Microsoft)) + && !u.Name.StartsWith(nameof(System)) + && !u.Name.StartsWith("netstandard") + && (u.Type == "project")); + + var assemblies = assembliesStr.Select(a => AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(a.Name))).ToList(); + var assemblies1 = Assembly.GetEntryAssembly().GetReferencedAssemblies().Where(x => x.Name.StartsWith("App.") || x.Name.StartsWith("UserCenter.")) + .Select(a => AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(a.Name))).ToList(); + foreach (var item in assemblies1) + { + if (!assemblies.Contains(item)) + assemblies.Add(item); + } + return assemblies; + } + /// + /// 获取Task处理后的 说话人字幕 + /// + public static TotalCaptionsDto GetSpeakerCaptions(string task) + { + var captionsArr = RedisExpand.Redis.HMGet(RedisExpandKey.Task(task), "Captions").FirstOrDefault(); + var speakerArr = RedisExpand.Redis.HMGet(RedisExpandKey.Task(task), "Speaker").FirstOrDefault(); + 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))) + .OrderByDescending(s=>s.Item2).First().Key; + var teacherSpeaking = 0m; + var studentSpeaking = 0m; + var results = new Dictionary>(); + foreach (var segment in captionsArr) + { + var spList = new List(); + foreach (var speakerRes in speakerArr) + { + if ((double)speakerRes.Start > segment.End.TotalSeconds) + break; + if (segment.Start.TotalSeconds <= (double)speakerRes.End + && segment.End.TotalSeconds >= (double)speakerRes.Start) + { + if (speakerRes.SpeakerIndex == techerId) + teacherSpeaking += speakerRes.Total; + else + studentSpeaking += speakerRes.Total; + spList.Add(speakerRes.SpeakerIndex); + break; + } + } + results.Add(segment, spList); + } + //拼接 提示词字幕源 + var stringBuilder = new StringBuilder(); + foreach (var item in results) + stringBuilder.Append($"{item.Value.First()}:{item.Key.Start.TotalSeconds}:{item.Key.End.TotalSeconds}:{item.Key.Text},"); + + //todo 分析上课时间段情况 分析 独立学习 小组合作 随堂练习等情况 + + return new TotalCaptionsDto + { + StudentSpeaking = studentSpeaking, + TeacherSpeaking = teacherSpeaking, + Captions = stringBuilder.ToString(), + TimeBase = results.Select(s=>new TimeBase() + { + Start = s.Key.Start.TotalSeconds, + End = s.Key.End.TotalSeconds, + Type = s.Value.Count == 1 && s.Value.First() == techerId + ? TimeBaseTypeEnum.教师讲授 + : TimeBaseTypeEnum.互动交流 + }) + }; + } + /// + /// 转化枚举 + /// + /// + /// + public static T? ToEnum(this object data) where T : struct, System.Enum + { + if (data is null || string.IsNullOrEmpty(data?.ToString())) + return null; + return System.Enum.Parse(data.ToString()); + } + /// + /// 转化本地缓存目录 + /// + /// 任务id + /// + public static string LocalPath(this string taskId) + { + return Path.Combine(AppCommon.TaskCachedFile, taskId); + } + + } + + /// + /// ffmpeg配置 + /// + public class KIMIConfig + { + /// + /// kimi请求 公开的服务地址 + /// + public string Host { get; set; } = string.Empty; + /// + /// api的密钥 + /// + public string ApiKey { get; set; } = string.Empty; + } + /// + /// 文本模型 配置 + /// + public class ChatGptConfig + { + /// + /// KIMI + /// + /// + public KIMIConfig KIMI { get; set; } = new KIMIConfig(); + } + + /// + /// ffmpeg配置 + /// + public class FFmpegConfig + { + /// + /// 音频切片时间段 + /// 0不切片 + /// + public int TimeSlice { get; set; } = 0; + } + /// + /// Whisper配置 + /// + public class WhisperConfig + { + /// + /// 模型名称 + /// + public string ModelName { get; set; } = string.Empty; + + } + /// + /// redis配置 + /// + public class RedisConfig + { + /// + /// redis连接字符串 + /// + public string ConnectionString { get; set; } = string.Empty; + } + + public class DBConfig + { + /// + /// 主库链接 + /// + public string ConnectionString { get; set; }=string.Empty; + /// + /// 数据库类型 + /// + public IocDbType SqlType { get; set; } + /// + /// 启动时更新表结构 + /// + public bool UpdateTable { get; set; } + + } + /// + /// 应用程序配置 + /// + public class AppConfig + { + /// + /// redis + /// + public RedisConfig Redis { get; set; } = new RedisConfig(); + /// + /// Whisper AI + /// + public WhisperConfig Whisper { get; set; } = new WhisperConfig(); + /// + /// FFmpeg + /// + public FFmpegConfig FFmpeg { get; set; } = new FFmpegConfig(); + /// + /// ChatGpt + /// + public ChatGptConfig ChatGpt { get; set; } = new ChatGptConfig(); + /// + /// 数据库配置 + /// + public DBConfig DB { get; set; } = new DBConfig(); + + } +} diff --git a/VideoAnalysisCore/Common/DownloadFile.cs b/VideoAnalysisCore/Common/DownloadFile.cs new file mode 100644 index 0000000..492b6e0 --- /dev/null +++ b/VideoAnalysisCore/Common/DownloadFile.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace VideoAnalysisCore.Common +{ + /// + /// + /// + public class DownloadFile + { + // 根据 Content-Type 映射文件后缀 + static string GetExtensionFromContentType(HttpResponseMessage res) + { + var contentType = res.Content.Headers.ContentType?.MediaType; + return contentType switch + { + "application/pdf" => ".pdf", + "image/jpeg" => ".jpg", + "image/png" => ".png", + "application/zip" => ".zip", + "text/html" => ".html", + "audio/wav" => ".wav", + "audio/mp4" => ".mp4", + "audio/mp3" => ".mp3", + // 根据需要添加其他 Content-Type 映射 + _ => ".mp4", // 默认二进制文件 + }; + } + + /// + /// 使用 HttpClient 下载任务的资源文件到本地 + /// + /// + /// + public static async Task RunTask(string task) + { + //获取资源文件 地址 + var fileUrl = RedisExpand.Redis.HMGet(RedisExpandKey.Task(task), "MediaUrl") + .FirstOrDefault(); + if (string.IsNullOrEmpty(fileUrl)) + throw new Exception($"任务id[{task}] 资源地址无效 {fileUrl}"); + + using HttpClient client = new HttpClient(); + // 发送 GET 请求以下载文件 + var response = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead); + // 确保响应是成功的 + response.EnsureSuccessStatusCode(); + + // 尝试从 URL 中获取文件后缀 + string fileExtension = Path.GetExtension(new Uri(fileUrl).AbsolutePath); + //否则 获取 从Content-Type 获取文件后缀 + if (string.IsNullOrEmpty(fileExtension)) + fileExtension = GetExtensionFromContentType(response); + + //创建下载文件缓存路径 + if (!Directory.Exists(AppCommon.TaskCachedFile)) Directory.CreateDirectory(AppCommon.TaskCachedFile); + + // 获取文件大小 + var totalBytes = response.Content.Headers.ContentLength; + var localPath = task.LocalPath(); + var outputPath = Path.Combine(localPath, task + fileExtension); + if (!Directory.Exists(localPath)) Directory.CreateDirectory(localPath); + + + //同步到redis + RedisExpand.Redis.HSet(RedisExpandKey.Task(task), "LocalMediaPath", outputPath); + // 打开本地文件流以写入文件 + using var fs = new FileStream(outputPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); + // 读取响应内容流 + using var contentStream = await response.Content.ReadAsStreamAsync(); + var buffer = new byte[512 * 1024]; // 512KB 缓冲区 + long totalBytesRead = 0; + var count = 0; + int bytesRead; + while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + count++; + await fs.WriteAsync(buffer, 0, bytesRead); + totalBytesRead += bytesRead; + + // 计算下载进度 + if (totalBytes.HasValue && count % 3 == 0) + { + var progress = (double)totalBytesRead / totalBytes.Value * 100; + Console.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}=> {task} 下载进度: {progress:F2}%"); + } + } + + //加入下一队列 + RedisExpand.InsertChannel(Enum.RedisChannelEnum.SeparateAudio, task); + } + } +} diff --git a/VideoAnalysisCore/Common/RedisExpand.cs b/VideoAnalysisCore/Common/RedisExpand.cs new file mode 100644 index 0000000..0323e5f --- /dev/null +++ b/VideoAnalysisCore/Common/RedisExpand.cs @@ -0,0 +1,169 @@ +using FreeRedis; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Channels; +using System.Threading.Tasks; +using System.Xml.Linq; +using VideoAnalysisCore.AICore.ChatGPT; +using VideoAnalysisCore.AICore.FFMPGE; + +//using VideoAnalysisCore.AICore.FFMPGE; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.AICore.Whisper; +using VideoAnalysisCore.Enum; + +namespace VideoAnalysisCore.Common +{ + /// + /// redis key + /// + public static class RedisExpandKey + { + /// + /// 基础key + /// + public const string BaseKey = "VideoAnalysis:"; + /// + /// 基础Channel key + /// + public const string ChannelKey = BaseKey + "Channel:"; + /// + /// 下载文件 + /// + public const string DownloadFile = ChannelKey + "DownloadFile"; + /// + /// 分离音频 + /// + public const string SeparateAudio = ChannelKey + "SeparateAudio"; + /// + /// 解析字幕 + /// + public const string ParsingCaptions = ChannelKey + "ParsingCaptions"; + /// + /// 解析说话人 + /// + public const string ParsingSpeaker = ChannelKey + "ParsingSpeaker"; + /// + /// Chat模型分析 + /// + public const string ChatModelAnalysis = ChannelKey + "ChatModelAnalysis"; + + /// + /// 任务数组 + /// + public const string TaskArr = BaseKey + "TaskArr"; + + /// + /// 获取枚举RedisKey + /// + /// + /// + public static string EnumKey(RedisChannelEnum e) + { + return ChannelKey + e.ToString(); + } + + /// + /// 任务对象地址 + /// + public static string Task(object taskId) => BaseKey + "Task:" + taskId; + + } + /// + /// redis拓展 + /// + public class RedisExpand + { + /// + /// redis 连接 + /// + public static RedisClient Redis = new RedisClient(AppCommon.Config.Redis.ConnectionString); + /// + /// 初始化redis + /// 需要在初始化配置文件时候调用 + /// + public static void Init() + { + Redis.Serialize = obj => System.Text.Json.JsonSerializer.Serialize(obj); + Redis.Deserialize = (json, type) => System.Text.Json.JsonSerializer.Deserialize(json, type); + InitChannel(); + } + /// + /// 将任务 插入 队列 + /// + /// 枚举 + /// 任务id + public static void InsertChannel(RedisChannelEnum @enum, object taskId) + { + var startTime = Redis.HMGet>(RedisExpandKey.Task(taskId), "StartTime").FirstOrDefault(); + if (startTime is null) + startTime = new Dictionary(); + if (!startTime.ContainsKey(@enum)) + startTime.Add(@enum, DateTime.Now); + else + startTime[@enum] = DateTime.Now; + Redis.HMSet(RedisExpandKey.Task(taskId), "StartTime", startTime); + + if (Redis is null) throw new Exception("redis未初始化"); + Redis.LPush(RedisExpandKey.EnumKey(@enum), taskId); + + } + + /// + /// 初始化 队列 任务 + /// + public static void InitChannel() + { + if (Redis is null) throw new Exception("redis未初始化"); + + Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.DownloadFile), + (msg) => { TouchChannel(RedisChannelEnum.DownloadFile, msg, DownloadFile.RunTask); }); + + Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.SeparateAudio), + (msg) => { TouchChannel(RedisChannelEnum.SeparateAudio, msg, FFMPGEHandle.Audio2WAV16KAsync); }); + + Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.ParsingCaptions), + (msg) => { TouchChannel(RedisChannelEnum.ParsingCaptions, msg, WhisperHandle.RunTask); }); + Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.ParsingSpeaker), + (msg) => { TouchChannel(RedisChannelEnum.ParsingSpeaker, msg, Speaker.Run); }); + Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.ChatModelAnalysis), + (msg) => + { + TouchChannel(RedisChannelEnum.ChatModelAnalysis, msg, + (task) => + { + using var scope = AppCommon.Services?.CreateScope(); + if (scope is null || scope.ServiceProvider.GetService() is null) + throw new Exception("IBserGPT 未注入"); + else + return scope.ServiceProvider.GetService()?.CallGPT(task)??Task.CompletedTask; + }); + }); + Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.CallBackSystem), + (msg) => { TouchChannel(RedisChannelEnum.ParsingSpeaker, msg); }); + + + } + /// + /// + /// + /// + /// + /// + public static async void TouchChannel(RedisChannelEnum key, string msg, Func action = null) + { + if (msg is null) return; + Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> " + key + " " + msg); + if (action is not null) + { + Redis.HMSet(RedisExpandKey.Task(msg), "LastEnum", key); + await action(msg); + } + else + { + Console.WriteLine(key + " 任务函数 未实现"); + } + } + + } +} diff --git a/VideoAnalysisCore/Common/Repository.cs b/VideoAnalysisCore/Common/Repository.cs new file mode 100644 index 0000000..6b463cf --- /dev/null +++ b/VideoAnalysisCore/Common/Repository.cs @@ -0,0 +1,19 @@ +using SqlSugar; +using SqlSugar.IOC; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VideoAnalysisCore.Common +{ + public class Repository : SimpleClient where T : class, new() + { + public Repository() + { + base.Context = DbScoped.SugarScope; + } + + } +} diff --git a/VideoAnalysisCore/Common/SqlSugarExpand.cs b/VideoAnalysisCore/Common/SqlSugarExpand.cs new file mode 100644 index 0000000..52d959a --- /dev/null +++ b/VideoAnalysisCore/Common/SqlSugarExpand.cs @@ -0,0 +1,154 @@ + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MySqlConnector; +using SqlSugar; +using SqlSugar.IOC; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; +using VideoAnalysisCore.Common; + +namespace VideoAnalysisCore.Common +{ + public static class SqlSugarExpand + { + public static bool ShowSQL = false; + public static void InitSqlSugar( this IServiceCollection services) + { + #region SqlSugar注入 + var dbList = new List() { + new IocConfig() + { + ConfigId =1, + ConnectionString = AppCommon.Config.DB.ConnectionString, + DbType =AppCommon.Config.DB.SqlType, + IsAutoCloseConnection = true//自动释放 + }, + }; + services.AddSingleton(typeof(Repository<>)); + + //注入SqlSugar 主库 + services.AddSqlSugar(dbList); + + + services.ConfigurationSugar(db => + { + var config = db.CurrentConnectionConfig; + // 设置超时时间 + db.Ado.CommandTimeOut = 61; +#if DEBUG + // 打印SQL语句 + db.Aop.OnLogExecuting = (sql, pars) => + { + if (!ShowSQL) return; + //var originColor = Console.ForegroundColor; + //if (sql.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) + // Console.ForegroundColor = ConsoleColor.Green; + //if (sql.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase)) + // Console.ForegroundColor = ConsoleColor.Yellow; + //if (sql.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase)) + // Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"【{DateTime.Now}——执行SQL - [{config.ConfigId}]】\r\n" + UtilMethods.GetSqlString(config.DbType, sql, pars) + "\r\n"); + //Console.ForegroundColor = originColor; + }; +#endif + db.Aop.OnError = (ex) => + { + if (ex.Parametres == null) return; + //var originColor = Console.ForegroundColor; + //Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine($"【{DateTime.Now}——错误SQL - [{config.ConfigId}]】\r\n" + UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres) + "\r\n"); + //Console.ForegroundColor = originColor; + }; + db.Aop.DataExecuting = (oldValue, entityInfo) => + { + if (entityInfo.OperationType == DataFilterType.InsertByObject) + { + // 主键(long类型)且没有值的---赋值雪花Id + if (entityInfo.EntityColumnInfo.IsPrimarykey && !entityInfo.EntityColumnInfo.IsIdentity && entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(long)) + { + var id = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue); + //if (id == null || (long)id == 0) + // entityInfo.SetValue(YitIdHelper.NextId()); + } + if (entityInfo.PropertyName == "CreateTime") + entityInfo.SetValue(DateTime.Now); + } + if (entityInfo.OperationType == DataFilterType.UpdateByObject) + { + + } + }; + + }); + + #endregion + } + public static void InitDB() + { + ShowSQL = false; + var builder = new MySqlConnectionStringBuilder(AppCommon.Config.DB.ConnectionString); + var dbName = builder.Database; + builder.Database = "mysql"; + using SqlSugarClient dbMysql = new SqlSugarClient(new ConnectionConfig + { + ConnectionString = builder.ConnectionString, + DbType = DbType.MySql, + IsAutoCloseConnection = true, + }); + + var dataBaseList = dbMysql.DbMaintenance.GetDataBaseList(); + if (dataBaseList.Contains(dbName)) + { + Console.WriteLine($"【0】数据库 {dbName} 已存在 【√】"); + } + else + { + Console.WriteLine($"【0】创建数据库{dbName} ... "); + var res = dbMysql.Ado.ExecuteCommand($"CREATE DATABASE `{dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci"); + try + { + dbMysql.Ado.ExecuteCommand($"alter database `{dbName}` character set utf8mb4;" + + $"alter database `{dbName}` character set utf8mb4 collate utf8mb4_general_ci;"); + //res 没有权限 + dbMysql.Ado.ExecuteCommand($"SET GLOBAL local_infile=1;"); + Console.WriteLine($"【0】数据库 {dbName} 创建完成 【√】"); + } + catch (Exception ex) + { + Console.WriteLine("【0】主库初始化配置 出现异常 " + ex.Message); + } + + + } + InitDbTable(); + } + public static void InitDbTable() + { + if (!AppCommon.Config.DB.UpdateTable) + { + Console.WriteLine($"【1】初始化主库表 跳过...."); + ShowSQL = true; + return; + } + Console.WriteLine($"【1】初始化主库表 执行中...."); + var entityTypes = AppCommon.DbMatserType; + Console.WriteLine($"【1】数量{entityTypes.Count()} ...."); + if (!entityTypes.Any()) return; + var i = 0; + var totalCount = entityTypes.Count().ToString().Length; + foreach (var t in entityTypes) + { + Console.Write($"【1】{entityTypes.Count()}/{(++i).ToString().PadLeft(totalCount, '0')} 执行 {t.FullName}".PadRight(60, ' ')); + DbScoped.SugarScope.CodeFirst.InitTables(t); + Console.WriteLine($"【√】"); + } + Console.WriteLine($"【1】数量{entityTypes.Count()} 执行完毕"); + ShowSQL = true; + } + } +} \ No newline at end of file diff --git a/VideoAnalysisCore/Enum/RedisChannelEnum.cs b/VideoAnalysisCore/Enum/RedisChannelEnum.cs new file mode 100644 index 0000000..66a2441 --- /dev/null +++ b/VideoAnalysisCore/Enum/RedisChannelEnum.cs @@ -0,0 +1,33 @@ +namespace VideoAnalysisCore.Enum +{ + /// + /// redis 频道 + /// + public enum RedisChannelEnum + { + /// + /// 下载文件 + /// + DownloadFile = 1, + /// + /// 分离音频 + /// + SeparateAudio = 2, + /// + /// 解析字幕 + /// + ParsingCaptions = 3, + /// + /// 解析说话人 + /// + ParsingSpeaker = 4, + /// + /// Chat模型分析 + /// + ChatModelAnalysis = 5, + /// + /// 回调三方系统 + /// + CallBackSystem = 6 + } +} diff --git a/VideoAnalysisCore/Enum/TimeBaseEnum.cs b/VideoAnalysisCore/Enum/TimeBaseEnum.cs new file mode 100644 index 0000000..1bc39ac --- /dev/null +++ b/VideoAnalysisCore/Enum/TimeBaseEnum.cs @@ -0,0 +1,29 @@ +namespace VideoAnalysisCore.Enum +{ + /// + /// 时间线类型 + /// + public enum TimeBaseTypeEnum + { + /// + /// 教师讲授 + /// + 教师讲授 = 1, + /// + /// 互动交流 + /// + 互动交流 = 2, + /// + /// 独立学习 + /// + 独立学习 = 3, + /// + /// 小组合作 + /// + 小组合作 = 4, + /// + /// 随堂练习 + /// + 随堂练习 + } +} diff --git a/VideoAnalysisCore/Model/CourseGradingCriteria.cs b/VideoAnalysisCore/Model/CourseGradingCriteria.cs new file mode 100644 index 0000000..bb4ed71 --- /dev/null +++ b/VideoAnalysisCore/Model/CourseGradingCriteria.cs @@ -0,0 +1,53 @@ +using SqlSugar; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Net; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Enum; +using Whisper.net; + +namespace VideoAnalysisCore.Model +{ + /// + /// 课堂评分标准 + /// + [SugarTable("coursegradingcriteria")] + public class CourseGradingCriteria + { + /// + /// Id + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + [DisplayName( "编号"), Required] + public long Id { get; set; } + /// + /// 标准提问词 + /// + [SugarColumn(Length = 100)] + [DisplayName( "标准提问词"), Required] + public string NamePrompt { get; set; }=string.Empty; + /// + /// 优点展示词 + /// + [SugarColumn(Length = 100)] + [DisplayName( "优点展示词"), Required] + public string Advantage { get; set; } = string.Empty; + + /// + /// 缺点展示词 + /// + [SugarColumn(Length = 100)] + [DisplayName( "缺点展示词"), Required] + public string Flaw { get; set; } = string.Empty; + + /// + /// 改进意见 + /// + [DisplayName( "改进意见")] + [SugarColumn(Length = 100), Required] + public string ImprovedMethods { get; set; } = string.Empty; + + + } +} diff --git a/VideoAnalysisCore/Model/Dto/SpeakerCaptionsDto.cs b/VideoAnalysisCore/Model/Dto/SpeakerCaptionsDto.cs new file mode 100644 index 0000000..5c3e340 --- /dev/null +++ b/VideoAnalysisCore/Model/Dto/SpeakerCaptionsDto.cs @@ -0,0 +1,49 @@ +using VideoAnalysisCore.Enum; + +namespace VideoAnalysisCore.Model.Dto +{ + /// + /// 时间线 + /// + public class TimeBase + { + public TimeBase() + { + + } + /// + /// 开始时间 + /// + public double Start { get; set; } + /// + /// 结束时间 + /// + public double End { get; set; } + /// + /// 时间段 类型 + /// + public TimeBaseTypeEnum Type { get; set; } + + } + public class TotalCaptionsDto + { + /// + /// 拼接说话人后的最终字幕 + /// + public string Captions { get; set; } = string.Empty; + /// + /// 教师发言时间 + /// + /// + public decimal TeacherSpeaking { get; set; } = 0; + /// + /// 学生发言时间 + /// + /// + public decimal StudentSpeaking { get; set; } = 0; + /// + /// 视频时间轴 + /// + public IEnumerable? TimeBase { get; set; } + } +} diff --git a/VideoAnalysisCore/Model/VideoTask.cs b/VideoAnalysisCore/Model/VideoTask.cs new file mode 100644 index 0000000..f208163 --- /dev/null +++ b/VideoAnalysisCore/Model/VideoTask.cs @@ -0,0 +1,83 @@ +using SqlSugar; +using System.ComponentModel.DataAnnotations; +using System.Net; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Enum; +using Whisper.net; + +namespace VideoAnalysisCore.Model +{ + /// + /// 视频任务模型 + /// + [SugarTable("videotask")] + public class VideoTask + { + /// + /// 任务id + /// 视频音频文件地址都使用taskID能获取 + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public long Id { get; set; } + /// + /// 媒体路径 + /// + public string MediaUrl { get; set; } = string.Empty; + /// + /// 下载后本地媒体目录 + /// + public string LocalMediaPath { get; set; } = string.Empty; + /// + /// 上一次执行的枚举 + /// + public RedisChannelEnum LastEnum { get; set; } + /// + /// ApiKey + /// + public string ApiToken { get; set; } = string.Empty; + /// + /// 请求来自哪个ip地址 + /// + public string ComeFrom { get; set; } = string.Empty; + /// + /// 自定义值 任务完成后附带通知 + /// + [SugarColumn(Length = 500)] + public string Tag { get; set; } + /// + ///回调Api地址 + /// + [SugarColumn(Length = 500)] + public string CallBackUrl { get; set; } + /// + /// 字幕缓存 + /// + [SugarColumn(ColumnDataType = "longtext", IsNullable = true)] + public SegmentData[]? Captions { get; set; } + /// + /// 说话人日志解析缓存 + /// + [SugarColumn(ColumnDataType = "longtext", IsNullable = true)] + public OfflineSpeakerRes[]? Speaker { get; set; } + /// + /// Chat模型分析缓存 + /// + [SugarColumn(ColumnDataType = "longtext", IsNullable = true)] + public object[]? ChatAnalysis { get; set; } + /// + /// 消耗token + /// + public int TotalTokens { get; set; } + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } = DateTime.Now; + /// + /// 开始时间轴 + /// + [SugarColumn(ColumnDataType = "varchar", Length =255)] + public Dictionary StartTime { get; set; } + + + } +} diff --git a/VideoAnalysisCore/VideoAnalysisCore.csproj b/VideoAnalysisCore/VideoAnalysisCore.csproj new file mode 100644 index 0000000..6be7135 --- /dev/null +++ b/VideoAnalysisCore/VideoAnalysisCore.csproj @@ -0,0 +1,59 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + Never + + + PreserveNewest + + + Always + + + PreserveNewest + + + Always + + + Always + + + + + + + + + + + + + + + +