tphim 1.0.1 → 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.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
- # 💎 TXA NEON CORE 2030
1
+ # 💎 TPHIM - Ultimate Video Pipeline
2
2
 
3
- **TXA Neon Core** là bộ công cụ tối thượng để xây dựng hệ thống VOD (Video on Demand) chuyên nghiệp. Hỗ trợ tự động hóa từ khâu tải video, transcode HLS đa chất lượng, tạo phụ đề AI và upload lên Cloud (S3/Tebi).
3
+ **TPHIM** là bộ công cụ tối thượng để xây dựng hệ thống VOD (Video on Demand) chuyên nghiệp. Hỗ trợ tự động hóa từ khâu tải video, transcode HLS đa chất lượng, tạo phụ đề AI và upload lên Cloud (S3/Tebi).
4
4
 
5
5
  ## 🚀 Tính năng vượt trội
6
6
  - **Batch Pipeline:** Xử lý hàng loạt phim chỉ với 1 dòng lệnh.
7
7
  - **AI Subtitles:** Tự động tạo phụ đề Tiếng Việt/Tiếng Anh bằng công cụ AI (Whisper) chạy offline.
8
- - **Neon Hybrid Player:** Trình phát V5 hỗ trợ tận răng cho cả Desktop và Mobile với cử chỉ vuốt cực xịn.
9
8
  - **Cloud Ready:** Upload trực tiếp lên Tebi.io hoặc bất kỳ S3-compatible storage nào.
9
+ - **Interactive CLI:** Giao diện terminal đẹp mắt với neon theme.
10
10
 
11
11
  ---
12
12
 
@@ -48,7 +48,7 @@ Bạn hoàn toàn có thể chạy Pipeline này ngay trên điện thoại:
48
48
  pkg update && pkg upgrade
49
49
  pkg install nodejs ffmpeg python python-pip wget
50
50
  ```
51
- 2. **Cài đặt yt-dlp:**
51
+ 2. **Cài đặt yt-dlp:**(không nhất thiết phải cài)
52
52
  ```bash
53
53
  pip install yt-dlp
54
54
  ```
@@ -61,14 +61,14 @@ Bạn hoàn toàn có thể chạy Pipeline này ngay trên điện thoại:
61
61
 
62
62
  Cài đặt vào dự án của bạn:
63
63
  ```bash
64
- npm install txa-neon-core
64
+ npm install tphim
65
65
  ```
66
66
 
67
67
  Trong code Node.js:
68
68
  ```javascript
69
- import { runPipeline } from 'txa-neon-core';
69
+ import { executePipeline } from 'tphim';
70
70
 
