diff --git a/Program.cs b/Program.cs
index f40c8da..fd20f5d 100644
--- a/Program.cs
+++ b/Program.cs
@@ -52,7 +52,7 @@ namespace dy.net
// ¹¹½¨ӦÓÃ
var app = builder.Build();
- Log.Debug("DOWN_IMGVIDEO=" + downImgConfig);
+ Log.Debug("ffmpeg=" + downImgConfig);
// ÅäÖÃÖмä¼þ
ConfigureMiddleware(app, builder.Environment);
diff --git a/Properties/PublishProfiles/FolderProfile.pubxml.user b/Properties/PublishProfiles/FolderProfile.pubxml.user
index 2d9d237..524b273 100644
--- a/Properties/PublishProfiles/FolderProfile.pubxml.user
+++ b/Properties/PublishProfiles/FolderProfile.pubxml.user
@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\
- 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||;
+ 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||;
\ No newline at end of file
diff --git a/app/src/pages/set/AppSet.vue b/app/src/pages/set/AppSet.vue
index 6d2929c..ae3ec7e 100644
--- a/app/src/pages/set/AppSet.vue
+++ b/app/src/pages/set/AppSet.vue
@@ -45,6 +45,13 @@
+
+
+
+
+
+
+
@@ -85,6 +92,8 @@ interface FormState {
UperSaveTogether: boolean;
UperUseViedoTitle: boolean;
LogKeepDay: number;
+ DownImage: boolean;
+ DownMp3: boolean;
}
// 表单初始数据
@@ -97,6 +106,8 @@ const formState: UnwrapRef = 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,
});
// 监听环境变量配置,控制是否显示图片视频下载选项
diff --git a/expand/dy.image/FFmpegHelper.cs b/expand/dy.image/FFmpegHelper.cs
index 02f2798..4f4abb7 100644
--- a/expand/dy.image/FFmpegHelper.cs
+++ b/expand/dy.image/FFmpegHelper.cs
@@ -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
+ // {
+ // "-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
+ // --- 修正点 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
+ {
+ "-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);
diff --git a/expand/dy.image/ImageMergeToVideoService.cs b/expand/dy.image/ImageMergeToVideoService.cs
index c39c0c2..1ce7055 100644
--- a/expand/dy.image/ImageMergeToVideoService.cs
+++ b/expand/dy.image/ImageMergeToVideoService.cs
@@ -13,7 +13,18 @@
_downloadHelper = downloadHelper;
_fFmpegHelper = fFmpegHelper;
}
- public async Task MergeToVideo(string rootPath, MediaMergeRequest request,string outputVideoPath,string fileNamefolder)
+ ///
+ /// 视频合成
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task 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)
{
diff --git a/extension/ServiceExtension.cs b/extension/ServiceExtension.cs
index 79aaa6a..403d7e5 100644
--- a/extension/ServiceExtension.cs
+++ b/extension/ServiceExtension.cs
@@ -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")
diff --git a/job/DouyinBaseSyncJob.cs b/job/DouyinBaseSyncJob.cs
index 9bfacac..2e93fb8 100644
--- a/job/DouyinBaseSyncJob.cs
+++ b/job/DouyinBaseSyncJob.cs
@@ -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();
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";
}
}
diff --git a/model/AppConfig.cs b/model/AppConfig.cs
index 425d017..77d8c17 100644
--- a/model/AppConfig.cs
+++ b/model/AppConfig.cs
@@ -33,6 +33,15 @@ namespace dy.net.model
/// 是否下载图片视频
///
public bool DownImageVideo { get; set; }
+ ///
+ /// 图文视频 是否额外下载图片
+ ///
+ public bool DownImage { get; set; }
+ ///
+ /// 图文视频 是否额外下载mp3
+ ///
+ public bool DownMp3 { get; set; }
+
///
/// 日志保留天数,防止容器日志太多,默认10天
///
@@ -40,6 +49,7 @@ namespace dy.net.model
[SugarColumn(IsIgnore=true)]
public bool DownImageVideoFromEnv { get; set; }
+
}
}
diff --git a/service/DouyinCommonService.cs b/service/DouyinCommonService.cs
index 2602008..6868c6f 100644
--- a/service/DouyinCommonService.cs
+++ b/service/DouyinCommonService.cs
@@ -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(cc).ExecuteCommandAsync();
diff --git a/service/DouyinHttpClientService.cs b/service/DouyinHttpClientService.cs
index d6daee2..778a299 100644
--- a/service/DouyinHttpClientService.cs
+++ b/service/DouyinHttpClientService.cs
@@ -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);
diff --git a/service/DouyinQuartzJobService.cs b/service/DouyinQuartzJobService.cs
index cd8431b..390caea 100644
--- a/service/DouyinQuartzJobService.cs
+++ b/service/DouyinQuartzJobService.cs
@@ -20,23 +20,23 @@ namespace dy.net.service
///
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);
}
diff --git a/utils/DouyinBaseParamDics.cs b/utils/DouyinBaseParamDics.cs
index c0cf2d0..c60834f 100644
--- a/utils/DouyinBaseParamDics.cs
+++ b/utils/DouyinBaseParamDics.cs
@@ -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;
}
+
+ ///
+ /// 初始化抖音网页端用户作品列表请求参数(参数来源于目标URL)
+ /// 适配接口:https://www.douyin.com/aweme/v1/web/aweme/post/
+ ///
+ /// 抖音作品列表请求参数字典
+ public static Dictionary InitializeDouyinPostParams()
+ {
+ var parameters = new Dictionary
+ {
+ // 基础设备与渠道参数
+ { "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 GetDyBaseParameters()
{