userscripts/AnimeBytes/ab-mediainfo.user.js

867 lines
31 KiB
JavaScript

// ==UserScript==
// @name AnimeBytes Mediainfo Improvements
// @author WeebDataHoarder
// @version 1.0
// @downloadURL https://git.gammaspectra.live/WeebDataHoarder/userscripts/raw/branch/master/AnimeBytes/ab-mediainfo.user.js
// @updateURL https://git.gammaspectra.live/WeebDataHoarder/userscripts/raw/branch/master/AnimeBytes/ab-mediainfo.user.js
// @description AnimeBytes Mediainfo Improvements. Adds several listing and matching releases against mediainfo utilities. MIT license
// @match https://animebytes.tv/torrents.php?*id=*
// @icon http://animebytes.tv/favicon.ico
// @run-at document-end
// ==/UserScript==
function parseMediaInfo(text){
let ob = {};
let currentKey = null;
let currentOb = null;
try{
text.split("\n").forEach((line) => {
const cleanLine = line.trim();
if(cleanLine === ""){
return;
}else if(!cleanLine.includes(":")){
if(currentOb !== null && Object.keys(currentOb).length === 0){
//Nothing added, error, invalid mediainfo
throw ("Invalid key " + currentKey);
}
currentKey = cleanLine.toLowerCase();
let listMatch = currentKey.match(/^(?<kind>(video|audio|text))( #)?(?<number>[0-9]*)$/i);
currentOb = {};
if(listMatch !== null){
let k = listMatch.groups.kind;
if(!(k in ob)){
ob[k] = [];
}
ob[k].push(currentOb);
}else{
ob[currentKey] = currentOb;
}
}else if(currentOb !== null){
const matches = cleanLine.match(/^(?<key>((?!\s:).)+)[\s]+:(?<value>.+)$/);
if(matches !== null){
let key = matches.groups.key.trim().toLowerCase().replace(/[ \/]/g, "_").replace(/[(),]/g, "");
currentOb[key] = matches.groups.value.trim();
}else{
throw "Invalid entry";
}
}else{
throw "Invalid state";
}
});
}catch (e){
console.log(text);
console.log(e);
return null;
}
return ob;
}
function getLineTagEntry(v, legacy){
if(typeof v === 'object'){
return legacy ? v.legacy : (v.name/* + ("lossless" in v ? " (Lossless)" : "")*/);
}
return v;
}
function getAudioChannels(a){
if(a === 3){
return "2.1";
}else if(a === 5){
return "4.1";
}else if(a === 6){
return "5.1";
}else if(a === 8){
return "7.1";
}
return a + ".0";
}
function getComparisonLine(tags){
let entries = [];
entries.push(getLineTagEntry(tags.source, true));
entries.push(getLineTagEntry(tags.container, true) + ("region" in tags ? " (" + tags.region + ")" : ""));
if("aspectRatio" in tags){
entries.push(tags.aspectRatio);
}
if("videoCodec" in tags){
entries.push(getLineTagEntry(tags.videoCodec, true));
}
entries.push(tags.resolution);
entries.push(getLineTagEntry(tags.audioCodec, true) + " " + getAudioChannels(tags.audioChannels));
if(tags.audioCount > 1){
entries.push("Dual Audio");
}
entries.push(("subtitleType" in tags ? getLineTagEntry(tags.subtitleType, true) : "RAW") + ("group" in tags ? " (" + tags.group + ")" : ""));
return entries.join(" | ");
}
function getEntryLine(tags){
let entries = [];
entries.push(getLineTagEntry(tags.source, false) + ("sourceName" in tags ? " (" + tags.sourceName + ")" : ""));
entries.push(getLineTagEntry(tags.container, false) + ("region" in tags ? " (" + tags.region + ")" : ""));
entries.push(getLineTagEntry(tags.videoCodec, false) + ("videoEncoder" in tags ? " (" + tags.videoEncoder + ("videoCRF" in tags ? " " + tags.videoCRF : "") + ")" : ""));
if("videoHDR" in tags){
entries.push(getLineTagEntry(tags.videoHDR, false));
}
entries.push(tags.resolution);
entries.push(getLineTagEntry(tags.audioCodec, false) + " " + getAudioChannels(tags.audioChannels));
if(tags.audioCount > 1){
entries.push("Dual Audio");
}
entries.push(("subtitleCodec" in tags ? tags.subtitleCodec + " " : "") + ("subtitleType" in tags ? getLineTagEntry(tags.subtitleType, false) : "RAW") + ("group" in tags ? " (" + tags.group + ")" : ""));
return entries.join(" | ");
}
const allowedVideoTypes = [
"Movie",
"OVA",
"ONA",
"TV Series",
"TV Special",
"DVD Special",
"BD Special"
];
const knownSources = [
"TV",
"DVD",
"DVD5",
"DVD9",
"Blu-ray",
"UHD Blu-ray",
"HD DVD",
"VHS",
"LD",
"Web"
];
const knownContainers = [
"AVI",
"MKV",
"MP4",
"OGM",
"WMV",
"MPG",
"ISO",
"VOB",
"VOB IFO",
"TS",
"M2TS",
"FLV",
"RMVB"
];
const knownVideoCodecs = [
"h264",
"h264 10-bit",
"h265",
"h265 10-bit",
"XviD",
"DivX",
"WMV",
"MPEG-1/2",
"VC-1",
"MPEG-TS",
"DVD5",
"DVD9",
"RealVideo",
"VP6",
"VP9",
"AV1"
];
const knownAudioCodecs = [
"MP3",
"Vorbis",
"Opus",
"AAC",
"AC3",
"TrueHD",
"DTS",
"DTS-ES",
"FLAC",
"PCM",
"WMA",
"MP2",
"WAV",
"DTS-HD",
"DTS-HD MA",
"RealAudio"
];
const audioCodecs = [
{
name: "E-AC3",
match: {
codec_id: "A_EAC3",
number_of_dynamic_objects: null
},
legacy: "AC3"
},
{
name: "E-AC3 Atmos",
match: {
codec_id: "A_EAC3",
number_of_dynamic_objects: /^[0-9]+$/
},
legacy: "AC3"
},
{
name: "TrueHD Atmos",
match: {
codec_id: "A_TRUEHD",
number_of_dynamic_objects: /^[0-9]+$/
},
legacy: "TrueHD",
lossless: true
},
{
name: "DTS:X MA",
match: {
codec_id: "A_DTS",
format: "DTS XLL X",
channels_original: "Object Based"
},
legacy: "DTS-HD MA",
lossless: true
},
{
name: "DTS-HD MA",
match: {
codec_id: "A_DTS",
format: "DTS XLL",
channels_original: null,
},
legacy: "DTS-HD MA",
lossless: true
}
];
const videoCodecs = [
{
name: "h264 10-bit",
match: {
codec_id: "V_MPEG4/ISO/AVC",
bit_depth: "10 bits",
chroma_subsampling: /^4:2:0/
},
legacy: "h264 10-bit"
},
{
name: "h264 10-bit 4:4:4",
match: {
codec_id: "V_MPEG4/ISO/AVC",
bit_depth: "10 bits",
chroma_subsampling: /^4:4:4/
},
legacy: "h264 10-bit"
},
{
name: "h264",
match: {
codec_id: "V_MPEG4/ISO/AVC",
bit_depth: "8 bits",
chroma_subsampling: /^4:2:0/
},
legacy: "h264"
},
{
name: "AV1",
match: {
codec_id: "V_AV1",
bit_depth: "8 bits",
chroma_subsampling: /^4:2:0/
},
legacy: "AV1"
},
{
name: "AV1 10-bit",
match: {
codec_id: "V_AV1",
bit_depth: "10 bits",
chroma_subsampling: /^4:2:0/
},
legacy: "AV1"
},
{
name: "h265 12-bit",
match: {
codec_id: "V_MPEGH/ISO/HEVC",
bit_depth: "12 bits",
chroma_subsampling: /^4:2:0/,
},
legacy: "h265 12-bit"
},
{
name: "h265 12-bit 4:2:0",
match: {
codec_id: "V_MPEGH/ISO/HEVC",
bit_depth: "12 bits",
chroma_subsampling: /^4:2:2/,
},
legacy: "h265 12-bit"
},
{
name: "h265 12-bit 4:4:4",
match: {
codec_id: "V_MPEGH/ISO/HEVC",
bit_depth: "12 bits",
chroma_subsampling: /^4:4:4/,
},
legacy: "h265 12-bit"
},
{
name: "h265 10-bit",
match: {
codec_id: "V_MPEGH/ISO/HEVC",
bit_depth: "10 bits",
chroma_subsampling: /^4:2:0/
},
legacy: "h265 10-bit"
},
{
name: "h265 10-bit 4:2:@",
match: {
codec_id: "V_MPEGH/ISO/HEVC",
bit_depth: "10 bits",
chroma_subsampling: /^4:2:0/
},
legacy: "h265 10-bit"
},
{
name: "h265 10-bit 4:4:4",
match: {
codec_id: "V_MPEGH/ISO/HEVC",
bit_depth: "10 bits",
chroma_subsampling: /^4:4:4/
},
legacy: "h265 10-bit"
},
{
name: "h265",
match: {
codec_id: "V_MPEGH/ISO/HEVC",
bit_depth: "8 bits",
chroma_subsampling: /^4:2:0/
},
legacy: "h265"
},
];
const torrentType = document.querySelector("a.scaledImg > img").getAttribute("title");
if(allowedVideoTypes.indexOf(torrentType.replace("Live Action ", "")) !== -1){
let episodeCount = 0;
document.querySelectorAll("ul.stats > li").forEach((item) => {
let strongItem = item.querySelector("strong");
if(strongItem !== null && strongItem.textContent === "Episodes:"){
episodeCount = parseInt(item.querySelector("a").textContent);
}
});
if(episodeCount === 0 && (torrentType === "Movie")){
episodeCount = 1;
}
let torrentListing = [];
document.querySelectorAll(".group_torrent").forEach((item) => {
const linkSection = item.querySelector("td");
const downloadLink = item.querySelector("a[title^='Download']");
const [torrentEntry] = Array.from(linkSection.querySelectorAll("a")).slice(-1);
const torrentId = parseInt(downloadLink.getAttribute("href").match(/\/torrent\/(?<torrent_id>[0-9]+)\/download/).groups.torrent_id);
let tags = {
audioCount: 1,
freeleech: torrentEntry.querySelector("img") !== null,
snatched: torrentEntry.textContent.match(/ - Snatched/) !== null
};
torrentEntry.textContent.replace("»", "").replace(" - Snatched", "").split(" | ").forEach((t) => {
let tagEntry = t.trim();
if(tagEntry === ""){
return;
}
let match = null;
if(!("source" in tags) && knownSources.indexOf(tagEntry) !== -1){
tags.source = tagEntry;
}else if(!("container" in tags) && knownContainers.some((i) => tagEntry.startsWith(i))){
if((match = tagEntry.match(/^(?<container>[^(]+) \((?<region>[^)]+)\)$/)) !== null){
tags.container = match.groups.container;
tags.region = match.groups.region;
}else{
tags.container = tagEntry;
}
}else if(!("videoCodec" in tags) && knownVideoCodecs.some((i) => tagEntry.startsWith(i))){
tags.videoCodec = tagEntry;
}else if(!("audioCodec" in tags) && knownAudioCodecs.some((i) => tagEntry.startsWith(i))){
match = tagEntry.match(/^(?<codec>.+) (?<channels>[0-9.]+)$/)
tags.audioCodec = match.groups.codec;
tags.audioChannels = match.groups.channels.split(".").reduce((p, c) => parseInt(p) + parseInt(c));
}else if(tagEntry.match(/^[0-9]+:[0-9]+$/) !== null){
tags.aspectRatio = tagEntry;
}else if(tagEntry.match(/^[0-9]+x[0-9]+$/) !== null || tagEntry.match(/^(720p|1080p|1080i|4K)$/) !== null){
tags.resolution = tagEntry;
}else if(tagEntry === "Dual Audio"){
tags.audioCount = 2;
}else if((match = tagEntry.match(/^(?<kind>(RAW|Hardsubs|Softsubs))( \()?(?<group>[^)]*)(\))?$/)) !== null){
if(match.groups.kind !== "RAW"){
tags.subtitleType = match.groups.kind;
}
if(match.groups.group !== ""){
tags.group = match.groups.group;
}
}else{
//console.log(tagEntry);
}
});
let torrent = {
id: torrentId,
tags: tags,
elements: {
item: item,
entry: torrentEntry,
link: downloadLink,
},
};
const dataSection = document.getElementById("torrent_" + torrentId);
if(dataSection !== null){
const descriptionSection = document.getElementById(torrentId + "_description");
const filelistSection = document.getElementById(torrentId + "_filelist");
const mediaInfoSection = document.getElementById(torrentId + "_mediainfo");
if(descriptionSection !== null){
torrent.elements.description = descriptionSection.querySelector("blockquote");
torrent.description = torrent.elements.description.textContent;
}
if(filelistSection !== null){
torrent.elements.filelist = filelistSection.querySelector("table");
torrent.filelist = [];
torrent.elements.filelist.querySelectorAll("tr").forEach((tableRow) => {
if(tableRow.querySelector("td > strong") !== null){
//Skip header
return;
}
torrent.filelist.push({
path: tableRow.querySelector("td").textContent,
size: tableRow.querySelector("td:last-of-type").textContent //TODO: convert back to bytes
})
});
}
if(mediaInfoSection !== null){
torrent.elements.mediainfo = mediaInfoSection;
let m_list = [];
torrent.elements.mediainfo.querySelectorAll("pre").forEach((mediainfoItem) => {
let m = parseMediaInfo(mediainfoItem.textContent);
if(m !== null){
m_list.push(m);
}
});
if(m_list.length > 0){
torrent.mediainfo = m_list;
}
}
torrentListing.push(torrent);
}
});
console.log(torrentListing);
torrentListing.forEach((torrent) => {
try {
if("mediainfo" in torrent && torrent.mediainfo.length > 0){
let mediainfo = torrent.mediainfo[0];
let japaneseAudio = null;
let otherAudio = null;
let defaultAudio = null;
let englishSubs = null;
let defaultSubs = null;
let tags = {
};
let warnings = {
general: [],
audio: [],
video: [],
text: []
}
switch(mediainfo.general.format){
case "Matroska":
tags.container = "MKV";
break;
case "BDAV":
tags.container = "M2TS";
break;
}
let fileName = null;
if("mediainfo.general.complete_name" in mediainfo.general){
[fileName] = mediainfo.general.complete_name.split("/").slice(-1);
}else{
fileName = torrent.filelist[0].path;
}
//handle file tags
fileName.split(".").forEach((p) => {
if(p === "NF"){
tags.sourceName = "Netflix";
}else if(p === "ATVP"){
tags.sourceName = "Apple TV+";
}else if(p === "AMZN"){
tags.sourceName = "Amazon Video";
}else if(p === "HDR"){
tags.videoHDR = "HDR";
}else if(p === "HDR10"){
tags.videoHDR = "HDR10";
}else if(p === "DV"){
tags.videoHDR = "HDR DV";
}
});
fileName.split(" ").forEach((p) => {
if(p === "HDR"){
tags.videoHDR = "HDR";
}else if(p === "HDR10"){
tags.videoHDR = "HDR10";
}else if(p === "DV"){
tags.videoHDR = "HDR DV";
}
});
mediainfo.audio.forEach((audio) => {
const isDefault = audio.default === "Yes";
if(isDefault){
defaultAudio = audio;
}
if("language" in audio && audio.language !== "Japanese" && audio.language !== "Unknown" && isDefault && torrentType.match(/Live Action/) === null){
warnings.audio.push("Default audio is not in Japanese");
}
if(audio.language === "Japanese"){
if(japaneseAudio === null){ // Pick first
japaneseAudio = audio;
}else{
const oldChannels = parseInt(japaneseAudio.channels.replace(" channels", ""));
const channels = parseInt(audio.channels.replace(" channels", ""));
if(channels > oldChannels){
japaneseAudio = audio;
}
}
}else if(mediainfo.audio.length === 1 && (audio.language === "Unknown" || !("language" in audio))){
japaneseAudio = audio;
}else{
if(otherAudio === null){ // Pick first
otherAudio = audio;
}else{
const oldChannels = parseInt(otherAudio.channels.replace(" channels", ""));
const channels = parseInt(audio.channels.replace(" channels", ""));
if(channels > oldChannels){
otherAudio = audio;
}
}
}
});
if(japaneseAudio !== otherAudio && otherAudio !== null && japaneseAudio !== null){
tags.audioCount = mediainfo.audio.length;
}
if("text" in mediainfo){
mediainfo.text.forEach((text) => {
const isSignsEntry = ("title" in text) ? text.title.match(/sign/i) !== null : false;
const isDefault = text.default === "Yes";
if(isSignsEntry && isDefault){
warnings.text.push("Default subtitle is a Signs track");
}
if(isDefault){
defaultSubs = text;
}
if(!isSignsEntry && text.language === "English"){
if(englishSubs === null){
englishSubs = text;
}else{
if(text.codec_id === "S_TEXT/ASS" && englishSubs.codec_id !== "S_TEXT/ASS"){
englishSubs = text;
}
}
}else if(mediainfo.text.length === 1 && (text.language === "Unknown" || !("language" in text))){
englishSubs = text;
}
});
}
let audio = japaneseAudio !== null ? japaneseAudio : (defaultAudio !== null ? defaultAudio : otherAudio);
if(audio !== null){
tags.audioChannels = parseInt(audio.channels.replace(" channels", ""));
audioCodecs.every((codec) => {
let match = true;
for(const [key, value] of Object.entries(codec.match)){
if(value === null){
if(key in audio){
match = false;
console.log("audio[" + key + "] exists but value was null");
break;
}
continue;
}
if(!(key in audio)){
match = false;
console.log("audio[" + key + "] does not exist but value was not null");
break;
}else if (value instanceof RegExp && audio[key].match(value) === null){
match = false;
console.log("audio[" + key + "] ("+ audio[key] +") ~= " + value.toString());
break;
}else if (!(value instanceof RegExp) && audio[key] !== value){
match = false;
console.log("audio[" + key + "] ("+ audio[key] +") !== " + value);
break;
}
}
if(match){
tags.audioCodec = codec;
}
return !match;
});
}
let video = mediainfo.video[0];
if(video !== null){
videoCodecs.every((codec) => {
let match = true;
for(const [key, value] of Object.entries(codec.match)){
if(value === null){
if(key in video){
match = false;
break;
}
continue;
}
if(!(key in video)){
match = false;
break;
}else if (value instanceof RegExp && video[key].match(value) === null){
match = false;
break;
}else if (!(value instanceof RegExp) && video[key] !== value){
match = false;
break;
}
}
if(match){
tags.videoCodec = codec;
}
return !match;
});
let width = parseInt(video.width.replace(/[^0-9]/g, ""));
let height = parseInt(video.height.replace(/[^0-9]/g, ""));
if(width === 1280 && height > 480 && height <= 720){
tags.resolution = "720" + ((!("scan_type" in video) || video.scan_type === "Progressive") ? "p" : "i");
}else if(width === 1920 && height > 720 && height <= 1080){
tags.resolution = "1080" + ((!("scan_type" in video) || video.scan_type === "Progressive") ? "p" : "i");
}else if(width === 3840 && height > 1080 && height <= 2160){
tags.resolution = "4K";
}else{
tags.resolution = width + "x" + height;
}
if("color_primaries" in video && video.color_primaries === "BT.2020"){
tags.videoHDR = "HDR";
}
if("hdr_format" in video){
if(video.hdr_format.match(/Dolby Vision/) !== null){
tags.videoHDR = "HDR DV";
}else if(video.hdr_format.match(/SMPTE ST 2086/) !== null && video.hdr_format.match(/HDR10/) !== null){
tags.videoHDR = "HDR10";
}else if(video.hdr_format.match(/SMPTE ST 2094-40/) !== null && video.hdr_format.match(/HDR10/) !== null){
tags.videoHDR = "HDR10+";
}else if(video.hdr_format.match(/SL-HDR1/) !== null){
tags.videoHDR = "SL-HDR1";
}else if(video.hdr_format.match(/SL-HDR2/) !== null){
tags.videoHDR = "SL-HDR2";
}
}
if("writing_library" in video){
if(video.writing_library.match(/x265/) !== null){
tags.videoEncoder = "x265";
}else if(video.writing_library.match(/x264/) !== null){
tags.videoEncoder = "x264";
}else if(video.writing_library.match(/_nvenc/) !== null){
tags.videoEncoder = "NVENC";
warnings.video.push("Found NVENC hardware-encoded stream");
}else if(video.writing_library.match(/_qsv/) !== null){
tags.videoEncoder = "QuickSync";
warnings.video.push("Found QuickSync hardware-encoded stream");
}else if(video.writing_library.match(/_vaapi/) !== null){
tags.videoEncoder = "VAAPI";
warnings.video.push("Found VAAPI hardware-encoded stream");
}else if(video.writing_library.match(/[^0-9A-F]/) !== null){
[tags.videoEncoder] = video.writing_library.split(" ");
}
}
if("encoding_settings" in video){
let match = null;
if((match = video.encoding_settings.match(/crf=(?<crf>[0-9.]+)/)) !== null){
tags.videoCRF = parseFloat(match.groups.crf).toFixed(1);
}
if(video.encoding_settings.match(/rc=2pass/) !== null){
tags.videoCRF = "2pass";
}
if((match = video.encoding_settings.match(/profile=(?<profile>baseline|main)/)) !== null){
warnings.video.push("Found encode using profile=" + match.groups.profile + " on encoding settings");
}
if(video.encoding_settings.match(/cabac=0/) !== null){
warnings.video.push("Found encode with CABAC disabled");
}
if(video.encoding_settings.match(/mbtree=0/) !== null){
warnings.video.push("Found encode with mbtree disabled");
}
if(video.encoding_settings.match(/bframes=0/) !== null){
warnings.video.push("Found encode with bframes disabled");
}
if(video.encoding_settings.match(/me=dia/) !== null){
warnings.video.push("Found encode with me=dia");
}
}
}
let text = englishSubs !== null ? englishSubs : (defaultSubs !== null ? defaultSubs : (("text" in mediainfo && mediainfo.text.length > 0) ? mediainfo.text[0] : null));
if(text !== null){
tags.subtitleCodec = text.format;
tags.subtitleTitle = "title" in text;
tags.subtitleType = "Softsubs";
}
for(const [key, value] of Object.entries(torrent.tags)){
if(!(key in tags)){
tags[key] = value;
}
}
let oldTagLine = getComparisonLine(torrent.tags);
let newTagLine = getComparisonLine(tags);
if(newTagLine !== oldTagLine){
warnings.general.push("Tag mismatch:\nold: " + oldTagLine + " !=\nnew: " + newTagLine);
}
if(tags.freeleech){
let fl = torrent.elements.entry.querySelector("img");
torrent.elements.entry.textContent = getEntryLine(tags) + " | ";
torrent.elements.entry.append(fl);
}else{
torrent.elements.entry.textContent = getEntryLine(tags);
}
if(tags.snatched){
torrent.elements.entry.append(" - Snatched");
}
let warningText = "";
for(const [key, value] of Object.entries(warnings)){
if(value.length > 0){
warningText += key.toUpperCase() + ":\n";
for(let v of value){
warningText += " " + v + "\n";
}
warningText += "\n";
}
}
if(warningText !== ""){
torrent.elements.item.style["font-style"] = "italic";
let pre = document.createElement("pre");
pre.textContent = "== WARNINGS ==\n\n" + warningText + "\n";
if("description" in torrent.elements){
torrent.elements.description.prepend(pre);
}
torrent.elements.entry.prepend(" - ");
for(let i = 0; i < 3; ++i){
let warn = document.createElement("img");
warn.src = "/static/common/symbols/warned.png";
torrent.elements.entry.prepend(warn);
}
}
}else{
torrent.elements.item.style["font-style"] = "italic";
torrent.elements.item.style["text-decoration"] = "line-through";
let pre = document.createElement("pre");
pre.textContent = "== WARNINGS ==\n\nNo valid mediainfo section found. Maybe check Description?\n\n";
if("description" in torrent.elements){
torrent.elements.description.prepend(pre);
}
torrent.elements.entry.prepend(" - ");
for(let i = 0; i < 3; ++i){
let warn = document.createElement("img");
warn.src = "/static/common/symbols/warned.png";
torrent.elements.entry.prepend(warn);
}
}
}catch (e){
console.log("Error:", e);
console.log(torrent);
}
});
}
//21:28:16 <+AMM> subs, signage, chapters, titles, codecs, containers
// 21:28:20 <+AMM> What is good, what is bad