mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-06 08:06:43 +08:00
feat: support selectable anime metadata sources
This commit is contained in:
+18
-2
@@ -1,3 +1,5 @@
|
|||||||
|
export type MediaDataSource = 'themoviedb' | 'douban' | 'bangumi' | 'anilist'
|
||||||
|
|
||||||
// 订阅
|
// 订阅
|
||||||
export interface Subscribe {
|
export interface Subscribe {
|
||||||
// 订阅ID
|
// 订阅ID
|
||||||
@@ -218,6 +220,10 @@ export interface TransferHistory {
|
|||||||
tvdbid?: number
|
tvdbid?: number
|
||||||
// 豆瓣ID
|
// 豆瓣ID
|
||||||
doubanid?: string
|
doubanid?: string
|
||||||
|
// 媒体数据源
|
||||||
|
media_source?: MediaDataSource
|
||||||
|
// 数据源原生ID
|
||||||
|
media_id?: string
|
||||||
// 季Sxx
|
// 季Sxx
|
||||||
seasons?: string
|
seasons?: string
|
||||||
// 集Exx
|
// 集Exx
|
||||||
@@ -238,7 +244,7 @@ export interface TransferHistory {
|
|||||||
|
|
||||||
// 媒体信息
|
// 媒体信息
|
||||||
export interface MediaInfo {
|
export interface MediaInfo {
|
||||||
// 来源:themoviedb、douban、bangumi
|
// 来源:themoviedb、douban、bangumi、anilist
|
||||||
source?: string
|
source?: string
|
||||||
// 类型 电影、电视剧、合集
|
// 类型 电影、电视剧、合集
|
||||||
type?: string
|
type?: string
|
||||||
@@ -259,7 +265,11 @@ export interface MediaInfo {
|
|||||||
// 豆瓣ID
|
// 豆瓣ID
|
||||||
douban_id?: string
|
douban_id?: string
|
||||||
// Bangumi ID
|
// Bangumi ID
|
||||||
bangumi_id?: string
|
bangumi_id?: string | number
|
||||||
|
// AniList ID
|
||||||
|
anilist_id?: number
|
||||||
|
// AniDB ID
|
||||||
|
anidb_id?: number
|
||||||
// 合集ID
|
// 合集ID
|
||||||
collection_id?: number
|
collection_id?: number
|
||||||
// 其它媒体ID前缀
|
// 其它媒体ID前缀
|
||||||
@@ -1353,6 +1363,8 @@ export interface MediaServerConf {
|
|||||||
enabled: boolean
|
enabled: boolean
|
||||||
// 同步媒体体库列表
|
// 同步媒体体库列表
|
||||||
sync_libraries?: string[]
|
sync_libraries?: string[]
|
||||||
|
// 自动同步间隔(小时),为空时使用旧全局配置
|
||||||
|
sync_interval?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件整理目录配置
|
// 文件整理目录配置
|
||||||
@@ -1513,6 +1525,10 @@ export interface TransferForm {
|
|||||||
tmdbid?: number
|
tmdbid?: number
|
||||||
// 豆瓣 ID
|
// 豆瓣 ID
|
||||||
doubanid?: string
|
doubanid?: string
|
||||||
|
// 媒体数据源
|
||||||
|
media_source?: MediaDataSource
|
||||||
|
// 数据源原生ID
|
||||||
|
media_id?: string | null
|
||||||
// 季号
|
// 季号
|
||||||
season?: number
|
season?: number
|
||||||
// 类型
|
// 类型
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ const props = defineProps({
|
|||||||
type: Array as PropType<MediaServerConf[]>,
|
type: Array as PropType<MediaServerConf[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
// 旧版全局同步间隔,用作服务器未单独设置时的默认值
|
||||||
|
defaultSyncInterval: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义触发的自定义事件
|
// 定义触发的自定义事件
|
||||||
@@ -56,6 +61,7 @@ function openMediaServerInfoDialog() {
|
|||||||
{
|
{
|
||||||
mediaserver: props.mediaserver,
|
mediaserver: props.mediaserver,
|
||||||
mediaservers: props.mediaservers,
|
mediaservers: props.mediaservers,
|
||||||
|
defaultSyncInterval: props.defaultSyncInterval,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
change: (...args: unknown[]) => emit('change', ...args),
|
change: (...args: unknown[]) => emit('change', ...args),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
||||||
import type { DownloaderConf, MediaInfo, TorrentInfo, TransferDirectoryConf } from '@/api/types'
|
import type { DownloaderConf, MediaDataSource, MediaInfo, TorrentInfo, TransferDirectoryConf } from '@/api/types'
|
||||||
import { formatFileSize } from '@/@core/utils/formatters'
|
import { formatFileSize } from '@/@core/utils/formatters'
|
||||||
import { VCardTitle, VChip } from 'vuetify/lib/components/index.mjs'
|
import { VCardTitle, VChip } from 'vuetify/lib/components/index.mjs'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
@@ -18,7 +18,11 @@ const globalSettingsStore = useGlobalSettingsStore()
|
|||||||
const globalSettings = globalSettingsStore.globalSettings
|
const globalSettings = globalSettingsStore.globalSettings
|
||||||
|
|
||||||
// 当前识别类型
|
// 当前识别类型
|
||||||
const mediaSource = ref(globalSettings.RECOGNIZE_SOURCE || 'themoviedb')
|
const mediaSource = ref<MediaDataSource>(
|
||||||
|
['themoviedb', 'douban', 'bangumi', 'anilist'].includes(globalSettings.RECOGNIZE_SOURCE)
|
||||||
|
? globalSettings.RECOGNIZE_SOURCE
|
||||||
|
: 'themoviedb',
|
||||||
|
)
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -51,11 +55,19 @@ const loading = ref(false)
|
|||||||
// 是否显示高级选项
|
// 是否显示高级选项
|
||||||
const showAdvancedOptions = ref(false)
|
const showAdvancedOptions = ref(false)
|
||||||
|
|
||||||
// TMDB ID
|
// 当前数据源的原生媒体ID
|
||||||
const tmdbid = ref<number | undefined>(undefined)
|
const mediaId = ref<string | undefined>(undefined)
|
||||||
|
|
||||||
// 豆瓣ID
|
// 当前数据源对应的原生ID标签。
|
||||||
const doubanId = ref<string | undefined>(undefined)
|
const mediaIdLabel = computed(() => {
|
||||||
|
const labels: Record<MediaDataSource, string> = {
|
||||||
|
themoviedb: t('dialog.reorganize.tmdbId'),
|
||||||
|
douban: t('dialog.reorganize.doubanId'),
|
||||||
|
bangumi: t('dialog.reorganize.bangumiId'),
|
||||||
|
anilist: t('dialog.reorganize.anilistId'),
|
||||||
|
}
|
||||||
|
return labels[mediaSource.value]
|
||||||
|
})
|
||||||
|
|
||||||
// TMDB选择对话框
|
// TMDB选择对话框
|
||||||
const mediaSelectorDialog = ref(false)
|
const mediaSelectorDialog = ref(false)
|
||||||
@@ -140,11 +152,9 @@ async function addDownload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加媒体ID辅助识别
|
// 添加媒体ID辅助识别
|
||||||
if (tmdbid.value) {
|
if (mediaId.value) {
|
||||||
payload.tmdbid = tmdbid.value
|
payload.media_source = mediaSource.value
|
||||||
}
|
payload.media_id = mediaId.value
|
||||||
if (doubanId.value) {
|
|
||||||
payload.doubanid = doubanId.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint = props.media ? 'download/' : 'download/add'
|
const endpoint = props.media ? 'download/' : 'download/add'
|
||||||
@@ -269,23 +279,8 @@ onMounted(() => {
|
|||||||
<VRow v-show="showAdvancedOptions" class="px-5">
|
<VRow v-show="showAdvancedOptions" class="px-5">
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-if="mediaSource === 'themoviedb'"
|
v-model="mediaId"
|
||||||
v-model="tmdbid"
|
:label="mediaIdLabel"
|
||||||
:label="t('dialog.reorganize.tmdbId')"
|
|
||||||
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
|
||||||
:rules="[numberValidator]"
|
|
||||||
append-inner-icon="mdi-magnify"
|
|
||||||
:hint="t('dialog.reorganize.mediaIdHint')"
|
|
||||||
persistent-hint
|
|
||||||
prepend-inner-icon="mdi-identifier"
|
|
||||||
variant="underlined"
|
|
||||||
density="comfortable"
|
|
||||||
@click:append-inner="mediaSelectorDialog = true"
|
|
||||||
/>
|
|
||||||
<VTextField
|
|
||||||
v-else
|
|
||||||
v-model="doubanId"
|
|
||||||
:label="t('dialog.reorganize.doubanId')"
|
|
||||||
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
||||||
:rules="[numberValidator]"
|
:rules="[numberValidator]"
|
||||||
append-inner-icon="mdi-magnify"
|
append-inner-icon="mdi-magnify"
|
||||||
@@ -307,13 +302,7 @@ onMounted(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
<!-- 媒体ID选择器 -->
|
<!-- 媒体ID选择器 -->
|
||||||
<VDialog v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
|
<VDialog v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
|
||||||
<MediaIdSelector
|
<MediaIdSelector v-model="mediaId" @close="mediaSelectorDialog = false" :type="mediaSource" />
|
||||||
v-if="mediaSource === 'themoviedb'"
|
|
||||||
v-model="tmdbid"
|
|
||||||
@close="mediaSelectorDialog = false"
|
|
||||||
:type="mediaSource"
|
|
||||||
/>
|
|
||||||
<MediaIdSelector v-else v-model="doubanId" @close="mediaSelectorDialog = false" :type="mediaSource" />
|
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
||||||
import type { SubtitleInfo, TransferDirectoryConf } from '@/api/types'
|
import type { MediaDataSource, SubtitleInfo, TransferDirectoryConf } from '@/api/types'
|
||||||
import { formatFileSize } from '@/@core/utils/formatters'
|
import { formatFileSize } from '@/@core/utils/formatters'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import MediaIdSelector from '../misc/MediaIdSelector.vue'
|
import MediaIdSelector from '../misc/MediaIdSelector.vue'
|
||||||
@@ -17,7 +17,11 @@ const globalSettingsStore = useGlobalSettingsStore()
|
|||||||
const globalSettings = globalSettingsStore.globalSettings
|
const globalSettings = globalSettingsStore.globalSettings
|
||||||
|
|
||||||
// 当前识别类型
|
// 当前识别类型
|
||||||
const mediaSource = ref(globalSettings.RECOGNIZE_SOURCE || 'themoviedb')
|
const mediaSource = ref<MediaDataSource>(
|
||||||
|
['themoviedb', 'douban', 'bangumi', 'anilist'].includes(globalSettings.RECOGNIZE_SOURCE)
|
||||||
|
? globalSettings.RECOGNIZE_SOURCE
|
||||||
|
: 'themoviedb',
|
||||||
|
)
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -43,11 +47,19 @@ const loading = ref(false)
|
|||||||
// 是否显示高级选项
|
// 是否显示高级选项
|
||||||
const showAdvancedOptions = ref(false)
|
const showAdvancedOptions = ref(false)
|
||||||
|
|
||||||
// TMDB ID
|
// 当前数据源的原生媒体ID
|
||||||
const tmdbid = ref<number | undefined>(undefined)
|
const mediaId = ref<string | undefined>(undefined)
|
||||||
|
|
||||||
// 豆瓣ID
|
// 当前数据源对应的原生ID标签。
|
||||||
const doubanId = ref<string | undefined>(undefined)
|
const mediaIdLabel = computed(() => {
|
||||||
|
const labels: Record<MediaDataSource, string> = {
|
||||||
|
themoviedb: t('dialog.reorganize.tmdbId'),
|
||||||
|
douban: t('dialog.reorganize.doubanId'),
|
||||||
|
bangumi: t('dialog.reorganize.bangumiId'),
|
||||||
|
anilist: t('dialog.reorganize.anilistId'),
|
||||||
|
}
|
||||||
|
return labels[mediaSource.value]
|
||||||
|
})
|
||||||
|
|
||||||
// TMDB选择对话框
|
// TMDB选择对话框
|
||||||
const mediaSelectorDialog = ref(false)
|
const mediaSelectorDialog = ref(false)
|
||||||
@@ -98,11 +110,9 @@ async function addSubtitleDownload() {
|
|||||||
save_path: selectedDirectory.value,
|
save_path: selectedDirectory.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tmdbid.value) {
|
if (mediaId.value) {
|
||||||
payload.tmdbid = tmdbid.value
|
payload.media_source = mediaSource.value
|
||||||
}
|
payload.media_id = mediaId.value
|
||||||
if (doubanId.value) {
|
|
||||||
payload.doubanid = doubanId.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.post('download/subtitle', payload)
|
const result: { [key: string]: any } = await api.post('download/subtitle', payload)
|
||||||
@@ -221,23 +231,8 @@ onMounted(() => {
|
|||||||
<VRow v-show="showAdvancedOptions" class="px-5">
|
<VRow v-show="showAdvancedOptions" class="px-5">
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-if="mediaSource === 'themoviedb'"
|
v-model="mediaId"
|
||||||
v-model="tmdbid"
|
:label="mediaIdLabel"
|
||||||
:label="t('dialog.reorganize.tmdbId')"
|
|
||||||
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
|
||||||
:rules="[numberValidator]"
|
|
||||||
append-inner-icon="mdi-magnify"
|
|
||||||
:hint="t('dialog.reorganize.mediaIdHint')"
|
|
||||||
persistent-hint
|
|
||||||
prepend-inner-icon="mdi-identifier"
|
|
||||||
variant="underlined"
|
|
||||||
density="comfortable"
|
|
||||||
@click:append-inner="mediaSelectorDialog = true"
|
|
||||||
/>
|
|
||||||
<VTextField
|
|
||||||
v-else
|
|
||||||
v-model="doubanId"
|
|
||||||
:label="t('dialog.reorganize.doubanId')"
|
|
||||||
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
||||||
:rules="[numberValidator]"
|
:rules="[numberValidator]"
|
||||||
append-inner-icon="mdi-magnify"
|
append-inner-icon="mdi-magnify"
|
||||||
@@ -258,13 +253,7 @@ onMounted(() => {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<VDialog v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
|
<VDialog v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
|
||||||
<MediaIdSelector
|
<MediaIdSelector v-model="mediaId" @close="mediaSelectorDialog = false" :type="mediaSource" />
|
||||||
v-if="mediaSource === 'themoviedb'"
|
|
||||||
v-model="tmdbid"
|
|
||||||
@close="mediaSelectorDialog = false"
|
|
||||||
:type="mediaSource"
|
|
||||||
/>
|
|
||||||
<MediaIdSelector v-else v-model="doubanId" @close="mediaSelectorDialog = false" :type="mediaSource" />
|
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ const props = defineProps({
|
|||||||
type: Array as PropType<MediaServerConf[]>,
|
type: Array as PropType<MediaServerConf[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
defaultSyncInterval: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义触发的自定义事件
|
// 定义触发的自定义事件
|
||||||
@@ -203,6 +207,20 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-key"
|
prepend-inner-icon="mdi-key"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model.number="mediaServerInfo.sync_interval"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
clearable
|
||||||
|
:label="t('mediaserver.syncInterval')"
|
||||||
|
:hint="t('mediaserver.syncIntervalHint', { interval: props.defaultSyncInterval ?? 6 })"
|
||||||
|
persistent-hint
|
||||||
|
suffix="h"
|
||||||
|
prepend-inner-icon="mdi-sync"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VAutocomplete
|
<VAutocomplete
|
||||||
v-model="mediaServerInfo.sync_libraries"
|
v-model="mediaServerInfo.sync_libraries"
|
||||||
@@ -243,7 +261,7 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-server"
|
prepend-inner-icon="mdi-server"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="6">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="mediaServerInfo.config.play_host"
|
v-model="mediaServerInfo.config.play_host"
|
||||||
:label="t('mediaserver.playHost')"
|
:label="t('mediaserver.playHost')"
|
||||||
@@ -274,6 +292,20 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-lock"
|
prepend-inner-icon="mdi-lock"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model.number="mediaServerInfo.sync_interval"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
clearable
|
||||||
|
:label="t('mediaserver.syncInterval')"
|
||||||
|
:hint="t('mediaserver.syncIntervalHint', { interval: props.defaultSyncInterval ?? 6 })"
|
||||||
|
persistent-hint
|
||||||
|
suffix="h"
|
||||||
|
prepend-inner-icon="mdi-sync"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VAutocomplete
|
<VAutocomplete
|
||||||
v-model="mediaServerInfo.sync_libraries"
|
v-model="mediaServerInfo.sync_libraries"
|
||||||
@@ -335,6 +367,20 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-key"
|
prepend-inner-icon="mdi-key"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model.number="mediaServerInfo.sync_interval"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
clearable
|
||||||
|
:label="t('mediaserver.syncInterval')"
|
||||||
|
:hint="t('mediaserver.syncIntervalHint', { interval: props.defaultSyncInterval ?? 6 })"
|
||||||
|
persistent-hint
|
||||||
|
suffix="h"
|
||||||
|
prepend-inner-icon="mdi-sync"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VAutocomplete
|
<VAutocomplete
|
||||||
v-model="mediaServerInfo.sync_libraries"
|
v-model="mediaServerInfo.sync_libraries"
|
||||||
@@ -375,7 +421,7 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-server"
|
prepend-inner-icon="mdi-server"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="6">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="mediaServerInfo.config.play_host"
|
v-model="mediaServerInfo.config.play_host"
|
||||||
:label="t('mediaserver.playHost')"
|
:label="t('mediaserver.playHost')"
|
||||||
@@ -403,6 +449,20 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-lock"
|
prepend-inner-icon="mdi-lock"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model.number="mediaServerInfo.sync_interval"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
clearable
|
||||||
|
:label="t('mediaserver.syncInterval')"
|
||||||
|
:hint="t('mediaserver.syncIntervalHint', { interval: props.defaultSyncInterval ?? 6 })"
|
||||||
|
persistent-hint
|
||||||
|
suffix="h"
|
||||||
|
prepend-inner-icon="mdi-sync"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VAutocomplete
|
<VAutocomplete
|
||||||
v-model="mediaServerInfo.sync_libraries"
|
v-model="mediaServerInfo.sync_libraries"
|
||||||
@@ -443,7 +503,7 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-server"
|
prepend-inner-icon="mdi-server"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="6">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="mediaServerInfo.config.play_host"
|
v-model="mediaServerInfo.config.play_host"
|
||||||
:label="t('mediaserver.playHost')"
|
:label="t('mediaserver.playHost')"
|
||||||
@@ -471,20 +531,18 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-lock"
|
prepend-inner-icon="mdi-lock"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12" md="6">
|
||||||
<VAutocomplete
|
<VTextField
|
||||||
v-model="mediaServerInfo.sync_libraries"
|
v-model.number="mediaServerInfo.sync_interval"
|
||||||
:label="t('mediaserver.syncLibraries')"
|
type="number"
|
||||||
:items="librariesOptions"
|
min="0"
|
||||||
chips
|
step="1"
|
||||||
multiple
|
|
||||||
clearable
|
clearable
|
||||||
:hint="t('mediaserver.syncLibrariesHint')"
|
:label="t('mediaserver.syncInterval')"
|
||||||
|
:hint="t('mediaserver.syncIntervalHint', { interval: props.defaultSyncInterval ?? 6 })"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
active
|
suffix="h"
|
||||||
append-inner-icon="mdi-refresh"
|
prepend-inner-icon="mdi-sync"
|
||||||
prepend-inner-icon="mdi-library"
|
|
||||||
@click:append-inner="loadLibrary(mediaServerInfo.name)"
|
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
@@ -508,6 +566,22 @@ onMounted(() => {
|
|||||||
inset
|
inset
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12">
|
||||||
|
<VAutocomplete
|
||||||
|
v-model="mediaServerInfo.sync_libraries"
|
||||||
|
:label="t('mediaserver.syncLibraries')"
|
||||||
|
:items="librariesOptions"
|
||||||
|
chips
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
:hint="t('mediaserver.syncLibrariesHint')"
|
||||||
|
persistent-hint
|
||||||
|
active
|
||||||
|
append-inner-icon="mdi-refresh"
|
||||||
|
prepend-inner-icon="mdi-library"
|
||||||
|
@click:append-inner="loadLibrary(mediaServerInfo.name)"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VRow v-else-if="mediaServerInfo.type == 'plex'">
|
<VRow v-else-if="mediaServerInfo.type == 'plex'">
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
@@ -553,6 +627,20 @@ onMounted(() => {
|
|||||||
prepend-inner-icon="mdi-key"
|
prepend-inner-icon="mdi-key"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model.number="mediaServerInfo.sync_interval"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
clearable
|
||||||
|
:label="t('mediaserver.syncInterval')"
|
||||||
|
:hint="t('mediaserver.syncIntervalHint', { interval: props.defaultSyncInterval ?? 6 })"
|
||||||
|
persistent-hint
|
||||||
|
suffix="h"
|
||||||
|
prepend-inner-icon="mdi-sync"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VAutocomplete
|
<VAutocomplete
|
||||||
v-model="mediaServerInfo.sync_libraries"
|
v-model="mediaServerInfo.sync_libraries"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ManualTransferPayload,
|
ManualTransferPayload,
|
||||||
ManualTransferPreviewData,
|
ManualTransferPreviewData,
|
||||||
ManualTransferPreviewItem,
|
ManualTransferPreviewItem,
|
||||||
|
MediaDataSource,
|
||||||
MediaInfo,
|
MediaInfo,
|
||||||
StorageConf,
|
StorageConf,
|
||||||
TransferDirectoryConf,
|
TransferDirectoryConf,
|
||||||
@@ -37,13 +38,22 @@ const props = defineProps({
|
|||||||
target_path: String,
|
target_path: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 从 provide 中获取全局设置
|
|
||||||
// 全局设置
|
// 全局设置
|
||||||
const globalSettingsStore = useGlobalSettingsStore()
|
const globalSettingsStore = useGlobalSettingsStore()
|
||||||
const globalSettings = globalSettingsStore.globalSettings
|
const globalSettings = globalSettingsStore.globalSettings
|
||||||
|
|
||||||
// 当前识别类型
|
const mediaSourceItems: { title: string; value: MediaDataSource }[] = [
|
||||||
const mediaSource = ref(globalSettings.RECOGNIZE_SOURCE || 'themoviedb')
|
{ title: 'TheMovieDb', value: 'themoviedb' },
|
||||||
|
{ title: '豆瓣', value: 'douban' },
|
||||||
|
{ title: 'Bangumi', value: 'bangumi' },
|
||||||
|
{ title: 'AniList', value: 'anilist' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 获取后台设置中的默认识别数据源,未知值兼容回退到TheMovieDb。
|
||||||
|
function getDefaultMediaSource(): MediaDataSource {
|
||||||
|
const configuredSource = globalSettings.RECOGNIZE_SOURCE as MediaDataSource
|
||||||
|
return mediaSourceItems.some(item => item.value === configuredSource) ? configuredSource : 'themoviedb'
|
||||||
|
}
|
||||||
|
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits(['done', 'close'])
|
const emit = defineEmits(['done', 'close'])
|
||||||
@@ -304,6 +314,8 @@ const transferForm = reactive<TransferForm>({
|
|||||||
logid: 0,
|
logid: 0,
|
||||||
target_storage: initialTargetPath ? (props.target_storage ?? 'local') : null,
|
target_storage: initialTargetPath ? (props.target_storage ?? 'local') : null,
|
||||||
target_path: initialTargetPath,
|
target_path: initialTargetPath,
|
||||||
|
media_source: getDefaultMediaSource(),
|
||||||
|
media_id: null,
|
||||||
transfer_type: null,
|
transfer_type: null,
|
||||||
min_filesize: 0,
|
min_filesize: 0,
|
||||||
scrape: initialTargetPath ? false : null,
|
scrape: initialTargetPath ? false : null,
|
||||||
@@ -313,6 +325,20 @@ const transferForm = reactive<TransferForm>({
|
|||||||
episode_group: null,
|
episode_group: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 当前手动识别与刮削数据源。
|
||||||
|
const mediaSource = computed(() => transferForm.media_source ?? 'themoviedb')
|
||||||
|
|
||||||
|
// 当前数据源对应的原生ID标签。
|
||||||
|
const mediaIdLabel = computed(() => {
|
||||||
|
const labels: Record<MediaDataSource, string> = {
|
||||||
|
themoviedb: t('dialog.reorganize.tmdbId'),
|
||||||
|
douban: t('dialog.reorganize.doubanId'),
|
||||||
|
bangumi: t('dialog.reorganize.bangumiId'),
|
||||||
|
anilist: t('dialog.reorganize.anilistId'),
|
||||||
|
}
|
||||||
|
return labels[mediaSource.value]
|
||||||
|
})
|
||||||
|
|
||||||
// 处理媒体搜索结果选择,同步搜索结果中已识别的媒体类型。
|
// 处理媒体搜索结果选择,同步搜索结果中已识别的媒体类型。
|
||||||
function handleMediaSelected(item: Pick<MediaInfo, 'type'>) {
|
function handleMediaSelected(item: Pick<MediaInfo, 'type'>) {
|
||||||
const typeName = resolveTransferMediaType(item.type)
|
const typeName = resolveTransferMediaType(item.type)
|
||||||
@@ -403,28 +429,39 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听 TMDB 编号变化,自动加载可用剧集组并清空旧选择。
|
// 监听媒体编号变化,仅在TMDB电视剧场景加载剧集组。
|
||||||
watch(
|
watch(
|
||||||
() => transferForm.tmdbid,
|
() => transferForm.media_id,
|
||||||
tmdbid => {
|
mediaId => {
|
||||||
transferForm.episode_group = null
|
transferForm.episode_group = null
|
||||||
episodeGroups.value = []
|
episodeGroups.value = []
|
||||||
if (episodeGroupQueryTimer) clearTimeout(episodeGroupQueryTimer)
|
if (episodeGroupQueryTimer) clearTimeout(episodeGroupQueryTimer)
|
||||||
if (transferForm.type_name !== '电视剧' || mediaSource.value !== 'themoviedb') return
|
if (transferForm.type_name !== '电视剧' || mediaSource.value !== 'themoviedb') return
|
||||||
episodeGroupQueryTimer = setTimeout(() => getEpisodeGroups(tmdbid), 400)
|
episodeGroupQueryTimer = setTimeout(() => getEpisodeGroups(mediaId ?? undefined), 400)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// 切换媒体类型或识别源时,非 TMDB 电视剧不保留剧集组选择。
|
// 切换媒体类型或识别源时,非 TMDB 电视剧不保留剧集组选择。
|
||||||
watch([() => transferForm.type_name, () => mediaSource.value], ([typeName, source]) => {
|
watch([() => transferForm.type_name, () => mediaSource.value], ([typeName, source]) => {
|
||||||
if (typeName === '电视剧' && source === 'themoviedb' && transferForm.tmdbid) {
|
if (typeName === '电视剧' && source === 'themoviedb' && transferForm.media_id) {
|
||||||
getEpisodeGroups(transferForm.tmdbid)
|
getEpisodeGroups(transferForm.media_id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
transferForm.episode_group = null
|
transferForm.episode_group = null
|
||||||
episodeGroups.value = []
|
episodeGroups.value = []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 切换数据源时清空上一来源的原生ID,避免把同一数字误传给新来源。
|
||||||
|
watch(
|
||||||
|
() => transferForm.media_source,
|
||||||
|
(source, previousSource) => {
|
||||||
|
if (previousSource && source !== previousSource) {
|
||||||
|
transferForm.media_id = null
|
||||||
|
mediaSelectorDialog.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => transferForm.episode_group,
|
() => transferForm.episode_group,
|
||||||
episodeGroup => {
|
episodeGroup => {
|
||||||
@@ -859,6 +896,8 @@ function createTransferPayload(options: { item?: FileItem; items?: FileItem[]; l
|
|||||||
target_storage: normalizeOptionalText(transferForm.target_storage),
|
target_storage: normalizeOptionalText(transferForm.target_storage),
|
||||||
target_path: normalizeTargetPath(transferForm.target_path),
|
target_path: normalizeTargetPath(transferForm.target_path),
|
||||||
transfer_type: normalizeOptionalText(transferForm.transfer_type),
|
transfer_type: normalizeOptionalText(transferForm.transfer_type),
|
||||||
|
media_source: mediaSource.value,
|
||||||
|
media_id: normalizeOptionalText(transferForm.media_id),
|
||||||
episode_group: normalizeEpisodeGroup(transferForm.episode_group),
|
episode_group: normalizeEpisodeGroup(transferForm.episode_group),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1385,7 +1424,7 @@ onUnmounted(() => {
|
|||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="4">
|
||||||
<VSelect
|
<VSelect
|
||||||
v-model="transferForm.type_name"
|
v-model="transferForm.type_name"
|
||||||
:label="t('dialog.reorganize.mediaType')"
|
:label="t('dialog.reorganize.mediaType')"
|
||||||
@@ -1399,25 +1438,21 @@ onUnmounted(() => {
|
|||||||
prepend-inner-icon="mdi-movie-open"
|
prepend-inner-icon="mdi-movie-open"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="4">
|
||||||
<VTextField
|
<VSelect
|
||||||
v-if="mediaSource === 'themoviedb'"
|
v-model="transferForm.media_source"
|
||||||
v-model="transferForm.tmdbid"
|
:items="mediaSourceItems"
|
||||||
:disabled="transferForm.type_name === ''"
|
:label="t('dialog.reorganize.mediaSource')"
|
||||||
:label="t('dialog.reorganize.tmdbId')"
|
:hint="t('dialog.reorganize.mediaSourceHint')"
|
||||||
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
|
||||||
:rules="[numberValidator]"
|
|
||||||
append-inner-icon="mdi-magnify"
|
|
||||||
:hint="t('dialog.reorganize.mediaIdHint')"
|
|
||||||
persistent-hint
|
persistent-hint
|
||||||
prepend-inner-icon="mdi-identifier"
|
prepend-inner-icon="mdi-database-search"
|
||||||
@click:append-inner="mediaSelectorDialog = true"
|
|
||||||
/>
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="4">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-else
|
v-model="transferForm.media_id"
|
||||||
v-model="transferForm.doubanid"
|
|
||||||
:disabled="transferForm.type_name === ''"
|
:disabled="transferForm.type_name === ''"
|
||||||
:label="t('dialog.reorganize.doubanId')"
|
:label="mediaIdLabel"
|
||||||
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
:placeholder="t('dialog.reorganize.mediaIdPlaceholder')"
|
||||||
:rules="[numberValidator]"
|
:rules="[numberValidator]"
|
||||||
append-inner-icon="mdi-magnify"
|
append-inner-icon="mdi-magnify"
|
||||||
@@ -1437,7 +1472,7 @@ onUnmounted(() => {
|
|||||||
item-value="value"
|
item-value="value"
|
||||||
:item-props="episodeGroupItemProps"
|
:item-props="episodeGroupItemProps"
|
||||||
:loading="episodeGroupLoading"
|
:loading="episodeGroupLoading"
|
||||||
:disabled="!transferForm.tmdbid"
|
:disabled="!transferForm.media_id"
|
||||||
clearable
|
clearable
|
||||||
:label="t('dialog.reorganize.episodeGroup')"
|
:label="t('dialog.reorganize.episodeGroup')"
|
||||||
:placeholder="t('dialog.reorganize.episodeGroupPlaceholder')"
|
:placeholder="t('dialog.reorganize.episodeGroupPlaceholder')"
|
||||||
@@ -1744,18 +1779,10 @@ onUnmounted(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
<!-- 手动整理进度框 -->
|
<!-- 手动整理进度框 -->
|
||||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" :value="progressValue" />
|
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" :value="progressValue" />
|
||||||
<!-- TMDB ID搜索框 -->
|
<!-- 媒体数据源ID搜索框 -->
|
||||||
<VDialog v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
|
<VDialog v-model="mediaSelectorDialog" width="40rem" scrollable max-height="85vh">
|
||||||
<MediaIdSelector
|
<MediaIdSelector
|
||||||
v-if="mediaSource === 'themoviedb'"
|
v-model="transferForm.media_id"
|
||||||
v-model="transferForm.tmdbid"
|
|
||||||
@close="mediaSelectorDialog = false"
|
|
||||||
@select="handleMediaSelected"
|
|
||||||
:type="mediaSource"
|
|
||||||
/>
|
|
||||||
<MediaIdSelector
|
|
||||||
v-else
|
|
||||||
v-model="transferForm.doubanid"
|
|
||||||
@close="mediaSelectorDialog = false"
|
@close="mediaSelectorDialog = false"
|
||||||
@select="handleMediaSelected"
|
@select="handleMediaSelected"
|
||||||
:type="mediaSource"
|
:type="mediaSource"
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { MediaInfo } from '@/api/types'
|
import type { MediaDataSource, MediaInfo } from '@/api/types'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 定义输入变量
|
// 定义输入变量
|
||||||
const props = defineProps({
|
const props = defineProps<{
|
||||||
type: String, // 来源 themoviedb | douban
|
type?: MediaDataSource
|
||||||
})
|
}>()
|
||||||
|
|
||||||
interface TmdbItem {
|
interface MediaSelectorItem {
|
||||||
|
// 数据源原生ID
|
||||||
|
id: string
|
||||||
// 媒体标题
|
// 媒体标题
|
||||||
title: string
|
title: string
|
||||||
// 媒体简介,包含类型标签
|
// 媒体简介,包含类型标签
|
||||||
overview: string
|
overview: string
|
||||||
// TMDB ID
|
|
||||||
tmdbid: number
|
|
||||||
// 豆瓣 ID
|
|
||||||
doubanid: string
|
|
||||||
// 海报地址
|
// 海报地址
|
||||||
poster: string
|
poster: string
|
||||||
// 媒体类型
|
// 媒体类型
|
||||||
@@ -25,7 +26,7 @@ interface TmdbItem {
|
|||||||
// update:modelValue 事件
|
// update:modelValue 事件
|
||||||
const emit = defineEmits(['update:modelValue', 'select', 'close'])
|
const emit = defineEmits(['update:modelValue', 'select', 'close'])
|
||||||
|
|
||||||
const items = ref<TmdbItem[]>([])
|
const items = ref<MediaSelectorItem[]>([])
|
||||||
|
|
||||||
// 搜索词
|
// 搜索词
|
||||||
const keyword = ref('')
|
const keyword = ref('')
|
||||||
@@ -37,8 +38,8 @@ const loading = ref(false)
|
|||||||
const inputKeyword = ref<HTMLElement | null>(null)
|
const inputKeyword = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
// 选中条目并通知父组件同步额外媒体信息。
|
// 选中条目并通知父组件同步额外媒体信息。
|
||||||
function selectMedia(item: TmdbItem) {
|
function selectMedia(item: MediaSelectorItem) {
|
||||||
emit('update:modelValue', item.tmdbid || item.doubanid)
|
emit('update:modelValue', item.id)
|
||||||
emit('select', item)
|
emit('select', item)
|
||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
@@ -51,16 +52,18 @@ function getW500Image(url = '') {
|
|||||||
|
|
||||||
// 搜索词条
|
// 搜索词条
|
||||||
async function searchMedias() {
|
async function searchMedias() {
|
||||||
if (!keyword) return
|
const searchKeyword = keyword.value.trim()
|
||||||
|
if (!searchKeyword) return
|
||||||
|
|
||||||
// 调用API搜索词条
|
// 调用API搜索词条
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const result: MediaInfo[] = await api.get('media/search', {
|
const result: MediaInfo[] = await api.get('media/search', {
|
||||||
params: {
|
params: {
|
||||||
title: keyword.value,
|
title: searchKeyword,
|
||||||
page: 1,
|
page: 1,
|
||||||
count: 20,
|
count: 20,
|
||||||
|
source: props.type,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -69,19 +72,25 @@ async function searchMedias() {
|
|||||||
|
|
||||||
// 赋值
|
// 赋值
|
||||||
for (const item of result) {
|
for (const item of result) {
|
||||||
if (props.type && props.type !== item.source) continue
|
const mediaId =
|
||||||
|
item.media_id ||
|
||||||
|
item.tmdb_id?.toString() ||
|
||||||
|
item.douban_id ||
|
||||||
|
item.bangumi_id?.toString() ||
|
||||||
|
item.anilist_id?.toString()
|
||||||
|
if (!mediaId) continue
|
||||||
items.value.push({
|
items.value.push({
|
||||||
tmdbid: item.tmdb_id || 0,
|
id: mediaId,
|
||||||
doubanid: item.douban_id || '',
|
|
||||||
poster: getW500Image(item.poster_path),
|
poster: getW500Image(item.poster_path),
|
||||||
type: item.type,
|
type: item.type,
|
||||||
title: `${item.title}(${item.year})`,
|
title: item.year ? `${item.title}(${item.year})` : item.title || '',
|
||||||
overview: `<span class="text-primary">${item.type}</span> ${item.overview}`,
|
overview: `<span class="text-primary">${item.type}</span> ${item.overview}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
loading.value = false
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,9 +109,9 @@ onMounted(() => {
|
|||||||
<VTextField
|
<VTextField
|
||||||
ref="inputKeyword"
|
ref="inputKeyword"
|
||||||
v-model="keyword"
|
v-model="keyword"
|
||||||
label="输入名称搜索"
|
:label="t('dialog.reorganize.mediaSearchInput')"
|
||||||
single-line
|
single-line
|
||||||
placeholder="电影或电视剧名称"
|
:placeholder="t('dialog.reorganize.mediaSearchPlaceholder')"
|
||||||
variant="solo"
|
variant="solo"
|
||||||
prepend-inner-icon="mdi-magnify"
|
prepend-inner-icon="mdi-magnify"
|
||||||
flat
|
flat
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export function getMediaSubscribeId(media?: MediaInfo) {
|
|||||||
if (media?.tmdb_id) return `tmdb:${media.tmdb_id}`
|
if (media?.tmdb_id) return `tmdb:${media.tmdb_id}`
|
||||||
if (media?.douban_id) return `douban:${media.douban_id}`
|
if (media?.douban_id) return `douban:${media.douban_id}`
|
||||||
if (media?.bangumi_id) return `bangumi:${media.bangumi_id}`
|
if (media?.bangumi_id) return `bangumi:${media.bangumi_id}`
|
||||||
|
if (media?.anilist_id) return `anilist:${media.anilist_id}`
|
||||||
return `${media?.mediaid_prefix}:${media?.media_id}`
|
return `${media?.mediaid_prefix}:${media?.media_id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-12
@@ -1536,7 +1536,11 @@ export default {
|
|||||||
recognizing: 'Recognizing...',
|
recognizing: 'Recognizing...',
|
||||||
recognizeAgain: 'Recognize Again',
|
recognizeAgain: 'Recognize Again',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
|
titleHint: 'Enter a torrent name, release title, or file name',
|
||||||
subtitle: 'Subtitle',
|
subtitle: 'Subtitle',
|
||||||
|
subtitleHint: 'Optional torrent description, alias, or release details to improve recognition accuracy',
|
||||||
|
source: 'Recognition Source',
|
||||||
|
sourceHint: 'Uses the backend recognition setting by default; switch it for this test only',
|
||||||
customWords: 'Custom Words',
|
customWords: 'Custom Words',
|
||||||
customWordsPlaceholder: 'Enter one recognition rule per line; applied directly to this recognition test',
|
customWordsPlaceholder: 'Enter one recognition rule per line; applied directly to this recognition test',
|
||||||
customWordsHint:
|
customWordsHint:
|
||||||
@@ -1546,8 +1550,6 @@ export default {
|
|||||||
saveWordsNoChange: 'These words already exist, no need to save again',
|
saveWordsNoChange: 'These words already exist, no need to save again',
|
||||||
saveWordsFailed: 'Failed to save custom words',
|
saveWordsFailed: 'Failed to save custom words',
|
||||||
requestFailed: 'Recognition request failed',
|
requestFailed: 'Recognition request failed',
|
||||||
inputTitle: 'Test Input',
|
|
||||||
inputSubtitle: 'Enter a torrent or file name to inspect the recognition breakdown',
|
|
||||||
unrecognized: 'No media recognized',
|
unrecognized: 'No media recognized',
|
||||||
waitingResult: 'Waiting for recognition result',
|
waitingResult: 'Waiting for recognition result',
|
||||||
analysisTitle: 'Analysis Flow',
|
analysisTitle: 'Analysis Flow',
|
||||||
@@ -1589,14 +1591,15 @@ export default {
|
|||||||
testing: 'Testing...',
|
testing: 'Testing...',
|
||||||
testAgain: 'Test Again',
|
testAgain: 'Test Again',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
|
titleHint: 'Enter a release title to simulate a search or download result',
|
||||||
subtitle: 'Subtitle',
|
subtitle: 'Subtitle',
|
||||||
|
subtitleHint: 'Optional release description, tags, or site subtitle used for recognition and filtering',
|
||||||
ruleGroup: 'Rule Group',
|
ruleGroup: 'Rule Group',
|
||||||
|
ruleGroupHint: 'Select the filter rule group to validate',
|
||||||
ruleGroupPlaceholder: 'Please select',
|
ruleGroupPlaceholder: 'Please select',
|
||||||
priority: 'Priority: {value}',
|
priority: 'Priority: {value}',
|
||||||
noPriorityRule: 'No priority rule matched!',
|
noPriorityRule: 'No priority rule matched!',
|
||||||
requestFailed: 'Rule test request failed',
|
requestFailed: 'Rule test request failed',
|
||||||
inputTitle: 'Rule Test',
|
|
||||||
inputSubtitle: 'Select a rule group to inspect filter matching and priority',
|
|
||||||
waitingResult: 'Waiting for rule test result',
|
waitingResult: 'Waiting for rule test result',
|
||||||
matched: 'Filter rule matched',
|
matched: 'Filter rule matched',
|
||||||
priorityLabel: 'Priority',
|
priorityLabel: 'Priority',
|
||||||
@@ -1692,12 +1695,6 @@ export default {
|
|||||||
wallpaperHint: 'Choose the source of the login page background',
|
wallpaperHint: 'Choose the source of the login page background',
|
||||||
recognizeSource: 'Recognition Data Source',
|
recognizeSource: 'Recognition Data Source',
|
||||||
recognizeSourceHint: 'Set the default media info recognition data source',
|
recognizeSourceHint: 'Set the default media info recognition data source',
|
||||||
mediaServerSyncInterval: 'Media Server Sync Interval',
|
|
||||||
mediaServerSyncIntervalHint: 'Time interval for syncing media server data to local',
|
|
||||||
hours: 'hours',
|
|
||||||
required: 'Required field, please fill in',
|
|
||||||
numbersOnly: 'Only numbers are supported, please do not enter other characters',
|
|
||||||
minInterval: 'Interval cannot be less than 1 hour',
|
|
||||||
apiToken: 'API Token',
|
apiToken: 'API Token',
|
||||||
apiTokenHint: 'Set the token value used when external requests access MoviePilot API',
|
apiTokenHint: 'Set the token value used when external requests access MoviePilot API',
|
||||||
apiTokenMinChars: 'Cannot be less than 16 characters',
|
apiTokenMinChars: 'Cannot be less than 16 characters',
|
||||||
@@ -2127,6 +2124,8 @@ export default {
|
|||||||
userAgentHint: 'User-Agent of the browser with CookieCloud plugin',
|
userAgentHint: 'User-Agent of the browser with CookieCloud plugin',
|
||||||
browserEmulation: 'Browser Emulation',
|
browserEmulation: 'Browser Emulation',
|
||||||
browserEmulationHint: 'Choose how to emulate browser when accessing sites (CloakBrowser or FlareSolverr)',
|
browserEmulationHint: 'Choose how to emulate browser when accessing sites (CloakBrowser or FlareSolverr)',
|
||||||
|
ocrHost: 'OCR Server',
|
||||||
|
ocrHostHint: 'Used for site check-in, cookie updates, and other captcha recognition tasks',
|
||||||
flaresolverrUrl: 'FlareSolverr URL',
|
flaresolverrUrl: 'FlareSolverr URL',
|
||||||
flaresolverrUrlHint: 'Required when using FlareSolverr, e.g. http://127.0.0.1:8191',
|
flaresolverrUrlHint: 'Required when using FlareSolverr, e.g. http://127.0.0.1:8191',
|
||||||
siteDataRefresh: 'Site Data Refresh',
|
siteDataRefresh: 'Site Data Refresh',
|
||||||
@@ -2238,7 +2237,7 @@ export default {
|
|||||||
'Word to replace => Replacement\n' +
|
'Word to replace => Replacement\n' +
|
||||||
'Front word <> Back word >> Episode offset (EP)\n' +
|
'Front word <> Back word >> Episode offset (EP)\n' +
|
||||||
'Word to replace => Replacement && Front word <> Back word >> Episode offset (EP)\n' +
|
'Word to replace => Replacement && Front word <> Back word >> Episode offset (EP)\n' +
|
||||||
'Replacement format supports: {[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} to directly specify TMDBID/Douban ID, where g is the episode group ID and s/e are season and episode numbers (all optional)',
|
'Replacement format supports: {[tmdbid/doubanid/bangumiid/anilistid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} to directly specify a media data source ID, where g is the episode group ID and s/e are season and episode numbers (all optional)',
|
||||||
identifierSaveSuccess: 'Custom identifiers saved successfully',
|
identifierSaveSuccess: 'Custom identifiers saved successfully',
|
||||||
identifierSaveFailed: 'Failed to save custom identifiers!',
|
identifierSaveFailed: 'Failed to save custom identifiers!',
|
||||||
|
|
||||||
@@ -3037,10 +3036,16 @@ export default {
|
|||||||
targetPathPlaceholder: 'Choose Auto or enter a path',
|
targetPathPlaceholder: 'Choose Auto or enter a path',
|
||||||
mediaType: 'Type',
|
mediaType: 'Type',
|
||||||
mediaTypeHint: 'File media type',
|
mediaTypeHint: 'File media type',
|
||||||
|
mediaSource: 'Data Source',
|
||||||
|
mediaSourceHint: 'Uses the backend recognition setting by default; switch it for this organization and scrape',
|
||||||
tmdbId: 'TheMovieDb ID',
|
tmdbId: 'TheMovieDb ID',
|
||||||
doubanId: 'Douban ID',
|
doubanId: 'Douban ID',
|
||||||
|
bangumiId: 'Bangumi ID',
|
||||||
|
anilistId: 'AniList ID',
|
||||||
mediaIdHint: 'Query media ID by name, leave empty for auto recognition',
|
mediaIdHint: 'Query media ID by name, leave empty for auto recognition',
|
||||||
mediaIdPlaceholder: 'Leave empty for auto recognition',
|
mediaIdPlaceholder: 'Leave empty for auto recognition',
|
||||||
|
mediaSearchInput: 'Search Media',
|
||||||
|
mediaSearchPlaceholder: 'Enter a media title',
|
||||||
episodeGroup: 'Episode Group',
|
episodeGroup: 'Episode Group',
|
||||||
episodeGroupHint:
|
episodeGroupHint:
|
||||||
'After entering a TMDB ID, episode groups are queried automatically; group IDs can still be entered manually',
|
'After entering a TMDB ID, episode groups are queried automatically; group IDs can still be entered manually',
|
||||||
@@ -3175,7 +3180,7 @@ export default {
|
|||||||
customWords: 'Custom Recognition Words',
|
customWords: 'Custom Recognition Words',
|
||||||
customWordsHint: 'Recognition words only used for this subscription',
|
customWordsHint: 'Recognition words only used for this subscription',
|
||||||
customWordsPlaceholder:
|
customWordsPlaceholder:
|
||||||
'Block word\nReplaced word => Replacement word\nPrefix <> Suffix >> Episode offset (EP)\nReplaced word => Replacement word && Prefix <> Suffix >> Episode offset (EP)\nReplacement word supports format: {[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} to directly specify TMDBID/Douban ID recognition, where g is the episode group ID and s/e are season and episode numbers (all optional)',
|
'Block word\nReplaced word => Replacement word\nPrefix <> Suffix >> Episode offset (EP)\nReplaced word => Replacement word && Prefix <> Suffix >> Episode offset (EP)\nReplacement word supports format: {[tmdbid/doubanid/bangumiid/anilistid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} to directly specify a media data source ID, where g is the episode group ID and s/e are season and episode numbers (all optional)',
|
||||||
cancelSubscribe: 'Cancel Subscription',
|
cancelSubscribe: 'Cancel Subscription',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
cancelSubscribeConfirm: 'Are you sure you want to cancel the subscription?',
|
cancelSubscribeConfirm: 'Are you sure you want to cancel the subscription?',
|
||||||
@@ -3728,6 +3733,9 @@ export default {
|
|||||||
type: 'Type',
|
type: 'Type',
|
||||||
customTypeHint: 'Custom media server type, for plugin scenarios',
|
customTypeHint: 'Custom media server type, for plugin scenarios',
|
||||||
enableMediaServer: 'Enable Media Server',
|
enableMediaServer: 'Enable Media Server',
|
||||||
|
syncInterval: 'Automatic Sync Interval',
|
||||||
|
syncIntervalHint:
|
||||||
|
'Leave blank to use the legacy global default ({interval} hours); set to 0 to disable automatic sync for this server',
|
||||||
nameRequired: 'Required; cannot be duplicated',
|
nameRequired: 'Required; cannot be duplicated',
|
||||||
serverAlias: 'Media server alias',
|
serverAlias: 'Media server alias',
|
||||||
host: 'Host',
|
host: 'Host',
|
||||||
|
|||||||
+19
-12
@@ -1529,7 +1529,11 @@ export default {
|
|||||||
recognizing: '识别中...',
|
recognizing: '识别中...',
|
||||||
recognizeAgain: '重新识别',
|
recognizeAgain: '重新识别',
|
||||||
title: '标题',
|
title: '标题',
|
||||||
|
titleHint: '输入种子名、发布标题或文件名',
|
||||||
subtitle: '副标题',
|
subtitle: '副标题',
|
||||||
|
subtitleHint: '可选,补充种子描述、别名或发布信息以提高识别准确度',
|
||||||
|
source: '识别数据源',
|
||||||
|
sourceHint: '默认使用后台识别设置,可为本次测试单独切换',
|
||||||
customWords: '识别词',
|
customWords: '识别词',
|
||||||
customWordsPlaceholder: '每行输入一组识别规则,可直接用于本次识别测试',
|
customWordsPlaceholder: '每行输入一组识别规则,可直接用于本次识别测试',
|
||||||
customWordsHint: '格式与"识别词管理"一致:屏蔽词 / 被替换词 => 替换词 / 前定位词 <> 后定位词 >> 集偏移量',
|
customWordsHint: '格式与"识别词管理"一致:屏蔽词 / 被替换词 => 替换词 / 前定位词 <> 后定位词 >> 集偏移量',
|
||||||
@@ -1538,8 +1542,6 @@ export default {
|
|||||||
saveWordsNoChange: '识别词已存在,无需重复保存',
|
saveWordsNoChange: '识别词已存在,无需重复保存',
|
||||||
saveWordsFailed: '识别词保存失败',
|
saveWordsFailed: '识别词保存失败',
|
||||||
requestFailed: '识别请求失败',
|
requestFailed: '识别请求失败',
|
||||||
inputTitle: '测试输入',
|
|
||||||
inputSubtitle: '输入种子名或文件名,查看媒体识别拆解结果',
|
|
||||||
unrecognized: '未识别到媒体',
|
unrecognized: '未识别到媒体',
|
||||||
waitingResult: '等待识别结果',
|
waitingResult: '等待识别结果',
|
||||||
analysisTitle: '解析链路',
|
analysisTitle: '解析链路',
|
||||||
@@ -1581,14 +1583,15 @@ export default {
|
|||||||
testing: '正在测试...',
|
testing: '正在测试...',
|
||||||
testAgain: '重新测试',
|
testAgain: '重新测试',
|
||||||
title: '标题',
|
title: '标题',
|
||||||
|
titleHint: '输入用于模拟搜索或下载结果的发布标题',
|
||||||
subtitle: '副标题',
|
subtitle: '副标题',
|
||||||
|
subtitleHint: '可选,补充发布描述、标签或站点副标题以参与识别和过滤',
|
||||||
ruleGroup: '规则组',
|
ruleGroup: '规则组',
|
||||||
|
ruleGroupHint: '选择要验证的过滤规则组',
|
||||||
ruleGroupPlaceholder: '请选择',
|
ruleGroupPlaceholder: '请选择',
|
||||||
priority: '优先级:{value}',
|
priority: '优先级:{value}',
|
||||||
noPriorityRule: '未命中任何优先级规则!',
|
noPriorityRule: '未命中任何优先级规则!',
|
||||||
requestFailed: '规则测试请求失败',
|
requestFailed: '规则测试请求失败',
|
||||||
inputTitle: '规则测试',
|
|
||||||
inputSubtitle: '选择规则组后,查看过滤命中和优先级结果',
|
|
||||||
waitingResult: '等待规则测试结果',
|
waitingResult: '等待规则测试结果',
|
||||||
matched: '命中过滤规则',
|
matched: '命中过滤规则',
|
||||||
priorityLabel: '优先级',
|
priorityLabel: '优先级',
|
||||||
@@ -1684,12 +1687,6 @@ export default {
|
|||||||
wallpaperHint: '选择登陆页面背景来源',
|
wallpaperHint: '选择登陆页面背景来源',
|
||||||
recognizeSource: '识别数据源',
|
recognizeSource: '识别数据源',
|
||||||
recognizeSourceHint: '设置默认媒体信息识别数据源',
|
recognizeSourceHint: '设置默认媒体信息识别数据源',
|
||||||
mediaServerSyncInterval: '媒体服务器同步间隔',
|
|
||||||
mediaServerSyncIntervalHint: '定时同步媒体服务器数据到本地的时间间隔',
|
|
||||||
hours: '小时',
|
|
||||||
required: '必选项,请勿留空',
|
|
||||||
numbersOnly: '仅支持输入数字,请勿输入其他字符',
|
|
||||||
minInterval: '间隔不能小于1个小时',
|
|
||||||
apiToken: 'API令牌',
|
apiToken: 'API令牌',
|
||||||
apiTokenHint: '设置外部请求MoviePilot API时使用的token值',
|
apiTokenHint: '设置外部请求MoviePilot API时使用的token值',
|
||||||
apiTokenMinChars: '不能小于16位字符',
|
apiTokenMinChars: '不能小于16位字符',
|
||||||
@@ -2093,6 +2090,8 @@ export default {
|
|||||||
siteOptions: '站点选项',
|
siteOptions: '站点选项',
|
||||||
browserEmulation: '浏览器仿真',
|
browserEmulation: '浏览器仿真',
|
||||||
browserEmulationHint: '站点访问仿真方式,支持 CloakBrowser 或 FlareSolverr',
|
browserEmulationHint: '站点访问仿真方式,支持 CloakBrowser 或 FlareSolverr',
|
||||||
|
ocrHost: '验证码识别服务器',
|
||||||
|
ocrHostHint: '用于站点签到、更新站点Cookie等识别验证码',
|
||||||
flaresolverrUrl: 'FlareSolverr 服务地址',
|
flaresolverrUrl: 'FlareSolverr 服务地址',
|
||||||
flaresolverrUrlHint: '当仿真方式为 FlareSolverr 时生效,例如:http://127.0.0.1:8191',
|
flaresolverrUrlHint: '当仿真方式为 FlareSolverr 时生效,例如:http://127.0.0.1:8191',
|
||||||
siteDataRefreshInterval: '站点数据刷新间隔',
|
siteDataRefreshInterval: '站点数据刷新间隔',
|
||||||
@@ -2200,7 +2199,7 @@ export default {
|
|||||||
'被替换词 => 替换词\n' +
|
'被替换词 => 替换词\n' +
|
||||||
'前定位词 <> 后定位词 >> 集偏移量(EP)\n' +
|
'前定位词 <> 后定位词 >> 集偏移量(EP)\n' +
|
||||||
'被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n' +
|
'被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n' +
|
||||||
'其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中g为剧集组编号,s、e为季数和集数(均可选)',
|
'其中替换词支持格式:{[tmdbid/doubanid/bangumiid/anilistid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定媒体数据源ID识别,其中g为剧集组编号,s、e为季数和集数(均可选)',
|
||||||
identifierSaveSuccess: '自定义识别词保存成功',
|
identifierSaveSuccess: '自定义识别词保存成功',
|
||||||
identifierSaveFailed: '自定义识别词保存失败!',
|
identifierSaveFailed: '自定义识别词保存失败!',
|
||||||
|
|
||||||
@@ -2987,10 +2986,16 @@ export default {
|
|||||||
targetPathPlaceholder: '选择自动或输入路径',
|
targetPathPlaceholder: '选择自动或输入路径',
|
||||||
mediaType: '类型',
|
mediaType: '类型',
|
||||||
mediaTypeHint: '文件的媒体类型',
|
mediaTypeHint: '文件的媒体类型',
|
||||||
|
mediaSource: '数据源',
|
||||||
|
mediaSourceHint: '默认使用后台识别设置,可为本次整理与刮削单独切换',
|
||||||
tmdbId: 'TheMovieDb编号',
|
tmdbId: 'TheMovieDb编号',
|
||||||
doubanId: '豆瓣编号',
|
doubanId: '豆瓣编号',
|
||||||
|
bangumiId: 'Bangumi编号',
|
||||||
|
anilistId: 'AniList编号',
|
||||||
mediaIdHint: '按名称查询媒体编号,留空自动识别',
|
mediaIdHint: '按名称查询媒体编号,留空自动识别',
|
||||||
mediaIdPlaceholder: '留空自动识别',
|
mediaIdPlaceholder: '留空自动识别',
|
||||||
|
mediaSearchInput: '搜索媒体',
|
||||||
|
mediaSearchPlaceholder: '输入媒体名称',
|
||||||
episodeGroup: '剧集组',
|
episodeGroup: '剧集组',
|
||||||
episodeGroupHint: '输入 TMDB 编号后自动查询剧集组,也可手动填写剧集组编号',
|
episodeGroupHint: '输入 TMDB 编号后自动查询剧集组,也可手动填写剧集组编号',
|
||||||
episodeGroupPlaceholder: '先输入 TMDB 编号',
|
episodeGroupPlaceholder: '先输入 TMDB 编号',
|
||||||
@@ -3123,7 +3128,7 @@ export default {
|
|||||||
customWords: '自定义识别词',
|
customWords: '自定义识别词',
|
||||||
customWordsHint: '只对该订阅使用的识别词',
|
customWordsHint: '只对该订阅使用的识别词',
|
||||||
customWordsPlaceholder:
|
customWordsPlaceholder:
|
||||||
'屏蔽词\n被替换词 => 替换词\n前定位词 <> 后定位词 >> 集偏移量(EP)\n被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中g为剧集组编号,s、e为季数和集数(均可选)',
|
'屏蔽词\n被替换词 => 替换词\n前定位词 <> 后定位词 >> 集偏移量(EP)\n被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n其中替换词支持格式:{[tmdbid/doubanid/bangumiid/anilistid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定媒体数据源ID识别,其中g为剧集组编号,s、e为季数和集数(均可选)',
|
||||||
cancelSubscribe: '取消订阅',
|
cancelSubscribe: '取消订阅',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
cancelSubscribeConfirm: '是否确认取消订阅?',
|
cancelSubscribeConfirm: '是否确认取消订阅?',
|
||||||
@@ -3669,6 +3674,8 @@ export default {
|
|||||||
type: '类型',
|
type: '类型',
|
||||||
customTypeHint: '自定义媒体服务器类型,用于插件等场景',
|
customTypeHint: '自定义媒体服务器类型,用于插件等场景',
|
||||||
enableMediaServer: '启用媒体服务器',
|
enableMediaServer: '启用媒体服务器',
|
||||||
|
syncInterval: '自动同步间隔',
|
||||||
|
syncIntervalHint: '留空时使用旧版全局默认值({interval} 小时),设置为 0 时关闭此服务器的自动同步',
|
||||||
nameRequired: '必填,不可重名',
|
nameRequired: '必填,不可重名',
|
||||||
serverAlias: '媒体服务器的别名',
|
serverAlias: '媒体服务器的别名',
|
||||||
host: '地址',
|
host: '地址',
|
||||||
|
|||||||
+19
-12
@@ -1528,7 +1528,11 @@ export default {
|
|||||||
recognizing: '識別中...',
|
recognizing: '識別中...',
|
||||||
recognizeAgain: '重新識別',
|
recognizeAgain: '重新識別',
|
||||||
title: '標題',
|
title: '標題',
|
||||||
|
titleHint: '輸入種子名、發佈標題或文件名',
|
||||||
subtitle: '副標題',
|
subtitle: '副標題',
|
||||||
|
subtitleHint: '可選,補充種子描述、別名或發佈信息以提高識別準確度',
|
||||||
|
source: '識別數據源',
|
||||||
|
sourceHint: '預設使用後台識別設置,可為本次測試單獨切換',
|
||||||
customWords: '識別詞',
|
customWords: '識別詞',
|
||||||
customWordsPlaceholder: '每行輸入一組識別規則,可直接用於本次識別測試',
|
customWordsPlaceholder: '每行輸入一組識別規則,可直接用於本次識別測試',
|
||||||
customWordsHint: '格式與「識別詞管理」一致:屏蔽詞 / 被替換詞 => 替換詞 / 前定位詞 <> 後定位詞 >> 集偏移量',
|
customWordsHint: '格式與「識別詞管理」一致:屏蔽詞 / 被替換詞 => 替換詞 / 前定位詞 <> 後定位詞 >> 集偏移量',
|
||||||
@@ -1537,8 +1541,6 @@ export default {
|
|||||||
saveWordsNoChange: '識別詞已存在,無需重複儲存',
|
saveWordsNoChange: '識別詞已存在,無需重複儲存',
|
||||||
saveWordsFailed: '識別詞儲存失敗',
|
saveWordsFailed: '識別詞儲存失敗',
|
||||||
requestFailed: '識別請求失敗',
|
requestFailed: '識別請求失敗',
|
||||||
inputTitle: '測試輸入',
|
|
||||||
inputSubtitle: '輸入種子名或檔案名,查看媒體識別拆解結果',
|
|
||||||
unrecognized: '未識別到媒體',
|
unrecognized: '未識別到媒體',
|
||||||
waitingResult: '等待識別結果',
|
waitingResult: '等待識別結果',
|
||||||
analysisTitle: '解析鏈路',
|
analysisTitle: '解析鏈路',
|
||||||
@@ -1580,14 +1582,15 @@ export default {
|
|||||||
testing: '正在測試...',
|
testing: '正在測試...',
|
||||||
testAgain: '重新測試',
|
testAgain: '重新測試',
|
||||||
title: '標題',
|
title: '標題',
|
||||||
|
titleHint: '輸入用於模擬搜索或下載結果的發佈標題',
|
||||||
subtitle: '副標題',
|
subtitle: '副標題',
|
||||||
|
subtitleHint: '可選,補充發佈描述、標籤或站點副標題以參與識別和過濾',
|
||||||
ruleGroup: '規則組',
|
ruleGroup: '規則組',
|
||||||
|
ruleGroupHint: '選擇要驗證的過濾規則組',
|
||||||
ruleGroupPlaceholder: '請選擇',
|
ruleGroupPlaceholder: '請選擇',
|
||||||
priority: '優先級:{value}',
|
priority: '優先級:{value}',
|
||||||
noPriorityRule: '未命中任何優先級規則!',
|
noPriorityRule: '未命中任何優先級規則!',
|
||||||
requestFailed: '規則測試請求失敗',
|
requestFailed: '規則測試請求失敗',
|
||||||
inputTitle: '規則測試',
|
|
||||||
inputSubtitle: '選擇規則組後,查看過濾命中和優先級結果',
|
|
||||||
waitingResult: '等待規則測試結果',
|
waitingResult: '等待規則測試結果',
|
||||||
matched: '命中過濾規則',
|
matched: '命中過濾規則',
|
||||||
priorityLabel: '優先級',
|
priorityLabel: '優先級',
|
||||||
@@ -1683,12 +1686,6 @@ export default {
|
|||||||
wallpaperHint: '選擇登陸頁面背景來源',
|
wallpaperHint: '選擇登陸頁面背景來源',
|
||||||
recognizeSource: '識別數據源',
|
recognizeSource: '識別數據源',
|
||||||
recognizeSourceHint: '設置默認媒體信息識別數據源',
|
recognizeSourceHint: '設置默認媒體信息識別數據源',
|
||||||
mediaServerSyncInterval: '媒體服務器同步間隔',
|
|
||||||
mediaServerSyncIntervalHint: '定時同步媒體服務器數據到本地的時間間隔',
|
|
||||||
hours: '小時',
|
|
||||||
required: '必選項,請勿留空',
|
|
||||||
numbersOnly: '僅支持輸入數字,請勿輸入其他字符',
|
|
||||||
minInterval: '間隔不能小於1個小時',
|
|
||||||
apiToken: 'API令牌',
|
apiToken: 'API令牌',
|
||||||
apiTokenHint: '設置外部請求MoviePilot API時使用的token值',
|
apiTokenHint: '設置外部請求MoviePilot API時使用的token值',
|
||||||
apiTokenMinChars: '不能小於16位字符',
|
apiTokenMinChars: '不能小於16位字符',
|
||||||
@@ -2092,6 +2089,8 @@ export default {
|
|||||||
siteOptions: '站點選項',
|
siteOptions: '站點選項',
|
||||||
browserEmulation: '瀏覽器仿真',
|
browserEmulation: '瀏覽器仿真',
|
||||||
browserEmulationHint: '站點訪問仿真方式,支援 CloakBrowser 或 FlareSolverr',
|
browserEmulationHint: '站點訪問仿真方式,支援 CloakBrowser 或 FlareSolverr',
|
||||||
|
ocrHost: '驗證碼識別服務器',
|
||||||
|
ocrHostHint: '用於站點簽到、更新站點Cookie等識別驗證碼',
|
||||||
flaresolverrUrl: 'FlareSolverr 服務地址',
|
flaresolverrUrl: 'FlareSolverr 服務地址',
|
||||||
flaresolverrUrlHint: '當仿真方式為 FlareSolverr 時生效,例如:http://127.0.0.1:8191',
|
flaresolverrUrlHint: '當仿真方式為 FlareSolverr 時生效,例如:http://127.0.0.1:8191',
|
||||||
siteDataRefreshInterval: '站點數據刷新間隔',
|
siteDataRefreshInterval: '站點數據刷新間隔',
|
||||||
@@ -2199,7 +2198,7 @@ export default {
|
|||||||
'被替換詞 => 替換詞\n' +
|
'被替換詞 => 替換詞\n' +
|
||||||
'前定位詞 <> 後定位詞 >> 集偏移量(EP)\n' +
|
'前定位詞 <> 後定位詞 >> 集偏移量(EP)\n' +
|
||||||
'被替換詞 => 替換詞 && 前定位詞 <> 後定位詞 >> 集偏移量(EP)\n' +
|
'被替換詞 => 替換詞 && 前定位詞 <> 後定位詞 >> 集偏移量(EP)\n' +
|
||||||
'其中替換詞支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID識別,其中g為劇集組編號,s、e為季數和集數(均可選)',
|
'其中替換詞支持格式:{[tmdbid/doubanid/bangumiid/anilistid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定媒體數據源ID識別,其中g為劇集組編號,s、e為季數和集數(均可選)',
|
||||||
identifierSaveSuccess: '自定義識別詞保存成功',
|
identifierSaveSuccess: '自定義識別詞保存成功',
|
||||||
identifierSaveFailed: '自定義識別詞保存失敗!',
|
identifierSaveFailed: '自定義識別詞保存失敗!',
|
||||||
|
|
||||||
@@ -2986,10 +2985,16 @@ export default {
|
|||||||
targetPathPlaceholder: '選擇自動或輸入路徑',
|
targetPathPlaceholder: '選擇自動或輸入路徑',
|
||||||
mediaType: '類型',
|
mediaType: '類型',
|
||||||
mediaTypeHint: '文件的媒體類型',
|
mediaTypeHint: '文件的媒體類型',
|
||||||
|
mediaSource: '數據源',
|
||||||
|
mediaSourceHint: '預設使用後台識別設置,可為本次整理與刮削單獨切換',
|
||||||
tmdbId: 'TheMovieDb編號',
|
tmdbId: 'TheMovieDb編號',
|
||||||
doubanId: '豆瓣編號',
|
doubanId: '豆瓣編號',
|
||||||
|
bangumiId: 'Bangumi編號',
|
||||||
|
anilistId: 'AniList編號',
|
||||||
mediaIdHint: '按名稱查詢媒體編號,留空自動識別',
|
mediaIdHint: '按名稱查詢媒體編號,留空自動識別',
|
||||||
mediaIdPlaceholder: '留空自動識別',
|
mediaIdPlaceholder: '留空自動識別',
|
||||||
|
mediaSearchInput: '搜索媒體',
|
||||||
|
mediaSearchPlaceholder: '輸入媒體名稱',
|
||||||
episodeGroup: '劇集組',
|
episodeGroup: '劇集組',
|
||||||
episodeGroupHint: '輸入 TMDB 編號後自動查詢劇集組,也可手動填寫劇集組編號',
|
episodeGroupHint: '輸入 TMDB 編號後自動查詢劇集組,也可手動填寫劇集組編號',
|
||||||
episodeGroupPlaceholder: '先輸入 TMDB 編號',
|
episodeGroupPlaceholder: '先輸入 TMDB 編號',
|
||||||
@@ -3122,7 +3127,7 @@ export default {
|
|||||||
customWords: '自定義識別詞',
|
customWords: '自定義識別詞',
|
||||||
customWordsHint: '只對該訂閱使用的識別詞',
|
customWordsHint: '只對該訂閱使用的識別詞',
|
||||||
customWordsPlaceholder:
|
customWordsPlaceholder:
|
||||||
'屏蔽詞\n被替換詞 => 替換詞\n前定位詞 <> 後定位詞 >> 集偏移量(EP)\n被替換詞 => 替換詞 && 前定位詞 <> 後定位詞 >> 集偏移量(EP)\n其中替換詞支援格式:{[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID識別,其中g為劇集組編號,s、e為季數和集數(均可選)',
|
'屏蔽詞\n被替換詞 => 替換詞\n前定位詞 <> 後定位詞 >> 集偏移量(EP)\n被替換詞 => 替換詞 && 前定位詞 <> 後定位詞 >> 集偏移量(EP)\n其中替換詞支援格式:{[tmdbid/doubanid/bangumiid/anilistid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定媒體數據源ID識別,其中g為劇集組編號,s、e為季數和集數(均可選)',
|
||||||
cancelSubscribe: '取消訂閱',
|
cancelSubscribe: '取消訂閱',
|
||||||
save: '儲存',
|
save: '儲存',
|
||||||
cancelSubscribeConfirm: '是否確認取消訂閱?',
|
cancelSubscribeConfirm: '是否確認取消訂閱?',
|
||||||
@@ -3666,6 +3671,8 @@ export default {
|
|||||||
type: '類型',
|
type: '類型',
|
||||||
customTypeHint: '自定義媒體伺服器類型,用於插件等場景',
|
customTypeHint: '自定義媒體伺服器類型,用於插件等場景',
|
||||||
enableMediaServer: '啟用媒體伺服器',
|
enableMediaServer: '啟用媒體伺服器',
|
||||||
|
syncInterval: '自動同步間隔',
|
||||||
|
syncIntervalHint: '留空時使用舊版全局預設值({interval} 小時),設置為 0 時關閉此服務器的自動同步',
|
||||||
nameRequired: '必填;不可與其他名稱重名',
|
nameRequired: '必填;不可與其他名稱重名',
|
||||||
serverAlias: '媒體伺服器的別名',
|
serverAlias: '媒體伺服器的別名',
|
||||||
host: '地址',
|
host: '地址',
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const $toast = useToast()
|
|||||||
const sourceItems = [
|
const sourceItems = [
|
||||||
{ 'title': 'TheMovieDb', 'value': 'themoviedb' },
|
{ 'title': 'TheMovieDb', 'value': 'themoviedb' },
|
||||||
{ 'title': '豆瓣', 'value': 'douban' },
|
{ 'title': '豆瓣', 'value': 'douban' },
|
||||||
|
{ 'title': 'Bangumi', 'value': 'bangumi' },
|
||||||
|
{ 'title': 'AniList', 'value': 'anilist' },
|
||||||
]
|
]
|
||||||
|
|
||||||
// 存储选项(排除已添加的)
|
// 存储选项(排除已添加的)
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ const mediaSourcesDict = [
|
|||||||
title: 'Bangumi',
|
title: 'Bangumi',
|
||||||
value: 'bangumi',
|
value: 'bangumi',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'AniList',
|
||||||
|
value: 'anilist',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// 当前选中的媒体信息数据源
|
// 当前选中的媒体信息数据源
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const siteSetting = ref<any>({
|
|||||||
SITE_MESSAGE: false,
|
SITE_MESSAGE: false,
|
||||||
SEARCH_RESOURCE_PAGES: 1,
|
SEARCH_RESOURCE_PAGES: 1,
|
||||||
BROWSER_EMULATION: 'cloakbrowser',
|
BROWSER_EMULATION: 'cloakbrowser',
|
||||||
|
OCR_HOST: '',
|
||||||
FLARESOLVERR_URL: '',
|
FLARESOLVERR_URL: '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -276,6 +277,16 @@ useSilentSettingRefresh(loadSiteSettings, {
|
|||||||
prepend-inner-icon="mdi-web"
|
prepend-inner-icon="mdi-web"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model="siteSetting.Site.OCR_HOST"
|
||||||
|
:label="t('setting.site.ocrHost')"
|
||||||
|
placeholder="https://movie-pilot.org"
|
||||||
|
:hint="t('setting.site.ocrHostHint')"
|
||||||
|
persistent-hint
|
||||||
|
prepend-inner-icon="mdi-text-recognition"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12" md="6" v-if="siteSetting.Site.BROWSER_EMULATION == 'flaresolverr'">
|
<VCol cols="12" md="6" v-if="siteSetting.Site.BROWSER_EMULATION == 'flaresolverr'">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="siteSetting.Site.FLARESOLVERR_URL"
|
v-model="siteSetting.Site.FLARESOLVERR_URL"
|
||||||
|
|||||||
@@ -43,10 +43,7 @@ const SystemSettings = ref<any>({
|
|||||||
APP_DOMAIN: null,
|
APP_DOMAIN: null,
|
||||||
API_TOKEN: null,
|
API_TOKEN: null,
|
||||||
WALLPAPER: 'tmdb',
|
WALLPAPER: 'tmdb',
|
||||||
MEDIASERVER_SYNC_INTERVAL: null,
|
|
||||||
RECOGNIZE_SOURCE: 'themoviedb',
|
|
||||||
GITHUB_TOKEN: null,
|
GITHUB_TOKEN: null,
|
||||||
OCR_HOST: null,
|
|
||||||
CUSTOMIZE_WALLPAPER_API_URL: null,
|
CUSTOMIZE_WALLPAPER_API_URL: null,
|
||||||
AI_AGENT_ENABLE: false,
|
AI_AGENT_ENABLE: false,
|
||||||
AI_AGENT_GLOBAL: false,
|
AI_AGENT_GLOBAL: false,
|
||||||
@@ -197,6 +194,9 @@ const isRequest = ref(true)
|
|||||||
// 选中的媒体服务器
|
// 选中的媒体服务器
|
||||||
const mediaServers = ref<MediaServerConf[]>([])
|
const mediaServers = ref<MediaServerConf[]>([])
|
||||||
|
|
||||||
|
// 旧版全局媒体服务器同步间隔,仅用于未单独设置时的默认值提示
|
||||||
|
const legacyMediaServerSyncInterval = ref<number | null>(null)
|
||||||
|
|
||||||
// 下载器
|
// 下载器
|
||||||
const downloaders = ref<DownloaderConf[]>([])
|
const downloaders = ref<DownloaderConf[]>([])
|
||||||
|
|
||||||
@@ -691,6 +691,8 @@ async function loadSystemSettings() {
|
|||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('system/env')
|
const result: { [key: string]: any } = await api.get('system/env')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
const defaultSyncInterval = Number(result.data.MEDIASERVER_SYNC_INTERVAL ?? Number.NaN)
|
||||||
|
legacyMediaServerSyncInterval.value = Number.isFinite(defaultSyncInterval) ? defaultSyncInterval : null
|
||||||
// 将API返回的值赋值给SystemSettings
|
// 将API返回的值赋值给SystemSettings
|
||||||
for (const sectionKey of Object.keys(SystemSettings.value) as Array<keyof typeof SystemSettings.value>) {
|
for (const sectionKey of Object.keys(SystemSettings.value) as Array<keyof typeof SystemSettings.value>) {
|
||||||
Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => {
|
Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => {
|
||||||
@@ -1100,36 +1102,6 @@ watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
|
|||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
|
||||||
<VSelect
|
|
||||||
v-model="SystemSettings.Basic.RECOGNIZE_SOURCE"
|
|
||||||
:label="t('setting.system.recognizeSource')"
|
|
||||||
:hint="t('setting.system.recognizeSourceHint')"
|
|
||||||
persistent-hint
|
|
||||||
:items="[
|
|
||||||
{ title: 'TheMovieDb', value: 'themoviedb' },
|
|
||||||
{ title: '豆瓣', value: 'douban' },
|
|
||||||
]"
|
|
||||||
prepend-inner-icon="mdi-database"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol cols="12" md="6">
|
|
||||||
<VTextField
|
|
||||||
v-model="SystemSettings.Basic.MEDIASERVER_SYNC_INTERVAL"
|
|
||||||
:label="t('setting.system.mediaServerSyncInterval')"
|
|
||||||
:hint="t('setting.system.mediaServerSyncIntervalHint')"
|
|
||||||
persistent-hint
|
|
||||||
:suffix="t('setting.system.hours')"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
:rules="[
|
|
||||||
(v: any) => !!v || t('setting.system.required'),
|
|
||||||
(v: any) => !isNaN(v) || t('setting.system.numbersOnly'),
|
|
||||||
(v: any) => v >= 1 || t('setting.system.minInterval'),
|
|
||||||
]"
|
|
||||||
prepend-inner-icon="mdi-sync"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="SystemSettings.Basic.API_TOKEN"
|
v-model="SystemSettings.Basic.API_TOKEN"
|
||||||
@@ -1159,16 +1131,6 @@ watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
|
|||||||
>
|
>
|
||||||
</VTextField>
|
</VTextField>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
|
||||||
<VTextField
|
|
||||||
v-model="SystemSettings.Basic.OCR_HOST"
|
|
||||||
:label="t('setting.system.ocrHost')"
|
|
||||||
placeholder="https://movie-pilot.org"
|
|
||||||
:hint="t('setting.system.ocrHostHint')"
|
|
||||||
persistent-hint
|
|
||||||
prepend-inner-icon="mdi-text-recognition"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
</VRow>
|
||||||
<VCard
|
<VCard
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -1817,6 +1779,7 @@ watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
|
|||||||
<MediaServerCard
|
<MediaServerCard
|
||||||
:mediaserver="element"
|
:mediaserver="element"
|
||||||
:mediaservers="mediaServers"
|
:mediaservers="mediaServers"
|
||||||
|
:default-sync-interval="legacyMediaServerSyncInterval ?? undefined"
|
||||||
@close="removeMediaServer(element)"
|
@close="removeMediaServer(element)"
|
||||||
@change="onMediaServerChange"
|
@change="onMediaServerChange"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { computed, reactive, ref } from 'vue'
|
|||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import { requiredValidator } from '@/@validators'
|
import { requiredValidator } from '@/@validators'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { Context } from '@/api/types'
|
import type { Context, MediaDataSource, MediaInfo } from '@/api/types'
|
||||||
import { getMediaSubscribeId } from '@/composables/useMediaSubscribe'
|
import { getMediaSubscribeId } from '@/composables/useMediaSubscribe'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
import { useGlobalSettingsStore } from '@/stores'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
interface PipelineStep {
|
interface PipelineStep {
|
||||||
@@ -16,6 +17,20 @@ interface PipelineStep {
|
|||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const globalSettingsStore = useGlobalSettingsStore()
|
||||||
|
|
||||||
|
const mediaSourceItems: { title: string; value: MediaDataSource }[] = [
|
||||||
|
{ title: 'TheMovieDb', value: 'themoviedb' },
|
||||||
|
{ title: '豆瓣', value: 'douban' },
|
||||||
|
{ title: 'Bangumi', value: 'bangumi' },
|
||||||
|
{ title: 'AniList', value: 'anilist' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 获取后台默认识别数据源,未知值兼容回退到TheMovieDb。
|
||||||
|
function getDefaultMediaSource(): MediaDataSource {
|
||||||
|
const configuredSource = globalSettingsStore.globalSettings.RECOGNIZE_SOURCE as MediaDataSource
|
||||||
|
return mediaSourceItems.some(item => item.value === configuredSource) ? configuredSource : 'themoviedb'
|
||||||
|
}
|
||||||
|
|
||||||
// 提示
|
// 提示
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
@@ -28,6 +43,7 @@ const nameTestForm = reactive({
|
|||||||
title: '',
|
title: '',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
customWords: '',
|
customWords: '',
|
||||||
|
source: getDefaultMediaSource(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// 识别按钮状态
|
// 识别按钮状态
|
||||||
@@ -67,9 +83,25 @@ const resourceChips = computed(() => {
|
|||||||
// 是否已匹配到具体媒体,决定是否展示查看详情入口
|
// 是否已匹配到具体媒体,决定是否展示查看详情入口
|
||||||
const canViewMediaDetail = computed(() =>
|
const canViewMediaDetail = computed(() =>
|
||||||
Boolean(
|
Boolean(
|
||||||
mediaInfo.value?.tmdb_id || mediaInfo.value?.douban_id || mediaInfo.value?.bangumi_id || mediaInfo.value?.media_id,
|
mediaInfo.value?.tmdb_id ||
|
||||||
|
mediaInfo.value?.douban_id ||
|
||||||
|
mediaInfo.value?.bangumi_id ||
|
||||||
|
mediaInfo.value?.anilist_id ||
|
||||||
|
mediaInfo.value?.media_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** 生成识别结果中的数据源原生ID摘要,并兼容旧接口字段。 */
|
||||||
|
function getMediaIdentityLabel(media?: MediaInfo) {
|
||||||
|
if (!media) return t('nameTest.unrecognized')
|
||||||
|
if (media.media_id) return `${media.source || media.mediaid_prefix} ${media.media_id}`
|
||||||
|
if (media.tmdb_id) return `TMDB ${media.tmdb_id}`
|
||||||
|
if (media.douban_id) return `Douban ${media.douban_id}`
|
||||||
|
if (media.bangumi_id) return `Bangumi ${media.bangumi_id}`
|
||||||
|
if (media.anilist_id) return `AniList ${media.anilist_id}`
|
||||||
|
return media.title || t('nameTest.unrecognized')
|
||||||
|
}
|
||||||
|
|
||||||
const pipelineSteps = computed<PipelineStep[]>(() => [
|
const pipelineSteps = computed<PipelineStep[]>(() => [
|
||||||
{
|
{
|
||||||
icon: 'mdi-file-document-outline',
|
icon: 'mdi-file-document-outline',
|
||||||
@@ -87,11 +119,7 @@ const pipelineSteps = computed<PipelineStep[]>(() => [
|
|||||||
{
|
{
|
||||||
icon: 'mdi-movie-search-outline',
|
icon: 'mdi-movie-search-outline',
|
||||||
title: t('nameTest.steps.media.title'),
|
title: t('nameTest.steps.media.title'),
|
||||||
value: mediaInfo.value?.tmdb_id
|
value: getMediaIdentityLabel(mediaInfo.value),
|
||||||
? `TMDB ${mediaInfo.value.tmdb_id}`
|
|
||||||
: mediaInfo.value?.douban_id
|
|
||||||
? `Douban ${mediaInfo.value.douban_id}`
|
|
||||||
: mediaInfo.value?.title || t('nameTest.unrecognized'),
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -130,6 +158,7 @@ async function nameTest() {
|
|||||||
title: nameTestForm.title,
|
title: nameTestForm.title,
|
||||||
subtitle: nameTestForm.subtitle,
|
subtitle: nameTestForm.subtitle,
|
||||||
custom_words: nameTestForm.customWords || undefined,
|
custom_words: nameTestForm.customWords || undefined,
|
||||||
|
source: nameTestForm.source,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
nameTestText.value = t('nameTest.recognizeAgain')
|
nameTestText.value = t('nameTest.recognizeAgain')
|
||||||
@@ -184,32 +213,34 @@ async function saveCustomWords() {
|
|||||||
<template>
|
<template>
|
||||||
<div class="shortcut-workbench">
|
<div class="shortcut-workbench">
|
||||||
<section class="shortcut-panel shortcut-input-panel">
|
<section class="shortcut-panel shortcut-input-panel">
|
||||||
<div class="panel-heading">
|
|
||||||
<div>
|
|
||||||
<div class="text-subtitle-1 font-weight-medium">
|
|
||||||
{{ t('nameTest.inputTitle') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-caption text-medium-emphasis">
|
|
||||||
{{ t('nameTest.inputSubtitle') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<VIcon icon="mdi-text-recognition" color="primary" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<VForm validate-on="submit lazy" @submit.prevent="nameTest">
|
<VForm validate-on="submit lazy" @submit.prevent="nameTest">
|
||||||
<VRow class="shortcut-form">
|
<VRow class="shortcut-form">
|
||||||
<VCol cols="12" class="shortcut-form-col">
|
<VCol cols="12" class="shortcut-form-col">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="nameTestForm.title"
|
v-model="nameTestForm.title"
|
||||||
:label="t('nameTest.title')"
|
:label="t('nameTest.title')"
|
||||||
|
:hint="t('nameTest.titleHint')"
|
||||||
|
persistent-hint
|
||||||
:rules="[requiredValidator]"
|
:rules="[requiredValidator]"
|
||||||
prepend-inner-icon="mdi-movie-open"
|
prepend-inner-icon="mdi-movie-open"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" class="shortcut-form-col">
|
||||||
|
<VSelect
|
||||||
|
v-model="nameTestForm.source"
|
||||||
|
:items="mediaSourceItems"
|
||||||
|
:label="t('nameTest.source')"
|
||||||
|
:hint="t('nameTest.sourceHint')"
|
||||||
|
persistent-hint
|
||||||
|
prepend-inner-icon="mdi-database-search"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12" class="shortcut-form-col">
|
<VCol cols="12" class="shortcut-form-col">
|
||||||
<VTextarea
|
<VTextarea
|
||||||
v-model="nameTestForm.subtitle"
|
v-model="nameTestForm.subtitle"
|
||||||
:label="t('nameTest.subtitle')"
|
:label="t('nameTest.subtitle')"
|
||||||
|
:hint="t('nameTest.subtitleHint')"
|
||||||
|
persistent-hint
|
||||||
rows="2"
|
rows="2"
|
||||||
auto-grow
|
auto-grow
|
||||||
prepend-inner-icon="mdi-subtitles"
|
prepend-inner-icon="mdi-subtitles"
|
||||||
@@ -220,6 +251,8 @@ async function saveCustomWords() {
|
|||||||
v-model="nameTestForm.customWords"
|
v-model="nameTestForm.customWords"
|
||||||
:label="t('nameTest.customWords')"
|
:label="t('nameTest.customWords')"
|
||||||
:placeholder="t('nameTest.customWordsPlaceholder')"
|
:placeholder="t('nameTest.customWordsPlaceholder')"
|
||||||
|
:hint="t('nameTest.customWordsHint')"
|
||||||
|
persistent-hint
|
||||||
rows="3"
|
rows="3"
|
||||||
auto-grow
|
auto-grow
|
||||||
prepend-inner-icon="mdi-tag-text-outline"
|
prepend-inner-icon="mdi-tag-text-outline"
|
||||||
@@ -370,14 +403,6 @@ async function saveCustomWords() {
|
|||||||
box-shadow: var(--app-surface-shadow);
|
box-shadow: var(--app-surface-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-heading {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin-block-end: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-form {
|
.shortcut-form {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,24 +180,14 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="shortcut-workbench">
|
<div class="shortcut-workbench">
|
||||||
<section class="shortcut-panel shortcut-input-panel">
|
<section class="shortcut-panel shortcut-input-panel">
|
||||||
<div class="panel-heading">
|
|
||||||
<div>
|
|
||||||
<div class="text-subtitle-1 font-weight-medium">
|
|
||||||
{{ t('ruleTest.inputTitle') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-caption text-medium-emphasis">
|
|
||||||
{{ t('ruleTest.inputSubtitle') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<VIcon icon="mdi-filter-cog" color="primary" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<VForm ref="ruleTestFormRef" validate-on="submit lazy" @submit.prevent="ruleTest">
|
<VForm ref="ruleTestFormRef" validate-on="submit lazy" @submit.prevent="ruleTest">
|
||||||
<VRow class="shortcut-form">
|
<VRow class="shortcut-form">
|
||||||
<VCol cols="12" class="shortcut-form-col">
|
<VCol cols="12" class="shortcut-form-col">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="ruleTestForm.title"
|
v-model="ruleTestForm.title"
|
||||||
:label="t('ruleTest.title')"
|
:label="t('ruleTest.title')"
|
||||||
|
:hint="t('ruleTest.titleHint')"
|
||||||
|
persistent-hint
|
||||||
:rules="[requiredValidator]"
|
:rules="[requiredValidator]"
|
||||||
prepend-inner-icon="mdi-movie-open"
|
prepend-inner-icon="mdi-movie-open"
|
||||||
/>
|
/>
|
||||||
@@ -207,6 +197,8 @@ onMounted(() => {
|
|||||||
v-model="ruleTestForm.rulegroup"
|
v-model="ruleTestForm.rulegroup"
|
||||||
:items="filterRuleGroupItems"
|
:items="filterRuleGroupItems"
|
||||||
:label="t('ruleTest.ruleGroup')"
|
:label="t('ruleTest.ruleGroup')"
|
||||||
|
:hint="t('ruleTest.ruleGroupHint')"
|
||||||
|
persistent-hint
|
||||||
:loading="filterRuleGroupLoading"
|
:loading="filterRuleGroupLoading"
|
||||||
:rules="[requiredValidator]"
|
:rules="[requiredValidator]"
|
||||||
prepend-inner-icon="mdi-filter"
|
prepend-inner-icon="mdi-filter"
|
||||||
@@ -216,6 +208,8 @@ onMounted(() => {
|
|||||||
<VTextarea
|
<VTextarea
|
||||||
v-model="ruleTestForm.subtitle"
|
v-model="ruleTestForm.subtitle"
|
||||||
:label="t('ruleTest.subtitle')"
|
:label="t('ruleTest.subtitle')"
|
||||||
|
:hint="t('ruleTest.subtitleHint')"
|
||||||
|
persistent-hint
|
||||||
rows="2"
|
rows="2"
|
||||||
auto-grow
|
auto-grow
|
||||||
prepend-inner-icon="mdi-subtitles"
|
prepend-inner-icon="mdi-subtitles"
|
||||||
@@ -317,14 +311,6 @@ onMounted(() => {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-heading {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-block-end: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-form {
|
.shortcut-form {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user