UNPKG

seasonme

Version:

A tool to organize your TV show files naming and make sure that the subtitle file matches with the video file in the folder.

222 lines (220 loc) 7.94 kB
const klawSync = require('klaw-sync'); const fs = require('fs'); const { isMochaRunning } = require('../utils/index.js'); const { PREFIX, CASES, X_REGEX, S_REGEX, E_REGEX, SEASON_REGEX, EPISODE_REGEX, SYMBOL_AND_REGEX, SYMBOL_DOT_REGEX, VIDEO_EXTS, SUB_EXTS } = require('../consts/index.js'); const chalk = require('chalk'); const boxen = require('boxen'); const log = chalk.bold.hex('#ffffff'); const error = chalk.bold.hex('#ffffff'); const boxenOptionsLog = { padding: 2, margin: 2, borderColor: '#54c754', backgroundColor: '#54c754', }; const boxenOptionsError = { padding: 2, margin: 2, borderColor: '#c75454', backgroundColor: '#c75454', }; const renderMyLog = msg => boxen(log(msg), boxenOptionsLog); const renderMyError = msg => boxen(error(msg), boxenOptionsError); const getFiles = pathName => { const files = klawSync(pathName, { nodir: true }) .map(item => item.path) .filter(path => path.split('/').slice(-1)[0][0] !== '.'); return files; }; const getFileName = file => { const start = file.lastIndexOf('/') + 1; const end = file.lastIndexOf('.'); const fileName = file.substr(start, end - start); const ext = file.substr(end, file.length); return [fileName, ext]; }; const formatVideoName = f => { f = f.split(' ').join('.').replace(/\.-\./g, '.').replace(/\.\./g, '.'); const symbolANDMatches = f.match(SYMBOL_AND_REGEX); if (symbolANDMatches) { f = f.replace(symbolANDMatches[0], symbolANDMatches[0].replace(/.&.E/gi, 'E')); return formatVideoName(f); } const symbolDOTMatches = f.match(SYMBOL_DOT_REGEX); if (symbolDOTMatches) { f = f.replace(symbolDOTMatches[0], symbolDOTMatches[0].replace(/.E/gi, 'E')); return formatVideoName(f); } const seasonMatches = f.match(SEASON_REGEX); if (seasonMatches) { f = f.replace(seasonMatches[0], seasonMatches[0].replace(/season./gi, 'S')); return formatVideoName(f); } const episodeMatches = f.match(EPISODE_REGEX); if (episodeMatches) { f = f.replace(episodeMatches[0], episodeMatches[0].replace(/episode./gi, 'E')); return formatVideoName(f); } return f; }; const findNamingCase = f => { if (f.match(X_REGEX)) { return CASES[0]; } if (f.match(S_REGEX) && f.match(E_REGEX)) { return CASES[1]; } if (f.match(SEASON_REGEX) && f.match(EPISODE_REGEX)) { return CASES[2]; } }; const getCaseXInfoObj = f => { const matches = f.match(X_REGEX)[0].toLowerCase().split('x'); if (matches.length >= 2) { const newMatches = [...matches]; newMatches.shift(); const paddedInfos = newMatches.map(i => i.padStart(2, '0')); return { season: `S${matches[0].padStart(2, '0')}`, episode: `E${paddedInfos.join('E')}`, matches, case: CASES[0] }; } return { season: 'S-1', episode: 'E-1' }; }; const getCaseAbbrInfoObj = f => { const seasonMatches = f.match(S_REGEX); const episodesMatches = f.match(E_REGEX); if (seasonMatches && episodesMatches) { const sNumber = seasonMatches[0].replace(/s/gi, ''); const eNumbers = episodesMatches.map(i => i.replace(/e/gi, '').padStart(2, '0')); return { season: `S${sNumber.padStart(2, '0')}`, episode: `E${eNumbers.join('E')}`, matches: [...seasonMatches, ...episodesMatches], case: CASES[1] }; } return { season: 'S-1', episode: 'E-1' }; }; const getCaseFullInfoObj = f => { const seasonMatches = f.match(SEASON_REGEX); const episodesMatches = f.match(EPISODE_REGEX); if (seasonMatches && episodesMatches) { const sNumber = seasonMatches[0].replace(/season/gi, '').replace(/\./g, ''); const eNumbers = episodesMatches.map(i => i .replace(/episode/gi, '') .replace(/\./g, '') .padStart(2, '0'), ); return { season: `S${sNumber.padStart(2, '0')}`, episode: `E${eNumbers.join('E')}`, matches: [...seasonMatches, ...episodesMatches], case: CASES[2] }; } return { season: 'S-1', episode: 'E-1' }; }; const getSequenceInfo = f => { switch (findNamingCase(f)) { case CASES[0]: { return getCaseXInfoObj(f); } case CASES[1]: { return getCaseAbbrInfoObj(f); } case CASES[2]: { return getCaseFullInfoObj(f); } } }; const handleVideoRenaming = async files => { const res = []; for (const file of files) { const [fileName, ext] = getFileName(file); if (VIDEO_EXTS.includes(ext)) { let newFileName = formatVideoName(fileName); const sequenceInfo = getSequenceInfo(newFileName); if (!sequenceInfo) { console.log(renderMyError('No season info, make sure the mkv file name follows {showname}.S01E01')); return; } const replacer = new RegExp(`(${sequenceInfo.matches.join(sequenceInfo.case === CASES[0] ? 'x' : '')})`, 'gi'); newFileName = newFileName.replace(replacer, `${sequenceInfo.season}${sequenceInfo.episode}`.toUpperCase()).replace('bluray', 'BluRay'); res.push(file.replace(fileName, newFileName)); if (!isMochaRunning) { const tempFileName = file.replace(fileName, `${PREFIX}${newFileName}`); try { await fs.promises.rename(file, tempFileName); } catch (err) { console.log(renderMyError('Failed to rename')); } try { await fs.promises.rename(tempFileName, tempFileName.replace(PREFIX, '')); } catch (err) { console.log(renderMyError('Failed to rename')); } } } } return res; }; const getVideoNamingHash = async files => { const fileNamesHash = {}; for (const file of files) { const [fileName, ext] = getFileName(file); if (VIDEO_EXTS.includes(ext)) { const sequenceInfo = getSequenceInfo(fileName); fileNamesHash[`${sequenceInfo.season}_${sequenceInfo.episode}`] = fileName; } } return fileNamesHash; }; const handleSubRenaming = async (files, fileNamesHash) => { const res = []; for (const file of files) { const [fileName, ext] = getFileName(file); if (SUB_EXTS.includes(ext)) { let newFileName = formatVideoName(fileName); const sequenceInfo = getSequenceInfo(newFileName); if (fileName.includes('中文') || fileName.includes('简体') || fileName.toLowerCase().includes('chs')) { res.push(file.replace(fileName, fileNamesHash[`${sequenceInfo.season}_${sequenceInfo.episode}`])); if (!isMochaRunning) { try { await fs.promises.rename(file, file.replace(fileName, fileNamesHash[`${sequenceInfo.season}_${sequenceInfo.episode}`] + '.中文字幕')); } catch (err) { console.log(renderMyError('Failed to rename')); } } } else { if (!sequenceInfo) { console.log(renderMyError('No episode info, make sure the srt file name follows {showname}.S01E01')); return; } res.push(file.replace(fileName, fileNamesHash[`${sequenceInfo.season}_${sequenceInfo.episode}`])); if (!isMochaRunning) { try { await fs.promises.rename(file, file.replace(fileName, fileNamesHash[`${sequenceInfo.season}_${sequenceInfo.episode}`])); } catch (err) { console.log(renderMyError('Failed to rename')); } } } } } return res; }; const format = async props => { const { directory } = props; const dir = directory || process.cwd(); const files = getFiles(dir); // handle formatting video name await handleVideoRenaming(files); // handle getting formatted video name hash const renamedFiles = getFiles(dir); const fileNamesHash = await getVideoNamingHash(renamedFiles); console.log(fileNamesHash); // handle rename srt file await handleSubRenaming(renamedFiles, fileNamesHash); console.log(renderMyLog('Formatted Successfully!')); }; module.exports = { formatVideoName, findNamingCase, getCaseXInfoObj, getCaseAbbrInfoObj, getCaseFullInfoObj, getSequenceInfo, handleVideoRenaming, getVideoNamingHash, handleSubRenaming, format, };