Files
douyin/expand/dy.image/ImageMergeToVideoService.cs
T
jianzhichu e6e9ff3cc3 1、去重
2、分享
3、视频播放
4、批量删除,重新下载
5、自定义标题(仅博主视频)
6、授权页面去掉博主添加,新增关注列表
7、图文视频合成优化
8、其他优化
2025-11-30 00:42:25 +08:00

174 lines
7.4 KiB
C#

namespace dy.image
{
/// <summary>
/// 合并多张图片+音频为视频
/// </summary>
public class ImageMergeToVideoService
{
private readonly DownloadHelper _downloadHelper;
private readonly FFmpegHelper _fFmpegHelper;
public ImageMergeToVideoService(DownloadHelper downloadHelper, FFmpegHelper fFmpegHelper)
{
_downloadHelper = downloadHelper;
_fFmpegHelper = fFmpegHelper;
}
/// <summary>
/// 视频合成
/// </summary>
/// <param name="rootPath"></param>
/// <param name="request"></param>
/// <param name="outputVideoPath"></param>
/// <param name="fileNamefolder"></param>
/// <param name="mergeImg2Viedo"></param>
/// <param name="downImage"></param>
/// <param name="downMp3"></param>
/// <returns></returns>
public async Task<bool> MergeToVideo(string rootPath, MediaMergeRequest request,string outputVideoPath,string fileNamefolder,bool mergeImg2Viedo,bool downImage=false,bool downMp3=false)
{
try
{
// 创建唯一临时目录(避免并发冲突)
var tempDir = Path.Combine(rootPath, "temp", Guid.NewGuid().ToString());
try
{
// 1. 下载图片
var (rawImages, error) = await DownloadMediaAsync(request.ImageUrls, Path.Combine(tempDir, "raw-images"), "image_", "webp");
if (!string.IsNullOrEmpty(error))
{
Serilog.Log.Error($"{error}");
return false;
}
else
{
if(downImage)
{
for (int i = 0; i < rawImages.Length; i++)
{
string sourcePath = rawImages[i];
// 重命名为有规律的文件名,如 temp_001.jpg, temp_002.png
string extension = Path.GetExtension(sourcePath);
string destFileName = $"temp_{i + 1:D3}{extension}"; // D3 确保是3位数字,不足补0
string destPath = Path.Combine(fileNamefolder, destFileName);
File.Copy(sourcePath, destPath);
}
}
}
// 2. 下载音频
var (rawAudios, audioError) = await DownloadMediaAsync(request.AudioUrls, Path.Combine(tempDir, "raw-audios"), "audio_", "mp3");
if (!string.IsNullOrEmpty(audioError))
{
Serilog.Log.Error($"{audioError}");
return false;
}
else
{
if(downMp3)
{
for (int i = 0; i < rawAudios.Length; i++)
{
string sourcePath = rawAudios[i];
// 重命名为有规律的文件名,如 temp_001.mp3, temp_002.mp3
string extension = Path.GetExtension(sourcePath);
string destFileName = $"temp_{i + 1:D3}{extension}"; // D3 确保是3位数字,不足补0
string destPath = Path.Combine(fileNamefolder, destFileName);
File.Copy(sourcePath, destPath);
}
}
}
if (!mergeImg2Viedo)
{
// 不合成视频,直接返回成功
Serilog.Log.Debug($"不合成视频,直接返回");
return true;
}
// 4. 合成视频
//var outputVideoPath = Path.Combine(tempDir, "output", $"merged-video.{request.OutputFormat.ToLower()}");
// 2. 创建帮助类实例
// 在Docker容器内,FFmpeg通常在PATH中,所以直接用 "ffmpeg" 即可
// 根据图片数量调整每张图片显示时长
if (request.ImageUrls.Count <= 3)
{
request.ImageDurationPerSecond = 3;
}
if (request.ImageUrls.Count > 20)
{
request.ImageDurationPerSecond = 2;
}
// 3. (可选)自定义视频参数
_fFmpegHelper.VideoWidth = 1080;
_fFmpegHelper.VideoHeight = 1920;
_fFmpegHelper.ImageDisplayDurationSeconds = request.ImageDurationPerSecond;
_fFmpegHelper.OutputFrameRate = 30;
// 4. 创建进度
var progress = new Progress<double>(p =>
{
Console.WriteLine($"进度: {p:F2}%");
});
// 5. 执行合成任务
using (var cancellationTokenSource = new CancellationTokenSource())
{
string resultPath = await _fFmpegHelper.CreateVideoFromImagesAndAudioAsync(
rawImages,
rawAudios[0],
outputVideoPath,
progress,
cancellationTokenSource.Token);
Console.WriteLine($"视频合成成功!文件已保存至: {resultPath}");
}
}
finally
{
// 清理临时目录(无论成功失败)
if (Directory.Exists(tempDir))
{
Directory.Delete(tempDir, recursive: true);
}
}
return true;
}
catch (Exception ex)
{
Serilog.Log.Error($"{ex.StackTrace}");
return false;
}
}
/// <summary>通用媒体下载方法</summary>
private async Task<(string[] SuccessPaths, string ErrorMsg)> DownloadMediaAsync(
List<string> urls, string saveDir, string prefix, string ext)
{
var successPaths = new List<string>();
for (var i = 0; i < urls.Count; i++)
{
var url = urls[i];
var fileExt = ext ?? Path.GetExtension(url).TrimStart('.') ?? "png";
var fileName = $"{prefix}{i + 1}.{fileExt}";
var savePath = Path.Combine(saveDir, fileName);
try
{
await _downloadHelper.DownloadFileAsync(url, savePath);
successPaths.Add(savePath);
Console.WriteLine($"下载成功:{url} → {savePath}");
}
catch (Exception ex)
{
var error = $"下载失败:{url},错误:{ex.Message}";
Console.WriteLine(error);
return (Array.Empty<string>(), error);
}
}
return (successPaths.ToArray(), null);
}
}
}