mirror of
https://github.com/xtrll/MusicMetaFinder.git
synced 2026-04-17 21:54:09 +02:00
feat: Refactor project structure for modularity and expandability
- Implemented the service and adapter layers instead of controllers to simplify the integration of new audio recognition and metadata fetching APIs. - Unified code styling and practices throughout the project to ensure consistency.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -129,5 +129,4 @@ dist
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# Miscellaneous
|
||||
*.mp3
|
||||
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
10
.idea/git_toolbox_prj.xml
generated
10
.idea/git_toolbox_prj.xml
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxProjectSettings">
|
||||
<option name="showEditorInlineBlameOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
138
.idea/workspace-LifeIsARace.xml
generated
138
.idea/workspace-LifeIsARace.xml
generated
@@ -1,138 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="d4160043-cf6e-432e-a9d1-4e77ea95ecd3" name="Changes" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/src/controllers/recognitionController.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/errors/ApiError.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app.js" beforeDir="false" afterPath="$PROJECT_DIR$/app.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/api/audioRecognition.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/api/audioRecognition.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/controllers/audioController.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/controllers/audioController.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/services/audioFileValidator.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/services/audioFileValidator.js" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="JavaScript File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GitHubPullRequestSearchHistory">{
|
||||
"lastFilter": {
|
||||
"state": "OPEN",
|
||||
"assignee": "xtrll"
|
||||
}
|
||||
}</component>
|
||||
<component name="GitToolBoxStore">
|
||||
<option name="projectConfigVersion" value="5" />
|
||||
</component>
|
||||
<component name="GithubPullRequestsUISettings">
|
||||
<option name="selectedUrlAndAccountId">
|
||||
<UrlAndAccount>
|
||||
<option name="accountId" value="2dd8b0a6-0bdb-4445-9876-eac917c9314a" />
|
||||
<option name="url" value="https://github.com/xtrll/MusicMetaFinder.git" />
|
||||
</UrlAndAccount>
|
||||
</option>
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 4
|
||||
}</component>
|
||||
<component name="ProjectId" id="2fNgHgn8pMO0IODj8huilr8qzvv" />
|
||||
<component name="ProjectLevelVcsManager">
|
||||
<ConfirmationsSetting value="2" id="Add" />
|
||||
</component>
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||
"Node.js.app.js.executor": "Run",
|
||||
"Node.js.audioController.js.executor": "Run",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"git-widget-placeholder": "main",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"javascript.nodejs.core.library.configured.version": "21.6.2",
|
||||
"javascript.nodejs.core.library.typings.version": "20.12.7",
|
||||
"last_opened_file_path": "C:/Users/XTRLL/OneDrive/Projects/WebstormProjects/MusicMetaFinder",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"settings.editor.selected.configurable": "terminal",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\XTRLL\OneDrive\Projects\WebstormProjects\MusicMetaFinder\src\utils" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="d4160043-cf6e-432e-a9d1-4e77ea95ecd3" name="Changes" comment="" />
|
||||
<created>1713646863779</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1713646863779</updated>
|
||||
<workItem from="1713646864896" duration="8000" />
|
||||
<workItem from="1713719339108" duration="1794000" />
|
||||
<workItem from="1713732443312" duration="323000" />
|
||||
<workItem from="1713732777028" duration="116000" />
|
||||
<workItem from="1713732899783" duration="2161000" />
|
||||
<workItem from="1713735288664" duration="15843000" />
|
||||
<workItem from="1713813884367" duration="1663000" />
|
||||
<workItem from="1713833652605" duration="5256000" />
|
||||
<workItem from="1713934593236" duration="4520000" />
|
||||
<workItem from="1714279427712" duration="806000" />
|
||||
<workItem from="1714435371913" duration="660000" />
|
||||
<workItem from="1714497169161" duration="4293000" />
|
||||
<workItem from="1714586158180" duration="2418000" />
|
||||
<workItem from="1714602276496" duration="10134000" />
|
||||
<workItem from="1714686118351" duration="10531000" />
|
||||
<workItem from="1714756397102" duration="5253000" />
|
||||
<workItem from="1714850771448" duration="8908000" />
|
||||
<workItem from="1714941636860" duration="3548000" />
|
||||
<workItem from="1715013121597" duration="1377000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,34 +1,36 @@
|
||||
#!/usr/bin/env node
|
||||
import { checkEnvVariables } from './src/services/checkEnvVariables.js';
|
||||
import { checkInputPath } from './src/services/checkInputPath.js';
|
||||
import fetchFiles from './src/utils/filesFetcher.js';
|
||||
import { validateAudioFiles } from './src/controllers/fileController.js';
|
||||
import { recognizeAudioFiles } from './src/controllers/recognitionController.js';
|
||||
import { retrieveMetadata } from './src/controllers/metadataController.js';
|
||||
import checkEnvVariables from './src/utils/checkEnvVariables.js';
|
||||
import checkInputPath from './src/utils/checkInputPath.js';
|
||||
import fetchFiles from './src/utils/fetchFiles.js';
|
||||
import validateAudioFiles from './src/services/fileService.js';
|
||||
import recognizeAudioFiles from './src/services/musicRecognitionService.js';
|
||||
import retrieveMetadata from './src/services/metadataRetrievalService.js';
|
||||
|
||||
const { ACOUSTID_API_KEY, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET } = process.env;
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Check for required environment variables
|
||||
checkEnvVariables();
|
||||
// Check for the input path
|
||||
// checkEnvVariables();
|
||||
// Check for required input path
|
||||
const inputPath = process.argv[[2]];
|
||||
checkInputPath(inputPath);
|
||||
// Preferred service can be provided
|
||||
const service = process.argv[[3]] || undefined;
|
||||
|
||||
// Resolve the input path to get array of file paths (handles one or more files)
|
||||
const files = await fetchFiles(inputPath);
|
||||
// Process the resolved paths to confirm and prepare the audio file for metadata recognition
|
||||
const audioFiles = await validateAudioFiles(files);
|
||||
// Recognize the content of the audio file and obtain the corresponding Spotify track ID
|
||||
const recordingIds = await recognizeAudioFiles(audioFiles);
|
||||
const audioIds = await recognizeAudioFiles(audioFiles, service);
|
||||
// Fetch the audio metadata from Spotify using the recognized track IDs
|
||||
const audioMetadata = await retrieveMetadata(recordingIds);
|
||||
const audioMetadata = await retrieveMetadata(audioIds, service);
|
||||
console.log(audioMetadata);
|
||||
// Write the fetched metadata into the audio file
|
||||
// const processedAudioFiles = await fileController.writeMetadata(audioMetadata, audioFiles);
|
||||
} catch (e) {
|
||||
console.error('An error occurred inside app.js:', e);
|
||||
console.error('An error occurred inside cli.js:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
157
package-lock.json
generated
157
package-lock.json
generated
@@ -10,14 +10,17 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"axios-retry": "^4.1.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"commander": "^12.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"fpcalc": "^1.3.0",
|
||||
"music-metadata": "^7.14.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"node-id3": "^0.2.6",
|
||||
"qs": "^6.12.1"
|
||||
},
|
||||
"bin": {
|
||||
"analyze-audio": "node --env-file .env ./app.js"
|
||||
"analyze-audio": "node --env-file .env ./cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.0.2",
|
||||
@@ -383,6 +386,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -517,10 +525,13 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/command-exists": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
|
||||
"integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="
|
||||
"node_modules/commander": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
|
||||
"integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
@@ -607,6 +618,14 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz",
|
||||
@@ -1268,6 +1287,28 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
@@ -1332,6 +1373,29 @@
|
||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fluent-ffmpeg": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
|
||||
"integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==",
|
||||
"dependencies": {
|
||||
"async": ">=0.2.9",
|
||||
"which": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fluent-ffmpeg/node_modules/which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"which": "bin/which"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
@@ -1373,6 +1437,17 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fpcalc": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fpcalc/-/fpcalc-1.3.0.tgz",
|
||||
@@ -1619,6 +1694,17 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -1964,8 +2050,7 @@
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
@@ -2137,6 +2222,49 @@
|
||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/node-id3": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/node-id3/-/node-id3-0.2.6.tgz",
|
||||
"integrity": "sha512-w8GuKXLlPpDjTxLowCt/uYMhRQzED3cg2GdSG1i6RSGKeDzPvxlXeLQuQInKljahPZ0aDnmyX7FX8BbJOM7REg==",
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
@@ -2600,6 +2728,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
@@ -3014,6 +3147,14 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -3,15 +3,18 @@
|
||||
"version": "1.0.0",
|
||||
"description": "CLI utility for music enthusiasts to automatically recognize tracks and enrich file metadata based on the fetched information.",
|
||||
"bin": {
|
||||
"analyze-audio": "node --env-file .env ./app.js"
|
||||
"analyze-audio": "node --env-file .env ./cli.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"axios-retry": "^4.1.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"commander": "^12.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"fpcalc": "^1.3.0",
|
||||
"music-metadata": "^7.14.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"node-id3": "^0.2.6",
|
||||
"qs": "^6.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -24,7 +27,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint . --fix",
|
||||
"analyze": "node --env-file .env app.js"
|
||||
"analyze": "node --env-file .env cli.js"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import getTrackMetadata from '../api/metadataRetrieval.js';
|
||||
import getLyrics from '../api/lyricRetrieval.js';
|
||||
import {getAlbumArt} from "../api/imageRetrieval.js";
|
||||
import requestMetadata from '../../api/metadata/musicBrainzApi.js';
|
||||
import getAlbumArt from "../../api/metadata/coverArtArchiveApi.js";
|
||||
import getLyrics from '../../api/metadata/lyricOvhApi.js';
|
||||
|
||||
/**
|
||||
* Controller to handle retrieval of metadata for an array of MusicBrainz recording IDs.
|
||||
* Adapter to handle retrieval of metadata for an array of MusicBrainz recording IDs.
|
||||
* @function
|
||||
* @param {string[]} recordingIds - An array of MusicBrainz recording IDs.
|
||||
* @returns {Promise<Object[]>} A promise that resolves with an array of metadata objects.
|
||||
* @returns {Promise<Object[]>} A promise that resolves with an object of metadata.
|
||||
*/
|
||||
export async function retrieveMetadata(recordingIds) {
|
||||
export default async function getMetadata(recordingIds) {
|
||||
try {
|
||||
if (!Array.isArray(recordingIds)) {
|
||||
throw new Error('MetadataController expects an array of recordingIds');
|
||||
throw new Error('musicBrainzAdapter expects an array of recordingIds');
|
||||
}
|
||||
|
||||
// Prepare an array to hold the metadata results initialized as an array of Promises
|
||||
// Prepare an object to hold the metadata results initialized as an array of Promises
|
||||
const metadataPromises = recordingIds.map(async (recordingId) => {
|
||||
const trackMetadata = await getTrackMetadata(recordingId);
|
||||
const trackMetadata = await requestMetadata(recordingId);
|
||||
const artist = trackMetadata['artist-credit']?.[0]?.name;
|
||||
const title = trackMetadata.title;
|
||||
const albumId = trackMetadata.releases[0].id
|
||||
@@ -35,7 +35,7 @@ export async function retrieveMetadata(recordingIds) {
|
||||
|
||||
return await Promise.all(metadataPromises);
|
||||
} catch (e) {
|
||||
console.error('Error retrieving metadata from MusicBrainz or lyrics:', e);
|
||||
console.error('Error retrieving metadata using MetaBrainz:', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
36
src/adapters/metadata/spotifyAdapter.js
Normal file
36
src/adapters/metadata/spotifyAdapter.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import getSpotifyAccessToken from '../../api/spotifyAuthApi.js';
|
||||
import requestMetadata from '../../api/metadata/spotifyApi.js';
|
||||
import getLyrics from '../../api/metadata/lyricOvhApi.js';
|
||||
|
||||
/**
|
||||
* Adapter to handle retrieval of metadata for an array of Spotify track IDs.
|
||||
* @function
|
||||
* @param {string[]} trackIds - An array of spotify track IDs.
|
||||
* @returns {Promise<Object[]>} A promise that resolves with an object of metadata.
|
||||
*/
|
||||
export default async function getMetadata(trackIds) {
|
||||
try {
|
||||
if (!Array.isArray(trackIds)) {
|
||||
throw new Error('spotifyAdapter expects an array of trackIds');
|
||||
}
|
||||
|
||||
const accessToken = await getSpotifyAccessToken();
|
||||
|
||||
const metadataPromises = trackIds.map(async (trackId) => {
|
||||
const trackMetadata = await requestMetadata(trackId, accessToken);
|
||||
|
||||
// const lyrics = await getLyrics(artist, title);
|
||||
|
||||
// Combine the track metadata with the lyrics
|
||||
return {
|
||||
...trackMetadata,
|
||||
// lyrics
|
||||
};
|
||||
});
|
||||
|
||||
return await Promise.all(metadataPromises);
|
||||
} catch (e) {
|
||||
console.error('Error retrieving metadata using Spotify:', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import audioRecognition from '../api/audioRecognition.js';
|
||||
import recognizeAudio from '../../api/recognition/acoustidApi.js';
|
||||
|
||||
/**
|
||||
* Recognizes a list of audio files.
|
||||
@@ -6,8 +6,8 @@ import audioRecognition from '../api/audioRecognition.js';
|
||||
* @param {string[]} audioFiles An array of file paths of the audio files.
|
||||
* @return {Promise<Object[]>} A promise that resolves to an array of recognition results.
|
||||
*/
|
||||
export async function recognizeAudioFiles(audioFiles) {
|
||||
const recognitionPromises = audioFiles.map((filePath) => audioRecognition(filePath)
|
||||
export default async function recognizeAudioFiles(audioFiles) {
|
||||
const recognitionPromises = audioFiles.map((filePath) => recognizeAudio(filePath)
|
||||
.catch((error) => {
|
||||
// Log the error and return null
|
||||
// This prevents one failed recognition from stopping the whole process
|
||||
22
src/adapters/recognition/auddAdapter.js
Normal file
22
src/adapters/recognition/auddAdapter.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import recognizeAudio from '../../api/recognition/auddApi.js';
|
||||
|
||||
/**
|
||||
* Recognizes a list of audio files.
|
||||
*
|
||||
* @param {string[]} audioFiles An array of file paths of the audio files.
|
||||
* @return {Promise<Object[]>} A promise that resolves to an array of recognition results.
|
||||
*/
|
||||
export default async function recognizeAudioFiles(audioFiles) {
|
||||
const recognitionPromises = audioFiles.map((filePath) => recognizeAudio(filePath)
|
||||
.catch((error) => {
|
||||
// Log the error and return null
|
||||
// This prevents one failed recognition from stopping the whole process
|
||||
console.error(`Recognition failed for file ${filePath}:`, error);
|
||||
return null;
|
||||
}));
|
||||
|
||||
// Wait for all recognitions to resolve. This will be an array of results or null values
|
||||
const recognizedAudioFiles = await Promise.all(recognitionPromises);
|
||||
// Filter out unsuccessful recognitions
|
||||
return recognizedAudioFiles.filter((result) => result !== null);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios from 'axios';
|
||||
import axiosRetry from '../../utils/retryAxios.js';
|
||||
|
||||
/**
|
||||
* Retrieves the album art image URL for a given album ID.
|
||||
@@ -7,10 +7,10 @@ import axios from 'axios';
|
||||
* @param {string} albumId - The unique identifier for the album whose art is being retrieved.
|
||||
* @returns {Promise<string|null>} - A promise that resolves to the image URL or null if not found or in case of an error.
|
||||
*/
|
||||
export const getAlbumArt = (albumId) => {
|
||||
export default async function getAlbumArt(albumId) {
|
||||
const endpoint = `http://coverartarchive.org/release/${albumId}/front`;
|
||||
|
||||
return axios.get(endpoint, { maxRedirects: 0 })
|
||||
return axiosRetry.get(endpoint, { maxRedirects: 0 })
|
||||
.then((response) => {
|
||||
// If the status code is 200, the image URL should be in the responseURL
|
||||
if (response.status === 200) {
|
||||
@@ -30,4 +30,4 @@ export const getAlbumArt = (albumId) => {
|
||||
console.error('Error retrieving cover art:', error);
|
||||
return null;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import axiosRetry from '../services/retryAxios.js';
|
||||
import { handleError } from '../errors/generalApiErrorHandler.js';
|
||||
import axiosRetry from '../../utils/retryAxios.js';
|
||||
import handleError from '../../errors/generalApiErrorHandler.js';
|
||||
|
||||
/**
|
||||
* Fetches lyrics for a specific song using the provided artist name and title.
|
||||
@@ -9,7 +9,7 @@ import { handleError } from '../errors/generalApiErrorHandler.js';
|
||||
* @param {string} title - The title of the song.
|
||||
* @returns {Promise<string|null>} Promise object representing the lyrics for the song or null if not found or in case of an error.
|
||||
*/
|
||||
export default function fetchLyrics(artist, title) {
|
||||
export default async function getLyrics(artist, title) {
|
||||
const endpoint = `https://api.lyrics.ovh/v1/${encodeURIComponent(artist)}/${encodeURIComponent(title)}`;
|
||||
|
||||
return axiosRetry.get(endpoint)
|
||||
@@ -1,13 +1,13 @@
|
||||
import axiosRetry from '../services/retryAxios.js';
|
||||
import { handleError } from '../errors/generalApiErrorHandler.js';
|
||||
import axiosRetry from '../../utils/retryAxios.js';
|
||||
import handleError from '../../errors/generalApiErrorHandler.js';
|
||||
|
||||
/**
|
||||
* Retrieves the metadata for a recording from MusicBrainz.
|
||||
*
|
||||
* @param {string} recordingId - The MusicBrainz recording ID.
|
||||
* @param {string} recordingId - The MusicBrainz recording ID (can be obtained from acoustid).
|
||||
* @returns {Promise<Object>} - A promise resolving to the track metadata.
|
||||
*/
|
||||
export default async function getAudioMetadata(recordingId) {
|
||||
export default async function getMetadata(recordingId) {
|
||||
const baseUrl = 'https://musicbrainz.org';
|
||||
const query = `/ws/2/recording/${recordingId}?fmt=json&inc=artists+releases+release-groups+isrcs+url-rels+discids+media+artist-credits+aliases+tags+ratings+genres`;
|
||||
|
||||
@@ -37,6 +37,7 @@ export default async function getAudioMetadata(recordingId) {
|
||||
console.error(`No metadata found for recording ID: ${recordingId}`);
|
||||
return null;
|
||||
}
|
||||
handleError(error, recordingId);
|
||||
const errorMessage = handleError(error, recordingId);
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
}
|
||||
19
src/api/metadata/spotifyApi.js
Normal file
19
src/api/metadata/spotifyApi.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import axiosRetry from '../../utils/retryAxios.js';
|
||||
import handleError from '../../errors/generalApiErrorHandler.js';
|
||||
|
||||
export default function getMetadata(trackId, accessToken) {
|
||||
const endpoint = `https://api.spotify.com/v1/tracks/${trackId}`;
|
||||
|
||||
const requestOptions = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
};
|
||||
|
||||
return axiosRetry.get(endpoint, requestOptions)
|
||||
.then((response) => response.data)
|
||||
.catch((error) => {
|
||||
const errorMessage = handleError(error, trackId);
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import fpcalc from 'fpcalc'; // Chromaprint MUST be present inside %PATH% as well. https://github.com/acoustid/chromaprint/releases
|
||||
import axiosRetry from '../services/retryAxios.js';
|
||||
import { handleError } from '../errors/acoustidApiErrorHandler.js';
|
||||
import axiosRetry from '../../utils/retryAxios.js';
|
||||
import handleError from '../../errors/acoustidApiErrorHandler.js';
|
||||
|
||||
/**
|
||||
* Identifies an audio file using the AcoustID API.
|
||||
*
|
||||
* @param {string} filePath - The path to the audio file to be recognized.
|
||||
* @returns {Promise<Object>} - A promise resolving to the recognition result.
|
||||
* @returns {Promise<Object>} - A promise resolving to the recording id.
|
||||
*/
|
||||
export default async function audioRecognition(filePath) {
|
||||
export default async function acoustdIdAudioRecognition(filePath) {
|
||||
const { duration, fingerprint } = await new Promise((resolve, reject) => {
|
||||
fpcalc(filePath, (err, result) => {
|
||||
if (err) reject(err);
|
||||
34
src/api/recognition/auddApi.js
Normal file
34
src/api/recognition/auddApi.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import fs from 'fs';
|
||||
import axios from 'axios';
|
||||
import axiosRetry from '../../utils/retryAxios.js';
|
||||
import handleError from '../../errors/generalApiErrorHandler.js';
|
||||
|
||||
export default async function auddAudioRecognition(filePath) {
|
||||
const audioData = fs.readFileSync(filePath);
|
||||
const base64Audio = Buffer.from(audioData).toString('base64');
|
||||
const body = new URLSearchParams({
|
||||
api_token: process.env.AUDD_API_TOKEN,
|
||||
audio: base64Audio,
|
||||
return: 'spotify',
|
||||
});
|
||||
|
||||
const requestOptions = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
};
|
||||
|
||||
return axios.post('https://api.audd.io/', new URLSearchParams(body), requestOptions)
|
||||
.then((response) => {
|
||||
const { data } = response;
|
||||
|
||||
if (!data) throw new Error('No data received from Audd API');
|
||||
if (data.error) throw new Error(`Audd API Error: ${JSON.stringify(data.error)}`);
|
||||
|
||||
console.log('Recognition successful for:', filePath);
|
||||
return data;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error recognizing audio');
|
||||
const errorMessage = handleError(error, filePath);
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import qs from 'qs';
|
||||
|
||||
const client_id = process.env.SPOTIFY_CLIENT_ID;
|
||||
const client_secret = process.env.SPOTIFY_CLIENT_SECRET;
|
||||
|
||||
export default function getSpotifyAccessToken() {
|
||||
const authOptions = {
|
||||
method: 'POST',
|
||||
url: 'https://accounts.spotify.com/api/token',
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${client_id}:${client_secret}`).toString('base64')}`,
|
||||
'Content-Type': 'applicaion/x-www-form-urlencoded',
|
||||
},
|
||||
data: qs.stringify({
|
||||
grant_type: 'client_credentials',
|
||||
}),
|
||||
};
|
||||
|
||||
return axios(authOptions)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.data.access_token;
|
||||
}
|
||||
return Promise.reject(new Error(`Could not get access token for ${response.status}`));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Auth Error:', error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
32
src/api/spotifyAuthApi.js
Normal file
32
src/api/spotifyAuthApi.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import qs from 'qs';
|
||||
import axiosRetry from '../utils/retryAxios.js';
|
||||
import handleError from '../errors/generalApiErrorHandler.js';
|
||||
|
||||
export default async function getSpotifyAccessToken() {
|
||||
const { client_id, client_secret } = process.env;
|
||||
|
||||
const authOptions = {
|
||||
method: 'POST',
|
||||
url: 'https://accounts.spotify.com/api/token',
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${client_id}:${client_secret}`).toString('base64')}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
data: qs.stringify({
|
||||
grant_type: 'client_credentials',
|
||||
}),
|
||||
};
|
||||
|
||||
return axiosRetry(authOptions)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.data.access_token;
|
||||
}
|
||||
throw new Error(`Could not get access token for ${response.status}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Spotify authentication error:', error);
|
||||
const errorMessage = handleError(error);
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export function handleError(error, filePath) {
|
||||
export default function handleError(error, filePath) {
|
||||
if (error.response) { // Detailed error information when the API responds with an error status
|
||||
console.error(`Request failed with status: ${error.response.status}`);
|
||||
console.error('Headers:', error.response.headers);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function handleError(error, identifier) {
|
||||
export default function handleError(error, identifier) {
|
||||
let message = '';
|
||||
|
||||
// Handler when the API responds with an error status.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import validateAudioFile from '../services/audioFileValidator.js';
|
||||
import validateAudioFile from '../utils/validateAudioFiles.js';
|
||||
|
||||
/**
|
||||
* Processes an array of file paths and returns an array containing only valid audio file paths.
|
||||
@@ -6,7 +6,7 @@ import validateAudioFile from '../services/audioFileValidator.js';
|
||||
* @param {string[]} filePaths - The array of file paths to process.
|
||||
* @returns {Promise<string[]>} A promise that resolves to an array of valid audio file paths.
|
||||
*/
|
||||
export async function validateAudioFiles(filePaths) {
|
||||
export default async function validateAudioFiles(filePaths) {
|
||||
// Validate input type
|
||||
if (!filePaths instanceof Array) {
|
||||
throw new TypeError('Input must be an array of file paths (strings).');
|
||||
14
src/services/metadataNormalizerService.js
Normal file
14
src/services/metadataNormalizerService.js
Normal file
@@ -0,0 +1,14 @@
|
||||
function normalize(metadata) {
|
||||
const normalized = {
|
||||
title: metadata.title || metadata.name || '',
|
||||
artist: metadata.artist || '',
|
||||
album: metadata.album || metadata.collection || '',
|
||||
year: metadata.year || metadata.releaseDate && metadata.releaseDate.substring(0, 4) || '',
|
||||
artwork: metadata.albumArt || metadata.artwork || metadata.image || '',
|
||||
lyrics: metadata.lyrics || '',
|
||||
genre: metadata.genre || (metadata.tags && metadata.tags.join(', ')) || '',
|
||||
// ... add any other fields necessary for normalization
|
||||
};
|
||||
|
||||
return normalized;
|
||||
}
|
||||
18
src/services/metadataRetrievalService.js
Normal file
18
src/services/metadataRetrievalService.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import getMetadataUsingSpotify from '../adapters/metadata/spotifyAdapter.js';
|
||||
import getMetadataUsingMusicBrainz from '../adapters/metadata/musicBrainzAdapter.js';
|
||||
|
||||
// List of supported services
|
||||
const serviceMap = {
|
||||
audd: getMetadataUsingSpotify,
|
||||
acoustid: getMetadataUsingMusicBrainz,
|
||||
};
|
||||
|
||||
export default async function recognizeAudio(audioIds, source) {
|
||||
let recognitionService = serviceMap[source];
|
||||
if (!recognitionService) {
|
||||
console.error('Recognition service unknown or not provided, using default configuration');
|
||||
recognitionService = getMetadataUsingMusicBrainz; // Default service
|
||||
}
|
||||
|
||||
return recognitionService(audioIds);
|
||||
}
|
||||
18
src/services/musicRecognitionService.js
Normal file
18
src/services/musicRecognitionService.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import recognizeUsingAudd from '../adapters/recognition/auddAdapter.js';
|
||||
import recognizeUsingAcoustid from '../adapters/recognition/acoustidAdapter.js';
|
||||
|
||||
// List of supported services
|
||||
const serviceMap = {
|
||||
audd: recognizeUsingAudd,
|
||||
acoustid: recognizeUsingAcoustid,
|
||||
};
|
||||
|
||||
export default async function recognizeAudio(filePaths, source) {
|
||||
let recognitionService = serviceMap[source];
|
||||
if (!recognitionService) {
|
||||
console.error('Recognition service unknown or not provided, using default configuration');
|
||||
recognitionService = recognizeUsingAcoustid; // Default service
|
||||
}
|
||||
|
||||
return recognitionService(filePaths);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import filesFetcher from '../utils/filesFetcher.js';
|
||||
import filesFetcher from '../utils/fetchFiles.js';
|
||||
|
||||
/**
|
||||
* Handle the input path provided by the user to resolve file paths
|
||||
@@ -1,4 +1,4 @@
|
||||
export function checkEnvVariables() {
|
||||
export default function checkEnvVariables() {
|
||||
if (!process.env.ACOUSTID_API_KEY || !process.env.SPOTIFY_CLIENT_ID || !process.env.SPOTIFY_CLIENT_SECRET) {
|
||||
throw new Error('Please set up ACOUSTID_API_KEY, SPOTIFY_CLIENT_ID, and SPOTIFY_CLIENT_SECRET in your .env file.');
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export function checkInputPath(inputPath) {
|
||||
export default function checkInputPath(inputPath) {
|
||||
if (!inputPath) {
|
||||
throw new Error('Please provide the path to an audio file or directory.');
|
||||
}
|
||||
Reference in New Issue
Block a user