From b2ac9246886f72bef8b96cf218ed2d803397dafa Mon Sep 17 00:00:00 2001 From: Florrie Date: Sun, 18 Feb 2018 23:44:07 -0400 Subject: Make completely new filter system See the man page for how it works now. --- src/smart-playlist.js | 111 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 10 deletions(-) (limited to 'src/smart-playlist.js') diff --git a/src/smart-playlist.js b/src/smart-playlist.js index 4d20e80..cbe5182 100644 --- a/src/smart-playlist.js +++ b/src/smart-playlist.js @@ -2,6 +2,7 @@ const fs = require('fs') const { getCrawlerByName } = require('./crawlers') +const { isGroup, filterTracks, sourceSymbol } = require('./playlist-utils') const { promisify } = require('util') const readFile = promisify(fs.readFile) @@ -10,26 +11,116 @@ async function processSmartPlaylist(item) { // Object.assign is used so that we keep original properties, e.g. "name" // or "apply". (It's also used so we return copies of original objects.) - if ('source' in item) { + const newItem = Object.assign({}, item) + + if ('source' in newItem) { const [ name, ...args ] = item.source const crawlModule = getCrawlerByName(name) - if (crawlModule === null) { + if (crawlModule) { + const { crawl } = crawlModule + Object.assign(newItem, await crawl(...args)) + } else { console.error(`No crawler by name ${name} - skipped item:`, item) - return Object.assign({}, item, {failed: true}) + newItem.failed = true } - const { crawl } = crawlModule + delete newItem.source + } else if ('items' in newItem) { + newItem.items = await Promise.all(item.items.map(processSmartPlaylist)) + } + + if ('filters' in newItem) filters: { + if (!isGroup(newItem)) { + console.warn('Filter on non-group (no effect):', newItem) + break filters + } - return Object.assign({}, item, await crawl(...args)) - } else if ('items' in item) { - return Object.assign({}, item, { - items: await Promise.all(item.items.map(processSmartPlaylist)) + newItem.filters = newItem.filters.filter(filter => { + if ('tag' in filter === false) { + console.warn('Filter is missing "tag" property (skipping this filter):', filter) + return false + } + + return true }) - } else { - return Object.assign({}, item) + + Object.assign(newItem, filterTracks(newItem, track => { + for (const filter of newItem.filters) { + const { tag } = filter + + let value = track + for (const key of tag.split('.')) { + if (key in Object(value)) { + value = value[key] + } else { + console.warn(`In tag "${tag}", key "${key}" not found.`) + console.warn('...value until now:', value) + console.warn('...track:', track) + console.warn('...filter:', filter) + return false + } + } + + if ('gt' in filter && value <= filter.gt) return false + if ('lt' in filter && value >= filter.lt) return false + if ('gte' in filter && value < filter.gte) return false + if ('lte' in filter && value > filter.lte) return false + if ('least' in filter && value < filter.least) return false + if ('most' in filter && value > filter.most) return false + if ('min' in filter && value < filter.min) return false + if ('max' in filter && value > filter.max) return false + + for (const prop of ['includes', 'contains']) { + if (prop in filter) { + if (Array.isArray(value) || typeof value === 'string') { + if (!value.includes(filter.includes)) return false + } else { + console.warn( + `Value of tag "${tag}" is not an array or string, so passing ` + + `"${prop}" does not make sense.` + ) + console.warn('...value:', value) + console.warn('...track:', track) + console.warn('...filter:', filter) + return false + } + } + } + + if (filter.regex) { + if (typeof value === 'string') { + let re + try { + re = new RegExp(filter.regex) + } catch (error) { + console.warn('Invalid regular expression:', re) + console.warn('...error message:', error.message) + console.warn('...filter:', filter) + return false + } + if (!re.test(value)) return false + } else { + console.warn( + `Value of tag "${tag}" is not a string, so passing "regex" ` + + 'does not make sense.' + ) + console.warn('...value:', value) + console.warn('...track:', track) + console.warn('...filter:', filter) + return false + } + } + } + + return true + })) + + delete newItem.filters } + + return newItem } async function main(opts) { -- cgit 1.3.0-6-gf8a5