goblin-malin 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -8,23 +8,35 @@ A keyboard-driven terminal UI for downloading and tagging music tracks with meta
|
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### Running the project as a developer
|
|
12
|
+
|
|
13
|
+
You can run the project as a developer:
|
|
12
14
|
|
|
13
15
|
1. Clone the repository and open a terminal in the project directory
|
|
14
16
|
2. Install [Node.js](https://nodejs.org/en/download) and [yarn](https://classic.yarnpkg.com/lang/en/docs/install)
|
|
15
17
|
3. Install dependencies in the project directory: `yarn install`
|
|
16
18
|
4. Run the application: `yarn run dev`
|
|
17
19
|
|
|
20
|
+
### Launching the app in js code
|
|
21
|
+
|
|
22
|
+
> Not customizable yet
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import GoblinMalin from "goblin-malin";
|
|
26
|
+
|
|
27
|
+
GoblinMalin.start();
|
|
28
|
+
```
|
|
29
|
+
|
|
18
30
|
## Steps
|
|
19
31
|
|
|
20
32
|
- Import track URLs from compatible streaming platforms with `Ctrl+V`:
|
|
21
|
-
- Spotify
|
|
22
|
-
- YouTube
|
|
33
|
+
- `Spotify`
|
|
34
|
+
- `YouTube`
|
|
23
35
|
- System fetches primary metadata from the corresponding URL platform
|
|
24
36
|
- System discovers the same track on other platforms (cross-referencing via ISRC or track/artist name)
|
|
25
37
|
- Filters/orders metadata sources by relevance or leaves the default ranking chosen by the system (use `TAB` key to switch the focused window).
|
|
26
38
|
- System downloads matching tracks from available download providers:
|
|
27
|
-
- yt-dlp
|
|
39
|
+
- `yt-dlp`
|
|
28
40
|
- User selects the best download source and previews the audio
|
|
29
41
|
- User saves the file to the desired folder with embedded tags
|
|
30
42
|
|
|
@@ -521,7 +521,7 @@ var FlowSettings = class {
|
|
|
521
521
|
};
|
|
522
522
|
|
|
523
523
|
// src/flows/musicDownloadFlow/utils/downloadTask.ts
|
|
524
|
-
import
|
|
524
|
+
import fs3 from "fs/promises";
|
|
525
525
|
|
|
526
526
|
// src/base/task/task-status.ts
|
|
527
527
|
var StatusType = /* @__PURE__ */ (function(StatusType2) {
|
|
@@ -995,6 +995,9 @@ function computeOutputPath(compiled, outputDir) {
|
|
|
995
995
|
}
|
|
996
996
|
__name(computeOutputPath, "computeOutputPath");
|
|
997
997
|
|
|
998
|
+
// src/flows/musicDownloadFlow/saveSettings.ts
|
|
999
|
+
import fs2 from "fs";
|
|
1000
|
+
|
|
998
1001
|
// src/flows/musicDownloadFlow/settings.ts
|
|
999
1002
|
import * as os from "os";
|
|
1000
1003
|
import * as path2 from "path";
|
|
@@ -1012,6 +1015,7 @@ function extractProviderDefaults(registry) {
|
|
|
1012
1015
|
return result;
|
|
1013
1016
|
}
|
|
1014
1017
|
__name(extractProviderDefaults, "extractProviderDefaults");
|
|
1018
|
+
var MEDIA_OUTPUT_DIR = path2.join(os.homedir(), "Music", "GoblinMalin");
|
|
1015
1019
|
var BASE_DEFAULT_MUSIC_DOWNLOAD_FLOW_SETTINGS = {
|
|
1016
1020
|
metadata: {
|
|
1017
1021
|
autoFetchOnImport: false,
|
|
@@ -1023,7 +1027,8 @@ var BASE_DEFAULT_MUSIC_DOWNLOAD_FLOW_SETTINGS = {
|
|
|
1023
1027
|
autoSaveToOutputDir: true,
|
|
1024
1028
|
autoDeleteTempAfter24h: false,
|
|
1025
1029
|
autoRelocateMissingFiles: false,
|
|
1026
|
-
outputDir:
|
|
1030
|
+
outputDir: MEDIA_OUTPUT_DIR,
|
|
1031
|
+
outputTemporaryDir: path2.join(MEDIA_OUTPUT_DIR, "tmp"),
|
|
1027
1032
|
providers: {}
|
|
1028
1033
|
}
|
|
1029
1034
|
};
|
|
@@ -1033,10 +1038,20 @@ function getFlowSettings() {
|
|
|
1033
1038
|
return SettingsStore.getInstance().getFlowSettings("music-downloader", BASE_DEFAULT_MUSIC_DOWNLOAD_FLOW_SETTINGS);
|
|
1034
1039
|
}
|
|
1035
1040
|
__name(getFlowSettings, "getFlowSettings");
|
|
1036
|
-
function
|
|
1037
|
-
return getFlowSettings().download.
|
|
1041
|
+
function getTempDownloadDir() {
|
|
1042
|
+
return getFlowSettings().download.outputTemporaryDir;
|
|
1038
1043
|
}
|
|
1039
|
-
__name(
|
|
1044
|
+
__name(getTempDownloadDir, "getTempDownloadDir");
|
|
1045
|
+
function clearTempDownloads() {
|
|
1046
|
+
const dir = getTempDownloadDir();
|
|
1047
|
+
if (fs2.existsSync(dir)) {
|
|
1048
|
+
fs2.rmSync(dir, {
|
|
1049
|
+
recursive: true,
|
|
1050
|
+
force: true
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
__name(clearTempDownloads, "clearTempDownloads");
|
|
1040
1055
|
function getSaveSettings() {
|
|
1041
1056
|
const s = getFlowSettings();
|
|
1042
1057
|
return {
|
|
@@ -1280,7 +1295,7 @@ var DownloadTask = class extends Task {
|
|
|
1280
1295
|
for (const src of sources) {
|
|
1281
1296
|
if (src !== selectedSource && src.savedFile) {
|
|
1282
1297
|
try {
|
|
1283
|
-
await
|
|
1298
|
+
await fs3.unlink(src.savedFile.path);
|
|
1284
1299
|
} catch (e) {
|
|
1285
1300
|
if (e.code !== "ENOENT") throw e;
|
|
1286
1301
|
}
|
|
@@ -1293,7 +1308,7 @@ var DownloadTask = class extends Task {
|
|
|
1293
1308
|
}
|
|
1294
1309
|
outputCreated = true;
|
|
1295
1310
|
} else {
|
|
1296
|
-
await
|
|
1311
|
+
await fs3.copyFile(selectedSource.localFile.path, outputPath);
|
|
1297
1312
|
outputCreated = true;
|
|
1298
1313
|
}
|
|
1299
1314
|
await cleanAndTagFlac(outputPath, tags);
|
|
@@ -1315,7 +1330,7 @@ var DownloadTask = class extends Task {
|
|
|
1315
1330
|
});
|
|
1316
1331
|
} catch (err) {
|
|
1317
1332
|
if (outputCreated) {
|
|
1318
|
-
await
|
|
1333
|
+
await fs3.unlink(outputPath).catch(() => {
|
|
1319
1334
|
});
|
|
1320
1335
|
}
|
|
1321
1336
|
this.status.set({
|
|
@@ -1377,7 +1392,7 @@ __name(taskIdFromUrl, "taskIdFromUrl");
|
|
|
1377
1392
|
|
|
1378
1393
|
// src/utils/cache.ts
|
|
1379
1394
|
import { create } from "flat-cache";
|
|
1380
|
-
import * as
|
|
1395
|
+
import * as fs4 from "fs";
|
|
1381
1396
|
var CACHE_ID = "api-cache";
|
|
1382
1397
|
var cacheDir = getCacheDir();
|
|
1383
1398
|
var cache = create({
|
|
@@ -1393,7 +1408,7 @@ var cache = create({
|
|
|
1393
1408
|
function clearCache() {
|
|
1394
1409
|
cache.clear();
|
|
1395
1410
|
try {
|
|
1396
|
-
|
|
1411
|
+
fs4.rmSync(cacheDir, {
|
|
1397
1412
|
recursive: true,
|
|
1398
1413
|
force: true
|
|
1399
1414
|
});
|
|
@@ -1766,7 +1781,7 @@ var EnvironmentError = class extends Error {
|
|
|
1766
1781
|
};
|
|
1767
1782
|
|
|
1768
1783
|
// src/base/env.ts
|
|
1769
|
-
import * as
|
|
1784
|
+
import * as fs5 from "fs/promises";
|
|
1770
1785
|
import * as path3 from "path";
|
|
1771
1786
|
var Env = class {
|
|
1772
1787
|
static {
|
|
@@ -1806,7 +1821,7 @@ var Env = class {
|
|
|
1806
1821
|
async saveToEnvFile(key, value) {
|
|
1807
1822
|
const envPath = path3.resolve(process.cwd(), ".env");
|
|
1808
1823
|
try {
|
|
1809
|
-
const envContent = await
|
|
1824
|
+
const envContent = await fs5.readFile(envPath, {
|
|
1810
1825
|
encoding: "utf-8",
|
|
1811
1826
|
flag: "w+"
|
|
1812
1827
|
});
|
|
@@ -1822,7 +1837,7 @@ var Env = class {
|
|
|
1822
1837
|
${newEntry}
|
|
1823
1838
|
` : `${newEntry}
|
|
1824
1839
|
`;
|
|
1825
|
-
await
|
|
1840
|
+
await fs5.writeFile(envPath, updatedContent, "utf-8");
|
|
1826
1841
|
this.logger.info(`Successfully added ${key} to .env file.`);
|
|
1827
1842
|
} catch (error) {
|
|
1828
1843
|
this.logger.error(`Failed to save ${key} to .env file:`, {
|
|
@@ -2793,7 +2808,7 @@ _ts_decorate2([
|
|
|
2793
2808
|
], YoutubeService.prototype, "getTrackMetadata", null);
|
|
2794
2809
|
|
|
2795
2810
|
// src/flows/musicDownloadFlow/services/download-providers/ytdlp/YtDlpService.ts
|
|
2796
|
-
import
|
|
2811
|
+
import fs8 from "fs";
|
|
2797
2812
|
import path7 from "path";
|
|
2798
2813
|
import { YtDlp } from "ytdlp-nodejs";
|
|
2799
2814
|
|
|
@@ -2811,7 +2826,7 @@ var DownloadService = class extends ServiceBase {
|
|
|
2811
2826
|
};
|
|
2812
2827
|
|
|
2813
2828
|
// src/utils/ytdlp-setup.ts
|
|
2814
|
-
import * as
|
|
2829
|
+
import * as fs6 from "fs/promises";
|
|
2815
2830
|
import { createWriteStream } from "fs";
|
|
2816
2831
|
import * as path4 from "path";
|
|
2817
2832
|
import * as https from "https";
|
|
@@ -2830,13 +2845,13 @@ async function ensureYtDlpSetup() {
|
|
|
2830
2845
|
const binaryName = `yt-dlp_${latestVersion}.exe`;
|
|
2831
2846
|
const binaryPath = path4.join(getBinDir(), binaryName);
|
|
2832
2847
|
try {
|
|
2833
|
-
await
|
|
2848
|
+
await fs6.access(binaryPath);
|
|
2834
2849
|
globalLogger.info(`yt-dlp ${latestVersion} already installed at ${binaryPath}`);
|
|
2835
2850
|
return binaryPath;
|
|
2836
2851
|
} catch {
|
|
2837
2852
|
globalLogger.info(`yt-dlp ${latestVersion} not found, downloading\u2026`);
|
|
2838
2853
|
}
|
|
2839
|
-
await
|
|
2854
|
+
await fs6.mkdir(getBinDir(), {
|
|
2840
2855
|
recursive: true
|
|
2841
2856
|
});
|
|
2842
2857
|
await cleanupOldVersions("yt-dlp_", binaryName);
|
|
@@ -2856,14 +2871,14 @@ async function ensureYtDlpSetup() {
|
|
|
2856
2871
|
__name(ensureYtDlpSetup, "ensureYtDlpSetup");
|
|
2857
2872
|
async function findExistingBinary(prefix, suffix) {
|
|
2858
2873
|
try {
|
|
2859
|
-
const files = await
|
|
2874
|
+
const files = await fs6.readdir(getBinDir());
|
|
2860
2875
|
const binaries = files.filter((file) => file.startsWith(prefix) && file.endsWith(suffix));
|
|
2861
2876
|
if (binaries.length === 0) {
|
|
2862
2877
|
return null;
|
|
2863
2878
|
}
|
|
2864
2879
|
binaries.sort().reverse();
|
|
2865
2880
|
const binaryPath = path4.join(getBinDir(), binaries[0]);
|
|
2866
|
-
await
|
|
2881
|
+
await fs6.access(binaryPath);
|
|
2867
2882
|
return binaryPath;
|
|
2868
2883
|
} catch (error) {
|
|
2869
2884
|
globalLogger.warn(`Failed to find existing binary: ${error}`);
|
|
@@ -2898,10 +2913,10 @@ async function getLatestYtDlpVersion() {
|
|
|
2898
2913
|
__name(getLatestYtDlpVersion, "getLatestYtDlpVersion");
|
|
2899
2914
|
async function cleanupOldVersions(prefix, currentVersion) {
|
|
2900
2915
|
try {
|
|
2901
|
-
const files = await
|
|
2916
|
+
const files = await fs6.readdir(getBinDir());
|
|
2902
2917
|
const oldVersions = files.filter((file) => file.startsWith(prefix) && file.endsWith(".exe") && file !== currentVersion);
|
|
2903
2918
|
for (const file of oldVersions) {
|
|
2904
|
-
await
|
|
2919
|
+
await fs6.unlink(path4.join(getBinDir(), file));
|
|
2905
2920
|
globalLogger.info(`Cleaned up old version: ${file}`);
|
|
2906
2921
|
}
|
|
2907
2922
|
} catch (error) {
|
|
@@ -2929,7 +2944,7 @@ async function downloadFile(url, destination) {
|
|
|
2929
2944
|
resolve2();
|
|
2930
2945
|
});
|
|
2931
2946
|
fileStream.on("error", (error) => {
|
|
2932
|
-
|
|
2947
|
+
fs6.unlink(destination).catch(() => {
|
|
2933
2948
|
});
|
|
2934
2949
|
reject(error);
|
|
2935
2950
|
});
|
|
@@ -2941,7 +2956,7 @@ async function downloadFile(url, destination) {
|
|
|
2941
2956
|
__name(downloadFile, "downloadFile");
|
|
2942
2957
|
|
|
2943
2958
|
// src/utils/ffmpeg-setup.ts
|
|
2944
|
-
import * as
|
|
2959
|
+
import * as fs7 from "fs/promises";
|
|
2945
2960
|
import { createWriteStream as createWriteStream2 } from "fs";
|
|
2946
2961
|
import * as path5 from "path";
|
|
2947
2962
|
import * as https2 from "https";
|
|
@@ -2962,13 +2977,13 @@ async function ensureFfmpeg() {
|
|
|
2962
2977
|
const binaryName = `ffmpeg_${versionDate}.exe`;
|
|
2963
2978
|
const binaryPath = path5.join(getBinDir(), binaryName);
|
|
2964
2979
|
try {
|
|
2965
|
-
await
|
|
2980
|
+
await fs7.access(binaryPath);
|
|
2966
2981
|
globalLogger.info(`ffmpeg ${versionDate} already installed at ${binaryPath}`);
|
|
2967
2982
|
return binaryPath;
|
|
2968
2983
|
} catch {
|
|
2969
2984
|
globalLogger.info(`ffmpeg ${versionDate} not found, downloading\u2026`);
|
|
2970
2985
|
}
|
|
2971
|
-
await
|
|
2986
|
+
await fs7.mkdir(getBinDir(), {
|
|
2972
2987
|
recursive: true
|
|
2973
2988
|
});
|
|
2974
2989
|
await cleanupOldVersions2("ffmpeg_", binaryName);
|
|
@@ -2985,8 +3000,8 @@ async function ensureFfmpeg() {
|
|
|
2985
3000
|
}
|
|
2986
3001
|
zip.extractEntryTo(ffmpegEntry, getBinDir(), false, true);
|
|
2987
3002
|
const extractedPath = path5.join(getBinDir(), "ffmpeg.exe");
|
|
2988
|
-
await
|
|
2989
|
-
await
|
|
3003
|
+
await fs7.rename(extractedPath, binaryPath);
|
|
3004
|
+
await fs7.unlink(zipPath);
|
|
2990
3005
|
globalLogger.info(`Successfully downloaded ffmpeg ${versionDate} to ${binaryPath}`);
|
|
2991
3006
|
return binaryPath;
|
|
2992
3007
|
} catch (error) {
|
|
@@ -3001,14 +3016,14 @@ async function ensureFfmpeg() {
|
|
|
3001
3016
|
__name(ensureFfmpeg, "ensureFfmpeg");
|
|
3002
3017
|
async function findExistingBinary2(prefix, suffix) {
|
|
3003
3018
|
try {
|
|
3004
|
-
const files = await
|
|
3019
|
+
const files = await fs7.readdir(getBinDir());
|
|
3005
3020
|
const binaries = files.filter((file) => file.startsWith(prefix) && file.endsWith(suffix));
|
|
3006
3021
|
if (binaries.length === 0) {
|
|
3007
3022
|
return null;
|
|
3008
3023
|
}
|
|
3009
3024
|
binaries.sort().reverse();
|
|
3010
3025
|
const binaryPath = path5.join(getBinDir(), binaries[0]);
|
|
3011
|
-
await
|
|
3026
|
+
await fs7.access(binaryPath);
|
|
3012
3027
|
return binaryPath;
|
|
3013
3028
|
} catch (error) {
|
|
3014
3029
|
globalLogger.warn(`Failed to find existing binary: ${error}`);
|
|
@@ -3043,10 +3058,10 @@ async function getLatestFfmpegRelease() {
|
|
|
3043
3058
|
__name(getLatestFfmpegRelease, "getLatestFfmpegRelease");
|
|
3044
3059
|
async function cleanupOldVersions2(prefix, currentVersion) {
|
|
3045
3060
|
try {
|
|
3046
|
-
const files = await
|
|
3061
|
+
const files = await fs7.readdir(getBinDir());
|
|
3047
3062
|
const oldVersions = files.filter((file) => file.startsWith(prefix) && file.endsWith(".exe") && file !== currentVersion);
|
|
3048
3063
|
for (const file of oldVersions) {
|
|
3049
|
-
await
|
|
3064
|
+
await fs7.unlink(path5.join(getBinDir(), file));
|
|
3050
3065
|
globalLogger.info(`Cleaned up old version: ${file}`);
|
|
3051
3066
|
}
|
|
3052
3067
|
} catch (error) {
|
|
@@ -3074,7 +3089,7 @@ async function downloadFile2(url, destination) {
|
|
|
3074
3089
|
resolve2();
|
|
3075
3090
|
});
|
|
3076
3091
|
fileStream.on("error", (error) => {
|
|
3077
|
-
|
|
3092
|
+
fs7.unlink(destination).catch(() => {
|
|
3078
3093
|
});
|
|
3079
3094
|
reject(error);
|
|
3080
3095
|
});
|
|
@@ -3180,11 +3195,12 @@ var YtDlpService = class _YtDlpService extends DownloadService {
|
|
|
3180
3195
|
});
|
|
3181
3196
|
const artistName = trackMetadata.artists?.[0]?.name || "Unknown Artist";
|
|
3182
3197
|
const outputName = `${artistName} - ${trackMetadata.trackName}`;
|
|
3198
|
+
const uriSlug = trackMetadata.uri?.replace(/::/g, "-").replace(/:/g, "-") ?? "";
|
|
3183
3199
|
const format = "flac";
|
|
3184
|
-
const filename = `${outputName}.${format}`;
|
|
3185
|
-
const fullPath = path7.join(
|
|
3200
|
+
const filename = uriSlug ? `${outputName} ${uriSlug}.${format}` : `${outputName}.${format}`;
|
|
3201
|
+
const fullPath = path7.join(getTempDownloadDir(), filename);
|
|
3186
3202
|
let localFile;
|
|
3187
|
-
if (
|
|
3203
|
+
if (fs8.existsSync(fullPath)) {
|
|
3188
3204
|
this.logger.info(`File already exists, skipping download: ${filename}`);
|
|
3189
3205
|
localFile = {
|
|
3190
3206
|
state: "found",
|
|
@@ -3194,14 +3210,14 @@ var YtDlpService = class _YtDlpService extends DownloadService {
|
|
|
3194
3210
|
sourceUrl: trackUrl
|
|
3195
3211
|
};
|
|
3196
3212
|
} else {
|
|
3197
|
-
if (!
|
|
3198
|
-
|
|
3213
|
+
if (!fs8.existsSync(getTempDownloadDir())) {
|
|
3214
|
+
fs8.mkdirSync(getTempDownloadDir(), {
|
|
3199
3215
|
recursive: true
|
|
3200
3216
|
});
|
|
3201
3217
|
}
|
|
3202
3218
|
const cookiesPath = path7.join(getBinDir(), "cookies.txt");
|
|
3203
3219
|
let downloadOptions = {
|
|
3204
|
-
paths:
|
|
3220
|
+
paths: getTempDownloadDir(),
|
|
3205
3221
|
output: filename,
|
|
3206
3222
|
audioFormat: format,
|
|
3207
3223
|
extractAudio: true,
|
|
@@ -3213,7 +3229,7 @@ var YtDlpService = class _YtDlpService extends DownloadService {
|
|
|
3213
3229
|
this.logger.debug(`Download progress: ${progress.percentage}% ${progress.speed_str} (ETA: ${progress.eta_str})`);
|
|
3214
3230
|
}, "onProgress")
|
|
3215
3231
|
};
|
|
3216
|
-
if (
|
|
3232
|
+
if (fs8.existsSync(cookiesPath)) {
|
|
3217
3233
|
this.logger.debug(`Using cookies from ${cookiesPath}`);
|
|
3218
3234
|
downloadOptions.cookies = cookiesPath;
|
|
3219
3235
|
} else {
|
|
@@ -3386,6 +3402,23 @@ function buildFlowSettingsItems(flowSettings, metadataProviders, downloadProvide
|
|
|
3386
3402
|
}
|
|
3387
3403
|
}), "set")
|
|
3388
3404
|
});
|
|
3405
|
+
items.push({
|
|
3406
|
+
kind: "textInput",
|
|
3407
|
+
indent: 0,
|
|
3408
|
+
label: "\u{1F5C1} Temporary download directory",
|
|
3409
|
+
get: /* @__PURE__ */ __name(() => flowSettings.download.outputTemporaryDir, "get"),
|
|
3410
|
+
set: /* @__PURE__ */ __name((v) => onChange({
|
|
3411
|
+
download: {
|
|
3412
|
+
outputTemporaryDir: v
|
|
3413
|
+
}
|
|
3414
|
+
}), "set")
|
|
3415
|
+
});
|
|
3416
|
+
items.push({
|
|
3417
|
+
kind: "action",
|
|
3418
|
+
indent: 0,
|
|
3419
|
+
label: "\u26D2 Clear temporary downloads",
|
|
3420
|
+
run: /* @__PURE__ */ __name(() => clearTempDownloads(), "run")
|
|
3421
|
+
});
|
|
3389
3422
|
if (downloadProviders.length > 0) {
|
|
3390
3423
|
items.push({
|
|
3391
3424
|
kind: "subHeader",
|
|
@@ -3831,10 +3864,10 @@ import { spawn as spawn2 } from "child_process";
|
|
|
3831
3864
|
import * as net from "net";
|
|
3832
3865
|
import * as path9 from "path";
|
|
3833
3866
|
import * as os2 from "os";
|
|
3834
|
-
import * as
|
|
3867
|
+
import * as fs10 from "fs";
|
|
3835
3868
|
|
|
3836
3869
|
// src/utils/mpv-setup.ts
|
|
3837
|
-
import * as
|
|
3870
|
+
import * as fs9 from "fs/promises";
|
|
3838
3871
|
import { createWriteStream as createWriteStream3 } from "fs";
|
|
3839
3872
|
import * as path8 from "path";
|
|
3840
3873
|
import * as https3 from "https";
|
|
@@ -3874,13 +3907,13 @@ async function ensureMpv() {
|
|
|
3874
3907
|
const binaryName = `mpv_${version}.exe`;
|
|
3875
3908
|
const binaryPath = path8.join(getBinDir(), binaryName);
|
|
3876
3909
|
try {
|
|
3877
|
-
await
|
|
3910
|
+
await fs9.access(binaryPath);
|
|
3878
3911
|
globalLogger.info(`mpv ${version} already at ${binaryPath}`);
|
|
3879
3912
|
return binaryPath;
|
|
3880
3913
|
} catch {
|
|
3881
3914
|
globalLogger.info(`Downloading mpv ${version}...`);
|
|
3882
3915
|
}
|
|
3883
|
-
await
|
|
3916
|
+
await fs9.mkdir(getBinDir(), {
|
|
3884
3917
|
recursive: true
|
|
3885
3918
|
});
|
|
3886
3919
|
await cleanupOldVersions3("mpv_", binaryName);
|
|
@@ -3892,8 +3925,8 @@ async function ensureMpv() {
|
|
|
3892
3925
|
await downloadFile3(asset.browser_download_url, archivePath);
|
|
3893
3926
|
globalLogger.info("Extracting mpv.exe...");
|
|
3894
3927
|
await extract7z(archivePath, getBinDir(), "mpv.exe");
|
|
3895
|
-
await
|
|
3896
|
-
await
|
|
3928
|
+
await fs9.rename(path8.join(getBinDir(), "mpv.exe"), binaryPath);
|
|
3929
|
+
await fs9.unlink(archivePath);
|
|
3897
3930
|
globalLogger.info(`mpv ${version} installed at ${binaryPath}`);
|
|
3898
3931
|
return binaryPath;
|
|
3899
3932
|
} catch (err) {
|
|
@@ -3950,11 +3983,11 @@ async function extract7z(archivePath, destDir, fileFilter) {
|
|
|
3950
3983
|
__name(extract7z, "extract7z");
|
|
3951
3984
|
async function findExistingBinary3(prefix, suffix) {
|
|
3952
3985
|
try {
|
|
3953
|
-
const files = await
|
|
3986
|
+
const files = await fs9.readdir(getBinDir());
|
|
3954
3987
|
const matches = files.filter((f) => f.startsWith(prefix) && f.endsWith(suffix)).sort().reverse();
|
|
3955
3988
|
if (!matches.length) return null;
|
|
3956
3989
|
const p = path8.join(getBinDir(), matches[0]);
|
|
3957
|
-
await
|
|
3990
|
+
await fs9.access(p);
|
|
3958
3991
|
return p;
|
|
3959
3992
|
} catch {
|
|
3960
3993
|
return null;
|
|
@@ -3963,10 +3996,10 @@ async function findExistingBinary3(prefix, suffix) {
|
|
|
3963
3996
|
__name(findExistingBinary3, "findExistingBinary");
|
|
3964
3997
|
async function cleanupOldVersions3(prefix, currentName) {
|
|
3965
3998
|
try {
|
|
3966
|
-
const files = await
|
|
3999
|
+
const files = await fs9.readdir(getBinDir());
|
|
3967
4000
|
for (const f of files) {
|
|
3968
4001
|
if (f.startsWith(prefix) && f.endsWith(".exe") && f !== currentName) {
|
|
3969
|
-
await
|
|
4002
|
+
await fs9.unlink(path8.join(getBinDir(), f));
|
|
3970
4003
|
globalLogger.info(`Removed old mpv binary: ${f}`);
|
|
3971
4004
|
}
|
|
3972
4005
|
}
|
|
@@ -3991,7 +4024,7 @@ async function downloadFile3(url, destination) {
|
|
|
3991
4024
|
res.pipe(ws);
|
|
3992
4025
|
ws.on("finish", () => ws.close(() => resolve2()));
|
|
3993
4026
|
ws.on("error", (e) => {
|
|
3994
|
-
|
|
4027
|
+
fs9.unlink(destination).catch(() => {
|
|
3995
4028
|
});
|
|
3996
4029
|
reject(e);
|
|
3997
4030
|
});
|
|
@@ -4048,8 +4081,8 @@ var MpvPlayer = class extends EventEmitter2 {
|
|
|
4048
4081
|
return this._starting;
|
|
4049
4082
|
}
|
|
4050
4083
|
async _start() {
|
|
4051
|
-
if (process.platform !== "win32" &&
|
|
4052
|
-
|
|
4084
|
+
if (process.platform !== "win32" && fs10.existsSync(this.socketPath)) {
|
|
4085
|
+
fs10.unlinkSync(this.socketPath);
|
|
4053
4086
|
}
|
|
4054
4087
|
const mpvBin = await ensureMpv();
|
|
4055
4088
|
this.mpvProcess = spawn2(mpvBin, [
|
|
@@ -4083,7 +4116,7 @@ var MpvPlayer = class extends EventEmitter2 {
|
|
|
4083
4116
|
return new Promise((resolve2, reject) => {
|
|
4084
4117
|
const deadline = Date.now() + timeoutMs;
|
|
4085
4118
|
const poll = /* @__PURE__ */ __name(() => {
|
|
4086
|
-
if (
|
|
4119
|
+
if (fs10.existsSync(this.socketPath)) {
|
|
4087
4120
|
resolve2();
|
|
4088
4121
|
} else if (Date.now() >= deadline) {
|
|
4089
4122
|
reject(new Error("Timed out waiting for mpv IPC socket"));
|
|
@@ -7493,7 +7526,7 @@ var MetadataPanel = /* @__PURE__ */ __name(({ selectedTask, width, height }) =>
|
|
|
7493
7526
|
// src/components/SecondaryPanel/DownloadPanel/DownloadPanel.tsx
|
|
7494
7527
|
import React45, { useState as useState15, useEffect as useEffect14 } from "react";
|
|
7495
7528
|
import { Box as Box33, Text as Text42, useInput as useInput9 } from "ink";
|
|
7496
|
-
import
|
|
7529
|
+
import fs12 from "fs";
|
|
7497
7530
|
|
|
7498
7531
|
// src/components/SecondaryPanel/DownloadPanel/DownloadSourceTree/DownloadSourceTree.tsx
|
|
7499
7532
|
import React39 from "react";
|
|
@@ -7819,7 +7852,7 @@ var DownloadSourceTree = /* @__PURE__ */ __name(({ sources, selectedSourceIndex,
|
|
|
7819
7852
|
import React44, { useState as useState14, useEffect as useEffect13 } from "react";
|
|
7820
7853
|
import { Box as Box32, Text as Text41, useInput as useInput8 } from "ink";
|
|
7821
7854
|
import * as path11 from "path";
|
|
7822
|
-
import * as
|
|
7855
|
+
import * as fs11 from "fs";
|
|
7823
7856
|
|
|
7824
7857
|
// src/components/SecondaryPanel/DownloadPanel/PlaybackBar.tsx
|
|
7825
7858
|
import React40 from "react";
|
|
@@ -8238,7 +8271,7 @@ var DownloadSourceDetail = /* @__PURE__ */ __name(({ source, savedSource, compil
|
|
|
8238
8271
|
}
|
|
8239
8272
|
const isSaved = !!source.savedFile;
|
|
8240
8273
|
const borderColor = isSaved ? "white" : "green";
|
|
8241
|
-
const outputFileExists = !isSaved && compiled !== null &&
|
|
8274
|
+
const outputFileExists = !isSaved && compiled !== null && fs11.existsSync(path11.join(outputDir, computeOutputFilename(compiled)));
|
|
8242
8275
|
const headerLabel = isSaved ? "FILE ON DISK" : "NEW FILE";
|
|
8243
8276
|
const headerColor = isSaved ? "white" : "green";
|
|
8244
8277
|
const innerW = width - 2;
|
|
@@ -8544,7 +8577,7 @@ var DownloadPanel = /* @__PURE__ */ __name(({ selectedTask, width, height }) =>
|
|
|
8544
8577
|
hint: "Absolute path to the FLAC file"
|
|
8545
8578
|
});
|
|
8546
8579
|
if (!newPath.trim()) return;
|
|
8547
|
-
if (!
|
|
8580
|
+
if (!fs12.existsSync(newPath)) return;
|
|
8548
8581
|
typedTask?.updateLocalFile(sourceIdx, newPath);
|
|
8549
8582
|
} catch {
|
|
8550
8583
|
}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goblin-malin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A keyboard-driven terminal UI for downloading and tagging music tracks with metadata from Spotify and YouTube",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
|
-
"README.md"
|
|
19
|
+
"README.md",
|
|
20
|
+
"docs/screenshots"
|
|
20
21
|
],
|
|
21
22
|
"scripts": {
|
|
22
23
|
"dev": "tsx src/cli.ts",
|