156 lines
7.7 KiB
C#
156 lines
7.7 KiB
C#
using System.Text.RegularExpressions;
|
||
using dy.net.model.dto;
|
||
using dy.net.model.entity;
|
||
using dy.net.model.response;
|
||
using dy.net.utils;
|
||
|
||
namespace dy.net.storage
|
||
{
|
||
public sealed class StorageMigrationPathPlan
|
||
{
|
||
public string VideoPath { get; set; }
|
||
public string CoverPath { get; set; }
|
||
public string AvatarPath { get; set; }
|
||
public string DirectoryPath => StoragePath.DirectoryName(VideoPath);
|
||
}
|
||
|
||
public static class StorageMigrationPathPolicy
|
||
{
|
||
private static readonly Regex EpisodeName = new("^S(?<season>\\d{2})E(?<episode>\\d{2,})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||
|
||
public static StorageMigrationPathPlan Build(
|
||
DouyinVideo video,
|
||
DouyinCookie cookie,
|
||
DouyinCollectCate category,
|
||
DouyinFollowed followed,
|
||
AppConfig config,
|
||
int episodeNumber = 1)
|
||
{
|
||
if (video == null) throw new ArgumentNullException(nameof(video));
|
||
if (cookie == null) throw new InvalidOperationException($"视频 {video.AwemeId} 缺少所属账号");
|
||
|
||
var root = GetRemoteRoot(cookie, video.ViedoType);
|
||
if (string.IsNullOrWhiteSpace(root)) throw new InvalidOperationException($"账号 {cookie.UserName} 未配置 {video.ViedoType} 的远端目标路径");
|
||
|
||
var extension = NormalizeExtension(Path.GetExtension(video.VideoSavePath));
|
||
var awemeId = string.IsNullOrWhiteSpace(video.AwemeId) ? video.Id : video.AwemeId;
|
||
var titleFolder = DouyinFileNameHelper.SanitizeLinuxFileName(video.VideoTitle, awemeId, true);
|
||
var authorFolder = DouyinFileNameHelper.SanitizeLinuxFileName(video.Author, video.AuthorId ?? "未知博主", true);
|
||
string directory;
|
||
string fileName;
|
||
|
||
switch (video.ViedoType)
|
||
{
|
||
case VideoTypeEnum.dy_custom_collect:
|
||
var customCategory = DouyinFileNameHelper.SanitizeLinuxFileName(category?.SaveFolder, category?.Name ?? "未分类", true);
|
||
directory = StoragePath.CombineRemote(root, customCategory, titleFolder + "_" + awemeId);
|
||
fileName = awemeId + extension;
|
||
break;
|
||
case VideoTypeEnum.dy_mix:
|
||
case VideoTypeEnum.dy_series:
|
||
var seriesFolder = DouyinFileNameHelper.SanitizeLinuxFileName(category?.SaveFolder, category?.Name ?? video.CateXId ?? "未分类", true);
|
||
directory = StoragePath.CombineRemote(root, seriesFolder);
|
||
var oldEpisode = EpisodeName.Match(Path.GetFileNameWithoutExtension(video.VideoSavePath) ?? string.Empty);
|
||
fileName = oldEpisode.Success
|
||
? oldEpisode.Value.ToUpperInvariant() + extension
|
||
: $"S01E{Math.Max(1, episodeNumber):D2}{extension}";
|
||
break;
|
||
case VideoTypeEnum.dy_follows:
|
||
var followedFolder = string.IsNullOrWhiteSpace(followed?.SavePath) ? authorFolder : followed.SavePath;
|
||
directory = StoragePath.CombineRemote(root, followedFolder, titleFolder);
|
||
fileName = BuildFollowedFileName(video, config, extension, awemeId);
|
||
break;
|
||
case VideoTypeEnum.dy_favorite:
|
||
case VideoTypeEnum.dy_collects:
|
||
default:
|
||
directory = StoragePath.CombineRemote(root, authorFolder, titleFolder + "_" + awemeId);
|
||
fileName = awemeId + extension;
|
||
break;
|
||
}
|
||
|
||
var videoPath = StoragePath.CombineRemote(directory, fileName);
|
||
var coverName = video.ViedoType is VideoTypeEnum.dy_mix or VideoTypeEnum.dy_series
|
||
? "poster.jpg"
|
||
: Path.GetFileNameWithoutExtension(fileName) + "-poster.jpg";
|
||
return new StorageMigrationPathPlan
|
||
{
|
||
VideoPath = videoPath,
|
||
CoverPath = StoragePath.CombineRemote(directory, coverName),
|
||
AvatarPath = string.IsNullOrWhiteSpace(video.AuthorAvatar) && string.IsNullOrWhiteSpace(video.AuthorAvatarUrl)
|
||
? string.Empty
|
||
: StoragePath.CombineRemote(root, "author", (string.IsNullOrWhiteSpace(video.AuthorId) ? awemeId : video.AuthorId) + ".jpg")
|
||
};
|
||
}
|
||
|
||
public static StorageMigrationPathPlan Build(
|
||
Aweme item,
|
||
VideoTypeEnum type,
|
||
DouyinCookie cookie,
|
||
DouyinCollectCate category,
|
||
DouyinFollowed followed,
|
||
AppConfig config)
|
||
{
|
||
if (item == null) throw new ArgumentNullException(nameof(item));
|
||
var bitrate = item.Video?.BitRate?.FirstOrDefault();
|
||
var format = string.IsNullOrWhiteSpace(bitrate?.Format) ? "mp4" : bitrate.Format.TrimStart('.');
|
||
var createdAt = DateTimeUtil.Convert10BitTimestamp(item.CreateTime);
|
||
var title = string.IsNullOrWhiteSpace(item.Desc)
|
||
? $"{item.Author?.Nickname}-{item.CreateTime}"
|
||
: item.Desc;
|
||
var video = new DouyinVideo
|
||
{
|
||
ViedoType = type,
|
||
AwemeId = item.AwemeId,
|
||
VideoTitle = title,
|
||
VideoSavePath = $"{item.AwemeId}.{format}",
|
||
Author = item.Author?.Nickname,
|
||
AuthorId = item.Author?.Uid,
|
||
AuthorAvatarUrl = item.Author?.AvatarLarger?.UrlList?.FirstOrDefault()
|
||
?? item.Author?.AvatarThumb?.UrlList?.FirstOrDefault(),
|
||
CreateTime = createdAt,
|
||
FileHash = bitrate?.PlayAddr?.FileHash,
|
||
Resolution = bitrate?.PlayAddr == null ? string.Empty : $"{bitrate.PlayAddr.Width}×{bitrate.PlayAddr.Height}"
|
||
};
|
||
var episode = item.MixInfo?.Statis?.CurrentEpisode ?? 1;
|
||
return Build(video, cookie, category, followed, config, Math.Max(1, episode));
|
||
}
|
||
|
||
public static string GetRemoteRoot(DouyinCookie cookie, VideoTypeEnum type) => type switch
|
||
{
|
||
VideoTypeEnum.dy_favorite => cookie.WebDavFavoritePath,
|
||
VideoTypeEnum.dy_follows => cookie.WebDavFollowPath,
|
||
VideoTypeEnum.dy_mix => cookie.WebDavMixPath,
|
||
VideoTypeEnum.dy_series => cookie.WebDavSeriesPath,
|
||
_ => cookie.WebDavCollectPath
|
||
};
|
||
|
||
public static IReadOnlyList<string> GetLocalRoots(DouyinCookie cookie)
|
||
{
|
||
if (cookie == null) return Array.Empty<string>();
|
||
return new[] { cookie.SavePath, cookie.FavSavePath, cookie.UpSavePath, cookie.MixPath, cookie.SeriesPath }
|
||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||
.Select(Path.GetFullPath)
|
||
.Distinct(StringComparer.Ordinal)
|
||
.ToArray();
|
||
}
|
||
|
||
private static string BuildFollowedFileName(DouyinVideo video, AppConfig config, string extension, string awemeId)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(config?.FullFollowedTitleTemplate)) return awemeId + extension;
|
||
var generated = VideoTitleGenerator.Generate(config.FullFollowedTitleTemplate, new VideoTitleDataTemplate
|
||
{
|
||
Id = awemeId,
|
||
ReleaseTime = video.CreateTime,
|
||
VideoTitle = video.VideoTitle,
|
||
Author = video.Author,
|
||
FileHash = video.FileHash,
|
||
Resolution = video.Resolution
|
||
});
|
||
generated = DouyinFileNameHelper.SanitizeLinuxFileName(generated, awemeId);
|
||
return generated + extension;
|
||
}
|
||
|
||
private static string NormalizeExtension(string extension) => string.IsNullOrWhiteSpace(extension) ? ".mp4" : extension.ToLowerInvariant();
|
||
}
|
||
}
|