nexus-typo-mcp 1.3.0 → 1.3.2
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 +2 -2
- package/cli/nexus-typo.mjs +2 -9
- package/luat/QUY-TAC.md +1 -0
- package/mcp/huong-dan-may.md +2 -2
- package/mcp/huong-dan.md +1 -0
- package/mcp/may.mjs +2 -18
- package/package.json +1 -1
- package/src/dist/README.md +1 -1
- package/src/luat/kich-ban.mjs +2 -1
- package/src/luat/kiem.mjs +5 -0
- package/src/nguoi/luat-che.mjs +2 -2
- package/src/phude/job.mjs +1 -1
- package/src/xuat/ghep.mjs +0 -13
package/README.md
CHANGED
|
@@ -64,8 +64,8 @@ onnxruntime và tải mô hình vào `~/.nexus-typo/` (khoảng 300 MB).
|
|
|
64
64
|
## Gửi file đi
|
|
65
65
|
|
|
66
66
|
`render` với `share: true` ra thêm bản gửi `_share.mp4` (bitrate bằng video gốc) cạnh bản chính, cùng một lượt
|
|
67
|
-
ghép; `max_mb` khai trần dung lượng (có `share` thì áp vào bản gửi).
|
|
68
|
-
|
|
67
|
+
ghép; `max_mb` khai trần dung lượng (có `share` thì áp vào bản gửi).
|
|
68
|
+
Tải lên một lần (GitHub Release, kho nhận file qua một kết nối) chỉ hợp file dưới khoảng 500 MB.
|
|
69
69
|
|
|
70
70
|
Lệnh chạy sau khi xuất xong (tự soát ĐẠT), ghi ở `~/.nexus-typo/may.json`:
|
|
71
71
|
|
package/cli/nexus-typo.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { phuDe } from '../src/phude/buoc.mjs';
|
|
|
14
14
|
import { ve, xem } from '../src/ve/buoc.mjs';
|
|
15
15
|
import { sfx } from '../src/sfx/buoc.mjs';
|
|
16
16
|
import { nhac, dsBai } from '../src/nhac/buoc.mjs';
|
|
17
|
-
import { ghep, thamSoHinh,
|
|
17
|
+
import { ghep, thamSoHinh, lenhSauXuat } from '../src/xuat/ghep.mjs';
|
|
18
18
|
import { spawn } from 'node:child_process';
|
|
19
19
|
import { docThietBi, canDia, kiemTruocDung, tomTatMay } from '../src/thiet-bi.mjs';
|
|
20
20
|
import { timTrinhDuyet } from '../src/trinh-duyet.mjs';
|
|
@@ -234,15 +234,8 @@ const LENH = {
|
|
|
234
234
|
const tsGui = gui ? thamSoHinh(vao, { nvenc: false, nhe: true, maxMb }) : null;
|
|
235
235
|
const can = canDia({ W: vao.hinh.rong, H: vao.hinh.cao, T: vao.dai || vao.hinh.dai || 0, bitrateM: thamSoHinh(vao, { nvenc: false, nhe: !!tuy.nhe, maxMb: gui ? null : maxMb }).bitrateM + (tsGui?.bitrateM || 0) });
|
|
236
236
|
const k = kiemTruocDung(tb, can);
|
|
237
|
-
|
|
238
|
-
const nv = await coNvenc(may, vao.hinh.codec === 'h264' ? 'h264' : 'hevc');
|
|
239
|
-
const ts = thamSoHinh(vao, { nvenc: nv, nhe: !!tuy.nhe, maxMb: gui ? null : maxMb });
|
|
240
|
-
const duKien = { chinhMb: ts.duKienMb, guiMb: gui ? thamSoHinh(vao, { nvenc: nv, nhe: true, maxMb }).duKienMb : null };
|
|
241
|
-
const cb = canhBaoDungLuong(ts, { gui });
|
|
242
|
-
if (cb) k.canhBao.push(cb);
|
|
243
|
-
ghiJson(path.join(thuMucLamViec(video), 'may-doc.json'), { ...tb, can, duKien, ...k });
|
|
237
|
+
ghiJson(path.join(thuMucLamViec(video), 'may-doc.json'), { ...tb, can, ...k });
|
|
244
238
|
console.log('máy: ' + tomTatMay(tb));
|
|
245
|
-
console.log(`video ra dự kiến: khoảng ${duKien.chinhMb} MB${gui ? `, bản gửi khoảng ${duKien.guiMb} MB` : ''}`);
|
|
246
239
|
for (const x of k.canhBao) console.log(' cảnh báo: ' + x);
|
|
247
240
|
if (k.loi.length) throw new Error('chưa dựng được:\n ' + k.loi.join('\n '));
|
|
248
241
|
}
|
package/luat/QUY-TAC.md
CHANGED
|
@@ -20,6 +20,7 @@ Hình minh hoạ: `dung/minh-hoa-vung-dat-chu.jpg` (dựng bằng `dung/minh-hoa
|
|
|
20
20
|
|
|
21
21
|
- Bỏ hẳn dòng ghi chú nhỏ dưới đáy (vd "Vị trí dự án gần đúng, minh hoạ").
|
|
22
22
|
- Khối tiêu đề (kể cả dòng viết tay đè lên) phải nằm gọn trong vùng 2. Phụ đề luôn ở vùng 4, không đổi vị trí theo cảnh.
|
|
23
|
+
- Nhích khối tiêu đề theo cảnh (P55, Phương duyệt 03/11/2026): kịch bản ghi `"dichDoc"` ở cảnh, số điểm ảnh khung thiết kế 960 (âm là lên, dương là xuống, -60 đến 60), dời cả khối mà không đổi style, cỡ chữ. Không ghi thì khối đứng đúng chỗ style đặt. Dời xong vẫn phải gọn trong vùng 2: bước vẽ đo mực chữ, lấn quá 1 % khung là lỗi như mọi cảnh. Khối đa số style đã chiếm gần hết vùng 2 nên chỗ nhích ít. Đo trên bảng 8 style (03/11/2026, mực chữ giữa cảnh, khung 960), chỗ còn trước khi chạm mép vùng: lên M14 57 px, M8 22, M12 17, M9 14, M11 6, M3, M6, M7 2 đến 3; xuống M12 36, M14 26, M6 13, M3 11, M7 8, M11 6, M9 3, M8 1. Lấn thêm tới 1 % khung (khoảng 10 px) chỉ là cảnh báo. Cảnh có chữ sau người (mục 1c) thì phần tool tự nhích cộng thêm vào số kịch bản ghi.
|
|
23
24
|
|
|
24
25
|
## 1b. Nội dung chính đặt ở đâu (Phương duyệt)
|
|
25
26
|
- Nội dung chính của clip: dự án (ghim vị trí, tag), tuyến đường chính, quy hoạch chính, điểm đến chính và nhãn thời gian/khoảng cách của nó.
|
package/mcp/huong-dan-may.md
CHANGED
|
@@ -33,14 +33,14 @@ Người dùng đã nói rõ thì không hỏi lại.
|
|
|
33
33
|
3. Đọc `loi_md` và ảnh tổng quan. Tên riêng nghe sai (tên dự án, chủ đầu tư, đơn vị quản lý): `fix_transcript` với `sua: [["lời nghe được chữ thường không dấu câu", "Chữ Đúng"]]`, `duAn`. Không chắc thì hỏi người dùng.
|
|
34
34
|
4. Viết kịch bản theo `get_guide` topic `kich-ban`, gọi `validate_script` với `script`. `dat: false` thì đọc các dòng `LỖI` trong `log`, sửa, gọi lại. Đọc cả bảng cảnh in ra: `nhịp bậc 2` là cảnh ngắn quá nên bỏ dòng bổ sung (nới `dau`, `cuoi`); `thu` gần 25 % thì rút gọn `hook`; cụm phụ đề dưới 0,9 s không ghi "kẹt giữa 2 cảnh typo" là neo `dau` lệch giữa câu.
|
|
35
35
|
5. Người dùng muốn xem trước: `previews`, gửi ảnh cho người dùng. Sửa cảnh nào thì `validate_script` rồi `previews` với `scenes: [số cảnh]`.
|
|
36
|
-
6. `render` (thêm `music_mode`, `music_file` nếu người dùng chọn), rồi `job_status` tới khi xong.
|
|
36
|
+
6. `render` (thêm `music_mode`, `music_file` nếu người dùng chọn), rồi `job_status` tới khi xong.
|
|
37
37
|
7. Đọc `bao_cao` và ảnh 24 khung: tự soát **ĐẠT** mới giao. CHƯA ĐẠT thì sửa rồi dựng lại, hoặc báo người dùng lỗi gì. Báo người dùng đường dẫn `video_ra` (thư mục `ket-qua` cạnh video), số cảnh typo, bộ style, chỗ nên xem kỹ. Dòng "lấn mép nhẹ" dưới 1 % khung trong báo cáo là dấu, bóng chữ chạm mép vùng, không cần sửa.
|
|
38
38
|
|
|
39
39
|
Sửa sau khi người dùng góp ý: sửa kịch bản, `validate_script`, `render` lại (không chép lời lại). Muốn file nhẹ gửi Zalo: `render` với `light: true`.
|
|
40
40
|
|
|
41
41
|
## Gửi file đi
|
|
42
42
|
|
|
43
|
-
- Bản chính giữ chất lượng cao nên nặng (video 3 phút khổ 2K có thể hơn 1 GB).
|
|
43
|
+
- Bản chính giữ chất lượng cao nên nặng (video 3 phút khổ 2K có thể hơn 1 GB). Người dùng nói cần gửi file qua mạng thì `render` với `share: true`: ra thêm `_share.mp4` (bitrate bằng video gốc) cùng lượt, bản chính không đổi, `video_share` là đường file gửi. Tốn thêm một lượt mã hoá chạy song song (máy yếu thêm vài phút).
|
|
44
44
|
- Kênh nhận có giới hạn thì thêm `max_mb` (vd 450 cho kênh nhận tới 500 MB): có `share` thì trần áp vào bản gửi; không có `share` thì hạ bitrate bản chính, kém nét hơn, nên ưu tiên `share`.
|
|
45
45
|
- Tải lên một lần (GitHub Release, kho nhận file qua một kết nối) chỉ hợp file dưới khoảng 500 MB; kết nối qua proxy hay đứt sau vài phút. Drive trên web, ứng dụng Drive trên máy tải lại được khi đứt.
|
|
46
46
|
- Máy có lệnh gửi riêng (vd rclone lên Drive) thì người dùng tự ghi `post_export_cmd` vào `~/.nexus-typo/may.json`; tool chạy lệnh đó sau khi tự soát ĐẠT, kết quả ở `post_export` (`ma` 0 là xong). Lệnh này chỉ đọc từ file cấu hình, không truyền qua công cụ, đừng tự ghi file đó thay người dùng.
|
package/mcp/huong-dan.md
CHANGED
|
@@ -51,6 +51,7 @@ Nexus Typo thêm tiêu đề chữ (typo, theo bộ style đã duyệt) và ph
|
|
|
51
51
|
- `nen`: `"sang"` khi nền sau tiêu đề sáng (trời trắng, bản đồ sáng), chữ có viền, bóng đậm; ghi ở gốc hoặc ở cảnh.
|
|
52
52
|
- `loe: true` (gốc hoặc cảnh): vệt sáng lướt chữ màu chuyển một lần, cho cảnh muốn gây chú ý. Mặc định tắt.
|
|
53
53
|
- `sauNguoi: true` (ở cảnh; ở gốc là cả clip, cảnh ghi `false` thì tắt riêng): chữ to nằm sau người, người đi trước chữ, phụ đề vẫn nằm trên người. Chỉ bật khi người cao tới chỗ chữ to (selfie, người đứng sát máy, quay thấp). Người che quá 25 % một dòng thì tool tự nhích khối chữ lên, vẫn che thì cảnh đó chữ ra trước người và báo. Tách người tốn thời gian (trang web: khoảng 1 s mỗi khung với card đồ hoạ, 1 đến 5 s khi chỉ có CPU tuỳ số nhân; lần đầu tải mô hình 176 MB), chỉ bật ở cảnh cần.
|
|
54
|
+
- `dichDoc` (ở cảnh, số nguyên -60 đến 60): nhích cả khối chữ to lên (số âm) hoặc xuống (số dương), tính bằng điểm ảnh của khung 540x960, không đổi style, cỡ chữ. Chỉ dùng khi người dùng muốn chữ xích lên, xuống chút (vd né mặt người, né chữ có sẵn trong video). Khối chữ đa số style đã gần kín vùng chữ to nên chỉ nhích được ít: lên nhiều nhất M14 (khoảng 57), M8 (22), M12 (17); xuống nhiều nhất M12 (36), M14 (26); các style khác dưới 15 mỗi chiều. Dời quá vùng chữ to thì bước dựng báo lỗi "chữ lệch vùng": giảm số rồi chạy lại.
|
|
54
55
|
- `tatPhuDe: [[0, 9]]` (giây video): đoạn không cần phụ đề (đồ hoạ tự có chữ). `chuSan: [[38, 47]]`: đoạn video có chữ sẵn ở vùng tiêu đề, tool cảnh báo cảnh typo chồng lên.
|
|
55
56
|
- `toi`: độ đậm nhất lớp tối nền trên, dưới (mặc định 0,15; 0 tắt; tối đa 0,6). Cảnh nền sáng thấy vệt tối ở đáy khung thì hạ, vd `0.08`.
|
|
56
57
|
- `vienPhuDe`: màu viền phụ đề `#rrggbb` theo màu chủ đạo của video (mặc định xanh lá đậm).
|
package/mcp/may.mjs
CHANGED
|
@@ -247,24 +247,8 @@ async function render(a) {
|
|
|
247
247
|
if (!(+a.max_mb > 0)) throw new Error('max_mb là số MB lớn hơn 0');
|
|
248
248
|
t.push('--max-mb', String(+a.max_mb));
|
|
249
249
|
}
|
|
250
|
-
const duKien = await duKienRender(tm, a);
|
|
251
250
|
const viec = taoViec({ loai: 'render', video, buoc: [t], tham: { light: !!a.light, share: !!a.share } });
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/* P66: dung lượng dự kiến theo bộ mã hoá máy này dùng; vượt ngưỡng mà chưa bật bản gửi, chưa khai trần thì gợi ý */
|
|
256
|
-
async function duKienRender(tm, a) {
|
|
257
|
-
try {
|
|
258
|
-
const { thamSoHinh, coNvenc, canhBaoDungLuong } = await import('../src/xuat/ghep.mjs');
|
|
259
|
-
const vao = JSON.parse(fs.readFileSync(path.join(tm, 'vao.json'), 'utf8'));
|
|
260
|
-
const nvenc = await coNvenc(docMay(), vao.hinh.codec === 'h264' ? 'h264' : 'hevc');
|
|
261
|
-
const maxMb = a.max_mb != null ? +a.max_mb : null;
|
|
262
|
-
const chinh = thamSoHinh(vao, { nvenc, nhe: !!a.light, maxMb: a.share ? null : maxMb });
|
|
263
|
-
const gui = a.share ? thamSoHinh(vao, { nvenc, nhe: true, maxMb }) : null;
|
|
264
|
-
const cb = canhBaoDungLuong(chinh, { gui: !!a.share });
|
|
265
|
-
return { du_kien_mb: chinh.duKienMb, ...(gui ? { du_kien_share_mb: gui.duKienMb } : {}),
|
|
266
|
-
...(cb ? { goi_y: cb + '. Hỏi người dùng có cần gửi file qua mạng không; cần thì huỷ việc này (job_status cancel) rồi render lại với share: true hoặc max_mb.' } : {}) };
|
|
267
|
-
} catch { return {}; }
|
|
251
|
+
return traViec(viec, Math.min(cho(a.wait_seconds), 5));
|
|
268
252
|
}
|
|
269
253
|
|
|
270
254
|
/* phần trả khi việc xong, dựng lại từ file việc (MCP mở lại cũng có) */
|
|
@@ -383,7 +367,7 @@ const TOOLS = [
|
|
|
383
367
|
music_start: { type: 'number', description: 'giây bắt đầu lấy trong bài nhạc' },
|
|
384
368
|
voice_file: { type: 'string', description: 'rãnh giọng đọc riêng (xuất từ CapCut) khi đổi nhạc' },
|
|
385
369
|
light: { type: 'boolean', description: 'bản nhẹ (bitrate bằng video vào) để gửi Zalo' },
|
|
386
|
-
share: { type: 'boolean', description: 'ra thêm bản gửi _share.mp4 (bitrate bằng video vào, có max_mb thì theo trần) cùng lượt, bản chính giữ nguyên chất lượng; bật khi người dùng cần gửi file qua mạng
|
|
370
|
+
share: { type: 'boolean', description: 'ra thêm bản gửi _share.mp4 (bitrate bằng video vào, có max_mb thì theo trần) cùng lượt, bản chính giữ nguyên chất lượng; bật khi người dùng cần gửi file qua mạng' },
|
|
387
371
|
max_mb: { type: 'number', description: 'trần dung lượng MB: có share thì áp vào bản gửi, không thì hạ bitrate bản chính (kém nét hơn); vd 450 để qua kênh nhận tới 500 MB' },
|
|
388
372
|
wait_seconds: CHO } } },
|
|
389
373
|
{ name: 'job_status', handler: jobStatus,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-typo-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "MCP server cho agent AI thêm tiêu đề chữ, phụ đề, tiếng động vào video bất động sản dọc 9:16 và xuất MP4 ngay trên máy (Nexus Typo, typo.nexusone.vn)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
package/src/dist/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Sau khi nạp: `TY_STYLES` (style đã duyệt), `TY_NAMES`; nếu app có mản
|
|
|
25
25
|
|
|
26
26
|
Tự bốc style (`typoPlan`) chạy cùng thuật toán với bộ bốc Node của Nexus Typo (thử mọi bộ ≤ 3 style, xếp cả clip một lượt): cùng kịch bản ra cùng style ở trình duyệt và ở lệnh `kiem`; style có `motLan` (M7, M8, M11) tối đa 1 lần mỗi clip.
|
|
27
27
|
|
|
28
|
-
Kịch bản (giữ tên trường của Map, K8): `script.titleColor` (`"vang"` | `"cyan"` | `"kem-dao"`, danh sách ở `HO_MAU` của `styles.mjs`), `script.nen` (`"sang"`: nền sáng), `script.titleBg` hoặc `hud.tag` (chữ nền M8); mỗi caption: `lead`, `hook`, `extra`, `loai` (loại chi tiết: `thoi-gian`, `khoang-cach`, `quy-mo`, `gia`, `moc`, `ten`, `khac`; không ghi thì đoán theo `hook`), `nhom` (cảnh cùng loại khác ý, vd `"thue"`, `"ban"`), `titleStyle` (bỏ trống thì bốc theo luật), `titleBg`, `chuyenDong` (bỏ trống thì `truot-trai`), `loe` (vệt sáng riêng cảnh, ghi đè `script.loe`), `lead2` (dòng dẫn thứ hai, M16), `doiLuc` (giây đổi dẫn tính từ đầu caption, M16), `mauNhan` (dải màu dòng nhấn M16), `nen` (`"sang"` riêng caption, đè `script.nen`).
|
|
28
|
+
Kịch bản (giữ tên trường của Map, K8): `script.titleColor` (`"vang"` | `"cyan"` | `"kem-dao"`, danh sách ở `HO_MAU` của `styles.mjs`), `script.nen` (`"sang"`: nền sáng), `script.titleBg` hoặc `hud.tag` (chữ nền M8); mỗi caption: `lead`, `hook`, `extra`, `loai` (loại chi tiết: `thoi-gian`, `khoang-cach`, `quy-mo`, `gia`, `moc`, `ten`, `khac`; không ghi thì đoán theo `hook`), `nhom` (cảnh cùng loại khác ý, vd `"thue"`, `"ban"`), `titleStyle` (bỏ trống thì bốc theo luật), `titleBg`, `chuyenDong` (bỏ trống thì `truot-trai`), `loe` (vệt sáng riêng cảnh, ghi đè `script.loe`), `lead2` (dòng dẫn thứ hai, M16), `doiLuc` (giây đổi dẫn tính từ đầu caption, M16), `mauNhan` (dải màu dòng nhấn M16), `nen` (`"sang"` riêng caption, đè `script.nen`), `dichDoc` (P55: nhích cả khối tiêu đề, px khung 960, âm là lên; bỏ trống thì chỗ style đặt; nằm ngoài vùng tiêu đề 8 đến 30 % thì phía nạp tự kiểm).
|
|
29
29
|
|
|
30
30
|
Đổi dẫn (M16, Phương duyệt 29/10/2026): caption có `lead2` thì bộ bốc chỉ lấy style có đổi dẫn (M16), bộ bốc không đưa M16 vào caption không có `lead2`. Từ gói 0.1.14 (P45) ghi tay `titleStyle: "M16"` cho caption một dòng dẫn cũng được: `dungDuoc('M16', ...)` luôn `true`, dòng dẫn đứng yên. App dò style đổi dẫn bằng `DOI_DAN` (hoặc `STYLES[id].doiDan`, `styles.json` `doiDan`) của `styles.mjs`, không suy từ `dungDuoc` nữa (cách suy cũ ra rỗng từ 0.1.14). Mã dải màu dòng nhấn có sẵn ở `MAU_NHAN` (`STYLES.M16.mauNhan`, `styles.json` `mauNhan`), khỏi chép từ `typo.js`. Khẩu vị, rung ngẫu nhiên tính trên bộ bỏ M16 khi kịch bản không có `lead2`: kịch bản cũ ra cùng style như gói 0.1.6. Dòng dẫn `lead` đổi thành `lead2` lúc `doiLuc`, dòng nhấn đứng yên; `doiLuc` bỏ trống thì không đổi (lệnh `kiem` của Nexus Typo tự lấy theo lời, app phải ghi). M16 bật vệt sáng sẵn lúc `doiLuc` + 0,5 s, `loe: false` để tắt.
|
|
31
31
|
|
package/src/luat/kich-ban.mjs
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
// "doiLuc": "?" (giây đổi dẫn tính từ đầu cảnh; trống: lấy theo giờ chữ đầu của lead2 trong lời),
|
|
13
13
|
// "mauNhan": "?" (dải màu dòng nhấn M16, mã trong luat/styles/M16.md; trống: bước phu-de tự chọn theo nền),
|
|
14
14
|
// "nen": "?" ("sang" | "toi" riêng cảnh này, đè nen cả clip),
|
|
15
|
-
// "sauNguoi": "?" (true: chữ tiêu đề nằm sau người, P22; chỉ bật khi người cao tới vùng tiêu đề, bước ve tách người ~0,4 s mỗi khung)
|
|
15
|
+
// "sauNguoi": "?" (true: chữ tiêu đề nằm sau người, P22; chỉ bật khi người cao tới vùng tiêu đề, bước ve tách người ~0,4 s mỗi khung),
|
|
16
|
+
// "dichDoc": "?" (P55: nhích cả khối tiêu đề, điểm ảnh khung 960, âm là lên, -60 đến 60; trống: chỗ style đặt) } ],
|
|
16
17
|
// "loe": "?" (vệt sáng lướt chữ màu chuyển cả clip, QUY-TAC mục 5; trống: tắt),
|
|
17
18
|
// "sauNguoi": "?" (true: chữ sau người mọi cảnh, P22; cảnh ghi "sauNguoi": false thì tắt riêng cảnh đó),
|
|
18
19
|
// "suKien": [ { "t": 97.05, "loai": "cham", "moTa": "tay chạm màn hình" } ],
|
package/src/luat/kiem.mjs
CHANGED
|
@@ -12,6 +12,8 @@ const NGUONG_THU = 0.25;
|
|
|
12
12
|
// đổi dẫn: style có cờ doiDan (M16); doiLuc sau lúc dòng dẫn thứ nhất vào xong, còn ít nhất 1 s trước khi khối ra
|
|
13
13
|
const DOI_DAN = DS_TAT_CA.filter((s) => STYLES[s].doiDan);
|
|
14
14
|
const DOI_SOM = 0.6, DOI_GIU = 1.0;
|
|
15
|
+
/** Nhích khối tiêu đề (P55): điểm ảnh khung 960, âm là lên. Rộng hơn vùng 2 cho mọi style; bước ve đo mực chữ mới chặn thật. */
|
|
16
|
+
export const DICH_DOC = [-60, 60];
|
|
15
17
|
// dải màu dòng nhấn (style có bangMau: M16): mauNhan ghi ở cảnh là một mã trong bảng; trống thì bước phu-de tự chọn theo nền
|
|
16
18
|
const MAU_NHAN = [...new Set(DS_TAT_CA.flatMap((s) => Object.keys(STYLES[s].bangMau || {})))];
|
|
17
19
|
|
|
@@ -58,6 +60,8 @@ export function kiemKichBan(kb, loi, { T = Infinity, thu = null } = {}) {
|
|
|
58
60
|
const n = c.i + 1;
|
|
59
61
|
if (c.loiNeo) { L(n, c.loiNeo); continue; }
|
|
60
62
|
if (c.nen != null && !NEN.includes(c.nen)) L(n, `nen "${c.nen}" không hợp lệ: chỉ ${NEN.map((x) => `"${x}"`).join(', ')}`);
|
|
63
|
+
if (c.dichDoc != null && !(Number.isInteger(c.dichDoc) && c.dichDoc >= DICH_DOC[0] && c.dichDoc <= DICH_DOC[1]))
|
|
64
|
+
L(n, `dichDoc ${JSON.stringify(c.dichDoc)} phải là số nguyên ${DICH_DOC[0]} đến ${DICH_DOC[1]} (điểm ảnh khung 960, âm là lên; QUY-TAC mục 1)`);
|
|
61
65
|
if (c.sauNguoi != null && typeof c.sauNguoi !== 'boolean') L(n, 'sauNguoi chỉ nhận true hoặc false (P22: chữ nằm sau người, chỉ bật ở cảnh người cao tới vùng tiêu đề)');
|
|
62
66
|
if (!c.loai) B(n, `chưa ghi loai (${LOAI.join(', ')}): bốc style và tính chuỗi kém chính xác`);
|
|
63
67
|
else if (!LOAI.includes(c.loai)) L(n, `loai "${c.loai}" không hợp lệ (${LOAI.join(', ')})`);
|
|
@@ -121,6 +125,7 @@ export function kiemKichBan(kb, loi, { T = Infinity, thu = null } = {}) {
|
|
|
121
125
|
...(c.loe != null ? { loe: c.loe } : {}),
|
|
122
126
|
...(c.nen ? { nen: c.nen } : {}),
|
|
123
127
|
...((c.sauNguoi ?? kb.sauNguoi) === true ? { sauNguoi: true } : {}), // P22: cảnh ghi riêng đè cả clip
|
|
128
|
+
...(c.dichDoc ? { dichDoc: c.dichDoc } : {}),
|
|
124
129
|
...(c.mauNhan != null && STYLES[st].bangMau ? { mauNhan: c.mauNhan } : {}),
|
|
125
130
|
style: st, lyDo: plan.lyDo[k], thu: thu && thu[k] ? thu[k][st] : null, soDong: n, nhip: nh, loi: c.loi, tu: [c.tuDau, c.tuCuoi] };
|
|
126
131
|
});
|
package/src/nguoi/luat-che.mjs
CHANGED
|
@@ -85,13 +85,13 @@ export function quyetCanh(x, khung, w, h) {
|
|
|
85
85
|
return { i: x.i, so: x.i + 1, hook: x.c.hook, dich: kq.dich, che: pt(kq.che), cheGoc: pt(goc.che), dong: goc.chu, tran, tat: kq.tat };
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
/** Job sau luật 1c: cảnh nhích có dichDoc, cảnh tắt bỏ sauNguoi. Hàm thuần. */
|
|
88
|
+
/** Job sau luật 1c: cảnh nhích có dichDoc (cộng vào số kịch bản ghi, P55: đo trên khối đã dời), cảnh tắt bỏ sauNguoi. Hàm thuần. */
|
|
89
89
|
export function jobNeNguoi(job, ne) {
|
|
90
90
|
if (!ne) return job;
|
|
91
91
|
const tieuDe = job.tieuDe.map((c) => ({ ...c }));
|
|
92
92
|
for (const x of ne.canh) {
|
|
93
93
|
if (x.tat) delete tieuDe[x.i].sauNguoi;
|
|
94
|
-
else if (x.dich) tieuDe[x.i].dichDoc = x.dich;
|
|
94
|
+
else if (x.dich) tieuDe[x.i].dichDoc = (tieuDe[x.i].dichDoc || 0) + x.dich;
|
|
95
95
|
}
|
|
96
96
|
return { ...job, tieuDe };
|
|
97
97
|
}
|
package/src/phude/job.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const VIEN_MAC_DINH = '#15803d';
|
|
|
6
6
|
export function chuJob(giai, phuDe) {
|
|
7
7
|
return {
|
|
8
8
|
titleColor: giai.mau, titleBg: giai.chuNen || '', toi: giai.toi ?? TOI_NEN, vien: giai.vienPhuDe || VIEN_MAC_DINH, ...(giai.loe ? { loe: giai.loe } : {}),
|
|
9
|
-
tieuDe: giai.canh.map((c) => ({ start: c.start, end: c.end, lead: c.lead, ...(c.lead2 ? { lead2: c.lead2, doiLuc: c.doiLuc } : {}), ...(c.mauNhan ? { mauNhan: c.mauNhan } : {}), hook: c.hook, extra: c.extra, titleStyle: c.style, ...(c.chuNen ? { titleBg: c.chuNen } : {}), ...(c.chuyenDong ? { chuyenDong: c.chuyenDong } : {}), ...(c.loe != null ? { loe: c.loe } : {}), ...(c.sauNguoi ? { sauNguoi: true } : {}) })),
|
|
9
|
+
tieuDe: giai.canh.map((c) => ({ start: c.start, end: c.end, lead: c.lead, ...(c.lead2 ? { lead2: c.lead2, doiLuc: c.doiLuc } : {}), ...(c.mauNhan ? { mauNhan: c.mauNhan } : {}), hook: c.hook, extra: c.extra, titleStyle: c.style, ...(c.chuNen ? { titleBg: c.chuNen } : {}), ...(c.chuyenDong ? { chuyenDong: c.chuyenDong } : {}), ...(c.loe != null ? { loe: c.loe } : {}), ...(c.sauNguoi ? { sauNguoi: true } : {}), ...(c.dichDoc ? { dichDoc: c.dichDoc } : {}) })),
|
|
10
10
|
phuDe: phuDe.map((c) => ({ start: c.start, end: c.end, sub: c.sub })),
|
|
11
11
|
};
|
|
12
12
|
}
|
package/src/xuat/ghep.mjs
CHANGED
|
@@ -40,8 +40,6 @@ export function khungMoc(h) {
|
|
|
40
40
|
return Math.max(1, Math.round(giay * (h.fps || 30)));
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** Dung lượng dự kiến quá mức này thì khó gửi qua các kênh phổ biến (Drive tải một lần, kho nhận file; báo cáo Kiki). */
|
|
44
|
-
export const NGUONG_GUI_MB = 800;
|
|
45
43
|
/** Phần dành cho vỏ mp4 và độ lệch bitrate của bộ mã hoá khi kẹp theo trần (MB tính 10^6 byte, nhỏ hơn cả MiB). */
|
|
46
44
|
const DU_TRAN = 0.94;
|
|
47
45
|
|
|
@@ -93,13 +91,6 @@ export function thamSoTieng(vao) {
|
|
|
93
91
|
return ['-c:a', 'aac', '-b:a', `${kbTieng(vao)}k`, '-ar', String(t.tanSo), '-ac', String(t.soKenh)];
|
|
94
92
|
}
|
|
95
93
|
|
|
96
|
-
/** Cảnh báo đầu lượt (P66): dung lượng dự kiến vượt ngưỡng mà chưa bật bản gửi, chưa khai trần. Hàm thuần. */
|
|
97
|
-
export function canhBaoDungLuong(hinh, { gui = false } = {}) {
|
|
98
|
-
if (gui || hinh.maxMb != null || hinh.duKienMb <= NGUONG_GUI_MB) return null;
|
|
99
|
-
return `video ra dự kiến khoảng ${hinh.duKienMb} MB, khó gửi qua các kênh phổ biến (tải lên một lần dễ đứt, kho nhận file hay chặn file lớn): ` +
|
|
100
|
-
'bật bản gửi (--gui, MCP share) hoặc khai trần dung lượng (--max-mb, MCP max_mb) trước khi dựng';
|
|
101
|
-
}
|
|
102
|
-
|
|
103
94
|
/**
|
|
104
95
|
* Bộ lọc ghép. Không có mặt nạ người: video + lớp chữ như cũ. Có (P22, input 3 = nguoi.mkv): người (video gốc, alpha là mặt nạ
|
|
105
96
|
* phóng về cỡ video) đặt trên cùng, nên che tiêu đề; mặt nạ chỉ phủ nửa trên khung nên phụ đề vẫn nằm trên.
|
|
@@ -197,10 +188,6 @@ export async function ghep(may, thuMuc, { nhe = false, gui = false, maxMb = null
|
|
|
197
188
|
fs.rmSync(path.join(thuMuc, 'sau-xuat.json'), { force: true });
|
|
198
189
|
|
|
199
190
|
const T = vao.dai || vao.hinh.dai || 0;
|
|
200
|
-
inRa(`dung lượng dự kiến: bản chính khoảng ${hinh.duKienMb} MB${hinh.theoTran ? ` (theo trần ${maxMb} MB)` : ''}` +
|
|
201
|
-
(hinhGui ? `, bản gửi khoảng ${hinhGui.duKienMb} MB (${hinhGui.bitrateM} Mbit/s${hinhGui.theoTran ? `, theo trần ${maxMb} MB` : ''})` : ''));
|
|
202
|
-
const cb = canhBaoDungLuong(hinh, { gui });
|
|
203
|
-
if (cb) inRa('cảnh báo: ' + cb);
|
|
204
191
|
const hTran = [hinh, hinhGui].find((h) => h?.theoTran), vaoM = (vao.hinh.bitrate || vao.bitrate || 0) / 1e6;
|
|
205
192
|
if (hTran && hTran.bitrateM < vaoM) inRa(`cảnh báo: trần ${maxMb} MB đưa bitrate hình xuống ${hTran.bitrateM} Mbit/s, thấp hơn video gốc ${vaoM.toFixed(1)} Mbit/s nên ${hTran === hinhGui ? 'bản gửi' : 'video ra'} kém nét hơn gốc`);
|
|
206
193
|
const kiem = danhGiaTruocGhep({
|