Erm: Công cụ CLI tự động loại bỏ các từ đệm 'um', 'uh' trong bản ghi âm
Bài viết giới thiệu `erm`, một công cụ dòng lệnh chạy cục bộ giúp tự động loại bỏ các từ đệm như "um", "uh" khỏi bản ghi âm. Tác giả giải thích các thách thức kỹ thuật trong việc xử lý âm thanh và cách công cụ này sử dụng mô hình Whisper cùng ffmpeg để tạo ra bản ghi sạch sẽ mà không làm mất ngữ cảnh.
Erm: Công cụ CLI tự động loại bỏ các từ đệm 'um', 'uh' trong bản ghi âm
Các nhà ngôn ngữ học có một thuật ngữ riêng cho những tiếng "um", "uh", "er" và các phiên bản kéo dài (ummmm, uhhhhh) thường xuất hiện trong tiếng Anh nói: đó là "disfluencies" (lỗi ngắt quãng). Mặc dù tôi không ghi âm nhiều, nhưng một số người bạn của tôi làm vậy và họ nói rằng việc chỉnh sửa thủ công những tiếng này là một trải nghiệm tồi tệ. Chính vì vậy, tôi đã xây dựng một công cụ tên là erm để giải quyết vấn đề này.
Giao diện của công cụ này cực kỳ đơn giản cho trường hợp sử dụng phổ biến:
uvx erm input.wav
Lệnh này sẽ tạo ra một file .wav đã được làm sạch và một danh sách cắt (cut list) dạng JSON nằm ngay cạnh file đầu vào. Bài viết này sẽ đi sâu vào cách hoạt động của erm, bởi vì phương pháp tiếp cận hiển nhiên thường không mang lại kết quả tốt và phần lớn mã nguồn của công cụ này dành cho việc khắc phục những vấn đề đó.
Phương pháp ngây thơ không hiệu quả
Bạn có thể nghĩ rằng công việc này rất đơn giản: chuyển đổi giọng nói thành văn bản với dấu thời gian (timestamp) cấp từ, tìm các token như "um" và "uh", sau đó cắt các đoạn đó bằng ffmpeg. Tuy nhiên, cách này chỉ giải quyết được khoảng 60% vấn đề và kết quả nghe còn tệ hơn bản gốc vì ba lý do chính:
- Whisper tự động lọc bỏ: Mô hình Whisper thường bỏ qua nhiều từ đệm trong bản chuyển đổi, nên đôi khi không có token "um" nào để tìm kiếm cả.
- Tiếng clicks khi cắt: Cắt âm thanh tại một thời điểm bất kỳ sẽ tạo ra một bước nhảy nhỏ trong dạng sóng (waveform). Tai bạn sẽ nghe thấy nó dưới dạng một tiếng "click".
- Sự không khớp của tiếng ồn nền: Ngay cả khi mối nối sạch sẽ, tiếng xì xèo (hiss) của nền trước và sau điểm cắt không khớp nhau, khiến bạn nghe thấy một sự thay đổi nhỏ ở mỗi điểm chỉnh sửa.
Đa số mã nguồn của erm được viết để khắc phục ba vấn đề trên.
Về Whisper
Whisper là mô hình chuyển đổi giọng nói thành văn bản mã nguồn mở của OpenAI. Bạn đưa âm thanh vào, nó trả về bản chuyển đổi, và với cờ đúng, nó sẽ cung cấp dấu thời gian bắt đầu và kết thúc của từng từ. Điều quan trọng là nó chạy cục bộ, cho phép một công cụ như thế này hoạt động mà không cần gửi bản ghi âm của bạn đi đâu cả.
erm sử dụng faster-whisper, một bản triển khai lại nhanh hơn gấp nhiều lần so với bản gốc và sử dụng ít bộ nhớ hơn. Các trọng số mô hình và đầu ra giống hệt nhau, chỉ là thời gian chạy tốt hơn. Mặc định là mô hình medium.en, cân bằng tốt giữa tốc độ và độ chính xác. Bạn có thể dùng --model để chọn small.en (nhanh hơn), nhưng tôi khuyên dùng large-v3 vì nó phát hiện từ đệm tốt hơn đáng kể và xứng đáng với tài nguyên tính toán thêm.
Phát hiện từ đệm
Đầu tiên, erm chạy Whisper. Công cụ yêu cầu dấu thời gian cấp từ và đưa ra một hướng dẫn nhỏ để Whisper không tự động làm sạch bản chuyển đổi. Nếu để mặc, Whisper sẽ loại bỏ từ đệm vì hầu hết dữ liệu huấn luyện của nó là văn xuôi sạch. Bất kỳ từ nào được nhận diện là từ đệm (um, uh, er, v.v.) sẽ được đánh dấu để cắt. Các phiên bản kéo dài như "ummmm" sẽ được so khớp với gốc "um" ngay lập tức.
Tuy nhiên, Whisper vẫn bỏ sót nhiều thứ, nên có thêm ba vòng quét khác nhìn trực tiếp vào âm thanh:
- Từ đệm trong khoảng trống: Nếu có khoảng nghỉ bất thường giữa hai từ (hơn 350ms mặc định), erm kiểm tra xem thực sự có âm thanh nào trong "khoảng nghỉ" đó không. Nếu một đoạn giọng nằm trong vùng im lặng mà Whisper đánh dấu, đó là từ đệm mà Whisper đã xóa hoàn toàn.
- Từ đệm ẩn trong từ: Đôi khi Whisper dán từ đệm vào từ liền kề, ví dụ "in, uhhhhh" trở thành một token "in" duy nhất. erm sẽ xem xét các từ dài, tách chúng tại các điểm giảm âm lượng ngắn, xác định phần nào là từ thực (dựa trên thời gian phát âm hợp lý) và coi phần còn lại là từ đệm.
- Từ quá dài: Nếu một từ kéo dài lâu hơn nhiều so với văn bản có thể phát âm, phần đuôi đáng ngờ. erm quét đuôi để tìm âm thanh có thanh nhạc và kiểm tra thêm bằng cao độ: đoạn đáng ngờ đó nghe giống như người ta giữ nguyên một nguyên âm (uhhhhh) hay chỉ đang nói chậm? Nguyên âm giữ nguyên có hình dạng âm học ổn định, trong khi lời nói thực sự luôn thay đổi khi chuyển đổi giữa các âm. Kiểm tra cao độ giúp công cụ không cắt nhầm những người nói chậm.
Bốn vòng quét này tạo ra danh sách các ứng viên cắt độc lập và được hợp nhất trước khi bước tiếp theo.
Tinh chỉnh điểm cắt
Một đường cắt tại chính xác t = 1.234s sẽ rơi vào vị trí bất kỳ trên dạng sóng, hiếm khi tại điểm 0. Ghép hai điểm bất kỳ lại với nhau sẽ để lại một bước nhảy trong dạng sóng, và bước nhảy đó chính là tiếng "click" bạn nghe thấy.
Có hai sửa nhỏ theo thứ tự. Thứ nhất, mỗi điểm cắt cuối được phép trượt một chút (tối đa 60ms) để rơi vào vị trí yên tĩnh nhất gần đó. Nếu có một khoảng lặng ngay trước hoặc sau điểm cắt gốc, nó sẽ trượt đến đó. Việc trượt bị giới hạn để không vượt sang từ bên cạnh, nếu không bạn sẽ cắt mất lời nói thực. Thứ hai, từ vị trí yên tĩnh đó, điểm cuối sẽ "snap" (dính) vào thời điểm gần nhất khi dạng sóng cắt qua chính xác điểm 0. Hai điểm 0 được ghép lại tạo thành dạng sóng liên tục không có bước nhảy và không có tiếng click.
Sau tất cả, các mảnh tồn tại quá ngắn sẽ được làm sạch: nếu hai đường cắt kề nhau để lại một mảnh âm thanh ngắn hơn khoảng 120ms, mảnh đó sẽ được gộp thành một đường cắt lớn hơn. Một mảnh nhỏ như vậy không thể tồn tại qua việc làm mịn hai bên và chỉ nghe như một tiếng "blip".
Ghép nối âm thanh
ffmpeg thực hiện việc ghép nối thực tế bằng cách sử dụng crossfade (chuyển tiếp chéo). Thay vì đặt hai đoạn âm thanh cạnh nhau, nó chồng chúng lên nhau một chút và làm mờ đoạn này ra trong khi đoạn khác fade in. Điều này làm mượt bất kỳ sự không khớp còn lại.
Mẹo ở đây là chọn độ dài chồng nhau. Một độ dài cố định (hầu hết hướng dẫn nói 80ms) nghe sai ở cả hai phía: các đoạn cắt ngắn bị nhòe vào nhau, các đoạn cắt dài vẫn bị nổ tiếng. erm điều chỉnh độ dài dựa trên kích thước của đường cắt: một đoạn ngắn "uh" sẽ có crossfade ngắn, một đoạn dài "ummmmm" sẽ có crossfade dài hơn. Có giới hạn sàn và trần (50ms đến 120ms), và crossfade không bao giờ được phép vươn lại qua đầu của một từ thực, điều này sẽ làm lời nói hai bên bị mờ.
Tông nền phòng (Room Tone)
Ngay cả sau tất cả các bước trên, tiếng xì xèo nền của bản ghi (âm thanh môi trường của phòng khi không ai nói) không khớp hoàn toàn qua các điểm cắt. Mỗi phòng có một dạng "sự im lặng" hơi khác nhau, và ghép hai đoạn gần im lặng vẫn tạo ra một sự thay đổi nhỏ bạn có thể nghe thấy.
Cách khắc phục thì ngốc nhưng hiệu quả. Tìm một đoạn yên tĩnh trong bản ghi gốc (một đoạn thực sự là "phòng này khi không ai nói chuyện") và lặp nó bên dưới toàn bộ đầu ra với âm lượng thấp. Bây giờ nền giống hệt nhau ở mọi nơi, vì đó là cùng một vòng lặp ở khắp nơi. Bất kỳ sự không khớp nhỏ nào tại mỗi điểm nối đều được che phủ bởi âm thanh ổn định nằm ở trên.
Mặc định, đoạn yên tĩnh được tìm tự động. Bạn cũng có thể chỉ định một khoảng thời gian cụ thể nếu biết một đoạn tốt.
Bộ khử nhiễu tinh vi
ffmpeg có sẵn bộ giảm nhiễu, và bạn có thể chạy nó trên âm thanh tại các điểm khác nhau trong quy trình. Tuy nhiên, việc giảm nhiễu sẽ làm mờ các chi tiết (sự thay đổi âm lượng và rung cao độ) mà các bộ phát hiện dựa vào để tìm từ đệm. Vì vậy, thời điểm thực hiện nó rất quan trọng.
erm có bốn chế độ:
- none: Phát hiện nhìn vào bản gốc, đầu ra cắt từ bản gốc.
- pre: Phát hiện nhìn vào bản đã giảm nhiễu, đầu ra cắt từ bản đã giảm nhiễu.
- post: Phát hiện nhìn vào bản gốc, đầu ra cắt từ bản gốc; giảm nhiễu ở cuối.
- hybrid: Phát hiện nhìn vào bản gốc, đầu ra cắt từ bản giảm nhiễu.
hybrid là mặc định và chế độ bạn nên dùng: phát hiện chạy trên âm thanh gốc (để nó thấy tất cả các manh mối), nhưng các đường cắt thực tế đến từ bản sao sạch, đã giảm nhiễu (để các mối nối nghe hay).
Chế độ pre có vẻ hợp lý nhưng là lựa chọn tồi nhất, vì chạy bộ phát hiện trên âm thanh đã giảm nhiễu sẽ che giấu chính những thứ chúng đang tìm kiếm.
Xác thực
Kết xuất âm thanh có thể bị hỏng một cách tinh tế, nên có một lệnh con validate:
uvx erm validate input.wav cleaned.wav --cuts cuts.json
Nó chạy ba kiểm tra:
- File đầu ra thực sự mở được.
- Đầu ra ngắn hơn đầu vào một khoảng bằng tổng độ dài của các đường cắt (trong một biên độ nhỏ).
- Khi bạn chuyển đổi file đã làm sạch trở lại thành văn bản, không có từ đệm nào xuất hiện.
Kiểm tra cuối cùng là cái hữu ích nhất. Nó là end-to-end: nó cho bạn biết công cụ thực sự đã làm được những gì nó tuyên bố.
Những gì nó không chạm vào
Nó sẽ để lại "like", "you know", và "I mean" yên thân. Những từ này nghe như từ đệm nhưng chúng đóng vai trò thực sự trong câu, và cắt chúng tự động sẽ thay đổi ý người nói. Quy tắc của erm: chỉ loại bỏ những thứ là âm thanh, không phải ngôn ngữ.
Nó cũng không chạm vào các từ lặp lại, khởi đầu sai (false starts), hoặc khoảng nghỉ suy nghĩ dài. Những thứ đó không phải là tiếng ồn trên lời nói; chúng là lời nói, chỉ lộn xộn hơn người nói mong muốn. Việc làm sạch chúng là một quyết định biên tập về việc giữ lại bản nào, và erm không có ý kiến về điều đó.
Hãy thử nó
Cách nhanh nhất là dùng uv, công cụ này tìm và chạy công cụ trong một bước mà không cần cài đặt vĩnh viễn:
uvx erm input.wav --dry-run # xem những gì sẽ bị cắt
uvx erm input.wav # kết xuất
Nếu bạn muốn cài đặt theo cách thông thường:
pip install erm # hoặc: pipx install erm
erm input.wav
Bạn cũng cần ffmpeg và ffprobe trên PATH của mình (brew install ffmpeg trên macOS).
Truy cập github.com/dougcalobrisi/erm để biết thêm chi tiết. Âm thanh giữ nguyên cục bộ. Nếu bạn ghi chú giọng nói hoặc podcast và nửa số bạn nói là "um", hãy thử xem.
