diff --git a/.gitmodules b/.gitmodules index 14b15b563a1541..a58b71d755baec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,6 +5,15 @@ [submodule "plugins/mac-syphon/syphon-framework"] path = plugins/mac-syphon/syphon-framework url = https://github.com/palana/Syphon-Framework.git + [submodule "plugins/libftl/ftl-sdk"] path = plugins/libftl/ftl-sdk url = https://github.com/WatchBeam/ftl-sdk.git + +[submodule "plugins/enc-amf"] + path = plugins/enc-amf + url = https://github.com/Xaymar/OBS-AMD-Advanced-Media-Framework.git + +[submodule "plugins/obs-browser"] + path = plugins/obs-browser + url = https://github.com/kc5nra/obs-browser.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d4f8db79c367b..fafd69818f6105 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,7 @@ if(NOT INSTALLER_RUN) add_subdirectory(libobs-opengl) add_subdirectory(libobs) - add_subdirectory(obs) + add_subdirectory(UI) add_subdirectory(plugins) if (BUILD_TESTS) add_subdirectory(test) diff --git a/CONTRIBUTING b/CONTRIBUTING index 36dfff2df6e3ad..59b209e199f72d 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING @@ -7,6 +7,9 @@ Contributing Information: https://obsproject.com/forum/list/general-development.21/ - Development IRC channel is primarily #obs-dev on QuakeNet + + - To contribute translations, see: + https://obsproject.com/forum/threads/how-to-contribute-translations-for-obs.16327/ Contributing Guidelines: diff --git a/obs/CMakeLists.txt b/UI/CMakeLists.txt similarity index 95% rename from obs/CMakeLists.txt rename to UI/CMakeLists.txt index 4185188063731c..69cd6937607564 100644 --- a/obs/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -1,5 +1,3 @@ -project(obs) - option(ENABLE_UI "Enables the OBS user interfaces" ON) if(DISABLE_UI) message(STATUS "UI disabled") @@ -10,6 +8,12 @@ else() set(FIND_MODE QUIET) endif() +add_subdirectory(obs-frontend-api) + +# ---------------------------------------------------------------------------- + +project(obs) + if(DEFINED QTDIR${_lib_suffix}) list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}") elseif(DEFINED QTDIR) @@ -40,6 +44,7 @@ if(NOT Qt5Widgets_FOUND) endif() endif() +include_directories(SYSTEM "obs-frontend-api") include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") find_package(Libcurl REQUIRED) @@ -97,6 +102,7 @@ endif() set(obs_SOURCES ${obs_PLATFORM_SOURCES} obs-app.cpp + api-interface.cpp window-basic-main.cpp window-basic-filters.cpp window-basic-settings.cpp @@ -106,6 +112,7 @@ set(obs_SOURCES window-basic-source-select.cpp window-basic-main-scene-collections.cpp window-basic-main-transitions.cpp + window-basic-main-dropfiles.cpp window-basic-main-profiles.cpp window-license-agreement.cpp window-basic-status-bar.cpp @@ -219,6 +226,7 @@ target_link_libraries(obs libobs libff Qt5::Widgets + obs-frontend-api ${LIBCURL_LIBRARIES} ${obs_PLATFORM_LIBRARIES}) @@ -232,3 +240,5 @@ if (UNIX AND UNIX_STRUCTURE AND NOT APPLE) install(FILES forms/images/obs.png DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/icons/hicolor/256x256/apps) endif() + +add_subdirectory(frontend-plugins) diff --git a/obs/adv-audio-control.cpp b/UI/adv-audio-control.cpp similarity index 100% rename from obs/adv-audio-control.cpp rename to UI/adv-audio-control.cpp diff --git a/obs/adv-audio-control.hpp b/UI/adv-audio-control.hpp similarity index 100% rename from obs/adv-audio-control.hpp rename to UI/adv-audio-control.hpp diff --git a/UI/api-interface.cpp b/UI/api-interface.cpp new file mode 100644 index 00000000000000..390c1da0510718 --- /dev/null +++ b/UI/api-interface.cpp @@ -0,0 +1,362 @@ +#include +#include "obs-app.hpp" +#include "qt-wrappers.hpp" +#include "window-basic-main.hpp" +#include "window-basic-main-outputs.hpp" + +#include + +using namespace std; + +Q_DECLARE_METATYPE(OBSScene); +Q_DECLARE_METATYPE(OBSSource); + +template +static T GetOBSRef(QListWidgetItem *item) +{ + return item->data(static_cast(QtDataRole::OBSRef)).value(); +} + +void EnumProfiles(function &&cb); +void EnumSceneCollections(function &&cb); + +/* ------------------------------------------------------------------------- */ + +template struct OBSStudioCallback { + T callback; + void *private_data; + + inline OBSStudioCallback(T cb, void *p) : + callback(cb), private_data(p) + {} +}; + +template inline size_t GetCallbackIdx( + vector> &callbacks, + T callback, void *private_data) +{ + for (size_t i = 0; i < callbacks.size(); i++) { + OBSStudioCallback curCB = callbacks[i]; + if (curCB.callback == callback && + curCB.private_data == private_data) + return i; + } + + return (size_t)-1; +} + +struct OBSStudioAPI : obs_frontend_callbacks { + OBSBasic *main; + vector> callbacks; + vector> saveCallbacks; + + inline OBSStudioAPI(OBSBasic *main_) : main(main_) {} + + void *obs_frontend_get_main_window(void) override + { + return (void*)main; + } + + void *obs_frontend_get_main_window_handle(void) override + { + return (void*)main->winId(); + } + + void obs_frontend_get_scenes( + struct obs_frontend_source_list *sources) override + { + for (int i = 0; i < main->ui->scenes->count(); i++) { + QListWidgetItem *item = main->ui->scenes->item(i); + OBSScene scene = GetOBSRef(item); + obs_source_t *source = obs_scene_get_source(scene); + + obs_source_addref(source); + da_push_back(sources->sources, &source); + } + } + + obs_source_t *obs_frontend_get_current_scene(void) override + { + OBSSource source; + + if (main->IsPreviewProgramMode()) { + source = obs_weak_source_get_source(main->programScene); + } else { + source = main->GetCurrentSceneSource(); + obs_source_addref(source); + } + return source; + } + + void obs_frontend_set_current_scene(obs_source_t *scene) override + { + if (main->IsPreviewProgramMode()) { + QMetaObject::invokeMethod(main, "TransitionToScene", + Q_ARG(OBSSource, OBSSource(scene))); + } else { + QMetaObject::invokeMethod(main, "SetCurrentScene", + Q_ARG(OBSSource, OBSSource(scene)), + Q_ARG(bool, false)); + } + } + + void obs_frontend_get_transitions( + struct obs_frontend_source_list *sources) override + { + for (int i = 0; i < main->ui->transitions->count(); i++) { + OBSSource tr = main->ui->transitions->itemData(i) + .value(); + + obs_source_addref(tr); + da_push_back(sources->sources, &tr); + } + } + + obs_source_t *obs_frontend_get_current_transition(void) override + { + OBSSource tr = main->GetCurrentTransition(); + + obs_source_addref(tr); + return tr; + } + + void obs_frontend_set_current_transition( + obs_source_t *transition) override + { + QMetaObject::invokeMethod(main, "SetTransition", + Q_ARG(OBSSource, OBSSource(transition))); + } + + void obs_frontend_get_scene_collections( + std::vector &strings) override + { + auto addCollection = [&](const char *name, const char *) + { + strings.emplace_back(name); + return true; + }; + + EnumSceneCollections(addCollection); + } + + char *obs_frontend_get_current_scene_collection(void) override + { + const char *cur_name = config_get_string(App()->GlobalConfig(), + "Basic", "SceneCollection"); + return bstrdup(cur_name); + } + + void obs_frontend_set_current_scene_collection( + const char *collection) override + { + QList menuActions = + main->ui->sceneCollectionMenu->actions(); + QString qstrCollection = QT_UTF8(collection); + + for (int i = 0; i < menuActions.count(); i++) { + QAction *action = menuActions[i]; + QVariant v = action->property("file_name"); + + if (v.typeName() != nullptr) { + if (action->text() == qstrCollection) { + action->trigger(); + break; + } + } + } + } + + void obs_frontend_get_profiles( + std::vector &strings) override + { + auto addProfile = [&](const char *name, const char *) + { + strings.emplace_back(name); + return true; + }; + + EnumProfiles(addProfile); + } + + char *obs_frontend_get_current_profile(void) override + { + const char *name = config_get_string(App()->GlobalConfig(), + "Basic", "Profile"); + return bstrdup(name); + } + + void obs_frontend_set_current_profile(const char *profile) override + { + QList menuActions = + main->ui->profileMenu->actions(); + QString qstrProfile = QT_UTF8(profile); + + for (int i = 0; i < menuActions.count(); i++) { + QAction *action = menuActions[i]; + QVariant v = action->property("file_name"); + + if (v.typeName() != nullptr) { + if (action->text() == qstrProfile) { + action->trigger(); + break; + } + } + } + } + + void obs_frontend_streaming_start(void) override + { + QMetaObject::invokeMethod(main, "StartStreaming"); + } + + void obs_frontend_streaming_stop(void) override + { + QMetaObject::invokeMethod(main, "StopStreaming"); + } + + bool obs_frontend_streaming_active(void) override + { + return main->outputHandler->StreamingActive(); + } + + void obs_frontend_recording_start(void) override + { + QMetaObject::invokeMethod(main, "StartRecording"); + } + + void obs_frontend_recording_stop(void) override + { + QMetaObject::invokeMethod(main, "StopRecording"); + } + + bool obs_frontend_recording_active(void) override + { + return main->outputHandler->StreamingActive(); + } + + void *obs_frontend_add_tools_menu_qaction(const char *name) override + { + main->ui->menuTools->setEnabled(true); + return (void*)main->ui->menuTools->addAction(QT_UTF8(name)); + } + + void obs_frontend_add_tools_menu_item(const char *name, + obs_frontend_cb callback, void *private_data) override + { + main->ui->menuTools->setEnabled(true); + + auto func = [private_data, callback] () + { + callback(private_data); + }; + + QAction *action = main->ui->menuTools->addAction(QT_UTF8(name)); + QObject::connect(action, &QAction::triggered, func); + } + + void obs_frontend_add_event_callback(obs_frontend_event_cb callback, + void *private_data) override + { + size_t idx = GetCallbackIdx(callbacks, callback, private_data); + if (idx == (size_t)-1) + callbacks.emplace_back(callback, private_data); + } + + void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, + void *private_data) override + { + size_t idx = GetCallbackIdx(callbacks, callback, private_data); + if (idx == (size_t)-1) + return; + + callbacks.erase(callbacks.begin() + idx); + } + + obs_output_t *obs_frontend_get_streaming_output(void) override + { + OBSOutput output = main->outputHandler->streamOutput; + obs_output_addref(output); + return output; + } + + obs_output_t *obs_frontend_get_recording_output(void) override + { + OBSOutput out = main->outputHandler->fileOutput; + obs_output_addref(out); + return out; + } + + config_t *obs_frontend_get_profile_config(void) override + { + return main->basicConfig; + } + + config_t *obs_frontend_get_global_config(void) override + { + return App()->GlobalConfig(); + } + + void obs_frontend_save(void) override + { + main->SaveProject(); + } + + void obs_frontend_add_save_callback(obs_frontend_save_cb callback, + void *private_data) override + { + size_t idx = GetCallbackIdx(saveCallbacks, callback, + private_data); + if (idx == (size_t)-1) + saveCallbacks.emplace_back(callback, private_data); + } + + void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, + void *private_data) override + { + size_t idx = GetCallbackIdx(saveCallbacks, callback, + private_data); + if (idx == (size_t)-1) + return; + + saveCallbacks.erase(saveCallbacks.begin() + idx); + } + + void obs_frontend_push_ui_translation( + obs_frontend_translate_ui_cb translate) override + { + App()->PushUITranslation(translate); + } + + void obs_frontend_pop_ui_translation(void) override + { + App()->PopUITranslation(); + } + + void on_load(obs_data_t *settings) override + { + for (auto cb : saveCallbacks) + cb.callback(settings, false, cb.private_data); + } + + void on_save(obs_data_t *settings) override + { + for (auto cb : saveCallbacks) + cb.callback(settings, true, cb.private_data); + } + + void on_event(enum obs_frontend_event event) override + { + if (main->disableSaving) + return; + + for (auto cb : callbacks) + cb.callback(event, cb.private_data); + } +}; + +obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main) +{ + obs_frontend_callbacks *api = new OBSStudioAPI(main); + obs_frontend_set_callbacks_internal(api); + return api; +} diff --git a/obs/audio-encoders.cpp b/UI/audio-encoders.cpp similarity index 100% rename from obs/audio-encoders.cpp rename to UI/audio-encoders.cpp diff --git a/obs/audio-encoders.hpp b/UI/audio-encoders.hpp similarity index 100% rename from obs/audio-encoders.hpp rename to UI/audio-encoders.hpp diff --git a/obs/crash-report.cpp b/UI/crash-report.cpp similarity index 100% rename from obs/crash-report.cpp rename to UI/crash-report.cpp diff --git a/obs/crash-report.hpp b/UI/crash-report.hpp similarity index 100% rename from obs/crash-report.hpp rename to UI/crash-report.hpp diff --git a/obs/data/license/gplv2.txt b/UI/data/license/gplv2.txt similarity index 100% rename from obs/data/license/gplv2.txt rename to UI/data/license/gplv2.txt diff --git a/obs/data/locale.ini b/UI/data/locale.ini similarity index 100% rename from obs/data/locale.ini rename to UI/data/locale.ini diff --git a/obs/data/locale/ar-SA.ini b/UI/data/locale/ar-SA.ini similarity index 89% rename from obs/data/locale/ar-SA.ini rename to UI/data/locale/ar-SA.ini index bebdd20ae2155d..af10ecae560218 100644 --- a/obs/data/locale/ar-SA.ini +++ b/UI/data/locale/ar-SA.ini @@ -52,14 +52,19 @@ Bottom="أسفل" QuickTransitions.SwapScenes="التبديل بين مشهدي المعاينة و الاخراج بعد عملية الانتقال" QuickTransitions.SwapScenesTT="يقوم بتبديل مشهد المعاينة مع مشهد الاخراج بعد عملية الانتقال بين المشاهد (اذا كان مشهد الاخراج الاصلي لازال موجوداً) \n هذا لن يقوم بالتراجع عن اي تغييرات قمت بها على مشهد الاخراج الأصلي." QuickTransitions.DuplicateScene="استنساخ المشهد" +QuickTransitions.DuplicateSceneTT="عند تحرير المشهد نفسه، يسمح بتحرير تحويل/رؤية مصادر دون تعديل خصائص تحرير الإخراج. \nTo من مصادر دون تعديل الإخراج، تمكين '\"تكرار المصادر\"'. \nChanging سيتم إعادة تعيين هذه القيمة الساحة الإخراج الحالي (إذا كان لا يزال موجوداً)." QuickTransitions.EditProperties="استنساخ المصدر" +QuickTransitions.EditPropertiesTT="عند تحرير المشهد نفسه، يسمح بتحرير خصائص المصادر دون تعديل الإخراج. \nThis يمكن استخدام فقط إذا تم تمكين '\"تكرار المشهد\"'. لا تدعم هذه المصادر \nCertain (مثل مصادر التقاط أو وسائط الإعلام) ولا يمكن تحريرها بشكل منفصل. \nChanging سيتم إعادة تعيين هذه القيمة الساحة الإخراج الحالي (إذا كان لا يزال exists).\n\nWarning: لأنه سوف يتم تكرار المصادر ، وهذا قد يتطلب نظام إضافية أو موارد الفيديو." QuickTransitions.HotkeyName="الانتقال السريع: %1" +Basic.AddTransition="إضافة المراحل الانتقالية للتكوين" +Basic.RemoveTransition="سلاو" Basic.TransitionProperties="خصائص تأثير الإنتقال" Basic.SceneTransitions="تأثير انتقال المشهد" Basic.TransitionDuration="مدة الانتقال" Basic.TogglePreviewProgramMode="طور الاستوديو" +TransitionNameDlg.Text="Text" TransitionNameDlg.Title="اسم تأثير الإنتقال" TitleBar.Profile="الملف الشخصي" @@ -91,6 +96,7 @@ Output.ConnectFail.Error="حدث خطأ غير متوقع عند محاولة ا Output.ConnectFail.Disconnected="تم قطع الاتصال من السيرفر." Output.RecordFail.Title="فشل في بدء التسجيل" +Output.RecordFail.Unsupported="تنسيق الإخراج أما غير معتمد أو لا يدعم أكثر من مسار للصوت. الرجاء التحقق من الإعدادات الخاصة بك وحاول مرة أخرى." Output.RecordNoSpace.Msg="لا توجد مساحة كافية على القرص لمتابعة التسجيل." Output.RecordError.Title="خطأ في التسجيل" Output.RecordError.Msg="حدث خطأ غير محدد أثناء التسجيل." @@ -110,11 +116,17 @@ LicenseAgreement.Exit="خروج" Remux.SourceFile="تسجيل OBS" Remux.TargetFile="الملف الهدف" +Remux.Remux="تحويل الصيغة" Remux.OBSRecording="تسجيل OBS" +Remux.FinishedTitle="انتهت عملية تحويل الصيغة" +Remux.Finished="تسجيل عملية تحويل الصيغة" +Remux.FinishedError="عملية التحويل قد تكون غير مكتملة" Remux.SelectRecording="إختر تسجيل OBS …" Remux.SelectTarget="إختر الملف الهدف …" Remux.FileExistsTitle="الملف الهدف موجود" Remux.FileExists="الملف الهدف موجود، هل تريد استبداله؟" +Remux.ExitUnfinishedTitle="تقدم عملية التحويل" +Remux.ExitUnfinished="إيقاف عميلة التحويل الآن قد تجعل الملف غير قابل للاستخدام./n هل أنت متأكد أنك تريد إيقاف عملية التحويل؟" UpdateAvailable="تحديث جديد متوفر" UpdateAvailable.Text="الإصدار %1.%2.%3 متوفر الآن. انقر هنا للتتحميل" @@ -131,6 +143,8 @@ Basic.DisplayCapture="التقاط الشاشة" Basic.Main.PreviewConextMenu.Enable="تمكين المعاينة" +ScaleFiltering="مقياس التصفية" +ScaleFiltering.Point="نقطة" Deinterlacing.Discard="تجاهل" @@ -243,6 +257,7 @@ Basic.MainMenu.View.Toolbars="شريط الأدوات" Basic.MainMenu.SceneCollection="&مجموعة المشاهد" Basic.MainMenu.Profile="&الملف الشخصي" + Basic.MainMenu.Help="&مساعدة" Basic.MainMenu.Help.Website="زيارة &الموقع الإلكتروني" Basic.MainMenu.Help.Logs="&ملفات السجل" @@ -345,3 +360,5 @@ Basic.Settings.Advanced.Video.ColorRange.Full="كامل" + + diff --git a/obs/data/locale/bg-BG.ini b/UI/data/locale/bg-BG.ini similarity index 99% rename from obs/data/locale/bg-BG.ini rename to UI/data/locale/bg-BG.ini index fa2a20b6aa632e..260ebfe438baf1 100644 --- a/obs/data/locale/bg-BG.ini +++ b/UI/data/locale/bg-BG.ini @@ -224,6 +224,7 @@ Basic.MainMenu.Edit.Order.MoveToBottom="Премести най-о&тдолу" + Basic.MainMenu.Help="&Помощ" Basic.MainMenu.Help.Logs="\"Log\" &файлове" Basic.MainMenu.Help.Logs.UploadCurrentLog="Качи &текущия \"Log\" файл" @@ -289,6 +290,8 @@ Basic.Hotkeys.StartRecording="Започни запис" Basic.Hotkeys.StopRecording="Спри запис" Basic.Hotkeys.SelectScene="Премини към сцена" + + Hotkeys.Insert="Вмъкни" Hotkeys.Delete="Изтрий" Hotkeys.Home="Начало" diff --git a/obs/data/locale/ca-ES.ini b/UI/data/locale/ca-ES.ini similarity index 98% rename from obs/data/locale/ca-ES.ini rename to UI/data/locale/ca-ES.ini index 62e9b7519f858a..a688099a9d0691 100644 --- a/obs/data/locale/ca-ES.ini +++ b/UI/data/locale/ca-ES.ini @@ -48,6 +48,7 @@ Left="Esquerra" Right="Dreta" Top="Part superior" Bottom="Part inferior" +Reset="Restableix" QuickTransitions.SwapScenes="Canvia la vista prèvia i sortida d'escenes després de la transició" QuickTransitions.SwapScenesTT="Canvia la vista prèvia i sortida d'escenes després de la transició (si encara existeix l'escena original de la sortida). \nAixò no desfarà qualsevol canvi que pugui haver fet a l'escena original de la sortida." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="Barra d'estat" Basic.MainMenu.SceneCollection="&Col·lecció d'escenes" Basic.MainMenu.Profile="&Perfil" +Basic.MainMenu.Tools="&Eines" + Basic.MainMenu.Help="&Ajuda" Basic.MainMenu.Help.Website="Visa el lloc &web" Basic.MainMenu.Help.Logs="Fitxers de ®istre" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Llengua" Basic.Settings.General.WarnBeforeStartingStream="Mostra diàleg de confirmació quan s'iniciï una transmissió" Basic.Settings.General.WarnBeforeStoppingStream="Mostra diàleg de confirmació quan s'aturi una transmissió" Basic.Settings.General.HideProjectorCursor="Amaga el cursor sobre projectors" +Basic.Settings.General.ProjectorAlwaysOnTop="Projectors sempre en la part superior" Basic.Settings.General.Snapping="Ajustament d'alineació de la font" Basic.Settings.General.ScreenSnapping="Ajustar les fonts a la vora de la pantalla" Basic.Settings.General.CenterSnapping="Ajustar les fonts al centre horitzontal i vertical" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Ajustar les fonts a altres fonts" Basic.Settings.General.SnapDistance="Ajusta la sensibilitat" Basic.Settings.General.RecordWhenStreaming="Enregistra automàticament quan es transmet" Basic.Settings.General.KeepRecordingWhenStreamStops="Mantenir l'enregistrament quan s'atura la transmissió" +Basic.Settings.General.SysTrayEnabled="Activar icona a la safata del sistema" +Basic.Settings.General.SysTrayWhenStarted="Minimitzar a la safata del sistema en iniciar" Basic.Settings.Stream="Directe" Basic.Settings.Stream.StreamType="Tipus de directe" @@ -488,6 +494,11 @@ Basic.Hotkeys.StartRecording="Inicia l'enregistrament" Basic.Hotkeys.StopRecording="Atura l'enregistrament" Basic.Hotkeys.SelectScene="Canviar a escena" +Basic.SystemTray.Show="Mostra" +Basic.SystemTray.Hide="Oculta" + +Basic.SystemTray.Message.Reconnecting="Desconnectat. Tornant a connectar..." + Hotkeys.Insert="Insereix" Hotkeys.Delete="Suprimeix" Hotkeys.Home="Inici" diff --git a/obs/data/locale/cs-CZ.ini b/UI/data/locale/cs-CZ.ini similarity index 98% rename from obs/data/locale/cs-CZ.ini rename to UI/data/locale/cs-CZ.ini index aad6296e6ba083..35042599ab845e 100644 --- a/obs/data/locale/cs-CZ.ini +++ b/UI/data/locale/cs-CZ.ini @@ -48,6 +48,7 @@ Left="Vlevo" Right="Vpravo" Top="Nahoře" Bottom="Dole" +Reset="Resetovat" QuickTransitions.SwapScenes="Prohodit scény náhledu a výstupu po přechodu" QuickTransitions.SwapScenesTT="Prohodí scény náhledu a výstupu po přechodu (pokud originální výstupní scéna stále existuje).\nTato funkce nevrátí provedené změny, které byly provedeny v originální scéně výstupu." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="&Stavový řádek" Basic.MainMenu.SceneCollection="Kolekce &scén" Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Tools="Nás&troje" + Basic.MainMenu.Help="Pomoc (&H)" Basic.MainMenu.Help.Website="Navštívit &web" Basic.MainMenu.Help.Logs="Soubory záznamu (&L)" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Jazyk" Basic.Settings.General.WarnBeforeStartingStream="Vyžadovat potvrzení pro spuštění vysílání" Basic.Settings.General.WarnBeforeStoppingStream="Vyžadovat potvrzení pro ukončení vysílání" Basic.Settings.General.HideProjectorCursor="Skrýt kurzor přes projektor" +Basic.Settings.General.ProjectorAlwaysOnTop="Zobrazovat projektor vždy navrchu" Basic.Settings.General.Snapping="Přichycování zdrojů" Basic.Settings.General.ScreenSnapping="Přichytávat zdroje k okraji obrazovky" Basic.Settings.General.CenterSnapping="Přichytávat zdroje k vertikálnímu a horizontálnímu středu" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Přichytávat zdroje k ostatním zdrojům Basic.Settings.General.SnapDistance="Citlivost přichycení" Basic.Settings.General.RecordWhenStreaming="Automaticky nahrávat při vysílání" Basic.Settings.General.KeepRecordingWhenStreamStops="Pokračovat v nahrávání i po zastavení vysílání" +Basic.Settings.General.SysTrayEnabled="Zobrazit ikonu v oznamovací oblasti" +Basic.Settings.General.SysTrayWhenStarted="Minimalizovat do systémové lišty při spuštění" Basic.Settings.Stream="Vysílání" Basic.Settings.Stream.StreamType="Typ vysílání" @@ -488,6 +494,11 @@ Basic.Hotkeys.StartRecording="Začít nahrávat" Basic.Hotkeys.StopRecording="Zastavit nahrávání" Basic.Hotkeys.SelectScene="Přepnout na scénu" +Basic.SystemTray.Show="Zobrazit" +Basic.SystemTray.Hide="Skrýt" + +Basic.SystemTray.Message.Reconnecting="Odpojen. Obnovuji spojení..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/da-DK.ini b/UI/data/locale/da-DK.ini similarity index 99% rename from obs/data/locale/da-DK.ini rename to UI/data/locale/da-DK.ini index 86f009df7aa1e9..251a7380804faa 100644 --- a/obs/data/locale/da-DK.ini +++ b/UI/data/locale/da-DK.ini @@ -48,6 +48,7 @@ Left="Venstre" Right="Højre" Top="Top" Bottom="Bund" +Reset="Nulstil" QuickTransitions.SwapScenes="Byt om på forhåndsvisning/output scener efter overgang" QuickTransitions.DuplicateScene="Dupliker scene" @@ -282,6 +283,7 @@ Basic.MainMenu.Edit.AdvAudio="&Avancerede lydegenskaber" Basic.MainMenu.SceneCollection="&Scenesamling" Basic.MainMenu.Profile="&Profil" + Basic.MainMenu.Help="&Hjælp" Basic.MainMenu.Help.Website="Besøg &websted" Basic.MainMenu.Help.Logs="&Logfiler" @@ -430,6 +432,8 @@ Basic.Hotkeys.StartRecording="Start optagelse" Basic.Hotkeys.StopRecording="Stop optagelse" Basic.Hotkeys.SelectScene="Skift til scene" + + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/de-DE.ini b/UI/data/locale/de-DE.ini similarity index 98% rename from obs/data/locale/de-DE.ini rename to UI/data/locale/de-DE.ini index 81c9c18d7e4290..b42c6c8fb35305 100644 --- a/obs/data/locale/de-DE.ini +++ b/UI/data/locale/de-DE.ini @@ -48,6 +48,7 @@ Left="Links" Right="Rechts" Top="Oben" Bottom="Unten" +Reset="Zurücksetzen" QuickTransitions.SwapScenes="Tausche Vorschau/Ausgabe-Szenen nach dem Übergang" QuickTransitions.SwapScenesTT="Vertauscht die Vorschau- und Ausgabe-Szenen nach dem Übergang (falls die ursprüngliche Ausgabe-Szene noch vorhanden ist).\nEventuelle Änderungen an der original Ausgabe-Szene werden hierbei nicht rückgängig gemacht." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="&Statusleiste" Basic.MainMenu.SceneCollection="&Szenen-Sammlung" Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Tools="Werkzeuge (&T)" + Basic.MainMenu.Help="&Hilfe" Basic.MainMenu.Help.Website="&Webseite besuchen" Basic.MainMenu.Help.Logs="&Logdateien" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Sprache" Basic.Settings.General.WarnBeforeStartingStream="Bestätigungsdialog beim Streamstart anzeigen" Basic.Settings.General.WarnBeforeStoppingStream="Bestätigungsdialog beim Streamstop anzeigen" Basic.Settings.General.HideProjectorCursor="Mauszeiger über Projektoren verstecken" +Basic.Settings.General.ProjectorAlwaysOnTop="Projektoren immer im Vordergrund anzeigen" Basic.Settings.General.Snapping="Quellenausrichtung" Basic.Settings.General.ScreenSnapping="Quellen am Bildschirmrand ausrichten" Basic.Settings.General.CenterSnapping="Quellen zur horizontalen und vertikalen Mitte ausrichten" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Quellen an anderen Quellen ausrichten" Basic.Settings.General.SnapDistance="Ausrichtungsempfindlichkeit" Basic.Settings.General.RecordWhenStreaming="Stream automatisch aufnehmen" Basic.Settings.General.KeepRecordingWhenStreamStops="Weiter aufnehmen, wenn der Livestream stoppt" +Basic.Settings.General.SysTrayEnabled="Symbol in der Taskleiste aktivieren" +Basic.Settings.General.SysTrayWhenStarted="Beim Start zur Taskleiste minimieren" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Stream Typ" @@ -363,6 +369,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="Verlustfreie Qualität-Warnung Basic.Settings.Output.Simple.Warn.MultipleQSV="Achtung: Sie können nicht mehrere separate QSV-Encoder beim streamen und aufnehmen gleichzeitig verwenden. Wenn Sie zur gleichen Zeit streamen und aufnehmen möchten, dann ändern Sie bitte entweder den Aufnahme-Encoder oder den Stream-Encoder." Basic.Settings.Output.Simple.Encoder.Software="Software (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Hardware (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Hardware (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Hardware (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Software (x264 niedrige CPU-Auslastung Voreinstellung, erhöht die Dateigröße)" Basic.Settings.Output.VideoBitrate="Videobitrate" @@ -488,6 +495,11 @@ Basic.Hotkeys.StartRecording="Aufnahme starten" Basic.Hotkeys.StopRecording="Aufnahme stoppen" Basic.Hotkeys.SelectScene="Zu Szene wechseln" +Basic.SystemTray.Show="Anzeigen" +Basic.SystemTray.Hide="Ausblenden" + +Basic.SystemTray.Message.Reconnecting="Verbindung verloren. Verbinde erneut..." + Hotkeys.Insert="Einfügen" Hotkeys.Delete="Entfernen" Hotkeys.Home="Pos1" diff --git a/obs/data/locale/el-GR.ini b/UI/data/locale/el-GR.ini similarity index 99% rename from obs/data/locale/el-GR.ini rename to UI/data/locale/el-GR.ini index 9412b86caf44a3..4ecdca7f21dabf 100644 --- a/obs/data/locale/el-GR.ini +++ b/UI/data/locale/el-GR.ini @@ -236,6 +236,7 @@ Basic.MainMenu.Edit.AdvAudio="Ιδιότητες(&A) Ήχου για Προχω Basic.MainMenu.SceneCollection="&Συλλογή Σκηνών" Basic.MainMenu.Profile="&Προφίλ" + Basic.MainMenu.Help="Βοήθεια(&H)" Basic.MainMenu.Help.Logs="Αρχεία(&) Καταγραφής" Basic.MainMenu.Help.Logs.ShowLogs="Εμφάνιση(&S) Αρχείων Καταγραφής" @@ -359,6 +360,8 @@ Basic.Hotkeys.StopRecording="Διακοπή Καταγραφής" Basic.Hotkeys.SelectScene="Μετάβαση σε σκηνή" + + Mute="Σίγαση" Unmute="Κατάργηση σίγασης" diff --git a/obs/data/locale/en-US.ini b/UI/data/locale/en-US.ini similarity index 98% rename from obs/data/locale/en-US.ini rename to UI/data/locale/en-US.ini index 5be0dc1558a477..3d6f196bd6233e 100644 --- a/obs/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -53,6 +53,7 @@ Left="Left" Right="Right" Top="Top" Bottom="Bottom" +Reset="Reset" # quick transitions QuickTransitions.SwapScenes="Swap Preview/Output Scenes After Transitioning" @@ -354,6 +355,9 @@ Basic.MainMenu.View.StatusBar="&Status Bar" Basic.MainMenu.SceneCollection="&Scene Collection" Basic.MainMenu.Profile="&Profile" +# basic mode help menu +Basic.MainMenu.Tools="&Tools" + # basic mode help menu Basic.MainMenu.Help="&Help" Basic.MainMenu.Help.Website="Visit &Website" @@ -376,6 +380,7 @@ Basic.Settings.General.Language="Language" Basic.Settings.General.WarnBeforeStartingStream="Show confirmation dialog when starting streams" Basic.Settings.General.WarnBeforeStoppingStream="Show confirmation dialog when stopping streams" Basic.Settings.General.HideProjectorCursor="Hide cursor over projectors" +Basic.Settings.General.ProjectorAlwaysOnTop="Make projectors always on top" Basic.Settings.General.Snapping="Source Alignment Snapping" Basic.Settings.General.ScreenSnapping="Snap Sources to edge of screen" Basic.Settings.General.CenterSnapping="Snap Sources to horizontal and vertical center" @@ -383,6 +388,8 @@ Basic.Settings.General.SourceSnapping="Snap Sources to other sources" Basic.Settings.General.SnapDistance="Snap Sensitivity" Basic.Settings.General.RecordWhenStreaming="Automatically record when streaming" Basic.Settings.General.KeepRecordingWhenStreamStops="Keep recording when stream stops" +Basic.Settings.General.SysTrayEnabled="Enable system tray icon" +Basic.Settings.General.SysTrayWhenStarted="Minimize to system tray when started" # basic mode 'stream' settings Basic.Settings.Stream="Stream" @@ -414,6 +421,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="Lossless quality warning!" Basic.Settings.Output.Simple.Warn.MultipleQSV="Warning: You cannot use multiple separate QSV encoders when streaming and recording at the same time. If you want to stream and record at the same time, please change either the recording encoder or the stream encoder." Basic.Settings.Output.Simple.Encoder.Software="Software (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Hardware (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Hardware (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Hardware (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Software (x264 low CPU usage preset, increases file size)" Basic.Settings.Output.VideoBitrate="Video Bitrate" @@ -550,6 +558,13 @@ Basic.Hotkeys.StartRecording="Start Recording" Basic.Hotkeys.StopRecording="Stop Recording" Basic.Hotkeys.SelectScene="Switch to scene" +# system tray +Basic.SystemTray.Show="Show" +Basic.SystemTray.Hide="Hide" + +# system tray messages +Basic.SystemTray.Message.Reconnecting="Disconnected. Reconnecting..." + # hotkeys that may lack translation on certain operating systems Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" diff --git a/obs/data/locale/es-ES.ini b/UI/data/locale/es-ES.ini similarity index 98% rename from obs/data/locale/es-ES.ini rename to UI/data/locale/es-ES.ini index cd7cb0432a0b56..e988ac915c6dd8 100644 --- a/obs/data/locale/es-ES.ini +++ b/UI/data/locale/es-ES.ini @@ -48,6 +48,7 @@ Left="Izquierda" Right="Derecha" Top="Arriba" Bottom="Abajo" +Reset="Reiniciar" QuickTransitions.SwapScenes="Cambiar vista previa y salida escenas después de la transición" QuickTransitions.SwapScenesTT="Cambia la vista previa y salida escenas después de la transición (si todavía existe la escena original de la salida). \nEsto no deshará cualquier cambio que pueda haber hecho a la escena original de la salida." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="&Barra de Estado" Basic.MainMenu.SceneCollection="&Colección de Escenas" Basic.MainMenu.Profile="&Perfil" +Basic.MainMenu.Tools="&Herramientas" + Basic.MainMenu.Help="&Ayuda" Basic.MainMenu.Help.Website="Visitar Sitio &Web" Basic.MainMenu.Help.Logs="&Archivos de registro" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Idioma" Basic.Settings.General.WarnBeforeStartingStream="Mostrar diálogo de confirmación cuando se inicia una transmisión" Basic.Settings.General.WarnBeforeStoppingStream="Mostrar diálogo de confirmación cuando se para una transmisión" Basic.Settings.General.HideProjectorCursor="Ocultar el cursor sobre proyectores" +Basic.Settings.General.ProjectorAlwaysOnTop="Proyectores siempre en la parte superior" Basic.Settings.General.Snapping="Ajuste de alineación de la fuente" Basic.Settings.General.ScreenSnapping="Ajustar las fuentes al borde de la pantalla" Basic.Settings.General.CenterSnapping="Ajustar las fuentes al centro horizontal y vertical" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Ajustar las fuentes a otras fuentes" Basic.Settings.General.SnapDistance="Ajustar la sensibilidad" Basic.Settings.General.RecordWhenStreaming="Grabar automáticamente cuando se transmite" Basic.Settings.General.KeepRecordingWhenStreamStops="Mantener la grabación cuando se detiene la trasmision" +Basic.Settings.General.SysTrayEnabled="Activar icono en la bandeja del sistema" +Basic.Settings.General.SysTrayWhenStarted="Minimizar a la bandeja del sistema al iniciar" Basic.Settings.Stream="Emision" Basic.Settings.Stream.StreamType="Tipo de Emision" @@ -363,6 +369,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="¡Atención de calidad sin pé Basic.Settings.Output.Simple.Warn.MultipleQSV="Advertencia: No se pueden usar varios codificadores QSV separados al transmitir y grabar al mismo tiempo. Si desea transmitir y grabar al mismo tiempo, por favor cambíelos, ya sea el codificador de grabación o el codificador de trasmisión." Basic.Settings.Output.Simple.Encoder.Software="Software (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Hardware (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Hardware (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Hardware (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Software (x264 bajo uso de CPU, aumenta el tamaño de archivo)" Basic.Settings.Output.VideoBitrate="Bitrate de vídeo" @@ -488,6 +495,11 @@ Basic.Hotkeys.StartRecording="Iniciar grabación" Basic.Hotkeys.StopRecording="Detener grabación" Basic.Hotkeys.SelectScene="Cambiar a la escena" +Basic.SystemTray.Show="Mostrar" +Basic.SystemTray.Hide="Ocultar" + +Basic.SystemTray.Message.Reconnecting="Desconectado. Volviendo a conectar..." + Hotkeys.Insert="Insertar" Hotkeys.Delete="Eliminar" Hotkeys.Home="Casa" diff --git a/obs/data/locale/et-EE.ini b/UI/data/locale/et-EE.ini similarity index 99% rename from obs/data/locale/et-EE.ini rename to UI/data/locale/et-EE.ini index 87c0edd8047686..2c31217ec9b62d 100644 --- a/obs/data/locale/et-EE.ini +++ b/UI/data/locale/et-EE.ini @@ -190,6 +190,7 @@ Basic.MainMenu.Edit.Undo="Võta tagasi (&U)" + Basic.MainMenu.Help.Logs="&Logifailid" Basic.MainMenu.Help.CheckForUpdates="Otsi värskendusi" @@ -247,6 +248,8 @@ Basic.AdvAudio.AudioTracks="Rajad" + + Hotkeys.Insert="Sisesta" Hotkeys.Delete="Kustuta" diff --git a/obs/data/locale/eu-ES.ini b/UI/data/locale/eu-ES.ini similarity index 98% rename from obs/data/locale/eu-ES.ini rename to UI/data/locale/eu-ES.ini index 3eb81950cac37f..d2ac30de9775f8 100644 --- a/obs/data/locale/eu-ES.ini +++ b/UI/data/locale/eu-ES.ini @@ -48,6 +48,7 @@ Left="Ezkerrean" Right="Eskuinean" Top="Goian" Bottom="Behean" +Reset="Berrezarri" QuickTransitions.SwapScenes="Trukatu Aurrebista/Irteera-eszenak trantsizioen ondoren" QuickTransitions.SwapScenesTT="Trukatu aurrebistak eta irteera-eszenak trantsizioen ondoren (baldin eta irteerakoaren jatorrizkoa eszena badago).\n Honek ez du desegingo irteerakoaren jatorrizko eszenari egindako aldaketak." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="Egoera-barra" Basic.MainMenu.SceneCollection="&Eszena-bilduma" Basic.MainMenu.Profile="&Profila" +Basic.MainMenu.Tools="&Tresnak" + Basic.MainMenu.Help="&Laguntza" Basic.MainMenu.Help.Website="Ikusi &webgunea" Basic.MainMenu.Help.Logs="&Egunkari-fitxategiak" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Hizkuntza" Basic.Settings.General.WarnBeforeStartingStream="Erakutsi baieztapen elkarrizketa transmisioak hasterakoan" Basic.Settings.General.WarnBeforeStoppingStream="Erakutsi baieztapen elkarrizketa transmisioak gelditzerakoan" Basic.Settings.General.HideProjectorCursor="Ezkutatu kurtsorea proiekzioetan" +Basic.Settings.General.ProjectorAlwaysOnTop="Proiektoreak beti gainean" Basic.Settings.General.Snapping="Iturburuaren lerrokatzearen doitzea" Basic.Settings.General.ScreenSnapping="Doitu iturburuak pantailaren ertzera" Basic.Settings.General.CenterSnapping="Doitu iturburuak bertikalki eta horizontalki erdira" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Doitu iturburuak beste iturburuetara" Basic.Settings.General.SnapDistance="Doitu sentikortasuna" Basic.Settings.General.RecordWhenStreaming="Grabatu automatikoki transmisioa egitean" Basic.Settings.General.KeepRecordingWhenStreamStops="Mantendu grabazioa transmisioa gelditzean" +Basic.Settings.General.SysTrayEnabled="Gaitu sistemaren erretiluko ikonoa" +Basic.Settings.General.SysTrayWhenStarted="Minimizatu sistemaren erretilura hastean" Basic.Settings.Stream="Transmisioa" Basic.Settings.Stream.StreamType="Transmisio-mota" @@ -488,6 +494,11 @@ Basic.Hotkeys.StartRecording="Hasi Grabazioa" Basic.Hotkeys.StopRecording="Gelditu grabazioa" Basic.Hotkeys.SelectScene="Aldatu eszenara" +Basic.SystemTray.Show="Erakutsi" +Basic.SystemTray.Hide="Ezkutatu" + +Basic.SystemTray.Message.Reconnecting="Deskonektatuta. Berriro konektatzen..." + Hotkeys.Insert="Txertatu" Hotkeys.Delete="Ezabatu" Hotkeys.Home="Hasiera" diff --git a/obs/data/locale/fi-FI.ini b/UI/data/locale/fi-FI.ini similarity index 98% rename from obs/data/locale/fi-FI.ini rename to UI/data/locale/fi-FI.ini index fd07e1a4f809a3..b8c5ff70ecc197 100644 --- a/obs/data/locale/fi-FI.ini +++ b/UI/data/locale/fi-FI.ini @@ -48,6 +48,7 @@ Left="Vasen" Right="Oikea" Top="Ylhäältä" Bottom="Alhaalta" +Reset="Palauta" QuickTransitions.SwapScenes="Vaihda esikatselu- ja ulostulo-skenet siirtymän jälkeen" QuickTransitions.SwapScenesTT="Vaihda esikatselu- ja ulostulo-skenet siirtymän jälkeen (jos ulostulon alkuperäinen skene on yhä olemassa).\nTämä ei peruuta muutoksia joita on tehty alkuperäiseen skeneen." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="&Tilapalkki" Basic.MainMenu.SceneCollection="&Skene-kokoelma" Basic.MainMenu.Profile="&Profiili" +Basic.MainMenu.Tools="T&yökalut" + Basic.MainMenu.Help="&Apua" Basic.MainMenu.Help.Website="Käy &verkkosivulla" Basic.MainMenu.Help.Logs="&Lokitiedostot" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Kieli" Basic.Settings.General.WarnBeforeStartingStream="Näytä varmistus-ikkuna kun lähetys aloitetaan" Basic.Settings.General.WarnBeforeStoppingStream="Näytä varmistus-ikkuna kun lähetys pysäytetään" Basic.Settings.General.HideProjectorCursor="Piilota osoitin peilattaessa" +Basic.Settings.General.ProjectorAlwaysOnTop="Pidä peilatut esikatselut aina päällimmäisenä" Basic.Settings.General.Snapping="Lähteiden kiinnitys" Basic.Settings.General.ScreenSnapping="Kiinnitä lähteitä ruudun reunaan" Basic.Settings.General.CenterSnapping="Kiinnitä lähteitä vaaka- sekä pystysuunnan keskilinjaan" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Kiinnitä lähteitä muihin lähteisiin" Basic.Settings.General.SnapDistance="Kiinnityksen herkkyys" Basic.Settings.General.RecordWhenStreaming="Tallenna automaattisesti kun lähetetään" Basic.Settings.General.KeepRecordingWhenStreamStops="Jatka tallennusta lähetyksen loputtua" +Basic.Settings.General.SysTrayEnabled="Ota järjestelmäkuvake käyttöön" +Basic.Settings.General.SysTrayWhenStarted="Pienennä ilmaisinalueelle käynnistyessä" Basic.Settings.Stream="Lähetys" Basic.Settings.Stream.StreamType="Lähetystyyppi" @@ -488,6 +494,11 @@ Basic.Hotkeys.StartRecording="Aloita tallennus" Basic.Hotkeys.StopRecording="Pysäytä tallennus" Basic.Hotkeys.SelectScene="Vaihda skeneen" +Basic.SystemTray.Show="Näytä" +Basic.SystemTray.Hide="Piilota" + +Basic.SystemTray.Message.Reconnecting="Yhteys katkaistu. Yhdistetään uudelleen..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/fr-FR.ini b/UI/data/locale/fr-FR.ini similarity index 97% rename from obs/data/locale/fr-FR.ini rename to UI/data/locale/fr-FR.ini index ad7613554c53ce..781e993bc027a7 100644 --- a/obs/data/locale/fr-FR.ini +++ b/UI/data/locale/fr-FR.ini @@ -48,6 +48,7 @@ Left="À gauche" Right="À droite" Top="En haut" Bottom="En bas" +Reset="Réinitialiser" QuickTransitions.SwapScenes="Permuter les scènes d'aperçu et de sortie après la transition" QuickTransitions.SwapScenesTT="Permute les scènes d'aperçu et de sortie après la transition (si la scène d'origine de la sortie existe toujours). \nCela n'annulera pas les modifications qui auront pu être faites sur la scène d'origine de la sortie." @@ -280,6 +281,7 @@ Basic.MainMenu.Edit.Undo="&Annuler" Basic.MainMenu.Edit.Redo="&Rétablir" Basic.MainMenu.Edit.UndoAction="&Annuler $1" Basic.MainMenu.Edit.RedoAction="&Rétablir $1" +Basic.MainMenu.Edit.LockPreview="Verrouiller la prévisualisation" Basic.MainMenu.Edit.Transform="&Transformer" Basic.MainMenu.Edit.Transform.EditTransform="Éditer la transformation..." Basic.MainMenu.Edit.Transform.ResetTransform="Réinitialiser la transformation" @@ -307,6 +309,8 @@ Basic.MainMenu.View.StatusBar="&Barre d'état" Basic.MainMenu.SceneCollection="Collection de &scènes" Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Tools="Outils" + Basic.MainMenu.Help="&Aide" Basic.MainMenu.Help.Website="Consulter le site &Web" Basic.MainMenu.Help.Logs="&Fichiers journaux" @@ -326,6 +330,7 @@ Basic.Settings.General.Language="Langue" Basic.Settings.General.WarnBeforeStartingStream="Afficher une boîte de dialogue de confirmation au démarrage d'un stream" Basic.Settings.General.WarnBeforeStoppingStream="Afficher une boîte de dialogue de confirmation à l'arrêt d'un stream" Basic.Settings.General.HideProjectorCursor="Cacher le curseur sur les projecteurs" +Basic.Settings.General.ProjectorAlwaysOnTop="Projecteurs toujours au premier plan" Basic.Settings.General.Snapping="Déclenchement d'alignement des sources" Basic.Settings.General.ScreenSnapping="Déclencher avec les bords de l'écran" Basic.Settings.General.CenterSnapping="Déclencher avec le centre de l'écran" @@ -333,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Déclencher avec d'autres sources" Basic.Settings.General.SnapDistance="Sensibilité du déclenchement" Basic.Settings.General.RecordWhenStreaming="Enregistrer automatiquement lors d'un stream" Basic.Settings.General.KeepRecordingWhenStreamStops="Continuer à enregistrer lorsque le stream s’arrête" +Basic.Settings.General.SysTrayEnabled="Afficher une icône dans la zone de notification" +Basic.Settings.General.SysTrayWhenStarted="Réduire dans la zone de notification dès le démarrage" Basic.Settings.Stream="Flux" Basic.Settings.Stream.StreamType="Type de diffusion" @@ -362,6 +369,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="Avertissement de qualité sans Basic.Settings.Output.Simple.Warn.MultipleQSV="Attention : Vous ne pouvez pas utiliser plusieurs encodeurs QSV distincts lorsque vous streamez et enregistrez en même temps. Si vous voulez streamer et enregistrer en même temps, veuillez changer soit l'encodeur d'enregistrement, soit l'encodeur de streaming." Basic.Settings.Output.Simple.Encoder.Software="Logiciel (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Matériel (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Matériel (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Matériel (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Logiciel (préréglage x264 \"faible utilisation du CPU\", augmente la taille du fichier)" Basic.Settings.Output.VideoBitrate="Débit vidéo" @@ -467,6 +475,8 @@ Basic.Settings.Advanced.StreamDelay="Retard du stream" Basic.Settings.Advanced.StreamDelay.Duration="Durée (en secondes)" Basic.Settings.Advanced.StreamDelay.Preserve="Préserver le point de coupure (augmente le retard) lors d'une reconnexion" Basic.Settings.Advanced.StreamDelay.MemoryUsage="Utilisation estimée de la mémoire : %1 Mo" +Basic.Settings.Advanced.Network="Carte réseau (adresse IP source du flux)" +Basic.Settings.Advanced.Network.BindToIP="Lier à :" Basic.AdvAudio="Propriétés audio avancées" Basic.AdvAudio.Name="Nom" @@ -485,6 +495,11 @@ Basic.Hotkeys.StartRecording="Démarrer l'enregistrement" Basic.Hotkeys.StopRecording="Arrêter l'enregistrement" Basic.Hotkeys.SelectScene="Passer à la scène" +Basic.SystemTray.Show="Restaurer" +Basic.SystemTray.Hide="Réduire" + +Basic.SystemTray.Message.Reconnecting="Déconnecté. Reconnexion en cours..." + Hotkeys.Insert="Insérer" Hotkeys.Delete="Supprimer" Hotkeys.Home="Début" diff --git a/obs/data/locale/gl-ES.ini b/UI/data/locale/gl-ES.ini similarity index 99% rename from obs/data/locale/gl-ES.ini rename to UI/data/locale/gl-ES.ini index 604d47e667be52..eb8b1ee200f489 100644 --- a/obs/data/locale/gl-ES.ini +++ b/UI/data/locale/gl-ES.ini @@ -247,6 +247,7 @@ Basic.MainMenu.Edit.AdvAudio="Propiedades de audio &avanzadas" Basic.MainMenu.SceneCollection="&Colección de escenas" Basic.MainMenu.Profile="&Perfil" + Basic.MainMenu.Help="&Axuda" Basic.MainMenu.Help.Website="Visitar sitio &web" Basic.MainMenu.Help.Logs="&Ficheiros de rexistro" @@ -366,6 +367,8 @@ Basic.Hotkeys.StopStreaming="Deter retransmisión" Basic.Hotkeys.StartRecording="Iniciar gravación" Basic.Hotkeys.StopRecording="Deter gravación" + + Hotkeys.Insert="Inserir" Hotkeys.Delete="Eliminar" Hotkeys.Home="Inicio" diff --git a/obs/data/locale/he-IL.ini b/UI/data/locale/he-IL.ini similarity index 99% rename from obs/data/locale/he-IL.ini rename to UI/data/locale/he-IL.ini index e6f30965d42202..cbeab9b032f940 100644 --- a/obs/data/locale/he-IL.ini +++ b/UI/data/locale/he-IL.ini @@ -308,6 +308,8 @@ Basic.MainMenu.View.StatusBar="&שורת מצב" Basic.MainMenu.SceneCollection="אוסף סצינות(&S)" Basic.MainMenu.Profile="פרופיל(&P)" +Basic.MainMenu.Tools="& כלים" + Basic.MainMenu.Help="עזרה(&H)" Basic.MainMenu.Help.Website="בקר אתר(&W)" Basic.MainMenu.Help.Logs="קבצי יומן רישום(&L)" @@ -468,6 +470,7 @@ Basic.Settings.Advanced.StreamDelay="השהיית זרם נתונים" Basic.Settings.Advanced.StreamDelay.Duration="משך זמן (בשניות)" Basic.Settings.Advanced.StreamDelay.Preserve="שמר נקודת חיתוך (השהייה מוגדלת) בעת חיבור מחדש" Basic.Settings.Advanced.StreamDelay.MemoryUsage="שימוש זיכרון משוער: %1 MB" +Basic.Settings.Advanced.Network="רשת" Basic.AdvAudio="מאפייני קול מתקדמים" Basic.AdvAudio.Name="שם" @@ -486,6 +489,10 @@ Basic.Hotkeys.StartRecording="התחל הקלטה" Basic.Hotkeys.StopRecording="עצור הקלטה" Basic.Hotkeys.SelectScene="עבור לסצנה" +Basic.SystemTray.Show="הצג" +Basic.SystemTray.Hide="הסתר" + + Hotkeys.Insert="הוסף" Hotkeys.Delete="מחק" Hotkeys.Home="בית" diff --git a/obs/data/locale/hr-HR.ini b/UI/data/locale/hr-HR.ini similarity index 98% rename from obs/data/locale/hr-HR.ini rename to UI/data/locale/hr-HR.ini index 1e339ab9b4ebb9..497e1a96433d80 100644 --- a/obs/data/locale/hr-HR.ini +++ b/UI/data/locale/hr-HR.ini @@ -308,6 +308,8 @@ Basic.MainMenu.View.StatusBar="&Statusna linija" Basic.MainMenu.SceneCollection="Kolekcija &scena" Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Tools="Ala&ti" + Basic.MainMenu.Help="Pomoć (&H)" Basic.MainMenu.Help.Website="Poseti stranicu (&W)" Basic.MainMenu.Help.Logs="&Log datoteke" @@ -327,6 +329,7 @@ Basic.Settings.General.Language="Jezik" Basic.Settings.General.WarnBeforeStartingStream="Prikaži prozor za potvrdu kada se započinju strimovi" Basic.Settings.General.WarnBeforeStoppingStream="Prikaži prozor za potvrdu kada se zaustavljaju strimovi" Basic.Settings.General.HideProjectorCursor="Sakrij pokazivač na projektorima" +Basic.Settings.General.ProjectorAlwaysOnTop="Uvek postavi projektor na vrh prozora" Basic.Settings.General.Snapping="Poravnavanje privlačenjem izvora" Basic.Settings.General.ScreenSnapping="Privuci izvore ivici ekrana" Basic.Settings.General.CenterSnapping="Privuci izvore horizontalnoj i vertikalnoj sredini" @@ -334,6 +337,8 @@ Basic.Settings.General.SourceSnapping="Privlačenje izvora ka drugim izvorima" Basic.Settings.General.SnapDistance="Osetljivost privlačenja" Basic.Settings.General.RecordWhenStreaming="Automatsko snimanje pri emitovanju" Basic.Settings.General.KeepRecordingWhenStreamStops="Nastavi snimati kada se emitovanje zaustavi" +Basic.Settings.General.SysTrayEnabled="Omogući ikonicu u sistemskom panelu" +Basic.Settings.General.SysTrayWhenStarted="Pri pokretanju minimiziraj na ikonicu u sistemskom panelu" Basic.Settings.Stream="Strim" Basic.Settings.Stream.StreamType="Vrsta strima" @@ -488,6 +493,11 @@ Basic.Hotkeys.StartRecording="Počni snimanje" Basic.Hotkeys.StopRecording="Zaustavi snimanje" Basic.Hotkeys.SelectScene="Prebaci na scenu" +Basic.SystemTray.Show="Prikaži" +Basic.SystemTray.Hide="Sakrij" + +Basic.SystemTray.Message.Reconnecting="Veza prekinuta. Ponovno uspostavljanje..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/hu-HU.ini b/UI/data/locale/hu-HU.ini similarity index 98% rename from obs/data/locale/hu-HU.ini rename to UI/data/locale/hu-HU.ini index 046a2e531365ea..f83119b3c1427d 100644 --- a/obs/data/locale/hu-HU.ini +++ b/UI/data/locale/hu-HU.ini @@ -48,6 +48,7 @@ Left="Bal" Right="Jobb" Top="Felső" Bottom="Alsó" +Reset="Újraindít" QuickTransitions.SwapScenes="Előnézeti/Kimeneti Jelenetek cseréje átmenet után" QuickTransitions.SwapScenesTT="Az előnézet és a kimeneti jelenet cseréje átmenet után (ha a kimenet eredeti jelenete még létezik).\nEz nincs kihatással a kimenet eredeti jelenetére." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="&Állapotsor" Basic.MainMenu.SceneCollection="&Jelenet gyűjtemény" Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Tools="&Eszközők" + Basic.MainMenu.Help="&Segítség" Basic.MainMenu.Help.Website="Weboldal meglátogatása" Basic.MainMenu.Help.Logs="&Naplófájlok" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Nyelv" Basic.Settings.General.WarnBeforeStartingStream="Megerősítő párbeszédpanel megjelenítése stream indításakor" Basic.Settings.General.WarnBeforeStoppingStream="Megerősítő párbeszédpanel megjelenítése stream leállításakor" Basic.Settings.General.HideProjectorCursor="Projektor nézetben a kurzor elrejtése" +Basic.Settings.General.ProjectorAlwaysOnTop="Projektorok mindig legfelül" Basic.Settings.General.Snapping="Forrás pozicionálásának igazítása" Basic.Settings.General.ScreenSnapping="Források igazítása a képernyő széléhez" Basic.Settings.General.CenterSnapping="Források vízszintes és függőleges középponthoz igazítása" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Források igazítása más forrásokhoz" Basic.Settings.General.SnapDistance="Igazítás érzékenysége" Basic.Settings.General.RecordWhenStreaming="Automatikus felvétel stream esetén" Basic.Settings.General.KeepRecordingWhenStreamStops="Felvétel folytatása a stream leállása esetén" +Basic.Settings.General.SysTrayEnabled="Tálca ikon elhelyezése" +Basic.Settings.General.SysTrayWhenStarted="Indításkor ikonként a tálcán" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Stream típusa" @@ -363,6 +369,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="Veszteségmentes minőség fig Basic.Settings.Output.Simple.Warn.MultipleQSV="Figyelem: Nem használható több különálló QSV kódoló streamelésre és felvételre egyidejűleg. Ha ön egyidejűleg kíván streamet és felvételt készíteni, akkor váltsa le a felvevő vagy a stream kódolóját." Basic.Settings.Output.Simple.Encoder.Software="Szoftver (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Hardver (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Hardver (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Hardver (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Szoftveres (x264 alacsony CPU használati készlet, növekvő fájlméret)" Basic.Settings.Output.VideoBitrate="Videó bitráta" @@ -488,6 +495,11 @@ Basic.Hotkeys.StartRecording="Felvétel indítása" Basic.Hotkeys.StopRecording="Felvétel leállítása" Basic.Hotkeys.SelectScene="Jelenethez kapcsolás" +Basic.SystemTray.Show="Mutat" +Basic.SystemTray.Hide="Elrejt" + +Basic.SystemTray.Message.Reconnecting="Szétkapcsolva. Újrakapcsolódás..." + Hotkeys.Insert="Beszúrás" Hotkeys.Delete="Törlés" Hotkeys.Home="Teteje" diff --git a/obs/data/locale/it-IT.ini b/UI/data/locale/it-IT.ini similarity index 99% rename from obs/data/locale/it-IT.ini rename to UI/data/locale/it-IT.ini index 44f2cff848c1f6..db93731da0b015 100644 --- a/obs/data/locale/it-IT.ini +++ b/UI/data/locale/it-IT.ini @@ -308,6 +308,7 @@ Basic.MainMenu.View.StatusBar="&Barra di stato" Basic.MainMenu.SceneCollection="&Collezione scene" Basic.MainMenu.Profile="&Profilo" + Basic.MainMenu.Help="Aiuto (&H)" Basic.MainMenu.Help.Website="Visita il sito" Basic.MainMenu.Help.Logs="File di &log" @@ -488,6 +489,8 @@ Basic.Hotkeys.StartRecording="Inizia registrazione" Basic.Hotkeys.StopRecording="Ferma registrazione" Basic.Hotkeys.SelectScene="Passa alla scena" + + Hotkeys.Insert="Ins" Hotkeys.Delete="Canc" Hotkeys.Home="Home" diff --git a/obs/data/locale/ja-JP.ini b/UI/data/locale/ja-JP.ini similarity index 97% rename from obs/data/locale/ja-JP.ini rename to UI/data/locale/ja-JP.ini index 644dfbca0e0685..9fcd29c2429c43 100644 --- a/obs/data/locale/ja-JP.ini +++ b/UI/data/locale/ja-JP.ini @@ -48,6 +48,7 @@ Left="左" Right="右" Top="上" Bottom="下" +Reset="リセット" QuickTransitions.SwapScenes="トランジション後にプレビュー/出力シーンを入れ替え" QuickTransitions.SwapScenesTT="(出力のオリジナルシーンがまだ存在する場合)、トランジション後のプレビューと出力シーンを入れ替えます。\nこれは出力のオリジナルシーンに加えられた可能性があるすべての変更を元に戻しません。" @@ -218,9 +219,9 @@ Basic.Filters="フィルタ" Basic.Filters.AsyncFilters="音声/映像フィルタ" Basic.Filters.AudioFilters="音声フィルタ" Basic.Filters.EffectFilters="エフェクトフィルタ" -Basic.Filters.Title="'%1' のためのフィルター" -Basic.Filters.AddFilter.Title="フィルター名" -Basic.Filters.AddFilter.Text="フィルターの名前を指定してください" +Basic.Filters.Title="'%1' のためのフィルタ" +Basic.Filters.AddFilter.Title="フィルタ名" +Basic.Filters.AddFilter.Text="フィルタの名前を指定してください" Basic.TransformWindow="シーン アイテム 変換" Basic.TransformWindow.Position="位置" @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="ステータスバー(&S)" Basic.MainMenu.SceneCollection="シーンコレクション(&S)" Basic.MainMenu.Profile="プロファイル(&P)" +Basic.MainMenu.Tools="ツール(&T)" + Basic.MainMenu.Help="ヘルプ(&H)" Basic.MainMenu.Help.Website="ウェブサイト(&W)" Basic.MainMenu.Help.Logs="ログファイル(&L)" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="言語" Basic.Settings.General.WarnBeforeStartingStream="配信を開始するときに確認ダイアログを表示する" Basic.Settings.General.WarnBeforeStoppingStream="配信を停止するときに確認ダイアログを表示する" Basic.Settings.General.HideProjectorCursor="プロジェクター上のカーソルを非表示にする" +Basic.Settings.General.ProjectorAlwaysOnTop="プロジェクタを常に手前に表示させる" Basic.Settings.General.Snapping="ソース配置のスナップ" Basic.Settings.General.ScreenSnapping="画面の端にソースをスナップする" Basic.Settings.General.CenterSnapping="水平方向および垂直方向の中心にソースをスナップする" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="他のソースにソースをスナッ Basic.Settings.General.SnapDistance="スナップ感度" Basic.Settings.General.RecordWhenStreaming="配信時に自動的に録画" Basic.Settings.General.KeepRecordingWhenStreamStops="配信が停止しても録画を継続" +Basic.Settings.General.SysTrayEnabled="システムトレイアイコンを有効にする" +Basic.Settings.General.SysTrayWhenStarted="起動時にシステムトレイへ最小化" Basic.Settings.Stream="配信" Basic.Settings.Stream.StreamType="配信種別" @@ -363,6 +369,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="無損失品質警告!" Basic.Settings.Output.Simple.Warn.MultipleQSV="警告: 配信と同時に録画する場合複数の独立した QSV エンコーダは使用できません。 配信と同時に録画したい場合、配信エンコーダか録画エンコーダのどちらかを変更してください。" Basic.Settings.Output.Simple.Encoder.Software="ソフトウェア (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="ハードウェア (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="ハードウェア (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="ハードウェア (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="ソフトウェア (x264 CPU使用率の低いプリセット、ファイルサイズ増加)" Basic.Settings.Output.VideoBitrate="映像ビットレート" @@ -420,7 +427,7 @@ Basic.Settings.Video="映像" Basic.Settings.Video.Adapter="ビデオアダプター:" Basic.Settings.Video.BaseResolution="基本 (キャンバス) 解像度:" Basic.Settings.Video.ScaledResolution="出力 (スケーリング) 解像度:" -Basic.Settings.Video.DownscaleFilter="縮小フィルター:" +Basic.Settings.Video.DownscaleFilter="縮小フィルタ:" Basic.Settings.Video.DisableAeroWindows="エアロ無効 (Windows のみ)" Basic.Settings.Video.FPS="FPS:" Basic.Settings.Video.FPSCommon="FPS 共通値" @@ -488,6 +495,11 @@ Basic.Hotkeys.StartRecording="録画開始" Basic.Hotkeys.StopRecording="録画終了" Basic.Hotkeys.SelectScene="シーン切り替え" +Basic.SystemTray.Show="表示" +Basic.SystemTray.Hide="非表示" + +Basic.SystemTray.Message.Reconnecting="切断。 再接続..." + Hotkeys.Insert="Insertキー" Hotkeys.Delete="Deleteキー" Hotkeys.Home="Homeキー" diff --git a/obs/data/locale/ko-KR.ini b/UI/data/locale/ko-KR.ini similarity index 98% rename from obs/data/locale/ko-KR.ini rename to UI/data/locale/ko-KR.ini index 4f545f89bf24d1..50353f0b588034 100644 --- a/obs/data/locale/ko-KR.ini +++ b/UI/data/locale/ko-KR.ini @@ -48,6 +48,7 @@ Left="왼쪽" Right="오른쪽" Top="위" Bottom="아래" +Reset="초기화" QuickTransitions.SwapScenes="전환 후 미리 보기/출력 장면을 교체" QuickTransitions.SwapScenesTT="(만약 출력 쪽 원본 장면이 있을 때) 전환 작업 이후 미리 보기와 출력 장면을 교체합니다. \n출력 쪽 원본 장면에서 변경한 내용은 사라지지 않습니다." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="상태 표시줄(&S)" Basic.MainMenu.SceneCollection="장면 모음(&S)" Basic.MainMenu.Profile="프로파일(&P)" +Basic.MainMenu.Tools="도구(&T)" + Basic.MainMenu.Help="도움말(&H)" Basic.MainMenu.Help.Website="웹사이트 방문(&W)" Basic.MainMenu.Help.Logs="기록 파일(&L)" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="언어" Basic.Settings.General.WarnBeforeStartingStream="방송을 시작할 때 확인 대화 상자 표시" Basic.Settings.General.WarnBeforeStoppingStream="방송을 중단할 때 확인 대화 상자 표시" Basic.Settings.General.HideProjectorCursor="프로젝터 위 커서 숨기기" +Basic.Settings.General.ProjectorAlwaysOnTop="프로젝터를 항상 위로" Basic.Settings.General.Snapping="소스를 자석처럼 달라붙여서 정렬" Basic.Settings.General.ScreenSnapping="소스를 화면 변두리에 붙임" Basic.Settings.General.CenterSnapping="소스를 수평과 수직 중앙에 붙임" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="소스를 다른 소스에 붙임" Basic.Settings.General.SnapDistance="자석 감도" Basic.Settings.General.RecordWhenStreaming="방송 시 자동으로 녹화" Basic.Settings.General.KeepRecordingWhenStreamStops="방송을 중단하더라도 녹화는 유지" +Basic.Settings.General.SysTrayEnabled="시스템 트레이 아이콘 활성화" +Basic.Settings.General.SysTrayWhenStarted="시작할 때 시스템 트레이로 최소화" Basic.Settings.Stream="방송" Basic.Settings.Stream.StreamType="방송 형식" @@ -488,6 +494,11 @@ Basic.Hotkeys.StartRecording="녹화 시작" Basic.Hotkeys.StopRecording="녹화 중단" Basic.Hotkeys.SelectScene="장면 전환" +Basic.SystemTray.Show="보이기" +Basic.SystemTray.Hide="숨기기" + +Basic.SystemTray.Message.Reconnecting="접속이 끊김. 재접속 시도 중..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/lt-LT.ini b/UI/data/locale/lt-LT.ini similarity index 99% rename from obs/data/locale/lt-LT.ini rename to UI/data/locale/lt-LT.ini index 00c8f27f9b7332..d3f837b519317c 100644 --- a/obs/data/locale/lt-LT.ini +++ b/UI/data/locale/lt-LT.ini @@ -271,6 +271,9 @@ Basic.MainMenu.Edit.Order.MoveToBottom="Perkelti į apačią" + + + diff --git a/obs/data/locale/ms-MY.ini b/UI/data/locale/ms-MY.ini similarity index 99% rename from obs/data/locale/ms-MY.ini rename to UI/data/locale/ms-MY.ini index 924db64de139c3..3970dbb0fe6ae8 100644 --- a/obs/data/locale/ms-MY.ini +++ b/UI/data/locale/ms-MY.ini @@ -255,6 +255,7 @@ Basic.MainMenu.Edit.Order.MoveDown="Gerakkan ke &bawah" Basic.MainMenu.Profile="&Profil" + Basic.MainMenu.Help.Website="Lawat laman &Web" Basic.MainMenu.Help.Logs="Fail &Log" Basic.MainMenu.Help.Logs.ShowLogs="&Tunjukkan Fail-Fail Log" @@ -297,6 +298,8 @@ Basic.Settings.Advanced.FormatWarning="Amaran:Format warna selain daripada 'NV12 + + Hotkeys.NumpadDecimal="Perpuluhan Numpad" diff --git a/obs/data/locale/nb-NO.ini b/UI/data/locale/nb-NO.ini similarity index 99% rename from obs/data/locale/nb-NO.ini rename to UI/data/locale/nb-NO.ini index 9683ab68ba1c95..1fafc39668f1da 100644 --- a/obs/data/locale/nb-NO.ini +++ b/UI/data/locale/nb-NO.ini @@ -296,6 +296,7 @@ Basic.MainMenu.Edit.AdvAudio="&Avanserte lydinstillinger" Basic.MainMenu.SceneCollection="&Scenesamling" Basic.MainMenu.Profile="&Profil" + Basic.MainMenu.Help="&Hjelp" Basic.MainMenu.Help.Website="Besøk &nettsted" Basic.MainMenu.Help.Logs="&Loggfiler" @@ -469,6 +470,8 @@ Basic.Hotkeys.StartRecording="Start opptak" Basic.Hotkeys.StopRecording="Stopp opptak" Basic.Hotkeys.SelectScene="Bytt til scene" + + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/nl-NL.ini b/UI/data/locale/nl-NL.ini similarity index 98% rename from obs/data/locale/nl-NL.ini rename to UI/data/locale/nl-NL.ini index e39ba29d05f09d..93c31a08bb4e29 100644 --- a/obs/data/locale/nl-NL.ini +++ b/UI/data/locale/nl-NL.ini @@ -48,6 +48,7 @@ Left="Links" Right="Rechts" Top="Boven" Bottom="Onder" +Reset="Herstellen" QuickTransitions.SwapScenes="Preview-/uitvoerscenes verwisselen na overgang" QuickTransitions.SwapScenesTT="Verwisselt de preview- en uitvoercenes na een overgang (als de originele uitvoerscène nog bestaat.)\nDit zal geen veranderingen ongedaan maken die mogelijk zijn gemaakt aan de originele uitvoerscène." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="&Statusbalk" Basic.MainMenu.SceneCollection="&Scèneverzameling" Basic.MainMenu.Profile="&Profiel" +Basic.MainMenu.Tools="&Tools" + Basic.MainMenu.Help="&Help" Basic.MainMenu.Help.Website="&Website Bezoeken" Basic.MainMenu.Help.Logs="&Logbestanden" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Taal" Basic.Settings.General.WarnBeforeStartingStream="Laat bevestigingsvenster zien bij het starten van streams" Basic.Settings.General.WarnBeforeStoppingStream="Laat bevestiginsvenster zien bij het stoppen van streams" Basic.Settings.General.HideProjectorCursor="Verberg cursor boven projectors" +Basic.Settings.General.ProjectorAlwaysOnTop="Houd projectoren altijd bovenaan" Basic.Settings.General.Snapping="Bronuitlijning" Basic.Settings.General.ScreenSnapping="Bronnen uitlijnen op de rand van het scherm" Basic.Settings.General.CenterSnapping="Bronnen uitlijnen op het horizontale en verticale midden" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Bronnen uitlijnen op andere bronnen" Basic.Settings.General.SnapDistance="Gevoeligheid" Basic.Settings.General.RecordWhenStreaming="Stream automatisch opnemen" Basic.Settings.General.KeepRecordingWhenStreamStops="Opname voortzetten als de stream stopt" +Basic.Settings.General.SysTrayEnabled="Systeemvakicoon weergeven" +Basic.Settings.General.SysTrayWhenStarted="Naar systeemvak minimaliseren bij opstarten" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Stream Type" @@ -363,6 +369,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="Lossless kwaliteit waarschuwin Basic.Settings.Output.Simple.Warn.MultipleQSV="Waarschuwing: Je kunt niet meerdere QSV encoders gebruiken wanneer je tegelijkertijd aan het streamen en opnemen bent. Als je tegelijkertijd wil streamen en opnemen, verander dan de opname- of streamencoder." Basic.Settings.Output.Simple.Encoder.Software="Software (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Hardware (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Hardware (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Hardware (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Software (x264 laag cpu gebruik, verhoogt bestandsgrootte)" Basic.Settings.Output.VideoBitrate="Video Bitrate" @@ -488,6 +495,11 @@ Basic.Hotkeys.StartRecording="Opname Starten" Basic.Hotkeys.StopRecording="Opname Stoppen" Basic.Hotkeys.SelectScene="Wissel naar scène" +Basic.SystemTray.Show="Weergeven" +Basic.SystemTray.Hide="Verbergen" + +Basic.SystemTray.Message.Reconnecting="Verbinding verbroken. Opnieuw verbinden..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/pl-PL.ini b/UI/data/locale/pl-PL.ini similarity index 98% rename from obs/data/locale/pl-PL.ini rename to UI/data/locale/pl-PL.ini index 007ff2c9a13921..14018ad8a741eb 100644 --- a/obs/data/locale/pl-PL.ini +++ b/UI/data/locale/pl-PL.ini @@ -48,6 +48,7 @@ Left="Od lewej" Right="Od prawej" Top="Od góry" Bottom="Od dołu" +Reset="Reset" QuickTransitions.SwapScenes="Zamień podgląd/wyjście scen po przejściu" QuickTransitions.SwapScenesTT="Zamienia podgląd i wyjście scen po przejściu (jeżeli wyjście oryginalnej sceny istnieje).\nNie przywraca to zmian jakie zostały dokonane w oryginalnej scenie." @@ -306,9 +307,11 @@ Basic.MainMenu.View.SceneTransitions="Efekty &przejścia scen" Basic.MainMenu.View.StatusBar="Pasek &stanu" Basic.MainMenu.SceneCollection="Zbiór &scen" -Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Profile="P&rofil" -Basic.MainMenu.Help="&Pomoc" +Basic.MainMenu.Tools="&Narzędzia" + +Basic.MainMenu.Help="P&omoc" Basic.MainMenu.Help.Website="Od&wiedź naszą stronę" Basic.MainMenu.Help.Logs="P&liki dziennika" Basic.MainMenu.Help.Logs.ShowLogs="Pokaż pliki dziennika (&s)" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Język" Basic.Settings.General.WarnBeforeStartingStream="Pokaż komunikat potwierdzenia uruchomienia streamowania" Basic.Settings.General.WarnBeforeStoppingStream="Pokaż komunikat potwierdzenia zatrzymania streamowania" Basic.Settings.General.HideProjectorCursor="Ukryj kursor podglądu na pełnym ekranie" +Basic.Settings.General.ProjectorAlwaysOnTop="Podgląd na pełnym ekranie zawsze na wierzchu" Basic.Settings.General.Snapping="Przyciąganie elementów źródłowych" Basic.Settings.General.ScreenSnapping="Przyciągaj do krawędzi ekranu" Basic.Settings.General.CenterSnapping="Przyciągaj do poziomego i pionowego środka" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Przyciągaj źródła do innych źródeł Basic.Settings.General.SnapDistance="Czułość przyciągania" Basic.Settings.General.RecordWhenStreaming="Automatyczne nagrywanie streamu" Basic.Settings.General.KeepRecordingWhenStreamStops="Zachowaj nagranie po zatrzymaniu streamu" +Basic.Settings.General.SysTrayEnabled="Wyświetlaj ikonę w zasobniku systemowym" +Basic.Settings.General.SysTrayWhenStarted="Minimalizuj do zasobnika systemowego podczas uruchamiania" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Typ streamu" @@ -363,6 +369,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="Ostrzeżenie o bezstratnej jak Basic.Settings.Output.Simple.Warn.MultipleQSV="Ostrzeżenie: Korzystanie z wielu różnych enkoderów QSV do streamowania i nagrywania jest niedozwolone. Jeżeli chcesz streamować i nagrywać w tym samym czasie, zmień ustawienia enkodera nagrywania bądź streamowania." Basic.Settings.Output.Simple.Encoder.Software="Programowy (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Sprzętowy (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Sprzętowy (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Sprzętowy (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Programowy (x264 ustawienia małego zużycia procesora, zwiększa wielkość pliku)" Basic.Settings.Output.VideoBitrate="Bitrate obrazu" @@ -488,6 +495,11 @@ Basic.Hotkeys.StartRecording="Rozpocznij nagrywanie" Basic.Hotkeys.StopRecording="Zatrzymaj nagrywanie" Basic.Hotkeys.SelectScene="Przełącz na scenę" +Basic.SystemTray.Show="Pokaż" +Basic.SystemTray.Hide="Ukryj" + +Basic.SystemTray.Message.Reconnecting="Rozłączony. Łączę ponownie..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/pt-BR.ini b/UI/data/locale/pt-BR.ini similarity index 95% rename from obs/data/locale/pt-BR.ini rename to UI/data/locale/pt-BR.ini index 139997a223b6fa..15cf8d3a822a88 100644 --- a/obs/data/locale/pt-BR.ini +++ b/UI/data/locale/pt-BR.ini @@ -145,7 +145,10 @@ Basic.DisplayCapture="Captura de tela" Basic.Main.PreviewConextMenu.Enable="Ativar pré-vizualização" +ScaleFiltering="Filtragem de escala" +ScaleFiltering.Point="Ponto" ScaleFiltering.Bilinear="Bilinear" +ScaleFiltering.Bicubic="Bicúbico" Deinterlacing="Desentrelaçamento" Deinterlacing.Discard="Descartar" @@ -276,6 +279,7 @@ Basic.MainMenu.Edit.Undo="&Desfazer" Basic.MainMenu.Edit.Redo="&Refazer" Basic.MainMenu.Edit.UndoAction="&Desfazer $1" Basic.MainMenu.Edit.RedoAction="&Refazer $1" +Basic.MainMenu.Edit.LockPreview="&Bloquear pré-visualização" Basic.MainMenu.Edit.Transform="&Transformar" Basic.MainMenu.Edit.Transform.EditTransform="&Editar Transformação..." Basic.MainMenu.Edit.Transform.ResetTransform="&Limpar Transformação" @@ -295,12 +299,16 @@ Basic.MainMenu.Edit.Order.MoveToBottom="Mover para a &Base" Basic.MainMenu.Edit.AdvAudio="&Propriedades de áudio avançadas" Basic.MainMenu.View="Mostrar" +Basic.MainMenu.View.Toolbars="&Barras de Ferramentas" +Basic.MainMenu.View.Toolbars.Listboxes="Caixa de &Listagem" Basic.MainMenu.View.SceneTransitions="Transições de Cena" Basic.MainMenu.View.StatusBar="Barra de Status" Basic.MainMenu.SceneCollection="&Coleção de cena" Basic.MainMenu.Profile="&Perfil" +Basic.MainMenu.Tools="Ferramentas (&T)" + Basic.MainMenu.Help="&Ajuda" Basic.MainMenu.Help.Website="Visitar &website" Basic.MainMenu.Help.Logs="&Arquivos de Log" @@ -319,12 +327,17 @@ Basic.Settings.General.Theme="Tema" Basic.Settings.General.Language="Idioma" Basic.Settings.General.WarnBeforeStartingStream="Mostrar diálogo de confirmação quando iniciar transmissões" Basic.Settings.General.WarnBeforeStoppingStream="Mostrar diálogo de confirmação quando terminar transmissões" +Basic.Settings.General.HideProjectorCursor="Ocultar o cursor sobre projetores" +Basic.Settings.General.ProjectorAlwaysOnTop="Colocar projetores sempre no topo" Basic.Settings.General.Snapping="Alinhamentos com encaixe na Cena" Basic.Settings.General.ScreenSnapping="Encaixar Fontes ás bordas da tela" Basic.Settings.General.CenterSnapping="Encaixar Fontes aos centros vertical e horizontal" Basic.Settings.General.SourceSnapping="Encaixar fontes com outras fontes" Basic.Settings.General.SnapDistance="Sensibilidade de Encaixamento" +Basic.Settings.General.RecordWhenStreaming="Gravar automaticamente quando estiver transmitindo" Basic.Settings.General.KeepRecordingWhenStreamStops="Continuar gravando quando a transmissão parar" +Basic.Settings.General.SysTrayEnabled="Habilitar o ícone de bandeja do sistema" +Basic.Settings.General.SysTrayWhenStarted="Minimizar para a bandeja do sistema quando iniciar" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Tipo de Stream" @@ -351,6 +364,7 @@ Basic.Settings.Output.Simple.Warn.Encoder="Aviso: Gravar com um codificador de s Basic.Settings.Output.Simple.Warn.Lossless="Aviso: Qualidade Lossless gera arquivos muito grandes! A qualidade Lossless pode usar mais de 7 gigabytes de espaço em disco por minuto em altas resoluções e framerates. Lossless não é recomendada para gravações longas, a menos que se tenha uma grande quantidade de espaço em disco disponível." Basic.Settings.Output.Simple.Warn.Lossless.Msg="Tem certeza que deseja usar qualidade lossless?" Basic.Settings.Output.Simple.Warn.Lossless.Title="Aviso de qualidade lossless!" +Basic.Settings.Output.Simple.Warn.MultipleQSV="Aviso: Você não pode usar vários codificadores QSV separados quando estiver transmitindo e gravando ao mesmo tempo. Se você deseja transmitir e gravar ao mesmo tempo, por favor altere o codificador de gravação ou o de transmissão." Basic.Settings.Output.Simple.Encoder.Software="Software (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Hardware (QSV)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Hardware (NVENC)" @@ -458,6 +472,8 @@ Basic.Settings.Advanced.StreamDelay="Atraso da transmissão" Basic.Settings.Advanced.StreamDelay.Duration="Duração (segundos)" Basic.Settings.Advanced.StreamDelay.Preserve="Preservar o ponto de corte (aumento de atraso) quando reconectar" Basic.Settings.Advanced.StreamDelay.MemoryUsage="Uso de memória estimado: %1 MB" +Basic.Settings.Advanced.Network="Rede" +Basic.Settings.Advanced.Network.BindToIP="Transmitir pelo IP" Basic.AdvAudio="Propriedades de áudio avançadas" Basic.AdvAudio.Name="Nome" @@ -476,6 +492,11 @@ Basic.Hotkeys.StartRecording="Iniciar gravação" Basic.Hotkeys.StopRecording="Parar gravação" Basic.Hotkeys.SelectScene="Mudar de cena" +Basic.SystemTray.Show="Exibir" +Basic.SystemTray.Hide="Ocultar" + +Basic.SystemTray.Message.Reconnecting="Desconectado. Reconectando-se..." + Hotkeys.Insert="Inserir" Hotkeys.Delete="Deletar" Hotkeys.Home="Início" diff --git a/obs/data/locale/pt-PT.ini b/UI/data/locale/pt-PT.ini similarity index 99% rename from obs/data/locale/pt-PT.ini rename to UI/data/locale/pt-PT.ini index e22eb7aaa2f5e4..6edac0afe567ec 100644 --- a/obs/data/locale/pt-PT.ini +++ b/UI/data/locale/pt-PT.ini @@ -290,6 +290,7 @@ Basic.MainMenu.Edit.AdvAudio="Propriedades &avançadas de áudio" Basic.MainMenu.SceneCollection="Coleção de cena" Basic.MainMenu.Profile="&Perfil" + Basic.MainMenu.Help="&Ajuda" Basic.MainMenu.Help.Website="Visitar &website" Basic.MainMenu.Help.Logs="Ficeiros de &Log" @@ -451,6 +452,8 @@ Basic.Hotkeys.StartRecording="Iniciar gravação" Basic.Hotkeys.StopRecording="Parar gravação" Basic.Hotkeys.SelectScene="Mudar para cena" + + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/ro-RO.ini b/UI/data/locale/ro-RO.ini similarity index 99% rename from obs/data/locale/ro-RO.ini rename to UI/data/locale/ro-RO.ini index 9abadd4df6cf78..e58bc1412eff55 100644 --- a/obs/data/locale/ro-RO.ini +++ b/UI/data/locale/ro-RO.ini @@ -291,6 +291,7 @@ Basic.MainMenu.Edit.AdvAudio="Proprietăți audio &avansate" Basic.MainMenu.SceneCollection="Colecție de &scene" Basic.MainMenu.Profile="&Profil" + Basic.MainMenu.Help="&Ajutor" Basic.MainMenu.Help.Website="Vizitează site-ul &web" Basic.MainMenu.Help.Logs="Fișiere jurna&l" @@ -460,6 +461,8 @@ Basic.Hotkeys.StartRecording="Pornește înregistrarea" Basic.Hotkeys.StopRecording="Oprește înregistrarea" Basic.Hotkeys.SelectScene="Comută la scenă" + + Hotkeys.Insert="Inserează" Hotkeys.Delete="Șterge" Hotkeys.Home="Home" diff --git a/obs/data/locale/ru-RU.ini b/UI/data/locale/ru-RU.ini similarity index 97% rename from obs/data/locale/ru-RU.ini rename to UI/data/locale/ru-RU.ini index 3aee290e53a521..b170c8b80e20ff 100644 --- a/obs/data/locale/ru-RU.ini +++ b/UI/data/locale/ru-RU.ini @@ -48,6 +48,7 @@ Left="Слева" Right="Справа" Top="Сверху" Bottom="Снизу" +Reset="Сбросить" QuickTransitions.SwapScenes="Замена Просмотра/Вывода Сцены После Перехода" QuickTransitions.SwapScenesTT="Замена просмотра и вывода сцены после перехода (если выходная оригинальная сцена до сих пор существует).\nЭто будет не отмена каких-либо изменений, что, возможно, было сделано в выходной оригинальной сцены." @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="&Строка состояния" Basic.MainMenu.SceneCollection="Коллекция сцен" Basic.MainMenu.Profile="Профиль" +Basic.MainMenu.Tools="&Инструменты" + Basic.MainMenu.Help="&Справка" Basic.MainMenu.Help.Website="Посетить &веб-сайт" Basic.MainMenu.Help.Logs="&Log файлы" @@ -327,13 +330,16 @@ Basic.Settings.General.Language="Язык" Basic.Settings.General.WarnBeforeStartingStream="Показывать окно подтверждения при запуске трансляции" Basic.Settings.General.WarnBeforeStoppingStream="Показывать окно подтверждения при остановке трансляции" Basic.Settings.General.HideProjectorCursor="Скрыть курсор за проекторы" +Basic.Settings.General.ProjectorAlwaysOnTop="Показывать проекторы поверх всего остального" Basic.Settings.General.Snapping="Привязка расположения источника" Basic.Settings.General.ScreenSnapping="Привязка к краю экрана" Basic.Settings.General.CenterSnapping="Привязка к центру по горизонтали и вертикали" Basic.Settings.General.SourceSnapping="Привязка к другим источникам" Basic.Settings.General.SnapDistance="Чувствительность привязки" Basic.Settings.General.RecordWhenStreaming="Автоматическая запись при стриме" -Basic.Settings.General.KeepRecordingWhenStreamStops="Сохранить запись, когда стрим остановится" +Basic.Settings.General.KeepRecordingWhenStreamStops="Продолжить запись, когда стрим остановится" +Basic.Settings.General.SysTrayEnabled="Показывать иконку в системном трее" +Basic.Settings.General.SysTrayWhenStarted="Скрывать окно в системный трей при запуске" Basic.Settings.Stream="Вещание" Basic.Settings.Stream.StreamType="Тип вещания" @@ -363,6 +369,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Title="Предупреждение Basic.Settings.Output.Simple.Warn.MultipleQSV="Предупреждение: вы не можете использовать несколько различных QSV-кодировщиков при одновременной трансляции и записи. Если вы хотите одновременно и транслировать, и записывать, поменяйте либо кодировщик вещания, либо кодировщик записи." Basic.Settings.Output.Simple.Encoder.Software="Программный (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Аппаратный (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Аппаратный (AМD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Аппаратный (NVENC)" Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Программный (x264 с низкой нагрузкой на ЦП, увеличивает размер файла)" Basic.Settings.Output.VideoBitrate="Видео битрейт" @@ -488,6 +495,11 @@ Basic.Hotkeys.StartRecording="Начать запись" Basic.Hotkeys.StopRecording="Остановить запись" Basic.Hotkeys.SelectScene="Перейти на сцену" +Basic.SystemTray.Show="Показать" +Basic.SystemTray.Hide="Скрыть" + +Basic.SystemTray.Message.Reconnecting="Соединение потеряно. Подключаемся заново..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/sk-SK.ini b/UI/data/locale/sk-SK.ini similarity index 99% rename from obs/data/locale/sk-SK.ini rename to UI/data/locale/sk-SK.ini index 45db79573fe5bb..bec05a6df6f9f3 100644 --- a/obs/data/locale/sk-SK.ini +++ b/UI/data/locale/sk-SK.ini @@ -177,6 +177,7 @@ Basic.MainMenu.Edit.Order.MoveToBottom="Premiestniť naspodok (&B)" + Basic.MainMenu.Help="Pomoc (&H)" Basic.MainMenu.Help.Logs="&Log súbory" Basic.MainMenu.Help.Logs.ShowLogs="Zobraziť log &súbory" @@ -229,3 +230,5 @@ Basic.Settings.Audio.Channels="Kanály" + + diff --git a/obs/data/locale/sl-SI.ini b/UI/data/locale/sl-SI.ini similarity index 99% rename from obs/data/locale/sl-SI.ini rename to UI/data/locale/sl-SI.ini index adb56a35462282..780c52f285a619 100644 --- a/obs/data/locale/sl-SI.ini +++ b/UI/data/locale/sl-SI.ini @@ -174,6 +174,7 @@ Basic.MainMenu.Edit.Order.MoveToBottom="Premakni na &Dno" + Basic.MainMenu.Help="&Pomoč" Basic.MainMenu.Help.Logs="&Dnevniki" Basic.MainMenu.Help.Logs.ShowLogs="&Pokaži Zapisnik" @@ -238,3 +239,5 @@ Basic.Settings.Advanced.Video.ColorRange.Full="Celotno" + + diff --git a/obs/data/locale/sr-CS.ini b/UI/data/locale/sr-CS.ini similarity index 98% rename from obs/data/locale/sr-CS.ini rename to UI/data/locale/sr-CS.ini index 35b99aa27d2beb..2097d044057bcf 100644 --- a/obs/data/locale/sr-CS.ini +++ b/UI/data/locale/sr-CS.ini @@ -308,6 +308,8 @@ Basic.MainMenu.View.StatusBar="&Statusna linija" Basic.MainMenu.SceneCollection="Kolekcija &scena" Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Tools="Ala&ti" + Basic.MainMenu.Help="Pomoć (&H)" Basic.MainMenu.Help.Website="Poseti stranicu (&W)" Basic.MainMenu.Help.Logs="&Log datoteke" @@ -327,6 +329,7 @@ Basic.Settings.General.Language="Jezik" Basic.Settings.General.WarnBeforeStartingStream="Prikaži prozor za potvrdu kada se započinju strimovi" Basic.Settings.General.WarnBeforeStoppingStream="Prikaži prozor za potvrdu kada se zaustavljaju strimovi" Basic.Settings.General.HideProjectorCursor="Sakrij pokazivač na projektorima" +Basic.Settings.General.ProjectorAlwaysOnTop="Uvek postavi projektor na vrh prozora" Basic.Settings.General.Snapping="Poravnavanje privlačenjem izvora" Basic.Settings.General.ScreenSnapping="Privuci izvore ivici ekrana" Basic.Settings.General.CenterSnapping="Privuci izvore horizontalnoj i vertikalnoj sredini" @@ -334,6 +337,8 @@ Basic.Settings.General.SourceSnapping="Privlačenje izvora ka drugim izvorima" Basic.Settings.General.SnapDistance="Osetljivost privlačenja" Basic.Settings.General.RecordWhenStreaming="Automatsko snimanje pri emitovanju" Basic.Settings.General.KeepRecordingWhenStreamStops="Nastavi snimati kada se emitovanje zaustavi" +Basic.Settings.General.SysTrayEnabled="Omogući ikonicu u sistemskom panelu" +Basic.Settings.General.SysTrayWhenStarted="Pri pokretanju minimiziraj na ikonicu u sistemskom panelu" Basic.Settings.Stream="Strim" Basic.Settings.Stream.StreamType="Vrsta strima" @@ -488,6 +493,11 @@ Basic.Hotkeys.StartRecording="Počni snimanje" Basic.Hotkeys.StopRecording="Zaustavi snimanje" Basic.Hotkeys.SelectScene="Prebaci na scenu" +Basic.SystemTray.Show="Prikaži" +Basic.SystemTray.Hide="Sakrij" + +Basic.SystemTray.Message.Reconnecting="Veza prekinuta. Ponovno uspostavljanje..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/sr-SP.ini b/UI/data/locale/sr-SP.ini similarity index 98% rename from obs/data/locale/sr-SP.ini rename to UI/data/locale/sr-SP.ini index 95d6e8ca2e3c6e..a9c5a4483ad7ee 100644 --- a/obs/data/locale/sr-SP.ini +++ b/UI/data/locale/sr-SP.ini @@ -308,6 +308,8 @@ Basic.MainMenu.View.StatusBar="Статусна линија (&S)" Basic.MainMenu.SceneCollection="Колекција сцена (&S)" Basic.MainMenu.Profile="Профил (&P)" +Basic.MainMenu.Tools="Алати (&T)" + Basic.MainMenu.Help="Помоћ (&H)" Basic.MainMenu.Help.Website="Посети страницу (&W)" Basic.MainMenu.Help.Logs="Лог датотеке (&L)" @@ -327,6 +329,7 @@ Basic.Settings.General.Language="Језик" Basic.Settings.General.WarnBeforeStartingStream="Прикажи прозор за потврду када се започињу стримови" Basic.Settings.General.WarnBeforeStoppingStream="Прикажи прозор за потврду када се заустављају стримови" Basic.Settings.General.HideProjectorCursor="Сакриј показивач на пројекторима" +Basic.Settings.General.ProjectorAlwaysOnTop="Увек постави пројектор на врх прозора" Basic.Settings.General.Snapping="Поравнавање привлачењем извора" Basic.Settings.General.ScreenSnapping="Привуци изворе ивици екрана" Basic.Settings.General.CenterSnapping="Привуци изворе хоризонталној и вертикалној средини" @@ -334,6 +337,8 @@ Basic.Settings.General.SourceSnapping="Привлачење извора ка д Basic.Settings.General.SnapDistance="Осетљивост привлачења" Basic.Settings.General.RecordWhenStreaming="Аутоматско снимање при емитовању" Basic.Settings.General.KeepRecordingWhenStreamStops="Настави снимати када се емитовање заустави" +Basic.Settings.General.SysTrayEnabled="Омогући иконицу у системском панелу" +Basic.Settings.General.SysTrayWhenStarted="При покретању минимизирај на иконицу у системском панелу" Basic.Settings.Stream="Стрим" Basic.Settings.Stream.StreamType="Врста стрима" @@ -488,6 +493,11 @@ Basic.Hotkeys.StartRecording="Почни снимање" Basic.Hotkeys.StopRecording="Заустави снимање" Basic.Hotkeys.SelectScene="Пребаци на сцену" +Basic.SystemTray.Show="Прикажи" +Basic.SystemTray.Hide="Сакриј" + +Basic.SystemTray.Message.Reconnecting="Веза прекинута. Поновно успостављање..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/sv-SE.ini b/UI/data/locale/sv-SE.ini similarity index 95% rename from obs/data/locale/sv-SE.ini rename to UI/data/locale/sv-SE.ini index 8491cff3369659..81c34c177b2b67 100644 --- a/obs/data/locale/sv-SE.ini +++ b/UI/data/locale/sv-SE.ini @@ -48,6 +48,7 @@ Left="Vänster" Right="Höger" Top="Överkant" Bottom="Nederkant" +Reset="Återställ" QuickTransitions.SwapScenes="Byt plats på Förhandsvisnings-/utdatascenerna efter skifte" QuickTransitions.SwapScenesTT="Byter plats på förhandsvisnings- och utdatascenerna efter övergång (om utdatans originalscen fortfarande finns). \nDet här kommer inte att ångra några förändringar i utdatans originalscen." @@ -149,8 +150,11 @@ ScaleFiltering.Bilinear="Bilinjär" ScaleFiltering.Bicubic="Bikubisk" ScaleFiltering.Lanczos="Lanczos" +Deinterlacing="Avflätning" Deinterlacing.Discard="Avfärda" Deinterlacing.Retro="Retro" +Deinterlacing.Blend="Blanda" +Deinterlacing.Blend2x="Blanda 2x" Deinterlacing.Linear="Linjär" Deinterlacing.Linear2x="Linjär 2x" Deinterlacing.Yadif="Yadif" @@ -192,6 +196,8 @@ Basic.PropertiesWindow.AddDir="Lägg till mapp" Basic.PropertiesWindow.AddURL="Lägg till Sökväg/URL" Basic.PropertiesWindow.AddEditableListDir="Lägg till mapp i '%1'" Basic.PropertiesWindow.AddEditableListFiles="Lägg till filer i '%1'" +Basic.PropertiesWindow.AddEditableListEntry="Lägg till post i '%1'" +Basic.PropertiesWindow.EditEditableListEntry="Redigera post från '%1'" Basic.PropertiesView.FPS.Simple="Enkla bildfrekvensvärden" Basic.PropertiesView.FPS.Rational="Rationella bildfrekvensvärden" @@ -273,6 +279,7 @@ Basic.MainMenu.Edit.Undo="&Ångra" Basic.MainMenu.Edit.Redo="&Gör om" Basic.MainMenu.Edit.UndoAction="&Ångra $1" Basic.MainMenu.Edit.RedoAction="&Gör om $1" +Basic.MainMenu.Edit.LockPreview="&Lås förhandsvisning" Basic.MainMenu.Edit.Transform="&Omvandla" Basic.MainMenu.Edit.Transform.EditTransform="&Redigera omvandling..." Basic.MainMenu.Edit.Transform.ResetTransform="&Återställ omvandling" @@ -300,6 +307,8 @@ Basic.MainMenu.View.StatusBar="&Statusfält" Basic.MainMenu.SceneCollection="&Scensamling" Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Tools="&Verktyg" + Basic.MainMenu.Help="&Hjälp" Basic.MainMenu.Help.Website="Besök &webbplats" Basic.MainMenu.Help.Logs="&Loggfiler" @@ -319,12 +328,16 @@ Basic.Settings.General.Language="Språk" Basic.Settings.General.WarnBeforeStartingStream="Visa bekräftelsedialog när ström startas" Basic.Settings.General.WarnBeforeStoppingStream="Visa bekräftelsedialog när ström stoppas" Basic.Settings.General.HideProjectorCursor="Dölj pekaren över projektorer" +Basic.Settings.General.ProjectorAlwaysOnTop="Lägg alltid projektorer överst" +Basic.Settings.General.Snapping="Fäst justerbara källor" Basic.Settings.General.ScreenSnapping="Fäst källor till skärmens kant" Basic.Settings.General.CenterSnapping="Fäst källor till den horisontala och vertikala mittenlinjen" Basic.Settings.General.SourceSnapping="Fäst källor till andra källor" Basic.Settings.General.SnapDistance="Fästkänslighet" Basic.Settings.General.RecordWhenStreaming="Spela automatiskt in vid strömning" Basic.Settings.General.KeepRecordingWhenStreamStops="Fortsätt spela in när strömmen stoppas" +Basic.Settings.General.SysTrayEnabled="Aktivera ikon i meddelandefältet" +Basic.Settings.General.SysTrayWhenStarted="Minimera till meddelandefältet vid start" Basic.Settings.Stream="Ström" Basic.Settings.Stream.StreamType="Strömtyp" @@ -458,6 +471,8 @@ Basic.Settings.Advanced.Video.ColorRange.Full="Full" Basic.Settings.Advanced.StreamDelay="Strömfördröjning" Basic.Settings.Advanced.StreamDelay.Duration="Varaktighet (sekunder)" Basic.Settings.Advanced.StreamDelay.MemoryUsage="Uppskattad minnesanvändning: %1 MB" +Basic.Settings.Advanced.Network="Nätverk" +Basic.Settings.Advanced.Network.BindToIP="Bind till IP" Basic.AdvAudio="Avancerade ljudinställningar" Basic.AdvAudio.Name="Namn" @@ -475,6 +490,11 @@ Basic.Hotkeys.StartRecording="Starta inspelning" Basic.Hotkeys.StopRecording="Stoppa inspelning" Basic.Hotkeys.SelectScene="Byt till scen" +Basic.SystemTray.Show="Visa" +Basic.SystemTray.Hide="Dölj" + +Basic.SystemTray.Message.Reconnecting="Frånkopplad. Återansluter..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" @@ -496,6 +516,19 @@ Hotkeys.Windows="Windows" Hotkeys.Super="Super" Hotkeys.Menu="Meny" Hotkeys.Space="Mellanslag" +Hotkeys.NumpadNum="%1 (Numpad)" +Hotkeys.NumpadMultiply="* (Numpad)" +Hotkeys.NumpadDivide="/ (Numpad)" +Hotkeys.NumpadAdd="+ (Numpad)" +Hotkeys.NumpadSubtract="- (Numpad)" +Hotkeys.NumpadDecimal=", (Numpad)" +Hotkeys.AppleKeypadNum="%1 (Keypad)" +Hotkeys.AppleKeypadMultiply="* (Keypad)" +Hotkeys.AppleKeypadDivide="/ (Keypad)" +Hotkeys.AppleKeypadAdd="+ (Keypad)" +Hotkeys.AppleKeypadSubtract="- (Keypad)" +Hotkeys.AppleKeypadDecimal=". (Keypad)" +Hotkeys.AppleKeypadEqual="= (Keypad)" Hotkeys.MouseButton="Musknapp %1" Mute="Stäng av ljud" diff --git a/obs/data/locale/ta-IN.ini b/UI/data/locale/ta-IN.ini similarity index 99% rename from obs/data/locale/ta-IN.ini rename to UI/data/locale/ta-IN.ini index 5514eec2667ec9..c79b30bb3db1a9 100644 --- a/obs/data/locale/ta-IN.ini +++ b/UI/data/locale/ta-IN.ini @@ -85,6 +85,9 @@ New="புதிய" + + + diff --git a/obs/data/locale/th-TH.ini b/UI/data/locale/th-TH.ini similarity index 99% rename from obs/data/locale/th-TH.ini rename to UI/data/locale/th-TH.ini index 8e0c37737ff9eb..0509a7ce420709 100644 --- a/obs/data/locale/th-TH.ini +++ b/UI/data/locale/th-TH.ini @@ -89,6 +89,7 @@ Basic.MainMenu.File.Exit="อ&อก" + Basic.MainMenu.Help.CheckForUpdates="ตรวจสอบการอัพเดต" Basic.Settings.ConfirmTitle="ยืนยันการเปลี่ยนแปลง" @@ -118,3 +119,5 @@ Basic.Settings.Audio="เสียง" + + diff --git a/obs/data/locale/tr-TR.ini b/UI/data/locale/tr-TR.ini similarity index 99% rename from obs/data/locale/tr-TR.ini rename to UI/data/locale/tr-TR.ini index f02cd31d245915..e7c88e422c13b0 100644 --- a/obs/data/locale/tr-TR.ini +++ b/UI/data/locale/tr-TR.ini @@ -257,6 +257,7 @@ Basic.MainMenu.Edit.AdvAudio="&Gelişmiş Ses Özellikleri" Basic.MainMenu.SceneCollection="&Sahne Koleksiyonu" Basic.MainMenu.Profile="&Profil" + Basic.MainMenu.Help="&Yardım" Basic.MainMenu.Help.Website="&Siteyi Ziyaret Et" Basic.MainMenu.Help.Logs="&Günlük Dosyaları" @@ -406,6 +407,8 @@ Basic.Hotkeys.StartRecording="Kaydı Başlat" Basic.Hotkeys.StopRecording="Kaydı Durdur" Basic.Hotkeys.SelectScene="Sahneye geçiş yap" + + Hotkeys.Insert="Ekle" Hotkeys.Delete="Sil" Hotkeys.Home="Ana Sayfa" diff --git a/obs/data/locale/uk-UA.ini b/UI/data/locale/uk-UA.ini similarity index 98% rename from obs/data/locale/uk-UA.ini rename to UI/data/locale/uk-UA.ini index 94486d9ca697e0..de7583584ac960 100644 --- a/obs/data/locale/uk-UA.ini +++ b/UI/data/locale/uk-UA.ini @@ -48,9 +48,10 @@ Left="Зліва" Right="Зправа" Top="Зверху" Bottom="Знизу" +Reset="Скинути" QuickTransitions.SwapScenes="Поміняти місцями сцени Перегляд/Вивід після Відео-переходу" -QuickTransitions.SwapScenesTT="Міняє місцями сцени Перегляд та Вивід після закінчення Відео-переходу (якщо сцена Вивід ще існує).\nЗміні внесені до обох сцен залишаються." +QuickTransitions.SwapScenesTT="Міняє місцями сцени Перегляд та Вивід після закінчення Відео-переходу (якщо сцена Вивід ще існує).\nЗміни внесені до обох сцен залишаються." QuickTransitions.DuplicateScene="Використовувати копію сцени" QuickTransitions.DuplicateSceneTT="Під час редагування поточної сцени дозволяє зберегти Вивід без змін.\nДля можливості редагувати властивості Джерел, увімкніть також 'Використовувати копію Джерел'.\nЗміна цієї опції призведе до оновлення поточної сцени, яка йде на Вивід (якщо вона ще існує)." QuickTransitions.EditProperties="Використовувати копію Джерел" @@ -308,6 +309,8 @@ Basic.MainMenu.View.StatusBar="Панель с&тану" Basic.MainMenu.SceneCollection="&Набір Сцен" Basic.MainMenu.Profile="&Профіль" +Basic.MainMenu.Tools="Додаткові &засоби" + Basic.MainMenu.Help="&Довідка" Basic.MainMenu.Help.Website="Відвідати &сайт" Basic.MainMenu.Help.Logs="&Файли журналів" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="Мова" Basic.Settings.General.WarnBeforeStartingStream="Показувати підтвердження для початку трансляції" Basic.Settings.General.WarnBeforeStoppingStream="Показувати підтвердження для закінчення трансляції" Basic.Settings.General.HideProjectorCursor="Приховати курсор у режимі Проектор" +Basic.Settings.General.ProjectorAlwaysOnTop="Режим Проектор відображати поверх всіх вікон" Basic.Settings.General.Snapping="Прив'язка та вирівнювання" Basic.Settings.General.ScreenSnapping="Примагнітити Джерела до краю екрана" Basic.Settings.General.CenterSnapping="Примагнітити Джерела до центру по вертикалі та горизонталі" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="Примагнітити Джерело Basic.Settings.General.SnapDistance="Чутливість примагничування" Basic.Settings.General.RecordWhenStreaming="Автоматично почати запис з початком трансляції" Basic.Settings.General.KeepRecordingWhenStreamStops="Не припиняти запис, якщо трансляцію закінчено" +Basic.Settings.General.SysTrayEnabled="Відображати іконку системному треї, та згортати в трей" +Basic.Settings.General.SysTrayWhenStarted="Згорнути програму до трею при запуску" Basic.Settings.Stream="Трансляція" Basic.Settings.Stream.StreamType="Тип Трансляції" @@ -488,6 +494,11 @@ Basic.Hotkeys.StartRecording="Почати запис" Basic.Hotkeys.StopRecording="Зупинити запис" Basic.Hotkeys.SelectScene="Перейти до сцени" +Basic.SystemTray.Show="Показати" +Basic.SystemTray.Hide="Приховати" + +Basic.SystemTray.Message.Reconnecting="З'єднання розірвано. Повторне підключення..." + Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" Hotkeys.Home="Home" diff --git a/obs/data/locale/vi-VN.ini b/UI/data/locale/vi-VN.ini similarity index 99% rename from obs/data/locale/vi-VN.ini rename to UI/data/locale/vi-VN.ini index 59b22de39d04b2..22db6b23e588bf 100644 --- a/obs/data/locale/vi-VN.ini +++ b/UI/data/locale/vi-VN.ini @@ -23,7 +23,7 @@ Settings="Tùy chỉnh" Display="Hiển thị" Name="Tên" Exit="Thoát" -Mixer="Máy trộn" +Mixer="Bộ trộn" Browse="trình duyệt" Mono="Âm thanh đơn" Stereo="Âm thanh nổi" @@ -260,6 +260,7 @@ Basic.MainMenu.Edit.AdvAudio="Thuộc tính âm thanh nâng cao" Basic.MainMenu.SceneCollection="& Cảnh bộ sưu tập" Basic.MainMenu.Profile="& Hồ sơ" + Basic.MainMenu.Help="&Trợ giúp" Basic.MainMenu.Help.Website="Ghé thăm Website" Basic.MainMenu.Help.Logs="& Tập tin đăng nhập" @@ -412,6 +413,8 @@ Basic.Hotkeys.StartRecording="Bắt đầu ghi âm" Basic.Hotkeys.StopRecording="Dừng ghi âm" Basic.Hotkeys.SelectScene="Chuyển cảnh" + + Hotkeys.Insert="Chèn" Hotkeys.Delete="Xoá" Hotkeys.Home="Home" diff --git a/obs/data/locale/zh-CN.ini b/UI/data/locale/zh-CN.ini similarity index 87% rename from obs/data/locale/zh-CN.ini rename to UI/data/locale/zh-CN.ini index e0ec6b48a62a66..25906032a81421 100644 --- a/obs/data/locale/zh-CN.ini +++ b/UI/data/locale/zh-CN.ini @@ -12,8 +12,8 @@ Disable="禁用" Yes="是" No="否" Add="添加" -Remove="移除" -Rename="重命名" +Remove="移除(&E)" +Rename="重命名(&R)" Interact="交互" Filters="滤镜" Properties="属性" @@ -36,8 +36,8 @@ Revert="还原" Show="显示" Hide="隐藏" Untitled="未命名" -New="新建" -Duplicate="复制" +New="新建(&N)" +Duplicate="复制(&D)" Enable="启用" DisableOSXVSync="禁用 OSX V-Sync" ResetOSXVSyncOnExit="退出时重置 OSX V-Sync" @@ -48,6 +48,7 @@ Left="左" Right="右" Top="上" Bottom="下" +Reset="重置" QuickTransitions.SwapScenes="在过渡动画后交换预览/输出场景" QuickTransitions.SwapScenesTT="在过渡后,交换预览和输出场景(如果输出的原始场景仍然存在). \n 这个不会撤消任何可能对输出的原始场景的更改." @@ -173,10 +174,10 @@ Basic.Main.AddSceneCollection.Text="请输入场景集合的名称" Basic.Main.RenameSceneCollection.Title="重命名场景集合" -AddProfile.Title="添加档案" -AddProfile.Text="请输入档案文件的名称" +AddProfile.Title="添加配置文件" +AddProfile.Text="请输入配置文件的名称" -RenameProfile.Title="重命名档案" +RenameProfile.Title="重命名配置文件" Basic.Main.PreviewDisabled="预览当前已禁用" @@ -264,57 +265,60 @@ Basic.Main.StopStreaming="停止串流" Basic.Main.StoppingStreaming="停止推流..." Basic.Main.ForceStopStreaming="停止流 (放弃延迟)" -Basic.MainMenu.File="& 文件" -Basic.MainMenu.File.Export="& 导出" -Basic.MainMenu.File.Import="& 导入" -Basic.MainMenu.File.ShowRecordings="显示 & 录像" -Basic.MainMenu.File.Remux="转&封装 录像" -Basic.MainMenu.File.Settings="& 设置" -Basic.MainMenu.File.ShowSettingsFolder="显示设置文件夹" -Basic.MainMenu.File.ShowProfileFolder="显示档案文件夹" -Basic.MainMenu.AlwaysOnTop="总是在最前面(&A)" -Basic.MainMenu.File.Exit="退出(&X)" - -Basic.MainMenu.Edit="& 编辑" -Basic.MainMenu.Edit.Undo="& 撤消" -Basic.MainMenu.Edit.Redo="& 重做" -Basic.MainMenu.Edit.UndoAction="& 撤消 $1" -Basic.MainMenu.Edit.RedoAction="& 重做 $1" -Basic.MainMenu.Edit.Transform="& 变换" -Basic.MainMenu.Edit.Transform.EditTransform="& 编辑变换" -Basic.MainMenu.Edit.Transform.ResetTransform="& 重置变换" -Basic.MainMenu.Edit.Transform.Rotate90CW="旋转 90 度 CW" -Basic.MainMenu.Edit.Transform.Rotate90CCW="旋转 90 度 CCW" -Basic.MainMenu.Edit.Transform.Rotate180="旋转 180 度" -Basic.MainMenu.Edit.Transform.FlipHorizontal="水平翻转" -Basic.MainMenu.Edit.Transform.FlipVertical="垂直翻转" -Basic.MainMenu.Edit.Transform.FitToScreen="& 适配屏幕" -Basic.MainMenu.Edit.Transform.StretchToScreen="& 拉伸到屏幕" -Basic.MainMenu.Edit.Transform.CenterToScreen="& 屏幕居中" -Basic.MainMenu.Edit.Order="& 顺序" -Basic.MainMenu.Edit.Order.MoveUp="上移" -Basic.MainMenu.Edit.Order.MoveDown="下移" -Basic.MainMenu.Edit.Order.MoveToTop="移至顶部" -Basic.MainMenu.Edit.Order.MoveToBottom="移至底部" -Basic.MainMenu.Edit.AdvAudio="&高级音频属性" - -Basic.MainMenu.View="查看(&V)" -Basic.MainMenu.View.Toolbars="工具栏(&T)" -Basic.MainMenu.View.Toolbars.Listboxes="列表框(&L)" -Basic.MainMenu.View.SceneTransitions="场景过渡(&C)" -Basic.MainMenu.View.StatusBar="状态栏(&S)" - -Basic.MainMenu.SceneCollection="&场景集合" -Basic.MainMenu.Profile="&档案" - -Basic.MainMenu.Help="& 帮助" -Basic.MainMenu.Help.Website="访问 &网站" -Basic.MainMenu.Help.Logs="& 日志文件" -Basic.MainMenu.Help.Logs.ShowLogs="& 显示日志文件" -Basic.MainMenu.Help.Logs.UploadCurrentLog="& 上传当前日志文件" -Basic.MainMenu.Help.Logs.UploadLastLog="& 上传最后一个日志文件" -Basic.MainMenu.Help.Logs.ViewCurrentLog="& 查看当前日志" -Basic.MainMenu.Help.CheckForUpdates="检查升级" +Basic.MainMenu.File="文件(&F)" +Basic.MainMenu.File.Export="导出(&E)" +Basic.MainMenu.File.Import="导入(&I)" +Basic.MainMenu.File.ShowRecordings="显示录像(&R)" +Basic.MainMenu.File.Remux="转封装录像 (&M)" +Basic.MainMenu.File.Settings="设置(&S)" +Basic.MainMenu.File.ShowSettingsFolder="打开设置所在路径(&F)" +Basic.MainMenu.File.ShowProfileFolder="打开配置文件所在路径(&P)" +Basic.MainMenu.AlwaysOnTop="窗口置顶 (&A)" +Basic.MainMenu.File.Exit="退出 (&X)" + +Basic.MainMenu.Edit="编辑 (&E)" +Basic.MainMenu.Edit.Undo="撤销 (&U)" +Basic.MainMenu.Edit.Redo="重做 (&R)" +Basic.MainMenu.Edit.UndoAction="撤消 $1 (&U)" +Basic.MainMenu.Edit.RedoAction="重做 $1 (&R)" +Basic.MainMenu.Edit.LockPreview="锁定预览 (&L)" +Basic.MainMenu.Edit.Transform="变换 (&T)" +Basic.MainMenu.Edit.Transform.EditTransform="编辑变换 (&E)" +Basic.MainMenu.Edit.Transform.ResetTransform="重置变换 (&R)" +Basic.MainMenu.Edit.Transform.Rotate90CW="顺时针旋转 90 度(&9)" +Basic.MainMenu.Edit.Transform.Rotate90CCW="逆时针旋转 90 度(&D)" +Basic.MainMenu.Edit.Transform.Rotate180="旋转 180 度(&1)" +Basic.MainMenu.Edit.Transform.FlipHorizontal="水平翻转 (&H)" +Basic.MainMenu.Edit.Transform.FlipVertical="垂直翻转 (&V)" +Basic.MainMenu.Edit.Transform.FitToScreen="比例适配屏幕 (&F)" +Basic.MainMenu.Edit.Transform.StretchToScreen="拉伸到全屏 (&S)" +Basic.MainMenu.Edit.Transform.CenterToScreen="屏幕居中 (&C)" +Basic.MainMenu.Edit.Order="排序 (&O)" +Basic.MainMenu.Edit.Order.MoveUp="上移 (&U)" +Basic.MainMenu.Edit.Order.MoveDown="下移 (&D)" +Basic.MainMenu.Edit.Order.MoveToTop="移至顶部 (&T)" +Basic.MainMenu.Edit.Order.MoveToBottom="移至底部 (&B)" +Basic.MainMenu.Edit.AdvAudio="高级音频属性(&A)" + +Basic.MainMenu.View="查看 (&V)" +Basic.MainMenu.View.Toolbars="工具栏 (&T)" +Basic.MainMenu.View.Toolbars.Listboxes="列表框 (&L)" +Basic.MainMenu.View.SceneTransitions="场景过渡 (&C)" +Basic.MainMenu.View.StatusBar="状态栏 (&S)" + +Basic.MainMenu.SceneCollection="场景集合 (&S)" +Basic.MainMenu.Profile="配置文件 (&P)" + +Basic.MainMenu.Tools="工具 (&T)" + +Basic.MainMenu.Help="帮助 (&H)" +Basic.MainMenu.Help.Website="访问OBS主页 (&W)" +Basic.MainMenu.Help.Logs="日志文件 (&L)" +Basic.MainMenu.Help.Logs.ShowLogs="显示日志文件 (&S)" +Basic.MainMenu.Help.Logs.UploadCurrentLog="上传当前日志文件 (&C)" +Basic.MainMenu.Help.Logs.UploadLastLog="上传最后一个日志文件 (&L)" +Basic.MainMenu.Help.Logs.ViewCurrentLog="查看当前日志 (&V)" +Basic.MainMenu.Help.CheckForUpdates="检查升级(&C)" Basic.Settings.ProgramRestart="要使这些设置生效,必须重新启动该程序。" Basic.Settings.ConfirmTitle="确认更改" @@ -326,6 +330,7 @@ Basic.Settings.General.Language="语言" Basic.Settings.General.WarnBeforeStartingStream="启动流时显示确认对话框" Basic.Settings.General.WarnBeforeStoppingStream="停止流时显示确认对话框" Basic.Settings.General.HideProjectorCursor="隐藏投影仪上的光标" +Basic.Settings.General.ProjectorAlwaysOnTop="使投影器总是置顶" Basic.Settings.General.Snapping="源对齐方式" Basic.Settings.General.ScreenSnapping="对齐源到屏幕边缘" Basic.Settings.General.CenterSnapping="水平和垂直居中对齐源" @@ -333,6 +338,8 @@ Basic.Settings.General.SourceSnapping="对齐源跟其他的源" Basic.Settings.General.SnapDistance="对齐的敏感性" Basic.Settings.General.RecordWhenStreaming="当推流时自动录像" Basic.Settings.General.KeepRecordingWhenStreamStops="当推流停止时保持录像" +Basic.Settings.General.SysTrayEnabled="启用系统托盘图标" +Basic.Settings.General.SysTrayWhenStarted="开始时最小化到系统托盘" Basic.Settings.Stream="串流" Basic.Settings.Stream.StreamType="串流类型" @@ -467,10 +474,12 @@ Basic.Settings.Advanced.StreamDelay="流延迟" Basic.Settings.Advanced.StreamDelay.Duration="持续时间 (秒)" Basic.Settings.Advanced.StreamDelay.Preserve="重新连接时保持截止点 (增加延迟)" Basic.Settings.Advanced.StreamDelay.MemoryUsage="估计的内存使用率: %1 MB" +Basic.Settings.Advanced.Network="网络" +Basic.Settings.Advanced.Network.BindToIP="绑定 IP" Basic.AdvAudio="高级音频属性" Basic.AdvAudio.Name="名称" -Basic.AdvAudio.Volume="卷 (%)" +Basic.AdvAudio.Volume="音量 (%)" Basic.AdvAudio.Mono="下降混合为单声道" Basic.AdvAudio.Panning="平移" Basic.AdvAudio.SyncOffset="同步偏移 (毫秒)" @@ -485,6 +494,11 @@ Basic.Hotkeys.StartRecording="开始录像" Basic.Hotkeys.StopRecording="停止录像" Basic.Hotkeys.SelectScene="切换到场景" +Basic.SystemTray.Show="显示" +Basic.SystemTray.Hide="隐藏" + +Basic.SystemTray.Message.Reconnecting="已断开连接。 重新连接..." + Hotkeys.Insert="插入" Hotkeys.Delete="删除" Hotkeys.Home="首页" diff --git a/obs/data/locale/zh-TW.ini b/UI/data/locale/zh-TW.ini similarity index 98% rename from obs/data/locale/zh-TW.ini rename to UI/data/locale/zh-TW.ini index 27e34a022858c0..12e1fd6d6a06e9 100644 --- a/obs/data/locale/zh-TW.ini +++ b/UI/data/locale/zh-TW.ini @@ -1,6 +1,6 @@ -Language="正體中文(臺灣)" -Region="Taiwan, R.O.C." +Language="繁體中文" +Region="臺灣/香港/澳門" OK="確定" Apply="套用" @@ -48,6 +48,7 @@ Left="左" Right="右" Top="上" Bottom="下" +Reset="重置" QuickTransitions.SwapScenes="轉場後交換預覽/輸出場景" QuickTransitions.SwapScenesTT="(如果輸出的原始場景仍然存在) 轉場後交換預覽和輸出場景。\n這並不會復原任何對輸出原始場景所作的改動。" @@ -308,13 +309,15 @@ Basic.MainMenu.View.StatusBar="狀態列(&S)" Basic.MainMenu.SceneCollection="場景群組 (&S)" Basic.MainMenu.Profile="設定檔 (&P)" +Basic.MainMenu.Tools="工具(&T)" + Basic.MainMenu.Help="幫助 (&H)" Basic.MainMenu.Help.Website="前往 OBS 網站 (&W)" Basic.MainMenu.Help.Logs="Log 檔案 (&L)" Basic.MainMenu.Help.Logs.ShowLogs="顯示 Log (&S)" Basic.MainMenu.Help.Logs.UploadCurrentLog="上傳目前 Log 檔 (&C)" Basic.MainMenu.Help.Logs.UploadLastLog="上傳上次的 Log 檔 (&L)" -Basic.MainMenu.Help.Logs.ViewCurrentLog="顯示當前紀錄檔" +Basic.MainMenu.Help.Logs.ViewCurrentLog="顯示當前紀錄檔 (&V)" Basic.MainMenu.Help.CheckForUpdates="檢查更新" Basic.Settings.ProgramRestart="為了套用新設定,請關閉後重啟。" @@ -327,6 +330,7 @@ Basic.Settings.General.Language="語言" Basic.Settings.General.WarnBeforeStartingStream="啟動串流時顯示確認對話框" Basic.Settings.General.WarnBeforeStoppingStream="停止串流時顯示確認對話框" Basic.Settings.General.HideProjectorCursor="當游標在投影上時隱藏游標" +Basic.Settings.General.ProjectorAlwaysOnTop="讓投影總是在最上層" Basic.Settings.General.Snapping="貼齊對準來源" Basic.Settings.General.ScreenSnapping="來源與螢幕邊緣貼齊" Basic.Settings.General.CenterSnapping="來源與水平中央以及垂直中央貼齊" @@ -334,6 +338,8 @@ Basic.Settings.General.SourceSnapping="來源與其他來源貼齊" Basic.Settings.General.SnapDistance="貼齊敏感度" Basic.Settings.General.RecordWhenStreaming="串流時自動錄製" Basic.Settings.General.KeepRecordingWhenStreamStops="串流停止時繼續錄製" +Basic.Settings.General.SysTrayEnabled="啟用系統列圖示" +Basic.Settings.General.SysTrayWhenStarted="開始時最小化至系統列" Basic.Settings.Stream="串流" Basic.Settings.Stream.StreamType="串流類型" @@ -488,6 +494,11 @@ Basic.Hotkeys.StartRecording="開始錄影" Basic.Hotkeys.StopRecording="停止錄影" Basic.Hotkeys.SelectScene="切換到場景" +Basic.SystemTray.Show="顯示" +Basic.SystemTray.Hide="隱藏" + +Basic.SystemTray.Message.Reconnecting="連線已中斷。 正在重新連接..." + Hotkeys.Insert="插入鍵" Hotkeys.Delete="刪除鍵" Hotkeys.Home="Home 鍵" diff --git a/obs/data/themes/Dark.qss b/UI/data/themes/Dark.qss similarity index 100% rename from obs/data/themes/Dark.qss rename to UI/data/themes/Dark.qss diff --git a/obs/data/themes/Dark/cogwheel.png b/UI/data/themes/Dark/cogwheel.png similarity index 100% rename from obs/data/themes/Dark/cogwheel.png rename to UI/data/themes/Dark/cogwheel.png diff --git a/obs/data/themes/Dark/down_arrow.png b/UI/data/themes/Dark/down_arrow.png similarity index 100% rename from obs/data/themes/Dark/down_arrow.png rename to UI/data/themes/Dark/down_arrow.png diff --git a/obs/data/themes/Dark/minus.png b/UI/data/themes/Dark/minus.png similarity index 100% rename from obs/data/themes/Dark/minus.png rename to UI/data/themes/Dark/minus.png diff --git a/obs/data/themes/Dark/mute.png b/UI/data/themes/Dark/mute.png similarity index 100% rename from obs/data/themes/Dark/mute.png rename to UI/data/themes/Dark/mute.png diff --git a/obs/data/themes/Dark/plus.png b/UI/data/themes/Dark/plus.png similarity index 100% rename from obs/data/themes/Dark/plus.png rename to UI/data/themes/Dark/plus.png diff --git a/obs/data/themes/Dark/unmute.png b/UI/data/themes/Dark/unmute.png similarity index 100% rename from obs/data/themes/Dark/unmute.png rename to UI/data/themes/Dark/unmute.png diff --git a/obs/data/themes/Dark/up_arrow.png b/UI/data/themes/Dark/up_arrow.png similarity index 100% rename from obs/data/themes/Dark/up_arrow.png rename to UI/data/themes/Dark/up_arrow.png diff --git a/obs/data/themes/Dark/updown.png b/UI/data/themes/Dark/updown.png similarity index 100% rename from obs/data/themes/Dark/updown.png rename to UI/data/themes/Dark/updown.png diff --git a/obs/data/themes/Default.qss b/UI/data/themes/Default.qss similarity index 100% rename from obs/data/themes/Default.qss rename to UI/data/themes/Default.qss diff --git a/obs/display-helpers.hpp b/UI/display-helpers.hpp similarity index 100% rename from obs/display-helpers.hpp rename to UI/display-helpers.hpp diff --git a/obs/dist/obs.desktop b/UI/dist/obs.desktop similarity index 100% rename from obs/dist/obs.desktop rename to UI/dist/obs.desktop diff --git a/obs/double-slider.cpp b/UI/double-slider.cpp similarity index 100% rename from obs/double-slider.cpp rename to UI/double-slider.cpp diff --git a/obs/double-slider.hpp b/UI/double-slider.hpp similarity index 100% rename from obs/double-slider.hpp rename to UI/double-slider.hpp diff --git a/obs/focus-list.cpp b/UI/focus-list.cpp similarity index 100% rename from obs/focus-list.cpp rename to UI/focus-list.cpp diff --git a/obs/focus-list.hpp b/UI/focus-list.hpp similarity index 100% rename from obs/focus-list.hpp rename to UI/focus-list.hpp diff --git a/obs/forms/NameDialog.ui b/UI/forms/NameDialog.ui similarity index 100% rename from obs/forms/NameDialog.ui rename to UI/forms/NameDialog.ui diff --git a/obs/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui similarity index 99% rename from obs/forms/OBSBasic.ui rename to UI/forms/OBSBasic.ui index 76d8e5517c7b44..5b922bcf8e17d9 100644 --- a/obs/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -229,7 +229,7 @@ 0 0 - 201 + 215 16 @@ -913,11 +913,20 @@ + + + false + + + Basic.MainMenu.Tools + + + diff --git a/obs/forms/OBSBasicFilters.ui b/UI/forms/OBSBasicFilters.ui similarity index 100% rename from obs/forms/OBSBasicFilters.ui rename to UI/forms/OBSBasicFilters.ui diff --git a/obs/forms/OBSBasicInteraction.ui b/UI/forms/OBSBasicInteraction.ui similarity index 100% rename from obs/forms/OBSBasicInteraction.ui rename to UI/forms/OBSBasicInteraction.ui diff --git a/obs/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui similarity index 98% rename from obs/forms/OBSBasicSettings.ui rename to UI/forms/OBSBasicSettings.ui index be10b2f2030f91..ff50261bfe080c 100644 --- a/obs/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -185,21 +185,38 @@ - + Basic.Settings.General.RecordWhenStreaming - + + + + Basic.Settings.General.SysTrayEnabled + + + + + + + false + + + Basic.Settings.General.SysTrayWhenStarted + + + + Qt::Horizontal - + true @@ -299,7 +316,7 @@ - + false @@ -309,6 +326,13 @@ + + + + Basic.Settings.General.ProjectorAlwaysOnTop + + + @@ -2521,8 +2545,8 @@ 0 0 - 98 - 28 + 80 + 16 @@ -2886,9 +2910,9 @@ 0 - -206 - 803 - 820 + 0 + 559 + 681 @@ -3753,5 +3777,21 @@ + + systemTrayEnabled + toggled(bool) + systemTrayWhenStarted + setEnabled(bool) + + + 404 + 245 + + + 404 + 271 + + + diff --git a/obs/forms/OBSBasicSourceSelect.ui b/UI/forms/OBSBasicSourceSelect.ui similarity index 100% rename from obs/forms/OBSBasicSourceSelect.ui rename to UI/forms/OBSBasicSourceSelect.ui diff --git a/obs/forms/OBSBasicTransform.ui b/UI/forms/OBSBasicTransform.ui similarity index 98% rename from obs/forms/OBSBasicTransform.ui rename to UI/forms/OBSBasicTransform.ui index 7f49ffaf55920f..c801b3da795617 100644 --- a/obs/forms/OBSBasicTransform.ui +++ b/UI/forms/OBSBasicTransform.ui @@ -638,6 +638,13 @@ + + + + QDialogButtonBox::Reset|QDialogButtonBox::Close + + + diff --git a/obs/forms/OBSLicenseAgreement.ui b/UI/forms/OBSLicenseAgreement.ui similarity index 75% rename from obs/forms/OBSLicenseAgreement.ui rename to UI/forms/OBSLicenseAgreement.ui index f6ba2a768911d2..e9c2d4970088c5 100644 --- a/obs/forms/OBSLicenseAgreement.ui +++ b/UI/forms/OBSLicenseAgreement.ui @@ -52,16 +52,6 @@ - - - - LicenseAgreement.ClickIAgreeToContinue - - - true - - - @@ -87,14 +77,7 @@ - LicenseAgreement.IAgree - - - - - - - LicenseAgreement.Exit + OK @@ -121,21 +104,5 @@ - - decline - clicked() - OBSLicenseAgreement - reject() - - - 312 - 410 - - - 424 - 418 - - - diff --git a/obs/forms/OBSLogReply.ui b/UI/forms/OBSLogReply.ui similarity index 100% rename from obs/forms/OBSLogReply.ui rename to UI/forms/OBSLogReply.ui diff --git a/obs/forms/OBSRemux.ui b/UI/forms/OBSRemux.ui similarity index 100% rename from obs/forms/OBSRemux.ui rename to UI/forms/OBSRemux.ui diff --git a/obs/forms/images/add.png b/UI/forms/images/add.png similarity index 100% rename from obs/forms/images/add.png rename to UI/forms/images/add.png diff --git a/obs/forms/images/configuration21_16.png b/UI/forms/images/configuration21_16.png similarity index 100% rename from obs/forms/images/configuration21_16.png rename to UI/forms/images/configuration21_16.png diff --git a/obs/forms/images/down.png b/UI/forms/images/down.png similarity index 100% rename from obs/forms/images/down.png rename to UI/forms/images/down.png diff --git a/obs/forms/images/editscene.png b/UI/forms/images/editscene.png similarity index 100% rename from obs/forms/images/editscene.png rename to UI/forms/images/editscene.png diff --git a/obs/forms/images/invisible_mask.png b/UI/forms/images/invisible_mask.png similarity index 100% rename from obs/forms/images/invisible_mask.png rename to UI/forms/images/invisible_mask.png diff --git a/obs/forms/images/list_remove.png b/UI/forms/images/list_remove.png similarity index 100% rename from obs/forms/images/list_remove.png rename to UI/forms/images/list_remove.png diff --git a/obs/forms/images/live.png b/UI/forms/images/live.png similarity index 100% rename from obs/forms/images/live.png rename to UI/forms/images/live.png diff --git a/obs/forms/images/mute.png b/UI/forms/images/mute.png similarity index 100% rename from obs/forms/images/mute.png rename to UI/forms/images/mute.png diff --git a/obs/forms/images/obs.png b/UI/forms/images/obs.png similarity index 100% rename from obs/forms/images/obs.png rename to UI/forms/images/obs.png diff --git a/obs/forms/images/properties.png b/UI/forms/images/properties.png similarity index 100% rename from obs/forms/images/properties.png rename to UI/forms/images/properties.png diff --git a/obs/forms/images/settings/advanced.png b/UI/forms/images/settings/advanced.png similarity index 100% rename from obs/forms/images/settings/advanced.png rename to UI/forms/images/settings/advanced.png diff --git a/obs/forms/images/settings/applications-system-2.png b/UI/forms/images/settings/applications-system-2.png similarity index 100% rename from obs/forms/images/settings/applications-system-2.png rename to UI/forms/images/settings/applications-system-2.png diff --git a/obs/forms/images/settings/decibel_audio_player.png b/UI/forms/images/settings/decibel_audio_player.png similarity index 100% rename from obs/forms/images/settings/decibel_audio_player.png rename to UI/forms/images/settings/decibel_audio_player.png diff --git a/obs/forms/images/settings/network-bluetooth.png b/UI/forms/images/settings/network-bluetooth.png similarity index 100% rename from obs/forms/images/settings/network-bluetooth.png rename to UI/forms/images/settings/network-bluetooth.png diff --git a/obs/forms/images/settings/network.png b/UI/forms/images/settings/network.png similarity index 100% rename from obs/forms/images/settings/network.png rename to UI/forms/images/settings/network.png diff --git a/obs/forms/images/settings/preferences-desktop-keyboard-shortcuts.png b/UI/forms/images/settings/preferences-desktop-keyboard-shortcuts.png similarity index 100% rename from obs/forms/images/settings/preferences-desktop-keyboard-shortcuts.png rename to UI/forms/images/settings/preferences-desktop-keyboard-shortcuts.png diff --git a/obs/forms/images/settings/preferences-system-network-3.png b/UI/forms/images/settings/preferences-system-network-3.png similarity index 100% rename from obs/forms/images/settings/preferences-system-network-3.png rename to UI/forms/images/settings/preferences-system-network-3.png diff --git a/obs/forms/images/settings/system-settings-3.png b/UI/forms/images/settings/system-settings-3.png similarity index 100% rename from obs/forms/images/settings/system-settings-3.png rename to UI/forms/images/settings/system-settings-3.png diff --git a/obs/forms/images/settings/video-display-3.png b/UI/forms/images/settings/video-display-3.png similarity index 100% rename from obs/forms/images/settings/video-display-3.png rename to UI/forms/images/settings/video-display-3.png diff --git a/obs/forms/images/sound.ico b/UI/forms/images/sound.ico similarity index 100% rename from obs/forms/images/sound.ico rename to UI/forms/images/sound.ico diff --git a/obs/forms/images/sound_muted.ico b/UI/forms/images/sound_muted.ico similarity index 100% rename from obs/forms/images/sound_muted.ico rename to UI/forms/images/sound_muted.ico diff --git a/UI/forms/images/tray_active.png b/UI/forms/images/tray_active.png new file mode 100644 index 00000000000000..d8da1c5f92937d Binary files /dev/null and b/UI/forms/images/tray_active.png differ diff --git a/obs/forms/images/unmute.png b/UI/forms/images/unmute.png similarity index 100% rename from obs/forms/images/unmute.png rename to UI/forms/images/unmute.png diff --git a/obs/forms/images/up.png b/UI/forms/images/up.png similarity index 100% rename from obs/forms/images/up.png rename to UI/forms/images/up.png diff --git a/obs/forms/images/visible_mask.png b/UI/forms/images/visible_mask.png similarity index 100% rename from obs/forms/images/visible_mask.png rename to UI/forms/images/visible_mask.png diff --git a/obs/forms/obs.qrc b/UI/forms/obs.qrc similarity index 96% rename from obs/forms/obs.qrc rename to UI/forms/obs.qrc index 60ab59a4bd02e0..361fef4ae75af7 100644 --- a/obs/forms/obs.qrc +++ b/UI/forms/obs.qrc @@ -13,6 +13,7 @@ images/properties.png images/up.png images/obs.png + images/tray_active.png images/settings/advanced.png diff --git a/UI/frontend-plugins/CMakeLists.txt b/UI/frontend-plugins/CMakeLists.txt new file mode 100644 index 00000000000000..0a90a60a0a0d32 --- /dev/null +++ b/UI/frontend-plugins/CMakeLists.txt @@ -0,0 +1,3 @@ +if(WIN32 OR APPLE) + add_subdirectory(frontend-tools) +endif() diff --git a/UI/frontend-plugins/frontend-tools/CMakeLists.txt b/UI/frontend-plugins/frontend-tools/CMakeLists.txt new file mode 100644 index 00000000000000..b15dc5256e9580 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/CMakeLists.txt @@ -0,0 +1,46 @@ +project(frontend-tools) + +if(APPLE) + find_library(COCOA Cocoa) + include_directories(${COCOA}) +endif() + +set(frontend-tools_HEADERS + auto-scene-switcher.hpp + ) +set(frontend-tools_SOURCES + frontend-tools.c + auto-scene-switcher.cpp + ) +set(frontend-tools_UI + forms/auto-scene-switcher.ui + ) + +if(WIN32) + set(frontend-tools_PLATFORM_SOURCES + auto-scene-switcher-win.cpp) +elseif(APPLE) + set(frontend-tools_PLATFORM_SOURCES + auto-scene-switcher-osx.mm) + set_source_files_properties(auto-scene-switcher-osx.mm + PROPERTIES COMPILE_FLAGS "-fobjc-arc") + + set(frontend-tools_PLATFORM_LIBS + ${COCOA}) +endif() + +qt5_wrap_ui(frontend-tools_UI_HEADERS ${frontend-tools_UI}) + +add_library(frontend-tools MODULE + ${frontend-tools_HEADERS} + ${frontend-tools_SOURCES} + ${frontend-tools_PLATFORM_SOURCES} + ${frontend-tools_UI_HEADERS} + ) +target_link_libraries(frontend-tools + ${frontend-tools_PLATFORM_LIBS} + obs-frontend-api + Qt5::Widgets + libobs) + +install_obs_plugin_with_data(frontend-tools data) diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher-osx.mm b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-osx.mm new file mode 100644 index 00000000000000..92dc8b757b2bd5 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-osx.mm @@ -0,0 +1,43 @@ +#import +#include +#include "auto-scene-switcher.hpp" + +using namespace std; + +void GetWindowList(vector &windows) +{ + windows.resize(0); + + @autoreleasepool { + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + NSArray *array = [ws runningApplications]; + for (NSRunningApplication *app in array) { + NSString *name = app.localizedName; + if (!name) + continue; + + const char *str = name.UTF8String; + if (str && *str) + windows.emplace_back(str); + } + } +} + +void GetCurrentWindowTitle(string &title) +{ + title.resize(0); + + @autoreleasepool { + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + NSRunningApplication *app = [ws frontmostApplication]; + if (app) { + NSString *name = app.localizedName; + if (!name) + return; + + const char *str = name.UTF8String; + if (str && *str) + title = str; + } + } +} diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher-win.cpp b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-win.cpp new file mode 100644 index 00000000000000..604504034aafe3 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-win.cpp @@ -0,0 +1,69 @@ +#include +#include +#include "auto-scene-switcher.hpp" + +using namespace std; + +static bool GetWindowTitle(HWND window, string &title) +{ + size_t len = (size_t)GetWindowTextLengthW(window); + wstring wtitle; + + wtitle.resize(len); + if (!GetWindowTextW(window, &wtitle[0], (int)len + 1)) + return false; + + len = os_wcs_to_utf8(wtitle.c_str(), 0, nullptr, 0); + title.resize(len); + os_wcs_to_utf8(wtitle.c_str(), 0, &title[0], len + 1); + return true; +} + +static bool WindowValid(HWND window) +{ + LONG_PTR styles, ex_styles; + RECT rect; + DWORD id; + + if (!IsWindowVisible(window)) + return false; + GetWindowThreadProcessId(window, &id); + if (id == GetCurrentProcessId()) + return false; + + GetClientRect(window, &rect); + styles = GetWindowLongPtr(window, GWL_STYLE); + ex_styles = GetWindowLongPtr(window, GWL_EXSTYLE); + + if (ex_styles & WS_EX_TOOLWINDOW) + return false; + if (styles & WS_CHILD) + return false; + + return true; +} + +void GetWindowList(vector &windows) +{ + HWND window = GetWindow(GetDesktopWindow(), GW_CHILD); + + while (window) { + string title; + if (WindowValid(window) && GetWindowTitle(window, title)) + windows.emplace_back(title); + window = GetNextWindow(window, GW_HWNDNEXT); + } +} + +void GetCurrentWindowTitle(string &title) +{ + HWND window = GetForegroundWindow(); + DWORD id; + + GetWindowThreadProcessId(window, &id); + if (id == GetCurrentProcessId()) { + title = ""; + return; + } + GetWindowTitle(window, title); +} diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp b/UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp new file mode 100644 index 00000000000000..999d3525c36b15 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp @@ -0,0 +1,595 @@ +#include +#include +#include +#include +#include +#include +#include +#include "auto-scene-switcher.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +#define DEFAULT_INTERVAL 300 + +struct SceneSwitch { + OBSWeakSource scene; + string window; + regex re; + + inline SceneSwitch(OBSWeakSource scene_, const char *window_) + : scene(scene_), window(window_), re(window_) + { + } +}; + +static inline bool WeakSourceValid(obs_weak_source_t *ws) +{ + obs_source_t *source = obs_weak_source_get_source(ws); + if (source) + obs_source_release(source); + return !!source; +} + +struct SwitcherData { + thread th; + condition_variable cv; + mutex m; + bool stop = false; + + vector switches; + OBSWeakSource nonMatchingScene; + int interval = DEFAULT_INTERVAL; + bool switchIfNotMatching = false; + bool startAtLaunch = false; + + void Thread(); + void Start(); + void Stop(); + + void Prune() + { + for (size_t i = 0; i < switches.size(); i++) { + SceneSwitch &s = switches[i]; + if (!WeakSourceValid(s.scene)) + switches.erase(switches.begin() + i--); + } + + if (nonMatchingScene && !WeakSourceValid(nonMatchingScene)) { + switchIfNotMatching = false; + nonMatchingScene = nullptr; + } + } + + inline ~SwitcherData() + { + Stop(); + } +}; + +static SwitcherData *switcher = nullptr; + +static inline QString MakeSwitchName(const QString &scene, + const QString &window) +{ + return QStringLiteral("[") + scene + QStringLiteral("]: ") + window; +} + +static inline string GetWeakSourceName(obs_weak_source_t *weak_source) +{ + string name; + + obs_source_t *source = obs_weak_source_get_source(weak_source); + if (source) { + name = obs_source_get_name(source); + obs_source_release(source); + } + + return name; +} + +static inline OBSWeakSource GetWeakSourceByName(const char *name) +{ + OBSWeakSource weak; + obs_source_t *source = obs_get_source_by_name(name); + if (source) { + weak = obs_source_get_weak_source(source); + obs_weak_source_release(weak); + obs_source_release(source); + } + + return weak; +} + +static inline OBSWeakSource GetWeakSourceByQString(const QString &name) +{ + return GetWeakSourceByName(name.toUtf8().constData()); +} + +SceneSwitcher::SceneSwitcher(QWidget *parent) + : QDialog(parent), + ui(new Ui_SceneSwitcher) +{ + ui->setupUi(this); + + lock_guard lock(switcher->m); + + switcher->Prune(); + + BPtr scenes = obs_frontend_get_scene_names(); + char **temp = scenes; + while (*temp) { + const char *name = *temp; + ui->scenes->addItem(name); + ui->noMatchSwitchScene->addItem(name); + temp++; + } + + if (switcher->switchIfNotMatching) + ui->noMatchSwitch->setChecked(true); + else + ui->noMatchDontSwitch->setChecked(true); + + ui->noMatchSwitchScene->setCurrentText( + GetWeakSourceName(switcher->nonMatchingScene).c_str()); + ui->checkInterval->setValue(switcher->interval); + + vector windows; + GetWindowList(windows); + + for (string &window : windows) + ui->windows->addItem(window.c_str()); + + for (auto &s : switcher->switches) { + string sceneName = GetWeakSourceName(s.scene); + QString text = MakeSwitchName(sceneName.c_str(), + s.window.c_str()); + + QListWidgetItem *item = new QListWidgetItem(text, + ui->switches); + item->setData(Qt::UserRole, s.window.c_str()); + } + + if (switcher->th.joinable()) + SetStarted(); + else + SetStopped(); + + loading = false; +} + +void SceneSwitcher::closeEvent(QCloseEvent*) +{ + obs_frontend_save(); +} + +int SceneSwitcher::FindByData(const QString &window) +{ + int count = ui->switches->count(); + int idx = -1; + + for (int i = 0; i < count; i++) { + QListWidgetItem *item = ui->switches->item(i); + QString itemWindow = + item->data(Qt::UserRole).toString(); + + if (itemWindow == window) { + idx = i; + break; + } + } + + return idx; +} + +void SceneSwitcher::on_switches_currentRowChanged(int idx) +{ + if (loading) + return; + if (idx == -1) + return; + + QListWidgetItem *item = ui->switches->item(idx); + + QString window = item->data(Qt::UserRole).toString(); + + lock_guard lock(switcher->m); + for (auto &s : switcher->switches) { + if (window.compare(s.window.c_str()) == 0) { + string name = GetWeakSourceName(s.scene); + ui->scenes->setCurrentText(name.c_str()); + ui->windows->setCurrentText(window); + break; + } + } +} + +void SceneSwitcher::on_close_clicked() +{ + done(0); +} + +void SceneSwitcher::on_add_clicked() +{ + QString sceneName = ui->scenes->currentText(); + QString windowName = ui->windows->currentText(); + + if (windowName.isEmpty()) + return; + + OBSWeakSource source = GetWeakSourceByQString(sceneName); + QVariant v = QVariant::fromValue(windowName); + + QString text = MakeSwitchName(sceneName, windowName); + + int idx = FindByData(windowName); + + if (idx == -1) { + try { + lock_guard lock(switcher->m); + switcher->switches.emplace_back(source, + windowName.toUtf8().constData()); + + QListWidgetItem *item = new QListWidgetItem(text, + ui->switches); + item->setData(Qt::UserRole, v); + } catch (const regex_error &) { + QMessageBox::warning(this, + obs_module_text("InvalidRegex.Title"), + obs_module_text("InvalidRegex.Text")); + } + } else { + QListWidgetItem *item = ui->switches->item(idx); + item->setText(text); + + string window = windowName.toUtf8().constData(); + + { + lock_guard lock(switcher->m); + for (auto &s : switcher->switches) { + if (s.window == window) { + s.scene = source; + break; + } + } + } + + ui->switches->sortItems(); + } +} + +void SceneSwitcher::on_remove_clicked() +{ + QListWidgetItem *item = ui->switches->currentItem(); + if (!item) + return; + + string window = + item->data(Qt::UserRole).toString().toUtf8().constData(); + + { + lock_guard lock(switcher->m); + auto &switches = switcher->switches; + + for (auto it = switches.begin(); it != switches.end(); ++it) { + auto &s = *it; + + if (s.window == window) { + switches.erase(it); + break; + } + } + } + + delete item; +} + +void SceneSwitcher::on_startAtLaunch_toggled(bool value) +{ + if (loading) + return; + + lock_guard lock(switcher->m); + switcher->startAtLaunch = value; +} + +void SceneSwitcher::UpdateNonMatchingScene(const QString &name) +{ + obs_source_t *scene = obs_get_source_by_name( + name.toUtf8().constData()); + obs_weak_source_t *ws = obs_source_get_weak_source(scene); + + switcher->nonMatchingScene = ws; + + obs_weak_source_release(ws); + obs_source_release(scene); +} + +void SceneSwitcher::on_noMatchDontSwitch_clicked() +{ + if (loading) + return; + + lock_guard lock(switcher->m); + switcher->switchIfNotMatching = false; +} + +void SceneSwitcher::on_noMatchSwitch_clicked() +{ + if (loading) + return; + + lock_guard lock(switcher->m); + switcher->switchIfNotMatching = true; + UpdateNonMatchingScene(ui->noMatchSwitchScene->currentText()); +} + +void SceneSwitcher::on_noMatchSwitchScene_currentTextChanged( + const QString &text) +{ + if (loading) + return; + + lock_guard lock(switcher->m); + UpdateNonMatchingScene(text); +} + +void SceneSwitcher::on_checkInterval_valueChanged(int value) +{ + if (loading) + return; + + lock_guard lock(switcher->m); + switcher->interval = value; +} + +void SceneSwitcher::SetStarted() +{ + ui->toggleStartButton->setText(obs_module_text("Stop")); + ui->pluginRunningText->setText(obs_module_text("Active")); +} + +void SceneSwitcher::SetStopped() +{ + ui->toggleStartButton->setText(obs_module_text("Start")); + ui->pluginRunningText->setText(obs_module_text("Inactive")); +} + +void SceneSwitcher::on_toggleStartButton_clicked() +{ + if (switcher->th.joinable()) { + switcher->Stop(); + SetStopped(); + } else { + switcher->Start(); + SetStarted(); + } +} + +static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) +{ + if (saving) { + lock_guard lock(switcher->m); + obs_data_t *obj = obs_data_create(); + obs_data_array_t *array = obs_data_array_create(); + + switcher->Prune(); + + for (SceneSwitch &s : switcher->switches) { + obs_data_t *array_obj = obs_data_create(); + + obs_source_t *source = obs_weak_source_get_source( + s.scene); + if (source) { + const char *n = obs_source_get_name(source); + obs_data_set_string(array_obj, "scene", n); + obs_data_set_string(array_obj, "window_title", + s.window.c_str()); + obs_data_array_push_back(array, array_obj); + obs_source_release(source); + } + + obs_data_release(array_obj); + } + + string nonMatchingSceneName = + GetWeakSourceName(switcher->nonMatchingScene); + + obs_data_set_int(obj, "interval", switcher->interval); + obs_data_set_string(obj, "non_matching_scene", + nonMatchingSceneName.c_str()); + obs_data_set_bool(obj, "switch_if_not_matching", + switcher->switchIfNotMatching); + obs_data_set_bool(obj, "active", switcher->th.joinable()); + obs_data_set_array(obj, "switches", array); + + obs_data_set_obj(save_data, "auto-scene-switcher", obj); + + obs_data_array_release(array); + obs_data_release(obj); + } else { + switcher->m.lock(); + + obs_data_t *obj = obs_data_get_obj(save_data, + "auto-scene-switcher"); + obs_data_array_t *array = obs_data_get_array(obj, "switches"); + size_t count = obs_data_array_count(array); + + if (!obj) + obj = obs_data_create(); + + obs_data_set_default_int(obj, "interval", DEFAULT_INTERVAL); + + switcher->interval = obs_data_get_int(obj, "interval"); + switcher->switchIfNotMatching = + obs_data_get_bool(obj, "switch_if_not_matching"); + string nonMatchingScene = + obs_data_get_string(obj, "non_matching_scene"); + bool active = obs_data_get_bool(obj, "active"); + + switcher->nonMatchingScene = + GetWeakSourceByName(nonMatchingScene.c_str()); + + switcher->switches.clear(); + + for (size_t i = 0; i < count; i++) { + obs_data_t *array_obj = obs_data_array_item(array, i); + + const char *scene = + obs_data_get_string(array_obj, "scene"); + const char *window = + obs_data_get_string(array_obj, "window_title"); + + switcher->switches.emplace_back( + GetWeakSourceByName(scene), + window); + + obs_data_release(array_obj); + } + + obs_data_array_release(array); + obs_data_release(obj); + + switcher->m.unlock(); + + if (active) + switcher->Start(); + else + switcher->Stop(); + } +} + +void SwitcherData::Thread() +{ + chrono::duration duration = + chrono::milliseconds(interval); + string lastTitle; + string title; + + for (;;) { + unique_lock lock(m); + OBSWeakSource scene; + bool match = false; + + cv.wait_for(lock, duration); + if (switcher->stop) { + switcher->stop = false; + break; + } + + duration = chrono::milliseconds(interval); + + GetCurrentWindowTitle(title); + + if (lastTitle != title) { + switcher->Prune(); + + for (SceneSwitch &s : switches) { + if (s.window == title) { + match = true; + scene = s.scene; + break; + } + } + + /* try regex */ + if (!match) { + for (SceneSwitch &s : switches) { + try { + bool matches = regex_match( + title, s.re); + if (matches) { + match = true; + scene = s.scene; + break; + } + } catch (const regex_error &) {} + } + } + + if (!match && switchIfNotMatching && + nonMatchingScene) { + match = true; + scene = nonMatchingScene; + } + + if (match) { + obs_source_t *source = + obs_weak_source_get_source(scene); + obs_source_t *currentSource = + obs_frontend_get_current_scene(); + + if (source && source != currentSource) + obs_frontend_set_current_scene(source); + + obs_source_release(currentSource); + obs_source_release(source); + } + } + + lastTitle = title; + } +} + +void SwitcherData::Start() +{ + if (!switcher->th.joinable()) + switcher->th = thread([] () {switcher->Thread();}); +} + +void SwitcherData::Stop() +{ + if (th.joinable()) { + { + lock_guard lock(m); + stop = true; + } + cv.notify_one(); + th.join(); + } +} + +extern "C" void FreeSceneSwitcher() +{ + delete switcher; + switcher = nullptr; +} + +static void OBSEvent(enum obs_frontend_event event, void *) +{ + if (event == OBS_FRONTEND_EVENT_EXIT) + FreeSceneSwitcher(); +} + +extern "C" void InitSceneSwitcher() +{ + QAction *action = (QAction*)obs_frontend_add_tools_menu_qaction( + obs_module_text("SceneSwitcher")); + + switcher = new SwitcherData; + + auto cb = [] () + { + obs_frontend_push_ui_translation(obs_module_get_string); + + QMainWindow *window = + (QMainWindow*)obs_frontend_get_main_window(); + + SceneSwitcher ss(window); + ss.exec(); + + obs_frontend_pop_ui_translation(); + }; + + obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr); + obs_frontend_add_event_callback(OBSEvent, nullptr); + + action->connect(action, &QAction::triggered, cb); +} diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp b/UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp new file mode 100644 index 00000000000000..4a155ac9ca77e0 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include + +#include "ui_auto-scene-switcher.h" + +struct obs_weak_source; +typedef struct obs_weak_source obs_weak_source_t; + +class QCloseEvent; + +class SceneSwitcher : public QDialog { + Q_OBJECT + +public: + std::unique_ptr ui; + bool loading = true; + + SceneSwitcher(QWidget *parent); + + void closeEvent(QCloseEvent *event) override; + + void SetStarted(); + void SetStopped(); + + int FindByData(const QString &window); + + void UpdateNonMatchingScene(const QString &name); + +public slots: + void on_switches_currentRowChanged(int idx); + void on_close_clicked(); + void on_add_clicked(); + void on_remove_clicked(); + void on_noMatchDontSwitch_clicked(); + void on_noMatchSwitch_clicked(); + void on_startAtLaunch_toggled(bool value); + void on_noMatchSwitchScene_currentTextChanged(const QString &text); + void on_checkInterval_valueChanged(int value); + void on_toggleStartButton_clicked(); +}; + +void GetWindowList(std::vector &windows); +void GetCurrentWindowTitle(std::string &title); diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ca-ES.ini b/UI/frontend-plugins/frontend-tools/data/locale/ca-ES.ini new file mode 100644 index 00000000000000..2b178557e8d105 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/ca-ES.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Canviador d'escena automàtica" +SceneSwitcher.OnNoMatch="Quan no coincideixi amb cap finestra:" +SceneSwitcher.OnNoMatch.DontSwitch="No canviar" +SceneSwitcher.OnNoMatch.SwitchTo="Canvia a:" +SceneSwitcher.CheckInterval="Comprova el títol de la finestra activa cada:" +SceneSwitcher.ActiveOrNotActive="El canviador de escena està:" +Active="Actiu" +Inactive="Inactiu" +Start="Inicia" +Stop="Atura" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/cs-CZ.ini b/UI/frontend-plugins/frontend-tools/data/locale/cs-CZ.ini new file mode 100644 index 00000000000000..c8a45651ef55bb --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/cs-CZ.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatický přepínač scén" +SceneSwitcher.OnNoMatch="Výchozí chování u nesouhlasící:" +SceneSwitcher.OnNoMatch.DontSwitch="Nepřepínat" +SceneSwitcher.OnNoMatch.SwitchTo="Přepnout na:" +SceneSwitcher.CheckInterval="Kontrolovat titulek aktivního okna každých:" +SceneSwitcher.ActiveOrNotActive="Přepínač scén je:" +Active="Aktivní" +Inactive="Neaktivní" +Start="Spustit" +Stop="Zastavit" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/da-DK.ini b/UI/frontend-plugins/frontend-tools/data/locale/da-DK.ini new file mode 100644 index 00000000000000..f72389cd61c883 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/da-DK.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatisk sceneskifter" +SceneSwitcher.OnNoMatch="Når intet vindue svarer til:" +SceneSwitcher.OnNoMatch.DontSwitch="Skift ikke" +SceneSwitcher.OnNoMatch.SwitchTo="Skift til:" +SceneSwitcher.CheckInterval="Kontroller aktivt vinduestitel hvert:" +SceneSwitcher.ActiveOrNotActive="Sceneskifter er:" +Active="Aktiv" +Inactive="Inaktiv" +Start="Start" +Stop="Stop" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/de-DE.ini b/UI/frontend-plugins/frontend-tools/data/locale/de-DE.ini new file mode 100644 index 00000000000000..4caf47eabb0410 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/de-DE.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatischer Szenenwechsler" +SceneSwitcher.OnNoMatch="Wenn kein Fenster passt:" +SceneSwitcher.OnNoMatch.DontSwitch="Nicht wechseln" +SceneSwitcher.OnNoMatch.SwitchTo="Wechseln zu:" +SceneSwitcher.CheckInterval="Titel des aktiven Fensters überprüfen alle:" +SceneSwitcher.ActiveOrNotActive="Szenenwechsler ist:" +Active="Aktiv" +Inactive="Inaktiv" +Start="Start" +Stop="Stop" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini b/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini new file mode 100644 index 00000000000000..154241d4438ec5 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini @@ -0,0 +1,12 @@ +SceneSwitcher="Automatic Scene Switcher" +SceneSwitcher.OnNoMatch="When no window matches:" +SceneSwitcher.OnNoMatch.DontSwitch="Don't switch" +SceneSwitcher.OnNoMatch.SwitchTo="Switch to:" +SceneSwitcher.CheckInterval="Check active window title every:" +SceneSwitcher.ActiveOrNotActive="Scene Switcher is:" +InvalidRegex.Title="Invalid Regular Expression" +InvalidRegex.Text="The regular expression that you entered is invalid." +Active="Active" +Inactive="Inactive" +Start="Start" +Stop="Stop" diff --git a/UI/frontend-plugins/frontend-tools/data/locale/es-ES.ini b/UI/frontend-plugins/frontend-tools/data/locale/es-ES.ini new file mode 100644 index 00000000000000..a08a8f9c7bb143 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/es-ES.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Cambiador de escena automática" +SceneSwitcher.OnNoMatch="Cuando no coincida con ninguna ventana:" +SceneSwitcher.OnNoMatch.DontSwitch="No cambiar" +SceneSwitcher.OnNoMatch.SwitchTo="Cambiar a:" +SceneSwitcher.CheckInterval="Comprobar el título de la ventana activa cada:" +SceneSwitcher.ActiveOrNotActive="El cambiador de escena está:" +Active="Activo" +Inactive="Inactivo" +Start="Iniciar" +Stop="Detener" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/eu-ES.ini b/UI/frontend-plugins/frontend-tools/data/locale/eu-ES.ini new file mode 100644 index 00000000000000..99076b329835e0 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/eu-ES.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Eszena-aldatzaile automatikoa" +SceneSwitcher.OnNoMatch="Leihoa bat etortzen ez denean:" +SceneSwitcher.OnNoMatch.DontSwitch="Ez aldatu" +SceneSwitcher.OnNoMatch.SwitchTo="Aldatu hona:" +SceneSwitcher.CheckInterval="Leiho aktiboaren titulua egiaztatzeko maiztasuna:" +SceneSwitcher.ActiveOrNotActive="Eszena aldatzailea dago:" +Active="Aktiboa" +Inactive="Inaktiboa" +Start="Hasi" +Stop="Gelditu" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/fi-FI.ini b/UI/frontend-plugins/frontend-tools/data/locale/fi-FI.ini new file mode 100644 index 00000000000000..0f208689db6c93 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/fi-FI.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automaattinen skenen vaihtaja" +SceneSwitcher.OnNoMatch="Kun ikkunaa ei löydy:" +SceneSwitcher.OnNoMatch.DontSwitch="Älä vaihda" +SceneSwitcher.OnNoMatch.SwitchTo="Vaihda:" +SceneSwitcher.CheckInterval="Tarkista aktiivisen ikkunan otsikko:" +SceneSwitcher.ActiveOrNotActive="Skenen vaihtaja:" +Active="Aktiivinen" +Inactive="Ei käytössä" +Start="Käynnistä" +Stop="Pysäytä" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/fr-FR.ini b/UI/frontend-plugins/frontend-tools/data/locale/fr-FR.ini new file mode 100644 index 00000000000000..65d0b4c3c65765 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/fr-FR.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Sélecteur automatique de scène" +SceneSwitcher.OnNoMatch="Si aucune fenêtre ne correspond :" +SceneSwitcher.OnNoMatch.DontSwitch="Ne rien faire" +SceneSwitcher.OnNoMatch.SwitchTo="Basculer vers :" +SceneSwitcher.CheckInterval="Détecter le titre de la fenêtre active toutes les :" +SceneSwitcher.ActiveOrNotActive="Etat du sélecteur automatique :" +Active="Actif" +Inactive="Inactif" +Start="Démarrer" +Stop="Arrêter" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/hr-HR.ini b/UI/frontend-plugins/frontend-tools/data/locale/hr-HR.ini new file mode 100644 index 00000000000000..fb81a5492b8769 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/hr-HR.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatski menjač scena" +SceneSwitcher.OnNoMatch="Kada se nijedan naslov prozora ne poklapa:" +SceneSwitcher.OnNoMatch.DontSwitch="Ne menjaj" +SceneSwitcher.OnNoMatch.SwitchTo="Promeni na:" +SceneSwitcher.CheckInterval="Proveravaj naziv aktivnog prozora svakih:" +SceneSwitcher.ActiveOrNotActive="Menjač scena je:" +Active="Aktivan" +Inactive="Neaktivan" +Start="Pokreni" +Stop="Zaustavi" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/hu-HU.ini b/UI/frontend-plugins/frontend-tools/data/locale/hu-HU.ini new file mode 100644 index 00000000000000..bfd99aa64220e6 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/hu-HU.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatikus jelenetváltó" +SceneSwitcher.OnNoMatch="Ha nem található megfelelő ablak:" +SceneSwitcher.OnNoMatch.DontSwitch="Ne váltson" +SceneSwitcher.OnNoMatch.SwitchTo="Váltson:" +SceneSwitcher.CheckInterval="Aktív ablak ellenőrzése ennyi időközönként:" +SceneSwitcher.ActiveOrNotActive="Jelenet váltó:" +Active="Aktív" +Inactive="Inaktiv" +Start="Start" +Stop="Stop" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ja-JP.ini b/UI/frontend-plugins/frontend-tools/data/locale/ja-JP.ini new file mode 100644 index 00000000000000..714c79cdcfa2a8 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/ja-JP.ini @@ -0,0 +1,11 @@ +SceneSwitcher="自動シーンスイッチャー" +SceneSwitcher.OnNoMatch="どのウィンドウにも一致しない場合:" +SceneSwitcher.OnNoMatch.DontSwitch="切り替えない" +SceneSwitcher.OnNoMatch.SwitchTo="切り替える:" +SceneSwitcher.CheckInterval="アクティブウィンドウタイトルを確認する間隔:" +SceneSwitcher.ActiveOrNotActive="シーンスイッチャーは:" +Active="アクティブ" +Inactive="非アクティブ" +Start="開始" +Stop="停止" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ko-KR.ini b/UI/frontend-plugins/frontend-tools/data/locale/ko-KR.ini new file mode 100644 index 00000000000000..cbfee50a85b04f --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/ko-KR.ini @@ -0,0 +1,11 @@ +SceneSwitcher="자동 장면 전환기" +SceneSwitcher.OnNoMatch="일치하는 윈도우가 없는 경우:" +SceneSwitcher.OnNoMatch.DontSwitch="전환하지 않음" +SceneSwitcher.OnNoMatch.SwitchTo="여기로 전환:" +SceneSwitcher.CheckInterval="활성화된 윈도우 제목을 확인:" +SceneSwitcher.ActiveOrNotActive="장면 전환기:" +Active="활성화" +Inactive="비활성화" +Start="시작" +Stop="중단" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/nl-NL.ini b/UI/frontend-plugins/frontend-tools/data/locale/nl-NL.ini new file mode 100644 index 00000000000000..b785c86159bf87 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/nl-NL.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatische Scènewisselaar" +SceneSwitcher.OnNoMatch="Als geen venster overeenkomt:" +SceneSwitcher.OnNoMatch.DontSwitch="Wissel niet" +SceneSwitcher.OnNoMatch.SwitchTo="Wissel naar:" +SceneSwitcher.CheckInterval="Controleer actieve venstertitel elke:" +SceneSwitcher.ActiveOrNotActive="Scènewisselaar is:" +Active="Actief" +Inactive="Inactief" +Start="Start" +Stop="Stop" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/pl-PL.ini b/UI/frontend-plugins/frontend-tools/data/locale/pl-PL.ini new file mode 100644 index 00000000000000..dd2d18dc193a93 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/pl-PL.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatyczne Przełączanie Scen" +SceneSwitcher.OnNoMatch="Gdy nie pasuje żadne okno:" +SceneSwitcher.OnNoMatch.DontSwitch="Nie przełączaj" +SceneSwitcher.OnNoMatch.SwitchTo="Przełącz na:" +SceneSwitcher.CheckInterval="Sprawdź tytuł aktywnego okna co:" +SceneSwitcher.ActiveOrNotActive="Przełączanie scen jest:" +Active="Aktywne" +Inactive="Nieaktywne" +Start="Start" +Stop="Stop" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/pt-BR.ini b/UI/frontend-plugins/frontend-tools/data/locale/pt-BR.ini new file mode 100644 index 00000000000000..5a6a4eb4cc3515 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/pt-BR.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Alternador automático de cena" +SceneSwitcher.OnNoMatch="Quando nenhuma janela corresponde:" +SceneSwitcher.OnNoMatch.DontSwitch="Não alternar" +SceneSwitcher.OnNoMatch.SwitchTo="Alternar para:" +SceneSwitcher.CheckInterval="Checar o título da janela ativa a cada:" +SceneSwitcher.ActiveOrNotActive="O alternador de cenas está:" +Active="Ligado" +Inactive="Desligado" +Start="Iniciar" +Stop="Parar" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ru-RU.ini b/UI/frontend-plugins/frontend-tools/data/locale/ru-RU.ini new file mode 100644 index 00000000000000..831e0d00d73ae1 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/ru-RU.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Автоматический переключатель сцен" +SceneSwitcher.OnNoMatch="Когда ни одно окно не подходит:" +SceneSwitcher.OnNoMatch.DontSwitch="Не переключать" +SceneSwitcher.OnNoMatch.SwitchTo="Переключать на:" +SceneSwitcher.CheckInterval="Проверять имя активного окна каждые:" +SceneSwitcher.ActiveOrNotActive="Переключатель сцен:" +Active="Активен" +Inactive="Неактивен" +Start="Запустить" +Stop="Остановить" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/sr-CS.ini b/UI/frontend-plugins/frontend-tools/data/locale/sr-CS.ini new file mode 100644 index 00000000000000..fb81a5492b8769 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/sr-CS.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatski menjač scena" +SceneSwitcher.OnNoMatch="Kada se nijedan naslov prozora ne poklapa:" +SceneSwitcher.OnNoMatch.DontSwitch="Ne menjaj" +SceneSwitcher.OnNoMatch.SwitchTo="Promeni na:" +SceneSwitcher.CheckInterval="Proveravaj naziv aktivnog prozora svakih:" +SceneSwitcher.ActiveOrNotActive="Menjač scena je:" +Active="Aktivan" +Inactive="Neaktivan" +Start="Pokreni" +Stop="Zaustavi" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/sr-SP.ini b/UI/frontend-plugins/frontend-tools/data/locale/sr-SP.ini new file mode 100644 index 00000000000000..983378fdd56a57 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/sr-SP.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Аутоматски мењач сцена" +SceneSwitcher.OnNoMatch="Када се ниједан наслов прозора не поклапа:" +SceneSwitcher.OnNoMatch.DontSwitch="Не мењај" +SceneSwitcher.OnNoMatch.SwitchTo="Промени на:" +SceneSwitcher.CheckInterval="Проверавај назив активног прозора сваких:" +SceneSwitcher.ActiveOrNotActive="Мењач сцена је:" +Active="Активан" +Inactive="Неактиван" +Start="Покрени" +Stop="Заустави" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/sv-SE.ini b/UI/frontend-plugins/frontend-tools/data/locale/sv-SE.ini new file mode 100644 index 00000000000000..a2f6330c46b66b --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/sv-SE.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatisk scenbytare" +SceneSwitcher.OnNoMatch="När inget fönster matchar:" +SceneSwitcher.OnNoMatch.DontSwitch="Byt inte" +SceneSwitcher.OnNoMatch.SwitchTo="Byt till:" +SceneSwitcher.CheckInterval="Kontrollera aktiv fönstertitel varje:" +SceneSwitcher.ActiveOrNotActive="Scenbytaren är:" +Active="Aktiv" +Inactive="Inaktiv" +Start="Starta" +Stop="Stoppa" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/uk-UA.ini b/UI/frontend-plugins/frontend-tools/data/locale/uk-UA.ini new file mode 100644 index 00000000000000..12a1a0b0398386 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/uk-UA.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Автоматичний перехід між Сценами" +SceneSwitcher.OnNoMatch="Якщо нема відповідних вікон:" +SceneSwitcher.OnNoMatch.DontSwitch="Нічого не робити" +SceneSwitcher.OnNoMatch.SwitchTo="Перейти до:" +SceneSwitcher.CheckInterval="Перевіряти заголовок активного вікна кожні:" +SceneSwitcher.ActiveOrNotActive="Автоматичний перехід між Сценами:" +Active="Активний" +Inactive="Неактивний" +Start="Запустити" +Stop="Зупинити" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/zh-CN.ini b/UI/frontend-plugins/frontend-tools/data/locale/zh-CN.ini new file mode 100644 index 00000000000000..1ae756f64a7786 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/zh-CN.ini @@ -0,0 +1,11 @@ +SceneSwitcher="自动场景切换器" +SceneSwitcher.OnNoMatch="当没有窗口匹配的时候:" +SceneSwitcher.OnNoMatch.DontSwitch="不切换" +SceneSwitcher.OnNoMatch.SwitchTo="切换到:" +SceneSwitcher.CheckInterval="检查活动窗口的标题,每:" +SceneSwitcher.ActiveOrNotActive="场景切换器是:" +Active="已激活" +Inactive="未激活" +Start="开始" +Stop="停止" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/zh-TW.ini b/UI/frontend-plugins/frontend-tools/data/locale/zh-TW.ini new file mode 100644 index 00000000000000..ac7668aee2c4d5 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/zh-TW.ini @@ -0,0 +1,11 @@ +SceneSwitcher="自動場景切換器" +SceneSwitcher.OnNoMatch="當沒有視窗相符時︰" +SceneSwitcher.OnNoMatch.DontSwitch="不切換" +SceneSwitcher.OnNoMatch.SwitchTo="切換到︰" +SceneSwitcher.CheckInterval="檢查使用中視窗標題的頻率︰" +SceneSwitcher.ActiveOrNotActive="場景切換器︰" +Active="啟動中" +Inactive="未啟動" +Start="開始" +Stop="停止" + diff --git a/UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui b/UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui new file mode 100644 index 00000000000000..ea8b51edfdb4c9 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui @@ -0,0 +1,310 @@ + + + SceneSwitcher + + + + 0 + 0 + 743 + 563 + + + + SceneSwitcher + + + + + + + + true + + + 20 + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + + + + 0 + 0 + + + + true + + + + + + + 4 + + + + + + 22 + 22 + + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + true + + + removeIconSmall + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + 0 + 0 + + + + SceneSwitcher.OnNoMatch + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + SceneSwitcher.OnNoMatch.DontSwitch + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + SceneSwitcher.OnNoMatch.SwitchTo + + + + + + + false + + + + 100 + 0 + + + + + + + + + + + + SceneSwitcher.CheckInterval + + + checkInterval + + + + + + + + 100 + 0 + + + + ms + + + 50 + + + 20000 + + + 300 + + + + + + + SceneSwitcher.ActiveOrNotActive + + + + + + + Qt::Horizontal + + + + 200 + 20 + + + + + + + + Start + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 40 + + + + + + + + Not Active + + + + + + + + + Close + + + + + + + + + noMatchSwitch + toggled(bool) + noMatchSwitchScene + setEnabled(bool) + + + 286 + 347 + + + 483 + 352 + + + + + diff --git a/UI/frontend-plugins/frontend-tools/frontend-tools.c b/UI/frontend-plugins/frontend-tools/frontend-tools.c new file mode 100644 index 00000000000000..3826f041f3d4ff --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/frontend-tools.c @@ -0,0 +1,18 @@ +#include + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("frontend-tools", "en-US") + +void InitSceneSwitcher(); +void FreeSceneSwitcher(); + +bool obs_module_load(void) +{ + InitSceneSwitcher(); + return true; +} + +void obs_module_unload(void) +{ + FreeSceneSwitcher(); +} diff --git a/obs/hotkey-edit.cpp b/UI/hotkey-edit.cpp similarity index 100% rename from obs/hotkey-edit.cpp rename to UI/hotkey-edit.cpp diff --git a/obs/hotkey-edit.hpp b/UI/hotkey-edit.hpp similarity index 100% rename from obs/hotkey-edit.hpp rename to UI/hotkey-edit.hpp diff --git a/obs/installer/mp-installer.nsi b/UI/installer/mp-installer.nsi similarity index 89% rename from obs/installer/mp-installer.nsi rename to UI/installer/mp-installer.nsi index 2a9146ff382d75..28c2229fc68b21 100644 --- a/obs/installer/mp-installer.nsi +++ b/UI/installer/mp-installer.nsi @@ -156,12 +156,18 @@ Function PreReqCheck ClearErrors ; Check previous instance - ; System::Call 'kernel32::OpenMutexW(i 0x100000, b 0, w "OBSMutex") i .R0' - ; IntCmp $R0 0 notRunning - ; System::Call 'kernel32::CloseHandle(i $R0)' - ; MessageBox MB_OK|MB_ICONEXCLAMATION "${APPNAME} is already running. Please close it first before installing a new version." /SD IDOK - ; Quit -notRunning: + FindProcDLL::FindProc "obs32.exe" + IntCmp $R0 1 0 notRunning1 + MessageBox MB_OK|MB_ICONEXCLAMATION "${APPNAME} is already running. Please close it first before installing a new version." /SD IDOK + Quit + notRunning1: + ${if} ${RunningX64} + FindProcDLL::FindProc "obs64.exe" + IntCmp $R0 1 0 notRunning2 + MessageBox MB_OK|MB_ICONEXCLAMATION "${APPNAME} is already running. Please close it first before installing a new version." /SD IDOK + Quit + ${endif} + notRunning2: FunctionEnd @@ -175,6 +181,10 @@ Section "OBS Studio" Section1 ; Set Section properties SetOverwrite on + AllowSkipFiles off + + FindProcDLL::KillProc "obs-plugins\32bit\cef-bootstrap.exe" + FindProcDLL::KillProc "obs-plugins\64bit\cef-bootstrap.exe" SetShellVarContext all @@ -283,4 +293,13 @@ SectionEnd !insertmacro MUI_DESCRIPTION_TEXT ${Section2} "Removes all settings, plugins, scenes and sources, profiles, log files and other application data." !insertmacro MUI_UNFUNCTION_DESCRIPTION_END +; Version information +VIProductVersion "0.${APPVERSION}" +VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "OBS Studio" +VIAddVersionKey /LANG=${LANG_ENGLISH} "CompanyName" "obsproject.com" +VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "(c) 2012-2016" +; FileDescription is what shows in the UAC elevation prompt when signed +VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "OBS Studio" +VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "1.0" + ; eof diff --git a/obs/item-widget-helpers.cpp b/UI/item-widget-helpers.cpp similarity index 100% rename from obs/item-widget-helpers.cpp rename to UI/item-widget-helpers.cpp diff --git a/obs/item-widget-helpers.hpp b/UI/item-widget-helpers.hpp similarity index 100% rename from obs/item-widget-helpers.hpp rename to UI/item-widget-helpers.hpp diff --git a/obs/menu-button.cpp b/UI/menu-button.cpp similarity index 100% rename from obs/menu-button.cpp rename to UI/menu-button.cpp diff --git a/obs/menu-button.hpp b/UI/menu-button.hpp similarity index 100% rename from obs/menu-button.hpp rename to UI/menu-button.hpp diff --git a/obs/mute-checkbox.hpp b/UI/mute-checkbox.hpp similarity index 100% rename from obs/mute-checkbox.hpp rename to UI/mute-checkbox.hpp diff --git a/obs/obs-app.cpp b/UI/obs-app.cpp similarity index 98% rename from obs/obs-app.cpp rename to UI/obs-app.cpp index 232f8c85f48f78..e7bb8f64313504 100644 --- a/obs/obs-app.cpp +++ b/UI/obs-app.cpp @@ -360,6 +360,10 @@ bool OBSApp::InitGlobalConfigDefaults() "RecordWhenStreaming", false); config_set_default_bool(globalConfig, "BasicWindow", "KeepRecordingWhenStreamStops", false); + config_set_default_bool(globalConfig, "BasicWindow", + "SysTrayEnabled", true); + config_set_default_bool(globalConfig, "BasicWindow", + "SysTrayWhenStarted", false); config_set_default_bool(globalConfig, "BasicWindow", "ShowTransitions", true); config_set_default_bool(globalConfig, "BasicWindow", @@ -933,12 +937,22 @@ const char *OBSApp::GetCurrentLog() const return currentLogFile.c_str(); } +bool OBSApp::TranslateString(const char *lookupVal, const char **out) const +{ + for (obs_frontend_translate_ui_cb cb : translatorHooks) { + if (cb(lookupVal, out)) + return true; + } + + return text_lookup_getstr(App()->GetTextLookup(), lookupVal, out); +} + QString OBSTranslator::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const { const char *out = nullptr; - if (!text_lookup_getstr(App()->GetTextLookup(), sourceText, &out)) - return QString(); + if (!App()->TranslateString(sourceText, &out)) + return QString(sourceText); UNUSED_PARAMETER(context); UNUSED_PARAMETER(disambiguation); @@ -1219,7 +1233,7 @@ static auto SnapshotRelease = [](profiler_snapshot_t *snap) profile_snapshot_free(snap); }; -using ProfilerSnapshot = +using ProfilerSnapshot = std::unique_ptr; ProfilerSnapshot GetSnapshot() @@ -1504,17 +1518,19 @@ bool GetClosestUnusedFileName(std::string &path, const char *extension) return true; } -bool WindowPositionValid(int x, int y) +bool WindowPositionValid(QRect rect) { vector monitors; GetMonitors(monitors); for (auto &monitor : monitors) { - int br_x = monitor.x + monitor.cx; - int br_y = monitor.y + monitor.cy; + int left = int(monitor.x); + int top = int(monitor.y); + int right = left + int(monitor.cx); + int bottom = top + int(monitor.cy); - if (x >= monitor.x && x < br_x && - y >= monitor.y && y < br_y) + if ((rect.left() - right) < 0 && (left - rect.right()) < 0 && + (rect.top() - bottom) < 0 && (top - rect.bottom()) < 0) return true; } diff --git a/obs/obs-app.hpp b/UI/obs-app.hpp similarity index 92% rename from obs/obs-app.hpp rename to UI/obs-app.hpp index a341a821eb8955..9c45803e0b2c6c 100644 --- a/obs/obs-app.hpp +++ b/UI/obs-app.hpp @@ -25,9 +25,11 @@ #include #include #include +#include #include #include #include +#include #include "window-main.hpp" @@ -71,6 +73,8 @@ class OBSApp : public QApplication { os_inhibit_t *sleepInhibitor = nullptr; int sleepInhibitRefs = 0; + std::deque translatorHooks; + bool InitGlobalConfig(); bool InitGlobalConfigDefaults(); bool InitLocale(); @@ -102,6 +106,8 @@ class OBSApp : public QApplication { return textLookup.GetString(lookupVal); } + bool TranslateString(const char *lookupVal, const char **out) const; + profiler_name_store_t *GetProfilerNameStore() const { return profilerNameStore; @@ -131,6 +137,16 @@ class OBSApp : public QApplication { if (--sleepInhibitRefs == 0) os_inhibit_sleep_set_active(sleepInhibitor, false); } + + inline void PushUITranslation(obs_frontend_translate_ui_cb cb) + { + translatorHooks.emplace_front(cb); + } + + inline void PopUITranslation() + { + translatorHooks.pop_front(); + } }; int GetConfigPath(char *path, size_t size, const char *name); @@ -150,7 +166,7 @@ inline const char *Str(const char *lookup) {return App()->GetString(lookup);} bool GetFileSafeName(const char *name, std::string &file); bool GetClosestUnusedFileName(std::string &path, const char *extension); -bool WindowPositionValid(int x, int y); +bool WindowPositionValid(QRect rect); static inline int GetProfilePath(char *path, size_t size, const char *file) { diff --git a/UI/obs-frontend-api/CMakeLists.txt b/UI/obs-frontend-api/CMakeLists.txt new file mode 100644 index 00000000000000..38eefb69eb08a4 --- /dev/null +++ b/UI/obs-frontend-api/CMakeLists.txt @@ -0,0 +1,20 @@ +project(obs-frontend-api) + +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") + +add_definitions(-DLIBOBS_EXPORTS) + +set(obs-frontend-api_SOURCES + obs-frontend-api.cpp) + +set(obs-frontend-api_HEADERS + obs-frontend-internal.hpp + obs-frontend-api.h) + +add_library(obs-frontend-api SHARED + ${obs-frontend-api_SOURCES} + ${obs-frontend-api_HEADERS}) +target_link_libraries(obs-frontend-api + libobs) + +install_obs_core(obs-frontend-api) diff --git a/UI/obs-frontend-api/obs-frontend-api.cpp b/UI/obs-frontend-api/obs-frontend-api.cpp new file mode 100644 index 00000000000000..5d01b64d8e50d4 --- /dev/null +++ b/UI/obs-frontend-api/obs-frontend-api.cpp @@ -0,0 +1,295 @@ +#include "obs-frontend-internal.hpp" +#include + +using namespace std; + +static unique_ptr c; + +void obs_frontend_set_callbacks_internal(obs_frontend_callbacks *callbacks) +{ + c.reset(callbacks); +} + +static inline bool callbacks_valid_(const char *func_name) +{ + if (!c) { + blog(LOG_WARNING, "Tried to call %s with no callbacks!", + func_name); + return false; + } + + return true; +} + +#define callbacks_valid() callbacks_valid_(__FUNCTION__) + +static char **convert_string_list(vector &strings) +{ + size_t size = 0; + size_t string_data_offset = (strings.size() + 1) * sizeof(char*); + uint8_t *out; + char **ptr_list; + char *string_data; + + size += string_data_offset; + + for (auto &str : strings) + size += str.size() + 1; + + if (!size) + return 0; + + out = (uint8_t*)bmalloc(size); + ptr_list = (char**)out; + string_data = (char*)(out + string_data_offset); + + for (auto &str : strings) { + *ptr_list = string_data; + memcpy(string_data, str.c_str(), str.size() + 1); + + ptr_list++; + string_data += str.size() + 1; + } + + *ptr_list = nullptr; + return (char**)out; +} + +/* ------------------------------------------------------------------------- */ + +void *obs_frontend_get_main_window(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_main_window() + : nullptr; +} + +void *obs_frontend_get_main_window_handle(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_main_window_handle() + : nullptr; +} + +char **obs_frontend_get_scene_names(void) +{ + if (!callbacks_valid()) + return NULL; + + struct obs_frontend_source_list sources = {}; + vector names; + c->obs_frontend_get_scenes(&sources); + + for (size_t i = 0; i < sources.sources.num; i++) { + obs_source_t *source = sources.sources.array[i]; + const char *name = obs_source_get_name(source); + names.emplace_back(name); + } + + obs_frontend_source_list_free(&sources); + return convert_string_list(names); +} + +void obs_frontend_get_scenes(struct obs_frontend_source_list *sources) +{ + if (callbacks_valid()) c->obs_frontend_get_scenes(sources); +} + +obs_source_t *obs_frontend_get_current_scene(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_current_scene() + : nullptr; +} + +void obs_frontend_set_current_scene(obs_source_t *scene) +{ + if (callbacks_valid()) c->obs_frontend_set_current_scene(scene); +} + +void obs_frontend_get_transitions(struct obs_frontend_source_list *sources) +{ + if (callbacks_valid()) c->obs_frontend_get_transitions(sources); +} + +obs_source_t *obs_frontend_get_current_transition(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_current_transition() + : nullptr; +} + +void obs_frontend_set_current_transition(obs_source_t *transition) +{ + if (callbacks_valid()) + c->obs_frontend_set_current_transition(transition); +} + +char **obs_frontend_get_scene_collections(void) +{ + if (!callbacks_valid()) + return nullptr; + + vector strings; + c->obs_frontend_get_scene_collections(strings); + return convert_string_list(strings); +} + +char *obs_frontend_get_current_scene_collection(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_current_scene_collection() + : nullptr; +} + +void obs_frontend_set_current_scene_collection(const char *collection) +{ + if (callbacks_valid()) + c->obs_frontend_set_current_scene_collection(collection); +} + +char **obs_frontend_get_profiles(void) +{ + if (!callbacks_valid()) + return nullptr; + + vector strings; + c->obs_frontend_get_profiles(strings); + return convert_string_list(strings); +} + +char *obs_frontend_get_current_profile(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_current_profile() + : nullptr; +} + +void obs_frontend_set_current_profile(const char *profile) +{ + if (callbacks_valid()) + c->obs_frontend_set_current_profile(profile); +} + +void obs_frontend_streaming_start(void) +{ + if (callbacks_valid()) c->obs_frontend_streaming_start(); +} + +void obs_frontend_streaming_stop(void) +{ + if (callbacks_valid()) c->obs_frontend_streaming_stop(); +} + +bool obs_frontend_streaming_active(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_streaming_active() + : false; +} + +void obs_frontend_recording_start(void) +{ + if (callbacks_valid()) c->obs_frontend_recording_start(); +} + +void obs_frontend_recording_stop(void) +{ + if (callbacks_valid()) c->obs_frontend_recording_stop(); +} + +bool obs_frontend_recording_active(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_recording_active() + : false; +} + +void *obs_frontend_add_tools_menu_qaction(const char *name) +{ + return !!callbacks_valid() + ? c->obs_frontend_add_tools_menu_qaction(name) + : nullptr; +} + +void obs_frontend_add_tools_menu_item(const char *name, + obs_frontend_cb callback, void *private_data) +{ + if (callbacks_valid()) + c->obs_frontend_add_tools_menu_item(name, callback, + private_data); +} + +void obs_frontend_add_event_callback(obs_frontend_event_cb callback, + void *private_data) +{ + if (callbacks_valid()) + c->obs_frontend_add_event_callback(callback, private_data); +} + +void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, + void *private_data) +{ + if (callbacks_valid()) + c->obs_frontend_remove_event_callback(callback, private_data); +} + +obs_output_t *obs_frontend_get_streaming_output(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_streaming_output() + : nullptr; +} + +obs_output_t *obs_frontend_get_recording_output(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_recording_output() + : nullptr; +} + +config_t *obs_frontend_get_profile_config(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_profile_config() + : nullptr; +} + +config_t *obs_frontend_get_global_config(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_global_config() + : nullptr; +} + +void obs_frontend_save(void) +{ + if (callbacks_valid()) + c->obs_frontend_save(); +} + +void obs_frontend_add_save_callback(obs_frontend_save_cb callback, + void *private_data) +{ + if (callbacks_valid()) + c->obs_frontend_add_save_callback(callback, private_data); +} + +void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, + void *private_data) +{ + if (callbacks_valid()) + c->obs_frontend_remove_save_callback(callback, private_data); +} + +void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) +{ + if (callbacks_valid()) + c->obs_frontend_push_ui_translation(translate); +} + +void obs_frontend_pop_ui_translation(void) +{ + if (callbacks_valid()) + c->obs_frontend_pop_ui_translation(); +} diff --git a/UI/obs-frontend-api/obs-frontend-api.h b/UI/obs-frontend-api/obs-frontend-api.h new file mode 100644 index 00000000000000..8a6272a2ac1abb --- /dev/null +++ b/UI/obs-frontend-api/obs-frontend-api.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct config_data; +typedef struct config_data config_t; + +struct obs_data; +typedef struct obs_data obs_data_t; + +/* ------------------------------------------------------------------------- */ + +struct obs_frontend_source_list { + DARRAY(obs_source_t*) sources; +}; + +static inline void obs_frontend_source_list_free( + struct obs_frontend_source_list *source_list) +{ + size_t num = source_list->sources.num; + for (size_t i = 0; i < num; i++) + obs_source_release(source_list->sources.array[i]); + da_free(source_list->sources); +} + +/* ------------------------------------------------------------------------- */ + +/* NOTE: Functions that return char** string lists are a single allocation of + * memory with pointers to itself. Free with a single call to bfree on the + * base char** pointer. */ + +/* NOTE: User interface should not use typical Qt locale translation methods, + * as the OBS UI bypasses it to use a custom translation implementation. Use + * standard module translation methods, obs_module_text. For text in a Qt + * window, use obs_frontend_push_ui_translation when the text is about to be + * translated, and obs_frontend_pop_ui_translation when translation is + * complete. */ + +EXPORT void *obs_frontend_get_main_window(void); +EXPORT void *obs_frontend_get_main_window_handle(void); + +EXPORT char **obs_frontend_get_scene_names(void); +EXPORT void obs_frontend_get_scenes(struct obs_frontend_source_list *sources); +EXPORT obs_source_t *obs_frontend_get_current_scene(void); +EXPORT void obs_frontend_set_current_scene(obs_source_t *scene); + +EXPORT void obs_frontend_get_transitions( + struct obs_frontend_source_list *sources); +EXPORT obs_source_t *obs_frontend_get_current_transition(void); +EXPORT void obs_frontend_set_current_transition(obs_source_t *transition); + +EXPORT char **obs_frontend_get_scene_collections(void); +EXPORT char *obs_frontend_get_current_scene_collection(void); +EXPORT void obs_frontend_set_current_scene_collection(const char *collection); + +EXPORT char **obs_frontend_get_profiles(void); +EXPORT char *obs_frontend_get_current_profile(void); +EXPORT void obs_frontend_set_current_profile(const char *profile); + +EXPORT void obs_frontend_streaming_start(void); +EXPORT void obs_frontend_streaming_stop(void); +EXPORT bool obs_frontend_streaming_active(void); + +EXPORT void obs_frontend_recording_start(void); +EXPORT void obs_frontend_recording_stop(void); +EXPORT bool obs_frontend_recording_active(void); + +typedef void (*obs_frontend_cb)(void *private_data); + +EXPORT void *obs_frontend_add_tools_menu_qaction(const char *name); +EXPORT void obs_frontend_add_tools_menu_item(const char *name, + obs_frontend_cb callback, void *private_data); + +enum obs_frontend_event { + OBS_FRONTEND_EVENT_STREAMING_STARTING, + OBS_FRONTEND_EVENT_STREAMING_STARTED, + OBS_FRONTEND_EVENT_STREAMING_STOPPING, + OBS_FRONTEND_EVENT_STREAMING_STOPPED, + OBS_FRONTEND_EVENT_RECORDING_STARTING, + OBS_FRONTEND_EVENT_RECORDING_STARTED, + OBS_FRONTEND_EVENT_RECORDING_STOPPING, + OBS_FRONTEND_EVENT_RECORDING_STOPPED, + OBS_FRONTEND_EVENT_SCENE_CHANGED, + OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED, + OBS_FRONTEND_EVENT_TRANSITION_CHANGED, + OBS_FRONTEND_EVENT_TRANSITION_STOPPED, + OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED, + OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED, + OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED, + OBS_FRONTEND_EVENT_PROFILE_CHANGED, + OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED, + OBS_FRONTEND_EVENT_EXIT +}; + +typedef void (*obs_frontend_event_cb)(enum obs_frontend_event event, + void *private_data); + +EXPORT void obs_frontend_add_event_callback(obs_frontend_event_cb callback, + void *private_data); +EXPORT void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, + void *private_data); + +typedef void (*obs_frontend_save_cb)(obs_data_t *save_data, bool saving, + void *private_data); + +EXPORT void obs_frontend_save(void); +EXPORT void obs_frontend_add_save_callback(obs_frontend_save_cb callback, + void *private_data); +EXPORT void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, + void *private_data); + +EXPORT obs_output_t *obs_frontend_get_streaming_output(void); +EXPORT obs_output_t *obs_frontend_get_recording_output(void); + +EXPORT config_t *obs_frontend_get_profile_config(void); +EXPORT config_t *obs_frontend_get_global_config(void); + +typedef bool (*obs_frontend_translate_ui_cb)(const char *text, + const char **out); + +EXPORT void obs_frontend_push_ui_translation( + obs_frontend_translate_ui_cb translate); +EXPORT void obs_frontend_pop_ui_translation(void); + +/* ------------------------------------------------------------------------- */ + +#ifdef __cplusplus +} +#endif diff --git a/UI/obs-frontend-api/obs-frontend-internal.hpp b/UI/obs-frontend-api/obs-frontend-internal.hpp new file mode 100644 index 00000000000000..cb8da964764445 --- /dev/null +++ b/UI/obs-frontend-api/obs-frontend-internal.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "obs-frontend-api.h" + +#include +#include + +struct obs_frontend_callbacks { + virtual void *obs_frontend_get_main_window(void)=0; + virtual void *obs_frontend_get_main_window_handle(void)=0; + + virtual void obs_frontend_get_scenes( + struct obs_frontend_source_list *sources)=0; + virtual obs_source_t *obs_frontend_get_current_scene(void)=0; + virtual void obs_frontend_set_current_scene(obs_source_t *scene)=0; + + virtual void obs_frontend_get_transitions( + struct obs_frontend_source_list *sources)=0; + virtual obs_source_t *obs_frontend_get_current_transition(void)=0; + virtual void obs_frontend_set_current_transition( + obs_source_t *transition)=0; + + virtual void obs_frontend_get_scene_collections( + std::vector &strings)=0; + virtual char *obs_frontend_get_current_scene_collection(void)=0; + virtual void obs_frontend_set_current_scene_collection( + const char *collection)=0; + + virtual void obs_frontend_get_profiles( + std::vector &strings)=0; + virtual char *obs_frontend_get_current_profile(void)=0; + virtual void obs_frontend_set_current_profile(const char *profile)=0; + + virtual void obs_frontend_streaming_start(void)=0; + virtual void obs_frontend_streaming_stop(void)=0; + virtual bool obs_frontend_streaming_active(void)=0; + + virtual void obs_frontend_recording_start(void)=0; + virtual void obs_frontend_recording_stop(void)=0; + virtual bool obs_frontend_recording_active(void)=0; + + virtual void *obs_frontend_add_tools_menu_qaction(const char *name)=0; + virtual void obs_frontend_add_tools_menu_item(const char *name, + obs_frontend_cb callback, void *private_data)=0; + + virtual void obs_frontend_add_event_callback( + obs_frontend_event_cb callback, void *private_data)=0; + virtual void obs_frontend_remove_event_callback( + obs_frontend_event_cb callback, void *private_data)=0; + + virtual obs_output_t *obs_frontend_get_streaming_output(void)=0; + virtual obs_output_t *obs_frontend_get_recording_output(void)=0; + + virtual config_t *obs_frontend_get_profile_config(void)=0; + virtual config_t *obs_frontend_get_global_config(void)=0; + + virtual void obs_frontend_save(void)=0; + virtual void obs_frontend_add_save_callback( + obs_frontend_save_cb callback, void *private_data)=0; + virtual void obs_frontend_remove_save_callback( + obs_frontend_save_cb callback, void *private_data)=0; + + virtual void obs_frontend_push_ui_translation( + obs_frontend_translate_ui_cb translate)=0; + virtual void obs_frontend_pop_ui_translation(void)=0; + + virtual void on_load(obs_data_t *settings)=0; + virtual void on_save(obs_data_t *settings)=0; + virtual void on_event(enum obs_frontend_event event)=0; +}; + +EXPORT void obs_frontend_set_callbacks_internal( + obs_frontend_callbacks *callbacks); diff --git a/obs/obs.rc b/UI/obs.rc similarity index 100% rename from obs/obs.rc rename to UI/obs.rc diff --git a/obs/platform-osx.mm b/UI/platform-osx.mm similarity index 97% rename from obs/platform-osx.mm rename to UI/platform-osx.mm index ec02bbec4f07e2..d60f8ab921a8e3 100644 --- a/obs/platform-osx.mm +++ b/UI/platform-osx.mm @@ -132,12 +132,12 @@ string GetDefaultVideoSavePath() return result; } -bool IsAlwaysOnTop(QMainWindow *window) +bool IsAlwaysOnTop(QWidget *window) { return (window->windowFlags() & Qt::WindowStaysOnTopHint) != 0; } -void SetAlwaysOnTop(QMainWindow *window, bool enable) +void SetAlwaysOnTop(QWidget *window, bool enable) { Qt::WindowFlags flags = window->windowFlags(); diff --git a/obs/platform-windows.cpp b/UI/platform-windows.cpp similarity index 94% rename from obs/platform-windows.cpp rename to UI/platform-windows.cpp index fb4c9975972499..8edde6ec32a3d8 100644 --- a/obs/platform-windows.cpp +++ b/UI/platform-windows.cpp @@ -203,13 +203,13 @@ void SetAeroEnabled(bool enable) func(enable ? DWM_EC_ENABLECOMPOSITION : DWM_EC_DISABLECOMPOSITION); } -bool IsAlwaysOnTop(QMainWindow *window) +bool IsAlwaysOnTop(QWidget *window) { DWORD exStyle = GetWindowLong((HWND)window->winId(), GWL_EXSTYLE); return (exStyle & WS_EX_TOPMOST) != 0; } -void SetAlwaysOnTop(QMainWindow *window, bool enable) +void SetAlwaysOnTop(QWidget *window, bool enable) { HWND hwnd = (HWND)window->winId(); SetWindowPos(hwnd, enable ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, @@ -230,3 +230,11 @@ void SetProcessPriority(const char *priority) else if (strcmp(priority, "Idle") == 0) SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS); } + +void SetWin32DropStyle(QWidget *window) +{ + HWND hwnd = (HWND)window->winId(); + LONG_PTR ex_style = GetWindowLongPtr(hwnd, GWL_EXSTYLE); + ex_style |= WS_EX_ACCEPTFILES; + SetWindowLongPtr(hwnd, GWL_EXSTYLE, ex_style); +} diff --git a/obs/platform-x11.cpp b/UI/platform-x11.cpp similarity index 97% rename from obs/platform-x11.cpp rename to UI/platform-x11.cpp index 582d626d3fe51a..1cca5da517d92b 100644 --- a/obs/platform-x11.cpp +++ b/UI/platform-x11.cpp @@ -148,12 +148,12 @@ vector GetPreferredLocales() return {}; } -bool IsAlwaysOnTop(QMainWindow *window) +bool IsAlwaysOnTop(QWidget *window) { return (window->windowFlags() & Qt::WindowStaysOnTopHint) != 0; } -void SetAlwaysOnTop(QMainWindow *window, bool enable) +void SetAlwaysOnTop(QWidget *window, bool enable) { Qt::WindowFlags flags = window->windowFlags(); diff --git a/obs/platform.hpp b/UI/platform.hpp similarity index 92% rename from obs/platform.hpp rename to UI/platform.hpp index a25e4a8d8b097a..ad579d62540d31 100644 --- a/obs/platform.hpp +++ b/UI/platform.hpp @@ -22,7 +22,7 @@ #include #include -class QMainWindow; +class QWidget; struct MonitorInfo { int32_t x, y; @@ -45,13 +45,14 @@ std::string GetDefaultVideoSavePath(); std::vector GetPreferredLocales(); -bool IsAlwaysOnTop(QMainWindow *window); -void SetAlwaysOnTop(QMainWindow *window, bool enable); +bool IsAlwaysOnTop(QWidget *window); +void SetAlwaysOnTop(QWidget *window, bool enable); #ifdef _WIN32 uint32_t GetWindowsVersion(); void SetAeroEnabled(bool enable); void SetProcessPriority(const char *priority); +void SetWin32DropStyle(QWidget *window); #endif #ifdef __APPLE__ diff --git a/obs/properties-view.cpp b/UI/properties-view.cpp similarity index 97% rename from obs/properties-view.cpp rename to UI/properties-view.cpp index 14cd3ff4e0cf29..f8e8faf13b052e 100644 --- a/obs/properties-view.cpp +++ b/UI/properties-view.cpp @@ -210,9 +210,13 @@ void OBSPropertiesView::resizeEvent(QResizeEvent *event) QWidget *OBSPropertiesView::NewWidget(obs_property_t *prop, QWidget *widget, const char *signal) { + const char *long_desc = obs_property_long_description(prop); + WidgetInfo *info = new WidgetInfo(this, prop, widget); connect(widget, signal, info, SLOT(ControlChanged())); children.emplace_back(info); + + widget->setToolTip(QT_UTF8(long_desc)); return widget; } @@ -263,6 +267,8 @@ QWidget *OBSPropertiesView::AddText(obs_property_t *prop, QFormLayout *layout, label = new QLabel(QT_UTF8(obs_property_description(prop))); layout->addRow(label, subLayout); + edit->setToolTip(QT_UTF8(obs_property_long_description(prop))); + connect(edit, SIGNAL(textEdited(const QString &)), info, SLOT(ControlChanged())); return nullptr; @@ -271,6 +277,7 @@ QWidget *OBSPropertiesView::AddText(obs_property_t *prop, QFormLayout *layout, QLineEdit *edit = new QLineEdit(); edit->setText(QT_UTF8(val)); + edit->setToolTip(QT_UTF8(obs_property_long_description(prop))); return NewWidget(prop, edit, SIGNAL(textEdited(const QString &))); } @@ -286,6 +293,7 @@ void OBSPropertiesView::AddPath(obs_property_t *prop, QFormLayout *layout, edit->setText(QT_UTF8(val)); edit->setReadOnly(true); + edit->setToolTip(QT_UTF8(obs_property_long_description(prop))); subLayout->addWidget(edit); subLayout->addWidget(button); @@ -316,6 +324,7 @@ void OBSPropertiesView::AddInt(obs_property_t *prop, QFormLayout *layout, spin->setMaximum(maxVal); spin->setSingleStep(stepVal); spin->setValue(val); + spin->setToolTip(QT_UTF8(obs_property_long_description(prop))); WidgetInfo *info = new WidgetInfo(this, prop, spin); children.emplace_back(info); @@ -361,6 +370,7 @@ void OBSPropertiesView::AddFloat(obs_property_t *prop, QFormLayout *layout, spin->setMaximum(maxVal); spin->setSingleStep(stepVal); spin->setValue(val); + spin->setToolTip(QT_UTF8(obs_property_long_description(prop))); WidgetInfo *info = new WidgetInfo(this, prop, spin); children.emplace_back(info); @@ -471,6 +481,7 @@ QWidget *OBSPropertiesView::AddList(obs_property_t *prop, bool &warning) combo->setEditable(true); combo->setMaxVisibleItems(40); + combo->setToolTip(QT_UTF8(obs_property_long_description(prop))); string value = from_obs_data(settings, name, format); @@ -545,6 +556,7 @@ void OBSPropertiesView::AddEditableList(obs_property_t *prop, list->setSortingEnabled(false); list->setSelectionMode(QAbstractItemView::ExtendedSelection); + list->setToolTip(QT_UTF8(obs_property_long_description(prop))); for (size_t i = 0; i < count; i++) { obs_data_t *item = obs_data_array_item(array, i); @@ -598,12 +610,14 @@ void OBSPropertiesView::AddColor(obs_property_t *prop, QFormLayout *layout, QColor color = color_from_int(val); button->setText(QTStr("Basic.PropertiesWindow.SelectColor")); + button->setToolTip(QT_UTF8(obs_property_long_description(prop))); colorLabel->setFrameStyle(QFrame::Sunken | QFrame::Panel); colorLabel->setText(color.name(QColor::HexArgb)); colorLabel->setPalette(QPalette(color)); colorLabel->setAutoFillBackground(true); colorLabel->setAlignment(Qt::AlignCenter); + colorLabel->setToolTip(QT_UTF8(obs_property_long_description(prop))); QHBoxLayout *subLayout = new QHBoxLayout; subLayout->setContentsMargins(0, 0, 0, 0); @@ -619,7 +633,7 @@ void OBSPropertiesView::AddColor(obs_property_t *prop, QFormLayout *layout, layout->addRow(label, subLayout); } -static void MakeQFont(obs_data_t *font_obj, QFont &font) +static void MakeQFont(obs_data_t *font_obj, QFont &font, bool limit = false) { const char *face = obs_data_get_string(font_obj, "face"); const char *style = obs_data_get_string(font_obj, "style"); @@ -631,8 +645,14 @@ static void MakeQFont(obs_data_t *font_obj, QFont &font) font.setStyleName(style); } - if (size) + if (size) { + if (limit) { + int max_size = font.pointSize(); + if (max_size < 28) max_size = 28; + if (size > max_size) size = max_size; + } font.setPointSize(size); + } if (flags & OBS_FONT_BOLD) font.setBold(true); if (flags & OBS_FONT_ITALIC) font.setItalic(true); @@ -652,14 +672,16 @@ void OBSPropertiesView::AddFont(obs_property_t *prop, QFormLayout *layout, QFont font; font = fontLabel->font(); - MakeQFont(font_obj, font); + MakeQFont(font_obj, font, true); button->setText(QTStr("Basic.PropertiesWindow.SelectFont")); + button->setToolTip(QT_UTF8(obs_property_long_description(prop))); fontLabel->setFrameStyle(QFrame::Sunken | QFrame::Panel); fontLabel->setFont(font); fontLabel->setText(QString("%1 %2").arg(face, style)); fontLabel->setAlignment(Qt::AlignCenter); + fontLabel->setToolTip(QT_UTF8(obs_property_long_description(prop))); QHBoxLayout *subLayout = new QHBoxLayout; subLayout->setContentsMargins(0, 0, 0, 0); @@ -988,6 +1010,8 @@ static OBSFrameRatePropertyWidget *CreateFrameRateWidget(obs_property_t *prop, combo->addItem(QTStr("Basic.PropertiesView.FPS.Rational"), QVariant::fromValue(frame_rate_tag::rational())); + combo->setToolTip(QT_UTF8(obs_property_long_description(prop))); + auto num = obs_property_frame_rate_options_count(prop); if (num) combo->insertSeparator(combo->count()); @@ -1188,6 +1212,8 @@ void OBSPropertiesView::AddFrameRate(obs_property_t *prop, bool &warning, fps_ranges); auto info = new WidgetInfo(this, prop, widget); + widget->setToolTip(QT_UTF8(obs_property_long_description(prop))); + widget->name = name; widget->settings = settings; @@ -1209,6 +1235,9 @@ void OBSPropertiesView::AddFrameRate(obs_property_t *prop, bool &warning, auto stack = widget->modeDisplay; auto combo = widget->modeSelect; + stack->setToolTip(QT_UTF8(obs_property_long_description(prop))); + combo->setToolTip(QT_UTF8(obs_property_long_description(prop))); + auto comboIndexChanged = static_cast( &QComboBox::currentIndexChanged); connect(combo, comboIndexChanged, stack, @@ -1609,7 +1638,9 @@ bool WidgetInfo::FontChanged(const char *setting) obs_data_set_int(font_obj, "flags", flags); QLabel *label = static_cast(widget); - label->setFont(font); + QFont labelFont; + MakeQFont(font_obj, labelFont, true); + label->setFont(labelFont); label->setText(QString("%1 %2").arg(font.family(), font.styleName())); obs_data_set_obj(view->settings, setting, font_obj); diff --git a/obs/properties-view.hpp b/UI/properties-view.hpp similarity index 100% rename from obs/properties-view.hpp rename to UI/properties-view.hpp diff --git a/obs/properties-view.moc.hpp b/UI/properties-view.moc.hpp similarity index 100% rename from obs/properties-view.moc.hpp rename to UI/properties-view.moc.hpp diff --git a/obs/qt-display.cpp b/UI/qt-display.cpp similarity index 100% rename from obs/qt-display.cpp rename to UI/qt-display.cpp diff --git a/obs/qt-display.hpp b/UI/qt-display.hpp similarity index 100% rename from obs/qt-display.hpp rename to UI/qt-display.hpp diff --git a/obs/qt-wrappers.cpp b/UI/qt-wrappers.cpp similarity index 100% rename from obs/qt-wrappers.cpp rename to UI/qt-wrappers.cpp diff --git a/obs/qt-wrappers.hpp b/UI/qt-wrappers.hpp similarity index 100% rename from obs/qt-wrappers.hpp rename to UI/qt-wrappers.hpp diff --git a/obs/remote-text.cpp b/UI/remote-text.cpp similarity index 100% rename from obs/remote-text.cpp rename to UI/remote-text.cpp diff --git a/obs/remote-text.hpp b/UI/remote-text.hpp similarity index 100% rename from obs/remote-text.hpp rename to UI/remote-text.hpp diff --git a/obs/slider-absoluteset-style.cpp b/UI/slider-absoluteset-style.cpp similarity index 100% rename from obs/slider-absoluteset-style.cpp rename to UI/slider-absoluteset-style.cpp diff --git a/obs/slider-absoluteset-style.hpp b/UI/slider-absoluteset-style.hpp similarity index 100% rename from obs/slider-absoluteset-style.hpp rename to UI/slider-absoluteset-style.hpp diff --git a/obs/source-label.cpp b/UI/source-label.cpp similarity index 100% rename from obs/source-label.cpp rename to UI/source-label.cpp diff --git a/obs/source-label.hpp b/UI/source-label.hpp similarity index 100% rename from obs/source-label.hpp rename to UI/source-label.hpp diff --git a/obs/source-list-widget.cpp b/UI/source-list-widget.cpp similarity index 100% rename from obs/source-list-widget.cpp rename to UI/source-list-widget.cpp diff --git a/obs/source-list-widget.hpp b/UI/source-list-widget.hpp similarity index 100% rename from obs/source-list-widget.hpp rename to UI/source-list-widget.hpp diff --git a/obs/sparkle-updater.mm b/UI/sparkle-updater.mm similarity index 100% rename from obs/sparkle-updater.mm rename to UI/sparkle-updater.mm diff --git a/obs/vertical-scroll-area.cpp b/UI/vertical-scroll-area.cpp similarity index 100% rename from obs/vertical-scroll-area.cpp rename to UI/vertical-scroll-area.cpp diff --git a/obs/vertical-scroll-area.hpp b/UI/vertical-scroll-area.hpp similarity index 100% rename from obs/vertical-scroll-area.hpp rename to UI/vertical-scroll-area.hpp diff --git a/obs/visibility-checkbox.cpp b/UI/visibility-checkbox.cpp similarity index 100% rename from obs/visibility-checkbox.cpp rename to UI/visibility-checkbox.cpp diff --git a/obs/visibility-checkbox.hpp b/UI/visibility-checkbox.hpp similarity index 100% rename from obs/visibility-checkbox.hpp rename to UI/visibility-checkbox.hpp diff --git a/obs/visibility-item-widget.cpp b/UI/visibility-item-widget.cpp similarity index 100% rename from obs/visibility-item-widget.cpp rename to UI/visibility-item-widget.cpp diff --git a/obs/visibility-item-widget.hpp b/UI/visibility-item-widget.hpp similarity index 100% rename from obs/visibility-item-widget.hpp rename to UI/visibility-item-widget.hpp diff --git a/obs/volume-control.cpp b/UI/volume-control.cpp similarity index 100% rename from obs/volume-control.cpp rename to UI/volume-control.cpp diff --git a/obs/volume-control.hpp b/UI/volume-control.hpp similarity index 100% rename from obs/volume-control.hpp rename to UI/volume-control.hpp diff --git a/obs/window-basic-adv-audio.cpp b/UI/window-basic-adv-audio.cpp similarity index 100% rename from obs/window-basic-adv-audio.cpp rename to UI/window-basic-adv-audio.cpp diff --git a/obs/window-basic-adv-audio.hpp b/UI/window-basic-adv-audio.hpp similarity index 100% rename from obs/window-basic-adv-audio.hpp rename to UI/window-basic-adv-audio.hpp diff --git a/obs/window-basic-filters.cpp b/UI/window-basic-filters.cpp similarity index 99% rename from obs/window-basic-filters.cpp rename to UI/window-basic-filters.cpp index f055d6168ba3bd..f3bb847f650419 100644 --- a/obs/window-basic-filters.cpp +++ b/UI/window-basic-filters.cpp @@ -146,8 +146,10 @@ inline OBSSource OBSBasicFilters::GetFilter(int row, bool async) void OBSBasicFilters::UpdatePropertiesView(int row, bool async) { - delete view; - view = nullptr; + if (view) { + view->deleteLater(); + view = nullptr; + } OBSSource filter = GetFilter(row, async); if (!filter) diff --git a/obs/window-basic-filters.hpp b/UI/window-basic-filters.hpp similarity index 100% rename from obs/window-basic-filters.hpp rename to UI/window-basic-filters.hpp diff --git a/obs/window-basic-interaction.cpp b/UI/window-basic-interaction.cpp similarity index 100% rename from obs/window-basic-interaction.cpp rename to UI/window-basic-interaction.cpp diff --git a/obs/window-basic-interaction.hpp b/UI/window-basic-interaction.hpp similarity index 100% rename from obs/window-basic-interaction.hpp rename to UI/window-basic-interaction.hpp diff --git a/UI/window-basic-main-dropfiles.cpp b/UI/window-basic-main-dropfiles.cpp new file mode 100644 index 00000000000000..64ae7f684fb116 --- /dev/null +++ b/UI/window-basic-main-dropfiles.cpp @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "window-basic-main.hpp" +#include "qt-wrappers.hpp" + +using namespace std; + +static const char *imageExtensions[] = { + "bmp", "tga", "png", "jpg", "jpeg", "gif", nullptr +}; + +static const char *mediaExtensions[] = { + "3ga", "669", "a52", "aac", "ac3", "adt", "adts", "aif", "aifc", + "aiff", "amb", "amr", "aob", "ape", "au", "awb", "caf", "dts", + "flac", "it", "kar", "m4a", "m4b", "m4p", "m5p", "mid", "mka", + "mlp", "mod", "mpa", "mp1", "mp2", "mp3", "mpc", "mpga", "mus", + "oga", "ogg", "oma", "opus", "qcp", "ra", "rmi", "s3m", "sid", + "spx", "tak", "thd", "tta", "voc", "vqf", "w64", "wav", "wma", + "wv", "xa", "xm" "3g2", "3gp", "3gp2", "3gpp", "amv", "asf", "avi", + "bik", "crf", "divx", "drc", "dv", "evo", "f4v", "flv", "gvi", + "gxf", "iso", "m1v", "m2v", "m2t", "m2ts", "m4v", "mkv", "mov", + "mp2", "mp2v", "mp4", "mp4v", "mpe", "mpeg", "mpeg1", "mpeg2", + "mpeg4", "mpg", "mpv2", "mts", "mtv", "mxf", "mxg", "nsv", "nuv", + "ogg", "ogm", "ogv", "ogx", "ps", "rec", "rm", "rmvb", "rpl", "thp", + "tod", "ts", "tts", "txd", "vob", "vro", "webm", "wm", "wmv", "wtv", + nullptr +}; + +static string GenerateSourceName(const char *base) +{ + string name; + int inc = 0; + + for (;; inc++) { + name = base; + + if (inc) { + name += " ("; + name += to_string(inc+1); + name += ")"; + } + + obs_source_t *source = obs_get_source_by_name(name.c_str()); + if (!source) + return name; + } +} + +void OBSBasic::AddDropSource(const char *file, bool image) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + obs_data_t *settings = obs_data_create(); + obs_source_t *source = nullptr; + const char *type = nullptr; + + if (image) { + obs_data_set_string(settings, "file", file); + type = "image_source"; + } else { + obs_data_set_string(settings, "local_file", file); + type = "ffmpeg_source"; + } + + const char *name = obs_source_get_display_name(type); + source = obs_source_create(type, GenerateSourceName(name).c_str(), + settings, nullptr); + if (source) { + OBSScene scene = main->GetCurrentScene(); + obs_scene_add(scene, source); + obs_source_release(source); + } + + obs_data_release(settings); +} + +void OBSBasic::dragEnterEvent(QDragEnterEvent *event) +{ + event->acceptProposedAction(); +} + +void OBSBasic::dragLeaveEvent(QDragLeaveEvent *event) +{ + event->accept(); +} + +void OBSBasic::dragMoveEvent(QDragMoveEvent *event) +{ + event->acceptProposedAction(); +} + +void OBSBasic::dropEvent(QDropEvent *event) +{ + const QMimeData* mimeData = event->mimeData(); + + if (mimeData->hasUrls()) { + QList urls = mimeData->urls(); + + for (int i = 0; i < urls.size() && i < 5; i++) { + QString file = urls.at(i).toLocalFile(); + QFileInfo fileInfo(file); + + if (!fileInfo.exists()) + continue; + + QString suffixQStr = fileInfo.suffix(); + QByteArray suffixArray = suffixQStr.toUtf8(); + const char *suffix = suffixArray.constData(); + bool found = false; + + const char **cmp = imageExtensions; + while (*cmp) { + if (strcmp(*cmp, suffix) == 0) { + AddDropSource(QT_TO_UTF8(file), true); + found = true; + break; + } + + cmp++; + } + + if (found) + continue; + + cmp = mediaExtensions; + while (*cmp) { + if (strcmp(*cmp, suffix) == 0) { + AddDropSource(QT_TO_UTF8(file), false); + break; + } + + cmp++; + } + } + } +} + diff --git a/obs/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp similarity index 91% rename from obs/window-basic-main-outputs.cpp rename to UI/window-basic-main-outputs.cpp index 796a46c1373fc4..e9fe61fa2353f9 100644 --- a/obs/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -191,9 +191,11 @@ struct SimpleOutput : BasicOutputHandler { int CalcCRF(int crf); + void UpdateStreamingSettings_amd(obs_data_t *settings, int bitrate); void UpdateRecordingSettings_x264_crf(int crf); void UpdateRecordingSettings_qsv11(int crf); void UpdateRecordingSettings_nvenc(int cqp); + void UpdateRecordingSettings_amd_cqp(int cqp); void UpdateRecordingSettings(); void UpdateRecordingAudioSettings(); virtual void Update() override; @@ -209,9 +211,8 @@ struct SimpleOutput : BasicOutputHandler { virtual bool StartStreaming(obs_service_t *service) override; virtual bool StartRecording() override; - virtual void StopStreaming() override; - virtual void ForceStopStreaming() override; - virtual void StopRecording() override; + virtual void StopStreaming(bool force) override; + virtual void StopRecording(bool force) override; virtual bool StreamingActive() const override; virtual bool RecordingActive() const override; }; @@ -285,6 +286,8 @@ void SimpleOutput::LoadRecordingPreset() lowCPUx264 = true; } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { LoadRecordingPreset_h264("obs_qsv11"); + } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { + LoadRecordingPreset_h264("amd_amf_h264"); } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { LoadRecordingPreset_h264("ffmpeg_nvenc"); } @@ -321,6 +324,8 @@ SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) "StreamEncoder"); if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) LoadStreamingPreset_h264("obs_qsv11"); + else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) + LoadStreamingPreset_h264("amd_amf_h264"); else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) LoadStreamingPreset_h264("ffmpeg_nvenc"); else @@ -389,12 +394,19 @@ void SimpleOutput::Update() const char *presetType; const char *preset; - if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) + if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { presetType = "QSVPreset"; - else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) + + } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { + presetType = "AMDPreset"; + UpdateStreamingSettings_amd(h264Settings, videoBitrate); + + } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { presetType = "NVENCPreset"; - else + + } else { presetType = "Preset"; + } preset = config_get_string(main->Config(), "SimpleOutput", presetType); @@ -536,6 +548,48 @@ void SimpleOutput::UpdateRecordingSettings_nvenc(int cqp) obs_data_release(settings); } +void SimpleOutput::UpdateStreamingSettings_amd(obs_data_t *settings, + int bitrate) +{ + int bits = bitrate * 1000; + obs_data_set_int(settings, "AMF.H264.Usage", 0); + obs_data_set_int(settings, "AMF.H264.QualityPreset", 2); + obs_data_set_int(settings, "AMF.H264.ProfileLevel", 51); + obs_data_set_int(settings, "AMF.H264.FillerData", 1); + obs_data_set_int(settings, "AMF.H264.FrameSkipping", -1); + obs_data_set_int(settings, "AMF.H264.BPicture.Pattern", 2); + obs_data_set_int(settings, "AMF.H264.BPicture.Reference", 1); + obs_data_set_int(settings, "AMF.H264.Bitrate.Target", bits); + obs_data_set_int(settings, "AMF.H264.Bitrate.Peak", bits); + obs_data_set_int(settings, "AMF.H264Advanced.VBVBuffer.Size", bits); + obs_data_set_string(settings, "profile", "high"); +} + +void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) +{ + obs_data_t *settings = obs_data_create(); + + obs_data_set_int(settings, "AMF.H264.Usage", 0); + obs_data_set_int(settings, "AMF.H264.QualityPreset", 2); + obs_data_set_int(settings, "AMF.H264.ProfileLevel", 51); + obs_data_set_int(settings, "AMF.H264.FillerData", 0); + obs_data_set_int(settings, "AMF.H264.FrameSkipping", 0); + obs_data_set_int(settings, "AMF.H264.QP.Minimum", 0); + obs_data_set_int(settings, "AMF.H264.QP.Maximum", 51); + obs_data_set_int(settings, "AMF.H264.QP.IFrame", cqp); + obs_data_set_int(settings, "AMF.H264.QP.PFrame", cqp); + obs_data_set_int(settings, "AMF.H264.QP.BFrame", cqp); + obs_data_set_int(settings, "AMF.H264.BPicture.Pattern", 3); + obs_data_set_int(settings, "AMF.H264.BPicture.Reference", 1); + obs_data_set_int(settings, "keyint_sec", 1); + obs_data_set_string(settings, "rate_control", "CQP"); + obs_data_set_string(settings, "profile", "high"); + + obs_encoder_update(h264Recording, settings); + + obs_data_release(settings); +} + void SimpleOutput::UpdateRecordingSettings() { bool ultra_hq = (videoQuality == "HQ"); @@ -547,6 +601,9 @@ void SimpleOutput::UpdateRecordingSettings() } else if (videoEncoder == SIMPLE_ENCODER_QSV) { UpdateRecordingSettings_qsv11(crf); + } else if (videoEncoder == SIMPLE_ENCODER_AMD) { + UpdateRecordingSettings_amd_cqp(crf); + } else if (videoEncoder == SIMPLE_ENCODER_NVENC) { UpdateRecordingSettings_nvenc(crf); } @@ -703,9 +760,13 @@ bool SimpleOutput::StartRecording() os_dir_t *dir = path ? os_opendir(path) : nullptr; if (!dir) { - QMessageBox::information(main, - QTStr("Output.BadPath.Title"), - QTStr("Output.BadPath.Text")); + if (main->isVisible()) + QMessageBox::information(main, + QTStr("Output.BadPath.Title"), + QTStr("Output.BadPath.Text")); + else + main->SysTrayNotify(QTStr("Output.BadPath.Text"), + QSystemTrayIcon::Warning); return false; } @@ -751,19 +812,20 @@ bool SimpleOutput::StartRecording() return false; } -void SimpleOutput::StopStreaming() -{ - obs_output_stop(streamOutput); -} - -void SimpleOutput::ForceStopStreaming() +void SimpleOutput::StopStreaming(bool force) { - obs_output_force_stop(streamOutput); + if (force) + obs_output_force_stop(streamOutput); + else + obs_output_stop(streamOutput); } -void SimpleOutput::StopRecording() +void SimpleOutput::StopRecording(bool force) { - obs_output_stop(fileOutput); + if (force) + obs_output_force_stop(fileOutput); + else + obs_output_stop(fileOutput); } bool SimpleOutput::StreamingActive() const @@ -804,9 +866,8 @@ struct AdvancedOutput : BasicOutputHandler { virtual bool StartStreaming(obs_service_t *service) override; virtual bool StartRecording() override; - virtual void StopStreaming() override; - virtual void ForceStopStreaming() override; - virtual void StopRecording() override; + virtual void StopStreaming(bool force) override; + virtual void StopRecording(bool force) override; virtual bool StreamingActive() const override; virtual bool RecordingActive() const override; }; @@ -1334,9 +1395,13 @@ bool AdvancedOutput::StartRecording() os_dir_t *dir = path ? os_opendir(path) : nullptr; if (!dir) { - QMessageBox::information(main, - QTStr("Output.BadPath.Title"), - QTStr("Output.BadPath.Text")); + if (main->isVisible()) + QMessageBox::information(main, + QTStr("Output.BadPath.Title"), + QTStr("Output.BadPath.Text")); + else + main->SysTrayNotify(QTStr("Output.BadPath.Text"), + QSystemTrayIcon::Warning); return false; } @@ -1372,19 +1437,20 @@ bool AdvancedOutput::StartRecording() return false; } -void AdvancedOutput::StopStreaming() +void AdvancedOutput::StopStreaming(bool force) { - obs_output_stop(streamOutput); -} - -void AdvancedOutput::ForceStopStreaming() -{ - obs_output_force_stop(streamOutput); + if (force) + obs_output_force_stop(streamOutput); + else + obs_output_stop(streamOutput); } -void AdvancedOutput::StopRecording() +void AdvancedOutput::StopRecording(bool force) { - obs_output_stop(fileOutput); + if (force) + obs_output_force_stop(fileOutput); + else + obs_output_stop(fileOutput); } bool AdvancedOutput::StreamingActive() const diff --git a/obs/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp similarity index 91% rename from obs/window-basic-main-outputs.hpp rename to UI/window-basic-main-outputs.hpp index 186d3b4da0bece..74a73a60e4a9aa 100644 --- a/obs/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -24,9 +24,8 @@ struct BasicOutputHandler { virtual bool StartStreaming(obs_service_t *service) = 0; virtual bool StartRecording() = 0; - virtual void StopStreaming() = 0; - virtual void ForceStopStreaming() = 0; - virtual void StopRecording() = 0; + virtual void StopStreaming(bool force = false) = 0; + virtual void StopRecording(bool force = false) = 0; virtual bool StreamingActive() const = 0; virtual bool RecordingActive() const = 0; diff --git a/obs/window-basic-main-profiles.cpp b/UI/window-basic-main-profiles.cpp similarity index 95% rename from obs/window-basic-main-profiles.cpp rename to UI/window-basic-main-profiles.cpp index d34b2b97d9a32b..9637fca34b6391 100644 --- a/obs/window-basic-main-profiles.cpp +++ b/UI/window-basic-main-profiles.cpp @@ -24,7 +24,7 @@ #include "window-namedialog.hpp" #include "qt-wrappers.hpp" -template static void EnumProfiles(Func &&cb) +void EnumProfiles(std::function &&cb) { char path[512]; os_glob_t *glob; @@ -239,6 +239,11 @@ bool OBSBasic::AddProfile(bool create_new, const char *title, const char *text, config_save_safe(App()->GlobalConfig(), "tmp", nullptr); UpdateTitleBar(); + + if (api) { + api->on_event(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED); + api->on_event(OBS_FRONTEND_EVENT_PROFILE_CHANGED); + } return true; } @@ -363,6 +368,11 @@ void OBSBasic::on_actionRenameProfile_triggered() DeleteProfile(curName.c_str(), curDir.c_str()); RefreshProfiles(); } + + if (api) { + api->on_event(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED); + api->on_event(OBS_FRONTEND_EVENT_PROFILE_CHANGED); + } } void OBSBasic::on_actionRemoveProfile_triggered() @@ -431,6 +441,11 @@ void OBSBasic::on_actionRemoveProfile_triggered() blog(LOG_INFO, "------------------------------------------------"); UpdateTitleBar(); + + if (api) { + api->on_event(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED); + api->on_event(OBS_FRONTEND_EVENT_PROFILE_CHANGED); + } } void OBSBasic::ChangeProfile() @@ -481,4 +496,7 @@ void OBSBasic::ChangeProfile() blog(LOG_INFO, "Switched to profile '%s' (%s)", newName, newDir); blog(LOG_INFO, "------------------------------------------------"); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_PROFILE_CHANGED); } diff --git a/obs/window-basic-main-scene-collections.cpp b/UI/window-basic-main-scene-collections.cpp similarity index 93% rename from obs/window-basic-main-scene-collections.cpp rename to UI/window-basic-main-scene-collections.cpp index b3ec51553f8049..c35b0b1016fe45 100644 --- a/obs/window-basic-main-scene-collections.cpp +++ b/UI/window-basic-main-scene-collections.cpp @@ -24,7 +24,9 @@ #include "window-namedialog.hpp" #include "qt-wrappers.hpp" -template static void EnumSceneCollections(Func &&cb) +using namespace std; + +void EnumSceneCollections(std::function &&cb) { char path[512]; os_glob_t *glob; @@ -176,6 +178,11 @@ void OBSBasic::AddSceneCollection(bool create_new) blog(LOG_INFO, "------------------------------------------------"); UpdateTitleBar(); + + if (api) { + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); + } } void OBSBasic::RefreshSceneCollections() @@ -265,6 +272,11 @@ void OBSBasic::on_actionRenameSceneCollection_triggered() UpdateTitleBar(); RefreshSceneCollections(); + + if (api) { + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); + } } void OBSBasic::on_actionRemoveSceneCollection_triggered() @@ -328,6 +340,11 @@ void OBSBasic::on_actionRemoveSceneCollection_triggered() blog(LOG_INFO, "------------------------------------------------"); UpdateTitleBar(); + + if (api) { + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); + } } void OBSBasic::ChangeSceneCollection() @@ -364,4 +381,7 @@ void OBSBasic::ChangeSceneCollection() blog(LOG_INFO, "------------------------------------------------"); UpdateTitleBar(); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); } diff --git a/obs/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp similarity index 98% rename from obs/window-basic-main-transitions.cpp rename to UI/window-basic-main-transitions.cpp index 1467866a604526..3a69b24ce86c07 100644 --- a/obs/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -220,7 +220,7 @@ obs_source_t *OBSBasic::FindTransition(const char *name) return nullptr; } -void OBSBasic::TransitionToScene(obs_scene_t *scene, bool force) +void OBSBasic::TransitionToScene(OBSScene scene, bool force) { obs_source_t *source = obs_scene_get_source(scene); TransitionToScene(source, force); @@ -234,10 +234,13 @@ void OBSBasic::TransitionStopped() SetCurrentScene(scene); } + if (api) + api->on_event(OBS_FRONTEND_EVENT_TRANSITION_STOPPED); + swapScene = nullptr; } -void OBSBasic::TransitionToScene(obs_source_t *source, bool force) +void OBSBasic::TransitionToScene(OBSSource source, bool force) { obs_scene_t *scene = obs_scene_from_source(source); bool usingPreviewProgram = IsPreviewProgramMode(); @@ -281,6 +284,9 @@ void OBSBasic::TransitionToScene(obs_source_t *source, bool force) obs_scene_release(scene); obs_source_release(transition); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_CHANGED); } static inline void SetComboTransition(QComboBox *combo, obs_source_t *tr) @@ -293,7 +299,7 @@ static inline void SetComboTransition(QComboBox *combo, obs_source_t *tr) } } -void OBSBasic::SetTransition(obs_source_t *transition) +void OBSBasic::SetTransition(OBSSource transition) { obs_source_t *oldTransition = obs_get_output_source(0); @@ -317,6 +323,9 @@ void OBSBasic::SetTransition(obs_source_t *transition) bool configurable = obs_source_configurable(transition); ui->transitionRemove->setEnabled(configurable); ui->transitionProps->setEnabled(configurable); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_TRANSITION_CHANGED); } OBSSource OBSBasic::GetCurrentTransition() @@ -378,6 +387,9 @@ void OBSBasic::AddTransition() ui->transitions->setCurrentIndex(ui->transitions->count() - 1); CreatePropertiesWindow(source); obs_source_release(source); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED); } } @@ -428,6 +440,9 @@ void OBSBasic::on_transitionRemove_clicked() } ui->transitions->removeItem(idx); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED); } void OBSBasic::RenameTransition() @@ -530,7 +545,7 @@ static T GetOBSRef(QListWidgetItem *item) return item->data(static_cast(QtDataRole::OBSRef)).value(); } -void OBSBasic::SetCurrentScene(obs_source_t *scene, bool force) +void OBSBasic::SetCurrentScene(OBSSource scene, bool force) { if (!IsPreviewProgramMode()) { TransitionToScene(scene, force); diff --git a/obs/window-basic-main.cpp b/UI/window-basic-main.cpp similarity index 93% rename from obs/window-basic-main.cpp rename to UI/window-basic-main.cpp index ccc92ad26d006e..81dc93d5173661 100644 --- a/obs/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -120,6 +120,8 @@ OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow (parent), ui (new Ui::OBSBasic) { + setAcceptDrops(true); + ui->setupUi(this); ui->previewDisabledLabel->setVisible(false); @@ -127,25 +129,20 @@ OBSBasic::OBSBasic(QWidget *parent) ui->sources->setItemDelegate(new VisibilityItemDelegate(ui->sources)); - int width = config_get_int(App()->GlobalConfig(), "BasicWindow", "cx"); - - // Check if no values are saved (new installation). - if (width != 0) { - int height = config_get_int(App()->GlobalConfig(), - "BasicWindow", "cy"); - int posx = config_get_int(App()->GlobalConfig(), "BasicWindow", - "posx"); - int posy = config_get_int(App()->GlobalConfig(), "BasicWindow", - "posy"); + const char *geometry = config_get_string(App()->GlobalConfig(), + "BasicWindow", "geometry"); + if (geometry != NULL) { + QByteArray byteArray = QByteArray::fromBase64( + QByteArray(geometry)); + restoreGeometry(byteArray); - if (!WindowPositionValid(posx, posy)) { + QRect windowGeometry = normalGeometry(); + if (!WindowPositionValid(windowGeometry)) { QRect rect = App()->desktop()->availableGeometry(); setGeometry(QStyle::alignedRect( Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } else { - setGeometry(posx, posy, width, height); } } @@ -187,7 +184,7 @@ OBSBasic::OBSBasic(QWidget *parent) installEventFilter(CreateShortcutFilter()); stringstream name; - name << "OBS " << App()->GetVersionString(); + name << "OBS " << App()->GetVersionString(); blog(LOG_INFO, "%s", name.str().c_str()); blog(LOG_INFO, "---------------------------------"); @@ -376,6 +373,13 @@ void OBSBasic::Save(const char *file) obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); + if (api) { + obs_data_t *moduleObj = obs_data_create(); + api->on_save(moduleObj); + obs_data_set_obj(saveData, "modules", moduleObj); + obs_data_release(moduleObj); + } + if (!obs_data_save_json_safe(saveData, file, "tmp", "bak")) blog(LOG_ERROR, "Could not save scene data to %s", file); @@ -658,6 +662,12 @@ void OBSBasic::Load(const char *file) ui->preview->SetLocked(previewLocked); ui->actionLockPreview->setChecked(previewLocked); + if (api) { + obs_data_t *modulesObj = obs_data_get_obj(data, "modules"); + api->on_load(modulesObj); + obs_data_release(modulesObj); + } + obs_data_release(data); if (!opt_starting_scene.empty()) @@ -1022,6 +1032,8 @@ void OBSBasic::ResetOutputs() #define SHUTDOWN_SEPARATOR \ "==== Shutting down ==================================================" +extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); + void OBSBasic::OBSInit() { ProfileScope("OBSBasic::OBSInit"); @@ -1068,6 +1080,8 @@ void OBSBasic::OBSInit() InitOBSCallbacks(); InitHotkeys(); + api = InitializeAPIInterface(this); + AddExtraModulePaths(); blog(LOG_INFO, "---------------------------------"); obs_load_all_modules(); @@ -1154,6 +1168,7 @@ void OBSBasic::OBSInit() connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); #ifdef _WIN32 + SetWin32DropStyle(this); show(); #endif @@ -1186,6 +1201,8 @@ void OBSBasic::OBSInit() } ui->mainSplitter->setSizes(defSizes); + + SystemTray(true); } void OBSBasic::InitHotkeys() @@ -1452,14 +1469,6 @@ OBSBasic::~OBSBasic() QList splitterSizes = ui->mainSplitter->sizes(); bool alwaysOnTop = IsAlwaysOnTop(this); - config_set_int(App()->GlobalConfig(), "BasicWindow", "cx", - lastGeom.width()); - config_set_int(App()->GlobalConfig(), "BasicWindow", "cy", - lastGeom.height()); - config_set_int(App()->GlobalConfig(), "BasicWindow", "posx", - lastGeom.x()); - config_set_int(App()->GlobalConfig(), "BasicWindow", "posy", - lastGeom.y()); config_set_int(App()->GlobalConfig(), "BasicWindow", "splitterTop", splitterSizes[0]); config_set_int(App()->GlobalConfig(), "BasicWindow", "splitterBottom", @@ -2073,6 +2082,9 @@ void OBSBasic::DuplicateSelectedScene() AddScene(source); SetCurrentScene(source, true); obs_scene_release(scene); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); break; } } @@ -2082,8 +2094,12 @@ void OBSBasic::RemoveSelectedScene() OBSScene scene = GetCurrentScene(); if (scene) { obs_source_t *source = obs_scene_get_source(scene); - if (QueryRemoveSource(source)) + if (QueryRemoveSource(source)) { obs_source_remove(source); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); + } } } @@ -2618,9 +2634,14 @@ void OBSBasic::ClearSceneData() void OBSBasic::closeEvent(QCloseEvent *event) { - blog(LOG_INFO, SHUTDOWN_SEPARATOR); + if (isVisible()) + config_set_string(App()->GlobalConfig(), + "BasicWindow", "geometry", + saveGeometry().toBase64().constData()); if (outputHandler && outputHandler->Active()) { + SetShowing(true); + QMessageBox::StandardButton button = QMessageBox::question( this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); @@ -2635,6 +2656,8 @@ void OBSBasic::closeEvent(QCloseEvent *event) if (!event->isAccepted()) return; + blog(LOG_INFO, SHUTDOWN_SEPARATOR); + if (updateCheckThread) updateCheckThread->wait(); if (logUploadThread) @@ -2643,6 +2666,10 @@ void OBSBasic::closeEvent(QCloseEvent *event) signalHandlers.clear(); SaveProjectNow(); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_EXIT); + disableSaving++; /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, @@ -2677,8 +2704,11 @@ void OBSBasic::on_actionRemux_triggered() void OBSBasic::on_action_Settings_triggered() { + disableHiding = true; OBSBasicSettings settings(this); settings.exec(); + SystemTray(false); + disableHiding = false; } void OBSBasic::on_actionAdvAudioProperties_triggered() @@ -3160,22 +3190,42 @@ QMenu *OBSBasic::CreateAddSourcePopupMenu() QMenu *popup = new QMenu(QTStr("Add"), this); - auto addSource = [this, popup] (const char *type, const char *name) { - QAction *popupItem = new QAction(QT_UTF8(name), this); + auto getActionAfter = [] (QMenu *menu, const QString &name) + { + QList actions = menu->actions(); + + for (QAction *menuAction : actions) { + if (menuAction->text().compare(name) >= 0) + return menuAction; + } + + return (QAction*)nullptr; + }; + + auto addSource = [this, getActionAfter] (QMenu *popup, + const char *type, const char *name) + { + QString qname = QT_UTF8(name); + QAction *popupItem = new QAction(qname, this); popupItem->setData(QT_UTF8(type)); connect(popupItem, SIGNAL(triggered(bool)), this, SLOT(AddSourceFromAction())); - popup->addAction(popupItem); + + QAction *after = getActionAfter(popup, qname); + popup->insertAction(after, popupItem); }; while (obs_enum_input_types(idx++, &type)) { const char *name = obs_source_get_display_name(type); + uint32_t caps = obs_get_source_output_flags(type); - addSource(type, name); - foundValues = true; + if ((caps & OBS_SOURCE_DEPRECATED) == 0) { + addSource(popup, type, name); + foundValues = true; + } } - addSource("scene", Str("Basic.Scene")); + addSource(popup, "scene", Str("Basic.Scene")); if (!foundValues) { delete popup; @@ -3490,6 +3540,9 @@ void OBSBasic::SceneNameEdited(QWidget *editor, obs_source_t *source = obs_scene_get_source(scene); RenameListItem(this, ui->scenes, source, text); + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); + UNUSED_PARAMETER(endHint); } @@ -3540,14 +3593,24 @@ void OBSBasic::OpenSceneFilters() void OBSBasic::StartStreaming() { + if (outputHandler->StreamingActive()) + return; + + if (api) + api->on_event(OBS_FRONTEND_EVENT_STREAMING_STARTING); + SaveProject(); ui->streamButton->setEnabled(false); ui->streamButton->setText(QTStr("Basic.Main.Connecting")); + sysTrayStream->setEnabled(false); + sysTrayStream->setText(ui->streamButton->text()); if (!outputHandler->StartStreaming(service)) { ui->streamButton->setText(QTStr("Basic.Main.StartStreaming")); ui->streamButton->setEnabled(true); + sysTrayStream->setText(ui->streamButton->text()); + sysTrayStream->setEnabled(true); } bool recordWhenStreaming = config_get_bool(GetGlobalConfig(), @@ -3577,18 +3640,36 @@ static inline void ClearProcessPriority() #define ClearProcessPriority() do {} while(false) #endif -void OBSBasic::StopStreaming() +inline void OBSBasic::OnActivate() { - SaveProject(); + if (ui->profileMenu->isEnabled()) { + ui->profileMenu->setEnabled(false); + App()->IncrementSleepInhibition(); + UpdateProcessPriority(); - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(); + trayIcon->setIcon(QIcon(":/res/images/tray_active.png")); + } +} +inline void OBSBasic::OnDeactivate() +{ if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { ui->profileMenu->setEnabled(true); App()->DecrementSleepInhibition(); ClearProcessPriority(); + + trayIcon->setIcon(QIcon(":/res/images/obs.png")); } +} + +void OBSBasic::StopStreaming() +{ + SaveProject(); + + if (outputHandler->StreamingActive()) + outputHandler->StopStreaming(streamingStopping); + + OnDeactivate(); bool recordWhenStreaming = config_get_bool(GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming"); @@ -3603,13 +3684,9 @@ void OBSBasic::ForceStopStreaming() SaveProject(); if (outputHandler->StreamingActive()) - outputHandler->ForceStopStreaming(); + outputHandler->StopStreaming(true); - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - } + OnDeactivate(); bool recordWhenStreaming = config_get_bool(GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming"); @@ -3623,6 +3700,8 @@ void OBSBasic::StreamDelayStarting(int sec) { ui->streamButton->setText(QTStr("Basic.Main.StopStreaming")); ui->streamButton->setEnabled(true); + sysTrayStream->setText(ui->streamButton->text()); + sysTrayStream->setEnabled(true); if (!startStreamMenu.isNull()) startStreamMenu->deleteLater(); @@ -3636,17 +3715,15 @@ void OBSBasic::StreamDelayStarting(int sec) ui->statusbar->StreamDelayStarting(sec); - if (ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - } + OnActivate(); } void OBSBasic::StreamDelayStopping(int sec) { ui->streamButton->setText(QTStr("Basic.Main.StartStreaming")); ui->streamButton->setEnabled(true); + sysTrayStream->setText(ui->streamButton->text()); + sysTrayStream->setEnabled(true); if (!startStreamMenu.isNull()) startStreamMenu->deleteLater(); @@ -3666,12 +3743,13 @@ void OBSBasic::StreamingStart() ui->streamButton->setText(QTStr("Basic.Main.StopStreaming")); ui->streamButton->setEnabled(true); ui->statusbar->StreamStarted(outputHandler->streamOutput); + sysTrayStream->setText(ui->streamButton->text()); + sysTrayStream->setEnabled(true); - if (ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - } + if (api) + api->on_event(OBS_FRONTEND_EVENT_STREAMING_STARTED); + + OnActivate(); blog(LOG_INFO, STREAMING_START); } @@ -3679,6 +3757,11 @@ void OBSBasic::StreamingStart() void OBSBasic::StreamStopping() { ui->streamButton->setText(QTStr("Basic.Main.StoppingStreaming")); + sysTrayStream->setText(ui->streamButton->text()); + + streamingStopping = true; + if (api) + api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPING); } void OBSBasic::StreamingStop(int code) @@ -3713,19 +3796,24 @@ void OBSBasic::StreamingStop(int code) ui->streamButton->setText(QTStr("Basic.Main.StartStreaming")); ui->streamButton->setEnabled(true); + sysTrayStream->setText(ui->streamButton->text()); + sysTrayStream->setEnabled(true); - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - } + streamingStopping = false; + if (api) + api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPED); + + OnDeactivate(); blog(LOG_INFO, STREAMING_STOP); - if (code != OBS_OUTPUT_SUCCESS) + if (code != OBS_OUTPUT_SUCCESS && isVisible()) { QMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); + } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { + SysTrayNotify(QT_UTF8(errorMessage), QSystemTrayIcon::Warning); + } if (!startStreamMenu.isNull()) { ui->streamButton->setMenu(nullptr); @@ -3739,6 +3827,9 @@ void OBSBasic::StartRecording() if (outputHandler->RecordingActive()) return; + if (api) + api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTING); + SaveProject(); outputHandler->StartRecording(); } @@ -3746,6 +3837,11 @@ void OBSBasic::StartRecording() void OBSBasic::RecordStopping() { ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording")); + sysTrayRecord->setText(ui->recordButton->text()); + + recordingStopping = true; + if (api) + api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPING); } void OBSBasic::StopRecording() @@ -3753,25 +3849,22 @@ void OBSBasic::StopRecording() SaveProject(); if (outputHandler->RecordingActive()) - outputHandler->StopRecording(); + outputHandler->StopRecording(recordingStopping); - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - } + OnDeactivate(); } void OBSBasic::RecordingStart() { ui->statusbar->RecordingStarted(outputHandler->fileOutput); ui->recordButton->setText(QTStr("Basic.Main.StopRecording")); + sysTrayRecord->setText(ui->recordButton->text()); - if (ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - } + recordingStopping = false; + if (api) + api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTED); + + OnActivate(); blog(LOG_INFO, RECORDING_START); } @@ -3780,29 +3873,41 @@ void OBSBasic::RecordingStop(int code) { ui->statusbar->RecordingStopped(); ui->recordButton->setText(QTStr("Basic.Main.StartRecording")); + sysTrayRecord->setText(ui->recordButton->text()); blog(LOG_INFO, RECORDING_STOP); - if (code == OBS_OUTPUT_UNSUPPORTED) { + if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { QMessageBox::information(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - } else if (code == OBS_OUTPUT_NO_SPACE) { + } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { QMessageBox::information(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - } else if (code != OBS_OUTPUT_SUCCESS) { + } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { QMessageBox::information(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - } - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); + } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { + SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), + QSystemTrayIcon::Warning); + + } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { + SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), + QSystemTrayIcon::Warning); + + } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { + SysTrayNotify(QTStr("Output.RecordError.Msg"), + QSystemTrayIcon::Warning); } + + if (api) + api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPED); + + OnDeactivate(); } void OBSBasic::on_streamButton_clicked() @@ -3811,7 +3916,7 @@ void OBSBasic::on_streamButton_clicked() bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm) { + if (confirm && isVisible()) { QMessageBox::StandardButton button = QMessageBox::question(this, QTStr("ConfirmStop.Title"), @@ -3826,7 +3931,7 @@ void OBSBasic::on_streamButton_clicked() bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow", "WarnBeforeStartingStream"); - if (confirm) { + if (confirm && isVisible()) { QMessageBox::StandardButton button = QMessageBox::question(this, QTStr("ConfirmStart.Title"), @@ -3850,8 +3955,7 @@ void OBSBasic::on_recordButton_clicked() void OBSBasic::on_settingsButton_clicked() { - OBSBasicSettings settings(this); - settings.exec(); + on_action_Settings_triggered(); } void OBSBasic::on_actionWebsite_triggered() @@ -4261,6 +4365,9 @@ void OBSBasic::TogglePreview() void OBSBasic::Nudge(int dist, MoveDir dir) { + if (ui->preview->Locked()) + return; + struct MoveInfo { float dist; MoveDir dir; @@ -4415,4 +4522,116 @@ void OBSBasic::on_actionLockPreview_triggered() ui->actionLockPreview->setChecked(ui->preview->Locked()); } +void OBSBasic::SetShowing(bool showing) +{ + if (!showing && isVisible()) { + config_set_string(App()->GlobalConfig(), + "BasicWindow", "geometry", + saveGeometry().toBase64().constData()); + + showHide->setText(QTStr("Basic.SystemTray.Show")); + QTimer::singleShot(250, this, SLOT(hide())); + + if (previewEnabled) + EnablePreviewDisplay(false); + + setVisible(false); + + } else if (showing && !isVisible()) { + showHide->setText(QTStr("Basic.SystemTray.Hide")); + QTimer::singleShot(250, this, SLOT(show())); + + if (previewEnabled) + EnablePreviewDisplay(true); + + setVisible(true); + } +} + +void OBSBasic::SystemTrayInit() { + trayIcon = new QSystemTrayIcon(QIcon(":/res/images/obs.png"), + this); + trayIcon->setToolTip("OBS Studio"); + + showHide = new QAction(QTStr("Basic.SystemTray.Show"), + trayIcon); + sysTrayStream = new QAction(QTStr("Basic.Main.StartStreaming"), + trayIcon); + sysTrayRecord = new QAction(QTStr("Basic.Main.StartRecording"), + trayIcon); + exit = new QAction(QTStr("Exit"), + trayIcon); + + connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + this, + SLOT(IconActivated(QSystemTrayIcon::ActivationReason))); + connect(showHide, SIGNAL(triggered()), + this, SLOT(ToggleShowHide())); + connect(sysTrayStream, SIGNAL(triggered()), + this, SLOT(on_streamButton_clicked())); + connect(sysTrayRecord, SIGNAL(triggered()), + this, SLOT(on_recordButton_clicked())); + connect(exit, SIGNAL(triggered()), + this, SLOT(close())); + + trayMenu = new QMenu; + trayMenu->addAction(showHide); + trayMenu->addAction(sysTrayStream); + trayMenu->addAction(sysTrayRecord); + trayMenu->addAction(exit); + trayIcon->setContextMenu(trayMenu); +} + +void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) +{ + if (reason == QSystemTrayIcon::Trigger) + ToggleShowHide(); +} + +void OBSBasic::SysTrayNotify(const QString &text, + QSystemTrayIcon::MessageIcon n) +{ + if (QSystemTrayIcon::supportsMessages()) { + QSystemTrayIcon::MessageIcon icon = + QSystemTrayIcon::MessageIcon(n); + trayIcon->showMessage("OBS Studio", text, icon, 10000); + } +} + +void OBSBasic::SystemTray(bool firstStarted) +{ + if (!QSystemTrayIcon::isSystemTrayAvailable()) + return; + + bool sysTrayWhenStarted = config_get_bool(GetGlobalConfig(), + "BasicWindow", "SysTrayWhenStarted"); + bool sysTrayEnabled = config_get_bool(GetGlobalConfig(), + "BasicWindow", "SysTrayEnabled"); + + if (firstStarted) + SystemTrayInit(); + + if (!sysTrayWhenStarted && !sysTrayEnabled) { + trayIcon->hide(); + } else if (sysTrayWhenStarted && sysTrayEnabled) { + trayIcon->show(); + if (firstStarted) { + QTimer::singleShot(50, this, SLOT(hide())); + EnablePreviewDisplay(false); + setVisible(false); + } + } else if (sysTrayEnabled) { + trayIcon->show(); + } else if (!sysTrayEnabled) { + trayIcon->hide(); + } else if (!sysTrayWhenStarted && sysTrayEnabled) { + trayIcon->hide(); + } + + if (isVisible()) + showHide->setText(QTStr("Basic.SystemTray.Hide")); + else + showHide->setText(QTStr("Basic.SystemTray.Show")); +} + string OBSBasic::codecName; \ No newline at end of file diff --git a/obs/window-basic-main.hpp b/UI/window-basic-main.hpp similarity index 91% rename from obs/window-basic-main.hpp rename to UI/window-basic-main.hpp index 097253471a1dd2..7424feaecf8312 100644 --- a/obs/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,8 @@ #include "window-basic-adv-audio.hpp" #include "window-basic-filters.hpp" +#include + #include #include #include @@ -51,6 +54,7 @@ class QNetworkReply; #define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" #define SIMPLE_ENCODER_QSV "qsv" #define SIMPLE_ENCODER_NVENC "nvenc" +#define SIMPLE_ENCODER_AMD "amd" #define PREVIEW_EDGE_SIZE 10 @@ -82,6 +86,7 @@ class OBSBasic : public OBSMainWindow { friend class OBSBasicPreview; friend class OBSBasicStatusBar; friend class OBSBasicSourceSelect; + friend struct OBSStudioAPI; enum class MoveDir { Up, @@ -91,6 +96,8 @@ class OBSBasic : public OBSMainWindow { }; private: + obs_frontend_callbacks *api = nullptr; + std::vector volumes; std::vector signalHandlers; @@ -114,6 +121,8 @@ class OBSBasic : public OBSMainWindow { OBSService service; std::unique_ptr outputHandler; + bool streamingStopping = false; + bool recordingStopping = false; gs_vertbuffer_t *box = nullptr; gs_vertbuffer_t *boxLeft = nullptr; @@ -135,6 +144,15 @@ class OBSBasic : public OBSMainWindow { QPointer startStreamMenu; + QSystemTrayIcon *trayIcon; + QMenu *trayMenu; + QAction *sysTrayStream; + QAction *sysTrayRecord; + QAction *showHide; + QAction *showPreview; + QAction *exit; + bool disableHiding = false; + void DrawBackdrop(float cx, float cy); void SetupEncoders(); @@ -223,10 +241,7 @@ class OBSBasic : public OBSMainWindow { void InitDefaultTransitions(); void InitTransition(obs_source_t *transition); - void TransitionToScene(obs_scene_t *scene, bool force = false); - void TransitionToScene(obs_source_t *scene, bool force = false); obs_source_t *FindTransition(const char *name); - void SetTransition(obs_source_t *transition); OBSSource GetCurrentTransition(); obs_data_array_t *SaveTransitions(); void LoadTransitions(obs_data_array_t *transitions); @@ -256,7 +271,6 @@ class OBSBasic : public OBSMainWindow { void SetPreviewProgramMode(bool enabled); void ResizeProgram(uint32_t cx, uint32_t cy); void SetCurrentScene(obs_scene_t *scene, bool force = false); - void SetCurrentScene(obs_source_t *scene, bool force = false); static void RenderProgram(void *data, uint32_t cx, uint32_t cy); std::vector quickTransitions; @@ -282,6 +296,15 @@ class OBSBasic : public OBSMainWindow { return os_atomic_load_bool(&previewProgramMode); } + inline void OnActivate(); + inline void OnDeactivate(); + + void AddDropSource(const char *file, bool image); + void dragEnterEvent(QDragEnterEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + public slots: void StartStreaming(); void StopStreaming(); @@ -304,6 +327,11 @@ public slots: void SaveProjectDeferred(); void SaveProject(); + void SetTransition(OBSSource transition); + void TransitionToScene(OBSScene scene, bool force = false); + void TransitionToScene(OBSSource scene, bool force = false); + void SetCurrentScene(OBSSource scene, bool force = false); + private slots: void AddSceneItem(OBSSceneItem item); void RemoveSceneItem(OBSSceneItem item); @@ -337,6 +365,19 @@ private slots: void SetScaleFilter(); + void IconActivated(QSystemTrayIcon::ActivationReason reason); + void SetShowing(bool showing); + + inline void ToggleShowHide() + { + bool showing = isVisible(); + if (disableHiding && showing) + return; + if (showing) + CloseDialogs(); + SetShowing(!showing); + } + private: /* OBS Callbacks */ static void SceneReordered(void *data, calldata_t *params); @@ -363,6 +404,8 @@ private slots: public: OBSScene GetCurrentScene(); + void SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n); + inline OBSSource GetCurrentSceneSource() { OBSScene curScene = GetCurrentScene(); @@ -412,6 +455,8 @@ private slots: void UpdateSceneSelection(OBSSource source); static std::string codecName; + void SystemTrayInit(); + void SystemTray(bool firstStarted); protected: virtual void closeEvent(QCloseEvent *event) override; @@ -431,7 +476,6 @@ private slots: void on_actionCheckForUpdates_triggered(); void on_actionEditTransform_triggered(); - void on_actionResetTransform_triggered(); void on_actionRotate90CW_triggered(); void on_actionRotate90CCW_triggered(); void on_actionRotate180_triggered(); @@ -533,6 +577,9 @@ private slots: void OpenSourceProjector(); void OpenSceneProjector(); +public slots: + void on_actionResetTransform_triggered(); + public: explicit OBSBasic(QWidget *parent = 0); virtual ~OBSBasic(); diff --git a/obs/window-basic-preview.cpp b/UI/window-basic-preview.cpp similarity index 100% rename from obs/window-basic-preview.cpp rename to UI/window-basic-preview.cpp diff --git a/obs/window-basic-preview.hpp b/UI/window-basic-preview.hpp similarity index 100% rename from obs/window-basic-preview.hpp rename to UI/window-basic-preview.hpp diff --git a/obs/window-basic-properties.cpp b/UI/window-basic-properties.cpp similarity index 100% rename from obs/window-basic-properties.cpp rename to UI/window-basic-properties.cpp diff --git a/obs/window-basic-properties.hpp b/UI/window-basic-properties.hpp similarity index 100% rename from obs/window-basic-properties.hpp rename to UI/window-basic-properties.hpp diff --git a/obs/window-basic-settings.cpp b/UI/window-basic-settings.cpp similarity index 98% rename from obs/window-basic-settings.cpp rename to UI/window-basic-settings.cpp index c22c5ac96a8134..6cadb98f8f46a1 100644 --- a/obs/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -272,8 +272,11 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->warnBeforeStreamStart,CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->warnBeforeStreamStop, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->hideProjectorCursor, CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->projectorAlwaysOnTop, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->recordWhenStreaming, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->keepRecordStreamStops,CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->systemTrayEnabled, CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->systemTrayWhenStarted,CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->snappingEnabled, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->screenSnapping, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->centerSnapping, CHECK_CHANGED, GENERAL_CHANGED); @@ -845,6 +848,14 @@ void OBSBasicSettings::LoadGeneralSettings() "BasicWindow", "KeepRecordingWhenStreamStops"); ui->keepRecordStreamStops->setChecked(keepRecordStreamStops); + bool systemTrayEnabled = config_get_bool(GetGlobalConfig(), + "BasicWindow", "SysTrayEnabled"); + ui->systemTrayEnabled->setChecked(systemTrayEnabled); + + bool systemTrayWhenStarted = config_get_bool(GetGlobalConfig(), + "BasicWindow", "SysTrayWhenStarted"); + ui->systemTrayWhenStarted->setChecked(systemTrayWhenStarted); + bool snappingEnabled = config_get_bool(GetGlobalConfig(), "BasicWindow", "SnappingEnabled"); ui->snappingEnabled->setChecked(snappingEnabled); @@ -877,6 +888,10 @@ void OBSBasicSettings::LoadGeneralSettings() "BasicWindow", "HideProjectorCursor"); ui->hideProjectorCursor->setChecked(hideProjectorCursor); + bool projectorAlwaysOnTop = config_get_bool(GetGlobalConfig(), + "BasicWindow", "ProjectorAlwaysOnTop"); + ui->projectorAlwaysOnTop->setChecked(projectorAlwaysOnTop); + loading = false; } @@ -2226,6 +2241,9 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_bool(GetGlobalConfig(), "BasicWindow", "HideProjectorCursor", ui->hideProjectorCursor->isChecked()); + config_set_bool(GetGlobalConfig(), "BasicWindow", + "ProjectorAlwaysOnTop", + ui->projectorAlwaysOnTop->isChecked()); if (WidgetChanged(ui->recordWhenStreaming)) config_set_bool(GetGlobalConfig(), "BasicWindow", @@ -2235,6 +2253,16 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_bool(GetGlobalConfig(), "BasicWindow", "KeepRecordingWhenStreamStops", ui->keepRecordStreamStops->isChecked()); + + if (WidgetChanged(ui->systemTrayEnabled)) + config_set_bool(GetGlobalConfig(), "BasicWindow", + "SysTrayEnabled", + ui->systemTrayEnabled->isChecked()); + + if (WidgetChanged(ui->systemTrayWhenStarted)) + config_set_bool(GetGlobalConfig(), "BasicWindow", + "SysTrayWhenStarted", + ui->systemTrayWhenStarted->isChecked()); } void OBSBasicSettings::SaveStream1Settings() @@ -2439,6 +2467,8 @@ void OBSBasicSettings::SaveOutputSettings() presetType = "QSVPreset"; else if (encoder == SIMPLE_ENCODER_NVENC) presetType = "NVENCPreset"; + else if (encoder == SIMPLE_ENCODER_AMD) + presetType = "AMDPreset"; else presetType = "Preset"; @@ -3218,6 +3248,10 @@ void OBSBasicSettings::FillSimpleRecordingValues() ui->simpleOutRecEncoder->addItem( ENCODER_STR("Hardware.NVENC"), QString(SIMPLE_ENCODER_NVENC)); + if (EncoderAvailable("amd_amf_h264")) + ui->simpleOutRecEncoder->addItem( + ENCODER_STR("Hardware.AMD"), + QString(SIMPLE_ENCODER_AMD)); #undef ADD_QUALITY } @@ -3234,6 +3268,10 @@ void OBSBasicSettings::FillSimpleStreamingValues() ui->simpleOutStrEncoder->addItem( ENCODER_STR("Hardware.NVENC"), QString(SIMPLE_ENCODER_NVENC)); + if (EncoderAvailable("amd_amf_h264")) + ui->simpleOutStrEncoder->addItem( + ENCODER_STR("Hardware.AMD"), + QString(SIMPLE_ENCODER_AMD)); #undef ENCODER_STR } @@ -3294,6 +3332,10 @@ void OBSBasicSettings::SimpleStreamingEncoderChanged() defaultPreset = "default"; preset = curNVENCPreset; + } else if (encoder == SIMPLE_ENCODER_AMD) { + /* none */ + defaultPreset = ""; + } else { ui->simpleOutPreset->addItem("ultrafast", "ultrafast"); ui->simpleOutPreset->addItem("superfast", "superfast"); diff --git a/obs/window-basic-settings.hpp b/UI/window-basic-settings.hpp similarity index 100% rename from obs/window-basic-settings.hpp rename to UI/window-basic-settings.hpp diff --git a/obs/window-basic-source-select.cpp b/UI/window-basic-source-select.cpp similarity index 100% rename from obs/window-basic-source-select.cpp rename to UI/window-basic-source-select.cpp diff --git a/obs/window-basic-source-select.hpp b/UI/window-basic-source-select.hpp similarity index 100% rename from obs/window-basic-source-select.hpp rename to UI/window-basic-source-select.hpp diff --git a/obs/window-basic-status-bar.cpp b/UI/window-basic-status-bar.cpp similarity index 89% rename from obs/window-basic-status-bar.cpp rename to UI/window-basic-status-bar.cpp index 64015efca2f2e7..1dd42b9996fbe8 100644 --- a/obs/window-basic-status-bar.cpp +++ b/UI/window-basic-status-bar.cpp @@ -72,6 +72,7 @@ void OBSBasicStatusBar::Deactivate() delaySecStopping = 0; reconnectTimeout = 0; active = false; + overloadedNotify = true; } } @@ -145,7 +146,9 @@ void OBSBasicStatusBar::UpdateCPUUsage() QString text; text += QString("CPU: ") + - QString::number(main->GetCPUUsage(), 'f', 1) + QString("%"); + QString::number(main->GetCPUUsage(), 'f', 1) + QString("%, ") + + QString::number(obs_get_active_fps(), 'f', 2) + QString(" fps"); + cpuUsage->setText(text); cpuUsage->setMinimumWidth(cpuUsage->width()); } @@ -165,9 +168,10 @@ void OBSBasicStatusBar::UpdateSessionTime() sessionTime->setMinimumWidth(sessionTime->width()); if (reconnectTimeout > 0) { - QString msg = QTStr("Basic.StatusBar.Reconnecting"); - showMessage(msg.arg(QString::number(retries), - QString::number(reconnectTimeout))); + QString msg = QTStr("Basic.StatusBar.Reconnecting") + .arg(QString::number(retries), + QString::number(reconnectTimeout)); + showMessage(msg); reconnectTimeout--; } else if (retries > 0) { @@ -224,12 +228,20 @@ void OBSBasicStatusBar::OBSOutputReconnectSuccess(void *data, calldata_t *params void OBSBasicStatusBar::Reconnect(int seconds) { - retries++; + OBSBasic *main = qobject_cast(parent()); + + if (!retries) + main->SysTrayNotify( + QTStr("Basic.SystemTray.Message.Reconnecting"), + QSystemTrayIcon::Warning); + reconnectTimeout = seconds; if (streamOutput) { delaySecTotal = obs_output_get_active_delay(streamOutput); UpdateDelayMsg(); + + retries++; } } @@ -246,7 +258,11 @@ void OBSBasicStatusBar::ReconnectClear() void OBSBasicStatusBar::ReconnectSuccess() { - showMessage(QTStr("Basic.StatusBar.ReconnectSuccessful"), 4000); + OBSBasic *main = qobject_cast(parent()); + + QString msg = QTStr("Basic.StatusBar.ReconnectSuccessful"); + showMessage(msg, 4000); + main->SysTrayNotify(msg, QSystemTrayIcon::Information); ReconnectClear(); if (streamOutput) { @@ -257,6 +273,8 @@ void OBSBasicStatusBar::ReconnectSuccess() void OBSBasicStatusBar::UpdateStatusBar() { + OBSBasic *main = qobject_cast(parent()); + UpdateBandwidth(); UpdateSessionTime(); UpdateDroppedFrames(); @@ -270,8 +288,14 @@ void OBSBasicStatusBar::UpdateStatusBar() int diff = skipped - lastSkippedFrameCount; double percentage = double(skipped) / double(total) * 100.0; - if (diff > 10 && percentage >= 0.1f) + if (diff > 10 && percentage >= 0.1f) { showMessage(QTStr("HighResourceUsage"), 4000); + if (!main->isVisible() && overloadedNotify) { + main->SysTrayNotify(QTStr("HighResourceUsage"), + QSystemTrayIcon::Warning); + overloadedNotify = false; + } + } lastSkippedFrameCount = skipped; } diff --git a/obs/window-basic-status-bar.hpp b/UI/window-basic-status-bar.hpp similarity index 97% rename from obs/window-basic-status-bar.hpp rename to UI/window-basic-status-bar.hpp index 3f9bc8fa0f9c85..e0c7a0d0aff705 100644 --- a/obs/window-basic-status-bar.hpp +++ b/UI/window-basic-status-bar.hpp @@ -21,6 +21,7 @@ class OBSBasicStatusBar : public QStatusBar { obs_output_t *streamOutput = nullptr; obs_output_t *recordOutput = nullptr; bool active = false; + bool overloadedNotify = true; int retries = 0; int totalSeconds = 0; diff --git a/obs/window-basic-transform.cpp b/UI/window-basic-transform.cpp similarity index 96% rename from obs/window-basic-transform.cpp rename to UI/window-basic-transform.cpp index 57ad6304f9210a..f1143e7d17f1f0 100644 --- a/obs/window-basic-transform.cpp +++ b/UI/window-basic-transform.cpp @@ -1,3 +1,4 @@ +#include #include "window-basic-transform.hpp" #include "window-basic-main.hpp" @@ -55,6 +56,11 @@ OBSBasicTransform::OBSBasicTransform(OBSBasic *parent) HookWidget(ui->cropTop, ISCROLL_CHANGED, SLOT(OnCropChanged())); HookWidget(ui->cropBottom, ISCROLL_CHANGED, SLOT(OnCropChanged())); + connect(ui->buttonBox->button(QDialogButtonBox::Reset), + SIGNAL(clicked()), this, SLOT(on_resetButton_clicked())); + connect(ui->buttonBox, + SIGNAL(rejected()), this, SLOT(close())); + installEventFilter(CreateShortcutFilter()); OBSScene curScene = main->GetCurrentScene(); @@ -288,3 +294,8 @@ void OBSBasicTransform::OnCropChanged() obs_sceneitem_set_crop(item, &crop); ignoreTransformSignal = false; } + +void OBSBasicTransform::on_resetButton_clicked() +{ + main->on_actionResetTransform_triggered(); +} diff --git a/obs/window-basic-transform.hpp b/UI/window-basic-transform.hpp similarity index 97% rename from obs/window-basic-transform.hpp rename to UI/window-basic-transform.hpp index 9764f28f602f4a..c86a24440e00db 100644 --- a/obs/window-basic-transform.hpp +++ b/UI/window-basic-transform.hpp @@ -42,6 +42,7 @@ private slots: void OnBoundsType(int index); void OnControlChanged(); void OnCropChanged(); + void on_resetButton_clicked(); public: OBSBasicTransform(OBSBasic *parent); diff --git a/obs/window-license-agreement.cpp b/UI/window-license-agreement.cpp similarity index 100% rename from obs/window-license-agreement.cpp rename to UI/window-license-agreement.cpp diff --git a/obs/window-license-agreement.hpp b/UI/window-license-agreement.hpp similarity index 100% rename from obs/window-license-agreement.hpp rename to UI/window-license-agreement.hpp diff --git a/obs/window-log-reply.cpp b/UI/window-log-reply.cpp similarity index 100% rename from obs/window-log-reply.cpp rename to UI/window-log-reply.cpp diff --git a/obs/window-log-reply.hpp b/UI/window-log-reply.hpp similarity index 100% rename from obs/window-log-reply.hpp rename to UI/window-log-reply.hpp diff --git a/obs/window-main.hpp b/UI/window-main.hpp similarity index 100% rename from obs/window-main.hpp rename to UI/window-main.hpp diff --git a/obs/window-namedialog.cpp b/UI/window-namedialog.cpp similarity index 100% rename from obs/window-namedialog.cpp rename to UI/window-namedialog.cpp diff --git a/obs/window-namedialog.hpp b/UI/window-namedialog.hpp similarity index 100% rename from obs/window-namedialog.hpp rename to UI/window-namedialog.hpp diff --git a/obs/window-projector.cpp b/UI/window-projector.cpp similarity index 95% rename from obs/window-projector.cpp rename to UI/window-projector.cpp index 172e7be0fd9629..5e3ecf972c4682 100644 --- a/obs/window-projector.cpp +++ b/UI/window-projector.cpp @@ -53,6 +53,11 @@ void OBSProjector::Init(int monitor) setGeometry(mi.x, mi.y, mi.cx, mi.cy); + bool alwaysOnTop = config_get_bool(GetGlobalConfig(), + "BasicWindow", "ProjectorAlwaysOnTop"); + if (alwaysOnTop) + SetAlwaysOnTop(this, true); + show(); if (source) diff --git a/obs/window-projector.hpp b/UI/window-projector.hpp similarity index 100% rename from obs/window-projector.hpp rename to UI/window-projector.hpp diff --git a/obs/window-remux.cpp b/UI/window-remux.cpp similarity index 100% rename from obs/window-remux.cpp rename to UI/window-remux.cpp diff --git a/obs/window-remux.hpp b/UI/window-remux.hpp similarity index 100% rename from obs/window-remux.hpp rename to UI/window-remux.hpp diff --git a/build_script/build.bat b/build_script/build.bat index bcec9dacd0bd5c..0988b4650e4972 100755 --- a/build_script/build.bat +++ b/build_script/build.bat @@ -1,6 +1,6 @@ REM @Echo Off SET build_config=Release -SET obs_version=0.15.4-ftl.9 +SET obs_version=0.16.2-ftl.10 SET coredeps=C:\beam\tachyon_deps SET QTDIR64=C:\Qt\5.6\msvc2015_64 SET QTDIR32=C:\Qt\5.6\msvc2015 diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 3cf6bd8db066c8..fa19fd99a0258d 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -13,6 +13,9 @@ endif() if(UNIX) find_package(DBus QUIET) + if (NOT APPLE) + find_package(X11_XCB REQUIRED) + endif() else() set(HAVE_DBUS "0") endif() @@ -125,6 +128,14 @@ elseif(UNIX) ${DBUS_LIBRARIES}) endif() + include_directories( + ${X11_XCB_INCLUDE_DIRS}) + add_definitions( + ${X11_XCB_DEFINITIONS}) + set(libobs_PLATFORM_DEPS + ${libobs_PLATFORM_DEPS} + ${X11_XCB_LIBRARIES}) + if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") # use the sysinfo compatibility library on bsd find_package(Libsysinfo REQUIRED) diff --git a/libobs/graphics/graphics.c b/libobs/graphics/graphics.c index a1f466ddbcbd27..aa886fb183f85c 100644 --- a/libobs/graphics/graphics.c +++ b/libobs/graphics/graphics.c @@ -962,6 +962,32 @@ static inline void build_sprite_norm(struct gs_vb_data *data, float fcx, build_sprite(data, fcx, fcy, start_u, end_u, start_v, end_v); } +static inline void build_subsprite_norm(struct gs_vb_data *data, + float fsub_x, float fsub_y, float fsub_cx, float fsub_cy, + float fcx, float fcy, uint32_t flip) +{ + float start_u, end_u; + float start_v, end_v; + + if ((flip & GS_FLIP_U) == 0) { + start_u = fsub_x / fcx; + end_u = (fsub_x + fsub_cx) / fcx; + } else { + start_u = (fsub_x + fsub_cx) / fcx; + end_u = fsub_x / fcx; + } + + if ((flip & GS_FLIP_V) == 0) { + start_v = fsub_y / fcy; + end_v = (fsub_y + fsub_cy) / fcy; + } else { + start_v = (fsub_y + fsub_cy) / fcy; + end_v = fsub_y / fcy; + } + + build_sprite(data, fsub_cx, fsub_cy, start_u, end_u, start_v, end_v); +} + static inline void build_sprite_rect(struct gs_vb_data *data, gs_texture_t *tex, float fcx, float fcy, uint32_t flip) { @@ -1011,6 +1037,37 @@ void gs_draw_sprite(gs_texture_t *tex, uint32_t flip, uint32_t width, gs_draw(GS_TRISTRIP, 0, 0); } +void gs_draw_sprite_subregion(gs_texture_t *tex, uint32_t flip, + uint32_t sub_x, uint32_t sub_y, + uint32_t sub_cx, uint32_t sub_cy) +{ + graphics_t *graphics = thread_graphics; + float fcx, fcy; + struct gs_vb_data *data; + + if (tex) { + if (gs_get_texture_type(tex) != GS_TEXTURE_2D) { + blog(LOG_ERROR, "A sprite must be a 2D texture"); + return; + } + } + + fcx = (float)gs_texture_get_width(tex); + fcy = (float)gs_texture_get_height(tex); + + data = gs_vertexbuffer_get_data(graphics->sprite_buffer); + build_subsprite_norm(data, + (float)sub_x, (float)sub_y, + (float)sub_cx, (float)sub_cy, + fcx, fcy, flip); + + gs_vertexbuffer_flush(graphics->sprite_buffer); + gs_load_vertexbuffer(graphics->sprite_buffer); + gs_load_indexbuffer(NULL); + + gs_draw(GS_TRISTRIP, 0, 0); +} + void gs_draw_cube_backdrop(gs_texture_t *cubetex, const struct quat *rot, float left, float right, float top, float bottom, float znear) { diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h index 6c7fc6606577c0..bd79fa800899e1 100644 --- a/libobs/graphics/graphics.h +++ b/libobs/graphics/graphics.h @@ -525,6 +525,9 @@ EXPORT uint8_t *gs_create_texture_file_data(const char *file, EXPORT void gs_draw_sprite(gs_texture_t *tex, uint32_t flip, uint32_t width, uint32_t height); +EXPORT void gs_draw_sprite_subregion(gs_texture_t *tex, uint32_t flip, + uint32_t x, uint32_t y, uint32_t cx, uint32_t cy); + EXPORT void gs_draw_cube_backdrop(gs_texture_t *cubetex, const struct quat *rot, float left, float right, float top, float bottom, float znear); diff --git a/libobs/obs-avc.c b/libobs/obs-avc.c index 757865a967ac93..647427ac8c1aa3 100644 --- a/libobs/obs-avc.c +++ b/libobs/obs-avc.c @@ -94,12 +94,7 @@ const uint8_t *obs_avc_find_startcode(const uint8_t *p, const uint8_t *end) static inline int get_drop_priority(int priority) { - switch (priority) { - case OBS_NAL_PRIORITY_DISPOSABLE: return OBS_NAL_PRIORITY_DISPOSABLE; - case OBS_NAL_PRIORITY_LOW: return OBS_NAL_PRIORITY_LOW; - } - - return OBS_NAL_PRIORITY_HIGHEST; + return priority; } static void serialize_avc_data(struct serializer *s, const uint8_t *data, diff --git a/libobs/obs-config.h b/libobs/obs-config.h index 6450cfad1cf4f9..3636e76b0441b7 100644 --- a/libobs/obs-config.h +++ b/libobs/obs-config.h @@ -34,14 +34,14 @@ * * Reset to zero each major version */ -#define LIBOBS_API_MINOR_VER 15 +#define LIBOBS_API_MINOR_VER 16 /* * Increment if backward-compatible bug fix * * Reset to zero each major or minor version */ -#define LIBOBS_API_PATCH_VER 4 +#define LIBOBS_API_PATCH_VER 2 #define MAKE_SEMANTIC_VERSION(major, minor, patch) \ ((major << 24) | \ diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 429aa7b67d473f..07d720b57f1462 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -243,6 +243,7 @@ struct obs_core_video { int cur_texture; uint64_t video_time; + double video_fps; video_t *video; pthread_t video_thread; uint32_t total_frames; diff --git a/libobs/obs-module.h b/libobs/obs-module.h index 5d446db68235c0..aa8c1d7bb0981e 100644 --- a/libobs/obs-module.h +++ b/libobs/obs-module.h @@ -112,6 +112,10 @@ MODULE_EXPORT void obs_module_free_locale(void); text_lookup_getstr(obs_module_lookup, val, &out); \ return out; \ } \ + bool obs_module_get_string(const char *val, const char **out) \ + { \ + return text_lookup_getstr(obs_module_lookup, val, out); \ + } \ void obs_module_set_locale(const char *locale) \ { \ if (obs_module_lookup) text_lookup_destroy(obs_module_lookup); \ @@ -127,6 +131,11 @@ MODULE_EXPORT void obs_module_free_locale(void); /** Helper function for looking up locale if default locale handler was used */ MODULE_EXTERN const char *obs_module_text(const char *lookup_string); +/** Helper function for looking up locale if default locale handler was used, + * returns true if text found, otherwise false */ +MODULE_EXTERN bool obs_module_get_string(const char *lookup_string, + const char **translated_string); + /** Helper function that returns the current module */ MODULE_EXTERN obs_module_t *obs_current_module(void); diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 21f6745507ed76..db84cd6c2c5e21 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -322,7 +322,7 @@ void obs_output_actual_stop(obs_output_t *output, bool force, uint64_t ts) bool call_stop = true; bool was_reconnecting = false; - if (stopping(output)) + if (stopping(output) && !force) return; os_event_reset(output->stopping_event); @@ -391,8 +391,8 @@ void obs_output_force_stop(obs_output_t *output) if (!stopping(output)) { output->stop_code = 0; do_output_signal(output, "stopping"); - obs_output_actual_stop(output, true, 0); } + obs_output_actual_stop(output, true, 0); } bool obs_output_active(const obs_output_t *output) diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index c2d5a99ec8b58f..1533bee5f24efb 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -726,6 +726,30 @@ enum obs_combo_format obs_property_list_format(obs_property_t *p) return data ? data->format : OBS_COMBO_FORMAT_INVALID; } +void obs_property_int_set_limits(obs_property_t *p, + int min, int max, int step) +{ + struct int_data *data = get_type_data(p, OBS_PROPERTY_INT); + if (!data) + return; + + data->min = min; + data->max = max; + data->step = step; +} + +void obs_property_float_set_limits(obs_property_t *p, + double min, double max, double step) +{ + struct float_data *data = get_type_data(p, OBS_PROPERTY_INT); + if (!data) + return; + + data->min = min; + data->max = max; + data->step = step; +} + void obs_property_list_clear(obs_property_t *p) { struct list_data *data = get_list_data(p); diff --git a/libobs/obs-properties.h b/libobs/obs-properties.h index c3b744606420cc..e280ab4a6ad64a 100644 --- a/libobs/obs-properties.h +++ b/libobs/obs-properties.h @@ -262,6 +262,11 @@ EXPORT const char * obs_property_path_default_path(obs_property_t *p); EXPORT enum obs_combo_type obs_property_list_type(obs_property_t *p); EXPORT enum obs_combo_format obs_property_list_format(obs_property_t *p); +EXPORT void obs_property_int_set_limits(obs_property_t *p, + int min, int max, int step); +EXPORT void obs_property_float_set_limits(obs_property_t *p, + double min, double max, double step); + EXPORT void obs_property_list_clear(obs_property_t *p); EXPORT size_t obs_property_list_add_string(obs_property_t *p, diff --git a/libobs/obs-source.h b/libobs/obs-source.h index f4b0f809e4ea84..42efaccf2f8492 100644 --- a/libobs/obs-source.h +++ b/libobs/obs-source.h @@ -115,6 +115,11 @@ enum obs_source_type { */ #define OBS_SOURCE_DO_NOT_DUPLICATE (1<<7) +/** + * Source is deprecated and should not be used + */ +#define OBS_SOURCE_DEPRECATED (1<<8) + /** @} */ typedef void (*obs_source_enum_proc_t)(obs_source_t *parent, diff --git a/libobs/obs-video.c b/libobs/obs-video.c index 6b046a9fcccbab..cdf83f250b51c8 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -573,6 +573,8 @@ void *obs_video_thread(void *param) { uint64_t last_time = 0; uint64_t interval = video_output_get_frame_time(obs->video.video); + uint64_t fps_total_ns = 0; + uint32_t fps_total_frames = 0; obs->video.video_time = os_gettime_ns(); @@ -603,6 +605,16 @@ void *obs_video_thread(void *param) profile_reenable_thread(); video_sleep(&obs->video, &obs->video.video_time, interval); + + fps_total_ns += (obs->video.video_time - last_time); + fps_total_frames++; + + if (fps_total_ns >= 1000000000ULL) { + obs->video.video_fps = (double)fps_total_frames / + ((double)fps_total_ns / 1000000000.0); + fps_total_ns = 0; + fps_total_frames = 0; + } } UNUSED_PARAMETER(param); diff --git a/libobs/obs-windows.c b/libobs/obs-windows.c index 0f3e76dddb9a2f..f9e9304cf32a3a 100644 --- a/libobs/obs-windows.c +++ b/libobs/obs-windows.c @@ -172,13 +172,27 @@ static void log_available_memory(void) note); } +static bool is_64_bit_windows(void) +{ +#if defined(_WIN64) + return true; +#elif defined(_WIN32) + BOOL b64 = false; + return IsWow64Process(GetCurrentProcess(), &b64) && b64; +#endif +} + static void log_windows_version(void) { struct win_version_info ver; get_win_ver(&ver); - blog(LOG_INFO, "Windows Version: %d.%d Build %d (revision: %d)", - ver.major, ver.minor, ver.build, ver.revis); + bool b64 = is_64_bit_windows(); + const char *windows_bitness = b64 ? "64" : "32"; + + blog(LOG_INFO, "Windows Version: %d.%d Build %d (revision: %d; %s-bit)", + ver.major, ver.minor, ver.build, ver.revis, + windows_bitness); } static void log_admin_status(void) diff --git a/libobs/obs.c b/libobs/obs.c index 060134361014e5..685d892075027d 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -820,6 +820,8 @@ void obs_shutdown(void) obs_free_graphics(); proc_handler_destroy(obs->procs); signal_handler_destroy(obs->signals); + obs->procs = NULL; + obs->signals = NULL; module = obs->first_module; while (module) { @@ -923,14 +925,35 @@ int obs_reset_video(struct obs_video_info *ovi) } } + const char *scale_type_name = ""; + switch (ovi->scale_type) { + case OBS_SCALE_DISABLE: + scale_type_name = "Disabled"; + break; + case OBS_SCALE_POINT: + scale_type_name = "Point"; + break; + case OBS_SCALE_BICUBIC: + scale_type_name = "Bicubic"; + break; + case OBS_SCALE_BILINEAR: + scale_type_name = "Bilinear"; + break; + case OBS_SCALE_LANCZOS: + scale_type_name = "Lanczos"; + break; + } + blog(LOG_INFO, "---------------------------------"); blog(LOG_INFO, "video settings reset:\n" "\tbase resolution: %dx%d\n" "\toutput resolution: %dx%d\n" + "\tdownscale filter: %s\n" "\tfps: %d/%d\n" "\tformat: %s", ovi->base_width, ovi->base_height, ovi->output_width, ovi->output_height, + scale_type_name, ovi->fps_num, ovi->fps_den, get_video_format_name(ovi->output_format)); @@ -1219,6 +1242,7 @@ void obs_enum_sources(bool (*enum_proc)(void*, obs_source_t*), void *param) (obs_source_t*)source->context.next; if ((source->info.type == OBS_SOURCE_TYPE_INPUT) != 0 && + !source->context.private && !enum_proc(param, source)) break; @@ -1816,6 +1840,11 @@ uint64_t obs_get_video_frame_time(void) return obs ? obs->video.video_time : 0; } +double obs_get_active_fps(void) +{ + return obs ? obs->video.video_fps : 0.0; +} + enum obs_obj_type obs_obj_get_type(void *obj) { struct obs_context_data *context = obj; diff --git a/libobs/obs.h b/libobs/obs.h index e83df2d58932ce..f96b91bf6917c8 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -612,6 +612,8 @@ EXPORT void obs_view_render(obs_view_t *view); EXPORT uint64_t obs_get_video_frame_time(void); +EXPORT double obs_get_active_fps(void); + /* ------------------------------------------------------------------------- */ /* Display context */ diff --git a/libobs/util/platform-windows.c b/libobs/util/platform-windows.c index 2e33cfaf1ecaaf..faadf622c5d3b5 100644 --- a/libobs/util/platform-windows.c +++ b/libobs/util/platform-windows.c @@ -84,9 +84,24 @@ void *os_dlopen(const char *path) if (wpath_slash) SetDllDirectoryW(NULL); - if (!h_library) - blog(LOG_INFO, "LoadLibrary failed for '%s', error: %ld", - path, GetLastError()); + if (!h_library) { + DWORD error = GetLastError(); + char *message = NULL; + + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, error, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + (LPSTR)&message, 0, NULL); + + blog(LOG_INFO, "LoadLibrary failed for '%s': %s (%lu)", + path, message, error); + + if (message) + LocalFree(message); + } + return h_library; } @@ -717,7 +732,7 @@ bool get_dll_ver(const wchar_t *lib, struct win_version_info *ver_info) } data = bmalloc(size); - if (!get_file_version_info(L"kernel32", 0, size, data)) { + if (!get_file_version_info(lib, 0, size, data)) { blog(LOG_ERROR, "Failed to get windows version info"); bfree(data); return false; @@ -739,6 +754,8 @@ bool get_dll_ver(const wchar_t *lib, struct win_version_info *ver_info) return true; } +#define WINVER_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" + void get_win_ver(struct win_version_info *info) { static struct win_version_info ver = {0}; @@ -750,6 +767,26 @@ void get_win_ver(struct win_version_info *info) if (!got_version) { get_dll_ver(L"kernel32", &ver); got_version = true; + + if (ver.major == 10 && ver.revis == 0) { + HKEY key; + DWORD size, win10_revision; + LSTATUS status; + + status = RegOpenKeyW(HKEY_LOCAL_MACHINE, + WINVER_REG_KEY, &key); + if (status != ERROR_SUCCESS) + return; + + size = sizeof(win10_revision); + + status = RegQueryValueExW(key, L"UBR", NULL, NULL, + (LPBYTE)&win10_revision, &size); + if (status == ERROR_SUCCESS) + ver.revis = (int)win10_revision; + + RegCloseKey(key); + } } *info = ver; diff --git a/libobs/util/platform.c b/libobs/util/platform.c index ffccf8ab2966bc..8d8669eccef8b7 100644 --- a/libobs/util/platform.c +++ b/libobs/util/platform.c @@ -164,7 +164,6 @@ size_t os_fread_mbs(FILE *file, char **pstr) size_t os_fread_utf8(FILE *file, char **pstr) { size_t size = 0; - size_t size_read; size_t len = 0; *pstr = NULL; @@ -177,11 +176,13 @@ size_t os_fread_utf8(FILE *file, char **pstr) char *utf8str; off_t offset; + bom[0] = 0; + bom[1] = 0; + bom[2] = 0; + /* remove the ghastly BOM if present */ fseek(file, 0, SEEK_SET); - size_read = fread(bom, 1, 3, file); - if (size_read != 3) - return 0; + fread(bom, 1, 3, file); offset = (astrcmp_n(bom, "\xEF\xBB\xBF", 3) == 0) ? 3 : 0; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 575359a046c2b3..62b882bffbc3e8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -11,6 +11,14 @@ if(WIN32) add_subdirectory(win-mf) add_subdirectory(obs-qsv11) add_subdirectory(vlc-video) + option(BUILD_AMF_ENCODER "Build AMD Advanced Media Framework encoder module" OFF) + if (BUILD_AMF_ENCODER) + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/enc-amf/CMakeLists.txt") + add_subdirectory(enc-amf) + else() + message(STATUS "enc-amf submodule not found! Please fetch submodules. enc-amf plugin disabled.") + endif() + endif() elseif(APPLE) add_subdirectory(coreaudio-encoder) add_subdirectory(mac-avcapture) @@ -19,6 +27,7 @@ elseif(APPLE) add_subdirectory(mac-syphon) add_subdirectory(decklink/mac) add_subdirectory(vlc-video) + add_subdirectory(linux-jack) elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") add_subdirectory(linux-capture) add_subdirectory(linux-pulseaudio) @@ -34,6 +43,17 @@ elseif("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD") add_subdirectory(linux-jack) endif() +if(WIN32 OR APPLE) + option(BUILD_BROWSER "Build browser plugin" OFF) + if (BUILD_BROWSER) + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/obs-browser/CMakeLists.txt") + add_subdirectory(obs-browser) + else() + message(STATUS "obs-browser submodule not found! Please fetch submodules. obs-browser plugin disabled.") + endif() + endif() +endif() + add_subdirectory(image-source) add_subdirectory(obs-x264) add_subdirectory(obs-libfdk) @@ -41,6 +61,7 @@ add_subdirectory(obs-ffmpeg) add_subdirectory(obs-outputs) add_subdirectory(obs-filters) add_subdirectory(obs-transitions) +add_subdirectory(obs-text) add_subdirectory(rtmp-services) add_subdirectory(ftl-services) add_subdirectory(libftl) diff --git a/plugins/coreaudio-encoder/data/locale/sv-SE.ini b/plugins/coreaudio-encoder/data/locale/sv-SE.ini index 8bf5f03cda0296..225da2fc61cd66 100644 --- a/plugins/coreaudio-encoder/data/locale/sv-SE.ini +++ b/plugins/coreaudio-encoder/data/locale/sv-SE.ini @@ -2,4 +2,5 @@ CoreAudioAAC="CoreAudio AAC-kodare" Bitrate="Bithastighet" AllowHEAAC="Tillåt HE-AAC" OutputSamplerate="Samplingsfrekvens för utmatning" +UseInputSampleRate="Använd inmatningens (OBS) samplingshastighet (kan lista bithastigheter som inte stöds)" diff --git a/plugins/decklink/data/locale/ko-KR.ini b/plugins/decklink/data/locale/ko-KR.ini index 43fd331989d60e..7247470301bbd7 100644 --- a/plugins/decklink/data/locale/ko-KR.ini +++ b/plugins/decklink/data/locale/ko-KR.ini @@ -1,6 +1,6 @@ BlackmagicDevice="Blackmagic 장치" Device="장치" -Mode="모드" +Mode="방식" Buffering="버퍼링 사용" PixelFormat="픽셀 형식" diff --git a/plugins/enc-amf b/plugins/enc-amf new file mode 160000 index 00000000000000..cfe33a10879252 --- /dev/null +++ b/plugins/enc-amf @@ -0,0 +1 @@ +Subproject commit cfe33a10879252cb7af61147ff909c636a3448ba diff --git a/plugins/image-source/data/locale/da-DK.ini b/plugins/image-source/data/locale/da-DK.ini index 75de5c0b9f1d8e..6c988c354e07cc 100644 --- a/plugins/image-source/data/locale/da-DK.ini +++ b/plugins/image-source/data/locale/da-DK.ini @@ -2,4 +2,14 @@ ImageInput="Billede" File="Billedfil" UnloadWhenNotShowing="Fjern billede fra hukommelsen når det ikke vises" +SlideShow="Billede diasshow" +SlideShow.TransitionSpeed="Overgangshastighed (millisekunder)" +SlideShow.SlideTime="Tid mellem dias (millisekunder)" +SlideShow.Files="Billedfiler" +SlideShow.Randomize="Tilfældig afspilning" +SlideShow.Transition="Overgang" +SlideShow.Transition.Cut="Klip" +SlideShow.Transition.Fade="Overgang" +SlideShow.Transition.Swipe="Stryg" +SlideShow.Transition.Slide="Glide" diff --git a/plugins/image-source/image-source.c b/plugins/image-source/image-source.c index 91ced6c4ded7f2..5986edddcb79cd 100644 --- a/plugins/image-source/image-source.c +++ b/plugins/image-source/image-source.c @@ -207,7 +207,7 @@ static void image_source_tick(void *data, float seconds) time_t t = get_modified_timestamp(context->file); context->update_time_elapsed = 0.0f; - if (context->file_timestamp < t) { + if (context->file_timestamp != t) { image_source_load(context); } } diff --git a/plugins/linux-alsa/alsa-input.c b/plugins/linux-alsa/alsa-input.c index 94ea5efebfbdc9..e9fb2003e54af7 100644 --- a/plugins/linux-alsa/alsa-input.c +++ b/plugins/linux-alsa/alsa-input.c @@ -64,6 +64,8 @@ struct alsa_data { }; static const char * alsa_get_name(void *); +static bool alsa_devices_changed(obs_properties_t *props, + obs_property_t *p, obs_data_t *settings); static obs_properties_t * alsa_get_properties(void *); static void * alsa_create(obs_data_t *, obs_source_t *); static void alsa_destroy(void *); @@ -119,7 +121,12 @@ void * alsa_create(obs_data_t *settings, obs_source_t *source) data->listen_thread = 0; data->reopen_thread = 0; - data->device = bstrdup(obs_data_get_string(settings, "device_id")); + const char *device = obs_data_get_string(settings, "device_id"); + + if (strcmp(device, "__custom__") == 0) + device = obs_data_get_string(settings, "custom_pcm"); + + data->device = bstrdup(device); data->rate = obs_data_get_int(settings, "rate"); if (os_event_init(&data->abort_event, OS_EVENT_TYPE_MANUAL) != 0) { @@ -179,6 +186,10 @@ void alsa_update(void *vptr, obs_data_t *settings) bool reset = false; device = obs_data_get_string(settings, "device_id"); + + if (strcmp(device, "__custom__") == 0) + device = obs_data_get_string(settings, "custom_pcm"); + if (strcmp(data->device, device) != 0) { bfree(data->device); data->device = bstrdup(device); @@ -215,9 +226,28 @@ const char * alsa_get_name(void *unused) void alsa_get_defaults(obs_data_t *settings) { obs_data_set_default_string(settings, "device_id", "default"); + obs_data_set_default_string(settings, "custom_pcm", "default"); obs_data_set_default_int(settings, "rate", 44100); } +static bool alsa_devices_changed(obs_properties_t *props, + obs_property_t *p, obs_data_t *settings) +{ + UNUSED_PARAMETER(p); + bool visible = false; + const char *device_id = obs_data_get_string(settings, "device_id"); + + if (strcmp(device_id, "__custom__") == 0) + visible = true; + + obs_property_t *custom_pcm = obs_properties_get(props, "custom_pcm"); + + obs_property_set_visible(custom_pcm, visible); + obs_property_modified(custom_pcm, settings); + + return true; +} + obs_properties_t * alsa_get_properties(void *unused) { void **hints; @@ -240,10 +270,15 @@ obs_properties_t * alsa_get_properties(void *unused) obs_property_list_add_string(devices, "Default", "default"); + obs_properties_add_text(props, "custom_pcm", + obs_module_text("PCM"), OBS_TEXT_DEFAULT); + rate = obs_properties_add_list(props, "rate", obs_module_text("Rate"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback(devices, alsa_devices_changed); + obs_property_list_add_int(rate, "32000 Hz", 32000); obs_property_list_add_int(rate, "44100 Hz", 44100); obs_property_list_add_int(rate, "48000 Hz", 48000); @@ -277,6 +312,8 @@ obs_properties_t * alsa_get_properties(void *unused) obs_property_list_add_string(devices, descr, name); + obs_property_list_add_string(devices, "Custom", "__custom__"); + next: if (name != NULL) free(name), name = NULL; diff --git a/plugins/linux-capture/data/locale/ja-JP.ini b/plugins/linux-capture/data/locale/ja-JP.ini index 332892cf02f46e..c004b9cf722947 100644 --- a/plugins/linux-capture/data/locale/ja-JP.ini +++ b/plugins/linux-capture/data/locale/ja-JP.ini @@ -2,7 +2,7 @@ X11SharedMemoryScreenInput="画面キャプチャ (XSHM)" Screen="画面" CaptureCursor="カーソルをキャプチャ" AdvancedSettings="高度な設定" -XServer="X サーバ" +XServer="X サーバー" XCCapture="ウィンドウキャプチャ (Xcomposite)" Window="ウィンドウ" CropTop="上部クロップ (ピクセル)" @@ -10,7 +10,7 @@ CropLeft="左側クロップ (ピクセル)" CropRight="右側クロップ (ピクセル)" CropBottom="下部クロップ (ピクセル)" SwapRedBlue="赤と青を入れ替え" -LockX="キャプチャ時にXサーバをロック" -IncludeXBorder="Xウインドウの境界を含める" +LockX="キャプチャ時にXサーバーをロック" +IncludeXBorder="Xウィンドウの境界を含める" ExcludeAlpha="アルファなしテクスチャ形式を使用 (Mesaの回避策)" diff --git a/plugins/linux-jack/jack-wrapper.c b/plugins/linux-jack/jack-wrapper.c index f55d1ea20a31c6..25b125cef1782c 100644 --- a/plugins/linux-jack/jack-wrapper.c +++ b/plugins/linux-jack/jack-wrapper.c @@ -17,7 +17,7 @@ along with this program. If not, see . #include "jack-wrapper.h" -#include +#include #include #include diff --git a/plugins/linux-jack/jack-wrapper.h b/plugins/linux-jack/jack-wrapper.h index 85202fff8105a3..460fe91890eb3e 100644 --- a/plugins/linux-jack/jack-wrapper.h +++ b/plugins/linux-jack/jack-wrapper.h @@ -19,7 +19,7 @@ along with this program. If not, see . #include #include -#include +#include struct jack_data { obs_source_t *source; diff --git a/plugins/linux-pulseaudio/pulse-input.c b/plugins/linux-pulseaudio/pulse-input.c index 563773e55f21a8..463e0c25197a59 100644 --- a/plugins/linux-pulseaudio/pulse-input.c +++ b/plugins/linux-pulseaudio/pulse-input.c @@ -183,7 +183,13 @@ static void pulse_source_info(pa_context *c, const pa_source_info *i, int eol, { UNUSED_PARAMETER(c); PULSE_DATA(userdata); - if (eol != 0) + // An error occured + if (eol < 0) { + data->format = PA_SAMPLE_INVALID; + goto skip; + } + // Terminating call for multi instance callbacks + if (eol > 0) goto skip; blog(LOG_INFO, "Audio format: %s, %"PRIu32" Hz" @@ -242,6 +248,10 @@ static int_fast32_t pulse_start_recording(struct pulse_data *data) blog(LOG_ERROR, "Unable to get source info !"); return -1; } + if (data->format == PA_SAMPLE_INVALID) { + blog(LOG_ERROR, "An error occurred while getting the source info!"); + return -1; + } pa_sample_spec spec; spec.format = data->format; @@ -507,11 +517,7 @@ static void *pulse_create(obs_data_t *settings, obs_source_t *source) pulse_init(); pulse_update(data, settings); - if (data->stream) - return data; - - pulse_destroy(data); - return NULL; + return data; } struct obs_source_info pulse_input_capture = { diff --git a/plugins/mac-capture/data/locale/ja-JP.ini b/plugins/mac-capture/data/locale/ja-JP.ini index a3299f6a22586f..78ff12347840d7 100644 --- a/plugins/mac-capture/data/locale/ja-JP.ini +++ b/plugins/mac-capture/data/locale/ja-JP.ini @@ -6,14 +6,14 @@ DisplayCapture="画面キャプチャ" DisplayCapture.Display="ディスプレイ" DisplayCapture.ShowCursor="カーソルを表示" WindowCapture="ウィンドウキャプチャ" -WindowCapture.ShowShadow="ウインドウの影を表示" +WindowCapture.ShowShadow="ウィンドウの影を表示" WindowUtils.Window="ウィンドウ" WindowUtils.ShowEmptyNames="空の名前でウィンドウを表示" CropMode="クロップ" CropMode.None="未設定" CropMode.Manual="手動" CropMode.ToWindow="ウィンドウにあわせる" -CropMode.ToWindowAndManual="ウインドウに合わせて手動" +CropMode.ToWindowAndManual="ウィンドウに合わせて手動" Crop.origin.x="左をクロップ" Crop.origin.y="上をクロップ" Crop.size.width="右をクロップ" diff --git a/plugins/mac-capture/data/locale/ko-KR.ini b/plugins/mac-capture/data/locale/ko-KR.ini index c49007eb445a72..9c7727d1ef8840 100644 --- a/plugins/mac-capture/data/locale/ko-KR.ini +++ b/plugins/mac-capture/data/locale/ko-KR.ini @@ -16,6 +16,6 @@ CropMode.ToWindow="윈도우에 맞추기" CropMode.ToWindowAndManual="윈도우에 맞추고 수동" Crop.origin.x="왼쪽 자르기" Crop.origin.y="위쪽 자르기" -Crop.size.width="우측 자르기" +Crop.size.width="오른쪽 자르기" Crop.size.height="아래 자르기" diff --git a/plugins/obs-browser b/plugins/obs-browser new file mode 160000 index 00000000000000..2eaf69f7a3d2ac --- /dev/null +++ b/plugins/obs-browser @@ -0,0 +1 @@ +Subproject commit 2eaf69f7a3d2ac98a45d5cc96124ab7c666bfa2f diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index 959d7512522caa..7dff1cef16e1be 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -6,6 +6,8 @@ RateControl="Rate Control" KeyframeIntervalSec="Keyframe Interval (seconds, 0=auto)" Lossless="Lossless" +BFrames="B-frames" + NVENC.Use2Pass="Use Two-Pass Encoding" NVENC.Preset.default="Default" NVENC.Preset.hq="High Quality" diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c index 65212bcb65fef1..1c45d672885465 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c @@ -274,7 +274,7 @@ static void ffmpeg_mux_stop(void *data, uint64_t ts) { struct ffmpeg_muxer *stream = data; - if (capturing(stream)) { + if (capturing(stream) || ts == 0) { stream->stop_ts = (int64_t)ts / 1000LL; os_atomic_set_bool(&stream->stopping, true); os_atomic_set_bool(&stream->capturing, false); diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c index 5ad1daf497bb06..520c6e80dfa731 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c @@ -54,6 +54,7 @@ struct nvenc_encoder { int height; bool first_packet; + bool initialized; }; static const char *nvenc_getname(void *unused) @@ -113,6 +114,8 @@ static bool nvenc_init_codec(struct nvenc_encoder *enc) return false; } + enc->initialized = true; + *((AVPicture*)enc->vframe) = enc->dst_picture; return true; } @@ -138,6 +141,7 @@ static bool nvenc_update(void *data, obs_data_t *settings) bool twopass = obs_data_get_bool(settings, "2pass"); int gpu = (int)obs_data_get_int(settings, "gpu"); bool cbr_override = obs_data_get_bool(settings, "cbr"); + int bf = (int)obs_data_get_int(settings, "bf"); video_t *video = obs_encoder_video(enc->encoder); const struct video_output_info *voi = video_output_get_info(video); @@ -159,7 +163,7 @@ static bool nvenc_update(void *data, obs_data_t *settings) nvenc_video_info(enc, &info); av_opt_set_int(enc->context->priv_data, "cbr", false, 0); - + av_opt_set(enc->context->priv_data, "profile", profile, 0); av_opt_set(enc->context->priv_data, "preset", preset, 0); if (astrcmpi(rc, "cqp") == 0) { @@ -198,6 +202,7 @@ static bool nvenc_update(void *data, obs_data_t *settings) AVCOL_SPC_BT709 : AVCOL_SPC_BT470BG; enc->context->color_range = info.range == VIDEO_RANGE_FULL ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; + enc->context->max_b_frames = bf; if (keyint_sec) enc->context->gop_size = keyint_sec * voi->fps_num / @@ -232,6 +237,20 @@ static void nvenc_destroy(void *data) { struct nvenc_encoder *enc = data; + if (enc->initialized) { + AVPacket pkt = {0}; + int r_pkt = 1; + + while (r_pkt) { + if (avcodec_encode_video2(enc->context, &pkt, NULL, + &r_pkt) < 0) + break; + + if (r_pkt) + av_free_packet(&pkt); + } + } + avcodec_close(enc->context); av_frame_free(&enc->vframe); avpicture_free(&enc->dst_picture); @@ -363,6 +382,7 @@ static void nvenc_defaults(obs_data_t *settings) obs_data_set_default_string(settings, "level", "auto"); obs_data_set_default_bool(settings, "2pass", true); obs_data_set_default_int(settings, "gpu", 0); + obs_data_set_default_int(settings, "bf", 2); } static bool rate_control_modified(obs_properties_t *ppts, obs_property_t *p, @@ -472,6 +492,9 @@ static obs_properties_t *nvenc_properties(void *unused) obs_module_text("NVENC.Use2Pass")); obs_properties_add_int(props, "gpu", obs_module_text("GPU"), 0, 8, 1); + obs_properties_add_int(props, "bf", obs_module_text("BFrames"), + 0, 4, 1); + return props; } diff --git a/plugins/obs-filters/data/locale/fr-FR.ini b/plugins/obs-filters/data/locale/fr-FR.ini index 8aebc6600fdee2..bdfedaf706168f 100644 --- a/plugins/obs-filters/data/locale/fr-FR.ini +++ b/plugins/obs-filters/data/locale/fr-FR.ini @@ -8,6 +8,7 @@ ColorKeyFilter="Couleur d'incrustation" SharpnessFilter="Accentuer" ScaleFilter="Mise à l’échelle / Ratio d'affichage" NoiseGate="Noise Gate" +NoiseSuppress="Suppression du bruit" Gain="Gain" DelayMs="Retard (en millisecondes)" Type="Type " @@ -59,4 +60,5 @@ ScaleFiltering.Point="Point" ScaleFiltering.Bilinear="Bilinéaire" ScaleFiltering.Bicubic="Bicubique" ScaleFiltering.Lanczos="Lanczos" +NoiseSuppress.SuppressLevel="Seuil de suppression (en dB)" diff --git a/plugins/obs-filters/data/locale/ko-KR.ini b/plugins/obs-filters/data/locale/ko-KR.ini index 9e93144c3995ad..1470851aa91851 100644 --- a/plugins/obs-filters/data/locale/ko-KR.ini +++ b/plugins/obs-filters/data/locale/ko-KR.ini @@ -30,8 +30,8 @@ KeyColor="키 색상" Similarity="유사성 (1-1000)" Smoothness="매끄러움 (1-1000)" ColorSpillReduction="키 색상 유출 감소 (1-1000)" -Crop.Left="좌측" -Crop.Right="우측" +Crop.Left="왼쪽" +Crop.Right="오른쪽" Crop.Top="상단" Crop.Bottom="하단" Crop.Width="너비" diff --git a/plugins/obs-filters/data/locale/pt-BR.ini b/plugins/obs-filters/data/locale/pt-BR.ini index 137175a08838dc..e3901ed4fca19a 100644 --- a/plugins/obs-filters/data/locale/pt-BR.ini +++ b/plugins/obs-filters/data/locale/pt-BR.ini @@ -52,4 +52,12 @@ NoiseGate.HoldTime="Tempo de Bloqueio (milissegundos)" NoiseGate.ReleaseTime="Tempo de libertação (milissegundos)" Gain.GainDB="Ganho (dB)" StretchImage="Esticar a Imagem (descartar aspecto da imagem)" +Resolution="Resolução" +None="Nenhum" +ScaleFiltering="Filtragem de escala" +ScaleFiltering.Point="Ponto" +ScaleFiltering.Bilinear="Bilinear" +ScaleFiltering.Bicubic="Bicúbico" +ScaleFiltering.Lanczos="Lanczos" +NoiseSuppress.SuppressLevel="Nível de redução (dB)" diff --git a/plugins/obs-filters/data/locale/ru-RU.ini b/plugins/obs-filters/data/locale/ru-RU.ini index 3ba18ce810d291..e9447a4601afc3 100644 --- a/plugins/obs-filters/data/locale/ru-RU.ini +++ b/plugins/obs-filters/data/locale/ru-RU.ini @@ -59,6 +59,6 @@ ScaleFiltering="Масштаб Фильтрации" ScaleFiltering.Point="Точечная" ScaleFiltering.Bilinear="Билинейная" ScaleFiltering.Bicubic="Бикубическая" -ScaleFiltering.Lanczos="Ланцошная" +ScaleFiltering.Lanczos="Метод Ланцоша" NoiseSuppress.SuppressLevel="Уровень подавления (дБ)" diff --git a/plugins/obs-filters/data/locale/sv-SE.ini b/plugins/obs-filters/data/locale/sv-SE.ini index b8b94be0960a72..00ad818d79892b 100644 --- a/plugins/obs-filters/data/locale/sv-SE.ini +++ b/plugins/obs-filters/data/locale/sv-SE.ini @@ -1,12 +1,14 @@ ColorFilter="Färgkorrigering" MaskFilter="Bild Mask/Blandning" AsyncDelayFilter="Videofördröjning (Async)" +CropFilter="Beskär/Fyll ut" ScrollFilter="Scrollning" ChromaKeyFilter="Kromafilter" ColorKeyFilter="Färgfilter" SharpnessFilter="Skärpa" ScaleFilter="Skalning/Bildförhållande" NoiseGate="Brusblockering" +NoiseSuppress="Brusreducering" Gain="Förstärkning" DelayMs="Fördröjning (millisekunder)" Type="Typ" @@ -58,4 +60,5 @@ ScaleFiltering.Point="Punkt" ScaleFiltering.Bilinear="Bilinjär" ScaleFiltering.Bicubic="Bikubisk" ScaleFiltering.Lanczos="Lanczos" +NoiseSuppress.SuppressLevel="Brusreduceringsnivå (dB)" diff --git a/plugins/obs-filters/data/locale/zh-CN.ini b/plugins/obs-filters/data/locale/zh-CN.ini index 48033b49165868..b818e37f5a2414 100644 --- a/plugins/obs-filters/data/locale/zh-CN.ini +++ b/plugins/obs-filters/data/locale/zh-CN.ini @@ -8,6 +8,7 @@ ColorKeyFilter="色值" SharpnessFilter="锐化" ScaleFilter="缩放比例" NoiseGate="噪音阈值" +NoiseSuppress="噪声抑制" Gain="增益" DelayMs="延迟(毫秒)" Type="类型" @@ -59,4 +60,5 @@ ScaleFiltering.Point="点" ScaleFiltering.Bilinear="双线性算法" ScaleFiltering.Bicubic="双立方算法" ScaleFiltering.Lanczos="兰索斯算法" +NoiseSuppress.SuppressLevel="抑制程度 (dB)" diff --git a/plugins/obs-filters/noise-suppress-filter.c b/plugins/obs-filters/noise-suppress-filter.c index 99cdb3aba97d27..9c9bb8c1246ee1 100644 --- a/plugins/obs-filters/noise-suppress-filter.c +++ b/plugins/obs-filters/noise-suppress-filter.c @@ -277,7 +277,7 @@ static obs_properties_t *noise_suppress_properties(void *data) obs_properties_t *ppts = obs_properties_create(); obs_properties_add_int_slider(ppts, S_SUPPRESS_LEVEL, - TEXT_SUPPRESS_LEVEL, SUP_MIN, SUP_MAX, 0); + TEXT_SUPPRESS_LEVEL, SUP_MIN, SUP_MAX, 1); UNUSED_PARAMETER(data); return ppts; diff --git a/plugins/obs-outputs/data/locale/fr-FR.ini b/plugins/obs-outputs/data/locale/fr-FR.ini index 6bd6f2b5e90f30..ad4b9b83f2dcca 100644 --- a/plugins/obs-outputs/data/locale/fr-FR.ini +++ b/plugins/obs-outputs/data/locale/fr-FR.ini @@ -2,4 +2,5 @@ RTMPStream="Flux RTMP" RTMPStream.DropThreshold="Seuil de baisse (en millisecondes)" FLVOutput="Fichier FLV sortant" FLVOutput.FilePath="Chemin du fichier" +Default="Interface par défaut" diff --git a/plugins/obs-outputs/data/locale/zh-CN.ini b/plugins/obs-outputs/data/locale/zh-CN.ini index c006fc60579f55..2f0a123b4f4261 100644 --- a/plugins/obs-outputs/data/locale/zh-CN.ini +++ b/plugins/obs-outputs/data/locale/zh-CN.ini @@ -2,4 +2,5 @@ RTMPStream="RTMP 流" RTMPStream.DropThreshold="Drop阈值(毫秒)" FLVOutput="FLV 文件输出" FLVOutput.FilePath="文件路径" +Default="默认" diff --git a/plugins/obs-outputs/librtmp/rtmp.c b/plugins/obs-outputs/librtmp/rtmp.c index f498da91a371f4..6b287a27186295 100644 --- a/plugins/obs-outputs/librtmp/rtmp.c +++ b/plugins/obs-outputs/librtmp/rtmp.c @@ -695,10 +695,10 @@ add_addr_info(struct sockaddr_storage *service, socklen_t *addrlen, AVal *host, goto finish; } - // they should come back in OS preferred order + // prefer ipv4 results, since lots of ISPs have broken ipv6 connectivity for (ptr = result; ptr != NULL; ptr = ptr->ai_next) { - if (ptr->ai_family == AF_INET || ptr->ai_family == AF_INET6) + if (ptr->ai_family == AF_INET) { memcpy(service, ptr->ai_addr, ptr->ai_addrlen); *addrlen = (socklen_t)ptr->ai_addrlen; @@ -706,6 +706,19 @@ add_addr_info(struct sockaddr_storage *service, socklen_t *addrlen, AVal *host, } } + if (!*addrlen) + { + for (ptr = result; ptr != NULL; ptr = ptr->ai_next) + { + if (ptr->ai_family == AF_INET6) + { + memcpy(service, ptr->ai_addr, ptr->ai_addrlen); + *addrlen = (socklen_t)ptr->ai_addrlen; + break; + } + } + } + freeaddrinfo(result); if (service->ss_family == AF_UNSPEC || *addrlen == 0) @@ -3091,7 +3104,10 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) { RTMP_Log(RTMP_LOGERROR, "rtmp server requested close"); RTMP_Close(r); -#if defined(CRYPTO) || defined(USE_ONLY_MD5) + + // disabled this for now, if the server sends an rtmp close message librtmp + // will enter an infinite loop here until stack is exhausted. +#if 0 && (defined(CRYPTO) || defined(USE_ONLY_MD5)) if ((r->Link.protocol & RTMP_FEATURE_WRITE) && !(r->Link.pFlags & RTMP_PUB_CLEAN) && ( !(r->Link.pFlags & RTMP_PUB_NAME) || diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index 822f10efe681de..179bee53767def 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -42,11 +42,23 @@ #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) #define OPT_DROP_THRESHOLD "drop_threshold_ms" +#define OPT_PFRAME_DROP_THRESHOLD "pframe_drop_threshold_ms" #define OPT_MAX_SHUTDOWN_TIME_SEC "max_shutdown_time_sec" #define OPT_BIND_IP "bind_ip" //#define TEST_FRAMEDROPS +#ifdef TEST_FRAMEDROPS + +#define DROPTEST_MAX_KBPS 3000 +#define DROPTEST_MAX_BYTES (DROPTEST_MAX_KBPS * 1000 / 8) + +struct droptest_info { + uint64_t ts; + size_t size; +}; +#endif + struct rtmp_stream { obs_output_t *output; @@ -66,6 +78,7 @@ struct rtmp_stream { os_sem_t *send_sem; os_event_t *stop_event; uint64_t stop_ts; + uint64_t shutdown_timeout_ts; struct dstr path, key; struct dstr username, password; @@ -75,6 +88,8 @@ struct rtmp_stream { /* frame drop variables */ int64_t drop_threshold_usec; int64_t min_drop_dts_usec; + int64_t pframe_drop_threshold_usec; + int64_t pframe_min_drop_dts_usec; int min_priority; int64_t last_dts_usec; @@ -82,6 +97,11 @@ struct rtmp_stream { uint64_t total_bytes_sent; int dropped_frames; +#ifdef TEST_FRAMEDROPS + struct circlebuf droptest_info; + size_t droptest_size; +#endif + RTMP rtmp; }; @@ -172,6 +192,9 @@ static void rtmp_stream_destroy(void *data) os_sem_destroy(stream->send_sem); pthread_mutex_destroy(&stream->packets_mutex); circlebuf_free(&stream->packets); +#ifdef TEST_FRAMEDROPS + circlebuf_free(&stream->droptest_info); +#endif bfree(stream); } } @@ -203,7 +226,7 @@ static void rtmp_stream_stop(void *data, uint64_t ts) { struct rtmp_stream *stream = data; - if (stopping(stream)) + if (stopping(stream) && ts != 0) return; if (connecting(stream)) @@ -212,6 +235,10 @@ static void rtmp_stream_stop(void *data, uint64_t ts) stream->stop_ts = ts / 1000ULL; os_event_signal(stream->stop_event); + if (ts) + stream->shutdown_timeout_ts = ts + + (uint64_t)stream->max_shutdown_time_sec * 1000000000ULL; + if (active(stream)) { if (stream->stop_ts == 0) os_sem_post(stream->send_sem); @@ -285,6 +312,40 @@ static bool discard_recv_data(struct rtmp_stream *stream, size_t size) return true; } +#ifdef TEST_FRAMEDROPS +static void droptest_cap_data_rate(struct rtmp_stream *stream, size_t size) +{ + uint64_t ts = os_gettime_ns(); + struct droptest_info info; + + info.ts = ts; + info.size = size; + + circlebuf_push_back(&stream->droptest_info, &info, sizeof(info)); + stream->droptest_size += size; + + if (stream->droptest_info.size) { + circlebuf_peek_front(&stream->droptest_info, + &info, sizeof(info)); + + if (stream->droptest_size > DROPTEST_MAX_BYTES) { + uint64_t elapsed = ts - info.ts; + + if (elapsed < 1000000000ULL) { + elapsed = 1000000000ULL - elapsed; + os_sleepto_ns(ts + elapsed); + } + + while (stream->droptest_size > DROPTEST_MAX_BYTES) { + circlebuf_pop_front(&stream->droptest_info, + &info, sizeof(info)); + stream->droptest_size -= info.size; + } + } + } +} +#endif + static int send_packet(struct rtmp_stream *stream, struct encoder_packet *packet, bool is_header, size_t idx) { @@ -306,9 +367,11 @@ static int send_packet(struct rtmp_stream *stream, } flv_packet_mux(packet, &data, &size, is_header); + #ifdef TEST_FRAMEDROPS - os_sleep_ms(rand() % 40); + droptest_cap_data_rate(stream, size); #endif + ret = RTMP_Write(&stream->rtmp, (char*)data, (int)size, (int)idx); bfree(data); @@ -320,6 +383,19 @@ static int send_packet(struct rtmp_stream *stream, static inline bool send_headers(struct rtmp_stream *stream); +static inline bool can_shutdown_stream(struct rtmp_stream *stream, + struct encoder_packet *packet) +{ + uint64_t cur_time = os_gettime_ns(); + bool timeout = cur_time >= stream->shutdown_timeout_ts; + + if (timeout) + info("Stream shutdown timeout reached (%d second(s))", + stream->max_shutdown_time_sec); + + return timeout || packet->sys_dts_usec >= (int64_t)stream->stop_ts; +} + static void *send_thread(void *data) { struct rtmp_stream *stream = data; @@ -337,7 +413,7 @@ static void *send_thread(void *data) continue; if (stopping(stream)) { - if (packet.sys_dts_usec >= (int64_t)stream->stop_ts) { + if (can_shutdown_stream(stream, &packet)) { obs_free_encoder_packet(&packet); break; } @@ -586,18 +662,7 @@ static int try_connect(struct rtmp_stream *stream) RTMP_EnableWrite(&stream->rtmp); - dstr_copy(&stream->encoder_name, "FMLE/3.0 (compatible; obs-studio/"); - -#ifdef HAVE_OBSCONFIG_H - dstr_cat(&stream->encoder_name, OBS_VERSION); -#else - dstr_catf(&stream->encoder_name, "%d.%d.%d", - LIBOBS_API_MAJOR_VER, - LIBOBS_API_MINOR_VER, - LIBOBS_API_PATCH_VER); -#endif - - dstr_cat(&stream->encoder_name, "; FMSc/1.0)"); + dstr_copy(&stream->encoder_name, "FMLE/3.0 (compatible; FMSc/1.0)"); set_rtmp_dstr(&stream->rtmp.Link.pubUser, &stream->username); set_rtmp_dstr(&stream->rtmp.Link.pubPasswd, &stream->password); @@ -652,6 +717,8 @@ static bool init_connect(struct rtmp_stream *stream) obs_service_t *service; obs_data_t *settings; const char *bind_ip; + int64_t drop_p; + int64_t drop_b; if (stopping(stream)) pthread_join(stream->send_thread, NULL); @@ -675,11 +742,17 @@ static bool init_connect(struct rtmp_stream *stream) dstr_copy(&stream->password, obs_service_get_password(service)); dstr_depad(&stream->path); dstr_depad(&stream->key); - stream->drop_threshold_usec = - (int64_t)obs_data_get_int(settings, OPT_DROP_THRESHOLD) * 1000; + drop_b = (int64_t)obs_data_get_int(settings, OPT_DROP_THRESHOLD); + drop_p = (int64_t)obs_data_get_int(settings, OPT_PFRAME_DROP_THRESHOLD); stream->max_shutdown_time_sec = (int)obs_data_get_int(settings, OPT_MAX_SHUTDOWN_TIME_SEC); + if (drop_p < (drop_b + 200)) + drop_p = drop_b + 200; + + stream->drop_threshold_usec = 1000 * drop_b; + stream->pframe_drop_threshold_usec = 1000 * drop_p; + bind_ip = obs_data_get_string(settings, OPT_BIND_IP); dstr_copy(&stream->bind_ip, bind_ip); @@ -741,14 +814,18 @@ static inline size_t num_buffered_packets(struct rtmp_stream *stream) return stream->packets.size / sizeof(struct encoder_packet); } -static void drop_frames(struct rtmp_stream *stream) +static void drop_frames(struct rtmp_stream *stream, const char *name, + int highest_priority, int64_t *p_min_dts_usec) { struct circlebuf new_buf = {0}; - int drop_priority = 0; uint64_t last_drop_dts_usec = 0; int num_frames_dropped = 0; - debug("Previous packet count: %d", (int)num_buffered_packets(stream)); +#ifdef _DEBUG + int start_packets = (int)num_buffered_packets(stream); +#else + UNUSED_PARAMETER(name); +#endif circlebuf_reserve(&new_buf, sizeof(struct encoder_packet) * 8); @@ -760,56 +837,71 @@ static void drop_frames(struct rtmp_stream *stream) /* do not drop audio data or video keyframes */ if (packet.type == OBS_ENCODER_AUDIO || - packet.drop_priority == OBS_NAL_PRIORITY_HIGHEST) { + packet.drop_priority >= highest_priority) { circlebuf_push_back(&new_buf, &packet, sizeof(packet)); } else { - if (drop_priority < packet.drop_priority) - drop_priority = packet.drop_priority; - num_frames_dropped++; obs_free_encoder_packet(&packet); } } circlebuf_free(&stream->packets); - stream->packets = new_buf; - stream->min_priority = drop_priority; - stream->min_drop_dts_usec = last_drop_dts_usec; + stream->packets = new_buf; + + if (stream->min_priority < highest_priority) + stream->min_priority = highest_priority; + + *p_min_dts_usec = last_drop_dts_usec; stream->dropped_frames += num_frames_dropped; - debug("New packet count: %d", (int)num_buffered_packets(stream)); +#ifdef _DEBUG + debug("Dropped %s, prev packet count: %d, new packet count: %d", + name, + start_packets, + (int)num_buffered_packets(stream)); +#endif } -static void check_to_drop_frames(struct rtmp_stream *stream) +static void check_to_drop_frames(struct rtmp_stream *stream, bool pframes) { struct encoder_packet first; int64_t buffer_duration_usec; - - if (num_buffered_packets(stream) < 5) + size_t num_packets = num_buffered_packets(stream); + const char *name = pframes ? "p-frames" : "b-frames"; + int priority = pframes ? + OBS_NAL_PRIORITY_HIGHEST : OBS_NAL_PRIORITY_HIGH; + int64_t *p_min_dts_usec = pframes ? + &stream->pframe_min_drop_dts_usec : + &stream->min_drop_dts_usec; + int64_t drop_threshold = pframes ? + stream->pframe_drop_threshold_usec : + stream->drop_threshold_usec; + + if (num_packets < 5) return; circlebuf_peek_front(&stream->packets, &first, sizeof(first)); /* do not drop frames if frames were just dropped within this time */ - if (first.dts_usec < stream->min_drop_dts_usec) + if (first.dts_usec < *p_min_dts_usec) return; /* if the amount of time stored in the buffered packets waiting to be * sent is higher than threshold, drop frames */ buffer_duration_usec = stream->last_dts_usec - first.dts_usec; - if (buffer_duration_usec > stream->drop_threshold_usec) { - drop_frames(stream); - debug("dropping %" PRId64 " worth of frames", - buffer_duration_usec); + if (buffer_duration_usec > drop_threshold) { + debug("buffer_duration_usec: %lld", buffer_duration_usec); + drop_frames(stream, name, priority, p_min_dts_usec); } } static bool add_video_packet(struct rtmp_stream *stream, struct encoder_packet *packet) { - check_to_drop_frames(stream); + check_to_drop_frames(stream, false); + check_to_drop_frames(stream, true); /* if currently dropping frames, drop packets until it reaches the * desired priority */ @@ -855,8 +947,9 @@ static void rtmp_stream_data(void *data, struct encoder_packet *packet) static void rtmp_stream_defaults(obs_data_t *defaults) { - obs_data_set_default_int(defaults, OPT_DROP_THRESHOLD, 600); - obs_data_set_default_int(defaults, OPT_MAX_SHUTDOWN_TIME_SEC, 5); + obs_data_set_default_int(defaults, OPT_DROP_THRESHOLD, 500); + obs_data_set_default_int(defaults, OPT_PFRAME_DROP_THRESHOLD, 800); + obs_data_set_default_int(defaults, OPT_MAX_SHUTDOWN_TIME_SEC, 30); obs_data_set_default_string(defaults, OPT_BIND_IP, "default"); } diff --git a/plugins/obs-qsv11/data/locale/pt-BR.ini b/plugins/obs-qsv11/data/locale/pt-BR.ini index 80c0aae026b7c4..1d3dd63e6bed88 100644 --- a/plugins/obs-qsv11/data/locale/pt-BR.ini +++ b/plugins/obs-qsv11/data/locale/pt-BR.ini @@ -4,7 +4,7 @@ MaxBitrate="Taxa de Bits Máxima" RateControl="Controle da Taxa de Bits" KeyframeIntervalSec="Intervalo de Keyframe (segundos, 0=auto)" Profile="Perfil" -AsyncDepth="Profundidade do Async" +AsyncDepth="Profundidade assíncrona" Accuracy="Precisão" Convergence="Convergência" ICQQuality="Qualidade ICQ" diff --git a/plugins/obs-text/CMakeLists.txt b/plugins/obs-text/CMakeLists.txt new file mode 100644 index 00000000000000..973f2a7ee20e90 --- /dev/null +++ b/plugins/obs-text/CMakeLists.txt @@ -0,0 +1,21 @@ +if (NOT WIN32) + return() +endif() + +project(obs-text) + +if(WIN32) + set(obs-text_PLATFORM_SOURCES + gdiplus/obs-text.cpp) + set(obs-text_PLATFORM_DEPS + gdiplus) +endif() + +add_library(obs-text MODULE + ${obs-text_PLATFORM_SOURCES} + ${obs-text_PLATFORM_HEADERS}) +target_link_libraries(obs-text + libobs + ${obs-text_PLATFORM_DEPS}) + +install_obs_plugin_with_data(obs-text data) diff --git a/plugins/obs-text/data/locale/ca-ES.ini b/plugins/obs-text/data/locale/ca-ES.ini new file mode 100644 index 00000000000000..35586b5574c565 --- /dev/null +++ b/plugins/obs-text/data/locale/ca-ES.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Text (GDI+)" +Font="Font" +Text="Text" +ReadFromFile="Llegeix del fitxer" +TextFile="Fitxer de text (UTF-8)" +Filter.TextFiles="Arxius de text" +Filter.AllFiles="Tots els fitxers" +Color="Color" +Opacity="Opacitat" +BkColor="Color de fons" +BkOpacity="Opacitat del fons" +Alignment="Alineació" +Alignment.Left="Esquerra" +Alignment.Center="Centre" +Alignment.Right="Dreta" +Vertical="Vertical" +VerticalAlignment="Alineació vertical" +VerticalAlignment.Top="Superior" +VerticalAlignment.Bottom="Inferior" +Outline="Contorn" +Outline.Size="Mida del contorn" +Outline.Color="Color del contorn" +Outline.Opacity="Opacitat del contorn" +ChatlogMode="Mode xat" +ChatlogMode.Lines="Límit de la línia de xat" +UseCustomExtents="Utilitza extensions de text personalitzat" +UseCustomExtents.Wrap="Ajusta" +Width="Amplada" +Height="Alçada" + diff --git a/plugins/obs-text/data/locale/cs-CZ.ini b/plugins/obs-text/data/locale/cs-CZ.ini new file mode 100644 index 00000000000000..dd4bf7db3e26ed --- /dev/null +++ b/plugins/obs-text/data/locale/cs-CZ.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Text (GDI+)" +Font="Písmo" +Text="Text" +ReadFromFile="Ze souboru" +TextFile="Textový soubor (UTF-8)" +Filter.TextFiles="Textové soubory" +Filter.AllFiles="Všechny soubory" +Color="Barva" +Opacity="Krytí" +BkColor="Barva pozadí" +BkOpacity="Průhlednost pozadí" +Alignment="Zarovnání" +Alignment.Left="Vlevo" +Alignment.Center="Uprostřed" +Alignment.Right="Vpravo" +Vertical="Vertikálně" +VerticalAlignment="Vertikální zarovnání" +VerticalAlignment.Top="Nahoře" +VerticalAlignment.Bottom="Dole" +Outline="Obtáhnout" +Outline.Size="Síla obtažení" +Outline.Color="Barva obtažení" +Outline.Opacity="Krytí obtažení" +ChatlogMode="Režim chatu" +ChatlogMode.Lines="Limit řádků režimu chatu" +UseCustomExtents="Použít vlastní rozsah textu" +UseCustomExtents.Wrap="Zalomit" +Width="Šířka" +Height="Výška" + diff --git a/plugins/obs-text/data/locale/de-DE.ini b/plugins/obs-text/data/locale/de-DE.ini new file mode 100644 index 00000000000000..36f8d77efeeed7 --- /dev/null +++ b/plugins/obs-text/data/locale/de-DE.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Text (GDI+)" +Font="Schriftart" +Text="Text" +ReadFromFile="Aus Datei lesen" +TextFile="Textdatei (UTF-8)" +Filter.TextFiles="Textdateien" +Filter.AllFiles="Alle Dateien" +Color="Farbe" +Opacity="Deckkraft" +BkColor="Hintergrundfarbe" +BkOpacity="Hintergrunddeckkraft" +Alignment="Ausrichtung" +Alignment.Left="Linksbündig" +Alignment.Center="Zentriert" +Alignment.Right="Rechtsbündig" +Vertical="Vertikal" +VerticalAlignment="Vertikal ausrichten" +VerticalAlignment.Top="Oben" +VerticalAlignment.Bottom="Unten" +Outline="Kontur" +Outline.Size="Konturgröße" +Outline.Color="Konturfarbe" +Outline.Opacity="Deckkraft der Kontur" +ChatlogMode="Chatlog-Modus" +ChatlogMode.Lines="Chatlog Zeilenlimit" +UseCustomExtents="Nutze benutzerdefinierten Textbereich" +UseCustomExtents.Wrap="Umbruch" +Width="Breite" +Height="Höhe" + diff --git a/plugins/obs-text/data/locale/en-US.ini b/plugins/obs-text/data/locale/en-US.ini new file mode 100644 index 00000000000000..d048ce794fb14a --- /dev/null +++ b/plugins/obs-text/data/locale/en-US.ini @@ -0,0 +1,33 @@ +TextGDIPlus="Text (GDI+)" +Font="Font" +Text="Text" +ReadFromFile="Read from file" +TextFile="Text File (UTF-8)" +Filter.TextFiles="Text Files" +Filter.AllFiles="All Files" +Color="Color" +Opacity="Opacity" +Gradient="Gradient" +Gradient.Color="Gradient Color" +Gradient.Opacity="Gradient Opacity" +Gradient.Direction="Gradient Direction" +BkColor="Background Color" +BkOpacity="Background Opacity" +Alignment="Alignment" +Alignment.Left="Left" +Alignment.Center="Center" +Alignment.Right="Right" +Vertical="Vertical" +VerticalAlignment="Vertical Alignment" +VerticalAlignment.Top="Top" +VerticalAlignment.Bottom="Bottom" +Outline="Outline" +Outline.Size="Outline Size" +Outline.Color="Outline Color" +Outline.Opacity="Outline Opacity" +ChatlogMode="Chatlog Mode" +ChatlogMode.Lines="Chatlog Line Limit" +UseCustomExtents="Use Custom Text Extents" +UseCustomExtents.Wrap="Wrap" +Width="Width" +Height="Height" diff --git a/plugins/obs-text/data/locale/es-ES.ini b/plugins/obs-text/data/locale/es-ES.ini new file mode 100644 index 00000000000000..2332182a2e3acd --- /dev/null +++ b/plugins/obs-text/data/locale/es-ES.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Texto (GDI+)" +Font="Fuente" +Text="Texto" +ReadFromFile="Leer desde archivo" +TextFile="Archivo de texto (UTF-8)" +Filter.TextFiles="Archivos de texto" +Filter.AllFiles="Todos los archivos" +Color="Color" +Opacity="Opacidad" +BkColor="Color del fondo" +BkOpacity="Opacidad del fondo" +Alignment="Alineamiento" +Alignment.Left="Izquierda" +Alignment.Center="Centrado" +Alignment.Right="Derecha" +Vertical="Vertical" +VerticalAlignment="Alineación vertical" +VerticalAlignment.Top="Arriba" +VerticalAlignment.Bottom="Abajo" +Outline="Contorno" +Outline.Size="Tamaño del borde" +Outline.Color="Color del borde" +Outline.Opacity="Opacidad del borde" +ChatlogMode="Modo chat" +ChatlogMode.Lines="Límite de la línea de chat" +UseCustomExtents="Usar extensiones de texto personalizado" +UseCustomExtents.Wrap="Ajustar" +Width="Ancho" +Height="Alto" + diff --git a/plugins/obs-text/data/locale/eu-ES.ini b/plugins/obs-text/data/locale/eu-ES.ini new file mode 100644 index 00000000000000..3a75a66fa65978 --- /dev/null +++ b/plugins/obs-text/data/locale/eu-ES.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Testua (GDI+)" +Font="Letra-tipoa" +Text="Testua" +ReadFromFile="Irakurri fitxategitik" +TextFile="Testu fitxategia (UTF-8)" +Filter.TextFiles="Testu fitxategiak" +Filter.AllFiles="Fitxategi guztiak" +Color="Kolorea" +Opacity="Opakutasuna" +BkColor="Atzeko planoaren kolorea" +BkOpacity="Atzeko planoaren opakutasuna" +Alignment="Lerrokatzea" +Alignment.Left="Ezkerrean" +Alignment.Center="Erdian" +Alignment.Right="Eskuinean" +Vertical="Bertikala" +VerticalAlignment="Lerrokatze bertikala" +VerticalAlignment.Top="Goian" +VerticalAlignment.Bottom="Behean" +Outline="Ertza" +Outline.Size="Ertzaren zabalera" +Outline.Color="Ertzaren kolorea" +Outline.Opacity="Ertzaren opakutasuna" +ChatlogMode="Berriketa modua" +ChatlogMode.Lines="Berriketa lerroaren limitea" +UseCustomExtents="Erabili testu hedapen pertsonalak" +UseCustomExtents.Wrap="Egokitu" +Width="Zabalera" +Height="Altuera" + diff --git a/plugins/obs-text/data/locale/fi-FI.ini b/plugins/obs-text/data/locale/fi-FI.ini new file mode 100644 index 00000000000000..912a4e14d4d26a --- /dev/null +++ b/plugins/obs-text/data/locale/fi-FI.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Teksti (GDI+)" +Font="Fontti" +Text="Teksti" +ReadFromFile="Lue tiedostosta" +TextFile="Tekstitiedosto (UTF-8)" +Filter.TextFiles="Tekstitiedostot" +Filter.AllFiles="Kaikki tiedostot" +Color="Väri" +Opacity="Läpinäkyvyys" +BkColor="Taustaväri" +BkOpacity="Taustan läpinäkyvyys" +Alignment="Sijoittelu" +Alignment.Left="Vasen" +Alignment.Center="Keskitetty" +Alignment.Right="Oikea" +Vertical="Pystysuunnassa" +VerticalAlignment="Pystysijoittelu" +VerticalAlignment.Top="Ylös" +VerticalAlignment.Bottom="Alas" +Outline="Reunaviiva" +Outline.Size="Reunaviivan koko" +Outline.Color="Reunaviivan väri" +Outline.Opacity="Reunaviivan läpinäkyvyys" +ChatlogMode="Chatlog-tila" +ChatlogMode.Lines="Chatlog riviraja" +UseCustomExtents="Käytä valinnaisia fonttilaajennuksia" +UseCustomExtents.Wrap="Sido" +Width="Leveys" +Height="Korkeus" + diff --git a/plugins/obs-text/data/locale/fr-FR.ini b/plugins/obs-text/data/locale/fr-FR.ini new file mode 100644 index 00000000000000..9db7ed5d5cb4ac --- /dev/null +++ b/plugins/obs-text/data/locale/fr-FR.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Texte (GDI+)" +Font="Police" +Text="Texte" +ReadFromFile="Lire depuis un fichier" +TextFile="Fichier texte (UTF-8)" +Filter.TextFiles="Fichiers texte" +Filter.AllFiles="Tous les fichiers" +Color="Couleur" +Opacity="Opacité" +BkColor="Couleur de l'arrière-plan" +BkOpacity="Transparence de l'arrière-plan" +Alignment="Alignement" +Alignment.Left="À gauche" +Alignment.Center="Centré" +Alignment.Right="À droite" +Vertical="Vertical" +VerticalAlignment="Alignement vertical" +VerticalAlignment.Top="En haut" +VerticalAlignment.Bottom="En bas" +Outline="Contour" +Outline.Size="Épaisseur du contour" +Outline.Color="Couleur du contour" +Outline.Opacity="Opacité du contour" +ChatlogMode="Mode discussion" +ChatlogMode.Lines="Lignes de discussion" +UseCustomExtents="Utiliser une taille personnalisée" +UseCustomExtents.Wrap="Retour à la ligne automatique" +Width="Largeur" +Height="Hauteur" + diff --git a/plugins/obs-text/data/locale/hu-HU.ini b/plugins/obs-text/data/locale/hu-HU.ini new file mode 100644 index 00000000000000..e46508a303ccc7 --- /dev/null +++ b/plugins/obs-text/data/locale/hu-HU.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Szöveg (GDI+)" +Font="Betűtípus" +Text="Szöveg" +ReadFromFile="Fájlból olvasás" +TextFile="Szövegfájl (UTF-8)" +Filter.TextFiles="Szöveges fájlok" +Filter.AllFiles="Minden fájl" +Color="Szín" +Opacity="Áttetszőség" +BkColor="Háttérszín" +BkOpacity="Háttér áttetszőség" +Alignment="Pozíció" +Alignment.Left="Balra" +Alignment.Center="Középre" +Alignment.Right="Jobbra" +Vertical="Függőleges" +VerticalAlignment="Függőleges igazítás" +VerticalAlignment.Top="Fent" +VerticalAlignment.Bottom="Lent" +Outline="Körvonal" +Outline.Size="Körvonal mérete" +Outline.Color="Körvonal színe" +Outline.Opacity="Körvonal áttetszőség" +ChatlogMode="Csevegőnapló mód" +ChatlogMode.Lines="Csevegőnapló sorlimit" +UseCustomExtents="Egyedi szövegdoboz használata" +UseCustomExtents.Wrap="Sortörés" +Width="Szélesség" +Height="Magasság" + diff --git a/plugins/obs-text/data/locale/ja-JP.ini b/plugins/obs-text/data/locale/ja-JP.ini new file mode 100644 index 00000000000000..7797aa078f720f --- /dev/null +++ b/plugins/obs-text/data/locale/ja-JP.ini @@ -0,0 +1,30 @@ +TextGDIPlus="テキスト (GDI+)" +Font="フォント" +Text="テキスト" +ReadFromFile="ファイルからの読み取り" +TextFile="テキストファイル (UTF-8)" +Filter.TextFiles="テキストファイル" +Filter.AllFiles="すべてのファイル" +Color="色" +Opacity="不透明度" +BkColor="背景色" +BkOpacity="背景の不透明度" +Alignment="位置揃え" +Alignment.Left="左" +Alignment.Center="中央" +Alignment.Right="右" +Vertical="垂直方向" +VerticalAlignment="垂直位置揃え" +VerticalAlignment.Top="上" +VerticalAlignment.Bottom="下" +Outline="輪郭" +Outline.Size="輪郭のサイズ" +Outline.Color="輪郭の色" +Outline.Opacity="輪郭の不透明度" +ChatlogMode="チャットログモード" +ChatlogMode.Lines="チャットログ行の制限" +UseCustomExtents="テキスト領域の範囲を指定する" +UseCustomExtents.Wrap="折り返す" +Width="幅" +Height="高さ" + diff --git a/plugins/obs-text/data/locale/ko-KR.ini b/plugins/obs-text/data/locale/ko-KR.ini new file mode 100644 index 00000000000000..2547c759c2be78 --- /dev/null +++ b/plugins/obs-text/data/locale/ko-KR.ini @@ -0,0 +1,30 @@ +TextGDIPlus="텍스트 (GDI+)" +Font="글꼴" +Text="텍스트" +ReadFromFile="파일에서 불러들이기" +TextFile="텍스트 파일 (UTF-8)" +Filter.TextFiles="텍스트 파일" +Filter.AllFiles="모든 파일" +Color="색" +Opacity="투명도" +BkColor="배경 색상" +BkOpacity="배경 불투명도" +Alignment="정렬" +Alignment.Left="왼쪽" +Alignment.Center="가운데" +Alignment.Right="오른쪽" +Vertical="수직" +VerticalAlignment="수직 정렬" +VerticalAlignment.Top="위" +VerticalAlignment.Bottom="아래" +Outline="외곽선" +Outline.Size="외곽선 크기" +Outline.Color="외곽선 색" +Outline.Opacity="외곽선 투명도" +ChatlogMode="채팅기록 모드" +ChatlogMode.Lines="채팅기록 줄 제한" +UseCustomExtents="사용자 정의 텍스트 설정" +UseCustomExtents.Wrap="자동 줄 바꿈" +Width="너비" +Height="높이" + diff --git a/plugins/obs-text/data/locale/nl-NL.ini b/plugins/obs-text/data/locale/nl-NL.ini new file mode 100644 index 00000000000000..dc939047c50a1f --- /dev/null +++ b/plugins/obs-text/data/locale/nl-NL.ini @@ -0,0 +1,28 @@ +TextGDIPlus="Tekst (GDI+)" +Font="Lettertype" +Text="Tekst" +ReadFromFile="Lees uit bestand" +TextFile="Tekstbestand (UTF-8)" +Filter.TextFiles="Tekstbestanden" +Filter.AllFiles="Alle bestanden" +Color="Kleur" +Opacity="Dekking" +Alignment="Uitlijning" +Alignment.Left="Links" +Alignment.Center="Midden" +Alignment.Right="Rechts" +Vertical="Verticaal" +VerticalAlignment="Verticale uitlijning" +VerticalAlignment.Top="Boven" +VerticalAlignment.Bottom="Onder" +Outline="Contour" +Outline.Size="Contourgrootte" +Outline.Color="Contourkleur" +Outline.Opacity="Contourdekking" +ChatlogMode="Chatlogmodus" +ChatlogMode.Lines="Chatlog regel-limiet" +UseCustomExtents="Aangepaste tekst-extents gebruiken" +UseCustomExtents.Wrap="Terugloop" +Width="Breedte" +Height="Hoogte" + diff --git a/plugins/obs-text/data/locale/pl-PL.ini b/plugins/obs-text/data/locale/pl-PL.ini new file mode 100644 index 00000000000000..6298b53682aed9 --- /dev/null +++ b/plugins/obs-text/data/locale/pl-PL.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Tekst (GDI +)" +Font="Czcionka" +Text="Tekst" +ReadFromFile="Czytaj z pliku" +TextFile="Plik tekstowy (UTF-8)" +Filter.TextFiles="Pliki tekstowe" +Filter.AllFiles="Wszystkie pliki" +Color="Kolor" +Opacity="Przezroczystość" +BkColor="Kolor tła" +BkOpacity="Przezroczystość tła" +Alignment="Wyrównanie" +Alignment.Left="Do lewej" +Alignment.Center="Wyśrodkuj" +Alignment.Right="Do prawej" +Vertical="Pionowo" +VerticalAlignment="Wyrównanie w pionie" +VerticalAlignment.Top="Do góry" +VerticalAlignment.Bottom="Do dołu" +Outline="Kontur" +Outline.Size="Rozmiar konturu" +Outline.Color="Kolor konturu" +Outline.Opacity="Przezroczystość konturu" +ChatlogMode="Tryb podglądu czatu" +ChatlogMode.Lines="Limit linii czatu" +UseCustomExtents="Użyj niestandardowego zakresu tekstu" +UseCustomExtents.Wrap="Zawiń" +Width="Szerokość" +Height="Wysokość" + diff --git a/plugins/obs-text/data/locale/ru-RU.ini b/plugins/obs-text/data/locale/ru-RU.ini new file mode 100644 index 00000000000000..04d81ce0f3471e --- /dev/null +++ b/plugins/obs-text/data/locale/ru-RU.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Текст (GDI+)" +Font="Шрифт" +Text="Текст" +ReadFromFile="Чтение из файла" +TextFile="Текстовый файл (UTF-8)" +Filter.TextFiles="Текстовые файлы" +Filter.AllFiles="Все файлы" +Color="Цвет" +Opacity="Непрозрачность" +BkColor="Цвет фона" +BkOpacity="Непрозрачность фона" +Alignment="Выравнивание" +Alignment.Left="По левому краю" +Alignment.Center="По центру" +Alignment.Right="По правому краю" +Vertical="Вертикальный" +VerticalAlignment="Вертикальное выравнивание" +VerticalAlignment.Top="Сверху" +VerticalAlignment.Bottom="Снизу" +Outline="Обводка" +Outline.Size="Размер обводки" +Outline.Color="Цвет обводки" +Outline.Opacity="Непрозрачность контура" +ChatlogMode="Режим чат-лога" +ChatlogMode.Lines="Лимит кол-ва строк чата" +UseCustomExtents="Свои размеры текстового поля" +UseCustomExtents.Wrap="Перенос строк" +Width="Ширина" +Height="Высота" + diff --git a/plugins/obs-text/data/locale/sv-SE.ini b/plugins/obs-text/data/locale/sv-SE.ini new file mode 100644 index 00000000000000..70fa8b9d39df3e --- /dev/null +++ b/plugins/obs-text/data/locale/sv-SE.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Text (GDI+)" +Font="Typsnitt" +Text="Text" +ReadFromFile="Läs från fil" +TextFile="Textfil (UTF-8)" +Filter.TextFiles="Textfiler" +Filter.AllFiles="Alla filer" +Color="Färg" +Opacity="Opacitet" +BkColor="Bakgrundsfärg" +BkOpacity="Bakgrundsopacitet" +Alignment="Justering" +Alignment.Left="Vänster" +Alignment.Center="Centrerad" +Alignment.Right="Höger" +Vertical="Vertikal" +VerticalAlignment="Vertikal justering" +VerticalAlignment.Top="Överkant" +VerticalAlignment.Bottom="Nederkant" +Outline="Kontur" +Outline.Size="Konturstorlek" +Outline.Color="Konturfärg" +Outline.Opacity="Konturopacitet" +ChatlogMode="Chattloggsläge" +ChatlogMode.Lines="Radgräns för chattlogg" +UseCustomExtents="Använd anpassade textmått" +UseCustomExtents.Wrap="Radbryt" +Width="Bredd" +Height="Höjd" + diff --git a/plugins/obs-text/data/locale/uk-UA.ini b/plugins/obs-text/data/locale/uk-UA.ini new file mode 100644 index 00000000000000..1ea325ccb37bcd --- /dev/null +++ b/plugins/obs-text/data/locale/uk-UA.ini @@ -0,0 +1,30 @@ +TextGDIPlus="Текст (GDI+)" +Font="Шрифт" +Text="Текст" +ReadFromFile="Текст з файлу" +TextFile="Текстовий файл (UTF-8)" +Filter.TextFiles="Текстові файли" +Filter.AllFiles="Всі файли" +Color="Колір" +Opacity="Непрозорість" +BkColor="Колір фону" +BkOpacity="Непрозорість фону" +Alignment="Вирівнювання" +Alignment.Left="Ліворуч" +Alignment.Center="Центр" +Alignment.Right="Праворуч" +Vertical="Вертикально" +VerticalAlignment="Вертикальне вирівнювання" +VerticalAlignment.Top="Вверху" +VerticalAlignment.Bottom="Внизу" +Outline="Контур" +Outline.Size="Розмір контуру" +Outline.Color="Колір контуру" +Outline.Opacity="Непрозорість контуру" +ChatlogMode="Режим чат-журналу" +ChatlogMode.Lines="Кількість рядків чат-журналу" +UseCustomExtents="Особливі властивості текстового блоку" +UseCustomExtents.Wrap="Перенос слів" +Width="Ширина" +Height="Висота" + diff --git a/plugins/obs-text/data/locale/zh-CN.ini b/plugins/obs-text/data/locale/zh-CN.ini new file mode 100644 index 00000000000000..d86290e69e060b --- /dev/null +++ b/plugins/obs-text/data/locale/zh-CN.ini @@ -0,0 +1,30 @@ +TextGDIPlus="文本 (GDI+)" +Font="字体" +Text="文本" +ReadFromFile="从文件读取" +TextFile="文本文件 (UTF-8)" +Filter.TextFiles="文本文件" +Filter.AllFiles="所有文件" +Color="色彩" +Opacity="不透明度" +BkColor="背景颜色" +BkOpacity="背景不透明度" +Alignment="对齐" +Alignment.Left="靠左对齐" +Alignment.Center="居中" +Alignment.Right="靠右对齐" +Vertical="垂直移动" +VerticalAlignment="垂直对齐" +VerticalAlignment.Top="顶部" +VerticalAlignment.Bottom="底部" +Outline="轮廓" +Outline.Size="轮廓大小" +Outline.Color="轮廓颜色" +Outline.Opacity="轮廓不透明度" +ChatlogMode="聊天模式" +ChatlogMode.Lines="聊天行限制" +UseCustomExtents="使用自定义文本区" +UseCustomExtents.Wrap="自动换行" +Width="宽度" +Height="高度" + diff --git a/plugins/obs-text/data/locale/zh-TW.ini b/plugins/obs-text/data/locale/zh-TW.ini new file mode 100644 index 00000000000000..b883e91efef2bd --- /dev/null +++ b/plugins/obs-text/data/locale/zh-TW.ini @@ -0,0 +1,28 @@ +TextGDIPlus="文字 (GDI+)" +Font="字型" +Text="文字" +ReadFromFile="從檔案讀取" +TextFile="文字檔 (UTF-8)" +Filter.TextFiles="文字檔案" +Filter.AllFiles="所有檔案" +Color="顏色" +Opacity="不透明度" +Alignment="對齊方式" +Alignment.Left="靠左對齊" +Alignment.Center="置中" +Alignment.Right="靠右對齊" +Vertical="垂直" +VerticalAlignment="垂直對齊" +VerticalAlignment.Top="向上對齊" +VerticalAlignment.Bottom="向下對齊" +Outline="外框" +Outline.Size="外框大小" +Outline.Color="外框颜色" +Outline.Opacity="外框不透明度" +ChatlogMode="聊天紀錄模式" +ChatlogMode.Lines="聊天紀錄行數" +UseCustomExtents="使用自動文字區塊大小" +UseCustomExtents.Wrap="自動換行" +Width="寬度" +Height="高度" + diff --git a/plugins/obs-text/gdiplus/obs-text.cpp b/plugins/obs-text/gdiplus/obs-text.cpp new file mode 100644 index 00000000000000..2878b0841d1fac --- /dev/null +++ b/plugins/obs-text/gdiplus/obs-text.cpp @@ -0,0 +1,1025 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace Gdiplus; + +#define warning(format, ...) blog(LOG_WARNING, "[%s] " format, \ + obs_source_get_name(source), ##__VA_ARGS__) + +#define warn_stat(call) \ + do { \ + if (stat != Ok) \ + warning("%s: %s failed (%d)", __FUNCTION__, call, \ + (int)stat); \ + } while (false) + +#ifndef clamp +#define clamp(val, min_val, max_val) \ + if (val < min_val) val = min_val; \ + else if (val > max_val) val = max_val; +#endif + +#define MIN_SIZE_CX 2 +#define MIN_SIZE_CY 2 +#define MAX_SIZE_CX 16384 +#define MAX_SIZE_CY 16384 + +#define MAX_AREA (4096LL * 4096LL) + +/* ------------------------------------------------------------------------- */ + +#define S_FONT "font" +#define S_USE_FILE "read_from_file" +#define S_FILE "file" +#define S_TEXT "text" +#define S_COLOR "color" +#define S_GRADIENT "gradient" +#define S_GRADIENT_COLOR "gradient_color" +#define S_GRADIENT_DIR "gradient_dir" +#define S_GRADIENT_OPACITY "gradient_opacity" +#define S_ALIGN "align" +#define S_VALIGN "valign" +#define S_OPACITY "opacity" +#define S_BKCOLOR "bk_color" +#define S_BKOPACITY "bk_opacity" +#define S_VERTICAL "vertical" +#define S_OUTLINE "outline" +#define S_OUTLINE_SIZE "outline_size" +#define S_OUTLINE_COLOR "outline_color" +#define S_OUTLINE_OPACITY "outline_opacity" +#define S_CHATLOG_MODE "chatlog" +#define S_CHATLOG_LINES "chatlog_lines" +#define S_EXTENTS "extents" +#define S_EXTENTS_WRAP "extents_wrap" +#define S_EXTENTS_CX "extents_cx" +#define S_EXTENTS_CY "extents_cy" + +#define S_ALIGN_LEFT "left" +#define S_ALIGN_CENTER "center" +#define S_ALIGN_RIGHT "right" + +#define S_VALIGN_TOP "top" +#define S_VALIGN_CENTER S_ALIGN_CENTER +#define S_VALIGN_BOTTOM "bottom" + +#define T_(v) obs_module_text(v) +#define T_FONT T_("Font") +#define T_USE_FILE T_("ReadFromFile") +#define T_FILE T_("TextFile") +#define T_TEXT T_("Text") +#define T_COLOR T_("Color") +#define T_GRADIENT T_("Gradient") +#define T_GRADIENT_COLOR T_("Gradient.Color") +#define T_GRADIENT_DIR T_("Gradient.Direction") +#define T_GRADIENT_OPACITY T_("Gradient.Opacity") +#define T_ALIGN T_("Alignment") +#define T_VALIGN T_("VerticalAlignment") +#define T_OPACITY T_("Opacity") +#define T_BKCOLOR T_("BkColor") +#define T_BKOPACITY T_("BkOpacity") +#define T_VERTICAL T_("Vertical") +#define T_OUTLINE T_("Outline") +#define T_OUTLINE_SIZE T_("Outline.Size") +#define T_OUTLINE_COLOR T_("Outline.Color") +#define T_OUTLINE_OPACITY T_("Outline.Opacity") +#define T_CHATLOG_MODE T_("ChatlogMode") +#define T_CHATLOG_LINES T_("ChatlogMode.Lines") +#define T_EXTENTS T_("UseCustomExtents") +#define T_EXTENTS_WRAP T_("UseCustomExtents.Wrap") +#define T_EXTENTS_CX T_("Width") +#define T_EXTENTS_CY T_("Height") + +#define T_FILTER_TEXT_FILES T_("Filter.TextFiles") +#define T_FILTER_ALL_FILES T_("Filter.AllFiles") + +#define T_ALIGN_LEFT T_("Alignment.Left") +#define T_ALIGN_CENTER T_("Alignment.Center") +#define T_ALIGN_RIGHT T_("Alignment.Right") + +#define T_VALIGN_TOP T_("VerticalAlignment.Top") +#define T_VALIGN_CENTER T_ALIGN_CENTER +#define T_VALIGN_BOTTOM T_("VerticalAlignment.Bottom") + +/* ------------------------------------------------------------------------- */ + +static inline DWORD get_alpha_val(uint32_t opacity) +{ + return ((opacity * 255 / 100) & 0xFF) << 24; +} + +static inline DWORD calc_color(uint32_t color, uint32_t opacity) +{ + return color & 0xFFFFFF | get_alpha_val(opacity); +} + +static inline wstring to_wide(const char *utf8) +{ + wstring text; + + size_t len = os_utf8_to_wcs(utf8, 0, nullptr, 0); + text.resize(len); + if (len) + os_utf8_to_wcs(utf8, 0, &text[0], len + 1); + + return text; +} + +static inline uint32_t rgb_to_bgr(uint32_t rgb) +{ + return ((rgb & 0xFF) << 16) | (rgb & 0xFF00) | ((rgb & 0xFF0000) >> 16); +} + +/* ------------------------------------------------------------------------- */ + +template class GDIObj { + T obj = nullptr; + + inline GDIObj &Replace(T obj_) + { + if (obj) deleter(obj); + obj = obj_; + return *this; + } + +public: + inline GDIObj() {} + inline GDIObj(T obj_) : obj(obj_) {} + inline ~GDIObj() {deleter(obj);} + + inline T operator=(T obj_) {Replace(obj_); return obj;} + + inline operator T() const {return obj;} + + inline bool operator==(T obj_) const {return obj == obj_;} + inline bool operator!=(T obj_) const {return obj != obj_;} +}; + +using HDCObj = GDIObj; +using HFONTObj = GDIObj; +using HBITMAPObj = GDIObj; + +/* ------------------------------------------------------------------------- */ + +enum class Align { + Left, + Center, + Right +}; + +enum class VAlign { + Top, + Center, + Bottom +}; + +struct TextSource { + obs_source_t *source = nullptr; + + gs_texture_t *tex = nullptr; + uint32_t cx = 0; + uint32_t cy = 0; + + HDCObj hdc; + Graphics graphics; + + HFONTObj hfont; + unique_ptr font; + + bool read_from_file = false; + string file; + time_t file_timestamp = 0; + float update_time_elapsed = 0.0f; + + wstring text; + wstring face; + int face_size = 0; + uint32_t color = 0xFFFFFF; + uint32_t color2 = 0xFFFFFF; + float gradient_dir = 0; + uint32_t opacity = 100; + uint32_t opacity2 = 100; + uint32_t bk_color = 0; + uint32_t bk_opacity = 0; + Align align = Align::Left; + VAlign valign = VAlign::Top; + bool gradient = false; + bool bold = false; + bool italic = false; + bool underline = false; + bool strikeout = false; + bool vertical = false; + + bool use_outline = false; + float outline_size = 0.0f; + uint32_t outline_color = 0; + uint32_t outline_opacity = 100; + + bool use_extents = false; + bool wrap = false; + uint32_t extents_cx = 0; + uint32_t extents_cy = 0; + + bool chatlog_mode = false; + int chatlog_lines = 6; + + /* --------------------------- */ + + inline TextSource(obs_source_t *source_, obs_data_t *settings) : + source (source_), + hdc (CreateCompatibleDC(nullptr)), + graphics (hdc) + { + obs_source_update(source, settings); + } + + inline ~TextSource() + { + if (tex) { + obs_enter_graphics(); + gs_texture_destroy(tex); + obs_leave_graphics(); + } + } + + void UpdateFont(); + void GetStringFormat(StringFormat &format); + void RemoveNewlinePadding(const StringFormat &format, RectF &box); + void CalculateTextSizes(const StringFormat &format, + RectF &bounding_box, SIZE &text_size); + void RenderOutlineText(Graphics &graphics, + const GraphicsPath &path, + const Brush &brush); + void RenderText(); + void LoadFileText(); + + const char *GetMainString(const char *str); + + inline void Update(obs_data_t *settings); + inline void Tick(float seconds); + inline void Render(gs_effect_t *effect); +}; + +static time_t get_modified_timestamp(const char *filename) +{ + struct stat stats; + if (os_stat(filename, &stats) != 0) + return -1; + return stats.st_mtime; +} + +void TextSource::UpdateFont() +{ + hfont = nullptr; + font.reset(nullptr); + + LOGFONT lf = {}; + lf.lfHeight = face_size; + lf.lfWeight = bold ? FW_BOLD : FW_DONTCARE; + lf.lfItalic = italic; + lf.lfUnderline = underline; + lf.lfStrikeOut = strikeout; + lf.lfQuality = ANTIALIASED_QUALITY; + + if (!face.empty()) { + wcscpy(lf.lfFaceName, face.c_str()); + hfont = CreateFontIndirect(&lf); + } + + if (!hfont) { + wcscpy(lf.lfFaceName, L"Arial"); + hfont = CreateFontIndirect(&lf); + } + + if (hfont) + font.reset(new Font(hdc, hfont)); +} + +void TextSource::GetStringFormat(StringFormat &format) +{ + UINT flags = StringFormatFlagsNoFitBlackBox | + StringFormatFlagsMeasureTrailingSpaces; + + if (vertical) + flags |= StringFormatFlagsDirectionVertical | + StringFormatFlagsDirectionRightToLeft; + + format.SetFormatFlags(flags); + format.SetTrimming(StringTrimmingWord); + + switch (align) { + case Align::Left: + if (vertical) + format.SetLineAlignment(StringAlignmentFar); + else + format.SetAlignment(StringAlignmentNear); + break; + case Align::Center: + if (vertical) + format.SetLineAlignment(StringAlignmentCenter); + else + format.SetAlignment(StringAlignmentCenter); + break; + case Align::Right: + if (vertical) + format.SetLineAlignment(StringAlignmentNear); + else + format.SetAlignment(StringAlignmentFar); + } + + switch (valign) { + case VAlign::Top: + if (vertical) + format.SetAlignment(StringAlignmentNear); + else + format.SetLineAlignment(StringAlignmentNear); + break; + case VAlign::Center: + if (vertical) + format.SetAlignment(StringAlignmentCenter); + else + format.SetLineAlignment(StringAlignmentCenter); + break; + case VAlign::Bottom: + if (vertical) + format.SetAlignment(StringAlignmentFar); + else + format.SetLineAlignment(StringAlignmentFar); + } +} + +/* GDI+ treats '\n' as an extra character with an actual render size when + * calculating the texture size, so we have to calculate the size of '\n' to + * remove the padding. Because we always add a newline to the string, we + * also remove the extra unused newline. */ +void TextSource::RemoveNewlinePadding(const StringFormat &format, RectF &box) +{ + RectF before; + RectF after; + Status stat; + + stat = graphics.MeasureString(L"W", 2, font.get(), PointF(0.0f, 0.0f), + &format, &before); + warn_stat("MeasureString (without newline)"); + + stat = graphics.MeasureString(L"W\n", 3, font.get(), PointF(0.0f, 0.0f), + &format, &after); + warn_stat("MeasureString (with newline)"); + + float offset_cx = after.Width - before.Width; + float offset_cy = after.Height - before.Height; + + if (!vertical) { + if (offset_cx >= 1.0f) + offset_cx -= 1.0f; + + if (valign == VAlign::Center) + box.Y -= offset_cy * 0.5f; + else if (valign == VAlign::Bottom) + box.Y -= offset_cy; + } else { + if (offset_cy >= 1.0f) + offset_cy -= 1.0f; + + if (align == Align::Center) + box.X -= offset_cx * 0.5f; + else if (align == Align::Right) + box.X -= offset_cx; + } + + box.Width -= offset_cx; + box.Height -= offset_cy; +} + +void TextSource::CalculateTextSizes(const StringFormat &format, + RectF &bounding_box, SIZE &text_size) +{ + RectF layout_box; + RectF temp_box; + Status stat; + + if (!text.empty()) { + if (use_extents && wrap) { + layout_box.X = layout_box.Y = 0; + layout_box.Width = float(extents_cx); + layout_box.Height = float(extents_cy); + + if (use_outline) { + layout_box.Width -= outline_size; + layout_box.Height -= outline_size; + } + + stat = graphics.MeasureString(text.c_str(), + (int)text.size() + 1, font.get(), + layout_box, &format, + &bounding_box); + warn_stat("MeasureString (wrapped)"); + + temp_box = bounding_box; + } else { + stat = graphics.MeasureString(text.c_str(), + (int)text.size() + 1, font.get(), + PointF(0.0f, 0.0f), &format, + &bounding_box); + warn_stat("MeasureString (non-wrapped)"); + + temp_box = bounding_box; + + bounding_box.X = 0.0f; + bounding_box.Y = 0.0f; + + RemoveNewlinePadding(format, bounding_box); + + if (use_outline) { + bounding_box.Width += outline_size; + bounding_box.Height += outline_size; + } + } + } + + if (vertical) { + if (bounding_box.Width < face_size) { + text_size.cx = face_size; + bounding_box.Width = float(face_size); + } else { + text_size.cx = LONG(bounding_box.Width + EPSILON); + } + + text_size.cy = LONG(bounding_box.Height + EPSILON); + } else { + if (bounding_box.Height < face_size) { + text_size.cy = face_size; + bounding_box.Height = float(face_size); + } else { + text_size.cy = LONG(bounding_box.Height + EPSILON); + } + + text_size.cx = LONG(bounding_box.Width + EPSILON); + } + + if (use_extents) { + text_size.cx = extents_cx; + text_size.cy = extents_cy; + } + + text_size.cx += text_size.cx % 2; + text_size.cy += text_size.cy % 2; + + int64_t total_size = int64_t(text_size.cx) * int64_t(text_size.cy); + + /* GPUs typically have texture size limitations */ + clamp(text_size.cx, MIN_SIZE_CX, MAX_SIZE_CX); + clamp(text_size.cy, MIN_SIZE_CY, MAX_SIZE_CY); + + /* avoid taking up too much VRAM */ + if (total_size > MAX_AREA) { + if (text_size.cx > text_size.cy) + text_size.cx = (LONG)MAX_AREA / text_size.cy; + else + text_size.cy = (LONG)MAX_AREA / text_size.cx; + } + + /* the internal text-rendering bounding box for is reset to + * its internal value in case the texture gets cut off */ + bounding_box.Width = temp_box.Width; + bounding_box.Height = temp_box.Height; +} + +void TextSource::RenderOutlineText(Graphics &graphics, + const GraphicsPath &path, + const Brush &brush) +{ + DWORD outline_rgba = calc_color(outline_color, outline_opacity); + Status stat; + + Pen pen(Color(outline_rgba), outline_size); + stat = pen.SetLineJoin(LineJoinRound); + warn_stat("pen.SetLineJoin"); + + stat = graphics.DrawPath(&pen, &path); + warn_stat("graphics.DrawPath"); + + stat = graphics.FillPath(&brush, &path); + warn_stat("graphics.FillPath"); +} + +void TextSource::RenderText() +{ + StringFormat format(StringFormat::GenericTypographic()); + Status stat; + + RectF box; + SIZE size; + + GetStringFormat(format); + CalculateTextSizes(format, box, size); + + unique_ptr bits(new uint8_t[size.cx * size.cy * 4]); + Bitmap bitmap(size.cx, size.cy, 4 * size.cx, PixelFormat32bppARGB, + bits.get()); + + Graphics graphics_bitmap(&bitmap); + LinearGradientBrush brush(RectF(0, 0, (float)size.cx, (float)size.cy), + Color(calc_color(color, opacity)), + Color(calc_color(color2, opacity2)), + gradient_dir, 1); + DWORD full_bk_color = bk_color & 0xFFFFFF; + + if (!text.empty() || use_extents) + full_bk_color |= get_alpha_val(bk_opacity); + + if ((size.cx > box.Width || size.cy > box.Height) && !use_extents) { + stat = graphics_bitmap.Clear(Color(0)); + warn_stat("graphics_bitmap.Clear"); + + SolidBrush bk_brush = Color(full_bk_color); + stat = graphics_bitmap.FillRectangle(&bk_brush, box); + warn_stat("graphics_bitmap.FillRectangle"); + } else { + stat = graphics_bitmap.Clear(Color(full_bk_color)); + warn_stat("graphics_bitmap.Clear"); + } + + graphics_bitmap.SetTextRenderingHint(TextRenderingHintAntiAlias); + graphics_bitmap.SetCompositingMode(CompositingModeSourceOver); + graphics_bitmap.SetSmoothingMode(SmoothingModeAntiAlias); + + if (!text.empty()) { + if (use_outline) { + box.Offset(outline_size / 2, outline_size / 2); + + FontFamily family; + GraphicsPath path; + + font->GetFamily(&family); + stat = path.AddString(text.c_str(), (int)text.size(), + &family, font->GetStyle(), + font->GetSize(), box, &format); + warn_stat("path.AddString"); + + RenderOutlineText(graphics_bitmap, path, brush); + } else { + stat = graphics_bitmap.DrawString(text.c_str(), + (int)text.size(), font.get(), + box, &format, &brush); + warn_stat("graphics_bitmap.DrawString"); + } + } + + if (!tex || (LONG)cx != size.cx || (LONG)cy != size.cy) { + obs_enter_graphics(); + if (tex) + gs_texture_destroy(tex); + + const uint8_t *data = (uint8_t*)bits.get(); + tex = gs_texture_create(size.cx, size.cy, GS_BGRA, 1, &data, + GS_DYNAMIC); + + obs_leave_graphics(); + + cx = (uint32_t)size.cx; + cy = (uint32_t)size.cy; + + } else if (tex) { + obs_enter_graphics(); + gs_texture_set_image(tex, bits.get(), size.cx * 4, false); + obs_leave_graphics(); + } +} + +const char *TextSource::GetMainString(const char *str) +{ + if (!str) + return ""; + if (!chatlog_mode || !chatlog_lines) + return str; + + int lines = chatlog_lines; + size_t len = strlen(str); + if (!len) + return str; + + const char *temp = str + len; + + while(temp != str) { + temp--; + + if (temp[0] == '\n' && temp[1] != 0) { + if (!--lines) + break; + } + } + + return *temp == '\n' ? temp + 1 : temp; +} + +void TextSource::LoadFileText() +{ + BPtr file_text = os_quick_read_utf8_file(file.c_str()); + text = to_wide(GetMainString(file_text)); + + if (!text.empty() && text.back() != '\n') + text.push_back('\n'); +} + +#define obs_data_get_uint32 (uint32_t)obs_data_get_int + +inline void TextSource::Update(obs_data_t *s) +{ + const char *new_text = obs_data_get_string(s, S_TEXT); + obs_data_t *font_obj = obs_data_get_obj(s, S_FONT); + const char *align_str = obs_data_get_string(s, S_ALIGN); + const char *valign_str = obs_data_get_string(s, S_VALIGN); + uint32_t new_color = obs_data_get_uint32(s, S_COLOR); + uint32_t new_opacity = obs_data_get_uint32(s, S_OPACITY); + bool gradient = obs_data_get_bool(s, S_GRADIENT); + uint32_t new_color2 = obs_data_get_uint32(s, S_GRADIENT_COLOR); + uint32_t new_opacity2 = obs_data_get_uint32(s, S_GRADIENT_OPACITY); + float new_grad_dir = (float)obs_data_get_double(s, S_GRADIENT_DIR); + bool new_vertical = obs_data_get_bool(s, S_VERTICAL); + bool new_outline = obs_data_get_bool(s, S_OUTLINE); + uint32_t new_o_color = obs_data_get_uint32(s, S_OUTLINE_COLOR); + uint32_t new_o_opacity = obs_data_get_uint32(s, S_OUTLINE_OPACITY); + uint32_t new_o_size = obs_data_get_uint32(s, S_OUTLINE_SIZE); + bool new_use_file = obs_data_get_bool(s, S_USE_FILE); + const char *new_file = obs_data_get_string(s, S_FILE); + bool new_chat_mode = obs_data_get_bool(s, S_CHATLOG_MODE); + int new_chat_lines = (int)obs_data_get_int(s, S_CHATLOG_LINES); + bool new_extents = obs_data_get_bool(s, S_EXTENTS); + bool new_extents_wrap = obs_data_get_bool(s, S_EXTENTS_WRAP); + uint32_t n_extents_cx = obs_data_get_uint32(s, S_EXTENTS_CX); + uint32_t n_extents_cy = obs_data_get_uint32(s, S_EXTENTS_CY); + + const char *font_face = obs_data_get_string(font_obj, "face"); + int font_size = (int)obs_data_get_int(font_obj, "size"); + int64_t font_flags = obs_data_get_int(font_obj, "flags"); + bool new_bold = (font_flags & OBS_FONT_BOLD) != 0; + bool new_italic = (font_flags & OBS_FONT_ITALIC) != 0; + bool new_underline = (font_flags & OBS_FONT_UNDERLINE) != 0; + bool new_strikeout = (font_flags & OBS_FONT_STRIKEOUT) != 0; + + uint32_t new_bk_color = obs_data_get_uint32(s, S_BKCOLOR); + uint32_t new_bk_opacity = obs_data_get_uint32(s, S_BKOPACITY); + + /* ----------------------------- */ + + wstring new_face = to_wide(font_face); + + if (new_face != face || + face_size != font_size || + new_bold != bold || + new_italic != italic || + new_underline != underline || + new_strikeout != strikeout) { + + face = new_face; + face_size = font_size; + bold = new_bold; + italic = new_italic; + underline = new_underline; + strikeout = new_strikeout; + + UpdateFont(); + } + + /* ----------------------------- */ + + new_color = rgb_to_bgr(new_color); + new_color2 = rgb_to_bgr(new_color2); + new_o_color = rgb_to_bgr(new_o_color); + new_bk_color = rgb_to_bgr(new_bk_color); + + color = new_color; + opacity = new_opacity; + color2 = new_color2; + opacity2 = new_opacity2; + gradient_dir = new_grad_dir; + vertical = new_vertical; + + bk_color = new_bk_color; + bk_opacity = new_bk_opacity; + use_extents = new_extents; + wrap = new_extents_wrap; + extents_cx = n_extents_cx; + extents_cy = n_extents_cy; + + if (!gradient) { + color2 = color; + opacity2 = opacity; + } + + read_from_file = new_use_file; + + chatlog_mode = new_chat_mode; + chatlog_lines = new_chat_lines; + + if (read_from_file) { + file = new_file; + file_timestamp = get_modified_timestamp(new_file); + LoadFileText(); + + } else { + text = to_wide(GetMainString(new_text)); + + /* all text should end with newlines due to the fact that GDI+ + * treats strings without newlines differently in terms of + * render size */ + if (!text.empty()) + text.push_back('\n'); + } + + use_outline = new_outline; + outline_color = new_o_color; + outline_opacity = new_o_opacity; + outline_size = roundf(float(new_o_size)); + + if (strcmp(align_str, S_ALIGN_CENTER) == 0) + align = Align::Center; + else if (strcmp(align_str, S_ALIGN_RIGHT) == 0) + align = Align::Right; + else + align = Align::Left; + + if (strcmp(valign_str, S_VALIGN_CENTER) == 0) + valign = VAlign::Center; + else if (strcmp(valign_str, S_VALIGN_BOTTOM) == 0) + valign = VAlign::Bottom; + else + valign = VAlign::Top; + + RenderText(); + update_time_elapsed = 0.0f; + + /* ----------------------------- */ + + obs_data_release(font_obj); +} + +inline void TextSource::Tick(float seconds) +{ + if (!read_from_file) + return; + + update_time_elapsed += seconds; + + if (update_time_elapsed >= 1.0f) { + time_t t = get_modified_timestamp(file.c_str()); + update_time_elapsed = 0.0f; + + if (file_timestamp != t) { + LoadFileText(); + RenderText(); + file_timestamp = t; + } + } +} + +inline void TextSource::Render(gs_effect_t *effect) +{ + if (!tex) + return; + + gs_reset_blend_state(); + gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), tex); + gs_draw_sprite(tex, 0, cx, cy); +} + +/* ------------------------------------------------------------------------- */ + +static ULONG_PTR gdip_token = 0; + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("obs-text", "en-US") + +#define set_vis(var, val, show) \ + do { \ + p = obs_properties_get(props, val); \ + obs_property_set_visible(p, var == show); \ + } while (false) + +static bool use_file_changed(obs_properties_t *props, obs_property_t *p, + obs_data_t *s) +{ + bool use_file = obs_data_get_bool(s, S_USE_FILE); + + set_vis(use_file, S_TEXT, false); + set_vis(use_file, S_FILE, true); + return true; +} + +static bool outline_changed(obs_properties_t *props, obs_property_t *p, + obs_data_t *s) +{ + bool outline = obs_data_get_bool(s, S_OUTLINE); + + set_vis(outline, S_OUTLINE_SIZE, true); + set_vis(outline, S_OUTLINE_COLOR, true); + set_vis(outline, S_OUTLINE_OPACITY, true); + return true; +} + +static bool chatlog_mode_changed(obs_properties_t *props, obs_property_t *p, + obs_data_t *s) +{ + bool chatlog_mode = obs_data_get_bool(s, S_CHATLOG_MODE); + + set_vis(chatlog_mode, S_CHATLOG_LINES, true); + return true; +} + +static bool gradient_changed(obs_properties_t *props, obs_property_t *p, + obs_data_t *s) +{ + bool gradient = obs_data_get_bool(s, S_GRADIENT); + + set_vis(gradient, S_GRADIENT_COLOR, true); + set_vis(gradient, S_GRADIENT_OPACITY, true); + set_vis(gradient, S_GRADIENT_DIR, true); + return true; +} + +static bool extents_modified(obs_properties_t *props, obs_property_t *p, + obs_data_t *s) +{ + bool use_extents = obs_data_get_bool(s, S_EXTENTS); + + set_vis(use_extents, S_EXTENTS_WRAP, true); + set_vis(use_extents, S_EXTENTS_CX, true); + set_vis(use_extents, S_EXTENTS_CY, true); + return true; +} + +#undef set_vis + +static obs_properties_t *get_properties(void *data) +{ + TextSource *s = reinterpret_cast(data); + string path; + + obs_properties_t *props = obs_properties_create(); + obs_property_t *p; + + obs_properties_add_font(props, S_FONT, T_FONT); + + p = obs_properties_add_bool(props, S_USE_FILE, T_USE_FILE); + obs_property_set_modified_callback(p, use_file_changed); + + string filter; + filter += T_FILTER_TEXT_FILES; + filter += " (*.txt);;"; + filter += T_FILTER_ALL_FILES; + filter += " (*.*)"; + + if (s && !s->file.empty()) { + const char *slash; + + path = s->file; + replace(path.begin(), path.end(), '\\', '/'); + slash = strrchr(path.c_str(), '/'); + if (slash) + path.resize(slash - path.c_str() + 1); + } + + obs_properties_add_text(props, S_TEXT, T_TEXT, OBS_TEXT_MULTILINE); + obs_properties_add_path(props, S_FILE, T_FILE, OBS_PATH_FILE, + filter.c_str(), path.c_str()); + + obs_properties_add_bool(props, S_VERTICAL, T_VERTICAL); + obs_properties_add_color(props, S_COLOR, T_COLOR); + obs_properties_add_int_slider(props, S_OPACITY, T_OPACITY, 0, 100, 1); + + p = obs_properties_add_bool(props, S_GRADIENT, T_GRADIENT); + obs_property_set_modified_callback(p, gradient_changed); + + obs_properties_add_color(props, S_GRADIENT_COLOR, T_GRADIENT_COLOR); + obs_properties_add_int_slider(props, S_GRADIENT_OPACITY, + T_GRADIENT_OPACITY, 0, 100, 1); + obs_properties_add_float_slider(props, S_GRADIENT_DIR, + T_GRADIENT_DIR, 0, 360, 0.1); + + obs_properties_add_color(props, S_BKCOLOR, T_BKCOLOR); + obs_properties_add_int_slider(props, S_BKOPACITY, T_BKOPACITY, + 0, 100, 1); + + p = obs_properties_add_list(props, S_ALIGN, T_ALIGN, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, T_ALIGN_LEFT, S_ALIGN_LEFT); + obs_property_list_add_string(p, T_ALIGN_CENTER, S_ALIGN_CENTER); + obs_property_list_add_string(p, T_ALIGN_RIGHT, S_ALIGN_RIGHT); + + p = obs_properties_add_list(props, S_VALIGN, T_VALIGN, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, T_VALIGN_TOP, S_VALIGN_TOP); + obs_property_list_add_string(p, T_VALIGN_CENTER, S_VALIGN_CENTER); + obs_property_list_add_string(p, T_VALIGN_BOTTOM, S_VALIGN_BOTTOM); + + p = obs_properties_add_bool(props, S_OUTLINE, T_OUTLINE); + obs_property_set_modified_callback(p, outline_changed); + + obs_properties_add_int(props, S_OUTLINE_SIZE, T_OUTLINE_SIZE, 1, 20, 1); + obs_properties_add_color(props, S_OUTLINE_COLOR, T_OUTLINE_COLOR); + obs_properties_add_int_slider(props, S_OUTLINE_OPACITY, + T_OUTLINE_OPACITY, 0, 100, 1); + + p = obs_properties_add_bool(props, S_CHATLOG_MODE, T_CHATLOG_MODE); + obs_property_set_modified_callback(p, chatlog_mode_changed); + + obs_properties_add_int(props, S_CHATLOG_LINES, T_CHATLOG_LINES, + 1, 1000, 1); + + p = obs_properties_add_bool(props, S_EXTENTS, T_EXTENTS); + obs_property_set_modified_callback(p, extents_modified); + + obs_properties_add_int(props, S_EXTENTS_CX, T_EXTENTS_CX, 32, 8000, 1); + obs_properties_add_int(props, S_EXTENTS_CY, T_EXTENTS_CY, 32, 8000, 1); + obs_properties_add_bool(props, S_EXTENTS_WRAP, T_EXTENTS_WRAP); + + return props; +} + +bool obs_module_load(void) +{ + obs_source_info si = {}; + si.id = "text_gdiplus"; + si.type = OBS_SOURCE_TYPE_INPUT; + si.output_flags = OBS_SOURCE_VIDEO; + si.get_properties = get_properties; + + si.get_name = [] (void*) + { + return obs_module_text("TextGDIPlus"); + }; + si.create = [] (obs_data_t *settings, obs_source_t *source) + { + return (void*)new TextSource(source, settings); + }; + si.destroy = [] (void *data) + { + delete reinterpret_cast(data); + }; + si.get_width = [] (void *data) + { + return reinterpret_cast(data)->cx; + }; + si.get_height = [] (void *data) + { + return reinterpret_cast(data)->cy; + }; + si.get_defaults = [] (obs_data_t *settings) + { + obs_data_t *font_obj = obs_data_create(); + obs_data_set_default_string(font_obj, "face", "Arial"); + obs_data_set_default_int(font_obj, "size", 36); + + obs_data_set_default_obj(settings, S_FONT, font_obj); + obs_data_set_default_string(settings, S_ALIGN, S_ALIGN_LEFT); + obs_data_set_default_string(settings, S_VALIGN, S_VALIGN_TOP); + obs_data_set_default_int(settings, S_COLOR, 0xFFFFFF); + obs_data_set_default_int(settings, S_OPACITY, 100); + obs_data_set_default_int(settings, S_GRADIENT_COLOR, 0xFFFFFF); + obs_data_set_default_int(settings, S_GRADIENT_OPACITY, 100); + obs_data_set_default_double(settings, S_GRADIENT_DIR, 90.0); + obs_data_set_default_int(settings, S_BKCOLOR, 0x000000); + obs_data_set_default_int(settings, S_BKOPACITY, 0); + obs_data_set_default_int(settings, S_OUTLINE_SIZE, 2); + obs_data_set_default_int(settings, S_OUTLINE_COLOR, 0xFFFFFF); + obs_data_set_default_int(settings, S_OUTLINE_OPACITY, 100); + obs_data_set_default_int(settings, S_CHATLOG_LINES, 6); + obs_data_set_default_bool(settings, S_EXTENTS_WRAP, true); + obs_data_set_default_int(settings, S_EXTENTS_CX, 100); + obs_data_set_default_int(settings, S_EXTENTS_CY, 100); + + obs_data_release(font_obj); + }; + si.update = [] (void *data, obs_data_t *settings) + { + reinterpret_cast(data)->Update(settings); + }; + si.video_tick = [] (void *data, float seconds) + { + reinterpret_cast(data)->Tick(seconds); + }; + si.video_render = [] (void *data, gs_effect_t *effect) + { + reinterpret_cast(data)->Render(effect); + }; + + obs_register_source(&si); + + const GdiplusStartupInput gdip_input; + GdiplusStartup(&gdip_token, &gdip_input, nullptr); + return true; +} + +void obs_module_unload(void) +{ + GdiplusShutdown(gdip_token); +} diff --git a/plugins/obs-transitions/data/locale/fr-FR.ini b/plugins/obs-transitions/data/locale/fr-FR.ini index 6a2a101d68b762..8ad0f6c06f97ee 100644 --- a/plugins/obs-transitions/data/locale/fr-FR.ini +++ b/plugins/obs-transitions/data/locale/fr-FR.ini @@ -11,14 +11,42 @@ Direction.Down="Bas" SwipeIn="Recouvrement" Color="Couleur" SwitchPoint="Point de couleur maximal (pourcentage)" +LumaWipeTransition="Luma" +LumaWipe.Image="Image" LumaWipe.Invert="Inverser" +LumaWipe.Softness="Douceur" +LumaWipe.Type.BarndoorBottomLeft="Portes coulissantes en diagonale (en bas à gauche)" +LumaWipe.Type.BarndoorHorizontal="Portes coulissantes horizontales" +LumaWipe.Type.BarndoorTopLeft="Portes coulissantes en diagonale (en haut à gauche)" +LumaWipe.Type.BarndoorVertical="Portes coulissantes verticales" +LumaWipe.Type.BlindsHorizontal="Persiennes horizontales" +LumaWipe.Type.BoxBottomLeft="Boîte (vers le bas à gauche)" +LumaWipe.Type.BoxBottomRight="Boîte (vers le bas à droite)" +LumaWipe.Type.BoxTopLeft="Boîte (vers le haut à gauche)" +LumaWipe.Type.BoxTopRight="Boîte (vers le haut à droîte)" +LumaWipe.Type.Burst="Explosion" +LumaWipe.Type.CheckerboardSmall="Échiquier (petit)" LumaWipe.Type.Circles="Cercles" LumaWipe.Type.Clock="Horloge" LumaWipe.Type.Cloud="Nuage" LumaWipe.Type.Curtain="Rideau" LumaWipe.Type.Fan="Ventilateur" LumaWipe.Type.Fractal="Fractale" +LumaWipe.Type.Iris="Diaphragme" +LumaWipe.Type.LinearHorizontal="Balayage horizontal" +LumaWipe.Type.LinearTopLeft="Balayage en diagonale (de gauche à droite)" +LumaWipe.Type.LinearTopRight="Balayage en diagonale (de droite à gauche)" +LumaWipe.Type.LinearVertical="Balayage vertical" +LumaWipe.Type.ParallelZigzagHorizontal="Zigzags en parallèle (horizontal)" +LumaWipe.Type.ParallelZigzagVertical="Zigzags en parallèle (vertical)" +LumaWipe.Type.Sinus9="Plasma" LumaWipe.Type.Spiral="Spirale" LumaWipe.Type.Square="Carré" +LumaWipe.Type.Squares="Carrés" +LumaWipe.Type.Stripes="Rayures" +LumaWipe.Type.StripsHorizontal="Bandes horizontales" +LumaWipe.Type.StripsVertical="Bandes verticales" LumaWipe.Type.Watercolor="Aquarelle" +LumaWipe.Type.ZigzagHorizontal="Zigzags à l'horizontale" +LumaWipe.Type.ZigzagVertical="Zigzags à la verticale" diff --git a/plugins/obs-transitions/data/locale/sv-SE.ini b/plugins/obs-transitions/data/locale/sv-SE.ini index 5eaf6a9f1d3fe6..89e14e35a9e34c 100644 --- a/plugins/obs-transitions/data/locale/sv-SE.ini +++ b/plugins/obs-transitions/data/locale/sv-SE.ini @@ -10,22 +10,42 @@ Direction.Up="Upp" Direction.Down="Ned" SwipeIn="Svep in" Color="Färg" +LumaWipeTransition="Luma Wipe" LumaWipe.Image="Bild" LumaWipe.Invert="Invertera" LumaWipe.Softness="Mjukhet" +LumaWipe.Type.BarndoorBottomLeft="Ladugårdsdörr, vänster nedkant" +LumaWipe.Type.BarndoorHorizontal="Ladugårdsdörr, horisontalt" +LumaWipe.Type.BarndoorTopLeft="Ladugårdsdörr, vänster överkant" +LumaWipe.Type.BarndoorVertical="Ladugårdsdörr, vertikalt" +LumaWipe.Type.BlindsHorizontal="Persienner, horisontalt" LumaWipe.Type.BoxBottomLeft="Box vänster nederkant" LumaWipe.Type.BoxBottomRight="Box höger nederkant" LumaWipe.Type.BoxTopLeft="Box vänster överkant" LumaWipe.Type.BoxTopRight="Box höger överkant" +LumaWipe.Type.Burst="Explosion" LumaWipe.Type.CheckerboardSmall="Schackbräde litet" LumaWipe.Type.Circles="Cirklar" LumaWipe.Type.Clock="Klocka" LumaWipe.Type.Cloud="Moln" LumaWipe.Type.Curtain="Gardin" +LumaWipe.Type.Fan="Fläkt" +LumaWipe.Type.Fractal="Fraktal" LumaWipe.Type.Iris="Iris" +LumaWipe.Type.LinearHorizontal="Linjer, horisontalt" +LumaWipe.Type.LinearTopLeft="Linjer, vänster överkant" +LumaWipe.Type.LinearTopRight="Linjer, höger överkant" +LumaWipe.Type.LinearVertical="Linjer, vertikalt" +LumaWipe.Type.ParallelZigzagHorizontal="Parallell sicksack, horisontalt" +LumaWipe.Type.ParallelZigzagVertical="Parallell sicksack, vertikalt" +LumaWipe.Type.Sinus9="Sinus 9" LumaWipe.Type.Spiral="Spiral" LumaWipe.Type.Square="Kvadrat" LumaWipe.Type.Squares="Kvadrater" LumaWipe.Type.Stripes="Ränder" +LumaWipe.Type.StripsHorizontal="Streck, horisontalt" +LumaWipe.Type.StripsVertical="Streck, vertikalt" LumaWipe.Type.Watercolor="Vattenfärg" +LumaWipe.Type.ZigzagHorizontal="Sicksack, horisontalt" +LumaWipe.Type.ZigzagVertical="Sicksack, vertikalt" diff --git a/plugins/obs-transitions/data/locale/zh-CN.ini b/plugins/obs-transitions/data/locale/zh-CN.ini index 8a4c1334557c56..5edfdff2de294a 100644 --- a/plugins/obs-transitions/data/locale/zh-CN.ini +++ b/plugins/obs-transitions/data/locale/zh-CN.ini @@ -11,4 +11,42 @@ Direction.Down="下" SwipeIn="向上滑动" Color="色彩" SwitchPoint="峰值颜色点(百分比)" +LumaWipeTransition="亮度擦除" +LumaWipe.Image="图像" +LumaWipe.Invert="反转" +LumaWipe.Softness="柔和" +LumaWipe.Type.BarndoorBottomLeft="左下角门" +LumaWipe.Type.BarndoorHorizontal="水平门" +LumaWipe.Type.BarndoorTopLeft="右上角门" +LumaWipe.Type.BarndoorVertical="垂直门" +LumaWipe.Type.BlindsHorizontal="水平百叶窗" +LumaWipe.Type.BoxBottomLeft="左下角盒子" +LumaWipe.Type.BoxBottomRight="右下角盒子" +LumaWipe.Type.BoxTopLeft="左上角盒子" +LumaWipe.Type.BoxTopRight="右上角盒子" +LumaWipe.Type.Burst="爆裂" +LumaWipe.Type.CheckerboardSmall="小棋盘" +LumaWipe.Type.Circles="圆" +LumaWipe.Type.Clock="时钟" +LumaWipe.Type.Cloud="云" +LumaWipe.Type.Curtain="窗帘" +LumaWipe.Type.Fan="风扇" +LumaWipe.Type.Fractal="不规则碎片" +LumaWipe.Type.Iris="中心" +LumaWipe.Type.LinearHorizontal="线性水平" +LumaWipe.Type.LinearTopLeft="线性左上" +LumaWipe.Type.LinearTopRight="线性右上" +LumaWipe.Type.LinearVertical="线性垂直" +LumaWipe.Type.ParallelZigzagHorizontal="平行水波水平" +LumaWipe.Type.ParallelZigzagVertical="平行水波垂直" +LumaWipe.Type.Sinus9="Sinus 9" +LumaWipe.Type.Spiral="螺旋" +LumaWipe.Type.Square="正方型" +LumaWipe.Type.Squares="方块" +LumaWipe.Type.Stripes="条纹" +LumaWipe.Type.StripsHorizontal="条纹水平" +LumaWipe.Type.StripsVertical="条纹垂直" +LumaWipe.Type.Watercolor="水彩" +LumaWipe.Type.ZigzagHorizontal="Z字形水平" +LumaWipe.Type.ZigzagVertical="Z字形垂直" diff --git a/plugins/rtmp-services/data/locale/ja-JP.ini b/plugins/rtmp-services/data/locale/ja-JP.ini index 0273b6b4a455a2..082962b72932c1 100644 --- a/plugins/rtmp-services/data/locale/ja-JP.ini +++ b/plugins/rtmp-services/data/locale/ja-JP.ini @@ -1,5 +1,5 @@ StreamingServices="ストリーミングサービス" -CustomStreamingServer="カスタムストリーミングサーバ" +CustomStreamingServer="カスタムストリーミングサーバー" Service="サービス" Server="サーバー" StreamKey="ストリームキー" diff --git a/plugins/rtmp-services/data/package.json b/plugins/rtmp-services/data/package.json index d323130588268c..99ef04c2c3e487 100644 --- a/plugins/rtmp-services/data/package.json +++ b/plugins/rtmp-services/data/package.json @@ -1,10 +1,10 @@ { "url": "https://obsproject.com/obs2_update/rtmp-services", - "version": 29, + "version": 38, "files": [ { "name": "services.json", - "version": 29 + "version": 38 } ] } diff --git a/plugins/rtmp-services/data/services.json b/plugins/rtmp-services/data/services.json index 1cc56da00a1c23..1133d566f76989 100644 --- a/plugins/rtmp-services/data/services.json +++ b/plugins/rtmp-services/data/services.json @@ -112,7 +112,6 @@ ], "recommended": { "keyint": 2, - "profile": "main", "max video bitrate": 3500, "max audio bitrate": 160, "x264opts": "scenecut=0" @@ -183,9 +182,13 @@ "url": "rtmp://live.vgn.hitbox.tv/push" }, { - "name": "US-East: New York", + "name": "US-East: New York - 1", "url": "rtmp://live.jfk.hitbox.tv/push" }, + { + "name": "US-East: New York - 2", + "url": "rtmp://live.nyc.hitbox.tv/push" + }, { "name": "US-Central: Denver", "url": "rtmp://live.den.hitbox.tv/push" @@ -258,17 +261,41 @@ "name": "EU: Amsterdam", "url": "rtmp://ingest-ams.beam.pro:1935/beam" }, + { + "name": "EU: Milan", + "url": "rtmp://ingest-mil.beam.pro:1935/beam" + }, + { + "name": "EU: Paris", + "url": "rtmp://ingest-par.beam.pro:1935/beam" + }, { "name": "EU: Frankfurt", "url": "rtmp://ingest-fra.beam.pro:1935/beam" }, + { + "name": "Brazil: Sao Paulo", + "url": "rtmp://ingest-sao.beam.pro:1935/beam" + }, { "name": "Australia: Melbourne", "url": "rtmp://ingest-mel.beam.pro:1935/beam" }, { - "name": "Brazil: Sao Paulo", - "url": "rtmp://ingest-sao.beam.pro:1935/beam" + "name": "Australia: Sydney", + "url": "rtmp://ingest-syd.beam.pro:1935/beam" + }, + { + "name": "Mexico: Mexico City", + "url": "rtmp://ingest-mex.beam.pro:1935/beam" + }, + { + "name": "Asia: Hong Kong", + "url": "rtmp://ingest-hkg.beam.pro:1935/beam" + }, + { + "name": "Asia: Tokyo", + "url": "rtmp://ingest-tok.beam.pro:1935/beam" } ], "recommended": { @@ -517,8 +544,8 @@ "recommended": { "keyint": 2, "profile": "main", - "max video bitrate": 2500, - "max audio bitrate": 160 + "max video bitrate": 4000, + "max audio bitrate": 128 } }, { @@ -551,6 +578,10 @@ }, { "name": "EU-Central (Frankfurt, Germany)", + "url": "rtmp://eu-central.restream.io/live" + }, + { + "name": "EU-Central 2 (Frankfurt, Germany)", "url": "rtmp://eu.restream.io/live" }, { @@ -565,9 +596,17 @@ "name": "Australia (Sydney)", "url": "rtmp://au.restream.io/live" }, + { + "name": "Australia Secondary (Sydney)", + "url": "rtmp://au-secondary.restream.io/live" + }, { "name": "South America (Sao Paulo, Brazil)", "url": "rtmp://sa.restream.io/live" + }, + { + "name": "Asia (Singapore)", + "url": "rtmp://singapore.restream.io/live" } ], "recommended": { @@ -657,6 +696,113 @@ "recommended": { "max video bitrate": 1500 } + }, + { + "name": "Meridix Live Sports Platform", + "servers": [ + { + "name": "Primary", + "url": "rtmp://publish.meridix.com/live" + } + ], + "recommended": { + "max video bitrate": 3500 + } + }, + { + "name": "Afreeca.TV", + "servers": [ + { + "name": "North America : US East", + "url": "rtmp://rtmpmanager-aws-en-east.afreeca.tv/live" + }, + { + "name": "North America : US West", + "url": "rtmp://rtmpmanager-aws-en-west.afreeca.tv/live" + }, + { + "name": "Asia : Singapore", + "url": "rtmp://rtmpmanager-aws-sg.afreeca.tv/live" + }, + { + "name": "Asia : South Korea", + "url": "rtmp://rtmpmanager-en-ko.afreeca.tv/live" + } + ], + "recommended": { + "keyint": 1, + "profile": "main", + "max video bitrate": 5000, + "max audio bitrate": 192 + } + }, + { + "name": "アフリカTV", + "servers": [ + { + "name": "Japan", + "url": "rtmp://rtmpmanager-aws-jp.afreeca.tv/live/" + }, + { + "name": "South Korea", + "url": "rtmp://rtmpmanager-jp.afreeca.tv/live/" + } + ], + "recommended": { + "keyint": 1, + "profile": "main", + "max video bitrate": 5000, + "max audio bitrate": 192 + } + }, + { + "name": "艾菲卡TV", + "servers": [ + { + "name": "Taiwan", + "url": "rtmp://rtmpmanager-gcp-tw.afreeca.tv/live/" + }, + { + "name": "South Korea", + "url": "rtmp://rtmpmanager-tw-ko.afreeca.tv/live/" + } + ], + "recommended": { + "keyint": 1, + "profile": "main", + "max video bitrate": 5000, + "max audio bitrate": 192 + } + }, + { + "name": "아프리카TV", + "servers": [ + { + "name": "Korea", + "url": "rtmp://rtmpmanager-freecat.afreeca.tv/app/" + } + ], + "recommended": { + "keyint": 1, + "profile": "main", + "max video bitrate": 5000, + "max audio bitrate": 192 + } + }, + { + "name": "CAM4", + "servers": [ + { + "name": "CAM4", + "url": "rtmp://origin.cam4.com/cam4-origin-live" + } + ], + "recommended": { + "keyint": 1, + "profile": "baseline", + "max video bitrate": 3000, + "max audio bitrate": 128 + } } ] } diff --git a/plugins/text-freetype2/text-freetype2.c b/plugins/text-freetype2/text-freetype2.c index b885620449ee78..02579593d8f510 100644 --- a/plugins/text-freetype2/text-freetype2.c +++ b/plugins/text-freetype2/text-freetype2.c @@ -35,6 +35,9 @@ static struct obs_source_info freetype2_source_info = { .id = "text_ft2_source", .type = OBS_SOURCE_TYPE_INPUT, .output_flags = OBS_SOURCE_VIDEO | +#ifdef _WIN32 + OBS_SOURCE_DEPRECATED | +#endif OBS_SOURCE_CUSTOM_DRAW, .get_name = ft2_source_get_name, .create = ft2_source_create, diff --git a/plugins/vlc-video/vlc-video-plugin.c b/plugins/vlc-video/vlc-video-plugin.c index 0cbe7aa54e2225..49c4e484387455 100644 --- a/plugins/vlc-video/vlc-video-plugin.c +++ b/plugins/vlc-video/vlc-video-plugin.c @@ -16,6 +16,8 @@ LIBVLC_EVENT_ATTACH libvlc_event_attach_; /* libvlc media */ LIBVLC_MEDIA_NEW_PATH libvlc_media_new_path_; +LIBVLC_MEDIA_NEW_LOCATION libvlc_media_new_location_; +LIBVLC_MEDIA_ADD_OPTION libvlc_media_add_option_; LIBVLC_MEDIA_RELEASE libvlc_media_release_; LIBVLC_MEDIA_RELEASE libvlc_media_retain_; @@ -76,6 +78,8 @@ static bool load_vlc_funcs(void) /* libvlc media */ LOAD_VLC_FUNC(libvlc_media_new_path); + LOAD_VLC_FUNC(libvlc_media_new_location); + LOAD_VLC_FUNC(libvlc_media_add_option); LOAD_VLC_FUNC(libvlc_media_release); LOAD_VLC_FUNC(libvlc_media_retain); diff --git a/plugins/vlc-video/vlc-video-plugin.h b/plugins/vlc-video/vlc-video-plugin.h index 9eefb39ec4aa35..53cb2551e03344 100644 --- a/plugins/vlc-video/vlc-video-plugin.h +++ b/plugins/vlc-video/vlc-video-plugin.h @@ -29,6 +29,9 @@ typedef int (*LIBVLC_EVENT_ATTACH)(libvlc_event_manager_t *p_event_manager, /* libvlc media */ typedef libvlc_media_t *(*LIBVLC_MEDIA_NEW_PATH)( libvlc_instance_t *p_instance, const char *path); +typedef libvlc_media_t *(*LIBVLC_MEDIA_NEW_LOCATION)( + libvlc_instance_t *p_instance, const char *location); +typedef void (*LIBVLC_MEDIA_ADD_OPTION)(libvlc_media_t *p_md, const char *options); typedef void (*LIBVLC_MEDIA_RETAIN)(libvlc_media_t *p_md); typedef void (*LIBVLC_MEDIA_RELEASE)(libvlc_media_t *p_md); @@ -119,6 +122,8 @@ extern LIBVLC_EVENT_ATTACH libvlc_event_attach_; /* libvlc media */ extern LIBVLC_MEDIA_NEW_PATH libvlc_media_new_path_; +extern LIBVLC_MEDIA_NEW_LOCATION libvlc_media_new_location_; +extern LIBVLC_MEDIA_ADD_OPTION libvlc_media_add_option_; extern LIBVLC_MEDIA_RELEASE libvlc_media_release_; extern LIBVLC_MEDIA_RETAIN libvlc_media_retain_; diff --git a/plugins/vlc-video/vlc-video-source.c b/plugins/vlc-video/vlc-video-source.c index 824d57cc57dd21..bac5de380fcd8a 100644 --- a/plugins/vlc-video/vlc-video-source.c +++ b/plugins/vlc-video/vlc-video-source.c @@ -76,7 +76,9 @@ static libvlc_media_t *get_media(struct darray *array, const char *path) static inline libvlc_media_t *create_media_from_file(const char *file) { - return libvlc_media_new_path_(libvlc, file); + return (file && strstr(file, "://") != NULL) + ? libvlc_media_new_location_(libvlc, file) + : libvlc_media_new_path_(libvlc, file); } static void free_files(struct darray *array) @@ -299,11 +301,25 @@ static unsigned vlcs_video_format(void **p_data, char *chroma, unsigned *width, enum video_format new_format; enum video_range_type range; bool new_range; + unsigned new_width = 0; + unsigned new_height = 0; size_t i = 0; new_format = convert_vlc_video_format(chroma, &new_range); - libvlc_video_get_size_(c->media_player, 0, width, height); + /* This is used because VLC will by default try to use a different + * scaling than what the file uses (probably for optimization reasons). + * For example, if the file is 1920x1080, it will try to render it by + * 1920x1088, which isn't what we want. Calling libvlc_video_get_size + * gets the actual video file's size, and thus fixes the problem. + * However this doesn't work with URLs, so if it returns a 0 value, it + * shouldn't be used. */ + libvlc_video_get_size_(c->media_player, 0, &new_width, &new_height); + + if (new_width && new_height) { + *width = new_width; + *height = new_height; + } /* don't allocate a new frame if format/width/height hasn't changed */ if (c->frame.format != new_format || @@ -382,12 +398,14 @@ static void add_file(struct vlc_source *c, struct darray *array, struct media_file_data data; struct dstr new_path = {0}; libvlc_media_t *new_media; + bool is_url = path && strstr(path, "://") != NULL; new_files.da = *array; dstr_copy(&new_path, path); #ifdef _WIN32 - dstr_replace(&new_path, "/", "\\"); + if (!is_url) + dstr_replace(&new_path, "/", "\\"); #endif path = new_path.array; @@ -399,6 +417,10 @@ static void add_file(struct vlc_source *c, struct darray *array, new_media = create_media_from_file(path); if (new_media) { + if (is_url) + libvlc_media_add_option_(new_media, + ":network-caching=100"); + data.path = new_path.array; data.media = new_media; da_push_back(new_files, &data); diff --git a/plugins/win-capture/data/locale/ca-ES.ini b/plugins/win-capture/data/locale/ca-ES.ini index 4c93bc7d98361c..f8ae6d0e891d81 100644 --- a/plugins/win-capture/data/locale/ca-ES.ini +++ b/plugins/win-capture/data/locale/ca-ES.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Capturar capes d'aplicacions externes (tals com Ste GameCapture.AntiCheatHook="Utilitzi la compatibilitat anti-trampa" GameCapture.HotkeyStart="Captura finestra en primer pla" GameCapture.HotkeyStop="Desactiva captura" +Mode="Mode" diff --git a/plugins/win-capture/data/locale/cs-CZ.ini b/plugins/win-capture/data/locale/cs-CZ.ini index 84ebdc36bf4002..495a8175e00b61 100644 --- a/plugins/win-capture/data/locale/cs-CZ.ini +++ b/plugins/win-capture/data/locale/cs-CZ.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Zaznamenávat overlaye (např. Steam Overlay)" GameCapture.AntiCheatHook="Použít ochranu proti detekci jako hack" GameCapture.HotkeyStart="Snímat okno v popředí" GameCapture.HotkeyStop="Zastavit snímání" +Mode="Režim" diff --git a/plugins/win-capture/data/locale/de-DE.ini b/plugins/win-capture/data/locale/de-DE.ini index 34ec86dd7d7c8f..b36f683c3d4297 100644 --- a/plugins/win-capture/data/locale/de-DE.ini +++ b/plugins/win-capture/data/locale/de-DE.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Aufnahme von Drittanbieter Overlays (z.B. Steam)" GameCapture.AntiCheatHook="Verwende Anti-Cheat-Kompatibilität Hook" GameCapture.HotkeyStart="Fenster im Vordergrund erfassen" GameCapture.HotkeyStop="Erfassen deaktivieren" +Mode="Modus" diff --git a/plugins/win-capture/data/locale/es-ES.ini b/plugins/win-capture/data/locale/es-ES.ini index 032d8ce24edae9..33605484448ae8 100644 --- a/plugins/win-capture/data/locale/es-ES.ini +++ b/plugins/win-capture/data/locale/es-ES.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Capturar capas de aplicaciones externas (tales como GameCapture.AntiCheatHook="Utilice la compatibilidad anti-trampa" GameCapture.HotkeyStart="Captura ventana en primer plano" GameCapture.HotkeyStop="Desactivar captura" +Mode="Modo" diff --git a/plugins/win-capture/data/locale/eu-ES.ini b/plugins/win-capture/data/locale/eu-ES.ini index 734eb94d388ed7..999b0da720de61 100644 --- a/plugins/win-capture/data/locale/eu-ES.ini +++ b/plugins/win-capture/data/locale/eu-ES.ini @@ -12,9 +12,14 @@ Monitor="Pantaila" PrimaryMonitor="Monitore nagusia" GameCapture="Bideojoko-kaptura" GameCapture.AnyFullscreen="Kapturatu pantaila osoko edozein aplikazio" +GameCapture.CaptureWindow="Kapturatu leiho zehatza" +GameCapture.UseHotkey="Kapturatu aurreko planoko leihoa laster teklen bidez" GameCapture.ForceScaling="Behartu eskalatzea" GameCapture.ScaleRes="Bereizmen eskalatua" GameCapture.LimitFramerate="Mugatu kapturaren fotograma tasa" GameCapture.CaptureOverlays="Kapturatu hirugarrengoen gainjarpenak (steam bezalakoak)" GameCapture.AntiCheatHook="Erabili iruzurren aurkako bateragarritasun kakoa" +GameCapture.HotkeyStart="Kapturatu aurreko planoko leihoa" +GameCapture.HotkeyStop="Desaktibatu kaptura" +Mode="Modua" diff --git a/plugins/win-capture/data/locale/fi-FI.ini b/plugins/win-capture/data/locale/fi-FI.ini index 28dcd88a3d5da9..198a2b860014b7 100644 --- a/plugins/win-capture/data/locale/fi-FI.ini +++ b/plugins/win-capture/data/locale/fi-FI.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Kaappaa kolmannen osapuolen 'overlay' (kuten Steam) GameCapture.AntiCheatHook="Yhteensopivuus huijaukseneston kanssa" GameCapture.HotkeyStart="Kaappaa aktiivinen ikkuna" GameCapture.HotkeyStop="Poista kaappaus käytöstä" +Mode="Tila" diff --git a/plugins/win-capture/data/locale/fr-FR.ini b/plugins/win-capture/data/locale/fr-FR.ini index 73f02a6189faa1..1ff71ce40d0d85 100644 --- a/plugins/win-capture/data/locale/fr-FR.ini +++ b/plugins/win-capture/data/locale/fr-FR.ini @@ -12,9 +12,14 @@ Monitor="Écran" PrimaryMonitor="Écran principal" GameCapture="Capture de jeu" GameCapture.AnyFullscreen="Capturer n'importe quelle application en plein écran" +GameCapture.CaptureWindow="Capturer une fenêtre spécifique" +GameCapture.UseHotkey="Capturer la fenêtre au premier plan par raccourci clavier" GameCapture.ForceScaling="Forcer la mise à l'échelle" GameCapture.ScaleRes="Résolution mise à l'échelle" GameCapture.LimitFramerate="Limiter la fréquence d'images de la capture" GameCapture.CaptureOverlays="Capturer les surcouches tierces (comme Steam)" GameCapture.AntiCheatHook="Utiliser la méthode de capture compatible anti-triche" +GameCapture.HotkeyStart="Capturer la fenêtre au premier plan" +GameCapture.HotkeyStop="Arrêter la capture" +Mode="Mode" diff --git a/plugins/win-capture/data/locale/hr-HR.ini b/plugins/win-capture/data/locale/hr-HR.ini index 48767c9e33b840..9d21b762a65238 100644 --- a/plugins/win-capture/data/locale/hr-HR.ini +++ b/plugins/win-capture/data/locale/hr-HR.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Snimaj overlay treće strane (kao što je steam)" GameCapture.AntiCheatHook="Koristi priključak za kompatibilnost sa zaštitom od varanja" GameCapture.HotkeyStart="Snimanje prozora u prvom planu" GameCapture.HotkeyStop="Isključivanje snimanja" +Mode="Režim" diff --git a/plugins/win-capture/data/locale/hu-HU.ini b/plugins/win-capture/data/locale/hu-HU.ini index 57d9896c3f7a9e..feb8cb43b3f804 100644 --- a/plugins/win-capture/data/locale/hu-HU.ini +++ b/plugins/win-capture/data/locale/hu-HU.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Külső overlay felvétele (mint például steam)" GameCapture.AntiCheatHook="Anti-csalás kompatibilitási metódus használata" GameCapture.HotkeyStart="Előtérben lévő ablak felvétele" GameCapture.HotkeyStop="Felvétel lekapcsolása" +Mode="Mód" diff --git a/plugins/win-capture/data/locale/ja-JP.ini b/plugins/win-capture/data/locale/ja-JP.ini index 2874835c606f8f..7b06c03c5694b7 100644 --- a/plugins/win-capture/data/locale/ja-JP.ini +++ b/plugins/win-capture/data/locale/ja-JP.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="(steamなどの) サードパーティ製のオー GameCapture.AntiCheatHook="アンチチート互換性フックを使用する" GameCapture.HotkeyStart="前面のウィンドウをキャプチャ" GameCapture.HotkeyStop="キャプチャを無効化" +Mode="モード" diff --git a/plugins/win-capture/data/locale/ko-KR.ini b/plugins/win-capture/data/locale/ko-KR.ini index a781827eccc507..90c55fe41c2a93 100644 --- a/plugins/win-capture/data/locale/ko-KR.ini +++ b/plugins/win-capture/data/locale/ko-KR.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="서드-파티 오버레이 (스팀과 같은) 캡 GameCapture.AntiCheatHook="치트 방지 기술로 인한 인식이 안될 때 호환성 모드 사용" GameCapture.HotkeyStart="전경에 있는 창을 캡쳐" GameCapture.HotkeyStop="캡쳐 끄기" +Mode="방식" diff --git a/plugins/win-capture/data/locale/nl-NL.ini b/plugins/win-capture/data/locale/nl-NL.ini index 9606adb21b1e36..2e6f3eab405e3e 100644 --- a/plugins/win-capture/data/locale/nl-NL.ini +++ b/plugins/win-capture/data/locale/nl-NL.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Capture overlays van derde partijen (zoals Steam)" GameCapture.AntiCheatHook="Hook met anti-cheat compatibiliteit" GameCapture.HotkeyStart="Venster op voorgrond opnemen" GameCapture.HotkeyStop="Capture deactiveren" +Mode="Modus" diff --git a/plugins/win-capture/data/locale/pl-PL.ini b/plugins/win-capture/data/locale/pl-PL.ini index b3f5283bcac1ca..454a18e735cd3b 100644 --- a/plugins/win-capture/data/locale/pl-PL.ini +++ b/plugins/win-capture/data/locale/pl-PL.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Przechwytywanie nakładek firm trzecich (np. Steam GameCapture.AntiCheatHook="Zgodność z mechanizmami anti-cheat" GameCapture.HotkeyStart="Przechwytywanie okna na pierwszym planie" GameCapture.HotkeyStop="Wyłącz przechwytywanie" +Mode="Tryb" diff --git a/plugins/win-capture/data/locale/pt-BR.ini b/plugins/win-capture/data/locale/pt-BR.ini index 1ae379f0cc26cb..2e4c0165f27a3a 100644 --- a/plugins/win-capture/data/locale/pt-BR.ini +++ b/plugins/win-capture/data/locale/pt-BR.ini @@ -12,9 +12,14 @@ Monitor="Monitor" PrimaryMonitor="Monitor principal" GameCapture="Captura de jogo" GameCapture.AnyFullscreen="Capturar qualquer aplicação em tela cheia" +GameCapture.CaptureWindow="Capturar janela específica" +GameCapture.UseHotkey="Capturar janela de primeiro plano com a tecla de atalho" GameCapture.ForceScaling="Forçar escalamento" GameCapture.ScaleRes="Resolução de Saída" GameCapture.LimitFramerate="Limitar framerate de captura" GameCapture.CaptureOverlays="Capturar overlays de terceiros (tais como as do Steam)" GameCapture.AntiCheatHook="Utilizar hook de compatibilidade de anti-cheat" +GameCapture.HotkeyStart="Capturar janela de primeiro plano" +GameCapture.HotkeyStop="Desativar a captura" +Mode="Modo" diff --git a/plugins/win-capture/data/locale/ru-RU.ini b/plugins/win-capture/data/locale/ru-RU.ini index ab2f95981d7b52..6544a7e7d1f29d 100644 --- a/plugins/win-capture/data/locale/ru-RU.ini +++ b/plugins/win-capture/data/locale/ru-RU.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Захват сторонних оверлеев ( GameCapture.AntiCheatHook="Использовать перехватчик, совместимый с защитой от читов" GameCapture.HotkeyStart="Захват окна на переднем плане" GameCapture.HotkeyStop="Остановить захват" +Mode="Режим" diff --git a/plugins/win-capture/data/locale/sr-CS.ini b/plugins/win-capture/data/locale/sr-CS.ini index 48767c9e33b840..9d21b762a65238 100644 --- a/plugins/win-capture/data/locale/sr-CS.ini +++ b/plugins/win-capture/data/locale/sr-CS.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Snimaj overlay treće strane (kao što je steam)" GameCapture.AntiCheatHook="Koristi priključak za kompatibilnost sa zaštitom od varanja" GameCapture.HotkeyStart="Snimanje prozora u prvom planu" GameCapture.HotkeyStop="Isključivanje snimanja" +Mode="Režim" diff --git a/plugins/win-capture/data/locale/sr-SP.ini b/plugins/win-capture/data/locale/sr-SP.ini index 72b14230787f11..ec13f7a52b2a4a 100644 --- a/plugins/win-capture/data/locale/sr-SP.ini +++ b/plugins/win-capture/data/locale/sr-SP.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Снимај overlay треће стране (ка GameCapture.AntiCheatHook="Користи прикључак за компатибилност са заштитом од варања" GameCapture.HotkeyStart="Снимање прозора у првом плану" GameCapture.HotkeyStop="Искључивање снимања" +Mode="Режим" diff --git a/plugins/win-capture/data/locale/sv-SE.ini b/plugins/win-capture/data/locale/sv-SE.ini index b362074cf7566b..1456b4c6ffe1d0 100644 --- a/plugins/win-capture/data/locale/sv-SE.ini +++ b/plugins/win-capture/data/locale/sv-SE.ini @@ -12,9 +12,14 @@ Monitor="Bildskärm" PrimaryMonitor="Primära bildskärmen" GameCapture="Spelintagning" GameCapture.AnyFullscreen="Spela in alla program i fullskärm" +GameCapture.CaptureWindow="Spela in specifikt fönster" +GameCapture.UseHotkey="Spela in förgrundsfönster med kortkommando" GameCapture.ForceScaling="Tvinga skalning" GameCapture.ScaleRes="Skala upplösning" GameCapture.LimitFramerate="Begränsa inspelad bildhastighet" GameCapture.CaptureOverlays="Spela in tredje part överlägg (som Steam)" GameCapture.AntiCheatHook="Använd anit-fusk kompabilitetshake" +GameCapture.HotkeyStart="Fånga förgrundsfönster" +GameCapture.HotkeyStop="Inaktivera källa" +Mode="Läge" diff --git a/plugins/win-capture/data/locale/uk-UA.ini b/plugins/win-capture/data/locale/uk-UA.ini index 0a600d62529e03..971d948c93027a 100644 --- a/plugins/win-capture/data/locale/uk-UA.ini +++ b/plugins/win-capture/data/locale/uk-UA.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="Захоплювати сторонні оверл GameCapture.AntiCheatHook="Використовувати сумісність з Anti-cheat технологіями" GameCapture.HotkeyStart="Захоплювати перше наявне вікно" GameCapture.HotkeyStop="Деактивувати захоплення" +Mode="Тип захвату" diff --git a/plugins/win-capture/data/locale/zh-CN.ini b/plugins/win-capture/data/locale/zh-CN.ini index 6946037df72e09..4bc1e4eb2bdd8d 100644 --- a/plugins/win-capture/data/locale/zh-CN.ini +++ b/plugins/win-capture/data/locale/zh-CN.ini @@ -12,9 +12,14 @@ Monitor="显示器" PrimaryMonitor="主监视器" GameCapture="游戏捕获" GameCapture.AnyFullscreen="捕获任何全屏应用程序" +GameCapture.CaptureWindow="捕获特定窗口" +GameCapture.UseHotkey="用热键捕获前景窗口" GameCapture.ForceScaling="强制缩放" GameCapture.ScaleRes="缩放分辨率" GameCapture.LimitFramerate="限制捕获帧速率" GameCapture.CaptureOverlays="捕获第三方 (如: steam) 覆盖" GameCapture.AntiCheatHook="使用反作弊兼容性钩子" +GameCapture.HotkeyStart="捕获前景窗口" +GameCapture.HotkeyStop="停止捕获" +Mode="模式" diff --git a/plugins/win-capture/data/locale/zh-TW.ini b/plugins/win-capture/data/locale/zh-TW.ini index 42be934b290c5f..a7f2234c0cacc2 100644 --- a/plugins/win-capture/data/locale/zh-TW.ini +++ b/plugins/win-capture/data/locale/zh-TW.ini @@ -21,4 +21,5 @@ GameCapture.CaptureOverlays="擷取第三方程式覆蓋層 (如steam)" GameCapture.AntiCheatHook="使用相容於反作弊的掛鉤" GameCapture.HotkeyStart="擷取前景視窗" GameCapture.HotkeyStop="停止擷取" +Mode="模式" diff --git a/plugins/win-capture/get-graphics-offsets/d3d9-offsets.cpp b/plugins/win-capture/get-graphics-offsets/d3d9-offsets.cpp index b6d7ddc3f86a6f..499392f8b48681 100644 --- a/plugins/win-capture/get-graphics-offsets/d3d9-offsets.cpp +++ b/plugins/win-capture/get-graphics-offsets/d3d9-offsets.cpp @@ -80,17 +80,107 @@ static inline void d3d9_free(d3d9_info &info) DestroyWindow(info.hwnd); } +#ifdef _WIN64 + +#define CMP_SIZE 21 + +static const uint8_t mask[CMP_SIZE] = +{0xF8, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, + 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00}; + +static const uint8_t mask_cmp[CMP_SIZE] = +{0x48, 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x75, 0x00, + 0x40, 0xB8, 0x00, 0x00, 0x00, 0x00}; +#else + +#define CMP_SIZE 19 + +static const uint8_t mask[CMP_SIZE] = +{0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x00}; + +static const uint8_t mask_cmp[CMP_SIZE] = +{0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x75, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x00}; +#endif + +#define MAX_FUNC_SCAN_BYTES 200 + +static inline bool pattern_matches(uint8_t *byte) +{ + for (size_t i = 0; i < CMP_SIZE; i++) { + if ((byte[i] & mask[i]) != mask_cmp[i]) + return false; + } + + return true; +} + void get_d3d9_offsets(struct d3d9_offsets *offsets) { d3d9_info info = {}; bool success = d3d9_init(info); if (success) { + uint8_t **vt = *(uint8_t***)info.device; + uint8_t *crr = vt[125]; + offsets->present = vtable_offset(info.module, info.device, 17); offsets->present_ex = vtable_offset(info.module, info.device, 121); offsets->present_swap = vtable_offset(info.module, info.swap, 3); + + for (size_t i = 0; i < MAX_FUNC_SCAN_BYTES; i++) { + if (pattern_matches(&crr[i])) { +#define get_offset(x) *(uint32_t*)&crr[i + x] +#ifdef _WIN64 + uint32_t off1 = get_offset(3); + uint32_t off2 = get_offset(9); +#else + uint32_t off1 = get_offset(2); + uint32_t off2 = get_offset(8); +#endif + + /* check to make sure offsets are within + * expected values */ + if (off1 > 0xFFFF || off2 > 0xFFFF) + break; + + /* check to make sure offsets actually point + * toward expected data */ +#ifdef _MSC_VER + __try { + uint8_t *ptr = (uint8_t*)(info.device); + + uint8_t *d3d9_ptr = + *(uint8_t**)(ptr + off1); + if (d3d9_ptr != (uint8_t*)info.d3d9ex) + break; + + BOOL &is_d3d9ex = + *(BOOL*)(d3d9_ptr + off2); + if (is_d3d9ex != TRUE) + break; + + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } +#endif + + offsets->d3d9_clsoff = off1; + offsets->is_d3d9ex_clsoff = off2; + break; + } + } } d3d9_free(info); diff --git a/plugins/win-capture/get-graphics-offsets/get-graphics-offsets.c b/plugins/win-capture/get-graphics-offsets/get-graphics-offsets.c index fc12f6116f245f..3345c680e46eee 100644 --- a/plugins/win-capture/get-graphics-offsets/get-graphics-offsets.c +++ b/plugins/win-capture/get-graphics-offsets/get-graphics-offsets.c @@ -31,6 +31,8 @@ int main(int argc, char *argv[]) printf("present=0x%"PRIx32"\n", d3d9.present); printf("present_ex=0x%"PRIx32"\n", d3d9.present_ex); printf("present_swap=0x%"PRIx32"\n", d3d9.present_swap); + printf("d3d9_clsoff=0x%"PRIx32"\n", d3d9.d3d9_clsoff); + printf("is_d3d9ex_clsoff=0x%"PRIx32"\n", d3d9.is_d3d9ex_clsoff); printf("[dxgi]\n"); printf("present=0x%"PRIx32"\n", dxgi.present); printf("resize=0x%"PRIx32"\n", dxgi.resize); diff --git a/plugins/win-capture/graphics-hook-info.h b/plugins/win-capture/graphics-hook-info.h index ff21372fc23a02..128fc97e50ebf0 100644 --- a/plugins/win-capture/graphics-hook-info.h +++ b/plugins/win-capture/graphics-hook-info.h @@ -33,6 +33,8 @@ struct d3d9_offsets { uint32_t present; uint32_t present_ex; uint32_t present_swap; + uint32_t d3d9_clsoff; + uint32_t is_d3d9ex_clsoff; }; struct dxgi_offsets { diff --git a/plugins/win-capture/graphics-hook/d3d9-capture.cpp b/plugins/win-capture/graphics-hook/d3d9-capture.cpp index 65ca74f1b1c4e2..221aeb296124db 100644 --- a/plugins/win-capture/graphics-hook/d3d9-capture.cpp +++ b/plugins/win-capture/graphics-hook/d3d9-capture.cpp @@ -223,14 +223,30 @@ static inline bool d3d9_shtex_init_shtex() static inline bool d3d9_shtex_init_copytex() { - uint8_t *patch_addr = get_d3d9_patch_addr(data.d3d9, data.patch); + struct d3d9_offsets offsets = global_hook_info->offsets.d3d9; + uint8_t *patch_addr = nullptr; + BOOL *p_is_d3d9 = nullptr; uint8_t saved_data[MAX_PATCH_SIZE]; size_t patch_size = 0; + BOOL was_d3d9ex = false; IDirect3DTexture9 *tex; DWORD protect_val; HRESULT hr; - if (patch_addr) { + if (offsets.d3d9_clsoff && offsets.is_d3d9ex_clsoff) { + uint8_t *device_ptr = (uint8_t*)(data.device); + uint8_t *d3d9_ptr = + *(uint8_t**)(device_ptr + offsets.d3d9_clsoff); + p_is_d3d9 = (BOOL*)(d3d9_ptr + offsets.is_d3d9ex_clsoff); + } else { + patch_addr = get_d3d9_patch_addr(data.d3d9, data.patch); + } + + if (p_is_d3d9) { + was_d3d9ex = *p_is_d3d9; + *p_is_d3d9 = true; + + } else if (patch_addr) { patch_size = patch[data.patch].size; VirtualProtect(patch_addr, patch_size, PAGE_EXECUTE_READWRITE, &protect_val); @@ -242,7 +258,10 @@ static inline bool d3d9_shtex_init_copytex() D3DUSAGE_RENDERTARGET, data.d3d9_format, D3DPOOL_DEFAULT, &tex, &data.handle); - if (patch_addr && patch_size) { + if (p_is_d3d9) { + *p_is_d3d9 = was_d3d9ex; + + } else if (patch_addr && patch_size) { memcpy(patch_addr, saved_data, patch_size); VirtualProtect(patch_addr, patch_size, protect_val, &protect_val); @@ -449,6 +468,9 @@ static bool d3d9_init_format_swapchain(uint32_t &cx, uint32_t &cy, HWND &window) static void d3d9_init(IDirect3DDevice9 *device) { IDirect3DDevice9Ex *d3d9ex = nullptr; + bool has_d3d9ex_bool_offset = + global_hook_info->offsets.d3d9.d3d9_clsoff && + global_hook_info->offsets.d3d9.is_d3d9ex_clsoff; bool success; uint32_t cx = 0; uint32_t cy = 0; @@ -463,8 +485,10 @@ static void d3d9_init(IDirect3DDevice9 *device) if (SUCCEEDED(hr)) { d3d9ex->Release(); data.patch = -1; - } else { + } else if (!has_d3d9ex_bool_offset) { data.patch = get_d3d9_patch(data.d3d9); + } else { + data.patch = -1; } if (!d3d9_init_format_backbuffer(cx, cy, window)) { @@ -473,7 +497,8 @@ static void d3d9_init(IDirect3DDevice9 *device) } } - if (global_hook_info->force_shmem || (!d3d9ex && data.patch == -1)) { + if (global_hook_info->force_shmem || + (!d3d9ex && data.patch == -1 && !has_d3d9ex_bool_offset)) { success = d3d9_shmem_init(cx, cy, window); } else { success = d3d9_shtex_init(cx, cy, window); diff --git a/plugins/win-capture/load-graphics-offsets.c b/plugins/win-capture/load-graphics-offsets.c index 0602a1cb91b9cc..4465d622cf39fd 100644 --- a/plugins/win-capture/load-graphics-offsets.c +++ b/plugins/win-capture/load-graphics-offsets.c @@ -30,6 +30,10 @@ static inline bool load_offsets_from_string(struct graphics_offsets *offsets, (uint32_t)config_get_uint(config, "d3d9", "present_ex"); offsets->d3d9.present_swap = (uint32_t)config_get_uint(config, "d3d9", "present_swap"); + offsets->d3d9.d3d9_clsoff = + (uint32_t)config_get_uint(config, "d3d9", "d3d9_clsoff"); + offsets->d3d9.is_d3d9ex_clsoff = + (uint32_t)config_get_uint(config, "d3d9", "is_d3d9ex_clsoff"); offsets->dxgi.present = (uint32_t)config_get_uint(config, "dxgi", "present"); diff --git a/plugins/win-dshow/CMakeLists.txt b/plugins/win-dshow/CMakeLists.txt index f34e978b283d11..4a9f6f87ea2718 100644 --- a/plugins/win-dshow/CMakeLists.txt +++ b/plugins/win-dshow/CMakeLists.txt @@ -1,3 +1,8 @@ +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libdshowcapture/dshowcapture.hpp") + message(STATUS "libdshowcapture submodule not found! Please fetch submodules. win-dshow plugin disabled.") + return() +endif() + project(win-dshow) find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil) diff --git a/plugins/win-dshow/data/locale/ca-ES.ini b/plugins/win-dshow/data/locale/ca-ES.ini index 725fe07dc91d72..0c5e38b43aa2cd 100644 --- a/plugins/win-dshow/data/locale/ca-ES.ini +++ b/plugins/win-dshow/data/locale/ca-ES.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Àudio de la sortida de l'escriptori (WaveOut)" UseCustomAudioDevice="Usa un dispositiu d'àudio personalitzat" AudioDevice="Dispositiu d'àudio" Buffering="Memòria intermèdia" +Buffering.ToolTip="Quan s'activa, les dades de vídeo/àudio s'emmagatzemen per assegurar una\nreproducció ho més suau i fluida possible, però a costa d'un major temps de resposta.\nQuan s'usa buffering amb una targeta capturadora de vídeo, es recomana establir\nla targeta i el programa a la mateixa velocitat de fotogrames per a un millor resultat.\n\nQuan es desactiva, es garanteix la reproducció amb un temps de resposta més baix, però a costa de perdre precisió en la\n\reproducció dels fotogrames. Això és ideal per a l'ús de càmera web, o quan voleu utilitzar la finestra de vista prèvia del\nprograma per jugar a la consola.\n\nL'autodetecció (per defecte) s'activa si el dispositiu té retard i es desactiva\nsi no té cap retard." Buffering.AutoDetect="Detecció automàtica" Buffering.Enable="Activa" Buffering.Disable="Desactiva" diff --git a/plugins/win-dshow/data/locale/cs-CZ.ini b/plugins/win-dshow/data/locale/cs-CZ.ini index 8c47ff7e344ec7..2afb79b2bad75b 100644 --- a/plugins/win-dshow/data/locale/cs-CZ.ini +++ b/plugins/win-dshow/data/locale/cs-CZ.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Poslouchat na ploše (WaveOut)" UseCustomAudioDevice="Použít vlastní zvukové zařízení" AudioDevice="Zvukové zařízení" Buffering="Načítání" +Buffering.ToolTip="Při povoleném, načte obrazová/zvuková data do vyrovnávací paměti pro zajištění\nnejplynulejšího a nejpřesnějšího možného přehrání, ale za cenu zvýšení odezvy.\nPři použití se střihovou kartou je doporučeno nastavit kartu \na program na stejnou snímkovací frekvenci.\n\nPři zakázaném zaručuje nízkou odezvu, ale to za cenu ztrátě přesnosti.\nToto je ideální pro webkamery, nebo pokud chcete použít okno\nnáhledu jako konzoli.\n\nAuto-detekce (výchozí) jej nastaví na povoleno, pouze pokud má zařízení\nzpoždění, jinak jej zakáže." Buffering.AutoDetect="Vybrat automaticky" Buffering.Enable="Povolit" Buffering.Disable="Zakázat" diff --git a/plugins/win-dshow/data/locale/de-DE.ini b/plugins/win-dshow/data/locale/de-DE.ini index 0a896a015df968..4aff5bc9a639ab 100644 --- a/plugins/win-dshow/data/locale/de-DE.ini +++ b/plugins/win-dshow/data/locale/de-DE.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Audioausgabe auf dem Desktop (WaveOut)" UseCustomAudioDevice="Benutzerdefiniertes Audiogerät verwenden" AudioDevice="Audiogerät" Buffering="Pufferung" +Buffering.ToolTip="Wenn aktiviert puffert Video/Ton Daten, um die möglichst flüssigste und\ngenaueste Wiedergabe zu gewährleisten, aber auf Kosten von erhöhter Verzögerung. Wenn mit einer Capture Card Pufferung benutzt wird,\nwird es empfohlen die Karte und das Programm (OBS Studio), auf die gleiche FPS Anzahl zu setzen, um die besten Ergebnisse zu erzielen.\n\nWenn deaktiviert, wird die niedrigste Verzögerung für die Wiedergabe gewährleistet, aber auf Kosten der Bilder-Wiedergabegenauigkeit.\nDies ist ideal für Webcams, oder wenn sie das Vorschaufenster des Programms benutzen wollen,\num zum Beispiel eine Spielkonsole zu spielen\n\nAutomatisch erkennen aktiviert es, wenn das Gerät Verzögerung hat und deaktiviert es,\nwenn es keine Verzögerung hat." Buffering.AutoDetect="Automatisch erkennen" Buffering.Enable="Aktivieren" Buffering.Disable="Deaktivieren" diff --git a/plugins/win-dshow/data/locale/en-US.ini b/plugins/win-dshow/data/locale/en-US.ini index dca867eb195a47..81568b9aee8c4a 100644 --- a/plugins/win-dshow/data/locale/en-US.ini +++ b/plugins/win-dshow/data/locale/en-US.ini @@ -25,6 +25,7 @@ AudioOutputMode.WaveOut="Output desktop audio (WaveOut)" UseCustomAudioDevice="Use custom audio device" AudioDevice="Audio Device" Buffering="Buffering" +Buffering.ToolTip="When enabled, buffers video/audio data to ensure the smoothest and most\naccurate playback possible, but at the cost of increased latency. When using\nbuffering with a video capture card, it's recommended to set the card and the\nprogram to the same framerate for best results.\n\nWhen disabled, ensures lowest latency playback, but at the cost of frame\nplayback accuracy. This is ideal for face cameras, or when you want to use the\nprogram's preview window to play a console.\n\nAuto-detect (default) sets it to enabled if the device has delay, and disabled\nif it has no delay." Buffering.AutoDetect="Auto-Detect" Buffering.Enable="Enable" Buffering.Disable="Disable" diff --git a/plugins/win-dshow/data/locale/es-ES.ini b/plugins/win-dshow/data/locale/es-ES.ini index f65dd1c39011d6..120b5031973559 100644 --- a/plugins/win-dshow/data/locale/es-ES.ini +++ b/plugins/win-dshow/data/locale/es-ES.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Salida de audio de escritorio (WaveOut)" UseCustomAudioDevice="Utilizar dispositivo de audio personalizado" AudioDevice="Dispositivo de audio" Buffering="Almacenando en buffer" +Buffering.ToolTip="Cuando se activa, almacena datos de vídeo/audio para asegurar una\nreproducción lo mas suave y fluida posible, pero a costa de un mayor tiempo de respuesta.\nCuando se usa buffering con una tarjeta capturadora de vídeo, se recomienda establecer\nla tarjeta y el programa a la misma velocidad de fotogramas para un mejor resultado.\n\nCuando se desactiva, se garantiza la reproducción con un tiempo de respuesta más bajo, pero a costa de perder precisión en la \n\reproduccion de los fotogramas. Esto es ideal para el uso de webcam, o cuando desea utilizar la ventana de vista previa de el\nprograma para jugar a una console.\n\nAutodetectar (por defecto) se activa si el dispositivo tiene retardo y se desactiva\nsi no tiene ningun retardo." Buffering.AutoDetect="Autodetectar" Buffering.Enable="Habilitado" Buffering.Disable="Deshabilitar" diff --git a/plugins/win-dshow/data/locale/eu-ES.ini b/plugins/win-dshow/data/locale/eu-ES.ini index bf31e7172c42bc..1e08c6fc6a2fc6 100644 --- a/plugins/win-dshow/data/locale/eu-ES.ini +++ b/plugins/win-dshow/data/locale/eu-ES.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Irteera mahaigaineko audioa (WaveOut)" UseCustomAudioDevice="Erabili audio gailu pertsonalizatua" AudioDevice="Audio gailua" Buffering="Bufferrereratzen" +Buffering.ToolTip="Gaitutakoan, bideo/audio datuak oroimeneratzen ditu irakurketa lehunagoa\neta zehatzagoa egiteko, baina atzerapena handitzearen ordainarekin.\nOroimeneratzea bideo harpen txartel batekin erabiltzerakoan, gomendagarria da\ntxartela eta programa frameneurri bera erabiltzeko ezartzea.\n\nEzgaitutakoan, irakurketa atzerapen txikiagoa zihurtatzen du, baina\nframe irakurketa zehaztasunaren ordainarekin. Hau egokia da aurpegi kamerentzat,\nedo programaren aurreikuspen leihoa erabiltzea nahi duzunean kontsola batean irakurtzeko.\n\nBerez-atzemanek (berezkoa) gaitu egiten du gailuak atzerapena badu, eta ezgaitu\natzerapenik ez badu." Buffering.AutoDetect="Auto-detektatu" Buffering.Enable="Gaitu" Buffering.Disable="Ezgaitu" diff --git a/plugins/win-dshow/data/locale/fi-FI.ini b/plugins/win-dshow/data/locale/fi-FI.ini index 037da982767d05..15cffcde6afd61 100644 --- a/plugins/win-dshow/data/locale/fi-FI.ini +++ b/plugins/win-dshow/data/locale/fi-FI.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Toista äänilaite (WaveOut)" UseCustomAudioDevice="Käytä mukautettua äänilaitetta" AudioDevice="Äänilaite" Buffering="Puskurointi" +Buffering.ToolTip="Käytössä ollessaan puskuroi videokuvaa ja ääntä viiveen kustannuksella,\nettä lopputulos olisi mahdollisimman sulava ja tarkka. Kun puskurointia käytetään\nvideokaappauskortin kanssa, on suositeltavaa laittaa ruudunpäivitysnopeus\nsamaksi sekä ohjelmasta, että kortista.\n\nPoissa käytöstä ollessaan mahdollistaa pienemmän viiveen ruudunpäivityksen\nkustannuksella. Webkameroille ja videokaappauslaitteille on\nparasta pitää puskurointi pois käytöstä.\n\nAutomaattinen tunnistus (oletus) ottaa asetuksen käyttöön jos laitteessa on viivettä\nja pois käytöstä jos ei." Buffering.AutoDetect="Automaattinen tunnistus" Buffering.Enable="Ota käyttöön" Buffering.Disable="Poista käytöstä" diff --git a/plugins/win-dshow/data/locale/fr-FR.ini b/plugins/win-dshow/data/locale/fr-FR.ini index c37d41bea95949..2d141a8c3e1fe2 100644 --- a/plugins/win-dshow/data/locale/fr-FR.ini +++ b/plugins/win-dshow/data/locale/fr-FR.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Sortie audio du bureau (WaveOut)" UseCustomAudioDevice="Utiliser un périphérique audio personnalisé" AudioDevice="Périphérique audio" Buffering="Mise en mémoire tampon" +Buffering.ToolTip="Activer la mise en mémoire tampon permet de conserver un flux vidéo/audio fluide et sans accrocs, au prix\nd'un peu de latence. Si vous utilisez la mise en mémoire tampon avec une carte de capture vidéo, il est\nrecommandé de configurer la carte et OBS sur le même débit d'images pour un résultat optimal.\n\nDésactiver la mise en mémoire tampon permet de réduire la latence, au prix d'éventuelles pertes d'images\n\nL'auto-détection active la mise en mémoire tampon si le périphérique a de la latence, et la désactive le cas échéant." Buffering.AutoDetect="Détecter automatiquement" Buffering.Enable="Activer" Buffering.Disable="Désactiver" diff --git a/plugins/win-dshow/data/locale/hr-HR.ini b/plugins/win-dshow/data/locale/hr-HR.ini index 33f96d7641d63c..804ba8846311a1 100644 --- a/plugins/win-dshow/data/locale/hr-HR.ini +++ b/plugins/win-dshow/data/locale/hr-HR.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Izlaz na zvuk radne površine (WaveOut)" UseCustomAudioDevice="Koristi specifičan uređaj za zvuk" AudioDevice="Uređaj za zvuk" Buffering="Baferovanje" +Buffering.ToolTip="Kada je omogućeno, baferovanje video/zvučnih podatke osigurava tečnu i najprecizniju\nmoguću reprodukciju, ali nosi i posledicu produženog kašnjenja. Kada je baferovanje u\nupotrebi sa karticom za hvatanje videa, preporučljivo je postaviti karticu i\nprogram na isti frejmrejt da dobijete najbolje rezultate.\n\nKada je onemogućeno, obezbeđuje najmanje kašnjenje reprodukcije, ali uz posledicu nepreciznosti\nreprodukovanog frejma. Ovo je idealno za kamere koje snimaju lica, ili kada želite\nda koristite programski prozor za pregled da biste igrali na konzoli.\n\nAutomatsko-otkrivanje (podrazumevano) automatski omogućava ovo ako uređaj ima kašnjenje i onemogućava\nako nema kašnjenja." Buffering.AutoDetect="Automatsko-otkrivanje" Buffering.Enable="Omogući" Buffering.Disable="Onemogući" diff --git a/plugins/win-dshow/data/locale/hu-HU.ini b/plugins/win-dshow/data/locale/hu-HU.ini index d11cc8e8590fbe..82e16dc7477ae4 100644 --- a/plugins/win-dshow/data/locale/hu-HU.ini +++ b/plugins/win-dshow/data/locale/hu-HU.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Asztali kimeneti hangeszköz (WaveOut)" UseCustomAudioDevice="Egyedi hangeszköz használata" AudioDevice="Audio eszköz" Buffering="Pufferelés" +Buffering.ToolTip="Engedélyezés esetén, az audio/video adatot puffereli, hogy biztosítsa a folyamatos\nés pontos lejátszásért, viszont ez megnövekedett késéssel járhat. Ha \nfelvevőkártyával használja, akkor ajánlott a kártya és a program \nazonos képkockafrissítésen való futtatása a legjobb eredményért.\n\nKikapcsolt állapotban, a késés a lehető legkevesebb, viszont ez a \nképlejátszás pontosságának a rovására mehet. Ez arckamerák esetében ideális, \n vagy ha a program előnézeti ablakát használja konzollal való játékhoz.\n\nAutomata érzékelés (alapértelmezett) engedélyezi\b, ha az eszközön van késés, és kikapcsolja, ha nincs." Buffering.AutoDetect="Automatikus felismerés" Buffering.Enable="Engedélyezés" Buffering.Disable="Letiltás" diff --git a/plugins/win-dshow/data/locale/ja-JP.ini b/plugins/win-dshow/data/locale/ja-JP.ini index fc84ad90cc437f..70791e64696b73 100644 --- a/plugins/win-dshow/data/locale/ja-JP.ini +++ b/plugins/win-dshow/data/locale/ja-JP.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="デスクトップ音声出力 (WaveOut)" UseCustomAudioDevice="カスタム音声デバイスを使用する" AudioDevice="音声デバイス" Buffering="バッファリング" +Buffering.ToolTip="有効の場合、最も正確で滑らかな再生を可能にするために映像/音声のデータをバッファしますが、遅延は増加します。\nビデオキャプチャカードでバッファリングを使用する場合は、最良の結果を得るためにカードとプログラムに同じフレームレートを設定することを推奨します。\n\n無効の場合、再生の遅延は最小になりますが、フレーム再生の精度が落ちます。\nこれは顔カメラや、コンソールで再生してプログラムのプレビューウィンドウを使用したい場合に最適です。\n\n自動検出 (既定) ではデバイスに遅延がある場合は有効にし、遅延がない場合は無効にします。" Buffering.AutoDetect="自動検出" Buffering.Enable="有効" Buffering.Disable="無効" diff --git a/plugins/win-dshow/data/locale/ko-KR.ini b/plugins/win-dshow/data/locale/ko-KR.ini index 06073edf81b204..d884c79e335b43 100644 --- a/plugins/win-dshow/data/locale/ko-KR.ini +++ b/plugins/win-dshow/data/locale/ko-KR.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="출력 데스크탑 오디오 (WaveOut)" UseCustomAudioDevice="사용자 지정 오디오 장치 사용" AudioDevice="오디오 장치" Buffering="버퍼링" +Buffering.ToolTip="해당 설정이 활성화가 되면 영상/소리 데이터를 버퍼링하여 가능하면 최대한 부드럽고 \n정확하게 전달될 수 있도록 합니다. 대신 지연 시간이 증가합니다. 비디오 캡쳐 카드를 통해\n버퍼링을 한다면, 그 카드와 \n프로그램이 동일한 프레임률을 사용할 것을 추천합니다.\n\n반대로 비활성화하면 지연 시간이 최대한 낮아지지만 프레임\n과 재생 정확도가 떨어집니다. 이 설정은 전면 카메라 환경이나\n프로그램의 미리보기 창을 통해 게임 콘솔을 이용할 때 이상적입니다.\n\n자동 감지 (기본) 설정은 장치에 지연 현상이 있을 경우 활성화하고 지연이 감지 되지 않으면\n비활성화합니다." Buffering.AutoDetect="자동 감지" Buffering.Enable="활성화" Buffering.Disable="비활성화" diff --git a/plugins/win-dshow/data/locale/nl-NL.ini b/plugins/win-dshow/data/locale/nl-NL.ini index ea6d57c8a2cc81..275658d500e20e 100644 --- a/plugins/win-dshow/data/locale/nl-NL.ini +++ b/plugins/win-dshow/data/locale/nl-NL.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Afspelen naar desktop-audio (WaveOut)" UseCustomAudioDevice="Gebruik aangepast audioapparaat" AudioDevice="Audioapparaat" Buffering="Bufferen" +Buffering.ToolTip="Indien ingeschakeld wordt video/audio data gebufferd om soepele en accurate\nweergave te garanderen, ten koste van een hogere vertraging. Als buffering\ngebruikt wordt, wordt het aangeraden om de kaart en het programma op\ndezelfde frame-rate in te stellen voor de beste resultaten.\n\nIndien uitgeschakeld, wordt de vertraging geminimaliseerd ten koste van\naccurate frame weergave. Dit is ideaal voor gezichtscameras, of wanneer je de\npreview van het programma wil gebruiken om een console te spelen.\n\nAutomatisch detecteren (standaard) stelt het in op ingeschakeld als het\napparaat vertraging heeft, en uitgeschakeld als het geen vertraging heeft." Buffering.AutoDetect="Automatisch detecteren" Buffering.Enable="Inschakelen" Buffering.Disable="Uitschakelen" diff --git a/plugins/win-dshow/data/locale/pl-PL.ini b/plugins/win-dshow/data/locale/pl-PL.ini index b598b5e96d9cc1..a23e4c54f47e7e 100644 --- a/plugins/win-dshow/data/locale/pl-PL.ini +++ b/plugins/win-dshow/data/locale/pl-PL.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Przekaż dźwięk dalej (WaveOut)" UseCustomAudioDevice="Użyj własnego wyjścia audio" AudioDevice="Urządzenie audio" Buffering="Buforowanie" +Buffering.ToolTip="Przy włączonej opcji obraz i dźwięk jest buforowany. Zapewnić to ma jak\nnajbardziej płynne odtwarzanie materiału kosztem zwiększonego opóźnienia.\nKorzystając z tej opcji w przypadku kart przechwytujących zaleca się\nustawienie identycznej wartości klatek na sekundę.\n\nPrzy opcji wyłączonej materiał odtwarzany jest z jak najmniejszym\nopóźnieniem nawet kosztem dokładności odtwarzania poszczególnych klatek.\nOpcja ta nadaje się idealnie do takich źródeł jak kamera filmująca twarz\nbądź gdy chcemy użyć podglądu w programie w celu grania na konsoli.\n\nOpcja automatycznie (domyślnie) sprawia, że opcja jest włączona dla urządzeń\nz opóźnieniem a wyłączona dla tych bez opóźnienia." Buffering.AutoDetect="automatycznie" Buffering.Enable="włączone" Buffering.Disable="wyłączone" diff --git a/plugins/win-dshow/data/locale/pt-BR.ini b/plugins/win-dshow/data/locale/pt-BR.ini index 95e79befa74d2b..257c887f807ede 100644 --- a/plugins/win-dshow/data/locale/pt-BR.ini +++ b/plugins/win-dshow/data/locale/pt-BR.ini @@ -20,7 +20,7 @@ VideoFormat.Unknown="Desconhecido (%1)" AudioOutputMode="Modo da saída de Audio" AudioOutputMode.Capture="Capturar apenas o áudio" AudioOutputMode.DirectSound="Saída de áudio de ambiente de trabalho (DirectSound)" -AudioOutputMode.WaveOut="Saída de áudio de ambiente de trabalho (WaveOut)" +AudioOutputMode.WaveOut="Saída de áudio do ambiente de trabalho (WaveOut)" UseCustomAudioDevice="Utilizar dispositivo de áudio personalizado" AudioDevice="Dispositivo de Áudio" Buffering="Buffering" diff --git a/plugins/win-dshow/data/locale/ru-RU.ini b/plugins/win-dshow/data/locale/ru-RU.ini index a3f45aab6b4d2a..889f31c13b8180 100644 --- a/plugins/win-dshow/data/locale/ru-RU.ini +++ b/plugins/win-dshow/data/locale/ru-RU.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Вывод системного аудио (WaveOut)" UseCustomAudioDevice="Использовать пользовательское аудиоустройство" AudioDevice="Аудиоустройство" Buffering="Буферизация" +Buffering.ToolTip="Если функция включена, аудио/видео поток буферизируется для плавного и\nточного воспроизведения, но при этом увеличивается задержка. Если вы\nиспользуете карту захвата, рекомендуем программно установить одинаковую\nчастоту кадров для лучшего результата.\n\nЕсли функция выключена, поток воспроизводится с наименьшей задержкой,\nно при этом страдает точность воспроизведения кадров. Этот вариант\nидеален для веб-камер, или если вы хотите использовать окно предпросмотра\nпрограммы для игры на консоли.\n\nАвто-обнаружение (по умолчанию), устанавливает опцию на \"Включено\", если\nустройство имеет задержку, и на \"Отключено\", если не имеет задержки." Buffering.AutoDetect="Авто-обнаружение" Buffering.Enable="Включить" Buffering.Disable="Отключить" diff --git a/plugins/win-dshow/data/locale/sr-CS.ini b/plugins/win-dshow/data/locale/sr-CS.ini index 2a9392a2694dc3..13bfebac6f9980 100644 --- a/plugins/win-dshow/data/locale/sr-CS.ini +++ b/plugins/win-dshow/data/locale/sr-CS.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Izlaz na zvuk radne površine (WaveOut)" UseCustomAudioDevice="Koristi specifičan uređaj za zvuk" AudioDevice="Uređaj za zvuk" Buffering="Baferovanje" +Buffering.ToolTip="Kada je omogućeno, baferovanje video/zvučnih podatke osigurava tečnu i najprecizniju\nmoguću reprodukciju, ali nosi i posledicu produženog kašnjenja. Kada je baferovanje u\nupotrebi sa karticom za hvatanje videa, preporučljivo je postaviti karticu i\nprogram na isti frejmrejt da dobijete najbolje rezultate.\n\nKada je onemogućeno, obezbeđuje najmanje kašnjenje reprodukcije, ali uz posledicu nepreciznosti\nreprodukovanog frejma. Ovo je idealno za kamere koje snimaju lica, ili kada želite\nda koristite programski prozor za pregled da biste igrali na konzoli.\n\nAutomatsko-otkrivanje (podrazumevano) automatski omogućava ovo ako uređaj ima kašnjenje i onemogućava\nako nema kašnjenja." Buffering.AutoDetect="Automatsko-otkrivanje" Buffering.Enable="Omogući" Buffering.Disable="Onemogući" diff --git a/plugins/win-dshow/data/locale/sr-SP.ini b/plugins/win-dshow/data/locale/sr-SP.ini index 68fe47c6a753c6..134db99347512c 100644 --- a/plugins/win-dshow/data/locale/sr-SP.ini +++ b/plugins/win-dshow/data/locale/sr-SP.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Излаз на звук радне површине (W UseCustomAudioDevice="Користи специфичан уређај за звук" AudioDevice="Уређај за звук" Buffering="Баферовање" +Buffering.ToolTip="Када је омогућено, баферовање видео/звучних података осигурава течну и најпрецизнију\nмогућу репродукцију, али носи и последицу продуженог кашњења. Када је баферовање у\nупотреби са картицом за хватање видеа, препоручљиво је поставити картицу и\nпрограм на исти фрејмрејт да добијете најбоље резултате.\n\nКада је онемогућено, обезбеђује најмање кашњење репродукције, али уз последицу непрецизности\nрепродукованог фрејма. Ово је идеално за камере које снимају лица, или када желите\nда користите програмски прозор за преглед да бисте играли на конзоли.\n\nАутоматско-откривање (подразумевано) аутоматски омогућава ово ако уређај има кашњење и онемогућава\nако нема кашњења." Buffering.AutoDetect="Аутоматско-откривање" Buffering.Enable="Омогући" Buffering.Disable="Онемогући" diff --git a/plugins/win-dshow/data/locale/sv-SE.ini b/plugins/win-dshow/data/locale/sv-SE.ini index b9ee5979cedfa3..3f27a26ebdc7d8 100644 --- a/plugins/win-dshow/data/locale/sv-SE.ini +++ b/plugins/win-dshow/data/locale/sv-SE.ini @@ -19,6 +19,8 @@ VideoFormat.Any="Valfri" VideoFormat.Unknown="Okänd (%1)" AudioOutputMode="Ljudutgångsläge" AudioOutputMode.Capture="Fånga endast ljud" +AudioOutputMode.DirectSound="Skrivbordsljud (DirectSound)" +AudioOutputMode.WaveOut="Skrivbordsljud (WaveOut)" UseCustomAudioDevice="Använd anpassad ljudenhet" AudioDevice="Ljudenhet" Buffering="Buffrar" diff --git a/plugins/win-dshow/data/locale/uk-UA.ini b/plugins/win-dshow/data/locale/uk-UA.ini index 9bdf2633f9f2ea..7e99202b54edfc 100644 --- a/plugins/win-dshow/data/locale/uk-UA.ini +++ b/plugins/win-dshow/data/locale/uk-UA.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="Виводити як Системне аудіо (Wave UseCustomAudioDevice="Інший пристрій для аудіо" AudioDevice="Аудіопристрій" Buffering="Буферизація" +Buffering.ToolTip="Коли Увімкнено, буферизує відео/аудіо данні задля плавного та точного\nвідтворення, яке тільки можливе, але за рахунок збільшення затримки.\nЯкщо буферизація використовується з картою захоплення, то для кращого\nрезультату рекомендується встановити однакову частоту кадрів\nдля програми і самого пристрою.\n\nЯкщо Вимкнено, данні програються з найменшою затримкою,\nале за рахунок плавності відтворення. Це зручно для фронтальних\nкамер, або у випадку використання вікна Перегляду програми\nдля гри у консольні ігри.\n\nАвтовизначення (за замовчанням) встановлює стан Увімкнено\nдля пристроїв з внутрішньою затримкою, і стан Вимкнено,\nякщо пристрій не має внутрішньої затримки." Buffering.AutoDetect="Автовизначення" Buffering.Enable="Увімкнути" Buffering.Disable="Вимкнено" diff --git a/plugins/win-dshow/data/locale/zh-CN.ini b/plugins/win-dshow/data/locale/zh-CN.ini index 0742ba42c886e1..94ea6083dc50bd 100644 --- a/plugins/win-dshow/data/locale/zh-CN.ini +++ b/plugins/win-dshow/data/locale/zh-CN.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="输出桌面音频(WaveOut)" UseCustomAudioDevice="使用自定义的音频设备" AudioDevice="音频设备" Buffering="正在缓冲" +Buffering.ToolTip="当启用时, 缓存视频/音频数据以确保最流畅和\n最准确的回放, 但是会增加延迟成本. 当使用\n视频卡来缓存时, 推荐设置卡和程序\n为同一个帧率来得到最好的结果.\n\n当禁用时, 确保最低的延时回放, 但是损失回放帧的准确性.\n 这是对于脸部相机是理想的, 或者当你先要使用程序的\n预览窗口来播放一个控制台.\n\n自动检测(默认) 如果设备有延时设置为启用, \n如果没有延时禁用" Buffering.AutoDetect="自动检测" Buffering.Enable="启用" Buffering.Disable="禁用" diff --git a/plugins/win-dshow/data/locale/zh-TW.ini b/plugins/win-dshow/data/locale/zh-TW.ini index 5b4793868c9723..4645531f570399 100644 --- a/plugins/win-dshow/data/locale/zh-TW.ini +++ b/plugins/win-dshow/data/locale/zh-TW.ini @@ -24,6 +24,7 @@ AudioOutputMode.WaveOut="輸出桌面音訊 (WaveOut)" UseCustomAudioDevice="使用自訂音訊裝置" AudioDevice="音訊裝置" Buffering="緩衝" +Buffering.ToolTip="啟用時,將會緩衝影音資料以確保平順且準確的播放,但將會\n增加延遲。當同時使用緩衝以及影音擷取卡時,建議將擷取卡\n與程式設定為同樣的擷取速率以獲得最好的效果。\n\n停用時,會有最少的延遲,但是畫面播放會較為不精準。建議\n使用於臉部攝影機或是利用本程式的預覽視窗來遊玩主機的情\n境。\n\n自動偵測(預設值)會在裝置有延遲時開啟,沒有延遲時關閉。" Buffering.AutoDetect="自動偵測" Buffering.Enable="啟用" Buffering.Disable="停用" diff --git a/plugins/win-dshow/win-dshow.cpp b/plugins/win-dshow/win-dshow.cpp index ded5d7662e1269..344745de07be18 100644 --- a/plugins/win-dshow/win-dshow.cpp +++ b/plugins/win-dshow/win-dshow.cpp @@ -1794,6 +1794,9 @@ static obs_properties_t *GetDShowProperties(void *obj) obs_property_list_add_int(p, TEXT_BUFFERING_OFF, (int64_t)BufferingType::Off); + obs_property_set_long_description(p, + obs_module_text("Buffering.ToolTip")); + obs_properties_add_bool(ppts, FLIP_IMAGE, TEXT_FLIP_IMAGE); /* ------------------------------------- */ diff --git a/plugins/win-mf/data/locale/pt-BR.ini b/plugins/win-mf/data/locale/pt-BR.ini index ae9598f924ea50..a97fdbfc5d4552 100644 --- a/plugins/win-mf/data/locale/pt-BR.ini +++ b/plugins/win-mf/data/locale/pt-BR.ini @@ -23,7 +23,7 @@ MF.H264.QPB="QP B-Frame" MF.H264.Profile="Perfil" MF.H264.Advanced="Avançado" -MF.H264.EncoderSWMicrosoft="Codificador da Microsoft Software H.264" +MF.H264.EncoderSWMicrosoft="Codificador H.264 da Microsoft" MF.H264.EncoderHWAMD="Motor de Codificação de Vídeo H.264 AMD (Media Foundation)" MF.H264.EncoderHWIntel="Codificador da Intel Quick Sync H.264 (Media Foundation)" MF.H264.EncoderHWNVIDIA="Codificador da NVIDIA NVENC H.264 (Media Foundation)" diff --git a/plugins/win-mf/data/locale/sv-SE.ini b/plugins/win-mf/data/locale/sv-SE.ini index 5249beb8c9e799..756083dd5b5858 100644 --- a/plugins/win-mf/data/locale/sv-SE.ini +++ b/plugins/win-mf/data/locale/sv-SE.ini @@ -4,6 +4,7 @@ Bitrate="Bithastighet" MF.H264.EncoderName="Media Foundation H264-kodare" MF.H264.Encoder="Kodarnamn" MF.H264.LowLatency="Låg latens (Avaktivera bildrute-platsförändring)" +MF.H264.BFrames="Antal efterföljande B-Frames" MF.H264.CustomBufsize="Använd anpassad buffertstorlek" MF.H264.BufferSize="Buffertstorlek" MF.H264.CustomMaxBitrate="Använd anpassad maximal bithastighet" @@ -16,10 +17,14 @@ MF.H264.VBR="VBR (varierande bithastighet)" MF.H264.CQP="CQP (konstant kvalitet)" MF.H264.MinQP="Minimal QP" MF.H264.MaxQP="Maximal QP" +MF.H264.QPI="QP I-Frame" +MF.H264.QPP="QP P-Frame" +MF.H264.QPB="QP B-Frame" MF.H264.Profile="Profil" MF.H264.Advanced="Avancerat" MF.H264.EncoderSWMicrosoft="Microsoft Software H.264-kodare" +MF.H264.EncoderHWAMD="AMD Video Coding Engine H.264-kodare (Media Foundation)" MF.H264.EncoderHWIntel="Intel Quick Sync H.264-kodare (Media Foundation)" MF.H264.EncoderHWNVIDIA="NVIDIA NVENC H.264-kodare (Media Foundation)" diff --git a/plugins/win-mf/data/locale/uk-UA.ini b/plugins/win-mf/data/locale/uk-UA.ini index f01d3fe2498ab8..dadeaafc35c19b 100644 --- a/plugins/win-mf/data/locale/uk-UA.ini +++ b/plugins/win-mf/data/locale/uk-UA.ini @@ -15,8 +15,8 @@ MF.H264.RateControl="Керування потоком" MF.H264.CBR="CBR (постійний бітрейт)" MF.H264.VBR="VBR (змінний бітрейт)" MF.H264.CQP="CQP (фіксована якість)" -MF.H264.MinQP="Мінімальне QP" -MF.H264.MaxQP="Максимальне QP" +MF.H264.MinQP="Мінімальний QP" +MF.H264.MaxQP="Максимальний QP" MF.H264.QPI="QP для I-кадрів" MF.H264.QPP="QP для P-кадрів" MF.H264.QPB="QP для B-кадрів"