UNPKG

medoc

Version:

A JavaScript library that can simply detect, clean up and transfert tv show episodes from a/ to b/.

291 lines (245 loc) 7.6 kB
/* * Medoc * Licensed under MIT, https://opensource.org/licenses/MIT/ */ const fs = require("fs") const path = require("path") const mkdirp = require("mkdirp") const rimraf = require("rimraf") const VIDEO_FORMATS = [ ".avi", ".mkv", ".mp4", ".webm", ".flv", ".vob", ".ogg", ".amv" ] module.exports = class Medoc { constructor(from, to) { this.from = from this.to = to } async run() { const episodes = Medoc.search(this.from, this.to) if (Array.isArray(episodes)) { return await Promise.all( episodes.map(async episode => { return await Medoc.move(episode) }) ) } else { return await Promise.reject(episodes) } } static async move(episode) { const sourcePath = path.normalize(episode.origin.path) const destinationPath = path.normalize(episode.destination.path) const destinationDirectory = Medoc.getDestinationDirectory(episode) if (episode.origin.isDirectory) { const sourceDirectory = Medoc.getOriginDirectory(episode) if (Medoc.hasFile(sourceDirectory)) { if (!fs.existsSync(destinationDirectory)) { Medoc.createDirectory(destinationDirectory) } return await new Promise((resolve, reject) => { var reader = fs.createReadStream(sourcePath) reader.on("open", () => { let writer = fs.createWriteStream(destinationPath) reader.pipe(writer) }) reader.on("close", async () => { await Medoc.removePath(sourceDirectory) resolve({ origin: sourcePath, destination: destinationPath }) }) }) } else { await Medoc.removePath(sourceDirectory) console.log(`${sourceDirectory} directory has no video file !`) resolve({ message: `${sourceDirectory} directory has no video file !` }) } } if (episode.origin.isFile) { if (!fs.existsSync(destinationDirectory)) { Medoc.createDirectory(destinationDirectory) } return await new Promise((resolve, reject) => { var reader = fs.createReadStream(sourcePath) reader.on("open", () => { let writer = fs.createWriteStream(destinationPath) reader.pipe(writer) }) reader.on("close", async () => { await Medoc.removePath(episode.origin.file) resolve({ origin: sourcePath, destination: destinationPath }) }) }) } } static removePath(url) { return new Promise((resolve, reject) => { rimraf(url, () => { resolve(`${url} removed !`) }) }) } // Create directory and subdirectories if necessary static createDirectory(url) { if (!fs.existsSync(url)) { mkdirp.sync(url) return true } return true } static isEpisode(filename) { if (/([sS]\d{2}[eE]\d{2})/g.exec(filename)) { return true } return false } static hasFile(url) { let files = fs.readdirSync(url) let filteredFiles = files.filter(file => { if (VIDEO_FORMATS.includes(path.extname(file))) { return true } }) if (filteredFiles[0]) { return true } return false } static getYear(filename) { let rst = /(\d{4})/.exec(filename) if (rst && rst[0] !== "1080") { return rst[0] } return null } static getShowName(filename) { if (Medoc.isEpisode(filename)) { let rst = /([sS]\d{2}[eE]\d{2})/g.exec(filename) let tmp = filename.substr(0, rst.index) tmp = tmp.replace(/\./g, " ").trim() // Extract year from name let year = Medoc.getYear(filename) if (year) { tmp = tmp.replace(year, "") tmp += `(${year})` } // Extract sub team from name let subname = /([[\s\S]*])[\s\S]*/g.exec(tmp) if (subname && subname[0]) { tmp = tmp.substr(subname[0].length, tmp.length) } subname = /www *[\w]+ *[org|com]+[\s-]*/g.exec(tmp) if (subname && subname[0]) { tmp = tmp.substr(subname[0].length, tmp.length) } tmp = tmp.trim() return tmp } return null } static getEpisodeSeason(filename) { let rst = /[sS](\d{2})[eE]\d{2}/g.exec(filename) if (rst) { return rst[1] } } static getEpisodeNumber(filename) { let rst = /[sS]\d{2}[eE](\d{2})/g.exec(filename) if (rst) { return rst[1] } } static getFile(url) { if (fs.lstatSync(url).isDirectory()) { let files = fs.readdirSync(url) let filteredFiles = files.filter(file => { if (VIDEO_FORMATS.includes(path.extname(file))) { return true } }) return filteredFiles[0] } else { return url } } static getOriginDirectory(episode) { return path.join(episode.origin.root, episode.origin.directory) } static getOriginPath(episode) { return path.join( episode.origin.root, episode.origin.directory, episode.origin.file ) } static getDestinationDirectory(episode) { return path.join(episode.destination.root, episode.destination.directory) } static search(from, to) { var list = [] try { const files = fs.readdirSync(from) if (files && files.length > 0) { files.map(filename => { if (Medoc.isEpisode(filename)) { let filePath = path.join(from, filename) let isDirectory = fs.lstatSync(filePath).isDirectory() let isFile = fs.lstatSync(filePath).isFile() let showName = Medoc.getShowName(filename) let season = Number(Medoc.getEpisodeSeason(filename)) let number = Number(Medoc.getEpisodeNumber(filename)) let format = isDirectory ? path.extname(Medoc.getFile(filePath)) : path.extname(filename) list.push({ origin: { directory: fs.lstatSync(filePath).isDirectory() ? filename : null, file: Medoc.getFile(filePath), format: format, isDirectory: isDirectory, isFile: isFile, path: fs.lstatSync(filePath).isDirectory() ? path.join(filePath, Medoc.getFile(filePath)) : filePath, root: from }, episode: { show: showName, season: season, number: number }, destination: { directory: path.normalize(`${showName}\\Season ${season}`), filename: `${showName} - ${season}x${ number < 10 ? "0" + number : number }${format}`, path: path.join( to, showName, `Season ${season}`, `${showName} - ${season}x${ number < 10 ? "0" + number : number }${format}` ), root: to } }) } }) } return list } catch (e) { console.error(e) return new Error( `{ "message": "Directory ${from} is not accessible !", "stack" : "${e}"` ) } } }