// ==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);
})();