Skip to content

Commit 3f36848

Browse files
committed
feat: 添加下载 AI 原声翻译音轨功能
- 在视频下载功能中支持选择音轨 - 更新相关数据结构以包含音轨信息 - 修改界面以显示可用音轨选项
1 parent 617579f commit 3f36848

4 files changed

Lines changed: 76 additions & 4 deletions

File tree

registry/lib/components/video/download/DownloadVideo.vue

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@
3737
@change="saveSelectedQuality()"
3838
/>
3939
</div>
40+
<div v-if="audioTracks && audioTracks.length > 0" class="download-video-config-item">
41+
<div class="download-video-config-title">AI 原声翻译:</div>
42+
<VDropdown
43+
v-model="selectedAudioTrack"
44+
:items="audioTrackOptions"
45+
value-key="name"
46+
@change="saveSelectedAudioTrack()"
47+
/>
48+
</div>
4049
<template v-if="!testData.multiple && selectedQuality">
4150
<div v-if="testData.videoInfo" class="download-video-config-description">
4251
预计大小: {{ formatFileSize(testData.videoInfo.totalSize) }}
@@ -166,6 +175,8 @@ const getFallbackTestVideoInfo = () =>
166175
title: getFriendlyTitle(true),
167176
} as DownloadVideoInputItem)
168177
178+
const DEFAULT_AUDIO_TRACK = { name: '', displayName: '原声' }
179+
169180
export default Vue.extend({
170181
components: {
171182
VPopup,
@@ -199,6 +210,8 @@ export default Vue.extend({
199210
assets,
200211
qualities: [],
201212
selectedQuality: undefined,
213+
audioTracks: [],
214+
selectedAudioTrack: undefined,
202215
inputs: [],
203216
selectedInput: undefined,
204217
apis: [],
@@ -220,12 +233,19 @@ export default Vue.extend({
220233
}
221234
return this.qualities
222235
},
236+
audioTrackOptions() {
237+
const options = this.audioTracks.map(t => ({ name: t.id, displayName: t.name }))
238+
if (options.length > 0) {
239+
options.unshift(DEFAULT_AUDIO_TRACK)
240+
}
241+
return options
242+
},
223243
canStartDownload() {
224244
if (this.busy || !this.open) {
225245
return false
226246
}
227247
const isAnySelectionEmpty = Object.entries(this)
228-
.filter(([key]) => key.startsWith('selected'))
248+
.filter(([key]) => key.startsWith('selected') && key !== 'selectedAudioTrack')
229249
.some(([, value]) => !value)
230250
if (isAnySelectionEmpty) {
231251
return false
@@ -289,6 +309,11 @@ export default Vue.extend({
289309
basicConfig.quality = quality.value
290310
this.updateTestVideoInfo()
291311
},
312+
saveSelectedAudioTrack() {
313+
// 避免冗余请求:这里不再使用引用对比,依赖 VDropdown 的 value-key="name" 属性
314+
// 它能够正确阻止触发不需要的 change 事件
315+
this.updateTestVideoInfo()
316+
},
292317
async getVideoItems() {
293318
const input = this.selectedInput as DownloadVideoInput
294319
const videoItems = await input.getInputs(this.$refs.inputOptions)
@@ -307,6 +332,8 @@ export default Vue.extend({
307332
try {
308333
const videoInfo = await api.downloadVideoInfo(testItem)
309334
this.qualities = videoInfo.qualities
335+
this.audioTracks = videoInfo.audioTracks || []
336+
310337
const isSelectedQualityOutdated =
311338
!this.selectedQuality ||
312339
!videoInfo.qualities.some(q => q.value === this.selectedQuality.value)
@@ -319,8 +346,22 @@ export default Vue.extend({
319346
}
320347
}
321348
}
349+
350+
const isSelectedAudioTrackOutdated =
351+
!this.selectedAudioTrack ||
352+
(this.selectedAudioTrack.name !== '' &&
353+
!this.audioTracks.some(t => t.id === this.selectedAudioTrack.name))
354+
355+
if (isSelectedAudioTrackOutdated) {
356+
if (this.audioTracks.length > 0) {
357+
this.selectedAudioTrack = DEFAULT_AUDIO_TRACK
358+
} else {
359+
this.selectedAudioTrack = undefined
360+
}
361+
}
322362
// 填充 quality 后要再请求一次得到对应 quality 的统计数据
323363
testItem.quality = this.selectedQuality
364+
testItem.audioTrack = this.selectedAudioTrack?.name
324365
const qualityVideoInfo = await api.downloadVideoInfo(testItem)
325366
this.testData.videoInfo = qualityVideoInfo
326367
console.log('[qualityVideoInfo]', qualityVideoInfo)
@@ -341,6 +382,7 @@ export default Vue.extend({
341382
}
342383
videoInputs.forEach(item => {
343384
item.quality = this.selectedQuality
385+
item.audioTrack = this.selectedAudioTrack?.name
344386
})
345387
const videoInfos = await Promise.all(videoInputs.map(i => api.downloadVideoInfo(i)))
346388
if (videoInfos.length === 0 || lodash.sumBy(videoInfos, it => it.fragments.length) === 0) {

registry/lib/components/video/download/apis/dash.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Options } from '..'
88
import { compareQuality } from '../error'
99
import {
1010
DownloadVideoApi,
11+
DownloadVideoAudioTrack,
1112
DownloadVideoFragment,
1213
DownloadVideoInfo,
1314
DownloadVideoInputItem,
@@ -115,8 +116,8 @@ const downloadDash = async (
115116
audio: () => true,
116117
...filters,
117118
}
118-
const { aid, cid, quality } = input
119-
const params = {
119+
const { aid, cid, quality, audioTrack } = input
120+
const params: Record<string, string | number> = {
120121
avid: aid,
121122
cid,
122123
qn: quality?.value ?? '',
@@ -125,6 +126,9 @@ const downloadDash = async (
125126
fnver: 0,
126127
fnval: 4048,
127128
}
129+
if (audioTrack) {
130+
params.cur_language = audioTrack
131+
}
128132
const isBanugmi = bangumiUrls.some(url => matchUrlPattern(url))
129133
const api = isBanugmi ? bangumiApi(formData(params)) : videoApi(formData(params))
130134
const data = await bilibiliApi(getJsonWithCredentials(api), '获取视频链接失败')
@@ -219,6 +223,13 @@ const downloadDash = async (
219223
})()
220224
const currentCodec = fragments.find(x => x.type === 'video')?.codec
221225
const currentBandWidth = fragments.reduce((p, c) => p + c.bandWidth, 0)
226+
let audioTracks: DownloadVideoAudioTrack[] | undefined
227+
if (data.language?.items?.length) {
228+
audioTracks = data.language.items.map((item: any) => ({
229+
id: item.lang,
230+
name: item.title,
231+
}))
232+
}
222233
const info = new DownloadVideoInfo({
223234
input,
224235
jsonData: data,
@@ -227,6 +238,7 @@ const downloadDash = async (
227238
currentQuality,
228239
currentCodec,
229240
currentBandWidth,
241+
audioTracks,
230242
})
231243
compareQuality(input, info)
232244
return info

registry/lib/components/video/download/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ export interface DownloadVideoInputItem {
1919
quality?: VideoQuality
2020
/** 是否允许画质回退, 当实际画质和期望画质不符时此项将决定是否抛出异常 */
2121
allowQualityDrop?: boolean
22+
/** 请求的音轨语言 */
23+
audioTrack?: string
24+
}
25+
export interface DownloadVideoAudioTrack {
26+
id: string
27+
name: string
2228
}
2329
/** 页面数据提供者 */
2430
export interface DownloadVideoInput<InputParameter = any> extends VueInstanceInput, WithName {
@@ -50,6 +56,7 @@ export class DownloadVideoInfo {
5056
public currentQuality: VideoQuality
5157
public currentCodec?: string
5258
public currentBandWidth?: number
59+
public audioTracks?: DownloadVideoAudioTrack[]
5360
public jsonData: any
5461
constructor(
5562
parameters: Omit<DownloadVideoInfo, 'totalSize' | 'totalLength' | 'titledFragments'>,

src/ui/VDropdown.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export default Vue.extend({
8989
type: Function,
9090
default: (item: any) => item.name,
9191
},
92+
valueKey: {
93+
type: String,
94+
default: '',
95+
},
9296
round: {
9397
type: Boolean,
9498
default: false,
@@ -118,7 +122,14 @@ export default Vue.extend({
118122
},
119123
methods: {
120124
selectItem(item: any) {
121-
if (item !== this.value) {
125+
let isChanged = false
126+
if (this.valueKey && item && this.value) {
127+
isChanged = item[this.valueKey] !== this.value[this.valueKey]
128+
} else {
129+
isChanged = item !== this.value
130+
}
131+
132+
if (isChanged) {
122133
this.$emit('change', item)
123134
}
124135
this.popupOpen = false

0 commit comments

Comments
 (0)