From 842bda03322d28b5a7f6882443b710fd95ee639a Mon Sep 17 00:00:00 2001 From: Ryan Shea Date: Fri, 14 May 2021 17:32:05 -0500 Subject: [PATCH 1/6] refactor: pluralize words in output --- package-lock.json | 27 +++++++++++++++++++++++++++ package.json | 2 ++ src/commands/import-ext.ts | 22 +++++++++++++++------- src/commands/relocate.ts | 27 +++++++++++++++++++-------- src/commands/smart.ts | 9 ++++++--- 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ca8446..f79f150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "node-fetch": "2.6.1", "node-rsa": "1.1.1", "ora": "5.4.0", + "pluralize": "8.0.0", "prompts": "2.4.1", "sqlite3": "5.0.2", "terminal-link": "2.1.1", @@ -46,6 +47,7 @@ "@types/node": "14.14.41", "@types/node-fetch": "2.5.10", "@types/node-rsa": "1.1.0", + "@types/pluralize": "0.0.29", "@types/prompts": "2.0.11", "aws-sdk": "2.903.0", "execa": "0.10.0", @@ -678,6 +680,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/pluralize": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz", + "integrity": "sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==", + "dev": true + }, "node_modules/@types/prompts": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.11.tgz", @@ -7242,6 +7250,14 @@ "node": ">= 10.0.0" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "engines": { + "node": ">=4" + } + }, "node_modules/prebuild-install": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.0.1.tgz", @@ -10009,6 +10025,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/pluralize": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz", + "integrity": "sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==", + "dev": true + }, "@types/prompts": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.11.tgz", @@ -14999,6 +15021,11 @@ "find-up": "^3.0.0" } }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==" + }, "prebuild-install": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.0.1.tgz", diff --git a/package.json b/package.json index 094cbd6..3f0ed9c 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "node-fetch": "2.6.1", "node-rsa": "1.1.1", "ora": "5.4.0", + "pluralize": "8.0.0", "prompts": "2.4.1", "sqlite3": "5.0.2", "terminal-link": "2.1.1", @@ -113,6 +114,7 @@ "@types/node": "14.14.41", "@types/node-fetch": "2.5.10", "@types/node-rsa": "1.1.0", + "@types/pluralize": "0.0.29", "@types/prompts": "2.0.11", "aws-sdk": "2.903.0", "execa": "0.10.0", diff --git a/src/commands/import-ext.ts b/src/commands/import-ext.ts index 68f35d7..e32aef8 100644 --- a/src/commands/import-ext.ts +++ b/src/commands/import-ext.ts @@ -1,5 +1,6 @@ import chalk from 'chalk'; import * as path from 'path'; +import pluralize from 'pluralize'; import prompts from 'prompts'; import { BaseEngineCommand } from '../base-commands'; @@ -22,7 +23,7 @@ export default class ImportExt extends BaseEngineCommand { await this.connectToEngine(); if (this.engineDb.version === engine.Version.V2_0) { - this.error(`import-ext doesn't support Engine 2.0`); + this.error(`${ImportExt.name} doesn't support Engine 2.0`); } const extLibraries = await spinner({ @@ -32,9 +33,10 @@ export default class ImportExt extends BaseEngineCommand { const length = libraries.length; if (length) { ctx.succeed( - chalk`Found {blue ${length.toString()}} external ${ - length > 1 ? 'libraries' : 'library' - }`, + chalk`Found {blue ${length.toString()}} external ${pluralize( + 'library', + length, + )}`, ); } else { ctx.warn(`Didn't find any external Engine libraries`); @@ -67,7 +69,10 @@ export default class ImportExt extends BaseEngineCommand { const length = extPlaylists.length; if (length) { ctx.succeed( - chalk`Found {blue ${length.toString()}} external playlists`, + chalk`Found {blue ${length.toString()}} external ${pluralize( + 'playlist', + length, + )}`, ); } else { ctx.warn(`Didn't find any external playlists`); @@ -86,7 +91,10 @@ export default class ImportExt extends BaseEngineCommand { const imported = await this.importPlaylists(selectedPlaylists); ctx.succeed( - chalk`Imported {blue ${imported.length.toString()}} external playlists`, + chalk`Imported {blue ${imported.length.toString()}} external ${pluralize( + 'playlist', + imported.length, + )}`, ); this.logPlaylistsWithTrackCount(imported); @@ -117,7 +125,7 @@ export default class ImportExt extends BaseEngineCommand { private checkLicense(): boolean { if (!isLicensed()) { this.log( - chalk`{yellow Warning} The import-ext command isn't included in the free version.`, + chalk`{yellow Warning} The {cyan ${ImportExt.name}} command isn't included in the free version.`, ); return false; } diff --git a/src/commands/relocate.ts b/src/commands/relocate.ts index a3ebf00..8e6baff 100644 --- a/src/commands/relocate.ts +++ b/src/commands/relocate.ts @@ -1,6 +1,7 @@ import chalk from 'chalk'; import { keyBy, partition } from 'lodash'; import path from 'path'; +import pluralize from 'pluralize'; import prompts from 'prompts'; import { trueCasePath } from 'true-case-path'; @@ -25,9 +26,13 @@ export default class Relocate extends BaseEngineCommand { text: 'Find missing tracks', run: async ctx => { const missing = await this.findMissingTracks(); - if (missing.length) { + const length = missing.length; + if (length) { ctx.succeed( - chalk`Found {red ${missing.length.toString()}} missing tracks`, + chalk`Found {red ${length.toString()}} missing ${pluralize( + 'track', + length, + )}`, ); this.logTracks(missing); } else { @@ -53,17 +58,23 @@ export default class Relocate extends BaseEngineCommand { searchFolder, }); - const numRelocated = relocated.length.toString(); - const numMissing = stillMissing.length.toString(); + const relocatedLength = relocated.length; + const missingLength = stillMissing.length; + const relocatedTrackWord = pluralize('track', relocatedLength); + const missingTrackWord = pluralize('track', missingLength); if (!relocated.length) { - ctx.fail(chalk`Couldn't find {red ${numMissing}} tracks`); - } else if (stillMissing.length) { + ctx.fail( + chalk`Couldn't find {red ${missingLength.toString()}} ${missingTrackWord}`, + ); + } else if (missingLength) { ctx.warn( - chalk`Relocated {green ${numRelocated}} tracks, couldn't find {red ${numMissing}} tracks`, + chalk`Relocated {green ${relocatedLength.toString()}} ${relocatedTrackWord}, couldn't find {red ${missingLength.toString()}} ${missingTrackWord}`, ); } else { - ctx.succeed(chalk`Relocated {green ${numRelocated}} tracks`); + ctx.succeed( + chalk`Relocated {green ${relocatedLength.toString()}} ${relocatedTrackWord}`, + ); } this.logTracks(relocated); if (stillMissing.length) { diff --git a/src/commands/smart.ts b/src/commands/smart.ts index 2223398..0e7f980 100644 --- a/src/commands/smart.ts +++ b/src/commands/smart.ts @@ -1,5 +1,6 @@ import chalk from 'chalk'; import { every, some } from 'lodash'; +import pluralize from 'pluralize'; import { BaseEngineCommand } from '../base-commands'; import * as engine from '../engine'; @@ -41,15 +42,17 @@ export default class Smart extends BaseEngineCommand { tracks: filterTracks({ tracks, playlistConfig }), }), ); + const length = inputs.length; const numEmpty = inputs.filter(x => !x.tracks.length).length; + const playlistWord = pluralize('playlist', length); if (numEmpty) { ctx.warn( - chalk`Built {blue ${inputs.length.toString()}} smart playlists, but {yellow ${numEmpty.toString()}} didn't match any tracks`, + chalk`Built {blue ${length.toString()}} smart ${playlistWord}, but {yellow ${numEmpty.toString()}} didn't match any tracks`, ); } else { ctx.succeed( - chalk`Built {blue ${inputs.length.toString()}} smart playlists`, + chalk`Built {blue ${length.toString()}} smart ${playlistWord}`, ); } this.logPlaylistsWithTrackCount(inputs); @@ -82,7 +85,7 @@ export default class Smart extends BaseEngineCommand { const numPlaylists = this.smartPlaylists.length; if (numPlaylists > MAX_FREE_PLAYLISTS) { this.log( - chalk`{yellow Warning} The free version only supports up to {green ${MAX_FREE_PLAYLISTS.toString()}}, but you have {yellow ${numPlaylists.toString()}}.`, + chalk`{yellow Warning} The free version only supports up to {green ${MAX_FREE_PLAYLISTS.toString()}} smart playlists, but you have {yellow ${numPlaylists.toString()}}.`, ); return false; } From 23b80b949c2ec5fe7b18fec1e1698f7b15c8d12e Mon Sep 17 00:00:00 2001 From: Ryan Shea Date: Fri, 14 May 2021 18:10:22 -0500 Subject: [PATCH 2/6] refactor: add logging helpers --- src/base-commands/base-engine-command.ts | 35 +++++++++++++++++++----- src/commands/import-ext.ts | 4 +-- src/commands/smart.ts | 10 +++---- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/base-commands/base-engine-command.ts b/src/base-commands/base-engine-command.ts index a314b53..58ef34b 100644 --- a/src/base-commands/base-engine-command.ts +++ b/src/base-commands/base-engine-command.ts @@ -9,6 +9,28 @@ export abstract class BaseEngineCommand extends Command { protected libraryFolder!: string; protected engineDb!: engine.EngineDB; + log(message?: string, { indent = 0 }: { indent?: number } = {}) { + const indentStr = ' '.repeat(indent); + super.log(indentStr + message); + } + + logBlock(message: string, opts?: { indent?: number }) { + super.log(); + this.log(message, opts); + super.log(); + } + + warn(message: string, opts?: { indent?: number }) { + this.log(chalk`{yellow Warning} ${message}`, opts); + } + + warnBlock(message: string, { indent = 0 }: { indent?: number } = {}) { + super.log(); + this.warn('', { indent }); + this.log(message, { indent: indent + 2 }); + super.log(); + } + protected async init() { this.libraryFolder = appConf().get(AppConfKey.EngineLibraryFolder); } @@ -35,25 +57,24 @@ export abstract class BaseEngineCommand extends Command { color?: string; } = {}, ) { - const indentStr = ' '.repeat(indent); - tracks.forEach(t => - this.log(`${indentStr}${(chalk as any)[color](t.path)}`), - ); + tracks.forEach(t => { + this.log((chalk as any)[color](t.path), { indent }); + }); } protected logPlaylistsWithTrackCount( playlists: engine.PlaylistInput[], { indent = 4 }: { indent?: number } = {}, ) { - const indentStr = ' '.repeat(indent); playlists.forEach(({ title, tracks }) => { const numTracks = tracks.length; if (numTracks) { this.log( - chalk`${indentStr}{blue ${title}} [{green ${numTracks.toString()}} tracks]`, + chalk`{blue ${title}} [{green ${numTracks.toString()}} tracks]`, + { indent }, ); } else { - this.log(chalk`${indentStr}{blue ${title}} [{yellow 0} tracks]`); + this.log(chalk`{blue ${title}} [{yellow 0} tracks]`, { indent }); } }); } diff --git a/src/commands/import-ext.ts b/src/commands/import-ext.ts index e32aef8..b5a57ba 100644 --- a/src/commands/import-ext.ts +++ b/src/commands/import-ext.ts @@ -124,8 +124,8 @@ export default class ImportExt extends BaseEngineCommand { private checkLicense(): boolean { if (!isLicensed()) { - this.log( - chalk`{yellow Warning} The {cyan ${ImportExt.name}} command isn't included in the free version.`, + this.warnBlock( + chalk`The {cyan ${ImportExt.name}} command isn't included in the free version.`, ); return false; } diff --git a/src/commands/smart.ts b/src/commands/smart.ts index 0e7f980..d484b5e 100644 --- a/src/commands/smart.ts +++ b/src/commands/smart.ts @@ -84,8 +84,8 @@ export default class Smart extends BaseEngineCommand { const numPlaylists = this.smartPlaylists.length; if (numPlaylists > MAX_FREE_PLAYLISTS) { - this.log( - chalk`{yellow Warning} The free version only supports up to {green ${MAX_FREE_PLAYLISTS.toString()}} smart playlists, but you have {yellow ${numPlaylists.toString()}}.`, + this.warnBlock( + chalk`The free version only supports up to {green ${MAX_FREE_PLAYLISTS.toString()}} smart playlists, but you have {yellow ${numPlaylists.toString()}}.`, ); return false; } @@ -93,10 +93,10 @@ export default class Smart extends BaseEngineCommand { normalizeRuleGroup(p.rules).nodes.some(isNodeGroup), ); if (withNestedRules.length) { - this.log( - chalk`{yellow Warning} The free version doesn't support nested rules. The following playlists contain nested rules:`, + this.warnBlock( + `The free version doesn't support nested rules. The following playlists contain nested rules:`, ); - withNestedRules.forEach(p => this.log(` ${p.name}`)); + withNestedRules.forEach(p => this.log(p.name, { indent: 4 })); return false; } From e4bf05731257948dd08038bbadb31e57899d0605 Mon Sep 17 00:00:00 2001 From: Ryan Shea Date: Fri, 14 May 2021 18:10:40 -0500 Subject: [PATCH 3/6] feat: detect tracks with accents refs #29 --- .vscode/launch.json | 12 ++-- src/commands/strip-accents.ts | 106 ++++++++++++++++++++++++++++++++++ webpack.config.js | 1 + 3 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 src/commands/strip-accents.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 69228d9..5eb672a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,7 +1,4 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { @@ -19,7 +16,14 @@ "id": "command", "type": "pickString", "description": "Which command do you want to run?", - "options": ["smart", "relocate", "import-ext", "help"], + "options": [ + // + "import-ext", + "relocate", + "smart", + "strip-accents", + "help" + ], "default": "smart" } ] diff --git a/src/commands/strip-accents.ts b/src/commands/strip-accents.ts new file mode 100644 index 0000000..bf49925 --- /dev/null +++ b/src/commands/strip-accents.ts @@ -0,0 +1,106 @@ +import chalk from 'chalk'; +import pluralize from 'pluralize'; + +import { BaseEngineCommand } from '../base-commands'; +import * as engine from '../engine'; +import { isLicensed } from '../licensing'; +import { spinner } from '../utils'; + +export default class StripAccents extends BaseEngineCommand { + async run() { + if (!this.checkLicense()) { + return; + } + + this.warnBlock( + `This will NOT update tags in your files. It will only update tags in the Engine DB.`, + ); + + await this.connectToEngine(); + + const accentedTracks = await spinner({ + text: 'Find accented tracks', + run: async ctx => { + const accented = await this.findTracksWithAccents(); + const length = accented.length; + + const msgSuffix = `${pluralize( + 'track', + length, + )} with accents in their filename or tags`; + + if (length) { + ctx.succeed(chalk`Found {yellow ${length.toString()}} ${msgSuffix}`); + } else { + ctx.warn(`Didn't find any ${msgSuffix}`); + } + + return accented; + }, + }); + this.logTracks(accentedTracks); + } + + private checkLicense(): boolean { + if (!isLicensed()) { + this.warnBlock( + chalk`The {cyan ${StripAccents.id}} command isn't included in the free version.`, + ); + return false; + } + + return true; + } + + private async findTracksWithAccents(): Promise { + const tracks = await this.engineDb.getTracks(); + + return tracks.filter(track => + ACCENTS_REGEX.test( + [ + track.album, + track.artist, + track.comment, + track.composer, + track.filename, + track.genre, + track.label, + track.remixer, + track.title, + ].join(''), + ), + ); + } +} + +// spell-checker: disable +const ACCENT_MAP = { + ÀÁÂÃÄÅ: 'A', + àáâãäå: 'a', + ÈÉÊË: 'E', + èéêë: 'e', + ÌÍÎÏ: 'I', + ìíîï: 'i', + ÒÓÔÕÖØ: 'O', + òóôõöø: 'o', + ÙÚÛÜ: 'U', + ùúûüµ: 'u', + Ææ: 'AE', + Œœ: 'OE', + Ðð: 'DH', + Þþ: 'TH', + Çç: 'C', + ß: 'S', + '¢£€¤¥': '$', + '©': '©', + '«»': '"', + // '·': '.', + '¡': '!', + '¿': '?', + '×': '*', + '÷': '/', +} as const; +// spell-checker: enable + +const ALL_ACCENTS = Object.keys(ACCENT_MAP).join(''); +const ACCENTS_REGEX = new RegExp(`[${ALL_ACCENTS}]`); diff --git a/webpack.config.js b/webpack.config.js index b8f9356..e325bce 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -35,6 +35,7 @@ module.exports = { ...commandEntry('import-ext'), ...commandEntry('relocate'), ...commandEntry('smart'), + ...commandEntry('strip-accents'), ...hookEntry('init', 'conf'), ...hookEntry('prerun', 'engine-library'), }, From e22a1fc67e74aacd4e0a1286529cf6ac901b8b08 Mon Sep 17 00:00:00 2001 From: Ryan Shea Date: Fri, 14 May 2021 18:18:12 -0500 Subject: [PATCH 4/6] feat: add disconnect message --- src/base-commands/base-engine-command.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/base-commands/base-engine-command.ts b/src/base-commands/base-engine-command.ts index 58ef34b..8c8fe53 100644 --- a/src/base-commands/base-engine-command.ts +++ b/src/base-commands/base-engine-command.ts @@ -36,7 +36,13 @@ export abstract class BaseEngineCommand extends Command { } protected async finally() { - await this.engineDb?.disconnect(); + if (this.engineDb) { + await spinner({ + text: 'Disconnect from Engine DB', + successMessage: 'Disconnected from Engine DB', + run: async () => this.engineDb.disconnect(), + }); + } } protected async connectToEngine() { From 1a25c3e92028f861f7c858a554039a8b328ea1e8 Mon Sep 17 00:00:00 2001 From: Ryan Shea Date: Wed, 19 May 2021 15:09:49 -0500 Subject: [PATCH 5/6] feat: add update track function --- src/commands/relocate.ts | 8 +++++++- src/commands/strip-accents.ts | 2 +- src/engine/1.6/engine-db-1_6.ts | 32 ++++++++++++++++++++++++++------ src/engine/1.6/schema-1_6.ts | 1 + src/engine/2.0/engine-db-2_0.ts | 27 +++++++++++++++++++++------ src/engine/engine-db.ts | 2 +- src/engine/public-schema.ts | 15 ++++++++++++++- 7 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/commands/relocate.ts b/src/commands/relocate.ts index 8e6baff..058109d 100644 --- a/src/commands/relocate.ts +++ b/src/commands/relocate.ts @@ -96,7 +96,13 @@ export default class Relocate extends BaseEngineCommand { await spinner({ text: 'Save relocated tracks to Engine', successMessage: 'Saved relocated tracks to Engine', - run: async () => this.engineDb.updateTrackPaths(relocated), + run: async () => + this.engineDb.updateTracks( + relocated.map(track => ({ + id: track.id, + path: track.path, + })), + ), }); } diff --git a/src/commands/strip-accents.ts b/src/commands/strip-accents.ts index bf49925..cf4d35e 100644 --- a/src/commands/strip-accents.ts +++ b/src/commands/strip-accents.ts @@ -31,6 +31,7 @@ export default class StripAccents extends BaseEngineCommand { if (length) { ctx.succeed(chalk`Found {yellow ${length.toString()}} ${msgSuffix}`); + this.logTracks(accented); } else { ctx.warn(`Didn't find any ${msgSuffix}`); } @@ -38,7 +39,6 @@ export default class StripAccents extends BaseEngineCommand { return accented; }, }); - this.logTracks(accentedTracks); } private checkLicense(): boolean { diff --git a/src/engine/1.6/engine-db-1_6.ts b/src/engine/1.6/engine-db-1_6.ts index 089491f..4fec9e0 100644 --- a/src/engine/1.6/engine-db-1_6.ts +++ b/src/engine/1.6/engine-db-1_6.ts @@ -4,9 +4,11 @@ import { Dictionary, fromPairs, groupBy, + pick, pullAt, transform, } from 'lodash'; +import { ConditionalKeys } from 'type-fest'; import { asyncSeries } from '../../utils'; import { EngineDB } from '../engine-db'; @@ -316,16 +318,34 @@ export class EngineDB_1_6 extends EngineDB { }); } - async updateTrackPaths(tracks: publicSchema.Track[]) { - await this.knex.transaction(async trx => { - await asyncSeries( + async updateTracks(tracks: publicSchema.UpdateTrackInput[]) { + await this.knex.transaction(async trx => + asyncSeries( tracks.map(track => async () => { + const updateKeys: (keyof publicSchema.UpdateTrackInput)[] = [ + 'filename', + 'path', + 'year', + ]; await this.table('Track', trx) .where('id', track.id) - .update({ path: track.path }); + .update(pick(track, updateKeys)); + + const stringMetaUpdateKeys: ConditionalKeys< + publicSchema.UpdateTrackInput, + string | undefined + >[] = [ + 'album', + 'artist', + 'comment', + 'composer', + 'genre', + 'label', + 'title', + ]; }), - ); - }); + ), + ); } async getExtTrackMapping( diff --git a/src/engine/1.6/schema-1_6.ts b/src/engine/1.6/schema-1_6.ts index 2cd12ee..7cca3ab 100644 --- a/src/engine/1.6/schema-1_6.ts +++ b/src/engine/1.6/schema-1_6.ts @@ -128,6 +128,7 @@ export interface MetaData { } export enum MetaDataIntegerType { + DateLastPlayed = 1, DateAdded = 2, DateCreated = 3, Key = 4, diff --git a/src/engine/2.0/engine-db-2_0.ts b/src/engine/2.0/engine-db-2_0.ts index 3e371ed..1c595dc 100644 --- a/src/engine/2.0/engine-db-2_0.ts +++ b/src/engine/2.0/engine-db-2_0.ts @@ -1,4 +1,5 @@ import { Knex } from 'knex'; +import { pick } from 'lodash'; import { asyncSeries } from '../../utils'; import { EngineDB } from '../engine-db'; @@ -161,15 +162,29 @@ export class EngineDB_2_0 extends EngineDB { throw new Error('Not implemented'); } - async updateTrackPaths(tracks: publicSchema.Track[]) { - await this.knex.transaction(async trx => { - await asyncSeries( + async updateTracks(tracks: publicSchema.UpdateTrackInput[]) { + await this.knex.transaction(async trx => + asyncSeries( tracks.map(track => async () => { + const updateKeys: (keyof publicSchema.UpdateTrackInput)[] = [ + 'album', + 'artist', + 'comment', + 'composer', + 'filename', + 'genre', + 'label', + 'path', + 'rating', + 'remixer', + 'title', + 'year', + ]; await this.table('Track', trx) .where('id', track.id) - .update({ path: track.path }); + .update(pick(track, updateKeys)); }), - ); - }); + ), + ); } } diff --git a/src/engine/engine-db.ts b/src/engine/engine-db.ts index 8a5716d..7a064b7 100644 --- a/src/engine/engine-db.ts +++ b/src/engine/engine-db.ts @@ -71,7 +71,7 @@ export abstract class EngineDB { abstract getPlaylistTracks(playlistId: number): Promise; - abstract updateTrackPaths(tracks: schema.Track[]): Promise; + abstract updateTracks(tracks: schema.UpdateTrackInput[]): Promise; protected async getSchemaInfo(): Promise { const results = await this.knex('Information') // diff --git a/src/engine/public-schema.ts b/src/engine/public-schema.ts index 6fc1388..9e9284a 100644 --- a/src/engine/public-schema.ts +++ b/src/engine/public-schema.ts @@ -1,4 +1,4 @@ -import { Opaque } from 'type-fest'; +import { Except, Opaque, SetRequired } from 'type-fest'; export interface Information { id: number; @@ -45,4 +45,17 @@ export interface Track { year?: number; } +export type UpdateTrackInput = Except< + SetRequired, 'id'>, + | 'bitrate' + | 'bpmAnalyzed' + | 'dateAdded' + | 'dateCreated' + | 'fileType' + | 'isBeatGridLocked' + | 'key' + | 'length' + | 'timeLastPlayed' +>; + export type CamelotKeyId = Opaque<'CamelotKeyId', number>; From cca85b2c2e5725e09388089471f37bb274d63cdb Mon Sep 17 00:00:00 2001 From: Ryan Shea Date: Wed, 19 May 2021 16:24:09 -0500 Subject: [PATCH 6/6] feat: strip accents from filenames and tags closes #29 --- img/enjinn-square.png | Bin 0 -> 19398 bytes src/base-commands/base-engine-command.ts | 2 +- src/commands/relocate.ts | 2 +- src/commands/strip-accents.ts | 97 ++++++++++++++++++----- src/engine/1.6/engine-db-1_6.ts | 26 ++++-- src/engine/1.6/schema-1_6.ts | 24 +++--- 6 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 img/enjinn-square.png diff --git a/img/enjinn-square.png b/img/enjinn-square.png new file mode 100644 index 0000000000000000000000000000000000000000..78f28a454c85555faa375f011aa64049995cb8a2 GIT binary patch literal 19398 zcmeIa2T)tdwl?bV%oq$d3shJy8GMROA6CeSG-F` zPj=(Rjk`)O<+N|yxcTZjklqAx_ z!ffqcdb_}&-s(D*-VT;xR&3HztP-9OfPfRs&79TK$r0`f@swoylP&~!zRu=lWBs#= zn}Z~q%yoyXuhcYIWf3kgRuLXPkR>0V2rF2O$I9Ho+|t70IjaDl06#CE5HG(Fh>s5< zCsHXN`S9NmwM{QR(d3T_fzw!H@5_Z+`a)$A0!(0(47fYDDI}Gl| z{`bzTEdSBg8Rg>m=SZzAd0~z)C!m@uz!?8Ox^lKfxFK9^5&wsA{G<3Eqj84Fy1>lc z5H30hgyY{erTKR%tO5c&f~=hSc5o|%hbz}LM}KPolQVaNNwQsQ4a6r5;uF;22SfNp zAb%zye0+aZRRavc%G}NTUn}#A=()b7cik6crNW7liRyg1~%2!XOw-2nG@r6|n|^g@7plwiGcJGZ$lH zwX%d*BV3%!0XwmCGPi;8I>T+)SpS0?S%f3PMGavE$XxKB^Oa;}HC+(ac8)-StG40` zRwa2^5q>cd5s&~6|FzizYyb?tv~vY??De;a1cf30_T*^C`loe5%q_1cg(REhwKc%3 z*#4Gn_n(OQzc--2$a>hq0HXhemHbuB6=Ch>VeSHxu>n-_e`|%j|0m{M&E5ZN*1`N> zYawe9L6DFrj1MGgEhGRk7Z&9M!T5!&1o%WP#4M~V{yy~oS6Kg>SS@YM;WjW}iRESc ze?FN1p1J>LdiYN!;D1*SLjSxP{MG1x84U0B!upqm@&2#I_h;EZEy92z|I7kb_Up%g z>?uIvAG;C^4#>#`*mD#p^x-#d$Q3Ke$>?}~-=01*(+O?5+QXZ@pz63UBmeqW8ZAwl zi4Z>qoVi$g$<6Y`PPT?OWTf1zzx@7Dh+oYysS;yXafo~Dz1#r<9)pIb8)VQNfj7~JLaeb>FW)6yTJ=i5;|mZS!f{?A-j>v9V}?aF z_Y-#;f%-62Pg94o66KeFMc1kI>4oPW)oD3F0I!NB@hP<#RfnE+e+1@pJVDAh3^g1W zd23VCRnl&TDXSO{8hTgPw}mNd08fse4+_Cla91o$saFZCd4I`$5W6x`?T~(Vc5pqLL}d(u@8{si>xJ#?mTfx09eMcG&_0_1zidZ+ zEELzSnwjWd?p)a~TJK$E)^}0vHtru8xL8@~-dZnWB53H?VvQ|R4pQ^``gFxcCVC`G zmMSCb8gJ~GV5{Y8VGds%S2AyDkfhgvnjclvg))dhe8I>4OY*obbn`;C;Y)^s>Q!cVc_1H`wgKSa!xt-EfZlaSgj z%(X4P?P)_!mRu55Y&Nv~o9T8}d0uQ&%BD=^Gsk*U7vqgjh?jOJKHaz2(G1l3?pckK z4&`WQQ|hMN&n%zd$^~Y`_%{qULz4x+Uosg4IXGm0prs&)dQ%t~C4X%EW&3nGUrG5> z%tUqDG~bq#5W>CSlbw^A@`i89R;`Og>}aNQ5151%#VBFCxPELB8=I0x5j-ow;J+dI zKzIAlhFx4zmWgcpXwFdI#5PH=_;BS8N!IEHTZ$dlRNo{;I1waX;s_0a(7?ThZ-FLU z>%{fFvnVN$u_&38n8NswPFgPk~9>l9{R$#0hV4LHKxHm$qxovNm8 z5qI6&AvEeC;yoF2W$adYta;+rh4fT>RXFROw3%f2@ zmNV#*Ib-m5xOpqtHKhGXdhusnDxRpoveamQvEg<_CXz*nA7cxHpjRh_(RN6KzK46a z=i9~fC&lSbT!X$fcq1pnH|!Nm2@B98>t9wflzeMZX0?3ka6bOZ=AQ})vX|3n|JZL?ZwjZuO#X9!plQp zHYib0l z1D;z>FWQN9*nyUk zl(&x%T60@c*|lOd{7$um*4{T)VG^c*tbLc2@E=i3W1R$-O-`=bi0^r%;8P74J(Cng zz0+}ecNBhVXLp+PW3S5E=WTc`f`Jbmx#hmrFm817DfvBd_cgx_{RqBM-orAsIN!s= ziDzw7zYTVU^X2(RsBB$mQ_((np?{>%82HqF_ig zO!KNDjqe(093b9CF;;Fn+};sCXQ&9JgdEB_O-g4*2SRL;a@n3wfB@#QNL$ePi?8EK zvsXXkxi&;UuANSE>UFg~o($M{)Y&RKe-aKTdgzk(FqbXTZz)#`|Hrq5CHO4fnd+(Z z{_tA03AN%#gYjbULN1%C*{H!x(<>$t@=GToYq6=kjmog0UTbeU37eLb9ef4bl`Mh83XfVNUsvR z3ak~^M>%PYyFM(E#sARJkwqd&ta0o~e~5>E zl-+ZC;aNu`dXk1WdhUDIPC2hx5#jQ#*_6)uAOo$_e_yg{=7CV$VW5M!$YpG`rn@h>u`)mv{oPn;kX!QYhgj%J_nT}c{$(qhyuSF-xmx$1G=y>__cI@y zY!w~Wbzc^uvb2jCJbJ^lS62$|GbZARx7US_o`Kq`nzKLMUab)a7T{I!q-4*TfxQO) z4~asby&E#&vnfJzh?92X8rK|FuO~GO^(S9_;a|a~W5Qcv66q`pYZ^Diezo;H^!+x2 zNQ=7Ea?Q>B*57hKV!6t#e=yo{L~O5mc-gvBSbJF?Nt||Xk@~f1ns9#_zBWHzx1rd7 zLCBcz+#*UAdSvN&ookevk2yot1HO`}>sSpN7w@8`@6(zq=R+vyEf_E7RKqm8WB~EW zZiv>_QyX>9|8!0n%=I2fePF2a2d|&b1k_%%`%V6ewCM8Kaca8@3VujWr&*7{ODBEb z*+Njm9jiZfQj{!gW_?roi-wC!(q(Ad5)F#c_*)d*vE(bgc;ieOb+T;8i_OGfz2YT> z;#oBaEmz}3J5`I+{HUd}A%HZnx(46K4I@yx>ichS!xim!`+Et~Idz$0hF5J7VQ)Wa z_#FNyYSa*4=~bL-WCBx427peX^6+X^0zAO4p|PsrxgP*_X7s!W*U(4zJ%4GHoAmyA z7bwI}@X5K*fP$OOg>l+!woUfttCbX7Ihj5qe7m4OWyG+a`RsHe;g8Sj-JcyjcChXM zS^)Opm0sS(>%Fx^@&m}PX8MtIdu5-cS_ZCsd%X}>d>d<6ja+rMpCzAONiADe^60JF z`(cJZMpaQN&Ebw=L`<`;H=d!enqijw=;+%xVX305hl4OwQRW{d7W&q(A&qNdL+%YEl2BUgwmd z%-%jvxZbh7!kY|Slkzp0>-fIE`F=t|{l&wMr2VkSROwjFWl+z#TxVapQe2*u&-m*! zt_d!wlFpl6-OSo#E-TM&=1ND!Ykw3pd$_jv<)me>_wbC+G+g?}jE%CH?Rdk00fmK% zgUXO&UHV}^?z^Fok-3^vM6}tOx)}F`__~UcXH}ZEbzx4|@~AgKiK`SR*uAB-cgv`s zTd=E$D}6lZ>~}2pJU-?0%ah(zI<%b6_*99iend=Ro$RNQy^7X~s2ox-j-@a4Wg^jY zuajU|7rUN3ou*}Utk{>LjG>#6uMUzQNKr4oBG7UBLX!4v_H$XH^`O>X_7gr2m?d40 z15TXJFdsivR}!|>&(Qlb!oz7JRBRfkuTYxIHMlFQ`?0Y?;O?6_5Q(Kt0})(ReBiiO z#CAE?*o-Yd;cF9}w6x!iM|)R6ak_r{cbNtH50f`q)Sd8aI@ZcH?2gGdg+2&G*D9Q@q*uG>VO&-Lq&gnem(v?D#&9K0esKFV4m(wp^2a z=CxXdz}3s{cWF}6?=+uESjj37rp|(fN5!;8i@S|ePiqS_HeMFjl2(wru5y2oj{OLY z69-gI-_i(LEjt-D@a!BX4#Z1(nKHc6P8bsHuiVp(Dlvl_qVq=)4C7t%``V- z1&;Zi+gEcm2)|6*7*+QQQtUha27bUSJd!@W|EjwtTk+HLU()lBO<`Hi_tmk#=dY7b z^ffGDWRUjD@@peA@5B?>E>C{bP_*3f>{h6;8PQ*S{acI>jEUr~)GQ;uNY=b;*GA(S zmyJyjYKs;#WPFWr$U)4kx~mbLDAttkL#2Ak#yd3Ui&oJGwQ5NPBQO1gXSmv=k>re%i*ca`D0DI%jjz6JOCa6~CVA65E|S ze>YEW5z!IE&S_U1+RP(HAnloil}1a6DeU<`9*2E8gd%A&*h4Pn5@-UiF|T-DLwdsW z$w3K`;2*q4WIrfg2NB@|Y%%2@8qczaXG4xTpY^3n=cPg%mD3TDk1q|f+VdjMaP3?o z5+yDN7sxZDE={jK-i0vs2Y7VMpbG~Y*70lgg=M%li*9twuW9*L+Rn>4XGH6yx^oNg zI%F=sJ)F+Y^sgRN;>vXj?4eOldKq#cvl^jy9 z{`hq9{-XLiQF}q4A#yF{BdJ5Ho#W|_fs|wJ4bCZ|(Lbgs? z0#dIvOWW{vu{ZhSY-eGJW`5IypsjI_hCeqt(0pG9Pp!Sdjbgk*hj7+rB1w&9rhgDnh7Q<&wLSwA>Dh$VAd))7bW>MN>y6 zlH9AuNZx7yHmh{3(UVy^iqO>48FjTzFD#Zv2R4|@vumlyzp96(Xh(c@M7uJ7=%LMQ zuwWuN`Dx?js3hoqwnJk?*@Wg=$*Zo`U-po|OF&=Tb$JKFbAae04WH^EVH$-=M^3lv zP9EK2!o)rj@vk<3Ql6QZ#QgK9z-e<0H7CA1 z>kr6^xs2`v1E|iUcDFX6QwG`1@9tJZ7ir^o4-Fyf6ax)9rydB|#%4MLFb%TBqkgv= zD)Ou?w{U%x%rBTu$FMXnop;}_+F!9p5Ik8`#x*V8h;gIgy8zn0+d>v?Op~ z>?3#Bt*+xDJN#3GAFjfrOPIpQOV9IR$C_47bqtv&fSYJ!I@JT@{huyE3#vj(D zm9W_L3s`Prpkn_Lr5srezR71P(jbM76e;>{TEutJhvv%Zr%#1Lc4zGWEFnVDf!XK1L>Yww7!_9($0M z-*LUsHfv68aT1%Noay65i?4h4uPhq?5N8c!G!Na}6n(HrA5aehsX%xesaG17n-;CPbBS7)p-m9+6{ z{&D?2o11Hdx<+c@F9C8=PbE>!`$KUE4~LmX+;DLTJVbg0+Mxu;Q2=WwAGr@5_?p5n--DiONy7YPip>!yGh#v&p#5? zsu^lFn>=pcJjC8S`8Z8zcI`g;p86-WcVJGyQqaf>MoJR%&EIPy(#z#n@pxyP1k$vz z^;G2GhK72cx-7{er<(CLeyL;l~wiUpD5KgluSMfqz}KZH zQ>#?VM1n3_V$90<6yFXT-Ta$Kg*hug?#iF&<3H&I9=gx+H zTBAbnHL29ipgB;*z!I%h0zkxJ@A$%f9k+A=|JKhlw<8AGhdo_6t6U0A()Obtq&GOC zCkYAD4`}frNl8pf#qaX6A}g|wRr-vTK#6%-QG%A34%K0YN^_JJ{?@) zgVvN;cNU|WR)x z@KH`lU#?*ehH;{dA`acr}4=S9zF!F!ZDY(}5&P8lwggJ)!sG8?l zVz~s<1*@Ki<*R0fG?`V!xQK~A_4=cJTqdKrz2D#;9|htz^QF6wN7}nTmn)^vYlXx> z@cLm=2l8vxV)=*{51;SRpr6LKY9k9glnCr$|F;o$yCM~=%jm@5tSaC>HjPZcB z>>F2=X0v>mQTwsmRd{%M7cNoSyYbRQ)7oJ>&i;Xpwfa}6Y`1D^Ad!=p*}kZ(Sjkt# z;dWXu{C=fga*Y63o{4nPlqh?1#b^vCxIY|<%I?-lmO#mWvQI>J#(kskvcFK`!c*(V zhpF$~awvT^24J|`%SqALIG19@l!8y%CCA}V7&vZEia~O6ypj|=TEzw44BXnsP3KWF zb-Sv`MEfT@kA$f+HI%J?5rb75q-|(h8*RlEZ0lJY=BD?2*O|=jO}XRJI~g?8xxP0Q zBH5;sge2I*1061s7Ag#blI=s{7$g_Ojr!!56-q^Q{ggfdbE8Q0C=A*a8V}4}d$~`H zgQ^DlYAJMx-SPX&grAWcYad;lG}Gz}j2k#LOrRye)Q#KhK3!&=Y*e#7XLBUKTh$ZT zWf;VCTvv>E)~3D=e|~>nY<*-04$h? zN(k@gV6~~-!@;(oHlAw+Almj+$6tZo0X^Z^w*EAth`}0%mvt7^1FICi115S3PSGk8=AJ7Whi5s<9ku*I}%kH-e0271^A`-@@e=!k6EbPwc7)q}vsMBR4 z&VyXeYES!dQW%a=kzC_~1T>s8(f$jjVx(Ihn10I^#n#;?y+s0UFB)3~!SSsSp(c;eTPQkBzaI=3T}|BY3fJ~iP8!HwGP#{Zn0xEc@2qyY^VR`Uf|+GP>UIeq z+1q3NF-WE{EI#ak$CB_7u5cZloFmO_3!L^4;#{wm<|<5r-c#wj8AAgeFLdXmVe_LM zHb&!suWU92Ah$Fx%}vcu=ga5o9Cn_~+B_-9!UrH>$;Pt|B%Us0T_@N>96i;V?ndxo z9qYw@iFZuwQn$@quWRKDZOMR5M?KnuZ-E5*OtJCxR;4;+b}Qu)xgV`Vn9dY$e^pCo zj=mR=tms8Je5sSwBa&H2H$StO$6&e8k|Zq5qaLi2oL8ctk z8B`BW;ZsxX2EVeY_h;ueB_O_gk5t)t6|}lJ>805VnfUQ^bE=+!lR6O!6I*7})z4m( z=BYO{@obD@x9^ljcncq`jo&TpG-WE)^%g#!;2j+-T|{Y>hWyag#6yTb3fVy(*sV_n zCEuOa??XqydAd6y1`GXPbv;WlJ!1FPMr8va;;5h@>$*`rKNCQ4G)j6MNKose8hJK! zeN4JGvrMa~|E!ieyjP#E-xn7>Cg0Qz=ah`9_Ngw@rGkeE%{<`a!Ph&rltgr^73Zp$ zpp@z`Rc`W9E=G!x&_IPyGgZW&^2^73i`dtRL+*b_)lSZiq)v=_YRCOul;2UP*khOF zNx^fUdOTH!k4aFnVB}b5Q9tr%P2y@T(Q z2GyPV1)|mH1%vcuoccvf>&#UX(F*8gV5MIxO2dA2?Gysm>JsE>9{=|cx`By&(rMWf zhT*$CzNl~0Hic;5{Kl)hKsaVfZ+su&aP)jzA;S$kbrQlboc0a-gRJ2@!N$hgNBwJb z%b+`6j&FMoBEHYBgd5k9PMpYHXQ}*nf|?dOPJsFmodF-#Ibve%a~^I{W#Zg zX+u6Y!I$34L&Rr42fE7iU$_Q~4s|_jfOA9?Ex7Fz{<9-V|U6cDd zc5Yn*)Rwuk2wmD~qPj(-kS_D|aasxXJd8idOi`E0@0d<$Xc$pY@K)?V#j8Nz_^@e- z)2@K~7#qr^HNT*i$M9)&I24fx)&L-j^fScpdvA0W(z`H~S^f>1Y6QJMza&^w zLd%5c@2#GZtfCxIf2i&4{?3!J(ak8W`)&Pccp7%FaNVHcOW^dJ!$XKR-Hz2qcVIdG z>X4AhaBCgR&)5uqm?9#&J?0|rL#3y9GS&A|noV_7&Z8yuJ@XUQQ3a1HV~v8%)W}L( z^9{Mfx|1bL{aXKtpmuhZKI(!^@QFfgYsklAv!o@=c(sqgx3V@J%dH8@WlY}q2n+0q z60{GA_D@ZHU{k6%v>(A&Z}yOZ@|YGbZU1YXi?C;5vPw9lG}4qB?~DBz=Jp~5B9I@s z_R>Tr1nrjdJQ1;#kjKVrz@vtDACg|alJI&MI-}Yn!K!|j_$m!J>S0VOzcck6#Po_{ z`txB6t47}|f*B2(&HB0DY$7_X;;{L-#&qT36i%Hf<=MJFtmd#so1#C_{ql8;5bAa2 z)>Pp>ws^l^d_m`we0f_s4sBXwd#?5N(%-{xLcX@i+w`GrLbHDP<>{N@*{xpuX9=g( z7|GJZs<)J94!MX@%pLK?W>PLI2#fNG=227arSN*_VuRK$p@*e%%1B4D;#?*k& z3PRAy`1JS`Mk>VT)6`+yvtev>mPe1FTh0tGf|(9Y=C3v5rkh59$uA0vOHE!MvxFWu z<_j&~u4$I8JU>^qOan}poqn9RSHXd9Zu74MM6n-Jslf(W z2j*BjfjSt@)8B9^+%qV=e$e7E#MCo~GFz2+jOJ-u@h*VGo>_ukB^h-6oBoeA zu}ynbAP?Q)VOlBlUh-+&d=xE74)2K@N|}564qo9BsP$-xWjB#zv+6t3cRu)MdlH>` z=8WIDMhB^z1RZt$-r)7~aDcS%gY(&bK^16JCoz5XQ$!HvspYKrzD`BiNmJv61+rlE zveCDX(zNP}#}%8j2H{f?E~vyT$raRw{r1K6gzO6j8kWjJyAt;ny3KOmI*|;Dj{WX$ z5~sV8BX6sn!$MO{n~CvkNmeNb;+ORYI5wgC!C_GL(ysu35xTGZ=XXd1G;!x6S=+Sn zls47OcaBCzYYeH`_-cy&4KY8eB$r%iZF|rQF8~1L8L#6T02mj?u4Vvy$8xJ5H6psz z{j{m9VY%y^ZI0I{Ni5HeN&aIY`zTQht`HK<)=Q1v(h4f0lI7(_J)OrbvfXMaP5J85 zx7u6#mYkQXYB&|FU|d^_wT^(UnrYeRU{2rut{}VGANLJfYS8}`8s+gEy=iiwarX7#stgQD{c#vP?-xTHA4bk7Npmh?-o(W^7uR}SpH(@=IPG1Gw=;81jQ;kRVgl9kc z!+E}W3tz5z?LDGXdzMYNgn0rPJwu=CnG+@8jmKP`L9PgL1PiZAeiLgAmZ0g)T zTGyw|qVTG%V?(Vfrqkgsjyn&B=niufOd~%K6IPG&4(Ah>Fe{NgyowP}(8ENyB8yv} z^zv9L|7&`0kRhSE^t(`?M^@@_Dx9Mw+S$mg^ACohc?K9)Ix&(6UdH?$NlgC=T@@tE zd_eP99nM5i#OB=+^q#?~pWT7EU#T{?M&y-vSR9k{X71O;p?0QU*>#`0=&hdbW1aQR zc`Z(qpzIS)Es>KpHP7%r%|5T_aNd`9tvZaF0EOW2$ua1o}R)}m`d^~fDE%H6Dn3yPi`vPTv9T?0-KTD{$ z%2h6Z{49~$%l`Y+ae^o;hRy9@+&SY5BIZz%E9UNeu788cVblvh@zT6*x7_ZdoHp(Y z_w&t{XC#|1b-SUnOU-tPt9r$-*m~}9*Fs#m-J&wW&8F;;K#y#S_MOm*RKoIWy3?bu zD2C%I0v^Jt9$ZZzk^c(iK;^2hE2kb)LLO_RdcCk;3~92t_%WAZs?r>h9582YXTLa8 zeS-<}?)bMqUV0U<|G}!Uy&iYH#sN-Ta}Q+!KfAv>bNvPI<@=g{Ms$MdZxd5o8?(V|CHPGwg(oiq67-P&sTD<5c`=<;hL zQ*rOZ3Yv9|kMKIzq75@>^*Bo7$7o-Fg*v*Y5=|lO>FK&V!JEYFBEROW1%K2_jF7H@ ze~bG-x%Thx(>-Ws#aXA?|M<$=FZn39WP`ieL$kIu<14RsI9|SJPEI!wB(g)5o}X0S zk++=d&P24xSetn?&s=~x*pEx)Xj2pKJ>gDX4IZO~@AqA06xg-Gq$;XQ*o*fl<+<8; z=S;^w&Fm^6m5<5b;{i6`5f1kFt++hx0DYr_c7#TqS${q-Uw8Q@R;GDvn6Er~GMDoj zDK4v4y6CDAk!sn^t-%`y1PD9EL~tFw5twOJf+3LeV zKQj%wK;sBMvS3GBYPPUHqZpq{u{JUK1AZs*&Z%@m2-0&kXkXJW9(Egxd5bJTwK&l= zi2NE8Ayn{wOPCuSy8qhF&swGFSjnyDS88#G!tsOBY0s`+30`aO zO4P8qHLX0xQqMaJ4o(2+MQC8R^U6Dt5GqcS=Uu-K$5`g_4sTs9o(;z=>RoEt1)h+C z&oHv)?Y(+-%1mUNxm=;5@#Ci?D)omR&owwid@YoC4fcx9Npk!Qp-1I4Lh%Fxr{!&Q z-mMP>SH)P)OEm6mY4OUm`a)-L@(9JUZN}U9J|kT?Vj*sRVhK#m|N%Wl2wA1Ip3U$qs{Fl$W=lSvA1ntgwDh<&NGce(O%4|yLm zVwAHoz!615$6KwS9AIGgMZ3{cIb~RM>GOEU%tg(cAk6M~M>9exDvlOZ%|y$hZ!lzW zK93u13HZL$E?`C(+*WaNr1@ZJX+ThAac=tET}WqZO)ds~H~7(zcSxJ$kOm9MNy(@n z1&{P{;su&ep4b?qxRrRg8lsKd7(zPF8rpS3D3pO~bVo+xXX>f(=rj?UTMu)&HZ>pE zUL+tjYeUUq0v*yND7wM6Oe9XOSqUL+e|SpD&c{N5g0@myf;VGj7oGUT3N%~hhbJf8 z8pd?~{K$8$`!c%QMc-)Fb*k9FQ#fpN{+-jzz|Ao+?A6jf%)kf zxo_S^4FflIiqQHXpW))@z%K3j#LHP0X{$4+-^$xvy=WFa;?z6-tWD!1%;IY&i*&xx z(&)gd+n4Z?uRn+HzJXAPbeO!$!y-Q$q_?CP_ z)RSAUJ~R-BA?}*4@eqo%A=9JEy9JA{lSPjn?@@(CG>Mg-1M@^f^*Ylg^0XF<+%@ja zFxknfuJ>)@gE}9Z8t3oo3*NLQ8}NO7XL_kpL%@M@TDPlJbDq4`fqe#W;_?&mo$#3K zNyyWn{xR5Wf8DhqXRqRMpn|>AS_23D&Ng-PajiSv2X(~q(sY&aThTdG*SO*E=$DZ& zX_ouwB3$s4L&2FEaC?W_KFe4??V8`5HvTX|Hy<)Ln}Ru&eS;Q7RWDG6iCj40ilyy} zYR%?s-sL5A z@%uKH)2=G2QGnR)|36jrWGO=WYu<9 zc#M+iR$u)#yT*}Sjv@Izt&hc*F5Qnc;|ziprHb$G^5zGuguFPBMd;Kt(>-O}nqr?R zvh$MeT}i*WKPpVKGx&gP`s7uY%=(R`=1B+z^Sj*dQSku<8nH9UlTr5r;4kk>-@^R_ z-1L&pBa0^huLO2uH%2!Cn=xIE4WkZJgqftpKT2*)b1j>nri2_TNKr!WyC9lhGR#lm zee1h7;|1Xz>Ta+Uy=-QED5O(H7}kXxtd?JMe_ey(WZaL-E0Ud;UuC#>cTj<&dThFiFCNe=5HBeMw*svr} zgSxcTXIM19X=Adp^~+}A?$W303OTZw!lNgz0M9(tcRTX{W#{SW+}D$(uOhSz7!DC+BAsuz&Yl>;dP2*Hg%uh^5AI}thRtVF^|O?P^Z|4Y zi<@!hCF#sgr-`ETOjCYi)0Rg8Z)447;Y~@+JKHA1I$!Yg>KCeUhSaBUr@odar0MfT zn8hm|HLai?rr&PvRO$J`zXSImMW+%D@g+sy_tYcmkt~5isPc=0tt%yrk7xD80pl66 zDn8(SgRYP6IkldolOtK}n8C3;5peMFn8Zc_yGFw{%0jR0XM47bkG)H0GsOm>9 z(r0ItPx0Z-=fU#{1NN>h%J)N_UAeQ_7}o}8{n&A3nkZSaT8kT?8@uFs^6M!%ev>Ya zIv^n@?(Lg_WC4z#z|?#1ujHYfIpi^cz&myB?TM3toL*9N%J7%1bDIvXiQ~y7pZGXb z%i^YvoeS<#{fzcQ1?@m?uYImEw4An~QeV<35MvM_`K_W*L?k{B209L}L8u-1rE;r* zK@^X}YTlU}N~8-p%#M*d5pyRq>qFW!IT*-J`~&P5X?8TessJ0uitbd|uO_mY^M#q3 zmn_nND}#8>9*aR&D-&U5YJ7Sk0Fw6n@~ziaT9LFq2k1O@rs%ZMED6Ll5p^pLQ0CH$|G!1gV(K;Su4RKO1T~QA>a9+#Uhw=}( z{jZLEKiu%Fju>1#s~_&bN#;^W{Tj2H42p1SNN;8c>>^{X{3&;%Ay|?s;2}8R7pK+I zA9rg}>Ry8kjp68%*I&$@9!P(6Hj7Wi>oOaYy7&4m?q*)q8l0L}Q(xU{st-K~W?2zk zWTrUcrY!vlZCA2UA;CDCnull@(z-gc|EBn}yVOz@uW_ljXj6^j5&V$|p=E~iTzAj1 z3x%w_=t=f*OUqCBc;#KVppQD1(wc42y{s|HTrTt=_bR)(R39x(W^_+C>j)eC%)1gn zE|1@F-IX2th554MWov`RSw_ul^Obng!N@d01TQsY5Cffr?XBju1->K5slmGRl?LzjQRVc8-*w5!#~9HLmrZl2zxdwFghnv#E1+FV2t{Aj(fYp4GRxs++Z0?^9Ned5Gxd?uP1sJ9=Tm3A)<4@HSjwUpYF_)ol)ZFUVT~f*NxCS!MCEONn`x^ zk`BD*lsjc8NthE+oQ{Iyr-%N+6tq4?g#JR=d9lFD9R{jnhR)C*VPNZrC-`pnnO?-V3Fs<+W;Q9%+qPIA!nbpc>iqpS`#mqx-}hNzdWt zcxl+YF-vL-ysKV9}S`@S)^y)l(}^P>jCM)`t~HW}CL1&w|E`wsRwv z#qUx`X_wY`gCZ3DTFMxx?%uFE-CtY}SBPq%dyHEWi1?QLX}yF2j;35@vh5EAOTHbOYllSF7~> zBDNa>PV7g~C<^JNVh^)?Z@{PB`lOx7$N#FcY=FMy?A{u-YSFBjws=);oBZt3Xfz#F z;&B+ZPn>WV4QSY4u`S%*raZe8ue$oaxVu>1g9_S1Dn-z#*3^i3bE>{HX8cy$bjcF_ zS!ICZ(6FbR<&L6QJD&N=>Jy3DBw~1!|DD<@9u+1M#qA^BLr>SXK+K`O-c{l|b&+L2 zT=zMcNi?dL3Kla)b~39-5kbNd7*d-yH{T_^l}{#w0@D7dOFvJtG4}jN2EU|v9sM#0 zMf7ERp&yRT_w2W5YSc!#JQGQm2!ts2q|%zWU2-x|2 z%*Ux*-lr~&E1T(lP92nHO24!SZQQqFB1ukJlBw*omYv6$;E=mHopB=`WlKHa*bd&P zTi1|s=13ixB?7OR(6l859JN+c7izZ-`vfq!Oup%PS@QxEr>c(|XQYOzFJIDew;t1C z2C_|T=M!#`TPL&)8&7%d?Kkzs`h3T?3__DvvY`o}!%hv+Jp>4F3{-Cl-P7fwG!waG zed#b-h7B#XEhSVV`MidE3@G@D4VlO)^rUD2cR&o(P=o6>TApm0akv z%eBLb4b6`Y``502s_6ov9WoQ!&v4Z(lgq!&GU3Lf4ib2k8?TxffZKNv%UhnGOz|hJ zpZ605&|W_oT}d`vHc>6}3eP-)G$mh3J>Vs(q$d98PAu3@gy2W(5srITy9(a|gv3T> zH(5@c^OmFyQ#>iBo9mOB7qWeebpfK!qy-{6W!e>abuVqs^=!OE*_6K-p6Iv9jOKmxYe!3(Sf4^dO zO;{|S^@vKa{z%LFxDP@IemA@o+4261qI>&6yn9ce<(tG6W!DjwNRHkT4_EHo<#o!E zr@bYwKRzo~_2GLc-7rBVZWd2`&21hJuP=fel!9Z^To=p>i=G~o9suGtlhpQiZdx!e zU5N-ylm3|RJI>*0QdY!uGGAy1PAwwz7`m{`7z~-lr}v)kM;#_>Y@HmH&OnIE`E#g_ zeba+P1Z?Jxa|yNXj16&ey0`BBMQrAU@BHixf8WED<+odBxs&3v+pZy*gD2AwF?WM; z`ii{ryyup@&3M3{6cLylySHmHq?zAYyqFOft?Xa-_Md#rSFW>0G$H!;n`;rb#XXi!rp4Hrq&HDlp8V_({J( zbdQxUU$HGnVYA?21kS>(M}T-F7J*wJ1Hk%MrPIw97~F@sgxJ%u)t<@(xtggjEzO#F zRgdeJpGf(Z89OMs^=SB>YS-aDoSta3dY(=-8SL2wt|az2Hi(;@*)6gcl6_zyW@!J_ z{rz0uv-WI5m2e|uccL_;pepD2CX|n%rsZ;fZ=jyVO9sPp)RWFQX6R;$wAH`69^liU z;GpE;pn5eq;82<1#v#F@NHb3#^QJh>Fe0>1A9O%((0;sKp=BBzvt>T7r%~L&K6Qpq z+LR=_l&qqSK^KvEq@_~sVhtslVezd{hdt4`m2MGFnp@uXAZ9oNPRe}L@UrlYL{&mn zOq#6h*XZOkBXVIDxee|E_QHf385_w1hX{8sdyvDlNC}7xfS?IJ#=n;}gEc(qF7a1Q zD5s&;Bg2SiDV(TP_G_@3Ufz8>CSsQTryIU#;C%Q0|2_Es+;7PLcPH0><>g<`-2dYT w)&F12m;cK3@AE$j*X}s~lmDaOb9!~pJ#(&3jz9bQpSG0b)#XZFynge40b8u|#sB~S literal 0 HcmV?d00001 diff --git a/src/base-commands/base-engine-command.ts b/src/base-commands/base-engine-command.ts index 8c8fe53..ef0cc2e 100644 --- a/src/base-commands/base-engine-command.ts +++ b/src/base-commands/base-engine-command.ts @@ -9,7 +9,7 @@ export abstract class BaseEngineCommand extends Command { protected libraryFolder!: string; protected engineDb!: engine.EngineDB; - log(message?: string, { indent = 0 }: { indent?: number } = {}) { + log(message = '', { indent = 0 }: { indent?: number } = {}) { const indentStr = ' '.repeat(indent); super.log(indentStr + message); } diff --git a/src/commands/relocate.ts b/src/commands/relocate.ts index 058109d..f07bf55 100644 --- a/src/commands/relocate.ts +++ b/src/commands/relocate.ts @@ -41,12 +41,12 @@ export default class Relocate extends BaseEngineCommand { return missing; }, }); + this.log(); if (!missingTracks.length) { return; } - this.log(); const searchFolder = await this.promptForSearchFolder(); this.log(); diff --git a/src/commands/strip-accents.ts b/src/commands/strip-accents.ts index cf4d35e..b88dba7 100644 --- a/src/commands/strip-accents.ts +++ b/src/commands/strip-accents.ts @@ -1,12 +1,17 @@ import chalk from 'chalk'; +import fs from 'fs'; +import { compact, pick } from 'lodash'; +import path from 'path'; import pluralize from 'pluralize'; import { BaseEngineCommand } from '../base-commands'; import * as engine from '../engine'; import { isLicensed } from '../licensing'; -import { spinner } from '../utils'; +import { asyncSeries, spinner } from '../utils'; export default class StripAccents extends BaseEngineCommand { + static readonly description = 'strip accents from tags and filenames'; + async run() { if (!this.checkLicense()) { return; @@ -18,7 +23,8 @@ export default class StripAccents extends BaseEngineCommand { await this.connectToEngine(); - const accentedTracks = await spinner({ + this.log(); + const accented = await spinner({ text: 'Find accented tracks', run: async ctx => { const accented = await this.findTracksWithAccents(); @@ -39,6 +45,22 @@ export default class StripAccents extends BaseEngineCommand { return accented; }, }); + this.log(); + + if (!accented.length) { + return; + } + + await spinner({ + text: 'Strip accents from tracks and save to Engine DB', + successMessage: 'Stripped accents from tracks and saved to Engine DB', + run: async () => + asyncSeries( + accented.map( + track => async () => this.stripAccentsAndSaveTrack(track), + ), + ), + }); } private checkLicense(): boolean { @@ -57,22 +79,49 @@ export default class StripAccents extends BaseEngineCommand { return tracks.filter(track => ACCENTS_REGEX.test( - [ - track.album, - track.artist, - track.comment, - track.composer, - track.filename, - track.genre, - track.label, - track.remixer, - track.title, - ].join(''), + compact(Object.values(pick(track, TRACK_ACCENT_FIELDS))).join(''), ), ); } + + private async stripAccentsAndSaveTrack(track: engine.Track) { + const origFilename = track.filename; + + TRACK_ACCENT_FIELDS.forEach(field => { + const value = track[field]; + + if (value) { + track[field] = ACCENT_REPLACE_FUNCS.reduce((v, func) => func(v), value); + } + }); + + if (track.filename !== origFilename) { + const oldPath = track.path; + const parsedPath = path.parse(oldPath); + track.path = path.join(parsedPath.dir, track.filename); + + const oldFile = path.resolve(this.libraryFolder, oldPath); + const newFile = path.resolve(this.libraryFolder, track.path); + + await fs.promises.rename(oldFile, newFile); + } + + await this.engineDb.updateTracks([track]); + } } +const TRACK_ACCENT_FIELDS = [ + 'album', + 'artist', + 'comment', + 'composer', + 'filename', + 'genre', + 'label', + 'remixer', + 'title', +] as const; + // spell-checker: disable const ACCENT_MAP = { ÀÁÂÃÄÅ: 'A', @@ -85,22 +134,26 @@ const ACCENT_MAP = { òóôõöø: 'o', ÙÚÛÜ: 'U', ùúûüµ: 'u', + ß: 'B', + Ç: 'C', + ç: 'c', Ææ: 'AE', Œœ: 'OE', Ðð: 'DH', Þþ: 'TH', - Çç: 'C', - ß: 'S', '¢£€¤¥': '$', - '©': '©', - '«»': '"', - // '·': '.', + '©': 'c', '¡': '!', - '¿': '?', - '×': '*', - '÷': '/', + '×': 'x', + '·': '.', + '¿÷«»': '_', } as const; // spell-checker: enable const ALL_ACCENTS = Object.keys(ACCENT_MAP).join(''); -const ACCENTS_REGEX = new RegExp(`[${ALL_ACCENTS}]`); +const ACCENTS_REGEX = new RegExp(`[${ALL_ACCENTS}]`, 'g'); +const ACCENT_REPLACE_FUNCS = Object.entries(ACCENT_MAP).map(([key, value]) => { + const regex = new RegExp(`[${key}]`, 'g'); + + return (v: string) => v.replace(regex, value); +}); diff --git a/src/engine/1.6/engine-db-1_6.ts b/src/engine/1.6/engine-db-1_6.ts index 4fec9e0..3982efa 100644 --- a/src/engine/1.6/engine-db-1_6.ts +++ b/src/engine/1.6/engine-db-1_6.ts @@ -1,6 +1,5 @@ import { Knex } from 'knex'; import { - camelCase, Dictionary, fromPairs, groupBy, @@ -291,7 +290,7 @@ export class EngineDB_1_6 extends EngineDB { ...transform( textMetaMap[track.id] ?? [], (result, meta) => { - const key = camelCase(schema.MetaDataType[meta.type]); + const key = schema.MetaDataType[meta.type]; result[key] = meta.text; }, {} as any, @@ -299,7 +298,7 @@ export class EngineDB_1_6 extends EngineDB { ...transform( intMetaMap[track.id] ?? [], (result, meta) => { - const key = camelCase(schema.MetaDataIntegerType[meta.type]); + const key = schema.MetaDataIntegerType[meta.type]; result[key] = meta.value; }, {} as any, @@ -318,18 +317,18 @@ export class EngineDB_1_6 extends EngineDB { }); } - async updateTracks(tracks: publicSchema.UpdateTrackInput[]) { + async updateTracks(updates: publicSchema.UpdateTrackInput[]) { await this.knex.transaction(async trx => asyncSeries( - tracks.map(track => async () => { + updates.map(trackUpdates => async () => { const updateKeys: (keyof publicSchema.UpdateTrackInput)[] = [ 'filename', 'path', 'year', ]; await this.table('Track', trx) - .where('id', track.id) - .update(pick(track, updateKeys)); + .where('id', trackUpdates.id) + .update(pick(trackUpdates, updateKeys)); const stringMetaUpdateKeys: ConditionalKeys< publicSchema.UpdateTrackInput, @@ -343,6 +342,19 @@ export class EngineDB_1_6 extends EngineDB { 'label', 'title', ]; + + await asyncSeries( + stringMetaUpdateKeys + .filter(key => trackUpdates[key]) + .map(key => async () => { + const metaType = schema.MetaDataType[key as any]; + + await this.table('MetaData', trx) + .where('id', trackUpdates.id) + .andWhere('type', metaType) + .update({ text: trackUpdates[key] }); + }), + ); }), ), ); diff --git a/src/engine/1.6/schema-1_6.ts b/src/engine/1.6/schema-1_6.ts index 7cca3ab..0f15f83 100644 --- a/src/engine/1.6/schema-1_6.ts +++ b/src/engine/1.6/schema-1_6.ts @@ -111,14 +111,14 @@ export interface TrackWithMeta extends Track { } export enum MetaDataType { - Album = 3, - Artist = 2, - Comment = 5, - Composer = 7, - FileType = 13, - Genre = 4, - Label = 6, - Title = 1, + album = 3, + artist = 2, + comment = 5, + composer = 7, + fileType = 13, + genre = 4, + label = 6, + title = 1, } export interface MetaData { @@ -128,10 +128,10 @@ export interface MetaData { } export enum MetaDataIntegerType { - DateLastPlayed = 1, - DateAdded = 2, - DateCreated = 3, - Key = 4, + dateLastPlayed = 1, + dateAdded = 2, + dateCreated = 3, + key = 4, } export interface MetaDataInteger {