Add batch support for dash

This commit is contained in:
the1812 2019-11-09 12:23:46 +08:00
parent d2acf71d01
commit 88af768056
15 changed files with 180 additions and 113 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@
"auto-continue.min.js": "96CD47C367D7397CE1467A69764409BAEFF16E8F535BC8E15523CD0B8A86687E",
"auto-draw.min.js": "AE72CF2623DF2D15AD4AF82D125FFAF7EF5B1E6B36D9E9AC646AC98AA6AA8698",
"auto-play.min.js": "DC9938AC15DADDC9D88DCA0C9BE64BE142C37D32CB85E42E23DAB2A7378531E5",
"batch-download.min.js": "226DA052A6A9542AEF8B1767823F774C600D586394E2B36A6E6765BD76A7597E",
"batch-download.min.js": "B1E7B544EB4A04CEAE1FCC8668DDF29642C2B394D4C5F63A4EB3D2BA8519587E",
"biliplus-redirect.min.js": "9882D14DAC5C103212A101A5168883C41B4B3B2737FD5F222DC343D0304FF8AF",
"blackboard.min.js": "8BB9CCDE72302AE7DE39D66F9231EFB82BA27DDA799BA4AAAC06F6DE802661BF",
"blackboard.vue.min.js": "8F1984AB69B029701DC88F33568EF1881D0C54EEC72D65EA3029CF2F68BD6674",
@ -47,7 +47,7 @@
"download-video-package.min.js": "B10FF11DE7DFBACEC38F195B8E836511E527215B5E177CB1D223D14F30EB1435",
"download-video.min.css": "95FF9DA3C1FFF1C1134D70AE3BB609049B7D5D3FBB71CAAAE84C8742C51AD841",
"download-video.min.html": "2727789E8423E8DBC8801FF6AC0D8181AC684A643A83CD446A5D69318DDBF9F6",
"download-video.min.js": "4013A2F8DB6B2859DF83EFEC32274158ABA1769536D8656537E2FCDF96560AD8",
"download-video.min.js": "06CA9FEB6A20B45E142D68D8C8C102993F30237B35D8D6562C9D7D7EAD04F866",
"dpi-img.vue.min.js": "3D9D93079365DCABA8DC8970F051AD1A7F25C3EFFF850CD1000C4312E9B910DD",
"expand-danmaku.min.js": "B21658C40085AEA8DC49652AE62EB8610BB2EBEAF7A9C9AF69EF3B11E08EEC8D",
"expand-description.min.css": "58C7710A50521B80F7D872BDC4C652610D84C4FABC6874BA66DA37B4F8759224",
@ -56,7 +56,7 @@
"feeds-apis.min.js": "ED1FFDEBC22680C5089F9D30CF667535FB99881C96A969DB47BC70BF095A99E8",
"feeds-filter-card.vue.min.js": "3A7F8F402EA46E43B718506329814953C9E358A8AF94088D4E5745D48296D80B",
"feeds-filter.min.js": "10557E498B8BADA3C1C03E8C1DCD6FB24DBA2DA27A5A03BD861054F7E2989D46",
"ffmpeg-support.min.js": "CB33E150098394ABCACE6FC45CC1C6DA1B71012C77265D93F23C555A532016A1",
"ffmpeg-support.min.js": "1C9B76371E6E9DDE615FE0D74E11BF205F54C9B825FC9FF0223FB835617EA219",
"fill-with-comments.min.js": "5D52ACB5057267C2EEBA5AA74EEE5D4086215645623560825C25AAA133536F2F",
"filter-type-switch.vue.min.js": "E06B00EF48F3CCFACC82B858A266CE0DF48F8332C5900B3AD579330D4C6D3125",
"fix-fullscreen.min.js": "C0628A7CABB4421FCBD7663700EF9965C96F6D79979B9F7523A9F9B0B009C8B6",
@ -172,7 +172,7 @@
"v-dropdown.vue.min.js": "D26B4A74515F53A34A3CB0590FC9683476D99FBBBB9647FDFCBF33E265C3F662",
"video-card-info.min.js": "13B755C0EE0CA018AE65251E168BAF3395FEA2EB4FAD6949AADD752F61B8819B",
"video-card.vue.min.js": "340436CDE3B102F467F671411D4373E0DBF59C5A72867D18BDF45BA6C66156C2",
"video-dash.min.js": "160CD4F33B2AA90D66F9EEFE4070EFD16AA794F542DCBCE3E5E504A1F2A26F28",
"video-dash.min.js": "E0345C36E793F8C05E5FC83DDAB88751CD6BA28FB0A1AA63BC20FBCE330F037B",
"video-downloader-fragment.min.js": "13B755C0EE0CA018AE65251E168BAF3395FEA2EB4FAD6949AADD752F61B8819B",
"video-info.min.js": "7234A74CA28A772A31E41BBD4925FC2B22C6701419ADFD511402C6A9D6D9E5FA",
"video-list.vue.min.js": "BC1DB652C4CD8E98AC620E74ED306AACE0CD99359EE472BFD2A846ADCA37AD0B",

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(()=>{return(e,t)=>{const n=(e,t,n)=>{if(e<2){return null}const s=[];for(let r=1;r<=e;r++){let e=` - ${r}`;if(n.includes(".m4a")){e=""}s.push(escapeFilename(`file '${t}${e}${n[r-1]}'`))}return s.join("\n")};const s=(e,t)=>{const n=e=>e.fragments.length>1;const s=e.filter(n);if(s.length===0){return null}const r=new Map;s.forEach(e=>{r.set(escapeFilename(`ffmpeg-files-${e.title}.txt`),e.fragments.map((n,s)=>{return escapeFilename(`file '${e.title} - ${s+1}${t[s]}'`)}).join("\n"))});return r};const r=(e,t)=>{const n=[];e.forEach(e=>{e.fragments.forEach((s,r)=>{let i="";if(e.fragments.length>1&&!t.includes(".m4a")){i=` - ${r+1}`}n.push(escapeFilename(`file '${e.title}${i}${t[r]}'`))})});return n.join("\n")};return{export:{getFragmentsList:n,getBatchFragmentsList:s,getBatchEpisodesList:r}}}})();
(()=>{return(e,t)=>{const n=[".mp4",".m4a"];const r=(e,t,n)=>{if(e<2){return null}const r=[];for(let s=1;s<=e;s++){let e=` - ${s}`;if(n.includes(".m4a")){e=""}r.push(escapeFilename(`file '${t}${e}${n[s-1]}'`))}return r.join("\n")};const s=(e,t)=>{const r=e=>e.fragments.length>1;const s=e.filter(r);if(s.length===0){return null}const i=new Map;s.forEach(e=>{i.set(escapeFilename(`ffmpeg-files-${e.title}.txt`),e.fragments.map((r,s)=>{let i=` - ${s+1}`;if(t===true){i=""}return escapeFilename(`file '${e.title}${i}${t===true?n[s]:t}'`)}).join("\n"))});return i};const i=(e,t)=>{const r=[];e.forEach(e=>{e.fragments.forEach((s,i)=>{let l="";if(e.fragments.length>1&&t!==true){l=` - ${i+1}`}r.push(escapeFilename(`file '${e.title}${l}${t===true?n[i]:t}'`))})});return r.join("\n")};return{export:{getFragmentsList:r,getBatchFragmentsList:s,getBatchEpisodesList:i}}}})();

