tphim 1.0.2 → 1.0.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/pro-terminal.mjs +72 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tphim",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "TPHIM - Ultimate Video Pipeline: Download, Transcode HLS, AI Subtitles, and Cloud Upload.",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/pro-terminal.mjs CHANGED
@@ -41,6 +41,37 @@ function removeVietnameseTones(str) {
41
41
  .trim();
42
42
  }
43
43
 
44
+ function validateUrl(url) {
45
+ try {
46
+ new URL(url);
47
+ return true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ function extractTitleFromUrl(url) {
54
+ const urlParts = url.split('/').filter(p => p && p !== 'http:' && p !== 'https:');
55
+ const lastPart = urlParts[urlParts.length - 1];
56
+ const secondLastPart = urlParts[urlParts.length - 2];
57
+
58
+ // Clean up parts
59
+ const cleanLast = lastPart?.replace(/\.(mp4|m3u8|mkv|avi|mov|flv|webm|html|htm)$/i, '').replace(/[_\-]/g, ' ').trim();
60
+ const cleanSecond = secondLastPart?.replace(/[_\-]/g, ' ').trim();
61
+
62
+ // Prefer second to last part (usually folder/slug structure)
63
+ if (cleanSecond && cleanSecond.length > 2 && !['index', 'master', 'video', 'movie', 'play'].includes(cleanSecond.toLowerCase())) {
64
+ return cleanSecond;
65
+ }
66
+
67
+ // Fallback to last part
68
+ if (cleanLast && cleanLast.length > 2 && !['index', 'master', 'video', 'movie', 'play'].includes(cleanLast.toLowerCase())) {
69
+ return cleanLast;
70
+ }
71
+
72
+ return `movie-${Date.now()}`;
73
+ }
74
+
44
75
  function checkEnv() {
45
76
  const required = [
46
77
  'TEBI_ENDPOINT',
@@ -141,6 +172,15 @@ async function main() {
141
172
  hint: chalk.yellow('Separate multiple links with COMMA (,)'),
142
173
  validate: (value) => {
143
174
  if (!value) return 'System requires a data source.';
175
+
176
+ const links = value.split(',').map(l => l.trim()).filter(l => l);
177
+ const invalidLinks = links.filter(link => !validateUrl(link));
178
+
179
+ if (invalidLinks.length > 0) {
180
+ return `Invalid URL(s): ${invalidLinks.join(', ')}`;
181
+ }
182
+
183
+ return undefined;
144
184
  },
145
185
  }),
146
186
  title: () =>
@@ -192,20 +232,40 @@ async function main() {
192
232
  try {
193
233
  s.message('🛰️ Fetching metadata...');
194
234
  const { execSync } = await import('child_process');
195
- // Use a lighter check first
196
- let fetchedTitle = execSync(
197
- `yt-dlp --print title --skip-download "${inputUrl}"`,
198
- { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }
199
- ).trim();
200
-
201
- // Fallback: If title is 'master' or empty, use the URL slug
202
- if (!fetchedTitle || fetchedTitle === 'master' || fetchedTitle === 'index') {
203
- const parts = inputUrl.split('/').filter(p => p);
204
- // Get second to last part if it looks like a slug
205
- fetchedTitle = parts[parts.length - 2] || 'movie';
235
+
236
+ // Try multiple methods to get title
237
+ let fetchedTitle = '';
238
+
239
+ // Method 1: Try yt-dlp title
240
+ try {
241
+ fetchedTitle = execSync(
242
+ `yt-dlp --print title --skip-download "${inputUrl}"`,
243
+ { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 10000 }
244
+ ).trim();
245
+ } catch (e1) {
246
+ // Method 2: Try yt-dlp with different flags
247
+ try {
248
+ fetchedTitle = execSync(
249
+ `yt-dlp --get-title --no-download "${inputUrl}"`,
250
+ { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 10000 }
251
+ ).trim();
252
+ } catch (e2) {
253
+ // Method 3: Try generic approach
254
+ try {
255
+ const info = execSync(
256
+ `yt-dlp --dump-json --no-download "${inputUrl}"`,
257
+ { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 15000 }
258
+ );
259
+ const parsed = JSON.parse(info);
260
+ fetchedTitle = parsed.title || parsed.fulltitle || parsed.description?.split('\n')[0] || '';
261
+ } catch (e3) {
262
+ // Method 4: Extract from URL as last resort
263
+ fetchedTitle = extractTitleFromUrl(inputUrl);
264
+ }
265
+ }
206
266
  }
207
267
 
208
- currentTitle = fetchedTitle;
268
+ currentTitle = fetchedTitle || extractTitleFromUrl(inputUrl);
209
269
  } catch (e) {
210
270
  currentTitle = `movie-${Date.now()}`;
211
271
  }