修复up主视频下载,不下载图文视频的bug
增加图文视频下载配置(是否合成视频,是否单独下载图片,单独下载音频)
This commit is contained in:
+1
-1
@@ -52,7 +52,7 @@ namespace dy.net
|
||||
// 构建应用
|
||||
var app = builder.Build();
|
||||
|
||||
Log.Debug("DOWN_IMGVIDEO=" + downImgConfig);
|
||||
Log.Debug("ffmpeg=" + downImgConfig);
|
||||
|
||||
// 配置中间件
|
||||
ConfigureMiddleware(app, builder.Environment);
|
||||
|
||||
@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
|
||||
<History>True|2025-11-24T00:07:00.9951205Z||;True|2025-11-23T23:34:50.0030826+08:00||;True|2025-11-23T23:32:36.7452616+08:00||;True|2025-11-22T23:18:56.3202345+08:00||;True|2025-11-22T22:52:51.7203302+08:00||;True|2025-11-22T22:52:42.9620946+08:00||;True|2025-11-22T22:52:09.8257640+08:00||;True|2025-11-22T22:39:31.5894141+08:00||;True|2025-11-22T22:31:11.0704815+08:00||;True|2025-11-22T22:20:36.4579131+08:00||;True|2025-11-22T22:19:10.2281364+08:00||;True|2025-11-22T19:34:45.9336901+08:00||;True|2025-11-12T23:06:38.7834109+08:00||;False|2025-11-12T23:01:02.2269478+08:00||;True|2025-10-22T22:08:11.6423148+08:00||;True|2025-10-22T21:54:24.7180547+08:00||;True|2025-10-22T21:40:03.5194315+08:00||;True|2025-10-22T21:28:28.8962766+08:00||;True|2025-10-22T21:22:47.9631689+08:00||;True|2025-10-22T21:18:24.5274318+08:00||;True|2025-10-22T21:14:51.6386326+08:00||;False|2025-10-22T21:14:07.6282769+08:00||;True|2025-10-22T21:03:48.9892860+08:00||;True|2025-10-22T21:00:56.3617243+08:00||;False|2025-10-22T21:00:30.5472941+08:00||;True|2025-10-22T20:51:29.6155916+08:00||;False|2025-10-22T20:50:47.1882956+08:00||;True|2025-10-22T15:01:04.1668366+08:00||;True|2025-10-22T14:49:43.6340569+08:00||;True|2025-10-22T14:38:39.4685603+08:00||;True|2025-10-21T18:35:28.8392541+08:00||;True|2025-10-20T10:31:05.5865212+08:00||;True|2025-10-20T10:20:41.5717101+08:00||;True|2025-10-19T10:08:33.6669332+08:00||;False|2025-10-19T10:07:22.3337545+08:00||;False|2025-10-19T10:05:22.9484805+08:00||;True|2025-10-10T16:54:27.1472888+08:00||;False|2025-10-10T16:53:44.1700030+08:00||;False|2025-10-10T16:52:48.1740453+08:00||;False|2025-10-10T16:51:21.5067253+08:00||;False|2025-10-10T16:50:19.2140597+08:00||;False|2025-10-10T16:49:21.2213290+08:00||;False|2025-10-10T16:48:47.7229948+08:00||;False|2025-10-10T16:48:15.1258700+08:00||;True|2025-09-30T13:29:42.2354235+08:00||;True|2025-09-25T11:44:52.6858389+08:00||;True|2025-09-25T11:08:27.8810109+08:00||;True|2025-09-25T09:28:50.2563374+08:00||;True|2025-09-24T16:14:02.3072184+08:00||;True|2025-09-23T22:38:52.4414757+08:00||;True|2025-09-23T22:19:07.1006356+08:00||;True|2025-09-23T22:18:01.5945225+08:00||;True|2025-09-23T22:06:15.6293613+08:00||;True|2025-09-23T21:53:14.2977444+08:00||;True|2025-09-23T21:46:55.2322411+08:00||;False|2025-09-23T21:45:23.7858854+08:00||;False|2025-09-23T21:03:51.8325689+08:00||;True|2025-09-23T10:03:32.2251920+08:00||;False|2025-09-23T09:38:08.2372201+08:00||;False|2025-09-23T09:37:49.9390545+08:00||;True|2024-03-11T21:31:45.8102398+08:00||;True|2024-03-11T07:26:24.7660541+08:00||;True|2024-03-08T22:08:40.0154831+08:00||;True|2024-03-03T10:14:36.8109114+08:00||;True|2024-03-02T18:44:57.3288537+08:00||;True|2024-01-24T17:51:37.9164415+08:00||;True|2024-01-24T16:36:50.5612157+08:00||;True|2024-01-24T15:51:35.7556653+08:00||;True|2024-01-17T23:40:40.7526618+08:00||;True|2024-01-17T23:36:10.3692844+08:00||;True|2024-01-17T23:22:03.2378834+08:00||;True|2024-01-03T11:35:44.7118292+08:00||;True|2024-01-03T11:11:23.4270453+08:00||;True|2024-01-03T11:04:35.2081526+08:00||;True|2024-01-03T10:57:03.7053107+08:00||;True|2024-01-03T10:51:50.7463989+08:00||;False|2024-01-03T10:50:24.9775312+08:00||;True|2024-01-03T10:47:30.1128183+08:00||;True|2024-01-03T10:42:55.8640657+08:00||;True|2024-01-03T09:24:24.3436056+08:00||;True|2024-01-02T23:40:38.2001198+08:00||;True|2024-01-02T23:08:36.7230444+08:00||;True|2024-01-02T22:53:43.9658255+08:00||;True|2024-01-02T22:25:38.5545279+08:00||;False|2024-01-02T22:24:42.6577609+08:00||;True|2024-01-02T22:04:33.4708889+08:00||;True|2024-01-02T21:49:57.9249898+08:00||;True|2023-12-28T22:42:58.5976180+08:00||;True|2023-12-28T22:24:09.2051205+08:00||;True|2023-12-28T22:05:48.4865497+08:00||;True|2023-12-28T21:50:57.4549336+08:00||;True|2023-12-28T21:45:30.4557939+08:00||;True|2023-12-28T21:19:59.3614594+08:00||;False|2023-12-28T21:17:04.3069097+08:00||;False|2023-12-28T21:15:56.7788810+08:00||;False|2023-12-28T21:14:57.3617402+08:00||;False|2023-12-28T21:10:45.4382469+08:00||;True|2023-12-28T19:45:33.1431914+08:00||;True|2023-12-26T11:30:36.6418716+08:00||;True|2023-12-26T11:17:40.6433651+08:00||;</History>
|
||||
<History>True|2025-11-24T00:17:50.3994607Z||;True|2025-11-24T08:07:00.9951205+08:00||;True|2025-11-23T23:34:50.0030826+08:00||;True|2025-11-23T23:32:36.7452616+08:00||;True|2025-11-22T23:18:56.3202345+08:00||;True|2025-11-22T22:52:51.7203302+08:00||;True|2025-11-22T22:52:42.9620946+08:00||;True|2025-11-22T22:52:09.8257640+08:00||;True|2025-11-22T22:39:31.5894141+08:00||;True|2025-11-22T22:31:11.0704815+08:00||;True|2025-11-22T22:20:36.4579131+08:00||;True|2025-11-22T22:19:10.2281364+08:00||;True|2025-11-22T19:34:45.9336901+08:00||;True|2025-11-12T23:06:38.7834109+08:00||;False|2025-11-12T23:01:02.2269478+08:00||;True|2025-10-22T22:08:11.6423148+08:00||;True|2025-10-22T21:54:24.7180547+08:00||;True|2025-10-22T21:40:03.5194315+08:00||;True|2025-10-22T21:28:28.8962766+08:00||;True|2025-10-22T21:22:47.9631689+08:00||;True|2025-10-22T21:18:24.5274318+08:00||;True|2025-10-22T21:14:51.6386326+08:00||;False|2025-10-22T21:14:07.6282769+08:00||;True|2025-10-22T21:03:48.9892860+08:00||;True|2025-10-22T21:00:56.3617243+08:00||;False|2025-10-22T21:00:30.5472941+08:00||;True|2025-10-22T20:51:29.6155916+08:00||;False|2025-10-22T20:50:47.1882956+08:00||;True|2025-10-22T15:01:04.1668366+08:00||;True|2025-10-22T14:49:43.6340569+08:00||;True|2025-10-22T14:38:39.4685603+08:00||;True|2025-10-21T18:35:28.8392541+08:00||;True|2025-10-20T10:31:05.5865212+08:00||;True|2025-10-20T10:20:41.5717101+08:00||;True|2025-10-19T10:08:33.6669332+08:00||;False|2025-10-19T10:07:22.3337545+08:00||;False|2025-10-19T10:05:22.9484805+08:00||;True|2025-10-10T16:54:27.1472888+08:00||;False|2025-10-10T16:53:44.1700030+08:00||;False|2025-10-10T16:52:48.1740453+08:00||;False|2025-10-10T16:51:21.5067253+08:00||;False|2025-10-10T16:50:19.2140597+08:00||;False|2025-10-10T16:49:21.2213290+08:00||;False|2025-10-10T16:48:47.7229948+08:00||;False|2025-10-10T16:48:15.1258700+08:00||;True|2025-09-30T13:29:42.2354235+08:00||;True|2025-09-25T11:44:52.6858389+08:00||;True|2025-09-25T11:08:27.8810109+08:00||;True|2025-09-25T09:28:50.2563374+08:00||;True|2025-09-24T16:14:02.3072184+08:00||;True|2025-09-23T22:38:52.4414757+08:00||;True|2025-09-23T22:19:07.1006356+08:00||;True|2025-09-23T22:18:01.5945225+08:00||;True|2025-09-23T22:06:15.6293613+08:00||;True|2025-09-23T21:53:14.2977444+08:00||;True|2025-09-23T21:46:55.2322411+08:00||;False|2025-09-23T21:45:23.7858854+08:00||;False|2025-09-23T21:03:51.8325689+08:00||;True|2025-09-23T10:03:32.2251920+08:00||;False|2025-09-23T09:38:08.2372201+08:00||;False|2025-09-23T09:37:49.9390545+08:00||;True|2024-03-11T21:31:45.8102398+08:00||;True|2024-03-11T07:26:24.7660541+08:00||;True|2024-03-08T22:08:40.0154831+08:00||;True|2024-03-03T10:14:36.8109114+08:00||;True|2024-03-02T18:44:57.3288537+08:00||;True|2024-01-24T17:51:37.9164415+08:00||;True|2024-01-24T16:36:50.5612157+08:00||;True|2024-01-24T15:51:35.7556653+08:00||;True|2024-01-17T23:40:40.7526618+08:00||;True|2024-01-17T23:36:10.3692844+08:00||;True|2024-01-17T23:22:03.2378834+08:00||;True|2024-01-03T11:35:44.7118292+08:00||;True|2024-01-03T11:11:23.4270453+08:00||;True|2024-01-03T11:04:35.2081526+08:00||;True|2024-01-03T10:57:03.7053107+08:00||;True|2024-01-03T10:51:50.7463989+08:00||;False|2024-01-03T10:50:24.9775312+08:00||;True|2024-01-03T10:47:30.1128183+08:00||;True|2024-01-03T10:42:55.8640657+08:00||;True|2024-01-03T09:24:24.3436056+08:00||;True|2024-01-02T23:40:38.2001198+08:00||;True|2024-01-02T23:08:36.7230444+08:00||;True|2024-01-02T22:53:43.9658255+08:00||;True|2024-01-02T22:25:38.5545279+08:00||;False|2024-01-02T22:24:42.6577609+08:00||;True|2024-01-02T22:04:33.4708889+08:00||;True|2024-01-02T21:49:57.9249898+08:00||;True|2023-12-28T22:42:58.5976180+08:00||;True|2023-12-28T22:24:09.2051205+08:00||;True|2023-12-28T22:05:48.4865497+08:00||;True|2023-12-28T21:50:57.4549336+08:00||;True|2023-12-28T21:45:30.4557939+08:00||;True|2023-12-28T21:19:59.3614594+08:00||;False|2023-12-28T21:17:04.3069097+08:00||;False|2023-12-28T21:15:56.7788810+08:00||;False|2023-12-28T21:14:57.3617402+08:00||;False|2023-12-28T21:10:45.4382469+08:00||;True|2023-12-28T19:45:33.1431914+08:00||;True|2023-12-26T11:30:36.6418716+08:00||;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -45,6 +45,13 @@
|
||||
<a-switch v-model:checked="formState.DownImageVideo" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="downImgVideo" has-feedback label="单独下载图文-音频" name="DownMp3" :wrapper-col="{ span: 6 }">
|
||||
<a-switch v-model:checked="formState.DownMp3" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="downImgVideo" has-feedback label="单独下载图文-图片" name="DownImage" :wrapper-col="{ span: 6 }">
|
||||
<a-switch v-model:checked="formState.DownImage" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
@@ -85,6 +92,8 @@ interface FormState {
|
||||
UperSaveTogether: boolean;
|
||||
UperUseViedoTitle: boolean;
|
||||
LogKeepDay: number;
|
||||
DownImage: boolean;
|
||||
DownMp3: boolean;
|
||||
}
|
||||
|
||||
// 表单初始数据
|
||||
@@ -97,6 +106,8 @@ const formState: UnwrapRef<FormState> = reactive({
|
||||
UperSaveTogether: false,
|
||||
DownImageVideo: false,
|
||||
DownImageVideoFromEnv: false,
|
||||
DownMp3: false,
|
||||
DownImage: false,
|
||||
});
|
||||
|
||||
// 表单校验规则
|
||||
@@ -144,6 +155,8 @@ const getConfig = () => {
|
||||
UperUseViedoTitle: res.data.uperUseViedoTitle,
|
||||
UperSaveTogether: res.data.uperSaveTogether,
|
||||
LogKeepDay: res.data.logKeepDay,
|
||||
DownImage: res.data.downImage,
|
||||
DownMp3: res.data.downMp3,
|
||||
});
|
||||
|
||||
// 监听环境变量配置,控制是否显示图片视频下载选项
|
||||
|
||||
+165
-32
@@ -70,6 +70,81 @@ namespace dy.image
|
||||
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];
|
||||
// // 重命名为有规律的文件名,如 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(tempImageDir, destFileName);
|
||||
// File.Copy(sourcePath, destPath);
|
||||
// }
|
||||
|
||||
// // 关键步骤 2: 构建符合你成功经验的 FFmpeg 命令
|
||||
// string imageSequencePattern = Path.Combine(tempImageDir, "temp_%03d" + Path.GetExtension(imageList[0]));
|
||||
// double imageFps = Math.Round(1.0 / ImageDisplayDurationSeconds, 2); ; // 例如 1/3 = 0.333... fps
|
||||
|
||||
// // 音频滤镜:如果音频比图片长则截断,比图片短则循环
|
||||
// string audioFilter = await GetAudioFilterAsync(audioFilePath, imageList.Count * ImageDisplayDurationSeconds);
|
||||
|
||||
|
||||
// // 构建 FFmpeg 参数列表
|
||||
// var arguments = new List<string>
|
||||
// {
|
||||
// "-y", // 覆盖输出文件
|
||||
// // --- 输入图片序列的配置 ---
|
||||
// "-f", "image2", // 明确指定输入为图片序列
|
||||
// "-vcodec", "webp", // 强制使用 WebP 解码器
|
||||
// "-r", imageFps.ToString(CultureInfo.InvariantCulture), // 设置图片播放速度
|
||||
// $"-i", $"\"{imageSequencePattern}\"", // 图片序列的路径模式
|
||||
// // --- 输入音频的配置 ---
|
||||
// audioFilter, // 应用音频滤镜(可能为空)
|
||||
// $"-i", $"\"{audioFilePath}\"", // 音频文件路径
|
||||
// // --- 视频编码配置 ---
|
||||
// "-c:v", VideoCodec, // 视频编码器 (如 libx264)
|
||||
// "-preset", VideoPreset, // 编码预设 (如 medium)
|
||||
// $"-crf", $"{VideoCrf}", // 视频质量因子
|
||||
// $"-s", $"{VideoWidth}x{VideoHeight}", // 输出视频分辨率
|
||||
// "-pix_fmt", "yuv420p", // 像素格式,确保兼容性
|
||||
// // --- 音频编码配置 ---
|
||||
// "-c:a", AudioCodec, // 音频编码器 (如 aac)
|
||||
// $"-b:a", $"{AudioBitrate}", // 音频比特率 (如 192k)
|
||||
// // --- 输出文件 ---
|
||||
// $"\"{outputVideoPath}\"" // 最终输出视频路径
|
||||
// };
|
||||
|
||||
// string args = string.Join(" ", arguments);
|
||||
// Console.WriteLine($"执行FFmpeg命令: {_ffmpegExecutablePath} {args}");
|
||||
|
||||
// // 执行命令
|
||||
// await ExecuteFFmpegAsync(args, progress, cancellationToken);
|
||||
|
||||
// if (File.Exists(outputVideoPath))
|
||||
// {
|
||||
// return outputVideoPath;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// throw new InvalidOperationException("视频合成失败,未生成输出文件。");
|
||||
// }
|
||||
//}
|
||||
//finally
|
||||
//{
|
||||
// // 关键步骤 3: 清理临时文件
|
||||
// if (Directory.Exists(tempImageDir))
|
||||
// {
|
||||
// Directory.Delete(tempImageDir, recursive: true);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
// 关键步骤 1: 创建临时目录并生成有序图片序列
|
||||
string tempImageDir = Path.Combine(AppContext.BaseDirectory, "temp", Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempImageDir);
|
||||
@@ -80,51 +155,108 @@ namespace dy.image
|
||||
for (int i = 0; i < imageList.Count; i++)
|
||||
{
|
||||
string sourcePath = imageList[i];
|
||||
// 重命名为有规律的文件名,如 temp_001.jpg, temp_002.png
|
||||
string extension = Path.GetExtension(sourcePath);
|
||||
string destFileName = $"temp_{i + 1:D3}{extension}"; // D3 确保是3位数字,不足补0
|
||||
string destFileName = $"temp_{i + 1:D3}{extension}";
|
||||
string destPath = Path.Combine(tempImageDir, destFileName);
|
||||
File.Copy(sourcePath, destPath);
|
||||
}
|
||||
|
||||
// 关键步骤 2: 构建符合你成功经验的 FFmpeg 命令
|
||||
string imageSequencePattern = Path.Combine(tempImageDir, "temp_%03d" + Path.GetExtension(imageList[0]));
|
||||
double imageFps = Math.Round(1.0 / ImageDisplayDurationSeconds, 2); ; // 例如 1/3 = 0.333... fps
|
||||
double imageFps = Math.Round(1.0 / ImageDisplayDurationSeconds, 2);
|
||||
|
||||
// 音频滤镜:如果音频比图片长则截断,比图片短则循环
|
||||
string audioFilter = await GetAudioFilterAsync(audioFilePath, imageList.Count * ImageDisplayDurationSeconds);
|
||||
|
||||
// 构建 FFmpeg 参数列表
|
||||
var arguments = new List<string>
|
||||
// --- 修正点 1: 整合音频时长获取逻辑 ---
|
||||
// 我们不再需要 GetAudioFilterAsync,而是直接获取时长用于计算视频循环
|
||||
double audioDurationSeconds = imageList.Count * ImageDisplayDurationSeconds; // 默认值为图片总时长
|
||||
try
|
||||
{
|
||||
"-y", // 覆盖输出文件
|
||||
// --- 输入图片序列的配置 ---
|
||||
"-f", "image2", // 明确指定输入为图片序列
|
||||
"-vcodec", "webp", // 强制使用 WebP 解码器
|
||||
"-r", imageFps.ToString(CultureInfo.InvariantCulture), // 设置图片播放速度
|
||||
$"-i", $"\"{imageSequencePattern}\"", // 图片序列的路径模式
|
||||
// --- 输入音频的配置 ---
|
||||
audioFilter, // 应用音频滤镜(可能为空)
|
||||
$"-i", $"\"{audioFilePath}\"", // 音频文件路径
|
||||
// --- 视频编码配置 ---
|
||||
"-c:v", VideoCodec, // 视频编码器 (如 libx264)
|
||||
"-preset", VideoPreset, // 编码预设 (如 medium)
|
||||
$"-crf", $"{VideoCrf}", // 视频质量因子
|
||||
$"-s", $"{VideoWidth}x{VideoHeight}", // 输出视频分辨率
|
||||
"-pix_fmt", "yuv420p", // 像素格式,确保兼容性
|
||||
// --- 音频编码配置 ---
|
||||
"-c:a", AudioCodec, // 音频编码器 (如 aac)
|
||||
$"-b:a", $"{AudioBitrate}", // 音频比特率 (如 192k)
|
||||
// --- 输出文件 ---
|
||||
$"\"{outputVideoPath}\"" // 最终输出视频路径
|
||||
};
|
||||
// 使用 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)
|
||||
{
|
||||
Console.WriteLine($"获取音频时长失败,将使用图片总时长 ({audioDurationSeconds:F2}s) 作为视频时长: {ex.Message}");
|
||||
}
|
||||
|
||||
// 计算图片序列需要循环的次数
|
||||
int loopCount = 1;
|
||||
double singleLoopDuration = imageList.Count * ImageDisplayDurationSeconds;
|
||||
if (audioDurationSeconds > singleLoopDuration)
|
||||
{
|
||||
loopCount = (int)Math.Ceiling(audioDurationSeconds / singleLoopDuration);
|
||||
Console.WriteLine($"图片序列将循环 {loopCount} 次以匹配音频时长");
|
||||
}
|
||||
|
||||
// --- 修正点 2: 重新构建 FFmpeg 参数列表 ---
|
||||
var arguments = new List<string>
|
||||
{
|
||||
"-y", // 覆盖输出文件
|
||||
|
||||
// --- 所有输入文件放在最前面 ---
|
||||
// 图片序列输入
|
||||
"-f", "image2",
|
||||
"-r", imageFps.ToString(CultureInfo.InvariantCulture),
|
||||
"-i", $"\"{imageSequencePattern}\"",
|
||||
|
||||
// 音频输入
|
||||
"-i", $"\"{audioFilePath}\"",
|
||||
|
||||
// --- 修正点 3: 使用 filter_complex 对视频流进行循环 ---
|
||||
// [0:v] 表示第一个输入(图片序列)的视频流
|
||||
// loop={loopCount-1} 表示循环 (次数-1) 次
|
||||
// [v] 是处理后的视频流的别名
|
||||
"-filter_complex", $"\"[0:v]loop={loopCount - 1}[v]\"",
|
||||
|
||||
// --- 修正点 4: 明确映射输出流 ---
|
||||
// 将处理后的视频流 [v] 映射到输出
|
||||
"-map", "\"[v]\"",
|
||||
// 将第二个输入(音频文件)的音频流映射到输出
|
||||
"-map", "\"1:a\"",
|
||||
|
||||
// --- 视频编码配置 ---
|
||||
"-c:v", VideoCodec,
|
||||
"-preset", VideoPreset,
|
||||
"-crf", $"{VideoCrf}",
|
||||
"-s", $"{VideoWidth}x{VideoHeight}",
|
||||
"-pix_fmt", "yuv420p",
|
||||
|
||||
// --- 音频编码配置 ---
|
||||
"-c:a", AudioCodec,
|
||||
"-b:a", $"{AudioBitrate}",
|
||||
|
||||
// --- 修正点 5: 使用 -shortest 参数 ---
|
||||
// 确保视频和音频同时结束,即使循环次数计算得不完全精确
|
||||
"-shortest",
|
||||
|
||||
// --- 输出文件 ---
|
||||
$"\"{outputVideoPath}\""
|
||||
};
|
||||
|
||||
string args = string.Join(" ", arguments);
|
||||
Console.WriteLine($"执行FFmpeg命令: {_ffmpegExecutablePath} {args}");
|
||||
|
||||
// 执行命令
|
||||
await ExecuteFFmpegAsync(args, progress, cancellationToken);
|
||||
|
||||
if (File.Exists(outputVideoPath))
|
||||
{
|
||||
return outputVideoPath;
|
||||
@@ -133,10 +265,11 @@ namespace dy.image
|
||||
{
|
||||
throw new InvalidOperationException("视频合成失败,未生成输出文件。");
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 关键步骤 3: 清理临时文件
|
||||
// 清理临时目录
|
||||
if (Directory.Exists(tempImageDir))
|
||||
{
|
||||
Directory.Delete(tempImageDir, recursive: true);
|
||||
|
||||
@@ -13,7 +13,18 @@
|
||||
_downloadHelper = downloadHelper;
|
||||
_fFmpegHelper = fFmpegHelper;
|
||||
}
|
||||
public async Task<bool> MergeToVideo(string rootPath, MediaMergeRequest request,string outputVideoPath,string fileNamefolder)
|
||||
/// <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
|
||||
@@ -31,17 +42,19 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < rawImages.Count(); i++)
|
||||
if(downImage)
|
||||
{
|
||||
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);
|
||||
for (int i = 0; i < rawImages.Count(); 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))
|
||||
@@ -51,17 +64,25 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < rawAudios.Count(); i++)
|
||||
if(downMp3)
|
||||
{
|
||||
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);
|
||||
for (int i = 0; i < rawAudios.Count(); 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()}");
|
||||
@@ -72,7 +93,7 @@
|
||||
// 根据图片数量调整每张图片显示时长
|
||||
if (request.ImageUrls.Count <= 3)
|
||||
{
|
||||
request.ImageDurationPerSecond = 5;
|
||||
request.ImageDurationPerSecond = 3;
|
||||
}
|
||||
if (request.ImageUrls.Count > 20)
|
||||
{
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace dy.net.extension
|
||||
|
||||
services.AddHttpClient("dy_uper", client =>
|
||||
{
|
||||
//client.DefaultRequestHeaders.Referrer = new Uri("https://www.douyin.com/user/self?showTab=like");
|
||||
client.DefaultRequestHeaders.Referrer = new Uri("https://www.douyin.com/user/MS4wLjABAAAA1zFIBhWG-3qS8MiggDMhyCpqDnvfGYf_mtsdgtBzV7A?from_tab_name=main&showTab=post&vid=7576282367263807451");
|
||||
});
|
||||
// 配置HttpClient(Startup.cs或Program.cs)
|
||||
services.AddHttpClient("dy_download")
|
||||
|
||||
+36
-18
@@ -4,6 +4,7 @@ using dy.net.dto;
|
||||
using dy.net.model;
|
||||
using dy.net.service;
|
||||
using dy.net.utils;
|
||||
using Newtonsoft.Json;
|
||||
using Quartz;
|
||||
using Quartz.Util;
|
||||
using Serilog;
|
||||
@@ -133,6 +134,10 @@ namespace dy.net.job
|
||||
return;
|
||||
}
|
||||
|
||||
//将配置项打印日志
|
||||
Log.Debug($"最新:{JsonConvert.SerializeObject(config)}");
|
||||
|
||||
|
||||
// 2. 从配置中获取每页请求数量
|
||||
if (config.BatchCount > 0)
|
||||
count = config.BatchCount.ToString();
|
||||
@@ -356,9 +361,12 @@ namespace dy.net.job
|
||||
// 如果配置了下载图片视频,则处理图片集并合成视频
|
||||
if (_downImageVideo)
|
||||
{
|
||||
var mergevideo = await ProcessImageSetAndMergeToVideo(cookie, item, data, config);
|
||||
if (mergevideo != null)
|
||||
videos.Add(mergevideo);
|
||||
if(config.DownImage||config.DownMp3||config.DownImage)
|
||||
{
|
||||
var mergevideo = await ProcessImageSetAndMergeToVideo(cookie, item, data, config);
|
||||
if (mergevideo != null)
|
||||
videos.Add(mergevideo);
|
||||
}
|
||||
}
|
||||
}
|
||||
return videos;
|
||||
@@ -404,9 +412,13 @@ namespace dy.net.job
|
||||
// 下载视频
|
||||
if (!await _douyinService.DownloadAsync(videoUrl, savePath, cookie.Cookies))
|
||||
{
|
||||
Log.Error($"{JobType}-视频[{TikTokFileNameHelper.SanitizePath(item.Desc)}]下载失败");
|
||||
Log.Error($"{JobType}-{item?.Author?.Nickname??""}-视频[{TikTokFileNameHelper.SanitizePath(item.Desc)}]下载失败");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"{JobType}-{item?.Author?.Nickname ?? ""}-视频[{TikTokFileNameHelper.SanitizePath(item.Desc)}]下载完成");
|
||||
}
|
||||
|
||||
// 下载视频封面
|
||||
await DownVideoCover(item, saveFolder, cookie, config);
|
||||
@@ -455,7 +467,7 @@ namespace dy.net.job
|
||||
var imageService = _serviceProvider.GetService<ImageMergeToVideoService>();
|
||||
if (imageService == null)
|
||||
{
|
||||
Log.Error($"{JobType} -图片视频同步-无法创建 ImageMergeToVideoService。");
|
||||
Log.Error($"{JobType} -图片视频同步-无法实例化 ImageMergeToVideoService。");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -484,29 +496,35 @@ namespace dy.net.job
|
||||
};
|
||||
|
||||
// 执行图片合成视频操作
|
||||
var mergeResult = await imageService.MergeToVideo(AppContext.BaseDirectory, reqParams, savePath, fileNamefolder);
|
||||
var mergeResult = await imageService.MergeToVideo(AppContext.BaseDirectory, reqParams, savePath, fileNamefolder,config.DownImageVideo,config.DownImage,config.DownMp3);
|
||||
if (!mergeResult)
|
||||
{
|
||||
Log.Error($"{JobType}-图片视频同步-视频[{TikTokFileNameHelper.SanitizePath(item.Desc)}]合成失败");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查合成后的视频文件是否有效
|
||||
if (!File.Exists(savePath) || new FileInfo(savePath).Length <= 0)
|
||||
if (config.DownImageVideo)
|
||||
{
|
||||
Log.Error($"{JobType}-图片视频同步-视频[{TikTokFileNameHelper.SanitizePath(item.Desc)}]合成失败");
|
||||
// 清理无效的文件和文件夹
|
||||
if (Directory.Exists(fileNamefolder))
|
||||
// 检查合成后的视频文件是否有效
|
||||
if (!File.Exists(savePath) || new FileInfo(savePath).Length <= 0)
|
||||
{
|
||||
File.Delete(savePath);
|
||||
Directory.Delete(fileNamefolder, true);
|
||||
Log.Error($"{JobType}-图片视频同步-已删除合成失败的视频文件和目录");
|
||||
Log.Error($"{JobType}-图片视频同步-视频[{TikTokFileNameHelper.SanitizePath(item.Desc)}]合成失败");
|
||||
// 清理无效的文件和文件夹
|
||||
if (Directory.Exists(fileNamefolder))
|
||||
{
|
||||
File.Delete(savePath);
|
||||
Directory.Delete(fileNamefolder, true);
|
||||
Log.Error($"{JobType}-图片视频同步-已删除合成失败的视频文件和目录");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
// 下载视频封面(使用第一张图片作为封面)
|
||||
await DownVideoCover(imageUrls.FirstOrDefault(), fileNamefolder, cookie, item, config);
|
||||
// 下载视频封面(使用第一张图片作为封面)
|
||||
await DownVideoCover(imageUrls.FirstOrDefault(), fileNamefolder, cookie, item, config);
|
||||
// 下载作者头像
|
||||
var (avatarSavePath, avatarUrl) = await DownAuthorAvatar(cookie, item);
|
||||
// 生成NFO文件
|
||||
@@ -690,7 +708,7 @@ namespace dy.net.job
|
||||
if (!string.IsNullOrWhiteSpace(downImageVideoConfig))
|
||||
{
|
||||
downImageVideoConfig = downImageVideoConfig.ToLower();
|
||||
_downImageVideo = config.DownImageVideo && (downImageVideoConfig == "1" || downImageVideoConfig == "y" || downImageVideoConfig == "t" || downImageVideoConfig == "true");
|
||||
_downImageVideo = config.DownImageVideo && downImageVideoConfig == "1";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,15 @@ namespace dy.net.model
|
||||
/// 是否下载图片视频
|
||||
/// </summary>
|
||||
public bool DownImageVideo { get; set; }
|
||||
/// <summary>
|
||||
/// 图文视频 是否额外下载图片
|
||||
/// </summary>
|
||||
public bool DownImage { get; set; }
|
||||
/// <summary>
|
||||
/// 图文视频 是否额外下载mp3
|
||||
/// </summary>
|
||||
public bool DownMp3 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志保留天数,防止容器日志太多,默认10天
|
||||
/// </summary>
|
||||
@@ -40,6 +49,7 @@ namespace dy.net.model
|
||||
|
||||
[SugarColumn(IsIgnore=true)]
|
||||
public bool DownImageVideoFromEnv { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,12 @@ namespace dy.net.service
|
||||
Id = IdGener.GetLong().ToString(),
|
||||
Cron = "30",
|
||||
BatchCount = 10,
|
||||
LogKeepDay = 10,
|
||||
UperSaveTogether = false,//博主视频:true-->每个视频单独一个文件夹 false-->所有视频放在同一个文件夹
|
||||
UperUseViedoTitle = false,//博主视频:true-->使用视频标题作为文件名 false-->使用视频id作为文件名
|
||||
DownImageVideo = downLoadImage,
|
||||
LogKeepDay = 10
|
||||
DownMp3 = false,
|
||||
DownImage = false
|
||||
};
|
||||
sqlSugarClient.Insertable(config).ExecuteCommand();
|
||||
return config;
|
||||
@@ -77,6 +81,8 @@ namespace dy.net.service
|
||||
cc.UperSaveTogether = config.UperSaveTogether;
|
||||
cc.UperUseViedoTitle = config.UperUseViedoTitle;
|
||||
cc.LogKeepDay = config.LogKeepDay;
|
||||
cc.DownMp3= config.DownMp3;
|
||||
cc.DownImage= config.DownImage;
|
||||
}
|
||||
|
||||
var update = await sqlSugarClient.Updateable<AppConfig>(cc).ExecuteCommandAsync();
|
||||
|
||||
@@ -184,7 +184,7 @@ namespace dy.net.service
|
||||
}
|
||||
httpClient.DefaultRequestHeaders.Add("Cookie", cookie);
|
||||
|
||||
var parameters = DouyinBaseParamDics.UpderPostParams;
|
||||
var parameters = DouyinBaseParamDics.InitializeDouyinPostParams();//修复关注的不下载图文视频
|
||||
{
|
||||
// 添加动态参数
|
||||
parameters["max_cursor"] = cursor;
|
||||
@@ -195,7 +195,6 @@ namespace dy.net.service
|
||||
// 构建请求URL
|
||||
var queryString = new FormUrlEncodedContent(parameters);
|
||||
string fullUrl = $"{DouYinApi}/post?{await queryString.ReadAsStringAsync()}";
|
||||
|
||||
var ablog = new ABogus();//计算X-Bogus
|
||||
var a_bogus = ablog.GetValue(parameters);
|
||||
|
||||
|
||||
@@ -20,23 +20,23 @@ namespace dy.net.service
|
||||
/// <returns></returns>
|
||||
public async Task StartJob(string expression,int delay=5000)
|
||||
{
|
||||
await StartCollectJob(expression);
|
||||
//await StartCollectJob(expression);
|
||||
|
||||
await Task.Delay(delay);
|
||||
//如果是数字则加1分钟,减少并发
|
||||
if (int.TryParse(expression, out int cron))
|
||||
{
|
||||
cron++;
|
||||
expression = cron.ToString();
|
||||
}
|
||||
await StartFavoriteJob(expression);
|
||||
//await Task.Delay(delay);
|
||||
////如果是数字则加1分钟,减少并发
|
||||
//if (int.TryParse(expression, out int cron))
|
||||
//{
|
||||
// cron++;
|
||||
// expression = cron.ToString();
|
||||
//}
|
||||
//await StartFavoriteJob(expression);
|
||||
|
||||
await Task.Delay(delay);
|
||||
if (int.TryParse(expression, out int cron2))
|
||||
{
|
||||
cron2++;
|
||||
expression = cron2.ToString();
|
||||
}
|
||||
//await Task.Delay(delay);
|
||||
//if (int.TryParse(expression, out int cron2))
|
||||
//{
|
||||
// cron2++;
|
||||
// expression = cron2.ToString();
|
||||
//}
|
||||
await StartUperPostJob(expression);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace dy.net.utils
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace dy.net.utils
|
||||
{
|
||||
public class DouyinBaseParamDics
|
||||
{
|
||||
@@ -67,14 +69,14 @@
|
||||
{ "browser_online", "true" },
|
||||
{ "browser_platform", "Win32" },
|
||||
{ "browser_version", "5.0%20%28Windows%29" },
|
||||
{ "channel", "tiktok_web" },
|
||||
{ "cookie_enabled", "true" },
|
||||
{ "count", "18" },
|
||||
{ "coverFormat", "2" },
|
||||
{ "cursor", "0" },
|
||||
{ "data_collection_enabled", "true" },
|
||||
{ "device_id", "7380187414842836523" },
|
||||
{ "device_platform", "web_pc" },
|
||||
{ "device_platform", "webapp" },
|
||||
{ "channel", "channel_pc_web" },
|
||||
{ "focus_state", "true" },
|
||||
{ "from_page", "user" },
|
||||
{ "history_len", "3" },
|
||||
@@ -102,6 +104,92 @@
|
||||
return parameters;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 初始化抖音网页端用户作品列表请求参数(参数来源于目标URL)
|
||||
/// 适配接口:https://www.douyin.com/aweme/v1/web/aweme/post/
|
||||
/// </summary>
|
||||
/// <returns>抖音作品列表请求参数字典</returns>
|
||||
public static Dictionary<string, string> InitializeDouyinPostParams()
|
||||
{
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
// 基础设备与渠道参数
|
||||
{ "device_platform", "webapp" },
|
||||
{ "aid", "6383" },
|
||||
{ "channel", "channel_pc_web" },
|
||||
|
||||
// 用户标识参数
|
||||
{ "sec_user_id", "" },
|
||||
|
||||
// 分页与内容定位参数
|
||||
{ "max_cursor", "0" },
|
||||
{ "locate_item_id", "7576282367263807451" },
|
||||
{ "locate_query", "false" },
|
||||
{ "count", "18" },
|
||||
|
||||
// 视频相关配置参数
|
||||
{ "show_live_replay_strategy", "1" },
|
||||
{ "need_time_list", "1" },
|
||||
{ "time_list_query", "0" },
|
||||
{ "publish_video_strategy_type", "2" },
|
||||
{ "support_h265", "1" },
|
||||
{ "support_dash", "1" },
|
||||
|
||||
// 版本与更新参数
|
||||
{ "from_user_page", "1" },
|
||||
{ "update_version_code", "170400" },
|
||||
{ "version_code", "290100" },
|
||||
{ "version_name", "29.1.0" },
|
||||
|
||||
// PC端特有参数
|
||||
{ "pc_client_type", "1" },
|
||||
{ "pc_libra_divert", "Windows" },
|
||||
{ "cpu_core_num", "32" },
|
||||
|
||||
// 浏览器环境参数
|
||||
{ "browser_language", "zh-CN" },
|
||||
{ "browser_platform", "Win32" },
|
||||
{ "browser_name", "Chrome" },
|
||||
{ "browser_version", "142.0.0.0" },
|
||||
{ "browser_online", "true" },
|
||||
{ "engine_name", "Blink" },
|
||||
{ "engine_version", "142.0.0.0" },
|
||||
|
||||
// 系统环境参数
|
||||
{ "os_name", "Windows" },
|
||||
{ "os_version", "10" },
|
||||
{ "device_memory", "8" },
|
||||
{ "platform", "PC" },
|
||||
{ "cookie_enabled", "true" },
|
||||
|
||||
// 屏幕与网络参数
|
||||
{ "screen_width", "1707" },
|
||||
{ "screen_height", "1067" },
|
||||
{ "downlink", "10" },
|
||||
{ "effective_type", "4g" },
|
||||
{ "round_trip_time", "0" },
|
||||
|
||||
// 用户唯一标识参数
|
||||
{ "webid", "7574080345697584675" },
|
||||
{ "uifid", "" },
|
||||
|
||||
// 安全验证与签名参数
|
||||
{ "msToken", "" },
|
||||
{ "a_bogus", "" },
|
||||
{ "verifyFp", "" },
|
||||
{ "fp", "" },
|
||||
{ "x-secsdk-web-expire", "" },
|
||||
{ "x-secsdk-web-signature", "" },
|
||||
|
||||
// 其他辅助参数
|
||||
{ "whale_cut_token", "" },
|
||||
{ "cut_version", "1" }
|
||||
};
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// 静态基础参数(供内部初始化使用)
|
||||
private static Dictionary<string, string> GetDyBaseParameters()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user