去重优先级,视频合成,没有音频用默认音频
This commit is contained in:
@@ -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
@@ -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>
|
||||||
@@ -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
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dbconn": "",
|
"dbconn": "",
|
||||||
"dbtype": "Sqlite",
|
"dbtype": "Sqlite",
|
||||||
"tagName": "beta_1.7.1"
|
"tagName": "dev_1.0"
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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; // 假设数据库存储了 VideoType(1/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文件,创建视频实体等
|
||||||
|
|||||||
@@ -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
@@ -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}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -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();
|
||||||
|
|||||||
@@ -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], // 取第一个音频(可根据需求调整)
|
||||||
|
|||||||
Reference in New Issue
Block a user