Learn.VideoAnalysis/VideoAnalysisCore/Common/Expand/TidySlideExpand.cs

239 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AlibabaCloud.SDK.Vod20170321;
using AlibabaCloud.SDK.Vod20170321.Models;
using Aliyun.OSS;
using Microsoft.Extensions.DependencyInjection;
using SqlSugar.IOC;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using VideoAnalysisCore.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Security.Cryptography;
using Yitter.IdGenerator;
namespace VideoAnalysisCore.Common.Expand
{
public static class TidySlideExpand
{
public static void AddTidySlideExpand(this IServiceCollection services)
{
services.AddSingleton<TidySlideHandle>();
services.AddTransient<Repository<TidySlideTaskResult>>();
}
}
public class TidySlideHandle
{
private readonly Client _vodClient;
private readonly Repository<VideoTask> _videoTaskDB;
private readonly Repository<TidySlideTaskResult> _tidySlideTaskResultDB;
private readonly RedisManager _redisManager;
private readonly OssClient _ossClient; // 使用系统统一注入的 OSS Client
private readonly TidySlideWorkflowManager _workflowManager; // 注入工作流管理器
public TidySlideHandle(Client vodClient, Repository<VideoTask> videoTaskDB, Repository<TidySlideTaskResult> tidySlideTaskResultDB, RedisManager redisManager, OssClient ossClient, TidySlideWorkflowManager workflowManager)
{
_vodClient = vodClient;
_videoTaskDB = videoTaskDB;
_tidySlideTaskResultDB = tidySlideTaskResultDB;
_redisManager = redisManager;
_ossClient = ossClient;
_workflowManager = workflowManager;
}
public async Task RunAsync(string task)
{
var taskId = long.Parse(task);
var localPath = task.LocalPath();
var m3u8Path = Path.Combine(localPath, "out.m3u8");
if (!File.Exists(m3u8Path))
{
await _workflowManager.AddTaskLog(task, "未找到 m3u8 文件,无法进行切片上传");
throw new FileNotFoundException("M3U8文件未找到", m3u8Path);
}
// 获取所有切片文件 (out*.ts)
var tsFiles = Directory.GetFiles(localPath, "out*.ts");
if (tsFiles.Length == 0)
{
await _workflowManager.AddTaskLog(task, "未找到 ts 切片文件");
throw new FileNotFoundException("TS切片文件未找到");
}
var title = $"Task_{taskId}_{DateTime.Now:yyyyMMddHHmmss}";
await _workflowManager.AddTaskLog(task, "正在获取VOD上传凭证...");
// 1. 获取上传凭证和地址
// 注意VOD上传m3u8时FileName必须以 .m3u8 结尾
var request = new CreateUploadVideoRequest
{
Title = title,
FileName = "out.m3u8", // 必须是 m3u8 文件名
Description = "视频分析_PPT清洗 ",
Tags = "PPT清洗", // 可选:设置标签
CateId = 1000709090,
};
var response = await _vodClient.CreateUploadVideoAsync(request);
if (response.Body == null || string.IsNullOrEmpty(response.Body.UploadAddress) || string.IsNullOrEmpty(response.Body.UploadAuth))
{
throw new Exception($"获取上传凭证失败: RequestId={response.Body?.RequestId}");
}
var videoId = response.Body.VideoId;
var uploadAddressStr = response.Body.UploadAddress;
var uploadAuthStr = response.Body.UploadAuth;
await _workflowManager.AddTaskLog(task, $"获取凭证成功VideoId: {videoId}");
// 2. 解析凭证 (Base64 -> JSON)
var addressJson = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(uploadAddressStr)));
var authJson = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(uploadAuthStr)));
var endpoint = addressJson["Endpoint"]?.ToString();
var bucket = addressJson["Bucket"]?.ToString();
// 这是 VOD 分配的 m3u8 存储路径,例如 "sv/243d.../out.m3u8"
var objectName = addressJson["FileName"]?.ToString();
var accessKeyId = authJson["AccessKeyId"]?.ToString();
var accessKeySecret = authJson["AccessKeySecret"]?.ToString();
var securityToken = authJson["SecurityToken"]?.ToString();
if (string.IsNullOrEmpty(endpoint) || string.IsNullOrEmpty(bucket) || string.IsNullOrEmpty(objectName))
throw new Exception("解析上传地址失败");
// 修正 Endpoint 格式 (如果缺少协议头)
if (!endpoint.StartsWith("http"))
endpoint = "https://" + endpoint;
// 3. 构造 OSS 客户端 (使用临时凭证)
var ossClient = new OssClient(endpoint, accessKeyId, accessKeySecret, securityToken);
// 4. 确定 OSS 目录前缀
// VOD 返回的 objectName 是完整的文件路径,我们需要提取目录部分来存放 .ts 文件
// 例如: objectName = "sv/5903240e-19544975a64/out.m3u8"
// 则 prefix = "sv/5903240e-19544975a64/"
var ossPrefix = objectName.Substring(0, objectName.LastIndexOf('/') + 1);
await _workflowManager.AddTaskLog(task, $"开始上传文件到 VOD OSS (Bucket: {bucket}, Prefix: {ossPrefix})...");
try
{
// A. 上传所有 TS 切片
await _workflowManager.AddTaskLog(task, $"开始上传 TS 切片 (共 {tsFiles.Length} 个)...");
for (int i = 0; i < tsFiles.Length; i++)
{
var tsFile = tsFiles[i];
var fileName = Path.GetFileName(tsFile);
var tsObjectKey = ossPrefix + fileName;
var tsRetryCount = 0;
var tsMaxRetries = 10;
while (true)
{
try
{
using var fs = File.OpenRead(tsFile);
ossClient.PutObject(bucket, tsObjectKey, fs);
break; // Upload successful, break retry loop
}
catch (Exception ex)
{
tsRetryCount++;
if (tsRetryCount >= tsMaxRetries)
{
await _workflowManager.AddTaskLog(task, $"上传 TS 切片 {fileName} 失败,已重试 {tsMaxRetries} 次: {ex.Message}");
throw; // Re-throw exception to stop the process
}
else
{
await _workflowManager.AddTaskLog(task, $"上传 TS 切片 {fileName} 失败 (第 {tsRetryCount} 次重试): {ex.Message}1秒后重试...");
await Task.Delay(1000);
}
}
}
// 更新上传进度
if (i % 5 == 0) // 每5个文件更新一次进度
{
var progress = Math.Round((double)i / tsFiles.Length * 100, 1);
_workflowManager.SetTaskProgress(taskId, $"Upload->{progress}%");
}
}
// B. 上传 m3u8 索引文件
// 必须使用 VOD 指定的 objectName
await _workflowManager.AddTaskLog(task, "开始上传 m3u8 索引文件...");
var m3u8RetryCount = 0;
var m3u8MaxRetries = 3;
while (true)
{
try
{
using (var fs = File.OpenRead(m3u8Path))
{
ossClient.PutObject(bucket, objectName, fs);
}
break; // Upload successful
}
catch (Exception ex)
{
m3u8RetryCount++;
if (m3u8RetryCount >= m3u8MaxRetries)
{
await _workflowManager.AddTaskLog(task, $"上传 m3u8 文件失败,已重试 {m3u8MaxRetries} 次: {ex.Message}");
throw;
}
else
{
await _workflowManager.AddTaskLog(task, $"上传 m3u8 文件失败 (第 {m3u8RetryCount} 次重试): {ex.Message}1秒后重试...");
await Task.Delay(1000);
}
}
}
await _workflowManager.AddTaskLog(task, "上传成功");
// 5. 更新数据库
// 对于 VOD 托管视频,我们主要存储 VideoId (TagId),播放地址通常由前端调用 VOD 接口获取
// 或者我们可以尝试获取播放地址存入 MediaUrl
await _tidySlideTaskResultDB.InsertAsync(new TidySlideTaskResult()
{
Id = YitIdHelper.NextId(),
VideoTaskId = long.Parse(task),
VideoId = videoId,
});
}
catch (Exception ex)
{
await _workflowManager.AddTaskLog(task, $"上传 VOD OSS 异常: {ex.Message}");
// 如果已获取 VideoId 但上传失败,则删除 VOD 记录
if (!string.IsNullOrEmpty(videoId))
{
try
{
await _workflowManager.AddTaskLog(task, $"正在回滚删除 VOD 视频记录 (VideoId: {videoId})...");
var deleteRequest = new DeleteVideoRequest { VideoIds = videoId };
await _vodClient.DeleteVideoAsync(deleteRequest);
await _workflowManager.AddTaskLog(task, "VOD 视频记录删除成功");
}
catch (Exception deleteEx)
{
await _workflowManager.AddTaskLog(task, $"回滚删除 VOD 视频记录失败: {deleteEx.Message}");
}
}
throw;
}
}
}
}