SilverElixir commited on
Commit
4e6721e
·
1 Parent(s): a348a03

TikTok UX: localized slideshow status (25 langs), audio duration probe, status lives through music fetch

Browse files
Files changed (5) hide show
  1. bot.py +2 -0
  2. lumen_lang.py +27 -0
  3. lumen_tiktok.py +20 -0
  4. lumen_tiktok_flow.py +14 -8
  5. tests/test_bot_tiktok.py +128 -0
bot.py CHANGED
@@ -692,6 +692,7 @@ __all__ = [
692
  "_tiktok_music_page_id",
693
  "_write_mp3_tags",
694
  "_download_url_bin",
 
695
  "_probe_video_dimensions",
696
  "_generate_video_thumbnail",
697
  "_probe_and_thumbnail_from_bytes",
@@ -968,6 +969,7 @@ from lumen_tiktok import (
968
  _tiktok_music_page_id,
969
  _write_mp3_tags,
970
  _download_url_bin,
 
971
  _probe_video_dimensions,
972
  _generate_video_thumbnail,
973
  _communicate_process,
 
692
  "_tiktok_music_page_id",
693
  "_write_mp3_tags",
694
  "_download_url_bin",
695
+ "_probe_audio_duration",
696
  "_probe_video_dimensions",
697
  "_generate_video_thumbnail",
698
  "_probe_and_thumbnail_from_bytes",
 
969
  _tiktok_music_page_id,
970
  _write_mp3_tags,
971
  _download_url_bin,
972
+ _probe_audio_duration,
973
  _probe_video_dimensions,
974
  _generate_video_thumbnail,
975
  _communicate_process,
lumen_lang.py CHANGED
@@ -591,6 +591,14 @@ STRINGS: dict[str, dict[str, str]] = {
591
  "ru": "Музыка из TikTok",
592
  "uk": "Музика з TikTok",
593
  },
 
 
 
 
 
 
 
 
594
  # ── Статусы ──
595
  "status_generating_image": {
596
  "be": "Генерую малюнак",
@@ -739,6 +747,7 @@ PACK_PT: dict[str, str] = {
739
  "tiktok_generic_fail": "Não consegui baixar este vídeo ou apresentação do TikTok. Tente outro link ou repita um pouco mais tarde.",
740
  "tiktok_author": "Autor do TikTok",
741
  "tiktok_music": "Música do TikTok",
 
742
  "status_generating_image": "Gerando imagem",
743
  "status_voicing": "Convertendo texto em voz",
744
  "status_taking_longer": "Isso vai demorar um pouco mais…",
@@ -819,6 +828,7 @@ PACK_AR: dict[str, str] = {
819
  "tiktok_generic_fail": "تعذر تنزيل هذا الفيديو أو عرض الشرائح من TikTok. جرب رابطًا آخر أو أعد المحاولة بعد قليل.",
820
  "tiktok_author": "مؤلف TikTok",
821
  "tiktok_music": "موسيقى من TikTok",
 
822
  "status_generating_image": "جارٍ توليد الصورة",
823
  "status_voicing": "جارٍ تحويل النص إلى صوت",
824
  "status_taking_longer": "سيستغرق هذا وقتًا أطول قليلًا…",
@@ -899,6 +909,7 @@ PACK_TR: dict[str, str] = {
899
  "tiktok_generic_fail": "Bu video veya slayt TikTok'tan indirilemedi. Başka bağlantı dene veya biraz sonra tekrarla.",
900
  "tiktok_author": "TikTok yazarı",
901
  "tiktok_music": "TikTok müziği",
 
902
  "status_generating_image": "Görsel oluşturuluyor",
903
  "status_voicing": "Metin seslendiriliyor",
904
  "status_taking_longer": "Bu biraz daha uzun sürecek…",
@@ -979,6 +990,7 @@ PACK_DE: dict[str, str] = {
979
  "tiktok_generic_fail": "Dieses Video bzw. diese Slideshow ließ sich nicht von TikTok laden. Anderen Link versuchen oder etwas später wiederholen.",
980
  "tiktok_author": "TikTok-Autor",
981
  "tiktok_music": "Musik von TikTok",
 
982
  "status_generating_image": "Bild wird erzeugt",
983
  "status_voicing": "Text wird vertont",
984
  "status_taking_longer": "Das dauert etwas länger…",
@@ -1059,6 +1071,7 @@ PACK_FR: dict[str, str] = {
1059
  "tiktok_generic_fail": "Impossible de télécharger cette vidéo ou ce diaporama depuis TikTok. Essaie un autre lien ou réessaie un peu plus tard.",
1060
  "tiktok_author": "Auteur TikTok",
1061
  "tiktok_music": "Musique de TikTok",
 
1062
  "status_generating_image": "Génération de l'image",
1063
  "status_voicing": "Conversion du texte en voix",
1064
  "status_taking_longer": "Ça va prendre un peu plus de temps…",
@@ -1139,6 +1152,7 @@ PACK_IT: dict[str, str] = {
1139
  "tiktok_generic_fail": "Impossibile scaricare questo video o slideshow da TikTok. Prova un altro link o ripeti tra poco.",
1140
  "tiktok_author": "Autore TikTok",
1141
  "tiktok_music": "Musica di TikTok",
 
1142
  "status_generating_image": "Generazione dell'immagine",
1143
  "status_voicing": "Conversione del testo in voce",
1144
  "status_taking_longer": "Ci vorrà un po' più di tempo…",
@@ -1219,6 +1233,7 @@ PACK_HI: dict[str, str] = {
1219
  "tiktok_generic_fail": "यह वीडियो या स्लाइडशो TikTok से डाउनलोड नहीं हो सका। दूसरा लिंक आज़माओ या थोड़ी देर बाद दोहराओ।",
1220
  "tiktok_author": "TikTok लेखक",
1221
  "tiktok_music": "TikTok संगीत",
 
1222
  "status_generating_image": "चित्र बनाया जा रहा है",
1223
  "status_voicing": "टेक्स्ट को आवाज़ दी जा रही है",
1224
  "status_taking_longer": "इसमें थोड़ा और समय लगेगा…",
@@ -1299,6 +1314,7 @@ PACK_ID: dict[str, str] = {
1299
  "tiktok_generic_fail": "Tidak bisa mengunduh video atau slideshow ini dari TikTok. Coba tautan lain atau ulangi sedikit lagi.",
1300
  "tiktok_author": "Penulis TikTok",
1301
  "tiktok_music": "Musik dari TikTok",
 
1302
  "status_generating_image": "Membuat gambar",
1303
  "status_voicing": "Mengucapkan teks",
1304
  "status_taking_longer": "Ini akan memakan waktu sedikit lebih lama…",
@@ -1379,6 +1395,7 @@ PACK_MS: dict[str, str] = {
1379
  "tiktok_generic_fail": "Tidak dapat memuat turun video atau slaid ini dari TikTok. Cuba pautan lain atau ulangi sedikit lagi.",
1380
  "tiktok_author": "Pengarang TikTok",
1381
  "tiktok_music": "Muzik dari TikTok",
 
1382
  "status_generating_image": "Menjana gambar",
1383
  "status_voicing": "Mengucapkan teks",
1384
  "status_taking_longer": "Ini akan mengambil masa sedikit lebih lama…",
@@ -1459,6 +1476,7 @@ PACK_VI: dict[str, str] = {
1459
  "tiktok_generic_fail": "Không tải được video hay slideshow này từ TikTok. Thử liên kết khác hoặc lặp lại sau một lúc.",
1460
  "tiktok_author": "Tác giả TikTok",
1461
  "tiktok_music": "Nhạc từ TikTok",
 
1462
  "status_generating_image": "Đang tạo ảnh",
1463
  "status_voicing": "Đang đọc văn bản",
1464
  "status_taking_longer": "Việc này sẽ mất lâu hơn một chút…",
@@ -1539,6 +1557,7 @@ PACK_TH: dict[str, str] = {
1539
  "tiktok_generic_fail": "ดาวน์โหลดวิดีโอหรือสไลด์โชว์นี้จาก TikTok ไม่ได้ ลองลิงก์อื่นหรือทำซ้ำอีกสักครู่",
1540
  "tiktok_author": "ผู้เขียน TikTok",
1541
  "tiktok_music": "เพลงจาก TikTok",
 
1542
  "status_generating_image": "กำลังสร้างรูปภาพ",
1543
  "status_voicing": "กำลังอ่านข้อความ",
1544
  "status_taking_longer": "อันนี้จะใช้เวลานานกว่านิดหน่อย…",
@@ -1619,6 +1638,7 @@ PACK_FA: dict[str, str] = {
1619
  "tiktok_generic_fail": "نتوانستم این ویدیو یا اسلایدشو را از TikTok دانلود کنم. لینک دیگری امتحان کن یا کمی بعد تکرار کن.",
1620
  "tiktok_author": "نویسنده TikTok",
1621
  "tiktok_music": "موسیقی از TikTok",
 
1622
  "status_generating_image": "در حال تولید تصویر",
1623
  "status_voicing": "در حال تبدیل متن به گفتار",
1624
  "status_taking_longer": "این کمی بیشتر طول می‌کشد…",
@@ -1699,6 +1719,7 @@ PACK_UR: dict[str, str] = {
1699
  "tiktok_generic_fail": "یہ ویڈیو یا سلائیڈشو TikTok سے ڈاؤن لوڈ نہ ہو سکا۔ دوسرا لنک آزماؤ یا تھوڑی دیر بعد دہراؤ۔",
1700
  "tiktok_author": "TikTok مصنف",
1701
  "tiktok_music": "TikTok موسیقی",
 
1702
  "status_generating_image": "تصویر بنائی جا رہی ہے",
1703
  "status_voicing": "متن کو آواز دی جا رہی ہے",
1704
  "status_taking_longer": "اس میں تھوڑا اور وقت لگے گا…",
@@ -1779,6 +1800,7 @@ PACK_UZ: dict[str, str] = {
1779
  "tiktok_generic_fail": "Bu video yoki slaydshouni TikTok'dan yuklab bo'lmadi. Boshqa havola urin yoki birozdan keyin takrorla.",
1780
  "tiktok_author": "TikTok muallifi",
1781
  "tiktok_music": "TikTok musiqasi",
 
1782
  "status_generating_image": "Rasm yaratilmoqda",
1783
  "status_voicing": "Matn ovozlanmoqda",
1784
  "status_taking_longer": "Bu biroz ko'proq vaqt oladi…",
@@ -1859,6 +1881,7 @@ PACK_BN: dict[str, str] = {
1859
  "tiktok_generic_fail": "TikTok থেকে এই ভিডিও বা স্লাইডশো ডাউনলোড করা যায়নি। অন্য লিংক চেষ্টা করো বা একটু পরে পুনরাবৃত্তি করো।",
1860
  "tiktok_author": "TikTok লেখক",
1861
  "tiktok_music": "TikTok সঙ্গীত",
 
1862
  "status_generating_image": "ছবি তৈরি হচ্ছে",
1863
  "status_voicing": "টেক্সটে কণ্ঠ দেওয়া হচ্ছে",
1864
  "status_taking_longer": "এতে আরও একটু সময় লাগবে…",
@@ -1939,6 +1962,7 @@ PACK_FIL: dict[str, str] = {
1939
  "tiktok_generic_fail": "Hindi mada-download ang video o slideshow na ito mula sa TikTok. Subukan ang ibang link o ulitin mamaya.",
1940
  "tiktok_author": "May-akda ng TikTok",
1941
  "tiktok_music": "Musika mula sa TikTok",
 
1942
  "status_generating_image": "Gumuguhit ng larawan",
1943
  "status_voicing": "Binabasa ang text",
1944
  "status_taking_longer": "Medyo matatagalan ito…",
@@ -2019,6 +2043,7 @@ PACK_PL: dict[str, str] = {
2019
  "tiktok_generic_fail": "Nie udało się pobrać tego filmu ani pokazu z TikToka. Spróbuj inny link albo powtórz nieco później.",
2020
  "tiktok_author": "Autor TikToka",
2021
  "tiktok_music": "Muzyka z TikToka",
 
2022
  "status_generating_image": "Generuję obraz",
2023
  "status_voicing": "Odczytuję tekst",
2024
  "status_taking_longer": "To zajmie trochę więcej czasu…",
@@ -2099,6 +2124,7 @@ PACK_NL: dict[str, str] = {
2099
  "tiktok_generic_fail": "Deze video of slideshow kon niet van TikTok worden gedownload. Probeer een andere link of herhaal later.",
2100
  "tiktok_author": "TikTok-auteur",
2101
  "tiktok_music": "Muziek van TikTok",
 
2102
  "status_generating_image": "Afbeelding genereren",
2103
  "status_voicing": "Tekst uitspreken",
2104
  "status_taking_longer": "Dit duurt iets langer…",
@@ -2179,6 +2205,7 @@ PACK_ZU: dict[str, str] = {
2179
  "tiktok_generic_fail": "Ayikwazanga ukulandwa le vidiyo noma i-slideshow ku-TikTok. Zama esinye isixhumanisi noma phinda maduzane.",
2180
  "tiktok_author": "Umbhali we-TikTok",
2181
  "tiktok_music": "Umculo ovela ku-TikTok",
 
2182
  "status_generating_image": "Kudweba isithombe",
2183
  "status_voicing": "Kufundwa umbiko",
2184
  "status_taking_longer": "Lokhu kuzothatha isikhathi eside…",
 
591
  "ru": "Музыка из TikTok",
592
  "uk": "Музика з TikTok",
593
  },
594
+ "tiktok_dl_slideshow": {
595
+ "be": "Слайдшоў TikTok: {shown}/{total}",
596
+ "en": "TikTok slideshow: {shown}/{total}",
597
+ "es": "Presentación de TikTok: {shown}/{total}",
598
+ "kk": "TikTok слайдшоуы: {shown}/{total}",
599
+ "ru": "Слайдшоу TikTok: {shown}/{total}",
600
+ "uk": "Слайдшоу TikTok: {shown}/{total}",
601
+ },
602
  # ── Статусы ──
603
  "status_generating_image": {
604
  "be": "Генерую малюнак",
 
747
  "tiktok_generic_fail": "Não consegui baixar este vídeo ou apresentação do TikTok. Tente outro link ou repita um pouco mais tarde.",
748
  "tiktok_author": "Autor do TikTok",
749
  "tiktok_music": "Música do TikTok",
750
+ "tiktok_dl_slideshow": "Apresentação de slides do TikTok: {shown}/{total}",
751
  "status_generating_image": "Gerando imagem",
752
  "status_voicing": "Convertendo texto em voz",
753
  "status_taking_longer": "Isso vai demorar um pouco mais…",
 
828
  "tiktok_generic_fail": "تعذر تنزيل هذا الفيديو أو عرض الشرائح من TikTok. جرب رابطًا آخر أو أعد المحاولة بعد قليل.",
829
  "tiktok_author": "مؤلف TikTok",
830
  "tiktok_music": "موسيقى من TikTok",
831
+ "tiktok_dl_slideshow": "عرض شرائح TikTok: {shown}/{total}",
832
  "status_generating_image": "جارٍ توليد الصورة",
833
  "status_voicing": "جارٍ تحويل النص إلى صوت",
834
  "status_taking_longer": "سيستغرق هذا وقتًا أطول قليلًا…",
 
909
  "tiktok_generic_fail": "Bu video veya slayt TikTok'tan indirilemedi. Başka bağlantı dene veya biraz sonra tekrarla.",
910
  "tiktok_author": "TikTok yazarı",
911
  "tiktok_music": "TikTok müziği",
912
+ "tiktok_dl_slideshow": "TikTok slayt gösterisi: {shown}/{total}",
913
  "status_generating_image": "Görsel oluşturuluyor",
914
  "status_voicing": "Metin seslendiriliyor",
915
  "status_taking_longer": "Bu biraz daha uzun sürecek…",
 
990
  "tiktok_generic_fail": "Dieses Video bzw. diese Slideshow ließ sich nicht von TikTok laden. Anderen Link versuchen oder etwas später wiederholen.",
991
  "tiktok_author": "TikTok-Autor",
992
  "tiktok_music": "Musik von TikTok",
993
+ "tiktok_dl_slideshow": "TikTok-Diashow: {shown}/{total}",
994
  "status_generating_image": "Bild wird erzeugt",
995
  "status_voicing": "Text wird vertont",
996
  "status_taking_longer": "Das dauert etwas länger…",
 
1071
  "tiktok_generic_fail": "Impossible de télécharger cette vidéo ou ce diaporama depuis TikTok. Essaie un autre lien ou réessaie un peu plus tard.",
1072
  "tiktok_author": "Auteur TikTok",
1073
  "tiktok_music": "Musique de TikTok",
1074
+ "tiktok_dl_slideshow": "Diaporama TikTok : {shown}/{total}",
1075
  "status_generating_image": "Génération de l'image",
1076
  "status_voicing": "Conversion du texte en voix",
1077
  "status_taking_longer": "Ça va prendre un peu plus de temps…",
 
1152
  "tiktok_generic_fail": "Impossibile scaricare questo video o slideshow da TikTok. Prova un altro link o ripeti tra poco.",
1153
  "tiktok_author": "Autore TikTok",
1154
  "tiktok_music": "Musica di TikTok",
1155
+ "tiktok_dl_slideshow": "Presentazione TikTok: {shown}/{total}",
1156
  "status_generating_image": "Generazione dell'immagine",
1157
  "status_voicing": "Conversione del testo in voce",
1158
  "status_taking_longer": "Ci vorrà un po' più di tempo…",
 
1233
  "tiktok_generic_fail": "यह वीडियो या स्लाइडशो TikTok से डाउनलोड नहीं हो सका। दूसरा लिंक आज़माओ या थोड़ी देर बाद दोहराओ।",
1234
  "tiktok_author": "TikTok लेखक",
1235
  "tiktok_music": "TikTok संगीत",
1236
+ "tiktok_dl_slideshow": "TikTok स्लाइडशो: {shown}/{total}",
1237
  "status_generating_image": "चित्र बनाया जा रहा है",
1238
  "status_voicing": "टेक्स्ट को आवाज़ दी जा रही है",
1239
  "status_taking_longer": "इसमें थोड़ा और समय लगेगा…",
 
1314
  "tiktok_generic_fail": "Tidak bisa mengunduh video atau slideshow ini dari TikTok. Coba tautan lain atau ulangi sedikit lagi.",
1315
  "tiktok_author": "Penulis TikTok",
1316
  "tiktok_music": "Musik dari TikTok",
1317
+ "tiktok_dl_slideshow": "Tayangan slide TikTok: {shown}/{total}",
1318
  "status_generating_image": "Membuat gambar",
1319
  "status_voicing": "Mengucapkan teks",
1320
  "status_taking_longer": "Ini akan memakan waktu sedikit lebih lama…",
 
1395
  "tiktok_generic_fail": "Tidak dapat memuat turun video atau slaid ini dari TikTok. Cuba pautan lain atau ulangi sedikit lagi.",
1396
  "tiktok_author": "Pengarang TikTok",
1397
  "tiktok_music": "Muzik dari TikTok",
1398
+ "tiktok_dl_slideshow": "Tayangan slaid TikTok: {shown}/{total}",
1399
  "status_generating_image": "Menjana gambar",
1400
  "status_voicing": "Mengucapkan teks",
1401
  "status_taking_longer": "Ini akan mengambil masa sedikit lebih lama…",
 
1476
  "tiktok_generic_fail": "Không tải được video hay slideshow này từ TikTok. Thử liên kết khác hoặc lặp lại sau một lúc.",
1477
  "tiktok_author": "Tác giả TikTok",
1478
  "tiktok_music": "Nhạc từ TikTok",
1479
+ "tiktok_dl_slideshow": "Trình chiếu TikTok: {shown}/{total}",
1480
  "status_generating_image": "Đang tạo ảnh",
1481
  "status_voicing": "Đang đọc văn bản",
1482
  "status_taking_longer": "Việc này sẽ mất lâu hơn một chút…",
 
1557
  "tiktok_generic_fail": "ดาวน์โหลดวิดีโอหรือสไลด์โชว์นี้จาก TikTok ไม่ได้ ลองลิงก์อื่นหรือทำซ้ำอีกสักครู่",
1558
  "tiktok_author": "ผู้เขียน TikTok",
1559
  "tiktok_music": "เพลงจาก TikTok",
1560
+ "tiktok_dl_slideshow": "สไลด์โชว์ TikTok: {shown}/{total}",
1561
  "status_generating_image": "กำลังสร้างรูปภาพ",
1562
  "status_voicing": "กำลังอ่านข้อความ",
1563
  "status_taking_longer": "อันนี้จะใช้เวลานานกว่านิดหน่อย…",
 
1638
  "tiktok_generic_fail": "نتوانستم این ویدیو یا اسلایدشو را از TikTok دانلود کنم. لینک دیگری امتحان کن یا کمی بعد تکرار کن.",
1639
  "tiktok_author": "نویسنده TikTok",
1640
  "tiktok_music": "موسیقی از TikTok",
1641
+ "tiktok_dl_slideshow": "اسلایدشوی TikTok: {shown}/{total}",
1642
  "status_generating_image": "در حال تولید تصویر",
1643
  "status_voicing": "در حال تبدیل متن به گفتار",
1644
  "status_taking_longer": "این کمی بیشتر طول می‌کشد…",
 
1719
  "tiktok_generic_fail": "یہ ویڈیو یا سلائیڈشو TikTok سے ڈاؤن لوڈ نہ ہو سکا۔ دوسرا لنک آزماؤ یا تھوڑی دیر بعد دہراؤ۔",
1720
  "tiktok_author": "TikTok مصنف",
1721
  "tiktok_music": "TikTok موسیقی",
1722
+ "tiktok_dl_slideshow": "TikTok سلائیڈ شو: {shown}/{total}",
1723
  "status_generating_image": "تصویر بنائی جا رہی ہے",
1724
  "status_voicing": "متن کو آواز دی جا رہی ہے",
1725
  "status_taking_longer": "اس میں تھوڑا اور وقت لگے گا…",
 
1800
  "tiktok_generic_fail": "Bu video yoki slaydshouni TikTok'dan yuklab bo'lmadi. Boshqa havola urin yoki birozdan keyin takrorla.",
1801
  "tiktok_author": "TikTok muallifi",
1802
  "tiktok_music": "TikTok musiqasi",
1803
+ "tiktok_dl_slideshow": "TikTok slayd-shousi: {shown}/{total}",
1804
  "status_generating_image": "Rasm yaratilmoqda",
1805
  "status_voicing": "Matn ovozlanmoqda",
1806
  "status_taking_longer": "Bu biroz ko'proq vaqt oladi…",
 
1881
  "tiktok_generic_fail": "TikTok থেকে এই ভিডিও বা স্লাইডশো ডাউনলোড করা যায়নি। অন্য লিংক চেষ্টা করো বা একটু পরে পুনরাবৃত্তি করো।",
1882
  "tiktok_author": "TikTok লেখক",
1883
  "tiktok_music": "TikTok সঙ্গীত",
1884
+ "tiktok_dl_slideshow": "TikTok স্লাইডশো: {shown}/{total}",
1885
  "status_generating_image": "ছবি তৈরি হচ্ছে",
1886
  "status_voicing": "টেক্সটে কণ্ঠ দেওয়া হচ্ছে",
1887
  "status_taking_longer": "এতে আরও একটু সময় লাগবে…",
 
1962
  "tiktok_generic_fail": "Hindi mada-download ang video o slideshow na ito mula sa TikTok. Subukan ang ibang link o ulitin mamaya.",
1963
  "tiktok_author": "May-akda ng TikTok",
1964
  "tiktok_music": "Musika mula sa TikTok",
1965
+ "tiktok_dl_slideshow": "Slideshow ng TikTok: {shown}/{total}",
1966
  "status_generating_image": "Gumuguhit ng larawan",
1967
  "status_voicing": "Binabasa ang text",
1968
  "status_taking_longer": "Medyo matatagalan ito…",
 
2043
  "tiktok_generic_fail": "Nie udało się pobrać tego filmu ani pokazu z TikToka. Spróbuj inny link albo powtórz nieco później.",
2044
  "tiktok_author": "Autor TikToka",
2045
  "tiktok_music": "Muzyka z TikToka",
2046
+ "tiktok_dl_slideshow": "Pokaz slajdów TikTok: {shown}/{total}",
2047
  "status_generating_image": "Generuję obraz",
2048
  "status_voicing": "Odczytuję tekst",
2049
  "status_taking_longer": "To zajmie trochę więcej czasu…",
 
2124
  "tiktok_generic_fail": "Deze video of slideshow kon niet van TikTok worden gedownload. Probeer een andere link of herhaal later.",
2125
  "tiktok_author": "TikTok-auteur",
2126
  "tiktok_music": "Muziek van TikTok",
2127
+ "tiktok_dl_slideshow": "Diavoorstelling TikTok: {shown}/{total}",
2128
  "status_generating_image": "Afbeelding genereren",
2129
  "status_voicing": "Tekst uitspreken",
2130
  "status_taking_longer": "Dit duurt iets langer…",
 
2205
  "tiktok_generic_fail": "Ayikwazanga ukulandwa le vidiyo noma i-slideshow ku-TikTok. Zama esinye isixhumanisi noma phinda maduzane.",
2206
  "tiktok_author": "Umbhali we-TikTok",
2207
  "tiktok_music": "Umculo ovela ku-TikTok",
2208
+ "tiktok_dl_slideshow": "Isilayidi se-TikTok: {shown}/{total}",
2209
  "status_generating_image": "Kudweba isithombe",
2210
  "status_voicing": "Kufundwa umbiko",
2211
  "status_taking_longer": "Lokhu kuzothatha isikhathi eside…",
lumen_tiktok.py CHANGED
@@ -315,6 +315,26 @@ async def _communicate_process(proc: asyncio.subprocess.Process, *, timeout: flo
315
  raise
316
 
317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  async def _probe_video_dimensions(path: str) -> tuple[int, int, int]:
319
  """Возвращает (duration, width, height): без них Telegram показывает видео "сырым файлом" 0:00 (TikTok не всегда ставит faststart)."""
320
  try:
 
315
  raise
316
 
317
 
318
+ async def _probe_audio_duration(path: str) -> int:
319
+ """Длительность аудиофайла в секундах (для send_audio — без неё Telegram показывает 0:00). 0 при любой неудаче."""
320
+ try:
321
+ proc = await asyncio.create_subprocess_exec(
322
+ "ffprobe", "-v", "error",
323
+ "-show_entries", "format=duration",
324
+ "-of", "default=noprint_wrappers=1:nokey=1",
325
+ path,
326
+ stdout=asyncio.subprocess.PIPE,
327
+ stderr=asyncio.subprocess.PIPE,
328
+ )
329
+ stdout, _ = await _communicate_process(proc, timeout=15)
330
+ raw = stdout.decode(errors="replace").strip().splitlines()
331
+ if raw and raw[0] and raw[0] != "N/A":
332
+ return max(1, round(float(raw[0])))
333
+ except Exception as e:
334
+ log.warning("[ffmpeg] Audio probe failed: %s", e)
335
+ return 0
336
+
337
+
338
  async def _probe_video_dimensions(path: str) -> tuple[int, int, int]:
339
  """Возвращает (duration, width, height): без них Telegram показывает видео "сырым файлом" 0:00 (TikTok не всегда ставит faststart)."""
340
  try:
lumen_tiktok_flow.py CHANGED
@@ -101,6 +101,7 @@ async def _send_tiktok_music(session, media_data: dict, message: Message, author
101
 
102
  # вшиваем метаданные и обложку в MP3 перед отправкой — чтобы теги видели и другие плееры
103
  tagged_music_bytes = music_bytes
 
104
  try:
105
  with tempfile.TemporaryDirectory() as tmp_dir:
106
  tmp_mp3_path = os.path.join(tmp_dir, "music.mp3")
@@ -110,6 +111,8 @@ async def _send_tiktok_music(session, media_data: dict, message: Message, author
110
  if os.path.exists(tmp_mp3_path) and os.path.getsize(tmp_mp3_path) > 0:
111
  with open(tmp_mp3_path, "rb") as f:
112
  tagged_music_bytes = f.read()
 
 
113
  except Exception as tag_err:
114
  log.warning("[tiktok] failed to write embedded tags to MP3: %s", tag_err)
115
 
@@ -117,10 +120,11 @@ async def _send_tiktok_music(session, media_data: dict, message: Message, author
117
  # иначе multipart-имя файла битое.
118
  safe_title = re.sub(r'[\\/:*?"<>|\x00-\x1f]', "_", cleaned_title[:60]).strip() or "track"
119
  await bot.bot.send_audio(
120
- chat_id=message.chat.id,
121
- audio=BufferedInputFile(tagged_music_bytes, filename=f"{safe_title}.mp3"),
122
  title=cleaned_title,
123
  performer=performer_name,
 
124
  thumbnail=thumbnail_file,
125
  reply_to_message_id=message.message_id
126
  )
@@ -166,10 +170,11 @@ async def _try_send_tiktok_slideshow(
166
  if images and isinstance(images, list):
167
  # TikTok разрешает до 35 слайдов, Telegram — 10 за вызов: берём весь пост и шлём несколькими группами.
168
  images_to_fetch = images[:bot.TIKTOK_SLIDESHOW_MAX_ITEMS]
169
- status_text = f"Скачиваю слайдшоу TikTok ({len(images_to_fetch)} слайдов)"
170
- if len(images) > len(images_to_fetch):
171
- status_text += f" — показаны первые {len(images_to_fetch)} из {len(images)}"
172
- await bot._edit_message_quietly(status, status_text)
 
173
  # Качаем слайды параллельно gather; конкурентность режем семафором, чтобы не занимать весь пул сессии.
174
  fetch_urls = _slideshow_slide_urls(media_data, images_to_fetch)
175
 
@@ -217,7 +222,7 @@ async def _try_send_tiktok_slideshow(
217
  else:
218
  media_items.append(InputMediaPhoto(media=BufferedInputFile(item_bytes, filename=f"photo_{idx}.jpg")))
219
  if media_items:
220
- await bot._delete_message_quietly(status)
221
  if len(media_items) == 1:
222
  # Один уцелевший слайд — обычным send_photo/send_video: media group требует минимум 2.
223
  only_item = media_items[0]
@@ -243,6 +248,7 @@ async def _try_send_tiktok_slideshow(
243
  # Пауза между группами — против анти-флуда.
244
  await asyncio.sleep(0.3)
245
  await _send_tiktok_music(session, media_data, message, author, headers)
 
246
  return True
247
  return False
248
 
@@ -315,8 +321,8 @@ async def _send_tiktok_single_video(
315
  )
316
  continue
317
 
318
- await bot._delete_message_quietly(status)
319
  await _send_tiktok_music(session, media_data, message, author, headers)
 
320
  return
321
 
322
  if hit_size_limit:
 
101
 
102
  # вшиваем метаданные и обложку в MP3 перед отправкой — чтобы теги видели и другие плееры
103
  tagged_music_bytes = music_bytes
104
+ music_duration = 0
105
  try:
106
  with tempfile.TemporaryDirectory() as tmp_dir:
107
  tmp_mp3_path = os.path.join(tmp_dir, "music.mp3")
 
111
  if os.path.exists(tmp_mp3_path) and os.path.getsize(tmp_mp3_path) > 0:
112
  with open(tmp_mp3_path, "rb") as f:
113
  tagged_music_bytes = f.read()
114
+ # Длительность — явно: без неё Telegram показывает 0:00 (та же история, что была с видео).
115
+ music_duration = await bot._probe_audio_duration(tmp_mp3_path)
116
  except Exception as tag_err:
117
  log.warning("[tiktok] failed to write embedded tags to MP3: %s", tag_err)
118
 
 
120
  # иначе multipart-имя файла битое.
121
  safe_title = re.sub(r'[\\/:*?"<>|\x00-\x1f]', "_", cleaned_title[:60]).strip() or "track"
122
  await bot.bot.send_audio(
123
+ chat_id=message.chat.id,
124
+ audio=BufferedInputFile(tagged_music_bytes, filename=f"{safe_title}.mp3"),
125
  title=cleaned_title,
126
  performer=performer_name,
127
+ duration=music_duration if music_duration > 0 else None,
128
  thumbnail=thumbnail_file,
129
  reply_to_message_id=message.message_id
130
  )
 
170
  if images and isinstance(images, list):
171
  # TikTok разрешает до 35 слайдов, Telegram — 10 за вызов: берём весь пост и шлём несколькими группами.
172
  images_to_fetch = images[:bot.TIKTOK_SLIDESHOW_MAX_ITEMS]
173
+ # Статус слайдшоу — через переводы (был захардкожен по-русски).
174
+ await bot._edit_message_quietly(status, bot._t(
175
+ message.chat.id, "tiktok_dl_slideshow",
176
+ shown=len(images_to_fetch), total=len(images),
177
+ ))
178
  # Качаем слайды параллельно gather; конкурентность режем семафором, чтобы не занимать весь пул сессии.
179
  fetch_urls = _slideshow_slide_urls(media_data, images_to_fetch)
180
 
 
222
  else:
223
  media_items.append(InputMediaPhoto(media=BufferedInputFile(item_bytes, filename=f"photo_{idx}.jpg")))
224
  if media_items:
225
+ # Статус держим до конца (включая музыку ниже) — иначе человек висит в тишине, пока качается трек.
226
  if len(media_items) == 1:
227
  # Один уцелевший слайд — обычным send_photo/send_video: media group требует минимум 2.
228
  only_item = media_items[0]
 
248
  # Пауза между группами — против анти-флуда.
249
  await asyncio.sleep(0.3)
250
  await _send_tiktok_music(session, media_data, message, author, headers)
251
+ await bot._delete_message_quietly(status)
252
  return True
253
  return False
254
 
 
321
  )
322
  continue
323
 
 
324
  await _send_tiktok_music(session, media_data, message, author, headers)
325
+ await bot._delete_message_quietly(status)
326
  return
327
 
328
  if hit_size_limit:
tests/test_bot_tiktok.py CHANGED
@@ -1087,3 +1087,131 @@ def test_communicate_process_propagates_communicate_failure():
1087
 
1088
  asyncio.run(run())
1089
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1087
 
1088
  asyncio.run(run())
1089
 
1090
+
1091
+ def test_slideshow_status_uses_localized_key():
1092
+ # Статус слайдшоу был захардкожен по-русски — теперь ключ tiktok_dl_slideshow с плейсхолдерами.
1093
+ text = bot._t(999451, "tiktok_dl_slideshow", shown=35, total=40)
1094
+ assert "35/40" in text
1095
+ try:
1096
+ assert "слайдов" in text or "slideshow" in text.lower() or "Слайдшоу" in text
1097
+ finally:
1098
+ bot.chat_state.pop(999451, None)
1099
+
1100
+
1101
+ def test_send_tiktok_music_passes_audio_duration():
1102
+ # Без явной длительности Telegram показывает 0:00 — пробинг обязан доезжать до send_audio.
1103
+ import lumen_tiktok_flow
1104
+
1105
+ incoming = _FakeIncomingMessage(999452)
1106
+ incoming.message_id = 1
1107
+ incoming.from_user = SimpleNamespace(language_code="ru")
1108
+ sent = {}
1109
+
1110
+ class _AudioBot:
1111
+ async def send_audio(self, **kwargs):
1112
+ sent.update(kwargs)
1113
+ return SimpleNamespace()
1114
+
1115
+ async def fake_download(session, url, headers=None):
1116
+ return b"fake-bytes"
1117
+
1118
+ async def fake_probe(path):
1119
+ return 42
1120
+
1121
+ def fake_write_tags(path, title, artist, cover):
1122
+ pass
1123
+
1124
+ media_data = {
1125
+ "music": "https://tikwm.com/song.mp3",
1126
+ "music_info": {"title": "Song", "author": "Auth"},
1127
+ "author": {"nickname": "Nick", "unique_id": "@nick"},
1128
+ }
1129
+ original_download = bot._download_url_bin
1130
+ original_write_tags = bot._write_mp3_tags
1131
+ original_probe = bot._probe_audio_duration
1132
+ original_bot = bot.bot
1133
+ bot._download_url_bin = fake_download
1134
+ bot._write_mp3_tags = fake_write_tags
1135
+ bot._probe_audio_duration = fake_probe
1136
+ bot.bot = _AudioBot()
1137
+ try:
1138
+ asyncio.run(lumen_tiktok_flow._send_tiktok_music(None, media_data, incoming, "Nick", {}))
1139
+ assert sent.get("duration") == 42
1140
+ assert sent.get("title") == "Song"
1141
+ finally:
1142
+ bot._download_url_bin = original_download
1143
+ bot._write_mp3_tags = original_write_tags
1144
+ bot._probe_audio_duration = original_probe
1145
+ bot.bot = original_bot
1146
+ bot.chat_state.pop(999452, None)
1147
+
1148
+
1149
+ def test_single_video_status_deleted_after_music():
1150
+ # Статус живёт и во время скачивания музыки тоже — сносится только после неё, а не до.
1151
+ import lumen_tiktok_flow
1152
+
1153
+ tikwm_json = {
1154
+ "code": 0,
1155
+ "data": {
1156
+ "play": "https://tikwm.com/sd.mp4", "size": 1000,
1157
+ "author": {"nickname": "TestAuthor"},
1158
+ },
1159
+ }
1160
+ incoming = _FakeIncomingMessage(999453)
1161
+ incoming.message_id = 1
1162
+ fake_bot = _FakeTikTokBot()
1163
+ events = []
1164
+
1165
+ async def fake_get_http_session():
1166
+ return _FakeTikTokSession(tikwm_json)
1167
+
1168
+ async def fake_resolve(session, url):
1169
+ return url
1170
+
1171
+ async def fake_download_url_bin(session, url, headers=None):
1172
+ return b"\x00" * 100
1173
+
1174
+ async def fake_probe_dims(path):
1175
+ return 0, 0, 0
1176
+
1177
+ async def fake_thumb(path, duration):
1178
+ return None
1179
+
1180
+ async def rec_music(*args, **kwargs):
1181
+ events.append("music")
1182
+ return None
1183
+
1184
+ async def rec_delete(message):
1185
+ events.append("delete")
1186
+ return True
1187
+
1188
+ original_get_session = bot._get_http_session
1189
+ original_resolve = bot._resolve_tiktok_short
1190
+ original_download = bot._download_url_bin
1191
+ original_probe = bot._probe_video_dimensions
1192
+ original_thumb = bot._generate_video_thumbnail
1193
+ original_bot = bot.bot
1194
+ original_delete = bot._delete_message_quietly
1195
+ original_music = lumen_tiktok_flow._send_tiktok_music
1196
+ bot._get_http_session = fake_get_http_session
1197
+ bot._resolve_tiktok_short = fake_resolve
1198
+ bot._download_url_bin = fake_download_url_bin
1199
+ bot._probe_video_dimensions = fake_probe_dims
1200
+ bot._generate_video_thumbnail = fake_thumb
1201
+ bot.bot = fake_bot
1202
+ bot._delete_message_quietly = rec_delete
1203
+ lumen_tiktok_flow._send_tiktok_music = rec_music
1204
+ try:
1205
+ asyncio.run(bot.handle_tiktok(incoming, "https://www.tiktok.com/@test/video/123"))
1206
+ assert events == ["music", "delete"]
1207
+ assert len(fake_bot.sent_videos) == 1
1208
+ finally:
1209
+ bot._get_http_session = original_get_session
1210
+ bot._resolve_tiktok_short = original_resolve
1211
+ bot._download_url_bin = original_download
1212
+ bot._probe_video_dimensions = original_probe
1213
+ bot._generate_video_thumbnail = original_thumb
1214
+ bot.bot = original_bot
1215
+ bot._delete_message_quietly = original_delete
1216
+ lumen_tiktok_flow._send_tiktok_music = original_music
1217
+