您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动提取 TMDb 信息、识别编码格式和下拉选项
// ==UserScript== // @name DolbyAutoTools 助手 // @namespace https://www.hddolby.com/ // @version 1.1.5 // @description 自动提取 TMDb 信息、识别编码格式和下拉选项 // @match https://www.hddolby.com/upload.php // @grant none // @license MIT // ==/UserScript== /** * DolbyAutoTools 助手 - 优化版 * 功能:自动提取 TMDb 信息、识别编码格式和下拉选项 * 版本:1.1.5 */ (function () { 'use strict'; // -------------------------- // 配置区域 // -------------------------- const CONFIG = { // 映射表配置 maps: { resolution: { '4320p': '6', '8k': '6', '2160p': '1', '4k': '1', '1080p': '2', '1080i': '3', '720p': '4' }, codec: { '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' }, audioCodec: { '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' }, team: { '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' }, medium: { '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' } }, // 官方制作组列表 officialTeams: ['dream', 'dbtv', 'qhstudio', 'cornermv'], // 正则表达式 regex: { title: /TMDb Title:\s*(.+)/, url: /TMDb URL:\s*(.+)/, audioChannels: /\b([a-z0-9]+)\s+([2-8])\s([01])(?=[^\d]|$)/ig, bitrate: /Video.*?(\d+\.\d+)\s*Mb\/s/i, teamMatch: /-([^\s-]+)$/, mediaInfo: /Mediainfo:\s*([\s\S]*?)(?:\n\s*[Ss]creenshot:|$)/i, screenshots: /Screenshot:\s*([\s\S]*?)Screenshot_Finish/i, allToRemove: /MediaInfo:[\s\S]*?Screenshot_Finish\s*/i }, // 类别关键词 categoryKeywords: { documentary: ['纪录'], animation: ['动画'], realityShow: ['真人秀'], tvSeries: ['剧情', '喜剧', 'Sci-Fi & Fantasy', '犯罪'] }, // 配置键名 configKey: 'dolbyAutoToolsConfig', // 默认配置 defaultConfig: { autoCheckOfficial: true, highBitrateThreshold: 10 } }; // -------------------------- // DOM 元素缓存 // -------------------------- const DOM = { get descrBox() { return document.querySelector('#descr'); }, get nameInput() { return document.querySelector('#name'); }, get smallDescr() { return document.querySelector('input[name="small_descr"]'); }, get tmdbUrl() { return document.querySelector('input[name="tmdb_url"]'); }, get officialCheckbox() { return document.querySelector('input[name="officialteam"]'); }, get tagGfCheckbox() { return document.querySelector('input#tag_gf[name="tags[]"][value="gf"]'); }, get tagWjCheckbox() { return document.querySelector('input#tag_wj[name="tags[]"][value="wj"]'); }, get tagHdrmCheckbox() { return document.querySelector('input#tag_hdrm[name="tags[]"][value="hdrm"]'); }, get tagHdr10Checkbox() { return document.querySelector('input#tag_hdr10[name="tags[]"][value="hdr10"]'); }, get tagZzCheckbox() { return document.querySelector('input#tag_zz[name="tags[]"][value="zz"]'); }, get tagKoCheckbox() { return document.querySelector('input#tag_ko[name="tags[]"][value="ko"]'); }, get tagJaCheckbox() { return document.querySelector('input#tag_ja[name="tags[]"][value="ja"]'); }, get tagHqCheckbox() { return document.querySelector('input#tag_hq[name="tags[]"][value="hq"]'); }, get typeSelect() { return document.querySelector('select[name="type"]#browsecat'); }, get mediaInfoTextarea() { return document.querySelector('textarea[name="media_info"]'); }, get screenshotsTextarea() { return document.querySelector('textarea[name="screenshots"]'); } }; // -------------------------- // 工具函数 // -------------------------- /** * 获取用户配置 * @returns {Object} 用户配置对象 */ function getUserConfig() { try { const config = JSON.parse(localStorage.getItem(CONFIG.configKey) || '{}'); return { ...CONFIG.defaultConfig, ...config }; } catch (error) { console.error('获取用户配置失败:', error); return { ...CONFIG.defaultConfig }; } } /** * 设置复选框状态 * @param {HTMLElement|null} checkbox - 复选框元素 * @param {boolean} checked - 是否勾选 */ function setCheckboxState(checkbox, checked) { if (checkbox) { checkbox.checked = checked; } } /** * 根据映射表选择下拉菜单选项 * @param {Object} map - 映射表 * @param {string} selectName - 下拉菜单名称 * @param {string} [nameOverride] - 覆盖使用的名称 */ function selectByMap(map, selectName, nameOverride) { const name = nameOverride || DOM.nameInput?.value?.toLowerCase() || ''; const select = document.querySelector(`select[name="${selectName}"]`); if (!select) return; // 针对 team_sel,只识别最后一个'-'后面的内容 if (selectName === 'team_sel') { const teamMatch = name.match(CONFIG.regex.teamMatch); 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; } } } /** * 显示通知 * @param {string} message - 消息内容 * @param {string} [type='success'] - 消息类型 ('success', 'error', 'info') */ function showNotification(message, type = 'success') { // 移除已有的通知 document.querySelectorAll('.dolby-notification').forEach(elem => elem.remove()); const notification = document.createElement('div'); notification.className = `dolby-notification ${type}`; notification.textContent = message; notification.style.position = 'fixed'; notification.style.top = '20px'; notification.style.right = '20px'; notification.style.padding = '10px 15px'; notification.style.borderRadius = '4px'; notification.style.color = 'white'; notification.style.zIndex = '9999'; notification.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)'; notification.style.transition = 'opacity 0.3s ease'; // 设置背景色 if (type === 'error') { notification.style.backgroundColor = '#e74c3c'; } else if (type === 'info') { notification.style.backgroundColor = '#3498db'; } else { notification.style.backgroundColor = '#2ecc71'; } document.body.appendChild(notification); // 3秒后自动移除 setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => notification.remove(), 300); }, 3000); } // -------------------------- // 核心功能 // -------------------------- /** * 处理标签勾选 * @param {string} name - 文件名 * @param {string} descrText - 描述文本 * @param {Object} config - 用户配置 */ function processTags(name, descrText, config) { const lowerName = name.toLowerCase(); const lowerDescr = descrText.toLowerCase(); // 官方制作组自动勾选 if (config.autoCheckOfficial) { const isOfficial = CONFIG.officialTeams.some(team => lowerName.includes(team)); setCheckboxState(DOM.officialCheckbox, isOfficial); setCheckboxState(DOM.tagGfCheckbox, isOfficial); } // 勾选 tag_wj 如果 name 包含 complete setCheckboxState(DOM.tagWjCheckbox, lowerName.includes('complete')); // 勾选 HDR 相关标签 let hdr10PlusChecked = false; if (DOM.tagHdrmCheckbox) { // 检查文件名中的HDR10+标记 hdr10PlusChecked = lowerName.includes('hdr10+') || lowerName.includes('hdr10p'); // 如果文件名中没有,使用多行处理检查MediaInfo中的transfer characteristics和其他HDR10+相关信息 if (!hdr10PlusChecked) { const lines = lowerDescr.split(/[\n\r]+/); for (let i = 0; i < lines.length; i++) { // 检查HDR格式相关行 if (lines[i].includes('transfer characteristics') || lines[i].includes('hdr format') || lines[i].includes('hdr_format_compatibility')) { // 检查该特性行后面几行是否包含HDR10+相关标记 for (let j = 0; j <= 10 && i + j < lines.length; j++) { if (lines[i+j].includes('st.2094-10') || lines[i+j].includes('st.2094-40') || lines[i+j].includes('smpte st 2094 app 4') || lines[i+j].includes('hdr10+')) { hdr10PlusChecked = true; break; } } break; } } } setCheckboxState(DOM.tagHdrmCheckbox, hdr10PlusChecked); } if (DOM.tagHdr10Checkbox && !hdr10PlusChecked) { // 检查文件名中的HDR标记 let hasHDR = lowerName.includes('hdr'); // 如果文件名中没有,使用多行处理检查MediaInfo中的transfer characteristics if (!hasHDR) { const lines = lowerDescr.split(/[\n\r]+/); for (let i = 0; i < lines.length; i++) { if (lines[i].includes('transfer characteristics')) { // 检查该特性行后面几行是否包含HDR相关标记 for (let j = 0; j <= 5 && i + j < lines.length; j++) { if (lines[i+j].includes('st.2084') || lines[i+j].includes('smpte 2084')) { hasHDR = true; break; } } break; } } } setCheckboxState(DOM.tagHdr10Checkbox, hasHDR); } // 勾选 tag_zz 如果简介里字幕含有 zh if (DOM.tagZzCheckbox) { // 检查标准字幕标记或MediaInfo中的Text流 // 使用更可靠的分割方式 const lines = lowerDescr.split(/[\n\r]+/); // 改进的匹配逻辑:处理多行的MediaInfo信息 let hasZhSubtitle = false; // 标准字幕标记检查 hasZhSubtitle = lines.some(line => { return (line.includes('subtitles') || line.includes('字幕')) && (line.includes('zh') || line.includes('cmn')); }); // 如果标准检查未通过,检查MediaInfo Text流(多行处理) if (!hasZhSubtitle) { // 查找所有Text流起始行 for (let i = 0; i < lines.length; i++) { if (lines[i].includes('text #')) { // 检查该Text流后面的几行是否包含中文语言信息 for (let j = 1; j <= 10 && i + j < lines.length; j++) { if ((lines[i+j].includes('language') || lines[i+j].includes('语言')) && (lines[i+j].includes('cmn') || lines[i+j].includes('chi'))) { hasZhSubtitle = true; break; } // 如果遇到下一个流类型,停止检查 if (lines[i+j].includes('video') || lines[i+j].includes('audio') || lines[i+j].includes('general') || lines[i+j].includes('text #')) { break; } } if (hasZhSubtitle) break; } } } setCheckboxState(DOM.tagZzCheckbox, hasZhSubtitle); } // 勾选语言相关标签 if (DOM.tagKoCheckbox || DOM.tagJaCheckbox) { // 使用更可靠的分割方式 const lines = lowerDescr.split(/[\n\r]+/); if (DOM.tagKoCheckbox) { // 检查标准音频标记 let hasKoAudio = lines.some(line => { return (line.startsWith('audio') || line.includes('音频')) && line.includes('ko'); }); // 如果标准检查未通过,使用更改进的多行处理检查MediaInfo中的Audio流 if (!hasKoAudio) { let inAudioSection = false; for (const line of lines) { // 检测是否进入音频部分(支持有#号和无#号的Audio流) if (line.trim().startsWith('audio')) { inAudioSection = true; } // 如果在音频部分并且找到了语言代码 if (inAudioSection && ((line.includes('language') || line.includes('语言')) && line.includes('ko'))) { hasKoAudio = true; break; } // 检测是否离开音频部分 if (inAudioSection && line.trim() && !line.startsWith(' ') && !line.startsWith('audio')) { inAudioSection = false; } } } setCheckboxState(DOM.tagKoCheckbox, hasKoAudio); } if (DOM.tagJaCheckbox) { // 检查标准音频标记 let hasJaAudio = lines.some(line => { return (line.startsWith('audio') || line.includes('音频')) && line.includes('ja'); }); // 如果标准检查未通过,使用更改进的多行处理检查MediaInfo中的Audio流 if (!hasJaAudio) { let inAudioSection = false; for (const line of lines) { // 检测是否进入音频部分(支持有#号和无#号的Audio流) if (line.trim().startsWith('audio')) { inAudioSection = true; } // 如果在音频部分并且找到了语言代码 if (inAudioSection && ((line.includes('language') || line.includes('语言')) && line.includes('ja'))) { hasJaAudio = true; break; } // 检测是否离开音频部分 if (inAudioSection && line.trim() && !line.startsWith(' ') && !line.startsWith('audio')) { inAudioSection = false; } } } setCheckboxState(DOM.tagJaCheckbox, hasJaAudio); } } // 检测视频码率并勾选高码率标签 if (DOM.tagHqCheckbox) { let hasHighBitrate = false; // 使用多行处理检查MediaInfo中的码率信息 const lines = lowerDescr.split(/[\n\r]+/); const bitrateRegex = /bit rate\s*:\s*(\d+(?:\.\d+)?)\s*(?:mb\/s|kb\/s)/; for (let i = 0; i < lines.length; i++) { // 检查是否为视频流相关的码率信息 if ((lines[i].includes('video') || lines[i].includes('video #')) && i < lines.length - 10) { // 检查视频流后面的10行内是否有码率信息 for (let j = 0; j <= 10 && i + j < lines.length; j++) { const bitrateMatch = lines[i+j].match(bitrateRegex); if (bitrateMatch && !isNaN(bitrateMatch[1])) { const bitrateValue = parseFloat(bitrateMatch[1]); // 检查单位是Mb/s还是Kb/s const isKbps = bitrateMatch[0].includes('kb/s'); const bitrate = isKbps ? bitrateValue / 1000 : bitrateValue; if (bitrate > config.highBitrateThreshold) { hasHighBitrate = true; break; } } } if (hasHighBitrate) break; } } // 如果在视频流中没有找到,尝试在整个文本中查找 if (!hasHighBitrate) { const bitrateMatch = lowerDescr.match(bitrateRegex); if (bitrateMatch && !isNaN(bitrateMatch[1])) { const bitrateValue = parseFloat(bitrateMatch[1]); const isKbps = bitrateMatch[0].includes('kb/s'); const bitrate = isKbps ? bitrateValue / 1000 : bitrateValue; hasHighBitrate = bitrate > config.highBitrateThreshold; } // 如果MediaInfo中没有找到,回退到原始方法 else if (descrText.match(CONFIG.regex.bitrate)) { const bitrateMatchOriginal = descrText.match(CONFIG.regex.bitrate); if (bitrateMatchOriginal && !isNaN(bitrateMatchOriginal[1])) { const bitrate = parseFloat(bitrateMatchOriginal[1]); hasHighBitrate = bitrate > config.highBitrateThreshold; } } } setCheckboxState(DOM.tagHqCheckbox, hasHighBitrate); } } /** * 选择媒体类型 * @param {string} descrText - 描述文本 * @param {string} tmdbUrl - TMDb URL */ function selectMediaType(descrText, tmdbUrl) { if (!DOM.typeSelect) return; // 收集所有类别行 - 使用更可靠的分割方式 const categoryLines = descrText.split(/[\n\r]+/).filter(line => line.includes('◎类 别') || line.includes('类别') ).join(' '); // 统一小写处理 const categoryText = categoryLines.toLowerCase(); // 优先级顺序:纪录片 > 动画 > 剧情+tv > 真人秀 > 电影 if (CONFIG.categoryKeywords.documentary.some(keyword => categoryText.includes(keyword))) { DOM.typeSelect.value = "404"; } else if (CONFIG.categoryKeywords.animation.some(keyword => categoryText.includes(keyword))) { DOM.typeSelect.value = "405"; } else if (CONFIG.categoryKeywords.realityShow.some(keyword => categoryText.includes(keyword))) { DOM.typeSelect.value = "403"; } else { const isTVSeries = CONFIG.categoryKeywords.tvSeries.some(keyword => categoryText.includes(keyword)); if (isTVSeries && /\/tv\//.test(tmdbUrl)) { DOM.typeSelect.value = "402"; } else { // 不包含 真人秀/纪录/动画 且 tmdbUrl 含 /movie/ const excludeKeywords = [...CONFIG.categoryKeywords.documentary, ...CONFIG.categoryKeywords.animation, ...CONFIG.categoryKeywords.realityShow]; const hasExclude = excludeKeywords.some(keyword => categoryText.includes(keyword)); if (!hasExclude && /\/movie\//.test(tmdbUrl)) { DOM.typeSelect.value = "401"; } } } } /** * 处理点击事件 */ function handleClick() { try { if (!DOM.descrBox) { showNotification('未找到描述框元素', 'error'); return; } const config = getUserConfig(); let text = DOM.descrBox.value.trimStart(); // 提取 TMDb 信息 const titleMatch = text.match(CONFIG.regex.title); const urlMatch = text.match(CONFIG.regex.url); const tmdbUrlValue = urlMatch ? urlMatch[1].trim() : ''; if (titleMatch) { const title = titleMatch[1].trim(); if (DOM.smallDescr) DOM.smallDescr.value = title; } if (DOM.tmdbUrl) { DOM.tmdbUrl.value = tmdbUrlValue; } // 先保存原始文本用于提取 const originalText = text; // 提取 MediaInfo 内容 const mediaInfoMatch = originalText.match(CONFIG.regex.mediaInfo); const mediaInfoContent = mediaInfoMatch ? mediaInfoMatch[1].trim() : ''; // 提取 Screenshot 内容并填写到 textarea const screenshotsMatch = originalText.match(CONFIG.regex.screenshots); if (screenshotsMatch && DOM.screenshotsTextarea) { // 直接使用匹配到的内容,不再判断是否为网址 const screenshotContent = screenshotsMatch[1].trim(); // 填写到textarea中 DOM.screenshotsTextarea.value = screenshotContent; } // 清理描述文本 text = text.replace(/[\s\S]*TMDb Format:.*\n?/i, '').trimStart(); // 使用组合正则表达式一次性删除MediaInfo和Screenshot部分(包括Screenshot_Finish标记) if (CONFIG.regex.allToRemove) { text = text.replace(CONFIG.regex.allToRemove, '').trimStart(); } else { // 兼容性回退:如果allToRemove不存在,则分别删除 if (mediaInfoMatch) { text = text.replace(CONFIG.regex.mediaInfo, '').trimStart(); } if (screenshotsMatch) { text = text.replace(CONFIG.regex.screenshots, '').trimStart(); } } DOM.descrBox.value = text; // 自动识别选项 selectByMap(CONFIG.maps.resolution, 'standard_sel'); selectByMap(CONFIG.maps.codec, 'codec_sel'); selectByMap(CONFIG.maps.audioCodec, 'audiocodec_sel'); selectByMap(CONFIG.maps.team, 'team_sel'); selectByMap(CONFIG.maps.medium, 'medium_sel'); // 处理音频声道格式 if (DOM.nameInput && DOM.nameInput.value) { DOM.nameInput.value = DOM.nameInput.value.replace( CONFIG.regex.audioChannels, (match, codec, ch1, ch2) => `${codec} ${ch1}.${ch2}` ); } // 处理标签勾选 - 使用原始文本和MediaInfo内容 const name = DOM.nameInput?.value || ''; // 先创建一个包含原始文本和MediaInfo的组合文本用于标签匹配 const combinedText = text + '\n' + mediaInfoContent; processTags(name, combinedText, config); // 最后将MediaInfo内容填写到textarea if (mediaInfoContent && DOM.mediaInfoTextarea) { DOM.mediaInfoTextarea.value = mediaInfoContent; } // 选择媒体类型 selectMediaType(text, tmdbUrlValue); showNotification('处理完成!'); } catch (error) { console.error('DolbyAutoTools 错误:', error); showNotification('处理失败: ' + error.message, 'error'); } } /** * 创建按钮 */ function createButton() { if (!DOM.descrBox) { console.warn('未找到描述框元素,无法创建按钮'); return; } const row = DOM.descrBox.closest('td'); if (!row) return; // 检查是否已存在按钮 if (document.querySelector('.dolby-auto-tools-button')) return; const button = document.createElement('button'); button.className = 'dolby-auto-tools-button'; button.textContent = '处理信息'; button.type = 'button'; button.style.margin = '5px 0'; button.style.padding = '4px 10px'; button.style.cursor = 'pointer'; button.addEventListener('click', handleClick); row.appendChild(button); } // -------------------------- // 初始化 // -------------------------- // 使用延迟执行避免阻塞页面加载 window.addEventListener('load', () => { setTimeout(createButton, 100); }); // 添加键盘快捷键支持 (Alt+D) document.addEventListener('keydown', (e) => { if (e.altKey && e.key === 'd') { e.preventDefault(); const button = document.querySelector('.dolby-auto-tools-button'); if (button) button.click(); } }); })();