diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7eb719f5..fe4a61ff 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v5.0.0
+ rev: v6.0.0
hooks:
- id: mixed-line-ending
- id: trailing-whitespace
@@ -8,8 +8,7 @@ repos:
- id: requirements-txt-fixer
- id: check-yaml
- id: check-json
- - id: fix-encoding-pragma
- - id: check-byte-order-marker
+ - id: fix-byte-order-marker
- id: debug-statements
- id: check-added-large-files
exclude: |
@@ -24,7 +23,7 @@ repos:
- id: end-of-file-fixer
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
- rev: v0.12.1
+ rev: v0.15.1
hooks:
# Run the linter.
- id: ruff-check
diff --git a/CHANGES b/CHANGES
index b7837481..c6ed6b31 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,34 @@
# Changelog
+## Version 6.1.0
+
+* Adding #717 Output Naming settings tab with template editor, clickable variable chips, live preview, and validation for customizing output filenames with 23 pre-encode variables (thanks to roxerqermik)
+* Adding #660 Data & Attachments tab for per-track control of data streams (timecodes, navigation) and non-image attachments (fonts) (thanks to techguru0)
+* Adding #706 language and disposition metadata parsing from auto-detected external subtitle filenames (e.g., video.forced.deu.srt) (thanks to mpek)
+* Adding #706 auto-detection of external subtitle files (.srt, .ass, .ssa, .vtt, .sup, .sub, .idx) when loading a video, configurable in Settings (thanks to mpek)
+* Adding #698 external subtitle support for Rigaya encoders (NVEncC, QSVEncC, VCEEncC) via --sub-source (thanks to Augusto7743)
+* Adding always-on ffprobe validation of output files after every encode to catch silent failures
+* Adding Visual Crop window for dragging crop edges directly on a video frame preview, with live overlay and divisible-by-8 snapping on save
+* Adding bottom status bar with animated icon showing encoding state, progress bar, and status messages
+* Adding startup tasks (FFmpeg config, GPU detect, HDR10+ download) running through the status bar with main window visible
+* Adding Terms and Agreements dialog shown on first startup requiring user acceptance before proceeding
+* Fixing #719 Unable to save/load film grain setting for SVT-AV1 (thanks to gabriel101x)
+* Fixing #716 Maximize button not working (thanks to roxerqermik and 19Battlestar65)
+* Fixing #349 NVEncC audio conversion losing multichannel layout for EAC3 (thanks to Wontell)
+* Fixing #384 Remove HDR leaving Dolby Vision metadata traces in Rigaya encoder output (thanks to end2endzone)
+* Fixing #511 UI labels and buttons truncated in non-English translations by auto-shrinking text to fit (thanks to PegHorse)
+* Fixing #514 excessive memory usage when adding directory of files (thanks to gxcreator)
+* Fixing #548 incorrect aspect ratio for DVD sources with non-square pixels on auto resolution (thanks to DCNerds)
+* Fixing #600 anime subtitle size increasing during burn-in encoding (thanks to TinderboxUK)
+* Fixing #693 subtitle tracks losing title metadata during encoding (thanks to mpissarello)
+* Fixing #715 WINDOWS_BUILD.md needed updated to show Python 3.13 (thanks to Jack L)
+* Fixing #720 custom profile resolution settings (Height, Width, Long Edge, Custom, explicit like 640x480) being ignored when loading a video, defaulting to source resolution instead of profile resolution (thanks to Xoanon88)
+* Fixing UI scaling for Source/Folder/Filename text boxes, file extension dropdown, Resolution label, Start/End Time controls, and Crop input fields being too small
+* Fixing profile load/save for VVC period and threads, VP9 auto alt ref, lag in frames, AQ mode, and sharpness, rav1e photon noise, and AOM-AV1 denoise settings using integer as combo box index instead of matching by value
+* Fixing VCEEncC pre-analysis lookahead setting reading from wrong widget (pa_initqpsc instead of pa_lookahead) in HEVC, AV1, and AVC encoders
+* Fixing Settings window staying on top and freezing when notification dialogs appeared behind it
+* Fixing tab bar scroll arrows being too small and both stuck on the right side - now larger with left arrow on far left
+
## Version 6.0.1
* Fixing Dolby Vision copy for Rigaya encoders (NVEncC, QSVEncC, VCEEncC) by adding --dolby-vision-profile copy alongside --dolby-vision-rpu copy
diff --git a/WINDOWS_BUILD.md b/WINDOWS_BUILD.md
index e34465e2..8f46effa 100644
--- a/WINDOWS_BUILD.md
+++ b/WINDOWS_BUILD.md
@@ -4,7 +4,7 @@ This guide explains how to build FastFlix executables on Windows.
## Prerequisites
-1. **Python 3.12 or higher**
+1. **Python 3.13 or higher**
- Download from [python.org](https://www.python.org/downloads/)
- Make sure to check "Add Python to PATH" during installation
diff --git a/fastflix/application.py b/fastflix/application.py
index b98c7bb0..bacd25b1 100644
--- a/fastflix/application.py
+++ b/fastflix/application.py
@@ -15,8 +15,9 @@
from fastflix.program_downloads import ask_for_ffmpeg, grab_stable_ffmpeg, download_hdr10plus_tool
from fastflix.resources import main_icon, breeze_styles_path
from fastflix.shared import file_date, message, latest_fastflix, DEVMODE, yes_no_message
+from fastflix.ui_constants import FONTS
from fastflix.widgets.container import Container
-from fastflix.widgets.progress_bar import ProgressBar, Task
+from fastflix.widgets.status_bar import Task, STATE_IDLE, STATE_ERROR
from fastflix.gpu_detect import automatic_rigaya_download
logger = logging.getLogger("fastflix")
@@ -41,10 +42,14 @@ def create_app(enable_scaling):
main_app = FastFlixApp(sys.argv)
main_app.allWindows()
main_app.setApplicationDisplayName("FastFlix")
+
+ # On Linux, ensure an icon theme is set so QFileDialog toolbar icons appear
+ if sys.platform == "linux" and not QtGui.QIcon.themeName():
+ QtGui.QIcon.setThemeName("breeze")
available_fonts = QtGui.QFontDatabase().families()
font_preference = ["Roboto", "Segoe UI", "Ubuntu", "Open Sans", "Sans Serif"]
selected_font = next((f for f in font_preference if f in available_fonts), "Sans Serif")
- my_font = QtGui.QFont(selected_font, 9)
+ my_font = QtGui.QFont(selected_font, FONTS.SMALL)
main_app.setFont(my_font)
icon = QtGui.QIcon()
icon.addFile(main_icon, QtCore.QSize(16, 16))
@@ -203,24 +208,30 @@ def app_setup(
f"{app.fastflix.config.config_path}",
title="Upgraded",
)
+ missing_ff = False
try:
app.fastflix.config.load(portable_mode=portable_mode)
except MissingFF as err:
if reusables.win_based and ask_for_ffmpeg():
- try:
- ProgressBar(app, [Task(t("Downloading FFmpeg"), grab_stable_ffmpeg)], signal_task=True)
- app.fastflix.config.load()
- except Exception as err:
- logger.exception(str(err))
- sys.exit(1)
+ # User wants to download FFmpeg — will be handled after Container is shown
+ missing_ff = "download"
else:
- logger.error(f"Could not find {err} location, please manually set in {app.fastflix.config.config_path}")
- sys.exit(1)
+ missing_ff = str(err)
+ logger.warning(f"FFmpeg not found during config load: {err}")
except Exception:
- # TODO give edit / delete options
logger.exception(t("Could not load config file!"))
sys.exit(1)
+ if not app.fastflix.config.terms_accepted:
+ from fastflix.widgets.terms_agreement import TermsAgreementDialog
+
+ dialog = TermsAgreementDialog()
+ if dialog.exec() == QtWidgets.QDialog.Accepted:
+ app.fastflix.config.terms_accepted = True
+ app.fastflix.config.save()
+ else:
+ sys.exit(0)
+
if app.fastflix.config.theme != "system":
file = QtCore.QFile(str(breeze_styles_path / app.fastflix.config.theme / "stylesheet.qss"))
file.open(QtCore.QFile.OpenModeFlag.ReadOnly | QtCore.QFile.OpenModeFlag.Text)
@@ -239,8 +250,65 @@ def app_setup(
app.setStyleSheet(data)
+ # On Linux/KDE, applying a custom stylesheet can disrupt the platform
+ # icon theme for standard dialog icons (e.g., QFileDialog toolbar).
+ # Re-asserting the icon theme after stylesheet application restores them.
+ if sys.platform == "linux":
+ theme_name = QtGui.QIcon.themeName() or "breeze"
+ QtGui.QIcon.setThemeName(theme_name)
+
logger.setLevel(app.fastflix.config.logging_level)
+ # Initialize empty encoder/audio lists so Container can be created before startup tasks
+ if app.fastflix.encoders is None:
+ app.fastflix.encoders = {}
+ if app.fastflix.audio_encoders is None:
+ app.fastflix.audio_encoders = []
+
+ # Create and show Container immediately (UI starts disabled via Main.__init__)
+ container = Container(app)
+ container.show()
+
+ cursor_pos = QtGui.QCursor.pos()
+ screen = QtGui.QGuiApplication.screenAt(cursor_pos) or QtGui.QGuiApplication.primaryScreen()
+ screen_geometry = screen.availableGeometry()
+ container.move(screen_geometry.center() - container.rect().center())
+
+ # Disable entire window during startup tasks
+ container.setEnabled(False)
+ app.processEvents()
+
+ # Handle missing FFmpeg
+ if missing_ff:
+ if missing_ff == "download":
+ # Download FFmpeg through status bar
+ try:
+ container.status_bar.run_tasks(
+ [Task(t("Downloading FFmpeg"), grab_stable_ffmpeg)],
+ signal_task=True,
+ persist_complete=True,
+ )
+ app.fastflix.config.load()
+ except Exception as err:
+ logger.exception(str(err))
+ container.status_bar.set_state(
+ STATE_ERROR,
+ t("FFmpeg not found") + " — " + t("configure in Settings") + " (Ctrl+S)",
+ )
+ container.setEnabled(True)
+ return app
+ else:
+ logger.error(
+ f"Could not find {missing_ff} location, please manually set in {app.fastflix.config.config_path}"
+ )
+ container.status_bar.set_state(
+ STATE_ERROR,
+ t("FFmpeg not found") + " — " + t("configure in Settings") + " (Ctrl+S)",
+ )
+ container.setEnabled(True)
+ return app
+
+ # GPU detect and HDR10+ download (Windows only, with user permission dialogs)
if platform.system() == "Windows":
if app.fastflix.config.auto_gpu_check is None:
app.fastflix.config.auto_gpu_check = yes_no_message(
@@ -250,9 +318,14 @@ def app_setup(
title="Allow Optional Downloads",
)
if app.fastflix.config.auto_gpu_check:
- ProgressBar(
- app, [Task(name=t("Detect GPUs"), command=automatic_rigaya_download)], signal_task=True, can_cancel=True
- )
+ try:
+ container.status_bar.run_tasks(
+ [Task(name=t("Detect GPUs"), command=automatic_rigaya_download)],
+ signal_task=True,
+ can_cancel=True,
+ )
+ except Exception:
+ logger.exception("Failed to detect GPUs")
if app.fastflix.config.auto_hdr10plus_check is None and not app.fastflix.config.hdr10plus_parser:
app.fastflix.config.auto_hdr10plus_check = yes_no_message(
@@ -263,8 +336,7 @@ def app_setup(
)
if app.fastflix.config.auto_hdr10plus_check:
try:
- ProgressBar(
- app,
+ container.status_bar.run_tasks(
[Task(t("Downloading HDR10+ Tool"), download_hdr10plus_tool)],
signal_task=True,
can_cancel=True,
@@ -277,6 +349,7 @@ def app_setup(
app.fastflix.config.save()
+ # Run startup tasks (FFmpeg config, encoder init) through status bar
startup_tasks = [
Task(t("Gather FFmpeg version"), ffmpeg_configuration),
Task(t("Gather FFprobe version"), ffprobe_configuration),
@@ -286,17 +359,19 @@ def app_setup(
]
try:
- ProgressBar(app, startup_tasks)
+ container.status_bar.run_tasks(startup_tasks, persist_complete=True)
except Exception:
logger.exception(f"{t('Could not start FastFlix')}!")
- sys.exit(1)
+ container.status_bar.set_state(STATE_ERROR, t("Could not start FastFlix"))
+ container.setEnabled(True)
+ return app
- container = Container(app)
- container.show()
+ # Encoders are now populated — initialize the encoder UI
+ container.main.init_encoders_ui()
- # container.move(QtGui.QGuiApplication.primaryScreen().availableGeometry().center() - container.rect().center())
- screen_geometry = QtGui.QGuiApplication.primaryScreen().availableGeometry()
- container.move(screen_geometry.center() - container.rect().center())
+ # Re-enable UI after startup tasks complete
+ container.setEnabled(True)
+ container.status_bar.set_state(STATE_IDLE)
if not app.fastflix.config.disable_version_check:
QtCore.QTimer.singleShot(500, lambda: latest_fastflix(app=app, show_new_dialog=False))
diff --git a/fastflix/data/icons/black/crop.svg b/fastflix/data/icons/black/crop.svg
new file mode 100644
index 00000000..ad9d78cc
--- /dev/null
+++ b/fastflix/data/icons/black/crop.svg
@@ -0,0 +1,15 @@
+
diff --git a/fastflix/data/icons/black/onyx-data.svg b/fastflix/data/icons/black/onyx-data.svg
new file mode 100644
index 00000000..6b1b2c30
--- /dev/null
+++ b/fastflix/data/icons/black/onyx-data.svg
@@ -0,0 +1 @@
+
diff --git a/fastflix/data/icons/selected/onyx-data.svg b/fastflix/data/icons/selected/onyx-data.svg
new file mode 100644
index 00000000..9dc3836f
--- /dev/null
+++ b/fastflix/data/icons/selected/onyx-data.svg
@@ -0,0 +1 @@
+
diff --git a/fastflix/data/icons/white/crop.svg b/fastflix/data/icons/white/crop.svg
new file mode 100644
index 00000000..df0f177c
--- /dev/null
+++ b/fastflix/data/icons/white/crop.svg
@@ -0,0 +1,15 @@
+
diff --git a/fastflix/data/icons/white/onyx-data.png b/fastflix/data/icons/white/onyx-data.png
new file mode 100644
index 00000000..6f4b0a99
Binary files /dev/null and b/fastflix/data/icons/white/onyx-data.png differ
diff --git a/fastflix/data/languages.yaml b/fastflix/data/languages.yaml
index b4a6a1a9..33d0fa0d 100644
--- a/fastflix/data/languages.yaml
+++ b/fastflix/data/languages.yaml
@@ -1831,7 +1831,7 @@ Encoder Settings:
Encoding Queue:
deu: Kodierer-Warteschlange
eng: Encoding Queue
- fra: File d'attente d'encodage
+ fra: Attente de chargement
ita: Coda codifica
spa: Cola de codificación
chs: 编码队列
@@ -12438,3 +12438,1512 @@ Video Info:
ukr: Інформація про відео
kor: 비디오 정보
ron: Informații video
+Accept:
+ eng: Accept
+ deu: Akzeptieren
+ fra: Accepter
+ ita: Accetta
+ spa: Aceptar
+ chs: 接受
+ jpn: 同意する
+ rus: Принять
+ por: Aceitar
+ swe: Acceptera
+ pol: Akceptuj
+ ukr: Прийняти
+ kor: 동의
+ ron: Acceptă
+Reject:
+ eng: Reject
+ deu: Ablehnen
+ fra: Refuser
+ ita: Rifiuta
+ spa: Rechazar
+ chs: 拒绝
+ jpn: 拒否する
+ rus: Отклонить
+ por: Rejeitar
+ swe: Avvisa
+ pol: Odrzuć
+ ukr: Відхилити
+ kor: 거부
+ ron: Respinge
+FastFlix Terms and Agreements:
+ eng: FastFlix Terms and Agreements
+ deu: FastFlix Nutzungsbedingungen
+ fra: Conditions d'utilisation de FastFlix
+ ita: Termini e condizioni di FastFlix
+ spa: Términos y acuerdos de FastFlix
+ chs: FastFlix 条款和协议
+ jpn: FastFlix 利用規約
+ rus: Условия и соглашения FastFlix
+ por: Termos e acordos do FastFlix
+ swe: FastFlix villkor och avtal
+ pol: Warunki i umowy FastFlix
+ ukr: Умови та угоди FastFlix
+ kor: FastFlix 이용 약관
+ ron: Termeni și acorduri FastFlix
+"I have read and agree to the Terms and Agreements":
+ eng: I have read and agree to the Terms and Agreements
+ deu: Ich habe die Nutzungsbedingungen gelesen und stimme ihnen zu
+ fra: J'ai lu et j'accepte les conditions d'utilisation
+ ita: Ho letto e accetto i termini e le condizioni
+ spa: He leído y acepto los términos y acuerdos
+ chs: 我已阅读并同意条款和协议
+ jpn: 利用規約を読み、同意します
+ rus: Я прочитал и согласен с условиями и соглашениями
+ por: Li e concordo com os termos e acordos
+ swe: Jag har läst och godkänner villkoren och avtalen
+ pol: Przeczytałem i zgadzam się z warunkami i umowami
+ ukr: Я прочитав і погоджуюся з умовами та угодами
+ kor: 이용 약관을 읽었으며 동의합니다
+ ron: Am citit și sunt de acord cu termenii și acordurile
+"By using FastFlix, you agree to the following terms:":
+ eng: "By using FastFlix, you agree to the following terms:"
+ deu: "Durch die Nutzung von FastFlix stimmen Sie den folgenden Bedingungen zu:"
+ fra: "En utilisant FastFlix, vous acceptez les conditions suivantes :"
+ ita: "Utilizzando FastFlix, accetti i seguenti termini:"
+ spa: "Al usar FastFlix, aceptas los siguientes términos:"
+ chs: "使用 FastFlix 即表示您同意以下条款:"
+ jpn: "FastFlixを使用することにより、以下の規約に同意するものとします:"
+ rus: "Используя FastFlix, вы соглашаетесь со следующими условиями:"
+ por: "Ao usar o FastFlix, você concorda com os seguintes termos:"
+ swe: "Genom att använda FastFlix godkänner du följande villkor:"
+ pol: "Korzystając z FastFlix, zgadzasz się na następujące warunki:"
+ ukr: "Використовуючи FastFlix, ви погоджуєтеся з наступними умовами:"
+ kor: "FastFlix를 사용함으로써 다음 약관에 동의하게 됩니다:"
+ ron: "Prin utilizarea FastFlix, sunteți de acord cu următorii termeni:"
+1. Authorized Use Only:
+ eng: 1. Authorized Use Only
+ deu: 1. Nur autorisierte Nutzung
+ fra: 1. Utilisation autorisée uniquement
+ ita: 1. Solo uso autorizzato
+ spa: 1. Solo uso autorizado
+ chs: 1. 仅限授权使用
+ jpn: 1. 許可された使用のみ
+ rus: 1. Только авторизованное использование
+ por: 1. Apenas uso autorizado
+ swe: 1. Endast auktoriserad användning
+ pol: 1. Tylko autoryzowane użycie
+ ukr: 1. Лише авторизоване використання
+ kor: 1. 승인된 사용만 허용
+ ron: 1. Doar utilizare autorizată
+? "You must only use FastFlix to encode, transcode, or otherwise process video content that you own or have the legal rights to use. Unauthorized copying, encoding, or distribution of copyrighted material
+ is strictly prohibited."
+: eng: "You must only use FastFlix to encode, transcode, or otherwise process video content that you own or have the legal rights to use. Unauthorized copying, encoding, or distribution of copyrighted material
+ is strictly prohibited."
+ deu: "Sie dürfen FastFlix nur zum Kodieren, Transkodieren oder anderweitigen Verarbeiten von Videoinhalten verwenden, die Ihnen gehören oder für die Sie die gesetzlichen Nutzungsrechte besitzen. Das unbefugte
+ Kopieren, Kodieren oder Verbreiten von urheberrechtlich geschütztem Material ist strengstens untersagt."
+ fra: "Vous ne devez utiliser FastFlix que pour encoder, transcoder ou traiter des contenus vidéo dont vous êtes propriétaire ou pour lesquels vous disposez des droits légaux d'utilisation. La copie, l'encodage
+ ou la distribution non autorisés de matériel protégé par le droit d'auteur sont strictement interdits."
+ ita: "Devi utilizzare FastFlix solo per codificare, transcodificare o elaborare contenuti video di cui sei proprietario o per i quali possiedi i diritti legali di utilizzo. La copia, la codifica o la
+ distribuzione non autorizzata di materiale protetto da copyright è severamente vietata."
+ spa: "Solo debes usar FastFlix para codificar, transcodificar o procesar contenido de video del cual seas propietario o tengas los derechos legales de uso. La copia, codificación o distribución no autorizada
+ de material protegido por derechos de autor está estrictamente prohibida."
+ chs: "您只能使用 FastFlix 来编码、转码或以其他方式处理您拥有或具有合法使用权的视频内容。严禁未经授权复制、编码或分发受版权保护的材料。"
+ jpn: "FastFlixは、お客様が所有する、または使用する法的権利を有するビデオコンテンツのエンコード、トランスコード、またはその他の処理にのみ使用してください。著作権で保護された素材の無断複製、エンコード、または配布は固く禁じられています。"
+ rus: "Вы должны использовать FastFlix только для кодирования, перекодирования или иной обработки видеоконтента, который принадлежит вам или на использование которого у вас есть законные права. Несанкционированное
+ копирование, кодирование или распространение материалов, защищённых авторским правом, строго запрещено."
+ por: "Você deve usar o FastFlix apenas para codificar, transcodificar ou processar conteúdo de vídeo que você possui ou tem os direitos legais de uso. A cópia, codificação ou distribuição não autorizada
+ de material protegido por direitos autorais é estritamente proibida."
+ swe: "Du får bara använda FastFlix för att koda, transkoda eller på annat sätt bearbeta videoinnehåll som du äger eller har laglig rätt att använda. Otillåten kopiering, kodning eller distribution av
+ upphovsrättsskyddat material är strängt förbjuden."
+ pol: "FastFlix należy używać wyłącznie do kodowania, transkodowania lub innego przetwarzania treści wideo, których jesteś właścicielem lub do których masz prawa do użytkowania. Nieautoryzowane kopiowanie,
+ kodowanie lub dystrybucja materiałów chronionych prawem autorskim jest surowo zabronione."
+ ukr: "Ви повинні використовувати FastFlix лише для кодування, перекодування або іншої обробки відеоконтенту, який належить вам або на використання якого ви маєте законні права. Несанкціоноване копіювання,
+ кодування або розповсюдження матеріалів, захищених авторським правом, суворо заборонено."
+ kor: "FastFlix는 본인이 소유하거나 사용할 법적 권리가 있는 비디오 콘텐츠를 인코딩, 트랜스코딩 또는 처리하는 데에만 사용해야 합니다. 저작권이 있는 자료의 무단 복제, 인코딩 또는 배포는 엄격히 금지됩니다."
+ ron: "Trebuie să utilizați FastFlix doar pentru a codifica, transcoda sau prelucra conținut video pe care îl dețineți sau pentru care aveți drepturi legale de utilizare. Copierea, codificarea sau distribuirea
+ neautorizată a materialelor protejate de drepturi de autor este strict interzisă."
+2. No Developer Liability for Misuse:
+ eng: 2. No Developer Liability for Misuse
+ deu: 2. Keine Haftung der Entwickler bei Missbrauch
+ fra: 2. Aucune responsabilité des développeurs en cas d'utilisation abusive
+ ita: 2. Nessuna responsabilità degli sviluppatori per uso improprio
+ spa: 2. Sin responsabilidad del desarrollador por uso indebido
+ chs: 2. 开发者不对滥用承担责任
+ jpn: 2. 悪用に対する開発者の免責
+ rus: 2. Отсутствие ответственности разработчиков за злоупотребление
+ por: 2. Sem responsabilidade do desenvolvedor por uso indevido
+ swe: 2. Inget utvecklaransvar vid missbruk
+ pol: 2. Brak odpowiedzialności deweloperów za nadużycia
+ ukr: 2. Відсутність відповідальності розробників за зловживання
+ kor: 2. 오용에 대한 개발자 면책
+ ron: 2. Nicio responsabilitate a dezvoltatorilor pentru utilizarea abuzivă
+? "The developers and contributors of FastFlix are not responsible for any misuse of this software, including but not limited to the unauthorized reproduction or distribution of copyrighted content. You
+ assume full responsibility for how you use FastFlix."
+: eng: "The developers and contributors of FastFlix are not responsible for any misuse of this software, including but not limited to the unauthorized reproduction or distribution of copyrighted content.
+ You assume full responsibility for how you use FastFlix."
+ deu: "Die Entwickler und Mitwirkenden von FastFlix sind nicht verantwortlich für jeglichen Missbrauch dieser Software, einschließlich, aber nicht beschränkt auf die unbefugte Vervielfältigung oder Verbreitung
+ urheberrechtlich geschützter Inhalte. Sie übernehmen die volle Verantwortung für die Art und Weise, wie Sie FastFlix nutzen."
+ fra: "Les développeurs et contributeurs de FastFlix ne sont pas responsables de toute utilisation abusive de ce logiciel, y compris, mais sans s'y limiter, la reproduction ou la distribution non autorisées
+ de contenus protégés par le droit d'auteur. Vous assumez l'entière responsabilité de la manière dont vous utilisez FastFlix."
+ ita: "Gli sviluppatori e i contributori di FastFlix non sono responsabili per qualsiasi uso improprio di questo software, inclusa, ma non limitata alla riproduzione o distribuzione non autorizzata di
+ contenuti protetti da copyright. Ti assumi la piena responsabilità di come utilizzi FastFlix."
+ spa: "Los desarrolladores y colaboradores de FastFlix no son responsables de ningún uso indebido de este software, incluyendo, entre otros, la reproducción o distribución no autorizada de contenido protegido
+ por derechos de autor. Asumes toda la responsabilidad por cómo usas FastFlix."
+ chs: "FastFlix 的开发者和贡献者不对本软件的任何滥用行为承担责任,包括但不限于未经授权复制或分发受版权保护的内容。您对使用 FastFlix 的方式承担全部责任。"
+ jpn: "FastFlixの開発者および貢献者は、著作権で保護されたコンテンツの無断複製または配布を含むがこれに限定されない、本ソフトウェアのいかなる悪用についても責任を負いません。FastFlixの使用方法については、お客様が全責任を負うものとします。"
+ rus: "Разработчики и участники FastFlix не несут ответственности за любое злоупотребление данным программным обеспечением, включая, но не ограничиваясь, несанкционированное воспроизведение или распространение
+ контента, защищённого авторским правом. Вы несёте полную ответственность за то, как вы используете FastFlix."
+ por: "Os desenvolvedores e colaboradores do FastFlix não são responsáveis por qualquer uso indevido deste software, incluindo, mas não se limitando à reprodução ou distribuição não autorizada de conteúdo
+ protegido por direitos autorais. Você assume total responsabilidade pela forma como usa o FastFlix."
+ swe: "Utvecklarna och bidragsgivarna till FastFlix ansvarar inte för något missbruk av denna programvara, inklusive men inte begränsat till otillåten reproduktion eller distribution av upphovsrättsskyddat
+ innehåll. Du tar fullt ansvar för hur du använder FastFlix."
+ pol: "Deweloperzy i współtwórcy FastFlix nie ponoszą odpowiedzialności za jakiekolwiek nadużycia tego oprogramowania, w tym między innymi za nieautoryzowane powielanie lub dystrybucję treści chronionych
+ prawem autorskim. Ponosisz pełną odpowiedzialność za sposób korzystania z FastFlix."
+ ukr: "Розробники та учасники FastFlix не несуть відповідальності за будь-яке зловживання цим програмним забезпеченням, включаючи, але не обмежуючись, несанкціоноване відтворення або розповсюдження контенту,
+ захищеного авторським правом. Ви несете повну відповідальність за те, як ви використовуєте FastFlix."
+ kor: "FastFlix의 개발자 및 기여자는 저작권으로 보호되는 콘텐츠의 무단 복제 또는 배포를 포함하되 이에 국한되지 않는 본 소프트웨어의 오용에 대해 책임을 지지 않습니다. FastFlix 사용 방법에 대한 전적인 책임은 사용자에게 있습니다."
+ ron: "Dezvoltatorii și contribuitorii FastFlix nu sunt responsabili pentru nicio utilizare abuzivă a acestui software, inclusiv, dar fără a se limita la reproducerea sau distribuirea neautorizată a conținutului
+ protejat de drepturi de autor. Vă asumați întreaga responsabilitate pentru modul în care utilizați FastFlix."
+3. No Warranty:
+ eng: 3. No Warranty
+ deu: 3. Keine Gewährleistung
+ fra: 3. Aucune garantie
+ ita: 3. Nessuna garanzia
+ spa: 3. Sin garantía
+ chs: 3. 无保证
+ jpn: 3. 無保証
+ rus: 3. Отсутствие гарантий
+ por: 3. Sem garantia
+ swe: 3. Ingen garanti
+ pol: 3. Brak gwarancji
+ ukr: 3. Відсутність гарантій
+ kor: 3. 무보증
+ ron: 3. Fără garanție
+? 'FastFlix is provided "as-is" without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement.'
+: eng: 'FastFlix is provided "as-is" without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement.'
+ deu: 'FastFlix wird „wie besehen" ohne jegliche ausdrückliche oder stillschweigende Gewährleistung bereitgestellt, einschließlich, aber nicht beschränkt auf die Gewährleistung der Marktgängigkeit, der
+ Eignung für einen bestimmten Zweck und der Nichtverletzung von Rechten.'
+ fra: 'FastFlix est fourni « tel quel » sans garantie d''aucune sorte, expresse ou implicite, y compris, mais sans s''y limiter, les garanties de qualité marchande, d''adéquation à un usage particulier
+ et de non-contrefaçon.'
+ ita: 'FastFlix è fornito "così com''è" senza garanzia di alcun tipo, espressa o implicita, incluse, ma non limitate alle garanzie di commerciabilità, idoneità per uno scopo particolare e non violazione.'
+ spa: 'FastFlix se proporciona "tal cual" sin garantía de ningún tipo, expresa o implícita, incluyendo, entre otras, las garantías de comerciabilidad, idoneidad para un propósito particular y no infracción.'
+ chs: 'FastFlix 按"原样"提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途适用性和不侵权的保证。'
+ jpn: FastFlixは、商品性、特定目的への適合性、および非侵害の保証を含むがこれに限定されない、明示または黙示のいかなる保証もなく「現状のまま」提供されます。
+ rus: FastFlix предоставляется «как есть» без каких-либо гарантий, явных или подразумеваемых, включая, но не ограничиваясь, гарантии товарной пригодности, пригодности для определённой цели и
+ ненарушения прав.
+ por: 'O FastFlix é fornecido "como está" sem garantia de qualquer tipo, expressa ou implícita, incluindo, mas não se limitando às garantias de comercialização, adequação a um propósito específico e não
+ violação.'
+ swe: 'FastFlix tillhandahålls "i befintligt skick" utan garanti av något slag, uttrycklig eller underförstådd, inklusive men inte begränsat till garantier om säljbarhet, lämplighet för ett visst ändamål
+ och icke-intrång.'
+ pol: 'FastFlix jest dostarczany „tak jak jest" bez jakiejkolwiek gwarancji, wyraźnej lub dorozumianej, w tym między innymi gwarancji przydatności handlowej, przydatności do określonego celu i nienaruszalności
+ praw.'
+ ukr: FastFlix надається «як є» без будь-яких гарантій, явних або неявних, включаючи, але не обмежуючись, гарантії придатності для продажу, придатності для певної мети та непорушення прав.
+ kor: 'FastFlix는 상품성, 특정 목적에의 적합성 및 비침해에 대한 보증을 포함하되 이에 국한되지 않는 어떠한 종류의 명시적 또는 묵시적 보증 없이 "있는 그대로" 제공됩니다.'
+ ron: 'FastFlix este furnizat „ca atare" fără garanție de niciun fel, expresă sau implicită, inclusiv, dar fără a se limita la garanțiile de vandabilitate, adecvare pentru un anumit scop și neîncălcare.'
+4. Copyright Compliance:
+ eng: 4. Copyright Compliance
+ deu: 4. Einhaltung des Urheberrechts
+ fra: 4. Conformité au droit d'auteur
+ ita: 4. Conformità al diritto d'autore
+ spa: 4. Cumplimiento de derechos de autor
+ chs: 4. 版权合规
+ jpn: 4. 著作権の遵守
+ rus: 4. Соблюдение авторских прав
+ por: 4. Conformidade com direitos autorais
+ swe: 4. Upphovsrättsefterlevnad
+ pol: 4. Zgodność z prawem autorskim
+ ukr: 4. Дотримання авторських прав
+ kor: 4. 저작권 준수
+ ron: 4. Conformitatea cu drepturile de autor
+? "You assume all responsibility for compliance with applicable copyright laws and regulations in your jurisdiction. It is your obligation to ensure that your use of FastFlix does not violate any third-party
+ rights."
+: eng: "You assume all responsibility for compliance with applicable copyright laws and regulations in your jurisdiction. It is your obligation to ensure that your use of FastFlix does not violate any third-party
+ rights."
+ deu: "Sie übernehmen die volle Verantwortung für die Einhaltung der geltenden Urheberrechtsgesetze und -vorschriften in Ihrem Rechtsgebiet. Es ist Ihre Pflicht sicherzustellen, dass Ihre Nutzung von FastFlix
+ keine Rechte Dritter verletzt."
+ fra: "Vous assumez l'entière responsabilité du respect des lois et réglementations applicables en matière de droit d'auteur dans votre juridiction. Il vous incombe de vous assurer que votre utilisation
+ de FastFlix ne viole aucun droit de tiers."
+ ita: "Ti assumi la piena responsabilità per la conformità alle leggi e ai regolamenti applicabili sul diritto d'autore nella tua giurisdizione. È tuo obbligo assicurarti che il tuo utilizzo di FastFlix
+ non violi alcun diritto di terzi."
+ spa: "Asumes toda la responsabilidad del cumplimiento de las leyes y regulaciones de derechos de autor aplicables en tu jurisdicción. Es tu obligación asegurarte de que tu uso de FastFlix no viole ningún
+ derecho de terceros."
+ chs: "您对遵守您所在司法管辖区适用的版权法律法规承担全部责任。确保您对 FastFlix 的使用不侵犯任何第三方权利是您的义务。"
+ jpn: "お客様の管轄区域で適用される著作権法および規制の遵守について、お客様が全責任を負うものとします。FastFlixの使用が第三者の権利を侵害しないことを確認するのは、お客様の義務です。"
+ rus: "Вы несёте полную ответственность за соблюдение применимых законов и нормативных актов об авторском праве в вашей юрисдикции. Вы обязаны убедиться, что ваше использование FastFlix не нарушает права
+ третьих лиц."
+ por: "Você assume toda a responsabilidade pelo cumprimento das leis e regulamentos de direitos autorais aplicáveis em sua jurisdição. É sua obrigação garantir que seu uso do FastFlix não viole nenhum
+ direito de terceiros."
+ swe: "Du tar fullt ansvar för efterlevnad av tillämpliga upphovsrättslagar och förordningar i din jurisdiktion. Det är din skyldighet att säkerställa att din användning av FastFlix inte kränker någon
+ tredje parts rättigheter."
+ pol: "Ponosisz pełną odpowiedzialność za przestrzeganie obowiązujących przepisów prawa autorskiego w Twojej jurysdykcji. Twoim obowiązkiem jest upewnienie się, że korzystanie z FastFlix nie narusza praw
+ osób trzecich."
+ ukr: "Ви несете повну відповідальність за дотримання чинних законів та нормативних актів про авторське право у вашій юрисдикції. Ви зобов'язані переконатися, що ваше використання FastFlix не порушує права
+ третіх осіб."
+ kor: "귀하의 관할권에서 적용되는 저작권법 및 규정을 준수할 모든 책임은 사용자에게 있습니다. FastFlix 사용이 제3자의 권리를 침해하지 않도록 하는 것은 사용자의 의무입니다."
+ ron: "Vă asumați întreaga responsabilitate pentru respectarea legilor și reglementărilor aplicabile privind drepturile de autor din jurisdicția dumneavoastră. Este obligația dumneavoastră să vă asigurați
+ că utilizarea FastFlix nu încalcă drepturile niciunui terț."
+5. Limitation of Liability:
+ eng: 5. Limitation of Liability
+ deu: 5. Haftungsbeschränkung
+ fra: 5. Limitation de responsabilité
+ ita: 5. Limitazione di responsabilità
+ spa: 5. Limitación de responsabilidad
+ chs: 5. 责任限制
+ jpn: 5. 責任の制限
+ rus: 5. Ограничение ответственности
+ por: 5. Limitação de responsabilidade
+ swe: 5. Ansvarsbegränsning
+ pol: 5. Ograniczenie odpowiedzialności
+ ukr: 5. Обмеження відповідальності
+ kor: 5. 책임 제한
+ ron: 5. Limitarea răspunderii
+? "In no event shall the developers or contributors of FastFlix be liable for any damages arising from the use of this software, including but not limited to direct, indirect, incidental, special, or consequential
+ damages."
+: eng: "In no event shall the developers or contributors of FastFlix be liable for any damages arising from the use of this software, including but not limited to direct, indirect, incidental, special,
+ or consequential damages."
+ deu: "Die Entwickler oder Mitwirkenden von FastFlix haften in keinem Fall für Schäden, die aus der Nutzung dieser Software entstehen, einschließlich, aber nicht beschränkt auf direkte, indirekte, zufällige,
+ besondere oder Folgeschäden."
+ fra: "En aucun cas, les développeurs ou contributeurs de FastFlix ne pourront être tenus responsables de tout dommage résultant de l'utilisation de ce logiciel, y compris, mais sans s'y limiter, les dommages
+ directs, indirects, accessoires, spéciaux ou consécutifs."
+ ita: "In nessun caso gli sviluppatori o i contributori di FastFlix saranno responsabili per eventuali danni derivanti dall'uso di questo software, inclusi, ma non limitati a danni diretti, indiretti,
+ incidentali, speciali o consequenziali."
+ spa: "En ningún caso los desarrolladores o colaboradores de FastFlix serán responsables de ningún daño derivado del uso de este software, incluyendo, entre otros, daños directos, indirectos, incidentales,
+ especiales o consecuentes."
+ chs: "在任何情况下,FastFlix 的开发者或贡献者均不对因使用本软件而产生的任何损害承担责任,包括但不限于直接、间接、附带、特殊或后果性损害。"
+ jpn: "いかなる場合も、FastFlixの開発者または貢献者は、直接的、間接的、偶発的、特別、または結果的損害を含むがこれに限定されない、本ソフトウェアの使用から生じるいかなる損害についても責任を負わないものとします。"
+ rus: "Ни при каких обстоятельствах разработчики или участники FastFlix не несут ответственности за любой ущерб, возникший в результате использования данного программного обеспечения, включая, но не ограничиваясь,
+ прямой, косвенный, случайный, особый или последующий ущерб."
+ por: "Em nenhum caso os desenvolvedores ou colaboradores do FastFlix serão responsáveis por quaisquer danos decorrentes do uso deste software, incluindo, mas não se limitando a danos diretos, indiretos,
+ incidentais, especiais ou consequenciais."
+ swe: "Under inga omständigheter ska utvecklarna eller bidragsgivarna till FastFlix hållas ansvariga för eventuella skador som uppstår till följd av användningen av denna programvara, inklusive men inte
+ begränsat till direkta, indirekta, tillfälliga, speciella eller följdskador."
+ pol: "W żadnym wypadku deweloperzy ani współtwórcy FastFlix nie ponoszą odpowiedzialności za jakiekolwiek szkody wynikające z użytkowania tego oprogramowania, w tym między innymi za szkody bezpośrednie,
+ pośrednie, przypadkowe, szczególne lub wynikowe."
+ ukr: "За жодних обставин розробники або учасники FastFlix не несуть відповідальності за будь-які збитки, що виникли внаслідок використання цього програмного забезпечення, включаючи, але не обмежуючись,
+ прямі, непрямі, випадкові, особливі або побічні збитки."
+ kor: "어떠한 경우에도 FastFlix의 개발자 또는 기여자는 직접적, 간접적, 부수적, 특별 또는 결과적 손해를 포함하되 이에 국한되지 않는 본 소프트웨어 사용으로 인해 발생하는 어떠한 손해에 대해서도 책임을 지지 않습니다."
+ ron: "În niciun caz dezvoltatorii sau contribuitorii FastFlix nu vor fi răspunzători pentru niciun fel de daune rezultate din utilizarea acestui software, inclusiv, dar fără a se limita la daune directe,
+ indirecte, incidentale, speciale sau consecutive."
+Copy Dolby Vision:
+ eng: Copy Dolby Vision
+ deu: Dolby Vision kopieren
+ fra: Copie Dolby Vision
+ ita: Copia Dolby Vision
+ spa: Copia Dolby Vision
+ jpn: ドルビービジョンをコピーする
+ rus: Копия Dolby Vision
+ por: Copiar Dolby Vision
+ swe: Kopia Dolby Vision
+ pol: Kopiowanie Dolby Vision
+ chs: 复制杜比视界
+ ukr: Копіювати Dolby Vision
+ kor: 돌비 비전 복사
+ ron: Copiere Dolby Vision
+Copy Dolby Vision RPU metadata from input file:
+ eng: Copy Dolby Vision RPU metadata from input file
+ deu: Dolby Vision RPU-Metadaten aus Eingabedatei kopieren
+ fra: Copier les métadonnées Dolby Vision RPU du fichier d'entrée
+ ita: Copia dei metadati Dolby Vision RPU dal file di ingresso
+ spa: Copiar metadatos Dolby Vision RPU del archivo de entrada
+ jpn: 入力ファイルからDolby Vision RPUメタデータをコピーする
+ rus: Копирование метаданных Dolby Vision RPU из входного файла
+ por: Copiar metadados Dolby Vision RPU do ficheiro de entrada
+ swe: Kopiera Dolby Vision RPU-metadata från inmatningsfilen
+ pol: Kopiowanie metadanych Dolby Vision RPU z pliku wejściowego
+ chs: 从输入文件复制杜比视界 RPU 元数据
+ ukr: Скопіюйте метадані Dolby Vision RPU з вхідного файлу
+ kor: 입력 파일에서 Dolby Vision RPU 메타데이터 복사하기
+ ron: Copierea metadatelor Dolby Vision RPU din fișierul de intrare
+All conversions complete:
+ eng: All conversions complete
+ deu: Alle Konvertierungen abgeschlossen
+ fra: Toutes les conversions sont terminées
+ ita: Tutte le conversioni sono state completate
+ spa: Todas las conversiones completadas
+ jpn: すべてのコンバージョンが完了
+ rus: Все преобразования завершены
+ por: Todas as conversões concluídas
+ swe: Alla konverteringar slutförda
+ pol: Wszystkie konwersje zakończone
+ chs: 完成所有转换
+ ukr: Усі перетворення завершено
+ kor: 모든 전환 완료
+ ron: Toate conversiile sunt finalizate
+Encoding:
+ eng: Encoding
+ deu: Kodierung
+ fra: Encodage
+ ita: Codifica
+ spa: Codificación
+ jpn: エンコーディング
+ rus: Кодирование
+ por: Codificação
+ swe: Kodning
+ pol: Kodowanie
+ chs: 编码
+ ukr: Кодування
+ kor: 인코딩
+ ron: Codificare
+Complete:
+ eng: Complete
+ deu: Vollständig
+ fra: Compléter
+ ita: Completo
+ spa: Complete
+ jpn: 完全
+ rus: Полный
+ por: Completo
+ swe: Komplett
+ pol: Kompletny
+ chs: 完整
+ ukr: Завершено
+ kor: 완료
+ ron: Completați
+Ready:
+ eng: Ready
+ deu: Bereit
+ fra: Prêt
+ ita: Pronto
+ spa: Listo
+ jpn: 準備完了
+ rus: Готовые
+ por: Pronto
+ swe: Redo
+ pol: Gotowy
+ chs: 准备就绪
+ ukr: Готово.
+ kor: 준비
+ ron: Gata
+Visual Crop:
+ eng: Visual Crop
+ deu: Visueller Ausschnitt
+ fra: Recadrage visuel
+ ita: Ritaglio visivo
+ spa: Recorte visual
+ jpn: ビジュアル・クロップ
+ rus: Визуальное обрезание
+ por: Corte visual
+ swe: Visuell beskärning
+ pol: Visual Crop
+ chs: 视觉裁剪
+ ukr: Візуальне обрізання
+ kor: 시각적 자르기
+ ron: Crop vizual
+Preview:
+ eng: Preview
+ deu: Vorschau
+ fra: Avant-première
+ ita: Anteprima
+ spa: Vista previa
+ jpn: プレビュー
+ rus: Предварительный просмотр
+ por: Pré-visualização
+ swe: Förhandsgranskning
+ pol: Podgląd
+ chs: 预览
+ ukr: Попередній перегляд
+ kor: 미리 보기
+ ron: Previzualizare
+Set Start Time:
+ eng: Set Start Time
+ deu: Startzeit einstellen
+ fra: Définir l'heure de début
+ ita: Impostare l'ora di inizio
+ spa: Hora de inicio
+ jpn: 開始時間の設定
+ rus: Установить время начала
+ por: Definir hora de início
+ swe: Ställ in starttid
+ pol: Ustaw czas rozpoczęcia
+ chs: 设置开始时间
+ ukr: Встановити час запуску
+ kor: 시작 시간 설정
+ ron: Setați ora de începere
+Set End Time:
+ eng: Set End Time
+ deu: Endzeit einstellen
+ fra: Définir l'heure de fin
+ ita: Impostare l'ora di fine
+ spa: Fijar hora de finalización
+ jpn: 終了時間の設定
+ rus: Установить время окончания
+ por: Definir hora de fim
+ swe: Ställ in sluttid
+ pol: Ustaw czas zakończenia
+ chs: 设置结束时间
+ ukr: Встановити час завершення
+ kor: 종료 시간 설정
+ ron: Setați ora de sfârșit
+Close:
+ eng: Close
+ deu: Schließen Sie
+ fra: Fermer
+ ita: Chiudere
+ spa: Cerrar
+ jpn: 閉じる
+ rus: Закрыть
+ por: Fechar
+ swe: Nära
+ pol: Zamknij
+ chs: 关闭
+ ukr: Закрити
+ kor: 닫기
+ ron: Închidere
+Data & Attachments:
+ eng: Data & Attachments
+ deu: Daten & Anhänge
+ fra: Données et pièces jointes
+ ita: Dati e allegati
+ spa: Datos y anexos
+ jpn: データと添付ファイル
+ rus: Данные и вложения
+ por: Dados e anexos
+ swe: Data och bilagor
+ pol: Dane i załączniki
+ chs: 数据和附件
+ ukr: Дані та додатки
+ kor: 데이터 및 첨부 파일
+ ron: Date și anexe
+Data:
+ eng: Data
+ deu: Daten
+ fra: Données
+ ita: Dati
+ spa: Datos
+ jpn: データ
+ rus: Данные
+ por: Dados
+ swe: Uppgifter
+ pol: Dane
+ chs: 数据
+ ukr: Дані
+ kor: 데이터
+ ron: Date
+Attachment:
+ eng: Attachment
+ deu: Anlage
+ fra: Pièce jointe
+ ita: Allegato
+ spa: Adjunto
+ jpn: アタッチメント
+ rus: Вложение
+ por: Anexo
+ swe: Bilaga
+ pol: Załącznik
+ chs: 附件
+ ukr: Вкладення
+ kor: 첨부 파일
+ ron: Atașament
+Attachment streams are not supported in this output format:
+ eng: Attachment streams are not supported in this output format
+ deu: Attachment Streams werden in diesem Ausgabeformat nicht unterstützt
+ fra: Les flux de pièces jointes ne sont pas pris en charge dans ce format de sortie.
+ ita: I flussi di allegati non sono supportati in questo formato di output.
+ spa: Este formato de salida no admite flujos adjuntos.
+ jpn: この出力形式では、添付ストリームはサポートされていません。
+ rus: Потоки вложений не поддерживаются в этом формате вывода
+ por: Os fluxos de anexos não são suportados neste formato de saída
+ swe: Attachment-strömmar stöds inte i detta utdataformat
+ pol: Strumienie załączników nie są obsługiwane w tym formacie wyjściowym
+ chs: 此输出格式不支持附件流
+ ukr: Потоки вкладених файлів не підтримуються у цьому форматі виводу
+ kor: 첨부 파일 스트림은 이 출력 형식에서 지원되지 않습니다.
+ ron: Fluxurile atașate nu sunt acceptate în acest format de ieșire
+Auto-detect external subtitle files:
+ eng: Auto-detect external subtitle files
+ deu: Automatische Erkennung von externen Untertiteldateien
+ fra: Détection automatique des fichiers de sous-titres externes
+ ita: Rilevamento automatico dei file di sottotitoli esterni
+ spa: Detección automática de archivos de subtítulos externos
+ jpn: 外部字幕ファイルの自動検出
+ rus: Автоматическое определение внешних файлов субтитров
+ por: Deteção automática de ficheiros de legendas externos
+ swe: Automatisk detektering av externa undertextfiler
+ pol: Automatyczne wykrywanie zewnętrznych plików napisów
+ chs: 自动检测外部字幕文件
+ ukr: Автоматичне визначення зовнішніх файлів субтитрів
+ kor: 외부 자막 파일 자동 감지
+ ron: Detectarea automată a fișierelor externe de subtitrare
+Autocrop tried to crop too much:
+ eng: Autocrop tried to crop too much
+ deu: Autocrop hat versucht, zu viel zu beschneiden
+ fra: Autocrop a essayé de trop recadrer
+ ita: L'autocrop ha cercato di ritagliare troppo
+ spa: Autocrop intentó recortar demasiado
+ jpn: オートクロップが過剰にトリミングしようとした
+ rus: Автокроп пытался обрезать слишком много
+ por: O corte automático tentou cortar demasiado
+ swe: Autocrop försökte skörda för mycket
+ pol: Autocrop próbował przyciąć za dużo
+ chs: 自动裁剪试图裁剪太多
+ ukr: Автопосів намагався засіяти занадто багато
+ kor: 자동 자르기가 너무 많이 자르려고 했습니다.
+ ron: Autocrop a încercat să recolteze prea mult
+Bad image:
+ eng: Bad image
+ deu: Schlechtes Image
+ fra: Mauvaise image
+ ita: Immagine negativa
+ spa: Mala imagen
+ jpn: 悪いイメージ
+ rus: Плохое изображение
+ por: Má imagem
+ swe: Dålig image
+ pol: Zły wizerunek
+ chs: 形象不佳
+ ukr: Погане зображення
+ kor: 나쁜 이미지
+ ron: Imagine proastă
+Cannot reload encoders while encoding is in progress:
+ eng: Cannot reload encoders while encoding is in progress
+ deu: Kodierer können nicht neu geladen werden, während die Kodierung läuft
+ fra: Impossible de recharger les encodeurs lorsque l'encodage est en cours
+ ita: Impossibile ricaricare i codificatori mentre è in corso la codifica
+ spa: No se pueden recargar los codificadores mientras la codificación está en curso.
+ jpn: エンコード中にエンコーダをリロードできない
+ rus: Невозможно перезагрузить кодировщики во время выполнения кодирования
+ por: Não é possível recarregar os codificadores enquanto a codificação está a decorrer
+ swe: Kan inte ladda om kodare medan kodning pågår
+ pol: Nie można przeładować koderów, gdy kodowanie jest w toku
+ chs: 编码过程中无法重新加载编码器
+ ukr: Неможливо перезавантажити енкодери під час кодування
+ kor: 인코딩이 진행되는 동안 인코더를 다시 로드할 수 없습니다.
+ ron: Nu se pot reîncărca codificatoarele în timp ce codificarea este în curs
+Command:
+ eng: Command
+ deu: Befehl
+ fra: Commandement
+ ita: Comando
+ spa: Comando
+ jpn: コマンド
+ rus: Команда
+ por: Comando
+ swe: Kommando
+ pol: Polecenie
+ chs: 指挥
+ ukr: Командир.
+ kor: 명령
+ ron: Comanda
+Could not connect to GitHub to check for newer versions.:
+ eng: Could not connect to GitHub to check for newer versions.
+ deu: Es konnte keine Verbindung zu GitHub hergestellt werden, um nach neueren Versionen zu suchen.
+ fra: Impossible de se connecter à GitHub pour vérifier les nouvelles versions.
+ ita: Impossibile connettersi a GitHub per verificare la presenza di nuove versioni.
+ spa: No se ha podido conectar a GitHub para comprobar si hay versiones más recientes.
+ jpn: 新しいバージョンをチェックするためにGitHubに接続できませんでした。
+ rus: Не удалось подключиться к GitHub, чтобы проверить наличие новых версий.
+ por: Não foi possível conectar-se ao GitHub para verificar se há versões mais recentes.
+ swe: Det gick inte att ansluta till GitHub för att kontrollera om det finns nyare versioner.
+ pol: Nie można połączyć się z serwisem GitHub, aby sprawdzić dostępność nowszych wersji.
+ chs: 无法连接到 GitHub 以检查更新版本。
+ ukr: Не вдалося підключитися до GitHub, щоб перевірити наявність нових версій.
+ kor: 최신 버전을 확인하기 위해 GitHub에 연결할 수 없습니다.
+ ron: Nu s-a putut conecta la GitHub pentru a verifica dacă există versiuni mai noi.
+Could not connect to github to check for newer versions.:
+ eng: Could not connect to github to check for newer versions.
+ deu: Konnte keine Verbindung zu Github herstellen, um nach neueren Versionen zu suchen.
+ fra: Impossible de se connecter à github pour vérifier les nouvelles versions.
+ ita: Impossibile connettersi a github per verificare la presenza di nuove versioni.
+ spa: No se ha podido conectar a github para comprobar si hay versiones más recientes.
+ jpn: 新しいバージョンをチェックするためにgithubに接続できませんでした。
+ rus: Не удалось подключиться к github, чтобы проверить наличие новых версий.
+ por: Não foi possível conectar-se ao github para verificar se há versões mais recentes.
+ swe: Det gick inte att ansluta till github för att kontrollera om det finns nyare versioner.
+ pol: Nie można połączyć się z serwisem github, aby sprawdzić dostępność nowszych wersji.
+ chs: 无法连接到 github 以检查更新版本。
+ ukr: Не вдалося підключитися до github, щоб перевірити наявність нових версій.
+ kor: 최신 버전을 확인하기 위해 깃허브에 연결할 수 없습니다.
+ ron: Nu s-a putut conecta la github pentru a verifica dacă există versiuni mai noi.
+'Could not delete previous temp extract directory: ':
+ eng: 'Could not delete previous temp extract directory: '
+ deu: 'Das vorherige temporäre Extraktverzeichnis konnte nicht gelöscht werden:'
+ fra: "Impossible de supprimer le répertoire d'extraction temporaire précédent :"
+ ita: 'Impossibile eliminare la precedente directory di estrazione temporanea:'
+ spa: 'No se ha podido eliminar el directorio de extracción temporal anterior:'
+ jpn: 以前の一時解凍ディレクトリを削除できませんでした:
+ rus: 'Не удалось удалить предыдущий каталог временных распаковок:'
+ por: 'Não foi possível eliminar o anterior diretório de extração temporário:'
+ swe: 'Det gick inte att ta bort föregående temp extract-katalog:'
+ pol: 'Nie można usunąć poprzedniego katalogu temp extract:'
+ chs: 无法删除先前的临时解压缩目录:
+ ukr: 'Не вдалося видалити попередній каталог тимчасового розпакування:'
+ kor: '이전 임시 추출 디렉터리를 삭제할 수 없습니다:'
+ ron: 'Nu s-a putut șterge directorul anterior de extragere temporară:'
+Could not download HDR10+ tool:
+ eng: Could not download HDR10+ tool
+ deu: HDR10+ Tool konnte nicht heruntergeladen werden
+ fra: Impossible de télécharger l'outil HDR10
+ ita: Impossibile scaricare lo strumento HDR10+
+ spa: No se puede descargar la herramienta HDR10
+ jpn: HDR10+ツールをダウンロードできませんでした
+ rus: Не удалось загрузить инструмент HDR10+
+ por: Não foi possível descarregar a ferramenta HDR10
+ swe: Det gick inte att ladda ner HDR10+-verktyget
+ pol: Nie można pobrać narzędzia HDR10+
+ chs: 无法下载 HDR10+ 工具
+ ukr: Не вдалося завантажити інструмент HDR10+
+ kor: HDR10+ 도구를 다운로드할 수 없습니다.
+ ron: Nu s-a putut descărca instrumentul HDR10+
+Could not download the newest FFmpeg:
+ eng: Could not download the newest FFmpeg
+ deu: Konnte das neueste FFmpeg nicht herunterladen
+ fra: Impossible de télécharger la dernière version de FFmpeg
+ ita: Impossibile scaricare il nuovo FFmpeg
+ spa: No se ha podido descargar la última versión de FFmpeg
+ jpn: 最新のFFmpegをダウンロードできませんでした。
+ rus: Не удалось загрузить новейший FFmpeg
+ por: Não foi possível descarregar o FFmpeg mais recente
+ swe: Det gick inte att ladda ner den senaste FFmpeg
+ pol: Nie można pobrać najnowszej wersji FFmpeg
+ chs: 无法下载最新的 FFmpeg
+ ukr: Не вдалося завантажити найновіший FFmpeg
+ kor: 최신 FFmpeg를 다운로드할 수 없습니다.
+ ron: Nu s-a putut descărca cel mai nou FFmpeg
+Could not extract FFmpeg files from:
+ eng: Could not extract FFmpeg files from
+ deu: FFmpeg-Dateien konnten nicht extrahiert werden von
+ fra: Impossible d'extraire les fichiers FFmpeg de
+ ita: Impossibile estrarre i file FFmpeg da
+ spa: No se pueden extraer archivos FFmpeg de
+ jpn: からFFmpegファイルを抽出できませんでした。
+ rus: Не удалось извлечь файлы FFmpeg из
+ por: Não foi possível extrair ficheiros FFmpeg de
+ swe: Kunde inte extrahera FFmpeg-filer från
+ pol: Nie udało się wyodrębnić plików FFmpeg z
+ chs: 无法从以下文件中提取 FFmpeg 文件
+ ukr: Не вдалося розпакувати файли FFmpeg з
+ kor: 다음에서 FFmpeg 파일을 추출할 수 없습니다.
+ ron: Nu s-a putut extrage fișierele FFmpeg din
+Could not extract hdr10plus_tool files from:
+ eng: Could not extract hdr10plus_tool files from
+ deu: Die hdr10plus_tool-Dateien konnten nicht extrahiert werden aus
+ fra: Impossible d'extraire les fichiers hdr10plus_tool de
+ ita: Impossibile estrarre i file hdr10plus_tool da
+ spa: No se han podido extraer los archivos hdr10plus_tool de
+ jpn: からhdr10plus_toolファイルを展開できませんでした。
+ rus: Не удалось извлечь файлы hdr10plus_tool из
+ por: Não foi possível extrair os ficheiros hdr10plus_tool de
+ swe: Kunde inte extrahera hdr10plus_tool-filer från
+ pol: Nie udało się wyodrębnić plików hdr10plus_tool z pliku
+ chs: 无法从以下文件中提取 hdr10plus_tool 文件
+ ukr: Не вдалося розпакувати файли hdr10plus_tool з
+ kor: 다음에서 hdr10plus_tool 파일을 추출할 수 없습니다.
+ ron: Nu s-a putut extrage fișierele hdr10plus_tool din
+Could not find any matching FFmpeg expected patterns, please check:
+ eng: Could not find any matching FFmpeg expected patterns, please check
+ deu: Es konnten keine passenden FFmpeg-Muster gefunden werden, bitte überprüfen Sie
+ fra: Il n'a pas été possible de trouver des modèles correspondants aux attentes de FFmpeg, veuillez vérifier.
+ ita: Non è stato possibile trovare alcuno schema FFmpeg corrispondente, controllare
+ spa: No se ha encontrado ningún patrón esperado de FFmpeg que coincida, por favor, compruébelo
+ jpn: 一致するFFmpeg期待パターンが見つかりませんでした。
+ rus: Не удалось найти ни одного подходящего шаблона, ожидаемого FFmpeg, пожалуйста, проверьте
+ por: Não foi possível encontrar nenhum padrão esperado do FFmpeg, por favor verifique
+ swe: Kunde inte hitta några matchande FFmpeg förväntade mönster, vänligen kontrollera
+ pol: Nie znaleziono żadnych pasujących wzorców oczekiwanych przez FFmpeg, sprawdź
+ chs: 找不到任何匹配的 FFmpeg 预期模式,请检查
+ ukr: Не вдалося знайти відповідні шаблони FFmpeg, будь ласка, перевірте
+ kor: 일치하는 FFmpeg 예상 패턴을 찾을 수 없습니다.
+ ron: Nu a putut fi găsit niciun model FFmpeg așteptat, vă rugăm să verificați
+Could not find any matching expected patterns, please check:
+ eng: Could not find any matching expected patterns, please check
+ deu: Es konnten keine passenden erwarteten Muster gefunden werden, bitte überprüfen Sie
+ fra: Il n'a pas été possible de trouver les motifs attendus correspondants, veuillez vérifier.
+ ita: Non è stato possibile trovare alcun modello corrispondente a quello previsto, verificare
+ spa: No se ha encontrado ningún patrón que coincida con los esperados, por favor, compruébelo
+ jpn: 一致するパターンが見つかりませんでした。
+ rus: Не удалось найти ни одного подходящего шаблона, пожалуйста, проверьте
+ por: Não foi possível encontrar nenhum padrão esperado correspondente, verifique
+ swe: Kunde inte hitta några matchande förväntade mönster, vänligen kontrollera
+ pol: Nie znaleziono pasujących oczekiwanych wzorców, sprawdź
+ chs: 找不到匹配的预期模式,请检查
+ ukr: Не вдалося знайти відповідні шаблони, будь ласка, перевірте
+ kor: 예상 패턴과 일치하는 패턴을 찾을 수 없습니다.
+ ron: Nu a putut fi găsit niciun model așteptat corespunzător, vă rugăm să verificați
+Could not find language for:
+ eng: Could not find language for
+ deu: Konnte keine Sprache finden für
+ fra: Impossible de trouver la langue pour
+ ita: Impossibile trovare la lingua per
+ spa: No se ha podido encontrar el idioma para
+ jpn: の言語が見つからなかった。
+ rus: Не удалось найти язык для
+ por: Não foi possível encontrar a língua para
+ swe: Kunde inte hitta språk för
+ pol: Nie można znaleźć języka dla
+ chs: 找不到
+ ukr: Не могли знайти мову для
+ kor: 언어를 찾을 수 없습니다.
+ ron: Nu s-a putut găsi limba pentru
+Could not find the executable in the extracted files:
+ eng: Could not find the executable in the extracted files
+ deu: Die ausführbare Datei konnte in den extrahierten Dateien nicht gefunden werden
+ fra: Impossible de trouver l'exécutable dans les fichiers extraits
+ ita: Impossibile trovare l'eseguibile nei file estratti
+ spa: No se ha podido encontrar el ejecutable en los archivos extraídos
+ jpn: 解凍したファイルの中に実行ファイルが見つかりませんでした。
+ rus: Не удалось найти исполняемый файл в распакованных файлах
+ por: Não foi possível encontrar o executável nos ficheiros extraídos
+ swe: Kunde inte hitta den körbara filen i de extraherade filerna
+ pol: Nie można znaleźć pliku wykonywalnego w wyodrębnionych plikach
+ chs: 无法在提取的文件中找到可执行文件
+ ukr: Не вдалося знайти виконуваний файл у розпакованих файлах
+ kor: 압축을 푼 파일에서 실행 파일을 찾을 수 없습니다.
+ ron: Nu s-a putut găsi executabilul în fișierele extrase
+Could not locate the downloaded HDR10+ tool:
+ eng: Could not locate the downloaded HDR10+ tool
+ deu: Das heruntergeladene HDR10+-Tool konnte nicht gefunden werden.
+ fra: Impossible de localiser l'outil HDR10+ téléchargé
+ ita: Impossibile individuare lo strumento HDR10+ scaricato
+ spa: No se ha podido localizar la herramienta HDR10+ descargada
+ jpn: ダウンロードしたHDR10+ツールが見つかりません。
+ rus: Не удалось найти загруженный инструмент HDR10+
+ por: Não foi possível localizar a ferramenta HDR10+ descarregada
+ swe: Det gick inte att hitta det nedladdade HDR10+-verktyget
+ pol: Nie można zlokalizować pobranego narzędzia HDR10+
+ chs: 无法找到下载的 HDR10+ 工具
+ ukr: Не вдалося знайти завантажений інструмент HDR10+
+ kor: 다운로드한 HDR10+ 도구를 찾을 수 없습니다.
+ ron: Nu s-a putut localiza instrumentul HDR10+ descărcat
+Could not locate the downloaded files at:
+ eng: Could not locate the downloaded files at
+ deu: Die heruntergeladenen Dateien konnten nicht gefunden werden unter
+ fra: Impossible de localiser les fichiers téléchargés à l'adresse
+ ita: Impossibile individuare i file scaricati in
+ spa: No se han podido localizar los archivos descargados en
+ jpn: にダウンロードしたファイルが見つかりません。
+ rus: Не удалось обнаружить загруженные файлы по адресу
+ por: Não foi possível localizar os ficheiros descarregados em
+ swe: Det gick inte att hitta de nedladdade filerna på
+ pol: Nie można zlokalizować pobranych plików pod adresem
+ chs: 无法在
+ ukr: Не вдалося знайти завантажені файли за адресою
+ kor: 다운로드한 파일을 다음 위치에서 찾을 수 없습니다.
+ ron: Nu am putut localiza fișierele descărcate la adresa
+Could not update Rigaya's encoders:
+ eng: Could not update Rigaya's encoders
+ deu: Rigayas Geber konnten nicht aktualisiert werden
+ fra: Impossible de mettre à jour les encodeurs de Rigaya
+ ita: Impossibile aggiornare gli encoder di Rigaya
+ spa: No se pudieron actualizar los codificadores de Rigaya
+ jpn: リガヤのエンコーダーを更新できなかった
+ rus: Не удалось обновить кодеры Rigaya
+ por: Não foi possível atualizar os codificadores de Rigaya
+ swe: Kunde inte uppdatera Rigayas kodare
+ pol: Nie można zaktualizować koderów Rigaya
+ chs: 无法更新 Rigaya 编码器
+ ukr: Не вдалося оновити енкодери Rigaya
+ kor: 리가야의 인코더를 업데이트할 수 없습니다.
+ ron: Nu s-a putut actualiza codificatoarele Rigaya
+Custom Filters:
+ eng: Custom Filters
+ deu: Benutzerdefinierte Filter
+ fra: Filtres personnalisés
+ ita: Filtri personalizzati
+ spa: Filtros personalizados
+ jpn: カスタムフィルター
+ rus: Пользовательские фильтры
+ por: Filtros personalizados
+ swe: Anpassade filter
+ pol: Filtry niestandardowe
+ chs: 自定义过滤器
+ ukr: Користувацькі фільтри
+ kor: 사용자 지정 필터
+ ron: Filtre personalizate
+Data streams are not supported in this output format:
+ eng: Data streams are not supported in this output format
+ deu: Datenströme werden in diesem Ausgabeformat nicht unterstützt
+ fra: Les flux de données ne sont pas pris en charge dans ce format de sortie
+ ita: I flussi di dati non sono supportati in questo formato di uscita.
+ spa: No se admiten flujos de datos en este formato de salida
+ jpn: この出力フォーマットでは、データストリームはサポートされていません。
+ rus: Потоки данных не поддерживаются в этом формате вывода
+ por: Os fluxos de dados não são suportados neste formato de saída
+ swe: Dataströmmar stöds inte i detta utdataformat
+ pol: Strumienie danych nie są obsługiwane w tym formacie wyjściowym
+ chs: 此输出格式不支持数据流
+ ukr: Потоки даних не підтримуються у цьому форматі виводу
+ kor: 이 출력 형식에서는 데이터 스트림이 지원되지 않습니다.
+ ron: Fluxurile de date nu sunt acceptate în acest format de ieșire
+Downloading HDR10+ Tool:
+ eng: Downloading HDR10+ Tool
+ deu: Herunterladen des HDR10+ Tools
+ fra: Téléchargement de l'outil HDR10
+ ita: Scaricare lo strumento HDR10+
+ spa: Descarga de la herramienta HDR10
+ jpn: HDR10+ツールのダウンロード
+ rus: Загрузка инструмента HDR10+
+ por: Descarregar a ferramenta HDR10
+ swe: Ladda ner HDR10+-verktyget
+ pol: Pobieranie narzędzia HDR10+
+ chs: 下载 HDR10+ 工具
+ ukr: Завантаження HDR10+ Tool
+ kor: HDR10+ 도구 다운로드
+ ron: Descărcarea instrumentului HDR10+
+Encoding cancelled:
+ eng: Encoding cancelled
+ deu: Kodierung abgebrochen
+ fra: Encodage annulé
+ ita: Codifica annullata
+ spa: Codificación anulada
+ jpn: エンコード中止
+ rus: Кодирование отменено
+ por: Codificação anulada
+ swe: Kodning avbruten
+ pol: Kodowanie anulowane
+ chs: 编码取消
+ ukr: Кодування скасовано
+ kor: 인코딩 취소됨
+ ron: Codare anulată
+Encoding error:
+ eng: Encoding error
+ deu: Kodierungsfehler
+ fra: Erreur d'encodage
+ ita: Errore di codifica
+ spa: Error de codificación
+ jpn: エンコードエラー
+ rus: Ошибка кодирования
+ por: Erro de codificação
+ swe: Kodningsfel
+ pol: Błąd kodowania
+ chs: 编码错误
+ ukr: Помилка кодування
+ kor: 인코딩 오류
+ ron: Eroare de codare
+Error while moving files in:
+ eng: Error while moving files in
+ deu: Fehler beim Verschieben von Dateien in
+ fra: Erreur lors du déplacement de fichiers dans
+ ita: Errore durante lo spostamento dei file in
+ spa: Error al mover archivos en
+ jpn: のファイル移動中にエラーが発生しました。
+ rus: Ошибка при перемещении файлов в
+ por: Erro ao mover ficheiros em
+ swe: Fel vid flyttning av filer i
+ pol: Błąd podczas przenoszenia plików w
+ chs: 将文件移入
+ ukr: Помилка під час переміщення файлів
+ kor: 파일 이동 중 오류 발생
+ ron: Eroare în timpul mutării fișierelor în
+FFmpeg downloaded successfully:
+ eng: FFmpeg downloaded successfully
+ deu: FFmpeg erfolgreich heruntergeladen
+ fra: FFmpeg téléchargé avec succès
+ ita: FFmpeg è stato scaricato con successo
+ spa: FFmpeg se ha descargado correctamente
+ jpn: FFmpegのダウンロードに成功
+ rus: FFmpeg успешно загружен
+ por: FFmpeg descarregado com sucesso
+ swe: FFmpeg nedladdad framgångsrikt
+ pol: FFmpeg pobrany pomyślnie
+ chs: FFmpeg 下载成功
+ ukr: FFmpeg успішно завантажено
+ kor: FFmpeg 다운로드 성공
+ ron: FFmpeg descărcat cu succes
+FFmpeg not found:
+ eng: FFmpeg not found
+ deu: FFmpeg nicht gefunden
+ fra: FFmpeg introuvable
+ ita: FFmpeg non trovato
+ spa: FFmpeg no encontrado
+ jpn: FFmpegが見つからない
+ rus: FFmpeg не найден
+ por: FFmpeg não encontrado
+ swe: FFmpeg hittades inte
+ pol: Nie znaleziono FFmpeg
+ chs: 未找到 FFmpeg
+ ukr: FFmpeg не знайдено
+ kor: FFmpeg를 찾을 수 없음
+ ron: FFmpeg nu a fost găsit
+FFmpeg was not properly downloaded as the file size is too small:
+ eng: FFmpeg was not properly downloaded as the file size is too small
+ deu: FFmpeg wurde nicht richtig heruntergeladen, da die Datei zu klein ist
+ fra: FFmpeg n'a pas été correctement téléchargé car la taille du fichier est trop petite.
+ ita: FFmpeg non è stato scaricato correttamente perché le dimensioni del file sono troppo ridotte.
+ spa: FFmpeg no se ha descargado correctamente porque el tamaño del archivo es demasiado pequeño.
+ jpn: ファイルサイズが小さすぎるため、FFmpegが正しくダウンロードされませんでした。
+ rus: FFmpeg не удалось загрузить должным образом, так как размер файла слишком мал
+ por: O FFmpeg não foi descarregado corretamente porque o tamanho do ficheiro é demasiado pequeno
+ swe: FFmpeg laddades inte ner ordentligt eftersom filstorleken är för liten
+ pol: FFmpeg nie został poprawnie pobrany, ponieważ rozmiar pliku jest zbyt mały
+ chs: FFmpeg 无法正确下载,因为文件太小
+ ukr: FFmpeg не вдалося завантажити належним чином, оскільки розмір файлу замалий
+ kor: 파일 크기가 너무 작아서 FFmpeg가 제대로 다운로드되지 않았습니다.
+ ron: FFmpeg nu a fost descărcat corect deoarece dimensiunea fișierului este prea mică
+Failed to reload encoders:
+ eng: Failed to reload encoders
+ deu: Nachladen der Encoder fehlgeschlagen
+ fra: Échec du rechargement des codeurs
+ ita: Impossibile ricaricare gli encoder
+ spa: Fallo al recargar codificadores
+ jpn: エンコーダのリロードに失敗
+ rus: Не удалось перезагрузить энкодеры
+ por: Falha ao recarregar os codificadores
+ swe: Misslyckades med att ladda om pulsgivarna
+ pol: Nie udało się przeładować enkoderów
+ chs: 重新加载编码器失败
+ ukr: Не вдалося перезавантажити енкодери
+ kor: 인코더를 다시 로드하지 못함
+ ron: A eșuat reîncărcarea codificatoarelor
+First Pass:
+ eng: First Pass
+ deu: Erster Durchgang
+ fra: Premier passage
+ ita: Primo passaggio
+ spa: Primer pase
+ jpn: ファーストパス
+ rus: Первый проход
+ por: Primeira passagem
+ swe: Första passet
+ pol: Pierwsze przejście
+ chs: 第一次通过
+ ukr: Перший прохід
+ kor: 첫 번째 패스
+ ron: Prima trecere
+Font:
+ eng: Font
+ deu: Schriftart
+ fra: Police
+ ita: Carattere
+ spa: Fuente
+ jpn: フォント
+ rus: Шрифт
+ por: Tipo de letra
+ swe: Typsnitt
+ pol: Czcionka
+ chs: 字体
+ ukr: Шрифт
+ kor: 글꼴
+ ron: Font
+HDR10+ detected but requires FFmpeg 8.0+ for AV1 passthrough:
+ eng: HDR10+ detected but requires FFmpeg 8.0+ for AV1 passthrough
+ deu: HDR10+ wird erkannt, erfordert aber FFmpeg 8.0+ für AV1-Passthrough
+ fra: HDR10+ détecté mais nécessite FFmpeg 8.0+ pour le passthrough AV1
+ ita: HDR10+ rilevato ma richiede FFmpeg 8.0+ per il passthrough AV1
+ spa: Se detecta HDR10+ pero requiere FFmpeg 8.0+ para el paso a AV1
+ jpn: HDR10+は検出されたが、AV1パススルーにはFFmpeg 8.0+が必要。
+ rus: HDR10+ обнаружен, но требуется FFmpeg 8.0+ для AV1 passthrough
+ por: HDR10+ detectado mas requer FFmpeg 8.0+ para passagem AV1
+ swe: HDR10+ detekteras men kräver FFmpeg 8.0+ för AV1-passthrough
+ pol: HDR10+ wykryty, ale wymaga FFmpeg 8.0+ dla AV1 passthrough
+ chs: 检测到 HDR10+,但需要 FFmpeg 8.0+ 实现 AV1 直通
+ ukr: Визначено HDR10+, але для проходження AV1 потрібен FFmpeg 8.0+
+ kor: HDR10+가 감지되었지만 AV1 패스스루를 위해서는 FFmpeg 8.0+가 필요합니다.
+ ron: HDR10+ detectat, dar necesită FFmpeg 8.0+ pentru AV1 passthrough
+HDR10+ tool has been downloaded to:
+ eng: HDR10+ tool has been downloaded to
+ deu: Das Tool HDR10+ wurde heruntergeladen auf
+ fra: L'outil HDR10+ a été téléchargé sur
+ ita: Lo strumento HDR10+ è stato scaricato su
+ spa: La herramienta HDR10+ se ha descargado en
+ jpn: HDR10+ツールがダウンロードされました。
+ rus: Инструмент HDR10+ был загружен в
+ por: A ferramenta HDR10+ foi descarregada para
+ swe: HDR10+-verktyget har laddats ner till
+ pol: Narzędzie HDR10+ zostało pobrane do
+ chs: HDR10+ 工具已下载到
+ ukr: Інструмент HDR10+ було завантажено на
+ kor: HDR10+ 도구가 다운로드되었습니다.
+ ron: Instrumentul HDR10+ a fost descărcat la
+? "HDR10+ tool not found. Do you want FastFlix to automatically download it?\n\nThis tool is used for extracting and injecting HDR10+ dynamic metadata during encoding."
+: eng: "HDR10+ tool not found. Do you want FastFlix to automatically download it?\n\nThis tool is used for extracting and injecting HDR10+ dynamic metadata during encoding."
+ deu: "HDR10+ Tool nicht gefunden. Möchten Sie, dass FastFlix es automatisch herunterlädt?\n\nDieses Tool wird zum Extrahieren und Einfügen von dynamischen HDR10+-Metadaten während der Codierung verwendet."
+ fra: "L'outil HDR10+ n'a pas été trouvé. Voulez-vous que FastFlix le télécharge automatiquement ?\n\nCet outil est utilisé pour extraire et injecter des métadonnées dynamiques HDR10+ pendant l'encodage."
+ ita: "Strumento HDR10+ non trovato. Volete che FastFlix lo scarichi automaticamente?\n\nQuesto strumento viene utilizzato per estrarre e inserire i metadati dinamici HDR10+ durante la codifica."
+ spa: "No se ha encontrado la herramienta HDR10+. ¿Desea que FastFlix la descargue automáticamente?\n\nEsta herramienta se utiliza para extraer e inyectar metadatos dinámicos HDR10+ durante la codificación."
+ jpn: "HDR10+ツールが見つかりません。FastFlixに自動的にダウンロードさせますか?\n\nこのツールは、エンコード中にHDR10+ダイナミックメタデータを抽出・注入するために使用します。"
+ rus: "Инструмент HDR10+ не найден. Хотите, чтобы FastFlix автоматически загрузил его?\n\nЭтот инструмент используется для извлечения и вставки динамических метаданных HDR10+ во время кодирования."
+ por: "Ferramenta HDR10+ não encontrada. Quer que o FastFlix a descarregue automaticamente?\n\nEsta ferramenta é usada para extrair e injetar metadados dinâmicos HDR10+ durante a codificação."
+ swe: "HDR10+ verktyg hittades inte. Vill du att FastFlix automatiskt ska ladda ner det?\n\nDetta verktyg används för att extrahera och injicera HDR10+ dynamiska metadata under kodning."
+ pol: "Nie znaleziono narzędzia HDR10+. Czy chcesz, aby FastFlix automatycznie je pobrał?\n\nNarzędzie to służy do wyodrębniania i wstawiania dynamicznych metadanych HDR10+ podczas kodowania."
+ chs: "未找到 HDR10+ 工具。您想让 FastFlix 自动下载吗?\n\n该工具用于在编码过程中提取和注入 HDR10+ 动态元数据。"
+ ukr: "Інструмент HDR10+ не знайдено. Ви хочете, щоб FastFlix автоматично завантажив його?\n\nЦей інструмент використовується для вилучення та введення динамічних метаданих HDR10+ під час кодування."
+ kor: "HDR10+ 도구를 찾을 수 없습니다. FastFlix가 자동으로 다운로드하도록 하시겠습니까?\n\n이 도구는 인코딩 중에 HDR10+ 동적 메타데이터를 추출하고 삽입하는 데 사용됩니다."
+ ron: "Ferramenta HDR10+ não encontrada. Quer que o FastFlix a descarregue automaticamente?\n\nEsta ferramenta é usada para extrair e injetar metadados dinâmicos HDR10+ durante a codificação."
+Height must be smaller than video height:
+ eng: Height must be smaller than video height
+ deu: Höhe muss kleiner als die Videohöhe sein
+ fra: La hauteur doit être inférieure à celle de la vidéo
+ ita: L'altezza deve essere inferiore a quella del video
+ spa: La altura debe ser inferior a la altura del vídeo
+ jpn: 高さはビデオの高さより小さくなければならない
+ rus: Высота должна быть меньше высоты видео
+ por: A altura deve ser inferior à altura do vídeo
+ swe: Höjden måste vara mindre än videohöjden
+ pol: Wysokość musi być mniejsza niż wysokość wideo
+ chs: 高度必须小于视频高度
+ ukr: Висота повинна бути меншою за висоту відео
+ kor: 높이는 동영상 높이보다 작아야 합니다.
+ ron: Înălțimea trebuie să fie mai mică decât înălțimea videoclipului
+Left must be positive number:
+ eng: Left must be positive number
+ deu: Links muss eine positive Zahl sein
+ fra: La gauche doit être un nombre positif
+ ita: La sinistra deve essere un numero positivo
+ spa: Izquierda debe ser un número positivo
+ jpn: 左は正の数でなければならない
+ rus: Слева должно быть положительное число
+ por: A esquerda deve ser um número positivo
+ swe: Vänster måste vara ett positivt tal
+ pol: Left musi być liczbą dodatnią
+ chs: 左数必须是正数
+ ukr: Зліва повинно бути додатнім числом
+ kor: 왼쪽은 양수여야 합니다.
+ ron: Stânga trebuie să fie un număr pozitiv
+Missing dependencies for PGS OCR:
+ eng: Missing dependencies for PGS OCR
+ deu: Fehlende Abhängigkeiten für PGS OCR
+ fra: Dépendances manquantes pour PGS OCR
+ ita: Dipendenze mancanti per PGS OCR
+ spa: Faltan dependencias para PGS OCR
+ jpn: PGS OCRの欠落している依存関係
+ rus: Отсутствующие зависимости для PGS OCR
+ por: Dependências em falta para o PGS OCR
+ swe: Saknade beroenden för PGS OCR
+ pol: Brakujące zależności dla PGS OCR
+ chs: PGS OCR 缺少依赖项
+ ukr: Відсутні залежності для PGS OCR
+ kor: PGS OCR에 대한 누락된 종속성
+ ron: Dependențe lipsă pentru PGS OCR
+'Missing dependencies: tesseract or pgsrip':
+ eng: 'Missing dependencies: tesseract or pgsrip'
+ deu: 'Fehlende Abhängigkeiten: tesseract oder pgsrip'
+ fra: 'Dépendances manquantes : tesseract ou pgsrip'
+ ita: 'Dipendenze mancanti: tesseract o pgsrip'
+ spa: 'Dependencias que faltan: tesseract o pgsrip'
+ jpn: '欠けている依存関係: tesseract または pgsrip'
+ rus: 'Отсутствующие зависимости: tesseract или pgsrip'
+ por: 'Dependências em falta: tesseract ou pgsrip'
+ swe: 'Beroenden som saknas: tesseract eller pgsrip'
+ pol: 'Brakujące zależności: tesseract lub pgsrip'
+ chs: 缺少依赖项:tesseract 或 pgsrip
+ ukr: 'Відсутні залежності: tesseract або pgsrip'
+ kor: '누락된 종속성: 테서랙트 또는 pgsrip'
+ ron: 'Dependențe lipsă: tesseract sau pgsrip'
+NVEnc was not properly downloaded as the file size is too small:
+ eng: NVEnc was not properly downloaded as the file size is too small
+ deu: NVEnc wurde nicht richtig heruntergeladen, da die Datei zu klein ist
+ fra: NVEnc n'a pas été correctement téléchargé car la taille du fichier est trop petite.
+ ita: NVEnc non è stato scaricato correttamente perché le dimensioni del file sono troppo piccole
+ spa: NVEnc no se ha descargado correctamente porque el tamaño del archivo es demasiado pequeño.
+ jpn: ファイルサイズが小さすぎるため、NVEncが正しくダウンロードされませんでした。
+ rus: NVEnc не удалось загрузить должным образом, так как размер файла слишком мал
+ por: O NVEnc não foi descarregado corretamente porque o tamanho do ficheiro é demasiado pequeno
+ swe: NVEnc laddades inte ner ordentligt eftersom filstorleken är för liten
+ pol: Plik NVEnc nie został pobrany poprawnie, ponieważ był zbyt mały
+ chs: 由于文件太小,无法正确下载 NVEnc
+ ukr: NVEnc не вдалося завантажити належним чином, оскільки розмір файлу занадто малий
+ kor: 파일 크기가 너무 작아서 NVEnc가 제대로 다운로드되지 않았습니다.
+ ron: NVEnc nu a fost descărcat corect deoarece dimensiunea fișierului este prea mică
+Not deinterlacing will result in banding after encoding.:
+ eng: Not deinterlacing will result in banding after encoding.
+ deu: Ohne Deinterlacing kommt es nach der Kodierung zu Banding.
+ fra: L'absence de désentrelacement entraînera un effet de bande après l'encodage.
+ ita: Se non si esegue il deinterlacciamento, dopo la codifica si verifica il banding.
+ spa: Si no se desentrelaza, se producirán bandas después de la codificación.
+ jpn: デインターレースをしないと、エンコード後にバンディングが発生する。
+ rus: Отсутствие деинтерлейсинга приведет к появлению полос после кодирования.
+ por: A ausência de desentrelaçamento resultará em bandas após a codificação.
+ swe: Om deinterlacing inte används uppstår bandning efter kodning.
+ pol: Brak usuwania przeplotu spowoduje powstawanie pasm po zakodowaniu.
+ chs: 不去掉隔行扫描会导致编码后出现带状。
+ ukr: Якщо не зняти чересстрочність, це призведе до появи смуг після кодування.
+ kor: 디인터레이싱을 하지 않으면 인코딩 후 밴딩이 발생합니다.
+ ron: Neinterlațarea va duce la banding după codare.
+OCR conversion failed, kept .sup file:
+ eng: OCR conversion failed, kept .sup file
+ deu: OCR-Konvertierung fehlgeschlagen, .sup-Datei beibehalten
+ fra: La conversion OCR a échoué, le fichier .sup a été conservé
+ ita: Conversione OCR fallita, mantenuto il file .sup
+ spa: Error de conversión OCR, archivo .sup conservado
+ jpn: OCR変換に失敗し、.supファイルを保持
+ rus: Преобразование OCR не удалось, сохранился файл .sup
+ por: A conversão de OCR falhou, manteve o ficheiro .sup
+ swe: OCR-konvertering misslyckades, behöll .sup-fil
+ pol: Konwersja OCR nie powiodła się, zachowano plik .sup
+ chs: OCR 转换失败,保留 .sup 文件
+ ukr: OCR-перетворення не вдалося, збережено файл .sup
+ kor: OCR 변환 실패, .sup 파일 유지
+ ron: Conversia OCR a eșuat, a păstrat fișierul .sup
+Output video path is same as source!:
+ eng: Output video path is same as source!
+ deu: Der Videoausgangspfad ist derselbe wie die Quelle!
+ fra: Le chemin vidéo de sortie est le même que celui de la source !
+ ita: Il percorso video di uscita è lo stesso della sorgente!
+ spa: La ruta de vídeo de salida es la misma que la de la fuente.
+ jpn: 出力ビデオパスはソースと同じです!
+ rus: Выходной видеотракт совпадает с исходным!
+ por: O caminho do vídeo de saída é o mesmo da fonte!
+ swe: Utgående videosökväg är samma som källan!
+ pol: Wyjściowa ścieżka wideo jest taka sama jak źródłowa!
+ chs: 输出视频路径与信号源相同!
+ ukr: Вихідний відеошлях такий самий, як і вихідний!
+ kor: 출력 비디오 경로가 소스와 동일합니다!
+ ron: Calea video de ieșire este aceeași ca și sursa!
+PGS OCR setup instructions:
+ eng: PGS OCR setup instructions
+ deu: PGS OCR-Einrichtungsanweisungen
+ fra: Instructions de configuration de l'OCR PGS
+ ita: Istruzioni per l'impostazione di PGS OCR
+ spa: Instrucciones de configuración de PGS OCR
+ jpn: PGS OCRセットアップ手順
+ rus: Инструкции по настройке PGS OCR
+ por: Instruções de configuração do PGS OCR
+ swe: Instruktioner för installation av PGS OCR
+ pol: Instrukcje konfiguracji PGS OCR
+ chs: PGS OCR 设置说明
+ ukr: Інструкція з налаштування PGS OCR
+ kor: PGS OCR 설정 지침
+ ron: Instrucțiuni de configurare PGS OCR
+Please:
+ eng: Please
+ deu: Bitte
+ fra: S'il vous plaît
+ ita: Per favore
+ spa: Por favor,
+ jpn: お願い
+ rus: Пожалуйста,
+ por: Por favor
+ swe: Vänligen
+ pol: Proszę
+ chs: 请
+ ukr: Будь ласка.
+ kor: 제발
+ ron: Vă rog
+Please provide bitrates for the audio streams:
+ eng: Please provide bitrates for the audio streams
+ deu: Bitte geben Sie die Bitraten für die Audiostreams an
+ fra: Veuillez fournir les débits binaires pour les flux audio
+ ita: Fornire il bitrate dei flussi audio
+ spa: Indique la velocidad de bits de los flujos de audio
+ jpn: オーディオストリームのビットレートを教えてください。
+ rus: Пожалуйста, укажите битрейт аудиопотоков
+ por: Forneça as taxas de bits para os fluxos de áudio
+ swe: Vänligen ange bitrate för ljudströmmarna
+ pol: Podaj bitrate dla strumieni audio
+ chs: 请提供音频流的比特率
+ ukr: Будь ласка, вкажіть бітрейт для аудіопотоків
+ kor: 오디오 스트림의 비트레이트를 입력하세요.
+ ron: Vă rugăm să furnizați bitrate pentru fluxurile audio
+Please specify output video:
+ eng: Please specify output video
+ deu: Bitte geben Sie das Ausgangsvideo an
+ fra: Veuillez spécifier la vidéo de sortie
+ ita: Specificare il video in uscita
+ spa: Especifique el vídeo de salida
+ jpn: 出力ビデオを指定してください
+ rus: Пожалуйста, укажите выходное видео
+ por: Especificar o vídeo de saída
+ swe: Vänligen ange utmatningsvideo
+ pol: Należy określić wyjściowy sygnał wideo
+ chs: 请指定输出视频
+ ukr: Будь ласка, вкажіть вихідне відео
+ kor: 출력 비디오를 지정하세요.
+ ron: Vă rugăm să specificați ieșirea video
+Remove:
+ eng: Remove
+ deu: entfernen
+ fra: Retirer
+ ita: Rimuovere
+ spa: Eliminar
+ jpn: 削除
+ rus: Удалить
+ por: Remover
+ swe: Ta bort
+ pol: Usunąć
+ chs: 移除
+ ukr: Видалити
+ kor: 제거
+ ron: Eliminați
+Rigaya's encoders updated:
+ eng: Rigaya's encoders updated
+ deu: Rigayas Geber aktualisiert
+ fra: Mise à jour des encodeurs de Rigaya
+ ita: Aggiornamento dei codificatori di Rigaya
+ spa: Actualización de los codificadores de Rigaya
+ jpn: リガヤのエンコーダーが更新
+ rus: Обновление кодировщиков Rigaya
+ por: Atualização dos codificadores de Rigaya
+ swe: Rigayas pulsgivare uppdaterade
+ pol: Zaktualizowano kodery Rigaya
+ chs: 更新里加亚编码器
+ ukr: Оновлені енкодери Rigaya
+ kor: 리가야의 인코더 업데이트
+ ron: Codificatoarele Rigaya actualizate
+Second Pass:
+ eng: Second Pass
+ deu: Zweiter Durchgang
+ fra: Deuxième passage
+ ita: Secondo passaggio
+ spa: Segundo pase
+ jpn: セカンドパス
+ rus: Второе прохождение
+ por: Segunda passagem
+ swe: Andra passet
+ pol: Drugi przejazd
+ chs: 第二次通过
+ ukr: Другий прохід
+ kor: 두 번째 패스
+ ron: A doua trecere
+Select Subtitle File:
+ eng: Select Subtitle File
+ deu: Untertiteldatei auswählen
+ fra: Sélectionner le fichier de sous-titres
+ ita: Selezionare il file dei sottotitoli
+ spa: Seleccionar archivo de subtítulos
+ jpn: 字幕ファイルを選択
+ rus: Выберите файл субтитров
+ por: Selecionar ficheiro de legendas
+ swe: Välj undertextfil
+ pol: Wybierz plik napisów
+ chs: 选择字幕文件
+ ukr: Виберіть файл субтитрів
+ kor: 자막 파일 선택
+ ron: Selectați fișierul de subtitrare
+There is an existing burn-in track, only one can be enabled at a time:
+ eng: There is an existing burn-in track, only one can be enabled at a time
+ deu: Es gibt eine vorhandene Einbrennspur, es kann jeweils nur eine aktiviert werden
+ fra: Il y a une piste de déverminage existante, une seule peut être activée à la fois.
+ ita: Esiste una traccia di burn-in, ma è possibile attivarne solo una alla volta.
+ spa: Existe una pista de quemado, sólo se puede activar una a la vez
+ jpn: 既存のバーンイントラックがあり、一度に1つしか有効にできない。
+ rus: Имеется существующая дорожка прожига, одновременно может быть включена только одна.
+ por: Existe uma pista de burn-in, mas só pode ser activada uma de cada vez
+ swe: Det finns ett befintligt inbränningsspår, men endast ett kan aktiveras åt gången
+ pol: Istnieje ścieżka wypalania, tylko jedna może być włączona w danym momencie.
+ chs: 现有一条预烧轨道,每次只能启用一条
+ ukr: Існує існуюча доріжка вигоряння, одночасно може бути ввімкнена лише одна
+ kor: 기존 번인 트랙이 있으며, 한 번에 하나만 활성화할 수 있습니다.
+ ron: Există o cale de ardere existentă, numai una poate fi activată simultan
+This video has been detected to have an interlaced video.:
+ eng: This video has been detected to have an interlaced video.
+ deu: Dieses Video wurde als Zeilensprungvideo erkannt.
+ fra: Cette vidéo a été détectée comme étant une vidéo entrelacée.
+ ita: È stato rilevato che questo video è interlacciato.
+ spa: Se ha detectado que este vídeo está entrelazado.
+ jpn: このビデオはインターレースビデオとして検出されました。
+ rus: Было обнаружено, что это видео имеет чересстрочную развертку.
+ por: Foi detectado que este vídeo tem um vídeo entrelaçado.
+ swe: Den här videon har upptäckts ha en interlaced video.
+ pol: Wykryto, że to wideo ma przeplot.
+ chs: 检测到此视频为隔行扫描视频。
+ ukr: Виявлено, що це відео має чересстрочний формат.
+ kor: 이 동영상에 인터레이스된 동영상이 있는 것으로 감지되었습니다.
+ ron: Acest videoclip a fost detectat ca având un videoclip întrepătruns.
+Top must be positive number:
+ eng: Top must be positive number
+ deu: Oben muss eine positive Zahl sein
+ fra: Le sommet doit être un nombre positif
+ ita: Top deve essere un numero positivo
+ spa: Top debe ser un número positivo
+ jpn: トップは正の数でなければならない
+ rus: Top должно быть положительным числом
+ por: O topo deve ser um número positivo
+ swe: Top måste vara ett positivt tal
+ pol: Top musi być liczbą dodatnią
+ chs: 顶部必须是正数
+ ukr: Верхня частина має бути додатнім числом
+ kor: 상단은 양수여야 합니다.
+ ron: Topul trebuie să fie un număr pozitiv
+Total video width must be greater than 0:
+ eng: Total video width must be greater than 0
+ deu: Die Gesamtbreite des Videos muss größer als 0 sein.
+ fra: La largeur totale de la vidéo doit être supérieure à 0
+ ita: La larghezza totale del video deve essere maggiore di 0
+ spa: La anchura total del vídeo debe ser superior a 0
+ jpn: ビデオ幅の合計は0より大きくなければならない
+ rus: Общая ширина видео должна быть больше 0
+ por: A largura total do vídeo deve ser superior a 0
+ swe: Total videobredd måste vara större än 0
+ pol: Całkowita szerokość wideo musi być większa niż 0
+ chs: 视频总宽度必须大于 0
+ ukr: Загальна ширина відео повинна бути більшою за 0
+ kor: 총 동영상 너비는 0보다 커야 합니다.
+ ron: Lățimea video totală trebuie să fie mai mare decât 0
+Updating Rigaya's encoders:
+ eng: Updating Rigaya's encoders
+ deu: Aktualisierung der Rigaya-Geber
+ fra: Mise à jour des encodeurs de Rigaya
+ ita: Aggiornamento degli encoder di Rigaya
+ spa: Actualización de los codificadores de Rigaya
+ jpn: リガヤのエンコーダーをアップデート
+ rus: Обновление кодеров Rigaya
+ por: Atualização dos codificadores Rigaya
+ swe: Uppdatering av Rigayas kodare
+ pol: Aktualizacja enkoderów Rigaya
+ chs: 更新 Rigaya 编码器
+ ukr: Оновлення енкодерів Rigaya
+ kor: 리가야의 인코더 업데이트
+ ron: Actualizarea codificatoarelor Rigaya
+Waiting for current encode to finish before shutdown:
+ eng: Waiting for current encode to finish before shutdown
+ deu: Warten auf das Ende der aktuellen Kodierung vor dem Herunterfahren
+ fra: Attente de la fin de l'encodage en cours avant l'arrêt
+ ita: Attendere che la codifica in corso sia terminata prima di spegnersi
+ spa: Esperar a que finalice la codificación actual antes del apagado
+ jpn: シャットダウン前に現在のエンコードが終了するのを待つ
+ rus: Ожидание завершения текущего кодирования перед выключением
+ por: Aguardar que a codificação atual termine antes de encerrar
+ swe: Väntar på att aktuell kodning ska avslutas innan avstängning
+ pol: Oczekiwanie na zakończenie bieżącego kodowania przed wyłączeniem
+ chs: 关闭前等待当前编码完成
+ ukr: Очікування завершення поточного кодування перед вимкненням
+ kor: 종료하기 전에 현재 인코딩이 완료되기를 기다리는 중입니다.
+ ron: Așteptarea finalizării codării curente înainte de închidere
+Width must be smaller than video width:
+ eng: Width must be smaller than video width
+ deu: Die Breite muss kleiner sein als die Videobreite
+ fra: La largeur doit être inférieure à la largeur de la vidéo
+ ita: La larghezza deve essere inferiore alla larghezza del video
+ spa: La anchura debe ser inferior a la anchura del vídeo
+ jpn: 幅はビデオの幅より小さくなければならない
+ rus: Ширина должна быть меньше ширины видео
+ por: A largura deve ser inferior à largura do vídeo
+ swe: Bredden måste vara mindre än videobredden
+ pol: Szerokość musi być mniejsza niż szerokość wideo
+ chs: 宽度必须小于视频宽度
+ ukr: Ширина повинна бути меншою за ширину відео
+ kor: 너비는 동영상 너비보다 작아야 합니다.
+ ron: Lățimea trebuie să fie mai mică decât lățimea videoclipului
+and add it to PATH:
+ eng: and add it to PATH
+ deu: und fügen Sie es zu PATH hinzu
+ fra: et l'ajouter au PATH
+ ita: e aggiungerlo al PATH
+ spa: y añádelo a PATH
+ jpn: に追加する。
+ rus: и добавьте его в PATH
+ por: e adicioná-lo ao PATH
+ swe: och lägg till den i PATH
+ pol: i dodać go do PATH
+ chs: 并将其添加到 PATH
+ ukr: і додайте його до PATH
+ kor: 를 클릭하고 PATH에 추가합니다.
+ ron: și adăugați-l la PATH
+bottom:
+ eng: bottom
+ deu: unten
+ fra: fond
+ ita: fondo
+ spa: fondo
+ jpn: 下
+ rus: дно
+ por: fundo
+ swe: botten
+ pol: dno
+ chs: 底层
+ ukr: дно
+ kor: 하단
+ ron: fund
+configure in Settings:
+ eng: configure in Settings
+ deu: in den Einstellungen konfigurieren
+ fra: configurer dans Paramètres
+ ita: configurare in Impostazioni
+ spa: configurar en Ajustes
+ jpn: 設定
+ rus: настроить в разделе Настройки
+ por: configurar em Definições
+ swe: konfigurera i Inställningar
+ pol: skonfigurować w Ustawieniach
+ chs: 在设置中配置
+ ukr: налаштувати в Налаштуваннях
+ kor: 설정에서 구성
+ ron: configurați în Setări
+download a static FFmpeg:
+ eng: download a static FFmpeg
+ deu: ein statisches FFmpeg herunterladen
+ fra: télécharger un FFmpeg statique
+ ita: scaricare un FFmpeg statico
+ spa: descargar un FFmpeg estático
+ jpn: 静的なFFmpegをダウンロードする
+ rus: загрузить статический FFmpeg
+ por: descarregar um FFmpeg estático
+ swe: ladda ner en statisk FFmpeg
+ pol: pobierz statyczny FFmpeg
+ chs: 下载静态 FFmpeg
+ ukr: завантажити статичний FFmpeg
+ kor: 정적 FFmpeg 다운로드
+ ron: descărcați un FFmpeg static
+hdr10plus_tool was not properly downloaded as the file size is too small:
+ eng: hdr10plus_tool was not properly downloaded as the file size is too small
+ deu: hdr10plus_tool wurde nicht ordnungsgemäß heruntergeladen, da die Dateigröße zu klein ist
+ fra: hdr10plus_tool n'a pas été correctement téléchargé car la taille du fichier est trop petite
+ ita: hdr10plus_tool non è stato scaricato correttamente perché la dimensione del file è troppo piccola.
+ spa: hdr10plus_tool no se ha descargado correctamente porque el tamaño del archivo es demasiado pequeño.
+ jpn: ファイルサイズが小さすぎるため、hdr10plus_toolが正しくダウンロードされませんでした。
+ rus: hdr10plus_tool не был загружен должным образом, так как размер файла слишком мал
+ por: hdr10plus_tool não foi descarregado corretamente porque o tamanho do ficheiro é demasiado pequeno
+ swe: hdr10plus_tool laddades inte ner ordentligt eftersom filstorleken är för liten
+ pol: hdr10plus_tool nie został poprawnie pobrany, ponieważ rozmiar pliku jest zbyt mały
+ chs: 由于文件太小,无法正确下载 hdr10plus_tool
+ ukr: hdr10plus_tool не вдалося завантажити належним чином, оскільки розмір файлу занадто малий
+ kor: 파일 크기가 너무 작아서 hdr10plus_tool이 제대로 다운로드되지 않았습니다.
+ ron: hdr10plus_tool nu nu a fost descărcat corect deoarece dimensiunea fișierului este prea mică
+ignoring:
+ eng: ignoring
+ deu: Ignorieren von
+ fra: ignorant
+ ita: ignorare
+ spa: ignorando
+ jpn: 無視
+ rus: игнорирование
+ por: ignorando
+ swe: ignorerar
+ pol: ignorowanie
+ chs: 视而不见
+ ukr: ігнорування
+ kor: 무시
+ ron: ignorare
+latest release from:
+ eng: latest release from
+ deu: neueste Veröffentlichung von
+ fra: dernière publication de
+ ita: l'ultima uscita di
+ spa: último lanzamiento de
+ jpn: からの最新リリース
+ rus: последний релиз от
+ por: último lançamento da
+ swe: senaste utgåvan från
+ pol: najnowsze wydanie od
+ chs: 最新发布的
+ ukr: останній реліз від
+ kor: 최신 릴리스
+ ron: cea mai recentă versiune de la
+left:
+ eng: left
+ deu: links
+ fra: gauche
+ ita: sinistra
+ spa: izquierda
+ jpn: 左
+ rus: слева
+ por: esquerda
+ swe: vänster
+ pol: lewy
+ chs: 左侧
+ ukr: ліворуч
+ kor: 왼쪽
+ ron: stânga
+right:
+ eng: right
+ deu: rechts
+ fra: droit
+ ita: diritto
+ spa: derecha
+ jpn: 右
+ rus: справа
+ por: correto
+ swe: rätt
+ pol: prawo
+ chs: 对
+ ukr: Так.
+ kor: 오른쪽
+ ron: corect
+top:
+ eng: top
+ deu: top
+ fra: sommet
+ ita: top
+ spa: top
+ jpn: トップ
+ rus: топ
+ por: topo
+ swe: topp
+ pol: top
+ chs: 顶级
+ ukr: верхній
+ kor: top
+ ron: top
diff --git a/fastflix/encoders/av1_aom/main.py b/fastflix/encoders/av1_aom/main.py
index 59944634..b2f45a96 100644
--- a/fastflix/encoders/av1_aom/main.py
+++ b/fastflix/encoders/av1_aom/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.av1_aom.command_builder import build # noqa: F401,E402
from fastflix.encoders.av1_aom.settings_panel import AV1 as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/av1_aom/settings_panel.py b/fastflix/encoders/av1_aom/settings_panel.py
index 7bdced2c..ba81dac9 100644
--- a/fastflix/encoders/av1_aom/settings_panel.py
+++ b/fastflix/encoders/av1_aom/settings_panel.py
@@ -88,14 +88,14 @@ def __init__(self, parent, main, app: FastFlixApp):
self.ffmpeg_level = QtWidgets.QLabel()
grid.addWidget(self.ffmpeg_level, 8, 2, 1, 4)
- grid.addLayout(self._add_custom(), 10, 0, 1, 6)
- grid.setRowStretch(9, 1)
+ custom_layout = self._add_custom()
guide_label = QtWidgets.QLabel(
link("https://trac.ffmpeg.org/wiki/Encode/AV1", t("FFMPEG AV1 Encoding Guide"), app.fastflix.config.theme)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, -1, 1)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
+ grid.setRowStretch(9, 1)
self.hdr10plus_signal.connect(self.done_hdr10plus_extract)
self.hdr10plus_ffmpeg_signal.connect(lambda x: self.ffmpeg_level.setText(x))
@@ -221,21 +221,31 @@ def denoise_update(self):
self.widgets.custom_denoise.setDisabled(not custom)
self.main.page_update()
+ def _set_denoise_from_value(self, saved):
+ """Set denoise combo box and custom field from an integer value."""
+ if not saved or str(saved) == "0":
+ self.widgets.denoise.setCurrentIndex(0)
+ return
+ matched = False
+ for i, opt in enumerate(denoise_options):
+ if opt.startswith(str(saved) + " "):
+ self.widgets.denoise.setCurrentIndex(i)
+ matched = True
+ break
+ if not matched:
+ self.widgets.denoise.setCurrentIndex(len(denoise_options) - 1)
+ self.widgets.custom_denoise.setText(str(saved))
+ self.denoise_update()
+
+ def update_profile(self):
+ saved = self.app.fastflix.config.encoder_opt(self.profile_name, "denoise_noise_level")
+ self._set_denoise_from_value(saved)
+ super().update_profile()
+
def reload(self):
super().reload()
saved = self.app.fastflix.current_video.video_settings.video_encoder_settings.denoise_noise_level
- if saved and str(saved) != "0":
- matched = False
- for i, opt in enumerate(denoise_options):
- if opt.startswith(str(saved)):
- self.widgets.denoise.setCurrentIndex(i)
- matched = True
- break
- if not matched:
- self.widgets.denoise.setCurrentIndex(len(denoise_options) - 1)
- self.widgets.custom_denoise.setText(str(saved))
- else:
- self.widgets.denoise.setCurrentIndex(0)
+ self._set_denoise_from_value(saved)
def init_aom_params(self):
layout = QtWidgets.QHBoxLayout()
diff --git a/fastflix/encoders/avc_x264/main.py b/fastflix/encoders/avc_x264/main.py
index af11606c..7b32827d 100644
--- a/fastflix/encoders/avc_x264/main.py
+++ b/fastflix/encoders/avc_x264/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.avc_x264.command_builder import build # noqa: F401,E402
from fastflix.encoders.avc_x264.settings_panel import AVC as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/avc_x264/settings_panel.py b/fastflix/encoders/avc_x264/settings_panel.py
index ab36a25f..43778fec 100644
--- a/fastflix/encoders/avc_x264/settings_panel.py
+++ b/fastflix/encoders/avc_x264/settings_panel.py
@@ -2,7 +2,7 @@
import logging
from box import Box
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import SettingPanel
from fastflix.language import t
@@ -74,7 +74,6 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 5, 4)
- grid.addLayout(self._add_custom(), 10, 0, 1, 6)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
grid.addLayout(self.init_max_mux(), 1, 0, 1, 2)
@@ -89,6 +88,7 @@ def __init__(self, parent, main, app: FastFlixApp):
grid.setRowStretch(9, 1)
+ custom_layout = self._add_custom()
guide_label = QtWidgets.QLabel(
link(
"https://trac.ffmpeg.org/wiki/Encode/H.264",
@@ -96,9 +96,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 6)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/common/encc_helpers.py b/fastflix/encoders/common/encc_helpers.py
index 22250b5a..6db0e894 100644
--- a/fastflix/encoders/common/encc_helpers.py
+++ b/fastflix/encoders/common/encc_helpers.py
@@ -2,7 +2,7 @@
import logging
from typing import List
-from fastflix.models.video import SubtitleTrack, AudioTrack
+from fastflix.models.video import SubtitleTrack, AudioTrack, DataTrack
from fastflix.encoders.common.audio import lossless
from fastflix.models.fastflix import FastFlix
from fastflix.models.encode import VCEEncCAVCSettings, VCEEncCAV1Settings, VCEEncCSettings
@@ -122,11 +122,14 @@ def build_audio(audio_tracks: list[AudioTrack], audio_streams) -> List[str]:
if not track.conversion_codec or track.conversion_codec == "none":
copies.append(str(audio_id))
elif track.conversion_codec:
- downmix = (
- ["--audio-stream", f"{audio_id}?:{track.downmix}"]
- if track.downmix and track.downmix != "No Downmix"
- else []
- )
+ if track.downmix and track.downmix != "No Downmix":
+ downmix = ["--audio-stream", f"{audio_id}?:{track.downmix}"]
+ else:
+ raw_layout = track.raw_info.get("channel_layout", "") if track.raw_info else ""
+ if raw_layout:
+ downmix = ["--audio-stream", f"{audio_id}?:{raw_layout}"]
+ else:
+ downmix = []
bitrate_parts = []
if track.conversion_codec not in lossless:
if track.conversion_bitrate:
@@ -168,17 +171,15 @@ def build_audio(audio_tracks: list[AudioTrack], audio_streams) -> List[str]:
def build_subtitle(subtitle_tracks: list[SubtitleTrack], subtitle_streams, video_height: int) -> List[str]:
- # Rigaya encoders only support embedded streams, filter out external tracks
- subtitle_tracks = [t for t in subtitle_tracks if not t.external]
+ embedded_tracks = [t for t in subtitle_tracks if not t.external]
+ external_tracks = [t for t in subtitle_tracks if t.external]
command_list = []
copies = []
stream_ids = get_stream_pos(subtitle_streams)
- if not subtitle_tracks:
- return []
scale = ",scale=2.0" if video_height > 1800 else ""
- for track in sorted(subtitle_tracks, key=lambda x: x.outdex):
+ for track in sorted(embedded_tracks, key=lambda x: x.outdex):
if not track.enabled:
continue
sub_id = stream_ids[track.index]
@@ -197,10 +198,43 @@ def build_subtitle(subtitle_tracks: list[SubtitleTrack], subtitle_streams, video
command_list.extend(["--sub-metadata", f"{sub_id}?language={track.language}"])
- if not command_list:
+ for track in sorted(external_tracks, key=lambda x: x.outdex):
+ if not track.enabled:
+ continue
+ if track.burn_in:
+ ext_scale = ",scale=2.0" if video_height > 1800 else ""
+ command_list.extend(["--vpp-subburn", f"filename={track.file_path}{ext_scale}"])
+ else:
+ command_list.extend(["--sub-source", track.file_path])
+
+ if not command_list and not copies:
return []
result = []
if copies:
result.extend(["--sub-copy", ",".join(copies)])
result.extend(command_list)
return result
+
+
+def build_data(data_tracks: list[DataTrack], data_streams, attachment_streams) -> List[str]:
+ if not data_tracks:
+ return []
+ command_list = []
+ data_copies = []
+ attachment_copies = []
+ data_stream_ids = get_stream_pos(data_streams)
+ attachment_stream_ids = get_stream_pos(attachment_streams)
+
+ for track in data_tracks:
+ if not track.enabled:
+ continue
+ if track.codec_type == "data" and track.index in data_stream_ids:
+ data_copies.append(str(data_stream_ids[track.index]))
+ elif track.codec_type == "attachment" and track.index in attachment_stream_ids:
+ attachment_copies.append(str(attachment_stream_ids[track.index]))
+
+ if data_copies:
+ command_list.extend(["--data-copy", ",".join(data_copies)])
+ if attachment_copies:
+ command_list.extend(["--attachment-copy", ",".join(attachment_copies)])
+ return command_list
diff --git a/fastflix/encoders/common/helpers.py b/fastflix/encoders/common/helpers.py
index 0012c8a9..7305330a 100644
--- a/fastflix/encoders/common/helpers.py
+++ b/fastflix/encoders/common/helpers.py
@@ -167,6 +167,7 @@ def generate_ending(
output_fps: Union[str, None] = None,
disable_rotate_metadata=False,
copy_data=False,
+ data_tracks=None,
**_,
):
command = []
@@ -196,7 +197,22 @@ def generate_ending(
if cover:
command.extend(cover)
- if copy_data:
+ if data_tracks:
+ has_data = False
+ has_attachment = False
+ for track in data_tracks:
+ if not track.enabled:
+ continue
+ command.extend(["-map", f"0:{track.index}"])
+ if track.codec_type == "data":
+ has_data = True
+ elif track.codec_type == "attachment":
+ has_attachment = True
+ if has_data:
+ command.extend(["-c:d", "copy"])
+ if has_attachment:
+ command.extend(["-c:t", "copy"])
+ elif copy_data:
command.extend(["-map", "0:d", "-c:d", "copy"])
if output_video and not null_ending:
@@ -213,6 +229,7 @@ def generate_filters(
crop: Optional[dict] = None,
scale=None,
scale_filter="lanczos",
+ sar=None,
remove_hdr=False,
vaapi: bool = False,
rotate=0,
@@ -221,6 +238,8 @@ def generate_filters(
burn_in_subtitle_track=None,
burn_in_subtitle_type=None,
burn_in_file_index: int = 0,
+ source_width: Optional[int] = None,
+ source_height: Optional[int] = None,
custom_filters=None,
start_filters=None,
raw_filters=False,
@@ -247,6 +266,8 @@ def generate_filters(
if scale:
if not vaapi:
filter_list.append(f"scale={scale}:flags={scale_filter},setsar=1:1")
+ elif sar and sar != "1:1" and sar != "1/1":
+ filter_list.append("setsar=1:1")
if rotate:
if rotate == 1:
filter_list.append("transpose=1")
@@ -310,7 +331,8 @@ def generate_filters(
filter_complex = f"[0:{selected_track}][{burn_in_file_index}:{burn_in_subtitle_track}]overlay[v]"
else:
filter_prefix = f"{filters}," if filters else ""
- filter_complex = f"[0:{selected_track}]{filter_prefix}subtitles='{quoted_path(str(source))}':si={burn_in_subtitle_track}[v]"
+ original_size = f":original_size={source_width}x{source_height}" if source_width and source_height else ""
+ filter_complex = f"[0:{selected_track}]{filter_prefix}subtitles='{quoted_path(str(source))}':si={burn_in_subtitle_track}{original_size}[v]"
elif filters:
filter_complex = f"[0:{selected_track}]{filters}[v]"
else:
@@ -390,7 +412,10 @@ def generate_all(
burn_in_subtitle_track=burn_in_track,
burn_in_subtitle_type=burn_in_type,
burn_in_file_index=burn_in_file_index,
+ source_width=fastflix.current_video.width,
+ source_height=fastflix.current_video.height,
scale=fastflix.current_video.scale,
+ sar=fastflix.current_video.sar,
enable_opencl=enable_opencl,
vaapi=vaapi,
**filter_details,
@@ -402,6 +427,7 @@ def generate_all(
cover=attachments_cmd,
output_video=fastflix.current_video.video_settings.output_path,
disable_rotate_metadata=encoder == "copy",
+ data_tracks=fastflix.current_video.data_tracks,
**fastflix.current_video.video_settings.model_dump(),
)
diff --git a/fastflix/encoders/common/setting_panel.py b/fastflix/encoders/common/setting_panel.py
index af6e86e5..da923669 100644
--- a/fastflix/encoders/common/setting_panel.py
+++ b/fastflix/encoders/common/setting_panel.py
@@ -95,6 +95,7 @@ def translate_tip(tooltip):
return " ".join([t(x) for x in tooltip.split("\n") if x.strip()])
def determine_default(self, widget_name, opt, items: List, raise_error: bool = False):
+ original_opt = opt
if widget_name == "pix_fmt":
items = [x.split(":")[1].strip() for x in items]
elif widget_name in ("crf", "qp", "qscale"):
@@ -106,6 +107,24 @@ def determine_default(self, widget_name, opt, items: List, raise_error: bool = F
if not opt:
return 5
items = [x.split("(")[0].split("-")[0].strip() for x in items]
+ elif widget_name in ("film_grain", "photon_noise"):
+ if opt is None or opt == 0:
+ return 0
+ opt = str(opt)
+ items = [x.split(" - ")[0].split(" ")[0].strip() for x in items]
+ elif widget_name == "period":
+ if opt is None:
+ return 0
+ opt = str(opt)
+ elif widget_name == "threads":
+ if opt == 0:
+ return 0
+ opt = str(opt)
+ elif widget_name in ("auto_alt_ref", "lag_in_frames", "aq_mode", "sharpness"):
+ if opt == -1:
+ return 0
+ opt = str(opt)
+ items = [x.split("(")[0].split()[0] for x in items]
elif widget_name == "gpu":
if opt == -1:
return 0
@@ -117,6 +136,10 @@ def determine_default(self, widget_name, opt, items: List, raise_error: bool = F
for i, item in enumerate(items):
if item.split(" - ")[0].strip() == opt:
return i
+ # If original opt was an integer, use it directly as a combo box index
+ # (e.g. x265 aq_mode stores index 0-4, not a string label)
+ if isinstance(original_opt, int) and 0 <= original_opt < len(items):
+ return original_opt
if raise_error:
raise FastFlixInternalException
else:
diff --git a/fastflix/encoders/common/subtitles.py b/fastflix/encoders/common/subtitles.py
index 16be1b5f..5bb1fb7d 100644
--- a/fastflix/encoders/common/subtitles.py
+++ b/fastflix/encoders/common/subtitles.py
@@ -47,6 +47,12 @@ def build_subtitle(
else:
command_list.extend([f"-disposition:{outdex}", "0"])
command_list.extend([f"-metadata:s:{outdex}", f"language={track.language}"])
+ if track.title:
+ command_list.extend([f"-metadata:s:{outdex}", f"title={track.title}"])
+ command_list.extend([f"-metadata:s:{outdex}", f"handler={track.title}"])
+ else:
+ command_list.extend([f"-metadata:s:{outdex}", "title="])
+ command_list.extend([f"-metadata:s:{outdex}", "handler="])
if not subs_enabled:
command_list.extend(["-default_mode", "infer_no_subs"])
return command_list, burn_in_track, burn_in_type
diff --git a/fastflix/encoders/copy/main.py b/fastflix/encoders/copy/main.py
index f93080f6..d5343663 100644
--- a/fastflix/encoders/copy/main.py
+++ b/fastflix/encoders/copy/main.py
@@ -16,6 +16,7 @@
enable_audio = True
enable_attachments = True
enable_advanced = False
+enable_data = True
from fastflix.encoders.copy.command_builder import build # noqa: F401,E402
from fastflix.encoders.copy.settings_panel import Copy as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/ffmpeg_hevc_nvenc/main.py b/fastflix/encoders/ffmpeg_hevc_nvenc/main.py
index 1474e66f..7221636f 100644
--- a/fastflix/encoders/ffmpeg_hevc_nvenc/main.py
+++ b/fastflix/encoders/ffmpeg_hevc_nvenc/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.ffmpeg_hevc_nvenc.command_builder import build # noqa: F401,E402
from fastflix.encoders.ffmpeg_hevc_nvenc.settings_panel import NVENC as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/gif/main.py b/fastflix/encoders/gif/main.py
index 0b0e40e5..81850e0a 100644
--- a/fastflix/encoders/gif/main.py
+++ b/fastflix/encoders/gif/main.py
@@ -16,6 +16,7 @@
enable_audio = False
enable_attachments = False
enable_concat = False
+enable_data = False
audio_formats = []
diff --git a/fastflix/encoders/gifski/main.py b/fastflix/encoders/gifski/main.py
index 38872f6a..04c0edc4 100644
--- a/fastflix/encoders/gifski/main.py
+++ b/fastflix/encoders/gifski/main.py
@@ -16,6 +16,7 @@
enable_audio = False
enable_attachments = False
enable_concat = False
+enable_data = False
audio_formats = []
diff --git a/fastflix/encoders/h264_videotoolbox/main.py b/fastflix/encoders/h264_videotoolbox/main.py
index 22683aff..3ac30cea 100644
--- a/fastflix/encoders/h264_videotoolbox/main.py
+++ b/fastflix/encoders/h264_videotoolbox/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = False
enable_concat = True
+enable_data = True
from fastflix.encoders.h264_videotoolbox.command_builder import build # noqa: F401,E402
from fastflix.encoders.h264_videotoolbox.settings_panel import H264VideoToolbox as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/hevc_videotoolbox/main.py b/fastflix/encoders/hevc_videotoolbox/main.py
index b414cd2c..6d24c719 100644
--- a/fastflix/encoders/hevc_videotoolbox/main.py
+++ b/fastflix/encoders/hevc_videotoolbox/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = False
enable_concat = True
+enable_data = True
from fastflix.encoders.hevc_videotoolbox.command_builder import build # noqa: F401,E402
from fastflix.encoders.hevc_videotoolbox.settings_panel import HEVCVideoToolbox as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/hevc_x265/main.py b/fastflix/encoders/hevc_x265/main.py
index 43c62fb1..408a2b6f 100644
--- a/fastflix/encoders/hevc_x265/main.py
+++ b/fastflix/encoders/hevc_x265/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.hevc_x265.command_builder import build # noqa: F401,E402
from fastflix.encoders.hevc_x265.settings_panel import HEVC as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/hevc_x265/settings_panel.py b/fastflix/encoders/hevc_x265/settings_panel.py
index 914891ea..80ccdecd 100644
--- a/fastflix/encoders/hevc_x265/settings_panel.py
+++ b/fastflix/encoders/hevc_x265/settings_panel.py
@@ -128,7 +128,7 @@ def __init__(self, parent, main, app: FastFlixApp):
grid.setRowStretch(11, True)
- grid.addLayout(self._add_custom(), 12, 0, 1, 6)
+ custom_layout = self._add_custom()
link_1 = link(
"https://trac.ffmpeg.org/wiki/Encode/H.265",
@@ -140,17 +140,12 @@ def __init__(self, parent, main, app: FastFlixApp):
t("CodeCalamity UHD HDR Encoding Guide"),
app.fastflix.config.theme,
)
- link_3 = link(
- "https://github.com/cdgriffith/FastFlix/wiki/HDR10-Plus-Metadata-Extraction",
- t("HDR10+ Metadata Extraction"),
- app.fastflix.config.theme,
- )
- guide_label = QtWidgets.QLabel(f"{link_1} | {link_2} | {link_3}")
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
+ guide_label = QtWidgets.QLabel(f"{link_1} | {link_2}")
guide_label.setOpenExternalLinks(True)
+ custom_layout.addWidget(guide_label)
- grid.addWidget(guide_label, 13, 0, 1, 6)
+ grid.addLayout(custom_layout, 12, 0, 1, 6)
self.hdr10plus_signal.connect(self.done_hdr10plus_extract)
self.hdr10plus_ffmpeg_signal.connect(lambda x: self.ffmpeg_level.setText(x))
@@ -164,6 +159,15 @@ def init_dhdr10_info(self):
button_action=lambda: self.dhdr10_update(),
tooltip="dhdr10_info: Path to HDR10+ JSON metadata file",
)
+ # Replace plain label with clickable wiki link
+ self.labels["hdr10plus_metadata"].setText(
+ link(
+ "https://github.com/cdgriffith/FastFlix/wiki/HDR10-Plus-Metadata-Extraction",
+ t("HDR10+ Metadata"),
+ self.app.fastflix.config.theme,
+ )
+ )
+ self.labels["hdr10plus_metadata"].setOpenExternalLinks(True)
self.labels["hdr10plus_metadata"].setFixedWidth(200)
return layout
diff --git a/fastflix/encoders/modify/main.py b/fastflix/encoders/modify/main.py
index c60a2f08..7af8d531 100644
--- a/fastflix/encoders/modify/main.py
+++ b/fastflix/encoders/modify/main.py
@@ -16,6 +16,7 @@
enable_audio = False
enable_attachments = False
enable_advanced = False
+enable_data = False
from fastflix.encoders.modify.command_builder import build # noqa: F401,E402
from fastflix.encoders.modify.settings_panel import Modify as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/nvencc_av1/command_builder.py b/fastflix/encoders/nvencc_av1/command_builder.py
index 1634c025..d1a2d31e 100644
--- a/fastflix/encoders/nvencc_av1/command_builder.py
+++ b/fastflix/encoders/nvencc_av1/command_builder.py
@@ -9,6 +9,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_auto_options,
rigaya_avformat_reader,
)
@@ -82,6 +83,8 @@ def build(fastflix: FastFlix):
if video.video_settings.remove_metadata:
command.extend(["--video-metadata", "clear", "--metadata", "clear"])
+ elif video.video_settings.remove_hdr:
+ command.extend(["--video-metadata", "clear", "--metadata", "copy"])
else:
command.extend(["--video-metadata", "copy", "--metadata", "copy"])
@@ -142,9 +145,9 @@ def build(fastflix: FastFlix):
)
if fastflix.current_video.cll:
command.extend(["--max-cll", str(fastflix.current_video.cll)])
- if settings.copy_hdr10:
+ if settings.copy_hdr10 and not video.video_settings.remove_hdr:
command.extend(["--dhdr10-info", "copy"])
- if settings.copy_dv:
+ if settings.copy_dv and not video.video_settings.remove_hdr:
command.extend(["--dolby-vision-rpu", "copy"])
command.extend(["--dolby-vision-profile", "copy"])
@@ -175,6 +178,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(settings.extra.split())
diff --git a/fastflix/encoders/nvencc_av1/main.py b/fastflix/encoders/nvencc_av1/main.py
index 52cb3d2e..acc547b1 100644
--- a/fastflix/encoders/nvencc_av1/main.py
+++ b/fastflix/encoders/nvencc_av1/main.py
@@ -15,6 +15,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from NVEncC64.exe --check-encoders
diff --git a/fastflix/encoders/nvencc_av1/settings_panel.py b/fastflix/encoders/nvencc_av1/settings_panel.py
index 43d2e6e5..2123c389 100644
--- a/fastflix/encoders/nvencc_av1/settings_panel.py
+++ b/fastflix/encoders/nvencc_av1/settings_panel.py
@@ -74,7 +74,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom NVEncC options", disable_both_passes=True), 10, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom NVEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
# grid.addLayout(self.init_profile(), 1, 0, 1, 2)
@@ -143,10 +143,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/nvencc_avc/command_builder.py b/fastflix/encoders/nvencc_avc/command_builder.py
index 376b68c8..763eabe8 100644
--- a/fastflix/encoders/nvencc_avc/command_builder.py
+++ b/fastflix/encoders/nvencc_avc/command_builder.py
@@ -9,6 +9,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_auto_options,
rigaya_avformat_reader,
)
@@ -149,6 +150,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(settings.extra.split())
diff --git a/fastflix/encoders/nvencc_avc/main.py b/fastflix/encoders/nvencc_avc/main.py
index 310c63cb..df454090 100644
--- a/fastflix/encoders/nvencc_avc/main.py
+++ b/fastflix/encoders/nvencc_avc/main.py
@@ -15,6 +15,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from NVEncC64.exe --check-encoders
diff --git a/fastflix/encoders/nvencc_avc/settings_panel.py b/fastflix/encoders/nvencc_avc/settings_panel.py
index f947b983..88910a6a 100644
--- a/fastflix/encoders/nvencc_avc/settings_panel.py
+++ b/fastflix/encoders/nvencc_avc/settings_panel.py
@@ -74,7 +74,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom NVEncC options", disable_both_passes=True), 10, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom NVEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
# grid.addLayout(self.init_profile(), 1, 0, 1, 2)
@@ -135,10 +135,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/nvencc_hevc/command_builder.py b/fastflix/encoders/nvencc_hevc/command_builder.py
index 2fb08075..5482dc4f 100644
--- a/fastflix/encoders/nvencc_hevc/command_builder.py
+++ b/fastflix/encoders/nvencc_hevc/command_builder.py
@@ -9,6 +9,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_auto_options,
rigaya_avformat_reader,
)
@@ -82,6 +83,8 @@ def build(fastflix: FastFlix):
if video.video_settings.remove_metadata:
command.extend(["--video-metadata", "clear", "--metadata", "clear"])
+ elif video.video_settings.remove_hdr:
+ command.extend(["--video-metadata", "clear", "--metadata", "copy"])
else:
command.extend(["--video-metadata", "copy", "--metadata", "copy"])
@@ -142,9 +145,9 @@ def build(fastflix: FastFlix):
)
if fastflix.current_video.cll:
command.extend(["--max-cll", str(fastflix.current_video.cll)])
- if settings.copy_hdr10:
+ if settings.copy_hdr10 and not video.video_settings.remove_hdr:
command.extend(["--dhdr10-info", "copy"])
- if settings.copy_dv:
+ if settings.copy_dv and not video.video_settings.remove_hdr:
command.extend(["--dolby-vision-rpu", "copy"])
command.extend(["--dolby-vision-profile", "copy"])
@@ -175,6 +178,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(settings.extra.split())
diff --git a/fastflix/encoders/nvencc_hevc/main.py b/fastflix/encoders/nvencc_hevc/main.py
index d1160df0..8eda77e7 100644
--- a/fastflix/encoders/nvencc_hevc/main.py
+++ b/fastflix/encoders/nvencc_hevc/main.py
@@ -15,6 +15,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from NVEncC64.exe --check-encoders
diff --git a/fastflix/encoders/nvencc_hevc/settings_panel.py b/fastflix/encoders/nvencc_hevc/settings_panel.py
index abe4b746..5e683cfd 100644
--- a/fastflix/encoders/nvencc_hevc/settings_panel.py
+++ b/fastflix/encoders/nvencc_hevc/settings_panel.py
@@ -74,7 +74,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom NVEncC options", disable_both_passes=True), 10, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom NVEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
# grid.addLayout(self.init_profile(), 1, 0, 1, 2)
@@ -143,10 +143,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/qsvencc_av1/command_builder.py b/fastflix/encoders/qsvencc_av1/command_builder.py
index 9ad9e85e..60ca7207 100644
--- a/fastflix/encoders/qsvencc_av1/command_builder.py
+++ b/fastflix/encoders/qsvencc_av1/command_builder.py
@@ -10,6 +10,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_auto_options,
rigaya_avformat_reader,
)
@@ -83,6 +84,8 @@ def build(fastflix: FastFlix):
if video.video_settings.remove_metadata:
command.extend(["--video-metadata", "clear", "--metadata", "clear"])
+ elif video.video_settings.remove_hdr:
+ command.extend(["--video-metadata", "clear", "--metadata", "copy"])
else:
command.extend(["--video-metadata", "copy", "--metadata", "copy"])
@@ -137,9 +140,9 @@ def build(fastflix: FastFlix):
if fastflix.current_video.cll:
command.extend(["--max-cll", str(fastflix.current_video.cll)])
- if settings.copy_hdr10:
+ if settings.copy_hdr10 and not video.video_settings.remove_hdr:
command.extend(["--dhdr10-info", "copy"])
- if settings.copy_dv:
+ if settings.copy_dv and not video.video_settings.remove_hdr:
command.extend(["--dolby-vision-rpu", "copy"])
command.extend(["--dolby-vision-profile", "copy"])
@@ -178,6 +181,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(shlex.split(settings.extra))
diff --git a/fastflix/encoders/qsvencc_av1/main.py b/fastflix/encoders/qsvencc_av1/main.py
index 418ee020..3aefc5e9 100644
--- a/fastflix/encoders/qsvencc_av1/main.py
+++ b/fastflix/encoders/qsvencc_av1/main.py
@@ -15,6 +15,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from NVEncC64.exe --check-encoders
diff --git a/fastflix/encoders/qsvencc_av1/settings_panel.py b/fastflix/encoders/qsvencc_av1/settings_panel.py
index e47e0c50..5795820e 100644
--- a/fastflix/encoders/qsvencc_av1/settings_panel.py
+++ b/fastflix/encoders/qsvencc_av1/settings_panel.py
@@ -82,7 +82,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom QSVEncC options", disable_both_passes=True), 10, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom QSVEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
grid.addLayout(self.init_lookahead(), 1, 0, 1, 2)
@@ -148,10 +148,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
self.hdr10plus_signal.connect(self.done_hdr10plus_extract)
diff --git a/fastflix/encoders/qsvencc_avc/command_builder.py b/fastflix/encoders/qsvencc_avc/command_builder.py
index 1ed7abe4..dcf4cfdc 100644
--- a/fastflix/encoders/qsvencc_avc/command_builder.py
+++ b/fastflix/encoders/qsvencc_avc/command_builder.py
@@ -10,6 +10,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_auto_options,
rigaya_avformat_reader,
)
@@ -161,6 +162,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(shlex.split(settings.extra))
diff --git a/fastflix/encoders/qsvencc_avc/main.py b/fastflix/encoders/qsvencc_avc/main.py
index e981a1b1..de3523ab 100644
--- a/fastflix/encoders/qsvencc_avc/main.py
+++ b/fastflix/encoders/qsvencc_avc/main.py
@@ -16,6 +16,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from NVEncC64.exe --check-encoders
diff --git a/fastflix/encoders/qsvencc_avc/settings_panel.py b/fastflix/encoders/qsvencc_avc/settings_panel.py
index 7b231118..9763fa26 100644
--- a/fastflix/encoders/qsvencc_avc/settings_panel.py
+++ b/fastflix/encoders/qsvencc_avc/settings_panel.py
@@ -80,7 +80,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom QSVEncC options", disable_both_passes=True), 10, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom QSVEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
grid.addLayout(self.init_profile(), 1, 0, 1, 2)
@@ -134,10 +134,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/qsvencc_hevc/command_builder.py b/fastflix/encoders/qsvencc_hevc/command_builder.py
index f6d9a097..f41f4043 100644
--- a/fastflix/encoders/qsvencc_hevc/command_builder.py
+++ b/fastflix/encoders/qsvencc_hevc/command_builder.py
@@ -10,6 +10,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_auto_options,
rigaya_avformat_reader,
)
@@ -83,6 +84,8 @@ def build(fastflix: FastFlix):
if video.video_settings.remove_metadata:
command.extend(["--video-metadata", "clear", "--metadata", "clear"])
+ elif video.video_settings.remove_hdr:
+ command.extend(["--video-metadata", "clear", "--metadata", "copy"])
else:
command.extend(["--video-metadata", "copy", "--metadata", "copy"])
@@ -137,9 +140,9 @@ def build(fastflix: FastFlix):
if fastflix.current_video.cll:
command.extend(["--max-cll", str(fastflix.current_video.cll)])
- if settings.copy_hdr10:
+ if settings.copy_hdr10 and not video.video_settings.remove_hdr:
command.extend(["--dhdr10-info", "copy"])
- if settings.copy_dv:
+ if settings.copy_dv and not video.video_settings.remove_hdr:
command.extend(["--dolby-vision-rpu", "copy"])
command.extend(["--dolby-vision-profile", "copy"])
@@ -178,6 +181,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(shlex.split(settings.extra))
diff --git a/fastflix/encoders/qsvencc_hevc/main.py b/fastflix/encoders/qsvencc_hevc/main.py
index b3fe0bd6..8a0c041e 100644
--- a/fastflix/encoders/qsvencc_hevc/main.py
+++ b/fastflix/encoders/qsvencc_hevc/main.py
@@ -15,6 +15,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from NVEncC64.exe --check-encoders
diff --git a/fastflix/encoders/qsvencc_hevc/settings_panel.py b/fastflix/encoders/qsvencc_hevc/settings_panel.py
index 46d3fb70..423b7ef6 100644
--- a/fastflix/encoders/qsvencc_hevc/settings_panel.py
+++ b/fastflix/encoders/qsvencc_hevc/settings_panel.py
@@ -82,7 +82,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom QSVEncC options", disable_both_passes=True), 10, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom QSVEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
grid.addLayout(self.init_qp_mode(), 2, 0, 1, 2)
@@ -140,10 +140,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/rav1e/main.py b/fastflix/encoders/rav1e/main.py
index c5430f95..f466da81 100644
--- a/fastflix/encoders/rav1e/main.py
+++ b/fastflix/encoders/rav1e/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.rav1e.command_builder import build # noqa: F401,E402
from fastflix.encoders.rav1e.settings_panel import RAV1E as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/rav1e/settings_panel.py b/fastflix/encoders/rav1e/settings_panel.py
index f0ebdc62..05418f9f 100644
--- a/fastflix/encoders/rav1e/settings_panel.py
+++ b/fastflix/encoders/rav1e/settings_panel.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import logging
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import SettingPanel
from fastflix.language import t
@@ -87,15 +87,14 @@ def __init__(self, parent, main, app: FastFlixApp):
grid.addLayout(self.init_photon_noise(), 6, 2, 1, 4)
grid.addLayout(self.init_rav1e_params(), 7, 2, 1, 4)
- grid.addLayout(self._add_custom(), 10, 0, 1, 6)
-
grid.setRowStretch(9, 1)
+ custom_layout = self._add_custom()
guide_label = QtWidgets.QLabel(
link("https://github.com/xiph/rav1e/blob/master/README.md", t("rav1e github"), app.fastflix.config.theme)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 6)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
@@ -233,7 +232,10 @@ def update_video_encoder_settings(self):
except (ValueError, TypeError):
photon_noise = 0
else:
- photon_noise = int(photon_noise_text.split(" ")[0])
+ try:
+ photon_noise = int(photon_noise_text.split(" ")[0])
+ except (ValueError, TypeError):
+ photon_noise = 0
rav1e_params_text = self.widgets.rav1e_params.text().strip()
@@ -257,6 +259,32 @@ def update_video_encoder_settings(self):
settings.bitrate = q_value if encode_type == "bitrate" else None
self.app.fastflix.current_video.video_settings.video_encoder_settings = settings
+ def _set_photon_noise_from_value(self, saved):
+ """Set photon noise combo box and custom field from an integer value."""
+ if not saved or str(saved) == "0":
+ self.widgets.photon_noise.setCurrentIndex(0)
+ return
+ matched = False
+ for i, opt in enumerate(photon_noise_options):
+ if opt.startswith(str(saved) + " "):
+ self.widgets.photon_noise.setCurrentIndex(i)
+ matched = True
+ break
+ if not matched:
+ self.widgets.photon_noise.setCurrentIndex(len(photon_noise_options) - 1)
+ self.widgets.custom_photon_noise.setText(str(saved))
+ self.photon_noise_update()
+
+ def update_profile(self):
+ saved = self.app.fastflix.config.encoder_opt(self.profile_name, "photon_noise")
+ self._set_photon_noise_from_value(saved)
+ super().update_profile()
+
+ def reload(self):
+ super().reload()
+ photon_noise = self.app.fastflix.current_video.video_settings.video_encoder_settings.photon_noise
+ self._set_photon_noise_from_value(photon_noise)
+
def set_mode(self, x):
self.mode = x.text()
self.main.build_commands()
diff --git a/fastflix/encoders/svt_av1/main.py b/fastflix/encoders/svt_av1/main.py
index e3548704..312c2503 100644
--- a/fastflix/encoders/svt_av1/main.py
+++ b/fastflix/encoders/svt_av1/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.svt_av1.command_builder import build # noqa: F401,E402
from fastflix.encoders.svt_av1.settings_panel import SVT_AV1 as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/svt_av1/settings_panel.py b/fastflix/encoders/svt_av1/settings_panel.py
index a5825d5b..8012fada 100644
--- a/fastflix/encoders/svt_av1/settings_panel.py
+++ b/fastflix/encoders/svt_av1/settings_panel.py
@@ -3,7 +3,7 @@
import logging
from box import Box
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import SettingPanel
from fastflix.language import t
@@ -98,6 +98,7 @@ def __init__(self, parent, main, app: FastFlixApp):
grid.addLayout(self.init_svtav1_params(), 7, 2, 1, 4)
grid.setRowStretch(12, 1)
+ custom_layout = self._add_custom()
guide_label = QtWidgets.QLabel(
link(
"https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md",
@@ -105,10 +106,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addLayout(self._add_custom(), 14, 0, 1, 6)
- grid.addWidget(guide_label, 15, 0, -1, 1)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 14, 0, 1, 6)
self.setLayout(grid)
self.hide()
@@ -260,6 +260,32 @@ def init_svtav1_params(self):
layout.addWidget(self.widgets.svtav1_params)
return layout
+ def _set_film_grain_from_value(self, saved):
+ """Set film grain combo box and custom field from an integer value."""
+ if not saved or str(saved) == "0":
+ self.widgets.film_grain.setCurrentIndex(0)
+ return
+ matched = False
+ for i, opt in enumerate(film_grain_options):
+ if opt.startswith(str(saved) + " "):
+ self.widgets.film_grain.setCurrentIndex(i)
+ matched = True
+ break
+ if not matched:
+ self.widgets.film_grain.setCurrentIndex(len(film_grain_options) - 1)
+ self.widgets.custom_film_grain.setText(str(saved))
+ self.film_grain_update()
+
+ def update_profile(self):
+ saved = self.app.fastflix.config.encoder_opt(self.profile_name, "film_grain")
+ self._set_film_grain_from_value(saved)
+ super().update_profile()
+
+ def reload(self):
+ super().reload()
+ film_grain = self.app.fastflix.current_video.video_settings.video_encoder_settings.film_grain
+ self._set_film_grain_from_value(film_grain)
+
def init_modes(self):
return self._add_modes(recommended_bitrates, recommended_qp, qp_name="qp", qp_display_name="CRF/QP")
@@ -279,7 +305,10 @@ def update_video_encoder_settings(self):
except (ValueError, TypeError):
film_grain = 0
else:
- film_grain = int(film_grain_text.split(" ")[0])
+ try:
+ film_grain = int(film_grain_text.split(" ")[0])
+ except (ValueError, TypeError):
+ film_grain = 0
settings = SVTAV1Settings(
speed=self.widgets.speed.currentText(),
diff --git a/fastflix/encoders/svt_av1_avif/main.py b/fastflix/encoders/svt_av1_avif/main.py
index a7099b15..fab1e246 100644
--- a/fastflix/encoders/svt_av1_avif/main.py
+++ b/fastflix/encoders/svt_av1_avif/main.py
@@ -18,6 +18,7 @@
enable_audio = False
enable_attachments = False
enable_concat = True
+enable_data = False
from fastflix.encoders.svt_av1_avif.command_builder import build # noqa: F401,E402
from fastflix.encoders.svt_av1_avif.settings_panel import SVT_AV1_AVIF as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/svt_av1_avif/settings_panel.py b/fastflix/encoders/svt_av1_avif/settings_panel.py
index 63501404..027d4d32 100644
--- a/fastflix/encoders/svt_av1_avif/settings_panel.py
+++ b/fastflix/encoders/svt_av1_avif/settings_panel.py
@@ -3,7 +3,7 @@
import logging
from box import Box
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import SettingPanel
from fastflix.language import t
@@ -81,6 +81,7 @@ def __init__(self, parent, main, app: FastFlixApp):
grid.addLayout(self.init_svtav1_params(), 5, 2, 1, 4)
grid.setRowStretch(8, 1)
+ custom_layout = self._add_custom()
guide_label = QtWidgets.QLabel(
link(
"https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md",
@@ -88,10 +89,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addLayout(self._add_custom(), 10, 0, 1, 6)
- grid.addWidget(guide_label, 11, 0, -1, 1)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/vaapi_h264/main.py b/fastflix/encoders/vaapi_h264/main.py
index f6d5ec69..a3adb282 100644
--- a/fastflix/encoders/vaapi_h264/main.py
+++ b/fastflix/encoders/vaapi_h264/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.vaapi_h264.command_builder import build # noqa: F401,E402
from fastflix.encoders.vaapi_h264.settings_panel import VAAPIH264 as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/vaapi_h264/settings_panel.py b/fastflix/encoders/vaapi_h264/settings_panel.py
index ffbd7b87..7db9b26f 100644
--- a/fastflix/encoders/vaapi_h264/settings_panel.py
+++ b/fastflix/encoders/vaapi_h264/settings_panel.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import logging
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import VAAPIPanel
from fastflix.language import t
@@ -61,14 +61,14 @@ def __init__(self, parent, main, app: FastFlixApp):
more_line.addLayout(self.init_low_power())
grid.addLayout(more_line, 5, 0, 1, 6)
- grid.addLayout(self._add_custom(disable_both_passes=True), 10, 0, 1, 6)
grid.setRowStretch(9, 1)
+ custom_layout = self._add_custom(disable_both_passes=True)
guide_label = QtWidgets.QLabel(
link("https://trac.ffmpeg.org/wiki/Hardware/VAAPI", t("VAAPI FFmpeg encoding"), app.fastflix.config.theme)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 6)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/vaapi_hevc/main.py b/fastflix/encoders/vaapi_hevc/main.py
index d0fce048..d792c6f3 100644
--- a/fastflix/encoders/vaapi_hevc/main.py
+++ b/fastflix/encoders/vaapi_hevc/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.vaapi_hevc.command_builder import build # noqa: F401,E402
from fastflix.encoders.vaapi_hevc.settings_panel import VAAPIHEVC as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/vaapi_hevc/settings_panel.py b/fastflix/encoders/vaapi_hevc/settings_panel.py
index 445c4631..4a4e25ab 100644
--- a/fastflix/encoders/vaapi_hevc/settings_panel.py
+++ b/fastflix/encoders/vaapi_hevc/settings_panel.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import logging
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import VAAPIPanel
from fastflix.language import t
@@ -61,14 +61,14 @@ def __init__(self, parent, main, app: FastFlixApp):
more_line.addLayout(self.init_low_power())
grid.addLayout(more_line, 5, 0, 1, 6)
- grid.addLayout(self._add_custom(disable_both_passes=True), 10, 0, 1, 6)
grid.setRowStretch(9, 1)
+ custom_layout = self._add_custom(disable_both_passes=True)
guide_label = QtWidgets.QLabel(
link("https://trac.ffmpeg.org/wiki/Hardware/VAAPI", t("VAAPI FFmpeg encoding"), app.fastflix.config.theme)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 6)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/vaapi_mpeg2/main.py b/fastflix/encoders/vaapi_mpeg2/main.py
index 98ac6d28..fac1725b 100644
--- a/fastflix/encoders/vaapi_mpeg2/main.py
+++ b/fastflix/encoders/vaapi_mpeg2/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.vaapi_mpeg2.command_builder import build # noqa: F401,E402
from fastflix.encoders.vaapi_mpeg2.settings_panel import VAAPIMPEG2 as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/vaapi_mpeg2/settings_panel.py b/fastflix/encoders/vaapi_mpeg2/settings_panel.py
index a9d73365..20d3dae2 100644
--- a/fastflix/encoders/vaapi_mpeg2/settings_panel.py
+++ b/fastflix/encoders/vaapi_mpeg2/settings_panel.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import logging
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import VAAPIPanel
from fastflix.language import t
@@ -55,14 +55,14 @@ def __init__(self, parent, main, app: FastFlixApp):
more_line.addLayout(self.init_low_power())
grid.addLayout(more_line, 5, 0, 1, 6)
- grid.addLayout(self._add_custom(disable_both_passes=True), 10, 0, 1, 6)
grid.setRowStretch(9, 1)
+ custom_layout = self._add_custom(disable_both_passes=True)
guide_label = QtWidgets.QLabel(
link("https://trac.ffmpeg.org/wiki/Hardware/VAAPI", t("VAAPI FFmpeg encoding"), app.fastflix.config.theme)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 6)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/vaapi_vp9/main.py b/fastflix/encoders/vaapi_vp9/main.py
index 484009f4..610b8678 100644
--- a/fastflix/encoders/vaapi_vp9/main.py
+++ b/fastflix/encoders/vaapi_vp9/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.vaapi_vp9.command_builder import build # noqa: F401,E402
from fastflix.encoders.vaapi_vp9.settings_panel import VAAPIVP9 as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/vaapi_vp9/settings_panel.py b/fastflix/encoders/vaapi_vp9/settings_panel.py
index e5d4f35e..5bd88686 100644
--- a/fastflix/encoders/vaapi_vp9/settings_panel.py
+++ b/fastflix/encoders/vaapi_vp9/settings_panel.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import logging
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import VAAPIPanel
from fastflix.language import t
@@ -55,14 +55,14 @@ def __init__(self, parent, main, app: FastFlixApp):
more_line.addLayout(self.init_low_power())
grid.addLayout(more_line, 5, 0, 1, 6)
- grid.addLayout(self._add_custom(disable_both_passes=True), 10, 0, 1, 6)
grid.setRowStretch(9, 1)
+ custom_layout = self._add_custom(disable_both_passes=True)
guide_label = QtWidgets.QLabel(
link("https://trac.ffmpeg.org/wiki/Hardware/VAAPI", t("VAAPI FFmpeg encoding"), app.fastflix.config.theme)
)
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 11, 0, 1, 6)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 10, 0, 1, 6)
self.setLayout(grid)
self.hide()
diff --git a/fastflix/encoders/vceencc_av1/command_builder.py b/fastflix/encoders/vceencc_av1/command_builder.py
index 0abd031c..7c2010fc 100644
--- a/fastflix/encoders/vceencc_av1/command_builder.py
+++ b/fastflix/encoders/vceencc_av1/command_builder.py
@@ -9,6 +9,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_auto_options,
rigaya_avformat_reader,
pa_builder,
@@ -72,6 +73,8 @@ def build(fastflix: FastFlix):
if video.video_settings.remove_metadata:
command.extend(["--video-metadata", "clear", "--metadata", "clear"])
+ elif video.video_settings.remove_hdr:
+ command.extend(["--video-metadata", "clear", "--metadata", "copy"])
else:
command.extend(["--video-metadata", "copy", "--metadata", "copy"])
@@ -112,9 +115,9 @@ def build(fastflix: FastFlix):
)
if fastflix.current_video.cll:
command.extend(["--max-cll", str(fastflix.current_video.cll)])
- if settings.copy_hdr10:
+ if settings.copy_hdr10 and not video.video_settings.remove_hdr:
command.extend(["--dhdr10-info", "copy"])
- if settings.copy_dv:
+ if settings.copy_dv and not video.video_settings.remove_hdr:
command.extend(["--dolby-vision-rpu", "copy"])
command.extend(["--dolby-vision-profile", "copy"])
@@ -150,6 +153,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(shlex.split(settings.extra))
diff --git a/fastflix/encoders/vceencc_av1/main.py b/fastflix/encoders/vceencc_av1/main.py
index b21a8d8a..0a778d30 100644
--- a/fastflix/encoders/vceencc_av1/main.py
+++ b/fastflix/encoders/vceencc_av1/main.py
@@ -15,6 +15,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from VCEEncC64.exe --check-encoders
diff --git a/fastflix/encoders/vceencc_av1/settings_panel.py b/fastflix/encoders/vceencc_av1/settings_panel.py
index 949798c6..776d117e 100644
--- a/fastflix/encoders/vceencc_av1/settings_panel.py
+++ b/fastflix/encoders/vceencc_av1/settings_panel.py
@@ -75,7 +75,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom VCEEncC options", disable_both_passes=True), 11, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom VCEEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
grid.addLayout(self.init_bitrate_mode(), 1, 0, 1, 2)
grid.addLayout(self.init_mv_precision(), 2, 0, 1, 2)
@@ -130,10 +130,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 12, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 11, 0, 1, 6)
self.setLayout(grid)
self.hide()
@@ -296,7 +295,7 @@ def update_video_encoder_settings(self):
pa_activity_type=self.widgets.pa_activity_type.currentText(),
pa_caq_strength=self.widgets.pa_caq_strength.currentText(),
pa_initqpsc=self.widgets.pa_initqpsc.currentIndex() or None,
- pa_lookahead=self.widgets.pa_initqpsc.currentIndex() or None,
+ pa_lookahead=self.widgets.pa_lookahead.currentIndex() or None,
pa_fskip_maxqp=int(self.widgets.pa_fskip_maxqp.text() or 0) or None,
pa_ltr=self.widgets.pa_ltr.isChecked(),
pa_paq=self.widgets.pa_paq.currentText(),
diff --git a/fastflix/encoders/vceencc_avc/command_builder.py b/fastflix/encoders/vceencc_avc/command_builder.py
index dc07c097..ed7f4470 100644
--- a/fastflix/encoders/vceencc_avc/command_builder.py
+++ b/fastflix/encoders/vceencc_avc/command_builder.py
@@ -9,6 +9,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_auto_options,
rigaya_avformat_reader,
pa_builder,
@@ -140,6 +141,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(shlex.split(settings.extra))
diff --git a/fastflix/encoders/vceencc_avc/main.py b/fastflix/encoders/vceencc_avc/main.py
index 42efa685..462250a7 100644
--- a/fastflix/encoders/vceencc_avc/main.py
+++ b/fastflix/encoders/vceencc_avc/main.py
@@ -15,6 +15,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from VCEEncC64.exe --check-encoders
diff --git a/fastflix/encoders/vceencc_avc/settings_panel.py b/fastflix/encoders/vceencc_avc/settings_panel.py
index 2f44001b..de142e1e 100644
--- a/fastflix/encoders/vceencc_avc/settings_panel.py
+++ b/fastflix/encoders/vceencc_avc/settings_panel.py
@@ -74,7 +74,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom VCEEncC options", disable_both_passes=True), 11, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom VCEEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
grid.addLayout(self.init_profile(), 1, 0, 1, 2)
grid.addLayout(self.init_mv_precision(), 2, 0, 1, 2)
@@ -121,10 +121,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 12, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 11, 0, 1, 6)
self.setLayout(grid)
self.hide()
@@ -292,7 +291,7 @@ def update_video_encoder_settings(self):
pa_activity_type=self.widgets.pa_activity_type.currentText(),
pa_caq_strength=self.widgets.pa_caq_strength.currentText(),
pa_initqpsc=self.widgets.pa_initqpsc.currentIndex() or None,
- pa_lookahead=self.widgets.pa_initqpsc.currentIndex() or None,
+ pa_lookahead=self.widgets.pa_lookahead.currentIndex() or None,
pa_fskip_maxqp=int(self.widgets.pa_fskip_maxqp.text() or 0) or None,
pa_ltr=self.widgets.pa_ltr.isChecked(),
pa_paq=self.widgets.pa_paq.currentText(),
diff --git a/fastflix/encoders/vceencc_hevc/command_builder.py b/fastflix/encoders/vceencc_hevc/command_builder.py
index 3751bd96..77e541a6 100644
--- a/fastflix/encoders/vceencc_hevc/command_builder.py
+++ b/fastflix/encoders/vceencc_hevc/command_builder.py
@@ -9,6 +9,7 @@
from fastflix.encoders.common.encc_helpers import (
build_subtitle,
build_audio,
+ build_data,
rigaya_avformat_reader,
rigaya_auto_options,
pa_builder,
@@ -72,6 +73,8 @@ def build(fastflix: FastFlix):
if video.video_settings.remove_metadata:
command.extend(["--video-metadata", "clear", "--metadata", "clear"])
+ elif video.video_settings.remove_hdr:
+ command.extend(["--video-metadata", "clear", "--metadata", "copy"])
else:
command.extend(["--video-metadata", "copy", "--metadata", "copy"])
@@ -113,9 +116,9 @@ def build(fastflix: FastFlix):
)
if fastflix.current_video.cll:
command.extend(["--max-cll", str(fastflix.current_video.cll)])
- if settings.copy_hdr10:
+ if settings.copy_hdr10 and not video.video_settings.remove_hdr:
command.extend(["--dhdr10-info", "copy"])
- if settings.copy_dv:
+ if settings.copy_dv and not video.video_settings.remove_hdr:
command.extend(["--dolby-vision-rpu", "copy"])
command.extend(["--dolby-vision-profile", "copy"])
@@ -151,6 +154,9 @@ def build(fastflix: FastFlix):
command.extend(build_audio(video.audio_tracks, video.streams.audio))
command.extend(build_subtitle(video.subtitle_tracks, video.streams.subtitle, video_height=video.height))
+ command.extend(
+ build_data(video.data_tracks, getattr(video.streams, "data", []), getattr(video.streams, "attachment", []))
+ )
if settings.extra:
command.extend(shlex.split(settings.extra))
diff --git a/fastflix/encoders/vceencc_hevc/main.py b/fastflix/encoders/vceencc_hevc/main.py
index 78206abd..a58ada4e 100644
--- a/fastflix/encoders/vceencc_hevc/main.py
+++ b/fastflix/encoders/vceencc_hevc/main.py
@@ -15,6 +15,7 @@
enable_subtitles = True
enable_audio = True
enable_attachments = False
+enable_data = True
original_audio_tracks_only = True
# Taken from VCEEncC64.exe --check-encoders
diff --git a/fastflix/encoders/vceencc_hevc/settings_panel.py b/fastflix/encoders/vceencc_hevc/settings_panel.py
index 70327315..eb287ccb 100644
--- a/fastflix/encoders/vceencc_hevc/settings_panel.py
+++ b/fastflix/encoders/vceencc_hevc/settings_panel.py
@@ -75,7 +75,7 @@ def __init__(self, parent, main, app: FastFlixApp):
self.updating_settings = False
grid.addLayout(self.init_modes(), 0, 2, 4, 4)
- grid.addLayout(self._add_custom(title="Custom VCEEncC options", disable_both_passes=True), 11, 0, 1, 6)
+ custom_layout = self._add_custom(title="Custom VCEEncC options", disable_both_passes=True)
grid.addLayout(self.init_preset(), 0, 0, 1, 2)
grid.addLayout(self.init_tier(), 1, 0, 1, 2)
grid.addLayout(self.init_mv_precision(), 2, 0, 1, 2)
@@ -128,10 +128,9 @@ def __init__(self, parent, main, app: FastFlixApp):
app.fastflix.config.theme,
)
)
-
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 12, 0, 1, 4)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 11, 0, 1, 6)
self.setLayout(grid)
self.hide()
@@ -290,7 +289,7 @@ def update_video_encoder_settings(self):
pa_activity_type=self.widgets.pa_activity_type.currentText(),
pa_caq_strength=self.widgets.pa_caq_strength.currentText(),
pa_initqpsc=self.widgets.pa_initqpsc.currentIndex() or None,
- pa_lookahead=self.widgets.pa_initqpsc.currentIndex() or None,
+ pa_lookahead=self.widgets.pa_lookahead.currentIndex() or None,
pa_fskip_maxqp=int(self.widgets.pa_fskip_maxqp.text() or 0) or None,
pa_ltr=self.widgets.pa_ltr.isChecked(),
pa_paq=self.widgets.pa_paq.currentText(),
diff --git a/fastflix/encoders/vp9/main.py b/fastflix/encoders/vp9/main.py
index 03c123aa..0efb7c47 100644
--- a/fastflix/encoders/vp9/main.py
+++ b/fastflix/encoders/vp9/main.py
@@ -18,6 +18,7 @@
enable_audio = True
enable_attachments = False
enable_concat = True
+enable_data = True
from fastflix.encoders.vp9.command_builder import build # noqa: F401,E402
from fastflix.encoders.vp9.settings_panel import VP9 as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/vp9/settings_panel.py b/fastflix/encoders/vp9/settings_panel.py
index fb289dad..2a5321ad 100644
--- a/fastflix/encoders/vp9/settings_panel.py
+++ b/fastflix/encoders/vp9/settings_panel.py
@@ -2,7 +2,7 @@
import logging
from box import Box
-from PySide6 import QtCore, QtWidgets
+from PySide6 import QtWidgets
from fastflix.encoders.common.setting_panel import SettingPanel
from fastflix.language import t
@@ -95,7 +95,7 @@ def __init__(self, parent, main, app: FastFlixApp):
grid.addLayout(self.init_sharpness(), 9, 0, 1, 2)
grid.setRowStretch(10, 1)
- grid.addLayout(self._add_custom(), 11, 0, 1, 6)
+ custom_layout = self._add_custom()
link_1 = link(
"https://trac.ffmpeg.org/wiki/Encode/VP9", t("FFMPEG VP9 Encoding Guide"), app.fastflix.config.theme
@@ -107,9 +107,9 @@ def __init__(self, parent, main, app: FastFlixApp):
)
guide_label = QtWidgets.QLabel(f"{link_1} | {link_2}")
- guide_label.setAlignment(QtCore.Qt.AlignBottom)
guide_label.setOpenExternalLinks(True)
- grid.addWidget(guide_label, 12, 0, 1, 6)
+ custom_layout.addWidget(guide_label)
+ grid.addLayout(custom_layout, 11, 0, 1, 6)
self.setLayout(grid)
self.hide()
@@ -299,6 +299,51 @@ def update_video_encoder_settings(self):
settings.bitrate = q_value if encode_type == "bitrate" else None
self.app.fastflix.current_video.video_settings.video_encoder_settings = settings
+ def _set_default_combo_from_value(self, widget_name, value, options):
+ """Set a combo box from model value where -1 means 'Default' and others match by first token."""
+ if value == -1:
+ self.widgets[widget_name].setCurrentIndex(0) # "Default"
+ return
+ text = str(value)
+ for i, opt in enumerate(options):
+ if opt.split()[0] == text:
+ self.widgets[widget_name].setCurrentIndex(i)
+ return
+ self.widgets[widget_name].setCurrentIndex(0)
+
+ def update_profile(self):
+ auto_alt_ref_opts = ["Default", "0 (disabled)", "1", "2", "3", "4", "5", "6"]
+ lag_opts = ["Default", "0", "10", "16", "20", "25", "30", "40", "50"]
+ aq_opts = ["Default", "0 (none)", "1 (variance)", "2 (complexity)", "3 (cyclic)", "4 (equator360)"]
+ sharpness_opts = ["Default", "0", "1", "2", "3", "4", "5", "6", "7"]
+
+ self._set_default_combo_from_value(
+ "auto_alt_ref", self.app.fastflix.config.encoder_opt(self.profile_name, "auto_alt_ref"), auto_alt_ref_opts
+ )
+ self._set_default_combo_from_value(
+ "lag_in_frames", self.app.fastflix.config.encoder_opt(self.profile_name, "lag_in_frames"), lag_opts
+ )
+ self._set_default_combo_from_value(
+ "aq_mode", self.app.fastflix.config.encoder_opt(self.profile_name, "aq_mode"), aq_opts
+ )
+ self._set_default_combo_from_value(
+ "sharpness", self.app.fastflix.config.encoder_opt(self.profile_name, "sharpness"), sharpness_opts
+ )
+ super().update_profile()
+
+ def reload(self):
+ super().reload()
+ settings = self.app.fastflix.current_video.video_settings.video_encoder_settings
+ auto_alt_ref_opts = ["Default", "0 (disabled)", "1", "2", "3", "4", "5", "6"]
+ lag_opts = ["Default", "0", "10", "16", "20", "25", "30", "40", "50"]
+ aq_opts = ["Default", "0 (none)", "1 (variance)", "2 (complexity)", "3 (cyclic)", "4 (equator360)"]
+ sharpness_opts = ["Default", "0", "1", "2", "3", "4", "5", "6", "7"]
+
+ self._set_default_combo_from_value("auto_alt_ref", settings.auto_alt_ref, auto_alt_ref_opts)
+ self._set_default_combo_from_value("lag_in_frames", settings.lag_in_frames, lag_opts)
+ self._set_default_combo_from_value("aq_mode", settings.aq_mode, aq_opts)
+ self._set_default_combo_from_value("sharpness", settings.sharpness, sharpness_opts)
+
def set_mode(self, x):
self.mode = x.text()
self.main.build_commands()
diff --git a/fastflix/encoders/vvc/main.py b/fastflix/encoders/vvc/main.py
index 4efc9761..15c2934f 100644
--- a/fastflix/encoders/vvc/main.py
+++ b/fastflix/encoders/vvc/main.py
@@ -17,6 +17,7 @@
enable_audio = True
enable_attachments = True
enable_concat = True
+enable_data = True
from fastflix.encoders.vvc.command_builder import build # noqa: F401,E402
from fastflix.encoders.vvc.settings_panel import VVC as settings_panel # noqa: F401,E402
diff --git a/fastflix/encoders/vvc/settings_panel.py b/fastflix/encoders/vvc/settings_panel.py
index 120f71e2..7a18b9f9 100644
--- a/fastflix/encoders/vvc/settings_panel.py
+++ b/fastflix/encoders/vvc/settings_panel.py
@@ -297,6 +297,43 @@ def update_video_encoder_settings(self):
settings.bitrate = q_value if encode_type == "bitrate" else None
self.app.fastflix.current_video.video_settings.video_encoder_settings = settings
+ def _set_period_from_value(self, value):
+ """Set period combo box from model value (None=Auto, else int)."""
+ if value is None:
+ self.widgets.period.setCurrentIndex(0) # "Auto"
+ return
+ text = str(value)
+ for i in range(self.widgets.period.count()):
+ if self.widgets.period.itemText(i) == text:
+ self.widgets.period.setCurrentIndex(i)
+ return
+ self.widgets.period.setCurrentIndex(0)
+
+ def _set_threads_from_value(self, value):
+ """Set threads combo box from model value (0=Auto, else int)."""
+ if value == 0:
+ self.widgets.threads.setCurrentIndex(0) # "Auto"
+ return
+ text = str(value)
+ for i in range(self.widgets.threads.count()):
+ if self.widgets.threads.itemText(i) == text:
+ self.widgets.threads.setCurrentIndex(i)
+ return
+ self.widgets.threads.setCurrentIndex(0)
+
+ def update_profile(self):
+ period = self.app.fastflix.config.encoder_opt(self.profile_name, "period")
+ self._set_period_from_value(period)
+ threads = self.app.fastflix.config.encoder_opt(self.profile_name, "threads")
+ self._set_threads_from_value(threads)
+ super().update_profile()
+
+ def reload(self):
+ super().reload()
+ settings = self.app.fastflix.current_video.video_settings.video_encoder_settings
+ self._set_period_from_value(settings.period)
+ self._set_threads_from_value(settings.threads)
+
def set_mode(self, x):
self.mode = x.text()
self.main.build_commands()
diff --git a/fastflix/encoders/webp/main.py b/fastflix/encoders/webp/main.py
index 364afd15..d3674e33 100644
--- a/fastflix/encoders/webp/main.py
+++ b/fastflix/encoders/webp/main.py
@@ -17,6 +17,7 @@
enable_audio = False
enable_attachments = False
enable_concat = True
+enable_data = False
audio_formats = []
diff --git a/fastflix/models/config.py b/fastflix/models/config.py
index 93dd8569..d8a2ac3a 100644
--- a/fastflix/models/config.py
+++ b/fastflix/models/config.py
@@ -265,6 +265,7 @@ class Config(BaseModel):
sane_audio_selection: list = Field(
default_factory=lambda: [
"aac",
+ "aac_mf",
"ac3",
"alac",
"dca",
@@ -302,6 +303,8 @@ class Config(BaseModel):
pgs_ocr_language: str = "eng"
use_keyframes_for_preview: bool = True
+ terms_accepted: bool = False
+ auto_detect_subtitles: bool = True
@property
def pgs_ocr_available(self) -> bool:
diff --git a/fastflix/models/encode.py b/fastflix/models/encode.py
index fdb97d2d..9e3c3d6b 100644
--- a/fastflix/models/encode.py
+++ b/fastflix/models/encode.py
@@ -33,6 +33,7 @@ class SubtitleTrack(BaseModel):
disposition: Optional[str] = ""
burn_in: bool = False
language: str = ""
+ title: str = ""
subtitle_type: str = ""
dispositions: dict = Field(default_factory=dict)
enabled: bool = True
@@ -53,6 +54,21 @@ class AttachmentTrack(BaseModel):
filename: Optional[str] = None
+class DataTrack(BaseModel):
+ index: int # Source stream index from FFprobe
+ outdex: int # Output stream index
+ enabled: bool = True # Whether to include in output
+ codec_name: str = "" # e.g., "bin_data", "tmcd", "ttf"
+ codec_type: str = "" # "data" or "attachment"
+ title: str = "" # Track title from tags
+ mimetype: str = "" # MIME type (attachments only)
+ filename: str = "" # Filename (attachments only)
+ friendly_info: str = "" # Display string for UI
+ raw_info: Optional[Union[dict, Box]] = None
+
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
class EncoderSettings(BaseModel):
max_muxing_queue_size: str = "1024"
pix_fmt: str = "yuv420p10le"
diff --git a/fastflix/models/video.py b/fastflix/models/video.py
index ebfd175a..10d3c88c 100644
--- a/fastflix/models/video.py
+++ b/fastflix/models/video.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import uuid
+from datetime import datetime
from pathlib import Path
from typing import List, Optional, Union, Tuple
@@ -11,6 +12,7 @@
AttachmentTrack,
AudioTrack,
CopySettings,
+ DataTrack,
GIFSettings,
GifskiSettings,
FFmpegNVENCSettings,
@@ -112,6 +114,7 @@ class VideoSettings(BaseModel):
contrast: Optional[str] = None
saturation: Optional[str] = None
copy_data: bool = False
+ template_generated_name: str = ""
video_encoder_settings: Optional[
Union[
x265Settings,
@@ -180,6 +183,7 @@ class Status(BaseModel):
cancelled: bool = False
subtitle_fixed: bool = False
current_command: int = 0
+ encode_started_at: Optional[datetime] = None
@property
def ready(self) -> bool:
@@ -193,6 +197,7 @@ def clear(self):
self.cancelled = False
self.subtitle_fixed = False
self.current_command = 0
+ self.encode_started_at = None
class Video(BaseModel):
@@ -212,6 +217,7 @@ class Video(BaseModel):
audio_tracks: list[AudioTrack] = Field(default_factory=list)
subtitle_tracks: list[SubtitleTrack] = Field(default_factory=list)
attachment_tracks: list[AttachmentTrack] = Field(default_factory=list)
+ data_tracks: list[DataTrack] = Field(default_factory=list)
status: Status = Field(default_factory=Status)
uuid: str = Field(default_factory=lambda: str(uuid.uuid4()))
@@ -295,6 +301,13 @@ def average_frame_rate(self):
return ""
return stream.get("avg_frame_rate", "")
+ @property
+ def sar(self):
+ stream = self.current_video_stream
+ if not stream:
+ return ""
+ return stream.get("sample_aspect_ratio", "1:1")
+
@property
def scale(self):
if self.video_settings.resolution_method == "auto":
diff --git a/fastflix/naming.py b/fastflix/naming.py
new file mode 100644
index 00000000..f4d58506
--- /dev/null
+++ b/fastflix/naming.py
@@ -0,0 +1,408 @@
+# -*- coding: utf-8 -*-
+import logging
+import re
+from dataclasses import dataclass
+from datetime import datetime
+from enum import Enum
+from pathlib import Path
+from typing import Optional
+
+from pathvalidate import sanitize_filename
+
+logger = logging.getLogger("fastflix")
+
+MAX_FILENAME_PATH_LENGTH = 250
+
+
+class VariablePhase(Enum):
+ PRE_ENCODE = "pre_encode"
+ POST_ENCODE = "post_encode"
+
+
+@dataclass
+class TemplateVariable:
+ name: str
+ description: str
+ phase: VariablePhase
+ example: str
+ placeholder: str = "" # For post-encode vars, filesystem-safe placeholder
+
+
+PRE_ENCODE_VARIABLES = [
+ TemplateVariable("source", "Source filename stem", VariablePhase.PRE_ENCODE, "MyMovie"),
+ TemplateVariable("datetime", "ISO date+time (queue time)", VariablePhase.PRE_ENCODE, "2026-02-13T14-30-00"),
+ TemplateVariable("date", "Date only (queue time)", VariablePhase.PRE_ENCODE, "2026-02-13"),
+ TemplateVariable("time", "Time only (queue time)", VariablePhase.PRE_ENCODE, "14-30-00"),
+ TemplateVariable("rand_4", "4-char random hex", VariablePhase.PRE_ENCODE, "a3f1"),
+ TemplateVariable("rand_8", "8-char random hex", VariablePhase.PRE_ENCODE, "a3f1b2c4"),
+ TemplateVariable("encoder", "Short encoder name", VariablePhase.PRE_ENCODE, "x265"),
+ TemplateVariable("codec", "Full codec name", VariablePhase.PRE_ENCODE, "HEVC (x265)"),
+ TemplateVariable("preset", "Encoder preset", VariablePhase.PRE_ENCODE, "medium"),
+ TemplateVariable("crf", "CRF/QP value", VariablePhase.PRE_ENCODE, "22"),
+ TemplateVariable("bitrate", "Target bitrate", VariablePhase.PRE_ENCODE, "5000k"),
+ TemplateVariable("pix_fmt", "Pixel format", VariablePhase.PRE_ENCODE, "yuv420p10le"),
+ TemplateVariable("resolution", "Output resolution", VariablePhase.PRE_ENCODE, "1920x1080"),
+ TemplateVariable("source_resolution", "Source resolution", VariablePhase.PRE_ENCODE, "3840x2160"),
+ TemplateVariable("source_duration", "Source duration", VariablePhase.PRE_ENCODE, "01-23-45"),
+ TemplateVariable("source_fps", "Source frame rate", VariablePhase.PRE_ENCODE, "23.976"),
+ TemplateVariable("source_codec", "Source video codec", VariablePhase.PRE_ENCODE, "hevc"),
+ TemplateVariable("source_bitrate", "Source overall bitrate", VariablePhase.PRE_ENCODE, "45000k"),
+ TemplateVariable("audio_codec", "First audio track codec", VariablePhase.PRE_ENCODE, "aac"),
+ TemplateVariable("audio_channels", "First audio channels", VariablePhase.PRE_ENCODE, "5.1"),
+ TemplateVariable("color_space", "Color space", VariablePhase.PRE_ENCODE, "bt2020nc"),
+ TemplateVariable("color_primaries", "Color primaries", VariablePhase.PRE_ENCODE, "bt2020"),
+ TemplateVariable("hdr", "HDR type or SDR", VariablePhase.PRE_ENCODE, "HDR10"),
+]
+
+POST_ENCODE_VARIABLES = [
+ TemplateVariable("encode_time", "Encode duration", VariablePhase.POST_ENCODE, "00-15-32", "FFETIME"),
+ TemplateVariable("encode_end", "Finish timestamp", VariablePhase.POST_ENCODE, "2026-02-13T14-45-32", "FFEEND"),
+ TemplateVariable("filesize", "Human-readable size", VariablePhase.POST_ENCODE, "1.5GB", "FFFSIZE"),
+ TemplateVariable("filesize_mb", "Size in MB", VariablePhase.POST_ENCODE, "1536", "FFFSMB"),
+ TemplateVariable("video_bitrate", "Actual video bitrate", VariablePhase.POST_ENCODE, "4523k", "FFVIDBIT"),
+ TemplateVariable("audio_bitrate", "Actual audio bitrate", VariablePhase.POST_ENCODE, "320k", "FFAUDBIT"),
+ TemplateVariable("overall_bitrate", "Overall bitrate", VariablePhase.POST_ENCODE, "4843k", "FFALLBIT"),
+]
+
+ALL_VARIABLES = PRE_ENCODE_VARIABLES + POST_ENCODE_VARIABLES
+
+_VARIABLE_MAP = {v.name: v for v in ALL_VARIABLES}
+_POST_ENCODE_PLACEHOLDERS = {v.placeholder: v for v in POST_ENCODE_VARIABLES}
+
+
+def safe_format(template: str, variables: dict) -> str:
+ """Replace {var} patterns in template with values from variables dict.
+ Unknown variables are left as-is."""
+
+ def replacer(match):
+ key = match.group(1)
+ if key in variables:
+ return str(variables[key])
+ return match.group(0)
+
+ return re.sub(r"\{(\w+)\}", replacer, template)
+
+
+def _extract_short_encoder_name(full_name: str) -> str:
+ """Extract short encoder name from full codec name like 'HEVC (x265)' -> 'x265'."""
+ m = re.search(r"\((.+)\)", full_name)
+ if m:
+ return m.group(1)
+ return full_name.replace(" ", "-")
+
+
+def _format_duration_hms(seconds: float) -> str:
+ """Format seconds as HH-MM-SS (filesystem-safe)."""
+ hours = int(seconds // 3600)
+ minutes = int((seconds % 3600) // 60)
+ secs = int(seconds % 60)
+ return f"{hours:02d}-{minutes:02d}-{secs:02d}"
+
+
+def _format_bitrate(bps: float) -> str:
+ """Format bits per second as human-readable like '4523k'."""
+ kbps = int(bps / 1000)
+ if kbps >= 1000:
+ return f"{kbps}k"
+ return f"{kbps}k"
+
+
+def _format_filesize(size_bytes: int) -> str:
+ """Format file size as human-readable."""
+ if size_bytes >= 1024 * 1024 * 1024:
+ return f"{size_bytes / (1024 * 1024 * 1024):.1f}GB"
+ if size_bytes >= 1024 * 1024:
+ return f"{size_bytes / (1024 * 1024):.1f}MB"
+ return f"{size_bytes / 1024:.1f}KB"
+
+
+def _get_channel_layout(channels: int) -> str:
+ """Convert channel count to common layout name."""
+ layouts = {1: "mono", 2: "stereo", 6: "5.1", 8: "7.1"}
+ return layouts.get(channels, str(channels))
+
+
+def _get_hdr_type(video) -> str:
+ """Determine HDR type from video metadata."""
+ if hasattr(video, "hdr10_plus") and video.hdr10_plus:
+ return "HDR10+"
+ if hasattr(video, "hdr10_streams") and video.hdr10_streams:
+ return "HDR10"
+ color_transfer = ""
+ if hasattr(video, "color_transfer"):
+ color_transfer = video.color_transfer
+ if color_transfer == "smpte2084":
+ return "HDR10"
+ if color_transfer == "arib-std-b67":
+ return "HLG"
+ return "SDR"
+
+
+def _safe_value(value: str) -> str:
+ """Sanitize a single variable value for use in filenames."""
+ return str(sanitize_filename(str(value), replacement_text="-"))
+
+
+def _eval_frame_rate(fr_string: str) -> str:
+ """Evaluate frame rate fraction like '24000/1001' to '23.976'."""
+ if not fr_string:
+ return "N-A"
+ if "/" in fr_string:
+ try:
+ num, den = fr_string.split("/")
+ val = float(num) / float(den)
+ return f"{val:.3f}"
+ except (ValueError, ZeroDivisionError):
+ return fr_string
+ return fr_string
+
+
+def resolve_pre_encode_variables(
+ template: str,
+ source_path: Path,
+ video=None,
+ encoder_settings=None,
+ video_settings=None,
+) -> str:
+ """Resolve all pre-encode variables in a template string.
+
+ Inserts placeholders for any post-encode variables present in the template.
+ """
+ import secrets
+
+ now = datetime.now()
+ iso_datetime = now.strftime("%Y-%m-%dT%H-%M-%S")
+ date_only = now.strftime("%Y-%m-%d")
+ time_only = now.strftime("%H-%M-%S")
+
+ variables = {
+ "source": source_path.stem,
+ "datetime": iso_datetime,
+ "date": date_only,
+ "time": time_only,
+ "rand_4": secrets.token_hex(2),
+ "rand_8": secrets.token_hex(4),
+ }
+
+ # Encoder-specific variables
+ if encoder_settings is not None:
+ full_name = getattr(encoder_settings, "name", "N-A")
+ variables["codec"] = full_name
+ variables["encoder"] = _extract_short_encoder_name(full_name)
+ variables["preset"] = str(getattr(encoder_settings, "preset", "N-A"))
+
+ crf_val = getattr(encoder_settings, "crf", None)
+ if crf_val is None:
+ crf_val = getattr(encoder_settings, "qp", None)
+ variables["crf"] = str(crf_val) if crf_val is not None else "N-A"
+
+ bitrate_val = getattr(encoder_settings, "bitrate", None)
+ variables["bitrate"] = str(bitrate_val) if bitrate_val else "N-A"
+
+ variables["pix_fmt"] = str(getattr(encoder_settings, "pix_fmt", "N-A"))
+ else:
+ for key in ("encoder", "codec", "preset", "crf", "bitrate", "pix_fmt"):
+ variables.setdefault(key, "N-A")
+
+ # Source and video variables
+ if video is not None:
+ variables["source_resolution"] = f"{video.width}x{video.height}"
+ variables["source_duration"] = _format_duration_hms(video.duration) if video.duration else "N-A"
+ variables["source_fps"] = _eval_frame_rate(video.frame_rate)
+ variables["color_space"] = video.color_space or "N-A"
+ variables["color_primaries"] = video.color_primaries or "N-A"
+ variables["hdr"] = _get_hdr_type(video)
+
+ # Source codec from streams
+ if video.streams and video.streams.video:
+ stream = video.current_video_stream
+ if stream:
+ variables["source_codec"] = stream.get("codec_name", "N-A")
+ else:
+ variables["source_codec"] = "N-A"
+ else:
+ variables["source_codec"] = "N-A"
+
+ # Source bitrate from format
+ if video.format and video.format.get("bit_rate"):
+ try:
+ bps = int(video.format.bit_rate)
+ variables["source_bitrate"] = _format_bitrate(bps)
+ except (ValueError, TypeError):
+ variables["source_bitrate"] = "N-A"
+ else:
+ variables["source_bitrate"] = "N-A"
+
+ # Audio info from first audio track
+ if video.streams and video.streams.get("audio"):
+ audio = video.streams.audio[0]
+ variables["audio_codec"] = audio.get("codec_name", "N-A")
+ channels = audio.get("channels", 0)
+ variables["audio_channels"] = _get_channel_layout(channels)
+ else:
+ variables["audio_codec"] = "N-A"
+ variables["audio_channels"] = "N-A"
+ else:
+ for key in (
+ "source_resolution",
+ "source_duration",
+ "source_fps",
+ "source_codec",
+ "source_bitrate",
+ "audio_codec",
+ "audio_channels",
+ "color_space",
+ "color_primaries",
+ "hdr",
+ ):
+ variables.setdefault(key, "N-A")
+
+ # Output resolution
+ if video_settings is not None and video is not None:
+ scale = video.scale
+ if scale:
+ variables["resolution"] = scale.replace(":", "x").replace("-8", "auto")
+ else:
+ variables["resolution"] = f"{video.width}x{video.height}"
+ else:
+ variables["resolution"] = variables.get("source_resolution", "N-A")
+
+ # Sanitize all values for filesystem safety
+ variables = {k: _safe_value(v) for k, v in variables.items()}
+
+ # Insert placeholders for post-encode variables
+ for var in POST_ENCODE_VARIABLES:
+ variables[var.name] = var.placeholder
+
+ return safe_format(template, variables)
+
+
+def resolve_post_encode_variables(
+ filename: str,
+ output_path: Path,
+ probe_data,
+ encode_start: Optional[datetime] = None,
+ encode_end: Optional[datetime] = None,
+) -> str:
+ """Replace post-encode placeholders in a filename with actual values from probe data."""
+ replacements = {}
+
+ # Encode time
+ if encode_start and encode_end:
+ elapsed = (encode_end - encode_start).total_seconds()
+ replacements["FFETIME"] = _safe_value(_format_duration_hms(elapsed))
+ else:
+ replacements["FFETIME"] = "N-A"
+
+ # Encode end timestamp
+ if encode_end:
+ replacements["FFEEND"] = _safe_value(encode_end.strftime("%Y-%m-%dT%H-%M-%S"))
+ else:
+ replacements["FFEEND"] = _safe_value(datetime.now().strftime("%Y-%m-%dT%H-%M-%S"))
+
+ # File size
+ try:
+ size_bytes = output_path.stat().st_size
+ replacements["FFFSIZE"] = _safe_value(_format_filesize(size_bytes))
+ replacements["FFFSMB"] = str(int(size_bytes / (1024 * 1024)))
+ except OSError:
+ replacements["FFFSIZE"] = "N-A"
+ replacements["FFFSMB"] = "N-A"
+
+ # Bitrate info from probe data
+ if probe_data:
+ # Overall bitrate
+ overall_br = None
+ if hasattr(probe_data, "format") and probe_data.format:
+ overall_br = probe_data.format.get("bit_rate")
+ if overall_br:
+ try:
+ replacements["FFALLBIT"] = _format_bitrate(int(overall_br))
+ except (ValueError, TypeError):
+ replacements["FFALLBIT"] = "N-A"
+ else:
+ replacements["FFALLBIT"] = "N-A"
+
+ # Per-stream bitrates
+ video_br = "N-A"
+ audio_br = "N-A"
+ if hasattr(probe_data, "streams"):
+ for stream in probe_data.streams:
+ codec_type = stream.get("codec_type", "")
+ br = stream.get("bit_rate")
+ if codec_type == "video" and br and video_br == "N-A":
+ try:
+ video_br = _format_bitrate(int(br))
+ except (ValueError, TypeError):
+ pass
+ elif codec_type == "audio" and br and audio_br == "N-A":
+ try:
+ audio_br = _format_bitrate(int(br))
+ except (ValueError, TypeError):
+ pass
+ replacements["FFVIDBIT"] = video_br
+ replacements["FFAUDBIT"] = audio_br
+ else:
+ for key in ("FFALLBIT", "FFVIDBIT", "FFAUDBIT"):
+ replacements[key] = "N-A"
+
+ # Do the replacements
+ result = filename
+ for placeholder, value in replacements.items():
+ result = result.replace(placeholder, value)
+
+ return result
+
+
+def has_post_encode_placeholders(filename: str) -> bool:
+ """Check if any post-encode placeholders exist in the filename."""
+ return any(var.placeholder in filename for var in POST_ENCODE_VARIABLES)
+
+
+def generate_preview(template: str) -> str:
+ """Generate a preview of the template using example values."""
+ variables = {}
+ for var in ALL_VARIABLES:
+ variables[var.name] = var.example
+ return safe_format(template, variables)
+
+
+def validate_template(template: str) -> tuple[bool, str]:
+ """Validate a template string. Returns (is_valid, message)."""
+ if not template.strip():
+ return False, "Template cannot be empty"
+
+ unknown = []
+ for match in re.finditer(r"\{(\w+)\}", template):
+ name = match.group(1)
+ if name not in _VARIABLE_MAP:
+ unknown.append(name)
+
+ if unknown:
+ return False, f"Unknown variable(s): {', '.join('{' + n + '}' for n in unknown)}"
+
+ return True, "Valid template"
+
+
+def truncate_filename(name: str, directory: str, extension: str) -> tuple[str, bool]:
+ """Truncate the filename stem so the full path stays within MAX_FILENAME_PATH_LENGTH.
+
+ Returns (possibly_truncated_name, was_truncated).
+ The directory and extension lengths are accounted for.
+ """
+ # +1 for the path separator between directory and filename
+ overhead = len(directory) + 1 + len(extension)
+ max_name_len = MAX_FILENAME_PATH_LENGTH - overhead
+
+ if max_name_len < 10:
+ # Directory path is extremely long, allow at least 10 chars for the name
+ max_name_len = 10
+
+ if len(name) <= max_name_len:
+ return name, False
+
+ truncated = name[:max_name_len]
+ logger.info(
+ f"Output filename truncated from {len(name)} to {max_name_len} chars (path limit {MAX_FILENAME_PATH_LENGTH})"
+ )
+ return truncated, True
diff --git a/fastflix/shared.py b/fastflix/shared.py
index 8afaa7a9..56481095 100644
--- a/fastflix/shared.py
+++ b/fastflix/shared.py
@@ -43,9 +43,62 @@
)
+def _measure_text_width(metrics, text):
+ """Measure the widest line in potentially multi-line text."""
+ lines = text.split("\n")
+ return max(metrics.horizontalAdvance(line) for line in lines)
+
+
+def shrink_text_to_fit(widget, padding=10, min_point_size=6):
+ """Shrink a widget's font until its text fits within its fixed width.
+
+ Works with QLabel, QPushButton, QCheckBox, and similar widgets.
+ Call after setting the widget's text and fixed width.
+
+ Uses widget-level stylesheet to set font-size, because the global parent
+ stylesheet (QWidget { font-size: Xpt; }) overrides widget.setFont() calls.
+ A widget's own stylesheet takes precedence over inherited stylesheets.
+ """
+ import re
+
+ from fastflix.ui_constants import FONTS
+ from fastflix.ui_scale import scaler
+
+ width = widget.maximumWidth() if widget.maximumWidth() < 16777215 else widget.width()
+ if width <= 0:
+ return
+ text = widget.text()
+ if not text:
+ return
+
+ # Start from the app's actual styled font size (matches the global stylesheet).
+ app_font_size_pt = max(6, round(scaler.scale_font(FONTS.LARGE) * 0.75))
+ font = widget.font()
+ font.setPointSizeF(app_font_size_pt)
+
+ metrics = QtGui.QFontMetrics(font)
+ available = width - padding
+ if _measure_text_width(metrics, text) <= available:
+ return
+
+ size = float(app_font_size_pt)
+ while size > min_point_size:
+ size -= 0.5
+ font.setPointSizeF(size)
+ metrics = QtGui.QFontMetrics(font)
+ if _measure_text_width(metrics, text) <= available:
+ break
+
+ # Apply via widget stylesheet to override the global parent stylesheet.
+ existing = widget.styleSheet() or ""
+ existing = re.sub(r"font-size:\s*[^;]+;?\s*", "", existing).strip()
+ sep = " " if existing else ""
+ widget.setStyleSheet(f"{existing}{sep}font-size: {size}pt;")
+
+
class MyMessageBox(QtWidgets.QMessageBox):
- def __init__(self):
- QtWidgets.QMessageBox.__init__(self)
+ def __init__(self, parent=None):
+ QtWidgets.QMessageBox.__init__(self, parent)
self.setSizeGripEnabled(True)
def event(self, e):
@@ -68,11 +121,9 @@ def event(self, e):
return result
-def message(msg, title=None):
- sm = QtWidgets.QMessageBox()
- sm.setStyleSheet("font-size: 14px")
+def message(msg, title=None, parent=None):
+ sm = QtWidgets.QMessageBox(parent)
sm.setText(msg)
- # Removed WindowStaysOnTopHint to allow minimizing dialog (#687)
if title:
sm.setWindowTitle(title)
sm.setStandardButtons(QtWidgets.QMessageBox.Ok)
@@ -80,12 +131,10 @@ def message(msg, title=None):
sm.exec_()
-def error_message(msg, details=None, traceback=False, title=None):
- em = MyMessageBox()
- em.setStyleSheet("font-size: 14px")
+def error_message(msg, details=None, traceback=False, title=None, parent=None):
+ em = MyMessageBox(parent)
em.setText(msg)
em.setWindowIcon(QtGui.QIcon(my_data))
- # Removed WindowStaysOnTopHint to allow minimizing dialog (#687)
if title:
em.setWindowTitle(title)
if details:
@@ -98,14 +147,12 @@ def error_message(msg, details=None, traceback=False, title=None):
em.exec_()
-def yes_no_message(msg, title=None, yes_text=t("Yes"), no_text=t("No"), yes_action=None, no_action=None):
- sm = QtWidgets.QMessageBox()
- sm.setStyleSheet("font-size: 14px")
+def yes_no_message(msg, title=None, yes_text=t("Yes"), no_text=t("No"), yes_action=None, no_action=None, parent=None):
+ sm = QtWidgets.QMessageBox(parent)
sm.setWindowTitle(t(title))
sm.setText(msg)
sm.addButton(yes_text, QtWidgets.QMessageBox.YesRole)
sm.addButton(no_text, QtWidgets.QMessageBox.NoRole)
- # Removed WindowStaysOnTopHint to allow minimizing dialog (#687)
sm.exec_()
if sm.clickedButton().text() == yes_text:
if yes_action:
diff --git a/fastflix/ui_constants.py b/fastflix/ui_constants.py
index 689526e9..4d331c62 100644
--- a/fastflix/ui_constants.py
+++ b/fastflix/ui_constants.py
@@ -17,7 +17,7 @@ class BaseWidths:
PROFILE_BOX: int = 190
ENCODER_MIN: int = 165
CROP_BOX_MIN: int = 280
- SOURCE_LABEL: int = 65
+ SOURCE_LABEL: int = 90
RESOLUTION_CUSTOM: int = 115
FLIP_DROPDOWN: int = 120
ROTATE_DROPDOWN: int = 130
@@ -41,16 +41,17 @@ class BaseHeights:
"""Base height values (~25% smaller than original for better default scaling)."""
TOP_BAR_BUTTON: int = 38
- PATH_WIDGET: int = 20
- COMBO_BOX: int = 22
+ PATH_WIDGET: int = 28
+ COMBO_BOX: int = 28
PANEL_ITEM: int = 62
SCROLL_MIN: int = 150
PREVIEW_MIN: int = 195
- OUTPUT_DIR: int = 18
+ OUTPUT_DIR: int = 26
HEADER: int = 23
SPACER_TINY: int = 2
SPACER_SMALL: int = 4
BUTTON_SIZE: int = 22
+ STATUS_BAR: int = 28
@dataclass(frozen=True, slots=True)
diff --git a/fastflix/ui_styles.py b/fastflix/ui_styles.py
index 3d3c1394..333ccf9d 100644
--- a/fastflix/ui_styles.py
+++ b/fastflix/ui_styles.py
@@ -29,6 +29,10 @@ def get_scaled_stylesheet(theme: str) -> str:
# when fonts propagate to child widgets. Convert px to pt (at 96 DPI: pt = px * 0.75).
font_size_pt = max(6, round(scaler.scale_font(FONTS.LARGE) * 0.75))
border_radius = scaler.scale(10)
+ # Ensure text inputs are always tall enough for their rounded edges.
+ # The min-height must be at least 2 * border_radius so the left/right
+ # semicircles have room to render without clipping.
+ input_min_height = 2 * border_radius + scaler.scale(4)
base = f"QWidget {{ font-size: {font_size_pt}pt; }}"
@@ -41,10 +45,11 @@ def get_scaled_stylesheet(theme: str) -> str:
background-color: #4a555e;
color: white;
border-radius: {border_radius}px;
+ min-height: {input_min_height}px;
}}
QTextEdit {{ background-color: #4a555e; color: white; }}
QTabBar::tab {{ background-color: #4f5962; }}
- QComboBox {{ border-radius: {border_radius}px; }}
+ QComboBox {{ border-radius: {border_radius}px; min-height: {input_min_height}px; }}
QScrollArea {{ border: 1px solid #919191; }}
"""
diff --git a/fastflix/version.py b/fastflix/version.py
index 82a7fc36..854e0118 100644
--- a/fastflix/version.py
+++ b/fastflix/version.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-__version__ = "6.0.1"
+__version__ = "6.1.0"
__author__ = "Chris Griffith"
diff --git a/fastflix/widgets/container.py b/fastflix/widgets/container.py
index 99d0d599..e1b74ec2 100644
--- a/fastflix/widgets/container.py
+++ b/fastflix/widgets/container.py
@@ -23,7 +23,6 @@
clean_logs,
error_message,
latest_fastflix,
- message,
yes_no_message,
parse_filesafe_datetime,
is_date_older_than_7days,
@@ -36,8 +35,8 @@
# from fastflix.widgets.logs import Logs
from fastflix.widgets.main import Main
from fastflix.widgets.windows.profile_window import ProfileWindow
-from fastflix.widgets.progress_bar import ProgressBar, Task
from fastflix.widgets.settings import Settings
+from fastflix.widgets.status_bar import StatusBarWidget, STATE_COMPLETE, STATE_ERROR
from fastflix.widgets.windows.concat import ConcatWindow
from fastflix.widgets.windows.multiple_files import MultipleFilesWindow
@@ -55,7 +54,6 @@ class Container(QtWidgets.QMainWindow):
def __init__(self, app: FastFlixApp, **kwargs):
super().__init__(None)
self.app = app
- self.pb = None
self.profile_window = None
self.setMinimumSize(self.MIN_WIDTH, self.MIN_HEIGHT)
@@ -94,7 +92,18 @@ def __init__(self, app: FastFlixApp, **kwargs):
self.main = Main(self, app)
- self.setCentralWidget(self.main)
+ self.status_bar = StatusBarWidget(self.app, parent=self)
+
+ # Wrap Main + StatusBar in a vertical layout as the central widget
+ central = QtWidgets.QWidget(self)
+ central_layout = QtWidgets.QVBoxLayout(central)
+ central_layout.setContentsMargins(0, 0, 0, 0)
+ central_layout.setSpacing(0)
+ central_layout.addWidget(self.main)
+ central_layout.addWidget(self.status_bar)
+ central.setLayout(central_layout)
+
+ self.setCentralWidget(central)
self.setBaseSize(QtCore.QSize(self.BASE_WIDTH, self.BASE_HEIGHT))
# Set initial window size to base dimensions
self.resize(self.BASE_WIDTH, self.BASE_HEIGHT)
@@ -103,29 +112,43 @@ def __init__(self, app: FastFlixApp, **kwargs):
self._constrain_to_screen()
self.main.set_profile()
+ # Connect encoding signals to status bar
+ self.main.encoding_status_signal.connect(lambda msg, state: self.status_bar.set_state(state, msg))
+ self.main.encoding_progress_signal.connect(self.status_bar.set_progress)
+ self.main.video_options.status.progress_percent.connect(self.status_bar.set_progress)
+
self._update_scaled_styles()
# self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)
self.moveFlag = False
+ def _current_screen(self) -> QtGui.QScreen:
+ """Return the screen the window center is on, falling back to primary."""
+ screen = QtGui.QGuiApplication.screenAt(self.geometry().center())
+ if screen is None:
+ screen = QtGui.QGuiApplication.primaryScreen()
+ return screen
+
def _update_scaled_styles(self) -> None:
"""Update all stylesheets based on current scale factors."""
self.setStyleSheet(get_scaled_stylesheet(self.app.fastflix.config.theme))
def _constrain_to_screen(self):
"""Ensure the window fits within the available screen geometry."""
- screen = QtGui.QGuiApplication.primaryScreen()
+ screen = self._current_screen()
if screen is None:
return
available = screen.availableGeometry()
- # Set maximum size to screen available geometry with some margin
- max_width = available.width() - 20
- max_height = available.height() - 20
- self.setMaximumSize(max_width, max_height)
+ # Clamp current size to fit screen if needed, but don't set a maximum
+ # so the window can still be maximized via the title bar button
+ if self.width() > available.width() or self.height() > available.height():
+ self.resize(min(self.width(), available.width()), min(self.height(), available.height()))
def ensure_window_in_bounds(self):
"""Public method to ensure window stays within screen bounds after content changes."""
+ if self.isMaximized() or self.isFullScreen():
+ return
self._constrain_to_screen()
- screen = QtGui.QGuiApplication.primaryScreen()
+ screen = self._current_screen()
if screen is None:
return
available = screen.availableGeometry()
@@ -155,11 +178,16 @@ def ensure_window_in_bounds(self):
def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
"""Handle resize events to ensure window stays within screen bounds and update scaling."""
super().resizeEvent(event)
- # Update scale factors based on new size
+
+ # Always update scale factors and styles so the UI adapts to any window size
scaler.calculate_factors(event.size().width(), event.size().height())
self._update_scaled_styles()
- screen = QtGui.QGuiApplication.primaryScreen()
+ # Don't adjust position when maximized/fullscreen — the OS manages geometry
+ if self.isMaximized() or self.isFullScreen():
+ return
+
+ screen = self._current_screen()
if screen is None:
return
available = screen.availableGeometry()
@@ -211,11 +239,10 @@ def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
self.app.fastflix.shutting_down = True
- if self.pb:
- try:
- self.pb.stop_signal.emit()
- except Exception:
- pass
+ try:
+ self.status_bar.cancel()
+ except Exception:
+ pass
if self.app.fastflix.currently_encoding:
sm = QtWidgets.QMessageBox()
sm.setText(f"