// ==UserScript== // @name bilibili download // @name:zh bilibili 视频下载 // @namespace http://tampermonkey.net/ // @version 0.3.2 // @description bilibili视频下载 可以切换是否合并音视频 显示下载进度 // @author You // @match https://www.bilibili.com/video/* // @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com // @require https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; // Your code here... const CONFIG = "tampermonkey_config"; const changeDownloadMode = (userConfig = { merge: true }) => { let config = getStorageItem(CONFIG) || {}; localStorage.setItem( CONFIG, JSON.stringify({ ...config, ...userConfig, }) ); }; const getStorageItem = (key) => { let item = localStorage.getItem(key); if (item) { try { item = JSON.parse(item); } catch (error) { item = null; console.log(error); } } return item; }; const getPlayInfo = async () => { const res = await fetch(window.location.href); const str = await res.text(); return JSON.parse(str.match(/window.__playinfo__=([\d\D]+?)<\/script>/)[1]); }; const initFFmpeg = () => { const { createFFmpeg } = FFmpeg; let ffmpeg = null; if (ffmpeg === null) { ffmpeg = createFFmpeg({ log: false }); } if (!ffmpeg.isLoaded()) { ffmpeg.load(); } return ffmpeg; }; const fetchFile = async (url) => { const res = await fetch(url); const reader = res.body.getReader(); const contentLength = +res.headers.get("Content-Length"); if (!contentLength) { const data = await res.arrayBuffer(); return new Uint8Array(data); } let receivedLength = 0; let chunks = []; while (true) { const { done, value } = await reader.read(); if (done) { break; } chunks.push(value); receivedLength += value.length; console.log( document.title = (receivedLength / contentLength * 100).toFixed(2) + "%" ); } document.title = '下载完成!'; const config = getStorageItem(CONFIG); if (config?.merge === false) { return new Blob(chunks); } let chunksAll = new Uint8Array(receivedLength); let position = 0; for (let chunk of chunks) { chunksAll.set(chunk, position); position += chunk.length; } return chunksAll; }; const transcode = async (video, audio) => { if (!ffmpeg.isLoaded()) { await ffmpeg.load(); } ffmpeg.FS("writeFile", "video.mp4", video); ffmpeg.FS("writeFile", "audio.mp3", audio); // ffmpeg -i video.mp4 -i audio.mp3 -c copy -map 0:v:0 -map 1:a:0 output.mp4 await ffmpeg.run( ...`-i video.mp4 -i audio.mp3 -c copy -map 0:v:0 -map 1:a:0 output.mp4`.split( " " ) ); const data = ffmpeg.FS("readFile", "output.mp4"); return new Blob([data.buffer], { type: "video/mp4" }); }; const download = async (data, fileName = "download.mp4") => { const link = window.URL.createObjectURL(data); const a = document.createElement("a"); a.href = link; a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(link); }; const startDownload = async (videoUrl, audioUrl, fileName) => { const config = getStorageItem(CONFIG); const audio = await fetchFile(audioUrl); const video = await fetchFile(videoUrl); if (config?.merge === false) { download(audio, `audio_${fileName}`); download(video, `video_${fileName}`); } else { console.log( `%c merge audio and video, if you do not want to merge, just need download single audio and video, please use { changeDownloadMode({merge: false}) } to change`, "background: #222; color: #bada55" ); const data = await transcode(video, audio); download(data, fileName); } }; const addDownloadBtn = () => { document .querySelectorAll( ".bpx-player-ctrl-quality-menu .bpx-player-ctrl-quality-menu-item" ) ?.forEach((ele) => { if (ele.children?.length === 1) { const div = document.createElement("div"); div.textContent = "下载"; div.className = "bpx-player-ctrl-quality-badge bpx-player-ctrl-quality-badge-bigvip"; div.addEventListener("click", async (e) => { e.stopPropagation(); const fileName = document.querySelector(".video-title.tit")?.textContent; const playinfo = await getPlayInfo(); const dash = playinfo.data.dash; const quality = ele.textContent; const video = dash.video.find((i) => quality.includes(i.height)) || dash.video.sort((a, b) => b.height - a.height)[0]; const audio = dash.audio[0]; startDownload( video.baseUrl, audio.baseUrl, `${fileName}_${Date.now()}.mp4` ); }); ele.appendChild(div); } }); }; window.changeDownloadMode = changeDownloadMode; const ffmpeg = initFFmpeg(); document.querySelector("video").addEventListener("play", addDownloadBtn); })();