去重优先级,视频合成,没有音频用默认音频

This commit is contained in:
jianzhichu
2025-12-02 22:14:42 +08:00
parent 4663a03483
commit bd3a103f8f
15 changed files with 307 additions and 25 deletions
@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl> <_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
<History>True|2025-12-02T06:39:58.8932130Z||;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||;False|2025-11-30T19:44:54.7824905+08:00||;True|2025-11-30T19:44:17.0179491+08:00||;True|2025-11-30T19:44:06.7073484+08:00||;False|2025-11-30T19:43:58.2896556+08:00||;True|2025-11-30T19:42:08.2261927+08:00||;True|2025-11-30T19:28:15.7115077+08:00||;False|2025-11-30T19:28:10.2280725+08:00||;True|2025-11-30T19:23:31.7859158+08:00||;True|2025-11-30T19:23:20.7788277+08:00||;True|2025-11-30T19:17:11.8583296+08:00||;False|2025-11-30T19:10:41.8369574+08:00||;True|2025-11-30T19:02:08.8184758+08:00||;False|2025-11-30T19:01:59.4711045+08:00||;True|2025-11-30T18:16:01.8372242+08:00||;True|2025-11-30T14:50:31.9356543+08:00||;True|2025-11-30T14:48:27.1833064+08:00||;True|2025-11-30T02:06:06.6099669+08:00||;True|2025-11-30T01:49:46.4358206+08:00||;False|2025-11-30T01:49:34.4884036+08:00||;True|2025-11-30T01:43:41.1028753+08:00||;True|2025-11-30T01:38:18.2856609+08:00||;True|2025-11-30T01:23:02.4378259+08:00||;True|2025-11-30T01:08:29.4041159+08:00||;True|2025-11-30T00:56:33.4172007+08:00||;False|2025-11-30T00:56:21.2777389+08:00||;True|2025-11-27T13:37:08.6884390+08:00||;False|2025-11-27T13:36:54.2595622+08:00||;True|2025-11-27T11:03:33.8401644+08:00||;False|2025-11-27T11:02:48.8942827+08:00||;True|2025-11-26T23:48:02.3957186+08:00||;True|2025-11-26T23:43:06.8154188+08:00||;False|2025-11-26T23:42:05.9191485+08:00||;True|2025-11-26T23:30:11.1295861+08:00||;True|2025-11-26T00:00:42.1507258+08:00||;True|2025-11-26T00:00:17.0107229+08:00||;</History> <History>True|2025-12-02T14:11:37.4645042Z||;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||;False|2025-11-30T19:44:54.7824905+08:00||;True|2025-11-30T19:44:17.0179491+08:00||;True|2025-11-30T19:44:06.7073484+08:00||;False|2025-11-30T19:43:58.2896556+08:00||;True|2025-11-30T19:42:08.2261927+08:00||;True|2025-11-30T19:28:15.7115077+08:00||;False|2025-11-30T19:28:10.2280725+08:00||;True|2025-11-30T19:23:31.7859158+08:00||;True|2025-11-30T19:23:20.7788277+08:00||;True|2025-11-30T19:17:11.8583296+08:00||;False|2025-11-30T19:10:41.8369574+08:00||;True|2025-11-30T19:02:08.8184758+08:00||;False|2025-11-30T19:01:59.4711045+08:00||;True|2025-11-30T18:16:01.8372242+08:00||;True|2025-11-30T14:50:31.9356543+08:00||;True|2025-11-30T14:48:27.1833064+08:00||;True|2025-11-30T02:06:06.6099669+08:00||;True|2025-11-30T01:49:46.4358206+08:00||;False|2025-11-30T01:49:34.4884036+08:00||;True|2025-11-30T01:43:41.1028753+08:00||;True|2025-11-30T01:38:18.2856609+08:00||;True|2025-11-30T01:23:02.4378259+08:00||;True|2025-11-30T01:08:29.4041159+08:00||;True|2025-11-30T00:56:33.4172007+08:00||;False|2025-11-30T00:56:21.2777389+08:00||;True|2025-11-27T13:37:08.6884390+08:00||;False|2025-11-27T13:36:54.2595622+08:00||;True|2025-11-27T11:03:33.8401644+08:00||;False|2025-11-27T11:02:48.8942827+08:00||;True|2025-11-26T23:48:02.3957186+08:00||;True|2025-11-26T23:43:06.8154188+08:00||;False|2025-11-26T23:42:05.9191485+08:00||;True|2025-11-26T23:30:11.1295861+08:00||;</History>
<LastFailureDetails /> <LastFailureDetails />
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+3 -1
View File
@@ -32,11 +32,13 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.33", "pinia": "^2.0.33",
"qs": "^6.10.1", "qs": "^6.10.1",
"sortablejs": "^1.15.6",
"stepin": "2.1.42-beta", "stepin": "2.1.42-beta",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-json-editor": "^1.4.3", "vue-json-editor": "^1.4.3",
"vue-request": "^2.0.4", "vue-request": "^2.0.4",
"vue-router": "^4.1.6" "vue-router": "^4.1.6",
"vue-sortablejs": "^1.0.4"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/container-queries": "^0.1.0", "@tailwindcss/container-queries": "^0.1.0",
@@ -1,4 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
// 步骤2:声明父组件传递的 showSetting 事件(关键!)
const emits = defineEmits(['showSetting']);
import { ref, h, VNode, onMounted } from 'vue'; import { ref, h, VNode, onMounted } from 'vue';
import { StepinHeaderAction } from 'stepin'; import { StepinHeaderAction } from 'stepin';
import DayNightSwitch from '@/components/switch/DayNightSwitch.vue'; import DayNightSwitch from '@/components/switch/DayNightSwitch.vue';
@@ -301,6 +303,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<!-- 步骤1添加 div 作为单个根元素 -->
<StepinHeaderAction> <StepinHeaderAction>
<DayNightSwitch /> <DayNightSwitch />
</StepinHeaderAction> </StepinHeaderAction>
@@ -376,4 +379,10 @@ onMounted(() => {
color: #1890ff !important; color: #1890ff !important;
} }
} }
/* 新增:根容器样式(和原布局保持一致) */
.header-actions-root {
display: flex;
align-items: center;
gap: 8px; // 按钮之间间距,可根据需要调整
}
</style> </style>
+103 -2
View File
@@ -138,6 +138,20 @@
</div> </div>
</a-form-item> </a-form-item>
<a-form-item v-show="formState.AutoDistinct" has-feedback label="优先级" name="PriorityLevel" :wrapper-col="{ span: 10 }">
<!-- Tag 拖拽容器 -->
<div class="tag-drag-container">
<!-- Tag 拖拽容器绑定 ref Sortable 初始化 -->
<div ref="tagContainer" class="tag-list">
<a-tag v-for="(item, index) in tagData" :key="item.id" class="sortable-tag" color="blue">
<!-- "" 符号替代 a-icon 作为拖拽手柄 -->
<span class="drag-handle" style="margin-right: 6px; cursor: move;"></span>
{{ item.name }}
</a-tag>
</div>
</div>
</a-form-item>
</div> </div>
<!-- 操作按钮 --> <!-- 操作按钮 -->
@@ -157,13 +171,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, toRaw, ref, watch, onMounted, computed } from 'vue'; import { reactive, toRaw, ref, watch, onMounted, computed, nextTick } from 'vue';
import type { UnwrapRef } from 'vue'; import type { UnwrapRef } from 'vue';
import { Form } from 'ant-design-vue'; import { Form } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form'; import type { Rule } from 'ant-design-vue/es/form';
import type { FormInstance } from 'ant-design-vue'; import type { FormInstance } from 'ant-design-vue';
import { useApiStore } from '@/store'; import { useApiStore } from '@/store';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { Sortable } from 'sortablejs';
import { InfoCircleOutlined, SaveOutlined, CheckOutlined } from '@ant-design/icons-vue'; import { InfoCircleOutlined, SaveOutlined, CheckOutlined } from '@ant-design/icons-vue';
// 表单引用 // 表单引用
@@ -200,6 +216,7 @@ interface FormState {
FollowedTitleSeparator: string; // 分隔符 FollowedTitleSeparator: string; // 分隔符
FullFollowedTitleTemplate: string; // 新增:完整模板字符串(自动生成) FullFollowedTitleTemplate: string; // 新增:完整模板字符串(自动生成)
AutoDistinct: boolean; AutoDistinct: boolean;
PriorityLevel: string;
} }
// 表单初始数据 // 表单初始数据
@@ -219,6 +236,7 @@ const formState: UnwrapRef<FormState> = reactive({
FollowedTitleSeparator: '', FollowedTitleSeparator: '',
FullFollowedTitleTemplate: '', // 初始为空 FullFollowedTitleTemplate: '', // 初始为空
AutoDistinct: false, AutoDistinct: false,
PriorityLevel: '',
}); });
// 实时计算完整模板(可选:让用户实时预览,提交时无需重复计算) // 实时计算完整模板(可选:让用户实时预览,提交时无需重复计算)
@@ -294,9 +312,11 @@ const getConfig = () => {
FullFollowedTitleTemplate: fullTemplate, // 回显完整模板 FullFollowedTitleTemplate: fullTemplate, // 回显完整模板
ImageViedoSaveAlone: res.data.imageViedoSaveAlone, ImageViedoSaveAlone: res.data.imageViedoSaveAlone,
AutoDistinct: res.data.autoDistinct, AutoDistinct: res.data.autoDistinct,
PriorityLevel: res.data.priorityLevel,
}); });
downImgVideo.value = res.data.downImageVideoFromEnv; downImgVideo.value = res.data.downImageVideoFromEnv;
tagData.value = JSON.parse(res.data.priorityLevel);
} else { } else {
message.error(res.erro || '获取配置失败', 8); message.error(res.erro || '获取配置失败', 8);
} }
@@ -319,9 +339,48 @@ const parseTemplateToArr = (templateStr: string | null | undefined) => {
return Array.from(new Set(validPlaceholders)); return Array.from(new Set(validPlaceholders));
}; };
// 拖拽容器 ref
const tagContainer = ref(null);
let sortableInstance = ref(null);
// 模拟 Tag 数据(替换为你的实际数据)
const tagData = ref([
{ id: 1, name: '喜欢的视频', sort: 1 },
{ id: 2, name: '收藏的视频', sort: 2 },
{ id: 3, name: '关注的视频', sort: 3 },
]);
// 重新计算所有 Tag 的 sort 值(核心方法)
const updateTagSort = () => {
tagData.value.forEach((item, index) => {
item.sort = index + 1; // sort = 索引+1(保证顺序和 sort 一一对应)
});
};
// 组件挂载时获取配置 // 组件挂载时获取配置
onMounted(() => { onMounted(async () => {
getConfig(); getConfig();
// 等待 Form 组件完全渲染(关键:解决 DOM 未挂载问题)
await nextTick();
if (tagContainer.value) {
sortableInstance.value = new Sortable(tagContainer.value, {
handle: '.drag-handle',
animation: 150,
ghostClass: 'tag-ghost',
preventOnFilter: true,
onEnd: (evt) => {
// 1. 调整 Tag 顺序
const [movedTag] = tagData.value.splice(evt.oldIndex, 1);
tagData.value.splice(evt.newIndex, 0, movedTag);
// 2. 重新计算所有 Tag 的 sort 值(关键:同步 sort 和顺序)
updateTagSort();
// 打印结果(验证 sort 是否同步)
console.log('最新 Tag 数据(含 sort):', tagData.value);
},
});
} else {
console.warn('拖拽容器未找到,请检查 ref 绑定');
}
}); });
// 提交表单(核心:自动生成完整模板字符串) // 提交表单(核心:自动生成完整模板字符串)
@@ -342,6 +401,7 @@ const onSubmit = () => {
...toRaw(formState), ...toRaw(formState),
FullFollowedTitleTemplate: fullTemplate, // 确保提交最新拼接结果 FullFollowedTitleTemplate: fullTemplate, // 确保提交最新拼接结果
FollowedTitleTemplate: formState.FollowedTitleTemplate.join(''), FollowedTitleTemplate: formState.FollowedTitleTemplate.join(''),
PriorityLevel: JSON.stringify(tagData.value),
}; };
// 3. 提交接口 // 3. 提交接口
@@ -440,4 +500,45 @@ const onCancel = () => {
color: #444 !important; color: #444 !important;
font-weight: 500; font-weight: 500;
} }
/* 关键修改:确保拖拽容器不限制子元素 */
.tag-drag-container {
position: relative; /* 必须:让拖拽的 ghost 元素不被截断 */
overflow: visible !important; /* 覆盖 Form 可能的 overflow: hidden */
}
.tag-
/* Tag 列表布局(换行显示) */
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 12px; /* Tag 之间间距 */
}
/* 拖拽中的 Tag 占位样式 */
.tag-ghost {
opacity: 0.5;
background-color: #e6f7ff !important;
border-color: #91d5ff !important;
}
/* 拖拽手柄样式优化 */
.drag-handle {
font-size: 14px;
color: #666;
user-select: none; /* 禁止选中文字 */
}
.drag-handle:hover {
color: #1890ff;
}
/* Tag hover 效果 */
.sortable-tag {
transition: all 0.2s;
}
.sortable-tag:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style> </style>
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"dbconn": "", "dbconn": "",
"dbtype": "Sqlite", "dbtype": "Sqlite",
"tagName": "beta_1.7.1" "tagName": "dev_1.0"
} }
+11
View File
@@ -0,0 +1,11 @@
namespace dy.net.dto
{
public class PriorityLevelDto
{
public int Id { get; set; }
public string Name { get; set; }
public int Sort { get; set; }
}
}
+3
View File
@@ -93,6 +93,9 @@
<None Update="Dockerfile"> <None Update="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="mp3\silent_10.mp3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish"> <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
+2
View File
@@ -17,6 +17,8 @@ namespace dy.net.job
protected override string JobType => SystemStaticUtil.DY_COLLECTS; protected override string JobType => SystemStaticUtil.DY_COLLECTS;
protected override VideoTypeEnum VideoType => VideoTypeEnum.Collect;
protected override async Task BeforeProcessCookies() protected override async Task BeforeProcessCookies()
{ {
var now = DateTime.Now; var now = DateTime.Now;
+3
View File
@@ -18,6 +18,9 @@ namespace dy.net.job
: base(douyinCookieService, douyinHttpClientService, douyinVideoService, douyinCommonService, douyinFollowService, douyinMergeVideoService) { } : base(douyinCookieService, douyinHttpClientService, douyinVideoService, douyinCommonService, douyinFollowService, douyinMergeVideoService) { }
protected override string JobType => SystemStaticUtil.DY_FAVORITES; protected override string JobType => SystemStaticUtil.DY_FAVORITES;
protected override VideoTypeEnum VideoType => VideoTypeEnum.Favorite;
protected override async Task<List<DouyinCookie>> GetValidCookies() protected override async Task<List<DouyinCookie>> GetValidCookies()
{ {
return await douyinCookieService.GetAllOpendAsync(x=> !string.IsNullOrWhiteSpace(x.FavSavePath)); return await douyinCookieService.GetAllOpendAsync(x=> !string.IsNullOrWhiteSpace(x.FavSavePath));
+138 -12
View File
@@ -84,6 +84,8 @@ namespace dy.net.job
/// </summary> /// </summary>
protected abstract string JobType { get; } protected abstract string JobType { get; }
protected abstract VideoTypeEnum VideoType { get; }
#endregion #endregion
#region #region
@@ -410,20 +412,12 @@ namespace dy.net.job
var videos = new List<DouyinVideo>(); var videos = new List<DouyinVideo>();
foreach (var item in data.AwemeList) foreach (var item in data.AwemeList)
{ {
//去重,检查视频是否已存在 //判断是否存在视频是否根据去重规则进行去重处理。。
if (config.AutoDistinct) bool Goon = await AutoDistinct(config, item);
if (!Goon)
{ {
var exitVideo = await douyinVideoService.GetByAwemeId(item.AwemeId); continue;
if (exitVideo != null)
{
if (File.Exists(exitVideo.VideoSavePath))
{
//Log.Debug($"视频-{exitVideo.AwemeId}-[{exitVideo.VideoTitle}]已存在,跳过");
continue;// 已存在则跳过
} }
}
}
var uper = await douyinFollowService.GetByUperId(item.AuthorUserId.ToString(), cookie.MyUserId); var uper = await douyinFollowService.GetByUperId(item.AuthorUserId.ToString(), cookie.MyUserId);
if (uper != null) if (uper != null)
{ {
@@ -448,6 +442,138 @@ namespace dy.net.job
return videos; return videos;
} }
private async Task<bool> AutoDistinct(AppConfig config, Aweme item)
{
// 去重,检查视频是否已存在(按优先级下载)
if (config.AutoDistinct)
{
// 1. 查询数据库中是否已存在该视频(通过 AwemeId 唯一标识)
var exitVideo = await douyinVideoService.GetByAwemeId(item.AwemeId);
if (exitVideo != null)
{
// 2. 已存在视频:先判断本地文件是否存在
if (File.Exists(exitVideo.VideoSavePath))
{
// 3. 解析优先级配置(前端传递的 JSON 字符串)
List<PriorityLevelDto> priLevs = new List<PriorityLevelDto>();
if (!string.IsNullOrWhiteSpace(config.PriorityLevel))
{
try
{
// 反序列化前端传递的优先级列表(JSON 字符串转对象)
priLevs = JsonConvert.DeserializeObject<List<PriorityLevelDto>>(config.PriorityLevel);
}
catch (Exception ex)
{
Log.Error($"解析优先级配置失败:{ex.Message}", ex);
priLevs = new List<PriorityLevelDto>(); // 解析失败则用默认优先级
}
}
// 4. 处理优先级:获取「最高优先级」(Sort 越小优先级越高)
PriorityLevelDto maxPriority = null;
if (priLevs.Any())
{
// 前端已配置优先级:取 Sort 最小的(1最高)
maxPriority = priLevs.OrderBy(x => x.Sort).FirstOrDefault();
}
else
{
// 前端未配置:使用默认优先级(喜欢 > 收藏 > 关注)
maxPriority = new PriorityLevelDto { Id = 1, Sort = 1, Name = "喜欢的" }; // 默认「喜欢的视频」最高
}
// 5. 转换为当前上下文的视频类型
var currentVideoType = VideoType; // 当前要下载的视频类型(如:喜欢/收藏/关注)
var maxPriorityType = (VideoTypeEnum)maxPriority.Id; // 配置的最高优先级类型
// 6. 获取已存在视频的类型(从数据库中 exitVideo 读取,需确保字段存在)
var exitVideoType = exitVideo.ViedoType; // 假设数据库存储了 VideoType1/2/3
// 7. 优先级逻辑判断(核心)
if (currentVideoType == maxPriorityType)
{
// 情况1:当前要下载的是「最高优先级」视频
if (exitVideoType == currentVideoType)
{
// 已存在同优先级视频 → 跳过下载(避免重复)
Log.Debug($"视频-{exitVideo.AwemeId}-[{exitVideo.VideoTitle}]已存在(同最高优先级),跳过");
return false;
}
else
{
// 已存在「低优先级」视频 → 替换(删除旧文件,继续下载新的最高优先级视频)
Log.Debug($"视频-{exitVideo.AwemeId}-[{exitVideo.VideoTitle}]已存在(低优先级:{exitVideoType}),替换为最高优先级:{currentVideoType}");
// 删除旧的低优先级文件(可选:也可保留备份,根据需求调整)
try
{
File.Delete(exitVideo.VideoSavePath);
Log.Debug($"已删除旧文件:{exitVideo.VideoSavePath}");
}
catch (Exception ex)
{
Log.Error($"删除旧文件失败:{ex.Message}", ex);
// 即使删除失败,仍继续下载(新文件会覆盖旧文件,或按路径规则重命名)
}
// 继续执行下载逻辑(覆盖旧数据)
}
}
else
{
// 情况2:当前要下载的是「非最高优先级」视频
if (exitVideoType == maxPriorityType)
{
// 已存在「最高优先级」视频 → 跳过(不替换最高优先级)
Log.Debug($"视频-{exitVideo.AwemeId}-[{exitVideo.VideoTitle}]已存在最高优先级视频({maxPriorityType}),当前类型({currentVideoType})优先级低,跳过");
return false;
}
else
{
// 已存在「其他非最高优先级」视频 → 比较两者优先级
// 获取当前类型和已存在类型的 Sort 值
var currentSort = priLevs.FirstOrDefault(x => x.Id == (int)currentVideoType)?.Sort ?? int.MaxValue;
var exitSort = priLevs.FirstOrDefault(x => x.Id == (int)exitVideoType)?.Sort ?? int.MaxValue;
if (currentSort < exitSort)
{
// 当前类型优先级更高 → 替换旧视频
Log.Debug($"视频-{exitVideo.AwemeId}-[{exitVideo.VideoTitle}]已存在低优先级视频({exitVideoType}),替换为当前优先级:{currentVideoType}");
// 删除旧文件
if (File.Exists(exitVideo.VideoSavePath))
{
File.Delete(exitVideo.VideoSavePath);
}
// 继续下载
}
else
{
// 当前类型优先级更低或相等 → 跳过
Log.Debug($"视频-{exitVideo.AwemeId}-[{exitVideo.VideoTitle}]已存在更高/同等优先级视频({exitVideoType}),当前类型({currentVideoType})跳过");
return false;
}
}
}
}
else
{
// 数据库存在记录,但本地文件丢失 → 直接重新下载(无论优先级)
Log.Debug($"视频-{exitVideo.AwemeId}-[{exitVideo.VideoTitle}]数据库存在但本地文件丢失,重新下载");
}
}
else
{
// 数据库和本地均无该视频 → 直接下载
//Log.Debug($"视频-{item.AwemeId}-[{item.Desc}]未存在,开始下载");
}
}
return true;
}
/// <summary> /// <summary>
/// 处理单个视频 /// 处理单个视频
/// 负责下载视频、封面、头像,生成NFO文件,创建视频实体等 /// 负责下载视频、封面、头像,生成NFO文件,创建视频实体等
+2
View File
@@ -18,6 +18,8 @@ namespace dy.net.job
protected override string JobType => SystemStaticUtil.DY_FOLLOWEDS; protected override string JobType => SystemStaticUtil.DY_FOLLOWEDS;
protected override VideoTypeEnum VideoType => VideoTypeEnum.UperPost;
protected override async Task<List<DouyinCookie>> GetValidCookies() protected override async Task<List<DouyinCookie>> GetValidCookies()
{ {
return await douyinCookieService.GetAllOpendAsync(x => !string.IsNullOrWhiteSpace(x.UpSavePath)); return await douyinCookieService.GetAllOpendAsync(x => !string.IsNullOrWhiteSpace(x.UpSavePath));
+9 -1
View File
@@ -1,4 +1,5 @@
using SqlSugar; using Newtonsoft.Json;
using SqlSugar;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace dy.net.model namespace dy.net.model
@@ -73,5 +74,12 @@ namespace dy.net.model
/// 自动去重-逻辑是遇到相同ID的视频直接跳过 /// 自动去重-逻辑是遇到相同ID的视频直接跳过
/// </summary> /// </summary>
public bool AutoDistinct { get; set; } public bool AutoDistinct { get; set; }
/// <summary>
/// 去重优先级配置,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}]";
} }
} }
BIN
View File
Binary file not shown.
+9 -1
View File
@@ -31,6 +31,12 @@ namespace dy.net.service
var conf = GetConfig(); var conf = GetConfig();
if (conf != null) if (conf != null)
{ {
if (string.IsNullOrWhiteSpace(conf.PriorityLevel))
{
conf.PriorityLevel = "[{\"id\":1,\"name\":\"喜欢的视频\",\"sort\":1},{\"id\":2,\"name\":\"收藏的视频\",\"sort\":2},{\"id\":3,\"name\":\"关注的视频\",\"sort\":3}]";
sqlSugarClient.Updateable(conf).ExecuteCommand();
//兼容旧版本
}
return conf; return conf;
} }
else else
@@ -50,7 +56,8 @@ namespace dy.net.service
FollowedTitleTemplate = "", FollowedTitleTemplate = "",
FullFollowedTitleTemplate = "", FullFollowedTitleTemplate = "",
FollowedTitleSeparator = "", FollowedTitleSeparator = "",
AutoDistinct = true//默认开启 AutoDistinct = true,//默认开启
PriorityLevel= "[{\"id\":1,\"name\":\"喜欢的视频\",\"sort\":1},{\"id\":2,\"name\":\"收藏的视频\",\"sort\":2},{\"id\":3,\"name\":\"关注的视频\",\"sort\":3}]"
}; };
sqlSugarClient.Insertable(config).ExecuteCommand(); sqlSugarClient.Insertable(config).ExecuteCommand();
return config; return config;
@@ -83,6 +90,7 @@ namespace dy.net.service
cc.ImageViedoSaveAlone = config.ImageViedoSaveAlone; cc.ImageViedoSaveAlone = config.ImageViedoSaveAlone;
cc.FollowedTitleSeparator = config.FollowedTitleSeparator; cc.FollowedTitleSeparator = config.FollowedTitleSeparator;
cc.AutoDistinct = config.AutoDistinct; cc.AutoDistinct = config.AutoDistinct;
cc.PriorityLevel = config.PriorityLevel;
} }
int update = await sqlSugarClient.Updateable(cc).ExecuteCommandAsync(); int update = await sqlSugarClient.Updateable(cc).ExecuteCommandAsync();
+13 -6
View File
@@ -177,11 +177,11 @@ namespace dy.net.service
Log.Error("图片URL列表为空,无法合成视频"); Log.Error("图片URL列表为空,无法合成视频");
return false; return false;
} }
if (mergeImg2Viedo && (request.AudioUrls == null || request.AudioUrls.Count == 0)) //if (mergeImg2Viedo && (request.AudioUrls == null || request.AudioUrls.Count == 0))
{ //{
Log.Error("合成视频时音频URL列表为空"); // Log.Error("合成视频时音频URL列表为空");
return false; // return false;
} //}
string tempDir = null; string tempDir = null;
try try
@@ -206,7 +206,7 @@ namespace dy.net.service
// 2. 下载音频 // 2. 下载音频
string[] rawAudios = Array.Empty<string>(); string[] rawAudios = Array.Empty<string>();
if (mergeImg2Viedo) if (mergeImg2Viedo&& request.AudioUrls != null && request.AudioUrls.Count>0)
{ {
var (audios, audioError) = await DownloadMediaAsync( var (audios, audioError) = await DownloadMediaAsync(
request.AudioUrls, Path.Combine(tempDir, "raw-audios"), "audio_", "mp3", cookie); request.AudioUrls, Path.Combine(tempDir, "raw-audios"), "audio_", "mp3", cookie);
@@ -257,6 +257,13 @@ namespace dy.net.service
// 超时控制:避免 FFmpeg 进程无限运行(300秒=5分钟) // 超时控制:避免 FFmpeg 进程无限运行(300秒=5分钟)
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300)); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300));
// 执行合成(确保每个图片都参与) // 执行合成(确保每个图片都参与)
if (rawAudios.Length == 0)
{
rawAudios= new string[] { Path.Combine(AppContext.BaseDirectory,"mp3", "silent_10.mp3") };
Log.Debug("未检测到音频,使用默认静音音频-无声");
}
string resultPath = await ffmpegHelper.CreateVideoFromImagesAndAudioAsync( string resultPath = await ffmpegHelper.CreateVideoFromImagesAndAudioAsync(
rawImages, // 所有下载的图片(确保无遗漏) rawImages, // 所有下载的图片(确保无遗漏)
rawAudios[0], // 取第一个音频(可根据需求调整) rawAudios[0], // 取第一个音频(可根据需求调整)