DolbyAutoTools 助手

自动提取 TMDb 信息、识别编码格式和下拉选项

// ==UserScript==
// @name         DolbyAutoTools 助手
// @namespace    https://www.hddolby.com/
// @version      1.0.17
// @description  自动提取 TMDb 信息、识别编码格式和下拉选项
// @match        https://www.hddolby.com/upload.php
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    function createButton() {
        const descrBox = document.querySelector('#descr');
        if (!descrBox) return;

        const row = descrBox.closest('td');
        const button = document.createElement('button');
        button.textContent = '处理信息';
        button.type = 'button';
        button.style.margin = '5px 0';
        button.style.padding = '4px 10px';
        button.addEventListener('click', handleClick);
        row.appendChild(button);
    }

    function selectByMap(map, selectName) {
        let name = document.querySelector('#name')?.value?.toLowerCase() || '';
        const select = document.querySelector(`select[name="${selectName}"]`);
        if (!select) return;

        // 针对 team_sel,只识别最后一个'-'后面的内容
        if (selectName === 'team_sel') {
            // 匹配最后一个'-'后面的非空白字符
            const teamMatch = name.match(/-([^\s-]+)$/);
            if (teamMatch) {
                const teamKey = teamMatch[1];
                for (const [key, value] of Object.entries(map)) {
                    if (teamKey.includes(key)) {
                        select.value = value;
                        break;
                    }
                }
                return;
            }
        }

        // 其他情况保持原有逻辑
        for (const [key, value] of Object.entries(map)) {
            if (name.includes(key)) {
                select.value = value;
                break;
            }
        }
    }

    function handleClick() {
        const descrBox = document.querySelector('#descr');
        let text = descrBox.value.trimStart();

        const titleMatch = text.match(/TMDb Title:\s*(.+)/);
        const urlMatch = text.match(/TMDb URL:\s*(.+)/);

        if (titleMatch) {
            const title = titleMatch[1].trim();
            const smallDescr = document.querySelector('input[name="small_descr"]');
            if (smallDescr) smallDescr.value = title;
        }

        if (urlMatch) {
            const url = urlMatch[1].trim();
            const tmdbUrl = document.querySelector('input[name="tmdb_url"]');
            if (tmdbUrl) tmdbUrl.value = url;
        }

        text = text
          // 删除 TMDb Format: 及其前面所有内容
            .replace(/[\s\S]*TMDb Format:.*\n?/i, '');

        descrBox.value = text.trimStart();

        // 自动识别其他选项
        const resolutionMap = {
            '4320p': '6', '8k': '6',
            '2160p': '1', '4k': '1',
            '1080p': '2',
            '1080i': '3',
            '720p': '4',
        };

        const codecMap = {
            'h.265': '2', 'hevc': '2',
            'h.264': '1', 'avc': '1',
            'vvc': '13',
            'av1': '11',
            'vp9': '12',
            'avs3': '14',
            'avs+': '15',
            'avs2': '16',
            'vc-1': '5',
            'mpeg-2': '6',
        };

        const audioCodecMap = {
            'dts-hd ma': '1',
            'dts-hd': '1',
            'truehd': '2',
            'dts-x': '15',
            'lpcm': '3',
            'dts': '4',
            'eac3': '14', 'ddp': '14', 
            'ac3': '5', 'dd': '5',
            'aac': '6',
            'opus': '13',
            'flac': '7',
            'ape': '8',
            'wav': '9',
            'mp3': '10',
            'm4a': '11',
            'av3a': '16',
            'avsa': '17',
            'mpeg': '18',
        };

        const teamMap = {
            'dream': '1',
            'dbtv': '10',
            'qhstudio': '12',
            'cornermv': '13',
            'telesto': '14',
            'mteam': '2',
            'wiki': '4',
            'frds': '7',
            'hdo': '9',
            'beast': '11',
            'chd': '5',
            'cmct': '6',
            'pthome': '3',
        };

        const mediumMap = {
            'remux': '3',
            'uhd': '1',
            'blu-ray': '2', 'bluray': '2', 'bdrip': '2',
            'encode': '10',
            'web-dl': '6', 'webrip': '7', 'webdl': '6',
            'feed': '12',
            'hdtv': '5',
            'hd dvd': '4', 'hddvd': '4',
            'dvd': '8',
            'cd': '9',
        };

        selectByMap(resolutionMap, 'standard_sel');
        selectByMap(codecMap, 'codec_sel');
        selectByMap(audioCodecMap, 'audiocodec_sel');
        selectByMap(teamMap, 'team_sel');
        selectByMap(mediumMap, 'medium_sel');

        // 处理 name 字段中的音频声道格式,比如 AAC 2 0- => AAC 2.0-
        const nameInput = document.querySelector('#name');
        if (nameInput && nameInput.value) {
            // 匹配常见音频编码后跟 2 0 或 5 1 等,替换为 2.0、5.1
            nameInput.value = nameInput.value.replace(
                /\b([a-z0-9]+)\s+([2-8])\s([01])(?=[^\d]|$)/ig,
                (match, codec, ch1, ch2) => `${codec} ${ch1}.${ch2}`
            );
        }

        // 官方制作组自动勾选
        const name = document.querySelector('#name')?.value?.toLowerCase() || '';
        const officialTeams = ['dream', 'dbtv', 'qhstudio', 'cornermv'];
        const officialCheckbox = document.querySelector('input[name="officialteam"]');
        if (officialCheckbox) {
            const checked = officialTeams.some(team => name.includes(team));
            officialCheckbox.checked = checked;
            const tagGfCheckbox = document.querySelector('input#tag_gf[name="tags[]"][value="gf"]');
            if (tagGfCheckbox) tagGfCheckbox.checked = checked;
        }

        // 勾选 tag_wj 如果 name 包含 complete
        const tagWjCheckbox = document.querySelector('input#tag_wj[name="tags[]"][value="wj"]');
        if (tagWjCheckbox) tagWjCheckbox.checked = name.includes('complete');
        // 勾选 tag_hdrm 如果 name 包含 HDR10+ 或 HDR10P(不区分大小写)
        const tagHdrmCheckbox = document.querySelector('input#tag_hdrm[name="tags[]"][value="hdrm"]');
        let hdr10PlusChecked = false;
        if (tagHdrmCheckbox) {
            const lowerName = name.toLowerCase();
            hdr10PlusChecked = lowerName.includes('hdr10+') || lowerName.includes('hdr10p');
            tagHdrmCheckbox.checked = hdr10PlusChecked;
        }
        // 勾选 tag_hdr10 如果 name 包含 HDR 或 HDR10(不区分大小写),且未勾选 HDR10+
        const tagHdr10Checkbox = document.querySelector('input#tag_hdr10[name="tags[]"][value="hdr10"]');
        if (tagHdr10Checkbox && !hdr10PlusChecked) {
            const lowerName = name.toLowerCase();
            tagHdr10Checkbox.checked = lowerName.includes('hdr');
        }
        // 勾选 tag_zz 如果简介里字幕含有 zh
        const tagZzCheckbox = document.querySelector('input#tag_zz[name="tags[]"][value="zz"]');
        if (tagZzCheckbox) {
            // 检查 descrBox.value 里是否有 "Subtitles" 或 "字幕" 行包含 "zh"
            const descrText = descrBox.value.toLowerCase();
            // 匹配 "subtitles" 或 "字幕" 开头的行,且包含 "zh"
            const hasZhSubtitle = descrText.split('\n').some(line =>
                (line.includes('subtitles') || line.includes('字幕')) && (line.includes('zh') || line.includes('cmn'))
            );
            tagZzCheckbox.checked = hasZhSubtitle;
        }

        // 勾选 tag_ko 如果简介中 audio 行包含 ko
        const tagKoCheckbox = document.querySelector('input#tag_ko[name="tags[]"][value="ko"]');
        if (tagKoCheckbox) {
            const descrText = descrBox.value.toLowerCase();
            tagKoCheckbox.checked = descrText.split('\n').some(line =>
                (line.startsWith('audio') || line.includes('音频')) && line.includes('ko')
            );
        }

        // 勾选 tag_ja 如果简介中 audio 行包含 ja
        const tagJaCheckbox = document.querySelector('input#tag_ja[name="tags[]"][value="ja"]');
        if (tagJaCheckbox) {
            const descrText = descrBox.value.toLowerCase();
            tagJaCheckbox.checked = descrText.split('\n').some(line =>
                (line.startsWith('audio') || line.includes('音频')) && line.includes('ja')
            );
        }
        // 检测视频码率并勾选高码率标签
        const descrText = descrBox.value;
        const bitrateMatch = descrText.match(/Video.*?(\d+\.\d+)\s*Mb\/s/i);
        if (bitrateMatch && !isNaN(bitrateMatch[1])) {
            const bitrate = parseFloat(bitrateMatch[1]);
            const tagHqCheckbox = document.querySelector('input#tag_hq[name="tags[]"][value="hq"]');
            if (tagHqCheckbox) {
                tagHqCheckbox.checked = bitrate > 10;
            }
        }
        // 如果简介中类别匹配到“纪录”,则选择 Documentaries纪录片
        const typeSelect = document.querySelector('select[name="type"]#browsecat');
        if (typeSelect) {
            const descrText = descrBox.value;
            const tmdbUrlInput = document.querySelector('input[name="tmdb_url"]');
            const tmdbUrl = tmdbUrlInput ? tmdbUrlInput.value : '';

            // 只遍历一次,收集所有类别行
            const categoryLines = descrText.split('\n').filter(line =>
                line.includes('◎类  别') || line.includes('类别')
            ).join(' ');

            // 统一小写处理
            const categoryText = categoryLines.toLowerCase();

            // 优先级顺序:纪录片 > 动画 > 剧情+tv > 真人秀 > 电影
            if (categoryText.includes('纪录')) {
                typeSelect.value = "404";
            } else if (categoryText.includes('动画')) {
                typeSelect.value = "405";
            } else if (categoryText.includes('真人秀')) {
                typeSelect.value = "403";
            } else {
                const tvKeywords = ['剧情', '喜剧', 'Sci-Fi & Fantasy', '犯罪'];
                const isTVSeries = tvKeywords.some(keyword => categoryText.includes(keyword));
                if (isTVSeries && /\/tv\//.test(tmdbUrl)) {
                    typeSelect.value = "402";
                } else {
                    // 不包含 真人秀/纪录/动画 且 tmdbUrl 含 /movie/
                    const excludeKeywords = ['真人秀', '纪录', '动画'];
                    const hasExclude = excludeKeywords.some(keyword => categoryText.includes(keyword));
                    if (!hasExclude && /\/movie\//.test(tmdbUrl)) {
                        typeSelect.value = "401";
                    }
                }
            }
        }
    }

    // 初始化按钮
    window.addEventListener('load', createButton);
})();