Bilibili-Evolved/min/download-video.min.js
2019-11-08 17:22:37 +08:00

1 line
17 KiB
JavaScript

(()=>{return(t,e)=>{const{getFriendlyTitle:s}=e.import("title");const{VideoInfo:a,DanmakuInfo:i}=e.import("video-info");const{DownloadVideoPackage:n}=e.import("download-video-package");class o{async getDashUrl(t){if(t){return`https://api.bilibili.com/x/player/playurl?avid=${c.aid}&cid=${c.cid}&qn=${t}&otype=json&fnver=0&fnval=16`}else{return`https://api.bilibili.com/x/player/playurl?avid=${c.aid}&cid=${c.cid}&otype=json&fnver=0&fnval=16`}}async getUrl(t){if(t){return`https://api.bilibili.com/x/player/playurl?avid=${c.aid}&cid=${c.cid}&qn=${t}&otype=json`}else{return`https://api.bilibili.com/x/player/playurl?avid=${c.aid}&cid=${c.cid}&otype=json`}}}class r extends o{async getDashUrl(t){if(t){return`https://api.bilibili.com/pgc/player/web/playurl?avid=56995872&cid=99547543&qn=${t}&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(t){if(t){return`https://api.bilibili.com/pgc/player/web/playurl?avid=${c.aid}&cid=${c.cid}&qn=${t}&otype=json`}else{return`https://api.bilibili.com/pgc/player/web/playurl?avid=${c.aid}&cid=${c.cid}&qn=&otype=json`}}}class l extends o{constructor(t){super();this.ep=t}async getDashUrl(t){if(t){return`https://api.bilibili.com/pugv/player/web/playurl?avid=${c.aid}&cid=${c.cid}&qn=${t}&otype=json&ep_id=${this.ep}&fnver=0&fnval=16`}else{return`https://api.bilibili.com/pugv/player/web/playurl?avid=${c.aid}&cid=${c.cid}&otype=json&ep_id=${this.ep}&fnver=0&fnval=16`}}async getUrl(t){if(t){return`https://api.bilibili.com/pugv/player/web/playurl?avid=${c.aid}&cid=${c.cid}&qn=${t}&otype=json&ep_id=${this.ep}`}else{return`https://api.bilibili.com/pugv/player/web/playurl?avid=${c.aid}&cid=${c.cid}&otype=json&ep_id=${this.ep}`}}}const c={entity:new o,aid:"",cid:""};let d=[];let p=null;class h{constructor(t,e,s){this.quality=t;this.internalName=e;this.displayName=s}async downloadInfo(t=false){const e=new u(this);await e.fetchVideoInfo(t);return e}static parseFormats(t){const e=t.accept_quality;const s=t.accept_format.split(",");const a=t.accept_description;const i=e.map((t,e)=>{return new h(t,s[e],a[e])});return i}static async getAvailableDashFormats(){const t=await c.entity.getDashUrl();const e=await Ajax.getJsonWithCredentials(t);if(e.code!==0){throw new Error("获取清晰度信息失败.")}return h.parseFormats(e.data||e.result||e)}static async getAvailableFormats(){const t=await c.entity.getUrl();const e=await Ajax.getJsonWithCredentials(t);if(e.code!==0){throw new Error("获取清晰度信息失败.")}const s=e.data||e.result||e;return h.parseFormats(s)}}class u{constructor(t,e){this.fragmentSplitFactor=6*2;this.workingXhr=null;this.progressMap=new Map;this.format=t;this.fragments=e||[];this.videoSpeed=new f(this)}get danmakuOption(){return t.downloadVideoDefaultDanmaku}get isDash(){return this.fragments.some(t=>t.url.includes(".m4s"))}get totalSize(){return this.fragments.map(t=>t.size).reduce((t,e)=>t+e)}async fetchVideoInfo(s=false){if(!s){const t=await c.entity.getUrl(this.format.quality);const e=await Ajax.getTextWithCredentials(t);const s=JSON.parse(e.replace(/http:/g,"https:"));const a=s.data||s.result||s;if(a.quality!==this.format.quality){throw new Error("获取下载链接失败, 请确认当前账号有下载权限后重试.")}const i=a.durl;this.fragments=i.map(t=>{return{length:t.length,size:t.size,url:t.url,backupUrls:t.backup_url}})}else{const{dashToFragment:s,getDashInfo:a}=await e.importAsync("video-dash");const i=await a(await c.entity.getDashUrl(this.format.quality),this.format.quality);console.log(i.videoDashes);const n=(()=>{const e=e=>e.videoCodec===t.downloadVideoDashCodec;if(i.videoDashes.some(e)){return i.videoDashes.filter(e).sort(ascendingSort(t=>t.bandWidth))[0]}else{return i.videoDashes.sort(ascendingSort(t=>t.bandWidth))[0]}})();const o=i.audioDashes.sort(descendingSort(t=>t.bandWidth))[0];this.fragments=[s(n),s(o)]}return this.fragments}updateProgress(){const t=this.progressMap?[...this.progressMap.values()].reduce((t,e)=>t+e,0)/this.totalSize:0;if(t>1||t<0){console.error(`[下载视频] 进度异常: ${t}`,this.progressMap.values())}this.progress&&this.progress(t)}cancelDownload(){this.videoSpeed.stopMeasure();if(this.workingXhr!==null){this.workingXhr.forEach(t=>t.abort())}else{logError("Cancel Download Failed: forEach in this.workingXhr not found.")}}downloadFragment(t){const e=[];const s=this.isDash?4*1024*1024:16*1024*1024;let a;if(t.size<=s*6){a=t.size/this.fragmentSplitFactor}else{a=s}let i=0;const n=t=>[...this.progressMap.keys()].indexOf(t)+1;while(i<t.size){const s=Math.min(t.size-1,Math.round(i+a));const o=`bytes=${i}-${s}`;const r=s-i+1;e.push(new Promise((e,s)=>{const a=new XMLHttpRequest;a.open("GET",t.url);a.responseType="arraybuffer";a.withCredentials=false;a.addEventListener("progress",t=>{console.log(`[下载视频] 视频片段${n(a)}下载进度: ${t.loaded}/${r} bytes loaded, ${o}`);this.progressMap.set(a,t.loaded);this.updateProgress()});a.addEventListener("load",()=>{if((""+a.status)[0]==="2"){console.log(`[下载视频] 视频片段${n(a)}下载完成`);e(a.response)}else{s(`视频片段${n(a)}请求失败, response = ${a.status}`)}});a.addEventListener("abort",()=>s("canceled"));a.addEventListener("error",()=>{console.error(`[下载视频] 视频片段${n(a)}下载失败: ${o}`);this.progressMap.set(a,0);this.updateProgress();a.open("GET",t.url);a.setRequestHeader("Range",o);a.send()});a.setRequestHeader("Range",o);this.progressMap.set(a,0);a.send();this.workingXhr.push(a)}));i=Math.round(i+a)+1}return Promise.all(e)}async copyUrl(){const t=this.fragments.map(t=>t.url).reduce((t,e)=>t+"\r\n"+e);GM.setClipboard(t,"text")}async showUrl(){const t=this.fragments.map(t=>`\n<a class="download-link" href="${t.url}">${t.url}</a>\n`).reduce((t,e)=>t+"\r\n"+e);Toast.success(t+`<a class="link" id="copy-link" style="cursor: pointer;margin: 8px 0 0 0;">复制全部</a>`,"显示链接");const e=await SpinQuery.select("#copy-link");e.addEventListener("click",async()=>{await this.copyUrl()})}async exportData(t=false){const e=JSON.stringify([{fragments:this.fragments,title:s(),totalSize:this.fragments.map(t=>t.size).reduce((t,e)=>t+e),referer:document.URL.replace(window.location.search,"")}]);if(t){GM.setClipboard(e,"text")}else{const t=new Blob([e],{type:"text/json"});const a=await this.downloadDanmaku();const i=new n;i.add(`${s()}.json`,t);i.add(s()+"."+this.danmakuOption.toLowerCase(),a);await i.emit(`${s()}.zip`)}}async exportAria2(a=false){if(a){const a=await this.downloadDanmaku();const i=new n;i.add(`${s()}.${this.danmakuOption==="ASS"?"ass":"xml"}`,a);await i.emit();const o=t.aria2RpcOption;const r=this.fragments.map((t,e)=>{let a="";if(this.fragments.length>1){a=" - "+(e+1)}const i=[];if(o.secretKey!==""){i.push(`token:${o.secretKey}`)}i.push([t.url]);i.push({referer:document.URL.replace(window.location.search,""),"user-agent":UserAgent,out:`${s()}${a}${this.extension(t)}`,split:this.fragmentSplitFactor,dir:o.baseDir+o.dir||undefined,"max-download-limit":o.maxDownloadLimit||undefined});const n=encodeURIComponent(`${s()}${a}`);return{params:i,id:n}});const{sendRpc:l}=await e.importAsync("aria2-rpc");await l(r)}else{const t=`\n# Generated by Bilibili Evolved Video Export\n# https://github.com/the1812/Bilibili-Evolved/\n${this.fragments.map((t,e)=>{let a="";if(this.fragments.length>1){a=" - "+(e+1)}return`\n${t.url}\n referer=${document.URL.replace(window.location.search,"")}\n user-agent=${UserAgent}\n out=${s()}${a}${this.extension(t)}\n split=${this.fragmentSplitFactor}\n`.trim()}).join("\n")}\n`.trim();const e=new Blob([t],{type:"text/plain"});const a=await this.downloadDanmaku();const i=new n;i.add(`${s()}.txt`,e);i.add(s()+"."+this.danmakuOption.toLowerCase(),a);await i.emit(`${s()}.zip`)}}extension(t){const e=t||this.fragments[0];const s=[".flv",".mp4",".m4s"].find(t=>e.url.includes(t));if(s){return s}else{console.warn("No extension detected.");return".flv"}}async downloadDanmaku(){if(this.danmakuOption!=="无"){const t=new i(c.cid);await t.fetchInfo();if(this.danmakuOption==="XML"){return t.rawXML}else{const{convertToAss:s}=await e.importAsync("download-danmaku");return s(t.rawXML)}}else{return null}}async download(){this.workingXhr=[];this.progressMap=new Map;this.updateProgress();const t=[];this.videoSpeed.startMeasure();for(const e of this.fragments){const s=await this.downloadFragment(e);t.push(s)}if(t.length<1){throw new Error("下载失败.")}const e=s();const a=new n;t.forEach((s,i)=>{let n;const o=this.fragments[i];if(t.length>1){n=`${e} - ${i+1}${this.extension(o)}`}else{n=`${e}${this.extension(o)}`}a.add(n,new Blob(Array.isArray(s)?s:[s],{type:this.extension(o)===".flv"?"video/x-flv":"video/mp4"}))});const i=await this.downloadDanmaku();a.add(`${s()}.${this.danmakuOption==="ASS"?"ass":"xml"}`,i);await a.emit(e+".zip");this.progress&&this.progress(0);this.videoSpeed.stopMeasure()}}class f{constructor(t){this.lastProgress=0;this.measureInterval=1e3;this.workingDownloader=t}startMeasure(){this.intervalTimer=setInterval(()=>{const t=this.workingDownloader.progressMap?[...this.workingDownloader.progressMap.values()].reduce((t,e)=>t+e,0):0;const e=t-this.lastProgress;if(this.speedUpdate!==undefined){this.speedUpdate(formatFileSize(e)+"/s")}this.lastProgress=t},this.measureInterval)}stopMeasure(){clearInterval(this.intervalTimer)}}async function m(){const t=await SpinQuery.select(()=>(unsafeWindow||window).aid);const e=await SpinQuery.select(()=>(unsafeWindow||window).cid);if(!(t&&e)){return false}c.aid=t;c.cid=e;if(document.URL.includes("bangumi")){c.entity=new r}else if(document.URL.includes("cheese")){const t=document.URL.match(/cheese\/play\/ep([\d]+)/);c.entity=new l(t[1])}else{c.entity=new o}try{d=await h.getAvailableFormats()}catch(t){return false}return true}async function w(){p=d[0];e.applyStyle("downloadVideoStyle");const t=dq("#download-video");t.addEventListener("click",()=>{const t=dq(".download-video");t.classList.toggle("opened");window.scroll(0,0);dq(".gui-settings-mask").click()});t.addEventListener("mouseover",()=>{document.body.insertAdjacentHTML("beforeend",e.import("downloadVideoHtml"));g()},{once:true})}async function g(){let o;const r=new Vue({el:".download-video",components:{VDropdown:()=>e.importAsync("v-dropdown.vue"),VCheckbox:()=>e.importAsync("v-checkbox.vue"),RpcProfiles:()=>e.importAsync("aria2-rpc-profiles.vue")},data:{downloadSingle:true,coverUrl:EmptyImageUrl,aid:c.aid,cid:c.cid,dashModel:{value:t.downloadVideoFormat,items:["flv","dash"]},qualityModel:{value:p.displayName,items:d.map(t=>t.displayName)},danmakuModel:{value:t.downloadVideoDefaultDanmaku,items:["无","XML","ASS"]},codecModel:{value:t.downloadVideoDashCodec,items:["AVC/H.264","HEVC/H.265"]},progressPercent:0,size:"获取大小中",blobUrl:"",episodeList:[],downloading:false,speed:"",batch:false,rpcSettings:t.aria2RpcOption,showRpcSettings:false,busy:false,saveRpcSettingsText:"保存配置",enableDash:t.enableDashDownload,lastDirectDownloadLink:""},computed:{displaySize(){if(typeof this.size==="string"){return this.size}return formatFileSize(this.size)},sizeWarning(){if(typeof this.size==="string"){return false}return this.size>1073741824},selectedEpisodeCount(){return this.episodeList.filter(t=>t.checked).length},dash(){return this.dashModel.value==="dash"}},methods:{close(){this.$el.classList.remove("opened")},danmakuOptionChange(){t.downloadVideoDefaultDanmaku=this.danmakuModel.value},async codecChange(){t.downloadVideoDashCodec=this.codecModel.value;await this.formatChange()},async dashChange(){console.log("dash change");t.downloadVideoFormat=this.dashModel.value;const e=this.dashModel.value;let s=[];if(e==="flv"){s=await h.getAvailableFormats()}else{s=await h.getAvailableDashFormats()}d=s;[p]=e;this.qualityModel.items=s.map(t=>t.displayName);[this.qualityModel.value]=this.qualityModel.items;await this.formatChange()},async formatChange(){console.log("format change");const t=this.getFormat();try{this.size="获取大小中";const e=await t.downloadInfo(this.dash);this.size=e.totalSize}catch(t){this.size="获取大小失败";throw t}},getFormat(){const t=d.find(t=>t.displayName===this.qualityModel.value);if(!t){console.error(`No format found. model value = ${this.qualityModel.value}`);return null}return t},async exportData(t){if(this.busy===true){return}try{this.busy=true;if(!this.downloadSingle){await this.exportBatchData(t);return}const a=this.getFormat();const i=await a.downloadInfo(this.dash);switch(t){case"copyLink":await i.copyUrl();Toast.success("已复制链接到剪贴板.","下载视频",3e3);break;case"showLink":await i.showUrl();break;case"aria2":await i.exportAria2(false);break;case"aria2RPC":await i.exportAria2(true);break;case"copyVLD":await i.exportData(true);Toast.success("已复制VLD数据到剪贴板.","下载视频",3e3);break;case"exportVLD":await i.exportData(false);break;case"ffmpegFragments":if(i.fragments.length<2){Toast.info("当前视频没有分段.","分段列表",3e3)}else{const{getFragmentsList:t}=await e.importAsync("ffmpeg-support");const a=new n;a.add("ffmpeg-files.txt",t(i.fragments.length,s(),i.extension(i.fragments[0])));await a.emit()}break;default:break}}catch(t){logError(t)}finally{this.busy=false}},async exportBatchData(t){const a=this.episodeList;if(a.every(t=>t.checked===false)){Toast.info("请至少选择1集或以上的数量!","批量导出",3e3);return}const o=t=>{const e=a.find(e=>e.cid===t.cid);if(e===undefined){return false}return e.checked};const r=this.getFormat();if(this.danmakuModel.value!=="无"){const t=Toast.info("下载弹幕中...","批量导出");const s=new n;try{if(this.danmakuModel.value==="XML"){for(const t of a.filter(o)){const e=new i(t.cid);await e.fetchInfo();s.add(t.title+".xml",e.rawXML)}}else{const{convertToAss:t}=await e.importAsync("download-danmaku");for(const e of a.filter(o)){const a=new i(e.cid);await a.fetchInfo();s.add(e.title+".ass",await t(a.rawXML))}}await s.emit(this.cid+".danmakus.zip")}catch(t){logError(`弹幕下载失败`)}finally{t.dismiss()}}const l=Toast.info("获取链接中...","批量导出");const c=this.batchExtractor;c.itemFilter=o;let d;try{switch(t){case"aria2":d=await c.collectAria2(r,l,false);await n.single(s(false)+".txt",new Blob([d],{type:"text/plain"}),{ffmpeg:this.ffmpegOption});return;case"aria2RPC":await c.collectAria2(r,l,true);Toast.success(`成功发送了批量请求.`,"aria2 RPC",3e3);return;case"copyVLD":GM.setClipboard(await c.collectData(r,l),{mimetype:"text/plain"});Toast.success("已复制批量vld数据到剪贴板.","批量导出",3e3);return;case"exportVLD":d=await c.collectData(r,l);await n.single(s(false)+".json",new Blob([d],{type:"text/json"}),{ffmpeg:this.ffmpegOption});return;case"ffmpegFragments":{const t=await c.getRawItems(r);const a=new u(r,t[0].fragments);const{getBatchFragmentsList:i}=await e.importAsync("ffmpeg-support");const o=i(t,a.extension());if(!o){Toast.info("所有选择的分P都没有分段.","分段列表",3e3)}else{const t=new n;for(const[e,s]of o.entries()){t.add(e,s)}await t.emit(escapeFilename(`${s(false)}.zip`))}}break;case"ffmpegEpisodes":{const t=await c.getRawItems(r);const s=new u(r,t[0].fragments);const{getBatchEpisodesList:a}=await e.importAsync("ffmpeg-support");const i=a(t,s.extension());const o=new n;o.add("ffmpeg-files.txt",i);await o.emit()}break;default:return}}catch(t){logError(t)}finally{l.dismiss()}},async checkBatch(){const t=["/www.bilibili.com/bangumi","/www.bilibili.com/video/av"];if(!t.some(t=>document.URL.includes(t))){this.batch=false;this.episodeList=[];return}const{BatchExtractor:s}=await e.importAsync("batch-download");if(await s.test()!==true){this.batch=false;this.episodeList=[];return}const a=this.batchExtractor=new s;this.batch=true;this.episodeList=(await a.getItemList()).map((t,e)=>{return{aid:t.aid,cid:t.cid,title:t.title,index:e,checked:true}})},cancelDownload(){if(o){o.cancelDownload()}},async startDownload(){const t=this.getFormat();try{this.downloading=true;const e=await t.downloadInfo(this.dash);e.videoSpeed.speedUpdate=(t=>this.speed=t);e.progress=(t=>{this.progressPercent=Math.trunc(t*100)});o=e;await e.download();this.lastDirectDownloadLink=n.lastPackageUrl}catch(t){if(t!=="canceled"){logError(t)}this.progressPercent=0}finally{this.downloading=false;this.speed=""}},selectAllEpisodes(){this.episodeList.forEach(t=>t.checked=true)},unselectAllEpisodes(){this.episodeList.forEach(t=>t.checked=false)},inverseAllEpisodes(){this.episodeList.forEach(t=>t.checked=!t.checked)},toggleRpcSettings(){this.showRpcSettings=!this.showRpcSettings},saveRpcSettings(){if(this.rpcSettings.host===""){this.rpcSettings.host="127.0.0.1"}if(this.rpcSettings.port===""){this.rpcSettings.port="6800"}t.aria2RpcOption=this.rpcSettings;const e=t.aria2RpcOptionProfiles.find(e=>e.name===t.aria2RpcOptionSelectedProfile);if(e){Object.assign(e,this.rpcSettings);t.aria2RpcOptionProfiles=t.aria2RpcOptionProfiles}this.saveRpcSettingsText="已保存";setTimeout(()=>this.saveRpcSettingsText="保存配置",2e3)},updateProfile(e){t.aria2RpcOption=this.rpcSettings=_.omit(e,"name")}},async mounted(){}});Observer.videoChange(async()=>{r.close();r.batch=false;r.downloadSingle=true;const t=dq("#download-video");const e=await m();t.style.display=e?"flex":"none";if(!e){return}r.aid=c.aid;r.cid=c.cid;try{const t=new a(c.aid);await t.fetchInfo();r.coverUrl=t.coverUrl.replace("http:","https:")}catch(t){r.coverUrl=EmptyImageUrl}r.dashChange();await r.checkBatch()})}return{widget:{content:`\n<button class="gui-settings-flat-button" style="position: relative; z-index: 100;" id="download-video">\n<i class="icon-download"></i>\n<span>下载视频</span>\n</button>`,condition:m,success:w}}}})();