71
- await runPipeline({
71
+ await executePipeline({
72
72
  input: "https://link-phim.com/phim.m3u8",
73
73
  slug: "phim-hay-2030",
74
74
  title: "Phim Hay 2030",
@@ -78,32 +78,56 @@ await runPipeline({
78
78
 
79
79
  ---
80
80
 
81
- ## 🎬 Tích hợp TXAPlayer vào Website
81
+ ## ⌨️ Sử dụng CLI (Terminal)
82
82
 
83
- Link trực tiếp mã nguồn trình phát:
84
- `node_modules/txa-neon-core/TXAPlayer.js`
83
+ ### Cài đặt globally:
84
+ ```bash
85
+ npm install -g tphim
86
+ ```
85
87
 
86
- ```html
87
- <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
88
- <script src="path/to/TXAPlayer.js"></script>
88
+ ### Các lệnh CLI:
89
+ ```bash
90
+ # Xem hướng dẫn
91
+ ntxa help
89
92
 
90
- <div id="player-container"></div>
93
+ # Chạy interactive mode
94
+ ntxa
95
+
96
+ # Chạy với tên phim mặc định
97
+ ntxa "Tên Phim Của Bạn"
91
98
 
92
- <script>
93
- new TXAPlayer("player-container", "https://s3.tebi.io/.../master.m3u8");
94
- </script>
99
+ # Xử lý batch nhiều link
100
+ ntxa
101
+ # (sẽ hỏi nhập nhiều link cách nhau bằng dấu phẩy)
102
+ ```
103
+
104
+ ### Cấu hình bắt buộc:
105
+ Tạo file `.env` với các biến sau:
106
+ ```env
107
+ TEBI_ENDPOINT=https://s3.tebi.io
108
+ TEBI_ACCESS_KEY_ID=your_access_key
109
+ TEBI_SECRET_ACCESS_KEY=your_secret_key
110
+ TEBI_BUCKET=your_bucket_name
111
+ TEBI_PUBLIC_URL=https://your-bucket.tebi.io
95
112
  ```
96
113
 
97
114
  ---
98
115
 
99
- ## ⌨️ Sử dụng CLI (Terminal)
116
+ ## 🔧 Tính năng CLI
100
117
 
101
- Sau khi cài đặt gói globally (`npm install -g`), bạn có thể dùng lệnh:
118
+ **Interactive Mode:**
119
+ - Nhập multiple links (cách nhau bằng dấu phẩy)
120
+ - Tự động fetch metadata từ video
121
+ - Chọn ngôn ngữ phụ đề (VI/EN/Both)
122
+ - Batch processing với progress bar
102
123
 
124
+ **Help System:**
103
125
  ```bash
104
- ntxa "titile phim"
105
- # Hoặc chạy batch mode
106
- ntxa
126
+ ntxa help # Hiển thị toàn bộ hướng dẫn
127
+ ntxa --help # Tương tự
128
+ ntxa -h # Tương tự
107
129
  ```
108
130
 
109
- *Sản phẩm được phát triển bởi Antigravity AI Engine v2030.* 🍿🎬
131
+ ---
132
+
133
+ *Phát triển bởi TXA - Ultimate Video Pipeline 2030* 🍿🎬
package/index.js CHANGED
@@ -1,16 +1,25 @@
1
1
  // index.js
2
2
  /**
3
- * TXA NEON CORE 2030
3
+ * TPHIM - Ultimate Video Pipeline
4
4
  * The ultimate video processing pipeline and player library
5
5
  */
6
6
 
7
7
  import { executePipeline } from './pipeline.mjs';
8
- import { readFileSync } from 'fs';
8
+ import { readFileSync, copyFileSync, existsSync } from 'fs';
9
9
  import { join, dirname } from 'path';
10
10
  import { fileURLToPath } from 'url';
11
11
 
12
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
13
 
14
+ // Auto-copy .env.example to .env if .env doesn't exist
15
+ const envPath = join(__dirname, '.env');
16
+ const envExamplePath = join(__dirname, '.env.example');
17
+
18
+ if (!existsSync(envPath) && existsSync(envExamplePath)) {
19
+ copyFileSync(envExamplePath, envPath);
20
+ console.log('📄 Auto-copied .env.example to .env');
21
+ }
22
+
14
23
  /**
15
24
  * Node-side API: Run the full VOD pipeline.
16
25
  * @param {Object} options - pipeline options { input, slug, title, langArg }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tphim",
3
- "version": "1.0.1",
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
@@ -10,8 +10,22 @@ import figlet from 'figlet';
10
10
  import ora from 'ora';
11
11
  import { spawn } from 'child_process';
12
12
  import { executePipeline } from './pipeline.mjs';
13
+ import { copyFileSync, existsSync } from 'fs';
14
+ import { join, dirname } from 'path';
15
+ import { fileURLToPath } from 'url';
13
16
  import 'dotenv/config';
14
17
 
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+
20
+ // Auto-copy .env.example to .env if .env doesn't exist
21
+ const envPath = join(__dirname, '.env');
22
+ const envExamplePath = join(__dirname, '.env.example');
23
+
24
+ if (!existsSync(envPath) && existsSync(envExamplePath)) {
25
+ copyFileSync(envExamplePath, envPath);
26
+ console.log(chalk.cyan('📄 Auto-copied .env.example to .env'));
27
+ }
28
+
15
29
  // Color palette
16
30
  const neonCyan = chalk.hex('#00f2ff');
17
31
  const neonPurple = chalk.hex('#bc13fe');
@@ -27,6 +41,37 @@ function removeVietnameseTones(str) {
27
41
  .trim();
28
42
  }
29
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
+
30
75
  function checkEnv() {
31
76
  const required = [
32
77
  'TEBI_ENDPOINT',
@@ -127,6 +172,15 @@ async function main() {
127
172
  hint: chalk.yellow('Separate multiple links with COMMA (,)'),
128
173
  validate: (value) => {
129
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;
130
184
  },
131
185
  }),
132
186
  title: () =>
@@ -178,20 +232,40 @@ async function main() {
178
232
  try {
179
233
  s.message('🛰️ Fetching metadata...');
180
234
  const { execSync } = await import('child_process');
181
- // Use a lighter check first
182
- let fetchedTitle = execSync(
183
- `yt-dlp --print title --skip-download "${inputUrl}"`,
184
- { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }
185
- ).trim();
186
-
187
- // Fallback: If title is 'master' or empty, use the URL slug
188
- if (!fetchedTitle || fetchedTitle === 'master' || fetchedTitle === 'index') {
189
- const parts = inputUrl.split('/').filter(p => p);
190
- // Get second to last part if it looks like a slug
191
- 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
+ }
192
266
  }
193
267
 
194
- currentTitle = fetchedTitle;
268
+ currentTitle = fetchedTitle || extractTitleFromUrl(inputUrl);
195
269
  } catch (e) {
196
270
  currentTitle = `movie-${Date.now()}`;
197
271
  }