Last active
August 6, 2025 19:24
-
-
Save FoxxMD/f3045b4e2c0afe39631c9550bd131133 to your computer and use it in GitHub Desktop.
Qbittorrent get total size, downloaded, uploaded, and overall ratio (WebUI only)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Tested on qBittorrent 4.6.1 | |
// | |
// 1. open qbittorent web interface | |
// 1a. (optionally) filter to just the torrents that should be totalled | |
// 1b. MAY need to ensure any/all columns to be totalled are enabled -> Size, Downloaded, Uploaded | |
// 2. open developer tools | |
// 3. copy and paste the *entirety* of the below code into the developer tools console and hit enter | |
// | |
// Will print totals in MiB/GiB/TiB for: | |
// * Size + Min/Max/Avg | |
// * Downloaded + % of Size + Min/Max/Avg | |
// * Uploaded + % of Size + Min/Max/Avg | |
// and Ratio + Min/Max/Avg | |
(function() { | |
const sizeIndex = Array.from(document.querySelector('tr.dynamicTableHeader').childNodes).findIndex(x => x.title === 'Size'); | |
const downloadedIndex = Array.from(document.querySelector('tr.dynamicTableHeader').childNodes).findIndex(x => x.title === 'Downloaded'); | |
const uploadedIndex = Array.from(document.querySelector('tr.dynamicTableHeader').childNodes).findIndex(x => x.title === 'Uploaded'); | |
const ratioIndex = Array.from(document.querySelector('tr.dynamicTableHeader').childNodes).findIndex(x => x.title === 'Ratio'); | |
const tibThreshold = 1048576; | |
if(sizeIndex === -1 && downloadedIndex === -1 && uploadedIndex === -1) { | |
console.warn('Must have at least one of the following columns enabled to compute some statistics: Size, Downloaded, Uploaded'); | |
return; | |
} | |
const getMibVal = (valueStr, unit) => { | |
const val = parseFloat(valueStr); | |
if(val === 0) { | |
return 0; | |
} | |
switch (unit.toLocaleLowerCase()) { | |
case 'tib': | |
return val * 1024 * 1024; | |
case 'gib': | |
return val * 1024; | |
case 'mib': | |
return val; | |
case 'kib': | |
return val / 1024; | |
case 'B': | |
return val / 1024 / 1024; | |
} | |
} | |
const getTotalBreakdown = (val) => { | |
return [ | |
`${((val / 1024) / 1024).toLocaleString('en', {maximumFractionDigits: 2})} TiB`, | |
`${(val / 1024).toLocaleString('en', {maximumFractionDigits: 2})} GiB`, | |
`${val.toLocaleString('en', {maximumFractionDigits: 2})} MiB`, | |
].join(' == '); | |
} | |
const getHumanSize = (valMib) => { | |
if(valMib === 0) { | |
return '0 B'; | |
} | |
if(valMib < 1) { | |
return `${(valMib / 1024).toLocaleString('en', {maximumFractionDigits: 2})} KiB` | |
} | |
if(valMib >= tibThreshold) { | |
return `${(valMib / 1024 / 1024).toLocaleString('en', {maximumFractionDigits: 2})} TiB` | |
} | |
if(valMib >= 1024) { | |
return `${(valMib / 1024).toLocaleString('en', {maximumFractionDigits: 2})} GiB` | |
} | |
return `${(valMib).toLocaleString('en', {maximumFractionDigits: 2})} MiB` | |
} | |
let mibSize = sizeIndex !== -1 ? 0 : null; | |
let sizeMinMax = [Infinity,0]; | |
let ratio = ratioIndex !== -1 ? 0 : null; | |
let ratioMinMax = [Infinity,0]; | |
let mibDl = downloadedIndex !== -1 ? 0 : null; | |
let dlMinMax = [Infinity,0]; | |
let mibUl = uploadedIndex !== -1 ? 0 : null; | |
let ulMinMax = [Infinity,0]; | |
const elms = document.querySelectorAll('div#torrentsTableDiv tr.torrentsTableContextMenuTarget'); | |
const torNum = elms.length; | |
elms.forEach((row) => { | |
if(mibSize !== null) { | |
const [str, unit] = row.childNodes[sizeIndex].title.split(' '); | |
const val = getMibVal(str, unit); | |
mibSize += val; | |
sizeMinMax = [Math.min(val, sizeMinMax[0]), Math.max(val, sizeMinMax[1])]; | |
} | |
if(mibDl !== null) { | |
const [str, unit] = row.childNodes[downloadedIndex].title.split(' '); | |
const val = getMibVal(str, unit); | |
mibDl += val; | |
dlMinMax = [Math.min(val, dlMinMax[0]), Math.max(val, dlMinMax[1])]; | |
} | |
if(mibDl !== null) { | |
const [str, unit] = row.childNodes[uploadedIndex].title.split(' '); | |
const val = getMibVal(str, unit); | |
mibUl += val; | |
ulMinMax = [Math.min(val, ulMinMax[0]), Math.max(val, ulMinMax[1])]; | |
} | |
if(ratio !== null) { | |
const val = parseFloat(row.childNodes[ratioIndex].title); | |
ratio += val | |
ratioMinMax = [Math.min(val, ratioMinMax[0]), Math.max(val, ratioMinMax[1])]; | |
} | |
}); | |
if(mibSize !== null) { | |
console.log(`${'Size'.padEnd(11,' ')}: ${getTotalBreakdown(mibSize)} | Min ${getHumanSize(sizeMinMax[0])} | Max ${getHumanSize(sizeMinMax[1])} | Avg ${getHumanSize(mibSize / torNum)}`); | |
} | |
if(mibDl !== null) { | |
const parts = [`${'Downloaded'.padEnd(11,' ')}: ${getTotalBreakdown(mibDl)}`]; | |
if(mibSize !== null) { | |
parts.push(`${((mibDl / mibSize) * 100).toLocaleString('en', {maximumFractionDigits: 2})}% of Size`); | |
} | |
parts.push(`Min ${getHumanSize(dlMinMax[0])} | Max ${getHumanSize(dlMinMax[1])} | Avg ${getHumanSize(mibDl / torNum)}`); | |
console.log(parts.join(' | ')); | |
} | |
if(mibUl !== null) { | |
const parts = [`${'Uploaded'.padEnd(11,' ')}: ${getTotalBreakdown(mibUl)}`]; | |
if(mibSize !== null) { | |
parts.push(`${((mibUl / mibSize) * 100).toLocaleString('en', {maximumFractionDigits: 2})}% of Size`); | |
} | |
parts.push(`Min ${getHumanSize(ulMinMax[0])} | Max ${getHumanSize(ulMinMax[1])} | Avg ${getHumanSize(mibUl / torNum)}`); | |
console.log(parts.join(' | ')); | |
} | |
if(mibDl !== null && mibUl !== null) { | |
const parts = [ | |
`${'Ratio'.padEnd(11,' ')}: ${((mibUl / mibDl)).toLocaleString('en', {maximumFractionDigits: 2})}`, | |
`Min ${ratioMinMax[0]} | Max ${ratioMinMax[1]} | Avg ${(ratio / torNum).toLocaleString('en', {maximumFractionDigits: 2})}` | |
]; | |
console.log(parts.join(' | ')); | |
} | |
})(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello! I just revised the script from https://greasyfork.org/en/scripts/483775-calculate-qbittorrent-selected-torrents-size to work with Qbitorrent latest version (5.x.x). If you access the webui remotely, make sure to adjust the @match to your address. Hope this helps someone.
// ==UserScript==
// @name qBittorrent Selected Size (torrentsTableDiv fix)
// @namespace http://tampermonkey.net/
// @Version 1.0
// @description Show total size of selected torrents in the footer
// @match http://localhost:8080/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
'use strict';
const FOOTER_ID = 'tmSelectedSizeTotal';
const LABEL = 'Selected Torrents Total Size:';
let sizeColIndex = null;
let lastSig = '';
function ensureFooter() {
const row = document.querySelector('#desktopFooter > table > tbody > tr');
if (!row) { setTimeout(ensureFooter, 500); return; }
if (!document.getElementById(FOOTER_ID)) {
const td = document.createElement('td');
td.id = FOOTER_ID;
td.textContent =
${LABEL} 0.00 MiB
;const sep = document.createElement('td');
sep.className = 'statusBarSeparator';
row.insertBefore(td, row.firstElementChild);
row.insertBefore(sep, td.nextSibling);
}
}
// Find the "Size" column index using the header th.column_size
function findSizeColIndex() {
// Header and body are in separate tables; just grab any "th.column_size"
const th = document.querySelector('th.column_size');
if (!th) return null;
const tr = th.closest('tr');
if (!tr) return null;
const idx = Array.from(tr.children).indexOf(th);
return (idx >= 0) ? idx : null;
}
function toBytes(text) {
const t = text.replace(/\u00A0/g,' ').replace(/,/g,'').trim(); // NBSP/commas
const m = t.match(/^([\d.]+)\s*(B|KiB|MiB|GiB|TiB)$/i);
if (!m) return null;
const v = parseFloat(m[1]);
const u = m[2].toUpperCase();
const mult = (u==='B')?1:(u==='KIB')?1024:(u==='MIB')?10242:(u==='GIB')?10243:1024**4;
return v * mult;
}
function fmt(bytes) {
if (bytes < 1024) return
${bytes.toFixed(2)} B
;if (bytes < 10242) return
${(bytes/1024).toFixed(2)} KiB
;if (bytes < 10243) return
${(bytes/1024**2).toFixed(2)} MiB
;if (bytes < 1024**4) return
${(bytes/1024**3).toFixed(2)} GiB
;return
${(bytes/1024**4).toFixed(2)} TiB
;}
function update() {
ensureFooter();
}
function startObservers() {
const tableDiv = document.getElementById('torrentsTableDiv');
if (!tableDiv) { setTimeout(startObservers, 500); return; }
}
window.addEventListener('load', () => { ensureFooter(); startObservers(); });
})();