-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmusic_transliterate.py
More file actions
1753 lines (1575 loc) · 98.3 KB
/
Copy pathmusic_transliterate.py
File metadata and controls
1753 lines (1575 loc) · 98.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# https://github.com/mtxadmin/music_transliterate/
# Many thanks to Deepseek AI, who greatly helped with writing and debugging of this script
# модуль transliterate - херня, детектит всё как ru. Issue сделал, но проект старый и заброшенный
# https://github.com/barseghyanartur/transliterate/issues/73
# также, этот модуль транслитерирует криво. й в j, например. Короче, лучше без него
import os
import re
import sys
import unicodedata
import random
from pathlib import Path
from typing import Dict, Optional, Tuple, Set, List, Any
# безопасная запись в stderr с обработкой ошибок кодировки
def safe_stderr_write(message: str) -> None:
"""Безопасная запись в stderr с обработкой ошибок кодировки."""
try:
sys.stderr.write(message)
except UnicodeEncodeError:
# Заменяем не-ASCII символы на их кодовые точки (формат \uXXXX)
safe_message = message.encode('ascii', errors='backslashreplace').decode('ascii')
sys.stderr.write(safe_message)
except Exception:
# Если и это не помогло, игнорируем
pass
finally:
sys.stderr.flush()
def safe_stdout_write(message: str) -> None:
"""Безопасная запись в stdout с обработкой ошибок кодировки."""
try:
sys.stdout.write(message)
except UnicodeEncodeError:
safe_message = message.encode('ascii', errors='backslashreplace').decode('ascii')
sys.stdout.write(safe_message)
except Exception:
pass
finally:
sys.stdout.flush()
class FileTransliterator:
def __init__(self):
# Таблицы транслитерации для разных языков
self.transliteration_tables = self._create_transliteration_tables()
# Специфические символы для определения языка
self.language_specific_chars = {
'russian': set('абвгдеёжзийклмнопрстуфхцчшщъыьэюя'),
'ukrainian': set('абвгґдеєжзиіїйклмнопрстуфхцчшщьюя'),
'polish': set('ąćęłńóśźż'),
'bulgarian': set('абвгдежзийклмнопрстуфхцчшщъьюя'),
'german': set('äöüß'),
'european': set('áàâãäåæçéèêëíìîïñóòôõöøœúùûüýÿ')
}
# Символы, недопустимые в именах файлов (для Windows, macOS, Linux)
self.invalid_filename_chars = set(r'<>:"/\|?*')
# Замены для недопустимых символов
self.invalid_char_replacements = {
'<': '_',
'>': '_',
':': '_',
'"': "'", # Кавычки заменяем на апостроф
'/': '_',
'\\': '_',
'|': '_',
'?': '_',
'*': '_' # Звездочка -> подчеркивание
}
# Популярные ID3 теги для транслитерации (только текстовые теги)
self.id3_text_tags_to_transliterate = [
'TIT2', # Название трека
'TPE1', # Исполнитель
'TPE2', # Альбомный исполнитель
'TALB', # Альбом
'TCOM', # Композитор
'TCON', # Жанр
'TENC', # Кодировщик (Encoded by)
'TEXT', # Автор текста
'TFLT', # Тип файла
'TIT1', # Группировка
'TIT3', # Подзаголовок/описание
'TKEY', # Тональность
'TLAN', # Язык
'TMED', # Тип носителя
'TOAL', # Оригинальный альбом
'TOFN', # Оригинальное имя файла
'TOLY', # Автор текста оригинала
'TOPE', # Оригинальный исполнитель
'TOWN', # Владелец
'TPE3', # Дирижер
'TPE4', # Аранжировщик
'TPOS', # Номер диска
'TPUB', # Издатель
'TRCK', # Номер трека
'TRSN', # Радиостанция
'TRSO', # Владелец радиостанции
'TSOA', # Альбомный порядок
'TSOP', # Исполнитель для сортировки
'TSOT', # Название для сортировки
'TSRC', # ISRC
'TSSE', # Настройки кодировщика (Software/Hardware)
'TSST', # Подзаголовок набора
]
# Состояния обработки текущего файла (для подробного логирования при ошибках)
self.file_state = {
'original': None, # исходное имя файла (как на диске)
'raw_name': None, # имя без расширения (после splitext)
'cleaned_before': None, # после _clean_decorative_symbols
'transliterated': None, # после транслитерации
'sanitized': None, # после sanitize_filename
'final_name': None, # итоговое имя с расширением
'language': None, # определённый язык
}
def _reset_file_state(self):
"""Сбрасывает сохранённые состояния перед обработкой нового файла."""
for key in self.file_state:
self.file_state[key] = None
def _log_file_state_on_error(self):
"""Выводит все непустые состояния имени файла в stderr при возникновении ошибки."""
sys.stdout.flush() # гарантируем, что предыдущие успешные сообщения уже выведены
safe_stderr_write("\n" + "=" * 70 + "\n")
safe_stderr_write("ОШИБКА ПРИ ОБРАБОТКЕ ФАЙЛА. ИСТОРИЯ ИЗМЕНЕНИЙ ИМЕНИ:\n")
safe_stderr_write("=" * 70 + "\n")
if self.file_state['original'] is not None:
safe_stderr_write(f"Исходное имя (с диска) : {repr(self.file_state['original'])}\n")
if self.file_state['raw_name'] is not None:
safe_stderr_write(f"Имя без расширения : {repr(self.file_state['raw_name'])}\n")
if self.file_state['cleaned_before'] is not None:
safe_stderr_write(f"После очистки (до транслит): {repr(self.file_state['cleaned_before'])}\n")
if self.file_state['transliterated'] is not None:
safe_stderr_write(f"После транслитерации : {repr(self.file_state['transliterated'])}\n")
if self.file_state['sanitized'] is not None:
safe_stderr_write(f"После sanitize : {repr(self.file_state['sanitized'])}\n")
if self.file_state['final_name'] is not None:
safe_stderr_write(f"Итоговое имя с расширением: {repr(self.file_state['final_name'])}\n")
if self.file_state['language'] is not None:
safe_stderr_write(f"Определённый язык : {self.file_state['language']}\n")
safe_stderr_write("=" * 70 + "\n")
def _add_extra_cyrillic(self, table: Dict[str, str]) -> None:
"""Добавляет в таблицу транслитерации символы расширенной кириллицы."""
extra_cyrillic = {
# Азербайджанский, башкирский, казахский, татарский и др.
'Ә': 'A', 'ә': 'a', # A with diaeresis (азерб., башк., каз., тат.)
'Ғ': 'G', 'ғ': 'g', # G with stroke (башк., каз., тадж., тат., уйг.)
'Ҙ': 'Z', 'ҙ': 'z', # Z with descender (башк.)
'Ҡ': 'K', 'ҡ': 'k', # K with descender (башк.)
'Ң': 'N', 'ң': 'n', # N with descender (башк., каз., кирг., тат., уйг.)
'Ө': 'O', 'ө': 'o', # O with bar (башк., бурят., калм., каз., монг., тат., тув., якут.)
'Ү': 'U', 'ү': 'u', # U with straight stroke (башк., бурят., калм., каз., кирг., монг., тат., тув., якут.)
'Һ': 'H', 'һ': 'h', # Shha (башк., бурят., калм., каз., тат., якут.)
'Ҷ': 'Ch', 'ҷ': 'ch', # Che with descender (тадж.)
'Ҳ': 'H', 'ҳ': 'h', # Ha with descender (тадж., узб.)
'Қ': 'Q', 'қ': 'q', # Ka with descender (каз., тадж., узб., уйг.)
'Ӯ': 'U', 'ӯ': 'u', # U with macron (тадж.)
'Ғ': 'G', 'ғ': 'g', # (повтор для единообразия)
'Ҷ': 'J', 'ҷ': 'j', # вариант транслитерации
# Белорусский, украинский
'Ў': 'U', 'ў': 'u', # Short U (белорус., узб.)
'І': 'I', 'і': 'i', # Belarusian/Ukrainian I (уже есть в ukrainian_table)
'Ґ': 'G', 'ґ': 'g', # G with upturn (укр.)
'Є': 'Ye', 'є': 'ye', # Ukrainian Ye
'Ї': 'Yi', 'ї': 'yi', # Ukrainian Yi
# Дополнительные буквы с диакритикой, встречающиеся в именах
'Ӂ': 'Zh', 'ӂ': 'zh', # Zhe with breve (молд., гагауз.)
'Ӌ': 'Ch', 'ӌ': 'ch', # Che with descender (хакас.)
'Ӑ': 'A', 'ӑ': 'a', # A with breve (чуваш.)
'Ӗ': 'E', 'ӗ': 'e', # E with breve (чуваш.)
'Ҫ': 'S', 'ҫ': 's', # Es with descender (башк., чуваш.)
'Ҭ': 'T', 'ҭ': 't', # Te with descender (абхаз.)
'Ӳ': 'U', 'ӳ': 'u', # U with double acute (чуваш.)
'Ӱ': 'U', 'ӱ': 'u', # U with diaeresis (алт., хакас., марийск.)
'Ӹ': 'Y', 'ӹ': 'y', # Yeru with diaeresis (марийск.)
'Ӓ': 'A', 'ӓ': 'a', # A with diaeresis (марийск., саам.)
'Ӛ': 'E', 'ӛ': 'e', # E with diaeresis (ненецк.)
'Ӝ': 'Zh', 'ӝ': 'zh', # Zhe with diaeresis (удм.)
'Ӟ': 'Dz', 'ӟ': 'dz', # Ze with diaeresis (удм.)
'Ӥ': 'I', 'ӥ': 'i', # I with diaeresis (удм.)
'Ӧ': 'O', 'ӧ': 'o', # O with diaeresis (коми, марийск., удм.)
'Ӫ': 'O', 'ӫ': 'o', # O with diaeresis and macron (эвенк.)
'Ӭ': 'E', 'ӭ': 'e', # E with diaeresis and macron (эвенк.)
'Ӵ': 'Ch', 'ӵ': 'ch', # Che with diaeresis (удм.)
'Ҕ': 'G', 'ҕ': 'g', # Ghe with middle hook (абхазский, якутский)
'Ӄ': 'K', 'ӄ': 'k', # Ka with vertical stroke (чукотский, корякский)
}
table.update(extra_cyrillic)
def _add_decorative_latin(self, table: Dict[str, str]) -> None:
"""Добавляет в таблицу транслитерации декоративные латинские символы (Small Caps, коптские буквы и др.)."""
decorative = {
# Small Caps (U+1D00 – U+1D7F, выборочно)
'ᴀ': 'A', 'ʙ': 'B', 'ᴄ': 'C', 'ᴅ': 'D', 'ᴇ': 'E',
'ꜰ': 'F', 'ɢ': 'G', 'ʜ': 'H', 'ɪ': 'I', 'ᴊ': 'J',
'ᴋ': 'K', 'ʟ': 'L', 'ᴍ': 'M', 'ɴ': 'N', 'ᴏ': 'O',
'ᴘ': 'P', 'ꞯ': 'Q', 'ʀ': 'R', 'ꜱ': 'S', 'ᴛ': 'T',
'ᴜ': 'U', 'ᴠ': 'V', 'ᴡ': 'W', 'x': 'X', 'ʏ': 'Y',
'ᴢ': 'Z',
# Latin Letter Yr (стилизованная R)
'Ʀ': 'R',
# Коптские буквы (часто используются для стилизации)
'Ⲁ': 'A', 'ⲁ': 'a',
'Ⲃ': 'B', 'ⲃ': 'b',
'Ⲅ': 'G', 'ⲅ': 'g',
'Ⲇ': 'D', 'ⲇ': 'd',
'Ⲉ': 'E', 'ⲉ': 'e',
'Ⲋ': 'S', 'ⲋ': 's',
'Ⲍ': 'Z', 'ⲍ': 'z',
'Ⲏ': 'I', 'ⲏ': 'i',
'Ⲑ': 'Th', 'ⲑ': 'th',
'Ⲓ': 'I', 'ⲓ': 'i',
'Ⲕ': 'K', 'ⲕ': 'k',
'Ⲗ': 'L', 'ⲗ': 'l',
'Ⲙ': 'M', 'ⲙ': 'm',
'Ⲛ': 'N', 'ⲛ': 'n',
'Ⲝ': 'X', 'ⲝ': 'x',
'Ⲟ': 'O', 'ⲟ': 'o',
'Ⲡ': 'P', 'ⲡ': 'p',
'Ⲣ': 'R', 'ⲣ': 'r',
'Ⲥ': 'S', 'ⲥ': 's',
'Ⲧ': 'T', 'ⲧ': 't',
'Ⲩ': 'U', 'ⲩ': 'u',
'Ⲫ': 'Ph', 'ⲫ': 'ph',
'Ⲭ': 'Kh', 'ⲭ': 'kh',
'Ⲯ': 'Ps', 'ⲯ': 'ps',
'Ⲱ': 'O', 'ⲱ': 'o',
'Ⲳ': 'Sh', 'ⲳ': 'sh',
'Ⲵ': 'F', 'ⲵ': 'f',
'Ⲷ': 'Kh', 'ⲷ': 'kh',
'Ⲹ': 'H', 'ⲹ': 'h',
'Ⲻ': 'Ch', 'ⲻ': 'ch',
'Ⲽ': 'Ti', 'ⲽ': 'ti',
# Декоративные буквы из других письменностей (похожие на латиницу)
# Канадское слоговое письмо и чероки
'ᗰ': 'M', # U+15F0
'Ꮌ': 'M', # U+13BC (Cherokee)
'Ꮗ': 'W', # U+13C7 (Cherokee)
'Ꭵ': 'i', # U+13A5
'Ꮢ': 'R', # U+13D2
# Грузинский (Mkhedruli) – визуально похожие на латиницу
'ყ': 'y', # U+10E7
'მ': 'm', # U+10DB
'ნ': 'n', # U+10DC
'ა': 'a', # U+10D0
'ბ': 'b', # U+10D1
'გ': 'g', # U+10D2
'დ': 'd', # U+10D3
'ე': 'e', # U+10D4
'ვ': 'v', # U+10D5
'ზ': 'z', # U+10D6
'თ': 't', # U+10D7
'ი': 'i', # U+10D8
'კ': 'k', # U+10D9
'ლ': 'l', # U+10DA
'ო': 'o', # U+10DD
'პ': 'p', # U+10DE
'ჟ': 'zh', # U+10DF
'რ': 'r', # U+10E0
'ს': 's', # U+10E1
'ტ': 't', # U+10E2
'უ': 'u', # U+10E3
'ფ': 'p', # U+10E4
'ქ': 'k', # U+10E5
'ღ': 'gh', # U+10E6
'შ': 'sh', # U+10E8
'ჩ': 'ch', # U+10E9
'ც': 'ts', # U+10EA
'ძ': 'dz', # U+10EB
'წ': 'ts', # U+10EC
'ჭ': 'ch', # U+10ED
'ხ': 'kh', # U+10EE
'ჯ': 'j', # U+10EF
'ჰ': 'h', # U+10F0
# Греческие буквы (стилистические)
'ϐ': 'b', # U+03D0 (Greek beta symbol)
# Тайские буквы (стилизация)
'ค': 'a', # U+0E04 (kho khwai, визуально 'a')
# Гурмукхи (пенджаби)
'ਮ': 'm', # U+0A2E # indents are ok here
# Other
'ʍ': 'w', # U+028D LATIN SMALL LETTER TURNED W
'ნ': 'n', # U+10DC (уже добавлен выше, но оставлен для ясности)
}
table.update(decorative)
def _create_transliteration_tables(self) -> Dict[str, Dict[str, str]]:
"""Создает таблицы транслитерации для всех языков"""
# Русский язык по ГОСТ 7.79-2000 (система B) с изменениями:
# - ъ -> '
# - й -> y (вместо j)
russian_table = {
'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D',
'Е': 'E', 'Ё': 'Yo', 'Ж': 'Zh', 'З': 'Z', 'И': 'I',
'Й': 'Y', 'К': 'K', 'Л': 'L', 'М': 'M', 'Н': 'N', # Й -> Y
'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T',
'У': 'U', 'Ф': 'F', 'Х': 'Kh', 'Ц': 'Ts', 'Ч': 'Ch',
'Ш': 'Sh', 'Щ': 'Shch', 'Ъ': "'", 'Ы': 'Y', 'Ь': "'",
'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya',
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i',
'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', # й -> y
'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch',
'ш': 'sh', 'щ': 'shch', 'ъ': "'", 'ы': 'y', 'ь': "'",
'э': 'e', 'ю': 'yu', 'я': 'ya'
}
# Украинский язык
ukrainian_table = {
'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Ґ': 'G', 'Д': 'D',
'Е': 'E', 'Є': 'Ye', 'Ж': 'Zh', 'З': 'Z', 'И': 'Y', 'І': 'I',
'Ї': 'Yi', 'Й': 'Y', 'К': 'K', 'Л': 'L', 'М': 'M', 'Н': 'N',
'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U',
'Ф': 'F', 'Х': 'Kh', 'Ц': 'Ts', 'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Shch',
'Ь': "'", 'Ю': 'Yu', 'Я': 'Ya',
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'ґ': 'g', 'д': 'd',
'е': 'e', 'є': 'ye', 'ж': 'zh', 'з': 'z', 'и': 'y', 'і': 'i',
'ї': 'yi', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'shch',
'ь': "'", 'ю': 'yu', 'я': 'ya'
}
# Польский язык
polish_table = {
'Ą': 'A', 'Ć': 'C', 'Ę': 'E', 'Ł': 'L', 'Ń': 'N',
'Ó': 'O', 'Ś': 'S', 'Ź': 'Z', 'Ż': 'Z',
'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n',
'ó': 'o', 'ś': 's', 'ź': 'z', 'ż': 'z'
}
# Болгарский язык
bulgarian_table = {
'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D',
'Е': 'E', 'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'Й': 'Y',
'К': 'K', 'Л': 'L', 'М': 'M', 'Н': 'N', 'О': 'O',
'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U',
'Ф': 'F', 'Х': 'H', 'Ц': 'Ts', 'Ч': 'Ch', 'Ш': 'Sh',
'Щ': 'Sht', 'Ъ': 'A', 'Ь': 'Y', 'Ю': 'Yu', 'Я': 'Ya',
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
'е': 'e', 'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y',
'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o',
'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
'ф': 'f', 'х': 'h', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh',
'щ': 'sht', 'ъ': 'a', 'ь': 'y', 'ю': 'yu', 'я': 'ya'
}
# Немецкий язык
german_table = {
'Ä': 'Ae', 'Ö': 'Oe', 'Ü': 'Ue', 'ß': 'ss',
'ä': 'ae', 'ö': 'oe', 'ü': 'ue'
}
# Общая таблица для европейских языков с диакритикой и другими символами Unicode
european_table = {
# Latin-1 Supplement (ISO-8859-1)
'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A',
'Å': 'A', 'Æ': 'AE', 'Ç': 'C', 'È': 'E', 'É': 'E',
'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I',
'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O',
'Ô': 'O', 'Õ': 'O', 'Ö': 'O', 'Ø': 'O', 'Ù': 'U',
'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ý': 'Y', 'Þ': 'Th',
'ß': 'ss', 'à': 'a', 'á': 'a', 'â': 'a', 'ã': 'a',
'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e',
'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i',
'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o',
'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ø': 'o',
'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u', 'ý': 'y',
'þ': 'th', 'ÿ': 'y',
# Latin Extended-A (часть символов из Unicode диапазона U+0100-U+017F)
'Ā': 'A', 'ā': 'a', 'Ă': 'A', 'ă': 'a', 'Ą': 'A',
'ą': 'a', 'Ć': 'C', 'ć': 'c', 'Ĉ': 'C', 'ĉ': 'c',
'Ċ': 'C', 'ċ': 'c', 'Č': 'C', 'č': 'c', 'Ď': 'D',
'ď': 'd', 'Đ': 'D', 'đ': 'd', 'Ē': 'E', 'ē': 'e',
'Ĕ': 'E', 'ĕ': 'e', 'Ė': 'E', 'ė': 'e', 'Ę': 'E',
'ę': 'e', 'Ě': 'E', 'ě': 'e', 'Ĝ': 'G', 'ĝ': 'g',
'Ğ': 'G', 'ğ': 'g', 'Ġ': 'G', 'ġ': 'g', 'Ģ': 'G',
'ģ': 'g', 'Ĥ': 'H', 'ĥ': 'h', 'Ħ': 'H', 'ħ': 'h',
'Ĩ': 'I', 'ĩ': 'i', 'Ī': 'I', 'ī': 'i', 'Ĭ': 'I',
'ĭ': 'i', 'Į': 'I', 'į': 'i', 'İ': 'I', 'ı': 'i',
'Ĵ': 'J', 'ĵ': 'j', 'Ķ': 'K', 'ķ': 'k', 'ĸ': 'k',
'Ĺ': 'L', 'ĺ': 'l', 'Ļ': 'L', 'ļ': 'l', 'Ľ': 'L',
'ľ': 'l', 'Ŀ': 'L', 'ŀ': 'l', 'Ł': 'L', 'ł': 'l',
'Ń': 'N', 'ń': 'n', 'Ņ': 'N', 'ņ': 'n', 'Ň': 'N',
'ň': 'n', 'ʼn': 'n', 'Ŋ': 'N', 'ŋ': 'n', 'Ō': 'O',
'ō': 'o', 'Ŏ': 'O', 'ŏ': 'o', 'Ő': 'O', 'ő': 'o',
'Œ': 'OE', 'œ': 'oe', 'Ŕ': 'R', 'ŕ': 'r', 'Ŗ': 'R',
'ŗ': 'r', 'Ř': 'R', 'ř': 'r', 'Ś': 'S', 'ś': 's',
'Ŝ': 'S', 'ŝ': 's', 'Ş': 'S', 'ş': 's', 'Š': 'S',
'š': 's', 'Ţ': 'T', 'ţ': 't', 'Ť': 'T', 'ť': 't',
'Ŧ': 'T', 'ŧ': 't', 'Ũ': 'U', 'ũ': 'u', 'Ū': 'U',
'ū': 'u', 'Ŭ': 'U', 'ŭ': 'u', 'Ů': 'U', 'ů': 'u',
'Ű': 'U', 'ű': 'u', 'Ų': 'U', 'ų': 'u', 'Ŵ': 'W',
'ŵ': 'w', 'Ŷ': 'Y', 'ŷ': 'y', 'Ÿ': 'Y', 'Ź': 'Z',
'ź': 'z', 'Ż': 'Z', 'ż': 'z', 'Ž': 'Z', 'ž': 'z',
'ſ': 's',
# Latin Extended-B (некоторые символы)
'ƀ': 'b', 'Ɓ': 'B', 'Ƃ': 'B', 'ƃ': 'b', 'Ƅ': 'B',
'ƅ': 'b', 'Ɔ': 'O', 'Ƈ': 'C', 'ƈ': 'c', 'Ɖ': 'D',
'Ɗ': 'D', 'Ƌ': 'D', 'ƌ': 'd', 'ƍ': 'd', 'Ǝ': 'E',
'Ə': 'E', 'Ɛ': 'E', 'Ƒ': 'F', 'ƒ': 'f', 'Ɠ': 'G',
'Ɣ': 'G', 'ƕ': 'hv', 'Ɩ': 'I', 'Ɨ': 'I', 'Ƙ': 'K',
'ƙ': 'k', 'ƚ': 'l', 'ƛ': 'l', 'Ɯ': 'M', 'Ɲ': 'N',
'ƞ': 'n', 'Ɵ': 'O', 'Ơ': 'O', 'ơ': 'o', 'Ƣ': 'OI',
'ƣ': 'oi', 'Ƥ': 'P', 'ƥ': 'p', 'Ʀ': 'R', 'Ƨ': 'S',
'ƨ': 's', 'Ʃ': 'E', 'ƪ': 'E', 'ƫ': 't', 'Ƭ': 'T',
'ƭ': 't', 'Ʈ': 'T', 'Ư': 'U', 'ư': 'u', 'Ʊ': 'Y',
'Ʋ': 'V', 'Ƴ': 'Y', 'ƴ': 'y', 'Ƶ': 'Z', 'ƶ': 'z',
'Ʒ': 'Z', 'Ƹ': 'Z', 'ƹ': 'z', 'ƺ': 'z', 'ƻ': '2',
'Ƽ': '5', 'ƽ': '5', 'ƾ': 'T', 'ƿ': 'p',
# Latin Extended Additional (некоторые символы)
'Ḁ': 'A', 'ḁ': 'a', 'Ḃ': 'B', 'ḃ': 'b', 'Ḅ': 'B',
'ḅ': 'b', 'Ḇ': 'B', 'ḇ': 'b', 'Ḉ': 'C', 'ḉ': 'c',
'Ḋ': 'D', 'ḋ': 'd', 'Ḍ': 'D', 'ḍ': 'd', 'Ḏ': 'D',
'ḏ': 'd', 'Ḑ': 'D', 'ḑ': 'd', 'Ḓ': 'D', 'ḓ': 'd',
'Ḕ': 'E', 'ḕ': 'e', 'Ḗ': 'E', 'ḗ': 'e', 'Ḙ': 'E',
'ḙ': 'e', 'Ḛ': 'E', 'ḛ': 'e', 'Ḝ': 'E', 'ḝ': 'e',
'Ḟ': 'F', 'ḟ': 'f', 'Ḡ': 'G', 'ḡ': 'g', 'Ḣ': 'H',
'ḣ': 'h', 'Ḥ': 'H', 'ḥ': 'h', 'Ḧ': 'H', 'ḧ': 'h',
'Ḩ': 'H', 'ḩ': 'h', 'Ḫ': 'H', 'ḫ': 'h', 'Ḭ': 'I',
'ḭ': 'i', 'Ḯ': 'I', 'ḯ': 'i', 'Ḱ': 'K', 'ḱ': 'k',
'Ḳ': 'K', 'ḳ': 'k', 'Ḵ': 'K', 'ḵ': 'k', 'Ḷ': 'L',
'ḷ': 'l', 'Ḹ': 'L', 'ḹ': 'l', 'Ḻ': 'L', 'ḻ': 'l',
'Ḽ': 'L', 'ḽ': 'l', 'Ḿ': 'M', 'ḿ': 'm', 'Ṁ': 'M',
'ṁ': 'm', 'Ṃ': 'M', 'ṃ': 'm', 'Ṅ': 'N', 'ṅ': 'n',
'Ṇ': 'N', 'ṇ': 'n', 'Ṉ': 'N', 'ṉ': 'n', 'Ṋ': 'N',
'ṋ': 'n', 'Ṍ': 'O', 'ṍ': 'o', 'Ṏ': 'O', 'ṏ': 'o',
'Ṑ': 'O', 'ṑ': 'o', 'Ṓ': 'O', 'ṓ': 'o', 'Ṕ': 'P',
'ṕ': 'p', 'Ṗ': 'P', 'ṗ': 'p', 'Ṙ': 'R', 'ṙ': 'r',
'Ṛ': 'R', 'ṛ': 'r', 'Ṝ': 'R', 'ṝ': 'r', 'Ṟ': 'R',
'ṟ': 'r', 'Ṡ': 'S', 'ṡ': 's', 'Ṣ': 'S', 'ṣ': 's',
'Ṥ': 'S', 'ṥ': 's', 'Ṧ': 'S', 'ṧ': 's', 'Ṩ': 'S',
'ṩ': 's', 'Ṫ': 'T', 'ṫ': 't', 'Ṭ': 'T', 'ṭ': 't',
'Ṯ': 'T', 'ṯ': 't', 'Ṱ': 'T', 'ṱ': 't', 'Ṳ': 'U',
'ṳ': 'u', 'Ṵ': 'U', 'ṵ': 'u', 'Ṷ': 'U', 'ṷ': 'u',
'Ṹ': 'U', 'ṹ': 'u', 'Ṻ': 'U', 'ṻ': 'u', 'Ṽ': 'V',
'ṽ': 'v', 'Ṿ': 'V', 'ṿ': 'v', 'Ẁ': 'W', 'ẁ': 'w',
'Ẃ': 'W', 'ẃ': 'w', 'Ẅ': 'W', 'ẅ': 'w', 'Ẇ': 'W',
'ẇ': 'w', 'Ẉ': 'W', 'ẉ': 'w', 'Ẋ': 'X', 'ẋ': 'x',
'Ẍ': 'X', 'ẍ': 'x', 'Ẏ': 'Y', 'ẏ': 'y', 'Ẑ': 'Z',
'ẑ': 'z', 'Ẓ': 'Z', 'ẓ': 'z', 'Ẕ': 'Z', 'ẕ': 'z',
'ẖ': 'h', 'ẗ': 't', 'ẘ': 'w', 'ẙ': 'y', 'ẚ': 'a',
'ẛ': 's', 'ẜ': 's', 'ẝ': 's', 'ẞ': 'SS', 'ẟ': 'd',
# Latin Extended-C, D, E (некоторые символы)
'ꞌ': "'", 'Ɥ': 'H', 'ꞎ': 'l', 'ꞏ': '.', 'Ꞑ': 'N',
'ꞑ': 'n', 'Ꞓ': 'C', 'ꞓ': 'c', 'ꞔ': 'c', 'ꞕ': 'h',
'Ꞗ': 'B', 'ꞗ': 'b', 'Ꞙ': 'F', 'ꞙ': 'f', 'Ꞛ': 'V',
'ꞛ': 'v', 'Ꞝ': 'V', 'ꞝ': 'v', 'Ꞟ': 'V', 'ꞟ': 'v',
# Дополнительные символы с диакритикой
'Ȁ': 'A', 'ȁ': 'a', 'Ȃ': 'A', 'ȃ': 'a', 'Ȅ': 'E',
'ȅ': 'e', 'Ȇ': 'E', 'ȇ': 'e', 'Ȉ': 'I', 'ȉ': 'i',
'Ȋ': 'I', 'ȋ': 'i', 'Ȍ': 'O', 'ȍ': 'o', 'Ȏ': 'O',
'ȏ': 'o', 'Ȑ': 'R', 'ȑ': 'r', 'Ȓ': 'R', 'ȓ': 'r',
'Ȕ': 'U', 'ȕ': 'u', 'Ȗ': 'U', 'ȗ': 'u', 'Ș': 'S',
'ș': 's', 'Ț': 'T', 'ț': 't', 'Ȝ': 'Y', 'ȝ': 'y',
'Ȟ': 'H', 'ȟ': 'h', 'Ƞ': 'N', 'ȡ': 'd', 'Ȣ': 'OU',
'ȣ': 'ou', 'Ȥ': 'Z', 'ȥ': 'z', 'Ȧ': 'A', 'ȧ': 'a',
'Ȩ': 'E', 'ȩ': 'e', 'Ȫ': 'O', 'ȫ': 'o', 'Ȭ': 'O',
'ȭ': 'o', 'Ȯ': 'O', 'ȯ': 'o', 'Ȱ': 'O', 'ȱ': 'o',
'Ȳ': 'Y', 'ȳ': 'y',
# Символы пунктуации и другие символы Unicode
# Все кавычки заменяем строго на апостроф '
'…': '...', # HORIZONTAL ELLIPSIS (U+2026)
'«': "'", # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK (U+00AB) -> апостроф
'»': "'", # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (U+00BB) -> апостроф
'„': "'", # DOUBLE LOW-9 QUOTATION MARK (U+201E) -> апостроф
'“': "'", # LEFT DOUBLE QUOTATION MARK (U+201C) -> апостроф
'”': "'", # RIGHT DOUBLE QUOTATION MARK (U+201D) -> апостроф
'‘': "'", # LEFT SINGLE QUOTATION MARK (U+2018) -> апостроф
'’': "'", # RIGHT SINGLE QUOTATION MARK (U+2019) -> апостроф
'‹': "'", # SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) -> апостроф
'›': "'", # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) -> апостроф
'–': '-', # EN DASH (U+2013)
'—': '-', # EM DASH (U+2014)
'―': '-', # HORIZONTAL BAR (U+2015)
'‒': '-', # FIGURE DASH (U+2012)
'−': '-', # MINUS SIGN (U+2212)
'±': '+/-', # PLUS-MINUS SIGN (U+00B1)
'×': 'x', # MULTIPLICATION SIGN (U+00D7)
'÷': '/', # DIVISION SIGN (U+00F7)
'©': '(c)', # COPYRIGHT SIGN (U+00A9)
'®': '(R)', # REGISTERED SIGN (U+00AE)
'™': '(TM)', # TRADE MARK SIGN (U+2122)
'°': 'deg', # DEGREE SIGN (U+00B0)
'№': 'No.', # NUMERO SIGN (U+2116)
'§': 'S.', # SECTION SIGN (U+00A7)
'¶': 'P.', # PILCROW SIGN (U+00B6)
'†': '+', # DAGGER (U+2020)
'‡': '++', # DOUBLE DAGGER (U+2021)
'•': '*', # BULLET (U+2022)
'·': '.', # MIDDLE DOT (U+00B7)
'¿': '?', # INVERTED QUESTION MARK (U+00BF)
'¡': '!', # INVERTED EXCLAMATION MARK (U+00A1)
'¦': '|', # BROKEN BAR (U+00A6)
'¬': '-', # NOT SIGN (U+00AC)
'µ': 'u', # MICRO SIGN (U+00B5)
'€': 'EUR', # EURO SIGN (U+20AC)
'£': 'GBP', # POUND SIGN (U+00A3)
'¥': 'JPY', # YEN SIGN (U+00A5)
'¢': 'cent', # CENT SIGN (U+00A2)
'ƒ': 'f', # LATIN SMALL LETTER F WITH HOOK (U+0192)
'‰': '%o', # PER MILLE SIGN (U+2030)
'‱': '%oo', # PER TEN THOUSAND SIGN (U+2031)
'‽': '?!', # INTERROBANG (U+203D)
'※': '*', # REFERENCE MARK (U+203B)
'⁂': '***', # ASTERISM (U+2042)
'⁇': '??', # DOUBLE QUESTION MARK (U+2047)
'⁈': '?!', # QUESTION EXCLAMATION MARK (U+2048)
'⁉': '!?', # EXCLAMATION QUESTION MARK (U+2049)
'⁊': '&', # TIRONIAN SIGN ET (U+204A)
'⁋': 'P', # REVERSED PILCROW SIGN (U+204B)
'⁌': '<', # BLACK LEFTWARDS BULLET (U+204C)
'⁍': '>', # BLACK RIGHTWARDS BULLET (U+204D)
# Математические символы
'¼': '1/4', # VULGAR FRACTION ONE QUARTER (U+00BC)
'½': '1/2', # VULGAR FRACTION ONE HALF (U+00BD)
'¾': '3/4', # VULGAR FRACTION THREE QUARTERS (U+00BE)
'⅓': '1/3', # VULGAR FRACTION ONE THIRD (U+2153)
'⅔': '2/3', # VULGAR FRACTION TWO THIRDS (U+2154)
'⅕': '1/5', # VULGAR FRACTION ONE FIFTH (U+2155)
'⅖': '2/5', # VULGAR FRACTION TWO FIFTHS (U+2156)
'⅗': '3/5', # VULGAR FRACTION THREE FIFTHS (U+2157)
'⅘': '4/5', # VULGAR FRACTION FOUR FIFTHS (U+2158)
'⅙': '1/6', # VULGAR FRACTION ONE SIXTH (U+2159)
'⅚': '5/6', # VULGAR FRACTION FIVE SIXTHS (U+215A)
'⅛': '1/8', # VULGAR FRACTION ONE EIGHTH (U+215B)
'⅜': '3/8', # VULGAR FRACTION THREE EIGHTHS (U+215C)
'⅝': '5/8', # VULGAR FRACTION FIVE EIGHTHS (U+215D)
'⅞': '7/8', # VULGAR FRACTION SEVEN EIGHTHS (U+215E)
# Другие символы
'★': '*', # BLACK STAR (U+2605)
'☆': '*', # WHITE STAR (U+2606)
'☺': ':)', # WHITE SMILING FACE (U+263A)
'☹': ':(', # WHITE FROWNING FACE (U+2639)
'♡': '<3', # WHITE HEART SUIT (U+2661)
'♥': '<3', # BLACK HEART SUIT (U+2665)
'♦': '<>', # BLACK DIAMOND SUIT (U+2666)
'♣': 'cl', # BLACK CLUB SUIT (U+2663)
'♠': 'sp', # BLACK SPADE SUIT (U+2660)
'✓': 'V', # CHECK MARK (U+2713)
'✔': 'V', # HEAVY CHECK MARK (U+2714)
'✗': 'X', # BALLOT X (U+2717)
'✘': 'X', # HEAVY BALLOT X (U+2718)
'○': 'O', # WHITE CIRCLE (U+25CB)
'●': 'O', # BLACK CIRCLE (U+25CF)
'◎': '(O)', # BULLSEYE (U+25CE)
'◇': '<>', # WHITE DIAMOND (U+25C7)
'◆': '<>', # BLACK DIAMOND (U+25C6)
'□': '[]', # WHITE SQUARE (U+25A1)
'■': '[]', # BLACK SQUARE (U+25A0)
'△': '/_\\', # WHITE UP-POINTING TRIANGLE (U+25B3)
'▲': '/_\\', # BLACK UP-POINTING TRIANGLE (U+25B2)
'▽': '\\_/', # WHITE DOWN-POINTING TRIANGLE (U+25BD)
'▼': '\\_/', # BLACK DOWN-POINTING TRIANGLE (U+25BC)
'→': '->', # RIGHTWARDS ARROW (U+2192)
'←': '<-', # LEFTWARDS ARROW (U+2190)
'↑': '^', # UPWARDS ARROW (U+2191)
'↓': 'v', # DOWNWARDS ARROW (U+2193)
'↔': '<->', # LEFT RIGHT ARROW (U+2194)
'↕': '^v', # UP DOWN ARROW (U+2195)
'↵': '<-', # DOWNWARDS ARROW WITH CORNER LEFTWARDS (U+21B5)
'⇄': '<->', # RIGHTWARDS ARROW OVER LEFTWARDS ARROW (U+21C4)
'⇅': '^v', # UPWARDS ARROW LEFT OF DOWNWARDS ARROW (U+21C5)
'⇆': '<->', # LEFTWARDS ARROW OVER RIGHTWARDS ARROW (U+21C6)
# Кириллические символы для других языков (не русский/украинский/болгарский)
'Ђ': 'Dj', # CYRILLIC CAPITAL LETTER DJE (U+0402) - сербский
'ђ': 'dj', # CYRILLIC SMALL LETTER DJE (U+0452) - сербский
'Љ': 'Lj', # CYRILLIC CAPITAL LETTER LJE (U+0409) - сербский/македонский
'љ': 'lj', # CYRILLIC SMALL LETTER LJE (U+0459) - сербский/македонский
'Њ': 'Nj', # CYRILLIC CAPITAL LETTER NJE (U+040A) - сербский/македонский
'њ': 'nj', # CYRILLIC SMALL LETTER NJE (U+045A) - сербский/македонский
'Ћ': 'C', # CYRILLIC CAPITAL LETTER TSHE (U+040B) - сербский
'ћ': 'c', # CYRILLIC SMALL LETTER TSHE (U+045B) - сербский
'Џ': 'Dz', # CYRILLIC CAPITAL LETTER DZHE (U+040F) - сербский/македонский
'џ': 'dz', # CYRILLIC SMALL LETTER DZHE (U+045F) - сербский/македонский
'Ѕ': 'Dz', # CYRILLIC CAPITAL LETTER DZE (U+0405) - македонский
'ѕ': 'dz', # CYRILLIC SMALL LETTER DZE (U+0455) - македонский
'Ѣ': 'E', # CYRILLIC CAPITAL LETTER YAT (U+0462) - старославянский
'ѣ': 'e', # CYRILLIC SMALL LETTER YAT (U+0463) - старославянский
'Ѥ': 'Je', # CYRILLIC CAPITAL LETTER IOTIFIED E (U+0464) - старославянский
'ѥ': 'je', # CYRILLIC SMALL LETTER IOTIFIED E (U+0465) - старославянский
'Ѧ': 'Ja', # CYRILLIC CAPITAL LETTER LITTLE YUS (U+0466) - старославянский
'ѧ': 'ja', # CYRILLIC SMALL LETTER LITTLE YUS (U+0467) - старославянский
'Ѩ': 'Jja', # CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS (U+0468) - старославянский
'ѩ': 'jja', # CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS (U+0469) - старославянский
'Ѫ': 'O', # CYRILLIC CAPITAL LETTER BIG YUS (U+046A) - старославянский
'ѫ': 'o', # CYRILLIC SMALL LETTER BIG YUS (U+046B) - старославянский
'Ѭ': 'Jo', # CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS (U+046C) - старославянский
'ѭ': 'jo', # CYRILLIC SMALL LETTER IOTIFIED BIG YUS (U+046D) - старославянский
'Ѯ': 'Ks', # CYRILLIC CAPITAL LETTER KSI (U+046E) - старославянский
'ѯ': 'ks', # CYRILLIC SMALL LETTER KSI (U+046F) - старославянский
'Ѱ': 'Ps', # CYRILLIC CAPITAL LETTER PSI (U+0470) - старославянский
'ѱ': 'ps', # CYRILLIC SMALL LETTER PSI (U+0471) - старославянский
'Ѳ': 'F', # CYRILLIC CAPITAL LETTER FITA (U+0472) - старославянский
'ѳ': 'f', # CYRILLIC SMALL LETTER FITA (U+0473) - старославянский
'Ѵ': 'I', # CYRILLIC CAPITAL LETTER IZHITSA (U+0474) - старославянский
'ѵ': 'i', # CYRILLIC SMALL LETTER IZHITSA (U+0475) - старославянский
'Ѷ': 'I', # CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT (U+0476) - старославянский
'ѷ': 'i', # CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT (U+0477) - старославянский
}
# Добавляем расширенную кириллицу в русскую и европейскую таблицы
self._add_extra_cyrillic(russian_table)
self._add_extra_cyrillic(european_table)
# Добавляем декоративные латинские и коптские символы в русскую (встречено in the wild) и европейскую таблицы
self._add_decorative_latin(russian_table)
self._add_decorative_latin(european_table)
# Добавляем общие символы пунктуации ко всем таблицам
# Все кавычки заменяем строго на апостроф, звездочки на подчеркивание
common_punctuation = {
'…': '...', # Многоточие
'–': '-', # Длинное тире
'—': '-', # Длинное тире
'«': "'", # Левая кавычка -> апостроф
'»': "'", # Правая кавычка -> апостроф
'„': "'", # Нижняя кавычка -> апостроф
'"': "'", # Двойная кавычка -> апостроф
'“': "'", # Левая кавычка -> апостроф
'”': "'", # Правая кавычка -> апостроф
'‘': "'", # Левая одинарная кавычка -> апостроф
'’': "'", # Правая одинарная кавычка -> апостроф
'‹': "'", # Левая угловая кавычка -> апостроф
'›': "'", # Правая угловая кавычка -> апостроф
'/': '_', # Слэш -> подчеркивание
'\\': '_', # Обратный слэш -> подчеркивание
':': '_', # Двоеточие -> подчеркивание
'|': '_', # Вертикальная черта -> подчеркивание
'?': '_', # Вопросительный знак -> подчеркивание
'*': '_', # Звездочка -> подчеркивание
'<': '_', # Меньше -> подчеркивание
'>': '_', # Больше -> подчеркивание
}
# Объединяем таблицы с общими символами пунктуации
russian_table.update(common_punctuation)
ukrainian_table.update(common_punctuation)
polish_table.update(common_punctuation)
bulgarian_table.update(common_punctuation)
german_table.update(common_punctuation)
# Для европейской таблицы добавляем еще больше символов
european_table.update(common_punctuation)
# Добавляем базовую кириллицу из русской таблицы в европейскую (встречается смесь)
european_table.update(russian_table)
return {
'russian': russian_table,
'ukrainian': ukrainian_table,
'polish': polish_table,
'bulgarian': bulgarian_table,
'german': german_table,
'european': european_table
}
# Метод для удаления декоративных Unicode-символов
def _clean_decorative_symbols(self, text: str, strict: bool = False) -> str:
"""
Удаляет декоративные и нетекстовые символы, оставляя только буквы, цифры, пробелы и базовую пунктуацию.
Параметры:
text (str): исходная строка
strict (bool): если True, оставляет только ASCII-символы (буквы, цифры, ограниченная пунктуация)
Возвращает:
str: очищенная строка
"""
# 1. Нормализация Unicode (NFKC) – преобразует лигатуры, надстрочные символы, декоративные варианты в обычные ASCII-аналоги
# Например: fi -> fi, ① -> 1, ℌ -> H, C -> C
text = unicodedata.normalize('NFKC', text)
# 2. Замена нелатинских цифр (арабских, тайских, деванагари и т.д.) на ASCII-цифры
# Используем unicodedata.decimal() для получения числового значения.
digit_replaced_chars = []
for ch in text:
try:
# Пытаемся получить десятичное значение символа (работает для всех цифровых символов Unicode)
decimal_value = unicodedata.decimal(ch)
# Заменяем на соответствующую ASCII-цифру
digit_replaced_chars.append(str(decimal_value))
except ValueError:
# Не является десятичной цифрой – оставляем как есть
digit_replaced_chars.append(ch)
text = ''.join(digit_replaced_chars)
# 3. Удаление комбинирующих диакритических знаков (акцентов, огласовок и т.д.)
# Нормализуем в NFD (декомпозиция) – все составные символы разбиваются на базовый символ + combining marks
text = unicodedata.normalize('NFD', text)
# Удаляем все символы категории Mn (Non-spacing Mark) и Mc (Spacing Combining Mark)
text = ''.join(ch for ch in text if unicodedata.category(ch) not in ('Mn', 'Mc'))
# После удаления диакритик можно снова нормализовать в NFC для компактности (опционально)
text = unicodedata.normalize('NFC', text)
# Определяем разрешённые категории символов (по первой букве категории Unicode)
# L - буквы, N - цифры, Zs - пробелы, P - пунктуация, S - символы (валюта и т.п.)
allowed_categories = {'L', 'N', 'Zs', 'P', 'S'} if not strict else {'L', 'N', 'Zs'}
# Символы пунктуации, разрешённые в строгом режиме (ASCII)
allowed_punctuation = set('.,!?;:\'\"- _()[]{}#$')
# Чёрный список конкретных кодовых точек (невидимые, управляющие, мешающие)
forbidden_codepoints = {
# Невидимые и управляющие
0x200B, # ZERO WIDTH SPACE
0x200C, # ZERO WIDTH NON-JOINER
0x200D, # ZERO WIDTH JOINER
0x2060, # WORD JOINER
0x2061, # FUNCTION APPLICATION
0x2062, # INVISIBLE TIMES
0x2063, # INVISIBLE SEPARATOR
0x2064, # INVISIBLE PLUS
0x2065, # INVISIBLE ASTERISK
0x2066, # LEFT-TO-RIGHT ISOLATE
0x2067, # RIGHT-TO-LEFT ISOLATE
0x2068, # FIRST STRONG ISOLATE
0x2069, # POP DIRECTIONAL ISOLATE
0xFEFF, # ZERO WIDTH NO-BREAK SPACE (BOM)
0x00AD, # SOFT HYPHEN (невидимый дефис)
0x034F, # COMBINING GRAPHEME JOINER
0x061C, # ARABIC LETTER MARK
0x115F, # HANGUL CHOSEONG FILLER
0x1160, # HANGUL JUNGSEONG FILLER
0x17B4, # KHMER VOWEL INHERENT AQ
0x17B5, # KHMER VOWEL INHERENT AA
0x180E, # MONGOLIAN VOWEL SEPARATOR
0x200E, # LEFT-TO-RIGHT MARK
0x200F, # RIGHT-TO-LEFT MARK
0x202A, # LEFT-TO-RIGHT EMBEDDING
0x202B, # RIGHT-TO-LEFT EMBEDDING
0x202C, # POP DIRECTIONAL FORMATTING
0x202D, # LEFT-TO-RIGHT OVERRIDE
0x202E, # RIGHT-TO-LEFT OVERRIDE
# Декоративные и нежелательные символы
0x00B0, # DEGREE SIGN (°)
0x00B4, # ACUTE ACCENT (´) – обычно удаляется, но для надёжности
0x00B8, # CEDILLA (¸)
0x00AF, # MACRON (¯)
0x00B7, # MIDDLE DOT (·)
0x2206, # INCREMENT / DELTA (∆) – часто используется как символ "изменение"
0x2211, # N-ARY SUMMATION (∑)
0x221E, # INFINITY (∞)
0x2248, # ALMOST EQUAL TO (≈)
0x2260, # NOT EQUAL TO (≠)
0x2264, # LESS-THAN OR EQUAL TO (≤)
0x2265, # GREATER-THAN OR EQUAL TO (≥)
# Декоративные круги, точки и геометрические фигуры
0x25CF, # BLACK CIRCLE (●)
0x25CB, # WHITE CIRCLE (○)
0x25CE, # BULLSEYE (◎)
0x25A0, # BLACK SQUARE (■)
0x25A1, # WHITE SQUARE (□)
0x25B2, # BLACK UP-POINTING TRIANGLE (▲)
0x25B6, # BLACK RIGHT-POINTING TRIANGLE (▶)
0x25C0, # BLACK LEFT-POINTING TRIANGLE (◀)
0x25BC, # BLACK DOWN-POINTING TRIANGLE (▼)
0xFFFD, # REPLACEMENT CHARACTER (�) – появляется при ошибках декодирования
# Можно добавить другие по мере обнаружения
}
# Диапазоны нежелательных декоративных символов (могут быть добавлены блоком)
forbidden_ranges = [
(0x02B0, 0x02FF), # Spacing Modifier Letters (например, ˙ U+02D9)
(0x0370, 0x03FF), # Greek and Coptic (включает греческую точку · U+0387)
(0x1F000, 0x1F02F), # Mahjong Tiles
(0x1F0A0, 0x1F0FF), # Playing Cards
(0x1F100, 0x1F1FF), # Enclosed Alphanumeric Supplement
(0x1F200, 0x1F2FF), # Enclosed Ideographic Supplement
(0x1F300, 0x1F5FF), # Miscellaneous Symbols and Pictographs (эмодзи)
(0x1F600, 0x1F64F), # Emoticons
(0x1F680, 0x1F6FF), # Transport and Map Symbols
(0x1F700, 0x1F77F), # Alchemical Symbols
(0x1F780, 0x1F7FF), # Geometric Shapes Extended
(0x1F800, 0x1F8FF), # Supplemental Arrows-C
(0x1F900, 0x1F9FF), # Supplemental Symbols and Pictographs
(0x1FA00, 0x1FA6F), # Chess Symbols
(0x1FA70, 0x1FAFF), # Symbols and Pictographs Extended-A
(0x2600, 0x26FF), # Miscellaneous Symbols
(0x2700, 0x27BF), # Dingbats
(0xFE00, 0xFE0F), # Variation Selectors
(0x0600, 0x06FF), # Arabic (включает декоративный ۩ U+06E9)
(0x0E00, 0x0E7F), # Thai (включает тайскую цифру ๑ U+0E51, но она уже заменена на ASCII-цифру ранее)
(0x2000, 0x206F), # General Punctuation (включает BULLET • U+2022)
(0x2E00, 0x2E7F), # Supplemental Punctuation
(0x3000, 0x303F), # CJK Symbols and Punctuation
(0x3200, 0x32FF), # Enclosed CJK Letters and Months
(0x3300, 0x33FF), # CJK Compatibility
(0xFF00, 0xFFEF), # Halfwidth and Fullwidth Forms
]
# Категории Unicode, которые всегда запрещены (даже если их первая буква совпадает с разрешёнными)
forbidden_categories = {'Cc', 'Cf', 'Co', 'Cn', 'Cs'}
result_chars = []
for ch in text:
cp = ord(ch)
# Проверка на вхождение в запрещённые диапазоны
if any(start <= cp <= end for start, end in forbidden_ranges):
continue
# Проверка на вхождение в чёрный список конкретных точек
if cp in forbidden_codepoints:
continue
# Проверка категории
cat = unicodedata.category(ch)
if cat in forbidden_categories:
continue
# Фильтрация по разрешённым категориям
cat_prefix = cat[0] # первая буква категории
if cat_prefix not in allowed_categories:
continue
# В строгом режиме дополнительно проверяем, что символ ASCII (<= 127) и/или входит в разрешённую пунктуацию
if strict:
if cp <= 127:
if cat_prefix == 'P' and ch not in allowed_punctuation:
continue
# Для остальных ASCII-символов (L, N, Zs) пропускаем
else:
# Не-ASCII символы в строгом режиме запрещены
continue
result_chars.append(ch)
# Собираем результат
cleaned = ''.join(result_chars)
# 4. Нормализация пробельных символов: заменяем любые пробельные последовательности (включая неразрывные) на одиночный пробел
cleaned = re.sub(r'\s+', ' ', cleaned)
# Удаляем пробелы в начале и конце
cleaned = cleaned.strip()
return cleaned
def sanitize_filename(self, filename: str) -> str:
"""Очищает имя файла от недопустимых символов"""
result_chars = []
for char in filename:
if char in self.invalid_filename_chars:
# Заменяем недопустимые символы
if char in self.invalid_char_replacements:
result_chars.append(self.invalid_char_replacements[char])
else:
result_chars.append('_') # На всякий случай заменяем на подчеркивание
else:
result_chars.append(char)
result = ''.join(result_chars)
return result
def detect_language(self, text: str) -> Optional[str]:
"""Определяет язык по специфическим символам в тексте"""
text_lower = text.lower()
# Считаем количество специфических символов для каждого языка
language_scores = {}
for lang, chars in self.language_specific_chars.items():
count = sum(1 for char in text_lower if char in chars)
if count > 0:
language_scores[lang] = count
if not language_scores:
return None
# Возвращаем язык с максимальным количеством специфических символов
return max(language_scores.items(), key=lambda x: x[1])[0]
def transliterate_text(self, text: str, language: Optional[str] = None, check_ascii: bool = True, filename: str = "") -> Optional[str]:
"""Транслитерирует текст с учетом выбранного языка.
filename - имя файла для вывода в сообщениях об ошибках."""
if not language:
language = self.detect_language(text)
if not language:
# Если язык не определен, используем общую европейскую таблицу
language = 'european'
transliteration_table = self.transliteration_tables[language]
result_chars = []
for char in text:
if char in transliteration_table:
result_chars.append(transliteration_table[char])
elif self._is_cjk(char):
# Заменяем китайские, японские, корейские иероглифы на #
result_chars.append('#')
elif self._should_transliterate(char):
# Пытаемся нормализовать символ
try:
# Пробуем декомпозицию символа
decomposed = unicodedata.normalize('NFD', char)
# Убираем диакритические знаки
base_char = ''.join(c for c in decomposed if not unicodedata.combining(c))
if base_char and base_char != char:
result_chars.append(base_char)
else:
# Если нормализация не помогла, оставляем символ как есть
result_chars.append(char)
except:
# Если произошла ошибка, оставляем символ как есть
result_chars.append(char)
else:
result_chars.append(char)
result = ''.join(result_chars)
# Сохраняем результат транслитерации в состояние (если это имя файла)
if filename:
self.file_state['transliterated'] = result
# Проверка на не-ASCII символы (только для имен файлов)
if check_ascii:
has_non_ascii, highlighted = self.has_non_ascii(result)
if has_non_ascii:
file_info = f" (файл: {filename})" if filename else ""
# Безопасный вывод в stderr с заменой проблемных символов
sys.stdout.flush()
safe_stderr_write(f"Ошибка: после транслитерации остались не-ASCII символы{file_info}:\n")
safe_stderr_write(f"Исходный текст: {repr(text)}\n")
# Пытаемся записать highlighted, но если не получается, заменяем проблемные символы
try:
safe_stderr_write(f"Транслитерированный: {repr(result)}\n")
except Exception:
safe_highlighted = result.encode('ascii', errors='backslashreplace').decode('ascii')
safe_stderr_write(f"Транслитерированный (с заменой): {safe_highlighted}\n")
# Дополнительно показываем, какие именно символы не-ASCII
non_ascii_chars = [ch for ch in result if ord(ch) > 127]
safe_stderr_write(f"Оставшиеся не-ASCII символы: {repr(''.join(non_ascii_chars))}\n")
# Логируем состояние файла при ошибке
self._log_file_state_on_error()
return None
return result
def _should_transliterate(self, char: str) -> bool:
"""Определяет, нужно ли транслитерировать символ"""
# Если символ уже ASCII, не транслитерируем
if ord(char) <= 127:
return False
# Проверяем, является ли символ буквой
category = unicodedata.category(char)
# Буквенные символы: L* категории
if category.startswith('L'):
return True
# Символы пунктуации: P* категории
if category.startswith('P'):
return True
# Символы символов (валюта и т.д.): S* категории
if category.startswith('S'):
return True
# Числовые символы: N* категории (не ASCII цифры)
if category.startswith('N') and ord(char) > 127:
return True
# Проверяем диапазоны Unicode для различных символов
code = ord(char)
# Общие диапазоны символов, которые нужно транслитерировать:
# Диапазоны Unicode для различных символов
ranges_to_transliterate = [
(0x0080, 0x00FF), # Latin-1 Supplement
(0x0100, 0x017F), # Latin Extended-A
(0x0180, 0x024F), # Latin Extended-B
(0x0250, 0x02AF), # IPA Extensions
(0x02B0, 0x02FF), # Spacing Modifier Letters