UNPKG

kodilist

Version:

CLI tool for generating Kodi playlists based on filename-embedded tags.

185 lines (174 loc) 5.58 kB
import fs from "fs"; import path from "path"; import os from "os"; import chalk from "chalk"; export interface SearchTerms { [index: string]: string[]; sources: string[]; tags: string[]; people: string[]; } export interface ConfigFile { kodi: string; configPath: string; save: () => void; directories: string[]; ignoreList: string[]; searchTerms: SearchTerms; } /** * Turns a full file path like "~/foo/bar/Rainbow.Lorikeet.2017.mp4" * and returns a simplified name like "rainbow lorikeet 2017 mp4" by * lowercasing it, swapping dots for spaces, and removing path information. */ export function simplifyName(name: string): string { return name .split(path.sep) .pop()! .toLowerCase() .replace(/\./g, " "); } /** * jA list of the default video playlist folders for each platform, relative to * the user's home directory. */ const possiblePlaylistFolders: string[][] = [ ["AppData", "Roaming", "Kodi", "userdata", "playlists", "video"], // Win32 [ "AppData", "Local", "Packages", "XBMCFoundation.Kodi_4n2hpmxwrvr6p", "LocalCache", "Roaming", "Kodi", "userdata", "playlists", "video" ], // UWP [".kodi", "userdata", "playlists", "video"], ["Library", "Application Support", "Kodi", "userdata"] ]; /** * Returns the path to Kodi's video playlists folder if it exists; aborts the * entire program if it doesn't. The path is discovered by checking for the * default path on various platforms, so if the user has installed Kodi in a * non-standard location, this can fail. * * TODO: Allow the user to manually specify where they installed Kodi without * having to edit `kodilist.json`. */ export function getKodiFolder(): string { const home = os.homedir(); const kodiFolder = possiblePlaylistFolders.find(folder => { const absolutePath = path.resolve(path.join(home, ...folder)); return fs.existsSync(absolutePath); }); if (kodiFolder === undefined) { console.error("Could not locate Kodi folder!"); return process.exit(1); } return path.resolve(path.join(home, ...kodiFolder)); } /** * Creates a folder at the supplied path if it doesn't already exist. */ export function guaranteeFolder(root: string, name: string): void { const target = path.join(root, name); if (!fs.existsSync(target)) fs.mkdirSync(target); } /** * Writes M3U playlists for each of the user's tracked people, tags, and * sources in folders named 'people', 'tags', and 'sources' respectively. */ export function writePlaylistsSearchTerms( config: ConfigFile, files: any[] ): void { Object.keys(config.searchTerms).forEach((folderName: string) => { process.stdout.write(" " + folderName + "..."); guaranteeFolder(config.kodi, folderName); config.searchTerms[folderName].forEach((tagName: string) => { const matches = files .filter(name => name.simpleName.includes(tagName)) .map(file => file.filename) .join("\n"); const p = path.join(config.kodi, folderName, tagName + ".m3u"); fs.writeFileSync(p, matches); }); process.stdout.write(`\r${chalk.green("✔️")} ${folderName} \n`); }); } /** * Writes a playlist `new.m3u` with files created within the last week, * measured using `fs.Stats.birthTimeMs`. For large libraries this is the * slowest part of the program after generating the initial file list. This * could be improved performance-wise by caching data from the last run, at the * potential cost of reliability -- what if a file is replaced by a new but * identically-named file -- and we'd still need to check that the files still * exist so it'd still involve touching the disk again. */ export function writePlaylistsNew(config: ConfigFile, files: any[]): void { process.stdout.write(" this week's files..."); const now = new Date().getTime(); const lastWeek = now - 604800000; const newFiles = files .filter(file => file.mtime > lastWeek) .sort((a: any, b: any) => (a.mtime > b.mtime ? -1 : 1)) .map(file => file.filename) .join("\n"); fs.writeFileSync(path.join(config.kodi, "new.m3u"), newFiles); process.stdout.write(`\r${chalk.green("✔️")} this week's files \n`); } const alphabet: { [index: string]: string[] } = { a: [], b: [], c: [], d: [], e: [], f: [], g: [], h: [], i: [], j: [], k: [], l: [], m: [], n: [], o: [], p: [], q: [], r: [], s: [], t: [], u: [], v: [], w: [], x: [], y: [], z: [], "!": [] }; /** * Writes a playlist `a.m3u` with all files beginning with A or a, `b.m3u` * with all files beginning with B or b, and so on. If the filename begins with * any non-alphabetical character it falls under `!.m3u`. */ export function writePlaylistsAlphabet(config: ConfigFile, files: any[]): void { guaranteeFolder(config.kodi, "alphabetical"); process.stdout.write(" alphabetical files..."); files.forEach(file => { if (alphabet.hasOwnProperty(file.simpleName[0])) { alphabet[file.simpleName[0]].push(file.filename); } else { alphabet["!"].push(file.filename); } }); Object.keys(alphabet).forEach(letter => { fs.writeFileSync( path.join(config.kodi, "alphabetical", letter + ".m3u"), alphabet[letter].join("\n") ); }); process.stdout.write(`\r${chalk.green("✔️")} alphabetical files \n`); }