Files
douyin/utils/NfoFileGenerator.cs
T
jianzhichu 8cef13c926 1、增加图片视频
2、优化代码结构
3、up主下载分类逻辑可配置
2025-11-23 00:46:41 +08:00

155 lines
6.0 KiB
C#

using dy.net.dto;
using System.Text;
using System.Xml.Linq;
namespace dy.net.utils
{
/// <summary>
/// NFO文件生成器
/// </summary>
public class NfoFileGenerator
{
public static void GenerateNfoFile(DouyinVideoNfo videoInfo, string filePath)
{
try
{
if (videoInfo == null)
throw new ArgumentNullException(nameof(videoInfo));
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("文件路径不能为空", nameof(filePath));
// 创建根元素
XElement root = new XElement("movie");
// 添加视频信息(先清理无效字符)
if (!string.IsNullOrWhiteSpace(videoInfo.Title))
root.Add(new XElement("title", CleanInvalidXmlChars(videoInfo.Title)));
if (!string.IsNullOrWhiteSpace(videoInfo.Author))
root.Add(new XElement("author", CleanInvalidXmlChars(videoInfo.Author)));
// 发布时间(无需清理,因为是格式化的日期字符串)
if (videoInfo.ReleaseDate.HasValue)
root.Add(new XElement("releasedate", videoInfo.ReleaseDate.Value.ToString("yyyy-MM-dd")));
// 分类标签(清理每个标签)
if (videoInfo.Genres != null && videoInfo.Genres.Any())
{
foreach (var genre in videoInfo.Genres)
{
if (!string.IsNullOrWhiteSpace(genre))
root.Add(new XElement("genre", CleanInvalidXmlChars(genre)));
}
}
// --- 新增:处理演员信息 ---
if (videoInfo.Actors != null && videoInfo.Actors.Any())
{
var actorsElement = new XElement("actors");
foreach (var actor in videoInfo.Actors)
{
// 至少需要演员姓名
if (!string.IsNullOrWhiteSpace(actor.Name))
{
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)));
actorsElement.Add(actorElement);
}
}
// 将整个 <actors> 节点添加到根节点
if (actorsElement.HasElements)
{
root.Add(actorsElement);
}
}
// --- 演员信息处理结束 ---
if (!string.IsNullOrWhiteSpace(videoInfo.Thumbnail))
root.Add(new XElement("thumb", new XAttribute("aspect", "poster"), CleanInvalidXmlChars(videoInfo.Thumbnail)));
if (!string.IsNullOrWhiteSpace(videoInfo.Poster))
root.Add(new XElement("fanart",
new XElement("thumb", CleanInvalidXmlChars(videoInfo.Poster))
));
// 创建XDocument并保存
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
root
);
// 确保目录存在
string directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
doc.Save(filePath);
}
catch (Exception ex)
{
Serilog.Log.Error(ex, $"生成 {videoInfo?.Title ?? "未知视频"} 的 NFO 文件时出错。");
}
}
/// <summary>
/// 清理字符串中无效的XML字符(包括无效的UTF-16代理对)
/// </summary>
private static string CleanInvalidXmlChars(string input)
{
if (string.IsNullOrEmpty(input))
return input;
StringBuilder cleaned = new StringBuilder();
for (int i = 0; i < input.Length; i++)
{
char c = input[i];
// XML 1.0 允许的字符范围:#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
if (IsValidXmlChar(c))
{
// 检查是否是高代理项,如果是则需要配对低代理项
if (char.IsHighSurrogate(c))
{
// 确保不越界,且下一个字符是低代理项
if (i + 1 < input.Length && char.IsLowSurrogate(input[i + 1]))
{
cleaned.Append(c); // 高代理项
cleaned.Append(input[i + 1]); // 低代理项
i++; // 跳过已处理的低代理项
}
// 否则,高代理项无效,跳过
}
else
{
cleaned.Append(c);
}
}
// 无效字符直接跳过
}
return cleaned.ToString();
}
/// <summary>
/// 判断字符是否为XML允许的有效字符
/// </summary>
private static bool IsValidXmlChar(char c)
{
return (c == 0x9) || (c == 0xA) || (c == 0xD)
|| (c >= 0x20 && c <= 0xD7FF)
|| (c >= 0xE000 && c <= 0xFFFD)
|| (c >= 0x10000 && c <= 0x10FFFF);
}
}
}