Đạo diễn một cảnh phim điện ảnh gai góc, sống động về vụ tấn công cướp ngân hàng JPMorgan. Ghi lại sự căng thẳng và bầu không khí nghẹt thở trong phân cảnh dài 32 giây, được chia thành bốn đoạn 8 giây.
Hãy đóng vai một đạo diễn điện ảnh. Bạn được giao nhiệm vụ tạo dựng một cảnh phim điện ảnh gai góc, sống động về vụ tấn công cướp ngân hàng JPMorgan, ngân hàng lớn nhất tại Mỹ. Phân cảnh này kéo dài 32 giây, với 8 giây cho mỗi cảnh nhỏ nhằm khắc họa trọn vẹn sự căng thẳng và bầu không khí nghẹt thở của sự kiện. Cảnh 1 (0-8 giây): - Cảnh toàn (establishing shot) quay trụ sở cao sừng sững của JPMorgan nổi bật trên nền trời đêm. - Máy quay phóng cận dần để lộ bầu không khí thiếu sáng, đặc quánh sự căng thẳng xung quanh tòa nhà. - Tiếng xì xào và âm thanh ồn ào của thành phố làm nền tạo nên một khung cảnh đầy điềm báo bất an. Cảnh 2 (8-16 giây): - Cảnh cận (close-up) nhóm cướp đeo mặt nạ bước ra từ một chiếc xe tải nhỏ màu đen, vũ khí lăm lăm trên tay. - Chuyển động chậm (slow-motion) khi chúng tiến về phía lối vào với sự tập trung đầy quyết đoán. - Sự căng thẳng dâng trào cùng tiếng nhạc nền kịch tính nhấn mạnh từng bước chân của chúng. Cảnh 3 (16-24 giây): - Bên trong ngân hàng: chuông báo động an ninh rú vang, đèn đỏ nhấp nháy liên hồi. - Khách hàng và nhân viên cúi rạp người trong nỗi sợ hãi khi toán cướp ập vào bên trong. - Những cú cắt cảnh nhanh (quick cuts) luân phiên giữa nhóm cướp và những khuôn mặt hoảng loạn, đẩy sự hỗn loạn lên cao trào. Cảnh 4 (24-32 giây): - Cảnh rượt đuổi cường độ cao khi lực lượng an ninh giao chiến với toán cướp. - Các góc máy động (dynamic camera angles) ghi lại nỗ lực tẩu thoát điên cuồng. - Phân cảnh kết thúc bằng một khoảnh khắc bỏ lửng đầy kịch tính (cliffhanger) khi một tên cướp đối mặt trực diện với nhân viên bảo vệ. Nhiệm vụ của bạn là truyền tải sự căng thẳng, tính cấp bách và mức độ rủi ro cực cao của từng khoảnh khắc, đảm bảo mang lại trải nghiệm nhập vai cuốn hút cho khán giả.
Kiểm tra các tệp phụ đề SRT và WebVTT để phát hiện thời gian bị lỗi hoặc chồng lấp, khoảng trống đánh số thứ tự, thẻ phụ đề (cue) trống, tốc độ đọc vượt quá giới hạn ký tự trên giây, dòng quá dài hoặc thừa dòng, phụ đề lướt qua quá nhanh hoặc hiển thị quá lâu, thẻ định dạng không cân xứng và các nhãn âm thanh, sau đó viết lại cũng như căn chỉnh lại thời gian cho các đoạn phụ đề gặp sự cố và có thể dịch chuyển thời gian của toàn bộ tệp. Sử dụng khi người dùng chia sẻ phụ đề và yêu cầu "kiểm tra phụ đề giúp tôi", "tại sao những phụ đề này khó đọc thế?", "sửa lại thời gian hiển thị", hoặc "làm cho các phụ đề này tuân thủ đúng quy chuẩn".
---
name: srt-subtitle-quality-checker
description: Kiểm tra các tệp phụ đề SRT và WebVTT để phát hiện thời gian bị lỗi hoặc chồng lấp, khoảng trống đánh số thứ tự, thẻ phụ đề (cue) trống, tốc độ đọc vượt quá giới hạn ký tự trên giây, dòng quá dài hoặc thừa dòng, phụ đề lướt qua quá nhanh hoặc hiển thị quá lâu, thẻ định dạng không cân xứng và các nhãn âm thanh, sau đó viết lại cũng như căn chỉnh lại thời gian cho các đoạn phụ đề gặp sự cố và có thể dịch chuyển thời gian của toàn bộ tệp. Sử dụng khi người dùng chia sẻ phụ đề và yêu cầu "kiểm tra phụ đề giúp tôi", "tại sao những phụ đề này khó đọc thế?", "sửa lại thời gian hiển thị", hoặc "làm cho các phụ đề này tuân thủ đúng quy chuẩn".
---
# Trình Kiểm tra Chất lượng Phụ đề SRT (SRT Subtitle Quality Checker)
Bạn giúp các nhà làm video, biên dịch viên và đội ngũ hỗ trợ tiếp cận (accessibility) xuất bản những bản phụ đề mà người xem thực sự có thể đọc kịp và thoải mái. Bạn tìm ra các lỗi mốc thời gian làm hỏng trình phát video, các thẻ phụ đề (cues) xuất hiện quá chớp nhoáng hoặc chứa quá nhiều chữ, và các dòng quá dài, đồng thời khắc phục chúng bằng cách viết lại câu chữ cô đọng hơn và điều chỉnh lại thời gian mà không làm thay đổi ý nghĩa gốc.
## Các tệp trong kỹ năng này
- `scripts/check_subtitles.py` - phân tích cú pháp SRT và WebVTT, kiểm tra thời gian, đánh số thứ tự, tốc độ đọc, độ dài dòng, số lượng dòng, thời lượng hiển thị, khoảng nghỉ giữa các câu, thẻ định dạng và nhãn âm thanh, đồng thời có thể dịch chuyển toàn bộ mốc thời gian (chỉ sử dụng thư viện chuẩn của Python 3)
- `references/subtitle-guidelines.md` - các giới hạn tiêu chuẩn phổ biến về tốc độ đọc, độ dài dòng, thời lượng hiển thị, khoảng nghỉ, ngắt dòng và thời điểm sử dụng nhãn SDH
- `references/condensing-techniques.md` - cách rút gọn văn bản phụ đề mà không làm mất ý nghĩa, và cách căn chỉnh lại thời gian cho các thẻ phụ đề (cues)
- `templates/subtitle-review.md` - mẫu báo cáo đánh giá để trả về cho người dùng
- `examples/example-workshop-video.md` - một bài đánh giá hoàn chỉnh thực tế cho một video hướng dẫn ngắn
## Quy trình làm việc
### 1. Thu thập thông tin thực tế
Hỏi người dùng, hoặc tự đặt giả định hợp lý và nêu rõ giả định đó:
- Tệp đầu vào (SRT hoặc VTT) và tổng thời lượng video; tốc độ khung hình nếu biết (24, 25 hoặc 30 fps).
- Đối tượng khán giả và quy chuẩn phong cách (style guide): khán giả trưởng thành nói chung, trẻ em, quy chuẩn của một nền tảng cụ thể, hay giới hạn riêng của khách hàng.
- Loại phụ đề: phụ đề cùng ngôn ngữ (captions), phụ đề dịch thuật, hay phụ đề SDH (dành cho người điếc và khiếm thính, có kèm nhãn mô tả âm thanh).
- Liệu bạn được phép viết lại rút gọn câu chữ hay chỉ được phép điều chỉnh mốc thời gian.
### 2. Chạy trình kiểm tra
```bash
python3 scripts/check_subtitles.py movie.srt
python3 scripts/check_subtitles.py movie.srt --max-cps 15 --max-line 37 # children or stricter guides
python3 scripts/check_subtitles.py movie.vtt --json
python3 scripts/check_subtitles.py movie.srt --shift -1200 > movie-synced.srt # whole file 1.2 s earlier
```
Giá trị mặc định: 17 ký tự mỗi giây, 42 ký tự mỗi dòng, tối đa 2 dòng, hiển thị từ 0.833 đến 7 giây trên màn hình, khoảng nghỉ tối thiểu 83 ms. Hãy chọn các ngưỡng giới hạn từ `references/subtitle-guidelines.md` sao cho phù hợp với đối tượng khán giả. Nếu bạn không thể chạy kịch bản kiểm tra, hãy kiểm tra thủ công từng thẻ phụ đề (cue): thời lượng hiển thị, số ký tự chia cho số giây, dòng dài nhất, và sự chồng lấp thời gian với thẻ phụ đề kế tiếp.
### 3. Sửa lỗi theo thứ tự ưu tiên sau
1. Xử lý các phát hiện mức HIGH trước tiên: chồng lấp thời gian, thẻ phụ đề sai thứ tự thời gian, thời điểm kết thúc đứng trước thời điểm bắt đầu, mốc thời gian sai định dạng, thẻ phụ đề trống. Những lỗi này làm hỏng trình phát hoặc làm ẩn mất chữ.
2. Tốc độ đọc (Reading speed): kéo dài thời gian hiển thị của thẻ phụ đề sang khoảng thời gian trống phía trước hoặc phía sau nó (tuân thủ khoảng nghỉ tối thiểu và các điểm chuyển cảnh mà người dùng đề cập), nếu không đủ thì rút gọn văn bản theo hướng dẫn trong `references/condensing-techniques.md`, nếu vẫn không được thì tách thẻ phụ đề thành hai phần tại một nhịp nghỉ tự nhiên.
3. Độ dài dòng và số lượng dòng: ngắt lại dòng tại ranh giới cụm từ tự nhiên; ưu tiên giữ cho dòng trên ngắn hơn khi có thể; tuyệt đối không ngắt dòng giữa mạo từ/lượng từ và danh từ đi kèm.
4. Hiển thị quá ngắn hoặc quá dài trên màn hình: gộp các thẻ phụ đề quá ngắn với thẻ liền kề, tách nhỏ các thẻ quá dài.
5. Các mục mức INFO: đánh lại số thứ tự, đóng mở thẻ định dạng cho cân xứng, xóa nhãn mô tả âm thanh khỏi các tệp không phải SDH, nối liền các thẻ phụ đề (khoảng nghỉ 0 ms) hoặc chừa một khoảng nghỉ đủ rõ.
Chạy lại trình kiểm tra trên tệp đã sửa cho đến khi không còn phát hiện mức HIGH nào và mọi cảnh báo mức WARN đều đã được sửa hoặc được giải thích rõ ràng.
### 4. Bàn giao kết quả
Điền vào mẫu `templates/subtitle-review.md`: bản tóm tắt kèm số liệu thống kê trước và sau khi sửa, các ngưỡng giới hạn đã áp dụng, bảng liệt kê các thẻ phụ đề đã thay đổi (thời gian và nội dung cũ so với mới), cùng toàn bộ tệp đã được chỉnh sửa hoàn chỉnh giữ đúng định dạng đầu vào. Tuân theo phong cách trình bày của `examples/example-workshop-video.md`.
## Quy tắc
- Tuyệt đối không làm thay đổi ý nghĩa, giọng điệu hoặc người nói của câu thoại; chỉ cắt gọn các từ đệm rườm rà chứ không cắt bỏ nội dung thông tin.
- Giữ nguyên chính xác tên riêng, con số và thuật ngữ kỹ thuật; nếu phát hiện một thuật ngữ có vẻ sai, hãy gắn cờ cảnh báo thay vì tự ý đoán mò.
- Mọi thay đổi về mốc thời gian phải giữ đồng bộ với lời nói: chỉ kéo dài thẻ phụ đề lấn vào khoảng im lặng, tối đa khoảng 0.5 giây trước khi lời nói bắt đầu hoặc 1 giây sau khi lời nói kết thúc, trừ khi người dùng cho phép nới rộng hơn.
- Giữ nguyên định dạng đầu vào (tệp SRT giữ nguyên SRT, tệp VTT giữ nguyên VTT) và bảng mã gốc (UTF-8).
- Nêu rõ những phát hiện nào bạn không thể tự sửa nếu không được nghe trực tiếp âm thanh hoặc xem hình ảnh video.
FILE:references/subtitle-guidelines.md
# Quy chuẩn phụ đề: các ngưỡng giới hạn phổ biến
Các đài truyền hình và nền tảng phát trực tuyến khác nhau đều công bố bộ quy chuẩn phong cách (style guide) riêng của họ. Các giá trị dưới đây là những mốc khởi đầu phổ biến; hãy luôn ưu tiên quy chuẩn của khách hàng hoặc của nền tảng đích khi có sẵn, và nêu rõ bạn đã sử dụng bộ giới hạn nào.
## Tốc độ đọc (số ký tự trên giây - characters per second, CPS)
CPS = tổng số ký tự hiển thị nhìn thấy được trong thẻ phụ đề (chữ cái, chữ số, khoảng trắng, dấu câu; không tính các thẻ định dạng HTML/tag) chia cho số giây hiển thị trên màn hình.
| Đối tượng khán giả | Giới hạn CPS điển hình |
|---|---|
| Khán giả trưởng thành nói chung, phụ đề cùng ngôn ngữ hoặc dịch thuật | 15 đến 17 |
| Nội dung người lớn có nhịp độ nhanh nơi bộ quy chuẩn cho phép | tối đa 20 |
| Trẻ em (khoảng từ 6 đến 11 tuổi) | 12 đến 13 |
| Người đang học ngôn ngữ đó, phụ đề SDH có nhiều nhãn mô tả âm thanh | 13 đến 15 |
Số từ trên phút (words per minute - wpm) là thước đo cũ hơn; khoảng 160 đến 180 wpm tương ứng với khoảng 15 đến 17 CPS trong tiếng Anh.
## Độ dài dòng và số lượng dòng
- Từ 37 đến 42 ký tự mỗi dòng đối với hầu hết các ngôn ngữ dùng chữ viết Latinh; 42 là giới hạn hiện đại phổ biến nhất.
- Tối đa 2 dòng trên mỗi thẻ phụ đề (cue). Dòng thứ ba sẽ che khuất hình ảnh và rất khó đọc kịp.
- Sử dụng một dòng khi văn bản nằm gọn trên một dòng; chỉ ngắt thành hai dòng khi thực sự cần thiết.
## Thời lượng hiển thị trên màn hình
- Tối thiểu: khoảng 5/6 giây (20 khung hình ở tốc độ 24 fps), ngay cả đối với một từ đơn lẻ, để mắt người xem kịp ghi nhận.
- Tối đa: khoảng 7 giây. Các thẻ phụ đề hiển thị lâu hơn sẽ khiến người xem đọc đi đọc lại và tạo cảm giác bị treo; hãy tách nhỏ chúng ra.
## Khoảng nghỉ giữa các thẻ phụ đề (Gaps)
- Các thẻ phụ đề nối tiếp (Chained cues): khoảng nghỉ 0 ms hoàn toàn ổn khi lời thoại diễn ra liên tục không ngắt quãng.
- Trong các trường hợp khác, hãy chừa ít nhất 2 khung hình (83 ms ở 24 fps, 80 ms ở 25 fps) để người xem nhận biết được rằng dòng chữ trên màn hình vừa thay đổi.
- Các khoảng nghỉ nằm trong khoảng từ 1 ms đến dưới 2 khung hình sẽ gây ra hiện tượng nhấp nháy chữ rất khó chịu và cần phải được khép kín về 0 hoặc mở rộng ra.
## Ngắt dòng (Line breaks)
Hãy ngắt dòng tại các đơn vị ngôn ngữ tự nhiên:
- Sau dấu câu (dấu phẩy, dấu chấm) khi có thể.
- Trước các liên từ (và, nhưng, bởi vì / and, but, because) và giới từ.
- Tuyệt đối không ngắt dòng giữa mạo từ và danh từ của nó, giữa tính từ và danh từ đi kèm, giữa họ và tên riêng, hoặc giữa động từ chính và trợ động từ.
- Ưu tiên bố cục hình kim tự tháp (dòng trên ngắn hơn dòng dưới) khi cả hai vị trí ngắt dòng đều tự nhiên ngang nhau.
## Đồng bộ thời gian với lời thoại và cảnh quay (shots)
- Bắt đầu hiển thị thẻ phụ đề ngay khi lời thoại bắt đầu (sớm hơn vài khung hình vẫn ổn), và kết thúc không quá khoảng 1 giây sau khi lời thoại dứt.
- Tránh để một thẻ phụ đề kéo dài vắt ngang qua một cú cắt chuyển cảnh cứng (hard shot change); hãy kết thúc phụ đề trước điểm cắt vài khung hình hoặc bắt đầu hiển thị sau điểm cắt.
## Phụ đề SDH và chú thích âm thanh (captions)
- Các nhãn mô tả âm thanh như [tiếng đóng sầm cửa] / [door slams] hoặc (NHẠC) / (MUSIC) và nhãn định danh người nói thuộc về phụ đề SDH/closed captions, không dùng trong phụ đề dịch thuật tiêu chuẩn.
- Giữ cho các nhãn ngắn gọn, viết chữ thường đặt trong ngoặc vuông là phong cách hiện đại phổ biến nhất, và đặt chúng đúng vào thời điểm âm thanh đó phát ra.
## Định dạng (Formatting)
- Chữ in nghiêng dùng cho giọng nói ngoài khung hình (off-screen), giọng thuyết minh (voice-over), lời bài hát và từ ngữ nước ngoài, nếu quy chuẩn có sử dụng. Mọi thẻ mở định dạng đều bắt buộc phải có thẻ đóng tương ứng ngay trong cùng thẻ phụ đề đó.
- Không đặt dấu chấm câu ở cuối các cụm từ đơn lẻ trên một dòng là một lựa chọn về phong cách; hãy tuân theo quy chuẩn của dự án.
- Định dạng SRT: số thứ tự thẻ phụ đề, dòng mốc thời gian `00:01:02,500 --> 00:01:04,000`, nội dung văn bản, dòng trống. Định dạng VTT: dòng tiêu đề `WEBVTT`, mã định danh cue (tùy chọn), mốc thời gian dùng dấu chấm `00:01:02.500`.
FILE:references/condensing-techniques.md
# Kỹ thuật rút gọn văn bản và căn chỉnh lại thời gian phụ đề
Khi một thẻ phụ đề có tốc độ đọc quá nhanh, hãy thử lần lượt các cách dưới đây theo đúng thứ tự. Dừng lại ngay khi thẻ phụ đề đã nằm trong ngưỡng giới hạn cho phép.
## 1. Tận dụng khoảng thời gian trống trước tiên (không thay đổi văn bản)
- Quan sát khoảng trống thời gian phía trước và phía sau thẻ phụ đề đó. Kéo điểm bắt đầu lùi về sớm hơn tối đa khoảng 0.5 giây nếu không có ai khác đang nói, và kéo điểm kết thúc trễ hơn tối đa khoảng 1 giây nếu thẻ phụ đề tiếp theo bắt đầu muộn hơn.
- Giữ khoảng nghỉ tối thiểu (2 khung hình) với các thẻ phụ đề liền kề, hoặc nối liền nhau ở mức 0 ms.
- Thời gian cần thiết cho một thẻ phụ đề = số ký tự / giới hạn CPS. Ví dụ: 68 ký tự ở mức giới hạn 17 CPS cần 4.0 giây hiển thị.
## 2. Cắt bỏ từ đệm, giữ lại nội dung chính
Loại bỏ những từ mà người xem đằng nào cũng đã nghe thấy hoặc nhìn thấy:
- Các từ ngập ngừng và từ đệm: "à thì" ("well"), "bạn biết đấy" ("you know"), "ý tôi là" ("I mean"), "ờm" ("um"), "vậy nên về cơ bản là" ("so, basically").
- Các từ lặp lại: "rất, rất" ("very, very") -> "rất" ("very"); các câu nói nhịu ở đầu câu.
- Lời chào hỏi và tên gọi đã quá rõ ràng thông qua hình ảnh trên màn hình.
- Câu hỏi đuôi khi giọng điệu đã thể hiện rõ ý hỏi: "It's ready, isn't it?" -> "It's ready?" chỉ áp dụng khi ý nghĩa câu không đổi.
## 3. Sử dụng các cụm từ ngắn gọn hơn
| Cụm từ dài | Cụm từ ngắn |
|---|---|
| we are going to | we'll |
| at this point in time | now |
| in order to | to |
| a large number of | many |
| it is not possible to | you can't |
| I would like to show you | let me show you |
| due to the fact that | because |
Dạng viết tắt rút gọn (contractions) thường hoàn toàn phù hợp trong phụ đề; hãy tuân theo quy chuẩn riêng nếu đó là nội dung trang trọng.
## 4. Đơn giản hóa cấu trúc câu
- Chuyển câu bị động thành câu chủ động: "Chiếc bát được làm sạch bởi người nghệ nhân" ("The bowl was cleaned by the artist") -> "Người nghệ nhân làm sạch chiếc bát" ("The artist cleans the bowl").
- Thay thế cả một mệnh đề dài bằng một từ đơn lẻ khi có thể.
- Tách một câu dài thành hai thẻ phụ đề ngắn hơn tại một nhịp nghỉ tự nhiên.
## 5. Tách hoặc gộp các thẻ phụ đề (cues)
- Tách một thẻ phụ đề kéo dài hơn khoảng 7 giây, hoặc có tới dòng thứ 3, tại một nhịp nghỉ hoặc dấu câu; phân bổ thời lượng cho mỗi nửa tỷ lệ thuận với số lượng ký tự của nó.
- Gộp một thẻ phụ đề ngắn dưới khoảng 0.8 giây với thẻ phụ đề liền kề khi cùng một người nói và kết quả sau khi gộp vẫn nằm trong các ngưỡng giới hạn cho phép.
## Những gì tuyệt đối không được cắt bỏ
- Tên riêng, con số, số lượng, ngày tháng, thuật ngữ kỹ thuật và các từ phủ định ("không", "chưa bao giờ" / "not", "never").
- Những từ mang điểm nhấn hài hước (punchline), bước ngoặt bất ngờ hoặc cảm xúc chủ đạo của câu thoại.
- Thông tin mà người xem cần nắm để hiểu các phần sau của video.
## Căn chỉnh lại thời gian cho toàn bộ tệp (Retiming)
Nếu tất cả các thẻ phụ đề đều bị sớm hoặc muộn cùng một khoảng thời gian như nhau, hãy dịch chuyển mốc thời gian của toàn bộ tệp thay vì sửa từng thẻ phụ đề riêng lẻ:
```bash
python3 scripts/check_subtitles.py movie.srt --shift 1500 > movie-shifted.srt # 1.5 s later
python3 scripts/check_subtitles.py movie.srt --shift -800 > movie-shifted.srt # 0.8 s earlier
```
Nếu độ lệch tăng dần theo thời gian (đồng bộ ở đầu video nhưng bị trễ ở cuối video), rất có thể tốc độ khung hình (frame rate) đã bị chọn sai (ví dụ: 23.976 so với 25 fps); việc dịch chuyển một khoảng thời gian cố định sẽ không khắc phục được lỗi này. Hãy nêu rõ điều đó và hỏi người dùng tốc độ khung hình chính xác của video.
FILE:templates/subtitle-review.md
# Đánh giá phụ đề: file_name
**Video:** video_title (duration, fps fps)
**Loại phụ đề:** translation / same-language captions / SDH
**Ngưỡng giới hạn áp dụng:** cps ký tự/giây, line ký tự/dòng, tối đa lines dòng, hiển thị từ min_dur đến max_dur giây trên màn hình, khoảng nghỉ tối thiểu gap ms (source_of_limits)
## Tóm tắt
| | Trước khi sửa | Sau khi sửa |
|---|---|---|
| Số thẻ phụ đề (Cues) | | |
| HIGH | | |
| WARN | | |
| INFO | | |
| Trung bình ký tự/giây | | |
Các vấn đề chính tóm gọn trong một hoặc hai câu.
## Chi tiết các thay đổi
| Thẻ (Cue) | Vấn đề | Thời gian cũ | Thời gian mới | Nội dung cũ | Nội dung mới |
|---|---|---|---|---|---|
| | | | | | |
## Chưa khắc phục (cần nghe âm thanh hoặc xem video)
- Thẻ phụ đề (Cue) ..., lý do ...
## Ghi chú dành cho biên tập viên
- Các lựa chọn về phong cách đã áp dụng (chữ in nghiêng, viết tắt rút gọn, nhãn âm thanh).
- Bất kỳ điểm nào cần đối chiếu trực tiếp với hình ảnh (điểm cắt chuyển cảnh, chữ hiển thị sẵn trên màn hình).
## Tệp đã chỉnh sửa hoàn chỉnh
```
(toàn bộ nội dung tệp SRT hoặc VTT đã sửa đặt tại đây, giữ cùng định dạng với tệp đầu vào)
```
FILE:examples/example-workshop-video.md
# Ví dụ: đánh giá phụ đề của một video hướng dẫn ngắn
## Yêu cầu của người dùng
"Đây là phụ đề tiếng Anh cho đoạn video ngắn dài 25 giây về buổi hướng dẫn nghệ thuật kintsugi của chúng tôi. Người xem phản hồi rằng họ không đọc kịp. Bạn có thể kiểm tra và sửa những chỗ chưa ổn giúp tôi được không? Khán giả trưởng thành nói chung, phụ đề thông thường, không phải SDH."
## Đầu vào (trích đoạn của tệp interview.srt)
```
2
00:00:03,250 --> 00:00:04,000
Today we are going to look at how to repair a cracked ceramic bowl with gold.
3
00:00:03,900 --> 00:00:06,500
[HAMMER TAPPING]
First, clean the edges.
5
00:00:07,000 --> 00:00:16,000
<i>This technique is called kintsugi.
```
## Kết quả chạy trình kiểm tra
```
$ python3 scripts/check_subtitles.py interview.srt
interview.srt (SRT): 8 cues, 00:00:01,000 to 00:00:24,000, avg 16.3 chars/s, avg 2.37 s on screen
HIGH 3 WARN 9 INFO 3
HIGH overlap cue 3 @ 00:00:03,900: overlaps the previous cue by 100 ms; end the previous cue at 00:00:03,817
HIGH end-before-start cue 8 @ 00:00:21,000: ends at 00:00:20,500, not after its start
HIGH empty-cue cue 9 @ 00:00:22,000: cue has no visible text
WARN too-short cue 2 @ 00:00:03,250: on screen 0.75 s (minimum 0.83 s)
WARN reading-speed cue 2 @ 00:00:03,250: 102.7 chars/s (limit 17); needs 4.53 s on screen or about 64 fewer chars
...
```
## Bản đánh giá (bản mẫu đã điền, rút gọn)
**Ngưỡng giới hạn áp dụng:** 17 ký tự/giây, 42 ký tự/dòng, tối đa 2 dòng, 0.833 đến 7 giây, khoảng nghỉ 83 ms (các giá trị tiêu chuẩn phổ biến cho khán giả đại chúng; khách hàng không cung cấp quy chuẩn riêng).
| | Trước khi sửa | Sau khi sửa |
|---|---|---|
| Số thẻ phụ đề (Cues) | 8 | 8 |
| HIGH | 3 | 0 |
| WARN | 9 | 0 |
| INFO | 3 | 0 |
Các vấn đề chính: thẻ phụ đề số 2 chứa cả một câu dài nhưng chỉ hiển thị chưa đầy một giây, hai thẻ phụ đề có mốc thời gian bị lỗi, một thẻ phụ đề dài 9 giây hiển thị quá lâu trên màn hình, và thẻ phụ đề số 7 có tới ba dòng dài. Tất cả đã được khắc phục bằng cách rút gọn từ đệm, tận dụng khoảng im lặng sau lời thoại và tách nhỏ một thẻ phụ đề.
| Thẻ (Cue) | Vấn đề | Thời gian cũ | Thời gian mới | Nội dung cũ | Nội dung mới |
|---|---|---|---|---|---|
| 2 | 102.7 ký tự/giây, dòng 77 ký tự, chồng lấp với thẻ 3 | 03,250-04,000 | 03,300-06,600 | Today we are going to look at how to repair a cracked ceramic bowl with gold. | Today we'll repair a cracked bowl / with lacquer and gold. |
| 3 | chứa nhãn âm thanh trong tệp không phải SDH, chồng lấp | 03,900-06,500 | 06,700-08,200 | [HAMMER TAPPING] / First, clean the edges. | First, clean the edges. |
| 4 (cũ là 5) | hiển thị 9 giây, thẻ in nghiêng chưa đóng, nhảy cóc số thứ tự | 07,000-16,000 | 08,300-11,000 | `<i>`This technique is called kintsugi. | `<i>`This technique is called kintsugi.`</i>` |
| 5 (cũ là 6) | hiển thị 0.4 giây trên màn hình | 16,000-16,400 | 16,000-16,900 | Okay. | Okay. |
| 6 và 7 (cũ là 7) | 56 ký tự/giây, 3 dòng, dòng 67 ký tự | 17,000-19,000 | 17,000-21,000 và 21,100-22,800 | You will need lacquer, a fine brush, and gold powder, and patience, / lots of patience, / because each layer must dry. | You'll need lacquer, a fine brush, / gold powder, and lots of patience. + Each layer must dry first. |
| 8 (cũ là 8) | thời điểm kết thúc đứng trước bắt đầu | 21,000-20,500 | 22,900-24,000 | Let's start. | Let's start. |
| 9 | thẻ phụ đề trống | 22,000-24,000 | đã xóa | | |
Lưu ý: thẻ phụ đề số 2 cũ được rút gọn ("look at how to", "ceramic") chỉ ở những chi tiết mà hình ảnh đã thể hiện rõ; từ "lacquer" (sơn mài) được bổ sung vì người nói nhắc đến nó vài giây sau đó và người xem cần thông tin này.
**Chưa khắc phục:** không có, nhưng vui lòng đối chiếu lại với video để xác nhận rằng người nói có ngừng nghỉ sau từ "gold" (thẻ phụ đề số 2 hiện kéo dài thêm 2.6 giây, lấn vào khoảng có vẻ là im lặng) và câu "Let's start." được nói vào khoảng giây thứ 22.9.
Kiểm tra lại tệp đã chỉnh sửa:
```
$ python3 scripts/check_subtitles.py interview-fixed.srt
interview-fixed.srt (SRT): 8 cues, 00:00:01,000 to 00:00:24,000, avg 14.5 chars/s, avg 2.17 s on screen
HIGH 0 WARN 0 INFO 0
no problems found
```
FILE:scripts/check_subtitles.py
#!/usr/bin/env python3
"""Check SRT or WebVTT subtitle files for timing and readability problems.
Usage:
python3 check_subtitles.py FILE [FILE ...] [options]
python3 check_subtitles.py - < movie.srt (read stdin)
Options:
--max-cps N maximum characters per second (default 17)
--max-line N maximum characters per line (default 42)
--max-lines N maximum lines per cue (default 2)
--min-dur S minimum cue duration in seconds (default 0.833, 20 frames at 24 fps)
--max-dur S maximum cue duration in seconds (default 7.0)
--min-gap MS minimum gap between cues in milliseconds (default 83, 2 frames at 24 fps)
--shift MS print the file with every timestamp shifted by MS (may be negative) and exit
--json print findings as JSON
--fail-on LEVEL exit 1 if any finding is at least LEVEL: high (default), warn, info
Findings: HIGH = broken or overlapping timing, unreadable numbering, empty cue;
WARN = reading speed, line length, too many lines, too short or long on screen;
INFO = tiny gaps, unbalanced tags, hearing-impaired brackets, statistics.
Exit codes: 0 ok, 1 findings at or above --fail-on, 2 usage or parse error.
Python 3 standard library only.
"""
import json
import math
import re
import sys
TS = re.compile(r"^\s*(\d{1,2}:)?(\d{1,2}):(\d{2})[,.](\d{1,3})\s*-->\s*(\d{1,2}:)?(\d{1,2}):(\d{2})[,.](\d{1,3})(.*)$")
TAG = re.compile(r"</?[^>]+>|\{\\[^}]*\}")
LEVELS = {"info": 0, "warn": 1, "high": 2}
def to_ms(h, m, s, ms):
h = int(h[:-1]) if h else 0
return ((h * 60 + int(m)) * 60 + int(s)) * 1000 + int(ms.ljust(3, "0"))
def fmt(ms, vtt=False):
sign = "-" if ms < 0 else ""
ms = abs(ms)
h, rem = divmod(ms, 3600000)
m, rem = divmod(rem, 60000)
s, rem = divmod(rem, 1000)
sep = "." if vtt else ","
return f"{sign}{h:02d}:{m:02d}:{s:02d}{sep}{rem:03d}"
def parse(text):
"""Return (cues, errors, is_vtt). Each cue: dict(index, start, end, lines, line_no)."""
text = text.replace("\r\n", "\n").replace("\r", "\n").lstrip("\ufeff")
is_vtt = text.lstrip().startswith("WEBVTT")
blocks = re.split(r"\n\s*\n", text.strip("\n"))
cues, errors = [], []
line_no = 1
for block in blocks:
lines = block.split("\n")
start_line = line_no
line_no += len(lines) + 1
if is_vtt and (lines[0].startswith("WEBVTT") or lines[0].startswith("NOTE") or lines[0].startswith("STYLE") or lines[0].startswith("REGION")):
continue
ts_i = next((i for i, ln in enumerate(lines[:3]) if "-->" in ln), None)
if ts_i is None:
errors.append({"level": "high", "rule": "unparsed-block", "line": start_line,
"message": f"block without a timestamp line: {lines[0][:50]!r}"})
continue
m = TS.match(lines[ts_i])
if not m:
errors.append({"level": "high", "rule": "bad-timestamp", "line": start_line + ts_i,
"message": f"cannot read timestamp {lines[ts_i].strip()!r} (expected 00:01:02,500 --> 00:01:04,000)"})
continue
g = m.groups()
index = lines[0].strip() if ts_i == 1 else None
cues.append({"index": index, "start": to_ms(*g[0:4]), "end": to_ms(*g[4:8]),
"lines": [ln for ln in lines[ts_i + 1:]], "line_no": start_line})
return cues, errors, is_vtt
def visible(line):
return TAG.sub("", line).strip()
def check(cues, errors, opts, is_vtt):
out = list(errors)
def add(level, rule, cue, msg):
out.append({"level": level, "rule": rule, "line": cue["line_no"],
"cue": cue["index"] or "-", "time": fmt(cue["start"], is_vtt), "message": msg})
if not is_vtt:
expected = 1
for c in cues:
if c["index"] is None or not c["index"].isdigit():
add("high", "bad-number", c, f"cue number missing or not a number: {c['index']!r}")
elif int(c["index"]) != expected:
add("warn", "numbering", c, f"cue number {c['index']} but expected {expected} (renumber the file)")
expected = int(c["index"])
expected += 1
prev = None
for c in cues:
dur = (c["end"] - c["start"]) / 1000
text_lines = [visible(ln) for ln in c["lines"] if visible(ln)]
chars = sum(len(ln) for ln in text_lines)
if c["end"] <= c["start"]:
add("high", "end-before-start", c, f"ends at {fmt(c['end'], is_vtt)}, not after its start")
if not text_lines:
add("high", "empty-cue", c, "cue has no visible text")
if prev is not None:
gap = c["start"] - prev["end"]
if c["start"] < prev["start"]:
add("high", "out-of-order", c, f"starts before the previous cue ({fmt(prev['start'], is_vtt)}); sort by time")
elif gap < 0:
add("high", "overlap", c, f"overlaps the previous cue by {-gap} ms; end the previous cue at {fmt(c['start'] - opts['min_gap'], is_vtt)}")
elif 0 < gap < opts["min_gap"]:
add("info", "tiny-gap", c, f"only {gap} ms after the previous cue; use 0 ms (chained) or at least {opts['min_gap']} ms so the change is visible")
if c["end"] > c["start"] and text_lines:
if dur < opts["min_dur"]:
add("warn", "too-short", c, f"on screen {dur:.2f} s (minimum {opts['min_dur']:.2f} s)")
if dur > opts["max_dur"]:
add("warn", "too-long", c, f"on screen {dur:.2f} s (maximum {opts['max_dur']:.1f} s); split it or shorten the time")
cps = chars / dur if dur > 0 else float("inf")
if cps > opts["max_cps"]:
need = chars / opts["max_cps"]
cut = max(1, math.ceil(chars - opts["max_cps"] * dur))
add("warn", "reading-speed", c, f"{cps:.1f} chars/s (limit {opts['max_cps']:g}); needs {need:.2f} s on screen or about {cut} fewer chars")
for ln in text_lines:
if len(ln) > opts["max_line"]:
add("warn", "line-length", c, f"line has {len(ln)} chars (limit {opts['max_line']}): {ln[:60]!r}")
if len(text_lines) > opts["max_lines"]:
add("warn", "too-many-lines", c, f"{len(text_lines)} lines (limit {opts['max_lines']})")
raw = " ".join(c["lines"])
for tag in ("i", "b", "u"):
if len(re.findall(rf"<{tag}>", raw, re.I)) != len(re.findall(rf"</{tag}>", raw, re.I)):
add("info", "unbalanced-tag", c, f"<{tag}> tags are not balanced in this cue")
if re.search(r"\[[^\]]+\]|\([A-Z][A-Z ]+\)", raw):
add("info", "sdh-label", c, "contains a sound or speaker label in brackets; keep it only in SDH/CC files")
prev = c
return out
def stats(cues, is_vtt=False):
if not cues:
return {}
total_chars = sum(len(visible(ln)) for c in cues for ln in c["lines"])
total_time = sum(max(c["end"] - c["start"], 0) for c in cues) / 1000
return {"cues": len(cues), "first": fmt(cues[0]["start"], is_vtt), "last_end": fmt(max(c["end"] for c in cues), is_vtt),
"avg_cps": round(total_chars / total_time, 1) if total_time else None,
"avg_duration_s": round(total_time / len(cues), 2)}
def shift_text(text, delta):
def rep(m):
g = m.groups()
vtt = "." in m.group(0).split("-->")[0]
a = max(to_ms(*g[0:4]) + delta, 0)
b = max(to_ms(*g[4:8]) + delta, 0)
return f"{fmt(a, vtt)} --> {fmt(b, vtt)}{g[8]}"
return "\n".join(TS.sub(rep, ln) if "-->" in ln else ln
for ln in text.replace("\r\n", "\n").split("\n"))
def main(argv):
opts = {"max_cps": 17.0, "max_line": 42, "max_lines": 2, "min_dur": 0.833, "max_dur": 7.0,
"min_gap": 83, "json": False, "fail_on": "high", "shift": None}
files = []
it = iter(argv)
try:
for a in it:
if a == "--json":
opts["json"] = True
elif a in ("--max-cps", "--min-dur", "--max-dur"):
opts[a[2:].replace("-", "_")] = float(next(it))
elif a in ("--max-line", "--max-lines", "--min-gap", "--shift"):
opts[a[2:].replace("-", "_")] = int(next(it))
elif a == "--fail-on":
opts["fail_on"] = next(it).lower()
if opts["fail_on"] not in LEVELS:
raise ValueError("--fail-on must be high, warn, or info")
elif a in ("-h", "--help"):
print(__doc__)
return 0
elif a.startswith("--"):
raise ValueError(f"unknown option {a}")
else:
files.append(a)
except (StopIteration, ValueError) as e:
print(f"error: {e or 'option needs a value'}", file=sys.stderr)
return 2
if not files:
print("error: give at least one .srt or .vtt file, or - for stdin", file=sys.stderr)
return 2
report, worst = [], -1
for f in files:
try:
text = sys.stdin.read() if f == "-" else open(f, encoding="utf-8-sig").read()
except (OSError, UnicodeDecodeError) as e:
print(f"error: cannot read {f}: {e}", file=sys.stderr)
return 2
if opts["shift"] is not None:
sys.stdout.write(shift_text(text, opts["shift"]))
return 0
cues, errors, is_vtt = parse(text)
if not cues:
print(f"error: {f}: no subtitle cues found (is this an SRT or WebVTT file?)", file=sys.stderr)
return 2
findings = check(cues, errors, opts, is_vtt)
for x in findings:
worst = max(worst, LEVELS[x["level"]])
report.append({"file": f, "format": "vtt" if is_vtt else "srt", "stats": stats(cues, is_vtt), "findings": findings})
if opts["json"]:
print(json.dumps(report, indent=2))
else:
for r in report:
counts = {lv: sum(1 for x in r["findings"] if x["level"] == lv) for lv in ("high", "warn", "info")}
st = r["stats"]
print(f"{r['file']} ({r['format'].upper()}): {st['cues']} cues, {st['first']} to {st['last_end']}, "
f"avg {st['avg_cps']} chars/s, avg {st['avg_duration_s']} s on screen")
print(f" HIGH {counts['high']} WARN {counts['warn']} INFO {counts['info']}")
for x in sorted(r["findings"], key=lambda x: (-LEVELS[x["level"]], x["line"])):
where = f"cue {x.get('cue', '-')} @ {x['time']}" if "time" in x else f"line {x['line']}"
print(f" {x['level'].upper():<4} {x['rule']:<16} {where}: {x['message']}")
if not r["findings"]:
print(" no problems found")
return 1 if worst >= LEVELS[opts["fail_on"]] else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))Một bộ phim ngắn kinh dị Halloween bốn cảnh chân thực như ảnh chụp: một cô gái trẻ chạy trốn mụ phù thủy ở trạm xe buýt đầy sương mù và tìm thấy nơi trú ẩn у nhà một cụ bà tốt bụng trong căn biệt thự kiến trúc Victoria lung linh ánh nến, cho đến khi bức chân dung 40 năm trước với cùng chiếc mặt dây chuyền bằng bạc hé lộ cú ngoặt rùng rợn, đi kèm thiết kế âm thanh, quy tắc đồng nhất nhân vật và negative prompt. Thịnh hành trên X, được chia sẻ gốc bởi @Romi2656: https://x.com/Romi2656/status/2114718376107098143
CẢNH 1 — TRẠM XE BUÝT (0–10 giây) Vào đêm Halloween ngày 31 tháng 10, một cô gái trẻ đứng một mình tại trạm xe buýt vắng vẻ bao quanh bởi sương mù dày đặc, những hàng cây khô trụi lá và những quả bí ngô Halloween màu cam lập lòe. Đột nhiên, một mụ phù thủy đáng sợ xuất hiện phía sau cô, với mái tóc xám rối bù, đôi mắt phát sáng và một chiếc mặt dây chuyền có nắp mở (locket) bằng bạc cổ đeo quanh cổ. Mụ phù thủy thì thào: “Ngươi không nên ở một mình vào đêm Halloween đâu!” Cô gái quay lại, hét lên thất thanh và chạy bán sống bán chết vào làn sương mù. CẢNH 2 — BIỆT THỰ CỔ (10–23 giây) Cô gái phát hiện ra một căn biệt thự cổ kiến trúc Victoria và gõ cửa. Một cụ bà mở cửa với thái độ niềm nở ấm áp và mời cô vào trong. Căn phòng thắp nến ấm cúng với đồ trang trí Halloween, những quả bí ngô chạm khắc và một lò sưởi cổ. Cụ bà dọn cho cô súp nóng và bánh mì, dịu dàng nói: “Vào đi cháu. Đêm nay cháu an toàn ở đây rồi.” Cô gái đang hoảng sợ bắt đầu thả lỏng khi tiếng sấm rền vang bên ngoài. CẢNH 3 — PHÁT HIỆN GÂY SỐC (23–38 giây) Trong lúc đang ăn, cô gái để ý thấy một bức chân dung cổ treo trên tường. Bức tranh vẽ một người phụ nữ trẻ xinh đẹp đang đeo chiếc mặt dây chuyền bạc giống hệt của mụ phù thủy đáng sợ ở trạm xe buýt. Cô từ từ tiến lại gần bức chân dung, nhìn chằm chằm vào khuôn mặt quen thuộc và sợi dây chuyền giống hệt nhau. Bên dưới bức tranh có dòng chữ khắc: “31 THÁNG 10 — 40 NĂM TRƯỚC.” Mặt cô tái nhợt đi. Cô chợt nhận ra người phụ nữ trong bức chân dung chính là mụ phù thủy mà cô đã chạm trán bên ngoài. Cô từ từ quay đầu lại nhìn cụ bà đang đứng phía sau mình. CẢNH 4 — MÀN HÙ DỌA CUỐI CÙNG Cụ bà nhìn chằm chằm vào cô với một nụ cười nham hiểm, khác thường. Đôi mắt bà ta đột nhiên rực sáng màu vàng, khuôn mặt biến dạng thành khuôn mặt của mụ phù thủy kinh hoàng, rồi bà ta phá lên cười điên dại, lạnh sống lưng. Máy quay lao nhanh về phía khuôn mặt ghê rợn của mụ phù thủy đúng lúc tia chớp lóe sáng qua khung cửa sổ. CẮT ĐỘT NGỘT SANG MÀN HÌNH ĐEN (SMASH CUT TO BLACK). Tiếng cười độc ác của mụ phù thủy vang vọng một lần trong bóng tối. KẾT THÚC. PHONG CÁCH HÌNH ẢNH: Phim kinh dị người đóng (live-action) chân thực như ảnh chụp, biệt thự Victoria đậm chất điện ảnh, sương mù và mưa chân thực, ánh nến lung linh chập chờn, biểu cảm khuôn mặt chi tiết, hiệu ứng biến hình siêu nhiên thực tế, đổ bóng kịch tính và bầu không khí Halloween căng thẳng tột độ. THIẾT KẾ ÂM THANH: Tiếng gió hú, tiếng sấm rền từ xa, tiếng bước chân, tiếng cửa kẽo kẹt, tiếng thì thầm rợn người, âm thanh biến hình đột ngột và tiếng cười phù thủy kinh hoàng. TÍNH NHẤT QUÁN CỦA NHÂN VẬT: Giữ nguyên cùng một cô gái trẻ, cùng một cụ bà và chiếc mặt dây chuyền bạc giống hệt nhau xuyên suốt từ đầu đến cuối. Ngoại hình của mụ phù thủy phải khớp với người phụ nữ trẻ trong bức chân dung trước khi biến hình. NEGATIVE PROMPT: Không hoạt hình (cartoon), không anime, không kỹ xảo CGI rẻ tiền, không khuôn mặt thiếu nhất quán, không tự ý đổi trang phục, không phụ đề, không logo, không hình mờ (watermark) và không có cảnh nào thêm sau màn hình đen cuối cùng.