View File

@ -1 +1 @@
(()=>{return(t,a)=>{const e=t=>{return{url:t.downloadUrl,backupUrls:t.backupUrls,length:t.duration,size:Math.trunc(t.bandWidth*t.duration/8)}};const r=async(t,a)=>{const e=await Ajax.getJsonWithCredentials(t);const r=e.data||e.result||e;if(e.code!==0||!r.dash){throw new Error("DASH api failed")}const c=r.accept_quality;if(!c.includes(a)){throw new Error("没有找到请求的清晰度")}if(r.quality!==a){throw new Error("无法获取请求的清晰度, 请确认当前账号有相应的权限")}const d=r.accept_description;const o=d[c.indexOf(a)];const s=r.dash.duration;const n=r.dash.video.filter(t=>t.id===a).map(t=>{const e=(()=>{switch(t.codecid){case 12:return"HEVC/H.265";default:case 7:return"AVC/H.264"}})();const r={quality:a,qualityText:o,width:t.width,height:t.height,codecs:t.codecs,codecId:t.codecid,bandWidth:t.bandwidth,frameRate:t.frameRate,backupUrls:(t.backupUrl||t.backup_url||[]).forEach(t=>t.replace("http:","https:")),downloadUrl:(t.baseUrl||t.base_url||"").replace("http:","https:"),duration:s,videoCodec:e};return r});const i=r.dash.audio.map(t=>{return{bandWidth:t.bandwidth,codecs:t.codecs,codecId:t.codecid,backupUrls:(t.backupUrl||t.backup_url||[]).forEach(t=>t.replace("http:","https:")),downloadUrl:(t.baseUrl||t.base_url||"").replace("http:","https:"),duration:s}});return{videoDashes:n,audioDashes:i}};return{export:{getDashInfo:r,dashToFragment:e}}}})();
(()=>{return(t,e)=>{const a=t=>{return{url:t.downloadUrl,backupUrls:t.backupUrls,length:t.duration,size:Math.trunc(t.bandWidth*t.duration/8)}};const d=e=>{console.log(e.videoDashes);const d=(()=>{const a=e=>e.videoCodec===t.downloadVideoDashCodec;if(e.videoDashes.some(a)){return e.videoDashes.filter(a).sort(ascendingSort(t=>t.bandWidth))[0]}else{return e.videoDashes.sort(ascendingSort(t=>t.bandWidth))[0]}})();const o=e.audioDashes.sort(descendingSort(t=>t.bandWidth))[0];return[a(d),a(o)]};const o=async(t,e)=>{const a=await Ajax.getJsonWithCredentials(t);const d=a.data||a.result||a;if(a.code!==0||!d.dash){throw new Error("DASH api failed")}const o=d.accept_quality;if(!o.includes(e)){throw new Error("没有找到请求的清晰度")}if(d.quality!==e){throw new Error("无法获取请求的清晰度, 请确认当前账号有相应的权限")}const r=d.accept_description;const s=r[o.indexOf(e)];const c=d.dash.duration;const n=d.dash.video.filter(t=>t.id===e).map(t=>{const a=(()=>{switch(t.codecid){case 12:return"HEVC/H.265";default:case 7:return"AVC/H.264"}})();const d={quality:e,qualityText:s,width:t.width,height:t.height,codecs:t.codecs,codecId:t.codecid,bandWidth:t.bandwidth,frameRate:t.frameRate,backupUrls:(t.backupUrl||t.backup_url||[]).forEach(t=>t.replace("http:","https:")),downloadUrl:(t.baseUrl||t.base_url||"").replace("http:","https:"),duration:c,videoCodec:a};return d});const i=d.dash.audio.map(t=>{return{bandWidth:t.bandwidth,codecs:t.codecs,codecId:t.codecid,backupUrls:(t.backupUrl||t.backup_url||[]).forEach(t=>t.replace("http:","https:")),downloadUrl:(t.baseUrl||t.base_url||"").replace("http:","https:"),duration:c}});return{videoDashes:n,audioDashes:i}};return{export:{getDashInfo:o,dashToFragments:d}}}})();

