@codearcade/subtitle-generator 1.0.3 → 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 +86 -100
- 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,44 +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
|
+
});
|
|
42
50
|
|
|
43
|
-
|
|
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
|
+
}
|
|
58
|
+
|
|
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
|
-
write(chunk) {
|
|
59
|
-
downloaded += chunk.length;
|
|
60
|
-
if (total) {
|
|
61
|
-
const percent = ((downloaded / total) * 100).toFixed(2);
|
|
62
|
-
process.stdout.write(`Downloading ${label}... ${percent}%\r`);
|
|
63
|
-
} else {
|
|
64
|
-
process.stdout.write(`Downloaded ${downloaded} bytes\r`);
|
|
65
|
-
}
|
|
66
|
-
file.write(chunk);
|
|
67
|
-
},
|
|
68
|
-
close() {
|
|
69
|
-
file.end(); // triggers 'close' event on the file stream once flushed
|
|
70
|
-
console.log(`\n${label} download complete.`);
|
|
71
|
-
},
|
|
72
|
-
abort(err) {
|
|
73
|
-
file.destroy(err);
|
|
74
|
-
reject(err);
|
|
75
|
-
},
|
|
76
|
-
}),
|
|
77
|
-
).catch(reject);
|
|
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
|
+
}
|
|
78
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.`);
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
// 1. Download the AI Model
|
|
@@ -97,11 +107,11 @@ const init = async () => {
|
|
|
97
107
|
}
|
|
98
108
|
|
|
99
109
|
if (isWindows) {
|
|
100
|
-
// Windows Setup
|
|
101
110
|
const binaryDest = path.join(whisperDir, "whisper-cli.exe");
|
|
111
|
+
|
|
102
112
|
if (!fs.existsSync(binaryDest)) {
|
|
103
113
|
console.log("Detecting Windows architecture...");
|
|
104
|
-
const arch = os.arch();
|
|
114
|
+
const arch = os.arch();
|
|
105
115
|
|
|
106
116
|
let zipName;
|
|
107
117
|
|
|
@@ -110,15 +120,10 @@ const init = async () => {
|
|
|
110
120
|
} else if (arch === "ia32") {
|
|
111
121
|
zipName = "whisper-bin-Win32.zip";
|
|
112
122
|
} else if (arch === "arm64") {
|
|
113
|
-
|
|
114
|
-
console.log(
|
|
115
|
-
"ARM64 architecture detected. Using x64 binary via Windows emulation...",
|
|
116
|
-
);
|
|
123
|
+
console.log("ARM64 detected → using x64 binary via emulation...");
|
|
117
124
|
zipName = "whisper-bin-x64.zip";
|
|
118
125
|
} else {
|
|
119
|
-
throw new Error(
|
|
120
|
-
`Unsupported Windows architecture: ${arch}. Whisper.cpp does not provide pre-compiled binaries for this system.`,
|
|
121
|
-
);
|
|
126
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
const zipUrl = `https://github.com/ggml-org/whisper.cpp/releases/download/v1.8.4/${zipName}`;
|
|
@@ -126,72 +131,53 @@ const init = async () => {
|
|
|
126
131
|
|
|
127
132
|
await downloadFile(zipUrl, zipPath, `Whisper Windows Binary (${arch})`);
|
|
128
133
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
let retryDelay = 1000; // 1 second
|
|
134
|
-
|
|
135
|
-
// 1. The Retry Loop for safe extraction
|
|
136
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
137
|
-
try {
|
|
138
|
-
execSync(
|
|
139
|
-
`powershell -command "Expand-Archive -Force -LiteralPath '${zipPath}' -DestinationPath '${whisperDir}'"`,
|
|
140
|
-
{ stdio: "inherit" },
|
|
141
|
-
);
|
|
142
|
-
extractionSuccess = true;
|
|
143
|
-
break; // It worked! Break out of the loop.
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (attempt === maxRetries) {
|
|
146
|
-
console.error(
|
|
147
|
-
`\nExtraction failed after ${maxRetries} attempts. File might be permanently locked or corrupted.`,
|
|
148
|
-
);
|
|
149
|
-
throw error;
|
|
150
|
-
}
|
|
151
|
-
console.log(
|
|
152
|
-
`\nFile locked by Windows (likely Antivirus). Retrying in 1 second... (Attempt ${attempt} of ${maxRetries})`,
|
|
153
|
-
);
|
|
154
|
-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
155
|
-
}
|
|
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)`);
|
|
156
138
|
}
|
|
157
139
|
|
|
158
|
-
|
|
159
|
-
if (extractionSuccess) {
|
|
160
|
-
try {
|
|
161
|
-
// Move everything out of the "Release" folder
|
|
162
|
-
const possibleReleaseDir = path.join(whisperDir, "Release");
|
|
140
|
+
console.log("Extracting binary (unzipper)...");
|
|
163
141
|
|
|
164
|
-
|
|
165
|
-
|
|
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);
|
|
166
147
|
|
|
167
|
-
|
|
168
|
-
fs.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
148
|
+
if (entry.type === "Directory") {
|
|
149
|
+
fs.mkdirSync(filePath, { recursive: true });
|
|
150
|
+
entry.autodrain();
|
|
151
|
+
} else {
|
|
152
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
153
|
+
|
|
154
|
+
entry.pipe(fs.createWriteStream(filePath));
|
|
172
155
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
console.log(`Whisper binary setup complete for Windows (${arch}).`);
|
|
191
|
-
} catch (error) {
|
|
192
|
-
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
|
+
);
|
|
193
172
|
}
|
|
173
|
+
|
|
174
|
+
fs.rmSync(releaseDir, { recursive: true, force: true });
|
|
194
175
|
}
|
|
176
|
+
|
|
177
|
+
// ✅ cleanup zip
|
|
178
|
+
fs.unlinkSync(zipPath);
|
|
179
|
+
|
|
180
|
+
console.log(`Whisper binary setup complete for Windows (${arch}).`);
|
|
195
181
|
} else {
|
|
196
182
|
console.log("Whisper binary already exists for Windows.");
|
|
197
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
|
}
|