@@ -258,6 +258,25 @@ pub(crate) fn check_file_size(len: u64) -> Result<(), String> {
258258 Ok ( ( ) )
259259}
260260
261+ /// Decode raw file bytes into a UTF-8 `String`, detecting the source encoding.
262+ ///
263+ /// A leading BOM (UTF-8 / UTF-16) is honoured first; otherwise the charset is
264+ /// guessed with `chardetng` (the detector Firefox uses), covering common
265+ /// subtitle encodings such as Big5, GBK/GB18030, Shift_JIS and EUC. Returns the
266+ /// decoded text along with the detected encoding's canonical name.
267+ pub ( crate ) fn decode_text ( bytes : & [ u8 ] ) -> ( String , & ' static str ) {
268+ if let Some ( ( enc, _) ) = encoding_rs:: Encoding :: for_bom ( bytes) {
269+ let ( text, _, _) = enc. decode ( bytes) ;
270+ return ( text. into_owned ( ) , enc. name ( ) ) ;
271+ }
272+
273+ let mut detector = chardetng:: EncodingDetector :: new ( ) ;
274+ detector. feed ( bytes, true ) ;
275+ let enc = detector. guess ( None , true ) ;
276+ let ( text, _, _) = enc. decode ( bytes) ;
277+ ( text. into_owned ( ) , enc. name ( ) )
278+ }
279+
261280pub ( crate ) fn build_service_info ( info : ServiceInfoResponse ) -> Result < ServiceInfo , String > {
262281 if info. code != 0 {
263282 return Err ( format ! ( "SERVICE_INFO_FAILED:{}" , info. code) ) ;
@@ -318,6 +337,54 @@ mod tests {
318337 assert_eq ! ( sanitize_filename_part( "a!@#b$%^c" ) , "abc" ) ;
319338 }
320339
340+ // --- decode_text ---
341+
342+ #[ test]
343+ fn decode_plain_utf8 ( ) {
344+ let ( text, _) = decode_text ( "繁化姬" . as_bytes ( ) ) ;
345+ assert_eq ! ( text, "繁化姬" ) ;
346+ }
347+
348+ #[ test]
349+ fn decode_utf8_with_bom ( ) {
350+ let mut bytes = vec ! [ 0xEF , 0xBB , 0xBF ] ;
351+ bytes. extend_from_slice ( "字幕" . as_bytes ( ) ) ;
352+ let ( text, _) = decode_text ( & bytes) ;
353+ assert_eq ! ( text, "字幕" ) ;
354+ }
355+
356+ #[ test]
357+ fn decode_utf16le_with_bom ( ) {
358+ let mut bytes = vec ! [ 0xFF , 0xFE ] ;
359+ for unit in "字幕" . encode_utf16 ( ) {
360+ bytes. extend_from_slice ( & unit. to_le_bytes ( ) ) ;
361+ }
362+ let ( text, _) = decode_text ( & bytes) ;
363+ assert_eq ! ( text, "字幕" ) ;
364+ }
365+
366+ #[ test]
367+ fn decode_big5 ( ) {
368+ let ( bytes, _, had_errors) = encoding_rs:: BIG5 . encode ( "台灣繁體字幕測試內容夠長以利偵測" ) ;
369+ assert ! ( !had_errors) ;
370+ let ( text, _) = decode_text ( & bytes) ;
371+ assert_eq ! ( text, "台灣繁體字幕測試內容夠長以利偵測" ) ;
372+ }
373+
374+ #[ test]
375+ fn decode_gbk ( ) {
376+ let ( bytes, _, had_errors) = encoding_rs:: GBK . encode ( "简体中文字幕测试内容够长以利侦测" ) ;
377+ assert ! ( !had_errors) ;
378+ let ( text, _) = decode_text ( & bytes) ;
379+ assert_eq ! ( text, "简体中文字幕测试内容够长以利侦测" ) ;
380+ }
381+
382+ #[ test]
383+ fn decode_empty_is_empty ( ) {
384+ let ( text, _) = decode_text ( & [ ] ) ;
385+ assert_eq ! ( text, "" ) ;
386+ }
387+
321388 // --- build_output_name ---
322389
323390 #[ test]
0 commit comments