View File

@ -162,3 +162,4 @@ export function html(strings, ...values) {
return previous + current + (value === undefined ? '' : value)
}, '')
}
export const dashExtensions = ['.mp4', '.m4a']

View File

@ -1,4 +1,10 @@
const fragmentSplitFactor = 12
const dashExtensions = ['.mp4', '.m4a']
export interface BatchExtractorConfig {
itemFilter: (item: BatchItem) => boolean
api?: (aid: number | string, cid: number | string, quality?: number | string) => string
}
interface BatchItem {
aid: number | string
cid: number | string
@ -13,13 +19,26 @@ export interface RawItem {
referer: string
}
abstract class Batch {
constructor(public config: BatchExtractorConfig) { }
itemList: BatchItem[] = []
itemFilter: (item: BatchItem) => boolean = () => true
abstract async getItemList(): Promise<BatchItem[]>
abstract async collectData(quality: number | string): Promise<string>
async getRawItems(quality: number | string): Promise<RawItem[]> {
return JSON.parse(await this.collectData(quality))
}
extension(url: string, index: number) {
const match = [
'.flv',
'.mp4'
].find(it => url.includes(it))
if (match) {
return match
} else if (url.includes('.m4s')) {
return dashExtensions[index]
} else {
return '.flv'
}
}
async collectAria2(quality: number | string, rpc: boolean) {
const json = await this.getRawItems(quality)
if (rpc) {
@ -39,7 +58,7 @@ abstract class Batch {
params.push({
referer: document.URL.replace(window.location.search, ''),
'user-agent': UserAgent,
out: `${item.title}${indexNumber}.flv`,
out: `${item.title}${indexNumber}${this.extension(fragment.url, index)}`,
split: fragmentSplitFactor,
dir: (option.baseDir + option.dir) || undefined,
'max-download-limit': option.maxDownloadLimit || undefined,
@ -66,7 +85,7 @@ ${json.map(item => {
${f.url}
referer=${item.referer}
user-agent=${UserAgent}
out=${item.title}${indexNumber}.flv
out=${item.title}${indexNumber}${this.extension(f.url, index)}
split=${fragmentSplitFactor}
`.trim()
})
@ -108,20 +127,27 @@ class VideoEpisodeBatch extends Batch {
}
async collectData(quality: number | string) {
const result = []
for (const item of (await this.getItemList()).filter(this.itemFilter)) {
const url = `https://api.bilibili.com/x/player/playurl?avid=${item.aid}&cid=${item.cid}&qn=${quality}&otype=json`
for (const item of (await this.getItemList()).filter(this.config.itemFilter)) {
const url = this.config.api ? this.config.api(item.aid, item.cid, quality) : `https://api.bilibili.com/x/player/playurl?avid=${item.aid}&cid=${item.cid}&qn=${quality}&otype=json`
const json = await Ajax.getJsonWithCredentials(url)
const data = json.data || json.result || json
if (data.quality !== quality) {
console.warn(`${item.title} 不支持所选画质, 已回退到较低画质. (quality=${data.quality})`)
}
const fragments: RawItemFragment[] = data.durl.map((it: any) => {
return {
length: it.length,
size: it.size,
url: it.url
}
})
let fragments: RawItemFragment[]
if (data.durl) {
fragments = data.durl.map((it: any) => {
return {
length: it.length,
size: it.size,
url: it.url
}
})
} else {
const { getDashInfo, dashToFragments } = await import('./video-dash')
const info = await getDashInfo(url, typeof quality === 'string' ? parseInt(quality) : quality)
fragments = dashToFragments(info)
}
result.push({
fragments,
title: item.title.replace(/[\/\\:\*\?"<>\|]/g, ' '),
@ -167,20 +193,27 @@ class BangumiBatch extends Batch {
}
async collectData(quality: string | number) {
const result = []
for (const item of (await this.getItemList()).filter(this.itemFilter)) {
const url = `https://api.bilibili.com/pgc/player/web/playurl?avid=${item.aid}&cid=${item.cid}&qn=${quality}&otype=json`
for (const item of (await this.getItemList()).filter(this.config.itemFilter)) {
const url = this.config.api ? this.config.api(item.aid, item.cid, quality) : `https://api.bilibili.com/pgc/player/web/playurl?avid=${item.aid}&cid=${item.cid}&qn=${quality}&otype=json`
const json = await Ajax.getJsonWithCredentials(url)
const data = json.data || json.result || json
if (data.quality !== quality) {
console.warn(`${item.title} 不支持所选画质, 已回退到较低画质. (quality=${data.quality})`)
}
const fragments: RawItemFragment[] = data.durl.map((it: any) => {
return {
length: it.length,
size: it.size,
url: it.url
}
})
let fragments: RawItemFragment[]
if (data.durl) {
fragments = data.durl.map((it: any) => {
return {
length: it.length,
size: it.size,
url: it.url
}
})
} else {
const { getDashInfo, dashToFragments } = await import('./video-dash')
const info = await getDashInfo(url, typeof quality === 'string' ? parseInt(quality) : quality)
fragments = dashToFragments(info)
}
result.push({
fragments,
title: item.title.replace(/[\/\\:\*\?"<>\|]/g, ' '),
@ -193,9 +226,14 @@ class BangumiBatch extends Batch {
}
}
const extractors = [BangumiBatch, VideoEpisodeBatch]
let ExtractorClass: new() => Batch
let ExtractorClass: new (config: BatchExtractorConfig) => Batch
export class BatchExtractor {
itemFilter: (item: BatchItem) => boolean = () => true
config: BatchExtractorConfig
constructor(config?: BatchExtractorConfig) {
this.config = Object.assign({
itemFilter: () => true,
}, config)
}
static async test() {
for (const e of extractors) {
if (await e.test() === true) {
@ -210,8 +248,7 @@ export class BatchExtractor {
logError('[批量下载] 未找到合适的解析模块.')
throw new Error(`[Batch Download] module not found.`)
}
const extractor = new ExtractorClass()
extractor.itemFilter = this.itemFilter
const extractor = new ExtractorClass(this.config)
return extractor
}
async getItemList() {

View File

@ -11,53 +11,71 @@ interface PageData {
cid: string
}
class Video {
async getDashUrl(quality?: number) {
if (quality) {
return `https://api.bilibili.com/x/player/playurl?avid=${pageData.aid}&cid=${pageData.cid}&qn=${quality}&otype=json&fnver=0&fnval=16`
} else {
return `https://api.bilibili.com/x/player/playurl?avid=${pageData.aid}&cid=${pageData.cid}&otype=json&fnver=0&fnval=16`
async getApiGenerator(dash = false) {
function api(aid: number | string, cid: number | string, quality?: number) {
if (dash) {
if (quality) {
return `https://api.bilibili.com/x/player/playurl?avid=${aid}&cid=${cid}&qn=${quality}&otype=json&fnver=0&fnval=16`
} else {
return `https://api.bilibili.com/x/player/playurl?avid=${aid}&cid=${cid}&otype=json&fnver=0&fnval=16`
}
} else {
if (quality) {
return `https://api.bilibili.com/x/player/playurl?avid=${aid}&cid=${cid}&qn=${quality}&otype=json`
} else {
return `https://api.bilibili.com/x/player/playurl?avid=${aid}&cid=${cid}&otype=json`
}
}
}
return api.bind(this) as typeof api
}
async getDashUrl(quality?: number) {
return (await this.getApiGenerator(true))(pageData.aid, pageData.cid, quality)
}
async getUrl(quality?: number) {
if (quality) {
return `https://api.bilibili.com/x/player/playurl?avid=${pageData.aid}&cid=${pageData.cid}&qn=${quality}&otype=json`
} else {
return `https://api.bilibili.com/x/player/playurl?avid=${pageData.aid}&cid=${pageData.cid}&otype=json`
}
return (await this.getApiGenerator())(pageData.aid, pageData.cid, quality)
}
}
class Bangumi extends Video {
async getDashUrl(quality?: number) {
if (quality) {
return `https://api.bilibili.com/pgc/player/web/playurl?avid=56995872&cid=99547543&qn=${quality}&otype=json&fourk=1&fnval=16`
} else {
return `https://api.bilibili.com/pgc/player/web/playurl?avid=56995872&cid=99547543&otype=json&fourk=1&fnval=16`
}
}
async getUrl(quality?: number) {
if (quality) {
return `https://api.bilibili.com/pgc/player/web/playurl?avid=${pageData.aid}&cid=${pageData.cid}&qn=${quality}&otype=json`
} else {
return `https://api.bilibili.com/pgc/player/web/playurl?avid=${pageData.aid}&cid=${pageData.cid}&qn=&otype=json`
async getApiGenerator(dash = false) {
function api(aid: number | string, cid: number | string, quality?: number) {
if (dash) {
if (quality) {
return `https://api.bilibili.com/pgc/player/web/playurl?avid=${aid}&cid=${cid}&qn=${quality}&otype=json&fourk=1&fnval=16`
} else {
return `https://api.bilibili.com/pgc/player/web/playurl?avid=${aid}&cid=${cid}&otype=json&fourk=1&fnval=16`
}
} else {
if (quality) {
return `https://api.bilibili.com/pgc/player/web/playurl?avid=${pageData.aid}&cid=${pageData.cid}&qn=${quality}&otype=json`
} else {
return `https://api.bilibili.com/pgc/player/web/playurl?avid=${pageData.aid}&cid=${pageData.cid}&qn=&otype=json`
}
}
}
return api.bind(this) as typeof api
}
}
// 课程, 不知道为什么b站给它起名cheese
class Cheese extends Video {
constructor(public ep: number | string) { super() }
async getDashUrl(quality?: number) {
if (quality) {
return `https://api.bilibili.com/pugv/player/web/playurl?avid=${pageData.aid}&cid=${pageData.cid}&qn=${quality}&otype=json&ep_id=${this.ep}&fnver=0&fnval=16`
} else {
return `https://api.bilibili.com/pugv/player/web/playurl?avid=${pageData.aid}&cid=${pageData.cid}&otype=json&ep_id=${this.ep}&fnver=0&fnval=16`
}
}
async getUrl(quality?: number) {
if (quality) {
return `https://api.bilibili.com/pugv/player/web/playurl?avid=${pageData.aid}&cid=${pageData.cid}&qn=${quality}&otype=json&ep_id=${this.ep}`
} else {
return `https://api.bilibili.com/pugv/player/web/playurl?avid=${pageData.aid}&cid=${pageData.cid}&otype=json&ep_id=${this.ep}`
async getApiGenerator(dash = false) {
function api(aid: number | string, cid: number | string, quality?: number) {
if (dash) {
if (quality) {
return `https://api.bilibili.com/pugv/player/web/playurl?avid=${aid}&cid=${cid}&qn=${quality}&otype=json&ep_id=${this.ep}&fnver=0&fnval=16`
} else {
return `https://api.bilibili.com/pugv/player/web/playurl?avid=${aid}&cid=${cid}&otype=json&ep_id=${this.ep}&fnver=0&fnval=16`
}
} else {
if (quality) {
return `https://api.bilibili.com/pugv/player/web/playurl?avid=${aid}&cid=${cid}&qn=${quality}&otype=json&ep_id=${this.ep}`
} else {
return `https://api.bilibili.com/pugv/player/web/playurl?avid=${aid}&cid=${cid}&otype=json&ep_id=${this.ep}`
}
}
}
return api.bind(this) as typeof api
}
}
@ -168,26 +186,12 @@ class VideoDownloader {
})
}
else {
const { dashToFragment, getDashInfo } = await import('./video-dash')
const { dashToFragments, getDashInfo } = await import('./video-dash')
const dashes = await getDashInfo(
await pageData.entity.getDashUrl(this.format.quality),
this.format.quality
)
// 画面按照首选编码选择, 若没有相应编码则选择大小较小的编码
console.log(dashes.videoDashes)
const video = (() => {
const matchPreferredCodec = (d: VideoDash) => d.videoCodec === settings.downloadVideoDashCodec
if (dashes.videoDashes.some(matchPreferredCodec)) {
return dashes.videoDashes
.filter(matchPreferredCodec)
.sort(ascendingSort(d => d.bandWidth))[0]
} else {
return dashes.videoDashes.sort(ascendingSort(d => d.bandWidth))[0]
}
})()
// 声音倒序排, 选择最高音质
const audio = dashes.audioDashes.sort(descendingSort(d => d.bandWidth))[0]
this.fragments = [dashToFragment(video), dashToFragment(audio)]
this.fragments = dashToFragments(dashes)
}
return this.fragments
}
@ -799,7 +803,8 @@ async function loadPanel() {
}
const toast = Toast.info('获取链接中...', '批量导出')
const batchExtractor = this.batchExtractor as BatchExtractor
batchExtractor.itemFilter = episodeFilter
batchExtractor.config.itemFilter = episodeFilter
batchExtractor.config.api = await pageData.entity.getApiGenerator(this.dash)
let result: string
try {
switch (type) {
@ -832,8 +837,7 @@ async function loadPanel() {
const items = await batchExtractor.getRawItems(format)
const videoDownloader = new VideoDownloader(format, items[0].fragments)
const { getBatchFragmentsList } = await import('./ffmpeg-support')
const extensions = this.dash ? ['.mp4', 'm4a'] : [videoDownloader.extension()]
const map = getBatchFragmentsList(items, extensions)
const map = getBatchFragmentsList(items, this.dash || videoDownloader.extension())
if (!map) {
Toast.info('所有选择的分P都没有分段.', '分段列表', 3000)
} else {
@ -850,8 +854,7 @@ async function loadPanel() {
const items = await batchExtractor.getRawItems(format)
const videoDownloader = new VideoDownloader(format, items[0].fragments)
const { getBatchEpisodesList } = await import('./ffmpeg-support')
const extensions = this.dash ? ['.mp4', 'm4a'] : [videoDownloader.extension()]
const content = getBatchEpisodesList(items, extensions)
const content = getBatchEpisodesList(items, this.dash || videoDownloader.extension())
const pack = new DownloadVideoPackage()
pack.add('ffmpeg-files.txt', content)
await pack.emit()

View File

@ -1,5 +1,6 @@
import { RawItem } from './batch-download'
const dashExtensions = ['.mp4', '.m4a']
export const getFragmentsList = (count: number, originalTitle: string, extensions: string[]) => {
if (count < 2) {
return null
@ -14,7 +15,7 @@ export const getFragmentsList = (count: number, originalTitle: string, extension
}
return names.join('\n')
}
export const getBatchFragmentsList = (items: RawItem[], extensions: string[]) => {
export const getBatchFragmentsList = (items: RawItem[], extensionOrDash: string | boolean) => {
const multipleFragments = (item: RawItem) => item.fragments.length > 1
const fragmentsItems = items.filter(multipleFragments)
if (fragmentsItems.length === 0) {
@ -23,20 +24,24 @@ export const getBatchFragmentsList = (items: RawItem[], extensions: string[]) =>
const names = new Map<string, string>()
fragmentsItems.forEach(item => {
names.set(escapeFilename(`ffmpeg-files-${item.title}.txt`), item.fragments.map((_, index) => {
return escapeFilename(`file '${item.title} - ${index + 1}${extensions[index]}'`)
let indexNumber = ` - ${index + 1}`
if (extensionOrDash === true) {
indexNumber = ''
}
return escapeFilename(`file '${item.title}${indexNumber}${extensionOrDash === true ? dashExtensions[index] : extensionOrDash}'`)
}).join('\n'))
})
return names
}
export const getBatchEpisodesList = (items: RawItem[], extensions: string[]) => {
export const getBatchEpisodesList = (items: RawItem[], extensionOrDash: string | boolean) => {
const names: string[] = []
items.forEach(item => {
item.fragments.forEach((_, index) => {
let indexNumber = ''
if (item.fragments.length > 1 && !extensions.includes('.m4a')) {
if (item.fragments.length > 1 && extensionOrDash !== true) {
indexNumber = ` - ${index + 1}`
}
names.push(escapeFilename(`file '${item.title}${indexNumber}${extensions[index]}'`))
names.push(escapeFilename(`file '${item.title}${indexNumber}${extensionOrDash === true ? dashExtensions[index] : extensionOrDash}'`))
})
})
return names.join('\n')

View File

@ -16,7 +16,7 @@ export interface VideoDash extends AudioDash {
width: number
videoCodec: DashCodec
}
export const dashToFragment = (dash: AudioDash): VideoDownloaderFragment => {
const dashToFragment = (dash: AudioDash): VideoDownloaderFragment => {
return {
url: dash.downloadUrl,
backupUrls: dash.backupUrls,
@ -24,6 +24,23 @@ export const dashToFragment = (dash: AudioDash): VideoDownloaderFragment => {
size: Math.trunc(dash.bandWidth * dash.duration / 8),
}
}
export const dashToFragments = (dashes: { videoDashes: VideoDash[], audioDashes: AudioDash[] }) => {
// 画面按照首选编码选择, 若没有相应编码则选择大小较小的编码
console.log(dashes.videoDashes)
const video = (() => {
const matchPreferredCodec = (d: VideoDash) => d.videoCodec === settings.downloadVideoDashCodec
if (dashes.videoDashes.some(matchPreferredCodec)) {
return dashes.videoDashes
.filter(matchPreferredCodec)
.sort(ascendingSort(d => d.bandWidth))[0]
} else {
return dashes.videoDashes.sort(ascendingSort(d => d.bandWidth))[0]
}
})()
// 声音倒序排, 选择最高音质
const audio = dashes.audioDashes.sort(descendingSort(d => d.bandWidth))[0]
return [dashToFragment(video), dashToFragment(audio)]
}
export const getDashInfo = async (api: string, quality: number) => {
const json = await Ajax.getJsonWithCredentials(api)
const data = json.data || json.result || json
@ -86,6 +103,6 @@ export const getDashInfo = async (api: string, quality: number) => {
export default {
export: {
getDashInfo,
dashToFragment,
dashToFragments,
},
}