2528 lines
110 KiB
JavaScript
2528 lines
110 KiB
JavaScript
// ==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);
|
||
}
|