Spaces:
Running
Running
Commit History
24 days ago
-
fix(dub): gen voice từ text báo lỗi thật thay vì trả audio câm
Claude +1: Claude Opus 5.5Unverified signatureEngine TTS hỏng (VieNeu nạp lỗi, Edge không gọi được) thì run_tts trả clip lặng 0.4s và job vẫn "xong" -> người dùng chỉ thấy audio 0 giây, không biết vì sao. - /api/dub/tts: clip ra lặng -> báo lỗi kèm nguyên nhân của từng engine. - Giao diện: job không có kết quả thì hiện thông báo lỗi. - synth_line: lỗi của Edge (khi đứng thay engine nạp hỏng) ghi dưới tên "edge", không đè mất lỗi nạp thật của engine gốc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwiJZLUb773B8NwksxfTU9bb75713
-
fix(infra): chặn 502 từ gốc — uvicorn đóng kết nối rảnh quá sớm
Administrator +1: Claude Opus 5Đo thẳng lên Space: 5/20 lời gọi API trả 502. Thân của 502 là trang HTML của huggingface.co (3044 byte) và KHÔNG có header `server: uvicorn` -> request chưa hề tới app. Không phải app lỗi: router của HF không nối được tới uvicorn. Lý do: router giữ sẵn một nắm kết nối tới app rồi dùng lại, trong khi uvicorn mặc định đóng kết nối rảnh sau 5 GIÂY. Router gửi request vào kết nối vừa bị đóng -> 502. Đây chính là nguồn đẻ ra "Unexpected token '<'" mà người dùng gặp: các bản vá trước chỉ dịch nó thành câu tiếng Việt đọc được, gốc rễ nằm ở đây. - Dockerfile: uvicorn --timeout-keep-alive 120, cao hơn hẳn thời gian rảnh của router, để bên đóng kết nối là router và đóng đàng hoàng từ phía nó. - api.js: lời gọi CHỈ ĐỌC (GET/HEAD) gặp trục trặc tạm thời thì tự thử lại 2 nhịp (0.6s, 1.2s). Lệnh GHI KHÔNG tự thử lại — bấm một lần mà chạy hai job thì tai hại hơn là báo lỗi để người dùng bấm lại. Kiểm: chạy thử api.js — GET vượt qua 2 nhịp 502 rồi trả dữ liệu, POST vẫn báo lỗi sau đúng 1 lần …a04fadb -
fix(api): 7 màn còn lại dùng chung lớp đọc JSON an toàn
Administrator +1: Claude Opus 5Nốt các trang gọi fetch trần rồi r.json(): Listing ảnh, Listing free, Flux2, Tạo video, Lịch sử cá nhân, Đăng nhập, Quản trị người dùng (+ tab Thời lượng). Space trả trang HTML lúc bận là các trang này vẫn văng "Unexpected token '<'". - api.js mở ra `request/jget/jpost` cho các trang dùng chung; AbortError đi xuyên qua nguyên vẹn để Listing ảnh còn nhận ra "quá thời gian chờ 2 phút". - Flux2: vòng hỏi trạng thái chịu đựng 90s mất liên lạc thay vì bỏ mẻ ảnh đang vẽ dở ngay lần hỏng đầu — cùng cách đã làm cho pollJob. - Đăng nhập: tách "chưa được cấp quyền" (máy chủ trả lời rành mạch) khỏi "máy chủ trục trặc". Trước đây Space bận là báo oan người dùng chưa có quyền rồi gửi luôn yêu cầu cấp quyền cho người vốn đã có. - Quản trị: chỉ 403 mới là sai mật khẩu; lỗi khác không còn đá người quản trị ra khỏi phiên đang mở. - Lịch sử cá nhân: bỏ bản jpost chép tay, và xoá/liên kết Drive lỗi thì báo thay vì nuốt thành unhandled rejection. - Hai nút tải .zip: 200-mà-HTML không còn lưu …d94104e -
fix(api): trang HTML của cổng vào Space không còn giết job đang chạy dở
Administrator +1: Claude Opus 5Lỗi người dùng gặp: "Unexpected token '<', "<!DOCTYPE "... is not valid JSON" văng ra giữa lúc dịch đang chạy 60%. Gốc: api.job() (và các GET khác) gọi thẳng r.json() không xem r.ok. Khi Space bận / vừa khởi động lại / cổng vào HF hết giờ chờ, nó trả trang HTML 502-504; JSON.parse ném ra câu tiếng Anh khó hiểu, pollJob reject NGAY từ lần hỏng đầu tiên — trong khi job vẫn đang chạy ngon lành trên máy chủ. Sửa: - Mọi lời gọi đi qua một chỗ đọc body: nhận ra HTML/không-JSON, trả câu tiếng Việt đọc được, gắn cờ `transient` cho lỗi tạm thời (5xx, 429, 408, đứt mạng). - pollJob thử lại tối đa 90s khi mất liên lạc thay vì bỏ job; 404 (máy chủ khởi động lại, mất tiến trình trong RAM) thì báo rõ và dừng luôn. - Upload qua XHR cũng không còn ném nguyên khối HTML vào alert, và 200-mà-HTML không còn âm thầm resolve {}. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>5b27e49
-
fix(cut): cắt 2+ video không còn 500; tách khúc tự động nhanh gấp ~3 lần
Lỗi: thả nhiều video một lượt lúc chưa có video nào -> `cur` là biến chụp lúc render nên mọi lần upload đều gửi session rỗng -> backend tạo mỗi video một phiên riêng. Lúc cắt, FE chỉ gửi phiên của video đầu nên video thứ 2 trở đi không tìm thấy file, ffprobe chết, cả request 500 "Internal Server Error". Một video thì không bao giờ dính. Sửa lỗi: - FE giữ sid chạy theo vòng lặp upload -> mọi video vào cùng một phiên; và gửi kèm session của TỪNG video khi cắt để các phiên cũ đã lỡ tách vẫn chạy. - resolve_src() chặn thoát thư mục phiên; probe() báo lý do đọc được thay vì CalledProcessError trống trơn. - execute-batch đưa việc nặng vào job: 1 video hỏng chỉ hỏng phần của nó, hỏng hết thì job báo lỗi thật. - Khúc end <= start trước đây bị ffmpeg nuốt im lặng (-frames:v âm = không giới hạn) và xuất ra clip dài tới hết video -> nay chặn. - alert hiện `detail` thay vì nguyên cục JSON. Tăng tốc (đo thật, video QC 1080x1920 30s): - Tách khúc tự động 11.9s -> 3.9s/video. PySceneDetect …07d3e14
-
fix(báo cáo): ô "Số tài khoản được cấp" đếm nhầm bảng; buộc tải video qua relay trên Space
Ô "Số tài khoản được cấp" hiện 4 trong khi thực tế đã cấp hơn 80. Nó đang đếm bảng mã U00N, mà mã chỉ sinh khi người đó ĐĂNG NHẬP lần đầu hoặc khi quản trị tự tay thêm — duyệt từ hàng chờ thì không cấp mã. Đổi sang đếm bảng tài khoản đã duyệt, đúng bằng số dòng tab "Quản lý người dùng". Kèm nhóm sửa relay/HF đang làm dở: - Trên Space, tikwm/iesdouyin/Piped BẮT BUỘC đi qua TIKTOK_RELAY_URL; thiếu relay thì nêu lỗi rõ ràng thay vì gọi thẳng từ IP datacenter (hay bị chặn). Dev local vẫn gọi trực tiếp như cũ. - Worker relay đặt Referer theo đúng nguồn: CDN Douyin chống hotlink, sai Referer là 403. - CI loại thư mục deploy/ khỏi bản đẩy lên Space (commit tạm trong runner, main trên GitHub giữ nguyên). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>9dbe21e
-
feat(báo cáo): gộp bộ lọc ngày thành 1 ô, thêm 2 ô tool đóng góp nhiều/ít nhất
Bộ lọc ngày: bỏ cặp "Từ ngày / Đến ngày" luôn hiện, thay bằng MỘT ô chọn có sẵn Hôm nay / Hôm qua / 7-30-90 ngày qua / Tuần này / Tuần trước / Tháng này / Tháng trước. Quản trị hầu hết chỉ muốn "7 ngày qua" chứ không muốn gõ hai cái ngày; chọn "Tùy chọn khoảng ngày…" mới mở lịch ra cho ai cần mốc chính xác. Tuần tính từ THỨ HAI theo lịch VN. Không có mốc tương lai (ngày mai, 7 ngày tới…) vì đây là báo cáo đọc log đã ghi, khoảng tương lai luôn rỗng. Nhóm "Năng suất" thêm hai ô "Tool đóng góp nhiều nhất / ít nhất" — cùng bảng xếp hạng với card 🏆 bên dưới, kéo lên đầu để nhìn phát thấy ngay. Giá trị của hai ô này là TÊN TOOL nên dùng cỡ chữ nhỏ hơn ô số (.val.text), kẻo tên dài như "Chèn chữ vào video" tràn khỏi thẻ. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>cc970b0 -
fix(báo cáo): ô Hài lòng hiện "3.3/5" — thang điểm cỡ nhỏ cạnh con số
Bỏ "/5" khỏi dòng phụ, đưa lên ngay cạnh điểm dưới dạng chữ nhỏ màu mờ: con số đứng một mình thì không biết là thang mấy, mà để dưới dòng phụ thì đọc rời rạc. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>146a9ed -
fix(báo cáo): ô Hài lòng bỏ chữ "/5" ở dòng phụ
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>09a5805 -
fix(báo cáo): commit trước lỡ đẩy bản JSX nháp, lệch hẳn với CSS
Commit 4ffa05a đẩy nhầm bản dashboard NHÁP: lệnh ghi file bản cuối bị hỏng (chuỗi lệnh quá dài, shell không chạy tới heredoc) nên JSX vẫn dùng lớp .rep-hero/.rep-seg/.kpi trong khi theme.css đã thay bằng .grp/.metric/ .rep-filters. Class không khớp -> trang báo cáo mất sạch định dạng, chữ tràn ra như văn bản thô. Nay đưa đúng bản cuối vào, và bỏ component Kpi đã chết trong UsageCharts (dashboard tự dựng ô Metric riêng). Bảng "Chi tiết theo tool": thêm cột "Việc xong" và chú thích cho ô rỗng. Tool mở ra rồi bỏ dở thì các cột thời gian/hài lòng KHÔNG có số thật để in — trước chỉ hiện trơ dấu "—", nhìn cứ như báo cáo mất dữ liệu. Nay rê chuột vào dấu — là biết vì sao trống, và cột "Việc xong" nói thẳng chưa việc nào chạy tới đích. Bỏ luôn tiêu đề "Quản trị thời lượng" trong component: trang AdminUsers đã in sẵn tiêu đề đổi theo tab, để cả hai thì lặp hai lần trên màn hình. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>16d58d0 -
feat(báo cáo): dựng lại tab thời lượng theo mẫu, CSV đọc được bằng tiếng Việt
Bảng nhân sự: cột "Xong" chỉ in SỐ việc xong, bỏ phần trăm đi kèm — cột này đứng cạnh cột "Số task" nên tỷ lệ tự đọc ra được, in thêm % chỉ làm ô chật. Thêm cột Team/BU tách từ tên đăng nhập kiểu "họ tên - team - bu". Giao diện dựng theo hai file mẫu đã chốt form: tiêu đề + thanh lọc (người dùng / công cụ / từ ngày / đến ngày) + ba nhóm ô chỉ số có gạch phân cách, rồi tới các card nội dung. Bật lại bảng "Tốc độ máy mỗi bước (B2)". Chỉ số mới trong ETL: số user đã dùng thật (không tính người chỉ ngó trang), dùng hàng tuần/hàng ngày — đếm theo mốc MỚI NHẤT trong khoảng lọc chứ không so với hôm nay, để xem lại báo cáo cũ vẫn đúng. CSV tải về: mỗi cột là một câu tiếng Việt, cục properties_json được bung thành "Bước máy: Bóc lời & dịch · Lần bấm chạy thứ: 1". Cột mã và JSON gốc dồn về cuối để vẫn dò ngược được. `?raw=1` giữ nguyên 11 cột schema v2 cho bên nào cần nạp vào công cụ khác. Bảng dịch để một chỗ ở labels.py, màn "Nhật ký chi tiết" đọc chung nên không lệch với file tải về; khoá …4ffa05a
-
feat(dub): en/fil — giọng dựng sẵn chạy Edge, OmniVoice chỉ lo phần clone
Đo thật trên Space: OmniVoice trên CPU mất 47.8s để đọc 2.83s tiếng (~17x thời gian thực), giọng khác 61.7s cho 2.92s (~21x). Quy ra video 60s có 50s lời thoại = 14-17 phút chỉ riêng khâu đọc, trong khi Edge làm cả video ~10s (8 luồng song song). Không đáng đánh đổi cho giọng dựng sẵn. Nay một ngôn ngữ chạy HAI engine: giọng dựng sẵn = Edge (nhanh như cũ), clone = OmniVoice — thứ Edge hoàn toàn không làm được, đáng để chờ. pick_engine chỉ chọn omni khi CÓ MẪU GIỌNG thật (speaker_wav hoặc clone-preset); bấm "Clone từ video" mà chưa cắt được mẫu thì vẫn Edge. /api/dub/voices trả riêng hai vế (engine/model_label cho giọng sẵn, clone_engine/clone_label/clone_slow cho clone). Giao diện báo trước ngay dưới nút chọn nguồn giọng: mỗi 1 giây lời thoại tốn ~15-20 giây xử lý, kẻo user tưởng treo máy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>54ef81a -
feat(dub): thêm OmniVoice (k2-fsa) làm engine TTS cho tiếng Anh & Filipino
en/fil trước chỉ có Edge TTS: giọng dựng sẵn, KHÔNG clone được. Nay chạy OmniVoice (k2-fsa/OmniVoice, 0.6B, 646 ngôn ngữ, ra 24 kHz) với 2 chế độ: - CLONE : có mẫu giọng (từ video hoặc file tải lên) -> ref_audio. - DESIGN : không mẫu -> "instruct" tả giọng bằng chữ. Gói sẵn 6 giọng en + 4 giọng fil (nam/nữ, Mỹ/Anh), ghép thêm ngữ điệu đang chọn (tự nhiên / quảng cáo / đọc tin / kể chuyện). CHƯA CÀI GÓI thì en/fil tự chạy Edge TTS y như cũ — quyết định lấy engine nào làm TRƯỚC bằng find_spec (không import torch trong request /voices), nên /voices trả đúng danh sách giọng của engine sẽ thật sự chạy. /api/dub/voices trả thêm engine + model_label + can_clone; giao diện bỏ hàm đoán isEdge(lang) — clone được hay không là do ENGINE quyết, không phải do tiếng gì. Edge chặn id giọng lạ (vd "omni:*" còn kẹt trong state trình duyệt) để không lỗi cả câu. Chỉnh bằng env: OMNI_DEVICE (cpu|cuda:0|mps), OMNI_STEPS (16), OMNI_ENABLED=0. Co-Authored-By: Claude Opus 5 <…6f5141d -
style(analytics): bỏ chú thích "ít càng tốt" ở tiêu đề cột lần bấm/việc
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>31777af -
fix(analytics): thanh trong bảng đóng góp vẽ theo số thật, không theo điểm
Cột "lần bấm/việc" nhìn ngược đời: 0.5 lần/việc thanh DÀI hơn 0.8 lần/việc, vì thanh vẽ theo điểm quy đổi (ít bấm = điểm cao). Bảng này chủ yếu để nhìn cho nhanh nên đổi luật vẽ: thanh tỷ lệ với ĐÚNG CON SỐ hiện bên cạnh (so với tool cao nhất cùng cột) — số to là thanh dài, không ngoại lệ. Cột nào "ít càng tốt" thì ghi thẳng ở tiêu đề. Tool mở ra chưa bấm chạy lần nào vẽ thanh gạch mờ (không có số để vẽ) để khỏi đọc nhầm thành "0 lần bấm = giỏi nhất". Điểm quy đổi chuyển vào tooltip khi rê chuột. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>121ae3e -
fix(analytics): bảng đóng góp hiện số thật; tool chưa chạy hết được 100đ "ít chạy lại"
Ảnh chụp thực tế cho thấy cột "Ít chạy lại" TOOL NÀO CŨNG 100 -> thành phần 20% mất tác dụng. Nguyên nhân: điểm kẹp sàn attempts_avg = 1.0, nên tool mở ra rồi bỏ (attempts_avg = 0) cũng hoá "ít chạy lại nhất". Bỏ kẹp sàn: chỉ so nghịch đảo giữa các tool THẬT SỰ có bấm chạy; chưa chạy lần nào = 0 điểm. Giao diện: 3 cột nhu cầu / ít chạy lại / sản phẩm giờ hiện SỐ THẬT (33 việc, 0.8 lần/việc, 26 sản phẩm) thay vì điểm quy đổi — thanh tỷ lệ vẫn vẽ theo điểm, rê chuột vào thanh mới hiện điểm. Tool chưa chạy ghi rõ "chưa chạy". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>4b0cace -
feat(analytics): xếp hạng tool đóng góp nhiều/ít nhất; tạm ẩn bảng B2
Điểm đóng góp mỗi tool (etl.score_contribution), trọng số nghiệp vụ chốt: 40% nhu cầu người dùng — số việc người giao cho tool 20% ít phải bấm chạy lại — A4 nghịch đảo, làm 1 phát ăn ngay = điểm cao 40% sản phẩm đầu ra — B1 Mỗi thành phần quy về thang 100 theo tool cao nhất TRONG KỲ ĐANG LỌC, nên điểm chỉ so được giữa các tool cùng khoảng thời gian. Tool chưa chạy việc nào = 0 điểm. Giao diện: thẻ "🏆 Tool đóng góp nhiều nhất / ít nhất" — 2 ô nổi bật + bảng đủ 3 điểm thành phần (có thanh tỷ lệ) để đối chiếu vì sao ra thứ hạng đó. Tạm ẩn (comment) bảng "Tốc độ máy mỗi bước (B2)" theo yêu cầu; backend vẫn tính và trả data.jobs như cũ, muốn hiện lại chỉ cần bỏ comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>392e31c
-
feat(dub): nút xoá video + bản dịch ở bước xem/sửa bản dịch
Dịch xong cả lô mới thấy có video không muốn tạo giọng thì trước đây phải "Làm video mới" (xoá sạch) rồi tải lại từ đầu. Nay mỗi thẻ bản dịch ở bước 3 có nút ✕: xoá đúng video đó — bỏ cả bản dịch, video đã tải lên và kết quả cũ (nếu có) — các video còn lại giữ nguyên để tạo giọng tiếp. Xoá phía client như nút ✕ ở bước tải lên; file tạm trên server tự dọn theo phiên. Thêm class dùng chung .btn-x cho nút ✕ nằm trong hàng chữ nhỏ. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>683c672
-
feat(analytics): mã người dùng U00N + task_id/session_id đọc-được + source/parent_task
Theo góp ý chốt với team về 2 cột task_id/session_id trong bản log: - user_id đổi từ tên tự do sang mã U00N cấp tuần tự khi tạo tài khoản. Mã lưu bảng/file RIÊNG (user_codes), KHÔNG đụng bảng phân quyền: cấp mã lỗi thì đăng nhập vẫn chạy (lùi về username). login trả code; admin thêm user cấp mã ngay. - task_id = <tool>_<user>_<YYYYMMDD>_<HHMMSS>_<rand4>, session_id = s_<user>_<YYYYMMDD>_<HHMMSS>_<rand4> — đọc được, tự sắp theo thời gian, dùng giờ+random thay số thứ tự trong ngày nên mở 2 tab/2 máy không đụng ID. - task mới sinh từ task cũ ghi source + parent_task: rerun (nút Làm mới) và forward (chuyển sang tool khác, mang "cha" qua sessionStorage). - dashboard hiện TÊN cạnh mã (map U00N -> username) để quản trị vẫn biết ai. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>5edb87f -
feat(analytics): tab "Quản lý thời lượng" theo dõi người dùng cho quản trị
Ghi log thô raw_events rồi suy ra 9 chỉ số (A1-A5, B1-B4) bằng ETL, không lưu sẵn chỉ số. Lưu Postgres nếu có DATABASE_URL, không thì SQLite trong STORAGE_DIR (giữ 90 ngày, tự dọn). - backend: services/analytics (storage + etl + track), api/analytics (POST /api/events + các endpoint quản trị), middleware reqctx đọc header X-TNT-* để dòng log máy chạy gắn đúng task; JobManager tự phát job_queued/started/done kèm latency_ms (đã trừ thời gian chờ hàng đợi). - frontend: lib/track.js (đồng hồ người), nút 👍/👎 (ThumbsFeedback) cho B3, dashboard SVG thuần trong tab Quản lý thời lượng; gắn điểm đo vào 8 tool. Kèm sửa lỗi phát hiện khi test: - khôi phục core.files.rel_path() + gom mọi đường dẫn phiên ở cut/dub/shuffle/ download về "/" (trước nối "\" của Windows làm hỏng URL). - get_file trả 404 khi trỏ vào thư mục (thay vì 500). - run_tests.py cô lập STORAGE_DIR sang temp (trước dọn nhầm phiên thật). - cập nhật test_cut/test_dub theo API hiện hành + thêm test_analytics (14 test). …00d2c12
-
feat(text): thêm tab "Chèn chữ vào video" (nhiều video) + nhận dạng lời (ASR)
- Tab mới /chen-chu: vào 1..N video, chèn chữ hard-sub, ra N video tương ứng. - Dùng lại SubtitleEditor (đủ phông/màu/11 kiểu nền/3 kiểu hiển thị/kéo thả) qua props tùy chọn initialSegs/initialMode/onChange/headerExtra/onFetchLines (mặc định null -> tab Dịch/đổi giọng không đổi). - Lấy text: nút "Tự lấy lời từ video (nhận dạng)" -> ASR Whisper (Groq) không dịch, đổ lời + mốc vào editor để sửa; hoặc gõ tay. - Áp form giữa các video: "Áp cả lời + kiểu" (cùng nội dung) và "Áp kiểu (giữ lời riêng)" (mỗi video tự nhận dạng lời khác nhau). - Backend: endpoint /api/dub/subtitle/transcribe (extract_tracks + asr, nhẹ); burn + preview-frame ra thư mục RIÊNG theo từng video (hash) -> burn nhiều video 1 phiên không đè nhau. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>788bc93
-
feat(dub): áp bản dịch cho các câu cùng câu gốc (dịch hàng loạt)
Mỗi câu trong "Xem & sửa bản dịch" thêm 2 nút: - "Áp trong video": áp bản dịch câu này cho mọi câu cùng câu gốc trong video đó. - "Áp mọi video": áp cho mọi câu cùng câu gốc ở tất cả video đang dịch. Khớp theo câu gốc đã chuẩn hoá (bỏ khoảng trắng thừa + không phân biệt hoa/thường) nên bắt được câu lặp dù lệch nhẹ. Tiện khi dịch hàng loạt (vd 200 video quảng cáo cùng chiến dịch) chỉ cần sửa 1 câu intro/CTA rồi áp cho tất cả. Thuần client-side nên tức thì, không đụng mốc thời gian; có banner báo số câu đã áp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>0172658
-
fix(dub): giữ bản dịch sau khi tạo giọng + nút làm video mới khỏi F5
- Panel "Xem & sửa bản dịch" không còn bị ẩn khi đã có video kết quả: user lỡ bấm tạo giọng vẫn sửa lời / dịch lại / đọc lại được. Nút "Dịch lại" giờ chạy lại pha dịch; nút chính đổi nhãn "Đọc & ghép lại" khi đã có output. - Thêm nút "Làm video mới" ở panel Kết quả: xoá video + bản dịch + kết quả hiện tại (giữ cài đặt ngôn ngữ/giọng) để làm video khác mà không cần F5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>9e3250e
-
style(login): đổi placeholder ô username -> "Username: Tên - Team - BU"
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>61318db -
style(admin): bỏ dòng mô tả VIDEO_ADMIN_TOKEN ở ô đăng nhập quản trị
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>ed076c3
-
feat(shuffle): bảng tổ hợp mirror video_cook — ảo hoá 300k + lọc bằng nút (JS)
Làm phần hiển thị list combo giống tool video_cook: - Thứ tự combo: tuần tự từ điển nếu ≤ trần, LẤY MẪU ĐA DẠNG nếu vượt (bỏ cách cũ chỉ lấy N cái đầu theo product). /plan trả sequences + clip_names (payload gọn). - Trần 300.000 combo. Bảng FE ẢO HOÁ (chỉ vẽ dòng đang thấy) -> không treo dù hàng trăm nghìn dòng. Hiển thị "tên + tên + tên" + cột 🎵 Source (xoay vòng theo thứ hạng trong tập đang chọn) + STT + ✓, giống combo_model.py. - Bộ lọc bằng NÚT nối chuỗi trên tập ĐANG CHỌN (giống tab2_output.py): Đặt lại / Chọn hết / Bỏ hết / Lọc ngẫu nhiên % / Lọc tương đồng % + "Ưu tiên khúc khác hoàn toàn". Lọc tương đồng port Y HỆT SimilarityWorker (greedy keep-first + nén, 0%=khác hoàn toàn), chạy Web Worker -> UI không đơ. Đã cross-check 40 case khớp 100% với bản Python. Component mới: ComboTable.jsx, worker: simFilter.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>824497d -
feat(dub): mục "Nâng cao (ASR)" cho user chỉnh ngưỡng lọc Whisper (ẩn, mặc định như cũ)
Thêm chỗ tinh chỉnh 2 ngưỡng quyết định giữ nhiều/ít lời khi Whisper bỏ sót (video nói nhỏ / lẫn nhạc): no_speech_max (0.6) và logprob_min (-1.0). Ẩn trong mục thu gọn "⚙ Nâng cao — nhận dạng lời (ASR)" vì đa số không cần; MẶC ĐỊNH giữ đúng giá trị hiện tại nên hành vi không đổi trừ khi user tự chỉnh. Có nút "Bắt nhiều lời hơn" (0.85/-1.6) và "Về mặc định". Nối: FE DubVideo -> dubPrepare -> DubReq -> DubOptions -> pipeline -> asr.transcribe -> _filter_speech. Đã verify: default bỏ đoạn ranh giới, nới thì giữ. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>aab8e67
-
feat(sub): thêm 7 phông chữ phụ đề (Oswald, Nunito, Lato, Merriweather, Playfair, Kanit, Dancing Script)
Phông mã nguồn mở (Google Fonts, OFL), đã kiểm cmap phủ ĐỦ dấu tiếng Việt. Variable font -> instance về 1 weight tĩnh bằng fontTools + đặt tên family sạch (như bộ cũ là 1 face khai báo normal). Nhúng Git LFS + @font-face; Dockerfile tự cp *.ttf. Đo hệ số co k (libass/CSS, nhất quán <1% qua 3 chuỗi) cho từng phông để preview KHỚP burn: Oswald .587, Nunito .724, Lato .714, Merriweather .577, Playfair Display .705, Kanit .630, Dancing Script .710. Đã verify overlay Oswald + Dancing Script khớp cỡ/ giãn dòng/vị trí, dấu tiếng Việt hiện đủ. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>9206420 -
style(sub): thu nhỏ 4 tay kéo cạnh ô phụ đề cho gọn (12x22 -> 6x13, viền 1px)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>0b41d4a -
feat(sub): loé nền nút "về giữa‑đáy / áp style / áp vị trí" khi bấm
Ba nút hành động tức thì trước đây bấm xong không đổi gì trên nút -> user không biết đã áp chưa. Thêm doFlash: loé nhẹ nền cam ~0.6s (dùng transition sẵn của .btn) để báo "đã nhấn". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>2621912 -
fix(fe): render lần 2 hiện video đen — cache-bust <video> khi ghi đè cùng tên file
Render/burn/xào LẠI ghi đè output cùng tên -> URL không đổi -> React tái dùng <video> + trình duyệt giữ bytes đã cache/đã decode của lần trước -> khung ĐEN. Tải về vẫn đúng vì là request mới. Sửa: gắn mốc thời gian (outStamp/resStamp/_v) vào src <video> (?v=...) + đổi key để remount mỗi lần render. Áp cho DubVideo (dịch/đổi giọng), SubtitleEditor (chèn phụ đề), ShuffleVideo (xào). Link tải giữ nguyên (đã đúng). videoRel/srtRel truyền sang trình sửa phụ đề không kèm query. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>9f6c247 -
feat(sub+shuffle): tay kéo giãn ô phụ đề + giữ/tắt tiếng gốc khi xào
Phụ đề: thêm 4 tay kéo cạnh cho ô đang chọn — trái/phải đổi BỀ RỘNG vùng (w), trên/dưới GIÃN cỡ chữ (fontSize theo khoảng cách con trỏ tới tâm ô, con trỏ bám cạnh). Bỏ tay kéo góc cũ. Xào video: thêm tùy chọn "Giữ âm thanh gốc" (mặc định BẬT) — ghép nối tiếng gốc các khúc theo đúng thứ tự xào; nếu có lồng track thì TRỘN track lên tiếng gốc. Tắt -> video câm (như cũ) / chỉ còn track. Mô hình theo tool video_cook: - normalize_clip(with_audio): giữ tiếng gốc (aac 44100 stereo), clip câm chèn im lặng để concat copy đồng nhất; cache key riêng theo chế độ audio. - attach_audio(keep_original): amix track lên tiếng gốc (normalize=0) thay vì thay hẳn. Đã verify render thật: giữ/tắt/trộn đều đúng, tiếng gốc đúng vị trí từng khúc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>ad3a570 -
fix(sub): CSS xem trước KHỚP burn - hệ số co cỡ chữ + giãn dòng đo thực nghiệm
Nguyên nhân: libass vẽ Fontsize=F ra cỡ pixel nhỏ hơn CSS font-size=F. Đo render_frame(libass) vs canvas trình duyệt -> hệ số co K cố định mỗi font (BVP .655, Roboto .829, OpenSans .692, Montserrat .641, Arimo .694, Tinos .905). Khoảng cách dòng libass = 1.0*fsize mọi font. - FE: cỡ chữ preview = fontSize*k*scale; lineHeight = 1/k; measureBreaks đo ở fontSize*k; padding/bo góc hộp quy theo fpx; lề ngang 1.5% khớp measureBreaks. - BE: box_h = n*fsize + 2*pad (bỏ *1.25*K sai) -> hộp ôm sát chữ, khớp hộp CSS. Kiểm chứng overlay CSS đè khung burn thật: cỡ/bề rộng/ngắt dòng/giãn dòng/hộp khớp cho cả font mặc định lẫn font lệch nhất. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>0ce96ff -
fix(sub): không bao giờ trống chữ - luôn hiện CSS nền, ảnh chuẩn đè lên
Bug: khi DỪNG mà ảnh chuẩn (server) chưa về/lỗi thì ẩn luôn chữ CSS -> phụ đề biến mất (chỉ còn khung chọn). Sửa: CHỈ ẩn CSS khi ảnh chuẩn ĐÃ hiện (showExact = idle && previewImg). Còn lại (đang chờ ảnh / lỗi / đang phát / gõ / kéo) luôn hiện CSS làm nền -> không bao giờ trống. Ảnh chuẩn về thì đè lên. - <img> lỗi tải -> tự lùi về CSS (onError) thay vì trống. - Dòng trạng thái phân biệt rõ: đang dựng / đã khớp / lỗi / đang phát. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>1b53507 -
fix(sub): lúc phát ẩn phụ đề gần-đúng + render khung chuẩn nhanh 3x
Gốc hiểu lầm: đang PHÁT vẫn hiện phụ đề CSS gần-đúng nên trông khác bản burn. - FE: chỉ hiện CSS khi TẮT exact (xem nhanh) hoặc đang gõ/kéo. Bật exact + đang phát -> KHÔNG hiện phụ đề (tránh nhầm); DỪNG mới hiện phụ đề CHUẨN (=burn). - Hiện lỗi rõ nếu server render khung thất bại ("Lỗi dựng khung chuẩn"). - render_frame: seek ĐẦU VÀO + -copyts (nhanh gấp ~3: 0.22s vs 0.62s, ảnh trùng khít) -> server yếu vẫn mượt. Debounce 200ms; render ngay khi có metadata. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>6547b89 -
fix(sub): preview khớp 100% burn bằng render server (bỏ libass-wasm)
libass-wasm và ffmpeg dùng 2 BẢN libass khác phiên bản -> render KHÁC nhau (chữ bị rớt do bug fallback libass #460, khác font-hinting...). Không thể đảm bảo giống hệt bằng bộ vẽ thứ 2 trong trình duyệt. Cách chắc chắn = dùng CHÍNH ffmpeg/libass của bản burn để xem trước: - Bỏ hẳn libass-wasm (gỡ package + public/libass + endpoint /subtitle/ass). - Khi BẬT "Xem trước khớp 100%" + video TẠM DỪNG + không gõ/kéo -> tự gọi /subtitle/preview-frame render đúng khung tại thời điểm hiện tại (server ffmpeg) rồi đè ảnh lên video. Ảnh này = ĐÚNG video sẽ xuất. - Đang phát/gõ/kéo -> hiện CSS nhanh (mượt); dừng lại tự cập nhật khung chuẩn (debounce 350ms, chống race bằng frameSeq). - Giữ fix cỡ video = nội dung thật (bỏ letterbox) để ảnh đè khớp vùng video. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>4f9859d -
feat(sub): xem trước phụ đề KHỚP 100% bản burn (libass-wasm + render khung server)
Preview CSS chỉ 'bắt chước' libass -> không bao giờ giống hệt. Nay preview dùng CHÍNH libass, cùng ASS + font như bản burn: - Backend: tách build_ass_str (1 nguồn ASS cho burn/preview), thêm render_frame (render đúng 1 khung), 2 endpoint: /subtitle/ass (ASS cho libass-wasm), /subtitle/preview-frame (ảnh khớp 100% để đối chiếu). - FE (A) libass-wasm (SubtitlesOctopus): vẽ phụ đề THẬT đè video, real-time; nạp ASS từ backend (debounce), font từ /fonts. Toggle "Xem trước khớp 100%"; lỗi -> tự lùi về CSS. Ẩn lớp CSS khi libass bật (tránh chồng đôi); ẩn canvas khi đang gõ. - FE (B) nút "Xem trước chuẩn (ảnh)" -> gọi render 1 khung server. - Sửa lệch hình học gốc: cỡ hiển thị video = nội dung THẬT (bỏ letterbox ngang) nên overlay ánh xạ đúng toạ độ video (trước tính theo khung thẻ kèm viền đen). - worker/wasm libass để LFS (khớp *.wasm), HF chấp nhận. Đã kiểm: build FE pass; backend build_ass_str/render_frame ra ASS+khung đúng (highlight đúng thời điểm); libass-wasm nạp worker+was…d908e31
-
fix(sub): nhúng font mã nguồn mở -> burn phụ đề KHỚP preview trên server
Server Linux không có font Microsoft nên libass fallback -> chữ lệch bản xem trước (Windows dev khớp vì có sẵn font MS = bẫy). Nhúng 6 font OSS đủ tiếng Việt (Be Vietnam Pro, Roboto, Open Sans, Montserrat, Arimo~Arial, Tinos~Times) vào backend/app/assets/fonts; trình duyệt nạp CHÍNH file này qua @font-face(/fonts) nên preview=render mọi máy. - FONTS/measureBreaks(bỏ bold)/boxStyle(fontWeight 400) đồng bộ tên+độ rộng - ASS Bold=0 (font đã đậm sẵn, tránh làm-đậm-giả lệch metric) - burn() dùng fontsdir tương đối (né dấu ":" ổ đĩa Windows làm hỏng filter ffmpeg) - main.py mount /fonts, vite proxy /fonts cho dev - Dockerfile cài fontconfig + copy font hệ thống + fc-cache Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>d4a04dd -
feat(dub): prompt dịch persona 20 năm + xưng hô nhất quán; nạp SRT tự tải
- Prompt dịch (Gemini + Groq dùng chung): đóng vai chuyên gia dịch <gốc>→<đích> 20 năm KN, ép xưng hô nhất quán theo nhân vật/hoàn cảnh. Ngôn ngữ gốc lấy tự động từ ASR (pipeline truyền source_lang xuống translate_all). - Nạp SRT tự tải: endpoint /api/dub/subtitle/import parse SRT + BỎ nhãn trong [] (giữ mốc + nội dung); nút "Dùng file SRT khác" ở khu xem/sửa bản dịch để thay bản dịch tự động khi user không ưng. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>ab852b0
-
feat(sub): nút "Áp vị trí cho mọi đoạn" trong trình sửa phụ đề
Chọn 1 đoạn, kéo tới vị trí mong muốn rồi bấm nút này để chép x/y và bề rộng vùng sang tất cả các đoạn còn lại, giữ nguyên font/màu/nền riêng của từng đoạn. Bổ trợ cho "Áp style cho mọi đoạn" (nút cũ cố ý giữ nguyên vị trí riêng của mỗi đoạn). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>00151ba
-
feat(dub): nút "Nghe thử" giọng trước khi chọn (vi/en/fil)
- api/dub.py: GET /api/dub/voice-preview?lang=&voice= — server đọc 1 câu mẫu bằng giọng chọn, CACHE dùng chung mọi user (VieNeu vi chậm nên chỉ chạy 1 lần/giọng, sau đó tức thì). Khoá tránh 2 request cùng tạo 1 giọng. - DubVideo.jsx: component VoicePreview (nút "▶ Nghe thử" + <audio>), gắn cạnh cả 3 ô chọn giọng: Edge (en/fil), VieNeu preset (vi), và khu tạo giọng từ text. VieNeu lần đầu hiện "Đang tạo…" trong lúc server dựng. Test: en/fil qua Edge OK (~2s lần đầu, cache 0.000s); vi cùng code path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>2b1978a -
feat(cut): tách video thành video không lời + MP3, tải cái nào tuỳ ý
Thêm tính năng trong tool Cut: user tải/kéo thả video -> tách thành 1 video KHÔNG lời (mp4) và 1 file âm thanh (mp3), cho tải riêng từng cái. - services/cutter/cut.py: split_audio() — video không lời (copy luồng hình, lỗi thì re-encode H.264) + mp3 (libmp3lame). has_audio_stream() để bỏ qua mp3 khi video vốn không có tiếng. Trả SplitResult. - api/cut.py: POST /api/cut/split-audio (chạy job) trả URL 2 file + has_audio; auto-save vào lịch sử/Drive nếu có username. - api.js: cutSplitAudio(). - CutVideo.jsx: card "🔊 Tách âm thanh" (dùng video đang chọn) + 2 nút tải; báo rõ khi video gốc không có tiếng. Test: video có tiếng -> video không lời (hết audio) + mp3 hợp lệ; video không tiếng -> chỉ video không lời; endpoint end-to-end OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>b7ce941
-
fix(shuffle): chỉ tạo 1 session dùng chung khi thả clip nhiều khúc cùng lúc
BUG THẬT khiến "xào lỗi mọi biến thể": uploadMany đọc `st.session` (state cập nhật TRỄ). Thả clip vào nhiều khúc CÙNG LÚC -> mọi luồng đều thấy session=null -> mỗi khúc xin backend tạo 1 session riêng (log: seg_0/seg_1/seg_2 nằm ở 3 session khác nhau). Khi /generate chỉ dùng 1 session -> clip các session kia không tồn tại -> ffmpeg ENOENT (exit 254) -> 0/N biến thể. Sửa: dùng sidRef (đồng bộ) làm nguồn sự thật + sidInit (khoá promise) để CHỈ luồng đầu tiên tạo session, các khúc khác chờ đúng session đó. one() luôn đọc sidRef.current tại thời điểm gọi thay vì biến chụp từ state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>014a2c8 -
fix(cut): sửa lỗi "pollAbort is not defined" khi bấm Cắt video
pollAbort ref bị đặt nhầm trong component VideoTimeline, trong khi executeAll (nơi gọi pollJob với pollAbort.current.signal) nằm ở component CutVideo -> ReferenceError ngay khi bấm Cắt. Chuyển ref sang đúng CutVideo. Đã audit lại Dub/Shuffle/DownList/SubtitleEditor: scope đều đúng, chỉ Cut bị. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>bbd4448 -
fix(shuffle): tiến độ upload theo TỪNG khúc, không còn nhảy loạn xạ
Trước đây dùng CHUNG 1 state `uploading` cho mọi khúc; up nhiều khúc cùng lúc thì mỗi lần paint() ghi đè seg của nhau -> thanh % nhảy qua lại giữa các khúc. Đổi sang map { [segKey]: {done,total,pct,name} }: mỗi khúc (và ô audio) có tiến độ riêng, xoá key của riêng mình khi tải xong. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>056724a -
chore(web): ẩn "Listing ảnh Free" khỏi menu, route và Hướng dẫn
- Sidebar: comment mục "Listing ảnh Free". - App.jsx: comment lazy import + route /listing-free (vào thẳng -> 404). - Guide: comment cả mục hướng dẫn Listing ảnh Free. - ListingImage: bỏ câu tip nhắc dùng tab Listing ảnh Free. Tất cả comment lại (không xoá) để bật lại dễ. Backend /api/listing-free giữ nguyên (không còn lối vào từ UI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>d249a38
-
fix(dub): cho câu gốc (tham khảo) tự xuống dòng ở bước sửa bản dịch
Dòng "Câu gốc" đang whiteSpace:nowrap + overflow:hidden + ellipsis -> câu dài bị cắt/tràn ngang, không đọc được để đối chiếu. Đổi sang whiteSpace:normal + overflowWrap:anywhere để wrap đầy đủ (parent đã có minWidth:0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>4411f81 -
feat: dịch dự phòng deep-translator + lọc ảo giác ASR + xào render tới 10k
Dịch (translate.py, asr.py): - Nối deep-translator (Google free, không cần key) làm dự phòng cuối: khi Gemini + Groq lỗi/thiếu key vẫn dịch được từ tiếng bất kỳ trong video sang vi/en/fil (source auto-detect), thay vì trả câu gốc. - Bắt cả trường hợp dịch ra y hệt câu gốc -> vẫn chạy deep-translator. - Log rõ khi Gemini/Groq lỗi (soi HF Logs ra ngay thủ phạm). - Parser batch bền hơn (1. 2) **3.** 4: markdown). - Lọc ảo giác Whisper: bỏ câu dài mà đọc quá chậm + cụm bịa quen ("đăng ký kênh", "cảm ơn đã xem"...) khi tốc độ đọc bất thường. Xào (shuffle.py, ShuffleVideo.jsx): - MAX_RENDER 150 -> 10000 (env SHUFFLE_MAX_RENDER); PLAN_CAP 3000 -> 5000 (env SHUFFLE_PLAN_CAP). FE lấy max_render từ backend nên luôn khớp env. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>3d2064d -
fix(auth): làm rõ ô token admin + vá bug danh sách chờ duyệt
- AdminUsers UI: đổi nhãn/placeholder ô token thành "mật khẩu quản trị (VIDEO_ADMIN_TOKEN)", thêm ghi chú rõ đây KHÔNG phải API key OpenAI; sửa tiêu đề phụ, thông báo lỗi, thêm Enter-để-đăng-nhập. - storage.py: _json_read trả {} cho file pending (vốn là list) khi file chưa tồn tại -> request_access/list_pending crash lúc danh sách chờ trống (backend JSON). Thêm tham số default + ép kiểu list ở các hàm pending. - Đã test end-to-end approve/reject/remove (16/16 pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>83fcf39
-
perf: queue theo loại + code-split FE + streaming upload cho nhiều user
Backend: - Viết lại JobManager thành hàng đợi theo loại tác vụ (heavy/io), có vị trí hàng đợi (queue_pos/total) và tự dọn job cũ để không rò rỉ RAM. Cấu hình qua env JOB_HEAVY_WORKERS/JOB_IO_WORKERS/JOB_HISTORY_MAX. - core/bg.py: pool nền có giới hạn (BG_WORKERS) thay raw thread không giới hạn ở listing/listing_free/flux2. - Đưa OpenAI blocking ra khỏi event loop (listing analyze/create_session, listing_free analyze/auto-generate: async def -> def). - Streaming upload (save_upload_stream) thay vì đọc cả file vào RAM; enforce MAX_UPLOAD_MB. Áp cho dub/cut/shuffle. - Cache: Demucs model, ffprobe theo (path,mtime), client Groq. - thumbnail_strip gộp 40 lần ffmpeg -> 1 pass (fallback chậm khi lỗi). - Endpoint GET /api/queue/stats. Frontend: - Sửa bug double <Outlet> trong Layout (mọi trang mount 2 lần -> chạy đôi upload/poll/API). - Code-split các page bằng React.lazy + Suspense; tách react-vendor chunk. - pollJob nhận AbortSignal; hủy poll khi rời trang (chống rò rỉ setTimeou…3370310