feat: add fnOS packaging, storage workflows and release pipeline
This commit is contained in:
@@ -102,12 +102,14 @@ namespace dy.net.service
|
||||
/// 视频合成(优化后:解决 FFmpeg 进程冲突,支持容错重试)
|
||||
/// </summary>
|
||||
public async Task<bool> MergeToVideo(string cookie, string rootPath, MediaMergeRequest request,
|
||||
string outputVideoPath, string fileNamefolder, bool mergeImg2Viedo, bool downImage = false, bool downMp3 = false)
|
||||
string outputVideoPath, string fileNamefolder, bool mergeImg2Viedo, bool downImage = false,
|
||||
bool downMp3 = false, Action<string> reportWarning = null, Action<string> reportError = null)
|
||||
{
|
||||
// 输入参数校验(避免无效执行)
|
||||
if (request == null || request.ImageUrls == null || request.ImageUrls.Count == 0)
|
||||
{
|
||||
Log.Error("图片URL列表为空,无法合成视频");
|
||||
reportError?.Invoke("图片 URL 列表为空");
|
||||
return false;
|
||||
}
|
||||
//if (mergeImg2Viedo && (request.AudioUrls == null || request.AudioUrls.Count == 0))
|
||||
@@ -128,7 +130,9 @@ namespace dy.net.service
|
||||
request.ImageUrls, Path.Combine(tempDir, "raw-images"), "image_", "webp", cookie);
|
||||
if (!string.IsNullOrEmpty(imageError) || rawImages == null || rawImages.Count == 0)
|
||||
{
|
||||
Log.Error($"图片下载失败:{imageError ?? "未下载到任何图片"}");
|
||||
var reason = imageError ?? "未下载到任何图片";
|
||||
Log.Error($"图片下载失败:{reason}");
|
||||
reportError?.Invoke($"图片下载失败:{reason}");
|
||||
return false;
|
||||
}
|
||||
// 保存下载的图片(如果需要)
|
||||
@@ -138,19 +142,25 @@ namespace dy.net.service
|
||||
}
|
||||
|
||||
// 2. 下载音频
|
||||
List<DouyinMergeVideoDto> rawAudios =new List<DouyinMergeVideoDto>();
|
||||
if (mergeImg2Viedo && request.AudioUrls != null && request.AudioUrls.Count > 0)
|
||||
List<DouyinMergeVideoDto> rawAudios = new List<DouyinMergeVideoDto>();
|
||||
var audioWarningReported = false;
|
||||
if ((mergeImg2Viedo || downMp3) && request.AudioUrls != null && request.AudioUrls.Count > 0)
|
||||
{
|
||||
var (audios, audioError) = await DownloadMediaAsync(
|
||||
request.AudioUrls.Select(x => new DouyinMergeVideoDto { Path = x })?.ToList(), Path.Combine(tempDir, "raw-audios"), "audio_", "mp3", cookie);
|
||||
if (!string.IsNullOrEmpty(audioError) || audios == null || audios.Count == 0)
|
||||
{
|
||||
Log.Error($"音频下载失败:{audioError ?? "未下载到任何音频"}");
|
||||
return false;
|
||||
var warning = downMp3
|
||||
? "作品音频不可用,已降级处理,未生成单独音频文件"
|
||||
: "作品音频不可用,图文视频已降级为无声视频";
|
||||
Log.Warning("音频下载失败:{Reason};{Fallback}",
|
||||
audioError ?? "未下载到任何音频", warning);
|
||||
reportWarning?.Invoke(warning);
|
||||
audioWarningReported = true;
|
||||
}
|
||||
rawAudios = audios;
|
||||
else rawAudios = audios;
|
||||
// 保存下载的音频(如果需要)
|
||||
if (downMp3)
|
||||
if (downMp3 && rawAudios.Count > 0)
|
||||
{
|
||||
var ext = Path.GetExtension(rawAudios.FirstOrDefault()?.Path);
|
||||
await SaveDownloadedFilesAsync(rawAudios, fileNamefolder, ext, Path.GetFileNameWithoutExtension(outputVideoPath));
|
||||
@@ -161,7 +171,11 @@ namespace dy.net.service
|
||||
if (!mergeImg2Viedo)
|
||||
{
|
||||
Log.Debug($"根据系统配置设置不下载图文视频-[{outputVideoPath}],{(downImage ? "下载图片" : "不下载图片")},{(downMp3 ? "下载音频" : "不下载音频")}");
|
||||
return true;
|
||||
// When only an audio attachment was requested, an unavailable audio source
|
||||
// leaves no persistent artifact and must remain a real item-level failure.
|
||||
var hasArtifact = downImage || !downMp3 || rawAudios.Count > 0;
|
||||
if (!hasArtifact) reportError?.Invoke("音频下载失败,且当前任务未配置保存图片");
|
||||
return hasArtifact;
|
||||
}
|
||||
|
||||
// 3. 调整图片显示时长
|
||||
@@ -194,14 +208,20 @@ namespace dy.net.service
|
||||
|
||||
if (rawAudios.Count == 0)
|
||||
{
|
||||
var musicPath = GetRandomMergeMusic();
|
||||
rawAudios = new List<DouyinMergeVideoDto> { new DouyinMergeVideoDto { Path= musicPath } };
|
||||
Log.Debug("版权原因无法下载音频,使用默认无声音频文件");
|
||||
if (!audioWarningReported)
|
||||
{
|
||||
var warning = downMp3
|
||||
? "作品没有可用音频,已生成无声视频,未生成单独音频文件"
|
||||
: "作品没有可用音频,已生成无声视频";
|
||||
reportWarning?.Invoke(warning);
|
||||
audioWarningReported = true;
|
||||
}
|
||||
Log.Warning("图文作品没有可用音频,将直接生成无声视频");
|
||||
}
|
||||
|
||||
string resultPath = await ffmpegHelper.CreateVideoFromImagesAndAudioAsync(
|
||||
rawImages, // 所有下载的图片(确保无遗漏)
|
||||
rawAudios.FirstOrDefault()?.Path, // 取第一个音频(可根据需求调整)
|
||||
rawAudios.FirstOrDefault()?.Path, // 音频缺失时 FFmpegHelper 会生成无声视频
|
||||
outputVideoPath,
|
||||
ffmpegHelper.VideoWidth,
|
||||
ffmpegHelper.VideoHeight,
|
||||
@@ -218,11 +238,13 @@ namespace dy.net.service
|
||||
}
|
||||
});
|
||||
|
||||
if (!mergeSuccess) reportError?.Invoke("FFmpeg 未生成有效的视频文件");
|
||||
return mergeSuccess;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "视频合成过程中发生未处理异常");
|
||||
reportError?.Invoke($"{ex.GetType().Name}:{ex.GetBaseException().Message}");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
@@ -330,6 +352,7 @@ namespace dy.net.service
|
||||
List<DouyinMergeVideoDto> urls, string saveDir, string prefix, string ext, string cookie)
|
||||
{
|
||||
var successPaths = new List<DouyinMergeVideoDto>();
|
||||
var failures = new List<string>();
|
||||
for (var i = 0; i < urls.Count; i++)
|
||||
{
|
||||
var url = urls[i];
|
||||
@@ -339,12 +362,12 @@ namespace dy.net.service
|
||||
|
||||
try
|
||||
{
|
||||
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(url.Path, savePath, cookie);
|
||||
if (Success)
|
||||
var result = await douyinHttpClientService.DownloadAsync(url.Path, savePath, cookie);
|
||||
if (result.Success)
|
||||
{
|
||||
successPaths.Add(new DouyinMergeVideoDto
|
||||
{
|
||||
Path = ActualSavePath,
|
||||
Path = result.ActualSavePath,
|
||||
Height = url.Height,
|
||||
Width =
|
||||
url.Width
|
||||
@@ -352,18 +375,22 @@ namespace dy.net.service
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Error($"下载失败:{url} → {savePath}");
|
||||
failures.Add($"{result.SourceHost ?? "未知来源"}/HTTP {result.HttpStatusCode?.ToString() ?? "-"}/{result.FailureKind}:{result.Message}");
|
||||
Serilog.Log.Warning("合成素材下载失败:Host={Host}, Status={Status}, Kind={Kind}, Target={Target}",
|
||||
result.SourceHost, result.HttpStatusCode, result.FailureKind, savePath);
|
||||
}
|
||||
//Console.WriteLine($"下载成功:{url} → {savePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"下载失败:{url},错误:{ex.Message}";
|
||||
Serilog.Log.Error(error);
|
||||
var error = $"合成素材下载失败:{ex.GetType().Name}:{ex.GetBaseException().Message}";
|
||||
Serilog.Log.Error("合成素材下载异常:Type={ExceptionType}, Target={Target}", ex.GetType().Name, savePath);
|
||||
return (new List<DouyinMergeVideoDto>(), error);
|
||||
}
|
||||
}
|
||||
return (successPaths, null);
|
||||
return successPaths.Count == 0 && failures.Count > 0
|
||||
? (successPaths, string.Join(";", failures.Take(3)))
|
||||
: (successPaths, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user