WIP 81 보충 (15 지적 · 관리자 판단): 빠른 보기의 글 미리 보기가 CP949 · UTF-16 · UTF-8 BOM 도 읽게 (검토 대기)
- 예전: UTF-8 만 보고 깨진 글자가 있으면 "미리 볼 수 없음" - 한국어 .smi (대부분 CP949) · 메모장의 UTF-16 글이 안 보였다. - 자막과 같은 글자 코드 판별 (detectCharset · decodeText) 을 그대로 써서 MKV 화면과 같게 판단한다 (UTF-8 (BOM) · UTF-16 (BOM) · CP949 · Shift-JIS · GBK). - 그래도 깨진 글자가 많거나 (2 + 글자 수의 1% 넘음) 글이 아니면 (NUL) "미리 볼 수 없음 (글자 코드를 알 수 없습니다)". 64KB 끝에서 잘린 글자는 빼고 본다. - 시험: CP949 .smi · UTF-16 (BOM) .srt · UTF-8 BOM .txt · 글이 아닌 파일. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>묶음6
parent
39dab23138
commit
58380e4ae0
|
|
@ -1085,6 +1085,7 @@
|
|||
"미리 보기": "Quick look",
|
||||
"미리 볼 수 없습니다: {0}": "Cannot preview: {0}",
|
||||
"미리 볼 수 없음 (글자 코드가 UTF-8 이 아닙니다)": "Can't preview (the text isn't UTF-8)",
|
||||
"미리 볼 수 없음 (글자 코드를 알 수 없습니다)": "Can't preview (unknown text encoding)",
|
||||
"미리보기: 목록에서 동영상을 고르세요": "Preview: choose a video in the list",
|
||||
"미얀마어": "Burmese",
|
||||
"미지정": "Unspecified",
|
||||
|
|
|
|||
|
|
@ -1085,6 +1085,7 @@
|
|||
"미리 보기": "クイックプレビュー",
|
||||
"미리 볼 수 없습니다: {0}": "プレビューできません: {0}",
|
||||
"미리 볼 수 없음 (글자 코드가 UTF-8 이 아닙니다)": "プレビューできません (文字コードが UTF-8 ではありません)",
|
||||
"미리 볼 수 없음 (글자 코드를 알 수 없습니다)": "プレビューできません (文字コードが分かりません)",
|
||||
"미리보기: 목록에서 동영상을 고르세요": "プレビュー: リストで動画を選んでください",
|
||||
"미얀마어": "ミャンマー語",
|
||||
"미지정": "未指定",
|
||||
|
|
|
|||
|
|
@ -1085,6 +1085,7 @@
|
|||
"미리 보기": "快速预览",
|
||||
"미리 볼 수 없습니다: {0}": "无法预览: {0}",
|
||||
"미리 볼 수 없음 (글자 코드가 UTF-8 이 아닙니다)": "无法预览 (文本不是 UTF-8)",
|
||||
"미리 볼 수 없음 (글자 코드를 알 수 없습니다)": "无法预览 (未知的文本编码)",
|
||||
"미리보기: 목록에서 동영상을 고르세요": "预览: 请在列表中选择视频",
|
||||
"미얀마어": "缅甸语",
|
||||
"미지정": "未指定",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../core/charset_detector.dart';
|
||||
import '../core/file_ops.dart';
|
||||
import '../core/text_codec.dart';
|
||||
import '../core/models.dart';
|
||||
import '../core/playlist.dart' show isAudioFile, isVideoFile;
|
||||
import '../core/vfs.dart';
|
||||
|
|
@ -15,16 +16,24 @@ import 'theme.dart';
|
|||
/// 81: 빠른 보기에서 글로 보는 확장자
|
||||
const textExtensions = ['txt', 'srt', 'ass', 'ssa', 'vtt', 'smi', 'log', 'md', 'json', 'xml', 'csv', 'ini', 'nfo'];
|
||||
|
||||
/// 81: 글 파일의 앞 64KB (UTF-8). 글자 코드가 깨졌으면 null - 끝에서 잘린 한 글자는 깨진 것으로 치지 않는다
|
||||
/// 81: 글 파일의 앞 64KB. 81 보충 (15 지적 · 관리자 판단): 자막과 같은 글자 코드 판별 (detectCharset · decodeText) 로
|
||||
/// UTF-8 (BOM) · UTF-16 (BOM) · CP949 · Shift-JIS · GBK 를 읽는다 (MKV 화면과 같은 판단 - 한국어 .smi 는 대부분 CP949).
|
||||
/// 그래도 깨진 글자가 많거나 글이 아니면 (NUL) null - 끝에서 잘린 한 글자는 깨진 것으로 치지 않는다
|
||||
Future<String?> readTextHead(String path, {int limit = 64 * 1024}) async {
|
||||
final bytes = <int>[];
|
||||
await for (final chunk in File(path).openRead(0, limit)) {
|
||||
bytes.addAll(chunk);
|
||||
}
|
||||
final text = const Utf8Decoder(allowMalformed: true).convert(bytes);
|
||||
final cs = detectCharset(Uint8List.fromList(bytes));
|
||||
var text = decodeText(bytes, cs);
|
||||
if (bytes.length >= limit) {
|
||||
while (text.endsWith('<EFBFBD>')) {
|
||||
text = text.substring(0, text.length - 1);
|
||||
}
|
||||
}
|
||||
final bad = '<EFBFBD>'.allMatches(text).length;
|
||||
final cut = bytes.length >= limit && text.endsWith('<EFBFBD>') ? 1 : 0;
|
||||
return bad - cut > 0 ? null : (cut > 0 ? text.substring(0, text.length - 1) : text);
|
||||
if (text.contains('\u0000') || bad > 2 + text.length ~/ 100) return null;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// 81: 빠른 보기 (Space · TC 는 F3 · 길게 누르기 [미리 보기]) - 동영상은 큰 썸네일 · 길이 · 해상도 · 코덱, 그림은 그림,
|
||||
|
|
@ -102,7 +111,7 @@ class _QuickLookState extends State<QuickLook> {
|
|||
builder: (_, s) => s.connectionState != ConnectionState.done
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: s.data == null
|
||||
? _info([tr('미리 볼 수 없음 (글자 코드가 UTF-8 이 아닙니다)')])
|
||||
? _info([tr('미리 볼 수 없음 (글자 코드를 알 수 없습니다)')])
|
||||
: SingleChildScrollView(
|
||||
child: SelectableText(s.data!, style: const TextStyle(fontFamily: 'Consolas', fontSize: 12)),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -14,11 +15,22 @@ void main() {
|
|||
setUp(() => tmp = Directory.systemTemp.createTempSync('jj_ql81_'));
|
||||
tearDown(() => tmp.deleteSync(recursive: true));
|
||||
|
||||
test('글 앞부분: UTF-8 은 그대로 · 다른 글자 코드 (CP949) 는 null · 64KB 끝에서 잘린 한 글자는 괜찮음', () async {
|
||||
test('글 앞부분: UTF-8 · CP949 .smi · UTF-16 (BOM) .srt · UTF-8 BOM .txt 를 읽고, 글이 아니면 null · 64KB 끝에서 잘린 한 글자는 괜찮음', () async {
|
||||
final a = File(p.join(tmp.path, 'a.txt'))..writeAsStringSync('안녕 hello');
|
||||
expect(await readTextHead(a.path), '안녕 hello');
|
||||
final b = File(p.join(tmp.path, 'b.smi'))..writeAsBytesSync([0xBE, 0xC8, 0xB3, 0xE7]); // CP949 "안녕"
|
||||
expect(await readTextHead(b.path), isNull);
|
||||
// 81 보충 (15 지적): 한국어 자막에 흔한 CP949 · 메모장의 UTF-16 · UTF-8 BOM
|
||||
final b = File(p.join(tmp.path, 'b.smi'))..writeAsBytesSync([0xBE, 0xC8, 0xB3, 0xE7, 0x20, 0xC0, 0xDA, 0xB8, 0xB7]); // CP949 "안녕 자막"
|
||||
expect(await readTextHead(b.path), '안녕 자막');
|
||||
final u16 = <int>[0xFF, 0xFE];
|
||||
for (final r in '자막 1'.codeUnits) {
|
||||
u16.addAll([r & 0xFF, r >> 8]);
|
||||
}
|
||||
final s = File(p.join(tmp.path, 's.srt'))..writeAsBytesSync(u16);
|
||||
expect(await readTextHead(s.path), '자막 1');
|
||||
final bom = File(p.join(tmp.path, 'bom.txt'))..writeAsBytesSync([0xEF, 0xBB, 0xBF, ...utf8.encode('hi 안녕')]);
|
||||
expect(await readTextHead(bom.path), 'hi 안녕');
|
||||
final bin = File(p.join(tmp.path, 'bin.txt'))..writeAsBytesSync([0x00, 0x01, 0x02, 0x00, 0xFF]);
|
||||
expect(await readTextHead(bin.path), isNull, reason: '글이 아님');
|
||||
// 64KB 경계에 3바이트 글자가 걸리게
|
||||
final c = File(p.join(tmp.path, 'c.txt'))..writeAsStringSync('${'x' * (64 * 1024 - 1)}가나다');
|
||||
final head = await readTextHead(c.path);
|
||||
|
|
|
|||
Loading…
Reference in New Issue