修复up主视频下载,不下载图文视频的bug

增加图文视频下载配置(是否合成视频,是否单独下载图片,单独下载音频)
This commit is contained in:
jianzhichu
2025-11-24 22:47:06 +08:00
parent 609649057f
commit 2db61f4958
12 changed files with 380 additions and 92 deletions
+1 -1
View File
@@ -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>
+13
View File
@@ -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
View File
@@ -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);
+39 -18
View File
@@ -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)
{
+1 -1
View File
@@ -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");
});
// 配置HttpClientStartup.cs或Program.cs
services.AddHttpClient("dy_download")
+36 -18
View File
@@ -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";
}
}
+10
View File
@@ -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; }
}
}
+7 -1
View File
@@ -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();
+1 -2
View File
@@ -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);
+15 -15
View File
@@ -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);
}
+91 -3
View File
@@ -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()
{