userscripts/AnimeBytes/ab-mediainfo.user.js

2528 lines
110 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==UserScript==
// @name AnimeBytes Mediainfo Improvements
// @author WeebDataHoarder
// @version 1.32.1
// @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=*
// @match https://animebytes.tv/torrents2.php?*id=*
// @exclude https://animebytes.tv/torrents*groupid=*
// @match https://animebytes.tv/user.php?action=edit
// @match https://animebytes.tv/upload.php*
// @icon https://mei.kuudere.pw/jEBHMictkaa.png
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @require deps/compat.js?1
// @require deps/resources.js?2
// @require deps/settings.js?1
// @require deps/mediainfo.js?17
// @require deps/ab-class.js?1
// @run-at document-end
// ==/UserScript==
function getComparisonLine(tags, legacy = true){
let entries = [];
entries.push(getLineTagEntry(tags.source, legacy));
entries.push(getLineTagEntry(tags.container, legacy) + ("region" in tags ? " (" + tags.region + ")" : ""));
if("aspectRatio" in tags){
entries.push(tags.aspectRatio);
}
if("videoCodec" in tags && !(
getLineTagEntry(tags.source, legacy).match(/^DVD[59]$/) !== null
)){
entries.push(getLineTagEntry(tags.videoCodec, legacy));
}
entries.push(getLineTagEntry(tags.resolution, legacy));
entries.push(getLineTagEntry(tags.audioCodec, legacy) + " " + Mediainfo.getAudioChannels(tags.audioChannels));
if((!("audioLanguages" in tags) || "English" in tags.audioLanguages) && tags.audioCount > 1){
entries.push("Dual Audio");
}
entries.push(("subtitleType" in tags ? getLineTagEntry(tags.subtitleType, legacy) : "RAW") + ("group" in tags ? " (" + tags.group + ")" : ""));
return entries.join(" | ");
}
function wrap(...e) {
let span = document.createElement("span");
e.forEach((i) => {
span.append(i);
})
return span;
}
function wrapStyleClass(e, classType) {
let span = document.createElement("span");
let value;
if (typeof e === 'string'){
value = e;
} else {
value = e.textContent;
if (value === "") {
value = e.getAttribute("alt");
}
}
if(value === ""){
return e;
}
if(settings.getSetting("script_evaHighlightEmulation") || settings.getSetting("script_abHighlights2Emulation")){
span.setAttribute("data-" + classType, value);
}
if(settings.getSetting("script_abHighlights2Emulation")){
span.classList.add("userscript-highlight", "torrent-field");
span.setAttribute("data-field", value);
}
span.append(e);
return span;
}
function getEntryLine(tags, messages){
// Source + container + remux | Codec / Resolution / HDR | Audio + Dual + commentary | Text | OtherTags | icons
let entries = {
source: [],
video: [],
audio: [],
text: [],
other: [],
icons: []
};
entries.source.push(wrap(
wrapStyleClass(getLineTagEntry(tags.source, false), "media"),
("sourceName" in tags ? wrap(" (", wrapStyleClass(tags.sourceName, "source"), ")") : "")
));
entries.source.push(wrap(
wrapStyleClass(getLineTagEntry(tags.container, false), "container"),
("region" in tags ? wrap(" (", wrapStyleClass(tags.region, "region"), ")") : "")
));
if("videoCodec" in tags){
let span = document.createElement("span");
if(settings.getSetting("debug_flagUnknownCodec") && typeof tags.videoCodec !== "object"){
span.append("NOID-");
}
let codec = document.createElement("span");
codec.classList.add("entry_codec");
codec.textContent = getLineTagEntry(tags.videoCodec, false);
span.append(wrapStyleClass(codec, "codec"));
if("videoEncoder" in tags && settings.getSetting("showEncodingDetails")){
span.append(wrap(
" (",
wrapStyleClass(tags.videoEncoder, "encoder"),
("videoEncodeMode" in tags ? wrap(" ", wrapStyleClass(tags.videoEncodeMode, "encode-mode")) : ""),
("videoEncodeRateControl" in tags ? wrap(" ",wrapStyleClass(tags.videoEncodeRateControl, "encode-rc")) : ""),
")"
));
}
entries.video.push(span);
}
entries.video.push(wrap(
wrapStyleClass(getLineTagEntry(tags.resolution, false), "resolution"),
("aspectRatio" in tags ? wrap(" ", wrapStyleClass(tags.aspectRatio, "aspect-ratio")) : "")
/* ("videoFrameRate" in tags ? " @ " + tags.videoFrameRate : "")*/
));
if("videoHDR" in tags){
entries.video.push(wrapStyleClass(getLineTagEntry(tags.videoHDR, false), "hdr"));
}
if(tags.videoCount > 1){
entries.video.push(wrapStyleClass(tags.videoCount === 2 ? "Dual" : "Multi (" + tags.videoCount + ")", "dual-video"));
}
{
let span = document.createElement("span");
if(settings.getSetting("debug_flagUnknownCodec") && typeof tags.audioCodec !== "object"){
span.append("NOID-");
}
let codec = document.createElement("span");
codec.classList.add("entry_codec");
codec.textContent = getLineTagEntry(tags.audioCodec, false);
span.append(wrapStyleClass(codec, "audio-codec"));
if(settings.getSetting("showAudioDetails") && "audioBitrate" in tags){
span.append(" ", wrapStyleClass(tags.audioBitrate, "audio-bitrate"));
}
span.append(" ", wrapStyleClass(Mediainfo.getAudioChannels(tags.audioChannels), "audio-channels"));
entries.audio.push(span);
}
if(tags.audioCount > 1){
if((!("audioLanguages" in tags) || "English" in tags.audioLanguages)){
entries.audio.push(wrapStyleClass(tags.audioCount === 2 ? "Dual" : "Dual Multi (" + ("audioLanguages" in tags ? Object.keys(tags.audioLanguages).length + "x" : "") + tags.audioCount + ")", "dual-audio"));
}else{
entries.audio.push(wrapStyleClass("Multi (" + ("audioLanguages" in tags ? Object.keys(tags.audioLanguages).length + "x" : "") + tags.audioCount + ")", "dual-audio"));
}
}
if("audioCommentary" in tags && tags.audioCommentary){
entries.audio.push(wrapStyleClass("Commentary", "audio-commentary"));
}
if("remux" in tags){
if(tags.remux === true){
entries.source.push(wrapStyleClass("REMUX", "remux"));
}else if(tags.remux === "probably"){
entries.source.push(wrapStyleClass("REMUX*", "remux"));
}
}
entries.text.push(wrap(
wrapStyleClass(("subtitleCodec" in tags ? tags.subtitleCodec + " " : ""), "subbing-codec"),
wrapStyleClass(("subtitleType" in tags ? getLineTagEntry(tags.subtitleType, false) : "RAW"), "subbing"),
("group" in tags ? wrap(" (", wrapStyleClass(tags.group, "group"), ")") : ""),
));
if(tags.remastered){
entries.other.push(createImage("remastered"));
}
if(tags.censoredHentai){
entries.other.push(createImage("hentaic"));
}else if (tags.uncensoredHentai){
entries.other.push(createImage("hentai"));
}
if(tags.freeleech){
entries.other.push(wrapStyleClass(createImage("freeleech"), "freeleech"));
}
if("chapters" in tags){
entries.other.push(tags.chapters === "ordered" ? "Ordered Chapters" : "Chapters");
}
if(tags.openFormat && settings.getSetting("openFormats")){
entries.other.push("Open Format");
}
if(tags.snatched){
entries.other.push("Snatched");
}
if(tags.pruned){
entries.other.push("Pruned");
}
if(tags.reported){
entries.other.push("Reported");
}
let messageCount = 0;
let span = document.createElement("span");
span.classList.add("emoji", "icons");
for(const [key, value] of Object.entries(messages)){
if(value.length > 0){
for(let v of value){
if(Array.isArray(v)){
switch (v[0]){
case "info":
span.append(createIcon("", null, key + ": " + v[1]));
break;
case "warning":
span.append(createIcon("⚠", "#ffff00", key + ": " + v[1]));
break;
case "danger":
span.append(createIcon("❌", "#ff0000", key + ": " + v[1]));
break;
case "mediainfo":
span.append(createIcon("📄", "#ff0000", key + ": " + v[1]));
break;
}
}else{
span.append(createIcon("⚠", "#ffff00", key + ": " + v));
}
++messageCount;
}
}
}
if(messageCount > 0){
entries.icons.unshift(span);
}
return entries;
}
function extractTagsFromFilename(fileName, sourceTags, source = ""){
let tags = {
};
let cleanName = fileName.replace(/\.[^/.]+$/, "")
cleanName = cleanName.replace(/\[[A-F0-9]{8}\]$/i, "")
cleanName = cleanName.replace(/\([A-F0-9]{8}\)$/i, "")
//handle file tags
cleanName.split(".").forEach((p) => {
const pCase = p.toUpperCase();
if(pCase === "NF" || pCase === "NETFLIX"){
tags.sourceName = "Netflix";
}else if(pCase === "CR" || pCase === "CRUNCHYROLL"){
tags.sourceName = "Crunchyroll";
}else if(pCase === "ITUNES"){
tags.sourceName = "Apple iTunes";
}else if(pCase === "ATVP"){
tags.sourceName = "Apple TV+";
}else if(pCase === "DSNP"){
tags.sourceName = "Disney Plus";
}else if(pCase === "AMZN" || pCase === "AMAZON"){
tags.sourceName = "Amazon Prime Video";
}else if(pCase === "FUNI" || pCase === "FUNIMATION"){
tags.sourceName = "Funimation";
}else if(pCase === "WAKANIM"){
tags.sourceName = "Wakanim";
}else if(pCase === "ANIMELAB"){
tags.sourceName = "AnimeLab";
}else if(pCase === "HIDIVE"){
tags.sourceName = "Hidive";
}else if(pCase === "VRV"){
tags.sourceName = "VRV";
}else if(pCase === "HULU"){
tags.sourceName = "Hulu";
}else if(p === "HDR"){
tags.videoHDR = "HDR";
}else if(p === "HDR10"){
tags.videoHDR = "HDR10";
}else if(p === "DV"){
tags.videoHDR = "HDR DV";
}else if(pCase === "REMUX"){
tags.remux = true;
}else if(p.match(/^(bd|(blu-?ray))(-?rip)?$/i) !== null){
tags.source = (cleanName.match(/(4K|2160|3840|UHD)/) !== null || sourceTags.source === "UHD Blu-ray") ? "UHD Blu-ray" : "Blu-ray";
}else if(p.match(/^dvd(-?rip)?$/i) !== null){
tags.source = "DVD";
}else if(p.match(/^web(-?(rip|dl))?$/i) !== null){
tags.source = "Web";
}
// negative match known rips to not be flagged by REMUX* later
if(p.match(/^(bd|(blu-?ray))(-?rip)$/i) !== null){
tags.remux = false;
} else if(p.match(/^(dvd)(-?rip)$/i) !== null){
tags.remux = false;
}
});
cleanName.split(/[ _\[(\])]/).forEach((p) => {
const pCase = p.toUpperCase();
if(source === "Web" && (pCase === "NF" || pCase === "NETFLIX")){
tags.sourceName = "Netflix";
}else if(pCase === "ITUNES"){
tags.sourceName = "Apple iTunes";
}else if(source === "Web" && (pCase === "CR" || pCase === "CRUNCHYROLL" || pCase === "CR-DUB")){
tags.sourceName = "Crunchyroll";
}else if(source === "Web" && (pCase === "AMZN" || pCase === "AMAZON")){
tags.sourceName = "Amazon Prime Video";
}else if(source === "Web" && pCase === "HULU"){
tags.sourceName = "Hulu";
}else if(source === "Web" && (pCase === "FUNIMATION" || pCase === "FUNI" || pCase === "FUNIDUB" || pCase === "FUNI-DUB")){
tags.sourceName = "Funimation";
}else if(source === "Web" && (pCase === "HIDIVE" || pCase === "HIDIVE-DUB")){
tags.sourceName = "Hidive";
}else if(source === "Web" && pCase === "VRV"){
tags.sourceName = "VRV";
}else if(source === "TV" && pCase === "SSTV"){
tags.sourceName = "Space Shower TV";
}else if(source === "TV" && pCase === "SSTV+"){
tags.sourceName = "Space Shower TV+";
}else if(source === "TV" && (pCase === "M-ON" || pCase === "M-ON!")){
tags.sourceName = "MUSIC ON! TV";
}else if(source === "Web" && (pCase === "WETV")){
tags.sourceName = "WeTV";
}else if(source === "TV" && pCase === "MX"){
tags.sourceName = "TOKYO MX";
}else if(source === "TV" && pCase === "CX"){
tags.sourceName = "Fuji TV";
}else if(source === "TV" && pCase === "TX"){
tags.sourceName = "TX Network";
}else if(source === "TV" && pCase === "NTV"){
tags.sourceName = "NIPPON TV";
}else if(source === "TV" && pCase === "ABC"){
tags.sourceName = "ABC";
}else if(source === "TV" && pCase === "RKC"){
tags.sourceName = "RKC";
}else if(source === "TV" && pCase === "CS3"){
tags.sourceName = "CS3";
}else if(source === "TV" && pCase === "BS6"){
tags.sourceName = "BS6";
}else if(source === "TV" && pCase === "RKC"){
tags.sourceName = "RKC";
}else if(source === "TV" && pCase === "EX-BS"){
tags.sourceName = "EX-BS";
}else if(source === "TV" && pCase === "TBS"){
tags.sourceName = "TBS Television";
}else if(source === "TV" && pCase === "EX"){
tags.sourceName = "TV Asahi";
}else if(source === "TV" && pCase === "TVA"){
tags.sourceName = "TV Aichi";
}else if(source === "TV" && pCase === "QAB"){
tags.sourceName = "QAB";
}else if(source === "TV" && pCase === "TVQ"){
tags.sourceName = "TVQ Kyushu Broadcasting";
}else if(source === "TV" && pCase === "KBC"){
tags.sourceName = "KBC";
}else if(source === "TV" && pCase === "BS4"){
tags.sourceName = "BS Nippon TV";
}else if(source === "TV" && pCase === "BS11"){
tags.sourceName = "BS11";
}else if(source === "TV" && (pCase === "AT-X" || pCase === "ATX")){
tags.sourceName = "AT-X";
}else if(source === "TV" && pCase === "WOWOW"){
tags.sourceName = "WOWOW";
}else if(source === "TV" && pCase === "YTV"){
tags.sourceName = "Yomiuri TV";
}else if(p === "HDR"){
tags.videoHDR = "HDR";
}else if(p === "HDR10"){
tags.videoHDR = "HDR10";
}else if(p === "DV"){
tags.videoHDR = "HDR DV";
}else if(pCase.indexOf("REMUX") !== -1){
tags.remux = true;
}else if(p.match(/^(bd|(blu-?ray))(-?rip)?$/i) !== null){
tags.source = (cleanName.match(/(4K|2160|3840|UHD)/) !== null || sourceTags.source === "UHD Blu-ray") ? "UHD Blu-ray" : "Blu-ray";
}else if(p.match(/^dvd(-?rip)?$/i) !== null){
tags.source = "DVD";
}else if(p.match(/^web(-?(rip|dl))?$/i) !== null){
tags.source = "Web";
}
// negative match known rips to not be flagged by REMUX* later
if(p.match(/^(bd|(blu-?ray))(-?rip)$/i) !== null){
tags.remux = false;
} else if(p.match(/^(dvd)(-?rip)$/i) !== null){
tags.remux = false;
}
});
return tags;
}
function extractFromMediainfo(tags, mediainfo, warnings, fileName, fileList, sourceTags, isLiveActionType = false){
tags.openFormat = false;
let japaneseAudio = null;
let otherAudio = null;
let defaultAudio = null;
let englishSubs = null;
let defaultSubs = null;
const fversions = ("format_version" in mediainfo.general ? mediainfo.general.format_version.replace(/[^0-9 ]+/g, "").split(" ").filter((a) => a) : []);
const fversion = fversions.length > 0 ? " v" + fversions.join(".") : "";
switch(mediainfo.general.format){
case "Matroska":
tags.container = {
name: "MKV" + (settings.getSetting("showContainerVersions") ? fversion : ""),
legacy: "MKV"
};
tags.openFormat = true;
break;
case "WebM":
tags.container = {
name: "WebM" + (settings.getSetting("showContainerVersions") ? fversion : ""),
legacy: "MKV"
};
tags.openFormat = true;
break;
case "Ogg":
tags.container = "OGM";
tags.openFormat = true;
break;
case "MPEG-4":
tags.container = "MP4";
break;
case "MPEG-PS":
let baseContainer = "MPG";
if(fileName.split(".").slice(-1)[0].toUpperCase() === "VOB"){
baseContainer = "VOB"
}
if(sourceTags.container.match(/ IFO$/) !== null){
baseContainer += " IFO";
}
tags.container = {
name: sourceTags.container === "ISO" ? "ISO-" + baseContainer : baseContainer,
legacy: sourceTags.container === "ISO" ? "ISO" : baseContainer
};
break;
case "MPEG-TS":
tags.container = "TS";
if("original_network_name" in mediainfo.general){
tags.sourceName = mediainfo.general.original_network_name;
}
break;
case "Flash Video":
tags.container = "FLV";
break;
case "AVI":
tags.container = "AVI";
break;
case "RealMedia":
tags.container = "RMVB";
break;
case "Windows Media":
tags.container = "WMV";
break;
case "BDAV":
tags.container = {
name: sourceTags.container === "ISO" ? "ISO-M2TS" : "M2TS",
legacy: sourceTags.container === "ISO" ? "ISO" : "M2TS"
};
break;
}
let isHandbrake = false;
if ("general" in mediainfo) {
if("writing_application" in mediainfo.general){
if(mediainfo.general.writing_application.match(/^HandBrake /) !== null){
if(settings.getSetting("warning_handbrakeEncoder")){
warnings.general.push(["info", "Found HandBrake encoder"]);
}
isHandbrake = true;
}
}
}
let video = null;
mediainfo.video.forEach((v) => {
Mediainfo.makeVideoObject(v);
if(!("open" in v.__videoCodec) || !v.__videoCodec.open){
tags.openFormat = false;
}
if(video !== null && "default" in v && v.default === "Yes"){
video = v;
}
});
if(video === null && mediainfo.video.length > 0){
video = mediainfo.video[0];
}
tags.videoCount = mediainfo.video.length;
if(video !== null){
tags.videoCodec = Mediainfo.matchCodec(Mediainfo.videoCodecs, video, sourceTags.videoCodec ? sourceTags.videoCodec : video.__videoCodec);
if("frame_rate" in video){
tags.videoFrameRate = video.frame_rate.split(" ")[0];
}else if("original_frame_rate" in video){
tags.videoFrameRate = video.original_frame_rate.split(" ")[0];
}
if("videoFrameRate" in tags && tags.videoFrameRate > settings.getSetting("warning_progressiveFpsTooHigh") && (!("scan_type" in video) || video.scan_type === "Progressive")){
warnings.video.push(["danger", "Progressive Frame Rate too high! Found " + tags.videoFrameRate + " > " + settings.getSetting("warning_progressiveFpsTooHigh")]);
}
let width = parseInt(video.width.replace(/[^0-9]/g, ""));
let height = parseInt(video.height.replace(/[^0-9]/g, ""));
let ar = width / height;
//Handle anamorphic video
if("display_aspect_ratio" in video && settings.getSetting("handleAnamorphicVideo")){
let params = video.display_aspect_ratio.replace("/", ":").split(":");
if(params.length === 2){
let dar = params[0] / params[1];
if(Math.abs(ar - dar) >= 0.02){
warnings.video.push(["info", "Anamorphic video found: " + width + "x" + height + " -> "+ Math.floor(height * dar) + "x" + height]);
width = Math.floor(height * dar);
}
ar = width / height;
}
}
let scanTypeLetter = "p";
if("scan_type" in video){
if(video.scan_type.toUpperCase() === "MBAFF"){
scanTypeLetter = "i";
warnings.video.push(["info", "MBAFF found as video storage method. Content might still be Progressive"]);
}else if(video.scan_type.toUpperCase() === "INTERLACED"){
scanTypeLetter = "i";
}else if(video.scan_type.toUpperCase() === "PROGRESSIVE"){
scanTypeLetter = "p";
}
}
//TODO: make this % offset from expected
if(ar < 1.4){ //Closer to 4:3, including crop
if(width <= 1008 && width >= 900 && height >= 540 && height <= 730){
tags.resolution = {
name: "720" + scanTypeLetter,
legacy: "720p"/* + ((scanTypeLetter !== "p" && scanTypeLetter !== "i") ? "i" : scanTypeLetter)*/
};
}else if(width <= 1580 && width >= 1340 && height > 720 && height <= 1080){
tags.resolution = {
name: "1080" + scanTypeLetter,
legacy: "1080" + ((scanTypeLetter !== "p" && scanTypeLetter !== "i") ? "i" : scanTypeLetter)
};
}else if(width <= 2900 && width >= 2800 && height > 1100 && height <= 2160){
tags.resolution = "4K";
}else{
tags.resolution = width + "x" + height;
}
} else { //Closer to 16:9, including crop
if(width <= 1290 && width >= 1200 && height >= 540 && height <= 720){
tags.resolution = {
name: "720" + scanTypeLetter,
legacy: "720p"/* + ((scanTypeLetter !== "p" && scanTypeLetter !== "i") ? "i" : scanTypeLetter)*/
};
}else if(width <= 1940 && width >= 1820 && height > 720 && height <= 1084){
tags.resolution = {
name: "1080" + scanTypeLetter,
legacy: "1080" + ((scanTypeLetter !== "p" && scanTypeLetter !== "i") ? "i" : scanTypeLetter)
};
}else if(width <= 3850 && width >= 3640 && height > 1084 && height <= 2160){
tags.resolution = "4K";
}else{
tags.resolution = width + "x" + height;
}
}
if("display_aspect_ratio" in video){
tags.aspectRatio = video.display_aspect_ratio.replace("/", ":");
}
if("color_primaries" in video && (video.color_primaries === "BT.2020" || video.color_primaries === "BT.2100")){
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(!("videoHDR" in tags) && "commercial_name" in video){
if(video.commercial_name.match(/HDR10/) !== null){
tags.videoHDR = "HDR10";
}
}
if("writing_library" in video){
if(video.writing_library.match(/^x265/) !== null){
tags.videoEncoder = "x265";
let versionMatch = video.writing_library.match(/^x265 (?<version>[0-9+]+(-[a-f0-9]+)?):/)
if(versionMatch !== null){
tags.videoEncoderVersion = parseInt(versionMatch.groups.version);
}
}else if(video.writing_library.match(/x264/) !== null){
tags.videoEncoder = "x264";
let versionMatch = video.writing_library.match(/ r(?<version>[0-9]+) /)
if(versionMatch !== null){
tags.videoEncoderVersion = parseInt(versionMatch.groups.version);
}
}else if(video.writing_library.match(/^ATEME/) !== null){
tags.videoEncoder = "ATEME";
}else if(video.writing_library.match(/www\.letv\.com/) !== null || video.writing_library.match(/letv cloud/) !== null){
tags.videoEncoder = "letv";
let versionMatch = video.writing_library.match(/-V (?<version>[0-9.\-]+)$/)
if(versionMatch !== null){
tags.videoEncoderVersion = parseInt(versionMatch.groups.version);
}
}else if(video.writing_library.match(/^BiliBili/) !== null){
tags.videoEncoder = "BiliBili";
let versionMatch = video.writing_library.match(/v(?<version>[0-9.\-]+)$/)
if(versionMatch !== null){
tags.videoEncoderVersion = parseInt(versionMatch.groups.version);
}
}else if(video.writing_library.match(/libaom/) !== null){
tags.videoEncoder = "libaom";
}else if(video.writing_library.match(/_nvenc/) !== null){
tags.videoEncoder = "NVENC";
if(settings.getSetting("warning_hwEncoder")){
warnings.video.push(["danger", "Found NVENC hardware-encoded stream"]);
}
}else if(video.writing_library.match(/_qsv/) !== null){
tags.videoEncoder = "QuickSync";
if(settings.getSetting("warning_hwEncoder")){
warnings.video.push(["danger", "Found QuickSync hardware-encoded stream"]);
}
}else if(video.writing_library.match(/_vaapi/) !== null){
tags.videoEncoder = "VAAPI";
if(settings.getSetting("warning_hwEncoder")){
warnings.video.push(["danger", "Found VAAPI hardware-encoded stream"]);
}
}else if(video.writing_library.trim().match(/[^0-9A-F]/i) !== null){
[tags.videoEncoder] = video.writing_library.split(" ");
}
} else if (isHandbrake) {
// HandBrake removes NVenc tags when using Hardware Encoder, at least on H.264 and H.265
tags.videoEncoder = "HandBrake HW";
if(settings.getSetting("warning_hwEncoder")){
warnings.video.push(["danger", "Found probable HandBrake hardware-encoded stream"]);
}
}
//Detect probably H262 remuxes
try{
if(
!("remux" in tags)
&& !("encoding_settings" in video) && !("writing_library" in video)
&& video.codec_id === "V_MPEG2" && video.format_profile === "Main@Main"
&& ((video.width === "720 pixels" && video.height === "480 pixels") || (video.width === "720 pixels" && video.height === "576 pixels"))
&& (!("original_source_medium" in video) || video.original_source_medium === "DVD-Video")
&& (!("bits_pixel_frame" in video) || parseFloat(video.bits_pixel_frame) > 0.5)
){
tags.remux = "probably";
}
}catch (e){
}
//Detect probably H264 remuxes
try{
if(
!("remux" in tags) &&
!("encoding_settings" in video) && !("writing_library" in video) &&
video.codec_id === "V_MPEG4/ISO/AVC" && video.format_profile === "High@L4.1" &&
(
("format_settings" in video && video.format_settings.match(/CABAC \/ [234] Ref Frames/) !== null) ||
(video.format_settings_cabac === "Yes" && "format_settings_reframes" in video && video.format_settings_reframes.match(/^[234] frames/) !== null)
)
&& width === 1920 && height === 1080 &&
video.frame_rate_mode === "Constant" &&
(!("bits_pixel_frame" in video) || parseFloat(video.bits_pixel_frame) > 0.4)
){
tags.remux = "probably";
}
}catch (e){
}
//Detect probably H265 4K remuxes
try{
if(
!("remux" in tags) &&
(
//TODO: add more if found
(!("encoding_settings" in video) && !("writing_library" in video)) ||
(!("encoding_settings" in video) && video.writing_library.match(/^ATEME/) !== null) ||
((!("encoding_settings" in video) || (video.encoding_settings.match(/crf=[0-9]+/) === null)) && video.writing_library.match(/^x265/) !== null)
) &&
video.codec_id === "V_MPEGH/ISO/HEVC" && video.format_profile === "Main 10@L5.1@High" &&
width === 3840 && height === 2160 &&
video.frame_rate_mode === "Constant" &&
(!("bits_pixel_frame" in video) || parseFloat(video.bits_pixel_frame) > 0.2)
){
tags.remux = "probably";
}
}catch (e){
}
if("encoding_settings" in video){
let match = null;
if((match = video.encoding_settings.match(/ rc=(?<rc>[0-9a-z]+)/i)) !== null){
tags.videoEncodeMode = match.groups.rc.toLowerCase();
}
if((match = video.encoding_settings.match(/crf=(?<crf>[0-9.]+)/)) !== null){
tags.videoEncodeMode = "crf";
tags.videoEncodeRateControl = parseFloat(match.groups.crf).toFixed(1);
}else if(video.encoding_settings.match(/rc=cqp/) !== null && (match = video.encoding_settings.match(/ qp=(?<qp>[0-9.]+)/)) !== null){
tags.videoEncodeMode = "cqp";
tags.videoEncodeRateControl = parseFloat(match.groups.qp).toFixed(1);
if(settings.getSetting("warning_encodeSettings")){
warnings.video.push(["info", "Found encode using rc=cqp (Constant QP) mode on encoding settings"]);
}
}else if((match = video.encoding_settings.match(/bitrate=(?<bitrate>[0-9.]+)/)) !== null){
tags.videoEncodeRateControl = parseInt(match.groups.bitrate) + "k";
}
if(video.encoding_settings.match(/rc=2pass/) !== null){
tags.videoEncodeMode = "2pass";
}
if(!("remux" in tags) && video.codec_id === "V_MPEG4/ISO/AVC" && (match = video.format_profile.match(/(?<profile>(baseline|main)[ 0-9]*)@/i)) !== null){
if(settings.getSetting("warning_encodeProfile")){
warnings.video.push(["warning", "Found encode using profile=" + match.groups.profile + " on encoding settings"]);
}
}
if(video.encoding_settings.match(/cabac=0/) !== null && settings.getSetting("warning_encodeSettings")){
warnings.video.push(["info", "Found encode with CABAC disabled"]);
}
if(video.encoding_settings.match(/mbtree=0/) !== null && settings.getSetting("warning_encodeSettings")){
warnings.video.push(["info", "Found encode with mbtree disabled"]);
}
if(video.encoding_settings.match(/bframes=0/) !== null && settings.getSetting("warning_encodeSettings")){
warnings.video.push(["info", "Found encode with bframes disabled"]);
}
if(video.encoding_settings.match(/me=dia/) !== null && settings.getSetting("warning_encodeSettings")){
warnings.video.push(["info", "Found encode with me=dia"]);
}
}
}
let audioLanguages = {};
let audioCount = 0;
mediainfo.audio.forEach((audio, index) => {
Mediainfo.makeAudioObject(audio);
if(!("open" in audio.__audioCodec) || !audio.__audioCodec.open){
tags.openFormat = false;
}
const isDescriptionEntry = ("title" in audio) ? audio.title.match(/descript/i) !== null : false;
const isCommentary = "title" in audio && audio.title.match(/comment/i) !== null;
const isDefault = audio.default === "Yes";
let audioLang = "language" in audio ? audio.language : "Unknown";
//Pick first default audio
if(isDefault && defaultAudio == null){
defaultAudio = audio;
}
if(audioLang !== "Japanese" && audioLang !== "Unknown" && (isDefault && defaultAudio === audio) && !isLiveActionType && settings.getSetting("warning_defaultAudioNotJapanese")){
warnings.audio.push(["info", "First default audio #"+(index+1)+" is not in Japanese"]);
}
if(isCommentary){
tags.audioCommentary = true;
}
if(!isCommentary && !isDescriptionEntry){
++audioCount;
if(!(audioLang in audioLanguages)){
audioLanguages[audioLang] = 0;
}
audioLanguages[audioLang]++;
}
if(!isDescriptionEntry && !isCommentary && audioLang === "Japanese"){
if(japaneseAudio === null){ // Pick first
japaneseAudio = audio;
}else{
if(audio.channels > japaneseAudio.channels || (
audio.channels === japaneseAudio.channels && ( // Alternate upgrade path
(["AAC-LC", "HE-AAC", "AAC-Main", "AC3", "Vorbis", "MP2", "MP3", "DTS"].includes(japaneseAudio.__audioCodec.name) && ["E-AC3", "E-AC3 Atmos", "DTS-HD", "DTS-ES"].includes(audio.__audioCodec.name)) ||
(!japaneseAudio.__audioCodec.lossless && audio.__audioCodec.lossless === true)
)
)){
japaneseAudio = audio;
}
}
}else if(!isDescriptionEntry && !isCommentary && audioLang === "Unknown"){
if(japaneseAudio === null && (mediainfo.audio.length === 1 || (Object.keys(audioLanguages).length === 1 && "Unknown" in audioLanguages))){
japaneseAudio = audio;
}else if(japaneseAudio !== null && (!("language" in japaneseAudio) || japaneseAudio.language === "Unknown")){
if(audio.channels > japaneseAudio.channels || (
audio.channels === japaneseAudio.channels && ( // Alternate upgrade path
(["AAC-LC", "HE-AAC", "AAC-Main", "AC3", "Vorbis", "MP2", "MP3", "DTS"].includes(japaneseAudio.__audioCodec.name) && ["E-AC3", "E-AC3 Atmos", "DTS-HD", "DTS-ES"].includes(audio.__audioCodec.name)) ||
(!japaneseAudio.__audioCodec.lossless && audio.__audioCodec.lossless === true)
)
)){
japaneseAudio = audio;
}
}
}else if(!isDescriptionEntry && !isCommentary){
if(otherAudio === null){ // Pick first
otherAudio = audio;
}else{
if(audio.channels > otherAudio.channels){
otherAudio = audio;
}
}
}
let ratio = null;
if("stream_size" in audio){
try{
ratio = parseFloat(audio.stream_size.match(/\((?<percentage>[0-9.]+)%\)$/).groups.percentage);
}catch (e){
}
}
const isRemuxOrRawSource = ("remux" in tags || getLineTagEntry(sourceTags.container, true).match(/^(ISO|M2TS|DVD|(VOB IFO))/) !== null);
if(!isRemuxOrRawSource && audio.format === "PCM" && settings.getSetting("warning_bloatPCM")){
warnings.audio.push(["warning", "BLOAT: Audio track #"+(index+1)+" is uncompressed PCM."]);
}
if(!isRemuxOrRawSource && (audio.format === "FLAC" || audio.format.match(/^DTS/) !== null) && (ratio === null || (ratio > 10)) && settings.getSetting("warning_bloatg16bit") && "bit_depth" in audio){
let bitDepth = parseInt(audio.bit_depth.replace(/[^0-9]/g, ""));
if(bitDepth > 16){
warnings.audio.push(["warning", "BLOAT: Audio track #"+(index+1)+" is "+audio.__audioCodec.name+" with bit depth greater than 16-bit, found "+bitDepth+"-bit."]);
}
}
});
if(Object.keys(audioLanguages).length > 1 || !("Unknown" in audioLanguages)){
tags.audioCount = audioCount;
tags.audioLanguages = audioLanguages;
}
let audio = japaneseAudio !== null ? japaneseAudio : (defaultAudio !== null ? defaultAudio : otherAudio);
if(audio !== null){
tags.audioChannels = audio.channels;
tags.audioCodec = Mediainfo.matchCodec(Mediainfo.audioCodecs, audio, sourceTags.audioCodec ? sourceTags.audioCodec : audio.__audioCodec);
if(tags.audioCodec.lossless){
if("bit_depth" in audio){
let bitDepth = parseInt(audio.bit_depth.split("/")[0].replace(/[^0-9]/g, ""));
tags.audioBitrate = bitDepth + "-bit";
}
}else{
if("bit_rate" in audio){
let bitRate = parseInt(audio.bit_rate.split("/")[0].replace(/[^0-9.]/g, ""));
tags.audioBitrate = bitRate + "k";
}
}
}
if("text" in mediainfo){
mediainfo.text.forEach((text, index) => {
const isSignsEntry = ("title" in text) ? (text.title.match(/sign/i) !== null && text.title.match(/(text|full|dialogue)/i) === null) : false;
const isDefault = text.default === "Yes";
if(isSignsEntry && isDefault && settings.getSetting("warning_signsDefault")){
warnings.text.push(["warning", "Default Subtitles track #"+(index+1)+" is a Signs track"]);
}
if(isDefault){
defaultSubs = text;
}
if(!isSignsEntry && (text.language === "English" || (englishSubs === null && "title" in text && text.title.match(/english/i) !== null))){
if(englishSubs === null){
englishSubs = text;
}else{
if(text.codec_id === "S_TEXT/ASS" && englishSubs.codec_id !== "S_TEXT/ASS"){
englishSubs = text;
}
}
}else if(!isSignsEntry && mediainfo.text.length === 1 && (text.language === "Unknown" || !("language" in text))){
englishSubs = text;
}else if(englishSubs === null && !isSignsEntry && text.language === "Japanese" && ("title" in text && text.title.match(/(text|full|dialogue|english)/i) !== null)){
englishSubs = text;
if(settings.getSetting("warning_textLabeling")){
warnings.text.push(["warning", "Subtitles track #"+(index+1)+" is labeled as Japanese, but is not Japanese"]);
}
}
if(!isSignsEntry && isDefault && text !== englishSubs && text.language !== "English" && sourceTags.subtitleType === "Softsubs"){
warnings.text.push(["warning", "Default Subtitles track #"+(index+1)+" is not in English"]);
}
});
}
let text = englishSubs !== null ? englishSubs : (defaultSubs !== null ? defaultSubs : (("text" in mediainfo && mediainfo.text.length > 0 && (!("language" in mediainfo.text[0]) || mediainfo.text[0].language === "Unknown" || mediainfo.text[0].language === "Japanese")) ? mediainfo.text[0] : null));
let isTrackExternal = false;
let externalTrackFormat = null;
fileList.every((file) => {
const ext = file.path.split(".").slice(-1)[0].toUpperCase();
if(ext === "ASS" || ext === "SRT" || ext === "SSA" || ext === "MKS" || ext === "VTT" || ext === "SUB"){
isTrackExternal = true;
externalTrackFormat = ext;
}
return !isTrackExternal;
});
if(sourceTags.subtitleType === "Guess"){
if(englishSubs !== null){
sourceTags.subtitleType = "Softsubs";
}else{
sourceTags.subtitleType = "";
}
}
if(text !== null){
switch (text.format){
case "EIA-608":
tags.subtitleCodec = "CC SRT";
break;
case "UTF-8":
tags.subtitleCodec = text.codec_id === "S_TEXT/UTF8" ? "SRT" : text.format;
break;
case "Subrip":
tags.subtitleCodec = "SRT";
break;
case "RLE":
tags.subtitleCodec = "VobSub";
break;
default:
tags.subtitleCodec = text.format;
}
tags.subtitleTitle = "title" in text ? text.title : "";
if(isTrackExternal === false && sourceTags.subtitleType === "Softsubs" && englishSubs === null){
warnings.text.push(["warning", "Could not find English labeled subtitles, but labeled Softsubs"]);
}else{
if(!("subtitleType" in sourceTags) || sourceTags.subtitleType !== "Softsubs"){
if(settings.getSetting("warning_textLabeling")){
warnings.text.push(["warning", "Found "+tags.subtitleCodec+" Softsubs, but labeled " + (!("subtitleType" in sourceTags) ? "RAW" : sourceTags.subtitleType)]);
}
}
}
}
if((tags.subtitleType === "Softsubs" || sourceTags.subtitleType === "Softsubs") && (text === null || englishSubs === null)){
if(isTrackExternal){
tags.subtitleType = "Softsubs";
tags.subtitleCodec = "External " + externalTrackFormat;
if(settings.getSetting("warning_externalSubs")){
warnings.text.push(["info", "Subtitle track is external"]);
}
}else if("subtitleType" in sourceTags && sourceTags.subtitleType !== "Hardsubs" && (text === null || ("language" in text && text.language !== "Unknown"))){
warnings.text.push(["warning", "Could not find subtitles either on file listing or disk, probably hardsubbed?"]);
}
}
if("menu" in mediainfo){
//Check for chapters and ordered chapters
let chapterCount = 0;
for(const [key, value] of Object.entries(mediainfo.menu)){
let entryCount = 0;
value.split(" / ").forEach((i) => {
i = i.trim();
if(i.indexOf(":") !== -1){
entryCount++;
}
});
if(chapterCount === 0 && entryCount > 1 && !("chapters" in tags)){
tags.chapters = "ordered";
if(settings.getSetting("warning_orderedChapters")){
warnings.general.push(["warning", "Found Ordered Chapters"]);
}
}
++chapterCount;
}
if(!("chapters" in tags) && chapterCount > 1){
tags.chapters = chapterCount
}
}
matchSet.forEach((entry) => {
try {
let match = true;
let set = Object.assign({}, entry.set);
let m = null;
for(const [k, v] of Object.entries(entry.match)){
let ob = null;
if(k === "tags"){
ob = {};
for(const [key, value] of Object.entries(tags)){
if(!(key in ob)){
ob[key] = value;
}
}
for(const [key, value] of Object.entries(sourceTags)){
if(!(key in ob)){
ob[key] = value;
}
}
}else if(k === "video"){
ob = video;
}else if(k === "audio"){
ob = audio;
}else if(k === "text"){
ob = text;
}else if(k === "custom" && typeof v === 'function'){
m = v(sourceTags, mediainfo, tags, video, audio, text);
if(m === null || m === false){
match = false;
break;
}
if(typeof m === "object" && "groups" in m && typeof m.groups === "object"){
for(const [k2, v2] of Object.entries(m.groups)){
if(typeof v2 !== "undefined"){
for(const [k3, v3] of Object.entries(set)){
if(typeof v3 === 'string'){
set[k3] = v3.replace('$' + k2, v2);
}
}
}else{
for(const [k3, v3] of Object.entries(set)){
if(typeof v3 === 'string'){
set[k3] = v3.replace('$' + k2, "");
}
}
}
}
}
continue;
}
if(ob === null){
match = false;
break;
}
for(const [key, value] of Object.entries(v)){
if(value === null){
if(key in ob){
match = false;
break;
}
continue;
}
if(!(key in ob)){
match = false;
break;
}else if (value instanceof RegExp){
if((m = ob[key].match(value)) === null){
match = false;
break;
}
if("groups" in m && typeof m.groups === "object"){
for(const [k2, v2] of Object.entries(m.groups)){
if(typeof v2 !== "undefined"){
for(const [k3, v3] of Object.entries(set)){
if(typeof v3 === 'string'){
set[k3] = v3.replace('$' + k2, v2);
}
}
}else{
for(const [k3, v3] of Object.entries(set)){
if(typeof v3 === 'string'){
set[k3] = v3.replace('$' + k2, "");
}
}
}
}
}
}else if (ob[key] !== value){
match = false;
break;
}
}
}
if(match){
for(const [k, v] of Object.entries(set)){
if(v === null){
delete tags[k];
}else{
tags[k] = v.trim();
}
}
}
}catch (e){
console.log(e);
}
});
return tags;
}
function createIcon(text, color = null, title = null){
let e = document.createElement("span");
if(color){
e.style["color"] = color;
}
if(title){
e.setAttribute("title", title);
}
e.append(text);
return e;
}
const imageTypes = {
freeleech: {
src: "/static/common/flicon.png",
alt: "Freeleech!",
title: "This torrent is freeleech. Remember to seed!"
},
remastered: {
src: "/static/common/rmstr.png",
alt: "Remastered",
title: "This torrent is from a remastered source!"
},
hentaic: {
src: "/static/common/hentaic.svg",
alt: "Hentai",
title: "This torrent is of censored hentai (18+) material!"
},
hentai: {
src: "/static/common/hentai.svg",
alt: "Hentai",
title: "This torrent is of uncensored hentai (18+) material!"
},
script_warning: {
src: "/static/common/symbols/warned.png"
},
script_danger: {
src: "/static/common/symbols/disabled.png"
}
}
function createImage(type){
if(!(type in imageTypes)){
throw "Unknown image type " + type;
}
const i = imageTypes[type];
let img = document.createElement("img");
img.src = i.src;
if("alt" in i){
img.alt = i.alt;
}
if("title" in i){
img.title = i.title;
}
return img;
}
/**
* @param v
* @param legacy
* @returns String
*/
function getLineTagEntry(v, legacy){
if(typeof v === 'object'){
return (legacy && "legacy" in v) ? v.legacy : v.name;
}
return v;
}
const matchSet = [
{
//Hidive 480p
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
height: /^(480) pixels$/,
frame_rate_mode: "Constant",
writing_library: "x264 core 148",
encoding_settings: new RegExp("cabac=1 / ref=[36] / deblock=1:0:0 / analyse=0x1:0x111 / me=hex / subme=7 / psy=1 / psy_rd=1\\.00:0\\.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=0 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=(15) / lookahead_threads=[2] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=2 / keyint=90 / keyint_min=9 / scenecut=0 / intra_refresh=0 / rc_lookahead=40 / rc=crf / mbtree=1 / crf=22\\.0 / qcomp=0\\.60 / qpmin=0 / qpmax=69 / qpstep=4 / vbv_maxrate=(3500) / vbv_bufsize=(7680) / crf_max=0\\.0 / nal_hrd=none / filler=0 / ip_ratio=1\\.40 / aq=1:1\\.00")
}
},
set: {
sourceName: "Hidive"
}
},
{
//Hidive 720p/1080p
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
height: /^(720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
writing_library: "x264 core 148",
encoding_settings: new RegExp("cabac=1 / ref=[36] / deblock=1:0:0 / analyse=0x3:0x113 / me=hex / subme=7 / psy=1 / psy_rd=1\\.00:0\\.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=1 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=(22|34) / lookahead_threads=[35] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=2 / keyint=90 / keyint_min=9 / scenecut=0 / intra_refresh=0 / rc_lookahead=40 / rc=crf / mbtree=1 / crf=22\\.0 / qcomp=0\\.60 / qpmin=0 / qpmax=69 / qpstep=4 / vbv_maxrate=(11100|13500) / vbv_bufsize=(111000|135000) / crf_max=0\\.0 / nal_hrd=none / filler=0 / ip_ratio=1\\.40 / aq=1:1\\.00")
}
},
set: {
sourceName: "Hidive"
}
},
{
//Crunchyroll Aug 2023 encoding settings for 480p/720p/1080p
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
height: /^(480|720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
writing_library: "x264 core 164",
encoding_settings: new RegExp("cabac=1 / ref=[46] / deblock=1:1:1 / analyse=(0x1:0x111|0x3:0x113) / me=hex / subme=8 / psy=1 / psy_rd=0\\.40:0\\.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=2 / 8x8dct=[01] / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=(1|9|12) / lookahead_threads=[12] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=0 / weightp=2 / keyint=(96|120) / keyint_min=(48|60) / scenecut=40 / intra_refresh=0 / rc_lookahead=(48|60) / rc=2pass / mbtree=1 / bitrate=(2000|4000|8000) / ratetol=1\\.0 / qcomp=0\\.60 / qpmin=0 / qpmax=69 / qpstep=4 / cplxblur=20\\.0 / qblur=0\\.5 / vbv_maxrate=(3000|6000|12000) / vbv_bufsize=(4500|9000|18000) / nal_hrd=none / filler=0 / ip_ratio=1\\.40 / aq=1:0\\.60")
}
},
set: {
sourceName: "Crunchyroll"
}
},
{
//Crunchyroll Jan 2023 encoding settings for 480p/720p/1080p
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
height: /^(480|720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
writing_library: "x264 core 164",
encoding_settings: new RegExp("cabac=1 / ref=[46] / deblock=1:1:1 / analyse=(0x1:0x111|0x3:0x113) / me=hex / subme=8 / psy=1 / psy_rd=0\\.40:0\\.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=2 / 8x8dct=[01] / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=(15|22|34) / lookahead_threads=[235] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=0 / weightp=2 / keyint=(96|120) / keyint_min=(48|60) / scenecut=40 / intra_refresh=0 / rc_lookahead=(48|60) / rc=2pass / mbtree=1 / bitrate=(2000|4000|8000) / ratetol=1\\.0 / qcomp=0\\.60 / qpmin=0 / qpmax=69 / qpstep=4 / cplxblur=20\\.0 / qblur=0\\.5 / vbv_maxrate=(3000|6000|12000) / vbv_bufsize=(4500|9000|18000) / nal_hrd=none / filler=0 / ip_ratio=1\\.40 / aq=1:0\\.60")
}
},
set: {
sourceName: "Crunchyroll"
}
},
{
//Crunchyroll new encoding settings for 480p/720p/1080p
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
height: /^(480|720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
writing_library: "x264 core 142",
encoding_settings: new RegExp("cabac=1 / ref=[46] / deblock=1:1:1 / analyse=(0x1:0x111|0x3:0x113) / me=umh / subme=8 / psy=1 / psy_rd=0\\.40:0\\.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=[01] / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=(6|10|12) / lookahead_threads=[12] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=0 / weightp=2 / keyint=(96|120) / keyint_min=(48|60) / scenecut=40 / intra_refresh=0 / rc_lookahead=(48|60) / rc=2pass / mbtree=1 / bitrate=(2000|4000|8000) / ratetol=1\\.0 / qcomp=0\\.60 / qpmin=0 / qpmax=69 / qpstep=4 / cplxblur=20\\.0 / qblur=0\\.5 / vbv_maxrate=(3000|6000|12000) / vbv_bufsize=(4500|9000|18000) / nal_hrd=none / filler=0 / ip_ratio=1\\.40 / aq=1:0\\.60")
}
},
set: {
sourceName: "Crunchyroll"
}
},
{
//Crunchyroll new crf encoding settings for 720p/1080p
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
height: /^(720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
writing_library: "x264 core 142",
encoding_settings: new RegExp("cabac=1 / ref=[46] / deblock=1:1:1 / analyse=0x3:0x113 / me=umh / subme=8 / psy=1 / psy_rd=0\\.40:0\\.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=1 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=(10|12|16) / lookahead_threads=[12] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=2 / b_bias=0 / direct=3 / weightb=1 / open_gop=0 / weightp=2 / keyint=(48|96) / keyint_min=(24|48) / scenecut=40 / intra_refresh=0 / rc_lookahead=48 / rc=crf / mbtree=1 / crf=(15\\.0|16\\.0) / qcomp=0\\.60 / qpmin=0 / qpmax=69 / qpstep=4 / vbv_maxrate=(4000|8000) / vbv_bufsize=(6000|12000) / crf_max=0\\.0 / nal_hrd=none / filler=0 / ip_ratio=1\\.40 / aq=1:0\\.60")
}
},
set: {
sourceName: "Crunchyroll"
}
},
{
//Crunchyroll old encoding settings for 480p/720p/1080p
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
height: /^(480|720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
writing_library: "x264 core 120 r2120 0c7dab9",
encoding_settings: new RegExp("cabac=1 / ref=[46] / deblock=1:1:1 / analyse=(0x1:0x111|0x3:0x113) / me=umh / subme=8 / psy=1 / psy_rd=0\\.40:0\\.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=[01] / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=[48] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=2 / b_bias=0 / direct=3 / weightb=1 / open_gop=0 / weightp=2 / keyint=250 / keyint_min=(23|25) / scenecut=40 / intra_refresh=0 / rc_lookahead=50 / rc=2pass / mbtree=1 / bitrate=(768|1776|3072|3088) / ratetol=1\\.0 / qcomp=0\\.60 / qpmin=0 / qpmax=69 / qpstep=4 / cplxblur=20\\.0 / qblur=0\\.5 / vbv_maxrate=(1536|3552|6144|6176) / vbv_bufsize=(3840|8880|15360|15440) / nal_hrd=none / ip_ratio=1\\.40 / aq=1:0\\.60")
}
},
set: {
sourceName: "Crunchyroll"
}
},
{
//Funimation 360p/720p/1080p
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
height: /^(360|720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
writing_library: "x264 core 157 r2948 dada181",
encoding_settings: new RegExp("cabac=[01] / ref=1 / deblock=1:0:0 / analyse=(0x1:0x111|0x3:0x113) / me=hex / subme=7 / psy=1 / psy_rd=1\\.00:0\\.00 / mixed_ref=0 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=[01] / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=(11|12|22|24) / lookahead_threads=[1-4] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=[02] / ((b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / )|)weightp=[02] / keyint=48 / keyint_min=4 / scenecut=40 / intra_refresh=0 / rc_lookahead=40 / rc=cbr / mbtree=1 / bitrate=(1200|3500|8000) / ratetol=1\\.0 / qcomp=0\\.60 / qpmin=0 / qpmax=69 / qpstep=4 / vbv_maxrate=(1200|3500|8000) / vbv_bufsize=(3600|10500|24000) / nal_hrd=none / filler=0 / ip_ratio=1\\.40 / aq=1:1\\.00")
}
},
set: {
sourceName: "Funimation"
}
},
{
//letv
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
writing_library: /letv.*codec -V [0-9.\-]+$/,
encoding_settings: null
}
},
set: {
sourceName: "letv"
}
},
{
//bilibili
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_AAC/
},
video: {
frame_rate_mode: "Constant",
writing_library: /^BiliBili H264 Encoder v[0-9.\-]+$/,
encoding_settings: null
}
},
set: {
sourceName: "bilibili"
}
},
{
//Google/YouTube
match: {
tags: {
sourceName: null,
source: "Web"
},
video: {
frame_rate_mode: "Constant",
title: /^ISO Media file produced by Google Inc/,
encoding_settings: null
}
},
set: {
sourceName: "YouTube"
}
},
{
//Le.com / LeTV / Leshi Video
match: {
tags: {
sourceName: null,
source: "Web"
},
video: {
frame_rate_mode: "Constant",
writing_library: /^Provided by www\.letv\.com/,
encoding_settings: null
}
},
set: {
sourceName: "Leshi Video",
videoEncoder: null,
}
},
{
//Netflix 2pass old?
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: "A_EAC3",
},
video: {
height: /^(720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
bit_rate_mode: "Variable",
writing_library: /^x264 core 118$/,
encoding_settings: new RegExp("cabac=1 / ref=3 / deblock=1:0:0 / analyse=0x1:0x111 / me=umh / subme=10 / psy=1 / psy_rd=1\\.00:0\\.00 / mixed_ref=1 / me_range=24 / chroma_me=1 / trellis=2 / 8x8dct=0 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=[68] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=2 / b_pyramid=0 / b_adapt=2 / b_bias=0 / direct=3 / weightb=1 / open_gop=0 / weightp=2 / keyint=48 / keyint_min=(23|24|25) / scenecut=0 / intra_refresh=0 / rc_lookahead=0 / rc=2pass / mbtree=1 / bitrate=[0-9]+ / ratetol=1\\.0 / qcomp=0\\.50 / qpmin=6 / qpmax=51 / qpstep=4 / cplxblur=20\\.0 / qblur=0\\.5 / vbv_maxrate=[0-9]+ / vbv_bufsize=[0-9]+ / nal_hrd=vbr / ip_ratio=1\\.40 / aq=1:1\\.00")
}
},
set: {
sourceName: "Netflix*"
}
},
{
//Netflix 2pass?
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_E?AC3$/,
},
video: {
height: /^(720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
bit_rate_mode: "Variable",
writing_library: /^x264 core 148 r[0-9]+ [0-9a-f]+$/,
encoding_settings: new RegExp("cabac=1 / ref=3 / deblock=1:0:0 / analyse=0x1:0x111 / me=umh / subme=10 / psy=1 / psy_rd=1\\.00:0\\.00 / mixed_ref=1 / me_range=24 / chroma_me=1 / trellis=2 / 8x8dct=0 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=[68] / lookahead_threads=1 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / stitchable=1 / constrained_intra=0 / bframes=2 / b_pyramid=0 / b_adapt=2 / b_bias=0 / direct=3 / weightb=1 / open_gop=0 / weightp=2 / keyint=(48|250) / keyint_min=(23|24|25) / scenecut=0 / intra_refresh=0 / rc_lookahead=48 / rc=2pass / mbtree=1 / bitrate=[0-9]+ / ratetol=1\\.0 / qcomp=0\\.50 / qpmin=6 / qpmax=51 / qpstep=4 / cplxblur=20\\.0 / qblur=0\\.5 / vbv_maxrate=[0-9]+ / vbv_bufsize=[0-9]+ / nal_hrd=vbr / filler=0 / ip_ratio=1\\.40 / aq=1:1\\.00")
}
},
set: {
sourceName: "Netflix*"
}
},
{
//Netflix crf 18/20?
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^A_E?AC3$/,
},
video: {
height: /^(720|(1 080)) pixels$/,
frame_rate_mode: "Constant",
bit_rate_mode: "Variable",
writing_library: /^x264 core 148 r[0-9]+ [0-9a-f]+$/,
encoding_settings: new RegExp("cabac=1 / ref=4 / deblock=1:0:0 / analyse=0x3:0x111 / me=umh / subme=10 / psy=1 / psy_rd=1\\.00:0\\.00 / mixed_ref=1 / me_range=120 / chroma_me=1 / trellis=2 / 8x8dct=1 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=[68] / lookahead_threads=[12] / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / stitchable=1 / constrained_intra=0 / bframes=16 / b_pyramid=2 / b_adapt=2 / b_bias=0 / direct=3 / weightb=1 / open_gop=0 / weightp=2 / keyint=(240) / keyint_min=(121) / scenecut=0 / intra_refresh=0 / rc_lookahead=240 / rc=crf / mbtree=1 / crf=(18\\.0|19\\.0|20\\.0|21\\.0) / qcomp=0\\.50 / qpmin=6 / qpmax=51 / qpstep=4 / vbv_maxrate=(14000|20000) / vbv_bufsize=(14000|25000) / crf_max=0\\.0 / nal_hrd=vbr / filler=0 / ip_ratio=1\\.40 / aq=1:0\\.80")
}
},
set: {
sourceName: "Netflix*"
}
},
{
//Amazon Prime Video?
match: {
tags: {
sourceName: null,
source: "Web"
},
audio: {
codec_id: /^(A_AAC|A_EAC3$)/
},
video: {
codec_id: "V_MPEG4/ISO/AVC",
format_profile: /^((High@(L4|L3\.1))|Main@L3)$/,
format_settings_cabac: "Yes",
frame_rate_mode: "Constant",
writing_library: null,
encoding_settings: null,
color_range: "Limited",
color_primaries: "BT.709",
transfer_characteristics: "BT.709",
matrix_coefficients: "BT.709",
codec_configuration_box: null,
},
custom: (sourceTags, mediainfo, tags, video, audio, text) => {
let match = (mediainfo.audio.length === 1 && "codec_id" in mediainfo.audio[0] && mediainfo.audio[0].codec_id.match(/^A_EAC3$/) !== null) ||
(mediainfo.audio.length === 2 && "codec_id" in mediainfo.audio[0] && "codec_id" in mediainfo.audio[1] && ((mediainfo.audio[0].codec_id.match(/^(A_AAC|^A_EAC3$)/) !== null && mediainfo.audio[1].codec_id.match(/^A_EAC3$/) !== null) || (mediainfo.audio[1].codec_id.match(/^(A_AAC|^A_EAC3$)/) !== null && mediainfo.audio[0].codec_id.match(/^A_EAC3$/) !== null)));
if(match){
let eac3Track = mediainfo.audio[0].codec_id.match(/^A_EAC3$/) !== null ? mediainfo.audio[0] : ((mediainfo.audio.length > 1 && mediainfo.audio[1].codec_id.match(/^A_EAC3$/) !== null) ? mediainfo.audio[1] : null);
if(eac3Track === null){
match = false;
}
if("bit_rate_mode" in video && video.bit_rate_mode !== "Constant"){
match = false;
}
switch (parseInt(video.width.replace(/[^0-9]/g, ""))){
case 1920:
if("nominal_bit_rate" in video && video.nominal_bit_rate !== "10 000 kb/s"){
match = false;
}
if(video.format_profile !== "High@L4"){
match = false;
}
if(("format_settings_reframes" in video && video.format_settings_reframes !== "4 frames") || ("format_settings_reference_frames" in video && video.format_settings_reference_frames !== "4 frames")){
match = false;
}
break;
case 1280:
if("nominal_bit_rate" in video && video.nominal_bit_rate !== "4 000 kb/s" && video.nominal_bit_rate !== "6 000 kb/s"){
match = false;
}
if(video.format_profile !== "High@L3.1"){
match = false;
}
if(("format_settings_reframes" in video && video.format_settings_reframes !== "5 frames" && video.format_settings_reframes !== "4 frames") || ("format_settings_reference_frames" in video && video.format_settings_reference_frames !== "5 frames" && video.format_settings_reference_frames !== "4 frames")){
match = false;
}
break;
case 720:
/*if("nominal_bit_rate" in video && video.nominal_bit_rate !== "4 000 kb/s"){
match = false;
}*/
if(video.format_profile !== "Main@L3"){
match = false;
}
if(("format_settings_reframes" in video && video.format_settings_reframes !== "5 frames") || ("format_settings_reference_frames" in video && video.format_settings_reference_frames !== "5 frames")){
match = false;
}
break;
default:
match = false;
}
switch (eac3Track.channels){
case 2:
if("bit_rate_mode" in eac3Track && eac3Track.bit_rate_mode !== "Constant"){
match = false;
}
if("bit_rate" in eac3Track && eac3Track.bit_rate !== "224 kb/s"){
match = false;
}
if("number_of_dynamic_objects" in eac3Track){ //No Atmos
match = false;
}
break;
case 6:
if("bit_rate_mode" in eac3Track && eac3Track.bit_rate_mode !== "Constant"){
match = false;
}
if("bit_rate" in eac3Track && eac3Track.bit_rate !== "640 kb/s"){
match = false;
}
if("number_of_dynamic_objects" in eac3Track){ //No Atmos
match = false;
}
break;
default:
match = false;
}
}
return match;
},
},
set: {
sourceName: "Amazon Prime Video*"
}
},
];
const settings = new ScriptSettings("");
settings.addSetting("handleMusicVideos", "Process Music", "Process Video formats under Music categories. Example: DVD, PV, Live", "bool", true);
settings.addSetting("handleUploadAutofill", "Autofill Upload Details", "Parse Mediainfo on upload and autofill details.", "bool", true);
settings.addSetting("enhanceTags", "Enhance Tags", "Entries will have their tags replaced/extended. Formats (like Atmos, HDR, higher bitDepth, chroma) and detected Sources will be labeled accordingly. Disabling this removes most of this scripts functions.", "bool", true);
settings.addSetting("openFormats", "Label Open Formats", "Entries that are composed fully of known Open Formats/Codecs will be labeled as such.", "bool", true);
settings.addSetting("showContainerVersions", "Show Container Versions", "Display container versions. Currently implemented for Matroska / WebM", "bool", false);
settings.addSetting("showEncodingDetails", "Show Encoding Details", "Display encoding details such as encoder, mode, CRF, and bitrate", "bool", true);
settings.addSetting("showAudioDetails", "Show Audio Details", "Display extra audio details such as bit-depth, bitrate.", "bool", false);
settings.addSetting("handleAnamorphicVideo", "Handle Anamorphic Video", "Use Display Aspect Ratio information to calculate display resolution.", "bool", true);
settings.addSetting("regexpFilter", "RegExp Filter", "Any entry that matches one of these RegExp (without delimiters) will be filtered out. Can enter a different one per line. Empty to disable", "text", "");
settings.addSetting("extraStyles", "CSS Styles", "Extra CSS styles to add to Torrent pages. Empty to disable. Enter a single style rule per line.", "text", "");
settings.addSetting("script_deliciousCompatMode", "Enable Delicious Compatibility", "Enable workarounds to make output compatible with AnimeBytes Delicious user scripts Bundle", "bool", true);
settings.addSetting("script_evaHighlightEmulation", "Enable Eva's torrent highlighter emulation", "Create classes and data tags similar to Eva's torrent highlighter, without breaking this script functions.", "bool", true);
settings.addSetting("script_abHighlights2Emulation", "Enable AB Highlights 2 emulation", "Create classes and data tags similar to AB Highlights 2, without breaking this script functions.", "bool", true);
settings.addSetting("warning_mediainfoSource", "General: Mediainfo Source", "Alert when Mediainfo was found on Description and not on Mediainfo tab.", "bool", true);
settings.addSetting("warning_mediainfoInvalid", "General: Mediainfo not found", "Alert when Mediainfo was not found or invalid", "bool", true);
settings.addSetting("warning_tagMismatch", "General: Mismatched Tags", "Alert when site tags differ from calculated tags", "bool", true);
settings.addSetting("warning_orderedChapters", "General: Ordered Chapters", "Warn when Ordered Chapters are found", "bool", true);
settings.addSetting("warning_japaneseNumbering", "General: Japanese Episode Numbering", "Inform when episodes are labeled in Japanese. Example: 第02話", "bool", true);
settings.addSetting("warning_handbrakeEncoder", "General: HandBrake Encode", "Inform when HandBrake is found on a release.", "bool", true);
settings.addSetting("warning_hwEncoder", "Video: Hardware Encoded", "Alert when a hardware encoder is found on a release.", "bool", true);
settings.addSetting("warning_encodeProfile", "Video: Low Profile", "Warn when low encoding profile is found on encode settings.", "bool", true);
settings.addSetting("warning_encodeSettings", "Video: Settings Information", "Show extra information messages about encode settings.", "bool", true);
settings.addSetting("warning_progressiveFpsTooHigh", "Video: High FPS Threshold", "Alert when fps for progressive scan encodes is above this number.", "int", 30);
settings.addSetting("warning_defaultAudioNotJapanese", "Audio: Default not Japanese", "Inform when Default audio is not Japanese (Does not apply to Live Action)", "bool", true);
settings.addSetting("warning_bloatPCM", "Audio: bloat, PCM", "Warn when any audio track is left as PCM (Does not apply to remux or raw sources)", "bool", true);
settings.addSetting("warning_bloatg16bit", "Audio: bloat, >16-bit", "Warn when specific lossless audio has bit depth larger than 16-bit (Does not apply to remux or raw sources)", "bool", true);
settings.addSetting("warning_signsDefault", "Text: Default is Signs", "Warn when Default track is a known Signs track", "bool", true);
settings.addSetting("warning_textLabeling", "Text: Wrongly Labeled Subtitles", "Warn when tracks are believed not their Language labeled not English.", "bool", true);
settings.addSetting("warning_externalSubs", "Text: External Subtitles", "Warn when only external subtitles are present", "bool", true);
settings.addSetting("debug_printGroup", "Print Torrent Groups", "Outputs the torrent group result of matching and parsing on console for inspection.", "bool", false);
settings.addSetting("debug_flagUnknownCodec", "Flag Unknown Codecs", "Flags unknown codecs that have not been matched internally.", "bool", false);
const torrentTypeImage = document.querySelector("a.scaledImg > img");
const torrentType = torrentTypeImage ? torrentTypeImage.getAttribute("title") : null;
const torrentTypeHeader = document.querySelector("#content .thin > h2");
const torrentTypeMusic = torrentTypeHeader ? torrentTypeHeader.textContent.split(/[()\[\]\-]/g).filter((a) => a).slice(-1)[0] : null;
const torrentTableColspan = 6;
if(
(torrentType !== null && ABClass.videoTypes.indexOf(torrentType.replace("Live Action ", "")) !== -1) ||
(settings.getSetting("handleMusicVideos") && torrentTypeMusic !== null && ABClass.musicVideoTypes.indexOf(torrentTypeMusic) !== -1)
){
const isMusicType = torrentTypeMusic !== null && ABClass.musicVideoTypes.indexOf(torrentTypeMusic) !== -1;
const isLiveActionType = torrentType !== null && torrentType.match(/Live Action/) !== null;
console.log("Found entry type " + (isMusicType ? torrentTypeMusic : torrentType));
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 !== null && torrentType.match(/Movie$/) !== null){
episodeCount = 1;
}
let torrentListing = [];
document.querySelectorAll(".group_torrent").forEach((item) => {
const downloadLink = item.querySelector("td > span a[title^='Download']");
const linkSection = downloadLink.closest("td");
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);
if(!torrentEntry){
//Skip unknown entries
return;
}
let tags = {
audioCount: 1,
freeleech: torrentEntry.querySelector("img[alt^='Freeleech!']") !== null,
remastered: torrentEntry.querySelector("img[alt^='Remastered']") !== null,
censoredHentai: torrentEntry.querySelector("img[src*='/hentaic-']") !== null,
uncensoredHentai: torrentEntry.querySelector("img[src*='/hentai-']") !== null,
snatched: torrentEntry.textContent.match(/- Snatched/) !== null,
reported: torrentEntry.textContent.match(/- Reported/) !== null,
pruned: torrentEntry.textContent.match(/- Pruned!/) !== null
};
torrentEntry.textContent.replace("»", "").replace(/- (Snatched|Pruned!|Reported)/g, "").split(/( \| | \/ )/).forEach((t) => {
let tagEntry = t.trim();
if(tagEntry === ""){
return;
}
let match = null;
if(!("source" in tags) && ABClass.sources.indexOf(tagEntry) !== -1){
tags.source = tagEntry;
}else if(!("container" in tags) && ABClass.containers.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) && ABClass.videoCodecs.some((i) => tagEntry.startsWith(i))){
tags.videoCodec = tagEntry;
}else if(!("audioCodec" in tags) && ABClass.audioCodecs.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]+$/i) !== null || tagEntry.match(/^(720p|1080p|1080i|4K)$/i) !== null){
tags.resolution = tagEntry.toLowerCase();
}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){
torrent.elements.data = dataSection;
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 t = mediainfoItem.textContent.trim();
let index = t.indexOf("General");
if(index !== -1){
let m = Mediainfo.parseText(t.substr(index));
if(m !== null){
m_list.push(m);
}
}
});
if(m_list.length > 0){
torrent.mediainfo = m_list;
torrent.mediainfoSource = "mediainfo";
}
}else if(descriptionSection !== null){
//Find mediainfo tags
torrent.elements.description.querySelectorAll("input.spoilerButton").forEach((e) => {
let ee = e.parentElement.querySelector("div.spoiler");
let tt = ee.querySelector("pre");
if(!tt){
tt = ee;
}
let t = tt.textContent.trim()
let index = t.indexOf("General");
if(index !== -1){
let m = Mediainfo.parseText(t.substr(index));
if(m !== null){
if(!("mediainfo" in torrent)){
torrent.mediainfo = [];
torrent.mediainfoSource = "description spoiler";
}
torrent.mediainfo.push(m);
}
}
});
if(!("mediainfo" in torrent)){
//Fallback, find General tag
let t = torrent.elements.description.parentElement.textContent.trim();
let index = t.indexOf("General");
if(index !== -1){
let m = Mediainfo.parseText(t.substr(index));
if(m !== null){
if(!("mediainfo" in torrent)){
torrent.mediainfo = [];
torrent.mediainfoSource = "description";
}
torrent.mediainfo.push(m);
}
}
}
}
torrentListing.push(torrent);
}
});
{
{
let parentTd = document.querySelector("table.torrent_table tr > td[width*='%']");
parentTd.insertAdjacentElement("beforebegin", document.createElement("td"));
// Source + container + remux | Codec / Resolution / HDR | Audio + Dual + commentary | Text | OtherTags | Icons
parentTd.setAttribute("colspan", torrentTableColspan.toString());
document.querySelectorAll("table.torrent_table tr.edition_info > td").forEach((e) => {
e.setAttribute("colspan", (parseInt(e.getAttribute("colspan")) + torrentTableColspan).toString());
});
}
torrentListing.forEach((torrent) => {
let parent = torrent.elements.link.closest("span:not(.download_link)");
let td = document.createElement("td");
let element = torrent.elements.data.querySelector("td");
element.setAttribute("colspan", (parseInt(element.getAttribute("colspan")) + torrentTableColspan).toString());
parent.closest("td").insertAdjacentElement("beforebegin", td);
parent.style["white-space"] = "nowrap";
td.append(parent);
Array.from(parent.childNodes).forEach((e) => {
if(e.nodeType === Node.TEXT_NODE && ["[", "]"].includes(e.textContent.trim())){
e.remove();
}
});
});
}
GM_addStyle("#content{ width: -moz-fit-content; width: fit-content; max-width: calc(100% - 50px); }");
GM_addStyle(`
@font-face {
font-family: 'EmojiSymbols'; /*This is what your font will be named*/
src: local('EmojiSymbols-Regular'),
url('`+ getResource("EmojiSymbols") + `')
format('woff');
}
`);
GM_addStyle(".emoji{ font-family: 'EmojiSymbols'; }");
GM_addStyle(".group_torrent .icons { font-weight: normal; font-style: normal; font-size: 16px; text-shadow: 0px 0px 2px #000;}");
GM_addStyle("#content{ max-width: calc(100% - 50px); width: -moz-fit-content; width: fit-content; }");
GM_addStyle("table.torrent_table { width: -moz-fit-content; width: fit-content; min-width: calc(950px - 240px); }");
GM_addStyle("table.torrent_table tr > td[width*='%'] { width: -moz-fit-content; width: fit-content; max-width: 80%;");
if(settings.getSetting("script_deliciousCompatMode")){
//Fix delicious user scripts
GM_addStyle("div.torrent_filter_box { width: -moz-fit-content !important; width: fit-content !important; float: none !important; min-width: calc(950px - 240px); }");
}
settings.getSetting("extraStyles").split("\n").forEach((s) => {
if(s.trim() === ""){
return;
}
GM_addStyle(s);
});
const torrentTableElement = document.querySelector("div#content table.torrent_table");
const resizeCallback = () => {
document.querySelectorAll(isMusicType ? "#torrents2 div.main_column > table:not(.torrent_table), #torrents2 div.main_column > div" : "#torrents div.main_column > table:not(.torrent_table), #torrents div.main_column > div").forEach((e) => {
e.style["width"] = torrentTableElement.offsetWidth + "px";
});
};
const tableResizeObserver = new ResizeObserver(resizeCallback);
tableResizeObserver.observe(torrentTableElement);
resizeCallback();
if(settings.getSetting("debug_printGroup")){
console.log(torrentListing);
}
torrentListing.forEach((torrent) => {
try {
let tags = {
};
let warnings = {
general: [],
audio: [],
video: [],
text: []
}
let fileName = null;
if("mediainfo" in torrent && torrent.mediainfo.length > 0 && "complete_name" in torrent.mediainfo[0].general){
[fileName] = torrent.mediainfo[0].general.complete_name.split(/[/\\]/g).slice(-1);
}else{
torrent.filelist.every((f) => {
[f] = f.path.split("/").slice(-1);
const ext = f.split(".").slice(-1)[0].toUpperCase();
if(ext === "ISO" || ext === "MKV" || ext === "MP4" || ext === "OGM" || ext === "AVI"){
fileName = f;
}
return fileName === null;
});
if(fileName === null){
[fileName] = torrent.filelist[0].path.split("/").slice(-1);
}
}
let japaneseEpisodeMatch = fileName.match(/第[0-9.]+話/u);
if(japaneseEpisodeMatch !== null && settings.getSetting("warning_japaneseNumbering")){
warnings.general.push(["info", "Episode numbering is in Japanese format: example " + japaneseEpisodeMatch[0]]);
}
for(const [key, value] of Object.entries(extractTagsFromFilename(fileName, torrent.tags, torrent.tags.source))){
tags[key] = value;
}
if("mediainfo" in torrent && torrent.mediainfo.length > 0 && torrent.mediainfo[0].video && torrent.mediainfo[0].video.length > 0){
let mediainfo = torrent.mediainfo[0];
if(torrent.mediainfoSource !== "mediainfo" && settings.getSetting("warning_mediainfoSource")){
warnings.general.push(["mediainfo", "Mediainfo sourced from " + torrent.mediainfoSource + ". Consider reporting the torrent and providing it."])
}
let isFileMatch = false;
const fileNameToMatch = fileName.toLowerCase().replace(/[ \t\r\n]/g, "");
torrent.filelist.every((file) => {
if(file.path.split("/").slice(-1)[0].toLowerCase().replace(/[ \t\r\n]/g, "") === fileNameToMatch){
isFileMatch = true;
}
return !isFileMatch;
});
if(!isFileMatch){
warnings.general.push(["warning", "Source file \""+fileName+"\" not found on torrent file list. It might be part of an archive."]);
}
extractFromMediainfo(tags, mediainfo, warnings, fileName, torrent.filelist, torrent.tags, isLiveActionType);
}else{
torrent.elements.item.style["font-style"] = "italic";
if(settings.getSetting("warning_mediainfoInvalid")){
warnings.text.push(["mediainfo", "No valid mediainfo section found. Report torrent with MediaInfo."]);
}
}
for(const [key, value] of Object.entries(torrent.tags)){
if(!(key in tags)){
tags[key] = value;
}
}
if(!("aspectRatio" in torrent.tags) && "aspectRatio" in tags){
torrent.tags.aspectRatio = tags.aspectRatio;
}
//console.log(tags);
let oldTagLine = getComparisonLine(torrent.tags);
let newTagLine = getComparisonLine(tags);
if(newTagLine.toLowerCase() !== oldTagLine.toLowerCase() && settings.getSetting("warning_tagMismatch")){
warnings.general.push(["danger", "Tag mismatch:\nold: " + oldTagLine + " !=\nnew: " + newTagLine]);
}
let messages = document.createElement("div");
messages.append(document.createElement("br"));
let messageCount = 0;
for(const [key, value] of Object.entries(warnings)){
if(value.length > 0){
let h3 = document.createElement("h3");
h3.textContent = key.toUpperCase() + " MESSAGES";
messages.append(h3);
let messageList = document.createElement("ul");
for(let v of value){
let li = document.createElement("li");
li.style.float = "inherit";
let value = "";
if(Array.isArray(v)){
switch (v[0]){
case "info":
value = "info: " + v[1];
break;
case "warning":
value = "warning: " + v[1];
break;
case "danger":
value = "danger: " + v[1];
break;
case "mediainfo":
value = "mediainfo: " + v[1];
break;
}
}else{
value = v;
}
++messageCount;
if(value.includes("\n")){
let pre = document.createElement("pre");
pre.textContent = value;
li.append(pre);
}else{
li.textContent = value;
}
messageList.append(li);
}
messages.append(messageList);
messages.append(document.createElement("br"));
}
}
messages.append(document.createElement("hr"));
if(messageCount > 0){
if("description" in torrent.elements){
torrent.elements.description.prepend(messages);
}
}
/*while(torrent.elements.entry.firstChild){
torrent.elements.entry.firstChild.remove();
}*/
torrent.elements.entry.parentElement.remove();
let entries = getEntryLine(settings.getSetting("enhanceTags") === false ? torrent.tags : tags, warnings);
let lastTd = torrent.elements.link.closest("td");
let onclickFn = (e) => {
torrent.elements.data.classList.toggle("hide");
e.preventDefault();
};
let entryText = [];
let fields = [];
["icons", "source", "video", "audio", "text", "other"].forEach((k) => {
let td = document.createElement("td");
td.style["max-width"] = "calc(80% / " + torrentTableColspan + ")";
if(k !== "other"){
td.style["width"] = "fit-content";
}
if(k === "icons"){
td.style["padding"] = "0px 5px";
}
td.style["cursor"] = "pointer";
let a = document.createElement("a");
if(tags.snatched){
a.classList.add("snatched-torrent");
}
a.setAttribute("href", torrent.elements.entry.href);
a.addEventListener("click", (e) => e.preventDefault());
td.addEventListener("click", onclickFn);
entries[k].forEach((e) => {
if(a.firstChild){
a.append(" / ");
}
a.append(e);
});
if(settings.getSetting("script_evaHighlightEmulation")){
a.classList.add("userscript-highlight");
}
if(settings.getSetting("script_abHighlights2Emulation")){
a.classList.add("userscript-highlight", "torrent-page");
a.querySelectorAll("span.userscript-highlight").forEach((e) => {
e.getAttributeNames().forEach((name) => {
if(name.match(/^data-/) !== null && name !== "data-field"){
let attr = e.getAttribute(name);
a.setAttribute(name, e.getAttribute(name));
fields.push(attr);
}
});
});
}
td.append(a);
lastTd.insertAdjacentElement("afterend", td)
lastTd = td;
entryText.push(td.textContent.trim());
});
if(settings.getSetting("script_abHighlights2Emulation")) {
lastTd.parentElement.querySelectorAll("td > a.userscript-highlight").forEach((e) => {
e.setAttribute("data-fields", fields.join(" "))
})
}
if(settings.getSetting("script_deliciousCompatMode")){
let hiddenParent = torrent.elements.link.closest("td");
let span = document.createElement("span");
span.style["display"] = "none";
span.classList.add("fix_delicious_metadata");
span.append(getComparisonLine(settings.getSetting("enhanceTags") === false ? torrent.tags : tags, false));
hiddenParent.append(span);
}
entryText = entryText.join(" | ");
settings.getSetting("regexpFilter").split("\n").every((regexpFilter) => {
regexpFilter = regexpFilter.trim();
if(regexpFilter !== "" && entryText.match(new RegExp(regexpFilter)) !== null){
//Filter out, only if not directly linked
if(!window.location.href.includes("torrentid=" + torrent.id)){
torrent.elements.item.style["display"] = "none";
torrent.elements.data.style["display"] = "none";
}
return false;
}
return true;
});
}catch (e){
console.log("Error:", e);
console.log(torrent);
//Fix entries
torrent.elements.entry.parentElement.setAttribute("colspan", torrentTableColspan.toString());
}
});
}else if(window.location.href.match(/upload\.php/) !== null && settings.getSetting("handleUploadAutofill")){
let mediainfoField = document.getElementById("mediainfo_desc");
if(mediainfoField !== null){
let errorField = document.createElement("p");
errorField.style["font-weight"] = "bold";
errorField.style["display"] = "none";
mediainfoField.closest("dd").append(errorField);
const eventHandler = () => {
let text = mediainfoField.value;
let mediainfo = null;
errorField.style["display"] = "none";
try{
let index = text.indexOf("General");
if(index !== -1){
let m = Mediainfo.parseText(text.substr(index));
if(m !== null){
mediainfo = m;
}
}
}catch (e) {
}
if(mediainfo === null){
if(text.trim() !== ""){
errorField.textContent = "Could not parse Mediainfo. Maybe it is invalid / not English / not Mediainfo?"
errorField.style["display"] = "";
}
}else{
let sourceElement = document.getElementById("media");
let containerElement = document.getElementById("containers_anime");
let videoCodecElement = document.getElementById("codecs");
let resolutionListElement = document.getElementById("resolution_anime");
let resolutionOtherElement = document.getElementById("other_resolution_anime");
let regionElement = document.getElementById("region_anime");
let arElement = document.getElementById("aspectratio_anime");
let audioCodecElement = document.getElementById("audio");
let audioChannelsElement = document.getElementById("audiochannels");
let dualAudioElement = document.getElementById("dual_audio");
let subbingElement = document.getElementById("subbing");
let isLiveActionType = false;
let sourceTags = {
source: sourceElement.value,
container: containerElement.value,
videoCodec: videoCodecElement.value,
resolution: resolutionListElement.value !== "OTHER" ? resolutionListElement.value : resolutionOtherElement.value,
region: regionElement.value,
aspectRatio: arElement.value,
audioCodec: audioCodecElement.value,
audioChannels: audioChannelsElement.value.split(".").reduce((p, c) => parseInt(p) + parseInt(c)),
audioCount: dualAudioElement.checked ? 2 : 1,
};
if(subbingElement.value === "Softsubs" || subbingElement.value === "Hardsubs"){
sourceTags.subtitleType = subbingElement.value;
}else{
sourceTags.subtitleType = "Guess";
}
let tags = {
};
let warnings = {
general: [],
audio: [],
video: [],
text: []
}
try{
let fileName = "complete_name" in mediainfo.general ? mediainfo.general.complete_name.split(/[/\\]/g).slice(-1)[0] : "";
for(const [key, value] of Object.entries(extractTagsFromFilename(fileName, sourceTags, sourceTags.source))){
tags[key] = value;
}
extractFromMediainfo(tags, mediainfo, warnings, fileName, [{path: fileName, size: 0}], sourceTags, isLiveActionType);
for(const [key, value] of Object.entries(sourceTags)){
if(!(key in tags)){
tags[key] = value;
}
}
let resolution = getLineTagEntry(tags.resolution, true);
if(resolution === "4K" || resolution === "1080p" || resolution === "1080i" || resolution === "720p" ){
resolutionListElement.value = resolution;
resolutionOtherElement.value = "";
resolutionOtherElement.closest("dd").style["display"] = "none";
}else{
resolutionListElement.value = "OTHER";
resolutionOtherElement.value = resolution;
resolutionOtherElement.closest("dd").style["display"] = "";
}
const addMessage = (fieldElement, text) => {
let parentDD = fieldElement.closest("dd");
let e = parentDD.querySelector("p.mediainfo");
if(e === null){
e = document.createElement("p");
e.classList.add("mediainfo");
parentDD.append(e);
}
e.textContent = "Detected: " + text;
};
sourceElement.value = getLineTagEntry(tags.source, true);
containerElement.value = getLineTagEntry(tags.container, true);
addMessage(containerElement, getLineTagEntry(tags.container, false));
if(containerElement.value === "ISO" || containerElement.value === "VOB IFO" || containerElement.value === "M2TS"){
regionElement.closest("dd").style["display"] = "";
arElement.closest("dd").style["display"] = "";
}else{
regionElement.closest("dd").style["display"] = "none";
arElement.closest("dd").style["display"] = "none";
}
videoCodecElement.value = getLineTagEntry(tags.videoCodec, true);
addMessage(videoCodecElement, getLineTagEntry(tags.videoCodec, false));
if(regionElement.closest("dd").style["display"] !== "none"){
regionElement.value = getLineTagEntry(tags.region, true);
}
if(arElement.closest("dd").style["display"] !== "none"){
arElement.value = getLineTagEntry(tags.aspectRatio, true);
addMessage(arElement, getLineTagEntry(tags.aspectRatio, false));
}
audioCodecElement.value = getLineTagEntry(tags.audioCodec, true);
addMessage(audioCodecElement, getLineTagEntry(tags.audioCodec, false));
audioChannelsElement.value = Mediainfo.getAudioChannels(getLineTagEntry(tags.audioChannels, true));
addMessage(audioChannelsElement, Mediainfo.getAudioChannels(getLineTagEntry(tags.audioChannels, false)));
subbingElement.value = getLineTagEntry(tags.subtitleType, true);
addMessage(subbingElement, ("subtitleCodec" in tags ? tags.subtitleCodec + " " : "") + getLineTagEntry(tags.subtitleType, false));
if((!("audioLanguages" in tags) || "English" in tags.audioLanguages) && tags.audioCount > 1){
dualAudioElement.checked = 1;
}else{
dualAudioElement.checked = 0;
}
}catch (e) {
errorField.textContent = e;
errorField.style["display"] = "";
}
}
};
mediainfoField.addEventListener("input", eventHandler);
mediainfoField.addEventListener("change", eventHandler);
mediainfoField.addEventListener("keyup", eventHandler);
}
}else if(window.location.href.match(/user\.php\?action=edit/) !== null){
let tabs = document.querySelector("ul.ue_tabs");
let sections = document.getElementById("tabs");
let sectionsHandle = sections.querySelector("p > input[type='submit']").closest("p");
let section = document.createElement("div");
section.id = "mediainfo_settings";
section.style["display"] = "none";
let e = document.createElement("div");
e.classList.add("head", "colhead_dark", "strong");
e.textContent = "Mediainfo Userscript Settings";
section.append(e);
let ul = document.createElement("ul");
ul.classList.add("nobullet", "ue_list");
section.append(ul);
e = document.createElement("div");
e.classList.add("head", "colhead_dark", "strong");
e.textContent = "Mediainfo Userscript Messages";
section.append(e);
let ul_warn = document.createElement("ul");
ul_warn.classList.add("nobullet", "ue_list");
section.append(ul_warn);
e = document.createElement("div");
e.classList.add("head", "colhead_dark", "strong");
e.textContent = "Mediainfo Userscript Debugging";
section.append(e);
let ul_debug = document.createElement("ul");
ul_debug.classList.add("nobullet", "ue_list");
section.append(ul_debug);
Object.values(settings.settings).forEach((setting) => {
let li = document.createElement("li");
let name = document.createElement("span");
name.classList.add("ue_left", "strong");
name.textContent = setting.name;
li.append(name);
let entry = document.createElement("span");
entry.classList.add("ue_right");
let input = setting.type === "text" ? document.createElement("textarea") : document.createElement("input");
input.id = "mediainfo_" + setting.id;
switch (setting.type){
case "bool":
input.setAttribute("type", "checkbox");
input.checked = setting.value;
break;
case "text":
input.setAttribute("type", "text");
input.value = setting.value;
input.style["min-width"] = "300px";
input.style["min-height"] = "60px";
break;
case "string":
input.setAttribute("type", "text");
input.value = setting.value;
break;
case "int":
input.setAttribute("type", "number");
input.value = setting.value;
break;
}
const eventHandler = () => {
switch (setting.type){
case "bool":
settings.setSetting(setting.id, input.checked);
break;
case "text":
settings.setSetting(setting.id, input.value);
break;
case "string":
settings.setSetting(setting.id, input.value);
break;
case "int":
settings.setSetting(setting.id, parseInt(input.value));
break;
}
};
input.addEventListener("input", eventHandler);
input.addEventListener("change", eventHandler);
input.addEventListener("keyup", eventHandler);
entry.append(input);
if(setting.type === "text"){
entry.append(document.createElement("br"));
}
let desc = document.createElement("label");
desc.setAttribute("for", input.id);
desc.textContent = " " + setting.description;
entry.append(desc);
li.append(entry);
if(setting.id.match(/^warning_/) !== null){
ul_warn.append(li);
}else if(setting.id.match(/^debug_/) !== null){
ul_debug.append(li);
}else{
ul.append(li);
}
});
sectionsHandle.insertAdjacentElement('beforebegin', section);
if(settings.getSetting("script_deliciousCompatMode")){
let div = document.getElementById("#potatoes_setting");
if(div){
//Fix their styling so it defaults to hidden
div.style["display"] = "none";
sectionsHandle.insertAdjacentElement('beforebegin', div);
}else{
}
}
let tab = document.createElement("li");
let a = document.createElement("a");
a.href = "#" + section.id;
a.textContent = "Mediainfo Settings";
tab.append("•");
tab.append(a);
tabs.append(tab);
}