kodilist
Version:
CLI tool for generating Kodi playlists based on filename-embedded tags.
168 lines (167 loc) • 6.09 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os"));
const chalk_1 = __importDefault(require("chalk"));
/**
* 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.
*/
function simplifyName(name) {
return name
.split(path_1.default.sep)
.pop()
.toLowerCase()
.replace(/\./g, " ");
}
exports.simplifyName = simplifyName;
/**
* jA list of the default video playlist folders for each platform, relative to
* the user's home directory.
*/
const possiblePlaylistFolders = [
["AppData", "Roaming", "Kodi", "userdata", "playlists", "video"],
[
"AppData",
"Local",
"Packages",
"XBMCFoundation.Kodi_4n2hpmxwrvr6p",
"LocalCache",
"Roaming",
"Kodi",
"userdata",
"playlists",
"video"
],
[".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`.
*/
function getKodiFolder() {
const home = os_1.default.homedir();
const kodiFolder = possiblePlaylistFolders.find(folder => {
const absolutePath = path_1.default.resolve(path_1.default.join(home, ...folder));
return fs_1.default.existsSync(absolutePath);
});
if (kodiFolder === undefined) {
console.error("Could not locate Kodi folder!");
return process.exit(1);
}
return path_1.default.resolve(path_1.default.join(home, ...kodiFolder));
}
exports.getKodiFolder = getKodiFolder;
/**
* Creates a folder at the supplied path if it doesn't already exist.
*/
function guaranteeFolder(root, name) {
const target = path_1.default.join(root, name);
if (!fs_1.default.existsSync(target))
fs_1.default.mkdirSync(target);
}
exports.guaranteeFolder = guaranteeFolder;
/**
* Writes M3U playlists for each of the user's tracked people, tags, and
* sources in folders named 'people', 'tags', and 'sources' respectively.
*/
function writePlaylistsSearchTerms(config, files) {
Object.keys(config.searchTerms).forEach((folderName) => {
process.stdout.write(" " + folderName + "...");
guaranteeFolder(config.kodi, folderName);
config.searchTerms[folderName].forEach((tagName) => {
const matches = files
.filter(name => name.simpleName.includes(tagName))
.map(file => file.filename)
.join("\n");
const p = path_1.default.join(config.kodi, folderName, tagName + ".m3u");
fs_1.default.writeFileSync(p, matches);
});
process.stdout.write(`\r${chalk_1.default.green("✔️")} ${folderName} \n`);
});
}
exports.writePlaylistsSearchTerms = writePlaylistsSearchTerms;
/**
* 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.
*/
function writePlaylistsNew(config, files) {
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, b) => (a.mtime > b.mtime ? -1 : 1))
.map(file => file.filename)
.join("\n");
fs_1.default.writeFileSync(path_1.default.join(config.kodi, "new.m3u"), newFiles);
process.stdout.write(`\r${chalk_1.default.green("✔️")} this week's files \n`);
}
exports.writePlaylistsNew = writePlaylistsNew;
const alphabet = {
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`.
*/
function writePlaylistsAlphabet(config, files) {
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_1.default.writeFileSync(path_1.default.join(config.kodi, "alphabetical", letter + ".m3u"), alphabet[letter].join("\n"));
});
process.stdout.write(`\r${chalk_1.default.green("✔️")} alphabetical files \n`);
}
exports.writePlaylistsAlphabet = writePlaylistsAlphabet;