diff --git a/src/Filee.App/Assets/i18n/ar.json b/src/Filee.App/Assets/i18n/ar.json index 58f678a03..a876d0777 100644 --- a/src/Filee.App/Assets/i18n/ar.json +++ b/src/Filee.App/Assets/i18n/ar.json @@ -976,6 +976,20 @@ "presets.split": "ملف لكل صفحة", "presets.ask_pages": "السؤال في كل مرة", "presets.ask_pages_hint": "عند إفلات ملف PDF على شريط الأدوات الدائري، تُفتح نافذة صغيرة لاختيار صفحاته. تبدأ النافذة بالنطاق أعلاه، وهو أيضًا ما تستخدمه مجلدات المراقبة وسطر الأوامر.", + "presets.ask_compression": "السؤال في كل مرة", + "presets.ask_compression_hint": "عند إفلات الملفات على شريط الأدوات الدائري تُفتح نافذة صغيرة لاختيار مقدار ضغطها، مع الحجم التقريبي الذي ستصل إليه. الإعداد أعلاه هو ما تبدأ به النافذة، وهو ما تستخدمه المجلدات المراقبة وسطر الأوامر.", + "compression.title": "اختيار الضغط", + "compression.files": "{0} ملفات", + "compression.size_now": "الحجم الحالي: {0}", + "compression.estimate_label": "الحجم المقدّر", + "compression.estimating": "جارٍ تقدير الحجم…", + "compression.estimate": "نحو {0} (أصغر بنسبة {1}%)", + "compression.not_smaller": "نحو {0}، ليس أصغر من الآن.", + "compression.not_smaller_kept": "على الأرجح لن يصغر: يُحتفظ بالأصل كما هو.", + "compression.unknown": "لا يستطيع Filee تقدير حجم هذه الملفات.", + "compression.estimate_hint": "هذا تقدير: الحجم الفعلي يعتمد على محتوى الملفات.", + "compression.estimate_hint_video": "هذا تقدير: يعتمد الحجم الفعلي للفيديو كثيرًا على صورته (تسجيل شاشة ثابت يكون أصغر بكثير).", + "compression.remember": "تذكّر لهذا الإعداد المسبق", "pages.title": "اختيار الصفحات", "pages.file_of": "الملف {0} من {1}", "pages.count": "عدد صفحات ملف PDF هذا: {0}", diff --git a/src/Filee.App/Assets/i18n/bn-BD.json b/src/Filee.App/Assets/i18n/bn-BD.json index 4edb7bb39..9e0c2bfa4 100644 --- a/src/Filee.App/Assets/i18n/bn-BD.json +++ b/src/Filee.App/Assets/i18n/bn-BD.json @@ -975,6 +975,20 @@ "presets.split": "পৃষ্ঠা প্রতি একটি ফাইল", "presets.ask_pages": "প্রতিবার জিজ্ঞাসা করুন", "presets.ask_pages_hint": "ডোনাটে একটি পিডিএফ ড্রপ করলে এর পৃষ্ঠা নির্বাচন করার জন্য একটি ছোট উইন্ডো খোলে। ওপরের পরিসরটি দিয়ে উইন্ডোটি শুরু হয়, এবং ওয়াচ ফোল্ডার ও কমান্ড লাইন সেটিই ব্যবহার করে।", + "presets.ask_compression": "প্রতিবার জিজ্ঞাসা করুন", + "presets.ask_compression_hint": "ডোনাটে ফাইল ছাড়লে একটি ছোট উইন্ডো খোলে, যেখানে কতটা সংকুচিত হবে তা বেছে নিতে ও আনুমানিক আকার দেখতে পারেন। উপরের সেটিং উইন্ডোর শুরুর মান, আর নজরে রাখা ফোল্ডার ও কমান্ড লাইন এটিই ব্যবহার করে।", + "compression.title": "সংকোচন বেছে নিন", + "compression.files": "{0}টি ফাইল", + "compression.size_now": "এখনকার আকার: {0}", + "compression.estimate_label": "আনুমানিক আকার", + "compression.estimating": "আকার অনুমান করা হচ্ছে…", + "compression.estimate": "প্রায় {0} ({1}% ছোট)", + "compression.not_smaller": "প্রায় {0}, এখনকার চেয়ে ছোট নয়।", + "compression.not_smaller_kept": "সম্ভবত ছোট হবে না: মূল ফাইল যেমন আছে তেমনই রাখা হবে।", + "compression.unknown": "Filee এই ফাইলগুলোর আকার অনুমান করতে পারে না।", + "compression.estimate_hint": "এটি একটি অনুমান: আসল আকার ফাইলের বিষয়বস্তুর ওপর নির্ভর করে।", + "compression.estimate_hint_video": "এটি একটি অনুমান: ভিডিওর আসল আকার তার ছবির ওপর অনেকটা নির্ভর করে (স্থির স্ক্রিন রেকর্ডিং অনেক ছোট হয়)।", + "compression.remember": "এই প্রিসেটের জন্য মনে রাখুন", "pages.title": "পৃষ্ঠা নির্বাচন করুন", "pages.file_of": "ফাইল {1}-এর মধ্যে {0}", "pages.count": "এই পিডিএফ-এ পৃষ্ঠা সংখ্যা: {0}", diff --git a/src/Filee.App/Assets/i18n/de.json b/src/Filee.App/Assets/i18n/de.json index 74f990d1c..80945a748 100644 --- a/src/Filee.App/Assets/i18n/de.json +++ b/src/Filee.App/Assets/i18n/de.json @@ -976,6 +976,20 @@ "presets.split": "Eine Datei pro Seite", "presets.ask_pages": "Jedes Mal fragen", "presets.ask_pages_hint": "Ziehst du eine PDF auf das Ringmenü, öffnet sich ein kleines Fenster, in dem du die Seiten auswählst. Es beginnt mit dem Bereich oben, den auch überwachte Ordner und die Befehlszeile verwenden.", + "presets.ask_compression": "Jedes Mal fragen", + "presets.ask_compression_hint": "Ziehst du Dateien auf das Ringmenü, öffnet sich ein kleines Fenster, in dem du die Komprimierung wählst und die ungefähre Größe siehst. Die Einstellung oben ist der Startwert des Fensters und gilt für überwachte Ordner und die Befehlszeile.", + "compression.title": "Komprimierung wählen", + "compression.files": "{0} Dateien", + "compression.size_now": "Aktuelle Größe: {0}", + "compression.estimate_label": "Geschätzte Größe", + "compression.estimating": "Größe wird geschätzt…", + "compression.estimate": "Etwa {0} ({1} % kleiner)", + "compression.not_smaller": "Etwa {0}, nicht kleiner als jetzt.", + "compression.not_smaller_kept": "Wahrscheinlich nicht kleiner: Das Original bleibt, wie es ist.", + "compression.unknown": "Filee kann die Größe dieser Dateien nicht schätzen.", + "compression.estimate_hint": "Eine Schätzung: Die tatsächliche Größe hängt vom Inhalt der Dateien ab.", + "compression.estimate_hint_video": "Eine Schätzung: Die tatsächliche Größe eines Videos hängt stark vom Bild ab (eine ruhige Bildschirmaufnahme wird viel kleiner).", + "compression.remember": "Für diese Vorlage merken", "pages.title": "Seiten auswählen", "pages.file_of": "Datei {0} von {1}", "pages.count": "Seiten in dieser PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/en.json b/src/Filee.App/Assets/i18n/en.json index 8c2d96d45..e53bd8694 100644 --- a/src/Filee.App/Assets/i18n/en.json +++ b/src/Filee.App/Assets/i18n/en.json @@ -991,6 +991,20 @@ "presets.split": "One file per page", "presets.ask_pages": "Ask every time", "presets.ask_pages_hint": "Dropping a PDF on the donut opens a small window to choose its pages. The range above is what the window starts with, and what watch folders and the command line use.", + "presets.ask_compression": "Ask every time", + "presets.ask_compression_hint": "Dropping files on the donut opens a small window to choose how much they are compressed, with the size they will roughly come to. The setting above is what the window starts with, and what watch folders and the command line use.", + "compression.title": "Choose the compression", + "compression.files": "{0} files", + "compression.size_now": "Size now: {0}", + "compression.estimate_label": "Estimated size", + "compression.estimating": "Estimating the size…", + "compression.estimate": "About {0} ({1}% smaller)", + "compression.not_smaller": "About {0}, no smaller than now.", + "compression.not_smaller_kept": "Probably no smaller: the original is kept as it is.", + "compression.unknown": "Filee can't estimate the size of these files.", + "compression.estimate_hint": "An estimate: the real size depends on what the files hold.", + "compression.estimate_hint_video": "An estimate: a video's real size depends a lot on its picture (a still screen recording comes out far smaller).", + "compression.remember": "Remember for this preset", "pages.title": "Choose pages", "pages.file_of": "File {0} of {1}", "pages.count": "Pages in this PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/es.json b/src/Filee.App/Assets/i18n/es.json index c0514ff66..ca53baa77 100644 --- a/src/Filee.App/Assets/i18n/es.json +++ b/src/Filee.App/Assets/i18n/es.json @@ -976,6 +976,20 @@ "presets.split": "Un archivo por página", "presets.ask_pages": "Preguntar cada vez", "presets.ask_pages_hint": "Al soltar un PDF en el menú circular se abre una ventana pequeña para elegir sus páginas. Empieza con el rango de arriba, que también usan las carpetas supervisadas y la línea de comandos.", + "presets.ask_compression": "Preguntar cada vez", + "presets.ask_compression_hint": "Al soltar archivos en el menú circular se abre una ventana pequeña para elegir cuánto se comprimen, con el tamaño aproximado que tendrán. El ajuste de arriba es con el que empieza la ventana, y el que usan las carpetas vigiladas y la línea de comandos.", + "compression.title": "Elegir la compresión", + "compression.files": "{0} archivos", + "compression.size_now": "Tamaño actual: {0}", + "compression.estimate_label": "Tamaño estimado", + "compression.estimating": "Estimando el tamaño…", + "compression.estimate": "Unos {0} ({1} % menos)", + "compression.not_smaller": "Unos {0}, no menos que ahora.", + "compression.not_smaller_kept": "Probablemente no será menor: se conserva el original tal cual.", + "compression.unknown": "Filee no puede estimar el tamaño de estos archivos.", + "compression.estimate_hint": "Es una estimación: el tamaño real depende del contenido de los archivos.", + "compression.estimate_hint_video": "Es una estimación: el tamaño real de un vídeo depende mucho de su imagen (una grabación de pantalla estática queda mucho menor).", + "compression.remember": "Recordar para este ajuste preestablecido", "pages.title": "Elegir páginas", "pages.file_of": "Archivo {0} de {1}", "pages.count": "Páginas de este PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/fa.json b/src/Filee.App/Assets/i18n/fa.json index 1bd8836ca..670e4a9cd 100644 --- a/src/Filee.App/Assets/i18n/fa.json +++ b/src/Filee.App/Assets/i18n/fa.json @@ -975,6 +975,20 @@ "presets.split": "یک فایل به ازای هر صفحه", "presets.ask_pages": "پرسش در هر بار", "presets.ask_pages_hint": "رها کردن یک فایل PDF روی دونات، پنجره کوچکی را برای انتخاب صفحات آن باز می‌کند. محدوده بالا چیزی است که پنجره با آن شروع می‌شود و پوشه‌های نظارتی و خط فرمان از آن استفاده می‌کنند.", + "presets.ask_compression": "پرسش در هر بار", + "presets.ask_compression_hint": "رها کردن فایل‌ها روی حلقه پنجره‌ای کوچک باز می‌کند تا میزان فشرده‌سازی را انتخاب کنید و اندازهٔ تقریبی را ببینید. تنظیم بالا مقدار آغازین پنجره است و پوشه‌های پایش‌شده و خط فرمان هم از آن استفاده می‌کنند.", + "compression.title": "انتخاب فشرده‌سازی", + "compression.files": "{0} فایل", + "compression.size_now": "اندازهٔ کنونی: {0}", + "compression.estimate_label": "اندازهٔ تخمینی", + "compression.estimating": "در حال تخمین اندازه…", + "compression.estimate": "حدود {0} ({1}٪ کوچک‌تر)", + "compression.not_smaller": "حدود {0}، کوچک‌تر از اکنون نیست.", + "compression.not_smaller_kept": "احتمالاً کوچک‌تر نمی‌شود: فایل اصلی همان‌طور که هست می‌ماند.", + "compression.unknown": "Filee نمی‌تواند اندازهٔ این فایل‌ها را تخمین بزند.", + "compression.estimate_hint": "این یک تخمین است: اندازهٔ واقعی به محتوای فایل‌ها بستگی دارد.", + "compression.estimate_hint_video": "این یک تخمین است: اندازهٔ واقعی ویدیو بسیار به تصویر آن بستگی دارد (ضبط صفحهٔ ثابت بسیار کوچک‌تر می‌شود).", + "compression.remember": "به خاطر سپردن برای این پیش‌تنظیم", "pages.title": "انتخاب صفحات", "pages.file_of": "فایل {0} از {1}", "pages.count": "صفحات موجود در این PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/fr.json b/src/Filee.App/Assets/i18n/fr.json index c27675e61..6a90dab25 100644 --- a/src/Filee.App/Assets/i18n/fr.json +++ b/src/Filee.App/Assets/i18n/fr.json @@ -991,6 +991,20 @@ "presets.split": "Un fichier par page", "presets.ask_pages": "Demander à chaque fois", "presets.ask_pages_hint": "Déposer un PDF sur le menu circulaire ouvre une petite fenêtre pour choisir ses pages. Elle s'ouvre avec la plage ci-dessus, qui sert aussi aux dossiers surveillés et à la ligne de commande.", + "presets.ask_compression": "Demander à chaque fois", + "presets.ask_compression_hint": "Déposer des fichiers sur le menu circulaire ouvre une petite fenêtre pour choisir leur compression, avec la taille qu'ils auront à peu près. Le réglage ci-dessus est celui de départ de la fenêtre, et celui qu'utilisent les dossiers surveillés et la ligne de commande.", + "compression.title": "Choisir la compression", + "compression.files": "{0} fichiers", + "compression.size_now": "Taille actuelle : {0}", + "compression.estimate_label": "Taille estimée", + "compression.estimating": "Estimation de la taille…", + "compression.estimate": "Environ {0} ({1} % plus petit)", + "compression.not_smaller": "Environ {0}, pas plus petit qu'avant.", + "compression.not_smaller_kept": "Sans doute pas plus petit : l'original est gardé tel quel.", + "compression.unknown": "Filee ne peut pas estimer la taille de ces fichiers.", + "compression.estimate_hint": "Une estimation : la taille réelle dépend du contenu des fichiers.", + "compression.estimate_hint_video": "Une estimation : la taille réelle d'une vidéo dépend beaucoup de son image (un enregistrement d'écran fixe sera bien plus petit).", + "compression.remember": "Mémoriser pour ce préréglage", "pages.title": "Choisir les pages", "pages.file_of": "Fichier {0} sur {1}", "pages.count": "Pages de ce PDF : {0}", diff --git a/src/Filee.App/Assets/i18n/hi.json b/src/Filee.App/Assets/i18n/hi.json index 1c9e0b53c..016d506cd 100644 --- a/src/Filee.App/Assets/i18n/hi.json +++ b/src/Filee.App/Assets/i18n/hi.json @@ -976,6 +976,20 @@ "presets.split": "प्रति पेज एक फ़ाइल", "presets.ask_pages": "हर बार पूछें", "presets.ask_pages_hint": "डोनट टूलबार पर PDF छोड़ने पर पेज चुनने के लिए एक छोटी विंडो खुलती है। विंडो ऊपर दी गई रेंज से शुरू होती है, और वॉच फ़ोल्डर और कमांड लाइन भी यही रेंज इस्तेमाल करते हैं।", + "presets.ask_compression": "हर बार पूछें", + "presets.ask_compression_hint": "डोनट टूलबार पर फ़ाइलें छोड़ने से एक छोटी विंडो खुलती है, जिसमें आप चुनते हैं कि उन्हें कितना कंप्रेस करना है और उनका लगभग आकार देखते हैं। ऊपर की सेटिंग विंडो का शुरुआती मान है, और वॉच फ़ोल्डर व कमांड लाइन यही इस्तेमाल करते हैं।", + "compression.title": "कंप्रेशन चुनें", + "compression.files": "{0} फ़ाइलें", + "compression.size_now": "अभी का आकार: {0}", + "compression.estimate_label": "अनुमानित आकार", + "compression.estimating": "आकार का अनुमान लगाया जा रहा है…", + "compression.estimate": "लगभग {0} ({1}% छोटा)", + "compression.not_smaller": "लगभग {0}, अभी से छोटा नहीं।", + "compression.not_smaller_kept": "शायद छोटा नहीं होगा: मूल फ़ाइल जैसी है वैसी रखी जाएगी।", + "compression.unknown": "Filee इन फ़ाइलों के आकार का अनुमान नहीं लगा सकता।", + "compression.estimate_hint": "यह एक अनुमान है: असली आकार फ़ाइलों की सामग्री पर निर्भर करता है।", + "compression.estimate_hint_video": "यह एक अनुमान है: वीडियो का असली आकार उसकी तस्वीर पर बहुत निर्भर करता है (स्थिर स्क्रीन रिकॉर्डिंग काफ़ी छोटी बनती है)।", + "compression.remember": "इस प्रीसेट के लिए याद रखें", "pages.title": "पेज चुनें", "pages.file_of": "फ़ाइल {0} / {1}", "pages.count": "इस PDF में पेज: {0}", diff --git a/src/Filee.App/Assets/i18n/id.json b/src/Filee.App/Assets/i18n/id.json index 4c02a912f..5affa3f57 100644 --- a/src/Filee.App/Assets/i18n/id.json +++ b/src/Filee.App/Assets/i18n/id.json @@ -976,6 +976,20 @@ "presets.split": "Satu file per halaman", "presets.ask_pages": "Tanyakan setiap kali", "presets.ask_pages_hint": "Saat PDF dijatuhkan ke toolbar donut, jendela kecil terbuka untuk memilih halamannya. Jendela dimulai dengan rentang di atas, yang juga dipakai folder pantau dan baris perintah.", + "presets.ask_compression": "Tanyakan setiap kali", + "presets.ask_compression_hint": "Menjatuhkan file ke toolbar donut membuka jendela kecil untuk memilih seberapa besar file dikompres, beserta perkiraan ukurannya. Pengaturan di atas adalah nilai awal jendela, dan yang dipakai folder pantauan serta baris perintah.", + "compression.title": "Pilih kompresi", + "compression.files": "{0} file", + "compression.size_now": "Ukuran sekarang: {0}", + "compression.estimate_label": "Perkiraan ukuran", + "compression.estimating": "Memperkirakan ukuran…", + "compression.estimate": "Sekitar {0} ({1}% lebih kecil)", + "compression.not_smaller": "Sekitar {0}, tidak lebih kecil dari sekarang.", + "compression.not_smaller_kept": "Kemungkinan tidak lebih kecil: file asli dibiarkan apa adanya.", + "compression.unknown": "Filee tidak dapat memperkirakan ukuran file ini.", + "compression.estimate_hint": "Ini perkiraan: ukuran sebenarnya bergantung pada isi file.", + "compression.estimate_hint_video": "Ini perkiraan: ukuran sebenarnya sebuah video sangat bergantung pada gambarnya (rekaman layar yang diam jauh lebih kecil).", + "compression.remember": "Ingat untuk preset ini", "pages.title": "Pilih halaman", "pages.file_of": "File {0} dari {1}", "pages.count": "Jumlah halaman PDF ini: {0}", diff --git a/src/Filee.App/Assets/i18n/it.json b/src/Filee.App/Assets/i18n/it.json index 292f914b9..a86d7348d 100644 --- a/src/Filee.App/Assets/i18n/it.json +++ b/src/Filee.App/Assets/i18n/it.json @@ -975,6 +975,20 @@ "presets.split": "Un file per pagina", "presets.ask_pages": "Chiedi ogni volta", "presets.ask_pages_hint": "Trascinando un PDF sulla barra donut si apre una piccola finestra per sceglierne le pagine. Parte dall'intervallo qui sopra, che usano anche le cartelle monitorate e la riga di comando.", + "presets.ask_compression": "Chiedi ogni volta", + "presets.ask_compression_hint": "Trascinando dei file sulla barra donut si apre una piccola finestra per scegliere quanto comprimerli, con la dimensione che avranno all'incirca. L'impostazione qui sopra è quella da cui parte la finestra, e quella usata dalle cartelle monitorate e dalla riga di comando.", + "compression.title": "Scegli la compressione", + "compression.files": "{0} file", + "compression.size_now": "Dimensione attuale: {0}", + "compression.estimate_label": "Dimensione stimata", + "compression.estimating": "Stima della dimensione…", + "compression.estimate": "Circa {0} ({1}% in meno)", + "compression.not_smaller": "Circa {0}, non più piccolo di ora.", + "compression.not_smaller_kept": "Probabilmente non più piccolo: l'originale resta com'è.", + "compression.unknown": "Filee non riesce a stimare la dimensione di questi file.", + "compression.estimate_hint": "È una stima: la dimensione reale dipende dal contenuto dei file.", + "compression.estimate_hint_video": "È una stima: la dimensione reale di un video dipende molto dalle immagini (una registrazione dello schermo statica risulta molto più piccola).", + "compression.remember": "Ricorda per questo preset", "pages.title": "Scegli le pagine", "pages.file_of": "File {0} di {1}", "pages.count": "Pagine di questo PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/ja.json b/src/Filee.App/Assets/i18n/ja.json index a35cbc281..4fd5e5109 100644 --- a/src/Filee.App/Assets/i18n/ja.json +++ b/src/Filee.App/Assets/i18n/ja.json @@ -976,6 +976,20 @@ "presets.split": "1ページずつ別ファイルにする", "presets.ask_pages": "毎回確認する", "presets.ask_pages_hint": "ドーナツメニューにPDFをドロップすると、小さなウィンドウでページを選べます。上の範囲がウィンドウの初期値になり、監視フォルダーとコマンドラインではこの範囲が使われます。", + "presets.ask_compression": "毎回確認する", + "presets.ask_compression_hint": "ドーナツメニューにファイルをドロップすると小さなウィンドウが開き、圧縮の度合いとおおよそのサイズを確認して選べます。上の設定はウィンドウの初期値で、監視フォルダーとコマンドラインでも使われます。", + "compression.title": "圧縮を選択", + "compression.files": "{0} 個のファイル", + "compression.size_now": "現在のサイズ: {0}", + "compression.estimate_label": "推定サイズ", + "compression.estimating": "サイズを推定中…", + "compression.estimate": "約 {0}({1}% 小さく)", + "compression.not_smaller": "約 {0}、今より小さくなりません。", + "compression.not_smaller_kept": "おそらく小さくなりません。元のファイルはそのまま残ります。", + "compression.unknown": "これらのファイルのサイズは推定できません。", + "compression.estimate_hint": "推定値です。実際のサイズはファイルの内容によって変わります。", + "compression.estimate_hint_video": "推定値です。動画の実際のサイズは映像によって大きく変わります(動きの少ない画面録画はずっと小さくなります)。", + "compression.remember": "このプリセットに記憶する", "pages.title": "ページの選択", "pages.file_of": "ファイル {0}/{1}", "pages.count": "このPDFのページ数: {0}", diff --git a/src/Filee.App/Assets/i18n/ko.json b/src/Filee.App/Assets/i18n/ko.json index e2e06bada..02871a927 100644 --- a/src/Filee.App/Assets/i18n/ko.json +++ b/src/Filee.App/Assets/i18n/ko.json @@ -991,6 +991,20 @@ "presets.split": "페이지마다 파일 하나", "presets.ask_pages": "매번 묻기", "presets.ask_pages_hint": "원형 툴바에 PDF를 놓으면 작은 창이 열려 페이지를 고를 수 있어요. 창은 위의 범위로 시작하고, 자동 변환 폴더와 명령줄은 이 범위를 그대로 써요.", + "presets.ask_compression": "매번 묻기", + "presets.ask_compression_hint": "원형 툴바에 파일을 놓으면 작은 창이 열려 압축 정도를 고르고 대략적인 크기를 확인할 수 있어요. 창은 위의 설정으로 시작하고, 자동 변환 폴더와 명령줄은 이 설정을 그대로 써요.", + "compression.title": "압축 선택", + "compression.files": "파일 {0}개", + "compression.size_now": "현재 크기: {0}", + "compression.estimate_label": "예상 크기", + "compression.estimating": "크기 예상 중…", + "compression.estimate": "약 {0} ({1}% 작음)", + "compression.not_smaller": "약 {0}, 지금보다 작지 않아요.", + "compression.not_smaller_kept": "작아지지 않을 것 같아요. 원본은 그대로 둬요.", + "compression.unknown": "이 파일들은 크기를 예상할 수 없어요.", + "compression.estimate_hint": "예상치예요. 실제 크기는 파일 내용에 따라 달라져요.", + "compression.estimate_hint_video": "예상치예요. 동영상의 실제 크기는 화면에 따라 크게 달라져요(움직임이 없는 화면 녹화는 훨씬 작아져요).", + "compression.remember": "이 프리셋에 기억", "pages.title": "페이지 선택", "pages.file_of": "파일 {0}/{1}", "pages.count": "이 PDF의 페이지 수: {0}", diff --git a/src/Filee.App/Assets/i18n/ne.json b/src/Filee.App/Assets/i18n/ne.json index 320f12e7a..8a943d00c 100644 --- a/src/Filee.App/Assets/i18n/ne.json +++ b/src/Filee.App/Assets/i18n/ne.json @@ -976,6 +976,20 @@ "presets.split": "प्रति पृष्ठ एउटा फाइल", "presets.ask_pages": "हरेक पटक सोध्नुहोस्", "presets.ask_pages_hint": "डोनट टुलबारमा PDF छोड्दा पृष्ठ छान्न एउटा सानो विन्डो खुल्छ। विन्डो माथिको दायराबाट सुरु हुन्छ, र वाच फोल्डरहरू र कमान्ड लाइनले पनि यही दायरा प्रयोग गर्छन्।", + "presets.ask_compression": "हरेक पटक सोध्नुहोस्", + "presets.ask_compression_hint": "डोनट टुलबारमा फाइलहरू छोड्दा एउटा सानो विन्डो खुल्छ, जहाँ कति कम्प्रेस गर्ने छान्न र लगभग आकार हेर्न सकिन्छ। माथिको सेटिङ विन्डोको सुरुको मान हो, र हेरिने फोल्डर तथा कमान्ड लाइनले यही प्रयोग गर्छन्।", + "compression.title": "कम्प्रेसन छान्नुहोस्", + "compression.files": "{0} फाइलहरू", + "compression.size_now": "अहिलेको आकार: {0}", + "compression.estimate_label": "अनुमानित आकार", + "compression.estimating": "आकार अनुमान गर्दै…", + "compression.estimate": "लगभग {0} ({1}% सानो)", + "compression.not_smaller": "लगभग {0}, अहिलेभन्दा सानो छैन।", + "compression.not_smaller_kept": "सायद सानो हुँदैन: मूल फाइल जस्तो छ त्यस्तै राखिन्छ।", + "compression.unknown": "Filee ले यी फाइलहरूको आकार अनुमान गर्न सक्दैन।", + "compression.estimate_hint": "यो अनुमान हो: वास्तविक आकार फाइलहरूको सामग्रीमा भर पर्छ।", + "compression.estimate_hint_video": "यो अनुमान हो: भिडियोको वास्तविक आकार यसको दृश्यमा धेरै भर पर्छ (स्थिर स्क्रिन रेकर्डिङ धेरै सानो हुन्छ)।", + "compression.remember": "यो प्रिसेटका लागि सम्झनुहोस्", "pages.title": "पृष्ठ छान्नुहोस्", "pages.file_of": "फाइल {0} / {1}", "pages.count": "यो PDF मा पृष्ठ: {0}", diff --git a/src/Filee.App/Assets/i18n/pl.json b/src/Filee.App/Assets/i18n/pl.json index 016fb9607..82b3ca0da 100644 --- a/src/Filee.App/Assets/i18n/pl.json +++ b/src/Filee.App/Assets/i18n/pl.json @@ -991,6 +991,20 @@ "presets.split": "Jeden plik na stronę", "presets.ask_pages": "Pytaj za każdym razem", "presets.ask_pages_hint": "Upuszczenie pliku PDF na pasek-pierścień otwiera małe okno wyboru stron. Startuje ono z zakresem podanym wyżej, którego używają też obserwowane foldery i wiersz poleceń.", + "presets.ask_compression": "Pytaj za każdym razem", + "presets.ask_compression_hint": "Upuszczenie plików na pasek-pierścień otwiera małe okno, w którym wybierasz stopień kompresji i widzisz przybliżony rozmiar. Ustawienie powyżej jest wartością początkową okna i tą, której używają obserwowane foldery oraz wiersz poleceń.", + "compression.title": "Wybierz kompresję", + "compression.files": "Pliki: {0}", + "compression.size_now": "Obecny rozmiar: {0}", + "compression.estimate_label": "Szacowany rozmiar", + "compression.estimating": "Szacowanie rozmiaru…", + "compression.estimate": "Około {0} (mniej o {1}%)", + "compression.not_smaller": "Około {0}, nie mniej niż teraz.", + "compression.not_smaller_kept": "Prawdopodobnie nie będzie mniejszy: oryginał zostaje bez zmian.", + "compression.unknown": "Filee nie może oszacować rozmiaru tych plików.", + "compression.estimate_hint": "To szacunek: rzeczywisty rozmiar zależy od zawartości plików.", + "compression.estimate_hint_video": "To szacunek: rzeczywisty rozmiar wideo mocno zależy od obrazu (nieruchome nagranie ekranu będzie znacznie mniejsze).", + "compression.remember": "Zapamiętaj dla tego ustawienia wstępnego", "pages.title": "Wybierz strony", "pages.file_of": "Plik {0} z {1}", "pages.count": "Liczba stron w tym PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/pt-BR.json b/src/Filee.App/Assets/i18n/pt-BR.json index 044461a63..dd690bac6 100644 --- a/src/Filee.App/Assets/i18n/pt-BR.json +++ b/src/Filee.App/Assets/i18n/pt-BR.json @@ -991,6 +991,20 @@ "presets.split": "Um arquivo por página", "presets.ask_pages": "Perguntar sempre", "presets.ask_pages_hint": "Ao soltar um PDF no menu circular, abre uma janela pequena para escolher as páginas. Ela começa com o intervalo acima, que também é usado pelas pastas monitoradas e pela linha de comando.", + "presets.ask_compression": "Perguntar sempre", + "presets.ask_compression_hint": "Soltar arquivos no menu circular abre uma janela pequena para escolher quanto eles são compactados, com o tamanho aproximado que terão. A configuração acima é a inicial da janela, e a que as pastas monitoradas e a linha de comando usam.", + "compression.title": "Escolher a compressão", + "compression.files": "{0} arquivos", + "compression.size_now": "Tamanho atual: {0}", + "compression.estimate_label": "Tamanho estimado", + "compression.estimating": "Estimando o tamanho…", + "compression.estimate": "Cerca de {0} ({1}% menor)", + "compression.not_smaller": "Cerca de {0}, não menor que agora.", + "compression.not_smaller_kept": "Provavelmente não fica menor: o original é mantido como está.", + "compression.unknown": "O Filee não consegue estimar o tamanho destes arquivos.", + "compression.estimate_hint": "É uma estimativa: o tamanho real depende do conteúdo dos arquivos.", + "compression.estimate_hint_video": "É uma estimativa: o tamanho real de um vídeo depende muito da imagem (uma gravação de tela parada fica bem menor).", + "compression.remember": "Lembrar para esta predefinição", "pages.title": "Escolher páginas", "pages.file_of": "Arquivo {0} de {1}", "pages.count": "Páginas deste PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/pt-PT.json b/src/Filee.App/Assets/i18n/pt-PT.json index 3b598acc3..b8f2983a9 100644 --- a/src/Filee.App/Assets/i18n/pt-PT.json +++ b/src/Filee.App/Assets/i18n/pt-PT.json @@ -991,6 +991,20 @@ "presets.split": "Um ficheiro por página", "presets.ask_pages": "Perguntar sempre", "presets.ask_pages_hint": "Ao largar um PDF na barra circular, abre-se uma pequena janela para escolher as páginas. Começa com o intervalo acima, que também é usado pelas pastas monitorizadas e pela linha de comandos.", + "presets.ask_compression": "Perguntar sempre", + "presets.ask_compression_hint": "Largar ficheiros na barra circular abre uma pequena janela para escolher quanto são comprimidos, com o tamanho aproximado que vão ter. A definição acima é a inicial da janela, e a que as pastas monitorizadas e a linha de comandos usam.", + "compression.title": "Escolher a compressão", + "compression.files": "{0} ficheiros", + "compression.size_now": "Tamanho atual: {0}", + "compression.estimate_label": "Tamanho estimado", + "compression.estimating": "A estimar o tamanho…", + "compression.estimate": "Cerca de {0} ({1}% mais pequeno)", + "compression.not_smaller": "Cerca de {0}, não mais pequeno do que agora.", + "compression.not_smaller_kept": "Provavelmente não fica mais pequeno: o original é mantido tal como está.", + "compression.unknown": "O Filee não consegue estimar o tamanho destes ficheiros.", + "compression.estimate_hint": "É uma estimativa: o tamanho real depende do conteúdo dos ficheiros.", + "compression.estimate_hint_video": "É uma estimativa: o tamanho real de um vídeo depende muito da imagem (uma gravação de ecrã parada fica muito mais pequena).", + "compression.remember": "Memorizar para esta predefinição", "pages.title": "Escolher páginas", "pages.file_of": "Ficheiro {0} de {1}", "pages.count": "Páginas deste PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/ru.json b/src/Filee.App/Assets/i18n/ru.json index 4ebb6fab9..5a11030cd 100644 --- a/src/Filee.App/Assets/i18n/ru.json +++ b/src/Filee.App/Assets/i18n/ru.json @@ -991,6 +991,20 @@ "presets.split": "По файлу на страницу", "presets.ask_pages": "Спрашивать каждый раз", "presets.ask_pages_hint": "Если перетащить PDF на круговое меню, откроется небольшое окно для выбора страниц. В нём сразу стоит диапазон, указанный выше; его же используют папки наблюдения и командная строка.", + "presets.ask_compression": "Спрашивать каждый раз", + "presets.ask_compression_hint": "Если перетащить файлы на круговое меню, откроется небольшое окно: в нём выбирается степень сжатия и видно примерный размер. Значение выше — начальное для окна; его же используют отслеживаемые папки и командная строка.", + "compression.title": "Выбор сжатия", + "compression.files": "Файлов: {0}", + "compression.size_now": "Сейчас: {0}", + "compression.estimate_label": "Примерный размер", + "compression.estimating": "Оценка размера…", + "compression.estimate": "Около {0} (меньше на {1}%)", + "compression.not_smaller": "Около {0}, не меньше, чем сейчас.", + "compression.not_smaller_kept": "Скорее всего, не меньше: оригинал останется как есть.", + "compression.unknown": "Filee не может оценить размер этих файлов.", + "compression.estimate_hint": "Это оценка: реальный размер зависит от содержимого файлов.", + "compression.estimate_hint_video": "Это оценка: реальный размер видео сильно зависит от изображения (статичная запись экрана получится гораздо меньше).", + "compression.remember": "Запомнить для этого пресета", "pages.title": "Выбор страниц", "pages.file_of": "Файл {0} из {1}", "pages.count": "Страниц в этом PDF: {0}", diff --git a/src/Filee.App/Assets/i18n/th.json b/src/Filee.App/Assets/i18n/th.json index 774623b30..453435b47 100644 --- a/src/Filee.App/Assets/i18n/th.json +++ b/src/Filee.App/Assets/i18n/th.json @@ -976,6 +976,20 @@ "presets.split": "แยกหนึ่งไฟล์ต่อหนึ่งหน้า", "presets.ask_pages": "ถามทุกครั้ง", "presets.ask_pages_hint": "เมื่อวาง PDF บนแถบโดนัท หน้าต่างเล็ก ๆ จะเปิดขึ้นให้เลือกหน้า โดยเริ่มจากช่วงหน้าด้านบน ซึ่งโฟลเดอร์เฝ้าติดตามและบรรทัดคำสั่งก็ใช้ช่วงนี้เช่นกัน", + "presets.ask_compression": "ถามทุกครั้ง", + "presets.ask_compression_hint": "เมื่อวางไฟล์บนแถบโดนัท จะมีหน้าต่างเล็กๆ เปิดขึ้นให้เลือกระดับการบีบอัด พร้อมขนาดโดยประมาณ ค่าด้านบนคือค่าเริ่มต้นของหน้าต่าง และเป็นค่าที่โฟลเดอร์เฝ้าดูและบรรทัดคำสั่งใช้", + "compression.title": "เลือกการบีบอัด", + "compression.files": "{0} ไฟล์", + "compression.size_now": "ขนาดตอนนี้: {0}", + "compression.estimate_label": "ขนาดโดยประมาณ", + "compression.estimating": "กำลังประมาณขนาด…", + "compression.estimate": "ประมาณ {0} (เล็กลง {1}%)", + "compression.not_smaller": "ประมาณ {0} ไม่เล็กกว่าตอนนี้", + "compression.not_smaller_kept": "อาจไม่เล็กลง จึงเก็บไฟล์ต้นฉบับไว้ตามเดิม", + "compression.unknown": "Filee ประมาณขนาดของไฟล์เหล่านี้ไม่ได้", + "compression.estimate_hint": "เป็นค่าประมาณ ขนาดจริงขึ้นอยู่กับเนื้อหาของไฟล์", + "compression.estimate_hint_video": "เป็นค่าประมาณ ขนาดจริงของวิดีโอขึ้นอยู่กับภาพเป็นอย่างมาก (การบันทึกหน้าจอที่ภาพนิ่งจะเล็กกว่ามาก)", + "compression.remember": "จำไว้สำหรับพรีเซ็ตนี้", "pages.title": "เลือกหน้า", "pages.file_of": "ไฟล์ {0} จาก {1}", "pages.count": "จำนวนหน้าใน PDF นี้: {0}", diff --git a/src/Filee.App/Assets/i18n/tr.json b/src/Filee.App/Assets/i18n/tr.json index e4195232f..df2c3e9d7 100644 --- a/src/Filee.App/Assets/i18n/tr.json +++ b/src/Filee.App/Assets/i18n/tr.json @@ -976,6 +976,20 @@ "presets.split": "Sayfa başına bir dosya", "presets.ask_pages": "Her seferinde sor", "presets.ask_pages_hint": "Halka araç çubuğuna bir PDF bıraktığınızda sayfaları seçmek için küçük bir pencere açılır. Pencere yukarıdaki aralıkla açılır; izlenen klasörler ve komut satırı da bu aralığı kullanır.", + "presets.ask_compression": "Her seferinde sor", + "presets.ask_compression_hint": "Dosyaları halka araç çubuğuna bıraktığınızda, ne kadar sıkıştırılacaklarını seçtiğiniz ve yaklaşık boyutlarını gördüğünüz küçük bir pencere açılır. Yukarıdaki ayar pencerenin başlangıç değeridir; izlenen klasörler ve komut satırı da bunu kullanır.", + "compression.title": "Sıkıştırmayı seçin", + "compression.files": "{0} dosya", + "compression.size_now": "Şu anki boyut: {0}", + "compression.estimate_label": "Tahmini boyut", + "compression.estimating": "Boyut tahmin ediliyor…", + "compression.estimate": "Yaklaşık {0} (%{1} daha küçük)", + "compression.not_smaller": "Yaklaşık {0}, şimdikinden küçük değil.", + "compression.not_smaller_kept": "Büyük olasılıkla küçülmez: orijinal olduğu gibi tutulur.", + "compression.unknown": "Filee bu dosyaların boyutunu tahmin edemiyor.", + "compression.estimate_hint": "Bu bir tahmindir: gerçek boyut dosyaların içeriğine bağlıdır.", + "compression.estimate_hint_video": "Bu bir tahmindir: bir videonun gerçek boyutu büyük ölçüde görüntüsüne bağlıdır (hareketsiz bir ekran kaydı çok daha küçük olur).", + "compression.remember": "Bu ön ayar için hatırla", "pages.title": "Sayfaları seçin", "pages.file_of": "Dosya {0}/{1}", "pages.count": "Bu PDF'deki sayfa sayısı: {0}", diff --git a/src/Filee.App/Assets/i18n/vi.json b/src/Filee.App/Assets/i18n/vi.json index 4a4dbaa5f..ee3f4b01f 100644 --- a/src/Filee.App/Assets/i18n/vi.json +++ b/src/Filee.App/Assets/i18n/vi.json @@ -991,6 +991,20 @@ "presets.split": "Mỗi trang một tệp", "presets.ask_pages": "Luôn hỏi", "presets.ask_pages_hint": "Khi thả một tệp PDF lên thanh công cụ vòng tròn, một cửa sổ nhỏ sẽ mở ra để chọn trang. Cửa sổ bắt đầu với phạm vi ở trên, và thư mục theo dõi cùng dòng lệnh cũng dùng phạm vi này.", + "presets.ask_compression": "Luôn hỏi", + "presets.ask_compression_hint": "Thả tệp lên thanh công cụ vòng tròn sẽ mở một cửa sổ nhỏ để chọn mức nén, kèm kích thước ước chừng. Thiết lập ở trên là giá trị ban đầu của cửa sổ, và là giá trị thư mục theo dõi cùng dòng lệnh sử dụng.", + "compression.title": "Chọn mức nén", + "compression.files": "{0} tệp", + "compression.size_now": "Kích thước hiện tại: {0}", + "compression.estimate_label": "Kích thước ước tính", + "compression.estimating": "Đang ước tính kích thước…", + "compression.estimate": "Khoảng {0} (nhỏ hơn {1}%)", + "compression.not_smaller": "Khoảng {0}, không nhỏ hơn hiện tại.", + "compression.not_smaller_kept": "Có lẽ không nhỏ hơn: tệp gốc được giữ nguyên.", + "compression.unknown": "Filee không thể ước tính kích thước của các tệp này.", + "compression.estimate_hint": "Đây là ước tính: kích thước thật phụ thuộc vào nội dung tệp.", + "compression.estimate_hint_video": "Đây là ước tính: kích thước thật của video phụ thuộc nhiều vào hình ảnh (bản quay màn hình tĩnh sẽ nhỏ hơn nhiều).", + "compression.remember": "Ghi nhớ cho cấu hình sẵn này", "pages.title": "Chọn trang", "pages.file_of": "Tệp {0}/{1}", "pages.count": "Số trang của PDF này: {0}", diff --git a/src/Filee.App/Assets/i18n/zh-CN.json b/src/Filee.App/Assets/i18n/zh-CN.json index 8fefa9659..1a36b91be 100644 --- a/src/Filee.App/Assets/i18n/zh-CN.json +++ b/src/Filee.App/Assets/i18n/zh-CN.json @@ -991,6 +991,20 @@ "presets.split": "每页一个文件", "presets.ask_pages": "每次询问", "presets.ask_pages_hint": "将 PDF 拖放到环形工具栏上时,会打开一个小窗口来选择页面。窗口以上方的范围开始,自动转换文件夹和命令行也使用这个范围。", + "presets.ask_compression": "每次询问", + "presets.ask_compression_hint": "将文件拖放到环形工具栏上会打开一个小窗口,用于选择压缩程度并查看大致大小。上面的设置是窗口的初始值,也是自动转换文件夹和命令行使用的值。", + "compression.title": "选择压缩", + "compression.files": "{0} 个文件", + "compression.size_now": "当前大小: {0}", + "compression.estimate_label": "估计大小", + "compression.estimating": "正在估算大小…", + "compression.estimate": "约 {0}(减小 {1}%)", + "compression.not_smaller": "约 {0},不比现在小。", + "compression.not_smaller_kept": "可能不会变小,原文件保持不变。", + "compression.unknown": "Filee 无法估算这些文件的大小。", + "compression.estimate_hint": "这是估计值,实际大小取决于文件内容。", + "compression.estimate_hint_video": "这是估计值,视频的实际大小很大程度上取决于画面(静止的屏幕录制会小得多)。", + "compression.remember": "为此预设记住", "pages.title": "选择页面", "pages.file_of": "第 {0} 个文件,共 {1} 个", "pages.count": "此 PDF 的页数:{0}", diff --git a/src/Filee.App/Assets/i18n/zh-TW.json b/src/Filee.App/Assets/i18n/zh-TW.json index 4956c7684..8c4935da2 100644 --- a/src/Filee.App/Assets/i18n/zh-TW.json +++ b/src/Filee.App/Assets/i18n/zh-TW.json @@ -991,6 +991,20 @@ "presets.split": "每頁一個檔案", "presets.ask_pages": "每次詢問", "presets.ask_pages_hint": "將 PDF 拖放到環形工具列上時,會開啟一個小視窗來選擇頁面。視窗以上方的範圍開始,自動轉換資料夾和命令列也使用這個範圍。", + "presets.ask_compression": "每次詢問", + "presets.ask_compression_hint": "將檔案拖放到環形工具列上會開啟一個小視窗,用來選擇壓縮程度並查看大約大小。上方的設定是視窗的初始值,也是自動轉換資料夾和命令列使用的值。", + "compression.title": "選擇壓縮", + "compression.files": "{0} 個檔案", + "compression.size_now": "目前大小: {0}", + "compression.estimate_label": "預估大小", + "compression.estimating": "正在預估大小…", + "compression.estimate": "約 {0}(縮小 {1}%)", + "compression.not_smaller": "約 {0},不比現在小。", + "compression.not_smaller_kept": "可能不會變小,原始檔案維持不變。", + "compression.unknown": "Filee 無法預估這些檔案的大小。", + "compression.estimate_hint": "這是預估值,實際大小取決於檔案內容。", + "compression.estimate_hint_video": "這是預估值,影片的實際大小很大程度取決於畫面(靜止的螢幕錄影會小得多)。", + "compression.remember": "為此預設記住", "pages.title": "選擇頁面", "pages.file_of": "第 {0} 個檔案,共 {1} 個", "pages.count": "此 PDF 的頁數:{0}", diff --git a/src/Filee.App/Services/RadialController.cs b/src/Filee.App/Services/RadialController.cs index 60eb1cb63..ecb153c72 100644 --- a/src/Filee.App/Services/RadialController.cs +++ b/src/Filee.App/Services/RadialController.cs @@ -456,6 +456,16 @@ private void Convert(int index, int sub, IReadOnlyList files, WebDrop? w return; } + // "Ask every time" for the compression (#1353): a small window for the files with a level to choose first, then + // the save dialog when that asks too; the toast shows the job, the donut has closed for the window. + if (AskedCompression.Asks(preset) && sources.Any(s => AskedCompression.KindOf(preset, s) is not null)) + { + KeepKeyboard(); // for the compression window + Close(animate: false); + _ = AskCompressionThenConvertAsync(sources, preset, web, _toClipboard, _onJob); + return; + } + // "Ask every time" (#521): the save dialog first; the toast shows the job, the donut has closed for the dialog. // shortcut: files from a browser aren't asked about, they save into the download folder as "next to the source". if (preset.Output.AskEveryTime && (web is null || preset.IsCopyToFolder)) @@ -626,6 +636,106 @@ private void RememberSplitChoice(Preset preset, PageChoice chosen) } } + /// + /// "Choose the compression" (#1353): one window for all the files dropped, since one setting goes for every file of a + /// kind. Cancelling it starts nothing. The files then convert with the setting chosen, for this conversion only (as + /// "Choose pages"); ones without a level to choose (a PNG on "Compress image") convert as the preset says, in a job + /// of their own. The preset keeps the setting only with "Remember for this preset". Files from a browser are saved + /// first, as for a tool: the estimate reads the files themselves. + /// + private async Task AskCompressionThenConvertAsync(IReadOnlyList files, Preset preset, WebDrop? web, bool toClipboard, + Action? onJob) + { + try + { + if (web is not null) + { + var drop = web; + web = null; // saving it disposes it + files = await conversions.SaveBrowserFilesAsync(drop, (placeholder, ex) => + log.LogWarning(ex, "Saving {File} from a browser for {Preset} failed", Path.GetFileName(placeholder), preset.Id)); + } + if (await windows.AskCompressionAsync(files, preset) is not { } answer) + { + log.LogInformation("Choosing the compression for {Preset} was cancelled; nothing converts", preset.Id); + return; + } + if (answer.Remember) + RememberCompression(preset, answer.Choice, files); + var runs = AskedCompression.Runs(preset, answer.Choice, files); + if (preset.Output.AskEveryTime) + { + // "Ask every time" (#521): one save dialog for the whole drop, its place for every run, so a JPG and a + // PNG dropped together aren't asked about twice nor saved apart. + var running = store.Settings.WithDefaultOutput(preset); + if (await windows.AskSaveLocationAsync(files, running, AskedLocation.MakesOneResult(running, files.Count)) is not { } located) + { + log.LogInformation("Choosing where to save for {Preset} was cancelled; nothing converts", preset.Id); + return; + } + runs = AskedCompression.Runs(preset, answer.Choice, files, located.Output); + } + log.LogInformation("Converting {Count} file(s) with preset {Preset} at the compression chosen", files.Count, preset.Id); + foreach (var (runFiles, runPreset) in runs) + { + if (conversions.Start(runFiles, runPreset, toClipboard: toClipboard) is { } job) + onJob?.Invoke(job.Job); + } + } + catch (Exception ex) when (ex is not OutOfMemoryException) + { + log.LogError(ex, "Could not convert at the compression chosen"); + } + finally + { + web?.Dispose(); + } + } + + /// + /// "Choose the compression" with "Remember for this preset": the settings chosen for the kinds of file dropped + /// () go into the preset as saved, so the window, watch folders and the command line use + /// them from now on. + /// + private void RememberCompression(Preset preset, CompressionChoice chosen, IReadOnlyList files) + { + if (store.FindPreset(preset.Id) is not { } saved) + return; + var changed = false; + foreach (var kind in AskedCompression.KindsOf(preset, files)) + { + switch (kind) + { + case CompressionKind.Image when saved.Image.Quality != chosen.ImageQuality: + saved.Image.Quality = chosen.ImageQuality; + changed = true; + break; + case CompressionKind.Video when saved.Media.Quality != chosen.VideoQuality: + saved.Media.Quality = chosen.VideoQuality; + changed = true; + break; + case CompressionKind.Audio when saved.Media.AudioBitrateKbps != chosen.AudioBitrateKbps: + saved.Media.AudioBitrateKbps = chosen.AudioBitrateKbps; + changed = true; + break; + case CompressionKind.Pdf when saved.Pdf.Compression != chosen.Pdf: + saved.Pdf.Compression = chosen.Pdf; + changed = true; + break; + } + } + if (!changed) + return; + try + { + store.SaveLibrary(); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + log.LogWarning(ex, "Could not remember the compression chosen"); + } + } + private async Task AskThenConvertAsync(IReadOnlyList sources, Preset preset, bool toClipboard, WebDrop? web = null, Action? onJob = null) { diff --git a/src/Filee.App/Services/WindowService.cs b/src/Filee.App/Services/WindowService.cs index 7c7920527..32893d08b 100644 --- a/src/Filee.App/Services/WindowService.cs +++ b/src/Filee.App/Services/WindowService.cs @@ -248,6 +248,54 @@ private void ShowFileeConnectMainWindow(FileeConnectViewModel vm, Filee.App.Serv return dialog.Result; } + private AskCompressionWindow? _askCompression; + + internal AskCompressionWindow? AskCompression => _askCompression; + + /// + /// "Choose the compression" (#1353) for the files dropped on : the setting of each kind + /// among them and the size they will roughly come to, read from the files while the window is open. Null when + /// cancelled. + /// + public async Task AskCompressionAsync(IReadOnlyList files, Preset preset) + { + _askCompression?.Close(); + var asked = files.Where(f => AskedCompression.KindOf(preset, f) is not null).ToList(); + var sourceBytes = asked.Sum(f => File.Exists(f) ? new FileInfo(f).Length : 0); + var logger = services.GetService>(); + using var closing = new CancellationTokenSource(); + var reading = Filee.Engines.CompressionEstimator.ReadAsync(asked, preset, logger, closing.Token); + using var vm = new AskCompressionViewModel(preset, asked, sourceBytes, + async (choice, ct) => await (await reading).EstimateAsync(choice, ct), loc); + var dialog = new AskCompressionWindow { DataContext = vm }; + dialog.StartCompression.Content = loc.DisplayName(preset); + PrepareWindow(dialog); + var closed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + dialog.Closed += (_, _) => + { + if (_askCompression == dialog) _askCompression = null; + closed.TrySetResult(); + }; + _askCompression = dialog; + dialog.Show(); + dialog.Activate(); + await closed.Task; + // The pictures read for the estimate are let go once it is no longer shown, after the estimate under way ends. + vm.Dispose(); + await closing.CancelAsync(); + try + { + var estimator = await reading; + await Task.Run(estimator.Dispose); + } + catch (Exception ex) when (ex is not OutOfMemoryException) + { + if (ex is not OperationCanceledException) + logger?.LogInformation(ex, "Reading the files for the compression estimate failed"); + } + return dialog.Result; + } + /// The pages of a PDF, or null when it can't be read (damaged, locked with a password, gone). internal int? CountPages(string path) { diff --git a/src/Filee.App/ViewModels/AskCompressionViewModel.cs b/src/Filee.App/ViewModels/AskCompressionViewModel.cs new file mode 100644 index 000000000..6b0708b11 --- /dev/null +++ b/src/Filee.App/ViewModels/AskCompressionViewModel.cs @@ -0,0 +1,192 @@ +// "Choose the compression" (#1353): the files dropped on a preset that asks for its compression (Preset.AskCompression), +// the setting of each kind among them (the preset editor's own controls) and the size they will roughly come to, +// estimated again a moment after each change. + +using CommunityToolkit.Mvvm.ComponentModel; +using Filee.Core.Localization; +using Filee.Core.Presets; + +namespace Filee.App.ViewModels; + +/// +/// What the window answered: the settings for this conversion, and whether they also go into the preset (off unless +/// the user ticks it: watch folders and the command line use the preset too). +/// +public sealed record CompressionAnswer(CompressionChoice Choice, bool Remember); + +public sealed partial class AskCompressionViewModel : ObservableObject, IDisposable +{ + /// How long the slider rests before the size is estimated again: one encode per stop, not per step. + internal static readonly TimeSpan EstimateDelay = TimeSpan.FromMilliseconds(250); + + private readonly ILocalizer _loc; + private readonly Func> _estimate; + private readonly TimeSpan _delay; + private CancellationTokenSource? _pending; + + /// The dropped files a level is chosen for (AskedCompression.KindOf), in the order dropped. + /// Their size now. + /// About what they come to with a choice (CompressionEstimator.EstimateAsync); null when unknown. + /// The rest before estimating again; unless a test sets it. + public AskCompressionViewModel(Preset preset, IReadOnlyList files, long sourceBytes, + Func> estimate, ILocalizer loc, TimeSpan? delay = null) + { + _loc = loc; + _estimate = estimate; + _delay = delay ?? EstimateDelay; + var kinds = AskedCompression.KindsOf(preset, files); + ShowImage = kinds.Contains(CompressionKind.Image); + ShowVideo = kinds.Contains(CompressionKind.Video); + ShowAudio = kinds.Contains(CompressionKind.Audio); + ShowPdf = kinds.Contains(CompressionKind.Pdf); + OnlyIfSmaller = preset.Output.OnlyIfSmaller; + SourceBytes = sourceBytes; + FilesText = files.Count == 1 ? Path.GetFileName(files[0]) : loc.Format("compression.files", files.Count); + SizeNowText = loc.Format("compression.size_now", AskedCompression.SizeText(sourceBytes)); + HintText = loc[ShowVideo ? "compression.estimate_hint_video" : "compression.estimate_hint"]; + + var start = CompressionChoice.Of(preset); + _quality = Math.Clamp(start.ImageQuality, 1, 100); + VideoQualities = Enum.GetValues().Select(v => new Choice(v, loc[$"presets.media_quality.{v}"])).ToList(); + _videoQuality = VideoQualities.First(q => q.Value == start.VideoQuality); + AudioBitrates = [.. PresetEditorViewModel.BitrateSteps.Append(start.AudioBitrateKbps).Distinct() + .Select(k => new Choice(k, k == 0 ? loc["presets.audio_bitrate.auto"] : $"{k} kbps"))]; + _audioBitrate = AudioBitrates.First(b => b.Value == start.AudioBitrateKbps); + // "Don't compress" is no level for a compression: a preset saved with it starts at Balanced. + PdfLevels = Enum.GetValues().Where(v => v != PdfCompression.None) + .Select(v => new Choice(v, loc[$"presets.pdf_compression.{v}"])).ToList(); + _pdfLevel = PdfLevels.FirstOrDefault(l => l.Value == start.Pdf) ?? PdfLevels.First(l => l.Value == PdfCompression.Ebook); + _ = EstimateAsync(TimeSpan.Zero); + } + + /// The file's name, or "3 files". + public string FilesText { get; } + + /// "Size now: 100 MB". + public string SizeNowText { get; } + + public long SourceBytes { get; } + + /// The preset keeps the original when the result isn't smaller (OutputRule.OnlyIfSmaller). + public bool OnlyIfSmaller { get; } + + public bool ShowImage { get; } + public bool ShowVideo { get; } + public bool ShowAudio { get; } + public bool ShowPdf { get; } + + /// Under the estimate: what it rests on (a video's picture changes it the most). + public string HintText { get; } + + [ObservableProperty] + private int _quality; + + public IReadOnlyList> VideoQualities { get; } + + [ObservableProperty] + private Choice _videoQuality; + + public IReadOnlyList> AudioBitrates { get; } + + [ObservableProperty] + private Choice _audioBitrate; + + public IReadOnlyList> PdfLevels { get; } + + [ObservableProperty] + private Choice _pdfLevel; + + /// "About 30.0 MB (70% smaller)", "Estimating the size…", or why there is none. + [ObservableProperty] + private string _estimateText = ""; + + /// The estimate in bytes, null while it is worked out or when there is none. + [ObservableProperty] + private long? _estimatedBytes; + + [ObservableProperty] + private bool _isEstimating; + + /// "Remember for this preset": the settings also go into the preset as saved. Off by default. + [ObservableProperty] + private bool _remember; + + partial void OnQualityChanged(int value) => _ = EstimateAsync(_delay); + + partial void OnVideoQualityChanged(Choice? oldValue, Choice newValue) + { + if (!Choices.Restore(oldValue, newValue, v => VideoQuality = v)) + _ = EstimateAsync(TimeSpan.Zero); + } + + partial void OnAudioBitrateChanged(Choice? oldValue, Choice newValue) + { + if (!Choices.Restore(oldValue, newValue, v => AudioBitrate = v)) + _ = EstimateAsync(TimeSpan.Zero); + } + + partial void OnPdfLevelChanged(Choice? oldValue, Choice newValue) + { + if (!Choices.Restore(oldValue, newValue, v => PdfLevel = v)) + _ = EstimateAsync(TimeSpan.Zero); + } + + /// The settings chosen. + public CompressionChoice Result() => new(Quality, VideoQuality.Value, AudioBitrate.Value, PdfLevel.Value); + + /// The settings chosen, and whether the preset keeps them. + public CompressionAnswer Answer() => new(Result(), Remember); + + /// The estimate for the settings as they are now; one started before it is dropped. + internal async Task EstimateAsync(TimeSpan delay) + { + _pending?.Cancel(); + _pending?.Dispose(); + var pending = _pending = new CancellationTokenSource(); + var choice = Result(); + IsEstimating = true; + EstimatedBytes = null; + EstimateText = _loc["compression.estimating"]; + try + { + if (delay > TimeSpan.Zero) + await Task.Delay(delay, pending.Token); + var estimate = await _estimate(choice, pending.Token); + if (pending.IsCancellationRequested) + return; + EstimatedBytes = estimate; + EstimateText = Describe(estimate); + } + catch (OperationCanceledException) + { + return; + } + catch (Exception ex) when (ex is not OutOfMemoryException) + { + if (!pending.IsCancellationRequested) + EstimateText = _loc["compression.unknown"]; + } + finally + { + if (pending == _pending) + IsEstimating = false; + } + } + + private string Describe(long? estimate) + { + if (estimate is not { } bytes || SourceBytes <= 0) + return _loc["compression.unknown"]; + var smaller = AskedCompression.PercentSmaller(SourceBytes, bytes); + if (smaller > 0) + return _loc.Format("compression.estimate", AskedCompression.SizeText(bytes), smaller); + return OnlyIfSmaller ? _loc["compression.not_smaller_kept"] : _loc.Format("compression.not_smaller", AskedCompression.SizeText(bytes)); + } + + public void Dispose() + { + _pending?.Cancel(); + _pending?.Dispose(); + _pending = null; + } +} diff --git a/src/Filee.App/ViewModels/PresetEditorViewModel.cs b/src/Filee.App/ViewModels/PresetEditorViewModel.cs index 582ad7e46..850e45039 100644 --- a/src/Filee.App/ViewModels/PresetEditorViewModel.cs +++ b/src/Filee.App/ViewModels/PresetEditorViewModel.cs @@ -105,8 +105,11 @@ public sealed partial class PresetEditorViewModel : ObservableObject private static readonly HashSet LosslessAudioTargets = ["wav", "flac", "aiff", "wv", "tta", "w64"]; private static readonly int[] MaxHeightSteps = [0, 2160, 1440, 1080, 720, 480]; - // 32-64 kbit/s: speech (Opus is still clear at 32 kbit/s mono), small files. - private static readonly int[] BitrateSteps = [0, 32, 48, 64, 96, 128, 192, 256, 320]; + /// + /// The audio bitrates offered, 0 for the one the quality picks (also in the compression window, #1353). 32-64 kbit/s: + /// speech (Opus is still clear at 32 kbit/s mono), small files. + /// + internal static readonly int[] BitrateSteps = [0, 32, 48, 64, 96, 128, 192, 256, 320]; private static readonly int[] SampleRateSteps = [0, 8000, 16000, 22050, 44100, 48000]; private readonly Preset _original; @@ -372,6 +375,7 @@ .. WhisperLanguages.All _renderDpi = _edit.Pdf.RenderDpi; _pageRange = _edit.Pdf.PageRange; _askPageRange = _edit.Pdf.AskPageRange; + _askCompression = _edit.AskCompression; _pageOutput = PageOutputChoices.First(c => c.Value == PageOutputs.Of(_edit.Pdf)); _groupKind = GroupKinds.First(k => k.Value == PageOutputs.KindOf(_edit.Pdf)); _groupSize = PageOutputs.SizeOf(_edit.Pdf); @@ -525,7 +529,7 @@ public async Task TestTranslationAsync() [NotifyPropertyChangedFor(nameof(ShowImage), nameof(ShowTurnForPdf), nameof(ShowQuality), nameof(ShowTiff), nameof(ShowMultiPageTiff), nameof(ShowWebp), nameof(ShowIco), nameof(ShowFitsTiff), nameof(ShowFitsDebayer), nameof(ShowZipResult), nameof(ShowPdf), nameof(ShowPdfBuild), nameof(ShowSourceBookmarks), nameof(ShowDocument), nameof(ShowLeaveOutPictures), nameof(ShowPdfSplit), nameof(ShowPageGroups), nameof(ShowPdfCombine), - nameof(ShowAskPages), nameof(ShowAskPagesHint), + nameof(ShowAskPages), nameof(ShowAskPagesHint), nameof(ShowAskCompressionImage), nameof(ShowAskCompressionMedia), nameof(ShowAskCompressionPdf), nameof(ShowPdfCompression), nameof(ShowPdfCompressionOptions), nameof(ShowSheets), nameof(ShowOcr), nameof(ShowPageSize), nameof(ShowPdfTablesNote), nameof(ShowMedia), nameof(ShowMediaQuality), nameof(ShowMaxHeight), nameof(ShowVideoMaxSize), nameof(ShowAudioBitrate), nameof(ShowRemoveAudio), nameof(ShowVideoSplit), nameof(ShowVideoParts), @@ -804,7 +808,7 @@ public bool ColorProfileChecked [ObservableProperty] private bool _webpLossless; [ObservableProperty] private bool _multiPageTiff; [ObservableProperty] private string _icoSizes; - [ObservableProperty, NotifyPropertyChangedFor(nameof(ShowAskPages), nameof(ShowAskPagesHint), nameof(ShowSourceBookmarks))] + [ObservableProperty, NotifyPropertyChangedFor(nameof(ShowAskPages), nameof(ShowAskPagesHint), nameof(ShowSourceBookmarks), nameof(ShowAskCompressionPdf))] private bool _merge; [ObservableProperty] private bool _sourceBookmarks; [ObservableProperty] private double _marginMm; @@ -1154,6 +1158,22 @@ partial void OnAnimationSpeedChanged(Choice? oldValue, Choice ne public bool ShowAskPages => ShowPdf && !(TargetId == "pdf" && Merge); public bool ShowAskPagesHint => ShowAskPages && AskPageRange; + /// + /// "Ask every time" for the compression (Preset.AskCompression, #1353), under the setting it asks for: the picture + /// quality, the video quality and audio bitrate, or the PDF level. Not for a merge (AskedCompression.Asks). + /// + [ObservableProperty] + private bool _askCompression; + + /// + /// Only for the targets the window can estimate (AskedCompression.LossyImageTargets) or the source's own format: + /// a HEIC target keeps its quality slider, but would never open the window. + /// + public bool ShowAskCompressionImage => ShowImage && ShowQuality && !_original.JoinsImages + && (TargetId == BuiltInData.SameAsSource || AskedCompression.LossyImageTargets.Contains(TargetId)); + public bool ShowAskCompressionMedia => !IsTool && ShowMediaQuality && !_original.JoinsIntoOneMedia; + public bool ShowAskCompressionPdf => ShowPdfCompression && !Merge; + /// Compression is for PDF → PDF; Ghostscript and metadata only matter while it is on. public bool ShowPdfCompression => TargetId == "pdf" && !Recognizes; @@ -1344,6 +1364,7 @@ public bool Apply() pdf.RenderDpi = Math.Clamp(RenderDpi, 36, 1200); pdf.PageRange = PageRange.Trim(); pdf.AskPageRange = ShowAskPages && AskPageRange; + _original.AskCompression = AskCompression && (ShowAskCompressionImage || ShowAskCompressionMedia || ShowAskCompressionPdf); var (split, every, parts) = PageOutputs.Settings(PageOutput.Value, GroupKind.Value, GroupSize); pdf.SplitPages = split; // One PDF keeps the numbers of a split, as the pages window does, for when it is chosen again. diff --git a/src/Filee.App/Views/AskCompressionWindow.axaml b/src/Filee.App/Views/AskCompressionWindow.axaml new file mode 100644 index 000000000..716dfb848 --- /dev/null +++ b/src/Filee.App/Views/AskCompressionWindow.axaml @@ -0,0 +1,83 @@ + + + + + +