添加项目文件。
This commit is contained in:
commit
d8224e68d2
|
|
@ -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
|
||||||
|
|
@ -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/
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<base href="/" />
|
||||||
|
<link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet" />
|
||||||
|
<link href="_content/AntDesign.ProLayout/css/ant-design-pro-layout-blazor.css" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="Learn.VideoAnalysis.bundle.scp.css" />
|
||||||
|
<link href="./css/site.css" rel="stylesheet" />
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
|
<HeadOutlet @rendermode="InteractiveServer" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<Routes @rendermode="InteractiveServer" />
|
||||||
|
<script type="text/javascript" src="@("https://unpkg.com/@antv/g2plot@2.4.17/dist/g2plot.min.js")"></script>
|
||||||
|
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
|
||||||
|
<script src="_content/AntDesign.Charts/ant-design-charts-blazor.js"></script>
|
||||||
|
<script src="_framework/blazor.web.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
@page "/Error"
|
||||||
|
@using System.Diagnostics
|
||||||
|
|
||||||
|
<PageTitle>Error</PageTitle>
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
|
@if (ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3>Development Mode</h3>
|
||||||
|
<p>
|
||||||
|
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||||
|
It can result in displaying sensitive information from exceptions to end users.
|
||||||
|
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||||
|
and restarting the app.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
@namespace VideoAnalysisRazor.Layouts
|
||||||
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
|
<AntDesign.ProLayout.BasicLayout
|
||||||
|
Logo="@("https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg")"
|
||||||
|
MenuData="_menuData"
|
||||||
|
Context="学习视频分析"
|
||||||
|
MenuAccordion
|
||||||
|
Title="学习视频分析"
|
||||||
|
|
||||||
|
@bind-Collapsed="collapsed">
|
||||||
|
<HeaderContentRender>
|
||||||
|
<Space Size="@("24")">
|
||||||
|
<SpaceItem>
|
||||||
|
<Icon Class="action" Type="@(collapsed?"menu-unfold":"menu-fold")" OnClick="Toggle" />
|
||||||
|
</SpaceItem>
|
||||||
|
<SpaceItem>
|
||||||
|
<Icon Class="action" Type="reload" Theme="outline" OnClick="Reload" />
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</HeaderContentRender>
|
||||||
|
<RightContentRender>
|
||||||
|
</RightContentRender>
|
||||||
|
<ChildContent>
|
||||||
|
<ReuseTabs></ReuseTabs>
|
||||||
|
</ChildContent>
|
||||||
|
<FooterRender>
|
||||||
|
<FooterView Copyright="2024 重庆远轩教育科技有限公司" Links="new LinkItem[0]"></FooterView>
|
||||||
|
</FooterRender>
|
||||||
|
</AntDesign.ProLayout.BasicLayout>
|
||||||
|
|
||||||
|
<SettingDrawer />
|
||||||
|
|
@ -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()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
@page "/"
|
||||||
|
@using AntDesign
|
||||||
|
@using AntDesign.TableModels
|
||||||
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
@using SqlSugar
|
||||||
|
@using VideoAnalysisCore.Model
|
||||||
|
|
||||||
|
<Table @ref="_table" Loading="tableLoading" TItem="CourseGradingCriteria" PageSize="15" Total="_total" DataSource="_dataSource" @bind-SelectedRows="_selectedRows" OnChange="OnChange">
|
||||||
|
<TitleTemplate>
|
||||||
|
<Flex Justify="end" Gap="10">
|
||||||
|
<Button Type="primary" @onclick="()=> StartEdit(default)">新增</Button>
|
||||||
|
@* <Button Disabled="!_selectedRows.Any()" Danger @onclick="DeleteAll">Delete</Button> *@
|
||||||
|
</Flex>
|
||||||
|
</TitleTemplate>
|
||||||
|
<ColumnDefinitions Context="row">
|
||||||
|
<Selection />
|
||||||
|
<ActionColumn Title="操作列">
|
||||||
|
<a @onclick="() => StartEdit(row)">修改</a>
|
||||||
|
<Button Type="@ButtonType.Link" Danger @onclick="() => Delete(row)">
|
||||||
|
删除</Button>
|
||||||
|
</ActionColumn>
|
||||||
|
<GenerateColumns Definitions="@((n,c) => { c.Filterable = true; c.Sortable = true; })" />
|
||||||
|
</ColumnDefinitions>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
@inject ModalService ModalService
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 新增或者修改
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="row"></param>
|
||||||
|
void StartEdit(CourseGradingCriteria row)
|
||||||
|
{
|
||||||
|
var data = row == null ? new() : row;
|
||||||
|
IForm? form = default;
|
||||||
|
ModalRef<bool> modalRef = default;
|
||||||
|
modalRef = ModalService.CreateModal<bool>(new()
|
||||||
|
{
|
||||||
|
Title = data.Id > 0 ? "修改" : "新增",
|
||||||
|
Content =
|
||||||
|
@<Form @ref="form" Model="data" OnFinish="()=> modalRef.OkAsync(true)"
|
||||||
|
LabelColSpan="6" WrapperColSpan="18">
|
||||||
|
<GenerateFormItem NotGenerate="@(x=> x == "Id")" />
|
||||||
|
</Form>
|
||||||
|
,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<CourseGradingCriteria> criteria { get; set; } = default!;
|
||||||
|
|
||||||
|
|
||||||
|
IEnumerable<CourseGradingCriteria> _selectedRows = [];
|
||||||
|
ITable _table;
|
||||||
|
|
||||||
|
List<CourseGradingCriteria> _dataSource = null;
|
||||||
|
RefAsync<int> _total = 0;
|
||||||
|
|
||||||
|
bool tableLoading = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分页 查询 筛选 时
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query"></param>
|
||||||
|
async void OnChange(QueryModel<CourseGradingCriteria> query)
|
||||||
|
{
|
||||||
|
tableLoading = true;
|
||||||
|
Expression<Func<CourseGradingCriteria, bool>> 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();
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 删除行
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="row"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
async Task Delete(CourseGradingCriteria row)
|
||||||
|
{
|
||||||
|
if (!await Comfirm($"确定要删除这条数据吗? [{row.NamePrompt}]?"))
|
||||||
|
return;
|
||||||
|
await criteria.DeleteByIdAsync(row.Id);
|
||||||
|
_table.ReloadData();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> Comfirm(string message)
|
||||||
|
{
|
||||||
|
return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
input[aria-hidden="true"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace VideoAnalysisRazor.Resources;
|
||||||
|
|
||||||
|
|
||||||
|
internal class I18n
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="menu.account.center" xml:space="preserve">
|
||||||
|
<value>Account Center</value>
|
||||||
|
</data>
|
||||||
|
<data name="menu.account.logout" xml:space="preserve">
|
||||||
|
<value>Logout</value>
|
||||||
|
</data>
|
||||||
|
<data name="menu.account.settings" xml:space="preserve">
|
||||||
|
<value>Settings</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="menu.account.center" xml:space="preserve">
|
||||||
|
<value>个人中心</value>
|
||||||
|
</data>
|
||||||
|
<data name="menu.account.logout" xml:space="preserve">
|
||||||
|
<value>退出登录</value>
|
||||||
|
</data>
|
||||||
|
<data name="menu.account.settings" xml:space="preserve">
|
||||||
|
<value>个人设置</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<Router AppAssembly="typeof(Program).Assembly">
|
||||||
|
<Found Context="routeData">
|
||||||
|
<CascadingValue Value="routeData">
|
||||||
|
<RouteView RouteData="routeData" DefaultLayout="typeof(VideoAnalysisRazor.Layouts.BasicLayout)" />
|
||||||
|
</CascadingValue>
|
||||||
|
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||||
|
</Found>
|
||||||
|
</Router>
|
||||||
|
|
||||||
|
<AntContainer />
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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<ApiController> _logger;
|
||||||
|
public ApiController(ILogger<ApiController> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 音频转换wav_16k
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">文件路径</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet(Name = "Audio2WAV16K")]
|
||||||
|
public async Task<IActionResult> 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地址");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 插入队列
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enum"></param>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost(Name = "TestInsertChannel")]
|
||||||
|
public IActionResult TestInsertChannel(int @enum=1, string msg= "test_0001")
|
||||||
|
{
|
||||||
|
RedisExpand.InsertChannel(@enum.ToEnum<RedisChannelEnum>().Value
|
||||||
|
, msg);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 视频处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="req">请求体</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Learn.VideoAnalysis.Controllers.Dto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 视频处理 请求
|
||||||
|
/// </summary>
|
||||||
|
public class VideoAnalysisReq
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 媒体路径
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "资源URL是必填项")]
|
||||||
|
[Url(ErrorMessage = "请输入有效的 URL")]
|
||||||
|
public string MediaUrl { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// ApiKey
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "接口Token是必填项")]
|
||||||
|
public string ApiToken { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义值 任务完成后附带通知
|
||||||
|
/// </summary>
|
||||||
|
public string Tag { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
///回调Api地址
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "回调Api地址是必填项")]
|
||||||
|
[Url(ErrorMessage = "请输入有效的 URL")]
|
||||||
|
public string CallBackUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
global using VideoAnalysisRazor.Resources;
|
||||||
|
global using AntDesign;
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<InvariantGlobalization>false</InvariantGlobalization>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="AICore\**" />
|
||||||
|
<Content Remove="AICore\**" />
|
||||||
|
<EmbeddedResource Remove="AICore\**" />
|
||||||
|
<None Remove="AICore\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\VideoAnalysisCore\VideoAnalysisCore.csproj" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.5" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<PackageReference Include="AntDesign.Charts" Version="0.4.0" />
|
||||||
|
<PackageReference Include="AntDesign.Extensions.Localization" Version="0.20.2.1" />
|
||||||
|
<PackageReference Include="AntDesign.ProLayout" Version="0.20.2.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||||
|
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||||
|
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
@Learn.VideoAnalysis_HostAddress = http://localhost:5238
|
||||||
|
|
||||||
|
GET {{Learn.VideoAnalysis_HostAddress}}/Audio2Text?modelType=LargeV3
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
|
|
@ -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<IHttpContextAccessor>().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<ProSettings>(builder.Configuration.GetSection("ProSettings"));
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
builder.Services.AddSingleton<MoonshotClient>();
|
||||||
|
builder.Services.AddSingleton<IBserGPT, KIMI_GPT>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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<App>()
|
||||||
|
.AddInteractiveServerRenderMode();
|
||||||
|
//.AddInteractiveWebAssemblyRenderMode()
|
||||||
|
//.AddAdditionalAssemblies(typeof(VideoAnalysisRazor._Imports).Assembly);
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
SqlSugarExpand.InitDB();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using VideoAnalysisCore.AICore.SherpaOnnx;
|
||||||
|
using VideoAnalysisCore.Common;
|
||||||
|
using Whisper.net;
|
||||||
|
|
||||||
|
namespace VideoAnalysisCore.AICore.ChatGPT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// GPT 接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IBserGPT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 访问GPT
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">任务id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task CallGPT(string task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// kimi 文本模型
|
||||||
|
/// </summary>
|
||||||
|
public class KIMI_GPT : IBserGPT
|
||||||
|
{
|
||||||
|
private readonly MoonshotClient moonshotClient;
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="moonshotClient"></param>
|
||||||
|
/// <param name="logger"></param>
|
||||||
|
public KIMI_GPT(MoonshotClient moonshotClient)
|
||||||
|
{
|
||||||
|
MoonshotClient.Host = AppCommon.Config.ChatGpt.KIMI.Host;
|
||||||
|
MoonshotClient.ApiKey = AppCommon.Config.ChatGpt.KIMI.ApiKey;
|
||||||
|
|
||||||
|
this.moonshotClient = moonshotClient;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 访问GPT
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">任务id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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<MessagesItem>(){
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// https://platform.moonshot.cn/docs/api-reference
|
||||||
|
/// </summary>
|
||||||
|
public class MoonshotClient
|
||||||
|
{
|
||||||
|
private readonly ILogger<MoonshotClient> _logger;
|
||||||
|
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
|
public MoonshotClient(ILogger<MoonshotClient> logger, IHttpClientFactory httpClientFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// list models
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ModelListResp> ListModels()
|
||||||
|
{
|
||||||
|
var response = await GetAsync("/v1/models");
|
||||||
|
return await ParseResp<ModelListResp>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestBody"></param>
|
||||||
|
/// <returns>Return HttpResponseMessage for SSE</returns>
|
||||||
|
public async Task<HttpResponseMessage> Chat(string requestBody)
|
||||||
|
{
|
||||||
|
return await PostJsonStreamAsync("/v1/chat/completions", requestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chatReq"></param>
|
||||||
|
/// <returns>Return HttpResponseMessage for SSE</returns>
|
||||||
|
public async Task<HttpResponseMessage> Chat(ChatReq chatReq)
|
||||||
|
{
|
||||||
|
var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq);
|
||||||
|
return await PostJsonStreamAsync("/v1/chat/completions", requestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get as timate token count
|
||||||
|
/// </summary>
|
||||||
|
public async Task<int?> 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<int>();
|
||||||
|
}
|
||||||
|
var error = System.Text.Json.JsonSerializer.Deserialize<ErrorResponse>(responseText);
|
||||||
|
_logger.LogError($"{error?.error?.type}: {error?.error?.message}");
|
||||||
|
throw new Exception($"{error?.error.type}: {error?.error.message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get as timate token count
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chatReq"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<int?> GetAsTiMateTokenCount(ChatReq chatReq)
|
||||||
|
{
|
||||||
|
var chatReqText =System.Text.Json.JsonSerializer.Serialize(chatReq);
|
||||||
|
return await GetAsTiMateTokenCount(chatReqText);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List files
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<FileListResp> ListFiles()
|
||||||
|
{
|
||||||
|
var response = await GetAsync("/v1/files");
|
||||||
|
return await ParseResp<FileListResp>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Upload file
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<FileItem> 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<FileItem>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Upload file stream
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<FileItem> 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<FileItem>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get file content
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
|
public virtual async Task<FileContent> GetFileContent(string fileId)
|
||||||
|
{
|
||||||
|
var response = await GetAsync($"/v1/files/{fileId}/content");
|
||||||
|
return await ParseResp<FileContent>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task<HttpResponseMessage> GetAsync(string path)
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
|
||||||
|
return await client.GetAsync(Host + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<HttpResponseMessage> 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<HttpResponseMessage> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse response
|
||||||
|
/// </summary>
|
||||||
|
private async Task<T> ParseResp<T>(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return System.Text.Json.JsonSerializer.Deserialize<T>(responseText) ?? default;
|
||||||
|
}
|
||||||
|
var error = System.Text.Json.JsonSerializer.Deserialize<ErrorResponse>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string role { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string content { get; set; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// chat请求参数
|
||||||
|
/// </summary>
|
||||||
|
public class ChatReq
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 使用的模型
|
||||||
|
/// <para>例如[ moonshot-v1-8k ]</para>
|
||||||
|
/// </summary>
|
||||||
|
public string model { get; set; } = "moonshot-v1-8k";
|
||||||
|
/// <summary>
|
||||||
|
/// 消息主体
|
||||||
|
/// </summary>
|
||||||
|
public List<MessagesItem> messages { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 使用什么采样温度,介于 0 和 1 之间。较高的值(如 0.7)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定性
|
||||||
|
/// <para>默认为 0,如果设置,值域须为 [0, 1] 我们推荐 0.3,以达到较合适的效果</para>
|
||||||
|
/// </summary>
|
||||||
|
public double temperature { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 聊天完成时生成的最大 token 数。如果到生成了最大 token 数个结果仍然没有结束,finish reason 会是 "length", 否则会是 "stop"
|
||||||
|
/// <para>这个值建议按需给个合理的值,如果不给的话,我们会给一个不错的整数比如 1024。特别要注意的是,这个 max_tokens 是指您期待我们返回的 token 长度,而不是输入 + 输出的总长度。比如对一个 moonshot-v1-8k 模型,它的最大输入 + 输出总长度是 8192,当输入 messages 总长度为 4096 的时候,您最多只能设置为 4096,否则我们服务会返回不合法的输入参数( invalid_request_error ),并拒绝回答。如果您希望获得“输入的精确 token 数”,可以使用下面的“计算 Token” API 使用我们的计算器获得计数</para>
|
||||||
|
/// </summary>
|
||||||
|
public int? max_tokens { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 另一种采样方法,即模型考虑概率质量为 top_p 的标记的结果。因此,0.1 意味着只考虑概率质量最高的 10% 的标记。一般情况下,我们建议改变这一点或温度,但不建议 同时改变
|
||||||
|
/// </summary>
|
||||||
|
public float? top_p { get; set; } = 1.0f;
|
||||||
|
/// <summary>
|
||||||
|
/// 为每条输入消息生成多少个结果
|
||||||
|
/// <para>默认为 1,不得大于 5。特别的,当 temperature 非常小靠近 0 的时候,我们只能返回 1 个结果,如果这个时候 n 已经设置并且 > 1,我们的服务会返回不合法的输入参数(invalid_request_error)</para>
|
||||||
|
/// </summary>
|
||||||
|
public int? n { get; set; } = 1;
|
||||||
|
/// <summary>
|
||||||
|
/// 存在惩罚,介于-2.0到2.0之间的数字。正值会根据新生成的词汇是否出现在文本中来进行惩罚,增加模型讨论新话题的可能性
|
||||||
|
/// <para>默认为 0</para>
|
||||||
|
/// </summary>
|
||||||
|
public float? presence_penalty { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 频率惩罚,介于-2.0到2.0之间的数字。正值会根据新生成的词汇在文本中现有的频率来进行惩罚,减少模型一字不差重复同样话语的可能性
|
||||||
|
/// <para>默认为 0</para>
|
||||||
|
/// </summary>
|
||||||
|
public float? frequency_penalty { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 停止词,当全匹配这个(组)词后会停止输出,这个(组)词本身不会输出。最多不能超过 5 个字符串,每个字符串不得超过 32 字节
|
||||||
|
/// <para>默认 null</para>
|
||||||
|
/// </summary>
|
||||||
|
public List<string>? stop { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 是否流式返回
|
||||||
|
/// <para>false</para>
|
||||||
|
/// </summary>
|
||||||
|
public bool stream { get; set; } = false;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class PermissionItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public int created { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string @object { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string allow_create_engine { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string allow_sampling { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string allow_logprobs { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string allow_search_indices { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string allow_view { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string allow_fine_tuning { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string organization { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string @group { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string is_blocking { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModelInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public int created { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string @object { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string owned_by { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public List<PermissionItem> permission { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string root { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string parent { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModelListResp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string @object { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public List<ModelInfo> data { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class FileListResp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string @object { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public List<FileItem> data { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileContent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string content { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string file_type { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string filename { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string title { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class FileItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string @object { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public int bytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public int created_at { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string filename { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string purpose { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string status { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string status_details { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class ErrorMsg
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string message { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ErrorResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public ErrorMsg error { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ffmpeg处理程序
|
||||||
|
/// </summary>
|
||||||
|
public class FFMPGEHandle
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public static string FFmpegPath = Path.Combine(AppCommon.AIModelFile, "ffmpeg.exe");
|
||||||
|
/// <summary>
|
||||||
|
/// 音频转码为 wav_16k
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">任务id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 说话人模型
|
||||||
|
/// <para>pyannote </para>
|
||||||
|
/// </summary>
|
||||||
|
public class Speaker
|
||||||
|
{
|
||||||
|
private static OfflineSpeakerDiarization? SD;
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化 Speaker
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="speakerNumber"></param>
|
||||||
|
/// <param name="threshold"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 运行 说话人日志
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task"></param>
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 讲话人日志结果
|
||||||
|
/// </summary>
|
||||||
|
public class OfflineSpeakerRes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 总持续时间
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public decimal Total => End - Start;
|
||||||
|
/// <summary>
|
||||||
|
/// 开始时间
|
||||||
|
/// </summary>
|
||||||
|
public decimal Start { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 结束时间
|
||||||
|
/// </summary>
|
||||||
|
public decimal End { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 讲话人索引
|
||||||
|
/// </summary>
|
||||||
|
public int SpeakerIndex { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 讲话人日志结果
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sds"></param>
|
||||||
|
public OfflineSpeakerRes(OfflineSpeakerDiarizationSegment sds)
|
||||||
|
{
|
||||||
|
Start = (decimal)sds.Start;
|
||||||
|
End = (decimal)sds.End;
|
||||||
|
SpeakerIndex = sds.Speaker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
using Whisper.net;
|
||||||
|
|
||||||
|
namespace VideoAnalysisCore.AICore.Whisper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 字幕识别 结果
|
||||||
|
/// </summary>
|
||||||
|
public class WhisperResDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sd"></param>
|
||||||
|
public WhisperResDto(SegmentData sd)
|
||||||
|
{
|
||||||
|
Text = sd.Text;
|
||||||
|
Start = sd.Start;
|
||||||
|
End = sd.End;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 文本
|
||||||
|
/// </summary>
|
||||||
|
public string Text { get; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 开始时间
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
|
public TimeSpan Start { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// 结束时间
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
|
public TimeSpan End { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取语音字幕
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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<WhisperResDto>(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);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 检测语言的方法
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opt"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 音频处理选项
|
||||||
|
/// </summary>
|
||||||
|
public class WhisperOption
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 传入目标文件路径
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
public WhisperOption(string file)
|
||||||
|
{
|
||||||
|
FilePath = file;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 指令类型
|
||||||
|
/// </summary>
|
||||||
|
public string Command { get; set; } = "transcribe";
|
||||||
|
/// <summary>
|
||||||
|
/// 音频文件,默认要存放bin目录下
|
||||||
|
/// </summary>
|
||||||
|
public string FilePath { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 语言,默认自动选择
|
||||||
|
/// </summary>
|
||||||
|
public string Language { get; set; } = "chinese";
|
||||||
|
/// <summary>
|
||||||
|
/// 模型文件名称
|
||||||
|
/// </summary>
|
||||||
|
public string ModelName { get; set; } = "ggml-base.bin";
|
||||||
|
/// <summary>
|
||||||
|
/// 模型文件路径
|
||||||
|
/// </summary>
|
||||||
|
public string ModPath => Path.Combine(AppCommon.AIModelFile, ModelName);
|
||||||
|
/// <summary>
|
||||||
|
/// 模型类型
|
||||||
|
/// </summary>
|
||||||
|
//public GgmlType ModelType { get; set; } = GgmlType.Base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序 公共变量
|
||||||
|
/// </summary>
|
||||||
|
public static class AppCommon
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 应用有效程序集
|
||||||
|
/// </summary>
|
||||||
|
public static readonly IEnumerable<Assembly> Assemblies;
|
||||||
|
/// <summary>
|
||||||
|
/// 主库数据库表类型
|
||||||
|
/// </summary>
|
||||||
|
public static readonly IEnumerable<Type> 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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序配置
|
||||||
|
/// </summary>
|
||||||
|
public static AppConfig Config = new AppConfig();
|
||||||
|
/// <summary>
|
||||||
|
/// ServiceProvider
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceProvider? Services;
|
||||||
|
/// <summary>
|
||||||
|
/// 文件下载路径
|
||||||
|
/// </summary>
|
||||||
|
public static string TaskCachedFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TaskCachedFile");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型地址
|
||||||
|
/// </summary>
|
||||||
|
public static string AIModelFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AICore", "_Static");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 拓展函数
|
||||||
|
/// </summary>
|
||||||
|
public static class ExpandFunction
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取应用有效程序集
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IEnumerable</returns>
|
||||||
|
public static List<Assembly> 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;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 获取Task处理后的 说话人字幕
|
||||||
|
/// </summary>
|
||||||
|
public static TotalCaptionsDto GetSpeakerCaptions(string task)
|
||||||
|
{
|
||||||
|
var captionsArr = RedisExpand.Redis.HMGet<SegmentData[]>(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)
|
||||||
|
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<SegmentData, List<int>>();
|
||||||
|
foreach (var segment in captionsArr)
|
||||||
|
{
|
||||||
|
var spList = new List<int>();
|
||||||
|
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.互动交流
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 转化枚举
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T? ToEnum<T>(this object data) where T : struct, System.Enum
|
||||||
|
{
|
||||||
|
if (data is null || string.IsNullOrEmpty(data?.ToString()))
|
||||||
|
return null;
|
||||||
|
return System.Enum.Parse<T>(data.ToString());
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 转化本地缓存目录
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId">任务id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string LocalPath(this string taskId)
|
||||||
|
{
|
||||||
|
return Path.Combine(AppCommon.TaskCachedFile, taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ffmpeg配置
|
||||||
|
/// </summary>
|
||||||
|
public class KIMIConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// kimi请求 公开的服务地址
|
||||||
|
/// </summary>
|
||||||
|
public string Host { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// api的密钥
|
||||||
|
/// </summary>
|
||||||
|
public string ApiKey { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 文本模型 配置
|
||||||
|
/// </summary>
|
||||||
|
public class ChatGptConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// KIMI
|
||||||
|
/// <para></para>
|
||||||
|
/// </summary>
|
||||||
|
public KIMIConfig KIMI { get; set; } = new KIMIConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ffmpeg配置
|
||||||
|
/// </summary>
|
||||||
|
public class FFmpegConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 音频切片时间段
|
||||||
|
/// <para>0不切片</para>
|
||||||
|
/// </summary>
|
||||||
|
public int TimeSlice { get; set; } = 0;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Whisper配置
|
||||||
|
/// </summary>
|
||||||
|
public class WhisperConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 模型名称
|
||||||
|
/// </summary>
|
||||||
|
public string ModelName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// redis配置
|
||||||
|
/// </summary>
|
||||||
|
public class RedisConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// redis连接字符串
|
||||||
|
/// </summary>
|
||||||
|
public string ConnectionString { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DBConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 主库链接
|
||||||
|
/// </summary>
|
||||||
|
public string ConnectionString { get; set; }=string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库类型
|
||||||
|
/// </summary>
|
||||||
|
public IocDbType SqlType { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 启动时更新表结构
|
||||||
|
/// </summary>
|
||||||
|
public bool UpdateTable { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 应用程序配置
|
||||||
|
/// </summary>
|
||||||
|
public class AppConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// redis
|
||||||
|
/// </summary>
|
||||||
|
public RedisConfig Redis { get; set; } = new RedisConfig();
|
||||||
|
/// <summary>
|
||||||
|
/// Whisper AI
|
||||||
|
/// </summary>
|
||||||
|
public WhisperConfig Whisper { get; set; } = new WhisperConfig();
|
||||||
|
/// <summary>
|
||||||
|
/// FFmpeg
|
||||||
|
/// </summary>
|
||||||
|
public FFmpegConfig FFmpeg { get; set; } = new FFmpegConfig();
|
||||||
|
/// <summary>
|
||||||
|
/// ChatGpt
|
||||||
|
/// </summary>
|
||||||
|
public ChatGptConfig ChatGpt { get; set; } = new ChatGptConfig();
|
||||||
|
/// <summary>
|
||||||
|
/// 数据库配置
|
||||||
|
/// </summary>
|
||||||
|
public DBConfig DB { get; set; } = new DBConfig();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VideoAnalysisCore.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
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", // 默认二进制文件
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 HttpClient 下载任务的资源文件到本地
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// redis key
|
||||||
|
/// </summary>
|
||||||
|
public static class RedisExpandKey
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 基础key
|
||||||
|
/// </summary>
|
||||||
|
public const string BaseKey = "VideoAnalysis:";
|
||||||
|
/// <summary>
|
||||||
|
/// 基础Channel key
|
||||||
|
/// </summary>
|
||||||
|
public const string ChannelKey = BaseKey + "Channel:";
|
||||||
|
/// <summary>
|
||||||
|
/// 下载文件
|
||||||
|
/// </summary>
|
||||||
|
public const string DownloadFile = ChannelKey + "DownloadFile";
|
||||||
|
/// <summary>
|
||||||
|
/// 分离音频
|
||||||
|
/// </summary>
|
||||||
|
public const string SeparateAudio = ChannelKey + "SeparateAudio";
|
||||||
|
/// <summary>
|
||||||
|
/// 解析字幕
|
||||||
|
/// </summary>
|
||||||
|
public const string ParsingCaptions = ChannelKey + "ParsingCaptions";
|
||||||
|
/// <summary>
|
||||||
|
/// 解析说话人
|
||||||
|
/// </summary>
|
||||||
|
public const string ParsingSpeaker = ChannelKey + "ParsingSpeaker";
|
||||||
|
/// <summary>
|
||||||
|
/// Chat模型分析
|
||||||
|
/// </summary>
|
||||||
|
public const string ChatModelAnalysis = ChannelKey + "ChatModelAnalysis";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务数组
|
||||||
|
/// </summary>
|
||||||
|
public const string TaskArr = BaseKey + "TaskArr";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取枚举RedisKey
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string EnumKey(RedisChannelEnum e)
|
||||||
|
{
|
||||||
|
return ChannelKey + e.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务对象地址
|
||||||
|
/// </summary>
|
||||||
|
public static string Task(object taskId) => BaseKey + "Task:" + taskId;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// redis拓展
|
||||||
|
/// </summary>
|
||||||
|
public class RedisExpand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// redis 连接
|
||||||
|
/// </summary>
|
||||||
|
public static RedisClient Redis = new RedisClient(AppCommon.Config.Redis.ConnectionString);
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化redis
|
||||||
|
/// <para>需要在初始化配置文件时候调用</para>
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 将任务 插入 队列
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enum">枚举</param>
|
||||||
|
/// <param name="taskId">任务id</param>
|
||||||
|
public static void InsertChannel(RedisChannelEnum @enum, object taskId)
|
||||||
|
{
|
||||||
|
var startTime = Redis.HMGet<Dictionary<RedisChannelEnum, DateTime>>(RedisExpandKey.Task(taskId), "StartTime").FirstOrDefault();
|
||||||
|
if (startTime is null)
|
||||||
|
startTime = new Dictionary<RedisChannelEnum, DateTime>();
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化 队列 任务
|
||||||
|
/// </summary>
|
||||||
|
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<IBserGPT>() is null)
|
||||||
|
throw new Exception("IBserGPT 未注入");
|
||||||
|
else
|
||||||
|
return scope.ServiceProvider.GetService<IBserGPT>()?.CallGPT(task)??Task.CompletedTask;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Redis.SubscribeList(RedisExpandKey.EnumKey(RedisChannelEnum.CallBackSystem),
|
||||||
|
(msg) => { TouchChannel(RedisChannelEnum.ParsingSpeaker, msg); });
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
public static async void TouchChannel(RedisChannelEnum key, string msg, Func<string, Task> 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 + " 任务函数 未实现");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<T> : SimpleClient<T> where T : class, new()
|
||||||
|
{
|
||||||
|
public Repository()
|
||||||
|
{
|
||||||
|
base.Context = DbScoped.SugarScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<IocConfig>() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
namespace VideoAnalysisCore.Enum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// redis 频道
|
||||||
|
/// </summary>
|
||||||
|
public enum RedisChannelEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载文件
|
||||||
|
/// </summary>
|
||||||
|
DownloadFile = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// 分离音频
|
||||||
|
/// </summary>
|
||||||
|
SeparateAudio = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// 解析字幕
|
||||||
|
/// </summary>
|
||||||
|
ParsingCaptions = 3,
|
||||||
|
/// <summary>
|
||||||
|
/// 解析说话人
|
||||||
|
/// </summary>
|
||||||
|
ParsingSpeaker = 4,
|
||||||
|
/// <summary>
|
||||||
|
/// Chat模型分析
|
||||||
|
/// </summary>
|
||||||
|
ChatModelAnalysis = 5,
|
||||||
|
/// <summary>
|
||||||
|
/// 回调三方系统
|
||||||
|
/// </summary>
|
||||||
|
CallBackSystem = 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
namespace VideoAnalysisCore.Enum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 时间线类型
|
||||||
|
/// </summary>
|
||||||
|
public enum TimeBaseTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 教师讲授
|
||||||
|
/// </summary>
|
||||||
|
教师讲授 = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// 互动交流
|
||||||
|
/// </summary>
|
||||||
|
互动交流 = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// 独立学习
|
||||||
|
/// </summary>
|
||||||
|
独立学习 = 3,
|
||||||
|
/// <summary>
|
||||||
|
/// 小组合作
|
||||||
|
/// </summary>
|
||||||
|
小组合作 = 4,
|
||||||
|
/// <summary>
|
||||||
|
/// 随堂练习
|
||||||
|
/// </summary>
|
||||||
|
随堂练习
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 课堂评分标准
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("coursegradingcriteria")]
|
||||||
|
public class CourseGradingCriteria
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
|
||||||
|
[DisplayName( "编号"), Required]
|
||||||
|
public long Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 标准提问词
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(Length = 100)]
|
||||||
|
[DisplayName( "标准提问词"), Required]
|
||||||
|
public string NamePrompt { get; set; }=string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 优点展示词
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(Length = 100)]
|
||||||
|
[DisplayName( "优点展示词"), Required]
|
||||||
|
public string Advantage { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 缺点展示词
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(Length = 100)]
|
||||||
|
[DisplayName( "缺点展示词"), Required]
|
||||||
|
public string Flaw { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 改进意见
|
||||||
|
/// </summary>
|
||||||
|
[DisplayName( "改进意见")]
|
||||||
|
[SugarColumn(Length = 100), Required]
|
||||||
|
public string ImprovedMethods { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
using VideoAnalysisCore.Enum;
|
||||||
|
|
||||||
|
namespace VideoAnalysisCore.Model.Dto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 时间线
|
||||||
|
/// </summary>
|
||||||
|
public class TimeBase
|
||||||
|
{
|
||||||
|
public TimeBase()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 开始时间
|
||||||
|
/// </summary>
|
||||||
|
public double Start { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 结束时间
|
||||||
|
/// </summary>
|
||||||
|
public double End { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 时间段 类型
|
||||||
|
/// </summary>
|
||||||
|
public TimeBaseTypeEnum Type { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
public class TotalCaptionsDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 拼接说话人后的最终字幕
|
||||||
|
/// </summary>
|
||||||
|
public string Captions { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 教师发言时间
|
||||||
|
/// <para>秒</para>
|
||||||
|
/// </summary>
|
||||||
|
public decimal TeacherSpeaking { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 学生发言时间
|
||||||
|
/// <para>秒</para>
|
||||||
|
/// </summary>
|
||||||
|
public decimal StudentSpeaking { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 视频时间轴
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<TimeBase>? TimeBase { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 视频任务模型
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("videotask")]
|
||||||
|
public class VideoTask
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务id
|
||||||
|
/// <para>视频音频文件地址都使用taskID能获取</para>
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
|
||||||
|
public long Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 媒体路径
|
||||||
|
/// </summary>
|
||||||
|
public string MediaUrl { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 下载后本地媒体目录
|
||||||
|
/// </summary>
|
||||||
|
public string LocalMediaPath { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 上一次执行的枚举
|
||||||
|
/// </summary>
|
||||||
|
public RedisChannelEnum LastEnum { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// ApiKey
|
||||||
|
/// </summary>
|
||||||
|
public string ApiToken { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 请求来自哪个ip地址
|
||||||
|
/// </summary>
|
||||||
|
public string ComeFrom { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义值 任务完成后附带通知
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(Length = 500)]
|
||||||
|
public string Tag { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
///回调Api地址
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(Length = 500)]
|
||||||
|
public string CallBackUrl { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 字幕缓存
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDataType = "longtext", IsNullable = true)]
|
||||||
|
public SegmentData[]? Captions { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 说话人日志解析缓存
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDataType = "longtext", IsNullable = true)]
|
||||||
|
public OfflineSpeakerRes[]? Speaker { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Chat模型分析缓存
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDataType = "longtext", IsNullable = true)]
|
||||||
|
public object[]? ChatAnalysis { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 消耗token
|
||||||
|
/// </summary>
|
||||||
|
public int TotalTokens { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||||
|
/// <summary>
|
||||||
|
/// 开始时间轴
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDataType = "varchar", Length =255)]
|
||||||
|
public Dictionary<RedisChannelEnum, DateTime> StartTime { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="AICore\_Static\ffmpeg.exe" />
|
||||||
|
<None Remove="AICore\_Static\ggml-base.bin" />
|
||||||
|
<None Remove="AICore\_Static\ggml-large-v3.bin" />
|
||||||
|
<None Remove="AICore\_Static\ggml-medium.bin" />
|
||||||
|
<None Remove="AICore\_Static\ggml-small.bin" />
|
||||||
|
<None Remove="AICore\_Static\sherpa-onnx-pyannote-segmentation-3-0\model.int8.onnx" />
|
||||||
|
<None Remove="AICore\_Static\sherpa-onnx-pyannote-segmentation-3-0\model.onnx" />
|
||||||
|
<None Remove="AICore\_Static\wespeaker\wespeaker_zh_cnceleb_resnet34_LM.onnx" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="AICore\_Static\ffmpeg.exe">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="AICore\_Static\ggml-base.bin">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="AICore\_Static\ggml-large-v3.bin">
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="AICore\_Static\ggml-medium.bin">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="AICore\_Static\ggml-small.bin">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="AICore\_Static\sherpa-onnx-pyannote-segmentation-3-0\model.int8.onnx">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="AICore\_Static\sherpa-onnx-pyannote-segmentation-3-0\model.onnx">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="AICore\_Static\wespeaker\wespeaker_zh_cnceleb_resnet34_LM.onnx">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FreeRedis" Version="1.3.2" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="org.k2fsa.sherpa.onnx" Version="1.10.28" />
|
||||||
|
<PackageReference Include="SqlSugar.IOC" Version="2.0.0" />
|
||||||
|
<PackageReference Include="SqlSugarCore" Version="5.1.4.170" />
|
||||||
|
<PackageReference Include="Whisper.net" Version="1.5.0" />
|
||||||
|
<PackageReference Include="Whisper.net.Runtime" Version="1.5.0" />
|
||||||
|
<PackageReference Include="xFFmpeg.NET" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Loading…
Reference in New Issue