图片合成视频 尺寸优化
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
|
||||
<History>True|2026-02-11T12:39:23.8084775Z||;True|2026-02-11T20:37:06.6507806+08:00||;True|2026-02-11T20:04:19.1839991+08:00||;True|2026-02-10T21:59:37.6685615+08:00||;True|2026-02-10T15:43:23.5038986+08:00||;True|2026-02-10T14:42:48.6214001+08:00||;True|2026-02-06T23:15:19.0037070+08:00||;True|2026-02-06T22:52:30.1006808+08:00||;True|2026-02-06T22:49:02.0056235+08:00||;True|2026-02-06T22:45:30.6355735+08:00||;False|2026-02-06T22:45:25.7210123+08:00||;True|2026-02-06T22:38:37.6994272+08:00||;True|2026-02-06T22:14:47.8146805+08:00||;True|2026-02-03T22:34:12.3588819+08:00||;True|2026-02-03T22:20:08.9945012+08:00||;True|2026-02-03T21:06:17.7655312+08:00||;True|2026-02-02T23:24:09.9134119+08:00||;True|2026-02-02T23:21:52.3235946+08:00||;False|2026-02-02T23:21:47.1386702+08:00||;True|2026-02-02T23:20:46.6277206+08:00||;False|2026-02-02T23:20:40.3158947+08:00||;True|2026-02-02T22:55:39.4268475+08:00||;True|2026-02-01T21:34:30.5989260+08:00||;True|2026-02-01T21:30:20.7556066+08:00||;True|2026-01-30T21:56:47.1840381+08:00||;True|2026-01-28T22:01:00.8412391+08:00||;False|2026-01-28T21:49:29.1242233+08:00||;True|2026-01-28T21:48:26.1707204+08:00||;True|2026-01-28T21:48:05.8291762+08:00||;False|2026-01-28T21:48:01.1596391+08:00||;True|2026-01-28T21:37:59.4831763+08:00||;False|2026-01-28T21:37:53.5855464+08:00||;True|2026-01-28T21:27:51.1872828+08:00||;True|2026-01-28T13:01:14.1677037+08:00||;True|2026-01-28T12:36:45.7819776+08:00||;True|2026-01-28T12:09:15.6540204+08:00||;True|2026-01-28T12:08:40.0490617+08:00||;True|2026-01-28T11:58:55.4078870+08:00||;True|2026-01-27T22:37:03.3304942+08:00||;True|2026-01-27T22:05:40.4316281+08:00||;True|2026-01-26T22:30:43.5083924+08:00||;False|2026-01-26T22:30:33.7355213+08:00||;True|2026-01-26T22:26:26.0218548+08:00||;True|2026-01-26T22:21:32.3171217+08:00||;True|2026-01-26T22:20:40.3916780+08:00||;True|2026-01-26T22:16:19.1017273+08:00||;True|2026-01-26T22:13:34.2028883+08:00||;True|2026-01-26T21:46:23.7117812+08:00||;True|2026-01-26T21:41:19.6963033+08:00||;True|2026-01-25T21:27:13.9893777+08:00||;True|2026-01-24T21:45:22.8493624+08:00||;True|2026-01-24T21:31:35.4635265+08:00||;True|2026-01-24T21:27:31.3098506+08:00||;True|2026-01-24T15:47:01.6934638+08:00||;True|2026-01-24T15:32:35.6316009+08:00||;True|2026-01-24T15:16:28.7216639+08:00||;True|2026-01-23T22:14:26.8066324+08:00||;True|2026-01-23T13:36:48.4086406+08:00||;True|2026-01-23T13:30:27.2924204+08:00||;True|2026-01-23T13:02:53.3219368+08:00||;True|2026-01-23T13:01:54.3466638+08:00||;True|2026-01-23T13:01:28.8560809+08:00||;True|2026-01-22T23:18:32.0400436+08:00||;True|2026-01-22T22:29:29.5746209+08:00||;True|2026-01-22T21:32:21.7284081+08:00||;True|2026-01-22T21:20:26.5985955+08:00||;False|2026-01-22T21:20:21.1372539+08:00||;True|2026-01-22T21:11:18.3771739+08:00||;True|2026-01-22T20:58:55.9862778+08:00||;True|2026-01-22T20:51:48.6699461+08:00||;False|2026-01-22T20:51:42.2079210+08:00||;True|2026-01-22T20:50:34.7280114+08:00||;True|2026-01-22T20:50:11.1588492+08:00||;True|2026-01-22T16:11:14.0626037+08:00||;True|2026-01-22T15:53:16.6377347+08:00||;True|2026-01-22T15:45:34.0935091+08:00||;True|2026-01-22T12:44:28.1577014+08:00||;True|2026-01-22T12:40:45.7449335+08:00||;True|2026-01-22T12:39:07.0683214+08:00||;True|2026-01-22T09:55:49.7086190+08:00||;True|2026-01-22T01:37:53.6972395+08:00||;True|2026-01-22T01:34:14.2142463+08:00||;True|2026-01-22T01:32:50.9228911+08:00||;True|2026-01-22T01:29:43.8871986+08:00||;True|2026-01-22T01:29:40.7838709+08:00||;True|2026-01-22T01:27:34.4763970+08:00||;True|2026-01-22T01:26:55.0117367+08:00||;True|2026-01-22T01:23:19.4789587+08:00||;True|2026-01-19T10:19:44.8997385+08:00||;True|2026-01-19T10:11:10.2305307+08:00||;True|2026-01-17T00:52:08.8180346+08:00||;False|2026-01-17T00:52:03.1829000+08:00||;True|2026-01-17T00:45:04.0498090+08:00||;True|2026-01-16T21:00:57.7239379+08:00||;True|2026-01-16T21:00:03.3912989+08:00||;True|2026-01-16T20:56:08.5505592+08:00||;True|2026-01-16T20:46:32.3067302+08:00||;True|2026-01-15T22:18:57.9835738+08:00||;True|2026-01-15T22:07:25.4753938+08:00||;True|2026-01-15T22:07:14.8243754+08:00||;</History>
|
||||
<History>True|2026-02-12T04:18:02.1864306Z||;True|2026-02-11T20:39:23.8084775+08:00||;True|2026-02-11T20:37:06.6507806+08:00||;True|2026-02-11T20:04:19.1839991+08:00||;True|2026-02-10T21:59:37.6685615+08:00||;True|2026-02-10T15:43:23.5038986+08:00||;True|2026-02-10T14:42:48.6214001+08:00||;True|2026-02-06T23:15:19.0037070+08:00||;True|2026-02-06T22:52:30.1006808+08:00||;True|2026-02-06T22:49:02.0056235+08:00||;True|2026-02-06T22:45:30.6355735+08:00||;False|2026-02-06T22:45:25.7210123+08:00||;True|2026-02-06T22:38:37.6994272+08:00||;True|2026-02-06T22:14:47.8146805+08:00||;True|2026-02-03T22:34:12.3588819+08:00||;True|2026-02-03T22:20:08.9945012+08:00||;True|2026-02-03T21:06:17.7655312+08:00||;True|2026-02-02T23:24:09.9134119+08:00||;True|2026-02-02T23:21:52.3235946+08:00||;False|2026-02-02T23:21:47.1386702+08:00||;True|2026-02-02T23:20:46.6277206+08:00||;False|2026-02-02T23:20:40.3158947+08:00||;True|2026-02-02T22:55:39.4268475+08:00||;True|2026-02-01T21:34:30.5989260+08:00||;True|2026-02-01T21:30:20.7556066+08:00||;True|2026-01-30T21:56:47.1840381+08:00||;True|2026-01-28T22:01:00.8412391+08:00||;False|2026-01-28T21:49:29.1242233+08:00||;True|2026-01-28T21:48:26.1707204+08:00||;True|2026-01-28T21:48:05.8291762+08:00||;False|2026-01-28T21:48:01.1596391+08:00||;True|2026-01-28T21:37:59.4831763+08:00||;False|2026-01-28T21:37:53.5855464+08:00||;True|2026-01-28T21:27:51.1872828+08:00||;True|2026-01-28T13:01:14.1677037+08:00||;True|2026-01-28T12:36:45.7819776+08:00||;True|2026-01-28T12:09:15.6540204+08:00||;True|2026-01-28T12:08:40.0490617+08:00||;True|2026-01-28T11:58:55.4078870+08:00||;True|2026-01-27T22:37:03.3304942+08:00||;True|2026-01-27T22:05:40.4316281+08:00||;True|2026-01-26T22:30:43.5083924+08:00||;False|2026-01-26T22:30:33.7355213+08:00||;True|2026-01-26T22:26:26.0218548+08:00||;True|2026-01-26T22:21:32.3171217+08:00||;True|2026-01-26T22:20:40.3916780+08:00||;True|2026-01-26T22:16:19.1017273+08:00||;True|2026-01-26T22:13:34.2028883+08:00||;True|2026-01-26T21:46:23.7117812+08:00||;True|2026-01-26T21:41:19.6963033+08:00||;True|2026-01-25T21:27:13.9893777+08:00||;True|2026-01-24T21:45:22.8493624+08:00||;True|2026-01-24T21:31:35.4635265+08:00||;True|2026-01-24T21:27:31.3098506+08:00||;True|2026-01-24T15:47:01.6934638+08:00||;True|2026-01-24T15:32:35.6316009+08:00||;True|2026-01-24T15:16:28.7216639+08:00||;True|2026-01-23T22:14:26.8066324+08:00||;True|2026-01-23T13:36:48.4086406+08:00||;True|2026-01-23T13:30:27.2924204+08:00||;True|2026-01-23T13:02:53.3219368+08:00||;True|2026-01-23T13:01:54.3466638+08:00||;True|2026-01-23T13:01:28.8560809+08:00||;True|2026-01-22T23:18:32.0400436+08:00||;True|2026-01-22T22:29:29.5746209+08:00||;True|2026-01-22T21:32:21.7284081+08:00||;True|2026-01-22T21:20:26.5985955+08:00||;False|2026-01-22T21:20:21.1372539+08:00||;True|2026-01-22T21:11:18.3771739+08:00||;True|2026-01-22T20:58:55.9862778+08:00||;True|2026-01-22T20:51:48.6699461+08:00||;False|2026-01-22T20:51:42.2079210+08:00||;True|2026-01-22T20:50:34.7280114+08:00||;True|2026-01-22T20:50:11.1588492+08:00||;True|2026-01-22T16:11:14.0626037+08:00||;True|2026-01-22T15:53:16.6377347+08:00||;True|2026-01-22T15:45:34.0935091+08:00||;True|2026-01-22T12:44:28.1577014+08:00||;True|2026-01-22T12:40:45.7449335+08:00||;True|2026-01-22T12:39:07.0683214+08:00||;True|2026-01-22T09:55:49.7086190+08:00||;True|2026-01-22T01:37:53.6972395+08:00||;True|2026-01-22T01:34:14.2142463+08:00||;True|2026-01-22T01:32:50.9228911+08:00||;True|2026-01-22T01:29:43.8871986+08:00||;True|2026-01-22T01:29:40.7838709+08:00||;True|2026-01-22T01:27:34.4763970+08:00||;True|2026-01-22T01:26:55.0117367+08:00||;True|2026-01-22T01:23:19.4789587+08:00||;True|2026-01-19T10:19:44.8997385+08:00||;True|2026-01-19T10:11:10.2305307+08:00||;True|2026-01-17T00:52:08.8180346+08:00||;False|2026-01-17T00:52:03.1829000+08:00||;True|2026-01-17T00:45:04.0498090+08:00||;True|2026-01-16T21:00:57.7239379+08:00||;True|2026-01-16T21:00:03.3912989+08:00||;True|2026-01-16T20:56:08.5505592+08:00||;True|2026-01-16T20:46:32.3067302+08:00||;True|2026-01-15T22:18:57.9835738+08:00||;True|2026-01-15T22:07:25.4753938+08:00||;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
+15
-13
@@ -6,6 +6,7 @@ using dy.net.service;
|
||||
using dy.net.utils;
|
||||
using Newtonsoft.Json;
|
||||
using Quartz;
|
||||
using Quartz.Util;
|
||||
using Serilog;
|
||||
using System.Net;
|
||||
|
||||
@@ -513,6 +514,7 @@ namespace dy.net.job
|
||||
/// <summary>
|
||||
/// 遍历视频列表,分别处理每个视频和图片集
|
||||
/// </summary>
|
||||
/// <param name="syncCount1"></param>
|
||||
/// <param name="cookie">用户Cookie</param>
|
||||
/// <param name="data">视频信息对象</param>
|
||||
/// <param name="config">应用配置</param>
|
||||
@@ -525,7 +527,7 @@ namespace dy.net.job
|
||||
var videos = new List<DouyinVideo>();
|
||||
foreach (var item in data.AwemeList)
|
||||
{
|
||||
//if (item.AwemeId != "7565895895016295726")
|
||||
//if (item.AwemeId != "7321309610927770930")
|
||||
//{
|
||||
// continue;
|
||||
//}
|
||||
@@ -602,7 +604,7 @@ namespace dy.net.job
|
||||
else
|
||||
{
|
||||
//处理多个视频-组合的图文视频--类似动图。
|
||||
List<DouyinDynamicVideoDto> dynamicVideoUrls = new List<DouyinDynamicVideoDto>();
|
||||
List<DouyinMergeVideoDto> dynamicVideoUrls = new List<DouyinMergeVideoDto>();
|
||||
// 当需要下载动态视频时,获取其他URL
|
||||
if (config.DownDynamicVideo && item.Images != null && item.Images.Count > 0)
|
||||
{
|
||||
@@ -617,7 +619,7 @@ namespace dy.net.job
|
||||
{
|
||||
var height = btv.PlayAddr?.Height ?? 1920;
|
||||
var width = btv.PlayAddr?.Width ?? 1080;
|
||||
DouyinDynamicVideoDto info = new DouyinDynamicVideoDto
|
||||
DouyinMergeVideoDto info = new DouyinMergeVideoDto
|
||||
{
|
||||
Path = targetUrl,
|
||||
Height = height,
|
||||
@@ -639,7 +641,7 @@ namespace dy.net.job
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dynamicVideo.DynamicVideos))
|
||||
{
|
||||
var dynamicVideos = JsonConvert.DeserializeObject<List<DouyinDynamicVideoDto>>(dynamicVideo.DynamicVideos);
|
||||
var dynamicVideos = JsonConvert.DeserializeObject<List<DouyinMergeVideoDto>>(dynamicVideo.DynamicVideos);
|
||||
Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]-动态视频[{item.Desc}],下载成功 ,共{dynamicVideos?.Count}个视频...");
|
||||
if (dynamicVideos != null && dynamicVideos.Count > 0)
|
||||
{
|
||||
@@ -953,7 +955,7 @@ namespace dy.net.job
|
||||
/// <param name="followed"></param>
|
||||
/// <param name="cate"></param>
|
||||
/// <returns></returns>
|
||||
protected async Task<DouyinVideo> ProcessDynamicVideo(List<DouyinDynamicVideoDto> dynamicUrls, DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed = null, DouyinCollectCate cate = null)
|
||||
protected async Task<DouyinVideo> ProcessDynamicVideo(List<DouyinMergeVideoDto> dynamicUrls, DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed = null, DouyinCollectCate cate = null)
|
||||
{
|
||||
// 创建保存文件夹
|
||||
var saveFolder = CreateSaveFolder(cookie, item, config, followed, cate);
|
||||
@@ -968,12 +970,12 @@ namespace dy.net.job
|
||||
// 随机延迟
|
||||
int i = 1;
|
||||
|
||||
List<DouyinDynamicVideoDto> dynamicSavePaths = new List<DouyinDynamicVideoDto>();
|
||||
List<DouyinMergeVideoDto> dynamicSavePaths = new List<DouyinMergeVideoDto>();
|
||||
foreach (var dynamicUrl in dynamicUrls)
|
||||
{
|
||||
// 下载动态视频
|
||||
var dynamicSavePath = savePath.Replace(".mp4", $"_00{i}.mp4");
|
||||
DouyinDynamicVideoDto v = new DouyinDynamicVideoDto() { Height = dynamicUrl.Height, Width = dynamicUrl.Width, Path = dynamicSavePath };
|
||||
DouyinMergeVideoDto v = new DouyinMergeVideoDto() { Height = dynamicUrl.Height, Width = dynamicUrl.Width, Path = dynamicSavePath };
|
||||
|
||||
i++;
|
||||
// 如果文件已存在,跳过
|
||||
@@ -1087,10 +1089,10 @@ namespace dy.net.job
|
||||
try
|
||||
{
|
||||
// 提取图片URL列表
|
||||
List<string> imageUrls = item.Images?
|
||||
List<DouyinMergeVideoDto> imageUrls = item.Images?
|
||||
.Where(img => img.UrlList != null && img.UrlList.Any())
|
||||
.Select(img => img.UrlList.FirstOrDefault())
|
||||
.Where(url => !string.IsNullOrWhiteSpace(url))
|
||||
.Select(img => new DouyinMergeVideoDto { Path = img.UrlList.FirstOrDefault(),Height=img.Height,Width=img.Width })
|
||||
.Where(img => !string.IsNullOrWhiteSpace(img.Path))
|
||||
.ToList();
|
||||
|
||||
// 如果没有图片,返回null
|
||||
@@ -1169,8 +1171,8 @@ namespace dy.net.job
|
||||
}
|
||||
|
||||
var coverUrl = cate is not null && cate.CateType != VideoTypeEnum.dy_custom_collect
|
||||
? (item.MixInfo?.CoverUrl?.UrlList?.FirstOrDefault() ?? imageUrls.FirstOrDefault() ?? item.Music?.CoverHd?.UrlList?.FirstOrDefault())
|
||||
: imageUrls.FirstOrDefault();
|
||||
? (item.MixInfo?.CoverUrl?.UrlList?.FirstOrDefault() ?? imageUrls.FirstOrDefault()?.Path ?? item.Music?.CoverHd?.UrlList?.FirstOrDefault())
|
||||
: imageUrls.FirstOrDefault()?.Path;
|
||||
|
||||
// 下载作者头像
|
||||
var avatarSavePath = await DownAuthorAvatar(cookie, item);
|
||||
@@ -1408,7 +1410,7 @@ namespace dy.net.job
|
||||
/// <param name="cate">短剧、合集、自定义收藏夹</param>
|
||||
/// <returns>创建的视频实体对象</returns>
|
||||
private async Task<DouyinVideo> CreateVideoEntity(AppConfig config,
|
||||
DouyinCookie cookie, Aweme item, VideoBitRate bitRate, string savePath, string coverSavePath, string avatorPath, List<DouyinDynamicVideoDto> dynamicVideos = null, DouyinCollectCate cate = null)
|
||||
DouyinCookie cookie, Aweme item, VideoBitRate bitRate, string savePath, string coverSavePath, string avatorPath, List<DouyinMergeVideoDto> dynamicVideos = null, DouyinCollectCate cate = null)
|
||||
{
|
||||
// 获取视频标签
|
||||
var (tag1, tag2, tag3) = GetVideoTags(item);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public class DouyinDynamicVideoDto
|
||||
public class DouyinMergeVideoDto
|
||||
{
|
||||
public string Path { get; set; }
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
public class MediaMergeRequest
|
||||
{
|
||||
/// <summary>网络图片地址数组(必填)</summary>
|
||||
public List<string> ImageUrls { get; set; }
|
||||
public List<DouyinMergeVideoDto> ImageUrls { get; set; }
|
||||
|
||||
/// <summary>网络MP3地址数组(必填)</summary>
|
||||
public List<string> AudioUrls { get; set; }
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace dy.net.service
|
||||
/// <param name="ck"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<(string mp4Path, string mp3Path)> MergeMultipleVideosAsync(
|
||||
List<DouyinDynamicVideoDto> videoFilePaths,
|
||||
List<DouyinMergeVideoDto> videoFilePaths,
|
||||
string audioPath,
|
||||
string savePath,
|
||||
string ck)
|
||||
@@ -45,11 +45,11 @@ namespace dy.net.service
|
||||
string mergMusicPath = GetRandomMergeMusic();
|
||||
if (!string.IsNullOrWhiteSpace(audioPath))
|
||||
{
|
||||
var (SuccessPaths, _) = await DownloadMediaAsync(new List<string> { audioPath }, Path.GetDirectoryName(savePath), "audio_", "mp3", ck);
|
||||
if (SuccessPaths != null && SuccessPaths.Length > 0)
|
||||
var (SuccessPaths, _) = await DownloadMediaAsync(new List<DouyinMergeVideoDto> { new DouyinMergeVideoDto { Path= audioPath } }, Path.GetDirectoryName(savePath), "audio_", "mp3", ck);
|
||||
if (SuccessPaths != null && SuccessPaths.Count > 0)
|
||||
{
|
||||
mergMusicPath = SuccessPaths[0];
|
||||
mp3Path = SuccessPaths[0];
|
||||
mergMusicPath = SuccessPaths.FirstOrDefault()?.Path;
|
||||
mp3Path = SuccessPaths.FirstOrDefault()?.Path;
|
||||
}
|
||||
}
|
||||
var ffmpeg = new FFmpegHelper();
|
||||
@@ -126,7 +126,7 @@ namespace dy.net.service
|
||||
// 1. 下载图片
|
||||
var (rawImages, imageError) = await DownloadMediaAsync(
|
||||
request.ImageUrls, Path.Combine(tempDir, "raw-images"), "image_", "webp", cookie);
|
||||
if (!string.IsNullOrEmpty(imageError) || rawImages == null || rawImages.Length == 0)
|
||||
if (!string.IsNullOrEmpty(imageError) || rawImages == null || rawImages.Count == 0)
|
||||
{
|
||||
Log.Error($"图片下载失败:{imageError ?? "未下载到任何图片"}");
|
||||
return false;
|
||||
@@ -138,12 +138,12 @@ namespace dy.net.service
|
||||
}
|
||||
|
||||
// 2. 下载音频
|
||||
string[] rawAudios = Array.Empty<string>();
|
||||
List<DouyinMergeVideoDto> rawAudios =new List<DouyinMergeVideoDto>();
|
||||
if (mergeImg2Viedo && request.AudioUrls != null && request.AudioUrls.Count > 0)
|
||||
{
|
||||
var (audios, audioError) = await DownloadMediaAsync(
|
||||
request.AudioUrls, Path.Combine(tempDir, "raw-audios"), "audio_", "mp3", cookie);
|
||||
if (!string.IsNullOrEmpty(audioError) || audios == null || audios.Length == 0)
|
||||
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;
|
||||
@@ -152,7 +152,7 @@ namespace dy.net.service
|
||||
// 保存下载的音频(如果需要)
|
||||
if (downMp3)
|
||||
{
|
||||
var ext = Path.GetExtension(rawAudios[0]);
|
||||
var ext = Path.GetExtension(rawAudios.FirstOrDefault()?.Path);
|
||||
await SaveDownloadedFilesAsync(rawAudios, fileNamefolder, ext, Path.GetFileNameWithoutExtension(outputVideoPath));
|
||||
}
|
||||
}
|
||||
@@ -192,16 +192,16 @@ namespace dy.net.service
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300));
|
||||
// 执行合成(确保每个图片都参与)
|
||||
|
||||
if (rawAudios.Length == 0)
|
||||
if (rawAudios.Count == 0)
|
||||
{
|
||||
var musicPath = GetRandomMergeMusic();
|
||||
rawAudios = new string[] { musicPath };
|
||||
rawAudios = new List<DouyinMergeVideoDto> { new DouyinMergeVideoDto { Path= musicPath } };
|
||||
Log.Debug("版权原因无法下载音频,使用默认无声音频文件");
|
||||
}
|
||||
|
||||
string resultPath = await ffmpegHelper.CreateVideoFromImagesAndAudioAsync(
|
||||
rawImages, // 所有下载的图片(确保无遗漏)
|
||||
rawAudios[0], // 取第一个音频(可根据需求调整)
|
||||
rawAudios.FirstOrDefault()?.Path, // 取第一个音频(可根据需求调整)
|
||||
outputVideoPath,
|
||||
ffmpegHelper.VideoWidth,
|
||||
ffmpegHelper.VideoHeight,
|
||||
@@ -235,23 +235,23 @@ namespace dy.net.service
|
||||
/// <summary>
|
||||
/// 保存下载的文件(图片/音频)到目标目录
|
||||
/// </summary>
|
||||
private static async Task SaveDownloadedFilesAsync(string[] sourcePaths, string targetFolder, string defaultExt, string videoFileName)
|
||||
private static async Task SaveDownloadedFilesAsync(List<DouyinMergeVideoDto> sourcePaths, string targetFolder, string defaultExt, string videoFileName)
|
||||
{
|
||||
if (sourcePaths == null || sourcePaths.Length == 0) return;
|
||||
if (sourcePaths == null || sourcePaths.Count == 0) return;
|
||||
Directory.CreateDirectory(targetFolder); // 确保目标目录存在
|
||||
|
||||
// 异步保存(不阻塞主线程)
|
||||
await Task.WhenAll(sourcePaths.Select(async (sourcePath, index) =>
|
||||
{
|
||||
if (!File.Exists(sourcePath)) return;
|
||||
string extension = Path.GetExtension(sourcePath) ?? $".{defaultExt}";
|
||||
if (!File.Exists(sourcePath.Path)) return;
|
||||
string extension = Path.GetExtension(sourcePath.Path) ?? $".{defaultExt}";
|
||||
string destFileName = $"{videoFileName}{index + 1:D3}{extension}";
|
||||
string destPath = Path.Combine(targetFolder, destFileName);
|
||||
|
||||
// 文件不存在时才复制,避免覆盖
|
||||
if (!File.Exists(destPath))
|
||||
{
|
||||
await Task.Run(() => File.Copy(sourcePath, destPath, overwrite: false));
|
||||
await Task.Run(() => File.Copy(sourcePath.Path, destPath, overwrite: false));
|
||||
Log.Debug($"已保存文件:{destPath}");
|
||||
}
|
||||
}));
|
||||
@@ -326,23 +326,29 @@ namespace dy.net.service
|
||||
|
||||
|
||||
/// <summary>通用媒体下载方法</summary>
|
||||
private async Task<(string[] SuccessPaths, string ErrorMsg)> DownloadMediaAsync(
|
||||
List<string> urls, string saveDir, string prefix, string ext, string cookie)
|
||||
private async Task<(List<DouyinMergeVideoDto> SuccessPaths, string ErrorMsg)> DownloadMediaAsync(
|
||||
List<DouyinMergeVideoDto> urls, string saveDir, string prefix, string ext, string cookie)
|
||||
{
|
||||
var successPaths = new List<string>();
|
||||
var successPaths = new List<DouyinMergeVideoDto>();
|
||||
for (var i = 0; i < urls.Count; i++)
|
||||
{
|
||||
var url = urls[i];
|
||||
var fileExt = ext ?? Path.GetExtension(url).TrimStart('.') ?? "png";
|
||||
var fileExt = ext ?? Path.GetExtension(url.Path).TrimStart('.') ?? "png";
|
||||
var fileName = $"{prefix}{i + 1}.{fileExt}";
|
||||
var savePath = Path.Combine(saveDir, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(url, savePath, cookie);
|
||||
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(url.Path, savePath, cookie);
|
||||
if (Success)
|
||||
{
|
||||
successPaths.Add(ActualSavePath);
|
||||
successPaths.Add(new DouyinMergeVideoDto
|
||||
{
|
||||
Path = ActualSavePath,
|
||||
Height = url.Height,
|
||||
Width =
|
||||
url.Width
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -354,10 +360,10 @@ namespace dy.net.service
|
||||
{
|
||||
var error = $"下载失败:{url},错误:{ex.Message}";
|
||||
Serilog.Log.Error(error);
|
||||
return (Array.Empty<string>(), error);
|
||||
return (new List<DouyinMergeVideoDto>(), error);
|
||||
}
|
||||
}
|
||||
return (successPaths.ToArray(), null);
|
||||
return (successPaths, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+258
-76
@@ -51,14 +51,186 @@ namespace dy.net.utils
|
||||
/// <summary>
|
||||
/// 将多张图片和一个音频文件合成为视频(最终终极版)。
|
||||
/// </summary>
|
||||
//public async Task<string> CreateVideoFromImagesAndAudioAsync(
|
||||
// IEnumerable<DouyinMergeVideoDto> imageFilePaths,
|
||||
// string audioFilePath,
|
||||
// string outputVideoPath,
|
||||
// int VideoWidth = 1080,
|
||||
// int Height = 1920,
|
||||
// IProgress<double> progress = null,
|
||||
// CancellationToken cancellationToken = default)
|
||||
//{
|
||||
// // 输入验证
|
||||
// if (imageFilePaths == null || !imageFilePaths.Any())
|
||||
// throw new ArgumentException("图片路径列表不能为空。", nameof(imageFilePaths));
|
||||
|
||||
// if (string.IsNullOrEmpty(audioFilePath) || !File.Exists(audioFilePath))
|
||||
// throw new FileNotFoundException("音频文件未找到。", audioFilePath);
|
||||
|
||||
// if (string.IsNullOrEmpty(outputVideoPath))
|
||||
// throw new ArgumentNullException(nameof(outputVideoPath));
|
||||
|
||||
// foreach (var imagePath in imageFilePaths)
|
||||
// {
|
||||
// if (!File.Exists(imagePath.Path))
|
||||
// throw new FileNotFoundException("图片文件未找到。", imagePath.Path);
|
||||
// }
|
||||
|
||||
// var outputDirectory = Path.GetDirectoryName(outputVideoPath);
|
||||
// if (!string.IsNullOrEmpty(outputDirectory) && !Directory.Exists(outputDirectory))
|
||||
// {
|
||||
// Directory.CreateDirectory(outputDirectory);
|
||||
// }
|
||||
|
||||
// // 关键步骤 1: 创建临时目录并生成有序图片序列
|
||||
// string tempImageDir = Path.Combine(AppContext.BaseDirectory, "temp", Guid.NewGuid().ToString());
|
||||
// Directory.CreateDirectory(tempImageDir);
|
||||
|
||||
// var imageList = imageFilePaths.ToList();
|
||||
// try
|
||||
// {
|
||||
// for (int i = 0; i < imageList.Count; i++)
|
||||
// {
|
||||
// string sourcePath = imageList[i].Path;
|
||||
// string extension = Path.GetExtension(sourcePath);
|
||||
// string destFileName = $"temp_{i + 1:D3}{extension}";
|
||||
// string destPath = Path.Combine(tempImageDir, destFileName);
|
||||
// File.Copy(sourcePath, destPath);
|
||||
// }
|
||||
|
||||
// string imageSequencePattern = Path.Combine(tempImageDir, "temp_%03d" + Path.GetExtension(imageList.FirstOrDefault().Path));
|
||||
// double imageFps = Math.Round(1.0 / ImageDisplayDurationSeconds, 2);
|
||||
|
||||
// // --- 修正点 1: 整合音频时长获取逻辑 ---
|
||||
// // 我们不再需要 GetAudioFilterAsync,而是直接获取时长用于计算视频循环
|
||||
// double audioDurationSeconds = imageList.Count * ImageDisplayDurationSeconds; // 默认值为图片总时长
|
||||
// try
|
||||
// {
|
||||
// // 使用 ffprobe 获取音频时长
|
||||
// var startInfo = new ProcessStartInfo
|
||||
// {
|
||||
// FileName = _ffprobeExecutablePath,
|
||||
// Arguments = $"-v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"{audioFilePath}\"",
|
||||
// RedirectStandardOutput = true,
|
||||
// RedirectStandardError = true,
|
||||
// UseShellExecute = false,
|
||||
// CreateNoWindow = true
|
||||
// };
|
||||
|
||||
// using (var process = new Process { StartInfo = startInfo })
|
||||
// {
|
||||
// process.Start();
|
||||
// string output = await process.StandardOutput.ReadToEndAsync();
|
||||
// await process.WaitForExitAsync();
|
||||
|
||||
// if (double.TryParse(output, out double duration))
|
||||
// {
|
||||
// audioDurationSeconds = duration;
|
||||
// //Console.WriteLine($"成功获取音频时长: {audioDurationSeconds:F2}s");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Serilog.Log.Error($"获取音频时长失败,将使用图片总时长 ({audioDurationSeconds:F2}s) 作为视频时长: {ex.Message}");
|
||||
// }
|
||||
|
||||
// // 计算图片序列需要循环的次数
|
||||
// int loopCount = 1;
|
||||
// int imageCount = imageList.Count; // 要循环的图片总数(对应loop的size参数)
|
||||
// double singleLoopDuration = imageCount * ImageDisplayDurationSeconds;
|
||||
|
||||
// if (audioDurationSeconds > singleLoopDuration)
|
||||
// {
|
||||
// loopCount = (int)Math.Ceiling(audioDurationSeconds / singleLoopDuration);
|
||||
// //Console.WriteLine($"图片序列将循环 {loopCount} 次以匹配音频时长");
|
||||
// }
|
||||
// // H264编码要求宽高必须为偶数 20260103
|
||||
// if (VideoWidth % 2 != 0)
|
||||
// {
|
||||
// VideoWidth++;
|
||||
// }
|
||||
// if (VideoHeight % 2 != 0)
|
||||
// {
|
||||
// VideoHeight++;
|
||||
// }
|
||||
|
||||
// var arguments = new List<string>
|
||||
// {
|
||||
// "-y", // 覆盖输出文件
|
||||
|
||||
// // 图片序列输入:保留原有逻辑,修复帧率含义(-r 是输出帧率,输入用-framerate更准确)
|
||||
// "-f", "image2",
|
||||
// "-framerate", imageFps.ToString(CultureInfo.InvariantCulture), // 输入帧率(图片播放速度)
|
||||
// "-start_number", "0", // 序列起始编号
|
||||
// "-i", imageSequencePattern, // 图片序列模板(无需引号,后续用ProcessStartInfo传参更安全)
|
||||
|
||||
// // 音频输入
|
||||
// "-i", audioFilePath,
|
||||
|
||||
// // 滤镜修复:补充size参数(核心!解决Number of frames to loop is not set报错)
|
||||
// // loop=循环次数:size=要循环的帧数;loop=-1表示无限循环(配合-shortest更简洁)
|
||||
// "-filter_complex", $"[0:v]loop=loop={loopCount - 1}:size={imageCount}[v]",
|
||||
|
||||
// // 流映射
|
||||
// "-map", "[v]",
|
||||
// "-map", "1:a",
|
||||
|
||||
// // 视频编码:保留你的变量,确保兼容性
|
||||
// "-c:v", VideoCodec,
|
||||
// "-preset", VideoPreset,
|
||||
// "-crf", $"{VideoCrf}",
|
||||
// "-s", $"{VideoWidth}x{VideoHeight}",
|
||||
// "-pix_fmt", "yuv420p", // 兼容所有播放器
|
||||
// "-profile:v", "main",
|
||||
|
||||
// // 音频编码:标准化参数,避免无效值
|
||||
// "-c:a", AudioCodec,
|
||||
// "-b:a", $"{AudioBitrate}",
|
||||
// "-ac", "2", // 立体声
|
||||
// "-ar", "44100", // 标准采样率
|
||||
|
||||
// // 封装优化
|
||||
// "-f", "mp4",
|
||||
// "-movflags", "+faststart",
|
||||
|
||||
// // 同步参数:确保视频时长匹配音频
|
||||
// "-shortest",
|
||||
|
||||
// // 输出路径
|
||||
// outputVideoPath
|
||||
// };
|
||||
|
||||
// // 执行命令
|
||||
// await ExecuteFFmpegAsync(arguments, progress, cancellationToken);
|
||||
// if (File.Exists(outputVideoPath))
|
||||
// {
|
||||
// return outputVideoPath;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// throw new InvalidOperationException("视频合成失败,未生成输出文件。");
|
||||
// }
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// // 清理临时目录
|
||||
// if (Directory.Exists(tempImageDir))
|
||||
// {
|
||||
// Directory.Delete(tempImageDir, recursive: true);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
public async Task<string> CreateVideoFromImagesAndAudioAsync(
|
||||
IEnumerable<string> imageFilePaths,
|
||||
string audioFilePath,
|
||||
string outputVideoPath,
|
||||
int VideoWidth = 1080,
|
||||
int Height = 1920,
|
||||
IProgress<double> progress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
IEnumerable<DouyinMergeVideoDto> imageFilePaths,
|
||||
string audioFilePath,
|
||||
string outputVideoPath,
|
||||
int VideoWidth = 1080,
|
||||
int VideoHeight = 1920,
|
||||
IProgress<double> progress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 输入验证
|
||||
if (imageFilePaths == null || !imageFilePaths.Any())
|
||||
@@ -72,8 +244,11 @@ namespace dy.net.utils
|
||||
|
||||
foreach (var imagePath in imageFilePaths)
|
||||
{
|
||||
if (!File.Exists(imagePath))
|
||||
throw new FileNotFoundException("图片文件未找到。", imagePath);
|
||||
if (!File.Exists(imagePath.Path))
|
||||
throw new FileNotFoundException("图片文件未找到。", imagePath.Path);
|
||||
// 验证图片宽高字段有效性
|
||||
if (imagePath.Width <= 0 || imagePath.Height <= 0)
|
||||
throw new ArgumentException($"图片 {imagePath.Path} 的宽高必须大于0", nameof(imageFilePaths));
|
||||
}
|
||||
|
||||
var outputDirectory = Path.GetDirectoryName(outputVideoPath);
|
||||
@@ -91,22 +266,20 @@ namespace dy.net.utils
|
||||
{
|
||||
for (int i = 0; i < imageList.Count; i++)
|
||||
{
|
||||
string sourcePath = imageList[i];
|
||||
string sourcePath = imageList[i].Path;
|
||||
string extension = Path.GetExtension(sourcePath);
|
||||
string destFileName = $"temp_{i + 1:D3}{extension}";
|
||||
string destPath = Path.Combine(tempImageDir, destFileName);
|
||||
File.Copy(sourcePath, destPath);
|
||||
File.Copy(sourcePath, destPath, true);
|
||||
}
|
||||
|
||||
string imageSequencePattern = Path.Combine(tempImageDir, "temp_%03d" + Path.GetExtension(imageList[0]));
|
||||
string imageSequencePattern = Path.Combine(tempImageDir, "temp_%03d" + Path.GetExtension(imageList.FirstOrDefault().Path));
|
||||
double imageFps = Math.Round(1.0 / ImageDisplayDurationSeconds, 2);
|
||||
|
||||
// --- 修正点 1: 整合音频时长获取逻辑 ---
|
||||
// 我们不再需要 GetAudioFilterAsync,而是直接获取时长用于计算视频循环
|
||||
double audioDurationSeconds = imageList.Count * ImageDisplayDurationSeconds; // 默认值为图片总时长
|
||||
// 获取音频时长
|
||||
double audioDurationSeconds = imageList.Count * ImageDisplayDurationSeconds;
|
||||
try
|
||||
{
|
||||
// 使用 ffprobe 获取音频时长
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = _ffprobeExecutablePath,
|
||||
@@ -126,103 +299,112 @@ namespace dy.net.utils
|
||||
if (double.TryParse(output, out double duration))
|
||||
{
|
||||
audioDurationSeconds = duration;
|
||||
//Console.WriteLine($"成功获取音频时长: {audioDurationSeconds:F2}s");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error($"获取音频时长失败,将使用图片总时长 ({audioDurationSeconds:F2}s) 作为视频时长: {ex.Message}");
|
||||
Log.Error($"获取音频时长失败,将使用图片总时长 ({audioDurationSeconds:F2}s) 作为视频时长: {ex.Message}");
|
||||
}
|
||||
|
||||
// 计算图片序列需要循环的次数
|
||||
// 计算图片序列循环次数
|
||||
int loopCount = 1;
|
||||
int imageCount = imageList.Count; // 要循环的图片总数(对应loop的size参数)
|
||||
int imageCount = imageList.Count;
|
||||
double singleLoopDuration = imageCount * ImageDisplayDurationSeconds;
|
||||
|
||||
if (audioDurationSeconds > singleLoopDuration)
|
||||
{
|
||||
loopCount = (int)Math.Ceiling(audioDurationSeconds / singleLoopDuration);
|
||||
//Console.WriteLine($"图片序列将循环 {loopCount} 次以匹配音频时长");
|
||||
}
|
||||
// H264编码要求宽高必须为偶数 20260103
|
||||
if (VideoWidth % 2 != 0)
|
||||
{
|
||||
VideoWidth++;
|
||||
}
|
||||
if (VideoHeight % 2 != 0)
|
||||
{
|
||||
VideoHeight++;
|
||||
}
|
||||
|
||||
// H264编码要求宽高为偶数
|
||||
if (VideoWidth % 2 != 0) VideoWidth++;
|
||||
if (VideoHeight % 2 != 0) VideoHeight++;
|
||||
|
||||
// ========== 核心优化:图片适配滤镜 ==========
|
||||
// scale=iw*min({VideoWidth}/iw\,{VideoHeight}/ih):ih*min({VideoWidth}/iw\,{VideoHeight}/ih) 等比例缩放
|
||||
// pad={VideoWidth}:{VideoHeight}:(ow-iw)/2:(oh-ih)/2:black 居中填充黑色背景
|
||||
string fitFilter = $"scale=iw*min({VideoWidth}/iw\\,{VideoHeight}/ih):ih*min({VideoWidth}/iw\\,{VideoHeight}/ih),pad={VideoWidth}:{VideoHeight}:(ow-iw)/2:(oh-ih)/2:black";
|
||||
|
||||
// 组合最终滤镜:先适配尺寸,再循环
|
||||
string filterComplex = $"[0:v]{fitFilter},loop=loop={loopCount - 1}:size={imageCount}[v]";
|
||||
|
||||
var arguments = new List<string>
|
||||
{
|
||||
"-y", // 覆盖输出文件
|
||||
{
|
||||
"-y", // 覆盖输出文件
|
||||
|
||||
// 图片序列输入:保留原有逻辑,修复帧率含义(-r 是输出帧率,输入用-framerate更准确)
|
||||
"-f", "image2",
|
||||
"-framerate", imageFps.ToString(CultureInfo.InvariantCulture), // 输入帧率(图片播放速度)
|
||||
"-start_number", "0", // 序列起始编号
|
||||
"-i", imageSequencePattern, // 图片序列模板(无需引号,后续用ProcessStartInfo传参更安全)
|
||||
// 图片序列输入
|
||||
"-f", "image2",
|
||||
"-framerate", imageFps.ToString(CultureInfo.InvariantCulture),
|
||||
"-start_number", "1", // 修正:临时文件是 temp_001、temp_002... 所以起始编号为1
|
||||
"-i", imageSequencePattern,
|
||||
|
||||
// 音频输入
|
||||
"-i", audioFilePath,
|
||||
// 音频输入
|
||||
"-i", audioFilePath,
|
||||
|
||||
// 滤镜修复:补充size参数(核心!解决Number of frames to loop is not set报错)
|
||||
// loop=循环次数:size=要循环的帧数;loop=-1表示无限循环(配合-shortest更简洁)
|
||||
"-filter_complex", $"[0:v]loop=loop={loopCount - 1}:size={imageCount}[v]",
|
||||
// 滤镜:尺寸适配 + 循环
|
||||
"-filter_complex", filterComplex,
|
||||
|
||||
// 流映射
|
||||
"-map", "[v]",
|
||||
"-map", "1:a",
|
||||
// 流映射
|
||||
"-map", "[v]",
|
||||
"-map", "1:a",
|
||||
|
||||
// 视频编码:保留你的变量,确保兼容性
|
||||
"-c:v", VideoCodec,
|
||||
"-preset", VideoPreset,
|
||||
"-crf", $"{VideoCrf}",
|
||||
"-s", $"{VideoWidth}x{VideoHeight}",
|
||||
"-pix_fmt", "yuv420p", // 兼容所有播放器
|
||||
"-profile:v", "main",
|
||||
// 视频编码参数
|
||||
"-c:v", VideoCodec,
|
||||
"-preset", VideoPreset,
|
||||
"-crf", $"{VideoCrf}",
|
||||
"-s", $"{VideoWidth}x{VideoHeight}",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-profile:v", "main",
|
||||
|
||||
// 音频编码:标准化参数,避免无效值
|
||||
"-c:a", AudioCodec,
|
||||
"-b:a", $"{AudioBitrate}",
|
||||
"-ac", "2", // 立体声
|
||||
"-ar", "44100", // 标准采样率
|
||||
// 音频编码参数
|
||||
"-c:a", AudioCodec,
|
||||
"-b:a", $"{AudioBitrate}",
|
||||
"-ac", "2",
|
||||
"-ar", "44100",
|
||||
|
||||
// 封装优化
|
||||
"-f", "mp4",
|
||||
"-movflags", "+faststart",
|
||||
// 封装优化
|
||||
"-f", "mp4",
|
||||
"-movflags", "+faststart",
|
||||
|
||||
// 同步参数:确保视频时长匹配音频
|
||||
"-shortest",
|
||||
// 同步参数
|
||||
"-shortest",
|
||||
|
||||
// 输出路径
|
||||
outputVideoPath
|
||||
};
|
||||
// 输出路径
|
||||
outputVideoPath
|
||||
};
|
||||
|
||||
// 执行命令
|
||||
// 执行FFmpeg命令
|
||||
await ExecuteFFmpegAsync(arguments, progress, cancellationToken);
|
||||
if (File.Exists(outputVideoPath))
|
||||
{
|
||||
return outputVideoPath;
|
||||
}
|
||||
else
|
||||
|
||||
if (!File.Exists(outputVideoPath))
|
||||
{
|
||||
throw new InvalidOperationException("视频合成失败,未生成输出文件。");
|
||||
}
|
||||
|
||||
return outputVideoPath;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 清理临时目录
|
||||
// 清理临时目录(增加异常捕获,避免删除失败导致程序崩溃)
|
||||
if (Directory.Exists(tempImageDir))
|
||||
{
|
||||
Directory.Delete(tempImageDir, recursive: true);
|
||||
try
|
||||
{
|
||||
Directory.Delete(tempImageDir, recursive: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"清理临时目录失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将单个视频转封装为统一编码/分辨率的临时视频(解决concat合并兼容问题)
|
||||
/// </summary>
|
||||
@@ -232,7 +414,7 @@ namespace dy.net.utils
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>统一参数后的临时视频路径</returns>
|
||||
private async Task<string> CreateUnifiedTempVideoAsync(
|
||||
DouyinDynamicVideoDto videoDto,
|
||||
DouyinMergeVideoDto videoDto,
|
||||
int targetWidth, // 外部传入的动态目标宽(已取所有视频的最大值,偶数)
|
||||
int targetHeight, // 外部传入的动态目标高(已取所有视频的最大值,偶数)
|
||||
CancellationToken cancellationToken)
|
||||
@@ -325,13 +507,13 @@ namespace dy.net.utils
|
||||
/// <returns>输出视频路径</returns>
|
||||
// 移除了 width = 1080 和 height = 1920 两个传入参数
|
||||
public async Task<string> MergeMultipleVideosAsync(
|
||||
List<DouyinDynamicVideoDto> videoFilePaths,
|
||||
List<DouyinMergeVideoDto> videoFilePaths,
|
||||
string audioPath,
|
||||
string savePath,
|
||||
IProgress<double> progress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
List<DouyinDynamicVideoDto> validVideoList = new List<DouyinDynamicVideoDto>();
|
||||
List<DouyinMergeVideoDto> validVideoList = new List<DouyinMergeVideoDto>();
|
||||
List<string> tempUnifiedVideoPaths = new List<string>();
|
||||
|
||||
// 输入验证
|
||||
|
||||
Reference in New Issue
Block a user