1、视频下载失败-403,重新切换其他下载地址尝试下载
2、支持动态视频下载(抖音里面属于图文-但是又不是图文)--实际是多个视频组合的这种。(没有将视频做合并的计划)
This commit is contained in:
@@ -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-12-06T05:07:29.7182102Z||;True|2025-12-06T13:05:46.2855366+08:00||;False|2025-12-06T13:05:40.3550904+08:00||;True|2025-12-06T13:03:47.8773880+08:00||;True|2025-12-06T13:03:28.9521030+08:00||;True|2025-12-05T23:11:51.5026151+08:00||;True|2025-12-05T12:46:26.1415079+08:00||;False|2025-12-05T12:46:19.1274332+08:00||;True|2025-12-05T09:04:36.7111717+08:00||;True|2025-12-05T08:22:51.7343317+08:00||;True|2025-12-05T08:20:32.0383739+08:00||;True|2025-12-04T21:58:29.4112766+08:00||;True|2025-12-04T21:58:10.0382218+08:00||;True|2025-12-04T21:57:55.3894059+08:00||;True|2025-12-04T21:10:38.8943797+08:00||;True|2025-12-04T21:09:31.5024569+08:00||;True|2025-12-04T08:22:47.9617427+08:00||;False|2025-12-04T08:22:41.3759733+08:00||;True|2025-12-04T08:14:02.2478893+08:00||;True|2025-12-04T07:57:00.2208139+08:00||;True|2025-12-03T23:58:13.3693303+08:00||;True|2025-12-03T23:57:40.4257893+08:00||;True|2025-12-03T23:56:38.8717769+08:00||;True|2025-12-03T23:56:24.0663568+08:00||;False|2025-12-03T23:55:36.1902974+08:00||;True|2025-12-03T23:51:30.6688504+08:00||;True|2025-12-03T23:34:17.1648971+08:00||;False|2025-12-03T23:34:07.7649161+08:00||;True|2025-12-03T22:32:56.2435003+08:00||;True|2025-12-03T22:27:45.6059666+08:00||;True|2025-12-03T15:55:28.0186597+08:00||;False|2025-12-03T15:54:17.5032724+08:00||;True|2025-12-03T15:53:10.2273509+08:00||;True|2025-12-02T22:11:37.4645042+08:00||;False|2025-12-02T22:10:47.1320259+08:00||;True|2025-12-02T14:39:58.8932130+08:00||;True|2025-12-02T14:36:37.1529072+08:00||;True|2025-12-02T12:58:22.0951548+08:00||;True|2025-12-02T09:30:33.7066474+08:00||;True|2025-12-02T09:30:14.5481844+08:00||;True|2025-12-02T09:30:00.3123364+08:00||;False|2025-12-02T09:29:54.4615520+08:00||;True|2025-12-02T09:13:01.7995414+08:00||;False|2025-12-02T09:12:55.8360281+08:00||;True|2025-12-02T09:12:31.0156791+08:00||;True|2025-12-02T08:55:11.3773395+08:00||;True|2025-12-02T08:53:21.3927713+08:00||;False|2025-12-02T08:48:55.8638037+08:00||;True|2025-12-02T07:29:25.2192447+08:00||;False|2025-12-02T07:29:10.2504665+08:00||;True|2025-12-02T07:28:29.0769286+08:00||;True|2025-12-01T21:53:07.6474834+08:00||;True|2025-12-01T21:44:28.1674315+08:00||;True|2025-12-01T21:18:47.2119609+08:00||;True|2025-12-01T20:51:19.9948301+08:00||;True|2025-12-01T20:50:36.9904697+08:00||;True|2025-12-01T20:44:10.1695142+08:00||;True|2025-12-01T19:27:58.0479456+08:00||;True|2025-12-01T19:16:02.2288214+08:00||;False|2025-12-01T19:15:56.0372115+08:00||;True|2025-12-01T19:15:16.4854821+08:00||;False|2025-12-01T19:15:06.7001019+08:00||;True|2025-12-01T17:10:38.9739466+08:00||;False|2025-12-01T17:10:18.8928139+08:00||;True|2025-12-01T17:03:59.3171589+08:00||;True|2025-12-01T17:03:12.5681656+08:00||;True|2025-12-01T16:42:51.4415025+08:00||;True|2025-12-01T01:29:59.6975567+08:00||;True|2025-12-01T01:29:45.1898652+08:00||;False|2025-12-01T01:29:33.1787721+08:00||;True|2025-12-01T01:27:33.1297605+08:00||;True|2025-12-01T01:27:27.3976862+08:00||;False|2025-12-01T01:27:20.5193052+08:00||;True|2025-12-01T01:11:11.0210582+08:00||;True|2025-12-01T01:04:04.3307782+08:00||;True|2025-12-01T00:55:49.8018864+08:00||;True|2025-12-01T00:27:25.1323561+08:00||;True|2025-11-30T23:58:20.7284116+08:00||;True|2025-11-30T23:57:58.3027622+08:00||;True|2025-11-30T23:57:43.0829614+08:00||;False|2025-11-30T23:57:33.5606716+08:00||;True|2025-11-30T23:54:29.6203368+08:00||;True|2025-11-30T23:54:27.5383120+08:00||;False|2025-11-30T23:54:17.0823982+08:00||;True|2025-11-30T22:41:11.4538820+08:00||;True|2025-11-30T22:38:19.7064566+08:00||;True|2025-11-30T22:37:35.2889186+08:00||;True|2025-11-30T21:42:31.1408314+08:00||;True|2025-11-30T21:42:19.5385333+08:00||;False|2025-11-30T21:42:13.3290237+08:00||;True|2025-11-30T21:33:28.0806806+08:00||;True|2025-11-30T21:16:56.7088027+08:00||;False|2025-11-30T21:16:46.1444944+08:00||;True|2025-11-30T20:21:24.4059664+08:00||;False|2025-11-30T20:21:17.6006098+08:00||;True|2025-11-30T20:20:08.2030141+08:00||;False|2025-11-30T20:16:12.3608968+08:00||;True|2025-11-30T20:15:35.6048313+08:00||;True|2025-11-30T19:45:18.7229552+08:00||;True|2025-11-30T19:45:08.6083368+08:00||;</History>
|
||||
<History>True|2025-12-07T15:02:10.0037815Z||;True|2025-12-07T22:48:25.7859648+08:00||;True|2025-12-07T20:49:13.6683281+08:00||;True|2025-12-07T08:47:30.1236366+08:00||;True|2025-12-06T13:14:32.4410202+08:00||;True|2025-12-06T13:07:29.7182102+08:00||;True|2025-12-06T13:05:46.2855366+08:00||;False|2025-12-06T13:05:40.3550904+08:00||;True|2025-12-06T13:03:47.8773880+08:00||;True|2025-12-06T13:03:28.9521030+08:00||;True|2025-12-05T23:11:51.5026151+08:00||;True|2025-12-05T12:46:26.1415079+08:00||;False|2025-12-05T12:46:19.1274332+08:00||;True|2025-12-05T09:04:36.7111717+08:00||;True|2025-12-05T08:22:51.7343317+08:00||;True|2025-12-05T08:20:32.0383739+08:00||;True|2025-12-04T21:58:29.4112766+08:00||;True|2025-12-04T21:58:10.0382218+08:00||;True|2025-12-04T21:57:55.3894059+08:00||;True|2025-12-04T21:10:38.8943797+08:00||;True|2025-12-04T21:09:31.5024569+08:00||;True|2025-12-04T08:22:47.9617427+08:00||;False|2025-12-04T08:22:41.3759733+08:00||;True|2025-12-04T08:14:02.2478893+08:00||;True|2025-12-04T07:57:00.2208139+08:00||;True|2025-12-03T23:58:13.3693303+08:00||;True|2025-12-03T23:57:40.4257893+08:00||;True|2025-12-03T23:56:38.8717769+08:00||;True|2025-12-03T23:56:24.0663568+08:00||;False|2025-12-03T23:55:36.1902974+08:00||;True|2025-12-03T23:51:30.6688504+08:00||;True|2025-12-03T23:34:17.1648971+08:00||;False|2025-12-03T23:34:07.7649161+08:00||;True|2025-12-03T22:32:56.2435003+08:00||;True|2025-12-03T22:27:45.6059666+08:00||;True|2025-12-03T15:55:28.0186597+08:00||;False|2025-12-03T15:54:17.5032724+08:00||;True|2025-12-03T15:53:10.2273509+08:00||;True|2025-12-02T22:11:37.4645042+08:00||;False|2025-12-02T22:10:47.1320259+08:00||;True|2025-12-02T14:39:58.8932130+08:00||;True|2025-12-02T14:36:37.1529072+08:00||;True|2025-12-02T12:58:22.0951548+08:00||;True|2025-12-02T09:30:33.7066474+08:00||;True|2025-12-02T09:30:14.5481844+08:00||;True|2025-12-02T09:30:00.3123364+08:00||;False|2025-12-02T09:29:54.4615520+08:00||;True|2025-12-02T09:13:01.7995414+08:00||;False|2025-12-02T09:12:55.8360281+08:00||;True|2025-12-02T09:12:31.0156791+08:00||;True|2025-12-02T08:55:11.3773395+08:00||;True|2025-12-02T08:53:21.3927713+08:00||;False|2025-12-02T08:48:55.8638037+08:00||;True|2025-12-02T07:29:25.2192447+08:00||;False|2025-12-02T07:29:10.2504665+08:00||;True|2025-12-02T07:28:29.0769286+08:00||;True|2025-12-01T21:53:07.6474834+08:00||;True|2025-12-01T21:44:28.1674315+08:00||;True|2025-12-01T21:18:47.2119609+08:00||;True|2025-12-01T20:51:19.9948301+08:00||;True|2025-12-01T20:50:36.9904697+08:00||;True|2025-12-01T20:44:10.1695142+08:00||;True|2025-12-01T19:27:58.0479456+08:00||;True|2025-12-01T19:16:02.2288214+08:00||;False|2025-12-01T19:15:56.0372115+08:00||;True|2025-12-01T19:15:16.4854821+08:00||;False|2025-12-01T19:15:06.7001019+08:00||;True|2025-12-01T17:10:38.9739466+08:00||;False|2025-12-01T17:10:18.8928139+08:00||;True|2025-12-01T17:03:59.3171589+08:00||;True|2025-12-01T17:03:12.5681656+08:00||;True|2025-12-01T16:42:51.4415025+08:00||;True|2025-12-01T01:29:59.6975567+08:00||;True|2025-12-01T01:29:45.1898652+08:00||;False|2025-12-01T01:29:33.1787721+08:00||;True|2025-12-01T01:27:33.1297605+08:00||;True|2025-12-01T01:27:27.3976862+08:00||;False|2025-12-01T01:27:20.5193052+08:00||;True|2025-12-01T01:11:11.0210582+08:00||;True|2025-12-01T01:04:04.3307782+08:00||;True|2025-12-01T00:55:49.8018864+08:00||;True|2025-12-01T00:27:25.1323561+08:00||;True|2025-11-30T23:58:20.7284116+08:00||;True|2025-11-30T23:57:58.3027622+08:00||;True|2025-11-30T23:57:43.0829614+08:00||;False|2025-11-30T23:57:33.5606716+08:00||;True|2025-11-30T23:54:29.6203368+08:00||;True|2025-11-30T23:54:27.5383120+08:00||;False|2025-11-30T23:54:17.0823982+08:00||;True|2025-11-30T22:41:11.4538820+08:00||;True|2025-11-30T22:38:19.7064566+08:00||;True|2025-11-30T22:37:35.2889186+08:00||;True|2025-11-30T21:42:31.1408314+08:00||;True|2025-11-30T21:42:19.5385333+08:00||;False|2025-11-30T21:42:13.3290237+08:00||;True|2025-11-30T21:33:28.0806806+08:00||;True|2025-11-30T21:16:56.7088027+08:00||;False|2025-11-30T21:16:46.1444944+08:00||;True|2025-11-30T20:21:24.4059664+08:00||;False|2025-11-30T20:21:17.6006098+08:00||;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -97,8 +97,8 @@ Cookie 及 `sec_user_id` 是同步功能的核心,需严格按步骤获取,
|
||||
|
||||
| 镜像标签 | 架构 |
|
||||
| ----------------- | -------------- |
|
||||
| `beta_1.8.0` | x86_64 (amd64) |
|
||||
| `arm_1.8.0` | ARM64 |
|
||||
| `beta_1.8.2` | x86_64 (amd64) |
|
||||
| `arm_1.8.2` | ARM64 |
|
||||
|
||||
### 最新镜像查看
|
||||
[镜像列表](http://nas.synology2023.online:10108/api/docker/dysync/1)
|
||||
@@ -116,7 +116,7 @@ docker run -d --restart=always \
|
||||
-v /opt/dysync/uper:/app/uper \
|
||||
-p 10103:10101 \
|
||||
--name dysync2025 \
|
||||
ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_1.7.7
|
||||
ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_1.8.2
|
||||
# 注意:-p 后面的容器端口,可以用环境变量类似:ASPNETCORE_URLS = http://+:10108 指定
|
||||
```
|
||||
|
||||
@@ -130,7 +130,7 @@ version: '3.8'
|
||||
|
||||
services:
|
||||
dysync:
|
||||
image: ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_1.7.7
|
||||
image: ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_1.8.2
|
||||
container_name: dysync2025 # 容器名称
|
||||
restart: unless-stopped # 始终重启容器,除非容器被手动停止或Docker服务停止
|
||||
ports:
|
||||
|
||||
@@ -105,7 +105,13 @@
|
||||
<span>启用后,将单独下载所有图片文件</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item has-feedback label="下载动态视频" name="DownImage" :wrapper-col="{ span: 6 }">
|
||||
<a-switch v-model:checked="formState.DownDynamicVideo" />
|
||||
<div class="flex items-start mt-1 text-sm text-gray-500">
|
||||
<InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" />
|
||||
<span>针对有些视频是多个视频生成的,实际是分为多个视频,启用后将会分别下载多个视频,名字带_001,002这样</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback label="是否统一存储" name="ImageViedoSaveAlone" :wrapper-col="{ span: 10 }">
|
||||
<a-switch v-model:checked="formState.ImageViedoSaveAlone" />
|
||||
<div class="flex items-start mt-1 text-sm text-gray-500">
|
||||
@@ -222,6 +228,7 @@ interface FormState {
|
||||
FullFollowedTitleTemplate: string; // 新增:完整模板字符串(自动生成)
|
||||
AutoDistinct: boolean;
|
||||
PriorityLevel: string;
|
||||
DownDynamicVideo: boolean;
|
||||
}
|
||||
|
||||
// 表单初始数据
|
||||
@@ -242,6 +249,7 @@ const formState: UnwrapRef<FormState> = reactive({
|
||||
FullFollowedTitleTemplate: '', // 初始为空
|
||||
AutoDistinct: false,
|
||||
PriorityLevel: '',
|
||||
DownDynamicVideo: false,
|
||||
});
|
||||
|
||||
// 实时计算完整模板(可选:让用户实时预览,提交时无需重复计算)
|
||||
@@ -318,6 +326,7 @@ const getConfig = () => {
|
||||
ImageViedoSaveAlone: res.data.imageViedoSaveAlone,
|
||||
AutoDistinct: res.data.autoDistinct,
|
||||
PriorityLevel: res.data.priorityLevel,
|
||||
DownDynamicVideo: res.data.downDynamicVideo,
|
||||
});
|
||||
|
||||
// downImgVideo.value = res.data.downImageVideoFromEnv;
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"dbconn": "",
|
||||
//"tagName": "arm_1.8.0",
|
||||
//"tagName": "dev_1.2",
|
||||
"tagName": "beta_1.8.0",
|
||||
//"tagName": "arm_1.8.2",
|
||||
//"tagName": "dev_1.2.2",
|
||||
"tagName": "beta_1.8.2",
|
||||
"dbtype": "Sqlite"
|
||||
}
|
||||
@@ -1270,6 +1270,10 @@ namespace dy.net.dto
|
||||
/// </summary>
|
||||
[JsonProperty("url_list")]
|
||||
public List<string> UrlList { get; set; }
|
||||
|
||||
|
||||
[JsonProperty("video")]
|
||||
public Video DynamicVideo { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
+201
-21
@@ -134,11 +134,7 @@ namespace dy.net.job
|
||||
Log.Debug($"{VideoType}-未获取到系统配置,任务终止!!!");
|
||||
return;
|
||||
}
|
||||
if(VideoType== VideoTypeEnum.dy_follows )
|
||||
{
|
||||
Log.Debug($"{VideoType}-系统配置未启用关注同步,任务终止!!!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 2. 从配置中获取每页请求数量--固定18
|
||||
//if (config.BatchCount > 0)
|
||||
@@ -151,10 +147,9 @@ namespace dy.net.job
|
||||
var cookies = await GetValidCookies();
|
||||
if (cookies == null || !cookies.Any())
|
||||
{
|
||||
Log.Debug($"{VideoType}-无有效Cookie,任务终止!!!");
|
||||
Log.Debug($"{VideoType}-无有效的Cookie,任务终止!!!");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Debug($"{VideoType}-共发现{cookies.Count}个Cookie,同步任务即将开始...");
|
||||
|
||||
// 6. 遍历每个有效的Cookie,执行同步操作
|
||||
@@ -411,12 +406,11 @@ namespace dy.net.job
|
||||
var videos = new List<DouyinVideo>();
|
||||
foreach (var item in data.AwemeList)
|
||||
{
|
||||
|
||||
if(!item.Desc.Contains("致敬先烈"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//if (!item.Desc.Contains("人生苦短"))
|
||||
////if (!item.Desc.Contains("如果你也喜欢"))
|
||||
//{
|
||||
// continue;
|
||||
//}
|
||||
//判断视频是否是强制删除且不再下载的视频
|
||||
var deleteVideo = await douyinCommonService.ExistDeleteVideo(item.AwemeId);
|
||||
if (deleteVideo)
|
||||
@@ -434,7 +428,7 @@ namespace dy.net.job
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//如果存在,但是因为去重规则,选择的最高优先级变更了,需要删除原来的,重新下载。
|
||||
if (exitVideo!=null) {
|
||||
await douyinVideoService.DeleteById(exitVideo.Id);
|
||||
}
|
||||
@@ -451,14 +445,64 @@ namespace dy.net.job
|
||||
if (video != null)
|
||||
videos.Add(video);
|
||||
|
||||
// 如果配置了下载图片视频,则处理图片集并合成视频
|
||||
|
||||
// 处理图文视频
|
||||
if (config.DownImageVideo || config.DownMp3 || config.DownImage)
|
||||
{
|
||||
//处理多个视频-组合的图文视频--类似动图。
|
||||
|
||||
if (config.DownDynamicVideo)
|
||||
{
|
||||
List<string> dynamicVideoUrls = new List<string>();
|
||||
if (item.Images != null&& item.Images.Count > 0)
|
||||
{
|
||||
foreach (var img in item.Images)
|
||||
{
|
||||
if (img.DynamicVideo != null&& img.DynamicVideo.BitRate!=null&& img.DynamicVideo.BitRate.Count>0)
|
||||
{
|
||||
foreach (var btv in img.DynamicVideo.BitRate)
|
||||
{
|
||||
var payAddr = btv.PlayAddr;
|
||||
if(payAddr != null&& payAddr.UrlList!=null&&payAddr.UrlList.Count>0)
|
||||
{
|
||||
dynamicVideoUrls.Add(payAddr.UrlList.FirstOrDefault(x => x.StartsWith("https://www.douyin.com/aweme/v1/play")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//说明这个是图文视频里面的动态视频拼接的。。。
|
||||
if (dynamicVideoUrls.Count > 0)
|
||||
{
|
||||
var dynamicVideo = await ProcessDynamicVideo(dynamicVideoUrls, cookie, item, data, config);
|
||||
if (dynamicVideo != null)
|
||||
{
|
||||
videos.Add(dynamicVideo);
|
||||
Log.Debug($"{VideoType}-动态视频[{item.Desc}],下载成功 ,共{dynamicVideo.DynamicVideos.Count()}个视频...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"{VideoType}-动态视频[{item.Desc}],下载失败...");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var mergevideo = await ProcessImageSetAndMergeToVideo(cookie, item, data, config, followed);
|
||||
if (mergevideo != null)
|
||||
videos.Add(mergevideo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var mergevideo = await ProcessImageSetAndMergeToVideo(cookie, item, data, config, followed);
|
||||
if (mergevideo != null)
|
||||
videos.Add(mergevideo);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
return videos;
|
||||
}
|
||||
|
||||
@@ -625,7 +669,7 @@ namespace dy.net.job
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理单个视频
|
||||
/// 处理单个视频--正常的收藏喜欢关注的 视频
|
||||
/// 负责下载视频、封面、头像,生成NFO文件,创建视频实体等
|
||||
/// </summary>
|
||||
/// <param name="cookie">用户Cookie</param>
|
||||
@@ -664,19 +708,24 @@ namespace dy.net.job
|
||||
}
|
||||
|
||||
Log.Debug($"{VideoType}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc,item.AwemeId)}]开始下载...");
|
||||
// 随机延迟,模拟人类操作
|
||||
// 随机延迟
|
||||
await Task.Delay(_random.Next(1, 4) * 1000);
|
||||
|
||||
// 下载视频
|
||||
if (!await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies))
|
||||
{
|
||||
Log.Error($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]下载失败!!!");
|
||||
return null;
|
||||
|
||||
(bool flowControl, DouyinVideo value) = await SwitchOtherUrlAddressDown(cookie, item, videoUrl, savePath);
|
||||
if (!flowControl)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]下载完成.");
|
||||
}
|
||||
|
||||
// 下载视频封面
|
||||
await DownVideoCover(item, saveFolder, cookie, config);
|
||||
// 下载作者头像
|
||||
@@ -687,6 +736,131 @@ namespace dy.net.job
|
||||
return CreateVideoEntity(cookie, item, v, savePath, saveFolder, tag1, tag2, tag3, avatarSavePath, avatarUrl, data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected async Task<DouyinVideo> ProcessDynamicVideo(List<string> dynamicUrls,DouyinCookie cookie, Aweme item, DouyinVideoInfo data, AppConfig config, DouyinFollowed followed = null)
|
||||
{
|
||||
|
||||
|
||||
// 获取视频标签
|
||||
var (tag1, tag2, tag3) = GetVideoTags(item);
|
||||
// 创建保存文件夹
|
||||
var saveFolder = CreateSaveFolder(cookie, item, config, followed);
|
||||
// 获取视频文件名
|
||||
var fileName = DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)+".mp4";
|
||||
// 拼接视频保存路径
|
||||
var savePath = Path.Combine(saveFolder, fileName);
|
||||
|
||||
Log.Debug($"{VideoType}-动态视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]开始下载...");
|
||||
// 随机延迟
|
||||
int i = 1;
|
||||
|
||||
List<string> dynamicSavePaths = new List<string>();
|
||||
foreach (var dynamicUrl in dynamicUrls)
|
||||
{
|
||||
// 下载动态视频
|
||||
var dynamicSavePath = savePath.Replace(".mp4", $"_00{i}.mp4");
|
||||
|
||||
// 如果文件已存在,跳过
|
||||
if (File.Exists(dynamicSavePath))
|
||||
{
|
||||
//Log.Debug($"{VideoType}-视频[{DouyinFileNameHelper.SanitizePath(item.Desc)}]已存在,跳过下载.");
|
||||
dynamicSavePaths.Add(dynamicSavePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!await douyinHttpClientService.DownloadAsync(dynamicUrl, dynamicSavePath, cookie.Cookies))
|
||||
{
|
||||
Log.Error($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]-00{i},下载失败!!!");
|
||||
}
|
||||
else
|
||||
{
|
||||
dynamicSavePaths.Add(dynamicSavePath);
|
||||
i++;
|
||||
Log.Debug($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]-00{i},下载完成.");
|
||||
}
|
||||
await Task.Delay(_random.Next(1, 4) * 1000);
|
||||
}
|
||||
|
||||
|
||||
//// 下载视频封面
|
||||
//await DownVideoCover(item, saveFolder, cookie, config);
|
||||
//// 下载作者头像
|
||||
//var (avatarSavePath, avatarUrl) = await DownAuthorAvatar(cookie, item);
|
||||
//// 生成NFO文件
|
||||
//await GenerateNfoFile(saveFolder, item, avatarUrl, cookie, config);
|
||||
// 创建视频实体
|
||||
// 为合成的视频创建一个“虚拟”的BitRate对象,以便复用CreateVideoEntity方法
|
||||
var virtualBitRate = new VideoBitRate
|
||||
{
|
||||
PlayAddr = new PlayAddr
|
||||
{
|
||||
Width = item?.Images?.FirstOrDefault()?.Width ?? 0,
|
||||
Height = item?.Images?.FirstOrDefault()?.Height ?? 0,
|
||||
DataSize = DouyinFileUtils.GetTotalFileSize(dynamicSavePaths) // 合成视频的文件大小
|
||||
}
|
||||
};
|
||||
return CreateVideoEntity(cookie, item, virtualBitRate, dynamicSavePaths.FirstOrDefault(), saveFolder, tag1, tag2, tag3,"", "", data,dynamicSavePaths);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 寻找其他视频地址下载...
|
||||
/// </summary>
|
||||
/// <param name="cookie"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="videoUrl"></param>
|
||||
/// <param name="savePath"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<(bool flowControl, DouyinVideo value)> SwitchOtherUrlAddressDown(DouyinCookie cookie, Aweme item, string videoUrl, string savePath)
|
||||
{
|
||||
Log.Debug($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}],正在尝试切换其他地址再次重新下载...");
|
||||
var otherUrls = new List<string>();
|
||||
|
||||
foreach (var bit in item.Video.BitRate)
|
||||
{
|
||||
if (bit == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var payUrls = bit.PlayAddr;
|
||||
|
||||
if (payUrls == null || payUrls.UrlList == null || payUrls.UrlList.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var payurl in payUrls.UrlList)
|
||||
{
|
||||
if (payurl == videoUrl)// 排除最开始下载失败的视频地址
|
||||
continue;
|
||||
otherUrls.Add(payurl);
|
||||
}
|
||||
}
|
||||
|
||||
if (otherUrls.Count > 0)
|
||||
{
|
||||
Log.Debug($"{VideoType}-额外找到{otherUrls.Count}个视频地址,即将再次开始下载...");
|
||||
var result2 = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies, otherUrls);
|
||||
Log.Debug($"{VideoType}-尝试多个地址后,下载成功,{savePath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"{VideoType}-确实没有可以下载的视频链接2," + item.Desc);
|
||||
return (flowControl: false, value: null);
|
||||
}
|
||||
|
||||
return (flowControl: true, value: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理图片集并合成为视频
|
||||
/// 负责下载图片、合成视频、处理封面和头像等
|
||||
@@ -1042,14 +1216,15 @@ namespace dy.net.job
|
||||
/// <param name="avatarSavePath">作者头像保存路径</param>
|
||||
/// <param name="avatarUrl">作者头像URL</param>
|
||||
/// <param name="data">视频信息对象</param>
|
||||
/// <param name="dynamicVideos">动态视频</param>
|
||||
/// <returns>创建的视频实体对象</returns>
|
||||
private DouyinVideo CreateVideoEntity(
|
||||
DouyinCookie cookie, Aweme item, VideoBitRate bitRate, string savePath, string saveFolder,
|
||||
string tag1, string tag2, string tag3, string avatarSavePath, string avatarUrl, DouyinVideoInfo data)
|
||||
string tag1, string tag2, string tag3, string avatarSavePath, string avatarUrl, DouyinVideoInfo data,List<string> dynamicVideos=null)
|
||||
{
|
||||
var diffs = GetVideoEntityDifferences(cookie, item);
|
||||
|
||||
return new DouyinVideo
|
||||
var video= new DouyinVideo
|
||||
{
|
||||
ViedoType = VideoType,
|
||||
AwemeId = item.AwemeId,
|
||||
@@ -1075,6 +1250,11 @@ namespace dy.net.job
|
||||
DyUserId = item.AuthorUserId == 0 ? item.Author?.Uid : item.AuthorUserId.ToString(),
|
||||
CookieId = cookie.Id
|
||||
};
|
||||
if (dynamicVideos != null && dynamicVideos.Count > 0)
|
||||
{
|
||||
video.DynamicVideos = JsonConvert.SerializeObject(dynamicVideos);
|
||||
}
|
||||
return video;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
+6
-1
@@ -82,7 +82,12 @@ namespace dy.net.model
|
||||
/// 去重优先级配置,json字符串格式存储
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string PriorityLevel { get; set; }// = "[{\"id\":1,\"name\":\"喜欢的视频\",\"sort\":1},{\"id\":2,\"name\":\"收藏的视频\",\"sort\":2},{\"id\":3,\"name\":\"关注的视频\",\"sort\":3}]";
|
||||
public string PriorityLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否下载动态图视频
|
||||
/// </summary>
|
||||
public bool DownDynamicVideo { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace dy.net.model
|
||||
/// <summary>
|
||||
/// 视频地址(解析会有多个取第一个)
|
||||
/// </summary>
|
||||
[SugarColumn(Length =0200,IsNullable =true)]
|
||||
[SugarColumn(Length =2000,IsNullable =true)]
|
||||
public string VideoUrl { get; set; }
|
||||
/// <summary>
|
||||
/// 视频下载后保存路径
|
||||
@@ -149,5 +149,10 @@ namespace dy.net.model
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public string ViedoCate =>(string.IsNullOrWhiteSpace(Tag1) ? "" : Tag1) + "/" + (string.IsNullOrWhiteSpace(Tag2) ? "" : Tag2);
|
||||
|
||||
|
||||
// 普通字符串字段,显式标记为 SQLite TEXT 类型
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1)]
|
||||
public string DynamicVideos { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
using dy.net.utils;
|
||||
using NetTaste;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
|
||||
namespace dy.net.service
|
||||
{
|
||||
@@ -15,8 +18,6 @@ namespace dy.net.service
|
||||
public static readonly string DouYinApi = "https://www.douyin.com/aweme/v1/web/aweme";
|
||||
// 随机数生成器(避免重复实例化,保证随机性)
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
// 下载信号量锁:初始计数1,最大并发1(同时只能一个下载任务)
|
||||
private readonly SemaphoreSlim _downloadSemaphore = new SemaphoreSlim(1, 1);
|
||||
public DouyinHttpClientService(IHttpClientFactory clientFactory)
|
||||
{
|
||||
_clientFactory = clientFactory;
|
||||
@@ -271,6 +272,7 @@ namespace dy.net.service
|
||||
Serilog.Log.Error($"SyncMyFollows error: {err.StatusMsg}");
|
||||
callBack(err);
|
||||
}
|
||||
Serilog.Log.Error($"SyncMyFollows error: 关注列表为空 :{data}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -305,6 +307,7 @@ namespace dy.net.service
|
||||
/// <param name="videoUrl">文件地址</param>
|
||||
/// <param name="savePath">保存路径</param>
|
||||
/// <param name="cookie">请求Cookie</param>
|
||||
/// <param name="otherUrls">其他下载地址</param>
|
||||
/// <param name="cancellationToken">取消令牌(用于终止任务)</param>
|
||||
/// <param name="streamTimeout">流读取超时时间(默认60秒)</param>
|
||||
/// <param name="maxRetryCount">最大重试次数(默认3次)</param>
|
||||
@@ -314,6 +317,7 @@ namespace dy.net.service
|
||||
string videoUrl,
|
||||
string savePath,
|
||||
string cookie,
|
||||
List<string> otherUrls=null,
|
||||
CancellationToken cancellationToken = default,
|
||||
TimeSpan? streamTimeout = null,
|
||||
int maxRetryCount = 3,
|
||||
@@ -323,15 +327,25 @@ namespace dy.net.service
|
||||
int retryCount = 0;
|
||||
var retryDelay = initialRetryDelay ?? TimeSpan.FromSeconds(1); // 初始延迟1秒
|
||||
streamTimeout ??= TimeSpan.FromSeconds(60); // 流读取超时默认60秒
|
||||
|
||||
if (otherUrls != null) {
|
||||
maxRetryCount = otherUrls.Count;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 执行单次下载逻辑(提取为内部方法,避免代码重复)
|
||||
if (otherUrls != null&& retryCount>0) {
|
||||
return await TryDownloadOnceAsync(
|
||||
otherUrls[retryCount], savePath, cookie, cancellationToken, streamTimeout.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await TryDownloadOnceAsync(
|
||||
videoUrl, savePath, cookie, cancellationToken, streamTimeout.Value);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex) when (IsRetryableException(ex) && retryCount < maxRetryCount)
|
||||
{
|
||||
// 可重试异常且未达最大次数,等待后重试
|
||||
@@ -605,5 +619,96 @@ namespace dy.net.service
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 快速检测目标URL是否返回403 Forbidden状态码
|
||||
/// </summary>
|
||||
/// <param name="url">目标地址</param>
|
||||
/// <returns>true=403状态,false=非403状态,null=请求异常</returns>
|
||||
public async Task<bool> PayUrlIsOKAsync(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url) || !Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
{
|
||||
Serilog.Log.Error("URL格式无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 使用Head请求仅获取响应头,提升效率
|
||||
using var request = new HttpRequestMessage(HttpMethod.Head, url);
|
||||
using var _httpClient = _clientFactory.CreateClient("dy_download");
|
||||
using var response = await _httpClient.SendAsync(
|
||||
request
|
||||
);
|
||||
|
||||
// 直接判断状态码是否为403
|
||||
bool isOk = response.StatusCode == HttpStatusCode.OK;
|
||||
|
||||
Log.Debug($"URL: {url} | 状态码: {(int)response.StatusCode} ({response.StatusCode})");
|
||||
return isOk;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
// 处理HTTP请求异常(如连接失败、DNS解析错误等)
|
||||
Serilog.Log.Error($"请求异常: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
// 处理超时异常
|
||||
Serilog.Log.Error($"请求超时: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 其他异常
|
||||
Serilog.Log.Error($"未知异常: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 串行逐个检测URL,找到第一个「非403/404且请求成功」的URL立即终止并返回
|
||||
/// </summary>
|
||||
/// <param name="urls">待检测URL列表</param>
|
||||
/// <returns>第一个符合条件的URL(null=所有URL都是403/404,或全部请求失败)</returns>
|
||||
public async Task<string> CheckPayUrlIsOKAsync(List<string> urls)
|
||||
{
|
||||
// 逐个遍历URL,串行检测
|
||||
foreach (var url in urls)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 跳过无效URL
|
||||
if (string.IsNullOrWhiteSpace(url) || !Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
{
|
||||
Log.Error($"URL格式无效:{url},跳过检测");
|
||||
continue;
|
||||
}
|
||||
// 检测当前URL是否为403/404
|
||||
bool isOk = await PayUrlIsOKAsync(url);
|
||||
|
||||
// 核心判断:请求成功且不是403/404 → 立即返回该URL
|
||||
if (isOk)
|
||||
{
|
||||
Log.Debug($"✅ 找到可以下载的地址:{url}...");
|
||||
return url;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"检查下载地址 {url} 时发生异常,继续检查下一个");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,11 +109,11 @@ namespace dy.net.service
|
||||
FollowCount = list.Count(x => x.ViedoType == VideoTypeEnum.dy_follows),
|
||||
GraphicVideoCount = list.Count(x => x.IsMergeVideo == 1),
|
||||
|
||||
VideoSizeTotal = ByteToGbConverter.ConvertBytesToGb(list.Sum(x => x.FileSize)),
|
||||
VideoFavoriteSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.dy_favorite).Sum(x => x.FileSize)),
|
||||
VideoCollectSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.dy_collects).Sum(x => x.FileSize)),
|
||||
VideoFollowSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.dy_follows).Sum(x => x.FileSize)),
|
||||
GraphicVideoSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.IsMergeVideo == 1).Sum(x => x.FileSize)),
|
||||
VideoSizeTotal = DouyinFileUtils.ConvertBytesToGb(list.Sum(x => x.FileSize)),
|
||||
VideoFavoriteSize = DouyinFileUtils.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.dy_favorite).Sum(x => x.FileSize)),
|
||||
VideoCollectSize = DouyinFileUtils.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.dy_collects).Sum(x => x.FileSize)),
|
||||
VideoFollowSize = DouyinFileUtils.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.dy_follows).Sum(x => x.FileSize)),
|
||||
GraphicVideoSize = DouyinFileUtils.ConvertBytesToGb(list.Where(x => x.IsMergeVideo == 1).Sum(x => x.FileSize)),
|
||||
|
||||
//TotalDiskSize= ByteToGbConverter.GetHostTotalDiskSpaceGB(),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace dy.net.utils
|
||||
{
|
||||
public class ByteToGbConverter
|
||||
public class DouyinFileUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// 将字节数转换为GB(采用二进制换算:1GB = 1024^3 Byte)
|
||||
@@ -55,5 +55,24 @@
|
||||
return $"获取宿主机总磁盘空间失败:{ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 极简版:计算多个文件的总大小(字节)
|
||||
/// </summary>
|
||||
/// <param name="filePaths">文件路径列表</param>
|
||||
/// <returns>总字节数</returns>
|
||||
public static long GetTotalFileSize(List<string> filePaths)
|
||||
{
|
||||
long totalSize = 0;
|
||||
foreach (var path in filePaths)
|
||||
{
|
||||
// 仅判断文件是否存在,存在则累加大小
|
||||
if (File.Exists(path))
|
||||
{
|
||||
totalSize += new FileInfo(path).Length;
|
||||
}
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user