// ==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 (?[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(?[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 (?[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(?[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=(?[0-9a-z]+)/i)) !== null){ tags.videoEncodeMode = match.groups.rc.toLowerCase(); } if((match = video.encoding_settings.match(/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=(?[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=(?[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(/(?(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(/\((?[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\/(?[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(/^(?[^(]+) \((?[^)]+)\)$/)) !== 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(/^(?.+) (?[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(/^(?(RAW|Hardsubs|Softsubs))( \()?(?[^)]*)(\))?$/)) !== 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); }