1、nfo文件优化,修复演员信息(视频作者)
2、图文视频,因版权原因无法下载的音频,系统配置新增默认音频上传入口,上传成功后,后面在遇到无法下载音频时,将用该音频进行图文视频合成的音频数据 3、增加动态视频合成配置。 4、容器重启后,手机端会连续跳转登录页很多次的bug修复 5、其他优化
This commit is contained in:
@@ -99,5 +99,20 @@ namespace dy.net.utils
|
||||
// 忽略文化差异,仅按字符编码匹配
|
||||
return Regex.IsMatch(input, pattern, RegexOptions.None);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 去掉动态视频001_002
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static string RemoveNumberSuffix(string fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
return fileName;
|
||||
// 核心正则:只匹配「_+数字」且后面紧跟.的情况
|
||||
var pattern = @"_\d+(?=\.)";
|
||||
return Regex.Replace(fileName, pattern, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
-1
@@ -229,7 +229,113 @@ namespace dy.net.utils
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 合并多个视频文件为一个MP4视频
|
||||
/// </summary>
|
||||
/// <param name="videoFilePaths">待合并的视频路径列表(按合并顺序排列)</param>
|
||||
/// <param name="savePath">输出视频的保存路径</param>
|
||||
/// <param name="width">输出视频宽度(自动修正为偶数)</param>
|
||||
/// <param name="height">输出视频高度(自动修正为偶数)</param>
|
||||
/// <param name="progress">进度回调</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>输出视频路径</returns>
|
||||
public async Task<string> MergeMultipleVideosAsync(
|
||||
List<string> videoFilePaths,
|
||||
string savePath,
|
||||
int width = 1080,
|
||||
int height = 1920,
|
||||
IProgress<double> progress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 输入验证
|
||||
if (videoFilePaths == null || !videoFilePaths.Any())
|
||||
throw new ArgumentException("视频路径列表不能为空。", nameof(videoFilePaths));
|
||||
|
||||
foreach (var videoPath in videoFilePaths)
|
||||
{
|
||||
if (!File.Exists(videoPath))
|
||||
throw new FileNotFoundException("视频文件未找到。", videoPath);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(savePath))
|
||||
throw new ArgumentNullException(nameof(savePath));
|
||||
|
||||
// 自动修正分辨率为偶数(H264编码要求)
|
||||
if (width % 2 != 0) width++;
|
||||
if (height % 2 != 0) height++;
|
||||
|
||||
// 步骤1:创建临时文件列表(FFmpeg合并视频需要先生成文件列表)
|
||||
string tempListFile = Path.Combine(AppContext.BaseDirectory, "temp", $"{Guid.NewGuid()}.txt");
|
||||
var tempDir = Path.GetDirectoryName(tempListFile);
|
||||
if (!Directory.Exists(tempDir))
|
||||
{
|
||||
Directory.CreateDirectory(tempDir);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 生成FFmpeg识别的文件列表(格式:file '绝对路径')
|
||||
var fileListContent = new StringBuilder();
|
||||
foreach (var videoPath in videoFilePaths)
|
||||
{
|
||||
// 处理路径中的特殊字符,确保跨平台兼容
|
||||
string escapedPath = videoPath.Replace("\\", "/").Replace("'", "\\'");
|
||||
fileListContent.AppendLine($"file '{escapedPath}'");
|
||||
}
|
||||
File.WriteAllText(tempListFile, fileListContent.ToString(), Encoding.UTF8);
|
||||
|
||||
// 步骤2:构建FFmpeg合并参数
|
||||
var arguments = new List<string>
|
||||
{
|
||||
"-y", // 覆盖输出文件
|
||||
"-f", "concat", // 指定合并格式
|
||||
"-safe", "0", // 允许访问绝对路径
|
||||
"-i", tempListFile, // 输入文件列表
|
||||
|
||||
// 视频编码参数(复用现有类的编码配置,保证输出格式统一)
|
||||
"-c:v", VideoCodec,
|
||||
"-preset", VideoPreset,
|
||||
"-crf", $"{VideoCrf}",
|
||||
"-s", $"{width}x{height}", // 统一输出分辨率
|
||||
"-pix_fmt", "yuv420p", // 兼容所有播放器
|
||||
"-profile:v", "main",
|
||||
|
||||
// 音频编码参数
|
||||
"-c:a", AudioCodec,
|
||||
"-b:a", AudioBitrate,
|
||||
"-ac", "2", // 立体声
|
||||
"-ar", "44100", // 标准采样率
|
||||
|
||||
// 封装优化
|
||||
"-f", "mp4",
|
||||
"-movflags", "+faststart", // 适合网络播放
|
||||
|
||||
// 输出路径
|
||||
savePath
|
||||
};
|
||||
|
||||
// 执行FFmpeg合并命令
|
||||
await ExecuteFFmpegAsync(arguments, progress, cancellationToken);
|
||||
|
||||
// 验证输出文件
|
||||
if (File.Exists(savePath))
|
||||
{
|
||||
return savePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("视频合并失败,未生成输出文件。");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 清理临时文件
|
||||
if (File.Exists(tempListFile))
|
||||
{
|
||||
File.Delete(tempListFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行FFmpeg命令
|
||||
|
||||
+74
-12
@@ -1,4 +1,6 @@
|
||||
using dy.net.dto;
|
||||
using dy.net.model;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -10,7 +12,69 @@ namespace dy.net.utils
|
||||
public class NfoFileGenerator
|
||||
{
|
||||
|
||||
public static void GenerateNfoFile(DouyinVideoNfo videoInfo, string filePath)
|
||||
|
||||
/// <summary>
|
||||
/// 生成NFO文件
|
||||
/// NFO文件包含视频的元数据信息,如标题、作者、封面等
|
||||
/// </summary>
|
||||
/// <param name="video">视频信息</param>
|
||||
/// <returns>一个表示异步操作的任务</returns>
|
||||
public static void GenerateVideoNfoFile(DouyinVideo video)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
string videoDirectory = Path.GetDirectoryName(video.VideoSavePath); // 视频所在目录
|
||||
string videoFileNameWithoutExt = Path.GetFileNameWithoutExtension(video.VideoSavePath); // 无扩展名的文件名
|
||||
string nfoFullPath = Path.Combine(videoDirectory, $"{videoFileNameWithoutExt}.nfo"); // NFO文件完整路径
|
||||
string postFullPath = Path.Combine(videoDirectory, "poster.jpg"); // NFO文件完整路径
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(video.AuthorAvatar))
|
||||
{
|
||||
if (!video.OnlyImgOrOnlyMp3)
|
||||
{
|
||||
//说明是视频
|
||||
//复制作者头像到当前目录 并改名为跟nfo里面的作者相同的名字
|
||||
if (File.Exists(video.AuthorAvatar))
|
||||
{
|
||||
var fileExt = Path.GetExtension(video.AuthorAvatar);
|
||||
|
||||
var nfoActorFullPath = Path.Combine(videoDirectory, $"{video.Author}{fileExt}");
|
||||
|
||||
if (File.Exists(nfoActorFullPath))
|
||||
{
|
||||
File.Delete(nfoActorFullPath);
|
||||
}
|
||||
// 执行复制(CopyTo支持覆盖,但先删除更可控)
|
||||
File.Copy(video.AuthorAvatar, nfoActorFullPath, overwrite: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GenerateNfoFile(new DouyinVideoNfo
|
||||
{
|
||||
Actors = new List<Actor>
|
||||
{
|
||||
new() {
|
||||
Name = video.Author,
|
||||
Role = "主演",
|
||||
}
|
||||
},
|
||||
Author = video.Author,
|
||||
Poster = postFullPath,
|
||||
Title = video.VideoTitle,
|
||||
Thumbnail = postFullPath,// 使用poster作为缩略图
|
||||
ReleaseDate = video.CreateTime,
|
||||
Genres = new List<string> { video.Tag1, video.Tag2, video.Tag3 }.Where(t => !string.IsNullOrWhiteSpace(t)).ToList()
|
||||
}, nfoFullPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "{f}nfo文件生成异常", video.VideoTitle);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateNfoFile(DouyinVideoNfo videoInfo, string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -22,6 +86,10 @@ namespace dy.net.utils
|
||||
|
||||
// 创建根元素
|
||||
XElement root = new XElement("movie");
|
||||
root.Add(new XElement("outline"));
|
||||
root.Add(new XElement("lockdata", true));
|
||||
root.Add(new XElement("director", videoInfo.Author));
|
||||
root.Add(new XElement("plot", $"<![CDATA[{videoInfo.Title}]]>"));
|
||||
|
||||
// 添加视频信息(先清理无效字符)
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Title))
|
||||
@@ -32,7 +100,10 @@ namespace dy.net.utils
|
||||
|
||||
// 发布时间(无需清理,因为是格式化的日期字符串)
|
||||
if (videoInfo.ReleaseDate.HasValue)
|
||||
{
|
||||
root.Add(new XElement("releasedate", videoInfo.ReleaseDate.Value.ToString("yyyy-MM-dd")));
|
||||
root.Add(new XElement("premiered", videoInfo.ReleaseDate.Value.ToString("yyyy-MM-dd")));
|
||||
}
|
||||
|
||||
// 分类标签(清理每个标签)
|
||||
if (videoInfo.Genres != null && videoInfo.Genres.Any())
|
||||
@@ -47,7 +118,6 @@ namespace dy.net.utils
|
||||
// --- 新增:处理演员信息 ---
|
||||
if (videoInfo.Actors != null && videoInfo.Actors.Any())
|
||||
{
|
||||
var actorsElement = new XElement("actors");
|
||||
foreach (var actor in videoInfo.Actors)
|
||||
{
|
||||
// 至少需要演员姓名
|
||||
@@ -55,22 +125,14 @@ namespace dy.net.utils
|
||||
{
|
||||
var actorElement = new XElement("actor");
|
||||
actorElement.Add(new XElement("name", CleanInvalidXmlChars(actor.Name)));
|
||||
|
||||
// 可选的角色和头像
|
||||
if (!string.IsNullOrWhiteSpace(actor.Role))
|
||||
actorElement.Add(new XElement("role", CleanInvalidXmlChars(actor.Role)));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(actor.Thumb))
|
||||
actorElement.Add(new XElement("thumb", CleanInvalidXmlChars(actor.Thumb)));
|
||||
actorElement.Add(new XElement("tmdbid", "3141592610000"));//写死一个反正不存在的ID,防止被媒体管理软件误认
|
||||
|
||||
actorsElement.Add(actorElement);
|
||||
root.Add(actorElement);
|
||||
}
|
||||
}
|
||||
// 将整个 <actors> 节点添加到根节点
|
||||
if (actorsElement.HasElements)
|
||||
{
|
||||
root.Add(actorsElement);
|
||||
}
|
||||
}
|
||||
// --- 演员信息处理结束 ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user