@codearcade/subtitle-generator 1.0.2 → 1.0.4
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/cli/index.js +1 -1
- package/init/index.js +87 -93
- package/package.json +3 -2
package/cli/index.js
CHANGED
package/init/index.js
CHANGED
|
@@ -3,6 +3,9 @@ import path from "path";
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import os from "os";
|
|
5
5
|
import { execSync } from "child_process";
|
|
6
|
+
import { Readable } from "stream";
|
|
7
|
+
import { pipeline } from "stream/promises";
|
|
8
|
+
import unzipper from "unzipper";
|
|
6
9
|
|
|
7
10
|
const init = async () => {
|
|
8
11
|
const { modelSize } = await inquirer.prompt([
|
|
@@ -38,36 +41,51 @@ const init = async () => {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
console.log(`Downloading ${label}...`);
|
|
41
|
-
const res = await fetch(url
|
|
44
|
+
const res = await fetch(url, {
|
|
45
|
+
redirect: "follow",
|
|
46
|
+
headers: {
|
|
47
|
+
"User-Agent": "node",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const contentType = res.headers.get("content-type");
|
|
52
|
+
|
|
53
|
+
if (!contentType || !contentType.includes("application")) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Invalid response. Expected binary file but got: ${contentType}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
42
58
|
|
|
43
|
-
if (!res.ok)
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
throw new Error(`Failed to download: ${res.statusText}`);
|
|
61
|
+
}
|
|
44
62
|
|
|
45
63
|
const total = Number(res.headers.get("content-length")) || 0;
|
|
46
64
|
let downloaded = 0;
|
|
47
65
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
);
|
|
66
|
+
const fileStream = fs.createWriteStream(dest);
|
|
67
|
+
|
|
68
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
69
|
+
|
|
70
|
+
nodeStream.on("data", (chunk) => {
|
|
71
|
+
downloaded += chunk.length;
|
|
72
|
+
if (total) {
|
|
73
|
+
const percent = ((downloaded / total) * 100).toFixed(2);
|
|
74
|
+
process.stdout.write(`Downloading ${label}... ${percent}%\r`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await pipeline(nodeStream, fileStream);
|
|
79
|
+
|
|
80
|
+
const stats = fs.statSync(dest);
|
|
81
|
+
|
|
82
|
+
if (stats.size < 100000) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Downloaded file too small (${stats.size} bytes). Likely HTML instead of ZIP.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`\n${label} download complete.`);
|
|
71
89
|
}
|
|
72
90
|
|
|
73
91
|
// 1. Download the AI Model
|
|
@@ -89,11 +107,11 @@ const init = async () => {
|
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
if (isWindows) {
|
|
92
|
-
// Windows Setup
|
|
93
110
|
const binaryDest = path.join(whisperDir, "whisper-cli.exe");
|
|
111
|
+
|
|
94
112
|
if (!fs.existsSync(binaryDest)) {
|
|
95
113
|
console.log("Detecting Windows architecture...");
|
|
96
|
-
const arch = os.arch();
|
|
114
|
+
const arch = os.arch();
|
|
97
115
|
|
|
98
116
|
let zipName;
|
|
99
117
|
|
|
@@ -102,15 +120,10 @@ const init = async () => {
|
|
|
102
120
|
} else if (arch === "ia32") {
|
|
103
121
|
zipName = "whisper-bin-Win32.zip";
|
|
104
122
|
} else if (arch === "arm64") {
|
|
105
|
-
|
|
106
|
-
console.log(
|
|
107
|
-
"ARM64 architecture detected. Using x64 binary via Windows emulation...",
|
|
108
|
-
);
|
|
123
|
+
console.log("ARM64 detected → using x64 binary via emulation...");
|
|
109
124
|
zipName = "whisper-bin-x64.zip";
|
|
110
125
|
} else {
|
|
111
|
-
throw new Error(
|
|
112
|
-
`Unsupported Windows architecture: ${arch}. Whisper.cpp does not provide pre-compiled binaries for this system.`,
|
|
113
|
-
);
|
|
126
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
const zipUrl = `https://github.com/ggml-org/whisper.cpp/releases/download/v1.8.4/${zipName}`;
|
|
@@ -118,72 +131,53 @@ const init = async () => {
|
|
|
118
131
|
|
|
119
132
|
await downloadFile(zipUrl, zipPath, `Whisper Windows Binary (${arch})`);
|
|
120
133
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
let retryDelay = 1000; // 1 second
|
|
126
|
-
|
|
127
|
-
// 1. The Retry Loop for safe extraction
|
|
128
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
129
|
-
try {
|
|
130
|
-
execSync(
|
|
131
|
-
`powershell -command "Expand-Archive -Force -LiteralPath '${zipPath}' -DestinationPath '${whisperDir}'"`,
|
|
132
|
-
{ stdio: "inherit" },
|
|
133
|
-
);
|
|
134
|
-
extractionSuccess = true;
|
|
135
|
-
break; // It worked! Break out of the loop.
|
|
136
|
-
} catch (error) {
|
|
137
|
-
if (attempt === maxRetries) {
|
|
138
|
-
console.error(
|
|
139
|
-
`\nExtraction failed after ${maxRetries} attempts. File might be permanently locked or corrupted.`,
|
|
140
|
-
);
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
console.log(
|
|
144
|
-
`\nFile locked by Windows (likely Antivirus). Retrying in 1 second... (Attempt ${attempt} of ${maxRetries})`,
|
|
145
|
-
);
|
|
146
|
-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
147
|
-
}
|
|
134
|
+
// ✅ sanity check
|
|
135
|
+
const stats = fs.statSync(zipPath);
|
|
136
|
+
if (stats.size < 1_000_000) {
|
|
137
|
+
throw new Error(`Downloaded ZIP too small (${stats.size} bytes)`);
|
|
148
138
|
}
|
|
149
139
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
140
|
+
console.log("Extracting binary (unzipper)...");
|
|
141
|
+
|
|
142
|
+
await new Promise((resolve, reject) => {
|
|
143
|
+
fs.createReadStream(zipPath)
|
|
144
|
+
.pipe(unzipper.Parse())
|
|
145
|
+
.on("entry", (entry) => {
|
|
146
|
+
const filePath = path.join(whisperDir, entry.path);
|
|
155
147
|
|
|
156
|
-
|
|
157
|
-
|
|
148
|
+
if (entry.type === "Directory") {
|
|
149
|
+
fs.mkdirSync(filePath, { recursive: true });
|
|
150
|
+
entry.autodrain();
|
|
151
|
+
} else {
|
|
152
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
158
153
|
|
|
159
|
-
|
|
160
|
-
fs.renameSync(
|
|
161
|
-
path.join(possibleReleaseDir, file),
|
|
162
|
-
path.join(whisperDir, file),
|
|
163
|
-
);
|
|
154
|
+
entry.pipe(fs.createWriteStream(filePath));
|
|
164
155
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
console.log(`Whisper binary setup complete for Windows (${arch}).`);
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.error("Error during binary cleanup/renaming:", error.message);
|
|
156
|
+
})
|
|
157
|
+
.on("close", resolve)
|
|
158
|
+
.on("error", reject);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// 🔍 Handle "Release" folder
|
|
162
|
+
const releaseDir = path.join(whisperDir, "Release");
|
|
163
|
+
|
|
164
|
+
if (fs.existsSync(releaseDir)) {
|
|
165
|
+
const files = fs.readdirSync(releaseDir);
|
|
166
|
+
|
|
167
|
+
for (const file of files) {
|
|
168
|
+
fs.renameSync(
|
|
169
|
+
path.join(releaseDir, file),
|
|
170
|
+
path.join(whisperDir, file),
|
|
171
|
+
);
|
|
185
172
|
}
|
|
173
|
+
|
|
174
|
+
fs.rmSync(releaseDir, { recursive: true, force: true });
|
|
186
175
|
}
|
|
176
|
+
|
|
177
|
+
// ✅ cleanup zip
|
|
178
|
+
fs.unlinkSync(zipPath);
|
|
179
|
+
|
|
180
|
+
console.log(`Whisper binary setup complete for Windows (${arch}).`);
|
|
187
181
|
} else {
|
|
188
182
|
console.log("Whisper binary already exists for Windows.");
|
|
189
183
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codearcade/subtitle-generator",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"@types/bun": "^1.3.11"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"inquirer": "^13.3.2"
|
|
61
|
+
"inquirer": "^13.3.2",
|
|
62
|
+
"unzipper": "^0.12.3"
|
|
62
63
|
}
|
|
63
64
|
}
|