@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 CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const args = process.argv.slice(2);
4
4
 
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) throw new Error(`Failed to download: ${res.statusText}`);
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 file = fs.createWriteStream(dest);
49
-
50
- await res.body.pipeTo(
51
- new WritableStream({
52
- write(chunk) {
53
- downloaded += chunk.length;
54
- if (total) {
55
- const percent = ((downloaded / total) * 100).toFixed(2);
56
- process.stdout.write(`Downloading ${label}... ${percent}%\r`);
57
- } else {
58
- process.stdout.write(`Downloaded ${downloaded} bytes\r`);
59
- }
60
- file.write(chunk);
61
- },
62
- close() {
63
- file.end();
64
- console.log(`\n${label} download complete.`);
65
- },
66
- abort(err) {
67
- file.destroy(err);
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(); // Usually 'x64', 'ia32', or 'arm64'
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
- // Windows ARM can emulate x64 executables
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
- console.log("Extracting binary...");
122
-
123
- let extractionSuccess = false;
124
- let maxRetries = 5;
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
- // 2. The Cleanup Phase (Only runs if extraction worked)
151
- if (extractionSuccess) {
152
- try {
153
- // Move everything out of the "Release" folder
154
- const possibleReleaseDir = path.join(whisperDir, "Release");
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
- if (fs.existsSync(possibleReleaseDir)) {
157
- const files = fs.readdirSync(possibleReleaseDir);
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
- for (const file of files) {
160
- fs.renameSync(
161
- path.join(possibleReleaseDir, file),
162
- path.join(whisperDir, file),
163
- );
154
+ entry.pipe(fs.createWriteStream(filePath));
164
155
  }
165
- // Delete the now-empty Release folder
166
- fs.rmdirSync(possibleReleaseDir);
167
- }
168
-
169
- // Rename extracted main.exe to match our wrapper expectations
170
- const extractedMain = path.join(whisperDir, "main.exe");
171
- if (fs.existsSync(extractedMain)) {
172
- fs.renameSync(extractedMain, binaryDest);
173
- } else {
174
- console.error("Warning: Could not find main.exe after extraction.");
175
- }
176
-
177
- // Clean up the zip file
178
- if (fs.existsSync(zipPath)) {
179
- fs.unlinkSync(zipPath);
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.2",
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
  }