From baafb6325b9811702680ca9cdca5244c72445e8d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Feb 2018 20:54:37 +0100 Subject: [PATCH] New upstream version 21.0.2+dfsg1 --- .editorconfig | 19 + .gitignore | 11 + .gitmodules | 5 +- .travis.yml | 2 +- CI/before-deploy-osx.sh | 13 +- CI/before-script-osx.sh | 7 +- CI/install-dependencies-linux.sh | 21 +- CI/install-dependencies-osx.sh | 4 +- CI/install/osx/build_app.py | 37 +- CI/util/build-package-deps-osx.sh | 24 +- CMakeLists.txt | 12 +- CONTRIBUTING.rst | 32 +- README.rst | 13 +- UI/CMakeLists.txt | 21 +- UI/adv-audio-control.cpp | 10 +- UI/api-interface.cpp | 88 +- UI/audio-encoders.cpp | 8 + UI/data/locale.ini | 12 +- UI/data/locale/af-ZA.ini | 94 + UI/data/locale/ar-SA.ini | 104 +- UI/data/locale/bg-BG.ini | 379 ++ UI/data/locale/bn-BD.ini | 2 + UI/data/locale/ca-ES.ini | 116 + UI/data/locale/cs-CZ.ini | 34 + UI/data/locale/da-DK.ini | 38 + UI/data/locale/de-DE.ini | 46 +- UI/data/locale/el-GR.ini | 274 +- UI/data/locale/en-US.ini | 39 + UI/data/locale/es-ES.ini | 45 +- UI/data/locale/et-EE.ini | 15 +- UI/data/locale/eu-ES.ini | 52 +- UI/data/locale/fi-FI.ini | 40 +- UI/data/locale/fr-FR.ini | 60 +- UI/data/locale/gl-ES.ini | 2 + UI/data/locale/he-IL.ini | 2 + UI/data/locale/hr-HR.ini | 382 +- UI/data/locale/hu-HU.ini | 38 + UI/data/locale/it-IT.ini | 280 +- UI/data/locale/ja-JP.ini | 40 +- UI/data/locale/ka-GE.ini | 147 + UI/data/locale/ko-KR.ini | 56 +- UI/data/locale/lt-LT.ini | 42 +- UI/data/locale/ms-MY.ini | 2 + UI/data/locale/nb-NO.ini | 235 +- UI/data/locale/nl-NL.ini | 40 +- UI/data/locale/nn-NO.ini | 182 + UI/data/locale/pl-PL.ini | 38 + UI/data/locale/pt-BR.ini | 64 +- UI/data/locale/pt-PT.ini | 162 + UI/data/locale/ro-RO.ini | 55 + UI/data/locale/ru-RU.ini | 40 +- UI/data/locale/sk-SK.ini | 273 +- UI/data/locale/sl-SI.ini | 84 +- UI/data/locale/sr-CS.ini | 2 + UI/data/locale/sr-SP.ini | 2 + UI/data/locale/sv-SE.ini | 41 + UI/data/locale/ta-IN.ini | 2 + UI/data/locale/th-TH.ini | 2 + UI/data/locale/tr-TR.ini | 42 +- UI/data/locale/uk-UA.ini | 44 +- UI/data/locale/vi-VN.ini | 42 +- UI/data/locale/zh-CN.ini | 42 +- UI/data/locale/zh-TW.ini | 36 +- UI/data/themes/Acri.qss | 776 +++ UI/data/themes/Acri/bot_hook.png | Bin 0 -> 109 bytes UI/data/themes/Acri/bot_hook2.png | Bin 0 -> 109 bytes UI/data/themes/Acri/checkbox_checked.png | Bin 0 -> 120 bytes .../themes/Acri/checkbox_checked_disabled.png | Bin 0 -> 119 bytes .../themes/Acri/checkbox_checked_focus.png | Bin 0 -> 120 bytes UI/data/themes/Acri/checkbox_unchecked.png | Bin 0 -> 115 bytes .../Acri/checkbox_unchecked_disabled.png | Bin 0 -> 114 bytes .../themes/Acri/checkbox_unchecked_focus.png | Bin 0 -> 117 bytes UI/data/themes/Acri/cogwheel.png | Bin 0 -> 255 bytes UI/data/themes/Acri/down_arrow.png | Bin 0 -> 527 bytes UI/data/themes/Acri/minus.png | Bin 0 -> 110 bytes UI/data/themes/Acri/mute.png | Bin 0 -> 216 bytes UI/data/themes/Acri/plus.png | Bin 0 -> 115 bytes UI/data/themes/Acri/radio_checked.png | Bin 0 -> 764 bytes .../themes/Acri/radio_checked_disabled.png | Bin 0 -> 763 bytes UI/data/themes/Acri/radio_checked_focus.png | Bin 0 -> 765 bytes UI/data/themes/Acri/radio_unchecked.png | Bin 0 -> 661 bytes .../themes/Acri/radio_unchecked_disabled.png | Bin 0 -> 659 bytes UI/data/themes/Acri/radio_unchecked_focus.png | Bin 0 -> 660 bytes UI/data/themes/Acri/sizegrip.png | Bin 0 -> 107 bytes UI/data/themes/Acri/top_hook.png | Bin 0 -> 100 bytes UI/data/themes/Acri/unmute.png | Bin 0 -> 289 bytes UI/data/themes/Acri/up_arrow.png | Bin 0 -> 505 bytes UI/data/themes/Acri/updown.png | Bin 0 -> 965 bytes UI/data/themes/Dark.qss | 70 +- UI/data/themes/Dark/refresh.png | Bin 0 -> 3275 bytes UI/data/themes/Default.qss | 17 +- UI/data/themes/Rachni.qss | 1084 +++++ UI/data/themes/Rachni/checkbox_checked.png | Bin 0 -> 147 bytes .../Rachni/checkbox_checked_disabled.png | Bin 0 -> 288 bytes .../themes/Rachni/checkbox_checked_focus.png | Bin 0 -> 147 bytes UI/data/themes/Rachni/checkbox_unchecked.png | Bin 0 -> 288 bytes .../Rachni/checkbox_unchecked_disabled.png | Bin 0 -> 279 bytes .../Rachni/checkbox_unchecked_focus.png | Bin 0 -> 141 bytes UI/data/themes/Rachni/down_arrow.png | Bin 0 -> 84 bytes UI/data/themes/Rachni/down_arrow_disabled.png | Bin 0 -> 84 bytes UI/data/themes/Rachni/left_arrow.png | Bin 0 -> 90 bytes UI/data/themes/Rachni/left_arrow_disabled.png | Bin 0 -> 90 bytes UI/data/themes/Rachni/radio_checked.png | Bin 0 -> 579 bytes .../themes/Rachni/radio_checked_disabled.png | Bin 0 -> 576 bytes UI/data/themes/Rachni/radio_checked_focus.png | Bin 0 -> 657 bytes UI/data/themes/Rachni/radio_unchecked.png | Bin 0 -> 444 bytes .../Rachni/radio_unchecked_disabled.png | Bin 0 -> 443 bytes .../themes/Rachni/radio_unchecked_focus.png | Bin 0 -> 538 bytes UI/data/themes/Rachni/right_arrow.png | Bin 0 -> 89 bytes .../themes/Rachni/right_arrow_disabled.png | Bin 0 -> 89 bytes UI/data/themes/Rachni/sizegrip.png | Bin 0 -> 107 bytes UI/data/themes/Rachni/up_arrow.png | Bin 0 -> 86 bytes UI/data/themes/Rachni/up_arrow_disabled.png | Bin 0 -> 86 bytes UI/forms/OBSBasic.ui | 1639 ++++--- UI/forms/OBSBasicFilters.ui | 25 +- UI/forms/OBSBasicSettings.ui | 4317 +++++++++-------- UI/forms/images/locked_mask.png | Bin 0 -> 289 bytes UI/forms/images/refresh.png | Bin 0 -> 2712 bytes UI/forms/images/unlocked_mask.png | Bin 0 -> 297 bytes UI/forms/obs.qrc | 3 + .../frontend-tools/CMakeLists.txt | 31 +- .../frontend-tools/captions-mssapi.hpp | 10 + .../frontend-tools/captions.cpp | 10 + .../frontend-tools/data/locale/bn-BD.ini | 2 + .../frontend-tools/data/locale/ca-ES.ini | 15 + .../frontend-tools/data/locale/cs-CZ.ini | 15 + .../frontend-tools/data/locale/da-DK.ini | 15 + .../frontend-tools/data/locale/de-DE.ini | 15 + .../frontend-tools/data/locale/el-GR.ini | 29 + .../frontend-tools/data/locale/en-US.ini | 16 + .../frontend-tools/data/locale/es-ES.ini | 17 +- .../frontend-tools/data/locale/et-EE.ini | 2 + .../frontend-tools/data/locale/eu-ES.ini | 15 + .../frontend-tools/data/locale/fi-FI.ini | 15 + .../frontend-tools/data/locale/fr-FR.ini | 15 + .../frontend-tools/data/locale/he-IL.ini | 6 + .../frontend-tools/data/locale/hr-HR.ini | 3 + .../frontend-tools/data/locale/hu-HU.ini | 15 + .../frontend-tools/data/locale/it-IT.ini | 33 +- .../frontend-tools/data/locale/ja-JP.ini | 15 + .../frontend-tools/data/locale/ka-GE.ini | 11 + .../frontend-tools/data/locale/ko-KR.ini | 15 + .../frontend-tools/data/locale/lt-LT.ini | 29 + .../frontend-tools/data/locale/ms-MY.ini | 2 + .../frontend-tools/data/locale/nb-NO.ini | 17 + .../frontend-tools/data/locale/nl-NL.ini | 15 + .../frontend-tools/data/locale/pl-PL.ini | 15 + .../frontend-tools/data/locale/pt-BR.ini | 17 +- .../frontend-tools/data/locale/pt-PT.ini | 8 + .../frontend-tools/data/locale/ro-RO.ini | 7 +- .../frontend-tools/data/locale/ru-RU.ini | 15 + .../frontend-tools/data/locale/sk-SK.ini | 19 + .../frontend-tools/data/locale/sr-CS.ini | 2 + .../frontend-tools/data/locale/sr-SP.ini | 2 + .../frontend-tools/data/locale/sv-SE.ini | 15 + .../frontend-tools/data/locale/tr-TR.ini | 15 + .../frontend-tools/data/locale/uk-UA.ini | 15 + .../frontend-tools/data/locale/vi-VN.ini | 11 + .../frontend-tools/data/locale/zh-CN.ini | 15 + .../frontend-tools/data/locale/zh-TW.ini | 15 + .../data/scripts/clock-source.lua | 119 + .../data/scripts/clock-source/dial.png | Bin 0 -> 27082 bytes .../data/scripts/clock-source/hour.png | Bin 0 -> 1635 bytes .../data/scripts/clock-source/minute.png | Bin 0 -> 1692 bytes .../data/scripts/clock-source/second.png | Bin 0 -> 2074 bytes .../frontend-tools/data/scripts/countdown.lua | 180 + .../data/scripts/instant-replay.lua | 134 + .../frontend-tools/data/scripts/url-text.py | 77 + .../frontend-tools/forms/scripts.ui | 266 + .../frontend-tools/frontend-tools-config.h.in | 19 + .../frontend-tools/frontend-tools.c | 11 + .../frontend-tools/output-timer.cpp | 6 + .../frontend-tools/scripts.cpp | 552 +++ .../frontend-tools/scripts.hpp | 50 + UI/hotkey-edit.cpp | 4 + UI/hotkey-edit.hpp | 1 + UI/installer/mp-installer.nsi | 18 +- UI/locked-checkbox.cpp | 36 + UI/locked-checkbox.hpp | 17 + UI/obs-app.cpp | 54 +- UI/obs-frontend-api/obs-frontend-api.cpp | 58 + UI/obs-frontend-api/obs-frontend-api.h | 180 +- UI/obs-frontend-api/obs-frontend-internal.hpp | 15 + UI/platform-windows.cpp | 14 +- UI/platform.hpp | 1 - UI/properties-view.cpp | 5 + UI/visibility-item-widget.cpp | 44 + UI/visibility-item-widget.hpp | 5 + UI/volume-control.cpp | 668 ++- UI/volume-control.hpp | 190 +- UI/win-update/updater/http.cpp | 7 +- UI/win-update/updater/updater.cpp | 47 +- UI/window-basic-adv-audio.cpp | 2 +- UI/window-basic-auto-config-test.cpp | 44 +- UI/window-basic-auto-config.cpp | 60 +- UI/window-basic-auto-config.hpp | 4 +- UI/window-basic-filters.cpp | 50 + UI/window-basic-filters.hpp | 5 + UI/window-basic-main-dropfiles.cpp | 72 +- UI/window-basic-main-outputs.cpp | 437 +- UI/window-basic-main-outputs.hpp | 4 + UI/window-basic-main-profiles.cpp | 113 +- UI/window-basic-main-scene-collections.cpp | 6 +- UI/window-basic-main-transitions.cpp | 187 +- UI/window-basic-main.cpp | 887 +++- UI/window-basic-main.hpp | 82 +- UI/window-basic-preview.cpp | 40 +- UI/window-basic-preview.hpp | 30 +- UI/window-basic-properties.cpp | 26 +- UI/window-basic-settings.cpp | 482 +- UI/window-basic-settings.hpp | 8 + UI/window-basic-source-select.cpp | 6 + UI/window-basic-stats.cpp | 39 +- UI/window-basic-stats.hpp | 2 +- UI/window-namedialog.cpp | 7 +- UI/window-namedialog.hpp | 3 +- UI/window-projector.cpp | 739 ++- UI/window-projector.hpp | 20 +- appveyor.yml | 6 +- cmake/Modules/CopyMSVCBins.cmake | 12 + cmake/Modules/FindLuajit.cmake | 84 + cmake/Modules/FindPythonDeps.cmake | 68 + cmake/Modules/FindSwigDeps.cmake | 43 + cmake/Modules/ObsHelpers.cmake | 9 + deps/CMakeLists.txt | 1 + deps/file-updater/file-updater/file-updater.c | 51 + deps/file-updater/file-updater/file-updater.h | 8 + deps/glad/CMakeLists.txt | 10 +- deps/libff/libff/ff-util.c | 20 +- deps/media-playback/media-playback/decode.c | 18 +- deps/media-playback/media-playback/decode.h | 14 + deps/media-playback/media-playback/media.c | 35 +- deps/media-playback/media-playback/media.h | 3 +- deps/obs-scripting/CMakeLists.txt | 178 + deps/obs-scripting/cstrcache.cpp | 28 + deps/obs-scripting/cstrcache.h | 13 + deps/obs-scripting/obs-scripting-callback.h | 91 + deps/obs-scripting/obs-scripting-config.h.in | 23 + deps/obs-scripting/obs-scripting-internal.h | 51 + deps/obs-scripting/obs-scripting-logging.c | 64 + .../obs-scripting-lua-frontend.c | 315 ++ deps/obs-scripting/obs-scripting-lua-source.c | 762 +++ deps/obs-scripting/obs-scripting-lua.c | 1297 +++++ deps/obs-scripting/obs-scripting-lua.h | 264 + .../obs-scripting-python-frontend.c | 361 ++ .../obs-scripting-python-import.c | 149 + .../obs-scripting-python-import.h | 198 + deps/obs-scripting/obs-scripting-python.c | 1725 +++++++ deps/obs-scripting/obs-scripting-python.h | 244 + deps/obs-scripting/obs-scripting.c | 457 ++ deps/obs-scripting/obs-scripting.h | 73 + deps/obs-scripting/obslua/CMakeLists.txt | 44 + deps/obs-scripting/obslua/obslua.i | 101 + deps/obs-scripting/obspython/CMakeLists.txt | 76 + deps/obs-scripting/obspython/obspython.i | 106 + docs/sphinx/Makefile | 20 + docs/sphinx/_build/.gitignore | 0 docs/sphinx/_static/.gitignore | 0 docs/sphinx/_templates/.gitignore | 0 docs/sphinx/backend-design.rst | 194 + docs/sphinx/conf.py | 171 + docs/sphinx/frontends.rst | 252 + docs/sphinx/graphics.rst | 364 ++ docs/sphinx/index.rst | 26 + docs/sphinx/make.bat | 36 + docs/sphinx/plugins.rst | 569 +++ docs/sphinx/reference-core-objects.rst | 13 + docs/sphinx/reference-core.rst | 652 +++ docs/sphinx/reference-encoders.rst | 493 ++ docs/sphinx/reference-frontend-api.rst | 489 ++ docs/sphinx/reference-libobs-callback.rst | 276 ++ .../reference-libobs-graphics-axisang.rst | 65 + .../reference-libobs-graphics-effects.rst | 334 ++ .../reference-libobs-graphics-graphics.rst | 1462 ++++++ .../reference-libobs-graphics-image-file.rst | 71 + .../sphinx/reference-libobs-graphics-math.rst | 39 + .../reference-libobs-graphics-matrix4.rst | 151 + .../sphinx/reference-libobs-graphics-quat.rst | 228 + .../sphinx/reference-libobs-graphics-vec2.rst | 253 + .../sphinx/reference-libobs-graphics-vec3.rst | 306 ++ .../sphinx/reference-libobs-graphics-vec4.rst | 282 ++ docs/sphinx/reference-libobs-graphics.rst | 17 + docs/sphinx/reference-libobs-media-io.rst | 463 ++ docs/sphinx/reference-libobs-util-base.rst | 75 + docs/sphinx/reference-libobs-util-bmem.rst | 62 + .../reference-libobs-util-circlebuf.rst | 158 + .../reference-libobs-util-config-file.rst | 312 ++ docs/sphinx/reference-libobs-util-darray.rst | 275 ++ docs/sphinx/reference-libobs-util-dstr.rst | 490 ++ .../sphinx/reference-libobs-util-platform.rst | 465 ++ .../sphinx/reference-libobs-util-profiler.rst | 327 ++ .../reference-libobs-util-serializers.rst | 171 + .../reference-libobs-util-text-lookup.rst | 56 + .../reference-libobs-util-threading.rst | 193 + docs/sphinx/reference-libobs-util.rst | 17 + docs/sphinx/reference-modules.rst | 310 ++ docs/sphinx/reference-outputs.rst | 778 +++ docs/sphinx/reference-properties.rst | 597 +++ docs/sphinx/reference-scenes.rst | 414 ++ docs/sphinx/reference-services.rst | 276 ++ docs/sphinx/reference-settings.rst | 284 ++ docs/sphinx/reference-sources.rst | 1363 ++++++ docs/sphinx/scripting.rst | 320 ++ libobs-d3d11/d3d11-duplicator.cpp | 35 +- libobs-d3d11/d3d11-rebuild.cpp | 7 +- libobs-d3d11/d3d11-subsystem.cpp | 69 +- libobs-d3d11/d3d11-subsystem.hpp | 2 + libobs-opengl/gl-cocoa.m | 2 + libobs-opengl/gl-helpers.c | 2 +- libobs-opengl/gl-helpers.h | 2 +- libobs-opengl/gl-indexbuffer.c | 16 +- libobs-opengl/gl-vertexbuffer.c | 51 +- libobs/CMakeLists.txt | 52 +- .../null/null-audio-monitoring.c | 2 +- .../pulse/pulseaudio-enum-devices.c | 33 + .../pulse/pulseaudio-output.c | 552 +++ .../pulse/pulseaudio-wrapper.c | 341 ++ .../pulse/pulseaudio-wrapper.h | 185 + libobs/audio-monitoring/win32/wasapi-output.c | 10 +- libobs/audio-monitoring/win32/wasapi-output.h | 5 +- libobs/callback/calldata.h | 11 + libobs/callback/signal.c | 119 +- libobs/callback/signal.h | 8 + libobs/data/format_conversion.effect | 19 +- libobs/graphics/effect.c | 7 + libobs/graphics/graphics-ffmpeg.c | 13 +- libobs/graphics/graphics-imports.c | 2 + libobs/graphics/graphics-internal.h | 4 + libobs/graphics/graphics-magick.c | 6 + libobs/graphics/graphics.c | 72 +- libobs/graphics/graphics.h | 15 + libobs/graphics/image-file.c | 8 +- libobs/media-io/audio-io.h | 41 +- libobs/media-io/audio-resampler-ffmpeg.c | 9 +- libobs/media-io/format-conversion.c | 16 +- libobs/media-io/media-io-defs.h | 4 - libobs/media-io/media-remux.c | 20 +- libobs/media-io/video-io.c | 2 + libobs/obs-audio-controls.c | 246 +- libobs/obs-audio-controls.h | 19 +- libobs/obs-config.h | 4 +- libobs/obs-data.c | 2 +- libobs/obs-encoder.c | 4 +- libobs/obs-ffmpeg-compat.h | 9 + libobs/obs-hotkey.c | 2 +- libobs/obs-hotkey.h | 12 +- libobs/obs-hotkeys.h | 2 + libobs/obs-internal.h | 14 +- libobs/obs-module.c | 10 +- libobs/obs-module.h | 3 + libobs/obs-nix.c | 95 +- libobs/obs-output.c | 48 +- libobs/obs-output.h | 4 + libobs/obs-properties.c | 68 +- libobs/obs-properties.h | 8 + libobs/obs-scene.c | 67 +- libobs/obs-scene.h | 3 + libobs/obs-service.c | 10 + libobs/obs-service.h | 2 + libobs/obs-source-transition.c | 84 +- libobs/obs-source.c | 147 +- libobs/obs-source.h | 27 + libobs/obs-video.c | 40 +- libobs/obs-windows.c | 190 + libobs/obs.c | 102 +- libobs/obs.h | 67 + libobs/obsconfig.h.in | 4 + libobs/util/base.h | 2 +- libobs/util/circlebuf.h | 52 + libobs/util/dstr.c | 73 +- libobs/util/platform-cocoa.m | 65 + libobs/util/platform-nix.c | 243 +- libobs/util/platform-windows.c | 85 +- libobs/util/platform.c | 3 +- libobs/util/platform.h | 12 + libobs/util/profiler.c | 9 +- libobs/util/threading.h | 6 + libobs/util/windows/win-registry.h | 38 + plugins/CMakeLists.txt | 16 +- .../coreaudio-encoder/data/locale/el-GR.ini | 2 + .../coreaudio-encoder/data/locale/sk-SK.ini | 2 + .../coreaudio-encoder/data/locale/uk-UA.ini | 2 +- .../coreaudio-encoder/data/locale/vi-VN.ini | 4 + plugins/coreaudio-encoder/encoder.cpp | 49 +- plugins/decklink/audio-repack.c | 126 +- plugins/decklink/audio-repack.h | 5 +- plugins/decklink/data/locale/ca-ES.ini | 14 +- plugins/decklink/data/locale/cs-CZ.ini | 14 +- plugins/decklink/data/locale/da-DK.ini | 16 +- plugins/decklink/data/locale/de-DE.ini | 10 +- plugins/decklink/data/locale/el-GR.ini | 9 + plugins/decklink/data/locale/en-US.ini | 10 +- plugins/decklink/data/locale/es-ES.ini | 14 +- plugins/decklink/data/locale/eu-ES.ini | 14 +- plugins/decklink/data/locale/fi-FI.ini | 10 +- plugins/decklink/data/locale/fr-FR.ini | 16 +- plugins/decklink/data/locale/hu-HU.ini | 10 +- plugins/decklink/data/locale/it-IT.ini | 12 +- plugins/decklink/data/locale/ja-JP.ini | 10 +- plugins/decklink/data/locale/ko-KR.ini | 14 +- plugins/decklink/data/locale/nb-NO.ini | 14 + plugins/decklink/data/locale/nl-NL.ini | 10 +- plugins/decklink/data/locale/pl-PL.ini | 10 +- plugins/decklink/data/locale/pt-BR.ini | 10 +- plugins/decklink/data/locale/pt-PT.ini | 3 + plugins/decklink/data/locale/ru-RU.ini | 10 +- plugins/decklink/data/locale/sk-SK.ini | 13 + plugins/decklink/data/locale/sv-SE.ini | 14 +- plugins/decklink/data/locale/tr-TR.ini | 12 +- plugins/decklink/data/locale/uk-UA.ini | 10 +- plugins/decklink/data/locale/vi-VN.ini | 9 + plugins/decklink/data/locale/zh-CN.ini | 10 +- plugins/decklink/data/locale/zh-TW.ini | 10 +- plugins/decklink/decklink-device-instance.cpp | 159 +- plugins/decklink/decklink-device-instance.hpp | 7 + plugins/decklink/decklink-device-mode.cpp | 19 + plugins/decklink/decklink-device-mode.hpp | 5 + plugins/decklink/decklink-device.cpp | 18 +- plugins/decklink/decklink.cpp | 2 + plugins/decklink/decklink.hpp | 14 + plugins/decklink/platform.hpp | 3 + plugins/decklink/plugin-main.cpp | 100 +- plugins/image-source/data/locale/ca-ES.ini | 16 + plugins/image-source/data/locale/cs-CZ.ini | 14 + plugins/image-source/data/locale/da-DK.ini | 14 + plugins/image-source/data/locale/de-DE.ini | 16 +- plugins/image-source/data/locale/el-GR.ini | 17 +- plugins/image-source/data/locale/en-US.ini | 14 + plugins/image-source/data/locale/es-ES.ini | 14 + plugins/image-source/data/locale/eu-ES.ini | 14 + plugins/image-source/data/locale/fi-FI.ini | 14 + plugins/image-source/data/locale/fr-FR.ini | 14 + plugins/image-source/data/locale/hu-HU.ini | 14 + plugins/image-source/data/locale/it-IT.ini | 16 + plugins/image-source/data/locale/ja-JP.ini | 14 + plugins/image-source/data/locale/ko-KR.ini | 14 + plugins/image-source/data/locale/nb-NO.ini | 20 + plugins/image-source/data/locale/nl-NL.ini | 14 + plugins/image-source/data/locale/pl-PL.ini | 14 + plugins/image-source/data/locale/pt-BR.ini | 14 + plugins/image-source/data/locale/ro-RO.ini | 3 + plugins/image-source/data/locale/ru-RU.ini | 14 + plugins/image-source/data/locale/sk-SK.ini | 20 + plugins/image-source/data/locale/sv-SE.ini | 15 + plugins/image-source/data/locale/tr-TR.ini | 14 + plugins/image-source/data/locale/uk-UA.ini | 16 +- plugins/image-source/data/locale/vi-VN.ini | 36 + plugins/image-source/data/locale/zh-CN.ini | 14 + plugins/image-source/data/locale/zh-TW.ini | 14 + plugins/image-source/obs-slideshow.c | 316 +- plugins/linux-alsa/alsa-input.c | 18 +- plugins/linux-alsa/data/locale/el-GR.ini | 3 + plugins/linux-capture/data/locale/da-DK.ini | 16 +- plugins/linux-capture/data/locale/el-GR.ini | 1 + plugins/linux-capture/data/locale/ko-KR.ini | 2 +- plugins/linux-capture/data/locale/sk-SK.ini | 1 + plugins/linux-capture/xcompcap-helper.cpp | 3 + plugins/linux-capture/xcompcap-main.cpp | 17 +- plugins/linux-jack/data/locale/vi-VN.ini | 1 + plugins/linux-jack/jack-wrapper.c | 1 - .../linux-pulseaudio/data/locale/ko-KR.ini | 2 +- .../linux-pulseaudio/data/locale/vi-VN.ini | 4 + plugins/linux-pulseaudio/pulse-input.c | 189 +- plugins/linux-pulseaudio/pulse-wrapper.c | 22 + plugins/linux-pulseaudio/pulse-wrapper.h | 14 + plugins/linux-v4l2/data/locale/ko-KR.ini | 2 +- plugins/linux-v4l2/data/locale/tr-TR.ini | 2 +- plugins/linux-v4l2/data/locale/vi-VN.ini | 6 + plugins/linux-v4l2/v4l2-helpers.h | 7 + plugins/mac-avcapture/data/locale/el-GR.ini | 8 + plugins/mac-avcapture/data/locale/eu-ES.ini | 2 +- plugins/mac-avcapture/data/locale/sk-SK.ini | 3 + plugins/mac-avcapture/data/locale/vi-VN.ini | 4 + plugins/mac-capture/data/locale/eu-ES.ini | 4 +- plugins/mac-capture/data/locale/vi-VN.ini | 18 + plugins/mac-capture/mac-audio.c | 34 +- plugins/mac-capture/mac-window-capture.m | 9 + plugins/mac-syphon/data/locale/sk-SK.ini | 1 + plugins/mac-syphon/data/locale/vi-VN.ini | 3 + plugins/mac-vth264/data/locale/el-GR.ini | 8 + plugins/mac-vth264/data/locale/sk-SK.ini | 9 +- plugins/obs-ffmpeg/CMakeLists.txt | 2 +- plugins/obs-ffmpeg/data/locale/ar-SA.ini | 1 + plugins/obs-ffmpeg/data/locale/bg-BG.ini | 1 + plugins/obs-ffmpeg/data/locale/bn-BD.ini | 1 + plugins/obs-ffmpeg/data/locale/ca-ES.ini | 9 + plugins/obs-ffmpeg/data/locale/cs-CZ.ini | 6 + plugins/obs-ffmpeg/data/locale/da-DK.ini | 8 +- plugins/obs-ffmpeg/data/locale/de-DE.ini | 10 +- plugins/obs-ffmpeg/data/locale/el-GR.ini | 32 +- plugins/obs-ffmpeg/data/locale/en-US.ini | 5 + plugins/obs-ffmpeg/data/locale/es-ES.ini | 6 + plugins/obs-ffmpeg/data/locale/et-EE.ini | 1 + plugins/obs-ffmpeg/data/locale/eu-ES.ini | 6 + plugins/obs-ffmpeg/data/locale/fi-FI.ini | 8 +- plugins/obs-ffmpeg/data/locale/fr-FR.ini | 6 + plugins/obs-ffmpeg/data/locale/gl-ES.ini | 1 + plugins/obs-ffmpeg/data/locale/he-IL.ini | 1 + plugins/obs-ffmpeg/data/locale/hi-IN.ini | 1 + plugins/obs-ffmpeg/data/locale/hr-HR.ini | 1 + plugins/obs-ffmpeg/data/locale/hu-HU.ini | 6 + plugins/obs-ffmpeg/data/locale/it-IT.ini | 9 + plugins/obs-ffmpeg/data/locale/ja-JP.ini | 6 + plugins/obs-ffmpeg/data/locale/ko-KR.ini | 6 + plugins/obs-ffmpeg/data/locale/nb-NO.ini | 12 + plugins/obs-ffmpeg/data/locale/nl-NL.ini | 6 + plugins/obs-ffmpeg/data/locale/pl-PL.ini | 6 + plugins/obs-ffmpeg/data/locale/pt-BR.ini | 6 + plugins/obs-ffmpeg/data/locale/pt-PT.ini | 1 + plugins/obs-ffmpeg/data/locale/ro-RO.ini | 1 + plugins/obs-ffmpeg/data/locale/ru-RU.ini | 6 + plugins/obs-ffmpeg/data/locale/sk-SK.ini | 22 + plugins/obs-ffmpeg/data/locale/sl-SI.ini | 1 + plugins/obs-ffmpeg/data/locale/sr-CS.ini | 1 + plugins/obs-ffmpeg/data/locale/sr-SP.ini | 1 + plugins/obs-ffmpeg/data/locale/sv-SE.ini | 6 + plugins/obs-ffmpeg/data/locale/th-TH.ini | 1 + plugins/obs-ffmpeg/data/locale/tr-TR.ini | 6 + plugins/obs-ffmpeg/data/locale/uk-UA.ini | 12 +- plugins/obs-ffmpeg/data/locale/vi-VN.ini | 18 +- plugins/obs-ffmpeg/data/locale/zh-CN.ini | 6 + plugins/obs-ffmpeg/data/locale/zh-TW.ini | 6 + plugins/obs-ffmpeg/dynlink_cuda.h | 98 + plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c | 17 +- plugins/obs-ffmpeg/nvEncodeAPI.h | 3324 +++++++++++++ ...mpeg-aac.c => obs-ffmpeg-audio-encoders.c} | 197 +- plugins/obs-ffmpeg/obs-ffmpeg-compat.h | 10 + plugins/obs-ffmpeg/obs-ffmpeg-mux.c | 35 +- plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c | 34 +- plugins/obs-ffmpeg/obs-ffmpeg-output.c | 99 +- plugins/obs-ffmpeg/obs-ffmpeg-source.c | 72 +- plugins/obs-ffmpeg/obs-ffmpeg.c | 33 +- plugins/obs-filters/CMakeLists.txt | 6 + plugins/obs-filters/color-grade-filter.c | 7 +- plugins/obs-filters/compressor-filter.c | 348 +- .../data/color_grade_filter.effect | 3 +- plugins/obs-filters/data/locale/ca-ES.ini | 2 + plugins/obs-filters/data/locale/cs-CZ.ini | 1 + plugins/obs-filters/data/locale/da-DK.ini | 1 + plugins/obs-filters/data/locale/de-DE.ini | 1 + plugins/obs-filters/data/locale/el-GR.ini | 8 + plugins/obs-filters/data/locale/en-US.ini | 1 + plugins/obs-filters/data/locale/es-ES.ini | 1 + plugins/obs-filters/data/locale/eu-ES.ini | 3 +- plugins/obs-filters/data/locale/fi-FI.ini | 1 + plugins/obs-filters/data/locale/fr-FR.ini | 2 + plugins/obs-filters/data/locale/hu-HU.ini | 1 + plugins/obs-filters/data/locale/it-IT.ini | 5 +- plugins/obs-filters/data/locale/ja-JP.ini | 1 + plugins/obs-filters/data/locale/ka-GE.ini | 4 + plugins/obs-filters/data/locale/ko-KR.ini | 1 + plugins/obs-filters/data/locale/nb-NO.ini | 14 + plugins/obs-filters/data/locale/nl-NL.ini | 1 + plugins/obs-filters/data/locale/pl-PL.ini | 1 + plugins/obs-filters/data/locale/pt-BR.ini | 1 + plugins/obs-filters/data/locale/ru-RU.ini | 3 +- plugins/obs-filters/data/locale/sk-SK.ini | 41 + plugins/obs-filters/data/locale/sv-SE.ini | 2 + plugins/obs-filters/data/locale/tr-TR.ini | 1 + plugins/obs-filters/data/locale/uk-UA.ini | 1 + plugins/obs-filters/data/locale/vi-VN.ini | 28 + plugins/obs-filters/data/locale/zh-CN.ini | 1 + plugins/obs-filters/data/locale/zh-TW.ini | 1 + plugins/obs-filters/gain-filter.c | 9 +- plugins/obs-filters/gpu-delay.c | 3 +- plugins/obs-filters/noise-gate-filter.c | 9 +- plugins/obs-filters/noise-suppress-filter.c | 18 +- plugins/obs-libfdk/data/locale/vi-VN.ini | 2 + plugins/obs-libfdk/obs-libfdk.c | 12 +- plugins/obs-outputs/CMakeLists.txt | 67 +- plugins/obs-outputs/data/locale/ca-ES.ini | 7 + plugins/obs-outputs/data/locale/da-DK.ini | 2 +- plugins/obs-outputs/data/locale/es-ES.ini | 4 + plugins/obs-outputs/data/locale/fr-FR.ini | 4 + plugins/obs-outputs/data/locale/it-IT.ini | 7 + plugins/obs-outputs/data/locale/nb-NO.ini | 7 + plugins/obs-outputs/data/locale/ru-RU.ini | 7 + plugins/obs-outputs/data/locale/sk-SK.ini | 5 +- plugins/obs-outputs/data/locale/sv-SE.ini | 1 + plugins/obs-outputs/data/locale/vi-VN.ini | 2 + plugins/obs-outputs/flv-mux.c | 36 +- plugins/obs-outputs/flv-mux.h | 6 +- plugins/obs-outputs/flv-output.c | 135 +- plugins/obs-outputs/ftl-stream.c | 1166 +++++ plugins/obs-outputs/net-if.c | 2 + plugins/obs-outputs/null-output.c | 2 +- plugins/obs-outputs/obs-outputs-config.h.in | 15 + plugins/obs-outputs/obs-outputs.c | 8 + plugins/obs-outputs/rtmp-stream.c | 72 +- plugins/obs-outputs/rtmp-stream.h | 3 + plugins/obs-text/data/locale/el-GR.ini | 13 + plugins/obs-text/data/locale/ko-KR.ini | 2 +- plugins/obs-text/data/locale/nb-NO.ini | 9 + plugins/obs-text/data/locale/pt-PT.ini | 17 + plugins/obs-text/data/locale/sk-SK.ini | 33 +- plugins/obs-text/data/locale/vi-VN.ini | 10 + plugins/obs-text/gdiplus/obs-text.cpp | 8 +- plugins/obs-transitions/CMakeLists.txt | 1 + plugins/obs-transitions/data/locale/ca-ES.ini | 14 + plugins/obs-transitions/data/locale/cs-CZ.ini | 14 + plugins/obs-transitions/data/locale/da-DK.ini | 14 + plugins/obs-transitions/data/locale/de-DE.ini | 14 + plugins/obs-transitions/data/locale/el-GR.ini | 3 + plugins/obs-transitions/data/locale/en-US.ini | 14 + plugins/obs-transitions/data/locale/es-ES.ini | 14 + plugins/obs-transitions/data/locale/eu-ES.ini | 14 + plugins/obs-transitions/data/locale/fi-FI.ini | 14 + plugins/obs-transitions/data/locale/fr-FR.ini | 14 + plugins/obs-transitions/data/locale/hu-HU.ini | 14 + plugins/obs-transitions/data/locale/it-IT.ini | 14 + plugins/obs-transitions/data/locale/ja-JP.ini | 14 + plugins/obs-transitions/data/locale/ko-KR.ini | 14 + plugins/obs-transitions/data/locale/nb-NO.ini | 49 + plugins/obs-transitions/data/locale/nl-NL.ini | 14 + plugins/obs-transitions/data/locale/pl-PL.ini | 14 + plugins/obs-transitions/data/locale/pt-BR.ini | 14 + plugins/obs-transitions/data/locale/ru-RU.ini | 14 + plugins/obs-transitions/data/locale/sk-SK.ini | 29 + plugins/obs-transitions/data/locale/sv-SE.ini | 14 + plugins/obs-transitions/data/locale/tr-TR.ini | 14 + plugins/obs-transitions/data/locale/uk-UA.ini | 16 +- plugins/obs-transitions/data/locale/vi-VN.ini | 20 + plugins/obs-transitions/data/locale/zh-CN.ini | 14 + plugins/obs-transitions/data/locale/zh-TW.ini | 14 + plugins/obs-transitions/obs-transitions.c | 2 + plugins/obs-transitions/transition-stinger.c | 370 ++ plugins/obs-x264/data/locale/el-GR.ini | 3 +- plugins/obs-x264/data/locale/ka-GE.ini | 2 + plugins/obs-x264/data/locale/ko-KR.ini | 2 +- plugins/obs-x264/obs-x264.c | 25 +- plugins/rtmp-services/CMakeLists.txt | 3 + plugins/rtmp-services/data/locale/ca-ES.ini | 1 + plugins/rtmp-services/data/locale/cs-CZ.ini | 1 + plugins/rtmp-services/data/locale/da-DK.ini | 1 + plugins/rtmp-services/data/locale/de-DE.ini | 1 + plugins/rtmp-services/data/locale/en-US.ini | 1 + plugins/rtmp-services/data/locale/es-ES.ini | 1 + plugins/rtmp-services/data/locale/eu-ES.ini | 1 + plugins/rtmp-services/data/locale/fi-FI.ini | 1 + plugins/rtmp-services/data/locale/fr-FR.ini | 1 + plugins/rtmp-services/data/locale/hu-HU.ini | 1 + plugins/rtmp-services/data/locale/it-IT.ini | 1 + plugins/rtmp-services/data/locale/ja-JP.ini | 1 + plugins/rtmp-services/data/locale/ko-KR.ini | 1 + plugins/rtmp-services/data/locale/nb-NO.ini | 1 + plugins/rtmp-services/data/locale/nl-NL.ini | 1 + plugins/rtmp-services/data/locale/pl-PL.ini | 1 + plugins/rtmp-services/data/locale/pt-BR.ini | 3 +- plugins/rtmp-services/data/locale/ru-RU.ini | 1 + plugins/rtmp-services/data/locale/sk-SK.ini | 1 + plugins/rtmp-services/data/locale/sv-SE.ini | 1 + plugins/rtmp-services/data/locale/tr-TR.ini | 1 + plugins/rtmp-services/data/locale/uk-UA.ini | 1 + plugins/rtmp-services/data/locale/vi-VN.ini | 11 + plugins/rtmp-services/data/locale/zh-CN.ini | 1 + plugins/rtmp-services/data/locale/zh-TW.ini | 1 + plugins/rtmp-services/data/package.json | 4 +- plugins/rtmp-services/data/services.json | 506 +- plugins/rtmp-services/lookup-config.h.in | 17 + plugins/rtmp-services/rtmp-common.c | 108 +- plugins/rtmp-services/rtmp-services-main.c | 42 +- plugins/rtmp-services/twitch.c | 224 + plugins/rtmp-services/twitch.h | 11 + plugins/text-freetype2/data/locale/el-GR.ini | 1 + plugins/text-freetype2/data/locale/ko-KR.ini | 2 +- plugins/text-freetype2/data/locale/vi-VN.ini | 14 + plugins/text-freetype2/text-freetype2.c | 8 +- plugins/text-freetype2/text-freetype2.h | 1 + plugins/text-freetype2/text-functionality.c | 6 - plugins/vlc-video/data/locale/ca-ES.ini | 6 + plugins/vlc-video/data/locale/cs-CZ.ini | 6 + plugins/vlc-video/data/locale/da-DK.ini | 16 +- plugins/vlc-video/data/locale/de-DE.ini | 8 +- plugins/vlc-video/data/locale/el-GR.ini | 15 + plugins/vlc-video/data/locale/en-US.ini | 6 + plugins/vlc-video/data/locale/es-ES.ini | 6 + plugins/vlc-video/data/locale/eu-ES.ini | 6 + plugins/vlc-video/data/locale/fi-FI.ini | 6 + plugins/vlc-video/data/locale/fr-FR.ini | 6 + plugins/vlc-video/data/locale/hu-HU.ini | 6 + plugins/vlc-video/data/locale/it-IT.ini | 7 + plugins/vlc-video/data/locale/ja-JP.ini | 6 + plugins/vlc-video/data/locale/ko-KR.ini | 6 + plugins/vlc-video/data/locale/nb-NO.ini | 7 + plugins/vlc-video/data/locale/nl-NL.ini | 6 + plugins/vlc-video/data/locale/pl-PL.ini | 6 + plugins/vlc-video/data/locale/pt-BR.ini | 6 + plugins/vlc-video/data/locale/ru-RU.ini | 6 + plugins/vlc-video/data/locale/sk-SK.ini | 13 + plugins/vlc-video/data/locale/sv-SE.ini | 6 + plugins/vlc-video/data/locale/tr-TR.ini | 6 + plugins/vlc-video/data/locale/uk-UA.ini | 10 +- plugins/vlc-video/data/locale/vi-VN.ini | 15 + plugins/vlc-video/data/locale/zh-CN.ini | 6 + plugins/vlc-video/data/locale/zh-TW.ini | 6 + plugins/vlc-video/vlc-video-plugin.c | 4 + plugins/vlc-video/vlc-video-plugin.h | 6 + plugins/vlc-video/vlc-video-source.c | 183 +- test/osx/test.mm | 2 +- test/test-input/CMakeLists.txt | 4 + test/test-input/sync-async-source.c | 143 + test/test-input/sync-audio-buffering.c | 200 + test/test-input/sync-pair-aud.c | 137 + test/test-input/sync-pair-vid.c | 132 + test/test-input/test-input.c | 8 + test/win/test.cpp | 2 +- 706 files changed, 49633 insertions(+), 5044 deletions(-) create mode 100644 .editorconfig create mode 100644 UI/data/locale/af-ZA.ini create mode 100644 UI/data/locale/ka-GE.ini create mode 100644 UI/data/locale/nn-NO.ini create mode 100644 UI/data/themes/Acri.qss create mode 100644 UI/data/themes/Acri/bot_hook.png create mode 100644 UI/data/themes/Acri/bot_hook2.png create mode 100644 UI/data/themes/Acri/checkbox_checked.png create mode 100644 UI/data/themes/Acri/checkbox_checked_disabled.png create mode 100644 UI/data/themes/Acri/checkbox_checked_focus.png create mode 100644 UI/data/themes/Acri/checkbox_unchecked.png create mode 100644 UI/data/themes/Acri/checkbox_unchecked_disabled.png create mode 100644 UI/data/themes/Acri/checkbox_unchecked_focus.png create mode 100644 UI/data/themes/Acri/cogwheel.png create mode 100644 UI/data/themes/Acri/down_arrow.png create mode 100644 UI/data/themes/Acri/minus.png create mode 100644 UI/data/themes/Acri/mute.png create mode 100644 UI/data/themes/Acri/plus.png create mode 100644 UI/data/themes/Acri/radio_checked.png create mode 100644 UI/data/themes/Acri/radio_checked_disabled.png create mode 100644 UI/data/themes/Acri/radio_checked_focus.png create mode 100644 UI/data/themes/Acri/radio_unchecked.png create mode 100644 UI/data/themes/Acri/radio_unchecked_disabled.png create mode 100644 UI/data/themes/Acri/radio_unchecked_focus.png create mode 100644 UI/data/themes/Acri/sizegrip.png create mode 100644 UI/data/themes/Acri/top_hook.png create mode 100644 UI/data/themes/Acri/unmute.png create mode 100644 UI/data/themes/Acri/up_arrow.png create mode 100644 UI/data/themes/Acri/updown.png create mode 100644 UI/data/themes/Dark/refresh.png create mode 100644 UI/data/themes/Rachni.qss create mode 100644 UI/data/themes/Rachni/checkbox_checked.png create mode 100644 UI/data/themes/Rachni/checkbox_checked_disabled.png create mode 100644 UI/data/themes/Rachni/checkbox_checked_focus.png create mode 100644 UI/data/themes/Rachni/checkbox_unchecked.png create mode 100644 UI/data/themes/Rachni/checkbox_unchecked_disabled.png create mode 100644 UI/data/themes/Rachni/checkbox_unchecked_focus.png create mode 100644 UI/data/themes/Rachni/down_arrow.png create mode 100644 UI/data/themes/Rachni/down_arrow_disabled.png create mode 100644 UI/data/themes/Rachni/left_arrow.png create mode 100644 UI/data/themes/Rachni/left_arrow_disabled.png create mode 100644 UI/data/themes/Rachni/radio_checked.png create mode 100644 UI/data/themes/Rachni/radio_checked_disabled.png create mode 100644 UI/data/themes/Rachni/radio_checked_focus.png create mode 100644 UI/data/themes/Rachni/radio_unchecked.png create mode 100644 UI/data/themes/Rachni/radio_unchecked_disabled.png create mode 100644 UI/data/themes/Rachni/radio_unchecked_focus.png create mode 100644 UI/data/themes/Rachni/right_arrow.png create mode 100644 UI/data/themes/Rachni/right_arrow_disabled.png create mode 100644 UI/data/themes/Rachni/sizegrip.png create mode 100644 UI/data/themes/Rachni/up_arrow.png create mode 100644 UI/data/themes/Rachni/up_arrow_disabled.png create mode 100644 UI/forms/images/locked_mask.png create mode 100644 UI/forms/images/refresh.png create mode 100644 UI/forms/images/unlocked_mask.png create mode 100644 UI/frontend-plugins/frontend-tools/data/locale/el-GR.ini create mode 100644 UI/frontend-plugins/frontend-tools/data/locale/ka-GE.ini create mode 100644 UI/frontend-plugins/frontend-tools/data/locale/lt-LT.ini create mode 100644 UI/frontend-plugins/frontend-tools/data/scripts/clock-source.lua create mode 100644 UI/frontend-plugins/frontend-tools/data/scripts/clock-source/dial.png create mode 100644 UI/frontend-plugins/frontend-tools/data/scripts/clock-source/hour.png create mode 100644 UI/frontend-plugins/frontend-tools/data/scripts/clock-source/minute.png create mode 100644 UI/frontend-plugins/frontend-tools/data/scripts/clock-source/second.png create mode 100644 UI/frontend-plugins/frontend-tools/data/scripts/countdown.lua create mode 100644 UI/frontend-plugins/frontend-tools/data/scripts/instant-replay.lua create mode 100644 UI/frontend-plugins/frontend-tools/data/scripts/url-text.py create mode 100644 UI/frontend-plugins/frontend-tools/forms/scripts.ui create mode 100644 UI/frontend-plugins/frontend-tools/scripts.cpp create mode 100644 UI/frontend-plugins/frontend-tools/scripts.hpp create mode 100644 UI/locked-checkbox.cpp create mode 100644 UI/locked-checkbox.hpp create mode 100644 cmake/Modules/FindLuajit.cmake create mode 100644 cmake/Modules/FindPythonDeps.cmake create mode 100644 cmake/Modules/FindSwigDeps.cmake create mode 100644 deps/obs-scripting/CMakeLists.txt create mode 100644 deps/obs-scripting/cstrcache.cpp create mode 100644 deps/obs-scripting/cstrcache.h create mode 100644 deps/obs-scripting/obs-scripting-callback.h create mode 100644 deps/obs-scripting/obs-scripting-config.h.in create mode 100644 deps/obs-scripting/obs-scripting-internal.h create mode 100644 deps/obs-scripting/obs-scripting-logging.c create mode 100644 deps/obs-scripting/obs-scripting-lua-frontend.c create mode 100644 deps/obs-scripting/obs-scripting-lua-source.c create mode 100644 deps/obs-scripting/obs-scripting-lua.c create mode 100644 deps/obs-scripting/obs-scripting-lua.h create mode 100644 deps/obs-scripting/obs-scripting-python-frontend.c create mode 100644 deps/obs-scripting/obs-scripting-python-import.c create mode 100644 deps/obs-scripting/obs-scripting-python-import.h create mode 100644 deps/obs-scripting/obs-scripting-python.c create mode 100644 deps/obs-scripting/obs-scripting-python.h create mode 100644 deps/obs-scripting/obs-scripting.c create mode 100644 deps/obs-scripting/obs-scripting.h create mode 100644 deps/obs-scripting/obslua/CMakeLists.txt create mode 100644 deps/obs-scripting/obslua/obslua.i create mode 100644 deps/obs-scripting/obspython/CMakeLists.txt create mode 100644 deps/obs-scripting/obspython/obspython.i create mode 100644 docs/sphinx/Makefile create mode 100644 docs/sphinx/_build/.gitignore create mode 100644 docs/sphinx/_static/.gitignore create mode 100644 docs/sphinx/_templates/.gitignore create mode 100644 docs/sphinx/backend-design.rst create mode 100644 docs/sphinx/conf.py create mode 100644 docs/sphinx/frontends.rst create mode 100644 docs/sphinx/graphics.rst create mode 100644 docs/sphinx/index.rst create mode 100644 docs/sphinx/make.bat create mode 100644 docs/sphinx/plugins.rst create mode 100644 docs/sphinx/reference-core-objects.rst create mode 100644 docs/sphinx/reference-core.rst create mode 100644 docs/sphinx/reference-encoders.rst create mode 100644 docs/sphinx/reference-frontend-api.rst create mode 100644 docs/sphinx/reference-libobs-callback.rst create mode 100644 docs/sphinx/reference-libobs-graphics-axisang.rst create mode 100644 docs/sphinx/reference-libobs-graphics-effects.rst create mode 100644 docs/sphinx/reference-libobs-graphics-graphics.rst create mode 100644 docs/sphinx/reference-libobs-graphics-image-file.rst create mode 100644 docs/sphinx/reference-libobs-graphics-math.rst create mode 100644 docs/sphinx/reference-libobs-graphics-matrix4.rst create mode 100644 docs/sphinx/reference-libobs-graphics-quat.rst create mode 100644 docs/sphinx/reference-libobs-graphics-vec2.rst create mode 100644 docs/sphinx/reference-libobs-graphics-vec3.rst create mode 100644 docs/sphinx/reference-libobs-graphics-vec4.rst create mode 100644 docs/sphinx/reference-libobs-graphics.rst create mode 100644 docs/sphinx/reference-libobs-media-io.rst create mode 100644 docs/sphinx/reference-libobs-util-base.rst create mode 100644 docs/sphinx/reference-libobs-util-bmem.rst create mode 100644 docs/sphinx/reference-libobs-util-circlebuf.rst create mode 100644 docs/sphinx/reference-libobs-util-config-file.rst create mode 100644 docs/sphinx/reference-libobs-util-darray.rst create mode 100644 docs/sphinx/reference-libobs-util-dstr.rst create mode 100644 docs/sphinx/reference-libobs-util-platform.rst create mode 100644 docs/sphinx/reference-libobs-util-profiler.rst create mode 100644 docs/sphinx/reference-libobs-util-serializers.rst create mode 100644 docs/sphinx/reference-libobs-util-text-lookup.rst create mode 100644 docs/sphinx/reference-libobs-util-threading.rst create mode 100644 docs/sphinx/reference-libobs-util.rst create mode 100644 docs/sphinx/reference-modules.rst create mode 100644 docs/sphinx/reference-outputs.rst create mode 100644 docs/sphinx/reference-properties.rst create mode 100644 docs/sphinx/reference-scenes.rst create mode 100644 docs/sphinx/reference-services.rst create mode 100644 docs/sphinx/reference-settings.rst create mode 100644 docs/sphinx/reference-sources.rst create mode 100644 docs/sphinx/scripting.rst create mode 100644 libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c create mode 100644 libobs/audio-monitoring/pulse/pulseaudio-output.c create mode 100644 libobs/audio-monitoring/pulse/pulseaudio-wrapper.c create mode 100644 libobs/audio-monitoring/pulse/pulseaudio-wrapper.h create mode 100644 libobs/util/windows/win-registry.h create mode 100644 plugins/coreaudio-encoder/data/locale/vi-VN.ini create mode 100644 plugins/decklink/data/locale/vi-VN.ini create mode 100644 plugins/image-source/data/locale/vi-VN.ini create mode 100644 plugins/linux-alsa/data/locale/el-GR.ini create mode 100644 plugins/linux-pulseaudio/data/locale/vi-VN.ini create mode 100644 plugins/linux-v4l2/data/locale/vi-VN.ini create mode 100644 plugins/mac-avcapture/data/locale/vi-VN.ini create mode 100644 plugins/mac-capture/data/locale/vi-VN.ini create mode 100644 plugins/mac-syphon/data/locale/vi-VN.ini create mode 100644 plugins/obs-ffmpeg/dynlink_cuda.h create mode 100644 plugins/obs-ffmpeg/nvEncodeAPI.h rename plugins/obs-ffmpeg/{obs-ffmpeg-aac.c => obs-ffmpeg-audio-encoders.c} (55%) create mode 100644 plugins/obs-filters/data/locale/ka-GE.ini create mode 100644 plugins/obs-filters/data/locale/vi-VN.ini create mode 100644 plugins/obs-libfdk/data/locale/vi-VN.ini create mode 100644 plugins/obs-outputs/ftl-stream.c create mode 100644 plugins/obs-outputs/obs-outputs-config.h.in create mode 100644 plugins/obs-text/data/locale/pt-PT.ini create mode 100644 plugins/obs-text/data/locale/vi-VN.ini create mode 100644 plugins/obs-transitions/data/locale/sk-SK.ini create mode 100644 plugins/obs-transitions/data/locale/vi-VN.ini create mode 100644 plugins/obs-transitions/transition-stinger.c create mode 100644 plugins/obs-x264/data/locale/ka-GE.ini create mode 100644 plugins/rtmp-services/data/locale/vi-VN.ini create mode 100644 plugins/rtmp-services/twitch.c create mode 100644 plugins/rtmp-services/twitch.h create mode 100644 plugins/text-freetype2/data/locale/vi-VN.ini create mode 100644 plugins/vlc-video/data/locale/el-GR.ini create mode 100644 plugins/vlc-video/data/locale/vi-VN.ini create mode 100644 test/test-input/sync-async-source.c create mode 100644 test/test-input/sync-audio-buffering.c create mode 100644 test/test-input/sync-pair-aud.c create mode 100644 test/test-input/sync-pair-vid.c diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a1dbec5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: http://EditorConfig.org +# Since OBS follows the Linux kernel coding style I have started this file to +# help with automatically setting various text editors to those requirements. + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file. +[*] +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = tab +indent_size = 8 + +# As per notr1ch, for 3rd party code that's a part of obs-outputs. +[plugins/obs-outputs/librtmp/*.{cpp,c,h}] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index 3606bf5..b1289c5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ #xcode *.xcodeproj/ +#clion +.idea/ + #other stuff (windows stuff, qt moc stuff, etc) Release_MD/ Release/ @@ -42,6 +45,14 @@ install-sh Makefile.in Makefile +#python +__pycache__ + +#sphinx +/docs/sphinx/_build/* +!/docs/sphinx/_build/.gitignore +!/docs/sphinx/Makefile + #random useless file stuff *.dmg *.app diff --git a/.gitmodules b/.gitmodules index f0d468b..40acac3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,10 +6,13 @@ url = https://github.com/palana/Syphon-Framework.git [submodule "plugins/enc-amf"] path = plugins/enc-amf - url = https://github.com/Xaymar/obs-studio_amf-encoder-plugin.git + url = https://github.com/jp9000/obs-studio_amf-encoder-plugin.git [submodule "plugins/obs-browser"] path = plugins/obs-browser url = https://github.com/kc5nra/obs-browser.git [submodule "plugins/obs-vst"] path = plugins/obs-vst url = https://github.com/DDRBoxman/obs-vst.git +[submodule "plugins/obs-outputs/ftl-sdk"] + path = plugins/obs-outputs/ftl-sdk + url = https://github.com/Mixer/ftl-sdk.git diff --git a/.travis.yml b/.travis.yml index a61ade1..9efe056 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ matrix: - os: osx env: - CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake - - CEF_BUILD_VERSION=3.2987.1588.g1952835 + - CEF_BUILD_VERSION=3.3282.1726.gc8368c8 before_install: "./CI/install-dependencies-osx.sh" before_script: "./CI/before-script-osx.sh" before_deploy: "./CI/before-deploy-osx.sh" diff --git a/CI/before-deploy-osx.sh b/CI/before-deploy-osx.sh index f427b36..f1a87f4 100755 --- a/CI/before-deploy-osx.sh +++ b/CI/before-deploy-osx.sh @@ -19,6 +19,15 @@ hr "Moving CEF out to preserve linking" mv ./rundir/RelWithDebInfo/obs-plugins/CEF.app ./ mv ./rundir/RelWithDebInfo/obs-plugins/obs-browser.so ./ +# Move obslua +hr "Moving OBS LUA" +mv ./rundir/RelWithDebInfo/data/obs-scripting/obslua.so ./rundir/RelWithDebInfo/bin/ + +# Move obspython +# hr "Moving OBS Python" +# mv ./rundir/RelWithDebInfo/data/obs-scripting/_obspython.so ./rundir/RelWithDebInfo/bin/ +# mv ./rundir/RelWithDebInfo/data/obs-scripting/obspython.py ./rundir/RelWithDebInfo/bin/ + # Package everything into a nice .app hr "Packaging .app" STABLE=false @@ -47,8 +56,10 @@ security unlock-keychain -p mysecretpassword build.keychain security set-keychain-settings -t 3600 -u build.keychain hr "Importing certs into keychain" security import ./Certificates.p12 -k build.keychain -T /usr/bin/productsign -P "" +# macOS 10.12+ +security set-key-partition-list -S apple-tool:,apple: -s -k mysecretpassword build.keychain hr "Signing Package" -productsign --sign 'Developer ID Installer: Hugh Bailey (2MMRE5MTB8)' ./OBS.pkg ./$FILENAME +productsign --sign 2MMRE5MTB8 ./OBS.pkg ./$FILENAME # Move to the folder that travis uses to upload artifacts from hr "Moving package to nightly folder for distribution" diff --git a/CI/before-script-osx.sh b/CI/before-script-osx.sh index e76772d..6cff70d 100755 --- a/CI/before-script-osx.sh +++ b/CI/before-script-osx.sh @@ -3,4 +3,9 @@ export PATH=/usr/local/opt/ccache/libexec:$PATH mkdir build cd build -cmake -DENABLE_SPARKLE_UPDATER=ON -DCMAKE_OSX_DEPLOYMENT_TARGET=10.9 -DDepsPath=/tmp/obsdeps -DVLCPath=$PWD/../../vlc-master -DBUILD_BROWSER=ON -DCEF_ROOT_DIR=$PWD/../../cef_binary_${CEF_BUILD_VERSION}_macosx64 .. +cmake -DENABLE_SPARKLE_UPDATER=ON \ +-DCMAKE_OSX_DEPLOYMENT_TARGET=10.10 \ +-DDepsPath=/tmp/obsdeps \ +-DVLCPath=$PWD/../../vlc-master \ +-DBUILD_BROWSER=ON \ +-DCEF_ROOT_DIR=$PWD/../../cef_binary_${CEF_BUILD_VERSION}_macosx64 .. \ No newline at end of file diff --git a/CI/install-dependencies-linux.sh b/CI/install-dependencies-linux.sh index 51dc82c..2cbd8de 100755 --- a/CI/install-dependencies-linux.sh +++ b/CI/install-dependencies-linux.sh @@ -1,12 +1,18 @@ #!/bin/sh set -ex +sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next -y sudo apt-get -qq update sudo apt-get install -y \ build-essential \ checkinstall \ cmake \ libasound2-dev \ + libavcodec-ffmpeg-dev \ + libavdevice-ffmpeg-dev \ + libavfilter-ffmpeg-dev \ + libavformat-ffmpeg-dev \ + libavutil-ffmpeg-dev \ libcurl4-openssl-dev \ libfdk-aac-dev \ libfontconfig-dev \ @@ -14,9 +20,12 @@ sudo apt-get install -y \ libgl1-mesa-dev \ libjack-jackd2-dev \ libjansson-dev \ + libluajit-5.1-dev \ libpulse-dev \ libqt5x11extras5-dev \ libspeexdsp-dev \ + libswresample-ffmpeg-dev \ + libswscale-ffmpeg-dev \ libudev-dev \ libv4l-dev \ libvlc-dev \ @@ -27,14 +36,6 @@ sudo apt-get install -y \ libxcomposite-dev \ libxinerama-dev \ pkg-config \ + python3-dev \ qtbase5-dev \ - yasm \ - zlib1g-dev - -# FFmpeg -cd .. -git clone --depth 1 git://source.ffmpeg.org/ffmpeg.git -cd ffmpeg -./configure --enable-shared -make -j2 -sudo make install + swig diff --git a/CI/install-dependencies-osx.sh b/CI/install-dependencies-osx.sh index 7e7ce68..4d53296 100755 --- a/CI/install-dependencies-osx.sh +++ b/CI/install-dependencies-osx.sh @@ -17,7 +17,7 @@ sudo installer -pkg ./Packages.pkg -target / brew update #Base OBS Deps and ccache -brew install qt5 jack speexdsp ccache +brew install qt5 jack speexdsp ccache swig export PATH=/usr/local/opt/ccache/libexec:$PATH ccache -s || echo "CCache is not available." @@ -40,6 +40,8 @@ sudo cp -R ./sparkle/Sparkle.framework /Library/Frameworks/Sparkle.framework wget --retry-connrefused --waitretry=1 https://obs-nightly.s3-us-west-2.amazonaws.com/cef_binary_${CEF_BUILD_VERSION}_macosx64.tar.bz2 tar -xf ./cef_binary_${CEF_BUILD_VERSION}_macosx64.tar.bz2 cd ./cef_binary_${CEF_BUILD_VERSION}_macosx64 +# remove a broken test +sed -i '.orig' '/add_subdirectory(tests\/ceftests)/d' ./CMakeLists.txt mkdir build cd ./build cmake -DCMAKE_CXX_FLAGS="-std=c++11 -stdlib=libc++" -DCMAKE_EXE_LINKER_FLAGS="-std=c++11 -stdlib=libc++" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.9 .. diff --git a/CI/install/osx/build_app.py b/CI/install/osx/build_app.py index 907e113..aa98437 100644 --- a/CI/install/osx/build_app.py +++ b/CI/install/osx/build_app.py @@ -1,22 +1,22 @@ #!/usr/bin/env python - + candidate_paths = "bin obs-plugins data".split() - + plist_path = "../cmake/osxbundle/Info.plist" icon_path = "../cmake/osxbundle/obs.icns" run_path = "../cmake/osxbundle/obslaunch.sh" - + #not copied blacklist = """/usr /System""".split() - + #copied whitelist = """/usr/local""".split() - + # # # - - + + from sys import argv from glob import glob from subprocess import check_output, call @@ -33,7 +33,7 @@ def _str_to_bool(s): raise ValueError('Need bool; got %r' % s) return {'true': True, 'false': False}[s.lower()] -def add_boolean_argument(parser, name, default=False): +def add_boolean_argument(parser, name, default=False): """Add a boolean argument to an ArgumentParser instance.""" group = parser.add_mutually_exclusive_group() group.add_argument( @@ -58,14 +58,14 @@ def cmd(cmd): return subprocess.check_output(shlex.split(cmd)).rstrip('\r\n') LibTarget = namedtuple("LibTarget", ("path", "external", "copy_as")) - + inspect = list() - + inspected = set() - + build_path = args.dir build_path = build_path.replace("\\ ", " ") - + def add(name, external=False, copy_as=None): if external and copy_as is None: copy_as = name.split("/")[-1] @@ -93,7 +93,7 @@ for i in candidate_paths: rel_path = path[len(build_path)+1:] print(repr(path), repr(rel_path)) add(rel_path) - + def add_plugins(path, replace): for img in glob(path.replace( "lib/QtCore.framework/Versions/5/QtCore", @@ -114,13 +114,14 @@ while inspect: continue out = check_output("{0}otool -L '{1}'".format(args.prefix, path), shell=True, universal_newlines=True) - + if "QtCore" in path: add_plugins(path, "platforms") add_plugins(path, "imageformats") add_plugins(path, "accessible") - - + add_plugins(path, "styles") + + for line in out.split("\n")[1:]: new = line.strip().split(" (")[0] if '@' in new and "sparkle.framework" in new.lower(): @@ -197,6 +198,8 @@ for path, external, copy_as in inspected: filename = path rpath = "" if external: + if copy_as == "Python": + continue id_ = "-id '@rpath/%s'"%copy_as filename = prefix + "bin/" +copy_as rpath = "-add_rpath @loader_path/ -add_rpath @executable_path/" @@ -214,7 +217,7 @@ for path, external, copy_as in inspected: print(filename) rpath = "-add_rpath '@loader_path/{}/'".format(ospath.relpath("bin/", ospath.dirname(filename))) filename = prefix + filename - + cmd = "{0}install_name_tool {1} {2} {3} '{4}'".format(args.prefix, changes, id_, rpath, filename) call(cmd, shell=True) diff --git a/CI/util/build-package-deps-osx.sh b/CI/util/build-package-deps-osx.sh index bf950d7..12fb805 100755 --- a/CI/util/build-package-deps-osx.sh +++ b/CI/util/build-package-deps-osx.sh @@ -1,5 +1,15 @@ #!/usr/bin/env bash +exists() +{ + command -v "$1" >/dev/null 2>&1 +} + +if ! exists nasm; then + echo "nasm not found. Try brew install nasm" + exit +fi + CURDIR=$(pwd) # the temp directory @@ -22,6 +32,7 @@ DEPS_DEST=$WORK_DIR/obsdeps mkdir $DEPS_DEST mkdir $DEPS_DEST/bin mkdir $DEPS_DEST/include +mkdir $DEPS_DEST/lib # OSX COMPAT export MACOSX_DEPLOYMENT_TARGET=10.9 @@ -80,6 +91,7 @@ cd $WORK_DIR # x264 git clone git://git.videolan.org/x264.git cd ./x264 +git checkout origin/stable mkdir build cd ./build ../configure --extra-ldflags="-mmacosx-version-min=10.9" --enable-static --prefix="/tmp/obsdeps" @@ -117,12 +129,22 @@ unzip ./n3.2.2.zip cd ./FFmpeg-n3.2.2 mkdir build cd ./build -../configure --extra-ldflags="-mmacosx-version-min=10.9" --enable-shared --disable-static --shlibdir="/tmp/obsdeps/bin" --enable-gpl --disable-doc --enable-libx264 --enable-libopus --enable-libvorbis --enable-libvpx +../configure --extra-ldflags="-mmacosx-version-min=10.9" --enable-shared --disable-static --shlibdir="/tmp/obsdeps/bin" --enable-gpl --disable-doc --enable-libx264 --enable-libopus --enable-libvorbis --enable-libvpx --disable-outdev=sdl make -j 12 find . -name \*.dylib -exec cp \{\} $DEPS_DEST/bin/ \; rsync -avh --include="*/" --include="*.h" --exclude="*" ../* $DEPS_DEST/include/ rsync -avh --include="*/" --include="*.h" --exclude="*" ./* $DEPS_DEST/include/ +#luajit +curl -L -O https://luajit.org/download/LuaJIT-2.0.5.tar.gz +tar -xf LuaJIT-2.0.5.tar.gz +cd LuaJIT-2.0.5 +make PREFIX=/tmp/obsdeps +make PREFIX=/tmp/obsdeps install +find /tmp/obsdeps/lib -name libluajit\*.dylib -exec cp \{\} $DEPS_DEST/lib/ \; +rsync -avh --include="*/" --include="*.h" --exclude="*" src/* $DEPS_DEST/include/ +make PREFIX=/tmp/obsdeps uninstall + cd $WORK_DIR tar -czf osx-deps.tar.gz obsdeps diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e14579..e792ad6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ if(WIN32) endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +set(ENABLE_SCRIPTING TRUE CACHE BOOL "Enables scripting") +set(SCRIPTING_ENABLED OFF CACHE BOOL "Interal global cmake variable" FORCE) include(ObsHelpers) include(ObsCpack) @@ -62,7 +64,7 @@ elseif(MSVC) endif() if(WIN32) - add_definitions(-DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS) + add_definitions(-DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS) endif() if(MSVC) @@ -103,8 +105,14 @@ option(BUILD_TESTS "Build test directory (includes test sources and possibly a p mark_as_advanced(BUILD_TESTS) if(NOT INSTALLER_RUN) - add_subdirectory(deps) + option(ENABLE_UI "Enables the OBS user interfaces" ON) + if(DISABLE_UI OR NOT ENABLE_UI) + set(UI_ENABLED FALSE) + else() + set(UI_ENABLED TRUE) + endif() + add_subdirectory(deps) if(WIN32) add_subdirectory(libobs-d3d11) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 70cb070..29518b1 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -15,6 +15,9 @@ Quick Links for Contributing - Development forum: https://obsproject.com/forum/list/general-development.21/ + - Developer/API Documentation: + https://obsproject.com/docs + - To contribute language translations, do not make pull requests. Instead, use crowdin. Read here for more information: https://obsproject.com/forum/threads/how-to-contribute-translations-for-obs.16327/ @@ -59,32 +62,3 @@ Commit Guidlines libobs: Fix source not displaying - If you still need examples, please view the commit history. - -Headers -------- - - There's no formal documentation as of yet, so it's recommended to read - the headers (which are heavily commented) to learn the API. - - Here are the most important headers to check out:: - - libobs/obs.h Main header - - libobs/obs-module.h Main header for plugin modules - - libobs/obs-source.h Creating video/audio sources - - libobs/obs-output.h Creating outputs - - libobs/obs-encoder.h Implementing encoders - - libobs/obs-service.h Implementing custom streaming services - - libobs/graphics/graphics.h Graphics API - - UI/obs-frontend-api/obs-frontend-api.h - Front-end API - - If you would like to learn from example, examine the default plugins - (in the subdirectory). All features of OBS Studio are - implemented as plugins. diff --git a/README.rst b/README.rst index 6148529..f43960d 100644 --- a/README.rst +++ b/README.rst @@ -15,12 +15,14 @@ Quick Links - Website: https://obsproject.com - - Help/Guides: https://github.com/jp9000/obs-studio/wiki + - Help/Documentation/Guides: https://github.com/jp9000/obs-studio/wiki - Forums: https://obsproject.com/forum/ - Build Instructions: https://github.com/jp9000/obs-studio/wiki/Install-Instructions + - Developer/API Documentation: https://obsproject.com/docs + - Bug Tracker: https://obsproject.com/mantis/ (Note: The bug tracker is linked to forum accounts. To use the bug @@ -29,10 +31,13 @@ Quick Links Contributing ------------ - - If you wish to contribute code to the project, please make sure read - the coding and commit guidelines: + - If you wish to contribute code to the project, please make sure to + read the coding and commit guidelines: https://github.com/jp9000/obs-studio/blob/master/CONTRIBUTING.rst + - Developer/API documentation can be found here: + https://obsproject.com/docs + - If you wish to contribute translations, do not submit pull requests. Instead, please use Crowdin. For more information read this thread: https://obsproject.com/forum/threads/how-to-contribute-translations-for-obs.16327/ @@ -41,4 +46,4 @@ Contributing our forums or in our community chat. Please limit support to topics you fully understand -- bad advice is worse than no advice. When it comes to something that you don't fully know or understand, please - defer to the official help or official channels. + defer to the official help or official channels. diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 1293514..63cc72d 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -1,4 +1,3 @@ -option(ENABLE_UI "Enables the OBS user interfaces" ON) if(DISABLE_UI) message(STATUS "UI disabled") return() @@ -38,6 +37,10 @@ endif() find_package(Qt5Widgets ${FIND_MODE}) find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat) +if(APPLE) + find_package(Qt5MacExtras REQUIRED) +endif(APPLE) + if(NOT Qt5Widgets_FOUND) if (ENABLE_UI) message(FATAL_ERROR "Failed to find Qt5") @@ -161,6 +164,7 @@ set(obs_SOURCES adv-audio-control.cpp item-widget-helpers.cpp visibility-checkbox.cpp + locked-checkbox.cpp vertical-scroll-area.cpp visibility-item-widget.cpp slider-absoluteset-style.cpp @@ -208,6 +212,7 @@ set(obs_HEADERS adv-audio-control.hpp item-widget-helpers.hpp visibility-checkbox.hpp + locked-checkbox.hpp vertical-scroll-area.hpp visibility-item-widget.hpp slider-absoluteset-style.hpp @@ -269,6 +274,20 @@ target_link_libraries(obs ${LIBCURL_LIBRARIES} ${obs_PLATFORM_LIBRARIES}) +if (APPLE) + target_link_libraries(obs + Qt5::MacExtras) + set_target_properties(obs PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") + set_property( + TARGET obs + APPEND + PROPERTY INSTALL_RPATH + "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/" + "/Library/Frameworks/Python.framework/Versions/3.6/lib/" + "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/" + ) +endif() + define_graphic_modules(obs) install_obs_core(obs) diff --git a/UI/adv-audio-control.cpp b/UI/adv-audio-control.cpp index b7bfd4d..531b001 100644 --- a/UI/adv-audio-control.cpp +++ b/UI/adv-audio-control.cpp @@ -32,7 +32,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_) volume = new QSpinBox(); forceMono = new QCheckBox(); panning = new QSlider(Qt::Horizontal); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO monitoringType = new QComboBox(); #endif syncOffset = new QSpinBox(); @@ -93,7 +93,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_) syncOffset->setValue(int(cur_sync / NSEC_PER_MSEC)); int idx; -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.None"), (int)OBS_MONITORING_TYPE_NONE); monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.MonitorOnly"), @@ -138,7 +138,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_) this, SLOT(panningChanged(int))); QWidget::connect(syncOffset, SIGNAL(valueChanged(int)), this, SLOT(syncOffsetChanged(int))); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO QWidget::connect(monitoringType, SIGNAL(currentIndexChanged(int)), this, SLOT(monitoringTypeChanged(int))); #endif @@ -163,7 +163,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_) layout->addWidget(forceMonoContainer, lastRow, idx++); layout->addWidget(panningContainer, lastRow, idx++); layout->addWidget(syncOffset, lastRow, idx++); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO layout->addWidget(monitoringType, lastRow, idx++); #endif layout->addWidget(mixerContainer, lastRow, idx++); @@ -178,7 +178,7 @@ OBSAdvAudioCtrl::~OBSAdvAudioCtrl() forceMonoContainer->deleteLater(); panningContainer->deleteLater(); syncOffset->deleteLater(); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO monitoringType->deleteLater(); #endif mixerContainer->deleteLater(); diff --git a/UI/api-interface.cpp b/UI/api-interface.cpp index 025dd2c..a580b72 100644 --- a/UI/api-interface.cpp +++ b/UI/api-interface.cpp @@ -49,6 +49,7 @@ struct OBSStudioAPI : obs_frontend_callbacks { OBSBasic *main; vector> callbacks; vector> saveCallbacks; + vector> preloadCallbacks; inline OBSStudioAPI(OBSBasic *main_) : main(main_) {} @@ -239,6 +240,11 @@ struct OBSStudioAPI : obs_frontend_callbacks { QMetaObject::invokeMethod(main, "StartReplayBuffer"); } + void obs_frontend_replay_buffer_save(void) override + { + QMetaObject::invokeMethod(main, "ReplayBufferSave"); + } + void obs_frontend_replay_buffer_stop(void) override { QMetaObject::invokeMethod(main, "StopReplayBuffer"); @@ -343,6 +349,26 @@ struct OBSStudioAPI : obs_frontend_callbacks { saveCallbacks.erase(saveCallbacks.begin() + idx); } + void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, + void *private_data) override + { + size_t idx = GetCallbackIdx(preloadCallbacks, callback, + private_data); + if (idx == (size_t)-1) + preloadCallbacks.emplace_back(callback, private_data); + } + + void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, + void *private_data) override + { + size_t idx = GetCallbackIdx(preloadCallbacks, callback, + private_data); + if (idx == (size_t)-1) + return; + + preloadCallbacks.erase(preloadCallbacks.begin() + idx); + } + void obs_frontend_push_ui_translation( obs_frontend_translate_ui_cb translate) override { @@ -369,16 +395,70 @@ struct OBSStudioAPI : obs_frontend_callbacks { main->SaveService(); } + bool obs_frontend_preview_program_mode_active(void) override + { + return main->IsPreviewProgramMode(); + } + + void obs_frontend_set_preview_program_mode(bool enable) override + { + main->SetPreviewProgramMode(enable); + } + + bool obs_frontend_preview_enabled(void) override + { + return main->previewEnabled; + } + + void obs_frontend_set_preview_enabled(bool enable) override + { + if (main->previewEnabled != enable) + main->EnablePreviewDisplay(enable); + } + + obs_source_t *obs_frontend_get_current_preview_scene(void) override + { + OBSSource source = nullptr; + + if (main->IsPreviewProgramMode()) { + source = main->GetCurrentSceneSource(); + obs_source_addref(source); + } + + return source; + } + + void obs_frontend_set_current_preview_scene(obs_source_t *scene) override + { + if (main->IsPreviewProgramMode()) { + QMetaObject::invokeMethod(main, "SetCurrentScene", + Q_ARG(OBSSource, OBSSource(scene)), + Q_ARG(bool, false)); + } + } + void on_load(obs_data_t *settings) override { - for (auto cb : saveCallbacks) + for (size_t i = saveCallbacks.size(); i > 0; i--) { + auto cb = saveCallbacks[i - 1]; cb.callback(settings, false, cb.private_data); + } + } + + void on_preload(obs_data_t *settings) override + { + for (size_t i = preloadCallbacks.size(); i > 0; i--) { + auto cb = preloadCallbacks[i - 1]; + cb.callback(settings, false, cb.private_data); + } } void on_save(obs_data_t *settings) override { - for (auto cb : saveCallbacks) + for (size_t i = saveCallbacks.size(); i > 0; i--) { + auto cb = saveCallbacks[i - 1]; cb.callback(settings, true, cb.private_data); + } } void on_event(enum obs_frontend_event event) override @@ -386,8 +466,10 @@ struct OBSStudioAPI : obs_frontend_callbacks { if (main->disableSaving) return; - for (auto cb : callbacks) + for (size_t i = callbacks.size(); i > 0; i--) { + auto cb = callbacks[i - 1]; cb.callback(event, cb.private_data); + } } }; diff --git a/UI/audio-encoders.cpp b/UI/audio-encoders.cpp index 09bec9e..1e0805d 100644 --- a/UI/audio-encoders.cpp +++ b/UI/audio-encoders.cpp @@ -148,6 +148,10 @@ static void PopulateBitrateMap() { call_once(populateBitrateMap, []() { + struct obs_audio_info aoi; + obs_get_audio_info(&aoi); + uint32_t output_channels = get_audio_channels(aoi.speakers); + HandleEncoderProperties(fallbackEncoder.c_str()); const char *id = nullptr; @@ -174,6 +178,10 @@ static void PopulateBitrateMap() if (aac_ != GetCodec(encoder.c_str())) continue; + // disable mf_aac if audio ouput is not stereo nor mono + if ((output_channels >= 3) && (encoder == "mf_aac")) + continue; + HandleEncoderProperties(encoder.c_str()); } diff --git a/UI/data/locale.ini b/UI/data/locale.ini index efc9f70..4631d78 100644 --- a/UI/data/locale.ini +++ b/UI/data/locale.ini @@ -128,4 +128,14 @@ Name=বাংলা ভাষা Name=हिन्दी [ur-PK] -Name=اردو \ No newline at end of file +Name=اردو + +[af-ZA] +Name=Afrikaanse + +[ka-GE] +Name=ქართული + +[nn-NO] +Name=Norsk Nynorsk + diff --git a/UI/data/locale/af-ZA.ini b/UI/data/locale/af-ZA.ini new file mode 100644 index 0000000..d44bfd8 --- /dev/null +++ b/UI/data/locale/af-ZA.ini @@ -0,0 +1,94 @@ + + +OK="OK" +Apply="Pas veranderinge toe" +Cancel="Kanselleer" +Close="Maak toe" +Save="Stoor" +Discard="Gooi weg" +Disable="Deaktiveer" +Yes="Ja" +No="Nee" +Add="Voeg by" +Remove="Verwyder" +Rename="Hernoem" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UI/data/locale/ar-SA.ini b/UI/data/locale/ar-SA.ini index 146d2a5..e013963 100644 --- a/UI/data/locale/ar-SA.ini +++ b/UI/data/locale/ar-SA.ini @@ -27,14 +27,22 @@ Mixer="مختلط" Browse="استعراض" Mono="صوت أحادي القناة \"مونو\"" Stereo="صوت ثنائي القناة \"ستيريو\"" -DroppedFrames="إسقاط المشاهد %1 (% %2)" +DroppedFrames="إسقاط المشاهد 1% (%2%)" +StudioProgramProjector="عرض بالشاشة الكاملة (البث الحالي)" PreviewProjector="عرض بالشاشة الكاملة (المعاينة)" SceneProjector="عرض بالشاشة الكاملة (المشهد)" SourceProjector="عرض بالشاشة الكاملة (المصدر)" +StudioProgramWindow="عرض داخل نافذة (البث الحالي)" +PreviewWindow="عرض بالشاشة الكاملة (المعاينة)" +SceneWindow="عرض داخل نافذة (للمشهد)" +SourceWindow="عرض بالشاشة الكاملة (المصدر)" +MultiviewProjector="معاينة متعددة (شاشة كاملة)" +MultiviewWindowed="معاينة متعددة (داخل نافذة)" Clear="مسح" Revert="استعادة" Show="إظهار" Hide="اخفاء" +UnhideAll="إظهار الكل" Untitled="بدون عنوان" New="جديد" Duplicate="تكرار مزدوج" @@ -48,13 +56,92 @@ Left="يسار" Right="يمين" Top="أعلى" Bottom="أسفل" +Reset="إعادة تعيين" +Hours="ساعات" +Minutes="دقائق" +Seconds="ثواني" +Deprecated="مهمل" +ReplayBuffer="إعادة تشغيل المخزن المؤقت" +Import="استيراد" +Export="تصدير" +Copy="نسخ" +Paste="لصق" +PasteReference="لصق (مرجع)" +PasteDuplicate="لصق (مكررة)" +RemuxRecordings="تسجيلات Remux" +Next="التالي" +Back="السابق" +Defaults="الافتراضي" +HideMixer="إخفاء في الخالط" +TransitionOverride="فرض تأثير انتقالي" +None="لا شيء" +StudioMode.Preview="معاينة" +StudioMode.Program="البث" +ShowInMultiview="إظهار في العرض المتعدد" +AlreadyRunning.Title="البرنامج قيد التشغيل بالفعل" +AlreadyRunning.Text="أوبس قيد التشغيل بالفعل! إلا إذا كنت تريد القيام بذلك، يرجى إيقاف أية مثيلات موجودة من للبرنامج قبل محاولة تشغيله من جديد. إذا كان لديك أوبس تعيين لتقليل إلى علبة النظام، يرجى التحقق لمعرفة ما إذا كان لا يزال قيد التشغيل هناك." +AlreadyRunning.LaunchAnyway="إطلاق على أي حال" +Copy.Filters="نسخ الفلتر" +Paste.Filters="لصق الفلتر" +BandwidthTest.Region="منطقة" +BandwidthTest.Region.US="الولايات المتحدة" +BandwidthTest.Region.EU="أوروبا" +BandwidthTest.Region.Asia="آسيا" +BandwidthTest.Region.Other="\"غير ذلك\"" +Basic.FirstStartup.RunWizard="هل تريد تشغيل معالج التكوين التلقائي؟ يمكنك أيضا يدوياً تكوين الإعدادات الخاصة بك بواسطة النقر فوق زر \"إعدادات\" في الواجهة الرئيسية." +Basic.FirstStartup.RunWizard.BetaWarning="(ملاحظة: معالج التكوين التلقائي حاليا نسخة تجريبية)" +Basic.FirstStartup.RunWizard.NoClicked="إذا قمت بتغيير رأيك، يمكنك تشغيل معالج التكوين التلقائي في أي وقت مرة أخرى من قائمة أدوات." +Basic.AutoConfig="معالج التكوين التلقائي" +Basic.AutoConfig.Beta="معالج التكوين التلقائي (نسخة تجريبية)" +Basic.AutoConfig.ApplySettings="تطبيق الإعدادات" +Basic.AutoConfig.StartPage="معلومات الاستخدام" +Basic.AutoConfig.StartPage.SubTitle="حدد ما تريد استخدامه للبرنامج" +Basic.AutoConfig.StartPage.PrioritizeStreaming="تحسين لبث افيديو ، والتسجيل ثانياً" +Basic.AutoConfig.StartPage.PrioritizeRecording="تحسين فقط للتسجيل، وأنا لن أبث الفيديو" +Basic.AutoConfig.VideoPage="إعدادات الفيديو" +Basic.AutoConfig.VideoPage.SubTitle="حدد إعدادات الفيديو المطلوبة التي ترغب في استخدامها" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="الاستخدام الحالي (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="العرض %1 (%2x%3)" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="الاستخدام الحالي (%1)" +Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="إما 60 أو 30، ولكن يفضل 60 عندما يكون ذلك ممكنا" +Basic.AutoConfig.VideoPage.FPS.PreferHighRes="إما 60 أو 30، ولكن يفضل الدقة العالية" +Basic.AutoConfig.VideoPage.CanvasExplanation="ملاحظة: قرار قاعدة (قاعدة) ليس بالضرورة يكون بنفس دقة البث أو التسجيل. قد يتم تصغير حجم تدفق البث/ دقة التسجيل أقل من دقة القاعدة لتقليل استخدام الموارد أو متطلبات معدل البت." +Basic.AutoConfig.StreamPage="معلومات البث" +Basic.AutoConfig.StreamPage.SubTitle="من فضلك ادخل معلومات الخادم أو السيرفر" +Basic.AutoConfig.StreamPage.Service="خدمة" +Basic.AutoConfig.StreamPage.Service.ShowAll="عرض الكل..." +Basic.AutoConfig.StreamPage.Server="الخادم" +Basic.AutoConfig.StreamPage.StreamKey="مفتاح البث" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(رابط)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="تقدير معدل التدفق باستخدام فحص سرعة الانترنت (ربما يحتاج لبعض الوقت)" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding="تفضيل استخدام الترميز بواسطة الهاردوير" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="استخدام الترميز بالهاردوير يلغي الحاجة الى معظم موارد المعالج, لكن قد يحتاج الى معدل بث أعلى للحفاظ على نفس مستوى الجودة." +Basic.AutoConfig.StreamPage.StreamWarning.Title="تحذير يتعلق بالبث" +Basic.Stats="إحصائيات" +Basic.Stats.CPUUsage="استخدام المعالج" +Basic.Stats.HDDSpaceAvailable="مساحة القرص الصلب المتوفرة" +Basic.Stats.MemoryUsage="استخدام الذاكرة" +Basic.Stats.AverageTimeToRender="معدل الوقت لإنشاء الإطار الواحد" +Basic.Stats.SkippedFrames="إطارات تم تخطيها بسبب تأخر الترميز" +Basic.Stats.MissedFrames="إطارات فقدت بسبب تأخر الترميز" +Basic.Stats.Output.Stream="بث" +Basic.Stats.Output.Recording="التسجيل" +Basic.Stats.Status="الحالة" +Basic.Stats.Status.Recording="جارٍ التسجيل" +Basic.Stats.Status.Live="مباشر" +Basic.Stats.Status.Reconnecting="جاري إعادة الاتصال" +Basic.Stats.Status.Inactive="غير نشط" +Basic.Stats.DroppedFrames="الاطارات المفقودة (بسبب الشبكة)" +Basic.Stats.MegabytesSent="إجمالي البيانات الصادرة" +Updater.NoUpdatesAvailable.Text="لا توجد تحديثات متوفرة حاليا" +Updater.FailedToLaunch="فشل في تشغيل التحديث" QuickTransitions.SwapScenes="التبديل بين مشهدي المعاينة و الاخراج بعد عملية الانتقال" QuickTransitions.SwapScenesTT="يقوم بتبديل مشهد المعاينة مع مشهد الاخراج بعد عملية الانتقال بين المشاهد (اذا كان مشهد الاخراج الاصلي لازال موجوداً) \n هذا لن يقوم بالتراجع عن اي تغييرات قمت بها على مشهد الاخراج الأصلي." @@ -94,6 +181,8 @@ ConfirmExit.Text="OBS حالياً نشط، كافة عمليات البث/ال ConfirmRemove.Title="تأكيد الإزالة" ConfirmRemove.Text="هل أنت متأكد من رغبتك في إزالة '$1' ?" +Output.StartStreamFailed="فشل في بدء البث" +Output.StartRecordingFailed="فشل في بدء التسجيل" Output.ConnectFail.Title="فشل في الاتصال" Output.ConnectFail.BadPath="مسار أو رابط الاتصال غير صالح. الرجاء التحقق من الإعدادات للتحقق من كونه صالح." @@ -154,13 +243,26 @@ ScaleFiltering="مقياس التصفية" ScaleFiltering.Point="نقطة" Deinterlacing.Discard="تجاهل" +Deinterlacing.Linear="خطي" +Deinterlacing.Linear2x="X 2 الخطي" +Deinterlacing.TopFieldFirst="أعلى الحقل الأول" +Deinterlacing.BottomFieldFirst="أسفل الحقل الأول" +VolControl.SliderUnmuted="حجم شريط التمرير ل '1%': 2%" +VolControl.SliderMuted="حجم شريط التمرير ل '1%': 2% (كتم)" +VolControl.Mute="كتم الصوت '1%'" +VolControl.Properties="خصائص ل '1%'" Basic.Main.AddSceneDlg.Title="أضف المشهد" Basic.Main.AddSceneDlg.Text="الرجاء إدخال اسم المشهد" Basic.Main.DefaultSceneName.Text="المشهد %1" +Basic.Main.AddSceneCollection.Title="إضافة مجموعة مشاهد" +Basic.Main.AddSceneCollection.Text="الرجاء إدخال اسم لمجموعة مشهد" + +Basic.Main.RenameSceneCollection.Title="اعادة تسمية مجموعة مشاهد" + diff --git a/UI/data/locale/bg-BG.ini b/UI/data/locale/bg-BG.ini index bba5fee..e62a41c 100644 --- a/UI/data/locale/bg-BG.ini +++ b/UI/data/locale/bg-BG.ini @@ -28,29 +28,166 @@ Browse="Преглед" Mono="Моно" Stereo="Стерео" DroppedFrames="Изпуснати кадри %1 (%2%)" +StudioProgramProjector="Прожектиране на цял екран (от Програмата)" PreviewProjector="Прожектиране на цял екран (Предварителен преглед)" SceneProjector="Прожектиране на цял екран (Сцена)" SourceProjector="Прожектиране на цял екран (Източник)" +StudioProgramWindow="Прожектиране във прозорец (от Програмата)" +PreviewWindow="Прожектиране във прозорец (Предварителен преглед)" +SceneWindow="Прожектиране във прозорец (от Сцената)" +SourceWindow="Прожектиране във прозорец (от Източника)" +MultiviewProjector="Множествен Изглед (цял екран)" +MultiviewWindowed="Множествен Изглед (в прозорец)" Clear="Изчисти" Revert="Върни" Show="Покажи" Hide="Скрий" +UnhideAll="Покажи всички" Untitled="Неозаглавен" New="Нов" Duplicate="Дублирай" Enable="Активиране" DisableOSXVSync="Изключване на OSX V-синхронизация" ResetOSXVSyncOnExit="Рестартиране на OSX V-синхронизация при излизане" +HighResourceUsage="Претоварено кордиране! Моля намалете настройките за видео или ползвайте по-бърза настройка за кодиране." +Transition="Преход" +QuickTransitions="Бързи преходи" +Left="Ляво" +Right="Дясно" +Top="Горе" +Bottom="Долу" +Reset="Нулирай" +Hours="Часа" +Minutes="Минути" +Seconds="Секунди" +Deprecated="Отхвърлени" +ReplayBuffer="Буфер за повторение" +Import="Импорт" +Export="Експорт" +Copy="Копиране" +Paste="Постави" +PasteReference="Постави (препратка)" +PasteDuplicate="Постави (дубликат)" +RemuxRecordings="Конвертиране на запаиси" +Next="Напред" +Back="Назад" +Defaults="По подразбиране" +HideMixer="Скрий звука в миксера" +TransitionOverride="Налагане на преминавания" +None="Без" +StudioMode.Preview="Преглед" +StudioMode.Program="Програмен Изглед" +ShowInMultiview="Покажи във Множествен изглед" +AlreadyRunning.Title="OBS вече се изпълнява" +AlreadyRunning.Text="OBS вече е включен! Освен ако не е по желание, моля изключете другите работещи инстанции на OBS преди да включите нова. Ако сте настроили OBS да се минимизира във системната табла, моля проверете дали все още е включен там." +AlreadyRunning.LaunchAnyway="Включи въпреки това" +Copy.Filters="Копирай Филтъри" +Paste.Filters="Постави Филтъри" +BandwidthTest.Region="Регион" +BandwidthTest.Region.US="САЩ" +BandwidthTest.Region.EU="Европа" +BandwidthTest.Region.Asia="Азия" +BandwidthTest.Region.Other="Друг" +Basic.FirstStartup.RunWizard="Искате ли да изпълните съветника за автоматично конфигуриране? Можете също ръчно да конфигурирате настройките, като щракнете върху бутона настройки в главния прозорец." +Basic.FirstStartup.RunWizard.BetaWarning="(Забележка: съветника за автоматично конфигуриране в момента е в бета версия)" +Basic.FirstStartup.RunWizard.NoClicked="Ако промените решението си, можете да стартирате съветника за автоматично конфигуриране отново от меню \"Инструменти\"." +Basic.AutoConfig="Съветник за автоматично конфигуриране" +Basic.AutoConfig.Beta="Съветник за автоматично конфигуриране (Бета версия)" +Basic.AutoConfig.ApplySettings="Приложи настройките" +Basic.AutoConfig.StartPage="Информация за използване" +Basic.AutoConfig.StartPage.SubTitle="Посочете за какво искате да използвате програмата" +Basic.AutoConfig.StartPage.PrioritizeStreaming="Оптимизирай за стрийминг, записът е второстепенен" +Basic.AutoConfig.StartPage.PrioritizeRecording="Оптимизирай за записи, няма да стриймвам" +Basic.AutoConfig.VideoPage="Видео настройки" +Basic.AutoConfig.VideoPage.SubTitle="Въведете желаните видео настройки, които искате да използвате" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Използвай текущата (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="Диспей %1 (%2x%3)" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="Използвай Текущи (%1)" +Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="Или 60 или 30 кадъра, но предпочитам 60 когато е възможно" +Basic.AutoConfig.VideoPage.FPS.PreferHighRes="Или 60 или 30 кадъра, но предпочитам по-висока резолюция" +Basic.AutoConfig.VideoPage.CanvasExplanation="Бележка: резолюцията на платно (основата) не е задължително да бъде същата като резолюцията, с която ще излъчвате или записвате. Реалната разделителна способност на потока / записа може да бъде намалена от резолюцията на платното, за да се намалят използваните системни ресурси или битрейт." +Basic.AutoConfig.StreamPage="Стрийм информация" +Basic.AutoConfig.StreamPage.SubTitle="Моля въведете информация за вашия стрийм" +Basic.AutoConfig.StreamPage.Service="Услуга" +Basic.AutoConfig.StreamPage.Service.ShowAll="Покажи всички..." +Basic.AutoConfig.StreamPage.Server="Сървър" +Basic.AutoConfig.StreamPage.StreamKey="Стрийм ключ" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Линк)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Прогнозиране на битрейта с тест за трафик (може да отнеме няколко минути)" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Предпочитай хардуерно кодиране" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="Хардуерното кодиране намалява по-голямата част от употребата на процесора, но може да изисква повече битрейт, за да получи същото ниво на качество." +Basic.AutoConfig.StreamPage.StreamWarning.Title="Предупреждение за излъчването" +Basic.AutoConfig.StreamPage.StreamWarning.Text="Тестът за трафик е на път да излъчи разнообразни видео данни без аудио сигнал към вашия канал. Ако сте в състояние, препоръчваме временно да изключите запазването на видеоклипове и да зададете излъчването като лично докато не приключи теста. Продължи?" +Basic.AutoConfig.TestPage="Финални Резултати" +Basic.AutoConfig.TestPage.SubTitle.Testing="Програмата изпълнява набор от тестове, за да прецени най-идеалните настройки" +Basic.AutoConfig.TestPage.SubTitle.Complete="Тестването завършено" +Basic.AutoConfig.TestPage.TestingBandwidth="Провежда се тест за трафик, може да отнеме няколко минути..." +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Свързване към: %1..." +Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Неуспешно свързването към сървъра, проверете връзката си с интернет и опитайте отново." +Basic.AutoConfig.TestPage.TestingBandwidth.Server="Тестване на трафик към: %1" +Basic.AutoConfig.TestPage.TestingStreamEncoder="Тестване на енкодера за излъчване, може да отнеме минутка..." +Basic.AutoConfig.TestPage.TestingRecordingEncoder="Тестване на енкодера за записване, може да отнеме минутка..." +Basic.AutoConfig.TestPage.TestingRes="Изпробват се разолюции, може да отнеме минутка..." +Basic.AutoConfig.TestPage.TestingRes.Fail="Неуспешно стартиране на енкодер" +Basic.AutoConfig.TestPage.TestingRes.Resolution="Тестване %1x%2 %3 FPS..." +Basic.AutoConfig.TestPage.Result.StreamingEncoder="Стрийминг енкодер" +Basic.AutoConfig.TestPage.Result.RecordingEncoder="Кодиране на записи" +Basic.AutoConfig.TestPage.Result.Header="Програмата определи тези приблизителни настройки за най-оптимални във вашия случай:" +Basic.AutoConfig.TestPage.Result.Footer="За да ползвате тези настройки натиснете върху Приложи Настройките. За да промените настройките н тестовете и за да опитате отново, натиснете Назад. За ръчно настройване, натиснете Отхвърли и отворете Настройките." +Basic.Stats="Статистика" +Basic.Stats.CPUUsage="Използване на централния процесор" +Basic.Stats.HDDSpaceAvailable="Свободно пространство на диска" +Basic.Stats.MemoryUsage="Използване на паметта" +Basic.Stats.AverageTimeToRender="Средно време за рендиране на кадър" +Basic.Stats.SkippedFrames="Изпуснати кадри заради забавяне във кодирането" +Basic.Stats.MissedFrames="Брой изпуснати кадри поради забавяне във изобразяването" +Basic.Stats.Output.Stream="Стрийм" +Basic.Stats.Output.Recording="Записване" +Basic.Stats.Status="Статус" +Basic.Stats.Status.Recording="Записване" +Basic.Stats.Status.Live="НА ЖИВО" +Basic.Stats.Status.Reconnecting="Повторно свързване" +Basic.Stats.Status.Inactive="Неактивно" +Basic.Stats.DroppedFrames="Изпуснати Кадри (Мрежа)" +Basic.Stats.MegabytesSent="Общо количество изпратени данни" +Basic.Stats.Bitrate="Битрейт" +Updater.Title="Има нова актуализация" +Updater.Text="Нова версия на приложението е налична:" +Updater.UpdateNow="Обнови сега" +Updater.RemindMeLater="Напомни ми по-късно" +Updater.Skip="Пропусни Версията" +Updater.Running.Title="Програмата е работи във момента" +Updater.Running.Text="Във момента се предават данни, моля спрете активните канали преди да актуализирате до нова версия" +Updater.NoUpdatesAvailable.Title="Няма обновявания" +Updater.NoUpdatesAvailable.Text="Няма текущи обновявания на разположение" +Updater.FailedToLaunch="Неуспешно стартиране на обновителя" +Updater.GameCaptureActive.Title="Записването на Игра е активно" +Updater.GameCaptureActive.Text="Библиотеката със куки за Записване на Игра се ползва. Моля затворете игрите/програмите които се записват (или пуснете операционната система наново) и опитайте отново." +QuickTransitions.SwapScenes="Смени сцените Преглед/Продукция след Преминаване" +QuickTransitions.SwapScenesTT="Сменя сцените за Преглед и Продукция след преминаването (ако все още съществува оригиналната сцена).\n Това няма да отмени промените, които може да са направени в оригиналната сцена на изхода." +QuickTransitions.DuplicateScene="Дублиране на Сцената" +QuickTransitions.DuplicateSceneTT="Когато редактирате една и съща сцена, можете да редактирате трансформацията/видимостта на източниците, без да променяте изхода.\n За да редактирате свойствата на източниците, без да променяте изхода, активирайте \"Дублиране на източници\". \n Промяната на тази стойност ще нулира текущата изходна сцена (ако все още съществува)." +QuickTransitions.EditProperties="Дублиране на Източници" +QuickTransitions.EditPropertiesTT="При редактиране на една и съща сцена, можете да редактирате характеристиките на източниците, без да променяте изхода.\nМоже да се ползва само ако \"Дублиране на Сцената\" е включено.\nКонкретни източници (като тези от Записване и Медия) не подържат функцията и не могат да се редактират поотделно.\nПромяната на тази стойност ще нулира текущата изходна сцена (ако все още съществува). \n\n Предупреждение: Понеже източниците ще се дублират, може да са нужни допълнителни системни или видео ресурси." +QuickTransitions.HotkeyName="Бързо преминаване: %1" +Basic.AddTransition="Добави Преминаване със Настройки" +Basic.RemoveTransition="Премахни Преминаване със Настройки" +Basic.TransitionProperties="Настройки на Преминаването" +Basic.SceneTransitions="Преминавания във сцената" +Basic.TransitionDuration="Времетраене" +Basic.TogglePreviewProgramMode="Режим Студио" +TransitionNameDlg.Text="Моля въведете имато на преминаването" +TransitionNameDlg.Title="Има на преминаването" TitleBar.Profile="Профил" TitleBar.Scenes="Сцени" @@ -61,21 +198,39 @@ NameExists.Text="Името е вече използвано." NoNameEntered.Title="Моля, въведете валидно име" NoNameEntered.Text="Не може да използвате празни имена." +ConfirmStart.Title="Започни излъчването?" +ConfirmStart.Text="Сигурни ли сте че искате да започнете със предаването?" +ConfirmStop.Title="Спри излъчването?" +ConfirmStop.Text="Сигурни ли сте че искате да спрете предаването?" ConfirmExit.Title="Изход от OBS?" +ConfirmExit.Text="OBS във момента е активен. Всички предавания/записи ще бъдат изключени. Сигурни ли сте че искате да излезнете от програмата?" ConfirmRemove.Title="Потвърди премахване" ConfirmRemove.Text="Наистина ли искате да премахнете \"$1\"?" +ConfirmRemove.TextMultiple="Сигурни ли сте че искате да премахнете %1 избрани обекта?" +Output.StartStreamFailed="Неуспешно стартиране на предаване" +Output.StartRecordingFailed="Неуспешно стартиране на запис" +Output.StartReplayFailed="Неуспешно включване на Буферa за повторение" +Output.StartFailedGeneric="Започването на изкарване на кадри неуспешно. Моля проверете списъка за подробности. \n\n Бележка: Ако ползвате кодиране NVENC или AMD, моля проверете дали версията на драйвъри за видео картата е актуална." Output.ConnectFail.Title="Неуспешно свързване" Output.ConnectFail.BadPath="Невалиден път или URL. Проверете дали настройките ви са валидни." Output.ConnectFail.ConnectFailed="Неуспешна връзка със сървъра" +Output.ConnectFail.InvalidStream="Неуспешно сързване към посоченият канал или ключ за предаване, моля проверете повторно вашия ключ. Ако е верен, може би има проблем със всъзката към сървъра." Output.ConnectFail.Error="Неочаквана грешка при опит за връзка със сървъра. Повече информация в \"log\" файла." Output.ConnectFail.Disconnected="Изключен от сървъра." Output.RecordFail.Title="Неуспешно стартиране на запис" +Output.RecordFail.Unsupported="Формата на записа или не е подържа или не подържа повече от една писта за звук. Моля проверете настройките си и опитайте отново." +Output.RecordNoSpace.Title="Недостатъчно място на диска" +Output.RecordNoSpace.Msg="Няма достатъчно място на диска за да продължите да записвате." +Output.RecordError.Title="Грешка при записването" +Output.RecordError.Msg="Неопределена грешка по време на записването." +Output.ReplayBuffer.NoHotkey.Title="Без зададен клавиш!" +Output.ReplayBuffer.NoHotkey.Msg="Без зададен клавиш на Буферa за повторение. Моля настройте клавиша за \"Запаметяване\" на записи от Буфера за Повторение." Output.BadPath.Title="Неправилен файлов път" Output.BadPath.Text="Невалиден файлов път. Моля, проверете дали сте задали правилен файлов път в настройките." @@ -93,6 +248,7 @@ LicenseAgreement.Exit="Изход" Remux.SourceFile="OBS запис" Remux.TargetFile="Целеви файл" Remux.Remux="Конвертиране" +Remux.OBSRecording="OBS запис" Remux.FinishedTitle="Конвертирането завърши" Remux.Finished="Записът е конвертиран" Remux.FinishedError="Записът е конвертиран, но файлът може да бъде незавършен" @@ -118,44 +274,83 @@ Basic.DisplayCapture="Заснемане на екрана" Basic.Main.PreviewConextMenu.Enable="Разреши преглед" +ScaleFiltering="Мащабно Филтриране" +ScaleFiltering.Point="Точково" +ScaleFiltering.Bilinear="Двулинейно" +ScaleFiltering.Bicubic="Двукубово" +ScaleFiltering.Lanczos="Lanczos" +Deinterlacing="Непреплитане" +Deinterlacing.Discard="Отхвърли" +Deinterlacing.Retro="Ретро" +Deinterlacing.Blend="Преливане" +Deinterlacing.Blend2x="Преливане 2х" +Deinterlacing.Linear="Линейно" +Deinterlacing.Linear2x="Линейно 2х" +Deinterlacing.Yadif="Yadif" +Deinterlacing.Yadif2x="Yadif 2x" +Deinterlacing.TopFieldFirst="Първо горното поле" +Deinterlacing.BottomFieldFirst="Първо долното поле" +VolControl.SliderUnmuted="Плъзгач за сила на звука '%1': %2" +VolControl.SliderMuted="Плъзгач за сила на звука '%1': %2 (текущо заглушен)" +VolControl.Mute="Заглуши '%1'" +VolControl.Properties="Свойства на '%1'" Basic.Main.AddSceneDlg.Title="Добави сцена" Basic.Main.AddSceneDlg.Text="Моля, въведете името на сцената" Basic.Main.DefaultSceneName.Text="Сцена %1" +Basic.Main.AddSceneCollection.Title="Добавяне на колекция" +Basic.Main.AddSceneCollection.Text="Моля, въведете името на колекцията от сцени" +Basic.Main.RenameSceneCollection.Title="Преименуване на Колекцията от сцени" AddProfile.Title="Добавяне на профил" AddProfile.Text="Моля, въведете името на профила" RenameProfile.Title="Преименуване на профил" +Basic.Main.MixerRename.Title="Преименуване на Звуков Източник" +Basic.Main.MixerRename.Text="Моля, въведете името на източника на звук" + + Basic.Main.PreviewDisabled="Прегледа в момента е забранен" Basic.SourceSelect="Създай/избери източник" Basic.SourceSelect.CreateNew="Създай нов" Basic.SourceSelect.AddExisting="Добави съществуващ" +Basic.SourceSelect.AddVisible="Направете видим източника" Basic.PropertiesWindow="Свойства на '%1'" +Basic.PropertiesWindow.AutoSelectFormat="%1 (автоматично: %2)" Basic.PropertiesWindow.SelectColor="Избор на цвят" Basic.PropertiesWindow.SelectFont="Избор на шрифт" Basic.PropertiesWindow.ConfirmTitle="Настройките са променени" Basic.PropertiesWindow.Confirm="Има незаписани промени. Искате ли да ги запишете?" +Basic.PropertiesWindow.NoProperties="Без налични характеристики" Basic.PropertiesWindow.AddFiles="Добавяне на файлове" +Basic.PropertiesWindow.AddDir="Добави директория" Basic.PropertiesWindow.AddURL="Добавяне на път/URL" +Basic.PropertiesWindow.AddEditableListDir="Добави директория към '%1'" Basic.PropertiesWindow.AddEditableListFiles="Добавяне на файлове в \"%1\"" Basic.PropertiesWindow.AddEditableListEntry="Добавяне на запис към \"%1\"" Basic.PropertiesWindow.EditEditableListEntry="Редактиране на запис от \"%1\"" +Basic.PropertiesView.FPS.Simple="Прости Стойности на FPS" +Basic.PropertiesView.FPS.Rational="Дробни Стойности на FPS" +Basic.PropertiesView.FPS.ValidFPSRanges="Валиден обсег от Стойности на FPS:" Basic.InteractionWindow="Взаимодействие с \"%1\"" Basic.StatusBar.Reconnecting="Връзката прекъсна, свързване отново след %2 секунди(s) (опит %1)" Basic.StatusBar.AttemptingReconnect="Опит за повторно свързване... (опит %1)" Basic.StatusBar.ReconnectSuccessful="Успешно свързване" +Basic.StatusBar.Delay="Забавяне (%1 сек.)" +Basic.StatusBar.DelayStartingIn="Забавяне (започване след %1 сек.)" +Basic.StatusBar.DelayStoppingIn="Забавяне (спиране след %1 сек.)" +Basic.StatusBar.DelayStartingStoppingIn="Забавяне (спиране след %1 сек., започване след %2 сек.)" Basic.Filters="Филтри" Basic.Filters.AsyncFilters="Аудио/видео филтри" @@ -173,6 +368,7 @@ Basic.TransformWindow.Alignment="Позиционно подравняване" Basic.TransformWindow.BoundsType="Тип ограничител" Basic.TransformWindow.BoundsAlignment="Подравняване в ограничителя" Basic.TransformWindow.Bounds="Размер на ограничителя" +Basic.TransformWindow.Crop="Изрежи" Basic.TransformWindow.Alignment.TopLeft="Горе вляво" Basic.TransformWindow.Alignment.TopCenter="Горе в центъра" @@ -197,16 +393,28 @@ Basic.Main.AddSourceHelp.Text="Трябва да имате поне една с Basic.Main.Scenes="Сцени" Basic.Main.Sources="Източници" +Basic.Main.Controls="Управление" Basic.Main.Connecting="Свързване..." Basic.Main.StartRecording="Започни запис" +Basic.Main.StartReplayBuffer="Включване на Буферa за Повторение" Basic.Main.StartStreaming="Започни стрийм" Basic.Main.StopRecording="Спри запис" +Basic.Main.StoppingRecording="Спиране на записването..." +Basic.Main.StopReplayBuffer="Изкючване на Буферa за Повторение" +Basic.Main.StoppingReplayBuffer="Спиране на Буферa за Повторение..." 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="Винаги видим" Basic.MainMenu.File.Exit="&Изход" Basic.MainMenu.Edit="&Редактирай" @@ -214,8 +422,15 @@ Basic.MainMenu.Edit.Undo="&Отмени" Basic.MainMenu.Edit.Redo="&Върни" Basic.MainMenu.Edit.UndoAction="&Отмени $1" Basic.MainMenu.Edit.RedoAction="&Отмени $1" +Basic.MainMenu.Edit.LockPreview="Заключване на прегледа" +Basic.MainMenu.Edit.Scale="Усъразмеряване на прегледа" +Basic.MainMenu.Edit.Scale.Window="Усъразмери по Прозорец" +Basic.MainMenu.Edit.Scale.Canvas="Платно (%1x%2)" +Basic.MainMenu.Edit.Scale.Output="Запис (%1x%2)" Basic.MainMenu.Edit.Transform="&Трансформирай" Basic.MainMenu.Edit.Transform.EditTransform="&Редактирай трансформация..." +Basic.MainMenu.Edit.Transform.CopyTransform="Копиране на местоположението" +Basic.MainMenu.Edit.Transform.PasteTransform="Поставяне на местоположението" Basic.MainMenu.Edit.Transform.ResetTransform="&Анулирай трансформация" Basic.MainMenu.Edit.Transform.Rotate90CW="Завърти 90 градуса по часовниковата" Basic.MainMenu.Edit.Transform.Rotate90CCW="Завърти 90 градуса обратно на часовниковата" @@ -230,41 +445,155 @@ 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="Изглед" +Basic.MainMenu.View.Toolbars="Лента Инструменти" +Basic.MainMenu.View.Docks="Докове" +Basic.MainMenu.View.Docks.ResetUI="Върни UI" +Basic.MainMenu.View.Docks.LockUI="Заключи UI" +Basic.MainMenu.View.Toolbars.Listboxes="Често ползвани" +Basic.MainMenu.View.SceneTransitions="Преминавания между сцени" +Basic.MainMenu.View.StatusBar="Статус лента" +Basic.MainMenu.View.Fullscreen.Interface="Изглед на цял екран" +Basic.MainMenu.SceneCollection="Колекция Сцени" +Basic.MainMenu.Profile="Профили" +Basic.MainMenu.Profile.Import="Добави Профил" +Basic.MainMenu.Profile.Export="Запис на Профил" +Basic.MainMenu.SceneCollection.Import="Добавяне на Колекция от сцени" +Basic.MainMenu.SceneCollection.Export="Запис на Колекция от сцени" +Basic.MainMenu.Profile.Exists="Профилът вече съществува" +Basic.MainMenu.SceneCollection.Exists="Колекцията сцени вече съществува" +Basic.MainMenu.Tools="Инструменти" Basic.MainMenu.Help="&Помощ" +Basic.MainMenu.Help.HelpPortal="Портал за Помощ" +Basic.MainMenu.Help.Website="Посети Уебсайта" Basic.MainMenu.Help.Logs="\"Log\" &файлове" +Basic.MainMenu.Help.Logs.ShowLogs="Покажи техническите записи" Basic.MainMenu.Help.Logs.UploadCurrentLog="Качи &текущия \"Log\" файл" Basic.MainMenu.Help.Logs.UploadLastLog="Качи &последния \"Log\" файл" +Basic.MainMenu.Help.Logs.ViewCurrentLog="Преглеждане на Текущи Данни" +Basic.MainMenu.Help.CheckForUpdates="Провери за обновления" Basic.Settings.ProgramRestart="Програмата трябва да бъде рестартирана, за да влязат в сила тези настройки." Basic.Settings.ConfirmTitle="Потвърди промените" Basic.Settings.Confirm="Имате незаписани промени. Запиши промените?" Basic.Settings.General="Основни" +Basic.Settings.General.Theme="Теми" +Basic.Settings.General.Language="Език" +Basic.Settings.General.EnableAutoUpdates="Автоматично проверяване за обновления при включване" +Basic.Settings.General.OpenStatsOnStartup="Отвори диалогът със статистики при включване" +Basic.Settings.General.WarnBeforeStartingStream="Покажи диалогът за потвърждение при започване на предаване" +Basic.Settings.General.WarnBeforeStoppingStream="Покажи диалогът за потвърждение при приключване на предаване" +Basic.Settings.General.Projectors="Прожектори" +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.ReplayBufferWhileStreaming="Автоматично включване на Буферa за повторение при излъчване" +Basic.Settings.General.KeepReplayBufferStreamStops="Остави Буферa за повторение активен след приключване на излъчването" +Basic.Settings.General.SysTray="Системен контейнер" +Basic.Settings.General.SysTrayWhenStarted="Минимизирай във системния контейнер при включване" +Basic.Settings.General.SystemTrayHideMinimize="Винаги минимизирай във системния контейнер вместо във лентата със програми" +Basic.Settings.General.SaveProjectors="Запамети прожекторите при изход" +Basic.Settings.General.SwitchOnDoubleClick="Преминаване към сцена при двойно кликване" +Basic.Settings.General.StudioPortraitLayout="Включи портретен / вертикален изглед" +Basic.Settings.General.MultiviewLayout="Разположение на Множествения Изглед" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Хоризонтално, Горе" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Водоравно, Долу" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Вертикално, от Ляво" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Вертикално, от Дясно" Basic.Settings.Stream="Стрийм" Basic.Settings.Stream.StreamType="Тип Стрийм" Basic.Settings.Output="Излъчване" +Basic.Settings.Output.Format="Формат на Записа" +Basic.Settings.Output.Encoder="Енкодер" +Basic.Settings.Output.SelectDirectory="Настройте папка за записи" +Basic.Settings.Output.SelectFile="Изберете запис" +Basic.Settings.Output.EnforceBitrate="Приложи горна граница спрямо мястото на излъчване" Basic.Settings.Output.Mode="Режим на излъчване" +Basic.Settings.Output.Mode.Simple="Опростен" +Basic.Settings.Output.Mode.Adv="Допълнителни настройки" +Basic.Settings.Output.Mode.FFmpeg="FFmpeg изходен формат" +Basic.Settings.Output.UseReplayBuffer="Включване на Буферa за Повторение" +Basic.Settings.Output.ReplayBuffer.SecondsMax="Максимално време на повторение (Секунди)" +Basic.Settings.Output.ReplayBuffer.MegabytesMax="Максимална Памет (Мегабайти)" +Basic.Settings.Output.ReplayBuffer.Estimate="Приблизително използвана памет: %1 MB" +Basic.Settings.Output.ReplayBuffer.EstimateUnknown="Не може да се определи използваната памет. Моля нстройте максималния лимит за паметта." +Basic.Settings.Output.ReplayBuffer.HotkeyMessage="(Бележка: Моля проверете че има настроен клавиш за буфера във секцията с бързи-клавиши)" +Basic.Settings.Output.ReplayBuffer.Prefix="Представка на името на записи от Буферa за Повторение" +Basic.Settings.Output.ReplayBuffer.Suffix="Надставка" +Basic.Settings.Output.Simple.SavePath="Път към записа" +Basic.Settings.Output.Simple.RecordingQuality="Качество на записа" +Basic.Settings.Output.Simple.RecordingQuality.Stream="Същото като излъчването" +Basic.Settings.Output.Simple.RecordingQuality.Small="Високо Качество, Среден Размер" +Basic.Settings.Output.Simple.RecordingQuality.HQ="Много Високо Качество, Голям Размер на Записа" +Basic.Settings.Output.Simple.RecordingQuality.Lossless="Оригинално Качество, Изключително Голям размер на записа" +Basic.Settings.Output.Simple.Warn.VideoBitrate="Предупреждение: Битрейта на излъченото видео ще бъде настроен на %1, което е горната граница на текущата платформа. Ако сте сигурни че искате да надвишите %1, включете допълнителните настройки за Енкодера и махнете отметката от \"Приложи горна граница спрямо мястото на излъчване\"." +Basic.Settings.Output.Simple.Warn.AudioBitrate="Предупреждение: Битрейта на излъченият звук ще бъде настроен на %1, което е горната граница на текущата платформа. Ако сте сигурни че искате да надвишите %1, включете допълнителните настройки за Енкодера и махнете отметката от \"Приложи горна граница спрямо мястото на излъчване\"." +Basic.Settings.Output.Simple.Warn.Encoder="Предупреждение: Записването със програмен енкодер при различно качество от излъчването ще изисква допълнителни ресурси от процесора ако записвате и излъчвате едновременно." +Basic.Settings.Output.Simple.Warn.Lossless="Предупреждение: Оригиналното качество създава огромни файлове! Запис настроен на Оригинално качество може да заема над 7GB дисково пространство на минута, ако резолюцията и кадрите са високи. Не се препоръчва ако не разполагате със много пространство." +Basic.Settings.Output.Simple.Warn.Lossless.Msg="Сигурни ли сте че искате да ползвате оригиналното качество на записа?" +Basic.Settings.Output.Simple.Warn.Lossless.Title="Предупреждение при ползване на Оригинално Качество!" Basic.Settings.Output.VideoBitrate="Видео битрейт" Basic.Settings.Output.AudioBitrate="Аудио битрейт" Basic.Settings.Output.Reconnect="Автоматично повторно свързване" Basic.Settings.Output.RetryDelay="Отлагане на повторно свързване (секунди)" Basic.Settings.Output.MaxRetries="Максимален брой повторни опити" +Basic.Settings.Output.Adv.Audio.Track1="Писта 1" +Basic.Settings.Output.Adv.Audio.Track2="Писта 2" +Basic.Settings.Output.Adv.Audio.Track3="Писта 3" +Basic.Settings.Output.Adv.Audio.Track4="Писта 4" +Basic.Settings.Output.Adv.Audio.Track5="Писта 5" +Basic.Settings.Output.Adv.Audio.Track6="Писта 6" +Basic.Settings.Output.Adv.Recording="Запис" +Basic.Settings.Output.Adv.Recording.Type="Тип" +Basic.Settings.Output.Adv.Recording.Type.Standard="Стандартен" +Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput="Изход по Избор (FFmpeg)" +Basic.Settings.Output.Adv.Recording.UseStreamEncoder="(Използвай енкодера за излъчването)" +Basic.Settings.Output.Adv.Recording.Filename="Формат на името" +Basic.Settings.Output.Adv.Recording.OverwriteIfExists="Презапиши ако файлът съществува" +Basic.Settings.Output.Adv.FFmpeg.Type="Тип FFmpeg изходен формат" +Basic.Settings.Output.Adv.FFmpeg.Type.URL="Запиши към Интернет Адрес" +Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile="Запиши на файл" +Basic.Settings.Output.Adv.FFmpeg.SaveFilter.Common="Често срещани формати за запис" +Basic.Settings.Output.Adv.FFmpeg.SaveFilter.All="Всички файлове" +Basic.Settings.Output.Adv.FFmpeg.SavePathURL="Път към файла или адрес" +Basic.Settings.Output.Adv.FFmpeg.Format="Формат на контейнера" Basic.Settings.Output.Adv.FFmpeg.FormatAudio="Аудио" Basic.Settings.Output.Adv.FFmpeg.FormatVideo="Видео" Basic.Settings.Output.Adv.FFmpeg.FormatDefault="Формат по подразбиране" +Basic.Settings.Output.Adv.FFmpeg.FormatDesc="Описание на формата на контейнера" +Basic.Settings.Output.Adv.FFmpeg.FormatDescDef="Аудио / видео кодека се разпознава от файла или URL адреса" +Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault="Кодиране по подразбиране" +Basic.Settings.Output.Adv.FFmpeg.AVEncoderDisable="Изключи Кодирането" +Basic.Settings.Output.Adv.FFmpeg.VEncoder="Видео енкодер" +Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings="Настройки на видео енкодера (ако има)" +Basic.Settings.Output.Adv.FFmpeg.AEncoder="Звуков енкодер" +Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Настройки на звуковия енкодер (ако има)" +Basic.Settings.Output.Adv.FFmpeg.MuxerSettings="Настройки при сливане (ако има)" +Basic.Settings.Output.Adv.FFmpeg.GOPSize="Интервал между ключови кадри (кадри)" +Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat="Покажи всички кодеци (дори и ако са несъвместими)" Basic.Settings.Video="Видео" Basic.Settings.Video.Adapter="Видео адаптер:" +Basic.Settings.Video.BaseResolution="Основна Резолюция на Платното" +Basic.Settings.Video.ScaledResolution="Резолюция на записа (умален)" Basic.Settings.Video.DownscaleFilter="Филтър:" Basic.Settings.Video.DisableAeroWindows="Изключи Aero прозрачност (само за Windows)" Basic.Settings.Video.FPS="Кадри в секунда (FPS):" @@ -273,23 +602,58 @@ Basic.Settings.Video.FPSInteger="FPS стойност - цяло число" Basic.Settings.Video.FPSFraction="FPS стойност - дробно число" Basic.Settings.Video.Numerator="Числител:" Basic.Settings.Video.Denominator="Знаменател:" +Basic.Settings.Video.Renderer="Презаписване" Basic.Settings.Video.InvalidResolution="Невалидна резолюция. Трябва да бъде [ширина] x [височина] (пример: 1920 x 1080)" Basic.Settings.Video.CurrentlyActive="В момента излъчвате. Моля спрете излъчването за да промените видео настройките." +Basic.Settings.Video.DisableAero="Изключи Aero режима" +Basic.Settings.Video.DownscaleFilter.Bilinear="Двулинеен (Най-бърз, но замазан след усъразмеряване)" +Basic.Settings.Video.DownscaleFilter.Bicubic="Двукубичен (Изострено при усъразмеряване, 16 семпли)" +Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Изострено при усъразмеряване, 32 семпли)" Basic.Settings.Audio="Аудио" Basic.Settings.Audio.SampleRate="Честота на дискретизацията" Basic.Settings.Audio.Channels="Канали" +Basic.Settings.Audio.MultiChannelWarning.Enabled="ПРЕДУПРЕЖДЕНИЕ: Включен е Surround sound." +Basic.Settings.Audio.MultichannelWarning="Ако предавате, проверете дали вашата услуга за стрийминг поддържа едновременно приемане на съраунд звук и възпроизвеждане на съраунд звук. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast са примери, при които съраунд звукът е напълно поддържан. Въпреки че, Facebook Live и YouTube Live подържат и приемат съраунд, Facebook Live пемиксира към стерео звук, а YouTube Live възпроизвежда само два канала.\n\nЗвуковите филтъри на OBS подържат съраунд звук, въпреки това VST поддръжката не е гарантирана." +Basic.Settings.Audio.MultichannelWarning.Title="Включи записването на съраунд звук?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Сигурни ли сте че искате да включите записването на съраунд звук?" Basic.Settings.Audio.DesktopDevice="Аудио устройство (Работен плот)" Basic.Settings.Audio.DesktopDevice2="Аудио устройство 2 (Работен плот)" Basic.Settings.Audio.AuxDevice="Микрофон/Вторично аудио устройство" Basic.Settings.Audio.AuxDevice2="Микрофон/Вторично аудио устройство 2" Basic.Settings.Audio.AuxDevice3="Микрофон/Вторично аудио устройство 3" +Basic.Settings.Audio.EnablePushToMute="Включи Натисни-за-да-заглуши" +Basic.Settings.Audio.PushToMuteDelay="Натисни-за-да-заглуши забавяне" +Basic.Settings.Audio.EnablePushToTalk="Включи Натисни-за-говорене" +Basic.Settings.Audio.PushToTalkDelay="Натисни-за-говорене забавяне" +Basic.Settings.Audio.UnknownAudioDevice="[Устройството не е свързано или не е на разположение]" Basic.Settings.Advanced="За напреднали" +Basic.Settings.Advanced.General.ProcessPriority="Приоритет на Програмата" +Basic.Settings.Advanced.General.ProcessPriority.High="Висок" +Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Над Нормата" +Basic.Settings.Advanced.General.ProcessPriority.Normal="Нормален" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Под Нормата" +Basic.Settings.Advanced.FormatWarning="Предупреждение: Цветните формати освен NV12 се ползват главно при записи и не са препоръчани при предаване. Предаването може да упражни завишено ползване на Процесора поради прекодиране на форматите." +Basic.Settings.Advanced.Audio.BufferingTime="Време за буфериране на звук" Basic.Settings.Advanced.Video.ColorFormat="Формат на цвета" +Basic.Settings.Advanced.Video.ColorSpace="YUV Цветно Пространство" +Basic.Settings.Advanced.Video.ColorRange="YUV Цветен Обсег" +Basic.Settings.Advanced.Video.ColorRange.Partial="Частично" +Basic.Settings.Advanced.Video.ColorRange.Full="Пълен" +Basic.Settings.Advanced.Audio.MonitoringDevice="Устройство за Звуково възпроизвеждане" +Basic.Settings.Advanced.Audio.MonitoringDevice.Default="По подразбиране" +Basic.Settings.Advanced.StreamDelay.Duration="Продължителност (секунди)" +Basic.Settings.Advanced.StreamDelay.Preserve="Запази точката на прекъсване (увеличете забавянето) при повторно свързване" +Basic.Settings.Advanced.Network="Мрежа" +Basic.AdvAudio="Допълнителни Звукови Характеристики" +Basic.AdvAudio.Name="Име" Basic.AdvAudio.Volume="Сила на звука (%)" +Basic.AdvAudio.Mono="Премиксирай към Mono звук" +Basic.AdvAudio.Monitoring.None="Мониторът Изключен" +Basic.AdvAudio.AudioTracks="Писти" Basic.Settings.Hotkeys="Горещи клавиши" @@ -304,8 +668,23 @@ Hotkeys.End="Край" Hotkeys.PageUp="Страница нагоре" Hotkeys.PageDown="Страница надолу" Hotkeys.NumLock="Num Lock" +Hotkeys.NumpadAdd="Цифрова клавиатура +" +Hotkeys.NumpadSubtract="Цифрова клавиатура -" +Hotkeys.NumpadDecimal="Цифрова клавиатура ." +Hotkeys.MouseButton="Мишка %1" +Mute="Заглуши" +Unmute="Включи звука" +Push-to-mute="Натисни-за-да-заглуши" +Push-to-talk="Натисни-за-говорене" +SceneItemShow="Покажи '%1'" +SceneItemHide="Скрии '%1'" +OutputWarnings.NoTracksSelected="Трябва да изберете поне една писта за звук" +OutputWarnings.MultiTrackRecording="Предупреждение: Някои формати (като FLV) не подържат множествен брой на писти при запис" +OutputWarnings.MP4Recording="Предупреждение: Записи запаметени като MP4 не могат да бъдат възстановени ако записът не е приключен (във случаи на спиране на програмата или загуба на ток и т.н.). Ако искате да записвате на множество звукови писти ползвайте MKV формат и конвертирайте записа на mp4 след като приключи (Файл->Преконвертиране на записи)" +FinalScene.Title="Изтрий Сцената" +FinalScene.Text="Трябва да има поне една сцена във наличност." diff --git a/UI/data/locale/bn-BD.ini b/UI/data/locale/bn-BD.ini index 53a3630..b6d333f 100644 --- a/UI/data/locale/bn-BD.ini +++ b/UI/data/locale/bn-BD.ini @@ -217,6 +217,8 @@ AddProfile.Text="প্রোফাইলের নাম লিখুন" RenameProfile.Title="প্রোফাইল পুনঃনামকরণ করুন" + + Basic.Main.PreviewDisabled="প্রাক্-বীক্ষণ বর্তমানে নিষ্ক্রিয় করা হয়েছে" Basic.SourceSelect="সূত্র তৈরি/নির্বাচন করুন" diff --git a/UI/data/locale/ca-ES.ini b/UI/data/locale/ca-ES.ini index b203ba4..eb8a382 100644 --- a/UI/data/locale/ca-ES.ini +++ b/UI/data/locale/ca-ES.ini @@ -28,13 +28,21 @@ Browse="Navega" Mono="Mono" Stereo="Estèreo" DroppedFrames="Quadres perduts %1 (%2%)" +StudioProgramProjector="Projector de pantalla completa (programa)" PreviewProjector="Projector de pantalla completa (previsualització)" SceneProjector="Projector de pantalla completa (escena)" SourceProjector="Projector de pantalla completa (orígen)" +StudioProgramWindow="Projector amb finestra (programa)" +PreviewWindow="Projector amb finestra (vista prèvia)" +SceneWindow="Projector amb finestra (escena)" +SourceWindow="Projector amb finestra (origen)" +MultiviewProjector="Vista múltiple (pantalla completa)" +MultiviewWindowed="Vista múltiple (finestra)" Clear="Neteja" Revert="Reverteix" Show="Mostra" Hide="Amaga" +UnhideAll="Mostra-ho tot" Untitled="Sense títol" New="Nou" Duplicate="Duplica" @@ -61,14 +69,94 @@ Paste="Enganxa" PasteReference="Enganxa (referència)" PasteDuplicate="Enganxa (duplicat)" RemuxRecordings="Conversions enregistrades" +Next="Següent" +Back="Anterior" +Defaults="Per defecte" +HideMixer="Amaga al mesclador" +TransitionOverride="Anul·lació de la transició" +None="Cap" +StudioMode.Preview="Vista prèvia" +StudioMode.Program="Programa" +ShowInMultiview="Mostra en vista múltiple" +AlreadyRunning.Title="L'OBS ja s'està executant" +AlreadyRunning.Text="L'OBS ja s'està executant! A no ser que vulgueu fer això, tanqueu totes les finestres de l'OBS abans d'intentar iniciar una nova. Si teniu configurat OBS perquè es minimitzi a la barra de tasques, proveu a veure si segueix executant-se aquí." +AlreadyRunning.LaunchAnyway="Executa de totes maneres" Copy.Filters="Copia els filtres" Paste.Filters="Enganxa els filtres" +BandwidthTest.Region="Regió" +BandwidthTest.Region.US="Estats Units" +BandwidthTest.Region.EU="Europa" +BandwidthTest.Region.Asia="Àsia" +BandwidthTest.Region.Other="Altre" +Basic.FirstStartup.RunWizard="Voleu executar l'assistent de configuració automàtica? També podeu configurar els paràmetres manualment en fer clic al botó configuració de la finestra principal." +Basic.FirstStartup.RunWizard.BetaWarning="(Nota: L'assistent de configuració automàtica està en beta)" +Basic.FirstStartup.RunWizard.NoClicked="Si canvia la seva ment, pot executar l'assistent de configuració automàtica en qualsevol moment des del menú eines." +Basic.AutoConfig="Assistent de configuració automàtic" +Basic.AutoConfig.Beta="Assistent de configuració automàtica (beta)" +Basic.AutoConfig.ApplySettings="Aplica la configuració" +Basic.AutoConfig.StartPage="Informació sobre l'ús" +Basic.AutoConfig.StartPage.SubTitle="Especifiqueu perquè vol fer servir el programa" +Basic.AutoConfig.StartPage.PrioritizeStreaming="Optimitza per a transmissions, l'enregistrament és secundari" +Basic.AutoConfig.StartPage.PrioritizeRecording="Optimitza només per a l'enregistrament, no faré cap transmissió" +Basic.AutoConfig.VideoPage="Configuració de vídeo" +Basic.AutoConfig.VideoPage.SubTitle="Especifiqueu la configuració de vídeo desitjats que vulgueu utilitzar" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Utilitza l'actual (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="Pantalla %1 (%2x%3)" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="Utilitza l'actual (%1)" +Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="60 o 30, però utilitza 60 quan sigui possible" +Basic.AutoConfig.VideoPage.FPS.PreferHighRes="60 o 30, però utilitza l'alta resolució" +Basic.AutoConfig.VideoPage.CanvasExplanation="La resolució del llenç (base) no és necessàriament la mateixa que la resolució de la transmissió o enregistrament. La resolució actual pot ser reduïda del llenç per reduir l'ús dels recursos o de la tassa de bits." +Basic.AutoConfig.StreamPage="Informació de la transmissió" +Basic.AutoConfig.StreamPage.SubTitle="Introduïu informació sobre la seva transmissió" +Basic.AutoConfig.StreamPage.Service="Servei" +Basic.AutoConfig.StreamPage.Service.ShowAll="Mostra-ho tot..." +Basic.AutoConfig.StreamPage.Server="Servidor" +Basic.AutoConfig.StreamPage.StreamKey="Clau de la transmissió" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Enllaç)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Estima la tassa de bits amb una prova d'ample de banda (pot trigar uns minuts)" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Prefereix la codificació per maquinari" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="La codificació per maquinari elimina la majoria de l'ús de la CPU, però pot requerir una tassa de bits superior per obtenir el mateix nivell de qualitat." +Basic.AutoConfig.StreamPage.StreamWarning.Title="Advertència de la transmissió" +Basic.AutoConfig.StreamPage.StreamWarning.Text="La prova d'ample de banda és a punt de transmetre dades de vídeo aleatoris sense àudio al vostre canal. Si podeu, és recomanable desactivar temporalment el que es desin els vídeos de les transmissions i fer la transmissió privada després que la prova hagi finalitzat. Voleu continuar?" +Basic.AutoConfig.TestPage="Resultat final" +Basic.AutoConfig.TestPage.SubTitle.Testing="El programa ara està executant un conjunt de proves per estimar la configuració òptima" +Basic.AutoConfig.TestPage.SubTitle.Complete="Prova finalitzada" +Basic.AutoConfig.TestPage.TestingBandwidth="S'està executant la prova d'ample de banda, això pot trigar uns minuts..." +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="S'està connectant a: %1..." +Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="No s'ha pogut connectar a cap servidor. Reviseu la connexió a Internet i torneu-ho a provar." +Basic.AutoConfig.TestPage.TestingBandwidth.Server="S'està provant l'ample de banda per: %1" +Basic.AutoConfig.TestPage.TestingStreamEncoder="S'està provant codificador de la transmissió, això pot trigar un minut..." +Basic.AutoConfig.TestPage.TestingRecordingEncoder="S'està provant codificador de l'enregistrament, això pot trigar un minut..." +Basic.AutoConfig.TestPage.TestingRes="S'està provant les resolucions, això pot trigar uns minuts..." +Basic.AutoConfig.TestPage.TestingRes.Fail="No s'ha pogut iniciar el codificador" +Basic.AutoConfig.TestPage.TestingRes.Resolution="S'està provant %1x%2 %3 FPS..." +Basic.AutoConfig.TestPage.Result.StreamingEncoder="Codificador de la transmissió" +Basic.AutoConfig.TestPage.Result.RecordingEncoder="Codificador de l'enregistrament" +Basic.AutoConfig.TestPage.Result.Header="El programa ha determinat que aquesta configuració estimada és la més òptima per a vós:" +Basic.AutoConfig.TestPage.Result.Footer="Per utilitzar aquesta configuració, feu clic a aplica la configuració. Per tornar a configurar l'assistent, feu clic a enrere. Per configurar els paràmetres vostè mateix, feu clic a cancel·lar i obriu la configuració." +Basic.Stats="Estadístiques" +Basic.Stats.CPUUsage="Ús de CPU" +Basic.Stats.HDDSpaceAvailable="Espai al disc disponible" +Basic.Stats.MemoryUsage="Ús de la memòria" +Basic.Stats.AverageTimeToRender="Temps de mitjana per processar un fotograma" +Basic.Stats.SkippedFrames="Fotogrames omesos per retard de processament" +Basic.Stats.MissedFrames="Fotogrames perduts per retard de processament" +Basic.Stats.Output.Stream="Transmissió" +Basic.Stats.Output.Recording="S'està enregistrant" +Basic.Stats.Status="Estat" +Basic.Stats.Status.Recording="S'està enregistrant" +Basic.Stats.Status.Live="EN DIRECTE" +Basic.Stats.Status.Reconnecting="S'està reconnectant" +Basic.Stats.Status.Inactive="Inactiu" +Basic.Stats.DroppedFrames="Fotogrames perduts (xarxa)" +Basic.Stats.MegabytesSent="Sortida de dades total" +Basic.Stats.Bitrate="Tassa de bits" Updater.Title="Nova actualització disponible" Updater.Text="Hi ha una nova actualització disponible:" @@ -224,6 +312,10 @@ AddProfile.Text="Introduïu el nom del perfil" RenameProfile.Title="Canvia el nom del perfil" +Basic.Main.MixerRename.Title="Canvia el nom de la font d'àudio" +Basic.Main.MixerRename.Text="Introduïu el nom de la font d'àudio" + + Basic.Main.PreviewDisabled="La previsualització està inhabilitada" Basic.SourceSelect="Creació o selecció de l'origen" @@ -301,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Cal tenir com a mínim 1 escena per afegir una fo Basic.Main.Scenes="Escenes" Basic.Main.Sources="Orígens" +Basic.Main.Controls="Controls" Basic.Main.Connecting="S'està connectant..." Basic.Main.StartRecording="Inicia l'enregistrament" Basic.Main.StartReplayBuffer="Inicia la reproducció de la memòria intermèdia" @@ -356,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="&Propietats avançades d'àudio" Basic.MainMenu.View="Veure" Basic.MainMenu.View.Toolbars="Barres d'eines" +Basic.MainMenu.View.Docks="Acoblador" +Basic.MainMenu.View.Docks.ResetUI="Reinicia la interfície d'usuari" +Basic.MainMenu.View.Docks.LockUI="Bloqueja la interfície d'usuari" Basic.MainMenu.View.Toolbars.Listboxes="Quadre de Llista" Basic.MainMenu.View.SceneTransitions="Transicions d'escena" Basic.MainMenu.View.StatusBar="Barra d'estat" +Basic.MainMenu.View.Fullscreen.Interface="Pantalla completa" Basic.MainMenu.SceneCollection="&Col·lecció d'escenes" Basic.MainMenu.Profile="&Perfil" @@ -372,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="La col·lecció d'escenes ja existeix" Basic.MainMenu.Tools="&Eines" Basic.MainMenu.Help="&Ajuda" +Basic.MainMenu.Help.HelpPortal="Portal d'ajuda" Basic.MainMenu.Help.Website="Visa el lloc &web" Basic.MainMenu.Help.Logs="Fitxers de ®istre" Basic.MainMenu.Help.Logs.ShowLogs="&Mostra els arxius de registre" @@ -388,6 +486,7 @@ Basic.Settings.General="General" Basic.Settings.General.Theme="Tema" Basic.Settings.General.Language="Llengua" Basic.Settings.General.EnableAutoUpdates="Comprova si hi ha actualitzacions automàticament a l'inici" +Basic.Settings.General.OpenStatsOnStartup="Obre el diàleg d'estadístiques a l'inici" 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.Projectors="Projectors" @@ -406,6 +505,13 @@ Basic.Settings.General.SysTray="Safata del sistema" Basic.Settings.General.SysTrayWhenStarted="Minimitzar a la safata del sistema en iniciar" Basic.Settings.General.SystemTrayHideMinimize="Minimitza sempre a la safata del sistema en lloc de la barra de tasques" Basic.Settings.General.SaveProjectors="Desa els projectors en sortir" +Basic.Settings.General.SwitchOnDoubleClick="Transició a l'escena en fer doble clic" +Basic.Settings.General.StudioPortraitLayout="Habilita la disposició horitzontal/vertical" +Basic.Settings.General.MultiviewLayout="Disposició de vista múltiple" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horitzontal, amunt" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horitzontal, abaix" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertical, esquerra" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertical, dreta" Basic.Settings.Stream="Directe" Basic.Settings.Stream.StreamType="Tipus de directe" @@ -525,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (escalat accentuat, 32 mos Basic.Settings.Audio="Àudio" Basic.Settings.Audio.SampleRate="Frequència de mostreig" Basic.Settings.Audio.Channels="Canals" +Basic.Settings.Audio.MeterDecayRate="Resposta del ràtio de l'audiòmetre" +Basic.Settings.Audio.MeterDecayRate.Fast="Ràpida" +Basic.Settings.Audio.MeterDecayRate.Medium="Mitja (PPM de tipus I)" +Basic.Settings.Audio.MeterDecayRate.Slow="Lenta (PPM de tipus II)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="ATENCIÓ: El so envoltant està habilitat." +Basic.Settings.Audio.MultichannelWarning="Si esteu retransmetent, verifiqueu que el servei triat suporta el so envoltant, tant a la reproducció d'entrada com de sortida. El Twitch, el Facebook 360 Live, el Mixer RTMP i el Smashcast són exemples on està completament suportat. Encara que el Facebook Live i el YouTube Live accepten l'entrada de so envoltant, el Facebook Live la converteix a estèreo i el YouTube Live la reprodueix només en 2 canals.\n\nEls filtres d'àudio de l'OBS són compatibles amb el so envoltant, encara que no es garanteix la compatibilitat amb connectors VST." +Basic.Settings.Audio.MultichannelWarning.Title="Voleu habilitar el so envoltant?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Segur que voleu habilitar el so envoltant?" Basic.Settings.Audio.DesktopDevice="Dispositiu d'àudio d'escriptori" Basic.Settings.Audio.DesktopDevice2="Dispositiu d'àudio d'escriptori 2" Basic.Settings.Audio.AuxDevice="Micròfon/Dispositiu d'àudio auxiliar" @@ -541,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Prioritat del procés" Basic.Settings.Advanced.General.ProcessPriority.High="Alta" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Per sobre del normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Per sota del normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Inactiva" Basic.Settings.Advanced.FormatWarning="Advertiment: Els formats de color diferents de NV12 estan destinats principalment per a la gravació i no són recomanables quan es fa un directe. Fer un directe pot comportar un major ús de CPU a causa de la conversió de format de color." Basic.Settings.Advanced.Audio.BufferingTime="Temps de buffer d'àudio" @@ -551,6 +666,7 @@ Basic.Settings.Advanced.Video.ColorRange.Partial="Parcial" Basic.Settings.Advanced.Video.ColorRange.Full="Màxima" Basic.Settings.Advanced.Audio.MonitoringDevice="Dispositiu de monitorització d'àudio" Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Per defecte" +Basic.Settings.Advanced.Audio.DisableAudioDucking="Desactiva la reducció d'àudio de Windows" Basic.Settings.Advanced.StreamDelay="Retard del directe" Basic.Settings.Advanced.StreamDelay.Duration="Durada (en segons)" Basic.Settings.Advanced.StreamDelay.Preserve="Preservar el punt de tall (augmenta retard) quan s'estigui reconnectant" diff --git a/UI/data/locale/cs-CZ.ini b/UI/data/locale/cs-CZ.ini index cd3f3b8..5665c6f 100644 --- a/UI/data/locale/cs-CZ.ini +++ b/UI/data/locale/cs-CZ.ini @@ -28,16 +28,21 @@ Browse="Procházet" Mono="Mono" Stereo="Stereo" DroppedFrames="Ztracené snímky %1 (%2%)" +StudioProgramProjector="Celoobrazovkový projektor (program)" PreviewProjector="Celoobrazovkový projektor (náhled)" SceneProjector="Celoobrazovkový projektor (scéna)" SourceProjector="Celoobrazovkový projektor (zdroj)" +StudioProgramWindow="Projektor v okně (program)" PreviewWindow="Projektor v okně (náhled)" SceneWindow="Projektor v okně (scéna)" SourceWindow="Projektor v okně (zdroj)" +MultiviewProjector="Multiview (Celá obrazovka)" +MultiviewWindowed="Multiview (V okně)" Clear="Vyčistit" Revert="Zvrátit" Show="Zobrazit" Hide="Skrýt" +UnhideAll="Odkrýt vše" Untitled="Nepojmenované" New="Nové" Duplicate="Duplikovat" @@ -66,6 +71,13 @@ PasteDuplicate="Vložit (jako kopii)" RemuxRecordings="Převést nahrávky" Next="Další" Back="Zpět" +Defaults="Výchozí" +HideMixer="Skrýt ve směšovači" +TransitionOverride="Přepsat přechod" +None="Žádný" +StudioMode.Preview="Náhled" +StudioMode.Program="Program" +ShowInMultiview="Zobrazit v Multiview" AlreadyRunning.Title="OBS je již spuštěno" AlreadyRunning.Text="OBS již běží! Pokud jste to opravdu nechtěli udělat, tak prosím ukončete ostatní běžící instance programu OBS před spuštěním nové. Pokud máte nastavenu minimalizaci do lišty, tak se prosím podívejte, zda neběží tam." @@ -300,6 +312,10 @@ AddProfile.Text="Zadejte prosím jméno profilu" RenameProfile.Title="Přejmenování profilu" +Basic.Main.MixerRename.Title="Přejmenovat zdroj zvuku" +Basic.Main.MixerRename.Text="Zadejte prosím název zdroje zvuku" + + Basic.Main.PreviewDisabled="Náhled je zakázán" Basic.SourceSelect="Vytvořit/vybrat zdroj" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Pro přidání zdroje je potřeba přidat alespo Basic.Main.Scenes="Scény" Basic.Main.Sources="Zdroje" +Basic.Main.Controls="Ovládací prvky" Basic.Main.Connecting="Připojování..." Basic.Main.StartRecording="Začít nahrávat" Basic.Main.StartReplayBuffer="Spustit záznam do paměti" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="Rozšířené vl&astnosti zvuku" Basic.MainMenu.View="Zobrazit (&V)" Basic.MainMenu.View.Toolbars="Liš&ty nástrojů" +Basic.MainMenu.View.Docks="Doky" +Basic.MainMenu.View.Docks.ResetUI="Resetovat rozhraní" +Basic.MainMenu.View.Docks.LockUI="Zamknout rozhraní" Basic.MainMenu.View.Toolbars.Listboxes="Seznamy (&L)" Basic.MainMenu.View.SceneTransitions="Pře&chody scény" Basic.MainMenu.View.StatusBar="&Stavový řádek" +Basic.MainMenu.View.Fullscreen.Interface="Rozhraní přes celou obrazovku" Basic.MainMenu.SceneCollection="Kolekce &scén" Basic.MainMenu.Profile="&Profil" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Tato kolekce scén již existuje" Basic.MainMenu.Tools="Nás&troje" Basic.MainMenu.Help="Pomoc (&H)" +Basic.MainMenu.Help.HelpPortal="&Portál pomoci" Basic.MainMenu.Help.Website="Navštívit &web" Basic.MainMenu.Help.Logs="Soubory záznamu (&L)" Basic.MainMenu.Help.Logs.ShowLogs="Zobrazit soubory záznamu (&S)" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Systémová lišta" Basic.Settings.General.SysTrayWhenStarted="Minimalizovat do systémové lišty při spuštění" Basic.Settings.General.SystemTrayHideMinimize="Vždy minimalizovat do systémové lišty místo hlavního panelu" Basic.Settings.General.SaveProjectors="Ukládat projektory při ukončení" +Basic.Settings.General.SwitchOnDoubleClick="Přejít na scénu po dvojitém kliknutí" +Basic.Settings.General.StudioPortraitLayout="Povolit rozložení na výšku (portrét)" +Basic.Settings.General.MultiviewLayout="Rozložení Multiview" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontálně, nahoře" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontálně, dole" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertikálně, vlevo" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertikálně, vpravo" Basic.Settings.Stream="Vysílání" Basic.Settings.Stream.StreamType="Typ vysílání" @@ -602,6 +631,10 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Ostré při škálování Basic.Settings.Audio="Zvuk" Basic.Settings.Audio.SampleRate="Vzorkovací frekvence" Basic.Settings.Audio.Channels="Kanály" +Basic.Settings.Audio.MultiChannelWarning.Enabled="VAROVÁNÍ: Prostorový zvuk je zapnut." +Basic.Settings.Audio.MultichannelWarning="Předtím než začnete vysílat si zkontrolujte, zda vaše vysílací služba podporuje příjem a přehrávání prostorového zvuku. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast jsou příklady služeb, které jej plně podporují. I když Facebook Live a YouTube Live oba podporují příjem prostorového zvuku, Facebook Live jej převede na stereo a YouTube Live přehrává pouze dva kanály.\n\nOBS filtry zvuku jej plně podporují, ale podpora u pluginu VST není garantována." +Basic.Settings.Audio.MultichannelWarning.Title="Povolit prostorový zvuk?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Jste si jisti, že chcete povolit prostorový zvuk?" Basic.Settings.Audio.DesktopDevice="Zařízení zvuku plochy" Basic.Settings.Audio.DesktopDevice2="Zařízení zvuku plochy 2" Basic.Settings.Audio.AuxDevice="Zvukové zařízení - mikrofon/AUX" @@ -618,6 +651,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Priorita procesu" Basic.Settings.Advanced.General.ProcessPriority.High="Vysoká" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Vyšší než normální" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normální" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Menší než normální" Basic.Settings.Advanced.General.ProcessPriority.Idle="Nečinný" Basic.Settings.Advanced.FormatWarning="Varování: Jiné formáty barev než je NV12 jsou primárně určeny pro nahrávání a neměly by být použity pro vysílání. Použití při vysílání zvýší využití CPU." Basic.Settings.Advanced.Audio.BufferingTime="Čas vyrovnávací paměti zvuku" diff --git a/UI/data/locale/da-DK.ini b/UI/data/locale/da-DK.ini index 57e7ed6..15bbbe5 100644 --- a/UI/data/locale/da-DK.ini +++ b/UI/data/locale/da-DK.ini @@ -28,16 +28,21 @@ Browse="Browse" Mono="Mono" Stereo="Stereo" DroppedFrames="Tabte frames %1 (%2%)" +StudioProgramProjector="Fuldskærmsprojektering (program)" PreviewProjector="Fuldskærmsprojektering (forhåndsvisning)" SceneProjector="Fuldskærmsprojektering (scene)" SourceProjector="Fuldskærmsprojektering (kilde)" +StudioProgramWindow="Vinduesprojektering (program)" PreviewWindow="Vinduesprojektering (forhåndsvisning)" SceneWindow="Vinduesprojektering (Scene)" SourceWindow="Vinduesprojektering (kilde)" +MultiviewProjector="Multi View (fuldskærm)" +MultiviewWindowed="Multi View (i vindue)" Clear="Ryd" Revert="Gendan" Show="Vis" Hide="Skjul" +UnhideAll="Vis alle" Untitled="Ikke-navngivet" New="Ny" Duplicate="Dupliker" @@ -66,6 +71,13 @@ PasteDuplicate="Indsæt (dublet)" RemuxRecordings="Remux-optagelser" Next="Næste" Back="Tilbage" +Defaults="Standardindstillinger" +HideMixer="Skjul i Mixer" +TransitionOverride="Tilsidesæt overgang" +None="Ingen" +StudioMode.Preview="Forhåndsvisning" +StudioMode.Program="Program" +ShowInMultiview="Vis i Multi View" AlreadyRunning.Title="OBS kører allerede" AlreadyRunning.Text="OBS kører allerede! Medmindre dette er tilsigtet, så bedes du lukke enhver eksisterende OBS-proces, inden du forsøger at køre en ny. Hvis du har OBS indstillet til at minimeres sig til systembakken, så tjek venligst om den stadig kører dér." @@ -300,6 +312,10 @@ AddProfile.Text="Indtast venligst profilens navn" RenameProfile.Title="Omdøb profil" +Basic.Main.MixerRename.Title="Omdøb lydkilde" +Basic.Main.MixerRename.Text="Angiv navnet på lydkilden" + + Basic.Main.PreviewDisabled="Forhåndsvisning er i øjeblikket deaktiveret" Basic.SourceSelect="Opret/Vælg kilde" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Du skal have mindst 1 scene til at tilføje en ki Basic.Main.Scenes="Scener" Basic.Main.Sources="Kilder" +Basic.Main.Controls="Styring" Basic.Main.Connecting="Forbinder..." Basic.Main.StartRecording="Start optagelse" Basic.Main.StartReplayBuffer="Start Genafspilningsbuffer" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="&Avancerede lydegenskaber" Basic.MainMenu.View="&Vis" Basic.MainMenu.View.Toolbars="&Værktøjslinjer" +Basic.MainMenu.View.Docks="Dokker" +Basic.MainMenu.View.Docks.ResetUI="Nulstil UI" +Basic.MainMenu.View.Docks.LockUI="Lås UI" Basic.MainMenu.View.Toolbars.Listboxes="&Listebokse" Basic.MainMenu.View.SceneTransitions="S&cene overgange" Basic.MainMenu.View.StatusBar="&Statuslinje" +Basic.MainMenu.View.Fullscreen.Interface="Fuldskærms-grænseflade" Basic.MainMenu.SceneCollection="&Scenesamling" Basic.MainMenu.Profile="&Profil" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Scenesamlingen findes allerede" Basic.MainMenu.Tools="Værk&tøjer" Basic.MainMenu.Help="&Hjælp" +Basic.MainMenu.Help.HelpPortal="Hjælp og &Portal" Basic.MainMenu.Help.Website="Besøg &websted" Basic.MainMenu.Help.Logs="&Logfiler" Basic.MainMenu.Help.Logs.ShowLogs="Vis log-filer (&S)" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Processlinjen" Basic.Settings.General.SysTrayWhenStarted="Minimer til proceslinjen ved start" Basic.Settings.General.SystemTrayHideMinimize="Minimer altid til processlinjen i stedet for værktøjslinjen" Basic.Settings.General.SaveProjectors="Gem projektorer ved afslutning" +Basic.Settings.General.SwitchOnDoubleClick="Overgang til scenen ved dobbeltklik" +Basic.Settings.General.StudioPortraitLayout="Aktivere stående/liggende layout" +Basic.Settings.General.MultiviewLayout="Multivisningslayout" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horisontal, Top" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horisontal, Bund" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertikal, Venstre" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertikal, Højre" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Streamtype" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Skarp skalering, 32 prøv Basic.Settings.Audio="Lyd" Basic.Settings.Audio.SampleRate="Sample Rate" Basic.Settings.Audio.Channels="Kanaler" +Basic.Settings.Audio.MeterDecayRate="Lydmålerudstyring, faldhastighed" +Basic.Settings.Audio.MeterDecayRate.Fast="Hurtig" +Basic.Settings.Audio.MeterDecayRate.Medium="Medium (Type I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Langsom (Type II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="ADVARSEL: Surround Sound-lyd er aktiveret." +Basic.Settings.Audio.MultichannelWarning="Tjek ifm. streamer, om din streaming-tjeneste understøtter både Surround Sound-input og -afspilning. Twitch, Facebook 360 Live, Mixer RTMP og Smashcast er eksempler, hvor surroundlyd understøttes fuldt ud. Selvom Facebook Live og YouTube Live begge accepterer surround input, nedmikser Facebook Live til stereo, og YouTube Live afspiller kun to kanaler.\n\nOBS-lydfiltre er kompatible med surroundlyd, dog er VST-pluginsupport ikke garanteret." +Basic.Settings.Audio.MultichannelWarning.Title="Aktivér Surround Sound-lyd?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Sikker på, at du vil aktivere Surround Sound-lyd?" Basic.Settings.Audio.DesktopDevice="Skrivebord lydenhed" Basic.Settings.Audio.DesktopDevice2="Skrivebord lydenhed 2" Basic.Settings.Audio.AuxDevice="Mic/Auxiliary lydenhed" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Proces prioritet" Basic.Settings.Advanced.General.ProcessPriority.High="Høj" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Over normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Under Normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Ikke aktiv" Basic.Settings.Advanced.FormatWarning="Advarsel: Farveformater ud over NV12 er primært beregnet til optagelse, og de anbefales ikke under streaming. Streaming kan medføre øget CPU-forbrug grundet farveformatkonvertering." Basic.Settings.Advanced.Audio.BufferingTime="Lyd-bufferinterval" diff --git a/UI/data/locale/de-DE.ini b/UI/data/locale/de-DE.ini index 59a75a0..d0f64dc 100644 --- a/UI/data/locale/de-DE.ini +++ b/UI/data/locale/de-DE.ini @@ -27,20 +27,25 @@ Mixer="Mixer" Browse="Durchsuchen" Mono="Mono" Stereo="Stereo" -DroppedFrames="Verworfene Frames %1 (%2%)" +DroppedFrames="Ausgelassene Frames %1 (%2%)" +StudioProgramProjector="Vollbild-Projektor (Programm)" PreviewProjector="Vollbild-Projektor (Vorschau)" SceneProjector="Vollbild-Projektor (Szene)" SourceProjector="Vollbild-Projektor (Quelle)" +StudioProgramWindow="Fenstermodus-Projektor (Programm)" PreviewWindow="Fenstermodus-Projektor (Vorschau)" SceneWindow="Fenstermodus-Projektor (Szene)" SourceWindow="Fenstermodus-Projektor (Quelle)" -Clear="Entfernen" -Revert="Wiederherstellen" +MultiviewProjector="Multiview (Vollbild)" +MultiviewWindowed="Multiview (Fenstermodus)" +Clear="Löschen" +Revert="Zurücksetzen" Show="Anzeigen" Hide="Ausblenden" +UnhideAll="Alle einblenden" Untitled="Unbenannt" New="Neu" -Duplicate="Klonen" +Duplicate="Duplizieren" Enable="Aktivieren" DisableOSXVSync="OSX V-Sync deaktivieren" ResetOSXVSyncOnExit="OSX V-Sync beim Beenden zurücksetzen" @@ -66,6 +71,13 @@ PasteDuplicate="Einfügen (Duplikat)" RemuxRecordings="Remuxe Aufnahmen" Next="Weiter" Back="Zurück" +Defaults="Zurücksetzen" +HideMixer="Im Mixer ausblenden" +TransitionOverride="Übergangsüberschreibung" +None="Keine" +StudioMode.Preview="Vorschau" +StudioMode.Program="Programm" +ShowInMultiview="In Multiview anzeigen" AlreadyRunning.Title="OBS wird bereits ausgeführt" AlreadyRunning.Text="OBS wird bereits ausgeführt! Bitte beenden Sie alle vorhandenen OBS Instanzen bevor Sie eine neue Instanz starten, es sei denn, Sie tun dies absichtlich. Wenn Sie OBS so eingestellt haben das es sich zum Infobereich minimiert, überprüfen Sie bitte, ob es dort läuft." @@ -300,6 +312,10 @@ AddProfile.Text="Bitte geben Sie den Namen des Profils ein" RenameProfile.Title="Profil umbenennen" +Basic.Main.MixerRename.Title="Audioquelle umbenennen" +Basic.Main.MixerRename.Text="Bitte geben Sie einen Namen für die Audioquelle ein" + + Basic.Main.PreviewDisabled="Vorschau ist derzeit deaktiviert" Basic.SourceSelect="Quelle erstellen/auswählen" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Sie müssen mindestens 1 Szene besitzen, um eine Basic.Main.Scenes="Szenen" Basic.Main.Sources="Quellen" +Basic.Main.Controls="Steuerung" Basic.Main.Connecting="Verbinden..." Basic.Main.StartRecording="Aufnahme starten" Basic.Main.StartReplayBuffer="Replaypuffer starten" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="Erweiterte &Audioeigenschaften" Basic.MainMenu.View="Ansicht (&V)" Basic.MainMenu.View.Toolbars="Werkzeugleisten (&T)" +Basic.MainMenu.View.Docks="Docks" +Basic.MainMenu.View.Docks.ResetUI="GUI zurücksetzen" +Basic.MainMenu.View.Docks.LockUI="GUI sperren" Basic.MainMenu.View.Toolbars.Listboxes="&Listenfelder" Basic.MainMenu.View.SceneTransitions="Szenenübergänge (&c)" Basic.MainMenu.View.StatusBar="&Statusleiste" +Basic.MainMenu.View.Fullscreen.Interface="Vollbild-Benutzeroberfläche" Basic.MainMenu.SceneCollection="&Szenen-Sammlung" Basic.MainMenu.Profile="&Profil" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Die Szenen-Sammlung existiert bereits" Basic.MainMenu.Tools="Werkzeuge (&T)" Basic.MainMenu.Help="&Hilfe" +Basic.MainMenu.Help.HelpPortal="Hilfe&portal" Basic.MainMenu.Help.Website="&Webseite besuchen" Basic.MainMenu.Help.Logs="&Logdateien" Basic.MainMenu.Help.Logs.ShowLogs="Logdateien anzeigen (&S)" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Infobereich" Basic.Settings.General.SysTrayWhenStarted="Beim Start zum Infobereich minimieren" Basic.Settings.General.SystemTrayHideMinimize="Immer zum Infobereich. statt zur Taskleiste minimieren" Basic.Settings.General.SaveProjectors="Projektoren beim Beenden speichern" +Basic.Settings.General.SwitchOnDoubleClick="Übergang zur Szene beim Doppelklicken" +Basic.Settings.General.StudioPortraitLayout="Porträt/vertikales Layout aktivieren" +Basic.Settings.General.MultiviewLayout="Multiview Layout" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontal, oben" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontal, unten" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertikal, links" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertikal, rechts" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Stream Typ" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (geschärfte Skalierung, 3 Basic.Settings.Audio="Audio" Basic.Settings.Audio.SampleRate="Abtastrate" Basic.Settings.Audio.Channels="Kanäle" +Basic.Settings.Audio.MeterDecayRate="Aussteuerungsmesserverfallsrate" +Basic.Settings.Audio.MeterDecayRate.Fast="Schnell" +Basic.Settings.Audio.MeterDecayRate.Medium="Mittel (Type I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Langsam (Type II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="WARNUNG: Surround-Sound-Audio ist aktiviert." +Basic.Settings.Audio.MultichannelWarning="Überprüfen Sie beim Streaming, ob Ihr Streaming-Dienst sowohl die Einspeisung von Surround-Sound als auch die Surround-Sound-Wiedergabe unterstützt. Twitch, Facebook 360 Live, Mixer RTMP und Smashcast sind Beispiele, bei denen Surround Sound voll unterstützt wird. Obwohl Facebook Live und YouTube Live beide die Surround-Einspeisung akzeptieren, wird Facebook Live auf Stereo heruntergemischt und YouTube Live spielt nur zwei Kanäle ab.\n\nOBS-Audiofilter sind mit Surround-Sound kompatibel, obwohl die VST-Plugin-Unterstützung nicht garantiert ist." +Basic.Settings.Audio.MultichannelWarning.Title="Surround-Sound-Audio aktivieren?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Sind Sie sicher, dass Sie Surround-Sound-Audio wirklich aktivieren möchten?" Basic.Settings.Audio.DesktopDevice="Desktop Audiogerät" Basic.Settings.Audio.DesktopDevice2="Desktop Audiogerät 2" Basic.Settings.Audio.AuxDevice="Mikrofon/Externes Audiogerät" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Prozesspriorität" Basic.Settings.Advanced.General.ProcessPriority.High="Hoch" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Höher als normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Niedriger als normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Niedrig" Basic.Settings.Advanced.FormatWarning="Warnung: Andere Farbformate als NV12 sind in erster Linie für die Aufnahme bestimmt und sind für Streaming nicht zu empfehlen. Die erforderliche Farbformatkonvertierung kann eine erhöhte CPU-Auslastung hervorrufen." Basic.Settings.Advanced.Audio.BufferingTime="Audio Pufferungszeit" diff --git a/UI/data/locale/el-GR.ini b/UI/data/locale/el-GR.ini index f52f0cb..17fe7ff 100644 --- a/UI/data/locale/el-GR.ini +++ b/UI/data/locale/el-GR.ini @@ -23,7 +23,7 @@ Settings="Ρυθμίσεις" Display="Οθόνη" Name="Όνομα" Exit="Έξοδος" -Mixer="Μίξερ" +Mixer="Μίκτης" Browse="Αναζήτηση" Mono="Μονοφωνικό" Stereo="Στερεοφωνικό" @@ -31,34 +31,154 @@ DroppedFrames="Διακεκομμένα καρέ %1 (%2%)" PreviewProjector="Προβολέας Πλήρους Οθόνης (Προεπισκόπηση)" SceneProjector="Προβολέας Πλήρους Οθόνης (Σκηνή)" SourceProjector="Προβολέας Πλήρους Οθόνης (Πηγή)" +PreviewWindow="Σε παράθυρο στον Προβολέα (Προεπισκόπηση)" +SceneWindow="Σε παράθυρο στον Προβολέα (Σκηνή)" +SourceWindow="Σε παράθυρο στον Προβολέα (Πηγή)" Clear="Καθαρισμός" Revert="Επαναφορά" Show="Εμφάνιση" Hide="Απόκρυψη" +UnhideAll="Επανεμφάνιση όλων" Untitled="Χωρίς όνομα" New="Νέο" -Duplicate="Διπλότυπη εγγραφή" +Duplicate="Διπλότυπο" Enable="Ενεργοποίηση" DisableOSXVSync="Απενεργοποίηση OSX V-Sync" +ResetOSXVSyncOnExit="Επαναφορά OSX V-Sync κατά την έξοδο" +HighResourceUsage="Υπερφόρτωση κωδικοποίησης! Δοκιμάστε να χαμηλώσετε τις ρυθμίσεις βίντεο ή να χρησιμοποιήσετε μια ταχύτερη επιλογή κωδικοποίησης." +Transition="Μετάβαση" +QuickTransitions="Γρήγορες μεταβάσεις" Left="Αριστερά" Right="Δεξιά" Top="Επάνω" Bottom="Κάτω" +Reset="Επαναφορά" Hours="Ώρες" Minutes="Λεπτά" Seconds="Δευτερόλεπτα" +Deprecated="Παλαιότερα Χρησιμοποιούμενες" +ReplayBuffer="Διάρκεια μνήμης Replay" +Import="Εισαγωγή" +Export="Εξαγωγή" +Copy="Αντιγραφή" +Paste="Επικόλληση" +PasteReference="Επικόλληση (Με Αναφορά)" +PasteDuplicate="Επικόλληση (Αντίγραφο)" +RemuxRecordings="Μετατροπή εγγεγραμένων αρχείων" +Next="Επόμενο" +Back="Προηγούμενο" +Defaults="Προεπιλογές" +HideMixer="Απόκρυψη στον Μίκτη" +AlreadyRunning.Title="Το OBS εκτελείται ήδη" +AlreadyRunning.Text="Το OBS εκτελείται ήδη! Εκτός αν θέλατε να το κάνετε αυτό, παρακαλούμε τερματίστε τις τρέχουσες διεργασίες OBS πριν προσπαθήσετε να εκκινήσετε μια καινούρια. Εάν έχετε ρυθμίσει το OBS να ελαχιστοποιείται στην γραμμή εργαλείων, παρακαλούμε να ελένξετε αν τρέχει ήδη εκεί." +AlreadyRunning.LaunchAnyway="Εκκίνηση ούτως ή άλλως" +Copy.Filters="Αντιγραφή Φίλτρων" +Paste.Filters="Επικόλληση Φίλτρων" +BandwidthTest.Region="Περιοχή" +BandwidthTest.Region.US="Ηνωμένες Πολιτείες" +BandwidthTest.Region.EU="Ευρώπη" +BandwidthTest.Region.Asia="Ασία" +BandwidthTest.Region.Other="Άλλη" +Basic.FirstStartup.RunWizard="Θα θέλατε να εκτελέσετε τον Οδηγό αυτόματης ρύθμισης παραμέτρων; Μπορείτε επίσης να κάνετε ρύθμιση παραμέτρων με μη αυτόματο τρόπο, κάνοντας κλικ στο κουμπί ρυθμίσεις στο κύριο παράθυρο." +Basic.FirstStartup.RunWizard.BetaWarning="(Σημείωση: Ο οδηγός αυτόματης ρύθμισης παραμέτρων είναι επί του παρόντος σε δοκιμαστική έκδοση)" +Basic.FirstStartup.RunWizard.NoClicked="Εάν αλλάξετε γνώμη, μπορείτε να εκτελέσετε τον Οδηγό αυτόματης ρύθμισης παραμέτρων οποιαδήποτε στιγμή ξανά, από το μενού Εργαλεία." +Basic.AutoConfig="Οδηγός αυτόματης ρύθμισης παραμέτρων" +Basic.AutoConfig.Beta="Οδηγός αυτόματης ρύθμισης παραμέτρων (Δοκιμαστική Έκδοση)" +Basic.AutoConfig.ApplySettings="Εφαρμογή ρυθμίσεων" +Basic.AutoConfig.StartPage="Πληροφορίες χρήσης" +Basic.AutoConfig.StartPage.SubTitle="Καθορίστε για ποιό λόγο θέλετε να χρησιμοποιήσετε το πρόγραμμα" +Basic.AutoConfig.StartPage.PrioritizeStreaming="Βελτιστοποίηση για streaming, η εγγραφή είναι δευτερεύουσα" +Basic.AutoConfig.StartPage.PrioritizeRecording="Βελτιστοποίηση μόνο για εγγραφή, δεν θα κάνω streaming" +Basic.AutoConfig.VideoPage="Ρυθμίσεις βίντεο" +Basic.AutoConfig.VideoPage.SubTitle="Καθορίστε τις επιθυμητές ρυθμίσεις βίντεο που θα θέλατε να χρησιμοποιήσετε" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Χρήση Παρούσας (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="Οθόνη %1 (%2x%3)" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="Χρήση Παρόντων (%1)" +Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="Είτε 60 είτε 30, αλλά προτίμηση 60 όταν είναι δυνατόν" +Basic.AutoConfig.VideoPage.FPS.PreferHighRes="Είτε 60 είτε 30, αλλά προτίμηση υψηλής ανάλυσης" +Basic.AutoConfig.VideoPage.CanvasExplanation="Σημείωση: Η (βασική) ανάλυση του καμβά δεν είναι αναγκαστικά η ίδια με την ανάλυση στην οποία θα κάνετε stream ή εγγραφή. Η πραγματική ανάλυση stream/εγγραφής μπορεί να ελλατωθεί απο την ανάλυση του καμβά προς μείωση της χρήσης πόρων ή αναγκών bitrate." +Basic.AutoConfig.StreamPage="Πληροφορίες Stream" +Basic.AutoConfig.StreamPage.SubTitle="Παρακαλώ εισάγετε τις πληροφορίες του Stream σας" +Basic.AutoConfig.StreamPage.Service="Υπηρεσία" +Basic.AutoConfig.StreamPage.Service.ShowAll="Εμφάνιση Όλων..." +Basic.AutoConfig.StreamPage.Server="Διακομιστής" +Basic.AutoConfig.StreamPage.StreamKey="Κλειδί Stream" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Σύνδεσμος)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Εκτίμηση bitrate (ροής δεδομένων) με δοκιμή εύρους ζώνης (ενδέχεται να χρειαστούν μερικά λεπτά)" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Προτίμηση κωδικοποίησης υποβοηθούμενης από το υλικό" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="Η υποβοηθούμενη από το υλικό κωδικοποίηση εξαλείφει την περισσότερη χρήση της CPU, αλλά μπορεί να χρειαστεί υψυλότερο bitrate για την επίτευξη του ίδιου επιπέδου ποιότητας." +Basic.AutoConfig.StreamPage.StreamWarning.Title="Προειδοποίηση Stream" +Basic.AutoConfig.StreamPage.StreamWarning.Text="Η δοκιμή του εύρους ζώνης πρόκειται να μεταδόσει τυχαιοποιημένα δεδομένα βίντεο χωρίς ήχο, στο κανάλι σας. Εάν μπορείτε, προτείνεται να απενεργοποιήσετε προσωρινά την αποθήκευση των βίντεο των streams σας και να θέσετε το stream σας σε ιδιωτικό, μέχρι την ολοκλήρωση της δοκιμής. Να συνεχίσω;" +Basic.AutoConfig.TestPage="Τελικά αποτελέσματα" +Basic.AutoConfig.TestPage.SubTitle.Testing="Το πρόγραμμα εκτελεί τώρα ένα σύνολο από δοκιμές, για να εκτιμήσει τις πιο ιδανικές ρυθμίσεις" +Basic.AutoConfig.TestPage.SubTitle.Complete="Οι δοκιμές ολοκληρώθηκαν" +Basic.AutoConfig.TestPage.TestingBandwidth="Πραγματοποίηση δοκιμής εύρους ζώνης, αυτό μπορεί να διαρκέσει μερικά λεπτά..." +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Σύνδεση με %1..." +Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Αδυναμία σύνδεσης με όλους τους διακομιστές, παρακαλούμε ελέγξτε τη σύνδεση σας με το διαδίκτυο και προσπαθήστε ξανά." +Basic.AutoConfig.TestPage.TestingBandwidth.Server="Έλεγχος εύρους ζώνης προς: %1" +Basic.AutoConfig.TestPage.TestingStreamEncoder="Δοκιμή κωδικοποιητή ροής, αυτό μπορεί να διαρκέσει λίγο..." +Basic.AutoConfig.TestPage.TestingRecordingEncoder="Δοκιμή κωδικοποιητή εγγραφής, αυτό μπορεί να διαρκέσει λίγο..." +Basic.AutoConfig.TestPage.TestingRes="Δοκιμή ανάλυσης, αυτό μπορεί να διαρκέσει μερικά λεπτά..." +Basic.AutoConfig.TestPage.TestingRes.Fail="Αποτυχία έναρξης του κωδικοποιητή" +Basic.AutoConfig.TestPage.TestingRes.Resolution="Δοκιμή σε %1x%2 με %3 FPS..." +Basic.AutoConfig.TestPage.Result.StreamingEncoder="Κωδικοποιητής Ροής" +Basic.AutoConfig.TestPage.Result.RecordingEncoder="Κωδικοποιητής Εγγραφής" +Basic.AutoConfig.TestPage.Result.Header="Το πρόγραμμα έχει καθορίσει πως οι ακόλουθες ρυθμίσεις είναι οι πιο ιδανικές για σας:" +Basic.AutoConfig.TestPage.Result.Footer="Για χρήση αυτών των ρυθμίσεων, κάντε κλικ στο κουμπί Εφαρμογή Ρυθμίσεων. Για να ξαναρυθμίσετε τις παραμέτρους του οδηγού και να προσπαθήσετε ξανά, κάντε κλικ στο κουμπί Πίσω. Για να ρυθμίσετε χειροκίνητα οι ίδιοι τις ρυθμίσεις, κάντε κλικ στο κουμπί Άκυρο και ανοίξτε τις ρυθμίσεις." +Basic.Stats="Στατιστικά" +Basic.Stats.CPUUsage="Χρήση της CPU" +Basic.Stats.HDDSpaceAvailable="Διαθέσιμος χώρος στο σκληρό δίσκο" +Basic.Stats.MemoryUsage="Χρήση Mνήμης" +Basic.Stats.AverageTimeToRender="Μέσος χρόνος για να αποδοθεί ένα καρέ" +Basic.Stats.SkippedFrames="Υπερπηδημένα καρέ λόγω καθυστέρησης κωδικοποίησης" +Basic.Stats.MissedFrames="Χαμένα καρέ λόγω καθυστέρησης απόδοσης" +Basic.Stats.Output.Stream="Stream" +Basic.Stats.Output.Recording="Εγγραφή" +Basic.Stats.Status="Κατάσταση" +Basic.Stats.Status.Recording="Γίνεται εγγραφή" +Basic.Stats.Status.Live="ΖΩΝΤΑΝO" +Basic.Stats.Status.Reconnecting="Επανασύνδεση" +Basic.Stats.Status.Inactive="Ανενεργό" +Basic.Stats.DroppedFrames="Καρέ που απορρίφθηκαν (Δίκτυο)" +Basic.Stats.MegabytesSent="Συνολικά δεδομένα εξόδου" +Basic.Stats.Bitrate="Ρυθμός μετάδοσης bit" +Updater.Title="Νέα έκδοση διαθέσιμη" +Updater.Text="Υπάρχει διαθέσιμη μια νέα έκδοση:" +Updater.UpdateNow="Ενημέρωση Τώρα" +Updater.RemindMeLater="Υπενθύμιση αργότερα" +Updater.Skip="Παράλειψη έκδοσης" +Updater.Running.Title="Το πρόγραμμα είναι ενεργό αυτή τη στιγμή" +Updater.Running.Text="Οι έξοδοι είναι προσωρινά ενεργές, παρακαλούμε τερματίστε όλες τις ενεργές εξόδους προτού επιχειρήσετε να ενημερώσετε το πρόγραμμα" +Updater.NoUpdatesAvailable.Title="Δεν υπάρχουν διαθέσιμες ενημερώσεις" +Updater.NoUpdatesAvailable.Text="Δεν υπάρχουν διαθέσιμες ενημερώσεις αυτή τη στιγμή" +Updater.FailedToLaunch="Αποτυχία εκκίνησης της υπηρεσίας ενημέρωσης" +Updater.GameCaptureActive.Title="Καταγραφή παιχνιδιού ενεργή" +Updater.GameCaptureActive.Text="Η βιβλιοθήκη προσάρτησης της εγγραφής παιχνιδιού χρησιμοποιείται ήδη. Παρακαλούμε κλέιστε όλα τα παιχνίδια/προγράμματα που καταγράφονται (ή επανεκκινήστε τα Windows) και προσπαθήστε ξανά." +QuickTransitions.SwapScenes="Εναλλαγή των σκηνών Προεπισκόπησης/Εξόδου μετά τη μετάβαση" +QuickTransitions.SwapScenesTT="Εναλλάσσει τις σκηνές της προεπισκόπησης και της εξόδου μετά τη μετάβαση (εάν η αρχική σκηνή της εξόδου υφίσταται ακόμα). \nΑυτό δεν πρόκειται να ανεραίσει όποιες αλλαγές μπορεί να έχουν γίνει στην αρχική σκηνή της εξόδου." +QuickTransitions.DuplicateScene="Διπλότυπο Σκηνής" +QuickTransitions.DuplicateSceneTT="Κατά την επεξεργασία της ίδιας σκηνής, επιτρέπει την επεξεργασία μετασχηματισμού/ορατότητας των πηγών χωρίς τροποποίηση της εξόδου.\nΓια να επεξεργαστείτε τις ιδιότητες των πηγών χωρίς να αλλάξετε την έξοδο, ενεργοποιήστε τις \"Διπλότυπες Πηγές\".\nΗ αλλαγή αυτής της τιμής θα επαναφέρει την τρέχουσα σκηνή εξόδου (αν υπάρχει ακόμη)." +QuickTransitions.EditProperties="Διπλότυπες Πηγές" +QuickTransitions.EditPropertiesTT="Όταν επεξεργάζεστε την ίδια σκηνή, επιτρέπει την επεξεργασία ιδιοτήτων των πηγών χωρίς να τροποποιήσετε την έξοδο.\nΑυτό μπορεί να χρησιμοποιηθεί μόνο εάν είναι ενεργοποιημένη η λειτουργία \"Διπλότυπες Πηγές\".\nΟρισμένες πηγές (όπως πηγές καταγραφής ή πολυμέσων) δεν το υποστηρίζουν και δεν μπορούν να επεξεργαστούν ξεχωριστά.\nΗ αλλαγή αυτής της τιμής θα επαναφέρει την τρέχουσα σκηνή εξόδου (αν υπάρχει ακόμη).\n\nΠροειδοποίηση: Επειδή οι πηγές θα διπλασιαστούν, μπορεί να χρειαστούν πρόσθετοι πόροι συστήματος ή βίντεο." +QuickTransitions.HotkeyName="Γρήγορη μετάβαση: %1" +Basic.AddTransition="Προσθήκη Προσαρμόσιμης Μετάβασης" +Basic.RemoveTransition="Αφαίρεση Προσαρμόσιμης Μετάβασης" +Basic.TransitionProperties="Ιδιότητες Μετάβασης" +Basic.SceneTransitions="Μεταβάσεις Σκηνών" Basic.TransitionDuration="Διάρκεια" Basic.TogglePreviewProgramMode="Λειτουργία στούντιο" +TransitionNameDlg.Text="Παρακαλώ εισάγετε το όνομα της μετάβασης" +TransitionNameDlg.Title="Όνομα Μετάβασης" TitleBar.Profile="Προφίλ" TitleBar.Scenes="Σκηνές" @@ -69,22 +189,38 @@ NameExists.Text="Το όνομα χρησιμοποιείται ήδη." NoNameEntered.Title="Πληκτρολογήστε ένα έγκυρο όνομα" NoNameEntered.Text="Δεν μπορείτε να χρησιμοποιήσετε κενά ονόματα." +ConfirmStart.Title="Εκκίνηση Ροής (Steam);" +ConfirmStart.Text="Είστε σίγουροι οτι θέλετε να ξεκινήσετε το stream;" +ConfirmStop.Title="Διακοπή Ροής (Stream);" +ConfirmStop.Text="Είστε σίγουροι οτι θέλετε να διακόψετε το stream;" ConfirmExit.Title="Έξοδος από το OBS;" +ConfirmExit.Text="Το OBS είναι προσωρινά ενεργό. Όλες οι ροές (Streams)/εγγραφές θα τερματιστούν. Είστε σίγουροι ότι επιθυμείτε να το κλείσετε;" ConfirmRemove.Title="Επιβεβαίωση Αφαίρεσης" ConfirmRemove.Text="Είστε βέβαιοι ότι θέλετε να καταργήσετε \"$1\";" +ConfirmRemove.TextMultiple="Είστε βέβαιοι ότι θέλετε να αφαιρέσετε %1 επιλογές;" +Output.StartStreamFailed="Αποτυχία εκκίνησης της ροής (streaming)" +Output.StartRecordingFailed="Αποτυχία εκκίνησης της εγγραφής" +Output.StartReplayFailed="Αποτυχία εκκίνησης της προσωρινής μνήμης των επαναλήψεων" +Output.StartFailedGeneric="Αποτυχία εκκίνησης εξόδου. Παρακαλούμε ελέγξτε το αρχείο καταγραγής (log) για λεπτομέρειες.\n\nΣημείωση: Εάν χρησιμοποιείτε τους κωδικοποιητές NVENC ή AMD, βεβαιωθείτε ότι τα προγράμματα οδήγησης της κάρτας γραφικών σας είναι ενημερωμένα." Output.ConnectFail.Title="Η σύνδεση απέτυχε" Output.ConnectFail.BadPath="Μη έγκυρη Διαδρομή ή URL Σύνδεσης. Παρακαλώ ελέγξτε τις ρυθμίσεις σας και επιβεβαιώστε ότι είναι έγκυρες." Output.ConnectFail.ConnectFailed="Απέτυχε η σύνδεση στον διακομιστή" +Output.ConnectFail.InvalidStream="Δεν μπορέσαμε να αποκτήσουμε πρόσβαση στο συγκεκριμένο κανάλι ή κλειδί ροής, παρακαλούμε ελέγξτε ξανά το κλειδί ροής σας. Εάν είναι σωστό, μπορεί να υπάρχει πρόβλημα στη σύνδεση με τον διακομιστή." Output.ConnectFail.Error="Παρουσιάστηκε μη αναμενόμενο σφάλμα κατα την προσπάθεια σύνδεσης με τον διακομιστή. Περισσότερες πληροφορίες στο αρχείο καταγραφής." Output.ConnectFail.Disconnected="Αποσυνδεθήκατε από τον διακομιστή." Output.RecordFail.Title="Αποτυχία εκκίνησης της καταγραφής" Output.RecordFail.Unsupported="Η μορφή εξόδου είτε δεν υποστηρίζεται ή δεν υποστηρίζει παραπάνω από ένα κομμάτι ήχου. Παρακαλώ ελέγξτε τις ρυθμίσεις σας και δοκιμάστε ξανά." +Output.RecordNoSpace.Title="Ανεπαρκής χώρος στον δίσκο" +Output.RecordNoSpace.Msg="Δεν υπάρχει επαρκής χώρος στο δίσκο για να συνεχιστεί η εγγραφή." +Output.RecordError.Title="Σφάλμα εγγραφής" +Output.RecordError.Msg="Παρουσιάστηκε ένα αδιευκρίνιστο σφάλμα κατά την εγγραφή." +Output.ReplayBuffer.NoHotkey.Title="Δεν έχει επιλεχθεί hotkey!" Output.BadPath.Title="Λάθος Διαδρομή Αρχείου" Output.BadPath.Text="Η προκαθορισμένη διαδρομή αρχείου δεν ειναι έγκυρη. Παρακαλώ ελέγξτε τις ρυθμίσεις σας για να επιβεβαιώσετε ότι έχει οριστεί μια έγκυρη διαδρομή αρχείου." @@ -102,6 +238,7 @@ LicenseAgreement.Exit="Έξοδος" Remux.SourceFile="OBS Καταγραφή" Remux.TargetFile="Αρχείο Προορισμού" Remux.Remux="Μετατροπή" +Remux.OBSRecording="Εγγραφή OBS" Remux.FinishedTitle="Η μετατροπή τελείωσε" Remux.Finished="Η καταγραφή μετατράπηκε" Remux.FinishedError="Η καταγραφή μετατράπηκε, άλλα ενδέχεται το αρχείο να είναι ελλιπής" @@ -127,8 +264,28 @@ Basic.DisplayCapture="Σύλληψη Οθόνης" Basic.Main.PreviewConextMenu.Enable="Ενεργοποίηση Προεπισκόπησης" +ScaleFiltering="Φίλτρο Κλίμακας" +ScaleFiltering.Point="Point" +ScaleFiltering.Bilinear="Bilinear" +ScaleFiltering.Bicubic="Bicubic" +ScaleFiltering.Lanczos="Lanczos" +Deinterlacing="Απόπλεξη" +Deinterlacing.Discard="Απόρριψη" +Deinterlacing.Retro="Ρετρό" +Deinterlacing.Blend="Ανάμειξη" +Deinterlacing.Blend2x="Ανάμειξη 2x" +Deinterlacing.Linear="Γραμμικό" +Deinterlacing.Linear2x="Γραμμικό 2x" +Deinterlacing.Yadif="Yadif" +Deinterlacing.Yadif2x="Yadif 2x" +Deinterlacing.TopFieldFirst="Αρχικό Πεδίο Πρώτα" +Deinterlacing.BottomFieldFirst="Τελευταίο Πεδίο Πρώτα" +VolControl.SliderUnmuted="Ρυθμιστής έντασης για '%1': %2" +VolControl.SliderMuted="Ρυθμιστής έντασης για '%1': %2 (προσωρινά σε σίγαση)" +VolControl.Mute="Σίγαση '%1'" +VolControl.Properties="Ιδιότητες για '%1'" Basic.Main.AddSceneDlg.Title="Προσθήκη Σκηνής" Basic.Main.AddSceneDlg.Text="Παρακαλώ εισάγετε το όνομα της σκηνής" @@ -145,30 +302,43 @@ AddProfile.Text="Παρακαλώ εισάγετε το όνομα του προ RenameProfile.Title="Μετονομασία Προφίλ" + + Basic.Main.PreviewDisabled="Η προεπισκόπηση είναι απενεργοποιημένη" Basic.SourceSelect="Δημιουργία/Επιλογή Πηγής" Basic.SourceSelect.CreateNew="Δημιουργία νέων" Basic.SourceSelect.AddExisting="Προσθήκη Υπάρχουσας" +Basic.SourceSelect.AddVisible="Να γίνει η πηγή ορατή" Basic.PropertiesWindow="Ιδιότητες για '%1'" +Basic.PropertiesWindow.AutoSelectFormat="%1 (αυτόματη επιλογή: %2)" Basic.PropertiesWindow.SelectColor="Επιλέξτε χρώμα" Basic.PropertiesWindow.SelectFont="Επιλέξτε γραμματοσειρά" Basic.PropertiesWindow.ConfirmTitle="Άλλαξαν οι Ρυθμίσεις" Basic.PropertiesWindow.Confirm="Υπάρχουν μη αποθηκευμένες αλλαγές. Θέλετε να τις κρατήσετε;" Basic.PropertiesWindow.NoProperties="Δεν υπάρχουν διαθέσιμες ιδιότητες" Basic.PropertiesWindow.AddFiles="Προσθήκη Αρχείων" +Basic.PropertiesWindow.AddDir="Προσθήκη φακέλου" Basic.PropertiesWindow.AddURL="Προσθήκη Διαδρομής/URL" +Basic.PropertiesWindow.AddEditableListDir="Προσθήκη φακέλου στο '%1'" Basic.PropertiesWindow.AddEditableListFiles="Προσθήκη αρχείων στο '%1'" Basic.PropertiesWindow.AddEditableListEntry="Προσθήκη καταχώρησης στο '%1'" Basic.PropertiesWindow.EditEditableListEntry="Επεξεργασία καταχώρησης από '%1'" +Basic.PropertiesView.FPS.Simple="Ακέραιες Τιμές FPS" +Basic.PropertiesView.FPS.Rational="Κλασματικές Τιμές FPS" +Basic.PropertiesView.FPS.ValidFPSRanges="Έγκυρο εύρος τιμών FPS:" Basic.InteractionWindow="Αλληλεπίδραση με '%1'" Basic.StatusBar.Reconnecting="Έγινε αποσύνδεση, επανασύνδεση σε %2 δευτερόλεπτα (προσπάθεια %1)" Basic.StatusBar.AttemptingReconnect="Γίνεται προσπάθεια επανασύνδεσης... (προσπάθεια %1)" Basic.StatusBar.ReconnectSuccessful="Επανασύνδεση επιτυχής" +Basic.StatusBar.Delay="Καθυστέρηση (%1 δευτερόλεπτα)" +Basic.StatusBar.DelayStartingIn="Καθυστέρηση (έναρξη σε %1 δευτερόλεπτα)" +Basic.StatusBar.DelayStoppingIn="Καθυστέρηση (διακοπή σε %1 δευτερόλεπτα)" +Basic.StatusBar.DelayStartingStoppingIn="Καθυστέρηση (διακοπή σε %1 δευτερόλεπτα, έναρξη σε %2 δευτερόλεπτα)" Basic.Filters="Φίλτρα" Basic.Filters.AsyncFilters="Φίλτρα Ήχου/Βίντεο" @@ -186,6 +356,7 @@ Basic.TransformWindow.Alignment="Ευθυγράμμιση Τοποθεσίας" Basic.TransformWindow.BoundsType="Τύπος Πλαισίου Οριοθέτησης" Basic.TransformWindow.BoundsAlignment="Ευθυγράμμιση στο Πλαίσιο Οριοθέτησης" Basic.TransformWindow.Bounds="Μέγεθος Πλαισίου Οριοθέτησης" +Basic.TransformWindow.Crop="Περικοπή" Basic.TransformWindow.Alignment.TopLeft="Πάνω Αριστερά" Basic.TransformWindow.Alignment.TopCenter="Πάνω Κέντρο" @@ -210,11 +381,18 @@ Basic.Main.AddSourceHelp.Text="Πρέπει να έχετε τουλάχιστο Basic.Main.Scenes="Σκηνές" Basic.Main.Sources="Πηγές" +Basic.Main.Controls="Πλήκτρα ελέγχου" Basic.Main.Connecting="Σύνδεση..." Basic.Main.StartRecording="Έναρξη Καταγραφής" +Basic.Main.StartReplayBuffer="Έναρξη Buffer επανάληψης" Basic.Main.StartStreaming="Έναρξη Μετάδοσης" Basic.Main.StopRecording="Διακοπή Καταγραφής" +Basic.Main.StoppingRecording="Διακοπή Εγγραφής..." +Basic.Main.StopReplayBuffer="Διακοπή Buffer επανάληψης" +Basic.Main.StoppingReplayBuffer="Διακοπή Buffer επανάληψης..." Basic.Main.StopStreaming="Διακοπή Μετάδοσης" +Basic.Main.StoppingStreaming="Διακοπή Stream..." +Basic.Main.ForceStopStreaming="Διακοπή Stream (απόρριψη καθυστέρησης)" Basic.MainMenu.File="Αρχείο(&F)" Basic.MainMenu.File.Export="Εξαγωγή(&E)" @@ -224,6 +402,7 @@ Basic.MainMenu.File.Remux="Μετατροπή Ηχογραφών(&Re)" Basic.MainMenu.File.Settings="Ρυθμίσεις(&S)" Basic.MainMenu.File.ShowSettingsFolder="Προβολή Φακέλου Ρυθμίσεων" Basic.MainMenu.File.ShowProfileFolder="Προβολή Φακέλου Προφίλ" +Basic.MainMenu.AlwaysOnTop="&Πάντα από πάνω" Basic.MainMenu.File.Exit="Έξοδος(&E)" Basic.MainMenu.Edit="Επεξεργασία(&E)" @@ -231,8 +410,15 @@ Basic.MainMenu.Edit.Undo="Αναίρεση(&U)" Basic.MainMenu.Edit.Redo="Ακύρωση Αναίρεσης(&R)" Basic.MainMenu.Edit.UndoAction="Αναίρεση(&U) $1" Basic.MainMenu.Edit.RedoAction="Ακύρωση Αναίρεσης(&R) $1" +Basic.MainMenu.Edit.LockPreview="&Κλείδωμα προεπισκόπησης" +Basic.MainMenu.Edit.Scale="Προεπισκόπηση &Κλιμάκωσης" +Basic.MainMenu.Edit.Scale.Window="Κλιμάκωση σε Παράθυρο" +Basic.MainMenu.Edit.Scale.Canvas="Καμβάς (%1x%2)" +Basic.MainMenu.Edit.Scale.Output="Έξοδος (%1x%2)" Basic.MainMenu.Edit.Transform="Μετασχηματισμός(&T)" Basic.MainMenu.Edit.Transform.EditTransform="Επεξεργασία(&E) Μετασχηματισμόυ" +Basic.MainMenu.Edit.Transform.CopyTransform="Αντιγραφή Μετασχηματισμού" +Basic.MainMenu.Edit.Transform.PasteTransform="Επικόλληση Μετασχηματισμού" Basic.MainMenu.Edit.Transform.ResetTransform="Επαναφορά(&E) Μετασχηματισμού" Basic.MainMenu.Edit.Transform.Rotate90CW="Περιστροφή κατά 90 μοίρες CW" Basic.MainMenu.Edit.Transform.Rotate90CCW="Περιστροφή κατά 90 μοίρες CCW" @@ -249,12 +435,29 @@ Basic.MainMenu.Edit.Order.MoveToTop="Μετακίνηση στην Κορυφή( Basic.MainMenu.Edit.Order.MoveToBottom="Μετακίνηση τελέιως Κάτω(&B)" Basic.MainMenu.Edit.AdvAudio="Ιδιότητες(&A) Ήχου για Προχωρημένους" +Basic.MainMenu.View="&Προβολή" +Basic.MainMenu.View.Toolbars="&Γραμμές εργαλείων" +Basic.MainMenu.View.Docks="Μπάρες εφαρμογών" +Basic.MainMenu.View.Docks.ResetUI="Επαναφορά περιβάλλοντος χρήστη" +Basic.MainMenu.View.Docks.LockUI="Κλείδωμα περιβάλλοντος χρήστη" +Basic.MainMenu.View.Toolbars.Listboxes="&Κουτιά Λίστας" +Basic.MainMenu.View.SceneTransitions="&Μεταβάσεις Σκηνών" +Basic.MainMenu.View.StatusBar="&Γραμμή κατάστασης" +Basic.MainMenu.View.Fullscreen.Interface="Διεπαφή Πλήρους Οθόνης" Basic.MainMenu.SceneCollection="&Συλλογή Σκηνών" Basic.MainMenu.Profile="&Προφίλ" +Basic.MainMenu.Profile.Import="Εισαγωγή Προφίλ" +Basic.MainMenu.Profile.Export="Εξαγωγή Προφίλ" +Basic.MainMenu.SceneCollection.Import="Εισαγωγή Συλλογής Σκηνών" +Basic.MainMenu.SceneCollection.Export="Εξαγωγή Συλλογής Σκηνών" +Basic.MainMenu.Profile.Exists="Το προφίλ υπάρχει ήδη" +Basic.MainMenu.SceneCollection.Exists="Η συλλογή σκήνων υπάρχει ήδη" +Basic.MainMenu.Tools="&Εργαλεία" Basic.MainMenu.Help="Βοήθεια(&H)" +Basic.MainMenu.Help.Website="Μετάβαση στην &Ιστοσελίδα" Basic.MainMenu.Help.Logs="Αρχεία(&) Καταγραφής" Basic.MainMenu.Help.Logs.ShowLogs="Εμφάνιση(&S) Αρχείων Καταγραφής" Basic.MainMenu.Help.Logs.UploadCurrentLog="Μεταφορτώστε το Τρέχον(&C) Αρχείο Καταγραφής" @@ -269,6 +472,10 @@ Basic.Settings.Confirm="Έχετε μη αποθηκευμένες αλλαγέ Basic.Settings.General="Γενικά" Basic.Settings.General.Theme="Θέμα" Basic.Settings.General.Language="Γλώσσα" +Basic.Settings.General.EnableAutoUpdates="Αυτόματος έλεγχος ενημερώσεων κατά την εκκίνηση" +Basic.Settings.General.OpenStatsOnStartup="Άνοιγμα παραθύρου στατιστικών κατά την εκκίνηση" +Basic.Settings.General.WarnBeforeStartingStream="Εμφάνιση παραθύρου επιβεβαίωσης κατά την εκκίνηση των streams" +Basic.Settings.General.WarnBeforeStoppingStream="Εμφάνιση παραθύρου επιβεβαίωσης κατά τη διακοπή των streams" Basic.Settings.Stream="Μετάδοση" Basic.Settings.Stream.StreamType="Τύπος Μετάδοσης" @@ -282,8 +489,20 @@ Basic.Settings.Output.Mode="Λειτουργία Εξόδου" Basic.Settings.Output.Mode.Simple="Απλό" Basic.Settings.Output.Mode.Adv="Σύνθετες επιλογές" Basic.Settings.Output.Mode.FFmpeg="Έξοδος FFmpeg" +Basic.Settings.Output.ReplayBuffer.SecondsMax="Μέγιστος Χρόνος Επανάληψης (Δευτερόλεπτα)" +Basic.Settings.Output.ReplayBuffer.MegabytesMax="Μέγιστη Μνήμη (Megabytes)" +Basic.Settings.Output.ReplayBuffer.Estimate="Εκτιμώμενη χρήση μνήμης: %1 MB" +Basic.Settings.Output.ReplayBuffer.EstimateUnknown="Δεν είναι δυνατή η εκτίμηση της χρήσης μνήμης. Ορίστε μέγιστο όριο μνήμης." +Basic.Settings.Output.ReplayBuffer.Suffix="Επίθεμα" Basic.Settings.Output.Simple.SavePath="Διαδρομή Καταγραφής" +Basic.Settings.Output.Simple.RecordingQuality="Ποιότητα Εγγραφής" +Basic.Settings.Output.Simple.RecordingQuality.Small="Υψηλής Ποιότητας, Μεσαίου Μεγέθους Αρχείο" +Basic.Settings.Output.Simple.RecordingQuality.HQ="Δυσδιάκριτης Ποιότητας, Μεγάλου Μεγέθους Αρχείο" +Basic.Settings.Output.Simple.RecordingQuality.Lossless="Ποιότητας Χωρίς Απώλειες, Εξαιρετικά Μεγάλου Μεγέθους Αρχείο" 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.VideoBitrate="Ρυθμός Bit του Βίντεο" Basic.Settings.Output.AudioBitrate="Ρυθμός Bit του Ήχου" Basic.Settings.Output.Reconnect="Αυτόματη Επανασύνδεση" @@ -292,6 +511,8 @@ Basic.Settings.Output.MaxRetries="Μέγιστος Αριθμός Επαναλή Basic.Settings.Output.Advanced="Ενεργοποίηση Ρυθμίσεις Κωδικοποιήτη Για Προχωρημένους" Basic.Settings.Output.EncoderPreset="Προφίλ Κωδικοποιητή (υψηλότερο = λιγότερη CPU)" Basic.Settings.Output.CustomEncoderSettings="Προσαρμοσμένες Ρυθμίσεις Κωδικοποιητή" +Basic.Settings.Output.CustomMuxerSettings="Προσαρμοσμένες Ρυθμίσεις Πολυπλέκτη" +Basic.Settings.Output.NoSpaceFileName="Δημιουργία Ονόματος Αρχείου χωρίς Κενό" Basic.Settings.Output.Adv.Rescale="Κλιμάκωση Εξόδου" Basic.Settings.Output.Adv.AudioTrack="Κομμάτι ήχου" @@ -307,7 +528,10 @@ Basic.Settings.Output.Adv.Recording.Type="Τύπος" Basic.Settings.Output.Adv.Recording.Type.Standard="Κανονικός" Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput="Προσαρμοσμένη Έξοδος (FFmpeg)" Basic.Settings.Output.Adv.Recording.UseStreamEncoder="(Χρήση κωδικοποιητή ροής)" +Basic.Settings.Output.Adv.Recording.OverwriteIfExists="Αντικατάσταση εάν το αρχείο υπάρχει" +Basic.Settings.Output.Adv.FFmpeg.Type="Τύπος εξόδου FFmpeg" Basic.Settings.Output.Adv.FFmpeg.Type.URL="Έξοδος σε διεύθυνση URL" +Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile="Έξοδος σε αρχείο" Basic.Settings.Output.Adv.FFmpeg.SaveFilter.Common="Κοινή μορφές εγγραφής" Basic.Settings.Output.Adv.FFmpeg.SaveFilter.All="Όλα τα αρχεία" Basic.Settings.Output.Adv.FFmpeg.SavePathURL="Διαδρομή αρχείου ή URL" @@ -323,7 +547,11 @@ Basic.Settings.Output.Adv.FFmpeg.VEncoder="Κωδικοποιητής Βίντε Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings="Ρυθμίσεις Κωδικοποιητή Βίντεο (αν υπάρχουν)" Basic.Settings.Output.Adv.FFmpeg.AEncoder="Κωδικοποιητής Ήχου" Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Ρυθμίσεις Κωδικοποιητή Ήχου (αν υπάρχουν)" +Basic.Settings.Output.Adv.FFmpeg.MuxerSettings="Ρυθμίσεις Πολυπλέκτη (αν υπάρχει)" +Basic.Settings.Output.Adv.FFmpeg.GOPSize="Διάστημα καρέ-κλειδιού (σε καρέ)" +Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat="Προβολή όλων των codecs (ακόμα και αν είναι πιθανών μη συμβατοί)" +FilenameFormatting.completer="%AAXX-%MM-%ΗΗ %ωω-%λλ-%δδ\n%ΧΧ-%ΜΜ-%ΗΗ %ωω-%λλ-%δδ\n%Χ-%μ-%η %Ω-%Λ-%Δ\n%χ-%μ-%η %Ω-%Λ-%Δ\n%α %Χ-%μ-%η %Ω-%Λ-%Δ\n%Α %Χ-%μ-%η %Ω-%Λ-%Δ\n%Χ-%μ-%η %Ω-%Λ-%Δ\n%Χ-%Μ-%η %Ω-%Λ-%Δ\n%Χ-%μ-%η %Ω-%Λ-%Δ-%p\n%Χ-%μ-%μ %Ω-%Λ-%Δ-%ζ\n%Χ-%μ-%η %Ω-%Λ-%Δ-%Ζ" Basic.Settings.Video="Βίντεο" @@ -360,6 +588,13 @@ Basic.Settings.Advanced.Audio.BufferingTime="Χρόνος buffering ήχου" Basic.Settings.Advanced.Video.ColorFormat="Μορφή Χρώματος" Basic.Settings.Advanced.Video.ColorRange.Partial="Μερικό" Basic.Settings.Advanced.Video.ColorRange.Full="Πλήρες" +Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Προεπιλεγμένη" +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.EnableNewSocketLoop="Ενεργοποίηση νέου κώδικα δικτύωσης" +Basic.Settings.Advanced.Network.EnableLowLatencyMode="Λειτουργία χαμηλής καθυστέρησης" Basic.AdvAudio="Ιδιότητες Ήχου για Προχωρημένους" Basic.AdvAudio.Name="Όνομα" @@ -369,11 +604,46 @@ Basic.AdvAudio.Panning="Πανοραμικό" Basic.AdvAudio.SyncOffset="Μετατόπιση Συγχρονισμού (ms)" Basic.AdvAudio.AudioTracks="Κομμάτια" +Basic.Settings.Hotkeys="Πλήκτρα συντόμευσης" Basic.Hotkeys.SelectScene="Μετάβαση σε σκηνή" +Basic.SystemTray.Show="Εμφάνιση" +Basic.SystemTray.Hide="Απόκρυψη" +Basic.SystemTray.Message.Reconnecting="Έγινε αποσύνδεση. Γίνεται επανασύνδεση..." +Hotkeys.Insert="Insert" +Hotkeys.Delete="Delete" +Hotkeys.Home="Home" +Hotkeys.End="End" +Hotkeys.PageUp="Page Up" +Hotkeys.PageDown="Page Down" +Hotkeys.NumLock="Num Lock" +Hotkeys.ScrollLock="Scroll Lock" +Hotkeys.CapsLock="Caps Lock" +Hotkeys.Backspace="Backspace" +Hotkeys.Tab="Tab" +Hotkeys.Print="Print" +Hotkeys.Pause="Pause" +Hotkeys.Left="Left" +Hotkeys.Right="Right" +Hotkeys.Up="Up" +Hotkeys.Down="Down" +Hotkeys.Windows="Windows" +Hotkeys.Super="Super" +Hotkeys.Menu="Menu" +Hotkeys.Space="Space" +Hotkeys.NumpadNum="Numpad %1" +Hotkeys.NumpadMultiply="Numpad Multiply" +Hotkeys.NumpadDivide="Numpad Divide" +Hotkeys.NumpadAdd="Numpad Add" +Hotkeys.NumpadSubtract="Numpad Subtract" +Hotkeys.NumpadDecimal="Numpad Decimal" +Hotkeys.AppleKeypadNum="%1 (Keypad)" +Hotkeys.AppleKeypadMultiply="* (Keypad)" +Hotkeys.AppleKeypadDivide="/ (Keypad)" +Hotkeys.AppleKeypadAdd="+ (Keypad)" Mute="Σίγαση" Unmute="Κατάργηση σίγασης" diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index ab35921..9ed2184 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -33,16 +33,21 @@ Browse="Browse" Mono="Mono" Stereo="Stereo" DroppedFrames="Dropped Frames %1 (%2%)" +StudioProgramProjector="Fullscreen Projector (Program)" PreviewProjector="Fullscreen Projector (Preview)" SceneProjector="Fullscreen Projector (Scene)" SourceProjector="Fullscreen Projector (Source)" +StudioProgramWindow="Windowed Projector (Program)" PreviewWindow="Windowed Projector (Preview)" SceneWindow="Windowed Projector (Scene)" SourceWindow="Windowed Projector (Source)" +MultiviewProjector="Multiview (Fullscreen)" +MultiviewWindowed="Multiview (Windowed)" Clear="Clear" Revert="Revert" Show="Show" Hide="Hide" +UnhideAll="Unhide All" Untitled="Untitled" New="New" Duplicate="Duplicate" @@ -71,6 +76,13 @@ PasteDuplicate="Paste (Duplicate)" RemuxRecordings="Remux Recordings" Next="Next" Back="Back" +Defaults="Defaults" +HideMixer="Hide in Mixer" +TransitionOverride="Transition Override" +None="None" +StudioMode.Preview="Preview" +StudioMode.Program="Program" +ShowInMultiview="Show in Multiview" # warning if program already open AlreadyRunning.Title="OBS is already running" @@ -341,6 +353,11 @@ AddProfile.Text="Please enter the name of the profile" # rename profile dialog RenameProfile.Title="Rename Profile" +# rename audio source in mixer +Basic.Main.MixerRename.Title="Rename Audio Source" +Basic.Main.MixerRename.Text="Please enter the name of the audio source" + + # preview window disabled Basic.Main.PreviewDisabled="Preview is currently disabled" @@ -428,6 +445,7 @@ Basic.Main.AddSourceHelp.Text="You need to have at least 1 scene to add a source # basic mode main window Basic.Main.Scenes="Scenes" Basic.Main.Sources="Sources" +Basic.Main.Controls="Controls" Basic.Main.Connecting="Connecting..." Basic.Main.StartRecording="Start Recording" Basic.Main.StartReplayBuffer="Start Replay Buffer" @@ -486,9 +504,13 @@ Basic.MainMenu.Edit.AdvAudio="&Advanced Audio Properties" # basic mode view menu Basic.MainMenu.View="&View" Basic.MainMenu.View.Toolbars="&Toolbars" +Basic.MainMenu.View.Docks="Docks" +Basic.MainMenu.View.Docks.ResetUI="Reset UI" +Basic.MainMenu.View.Docks.LockUI="Lock UI" Basic.MainMenu.View.Toolbars.Listboxes="&Listboxes" Basic.MainMenu.View.SceneTransitions="S&cene Transitions" Basic.MainMenu.View.StatusBar="&Status Bar" +Basic.MainMenu.View.Fullscreen.Interface="Fullscreen Interface" # basic mode profile/scene collection menus Basic.MainMenu.SceneCollection="&Scene Collection" @@ -505,6 +527,7 @@ Basic.MainMenu.Tools="&Tools" # basic mode help menu Basic.MainMenu.Help="&Help" +Basic.MainMenu.Help.HelpPortal="Help &Portal" Basic.MainMenu.Help.Website="Visit &Website" Basic.MainMenu.Help.Logs="&Log Files" Basic.MainMenu.Help.Logs.ShowLogs="&Show Log Files" @@ -542,6 +565,13 @@ Basic.Settings.General.SysTray="System Tray" Basic.Settings.General.SysTrayWhenStarted="Minimize to system tray when started" Basic.Settings.General.SystemTrayHideMinimize="Always minimize to system tray instead of task bar" Basic.Settings.General.SaveProjectors="Save projectors on exit" +Basic.Settings.General.SwitchOnDoubleClick="Transition to scene when double-clicked" +Basic.Settings.General.StudioPortraitLayout="Enable portrait/vertical layout" +Basic.Settings.General.MultiviewLayout="Multiview Layout" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontal, Top" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontal, Bottom" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertical, Left" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertical, Right" # basic mode 'stream' settings Basic.Settings.Stream="Stream" @@ -670,6 +700,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Sharpened scaling, 32 sam Basic.Settings.Audio="Audio" Basic.Settings.Audio.SampleRate="Sample Rate" Basic.Settings.Audio.Channels="Channels" +Basic.Settings.Audio.MeterDecayRate="Audio Meter Decay Rate" +Basic.Settings.Audio.MeterDecayRate.Fast="Fast" +Basic.Settings.Audio.MeterDecayRate.Medium="Medium (Type I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Slow (Type II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="WARNING: Surround sound audio is enabled." +Basic.Settings.Audio.MultichannelWarning="If streaming, check to see if your streaming service supports both surround sound ingest and surround sound playback. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast are examples where surround sound is fully supported. Although Facebook Live and YouTube Live both accept surround ingest, Facebook Live downmixes to stereo, and YouTube Live plays only two channels.\n\nOBS audio filters are compatible with surround sound, though VST plugin support isn't guaranteed." +Basic.Settings.Audio.MultichannelWarning.Title="Enable surround sound audio?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Are you sure you want to enable surround sound audio?" Basic.Settings.Audio.DesktopDevice="Desktop Audio Device" Basic.Settings.Audio.DesktopDevice2="Desktop Audio Device 2" Basic.Settings.Audio.AuxDevice="Mic/Auxiliary Audio Device" @@ -687,6 +725,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Process Priority" Basic.Settings.Advanced.General.ProcessPriority.High="High" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Above Normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Below Normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Idle" Basic.Settings.Advanced.FormatWarning="Warning: Color formats other than NV12 are primarily intended for recording, and are not recommended when streaming. Streaming may incur increased CPU usage due to color format conversion." Basic.Settings.Advanced.Audio.BufferingTime="Audio Buffering Time" diff --git a/UI/data/locale/es-ES.ini b/UI/data/locale/es-ES.ini index 726c6f0..ac279b0 100644 --- a/UI/data/locale/es-ES.ini +++ b/UI/data/locale/es-ES.ini @@ -6,8 +6,8 @@ OK="Aceptar" Apply="Aplicar" Cancel="Cancelar" Close="Cerrar" -Save="Grabar" -Discard="Descartar" +Save="Guardar" +Discard="Guardar" Disable="Deshabilitar" Yes="Sí" No="No" @@ -28,16 +28,21 @@ Browse="Examinar" Mono="Mono" Stereo="Estéreo" DroppedFrames="Fotogramas Perdidos %1 (%2%)" +StudioProgramProjector="Proyector de pantalla completa (Programa)" PreviewProjector="Proyector de pantalla completa (Previsualización)" SceneProjector="Proyector de pantalla completa (escena)" SourceProjector="Proyector de pantalla completa (fuente)" +StudioProgramWindow="Proyector con ventana (Programa)" PreviewWindow="Proyector con ventana (Pre-visualización)" SceneWindow="Proyector con ventana (Escena)" SourceWindow="Proyector con ventana (Fuente)" +MultiviewProjector="Vista múltiple (pantalla completa)" +MultiviewWindowed="Vista múltiple (Ventana)" Clear="Borrar" Revert="Revertir" Show="Mostrar" Hide="Ocultar" +UnhideAll="Mostrar todos" Untitled="Sin Título" New="Nuevo" Duplicate="Duplicar" @@ -66,6 +71,13 @@ PasteDuplicate="Pegar (duplicado)" RemuxRecordings="Grabaciones Convertidas" Next="Siguiente" Back="Atrás" +Defaults="Predeterminados" +HideMixer="Ocultar en el mezclador" +TransitionOverride="Anulación de la transición" +None="Ninguno" +StudioMode.Preview="Vista previa" +StudioMode.Program="Programa" +ShowInMultiview="Mostrar en vista múltiple" AlreadyRunning.Title="OBS ya se está ejecutando" AlreadyRunning.Text="¡OBS ya se está ejecutando! A no ser que quieras hacer esto, por favor, cierra todas las ventanas de OBS antes de intentar iniciar una nueva. Si tienes configurado OBS para que se minimize a la barra de tareas, prueba a ver si sigue ejecutándose ahí." @@ -106,6 +118,7 @@ Basic.AutoConfig.StreamPage.Service.ShowAll="Mostrar todos..." Basic.AutoConfig.StreamPage.Server="Servidor" Basic.AutoConfig.StreamPage.StreamKey="Clave de retransmisión" Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Enlace)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Estimar bitrate con una prueba de ancho de banda (puede tardar unos minutos)" Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Preferir codificación por hardware" Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="Codificación por hardware elimina la mayoría del uso de la CPU, pero puede requerir mas bitrate para obtener el mismo nivel de calidad." Basic.AutoConfig.StreamPage.StreamWarning.Title="Advertencia de transmisión" @@ -299,6 +312,10 @@ AddProfile.Text="Escriba el nombre del perfil" RenameProfile.Title="Renombrar perfil" +Basic.Main.MixerRename.Title="Cambiar el nombre de la fuente de audio" +Basic.Main.MixerRename.Text="Por favor, introduzca el nombre de la fuente de audio" + + Basic.Main.PreviewDisabled="Vista previa desactivada" Basic.SourceSelect="Crear/seleccionar fuente" @@ -376,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Tienes que tener al menos 1 escena para agregar u Basic.Main.Scenes="Escenas" Basic.Main.Sources="Fuentes" +Basic.Main.Controls="Controles" Basic.Main.Connecting="Conectando..." Basic.Main.StartRecording="Iniciar grabación" Basic.Main.StartReplayBuffer="Iniciar la reproducción del búfer" @@ -431,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="Propiedades de &Audio avanzadas" Basic.MainMenu.View="Vista" Basic.MainMenu.View.Toolbars="&Barra de Herramientas" +Basic.MainMenu.View.Docks="Acoplar" +Basic.MainMenu.View.Docks.ResetUI="Reestablecer Interfaz de Usuario" +Basic.MainMenu.View.Docks.LockUI="Bloquear Interfaz de Usuario" Basic.MainMenu.View.Toolbars.Listboxes="&Cuadro de Lista" Basic.MainMenu.View.SceneTransitions="&Transición de Escenas" Basic.MainMenu.View.StatusBar="&Barra de Estado" +Basic.MainMenu.View.Fullscreen.Interface="Pantalla completa" Basic.MainMenu.SceneCollection="&Colección de Escenas" Basic.MainMenu.Profile="&Perfil" @@ -447,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="La colección de escenas ya existe" Basic.MainMenu.Tools="&Herramientas" Basic.MainMenu.Help="&Ayuda" +Basic.MainMenu.Help.HelpPortal="Ayuda (&P)" Basic.MainMenu.Help.Website="Visitar Sitio &Web" Basic.MainMenu.Help.Logs="&Archivos de registro" Basic.MainMenu.Help.Logs.ShowLogs="Mostrar archivo&s de registro" @@ -482,6 +505,13 @@ Basic.Settings.General.SysTray="Bandeja del sistema" Basic.Settings.General.SysTrayWhenStarted="Minimizar a la bandeja del sistema al iniciar" Basic.Settings.General.SystemTrayHideMinimize="Minimizar siempre en la bandeja del sistema en lugar de la barra de tareas" Basic.Settings.General.SaveProjectors="Guardar los proyectores al salir" +Basic.Settings.General.SwitchOnDoubleClick="Transición a la escena cuando se hace doble clic" +Basic.Settings.General.StudioPortraitLayout="Habilitar la disposición horizontal/vertical" +Basic.Settings.General.MultiviewLayout="Prueba de multivisión" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontal, parte superior" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontal, parte inferior" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertical, izquierda" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertical, derecha" Basic.Settings.Stream="Emision" Basic.Settings.Stream.StreamType="Tipo de Emision" @@ -512,7 +542,7 @@ Basic.Settings.Output.Simple.RecordingQuality.HQ="Tamaño de archivo grande, cal Basic.Settings.Output.Simple.RecordingQuality.Lossless="Tamaño del archivo sin pérdida de calidad, tremendamente grande" Basic.Settings.Output.Simple.Warn.VideoBitrate="ADVERTENCIA: El streaming de vídeo se establecerá a %1, que es el límite superior para el servicio de streaming actual. Si estás seguro que quieres ir por encima de %1, active las opciones avanzadas del codificador y desactive \"Forzar limites de bitrate en el servicio de streaming\"." Basic.Settings.Output.Simple.Warn.AudioBitrate="ADVERTENCIA: El streaming de audio se establecerá a %1, que es el límite superior para el servicio de streaming actual. Si estás seguro que quieres ir por encima de %1, active las opciones avanzadas del codificador y desactive \"Forzar limites de bitrate en el servicio de streaming\"." -Basic.Settings.Output.Simple.Warn.Encoder="ADVERTENCIA: La grabación con un codificador de software con una calidad diferente de la secuencia requerirá el uso de CPU extra si transmitir y registro al mismo tiempo." +Basic.Settings.Output.Simple.Warn.Encoder="ADVERTENCIA: Grabar con un codificador de software de una calidad diferente a la de la transmisión requerirá un uso adicional de la CPU si transmite y graba al mismo tiempo." Basic.Settings.Output.Simple.Warn.Lossless="ADVERTENCIA: ¡La calidad sin perdidas genera tamaños de archivo muy grandes! La calidad sin pérdidas puede utilizar más de 7 gigabytes de espacio en disco por minuto en alta resolución y con alta tasa de fotogramas. La calidad sin pérdidas no se recomienda para grabaciones largas, a menos que tenga una gran cantidad de espacio en disco disponible." Basic.Settings.Output.Simple.Warn.Lossless.Msg="¿Confirma que desea utilizar calidad sin perdidas?" Basic.Settings.Output.Simple.Warn.Lossless.Title="¡Atención de calidad sin pérdidas!" @@ -601,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Scalado fino, 32 muestras Basic.Settings.Audio="Audio" Basic.Settings.Audio.SampleRate="Frecuencia de muestreo" Basic.Settings.Audio.Channels="Canales" +Basic.Settings.Audio.MeterDecayRate="Tasa de decaimiento del medidor de audio" +Basic.Settings.Audio.MeterDecayRate.Fast="Rápida" +Basic.Settings.Audio.MeterDecayRate.Medium="Media (PPM de tipo I)" +Basic.Settings.Audio.MeterDecayRate.Slow="Lenta (PPM de tipo II)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="ADVERTENCIA: el audio de sonido envolvente está habilitado." +Basic.Settings.Audio.MultichannelWarning="Si se está transmitiendo, compruebe si su servicio de transmisión admite la ingesta de sonido envolvente y la reproducción de sonido envolvente. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast son ejemplos en los que el sonido envolvente es totalmente compatible. Aunque Facebook Live y YouTube Live aceptan la ingesta surround, Facebook Live mezcla a estéreo y YouTube Live solo reproduce dos canales.\n\nLos filtros de audio OBS son compatibles con sonido envolvente, aunque no se garantiza el soporte de complementos VST." +Basic.Settings.Audio.MultichannelWarning.Title="¿Habilitar el audio de sonido envolvente?" +Basic.Settings.Audio.MultichannelWarning.Confirm="¿Seguro que quiere habilitar el audio de sonido envolvente?" Basic.Settings.Audio.DesktopDevice="Dispositivo de audio de escritorio" Basic.Settings.Audio.DesktopDevice2="Dispositivo de audio de escritorio 2" Basic.Settings.Audio.AuxDevice="Dispositivo de audio Mic/auxiliar" @@ -617,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Proceso prioritario" Basic.Settings.Advanced.General.ProcessPriority.High="Alta" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Mayor a Normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Por debajo de lo normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Inactiva" Basic.Settings.Advanced.FormatWarning="ADVERTENCIA: Formatos de Color que no sean NV12 están diseñados principalmente para la grabación y no se recomiendan al retransmitir. La retransmisión puede incurrir en mayor uso de la CPU debido a la conversión de formato de color." Basic.Settings.Advanced.Audio.BufferingTime="Tiempo de búfer de audio" diff --git a/UI/data/locale/et-EE.ini b/UI/data/locale/et-EE.ini index 775aaf5..75a3c84 100644 --- a/UI/data/locale/et-EE.ini +++ b/UI/data/locale/et-EE.ini @@ -56,12 +56,23 @@ Deprecated="Aegunud" ReplayBuffer="Taasesituse puhver" Import="Impordi" Export="Ekspordi" +Copy="Kopeeri" +Paste="Kleebi" +PasteReference="Kleebi (Viide)" +Next="Edasi" +Back="Tagasi" + +AlreadyRunning.Title="OBS juba töötab" +Basic.AutoConfig.StreamPage.Service="Teenus" +Basic.AutoConfig.StreamPage.Server="Server" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Link)" - +Basic.Stats.CPUUsage="CPU kasutus" +Basic.Stats.Status.Inactive="Inaktiivne" Updater.Title="Uus värskendus saadaval" Updater.Text="Uus värskendus on saadaval:" @@ -193,6 +204,8 @@ AddProfile.Text="Sisesta uue profiili nimi" RenameProfile.Title="Profiili ümbernimetamine" + + Basic.Main.PreviewDisabled="Eelvaade on hetkel välja lülitatud" Basic.SourceSelect="Loo/vali allikas" diff --git a/UI/data/locale/eu-ES.ini b/UI/data/locale/eu-ES.ini index 8cd8754..4e80c29 100644 --- a/UI/data/locale/eu-ES.ini +++ b/UI/data/locale/eu-ES.ini @@ -28,16 +28,21 @@ Browse="Arakatu" Mono="Monoa" Stereo="Estereoa" DroppedFrames="Galdutako fotogramak %1 (%2%)" +StudioProgramProjector="Pantaila osoko proiektorea (programa)" PreviewProjector="Pantaila osoko proiektorea (aurrebista)" SceneProjector="Pantaila osoko proiektorea (eszena)" SourceProjector="Pantaila osoko proiektorea (iturburua)" +StudioProgramWindow="Leihoko proiektorea (programa)" PreviewWindow="Leihodun proiektorea (aurrebista)" SceneWindow="Leihodun proiektorea (Eszena)" SourceWindow="Leihodun proiektorea (iturburua)" +MultiviewProjector="Ikuspegi anitza (pantaila osoan)" +MultiviewWindowed="Ikuspegi anitza (leihoan)" Clear="Garbitu" Revert="Leheneratu" Show="Erakutsi" Hide="Ezkutatu" +UnhideAll="Erakutsi dena" Untitled="Izengabea" New="Berria" Duplicate="Bikoiztu" @@ -66,6 +71,13 @@ PasteDuplicate="Itsatsi (Bikoiztu)" RemuxRecordings="Birmultiplexatu grabazioak" Next="Hurrengoa" Back="Atzera" +Defaults="Lehenetsiak" +HideMixer="Ezkutatu nahasgailuan" +TransitionOverride="Trantsizio mota" +None="Gabe" +StudioMode.Preview="Aurreikusi" +StudioMode.Program="Programa" +ShowInMultiview="Erakutsi ikuspegi anitzean" AlreadyRunning.Title="OBS dagoeneko martxan dago" AlreadyRunning.Text="OBS dagoeneko martxan dago! Bestelakorik nahi ez baduzu Itxi irekita dagoen saioa beste saio bat ireki baino lehen. Ezarri baduzu OBS agertzea minimizatua sistemaren erretiluan begiratu eta oraindik exekutatzen ari den bertan." @@ -300,6 +312,10 @@ AddProfile.Text="Sartu profilaren izena" RenameProfile.Title="Berrizendatu profila" +Basic.Main.MixerRename.Title="Berrizendatu audio iturburua" +Basic.Main.MixerRename.Text="Sartu audio iturburuaren izena" + + Basic.Main.PreviewDisabled="Aurrebista ezgaituta dago" Basic.SourceSelect="Sortu/Hautatu Iturburua" @@ -344,7 +360,7 @@ Basic.Filters.Title="Iragazkiak '%1'-rako" Basic.Filters.AddFilter.Title="Iragazkiaren Izena" Basic.Filters.AddFilter.Text="Adierazi iragazkiaren izena" -Basic.TransformWindow="Eszenaren ezarpenak" +Basic.TransformWindow="Eszenaren elementuaren eraldaketa" Basic.TransformWindow.Position="Kokapena" Basic.TransformWindow.Rotation="Biraketa" Basic.TransformWindow.Size="Tamaina" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Gutxienez eszena bat eduki behar duzu iturburu ba Basic.Main.Scenes="Eszenak" Basic.Main.Sources="Iturburuak" +Basic.Main.Controls="Kontrolak" Basic.Main.Connecting="Konektatzen..." Basic.Main.StartRecording="Hasi grabazioa" Basic.Main.StartReplayBuffer="Abiatu erreprodukzio bufferra" @@ -430,11 +447,15 @@ Basic.MainMenu.Edit.Order.MoveToTop="Mugitu &goraino" Basic.MainMenu.Edit.Order.MoveToBottom="Mugitu &beheraino" Basic.MainMenu.Edit.AdvAudio="&Audio ezarpen aurreratuak" -Basic.MainMenu.View="Ikusi" +Basic.MainMenu.View="&Ikusi" Basic.MainMenu.View.Toolbars="&Tresna barrak" +Basic.MainMenu.View.Docks="Atrakagarriak" +Basic.MainMenu.View.Docks.ResetUI="Berrabiarazi erabiltzaile-interfazea" +Basic.MainMenu.View.Docks.LockUI="Blokeatu erabiltzaile-interfazea" Basic.MainMenu.View.Toolbars.Listboxes="&Zerrenda-kutxak" Basic.MainMenu.View.SceneTransitions="&Eszenen trantsizioak" Basic.MainMenu.View.StatusBar="Egoera-barra" +Basic.MainMenu.View.Fullscreen.Interface="Pantaila osoa" Basic.MainMenu.SceneCollection="&Eszena-bilduma" Basic.MainMenu.Profile="&Profila" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Eszena bilduma lehendik ere badago" Basic.MainMenu.Tools="&Tresnak" Basic.MainMenu.Help="&Laguntza" +Basic.MainMenu.Help.HelpPortal="Laguntza ataria" Basic.MainMenu.Help.Website="Ikusi &webgunea" Basic.MainMenu.Help.Logs="&Egunkari-fitxategiak" Basic.MainMenu.Help.Logs.ShowLogs="&Erakutsi egunkari-fitxategiak" @@ -467,7 +489,7 @@ Basic.Settings.General.EnableAutoUpdates="Abiaraztean begiratu automatikoki egun Basic.Settings.General.OpenStatsOnStartup="Ireki estatistikak abiatzean" Basic.Settings.General.WarnBeforeStartingStream="Erakutsi baieztapen elkarrizketa transmisioak hasterakoan" Basic.Settings.General.WarnBeforeStoppingStream="Erakutsi baieztapen elkarrizketa transmisioak gelditzean" -Basic.Settings.General.Projectors="Projektoreak" +Basic.Settings.General.Projectors="Proiektoreak" Basic.Settings.General.HideProjectorCursor="Ezkutatu kurtsorea proiekzioetan" Basic.Settings.General.ProjectorAlwaysOnTop="Proiektoreak beti gainean" Basic.Settings.General.Snapping="Iturburuaren lerrokatzearen doitzea" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Sistemaren erretilua" Basic.Settings.General.SysTrayWhenStarted="Minimizatu sistemaren erretilura hastean" Basic.Settings.General.SystemTrayHideMinimize="Minimizatu beti sistemaren erretilura ataza barrara egin ordez" Basic.Settings.General.SaveProjectors="Gorde proiekzioak irtetean" +Basic.Settings.General.SwitchOnDoubleClick="Aldatu eszena klik bikoitza egitean" +Basic.Settings.General.StudioPortraitLayout="Gaitu diseinu horizontala/bertikala" +Basic.Settings.General.MultiviewLayout="Ikuspegi anitzeko diseinua" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontala, goian" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontala, behean" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Bertikala, ezkerrean" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Bertikala, eskuinean" Basic.Settings.Stream="Transmisioa" Basic.Settings.Stream.StreamType="Transmisio-mota" @@ -496,7 +525,7 @@ Basic.Settings.Output.EnforceBitrate="Behartu transmisio zerbitzuaren bit-tasare Basic.Settings.Output.Mode="Irteera-modua" Basic.Settings.Output.Mode.Simple="Arrunta" Basic.Settings.Output.Mode.Adv="Aurreratua" -Basic.Settings.Output.Mode.FFmpeg="FFmpeg Irteera" +Basic.Settings.Output.Mode.FFmpeg="FFmpeg irteera" Basic.Settings.Output.UseReplayBuffer="Gaitu erreprodukzio bufferra" Basic.Settings.Output.ReplayBuffer.SecondsMax="Erreprodukzioaren gehienezko denbora (segundotan)" Basic.Settings.Output.ReplayBuffer.MegabytesMax="Gehienezko memoria (megabytetan)" @@ -545,7 +574,7 @@ Basic.Settings.Output.Adv.Audio.Track4="4 pista" Basic.Settings.Output.Adv.Audio.Track5="5. pista" Basic.Settings.Output.Adv.Audio.Track6="6. pista" -Basic.Settings.Output.Adv.Recording="Grabatzen" +Basic.Settings.Output.Adv.Recording="Grabazioa" Basic.Settings.Output.Adv.Recording.Type="Mota" Basic.Settings.Output.Adv.Recording.Type.Standard="Estandarra" Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput="Irteera pertsonalizatua (FFmpeg)" @@ -597,11 +626,19 @@ Basic.Settings.Video.DisableAero="Ezgaitu Aero" Basic.Settings.Video.DownscaleFilter.Bilinear="Bilineala (Azkarrena, baina lausoa eskalatuz gero)" Basic.Settings.Video.DownscaleFilter.Bicubic="Bikubikoa (enfokatutako eskalatzea, 16 lagin)" -Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (enfokatutako eskalatzea, 32 lagin)" +Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (eskalatze zorrotza, 32 lagin)" Basic.Settings.Audio="Audioa" Basic.Settings.Audio.SampleRate="Lagin-maiztasuna" Basic.Settings.Audio.Channels="Bideak" +Basic.Settings.Audio.MeterDecayRate="Audio neurtzailearen gutxiagotze-tasa" +Basic.Settings.Audio.MeterDecayRate.Fast="Azkarra" +Basic.Settings.Audio.MeterDecayRate.Medium="Tartekoa (I motako PPMa)" +Basic.Settings.Audio.MeterDecayRate.Slow="Geldoa (II motako PPMa)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="Kontu: soinu inguratzailea aktibatuta dago." +Basic.Settings.Audio.MultichannelWarning="Transmititzen ari bazara, begiratu ea zure transmisio zerbitzuak onartzen duen soinu inguratzailea sarrerako soinuan zein irteerakoan. Twitch, Facebook 360 LIve, Mixer RTMP, Samashcast esate baterako guztiz onartzen dute soinu inguratzailea. Facebook Live eta Youtube Live sarrerako soinu inguratzailea onartzen badute ere, Facebook Livek estereo bihurtzen du, eta Youtube Livek bakarrik bi kanal erreproduzitzen ditu.\n\nOBS audio iragazkiak soinu inguratzailearekin bateragarriak badira ere, ezin da bermatu VST pluginaren bateragarritasuna." +Basic.Settings.Audio.MultichannelWarning.Title="Nahi duzu soinu inguratzailea aktibatzea?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Seguru zaude soinu inguratzailea aktibatu nahi duzula?" Basic.Settings.Audio.DesktopDevice="Mahaigaineko audio gailua" Basic.Settings.Audio.DesktopDevice2="Mahaigaineko audio gailua 2" Basic.Settings.Audio.AuxDevice="Mik/Osagarri audio gailua" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Prozesuaren lehentasuna" Basic.Settings.Advanced.General.ProcessPriority.High="Altua" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Normala baino handiagoa" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normala" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Normalaren azpitik" Basic.Settings.Advanced.General.ProcessPriority.Idle="Inaktiboa" Basic.Settings.Advanced.FormatWarning="Oharra: NV12 ez diren kolore formatuak grabaziorako dira gehienbat, eta ez daude gomendatuta transmisiorako. Transmisioak PUZ erabilpena handitu dezake kolore formatu bihurketa dela medio." Basic.Settings.Advanced.Audio.BufferingTime="Audio bufferratze denbora" @@ -629,7 +667,7 @@ Basic.Settings.Advanced.Video.ColorRange.Full="Osoa" Basic.Settings.Advanced.Audio.MonitoringDevice="Audioa kontrolatzeko gailua" Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Lehenetsia" Basic.Settings.Advanced.Audio.DisableAudioDucking="Ezgaitu Windows audio ducking" -Basic.Settings.Advanced.StreamDelay="Taansmisio-atzerapena" +Basic.Settings.Advanced.StreamDelay="Transmisio-atzerapena" Basic.Settings.Advanced.StreamDelay.Duration="Iraupena (segundoak)" Basic.Settings.Advanced.StreamDelay.Preserve="Mantendu ebaketa puntua (handitu atzerapena) birkonektatzean" Basic.Settings.Advanced.StreamDelay.MemoryUsage="Estimatutako memoria erabilpena: %1 MB" diff --git a/UI/data/locale/fi-FI.ini b/UI/data/locale/fi-FI.ini index 1806ab8..d9b37b4 100644 --- a/UI/data/locale/fi-FI.ini +++ b/UI/data/locale/fi-FI.ini @@ -28,16 +28,21 @@ Browse="Selaa" Mono="Mono" Stereo="Stereo" DroppedFrames="Pudotettuja frameja %1 (%2%)" +StudioProgramProjector="Peilaa monitoriin (Lähetys)" PreviewProjector="Peilaa monitoriin (Esikatselu)" SceneProjector="Peilaa monitoriin (Skene)" SourceProjector="Peilaa monitoriin (Lähde)" +StudioProgramWindow="Peilaa ikkunaan (Lähetys)" PreviewWindow="Peilaa ikkunaan (Esikatselu)" SceneWindow="Peilaa ikkunaan (Skene)" SourceWindow="Peilaa ikkunaan (Lähde)" +MultiviewProjector="Moninäkymä (Koko näyttö)" +MultiviewWindowed="Moninäkymä (Ikkuna)" Clear="Tyhjennä" Revert="Palauta" Show="Näytä" Hide="Piilota" +UnhideAll="Näytä kaikki" Untitled="Nimetön" New="Uusi" Duplicate="Kahdenna" @@ -51,7 +56,7 @@ Left="Vasen" Right="Oikea" Top="Ylhäältä" Bottom="Alhaalta" -Reset="Palauta" +Reset="Nollaa" Hours="Tuntia" Minutes="Minuuttia" Seconds="Sekuntia" @@ -66,6 +71,13 @@ PasteDuplicate="Liitä (Kopio)" RemuxRecordings="Muunna tallenteet" Next="Seuraava" Back="Edellinen" +Defaults="Oletukset" +HideMixer="Piilota mikseristä" +TransitionOverride="Siirtymän ohitus" +None="Ei mitään" +StudioMode.Preview="Esikatselu" +StudioMode.Program="Ohjelma" +ShowInMultiview="Näytä moninäkymässä" AlreadyRunning.Title="OBS on jo käynnissä" AlreadyRunning.Text="OBS on jo käynnissä! Ellet tarkoittanut tehdä näin, ole hyvä ja sulje aikaisemmat OBS-prosessit ennen uuden käynnistämistä. Jos olet asettanut OBS:n pienentymään ilmaisinalueelle, varmista ettei se ole siellä yhä päällä." @@ -300,6 +312,10 @@ AddProfile.Text="Syötä profiilin nimi" RenameProfile.Title="Uudelleennimeä profiili" +Basic.Main.MixerRename.Title="Uudelleennimeä äänilähde" +Basic.Main.MixerRename.Text="Syötä äänilaitteen nimi" + + Basic.Main.PreviewDisabled="Esikatselu on poistettu käytöstä" Basic.SourceSelect="Luo/Valitse lähde" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Tarvitset vähintään yhden skenen lisätäksesi Basic.Main.Scenes="Skenet" Basic.Main.Sources="Lähteet" +Basic.Main.Controls="Ohjaimet" Basic.Main.Connecting="Yhdistetään..." Basic.Main.StartRecording="Aloita tallennus" Basic.Main.StartReplayBuffer="Käynnistä toistopuskuri" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="&Äänen lisäominaisuudet" Basic.MainMenu.View="&Näkymät" Basic.MainMenu.View.Toolbars="&Työkalurivit" +Basic.MainMenu.View.Docks="Telakat" +Basic.MainMenu.View.Docks.ResetUI="Palauta käyttöliittymä" +Basic.MainMenu.View.Docks.LockUI="Lukitse käyttöliittymä" Basic.MainMenu.View.Toolbars.Listboxes="&Luetteluruudut" Basic.MainMenu.View.SceneTransitions="&Skene-siirtymät" Basic.MainMenu.View.StatusBar="&Tilapalkki" +Basic.MainMenu.View.Fullscreen.Interface="Kokoruudun käyttöliittymä" Basic.MainMenu.SceneCollection="&Skene-kokoelma" Basic.MainMenu.Profile="&Profiili" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Skene-kokoelma on jo olemassa" Basic.MainMenu.Tools="T&yökalut" Basic.MainMenu.Help="&Apua" +Basic.MainMenu.Help.HelpPortal="&Apukeskus" Basic.MainMenu.Help.Website="Käy &verkkosivulla" Basic.MainMenu.Help.Logs="&Lokitiedostot" Basic.MainMenu.Help.Logs.ShowLogs="&Näytä lokitiedostot" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Ilmaisinalue" Basic.Settings.General.SysTrayWhenStarted="Pienennä ilmaisinalueelle käynnistyessä" Basic.Settings.General.SystemTrayHideMinimize="Pienennä aina tilapalkkiin tehtäväpalkin sijaan" Basic.Settings.General.SaveProjectors="Tallenna peilaus poistuessa" +Basic.Settings.General.SwitchOnDoubleClick="Siirtymä skeneen tuplaklikattaessa" +Basic.Settings.General.StudioPortraitLayout="Ota pystyasettelu käyttöön" +Basic.Settings.General.MultiviewLayout="Moninäkymän asettelu" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Vaaka, ylhäällä" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Vaaka, alhaalla" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Pysty, vasemmalla" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Pysty, oikealla" Basic.Settings.Stream="Lähetys" Basic.Settings.Stream.StreamType="Lähetystyyppi" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Terävöity skaalaus, 32 Basic.Settings.Audio="Ääni" Basic.Settings.Audio.SampleRate="Näytteenottotaajuus" Basic.Settings.Audio.Channels="Kanavat" +Basic.Settings.Audio.MeterDecayRate="Äänimittarin putoamisnopeus" +Basic.Settings.Audio.MeterDecayRate.Fast="Nopea" +Basic.Settings.Audio.MeterDecayRate.Medium="Keskinopea (Tyyppi I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Hidas (Tyyppi II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="VAROITUS: Monikanavaääni on käytössä." +Basic.Settings.Audio.MultichannelWarning="Varmista lähettäessä että palvelu tukee sekä monikanavaäänen lähettämistä, että toistamista. Twitch, Facebook 360 Live, Mixer RTMP ja Smashcast ovat esimerkkejä palveluista joissa monikanavaääni on täysin tuettu. Vaikka Facebook Live ja YouTube Live hyväksyvät monikanavaäänen lähettämisen, Facebook Live miksaa äänen stereoksi ja YouTube Live toistaa vain kaksi kanavaa.\n\nOBS:n äänisuodattimet tukevat monikanavaääntä, mutta VST-liitännäiset eivät välttämättä tue." +Basic.Settings.Audio.MultichannelWarning.Title="Käytä monikanava-ääntä?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Haluatko varmasti käyttää monikanavaista ääntä?" Basic.Settings.Audio.DesktopDevice="Äänentoistolaite" Basic.Settings.Audio.DesktopDevice2="Äänentoistolaite 2" Basic.Settings.Audio.AuxDevice="Mic/Aux -äänilaite" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Prosessin prioriteetti" Basic.Settings.Advanced.General.ProcessPriority.High="Suuri" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Suurempi kuin normaali" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normaali" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Alle normaalin" Basic.Settings.Advanced.General.ProcessPriority.Idle="Pieni" Basic.Settings.Advanced.FormatWarning="Varoitus: Muut kuin NV12 väriformaatit ovat tarkoitettuja tallentamiseen, eikä niitä suositella lähettämiseen. Väriformaation konversio saattaa johtaa korkeampaan suorittimen käyttöön lähettämisessä." Basic.Settings.Advanced.Audio.BufferingTime="Äänen puskurointiaika" diff --git a/UI/data/locale/fr-FR.ini b/UI/data/locale/fr-FR.ini index 9ade94f..17e52e0 100644 --- a/UI/data/locale/fr-FR.ini +++ b/UI/data/locale/fr-FR.ini @@ -28,17 +28,22 @@ Browse="Parcourir" Mono="Mono" Stereo="Stéréo" DroppedFrames="Images perdues : %1 (%2%)" +StudioProgramProjector="Projecteur plein écran (programme)" PreviewProjector="Projecteur plein écran (aperçu)" SceneProjector="Projecteur plein écran (scène)" SourceProjector="Projecteur plein écran (source)" +StudioProgramWindow="Projecteur fenêtré (programme)" PreviewWindow="Projecteur fenêtré (aperçu)" SceneWindow="Projecteur fenêtré (scène)" SourceWindow="Projecteur fenêtré (source)" +MultiviewProjector="Multivues (Plein écran)" +MultiviewWindowed="Multivues (Fenêtré)" Clear="Effacer" Revert="Annuler" Show="Afficher" Hide="Masquer" -Untitled="Nouveau" +UnhideAll="Tout afficher" +Untitled="Sans nom" New="Nouveau" Duplicate="Dupliquer" Enable="Activer" @@ -56,7 +61,7 @@ Hours="Heures" Minutes="Minutes" Seconds="Secondes" Deprecated="Obsolète" -ReplayBuffer="Tampon de relecture" +ReplayBuffer="Tampon de relecture (Replay Buffer)" Import="Importer" Export="Exporter" Copy="Copier" @@ -66,9 +71,16 @@ PasteDuplicate="Coller (Dupliquer)" RemuxRecordings="Convertir les enregistrements" Next="Suivant" Back="Retour" +Defaults="Valeurs par défaut" +HideMixer="Cacher dans la table de mixage" +TransitionOverride="Forcer une transition" +None="Aucune" +StudioMode.Preview="Aperçu" +StudioMode.Program="Programme" +ShowInMultiview="Montrer en Multivues" AlreadyRunning.Title="OBS est déjà en cours d'exécution" -AlreadyRunning.Text="OBS est déjà en cours d'exécution, s'il vous plait, fermer toute autre instances existantes d'OBS avant d'en exécuter une nouvelle. Vérifier dans votre barre d'état s'il n'est pas réduit et en cours d’exécution." +AlreadyRunning.Text="OBS est déjà en cours d'exécution, merci de bien fermer toute autre instances existantes d'OBS avant d'en exécuter une nouvelle. Vérifiez dans votre barre d'état s'il n'est pas réduit et en cours d’exécution." AlreadyRunning.LaunchAnyway="Démarrer tout de même" Copy.Filters="Copier les filtres" @@ -106,18 +118,18 @@ Basic.AutoConfig.StreamPage.Service.ShowAll="Afficher tout..." Basic.AutoConfig.StreamPage.Server="Serveur" Basic.AutoConfig.StreamPage.StreamKey="Clé de stream" Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Lien)" -Basic.AutoConfig.StreamPage.PerformBandwidthTest="Test de la bande passantes pour estimer le débit(peut prendre quelques minutes)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Test de la bande passantes pour estimer le débit (peut prendre quelques minutes)" Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Préférez l’encodage matériel" Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="L'encodage matériel minimise l'utilisation du processeur (CPU), mais peut nécessiter un débit vidéo plus élevé pour obtenir le même niveau de qualité." Basic.AutoConfig.StreamPage.StreamWarning.Title="Avertissement de diffusion" Basic.AutoConfig.StreamPage.StreamWarning.Text="Le test de bande passante est sur le point de diffuser (stream) des données vidéo aléatoire sans audio sur votre chaîne. Si vous avez la possibilité, il est recommandé de désactiver temporairement l'enregistrement des diffusions et de configurer le stream en privé jusqu'à ce que le test soit terminé. Continuer ?" Basic.AutoConfig.TestPage="Résultat final" -Basic.AutoConfig.TestPage.SubTitle.Testing="Le programme s’exécute maintenant une série de tests pour estimer les paramètres idéales" +Basic.AutoConfig.TestPage.SubTitle.Testing="Le programme s’exécute maintenant une série de tests pour estimer les paramètres idéaux" Basic.AutoConfig.TestPage.SubTitle.Complete="Test complet" Basic.AutoConfig.TestPage.TestingBandwidth="Test de la bande passante en cours, cela peut prendre quelques minutes..." Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Connection à : %1..." -Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Impossible de se connecter à aucun serveur, veuillez vérifier votre connexion internet et réessayez." -Basic.AutoConfig.TestPage.TestingBandwidth.Server="Test de la bande passante pour: %1" +Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Impossible de se connecter aux serveurs, veuillez vérifier votre connexion internet et réessayez." +Basic.AutoConfig.TestPage.TestingBandwidth.Server="Test de la bande passante pour : %1" Basic.AutoConfig.TestPage.TestingStreamEncoder="Test de l'encodeur de diffusion (stream), cela peut prendre une minute..." Basic.AutoConfig.TestPage.TestingRecordingEncoder="Test de l'encodeur d'enregistrement, cela peut prendre une minute..." Basic.AutoConfig.TestPage.TestingRes="Tests de résolutions, cela peut prendre quelques minutes..." @@ -125,14 +137,14 @@ Basic.AutoConfig.TestPage.TestingRes.Fail="Échec lors du démarrage de l'encode Basic.AutoConfig.TestPage.TestingRes.Resolution="Test FPS %1x%2 %3..." Basic.AutoConfig.TestPage.Result.StreamingEncoder="Encodeur de diffusion" Basic.AutoConfig.TestPage.Result.RecordingEncoder="Erreur d'enregistrement" -Basic.AutoConfig.TestPage.Result.Header="Le programme a déterminé que ces estimations de paramètres sont idéal pour vous :" -Basic.AutoConfig.TestPage.Result.Footer="Pour utiliser ces paramètres, cliquez sur « appliquer les paramètres. » Pour reconfigurer l’Assistant et essayer de nouveau, cliquez sur « précédent. » Pour configurer les paramètres vous-même, cliquez sur « Annuler et ouvrir les paramètres. »" +Basic.AutoConfig.TestPage.Result.Header="Le programme a déterminé que ces estimations de paramètres sont idéales pour vous :" +Basic.AutoConfig.TestPage.Result.Footer="Pour utiliser ces paramètres, cliquez sur « appliquer les paramètres ». Pour reconfigurer l’Assistant et essayer de nouveau, cliquez sur « précédent ». Pour configurer les paramètres vous-même, cliquez sur « Annuler et ouvrir les paramètres »." Basic.Stats="Statistiques" Basic.Stats.CPUUsage="Utilisation CPU" Basic.Stats.HDDSpaceAvailable="Espace disque dur disponible" Basic.Stats.MemoryUsage="Utilisation de la mémoire" -Basic.Stats.AverageTimeToRender="Temps moyen pour rendre une image" +Basic.Stats.AverageTimeToRender="Temps moyen de génération d'image" Basic.Stats.SkippedFrames="Sauts d'image dû à la latence d'encodage" Basic.Stats.MissedFrames="Images manqués en raison du retard de rendu" Basic.Stats.Output.Stream="Flux" @@ -142,7 +154,7 @@ Basic.Stats.Status.Recording="Enregistrement en cours" Basic.Stats.Status.Live="DIRECT" Basic.Stats.Status.Reconnecting="Reconnexion" Basic.Stats.Status.Inactive="Inactif" -Basic.Stats.DroppedFrames="Perte d’images (réseau)" +Basic.Stats.DroppedFrames="Images perdues (réseau)" Basic.Stats.MegabytesSent="Sortie Total des Données" Basic.Stats.Bitrate="Débit" @@ -300,6 +312,10 @@ AddProfile.Text="Veuillez entrer le nom du profil" RenameProfile.Title="Renommer le profil" +Basic.Main.MixerRename.Title="Renommer la source audio" +Basic.Main.MixerRename.Text="Veuillez indiquer le nom de la source audio" + + Basic.Main.PreviewDisabled="L'aperçu est actuellement désactivé" Basic.SourceSelect="Créer/Sélectionner la source" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Vous devez avoir au moins une scène pour ajouter Basic.Main.Scenes="Scènes" Basic.Main.Sources="Sources" +Basic.Main.Controls="Commandes" Basic.Main.Connecting="Connexion en cours..." Basic.Main.StartRecording="Démarrer l'enregistrement" Basic.Main.StartReplayBuffer="Démarrer le tampon de relecture" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="Propriétés audio &avancées" Basic.MainMenu.View="&Afficher" Basic.MainMenu.View.Toolbars="&Barres d'outils" +Basic.MainMenu.View.Docks="Docks" +Basic.MainMenu.View.Docks.ResetUI="Réinitialiser" +Basic.MainMenu.View.Docks.LockUI="Verrouiller" Basic.MainMenu.View.Toolbars.Listboxes="&Listes" Basic.MainMenu.View.SceneTransitions="&Transition de scènes" Basic.MainMenu.View.StatusBar="&Barre d'état" +Basic.MainMenu.View.Fullscreen.Interface="Interface plein écran" Basic.MainMenu.SceneCollection="Collection de &scènes" Basic.MainMenu.Profile="&Profil" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Cette collection de scène existe déjà" Basic.MainMenu.Tools="Outils" Basic.MainMenu.Help="&Aide" +Basic.MainMenu.Help.HelpPortal="&Portail d'aide" Basic.MainMenu.Help.Website="Consulter le site &Web" Basic.MainMenu.Help.Logs="&Fichiers journaux" Basic.MainMenu.Help.Logs.ShowLogs="Afficher les &fichiers de log" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Zone de notifications" Basic.Settings.General.SysTrayWhenStarted="Réduire dans la zone de notification dès le démarrage" Basic.Settings.General.SystemTrayHideMinimize="Toujours réduire dans la zone de notification au lieu de la barre des tâches" Basic.Settings.General.SaveProjectors="Enregistrer les projecteurs en quittant" +Basic.Settings.General.SwitchOnDoubleClick="Effectuer la transition vers la scène en cas de double clic" +Basic.Settings.General.StudioPortraitLayout="Activer la mise en page portrait/verticale" +Basic.Settings.General.MultiviewLayout="Disposition de la multivue" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontal, haut" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontal, bas" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertical, gauche" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertical, droit" Basic.Settings.Stream="Flux" Basic.Settings.Stream.StreamType="Type de diffusion" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (redimensionnement affiné Basic.Settings.Audio="Audio" Basic.Settings.Audio.SampleRate="Fréquence d'échantillonnage" Basic.Settings.Audio.Channels="Canaux" +Basic.Settings.Audio.MeterDecayRate="Vitesse de dégradation audiométrique" +Basic.Settings.Audio.MeterDecayRate.Fast="Rapide" +Basic.Settings.Audio.MeterDecayRate.Medium="Moyenne (Type I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Lente (Type II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="ATTENTION : le son multicanal est activé." +Basic.Settings.Audio.MultichannelWarning="Pour de besoins de diffusion, vérifiez que votre service de diffusion supporte l'intégration et la lecture du son multicanal. Twitch, Facebook 360 Live, Mixer RTMP ou Smashcast sont des exemples de services où le son multicanal est entièrement supporté. Bien que Facebook Live et YouTube Live acceptent l'intégration de son multicanal, Facebook Live transcode en stéréo, et YouTube Live ne lit que deux canaux.\n\nLes filtres audio d'OBS sont compatibles avec le son multicanal, toutefois le support du plugin VST n'est pas garanti." +Basic.Settings.Audio.MultichannelWarning.Title="Activer le son multicanal ?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Êtes vous sûr de vouloir activer le son multicanal ?" Basic.Settings.Audio.DesktopDevice="Périphérique audio du bureau" Basic.Settings.Audio.DesktopDevice2="Périphérique audio du bureau 2" Basic.Settings.Audio.AuxDevice="Périphérique audio micro/auxiliaire" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Priorité du processus" Basic.Settings.Advanced.General.ProcessPriority.High="Haute" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Supérieure à la normale" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normale" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Au-dessous de la normale" Basic.Settings.Advanced.General.ProcessPriority.Idle="Basse" Basic.Settings.Advanced.FormatWarning="Attention : les formats de couleur autres que NV12 sont principalement destinés à l'enregistrement, et ne sont pas recommandés pour le streaming. Le streaming peut pâtir d'une utilisation élevée du CPU due à la conversion de format de couleur." Basic.Settings.Advanced.Audio.BufferingTime="Temps de mise en mémoire tampon audio" diff --git a/UI/data/locale/gl-ES.ini b/UI/data/locale/gl-ES.ini index 6f3bc17..4411f70 100644 --- a/UI/data/locale/gl-ES.ini +++ b/UI/data/locale/gl-ES.ini @@ -150,6 +150,8 @@ AddProfile.Text="Por favor, insire o nome do perfil" RenameProfile.Title="Renomear perfil" + + Basic.Main.PreviewDisabled="A vista previa está actualmente deshabilitada" Basic.SourceSelect="Crear/seleccionar fonte" diff --git a/UI/data/locale/he-IL.ini b/UI/data/locale/he-IL.ini index e9e7e02..051cb50 100644 --- a/UI/data/locale/he-IL.ini +++ b/UI/data/locale/he-IL.ini @@ -295,6 +295,8 @@ AddProfile.Text="אנא הזן את שם הפרופיל" RenameProfile.Title="שנה שם פרופיל" + + Basic.Main.PreviewDisabled="תצוגה מקדימה אינה זמינה כעת" Basic.SourceSelect="בחר/צור מקור" diff --git a/UI/data/locale/hr-HR.ini b/UI/data/locale/hr-HR.ini index 9144202..c7ca15a 100644 --- a/UI/data/locale/hr-HR.ini +++ b/UI/data/locale/hr-HR.ini @@ -2,11 +2,11 @@ Language="hrvatski" Region="Croatia" -OK="OK" -Apply="Primeni" -Cancel="Otkaži" +OK="U redu" +Apply="Primijeni" +Cancel="Odustani" Close="Zatvori" -Save="Sačuvaj" +Save="Spremi" Discard="Odbaci" Disable="Onemogući" Yes="Da" @@ -17,10 +17,10 @@ Rename="Preimenuj" Interact="Interaktivno" Filters="Filteri" Properties="Svojstva" -MoveUp="Pomeri gore" -MoveDown="Pomeri dole" -Settings="Podešavanja" -Display="Ekran" +MoveUp="Pomakni gore" +MoveDown="Pomakni dolje" +Settings="Postavke" +Display="Zaslon" Name="Ime" Exit="Izlaz" Mixer="Mikseta" @@ -28,23 +28,27 @@ Browse="Pretraži" Mono="Mono" Stereo="Stereo" DroppedFrames="Ispušteni frejmovi %1 (%2%)" -PreviewProjector="Projektor celog ekrana (pregled)" -SceneProjector="Projektor celog ekrana (scena)" -SourceProjector="Projektor celog ekrana (izvor)" +PreviewProjector="Projektor cijelog zaslona (pregled)" +SceneProjector="Projektor cijelog zaslona (scena)" +SourceProjector="Projektor cijelog zaslona (izvor)" +PreviewWindow="Projektor zaslona (pretpregled)" +SceneWindow="Projektor zaslona (Scena)" +SourceWindow="Projektor zaslona (Izvor)" Clear="Poništi" Revert="Povrati" Show="Prikaži" Hide="Sakrij" +UnhideAll="Sakrij sve" Untitled="Nenaslovljeno" New="Novi" -Duplicate="Dupliraj" +Duplicate="Udvostruči" Enable="Omogući" -DisableOSXVSync="Onemogući OSX vertikalnu sinhronizaciju" -ResetOSXVSyncOnExit="Povrati OSX vertikalnu sinhronizaciju po izlasku" -HighResourceUsage="Enkodiranje preopterećeno! Razmotrite snižavanje video podešavanja ili korišćenje bržeg šablona za enkodiranje." -Transition="Prelaz" -QuickTransitions="Brzi prelazi" -Left="Sleva" +DisableOSXVSync="Onemogući OSX vertikalnu sinkronizaciju" +ResetOSXVSyncOnExit="Povrati OSX vertikalnu sinkronizaciju po izlasku" +HighResourceUsage="Kodiranje preopterećeno! Pokušajte sniziti postavke videa ili upotrijebiti brži predložak za kodiranje." +Transition="Prijelaz" +QuickTransitions="Brzi prijelazi" +Left="Slijeva" Right="Zdesna" Top="Odozgo" Bottom="Odozdo" @@ -52,106 +56,142 @@ Reset="Poništi" Hours="Sati" Minutes="Minuta" Seconds="Sekundi" -Deprecated="Prevaziđeno" +Deprecated="Zastarjelo" +Import="Uvezi" +Export="Izvezi" +Copy="Kopiraj" +Paste="Zalijepi" +PasteReference="Zaljepiti (" +PasteDuplicate="Zalijepi(Dupliciraj)" +RemuxRecordings="Remux snimka" +Next="Sljedeće" +Back="Prethodno" +Defaults="Zadano" +HideMixer="Sakriti u Mikser" + +AlreadyRunning.Title="OBS je već pokrenut" +AlreadyRunning.Text="OBS je već pokrenut! Osim ako si mislio da biste to učinili, isključite sve postojeće pojave OBS prije nego što pokušate pokrenuti novu instancu. Ako imate OBS postavljen na smanjivanje u traku sustava, provjerite ako to još uvijek radi tamo." +AlreadyRunning.LaunchAnyway="Svejedno pokreni" + +Copy.Filters="Kopiraj filtere" +Paste.Filters="Zalijepi filtere" + +BandwidthTest.Region="Regija" +BandwidthTest.Region.US="Sjedinjene Američke Države" +BandwidthTest.Region.EU="Europa" +BandwidthTest.Region.Asia="Azija" +BandwidthTest.Region.Other="Ostalo" + +Basic.FirstStartup.RunWizard="Želite li pokrenuti čarobnjak za automatsku konfiguraciju? Možete također ručno konfigurirati postavke tako da kliknete gumb Postavke u glavnom prozoru." +Basic.FirstStartup.RunWizard.BetaWarning="(Napomena: Automatsko postavljanje je trenutno u beti)" +Basic.FirstStartup.RunWizard.NoClicked="Ako se predomislite, možete pokrenuti čarobnjaka za automatsku konfiguraciju bilo kada opet u Alat meni." + +Basic.AutoConfig="Čarobnjak za automatske konfiguraciju" +Basic.AutoConfig.Beta="Čarobnjak za automatsku konfiguraciju (u razvoju)" +Basic.AutoConfig.ApplySettings="Primjeni postavke" +Basic.AutoConfig.StartPage="Informacije o korištenju" +Basic.AutoConfig.StartPage.SubTitle="Navedite što želite da koristite program za" +Basic.AutoConfig.StartPage.PrioritizeStreaming="Optimiziraj za streaming, snimanje je drugo" +Basic.AutoConfig.VideoPage="Video postavke" +Basic.AutoConfig.StreamPage.Service.ShowAll="Prikaži sve..." +Basic.AutoConfig.StreamPage.Server="Server" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Poveznica)" +Basic.AutoConfig.TestPage.SubTitle.Complete="Testiranje završeno" +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Povezivanje na: %1..." +QuickTransitions.SwapScenes="Zamijeni scene pregleda/izlaza nakon prijelaza" +QuickTransitions.SwapScenesTT="Zamjenjuje scene pregleda i izlaza nakon prijelaza (ako originalna scena izlaza još uvijek postoji).\nOvo neće poništiti promjene koje su načinjene nad originalnom scenom izlaza." +QuickTransitions.DuplicateScene="Udvostruči scenu" +QuickTransitions.DuplicateSceneTT="Kada se mijenja ista scena, dozvoli promjenu izmjena/vidljivosti izvora bez promjene izlaza.\nDa biste promijenili svojstva izvora bez promjene izlaza, omogućite 'Udvostruči izvore'.\nPromjena ove vrijednosti poništit će trenutačnu scenu izlaza (ako i dalje postoji)." +QuickTransitions.EditProperties="Udvostruči izvore" +QuickTransitions.EditPropertiesTT="Kad se mijenja ista scena, dozvoli promjenu svojstava izvora bez promjene izlaza.\nOvo može biti upotrijebljeno samo ako je 'Udvostruči scene' omogućeno.\nOdređeni izvori (kao što su video-hvatanja ili medijski izvori) ovo ne podržavaju i ne mogu biti izmijenjeni zasebno.\nPromjena ove vrijednosti poništit će trenutačnu scenu izlaza (ako i dalje postoji).\n\nUpozorenje: Budući da izvori mogu biti udvostručeni, ovo bi moglo zahtijevati dodatne sustavske ili video-resurse." +QuickTransitions.HotkeyName="Brzi prijelaz: %1" - - - - -QuickTransitions.SwapScenes="Zameni scene pregleda/izlaza nakon prelaza" -QuickTransitions.SwapScenesTT="Zamenjuje scene pregleda i izlaza nakon prelaza (ako originalna scena izlaza još uvek postoji).\nOvo neće poništiti promene koje su načinjene nad originalnom scenom izlaza." -QuickTransitions.DuplicateScene="Dupliraj scenu" -QuickTransitions.DuplicateSceneTT="Kada se menja ista scena, dozvoli promenu izmena/vidljivosti izvora bez promene izlaza.\nDa biste promenili svojstva izvora bez promene izlaza, omogućite 'Dupliraj izvore'.\nPromena ove vrednosti će poništiti trenutnu scenu izlaza (ako i dalje postoji)." -QuickTransitions.EditProperties="Dupliraj izvore" -QuickTransitions.EditPropertiesTT="Kada se menja ista scena, dozvoli promenu svojstava izvora bez promene izlaza.\nOvo može biti upotrebljeno samo ako je 'Dupliraj scene' omogućeno.\nOdređeni izvori (kao što su video hvatanja ili medija izvori) ne podržavaju ovo i ne mogu biti izmenjeni zasebno.\nPromena ove vrednosti će poništiti trenutnu scenu izlaza (ako i dalje postoji).\n\nUpozorenje: Zbog toga što izvori mogu biti duplirani, ovo može zahtevati dodatne sistemske ili video resurse." -QuickTransitions.HotkeyName="Brzi prelaz: %1" - -Basic.AddTransition="Dodaj podesivi prelaz" -Basic.RemoveTransition="Ukloni podesivi prelaz" -Basic.TransitionProperties="Svojstva prelaza" -Basic.SceneTransitions="Prelazi scena" +Basic.AddTransition="Dodaj podesivi prijelaz" +Basic.RemoveTransition="Ukloni podesivi prijelaz" +Basic.TransitionProperties="Svojstva prijelaza" +Basic.SceneTransitions="Prijelazi scena" Basic.TransitionDuration="Trajanje" -Basic.TogglePreviewProgramMode="Studijski režim" +Basic.TogglePreviewProgramMode="Studijski način rada" -TransitionNameDlg.Text="Molim unesite ime prelaza" -TransitionNameDlg.Title="Ime prelaza" +TransitionNameDlg.Text="Unesite ime prijelaza" +TransitionNameDlg.Title="Ime prijelaza" TitleBar.Profile="Profil" TitleBar.Scenes="Scene" NameExists.Title="Ime već postoji" -NameExists.Text="Ime je već u upotrebi." +NameExists.Text="Ime se već koristi." -NoNameEntered.Title="Molim unesite ispravno ime" +NoNameEntered.Title="Unesite ispravno ime" NoNameEntered.Text="Ne možete izostaviti ime." -ConfirmStart.Title="Započni strim?" -ConfirmStart.Text="Da li ste sigurni da želite započeti strim?" +ConfirmStart.Title="Započni stream?" +ConfirmStart.Text="Jeste li sigurni da želite započeti stream?" -ConfirmStop.Title="Zaustavi strim?" -ConfirmStop.Text="Da li ste sigurni da želite zaustaviti strim?" +ConfirmStop.Title="Zaustavi stream?" +ConfirmStop.Text="Jeste li sigurni da želite zaustaviti stream?" ConfirmExit.Title="Napustiti OBS?" -ConfirmExit.Text="OBS je trenutno aktivan. Svi strimovi/snimanja će biti ugašeni. Da li ste sigurni da želite izaći?" +ConfirmExit.Text="OBS je trenutačno aktivan. Svi streamovi/snimanja bit će ugašeni. Jeste li sigurni da želite izaći?" -ConfirmRemove.Title="Potvrdi izbacivanje" -ConfirmRemove.Text="Da li ste sigurni da želite izbaciti '$1'?" -ConfirmRemove.TextMultiple="Da li ste sigurni da želite izbaciti %1 stavke?" +ConfirmRemove.Title="Potvrdi uklanjanje" +ConfirmRemove.Text="Jeste li sigurni da želite ukloniti '$1'?" +ConfirmRemove.TextMultiple="Jeste li sigurni da želite ukloniti %1 stavke?" -Output.ConnectFail.Title="Neuspešno povezivanje" -Output.ConnectFail.BadPath="Neispravna putanja ili URL konekcije. Molim proverite vaša podešavanja da potvrdite njihovu ispravnost." -Output.ConnectFail.ConnectFailed="Neuspešno povezivanje na server" -Output.ConnectFail.InvalidStream="Ne mogu pristupiti navedenom kanalu ili strim ključu, molim proverite vaš strim ključ. Ako je ispravan, možda postoji problem pri povezivanju na server." -Output.ConnectFail.Error="Neočekivana greška u povezivanju sa serverom. Više informacija se nalazi u log datoteci." -Output.ConnectFail.Disconnected="Prekinuta veza sa serverom." +Output.ConnectFail.Title="Neuspješno povezivanje" +Output.ConnectFail.BadPath="Neispravna putanja ili URL veze. Provjerite jesu li Vam postavke ispravne." +Output.ConnectFail.ConnectFailed="Neuspješno povezivanje s poslužiteljem" +Output.ConnectFail.InvalidStream="Ne mogu pristupiti navedenom kanalu ili stream-ključu; provjerite potonji. Ako je ispravan, možda postoji problem pri povezivanju s poslužiteljem." +Output.ConnectFail.Error="Neočekivana greška u povezivanju s poslužiteljem. Više informacija nalazi se u zapisniku." +Output.ConnectFail.Disconnected="Prekinuta veza s poslužiteljem." -Output.RecordFail.Title="Neuspešno započinjanje snimanja" -Output.RecordFail.Unsupported="Izlazni format ili nije podržan ili ne podržava više od jedne zvučne trake. Molim proverite podešavanja i pokušajte ponovo." +Output.RecordFail.Title="Neuspješno započinjanje snimanja" +Output.RecordFail.Unsupported="Izlazni format ili nije podržan ili ne podržava više od jedne zvučne trake. Provjerite postavke i pokušajte ponovno." Output.RecordNoSpace.Title="Nedovoljno prostora na disku" Output.RecordNoSpace.Msg="Nema dovoljno prostora na disku da se nastavi snimanje." Output.RecordError.Title="Greška pri snimanju" Output.RecordError.Msg="Neodređena greška se dogodila pri snimanju." Output.BadPath.Title="Neispravna putanja datoteke" -Output.BadPath.Text="Podešavana putanja za dokument je neispravna. Molim proverite vaša podešavanja da potvrdite ispravnost putanje." +Output.BadPath.Text="Postavljena putanja za dokument nije ispravna. Provjerite jeste li ju ispravno naveli u postavkama." -LogReturnDialog="Slanje log dokumenta uspešno" +LogReturnDialog="Slanje zapisnika uspješno" LogReturnDialog.CopyURL="Kopiraj URL" -LogReturnDialog.ErrorUploadingLog="Greška u slanju log dokumenta" +LogReturnDialog.ErrorUploadingLog="Greška u slanju zapisnika" -LicenseAgreement="Uslovi korišćenja" -LicenseAgreement.PleaseReview="Molim proverite uslove korišćenja pre upotrebe OBS-a. Upotrebom ovog programa smatra se da ste pročitali i prihvatili uslove GNU General Public License v2.0 licence. Molimo skrolujte niže da vidite ostatak ugovora." -LicenseAgreement.ClickIAgreeToContinue="Ako prihvatate uslove korišćenja, kliknite na Slažem se da nastavite. Da biste koristili OBS morate prihvatiti ugovor." -LicenseAgreement.IAgree="Slažem se" +LicenseAgreement="Uvjeti korištenja" +LicenseAgreement.PleaseReview="Provjerite uvjete korištenja prije uporabe OBS-a. Upotrebom ovog programa smatra se da ste pročitali i prihvatili uvjete licence GNU General Public License v2.0. Povucite kotačić miša ka dolje da biste vidjeli ostatak ugovora." +LicenseAgreement.ClickIAgreeToContinue="Ako prihvaćate uvjete korištenja, kliknite na \"Prihvaćam\" da nastavite. Morate ih prihvatiti da biste mogli koristili OBS." +LicenseAgreement.IAgree="Prihvaćam" LicenseAgreement.Exit="Izlaz" -Remux.SourceFile="OBS snimak" +Remux.SourceFile="OBS-snimak" Remux.TargetFile="Datoteka" Remux.Remux="Remux" -Remux.OBSRecording="OBS snimak" +Remux.OBSRecording="OBS-snimak" Remux.FinishedTitle="Remux završen" Remux.Finished="Završen remux snimka" -Remux.FinishedError="Remux završen, ali datoteka možda nije kompletirana" -Remux.SelectRecording="Izaberi OBS snimak …" -Remux.SelectTarget="Izaberi datoteku …" -Remux.FileExistsTitle="Datoteka postoji" -Remux.FileExists="Datoteka već postoji, da li želite da je prepišete?" -Remux.ExitUnfinishedTitle="Remux u toku" -Remux.ExitUnfinished="Remux nije završen, ako sada zaustavite proces datoteka može biti neupotrebljiva.\nDa li ste sigurni da želite zaustaviti remux?" +Remux.FinishedError="Remux završen, ali datoteka možda nije dovršena" +Remux.SelectRecording="Odaberite OBS-snimak…" +Remux.SelectTarget="Odaberite ciljnu datoteku…" +Remux.FileExistsTitle="Ciljna datoteka postoji" +Remux.FileExists="Ciljna datoteka već postoji. Želite li ju zamijeniti?" +Remux.ExitUnfinishedTitle="Remux u tijeku" +Remux.ExitUnfinished="Remux nije završen, ako sada zaustavite proces datoteka može biti neupotrebljiva.\nJeste li sigurni da želite zaustaviti remux?" -UpdateAvailable="Dostupna je novija verzija programa" -UpdateAvailable.Text="Verzija %1.%2.%3 je dostupna. Kliknite ovde da preuzmete" +UpdateAvailable="Dostupno je novo ažuriranje" +UpdateAvailable.Text="Verzija %1.%2.%3 je dostupna. Kliknite ovdje da ju preuzmete" -Basic.DesktopDevice1="Zvuk sa računara" -Basic.DesktopDevice2="Zvuk sa računara 2" -Basic.AuxDevice1="Mirkofon/Ulaz" -Basic.AuxDevice2="Mikrofon/Ulaz 2" -Basic.AuxDevice3="Mikrofon/Ulaz 3" -Basic.AuxDevice4="Mikrofon/Ulaz 4" +Basic.DesktopDevice1="Zvuk s računala" +Basic.DesktopDevice2="Zvuk s računala 2" +Basic.AuxDevice1="Mikrofon/ulaz" +Basic.AuxDevice2="Mikrofon/ulaz 2" +Basic.AuxDevice3="Mikrofon/ulaz 3" +Basic.AuxDevice4="Mikrofon/ulaz 4" Basic.Scene="Scena" Basic.DisplayCapture="Prikaži ulaz" @@ -159,12 +199,12 @@ Basic.DisplayCapture="Prikaži ulaz" Basic.Main.PreviewConextMenu.Enable="Omogući pregled" ScaleFiltering="Filter uvećanja" -ScaleFiltering.Point="Tačka" +ScaleFiltering.Point="Točka" ScaleFiltering.Bilinear="Bilinearno" ScaleFiltering.Bicubic="Bikubično" -ScaleFiltering.Lanczos="Lankoz" +ScaleFiltering.Lanczos="Lanczos" -Deinterlacing="Deinterlejsing" +Deinterlacing="Preplitanje" Deinterlacing.Discard="Odbaci" Deinterlacing.Retro="Retro" Deinterlacing.Blend="Stapanje" @@ -178,51 +218,53 @@ Deinterlacing.BottomFieldFirst="Prvo donje polje" Basic.Main.AddSceneDlg.Title="Dodaj scenu" -Basic.Main.AddSceneDlg.Text="Molim unesite ime scene" +Basic.Main.AddSceneDlg.Text="Unesite ime scene" Basic.Main.DefaultSceneName.Text="Scena %1" -Basic.Main.AddSceneCollection.Title="Dodaj kolekciju scena" -Basic.Main.AddSceneCollection.Text="Molim unesite ime kolekcije scena" +Basic.Main.AddSceneCollection.Title="Dodaj zbirku scena" +Basic.Main.AddSceneCollection.Text="Unesite ime zbirke scena" -Basic.Main.RenameSceneCollection.Title="Promeni ime kolekcije scena" +Basic.Main.RenameSceneCollection.Title="Preimenuj zbirku scena" AddProfile.Title="Dodaj profil" -AddProfile.Text="Molim unesite ime profila" +AddProfile.Text="Unesite ime profila" -RenameProfile.Title="Promeni ime profila" +RenameProfile.Title="Preimenuj profil" -Basic.Main.PreviewDisabled="Pregled je trenutno onemogućen" -Basic.SourceSelect="Napravi ili izaberi izvor" + +Basic.Main.PreviewDisabled="Pregled je trenutačno onemogućen" + +Basic.SourceSelect="Napravi/odaberi izvor" Basic.SourceSelect.CreateNew="Napravi novi" Basic.SourceSelect.AddExisting="Dodaj postojeći" Basic.SourceSelect.AddVisible="Prikaži izvor" Basic.PropertiesWindow="Svojstva za '%1'" Basic.PropertiesWindow.AutoSelectFormat="%1 (automatski odabir: %2)" -Basic.PropertiesWindow.SelectColor="Izaberi boju" -Basic.PropertiesWindow.SelectFont="Izaberi font" -Basic.PropertiesWindow.ConfirmTitle="Podešavanja promenjena" -Basic.PropertiesWindow.Confirm="Postoje podešavanja koja nisu sačuvana. Da li želite da ih sačuvate?" +Basic.PropertiesWindow.SelectColor="Odaberi boju" +Basic.PropertiesWindow.SelectFont="Odaberi font" +Basic.PropertiesWindow.ConfirmTitle="Postavke promijenjene" +Basic.PropertiesWindow.Confirm="Imate nespremljenih promjena. Želite li ih spremiti?" Basic.PropertiesWindow.NoProperties="Nema dostupnih svojstava" Basic.PropertiesWindow.AddFiles="Dodaj datoteke" -Basic.PropertiesWindow.AddDir="Dodaj direktorijum" +Basic.PropertiesWindow.AddDir="Dodaj direktorij" Basic.PropertiesWindow.AddURL="Dodaj putanju/URL" -Basic.PropertiesWindow.AddEditableListDir="Dodaj direktorijum u '%1'" +Basic.PropertiesWindow.AddEditableListDir="Dodaj direktorij u '%1'" Basic.PropertiesWindow.AddEditableListFiles="Dodaj datoteke u '%1'" Basic.PropertiesWindow.AddEditableListEntry="Dodaj zapise u '%1'" -Basic.PropertiesWindow.EditEditableListEntry="Izmeni zapise za '%1'" +Basic.PropertiesWindow.EditEditableListEntry="Izmijeni zapise za '%1'" -Basic.PropertiesView.FPS.Simple="Jednostavne FPS vrednosti" -Basic.PropertiesView.FPS.Rational="Racionalne FPS vrednosti" +Basic.PropertiesView.FPS.Simple="Jednostavne FPS-vrijednosti" +Basic.PropertiesView.FPS.Rational="Racionalne FPS-vrijednosti" Basic.PropertiesView.FPS.ValidFPSRanges="Ispravni opsezi FPS-a:" -Basic.InteractionWindow="Interakcija sa '%1'" +Basic.InteractionWindow="Interakcija s '%1'" Basic.StatusBar.Reconnecting="Veza je prekinuta, ponovno povezivanje za %2 sekund(i) (%1 pokušaj)" Basic.StatusBar.AttemptingReconnect="Pokušavam ponovno povezivanje... (%1 pokušaj)" -Basic.StatusBar.ReconnectSuccessful="Ponovno povezivanje uspešno" +Basic.StatusBar.ReconnectSuccessful="Ponovno povezivanje uspješno" Basic.StatusBar.Delay="Odlaganje (%1 sek)" Basic.StatusBar.DelayStartingIn="Odlaganje (počinje za %1 sek)" Basic.StatusBar.DelayStoppingIn="Odlaganje (zaustavljam za %1 sek)" @@ -234,32 +276,32 @@ Basic.Filters.AudioFilters="Zvučni filteri" Basic.Filters.EffectFilters="Filteri efekata" Basic.Filters.Title="Filteri za '%1'" Basic.Filters.AddFilter.Title="Ime filtera" -Basic.Filters.AddFilter.Text="Molim naznačite ime filtera" +Basic.Filters.AddFilter.Text="Naznačite ime filtera" Basic.TransformWindow="Transformacija stavke na sceni" Basic.TransformWindow.Position="Pozicija" Basic.TransformWindow.Rotation="Rotacija" Basic.TransformWindow.Size="Veličina" -Basic.TransformWindow.Alignment="Poziciono poravnanje" -Basic.TransformWindow.BoundsType="Tip okvira" +Basic.TransformWindow.Alignment="Pozicijsko poravnanje" +Basic.TransformWindow.BoundsType="Vrsta okvira" Basic.TransformWindow.BoundsAlignment="Poravnanje u okviru" Basic.TransformWindow.Bounds="Veličina okvira" -Basic.TransformWindow.Crop="Isecanje" +Basic.TransformWindow.Crop="Isijecanje" -Basic.TransformWindow.Alignment.TopLeft="Gore levo" -Basic.TransformWindow.Alignment.TopCenter="Gore centar" +Basic.TransformWindow.Alignment.TopLeft="Gore lijevo" +Basic.TransformWindow.Alignment.TopCenter="Gore u sredini" Basic.TransformWindow.Alignment.TopRight="Gore desno" -Basic.TransformWindow.Alignment.CenterLeft="Levo centar" -Basic.TransformWindow.Alignment.Center="Centar" -Basic.TransformWindow.Alignment.CenterRight="Desno centar" -Basic.TransformWindow.Alignment.BottomLeft="Donje levo" -Basic.TransformWindow.Alignment.BottomCenter="Donje centar" -Basic.TransformWindow.Alignment.BottomRight="Donje desno" +Basic.TransformWindow.Alignment.CenterLeft="Lijevo od sredine" +Basic.TransformWindow.Alignment.Center="Sredina" +Basic.TransformWindow.Alignment.CenterRight="Desno od sredine" +Basic.TransformWindow.Alignment.BottomLeft="Dolje lijevo" +Basic.TransformWindow.Alignment.BottomCenter="Dolje od sredine" +Basic.TransformWindow.Alignment.BottomRight="Dolje desno" Basic.TransformWindow.BoundsType.None="Bez okvira" Basic.TransformWindow.BoundsType.MaxOnly="Samo najveća veličina" -Basic.TransformWindow.BoundsType.ScaleInner="Skaliraj prema unutrašnjim okvirima" -Basic.TransformWindow.BoundsType.ScaleOuter="Skaliraj prema spoljnjim okvirima" +Basic.TransformWindow.BoundsType.ScaleInner="Skaliraj prema unutarnjim okvirima" +Basic.TransformWindow.BoundsType.ScaleOuter="Skaliraj prema vanjskim okvirima" Basic.TransformWindow.BoundsType.ScaleToWidth="Skaliraj do širine okvira" Basic.TransformWindow.BoundsType.ScaleToHeight="Skaliraj do visine okvira" Basic.TransformWindow.BoundsType.Stretch="Razvuci do okvira" @@ -269,92 +311,94 @@ Basic.Main.AddSourceHelp.Text="Potrebno je najmanje jedna scena da dodate izvor. Basic.Main.Scenes="Scene" Basic.Main.Sources="Izvori" +Basic.Main.Controls="Kontrole" Basic.Main.Connecting="Povezivanje..." Basic.Main.StartRecording="Počni snimanje" -Basic.Main.StartStreaming="Počni strimovanje" +Basic.Main.StartStreaming="Počni streamanje" Basic.Main.StopRecording="Zaustavi snimanje" Basic.Main.StoppingRecording="Zaustavljanje snimanja..." -Basic.Main.StopStreaming="Zaustavi strimovanje" -Basic.Main.StoppingStreaming="Zaustavljanje emitovanja..." -Basic.Main.ForceStopStreaming="Zaustavi strimovanje (poništi odlaganje)" +Basic.Main.StopStreaming="Zaustavi streamanje" +Basic.Main.StoppingStreaming="Zaustavljanje streamanja..." +Basic.Main.ForceStopStreaming="Zaustavi streamanje (poništi odlaganje)" -Basic.MainMenu.File="&Fajl" +Basic.MainMenu.File="Datoteka (&F)" Basic.MainMenu.File.Export="Izv&ezi" Basic.MainMenu.File.Import="Uvoz (&I)" Basic.MainMenu.File.ShowRecordings="P&rikaži snimke" Basic.MainMenu.File.Remux="Re&mux snimaka" Basic.MainMenu.File.Settings="Podešavanja (&S)" -Basic.MainMenu.File.ShowSettingsFolder="Prikaži folder sa podešavanjima" -Basic.MainMenu.File.ShowProfileFolder="Prikaži folder sa profilima" -Basic.MainMenu.AlwaysOnTop="Uvek na vrhu (&A)" +Basic.MainMenu.File.ShowSettingsFolder="Prikaži mapu s postavkama" +Basic.MainMenu.File.ShowProfileFolder="Prikaži mapu s profilima" +Basic.MainMenu.AlwaysOnTop="Uvijek na vrhu (&A)" Basic.MainMenu.File.Exit="Izlaz (&X)" -Basic.MainMenu.Edit="Izm&ena" -Basic.MainMenu.Edit.Undo="Vrati (&U)" -Basic.MainMenu.Edit.Redo="U&radi ponovo" -Basic.MainMenu.Edit.UndoAction="Vrati $1 (&U)" -Basic.MainMenu.Edit.RedoAction="U&radi ponovo $1" +Basic.MainMenu.Edit="Ur&edi" +Basic.MainMenu.Edit.Undo="Poništi (&U)" +Basic.MainMenu.Edit.Redo="Ponovi (&R)" +Basic.MainMenu.Edit.UndoAction="Poništi $1 (&U)" +Basic.MainMenu.Edit.RedoAction="Ponovi $1 (&R)" Basic.MainMenu.Edit.LockPreview="Zak&ljučaj prikaz" Basic.MainMenu.Edit.Scale="Pregled &skaliranja" Basic.MainMenu.Edit.Scale.Window="Skaliraj na veličinu prozora" Basic.MainMenu.Edit.Scale.Canvas="Platno (%1x%2)" Basic.MainMenu.Edit.Scale.Output="Izlaz (%1x%2)" -Basic.MainMenu.Edit.Transform="&Transformiši" -Basic.MainMenu.Edit.Transform.EditTransform="Izm&eni transformaciju..." +Basic.MainMenu.Edit.Transform="&Transformiraj" +Basic.MainMenu.Edit.Transform.EditTransform="Izmij&eni transformaciju..." Basic.MainMenu.Edit.Transform.ResetTransform="Poništi t&ransformaciju" -Basic.MainMenu.Edit.Transform.Rotate90CW="Okreni za 90 stepeni u smeru kazaljke na satu" -Basic.MainMenu.Edit.Transform.Rotate90CCW="Okreni za 90 stepeni u kontra smeru kazaljke na satu" -Basic.MainMenu.Edit.Transform.Rotate180="Okreni za 180 stepeni" -Basic.MainMenu.Edit.Transform.FlipHorizontal="Prevrni &horizontalno" -Basic.MainMenu.Edit.Transform.FlipVertical="Prevrni &vertikalno" -Basic.MainMenu.Edit.Transform.FitToScreen="Raširi po ekranu (&F)" -Basic.MainMenu.Edit.Transform.StretchToScreen="Razvuci po ekranu (&S)" -Basic.MainMenu.Edit.Transform.CenterToScreen="&Centriraj na ekranu" -Basic.MainMenu.Edit.Order="Red&osled" -Basic.MainMenu.Edit.Order.MoveUp="Pomeri gore (&U)" -Basic.MainMenu.Edit.Order.MoveDown="Pomeri &dole" -Basic.MainMenu.Edit.Order.MoveToTop="Pomeri na vrh (&T)" -Basic.MainMenu.Edit.Order.MoveToBottom="Pomeri na dno (&B)" -Basic.MainMenu.Edit.AdvAudio="N&apredna podešavanja zvuka" +Basic.MainMenu.Edit.Transform.Rotate90CW="Okreni za 90 stupnjeva u smjeru kazaljke na satu" +Basic.MainMenu.Edit.Transform.Rotate90CCW="Okreni za 90 stupnjeva suprotno smjeru kazaljke na satu" +Basic.MainMenu.Edit.Transform.Rotate180="Okreni za 180 stupnjeva" +Basic.MainMenu.Edit.Transform.FlipHorizontal="Obrni vodoravno (&H)" +Basic.MainMenu.Edit.Transform.FlipVertical="Obrni okomito (&V)" +Basic.MainMenu.Edit.Transform.FitToScreen="Raširi po zaslonu (&F)" +Basic.MainMenu.Edit.Transform.StretchToScreen="Razvuci po zaslonu (&S)" +Basic.MainMenu.Edit.Transform.CenterToScreen="&Centriraj na zaslon" +Basic.MainMenu.Edit.Order="Red&oslijed" +Basic.MainMenu.Edit.Order.MoveUp="Pomakni gore (&U)" +Basic.MainMenu.Edit.Order.MoveDown="Pomakni &dolje" +Basic.MainMenu.Edit.Order.MoveToTop="Pomakni na vrh (&T)" +Basic.MainMenu.Edit.Order.MoveToBottom="Pomakni na dno (&B)" +Basic.MainMenu.Edit.AdvAudio="N&apredne postavke zvuka" -Basic.MainMenu.View="Pogled (&V)" -Basic.MainMenu.View.Toolbars="Linije ala&tki" -Basic.MainMenu.View.Toolbars.Listboxes="Spisak stavki (&L)" -Basic.MainMenu.View.SceneTransitions="S&censki prelazi" -Basic.MainMenu.View.StatusBar="&Statusna linija" +Basic.MainMenu.View="Prikaz (&V)" +Basic.MainMenu.View.Toolbars="Alatne &trake" +Basic.MainMenu.View.Toolbars.Listboxes="Popis stavki (&L)" +Basic.MainMenu.View.SceneTransitions="S&censki prijelazi" +Basic.MainMenu.View.StatusBar="Traka &stanja" -Basic.MainMenu.SceneCollection="Kolekcija &scena" +Basic.MainMenu.SceneCollection="Zbirka &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" -Basic.MainMenu.Help.Logs.ShowLogs="Prikaži log datoteke (&S)" -Basic.MainMenu.Help.Logs.UploadCurrentLog="Pošalji trenutnu log datoteku (&C)" -Basic.MainMenu.Help.Logs.UploadLastLog="Pošalji pos&lednju log datoteku" -Basic.MainMenu.Help.Logs.ViewCurrentLog="Prikaži trenutni zapisnik (&V)" +Basic.MainMenu.Help.Website="Posjeti stranicu (&W)" +Basic.MainMenu.Help.Logs="Zapisnici (&L)" +Basic.MainMenu.Help.Logs.ShowLogs="Prikaži zapisnike (&S)" +Basic.MainMenu.Help.Logs.UploadCurrentLog="Pošalji trenutačni zapisnik (&C)" +Basic.MainMenu.Help.Logs.UploadLastLog="Pošalji pos&ljednji zapisnik" +Basic.MainMenu.Help.Logs.ViewCurrentLog="Prikaži trenutačni zapisnik (&V)" Basic.MainMenu.Help.CheckForUpdates="Proveri verziju programa" -Basic.Settings.ProgramRestart="Program mora biti ponovo pokrenut da bi ova podešavanja bila prihvaćena." -Basic.Settings.ConfirmTitle="Potvrdite promene" -Basic.Settings.Confirm="Postoje promene koje nisu sačuvane. Sačuvati?" +Basic.Settings.ProgramRestart="Program se mora ponovno pokrenuti da bi ova postavka stupila na snagu." +Basic.Settings.ConfirmTitle="Potvrdite promjene" +Basic.Settings.Confirm="Niste spremili sve promjene. Spremiti?" -Basic.Settings.General="Opšte" +Basic.Settings.General="Općenito" Basic.Settings.General.Theme="Tema" 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.WarnBeforeStartingStream="Prikaži prozor za potvrdu kad se započinju streamovi" +Basic.Settings.General.WarnBeforeStoppingStream="Prikaži prozor za potvrdu kad se zaustavljaju streamovi" +Basic.Settings.General.Projectors="Projektori" Basic.Settings.General.HideProjectorCursor="Sakrij pokazivač na projektorima" -Basic.Settings.General.ProjectorAlwaysOnTop="Uvek postavi projektor na vrh prozora" +Basic.Settings.General.ProjectorAlwaysOnTop="Uvijek 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" +Basic.Settings.General.ScreenSnapping="Privuci izvore rubu zaslona" +Basic.Settings.General.CenterSnapping="Privuci izvore vodoravnoj i okomitoj sredini" 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.SnapDistance="Osjetljivost privlačenja" +Basic.Settings.General.RecordWhenStreaming="Automatski snimaj dok streamam" +Basic.Settings.General.KeepRecordingWhenStreamStops="Nastavi snimati nakon završetka streama" Basic.Settings.General.SysTrayWhenStarted="Pri pokretanju minimiziraj na ikonicu u sistemskom panelu" Basic.Settings.Stream="Strim" diff --git a/UI/data/locale/hu-HU.ini b/UI/data/locale/hu-HU.ini index f79e644..d8b91f0 100644 --- a/UI/data/locale/hu-HU.ini +++ b/UI/data/locale/hu-HU.ini @@ -28,16 +28,21 @@ Browse="Tallózás" Mono="Mono" Stereo="Sztereó" DroppedFrames="Ejtett képkockák: %1 (%2 %)" +StudioProgramProjector="Teljes képernyős projektor (Program)" PreviewProjector="Teljes képernyős projektor (Előnézet)" SceneProjector="Teljes képernyős projektor (Jelenet)" SourceProjector="Teljes képernyős projektor (Forrás)" +StudioProgramWindow="Ablakos projektor (Program)" PreviewWindow="Ablakos projektor (Előnézet)" SceneWindow="Ablakos projektor (Jelenet)" SourceWindow="Ablakos projektor (Forrás)" +MultiviewProjector="Multiview (Teljes képernyő)" +MultiviewWindowed="Multiview (Ablakos)" Clear="Törlés" Revert="Visszavonás" Show="Mutat" Hide="Elrejt" +UnhideAll="Összes megjelenítése" Untitled="Névtelen" New="Új" Duplicate="Másolat készítése" @@ -66,6 +71,13 @@ PasteDuplicate="Beillesztés (Másolat)" RemuxRecordings="Remux Felvételek" Next="Következő" Back="Vissza" +Defaults="Alapbeállítások" +HideMixer="Elrejtés a Keverőben" +TransitionOverride="Átmenet felülírása" +None="Nincs" +StudioMode.Preview="Előnézet" +StudioMode.Program="Program" +ShowInMultiview="Mutatás Multiviewban" AlreadyRunning.Title="Az OBS már fut" AlreadyRunning.Text="Az OBS már fut! Ha nem teljesen biztos benne mit tesz, akkor állítsa le az összes már futó OBS programot. Ha a programot úgy állította be, hogy rendszertálcára minimalizálódjon, akkor ellenőrizze, hogy ott megtalálható e." @@ -300,6 +312,10 @@ AddProfile.Text="Kérjük, adja meg a profil nevét" RenameProfile.Title="Profil átnevezése" +Basic.Main.MixerRename.Title="Hangforrás átnevezése" +Basic.Main.MixerRename.Text="Kérem írja be a hangforrás nevét" + + Basic.Main.PreviewDisabled="Előnézet pillanatnyilag lekapcsolva" Basic.SourceSelect="Forrás Létrehozása/Kijelölése" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Legalább 1 jelenetre van szükség forrás hozz Basic.Main.Scenes="Jelenetek" Basic.Main.Sources="Források" +Basic.Main.Controls="Vezérlés" Basic.Main.Connecting="Kapcsolódás..." Basic.Main.StartRecording="Felvétel indítása" Basic.Main.StartReplayBuffer="Visszajátszás puffer indítása" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="&Speciális hangtulajdonságok" Basic.MainMenu.View="&Nézet" Basic.MainMenu.View.Toolbars="&Eszköztárak" +Basic.MainMenu.View.Docks="Dokkolás" +Basic.MainMenu.View.Docks.ResetUI="Felület újraindítása" +Basic.MainMenu.View.Docks.LockUI="Felület zárása" Basic.MainMenu.View.Toolbars.Listboxes="&Gombsor" Basic.MainMenu.View.SceneTransitions="&Jelenet átmenetek" Basic.MainMenu.View.StatusBar="&Állapotsor" +Basic.MainMenu.View.Fullscreen.Interface="Teljes képernyős felület" Basic.MainMenu.SceneCollection="&Jelenet gyűjtemény" Basic.MainMenu.Profile="&Profil" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="A jelenet gyűjtemény már létezik" Basic.MainMenu.Tools="&Eszközök" Basic.MainMenu.Help="&Segítség" +Basic.MainMenu.Help.HelpPortal="Segítség &Portál" Basic.MainMenu.Help.Website="Weboldal megtekintése" Basic.MainMenu.Help.Logs="&Naplófájlok" Basic.MainMenu.Help.Logs.ShowLogs="&Naplófájlok megjelenítése" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Tálca" Basic.Settings.General.SysTrayWhenStarted="Indításkor ikonként a tálcán" Basic.Settings.General.SystemTrayHideMinimize="Mindig a rendszertálcára minimalizálás tálca helyett" Basic.Settings.General.SaveProjectors="Projektorok mentése kilépéskor" +Basic.Settings.General.SwitchOnDoubleClick="Átmenet a jelenetre dupla kattintás esetén" +Basic.Settings.General.StudioPortraitLayout="Portré/függőleges elrendezés engedélyezése" +Basic.Settings.General.MultiviewLayout="MultiView elrendezés" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Vízszintes, Felső" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Vízszintes, Alsó" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Függőleges, Bal" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Függőleges, Jobb" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Stream típusa" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (élesített méretezés, Basic.Settings.Audio="Hang" Basic.Settings.Audio.SampleRate="Mintavételezés" Basic.Settings.Audio.Channels="Csatornák" +Basic.Settings.Audio.MeterDecayRate="Hangmérő halkulási aránya" +Basic.Settings.Audio.MeterDecayRate.Fast="Gyors" +Basic.Settings.Audio.MeterDecayRate.Medium="Medium (Típus | PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Lassú (Típus II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="Figyelmeztetés: Surround sound hang engedélyezve van." +Basic.Settings.Audio.MultichannelWarning="Ha közvetít, ellenőrizze, hogy a stream szolgáltatója támogatja mind a surround sound kezelést, mind pedig a surround sound lejátszást. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast a legjobb példa, hogy mely platformokon van teljes támogatás. Ellenben a Facebook Live és a YouTube Live minden elfogadja a térhangzású hangot, a Facebook Live lekeveri stereora és a YouTube Live csak két csatornát játszik le.\n\nOBS audio szűrők kompatibilisek a térhangzású hanggal, viszont a VST bővítmények támogatása nem garantált." +Basic.Settings.Audio.MultichannelWarning.Title="Engedélyezi a surround hangzást?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Biztos benne, hogy engedélyezi a surround hangzást?" Basic.Settings.Audio.DesktopDevice="Asztali hangeszköz" Basic.Settings.Audio.DesktopDevice2="Asztali hangeszköz 2" Basic.Settings.Audio.AuxDevice="Mikrofon/Aux hangeszköz" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Folyamat Prioritás szintje" Basic.Settings.Advanced.General.ProcessPriority.High="Magas" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Normál feletti" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normál" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Normál alatti" Basic.Settings.Advanced.General.ProcessPriority.Idle="Tétlen" Basic.Settings.Advanced.FormatWarning="Figyelem: Az NV12-től eltérő színformátumok elsősorban felvételhez vannak és nem ajánlott a használatuk streamekhez. Adás közben megnövekedett processzor igényt okozhat a színkonverzió." Basic.Settings.Advanced.Audio.BufferingTime="Audio pufferelési idő" diff --git a/UI/data/locale/it-IT.ini b/UI/data/locale/it-IT.ini index 9753f92..39d72e9 100644 --- a/UI/data/locale/it-IT.ini +++ b/UI/data/locale/it-IT.ini @@ -17,7 +17,7 @@ Rename="Rinomina" Interact="Interazione" Filters="Filtri" Properties="Proprietà" -MoveUp="Sposta su" +MoveUp="Sposta in alto" MoveDown="Sposta giù" Settings="Impostazioni" Display="Schermo" @@ -28,20 +28,28 @@ Browse="Sfoglia" Mono="Mono" Stereo="Stereo" DroppedFrames="Fotogrammi persi %1 (%2%)" +StudioProgramProjector="Proiettore a schermo intero (anteprima)" PreviewProjector="Proiettore a schermo intero (anteprima)" SceneProjector="Proiettore a schermo intero (scena)" -SourceProjector="Proiettore a schermo intero (sorgente)" +SourceProjector="Proiettore a schermo intero (fonte)" +StudioProgramWindow="Proiettore a finestra (fonte)" +PreviewWindow="Proiettore a finestra (anteprima)" +SceneWindow="Proiettore a finestra (scena)" +SourceWindow="Proiettore a finestra (fonte)" +MultiviewProjector="Vista-Multipla (Schermo Intero)" +MultiviewWindowed="Vista-Multipla (Finestra)" Clear="Svuota" Revert="Ripristina" Show="Mostra" Hide="Nascondi" +UnhideAll="Mostra tutto" Untitled="Senza titolo" New="Nuovo" Duplicate="Duplica" Enable="Abilita" DisableOSXVSync="Disabilita V-Sync OSX" ResetOSXVSyncOnExit="Reimposta V-Sync OSX in uscita" -HighResourceUsage="Codifica in sovraccarico! È consigliabile abbassare le impostazioni video o utilizzare una preimpostazione predefinita di codifica più veloce." +HighResourceUsage="Codifica in sovraccarico! È consigliabile abbassare le impostazioni video o utilizzare una impostazione di codifica predefinita più veloce." Transition="Transizione" QuickTransitions="Transizioni rapide" Left="Sinistra" @@ -61,14 +69,94 @@ Paste="Incolla" PasteReference="Incolla (riferimento)" PasteDuplicate="Incolla (duplicato)" RemuxRecordings="Remux registrazioni" +Next="Avanti" +Back="Indietro" +Defaults="Predefinite" +HideMixer="Nascondi nel Mixer" +TransitionOverride="Sovrascrivi transizione" +None="Nessuno" +StudioMode.Preview="Anteprima" +StudioMode.Program="Programma" +ShowInMultiview="Mostra in Vista-Multipla" +AlreadyRunning.Title="OBS è già in esecuzione" +AlreadyRunning.Text="OBS è già in esecuzione! A meno che non si intendeva effettuare questa operazione, chiudere tutte le istanze esistenti di OBS prima di provare a eseguirne una nuova. Se avete OBS impostato per minimizzarsi nell'area di notifica, si prega di controllare per vedere se è ancora in esecuzione." +AlreadyRunning.LaunchAnyway="Avvia comunque" Copy.Filters="Filtri di copia" Paste.Filters="Filtri di incollamento" +BandwidthTest.Region="Regione" +BandwidthTest.Region.US="Stati Uniti" +BandwidthTest.Region.EU="Europa" +BandwidthTest.Region.Asia="Asia" +BandwidthTest.Region.Other="Altro" +Basic.FirstStartup.RunWizard="Vuoi eseguire la procedura guidata di configurazione automatica? È possibile configurare manualmente le impostazioni facendo clic sul pulsante Impostazioni nella finestra principale." +Basic.FirstStartup.RunWizard.BetaWarning="(Nota: la procedura guidata di configurazione automatica è attualmente in beta)" +Basic.FirstStartup.RunWizard.NoClicked="Se cambi idea, è possibile eseguire la procedura guidata di configurazione automatica qualsiasi momento nuovamente dal menu strumenti." +Basic.AutoConfig="Auto-configurazione guidata" +Basic.AutoConfig.Beta="Auto-configurazione guidata (Beta)" +Basic.AutoConfig.ApplySettings="Applica impostazioni" +Basic.AutoConfig.StartPage="Informazioni sull'utilizzo" +Basic.AutoConfig.StartPage.SubTitle="Specificare ciò per cui si desidera utilizzare il programma" +Basic.AutoConfig.StartPage.PrioritizeStreaming="Ottimizza per lo streaming, la registrazione è secondaria" +Basic.AutoConfig.StartPage.PrioritizeRecording="Ottimizza solo per la registrazione, non farò streaming" +Basic.AutoConfig.VideoPage="Impostazioni Video" +Basic.AutoConfig.VideoPage.SubTitle="Specificare le impostazioni video desiderate e che desideri utilizzare" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Attuale (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="Display %1 (%2x%3)" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="Attuale (%1)" +Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="60 o 30, ma preferisco 60 quando possibile" +Basic.AutoConfig.VideoPage.FPS.PreferHighRes="60 o 30, ma preferisco un'alta risoluzione" +Basic.AutoConfig.VideoPage.CanvasExplanation="Nota: La risoluzione di base non è necessariamente la stessa risoluzione che sarà per le dirette o per le registrazioni. La risoluzione di dirette/registrazioni effettiva può essere scalata dalla risoluzione base per ridurre le risorse utilizzate o i requisiti della velocità di trasmissione." +Basic.AutoConfig.StreamPage="Informazioni stream" +Basic.AutoConfig.StreamPage.SubTitle="Per favore inserisci le informazioni sullo stream" +Basic.AutoConfig.StreamPage.Service="Servizio" +Basic.AutoConfig.StreamPage.Service.ShowAll="Mostra tutto..." +Basic.AutoConfig.StreamPage.Server="Server" +Basic.AutoConfig.StreamPage.StreamKey="Chiave Stream" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Link)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Stima il bitrate con test di larghezza di banda (può richiedere alcuni minuti)" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Preferisci la codifica hardware" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="La codifica hardware elimina la maggior parte dell'utilizzo della CPU, ma può richiedere più bitrate per ottenere lo stesso livello di qualità." +Basic.AutoConfig.StreamPage.StreamWarning.Title="Avviso stream" +Basic.AutoConfig.StreamPage.StreamWarning.Text="Il test di larghezza di banda consiste di trasmettere dati video casuali senza audio sul tuo canale. Se possibile, è raccomandabile di disattivare temporaneamente il salvataggio degli stream e di metterli privati fino a quando non finisce il test. Continuare?" +Basic.AutoConfig.TestPage="Risultato finale" +Basic.AutoConfig.TestPage.SubTitle.Testing="Il programma sta ora eseguendo una serie di test per stimare le impostazioni più ideali" +Basic.AutoConfig.TestPage.SubTitle.Complete="Test completo" +Basic.AutoConfig.TestPage.TestingBandwidth="Esecuzione di test di larghezza di banda, questo potrebbe richiedere alcuni minuti..." +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Connessione a: %1..." +Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Impossibile connettersi ad alcun server, per favore verifica la tua connessione internet e riprova." +Basic.AutoConfig.TestPage.TestingBandwidth.Server="Test della larghezza di banda per: %1" +Basic.AutoConfig.TestPage.TestingStreamEncoder="Test streaming encoder, questo può richiedere un minuto..." +Basic.AutoConfig.TestPage.TestingRecordingEncoder="Test encoder di registrazione, questo può richiedere un minuto..." +Basic.AutoConfig.TestPage.TestingRes="Test di risoluzioni, questo potrebbe richiedere alcuni minuti..." +Basic.AutoConfig.TestPage.TestingRes.Fail="Impossibile avviare l'encoder" +Basic.AutoConfig.TestPage.TestingRes.Resolution="Testando %1x%2 %3 FPS..." +Basic.AutoConfig.TestPage.Result.StreamingEncoder="Streaming Encoder" +Basic.AutoConfig.TestPage.Result.RecordingEncoder="Encoder di registrazione" +Basic.AutoConfig.TestPage.Result.Header="Il programma ha determinato che queste impostazioni siano le più ideali per te:" +Basic.AutoConfig.TestPage.Result.Footer="Per utilizzare queste impostazioni, fare clic su Applica impostazioni. Per riconfigurare la procedura guidata e provare di nuovo, fare clic su Indietro. Per configurare manualmente le impostazioni, fare clic su Annulla e aprire le impostazioni." +Basic.Stats="Statistiche" +Basic.Stats.CPUUsage="Utilizzo CPU" +Basic.Stats.HDDSpaceAvailable="Spazio HDD disponibile" +Basic.Stats.MemoryUsage="Memoria occupata" +Basic.Stats.AverageTimeToRender="Tempo medio per il rendering frame" +Basic.Stats.SkippedFrames="Fotogrammi saltati a causa del lag della codifica" +Basic.Stats.MissedFrames="Perdita frame a causa del lag del rendering" +Basic.Stats.Output.Stream="Stream" +Basic.Stats.Output.Recording="Registrazione" +Basic.Stats.Status="Stato" +Basic.Stats.Status.Recording="Registrazione in corso" +Basic.Stats.Status.Live="IN DIRETTA" +Basic.Stats.Status.Reconnecting="Riconnessione in corso" +Basic.Stats.Status.Inactive="Inattivo" +Basic.Stats.DroppedFrames="Perdita di fotogrammi (rete)" +Basic.Stats.MegabytesSent="Uscita dati totale" +Basic.Stats.Bitrate="Bitrate" Updater.Title="Nuovo aggiornamento disponibile" Updater.Text="C'è un nuovo aggiornamento disponibile:" @@ -86,9 +174,9 @@ Updater.GameCaptureActive.Text="L'hook della libreria di cattura gioco è attual QuickTransitions.SwapScenes="Scambia scene di anteprima/uscita dopo la transizione" QuickTransitions.SwapScenesTT="Scambia le scene di uscita con quella in anteprima dopo la transizione (ammesso che la scena in uscita originale ci sia ancora).\nQuesto non modificherà eventuali cambiamenti apportati alla scena di uscita originale." QuickTransitions.DuplicateScene="Duplica scena" -QuickTransitions.DuplicateSceneTT="Quando si modifica la stessa scena, permette di modificare la trasformazione/visibilità delle sorgenti senza modificare l'uscita.\nPer modificare le proprietà delle source senza modificare l'uscita, abilità 'Sorgenti duplicate'.\nCambiare questo valore ripristinerà la scena di uscita attuale (se esiste ancora)." -QuickTransitions.EditProperties="Duplica Risorsa" -QuickTransitions.EditPropertiesTT="Quando si modifica la stessa scena, consente la modifica di risorse senza modificarne l'output. \nQuesto può essere usato solo se 'Scene doppia' è attivo. \nCerte risorse (come media o catture) non lo supportano e devono essere modificate separatamente. \nCambiare questo valore resetterà l'attuale scena di output (se esiste ancora). \n\nAttenzione: Dato che la risorsa verrà duplicata, questo potrebbe richiedere risorse di sistema o video aggiuntive." +QuickTransitions.DuplicateSceneTT="Quando si modifica la stessa scena, permette di modificare la trasformazione/visibilità delle fonti senza modificare l'uscita.\nPer modificare le proprietà delle fonti senza modificare l'uscita, abilità 'Fonti duplicate'.\nCambiare questo valore ripristinerà la scena di uscita attuale (se esiste ancora)." +QuickTransitions.EditProperties="Duplica fonte" +QuickTransitions.EditPropertiesTT="Quando si modifica la stessa scena, consente la modifica di fonti senza modificarne l'uscita. \nQuesto può essere usato solo se 'Scene doppia' è attivo.\nCerte fonti (come media o catture) non lo supportano e devono essere modificate separatamente.\nCambiare questo valore ripristinerà l'attuale scena di uscita (se esiste ancora).\n\nAttenzione: dato che la fonte verrà duplicata, questo potrebbe richiedere risorse video o di sistema aggiuntive." QuickTransitions.HotkeyName="Transizioni rapide: %1" Basic.AddTransition="Aggiungi transizione configurabile" @@ -111,13 +199,13 @@ NoNameEntered.Title="Inserisci un nome valido" NoNameEntered.Text="Non è possibile utilizzare nomi vuoti." ConfirmStart.Title="Vuoi iniziare la trasmissione?" -ConfirmStart.Text="Sei sicuro di voler iniziare una diretta?" +ConfirmStart.Text="Sei sicuro di voler iniziare una trasmissione?" ConfirmStop.Title="Vuoi fermare la trasmissione?" ConfirmStop.Text="Sei sicuro di voler interrompere questa trasmissione?" ConfirmExit.Title="Vuoi uscire da OBS?" -ConfirmExit.Text="OBS è attualmente attivo. Tutte le dirette/registrazioni saranno fermate. Sei sicuro di voler uscire?" +ConfirmExit.Text="OBS è attualmente attivo. Tutte le trasmissioni/registrazioni saranno fermate. Sei sicuro di voler uscire?" ConfirmRemove.Title="Conferma la rimozione" ConfirmRemove.Text="Sei sicuro di voler rimuovere '$1'?" @@ -131,7 +219,7 @@ Output.StartFailedGeneric="L'avvio dell'uscita non è riuscito. Controlla il log Output.ConnectFail.Title="Impossibile connettersi" Output.ConnectFail.BadPath="Percorso o URL di connessione non valido. Controlla le tue impostazioni per confermare che siano valide." Output.ConnectFail.ConnectFailed="Connessione al server fallita" -Output.ConnectFail.InvalidStream="Impossibile accedere al canale od alla stream key specificata, per favore, controlla di nuovo la tua stream key. Se è corretta, potrebbe esserci un problema con la connessione con il server." +Output.ConnectFail.InvalidStream="Impossibile accedere al canale o alla chiave di trasmissione specificata, controlla di nuovo la tua chiave di trasmissione. Se è corretta, potrebbe esserci un problema con la connessione con il server." Output.ConnectFail.Error="Si è verificato un errore non previsto durante la connessione al server. Controlla il file di log per più informazioni." Output.ConnectFail.Disconnected="Disconnesso dal server." @@ -142,7 +230,7 @@ Output.RecordNoSpace.Msg="Non c'è abbastanza spazio su disco per continuazre la Output.RecordError.Title="Errore di registrazione" Output.RecordError.Msg="Si è verificato un errore non specificato durante la registrazione." Output.ReplayBuffer.NoHotkey.Title="Nessuna scorciatoia assegnata!" -Output.ReplayBuffer.NoHotkey.Msg="Nessuna scorciatoia impostata per salvare il buffer di replay. Impostare la scorciatoia \"Salva\" per poter salvare le registrazioni in replay." +Output.ReplayBuffer.NoHotkey.Msg="Nessuna scorciatoia impostata per salvare il buffer di replay. Imposta la scorciatoia \"Salva\" per poter salvare le registrazioni in replay." Output.BadPath.Title="Percorso di file invalido" Output.BadPath.Text="Il percorso configurato per il file di output non è valido. Controlla le tue impostazioni per confermare che un percorso di file valido è stato impostato." @@ -219,17 +307,21 @@ Basic.Main.AddSceneCollection.Text="Per favore inserisci il nome della collezion Basic.Main.RenameSceneCollection.Title="Rinomina collezione scene" -AddProfile.Title="Aggiungi Profilo" -AddProfile.Text="Per favore inserisci il nome del profilo" +AddProfile.Title="Aggiungi profilo" +AddProfile.Text="Digita il nome del profilo" + +RenameProfile.Title="Rinomina profilo" + +Basic.Main.MixerRename.Title="Rinomina fonte audio" +Basic.Main.MixerRename.Text="Inserisci il nome per la fonte audio" -RenameProfile.Title="Rinomina Profilo" Basic.Main.PreviewDisabled="L'anteprima è attualmente disattivata" -Basic.SourceSelect="Crea/seleziona la sorgente" -Basic.SourceSelect.CreateNew="Crea nuovo" -Basic.SourceSelect.AddExisting="Aggiungi esistente" -Basic.SourceSelect.AddVisible="Rendi visibile la provenienza" +Basic.SourceSelect="Crea/seleziona la fonte" +Basic.SourceSelect.CreateNew="Crea una nuova fonte" +Basic.SourceSelect.AddExisting="Aggiungine una esistente" +Basic.SourceSelect.AddVisible="Rendi visibile la fonte" Basic.PropertiesWindow="Proprietà di '%1'" Basic.PropertiesWindow.AutoSelectFormat="%1 (autoseleziona: %2)" @@ -266,7 +358,7 @@ Basic.Filters.AudioFilters="Filtri audio" Basic.Filters.EffectFilters="Filtri per effetti" Basic.Filters.Title="Filtri per '%1'" Basic.Filters.AddFilter.Title="Nome del filtro" -Basic.Filters.AddFilter.Text="Si prega di specificare il nome del filtro" +Basic.Filters.AddFilter.Text="Specifica il nome del filtro" Basic.TransformWindow="Trasforma l'oggetto della scena" Basic.TransformWindow.Position="Posizione" @@ -296,31 +388,32 @@ Basic.TransformWindow.BoundsType.ScaleToWidth="Scala alla larghezza dei limiti" Basic.TransformWindow.BoundsType.ScaleToHeight="Scala all'altezza dei limiti" Basic.TransformWindow.BoundsType.Stretch="Allungare ai limiti" -Basic.Main.AddSourceHelp.Title="Impossibile aggiungere l'origine" -Basic.Main.AddSourceHelp.Text="Devi avere almeno 1 scena per aggiungere un'origine." +Basic.Main.AddSourceHelp.Title="Impossibile aggiungere la fonte" +Basic.Main.AddSourceHelp.Text="Devi avere almeno 1 scena per aggiungere una fonte." Basic.Main.Scenes="Scene" -Basic.Main.Sources="Origini" +Basic.Main.Sources="Fonti" +Basic.Main.Controls="Controlli" Basic.Main.Connecting="Connessione..." Basic.Main.StartRecording="Avvia registrazione" -Basic.Main.StartReplayBuffer="Avvia Buffer di Replay" +Basic.Main.StartReplayBuffer="Avvia buffer di replay" Basic.Main.StartStreaming="Avvia trasmissione" Basic.Main.StopRecording="Ferma registrazione" -Basic.Main.StoppingRecording="Fermando la registrazione..." -Basic.Main.StopReplayBuffer="Termina Buffer di Replay" -Basic.Main.StoppingReplayBuffer="Fermando il Buffer di Replay..." +Basic.Main.StoppingRecording="Arresto della registrazione..." +Basic.Main.StopReplayBuffer="Termina buffer di replay" +Basic.Main.StoppingReplayBuffer="Arresto del buffer di riproduzione in corso..." Basic.Main.StopStreaming="Ferma trasmissione" -Basic.Main.StoppingStreaming="Arresto diretta..." -Basic.Main.ForceStopStreaming="Ferma Diretta (annulla ritardo)" +Basic.Main.StoppingStreaming="Arresto trasmissione..." +Basic.Main.ForceStopStreaming="Ferma trasmissione (annulla ritardo)" Basic.MainMenu.File="&File" Basic.MainMenu.File.Export="&Esporta" Basic.MainMenu.File.Import="&Importa" Basic.MainMenu.File.ShowRecordings="Visualizza ®istrazioni" -Basic.MainMenu.File.Remux="Converti registrazioni" +Basic.MainMenu.File.Remux="Converti re&gistrazioni" Basic.MainMenu.File.Settings="&Impostazioni" Basic.MainMenu.File.ShowSettingsFolder="Visualizza cartella impostazioni" -Basic.MainMenu.File.ShowProfileFolder="Mostra la cartella del profilo" +Basic.MainMenu.File.ShowProfileFolder="Visualizza la cartella dei profili" Basic.MainMenu.AlwaysOnTop="&Sempre in primo piano" Basic.MainMenu.File.Exit="E&sci" @@ -336,29 +429,33 @@ Basic.MainMenu.Edit.Scale.Canvas="Tela (%1x%2)" Basic.MainMenu.Edit.Scale.Output="Uscita (%1x%2)" Basic.MainMenu.Edit.Transform="&Trasforma" Basic.MainMenu.Edit.Transform.EditTransform="&Modifica e trasforma..." -Basic.MainMenu.Edit.Transform.CopyTransform="Copia Trasformazione" -Basic.MainMenu.Edit.Transform.PasteTransform="Incolla Trasformazione" +Basic.MainMenu.Edit.Transform.CopyTransform="Copia e trasforma" +Basic.MainMenu.Edit.Transform.PasteTransform="Incolla e trasforma" Basic.MainMenu.Edit.Transform.ResetTransform="&Reset e trasforma" -Basic.MainMenu.Edit.Transform.Rotate90CW="Ruota di 90 gradi DW" -Basic.MainMenu.Edit.Transform.Rotate90CCW="Ruota di 90 gradi CCW" -Basic.MainMenu.Edit.Transform.Rotate180="Ruota di 180 grado" -Basic.MainMenu.Edit.Transform.FlipHorizontal="Capovolgi &Orizzontalmente" -Basic.MainMenu.Edit.Transform.FlipVertical="Capovolgi &Verticalmente" +Basic.MainMenu.Edit.Transform.Rotate90CW="Ruota di 90 gradi in senso orario" +Basic.MainMenu.Edit.Transform.Rotate90CCW="Ruota di 90 gradi in senso antiorario" +Basic.MainMenu.Edit.Transform.Rotate180="Ruota di 180 gradi" +Basic.MainMenu.Edit.Transform.FlipHorizontal="Ribalta &orizzontalmente" +Basic.MainMenu.Edit.Transform.FlipVertical="Ribalta &verticalmente" Basic.MainMenu.Edit.Transform.FitToScreen="&Adatta allo schermo" Basic.MainMenu.Edit.Transform.StretchToScreen="&Ridimensiona come lo schermo" -Basic.MainMenu.Edit.Transform.CenterToScreen="&Centra allo schermo" +Basic.MainMenu.Edit.Transform.CenterToScreen="&Centra nello schermo" Basic.MainMenu.Edit.Order="&Ordine" -Basic.MainMenu.Edit.Order.MoveUp="Muovi &Sopra" -Basic.MainMenu.Edit.Order.MoveDown="Sposta in &basso" -Basic.MainMenu.Edit.Order.MoveToTop="Sposta in &primo piano" +Basic.MainMenu.Edit.Order.MoveUp="Sposta s&u" +Basic.MainMenu.Edit.Order.MoveDown="Sposta &giù" +Basic.MainMenu.Edit.Order.MoveToTop="Sposta in &cima" Basic.MainMenu.Edit.Order.MoveToBottom="Sposta in &fondo" Basic.MainMenu.Edit.AdvAudio="Proprietà audio &avanzate" Basic.MainMenu.View="&Visualizza" Basic.MainMenu.View.Toolbars="&Barre degli strumenti" +Basic.MainMenu.View.Docks="Docks" +Basic.MainMenu.View.Docks.ResetUI="Reset UI" +Basic.MainMenu.View.Docks.LockUI="Blocca UI" Basic.MainMenu.View.Toolbars.Listboxes="&Listboxes" Basic.MainMenu.View.SceneTransitions="&Transizioni di scena" Basic.MainMenu.View.StatusBar="&Barra di stato" +Basic.MainMenu.View.Fullscreen.Interface="Interfaccia a schermo intero" Basic.MainMenu.SceneCollection="&Collezione scene" Basic.MainMenu.Profile="&Profilo" @@ -372,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="La collezione di scene già esiste" Basic.MainMenu.Tools="&Strumenti" Basic.MainMenu.Help="&Aiuto" +Basic.MainMenu.Help.HelpPortal="Portale Aiuto" Basic.MainMenu.Help.Website="Visita il sito" Basic.MainMenu.Help.Logs="File di &log" Basic.MainMenu.Help.Logs.ShowLogs="&Visualizza i file di Log" @@ -384,30 +482,38 @@ Basic.Settings.ProgramRestart="Il programma deve essere riavviato perché questi Basic.Settings.ConfirmTitle="Conferma cambiamenti" Basic.Settings.Confirm="Hai dei cambiamenti non salvati. Vuoi salvarli?" -Basic.Settings.General="Generali" +Basic.Settings.General="Generale" Basic.Settings.General.Theme="Tema" Basic.Settings.General.Language="Lingua" Basic.Settings.General.EnableAutoUpdates="Controlla aggiornamenti automaticamente all'avvio" -Basic.Settings.General.WarnBeforeStartingStream="Chiedi conferma quando si avvia una diretta" +Basic.Settings.General.OpenStatsOnStartup="Apri finestra statistiche all'avvio" +Basic.Settings.General.WarnBeforeStartingStream="Chiedi conferma quando si avvia una trasmissione" Basic.Settings.General.WarnBeforeStoppingStream="Chiedi conferma quando si termina una diretta" Basic.Settings.General.Projectors="Proiettori" Basic.Settings.General.HideProjectorCursor="Nascondi cursore sopra proiettori" Basic.Settings.General.ProjectorAlwaysOnTop="Rendono i proiettori sempre in primo piano" -Basic.Settings.General.Snapping="Allineamento Snapping Source" -Basic.Settings.General.ScreenSnapping="Snap source nei bordi dello schermo" -Basic.Settings.General.CenterSnapping="Snap source al centro orizzontale e verticale" -Basic.Settings.General.SourceSnapping="Snap sources ad altre sources" +Basic.Settings.General.Snapping="Allineamento snap fonti" +Basic.Settings.General.ScreenSnapping="Snap delle fonti ai bordi dello schermo" +Basic.Settings.General.CenterSnapping="Snap delle fonti al centro della scena, orizzontalmente e verticalmente" +Basic.Settings.General.SourceSnapping="Snap di fonti con altre fonti" Basic.Settings.General.SnapDistance="Sensibilità Snap" -Basic.Settings.General.RecordWhenStreaming="Registra automaticamente quando si è in diretta" -Basic.Settings.General.KeepRecordingWhenStreamStops="Continua a registrare quando la diretta s'interrompe" +Basic.Settings.General.RecordWhenStreaming="Registra automaticamente quando si trasmette" +Basic.Settings.General.KeepRecordingWhenStreamStops="Continua a registrare quando la trasmissione si interrompe" Basic.Settings.General.ReplayBufferWhileStreaming="Avvia automaticamente il buffer di riproduzione durante la trasmissione" Basic.Settings.General.KeepReplayBufferStreamStops="Mantieni il buffer di riproduzione attiva quando la trasmissione si interrompe" Basic.Settings.General.SysTray="Vassoio di sistema" Basic.Settings.General.SysTrayWhenStarted="Minimizza all'area di notifica all'avvio" Basic.Settings.General.SystemTrayHideMinimize="Minimizza sempre nel vassoio di sistema invece che nella barra delle applicazioni" Basic.Settings.General.SaveProjectors="Salva i proiettori all'uscita" +Basic.Settings.General.SwitchOnDoubleClick="Transizione alla scena al doppio-click" +Basic.Settings.General.StudioPortraitLayout="Attiva il layout Orizzontale/Verticale" +Basic.Settings.General.MultiviewLayout="Layout a viste multiple" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Orizzontale, Alto" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Orizzontale, Basso" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Orizzontale, Sinistra" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Orizzontale, Destra" -Basic.Settings.Stream="Stream" +Basic.Settings.Stream="Trasmissione" Basic.Settings.Stream.StreamType="Tipo di stream" Basic.Settings.Output="Output" @@ -415,32 +521,32 @@ Basic.Settings.Output.Format="Formato di registrazione" Basic.Settings.Output.Encoder="Codifica" Basic.Settings.Output.SelectDirectory="Seleziona cartella di registrazione" Basic.Settings.Output.SelectFile="Seleziona file di registrazione" -Basic.Settings.Output.EnforceBitrate="Forza limiti bitrate del servizio Streaming" +Basic.Settings.Output.EnforceBitrate="Forza limiti bitrate del servizio di trasmissione" Basic.Settings.Output.Mode="Modalità di output" Basic.Settings.Output.Mode.Simple="Semplice" Basic.Settings.Output.Mode.Adv="Avanzate" Basic.Settings.Output.Mode.FFmpeg="Uscita FFmpeg" -Basic.Settings.Output.UseReplayBuffer="Abilita il Buffer di Replay" -Basic.Settings.Output.ReplayBuffer.SecondsMax="Tempo massimo di Replay (Secondi)" -Basic.Settings.Output.ReplayBuffer.MegabytesMax="Memoria Massima (Megabytes)" +Basic.Settings.Output.UseReplayBuffer="Abilita buffer di replay" +Basic.Settings.Output.ReplayBuffer.SecondsMax="Tempo massimo di replay (secondi)" +Basic.Settings.Output.ReplayBuffer.MegabytesMax="Memoria massima (megabyte)" Basic.Settings.Output.ReplayBuffer.Estimate="Uso della memoria stimato: %1 MB" -Basic.Settings.Output.ReplayBuffer.EstimateUnknown="Impossibile stimare la memoria utilizzata. Impostare un limite massimo di memoria." -Basic.Settings.Output.ReplayBuffer.HotkeyMessage="(Nota: Assicurati di aver impostato una hotkey per il Buffer di Replay nella sezione delle hotkeys)" -Basic.Settings.Output.ReplayBuffer.Prefix="Prefisso del file per i Buffer di Replay" +Basic.Settings.Output.ReplayBuffer.EstimateUnknown="Impossibile stimare la memoria utilizzata. Imposta un limite massimo di memoria." +Basic.Settings.Output.ReplayBuffer.HotkeyMessage="(Nota: assicurati di aver impostato una scorciatoia per il buffer di replay nella sezione delle scorciatoie)" +Basic.Settings.Output.ReplayBuffer.Prefix="Prefisso del nome file per i buffer di replay" Basic.Settings.Output.ReplayBuffer.Suffix="Suffisso" Basic.Settings.Output.Simple.SavePath="Percorso registrazione" Basic.Settings.Output.Simple.RecordingQuality="Qualità della registrazione" -Basic.Settings.Output.Simple.RecordingQuality.Stream="Stesso della diretta" +Basic.Settings.Output.Simple.RecordingQuality.Stream="Stesso della trasmissione" Basic.Settings.Output.Simple.RecordingQuality.Small="Alta qualità, medie dimensioni del file" Basic.Settings.Output.Simple.RecordingQuality.HQ="Qualità Indistinguibuile, larghe dimensioni del file" -Basic.Settings.Output.Simple.RecordingQuality.Lossless="Senza perdità di qualità, dimensioni del file tremendamente larghe" -Basic.Settings.Output.Simple.Warn.VideoBitrate="Avviso: Il bitrate video in streaming verrà impostato su %1, che è il limite superiore per il servizio di streaming corrente. Se sei sicuro di che voler andare di sopra %1, abilita le opzioni avanzate del encorder e deseleziona \"Imponi streaming limiti di servizio bitrate\"." +Basic.Settings.Output.Simple.RecordingQuality.Lossless="Senza perdità di qualità, dimensioni del file enormi" +Basic.Settings.Output.Simple.Warn.VideoBitrate="Avviso: Il bitrate video della trasmissione verrà impostato su %1, che è il limite superiore per il servizio di trasmissione corrente. Se sei sicuro di che voler andare di sopra %1, abilita le opzioni avanzate del codificatore e deseleziona \"Imponi limiti bitrate del servizio di trasmissione\"." Basic.Settings.Output.Simple.Warn.AudioBitrate="Avviso: Il bitrate audio in streaming verrà impostato su %1, che è il limite superiore per il servizio di streaming corrente. Se sei sicuro di che voler andare di sopra %1, abilita le opzioni avanzate del encorder e deseleziona \"Imponi streaming limiti di servizio bitrate\"." -Basic.Settings.Output.Simple.Warn.Encoder="Attenzione: Registrare con un encoder software ad una qualità diversa dalla diretta richiederà un utilizzo extra della CPU se registri e vai in diretta allo stesso tempo." +Basic.Settings.Output.Simple.Warn.Encoder="Attenzione: registrare con un codificatore software a una qualità diversa dalla trasmissione richiederà un utilizzo aggiuntivo della CPU se trasmetti e registri allo stesso tempo." Basic.Settings.Output.Simple.Warn.Lossless="Attenzione: La qualità Lossless genera file estremamente grandi! La Lossless puo occupare 7 Gigabytes di spazio per minuto ad alte risoluzioni e framerate. La lossless non è consigliata per lunghe registrazione a meno che tu non abbia molto spazio disponibile sul disco rigido." Basic.Settings.Output.Simple.Warn.Lossless.Msg="Sei sicuro di volere utilizzare la qualità lossless?" Basic.Settings.Output.Simple.Warn.Lossless.Title="Avviso sulla qualità lossless!" -Basic.Settings.Output.Simple.Warn.MultipleQSV="Attenzione: Non è possibile usare più encoder QSV quando si è in diretta e si registra allo stesso tempo. Se vuoi andare in diretta e registrare allo stesso tempo, cambia l'encoder per la registrazione o l'encoder per la diretta." +Basic.Settings.Output.Simple.Warn.MultipleQSV="Attenzione: non è possibile usare più codificatori QSV quando si è in diretta e si registra allo stesso tempo. Se vuoi andare in diretta e registrare allo stesso tempo, cambia l'encoder per la registrazione o l'encoder per la diretta." 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)" @@ -459,7 +565,7 @@ Basic.Settings.Output.NoSpaceFileName="Genera il nome del file senza spazi" Basic.Settings.Output.Adv.Rescale="Riscala uscita" Basic.Settings.Output.Adv.AudioTrack="Traccia Audio" -Basic.Settings.Output.Adv.Streaming="Streaming" +Basic.Settings.Output.Adv.Streaming="Trasmissione" Basic.Settings.Output.Adv.ApplyServiceSettings="Applica le impostazioni di codifica servizio streaming" Basic.Settings.Output.Adv.Audio.Track1="Traccia 1" Basic.Settings.Output.Adv.Audio.Track2="Traccia 2" @@ -477,7 +583,7 @@ Basic.Settings.Output.Adv.Recording.Filename="Formattazione nome del file" Basic.Settings.Output.Adv.Recording.OverwriteIfExists="Sovrascrivi il file se già esistente" Basic.Settings.Output.Adv.FFmpeg.Type="Tipo di Output FFmpeg" Basic.Settings.Output.Adv.FFmpeg.Type.URL="Output in URL" -Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile="Output su File" +Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile="Output su file" Basic.Settings.Output.Adv.FFmpeg.SaveFilter.Common="Comuni formati di registrazione" Basic.Settings.Output.Adv.FFmpeg.SaveFilter.All="Tutti i file" Basic.Settings.Output.Adv.FFmpeg.SavePathURL="Percorso del file o URL" @@ -487,7 +593,7 @@ Basic.Settings.Output.Adv.FFmpeg.FormatVideo="Video" Basic.Settings.Output.Adv.FFmpeg.FormatDefault="Formato predefinito" Basic.Settings.Output.Adv.FFmpeg.FormatDesc="Descrizione Formato Contenitore" Basic.Settings.Output.Adv.FFmpeg.FormatDescDef="Codec Audio/Video indovinato dal percorso del file o URL" -Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault="Encoder predefinito" +Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault="Codificatore predefinito" Basic.Settings.Output.Adv.FFmpeg.AVEncoderDisable="Encoder Disabilitato" Basic.Settings.Output.Adv.FFmpeg.VEncoder="Encoder Video" Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings="Impostazioni codifica video (se presente)" @@ -495,7 +601,7 @@ Basic.Settings.Output.Adv.FFmpeg.AEncoder="Encoder Audio" Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Impostazioni codifica audio (se presente)" Basic.Settings.Output.Adv.FFmpeg.MuxerSettings="Impostazioni Muxer (se possibile)" Basic.Settings.Output.Adv.FFmpeg.GOPSize="Intervallo fotogrammi chiave (fotogrammi)" -Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat="Mostra tutti i codec (anche se potenzialmente incompatibili)" +Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat="Visualizza tutti i codec (anche se potenzialmente incompatibili)" FilenameFormatting.completer="%CCYY-%MM-%DD %hh-%mm-%ss\n%YY-%MM-%DD %hh-%mm-%ss\n%Y-%m-%d %H-%M-%S\n%y-%m-%d %H-%M-%S\n%a %Y-%m-%d %H-%M-%S\n%A %Y-%m-%d %H-%M-%S\n%Y-%b-%d %H-%M-%S\n%Y-%B-%d %H-%M-%S\n%Y-%m-%d %I-%M-%S-%p\n%Y-%m-%d %H-%M-%S-%z\n%Y-%m-%d %H-%M-%S-%Z" @@ -525,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (scalatura affilata, 32 ca Basic.Settings.Audio="Audio" Basic.Settings.Audio.SampleRate="Ratio di campionamento" Basic.Settings.Audio.Channels="Canali" +Basic.Settings.Audio.MeterDecayRate="Tasso di decadimento audio" +Basic.Settings.Audio.MeterDecayRate.Fast="Veloce" +Basic.Settings.Audio.MeterDecayRate.Medium="Medio (Tipo 1 PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Lento (Tipo 2 PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="ATTENZIONE: L'audio Surround è attivo." +Basic.Settings.Audio.MultichannelWarning="Per lo streaming, accertati che il servizio di streaming supporti sia integrazione che riproduzione di suono surround. Twitch, Facebook 360 Live, Mixer RTMP e Smashcast sono esempi di servizi in cui il suono surround è completamente supportato. Anche se sia Facebook Live che YouTube Live implementano il suono surround, Facebook Live lo converte in stereo, mentre YouTube Live ne riproduce solo due canali.\n\nI filtri di OBS Studio sono compatibili con il suono surround, anche se il supporto per il plugin VST non è garantito." +Basic.Settings.Audio.MultichannelWarning.Title="Abilitare l'audio surround?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Sei sicuro di voler attivare l'audio surround?" Basic.Settings.Audio.DesktopDevice="Dispositivo audio desktop" Basic.Settings.Audio.DesktopDevice2="Dispositivo audio desktop 2" Basic.Settings.Audio.AuxDevice="Dispositivo audio mic/ausiliario" @@ -541,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Priorità del processo" Basic.Settings.Advanced.General.ProcessPriority.High="Alta" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Superiore al normale" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normale" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Inferiore al normale" Basic.Settings.Advanced.General.ProcessPriority.Idle="Bassa" Basic.Settings.Advanced.FormatWarning="Attenzione: I formati colore diversi da NV12 sono principalmente pensati per la registrazione, e non sono consigliati durante le dirette. Lo streaming può avere un utilizzo maggiore delle CPU a causa della conversione del formato del colore." Basic.Settings.Advanced.Audio.BufferingTime="Tempo di buffer audio" @@ -551,7 +666,8 @@ Basic.Settings.Advanced.Video.ColorRange.Partial="Parziale" Basic.Settings.Advanced.Video.ColorRange.Full="Intero" Basic.Settings.Advanced.Audio.MonitoringDevice="Dispositivo monitor audio" Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Predefinito" -Basic.Settings.Advanced.StreamDelay="Ritardo Diretta" +Basic.Settings.Advanced.Audio.DisableAudioDucking="Disabilita ducking audio di Windows" +Basic.Settings.Advanced.StreamDelay="Ritardo diretta" Basic.Settings.Advanced.StreamDelay.Duration="Durata (secondi)" Basic.Settings.Advanced.StreamDelay.Preserve="Preserva il punto di taglio (aumenta ritardo) durante la riconnessione" Basic.Settings.Advanced.StreamDelay.MemoryUsage="Utilizzo di memoria stimato: %1 MB" @@ -572,15 +688,15 @@ Basic.AdvAudio.Monitoring.MonitorOnly="Solo monitor (uscita silenziata)" Basic.AdvAudio.Monitoring.Both="Monitor e uscita" Basic.AdvAudio.AudioTracks="Tracce" -Basic.Settings.Hotkeys="Tasti di scelta rapida" +Basic.Settings.Hotkeys="Scorciatoie" Basic.Settings.Hotkeys.Pair="La combinazione di chiavi condivisa con '%1' funziona da commutatore" Basic.Hotkeys.SelectScene="Passa alla scena" -Basic.SystemTray.Show="Mostra" -Basic.SystemTray.Hide="Nascondi" +Basic.SystemTray.Show="Visualizza la finestra" +Basic.SystemTray.Hide="Nascondi la finestra" -Basic.SystemTray.Message.Reconnecting="Disconnesso. Riconnessione..." +Basic.SystemTray.Message.Reconnecting="Disconnesso. Riconnessione in corso..." Hotkeys.Insert="Ins" Hotkeys.Delete="Canc" @@ -588,9 +704,9 @@ Hotkeys.Home="Home" Hotkeys.End="Fine" Hotkeys.PageUp="Pagina sù" Hotkeys.PageDown="Pagina giù" -Hotkeys.NumLock="Num Lock" -Hotkeys.ScrollLock="Scroll Lock" -Hotkeys.CapsLock="Caps Lock" +Hotkeys.NumLock="Bloc Num" +Hotkeys.ScrollLock="Blocco Scorr" +Hotkeys.CapsLock="Bloc Maiusc" Hotkeys.Backspace="Backspace" Hotkeys.Tab="Tab" Hotkeys.Print="Stampa" @@ -601,21 +717,21 @@ Hotkeys.Up="Sù" Hotkeys.Down="Giù" Hotkeys.Windows="Windows" Hotkeys.Super="Super" -Hotkeys.Menu="Menù" -Hotkeys.Space="Barra Spaziatrice" +Hotkeys.Menu="Menu" +Hotkeys.Space="Barra spaziatrice" Hotkeys.NumpadNum="TastNum %1" Hotkeys.NumpadMultiply="Moltiplicatore TastNum" Hotkeys.NumpadDivide="TastNum Diviso" Hotkeys.NumpadAdd="TastNum Più" Hotkeys.NumpadSubtract="TastNum Meno" Hotkeys.NumpadDecimal="Decimale TastNum" -Hotkeys.AppleKeypadNum="%1 (Tastiera)" -Hotkeys.AppleKeypadMultiply="* (Tastiera)" -Hotkeys.AppleKeypadDivide="/ (Tastiera)" -Hotkeys.AppleKeypadAdd="+ (Tastiera)" -Hotkeys.AppleKeypadSubtract="- (Tastiera)" -Hotkeys.AppleKeypadDecimal=". (Tastiera)" -Hotkeys.AppleKeypadEqual="= (Tastiera)" +Hotkeys.AppleKeypadNum="%1 (Tastierino)" +Hotkeys.AppleKeypadMultiply="* (Tastierino)" +Hotkeys.AppleKeypadDivide="/ (Tastierino)" +Hotkeys.AppleKeypadAdd="+ (Tastierino)" +Hotkeys.AppleKeypadSubtract="- (Tastierino)" +Hotkeys.AppleKeypadDecimal=". (Tastierino)" +Hotkeys.AppleKeypadEqual="= (Tastierino)" Hotkeys.MouseButton="Mouse %1" Mute="Silenzia" diff --git a/UI/data/locale/ja-JP.ini b/UI/data/locale/ja-JP.ini index dce421a..f1f4da4 100644 --- a/UI/data/locale/ja-JP.ini +++ b/UI/data/locale/ja-JP.ini @@ -28,23 +28,28 @@ Browse="参照" Mono="モノラル" Stereo="ステレオ" DroppedFrames="ドロップしたフレーム %1 (%2%)" +StudioProgramProjector="全画面プロジェクター (プログラム)" PreviewProjector="全画面プロジェクター (プレビュー)" SceneProjector="全画面プロジェクター (シーン)" SourceProjector="全画面プロジェクター (ソース)" +StudioProgramWindow="ウィンドウ付きプロジェクター (プログラム)" PreviewWindow="ウィンドウ プロジェクター (プレビュー)" SceneWindow="ウィンドウ プロジェクター (シーン)" SourceWindow="ウィンドウ プロジェクター (ソース)" +MultiviewProjector="マルチビュー (フルスクリーン)" +MultiviewWindowed="マルチビュー (ウィンドウ)" Clear="クリア" Revert="元に戻す" Show="表示" Hide="非表示" +UnhideAll="すべて再表示" Untitled="無題" New="新規" Duplicate="複製" Enable="有効にする" DisableOSXVSync="OSX の V-Sync を無効にする" ResetOSXVSyncOnExit="終了時に OSX の V-Sync をリセットする" -HighResourceUsage="エンコードが高負荷です! ビデオ設定を下げるかより高速のエンコードプリセットの使用を検討してください。" +HighResourceUsage="エンコードが高負荷です! 映像設定を下げるかより高速のエンコードプリセットの使用を検討してください。" Transition="トランジション" QuickTransitions="クイックトランジション" Left="左" @@ -66,6 +71,13 @@ PasteDuplicate="貼り付け (複製)" RemuxRecordings="録画の再多重化" Next="次へ" Back="戻る" +Defaults="既定値" +HideMixer="ミキサーを非表示" +TransitionOverride="トランジションオーバーライド" +None="未設定" +StudioMode.Preview="プレビュー" +StudioMode.Program="番組" +ShowInMultiview="マルチビューで表示" AlreadyRunning.Title="OBSは既に実行中です" AlreadyRunning.Text="OBSは既に実行されています! この操作を行うつもりがない限り、新しいインスタンスを実行する前に既存のOBSインスタンスを終了してください。OBSがシステムトレイに最小化されるように設定されている場合は、まだ実行中であるかどうかを確認してください。" @@ -300,6 +312,10 @@ AddProfile.Text="プロファイルの名前を入力してください" RenameProfile.Title="プロファイルの名前を変更" +Basic.Main.MixerRename.Title="音声ソースの名前を変更" +Basic.Main.MixerRename.Text="音声ソースの名前を入力してください" + + Basic.Main.PreviewDisabled="プレビューは現在無効です" Basic.SourceSelect="ソースを作成/選択" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="ソースを追加するには少なくとも1 Basic.Main.Scenes="シーン" Basic.Main.Sources="ソース" +Basic.Main.Controls="コントロール" Basic.Main.Connecting="接続中..." Basic.Main.StartRecording="録画開始" Basic.Main.StartReplayBuffer="リプレイバッファー開始" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="オーディオの詳細プロパティ(&A)" Basic.MainMenu.View="表示(&V)" Basic.MainMenu.View.Toolbars="ツールバー(&T)" +Basic.MainMenu.View.Docks="ドック" +Basic.MainMenu.View.Docks.ResetUI="UIをリセット" +Basic.MainMenu.View.Docks.LockUI="UIをロック" Basic.MainMenu.View.Toolbars.Listboxes="リストボックス(&L)" Basic.MainMenu.View.SceneTransitions="シーントランジション(&C)" Basic.MainMenu.View.StatusBar="ステータスバー(&S)" +Basic.MainMenu.View.Fullscreen.Interface="全画面インターフェイス" Basic.MainMenu.SceneCollection="シーンコレクション(&S)" Basic.MainMenu.Profile="プロファイル(&P)" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="シーンコレクションは既に存 Basic.MainMenu.Tools="ツール(&T)" Basic.MainMenu.Help="ヘルプ(&H)" +Basic.MainMenu.Help.HelpPortal="ヘルプポータル(&P)" Basic.MainMenu.Help.Website="ウェブサイト(&W)" Basic.MainMenu.Help.Logs="ログファイル(&L)" Basic.MainMenu.Help.Logs.ShowLogs="ログファイルを表示(&S)" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="システムトレイ" Basic.Settings.General.SysTrayWhenStarted="起動時にシステムトレイへ最小化" Basic.Settings.General.SystemTrayHideMinimize="タスクバーの代わりにシステムトレイに常に最小化する" Basic.Settings.General.SaveProjectors="終了時にプロジェクターを保存する" +Basic.Settings.General.SwitchOnDoubleClick="ダブルクリックしたときにシーンに遷移" +Basic.Settings.General.StudioPortraitLayout="縦長/垂直レイアウトを有効にする" +Basic.Settings.General.MultiviewLayout="マルチビューレイアウト" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="水平, 上" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="水平, 下" +Basic.Settings.General.MultiviewLayout.Vertical.Left="垂直, 左" +Basic.Settings.General.MultiviewLayout.Vertical.Right="垂直, 右" Basic.Settings.Stream="配信" Basic.Settings.Stream.StreamType="配信種別" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="ランチョス (先鋭化スケ Basic.Settings.Audio="音声" Basic.Settings.Audio.SampleRate="サンプリングレート" Basic.Settings.Audio.Channels="チャンネル" +Basic.Settings.Audio.MeterDecayRate="音声メーターの減衰率" +Basic.Settings.Audio.MeterDecayRate.Fast="速い" +Basic.Settings.Audio.MeterDecayRate.Medium="中 (タイプ I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="遅い (タイプ II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="警告: サラウンド音声が有効です。" +Basic.Settings.Audio.MultichannelWarning="配信する場合、配信サービスがサラウンド音声の取り込みと再生の両方をサポートしているかどうかを確認してください。Twitch、Facebook 360 Live、Mixer RTMP、Smashcastは、サラウンド音声が完全にサポートされている例です。しかしFacebook LiveとYouTube Liveはどちらもサラウンド取り込みを受信しますが、Facebook Liveはステレオにダウンミックスし、YouTube Liveは2チャンネルのみしか再生できません。\n\nVSTプラグインのサポートは保証されていませんが、OBS音声フィルタはサラウンド音声と互換性があります。" +Basic.Settings.Audio.MultichannelWarning.Title="サラウンド音声を有効にしますか?" +Basic.Settings.Audio.MultichannelWarning.Confirm="サラウンド音声を有効にしてもよろしいですか?" Basic.Settings.Audio.DesktopDevice="デスクトップ音声デバイス" Basic.Settings.Audio.DesktopDevice2="デスクトップ音声デバイス 2" Basic.Settings.Audio.AuxDevice="マイク音声デバイス" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="プロセスの優先度" Basic.Settings.Advanced.General.ProcessPriority.High="高" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="通常以上" Basic.Settings.Advanced.General.ProcessPriority.Normal="通常" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="通常以下" Basic.Settings.Advanced.General.ProcessPriority.Idle="低" Basic.Settings.Advanced.FormatWarning="警告: NV12以外のカラーフォーマットは主に録画を想定しており、配信には非推奨です。配信ではカラーフォーマットの変換による CPU使用率の増加が発生する可能性があります。" Basic.Settings.Advanced.Audio.BufferingTime="音声バッファー処理時間" diff --git a/UI/data/locale/ka-GE.ini b/UI/data/locale/ka-GE.ini new file mode 100644 index 0000000..2b8684d --- /dev/null +++ b/UI/data/locale/ka-GE.ini @@ -0,0 +1,147 @@ + + +OK="კაი" +Cancel="გაუქმება" +Close="დახურვა" +Save="შენახვა" +Discard="გაუქმება" +Disable="გამორთვა" +Yes="დიახ" +No="არა" +Add="დამატება" +Remove="წაშლა" +Rename="გადარქმევა" +Filters="ფილტრები" +MoveUp="ზევით" +MoveDown="ქვევით" +Settings="პარამეტრები" +Name="სახელი" +Exit="გასვლა" +Mixer="მიქშერი" +Browse="მოძიება" +Mono="მონო" +Stereo="სტერეო" +Show="ჩვენება" +Hide="დამალვა" +UnhideAll="ყველაფრის გამოჩენა" +Untitled="უსათაურო" +New="ახალი" +Duplicate="დუბლირება" +Enable="ჩართვა" +DisableOSXVSync="OSX V-Sync-ის გამორთვა" +ResetOSXVSyncOnExit="OSX V-Sync-ის გადატვირთვა გასვლისას" +Minutes="წუთი" +Seconds="წამი" + + + + + + +Basic.Stats.Status.Inactive="არააქტიური" + +Updater.UpdateNow="განაახლე ახლა" + + +Basic.TransitionDuration="ხანგრძლივობა" + + + + + + + +ConfirmExit.Title="OBS-დან გასვლა?" + + + + + + +LogReturnDialog.CopyURL="ბმულის კოპირება" + +LicenseAgreement.Exit="გასვლა" + + + +Basic.AuxDevice2="Mic/Aux 2" +Basic.AuxDevice3="Mic/Aux 3" +Basic.AuxDevice4="Mic/Aux 4" + + + + +Deinterlacing.Retro="Retro" +Deinterlacing.Blend="Blend" +Deinterlacing.Blend2x="Blend 2x" +Deinterlacing.Linear2x="Linear 2x" +Deinterlacing.Yadif="Yadif" +Deinterlacing.Yadif2x="Yadif 2x" + + + + + + + + + + + + + + + + + +Basic.TransformWindow.Position="პოზიცია" + + + + + + + +Basic.MainMenu.View.Docks.ResetUI="UI-ს გადატვირთვა" + + + + + +Basic.Settings.General.Language="ენა" + + + + +Basic.Settings.Output.Adv.Recording.Type="ტიპი" +Basic.Settings.Output.Adv.Recording.Type.Standard="სტანდარტული" +Basic.Settings.Output.Adv.FFmpeg.SaveFilter.All="ყველა ფაილი" +Basic.Settings.Output.Adv.FFmpeg.FormatVideo="ვიდეო" + + + +Basic.Settings.Video.FPS="FPS" +Basic.Settings.Video.Denominator="მნიშვნელი" + + +Basic.Settings.Audio.Channels="არხები" + + +Basic.AdvAudio.Name="სახელი" + + + + + +Hotkeys.CapsLock="Caps Lock" +Hotkeys.Backspace="Backspace" +Hotkeys.Pause="პაუზა" +Hotkeys.Super="Super" +Hotkeys.Menu="მენიუ" +Hotkeys.Space="Space" +Hotkeys.NumpadNum="Numpad %1" + + + + + diff --git a/UI/data/locale/ko-KR.ini b/UI/data/locale/ko-KR.ini index badd892..fb684d8 100644 --- a/UI/data/locale/ko-KR.ini +++ b/UI/data/locale/ko-KR.ini @@ -28,16 +28,21 @@ Browse="찾아보기" Mono="모노" Stereo="스테레오" DroppedFrames="손실된 프레임 %1 (%2%)" +StudioProgramProjector="전체화면 프로젝터 (프로그램)" PreviewProjector="전체화면 프로젝터 (미리보기)" SceneProjector="전체화면 프로젝터 (장면)" SourceProjector="전체화면 프로젝터 (소스)" +StudioProgramWindow="창 프로젝터 (프로그램)" PreviewWindow="창 프로젝터 (미리보기)" SceneWindow="창 프로젝터 (장면)" SourceWindow="창 프로젝터 (소스)" +MultiviewProjector="다중화면 (전체화면)" +MultiviewWindowed="다중화면 (창)" Clear="단축키 해제" Revert="되돌리기" Show="보이기" Hide="숨기기" +UnhideAll="모두 표시" Untitled="제목없음" New="새로 만들기" Duplicate="복제" @@ -66,6 +71,13 @@ PasteDuplicate="붙여넣기 (중복)" RemuxRecordings="녹화본 재다중화" Next="다음" Back="이전" +Defaults="기본값" +HideMixer="믹서에 숨기기" +TransitionOverride="이 전환을 우선 적용" +None="없음" +StudioMode.Preview="미리보기" +StudioMode.Program="프로그램" +ShowInMultiview="다중화면으로 표시" AlreadyRunning.Title="OBS가 이미 실행 중입니다" AlreadyRunning.Text="OBS가 이미 실행 중입니다! 의도한 것이 아니라면 새로운 OBS를 실행하기 전에 이미 동작 중인 프로그램을 종료하십시오. OBS가 시스템 트레이에 최소화되어 있는지도 확인하십시오." @@ -172,7 +184,7 @@ Basic.RemoveTransition="설정 가능한 화면 전환 제거" Basic.TransitionProperties="화면 전환 속성" Basic.SceneTransitions="장면 전환" Basic.TransitionDuration="지속 기간" -Basic.TogglePreviewProgramMode="스튜디오 모드" +Basic.TogglePreviewProgramMode="편집 방식" TransitionNameDlg.Text="이 화면 전환의 이름을 입력하세요" TransitionNameDlg.Title="화면 전환 이름" @@ -247,7 +259,7 @@ Remux.FileExists="대상 파일이 존재합니다. 이 것으로 대체하겠 Remux.ExitUnfinishedTitle="재다중화 작업 중" Remux.ExitUnfinished="재다중화 작업이 끝나지 않았습니다. 지금 멈추면 대상 파일은 사용할 수 없는 상태가 됩니다.\n정말로 작업을 중단하겠습니까?" -UpdateAvailable="새 업데이트 사용 가능" +UpdateAvailable="새 판올림 사용 가능" UpdateAvailable.Text="버전 %1. %2. %3이 나왔습니다. 다운로드" Basic.DesktopDevice1="데스크탑 오디오" @@ -300,6 +312,10 @@ AddProfile.Text="프로파일 이름을 입력하세요" RenameProfile.Title="프로파일 이름 바꾸기" +Basic.Main.MixerRename.Title="오디오 소스 이름 바꾸기" +Basic.Main.MixerRename.Text="이 오디오 소스의 이름을 입력하세요" + + Basic.Main.PreviewDisabled="미리보기는 현재 비활성화되어 있습니다" Basic.SourceSelect="소스 만들기/선택" @@ -332,8 +348,8 @@ Basic.StatusBar.Reconnecting="연결이 끊겼습니다, %2 초에 다시 연결 Basic.StatusBar.AttemptingReconnect="재접속 중... (시도 %1)" Basic.StatusBar.ReconnectSuccessful="재접속 성공" Basic.StatusBar.Delay="지연 (%1 초)" -Basic.StatusBar.DelayStartingIn="지연(시작까지 %1초 남음)" -Basic.StatusBar.DelayStoppingIn="지연(중단까지 %1초 남음)" +Basic.StatusBar.DelayStartingIn="지연 (시작까지 %1초 남음)" +Basic.StatusBar.DelayStoppingIn="지연 (중단까지 %1초 남음)" Basic.StatusBar.DelayStartingStoppingIn="지연 (중단까지 %1초, %2초 내 시작)" Basic.Filters="필터" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="소스를 추가하기 위해서는 최소 하나 Basic.Main.Scenes="장면 목록:" Basic.Main.Sources="소스 목록:" +Basic.Main.Controls="제어" Basic.Main.Connecting="연결 중..." Basic.Main.StartRecording="녹화 시작" Basic.Main.StartReplayBuffer="리플레이 버퍼 시작" @@ -387,7 +404,7 @@ Basic.Main.StopReplayBuffer="리플레이 버퍼 중단" Basic.Main.StoppingReplayBuffer="리플레이 버퍼를 멈추고 있습니다..," Basic.Main.StopStreaming="방송 중단" Basic.Main.StoppingStreaming="방송을 중지합니다..." -Basic.Main.ForceStopStreaming="방송 중지(지연된 분량도 마무리없이 즉시 송출 중단)" +Basic.Main.ForceStopStreaming="방송 중지 (지연된 분량도 마무리없이 즉시 송출 중단)" Basic.MainMenu.File="파일(&F)" Basic.MainMenu.File.Export="내보내기(&E)" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="오디오 고급 설정(&A)" Basic.MainMenu.View="보기(&V)" Basic.MainMenu.View.Toolbars="도구 모음(&T)" +Basic.MainMenu.View.Docks="독" +Basic.MainMenu.View.Docks.ResetUI="사용자 인터페이스 초기화" +Basic.MainMenu.View.Docks.LockUI="사용자 인터페이스 잠금" Basic.MainMenu.View.Toolbars.Listboxes="목록 상자(&L)" Basic.MainMenu.View.SceneTransitions="장면 전환(&C)" Basic.MainMenu.View.StatusBar="상태 표시줄(&S)" +Basic.MainMenu.View.Fullscreen.Interface="전체화면 인터페이스" Basic.MainMenu.SceneCollection="장면 모음(&S)" Basic.MainMenu.Profile="프로파일(&P)" @@ -448,13 +469,14 @@ Basic.MainMenu.SceneCollection.Exists="그 장면 모음은 이미 존재합니 Basic.MainMenu.Tools="도구(&T)" Basic.MainMenu.Help="도움말(&H)" +Basic.MainMenu.Help.HelpPortal="도움말 및 포털" Basic.MainMenu.Help.Website="웹사이트 방문(&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="업데이트 확인" +Basic.MainMenu.Help.CheckForUpdates="판올림 확인" Basic.Settings.ProgramRestart="설정을 적용하려면 프로그램을 다시 시작해야 합니다." Basic.Settings.ConfirmTitle="변경사항 확인" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="시스템 트레이" Basic.Settings.General.SysTrayWhenStarted="시작할 때 시스템 트레이로 최소화" Basic.Settings.General.SystemTrayHideMinimize="작업 표시줄 대신 시스템 트레이에 항상 최소화" Basic.Settings.General.SaveProjectors="종료 시 프로젝터 저장" +Basic.Settings.General.SwitchOnDoubleClick="더블클릭 시 장면으로 전환" +Basic.Settings.General.StudioPortraitLayout="프로젝터를 수직으로 배열" +Basic.Settings.General.MultiviewLayout="다중화면 배치" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="수평, 위" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="수평, 아래" +Basic.Settings.General.MultiviewLayout.Vertical.Left="수직, 왼쪽" +Basic.Settings.General.MultiviewLayout.Vertical.Right="수직, 오른쪽" Basic.Settings.Stream="방송" Basic.Settings.Stream.StreamType="방송 형식" @@ -493,7 +522,7 @@ Basic.Settings.Output.Encoder="인코더" Basic.Settings.Output.SelectDirectory="녹화 경로 선택" Basic.Settings.Output.SelectFile="녹화 파일 선택" Basic.Settings.Output.EnforceBitrate="방송 서비스의 비트레이트 제한 적용" -Basic.Settings.Output.Mode="출력 모드" +Basic.Settings.Output.Mode="출력 방식" Basic.Settings.Output.Mode.Simple="단순" Basic.Settings.Output.Mode.Adv="고급" Basic.Settings.Output.Mode.FFmpeg="FFmpeg 출력" @@ -529,7 +558,7 @@ Basic.Settings.Output.Reconnect="자동으로 다시 연결" Basic.Settings.Output.RetryDelay="재접속 간격 (초 단위)" Basic.Settings.Output.MaxRetries="최대 재시도 횟수" Basic.Settings.Output.Advanced="고급 인코더 설정 활성화" -Basic.Settings.Output.EncoderPreset="인코더 사전설정 (빠를수록 CPU 부담 적음)" +Basic.Settings.Output.EncoderPreset="인코더 사전설정 (빠를수록 부담 적으나 화질 나쁨)" Basic.Settings.Output.CustomEncoderSettings="사용자 임의 인코더 설정" Basic.Settings.Output.CustomMuxerSettings="사용자 임의 다중화 설정" Basic.Settings.Output.NoSpaceFileName="여백없이 파일 이름 짓기" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (영상 크기 변경을 Basic.Settings.Audio="오디오" Basic.Settings.Audio.SampleRate="샘플 레이트" Basic.Settings.Audio.Channels="채널" +Basic.Settings.Audio.MeterDecayRate="오디오 측정기 감퇴 속도" +Basic.Settings.Audio.MeterDecayRate.Fast="빠름" +Basic.Settings.Audio.MeterDecayRate.Medium="중간 (Type I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="느림 (Type II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="경고: 서라운드 음향이 켜져 있습니다." +Basic.Settings.Audio.MultichannelWarning="방송 중이라면 서비스에서 서라운드 음향에 대한 입력 및 재생을 지원하는지 확인하세요. 트위치, 페이스북 360 라이브, Mixer RTMP, Smashcast 는 해당 기능을 사용할 수 있습니다. 페이스북 Live와 유튜브 Live 서비스는 입력은 할 수 있지만 스테레오로 전환하며 유튜브 Live는 오로지 2채널로만 재생합니다.\n\nOBS 오디오 필터는 서라운드 음향을 지원하지만 VST 플러그인 지원은 보장하지 않습니다." +Basic.Settings.Audio.MultichannelWarning.Title="서라운드 음향을 활성화할까요?" +Basic.Settings.Audio.MultichannelWarning.Confirm="정말로 서라운드 음향을 사용하겠습니까?" Basic.Settings.Audio.DesktopDevice="데스크탑 오디오 장치" Basic.Settings.Audio.DesktopDevice2="데스크탑 오디오 장치 2" Basic.Settings.Audio.AuxDevice="마이크/보조 오디오 장치" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="프로세스 우선순위 설 Basic.Settings.Advanced.General.ProcessPriority.High="실시간" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="높음" Basic.Settings.Advanced.General.ProcessPriority.Normal="보통" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="낮은 우선 순위" Basic.Settings.Advanced.General.ProcessPriority.Idle="낮음" Basic.Settings.Advanced.FormatWarning="경고: NV12 이외의 색상 형식은 주로 녹화를 위한 것이며, 방송에 적합하지 않습니다. 이대로 방송을 하게 되면 형식 전환을 위해 CPU 사용량이 증가할 수 있습니다." Basic.Settings.Advanced.Audio.BufferingTime="오디오 버퍼링 시간" @@ -636,7 +674,7 @@ Basic.Settings.Advanced.StreamDelay.MemoryUsage="예상되는 메모리 사용 Basic.Settings.Advanced.Network="네트워크" Basic.Settings.Advanced.Network.BindToIP="IP에 고정" Basic.Settings.Advanced.Network.EnableNewSocketLoop="새로운 네트워크 코드 활성화" -Basic.Settings.Advanced.Network.EnableLowLatencyMode="짧은 지연시간 모드" +Basic.Settings.Advanced.Network.EnableLowLatencyMode="짧은 지연시간 방식 사용" Basic.AdvAudio="오디오 고급 설정" Basic.AdvAudio.Name="이름" diff --git a/UI/data/locale/lt-LT.ini b/UI/data/locale/lt-LT.ini index b9282eb..8e59372 100644 --- a/UI/data/locale/lt-LT.ini +++ b/UI/data/locale/lt-LT.ini @@ -35,6 +35,7 @@ Clear="Išvalyti" Revert="Atstatyti" Show="Parodyti" Hide="Paslėpti" +UnhideAll="Atslėpti visus" Untitled="Nepavadintas" New="Naujas" Duplicate="Dubliuoti" @@ -48,6 +49,11 @@ Left="Iš kairės" Right="Iš dešinės" Top="Iš viršaus" Bottom="Iš apačios" +Reset="Atstatyti" +Hours="Valandos" +Minutes="Minutės" +Seconds="Sekundės" +Import="Importuoti" @@ -183,6 +189,8 @@ AddProfile.Text="Įveskite pasirinktą profilio pavadinimą" RenameProfile.Title="Pervardinti profilį" + + Basic.Main.PreviewDisabled="Peržiūra šiuo metu išjungta" Basic.SourceSelect="Sukurti/Pasirinkti šaltinį" @@ -277,14 +285,44 @@ Basic.MainMenu.Edit.Order.MoveToBottom="Perkelti į apačią" +Basic.Settings.Advanced.General.ProcessPriority.High="Aukštas" +Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Virš vidutionio" +Basic.Settings.Advanced.General.ProcessPriority.Normal="Vidutinis" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Žemiau vidutinio" +Basic.Settings.Advanced.General.ProcessPriority.Idle="Tolygus" +Basic.Settings.Advanced.Audio.BufferingTime="Garso Sušvelninimo Laikas" +Basic.Settings.Advanced.Video.ColorFormat="Spalvos formatas" +Basic.Settings.Advanced.Video.ColorSpace="YUV Spalvų Erdvė" +Basic.Settings.Advanced.Video.ColorRange="YUV Spalvų Diapazonas" +Basic.Settings.Advanced.Video.ColorRange.Partial="Dalinis" +Basic.Settings.Advanced.Video.ColorRange.Full="Pilnas" +Basic.Settings.Advanced.Audio.MonitoringDevice="Garso prietaisas" +Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Numatytas" +Basic.Settings.Advanced.StreamDelay="Srauto Vėlavimas" +Basic.Settings.Advanced.StreamDelay.Duration="Trukmė (sekundėmis)" +Basic.Settings.Advanced.StreamDelay.Preserve="Išsaugoti apkarpymo tašką (padidina vėlavimą) įrašinėjant" +Basic.Settings.Advanced.StreamDelay.MemoryUsage="Numatomas atminties naudojimas: %1 MB" +Basic.Settings.Advanced.Network="Tinklas" +Basic.Settings.Advanced.Network.BindToIP="Priskirti prie IP" + +Basic.AdvAudio.Name="Prietaisas" +Basic.AdvAudio.Volume="Garsumas (%)" +Hotkeys.Print="Spausdinti" +Hotkeys.Menu="Meniu" +Hotkeys.Space="Tarpas" + +Mute="Užtildyti" +Unmute="Atitildyti" +Push-to-mute="Nutildyti paspaudus" +Push-to-talk="Kalbėti paspaudus" - - +FinalScene.Title="Ištrinti sceną" +FinalScene.Text="Turi būti bent viena scena." diff --git a/UI/data/locale/ms-MY.ini b/UI/data/locale/ms-MY.ini index 4cfd606..c0b7375 100644 --- a/UI/data/locale/ms-MY.ini +++ b/UI/data/locale/ms-MY.ini @@ -191,6 +191,8 @@ AddProfile.Text="Sila taip nama profil" RenameProfile.Title="Namakan Semula Profil" + + Basic.Main.PreviewDisabled="Pratonton kini ditutup" Basic.SourceSelect="Cipta/Pilih Sumber" diff --git a/UI/data/locale/nb-NO.ini b/UI/data/locale/nb-NO.ini index a4fdc59..744b7e5 100644 --- a/UI/data/locale/nb-NO.ini +++ b/UI/data/locale/nb-NO.ini @@ -28,20 +28,28 @@ Browse="Bla gjennom" Mono="Mono" Stereo="Stereo" DroppedFrames="Bildetap %2% (%1)" +StudioProgramProjector="Fullskjermprojektor (Program)" PreviewProjector="Fullskjermprojektor (forhåndsvisning)" -SceneProjector="Fullskjermprojektor (scene)" +SceneProjector="Fullskjermsprojektor (scene)" SourceProjector="Fullskjermprojektor (kilde)" +StudioProgramWindow="Projektorvindu (Program)" +PreviewWindow="Projektor i vindu (forhåndsvisning)" +SceneWindow="Projektor i vindu (scene)" +SourceWindow="Projektor i vindu (kilde)" +MultiviewProjector="Multiview (Fullskjerm)" +MultiviewWindowed="Multiview (Vindu)" Clear="Tøm" Revert="Tilbakestill" Show="Vis" Hide="Skjul" +UnhideAll="Vis alle" Untitled="Uten navn" New="Ny" Duplicate="Dupliser" Enable="Aktiver" DisableOSXVSync="Deaktiver OS X v-sync" -ResetOSXVSyncOnExit="Tilbakestill OS X v-sync ved avsluttning" -HighResourceUsage="Koder overbelastet! Vurder nedjustering av videokvaliteten eller en raskere forhåndsinstilling for koderen." +ResetOSXVSyncOnExit="Tilbakestill macOS sin V-sync ved avslutning" +HighResourceUsage="Koderen er overbelastet! Vurder nedjustering av videokvaliteten eller en raskere forhåndsinnstilling for koderen." Transition="Overgang" QuickTransitions="Raske overganger" Left="Venstre" @@ -63,37 +71,117 @@ PasteDuplicate="Lim inn (Duplikat)" RemuxRecordings="Remux Opptak" Next="Neste" Back="Tilbake" +Defaults="Standardinnstillinger" +HideMixer="Skjul i mikser" +TransitionOverride="Tilsett Overgang" +None="Ingen" +StudioMode.Preview="Forhåndsvisning" +StudioMode.Program="Program" +ShowInMultiview="Vis i Multiview" AlreadyRunning.Title="OBS kjører allerede" +AlreadyRunning.Text="OBS kjører allerede! Med mindre du ikke mente dette, vennligst lukk alle eksisterende kjørende tilfeller av OBS før du kjører noen nye. Hvis du har satt OBS til å minimere til systemkurven, vennligst sjekk om den fortsatt kjører der." +AlreadyRunning.LaunchAnyway="Start uansett" +Copy.Filters="Kopier filtre" +Paste.Filters="Lim inn filtre" +BandwidthTest.Region="Region" +BandwidthTest.Region.US="USA" +BandwidthTest.Region.EU="Europa" +BandwidthTest.Region.Asia="Asia" +BandwidthTest.Region.Other="Andre" +Basic.FirstStartup.RunWizard="Vil du kjøre veiviseren for automatisk konfigurasjon? Du kan også manuelt konfigurere innstillingene ved å klikke på Innstillinger-knappen i hovedvinduet." +Basic.FirstStartup.RunWizard.BetaWarning="(Merk: veiviseren for automatisk konfigurasjon er i beta)" +Basic.FirstStartup.RunWizard.NoClicked="Hvis du ombestemmer deg, kan du kjøre veiviseren for automatisk konfigurasjon fra Verktøy-menyen." +Basic.AutoConfig="Auto-oppsettsveiviser" +Basic.AutoConfig.Beta="Auto-oppsettsveiviser (Beta)" +Basic.AutoConfig.ApplySettings="Bruk innstillinger" +Basic.AutoConfig.StartPage="Bruksinformasjon" +Basic.AutoConfig.StartPage.SubTitle="Angi hva du vil bruke programmet til" +Basic.AutoConfig.StartPage.PrioritizeStreaming="Optimaliser for streaming, innspilling er sekundær" +Basic.AutoConfig.StartPage.PrioritizeRecording="Optimaliser for innspilling, jeg skal ikke streame" +Basic.AutoConfig.VideoPage="Videoinnstillinger" +Basic.AutoConfig.VideoPage.SubTitle="Velg de ønskede videoinnstillingene du ønsker å bruke" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Bruk Gjeldende (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="Skjerm %1 (%2x%3)" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="Bruk Gjeldende (%1)" +Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="60 eller 30, men foretrekk 60 når mulig" +Basic.AutoConfig.VideoPage.FPS.PreferHighRes="60 eller 30, men foretrekk høy oppløsning" +Basic.AutoConfig.VideoPage.CanvasExplanation="NB: Oppløsningen på lerretet er ikke nødvendigvis den samme oppløsningen som du vil kringkaste eller ta opp med. Den faktiske kringkastings- eller opptaks-oppløsningen kan bli nedskalert fra størrelsen på lerretet for å redusere ressursbruken eller bitfrekvensen." +Basic.AutoConfig.StreamPage="Strømmens informasjon" +Basic.AutoConfig.StreamPage.SubTitle="Vennligst skriv inn strømmens informasjon" +Basic.AutoConfig.StreamPage.Service="Tjeneste" +Basic.AutoConfig.StreamPage.Service.ShowAll="Vis alle..." +Basic.AutoConfig.StreamPage.Server="Tjener" +Basic.AutoConfig.StreamPage.StreamKey="Strømmenøkkel" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Lenke)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Beregn bitfrekvensen med en båndbreddetest (Kan ta noen minutter)" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Foretrekk maskinvarekoding" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="Maskinvarekoding reduserer mesteparten av CPU-bruken, men kan kreve mer bitrate for å få samme nivå av kvalitet." +Basic.AutoConfig.StreamPage.StreamWarning.Title="Strømmeadvarsel" +Basic.AutoConfig.StreamPage.StreamWarning.Text="Båndbreddetesten kommer til å kringkaste tilfeldig videodata uten lyd til kanalen din. Om du kan, er det anbefalt at du midlertidig skrur av alternativet for å lagre videoer, samt sette kanalen din som privat til testen er ferdig. Vil du fortsette?" +Basic.AutoConfig.TestPage="Sluttresultater" +Basic.AutoConfig.TestPage.SubTitle.Testing="Programmet kjører en rekke tester for å beregne de mest ideelle innstillingene" +Basic.AutoConfig.TestPage.SubTitle.Complete="Testingen er fullført" +Basic.AutoConfig.TestPage.TestingBandwidth="Utfører båndbreddetest. Dette kan ta noen minutter..." +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Kobler til: %1..." +Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Kan ikke koble til noen servere, kontroller Internett-tilkoblingen og prøv på nytt." +Basic.AutoConfig.TestPage.TestingBandwidth.Server="Tester båndbredde til: %1" +Basic.AutoConfig.TestPage.TestingStreamEncoder="Tester kringkastingsenkoder, dette kan ta litt tid..." +Basic.AutoConfig.TestPage.TestingRecordingEncoder="Tester opptaksenkoder, dette kan ta litt tid..." +Basic.AutoConfig.TestPage.TestingRes="Tester oppløsning, dette kan ta noen minutter..." +Basic.AutoConfig.TestPage.TestingRes.Fail="Kunne ikke starte enkoder" +Basic.AutoConfig.TestPage.TestingRes.Resolution="Tester %1x%2 %3 FPS..." +Basic.AutoConfig.TestPage.Result.StreamingEncoder="Kringkastingsenkoder" +Basic.AutoConfig.TestPage.Result.RecordingEncoder="Opptakskoder" +Basic.AutoConfig.TestPage.Result.Header="Programmet har estimert at disse innstillingene er den mest ideelle for deg:" +Basic.AutoConfig.TestPage.Result.Footer="For å bruke disse innstillingene, klikker du Bruk innstillinger. Å konfigurere veiviseren og prøv på nytt, klikker du tilbake. For å konfigurer innstillinger manuelt selv, klikk Avbryt og åpne innstillinger." +Basic.Stats="Statistikk" +Basic.Stats.CPUUsage="CPU-bruk" +Basic.Stats.HDDSpaceAvailable="Lagringsplass tilgjengelig" +Basic.Stats.MemoryUsage="Minnebruk" +Basic.Stats.AverageTimeToRender="Gjennomsnittlig tid for å rendre ett bilde" +Basic.Stats.SkippedFrames="Hoppet over bilder på grunn av problemer med enkoding" +Basic.Stats.MissedFrames="Noen bilder blir borte på grunn av problemer med enkoding" +Basic.Stats.Output.Stream="Strøm" +Basic.Stats.Output.Recording="Tar opp" +Basic.Stats.Status="Status" +Basic.Stats.Status.Recording="Tar opp" +Basic.Stats.Status.Live="DIREKTE" +Basic.Stats.Status.Reconnecting="Kobler til på nytt" +Basic.Stats.Status.Inactive="Inaktiv" +Basic.Stats.DroppedFrames="Tapte bilder (nettverk)" +Basic.Stats.MegabytesSent="Total datamengde ut" +Basic.Stats.Bitrate="Bitfrekvens" Updater.Title="Ny oppdatering tilgjengelig" Updater.Text="Det finnes en ny oppdatering:" Updater.UpdateNow="Oppdater nå" Updater.RemindMeLater="Påminn meg senere" Updater.Skip="Hopp over versjon" -Updater.Running.Title="Programmet er aktiv" +Updater.Running.Title="Programmet er aktivt" Updater.Running.Text="Utganger er aktive, deaktiver alle aktive utganger før du forsøker å oppdatere" Updater.NoUpdatesAvailable.Title="Ingen oppdateringer er tilgjengelig" Updater.NoUpdatesAvailable.Text="Ingen oppdateringer er tilgjengelig" Updater.FailedToLaunch="Kunne ikke starte oppdaterer" Updater.GameCaptureActive.Title="Spillopptak aktivt" +Updater.GameCaptureActive.Text="Spillopptakskrokbiblioteket er for øyeblikket i bruk. Vennligst lukk ethvert spill/program som blir tatt opp (eller start Windows på nytt), og prøv igjen." QuickTransitions.SwapScenes="Bytt forhåndsvisnings-/utgangsscener etter overgang" QuickTransitions.SwapScenesTT="Bytter forhåndsvisnings- og utgangsscenen etter overgang, hvis den originale utgangsscenen fortsatt eksisterer.\nDette vil ikke tilbakestille endringer på den originale utgangsscenen." QuickTransitions.DuplicateScene="Dupliser scene" -QuickTransitions.DuplicateSceneTT="Når denne instillingen er aktiv kan du justere visningen av kilder i den samme scenen uten endring i utgangsvisningen.\nOm du vil endre kildenes instillinger må du aktivere «dupliserte kilder.»\nVed aktivering eller deaktivering av denne instillingen vil utgangsscenen omstilles, om den fortsatt eksisterer." +QuickTransitions.DuplicateSceneTT="Når denne innstillingen er aktiv, kan du justere visningen av kilder i den samme scenen uten å endre utdataen.\nOm du vil endre kildenes innstillinger uten å endre utdataene, må du aktivere «Dupliser kilder».\nVed aktivering eller deaktivering av denne innstillingen vil utgangsscenen omstilles (om den fortsatt eksisterer)." QuickTransitions.EditProperties="Dupliserte kilder" QuickTransitions.EditPropertiesTT="Lar deg justere kilders egenskaper i samme scene uten å endre visning i utgangsscenen.\nKan bare brukes om instillingen «duplisert scene» er aktiv.\nEnkelte kilder (som opptak eller mediekilder) støtter ikke denne funksjonen og kan ikke endres separat.\nOm du aktiverer eller deaktiverer denne instillingen vil omstille utgangsscenen om den fortsatt eksisterer.\n\nAdvarsel: kan kreve mer systemressurser ettersom alle kildene i scenen blir duplisert." QuickTransitions.HotkeyName="Hurtigovergang: %1" Basic.AddTransition="Legg til konfigurerbar overgang" Basic.RemoveTransition="Fjern konfigurerbar overgang" -Basic.TransitionProperties="Egenskaper" +Basic.TransitionProperties="Overgangsegenskaper" Basic.SceneTransitions="Sceneoverganger" Basic.TransitionDuration="Varighet" Basic.TogglePreviewProgramMode="Studiomodus" @@ -111,7 +199,7 @@ NoNameEntered.Title="Vennligst oppgi et gyldig navn" NoNameEntered.Text="Du kan ikke bruke et tomt navn." ConfirmStart.Title="Begynn strømming?" -ConfirmStart.Text="Er du sikker på at du vil begynne strømming?" +ConfirmStart.Text="Er du sikker på at du vil begynne å strømme?" ConfirmStop.Title="Stans strømming?" ConfirmStop.Text="Er du sikker på at du vil stanse strømming?" @@ -125,11 +213,12 @@ ConfirmRemove.TextMultiple="Er du sikker du ønsker å fjerne %1 filer?" Output.StartStreamFailed="Kan ikke starte streaming" Output.StartRecordingFailed="Kan ikke starte innspillingen" -Output.StartReplayFailed="Kunne ikke starte replay bufferen" +Output.StartReplayFailed="Kunne ikke å starte opp omspillingsbufferen" +Output.StartFailedGeneric="Start av output har feilet. Venligst se loggen for detaljer.\n\nNote: Om du bruker NVENC eller AMD enkodere, pass på at skjerm driverene er oppdatert." -Output.ConnectFail.Title="Tilkobling misklytes" -Output.ConnectFail.BadPath="Ugyldig filbane eller tilkoblings-URL. Vennligst bekreft at instillingene dine er riktige." -Output.ConnectFail.ConnectFailed="Kunne ikke koble til tjener." +Output.ConnectFail.Title="Tilkobling mislyktes" +Output.ConnectFail.BadPath="Ugyldig filbane eller tilkoblings-URL. Vennligst sjekk at innstillingene dine er riktige." +Output.ConnectFail.ConnectFailed="Klarte ikke å koble til tjeneren" Output.ConnectFail.InvalidStream="Kunne ikke få adgang til den angitte kanalen eller strømmenøkkelen. Strømmenøkkelen kan være feil eller det kan være et problem med tilkoblingen til tjeneren." Output.ConnectFail.Error="En uventet feil oppstod ved tilkobling til serveren. Detaljert informasjon kan du finne i loggfila." Output.ConnectFail.Disconnected="Koblet fra tjeneren." @@ -141,10 +230,10 @@ Output.RecordNoSpace.Msg="Det er ikke nok diskplass til å fortsette opptaket." Output.RecordError.Title="Innspillingsfeil" Output.RecordError.Msg="Det oppstod en uspesifisert feil under opptaket." Output.ReplayBuffer.NoHotkey.Title="Ingen hurtigtast satt!" -Output.ReplayBuffer.NoHotkey.Msg="Ingen lagrings hurtigtast satt for omspillings buffer. Venligst sett \"Lagre\" hurtigtasten i bruk for å lagre opptak." +Output.ReplayBuffer.NoHotkey.Msg="Ingen lagringshurtigtast er valgt for omspillingsbufferen. Vennligst velg en \"Lagre\"-hurtigknapp, sånn at den kan brukes til å lagre opptak." Output.BadPath.Title="Ugyldig Filbane" -Output.BadPath.Text="Den oppgitte fillagringsbanen er ugyldig. Vennligst kontrollér instillingene dine og bekreft at filbanen er gyldig." +Output.BadPath.Text="Den oppgitte fillagringsbanen er ugyldig. Vennligst sjekk innstillingene dine og bekreft at filbanen er gyldig." LogReturnDialog="Vellykket Loggopplasting" LogReturnDialog.CopyURL="Kopiér URL" @@ -185,7 +274,7 @@ Basic.DisplayCapture="Skjermopptak" Basic.Main.PreviewConextMenu.Enable="Aktiver forhåndsvisning" -ScaleFiltering="Skala Filtrering" +ScaleFiltering="Skaleringsfiltrering" ScaleFiltering.Point="Punkt" ScaleFiltering.Bilinear="Bilineær" ScaleFiltering.Bicubic="Bikubisk" @@ -223,6 +312,10 @@ AddProfile.Text="Skriv inn navn på profilen" RenameProfile.Title="Gi nytt navn til profil" +Basic.Main.MixerRename.Title="Gi nytt navn til lydkilde" +Basic.Main.MixerRename.Text="Vennligst skriv inn navnet til lydkilden" + + Basic.Main.PreviewDisabled="Forhåndsvisning er deaktivert" Basic.SourceSelect="Opprett eller velg kilde" @@ -235,7 +328,7 @@ Basic.PropertiesWindow.AutoSelectFormat="%1 (selvvalg: %2)" Basic.PropertiesWindow.SelectColor="Velg farge" Basic.PropertiesWindow.SelectFont="Velg skrifttype" Basic.PropertiesWindow.ConfirmTitle="Innstillingene er endret" -Basic.PropertiesWindow.Confirm="Det er ulagra endringer. Vil du beholde dem?" +Basic.PropertiesWindow.Confirm="Det er ulagrede endringer. Vil du beholde dem?" Basic.PropertiesWindow.NoProperties="Ingen egenskaper tilgjengelige" Basic.PropertiesWindow.AddFiles="Legg til filer" Basic.PropertiesWindow.AddDir="Legg til mappe" @@ -267,7 +360,7 @@ Basic.Filters.Title="Filtre for '%1'" Basic.Filters.AddFilter.Title="Filternavn" Basic.Filters.AddFilter.Text="Angi navnet på filteret" -Basic.TransformWindow="Transformer Sceneelemnt" +Basic.TransformWindow="Transformer sceneelement" Basic.TransformWindow.Position="Posisjon" Basic.TransformWindow.Rotation="Rotasjon" Basic.TransformWindow.Size="Størrelse" @@ -300,14 +393,15 @@ Basic.Main.AddSourceHelp.Text="Du må ha minst én scene for å legge til en kil Basic.Main.Scenes="Scener" Basic.Main.Sources="Kilder" +Basic.Main.Controls="Kontrollere" Basic.Main.Connecting="Kobler til…" Basic.Main.StartRecording="Start Opptak" -Basic.Main.StartReplayBuffer="Start Omspill Buffer" +Basic.Main.StartReplayBuffer="Start omspillingsbuffer" Basic.Main.StartStreaming="Start Strømming" Basic.Main.StopRecording="Stopp Opptak" Basic.Main.StoppingRecording="Stanser innspilling…" -Basic.Main.StopReplayBuffer="Stopp Omspill Buffer" -Basic.Main.StoppingReplayBuffer="Stopper Omspill Bufferen..." +Basic.Main.StopReplayBuffer="Stopp omspillingsbufferen" +Basic.Main.StoppingReplayBuffer="Stopper omspillingsbufferen..." Basic.Main.StopStreaming="Stopp Strømming" Basic.Main.StoppingStreaming="Stanser strøm…" Basic.Main.ForceStopStreaming="Stopp strømming (forkast forsinkelse)" @@ -329,7 +423,7 @@ Basic.MainMenu.Edit.Redo="&Gjør om" Basic.MainMenu.Edit.UndoAction="&Angre $1" Basic.MainMenu.Edit.RedoAction="&Gjør om $1" Basic.MainMenu.Edit.LockPreview="Lås Forhåndsvisning" -Basic.MainMenu.Edit.Scale="Forhåndsvisning & Skalering" +Basic.MainMenu.Edit.Scale="Forhåndsvisnings-skalering" Basic.MainMenu.Edit.Scale.Window="Tilpass til vindu" Basic.MainMenu.Edit.Scale.Canvas="Lerret (%1x%2)" Basic.MainMenu.Edit.Scale.Output="Utgang (%1x%2)" @@ -351,27 +445,32 @@ Basic.MainMenu.Edit.Order.MoveUp="Flytt &opp" Basic.MainMenu.Edit.Order.MoveDown="Flytt &ned" Basic.MainMenu.Edit.Order.MoveToTop="Legg på &toppen" Basic.MainMenu.Edit.Order.MoveToBottom="Legg på &bunnen" -Basic.MainMenu.Edit.AdvAudio="&Avanserte lydinstillinger" +Basic.MainMenu.Edit.AdvAudio="&Avanserte lydinnstillinger" Basic.MainMenu.View="&Vis" -Basic.MainMenu.View.Toolbars="%Verktøylinje" +Basic.MainMenu.View.Toolbars="&Verktøylinjer" +Basic.MainMenu.View.Docks="Festede elementer" +Basic.MainMenu.View.Docks.ResetUI="Omstart grensesnittet" +Basic.MainMenu.View.Docks.LockUI="Lås grensesnittet" Basic.MainMenu.View.Toolbars.Listboxes="&Listebokser" Basic.MainMenu.View.SceneTransitions="Sceneoverganger" -Basic.MainMenu.View.StatusBar="Statuslinje" +Basic.MainMenu.View.StatusBar="&Statuslinje" +Basic.MainMenu.View.Fullscreen.Interface="Fullskjermsgrensesnitt" Basic.MainMenu.SceneCollection="&Scenesamling" Basic.MainMenu.Profile="&Profil" -Basic.MainMenu.Profile.Import="Importer Profil" +Basic.MainMenu.Profile.Import="Importer profil" Basic.MainMenu.Profile.Export="Eksporter Profil" Basic.MainMenu.SceneCollection.Import="Importer Scenesamling" -Basic.MainMenu.SceneCollection.Export="Eskporter Scenesamling" +Basic.MainMenu.SceneCollection.Export="Eksporter scenesamling" Basic.MainMenu.Profile.Exists="Profilen eksisterer allerede" Basic.MainMenu.SceneCollection.Exists="Scenesamlingen eksisterer allerede" Basic.MainMenu.Tools="&Verktøy" Basic.MainMenu.Help="&Hjelp" -Basic.MainMenu.Help.Website="Besøk &nettsted" +Basic.MainMenu.Help.HelpPortal="&Portal for hjelp" +Basic.MainMenu.Help.Website="Besøk &nettstedet" Basic.MainMenu.Help.Logs="&Loggfiler" Basic.MainMenu.Help.Logs.ShowLogs="Vis &loggfiler" Basic.MainMenu.Help.Logs.UploadCurrentLog="Last opp &nåværende loggfil" @@ -379,7 +478,7 @@ Basic.MainMenu.Help.Logs.UploadLastLog="Last opp &siste loggfil" Basic.MainMenu.Help.Logs.ViewCurrentLog="&Vis gjeldende logg" Basic.MainMenu.Help.CheckForUpdates="Se etter oppdateringer" -Basic.Settings.ProgramRestart="Programmet må startes på nytt for at disse instillingene skal tre i kraft." +Basic.Settings.ProgramRestart="Programmet må startes på nytt for at disse innstillingene skal tre i kraft." Basic.Settings.ConfirmTitle="Bekreft endringer" Basic.Settings.Confirm="Du har endringer som ikke er lagret. Vil du lagre?" @@ -387,6 +486,7 @@ Basic.Settings.General="Generelt" Basic.Settings.General.Theme="Tema" Basic.Settings.General.Language="Språk" Basic.Settings.General.EnableAutoUpdates="Automatisk se etter oppdateringer ved oppstart" +Basic.Settings.General.OpenStatsOnStartup="Åpne statistikkvindu ved oppstart" Basic.Settings.General.WarnBeforeStartingStream="Vis bekreftelsesdialogboks når du starter strømming" Basic.Settings.General.WarnBeforeStoppingStream="Vis bekreftelsesdialogboks når stanser strømming" Basic.Settings.General.Projectors="Projektorer" @@ -399,9 +499,19 @@ Basic.Settings.General.SourceSnapping="Fest kilder til andre kilder" Basic.Settings.General.SnapDistance="Festingfølsomhet" Basic.Settings.General.RecordWhenStreaming="Spill inn automatisk ved strømming" Basic.Settings.General.KeepRecordingWhenStreamStops="Fortsett innspilling etter strømming" -Basic.Settings.General.ReplayBufferWhileStreaming="Automatisk start replay bufferen når du strømmer" -Basic.Settings.General.KeepReplayBufferStreamStops="Hold replay bufferen når du stopper strømmen" +Basic.Settings.General.ReplayBufferWhileStreaming="Start omspillingsbufferen automatisk når du strømmer" +Basic.Settings.General.KeepReplayBufferStreamStops="Hold omspillingsbufferen gående når strømmen stoppes" +Basic.Settings.General.SysTray="Systemstatusfeltet" Basic.Settings.General.SysTrayWhenStarted="Minimer til systemstatusfelt ved oppstart" +Basic.Settings.General.SystemTrayHideMinimize="Alltid minimere til systemstatusfeltet istedet for oppgavelinjen" +Basic.Settings.General.SaveProjectors="Lagre projektorer ved avslutning" +Basic.Settings.General.SwitchOnDoubleClick="Dobbeltklikking vil gå til scenen" +Basic.Settings.General.StudioPortraitLayout="Aktiver portrett/loddrett vindu" +Basic.Settings.General.MultiviewLayout="Flervisningsplassering" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Vannrett, øverst" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Vannrett, nederst" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Loddrett, venstre" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Loddrett, høyre" Basic.Settings.Stream="Strøm" Basic.Settings.Stream.StreamType="Strømmetype" @@ -416,12 +526,13 @@ Basic.Settings.Output.Mode="Utgangsmodus" Basic.Settings.Output.Mode.Simple="Enkel" Basic.Settings.Output.Mode.Adv="Avansert" Basic.Settings.Output.Mode.FFmpeg="FFmpeg-utgang" -Basic.Settings.Output.UseReplayBuffer="Aktiver Omspill Buffer" -Basic.Settings.Output.ReplayBuffer.SecondsMax="Maksimal Omspill Tid (sekunder)" +Basic.Settings.Output.UseReplayBuffer="Aktiver omspillingsbufferen" +Basic.Settings.Output.ReplayBuffer.SecondsMax="Maksimal omspillingstid (i sekunder)" Basic.Settings.Output.ReplayBuffer.MegabytesMax="Maksimalt Minne (Megabytes)" -Basic.Settings.Output.ReplayBuffer.Estimate="Anslått minne bruk. %1 MB" +Basic.Settings.Output.ReplayBuffer.Estimate="Anslått minnebruk: %1 MB" Basic.Settings.Output.ReplayBuffer.EstimateUnknown="Kan ikke beregne minnebruk. Vennligst sett maksimalt minnebrukgrense." -Basic.Settings.Output.ReplayBuffer.HotkeyMessage="(Merk: Sørg for å angi en hurtigtast for omspill bufferen i hurtigtast innstillingen)" +Basic.Settings.Output.ReplayBuffer.HotkeyMessage="(Merk: Sørg for å angi en hurtigtast for omspillingsbufferen i hurtigtastsinnstillingene)" +Basic.Settings.Output.ReplayBuffer.Prefix="Replay Buffer Filnavn Prefiks" Basic.Settings.Output.ReplayBuffer.Suffix="Suffiks" Basic.Settings.Output.Simple.SavePath="Opptaksbane" Basic.Settings.Output.Simple.RecordingQuality="Opptakskvalitet" @@ -429,8 +540,8 @@ Basic.Settings.Output.Simple.RecordingQuality.Stream="Samme som strøm" Basic.Settings.Output.Simple.RecordingQuality.Small="Høy kvalitet, middels filstørrelse" Basic.Settings.Output.Simple.RecordingQuality.HQ="Veldig høy kvalitet, stor filstørrelse" Basic.Settings.Output.Simple.RecordingQuality.Lossless="Tapsfri kvalitet, veldig stor filstørrelse" -Basic.Settings.Output.Simple.Warn.VideoBitrate="Advarsel: strømmens bitrate vil settes til %1, som er den øvre grensen for gjeldende strømmetjeneste. Om du ønsker å gå over denne grensen må du aktivere avanserte koderinnstillinger og sørge for at valget for håndheving av strømmetjenestens grense ikke er huket av." -Basic.Settings.Output.Simple.Warn.AudioBitrate="Advarsel: strømmens lydbitrate vil settes til %1, som er den øvre grensen for gjeldende strømmetjeneste. Om du ønsker å gå over denne grensen må du aktivere avanserte koderinnstillinger og sørge for at valget for håndheving av strømmetjenestens grense ikke er huket av." +Basic.Settings.Output.Simple.Warn.VideoBitrate="Advarsel: Strømmens bitfrekvens vil bli satt til %1, som er den øvre grensen for den gjeldende strømmetjenesten. Om du ønsker å gå over %1, må du aktivere avanserte koderinnstillinger og sørge for at valget for håndheving av strømmetjenestens grense ikke er huket av." +Basic.Settings.Output.Simple.Warn.AudioBitrate="Advarsel: Strømmens lydbitfrekvens vil bli satt til %1, som er den øvre grensen for den gjeldende strømmetjenesten. Om du ønsker å gå over %1, må du aktivere avanserte koderinnstillinger og sørge for at valget for håndheving av strømmetjenestens grense ikke er huket av." Basic.Settings.Output.Simple.Warn.Encoder="Advarsel: Opptak med programvarekoder i en annen kvalitetsinnstilling enn strømmingen vil kreve ekstra prosessorressurser om du strømmer og tar opp på samme tid." Basic.Settings.Output.Simple.Warn.Lossless="Advarsel: Tapsfri kvalitet resulterer i enormt store filstørrelser! Denne innstillingen kan bruke oppimot 7 GB diskplass per minutt ved opptak med høy oppløsning og bildefrekvens. Tapsfri kvalitet anbefales ikke for lange opptak med mindre du har store mengder diskplass tilgjengelig." Basic.Settings.Output.Simple.Warn.Lossless.Msg="Er du sikker på at du vil bruke tapsfri kvalitet?" @@ -440,22 +551,22 @@ Basic.Settings.Output.Simple.Encoder.Software="Programvare (x264)" Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Maskinvare (QSV)" Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Maskinvare (AMD)" Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Maskinvare (NVENC)" -Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Programvare (x264 forhåndsinstilling for liten prosessorbruk, øker filstørrelsen)" +Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Programvare (Forhåndsinnstilt til x264 med lav CPU-bruk, øker filstørrelsen)" Basic.Settings.Output.VideoBitrate="Bildeoverføringshastighet" Basic.Settings.Output.AudioBitrate="Lydoverføringshastighet" -Basic.Settings.Output.Reconnect="Koble til automatisk" +Basic.Settings.Output.Reconnect="Koble automatisk til på nytt" Basic.Settings.Output.RetryDelay="Opphold mellom tilkoblingsforsøk (sekunder)" Basic.Settings.Output.MaxRetries="Høyst antall tilkoblingsforsøk" -Basic.Settings.Output.Advanced="Aktiver Avanserte Koderinstillinger" -Basic.Settings.Output.EncoderPreset="Koderforhåndsinstilling (raskere gir mindre prosessorbelastning)" -Basic.Settings.Output.CustomEncoderSettings="Egendefinert koderinstilling" -Basic.Settings.Output.CustomMuxerSettings="Egenfinderte mukserinstillinger" +Basic.Settings.Output.Advanced="Aktiver avanserte koderinnstillinger" +Basic.Settings.Output.EncoderPreset="Koder-forhåndsinnstilling (Raskere = mindre CPU-bruk)" +Basic.Settings.Output.CustomEncoderSettings="Egendefinerte koderinnstillinger" +Basic.Settings.Output.CustomMuxerSettings="Egendefinerte mukserinnstillinger" Basic.Settings.Output.NoSpaceFileName="Lag filnavn uten mellomrom" Basic.Settings.Output.Adv.Rescale="Reskaler utgang" Basic.Settings.Output.Adv.AudioTrack="Lydspor" Basic.Settings.Output.Adv.Streaming="Strømming" -Basic.Settings.Output.Adv.ApplyServiceSettings="Bruk strømmetjenerens koderinstillinger" +Basic.Settings.Output.Adv.ApplyServiceSettings="Fremtving strømmetjenestens koderinnstillinger" Basic.Settings.Output.Adv.Audio.Track1="Spor 1" Basic.Settings.Output.Adv.Audio.Track2="Spor 2" Basic.Settings.Output.Adv.Audio.Track3="Spor 3" @@ -476,19 +587,20 @@ Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile="Utgang til fil" Basic.Settings.Output.Adv.FFmpeg.SaveFilter.Common="Vanlige opptaksformater" Basic.Settings.Output.Adv.FFmpeg.SaveFilter.All="Alle filer" Basic.Settings.Output.Adv.FFmpeg.SavePathURL="Filbane eller URL" -Basic.Settings.Output.Adv.FFmpeg.Format="Konteinerformat" +Basic.Settings.Output.Adv.FFmpeg.Format="Innpakkingsformat" Basic.Settings.Output.Adv.FFmpeg.FormatAudio="Lyd" Basic.Settings.Output.Adv.FFmpeg.FormatVideo="Video" Basic.Settings.Output.Adv.FFmpeg.FormatDefault="Standardformat" -Basic.Settings.Output.Adv.FFmpeg.FormatDesc="Konteinerformatbeskrivelse" +Basic.Settings.Output.Adv.FFmpeg.FormatDesc="Innpakkingsformatets beskrivelse" Basic.Settings.Output.Adv.FFmpeg.FormatDescDef="Lyd- og videokodek gjettet fra filbane eller URL" Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault="Standardkoder" Basic.Settings.Output.Adv.FFmpeg.AVEncoderDisable="Deaktiver koder" Basic.Settings.Output.Adv.FFmpeg.VEncoder="Videokoder" -Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings="Videokoderinstillinger (om noen)" +Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings="Videokoderinnstillinger (om noen)" Basic.Settings.Output.Adv.FFmpeg.AEncoder="Lydkoder" -Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Lydkoderinstillinger (om noen)" -Basic.Settings.Output.Adv.FFmpeg.MuxerSettings="Mukserinstillinger (om noen)" +Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Lydkoderinnstillinger (om noen)" +Basic.Settings.Output.Adv.FFmpeg.MuxerSettings="Mukserinnstillinger (om noen)" +Basic.Settings.Output.Adv.FFmpeg.GOPSize="Nøkkelbilde intervall (frames)" Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat="Vis alle kodeker (selv om potensielt ikke-kompatibel)" FilenameFormatting.completer="%CCYY-%MM-%DD %hh-%mm-%ss\n%YY-%MM-%DD %hh-%mm-%ss\n%Y-%m-%d %H-%M-%S\n%y-%m-%d %H-%M-%S\n%a %Y-%m-%d %H-%M-%S\n%A %Y-%m-%d %H-%M-%S\n%Y-%b-%d %H-%M-%S\n%Y-%B-%d %H-%M-%S\n%Y-%m-%d %I-%M-%S-%p\n%Y-%m-%d %H-%M-%S-%z\n%Y-%m-%d %H-%M-%S-%Z" @@ -509,7 +621,7 @@ Basic.Settings.Video.Numerator="Teller" Basic.Settings.Video.Denominator="Nevner" Basic.Settings.Video.Renderer="Renderer" Basic.Settings.Video.InvalidResolution="Ugyldig oppløsningsverdi. Må være [bredde]x[høyde] (f.eks. 1920x1080)" -Basic.Settings.Video.CurrentlyActive="Bildeutgang er aktiv. Vennligst stans alle utganger for å endre bildeinstillingene." +Basic.Settings.Video.CurrentlyActive="Bildeutgangen er aktiv. Vennligst stans alle utganger for å endre bildeinnstillingene." Basic.Settings.Video.DisableAero="Skru av Aero" Basic.Settings.Video.DownscaleFilter.Bilinear="Bilineær (raskest, men uskarp ved skalering)" @@ -519,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (skjerpet skalering, 32 pr Basic.Settings.Audio="Lyd" Basic.Settings.Audio.SampleRate="Samplingsfrekvens" Basic.Settings.Audio.Channels="Kanaler" +Basic.Settings.Audio.MeterDecayRate="Audiometerets forfallsfrekvens" +Basic.Settings.Audio.MeterDecayRate.Fast="Raskt" +Basic.Settings.Audio.MeterDecayRate.Medium="Middels (Type I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Tregt (Type II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="Advarsel: Surroundlyd er aktivert." +Basic.Settings.Audio.MultichannelWarning="Hvis du strømmer, sjekk om strømmetjenesten din støtter både surround-lydinnføring og surround-lydavspilling. Twitch, Facebook 360 Live, Mixer RTMP, og Smashcast er eksempler hvor surroundlyd er full støttet. Selv om Facebook Live og YouTube Live begge støtter surround-innføring, nedmikser Facebook Live det ned til Stereo, og YouTube Live spiller bare av to kanaler.\n\nOBS-lydfiltre er kompatible med surroundlyd, selv om VST-tilleggsstøtte ikke er garantert." +Basic.Settings.Audio.MultichannelWarning.Title="Vil du aktivere surround-lyd?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Er du sikker på at du vil aktivere surround-lyd?" Basic.Settings.Audio.DesktopDevice="Skrivebordslyd" Basic.Settings.Audio.DesktopDevice2="Skrivebordslyd 2" Basic.Settings.Audio.AuxDevice="Mikrofon/Aux" @@ -535,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Prosessprioritet" Basic.Settings.Advanced.General.ProcessPriority.High="Høy" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Over Normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Under normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Inaktiv" Basic.Settings.Advanced.FormatWarning="Advarsel: Fargeformater andre enn NV12 er ment for opptak. Disse formatene anbefales ikke ved strømming, da det fører til økt prosessorbruk som følge av fargeformatkonvertering." Basic.Settings.Advanced.Audio.BufferingTime="Lydbuffertid" @@ -543,24 +664,27 @@ Basic.Settings.Advanced.Video.ColorSpace="YUV fargerom" Basic.Settings.Advanced.Video.ColorRange="YUV fargespekter" Basic.Settings.Advanced.Video.ColorRange.Partial="Delvis" Basic.Settings.Advanced.Video.ColorRange.Full="Full" +Basic.Settings.Advanced.Audio.MonitoringDevice="Lydenhet for overvåking" Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Standard" +Basic.Settings.Advanced.Audio.DisableAudioDucking="Deaktiver Windows-lyddukking" Basic.Settings.Advanced.StreamDelay="Strømforsinkelse" Basic.Settings.Advanced.StreamDelay.Duration="Varighet (sekunder)" Basic.Settings.Advanced.StreamDelay.Preserve="Bevar avkuttingspunktet (øk forsinkelse) ved tilbakekobling" Basic.Settings.Advanced.StreamDelay.MemoryUsage="Anslått minnebruk: %1 MB" Basic.Settings.Advanced.Network="Nettverk" Basic.Settings.Advanced.Network.BindToIP="Bind til IP" -Basic.Settings.Advanced.Network.EnableNewSocketLoop="Aktiver ny nettverk kode" +Basic.Settings.Advanced.Network.EnableNewSocketLoop="Aktiver den nye nettverkskoden" +Basic.Settings.Advanced.Network.EnableLowLatencyMode="Lavlatens-modus" -Basic.AdvAudio="Avanserte lydinstillinger" +Basic.AdvAudio="Avanserte lydinnstillinger" Basic.AdvAudio.Name="Navn" Basic.AdvAudio.Volume="Volum (%)" Basic.AdvAudio.Mono="Nedmiks til mono" Basic.AdvAudio.Panning="Panorering" -Basic.AdvAudio.SyncOffset="Synkronerings forskyvning (ms)" +Basic.AdvAudio.SyncOffset="Synkroniseringsforskyvning (ms)" Basic.AdvAudio.Monitoring="Hør på kilde" -Basic.AdvAudio.Monitoring.None="Ikke hør kilde" -Basic.AdvAudio.Monitoring.MonitorOnly="Kun hør kilde (Ikke output kilde)" +Basic.AdvAudio.Monitoring.None="Ikke hør kilden" +Basic.AdvAudio.Monitoring.MonitorOnly="Kun hør kilden (Demp utdataen)" Basic.AdvAudio.Monitoring.Both="Hør og output kilde" Basic.AdvAudio.AudioTracks="Spor" @@ -588,7 +712,7 @@ Hotkeys.Tab="Tab" Hotkeys.Print="Print" Hotkeys.Pause="Pause" Hotkeys.Left="Left" -Hotkeys.Right="Right" +Hotkeys.Right="Piltast høyre" Hotkeys.Up="Piltast opp" Hotkeys.Down="Piltast ned" Hotkeys.Windows="Windows" @@ -620,5 +744,8 @@ SceneItemHide="Gjem '%1'" OutputWarnings.NoTracksSelected="Du må velge minst ett spor" OutputWarnings.MultiTrackRecording="Advarsel: enkelte formater (som FLV) støtter ikke flere spor per opptak" +OutputWarnings.MP4Recording="Advarsel: Opptak lagret i MP4 bil bli slettet hvis filen ikke kan fullføres (f.eks. som følge av BSOD, tap av strøm osv.) Hvis du vil registere flere lydspor vurder å bruke MKV og remux inspillingen til MP4 etter det er ferdig (Fil -> Remux Opptak)" +FinalScene.Title="Slett scene" +FinalScene.Text="Det må være minst én scene." diff --git a/UI/data/locale/nl-NL.ini b/UI/data/locale/nl-NL.ini index f5e512f..07ede93 100644 --- a/UI/data/locale/nl-NL.ini +++ b/UI/data/locale/nl-NL.ini @@ -28,16 +28,21 @@ Browse="Bladeren" Mono="Mono" Stereo="Stereo" DroppedFrames="Gedropte Frames %1 (%2%)" +StudioProgramProjector="Full-screen Projector (Programma)" PreviewProjector="Full-screen Projector (Preview)" SceneProjector="Full-screen Projector (Scène)" SourceProjector="Full-screen Projector (Bron)" +StudioProgramWindow="Projectorvenster (Programma)" PreviewWindow="Projectorvenster (Preview)" SceneWindow="Projectorvenster (Scène)" SourceWindow="Projectorvenster (Bron)" +MultiviewProjector="Multiview (Fullscreen)" +MultiviewWindowed="Multiview (Venster)" Clear="Wissen" Revert="Herstellen" Show="Weergeven" Hide="Verbergen" +UnhideAll="Verberge alle" Untitled="Naamloos" New="Nieuw" Duplicate="Dupliceren" @@ -66,6 +71,13 @@ PasteDuplicate="Plakken (dupliceren)" RemuxRecordings="Remux opnames" Next="Volgende" Back="Vorige" +Defaults="Standaardwaarden" +HideMixer="Verberg in mixer" +TransitionOverride="Transitieoverschrijving" +None="Geen" +StudioMode.Preview="Preview" +StudioMode.Program="Programma" +ShowInMultiview="Weergeven in Multiview" AlreadyRunning.Title="OBS is al actief" AlreadyRunning.Text="OBS is al actief! Tenzij je dit wilde doen, sluit a.u.b. alle reeds draaiende instanties van OBS voor je een nieuwe instantie opstart. Als je OBS hebt ingesteld om naar het systeemvak te minimaliseren, controleer dan of hij daar nog staat." @@ -300,6 +312,10 @@ AddProfile.Text="Voer de naam van het profiel in" RenameProfile.Title="Profiel Hernoemen" +Basic.Main.MixerRename.Title="Hernoem Audiobron" +Basic.Main.MixerRename.Text="Voer a.u.b. de naam van de audiobron in" + + Basic.Main.PreviewDisabled="Preview is momenteel uitgeschakeld" Basic.SourceSelect="Maak/Selecteer Bron" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Je moet ten minste 1 scène hebben om een bron to Basic.Main.Scenes="Scènes" Basic.Main.Sources="Bronnen" +Basic.Main.Controls="Controls" Basic.Main.Connecting="Verbinden..." Basic.Main.StartRecording="Opname Starten" Basic.Main.StartReplayBuffer="Start Replay Buffer" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="Geavanceerde &Audioinstellingen" Basic.MainMenu.View="&Beeld" Basic.MainMenu.View.Toolbars="&Werkbalken" +Basic.MainMenu.View.Docks="Docks" +Basic.MainMenu.View.Docks.ResetUI="Herstel UI" +Basic.MainMenu.View.Docks.LockUI="Zet UI Vast" Basic.MainMenu.View.Toolbars.Listboxes="&Lijsten" Basic.MainMenu.View.SceneTransitions="S&cène-overgangen" Basic.MainMenu.View.StatusBar="&Statusbalk" +Basic.MainMenu.View.Fullscreen.Interface="Volledig scherm" Basic.MainMenu.SceneCollection="&Scèneverzameling" Basic.MainMenu.Profile="&Profiel" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="De scèneverzameling bestaat al" Basic.MainMenu.Tools="&Tools" Basic.MainMenu.Help="&Help" +Basic.MainMenu.Help.HelpPortal="Help-&portal" Basic.MainMenu.Help.Website="&Website Bezoeken" Basic.MainMenu.Help.Logs="&Logbestanden" Basic.MainMenu.Help.Logs.ShowLogs="Logbe&standen Weergeven" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Systeemvak" Basic.Settings.General.SysTrayWhenStarted="Naar systeemvak minimaliseren bij opstarten" Basic.Settings.General.SystemTrayHideMinimize="Altijd minimaliseren naar het systeemvak in plaats van de taakbalk" Basic.Settings.General.SaveProjectors="Projectors opslaan bij afsluiten" +Basic.Settings.General.SwitchOnDoubleClick="Ga over naar scène bij dubbelklik" +Basic.Settings.General.StudioPortraitLayout="Portret/verticale layout inschakelen" +Basic.Settings.General.MultiviewLayout="Multiview-indeling" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontaal, Boven" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontaal, Onder" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Verticaal, Links" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Verticaal, Rechts" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Stream Type" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Scherper schalen, 32 samp Basic.Settings.Audio="Audio" Basic.Settings.Audio.SampleRate="Sample Rate" Basic.Settings.Audio.Channels="Kanalen" +Basic.Settings.Audio.MeterDecayRate="Vervalsnelheid Volumemeter" +Basic.Settings.Audio.MeterDecayRate.Fast="Snel" +Basic.Settings.Audio.MeterDecayRate.Medium="Gemiddeld (Type I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Traag (Type II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="Waarschuwing: Surround sound audio is ingeschakeld." +Basic.Settings.Audio.MultichannelWarning="Als je streamt, controleer dan of je streaming service zowel surround sound ingest als surround sound afspelen ondersteunt. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast zijn voorbeelden waar surround sound volledig ondersteund is. Alhoewel Facebook Live en Youtube Live beide surround ingest ondersteunen, downmixt Facebook Live het naar stereo, terwijl Youtube Live slechts twee kanalen afspeelt.\n\nOBS audio filters kunnen overweg met surround sound, maar ondersteuning bij VST plugins is niet gegarandeerd." +Basic.Settings.Audio.MultichannelWarning.Title="Surround sound audio inschakelen?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Weet je zeker dat je surround sound audio wil inschakelen?" Basic.Settings.Audio.DesktopDevice="Desktop Audioapparaat" Basic.Settings.Audio.DesktopDevice2="Desktop Audioapparaat 1" Basic.Settings.Audio.AuxDevice="Mic/Aux Audioapparaat" @@ -616,8 +653,9 @@ Basic.Settings.Audio.UnknownAudioDevice="[Apparaat niet verbonden of niet beschi Basic.Settings.Advanced="Geavanceerd" Basic.Settings.Advanced.General.ProcessPriority="Procesprioriteit" Basic.Settings.Advanced.General.ProcessPriority.High="Hoog" -Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Bovennormaal" +Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Boven Normaal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normaal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Onder Normaal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Laagst" Basic.Settings.Advanced.FormatWarning="Waarschuwing: Andere kleurformaten dan NV12 zijn hoofdzakelijk bedoeld voor opnemen en worden niet aanbevolen om mee te streamen. Streamen kan verhoogd CPU-gebruik opleveren vanwege kleurformaatconversie." Basic.Settings.Advanced.Audio.BufferingTime="Audio Buffertijd" diff --git a/UI/data/locale/nn-NO.ini b/UI/data/locale/nn-NO.ini new file mode 100644 index 0000000..8405b91 --- /dev/null +++ b/UI/data/locale/nn-NO.ini @@ -0,0 +1,182 @@ + +Language="Norsk Nynorsk" +Region="Noreg" + +OK="OK" +Apply="Bruk" +Cancel="Avbryt" +Close="Lukk" +Save="Lagre" +Disable="Skrue av" +Yes="Ja" +No="Nei" +Add="Legg til" +Remove="Fjern" +Rename="Gje nytt namn" +Interact="Samhandle" +Filters="Filtre" +Properties="Eigenskapar" +MoveUp="Flytt opp" +MoveDown="Flytt ned" +Settings="Innstillingar" +Display="Skjerm" +Name="Namn" +Exit="Avslutt" +Browse="Bla gjennom" +Mono="Mono" +Stereo="Stereo" +MultiviewProjector="Fleirvising (Fullskjerm)" +MultiviewWindowed="Fleirvising (i vindauge)" +Clear="Tøm" +Show="Vis" +Hide="Skjul" +Untitled="Utan namn" +New="Ny" +Enable="Aktiver" +StudioMode.Preview="Førehandsvising" +StudioMode.Program="Program" + + + +BandwidthTest.Region="Region" +BandwidthTest.Region.EU="Europa" +BandwidthTest.Region.Asia="Asia" +BandwidthTest.Region.Other="Andre stader" + + +Basic.AutoConfig.ApplySettings="Bruk innstillingane" +Basic.AutoConfig.StartPage="Bruksinformasjon" +Basic.AutoConfig.VideoPage="Videoinnstillingar" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="Bruk noverande (%1)" +Basic.AutoConfig.StreamPage="Straum-informasjon" +Basic.AutoConfig.StreamPage.SubTitle="Ver snill å skriv inn din straum-informasjon" +Basic.AutoConfig.StreamPage.Service="Teneste" +Basic.AutoConfig.StreamPage.Service.ShowAll="Vis alle..." +Basic.AutoConfig.StreamPage.Server="Tener" +Basic.AutoConfig.StreamPage.StreamKey="Straumelykel" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Lenke)" +Basic.AutoConfig.StreamPage.StreamWarning.Title="Straumeåtvaring" +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Kopler til: %1..." + +Basic.Stats.Bitrate="Bitfrekvens" + +Updater.UpdateNow="Oppdater no" +Updater.Running.Title="Programmet er aktiv for augeblikket" + + +Basic.SceneTransitions="Sceneovergangar" +Basic.TransitionDuration="Tidslengd" +Basic.TogglePreviewProgramMode="Studiomodus" + +TransitionNameDlg.Title="Overgangsnamn" + +TitleBar.Profile="Profil" +TitleBar.Scenes="Scener" + +NameExists.Title="Namnet finns allereie" +NameExists.Text="Namnet er allereie i bruk." + +NoNameEntered.Title="Ver snill å skriv inn eit gyldig namn" +NoNameEntered.Text="Du kan ikkje bruke tomme namn." + +ConfirmStart.Title="Starting av straum?" + +ConfirmStop.Title="Stoppe straumen?" + +ConfirmExit.Title="Avslutt OBS?" + + + + + + + +LicenseAgreement.Exit="Avslutt" + + + +Basic.DesktopDevice1="Skrivebordslyd" +Basic.AuxDevice1="Mikrofon/Aux" +Basic.AuxDevice2="Mikrofon/Aux 2" +Basic.AuxDevice3="Mikrofon/Aux 3" +Basic.AuxDevice4="Mikrofon/Aux 4" + +Basic.Scene="Scene" + + + + + + + + + +AddProfile.Title="Legg til profil" + + + + + + + + + + + + + + + +Basic.Main.Scenes="Scener" +Basic.Main.Sources="Kjelder" +Basic.Main.Controls="Kontrollere" +Basic.Main.StartRecording="Byrje å ta opp" +Basic.Main.StartStreaming="Byrje å straume" + +Basic.MainMenu.File="&Fil" +Basic.MainMenu.File.Export="&Eksporter" +Basic.MainMenu.File.Import="&Importer" +Basic.MainMenu.File.Settings="&Innstillingar" +Basic.MainMenu.File.Exit="A&vslutt" + +Basic.MainMenu.Edit="&Rediger" +Basic.MainMenu.Edit.Undo="&Angre" +Basic.MainMenu.Edit.Redo="&Omgjere" +Basic.MainMenu.Edit.UndoAction="&Angre $1" +Basic.MainMenu.Edit.RedoAction="&Omgjer $1" + +Basic.MainMenu.View="&Vis" +Basic.MainMenu.View.Toolbars="&Verktøyslinjar" +Basic.MainMenu.View.SceneTransitions="S&ceneovergangar" + +Basic.MainMenu.Profile="&Profil" + +Basic.MainMenu.Tools="&Verktøy" + +Basic.MainMenu.Help="&Hjelp" + + +Basic.Settings.General="Generelt" +Basic.Settings.General.Theme="Tema" +Basic.Settings.General.Language="Språk" + + + + + + + + + + + + + + + + + + + + + diff --git a/UI/data/locale/pl-PL.ini b/UI/data/locale/pl-PL.ini index 05233b0..ace22ef 100644 --- a/UI/data/locale/pl-PL.ini +++ b/UI/data/locale/pl-PL.ini @@ -28,16 +28,21 @@ Browse="Przeglądaj" Mono="Mono" Stereo="Stereo" DroppedFrames="Zgubione klatki %1 (%2%)" +StudioProgramProjector="Wyświetlanie na pełnym ekranie (program)" PreviewProjector="Wyświetlanie na pełnym ekranie (podgląd)" SceneProjector="Wyświetlanie na pełnym ekranie (scena)" SourceProjector="Wyświetlanie na pełnym ekranie (źródło)" +StudioProgramWindow="Podgląd w oknie (program)" PreviewWindow="Podgląd w oknie (podgląd)" SceneWindow="Podgląd w oknie (scena)" SourceWindow="Podgląd w oknie (źródło)" +MultiviewProjector="MultiView (pełny ekran)" +MultiviewWindowed="MultiView (w oknie)" Clear="Wyczyść" Revert="Przywróć" Show="Pokaż" Hide="Ukryj" +UnhideAll="Pokaż wszystkie" Untitled="Bez tytułu" New="Nowy" Duplicate="Utwórz duplikat" @@ -66,6 +71,13 @@ PasteDuplicate="Wklej (duplikat)" RemuxRecordings="Przepakuj nagrania" Next="Dalej" Back="Wstecz" +Defaults="Domyślne" +HideMixer="Ukryj w mikserze" +TransitionOverride="Nadpisywanie efektu przejścia" +None="Brak" +StudioMode.Preview="Podgląd" +StudioMode.Program="Program" +ShowInMultiview="Pokaż w Multiview" AlreadyRunning.Title="OBS jest już uruchomiony" AlreadyRunning.Text="OBS jest już uruchomiony! Sprawdź wszystkie uruchomione wystąpienia OBS zanim uruchomisz go jeszcze raz. Jeżeli OBS jest zminimalizowany do zasobnika systemowego, sprawdź czy nie jest uruchomiony także w tym miejscu." @@ -300,6 +312,10 @@ AddProfile.Text="Podaj nazwę profilu" RenameProfile.Title="Zmień nazwę profilu" +Basic.Main.MixerRename.Title="Zmiana nazwy źródła dźwięku" +Basic.Main.MixerRename.Text="Podaj nazwę źródła dźwięku" + + Basic.Main.PreviewDisabled="Podgląd jest nieaktywny" Basic.SourceSelect="Stwórz/Wybierz źródło" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Wymagana jest przynajmniej jedna scena, aby doda Basic.Main.Scenes="Sceny" Basic.Main.Sources="Źródła obrazu" +Basic.Main.Controls="Panel sterujący" Basic.Main.Connecting="Łączenie..." Basic.Main.StartRecording="Rozpocznij nagrywanie" Basic.Main.StartReplayBuffer="Rozpocznij bufor replayu" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="Z&aawansowane ustawienia dźwięku" Basic.MainMenu.View="&Widok" Basic.MainMenu.View.Toolbars="Paski &narzędzi" +Basic.MainMenu.View.Docks="Panele" +Basic.MainMenu.View.Docks.ResetUI="Przywróć domyślny interfejs" +Basic.MainMenu.View.Docks.LockUI="Zablokuj interfejs" Basic.MainMenu.View.Toolbars.Listboxes="Panele kontrolne &list elementów" Basic.MainMenu.View.SceneTransitions="Efekty &przejścia scen" Basic.MainMenu.View.StatusBar="Pasek &stanu" +Basic.MainMenu.View.Fullscreen.Interface="Pełnoekranowy interfejs" Basic.MainMenu.SceneCollection="Zbiór &scen" Basic.MainMenu.Profile="P&rofil" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Kolekcja sceny już istnieje" Basic.MainMenu.Tools="&Narzędzia" Basic.MainMenu.Help="P&omoc" +Basic.MainMenu.Help.HelpPortal="&Portal pomocy" Basic.MainMenu.Help.Website="Od&wiedź naszą stronę" Basic.MainMenu.Help.Logs="P&liki dziennika" Basic.MainMenu.Help.Logs.ShowLogs="Pokaż pliki dziennika (&s)" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Zasobnik systemowy" Basic.Settings.General.SysTrayWhenStarted="Minimalizuj do zasobnika systemowego podczas uruchamiania" Basic.Settings.General.SystemTrayHideMinimize="Zawsze minimalizuj do zasobnika systemowego zamiast do paska zadań" Basic.Settings.General.SaveProjectors="Zapisz konfigurację podglądów na pełnym ekranie przy zamknięciu aplikacji" +Basic.Settings.General.SwitchOnDoubleClick="Przejdź do sceny po dwukrotnym kliknięciu" +Basic.Settings.General.StudioPortraitLayout="Włącz układ pionowy" +Basic.Settings.General.MultiviewLayout="Podgląd wielokrotny" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Poziomo, Góra" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Poziomo, Dół" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Pionowo, Lewo" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Pionowo, Prawo" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Typ streamu" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (skalowanie ostrzejsze, 32 Basic.Settings.Audio="Dźwięk" Basic.Settings.Audio.SampleRate="Częstotliwość próbkowania" Basic.Settings.Audio.Channels="Kanały" +Basic.Settings.Audio.MeterDecayRate="Szybkość powrotu miernika audio" +Basic.Settings.Audio.MeterDecayRate.Fast="Szybko" +Basic.Settings.Audio.MeterDecayRate.Medium="Średnio (PPM typu I)" +Basic.Settings.Audio.MeterDecayRate.Slow="Wolno (PPM typu II)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="Uwaga: Dźwięk przestrzenny jest włączony." +Basic.Settings.Audio.MultichannelWarning="W przypadku streamowania sprawdź, czy docelowa usługa obsługuje wielokanałowe wejście oraz odtwarzanie dźwięku surround. Twitch, Facebook, 360 Live, Mixer RTMP, Smashcast to przykłady usług obsługujących dźwięk wielokanałowy. O ile Facebook Live i Youtube Live obsługują wejście dźwięku wielokanałowego, to w przypadku odtwarzania Facebook miksuje kanału do stereo a Youtube Live odtwarza tylko dwa kanały.\n\nFiltry dźwiękowe OBS są w pełni kompatybilne z dźwiękiem kanałowym, natomiast wsparcie pluginów VST nie jest gwarantowane." +Basic.Settings.Audio.MultichannelWarning.Title="Włączyć dźwięk przestrzenny?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Czy na pewno chcesz włączyć dźwięk przestrzenny?" Basic.Settings.Audio.DesktopDevice="Urządzenie Audio" Basic.Settings.Audio.DesktopDevice2="Urządzenie Audio 2" Basic.Settings.Audio.AuxDevice="Mikrofon/dodatkowe urządzenie audio" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Priorytet procesu" Basic.Settings.Advanced.General.ProcessPriority.High="Wysoki" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Powyżej normalnego" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normalny" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Poniżej normalnego" Basic.Settings.Advanced.General.ProcessPriority.Idle="Bezczynny" Basic.Settings.Advanced.FormatWarning="Ostrzeżenie: Formaty koloru inne niż NV12 przeznaczone są głównie do nagrywania i nie są zalecane podczas przesyłania strumieniowego. Użycie innych formatów koloru podczas przesyłania strumieniowego wymagać będzie przekształcenia formatu w locie, co może znacząco zwiększyć obciążenie procesora." Basic.Settings.Advanced.Audio.BufferingTime="Czas buforowania dźwięku" diff --git a/UI/data/locale/pt-BR.ini b/UI/data/locale/pt-BR.ini index f73d41c..a036101 100644 --- a/UI/data/locale/pt-BR.ini +++ b/UI/data/locale/pt-BR.ini @@ -1,5 +1,5 @@ -Language="Português Brasileiro" +Language="Português" Region="Brasil" OK="Ok" @@ -28,22 +28,27 @@ Browse="Localizar" Mono="Mono" Stereo="Estéreo" DroppedFrames="Quadros Perdidos %1 (%2%)" -PreviewProjector="Projetor em tela cheia(pré-visualização)" -SceneProjector="Projetor em tela cheia(cena)" -SourceProjector="Projetor em tela cheia(fonte)" +StudioProgramProjector="Projetor em tela cheia (Programa)" +PreviewProjector="Projetor em tela cheia (pré-visualização)" +SceneProjector="Projetor em tela cheia (cena)" +SourceProjector="Projetor em tela cheia (fonte)" +StudioProgramWindow="Projetor em janela (Programa)" PreviewWindow="Projetor em janela (Pré-visualização)" SceneWindow="Projetor em janela (Cena)" SourceWindow="Projetor em janela (Fonte)" +MultiviewProjector="Multiview (Tela Cheia)" +MultiviewWindowed="Multiview (Em Janela)" Clear="Limpar" Revert="Desfazer" Show="Exibir" Hide="Ocultar" +UnhideAll="Mostrar Todos" Untitled="Sem nome" New="Novo" Duplicate="Duplicar" Enable="Habilitar" DisableOSXVSync="Desabilitar V-Sync em OSX" -ResetOSXVSyncOnExit="Resetar V-Sync em OSX ao Sair" +ResetOSXVSyncOnExit="Redefinir V-Sync em OSX na saída" HighResourceUsage="Codificação sobrecarregada! Considere abaixar as configurações de vídeo ou usar um padrão de codificação mais rápido." Transition="Transição" QuickTransitions="Transições Rápidas" @@ -66,6 +71,13 @@ PasteDuplicate="Colar (Duplicar)" RemuxRecordings="Remixar Gravações" Next="Avançar" Back="Voltar" +Defaults="Padrões" +HideMixer="Esconder no Mixer" +TransitionOverride="Substituição de Transição" +None="Nenhuma" +StudioMode.Preview="Prévia" +StudioMode.Program="Programa" +ShowInMultiview="Mostrar no Multiview" AlreadyRunning.Title="OBS já está em execução" AlreadyRunning.Text="OBS já está em execução! A menos que você tenha a intenção de fazer isso, por favor, feche todas as instâncias existentes do OBS antes de tentar executar uma nova. Se você tiver definido para minimizar o OBS na bandeja do sistema, verifique se ainda está lá em execução." @@ -84,8 +96,8 @@ Basic.FirstStartup.RunWizard="Você gostaria de executar o assistente de configu Basic.FirstStartup.RunWizard.BetaWarning="(Nota: O assistente de configuração está atualmente em beta)" Basic.FirstStartup.RunWizard.NoClicked="Se você mudar de ideia, você pode executar o assistente de configuração a qualquer momento no menu \"Ferramentas\"." -Basic.AutoConfig="Assistente de Configuração" -Basic.AutoConfig.Beta="Assistente de Configuração (Beta)" +Basic.AutoConfig="Assistente de configuração automática" +Basic.AutoConfig.Beta="Assistente de configuração automática (Beta)" Basic.AutoConfig.ApplySettings="Aplicar Configurações" Basic.AutoConfig.StartPage="Informações de Uso" Basic.AutoConfig.StartPage.SubTitle="Marque para que você deseja usar o programa" @@ -183,7 +195,7 @@ TitleBar.Scenes="Cenas" NameExists.Title="Nome Existente" NameExists.Text="O Nome já está em uso." -NoNameEntered.Title="Por favor digite um nome válido" +NoNameEntered.Title="Por favor, insira um nome válido" NoNameEntered.Text="Você não pode usar nomes vazios." ConfirmStart.Title="Iniciar Transmissão?" @@ -205,14 +217,14 @@ Output.StartReplayFailed="Falha ao iniciar o buffer de repetição" Output.StartFailedGeneric="Falha ao iniciar a saída. Favor verificar o log para informação detalhada do erro.\n\nAviso: Se estiver usando os codificadores NVENC ou AMD, tenha certeza de que seus drivers estão atualizados." Output.ConnectFail.Title="Falha ao conectar" -Output.ConnectFail.BadPath="Caminho inválido ou URL inválida. Por favor verifique se as configurações estão válidas." +Output.ConnectFail.BadPath="Caminho ou URL inválida. Por favor, verifique suas configurações para confirmar que estão válidas." Output.ConnectFail.ConnectFailed="Falha ao conectar com o Servidor" Output.ConnectFail.InvalidStream="Não foi possível acessar o canal especificado ou a chave de transmissão, por favor, verifique sua chave de transimissão. Se estiver correta, pode haver um problema em conectar ao servidor." Output.ConnectFail.Error="Um erro inesperado ocorreu ao tentar se conectar com o servidor. Veja o arquivo de Log para mais informações." Output.ConnectFail.Disconnected="Desconectado do Servidor." Output.RecordFail.Title="Falha ao iniciar a gravação" -Output.RecordFail.Unsupported="O formato de saída não é suportado ou não suporta mais que uma faixa de áudio. Por favor cheque suas configurações e tente de novo." +Output.RecordFail.Unsupported="O formato de saída ou não é compatível ou não suporta mais do que uma faixa de áudio. Por favor, verifique suas configurações e tente novamente." Output.RecordNoSpace.Title="Espaço em disco insuficiente" Output.RecordNoSpace.Msg="Não há espaço em disco suficiente para continuar a gravação." Output.RecordError.Title="Erro de gravação" @@ -291,15 +303,19 @@ Basic.Main.AddSceneDlg.Text="Por favor, digite o nome da cena" Basic.Main.DefaultSceneName.Text="Cena %1" Basic.Main.AddSceneCollection.Title="Adicionar grupo de cenas" -Basic.Main.AddSceneCollection.Text="Por favor insira o nome do grupo de cenas" +Basic.Main.AddSceneCollection.Text="Por favor, insira o nome da coleção de cena" Basic.Main.RenameSceneCollection.Title="Renomear grupo de cenas" AddProfile.Title="Adicionar perfil" -AddProfile.Text="Por favor entre com o nome do perfil" +AddProfile.Text="Por favor, insira o nome do perfil" RenameProfile.Title="Renomear perfil" +Basic.Main.MixerRename.Title="Renomear Fonte de Áudio" +Basic.Main.MixerRename.Text="Por favor, digite o nome da fonte de áudio" + + Basic.Main.PreviewDisabled="A pré-visualização esta desativada" Basic.SourceSelect="Criar/Selecionar Fonte" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Precisa de pelo menos uma cena para adicionar uma Basic.Main.Scenes="Cenas" Basic.Main.Sources="Fontes" +Basic.Main.Controls="Controles" Basic.Main.Connecting="Conectando..." Basic.Main.StartRecording="Iniciar gravação" Basic.Main.StartReplayBuffer="Iniciar Buffer do Replay" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="&Propriedades de áudio avançadas" Basic.MainMenu.View="Mostrar" Basic.MainMenu.View.Toolbars="&Barras de Ferramentas" +Basic.MainMenu.View.Docks="Ancorar" +Basic.MainMenu.View.Docks.ResetUI="Redefinir IU" +Basic.MainMenu.View.Docks.LockUI="Travar IU" 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.View.Fullscreen.Interface="Interface em Tela Cheia" Basic.MainMenu.SceneCollection="&Coleção de cena" Basic.MainMenu.Profile="&Perfil" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="O Grupo de Cenas já existe" Basic.MainMenu.Tools="Ferramentas (&T)" Basic.MainMenu.Help="&Ajuda" +Basic.MainMenu.Help.HelpPortal="&Portal de Ajuda" Basic.MainMenu.Help.Website="Visitar &website" Basic.MainMenu.Help.Logs="&Arquivos de Log" Basic.MainMenu.Help.Logs.ShowLogs="&Mostrar Arquivos de Log" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Bandeja do sistema" Basic.Settings.General.SysTrayWhenStarted="Minimizar para a bandeja do sistema quando iniciar" Basic.Settings.General.SystemTrayHideMinimize="Sempre minimizar para a bandeja (ignorar barra de tarefas)" Basic.Settings.General.SaveProjectors="Salvar projetores ao sair" +Basic.Settings.General.SwitchOnDoubleClick="Mudar para a cena quando clicar duas vezes" +Basic.Settings.General.StudioPortraitLayout="Ativar o layout Paisagem/Retrato" +Basic.Settings.General.MultiviewLayout="Layout do Multiview" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontal, Acima" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontal, Abaixo" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertical, à Esquerda" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertical, à Direita" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Tipo de Stream" @@ -592,7 +621,7 @@ Basic.Settings.Video.Numerator="Numerador:" Basic.Settings.Video.Denominator="Demoninador:" Basic.Settings.Video.Renderer="Renderizador:" Basic.Settings.Video.InvalidResolution="Resolução Inválida, Obrigatório ser [largura]x[altura] (ex. 1920x1080)" -Basic.Settings.Video.CurrentlyActive="Saída de vídeo está atualmente ativa. Por favor desligue quaisquer saídas para alterar as configurações de vídeo." +Basic.Settings.Video.CurrentlyActive="A saída de vídeo está atualmente ativa. Por favor, desligue quaisquer saídas para alterar as configurações de vídeo." Basic.Settings.Video.DisableAero="Desativar Aero" Basic.Settings.Video.DownscaleFilter.Bilinear="Bilinear(rápido, mas embaçado se redimensionando)" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (escalamento nítido, 32 a Basic.Settings.Audio="Áudio" Basic.Settings.Audio.SampleRate="Taxa de Amostragem" Basic.Settings.Audio.Channels="Canais" +Basic.Settings.Audio.MeterDecayRate="Taxa de Decaimento do Medidor de Áudio" +Basic.Settings.Audio.MeterDecayRate.Fast="Rápida" +Basic.Settings.Audio.MeterDecayRate.Medium="Média (PPM Tipo I)" +Basic.Settings.Audio.MeterDecayRate.Slow="Devagar (PPM Tipo II)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="AVISO: Áudio Surround está habilitado." +Basic.Settings.Audio.MultichannelWarning="Antes de transmitir, verifique se o seu serviço de transmissão suporta tanto receber como reproduzir som surround. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast são exemplos onde o som surround é totalmente suportado. Embora o Facebook Live e o YouTube Live ambos aceitem receber som surround, o Facebook Live transformará para estéreo e o YouTube Live reproduz apenas dois canais.\n\nOs filtros de áudio do OBS são compatíveis com o som surround, embora o suporte do plugin VST não seja garantido." +Basic.Settings.Audio.MultichannelWarning.Title="Ativar o som surround?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Você tem certeza de que deseja habilitar o som surround?" Basic.Settings.Audio.DesktopDevice="Dispositivo de áudio do desktop" Basic.Settings.Audio.DesktopDevice2="Dispositivo de áudio do desktop 2" Basic.Settings.Audio.AuxDevice="Dispositivo de áudio/microfone auxiliar" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Prioridade do Processo" Basic.Settings.Advanced.General.ProcessPriority.High="Alta" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Acima do Normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Abaixo do Normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Inativa" Basic.Settings.Advanced.FormatWarning="Aviso: Formatos de cor diferentes do NV12 destinam-se principalmente para gravação e não são recomendados para transmissão. Durante a transmissão pode ocorrer aumento do uso da CPU devido a conversão do formato de cor." Basic.Settings.Advanced.Audio.BufferingTime="Tempo de buffer de áudio" diff --git a/UI/data/locale/pt-PT.ini b/UI/data/locale/pt-PT.ini index 2825877..25c82a7 100644 --- a/UI/data/locale/pt-PT.ini +++ b/UI/data/locale/pt-PT.ini @@ -28,13 +28,21 @@ Browse="Localizar" Mono="Mono" Stereo="Estéreo" DroppedFrames="Quadros Perdidos %1 (%2%)" +StudioProgramProjector="Projetor de ecrã inteiro (Programa)" PreviewProjector="Projetor de ecrã inteiro (pré-visualização)" SceneProjector="Projetor de ecrã inteiro (cena)" SourceProjector="Projetor de ecrã inteiro (fonte)" +StudioProgramWindow="Projetor em modo de janela (Programa)" +PreviewWindow="Projetor em janela (pré-visualização)" +SceneWindow="Projetor em janela (Cena)" +SourceWindow="Projetor em janela (Fonte)" +MultiviewProjector="Multi-visualização (Ecrã inteiro)" +MultiviewWindowed="Multi-visualização (Modo de Janela)" Clear="Limpar" Revert="Reverter" Show="Mostrar" Hide="Ocultar" +UnhideAll="Esconder Tudo" Untitled="Sem título" New="Novo" Duplicate="Duplicar" @@ -52,13 +60,115 @@ Reset="Repor" Hours="Horas" Minutes="Minutos" Seconds="Segundos" +Deprecated="Descontinuado" +ReplayBuffer="Reproduzir Buffer" +Import="Importar" +Export="Exportar" +Copy="Copiar" +Paste="Colar" +PasteReference="Colar (Referência)" +PasteDuplicate="Colar (Duplicado)" +RemuxRecordings="Remux de gravações" +Next="Continuar" +Back="Anterior" +Defaults="Predefinições" +HideMixer="Esconder no Mixer" +TransitionOverride="Substituir Transição" +None="Nenhum" +StudioMode.Preview="Pré-visualização" +StudioMode.Program="Programa" +AlreadyRunning.Title="O OBS já está em execução" +AlreadyRunning.Text="O OBS já está em execução! Tem de desligar a instância existente do OBS antes de iniciar uma nova. Se o OBS estiver definido para correr na bandeja do sistema, por favor verifique se está aberto lá." +AlreadyRunning.LaunchAnyway="Executar de qualquer modo" +Copy.Filters="Copiar filtros" +Paste.Filters="Colar Filtros" +BandwidthTest.Region="Região" +BandwidthTest.Region.US="Estados Unidos" +BandwidthTest.Region.EU="Europa" +BandwidthTest.Region.Asia="Ásia" +BandwidthTest.Region.Other="Outro" +Basic.FirstStartup.RunWizard="Gostaria de executar o assistente de configuração automática? Pode também definir as suas configurações na janela principal." +Basic.FirstStartup.RunWizard.BetaWarning="(Nota: O assistente de configuração automática está atualmente em beta)" +Basic.FirstStartup.RunWizard.NoClicked="Se muder de ideias pode correr o assistente de configuração automática a qualquer momento no menu Ferramentas." +Basic.AutoConfig="Assistente de Configuração Automática" +Basic.AutoConfig.Beta="Assistente de Configuração Automática (Beta)" +Basic.AutoConfig.ApplySettings="Aplicar definições" +Basic.AutoConfig.StartPage="Informação de utilização" +Basic.AutoConfig.StartPage.SubTitle="Especifique qual vai ser a utilização do programa" +Basic.AutoConfig.StartPage.PrioritizeStreaming="Otimizar para transmissão, gravação é secundária" +Basic.AutoConfig.StartPage.PrioritizeRecording="Otimizar para gravação, a transmissão é secundária" +Basic.AutoConfig.VideoPage="Definições de video" +Basic.AutoConfig.VideoPage.SubTitle="Especifique as definições de video que pretende" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Usar atual (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="Monitor %1 (%2x%3)" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="Usar atual (%1)" +Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="60 ou 30, com prioridade a 60" +Basic.AutoConfig.VideoPage.FPS.PreferHighRes="60 ou 30, com prioridade a alta resolução" +Basic.AutoConfig.VideoPage.CanvasExplanation="Nota: A resolução base do canvas não é necessariamente a mesma que resolução da gravação ou stream. A resolução da gravação/stream pode ser reduzida para diminuir o uso de recursos e os requisitos de bitrate." +Basic.AutoConfig.StreamPage="Informação de Transmissão" +Basic.AutoConfig.StreamPage.SubTitle="Por favor introduza a informação necessária" +Basic.AutoConfig.StreamPage.Service="Serviço" +Basic.AutoConfig.StreamPage.Service.ShowAll="Mostrar Tudo..." +Basic.AutoConfig.StreamPage.Server="Servidor" +Basic.AutoConfig.StreamPage.StreamKey="Chave de Transmissão" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Link)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Estimar bitrate com teste de largura de banda (pode demorar alguns minutos)" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Preferir codificação de hardware" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="A Codificação de Hardware elimina maior parte da utilização do CPU mas pode precisar de uma bitrate maior para obter o mesmo nível de qualidade." +Basic.AutoConfig.StreamPage.StreamWarning.Title="Aviso de Transmissão" +Basic.AutoConfig.StreamPage.StreamWarning.Text="O teste de largura de banda vai transimitir dados de video aleatórios sem audio para o seu canal. É recomendado desligar temporariamente gravações do stream e definir o stream como privado até o teste estar completo. Continuar?" +Basic.AutoConfig.TestPage="Resultados Finais" +Basic.AutoConfig.TestPage.SubTitle.Testing="O programa está agora a executar testes para estimar as definições ideais" +Basic.AutoConfig.TestPage.SubTitle.Complete="Teste completo" +Basic.AutoConfig.TestPage.TestingBandwidth="A executar teste de largura de banda, pode demorar alguns minutos..." +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="A Conectar a: %1..." +Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Falha ao conectar a qualquer um servidor, por favor verifique a sua conexão de internet e tente novamente." +Basic.AutoConfig.TestPage.TestingBandwidth.Server="A testar largura de banda para: %1" +Basic.AutoConfig.TestPage.TestingStreamEncoder="A testar codificador de stream, poderá demorar algum tempo..." +Basic.AutoConfig.TestPage.TestingRecordingEncoder="A testar codificador de gravação, poderá demorar algum tempo..." +Basic.AutoConfig.TestPage.TestingRes="A testar resoluções, poderá demorar algum tempo..." +Basic.AutoConfig.TestPage.TestingRes.Fail="Erro ao iniciar o codificador" +Basic.AutoConfig.TestPage.TestingRes.Resolution="A testar %1x%2 %3 FPS..." +Basic.AutoConfig.TestPage.Result.StreamingEncoder="Codificador de stream" +Basic.AutoConfig.TestPage.Result.RecordingEncoder="Codificador de Gravação" +Basic.AutoConfig.TestPage.Result.Header="O programa determinou que estas definições são as mais ideais para si:" +Basic.AutoConfig.TestPage.Result.Footer="Para utilizar estas definições, carregue em Aplicar Definições. Para reconfigurar o assistente e tentar novamente, carregue em Voltar. Para configurar manualmente as definições, carregue em Cancelar e abra as Definições." +Basic.Stats="Estados" +Basic.Stats.CPUUsage="Utilização de CPU" +Basic.Stats.HDDSpaceAvailable="Espaço disponível no HDD" +Basic.Stats.MemoryUsage="Utilização de Memória" +Basic.Stats.AverageTimeToRender="Tempo médio para processar uma frame" +Basic.Stats.SkippedFrames="Frames saltadas devido ao lag do codificador" +Basic.Stats.MissedFrames="Frames perdidas devido ao lag de renderização" +Basic.Stats.Output.Stream="Transmissão" +Basic.Stats.Output.Recording="Gravação" +Basic.Stats.Status="Estado" +Basic.Stats.Status.Recording="A Gravar" +Basic.Stats.Status.Live="Ao Vivo" +Basic.Stats.Status.Reconnecting="A Reconectar" +Basic.Stats.Status.Inactive="Inactivo" +Basic.Stats.DroppedFrames="Frames perdidas (Rede)" +Basic.Stats.MegabytesSent="Total saída de dados" +Basic.Stats.Bitrate="Taxa de Bits" +Updater.Title="Nova atualização disponível" +Updater.Text="Existe uma nova atualização disponível:" +Updater.UpdateNow="Atualizar Agora" +Updater.RemindMeLater="Lembrar-me mais tarde" +Updater.Skip="Saltar Versão" +Updater.Running.Title="Programa atualmente ativo" +Updater.Running.Text="Existem Outputs ativos atualmente, por favor desligar quaisquer Outputs ativos antes de começar a atualização" +Updater.NoUpdatesAvailable.Title="Nenhuma atualização disponível" +Updater.NoUpdatesAvailable.Text="Não existem atualizações atualmente" +Updater.FailedToLaunch="Falha ao iniciar as atualizações" +Updater.GameCaptureActive.Title="Captura de Jogo ativa" +Updater.GameCaptureActive.Text="Biblioteca da captura do jogo está a ser utilizada de momento. Por favor feche qualquer jogo/programa que esteja a ser capturado (ou dê restart ao Windows) e tente novamente." QuickTransitions.SwapScenes="Trocar pré-visualização/saída de cenas Depois de uma Transição" QuickTransitions.DuplicateScene="Duplicar cena" @@ -98,6 +208,9 @@ ConfirmRemove.Title="Comfirmar Remover" ConfirmRemove.Text="Tem a certeza que quer remover '$1'?" ConfirmRemove.TextMultiple="Tem a certeza de que pretende remover %1 itens?" +Output.StartStreamFailed="Falha ao iniciar a stream" +Output.StartRecordingFailed="Falha ao iniciar a gravação" +Output.StartReplayFailed="Falha ao iniciar o buffer de repetição" Output.ConnectFail.Title="Falha ao ligar" Output.ConnectFail.BadPath="Caminho ou endereço de ligação inválido. Por favor, verifique as suas definições para confirmar que são válidas." @@ -112,6 +225,8 @@ Output.RecordNoSpace.Title="Espaço em disco insuficiente" Output.RecordNoSpace.Msg="Não há espaço em disco suficiente para continuar a gravação." Output.RecordError.Title="Erro de gravação" Output.RecordError.Msg="Ocorreu um erro desconhecido durante a gravação." +Output.ReplayBuffer.NoHotkey.Title="Nenhuma atalho adicionado!" +Output.ReplayBuffer.NoHotkey.Msg="Nenhum atalho gravado para o buffer de repetição. Por favor defina o atalho \"Gravar\" para gravar as repetições da gravação." Output.BadPath.Title="Caminho de Ficheiro de Gravação Inválido" Output.BadPath.Text="O caminho de ficheiro de gravação definido é inválido. Por favor verifique as definições e confirme que um caminho válido foi introduzido." @@ -155,6 +270,11 @@ Basic.DisplayCapture="Captura de Ecrã" Basic.Main.PreviewConextMenu.Enable="Ativar pré-visualização" +ScaleFiltering="Filtragem de escala" +ScaleFiltering.Point="Ponto" +ScaleFiltering.Bilinear="Bilinear" +ScaleFiltering.Bicubic="Bicubico" +ScaleFiltering.Lanczos="Lanczos" Deinterlacing="Desentrelaçamento" Deinterlacing.Discard="Discartar" @@ -168,6 +288,9 @@ Deinterlacing.Yadif2x="Yadif 2x" Deinterlacing.TopFieldFirst="Campo Superior Primeiro" Deinterlacing.BottomFieldFirst="Campo Inferior Primeiro" +VolControl.SliderUnmuted="Barra de volume para '%1': %2" +VolControl.Mute="Silêncio '%1'" +VolControl.Properties="Propriedades de '%1'" Basic.Main.AddSceneDlg.Title="Adicionar Cena" Basic.Main.AddSceneDlg.Text="Por favor introduza o nome da cena" @@ -184,6 +307,9 @@ AddProfile.Text="Por favor, introduza o nome do perfil" RenameProfile.Title="Renomear perfil" +Basic.Main.MixerRename.Title="Renomear fonte de áudio" + + Basic.Main.PreviewDisabled="A pré-visualização está desativada atualmente" Basic.SourceSelect="Criar/Selecionar Fonte" @@ -261,6 +387,7 @@ Basic.Main.AddSourceHelp.Text="Precisa de pelo menos uma cena para adicionar uma Basic.Main.Scenes="Cenas" Basic.Main.Sources="Fontes" +Basic.Main.Controls="Controlos" Basic.Main.Connecting="A ligar..." Basic.Main.StartRecording="Começar Gravação" Basic.Main.StartStreaming="Iniciar transmissão" @@ -289,6 +416,8 @@ Basic.MainMenu.Edit.RedoAction="&Refazer $1" Basic.MainMenu.Edit.LockPreview="B&loquear pré-visualização" Basic.MainMenu.Edit.Transform="&Transformar" Basic.MainMenu.Edit.Transform.EditTransform="&Editar Transformação..." +Basic.MainMenu.Edit.Transform.CopyTransform="Copiar e Transformar" +Basic.MainMenu.Edit.Transform.PasteTransform="Colar e Transformar" Basic.MainMenu.Edit.Transform.ResetTransform="&Reset Transform" Basic.MainMenu.Edit.Transform.Rotate90CW="Rodar 90 graus CW" Basic.MainMenu.Edit.Transform.Rotate90CCW="Rodar 90 graus CCW" @@ -307,11 +436,18 @@ Basic.MainMenu.Edit.AdvAudio="Propriedades &avançadas de áudio" Basic.MainMenu.View="&Ver" Basic.MainMenu.View.Toolbars="Barras de ferramen&tas" +Basic.MainMenu.View.Docks="Âncoras" +Basic.MainMenu.View.Docks.ResetUI="Reiniciar Interface" +Basic.MainMenu.View.Docks.LockUI="Bloquear Interface" Basic.MainMenu.View.SceneTransitions="Transições de &cenas" Basic.MainMenu.View.StatusBar="&Barra de estado" +Basic.MainMenu.View.Fullscreen.Interface="Interface de ecrã inteiro" Basic.MainMenu.SceneCollection="Coleção de cena" Basic.MainMenu.Profile="&Perfil" +Basic.MainMenu.Profile.Import="Importar perfil" +Basic.MainMenu.Profile.Export="Exportar Perfil" +Basic.MainMenu.Profile.Exists="O perfil já existe" Basic.MainMenu.Tools="&Ferramentas" @@ -331,13 +467,21 @@ Basic.Settings.Confirm="Voçê tem alterações não salvadas. Deseja salvar as Basic.Settings.General="Geral" Basic.Settings.General.Theme="Tema" Basic.Settings.General.Language="Idioma" +Basic.Settings.General.EnableAutoUpdates="Verificar atualizações no arranque" +Basic.Settings.General.OpenStatsOnStartup="Abrir diálogo de estatísticas no arranque" Basic.Settings.General.WarnBeforeStartingStream="Mostrar caixa de diálogo de confirmação ao iniciar transmissões" Basic.Settings.General.WarnBeforeStoppingStream="Mostrar caixa de diálogo de confirmação ao parar transmissões" +Basic.Settings.General.Projectors="Projetores" Basic.Settings.General.Snapping="Alinhamentos com encaixe na Cena" Basic.Settings.General.SnapDistance="Sensibilidade do Snap" Basic.Settings.General.RecordWhenStreaming="Gravar automaticamente quando estiver a transmitir" Basic.Settings.General.KeepRecordingWhenStreamStops="Continuar a gravar quando a transmissão parar" +Basic.Settings.General.SysTray="Bandeja do Sistema" Basic.Settings.General.SysTrayWhenStarted="Minimizar para a área de notificações quando iniciado" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontal, Topo" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontal, Inferior" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertical, Esquerda" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertical, Direita" Basic.Settings.Stream="Transmissão" Basic.Settings.Stream.StreamType="Tipo de transmissão" @@ -352,6 +496,8 @@ Basic.Settings.Output.Mode="Modo de saída" Basic.Settings.Output.Mode.Simple="Simples" Basic.Settings.Output.Mode.Adv="Avançado" Basic.Settings.Output.Mode.FFmpeg="Saída FFmpeg" +Basic.Settings.Output.ReplayBuffer.Prefix="Buffer de repetição de nome prefixo" +Basic.Settings.Output.ReplayBuffer.Suffix="Sufixo" Basic.Settings.Output.Simple.SavePath="Caminho da gravação" Basic.Settings.Output.Simple.RecordingQuality="Qualidade da gravação" Basic.Settings.Output.Simple.RecordingQuality.Stream="A mesma da transmissão" @@ -362,6 +508,7 @@ Basic.Settings.Output.Simple.Warn.Lossless.Msg="Tem a certeza de que pretende ut Basic.Settings.Output.Simple.Warn.Lossless.Title="Aviso de qualidade sem perda!" 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 baixa utilização da CPU pré-ajustada, aumenta o tamanho do arquivo)" Basic.Settings.Output.VideoBitrate="Bitrate de Vídeo" @@ -383,6 +530,8 @@ Basic.Settings.Output.Adv.Audio.Track1="Faixa 1" Basic.Settings.Output.Adv.Audio.Track2="Faixa 2" Basic.Settings.Output.Adv.Audio.Track3="Faixa 3" Basic.Settings.Output.Adv.Audio.Track4="Faixa 4" +Basic.Settings.Output.Adv.Audio.Track5="Faixa 5" +Basic.Settings.Output.Adv.Audio.Track6="Faixa 6" Basic.Settings.Output.Adv.Recording="Gravação" Basic.Settings.Output.Adv.Recording.Type="Tipo" @@ -438,6 +587,8 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (escalamento nítido, 32 a Basic.Settings.Audio="Áudio" Basic.Settings.Audio.SampleRate="Frequência de Samplagem" Basic.Settings.Audio.Channels="Canias" +Basic.Settings.Audio.MultichannelWarning.Title="Ativar audio surround?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Tem a certeza que quer ativar audio surround?" Basic.Settings.Audio.DesktopDevice="Desktop Audio Device" Basic.Settings.Audio.DesktopDevice2="Desktop Audio Device 2" Basic.Settings.Audio.AuxDevice="Mic/Auxiliary Audio Device" @@ -454,6 +605,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Prioridade do precesso" Basic.Settings.Advanced.General.ProcessPriority.High="Alta" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Acima do normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Abaixo do normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Inativo" Basic.Settings.Advanced.FormatWarning="Aviso: Formatos de cor diferentes de NV12 destinam-se principalmente a gravação e não são recomendados durante a transmissão. A transmissão pode incorrer numa maior utilização do processador devido à conversão do formato de cor." Basic.Settings.Advanced.Audio.BufferingTime="Tempo de carregamento do áudio" @@ -462,12 +614,17 @@ Basic.Settings.Advanced.Video.ColorSpace="Espaço de cor YUV" Basic.Settings.Advanced.Video.ColorRange="Gama de cor YUV" Basic.Settings.Advanced.Video.ColorRange.Partial="Parcial" Basic.Settings.Advanced.Video.ColorRange.Full="Total" +Basic.Settings.Advanced.Audio.MonitoringDevice="Dispositivo de monitorização de audio" +Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Predefinição" +Basic.Settings.Advanced.Audio.DisableAudioDucking="Desativar o ducking de audio do Windows" Basic.Settings.Advanced.StreamDelay="Atraso na trasmissão" Basic.Settings.Advanced.StreamDelay.Duration="Duração (segundos)" Basic.Settings.Advanced.StreamDelay.Preserve="Preservar o ponto de corte (aumentar atraso) quando reconectar" Basic.Settings.Advanced.StreamDelay.MemoryUsage="Utilização estimada de memória: %1 MB" Basic.Settings.Advanced.Network="Rede" Basic.Settings.Advanced.Network.BindToIP="Ligar pelo IP" +Basic.Settings.Advanced.Network.EnableNewSocketLoop="Ativar nova codificação de net" +Basic.Settings.Advanced.Network.EnableLowLatencyMode="Modo de baixa latência" Basic.AdvAudio="Propriedades avançadas de áudio" Basic.AdvAudio.Name="Nome" @@ -475,6 +632,10 @@ Basic.AdvAudio.Volume="Volume (%)" Basic.AdvAudio.Mono="Diminuir para mono" Basic.AdvAudio.Panning="Desproporcionar" Basic.AdvAudio.SyncOffset="Atraso de sincronização (ms)" +Basic.AdvAudio.Monitoring="Monitorização de Áudio" +Basic.AdvAudio.Monitoring.None="Monitor Desligado" +Basic.AdvAudio.Monitoring.MonitorOnly="Único Monitor (saída muda)" +Basic.AdvAudio.Monitoring.Both="Monitor e Saída" Basic.AdvAudio.AudioTracks="Faixas" Basic.Settings.Hotkeys="Teclas de atalho" @@ -534,4 +695,5 @@ SceneItemHide="Ocultar '%1'" OutputWarnings.NoTracksSelected="Tem de selecionar pelo menos uma faixa" OutputWarnings.MultiTrackRecording="Aviso: Alguns formatos (como FLV) não suportam várias faixas por gravação" +FinalScene.Title="Apagar Cena" diff --git a/UI/data/locale/ro-RO.ini b/UI/data/locale/ro-RO.ini index 90982d9..0c3b094 100644 --- a/UI/data/locale/ro-RO.ini +++ b/UI/data/locale/ro-RO.ini @@ -35,6 +35,7 @@ Clear="Șterge" Revert="Inversează" Show="Arată" Hide="Ascunde" +UnhideAll="Reafișare totală" Untitled="Fără nume" New="Nou" Duplicate="Duplică" @@ -48,19 +49,67 @@ Left="Stânga" Right="Dreapta" Top="Sus" Bottom="Jos" +Reset="Resetează" Hours="Ore" Minutes="Minute" Seconds="Secunde" Deprecated="Invechit" +ReplayBuffer="Reluare Buffer" Import="Importă" Export="Exportă" +Copy="Copiază" +Paste="Inserare" +PasteReference="Inserare (Referință)" +PasteDuplicate="Inserare (Duplicare)" +RemuxRecordings="Înregistrări Remux" +Next="Următorul" +Back="Înapoi" +Defaults="Prestabilite" +HideMixer="Ascunde în Mixer" +AlreadyRunning.Title="OBS ruleaza deja" +AlreadyRunning.Text="OBS rulează deja! În cazul în care ați încercat sa faceți acest lucru, vă rugăm să închideți toate instanțele OBS înainte de a porni una noua. Dacă aveți OBS-ul setat pentru a se minimiza in system tray vă rugam sa verificați daca inca ruleaza acolo." +AlreadyRunning.LaunchAnyway="Lansați Oricum" +Copy.Filters="Copiază filtrele" +Paste.Filters="Lipește filtrele" +BandwidthTest.Region="Regiune" +BandwidthTest.Region.US="Statele Unite" +BandwidthTest.Region.EU="Europa" +BandwidthTest.Region.Asia="Asia" +BandwidthTest.Region.Other="Alta" +Basic.FirstStartup.RunWizard="Dorești să rulezi expertul de configurare automată? Poți, de asemenea, să configurezi setările manual prin apăsarea butonului Setări din fereastra principală." +Basic.FirstStartup.RunWizard.BetaWarning="(Notă: Expertul de configurare este în prezent în versiune beta)" +Basic.AutoConfig="Expertul de Configurare-Automata" +Basic.AutoConfig.Beta="Expertul de Configurare-Automata (Beta)" +Basic.AutoConfig.ApplySettings="Aplicați setarile" +Basic.AutoConfig.StartPage="Informații de utilizare" +Basic.AutoConfig.StartPage.SubTitle="Specificați pentru ce vreți sa folosiți programul" +Basic.AutoConfig.VideoPage="Setări video" +Basic.AutoConfig.VideoPage.SubTitle="Specifică setările video dorite pe care ai dori să le folosești" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Utilizare curentă (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="Afișați %1 (%2x%3)" +Basic.AutoConfig.StreamPage="Flux de informații" +Basic.AutoConfig.StreamPage.Service="Service" +Basic.AutoConfig.StreamPage.Service.ShowAll="Arătați tot..." +Basic.AutoConfig.StreamPage.Server="Server" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Link)" +Basic.AutoConfig.StreamPage.PerformBandwidthTest="Estimează rata de biți cu un test al lățimii de bandă (poate dura câteva minute)" +Basic.AutoConfig.TestPage="Rezultate finale" +Basic.AutoConfig.TestPage.SubTitle.Complete="Testare completă" +Basic.AutoConfig.TestPage.TestingRes="Testare rezoluții, aceasta ar putea dura câteva minute..." +Basic.Stats="Statistici" +Basic.Stats.HDDSpaceAvailable="Spațiu HDD disponibil" +Basic.Stats.Status.Live="ÎN DIRECT" +Basic.Stats.Status.Inactive="Inactiv" +Basic.Stats.Bitrate="Rată de biți" +Updater.UpdateNow="Actualizați acum" +Updater.RemindMeLater="Amintește-mi mai târziu" QuickTransitions.SwapScenes="Comută între previzualizare/scenele de ieșire după tranziționare" QuickTransitions.SwapScenesTT="Schimba previzualizarea si scenele de output dupa tranzitionare (în cazul în care încă există outputul scenei originale). \nAceasta nu va anula nicio modificăre care au fost făcute la outputul scenei originale." @@ -159,6 +208,7 @@ Basic.DisplayCapture="Captură de display" Basic.Main.PreviewConextMenu.Enable="Activează previzualizarea" +ScaleFiltering.Point="Punct" ScaleFiltering.Bilinear="Biliniar" ScaleFiltering.Bicubic="Bicubic" ScaleFiltering.Lanczos="Lanczos" @@ -175,6 +225,7 @@ Deinterlacing.Yadif2x="Yadif 2x" Deinterlacing.TopFieldFirst="Câmpul de sus prima oară" Deinterlacing.BottomFieldFirst="Câmpul de jos prima oară" +VolControl.Mute="Mut '%1'" Basic.Main.AddSceneDlg.Title="Adaugă scenă" Basic.Main.AddSceneDlg.Text="Te rugăm să introduci numele scenei" @@ -191,6 +242,8 @@ AddProfile.Text="Te rugăm să introduci numele profilului" RenameProfile.Title="Redenumește profilul" + + Basic.Main.PreviewDisabled="Previzualizarea este în prezent dezactivată" Basic.SourceSelect="Creează/Selectează sursa" @@ -470,6 +523,7 @@ Basic.Settings.Advanced.Video.ColorSpace="Spațiu de culori YUV" Basic.Settings.Advanced.Video.ColorRange="Gamă de culori YUV" Basic.Settings.Advanced.Video.ColorRange.Partial="Parțială" Basic.Settings.Advanced.Video.ColorRange.Full="Completă" +Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Implicit" Basic.Settings.Advanced.StreamDelay="Întârziere pentru stream" Basic.Settings.Advanced.StreamDelay.Duration="Durată (secunde)" Basic.Settings.Advanced.StreamDelay.Preserve="Păstrează punctul de tăiere (crește întârzierea) la reconectare" @@ -542,4 +596,5 @@ SceneItemHide="Ascunde „%1”" OutputWarnings.NoTracksSelected="Trebuie să selectezi cel puțin o pistă" OutputWarnings.MultiTrackRecording="Atenție: Anumite formate (precum FLV) nu suportă multiple piste per înregistrare" +FinalScene.Text="Trebuie să existe cel puțin o scenă." diff --git a/UI/data/locale/ru-RU.ini b/UI/data/locale/ru-RU.ini index 10766b3..7462e48 100644 --- a/UI/data/locale/ru-RU.ini +++ b/UI/data/locale/ru-RU.ini @@ -28,16 +28,21 @@ Browse="Обзор" Mono="Моно" Stereo="Стерео" DroppedFrames="Пропуск кадров %1 (%2%)" +StudioProgramProjector="Полноэкранный проектор (программа)" PreviewProjector="Полноэкранный проектор (предпросмотр)" SceneProjector="Полноэкранный проектор (сцена)" SourceProjector="Полноэкранный проектор (источник)" +StudioProgramWindow="Оконный проектор (программа)" PreviewWindow="Оконный проектор (Предпросмотр)" SceneWindow="Оконный проектор (Сцена)" SourceWindow="Оконный проектор (Источник)" +MultiviewProjector="Мульти-обзор (Полноэкранный)" +MultiviewWindowed="Мульти-обзор (Оконный)" Clear="Очистить" Revert="Вернуть" Show="Показать" Hide="Скрыть" +UnhideAll="Показать все" Untitled="Безымянный" New="Создать" Duplicate="Дублировать" @@ -66,6 +71,13 @@ PasteDuplicate="Вставить (Дубликат)" RemuxRecordings="Ремультиплексирование записей" Next="Далее" Back="Назад" +Defaults="По умолчанию" +HideMixer="Скрыть в микшер" +TransitionOverride="Переопределение перехода" +None="Нет" +StudioMode.Preview="Предпросмотр" +StudioMode.Program="Программа" +ShowInMultiview="Отображать в Мульти-обзоре" AlreadyRunning.Title="OBS уже запущен" AlreadyRunning.Text="OBS уже запущен! Пожалуйста, закройте все запущенные экземпляры OBS перед попыткой запустить новые (только если вы не хотели именно этого). Если вы настроили OBS на сворачивание в системный трей, пожалуйста, проверьте, возможно он до сих пор запущен." @@ -300,6 +312,10 @@ AddProfile.Text="Пожалуйста, введите имя профиля" RenameProfile.Title="Переименовать профиль" +Basic.Main.MixerRename.Title="Переименовать источник аудио" +Basic.Main.MixerRename.Text="Пожалуйста, введите имя источника аудио" + + Basic.Main.PreviewDisabled="В настоящее время предпросмотр выключен" Basic.SourceSelect="Создать/Выбрать источник" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Вы должны создать по крайн Basic.Main.Scenes="Сцены" Basic.Main.Sources="Источники" +Basic.Main.Controls="Управление" Basic.Main.Connecting="Соединение..." Basic.Main.StartRecording="Начать запись" Basic.Main.StartReplayBuffer="Запустить повтор" @@ -430,11 +447,15 @@ Basic.MainMenu.Edit.Order.MoveToTop="Переместить &Наверх" Basic.MainMenu.Edit.Order.MoveToBottom="Переместить &Вниз" Basic.MainMenu.Edit.AdvAudio="&Расширенные свойства аудио" -Basic.MainMenu.View="&Посмотреть" +Basic.MainMenu.View="&Вид" Basic.MainMenu.View.Toolbars="&Панель инструментов" +Basic.MainMenu.View.Docks="Док-панели" +Basic.MainMenu.View.Docks.ResetUI="Сбросить интерфейс" +Basic.MainMenu.View.Docks.LockUI="Зафиксировать интерфейс" Basic.MainMenu.View.Toolbars.Listboxes="&Списки" Basic.MainMenu.View.SceneTransitions="Сцена переходов" Basic.MainMenu.View.StatusBar="&Строка состояния" +Basic.MainMenu.View.Fullscreen.Interface="Полный экран" Basic.MainMenu.SceneCollection="Коллекция сцен" Basic.MainMenu.Profile="Профиль" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Коллекция сцен уже сущ Basic.MainMenu.Tools="&Инструменты" Basic.MainMenu.Help="&Справка" +Basic.MainMenu.Help.HelpPortal="&Портал помощи" Basic.MainMenu.Help.Website="Посетить &веб-сайт" Basic.MainMenu.Help.Logs="&Log файлы" Basic.MainMenu.Help.Logs.ShowLogs="&Показать лог-файлы" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Системный трей" Basic.Settings.General.SysTrayWhenStarted="Скрывать окно в системный трей при запуске" Basic.Settings.General.SystemTrayHideMinimize="Всегда сворачивать в трей вместо панели задач" Basic.Settings.General.SaveProjectors="Сохранять проекторы при выходе" +Basic.Settings.General.SwitchOnDoubleClick="Переход к сцене при двойном щелчке" +Basic.Settings.General.StudioPortraitLayout="Включить портретное/вертикальное расположение" +Basic.Settings.General.MultiviewLayout="Размещение мульти-обзора" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Горизонтально, вверху" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Горизонтально, внизу" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Вертикально, слева" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Вертикально, справа" Basic.Settings.Stream="Вещание" Basic.Settings.Stream.StreamType="Тип вещания" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Метод Ланцоша (Чёт Basic.Settings.Audio="Аудио" Basic.Settings.Audio.SampleRate="Частота дискретизации" Basic.Settings.Audio.Channels="Каналы" +Basic.Settings.Audio.MeterDecayRate="Скорость спада аудиометра" +Basic.Settings.Audio.MeterDecayRate.Fast="Быстро" +Basic.Settings.Audio.MeterDecayRate.Medium="Средне (PPM типа I)" +Basic.Settings.Audio.MeterDecayRate.Slow="Медленно (PPM типа II)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="ВНИМАНИЕ: Объемный звук включен." +Basic.Settings.Audio.MultichannelWarning="При запуске стрима проверьте, поддерживает ли ваш сервис потокового вещания объемный звук (как запись, так и воспроизведение). Twitch, Facebook 360 Live, Mixer RTMP, Smashcast — примеры сервисов, полностью поддерживающих объемное звучание. Несмотря на то, что Facebook Live и Youtube Live принимают объёмный звук, Facebook Live микширует его в стерео, а Youtube Live воспроизводит только два канала.\n\nЗвуковые фильтры OBS совместимы с объемным звучанием, хотя поддержка плагинов VST не гарантируется." +Basic.Settings.Audio.MultichannelWarning.Title="Включить объемный звук?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Хотите включить объемный звук?" Basic.Settings.Audio.DesktopDevice="Desktop Аудиоустройство" Basic.Settings.Audio.DesktopDevice2="Desktop Аудиоустройство 2" Basic.Settings.Audio.AuxDevice="Mic/Auxiliary Аудиоустройство" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Приоритет процес Basic.Settings.Advanced.General.ProcessPriority.High="Высокий" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Выше нормального" Basic.Settings.Advanced.General.ProcessPriority.Normal="Средний" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Ниже среднего" Basic.Settings.Advanced.General.ProcessPriority.Idle="Низкий" Basic.Settings.Advanced.FormatWarning="Предупреждение: цветовые форматы, отличные от NV12, прежде всего предназначены для записи и не рекомендуются для потокового вещания. Преобразование цветового формата во время вещания может потребовать дополнительную нагрузку на ЦП." Basic.Settings.Advanced.Audio.BufferingTime="Время буферизации аудио" diff --git a/UI/data/locale/sk-SK.ini b/UI/data/locale/sk-SK.ini index 9624d94..fc9bb99 100644 --- a/UI/data/locale/sk-SK.ini +++ b/UI/data/locale/sk-SK.ini @@ -14,6 +14,7 @@ No="Nie" Add="Pridať" Remove="Odobrať" Rename="Premenovať" +Interact="Interakcia" Filters="Filtre" Properties="Vlastnosti" MoveUp="Posunúť vyššie" @@ -27,14 +28,28 @@ Browse="Prehľadávať" Mono="Mono" Stereo="Stereo" DroppedFrames="Vynechané snímky %1 (%2%)" +StudioProgramProjector="Celo-obrazovkový projektor (program)" +PreviewProjector="Celo-obrazovkový projektor (náhľad)" +SceneProjector="Celo-obrazovkový projektor (scéna)" +SourceProjector="Celo-obrazovkový projektor (zdroj)" +StudioProgramWindow="Projektor v okne (program)" +PreviewWindow="Projektor v okne (náhľad)" +SceneWindow="Projektor v okne (scéna)" +SourceWindow="Projektor v okne (zdroj)" +MultiviewProjector="Multiview (celá obrazovka)" +MultiviewWindowed="Multiview (okno)" Clear="Vymazať" Revert="Vrátiť" Show="Zobraziť" Hide="Skryť" +UnhideAll="Odkryť Všetky" Untitled="Bez názvu" New="Nový" Duplicate="Duplikovať" Enable="Povoliť" +DisableOSXVSync="Vypnúť OSX V-Sync" +ResetOSXVSyncOnExit="Resetovať OSX V-Sync po zavretí" +HighResourceUsage="Kódovanie preťažené! Uvažujte o znížení nastavení videa alebo vyberte rýchlejší preset kódovania." Transition="Prechod" QuickTransitions="Rýchle prechody" Left="Vľavo" @@ -45,19 +60,113 @@ Reset="Vynulovať" Hours="Hodín" Minutes="Minúty" Seconds="Sekundy" +Deprecated="Zastaralé" +ReplayBuffer="Medzipamäť znovuprehratia" +Import="Importovať" +Export="Exportovať" +Copy="Kopírovať" +Paste="Vložiť" +PasteReference="Vložiť (odkaz)" +PasteDuplicate="Vložiť (duplikát)" +RemuxRecordings="Multiplexné nahrávky" +Next="Ďalej" +Back="Späť" +Defaults="Predvolené" +HideMixer="Skryť v zmiešavači" +TransitionOverride="Nahradiť prechod" +None="Nič" +StudioMode.Preview="Náhľad" +StudioMode.Program="Program" +ShowInMultiview="Zobraziť v Multiview" +AlreadyRunning.Title="OBS je už spustený" +AlreadyRunning.Text="OBS je už spustená! Prosím vypnite všetky existujúce inštancie OBS pred pokusom o spustenie novej inštancie. Ak máte OBS minimalizovaný do systémovej lišty, prosím skontrolujte či tam stále beží." +AlreadyRunning.LaunchAnyway="Napriek tomu spustiť" +Copy.Filters="Kopírovať filtre" +Paste.Filters="Vložiť filtre" +BandwidthTest.Region="Región" +BandwidthTest.Region.US="Spojené Štáty Americké" +BandwidthTest.Region.EU="Európa" +BandwidthTest.Region.Asia="Ázia" +BandwidthTest.Region.Other="Iné" +Basic.FirstStartup.RunWizard="Chcete spustiť sprievodcu automatickej konfigurácie? Nastavenie môžete nakonfigurovať aj manuálne kliknutím na tlačidlo nastavenia v hlavnom okne." +Basic.FirstStartup.RunWizard.BetaWarning="(Poznámka: Sprievodca automatickou konfiguráciou je v súčasnosti v beta verzii)" +Basic.FirstStartup.RunWizard.NoClicked="Ak zmeníte názor, môžete spustiť sprievodcu automatickou konfiguráciou kedykoľvek znova z ponuky Nástroje." +Basic.AutoConfig="Sprievodca automatickou konfiguráciou" +Basic.AutoConfig.Beta="Sprievodca automatickou konfiguráciou (Beta)" +Basic.AutoConfig.ApplySettings="Použiť nastavenia" +Basic.AutoConfig.StartPage="Informácie o používaní" +Basic.AutoConfig.StartPage.SubTitle="Vyberte na čo chcete program použiť" +Basic.AutoConfig.StartPage.PrioritizeStreaming="Optimalizovať pre streamovanie, nahrávanie je sekundárne" +Basic.AutoConfig.StartPage.PrioritizeRecording="Optimalizovať len pre nahrávanie, streamovať nebudem" +Basic.AutoConfig.VideoPage="Nastavenia videa" +Basic.AutoConfig.VideoPage.SubTitle="Vyberte nastavenia videa, ktoré by ste chceli používať" +Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Použiť aktuálne (%1x%2)" +Basic.AutoConfig.VideoPage.BaseResolution.Display="Monitor %1 (%2x%3)" +Basic.AutoConfig.VideoPage.FPS.UseCurrent="Použiť aktuálne (%1)" +Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="60 alebo 30, ale radšej 60, ak je to možné" +Basic.AutoConfig.VideoPage.FPS.PreferHighRes="60 alebo 30, ale radšej vysoké rozlíšenie" +Basic.AutoConfig.VideoPage.CanvasExplanation="Poznámka: rozlíšenie (základné) plátna nie je nevyhnutne rovnaké ako rozlíšenie v ktorom budete streamovať alebo nahrávať. Skutočné streamovacie/nahrávacie rozlíšenie môže byť zmenšené preto aby sa znížilo používanie dát alebo zdrojov." +Basic.AutoConfig.StreamPage="Informácie o streame" +Basic.AutoConfig.StreamPage.Service="Služba" +Basic.AutoConfig.StreamPage.Service.ShowAll="Zobraziť všetky..." +Basic.AutoConfig.StreamPage.Server="Server" +Basic.AutoConfig.StreamPage.StreamKey="Streamovací kľúč" +Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Odkaz)" +Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Preferovať hardvérové enkódovanie" +Basic.AutoConfig.StreamPage.StreamWarning.Title="Upozornenie o streame" +Basic.AutoConfig.TestPage="Konečné výsledky" +Basic.AutoConfig.TestPage.SubTitle.Complete="Testovanie dokončené" +Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Pripájanie k: %1..." +Basic.AutoConfig.TestPage.TestingStreamEncoder="Testovanie streamovacieho enkodéra, toto môže trvať minútu..." +Basic.AutoConfig.TestPage.TestingRecordingEncoder="Testovanie nahrávacieho enkodéra, toto môže trvať minútu..." +Basic.AutoConfig.TestPage.TestingRes="Testovanie rozlíšení, toto môže trvať niekoľko minút..." +Basic.AutoConfig.TestPage.TestingRes.Fail="Nepodarilo sa spustiť enkodér" +Basic.AutoConfig.TestPage.TestingRes.Resolution="Testovanie %1x%2 %3 FPS..." +Basic.AutoConfig.TestPage.Result.StreamingEncoder="Streamovací Enkodér" +Basic.AutoConfig.TestPage.Result.RecordingEncoder="Nahrávací Enkodér" +Basic.Stats="Štatistiky" +Basic.Stats.CPUUsage="Využitie CPU" +Basic.Stats.HDDSpaceAvailable="Dostupné miesto na HDD" +Basic.Stats.MemoryUsage="Využitie pamäte" +Basic.Stats.Output.Stream="Stream" +Basic.Stats.Output.Recording="Nahrávanie" +Basic.Stats.Status="Stav" +Basic.Stats.Status.Recording="Nahrávanie" +Basic.Stats.Status.Live="NAŽIVO" +Basic.Stats.Status.Reconnecting="Opätovné pripájanie" +Basic.Stats.Status.Inactive="Neaktívne" +Basic.Stats.DroppedFrames="Stratené snímky (sieť)" +Basic.Stats.MegabytesSent="Celkový dátový výstup" +Basic.Stats.Bitrate="Dátový tok" +Updater.Title="Nová aktualizácia k dispozícii" +Updater.Text="Je dostupná nová aktualizácia:" +Updater.UpdateNow="Aktualizovať teraz" +Updater.RemindMeLater="Pripomenúť neskôr" +Updater.Skip="Preskočiť verziu" +Updater.Running.Title="Program momentálne aktívny" +Updater.NoUpdatesAvailable.Title="Neboli nájdené žiadne aktualizácie" +Updater.NoUpdatesAvailable.Text="Nie sú momentálne k dispozícii žiadne aktualizácie" +Updater.FailedToLaunch="Nepodarilo sa spustiť aktualizátor" +Updater.GameCaptureActive.Title="Zachytenie hry aktívne" QuickTransitions.DuplicateScene="Duplikovať scénu" +QuickTransitions.EditProperties="Duplikovať zdroje" +QuickTransitions.HotkeyName="Rýchly prechod: %1" +Basic.TransitionProperties="Vlastnosti prechodu" +Basic.SceneTransitions="Prechody medzi scénami" Basic.TransitionDuration="Trvanie" Basic.TogglePreviewProgramMode="Štúdiový režim" +TransitionNameDlg.Text="Prosím, zadajte názov prechodu" +TransitionNameDlg.Title="Názov prechodu" TitleBar.Profile="Profil" TitleBar.Scenes="Scéna" @@ -79,19 +188,27 @@ ConfirmExit.Text="OBS je momentálne aktívny. Všetky prúdy údajov/záznamy ConfirmRemove.Title="Potvrdenie odobratia" ConfirmRemove.Text="Naozaj si prajete odobrať '$1'?" +ConfirmRemove.TextMultiple="Naozaj si prajete odstrániť %1 položiek?" +Output.StartStreamFailed="Nepodarilo sa spustiť streamovanie" +Output.StartRecordingFailed="Nepodarilo sa spustiť nahrávanie" +Output.StartReplayFailed="Nepodarilo sa spustiť medzipamäť znovunahrávania" Output.ConnectFail.Title="Spojenie sa nepodarilo" Output.ConnectFail.BadPath="Neplatná cesta alebo URL. Prosím, skontrolujte, či sú vaše nastavenia správne." Output.ConnectFail.ConnectFailed="Spojenie so serverom sa nepodarilo" +Output.ConnectFail.Disconnected="Odpojený od servera." Output.RecordFail.Title="Nepodarilo sa spustiť nahrávanie" Output.RecordNoSpace.Title="Nedostatok miesta na disku" Output.RecordError.Title="Chyba nahrávania" +Output.ReplayBuffer.NoHotkey.Title="Nepriradená žiadna klávesová skratka!" Output.BadPath.Title="Nesprávna cesta k súboru" +LogReturnDialog="Nahranie logu bolo úspešné" LogReturnDialog.CopyURL="Kopírovať URL" +LogReturnDialog.ErrorUploadingLog="Chyba pri nahrávaní súboru denníka chýb" LicenseAgreement="Licenčná zmluva" LicenseAgreement.IAgree="Súhlasím" @@ -108,13 +225,27 @@ Remux.FileExists="Cieľový súbor už existuje, chcete ho nahradiť?" UpdateAvailable="Je dostupná aktualizácia" UpdateAvailable.Text="Verzia %1.%2.%3 je dostupná. Kliknite sem na jej stiahnutie" +Basic.AuxDevice1="Mik/Aux" +Basic.AuxDevice2="Mik/Aux 2" Basic.Scene="Scéna" Basic.DisplayCapture="Zachytávanie monitora" +Basic.Main.PreviewConextMenu.Enable="Zapnúť náhľad" +ScaleFiltering.Point="Bodové" +ScaleFiltering.Bilinear="Bilineárne" +ScaleFiltering.Bicubic="Bikubické" +ScaleFiltering.Lanczos="Lanczos" +Deinterlacing="Odstránenie prekladania" +Deinterlacing.Retro="Retro" +Deinterlacing.Yadif="Yadif" +Deinterlacing.Yadif2x="Yadif 2x" +VolControl.SliderMuted="Ovládanie hlasitosti pre '%1': %2 (momentálne umlčané)" +VolControl.Mute="Umlčať '%1'" +VolControl.Properties="Vlastnosti pre '%1'" Basic.Main.AddSceneDlg.Title="Pridať scénu" Basic.Main.AddSceneDlg.Text="Prosím, zadajte názov scény" @@ -125,21 +256,48 @@ Basic.Main.DefaultSceneName.Text="Scéna %1" AddProfile.Title="Pridanie profilu" +RenameProfile.Title="Premenovať profil" + +Basic.Main.PreviewDisabled="Náhľad je momentálne vypnutý" + Basic.SourceSelect="Vytvoriť/Vybrať zdroj" Basic.SourceSelect.CreateNew="Vytvoriť nový" Basic.SourceSelect.AddExisting="Pridať existujúci" Basic.PropertiesWindow="Vlastnosti pre '%1'" +Basic.PropertiesWindow.AutoSelectFormat="%1 (Automatický výber: %2)" Basic.PropertiesWindow.SelectColor="Vybrať farbu" Basic.PropertiesWindow.SelectFont="Vybrať písmo" +Basic.PropertiesWindow.ConfirmTitle="Nastavenia zmenené" +Basic.PropertiesWindow.Confirm="Existujú neuložené zmeny. Chcete ich ponechať?" +Basic.PropertiesWindow.NoProperties="Žiadne dostupné vlastnosti" +Basic.PropertiesWindow.AddFiles="Pridať súbory" +Basic.PropertiesWindow.AddDir="Pridať adresár" +Basic.PropertiesWindow.AddURL="Pridať cestu/URL" +Basic.PropertiesWindow.AddEditableListDir="Pridať adresár do '%1'" +Basic.PropertiesWindow.AddEditableListFiles="Pridať súbory do '%1'" +Basic.PropertiesWindow.AddEditableListEntry="Pridať položku do '%1'" +Basic.PropertiesView.FPS.Simple="Jednoduché hodnoty FPS" Basic.InteractionWindow="V interakcii s '%1'" +Basic.StatusBar.Reconnecting="Odpojený, prebieha znovu-pripájanie za %2 sekúnd(y) (pokus č. %1)" +Basic.StatusBar.AttemptingReconnect="Pokus o znovu-pripojenie... (pokus č. %1)" Basic.StatusBar.ReconnectSuccessful="Znovupripájanie úspešné" +Basic.StatusBar.Delay="Oneskorenie (%1 s)" +Basic.StatusBar.DelayStartingIn="Oneskorenie (začínajúci za %1 s)" +Basic.StatusBar.DelayStoppingIn="Oneskorenie (zastavenie za %1 s)" +Basic.StatusBar.DelayStartingStoppingIn="Oneskorenie (zastavenie za %1 s, začínajúci za %2 s)" +Basic.Filters="Filtre" +Basic.Filters.AsyncFilters="Audio/Video filtre" +Basic.Filters.AudioFilters="Audio filtre" +Basic.Filters.Title="Filtre pre '%1'" +Basic.Filters.AddFilter.Title="Názov filtra" +Basic.Filters.AddFilter.Text="Prosím, zadajte názov filtra" Basic.TransformWindow="Transformácia položky scény" Basic.TransformWindow.Position="Pozícia" @@ -149,6 +307,7 @@ Basic.TransformWindow.Alignment="Zarovnanie" Basic.TransformWindow.BoundsType="Typ ohraničenia" Basic.TransformWindow.BoundsAlignment="Zarovnanie v ohraničení" Basic.TransformWindow.Bounds="Veľkosť ohraničenia" +Basic.TransformWindow.Crop="Orezať" Basic.TransformWindow.Alignment.TopLeft="Hore/vľavo" Basic.TransformWindow.Alignment.TopCenter="Hore/na stred" @@ -173,17 +332,23 @@ Basic.Main.AddSourceHelp.Text="Na pridanie zdroja je potrebná aspoň 1 scéna." Basic.Main.Scenes="Scény" Basic.Main.Sources="Zdroje" +Basic.Main.Controls="Ovládacie prvky" Basic.Main.Connecting="Pripájanie..." Basic.Main.StartRecording="Spustiť nahrávanie" Basic.Main.StartStreaming="Spustiť stream" Basic.Main.StopRecording="Ukončiť nahrávanie" +Basic.Main.StoppingRecording="Zastavenie nahrávania..." Basic.Main.StopStreaming="Ukončiť stream" +Basic.Main.StoppingStreaming="Zastavenie streamu..." Basic.MainMenu.File="Súbor (&F)" Basic.MainMenu.File.Export="&Exportovať" Basic.MainMenu.File.Import="&Importovať" Basic.MainMenu.File.ShowRecordings="Zobraziť nah&rávky" Basic.MainMenu.File.Settings="Na&stavenia" +Basic.MainMenu.File.ShowSettingsFolder="Zobraziť priečinok nastavení" +Basic.MainMenu.File.ShowProfileFolder="Zobraziť priečinok profilu" +Basic.MainMenu.AlwaysOnTop="&Vždy navrchu" Basic.MainMenu.File.Exit="Ukončiť (&x)" Basic.MainMenu.Edit="Upraviť (&E)" @@ -191,8 +356,13 @@ Basic.MainMenu.Edit.Undo="Vrátiť späť (&U)" Basic.MainMenu.Edit.Redo="Opakovať v&rátené" Basic.MainMenu.Edit.UndoAction="Vrátiť $1 (&U)" Basic.MainMenu.Edit.RedoAction="Opakovať $1 (&R)" +Basic.MainMenu.Edit.LockPreview="&Zámknúť náhľad" +Basic.MainMenu.Edit.Scale.Canvas="Plátno (%1x%2)" +Basic.MainMenu.Edit.Scale.Output="Výstup (%1x%2)" Basic.MainMenu.Edit.Transform="&Transformácia" Basic.MainMenu.Edit.Transform.EditTransform="Upraviť transformáciu... (&E)" +Basic.MainMenu.Edit.Transform.CopyTransform="Kopírovať transformáciu" +Basic.MainMenu.Edit.Transform.PasteTransform="Vložiť transformáciu" Basic.MainMenu.Edit.Transform.ResetTransform="Obnoviť pôvodnú (&R)" Basic.MainMenu.Edit.Transform.Rotate90CW="Otočiť o 90 stupňov doprava" Basic.MainMenu.Edit.Transform.Rotate90CCW="Otočiť o 90 stupňov doľava" @@ -208,12 +378,20 @@ Basic.MainMenu.Edit.Order.MoveDown="Posunúť nižšie (&D)" Basic.MainMenu.Edit.Order.MoveToTop="Premies&tniť navrch" Basic.MainMenu.Edit.Order.MoveToBottom="Premiestniť naspodok (&B)" +Basic.MainMenu.View.Toolbars="&Panely s nástrojmi" +Basic.MainMenu.View.Docks="Doky" +Basic.MainMenu.View.Docks.ResetUI="Resetovať UI" +Basic.MainMenu.View.Docks.LockUI="Zamknúť UI" Basic.MainMenu.Profile="&Profil" +Basic.MainMenu.Profile.Import="Importovať profil" +Basic.MainMenu.Profile.Export="Exportovať profil" +Basic.MainMenu.Profile.Exists="Tento profil už existuje" Basic.MainMenu.Tools="Nás&troje" Basic.MainMenu.Help="Pomoc (&H)" +Basic.MainMenu.Help.Website="Navštíviť &webové stránky" Basic.MainMenu.Help.Logs="&Log súbory" Basic.MainMenu.Help.Logs.ShowLogs="Zobraziť log &súbory" Basic.MainMenu.Help.CheckForUpdates="Skontrolovať aktualizácie" @@ -225,13 +403,35 @@ Basic.Settings.Confirm="Máte neuložené zmeny. Chcete uložiť zmeny?" Basic.Settings.General="Všeobecné" Basic.Settings.General.Theme="Vzhľad" Basic.Settings.General.Language="Jazyk" +Basic.Settings.General.Projectors="Projektory" +Basic.Settings.General.SysTray="Systémová lišta" +Basic.Settings.General.SysTrayWhenStarted="Minimalizovať do systémovej lišty pri spustení" +Basic.Settings.General.SystemTrayHideMinimize="Vždy minimalizovať do systémovej lišty namiesto panela úloh" +Basic.Settings.General.SaveProjectors="Uložiť projektory pri skončení" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Typ streamu" Basic.Settings.Output="Výstup" +Basic.Settings.Output.Format="Formát nahrávania" +Basic.Settings.Output.Encoder="Enkodér" +Basic.Settings.Output.SelectDirectory="Vybrať adresár pre nahrávky" +Basic.Settings.Output.SelectFile="Vybrať súbor nahrávky" +Basic.Settings.Output.EnforceBitrate="Vynútiť obmedzenia dátového toku streamovacej služby" Basic.Settings.Output.Mode="Režim výstupu" Basic.Settings.Output.Mode.Simple="Jednoduchý" +Basic.Settings.Output.Mode.Adv="Rozšírené" +Basic.Settings.Output.Mode.FFmpeg="Výstup FFmpeg" +Basic.Settings.Output.ReplayBuffer.Suffix="Prípona" +Basic.Settings.Output.Simple.RecordingQuality="Kvalita nahrávania" +Basic.Settings.Output.Simple.RecordingQuality.Stream="Rovnaká ako pre stream" +Basic.Settings.Output.Simple.RecordingQuality.Small="Vysoká kvalita, stredná veľkosť súboru" +Basic.Settings.Output.Simple.RecordingQuality.Lossless="Bezstratová kvalita, ohromne veľké súbory" +Basic.Settings.Output.Simple.Encoder.Software="Softvér (x264)" +Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Hardvér (QSV)" +Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Hardvér (AMD)" +Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Hardvér (NVENC)" +Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Softvér (x264 prednastavené nízke zaťaženie CPU, zvyšuje veľkosť súboru)" Basic.Settings.Output.VideoBitrate="Bitrate videa" Basic.Settings.Output.AudioBitrate="Bitrate zvuku" Basic.Settings.Output.Reconnect="Automaticky znovupripájať" @@ -244,7 +444,24 @@ Basic.Settings.Output.Adv.Audio.Track2="Stopa 2" Basic.Settings.Output.Adv.Audio.Track3="Stopa 3" Basic.Settings.Output.Adv.Audio.Track4="Stopa 4" +Basic.Settings.Output.Adv.Recording="Nahrávanie" Basic.Settings.Output.Adv.Recording.Type="Typ" +Basic.Settings.Output.Adv.Recording.Type.Standard="Štandardný" +Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput="Vlastný výstup (FFmpeg)" +Basic.Settings.Output.Adv.Recording.OverwriteIfExists="Prepísať, ak súbor existuje" +Basic.Settings.Output.Adv.FFmpeg.Type="Typ výstupu FFmpeg" +Basic.Settings.Output.Adv.FFmpeg.Type.URL="Výstup na URL" +Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile="Výstup do súboru" +Basic.Settings.Output.Adv.FFmpeg.SaveFilter.Common="Bežné formáty nahrávania" +Basic.Settings.Output.Adv.FFmpeg.SaveFilter.All="Všetky súbory" +Basic.Settings.Output.Adv.FFmpeg.SavePathURL="Cesta k súboru alebo URL" +Basic.Settings.Output.Adv.FFmpeg.FormatAudio="Zvuk" +Basic.Settings.Output.Adv.FFmpeg.FormatVideo="Video" +Basic.Settings.Output.Adv.FFmpeg.FormatDefault="Predvolený formát" +Basic.Settings.Output.Adv.FFmpeg.AVEncoderDefault="Predvolený enkodér" +Basic.Settings.Output.Adv.FFmpeg.AVEncoderDisable="Vypnúť enkodér" +Basic.Settings.Output.Adv.FFmpeg.VEncoder="Video Enkodér" +Basic.Settings.Output.Adv.FFmpeg.AEncoder="Audio Enkodér" @@ -259,20 +476,64 @@ Basic.Settings.Video.Numerator="Čitateľ" Basic.Settings.Video.Denominator="Menovateľ" Basic.Settings.Video.InvalidResolution="Neplatné rozlíšenie. Správne je [šírka]x[výška] (napr. 1920x1080)" Basic.Settings.Video.CurrentlyActive="Výstup videa je práve aktívny. Prosím, vypnite všetky výstupy na zmenu nastavení videa." +Basic.Settings.Video.DisableAero="Vypnúť Aero" Basic.Settings.Audio="Zvuk" Basic.Settings.Audio.SampleRate="Vzorkovacia frekvencia" Basic.Settings.Audio.Channels="Kanály" +Basic.Settings.Advanced.General.ProcessPriority.High="Vysoká" +Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Zvýšená" +Basic.Settings.Advanced.General.ProcessPriority.Normal="Normálna" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Znížená" +Basic.Settings.Advanced.General.ProcessPriority.Idle="Nízka" +Basic.Settings.Advanced.Video.ColorSpace="Farebný priestor YUV" +Basic.Settings.Advanced.Video.ColorRange="Rozsah farieb YUV" +Basic.Settings.Advanced.Video.ColorRange.Partial="Čiastočný" +Basic.Settings.Advanced.Video.ColorRange.Full="Úplný" +Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Predvolené" +Basic.Settings.Advanced.Network.BindToIP="Zviazať s IP adresou" +Basic.Settings.Advanced.Network.EnableNewSocketLoop="Použiť nový sieťový kód" +Basic.Settings.Advanced.Network.EnableLowLatencyMode="Režim nízkej odozvy" + +Basic.AdvAudio.Name="Názov" +Basic.AdvAudio.Volume="Hlasitosť (%)" +Basic.AdvAudio.Mono="Previesť na Mono" + +Basic.Settings.Hotkeys="Klávesové skratky" + + +Basic.SystemTray.Show="Ukázať" +Basic.SystemTray.Hide="Skryť" + +Basic.SystemTray.Message.Reconnecting="Odpojený. Znovu-pripájanie..." + +Hotkeys.Insert="Insert" +Hotkeys.Delete="Delete" +Hotkeys.Home="Home" +Hotkeys.End="End" +Hotkeys.PageUp="Page Up" +Hotkeys.PageDown="Page Down" +Hotkeys.NumLock="Num Lock" +Hotkeys.ScrollLock="Scroll Lock" +Hotkeys.CapsLock="Caps Lock" +Hotkeys.Backspace="Backspace" +Hotkeys.Tab="Tab" +Hotkeys.Print="Print" +Hotkeys.Pause="Pause" +Hotkeys.Left="Vľavo" +Hotkeys.Right="Vpravo" +Hotkeys.Up="Hore" +Hotkeys.Down="Dole" +Hotkeys.Windows="Windows" +Hotkeys.Super="Super" +Hotkeys.Menu="Menu" +Hotkeys.Space="Medzerník" +Hotkeys.NumpadNum="Numerická klávesa %1" - - - - - - +FinalScene.Title="Odstrániť scénu" diff --git a/UI/data/locale/sl-SI.ini b/UI/data/locale/sl-SI.ini index c1d7a5a..ea3a3bd 100644 --- a/UI/data/locale/sl-SI.ini +++ b/UI/data/locale/sl-SI.ini @@ -8,6 +8,7 @@ Cancel="Prekliči" Close="Zapri" Save="Shrani" Discard="Opusti" +Disable="Onemogoči" Yes="Da" No="Ne" Add="Dodaj" @@ -19,6 +20,7 @@ Properties="Lastnosti" MoveUp="Premakni gor" MoveDown="Premakni Dol" Settings="Nastavitve" +Display="Zaslon" Name="Ime" Exit="Izhod" Mixer="Mešalnik" @@ -26,13 +28,87 @@ Browse="Prebrskaj" Mono="Mono" Stereo="Stereo" DroppedFrames="Izpuščene sličice %1 (%2 %)" +PreviewProjector="Celozaslonski predogled (Predogled)" +SceneProjector="Celozaslonski predogled (Scena)" +SourceProjector="Celozaslonski predogled (Vir)" +PreviewWindow="Predogled v oknu (Predogled)" +SceneWindow="Predogled v oknu (Scena)" +SourceWindow="Predogled v oknu (Vir)" +Clear="Počisti" +Revert="Povrni" +Show="Prikaži" +Hide="Skrij" +Untitled="Neimenovan" +New="Nov" +Duplicate="Podvoji" +Enable="Omogoči" +DisableOSXVSync="Onemogoči OSX V-Sync" +ResetOSXVSyncOnExit="Ponastavi OSX V-Sync ob izhodu" +HighResourceUsage="Preobremenjeno kodiranje! Zmanjšajte video nastavitve ali uporabite hitrejše kodirne prednastavitve." +Transition="Prehod" +QuickTransitions="Hitri prehodi" +Left="Levo" +Right="Desno" +Top="Zgoraj" +Bottom="Spodaj" +Reset="Ponastavi" +Hours="Ure" +Minutes="Minute" +Seconds="Sekunde" +Deprecated="Zastarelo" +ReplayBuffer="Ponovitev medpomnilnika" +Import="Uvozi" +Export="Izvozi" +Copy="Kopiraj" +Paste="Prilepi" +PasteReference="Prilepi (Referenca)" +PasteDuplicate="Prilepi (Dvojnika)" +RemuxRecordings="Nadaljuj snemanje" +Next="Naprej" +Back="Nazaj" +Defaults="Privzeto" + +AlreadyRunning.Title="OBS že obratuje" +AlreadyRunning.Text="OBS je že v izvajanju! Razen če ste imeli namen zagnati dve aplikaciji naenkrat, zaustavite vse OBS aplikacije v teku, tudi v sistemski vrstici." +AlreadyRunning.LaunchAnyway="Vseeno zaženi" + +Copy.Filters="Kopiraj Filtre" +Paste.Filters="Prilepi Filtre" + +BandwidthTest.Region="Območje" +BandwidthTest.Region.US="Združene države Amerike" +BandwidthTest.Region.EU="Evropa" +BandwidthTest.Region.Asia="Azija" +Basic.Stats.MemoryUsage="Poraba Pomnilnika" +Basic.Stats.AverageTimeToRender="Povprečen čas za izdelavo slike" +Basic.Stats.SkippedFrames="Preskočene slike zaradi kodirnega zastoja" +Basic.Stats.MissedFrames="Izgubljene slike zaradi kodirnega zastoja" +Basic.Stats.Output.Stream="Pretok" +Basic.Stats.Output.Recording="Snemanje" +Basic.Stats.Status="Stanje" +Basic.Stats.Status.Recording="Snemanje" +Basic.Stats.Status.Live="V ŽIVO" +Basic.Stats.Status.Reconnecting="Ponovno vzpostavljanje povezave" +Basic.Stats.Status.Inactive="Neaktiven" +Basic.Stats.DroppedFrames="Izpuščanje sličic (omrežje)" +Basic.Stats.MegabytesSent="Skupni podatkovni izhod" +Basic.Stats.Bitrate="Bitna hitrost" - - - +Updater.Title="Na Voljo So Nove Posodobitve" +Updater.Text="Nova Posodobitev Je Na Voljo:" +Updater.UpdateNow="Posodobi zdaj" +Updater.RemindMeLater="Opomni me kasneje" +Updater.Skip="Preskoči različico" +Updater.Running.Title="Program je trenutno aktiven" +Updater.Running.Text="Izhodi so trenutno v uporabi, prosimo ugasnite izhode pred posodabljanjem" +Updater.NoUpdatesAvailable.Title="Na voljo ni nobenih posodobitev" +Updater.NoUpdatesAvailable.Text="Na voljo ni novih posodobitev" +Updater.FailedToLaunch="Zagon posodobitve je spodletel" +Updater.GameCaptureActive.Title="Zajem Igre aktiven" +Updater.GameCaptureActive.Text="Knjižnica za zajem Igre je trenutno v uporabi. Prosimo zaprite vse programe, ki so bili snemani (ali ponovno zaženite Windows) in poskusite ponovno." @@ -100,6 +176,8 @@ Basic.Main.DefaultSceneName.Text="Scena %1" + + Basic.SourceSelect="Ustvarite / Izberite Vir" Basic.SourceSelect.CreateNew="Ustvari novo" Basic.SourceSelect.AddExisting="Dodaj obstoječo" diff --git a/UI/data/locale/sr-CS.ini b/UI/data/locale/sr-CS.ini index 210f416..d7fecd4 100644 --- a/UI/data/locale/sr-CS.ini +++ b/UI/data/locale/sr-CS.ini @@ -192,6 +192,8 @@ AddProfile.Text="Molim unesite ime profila" RenameProfile.Title="Promeni ime profila" + + Basic.Main.PreviewDisabled="Pregled je trenutno onemogućen" Basic.SourceSelect="Napravi ili izaberi izvor" diff --git a/UI/data/locale/sr-SP.ini b/UI/data/locale/sr-SP.ini index edf1a05..45c7242 100644 --- a/UI/data/locale/sr-SP.ini +++ b/UI/data/locale/sr-SP.ini @@ -192,6 +192,8 @@ AddProfile.Text="Молим унесите име профила" RenameProfile.Title="Промени име профила" + + Basic.Main.PreviewDisabled="Преглед је тренутно онемогућен" Basic.SourceSelect="Направи или изабери извор" diff --git a/UI/data/locale/sv-SE.ini b/UI/data/locale/sv-SE.ini index 6873cac..bd704aa 100644 --- a/UI/data/locale/sv-SE.ini +++ b/UI/data/locale/sv-SE.ini @@ -28,16 +28,21 @@ Browse="Bläddra" Mono="Mono" Stereo="Stereo" DroppedFrames="Tappade bildrutor %1 (%2%)" +StudioProgramProjector="Helskärmsprojektor (program)" PreviewProjector="Fullskärmsprojektor (förhandsvisning)" SceneProjector="Fullskärmsprojektor (scen)" SourceProjector="Fullskärmsprojektor (källa)" +StudioProgramWindow="Fönsterprojektor (program)" PreviewWindow="Fönsterprojektor (förhandsvisning)" SceneWindow="Fönsterprojektor (scen)" SourceWindow="Fönsterprojektor (källa)" +MultiviewProjector="Flervy (helskärm)" +MultiviewWindowed="Flervy (fönster)" Clear="Rensa" Revert="Återgå" Show="Visa" Hide="Dölj" +UnhideAll="Visa alla" Untitled="Namnlös" New="Ny" Duplicate="Duplicera" @@ -66,8 +71,16 @@ PasteDuplicate="Klistra in (duplicera)" RemuxRecordings="Remuxa inspelningar" Next="Nästa" Back="Tillbaka" +Defaults="Standardvärden" +HideMixer="Dölj i mixer" +TransitionOverride="Övergångsåsidosättande" +None="Ingen" +StudioMode.Preview="Förhandsvisning" +StudioMode.Program="Program" +ShowInMultiview="Visa i flervy" AlreadyRunning.Title="OBS körs redan" +AlreadyRunning.Text="OBS körs redan! Såvida du gjorde detta med flit, stäng ned alla befintliga instanser av OBS innan du försöker köra en ny instans. Om du har minimerat OBS till systemfältet, kontroller om det fortfarande körs där." AlreadyRunning.LaunchAnyway="Kör ändå" Copy.Filters="Kopiera filter" @@ -97,6 +110,7 @@ Basic.AutoConfig.VideoPage.BaseResolution.Display="Bildskärm %1 (%2x%3)" Basic.AutoConfig.VideoPage.FPS.UseCurrent="Använd nuvarande (%1)" Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="Antingen 60 eller 30, men föredra 60 när det är möjligt" Basic.AutoConfig.VideoPage.FPS.PreferHighRes="Antingen 60 eller 30, men föredra hög upplösning" +Basic.AutoConfig.VideoPage.CanvasExplanation="OBS: Kanvasens (grundens) upplösning är nödvändigtvis samma som upplösningen upp kommer att strömma eller spela in med. Din riktiga upplösning för strömning/inspelning kan skalas ned från kanvasupplösningen för att reducera användning av resurser eller krav på bithastighet." Basic.AutoConfig.StreamPage="Ströminformation" Basic.AutoConfig.StreamPage.SubTitle="Ange din ströminformation" Basic.AutoConfig.StreamPage.Service="Tjänst" @@ -298,6 +312,10 @@ AddProfile.Text="Skriv in namnet på profilen" RenameProfile.Title="Byt namn på Profilen" +Basic.Main.MixerRename.Title="Döp om ljudkälla" +Basic.Main.MixerRename.Text="Ange namnet på ljudkällan" + + Basic.Main.PreviewDisabled="Förhandsvisningen är inaktiverad" Basic.SourceSelect="Skapa/välj källa" @@ -375,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Du måste ha minst 1 scen för att kunna lägga t Basic.Main.Scenes="Scener" Basic.Main.Sources="Källor" +Basic.Main.Controls="Kontroller" Basic.Main.Connecting="Ansluter..." Basic.Main.StartRecording="Starta inspelning" Basic.Main.StartReplayBuffer="Starta reprisbuffert" @@ -430,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="&Avancerade ljudinställningar" Basic.MainMenu.View="&Visa" Basic.MainMenu.View.Toolbars="&Verktygsfält" +Basic.MainMenu.View.Docks="Komponenter" +Basic.MainMenu.View.Docks.ResetUI="Återställ gränssnitt" +Basic.MainMenu.View.Docks.LockUI="Lås gränssnitt" Basic.MainMenu.View.Toolbars.Listboxes="&Listrutor" Basic.MainMenu.View.SceneTransitions="S&cenövergångar" Basic.MainMenu.View.StatusBar="&Statusfält" +Basic.MainMenu.View.Fullscreen.Interface="Helskärmsgränssnitt" Basic.MainMenu.SceneCollection="&Scensamling" Basic.MainMenu.Profile="&Profil" @@ -446,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Scensamlingen finns redan" Basic.MainMenu.Tools="&Verktyg" Basic.MainMenu.Help="&Hjälp" +Basic.MainMenu.Help.HelpPortal="Hjälp&portal" Basic.MainMenu.Help.Website="Besök &webbplats" Basic.MainMenu.Help.Logs="&Loggfiler" Basic.MainMenu.Help.Logs.ShowLogs="&Visa loggfiler" @@ -481,6 +505,13 @@ Basic.Settings.General.SysTray="Systemfält" Basic.Settings.General.SysTrayWhenStarted="Minimera till meddelandefältet vid start" Basic.Settings.General.SystemTrayHideMinimize="Minimera alltid till meddelandefältet i stället för aktivitetsfältet" Basic.Settings.General.SaveProjectors="Spara projektorer vid avslut" +Basic.Settings.General.SwitchOnDoubleClick="Övergång till scen vid dubbelklick" +Basic.Settings.General.StudioPortraitLayout="Aktivera porträtt-/vertikalt utseende" +Basic.Settings.General.MultiviewLayout="Multivisningslayout" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horisontal, överkant" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horisontal, nederkant" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertikal, vänster" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertikal, höger" Basic.Settings.Stream="Ström" Basic.Settings.Stream.StreamType="Strömtyp" @@ -600,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Vässd skalning, 32 prove Basic.Settings.Audio="Ljud" Basic.Settings.Audio.SampleRate="Samplingsfrekvens" Basic.Settings.Audio.Channels="Kanaler" +Basic.Settings.Audio.MeterDecayRate="Ljudmätarens förfallfrekvens" +Basic.Settings.Audio.MeterDecayRate.Fast="Snabb" +Basic.Settings.Audio.MeterDecayRate.Medium="Medium (Type I PPM)" +Basic.Settings.Audio.MeterDecayRate.Slow="Långsam (Type II PPM)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="VARNING: Surroundljud är aktiverat." +Basic.Settings.Audio.MultichannelWarning="Om du strömmar, se till att kolla om din strömtjänst stöder både inmatning och uppspelning av surroundljud. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast är några exempel på tjänster som har fullt stöd. Fastän Facebook Live och YouTube Live stöder inmatning för surroundljud mixar Facebook Live ned till stereo och YouTube Live spelar upp i bara två kanaler.\n\nLjudfiltren i OBS är kompatibla med surroundljud, fast stöd för VST-insticksmodulen garanteras inte." +Basic.Settings.Audio.MultichannelWarning.Title="Aktivera surroundljud?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Är du säker på att du vill aktivera surroundljud?" Basic.Settings.Audio.DesktopDevice="Skrivbordsljudenhet" Basic.Settings.Audio.DesktopDevice2="Skrivbordsljudenhet 2" Basic.Settings.Audio.AuxDevice="Mikrofon/extra ljudenhet" @@ -616,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Processprioritet" Basic.Settings.Advanced.General.ProcessPriority.High="Hög" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Över normal" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Under normal" Basic.Settings.Advanced.General.ProcessPriority.Idle="Inaktiv" Basic.Settings.Advanced.FormatWarning="Varning: Andra färgformat än NV12 är avsedda för inspelning och rekommenderas inte för att strömma. Högre processoranvändning kan uppstå vid strömning p.g.a. konvertering av färgformat." Basic.Settings.Advanced.Audio.BufferingTime="Ljudbuffringstid" @@ -644,6 +684,7 @@ Basic.AdvAudio.Panning="Panorering" Basic.AdvAudio.SyncOffset="Sync Offset (ms)" Basic.AdvAudio.Monitoring="Ljuduppspelning" Basic.AdvAudio.Monitoring.None="Monitor av" +Basic.AdvAudio.Monitoring.MonitorOnly="Endast monitor (tysta utgång)" Basic.AdvAudio.Monitoring.Both="Monitor och utgång" Basic.AdvAudio.AudioTracks="Spår" diff --git a/UI/data/locale/ta-IN.ini b/UI/data/locale/ta-IN.ini index 28db817..a93f85a 100644 --- a/UI/data/locale/ta-IN.ini +++ b/UI/data/locale/ta-IN.ini @@ -99,6 +99,8 @@ New="புதிய" + + diff --git a/UI/data/locale/th-TH.ini b/UI/data/locale/th-TH.ini index 599ea64..cbbb8e6 100644 --- a/UI/data/locale/th-TH.ini +++ b/UI/data/locale/th-TH.ini @@ -84,6 +84,8 @@ Basic.Main.DefaultSceneName.Text="ฉาก %1" + + Basic.SourceSelect.CreateNew="สร้างใหม่" diff --git a/UI/data/locale/tr-TR.ini b/UI/data/locale/tr-TR.ini index 14a07b6..1f8c471 100644 --- a/UI/data/locale/tr-TR.ini +++ b/UI/data/locale/tr-TR.ini @@ -28,16 +28,21 @@ Browse="Gözat" Mono="Mono" Stereo="Stereo" DroppedFrames="Kaybedilen Kareler %1 (%2%)" +StudioProgramProjector="Tam Ekran Projektör (Program)" PreviewProjector="Tam Ekran Projektör (Önizleme)" SceneProjector="Tam Ekran Projektör (Sahne)" SourceProjector="Tam Ekran Projektör (Kaynak)" +StudioProgramWindow="Pencereli Projektör (Program)" PreviewWindow="Pencereli Projektör (Önizleme)" SceneWindow="Pencereli Projektör (Sahne)" SourceWindow="Pencereli Projektör (Kaynak)" +MultiviewProjector="Çoklu Görüntü (Tam ekran)" +MultiviewWindowed="Çoklu görüntü (Pencere)" Clear="Temizle" Revert="Eski Haline Döndür" Show="Göster" Hide="Gizle" +UnhideAll="Tümünü Göster" Untitled="İsimsiz" New="Yeni" Duplicate="Çoğalt" @@ -66,6 +71,13 @@ PasteDuplicate="Yapıştır (Çoğalt)" RemuxRecordings="Remux Kayıtları" Next="İleri" Back="Geri" +Defaults="Varsayılanlar" +HideMixer="Karıştırıcıda gizle" +TransitionOverride="Geçişi Etkisiz Kıl" +None="Hiçbiri" +StudioMode.Preview="Önizleme" +StudioMode.Program="Program" +ShowInMultiview="Çoklu Ekranda Göster" AlreadyRunning.Title="OBS zaten çalışıyor" AlreadyRunning.Text="OBS zaten çalışıyor! Bunu yapmak istemediyseniz, lütfen yeni bir örneği çalıştırmayı denemeden önce varolan tüm OBS örneklerini kapatın. OBS'yi sistem tablasına küçülmesi için ayarladıysanız, lütfen hala çalışıp çalışmadığını görmek için orayı kontrol edin." @@ -168,7 +180,7 @@ QuickTransitions.EditPropertiesTT="Aynı sahneyi düzenlerken, çıkışı deği QuickTransitions.HotkeyName="Hızlı Geçiş: %1" Basic.AddTransition="Yapılandırılabilir Geçiş Ekle" -Basic.RemoveTransition="Yapılandırılabilir Geçiş Kaldır" +Basic.RemoveTransition="Yapılandırılabilir Geçişi Kaldır" Basic.TransitionProperties="Geçiş Özellikleri" Basic.SceneTransitions="Sahne Geçişleri" Basic.TransitionDuration="Süre" @@ -300,6 +312,10 @@ AddProfile.Text="Lütfen profil isimini girin" RenameProfile.Title="Profili Yeniden Adlandır" +Basic.Main.MixerRename.Title="Ses Kaynağını Yeniden Adlandır" +Basic.Main.MixerRename.Text="Lütfen ses kaynağının adını girin" + + Basic.Main.PreviewDisabled="Önizleme şu anda devre dışı" Basic.SourceSelect="Kaynak Oluştur/Seç" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Kaynağa eklemek için en az 1 sahneye ihtiyacın Basic.Main.Scenes="Sahneler" Basic.Main.Sources="Kaynaklar" +Basic.Main.Controls="Kontroller" Basic.Main.Connecting="Bağlanıyor..." Basic.Main.StartRecording="Kaydı Başlat" Basic.Main.StartReplayBuffer="Tekrar Oynatma Arabelleğini Başlat" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="&Gelişmiş Ses Özellikleri" Basic.MainMenu.View="&Görünüm" Basic.MainMenu.View.Toolbars="&Araç Çubukları" +Basic.MainMenu.View.Docks="Paneller" +Basic.MainMenu.View.Docks.ResetUI="Arayüzü Sıfırla" +Basic.MainMenu.View.Docks.LockUI="Arayüzü Kilitle" Basic.MainMenu.View.Toolbars.Listboxes="&Liste Kutuları" Basic.MainMenu.View.SceneTransitions="S&ahne Geçişleri" Basic.MainMenu.View.StatusBar="&Durum Çubuğu" +Basic.MainMenu.View.Fullscreen.Interface="Tam Ekran Arayüz" Basic.MainMenu.SceneCollection="&Sahne Koleksiyonu" Basic.MainMenu.Profile="&Profil" @@ -448,13 +469,14 @@ Basic.MainMenu.SceneCollection.Exists="Sahne koleksiyonu zaten var" Basic.MainMenu.Tools="&Araçlar" Basic.MainMenu.Help="&Yardım" +Basic.MainMenu.Help.HelpPortal="Yardım &Portalı" Basic.MainMenu.Help.Website="&Siteyi Ziyaret Et" Basic.MainMenu.Help.Logs="&Günlük Dosyaları" Basic.MainMenu.Help.Logs.ShowLogs="&Günlük Dosyalarını Göster" Basic.MainMenu.Help.Logs.UploadCurrentLog="&Mevcut Günlük Dosyasını Karşıya Yükle" Basic.MainMenu.Help.Logs.UploadLastLog="&Son Günlük Dosyasını Karşıya Yükle" Basic.MainMenu.Help.Logs.ViewCurrentLog="&Şimdiki Günlüğü Göster" -Basic.MainMenu.Help.CheckForUpdates="Güncellemeleri Kontrol et" +Basic.MainMenu.Help.CheckForUpdates="Güncellemeleri Denetle" Basic.Settings.ProgramRestart="Programın, bu ayarların etkinleşmesi için yeniden başlatılması gerekir." Basic.Settings.ConfirmTitle="Değişiklikleri Onayla" @@ -463,7 +485,7 @@ Basic.Settings.Confirm="Kayıt edilmemiş değişiklikleriniz var. Değişiklikl Basic.Settings.General="Genel" Basic.Settings.General.Theme="Tema" Basic.Settings.General.Language="Dil" -Basic.Settings.General.EnableAutoUpdates="Başlangıçta güncellemeleri otomatik olarak kontrol et" +Basic.Settings.General.EnableAutoUpdates="Başlangıçta güncellemeleri otomatik olarak denetle" Basic.Settings.General.OpenStatsOnStartup="Başlangıçta istatistikler iletişim kutusunu aç" Basic.Settings.General.WarnBeforeStartingStream="Yayın başlatırken onay iletişim kutusunu göster" Basic.Settings.General.WarnBeforeStoppingStream="Yayın durduğunda onay iletişim kutusunu göster" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Sistem tepsisi" Basic.Settings.General.SysTrayWhenStarted="Başladığında sistem tepsisine küçült" Basic.Settings.General.SystemTrayHideMinimize="Her zaman görev çubuğu yerine sistem tepsisine küçült" Basic.Settings.General.SaveProjectors="Çıkışta projektörleri kaydet" +Basic.Settings.General.SwitchOnDoubleClick="Çift tıklamada sahneye geçiş yap" +Basic.Settings.General.StudioPortraitLayout="Dikey düzeni etkinleştir" +Basic.Settings.General.MultiviewLayout="Çoklu Görüntü Düzeni" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Yatay, Üst" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Yatay, Alt" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Dikey, Sol" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Dikey, Sağ" Basic.Settings.Stream="Yayın" Basic.Settings.Stream.StreamType="Yayın Türü" @@ -602,6 +631,10 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Keskinleştirilmiş boyut Basic.Settings.Audio="Ses" Basic.Settings.Audio.SampleRate="Örnekleme Sıklığı" Basic.Settings.Audio.Channels="Kanallar" +Basic.Settings.Audio.MultiChannelWarning.Enabled="Uyarı: Surround ses etkin." +Basic.Settings.Audio.MultichannelWarning="Yayın yapılıyorsa, yayın hizmetinizin hem surround ses alınımını hem de surround ses geri oynatımını desteklediğinden emin olun. Twitch, Facebook 360 Live, Karıştırıcı RTMP, Smashcast, surround sesin tam desteklendiği örneklerdir. Facebook Live'ın ve YouTube Live'ın her ikisi de surround alınımını desteklese de, Facebook Live stereo'ya indirger, ve YouTube Live sadece iki kanal oynatır.\n\nOBS ses filtreleri surround sesle uyumludur, ancak VST eklenti desteği kesin değildir." +Basic.Settings.Audio.MultichannelWarning.Title="Surround ses etkinleştirilsin mi?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Surround sesi etkinleştirmek istediğinize emin misiniz?" Basic.Settings.Audio.DesktopDevice="Masaüstü Ses Aygıtı" Basic.Settings.Audio.DesktopDevice2="Masaüstü Ses Aygıtı 2" Basic.Settings.Audio.AuxDevice="Mic/yardımcı ses aygıtı" @@ -618,9 +651,10 @@ Basic.Settings.Advanced.General.ProcessPriority="İşlem Önceliği" Basic.Settings.Advanced.General.ProcessPriority.High="Yüksek" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Normalin Üstünde" Basic.Settings.Advanced.General.ProcessPriority.Normal="Normal" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Normalin Altında" Basic.Settings.Advanced.General.ProcessPriority.Idle="Boşta" Basic.Settings.Advanced.FormatWarning="Uyarı: NV12 dışındaki renk biçimleri esas olarak kayıt için tasarlanmıştır ve yayın anında kullanma önerilmez. Yayın nedeni ile renk biçimini dönüştürme çok fazla İŞLEMCİ kullanımına sebep olabilir." -Basic.Settings.Advanced.Audio.BufferingTime="Ses Ön Bellekleme Süresi" +Basic.Settings.Advanced.Audio.BufferingTime="Ses Arabelleğe Alma Süresi" Basic.Settings.Advanced.Video.ColorFormat="Renk Biçimi" Basic.Settings.Advanced.Video.ColorSpace="YUV Renk Alanı" Basic.Settings.Advanced.Video.ColorRange="YUV Renk Aralığı" diff --git a/UI/data/locale/uk-UA.ini b/UI/data/locale/uk-UA.ini index 9770894..f45890b 100644 --- a/UI/data/locale/uk-UA.ini +++ b/UI/data/locale/uk-UA.ini @@ -28,16 +28,21 @@ Browse="Огляд" Mono="Моно" Stereo="Стерео" DroppedFrames="Пропущено кадрів %1 (%2%)" +StudioProgramProjector="Повноекранний Проектор (вікно Програма наживо)" PreviewProjector="Повноекранний Проектор (вікно Перегляду)" SceneProjector="Повноекранний Проектор (Сцена)" SourceProjector="Повноекранний Проектор (Джерело)" +StudioProgramWindow="Віконний Проектор (вікно Програма наживо)" PreviewWindow="Віконний Проектор (вікно Перегляду)" SceneWindow="Віконний Проектор (Сцена)" SourceWindow="Віконний Проектор (Джерело)" +MultiviewProjector="Мульти-перегляд (Повноекранний)" +MultiviewWindowed="Мульти-перегляд (Віконний)" Clear="Очистити" Revert="Відмінити" Show="Показати" Hide="Приховати" +UnhideAll="Показувати всі приховані" Untitled="Без назви" New="Новий" Duplicate="Дублювати" @@ -66,6 +71,13 @@ PasteDuplicate="Вставити (як повну копію)" RemuxRecordings="Ремультиплексація Записів" Next="Далі" Back="Назад" +Defaults="За замовчанням" +HideMixer="Приховати з Мікшеру" +TransitionOverride="Перевизначення Відео-переходу" +None="Немає" +StudioMode.Preview="вікно Перегляду" +StudioMode.Program="Програма наживо" +ShowInMultiview="Показувати у Мульти-перегляді" AlreadyRunning.Title="OBS вже виконується" AlreadyRunning.Text="OBS вже запущено! Тільки якщо ви дійсно не намагаєтесь цього зробити, будь ласка позакривайте всі відкриті OBS перед тим як запускати нову копію. Якщо OBS налаштовано згортатися в трей, перевірте чи не виконується він там й досі." @@ -223,7 +235,7 @@ Output.ReplayBuffer.NoHotkey.Msg="Гарячу клавішу для Буфер Output.BadPath.Title="Недійсний шлях до файлу" Output.BadPath.Text="Шлях вказаний для виводу файлу недійсний. Будь ласка, перевірте у налаштуваннях, що шлях було вказано вірно." -LogReturnDialog="Лог успішно завантажено" +LogReturnDialog="Файл журналу успішно завантажено" LogReturnDialog.CopyURL="Копіювати посилання" LogReturnDialog.ErrorUploadingLog="Помилка завантаження файлу журналу" @@ -300,6 +312,10 @@ AddProfile.Text="Будь ласка, введіть назву для Проф RenameProfile.Title="Перейменування Профілю" +Basic.Main.MixerRename.Title="Перейменування джерела Аудіо" +Basic.Main.MixerRename.Text="Будь ласка, введіть назву для джерела аудіо" + + Basic.Main.PreviewDisabled="Вікно Перегляду вимкнено" Basic.SourceSelect="Створити/Вибрати Джерело" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="Ви повинні мати принаймні 1 Basic.Main.Scenes="Сцени" Basic.Main.Sources="Джерела" +Basic.Main.Controls="Елементи керування" Basic.Main.Connecting="З'єднання..." Basic.Main.StartRecording="Почати запис" Basic.Main.StartReplayBuffer="Запустити Буфер Повторів" @@ -405,7 +422,7 @@ Basic.MainMenu.Edit.Undo="&Відмінити" Basic.MainMenu.Edit.Redo="Від&новити" Basic.MainMenu.Edit.UndoAction="&Відмінити $1" Basic.MainMenu.Edit.RedoAction="Від&новити $1" -Basic.MainMenu.Edit.LockPreview="&Зафіксувати вікно Перегляду" +Basic.MainMenu.Edit.LockPreview="&Заблокувати вікно Перегляду" Basic.MainMenu.Edit.Scale="&Масштабувати вікно Перегляду" Basic.MainMenu.Edit.Scale.Window="В розмір вікна" Basic.MainMenu.Edit.Scale.Canvas="Як Полотно (%1x%2)" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="Р&озширені Налаштування Ау Basic.MainMenu.View="&Вид" Basic.MainMenu.View.Toolbars="&Панелі інструментів" +Basic.MainMenu.View.Docks="Панелі інтерфейсу" +Basic.MainMenu.View.Docks.ResetUI="Скинути інтерфейс" +Basic.MainMenu.View.Docks.LockUI="Зафіксувати інтерфейс" Basic.MainMenu.View.Toolbars.Listboxes="У &Списках" Basic.MainMenu.View.SceneTransitions="В&ідео-переходи між Сценами" Basic.MainMenu.View.StatusBar="Панель с&тану" +Basic.MainMenu.View.Fullscreen.Interface="Повноекранний режим" Basic.MainMenu.SceneCollection="&Набір Сцен" Basic.MainMenu.Profile="&Профіль" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="Набір Сцен вже існує" Basic.MainMenu.Tools="Додаткові &засоби" Basic.MainMenu.Help="&Довідка" +Basic.MainMenu.Help.HelpPortal="&Портал з допомоги" Basic.MainMenu.Help.Website="Відвідати &сайт" Basic.MainMenu.Help.Logs="&Файли журналів" Basic.MainMenu.Help.Logs.ShowLogs="&Показати файли журналів" @@ -469,7 +491,7 @@ Basic.Settings.General.WarnBeforeStartingStream="Показувати підтв Basic.Settings.General.WarnBeforeStoppingStream="Показувати підтвердження для закінчення трансляції" Basic.Settings.General.Projectors="Проектор" Basic.Settings.General.HideProjectorCursor="Приховати курсор у режимі Проектор" -Basic.Settings.General.ProjectorAlwaysOnTop="Режим Проектор відображати поверх всіх вікон" +Basic.Settings.General.ProjectorAlwaysOnTop="Режим Проектор показувати поверх всіх вікон" Basic.Settings.General.Snapping="Прив'язка та вирівнювання" Basic.Settings.General.ScreenSnapping="Примагнітити Джерела до краю екрана" Basic.Settings.General.CenterSnapping="Примагнітити Джерела до центру по вертикалі та горизонталі" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="Системний трей" Basic.Settings.General.SysTrayWhenStarted="Згорнути програму до трею при запуску" Basic.Settings.General.SystemTrayHideMinimize="Згортати в трей замість того, щоб мінімізувати до панелі завдань" Basic.Settings.General.SaveProjectors="Зберегти налаштування режиму Проектор при виході" +Basic.Settings.General.SwitchOnDoubleClick="Відео-перехід до сцени за подвійним клацанням" +Basic.Settings.General.StudioPortraitLayout="Увімкнути портретне/вертикальне компонування" +Basic.Settings.General.MultiviewLayout="Мульти-перегляд компонування" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Горизонтально, зверху" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Горизонтально, внизу" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Вертикально, ліворуч" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Вертикально, праворуч" Basic.Settings.Stream="Трансляція" Basic.Settings.Stream.StreamType="Тип Трансляції" @@ -602,6 +631,14 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Ланцош (чітке масш Basic.Settings.Audio="Аудіо" Basic.Settings.Audio.SampleRate="Частота дискретизації" Basic.Settings.Audio.Channels="Канали" +Basic.Settings.Audio.MeterDecayRate="Швидкість спаду індикатору звуку" +Basic.Settings.Audio.MeterDecayRate.Fast="Швидко" +Basic.Settings.Audio.MeterDecayRate.Medium="Середньо (PPM типу I)" +Basic.Settings.Audio.MeterDecayRate.Slow="Повільно (PPM типу II)" +Basic.Settings.Audio.MultiChannelWarning.Enabled="ПОПЕРЕДЖЕННЯ: Увімкнуто об'ємний звук." +Basic.Settings.Audio.MultichannelWarning="Якщо робите трансляції, перевірте чи підтримує ваш сервіс трансляцій об'ємний звук на вході та виході. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast - це приклади сервісів де об'ємний звук цілком підтримується. Навпаки, Facebook Live і YouTube Live обидва підтримують вхідний об'ємний звук, але Facebook Live мікшує його до стерео, а YouTube Live відтворює лише два канали.\n\nАудіо фільтри самої OBS сумісні з об'ємним звуком, хоча підтримку VST плагінами не гарантовано." +Basic.Settings.Audio.MultichannelWarning.Title="Увімкнути об'ємний звук для виводу аудіо?" +Basic.Settings.Audio.MultichannelWarning.Confirm="Ви справді бажаєте увімкнути об'ємний звук для виводу аудіо?" Basic.Settings.Audio.DesktopDevice="Системний аудіопристрій" Basic.Settings.Audio.DesktopDevice2="Системний аудіопристрій 2" Basic.Settings.Audio.AuxDevice="Мікрофон/допоміжні аудіопристрої" @@ -618,6 +655,7 @@ Basic.Settings.Advanced.General.ProcessPriority="Пріоритет процес Basic.Settings.Advanced.General.ProcessPriority.High="Високий" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="Вище нормального" Basic.Settings.Advanced.General.ProcessPriority.Normal="Нормальний" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="Нижче нормального" Basic.Settings.Advanced.General.ProcessPriority.Idle="Очікування" Basic.Settings.Advanced.FormatWarning="Попередження: Формати кольору, які відрізняються від NV12, в першу чергу призначені для запису, і не рекомендуються для трансляцій. Трансляція може додатково навантажити ЦП через перетворення форматів." Basic.Settings.Advanced.Audio.BufferingTime="Розмір аудіо буфера, (мс)" diff --git a/UI/data/locale/vi-VN.ini b/UI/data/locale/vi-VN.ini index 179866c..b4caec9 100644 --- a/UI/data/locale/vi-VN.ini +++ b/UI/data/locale/vi-VN.ini @@ -17,9 +17,9 @@ Rename="Đổi tên" Interact="Tương tác" Filters="Bộ lọc" Properties="Thuộc tính" -MoveUp="Chuyển lên" -MoveDown="Chuyển xuống" -Settings="Tùy chỉnh" +MoveUp="Di chuyển lên" +MoveDown="Di chuyển xuống" +Settings="Cài đặt" Display="Hiển thị" Name="Tên" Exit="Thoát" @@ -27,21 +27,23 @@ Mixer="Bộ trộn" Browse="Chọn đường dẫn" Mono="Âm thanh đơn" Stereo="Âm thanh nổi" -DroppedFrames="Khung rớt %1 (%2%)" +DroppedFrames="Khung bị rớt %1 (%2%)" PreviewProjector="Toàn màn hình (xem trước)" SceneProjector="Toàn màn hình (cảnh)" SourceProjector="Toàn màn hình (nguồn)" +MultiviewProjector="Giao diện nhiều lớp (Toàn màn hình)" Clear="Xóa" Revert="Phục hồi" Show="Hiện" Hide="Ẩn" +UnhideAll="Bỏ ẩn tất cả" Untitled="Chưa Đặt Tên" New="Mới" Duplicate="Tạo bản sao" -Enable="Kích hoạt" +Enable="Bật" DisableOSXVSync="Tắt OSX V-Sync" ResetOSXVSyncOnExit="Đặt lại OSX V-Sync khi Thoát" -HighResourceUsage="Encoder quá tải! Hãy xem xét giảm chất lượng video hoặc sử dụng 1 encoder nhanh hơn." +HighResourceUsage="Bộ mã hóa bị quá tải! Hãy xem xét giảm chất lượng video hoặc sử dụng bộ mã hóa nhanh hơn." Transition="Chuyển cảnh" QuickTransitions="C. cảnh nhanh" Left="Trái" @@ -63,6 +65,9 @@ PasteDuplicate="Dán (Bản sao)" RemuxRecordings="Remux video" Next="Tiếp tục" Back="Quay lại" +Defaults="Mặc định" +StudioMode.Preview="Xem trước" +StudioMode.Program="Chương trình" AlreadyRunning.Title="OBS đã chạy" AlreadyRunning.Text="OBS đã chạy rồi! Trừ khi bạn muốn làm điều này, xin vui lòng tắt mọi chương trình hiện tại của OBS trước khi cố gắng chạy một chương trình mới. Nếu bạn có OBS thiết lập để thu nhỏ trên khay hệ thống, xin vui lòng kiểm tra để xem nếu nó vẫn đang chạy hay không." @@ -161,6 +166,7 @@ QuickTransitions.HotkeyName="C. cảnh nhanh: %1" Basic.AddTransition="Thêm cấu hình chuyển cảnh" Basic.RemoveTransition="Xóa cấu hình chuyển cảnh" +Basic.SceneTransitions="Chuyển cảnh" Basic.TransitionDuration="Thời gian" Basic.TogglePreviewProgramMode="Chế độ Studio" @@ -189,7 +195,7 @@ ConfirmRemove.Title="Xác nhận loại bỏ" ConfirmRemove.Text="Bạn có chắc bạn muốn loại bỏ '$1' không?" ConfirmRemove.TextMultiple="Bạn có chắc bạn muốn xóa %1 nội dung không?" -Output.StartStreamFailed="Không thể bắt đầu streaming" +Output.StartStreamFailed="Không thể bắt đầu stream" Output.StartRecordingFailed="Không thể bắt đầu quay video" Output.StartReplayFailed="Không thể khởi động replay buffer" Output.StartFailedGeneric="Bắt đầu đầu ra đã thất bại. Vui lòng kiểm tra các bản ghi để biết thêm chi tiết.\n\nGhi chú: nếu bạn đang sử dụng bộ mã hóa NVENC hoặc AMD, hãy chắc chắn rằng các trình điều khiển GPU được cập nhật phiên bản mới nhất." @@ -286,6 +292,8 @@ AddProfile.Text="Vui lòng nhập tên cấu hình" RenameProfile.Title="Đổi tên cấu hình" + + Basic.Main.PreviewDisabled="Xem trước hiện đang vô hiệu hoá" Basic.SourceSelect="Tạo/chọn nguồn" @@ -363,17 +371,18 @@ Basic.Main.AddSourceHelp.Text="Bạn cần phải có ít nhất 1 cảnh để Basic.Main.Scenes="Cảnh" Basic.Main.Sources="Nguồn" +Basic.Main.Controls="Điều khiển" Basic.Main.Connecting="Đang kết nối..." Basic.Main.StartRecording="Bắt đầu ghi" Basic.Main.StartReplayBuffer="Bắt đầu Replay Buffer" -Basic.Main.StartStreaming="Bắt đầu Streaming" +Basic.Main.StartStreaming="Bắt đầu Stream" Basic.Main.StopRecording="Dừng ghi" Basic.Main.StoppingRecording="Dừng ghi video..." Basic.Main.StopReplayBuffer="Dừng Replay Buffer" Basic.Main.StoppingReplayBuffer="Đang dừng Replay Buffer..." -Basic.Main.StopStreaming="Ngừng Streaming" -Basic.Main.StoppingStreaming="Dừng stream..." -Basic.Main.ForceStopStreaming="Ngừng Streaming (huỷ chậm trễ)" +Basic.Main.StopStreaming="Ngừng Stream" +Basic.Main.StoppingStreaming="Đang dừng stream..." +Basic.Main.ForceStopStreaming="Ngừng Stream (huỷ chậm trễ)" Basic.MainMenu.File="&Tập tin" Basic.MainMenu.File.Export="&Xuất" @@ -415,8 +424,11 @@ Basic.MainMenu.Edit.AdvAudio="Các thuộc tính âm thanh nâng cao" Basic.MainMenu.View="&Hiển thị" Basic.MainMenu.View.Toolbars="&Thanh công cụ" +Basic.MainMenu.View.Docks.ResetUI="Đặt lại UI" +Basic.MainMenu.View.Docks.LockUI="Khóa UI" Basic.MainMenu.View.Toolbars.Listboxes="&Listboxes" Basic.MainMenu.View.StatusBar="&Thanh trạng thái" +Basic.MainMenu.View.Fullscreen.Interface="Giao diện toàn màn hình" Basic.MainMenu.SceneCollection="& Bộ sưu tập cảnh" Basic.MainMenu.Profile="&Cấu hình" @@ -458,6 +470,12 @@ Basic.Settings.General.KeepReplayBufferStreamStops="Để replay buffer tiếp t Basic.Settings.General.SysTray="Khay hệ thống" Basic.Settings.General.SysTrayWhenStarted="Thu nhỏ về khay hệ thống khi bắt đầu" Basic.Settings.General.SystemTrayHideMinimize="Luôn luôn thu nhỏ về khay hệ thống thay vì thanh tác vụ" +Basic.Settings.General.StudioPortraitLayout="Bật bố cục theo chiều ngang/dọc" +Basic.Settings.General.MultiviewLayout="Giao diện nhiều lớp" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="Ngang, Trên" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Ngang, Dưới" +Basic.Settings.General.MultiviewLayout.Vertical.Left="Dọc, Trái" +Basic.Settings.General.MultiviewLayout.Vertical.Right="Dọc, Phải" Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Kiểu Stream" @@ -513,6 +531,8 @@ Basic.Settings.Output.Adv.Audio.Track1="Theo dõi 1" Basic.Settings.Output.Adv.Audio.Track2="Theo dõi 2" Basic.Settings.Output.Adv.Audio.Track3="Theo dõi 3" Basic.Settings.Output.Adv.Audio.Track4="Track 4" +Basic.Settings.Output.Adv.Audio.Track5="Track 5" +Basic.Settings.Output.Adv.Audio.Track6="Track 6" Basic.Settings.Output.Adv.Recording="Đang ghi" Basic.Settings.Output.Adv.Recording.Type="Loại" diff --git a/UI/data/locale/zh-CN.ini b/UI/data/locale/zh-CN.ini index 8d3f118..87fb751 100644 --- a/UI/data/locale/zh-CN.ini +++ b/UI/data/locale/zh-CN.ini @@ -28,16 +28,21 @@ Browse="浏览" Mono="单声道​" Stereo="立体声​​​" DroppedFrames="丢帧 %1 (%2%)" +StudioProgramProjector="全屏投影仪(程序)" PreviewProjector="全屏投影仪(预览)" SceneProjector="全屏投影仪 (现场)" SourceProjector="全屏投影仪(源)" -PreviewWindow="开窗式投影机 (预览)" -SceneWindow="开窗式投影机 (场景)" -SourceWindow="开窗式投影机 (源)" +StudioProgramWindow="窗口式投影仪 (程序)" +PreviewWindow="窗口化投影仪 (预览)" +SceneWindow="窗口化投影仪 (场景)" +SourceWindow="窗口化投影仪 (源)" +MultiviewProjector="多屏显示 (全屏)" +MultiviewWindowed="多屏显示 (窗口化)" Clear="清除" Revert="还原" Show="显示" Hide="隐藏" +UnhideAll="取消所有隐藏" Untitled="未命名" New="新建(&N)" Duplicate="复制(&D)" @@ -66,6 +71,13 @@ PasteDuplicate="粘贴(重复)" RemuxRecordings="转封装录像" Next="下一个" Back="后退" +Defaults="默认值" +HideMixer="混合器中隐藏" +TransitionOverride="过渡覆盖模式" +None="无" +StudioMode.Preview="预览" +StudioMode.Program="程序" +ShowInMultiview="多屏中显示" AlreadyRunning.Title="OBS 已在运行" AlreadyRunning.Text="OBS 已经在运行! 除非你想要这样做, 请在你运行一个新的 OBS 前, 关闭任何已经在运行的 OBS. 如果你有一个 OBS 设置最小化到系统托盘, 请检查他是否仍在运行." @@ -300,6 +312,10 @@ AddProfile.Text="请输入配置文件的名称" RenameProfile.Title="重命名配置文件" +Basic.Main.MixerRename.Title="重命名音频源" +Basic.Main.MixerRename.Text="请输入音频源名称" + + Basic.Main.PreviewDisabled="预览当前已禁用" Basic.SourceSelect="创建或选择源" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="你需要至少一个场景来添加源。" Basic.Main.Scenes="场景" Basic.Main.Sources="来源" +Basic.Main.Controls="控件" Basic.Main.Connecting="连接中..." Basic.Main.StartRecording="开始录制" Basic.Main.StartReplayBuffer="开始回放缓存" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="高级音频属性(&A)" Basic.MainMenu.View="查看 (&V)" Basic.MainMenu.View.Toolbars="工具栏 (&T)" +Basic.MainMenu.View.Docks="停靠部件" +Basic.MainMenu.View.Docks.ResetUI="重置界面" +Basic.MainMenu.View.Docks.LockUI="锁定界面" Basic.MainMenu.View.Toolbars.Listboxes="列表框 (&L)" Basic.MainMenu.View.SceneTransitions="场景过渡 (&C)" Basic.MainMenu.View.StatusBar="状态栏 (&S)" +Basic.MainMenu.View.Fullscreen.Interface="全屏界面" Basic.MainMenu.SceneCollection="场景集合 (&S)" Basic.MainMenu.Profile="配置文件 (&P)" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="场景机会已存在" Basic.MainMenu.Tools="工具 (&T)" Basic.MainMenu.Help="帮助 (&H)" +Basic.MainMenu.Help.HelpPortal="帮助门户" Basic.MainMenu.Help.Website="访问OBS主页 (&W)" Basic.MainMenu.Help.Logs="日志文件 (&L)" Basic.MainMenu.Help.Logs.ShowLogs="显示日志文件 (&S)" @@ -483,9 +505,16 @@ Basic.Settings.General.SysTray="系统托盘" Basic.Settings.General.SysTrayWhenStarted="开始时最小化到系统托盘" Basic.Settings.General.SystemTrayHideMinimize="总是最小化到系统托盘, 而不是任务栏" Basic.Settings.General.SaveProjectors="退出时保存投影仪" +Basic.Settings.General.SwitchOnDoubleClick="双击时切换到场景" +Basic.Settings.General.StudioPortraitLayout="启用纵向布局" +Basic.Settings.General.MultiviewLayout="多视图布局" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="水平、顶部" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="水平、底部" +Basic.Settings.General.MultiviewLayout.Vertical.Left="垂直, 左侧" +Basic.Settings.General.MultiviewLayout.Vertical.Right="垂直, 右侧" Basic.Settings.Stream="流" -Basic.Settings.Stream.StreamType="串流类型" +Basic.Settings.Stream.StreamType="流类型" Basic.Settings.Output="输出" Basic.Settings.Output.Format="录像格式" @@ -602,6 +631,10 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos(削尖缩放, 32个样本) Basic.Settings.Audio="音频" Basic.Settings.Audio.SampleRate="采样率" Basic.Settings.Audio.Channels="声道" +Basic.Settings.Audio.MultiChannelWarning.Enabled="警告: 已启用环绕声音频。" +Basic.Settings.Audio.MultichannelWarning="如果串流, 请检查串流服务是否支持环绕立体声接收和环绕立体声播放。 Twitch, Facebook 360 Live, Mixer RTMP, Smashcast 是充分支持环绕立体声的例子。 虽然 Facebook Live 和 Youtube Live 都支持环绕立体声接收, 但是Facebook Live 降低混合至立体声, 而 Youtube Live 则只播放两个声道。\n\nOBS 音频过滤器与环绕立体声兼容, 但 VST 插件支持无法保证。" +Basic.Settings.Audio.MultichannelWarning.Title="是否启用环绕立体声?" +Basic.Settings.Audio.MultichannelWarning.Confirm="确实要启用环绕立体声吗?" Basic.Settings.Audio.DesktopDevice="桌面音频设备" Basic.Settings.Audio.DesktopDevice2="桌面音频设备 2" Basic.Settings.Audio.AuxDevice="麦克风/辅助音频设备" @@ -618,6 +651,7 @@ Basic.Settings.Advanced.General.ProcessPriority="进程优先级" Basic.Settings.Advanced.General.ProcessPriority.High="高" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="高于正常" Basic.Settings.Advanced.General.ProcessPriority.Normal="常规" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="低于正常" Basic.Settings.Advanced.General.ProcessPriority.Idle="空闲" Basic.Settings.Advanced.FormatWarning="警告: NV12 之外的颜色格式主要用于录像, 并不推荐在流式传输时使用. 流可能导致更多的 CPU 使用率, 因为需要颜色格式转换." Basic.Settings.Advanced.Audio.BufferingTime="音频缓冲时间" diff --git a/UI/data/locale/zh-TW.ini b/UI/data/locale/zh-TW.ini index ea482a8..3e06437 100644 --- a/UI/data/locale/zh-TW.ini +++ b/UI/data/locale/zh-TW.ini @@ -28,16 +28,21 @@ Browse="瀏覽" Mono="單聲道" Stereo="立體聲" DroppedFrames="影格遺失: %1 (%2%)" +StudioProgramProjector="全屏投影(程式)" PreviewProjector="全螢幕投影(預覽)" SceneProjector="全螢幕投影(場景)" SourceProjector="全螢幕投影(來源)" +StudioProgramWindow="窗式投影儀(程序)" PreviewWindow="視窗化投影(預覽)" SceneWindow="視窗化投影(場景)" SourceWindow="視窗化投影(來源)" +MultiviewProjector="多視圖(全屏)" +MultiviewWindowed="多視圖(窗口)" Clear="清除" Revert="復原" Show="顯示" Hide="隱藏" +UnhideAll="全部取消隱藏" Untitled="無標題" New="新增" Duplicate="複製" @@ -66,6 +71,13 @@ PasteDuplicate="貼上 (重複)" RemuxRecordings="重新封裝錄影" Next="下一步" Back="返回" +Defaults="預設" +HideMixer="在混合器中隱藏" +TransitionOverride="轉換覆蓋" +None="無" +StudioMode.Preview="預覽" +StudioMode.Program="程式" +ShowInMultiview="在多視圖中顯示" AlreadyRunning.Title="OBS 已在執行中" AlreadyRunning.Text="OBS 已在執行中!除非這是您的意圖,請在執行新的 OBS 前關閉現存的 OBS 。如果有設定 OBS 最小化到系統工具列,請確認是否仍在該處執行。" @@ -300,6 +312,10 @@ AddProfile.Text="請輸入設定檔的名稱" RenameProfile.Title="重新命名設定檔" +Basic.Main.MixerRename.Title="重新命名音訊源" +Basic.Main.MixerRename.Text="請輸入此音訊源的名稱" + + Basic.Main.PreviewDisabled="目前預覽已停用" Basic.SourceSelect="建立/選取來源" @@ -377,6 +393,7 @@ Basic.Main.AddSourceHelp.Text="您必須擁有至少一個場景才能新增來 Basic.Main.Scenes="場景" Basic.Main.Sources="來源" +Basic.Main.Controls="控制項" Basic.Main.Connecting="連線中……" Basic.Main.StartRecording="開始錄製" Basic.Main.StartReplayBuffer="開始重播緩衝" @@ -432,9 +449,13 @@ Basic.MainMenu.Edit.AdvAudio="進階音訊屬性(&A)" Basic.MainMenu.View="檢視(&V)" Basic.MainMenu.View.Toolbars="工具列(&T)" +Basic.MainMenu.View.Docks="固定" +Basic.MainMenu.View.Docks.ResetUI="重置使用者介面" +Basic.MainMenu.View.Docks.LockUI="鎖定使用者介面" Basic.MainMenu.View.Toolbars.Listboxes="列表控制項(&L)" Basic.MainMenu.View.SceneTransitions="轉場特效(&c)" Basic.MainMenu.View.StatusBar="狀態列(&S)" +Basic.MainMenu.View.Fullscreen.Interface="全螢幕介面" Basic.MainMenu.SceneCollection="場景群組 (&S)" Basic.MainMenu.Profile="設定檔 (&P)" @@ -448,6 +469,7 @@ Basic.MainMenu.SceneCollection.Exists="已有該場景群組" Basic.MainMenu.Tools="工具(&T)" Basic.MainMenu.Help="說明 (&H)" +Basic.MainMenu.Help.HelpPortal="幫助和門戶" Basic.MainMenu.Help.Website="前往 OBS 網站 (&W)" Basic.MainMenu.Help.Logs="Log 檔案 (&L)" Basic.MainMenu.Help.Logs.ShowLogs="顯示 Log (&S)" @@ -483,6 +505,13 @@ Basic.Settings.General.SysTray="系統工具列" Basic.Settings.General.SysTrayWhenStarted="開始時最小化至系統列" Basic.Settings.General.SystemTrayHideMinimize="總是最小化到系統列,而非工作列" Basic.Settings.General.SaveProjectors="退出時保存投影設定" +Basic.Settings.General.SwitchOnDoubleClick="按兩下時切換到場景" +Basic.Settings.General.StudioPortraitLayout="啟用縱向/垂直佈局" +Basic.Settings.General.MultiviewLayout="多視圖佈局" +Basic.Settings.General.MultiviewLayout.Horizontal.Top="水平, 上" +Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="水平, 下" +Basic.Settings.General.MultiviewLayout.Vertical.Left="垂直, 左" +Basic.Settings.General.MultiviewLayout.Vertical.Right="垂直, 右" Basic.Settings.Stream="串流" Basic.Settings.Stream.StreamType="串流類型" @@ -602,6 +631,10 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos(縮放後最為銳利, Basic.Settings.Audio="音效" Basic.Settings.Audio.SampleRate="取樣頻率" Basic.Settings.Audio.Channels="通道數" +Basic.Settings.Audio.MultiChannelWarning.Enabled="警告: 已啟用環繞聲音訊。" +Basic.Settings.Audio.MultichannelWarning="如果是流媒體,請檢查您的流媒體服務是否同時支持環繞聲攝取和環繞聲播放。 Twitch,Facebook 360 Live,Mixer RTMP,Smashcast都是完全支持環繞聲的例子。 儘管Facebook Live和YouTube Live都接受環繞聲攝取,但是Facebook Live會混音為立體聲,而YouTube Live只播放兩個聲道。\n\n 儘管不支持VST插件,但OBS音頻濾波器與環繞聲兼容。" +Basic.Settings.Audio.MultichannelWarning.Title="是否啟用環繞聲音訊?" +Basic.Settings.Audio.MultichannelWarning.Confirm="確實要啟用環繞聲音訊嗎?" Basic.Settings.Audio.DesktopDevice="輸出音效 1" Basic.Settings.Audio.DesktopDevice2="輸出音效 2" Basic.Settings.Audio.AuxDevice="麥克風/輸入音效 1" @@ -618,6 +651,7 @@ Basic.Settings.Advanced.General.ProcessPriority="程序優先順序" Basic.Settings.Advanced.General.ProcessPriority.High="高" Basic.Settings.Advanced.General.ProcessPriority.AboveNormal="在標準以上" Basic.Settings.Advanced.General.ProcessPriority.Normal="標準" +Basic.Settings.Advanced.General.ProcessPriority.BelowNormal="低於一般" Basic.Settings.Advanced.General.ProcessPriority.Idle="閒置" Basic.Settings.Advanced.FormatWarning="警告: NV12以外的色彩格式主要用於錄影,並不推薦在串流時使用。串流時可能會因需要轉換色彩格式而增加CPU使用率。" Basic.Settings.Advanced.Audio.BufferingTime="音訊緩衝時間" @@ -650,7 +684,7 @@ Basic.AdvAudio.Monitoring.MonitorOnly="僅監測(輸出為靜音)" Basic.AdvAudio.Monitoring.Both="監測和輸出" Basic.AdvAudio.AudioTracks="音軌" -Basic.Settings.Hotkeys="快捷鍵" +Basic.Settings.Hotkeys="快速鍵" Basic.Settings.Hotkeys.Pair="按鍵組合與'%1'共用作為切換鍵" Basic.Hotkeys.SelectScene="切換到場景" diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss new file mode 100644 index 0000000..af3eb90 --- /dev/null +++ b/UI/data/themes/Acri.qss @@ -0,0 +1,776 @@ +/* General style, we override only what is needed. */ +QWidget { + background-color: #181819; + alternate-background-color: rgb(18,18,21); + color: rgb(225,224,225); + selection-background-color: #252458; + selection-color: white; + outline: none; + font-family: "Open Sans", "Tahoma", "Arial", sans-serif; + font-size: 12px; + padding: 4px; +} + +#menubar { + padding: 2px 8px 0px; +} + +QLabel:link { + color: #2a3a75; +} + +QMenu { + border: 1px solid #333336; +} + +* [frameShape="1"], * [frameShape="2"], * [frameShape="3"], * [frameShape="4"], * [frameShape="5"], * [frameShape="6"] { + /*border: 1px solid rgb(231,30,31); */ + border: none; +} + +* [frameShape="2"], +* [frameShape="4"] { + border: 1px solid #333336; +} + + +QSizeGrip { + background-color: transparent; + image: url(./Acri/sizegrip.png); +} + +/* Misc */ + +QWidget::disabled { + color: #484848; +} + +/* Dropdown menus, Scenes box, Sources box */ +QAbstractItemView { + background-color: #181819; + border: 1px solid #333336; +} + +/* Group Box */ + +QGroupBox { + border: 2px solid #2f2f2f; + margin-bottom: 8px; + padding-top: 32px; + font-weight: bold; + font-size: 14px; +} + +QGroupBox::title { + left: 4px; + right: 0; + top: 8px; + font-weight: bold; + padding-left: 8px; + padding-right: 8px; + padding-bottom: 8px; + font-size: 16px; +} + +/* --- */ + + +/* Tooltips */ +QToolTip { + background-color: #212121; + color: rgb(205,205,205); + border: 1px solid #343335; + border-radius: 4px; +} + +/* Top Menu Bar Items */ +QMenuBar::item { + background-color: rgb(24,24,25); + padding: 6px; +} + +QMenuBar::item:selected { + background: #2a3a75; +} + +/* Listbox item */ +QListWidget::item { + padding: 4px 2px; + margin-bottom: 2px; + margin-top: 0px; + border: 1px solid transparent; +} + +QListWidget QLineEdit { + padding-top: 0px; + padding-bottom: 0px; + padding-right: 0; + padding-left: 2px; + border: none; + border-radius: none; +} + +SourceListWidget::item { + margin-bottom: 1px; + padding: -4px 2px; +} + +/* Dock stuff */ +QDockWidget { + background: transparent; + border: none; + font-size: 14px; + font-weight: bold; + border-bottom: 2px solid #2f2f2f; +} + +QDockWidget::title { + border-bottom: 2px solid #2f2f2f; + margin-left: 5px; + margin-right: 5px; + padding-top: 0px; + padding-bottom: 6px; + text-align: left; + background-image: url(./Acri/top_hook.png); + background-origin: padding; + background-clip: padding; + background-position: bottom left; + background-repeat: none; +} + +QDockWidget::close-button, +QDockWidget::float-button { + icon-size: 20px; + subcontrol-position: top right; + subcontrol-origin: padding; + right: 0px; +} + +QDockWidget::float-button { + right: 20px; +} + + +QListWidget#scenes, +SourceListWidget { + border: none; + border-bottom: 2px solid #2f2f2f; +} + +#scenesFrame, +#sourcesFrame { + margin-left: -7px; + margin-right: -7px; + margin-top: -8px; + margin-bottom: -12px; +} + +#scenesToolbar, +#sourcesToolbar{ + background-image: url(./Acri/bot_hook2.png); + background-origin: margin; + background-clip: margin; + background-position: top left; + background-repeat: none; +} + +#sourcesToolbar { + background-image: url(./Acri/bot_hook.png); +} + +/* Listbox item selected, unfocused */ +QListWidget::item:hover { + background-color: #212121; + border: 1px solid #333336; +} + +/* Listbox item selected */ +QListWidget::item:selected { + background-color: #131a30; + border: 1px solid #252a45; +} + +/* ScrollBars */ + +QScrollBar::corner { + background-color: transparent; + border: none; +} + +QScrollBar:vertical { + background-color: transparent; + width: 20px; + margin-top: -3px; + margin-bottom: -3px; +} + +QScrollBar::handle:vertical { + background-color: #2f2f2f; + min-height: 20px; + margin: 0px 3px; + border-radius: 0px; + border: none; +} + +QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + border: none; + background: none; +} + +QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical, QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + border: none; + background: none; + color: none; +} + +QScrollBar:horizontal { + background-color: transparent; + height: 10px; + margin-left: -3px; + margin-right: -3px; +} + +QScrollBar::handle:horizontal { + background-color: #2f2f2f; + min-width: 20px; + margin: 0px 0px -3px; + border-radius: 0px; + border: none; +} + +QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { + border: none; + background: none; +} + +QScrollBar::left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + border: none; + background: none; + color: none; +} + +/* Scenes and Sources toolbar */ + +QToolBar { + background-color: rgb(24,24,25); + margin-top: 4px; +} + +QToolButton:hover { + background-color: #2a3a75; + border: 1px solid #233166; + border-radius: none; +} + +QToolButton:pressed { + background-color: #161f41; + border-radius: none; +} + +* [themeID="addIconSmall"] { + qproperty-icon: url(./Acri/plus.png); +} + +* [themeID="removeIconSmall"] { + qproperty-icon: url(./Acri/minus.png); +} + +* [themeID="propertiesIconSmall"] { + qproperty-icon: url(./Acri/cogwheel.png); +} + +* [themeID="configIconSmall"] { + qproperty-icon: url(./Acri/cogwheel.png); +} + +* [themeID="upArrowIconSmall"] { + qproperty-icon: url(./Acri/up_arrow.png); +} + +* [themeID="downArrowIconSmall"] { + qproperty-icon: url(./Acri/down_arrow.png); +} + +/* Tab Widget */ + +QTabWidget::pane { /* The tab widget frame */ + border-top: 1px solid #2f2f2f; +} + +QTabWidget::tab-bar { + alignment: left; +} + +QTabBar::tab { + background-color: #212121; + border: 1px solid #333336; + min-width: 8ex; + padding-top: 4px; + padding-bottom: 4px; + padding-left: 10px; + padding-right: 10px; + margin-right: 2px; + margin-top: 1px; + margin-bottom: 1px; +} + +QTabBar::tab:selected { + background-color: #131a30; + border-color: #252a45; +} + +QTabBar::tab:hover { + background-color: #233166; + border-color: #364683; +} + +QTabBar::tab:pressed { + background-color: #161f41; +} + +/* ComboBox */ + +QComboBox { + background-color: rgb(40,40,42); + border-style: solid; + border: 1px; + border-color: rgb(24,24,30); + padding: 4px; + padding-left: 10px; +} + +QComboBox::drop-down { + border:none; + border-left: 1px solid rgba(31,30,31,155); + width: 20px; +} + +QComboBox::down-arrow { + qproperty-alignment: AlignTop; + image: url(./Acri/updown.png); + width: 100%; +} + +QComboBox:on { + background-color: #2a3a75; +} + +QComboBox:editable { + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; +} + +QComboBox::drop-down:editable { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QComboBox::down-arrow:editable { + qproperty-alignment: AlignTop; + image: url(./Acri/down_arrow.png); + width: 8%; +} + +/* Textedits etc */ + +QLineEdit, QTextEdit, QPlainTextEdit { + background-color: rgb(8,8,11); + border: none; + padding-left: 2px; +} + +/* Spinbox and doubleSpinbox */ + +QSpinBox, QDoubleSpinBox { + background-color: rgb(8,8,11); + border: none; + padding-left: 2px; + padding-right: 15px; + margin-right: 10px; +} + +QSpinBox::up-button, QDoubleSpinBox::up-button { + subcontrol-origin: margin; + subcontrol-position: top right; /* position at the top right corner */ + + background-color: rgb(24,24,30); + border: 1px solid rgb(8,8,11); + border-radius: 3px; + border-width: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom-width: 0; +} + +QSpinBox::down-button, QDoubleSpinBox::down-button { + subcontrol-origin: margin; + subcontrol-position: bottom right; /* position at the top right corner */ + background-color: rgb(24,24,30); + border: 1px solid rgb(8,8,11); + border-radius: 3px; + border-width: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top-width: 0; +} + +QSpinBox::up-button:hover, QSpinBox::down-button:hover, QDoubleSpinBox::up-button:hover, QDoubleSpinBox::down-button:hover { + background-color: rgb(15,33,51); +} + +QSpinBox::up-button:pressed, QSpinBox::down-button:pressed, QDoubleSpinBox::up-button:pressed, QDoubleSpinBox::down-button:pressed { + background-color: rgb(24,24,25); +} + +QSpinBox::up-button:disabled, QSpinBox::up-button:off, QSpinBox::down-button:disabled, QSpinBox::down-button:off { + background-color: rgb(24,24,25); +} + +QDoubleSpinBox::up-button:disabled, QDoubleSpinBox::up-button:off, QDoubleSpinBox::down-button:disabled, QDoubleSpinBox::down-button:off { + background-color: rgb(24,24,25); +} + +QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { + image: url(./Acri/up_arrow.png); + width: 100%; +} + +QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { + image: url(./Acri/down_arrow.png); + width: 100%; +} + + +/* Buttons */ + +QPushButton { + color: rgb(225,224,225); + background-color: #162458; + border: 1px solid #233166; + padding: 6px 20px; + margin: 1px; +} + +QPushButton::flat { + background-color: rgb(24,24,25); +} + +QPushButton:checked { + background-color: #202b52; +} + +QPushButton:hover { + background-color: #2a3a75; + border: 1px solid #364683; +} + +QPushButton:pressed { + background-color: #161f41; +} + +/* Progress Bar */ + +QProgressBar { + background: #101010; + border: 2px solid #363636; + border-radius: 0px; + text-align: center; +} + +QProgressBar::chunk { + background-color: #2a3a75; +} + +/* Sliders */ + +QSlider::groove:horizontal { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(31,30,31), + stop: 0.75 rgb(50, 49, 50)); + height: 4px; + border: none; + border-radius: 2px; +} + +QSlider::handle:horizontal { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(240,239,240), + stop: 0.25 rgb(200,199,200), + stop: 1 rgb(162,161,162)); + border: 1px solid rgb(24,24,25); + border-radius: 3px; + height: 10px; + width: 18px; + margin: -3px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ +} + +QSlider::handle:horizontal:pressed { + background-color: QLinearGradient(x1: 0, y1: 1, x2: 0, y2: 0, + stop: 0 rgb(240,239,240), + stop: 0.25 rgb(200,199,200), + stop: 1 rgb(162,161,162)); +} + +QSlider::sub-page:horizontal:disabled { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(31,30,31), + stop: 0.75 rgb(50, 49, 50)); + border-radius: 2px; +} + +QSlider::groove:vertical { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0 rgb(31,30,31), + stop: 0.75 rgb(50, 49, 50)); + width: 4px; + border: none; + border-radius: 2px; +} + +QSlider::handle:vertical { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0 rgb(240,239,240), + stop: 0.25 rgb(200,199,200), + stop: 1 rgb(162,161,162)); + border: 1px solid rgb(24,24,25); + border-radius: 4px; + width: 10px; + height: 18px; + margin: -3px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ +} + +QSlider::handle:vertical:pressed { + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + stop: 0 rgb(240,239,240), + stop: 0.25 rgb(200,199,200), + stop: 1 rgb(162,161,162)); +} + +QSlider::sub-page:vertical:disabled { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0 rgb(31,30,31), + stop: 0.75 rgb(50, 49, 50)); + border-radius: 2px; +} + +QSlider::handle:hover { + background-color: rgb(200,199,200); +} + +QSlider::sub-page { + background-color: #2a3a75; +} + +QSlider::handle:disabled { + background-color: rgb(15,15,16); +} + + +/* Volume Control */ + +/* Old Meters */ +VolumeMeter { + qproperty-bkColor: rgb(8,8,11); + qproperty-magColor:; + qproperty-peakColor:; + qproperty-peakHoldColor: rgb(225,224,225); +} + +VolumeMeter { + + qproperty-backgroundNominalColor: #42740c; + qproperty-backgroundWarningColor: #988F0F; + qproperty-backgroundErrorColor: #802004; + qproperty-foregroundNominalColor: #84D82B; + qproperty-foregroundWarningColor: #E4D717; + qproperty-foregroundErrorColor: #D74116; + qproperty-magnitudeColor: rgb(49, 54, 59); /* Blue-gray */ + qproperty-majorTickColor: rgb(239, 240, 241); /* White */ + qproperty-minorTickColor: rgb(118, 121, 124); /* Light Gray */ + qproperty-peakDecayRate: 23.4; /* Override of the standard PPM Type I rate. */ +} + + +/* Status Bar */ + + +QStatusBar::item { + border: none; +} + +/* Checkboxes */ +QCheckBox::indicator, +QGroupBox::indicator { + width: 20px; + height: 20px; +} + +QGroupBox::indicator { + margin-left: 2px; +} + +QCheckBox::indicator:unchecked { + image: url(./Acri/checkbox_unchecked.png); +} + +QCheckBox::indicator:unchecked:hover, +QGroupBox::indicator:unchecked:hover { + border: none; + image: url(./Acri/checkbox_unchecked_focus.png); +} + +QCheckBox::indicator:checked { + image: url(./Acri/checkbox_checked.png); +} + +QCheckBox::indicator:checked:hover, +QGroupBox::indicator:checked:hover { + border: none; + image: url(./Acri/checkbox_checked_focus.png); +} + +QCheckBox::indicator:checked:disabled, +QGroupBox::indicator:checked:disabled { + image: url(./Acri/checkbox_checked_disabled.png); +} + +QCheckBox::indicator:unchecked:disabled, +QGroupBox::indicator:unchecked:disabled { + image: url(./Acri/checkbox_unchecked_disabled.png); +} + +/* Radio Buttons */ + +QRadioButton::indicator { + width: 19px; + height: 19px; +} + +QRadioButton::indicator:unchecked { + image: url(./Acri/radio_unchecked.png); +} + + +QRadioButton::indicator:unchecked:hover, +QRadioButton::indicator:unchecked:focus, +QRadioButton::indicator:unchecked:pressed { + border: none; + outline: none; + image: url(./Acri/radio_unchecked_focus.png); +} + +QRadioButton::indicator:checked { + border: none; + outline: none; + image: url(./Acri/radio_checked.png); +} + +QRadioButton::indicator:checked:hover, +QRadioButton::indicator:checked:focus, +QRadioButton::indicator:checked:pressed { + border: none; + outline: none; + image: url(./Acri/radio_checked_focus.png); +} + +QRadioButton::indicator:checked:disabled { + outline: none; + image: url(./Acri/radio_checked_disabled.png); +} + +QRadioButton::indicator:unchecked:disabled { + image: url(./Acri/radio_unchecked_disabled.png); +} + +/* Mute CheckBox */ + +MuteCheckBox { + outline: none; +} + +MuteCheckBox::indicator:checked { + image: url(./Dark/mute.png); +} + +MuteCheckBox::indicator:unchecked { + image: url(./Dark/unmute.png); +} + +MuteCheckBox::indicator:unchecked:hover { + image: url(./Dark/unmute.png); +} + +MuteCheckBox::indicator:unchecked:focus { + image: url(./Dark/unmute.png); +} +MuteCheckBox::indicator:checked:hover { + image: url(./Dark/mute.png); +} + +MuteCheckBox::indicator:checked:focus { + image: url(./Dark/mute.png); +} + +MuteCheckBox::indicator:checked:disabled { + image: url(./Dark/mute.png); +} + +MuteCheckBox::indicator:unchecked:disabled { + image: url(./Dark/unmute.png); +} + +OBSHotkeyLabel[hotkeyPairHover=true] { + color: rgba(27, 96, 166); +} + + +/* Label warning/error */ + +QLabel#warningLabel { + color: rgb(192, 128, 0); + font-weight: bold; +} + +QLabel#errorLabel { + color: rgb(192, 0, 0); + font-weight: bold; +} + +/* Settings Menu */ + +#buttonBox { + border-top: 2px solid grey; +} + +/* Special Fixes */ + +OBSBasicProperties, +#OBSBasicSettings, +#OBSBasicFilters { + background: #101010; +} + +#OBSBasicSourceSelect #sourceList { + border-bottom: 2px solid #333336; +} + +FocusList::item { + padding: 0px 2px; +} + +#transitionsContainer QPushButton, +#mixerDock QPushButton, +#effectWidget QPushButton, +#asyncWidget QPushButton { + border: none; +} + +#fpsTypes { + padding: 0px; +} + +#finishPage QLabel { + padding: -2px 0px; + background: transparent; + min-height: 26px; +} diff --git a/UI/data/themes/Acri/bot_hook.png b/UI/data/themes/Acri/bot_hook.png new file mode 100644 index 0000000000000000000000000000000000000000..6a0fe30ba579baa0e3b50912a795516a279834be GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0y~yV15B)Gjp&3Nrn@u7C_R()5S5Qg7NKjMaBaT0?ZqV zL;k-Sl&YZR=Clv>t;X>gQu&X%Q~lo FCIE8@9V7q% literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/bot_hook2.png b/UI/data/themes/Acri/bot_hook2.png new file mode 100644 index 0000000000000000000000000000000000000000..8e65fd1e85a2e37cfdfe51b678ff48d6aab7973e GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0y~yV15B)Gjp&3Nrn@u7C_R()5S5Qg7NKjMMee%0hSH< zC4Vo=a%D6mCMlZbKJ>Ws9jIo;w@s;8SuAO6C5)*OrV#AC%4Zj&m)EpWZ7@1f&ETkVO2|Y0WBpd$UbH%R7 T1^*5JjbiY0^>bP0l+XkK>_sPc literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/checkbox_checked_disabled.png b/UI/data/themes/Acri/checkbox_checked_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2a9d8908840b851844263a2a959544b8d90908db GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzJ5LwKkP61P*UcFZ1n@XJDjU~_ zakX!`pI7m_H%L|TmQBOEbMt+9SM)PS@K0!G1nK~SGrSu*7;Kc^|6s~kaW%T9>3}sz N)YH|^Wt~$(697rNBjEr5 literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/checkbox_checked_focus.png b/UI/data/themes/Acri/checkbox_checked_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..d8662a2aaeac77f7889573cc7bcc0383c86a85d9 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzdrud~kP61P*EcdU2#6fHU_V!} zC;j3L#s-m>@s;8SuAO6C5)*OrV#AC%4Zj&m)EpWZ7@1f&ETkVO2|Y0WBpd$vRq0P_ Tk$a{UHOc=D|#KgqOhxT|lzn(fR(pkf%<#c=L~?Ekxxlf$=io!SEu^>p=fS?83{ F1OV3HB8mV2 literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/checkbox_unchecked_disabled.png b/UI/data/themes/Acri/checkbox_unchecked_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..b6178741b46cc433a1dc7774bef4e64a1bb5b888 GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzOHUWakP61P*UcFZIPka{DjU~_ z*{c-A>g~Q3?m6wBUBkO`XYNY{G|L0kLP3IlgHh0N`MUkreD;{x=72;!UHx3vIVCg! E07w`gzW@LL literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/checkbox_unchecked_focus.png b/UI/data/themes/Acri/checkbox_unchecked_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..77a868281d36a08c45e3a5330a94234242a99877 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz8&4O3krCYI*jAj4;002ovPDHLk FV1i+YZ8rb_ literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/down_arrow.png b/UI/data/themes/Acri/down_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..39d31da89d6edfef47f642073fda4202e2b08b9c GIT binary patch literal 527 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV7%t(;uuoF`1U$uJ5#j8@rRjN zUlYZ$7=O1Os`N6rlk1|2)ZhF+l{J3ST>`qF%qDNFKd583`{CO5?}GpTT>D^9*&IAMeO6Kx>=SXD%`|P5g zu%8@i-;ccy>iDtnf?!Ae+R*9ddsY9{RJ=Z2zjcZo(?@5M?zYd-?ib>IK7N$I;xGKa z;A#KYZ|nYV?-9)Deem*8{^lQ-1?x5n_AL0q=BBVuup^)4k#yq^C^IjUB`<>|FP$YX zjU_LYB`<{~?{W98PW?Ax`*uiMG`xQ&uy1p7$9scE))Ri{muY;I^{HtWzR!5H+~cEc zNKL!w|C^X*A{h^2PVIO2Q{}!v{$BN-y+2C(9%#?|A=>v~#($lIdR+PlDZ@3IP{*Z?v2 XZxz<#oD^-J1mbwQ`njxgN@xNA6+!JL literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/minus.png b/UI/data/themes/Acri/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..26e35b4e40d74afb525d36578380cf1f4bd93cae GIT binary patch literal 110 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxc&!&(3-C~KoQ20AirP+ zhi5m^fE+PT7srqa#$<-3z=Z$uJpU#fVR$9Y$}rns+dj8Ezze8?!PC{xWt~$(699=Z BAl(1} literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/mute.png b/UI/data/themes/Acri/mute.png new file mode 100644 index 0000000000000000000000000000000000000000..682f91edfac637fd742aecaab27751f3e2bcf290 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`^F3W0Ln;`TUS?!taujL*cr#Qx zV)bd4MoahDMXO7i99DEN35XeTTOIDw6a4c#X~Us+cFp$>d`Ns|tNwoSm)LU~-Pno~ z8pRG>lDhGT*|kn%gR}JBg?)Jn?Y}4g=i~h_<6$fFvK0Z!J-ilI1lDCJywPnGV(dCM zBZ5g(N@Ldo3F#9b8VZ~n3O3n25@hwd^U>$O+i!)N?v+zF`A9!Dx>?J(SLv~l%nbWa QK&LWzy85}Sb4q9e0IU#H@c;k- literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/plus.png b/UI/data/themes/Acri/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..69325d1d810f6a5bdac715a356a2d6ac9e55287b GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxc&!&(3-C~KoQ20AirP+ zhi5m^fE;O07srqa#^eKBKlpoe+WyuX{BRaZW?>UiV_;ZyIP9gTfaMpUG6qjqKbLh* G2~7Y&C+Vj#0WG) zG-|+6oB$fCAp!lc6sPbU-|-ht2>iu2JjPKhKsQ983N*U`bhx82=HeQ@K@`##T)<@1 zgA2662GHS+uGocl5GMZOtK@_*@e&)*5^m6789;*vx?w-QKp6bQaZJS!bdiQ)7S7_g za?B^}L<`h{Wq@jEiajP5KZ7BNg%=`_Bt^g%i5QM6CLz|L4l2VmfCEWbX!6+CBN6^+ zh&(LDMrkqf&=CH}#%`0>F&+`n%>uY!Fm5OZu0t4-kb`}AhF@?>zwiwEkb@+|V-JME zS#&~mHGqyptWm!9(}+hB#^WyjK)}o&+{Ji#Otx}EUoaC9(2M~pqXW)CNZ&93{>Z^S zI3ZxgiF?Q~9BT}H01Z&l7{DE4@B%_Qf^am#KIJ5^^auOU5XraBD9?}3`Zo+i z!xH`tAF00~j#NfRlS0(rP)zRHC`5NH3eiEe5UHj3dkL1}EbCHSx{Fbq#bQ*WESBS4 zc9&zdA^B?3kPJpFJQ1!mBtEceNYv}C)`5-!k6x+(ou zh^Dj^+@aZN%RQ8~{ClalWK$z7U)H=@wN00000ycTisXFZJORZR-ZKb zjm|v>wrP?sX@LE6+1Xh~6}Tb@u}DEOVh{)yREA!X0UW4}c(g$-=3ouBNNX?)Bhehu zs0l}L0%)j?1oX!;oW=|Mz&|)4@DJbd1jn!t-4TVV(Ch}#;f^MlhwJzbQAl5L5mQhf zF3<`aK!-QFVK?4GnD~cpk`uzjD{Mq7xIwcF01Y1Kjsy4#VektlFbzY|RT_rbIEO#V zF`uyuEm7Mt0IH)I_L^M$EQTT$UWh=F6ail(Vg#<5gjkQdsA3ub4kTfb$z$JuMEIi- z@~{M(q$S8hBlsg5drV%(1VlhL3*dqwxTzet9$`pA4))_Ye#0sK#&hgP4w4X$y$}ZH z&>1z<06G$}R{7e`ARbAWfP450VFrJ34-@1u*~$%l#VkZXGX|)FjyMk?eaArfBM0~4 zgs=i9?jy%=tTFUKG(;t10C$YVO9<&G!qFJ}m6KS)U+hOCB;yK%^a#UHyC8suP%Og_ z2x$U5kdNmOHa$l^{ICc@`iAlFGX_Y+HvEIYU-U%;mf#nJO~0@N;TWY9u!V>y2%sYq zdzJsT4U(`4P6(Tv^4KKw!*2*_C1RnKWq?004WJF~-!Ke~ zO87T?jA5v)7NSljg=m1GnB22bi0)YwqC;vSQcLlV5-i18R;9SK7o#|f#i(XkEXTX; zF2`y^^39|n8G=}NB3x-md|=g(Y(QO9Elx}J6DKg;(2{k-FwC-O$#$a^T%eV7Q~JFS zO=)YmL$lMCdnj%BkJ4?+tp}3qzz$m7(h(;P%$TUW>Jn=vVsIWRq*oH37;Tmp9T*Fx$$>uCz zRVw`n5V0~#*`LdJnlJc+|ELo9kKg%($GDK)na_$uWdp>FVH3{db$%z*=vQ9k6xL=W zQLO=DCa@cK^F1NQfBcrH5^{XSjcmneqQU?~jAeHo;8#M*Up&ES9LlbV!#JDg_=k}4 zGk38is}}}fWj5p9oP$5hp)6uNbE!|vWfFB9!K*nTu4heF$PGY+dM?Vj>^D%y6gHxp zOSma<3EgbO6gs#k=XRXHT;jX{BRPaO2`Sezn|eCApU?T5YT|D`=YBe=X9@QbQl4XH zR>=m4spHzr)BX%gsOJRU<6lA^{^dPRpq`G*3H{1h%q2<(Sb-gRo{;#R1DQf6?^7jE zq{{nrrhc0a{U95%TspuQj^#^2;!)TzN zd2hF&o|~u=N~&@b_3X#rgv6CBA{xp7|BxFXqK?}K;TBw6a0}+7-h$zM2#a3B*=#hx zui?iW#_HKYv{TL?Iv_Pn-YYSP-YXbH4`l~Yb`<|HfTOs*XcQ0jVzjtmF@%-VVqS^$?j$=MiLElDgC{M zQo1!`h|1LRv6))_$57Ssyp72LT+Y*c&L8|o$ZSkL<#8@xkMbLnd=*Brgl*{L9IoXS vZsuyv9Lt!>+O8>a|r+jbk&ZQQ7~ZJTjzyNzpG=bQeM%q5e_-hSL|l3(;;pViFs ziu30$MMG+iCN&YMNWSbKCkmlFYM~(-peD+pAacNoXMijyfI8@oSy+W#IHc{u3QR>O z)IdIDi6wwU9@NDsY{3n@Ljux}4uv%Q#VcIKdJIB!7JOfA+#UPx;Zzw|&u3`zsqrWx*D{&hs zP==p4fi5Tj&j5MQ5vN@aehcGK1rCHI8){%O9=U|rix$Y4S%564j}0!DeIIH;gsBNm zx!jIi~tUr;k5PMc7uqM5lDv8wxbqgkO5LM z3m{PsM+3eERgAabe?ElXuVGmqUc;C6YbcNteO(r!Q9dk0=k0~aUWyZfT8jPNjH-BS zM)`u+j!#Cq9ovQEk4qsL=TS(q!mE(%Lkr}JrIID$DwZ0RYyc);g-0bjiLNLJ8Shg1 zH;hu+4MiX$spUnjTAtuP)v)SKWFQj?8Tf;jxP)~Wh^oi|8EOCzh0qSG@fd%gg7gb_u@Ehh zA2Qeg9*SWAPT&iaLk50pnNSYzu@7BP5He5ziNY9&Gx!B%NWm2>#yIrT#$yF;Aq~p# z1IN)B`2q#Vg$_97IQUJBLnU|+mj6%?c`fb8gv z+fdpcjDm>JbfiORXVDt}n*j=87T!T=7f}WxLN)LJN_&n8$d?sBq5?J}2}+v_5e5Ha zJ(Ttvb5PO@PzQ&R0flr7gNTwT)&jN;m9qkPXo6GLd)pNvN`@m9O527Skns$V<_uum zf+KOh1(nRVAfXSTzt^yI3|_+*_G>7R9eo@N(a0DqMCa^<$X<$*;cn{E*QurGJ7b zrCm`7GL%|g*sA48@u}tR!DJM+;5yzQ2^mn|R!a*Fs t4lKh|bVOC;!9TGcPV2>fIF0dm{uhb49NS#Y+}QvC002ovPDHLkV1l1!A;tgz literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/radio_unchecked_focus.png b/UI/data/themes/Acri/radio_unchecked_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..07e648597cfc9c53e076023b820a7fb44dc3ceb6 GIT binary patch literal 660 zcmV;F0&D$=P)9Lt!>+O8>hByTS48%jcVJr8P~SkxVH8AoBor`C7a1)k9$q>iw^s&W}a8H z?!P?}Qgbw^iExGUWd~W252aBJbx{XZQ3`pH1x_pjWI!I&KzGc-D(u1`Z5LKxDmtMG zav?)B0VHyuCPrZkZr~jfkcM<9q~R}K;WE}^5Go@ZWVitw6hM2d!4v$23ej)e!y+_8 zUdT`bI4FXFIEk-N7HRmSr9)YKzsg2{Mf39%PVkQLSd8BiM=ESG&BszHRP0Zv(N z$2?SoV+)WMU=?f_ZQ z2X~;fzZeY>r5Q+t($1j`GP?s5z-+vS(k`JCM3k!GA(Zw46OktnK%yMBAOT972N46A zu>nf^gSjZ~4p0k6kOqZR42Ou3sm21f9u)%t95ljdCkXWk6onXf_k8up5VP u5IeCP)6fZ(kqa689!~4{Kb-nJp8o?X#yt72rtnGt0000zopr0GN;)Z~y=R literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/top_hook.png b/UI/data/themes/Acri/top_hook.png new file mode 100644 index 0000000000000000000000000000000000000000..bfa7887ea85fce07d3c6e89cfae550e3f036f180 GIT binary patch literal 100 zcmeAS@N?(olHy`uVBq!ia0vp^CxDolgAGVtD|dJaq_jO<978G?lP_Gq{@*{;rc2V{ y$N&HT4>R;9{Ysy3k;kaZ-S~utoE&G02s6Xo;Lf!4UHTS4Z492SelF{r5}E*w3LlIB literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/unmute.png b/UI/data/themes/Acri/unmute.png new file mode 100644 index 0000000000000000000000000000000000000000..c50c263fd4e69dc33151a5d8a1d1e94a8f88e434 GIT binary patch literal 289 zcmV++0p9+JP)9L*)`>6r7gQLa|^RFhdevXTXrqZ-s0k{?3QQqfAxH4-&?=B7d_{&zrVjg z#oPDZ4#S;~QHf7f`wGa#wK&heagStA0d07LrQS7Lz%rsRsNJRsXu^RUL*)>#qrMBV zxKLkiS^mOPnSczusZT+x_B|}UlmcW%vnmwn@oFFewVI710YA76HXt#WfH}LjP;B0U zzaL@_KI}fkHtl=OOWTk`4*~n?I}nE(^_7;Tvj)661x#WQkr>l%58*BWl8q}jKjlLF n;1OxT{FzGeiCQxY`S1J>z?^g7k8|*Z00000NkvXXu0mjfWa58k literal 0 HcmV?d00001 diff --git a/UI/data/themes/Acri/up_arrow.png b/UI/data/themes/Acri/up_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..89a5d470791916214b28709585ad0a1cd332bf13 GIT binary patch literal 505 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU_9XI;uuoF`1Vd?nsT5B+k=^R zV;PtdQZt8ZHf4Bb2AK%_Q=i&QV-`@&NJMjHaMZL^-mP-A~V-|Ydo9=zsX(+N! zh$Eh}Wj&fH0!YHALzuinm`+EZlR0GG|KY4^&F9EIOWmV;rX}Wi8|;7h_TYtYi4oO? zOa4lno5zzH-?q`*`Nn*hbC3;ssD04+?|&OriPrTjk9GO)O@CFhGe%5kpHNL&sovFp z)BNlA{M$P(d1YM7{xw1-Gi8-eo86vorx6!^{d3H!@Q3<~S$xAL-p#U_T?zE~e$^SW zb-x$f7gn|4+<#kW&aDr(c~<{awV0Bn{O6t52F&c3-< z&g7WnE{ECfv#;)z`;z`bXkstF8gh^`Fxaf(&z$?2`{P2VCm;z=S3j3^P6<7gR`t)iS1tQ{Vq=8a+!t@=-S1EK zoA&0>|GTrRJ&#*50!;;hV});)U*78Z=hePfQjSf}D`U@z@2r1yOQFuP_MJV?;SbrB zb!jZUN7ldNe>{76(KWY%bwb;A)zANMyC|+_!NeawXZ}xa+TWMRl6xdxy`ZUGup^)4 zk#yq^w+l&s6pwRA_3U5$-t9uzpWJ!-pFS~F&d~frkUCaG&FdITIyJ3!ozu_QBnQojD;tX0C6Ia)j*f#@cz; zpZ+PC^|4ps?Yh^?jvsly`bYh|h#F;&%_eW|TQ}|gc=lb&BWcIGuk!5wZB)L|bJT#R zGWnypUDMqwT4wLN;%hIs+BQ`P)-P1{n0@04Z}K+5KXK=Je{@S8^qnQsXMdO@?`ZrV zj=c1a`VaS6=Ig)ye#xVIi|6VeD+JfpFD z<7KV4*GexR23oY2|6dBrav6Kx!<0rUG+9iyBPwb9IR^AVv+{~}_n)==@a;*^;M=2C z$E~c<_N(Lmv->RnWj@}PZTz9zhnC*FVdQ&MBb@00FSIrT_o{ literal 0 HcmV?d00001 diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index adcfea1..4a9326c 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -67,6 +67,31 @@ QListWidget::item:selected:!active { background-color: rgb(48,47,48); } +/* Dock Widget */ + +QDockWidget::title { + text-align: center; + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(86,85,86), + stop: 0.1 rgb(82,81,82), + stop: 0.5 rgb(78,77,78), + stop: 0.9 rgb(74,73,74), + stop: 1 rgb(70,69,70)); +} + +QDockWidget::close-button, QDockWidget::float-button { + border: 1px solid transparent; + background: transparent; + padding: 0px; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background: transparent; +} + +QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + padding: 1px -1px -1px 1px; +} /* Group Box */ @@ -180,6 +205,10 @@ QToolButton:pressed { qproperty-icon: url(./Dark/cogwheel.png); } +* [themeID="refreshIconSmall"] { + qproperty-icon: url(./Dark/refresh.png); +} + * [themeID="upArrowIconSmall"] { qproperty-icon: url(./Dark/up_arrow.png); } @@ -202,14 +231,24 @@ QTabWidget::tab-bar { QTabBar::tab { background-color: rgb(88,87,88); /* kindaDark */ border: none; + padding: 5px; + min-width: 50px; + margin: 1px; +} + +QTabBar::tab:top { + border-bottom: 1px transparent; border-top-left-radius: 5px; border-top-right-radius: 5px; - min-width: 8ex; - padding-top: 4px; - padding-bottom: 4px; - padding-left: 10px; - padding-right: 10px; - margin-right: 1px; + +} + +QTabBar::tab:bottom { + padding-top: 1px; + margin-bottom: 4px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + height: 14px; } QTabBar::tab:selected { @@ -374,6 +413,12 @@ QPushButton:checked { background-color: rgb(122,121,122); /* light */ } +QPushButton::menu-indicator { + image: url(./Dark/down_arrow.png); + subcontrol-position: right; + subcontrol-origin: padding; + width: 25px; +} /* Sliders */ @@ -464,10 +509,15 @@ QSlider::handle:disabled { /* Volume Control */ VolumeMeter { - qproperty-bkColor: rgb(31,30,31); /* veryDark */ - qproperty-magColor:; - qproperty-peakColor:; - qproperty-peakHoldColor: rgb(225,224,225); /* veryLight */ + qproperty-backgroundNominalColor: rgb(38, 127, 38); + qproperty-backgroundWarningColor: rgb(127, 127, 38); + qproperty-backgroundErrorColor: rgb(127, 38, 38); + qproperty-foregroundNominalColor: rgb(76, 255, 76); + qproperty-foregroundWarningColor: rgb(255, 255, 76); + qproperty-foregroundErrorColor: rgb(255, 76, 76); + qproperty-magnitudeColor: rgb(0, 0, 0); + qproperty-majorTickColor: rgb(225,224,225); /* veryLight */ + qproperty-minorTickColor: rgb(122,121,122); /* light */ } diff --git a/UI/data/themes/Dark/refresh.png b/UI/data/themes/Dark/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..7a6f6e6dc4b8846b6a8c529284b3d0bf7e8541a3 GIT binary patch literal 3275 zcmV;+3^enJP)t1R z{$mv49~i}djO7n7Di8~z!9;~BP(=|!Yl#K|wFaSJp#qiu*mifj?e51v?p(ZFUbi#v z&3*5__vV|t?AzUU@0*$Py=TsxIdhH_5fKp)5fKp)5fKp)3_vH)Y2U{IDIq!vzW+Q) zpOqwIOe8@YYJkNgSfBY@z1z@`YnugKMUTJzxfzf#=00Ogp~BAJKm8q;C~3N+X_6*M znrQoPB%P2nEa`}(gOc7d#vH5E)=J+I32Ma3e4u=#83Td;$O*0ydmP*xC0Jfi=L7fqj%8 zZvuA%>wvztCc-HQ0Pr?&^*D3;HNZCexn{#b;BnyEwzU-;2mmk$tjb-t1b7lSO7mk7 z*bXdhI}$h&AjPFXA8;qFi+ilG_zn>oO$Jjm0VHkrxa2>Q&TR4LVnEU+NxwD53^m%9 z&{49%IkOEE<77z>Ncs^lH+5h&N)xohx^-C6tCIFfdds?ck4PG_F5q+=daa9is&yI9 zl61DDzJl7dLDE9tmefVw2u;w61Q?U_M@dgf`kSQJB^|OtKWYgvVvHHhJD4POTbFU4 zCBtc!2p38ExTK{@_7FnhJV|!~3yd*0HHrjfF#>DB3E)BCN?-;s8R!n!j|spe;Qhem zz!vKk4uvth-0fq@ToU{!EftpFYY4g~1mKY)2HL4v>p7y=#wt|*IVtpX920}lX)YwYWDz=Ajhyc zFE&A+yMa#w4+kK@yr$Ly1xx{N16P%_uX!6`2CxwrsBt=&*|a1m$ObqLtSsxGWQhQD z0CVi7P%VaGbFK~wUIkoQc0(ycgkE4XFj8e-w>70vSO-7As%9a9eSb5sze+#B?M+95 zIuKxRym*3=l3*>cr;5G~G%~!t%$~rvvynw59*gXhumpI%ioWhnH;^I$-s>D%Q}%($ zkzjt6B=}i2<}ecA-GQbnnrV)-nxLBLU~L(KNyy3bV$xmE2ud=MXR#_&zW{YJx%t&}}1&d=8|}uU8|x>@jG7 zmw;P=37L5;N-_)ymV3q+eywn=Yy~_8*pd0-5gWO(I%Z%D!Nf0_H+p@Nx$;A?26z7 z$O!Rrk9*$}MOYg$zWsYWF1y^v4ltgK9G3g>=rfHmZ$vQ$u?40}dQHjk^;6-PF{UT@ z6aZYHB-N@5o{%&c)qgN8Ff8eD$4e$ySp~NN7J7X4Nz242!7wTQDtfT+-iNKW=3eh5#8gKG%@|$0hBJPk(UiQxX8;LICHp104?ROW)3RZ0M^> za`Hrk#bHVRcKqz@oa|~|22NCxJjn&GD@n){5f%eh3%H=i$}E@-Fx_JX@RpKnToGY0 zXvfwCT~=nn2rx}al?@jhQ2nET+X2H$avZu6U}{JLyw8IG19sUG5f&pJQ){OjBa2-L z(Btvh;d~Oth#*VBQ4h`9;cBzF3e_jn=tLA(VelE-u8;(XXi;=Dig^)LA-E=Xp4XKm zVRl`Ec1Br+g&k8@U(A@Rz2HiKBT5P?x?qyrwk;$4Fj2{|a2Jfat{7YiaL{8KnC8LM z2zT_juI;U?4u&Maeh&gnw|;|&u;`OC!-D|(gAu?Ob4>2eomp}}-W=hM$#!g=Y&I*i z+6j=^QrzSC&r%C#t<7fBeWLG@_GFytf>sCLRLkEg^yU3wLgZ2 z1tGy1N{VW_4Cb&grZ=bt$QTo@k*VF17DwR+!zCUM>yxeqpwBWeefzLSqp$I4NW{1g z-se0nd&t!QtfoP8JPs)yt84bE)+NDIn+Mqy=la~=YdhE&^P-18pj*=Vs0C`qm#myz z2Jv6Un3oD6Kt_o7d))u)g{hSzPpU-$zD^m|tKTp49pn+;$Sb6sbPV~mIM z3vZB3M6}Z*Y4f8Mv`9pC<39_y?<7Qcx?V}5x@<}^R~!JAM*`IR;AMaRq)ovylo{u* z)Gc|ZEqC&!2MHcXt9eHf1X&ge=&{ACci6;S7uB;Ny*53mPlb!skpP7d;X}YBz@&ON zVx3J??24^1Ygm0qQLiuTtD+?8Mw=2n4r)o-VXAGAEpV_=(q<(w04_Kp=^|syi=``Y zaPq!RP_hBmC^ifkW5!BFfZPCWm$b;^l0QpYX^a_a1`+@tkaV4-nQ{+}4}~5{w;E&i zl-WtKw2ER!cQlJ{z}7~%*3%y!5*Igvn!LxeQzO95mDac=wzgL{hyaVqEco>26weA5 z0RIKnmR$?vNU*Hc2vF1E&`e>*Ly|UmUVgfy?*U87YzL%mv-Zk1NtZRi7HEnkQ?D&- z?2G5qawlc4IFiwLYiz;l_MQSN49r~5{PtNAENKQ3Wcs|pPGgY(xp{IuFi=HD_oOyK zneBpHU#|v^wmktV49x8wwQlm&Ms99060C{@s5~&+s_gIxa7S7PuSrO7X(YgV?SU>} zR}CHilMO=cD6?&l>F-KQ-u5OyZct|0l5*8zz-}PT$lkleTHz{(u&vf7K#m0Sfahu) zgss*S*WCpDz8)BCdjjMJWkCQE9JY}~%TG2JH_{HsXn^16LbckL069ZxUX9a1HpcK# z;Ic*#q0$rte8vW49*#D^_#~KFqhXkhHa-FT)7I7NE1C!ugnl}36Y!d6bA<9r`3Y<_@5#U!z*^upf!p6O0+@P}AhWr0yQD8Dd_JKOB?XqA zQc`ARpORt=14>e^4@(-!=QFl>__~!uH}%?}6M>}UxBR3O26 zNw-KkufVSyQc`JSpOSq02bF}T4JpZ|hms7Oy;kVclw{+Yr6ea$qZ3`U76B@dV6LQZ zNZO!?x)Y!w$)YoH1Tx0#lJt2=>#Uk?b?hxuV;Yt%JSBnE21Am5p(MrJy)7*KA4>X& zq-9aDjlW!63TzMTQEL>A*nN$Kx&E$l?BAlcB!T^Xqdk0bun7gf-#V<;jYIUS9rg|u zwYxUxv&YbW>UkiwQP>OI30#pqt()sp_Wg!agqJOf(}LcKIh=2grtd7F;GeJ?LH*g9 zeJArw=K8i~oO1{5OMsIMT$c?)?+0!JUT&b!cLU!7J_?-XTPPz(f)$Yf)!PblfE%rg zdDuQpqk-JDW10I6+TFezfZ5}cP- zn-W4+;KSBMd_YNx+Y#UI2-uJHN={u*E1CdpH(k{0+5krH20H~TF7i8DzQkKCPiwu8 ztfk&jV~mV3TkJA&Fh0g@s */ +/* */ +/* */ +/* This program is free software: you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation, either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/* */ +/* ======================================================================= */ + +/* =========================== Color Palette ============================= */ +/* */ +/* rgb(49, 54, 59) - Blue-gray (Main Background) */ +/* rgb(58, 64, 69); - Light Blue-gray */ +/* */ +/* rgb(239, 240, 241) - "White" */ +/* */ +/* rgb(162, 161, 162) - Lighter Gray */ +/* rgb(118, 121, 124) - Light Grey */ +/* rgb(84, 87, 91) - Gray */ +/* rgb(35, 38, 41) - Dark Gray */ +/* */ +/* rgb(0, 188, 212) - Cyan (Primary) */ +/* rgb(98, 238, 255) - Light Cyan (Primary Light - unused) */ +/* rgb(0, 139, 163) - Dark Cyan (Primary Dark) */ +/* */ +/* rgb(240, 98, 146) - Pink (Secondary) */ +/* rgb(255, 148, 194) - Light Pink (Secondary Light) */ +/* rgb(186, 45, 101) - Dark Pink (Secondary Dark) */ +/* */ +/***************************************************************************/ + + +/*************************/ +/* --- General style --- */ +/*************************/ + +QMainWindow, +QDialog, +QWidget { + background-color: rgb(49, 54, 59); /* Blue-gray */ + color: rgb(239, 240, 241); /* White */ + selection-background-color: rgb(0, 188, 212); /* Cyan (Primary) */ + selection-color: rgb(239, 240, 241); /* White */ + outline: none; + font-family: Noto Sans, Tahoma; + font-size: 11px; +} + +QWidget::disabled { + color: 2px solid rgba(255, 148, 194, 0.25); /* Light Pink (Secondary Light) */ +} + +QWidget:item:hover { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ + color: rgb(118, 121, 124); /* Light Gray */ +} + +QComboBox:hover, +QAbstractSpinBox:hover, +QLineEdit:hover, +QTextEdit:hover, +QPlainTextEdit:hover, +QAbstractView:hover, +QTreeView:hover { + border: 1px solid rgb(0, 188, 212); /* Cyan (Primary) */ + color: rgb(239, 240, 241); /* White */ +} + +QSizeGrip { + image: url(./Rachni/sizegrip.png); + width: 12px; + height: 12px; +} + +/***********************/ +/* --- List widget --- */ +/***********************/ + +QListWidget::item:selected:!active { + color: rgb(239, 240, 241); /* White */ + background-color: rgba(255, 148, 194, 0.25); /* Light Pink (Secondary Light) */ + border: none; +} + +QListWidget::item:selected { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + border: none; +} + +QListWidget::item:hover, +QListWidget::item:disabled:hover, +QListWidget::item:hover:!active { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ + color: rgb(239, 240, 241); /* White */ + border: none; +} + +/***********************/ +/* --- Dock widget --- */ +/***********************/ + +QDockWidget { + background: rgb(49, 54, 59); /* Blue-gray */ + border: 1px solid rgb(58, 64, 69); /* Light Blue-gray */ +} + +QDockWidget::title { + text-align: left; + background: rgb(35, 38, 41); /* Dark Gray */ + padding-left: 5px; +} + + +QDockWidget::close-button, QDockWidget::float-button { + border: 1px solid transparent; + border-radius: 2px; + background: transparent; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background: rgba(255, 148, 194, 0.25); /* Light Pink (Secondary Light) */ +} + +QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + padding: 1px -1px -1px 1px; + background: rgba(255, 148, 194, 0.25); /* Light Pink (Secondary Light) */ +} + + +/***********************/ +/* --- Group Boxes --- */ +/***********************/ + +QGroupBox { + font-size: 13px; + border: 1px solid rgb(118, 121, 124); /* Light Gray */ + border-radius: 2px; + padding-top: 16px; + margin-top: 20px; +} + +QGroupBox::title { + color: rgb(240, 98, 146); /* Pink (Secondary) */ + left: 20px; + padding-left: 5px; + padding-right: 4px; + padding-top: -14px; +} + +/*****************/ +/* --- Menus --- */ +/*****************/ + +QMenuBar { + background-color: rgb(49, 54, 59); /* Blue-gray */ + color: rgb(239, 240, 241); /* White */ +} + +QMenuBar::item { + background: transparent; +} + +QMenuBar::item:selected { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + border-radius: 1px; +} + +QMenuBar::item:pressed { + border: none; + background-color: rgb(0, 188, 212); + margin-bottom: -1px; + padding-bottom: 1px; +} + +/**********************/ +/* --- ScrollBars --- */ +/**********************/ + +QScrollBar:horizontal { + height: 15px; + margin: 3px 15px 3px 15px; + border: 1px transparent; + border-radius: 4px; + background-color: rgb(35, 38, 41); /* Dark Gray */ +} + +QScrollBar::handle:horizontal { + background-color: rgb(118, 121, 124); /* Light Gray */ + min-width: 5px; + border-radius: 4px; +} + +QScrollBar::add-line:horizontal { + margin: 0 3px 0 3px; + border-image: url(./Rachni/right_arrow_disabled.png); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + margin: 0 3px 0 3px; + border-image: url(./Rachni/left_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:hover, +QScrollBar::add-line:horizontal:on { + border-image: url(./Rachni/right_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal:hover, +QScrollBar::sub-line:horizontal:on { + border-image: url(./Rachni/left_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:horizontal, +QScrollBar::down-arrow:horizontal { + background: none; +} + +QScrollBar::add-page:horizontal, +QScrollBar::sub-page:horizontal { + background: none; +} + +QScrollBar:vertical { + background-color: rgb(35, 38, 41); /* Dark Gray */ + width: 15px; + margin: 15px 3px 15px 3px; + border: 1px transparent; + border-radius: 4px; +} + +QScrollBar::handle:vertical { + background-color: rgb(118, 121, 124); /* Light Gray */ + min-height: 5px; + border-radius: 4px; +} + +QScrollBar::sub-line:vertical { + margin: 3px 0 3px 0; + border-image: url(./Rachni/up_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::add-line:vertical { + margin: 3px 0 3px 0; + border-image: url(./Rachni/down_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical:hover, +QScrollBar::sub-line:vertical:on { + border-image: url(./Rachni/up_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::add-line:vertical:hover, +QScrollBar::add-line:vertical:on { + border-image: url(./Rachni/down_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:vertical, +QScrollBar::down-arrow:vertical { + background: none; +} + +QScrollBar::add-page:vertical, +QScrollBar::sub-page:vertical { + background: none; +} + +/***********************/ +/* --- Tab Widgets --- */ +/***********************/ + +QTabWidget { + border: none; +} + +QTabWidget::pane { + border: 1px solid rgb(118, 121, 124); /* Light Gray */ + padding: 5px; + margin: 0; +} + +QTabWidget::tab-bar { + left: 5px; +} + +/********************/ +/* --- Tab Bars --- */ +/********************/ + +QTabBar { + border-radius: 3px; +} + +QTabBar:focus { + border: none; +} + +QTabBar::close-button { + image: url(./Rachni/close.png); + background: transparent; +} + +QTabBar::close-button:hover { + image: url(./Rachni/close-hover.png); + background: transparent; +} + +QTabBar::close-button:pressed { + image: url(:./Rachni/close-pressed.png); + background: transparent; +} + +QTabBar::tab { + color: rgb(239, 240, 241); /* White */ + border: 1px solid rgb(118, 121, 124); /* Light Gray */ + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + padding: 5px; + min-width: 50px; +} + +QTabBar::tab:top { + border-bottom: 1px transparent; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:bottom { + margin-bottom: 4px; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + height: 12px; +} + +QTabBar::tab:!selected { + color: rgb(239, 240, 241); /* White */ + background-color: rgb(84, 87, 91); /* Gray */ +} + +QTabBar::tab:!selected:hover { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ +} + +/********************/ +/* --- Toolbars --- */ +/********************/ + +QToolBar { + background-color: rgb(49, 54, 59); /* Blue-gray */ + border: none; +} + +QToolButton:hover { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + border-radius: 2px; +} + +QToolButton:pressed { + background-color: rgb(240, 98, 146); /* Pink (Secondary) */ + border-radius: 2px; +} + +* [themeID="addIconSmall"] { + qproperty-icon: url(./Dark/plus.png); +} + +* [themeID="removeIconSmall"] { + qproperty-icon: url(./Dark/minus.png); +} + +* [themeID="propertiesIconSmall"] { + qproperty-icon: url(./Dark/cogwheel.png); +} + +* [themeID="configIconSmall"] { + qproperty-icon: url(./Dark/cogwheel.png); +} + +* [themeID="refreshIconSmall"] { + qproperty-icon: url(./Dark/refresh.png); +} + +* [themeID="upArrowIconSmall"] { + qproperty-icon: url(./Dark/up_arrow.png); +} + +* [themeID="downArrowIconSmall"] { + qproperty-icon: url(./Dark/down_arrow.png); +} + +/***********************/ +/* --- Combo boxes --- */ +/***********************/ + +QComboBox { + background-color: rgb(35, 38, 41); /* Dark Gray */ + border: 1px solid rgb(118, 121, 124); /* Light Gray */ + border-radius: 2px; + padding: 5px; + min-width: 75px; +} + +QComboBox:on { + padding-top: 3px; + padding-left: 4px; +} + +QComboBox QAbstractItemView { + background-color: rgb(35, 38, 41); /* Dark Gray */ + border-radius: 2px; + border: 1px solid rgb(118, 121, 124); /* Light Gray */ +} + +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + width: 15px; + + border-left-width: 0; + border-left-color: rgb(169, 169, 169); + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QComboBox::down-arrow, +QComboBox::down-arrow:pressed, +QComboBox::down-arrow:focus { + image: url(./Rachni/down_arrow_disabled.png); +} + +QComboBox::down-arrow:on, +QComboBox::down-arrow:hover { + image: url(./Rachni/down_arrow.png); +} + +/*********************/ +/* --- Spinboxes --- */ +/*********************/ + +QAbstractSpinBox { + padding: 5px; + border: 1px solid rgb(118, 121, 124); /* Light Gray */ + background-color: rgb(35, 38, 41); /* Dark Gray */ + color: rgb(239, 240, 241); /* White */ + border-radius: 2px; + min-width: 75px; +} + +QAbstractSpinBox:up-button { + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: top right; +} + +QAbstractSpinBox:down-button { + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: bottom right; +} + +QAbstractSpinBox::up-arrow, +QAbstractSpinBox::up-arrow:disabled, +QAbstractSpinBox::up-arrow:off { + image: url(./Rachni/up_arrow_disabled.png); + width: 10px; + height: 10px; +} + +QAbstractSpinBox::up-arrow:hover { + image: url(./Rachni/up_arrow.png); +} + +QAbstractSpinBox::down-arrow, +QAbstractSpinBox::down-arrow:disabled, +QAbstractSpinBox::down-arrow:off { + image: url(./Rachni/down_arrow_disabled.png); + width: 10px; + height: 10px; +} + +QAbstractSpinBox::down-arrow:hover { + image: url(./Rachni/down_arrow.png); +} + +/**********************/ +/* --- Line edits --- */ +/**********************/ + +QLineEdit { + background-color: rgb(35, 38, 41); /* Dark Gray */ + padding: 5px; + border: 1px solid rgb(118, 121, 124); /* Light Gray */ + border-radius: 2px; + color: rgb(239, 240, 241); /* White */ +} + +QListWidget QLineEdit { + padding-top: 0; + padding-bottom: 0; + padding-right: 0; + padding-left: 2px; + border: none; + border-radius: none; +} + + +/**********************/ +/* --- Checkboxes --- */ +/**********************/ + +QCheckBox { + spacing: 5px; + outline: none; + color: rgb(239, 240, 241); /* White */ + margin-bottom: 2px; +} + +QCheckBox:hover, QCheckBox:focus { + color: rgb(240, 98, 146); /* Pink (Secondary) */ +} + +QCheckBox:disabled { + color: rgb(118, 121, 124); /* Light Gray */ +} + +QCheckBox::indicator, +QGroupBox::indicator { + width: 18px; + height: 18px; +} + +QGroupBox::indicator { + margin-left: 2px; +} + +QCheckBox::indicator:unchecked { + image: url(./Rachni/checkbox_unchecked.png); +} + +QCheckBox::indicator:unchecked:hover, +QGroupBox::indicator:unchecked:hover { + border: none; + image: url(./Rachni/checkbox_unchecked_focus.png); +} + +QCheckBox::indicator:checked { + image: url(./Rachni/checkbox_checked.png); +} + +QCheckBox::indicator:checked:hover, +QGroupBox::indicator:checked:hover { + border: none; + image: url(./Rachni/checkbox_checked_focus.png); +} + +QCheckBox::indicator:checked:disabled, +QGroupBox::indicator:checked:disabled { + image: url(./Rachni/checkbox_checked_disabled.png); +} + +QCheckBox::indicator:unchecked:disabled, +QGroupBox::indicator:unchecked:disabled { + image: url(./Rachni/checkbox_unchecked_disabled.png); +} + +/***********************/ +/* --- Radio boxes --- */ +/***********************/ + +QRadioButton { + spacing: 5px; + outline: none; + color: rgb(239, 240, 241); /* White */ + margin-bottom: 2px; +} + +QRadioButton:disabled { + color: rgb(118, 121, 124); /* Light Gray */ +} + +QRadioButton::indicator { + width: 21px; + height: 21px; +} + +QRadioButton::indicator:unchecked { + image: url(./Rachni/radio_unchecked.png); +} + + +QRadioButton::indicator:unchecked:hover, +QRadioButton::indicator:unchecked:focus, +QRadioButton::indicator:unchecked:pressed { + border: none; + outline: none; + image: url(./Rachni/radio_unchecked_focus.png); +} + +QRadioButton::indicator:checked { + border: none; + outline: none; + image: url(./Rachni/radio_checked.png); +} + +QRadioButton::indicator:checked:hover, +QRadioButton::indicator:checked:focus, +QRadioButton::indicator:checked:pressed { + border: none; + outline: none; + image: url(./Rachni/radio_checked_focus.png); +} + +QRadioButton::indicator:checked:disabled { + outline: none; + image: url(./Rachni/radio_checked_disabled.png); +} + +QRadioButton::indicator:unchecked:disabled { + image: url(./Rachni/radio_unchecked_disabled.png); +} + +/***************************/ +/* --- Mute Checkboxes --- */ +/***************************/ + +MuteCheckBox { + outline: none; +} + +MuteCheckBox::indicator:checked { + image: url(./Dark/mute.png); +} + +MuteCheckBox::indicator:unchecked { + image: url(./Dark/unmute.png); +} + +MuteCheckBox::indicator:unchecked:hover { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + image: url(./Dark/unmute.png); +} + +MuteCheckBox::indicator:unchecked:focus { + image: url(./Dark/unmute.png); +} +MuteCheckBox::indicator:checked:hover { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + image: url(./Dark/mute.png); +} + +MuteCheckBox::indicator:checked:focus { + image: url(./Dark/mute.png); +} + +MuteCheckBox::indicator:checked:disabled { + image: url(./Dark/mute.png); +} + +MuteCheckBox::indicator:unchecked:disabled { + image: url(./Dark/unmute.png); +} + +/*************************/ +/* --- Progress bars --- */ +/*************************/ + +QProgressBar { + border: 2px solid rgb(118, 121, 124); /* Light Gray */ + border-radius: 5px; + text-align: center; +} + +QProgressBar::chunk { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ +} + +/**************************/ +/* --- Volume Control --- */ +/**************************/ + +VolumeMeter { + qproperty-backgroundNominalColor: rgb(0, 128, 79); + qproperty-backgroundWarningColor: rgb(128, 57, 0); + qproperty-backgroundErrorColor: rgb(128, 9, 0); + qproperty-foregroundNominalColor: rgb(119, 255, 143); + qproperty-foregroundWarningColor: rgb(255, 157, 76); + qproperty-foregroundErrorColor: rgb(255, 89, 76); + qproperty-magnitudeColor: rgb(49, 54, 59); /* Blue-gray */ + qproperty-majorTickColor: rgb(239, 240, 241); /* White */ + qproperty-minorTickColor: rgb(118, 121, 124); /* Light Gray */ +} + +/*******************/ +/* --- Buttons --- */ +/*******************/ + +QPushButton { + background-color: rgb(0, 188, 212);; /* Cyan (Primary) */ + color: rgb(239, 240, 241); /* White */ + border-radius: 2px; + border: 1px solid rgb(0, 188, 212); /* Cyan (Primary) */ + padding: 4px; + padding-left: 15px; + padding-right: 15px; +} + +QPushButton:hover { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + border: 1px solid rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ +} + +QPushButton:pressed { + background-color: rgb(240, 98, 146); /* Pink (Secondary) */ + border: 1px solid rgb(240, 98, 146); /* Pink (Secondary) */ +} + +QPushButton:checked:pressed { + background-color: rgb(240, 98, 146); /* Pink (Secondary) */ + border: 1px solid rgb(240, 98, 146); /* Pink (Secondary) */ +} + +QPushButton:checked { + border: 1px solid rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ +} + +QPushButton:checked:hover { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ +} + +QPushButton:disabled { + background-color: rgb(0, 139, 163); /* Dark Cyan (Primary Dark) */ + border: 1px solid rgb(0, 139, 163); /* Dark Cyan (Primary Dark) */ + color: rgb(162, 161, 162); /* Lighter Gray */ +} + +QPushButton::menu-indicator { + image: url(./Rachni/down_arrow.png); + subcontrol-position: right; + subcontrol-origin: padding; + width: 25px; +} + +/******************************/ +/* --- Dialog Box Buttons --- */ +/******************************/ +/* These currently match the */ +/* default button style, but */ +/* I left this section in as */ +/* a reference to themers. */ +/******************************/ + +QDialogButtonBox QPushButton { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ +} + +QDialogButtonBox QPushButton:hover { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + border: 1px solid rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ +} + +QDialogButtonBox QPushButton:pressed { + background-color: rgb(240, 98, 146); /* Pink (Secondary) */ +} + +QDialogButtonBox QPushButton:disabled { + background-color: rgb(0, 139, 163); /* Dark Cyan (Primary Dark) */ + border: 1px solid rgb(0, 139, 163); /* Dark Cyan (Primary Dark) */ + color: rgb(162, 161, 162); /* Lighter Gray */ +} + +/*******************************/ +/* --- OBS Main UI Buttons --- */ +/********************************/ +/* This will style the buttons */ +/* from the main OBS UI apart */ +/* from the rest of the general */ +/* button styles. Currently */ +/* these match, but left in for */ +/* reference to new themers. */ +/********************************/ + +QPushButton#streamButton, +QPushButton#recordButton, +QPushButton[themeID="replayBufferButton"], +QPushButton#modeSwitch, +QPushButton#settingsButton, +QPushButton#exitButton { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ +} + +QPushButton#recordButton { + padding-left: 5px; + padding-right: 5px; +} + +QPushButton:hover#streamButton, +QPushButton:hover#recordButton, +QPushButton:hover[themeID="replayBufferButton"], +QPushButton:hover#modeSwitch, +QPushButton:hover#settingsButton, +QPushButton:hover#exitButton { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + border: 1px solid rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ +} + +QPushButton:pressed#streamButton, +QPushButton:pressed#recordButton, +QPushButton:pressed[themeID="replayBufferButton"], +QPushButton:pressed#modeSwitch, +QPushButton:pressed#settingsButton, +QPushButton:pressed#exitButton { + background-color: rgb(240, 98, 146); /* Pink (Secondary) */ + border: 1px solid rgb(240, 98, 146); /* Pink (Secondary) */ +} + +QPushButton:checked#streamButton, +QPushButton:checked#recordButton, +QPushButton:checked[themeID="replayBufferButton"], +QPushButton:checked#modeSwitch, +QPushButton:checked#settingsButton, +QPushButton:checked#exitButton { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + border: 1px solid rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ +} + +/**************************/ +/* --- Icon Buttons --- */ +/**************************/ +/* This fixes the issues */ +/* with the icon buttons */ +/* sharing the same style */ +/* as other buttons. */ +/**************************/ + +QPushButton[themeID="addIconSmall"], +QPushButton[themeID="removeIconSmall"], +QPushButton[themeID="configIconSmall"], +QPushButton#transitionRemove, +QPushButton#moveAsyncFilterUp, +QPushButton#moveAsyncFilterDown, +QPushButton#moveEffectFilterDown, +QPushButton#moveEffectFilterUp { + background-color: rgb(49, 54, 59); /* Blue-gray */ + border: none; +} + + +QPushButton:hover[themeID="addIconSmall"], +QPushButton:hover[themeID="removeIconSmall"], +QPushButton:hover[themeID="configIconSmall"], +QPushButton:hover#transitionRemove, +QPushButton:hover#moveAsyncFilterUp, +QPushButton:hover#moveAsyncFilterDown, +QPushButton:hover#moveEffectFilterDown, +QPushButton:hover#moveEffectFilterUp { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + margin: 0; + padding: 0; + border-radius: 2px; + border: none; + outline: none; +} + +/******************************/ +/* --- Hotkey Buttons --- */ +/******************************/ +/* Fix for the hotkey buttons */ +/* looking terrible with my */ +/* color choices. */ +/******************************/ + +QPushButton[themeID="hotkeyButtons"] { + background-color: rgb(58, 64, 69); /* Light Blue-gray */ + color: rgb(239, 240, 241); /* White */ + border-radius: 2px; + border: none; + margin: 4px; + padding-top: 6px; + padding-bottom: 6px; +} + +QPushButton:hover[themeID="hotkeyButtons"] { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ + border: 1px solid rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ +} + +QPushButton:pressed[themeID="hotkeyButtons"] { + background-color: rgb(240, 98, 146); /* Pink (Secondary) */ + border: 1px solid rgb(240, 98, 146); /* Pink (Secondary) */ +} + +QPushButton:disabled[themeID="hotkeyButtons"] { + background-color: rgb(58, 64, 69); /* Light Blue-gray */ + color: rgb(162, 161, 162); /* Lighter Gray */ +} + +/******************/ +/* --- Labels --- */ +/******************/ + +/* Titles for main UI */ +QLabel#scenesLabel, +QLabel#sourcesLabel, +QLabel#mixerLabel, +QLabel#sceneTransitionsLabel { + color: rgb(240, 98, 146); /* Pink (Secondary) */ + margin-top: 5px; +} + +/* warning and error */ +QLabel#warningLabel { + color: rgb(255, 148, 194); /* Light Pink (Secondary Light) */ + font-weight: bold; +} + +QLabel#errorLabel { + color: rgb(186, 45, 101); /* Dark Pink (Secondary Dark) */ + font-weight: bold; +} + +/****************************/ +/* --- Splitter --- */ +/****************************/ +/* This styles the splitter */ +/* object to show a dashed */ +/* line, indicating that it */ +/* is present and can be */ +/* adjusted. */ +/****************************/ + +QSplitter::handle { + border: 1px dashed rgb(118, 121, 124); /* Light Gray */ +} + +QSplitter::handle:hover { + background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */ +} + +QSplitter::handle:horizontal { + width: 1px; +} + +QSplitter::handle:vertical { + height: 1px; +} + +/*******************************/ +/* --- Sliders --- */ +/*******************************/ +/* Not really happy with */ +/* these, but not sure what */ +/* else to do. All colors not */ +/* in the palette for now */ +/*******************************/ + +QSlider::groove:horizontal { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(35, 38, 41), /* Dark Gray */ + stop: 0.75 rgb(50, 49, 50)); + height: 4px; + border: none; + border-radius: 2px; +} + +QSlider::handle:horizontal { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(240, 239, 240), + stop: 0.25 rgb(200, 199, 200), + stop: 1 rgb(162, 161, 162)); + border: 1px solid rgb(58, 57, 58); + border-radius: 3px; + height: 10px; + width: 18px; + margin: -3px 0; +} + +QSlider::handle:horizontal:pressed { + background-color: QLinearGradient(x1: 0, y1: 1, x2: 0, y2: 0, + stop: 0 rgb(240, 239, 240), + stop: 0.25 rgb(200, 199, 200), + stop: 1 rgb(162, 161, 162)); +} + +QSlider::sub-page:horizontal:disabled { + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(35, 38, 41), /* Dark Gray */ + stop: 0.75 rgb(50, 49, 50)); + border-radius: 2px; +} + +QSlider::handle:hover { + background-color: rgb(200, 199, 200); +} + +QSlider::sub-page { + background-color: rgb(0, 188, 212); /* Cyan (Primary) */ + border-radius: 2px; +} + +QSlider::handle:disabled { + background-color: rgb(122, 121, 122); +} + +/****************/ +/* --- Misc --- */ +/****************/ + +/* Highlight linked hotkeys */ +OBSHotkeyLabel[hotkeyPairHover=true] { + color: rgb(240, 98, 146); /* Pink (Secondary) */ +} + +/* Workaround so frame borders in dark themes don't look like poop */ +* [frameShape="1"], +* [frameShape="2"], +* [frameShape="3"], +* [frameShape="4"], +* [frameShape="5"], +* [frameShape="6"] { + border: 1px solid rgb(118, 121, 124); /* Light Gray */ + background-color: rgb(35, 38, 41); /* Dark Gray */ +} + +QFrame[frameShape="0"] { + border-radius: 2px; + border: 1px transparent; +} + + +/* Misc style tweaks for dark themes */ +QStatusBar::item { + border: none; +} + +QAbstractItemView { + background-color: rgb(35, 38, 41); /* Dark Gray */ +} + +QToolTip { + border: 1px solid rgb(118, 121, 124); /* Light Gray */ + background-color: rgb(49, 54, 59); /* Blue-gray */ + color: rgb(240, 98, 146); /* Pink (Secondary) */ +} diff --git a/UI/data/themes/Rachni/checkbox_checked.png b/UI/data/themes/Rachni/checkbox_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..c2aea12bf023a0ae75ab265bae5402b339a884b9 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@&H$ef*Z=?j|NH-+Vb7JGwC4su zL8g)*zhDN2vdL#HfIM?g7srqa#^eJmJ*y8KXl&#?G~>X5tqdAGN;{<-c+?b_7m2ho s23bc)%+PbOkb5V)gFP|sn2-d+vrm5NF)JFifkrTRy85}Sb4q9e0Kc{@k^lez literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/checkbox_checked_disabled.png b/UI/data/themes/Rachni/checkbox_checked_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..85a8899ca6ceecea49d86bb37667eada6797bf1d GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJ=bkQ(Ar-gYPWR<&a^Pt_9{r=( zG{<`Lhmf`-#RBD8r)pUu6SH5n^&B}e^T8thO7CC$5)~>-mrWFtySQ1p?6{oJ&dDq%7Ft2ahm3)SVB9%Y7$XitwYy39PslsSo^H$dp(rPLsf#Iurn z)ps3Zh$?$)hI6h7^x$*BSQzV`NKUfT9oE88(Yg+NmA+`k2y@*;9gY zk3_OCNNX;=*nH~Jg70M$zs=IzGmWS3R$Y{jSL=(fp`n^X5tqdAGN;{<-c+?b_7m2ho s23bc)%+PbOkb5V)gFP|sn2-d+vrm5NF)JFifkrTRy85}Sb4q9e0Kc{@k^lez literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/checkbox_unchecked.png b/UI/data/themes/Rachni/checkbox_unchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..f1eac9dc285a0f9a6bc694334d82488343329bce GIT binary patch literal 288 zcmV+*0pI?KP)WcUpk-!CH*bvM9p3wyCEDlA zH^=u-wyS3j3^P6p-G literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/checkbox_unchecked_focus.png b/UI/data/themes/Rachni/checkbox_unchecked_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..4baf3de89f98f9293ee53369db28bd609b987cf3 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@&H$ef*Z=?j|NH-+Vb7JGwC4su zL8g)*zhDN2vdL#HfILG_7srqa#^eJmJ*y8KXl&#?G~>X5tqdAGN;{<-c+?b_7nN`r m3iP#Cv%hnkF?}No8^hl&<>g9h;wONb89ZJ6T-G@yGywq1aVrD> literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/down_arrow.png b/UI/data/themes/Rachni/down_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..7c6f0088775593eb5f98d48c3844c9f788d07546 GIT binary patch literal 84 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&q@+Dv978G?lNF@?{Qv*oM$ace i(NUN)>Z}7F1H)~1n}ckgE98O77(8A5T-G@yGywqY`xLkU literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/down_arrow_disabled.png b/UI/data/themes/Rachni/down_arrow_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..6801ed969b4fa8b7a64af78e8606cb267925056e GIT binary patch literal 84 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&q@+Dv978G?lNF@S%rt(^tmhM; h=qSt?b=HB8fk87~$I&ua`X^8sgQu&X%Q~loCIF!I5@i4Y literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/left_arrow.png b/UI/data/themes/Rachni/left_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..afcdc682841a46dd9c58d15fcca5bbc012024818 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJ(0U`xw#m)s%ik>cxAr*|t3Q~Xm|Nn0j<|uGX lpe2#VF{w1c>rt7g9Z*HO&B5z2g>pc}44$rjF6*2UngDc57eW94 literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/left_arrow_disabled.png b/UI/data/themes/Rachni/left_arrow_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e1518c075125ded18c35243bf4540c63c5c119f3 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJ(0U`xw#m)s%ik>cxAr*|t3Q}ig8b4x|KQk+CgUFhpr0x`93WBAf+ZV?k?-a$M^veg{PK42UcB zMR;Y#3V&bOOREp_r5{A_zr^p~r`zq-TA$&4|3#6vu~_U-Xaz)gk(7GkaONq_K#A~f zhvh_%EiVXy<3x0i`J;?ofI~3C@4HyD`w$wOO=x||JK@;ntE|Ld%S-s?&d6O_7B zJx4Un3M74!ZxQ8H^N*Mpdk|hkPSl(W7z^$S5N#owYcAS$@W58hx#zgcO95nZSOwVq zFPOLC6M`j1;1dE;YPiObuf8EjGp55PA!$9!ytuOh7%aF9?bVhc>b!?C9JghdQ0km0 zbj_;~VX!d%*ES+3QQLe(fc+ll+CPE4<#PET{|_RR-@ku9>eq^mxhxkytyK&zMx5<9 z#&?|Pc5TOF-b-F95z&RP!|1J8`?Qz+upGkW%wrP|X`UD`QHX}+ddAZ$TLSAPfDaJgS6@YK_dt|$7!bKEOL>0b%yxFNb Rl+^$L002ovPDHLkV1j4i4GRDO literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/radio_checked_disabled.png b/UI/data/themes/Rachni/radio_checked_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..241595a491215e234eb96c91e6a941df31281b1e GIT binary patch literal 576 zcmV-G0>AxPZuQ{f3 zlh)dOoxJs0Z|!p)jvIdW-fyk97K*nyYkzR96?u}$WY#1mpvrT=?Kbw@;U1DmY<9i* ztuS`w%9V{6y9#%7;k`s)-GAKv3g^N)bxi@|d*TDmm?_{}7(0FZ_-2?m_&UdT$v27i zaZL${U%*6EU^3cyXz)32N9DMAp5wK^Ig$tpPl;?-D;#W_08K_*vE8booH7-na?wtOa2#HwSdRoW#S_Dcvchv;BHa^ zxWs*o&BhQIr}<)BfVKZh&UqW_g3rJ}2ZC_Oi@c8s!b6%u+d>*L#hRrdEYK6u&^fn5 z2_1+nM3b!TrGjB71poX*Os}zDz%dcFb=m zyTrK?(?0-Mn0Bp7B5WwUZ7GQ+vRajM&!bgIg)J3_Jysw8WO=Q(mOfXr=c&A;9VZud zH8vLY`vB7mxU1mzpw5jaFnjaBWMgq6pP*<2m{*Lj-ZR?U4Y*WNNu>w}QSR>#!Bi&fQ_0$Q&fW0w@*%<4jndzU(cX;G-|q49j?&;o#@Qvp*GR|N zkkjC)+2c6H*jC8d+3D^<#@JNH**eA7BEi@|#@NK-=%d);LB`pS)8IYD*kQ}tD8ko= z(cXvA-YLS^I>pyV#@TVr+&#tEWz5?{$JxW;=-KJ+^Yimj$=dJn@~GM4l+@sa(cU1! z*s9v&e9zr?&fQbV+CIkEV$0h+#n={HJy-w$08VsLPE!B?23M!q^w_3WRNnv6l6H~Y zQUi^*FEF@@0{wJ#a|dq8_Q-8e-L@Rjy4(KIF;&oP)1>ovc=6iDlhvZnG8+NB1yO5x z{!WOAxzwfCF(vS500039Nklx!ex%cwYdYUjjk?779xw`UL#3IPWE55iPJu5>n|* zHk-?*z$FD0=!zvMJ0vC&lS&z?HJt+V5+voQ%irn^9ZhK|z|e-gyw`zlPXR_ZWMcBa zSPqF`69v*lr~;00000NkvXXu0mjfUw~t6 literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/radio_unchecked.png b/UI/data/themes/Rachni/radio_unchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..9be66181bc68a172c951b908de234773c7825878 GIT binary patch literal 444 zcmV;t0YmRgIJ5>;ZRK1D3*Y?l~P|Q!`{!&!wmA%9k`zL=iASz+F>m6+%@(T4q0cwoB<~k mD?a$1dQY^c1zZtD6!8oafmQ{m2^a$a0000f9Xe{T{RY=KtNOYGamO z7pJO|6b=tJ(=@I4>MRsjCaPOJtImxT3WcI*1HAHApf`Z0k32LYI_%!_o={$&ns-&L zw-=nqEwEvBKx7BFHWenTogVm{o2cEnDj$WGU}m-&j5vty9+6`eS+*F!KA(*>fbGgT zVX^#))f{VpIyXRBCIf)}4BB`Qavm%OTrqqZ)WEzo1*^bu1_qqMkwr@pw)z&TG%HV% zYRce1vksyg&UT~z|8QcDsqE_NDz&yy13j>G1Ner3HyBYH+eTwtECELRr*h9T@-LRS zQXy;ayEHnf-2Ihf&4?dsxe^&uv4=+qEmc+RgC^`q8 lJkB}qS?!?(ms3tTeE?$s>d~{HO9%h}002ovPDHLkV1jIwzqtSa literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/radio_unchecked_focus.png b/UI/data/themes/Rachni/radio_unchecked_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..60263320b260ae7b3896490505a99b019b0ef09f GIT binary patch literal 538 zcmV+#0_FXQP)-JRCr9l_W?#n(H;*B-*y zz2NAW)!-<>*CoK#PR7>?zu0!o+jh;|3%}SO!q{BN+9SZ%P{-N3-ski4^Et%U?(*`R z*5P!_+X1}Sp4Q@M%-movc=6hk)uJ*1yaiEt{!WOAxzwfC zJQaHG0002ZNklQM` zjl~lNpLu|Nm1HWN&Lkz9^8xvS6ifK3T#;(c0}Ay9{%Y2%8dz!JskF2im`)c|uWty1 zq{Lt#LjvIuRO1PONJfKHjKH+1!EDMPt-*XjV42cj#aqP38mu=25>f`W6-&TmD>S~_ zFAoH86uQUhe9@m(t^bSNseXk9wVT`rKl|^`SMPTN?Y@jx>GbL$%M0KGV1|x6ow^fy c0D%DX4)Ko<*}{|IIsgCw07*qoM6N<$g4@L&2><{9 literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/right_arrow.png b/UI/data/themes/Rachni/right_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..718eb8a234b795899adf6177e81245b03b04a7c1 GIT binary patch literal 89 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJ(0U`xw#m)s%3Z5>GAr*|t2e|+J|6eb+oP{HK mYC|FqXNu1rzM6GPJPd(mHU~GX{*wY!%i!ti=d#Wzp$PzJco)V1 literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/right_arrow_disabled.png b/UI/data/themes/Rachni/right_arrow_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..c5506554f796fe8ecdbeafad4e89facd82243875 GIT binary patch literal 89 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJ(0U`xw#m)s%3Z5>GAr*|t2e_Y|nfaM?Ld^PKoco=To({WUA6BPxjW$<+Mb6Mw<&;$TSZ584G literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/sizegrip.png b/UI/data/themes/Rachni/sizegrip.png new file mode 100644 index 0000000000000000000000000000000000000000..319f41365a36da0e974bcd704da5880f32b8978c GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|Y$ZW{!9W@a@|Lkr1X2o~E{-7; wjL8a|YofR3y;$Y8p?zHzOErkWJ~2yzf#FnY;KYzopr0GN;)Z~y=R literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/up_arrow.png b/UI/data/themes/Rachni/up_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..21583a6e9b00a0aeef2fc22f216546001c11eb13 GIT binary patch literal 86 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&q+~r^978G?lNGrC{Qv*o#*x*) k$AOVaIH)1OLx+Lk!6%!8C%>LE1uA6lboFyt=akR{04V|%_W%F@ literal 0 HcmV?d00001 diff --git a/UI/data/themes/Rachni/up_arrow_disabled.png b/UI/data/themes/Rachni/up_arrow_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..0519c6f61ab10f7a5e4486eabe3b135ac7ca6f7a GIT binary patch literal 86 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!3HD+k8e2&q+~r^978G?lNGqn%rt(^?8s{1 j 0 0 - 1110 - 724 + 1079 + 730 @@ -34,238 +34,40 @@ - + - - - Qt::Vertical - - - false - - - + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + Basic.Main.PreviewDisabled + + + Qt::AlignCenter + + + + + + + 2 + - + - 2 + 4 - - - 4 - - - - - - 0 - 0 - - - - - 32 - 32 - - - - Qt::ClickFocus - - - Qt::CustomContextMenu - - - - - - - - - - - - 0 - 0 - - - - Qt::CustomContextMenu - - - Basic.Main.PreviewDisabled - - - Qt::AlignCenter - - - - - - - - - - - 2 - - - - - true - - - - 0 - 0 - - - - Basic.Main.StartStreaming - - - false - - - - - - - true - - - - 0 - 0 - - - - - 130 - 0 - - - - Basic.Main.StartRecording - - - - - - - Basic.TogglePreviewProgramMode - - - true - - - - - - - - 0 - 0 - - - - Settings - - - - - - - - 0 - 0 - - - - Exit - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - - - 220 - 0 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 215 - 16 - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - 0 - - - + 0 @@ -274,537 +76,24 @@ - 160 - 0 + 32 + 32 - - - 260 - 16777215 - + + Qt::ClickFocus - - QFrame::StyledPanel + + Qt::CustomContextMenu - - QFrame::Sunken - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Qt::CustomContextMenu - - - QFrame::NoFrame - - - QFrame::Plain - - - true - - - true - - - QAbstractItemView::InternalMove - - - Qt::TargetMoveAction - - - - - - - - - 16 - 16 - - - - false - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 150 - 0 - - - - - + - - - - Basic.Main.Sources - - - - - - - 2 - - - - - - 0 - 0 - - - - Mixer - - - - - - - - 0 - 0 - - - - - 22 - 22 - - - - Basic.AdvAudio - - - - - - - :/res/images/configuration21_16.png:/res/images/configuration21_16.png - - - true - - - configIconSmall - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - - - 0 - - - - - - 0 - 0 - - - - - 160 - 0 - - - - - 260 - 16777215 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Qt::CustomContextMenu - - - QFrame::NoFrame - - - true - - - true - - - QAbstractItemView::InternalMove - - - Qt::TargetMoveAction - - - QAbstractItemView::ExtendedSelection - - - - - - - - - 16 - 16 - - - - false - - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 150 - 0 - - - - - - - - - - - - - Basic.Main.Scenes - - - - - - - Basic.SceneTransitions - - - - - - - - 4 - - - 1 - - - 1 - - - 1 - - - 2 - - - - - - 120 - 0 - - - - Transition - - - - - - - 4 - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 22 - 22 - - - - Basic.AddTransition - - - Basic.AddTransition - - - - - - - :/res/images/add.png:/res/images/add.png - - - true - - - addIconSmall - - - - - - - - 0 - 0 - - - - - 22 - 22 - - - - Basic.RemoveTransition - - - Basic.RemoveTransition - - - - - - - :/res/images/list_remove.png:/res/images/list_remove.png - - - true - - - removeIconSmall - - - - - - - - 0 - 0 - - - - - 22 - 22 - - - - Basic.TransitionProperties - - - Basic.TransitionProperties - - - - - - - :/res/images/configuration21_16.png:/res/images/configuration21_16.png - - - true - - - configIconSmall - - - - - - - - - 4 - - - - - - 0 - 0 - - - - Basic.TransitionDuration - - - transitionDuration - - - - - - - Basic.TransitionDuration - - - ms - - - 2 - - - 10000 - - - 50 - - - 300 - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - + + @@ -813,7 +102,7 @@ 0 0 - 1110 + 1079 21 @@ -845,6 +134,7 @@ + @@ -892,7 +182,7 @@ - + Copy @@ -978,8 +268,31 @@ + + + Basic.MainMenu.View.Docks + + + + + + + + + + + + + Basic.MainMenu.View.Fullscreen.Interface + + + F11 + + + + + - @@ -1000,6 +313,729 @@ + + + QDockWidget::AllDockWidgetFeatures + + + Basic.Main.Scenes + + + 8 + + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + + 160 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + true + + + QAbstractItemView::InternalMove + + + Qt::TargetMoveAction + + + + + + + + + 16 + 16 + + + + false + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 150 + 0 + + + + + + + + + + + + + QDockWidget::AllDockWidgetFeatures + + + Basic.Main.Sources + + + 8 + + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + + 160 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + QFrame::NoFrame + + + true + + + true + + + QAbstractItemView::InternalMove + + + Qt::TargetMoveAction + + + QAbstractItemView::ExtendedSelection + + + + + + + + + 16 + 16 + + + + false + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 150 + 0 + + + + + + + + + + + + + QDockWidget::AllDockWidgetFeatures + + + Mixer + + + 8 + + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 220 + 0 + + + + Qt::CustomContextMenu + + + QFrame::StyledPanel + + + QFrame::Sunken + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 230 + 16 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + QDockWidget::AllDockWidgetFeatures + + + Basic.SceneTransitions + + + 8 + + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 4 + + + 1 + + + 1 + + + 1 + + + 2 + + + + + + 120 + 0 + + + + Transition + + + + + + + 4 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 22 + 22 + + + + Basic.AddTransition + + + Basic.AddTransition + + + + + + + :/res/images/add.png:/res/images/add.png + + + true + + + addIconSmall + + + + + + + + 0 + 0 + + + + + 22 + 22 + + + + Basic.RemoveTransition + + + Basic.RemoveTransition + + + + + + + :/res/images/list_remove.png:/res/images/list_remove.png + + + true + + + removeIconSmall + + + + + + + + 0 + 0 + + + + + 22 + 22 + + + + Basic.TransitionProperties + + + Basic.TransitionProperties + + + + + + + :/res/images/configuration21_16.png:/res/images/configuration21_16.png + + + true + + + configIconSmall + + + + + + + + + 4 + + + + + + 0 + 0 + + + + Basic.TransitionDuration + + + transitionDuration + + + + + + + Basic.TransitionDuration + + + ms + + + 2 + + + 10000 + + + 50 + + + 300 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + QDockWidget::AllDockWidgetFeatures + + + Basic.Main.Controls + + + 8 + + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + true + + + + 0 + 0 + + + + Basic.Main.StartStreaming + + + true + + + + + + + true + + + + 0 + 0 + + + + + 130 + 0 + + + + Basic.Main.StartRecording + + + true + + + + + + + Basic.TogglePreviewProgramMode + + + true + + + + + + + + 0 + 0 + + + + Settings + + + + + + + + 0 + 0 + + + + Exit + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + @@ -1404,17 +1440,6 @@ Basic.MainMenu.View.Toolbars.Listboxes - - - true - - - true - - - Basic.MainMenu.View.SceneTransitions - - true @@ -1478,6 +1503,82 @@ Basic.Stats + + + Basic.MainMenu.View.Docks.ResetUI + + + + + true + + + true + + + Basic.MainMenu.View.Docks.LockUI + + + + + true + + + true + + + Basic.Main.Scenes + + + + + true + + + true + + + Basic.Main.Sources + + + + + true + + + true + + + Mixer + + + + + true + + + true + + + Basic.SceneTransitions + + + + + true + + + true + + + Basic.Main.Controls + + + + + Basic.MainMenu.Help.HelpPortal + + @@ -1530,12 +1631,12 @@ close() - 838 - 272 + 976 + 601 - 902 - -10 + 862 + -11 diff --git a/UI/forms/OBSBasicFilters.ui b/UI/forms/OBSBasicFilters.ui index c53fe67..b25d5d3 100644 --- a/UI/forms/OBSBasicFilters.ui +++ b/UI/forms/OBSBasicFilters.ui @@ -412,12 +412,9 @@ - - - Close - - - false + + + QDialogButtonBox::Reset|QDialogButtonBox::Close @@ -446,21 +443,5 @@ - - close - clicked() - OBSBasicFilters - accept() - - - 811 - 701 - - - 722 - 727 - - - diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index 191f0bb..c89cc1e 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -11,11 +11,17 @@ - + 0 0 + + + 700 + 512 + + Settings @@ -139,8 +145,8 @@ 0 0 - 818 - 697 + 801 + 741 @@ -534,6 +540,64 @@ + + + + Basic.TogglePreviewProgramMode + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 2 + + + + + Basic.Settings.General.SwitchOnDoubleClick + + + + + + + Qt::Horizontal + + + + 170 + 5 + + + + + + + + Basic.Settings.General.StudioPortraitLayout + + + + + + + + + + Basic.Settings.General.MultiviewLayout + + + multiviewLayout + + + + + + @@ -659,80 +723,21 @@ 0 - - - - - 0 - 0 - + + + + true - - - QFormLayout::AllNonFixedFieldsGrow + + + + 0 + 0 + 818 + 697 + - - - - - 170 - 0 - - - - Basic.Settings.Output.Mode - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - outputMode - - - - - - - true - - - - 0 - 0 - - - - 0 - - - - Basic.Settings.Output.Mode.Simple - - - - - Basic.Settings.Output.Mode.Adv - - - - - - - - - - - Qt::Horizontal - - - - - - - 0 - - - + 0 @@ -743,28 +748,22 @@ 0 - 0 + 9 - - + + - + 0 0 - - Basic.Settings.Output.Adv.Streaming - - + QFormLayout::AllNonFixedFieldsGrow - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + 170 @@ -772,496 +771,526 @@ - Basic.Settings.Output.VideoBitrate + Basic.Settings.Output.Mode Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - simpleOutputVBitrate + outputMode - - - 200 - - - 1000000 - - - 2000 - - - - - - - Basic.Settings.Output.AudioBitrate - - - simpleOutputABitrate - - - - - - - 8 - - - - 32 - - - - - 48 - - - - - 64 - - - - - 80 - - - - - 96 - - - - - 112 - - - - - 128 - - - - - 160 - - - - - 192 - - - - - 256 - - - - - 320 - - - - - - - - Basic.Settings.Output.Advanced - - - true - - - - - - - - + true - - Basic.Settings.Output.EncoderPreset + + + 0 + 0 + - - simpleOutPreset - - - - - - - Basic.Settings.Output.CustomEncoderSettings - - - simpleOutCustom - - - - - - - - - - Basic.Settings.Output.EnforceBitrate - - - - - - - - - - Basic.Settings.Output.Encoder - - - simpleOutRecEncoder + + 0 + + + Basic.Settings.Output.Mode.Simple + + + + + Basic.Settings.Output.Mode.Adv + + - - - - 0 - 0 - - - - Basic.Settings.Output.Adv.Recording - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 170 - 0 - - - - Basic.Settings.Output.Simple.SavePath - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - simpleOutputPath - - - - - - - - - true - - - - - - - true - - - Browse - - - - - - - - - Basic.Settings.Output.NoSpaceFileName - - - true - - - - - - - Basic.Settings.Output.Simple.RecordingQuality - - - simpleOutRecQuality - - - - - - - - - - Basic.Settings.Output.Format - - - simpleOutRecFormat - - - - - - - - flv - - - - - mp4 - - - - - mov - - - - - mkv - - - - - ts - - - - - m3u8 - - - - - - - - Basic.Settings.Output.Encoder - - - simpleOutRecEncoder - - - - - - - - - - Basic.Settings.Output.CustomMuxerSettings - - - simpleOutMuxCustom - - - - - - - - - - Basic.Settings.Output.UseReplayBuffer - - - true - - - - - - - - - - ReplayBuffer - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - Basic.Settings.Output.ReplayBuffer.SecondsMax - - - - - - - sec - - - 5 - - - 21600 - - - 15 - - - - - - - Basic.Settings.Output.ReplayBuffer.MegabytesMax - - - - - - - MB - - - 20 - - - 8192 - - - 512 - - - - - - - Basic.Settings.Output.ReplayBuffer.HotkeyMessage - - - - - - - - - - - - - - - - - 10 - - - 10 - - - 10 - - - 10 - - - - - + - Qt::Vertical + Qt::Horizontal - - - 20 - 40 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - + 0 - - true - - - - Basic.Settings.Output.Adv.Streaming - - + + - 9 + 0 0 - 9 + 0 - 9 + 0 - - - - - 0 + + + + + 0 + 0 + + + + Basic.Settings.Output.Adv.Streaming + + + + QFormLayout::AllNonFixedFieldsGrow + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 0 + + + + Basic.Settings.Output.VideoBitrate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + simpleOutputVBitrate + + + + + + + 200 + + + 1000000 + + + 2000 + + + + + + + Basic.Settings.Output.AudioBitrate + + + simpleOutputABitrate + + + + + + + 8 + + + + 32 + + + + + 48 + + + + + 64 + + + + + 80 + + + + + 96 + + + + + 112 + + + + + 128 + + + + + 160 + + + + + 192 + + + + + 256 + + + + + 320 + + + + + + + + Basic.Settings.Output.Advanced + + + true + + + + + + + + + + true + + + Basic.Settings.Output.EncoderPreset + + + simpleOutPreset + + + + + + + Basic.Settings.Output.CustomEncoderSettings + + + simpleOutCustom + + + + + + + + + + Basic.Settings.Output.EnforceBitrate + + + + + + + + + + Basic.Settings.Output.Encoder + + + simpleOutRecEncoder + + + + + + + + + + + 0 + 0 + + + + Basic.Settings.Output.Adv.Recording + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 0 + + + + Basic.Settings.Output.Simple.SavePath + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + simpleOutputPath + + + + + + + + + true + + + + + + + true + + + Browse + + + + + + + + + Basic.Settings.Output.NoSpaceFileName + + + true + + + + + + + Basic.Settings.Output.Simple.RecordingQuality + + + simpleOutRecQuality + + + + + + + + + + Basic.Settings.Output.Format + + + simpleOutRecFormat + + + + + + + + flv + + + + + mp4 + + + + + mov + + + + + mkv + + + + + ts + + + + + m3u8 + + + + + + + + Basic.Settings.Output.Encoder + + + simpleOutRecEncoder + + + + + + + + + + Basic.Settings.Output.CustomMuxerSettings + + + simpleOutMuxCustom + + + + + + + + + + Basic.Settings.Output.UseReplayBuffer + + + true + + + + + + + + + + ReplayBuffer + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + Basic.Settings.Output.ReplayBuffer.SecondsMax + + + + + + + sec + + + 5 + + + 21600 + + + 15 + + + + + + + Basic.Settings.Output.ReplayBuffer.MegabytesMax + + + + + + + MB + + + 20 + + + 8192 + + + 512 + + + + + + + Basic.Settings.Output.ReplayBuffer.HotkeyMessage + + + + + + + + + + + + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + 0 @@ -1274,260 +1303,56 @@ 0 - - - - - 0 - 0 - - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 170 - 0 - - - - Basic.Settings.Output.Adv.AudioTrack - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - true - - - - - - - 2 - - - - - - - 3 - - - - - - - 4 - - - - - - - 5 - - - - - - - 6 - - - - - - - - - - Basic.Settings.Output.Encoder - - - advOutEncoder - - - - - - - - - - Basic.Settings.Output.Adv.ApplyServiceSettings - - - true - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Basic.Settings.Output.Adv.Rescale - - - - - - - false - - - true - - - - - - - - - Basic.Settings.Output.Adv.Recording - - + + - 9 + 0 - 9 + 0 - 9 + 0 - 9 + 0 - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0 - - - 0 - - - - - - 170 - 0 - - - - Basic.Settings.Output.Adv.Recording.Type - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - advOutRecType - - - - - - - - Basic.Settings.Output.Adv.Recording.Type.Standard - - - - - Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput - - - - - - - - - - - Qt::Horizontal - - - - - + 0 - - + + true + + + + Basic.Settings.Output.Adv.Streaming + + - 0 + 9 0 - 0 + 9 - 0 + 9 - - + + + + 0 + 0 @@ -1541,31 +1366,22 @@ 0 - + 0 0 - + QFormLayout::AllNonFixedFieldsGrow Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - 0 - - - - - - 0 - 0 - - + + 170 @@ -1573,107 +1389,22 @@ - Basic.Settings.Output.Simple.SavePath + Basic.Settings.Output.Adv.AudioTrack Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - advOutRecPath - - - - - - - true - - - - - - - true - - - Browse - - - - - - - - Basic.Settings.Output.NoSpaceFileName - - - true - - - - - - - Basic.Settings.Output.Format - - - advOutRecFormat - - - - - - - - flv - - - - - mp4 - - - - - mov - - - - - mkv - - - - - ts - - - - - m3u8 - - - - - - - - Basic.Settings.Output.Adv.AudioTrack - - - - - + 0 0 - + 0 @@ -1687,42 +1418,45 @@ 0 - + 1 + + true + - + 2 - + 3 - + 4 - + 5 - + 6 @@ -1731,21 +1465,31 @@ - - + + Basic.Settings.Output.Encoder - advOutRecEncoder + advOutEncoder - - + + - - + + + + Basic.Settings.Output.Adv.ApplyServiceSettings + + + true + + + + + 0 @@ -1760,47 +1504,16 @@ - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - false - - - true - - - - - - - - - - Basic.Settings.Output.CustomMuxerSettings + + + + false - - advOutMuxCustom + + true - - - @@ -1809,423 +1522,1641 @@ - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Basic.Settings.Output.Adv.Recording + + + + 9 - 0 + 9 - - - - - 0 - 0 - - - - - 170 - 0 - - - - Basic.Settings.Output.Adv.FFmpeg.SavePathURL - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - - - - Browse - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - - - - - - - - Basic.Settings.Output.Adv.FFmpeg.Format - - - advOutFFFormat - - - - - - - - - - Basic.Settings.Output.Adv.FFmpeg.FormatDesc - - - - - - - - - - - - - - Basic.Settings.Output.VideoBitrate - - - advOutFFVBitrate - - - - - - - 0 - - - 1000000000 - - - 2500 - - - - - - - 1000000000 - - - 250 - - - - - - - Basic.Settings.Output.Adv.FFmpeg.GOPSize - - - advOutFFVGOPSize - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Basic.Settings.Output.Adv.Rescale - - - - - - - false - - - true - - - - - - - Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat - - - - - - - Basic.Settings.Output.Adv.FFmpeg.VEncoder - - - advOutFFVEncoder - - - - - - - - - - Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings - - - advOutFFVCfg - - - - - - - - - - Basic.Settings.Output.AudioBitrate - - - advOutFFABitrate - - - - - - - 32 - - - 4096 - - - 16 - - - 128 - - - - - - - Basic.Settings.Output.Adv.AudioTrack - - - - - - - - 0 - 0 - - - - - 0 + + 9 + + + 9 + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 - - 0 - 0 - - - - 1 + + + + + 170 + 0 + - - true + + Basic.Settings.Output.Adv.Recording.Type + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutRecType + + + + + Basic.Settings.Output.Adv.Recording.Type.Standard + + + + + Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput + + + + + + + + + + + Qt::Horizontal + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + + + + + + 0 + 0 + + + + + 170 + 0 + + + + Basic.Settings.Output.Simple.SavePath + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutRecPath + + + + + + + + + true + + + + + + + true + + + Browse + + + + + + + + + Basic.Settings.Output.NoSpaceFileName + + + true + + + + + + + Basic.Settings.Output.Format + + + advOutRecFormat + + + + + + + + flv + + + + + mp4 + + + + + mov + + + + + mkv + + + + + ts + + + + + m3u8 + + + + + + + + Basic.Settings.Output.Adv.AudioTrack + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + + + + + 2 + + + + + + + 3 + + + + + + + 4 + + + + + + + 5 + + + + + + + 6 + + + + + + + + + + Basic.Settings.Output.Encoder + + + advOutRecEncoder + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Basic.Settings.Output.Adv.Rescale + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + true + + + + + + + + + + Basic.Settings.Output.CustomMuxerSettings + + + advOutMuxCustom + + + + + + + + + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + + + + + + 0 + 0 + + + + + 170 + 0 + + + + Basic.Settings.Output.Adv.FFmpeg.SavePathURL + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + + + Browse + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + + + + + + + Basic.Settings.Output.Adv.FFmpeg.Format + + + advOutFFFormat + + + + + + + + + + Basic.Settings.Output.Adv.FFmpeg.FormatDesc + + + + + + + + + + + + + + Basic.Settings.Output.VideoBitrate + + + advOutFFVBitrate + + + + + + + 0 + + + 1000000000 + + + 2500 + + + + + + + 1000000000 + + + 250 + + + + + + + Basic.Settings.Output.Adv.FFmpeg.GOPSize + + + advOutFFVGOPSize + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Basic.Settings.Output.Adv.Rescale + + + + + + + false + + + true + + + + + + + Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat + + + + + + + Basic.Settings.Output.Adv.FFmpeg.VEncoder + + + advOutFFVEncoder + + + + + + + + + + Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings + + + advOutFFVCfg + + + + + + + + + + Basic.Settings.Output.AudioBitrate + + + advOutFFABitrate + + + + + + + 32 + + + 4096 + + + 16 + + + 128 + + + + + + + Basic.Settings.Output.Adv.AudioTrack + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + true + + + + + + + 2 + + + + + + + 3 + + + + + + + 4 + + + + + + + 5 + + + + + + + 6 + + + + + + + + + + Basic.Settings.Output.Adv.FFmpeg.AEncoder + + + advOutFFAEncoder + + + + + + + + + + Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings + + + advOutFFACfg + + + + + + + + + + + 0 + 0 + + + + + 170 + 0 + + + + Basic.Settings.Output.Adv.FFmpeg.Type + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutFFType + + + + + + + + Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile + + + + + Basic.Settings.Output.Adv.FFmpeg.Type.URL + + + + + + + + Basic.Settings.Output.Adv.FFmpeg.MuxerSettings + + + advOutFFMCfg + + + + + + + + + + Basic.Settings.Output.NoSpaceFileName + + + true + + + + + + + + + + + + Basic.Settings.Audio + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + - - - 2 + + + + 0 + 0 + + + Basic.Settings.Output.Adv.Audio.Track1 + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + 8 + + + + 32 + + + + + 48 + + + + + 64 + + + + + 80 + + + + + 96 + + + + + 112 + + + + + 128 + + + + + 160 + + + + + 192 + + + + + 256 + + + + + 320 + + + + + + + + + 170 + 0 + + + + Basic.Settings.Output.AudioBitrate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutTrack1Bitrate + + + + + + + Name + + + advOutTrack1Name + + + + + + + - - - 3 + + + + 0 + 0 + + + Basic.Settings.Output.Adv.Audio.Track2 + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 0 + + + + Basic.Settings.Output.AudioBitrate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutTrack2Bitrate + + + + + + + 8 + + + + 32 + + + + + 48 + + + + + 64 + + + + + 80 + + + + + 96 + + + + + 112 + + + + + 128 + + + + + 160 + + + + + 192 + + + + + 256 + + + + + 320 + + + + + + + + Name + + + advOutTrack2Name + + + + + + + - - - 4 + + + + 0 + 0 + + + Basic.Settings.Output.Adv.Audio.Track3 + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 0 + + + + Basic.Settings.Output.AudioBitrate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutTrack3Bitrate + + + + + + + 8 + + + + 32 + + + + + 48 + + + + + 64 + + + + + 80 + + + + + 96 + + + + + 112 + + + + + 128 + + + + + 160 + + + + + 192 + + + + + 256 + + + + + 320 + + + + + + + + Name + + + advOutTrack3Name + + + + + + + - - - 5 + + + + 0 + 0 + + + Basic.Settings.Output.Adv.Audio.Track4 + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 0 + + + + Basic.Settings.Output.AudioBitrate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutTrack4Bitrate + + + + + + + 8 + + + + 32 + + + + + 48 + + + + + 64 + + + + + 80 + + + + + 96 + + + + + 112 + + + + + 128 + + + + + 160 + + + + + 192 + + + + + 256 + + + + + 320 + + + + + + + + Name + + + advOutTrack4Name + + + + + + + - + + + + 0 + 0 + + + + Basic.Settings.Output.Adv.Audio.Track5 + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 0 + + + + Basic.Settings.Output.AudioBitrate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutTrack5Bitrate + + + + + + + 8 + + + + 32 + + + + + 48 + + + + + 64 + + + + + 80 + + + + + 96 + + + + + 112 + + + + + 128 + + + + + 160 + + + + + 192 + + + + + 256 + + + + + 320 + + + + + + + + Name + + + advOutTrack5Name + + + + + + + + + + + + + + 0 + 0 + + + + Basic.Settings.Output.Adv.Audio.Track6 + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 0 + + + + Basic.Settings.Output.AudioBitrate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + advOutTrack6Bitrate + + + + + + + 8 + + + + 32 + + + + + 48 + + + + + 64 + + + + + 80 + + + + + 96 + + + + + 112 + + + + + 128 + + + + + 160 + + + + + 192 + + + + + 256 + + + + + 320 + + + + + + + + Name + + + advOutTrack6Name + + + + + + + + + + + + + + + + + ReplayBuffer + + + + + + Basic.Settings.Output.UseReplayBuffer + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + ReplayBuffer + + + false + + + false + + + + - 6 + Basic.Settings.Output.ReplayBuffer.SecondsMax + + + + + + + sec + + + 5 + + + 21600 + + + 15 + + + + + + + Basic.Settings.Output.ReplayBuffer.HotkeyMessage + + + + + + + + + + + + + + MB + + + 20 + + + 8192 + + + 512 + + + + + + + Basic.Settings.Output.ReplayBuffer.MegabytesMax - - - - Basic.Settings.Output.Adv.FFmpeg.AEncoder + + + + Qt::Vertical - - advOutFFAEncoder - - - - - - - - - - Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings - - - advOutFFACfg - - - - - - - - - - - 0 - 0 - - - + - 170 - 0 + 20 + 40 - - Basic.Settings.Output.Adv.FFmpeg.Type - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - advOutFFType - - - - - - - - Basic.Settings.Output.Adv.FFmpeg.Type.RecordToFile - - - - - Basic.Settings.Output.Adv.FFmpeg.Type.URL - - - - - - - - Basic.Settings.Output.Adv.FFmpeg.MuxerSettings - - - advOutFFMCfg - - - - - - - - - - Basic.Settings.Output.NoSpaceFileName - - - true - - + @@ -2233,727 +3164,6 @@ - - - Basic.Settings.Audio - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - - 0 - 0 - - - - Basic.Settings.Output.Adv.Audio.Track1 - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - 8 - - - - 32 - - - - - 48 - - - - - 64 - - - - - 80 - - - - - 96 - - - - - 112 - - - - - 128 - - - - - 160 - - - - - 192 - - - - - 256 - - - - - 320 - - - - - - - - - 170 - 0 - - - - Basic.Settings.Output.AudioBitrate - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - advOutTrack1Bitrate - - - - - - - Name - - - advOutTrack1Name - - - - - - - - - - - - - - 0 - 0 - - - - Basic.Settings.Output.Adv.Audio.Track2 - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 170 - 0 - - - - Basic.Settings.Output.AudioBitrate - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - advOutTrack2Bitrate - - - - - - - 8 - - - - 32 - - - - - 48 - - - - - 64 - - - - - 80 - - - - - 96 - - - - - 112 - - - - - 128 - - - - - 160 - - - - - 192 - - - - - 256 - - - - - 320 - - - - - - - - Name - - - advOutTrack2Name - - - - - - - - - - - - - - 0 - 0 - - - - Basic.Settings.Output.Adv.Audio.Track3 - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 170 - 0 - - - - Basic.Settings.Output.AudioBitrate - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - advOutTrack3Bitrate - - - - - - - 8 - - - - 32 - - - - - 48 - - - - - 64 - - - - - 80 - - - - - 96 - - - - - 112 - - - - - 128 - - - - - 160 - - - - - 192 - - - - - 256 - - - - - 320 - - - - - - - - Name - - - advOutTrack3Name - - - - - - - - - - - - - - 0 - 0 - - - - Basic.Settings.Output.Adv.Audio.Track4 - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 170 - 0 - - - - Basic.Settings.Output.AudioBitrate - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - advOutTrack4Bitrate - - - - - - - 8 - - - - 32 - - - - - 48 - - - - - 64 - - - - - 80 - - - - - 96 - - - - - 112 - - - - - 128 - - - - - 160 - - - - - 192 - - - - - 256 - - - - - 320 - - - - - - - - Name - - - advOutTrack4Name - - - - - - - - - - - - - - 0 - 0 - - - - Basic.Settings.Output.Adv.Audio.Track5 - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 170 - 0 - - - - Basic.Settings.Output.AudioBitrate - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - advOutTrack5Bitrate - - - - - - - 8 - - - - 32 - - - - - 48 - - - - - 64 - - - - - 80 - - - - - 96 - - - - - 112 - - - - - 128 - - - - - 160 - - - - - 192 - - - - - 256 - - - - - 320 - - - - - - - - Name - - - advOutTrack5Name - - - - - - - - - - - - - - 0 - 0 - - - - Basic.Settings.Output.Adv.Audio.Track6 - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 170 - 0 - - - - Basic.Settings.Output.AudioBitrate - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - advOutTrack6Bitrate - - - - - - - 8 - - - - 32 - - - - - 48 - - - - - 64 - - - - - 80 - - - - - 96 - - - - - 112 - - - - - 128 - - - - - 160 - - - - - 192 - - - - - 256 - - - - - 320 - - - - - - - - Name - - - advOutTrack6Name - - - - - - - - - - - - - - @@ -3028,6 +3238,31 @@ Stereo + + + 2.1 + + + + + 4.0 + + + + + 4.1 + + + + + 5.1 + + + + + 7.1 + + @@ -3127,7 +3362,7 @@ - + true @@ -3137,14 +3372,14 @@ 0 0 - 98 - 28 + 800 + 69 - + color: rgb(255, 0, 4); @@ -3157,6 +3392,54 @@ + + + + + + + true + + + warning + + + + + + + Basic.Settings.Audio.MeterDecayRate + + + meterDecayRate + + + + + + + Basic.Settings.Audio.MeterDecayRate.Fast + + + 0 + + + + Basic.Settings.Audio.MeterDecayRate.Fast + + + + + Basic.Settings.Audio.MeterDecayRate.Medium + + + + + Basic.Settings.Audio.MeterDecayRate.Slow + + + + diff --git a/UI/forms/images/locked_mask.png b/UI/forms/images/locked_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..4f05b1a56495c005e037ea94e921a3f7aee425b3 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPtn~Z=Uo6`NLP@vE{PZ!4!i_=#p+j21(3bfvDIhrMY z)9qkz1xx;enQz|Y?Co6pBJR+!Q%)?b^-{TZ-0mESks`Cd9-MGy&z|Xu&P&3WUN8wK z@XIu42Gk{Qc+hIP;mhAw!5>es7B65ZN_e)lu*&XA1yAkTGjFoe%sr>7D6PBLAf=}g zll$5>fqBl}yTThjTU5Pe%4z%;JzaG>kIK_|)8!1gJm>D%{o(bA|CbLuQt>`k{@s1i f!3P)D^?zrv&~4yR{3sg;bUA~ktDnm{r-UW|XFhJy literal 0 HcmV?d00001 diff --git a/UI/forms/images/refresh.png b/UI/forms/images/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..e3e4c5b8b7e213b390b41e7d65f88c711107648f GIT binary patch literal 2712 zcmV;J3TO3+P)ee4 z2z(d#3y>=?LW`INO0-~{k>6F})U0rVDqZVPZyR}J};w+Y~7V5GmP;_Xw`!IN1pt2QCKa z-)SU}#spB*a==r-SE{(bJAfy3saH;4U)Ono380{T1biz6eQpN63_Kk`1nW%zSMD9` zNlD*V0SAGxGG7O)O#qj-OTey_^uG}a963?u#4uw5SbXmwEwmzWk<$_TI$#1=y!EDR zJiZSMNAwdMF##;T)w(ZbW3m@GAEB>fq%gcb2~Xe$xJ4FK6I*~cBJ}kPRj`3nZ338g z4)vu>1e+p?;Biv4m7L^u@GRv;8&~Q{5ao5S*95S1+nzEJ?1?Zj93>@@MU@H&*`31G zf$tLW(KK}IjtU824md=*j;22xp`TmJ{n3V)J9Pu_LuBtfX%F0gkn@P|rN~+I4Dbby z&%A=1z^+;WG-}842@mFczorq_aavf1L@~8HUY`T*1Ac>16Eq?lNItKD6W9r)nqn1j z48Idd{ZW6csf^Dx>14|Y%LL-~?ZqtsHLI_JXIY4Hj-77c=jzQe!qbdI7Wo`VP0SV< z$8nD~9wZ^};6|2?)cGAX z7S>#0?$r0X>rnsa8lyZ8c?jTK+&A-S zPbbA%vtQY?;dczpD|?x5s@S_du000)+w2#_FYpJC>tj7QGvJ}bd(3vhxQi-@Aoh6_ z*=ishE0N5lF5~XC^Xl^o*y!Q#c|jjtM!5g+PB>n>LTwdX0JnKudy+aywww@CNWzQY zw(=1`5AN{;uOc2oBMg^-e>z^)WC1+da%1#kgL=3s|- z6~MU1#IBvNiJA?e1xbSKI$<0SDS#1=*FcwtQVn;qxCh$KxcUrS1u*O(fR*YuFv8M? zdywsn+ANp=rf_%etikSWJg$8z z&Kp^W`^lgwEPGW5IuX~d>$u+-_YIx%xS2y{zq0uO3)xSX8}GA8+|XeU0la1QE1L~m zo|30j`)Lw4)PrAj&s}E2vdIA7K#F6O8t)BB+)RhAGhLH5|Jm#pj5SE5@x_Gj5(y=V z${;DpT%Q6Lk(6gfIKGIa;K>z?^LI!v8fz1hoX}MSPhvqLp~)h}t6xF3es4+g$6Jwt z1wIunERSG3dF_8|$eOaMh3!99w#xY|hCRtLQ%fW;Yz*x_mQ6SStNuiH!jSCV1jI9ZhT zJQ9SOmc4^r_?LjMX#s$^PfwT8`7va_AuYRzdsseftF8n9upW55%t1J&p13pwmE4D9 z^)vw#aBBb&j3SXmJF5H%@GO^yYMB5Eo&=`@49Gf1d?Q06E$2UvDaa4Nu{ z%poD?Zy|?IDsckoMX6D0K|&3E)e}spNLT<|k-IQnXy7&Z<8J zzW<5|prL|7I0Af=@WG7JNT}M2NST!(eKe2h^nwDc>S1+T6Z%5MHtwIiATu0>-UBoJY zYM`5FyNLDDHd6<*>brHc4h;IP31DeD@DDFbQ~L!H=()`VP@1hswDCj@7=0Ys*C;OL z*z4H8x)i`X2X#h2l0w4|tHY|lME~}&{N6!b3jke`^#i}f??7s$+IzqwNEvsR)4B&J z9bQ)V0w|m)ZbFWx4_2Y!UqCj3dLk?x`&c^A)C91IC$U2Z`Y?Vui$b?Ez>k2>Ah~?Y zDU`9(1Q4Bdzys=Hp08Rq8^~QdSMYrk$Zp^Lc$8=LV0%5eg5zYP&boWl(R95ozcSjI zxH8gc-q17Ja!8=+n@H~6LH&2!>;{gqP;;TCUBOAxf@~CZs>?T9FiD%yC&xIl-8_u! z7gUP<$f@hDdT<3BORo;{#GG>1O1xDsN7X(PHWc+s6IO!HFbu;m48t&tp!^@zrY~HH Sm!w(%0000<9o-U3d7N^f%+UVt+$iw>JeO$IH z*SWi*bGY^LUS1Iwh^g(^=Xc%CU85uUIbUqd(TyBI7oYOIJ^wZ6WdL*Jq{AmIJuON@ za*v+bbHMhU)ES}kf*%8uUdMOH8yvL0XK*Z8dPCmKx?aIJgZ!e#*=P3Xe%!lBKrq{f z`%-vKQpZ(AF}_<@dP|2z0!WyL?I nfPV+YxBYmxBP#qq%SYxLw^Ral-g$l$=mQ2%S3j3^P6 images/mute.png images/unmute.png + images/refresh.png images/configuration21_16.png images/invisible_mask.png images/visible_mask.png @@ -14,6 +15,8 @@ images/up.png images/obs.png images/tray_active.png + images/locked_mask.png + images/unlocked_mask.png images/settings/advanced.png diff --git a/UI/frontend-plugins/frontend-tools/CMakeLists.txt b/UI/frontend-plugins/frontend-tools/CMakeLists.txt index 3ce774d..37a022a 100644 --- a/UI/frontend-plugins/frontend-tools/CMakeLists.txt +++ b/UI/frontend-plugins/frontend-tools/CMakeLists.txt @@ -5,12 +5,14 @@ if(APPLE) include_directories(${COCOA}) endif() -if(UNIX) +if(UNIX AND NOT APPLE) find_package(X11 REQUIRED) link_libraries(${X11_LIBRARIES}) include_directories(${X11_INCLUDE_DIR}) endif() +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/deps/obs-scripting") + configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/frontend-tools-config.h.in" "${CMAKE_BINARY_DIR}/config/frontend-tools-config.h") @@ -21,12 +23,19 @@ set(frontend-tools_HEADERS auto-scene-switcher.hpp output-timer.hpp tool-helpers.hpp + ../../properties-view.hpp + ../../properties-view.moc.hpp + ../../vertical-scroll-area.hpp + ../../double-slider.hpp ) set(frontend-tools_SOURCES ${frontend-tools_SOURCES} auto-scene-switcher.cpp frontend-tools.c output-timer.cpp + ../../properties-view.cpp + ../../vertical-scroll-area.cpp + ../../double-slider.cpp ) set(frontend-tools_UI ${frontend-tools_UI} @@ -34,6 +43,25 @@ set(frontend-tools_UI forms/output-timer.ui ) +if(SCRIPTING_ENABLED) + set(frontend-tools_HEADERS + ${frontend-tools_HEADERS} + scripts.hpp + ) + set(frontend-tools_SOURCES + ${frontend-tools_SOURCES} + scripts.cpp + ) + set(frontend-tools_UI + ${frontend-tools_UI} + forms/scripts.ui + ) + set(EXTRA_LIBS + ${EXTRA_LIBS} + obs-scripting + ) +endif() + if(WIN32) set(frontend-tools_PLATFORM_SOURCES auto-scene-switcher-win.cpp) @@ -79,6 +107,7 @@ add_library(frontend-tools MODULE ) target_link_libraries(frontend-tools ${frontend-tools_PLATFORM_LIBS} + ${EXTRA_LIBS} obs-frontend-api Qt5::Widgets libobs) diff --git a/UI/frontend-plugins/frontend-tools/captions-mssapi.hpp b/UI/frontend-plugins/frontend-tools/captions-mssapi.hpp index a9a580f..aee2f1b 100644 --- a/UI/frontend-plugins/frontend-tools/captions-mssapi.hpp +++ b/UI/frontend-plugins/frontend-tools/captions-mssapi.hpp @@ -8,8 +8,18 @@ #include #include #include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include #include diff --git a/UI/frontend-plugins/frontend-tools/captions.cpp b/UI/frontend-plugins/frontend-tools/captions.cpp index d2da4d3..43ebf94 100644 --- a/UI/frontend-plugins/frontend-tools/captions.cpp +++ b/UI/frontend-plugins/frontend-tools/captions.cpp @@ -10,8 +10,18 @@ #include #include #include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include #include #include diff --git a/UI/frontend-plugins/frontend-tools/data/locale/bn-BD.ini b/UI/frontend-plugins/frontend-tools/data/locale/bn-BD.ini index 848720a..edf10dc 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/bn-BD.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/bn-BD.ini @@ -11,3 +11,5 @@ Captions.CurrentSystemLanguage="বর্তমান সিস্টেমে OutputTimer="আউটপুট টাইমার" OutputTimer.Stream="এর পরে বন্ধ।:" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ca-ES.ini b/UI/frontend-plugins/frontend-tools/data/locale/ca-ES.ini index 0b7ff0d..eded949 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/ca-ES.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/ca-ES.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="La gravació s'aturarà en:" OutputTimer.Stream.EnableEverytime="Activa el temporitzador en cada transmissió" OutputTimer.Record.EnableEverytime="Activa el temporitzador en cada enregistrament" +Scripts="Scripts" +LoadedScripts="Scripts carregats" +AddScripts="Afegeix scripts" +RemoveScripts="Suprimeix scripts" +ReloadScripts="Torna a carregar els scripts" +PythonSettings="Configuració del Python" +PythonSettings.PythonInstallPath32bit="Camí d'instal·lació del Python (32bit)" +PythonSettings.PythonInstallPath64bit="Camí d'instal·lació del Python (64bit)" +PythonSettings.BrowsePythonPath="Camí del Python" +ScriptLogWindow="Informe script" +Description="Descripció" + +FileFilter.ScriptFiles="Fitxers script" +FileFilter.AllFiles="Tots els fitxers" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/cs-CZ.ini b/UI/frontend-plugins/frontend-tools/data/locale/cs-CZ.ini index a70a46a..6edc823 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/cs-CZ.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/cs-CZ.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Nahrávání se zastaví za:" OutputTimer.Stream.EnableEverytime="Pokaždé povolit časovač vysílání" OutputTimer.Record.EnableEverytime="Pokaždé povolit časovač nahrávání" +Scripts="Skripty" +LoadedScripts="Načtené skripty" +AddScripts="Přidat skripty" +RemoveScripts="Odebrat skripty" +ReloadScripts="Restartovat skripty" +PythonSettings="Python nastavení" +PythonSettings.PythonInstallPath32bit="Cesta k instalaci Pythonu (32bit)" +PythonSettings.PythonInstallPath64bit="Cesta k instalaci Pythonu (64bit)" +PythonSettings.BrowsePythonPath="Najít cestu k Pythonu" +ScriptLogWindow="Log skriptu" +Description="Popis" + +FileFilter.ScriptFiles="Soubory skriptů" +FileFilter.AllFiles="Všechny soubory" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/da-DK.ini b/UI/frontend-plugins/frontend-tools/data/locale/da-DK.ini index de1619d..db752c7 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/da-DK.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/da-DK.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Streaming standser om:" OutputTimer.Stream.EnableEverytime="Aktivér streaming-timer hver gang" OutputTimer.Record.EnableEverytime="Aktivér optage-timer hver gang" +Scripts="Scripts" +LoadedScripts="Indlæste scripts" +AddScripts="Tilføj scripts" +RemoveScripts="Fjern scripts" +ReloadScripts="Genindlæs scripts" +PythonSettings="Python-indstillinger" +PythonSettings.PythonInstallPath32bit="Python-installationssti (32bit)" +PythonSettings.PythonInstallPath64bit="Python-installationssti (64bit)" +PythonSettings.BrowsePythonPath="Gennemse Python-sti" +ScriptLogWindow="Scriptlog" +Description="Beskrivelse" + +FileFilter.ScriptFiles="Scriptfiler" +FileFilter.AllFiles="Alle filer" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/de-DE.ini b/UI/frontend-plugins/frontend-tools/data/locale/de-DE.ini index 7f09c60..7d444c0 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/de-DE.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/de-DE.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Aufnahme stoppt in:" OutputTimer.Stream.EnableEverytime="Streaming-Timer jedes Mal aktivieren" OutputTimer.Record.EnableEverytime="Aufnahme-Timer jedes Mal aktivieren" +Scripts="Skripte" +LoadedScripts="Geladene Skripte" +AddScripts="Skripte hinzufügen" +RemoveScripts="Skripte entfernen" +ReloadScripts="Skripte neu laden" +PythonSettings="Python-Einstellungen" +PythonSettings.PythonInstallPath32bit="Python Installationspfad (32bit)" +PythonSettings.PythonInstallPath64bit="Python Installationspfad (64bit)" +PythonSettings.BrowsePythonPath="Python-Pfad öffnen" +ScriptLogWindow="Skriptprotokoll" +Description="Beschreibung" + +FileFilter.ScriptFiles="Skriptdateien" +FileFilter.AllFiles="Alle Dateien" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/el-GR.ini b/UI/frontend-plugins/frontend-tools/data/locale/el-GR.ini new file mode 100644 index 0000000..7684795 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/el-GR.ini @@ -0,0 +1,29 @@ +SceneSwitcher="Αυτόματος Εναλλαγέας Σκηνών" +SceneSwitcher.OnNoMatch="Όταν δεν ταιριάζει με κανένα παράθυρο:" +SceneSwitcher.OnNoMatch.DontSwitch="Καμία Μετάβαση" +SceneSwitcher.OnNoMatch.SwitchTo="Μετάβαση σε:" +SceneSwitcher.CheckInterval="Έλεγχος τίτλου ενεργού παραθύρου κάθε:" +SceneSwitcher.ActiveOrNotActive="Ο Εναλλαγέας Σκηνών είναι:" +InvalidRegex.Title="Μη έγκυρη τυπική έκφραση" +InvalidRegex.Text="Η τυπική έκφραση που εισαγάγατε δεν είναι έγκυρη." +Active="Ενεργός" +Inactive="Ανενεργός" +Start="Eκκίνηση" +Stop="Διακοπή" + +Captions="Υπότιτλοι (Πειραματικό)" +Captions.AudioSource="Πηγή ήχου" +Captions.CurrentSystemLanguage="Τρέχουσα γλώσσα του συστήματος (%1)" +Captions.Provider="Πάροχος" +Captions.Error.GenericFail="Αποτυχία εκκίνησης των υποτίτλων" + +OutputTimer="Χρονόμετρο εξόδου" +OutputTimer.Stream="Διακοπή streaming μετά από:" +OutputTimer.Record="Διακοπή εγγραφής βίντεο μετά από:" +OutputTimer.Stream.StoppingIn="Διακοπή streaming σε:" +OutputTimer.Record.StoppingIn="Διακοπή εγγραφής σε:" +OutputTimer.Stream.EnableEverytime="Ενεργοποίηση χρονόμετρου streaming κάθε φορά" +OutputTimer.Record.EnableEverytime="Ενεργοποίηση χρονόμετρου εγγραφής κάθε φορά" + + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini b/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini index 092a097..c3ab608 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini @@ -24,3 +24,19 @@ OutputTimer.Stream.StoppingIn="Streaming stopping in:" OutputTimer.Record.StoppingIn="Recording stopping in:" OutputTimer.Stream.EnableEverytime="Enable streaming timer every time" OutputTimer.Record.EnableEverytime="Enable recording timer every time" + +Scripts="Scripts" +LoadedScripts="Loaded Scripts" +AddScripts="Add Scripts" +RemoveScripts="Remove Scripts" +ReloadScripts="Reload Scripts" +LoadedScripts="Loaded Scripts" +PythonSettings="Python Settings" +PythonSettings.PythonInstallPath32bit="Python Install Path (32bit)" +PythonSettings.PythonInstallPath64bit="Python Install Path (64bit)" +PythonSettings.BrowsePythonPath="Browse Python Path" +ScriptLogWindow="Script Log" +Description="Description" + +FileFilter.ScriptFiles="Script Files" +FileFilter.AllFiles="All Files" diff --git a/UI/frontend-plugins/frontend-tools/data/locale/es-ES.ini b/UI/frontend-plugins/frontend-tools/data/locale/es-ES.ini index 0547065..38ce98c 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/es-ES.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/es-ES.ini @@ -1,4 +1,4 @@ -SceneSwitcher="Cambiador de escena automática" +SceneSwitcher="Selector de escena automático" SceneSwitcher.OnNoMatch="Cuando no coincida con ninguna ventana:" SceneSwitcher.OnNoMatch.DontSwitch="No cambiar" SceneSwitcher.OnNoMatch.SwitchTo="Cambiar a:" @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Finalizando grabación en:" OutputTimer.Stream.EnableEverytime="Activar temporizador en cada transmisión" OutputTimer.Record.EnableEverytime="Activar temporizador en cada grabación" +Scripts="Guion" +LoadedScripts="Guiones cargados" +AddScripts="Agregar Guiones" +RemoveScripts="Quitar Guiones" +ReloadScripts="Recargar Guiones" +PythonSettings="Configuración de Python" +PythonSettings.PythonInstallPath32bit="Dirección de la instalación de Python (32bit)" +PythonSettings.PythonInstallPath64bit="Dirección de la instalación de Python (64bit)" +PythonSettings.BrowsePythonPath="Buscar Ruta de Phyton" +ScriptLogWindow="Registro de secuencia de comandos" +Description="Descripción" + +FileFilter.ScriptFiles="Archivos de Guiones" +FileFilter.AllFiles="Todos los archivos" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/et-EE.ini b/UI/frontend-plugins/frontend-tools/data/locale/et-EE.ini index 583ef77..5ebd518 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/et-EE.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/et-EE.ini @@ -23,3 +23,5 @@ OutputTimer.Record.StoppingIn="Salvestamine lõppeb:" OutputTimer.Stream.EnableEverytime="Lülita voogedastuse taimer alati sisse" OutputTimer.Record.EnableEverytime="Lülita salvestus taimer alati sisse" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/eu-ES.ini b/UI/frontend-plugins/frontend-tools/data/locale/eu-ES.ini index 77ecdec..d724168 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/eu-ES.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/eu-ES.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Grabazioa geldituko da hau barru:" OutputTimer.Stream.EnableEverytime="Gaitu transmisio tenporizadorea aldiro" OutputTimer.Record.EnableEverytime="Gaitu grabazio tenporizadorea aldiro" +Scripts="Script-ak" +LoadedScripts="Kargatutako script-ak" +AddScripts="Gehitu script-ak" +RemoveScripts="Kendu script-ak" +ReloadScripts="Birkargatu script-ak" +PythonSettings="Python ezarpenak" +PythonSettings.PythonInstallPath32bit="Python instalazioaren bide-izena (32bit)" +PythonSettings.PythonInstallPath64bit="Python instalazioaren bide-izena (64bit)" +PythonSettings.BrowsePythonPath="Arakatu Python-en bide-izena" +ScriptLogWindow="Script-aren erregistroa" +Description="Deskribapena" + +FileFilter.ScriptFiles="Script-aren fitxategiak" +FileFilter.AllFiles="Fitxategi guztiak" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/fi-FI.ini b/UI/frontend-plugins/frontend-tools/data/locale/fi-FI.ini index debb720..a27618b 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/fi-FI.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/fi-FI.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Tallennus pysäytetään:" OutputTimer.Stream.EnableEverytime="Ota lähetysajastin käyttöön aina" OutputTimer.Record.EnableEverytime="Ota tallennusajastin käyttöön aina" +Scripts="Skriptit" +LoadedScripts="Ladatut skriptit" +AddScripts="Lisää skriptejä" +RemoveScripts="Poista skriptejä" +ReloadScripts="Lataa skriptit uudelleen" +PythonSettings="Python-asetukset" +PythonSettings.PythonInstallPath32bit="Python-asennuspolku (32bit)" +PythonSettings.PythonInstallPath64bit="Python-asennuspolku (64bit)" +PythonSettings.BrowsePythonPath="Selaa Python-polku" +ScriptLogWindow="Skripti-lokit" +Description="Kuvaus" + +FileFilter.ScriptFiles="Skripti-tiedostot" +FileFilter.AllFiles="Kaikki tiedostot" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/fr-FR.ini b/UI/frontend-plugins/frontend-tools/data/locale/fr-FR.ini index 04c8bb7..4b9abd7 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/fr-FR.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/fr-FR.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Arrêt de l'enregistrement dans :" OutputTimer.Stream.EnableEverytime="Activer le minuteur automatiquement à chaque stream" OutputTimer.Record.EnableEverytime="Activer le minuteur automatiquement à chaque enregistrement" +Scripts="Scripts" +LoadedScripts="Scripts chargés" +AddScripts="Ajouter des Scripts" +RemoveScripts="Retirer des Scripts" +ReloadScripts="Recharger les Scripts" +PythonSettings="Paramètres Python" +PythonSettings.PythonInstallPath32bit="Chemin d’installation Python (32 bits)" +PythonSettings.PythonInstallPath64bit="Chemin d’installation Python (64 bits)" +PythonSettings.BrowsePythonPath="Parcourir le chemin de Python" +ScriptLogWindow="Journal de script" +Description="Description" + +FileFilter.ScriptFiles="Fichiers de script" +FileFilter.AllFiles="Tous les fichiers" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/he-IL.ini b/UI/frontend-plugins/frontend-tools/data/locale/he-IL.ini index cff8e9b..d100f7f 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/he-IL.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/he-IL.ini @@ -23,3 +23,9 @@ OutputTimer.Record.StoppingIn="הקלטה עוצרת ב:" OutputTimer.Stream.EnableEverytime="הפעל טיימר הזרמה כל פעם" OutputTimer.Record.EnableEverytime="הפעל טיימר הקלטה כל פעם" +ScriptLogWindow="סקריפט לוג" +Description="תיאור" + +FileFilter.ScriptFiles="קובץ סקריפט" +FileFilter.AllFiles="כל הקבצים" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/hr-HR.ini b/UI/frontend-plugins/frontend-tools/data/locale/hr-HR.ini index ed94f35..da8fbed 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/hr-HR.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/hr-HR.ini @@ -11,6 +11,7 @@ Inactive="Neaktivan" Start="Pokreni" Stop="Zaustavi" +Captions="Opisi (experimentalno)" OutputTimer="Tempomat snimanja i emitovanja" OutputTimer.Stream="Zaustavi emitovanje nakon:" @@ -20,3 +21,5 @@ OutputTimer.Record.StoppingIn="Prekidanje snimanja za:" OutputTimer.Stream.EnableEverytime="Omogući štopovanje emitovanja svaki put" OutputTimer.Record.EnableEverytime="Omogući štopovanje snimanja svaki put" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/hu-HU.ini b/UI/frontend-plugins/frontend-tools/data/locale/hu-HU.ini index 15b626c..b27d52c 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/hu-HU.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/hu-HU.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Felvétel leáll:" OutputTimer.Stream.EnableEverytime="Stream időzítő indítása minden alkalommal" OutputTimer.Record.EnableEverytime="Felvétel időzítő indítása minden alkalommal" +Scripts="Szkriptek" +LoadedScripts="Betöltött szkriptek" +AddScripts="Szkriptek hozzáadása" +RemoveScripts="Szkriptek eltávolítása" +ReloadScripts="Szkriptek újraindítása" +PythonSettings="Python beállítások" +PythonSettings.PythonInstallPath32bit="Python telepítési útvonal (32 bites)" +PythonSettings.PythonInstallPath64bit="Python telepítési útvonal (64 bites)" +PythonSettings.BrowsePythonPath="Python elérési útjának tallózása" +ScriptLogWindow="Szkript napló" +Description="Leírás" + +FileFilter.ScriptFiles="Szkriptfájl" +FileFilter.AllFiles="Minden fájl" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/it-IT.ini b/UI/frontend-plugins/frontend-tools/data/locale/it-IT.ini index b8c6a87..d07ae54 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/it-IT.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/it-IT.ini @@ -1,21 +1,23 @@ SceneSwitcher="Cambia scena automatico" -SceneSwitcher.OnNoMatch="Quando nessuna scena coincide a:" +SceneSwitcher.OnNoMatch="Quando nessuna finestra coincide:" SceneSwitcher.OnNoMatch.DontSwitch="Non passare" SceneSwitcher.OnNoMatch.SwitchTo="Passa a:" SceneSwitcher.CheckInterval="Controlla il titolo della finestra attiva ogni:" -SceneSwitcher.ActiveOrNotActive="Lo scene switcher è:" +SceneSwitcher.ActiveOrNotActive="Il cambio scena è:" InvalidRegex.Title="Espressione regolare non valida" -InvalidRegex.Text="L'espressione regolare che hai inserito non è valido." +InvalidRegex.Text="L'espressione regolare che hai inserito non è valida." Active="Attivo" Inactive="Inattivo" -Start="Inizio" -Stop="Stop" +Start="Avvia" +Stop="Ferma" -Captions="Sottotitoli (Sperimentale)" +Captions="Sottotitoli (sperimentale)" Captions.AudioSource="Fonte audio" -Captions.CurrentSystemLanguage="Lingua del sistema in uso (%1)" +Captions.CurrentSystemLanguage="Lingua del sistema (%1)" +Captions.Provider="Sintetizzatore" +Captions.Error.GenericFail="Impossibile avviare i sottititoli" -OutputTimer="Timer Output" +OutputTimer="Timer di uscita" OutputTimer.Stream="Termina diretta dopo:" OutputTimer.Record="Termina registrazione dopo:" OutputTimer.Stream.StoppingIn="La diretta terminerà in:" @@ -23,3 +25,18 @@ OutputTimer.Record.StoppingIn="La registrazione terminerà in:" OutputTimer.Stream.EnableEverytime="Abilita il timer per lo streaming ogni volta" OutputTimer.Record.EnableEverytime="Abilita il timer per la registrazione ogni volta" +Scripts="Script" +LoadedScripts="Script caricati" +AddScripts="Aggiungi script" +RemoveScripts="Rimuovi script" +ReloadScripts="Ricarica script" +PythonSettings="Impostazioni di Python" +PythonSettings.PythonInstallPath32bit="Percorso d'installazione di Python (32bit)" +PythonSettings.PythonInstallPath64bit="Percorso d'installazione di Python (64bit)" +PythonSettings.BrowsePythonPath="Sfoglia Percorso Python" +ScriptLogWindow="Log degli script" +Description="Descrizione" + +FileFilter.ScriptFiles="Lista degli script" +FileFilter.AllFiles="Tutti i file" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ja-JP.ini b/UI/frontend-plugins/frontend-tools/data/locale/ja-JP.ini index cd3e7ab..fd035ba 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/ja-JP.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/ja-JP.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="録画停止まで:" OutputTimer.Stream.EnableEverytime="毎回配信タイマーを有効にする" OutputTimer.Record.EnableEverytime="毎回録画タイマーを有効にする" +Scripts="スクリプト" +LoadedScripts="ロードしたスクリプト" +AddScripts="スクリプトを追加" +RemoveScripts="スクリプトを削除" +ReloadScripts="スクリプトの再読み込み" +PythonSettings="Python の設定" +PythonSettings.PythonInstallPath32bit="Python インストールパス (32bit)" +PythonSettings.PythonInstallPath64bit="Python インストールパス (64bit)" +PythonSettings.BrowsePythonPath="Python パスを参照" +ScriptLogWindow="スクリプトログ" +Description="説明" + +FileFilter.ScriptFiles="スクリプトファイル" +FileFilter.AllFiles="すべてのファイル" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ka-GE.ini b/UI/frontend-plugins/frontend-tools/data/locale/ka-GE.ini new file mode 100644 index 0000000..bbf5557 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/ka-GE.ini @@ -0,0 +1,11 @@ +Active="ჩართული" +Inactive="გამორთული" +Start="დაწყება" +Stop="შეწყვეტა" + +Captions.AudioSource="აუდიოს წყარო" + +OutputTimer.Record.StoppingIn="ჩაწერის შეწყვეტის დრო:" + + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ko-KR.ini b/UI/frontend-plugins/frontend-tools/data/locale/ko-KR.ini index 522b976..d413be8 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/ko-KR.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/ko-KR.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="녹화 중지까지 남은 시간:" OutputTimer.Stream.EnableEverytime="매번 방송 시간 기록기 활성화" OutputTimer.Record.EnableEverytime="매번 녹화 시간 기록기 활성화" +Scripts="스크립트" +LoadedScripts="입력한 스크립트" +AddScripts="스크립트 추가" +RemoveScripts="스크립트 제거" +ReloadScripts="스크립트 다시 읽기" +PythonSettings="Python 설정" +PythonSettings.PythonInstallPath32bit="Python 설치 경로 (32비트)" +PythonSettings.PythonInstallPath64bit="Python 설치 경로 (64비트)" +PythonSettings.BrowsePythonPath="Python 경로 찾기" +ScriptLogWindow="스크립트 기록" +Description="설명" + +FileFilter.ScriptFiles="스크립트 파일" +FileFilter.AllFiles="모든 파일" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/lt-LT.ini b/UI/frontend-plugins/frontend-tools/data/locale/lt-LT.ini new file mode 100644 index 0000000..d57a608 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/lt-LT.ini @@ -0,0 +1,29 @@ +SceneSwitcher="Automatinis scenų keitiklis" +SceneSwitcher.OnNoMatch="Kai langai nesutampa:" +SceneSwitcher.OnNoMatch.DontSwitch="Neperjungti" +SceneSwitcher.OnNoMatch.SwitchTo="Perjungti į:" +SceneSwitcher.CheckInterval="Tikrinti aktyvaus lango antraštę kas:" +SceneSwitcher.ActiveOrNotActive="Scenų keitimas yra:" +InvalidRegex.Title="Neteisinga Reguliarioji Išraiška" +InvalidRegex.Text="Reguliarioji išraiška, kurią įvedėte - neteisinga." +Active="Aktyvus" +Inactive="Neaktyvus" +Start="Įjungti" +Stop="Išjungti" + +Captions="Titrai (Eksperimentinis)" +Captions.AudioSource="Garso šaltinis" +Captions.CurrentSystemLanguage="Dabartinė sistemos kalba (%1)" +Captions.Provider="Iš" +Captions.Error.GenericFail="Nepavyko įjungti titrų" + +OutputTimer="Išvesties automatinis laikmatis" +OutputTimer.Stream="Sustabdyti transliaciją po:" +OutputTimer.Record="Stabdyti įrašymą po:" +OutputTimer.Stream.StoppingIn="Transliacija sustos po:" +OutputTimer.Record.StoppingIn="Įrašymas sustos po:" +OutputTimer.Stream.EnableEverytime="Įgalinti transliacijos laikmatį kiekvieną kartą" +OutputTimer.Record.EnableEverytime="Įgalinti įrašymo laikmatį kiekvieną kartą" + + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ms-MY.ini b/UI/frontend-plugins/frontend-tools/data/locale/ms-MY.ini index 36ec39f..18ab93e 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/ms-MY.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/ms-MY.ini @@ -9,3 +9,5 @@ OutputTimer.Record="Berhenti merakam selepas:" OutputTimer.Stream.StoppingIn="'Streaming' dihentikan dalam:" OutputTimer.Record.StoppingIn="Rakaman dihentikan dalam:" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/nb-NO.ini b/UI/frontend-plugins/frontend-tools/data/locale/nb-NO.ini index e525d27..8ce8952 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/nb-NO.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/nb-NO.ini @@ -14,6 +14,8 @@ Stop="Stopp" Captions="Bildetekster (eksperimentell)" Captions.AudioSource="Lyd kilde" Captions.CurrentSystemLanguage="Någjeldende System Språk" +Captions.Provider="Leverandør" +Captions.Error.GenericFail="Feilet å starte undertekst" OutputTimer="Stoppeklokke" OutputTimer.Stream="Stopp streaming etter:" @@ -23,3 +25,18 @@ OutputTimer.Record.StoppingIn="Opptak stopper om:" OutputTimer.Stream.EnableEverytime="Aktiver streaming timer hver gang" OutputTimer.Record.EnableEverytime="Aktiver opptaks timer hver gang" +Scripts="Skripter" +LoadedScripts="Innlastede skripter" +AddScripts="Legg til skripter" +RemoveScripts="Fjern skripter" +ReloadScripts="Last inn skripter på nytt" +PythonSettings="Python-innstillinger" +PythonSettings.PythonInstallPath32bit="Python-installasjonsfilbane (32-bit)" +PythonSettings.PythonInstallPath64bit="Python-installasjonsfilbane (64-bit)" +PythonSettings.BrowsePythonPath="Finn Python-filbanen" +ScriptLogWindow="Skripthistorikk" +Description="Beskrivelse" + +FileFilter.ScriptFiles="Skriptfiler" +FileFilter.AllFiles="Alle filer" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/nl-NL.ini b/UI/frontend-plugins/frontend-tools/data/locale/nl-NL.ini index 65441e9..6c8bd1e 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/nl-NL.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/nl-NL.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Opname stopt over:" OutputTimer.Stream.EnableEverytime="Schakel streaming timer elke keer in" OutputTimer.Record.EnableEverytime="Schakel opnametimer elke keer in" +Scripts="Scripts" +LoadedScripts="Geladen Scripts" +AddScripts="Voeg Scripts Toe" +RemoveScripts="Verwijder Scripts" +ReloadScripts="Herlaad Scripts" +PythonSettings="Python-instellingen" +PythonSettings.PythonInstallPath32bit="Python Installatiepad (32bit)" +PythonSettings.PythonInstallPath64bit="Python Installatiepad (64bit)" +PythonSettings.BrowsePythonPath="Blader Naar Python Pad" +ScriptLogWindow="Script Log" +Description="Beschrijving" + +FileFilter.ScriptFiles="Scriptbestanden" +FileFilter.AllFiles="Alle Bestanden" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/pl-PL.ini b/UI/frontend-plugins/frontend-tools/data/locale/pl-PL.ini index 8bd3edb..65481a8 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/pl-PL.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/pl-PL.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Zatrzymanie nagrywania za:" OutputTimer.Stream.EnableEverytime="Włącz timer streamu za każdym razem" OutputTimer.Record.EnableEverytime="Włącz timer nagrywania za każdym razem" +Scripts="Skrypty" +LoadedScripts="Wczytane skrypty" +AddScripts="Dodaj skrypty" +RemoveScripts="Usuń skrypty" +ReloadScripts="Przeładuj skrypty" +PythonSettings="Ustawienia Pythona" +PythonSettings.PythonInstallPath32bit="Ścieżka instalacji Pythona (32bit)" +PythonSettings.PythonInstallPath64bit="Ścieżka instalacji Pythona (64bit)" +PythonSettings.BrowsePythonPath="Wybierz ścieżkę instalacji Pythona" +ScriptLogWindow="Dziennik skryptów" +Description="Opis" + +FileFilter.ScriptFiles="Pliki skryptów" +FileFilter.AllFiles="Wszystkie Pliki" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/pt-BR.ini b/UI/frontend-plugins/frontend-tools/data/locale/pt-BR.ini index ca9f22a..7056fc4 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/pt-BR.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/pt-BR.ini @@ -1,5 +1,5 @@ SceneSwitcher="Alternador automático de cena" -SceneSwitcher.OnNoMatch="Quando nenhuma janela corresponde:" +SceneSwitcher.OnNoMatch="Quando nenhuma janela corresponder:" SceneSwitcher.OnNoMatch.DontSwitch="Não alternar" SceneSwitcher.OnNoMatch.SwitchTo="Alternar para:" SceneSwitcher.CheckInterval="Checar o título da janela ativa a cada:" @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="A gravação irá parar em:" OutputTimer.Stream.EnableEverytime="Ativar o timer streaming o tempo todo" OutputTimer.Record.EnableEverytime="Ativar o timer de gravação o tempo todo" +Scripts="Scripts" +LoadedScripts="Scripts Carregados" +AddScripts="Adicionar Scripts" +RemoveScripts="Remover Scripts" +ReloadScripts="Recarregar Scripts" +PythonSettings="Configurações Python" +PythonSettings.PythonInstallPath32bit="Caminho de Instalação Python (32 bits)" +PythonSettings.PythonInstallPath64bit="Caminho de Instalação Python (64 bits)" +PythonSettings.BrowsePythonPath="Procurar Caminho do Python" +ScriptLogWindow="Log dos Scripts" +Description="Descrição" + +FileFilter.ScriptFiles="Arquivos de Scripts" +FileFilter.AllFiles="Todos os Arquivos" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/pt-PT.ini b/UI/frontend-plugins/frontend-tools/data/locale/pt-PT.ini index b9363bf..a11f5d0 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/pt-PT.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/pt-PT.ini @@ -11,10 +11,18 @@ Inactive="Inativa" Start="Iniciar" Stop="Parar" +Captions="Legendas (Experimental)" +Captions.AudioSource="Fonte de audio" +Captions.CurrentSystemLanguage="Linguagem de Sistema Atual (%1)" +Captions.Error.GenericFail="Ocorreu um erro a iniciar legendas" OutputTimer="Temporizador de saída" OutputTimer.Stream="Para a transmissão após:" OutputTimer.Record="Parar a gravação após:" OutputTimer.Stream.StoppingIn="A transmissão irá parar em:" OutputTimer.Record.StoppingIn="A gravação irá parar em:" +OutputTimer.Stream.EnableEverytime="Ativar temporizador de transmissão sempre" +OutputTimer.Record.EnableEverytime="Ativar temporizador de gravação sempre" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ro-RO.ini b/UI/frontend-plugins/frontend-tools/data/locale/ro-RO.ini index 2f223b9..11aa798 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/ro-RO.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/ro-RO.ini @@ -1,13 +1,18 @@ SceneSwitcher="Schimbator automat de scenă" -SceneSwitcher.OnNoMatch="Cand nici o fereastra nu se potriveste:" +SceneSwitcher.OnNoMatch="Când nicio fereastră nu se potrivește:" SceneSwitcher.OnNoMatch.SwitchTo="Schimbă la:" Active="Activ" Inactive="Inactiv" Start="Pornire" Stop="Oprire" +Captions="Subtitrări (experimentale)" Captions.AudioSource="Sursa audio" Captions.CurrentSystemLanguage="Limba curentă a sistemului (%1)" +Captions.Provider="Furnizor" OutputTimer.Record="Opriți inregistrarea dupa:" +OutputTimer.Record.StoppingIn="Oprire înregistrare în:" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/ru-RU.ini b/UI/frontend-plugins/frontend-tools/data/locale/ru-RU.ini index f8ad049..42e53e6 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/ru-RU.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/ru-RU.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Запись будет завершена чер OutputTimer.Stream.EnableEverytime="Включать таймер стрима каждый раз" OutputTimer.Record.EnableEverytime="Включать таймер записи каждый раз" +Scripts="Скрипты" +LoadedScripts="Загруженные скрипты" +AddScripts="Добавить скрипты" +RemoveScripts="Удалить скрипты" +ReloadScripts="Перезагрузить скрипты" +PythonSettings="Параметры Python" +PythonSettings.PythonInstallPath32bit="Путь установки Python (32 бита)" +PythonSettings.PythonInstallPath64bit="Путь установки Python (64 бита)" +PythonSettings.BrowsePythonPath="Обзор пути Python" +ScriptLogWindow="Лог скрипта" +Description="Описание" + +FileFilter.ScriptFiles="Файлы скриптов" +FileFilter.AllFiles="Все файлы" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/sk-SK.ini b/UI/frontend-plugins/frontend-tools/data/locale/sk-SK.ini index b5d2316..8208407 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/sk-SK.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/sk-SK.ini @@ -1,10 +1,29 @@ +SceneSwitcher="Automatický prepínač scén" +SceneSwitcher.OnNoMatch="Ak sa žiadne okno nezhoduje:" +SceneSwitcher.OnNoMatch.DontSwitch="Neprepínať" +SceneSwitcher.OnNoMatch.SwitchTo="Prepnúť na:" SceneSwitcher.CheckInterval="Kontrolovať aktívne okno každých:" +SceneSwitcher.ActiveOrNotActive="Prepínač scén je:" +InvalidRegex.Title="Neplatný regulárny výraz" +InvalidRegex.Text="Zadaný regulárny výraz je chybný." Active="Aktívny" Inactive="Neaktivní" Start="Spustiť" Stop="Zastaviť" +Captions="Titulky (experiment.)" +Captions.AudioSource="Zdroj zvuku" +Captions.CurrentSystemLanguage="Aktuálny systémový jazyk (%1)" +Captions.Provider="Poskytovateľ" +Captions.Error.GenericFail="Nepodarilo sa spustiť titulky" +OutputTimer="Časovač" OutputTimer.Stream="Zastaviť stream po:" OutputTimer.Record="Zastaviť nahrávanie po:" +OutputTimer.Stream.StoppingIn="Streamovanie za zastaví za:" +OutputTimer.Record.StoppingIn="Nahrávanie za zastaví za:" +OutputTimer.Stream.EnableEverytime="Povoliť časovač streamovania zakaždým" +OutputTimer.Record.EnableEverytime="Zapnúť časovač nahrávania zakaždým" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/sr-CS.ini b/UI/frontend-plugins/frontend-tools/data/locale/sr-CS.ini index ed94f35..bf7c94f 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/sr-CS.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/sr-CS.ini @@ -20,3 +20,5 @@ OutputTimer.Record.StoppingIn="Prekidanje snimanja za:" OutputTimer.Stream.EnableEverytime="Omogući štopovanje emitovanja svaki put" OutputTimer.Record.EnableEverytime="Omogući štopovanje snimanja svaki put" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/sr-SP.ini b/UI/frontend-plugins/frontend-tools/data/locale/sr-SP.ini index f40fa5a..f1e7a07 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/sr-SP.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/sr-SP.ini @@ -20,3 +20,5 @@ OutputTimer.Record.StoppingIn="Прекидање снимања за:" OutputTimer.Stream.EnableEverytime="Омогући штоповање емитовање сваки пут" OutputTimer.Record.EnableEverytime="Омогући штоповање снимања сваки пут" + + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/sv-SE.ini b/UI/frontend-plugins/frontend-tools/data/locale/sv-SE.ini index dc6bb46..ecf4e3a 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/sv-SE.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/sv-SE.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Inspelningen stoppas om:" OutputTimer.Stream.EnableEverytime="Aktivera strömtimer varje gång" OutputTimer.Record.EnableEverytime="Aktivera inspelningstimer varje gång" +Scripts="Skript" +LoadedScripts="Inlästa skript" +AddScripts="Lägg till skript" +RemoveScripts="Ta bort skript" +ReloadScripts="Ladda om skript" +PythonSettings="Python-inställningar" +PythonSettings.PythonInstallPath32bit="Installationssökväg för Python (32 bitar)" +PythonSettings.PythonInstallPath64bit="Installationssökväg för Python (64 bitar)" +PythonSettings.BrowsePythonPath="Bläddra Python-sökväg" +ScriptLogWindow="Skriptlogg" +Description="Beskrivning" + +FileFilter.ScriptFiles="Skriptfiler" +FileFilter.AllFiles="Alla filer" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/tr-TR.ini b/UI/frontend-plugins/frontend-tools/data/locale/tr-TR.ini index 7408838..857edac 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/tr-TR.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/tr-TR.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Kayıt durduruluyor:" OutputTimer.Stream.EnableEverytime="Her zaman yayın zamanlayıcıyı etkinleştir" OutputTimer.Record.EnableEverytime="Her zaman kayıt zamanlayıcıyı etkinleştir" +Scripts="Betikler" +LoadedScripts="Yüklü Betikler" +AddScripts="Betik Ekle" +RemoveScripts="Betikleri Sil" +ReloadScripts="Betikleri Yeniden Yükle" +PythonSettings="Python Ayarları" +PythonSettings.PythonInstallPath32bit="Python Kurulum Yolu (32bit)" +PythonSettings.PythonInstallPath64bit="Python Kurulum Yolu (64bit)" +PythonSettings.BrowsePythonPath="Python Yoluna Göz At" +ScriptLogWindow="Betik Kütüğü" +Description="Açıklama" + +FileFilter.ScriptFiles="Betik Dosyaları" +FileFilter.AllFiles="Tüm Dosyalar" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/uk-UA.ini b/UI/frontend-plugins/frontend-tools/data/locale/uk-UA.ini index 41fe668..f445349 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/uk-UA.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/uk-UA.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="Запис зупиниться за:" OutputTimer.Stream.EnableEverytime="Щоразу запускається трансляція - вмикати Таймер для Виводу" OutputTimer.Record.EnableEverytime="Щоразу починається запис - вмикати Таймер для Виводу" +Scripts="Скрипти" +LoadedScripts="Завантажені скрипти" +AddScripts="Додати скрипти" +RemoveScripts="Видалити скрипти" +ReloadScripts="Перезавантажити скрипти" +PythonSettings="Python налаштування" +PythonSettings.PythonInstallPath32bit="Шлях до програми Python (32 біти)" +PythonSettings.PythonInstallPath64bit="Шлях до програми Python (64 біти)" +PythonSettings.BrowsePythonPath="Огляд шляху до програми Python" +ScriptLogWindow="Журнал роботи скрипта" +Description="Опис" + +FileFilter.ScriptFiles="Файли скриптів" +FileFilter.AllFiles="Всі файли" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/vi-VN.ini b/UI/frontend-plugins/frontend-tools/data/locale/vi-VN.ini index bd8a79c..bbaac9c 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/vi-VN.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/vi-VN.ini @@ -9,9 +9,20 @@ Inactive="Không hoạt động" Start="Bắt đầu" Stop="Dừng" +Captions="Phụ đề (Thử nghiệm)" +Captions.AudioSource="Nguồn âm thanh" +Captions.CurrentSystemLanguage="Ngôn ngữ hiện tại của máy tính (%1)" +Captions.Provider="Nhà cung cấp" +Captions.Error.GenericFail="Thất bại trong việc bắt đầu phụ đề" OutputTimer.Stream="Dừng stream sau:" OutputTimer.Record="Dừng ghi video sau:" OutputTimer.Stream.StoppingIn="Stream sẽ dừng trong:" OutputTimer.Record.StoppingIn="Quay video sẽ dừng trong:" +AddScripts="Thêm script" +Description="Mô tả" + +FileFilter.ScriptFiles="Tập tin script" +FileFilter.AllFiles="Tất cả tập tin" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/zh-CN.ini b/UI/frontend-plugins/frontend-tools/data/locale/zh-CN.ini index 168a072..274a488 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/zh-CN.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/zh-CN.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="录制停止在:" OutputTimer.Stream.EnableEverytime="每次启用流计时器" OutputTimer.Record.EnableEverytime="每次启用录制计时器" +Scripts="脚本" +LoadedScripts="已载入脚本" +AddScripts="添加脚本" +RemoveScripts="移除脚本" +ReloadScripts="重新载入脚本" +PythonSettings="Python 设置" +PythonSettings.PythonInstallPath32bit="Python 安装路径 (32位)" +PythonSettings.PythonInstallPath64bit="Python 安装路径 (64位)" +PythonSettings.BrowsePythonPath="浏览 Python 路径" +ScriptLogWindow="脚本日志" +Description="说明" + +FileFilter.ScriptFiles="脚本文件" +FileFilter.AllFiles="所有文件" + diff --git a/UI/frontend-plugins/frontend-tools/data/locale/zh-TW.ini b/UI/frontend-plugins/frontend-tools/data/locale/zh-TW.ini index 4f48415..b82fd69 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/zh-TW.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/zh-TW.ini @@ -25,3 +25,18 @@ OutputTimer.Record.StoppingIn="錄影將在下面時間內停止" OutputTimer.Stream.EnableEverytime="每次都啟動串流計時器" OutputTimer.Record.EnableEverytime="每次都啟動錄影計時器" +Scripts="腳本" +LoadedScripts="已載入腳本" +AddScripts="增加腳本" +RemoveScripts="移除腳本" +ReloadScripts="重新載入腳本" +PythonSettings="Python 設置" +PythonSettings.PythonInstallPath32bit="Python 安裝路徑 (32bit)" +PythonSettings.PythonInstallPath64bit="Python 安裝路徑 (64bit)" +PythonSettings.BrowsePythonPath="瀏覽 Python 路徑" +ScriptLogWindow="腳本記錄" +Description="描述" + +FileFilter.ScriptFiles="脚本文件" +FileFilter.AllFiles="所有檔案" + diff --git a/UI/frontend-plugins/frontend-tools/data/scripts/clock-source.lua b/UI/frontend-plugins/frontend-tools/data/scripts/clock-source.lua new file mode 100644 index 0000000..5beb678 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/scripts/clock-source.lua @@ -0,0 +1,119 @@ +obs = obslua +bit = require("bit") + +source_def = {} +source_def.id = "lua_clock_source" +source_def.output_flags = bit.bor(obs.OBS_SOURCE_VIDEO, obs.OBS_SOURCE_CUSTOM_DRAW) + +function image_source_load(image, file) + obs.obs_enter_graphics(); + obs.gs_image_file_free(image); + obs.obs_leave_graphics(); + + obs.gs_image_file_init(image, file); + + obs.obs_enter_graphics(); + obs.gs_image_file_init_texture(image); + obs.obs_leave_graphics(); + + if not image.loaded then + print("failed to load texture " .. file); + end +end + +source_def.get_name = function() + return "Lua Clock" +end + +source_def.create = function(source, settings) + local data = {} + data.image = obs.gs_image_file() + data.hour_image = obs.gs_image_file() + data.minute_image = obs.gs_image_file() + data.second_image = obs.gs_image_file() + + image_source_load(data.image, script_path() .. "clock-source/dial.png") + image_source_load(data.hour_image, script_path() .. "clock-source/hour.png") + image_source_load(data.minute_image, script_path() .. "clock-source/minute.png") + image_source_load(data.second_image, script_path() .. "clock-source/second.png") + + return data +end + +source_def.destroy = function(data) + obs.obs_enter_graphics(); + obs.gs_image_file_free(data.image); + obs.gs_image_file_free(data.hour_image); + obs.gs_image_file_free(data.minute_image); + obs.gs_image_file_free(data.second_image); + obs.obs_leave_graphics(); +end + +source_def.video_render = function(data, effect) + if not data.image.texture then + return; + end + + local time = os.date("*t") + local seconds = time.sec + local mins = time.min + seconds / 60.0; + local hours = time.hour + (mins * 60.0) / 3600.0; + + effect = obs.obs_get_base_effect(obs.OBS_EFFECT_DEFAULT) + + obs.gs_blend_state_push() + obs.gs_reset_blend_state() + + while obs.gs_effect_loop(effect, "Draw") do + obs.obs_source_draw(data.image.texture, 0, 0, data.image.cx, data.image.cy, false); + end + + obs.gs_matrix_push() + obs.gs_matrix_translate3f(250, 250, 0) + obs.gs_matrix_rotaa4f(0.0, 0.0, 1.0, 2 * math.pi / 60 * mins); + obs.gs_matrix_translate3f(-250, -250, 0) + + while obs.gs_effect_loop(effect, "Draw") do + obs.obs_source_draw(data.minute_image.texture, 0, 0, data.image.cx, data.image.cy, false); + end + + obs.gs_matrix_pop() + + obs.gs_matrix_push() + obs.gs_matrix_translate3f(250, 250, 0) + obs.gs_matrix_rotaa4f(0.0, 0.0, 1.0, 2.0 * math.pi / 12 * hours); + obs.gs_matrix_translate3f(-250, -250, 0) + + while obs.gs_effect_loop(effect, "Draw") do + obs.obs_source_draw(data.hour_image.texture, 0, 0, data.image.cx, data.image.cy, false); + end + + obs.gs_matrix_pop() + + obs.gs_matrix_push() + obs.gs_matrix_translate3f(250, 250, 0) + obs.gs_matrix_rotaa4f(0.0, 0.0, 1.0, 2 * math.pi / 60 * seconds); + obs.gs_matrix_translate3f(-250, -250, 0) + + while obs.gs_effect_loop(effect, "Draw") do + obs.obs_source_draw(data.second_image.texture, 0, 0, data.image.cx, data.image.cy, false); + end + + obs.gs_matrix_pop() + + obs.gs_blend_state_pop() +end + +source_def.get_width = function(data) + return 500 +end + +source_def.get_height = function(data) + return 500 +end + +function script_description() + return "Adds a \"Lua Clock\" source which draws an animated analog clock." +end + +obs.obs_register_source(source_def) diff --git a/UI/frontend-plugins/frontend-tools/data/scripts/clock-source/dial.png b/UI/frontend-plugins/frontend-tools/data/scripts/clock-source/dial.png new file mode 100644 index 0000000000000000000000000000000000000000..80e766830f9408fea82393bc2e744a4d0a7b4055 GIT binary patch literal 27082 zcmZ6z1yoes_Xa#eOC#L~2nYxW2nbR-fFL!}-7Q_x14s)9C|v^5jYzkI(j^_zCEfX* z@%{b3^{wxHmkZ|JJLjI*XP>>F=h=iQE6U(uQ(;3O5Ioseus0A0^ceXM3jJph3) zKxAQ(s_&+E<~+PfPnyJUCzAMnzJF`_kecun0_}eMM|7x`DxtfhRAZ^Cy?yo8gy+nj zZPU%v>FM{=Q;n9>`R*FBlO--?-SQGfX)i6i;{2Z0DKjv8v{m9&)G2MX_;hY3#3l`8G=to=z zDhUci5+oNj5sl%ulK2}z1PdetN`uA&S;R%CKnh8$qZvcnG;`UsFK}b{hj+yEhI}pr)vrs0{w1{46ezN!(1b zR?KyoWxmBQe?^k#&~&^-Pe?ChmVzx~*zWF%)+#yo=H@QrF0XUxJOn*p|4d|tGaey~ zdrjHO1)*oJ)a^yEVhFio0!BTCWelE zBKgtX`M|Lq4JISgfg(|^O~e?Eb?+XOlkE89)Y#Av7>n$Eh=kj*jHV_9BO?>EfmR>| zPe95)x3C;l$axKSdS-fPYCI@Dj&*o`K5BQ@?)dZ+|Ed{HvUa1}pZ=1hRR$semaZMy zqv1)M16LvUn=9zGPdn*CaCO#ollFJ*nE3eMq^YT%!9f{SRT4%f#*d(9KjT=LY`Son zSy-aZPnvHpZD^i3nND`|^6@>f7)T`&clYuV!5<(0bAGUFX>TuMZ$R(q>8Y%*9~D?# zE#$cLW1(Zg*qSCy`K_-MPI+7e^yfy8W}=>0e<@QDYG#PB~LN6 zmhklVM?!=2!!p!;Y{cVmPWnGJpTot&L3wzi65 zl4Ao>Q)!BID;S4LUq`>Pwk{l{=LX&A%+%9eU zZGY8A&wd}7h zntkLN{e}cxS?#@y)SoVDI|(DB2V6SkkO=a}R4O-u@u)sRUd`ty27l)wMn>LTua^0B zPNmbeS_iRtt}-u^LH5`y8VuAGfIicNlqnj?N4I@zOLJ41eR zRO)sYrcrE=96#7SKVyYl^9_`o{8cY3Y%gU?OiYi$+-sqaAm{EXEGol4zs2j4Xc_CC zsacNTZf@l$YYJZLlGcO~sTYkCbC2UL-f5U#oF(ke0QZ0$EYX7B+5u5-Xu?h~YqjnS zBoGUI7(u?=hDnE2e`}WMH^WiZ96slDy7*Z`AF4{4WztS7ID@_T5bXP37$!j>p1?5B6UY{JGpNlQEK%R6cu#s%K!OMJaZ@|FZG;u-DSc?(_%N zB@`l#xlbR?X;VB6+=Ku9l?aF3`g7Y|PdkOt%&E7Df_`Fj-iy#;of`ZYBBC7^jQU>B ztIKku4vfabt)l$md<5bBrCrIGJ(PEfF{|7ZJ3l1yDyTLHP|wj3rtZP#OAlX{pO!Z4 zD=plV=9Px=4h4QHEIePnJejPuB0k-j|GVoTj=p%OVtVoLl;0zum5ae$Z8^$ME{pl* z%_n9N1i1|L8ZhW^P9{NC%)67Tg$}!#c6!A&4nk}C;5XoWeXR*n;b-Tl;F6SN?XT}+zJwwRDV+H=qR(IsdTs4AbA3eI) zaywq;kei2j^{e-w=d8IKpS8?$#cQFX`OqnKbF;0??}UT-;lsM^PZy&Q44Q=`c?_`k zf6@nde_@jqfs8HB-v7!(;3(y9T=nOmo9^XJ43nkZ+aClOp4jl1m>q}1g8ZIW*ZT!| zL$i~imzSQ;ZzoNZ-+p)I!-*Q8f}BL{4ZZl+Ai1P(9GmP@NHe7TZO)+k?WapM4gIiw zYQgr>!t*(MKcpvd6S(-=*C&6yThkJxr*@DRN$t2ws!_ZA>6#Z}Ak?fP_+%0+<2=^k z3`|z|-Xbc5PewO#=54ul$@vA}phUcx_w}$$?NOA-8ktYbIrWV0=`saDruh6jdv z5ovh8KyqFx_+GdBj{3h&5lc2pf6Bp!>=}}v&XH(!jezw$xjB0>G!c^LcS!s`IJh#~ zN*)IfuX9xV;vUB3CGu{C%g@+heH$f={nBNTn&39Dg`F%u+M^JP$F7g?iP%VMKJab@!RGokrNma2ZBwXv4a~)b|Muu&-Kb{ffWGAd51c$c;cD^d|2kzOq$*$jV~$KK>K3@z$mzJNkJ$es1O|Fssa}5m-3=9jdJ)O3xu$bTi zPICEjyOdg?d>wNB>mY5_2W$*hh(scrto`z=&&q3Qs+F?^cY19dor;sDGaj4YoTuzD zQCd~zM%!L{U#hD~!QBPR0H1pUbKSQBv$cyvPll{MsI7v1L5=W**vL?=M2j6_VPIhF zA4YH+x4*x)qsvGxpU4B{ZaHT}UIruiz%n_3vO)fElf3vh?^GK@1T_ssC!S4q95PS$ z^E*A&%H6tqOCVeL4xG{bc7sgnI%DC+?pqrdWmRVenPJb^LhI z@bIvI!tb*60#`=vIOcoYB?i$^QTVf!o+qyYZ}cH<-4%$uKa?B%7z#uWHyH*>WOtoD za_d#1;mJfVDWwQJ5EDypl3jUuaBy%wnyX+bm`g zMK;9Sgn(J|j!(xf-pv*SNP8T6bUjae5m%s+y9ms2=l0rJURH_z=L?=GNK|<2@zs}Z zTwqFfXcrfLdMt8p=n>tz7hYqVFdJ-nOPrRUyxwwwOUd z1(-gMkWgr4C2wDnD0P|bBqD^~(~?<<7=O_ZChNK2h-=ghhSRNf!QSnYSEP60B^6)s zcJs|!?;{0WpOf*Av=b}9L;m{pYqAaITm0=C3E1QihF)HEJ)frn%@-{3kM)FOx3`OE zy9v22+a*}(-`Aldz#b7u)q!;$yN&VVIXXIOw)l$D-QAuI(vs|)MofSE_ARrhD1ugD zC8WN--r;cNXT)9h9DdPwcz4U4Pq9G@9r#XZdN8?z-Ee|!lnG8*3E$Aq*QF?#D79VV zh1Uzdx6a?be>3b1Csigfn{)$Rdie0+jv1r4>YF!CUxe>|yX(TsjXSZyLyO?yqZrlg z38ARR8Wnkz8!Juz?Ys)Rg` z*+kNNqWSsx7r{qGdIACh8EI(@$Rexw?Gs^PVTY*zbSABkrb)-*Kd;_l2z#{Z!ec!l zv%6dLc60Tl4Q@Nq^wO%thb-TocZxbip3WI*>+3^h&K;B&^^`Vr(58p^NtxtI6%tOx5`RMgsw z+>~HjVsi43u`$)T^@75}rC^3bROy9I@cBn)C*IX0aEoX&tq|~qyrR56Owb)?nm{4L zD1XIltfP1ESbWrX?VnpBu?V^o419tFfh#Jvm6Y@!Wv7K~ZEp`REDYkkSb2GKb0g$^ zUOw8brX~gM{yA#AxZFNJEGi=;BKotX^RC^IGu$E{tWIYkO=~|y7$WcdmAz$e-pjGR z2nUDj*~yx`r6pTsRhkl8H`EE%tnubeRs-7`H#dP7FJCH%P6T8zrta?i@Hv}Xx2_iJ zca*PB(s}Q%$kIOhq^+X?qFcZ5)c5+Gs;cT@i-G4oNUFXCwW6IJ7h2mz=@1C;fB#mu zV_>AWw6&G;@@hB=tYu&j6r4CdN)s2jK8xVa%F04n?v8dpd2CDia5L>SHX(4q4?lY( zv&rT`X1CXw zENwf}+-Lt-SNo{u$uXICS%-URwK+HeG&1|jW?UK zc?VnZ4I!X!=80H#$jZ)sVl|QjZyC_BZ}g`Xaj$nVA9O4hhNcIAwxqHr7Z5mklb_op zISsz!-ZV97)|-aS%nt^j6U6;?<{C0)l=ZuUpogjKqPm-plrddxI-Pp`bk%&rm zt`$hB&L+1P-1nNm)6&+~?7E5C?4yqI%P@Z24XS6qAI^=1Z83X4txaMMS;vDkr8$Z7Q|6+Q>o?Er{0;}AJdmSm| zhCj{H`rQQ=f?W6B5s1VOwu15Wy~?D|zPY)(XMg<~ME$u3Ra;woXm0NBv14`})9l}I zJnDT}$BLpaw=&%oQ4`~o8&GmEb4F1~@$w6?0#-|~Z#6Y0tKp>jeuJKQ^XQTi9v;sG zIb$$CB_xQXk++ZLO{Zzq8KY0n%sc@PYuxUrcR%6&fNaQLHAx9$=w94JFR4~nNzH6P z@TDg+GxPb1PhL)rP#QTE?{sx(-n@C^eK>#r{{10k zgJ>Q5y3WtmqN9#GDSdKj{&ZOJC_)$vG>AWCs=#Gi9htwKuN_{uZ_LksbtdpVIhkrM zkbo#Dj}6<}+Sw@ zckhIvXr2W;b)VlP-xFtKTxy^G)v^2h`HA9+fSH+Dy$X+osp$t(IBB$|^Lm<)(+XC5 zM~7aWt6Rcy;w#+aC&b8kCqu0FUq{Z_$qW>0ZCKlPj}@rgY#ptvu5Olw&5w<}AI(#8 z?jVkA-yF>w9vjQ?`asb?HjvKax~1y!z50R0Z_YWB9}m;rX{euRM)~jF8SQH7uD*u2 zr(C2Qape5v;C8=)J+YgTM1wl7_5HDZ3!^i2NtsT4=Im1BZcwhtNhn1G0&&NMrn7n{ zy&C|WG`rAn)E!W6`06A@WHCR5)b_niY-zG07HGV#H1PXwHJbZ?le2GgSyWWi^6lFd zMG1bC$jHcz%}vM4cK~O|RY+Nz$S~^Yh$8G=vibXi+i=9gEX~~u^f?8^F$iq zHKF+XsB;$M7pstFfl>8}DTm&zaW?u6)SmYCb{?Ngm%M1567^5<@lM?gykGuB2IHH{ zU~MO88X2-4=VkPAGBTMD+bR@bPKc;w?0!RU)YQTJWlIX;{` z#k3bPGMMg!jI`iCA=j->c3(x@cP9<(ZJsp)bipB4G*w`T2XOy(OsphsGM7ZC=Fku~#956AAdgu1|h zmW^&jA!R9eM}ucOZKCcAw8}m_X1_@0D2pzyu7-z(TGKj1_!UNP=r0sszC;Dr(!}V9 zi2NU^#wIO0~W?WN-H0=@7# zT--x0?gXgUmb)TfI-6uKC}BLIs;H=V_4@UwZHL!rnC_J#@QUbf6H(V$3amkrSnIgl zcl*Ii@qFtf5^db?^XD`$F9>-hMH4y23r(uQKSPE}dJ z4Gi{<9YO4&tV}rMV_2d2F*Wr=u1Poerp+nE1?S5&|J@TFC0W_F18@o zIIqdC`*eHEhj9(1l+i-W2U5d^hK3M(m=T=;2g}{d=M^TeWMz$j5jK&niSrV#wbuC? zN8;gq40x-*1dygVFDnrdk>Tm-Uuj_h+BP;E!XhGRVs_QqF?_-1Q7)LWu|tzXi@zYP z7PQ@_U_>-)3xPjv)~2+y@h6g0iYp*Bg8r@!BNX`hl zViGWlaP+=CT5vcPL7f}tX(4Z=mk;uA6FAjSLwEI4NanwX3OnYqRP)(h=~Z2Up%6_WO4DamCo${mpV+NXE-U`AqdPiU*D^r4O0gkr)QrYrfk;V9 ziNH>mYi1A@rrO-x9B}8Cl#r4_7YPrLladPB6`cSVL!+Y1WzzP_DtR?t+?1tWrTL5L ze$;`>OnS645d|aN*#s+>YPF9#G(ogW?U0hu3WbQ^3>;Az8L)ah_nM2F5QRywbb@3G%@?V=|n@P`C~lY=KH1#tlS>NlQzs{%j;&%!jJKzki1f z+YWNJ7KVGzW@0?H{h}o|w#sm?GaaV}(hw1LF zA3S|}NGq+a70Qb)DhvlloEr&x4)UZE5nftej!8}Ji*kA}W@2L8#~+EVoY%t}@N_JY zcZiny`ZYwwsC;P_Y!-*}p)Z*!P`htroSKWr0^g4?MN|5_qOuUiRZ}j0#fJhqO!J@+23`dNXr##^=vdX;-s0HY{lh3x=B_%tOJU@eae2@cjibWLH3?bK>_HT4$kE zdTc^M-qky2XTJJ|hAB7+A^3rZ=}qOs+9SL9SMzJh_j)RpqP+&%ZchwP1jaL%tTy(d z2cOX`a6*Yi8z{jU@HmzY`Ku<+YJq72_r4@0*_3`mhq^B3OOWl3Q z#%q6&CG`&Y*}mXQF34t^Q?zF*X8rO+5?ZT+W;Fn;HZyKX&SrfCTb|mfYHDt{jFoj7 zw>A4*P0I4;s;R3Bmo$Ua0pPYGKO=YRuI!%QjAp!eB3S+%13`lO*|SwiC8vNL4gMU< z5XD9M+st3LS-0(y^JV(mR{MyQppcO4q9PoN!F1Q@F7v7fj~<1kNtp~x82Ck&mU87q zvjR|!_w%D97XV>p*5_hi+So*LnT0}j-x-Px;P~$NK*<;EBR|Uj^-iEYm~pBFdgpoN8n znJ3S)gK!kJjvK`(6JY}Rp$iEnw*ZrI^)oJ1>D8iO2hMl9InUvIcS%AXzgutlWd5+U zmnP~eD#YyU>|EGxVB3usX;e8kW`YEj_|2O>F1I+$Ot}~{;M?vxXdqpvLbm7I+VKY` zw>LfgKmYt?)*qRi3_Uz_vP=$6;em3%aQcqEYd^V4t*C2Bd$n92C!A_G2+ z5CUXLReX(am)H7huSy&G-d_?)#pj_Qy* zlp={Dn*s~R!#{l@aeGhDH_+dCux75Rrbe0O8c7U$qM|Z>ruF5@;v*`^A-`|=8b+W$ z13;F6$FnnE#w^IEgpBB|WU9O>Rs0I7R!o1VEl~!PIifWyNOI(KFw#bv(nuMJ;D|SB zq>f=>_09&kF&LQ-319g1EqoU+wRP@@)rm3N;ot~lz?xkS3q|?FfHkS1Ztmte87@&U z)a_mQ?6-g)xkN0yK_NwuiHRwYX&OA0*l8>Y5`F>O`L`@%aR4o0We_{lEE_Ods(%KV z_3V8-+33i0=E9i8P-*^GyYZ~Kpu*nd*7GtE&qXG&8+C)nS_EVQ{9_Z&gDi~LFDOTI zzZBF>$&aEz(oCL?b$bT+Qmn68xq}4-sRffiUmFn-`d0u_0g@@XPP|D!W_8o##5rop zW+RJP)SjhJ{1KOVA~6wq#ryYY-fwi@u6bVa%Y6SJYqC547#_3zzfgT#2+zEM#b zo*ZgzN(>FjxKD%q$g4#K6R!Vl-m;?Y$%Mo)8b?f(uhNYS(n`Zh7 z&R)s<<{1(tSs{bRszYk^1p6>C52m-vm>xfl^x`5S(qn%77_Y49R`^q!WOi;1a7OJF zG-ruCEc5v$rdg2I{eZ4RvtDI@a-C^2GCEnW7OG8FIOiqhszatVYd_oeT^|B+mKtH> zBL1N>z`t2c@vj^VRu7Kd+MZW@IJMrzJ%`mMfA}tZ4#Q&6Kp!ZB>KJ=LikmiCf1fDE z@%$bNK-2`CMQ^CU&7|6pG=DpiwWxvHp8UV!vtHZ)!1^V@%EF2*0ic#?WY42wh8he}pp&PX!CZdZP)zh1$29J#yFD(yGOjwNVw2)}| zrjpjzF@&(mszkQy#+Z3G+NLDw;D?5Wx=j6bL*nz`wc;#tBP4z_4D0UxyT9^Jd&xK# z^W7L3sh$F_0zR~Yp_e7C&0ITXQMSB(36G@%Ob1*9;6-N=U=76Ewew z5I9o=)$t02y5AnN?mEI?D@1$On@Z+uo_@m%iVasYkIn=?P;qM2lL#)j5T6M#G9HGe z*w^2C9nbZ2^w`zUbHUf~*W3XIE5K{*T)BETHZ~-@y`TSkFmYf&5#+*&Nwj;vZ^Zsj z3&2I)`oOO?cbA;BKAUNN)F#`C>h?yX#TZzBOm}B@_woW@R4kLt$Bsq)C>hp|u;&i8 zEgmCkPNqA%y0ZSR$nKhfUOojr6J+?dV>@;bY9HDiv9cjr+WlO3%7WNcagyRgFwO`- zpkAL&S@h4Q+~$g_ZmNst58dIl@-_)*=z2UX)%=d@8bYw#m&EriZgwP-O7TQ$b(2Y=}*s67Z>qJ}u7Rl}d+-JPfH z8D1W5lHXxhXbu5HyCZ~viI?|20JmpxfK~iZOceq+l3Qj;30{E1!MJ6cwG?1Be)aZ7 znzQz+nqJIh77o#ld$1rpo7|o}dGgM-1EWW{#f@L#rMx^Yl3pey?QDt-4VB;C+?14+ zy{DKVc2mMYFBi)^+hRuxKG`0B8imjtNPXUo!=8hu`9T=ww5U7HJEDEhUoun7=9*V% z0x)=MYinp>Av!0Ac`ot=43<*}4ls5Qp<|@}zHo z$lk}s#{T%}(~qWv(9ogj>2Lst_f+n@N&i4Ch>wnr{z_i{$GX?4;m_3s>V5`|k{%~J z35|^?d`Fa$-fqi{_5w|mH92&+GyK~Zho_L7A^Ti`Rj=!2HC6e`B=N*pOaut(!ceua$ zT%G>uiD4p?8a}pR4-Wuvgb_$yb;Z*{*?kJ4^~EOOqqixH`AcU*k(e>==|mzf9QO>}j=1p5m9{V%)&MKrRD^YlhvgM zIDVg)bm3wJAON_lLOp0)Aps`=AUt3UaAaj=xzfTPFf!gT_3}1fZgJ2EIY5$glklAt zMn3|pk%}ODz{@*wybS`!cY}fkP=WIypleWBpekAr_x~c<&39GBC0P1mh zlnLT7b#*fH!SsH6G@@aOZ+>^aG{P=DU!nHlSq!40v>>Ho(x-;So4pSTx={GMdw|>q z3mA*gCbwwu1GLhX-Fl*yC???XaNuFnsy-3W;y zq21B+-BGk6LsL_mBd&)aH{jIyjte-4?Bq~+5-IP|(ZJY|H`(kyEezq9qIzGwzPg@OHveGG-^Ja%LG`l^Cs|*LAc0D7lI>WWeAK%IpzAq&qV`3k z@JJpqxLNJxFei)*6!jP2#>U1U!MuS92EkrRbF-LB(r>UA6q#PbR?L%gzce<|eEs@0 zH~wjpGJ|mLxqh?vGw_wh+h!>^%=tA=;ql(<493u-eg5U@B&T>3kThgdSd((^3n%#`-p)Nt<~$bG z>}+g0$}RUpL?nvh(w? z0Ka)nMhsWp-r9Nrj$HuEZI3weGcyBD_~Mm<0@GfV8E{!e;UUU3G$X^9RVG z?_TU_{Puf(#Tkx`(>)d(79RcrI4|TLn4Z?v&pkQb9DOAxcX+;n0fXI|J2((}o$Ypl zY#C2YO;7tbKY#;)yofF?&QnSnwwP@@b1Y_`!w}vXz$Q>vBKFht!HN$v>_BsQD!&t{1`=D?j66GBWmtAGI~L8COu? zMR{q&tj^q2yda0X9kDqYQ&BQn{q@#m)2$1Pvs``QDW8&f($d|jTVC)^d)X6+Ubr4n<1j>_rEl0=J z-5w^DRK@*JFvv}b~Xm{{Z!A>%6$Oi9AVW(0&Nh81ZSE^5BM^}ng@8=ow8lZyV6 z>IgKk7-cc*vDO>K@Ybk;bgqfOu)3{LzcXxJRR#pYOe<=Y@{OsAYo?pL~ zVi2Jy?EtJ-*3&ETn#BR+GXPNl%oUhx(9X#C0lNw09?_m(d24XPiDAQ=hC(3kv+%uC z;nb^SwiM6@feGL(FaVx9JuUn;WoSs5Q~O(}41^Jz0)#GrxCtG7B6YOZ{}QwcD4f7T z17JqC7Tx$w(6OoH!rR$A()wkRHeyJ+`)}zOv!2Qi0O!q|ZTstX74!&y&_D;saDXvM zJ2~-2LtrGzifO_@qF$%ELN}#szk(iB1iJp*1kUc&I+xP+(<=jrZ`lXq{&=|FOZUco z@$G6Y*T2=0)NAX0N&Ucq3eGy90`1KgD=IKR$~73t$Jn?yB6@m)|6&npyILsdD$X{aL$Pf_Mr^a{T%(w8PXsf5C;dOcUlKg+|Qa= zc_tN~LA7}lefqtc^@Q%aEl$Y^=ROe;^t_G0*KpEQRh&XvGYPd78=`0t4mYI)5h(*7 zQ=m=^Xk$Jm7(>3dkDrRB=6-7)Q(d}P7D!NDxNlb{C8iJXfLzn5r>{pM`QZ4wd$fzg ztfgX|_{I*jVUBK}uY(BWms?@o^n%*1$89lUHNAPWw0YRODCO|70C5l@utlw~Ff!I> z=GBl7!{O3-a}y|&E7o6koq~4X`Rkla_oawu7%9SFE@41320kq0PnGC51mm}EMy@93 z{`nI^x1`Pl^1!YR0#Va%E2p6>_KusM8o*Njg?!&o+sBx|1c_uoa;NbV;lI9^2zT9r zF+Gq(D0#=}r*ukw!@?mV8h(}7^DE`KFCJ}c(*WHNKc3A*>1sS9BjZQ~Q0je9Y*^TK z8sC#Mfyb6&?rSt*>%&O^i){B8Ji)4}7Q_|~x^1s)dht?8ZKLRzE)_@wJ5oVfgc!Ge z$~W!vnPYI%I@51r3Ki@I07C{UAB`6 z=(&BKe<@oz2X+lE=voA478ac3B-tCY6(d`2uKb1j^R^dF0t8~`3^iC_y08*^hJwr$ z8&X79ZxL0P0J@0njHr_W^DJ%nbEy;PLtvEN@UQk`5}$HZA*{#j@3^`^8pZF#LS`eY zVp?bR1MJ106C3~R=dmQXgUwRu5;^?Wrrv*7$igp8pLzfv#5w%J1oM6^@VIShQ8pm6 z&hr-+3hj&_b9@cN8j-Lio76KBxi*%L=vpGwAF1rU#R$ITU= zeVD2;uUHQY4V`7{71FVHBRdbM+M<^DKF>E_ZI0)a4%&tSF1E zXJEj55cIpQ>c!x%fUFkJg3-6X8-jO_Q2_L#tVSBZP=1DN5k#H9@aDER!Z~&1H9${F zM!eeg^Vx!*fdQm|6AAo2CjdONE&C8{3-;l0`VLST8F%_!**ze}N_Nh#E%Y>%jX!y-Rhe=E;3O6_pZS$SnI*pHORE2<68(U#(=D4B{{T>X_X0PTx zyW#)`4iN00@_-JJ=j7&4MCzo7x#V1Z&6`bAWbYu_1yeB}$7d5i{OT-%_bQ9XNt$W) zsX}0 zemAug9RSZ3$eyq*2zjmRk!(aA9o4^_#EA%>tOD=SS2ay-dbD0vfzukK^E>tTNpkK3 z0fBhY*I+WGK%zUuuTUmCeW5cEobKd)$tR&g0-UtzAo4A6b7>5Iso}>gJ;AoYyP=z0Godwhqy@!n=OA+Mf8Xv=Tr-D}H^Y_2#=`E=8Y3f!kTZVc|Q!W;@gJ zj2wAKAPnY1DTi_)Ld?c8s8mGu3cyEG*`R}BJbWTE`LY8;5=y^qv_57G(g6tS(BFF* zSCNitW7zXCNPYi%5Wq5KW6YkGnIX??zFJYCrh!c$11b?duq520rrO{;K@olO!2jQ? zQ32B+?JwqRnK|e%Ew6?Y^PB>VGKN4t{W+zwS`*7?2*xFIQ#>H4XhdYB znG%?tx_U`BRik?n$S5TirttGaS>a>`l3GI{sF$XKxuplstXk;iI%IF1-?4FPpuwhF*Qn-7rkO<=AN5WHPFnn&*IT#rZ4_}4| zI8ZM>wbkbZ>|VGueK0<>kuNed)Uvp0!$ABW z(9{41-V?$siz2VO0cMoHYZJ4+6Gbb23RodbjJcQBuz~`kK21*w+{>Mkx09{5B z^?HM$sSlzyVRJt9Y}k2_4aIZt~wkC2^8LgqkRfDl`po+HFWdCwxga!i`<< z^oSrEUCm_go!5{HN=&TvmKd@zZ#lq$>~^ymV}u!0qD4O{2^@g8k>GrhcX~gSS5*)|zF7Yz)~r9VI5;l{H%E6Akj4`h9@f^{iWu!C)%s3R8ZtjK)B9hOd|GcQHWq*I z{;yN$r=YjPlkQ?euH3OT?k?qdNULc5A-Xa55>?MV=L!VOlmGUbLw@N%zU2%pF7Qq;okDB7mR#*SqRBVC+iz+Wso3zVT+X6BF_p0*m zzw?0_UvuD2yy;?=RLWluK3N0?qHj(l6`Hki>?jrg_$7ynPwMSwS|E32>rN@4s_43QC@u#xZetvS3GDU zB8s11VyMnnjK2XLLiq3=L{SLOMN-1uUGTr#fLo4a`TXop&%U#NX%8=CL!X)Z@gt-E zTws{vX}XK)xm4aP$H{G0aaO>8j&rqZ%IiS`cl}7I7k51;sGX>CM|SRqaYbqi=PqXo z_e$;JFJitSu36{?UWTicmVY7=KoF~ zNmm6t@Yz#Aj9Orbc703rkq{jDO)M4RoEdbW-7f<=6G$8B`>gXD z@yA}rX4u2c`_crr;3mSDzC7 zi00{6W0z}(iJ>x-MHh@DfIPWW*HqPjDR|Dz^@IqZXPye`Y?aqZ zc9|kC(omO?@Bc9lbwnK1&0d}8TMm*Nl>d9W71o8HGCY9NG&xlTAlM4`J^fQ5&;d;E zYIpA9FJC+r!WGc#vwz&Z+2faxwR+@*jFM61UcZjS_~G+xcJrOZ#E^#M`e`*t?~c=+ zb?5^Nb*c*8u*Aj3-+Edn9kcp37Z9ae^{C4r?qd;EX*fL<(sEu2DDfPi{Kny(Am zn%kc;bp)2#?%S}Cj!jxx)I>wD*u7Rqi)iNnO7N~T-ehHrR#U0?6cBzzwc7E%Qv=Lp z-#oIPfk2oUsjfNNZ=P+m<>#o~780EGt@y)#5u{x2FNQ58wj=T;%ruPp?3IOrv+lj- zE3W@S&%~awG|#+cR~s*QtT{^l@S__KTI3mtC$B4>QPAU5(o0&;R8BzEDE!^;@LmlH z@EL4x?^cNUCI(&i#Q6BeQ%0+YJBO4@8^K}DFXwf-^Q7MQwqV$K1H9|_?p0YosF(Wn zU!+~uc$y54i^f&#-ZpsY{@#-vK`fjHd53(TB?)++(*FWB3)HNORIv)mF#IFNEV$2ZP4xm&)}? z@BW0)JN1Gvvu?42K%Acn*L~9Et9@?Fa_232Ygt-#(#OP5Vg-d+VX#~*Ulo$ye?N6= zViCvKFHXXqGd`7)lKq9IT;?nX5p8M;{SdlH2719eG&2zb^2luLf9X`;^NdZE-6&QX zm($v({3O%@Z~uGBs3RD!d}|P2&{CjJ58EY#JT3l`3};UU%meS{XAOfODZks(lhWrI zou9ivuuZU7Cam_HMCwU>MpS4hTYf3NZDq?P@K@AVuO?#om+X!EcD7pbBJ_fMbKw*wGX)&oyZFSr)m zXlzF`aP(mJo=dmYnS!S+*ty7xhZ-7ScZ7brE%TPVlx&raKgUP^A|e8pxC+JuA@7E! zmI0js>bzEdgDA`iS^ozJk0zEf@T48{plFuOMwANFe!Kda<-&tv`}QxCvkT) z^;0h|=DZmhnvJN@-3+QAF_{JsQl%Y*kn8i*tAY9`KjGNXzpZSFbs{gWosx(UoPwX|kK)GF>^yPIs+rk2UB-LAwby~a52c}xVAb5}=gtt~sz5OVo zX@*fKZaamdiEFa~k7S`&%LGM?n3+>n=-lDw_FM&?k_rlVw}%NWnT3VnX_7E#QDGsr z-$kzhpf@`P947aFaS%$u%VevVRAhWH4EgR>K@VoCF|^(a7SFzi~)tDc02G=GqT> z-ozWuN1d&`uBxJDSy-<@*qQO{rqi&Rk)NLgaCi6WBPRiFGAMpWb8>RBe%bC=F9i@@ zH#g(0v4ZPUVz|V`_0b@=uKG7k&hnYRme$to06zlN$XHhInQFT$E&PtCcdoCU=c)%u zwVKsc#^Fs-owWAq$hwrM#+gQ2(2)zm8N7sO~pJ;yd5ktvLpJN$W(lbd^p zB>{te0g_Dv-;1xKRti9{59H7>2?>}*<5c`N_sNK$YU0H;?3LcVmIfBz0+$9dUc8en zi+s+5Ah^9=&zy7s;p+KfAi)q)DcuAkL<55JBV?o5jg7kQQBH6YprL*bu!ete?Jz6- z^~K;vOpFi7P0aOw`hq(3?6p7T_(F^Z=nU#j?}7Rx{K?4Q|HpwAdugwZu#1B56Q~QH z-zOYSR0kn0DE!TqlHmfg0VqNwKPo4OJ?UHb|Aj`EXAiV@agY6=`!*KQK=lCRSUKrL5c{(X@=9^tU%*%iTtv%ZUCm(*5^Y9^s?k%y^?^#i2(YD zS||=;6C!eQ3x0~-?r=`t$;E>I(*lTZM@=}-Na0G7v_SSh3@j^?pmS)H;)ddxfrMO3 zE3C=u%-F$U=(ud;XyRi$w?WkT`8zsjD4QXuf_VVSdr?tQH+FXTU3E#&px}38Z7&8U zCJ+nmj1Zvso%Rj-2FF(NW}6rBt5sjA0i1ng0qT&V|Jkl)ru$mj+S1bDLf;ACkf78i zD~l00&;1l77__9Ui~vb(Zf)`E?WYO?6*J(bKU|0b`MLD?MFWt9?3PYA1K9rEXg{Pm z`$z@%-oXJIJ10#Xbsk_vfwmdu>|C{J@DV^7T89A!V= z-y=R(V9Cl%`1lb~T+9i8*35>6nd6g_DvLo7IFKdhMkXz9ez%?3KkVgxIm8X|V+b`4 zYAAG$R^5m)oM=+k{~xt;g7M7vS_p7Emw$h*}=T{{b+EhI{i( zpvM2hYtjawW$W$j0NlgP<+eX`32Ym{>k*ME0zQmF+_!PTC-;Bc6JGF@I}l{M7NgapNw{05 zZkILS>nwN;+6x3^u>mfOFZIvgH-&}9Ykf&QKY#wIi3_zvD=aE<++UPT=wf>m9)SFF z0pj8dUroN}l{BBId!G->NcXps5EOH7SESs)WT(*#BlSPePfu@=Pr&|<3&pen{vsj(Um9_F|?EXz6qB zw=NG%V`YT`JQUG2OX9)P2VEp|VM!)}5m|V8I`kK+>2EqHi~>r=ADHiG%Pp{2Yrtj%15pCz ztnM)&x7Y*($bieDprL^QBDi3w7sv$S&Bn&ZjXT*V6P z4FsTuWl$cKGP#e3 zrV_<`>SY!({zs?}5U^M74C^#J_k>O!{7Hrsj~9)!T&z(?t8D_V^dI@Gt*x6}`jnFs z7l)kd2UtA9`}f=JNr+;i-@kv4lobFD#?rtgPny_pC2ppg^BPrz5jmKz~bU zMoKE(Pj?@7@G-B@x$NIYNyGn5tmf(GCysQk^Ygn2EgK+wF}>dWQ1o+a_U~w;XA{s; zd=*5g9o13uR8u2G$|HctWyGaKC0D+UR72Mv$Oe5)0NkMp#8;J-mH##}Zwa!(5BNk- zf&@61CapAahR`KRCG~avBCyW$NQH+S{ol~D3ITQiwn%p;xIa#!nVK)?snjThmCeFL zbL3j|%H-^yc{st&zPh`*8m$kcfkTw{Mi98R2M-+xw>!Yb;pDd7lL0xgMX! zr$-hR7ObF7S|A1ip?n)CZ_v~KeH_kFD)Hvc{WEW% zAdLfm!UIT(GHYrkzM5%YU0y2d=-@QW*;&gSV*W09spr$Qpt`P)wCs6Y1_dx2Ax{#O zJbB8~0}0)<5P&2PsuWq!iMruZs^n3rt@np>dbwUG*k?FlBGe#azinzDrZ=Fd4WnD z6y3@uV5%6nO_^!Bt$qet7=CMCN<DPd*3BSpAa`~~H%pa{hF za_#P@_y4KstK*{TzOQLULO?}85D5Y4kWN8b0i|mw32CIe1Vjv^8$?_A z)6ItimyBPGVirt&?HSTFIQJNJd|9!k7(@R*2aPpwp(jIv3b-$mMlf4np`4Fl16M(( z_F<{fD`+C370Mu(jjS$q%hFzc_<7Gs_C^I(kLL(#bKL+~cT8^MU(N66-M|M3Pamif zb8~YwHo*3L)WmS_@P0#bgn8H?tt^K=a7*z za=7dBXC_8QZ18Zi<@u2N-h{m{TJOaRqvfe&9@X#KehwH$9aDP! zXrp$iqn{GtO%qQVLA956RrG6I7(q~__CI#WzSNYJ5&4f(LLm(KT2%$?naEF(m#5!} ztqH!LYsM^CnCdJ^mkjV0jp$&hzbc2&iOR$qThGLY1;ZY1goH>Eg+g5oI-Qu=YTOLy zUltZpi?J#$ohj|dof=fJ$8IP=^YHNeyR7tD{Pr~6>wBPeejbGg;tJzka%BOWyLjTy zj0n<41*n}Z{Fy@EIa-X~)v=dY<9#8VFq%+$)6c6}EziHL?+;Sqfy2wPZ~tv$hmn#a z5KQDaFH{h3@vha0hPA!%&2N?&c12iQ`?~jS`+dImbk$2ys@dl>-~_#?FqLv&vlUO4 zJWQI|sfRi>HPxT;RQUG!(z!3{JW$D8+E?Z|`s=F@eT@Yk)27E^`4~1=k$ETsLauao z?h$XjP8$$2W56shQ3NjmBVFewE-A<6M%mvy-QIiCeD1-OdRUEJ z5q!DN7zi!^7y_}k*b$)&N4>y-fO=TfPT~1li!PG=Vc4`#StRx(W9S)gx&&n>g5*l(tVyP}*<{?#Ff1_8Yiz zeyc3Vj^Ow1tFo~g*JAe<43 z`-3U3M{1NXi46slZcBzSVl7f37ll%VVNTMEe8F<2RwB;hZ99~lxmB34h*%y+Q94Y2 z?NS|-YeDQOZDimfZ7LSsQP}ib9=Jf5P?M7j9X5t}{__Y3CP44}`pdaVQExTd?8HHq z#ocXKB6Y~;3iW!)I*`IdJ?6rOP5yUrY-}8)D{o6Jxa%|@hHg~g|6bmNT~nP zNyVEu&IvP)wRJQHUG~HJ?BcENYELM>?$!$y>{`vTn~Gr3ia37S>XXQ0B@Sf>YMEzGG)70#`~E)o%v*#;qpx4T&MX9cOhyvh=g%30g-4=#INkG{ zh(`KpdT7L3ls|sc8!LPLI=!&)gCRE}lsw{YLB4BU-MH!?^+$ev#Y$6+0aS|xK?B8| zwFcaD6e|DuH=Ls1FhS-WXZXbb{q#5C>y|7GpJw+!7DWe>1tcK%r~Up5ybLIs!d&$I z^Y3DV^JzcbsK7$b{il50bfFDow=bMHjJU~{)(D_yHTKGN)*rr@*UcDZP7M}1CN|b0 zZ>B+A&(6Kzk^C|?g9iil*0HhGY)gpXZ}eXm(cBTt$QgK&-Yk#m0|l?-h6bKpC~}c} zK6i$C1aL$9gY60Oi z==SuFJJcbG&n-b?nG`9I?)de4U&rQZn4Nx$VEU?P?=3XJ&fnLL!{{&)Qec|1q7Q@ z(@DBV_7rKA)%;#@nUyP)v?DXfPSdQ$9tsX<`dK&SwAz;*lQ4f0*)M&%-rd9vTcGf1 z4rDIRT7t2!(J?-^wYSfysYyPuCz!lS%@+)7Pub;!=_zJ0NKdbtEpdI+*87R56b*AX z{4AC+gFn5c!^0|^SW%$Stuxr2F8U(${PZwr!8Q`L_BdTM=IvXOlmG@iN|^nZgk-M3 zJ1reXw(>#nD&E!Ab*<2ZHE>=3*6Lcxjq^;@aOp##s|QamVtaX=N=jWx_xI<2{5Uje z=OY1p*?F`Q$T+})%qc2T!C}FpR8v!f^m9Ezz14k zcTznM{D`yVtCNtJxHE8e?CP<14H%6k(QJ3`0-=~QM0d`9 zplszQZZ>`N2dX-;2NoQ&9-5y3e(FsXI@>#5THshQhZtNm@T{iuFANI06tBdS#{&y( zZbx8$1+><5m*j~9w}zIoY~U-p1cKGrTHIF?iS6W?T7PZ0nxh4&zFga?>nxf2W44ya zovWU%yf|Fs+P+7QNx*$*)KU`@75)7i@Kh0fio<_eHaz{-1UOG* zi-}=`@utEHS@-PkwT0eWKyj`W(wlYmWk}Y!WdB~Vtq9i27X2jU026l`S>)JMzNcO^ zqx+;~JBo;A;FsFLzp1qs^K}y&tP&DNp?AaZAls>#a*8%Mn{M=DQJ!kyBYCfy=1cy~q(v#F)XIXadoH@=~h37lNZIe@~LfakhC%-^-QwIVs^VeUVCpO9e})g+C(WxeuT{~b?HPWAxHd*Np}u&;?B*)*v* zgdSrUV=QnXynDX`7zz1v%mV|;Jdjwi!1R8DOpl}BR@r1MQ#rb zxl6sx${%m7El)P}zyD#b_&;-M%$_V7%J0%y3FyJgWO2t^@Zh?HECpD-zR6zi##@98TICFA%~?Rn&?8Nk>JnB}t+-4TFKZ<4KDs0(RRepy0NlAk0!N`P0wd%$knIn}X>LGJM0 zesqX}ZXQqcj0A+xrD(BFU~))q2`b~Tgi*dEAl<3E0|qgPa{h6?de*<_9;y__O86T4 zK4r^cNxYQnyz(enyhPB2WPwK3f%haI31q0b7FWzwbafNaAxJp7*idO}kj=SkJ9OsL za}iMS?xy^5V%`P8RyvGT>eh(XxWAfKcTdmPy6)p02)>_elHd@pt^g9uur zY^r)g5mU}ZmxNo-IIUo9D66Vpt*2Z7>f(Zj?Ie`(pa(8b;EK5#NRt2~l|I@O7BjRq zgzG$x*SyQZ<_1YqG4FBN7%MT;9?zrmnUJ7u9hBR4%8To)F57eMU&aQkE-%jME1Z+M z6--PrwV7nGpuNkb)|kHH2rTt01v*%5z{7e=D>J?Ni9bW&(yK^;X)p7`NwY!MFlp<@ zqN3MrK%W@R>qRSG2dob;RaN|Jcn}*TKFv||k({p0ep=Yw00p_W+`Fl%X$p%>dx5>f z-1|isrzW~DNtc$3VcMl&)ez%20=xhQ= ziukR|c#6`&o@TR@Uem}5%2cA_*;87aiT8oYYDzrm)Hgnbhcc*gpdstcy0e#ciW$dg zGXd$XHgG9kbj_5m^9t&-NwQ)Q&Bk6kx%p->}fgnq1E#2Vj*@sEP)z4le33D@r zR)Sj7^|rMX+6Y-7@cxJ9K}ZFcdQ6kiiINZBw{qRtHK=#GotormS<0+6VojBOC_)%= zC3r&bTi+W&j5EGLcE7&Mh708jg~>9 zOUs&mh`mdiFxg(tDY_Y`y|<1l(NO=lcMY&LHEX(u+f zRqbh`N{)NdInYms&8m_4*wQjrn?QE!CBS#^aedpHuVnXP`m>IuddBLDmUU85+o}{b z%uD*Ha{B=~L6yMMz~|kfd-=!0$o5BxczZU*#l<;gWrKfTylR#8^{vy!kzK|Cy#o^# z(7{kfB9RY*BHA^iyFx!P1raoF2u!FV$tuoy<(Fmedi1Wy>#g2w`8Ds>yJC5~vtYR} zKkHhHQ*JsnVY1{@owmO2aJ++`O$&`Ie#VMpPSu3-g2JtQu;<{)FaGUBL6#Tq!FO02 zv70s_q-O7XxaZx(>DTvp*K7UviI3P|SsBE&_`^$Lp6kizynC-mX%#IjKKXBl4v>Px z3Jv(C&cAx4CJO-TVSDs}t(NcXa4l?@T%8pOw&|&QYRMxWE9ZmlW9vAm{u2_cu7fXPa-(wNc7!tMs0(Yk4qdVd8q2iRIq%;9EMeUw>af zET3&+Eba@LDD-Q)Li1Hney&ccqhXQ%OQt=S?q8&{p(t&RFTb)J&dWOKb6gG_CY`*Z zdE|$tr`3IYYM0y+!JiJ;c&Bv`V326&V)9$xq=^Wb*vN-#>HXIEgDQ3stc^TnPr4PB z|8VU0Wkuucay^m~h?Q}iQXT#%>o76xR!{nGsvf=epc*>s39i!JGfijM!v?ipfaBUR z+wB`fi*R4ZiKdJ~*_TH{BPGu-HoE!x9a?jnjl+17{qgSNBvLsFlf0a;>79#FcvE-Y znU0rVw zQvc^-Mqc6umA*p!FJ7!qRF`%K&7cJO$~( z(DQ({i|jGo-^#i?a#WJaQ4S=UHXss6XE<>$@+#t%V5jsMiM))=&@x~P~D=gQ!hok?;cCnT^m1zNzoSpBuuH>6MtM z$!p)ZQ(_v_Dxill-mYWGv0Mrz7sV`JB=a2DAmdusg@{gR!{*G$!a~$uLiIq|lb-vr z32z3%Gjp`{>=0Wa&AjP=b=`Mv&dATtZw4_pDZd7Ftw+$HxH8kui;&}m=7@Iic~bOV z|MQben}O)j(H}h%&Nk3@iad_9?-5qI8AXvzER|kDyT}kv7$O&BQmml3lZ33N7IVGJ z+q8EL3ySH(zkMTXhy&uc3 zVd138CO(fV2|{sN5sn^+ly*Y{9q^jUyy;G zweyqkyiBqR)X}qxKn|$`Ucx&;E-T>9=u++h+J-ki;LvR^?HkJDVU)@s{k$C>u656H2SNX#Nf0XC#vHl z&2h-6C&WUpT-|3Eap0Mu5&dCwAJ{O=Z(s z1QOv{yQV5d^cd%7J<~z;bYL39?}1zE6{hb|lz7=mCbVN*>c=9l;LaP#z55_jt}a%^3pC zkPRf|>1Gly8xPNW0J=56Rmkg*H-MCUU&HAp=I9Ks>GbUwXF^Snyqj2hcgvaFWy?K+ zRgpz0Exj$-sHlKewyY1Cnd;wqQv6TGY_6Hf)fm^!%V+> zPGo&|T5n~6$(Ih3*tHcSHI1~$^rK=IAn`MGf7v!Nl4XBA^dgKqsk#_zSs z<=!nWS_ke;x{W#rXZkMEKm}WHL2=xsw1#$5GG_$wgR`B5q``FcplX4}m>2*vlQggJV~J z5}48bv#)+rnt+79r<}@ke)V_pwdah5wZ8it-6sAq;0BYuW&)-RGArW@XJsjB8RV#Z zwbt$3^X5YQLRyhG{p9iLP`lszgWeCVSVBF!q1pN!o*{xmxA{`qZLzO?#eD~JvU_3m zX9PRimAVGq&>>A8LyR#=ZA~V6c0?|mUrLHM!7vV_t6$Vu5yP%99}>(w|3g+J`9dy~ z8}DucoBGx|Sx}_fP%^}CL6hb9y(?{tmpAj#<&-tQ8}diq{J^KE8@OX}`35!2HOtzg z^b*-OE<~y} z5IAEFT`aK4WLH&H)d&2|;KKdXW1lZ4&$EXod?jSS=(+mRwb@IY9KX5qNhgti2cAUM zJ!YUp>hHCpNq6-X-U$IDEWuD`q@{nPqQVFwQ=#s-#ynKb7aPt#hf`i-WdRZgk&*x2 zK55Q0oIUPye2kB=Ny$eUVl@R=ZowMW9K~Yij$SYLHKfi3Q}bgI;#y%$s@)6S*BrAS zeKl_C!F<$JznYrAJv^jQF=2d`_sa4D`-zzeF_RnSUWJ$#6JCJC7V|8=IDPLSBEo#O z&NqSo^=mGl9hHBzOY}RgZY&RzNT!(m4*ppIhwA(t;TyJtDfRncj0(tZ&@dYWo}gWZf4olS26Wr%|U^RMTXd3Wv~} z*jq`@uEH3yWF-07kMZ71D(u6dviJU_pVq`{j7jJO!kjN!WjFH{ zVZMejNdu>@&4kIgT=-0?@c#waykjS$_aU?c_2y$>NtE*Uw3tL zrIz&O16-d~m^I?X-wybh5=Do{j~@^KVLHy^FQK) zY-iRrfZee^AO9!A!`t3z?@7ZijLKawBl*U5$lu~`#^Yl1F|%S~qhrJKQY|7wl#Ya6 zG8wQ4KD*(2b4tQiK>w>7Hl7jU+2O@OFuXr+J@uys~Y1un8q^&J{ymr+S?*@+rKkgPBKcBF!0ImLJu8EoRbE%%o zfwJl&wmQvM3bUAH^Zn7*GqBw%)~t7!DTs(}EstuPA+};r!?qMPVBPU3&Sq2|xQW}~ z8o2MZQsLm=-%cM}eX)R3C71alEipuCYH#$GJdgVvU;dedI6ms%wouPex|>3bWuYsbr?wns^`MT&&xr~B;N zbC~_%@#;xTK=TJ2Of#B&KDjI;)esi=9tYtzv}wc%h~SdR_R||^sDZz z&4!0%F^oA^!~Sjf6|;BGLHEAZX@6(u<6 z=2sM3Q6dF7L_v{;%%@H8c>gfnXbCjh`-c^n-flSi{A#O=a& zB|a7#^7!9T0Vc0|y0zP_;uFurm2Q+^6X$B;TVwf#{p$AUy7l$p=fw{fykbk-{F7I9 z+wsh=E3EcC{Q3Cfo*B&xn1c=N^!7*b9A2HZ|5wD^zol<-YHI6k%AXoqGx%KD|8MQP m`)>w3Gmkoe+97bD{xyF=XQ-dowTRUqvprq?T-G@yGywpx4q31O literal 0 HcmV?d00001 diff --git a/UI/frontend-plugins/frontend-tools/data/scripts/clock-source/minute.png b/UI/frontend-plugins/frontend-tools/data/scripts/clock-source/minute.png new file mode 100644 index 0000000000000000000000000000000000000000..aa012fb703d480f783fa4c642d4e35224f276b47 GIT binary patch literal 1692 zcmeAS@N?(olHy`uVBq!ia0y~yVEh8Y9Be?5)7S2I0V%d5Z+91l{~)+v@AAn&5zYdS z$YKTtZeb8+WSBKa0w~B{;_2(k{)kbSUq$J-gW7bUMomu_$B>F!Z*MNlnp!Ax>|^@u z45LbBZ7mJ8^#^q&YnsXvHtA(e+BPR)q-`ebtQugdQIb-$)PDV_8@*Z-JA zX12kMBilF=thP_C@h$q`erEEMq)FDEea9u-bN8-oHZMt7nExz%Qcc{)C#kpDr!hOS zPZR&TSf%`Y!KT?y7W=>7@UzT^;qs9h^^*@J&u_ae7tL=r|Nn(Vx$5jYZ)Wqd$VN<) z&b||4F3n-JL1(%zLlYkZhZTdu3xCyBG1~LqHd85|1M1~7w4Y_} zp8w=#yxi{zPokm?*@3xl#oPBQ>`$H7FZe#m#_yBL$-^qo>Hb@w$Z}5?$B>F!Z}0kN$AropfB0Ty zvR_x&1q~5T=?P8sTrW@g7Ow67#imnIsHb!^wJi20Q;bNA=7~G8waldptG$oRePFNQ zbSr=>O50a;?irKEXHV*7&xpzNZ|zoln)Vt@DWO| z@DW;wHiA+uK>d^&L7ZQSvjr4~tj^b;`@8RGlftwQw}wr7|2Bu$YOc1Y-8!$GA7A`D@aB_p`HYVf z`scj7_BGsd+oTw&607XD_u|BU|JC7Rise5Zl~yQIotqx4E6S{yhCF zgL+BAspX%vJ?}{#o-{vn>74Q?c1QMQ)kgO7p6pgR-&^?d{-im6MbTFrFEpM!wrA>- zlu7fAXU5Ndt6r&jvUk;#g|a~9YQ;wOv!3KlI>+Mvb&}2Oliz|Wgmi#U7@N>o{zFzye`=_U5o=TqXR?O(ge(cF# z%h}w2*xwbk-?W&kuTojj5q~Ht^whp^ra2cHn=6ygJ!b}LDyV|c8;qWR2I{)M`kwZI z*(@JYc9?_s?QTB`q4Jd{KJWh~1hkJmWa_8tNzv^l|H48~#ja=KuriRFf97V4!-a(t z?dELW*|5O*bI8&+YCCP7Zk+B8cFMx&>npu(dTcUYI?*~*Q~K_%Z9ff|{}hYO*tkNy z((>|8<4xg{{)9|TdJGI5&ep#%lU57Md@Kt+^}I}qMRrEbmHR%Qj}fWM=ihUwW)P zByQ54*-zf=ulTW5Wq#Pw9lf$r3v#&^1io@_e0S~ZYtK0EBAb&JRd##kssG%k^>p^d k= 10 then + obs.remove_current_callback() + end + else + local source = obs.obs_get_source_by_name(source_name) + if source ~= nil then + local settings = obs.obs_data_create() + obs.obs_data_set_string(settings, "local_file", path) + obs.obs_data_set_bool(settings, "is_local_file", true) + obs.obs_data_set_bool(settings, "close_when_inactive", true) + obs.obs_data_set_bool(settings, "restart_on_activate", true) + + -- updating will automatically cause the source to + -- refresh if the source is currently active, otherwise + -- the source will play whenever its scene is activated + obs.obs_source_update(source, settings) + + obs.obs_data_release(settings) + obs.obs_source_release(source) + end + + obs.remove_current_callback() + end +end + +-- The "Instant Replay" hotkey callback +function instant_replay(pressed) + if not pressed then + return + end + + local replay_buffer = obs.obs_frontend_get_replay_buffer_output() + if replay_buffer ~= nil then + -- Call the procedure of the replay buffer named "get_last_replay" to + -- get the last replay created by the replay buffer + local ph = obs.obs_output_get_proc_handler(replay_buffer) + obs.proc_handler_call(ph, "save", nil) + + -- Set a 1-second timer to attempt playback every 1 second + -- until the replay is available + if obs.obs_output_active(replay_buffer) then + attempts = 0 + obs.timer_add(try_play, 1000) + else + obs.script_log(obs.LOG_WARNING, "Tried to save an instant replay, but the replay buffer is not active!") + end + + obs.obs_output_release(replay_buffer) + else + obs.script_log(obs.LOG_WARNING, "Tried to save an instant replay, but found no active replay buffer!") + end +end + +---------------------------------------------------------- + +-- A function named script_update will be called when settings are changed +function script_update(settings) + source_name = obs.obs_data_get_string(settings, "source") +end + +-- A function named script_description returns the description shown to +-- the user +function script_description() + return "When the \"Instant Replay\" hotkey is triggered, saves a replay with the replay buffer, and then plays it in a media source as soon as the replay is ready. Requires an active replay buffer.\n\nMade by Jim" +end + +-- A function named script_properties defines the properties that the user +-- can change for the entire script module itself +function script_properties() + props = obs.obs_properties_create() + + local p = obs.obs_properties_add_list(props, "source", "Media Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING) + local sources = obs.obs_enum_sources() + if sources ~= nil then + for _, source in ipairs(sources) do + source_id = obs.obs_source_get_id(source) + if source_id == "ffmpeg_source" then + local name = obs.obs_source_get_name(source) + obs.obs_property_list_add_string(p, name, name) + end + end + end + obs.source_list_release(sources) + + return props +end + +-- A function named script_load will be called on startup +function script_load(settings) + hotkey_id = obs.obs_hotkey_register_frontend("instant_replay.trigger", "Instant Replay", instant_replay) + local hotkey_save_array = obs.obs_data_get_array(settings, "instant_replay.trigger") + obs.obs_hotkey_load(hotkey_id, hotkey_save_array) + obs.obs_data_array_release(hotkey_save_array) +end + +-- A function named script_save will be called when the script is saved +-- +-- NOTE: This function is usually used for saving extra data (such as in this +-- case, a hotkey's save data). Settings set via the properties are saved +-- automatically. +function script_save(settings) + local hotkey_save_array = obs.obs_hotkey_save(hotkey_id) + obs.obs_data_set_array(settings, "instant_replay.trigger", hotkey_save_array) + obs.obs_data_array_release(hotkey_save_array) +end diff --git a/UI/frontend-plugins/frontend-tools/data/scripts/url-text.py b/UI/frontend-plugins/frontend-tools/data/scripts/url-text.py new file mode 100644 index 0000000..256bcb0 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/scripts/url-text.py @@ -0,0 +1,77 @@ +import obspython as obs +import urllib.request +import urllib.error + +url = "" +interval = 30 +source_name = "" + +# ------------------------------------------------------------ + +def update_text(): + global url + global interval + global source_name + + source = obs.obs_get_source_by_name(source_name) + if source is not None: + try: + with urllib.request.urlopen(url) as response: + data = response.read() + text = data.decode('utf-8') + + settings = obs.obs_data_create() + obs.obs_data_set_string(settings, "text", text) + obs.obs_source_update(source, settings) + obs.obs_data_release(settings) + + except urllib.error.URLError as err: + obs.script_log(obs.LOG_WARNING, "Error opening URL '" + url + "': " + err.reason) + obs.remove_current_callback() + + obs.obs_source_release(source) + +def refresh_pressed(props, prop): + update_text() + +# ------------------------------------------------------------ + +def script_description(): + return "Updates a text source to the text retrieved from a URL at every specified interval.\n\nBy Jim" + +def script_update(settings): + global url + global interval + global source_name + + url = obs.obs_data_get_string(settings, "url") + interval = obs.obs_data_get_int(settings, "interval") + source_name = obs.obs_data_get_string(settings, "source") + + obs.timer_remove(update_text) + + if url != "" and source_name != "": + obs.timer_add(update_text, interval * 1000) + +def script_defaults(settings): + obs.obs_data_set_default_int(settings, "interval", 30) + +def script_properties(): + props = obs.obs_properties_create() + + obs.obs_properties_add_text(props, "url", "URL", obs.OBS_TEXT_DEFAULT) + obs.obs_properties_add_int(props, "interval", "Update Interval (seconds)", 5, 3600, 1) + + p = obs.obs_properties_add_list(props, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING) + sources = obs.obs_enum_sources() + if sources is not None: + for source in sources: + source_id = obs.obs_source_get_id(source) + if source_id == "text_gdiplus" or source_id == "text_ft2_source": + name = obs.obs_source_get_name(source) + obs.obs_property_list_add_string(p, name, name) + + obs.source_list_release(sources) + + obs.obs_properties_add_button(props, "button", "Refresh", refresh_pressed) + return props diff --git a/UI/frontend-plugins/frontend-tools/forms/scripts.ui b/UI/frontend-plugins/frontend-tools/forms/scripts.ui new file mode 100644 index 0000000..cd36cd7 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/forms/scripts.ui @@ -0,0 +1,266 @@ + + + ScriptsTool + + + + 0 + 0 + 775 + 492 + + + + Scripts + + + + + + 0 + + + + Scripts + + + + + + + + LoadedScripts + + + scripts + + + + + + + true + + + + + + + + + + 22 + 22 + + + + AddScripts + + + AddScripts + + + + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + RemoveScripts + + + RemoveScripts + + + + + + true + + + removeIconSmall + + + + + + + + 22 + 22 + + + + ReloadScripts + + + ReloadScripts + + + + + + true + + + refreshIconSmall + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + ScriptLogWindow + + + + + + + + + + + + + + 0 + 0 + + + + Description + + + + + + + + + + true + + + 12 + + + + + + + + + + PythonSettings + + + + + + + + + pythonPath + + + + + + + + + true + + + + + + + PythonSettings.BrowsePythonPath + + + Browse + + + + + + + + + Qt::Vertical + + + + 510 + 306 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + + + + + + + tabWidget + close + pythonPath + pythonPathBrowse + + + + diff --git a/UI/frontend-plugins/frontend-tools/frontend-tools-config.h.in b/UI/frontend-plugins/frontend-tools/frontend-tools-config.h.in index 3389a42..8bc3af9 100644 --- a/UI/frontend-plugins/frontend-tools/frontend-tools-config.h.in +++ b/UI/frontend-plugins/frontend-tools/frontend-tools-config.h.in @@ -1,3 +1,22 @@ #pragma once +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef ON +#define ON 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef OFF +#define OFF 0 +#endif + #define BUILD_CAPTIONS @BUILD_CAPTIONS@ +#define ENABLE_SCRIPTING @SCRIPTING_ENABLED@ +#define COMPILE_LUA @COMPILE_LUA@ +#define COMPILE_PYTHON @COMPILE_PYTHON@ diff --git a/UI/frontend-plugins/frontend-tools/frontend-tools.c b/UI/frontend-plugins/frontend-tools/frontend-tools.c index 81ee4b8..78e199d 100644 --- a/UI/frontend-plugins/frontend-tools/frontend-tools.c +++ b/UI/frontend-plugins/frontend-tools/frontend-tools.c @@ -15,6 +15,11 @@ void FreeCaptions(); void InitOutputTimer(); void FreeOutputTimer(); +#if ENABLE_SCRIPTING +void InitScripts(); +void FreeScripts(); +#endif + bool obs_module_load(void) { #if defined(_WIN32) && BUILD_CAPTIONS @@ -22,6 +27,9 @@ bool obs_module_load(void) #endif InitSceneSwitcher(); InitOutputTimer(); +#if ENABLE_SCRIPTING + InitScripts(); +#endif return true; } @@ -32,4 +40,7 @@ void obs_module_unload(void) #endif FreeSceneSwitcher(); FreeOutputTimer(); +#if ENABLE_SCRIPTING + FreeScripts(); +#endif } diff --git a/UI/frontend-plugins/frontend-tools/output-timer.cpp b/UI/frontend-plugins/frontend-tools/output-timer.cpp index 155c8d2..f6de097 100644 --- a/UI/frontend-plugins/frontend-tools/output-timer.cpp +++ b/UI/frontend-plugins/frontend-tools/output-timer.cpp @@ -40,11 +40,13 @@ void OutputTimer::closeEvent(QCloseEvent*) void OutputTimer::StreamingTimerButton() { if (!obs_frontend_streaming_active()) { + blog(LOG_INFO, "Starting stream due to OutputTimer"); obs_frontend_streaming_start(); } else if (streamingAlreadyActive) { StreamTimerStart(); streamingAlreadyActive = false; } else if (obs_frontend_streaming_active()) { + blog(LOG_INFO, "Stopping stream due to OutputTimer"); obs_frontend_streaming_stop(); } } @@ -52,11 +54,13 @@ void OutputTimer::StreamingTimerButton() void OutputTimer::RecordingTimerButton() { if (!obs_frontend_recording_active()) { + blog(LOG_INFO, "Starting recording due to OutputTimer"); obs_frontend_recording_start(); } else if (recordingAlreadyActive) { RecordTimerStart(); recordingAlreadyActive = false; } else if (obs_frontend_recording_active()) { + blog(LOG_INFO, "Stopping recording due to OutputTimer"); obs_frontend_recording_stop(); } } @@ -204,11 +208,13 @@ void OutputTimer::ShowHideDialog() void OutputTimer::EventStopStreaming() { + blog(LOG_INFO, "Stopping stream due to OutputTimer timeout"); obs_frontend_streaming_stop(); } void OutputTimer::EventStopRecording() { + blog(LOG_INFO, "Stopping recording due to OutputTimer timeout"); obs_frontend_recording_stop(); } diff --git a/UI/frontend-plugins/frontend-tools/scripts.cpp b/UI/frontend-plugins/frontend-tools/scripts.cpp new file mode 100644 index 0000000..d923dcf --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/scripts.cpp @@ -0,0 +1,552 @@ +#include "scripts.hpp" +#include "frontend-tools-config.h" +#include "../../properties-view.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "ui_scripts.h" + +#if COMPILE_PYTHON && (defined(_WIN32) || defined(__APPLE__)) +#define PYTHON_UI 1 +#else +#define PYTHON_UI 0 +#endif + +#if ARCH_BITS == 64 +#define ARCH_NAME "64bit" +#else +#define ARCH_NAME "32bit" +#endif + +#define PYTHONPATH_LABEL_TEXT "PythonSettings.PythonInstallPath" ARCH_NAME + +/* ----------------------------------------------------------------- */ + +using OBSScript = OBSObj; + +struct ScriptData { + std::vector scripts; + + inline obs_script_t *FindScript(const char *path) + { + for (OBSScript &script : scripts) { + const char *script_path = obs_script_get_path(script); + if (strcmp(script_path, path) == 0) { + return script; + } + } + + return nullptr; + } + + bool ScriptOpened(const char *path) + { + for (OBSScript &script : scripts) { + const char *script_path = obs_script_get_path(script); + if (strcmp(script_path, path) == 0) { + return true; + } + } + + return false; + } +}; + +static ScriptData *scriptData = nullptr; +static ScriptsTool *scriptsWindow = nullptr; +static ScriptLogWindow *scriptLogWindow = nullptr; +static QPlainTextEdit *scriptLogWidget = nullptr; + +/* ----------------------------------------------------------------- */ + +ScriptLogWindow::ScriptLogWindow() : QWidget(nullptr) +{ + const QFont fixedFont = + QFontDatabase::systemFont(QFontDatabase::FixedFont); + + QPlainTextEdit *edit = new QPlainTextEdit(); + edit->setReadOnly(true); + edit->setFont(fixedFont); + edit->setWordWrapMode(QTextOption::NoWrap); + + QHBoxLayout *buttonLayout = new QHBoxLayout(); + QPushButton *clearButton = new QPushButton(tr("Clear")); + connect(clearButton, &QPushButton::clicked, + this, &ScriptLogWindow::ClearWindow); + QPushButton *closeButton = new QPushButton(tr("Close")); + connect(closeButton, &QPushButton::clicked, + this, &QDialog::hide); + + buttonLayout->addStretch(); + buttonLayout->addWidget(clearButton); + buttonLayout->addWidget(closeButton); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(edit); + layout->addLayout(buttonLayout); + + setLayout(layout); + scriptLogWidget = edit; + + resize(600, 400); + + config_t *global_config = obs_frontend_get_global_config(); + const char *geom = config_get_string(global_config, + "ScriptLogWindow", "geometry"); + if (geom != nullptr) { + QByteArray ba = QByteArray::fromBase64(QByteArray(geom)); + restoreGeometry(ba); + } + + setWindowTitle(obs_module_text("ScriptLogWindow")); + + connect(edit->verticalScrollBar(), &QAbstractSlider::sliderMoved, + this, &ScriptLogWindow::ScrollChanged); +} + +ScriptLogWindow::~ScriptLogWindow() +{ + config_t *global_config = obs_frontend_get_global_config(); + config_set_string(global_config, + "ScriptLogWindow", "geometry", + saveGeometry().toBase64().constData()); +} + +void ScriptLogWindow::ScrollChanged(int val) +{ + QScrollBar *scroll = scriptLogWidget->verticalScrollBar(); + bottomScrolled = (val == scroll->maximum()); +} + +void ScriptLogWindow::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + if (bottomScrolled) { + QScrollBar *scroll = scriptLogWidget->verticalScrollBar(); + scroll->setValue(scroll->maximum()); + } +} + +void ScriptLogWindow::AddLogMsg(int log_level, QString msg) +{ + QScrollBar *scroll = scriptLogWidget->verticalScrollBar(); + bottomScrolled = scroll->value() == scroll->maximum(); + + lines += QStringLiteral("\n"); + lines += msg; + scriptLogWidget->setPlainText(lines); + + if (bottomScrolled) + scroll->setValue(scroll->maximum()); + + if (log_level <= LOG_WARNING) { + show(); + raise(); + } +} + +void ScriptLogWindow::ClearWindow() +{ + Clear(); + scriptLogWidget->setPlainText(QString()); +} + +void ScriptLogWindow::Clear() +{ + lines.clear(); +} + +/* ----------------------------------------------------------------- */ + +ScriptsTool::ScriptsTool() + : QWidget (nullptr), + ui (new Ui_ScriptsTool) +{ + ui->setupUi(this); + RefreshLists(); + +#if PYTHON_UI + config_t *config = obs_frontend_get_global_config(); + const char *path = config_get_string(config, "Python", + "Path" ARCH_NAME); + ui->pythonPath->setText(path); + ui->pythonPathLabel->setText(obs_module_text(PYTHONPATH_LABEL_TEXT)); +#else + delete ui->pythonSettingsTab; + ui->pythonSettingsTab = nullptr; +#endif + + delete propertiesView; + propertiesView = new QWidget(); + propertiesView->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding); + ui->propertiesLayout->addWidget(propertiesView); +} + +ScriptsTool::~ScriptsTool() +{ + delete ui; +} + +void ScriptsTool::RemoveScript(const char *path) +{ + for (size_t i = 0; i < scriptData->scripts.size(); i++) { + OBSScript &script = scriptData->scripts[i]; + + const char *script_path = obs_script_get_path(script); + if (strcmp(script_path, path) == 0) { + scriptData->scripts.erase( + scriptData->scripts.begin() + i); + break; + } + } +} + +void ScriptsTool::ReloadScript(const char *path) +{ + for (OBSScript &script : scriptData->scripts) { + const char *script_path = obs_script_get_path(script); + if (strcmp(script_path, path) == 0) { + obs_script_reload(script); + break; + } + } +} + +void ScriptsTool::RefreshLists() +{ + ui->scripts->clear(); + + for (OBSScript &script : scriptData->scripts) { + const char *script_file = obs_script_get_file(script); + const char *script_path = obs_script_get_path(script); + + QListWidgetItem *item = new QListWidgetItem(script_file); + item->setData(Qt::UserRole, QString(script_path)); + ui->scripts->addItem(item); + } +} + +void ScriptsTool::on_close_clicked() +{ + close(); +} + +void ScriptsTool::on_addScripts_clicked() +{ + const char **formats = obs_scripting_supported_formats(); + const char **cur_format = formats; + QString extensions; + QString filter; + + while (*cur_format) { + if (!extensions.isEmpty()) + extensions += QStringLiteral(" "); + + extensions += QStringLiteral("*."); + extensions += *cur_format; + + cur_format++; + } + + if (!extensions.isEmpty()) { + filter += obs_module_text("FileFilter.ScriptFiles"); + filter += QStringLiteral(" ("); + filter += extensions; + filter += QStringLiteral(")"); + } + + if (filter.isEmpty()) + return; + + static std::string lastBrowsedDir; + + if (lastBrowsedDir.empty()) { + BPtr baseScriptPath = obs_module_file("scripts"); + lastBrowsedDir = baseScriptPath; + } + + QFileDialog dlg(this, obs_module_text("AddScripts")); + dlg.setFileMode(QFileDialog::ExistingFiles); + dlg.setDirectory(QDir(lastBrowsedDir.c_str())); + dlg.setNameFilter(filter); + dlg.exec(); + + QStringList files = dlg.selectedFiles(); + if (!files.count()) + return; + + lastBrowsedDir = dlg.directory().path().toUtf8().constData(); + + for (const QString &file : files) { + QByteArray pathBytes = file.toUtf8(); + const char *path = pathBytes.constData(); + + if (scriptData->ScriptOpened(path)) { + continue; + } + + obs_script_t *script = obs_script_create(path, NULL); + if (script) { + const char *script_file = obs_script_get_file(script); + + scriptData->scripts.emplace_back(script); + + QListWidgetItem *item = new QListWidgetItem(script_file); + item->setData(Qt::UserRole, QString(file)); + ui->scripts->addItem(item); + } + } +} + +void ScriptsTool::on_removeScripts_clicked() +{ + QList items = ui->scripts->selectedItems(); + + for (QListWidgetItem *item : items) + RemoveScript(item->data(Qt::UserRole).toString() + .toUtf8().constData()); + RefreshLists(); +} + +void ScriptsTool::on_reloadScripts_clicked() +{ + QList items = ui->scripts->selectedItems(); + for (QListWidgetItem *item : items) + ReloadScript(item->data(Qt::UserRole).toString() + .toUtf8().constData()); + + on_scripts_currentRowChanged(ui->scripts->currentRow()); +} + +void ScriptsTool::on_scriptLog_clicked() +{ + scriptLogWindow->show(); + scriptLogWindow->raise(); +} + +void ScriptsTool::on_pythonPathBrowse_clicked() +{ + QString curPath = ui->pythonPath->text(); + QString newPath = QFileDialog::getExistingDirectory( + this, + ui->pythonPathLabel->text(), + curPath); + + if (newPath.isEmpty()) + return; + + QByteArray array = newPath.toUtf8(); + const char *path = array.constData(); + + config_t *config = obs_frontend_get_global_config(); + config_set_string(config, "Python", "Path" ARCH_NAME, path); + + ui->pythonPath->setText(newPath); + + if (obs_scripting_python_loaded()) + return; + if (!obs_scripting_load_python(path)) + return; + + for (OBSScript &script : scriptData->scripts) { + enum obs_script_lang lang = obs_script_get_lang(script); + if (lang == OBS_SCRIPT_LANG_PYTHON) { + obs_script_reload(script); + } + } + + on_scripts_currentRowChanged(ui->scripts->currentRow()); +} + +void ScriptsTool::on_scripts_currentRowChanged(int row) +{ + ui->propertiesLayout->removeWidget(propertiesView); + delete propertiesView; + + if (row == -1) { + propertiesView = new QWidget(); + propertiesView->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding); + ui->propertiesLayout->addWidget(propertiesView); + ui->description->setText(QString()); + return; + } + + QByteArray array = ui->scripts->item(row)->data(Qt::UserRole) + .toString().toUtf8(); + const char *path = array.constData(); + + obs_script_t *script = scriptData->FindScript(path); + if (!script) { + propertiesView = nullptr; + return; + } + + OBSData settings = obs_script_get_settings(script); + obs_data_release(settings); + + propertiesView = new OBSPropertiesView(settings, script, + (PropertiesReloadCallback)obs_script_get_properties, + (PropertiesUpdateCallback)obs_script_update); + ui->propertiesLayout->addWidget(propertiesView); + ui->description->setText(obs_script_get_description(script)); +} + +/* ----------------------------------------------------------------- */ + +extern "C" void FreeScripts() +{ + obs_scripting_unload(); +} + +static void obs_event(enum obs_frontend_event event, void *) +{ + if (event == OBS_FRONTEND_EVENT_EXIT) { + delete scriptData; + delete scriptsWindow; + delete scriptLogWindow; + + } else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP) { + scriptLogWindow->hide(); + scriptLogWindow->Clear(); + + delete scriptData; + scriptData = new ScriptData; + } +} + +static void load_script_data(obs_data_t *load_data, bool, void *) +{ + obs_data_array_t *array = obs_data_get_array(load_data, + "scripts-tool"); + + delete scriptData; + scriptData = new ScriptData; + + size_t size = obs_data_array_count(array); + for (size_t i = 0; i < size; i++) { + obs_data_t *obj = obs_data_array_item(array, i); + const char *path = obs_data_get_string(obj, "path"); + obs_data_t *settings = obs_data_get_obj(obj, "settings"); + + obs_script_t *script = obs_script_create(path, settings); + if (script) { + scriptData->scripts.emplace_back(script); + } + + obs_data_release(settings); + obs_data_release(obj); + } + + if (scriptsWindow) + scriptsWindow->RefreshLists(); + + obs_data_array_release(array); +} + +static void save_script_data(obs_data_t *save_data, bool saving, void *) +{ + if (!saving) + return; + + obs_data_array_t *array = obs_data_array_create(); + + for (OBSScript &script : scriptData->scripts) { + const char *script_path = obs_script_get_path(script); + obs_data_t *settings = obs_script_save(script); + + obs_data_t *obj = obs_data_create(); + obs_data_set_string(obj, "path", script_path); + obs_data_set_obj(obj, "settings", settings); + obs_data_array_push_back(array, obj); + obs_data_release(obj); + + obs_data_release(settings); + } + + obs_data_set_array(save_data, "scripts-tool", array); + obs_data_array_release(array); +} + +static void script_log(void *, obs_script_t *script, int log_level, + const char *message) +{ + QString qmsg; + + if (script) { + qmsg = QStringLiteral("[%1] %2").arg( + obs_script_get_file(script), + message); + } else { + qmsg = QStringLiteral("[Unknown Script] %1").arg(message); + } + + QMetaObject::invokeMethod(scriptLogWindow, "AddLogMsg", + Q_ARG(int, log_level), + Q_ARG(QString, qmsg)); +} + +extern "C" void InitScripts() +{ + scriptLogWindow = new ScriptLogWindow(); + + obs_scripting_load(); + obs_scripting_set_log_callback(script_log, nullptr); + + QAction *action = (QAction*)obs_frontend_add_tools_menu_qaction( + obs_module_text("Scripts")); + +#if PYTHON_UI + config_t *config = obs_frontend_get_global_config(); + const char *python_path = config_get_string(config, "Python", + "Path" ARCH_NAME); + + if (!obs_scripting_python_loaded() && python_path && *python_path) + obs_scripting_load_python(python_path); +#endif + + scriptData = new ScriptData; + + auto cb = [] () + { + obs_frontend_push_ui_translation(obs_module_get_string); + + if (!scriptsWindow) { + scriptsWindow = new ScriptsTool(); + scriptsWindow->show(); + } else { + scriptsWindow->show(); + scriptsWindow->raise(); + } + + obs_frontend_pop_ui_translation(); + }; + + obs_frontend_add_save_callback(save_script_data, nullptr); + obs_frontend_add_preload_callback(load_script_data, nullptr); + obs_frontend_add_event_callback(obs_event, nullptr); + + action->connect(action, &QAction::triggered, cb); +} diff --git a/UI/frontend-plugins/frontend-tools/scripts.hpp b/UI/frontend-plugins/frontend-tools/scripts.hpp new file mode 100644 index 0000000..f638c3f --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/scripts.hpp @@ -0,0 +1,50 @@ +#include +#include + +class Ui_ScriptsTool; + +class ScriptLogWindow : public QWidget { + Q_OBJECT + + QString lines; + bool bottomScrolled = true; + + void resizeEvent(QResizeEvent *event) override; + +public: + ScriptLogWindow(); + ~ScriptLogWindow(); + +public slots: + void AddLogMsg(int log_level, QString msg); + void ClearWindow(); + void Clear(); + void ScrollChanged(int val); +}; + +class ScriptsTool : public QWidget { + Q_OBJECT + + Ui_ScriptsTool *ui; + QWidget *propertiesView = nullptr; + +public: + ScriptsTool(); + ~ScriptsTool(); + + void RemoveScript(const char *path); + void ReloadScript(const char *path); + void RefreshLists(); + +public slots: + void on_close_clicked(); + + void on_addScripts_clicked(); + void on_removeScripts_clicked(); + void on_reloadScripts_clicked(); + void on_scriptLog_clicked(); + + void on_scripts_currentRowChanged(int row); + + void on_pythonPathBrowse_clicked(); +}; diff --git a/UI/hotkey-edit.cpp b/UI/hotkey-edit.cpp index d4e7257..e76dc37 100644 --- a/UI/hotkey-edit.cpp +++ b/UI/hotkey-edit.cpp @@ -272,10 +272,12 @@ void OBSHotkeyWidget::AddEdit(obs_key_combination combo, int idx) edit->setToolTip(toolTip); auto revert = new QPushButton; + revert->setProperty("themeID", "hotkeyButtons"); revert->setText(QTStr("Revert")); revert->setEnabled(false); auto clear = new QPushButton; + clear->setProperty("themeID", "hotkeyButtons"); clear->setText(QTStr("Clear")); clear->setEnabled(!obs_key_combination_is_empty(combo)); @@ -287,10 +289,12 @@ void OBSHotkeyWidget::AddEdit(obs_key_combination combo, int idx) }); auto add = new QPushButton; + add->setProperty("themeID", "hotkeyButtons"); add->setText("+"); add->setMinimumWidth(50); auto remove = new QPushButton; + remove->setProperty("themeID", "hotkeyButtons"); remove->setText("-"); remove->setEnabled(removeButtons.size() > 0); remove->setMinimumWidth(50); diff --git a/UI/hotkey-edit.hpp b/UI/hotkey-edit.hpp index 985d44b..3299e28 100644 --- a/UI/hotkey-edit.hpp +++ b/UI/hotkey-edit.hpp @@ -53,6 +53,7 @@ public: // enough with the default focus frame setReadOnly(true); #endif + setAttribute(Qt::WA_InputMethodEnabled, false); setAttribute(Qt::WA_MacShowFocusRect, true); InitSignalHandler(); ResetKey(); diff --git a/UI/installer/mp-installer.nsi b/UI/installer/mp-installer.nsi index 27017cd..06802c8 100644 --- a/UI/installer/mp-installer.nsi +++ b/UI/installer/mp-installer.nsi @@ -99,18 +99,32 @@ Function PreReqCheck gotPatch: ${EndIf} + ; 32 bit Visual Studio 2013 runtime check ClearErrors GetDLLVersion "MSVCR120.DLL" $R0 $R1 - IfErrors vs2013Missing vs2013OK + GetDLLVersion "MSVCP120.DLL" $R0 $R1 + IfErrors vs2013Missing vs2013OK1 vs2013Missing: MessageBox MB_YESNO|MB_ICONEXCLAMATION "Your system is missing runtime components that ${APPNAME} requires. Please make sure to install both vcredist_x64 and vcredist_x86. Would you like to download them?" IDYES vs2013true IDNO vs2013false vs2013true: ExecShell "open" "https://obsproject.com/visual-studio-2013-runtimes" vs2013false: Quit - vs2013OK: + vs2013OK1: ClearErrors + ; 64 bit Visual Studio 2013 runtime check + ${if} ${RunningX64} + SetOutPath "$TEMP\OBS" + File check_for_64bit_visual_studio_2013_runtimes.exe + ExecWait "$TEMP\OBS\check_for_64bit_visual_studio_2013_runtimes.exe" $R0 + Delete "$TEMP\OBS\check_for_64bit_visual_studio_2013_runtimes.exe" + RMDir "$TEMP\OBS" + IntCmp $R0 126 vs2013Missing vs2013OK2 + vs2013OK2: + ClearErrors + ${endif} + ; DirectX Version Check ClearErrors GetDLLVersion "D3DCompiler_33.dll" $R0 $R1 diff --git a/UI/locked-checkbox.cpp b/UI/locked-checkbox.cpp new file mode 100644 index 0000000..91a049f --- /dev/null +++ b/UI/locked-checkbox.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include "locked-checkbox.hpp" + +#include + +LockedCheckBox::LockedCheckBox() : QCheckBox() +{ + lockedImage = + QPixmap::fromImage(QImage(":/res/images/locked_mask.png")); + unlockedImage = + QPixmap::fromImage(QImage(":/res/images/unlocked_mask.png")); + setMinimumSize(16, 16); + + setStyleSheet("outline: none;"); +} + +void LockedCheckBox::paintEvent(QPaintEvent *event) +{ + UNUSED_PARAMETER(event); + + QPixmap &pixmap = isChecked() ? lockedImage : unlockedImage; + QImage image(pixmap.size(), QImage::Format_ARGB32); + + QPainter draw(&image); + draw.setCompositionMode(QPainter::CompositionMode_Source); + draw.drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap); + draw.setCompositionMode(QPainter::CompositionMode_SourceIn); + draw.fillRect(QRectF(QPointF(0.0f, 0.0f), pixmap.size()), + palette().color(foregroundRole())); + + QPainter p(this); + p.drawPixmap(0, 0, image.width(), image.height(), + QPixmap::fromImage(image)); +} diff --git a/UI/locked-checkbox.hpp b/UI/locked-checkbox.hpp new file mode 100644 index 0000000..017470f --- /dev/null +++ b/UI/locked-checkbox.hpp @@ -0,0 +1,17 @@ +#include +#include + +class QPaintEvernt; + +class LockedCheckBox : public QCheckBox { + Q_OBJECT + + QPixmap lockedImage; + QPixmap unlockedImage; + +public: + LockedCheckBox(); + +protected: + void paintEvent(QPaintEvent *event) override; +}; diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 01db714..d5fc09a 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -62,6 +62,7 @@ static string currentLogFile; static string lastLogFile; bool portable_mode = false; +static bool multi = false; static bool log_verbose = false; static bool unfiltered_log = false; bool opt_start_streaming = false; @@ -411,6 +412,11 @@ bool OBSApp::InitGlobalConfigDefaults() config_set_default_bool(globalConfig, "BasicWindow", "ShowStatusBar", true); + if (!config_get_bool(globalConfig, "General", "Pre21Defaults")) { + config_set_default_string(globalConfig, "General", + "CurrentTheme", "Dark"); + } + #ifdef _WIN32 config_set_default_bool(globalConfig, "Audio", "DisableAudioDucking", true); @@ -635,6 +641,17 @@ bool OBSApp::InitGlobalConfig() changed = true; } + if (!config_has_user_value(globalConfig, "General", "Pre21Defaults")) { + uint32_t lastVersion = config_get_int(globalConfig, "General", + "LastVersion"); + bool useOldDefaults = lastVersion && + lastVersion < MAKE_SEMANTIC_VERSION(21, 0, 0); + + config_set_bool(globalConfig, "General", "Pre21Defaults", + useOldDefaults); + changed = true; + } + if (changed) config_save_safe(globalConfig, "tmp", nullptr); @@ -737,14 +754,19 @@ bool OBSApp::SetTheme(std::string name, std::string path) bool OBSApp::InitTheme() { const char *themeName = config_get_string(globalConfig, "General", - "Theme"); + "CurrentTheme"); + if (!themeName) { + /* Use deprecated "Theme" value if available */ + themeName = config_get_string(globalConfig, + "General", "Theme"); + if (!themeName) + themeName = "Default"; + } - if (!themeName) - themeName = "Default"; + if (strcmp(themeName, "Default") != 0 && SetTheme(themeName)) + return true; - stringstream t; - t << themeName; - return SetTheme(t.str()); + return SetTheme("Default"); } OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store) @@ -1343,7 +1365,7 @@ static int run_program(fstream &logFile, int argc, char *argv[]) bool already_running = false; RunOnceMutex rom = GetRunOnceMutex(already_running); - if (already_running) { + if (already_running && !multi) { blog(LOG_WARNING, "\n================================"); blog(LOG_WARNING, "Warning: OBS is already running!"); blog(LOG_WARNING, "================================\n"); @@ -1371,10 +1393,22 @@ static int run_program(fstream &logFile, int argc, char *argv[]) blog(LOG_WARNING, "User is now running a secondary " "instance of OBS!"); + + } else if (already_running && multi) { + blog(LOG_INFO, "User enabled --multi flag and is now " + "running multiple instances of OBS."); } /* --------------------------------------- */ #endif + if (argc > 1) { + stringstream stor; + stor << argv[1]; + for (int i = 2; i < argc; ++i) { + stor << " " << argv[i]; + } + blog(LOG_INFO, "Command Line Arguments: %s", stor.str().c_str()); + } if (!program.OBSInit()) return 0; @@ -1851,6 +1885,9 @@ int main(int argc, char *argv[]) if (arg_is(argv[i], "--portable", "-p")) { portable_mode = true; + } else if (arg_is(argv[i], "--multi", "-m")) { + multi = true; + } else if (arg_is(argv[i], "--verbose", nullptr)) { log_verbose = true; @@ -1899,7 +1936,8 @@ int main(int argc, char *argv[]) "--scene : Start with specific scene.\n\n" << "--studio-mode: Enable studio mode.\n" << "--minimize-to-tray: Minimize to system tray.\n" << - "--portable, -p: Use portable mode.\n\n" << + "--portable, -p: Use portable mode.\n" << + "--multi, -m: Don't warn when launching multiple instances.\n\n" << "--verbose: Make log more verbose.\n" << "--always-on-top: Start in 'always on top' mode.\n\n" << "--unfiltered_log: Make log unfiltered.\n\n" << diff --git a/UI/obs-frontend-api/obs-frontend-api.cpp b/UI/obs-frontend-api/obs-frontend-api.cpp index 030fded..ee47fff 100644 --- a/UI/obs-frontend-api/obs-frontend-api.cpp +++ b/UI/obs-frontend-api/obs-frontend-api.cpp @@ -210,6 +210,11 @@ void obs_frontend_replay_buffer_start(void) if (callbacks_valid()) c->obs_frontend_replay_buffer_start(); } +void obs_frontend_replay_buffer_save(void) +{ + if (callbacks_valid()) c->obs_frontend_replay_buffer_save(); +} + void obs_frontend_replay_buffer_stop(void) { if (callbacks_valid()) c->obs_frontend_replay_buffer_stop(); @@ -306,6 +311,20 @@ void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, c->obs_frontend_remove_save_callback(callback, private_data); } +void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, + void *private_data) +{ + if (callbacks_valid()) + c->obs_frontend_add_preload_callback(callback, private_data); +} + +void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, + void *private_data) +{ + if (callbacks_valid()) + c->obs_frontend_remove_preload_callback(callback, private_data); +} + void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) { if (callbacks_valid()) @@ -336,3 +355,42 @@ void obs_frontend_save_streaming_service(void) if (callbacks_valid()) c->obs_frontend_save_streaming_service(); } + +bool obs_frontend_preview_program_mode_active(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_preview_program_mode_active() + : false; +} + +void obs_frontend_set_preview_program_mode(bool enable) +{ + if (callbacks_valid()) + c->obs_frontend_set_preview_program_mode(enable); +} + +void obs_frontend_set_preview_enabled(bool enable) +{ + if (callbacks_valid()) + c->obs_frontend_set_preview_enabled(enable); +} + +bool obs_frontend_preview_enabled(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_preview_enabled() + : false; +} + +obs_source_t *obs_frontend_get_current_preview_scene(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_current_preview_scene() + : nullptr; +} + +void obs_frontend_set_current_preview_scene(obs_source_t *scene) +{ + if (callbacks_valid()) + c->obs_frontend_set_current_preview_scene(scene); +} diff --git a/UI/obs-frontend-api/obs-frontend-api.h b/UI/obs-frontend-api/obs-frontend-api.h index 9918705..05483ff 100644 --- a/UI/obs-frontend-api/obs-frontend-api.h +++ b/UI/obs-frontend-api/obs-frontend-api.h @@ -13,73 +13,6 @@ 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); - -EXPORT void obs_frontend_replay_buffer_start(void); -EXPORT void obs_frontend_replay_buffer_stop(void); -EXPORT bool obs_frontend_replay_buffer_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, @@ -103,9 +36,76 @@ enum obs_frontend_event { OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING, OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED, OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING, - OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED + OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED, + + OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED, + OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED, + OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED, + + OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP }; +/* ------------------------------------------------------------------------- */ + +#ifndef SWIG + +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); +} + +#endif //!SWIG + +/* ------------------------------------------------------------------------- */ + +/* 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. */ + +#ifndef SWIG + +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); + +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); + typedef void (*obs_frontend_event_cb)(enum obs_frontend_event event, void *private_data); @@ -117,18 +117,15 @@ EXPORT void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, 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 obs_output_t *obs_frontend_get_replay_buffer_output(void); - -EXPORT config_t *obs_frontend_get_profile_config(void); -EXPORT config_t *obs_frontend_get_global_config(void); +EXPORT void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, + void *private_data); +EXPORT void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, + void *private_data); typedef bool (*obs_frontend_translate_ui_cb)(const char *text, const char **out); @@ -137,10 +134,43 @@ EXPORT void obs_frontend_push_ui_translation( obs_frontend_translate_ui_cb translate); EXPORT void obs_frontend_pop_ui_translation(void); +#endif //!SWIG + +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); + +EXPORT void obs_frontend_replay_buffer_start(void); +EXPORT void obs_frontend_replay_buffer_save(void); +EXPORT void obs_frontend_replay_buffer_stop(void); +EXPORT bool obs_frontend_replay_buffer_active(void); + +EXPORT void obs_frontend_save(void); + +EXPORT obs_output_t *obs_frontend_get_streaming_output(void); +EXPORT obs_output_t *obs_frontend_get_recording_output(void); +EXPORT obs_output_t *obs_frontend_get_replay_buffer_output(void); + +EXPORT config_t *obs_frontend_get_profile_config(void); +EXPORT config_t *obs_frontend_get_global_config(void); + EXPORT void obs_frontend_set_streaming_service(obs_service_t *service); EXPORT obs_service_t* obs_frontend_get_streaming_service(void); EXPORT void obs_frontend_save_streaming_service(void); +EXPORT bool obs_frontend_preview_program_mode_active(void); +EXPORT void obs_frontend_set_preview_program_mode(bool enable); + +EXPORT void obs_frontend_set_preview_enabled(bool enable); +EXPORT bool obs_frontend_preview_enabled(void); + +EXPORT obs_source_t *obs_frontend_get_current_preview_scene(void); +EXPORT void obs_frontend_set_current_preview_scene(obs_source_t *scene); + /* ------------------------------------------------------------------------- */ #ifdef __cplusplus diff --git a/UI/obs-frontend-api/obs-frontend-internal.hpp b/UI/obs-frontend-api/obs-frontend-internal.hpp index d9e4522..0749d2e 100644 --- a/UI/obs-frontend-api/obs-frontend-internal.hpp +++ b/UI/obs-frontend-api/obs-frontend-internal.hpp @@ -41,6 +41,7 @@ struct obs_frontend_callbacks { virtual bool obs_frontend_recording_active(void)=0; virtual void obs_frontend_replay_buffer_start(void)=0; + virtual void obs_frontend_replay_buffer_save(void) = 0; virtual void obs_frontend_replay_buffer_stop(void)=0; virtual bool obs_frontend_replay_buffer_active(void)=0; @@ -66,6 +67,11 @@ struct obs_frontend_callbacks { virtual void obs_frontend_remove_save_callback( obs_frontend_save_cb callback, void *private_data)=0; + virtual void obs_frontend_add_preload_callback( + obs_frontend_save_cb callback, void *private_data)=0; + virtual void obs_frontend_remove_preload_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; @@ -75,7 +81,16 @@ struct obs_frontend_callbacks { virtual obs_service_t *obs_frontend_get_streaming_service(void)=0; virtual void obs_frontend_save_streaming_service()=0; + virtual bool obs_frontend_preview_program_mode_active(void)=0; + virtual void obs_frontend_set_preview_program_mode(bool enable)=0; + virtual bool obs_frontend_preview_enabled(void)=0; + virtual void obs_frontend_set_preview_enabled(bool enable)=0; + + virtual obs_source_t *obs_frontend_get_current_preview_scene(void)=0; + virtual void obs_frontend_set_current_preview_scene(obs_source_t *scene)=0; + virtual void on_load(obs_data_t *settings)=0; + virtual void on_preload(obs_data_t *settings)=0; virtual void on_save(obs_data_t *settings)=0; virtual void on_event(enum obs_frontend_event event)=0; }; diff --git a/UI/platform-windows.cpp b/UI/platform-windows.cpp index 524361b..9074018 100644 --- a/UI/platform-windows.cpp +++ b/UI/platform-windows.cpp @@ -30,7 +30,6 @@ using namespace std; #include #include #include -#include #include #include @@ -212,6 +211,8 @@ void SetProcessPriority(const char *priority) SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); else if (strcmp(priority, "Normal") == 0) SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); + else if (strcmp(priority, "BelowNormal") == 0) + SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); else if (strcmp(priority, "Idle") == 0) SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS); } @@ -262,17 +263,6 @@ bool DisableAudioDucking(bool disable) return SUCCEEDED(result); } -uint64_t CurrentMemoryUsage() -{ - PROCESS_MEMORY_COUNTERS pmc = {}; - pmc.cb = sizeof(pmc); - - if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) - return 0; - - return (uint64_t)pmc.WorkingSetSize; -} - struct RunOnceMutexData { WinHandle handle; diff --git a/UI/platform.hpp b/UI/platform.hpp index ca0780b..5011e60 100644 --- a/UI/platform.hpp +++ b/UI/platform.hpp @@ -43,7 +43,6 @@ void SetAeroEnabled(bool enable); void SetProcessPriority(const char *priority); void SetWin32DropStyle(QWidget *window); bool DisableAudioDucking(bool disable); -uint64_t CurrentMemoryUsage(); struct RunOnceMutexData; diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index 17bcbfd..419787c 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -296,6 +296,7 @@ void OBSPropertiesView::AddPath(obs_property_t *prop, QFormLayout *layout, button->setEnabled(false); } + button->setProperty("themeID", "settingsButtons"); edit->setText(QT_UTF8(val)); edit->setReadOnly(true); edit->setToolTip(QT_UTF8(obs_property_long_description(prop))); @@ -610,6 +611,7 @@ QWidget *OBSPropertiesView::AddButton(obs_property_t *prop) const char *desc = obs_property_description(prop); QPushButton *button = new QPushButton(QT_UTF8(desc)); + button->setProperty("themeID", "settingsButtons"); button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); return NewWidget(prop, button, SIGNAL(clicked())); } @@ -628,6 +630,7 @@ void OBSPropertiesView::AddColor(obs_property_t *prop, QFormLayout *layout, colorLabel->setEnabled(false); } + button->setProperty("themeID", "settingsButtons"); button->setText(QTStr("Basic.PropertiesWindow.SelectColor")); button->setToolTip(QT_UTF8(obs_property_long_description(prop))); @@ -698,6 +701,7 @@ void OBSPropertiesView::AddFont(obs_property_t *prop, QFormLayout *layout, font = fontLabel->font(); MakeQFont(font_obj, font, true); + button->setProperty("themeID", "settingsButtons"); button->setText(QTStr("Basic.PropertiesWindow.SelectFont")); button->setToolTip(QT_UTF8(obs_property_long_description(prop))); @@ -1795,6 +1799,7 @@ public: if (browse) { QPushButton *browseButton = new QPushButton(QTStr("Browse")); + browseButton->setProperty("themeID", "settingsButtons"); topLayout->addWidget(browseButton); topLayout->setAlignment(browseButton, Qt::AlignVCenter); diff --git a/UI/visibility-item-widget.cpp b/UI/visibility-item-widget.cpp index f42d193..ba1ecbf 100644 --- a/UI/visibility-item-widget.cpp +++ b/UI/visibility-item-widget.cpp @@ -1,5 +1,6 @@ #include "visibility-item-widget.hpp" #include "visibility-checkbox.hpp" +#include "locked-checkbox.hpp" #include "qt-wrappers.hpp" #include "obs-app.hpp" #include @@ -49,6 +50,7 @@ VisibilityItemWidget::VisibilityItemWidget(obs_sceneitem_t *item_) { const char *name = obs_source_get_name(source); bool enabled = obs_sceneitem_visible(item); + bool locked = obs_sceneitem_locked(item); obs_scene_t *scene = obs_sceneitem_get_scene(item); obs_source_t *sceneSource = obs_scene_get_source(scene); @@ -60,13 +62,28 @@ VisibilityItemWidget::VisibilityItemWidget(obs_sceneitem_t *item_) #endif vis->setChecked(enabled); + lock = new LockedCheckBox(); + lock->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + /* Fix for non-apple systems where the spacing would be too big */ +#ifndef __APPLE__ + lock->setMaximumSize(16, 16); +#endif + lock->setChecked(locked); + label = new QLabel(QT_UTF8(name)); label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); +#ifdef __APPLE__ + vis->setAttribute(Qt::WA_LayoutUsesWidgetRect); + lock->setAttribute(Qt::WA_LayoutUsesWidgetRect); +#endif + QHBoxLayout *itemLayout = new QHBoxLayout(); itemLayout->addWidget(vis); + itemLayout->addWidget(lock); itemLayout->addWidget(label); itemLayout->setContentsMargins(5, 2, 5, 2); + itemLayout->setSpacing(2); setLayout(itemLayout); setStyleSheet("background-color: rgba(255, 255, 255, 0);"); @@ -80,6 +97,9 @@ VisibilityItemWidget::VisibilityItemWidget(obs_sceneitem_t *item_) connect(vis, SIGNAL(clicked(bool)), this, SLOT(VisibilityClicked(bool))); + + connect(lock, SIGNAL(clicked(bool)), + this, SLOT(LockClicked(bool))); } VisibilityItemWidget::~VisibilityItemWidget() @@ -137,6 +157,18 @@ void VisibilityItemWidget::OBSSceneItemVisible(void *param, calldata_t *data) Q_ARG(bool, enabled)); } +void VisibilityItemWidget::OBSSceneItemLocked(void *param, calldata_t *data) +{ + VisibilityItemWidget *window = + reinterpret_cast(param); + obs_sceneitem_t *curItem = (obs_sceneitem_t*)calldata_ptr(data, "item"); + bool locked = calldata_bool(data, "locked"); + + if (window->item == curItem) + QMetaObject::invokeMethod(window, "SourceLocked", + Q_ARG(bool, locked)); +} + void VisibilityItemWidget::OBSSourceEnabled(void *param, calldata_t *data) { VisibilityItemWidget *window = @@ -165,12 +197,24 @@ void VisibilityItemWidget::VisibilityClicked(bool visible) obs_source_set_enabled(source, visible); } +void VisibilityItemWidget::LockClicked(bool locked) +{ + if (item) + obs_sceneitem_set_locked(item, locked); +} + void VisibilityItemWidget::SourceEnabled(bool enabled) { if (vis->isChecked() != enabled) vis->setChecked(enabled); } +void VisibilityItemWidget::SourceLocked(bool locked) +{ + if (lock->isChecked() != locked) + lock->setChecked(locked); +} + void VisibilityItemWidget::SourceRenamed(QString name) { if (label && name != label->text()) diff --git a/UI/visibility-item-widget.hpp b/UI/visibility-item-widget.hpp index 439cfd3..80c46db 100644 --- a/UI/visibility-item-widget.hpp +++ b/UI/visibility-item-widget.hpp @@ -9,6 +9,7 @@ class QLineEdit; class QListWidget; class QListWidgetItem; class VisibilityCheckBox; +class LockedCheckBox; class VisibilityItemWidget : public QWidget { Q_OBJECT @@ -18,6 +19,7 @@ private: OBSSource source; QLabel *label = nullptr; VisibilityCheckBox *vis = nullptr; + LockedCheckBox *lock = nullptr; QString oldName; OBSSignal sceneRemoveSignal; @@ -31,6 +33,7 @@ private: static void OBSSceneRemove(void *param, calldata_t *data); static void OBSSceneItemRemove(void *param, calldata_t *data); static void OBSSceneItemVisible(void *param, calldata_t *data); + static void OBSSceneItemLocked(void *param, calldata_t *data); static void OBSSourceEnabled(void *param, calldata_t *data); static void OBSSourceRenamed(void *param, calldata_t *data); @@ -38,8 +41,10 @@ private: private slots: void VisibilityClicked(bool visible); + void LockClicked(bool locked); void SourceEnabled(bool enabled); void SourceRenamed(QString name); + void SourceLocked(bool locked); public: VisibilityItemWidget(obs_source_t *source); diff --git a/UI/volume-control.cpp b/UI/volume-control.cpp index ba66209..47019cc 100644 --- a/UI/volume-control.cpp +++ b/UI/volume-control.cpp @@ -3,8 +3,10 @@ #include "obs-app.hpp" #include "mute-checkbox.hpp" #include "slider-absoluteset-style.hpp" +#include #include #include +#include #include #include #include @@ -18,6 +20,8 @@ using namespace std; +#define CLAMP(x, min, max) ((x) < min ? min : ((x) > max ? max : (x))) + QWeakPointer VolumeMeter::updateTimer; void VolControl::OBSVolumeChanged(void *data, float db) @@ -28,15 +32,14 @@ void VolControl::OBSVolumeChanged(void *data, float db) QMetaObject::invokeMethod(volControl, "VolumeChanged"); } -void VolControl::OBSVolumeLevel(void *data, float level, float mag, - float peak, float muted) +void VolControl::OBSVolumeLevel(void *data, + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float inputPeak[MAX_AUDIO_CHANNELS]) { VolControl *volControl = static_cast(data); - if (muted) - level = mag = peak = 0.0f; - - volControl->volMeter->setLevels(mag, level, peak); + volControl->volMeter->setLevels(magnitude, peak, inputPeak); } void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata) @@ -53,21 +56,10 @@ void VolControl::VolumeChanged() slider->blockSignals(true); slider->setValue((int) (obs_fader_get_deflection(obs_fader) * 100.0f)); slider->blockSignals(false); - + updateText(); } -void VolControl::VolumeLevel(float mag, float peak, float peakHold, bool muted) -{ - if (muted) { - mag = 0.0f; - peak = 0.0f; - peakHold = 0.0f; - } - - volMeter->setLevels(mag, peak, peakHold); -} - void VolControl::VolumeMuted(bool muted) { if (mute->isChecked() != muted) @@ -117,6 +109,11 @@ void VolControl::EmitConfigClicked() emit ConfigClicked(); } +void VolControl::SetMeterDecayRate(qreal q) +{ + volMeter->setPeakDecayRate(q); +} + VolControl::VolControl(OBSSource source_, bool showConfig) : source (source_), levelTotal (0.0f), @@ -131,7 +128,7 @@ VolControl::VolControl(OBSSource source_, bool showConfig) nameLabel = new QLabel(); volLabel = new QLabel(); - volMeter = new VolumeMeter(); + volMeter = new VolumeMeter(0, obs_volmeter); mute = new MuteCheckBox(); slider = new QSlider(Qt::Horizontal); @@ -225,58 +222,225 @@ VolControl::~VolControl() obs_volmeter_destroy(obs_volmeter); } -QColor VolumeMeter::getBkColor() const +QColor VolumeMeter::getBackgroundNominalColor() const { - return bkColor; + return backgroundNominalColor; } -void VolumeMeter::setBkColor(QColor c) +void VolumeMeter::setBackgroundNominalColor(QColor c) { - bkColor = c; + backgroundNominalColor = c; } -QColor VolumeMeter::getMagColor() const +QColor VolumeMeter::getBackgroundWarningColor() const { - return magColor; + return backgroundWarningColor; } -void VolumeMeter::setMagColor(QColor c) +void VolumeMeter::setBackgroundWarningColor(QColor c) { - magColor = c; + backgroundWarningColor = c; } -QColor VolumeMeter::getPeakColor() const +QColor VolumeMeter::getBackgroundErrorColor() const { - return peakColor; + return backgroundErrorColor; } -void VolumeMeter::setPeakColor(QColor c) +void VolumeMeter::setBackgroundErrorColor(QColor c) { - peakColor = c; + backgroundErrorColor = c; } -QColor VolumeMeter::getPeakHoldColor() const +QColor VolumeMeter::getForegroundNominalColor() const { - return peakHoldColor; + return foregroundNominalColor; } -void VolumeMeter::setPeakHoldColor(QColor c) +void VolumeMeter::setForegroundNominalColor(QColor c) { - peakHoldColor = c; + foregroundNominalColor = c; } - -VolumeMeter::VolumeMeter(QWidget *parent) - : QWidget(parent) +QColor VolumeMeter::getForegroundWarningColor() const { - setMinimumSize(1, 3); + return foregroundWarningColor; +} - //Default meter color settings, they only show if there is no stylesheet, do not remove. - bkColor.setRgb(0xDD, 0xDD, 0xDD); - magColor.setRgb(0x20, 0x7D, 0x17); - peakColor.setRgb(0x3E, 0xF1, 0x2B); - peakHoldColor.setRgb(0x00, 0x00, 0x00); - +void VolumeMeter::setForegroundWarningColor(QColor c) +{ + foregroundWarningColor = c; +} + +QColor VolumeMeter::getForegroundErrorColor() const +{ + return foregroundErrorColor; +} + +void VolumeMeter::setForegroundErrorColor(QColor c) +{ + foregroundErrorColor = c; +} + +QColor VolumeMeter::getClipColor() const +{ + return clipColor; +} + +void VolumeMeter::setClipColor(QColor c) +{ + clipColor = c; +} + +QColor VolumeMeter::getMagnitudeColor() const +{ + return magnitudeColor; +} + +void VolumeMeter::setMagnitudeColor(QColor c) +{ + magnitudeColor = c; +} + +QColor VolumeMeter::getMajorTickColor() const +{ + return majorTickColor; +} + +void VolumeMeter::setMajorTickColor(QColor c) +{ + majorTickColor = c; +} + +QColor VolumeMeter::getMinorTickColor() const +{ + return minorTickColor; +} + +void VolumeMeter::setMinorTickColor(QColor c) +{ + minorTickColor = c; +} + +qreal VolumeMeter::getMinimumLevel() const +{ + return minimumLevel; +} + +void VolumeMeter::setMinimumLevel(qreal v) +{ + minimumLevel = v; +} + +qreal VolumeMeter::getWarningLevel() const +{ + return warningLevel; +} + +void VolumeMeter::setWarningLevel(qreal v) +{ + warningLevel = v; +} + +qreal VolumeMeter::getErrorLevel() const +{ + return errorLevel; +} + +void VolumeMeter::setErrorLevel(qreal v) +{ + errorLevel = v; +} + +qreal VolumeMeter::getClipLevel() const +{ + return clipLevel; +} + +void VolumeMeter::setClipLevel(qreal v) +{ + clipLevel = v; +} + +qreal VolumeMeter::getMinimumInputLevel() const +{ + return minimumInputLevel; +} + +void VolumeMeter::setMinimumInputLevel(qreal v) +{ + minimumInputLevel = v; +} + +qreal VolumeMeter::getPeakDecayRate() const +{ + return peakDecayRate; +} + +void VolumeMeter::setPeakDecayRate(qreal v) +{ + peakDecayRate = v; +} + +qreal VolumeMeter::getMagnitudeIntegrationTime() const +{ + return magnitudeIntegrationTime; +} + +void VolumeMeter::setMagnitudeIntegrationTime(qreal v) +{ + magnitudeIntegrationTime = v; +} + +qreal VolumeMeter::getPeakHoldDuration() const +{ + return peakHoldDuration; +} + +void VolumeMeter::setPeakHoldDuration(qreal v) +{ + peakHoldDuration = v; +} + +qreal VolumeMeter::getInputPeakHoldDuration() const +{ + return inputPeakHoldDuration; +} + +void VolumeMeter::setInputPeakHoldDuration(qreal v) +{ + inputPeakHoldDuration = v; +} + +VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter) + : QWidget(parent), obs_volmeter(obs_volmeter) +{ + // Use a font that can be rendered small. + tickFont = QFont("Arial"); + tickFont.setPixelSize(7); + // Default meter color settings, they only show if + // there is no stylesheet, do not remove. + backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green + backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow + backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red + foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green + foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow + foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red + clipColor.setRgb(0xff, 0xff, 0xff); // Bright white + magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black + majorTickColor.setRgb(0xff, 0xff, 0xff); // Black + minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black + minimumLevel = -60.0; // -60 dB + warningLevel = -20.0; // -20 dB + errorLevel = -9.0; // -9 dB + clipLevel = -0.5; // -0.5 dB + minimumInputLevel = -50.0; // -50 dB + peakDecayRate = 11.76; // 20 dB / 1.7 sec + magnitudeIntegrationTime = 0.3; // 99% in 300 ms + peakHoldDuration = 20.0; // 20 seconds + inputPeakHoldDuration = 1.0; // 1 second + + handleChannelCofigurationChange(); updateTimerRef = updateTimer.toStrongRef(); if (!updateTimerRef) { updateTimerRef = QSharedPointer::create(); @@ -292,35 +456,339 @@ VolumeMeter::~VolumeMeter() updateTimerRef->RemoveVolControl(this); } -void VolumeMeter::setLevels(float nmag, float npeak, float npeakHold) +void VolumeMeter::setLevels( + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float inputPeak[MAX_AUDIO_CHANNELS]) { uint64_t ts = os_gettime_ns(); QMutexLocker locker(&dataMutex); - mag += nmag; - peak += npeak; - peakHold += npeakHold; - multiple += 1.0f; - lastUpdateTime = ts; -} - -inline void VolumeMeter::calcLevels() -{ - uint64_t ts = os_gettime_ns(); - QMutexLocker locker(&dataMutex); - - if (lastUpdateTime && ts - lastUpdateTime > 1000000000) { - mag = peak = peakHold = 0.0f; - multiple = 1.0f; - lastUpdateTime = 0; + currentLastUpdateTime = ts; + for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { + currentMagnitude[channelNr] = magnitude[channelNr]; + currentPeak[channelNr] = peak[channelNr]; + currentInputPeak[channelNr] = inputPeak[channelNr]; } - if (multiple > 0.0f) { - curMag = mag / multiple; - curPeak = peak / multiple; - curPeakHold = peakHold / multiple; + // In case there are more updates then redraws we must make sure + // that the ballistics of peak and hold are recalculated. + locker.unlock(); + calculateBallistics(ts); +} - mag = peak = peakHold = multiple = 0.0f; +inline void VolumeMeter::resetLevels() +{ + currentLastUpdateTime = 0; + for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { + currentMagnitude[channelNr] = -M_INFINITE; + currentPeak[channelNr] = -M_INFINITE; + currentInputPeak[channelNr] = -M_INFINITE; + + displayMagnitude[channelNr] = -M_INFINITE; + displayPeak[channelNr] = -M_INFINITE; + displayPeakHold[channelNr] = -M_INFINITE; + displayPeakHoldLastUpdateTime[channelNr] = 0; + displayInputPeakHold[channelNr] = -M_INFINITE; + displayInputPeakHoldLastUpdateTime[channelNr] = 0; + } +} + +inline void VolumeMeter::handleChannelCofigurationChange() +{ + QMutexLocker locker(&dataMutex); + + int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); + if (displayNrAudioChannels != currentNrAudioChannels) { + displayNrAudioChannels = currentNrAudioChannels; + + // Make room for 3 pixels high meter, with one pixel between + // each. Then 9 pixels below it for ticks and numbers. + setMinimumSize(130, displayNrAudioChannels * 4 + 8); + + resetLevels(); + } +} + +inline bool VolumeMeter::detectIdle(uint64_t ts) +{ + float timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001; + if (timeSinceLastUpdate > 0.5) { + resetLevels(); + return true; + } else { + return false; + } +} + +inline void VolumeMeter::calculateBallisticsForChannel(int channelNr, + uint64_t ts, qreal timeSinceLastRedraw) +{ + if (currentPeak[channelNr] >= displayPeak[channelNr] || + isnan(displayPeak[channelNr])) { + // Attack of peak is immediate. + displayPeak[channelNr] = currentPeak[channelNr]; + } else { + // Decay of peak is 40 dB / 1.7 seconds for Fast Profile + // 20 dB / 1.7 seconds for Medium Profile (Type I PPM) + // 24 dB / 2.8 seconds for Slow Profile (Type II PPM) + qreal decay = peakDecayRate * timeSinceLastRedraw; + displayPeak[channelNr] = CLAMP(displayPeak[channelNr] - decay, + currentPeak[channelNr], 0); + } + + if (currentPeak[channelNr] >= displayPeakHold[channelNr] || + !isfinite(displayPeakHold[channelNr])) { + // Attack of peak-hold is immediate, but keep track + // when it was last updated. + displayPeakHold[channelNr] = currentPeak[channelNr]; + displayPeakHoldLastUpdateTime[channelNr] = ts; + } else { + // The peak and hold falls back to peak + // after 20 seconds. + qreal timeSinceLastPeak = (uint64_t)(ts - + displayPeakHoldLastUpdateTime[channelNr]) * 0.000000001; + if (timeSinceLastPeak > peakHoldDuration) { + displayPeakHold[channelNr] = currentPeak[channelNr]; + displayPeakHoldLastUpdateTime[channelNr] = ts; + } + } + + if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] || + !isfinite(displayInputPeakHold[channelNr])) { + // Attack of peak-hold is immediate, but keep track + // when it was last updated. + displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; + displayInputPeakHoldLastUpdateTime[channelNr] = ts; + } else { + // The peak and hold falls back to peak after 1 second. + qreal timeSinceLastPeak = (uint64_t)(ts - + displayInputPeakHoldLastUpdateTime[channelNr]) * + 0.000000001; + if (timeSinceLastPeak > inputPeakHoldDuration) { + displayInputPeakHold[channelNr] = + currentInputPeak[channelNr]; + displayInputPeakHoldLastUpdateTime[channelNr] = + ts; + } + } + + if (!isfinite(displayMagnitude[channelNr])) { + // The statements in the else-leg do not work with + // NaN and infinite displayMagnitude. + displayMagnitude[channelNr] = + currentMagnitude[channelNr]; + } else { + // A VU meter will integrate to the new value to 99% in 300 ms. + // The calculation here is very simplified and is more accurate + // with higher frame-rate. + qreal attack = (currentMagnitude[channelNr] - + displayMagnitude[channelNr]) * + (timeSinceLastRedraw / + magnitudeIntegrationTime) * 0.99; + displayMagnitude[channelNr] = CLAMP( + displayMagnitude[channelNr] + attack, + minimumLevel, 0); + } +} + +inline void VolumeMeter::calculateBallistics(uint64_t ts, + qreal timeSinceLastRedraw) +{ + QMutexLocker locker(&dataMutex); + + for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { + calculateBallisticsForChannel(channelNr, ts, + timeSinceLastRedraw); + } +} + +void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, + int width, int height, float peakHold) +{ + QMutexLocker locker(&dataMutex); + + if (peakHold < minimumInputLevel) { + painter.fillRect(x, y, width, height, backgroundNominalColor); + } else if (peakHold < warningLevel) { + painter.fillRect(x, y, width, height, foregroundNominalColor); + } else if (peakHold < errorLevel) { + painter.fillRect(x, y, width, height, foregroundWarningColor); + } else if (peakHold <= clipLevel) { + painter.fillRect(x, y, width, height, foregroundErrorColor); + } else { + painter.fillRect(x, y, width, height, clipColor); + } +} + +void VolumeMeter::paintTicks(QPainter &painter, int x, int y, + int width, int height) +{ + qreal scale = width / minimumLevel; + + painter.setFont(tickFont); + painter.setPen(majorTickColor); + + // Draw major tick lines and numeric indicators. + for (int i = 0; i >= minimumLevel; i-= 5) { + int position = x + width - (i * scale) - 1; + QString str = QString::number(i); + + if (i == 0 || i == -5) { + painter.drawText(position - 3, height, str); + } else { + painter.drawText(position - 5, height, str); + } + painter.drawLine(position, y, position, y + 2); + } + + // Draw minor tick lines. + painter.setPen(minorTickColor); + for (int i = 0; i >= minimumLevel; i--) { + int position = x + width - (i * scale) - 1; + + if (i % 5 != 0) { + painter.drawLine(position, y, position, y + 1); + } + } +} + +void VolumeMeter::paintMeter(QPainter &painter, int x, int y, + int width, int height, float magnitude, float peak, float peakHold) +{ + qreal scale = width / minimumLevel; + + QMutexLocker locker(&dataMutex); + int minimumPosition = x + 0; + int maximumPosition = x + width; + int magnitudePosition = x + width - (magnitude * scale); + int peakPosition = x + width - (peak * scale); + int peakHoldPosition = x + width - (peakHold * scale); + int warningPosition = x + width - (warningLevel * scale); + int errorPosition = x + width - (errorLevel * scale); + + int nominalLength = warningPosition - minimumPosition; + int warningLength = errorPosition - warningPosition; + int errorLength = maximumPosition - errorPosition; + locker.unlock(); + + if (peakPosition < minimumPosition) { + painter.fillRect( + minimumPosition, y, + nominalLength, height, + backgroundNominalColor); + painter.fillRect( + warningPosition, y, + warningLength, height, + backgroundWarningColor); + painter.fillRect( + errorPosition, y, + errorLength, height, + backgroundErrorColor); + + } else if (peakPosition < warningPosition) { + painter.fillRect( + minimumPosition, y, + peakPosition - minimumPosition, height, + foregroundNominalColor); + painter.fillRect( + peakPosition, y, + warningPosition - peakPosition, height, + backgroundNominalColor); + painter.fillRect( + warningPosition, y, + warningLength, height, + backgroundWarningColor); + painter.fillRect(errorPosition, y, + errorLength, height, + backgroundErrorColor); + + } else if (peakPosition < errorPosition) { + painter.fillRect( + minimumPosition, y, + nominalLength, height, + foregroundNominalColor); + painter.fillRect( + warningPosition, y, + peakPosition - warningPosition, height, + foregroundWarningColor); + painter.fillRect( + peakPosition, y, + errorPosition - peakPosition, height, + backgroundWarningColor); + painter.fillRect( + errorPosition, y, + errorLength, height, + backgroundErrorColor); + + } else if (peakPosition < maximumPosition) { + painter.fillRect( + minimumPosition, y, + nominalLength, height, + foregroundNominalColor); + painter.fillRect( + warningPosition, y, + warningLength, height, + foregroundWarningColor); + painter.fillRect( + errorPosition, y, + peakPosition - errorPosition, height, + foregroundErrorColor); + painter.fillRect( + peakPosition, y, + maximumPosition - peakPosition, height, + backgroundErrorColor); + + } else { + qreal end = errorLength + warningLength + nominalLength; + painter.fillRect( + minimumPosition, y, + end, height, + QBrush(foregroundErrorColor)); + } + + if (peakHoldPosition - 3 < minimumPosition) { + // Peak-hold below minimum, no drawing. + + } else if (peakHoldPosition < warningPosition) { + painter.fillRect( + peakHoldPosition - 3, y, + 3, height, + foregroundNominalColor); + + } else if (peakHoldPosition < errorPosition) { + painter.fillRect( + peakHoldPosition - 3, y, + 3, height, + foregroundWarningColor); + + } else { + painter.fillRect( + peakHoldPosition - 3, y, + 3, height, + foregroundErrorColor); + } + + if (magnitudePosition - 3 < minimumPosition) { + // Magnitude below minimum, no drawing. + + } else if (magnitudePosition < warningPosition) { + painter.fillRect( + magnitudePosition - 3, y, + 3, height, + magnitudeColor); + + } else if (magnitudePosition < errorPosition) { + painter.fillRect( + magnitudePosition - 3, y, + 3, height, + magnitudeColor); + + } else { + painter.fillRect( + magnitudePosition - 3, y, + 3, height, + magnitudeColor); } } @@ -328,46 +796,54 @@ void VolumeMeter::paintEvent(QPaintEvent *event) { UNUSED_PARAMETER(event); - QPainter painter(this); - QLinearGradient gradient; + uint64_t ts = os_gettime_ns(); + qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; int width = size().width(); int height = size().height(); - calcLevels(); + handleChannelCofigurationChange(); + calculateBallistics(ts, timeSinceLastRedraw); + bool idle = detectIdle(ts); - int scaledMag = int((float)width * curMag); - int scaledPeak = int((float)width * curPeak); - int scaledPeakHold = int((float)width * curPeakHold); + // Draw the ticks in a off-screen buffer when the widget changes size. + QSize tickPaintCacheSize = QSize(width, 9); + if (tickPaintCache == NULL || + tickPaintCache->size() != tickPaintCacheSize) { + delete tickPaintCache; + tickPaintCache = new QPixmap(tickPaintCacheSize); - gradient.setStart(qreal(scaledMag), 0); - gradient.setFinalStop(qreal(scaledPeak), 0); - gradient.setColorAt(0, magColor); - gradient.setColorAt(1, peakColor); + QColor clearColor(0, 0, 0, 0); + tickPaintCache->fill(clearColor); - // RMS - painter.fillRect(0, 0, - scaledMag, height, - magColor); + QPainter tickPainter(tickPaintCache); + paintTicks(tickPainter, 6, 0, tickPaintCacheSize.width() - 6, + tickPaintCacheSize.height()); + tickPainter.end(); + } - // RMS - Peak gradient - painter.fillRect(scaledMag, 0, - scaledPeak - scaledMag + 1, height, - QBrush(gradient)); + // Actual painting of the widget starts here. + QPainter painter(this); + painter.drawPixmap(0, height - 9, *tickPaintCache); - // Background - painter.fillRect(scaledPeak, 0, - width - scaledPeak, height, - bkColor); + for (int channelNr = 0; channelNr < displayNrAudioChannels; + channelNr++) { + paintMeter(painter, + 5, channelNr * 4, width - 5, 3, + displayMagnitude[channelNr], displayPeak[channelNr], + displayPeakHold[channelNr]); - // Peak hold - if (peakHold == 1.0f) - scaledPeakHold--; - - painter.setPen(peakHoldColor); - painter.drawLine(scaledPeakHold, 0, - scaledPeakHold, height); + if (!idle) { + // By not drawing the input meter boxes the user can + // see that the audio stream has been stopped, without + // having too much visual impact. + paintInputMeter(painter, + 0, channelNr * 4, 3, 3, + displayInputPeakHold[channelNr]); + } + } + lastRedrawTime = ts; } void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) diff --git a/UI/volume-control.hpp b/UI/volume-control.hpp index 9330af7..390e684 100644 --- a/UI/volume-control.hpp +++ b/UI/volume-control.hpp @@ -13,38 +13,179 @@ class VolumeMeterTimer; class VolumeMeter : public QWidget { Q_OBJECT - Q_PROPERTY(QColor bkColor READ getBkColor WRITE setBkColor DESIGNABLE true) - Q_PROPERTY(QColor magColor READ getMagColor WRITE setMagColor DESIGNABLE true) - Q_PROPERTY(QColor peakColor READ getPeakColor WRITE setPeakColor DESIGNABLE true) - Q_PROPERTY(QColor peakHoldColor READ getPeakHoldColor WRITE setPeakHoldColor DESIGNABLE true) + Q_PROPERTY(QColor backgroundNominalColor + READ getBackgroundNominalColor + WRITE setBackgroundNominalColor DESIGNABLE true) + Q_PROPERTY(QColor backgroundWarningColor + READ getBackgroundWarningColor + WRITE setBackgroundWarningColor DESIGNABLE true) + Q_PROPERTY(QColor backgroundErrorColor + READ getBackgroundErrorColor + WRITE setBackgroundErrorColor DESIGNABLE true) + Q_PROPERTY(QColor foregroundNominalColor + READ getForegroundNominalColor + WRITE setForegroundNominalColor DESIGNABLE true) + Q_PROPERTY(QColor foregroundWarningColor + READ getForegroundWarningColor + WRITE setForegroundWarningColor DESIGNABLE true) + Q_PROPERTY(QColor foregroundErrorColor + READ getForegroundErrorColor + WRITE setForegroundErrorColor DESIGNABLE true) + Q_PROPERTY(QColor clipColor + READ getClipColor + WRITE setClipColor DESIGNABLE true) + Q_PROPERTY(QColor magnitudeColor + READ getMagnitudeColor + WRITE setMagnitudeColor DESIGNABLE true) + Q_PROPERTY(QColor majorTickColor + READ getMajorTickColor + WRITE setMajorTickColor DESIGNABLE true) + Q_PROPERTY(QColor minorTickColor + READ getMinorTickColor + WRITE setMinorTickColor DESIGNABLE true) + + // Levels are denoted in dBFS. + Q_PROPERTY(qreal minimumLevel + READ getMinimumLevel + WRITE setMinimumLevel DESIGNABLE true) + Q_PROPERTY(qreal warningLevel + READ getWarningLevel + WRITE setWarningLevel DESIGNABLE true) + Q_PROPERTY(qreal errorLevel + READ getErrorLevel + WRITE setErrorLevel DESIGNABLE true) + Q_PROPERTY(qreal clipLevel + READ getClipLevel + WRITE setClipLevel DESIGNABLE true) + Q_PROPERTY(qreal minimumInputLevel + READ getMinimumInputLevel + WRITE setMinimumInputLevel DESIGNABLE true) + + // Rates are denoted in dB/second. + Q_PROPERTY(qreal peakDecayRate + READ getPeakDecayRate + WRITE setPeakDecayRate DESIGNABLE true) + + // Time in seconds for the VU meter to integrate over. + Q_PROPERTY(qreal magnitudeIntegrationTime + READ getMagnitudeIntegrationTime + WRITE setMagnitudeIntegrationTime DESIGNABLE true) + + // Duration is denoted in seconds. + Q_PROPERTY(qreal peakHoldDuration + READ getPeakHoldDuration + WRITE setPeakHoldDuration DESIGNABLE true) + Q_PROPERTY(qreal inputPeakHoldDuration + READ getInputPeakHoldDuration + WRITE setInputPeakHoldDuration DESIGNABLE true) private: + obs_volmeter_t *obs_volmeter; static QWeakPointer updateTimer; QSharedPointer updateTimerRef; - float curMag = 0.0f, curPeak = 0.0f, curPeakHold = 0.0f; - inline void calcLevels(); + inline void resetLevels(); + inline void handleChannelCofigurationChange(); + inline bool detectIdle(uint64_t ts); + inline void calculateBallistics(uint64_t ts, + qreal timeSinceLastRedraw=0.0); + inline void calculateBallisticsForChannel(int channelNr, + uint64_t ts, qreal timeSinceLastRedraw); + + void paintInputMeter(QPainter &painter, int x, int y, + int width, int height, float peakHold); + void paintMeter(QPainter &painter, int x, int y, + int width, int height, + float magnitude, float peak, float peakHold); + void paintTicks(QPainter &painter, int x, int y, int width, int height); QMutex dataMutex; - float mag = 0.0f, peak = 0.0f, peakHold = 0.0f; - float multiple = 0.0f; - uint64_t lastUpdateTime = 0; - QColor bkColor, magColor, peakColor, peakHoldColor; + uint64_t currentLastUpdateTime = 0; + float currentMagnitude[MAX_AUDIO_CHANNELS]; + float currentPeak[MAX_AUDIO_CHANNELS]; + float currentInputPeak[MAX_AUDIO_CHANNELS]; + + QPixmap *tickPaintCache = NULL; + int displayNrAudioChannels = 0; + float displayMagnitude[MAX_AUDIO_CHANNELS]; + float displayPeak[MAX_AUDIO_CHANNELS]; + float displayPeakHold[MAX_AUDIO_CHANNELS]; + uint64_t displayPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; + float displayInputPeakHold[MAX_AUDIO_CHANNELS]; + uint64_t displayInputPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; + + QFont tickFont; + QColor backgroundNominalColor; + QColor backgroundWarningColor; + QColor backgroundErrorColor; + QColor foregroundNominalColor; + QColor foregroundWarningColor; + QColor foregroundErrorColor; + QColor clipColor; + QColor magnitudeColor; + QColor majorTickColor; + QColor minorTickColor; + qreal minimumLevel; + qreal warningLevel; + qreal errorLevel; + qreal clipLevel; + qreal minimumInputLevel; + qreal peakDecayRate; + qreal magnitudeIntegrationTime; + qreal peakHoldDuration; + qreal inputPeakHoldDuration; + + uint64_t lastRedrawTime = 0; public: - explicit VolumeMeter(QWidget *parent = 0); + explicit VolumeMeter(QWidget *parent = 0, + obs_volmeter_t *obs_volmeter = 0); ~VolumeMeter(); - void setLevels(float nmag, float npeak, float npeakHold); - QColor getBkColor() const; - void setBkColor(QColor c); - QColor getMagColor() const; - void setMagColor(QColor c); - QColor getPeakColor() const; - void setPeakColor(QColor c); - QColor getPeakHoldColor() const; - void setPeakHoldColor(QColor c); + void setLevels( + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float inputPeak[MAX_AUDIO_CHANNELS]); + + QColor getBackgroundNominalColor() const; + void setBackgroundNominalColor(QColor c); + QColor getBackgroundWarningColor() const; + void setBackgroundWarningColor(QColor c); + QColor getBackgroundErrorColor() const; + void setBackgroundErrorColor(QColor c); + QColor getForegroundNominalColor() const; + void setForegroundNominalColor(QColor c); + QColor getForegroundWarningColor() const; + void setForegroundWarningColor(QColor c); + QColor getForegroundErrorColor() const; + void setForegroundErrorColor(QColor c); + QColor getClipColor() const; + void setClipColor(QColor c); + QColor getMagnitudeColor() const; + void setMagnitudeColor(QColor c); + QColor getMajorTickColor() const; + void setMajorTickColor(QColor c); + QColor getMinorTickColor() const; + void setMinorTickColor(QColor c); + qreal getMinimumLevel() const; + void setMinimumLevel(qreal v); + qreal getWarningLevel() const; + void setWarningLevel(qreal v); + qreal getErrorLevel() const; + void setErrorLevel(qreal v); + qreal getClipLevel() const; + void setClipLevel(qreal v); + qreal getMinimumInputLevel() const; + void setMinimumInputLevel(qreal v); + qreal getPeakDecayRate() const; + void setPeakDecayRate(qreal v); + qreal getMagnitudeIntegrationTime() const; + void setMagnitudeIntegrationTime(qreal v); + qreal getPeakHoldDuration() const; + void setPeakHoldDuration(qreal v); + qreal getInputPeakHoldDuration() const; + void setInputPeakHoldDuration(qreal v); protected: void paintEvent(QPaintEvent *event); @@ -85,8 +226,10 @@ private: obs_volmeter_t *obs_volmeter; static void OBSVolumeChanged(void *param, float db); - static void OBSVolumeLevel(void *data, float level, float mag, - float peak, float muted); + static void OBSVolumeLevel(void *data, + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float inputPeak[MAX_AUDIO_CHANNELS]); static void OBSVolumeMuted(void *data, calldata_t *calldata); void EmitConfigClicked(); @@ -94,7 +237,6 @@ private: private slots: void VolumeChanged(); void VolumeMuted(bool muted); - void VolumeLevel(float mag, float peak, float peakHold, bool muted); void SetMuted(bool checked); void SliderChanged(int vol); @@ -111,4 +253,6 @@ public: QString GetName() const; void SetName(const QString &newName); + + void SetMeterDecayRate(qreal q); }; diff --git a/UI/win-update/updater/http.cpp b/UI/win-update/updater/http.cpp index adf5652..13d0d9d 100644 --- a/UI/win-update/updater/http.cpp +++ b/UI/win-update/updater/http.cpp @@ -88,6 +88,8 @@ bool HTTPPostData(const wchar_t *url, const wchar_t *acceptTypes[] = {L"*/*", nullptr}; + const DWORD tlsProtocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; + responseBuf.clear(); /* -------------------------------------- * @@ -109,7 +111,7 @@ bool HTTPPostData(const wchar_t *url, /* -------------------------------------- * * connect to server */ - hSession = WinHttpOpen(L"OBS Updater/2.1", + hSession = WinHttpOpen(L"OBS Studio Updater/2.1", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, @@ -119,6 +121,9 @@ bool HTTPPostData(const wchar_t *url, return false; } + WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, + (LPVOID)&tlsProtocols, sizeof(tlsProtocols)); + hConnect = WinHttpConnect(hSession, hostName, secure ? INTERNET_DEFAULT_HTTPS_PORT diff --git a/UI/win-update/updater/updater.cpp b/UI/win-update/updater/updater.cpp index 10f850d..443c7fe 100644 --- a/UI/win-update/updater/updater.cpp +++ b/UI/win-update/updater/updater.cpp @@ -238,6 +238,7 @@ enum state_t { STATE_PENDING_DOWNLOAD, STATE_DOWNLOADING, STATE_DOWNLOADED, + STATE_INSTALL_FAILED, STATE_INSTALLED, }; @@ -296,7 +297,8 @@ struct update_t { void CleanPartialUpdate() { - if (state == STATE_INSTALLED) { + if (state == STATE_INSTALL_FAILED || + state == STATE_INSTALLED) { if (!previousFile.empty()) { DeleteFile(outputPath.c_str()); MyCopyFile(previousFile.c_str(), @@ -342,6 +344,8 @@ static inline void CleanupPartialUpdates() bool DownloadWorkerThread() { + const DWORD tlsProtocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; + HttpHandle hSession = WinHttpOpen(L"OBS Studio Updater/2.1", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, @@ -353,6 +357,9 @@ bool DownloadWorkerThread() return false; } + WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, + (LPVOID)&tlsProtocols, sizeof(tlsProtocols)); + HttpHandle hConnect = WinHttpConnect(hSession, L"obsproject.com", INTERNET_DEFAULT_HTTPS_PORT, 0); if (!hConnect) { @@ -823,6 +830,8 @@ static bool UpdateFile(update_t &file) return false; } + file.previousFile = oldFileRenamedPath; + int error_code; bool installed_ok; @@ -839,6 +848,8 @@ static bool UpdateFile(update_t &file) Status(L"Update failed: Couldn't " L"verify integrity of patched %s", curFileName); + + file.state = STATE_INSTALL_FAILED; return false; } @@ -848,6 +859,8 @@ static bool UpdateFile(update_t &file) L"check of patched " L"%s failed", curFileName); + + file.state = STATE_INSTALL_FAILED; return false; } } @@ -872,11 +885,12 @@ static bool UpdateFile(update_t &file) L"(error %d)", curFileName, GetLastError()); + + file.state = STATE_INSTALL_FAILED; return false; } - file.previousFile = oldFileRenamedPath; - file.state = STATE_INSTALLED; + file.state = STATE_INSTALLED; } else { if (file.patchable) { /* Uh oh, we thought we could patch something but it's @@ -890,6 +904,8 @@ static bool UpdateFile(update_t &file) * make sure they exist */ CreateFoldersForPath(file.outputPath.c_str()); + file.previousFile = L""; + bool success = !!MyCopyFile( file.tempPath.c_str(), file.outputPath.c_str()); @@ -897,11 +913,11 @@ static bool UpdateFile(update_t &file) Status(L"Update failed: Couldn't install %s (error %d)", file.outputPath.c_str(), GetLastError()); + file.state = STATE_INSTALL_FAILED; return false; } - file.previousFile = L""; - file.state = STATE_INSTALLED; + file.state = STATE_INSTALLED; } return true; @@ -1211,9 +1227,25 @@ static bool Update(wchar_t *cmdLine) /* ------------------------------------- * * Install updates */ + int updatesInstalled = 0; + int lastPosition = 0; + + SendDlgItemMessage(hwndMain, IDC_PROGRESS, + PBM_SETPOS, 0, 0); + for (update_t &update : updates) { - if (!UpdateFile(update)) + if (!UpdateFile(update)) { return false; + } else { + updatesInstalled++; + int position = (int)(((float)updatesInstalled / + (float)completedUpdates) * 100.0f); + if (position > lastPosition) { + lastPosition = position; + SendDlgItemMessage(hwndMain, IDC_PROGRESS, + PBM_SETPOS, position, 0); + } + } } /* If we get here, all updates installed successfully so we can purge @@ -1227,6 +1259,9 @@ static bool Update(wchar_t *cmdLine) DeleteFile(update.tempPath.c_str()); } + SendDlgItemMessage(hwndMain, IDC_PROGRESS, + PBM_SETPOS, 100, 0); + Status(L"Update complete."); SetDlgItemText(hwndMain, IDC_BUTTON, L"Launch OBS"); return true; diff --git a/UI/window-basic-adv-audio.cpp b/UI/window-basic-adv-audio.cpp index 33fe0cb..2894f6b 100644 --- a/UI/window-basic-adv-audio.cpp +++ b/UI/window-basic-adv-audio.cpp @@ -42,7 +42,7 @@ OBSBasicAdvAudio::OBSBasicAdvAudio(QWidget *parent) label = new QLabel(QTStr("Basic.AdvAudio.SyncOffset")); label->setAlignment(Qt::AlignHCenter); mainLayout->addWidget(label, 0, idx++); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO label = new QLabel(QTStr("Basic.AdvAudio.Monitoring")); label->setAlignment(Qt::AlignHCenter); mainLayout->addWidget(label, 0, idx++); diff --git a/UI/window-basic-auto-config-test.cpp b/UI/window-basic-auto-config-test.cpp index 04b44e1..e79cc58 100644 --- a/UI/window-basic-auto-config-test.cpp +++ b/UI/window-basic-auto-config-test.cpp @@ -157,6 +157,8 @@ static inline void string_depad_key(string &key) } } +const char *FindAudioEncoderFromCodec(const char *type); + void AutoConfigTestPage::TestBandwidthThread() { bool connected = false; @@ -189,9 +191,6 @@ void AutoConfigTestPage::TestBandwidthThread() "test_aac", nullptr, 0, nullptr); OBSService service = obs_service_create(serverType, "test_service", nullptr, nullptr); - OBSOutput output = obs_output_create("rtmp_output", - "test_stream", nullptr, nullptr); - obs_output_release(output); obs_encoder_release(vencoder); obs_encoder_release(aencoder); obs_service_release(service); @@ -246,24 +245,53 @@ void AutoConfigTestPage::TestBandwidthThread() else GetServers(servers); - /* just use the first server if it only has one alternate server */ - if (servers.size() < 3) + /* just use the first server if it only has one alternate server, + * or if using Mixer due to its "auto" server */ + if (servers.size() < 3 || wiz->serviceName == "Mixer.com - FTL") { servers.resize(1); + } else if (wiz->service == AutoConfig::Service::Twitch && + wiz->twitchAuto) { + /* if using Twitch and "Auto" is available, test 3 closest + * server */ + servers.erase(servers.begin() + 1); + servers.resize(3); + } + /* -----------------------------------*/ - /* apply settings */ + /* apply service settings */ obs_service_update(service, service_settings); obs_service_apply_encoder_settings(service, vencoder_settings, aencoder_settings); - obs_encoder_update(vencoder, vencoder_settings); - obs_encoder_update(aencoder, aencoder_settings); + /* -----------------------------------*/ + /* create output */ + + const char *output_type = obs_service_get_output_type(service); + if (!output_type) + output_type = "rtmp_output"; + + OBSOutput output = obs_output_create(output_type, + "test_stream", nullptr, nullptr); + obs_output_release(output); obs_output_update(output, output_settings); + const char *audio_codec = + obs_output_get_supported_audio_codecs(output); + + if (strcmp(audio_codec, "aac") != 0) { + const char *id = FindAudioEncoderFromCodec(audio_codec); + aencoder = obs_audio_encoder_create(id, + "test_audio", nullptr, 0, nullptr); + obs_encoder_release(aencoder); + } + /* -----------------------------------*/ /* connect encoders/services/outputs */ + obs_encoder_update(vencoder, vencoder_settings); + obs_encoder_update(aencoder, aencoder_settings); obs_encoder_set_video(vencoder, obs_get_video()); obs_encoder_set_audio(aencoder, obs_get_audio()); diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index 8411350..43e4c9a 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -324,10 +324,8 @@ bool AutoConfigStreamPage::validatePage() if (!wiz->customServer) { if (wiz->serviceName == "Twitch") wiz->service = AutoConfig::Service::Twitch; - else if (wiz->serviceName == "hitbox.tv") - wiz->service = AutoConfig::Service::Hitbox; - else if (wiz->serviceName == "beam.pro") - wiz->service = AutoConfig::Service::Beam; + else if (wiz->serviceName == "Smashcast") + wiz->service = AutoConfig::Service::Smashcast; else wiz->service = AutoConfig::Service::Other; } else { @@ -368,11 +366,14 @@ void AutoConfigStreamPage::ServiceChanged() std::string service = QT_TO_UTF8(ui->service->currentText()); bool regionBased = service == "Twitch" || - service == "hitbox.tv" || - service == "beam.pro"; + service == "Smashcast"; bool testBandwidth = ui->doBandwidthTest->isChecked(); bool custom = ui->streamType->currentIndex() == 1; + /* Test three closest servers if "Auto" is available for Twitch */ + if (service == "Twitch" && wiz->twitchAuto) + regionBased = false; + ui->service->setVisible(!custom); ui->serviceLabel->setVisible(!custom); @@ -415,6 +416,7 @@ void AutoConfigStreamPage::UpdateKeyLink() { bool custom = ui->streamType->currentIndex() == 1; QString serviceName = ui->service->currentText(); + bool isYoutube = false; if (custom) serviceName = ""; @@ -432,6 +434,15 @@ void AutoConfigStreamPage::UpdateKeyLink() text += "\">"; text += QTStr("Basic.AutoConfig.StreamPage.StreamKey.LinkToSite"); text += ""; + + isYoutube = true; + } + + if (isYoutube) { + ui->doBandwidthTest->setChecked(false); + ui->doBandwidthTest->setEnabled(false); + } else { + ui->doBandwidthTest->setEnabled(true); } ui->streamKeyLabel->setText(text); @@ -544,6 +555,13 @@ void AutoConfigStreamPage::UpdateCompleted() AutoConfig::AutoConfig(QWidget *parent) : QWizard(parent) { + calldata_t cd = {0}; + calldata_set_int(&cd, "seconds", 5); + + proc_handler_t *ph = obs_get_proc_handler(); + proc_handler_call(ph, "twitch_ingests_refresh", &cd); + calldata_free(&cd); + OBSBasic *main = reinterpret_cast(parent); main->EnableOutputs(false); @@ -568,6 +586,23 @@ AutoConfig::AutoConfig(QWidget *parent) baseResolutionCX = ovi.base_width; baseResolutionCY = ovi.base_height; + /* ----------------------------------------- */ + /* check to see if Twitch's "auto" available */ + + OBSData twitchSettings = obs_data_create(); + obs_data_release(twitchSettings); + + obs_data_set_string(twitchSettings, "service", "Twitch"); + + obs_properties_t *props = obs_get_service_properties("rtmp_common"); + obs_properties_apply_settings(props, twitchSettings); + + obs_property_t *p = obs_properties_get(props, "server"); + const char *first = obs_property_list_item_string(p, 0); + twitchAuto = strcmp(first, "auto") == 0; + + obs_properties_destroy(props); + /* ----------------------------------------- */ /* load service/servers */ @@ -674,7 +709,7 @@ bool AutoConfig::CanTestServer(const char *server) } else if (regionOther) { return true; } - } else if (service == Service::Hitbox) { + } else if (service == Service::Smashcast) { if (strcmp(server, "Default") == 0) { return true; } else if (astrcmp_n(server, "US-West:", 8) == 0 || @@ -689,17 +724,6 @@ bool AutoConfig::CanTestServer(const char *server) } else if (regionOther) { return true; } - } else if (service == Service::Beam) { - if (astrcmp_n(server, "US:", 3) == 0) { - return regionUS; - } else if (astrcmp_n(server, "EU:", 3) == 0) { - return regionEU; - } else if (astrcmp_n(server, "South Korea:", 12) == 0 || - astrcmp_n(server, "Asia:", 5) == 0) { - return regionAsia; - } else if (regionOther) { - return true; - } } else { return true; } diff --git a/UI/window-basic-auto-config.hpp b/UI/window-basic-auto-config.hpp index 1fbc692..3478df2 100644 --- a/UI/window-basic-auto-config.hpp +++ b/UI/window-basic-auto-config.hpp @@ -33,8 +33,7 @@ class AutoConfig : public QWizard { enum class Service { Twitch, - Hitbox, - Beam, + Smashcast, Other }; @@ -88,6 +87,7 @@ class AutoConfig : public QWizard { bool customServer = false; bool bandwidthTest = false; bool testRegions = true; + bool twitchAuto = false; bool regionUS = true; bool regionEU = true; bool regionAsia = true; diff --git a/UI/window-basic-filters.cpp b/UI/window-basic-filters.cpp index 744e3f7..d792b1e 100644 --- a/UI/window-basic-filters.cpp +++ b/UI/window-basic-filters.cpp @@ -87,6 +87,16 @@ OBSBasicFilters::OBSBasicFilters(QWidget *parent, OBSSource source_) SLOT(EffectFilterNameEdited(QWidget*, QAbstractItemDelegate::EndEditHint))); + QPushButton *close = ui->buttonBox->button(QDialogButtonBox::Close); + connect(close, SIGNAL(clicked()), this, SLOT(close())); + close->setDefault(true); + + ui->buttonBox->button(QDialogButtonBox::Reset)->setText( + QTStr("Defaults")); + + connect(ui->buttonBox->button(QDialogButtonBox::Reset), + SIGNAL(clicked()), this, SLOT(ResetFilters())); + uint32_t flags = obs_source_get_output_flags(source); bool audio = (flags & OBS_SOURCE_AUDIO) != 0; bool audioOnly = (flags & OBS_SOURCE_VIDEO) == 0; @@ -148,6 +158,7 @@ inline OBSSource OBSBasicFilters::GetFilter(int row, bool async) void OBSBasicFilters::UpdatePropertiesView(int row, bool async) { if (view) { + updatePropertiesSignal.Disconnect(); ui->rightLayout->removeWidget(view); view->deleteLater(); view = nullptr; @@ -163,6 +174,11 @@ void OBSBasicFilters::UpdatePropertiesView(int row, bool async) (PropertiesReloadCallback)obs_source_properties, (PropertiesUpdateCallback)obs_source_update); + updatePropertiesSignal.Connect(obs_source_get_signal_handler(filter), + "update_properties", + OBSBasicFilters::UpdateProperties, + this); + obs_data_release(settings); view->setMaximumHeight(250); @@ -171,6 +187,12 @@ void OBSBasicFilters::UpdatePropertiesView(int row, bool async) view->show(); } +void OBSBasicFilters::UpdateProperties(void *data, calldata_t *) +{ + QMetaObject::invokeMethod(static_cast(data)->view, + "ReloadProperties"); +} + void OBSBasicFilters::AddFilter(OBSSource filter) { uint32_t flags = obs_source_get_output_flags(filter); @@ -339,6 +361,12 @@ QMenu *OBSBasicFilters::CreateAddFilterPopupMenu(bool async) vector types; while (obs_enum_filter_types(idx++, &type_str)) { const char *name = obs_source_get_display_name(type_str); + uint32_t caps = obs_get_source_output_flags(type_str); + + if ((caps & OBS_SOURCE_DEPRECATED) != 0) + continue; + if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) + continue; auto it = types.begin(); for (; it != types.end(); ++it) { @@ -573,6 +601,7 @@ void OBSBasicFilters::on_moveAsyncFilterDown_clicked() void OBSBasicFilters::on_asyncFilters_GotFocus() { UpdatePropertiesView(ui->asyncFilters->currentRow(), true); + isAsync = true; } void OBSBasicFilters::on_asyncFilters_currentRowChanged(int row) @@ -614,6 +643,7 @@ void OBSBasicFilters::on_moveEffectFilterDown_clicked() void OBSBasicFilters::on_effectFilters_GotFocus() { UpdatePropertiesView(ui->effectFilters->currentRow(), false); + isAsync = false; } void OBSBasicFilters::on_effectFilters_currentRowChanged(int row) @@ -739,3 +769,23 @@ void OBSBasicFilters::EffectFilterNameEdited(QWidget *editor, FilterNameEdited(editor, ui->effectFilters); UNUSED_PARAMETER(endHint); } + +void OBSBasicFilters::ResetFilters() +{ + QListWidget *list = isAsync ? ui->asyncFilters : ui->effectFilters; + int row = list->currentRow(); + + OBSSource filter = GetFilter(row, isAsync); + + if (!filter) + return; + + obs_data_t *settings = obs_source_get_settings(filter); + obs_data_clear(settings); + obs_data_release(settings); + + if (!view->DeferUpdate()) + obs_source_update(filter, nullptr); + + view->RefreshProperties(); +} diff --git a/UI/window-basic-filters.hpp b/UI/window-basic-filters.hpp index 2438976..cc655a4 100644 --- a/UI/window-basic-filters.hpp +++ b/UI/window-basic-filters.hpp @@ -45,6 +45,7 @@ private: OBSSignal removeSourceSignal; OBSSignal renameSourceSignal; + OBSSignal updatePropertiesSignal; inline OBSSource GetFilter(int row, bool async); @@ -56,6 +57,7 @@ private: static void OBSSourceReordered(void *param, calldata_t *data); static void SourceRemoved(void *param, calldata_t *data); static void SourceRenamed(void *param, calldata_t *data); + static void UpdateProperties(void *data, calldata_t *params); static void DrawPreview(void *data, uint32_t cx, uint32_t cy); QMenu *CreateAddFilterPopupMenu(bool async); @@ -68,12 +70,15 @@ private: void FilterNameEdited(QWidget *editor, QListWidget *list); + bool isAsync; + private slots: void AddFilter(OBSSource filter); void RemoveFilter(OBSSource filter); void ReorderFilters(); void RenameAsyncFilter(); void RenameEffectFilter(); + void ResetFilters(); void AddFilterFromAction(); diff --git a/UI/window-basic-main-dropfiles.cpp b/UI/window-basic-main-dropfiles.cpp index 6d094f8..2622ab1 100644 --- a/UI/window-basic-main-dropfiles.cpp +++ b/UI/window-basic-main-dropfiles.cpp @@ -19,6 +19,10 @@ static const char *imageExtensions[] = { "bmp", "tga", "png", "jpg", "jpeg", "gif", nullptr }; +static const char *htmlExtensions[] = { + "htm", "html", nullptr +}; + static const char *mediaExtensions[] = { "3ga", "669", "a52", "aac", "ac3", "adt", "adts", "aif", "aifc", "aiff", "amb", "amr", "aob", "ape", "au", "awb", "caf", "dts", @@ -26,7 +30,7 @@ static const char *mediaExtensions[] = { "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", + "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", @@ -95,8 +99,17 @@ void OBSBasic::AddDropSource(const char *data, DropType image) name = QUrl::fromLocalFile(QString(data)).fileName(); type = "ffmpeg_source"; break; + case DropType_Html: + obs_data_set_bool(settings, "is_local_file", true); + obs_data_set_string(settings, "local_file", data); + name = QUrl::fromLocalFile(QString(data)).fileName(); + type = "browser_source"; + break; } + if (!obs_source_get_display_name(type)) + return; + if (name.isEmpty()) name = obs_source_get_display_name(type); source = obs_source_create(type, @@ -147,46 +160,27 @@ void OBSBasic::dropEvent(QDropEvent *event) const char **cmp; - cmp = textExtensions; - while (*cmp) { - if (strcmp(*cmp, suffix) == 0) { - AddDropSource(QT_TO_UTF8(file), - DropType_Text); - found = true; - break; - } +#define CHECK_SUFFIX(extensions, type) \ +cmp = extensions; \ +while (*cmp) { \ + if (strcmp(*cmp, suffix) == 0) { \ + AddDropSource(QT_TO_UTF8(file), type); \ + found = true; \ + break; \ + } \ +\ + cmp++; \ +} \ +\ +if (found) \ + continue; - cmp++; - } + CHECK_SUFFIX(textExtensions, DropType_Text); + CHECK_SUFFIX(htmlExtensions, DropType_Html); + CHECK_SUFFIX(imageExtensions, DropType_Image); + CHECK_SUFFIX(mediaExtensions, DropType_Media); - if (found) - continue; - - cmp = imageExtensions; - while (*cmp) { - if (strcmp(*cmp, suffix) == 0) { - AddDropSource(QT_TO_UTF8(file), - DropType_Image); - found = true; - break; - } - - cmp++; - } - - if (found) - continue; - - cmp = mediaExtensions; - while (*cmp) { - if (strcmp(*cmp, suffix) == 0) { - AddDropSource(QT_TO_UTF8(file), - DropType_Media); - break; - } - - cmp++; - } +#undef CHECK_SUFFIX } } else if (mimeData->hasText()) { AddDropSource(QT_TO_UTF8(mimeData->text()), DropType_RawText); diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 1dcd873..16039a3 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -313,12 +313,6 @@ void SimpleOutput::LoadRecordingPreset() SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) { - streamOutput = obs_output_create("rtmp_output", "simple_stream", - nullptr, nullptr); - if (!streamOutput) - throw "Failed to create stream output (simple output)"; - obs_output_release(streamOutput); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) @@ -334,16 +328,6 @@ SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) "simple_aac", 0)) throw "Failed to create aac streaming encoder (simple output)"; - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), - "starting", OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), - "stopping", OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), - "start", OBSStartStreaming, this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), - "stop", OBSStopStreaming, this); - LoadRecordingPreset(); if (!ffmpegOutput) { @@ -571,20 +555,17 @@ void SimpleOutput::UpdateStreamingSettings_amd(obs_data_t *settings, // Static Properties obs_data_set_int(settings, "Usage", 0); obs_data_set_int(settings, "Profile", 100); // High - obs_data_set_string(settings, "profile", "high"); // High - + // Rate Control Properties - obs_data_set_int(settings, "RateControlMethod", 1); - obs_data_set_string(settings, "rate_control", "CBR"); + obs_data_set_int(settings, "RateControlMethod", 3); obs_data_set_int(settings, "Bitrate.Target", bitrate); - obs_data_set_int(settings, "bitrate", bitrate); obs_data_set_int(settings, "FillerData", 1); obs_data_set_int(settings, "VBVBuffer", 1); obs_data_set_int(settings, "VBVBuffer.Size", bitrate); - + // Picture Control Properties obs_data_set_double(settings, "KeyframeInterval", 2.0); - obs_data_set_int(settings, "keyint_sec", 2); + obs_data_set_int(settings, "BFrame.Pattern", 0); } void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) @@ -594,11 +575,9 @@ void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) // Static Properties obs_data_set_int(settings, "Usage", 0); obs_data_set_int(settings, "Profile", 100); // High - obs_data_set_string(settings, "profile", "high"); // High // Rate Control Properties obs_data_set_int(settings, "RateControlMethod", 0); - obs_data_set_string(settings, "rate_control", "CQP"); obs_data_set_int(settings, "QP.IFrame", cqp); obs_data_set_int(settings, "QP.PFrame", cqp); obs_data_set_int(settings, "QP.BFrame", cqp); @@ -607,7 +586,7 @@ void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) // Picture Control Properties obs_data_set_double(settings, "KeyframeInterval", 2.0); - obs_data_set_int(settings, "keyint_sec", 2); + obs_data_set_int(settings, "BFrame.Pattern", 0); // Update and release obs_encoder_update(h264Recording, settings); @@ -650,15 +629,95 @@ inline void SimpleOutput::SetupOutputs() } } +const char *FindAudioEncoderFromCodec(const char *type) +{ + const char *alt_enc_id = nullptr; + size_t i = 0; + + while (obs_enum_encoder_types(i++, &alt_enc_id)) { + const char *codec = obs_get_encoder_codec(alt_enc_id); + if (strcmp(type, codec) == 0) { + return alt_enc_id; + } + } + + return nullptr; +} + bool SimpleOutput::StartStreaming(obs_service_t *service) { if (!Active()) SetupOutputs(); + /* --------------------- */ + + const char *type = obs_service_get_output_type(service); + if (!type) + type = "rtmp_output"; + + /* XXX: this is messy and disgusting and should be refactored */ + if (outputType != type) { + streamDelayStarting.Disconnect(); + streamStopping.Disconnect(); + startStreaming.Disconnect(); + stopStreaming.Disconnect(); + + streamOutput = obs_output_create(type, "simple_stream", + nullptr, nullptr); + if (!streamOutput) { + blog(LOG_WARNING, "Creation of stream output type '%s' " + "failed!", type); + return false; + } + obs_output_release(streamOutput); + + streamDelayStarting.Connect( + obs_output_get_signal_handler(streamOutput), + "starting", OBSStreamStarting, this); + streamStopping.Connect( + obs_output_get_signal_handler(streamOutput), + "stopping", OBSStreamStopping, this); + + startStreaming.Connect( + obs_output_get_signal_handler(streamOutput), + "start", OBSStartStreaming, this); + stopStreaming.Connect( + obs_output_get_signal_handler(streamOutput), + "stop", OBSStopStreaming, this); + + const char *codec = + obs_output_get_supported_audio_codecs(streamOutput); + if (!codec) { + return false; + } + + if (strcmp(codec, "aac") != 0) { + const char *id = FindAudioEncoderFromCodec(codec); + int audioBitrate = GetAudioBitrate(); + obs_data_t *settings = obs_data_create(); + obs_data_set_int(settings, "bitrate", audioBitrate); + + aacStreaming = obs_audio_encoder_create(id, + "alt_audio_enc", nullptr, 0, nullptr); + obs_encoder_release(aacStreaming); + if (!aacStreaming) + return false; + + obs_encoder_update(aacStreaming, settings); + obs_encoder_set_audio(aacStreaming, obs_get_audio()); + + obs_data_release(settings); + } + + outputType = type; + } + obs_output_set_video_encoder(streamOutput, h264Streaming); obs_output_set_audio_encoder(streamOutput, aacStreaming, 0); obs_output_set_service(streamOutput, service); + /* --------------------- */ + bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); int retryDelay = config_get_uint(main->Config(), "Output", @@ -700,6 +759,13 @@ bool SimpleOutput::StartStreaming(obs_service_t *service) return true; } + const char *error = obs_output_get_last_error(streamOutput); + bool has_last_error = error && *error; + + blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", + type, + has_last_error ? " Last Error: " : "", + has_last_error ? error : ""); return false; } @@ -778,7 +844,7 @@ bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) int rbSize = config_get_int(main->Config(), "SimpleOutput", "RecRBSize"); - os_dir_t *dir = path ? os_opendir(path) : nullptr; + os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr; if (!dir) { if (main->isVisible()) @@ -854,9 +920,15 @@ bool SimpleOutput::StartRecording() if (!ConfigureRecording(false)) return false; if (!obs_output_start(fileOutput)) { + QString error_reason; + const char *error = obs_output_get_last_error(fileOutput); + if (error) + error_reason = QT_UTF8(error); + else + error_reason = QTStr("Output.StartFailedGeneric"); QMessageBox::critical(main, - QTStr("Output.StartRecordingFailed"), - QTStr("Output.StartFailedGeneric")); + QTStr("Output.StartRecordingFailed"), + error_reason); return false; } @@ -924,9 +996,12 @@ struct AdvancedOutput : BasicOutputHandler { OBSEncoder h264Streaming; OBSEncoder h264Recording; + OBSEncoder streamAudioEnc; + bool ffmpegOutput; bool ffmpegRecording; bool useStreamEncoder; + bool usesBitrate = false; string aacEncoderID[MAX_AUDIO_MIXES]; @@ -945,10 +1020,13 @@ struct AdvancedOutput : BasicOutputHandler { virtual bool StartStreaming(obs_service_t *service) override; virtual bool StartRecording() override; + virtual bool StartReplayBuffer() override; virtual void StopStreaming(bool force) override; virtual void StopRecording(bool force) override; + virtual void StopReplayBuffer(bool force) override; virtual bool StreamingActive() const override; virtual bool RecordingActive() const override; + virtual bool ReplayBufferActive() const override; }; static OBSData GetDataFromJsonFile(const char *jsonFile) @@ -986,11 +1064,14 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); - streamOutput = obs_output_create("rtmp_output", "adv_stream", - nullptr, nullptr); - if (!streamOutput) - throw "Failed to create stream output (advanced output)"; - obs_output_release(streamOutput); + const char *rate_control = obs_data_get_string( + useStreamEncoder ? streamEncSettings : recordEncSettings, + "rate_control"); + if (!rate_control) + rate_control = ""; + usesBitrate = astrcmpi(rate_control, "CBR") == 0 || + astrcmpi(rate_control, "VBR") == 0 || + astrcmpi(rate_control, "ABR") == 0; if (ffmpegOutput) { fileOutput = obs_output_create("ffmpeg_output", @@ -1000,6 +1081,33 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) "(advanced output)"; obs_output_release(fileOutput); } else { + bool useReplayBuffer = config_get_bool(main->Config(), + "AdvOut", "RecRB"); + if (useReplayBuffer) { + const char *str = config_get_string(main->Config(), + "Hotkeys", "ReplayBuffer"); + obs_data_t *hotkey = obs_data_create_from_json(str); + replayBuffer = obs_output_create("replay_buffer", + Str("ReplayBuffer"), nullptr, hotkey); + + obs_data_release(hotkey); + if (!replayBuffer) + throw "Failed to create replay buffer output " + "(simple output)"; + obs_output_release(replayBuffer); + + signal_handler_t *signal = + obs_output_get_signal_handler( + replayBuffer); + + startReplayBuffer.Connect(signal, "start", + OBSStartReplayBuffer, this); + stopReplayBuffer.Connect(signal, "stop", + OBSStopReplayBuffer, this); + replayBufferStopping.Connect(signal, "stopping", + OBSReplayBufferStopping, this); + } + fileOutput = obs_output_create("ffmpeg_muxer", "adv_file_output", nullptr, nullptr); if (!fileOutput) @@ -1035,16 +1143,6 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) "(advanced output)"; } - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), - "starting", OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), - "stopping", OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), - "start", OBSStartStreaming, this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), - "stop", OBSStopStreaming, this); - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); stopRecording.Connect(obs_output_get_signal_handler(fileOutput), @@ -1094,12 +1192,6 @@ inline void AdvancedOutput::SetupStreaming() "Rescale"); const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RescaleRes"); - bool multitrack = config_get_bool(main->Config(), "AdvOut", - "Multitrack"); - int trackIndex = config_get_int(main->Config(), "AdvOut", - "TrackIndex"); - int trackCount = config_get_int(main->Config(), "AdvOut", - "TrackCount"); unsigned int cx = 0; unsigned int cy = 0; @@ -1112,21 +1204,6 @@ inline void AdvancedOutput::SetupStreaming() obs_encoder_set_scaled_size(h264Streaming, cx, cy); obs_encoder_set_video(h264Streaming, obs_get_video()); - - obs_output_set_video_encoder(streamOutput, h264Streaming); - - if (multitrack) { - int i = 0; - for (; i < trackCount; i++) - obs_output_set_audio_encoder(streamOutput, aacTrack[i], - i); - for (; i < MAX_AUDIO_MIXES; i++) - obs_output_set_audio_encoder(streamOutput, nullptr, i); - - } else { - obs_output_set_audio_encoder(streamOutput, - aacTrack[trackIndex - 1], 0); - } } inline void AdvancedOutput::SetupRecording() @@ -1147,6 +1224,9 @@ inline void AdvancedOutput::SetupRecording() if (useStreamEncoder) { obs_output_set_video_encoder(fileOutput, h264Streaming); + if (replayBuffer) + obs_output_set_video_encoder(replayBuffer, + h264Streaming); } else { if (rescale && rescaleRes && *rescaleRes) { if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { @@ -1158,18 +1238,27 @@ inline void AdvancedOutput::SetupRecording() obs_encoder_set_scaled_size(h264Recording, cx, cy); obs_encoder_set_video(h264Recording, obs_get_video()); obs_output_set_video_encoder(fileOutput, h264Recording); + if (replayBuffer) + obs_output_set_video_encoder(replayBuffer, + h264Recording); } for (int i = 0; i < MAX_AUDIO_MIXES; i++) { if ((tracks & (1<Config(), "AdvOut", + "TrackIndex"); + + const char *type = obs_service_get_output_type(service); + if (!type) + type = "rtmp_output"; + + /* XXX: this is messy and disgusting and should be refactored */ + if (outputType != type) { + streamDelayStarting.Disconnect(); + streamStopping.Disconnect(); + startStreaming.Disconnect(); + stopStreaming.Disconnect(); + + streamOutput = obs_output_create(type, "adv_stream", + nullptr, nullptr); + if (!streamOutput) { + blog(LOG_WARNING, "Creation of stream output type '%s' " + "failed!", type); + return false; + } + obs_output_release(streamOutput); + + streamDelayStarting.Connect( + obs_output_get_signal_handler(streamOutput), + "starting", OBSStreamStarting, this); + streamStopping.Connect( + obs_output_get_signal_handler(streamOutput), + "stopping", OBSStreamStopping, this); + + startStreaming.Connect( + obs_output_get_signal_handler(streamOutput), + "start", OBSStartStreaming, this); + stopStreaming.Connect( + obs_output_get_signal_handler(streamOutput), + "stop", OBSStopStreaming, this); + + const char *codec = + obs_output_get_supported_audio_codecs(streamOutput); + if (!codec) { + return false; + } + + if (strcmp(codec, "aac") == 0) { + streamAudioEnc = aacTrack[trackIndex - 1]; + } else { + const char *id = FindAudioEncoderFromCodec(codec); + int audioBitrate = GetAudioBitrate(trackIndex - 1); + obs_data_t *settings = obs_data_create(); + obs_data_set_int(settings, "bitrate", audioBitrate); + + streamAudioEnc = obs_audio_encoder_create(id, + "alt_audio_enc", nullptr, + trackIndex - 1, nullptr); + if (!streamAudioEnc) + return false; + + obs_encoder_update(streamAudioEnc, settings); + obs_encoder_set_audio(streamAudioEnc, obs_get_audio()); + + obs_data_release(settings); + } + + outputType = type; + } + + obs_output_set_video_encoder(streamOutput, h264Streaming); + obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); + + /* --------------------- */ + obs_output_set_service(streamOutput, service); bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); @@ -1360,6 +1522,13 @@ bool AdvancedOutput::StartStreaming(obs_service_t *service) return true; } + const char *error = obs_output_get_last_error(streamOutput); + bool has_last_error = error && *error; + + blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", + type, + has_last_error ? " Last Error: " : "", + has_last_error ? error : ""); return false; } @@ -1398,7 +1567,7 @@ bool AdvancedOutput::StartRecording() "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - os_dir_t *dir = path ? os_opendir(path) : nullptr; + os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr; if (!dir) { if (main->isVisible()) @@ -1437,6 +1606,127 @@ bool AdvancedOutput::StartRecording() } if (!obs_output_start(fileOutput)) { + QString error_reason; + const char *error = obs_output_get_last_error(fileOutput); + if (error) + error_reason = QT_UTF8(error); + else + error_reason = QTStr("Output.StartFailedGeneric"); + QMessageBox::critical(main, + QTStr("Output.StartRecordingFailed"), + error_reason); + return false; + } + + return true; +} + +bool AdvancedOutput::StartReplayBuffer() +{ + const char *path; + const char *recFormat; + const char *filenameFormat; + bool noSpace = false; + bool overwriteIfExists = false; + const char *rbPrefix; + const char *rbSuffix; + int rbTime; + int rbSize; + + if (!useStreamEncoder) { + if (!ffmpegOutput) + UpdateRecordingSettings(); + } else if (!obs_output_active(streamOutput)) { + UpdateStreamSettings(); + } + + UpdateAudioSettings(); + + if (!Active()) + SetupOutputs(); + + if (!ffmpegOutput || ffmpegRecording) { + path = config_get_string(main->Config(), "AdvOut", + ffmpegRecording ? "FFFilePath" : "RecFilePath"); + recFormat = config_get_string(main->Config(), "AdvOut", + ffmpegRecording ? "FFExtension" : "RecFormat"); + filenameFormat = config_get_string(main->Config(), "Output", + "FilenameFormatting"); + overwriteIfExists = config_get_bool(main->Config(), "Output", + "OverwriteIfExists"); + noSpace = config_get_bool(main->Config(), "AdvOut", + ffmpegRecording ? + "FFFileNameWithoutSpace" : + "RecFileNameWithoutSpace"); + rbPrefix = config_get_string(main->Config(), "SimpleOutput", + "RecRBPrefix"); + rbSuffix = config_get_string(main->Config(), "SimpleOutput", + "RecRBSuffix"); + rbTime = config_get_int(main->Config(), "AdvOut", + "RecRBTime"); + rbSize = config_get_int(main->Config(), "AdvOut", + "RecRBSize"); + + os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr; + + if (!dir) { + if (main->isVisible()) + OBSMessageBox::information(main, + QTStr("Output.BadPath.Title"), + QTStr("Output.BadPath.Text")); + else + main->SysTrayNotify(QTStr("Output.BadPath.Text"), + QSystemTrayIcon::Warning); + return false; + } + + os_closedir(dir); + + string strPath; + strPath += path; + + char lastChar = strPath.back(); + if (lastChar != '/' && lastChar != '\\') + strPath += "/"; + + strPath += GenerateSpecifiedFilename(recFormat, noSpace, + filenameFormat); + ensure_directory_exists(strPath); + if (!overwriteIfExists) + FindBestFilename(strPath, noSpace); + + obs_data_t *settings = obs_data_create(); + string f; + + if (rbPrefix && *rbPrefix) { + f += rbPrefix; + if (f.back() != ' ') + f += " "; + } + + f += filenameFormat; + + if (rbSuffix && *rbSuffix) { + if (*rbSuffix != ' ') + f += " "; + f += rbSuffix; + } + + remove_reserved_file_characters(f); + + obs_data_set_string(settings, "directory", path); + obs_data_set_string(settings, "format", f.c_str()); + obs_data_set_string(settings, "extension", recFormat); + obs_data_set_int(settings, "max_time_sec", rbTime); + obs_data_set_int(settings, "max_size_mb", + usesBitrate ? 0 : rbSize); + + obs_output_update(replayBuffer, settings); + + obs_data_release(settings); + } + + if (!obs_output_start(replayBuffer)) { QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), QTStr("Output.StartFailedGeneric")); @@ -1462,6 +1752,14 @@ void AdvancedOutput::StopRecording(bool force) obs_output_stop(fileOutput); } +void AdvancedOutput::StopReplayBuffer(bool force) +{ + if (force) + obs_output_force_stop(replayBuffer); + else + obs_output_stop(replayBuffer); +} + bool AdvancedOutput::StreamingActive() const { return obs_output_active(streamOutput); @@ -1472,6 +1770,11 @@ bool AdvancedOutput::RecordingActive() const return obs_output_active(fileOutput); } +bool AdvancedOutput::ReplayBufferActive() const +{ + return obs_output_active(replayBuffer); +} + /* ------------------------------------------------------------------------ */ BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main) diff --git a/UI/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp index ec814a7..4c4842d 100644 --- a/UI/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -1,5 +1,7 @@ #pragma once +#include + class OBSBasic; struct BasicOutputHandler { @@ -12,6 +14,8 @@ struct BasicOutputHandler { bool replayBufferActive = false; OBSBasic *main; + std::string outputType; + OBSSignal startRecording; OBSSignal stopRecording; OBSSignal startReplayBuffer; diff --git a/UI/window-basic-main-profiles.cpp b/UI/window-basic-main-profiles.cpp index 7c5d426..160e13c 100644 --- a/UI/window-basic-main-profiles.cpp +++ b/UI/window-basic-main-profiles.cpp @@ -144,16 +144,16 @@ static bool CopyProfile(const char *fromPartial, const char *to) { os_glob_t *glob; char path[512]; + char dir[512]; int ret; - ret = GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles/"); + ret = GetConfigPath(dir, sizeof(dir), "obs-studio/basic/profiles/"); if (ret <= 0) { blog(LOG_WARNING, "Failed to get profiles config path"); return false; } - strcat(path, fromPartial); - strcat(path, "/*"); + snprintf(path, sizeof(path), "%s%s/*", dir, fromPartial); if (os_glob(path, 0, &glob) != 0) { blog(LOG_WARNING, "Failed to glob profile '%s'", fromPartial); @@ -186,6 +186,7 @@ bool OBSBasic::AddProfile(bool create_new, const char *title, const char *text, { std::string newName; std::string newDir; + std::string newPath; ConfigFile config; if (!GetProfileName(this, newName, newDir, title, text, init_text)) @@ -194,27 +195,29 @@ bool OBSBasic::AddProfile(bool create_new, const char *title, const char *text, std::string curDir = config_get_string(App()->GlobalConfig(), "Basic", "ProfileDir"); - char newPath[512]; - int ret = GetConfigPath(newPath, 512, "obs-studio/basic/profiles/"); + char baseDir[512]; + int ret = GetConfigPath(baseDir, sizeof(baseDir), + "obs-studio/basic/profiles/"); if (ret <= 0) { blog(LOG_WARNING, "Failed to get profiles config path"); return false; } - strcat(newPath, newDir.c_str()); + newPath = baseDir; + newPath += newDir; - if (os_mkdir(newPath) < 0) { + if (os_mkdir(newPath.c_str()) < 0) { blog(LOG_WARNING, "Failed to create profile directory '%s'", newDir.c_str()); return false; } if (!create_new) - CopyProfile(curDir.c_str(), newPath); + CopyProfile(curDir.c_str(), newPath.c_str()); - strcat(newPath, "/basic.ini"); + newPath += "/basic.ini"; - if (config.Open(newPath, CONFIG_OPEN_ALWAYS) != 0) { + if (config.Open(newPath.c_str(), CONFIG_OPEN_ALWAYS) != 0) { blog(LOG_ERROR, "Failed to open new config file '%s'", newDir.c_str()); return false; @@ -524,15 +527,28 @@ void OBSBasic::on_actionExportProfile_triggered() if (!folder.exists()) { folder.mkpath(outputDir); - QFile::copy(inputPath + currentProfile + "/basic.ini", - outputDir + "/basic.ini"); - QFile::copy(inputPath + currentProfile + "/service.json", - outputDir + "/service.json"); - QFile::copy(inputPath + currentProfile + "/streamEncoder.json", - outputDir + "/streamEncoder.json"); - QFile::copy(inputPath + currentProfile + "/recordEncoder.json", - outputDir + "/recordEncoder.json"); + } else { + if (QFile::exists(outputDir + "/basic.ini")) + QFile::remove(outputDir + "/basic.ini"); + + if (QFile::exists(outputDir + "/service.json")) + QFile::remove(outputDir + "/service.json"); + + if (QFile::exists(outputDir + "/streamEncoder.json")) + QFile::remove(outputDir + "/streamEncoder.json"); + + if (QFile::exists(outputDir + "/recordEncoder.json")) + QFile::remove(outputDir + "/recordEncoder.json"); } + + QFile::copy(inputPath + currentProfile + "/basic.ini", + outputDir + "/basic.ini"); + QFile::copy(inputPath + currentProfile + "/service.json", + outputDir + "/service.json"); + QFile::copy(inputPath + currentProfile + "/streamEncoder.json", + outputDir + "/streamEncoder.json"); + QFile::copy(inputPath + currentProfile + "/recordEncoder.json", + outputDir + "/recordEncoder.json"); } } @@ -581,6 +597,8 @@ void OBSBasic::ChangeProfile() config_save_safe(App()->GlobalConfig(), "tmp", nullptr); UpdateTitleBar(); + CheckForSimpleModeX264Fallback(); + blog(LOG_INFO, "Switched to profile '%s' (%s)", newName, newDir); blog(LOG_INFO, "------------------------------------------------"); @@ -588,3 +606,62 @@ void OBSBasic::ChangeProfile() if (api) api->on_event(OBS_FRONTEND_EVENT_PROFILE_CHANGED); } + +void OBSBasic::CheckForSimpleModeX264Fallback() +{ + const char *curStreamEncoder = config_get_string(basicConfig, + "SimpleOutput", "StreamEncoder"); + const char *curRecEncoder = config_get_string(basicConfig, + "SimpleOutput", "RecEncoder"); + bool qsv_supported = false; + bool amd_supported = false; + bool nve_supported = false; + bool changed = false; + size_t idx = 0; + const char *id; + + while (obs_enum_encoder_types(idx++, &id)) { + if (strcmp(id, "amd_amf_h264") == 0) + amd_supported = true; + else if (strcmp(id, "obs_qsv11") == 0) + qsv_supported = true; + else if (strcmp(id, "ffmpeg_nvenc") == 0) + nve_supported = true; + } + + auto CheckEncoder = [&] (const char *&name) + { + if (strcmp(name, SIMPLE_ENCODER_QSV) == 0) { + if (!qsv_supported) { + changed = true; + name = SIMPLE_ENCODER_X264; + return false; + } + } else if (strcmp(name, SIMPLE_ENCODER_NVENC) == 0) { + if (!nve_supported) { + changed = true; + name = SIMPLE_ENCODER_X264; + return false; + } + } else if (strcmp(name, SIMPLE_ENCODER_AMD) == 0) { + if (!amd_supported) { + changed = true; + name = SIMPLE_ENCODER_X264; + return false; + } + } + + return true; + }; + + if (!CheckEncoder(curStreamEncoder)) + config_set_string(basicConfig, + "SimpleOutput", "StreamEncoder", + curStreamEncoder); + if (!CheckEncoder(curRecEncoder)) + config_set_string(basicConfig, + "SimpleOutput", "RecEncoder", + curRecEncoder); + if (changed) + config_save_safe(basicConfig, "tmp", nullptr); +} diff --git a/UI/window-basic-main-scene-collections.cpp b/UI/window-basic-main-scene-collections.cpp index 088fb70..067c7b7 100644 --- a/UI/window-basic-main-scene-collections.cpp +++ b/UI/window-basic-main-scene-collections.cpp @@ -425,8 +425,12 @@ void OBSBasic::on_actionExportSceneCollection_triggered() string file = QT_TO_UTF8(exportFile); - if (!exportFile.isEmpty() && !exportFile.isNull()) + if (!exportFile.isEmpty() && !exportFile.isNull()) { + if (QFile::exists(exportFile)) + QFile::remove(exportFile); + QFile::copy(path + currentFile + ".json", exportFile); + } } void OBSBasic::ChangeSceneCollection() diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index 176d6bb..726eea1 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -110,7 +110,7 @@ void OBSBasic::TriggerQuickTransition(int id) if (GetCurrentTransition() != qt->source) SetTransition(qt->source); - TransitionToScene(source); + TransitionToScene(source, false, false, true); } } @@ -127,9 +127,17 @@ void OBSBasic::InitTransition(obs_source_t *transition) Qt::QueuedConnection); }; + auto onTransitionFullStop = [] (void *data, calldata_t*) { + OBSBasic *window = (OBSBasic*)data; + QMetaObject::invokeMethod(window, "TransitionFullyStopped", + Qt::QueuedConnection); + }; + signal_handler_t *handler = obs_source_get_signal_handler(transition); signal_handler_connect(handler, "transition_video_stop", onTransitionStop, this); + signal_handler_connect(handler, "transition_stop", + onTransitionFullStop, this); } static inline OBSSource GetTransitionComboItem(QComboBox *combo, int idx) @@ -220,10 +228,10 @@ obs_source_t *OBSBasic::FindTransition(const char *name) return nullptr; } -void OBSBasic::TransitionToScene(OBSScene scene, bool force) +void OBSBasic::TransitionToScene(OBSScene scene, bool force, bool direct) { obs_source_t *source = obs_scene_get_source(scene); - TransitionToScene(source, force); + TransitionToScene(source, force, direct); } void OBSBasic::TransitionStopped() @@ -242,7 +250,29 @@ void OBSBasic::TransitionStopped() swapScene = nullptr; } -void OBSBasic::TransitionToScene(OBSSource source, bool force) +static void OverrideTransition(OBSSource transition) +{ + obs_source_t *oldTransition = obs_get_output_source(0); + + if (transition != oldTransition) { + obs_transition_swap_begin(transition, oldTransition); + obs_set_output_source(0, transition); + obs_transition_swap_end(transition, oldTransition); + } + + obs_source_release(oldTransition); +} + +void OBSBasic::TransitionFullyStopped() +{ + if (overridingTransition) { + OverrideTransition(GetCurrentTransition()); + overridingTransition = false; + } +} + +void OBSBasic::TransitionToScene(OBSSource source, bool force, bool direct, + bool quickTransition) { obs_scene_t *scene = obs_scene_from_source(source); bool usingPreviewProgram = IsPreviewProgramMode(); @@ -255,7 +285,7 @@ void OBSBasic::TransitionToScene(OBSSource source, bool force) lastProgramScene = programScene; programScene = OBSGetWeakRef(source); - if (swapScenesMode && !force) { + if (swapScenesMode && !force && !direct) { OBSSource newScene = OBSGetStrongRef(lastProgramScene); if (!sceneDuplicationMode && newScene == source) @@ -274,21 +304,45 @@ void OBSBasic::TransitionToScene(OBSSource source, bool force) source = obs_scene_get_source(scene); } - obs_source_t *transition = obs_get_output_source(0); + OBSSource transition = obs_get_output_source(0); + obs_source_release(transition); if (force) { obs_transition_set(transition, source); if (api) api->on_event(OBS_FRONTEND_EVENT_SCENE_CHANGED); } else { - obs_transition_start(transition, OBS_TRANSITION_MODE_AUTO, - ui->transitionDuration->value(), source); + /* check for scene override */ + OBSData data = obs_source_get_private_settings(source); + obs_data_release(data); + + const char *trOverrideName = obs_data_get_string(data, + "transition"); + int duration = ui->transitionDuration->value(); + + if (trOverrideName && *trOverrideName && !quickTransition) { + OBSSource trOverride = FindTransition(trOverrideName); + if (trOverride) { + transition = trOverride; + + obs_data_set_default_int(data, + "transition_duration", 300); + + duration = (int)obs_data_get_int(data, + "transition_duration"); + OverrideTransition(trOverride); + overridingTransition = true; + } + } + + bool success = obs_transition_start(transition, + OBS_TRANSITION_MODE_AUTO, duration, source); + if (!success) + TransitionFullyStopped(); } if (usingPreviewProgram && sceneDuplicationMode) obs_scene_release(scene); - - obs_source_release(transition); } static inline void SetComboTransition(QComboBox *combo, obs_source_t *tr) @@ -544,10 +598,10 @@ int OBSBasic::GetQuickTransitionIdx(int id) return -1; } -void OBSBasic::SetCurrentScene(obs_scene_t *scene, bool force) +void OBSBasic::SetCurrentScene(obs_scene_t *scene, bool force, bool direct) { obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, force); + SetCurrentScene(source, force, direct); } template @@ -556,10 +610,13 @@ static T GetOBSRef(QListWidgetItem *item) return item->data(static_cast(QtDataRole::OBSRef)).value(); } -void OBSBasic::SetCurrentScene(OBSSource scene, bool force) +void OBSBasic::SetCurrentScene(OBSSource scene, bool force, bool direct) { - if (!IsPreviewProgramMode()) { - TransitionToScene(scene, force); + if (!IsPreviewProgramMode() && !direct) { + TransitionToScene(scene, force, false); + + } else if (IsPreviewProgramMode() && direct) { + TransitionToScene(scene, force, true); } else { OBSSource actualLastScene = OBSGetStrongRef(lastScene); @@ -585,6 +642,9 @@ void OBSBasic::SetCurrentScene(OBSSource scene, bool force) break; } } + + if (api && IsPreviewProgramMode()) + api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); } UpdateSceneSelection(scene); @@ -598,6 +658,9 @@ void OBSBasic::SetCurrentScene(OBSSource scene, bool force) void OBSBasic::CreateProgramDisplay() { program = new OBSQTDisplay(); + program->setContextMenuPolicy(Qt::CustomContextMenu); + connect(program.data(), &QWidget::customContextMenuRequested, + this, &OBSBasic::on_program_customContextMenuRequested); auto displayResize = [this]() { struct obs_video_info ovi; @@ -745,6 +808,88 @@ static inline void ResetQuickTransitionText(QuickTransition *qt) qt->button->setText(MakeQuickTransitionText(qt)); } +QMenu *OBSBasic::CreatePerSceneTransitionMenu() +{ + OBSSource scene = GetCurrentSceneSource(); + QMenu *menu = new QMenu(QTStr("TransitionOverride")); + QAction *action; + + OBSData data = obs_source_get_private_settings(scene); + obs_data_release(data); + + obs_data_set_default_int(data, "transition_duration", 300); + + const char *curTransition = obs_data_get_string(data, "transition"); + int curDuration = (int)obs_data_get_int(data, "transition_duration"); + + QSpinBox *duration = new QSpinBox(menu); + duration->setMinimum(50); + duration->setSuffix("ms"); + duration->setMaximum(20000); + duration->setSingleStep(50); + duration->setValue(curDuration); + + auto setTransition = [this] (QAction *action) + { + int idx = action->property("transition_index").toInt(); + OBSSource scene = GetCurrentSceneSource(); + OBSData data = obs_source_get_private_settings(scene); + obs_data_release(data); + + if (idx == -1) { + obs_data_set_string(data, "transition", ""); + return; + } + + OBSSource tr = GetTransitionComboItem(ui->transitions, idx); + const char *name = obs_source_get_name(tr); + + obs_data_set_string(data, "transition", name); + }; + + auto setDuration = [this] (int duration) + { + OBSSource scene = GetCurrentSceneSource(); + OBSData data = obs_source_get_private_settings(scene); + obs_data_release(data); + + obs_data_set_int(data, "transition_duration", duration); + }; + + connect(duration, (void (QSpinBox::*)(int))&QSpinBox::valueChanged, + setDuration); + + for (int i = -1; i < ui->transitions->count(); i++) { + const char *name = ""; + + if (i >= 0) { + OBSSource tr; + tr = GetTransitionComboItem(ui->transitions, i); + name = obs_source_get_name(tr); + } + + bool match = (name && strcmp(name, curTransition) == 0); + + if (!name || !*name) + name = Str("None"); + + action = menu->addAction(QT_UTF8(name)); + action->setProperty("transition_index", i); + action->setCheckable(true); + action->setChecked(match); + + connect(action, &QAction::triggered, + std::bind(setTransition, action)); + } + + QWidgetAction *durationAction = new QWidgetAction(menu); + durationAction->setDefaultWidget(duration); + + menu->addSeparator(); + menu->addAction(durationAction); + return menu; +} + QMenu *OBSBasic::CreateTransitionMenu(QWidget *parent, QuickTransition *qt) { QMenu *menu = new QMenu(parent); @@ -1007,8 +1152,12 @@ void OBSBasic::SetPreviewProgramMode(bool enabled) ui->previewLayout->addWidget(programOptions); ui->previewLayout->addWidget(program); + ui->previewLayout->setAlignment(programOptions, Qt::AlignCenter); program->show(); + if (api) + api->on_event(OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED); + blog(LOG_INFO, "Switched to Preview/Program mode"); blog(LOG_INFO, "-----------------------------" "-------------------"); @@ -1017,7 +1166,7 @@ void OBSBasic::SetPreviewProgramMode(bool enabled) if (!actualProgramScene) actualProgramScene = GetCurrentSceneSource(); else - SetCurrentScene(actualProgramScene); + SetCurrentScene(actualProgramScene, true); TransitionToScene(actualProgramScene, true); delete programOptions; @@ -1039,11 +1188,15 @@ void OBSBasic::SetPreviewProgramMode(bool enabled) if (!previewEnabled) EnablePreviewDisplay(false); + if (api) + api->on_event(OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED); + blog(LOG_INFO, "Switched to regular Preview mode"); blog(LOG_INFO, "-----------------------------" "-------------------"); } + ResetUI(); UpdateTitleBar(); } @@ -1069,7 +1222,7 @@ void OBSBasic::RenderProgram(void *data, uint32_t cx, uint32_t cy) window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - obs_render_main_view(); + obs_render_main_texture(); gs_load_vertexbuffer(nullptr); /* --------------------------------------- */ diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index aa47c39..da9ddfc 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -139,33 +139,20 @@ OBSBasic::OBSBasic(QWidget *parent) projectorArray.resize(10, ""); previewProjectorArray.resize(10, 0); + multiviewProjectorArray.resize(10, 0); + studioProgramProjectorArray.resize(10, 0); setAcceptDrops(true); ui->setupUi(this); ui->previewDisabledLabel->setVisible(false); + startingDockLayout = saveState(); + copyActionsDynamicProperties(); ui->sources->setItemDelegate(new VisibilityItemDelegate(ui->sources)); - const char *geometry = config_get_string(App()->GlobalConfig(), - "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64( - QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = App()->desktop()->geometry(); - setGeometry(QStyle::alignedRect( - Qt::LeftToRight, - Qt::AlignCenter, - size(), rect)); - } - } - char styleSheetPath[512]; int ret = GetProfilePath(styleSheetPath, sizeof(styleSheetPath), "stylesheet.qss"); @@ -257,6 +244,58 @@ OBSBasic::OBSBasic(QWidget *parent) addNudge(Qt::Key_Down, SLOT(NudgeDown())); addNudge(Qt::Key_Left, SLOT(NudgeLeft())); addNudge(Qt::Key_Right, SLOT(NudgeRight())); + + auto assignDockToggle = [this](QDockWidget *dock, QAction *action) + { + auto handleWindowToggle = [action] (bool vis) + { + action->blockSignals(true); + action->setChecked(vis); + action->blockSignals(false); + }; + auto handleMenuToggle = [dock] (bool check) + { + dock->blockSignals(true); + dock->setVisible(check); + dock->blockSignals(false); + }; + + dock->connect(dock->toggleViewAction(), &QAction::toggled, + handleWindowToggle); + dock->connect(action, &QAction::toggled, + handleMenuToggle); + }; + + assignDockToggle(ui->scenesDock, ui->toggleScenes); + assignDockToggle(ui->sourcesDock, ui->toggleSources); + assignDockToggle(ui->mixerDock, ui->toggleMixer); + assignDockToggle(ui->transitionsDock, ui->toggleTransitions); + assignDockToggle(ui->controlsDock, ui->toggleControls); + + //hide all docking panes + ui->toggleScenes->setChecked(false); + ui->toggleSources->setChecked(false); + ui->toggleMixer->setChecked(false); + ui->toggleTransitions->setChecked(false); + ui->toggleControls->setChecked(false); + + //restore parent window geometry + const char *geometry = config_get_string(App()->GlobalConfig(), + "BasicWindow", "geometry"); + if (geometry != NULL) { + QByteArray byteArray = QByteArray::fromBase64( + QByteArray(geometry)); + restoreGeometry(byteArray); + + QRect windowGeometry = normalGeometry(); + if (!WindowPositionValid(windowGeometry)) { + QRect rect = App()->desktop()->geometry(); + setGeometry(QStyle::alignedRect( + Qt::LeftToRight, + Qt::AlignCenter, + size(), rect)); + } + } } static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, @@ -281,7 +320,9 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *transitions, OBSScene &scene, OBSSource &curProgramScene, obs_data_array_t *savedProjectorList, - obs_data_array_t *savedPreviewProjectorList) + obs_data_array_t *savedPreviewProjectorList, + obs_data_array_t *savedStudioProgramProjectorList, + obs_data_array_t *savedMultiviewProjectorList) { obs_data_t *saveData = obs_data_create(); @@ -325,6 +366,10 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_set_array(saveData, "saved_projectors", savedProjectorList); obs_data_set_array(saveData, "saved_preview_projectors", savedPreviewProjectorList); + obs_data_set_array(saveData, "saved_studio_preview_projectors", + savedStudioProgramProjectorList); + obs_data_set_array(saveData, "saved_multiview_projectors", + savedMultiviewProjectorList); obs_data_array_release(sourcesArray); obs_data_set_string(saveData, "current_transition", @@ -355,6 +400,16 @@ void OBSBasic::copyActionsDynamicProperties() } } +void OBSBasic::UpdateVolumeControlsDecayRate() +{ + double meterDecayRate = config_get_double(basicConfig, "Audio", + "MeterDecayRate"); + + for (size_t i = 0; i < volumes.size(); i++) { + volumes[i]->SetMeterDecayRate(meterDecayRate); + } +} + void OBSBasic::ClearVolumeControls() { VolControl *control; @@ -412,6 +467,36 @@ obs_data_array_t *OBSBasic::SavePreviewProjectors() return saveProjector; } +obs_data_array_t *OBSBasic::SaveStudioProgramProjectors() +{ + obs_data_array_t *saveProjector = obs_data_array_create(); + + for (size_t i = 0; i < studioProgramProjectorArray.size(); i++) { + obs_data_t *data = obs_data_create(); + obs_data_set_int(data, "saved_studio_preview_projectors", + studioProgramProjectorArray.at(i)); + obs_data_array_push_back(saveProjector, data); + obs_data_release(data); + } + + return saveProjector; +} + +obs_data_array_t *OBSBasic::SaveMultiviewProjectors() +{ + obs_data_array_t *saveProjector = obs_data_array_create(); + + for (size_t i = 0; i < multiviewProjectorArray.size(); i++) { + obs_data_t *data = obs_data_create(); + obs_data_set_int(data, "saved_multiview_projectors", + multiviewProjectorArray.at(i)); + obs_data_array_push_back(saveProjector, data); + obs_data_release(data); + } + + return saveProjector; +} + void OBSBasic::Save(const char *file) { OBSScene scene = GetCurrentScene(); @@ -424,14 +509,26 @@ void OBSBasic::Save(const char *file) obs_data_array_t *quickTrData = SaveQuickTransitions(); obs_data_array_t *savedProjectorList = SaveProjectors(); obs_data_array_t *savedPreviewProjectorList = SavePreviewProjectors(); - obs_data_t *saveData = GenerateSaveData(sceneOrder, quickTrData, + obs_data_array_t *savedStudioProgramProjectorList = + SaveStudioProgramProjectors(); + obs_data_array_t *savedMultiviewProjectorList = + SaveMultiviewProjectors(); + obs_data_t *saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), transitions, scene, curProgramScene, savedProjectorList, - savedPreviewProjectorList); + savedPreviewProjectorList, + savedStudioProgramProjectorList, + savedMultiviewProjectorList); obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_int(saveData, "scaling_mode", - static_cast(ui->preview->GetScalingMode())); + obs_data_set_bool(saveData, "scaling_enabled", + ui->preview->IsFixedScaling()); + obs_data_set_int(saveData, "scaling_level", + ui->preview->GetScalingLevel()); + obs_data_set_double(saveData, "scaling_off_x", + ui->preview->GetScrollX()); + obs_data_set_double(saveData, "scaling_off_y", + ui->preview->GetScrollY()); if (api) { obs_data_t *moduleObj = obs_data_create(); @@ -449,6 +546,8 @@ void OBSBasic::Save(const char *file) obs_data_array_release(transitions); obs_data_array_release(savedProjectorList); obs_data_array_release(savedPreviewProjectorList); + obs_data_array_release(savedStudioProgramProjectorList); + obs_data_array_release(savedMultiviewProjectorList); } static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) @@ -574,6 +673,32 @@ void OBSBasic::LoadSavedPreviewProjectors(obs_data_array_t *array) } } +void OBSBasic::LoadSavedStudioProgramProjectors(obs_data_array_t *array) +{ + size_t num = obs_data_array_count(array); + + for (size_t i = 0; i < num; i++) { + obs_data_t *data = obs_data_array_item(array, i); + studioProgramProjectorArray.at(i) = obs_data_get_int(data, + "saved_studio_preview_projectors"); + + obs_data_release(data); + } +} + +void OBSBasic::LoadSavedMultiviewProjectors(obs_data_array_t *array) +{ + size_t num = obs_data_array_count(array); + + for (size_t i = 0; i < num; i++) { + obs_data_t *data = obs_data_array_item(array, i); + multiviewProjectorArray.at(i) = obs_data_get_int(data, + "saved_multiview_projectors"); + + obs_data_release(data); + } +} + static void LogFilter(obs_source_t*, obs_source_t *filter, void *v_val) { const char *name = obs_source_get_name(filter); @@ -647,6 +772,10 @@ void OBSBasic::Load(const char *file) ClearSceneData(); InitDefaultTransitions(); + obs_data_t *modulesObj = obs_data_get_obj(data, "modules"); + if (api) + api->on_preload(modulesObj); + obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order"); obs_data_array_t *sources = obs_data_get_array(data, "sources"); obs_data_array_t *transitions= obs_data_get_array(data, "transitions"); @@ -705,6 +834,8 @@ void OBSBasic::Load(const char *file) ui->transitionDuration->setValue(newDuration); SetTransition(curTransition); + /* ------------------- */ + obs_data_array_t *savedProjectors = obs_data_get_array(data, "saved_projectors"); @@ -713,6 +844,8 @@ void OBSBasic::Load(const char *file) obs_data_array_release(savedProjectors); + /* ------------------- */ + obs_data_array_t *savedPreviewProjectors = obs_data_get_array(data, "saved_preview_projectors"); @@ -721,6 +854,26 @@ void OBSBasic::Load(const char *file) obs_data_array_release(savedPreviewProjectors); + /* ------------------- */ + + obs_data_array_t *savedStudioProgramProjectors = obs_data_get_array(data, + "saved_studio_preview_projectors"); + + if (savedStudioProgramProjectors) + LoadSavedStudioProgramProjectors(savedStudioProgramProjectors); + + obs_data_array_release(savedStudioProgramProjectors); + + /* ------------------- */ + + obs_data_array_t *savedMultiviewProjectors = obs_data_get_array(data, + "saved_multiview_projectors"); + + if (savedMultiviewProjectors) + LoadSavedMultiviewProjectors(savedMultiviewProjectors); + + obs_data_array_release(savedMultiviewProjectors); + retryScene: curScene = obs_get_source_by_name(sceneName); @@ -771,37 +924,39 @@ retryScene: ui->preview->SetLocked(previewLocked); ui->actionLockPreview->setChecked(previewLocked); - ScalingMode previewScaling = static_cast( - obs_data_get_int(data, "scaling_mode")); - switch (previewScaling) { - case ScalingMode::Window: - case ScalingMode::Canvas: - case ScalingMode::Output: - break; - default: - previewScaling = ScalingMode::Window; + /* ---------------------- */ + + bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); + int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); + float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); + float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); + + if (fixedScaling) { + ui->preview->SetScalingLevel(scalingLevel); + ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); } + ui->preview->SetFixedScaling(fixedScaling); - ui->preview->SetScaling(previewScaling); + /* ---------------------- */ - if (api) { - obs_data_t *modulesObj = obs_data_get_obj(data, "modules"); + if (api) api->on_load(modulesObj); - obs_data_release(modulesObj); - } + obs_data_release(modulesObj); obs_data_release(data); if (!opt_starting_scene.empty()) opt_starting_scene.clear(); if (opt_start_streaming) { + blog(LOG_INFO, "Starting stream due to command line parameter"); QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); opt_start_streaming = false; } if (opt_start_recording) { + blog(LOG_INFO, "Starting recording due to command line parameter"); QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); opt_start_recording = false; @@ -816,6 +971,9 @@ retryScene: LogScenes(); disableSaving--; + + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_CHANGED); } #define SERVICE_PATH "service.json" @@ -1012,6 +1170,10 @@ bool OBSBasic::InitBasicConfigDefaults() config_set_default_uint (basicConfig, "AdvOut", "Track5Bitrate", 160); config_set_default_uint (basicConfig, "AdvOut", "Track6Bitrate", 160); + config_set_default_bool (basicConfig, "AdvOut", "RecRB", false); + config_set_default_uint (basicConfig, "AdvOut", "RecRBTime", 20); + config_set_default_int (basicConfig, "AdvOut", "RecRBSize", 512); + config_set_default_uint (basicConfig, "Video", "BaseCX", cx); config_set_default_uint (basicConfig, "Video", "BaseCY", cy); @@ -1083,6 +1245,8 @@ bool OBSBasic::InitBasicConfigDefaults() config_set_default_uint (basicConfig, "Audio", "SampleRate", 44100); config_set_default_string(basicConfig, "Audio", "ChannelSetup", "Stereo"); + config_set_default_double(basicConfig, "Audio", "MeterDecayRate", + VOLUME_METER_DECAY_FAST); return true; } @@ -1218,6 +1382,7 @@ void OBSBasic::ResetOutputs() this, &OBSBasic::ReplayBufferClicked); + replayBufferButton->setProperty("themeID", "replayBufferButton"); ui->buttonsVLayout->insertWidget(2, replayBufferButton); } @@ -1229,6 +1394,9 @@ void OBSBasic::ResetOutputs() } } +static void AddProjectorMenuMonitors(QMenu *parent, QObject *target, + const char *slot); + #define STARTUP_SEPARATOR \ "==== Startup complete ===============================================" #define SHUTDOWN_SEPARATOR \ @@ -1286,7 +1454,7 @@ void OBSBasic::OBSInit() } /* load audio monitoring */ -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO const char *device_name = config_get_string(basicConfig, "Audio", "MonitoringDeviceName"); const char *device_id = config_get_string(basicConfig, "Audio", @@ -1308,6 +1476,10 @@ void OBSBasic::OBSInit() obs_load_all_modules(); blog(LOG_INFO, "---------------------------------"); obs_log_loaded_modules(); + blog(LOG_INFO, "---------------------------------"); + obs_post_load_modules(); + + CheckForSimpleModeX264Fallback(); blog(LOG_INFO, STARTUP_SEPARATOR); @@ -1344,7 +1516,6 @@ void OBSBasic::OBSInit() } \ } while (false) - SET_VISIBILITY("ShowTransitions", toggleSceneTransitions); SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); SET_VISIBILITY("ShowStatusBar", toggleStatusBar); #undef SET_VISIBILITY @@ -1408,29 +1579,34 @@ void OBSBasic::OBSInit() show(); #endif - QList defSizes; - - int top = config_get_int(App()->GlobalConfig(), "BasicWindow", - "splitterTop"); - int bottom = config_get_int(App()->GlobalConfig(), "BasicWindow", - "splitterBottom"); - - if (!top || !bottom) { - defSizes = ui->mainSplitter->sizes(); - int total = defSizes[0] + defSizes[1]; - defSizes[0] = total * 75 / 100; - defSizes[1] = total - defSizes[0]; + const char *dockStateStr = config_get_string(App()->GlobalConfig(), + "BasicWindow", "DockState"); + if (!dockStateStr) { + on_resetUI_triggered(); } else { - defSizes.push_back(top); - defSizes.push_back(bottom); + QByteArray dockState = + QByteArray::fromBase64(QByteArray(dockStateStr)); + if (!restoreState(dockState)) + on_resetUI_triggered(); } - ui->mainSplitter->setSizes(defSizes); + config_set_default_bool(App()->GlobalConfig(), "BasicWindow", + "DocksLocked", true); + + bool docksLocked = config_get_bool(App()->GlobalConfig(), + "BasicWindow", "DocksLocked"); + on_lockUI_toggled(docksLocked); + ui->lockUI->blockSignals(true); + ui->lockUI->setChecked(docksLocked); + ui->lockUI->blockSignals(false); SystemTray(true); OpenSavedProjectors(); + if (windowState().testFlag(Qt::WindowFullScreen)) + fullscreenInterface = true; + bool has_last_version = config_has_user_value(App()->GlobalConfig(), "General", "LastVersion"); bool first_run = config_get_bool(App()->GlobalConfig(), "General", @@ -1465,6 +1641,19 @@ void OBSBasic::OBSInit() on_stats_triggered(); OBSBasicStats::InitializeValues(); + + /* ----------------------- */ + /* Add multiview menu */ + + ui->viewMenu->addSeparator(); + + QMenu *multiviewProjectorMenu = new QMenu(QTStr("MultiviewProjector")); + AddProjectorMenuMonitors(multiviewProjectorMenu, this, + SLOT(OpenMultiviewProjector())); + ui->viewMenu->addMenu(multiviewProjectorMenu); + + ui->viewMenu->addAction(QTStr("MultiviewWindowed"), + this, SLOT(OpenMultiviewWindow())); } void OBSBasic::InitHotkeys() @@ -1576,11 +1765,12 @@ void OBSBasic::CreateHotkeys() obs_data_array_release(array1); }; -#define MAKE_CALLBACK(pred, method) \ +#define MAKE_CALLBACK(pred, method, log_action) \ [](void *data, obs_hotkey_pair_id, obs_hotkey_t*, bool pressed) \ { \ OBSBasic &basic = *static_cast(data); \ if (pred && pressed) { \ + blog(LOG_INFO, log_action " due to hotkey"); \ method(); \ return true; \ } \ @@ -1593,9 +1783,9 @@ void OBSBasic::CreateHotkeys() "OBSBasic.StopStreaming", Str("Basic.Main.StopStreaming"), MAKE_CALLBACK(!basic.outputHandler->StreamingActive(), - basic.StartStreaming), + basic.StartStreaming, "Starting stream"), MAKE_CALLBACK(basic.outputHandler->StreamingActive(), - basic.StopStreaming), + basic.StopStreaming, "Stopping stream"), this, this); LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); @@ -1621,9 +1811,9 @@ void OBSBasic::CreateHotkeys() "OBSBasic.StopRecording", Str("Basic.Main.StopRecording"), MAKE_CALLBACK(!basic.outputHandler->RecordingActive(), - basic.StartRecording), + basic.StartRecording, "Starting recording"), MAKE_CALLBACK(basic.outputHandler->RecordingActive(), - basic.StopRecording), + basic.StopRecording, "Stopping recording"), this, this); LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); @@ -1634,9 +1824,9 @@ void OBSBasic::CreateHotkeys() "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer), + basic.StartReplayBuffer, "Starting replay buffer"), MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer), + basic.StopReplayBuffer, "Stopping replay buffer"), this, this); LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); @@ -1744,13 +1934,8 @@ OBSBasic::~OBSBasic() config_set_int(App()->GlobalConfig(), "General", "LastVersion", LIBOBS_API_VER); - QList splitterSizes = ui->mainSplitter->sizes(); bool alwaysOnTop = IsAlwaysOnTop(this); - config_set_int(App()->GlobalConfig(), "BasicWindow", "splitterTop", - splitterSizes[0]); - config_set_int(App()->GlobalConfig(), "BasicWindow", "splitterBottom", - splitterSizes[1]); config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop", @@ -1763,6 +1948,8 @@ OBSBasic::~OBSBasic() "EditPropertiesMode", editPropertiesMode); config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); + config_set_bool(App()->GlobalConfig(), "BasicWindow", + "DocksLocked", ui->lockUI->isChecked()); config_save_safe(App()->GlobalConfig(), "tmp", nullptr); #ifdef _WIN32 @@ -1827,6 +2014,11 @@ void OBSBasic::SaveProjectDeferred() Save(savePath); } +OBSSource OBSBasic::GetProgramSource() +{ + return OBSGetStrongRef(programScene); +} + OBSScene OBSBasic::GetCurrentScene() { QListWidgetItem *item = ui->scenes->currentItem(); @@ -1845,13 +2037,22 @@ OBSSceneItem OBSBasic::GetCurrentSceneItem() void OBSBasic::UpdatePreviewScalingMenu() { - ScalingMode scalingMode = ui->preview->GetScalingMode(); - ui->actionScaleWindow->setChecked( - scalingMode == ScalingMode::Window); - ui->actionScaleCanvas->setChecked( - scalingMode == ScalingMode::Canvas); + bool fixedScaling = ui->preview->IsFixedScaling(); + float scalingAmount = ui->preview->GetScalingAmount(); + if (!fixedScaling) { + ui->actionScaleWindow->setChecked(true); + ui->actionScaleCanvas->setChecked(false); + ui->actionScaleOutput->setChecked(false); + return; + } + + obs_video_info ovi; + obs_get_video_info(&ovi); + + ui->actionScaleWindow->setChecked(false); + ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); ui->actionScaleOutput->setChecked( - scalingMode == ScalingMode::Output); + scalingAmount == float(ovi.output_width) / float(ovi.base_width)); } void OBSBasic::UpdateSources(OBSScene scene) @@ -1979,6 +2180,8 @@ void OBSBasic::AddScene(OBSSource source) obs_source_t *source = obs_scene_get_source(scene); blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); + + OBSProjector::UpdateMultiviewProjectors(); } if (api) @@ -2013,6 +2216,8 @@ void OBSBasic::RemoveScene(OBSSource source) if (!disableSaving) { blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); + + OBSProjector::UpdateMultiviewProjectors(); } if (api) @@ -2094,7 +2299,8 @@ static void RenameListValues(QListWidget *listWidget, const QString &newName, items[i]->setText(newName); } -void OBSBasic::RenameSources(QString newName, QString prevName) +void OBSBasic::RenameSources(OBSSource source, QString newName, + QString prevName) { RenameListValues(ui->scenes, newName, prevName); @@ -2112,6 +2318,10 @@ void OBSBasic::RenameSources(QString newName, QString prevName) } SaveProject(); + + obs_scene_t *scene = obs_scene_from_source(source); + if (scene) + OBSProjector::UpdateMultiviewProjectors(); } void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select) @@ -2136,6 +2346,22 @@ void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select) } } +static inline bool SourceMixerHidden(obs_source_t *source) +{ + obs_data_t *priv_settings = obs_source_get_private_settings(source); + bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); + obs_data_release(priv_settings); + + return hidden; +} + +static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) +{ + obs_data_t *priv_settings = obs_source_get_private_settings(source); + obs_data_set_bool(priv_settings, "mixer_hidden", hidden); + obs_data_release(priv_settings); +} + void OBSBasic::GetAudioSourceFilters() { QAction *action = reinterpret_cast(sender()); @@ -2154,12 +2380,121 @@ void OBSBasic::GetAudioSourceProperties() CreatePropertiesWindow(source); } +void OBSBasic::HideAudioControl() +{ + QAction *action = reinterpret_cast(sender()); + VolControl *vol = action->property("volControl").value(); + obs_source_t *source = vol->GetSource(); + + if (!SourceMixerHidden(source)) { + SetSourceMixerHidden(source, true); + DeactivateAudioSource(source); + } +} + +void OBSBasic::UnhideAllAudioControls() +{ + auto UnhideAudioMixer = [this] (obs_source_t *source) /* -- */ + { + if (!obs_source_active(source)) + return true; + if (!SourceMixerHidden(source)) + return true; + + SetSourceMixerHidden(source, false); + ActivateAudioSource(source); + return true; + }; + + using UnhideAudioMixer_t = decltype(UnhideAudioMixer); + + auto PreEnum = [] (void *data, obs_source_t *source) -> bool /* -- */ + { + return (*reinterpret_cast(data))(source); + }; + + obs_enum_sources(PreEnum, &UnhideAudioMixer); +} + +void OBSBasic::ToggleHideMixer() +{ + OBSSceneItem item = GetCurrentSceneItem(); + OBSSource source = obs_sceneitem_get_source(item); + + if (!SourceMixerHidden(source)) { + SetSourceMixerHidden(source, true); + DeactivateAudioSource(source); + } else { + SetSourceMixerHidden(source, false); + ActivateAudioSource(source); + } +} + +void OBSBasic::MixerRenameSource() +{ + QAction *action = reinterpret_cast(sender()); + VolControl *vol = action->property("volControl").value(); + OBSSource source = vol->GetSource(); + + const char *prevName = obs_source_get_name(source); + + for (;;) { + string name; + bool accepted = NameDialog::AskForName(this, + QTStr("Basic.Main.MixerRename.Title"), + QTStr("Basic.Main.MixerRename.Text"), + name, + QT_UTF8(prevName)); + if (!accepted) + return; + + if (name.empty()) { + OBSMessageBox::information(this, + QTStr("NoNameEntered.Title"), + QTStr("NoNameEntered.Text")); + continue; + } + + OBSSource sourceTest = obs_get_source_by_name(name.c_str()); + obs_source_release(sourceTest); + + if (sourceTest) { + OBSMessageBox::information(this, + QTStr("NameExists.Title"), + QTStr("NameExists.Text")); + continue; + } + + obs_source_set_name(source, name.c_str()); + break; + } +} + void OBSBasic::VolControlContextMenu() { VolControl *vol = reinterpret_cast(sender()); + /* ------------------- */ + + QAction hideAction(QTStr("Hide"), this); + QAction unhideAllAction(QTStr("UnhideAll"), this); + QAction mixerRenameAction(QTStr("Rename"), this); + QAction filtersAction(QTStr("Filters"), this); QAction propertiesAction(QTStr("Properties"), this); + QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); + + /* ------------------- */ + + connect(&hideAction, &QAction::triggered, + this, &OBSBasic::HideAudioControl, + Qt::DirectConnection); + connect(&unhideAllAction, &QAction::triggered, + this, &OBSBasic::UnhideAllAudioControls, + Qt::DirectConnection); + connect(&mixerRenameAction, &QAction::triggered, + this, &OBSBasic::MixerRenameSource, + Qt::DirectConnection); connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, @@ -2167,22 +2502,74 @@ void OBSBasic::VolControlContextMenu() connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, Qt::DirectConnection); + connect(&advPropAction, &QAction::triggered, + this, &OBSBasic::on_actionAdvAudioProperties_triggered, + Qt::DirectConnection); + + /* ------------------- */ + + hideAction.setProperty("volControl", + QVariant::fromValue(vol)); + mixerRenameAction.setProperty("volControl", + QVariant::fromValue(vol)); filtersAction.setProperty("volControl", QVariant::fromValue(vol)); propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); + /* ------------------- */ + QMenu popup(this); + popup.addAction(&unhideAllAction); + popup.addAction(&hideAction); + popup.addAction(&mixerRenameAction); + popup.addSeparator(); popup.addAction(&filtersAction); popup.addAction(&propertiesAction); + popup.addAction(&advPropAction); + popup.exec(QCursor::pos()); +} + +void OBSBasic::on_mixerScrollArea_customContextMenuRequested() +{ + QAction unhideAllAction(QTStr("UnhideAll"), this); + + QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); + + /* ------------------- */ + + connect(&unhideAllAction, &QAction::triggered, + this, &OBSBasic::UnhideAllAudioControls, + Qt::DirectConnection); + + connect(&advPropAction, &QAction::triggered, + this, &OBSBasic::on_actionAdvAudioProperties_triggered, + Qt::DirectConnection); + + /* ------------------- */ + + QMenu popup(this); + popup.addAction(&unhideAllAction); + popup.addSeparator(); + popup.addAction(&advPropAction); popup.exec(QCursor::pos()); } void OBSBasic::ActivateAudioSource(OBSSource source) { + if (SourceMixerHidden(source)) + return; + VolControl *vol = new VolControl(source, true); + double meterDecayRate = config_get_double(basicConfig, "Audio", + "MeterDecayRate"); + vol->SetMeterDecayRate(meterDecayRate); + vol->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(vol, &QWidget::customContextMenuRequested, + this, &OBSBasic::VolControlContextMenu); connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); @@ -2280,6 +2667,8 @@ void OBSBasic::CheckForUpdates(bool manualUpdate) updateCheckThread = new AutoUpdateThread(manualUpdate); updateCheckThread->start(); #endif + + UNUSED_PARAMETER(manualUpdate); } void OBSBasic::updateCheckFinished() @@ -2481,6 +2870,7 @@ void OBSBasic::SceneItemDeselected(void *data, calldata_t *params) QMetaObject::invokeMethod(window, "SelectSceneItem", Q_ARG(OBSScene, scene), Q_ARG(OBSSceneItem, item), Q_ARG(bool, false)); + } void OBSBasic::SourceLoaded(void *data, obs_source_t *source) @@ -2527,11 +2917,13 @@ void OBSBasic::SourceDeactivated(void *data, calldata_t *params) void OBSBasic::SourceRenamed(void *data, calldata_t *params) { + obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source"); const char *newName = calldata_string(params, "new_name"); const char *prevName = calldata_string(params, "prev_name"); QMetaObject::invokeMethod(static_cast(data), "RenameSources", + Q_ARG(OBSSource, source), Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); @@ -2595,7 +2987,7 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy) if (source) obs_source_video_render(source); } else { - obs_render_main_view(); + obs_render_main_texture(); } gs_load_vertexbuffer(nullptr); @@ -2697,6 +3089,17 @@ static inline enum video_format GetVideoFormatFromName(const char *name) return VIDEO_FORMAT_RGBA; } +void OBSBasic::ResetUI() +{ + bool studioPortraitLayout = config_get_bool(GetGlobalConfig(), + "BasicWindow", "StudioPortraitLayout"); + + if (studioPortraitLayout) + ui->previewLayout->setDirection(QBoxLayout::TopToBottom); + else + ui->previewLayout->setDirection(QBoxLayout::LeftToRight); +} + int OBSBasic::ResetVideo() { if (outputHandler && outputHandler->Active()) @@ -2730,7 +3133,8 @@ int OBSBasic::ResetVideo() VIDEO_CS_601 : VIDEO_CS_709; ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = 0; + ovi.adapter = config_get_uint(App()->GlobalConfig(), + "Video", "AdapterIdx"); ovi.gpu_conversion = true; ovi.scale_type = GetScaleType(basicConfig); @@ -2774,7 +3178,8 @@ int OBSBasic::ResetVideo() ResizeProgram(ovi.base_width, ovi.base_height); } - OBSBasicStats::InitializeValues(); + if (ret == OBS_VIDEO_SUCCESS) + OBSBasicStats::InitializeValues(); return ret; } @@ -2792,6 +3197,16 @@ bool OBSBasic::ResetAudio() if (strcmp(channelSetupStr, "Mono") == 0) ai.speakers = SPEAKERS_MONO; + else if (strcmp(channelSetupStr, "2.1") == 0) + ai.speakers = SPEAKERS_2POINT1; + else if (strcmp(channelSetupStr, "4.0") == 0) + ai.speakers = SPEAKERS_4POINT0; + else if (strcmp(channelSetupStr, "4.1") == 0) + ai.speakers = SPEAKERS_4POINT1; + else if (strcmp(channelSetupStr, "5.1") == 0) + ai.speakers = SPEAKERS_5POINT1; + else if (strcmp(channelSetupStr, "7.1") == 0) + ai.speakers = SPEAKERS_7POINT1; else ai.speakers = SPEAKERS_STEREO; @@ -2838,32 +3253,23 @@ void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) { QSize targetSize; - ScalingMode scalingMode; + bool isFixedScaling; obs_video_info ovi; /* resize preview panel to fix to the top section of the window */ targetSize = GetPixelSize(ui->preview); - scalingMode = ui->preview->GetScalingMode(); + isFixedScaling = ui->preview->IsFixedScaling(); obs_get_video_info(&ovi); - if (scalingMode == ScalingMode::Canvas) { - previewScale = 1.0f; + if (isFixedScaling) { + previewScale = ui->preview->GetScalingAmount(); GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - previewX += ui->preview->ScrollX(); - previewY += ui->preview->ScrollY(); - - } else if (scalingMode == ScalingMode::Output) { - previewScale = float(ovi.output_width) / float(ovi.base_width); - GetCenterPosFromFixedScale(int(cx), int(cy), - targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, - previewX, previewY, previewScale); - previewX += ui->preview->ScrollX(); - previewY += ui->preview->ScrollY(); + previewX += ui->preview->GetScrollX(); + previewY += ui->preview->GetScrollY(); } else { GetScaleAndCenterPos(int(cx), int(cy), @@ -2894,7 +3300,7 @@ void OBSBasic::CloseDialogs() projector.clear(); } - delete stats; + if (!stats.isNull()) stats->close(); //call close to save Stats geometry } void OBSBasic::EnumDialogs() @@ -2951,6 +3357,9 @@ void OBSBasic::ClearSceneData() obs_enum_sources(cb, nullptr); + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); + disableSaving--; blog(LOG_INFO, "All scene data cleared"); @@ -2964,6 +3373,10 @@ void OBSBasic::closeEvent(QCloseEvent *event) "BasicWindow", "geometry", saveGeometry().toBase64().constData()); + config_set_string(App()->GlobalConfig(), + "BasicWindow", "DockState", + saveState().toBase64().constData()); + if (outputHandler && outputHandler->Active()) { SetShowing(true); @@ -3150,6 +3563,7 @@ void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) popup.addMenu(&order); popup.addSeparator(); + sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); AddProjectorMenuMonitors(sceneProjectorMenu, this, SLOT(OpenSceneProjector())); @@ -3163,6 +3577,39 @@ void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) popup.addSeparator(); popup.addAction(QTStr("Filters"), this, SLOT(OpenSceneFilters())); + + popup.addSeparator(); + + QMenu *transitionMenu = CreatePerSceneTransitionMenu(); + popup.addMenu(transitionMenu); + + /* ---------------------- */ + + QAction *multiviewAction = popup.addAction( + QTStr("ShowInMultiview")); + + OBSSource source = GetCurrentSceneSource(); + OBSData data = obs_source_get_private_settings(source); + obs_data_release(data); + + obs_data_set_default_bool(data, "show_in_multiview", + true); + bool show = obs_data_get_bool(data, "show_in_multiview"); + + multiviewAction->setCheckable(true); + multiviewAction->setChecked(show); + + auto showInMultiview = [this] (OBSData data) + { + bool show = obs_data_get_bool(data, + "show_in_multiview"); + obs_data_set_bool(data, "show_in_multiview", + !show); + OBSProjector::UpdateMultiviewProjectors(); + }; + + connect(multiviewAction, &QAction::triggered, + std::bind(showInMultiview, data)); } popup.exec(QCursor::pos()); @@ -3441,6 +3888,7 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview) popup.addMenu(addSourceMenu); ui->actionCopyFilters->setEnabled(false); + ui->actionCopySource->setEnabled(false); popup.addSeparator(); popup.addAction(ui->actionCopySource); @@ -3462,6 +3910,8 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview) uint32_t flags = obs_source_get_output_flags(source); bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; + bool hasAudio = (flags & OBS_SOURCE_AUDIO) == + OBS_SOURCE_AUDIO; QAction *action; popup.addAction(QTStr("Rename"), this, @@ -3484,6 +3934,15 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview) popup.addAction(sourceWindow); popup.addSeparator(); + + if (hasAudio) { + QAction *actionHideMixer = popup.addAction( + QTStr("HideMixer"), + this, SLOT(ToggleHideMixer())); + actionHideMixer->setCheckable(true); + actionHideMixer->setChecked(SourceMixerHidden(source)); + } + if (isAsyncVideo) { popup.addMenu(AddDeinterlacingMenu(source)); popup.addSeparator(); @@ -3508,6 +3967,7 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview) SLOT(on_actionSourceProperties_triggered())); ui->actionCopyFilters->setEnabled(true); + ui->actionCopySource->setEnabled(true); } popup.exec(QCursor::pos()); @@ -3531,6 +3991,24 @@ void OBSBasic::on_sources_itemDoubleClicked(QListWidgetItem *witem) CreatePropertiesWindow(source); } +void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) +{ + if (!witem) + return; + + if (IsPreviewProgramMode()) { + bool doubleClickSwitch = config_get_bool(App()->GlobalConfig(), + "BasicWindow", "TransitionOnDoubleClick"); + + if (doubleClickSwitch) { + OBSScene scene = GetCurrentScene(); + + if (scene) + SetCurrentScene(scene, false, true); + } + } +} + void OBSBasic::AddSource(const char *id) { if (id && *id) { @@ -3580,6 +4058,9 @@ QMenu *OBSBasic::CreateAddSourcePopupMenu() const char *name = obs_source_get_display_name(type); uint32_t caps = obs_get_source_output_flags(type); + if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) + continue; + if ((caps & OBS_SOURCE_DEPRECATED) == 0) { addSource(popup, type, name); } else { @@ -3990,6 +4471,7 @@ void OBSBasic::StartStreaming() if (!outputHandler->StartStreaming(service)) { ui->streamButton->setText(QTStr("Basic.Main.StartStreaming")); ui->streamButton->setEnabled(true); + ui->streamButton->setChecked(false); if (sysTrayStream) { sysTrayStream->setText(ui->streamButton->text()); @@ -4112,6 +4594,7 @@ void OBSBasic::StreamDelayStarting(int sec) { ui->streamButton->setText(QTStr("Basic.Main.StopStreaming")); ui->streamButton->setEnabled(true); + ui->streamButton->setChecked(true); if (sysTrayStream) { sysTrayStream->setText(ui->streamButton->text()); @@ -4137,6 +4620,7 @@ void OBSBasic::StreamDelayStopping(int sec) { ui->streamButton->setText(QTStr("Basic.Main.StartStreaming")); ui->streamButton->setEnabled(true); + ui->streamButton->setChecked(false); if (sysTrayStream) { sysTrayStream->setText(ui->streamButton->text()); @@ -4160,6 +4644,7 @@ void OBSBasic::StreamingStart() { ui->streamButton->setText(QTStr("Basic.Main.StopStreaming")); ui->streamButton->setEnabled(true); + ui->streamButton->setChecked(true); ui->statusbar->StreamStarted(outputHandler->streamOutput); if (sysTrayStream) { @@ -4230,6 +4715,7 @@ void OBSBasic::StreamingStop(int code, QString last_error) ui->streamButton->setText(QTStr("Basic.Main.StartStreaming")); ui->streamButton->setEnabled(true); + ui->streamButton->setChecked(false); if (sysTrayStream) { sysTrayStream->setText(ui->streamButton->text()); @@ -4299,6 +4785,7 @@ void OBSBasic::RecordingStart() { ui->statusbar->RecordingStarted(outputHandler->fileOutput); ui->recordButton->setText(QTStr("Basic.Main.StopRecording")); + ui->recordButton->setChecked(true); if (sysTrayRecord) sysTrayRecord->setText(ui->recordButton->text()); @@ -4316,6 +4803,7 @@ void OBSBasic::RecordingStop(int code) { ui->statusbar->RecordingStopped(); ui->recordButton->setText(QTStr("Basic.Main.StartRecording")); + ui->recordButton->setChecked(false); if (sysTrayRecord) sysTrayRecord->setText(ui->recordButton->text()); @@ -4437,6 +4925,20 @@ void OBSBasic::ReplayBufferStart() blog(LOG_INFO, REPLAY_BUFFER_START); } +void OBSBasic::ReplayBufferSave() +{ + if (!outputHandler || !outputHandler->replayBuffer) + return; + if (!outputHandler->ReplayBufferActive()) + return; + + calldata_t cd = {0}; + proc_handler_t *ph = obs_output_get_proc_handler( + outputHandler->replayBuffer); + proc_handler_call(ph, "save", &cd); + calldata_free(&cd); +} + void OBSBasic::ReplayBufferStop(int code) { if (!outputHandler || !outputHandler->replayBuffer) @@ -4531,6 +5033,12 @@ void OBSBasic::on_settingsButton_clicked() on_action_Settings_triggered(); } +void OBSBasic::on_actionHelpPortal_triggered() +{ + QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); + QDesktopServices::openUrl(url); +} + void OBSBasic::on_actionWebsite_triggered() { QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); @@ -4573,6 +5081,27 @@ void OBSBasic::on_preview_customContextMenuRequested(const QPoint &pos) UNUSED_PARAMETER(pos); } +void OBSBasic::on_program_customContextMenuRequested(const QPoint&) +{ + QMenu popup(this); + QPointer studioProgramProjector; + + studioProgramProjector = new QMenu( + QTStr("StudioProgramProjector")); + AddProjectorMenuMonitors(studioProgramProjector, this, + SLOT(OpenStudioProgramProjector())); + + popup.addMenu(studioProgramProjector); + + QAction *studioProgramWindow = popup.addAction( + QTStr("StudioProgramWindow"), + this, SLOT(OpenStudioProgramWindow())); + + popup.addAction(studioProgramWindow); + + popup.exec(QCursor::pos()); +} + void OBSBasic::on_previewDisabledLabel_customContextMenuRequested( const QPoint &pos) { @@ -5000,6 +5529,9 @@ void OBSBasic::Nudge(int dist, MoveDir dir) auto func = [] (obs_scene_t*, obs_sceneitem_t *item, void *param) { + if (obs_sceneitem_locked(item)) + return true; + MoveInfo *info = reinterpret_cast(param); struct vec2 dir; struct vec2 pos; @@ -5031,17 +5563,12 @@ void OBSBasic::NudgeLeft() {Nudge(1, MoveDir::Left);} void OBSBasic::NudgeRight() {Nudge(1, MoveDir::Right);} void OBSBasic::OpenProjector(obs_source_t *source, int monitor, bool window, - QString title) + QString title, ProjectorType type) { /* seriously? 10 monitors? */ if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) return; - bool isPreview = false; - - if (source == nullptr) - isPreview = true; - if (!window) { delete projectors[monitor]; projectors[monitor].clear(); @@ -5052,18 +5579,22 @@ void OBSBasic::OpenProjector(obs_source_t *source, int monitor, bool window, const char *name = obs_source_get_name(source); if (!window) { - if (isPreview) { + if (type == ProjectorType::StudioProgram) { + studioProgramProjectorArray.at((size_t)monitor) = 1; + } else if (type == ProjectorType::Preview) { previewProjectorArray.at((size_t)monitor) = 1; + } else if (type == ProjectorType::Multiview) { + multiviewProjectorArray.at((size_t)monitor) = 1; } else { projectorArray.at((size_t)monitor) = name; } } if (!window) { - projector->Init(monitor, false, nullptr); + projector->Init(monitor, false, nullptr, type); projectors[monitor] = projector; } else { - projector->Init(monitor, true, title); + projector->Init(monitor, true, title, type); for (auto &projPtr : windowProjectors) { if (!projPtr) { @@ -5077,10 +5608,17 @@ void OBSBasic::OpenProjector(obs_source_t *source, int monitor, bool window, } } +void OBSBasic::OpenStudioProgramProjector() +{ + int monitor = sender()->property("monitor").toInt(); + OpenProjector(nullptr, monitor, false, nullptr, + ProjectorType::StudioProgram); +} + void OBSBasic::OpenPreviewProjector() { int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, false); + OpenProjector(nullptr, monitor, false, nullptr, ProjectorType::Preview); } void OBSBasic::OpenSourceProjector() @@ -5093,6 +5631,13 @@ void OBSBasic::OpenSourceProjector() OpenProjector(obs_sceneitem_get_source(item), monitor, false); } +void OBSBasic::OpenMultiviewProjector() +{ + int monitor = sender()->property("monitor").toInt(); + OpenProjector(nullptr, monitor, false, nullptr, + ProjectorType::Multiview); +} + void OBSBasic::OpenSceneProjector() { int monitor = sender()->property("monitor").toInt(); @@ -5103,11 +5648,19 @@ void OBSBasic::OpenSceneProjector() OpenProjector(obs_scene_get_source(scene), monitor, false); } +void OBSBasic::OpenStudioProgramWindow() +{ + int monitor = sender()->property("monitor").toInt(); + QString title = QTStr("StudioProgramWindow"); + OpenProjector(nullptr, monitor, true, title, + ProjectorType::StudioProgram); +} + void OBSBasic::OpenPreviewWindow() { int monitor = sender()->property("monitor").toInt(); QString title = QTStr("PreviewWindow"); - OpenProjector(nullptr, monitor, true, title); + OpenProjector(nullptr, monitor, true, nullptr, ProjectorType::Preview); } void OBSBasic::OpenSourceWindow() @@ -5125,6 +5678,13 @@ void OBSBasic::OpenSourceWindow() OpenProjector(obs_sceneitem_get_source(item), monitor, true, title); } +void OBSBasic::OpenMultiviewWindow() +{ + int monitor = sender()->property("monitor").toInt(); + OpenProjector(nullptr, monitor, true, "Multiview", + ProjectorType::Multiview); +} + void OBSBasic::OpenSceneWindow() { int monitor = sender()->property("monitor").toInt(); @@ -5162,9 +5722,24 @@ void OBSBasic::OpenSavedProjectors() } } + for (size_t i = 0; i < studioProgramProjectorArray.size(); i++) { + if (studioProgramProjectorArray.at(i) == 1) { + OpenProjector(nullptr, (int)i, false, nullptr, + ProjectorType::StudioProgram); + } + } + for (size_t i = 0; i < previewProjectorArray.size(); i++) { if (previewProjectorArray.at(i) == 1) { - OpenProjector(nullptr, (int)i, false); + OpenProjector(nullptr, (int)i, false, nullptr, + ProjectorType::Preview); + } + } + + for (size_t i = 0; i < multiviewProjectorArray.size(); i++) { + if (multiviewProjectorArray.at(i) == 1) { + OpenProjector(nullptr, (int)i, false, nullptr, + ProjectorType::Multiview); } } } @@ -5172,10 +5747,22 @@ void OBSBasic::OpenSavedProjectors() void OBSBasic::RemoveSavedProjectors(int monitor) { + studioProgramProjectorArray.at((size_t)monitor) = 0; + multiviewProjectorArray.at((size_t)monitor) = 0; previewProjectorArray.at((size_t)monitor) = 0; projectorArray.at((size_t)monitor) = ""; } +void OBSBasic::on_actionFullscreenInterface_triggered() +{ + if (!fullscreenInterface) + showFullScreen(); + else + showNormal(); + + fullscreenInterface = !fullscreenInterface; +} + void OBSBasic::UpdateTitleBar() { stringstream name; @@ -5223,13 +5810,59 @@ int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); } -void OBSBasic::on_toggleSceneTransitions_toggled(bool visible) +void OBSBasic::on_resetUI_triggered() { - ui->sceneTransitionsLabel->setVisible(visible); - ui->transitionsContainer->setVisible(visible); + restoreState(startingDockLayout); - config_set_bool(App()->GlobalConfig(), "BasicWindow", - "ShowTransitions", visible); +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + int cx = width(); + int cy = height(); + + int cx22_5 = cx * 225 / 1000; + int cx5 = cx * 5 / 100; + + cy = cy * 225 / 1000; + + int mixerSize = cx - (cx22_5 * 2 + cx5 * 2); + + QList docks { + ui->scenesDock, + ui->sourcesDock, + ui->mixerDock, + ui->transitionsDock, + ui->controlsDock + }; + + QList sizes { + cx22_5, + cx22_5, + mixerSize, + cx5, + cx5 + }; + + ui->scenesDock->setVisible(true); + ui->sourcesDock->setVisible(true); + ui->mixerDock->setVisible(true); + ui->transitionsDock->setVisible(true); + ui->controlsDock->setVisible(true); + + resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); + resizeDocks(docks, sizes, Qt::Horizontal); +#endif +} + +void OBSBasic::on_lockUI_toggled(bool lock) +{ + QDockWidget::DockWidgetFeatures features = lock + ? QDockWidget::NoDockWidgetFeatures + : QDockWidget::AllDockWidgetFeatures; + + ui->scenesDock->setFeatures(features); + ui->sourcesDock->setFeatures(features); + ui->mixerDock->setFeatures(features); + ui->transitionsDock->setFeatures(features); + ui->controlsDock->setFeatures(features); } void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) @@ -5271,26 +5904,38 @@ void OBSBasic::on_scalingMenu_aboutToShow() text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); action->setText(text); + action->setVisible(!(ovi.output_width == ovi.base_width && + ovi.output_height == ovi.base_height)); UpdatePreviewScalingMenu(); } void OBSBasic::on_actionScaleWindow_triggered() { - ui->preview->SetScaling(ScalingMode::Window); + ui->preview->SetFixedScaling(false); ui->preview->ResetScrollingOffset(); emit ui->preview->DisplayResized(); } void OBSBasic::on_actionScaleCanvas_triggered() { - ui->preview->SetScaling(ScalingMode::Canvas); + ui->preview->SetFixedScaling(true); + ui->preview->SetScalingLevel(0); emit ui->preview->DisplayResized(); } void OBSBasic::on_actionScaleOutput_triggered() { - ui->preview->SetScaling(ScalingMode::Output); + obs_video_info ovi; + obs_get_video_info(&ovi); + + ui->preview->SetFixedScaling(true); + float scalingAmount = float(ovi.output_width) / float(ovi.base_width); + // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) + int32_t approxScalingLevel = int32_t( + round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); + ui->preview->SetScalingLevel(approxScalingLevel); + ui->preview->SetScalingAmount(scalingAmount); emit ui->preview->DisplayResized(); } @@ -5395,8 +6040,18 @@ void OBSBasic::SystemTrayInit() connect(exit, SIGNAL(triggered()), this, SLOT(close())); + QMenu *previewProjector = new QMenu(QTStr("PreviewProjector")); + AddProjectorMenuMonitors(previewProjector, this, + SLOT(OpenPreviewProjector())); + QMenu *studioProgramProjector = new QMenu( + QTStr("StudioProgramProjector")); + AddProjectorMenuMonitors(studioProgramProjector, this, + SLOT(OpenStudioProgramProjector())); + trayMenu = new QMenu; trayMenu->addAction(showHide); + trayMenu->addMenu(previewProjector); + trayMenu->addMenu(studioProgramProjector); trayMenu->addAction(sysTrayStream); trayMenu->addAction(sysTrayRecord); trayMenu->addAction(sysTrayReplayBuffer); @@ -5479,7 +6134,12 @@ void OBSBasic::on_actionCopySource_triggered() copyVisible = obs_sceneitem_visible(item); ui->actionPasteRef->setEnabled(true); - ui->actionPasteDup->setEnabled(true); + + uint32_t output_flags = obs_source_get_output_flags(source); + if ((output_flags & OBS_SOURCE_DO_NOT_DUPLICATE) == 0) + ui->actionPasteDup->setEnabled(true); + else + ui->actionPasteDup->setEnabled(false); } void OBSBasic::on_actionPasteRef_triggered() @@ -5511,8 +6171,9 @@ void OBSBasic::on_actionCopyFilters_triggered() void OBSBasic::on_actionPasteFilters_triggered() { OBSSource source = obs_get_source_by_name(copyFiltersString); - OBSSceneItem sceneItem = GetCurrentSceneItem(); + obs_source_release(source); + OBSSceneItem sceneItem = GetCurrentSceneItem(); OBSSource dstSource = obs_sceneitem_get_source(sceneItem); if (source == dstSource) diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 9a4de40..30a1e98 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -67,6 +67,13 @@ enum class QtDataRole { OBSSignals, }; +enum class ProjectorType { + Source, + Preview, + StudioProgram, + Multiview +}; + struct QuickTransition { QPushButton *button = nullptr; OBSSource source; @@ -102,7 +109,8 @@ class OBSBasic : public OBSMainWindow { DropType_RawText, DropType_Text, DropType_Image, - DropType_Media + DropType_Media, + DropType_Html }; private: @@ -113,12 +121,15 @@ private: std::vector signalHandlers; std::vector projectorArray; + std::vector studioProgramProjectorArray; + std::vector multiviewProjectorArray; std::vector previewProjectorArray; bool loaded = false; long disableSaving = 1; bool projectChanged = false; bool previewEnabled = true; + bool fullscreenInterface = false; const char *copyString; const char *copyFiltersString; @@ -182,6 +193,7 @@ private: void CreateFirstRunSources(); void CreateDefaultScene(bool firstStart); + void UpdateVolumeControlsDecayRate(); void ClearVolumeControls(); void UploadLog(const char *file); @@ -238,7 +250,8 @@ private: void Nudge(int dist, MoveDir dir); void OpenProjector(obs_source_t *source, int monitor, bool window, - QString title = nullptr); + QString title = nullptr, + ProjectorType type = ProjectorType::Source); void GetAudioSourceFilters(); void GetAudioSourceProperties(); @@ -256,6 +269,7 @@ private: void DeleteProfile(const char *profile_name, const char *profile_dir); void RefreshProfiles(); void ChangeProfile(); + void CheckForSimpleModeX264Fallback(); void SaveProjectNow(); @@ -286,6 +300,8 @@ private: void RefreshQuickTransitions(); void CreateDefaultQuickTransitions(); + QMenu *CreatePerSceneTransitionMenu(); + QuickTransition *GetQuickTransition(int id); int GetQuickTransitionIdx(int id); QMenu *CreateTransitionMenu(QWidget *parent, QuickTransition *qt); @@ -297,7 +313,8 @@ private: void SetPreviewProgramMode(bool enabled); void ResizeProgram(uint32_t cx, uint32_t cy); - void SetCurrentScene(obs_scene_t *scene, bool force = false); + void SetCurrentScene(obs_scene_t *scene, bool force = false, + bool direct = false); static void RenderProgram(void *data, uint32_t cx, uint32_t cy); std::vector quickTransitions; @@ -313,6 +330,7 @@ private: obs_hotkey_id togglePreviewProgramHotkey = 0; obs_hotkey_id transitionHotkey = 0; int quickTransitionIdCounter = 1; + bool overridingTransition = false; int programX = 0, programY = 0; int programCX = 0, programCY = 0; @@ -320,11 +338,6 @@ private: int disableOutputsRef = 0; - inline bool IsPreviewProgramMode() const - { - return os_atomic_load_bool(&previewProgramMode); - } - inline void OnActivate(); inline void OnDeactivate(); @@ -346,6 +359,8 @@ private: QList visDlgPositions; + QByteArray startingDockLayout; + obs_data_array_t *SaveProjectors(); void LoadSavedProjectors(obs_data_array_t *savedProjectors); @@ -353,6 +368,14 @@ private: void LoadSavedPreviewProjectors( obs_data_array_t *savedPreviewProjectors); + obs_data_array_t *SaveStudioProgramProjectors(); + void LoadSavedStudioProgramProjectors( + obs_data_array_t *savedStudioProgramProjectors); + + obs_data_array_t *SaveMultiviewProjectors(); + void LoadSavedMultiviewProjectors( + obs_data_array_t *savedMultiviewProjectors); + public slots: void StartStreaming(); void StopStreaming(); @@ -376,6 +399,7 @@ public slots: void StopReplayBuffer(); void ReplayBufferStart(); + void ReplayBufferSave(); void ReplayBufferStopping(); void ReplayBufferStop(int code); @@ -383,16 +407,19 @@ public slots: 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); + void TransitionToScene(OBSScene scene, bool force = false, + bool direct = false); + void TransitionToScene(OBSSource scene, bool force = false, + bool direct = false, bool quickTransition = false); + void SetCurrentScene(OBSSource scene, bool force = false, + bool direct = false); private slots: void AddSceneItem(OBSSceneItem item); void RemoveSceneItem(OBSSceneItem item); void AddScene(OBSSource source); void RemoveScene(OBSSource source); - void RenameSources(QString newName, QString prevName); + void RenameSources(OBSSource source, QString newName, QString prevName); void SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select); @@ -413,6 +440,7 @@ private slots: void RenameTransition(); void TransitionClicked(); void TransitionStopped(); + void TransitionFullyStopped(); void TriggerQuickTransition(int id); void SetDeinterlacingMode(); @@ -425,6 +453,14 @@ private slots: void ToggleShowHide(); + void HideAudioControl(); + void UnhideAllAudioControls(); + void ToggleHideMixer(); + + void MixerRenameSource(); + + void on_mixerScrollArea_customContextMenuRequested(); + void on_actionCopySource_triggered(); void on_actionPasteRef_triggered(); void on_actionPasteDup_triggered(); @@ -456,7 +492,8 @@ private: static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); public: - OBSScene GetCurrentScene(); + OBSSource GetProgramSource(); + OBSScene GetCurrentScene(); void SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n); @@ -469,9 +506,15 @@ public: obs_service_t *GetService(); void SetService(obs_service_t *service); + inline bool IsPreviewProgramMode() const + { + return os_atomic_load_bool(&previewProgramMode); + } + bool StreamingActive() const; bool Active() const; + void ResetUI(); int ResetVideo(); bool ResetAudio(); @@ -529,6 +572,8 @@ protected: virtual void changeEvent(QEvent *event) override; private slots: + void on_actionFullscreenInterface_triggered(); + void on_actionShow_Recordings_triggered(); void on_actionRemux_triggered(); void on_action_Settings_triggered(); @@ -563,6 +608,7 @@ private slots: void on_sources_itemSelectionChanged(); void on_sources_customContextMenuRequested(const QPoint &pos); void on_sources_itemDoubleClicked(QListWidgetItem *item); + void on_scenes_itemDoubleClicked(QListWidgetItem *item); void on_actionAddSource_triggered(); void on_actionRemoveSource_triggered(); void on_actionInteract_triggered(); @@ -586,9 +632,11 @@ private slots: void on_recordButton_clicked(); void on_settingsButton_clicked(); + void on_actionHelpPortal_triggered(); void on_actionWebsite_triggered(); void on_preview_customContextMenuRequested(const QPoint &pos); + void on_program_customContextMenuRequested(const QPoint &pos); void on_previewDisabledLabel_customContextMenuRequested( const QPoint &pos); @@ -611,7 +659,6 @@ private slots: void on_actionAlwaysOnTop_triggered(); - void on_toggleSceneTransitions_toggled(bool visible); void on_toggleListboxToolbars_toggled(bool visible); void on_toggleStatusBar_toggled(bool visible); @@ -625,6 +672,9 @@ private slots: void on_autoConfigure_triggered(); void on_stats_triggered(); + void on_resetUI_triggered(); + void on_lockUI_toggled(bool lock); + void logUploadFinished(const QString &text, const QString &error); void updateCheckFinished(); @@ -653,12 +703,16 @@ private slots: void NudgeLeft(); void NudgeRight(); + void OpenStudioProgramProjector(); void OpenPreviewProjector(); void OpenSourceProjector(); + void OpenMultiviewProjector(); void OpenSceneProjector(); + void OpenStudioProgramWindow(); void OpenPreviewWindow(); void OpenSourceWindow(); + void OpenMultiviewWindow(); void OpenSceneWindow(); public slots: diff --git a/UI/window-basic-preview.cpp b/UI/window-basic-preview.cpp index 6cba59b..d3930d5 100644 --- a/UI/window-basic-preview.cpp +++ b/UI/window-basic-preview.cpp @@ -75,6 +75,8 @@ static bool FindItemAtPos(obs_scene_t *scene, obs_sceneitem_t *item, if (!SceneItemHasVideo(item)) return true; + if (obs_sceneitem_locked(item)) + return true; vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f); @@ -380,8 +382,7 @@ void OBSBasicPreview::GetStretchHandleData(const vec2 &pos) void OBSBasicPreview::keyPressEvent(QKeyEvent *event) { - if (GetScalingMode() == ScalingMode::Window || - event->isAutoRepeat()) { + if (!IsFixedScaling() || event->isAutoRepeat()) { OBSQTDisplay::keyPressEvent(event); return; } @@ -413,9 +414,23 @@ void OBSBasicPreview::keyReleaseEvent(QKeyEvent *event) OBSQTDisplay::keyReleaseEvent(event); } +void OBSBasicPreview::wheelEvent(QWheelEvent *event) +{ + if (scrollMode && IsFixedScaling() + && event->orientation() == Qt::Vertical) { + if (event->delta() > 0) + SetScalingLevel(scalingLevel + 1); + else if (event->delta() < 0) + SetScalingLevel(scalingLevel - 1); + emit DisplayResized(); + } + + OBSQTDisplay::wheelEvent(event); +} + void OBSBasicPreview::mousePressEvent(QMouseEvent *event) { - if (scrollMode && GetScalingMode() != ScalingMode::Window && + if (scrollMode && IsFixedScaling() && event->button() == Qt::LeftButton) { setCursor(Qt::ClosedHandCursor); scrollingFrom.x = event->x(); @@ -674,6 +689,9 @@ void OBSBasicPreview::SnapItemMovement(vec2 &offset) static bool move_items(obs_scene_t *scene, obs_sceneitem_t *item, void *param) { + if (obs_sceneitem_locked(item)) + return true; + vec2 *offset = reinterpret_cast(param); if (obs_sceneitem_selected(item)) { @@ -1084,6 +1102,9 @@ static inline bool crop_enabled(const obs_sceneitem_crop *crop) bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene, obs_sceneitem_t *item, void *param) { + if (obs_sceneitem_locked(item)) + return true; + if (!obs_sceneitem_selected(item)) return true; @@ -1183,6 +1204,7 @@ void OBSBasicPreview::DrawSceneEditing() gs_technique_begin_pass(tech, 0); OBSScene scene = main->GetCurrentScene(); + if (scene) obs_scene_enum_items(scene, DrawSelectedItem, this); @@ -1196,3 +1218,15 @@ void OBSBasicPreview::ResetScrollingOffset() { vec2_zero(&scrollingOffset); } + +void OBSBasicPreview::SetScalingLevel(int32_t newScalingLevelVal) { + float newScalingAmountVal = pow(ZOOM_SENSITIVITY, float(newScalingLevelVal)); + scalingLevel = newScalingLevelVal; + SetScalingAmount(newScalingAmountVal); +} + +void OBSBasicPreview::SetScalingAmount(float newScalingAmountVal) { + scrollingOffset.x *= newScalingAmountVal / scalingAmount; + scrollingOffset.y *= newScalingAmountVal / scalingAmount; + scalingAmount = newScalingAmountVal; +} \ No newline at end of file diff --git a/UI/window-basic-preview.hpp b/UI/window-basic-preview.hpp index ac0ab30..4042281 100644 --- a/UI/window-basic-preview.hpp +++ b/UI/window-basic-preview.hpp @@ -14,6 +14,8 @@ class QMouseEvent; #define ITEM_TOP (1<<2) #define ITEM_BOTTOM (1<<3) +#define ZOOM_SENSITIVITY 1.125f + enum class ItemHandle : uint32_t { None = 0, TopLeft = ITEM_TOP | ITEM_LEFT, @@ -26,12 +28,6 @@ enum class ItemHandle : uint32_t { BottomRight = ITEM_BOTTOM | ITEM_RIGHT }; -enum class ScalingMode : uint32_t { - Window = 0, - Canvas = 1, - Output = 2 -}; - class OBSBasicPreview : public OBSQTDisplay { Q_OBJECT @@ -41,7 +37,6 @@ private: vec2 cropSize; OBSSceneItem stretchItem; ItemHandle stretchHandle = ItemHandle::None; - ScalingMode scale = ScalingMode::Window; vec2 stretchItemSize; matrix4 screenToItem; matrix4 itemToScreen; @@ -56,6 +51,9 @@ private: bool cropping = false; bool locked = false; bool scrollMode = false; + bool fixedScaling = false; + int32_t scalingLevel = 0; + float scalingAmount = 1.0f; static vec2 GetMouseEventPos(QMouseEvent *event); static bool DrawSelectedItem(obs_scene_t *scene, obs_sceneitem_t *item, @@ -88,22 +86,30 @@ public: virtual void keyPressEvent(QKeyEvent *event) override; virtual void keyReleaseEvent(QKeyEvent *event) override; + virtual void wheelEvent(QWheelEvent *event) override; + virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; void DrawSceneEditing(); - void ResetScrollingOffset(); inline void SetLocked(bool newLockedVal) {locked = newLockedVal;} inline void ToggleLocked() {locked = !locked;} inline bool Locked() const {return locked;} - inline void SetScaling(ScalingMode newScaledVal) {scale = newScaledVal;} - inline ScalingMode GetScalingMode() const {return scale;} + inline void SetFixedScaling(bool newFixedScalingVal) { fixedScaling = newFixedScalingVal; } + inline bool IsFixedScaling() const { return fixedScaling; } - inline float ScrollX() const {return scrollingOffset.x;} - inline float ScrollY() const {return scrollingOffset.y;} + void SetScalingLevel(int32_t newScalingLevelVal); + void SetScalingAmount(float newScalingAmountVal); + inline int32_t GetScalingLevel() const { return scalingLevel; } + inline float GetScalingAmount() const { return scalingAmount; } + + void ResetScrollingOffset(); + inline void SetScrollingOffset(float x, float y) {vec2_set(&scrollingOffset, x, y);} + inline float GetScrollX() const {return scrollingOffset.x;} + inline float GetScrollY() const {return scrollingOffset.y;} /* use libobs allocator for alignment because the matrices itemToScreen * and screenToItem may contain SSE data, which will cause SSE diff --git a/UI/window-basic-properties.cpp b/UI/window-basic-properties.cpp index d163d41..d07336b 100644 --- a/UI/window-basic-properties.cpp +++ b/UI/window-basic-properties.cpp @@ -49,9 +49,15 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_) int cy = (int)config_get_int(App()->GlobalConfig(), "PropertiesWindow", "cy"); - buttonBox->setStandardButtons(QDialogButtonBox::Ok | - QDialogButtonBox::Cancel); buttonBox->setObjectName(QStringLiteral("buttonBox")); + buttonBox->setStandardButtons(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel | + QDialogButtonBox::RestoreDefaults); + + buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("OK")); + buttonBox->button(QDialogButtonBox::Cancel)->setText(QTStr("Cancel")); + buttonBox->button(QDialogButtonBox::RestoreDefaults)-> + setText(QTStr("Defaults")); if (cx > 400 && cy > 400) resize(cx, cy); @@ -88,7 +94,7 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_) setLayout(new QVBoxLayout(this)); layout()->addWidget(windowSplitter); layout()->addWidget(buttonBox); - layout()->setAlignment(buttonBox, Qt::AlignRight | Qt::AlignBottom); + layout()->setAlignment(buttonBox, Qt::AlignBottom); view->show(); installEventFilter(CreateShortcutFilter()); @@ -158,9 +164,8 @@ void OBSBasicProperties::on_buttonBox_clicked(QAbstractButton *button) if (view->DeferUpdate()) view->UpdateSettings(); - } - if (val == QDialogButtonBox::RejectRole) { + } else if (val == QDialogButtonBox::RejectRole) { obs_data_t *settings = obs_source_get_settings(source); obs_data_clear(settings); obs_data_release(settings); @@ -171,6 +176,16 @@ void OBSBasicProperties::on_buttonBox_clicked(QAbstractButton *button) obs_source_update(source, oldSettings); close(); + + } else if (val == QDialogButtonBox::ResetRole) { + obs_data_t *settings = obs_source_get_settings(source); + obs_data_clear(settings); + obs_data_release(settings); + + if (!view->DeferUpdate()) + obs_source_update(source, nullptr); + + view->RefreshProperties(); } } @@ -273,6 +288,7 @@ bool OBSBasicProperties::ConfirmQuit() switch (button) { case QMessageBox::Save: + acceptClicked = true; if (view->DeferUpdate()) view->UpdateSettings(); // Do nothing because the settings are already updated diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 52f10b9..9bf6196 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -45,6 +45,7 @@ #include "window-basic-main.hpp" #include "window-basic-settings.hpp" #include "window-basic-main-outputs.hpp" +#include "window-projector.hpp" #include @@ -243,6 +244,9 @@ static void PopulateAACBitrates(initializer_list boxes) } } +void RestrictResetBitrates(initializer_list boxes, + int maxbitrate); + void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal, const char *slot) { @@ -256,6 +260,7 @@ void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal, #define CHECK_CHANGED SIGNAL(clicked(bool)) #define SCROLL_CHANGED SIGNAL(valueChanged(int)) #define DSCROLL_CHANGED SIGNAL(valueChanged(double)) +#define TOGGLE_CHANGED SIGNAL(toggled(bool)) #define GENERAL_CHANGED SLOT(GeneralChanged()) #define STREAM1_CHANGED SLOT(Stream1Changed()) @@ -311,6 +316,9 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->centerSnapping, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->sourceSnapping, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->snapDistance, DSCROLL_CHANGED,GENERAL_CHANGED); + HookWidget(ui->doubleClickSwitch, CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->studioPortraitLayout, CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->multiviewLayout, COMBO_CHANGED, GENERAL_CHANGED); HookWidget(ui->outputMode, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->streamType, COMBO_CHANGED, STREAM1_CHANGED); HookWidget(ui->simpleOutputPath, EDIT_CHANGED, OUTPUTS_CHANGED); @@ -387,8 +395,12 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->advOutTrack5Name, EDIT_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutTrack6Bitrate, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutTrack6Name, EDIT_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advReplayBuf, CHECK_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advRBSecMax, SCROLL_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advRBMegsMax, SCROLL_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->channelSetup, COMBO_CHANGED, AUDIO_RESTART); HookWidget(ui->sampleRate, COMBO_CHANGED, AUDIO_RESTART); + HookWidget(ui->meterDecayRate, COMBO_CHANGED, AUDIO_CHANGED); HookWidget(ui->desktopAudioDevice1, COMBO_CHANGED, AUDIO_CHANGED); HookWidget(ui->desktopAudioDevice2, COMBO_CHANGED, AUDIO_CHANGED); HookWidget(ui->auxAudioDevice1, COMBO_CHANGED, AUDIO_CHANGED); @@ -410,7 +422,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->colorRange, COMBO_CHANGED, ADV_CHANGED); HookWidget(ui->disableOSXVSync, CHECK_CHANGED, ADV_CHANGED); HookWidget(ui->resetOSXVSync, CHECK_CHANGED, ADV_CHANGED); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO HookWidget(ui->monitoringDevice, COMBO_CHANGED, ADV_CHANGED); #endif #ifdef _WIN32 @@ -431,15 +443,11 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->enableNewSocketLoop, CHECK_CHANGED, ADV_CHANGED); HookWidget(ui->enableLowLatencyMode, CHECK_CHANGED, ADV_CHANGED); -#if !defined(_WIN32) && !defined(__APPLE__) - delete ui->monitoringDevice; - delete ui->monitoringDeviceLabel; - delete ui->advAudioGroupBox; +#if !defined(_WIN32) && !defined(__APPLE__) && !HAVE_PULSEAUDIO delete ui->enableAutoUpdates; - ui->monitoringDevice = nullptr; - ui->monitoringDeviceLabel = nullptr; - ui->advAudioGroupBox = nullptr; + delete ui->advAudioGroupBox; ui->enableAutoUpdates = nullptr; + ui->advAudioGroupBox = nullptr; #endif #ifdef _WIN32 @@ -467,6 +475,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) PROCESS_PRIORITY("High"), PROCESS_PRIORITY("AboveNormal"), PROCESS_PRIORITY("Normal"), + PROCESS_PRIORITY("BelowNormal"), PROCESS_PRIORITY("Idle") }; #undef PROCESS_PRIORITY @@ -484,7 +493,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) delete ui->advancedGeneralGroupBox; delete ui->enableNewSocketLoop; delete ui->enableLowLatencyMode; -#ifdef __APPLE__ +#if defined(__APPLE__) || HAVE_PULSEAUDIO delete ui->disableAudioDucking; #endif ui->rendererLabel = nullptr; @@ -496,7 +505,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) ui->advancedGeneralGroupBox = nullptr; ui->enableNewSocketLoop = nullptr; ui->enableLowLatencyMode = nullptr; -#ifdef __APPLE__ +#if defined(__APPLE__) || HAVE_PULSEAUDIO ui->disableAudioDucking = nullptr; #endif #endif @@ -583,10 +592,14 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) FillSimpleRecordingValues(); FillSimpleStreamingValues(); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO FillAudioMonitoringDevices(); #endif + connect(ui->channelSetup, SIGNAL(currentIndexChanged(int)), + this, SLOT(SurroundWarning(int))); + connect(ui->channelSetup, SIGNAL(currentIndexChanged(int)), + this, SLOT(SpeakerLayoutChanged(int))); connect(ui->simpleOutRecQuality, SIGNAL(currentIndexChanged(int)), this, SLOT(SimpleRecordingQualityChanged())); connect(ui->simpleOutRecQuality, SIGNAL(currentIndexChanged(int)), @@ -615,6 +628,38 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) this, SLOT(SimpleReplayBufferChanged())); connect(ui->simpleRBSecMax, SIGNAL(valueChanged(int)), this, SLOT(SimpleReplayBufferChanged())); + connect(ui->advReplayBuf, SIGNAL(toggled(bool)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutRecTrack1, SIGNAL(toggled(bool)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutRecTrack2, SIGNAL(toggled(bool)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutRecTrack3, SIGNAL(toggled(bool)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutRecTrack4, SIGNAL(toggled(bool)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutRecTrack5, SIGNAL(toggled(bool)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutRecTrack6, SIGNAL(toggled(bool)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutTrack1Bitrate, SIGNAL(currentIndexChanged(int)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutTrack2Bitrate, SIGNAL(currentIndexChanged(int)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutTrack3Bitrate, SIGNAL(currentIndexChanged(int)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutTrack4Bitrate, SIGNAL(currentIndexChanged(int)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutTrack5Bitrate, SIGNAL(currentIndexChanged(int)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutTrack6Bitrate, SIGNAL(currentIndexChanged(int)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutRecType, SIGNAL(currentIndexChanged(int)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advOutRecEncoder, SIGNAL(currentIndexChanged(int)), + this, SLOT(AdvReplayBufferChanged())); + connect(ui->advRBSecMax, SIGNAL(valueChanged(int)), + this, SLOT(AdvReplayBufferChanged())); connect(ui->listWidget, SIGNAL(currentRowChanged(int)), this, SLOT(SimpleRecordingEncoderChanged())); @@ -1037,6 +1082,39 @@ void OBSBasicSettings::LoadGeneralSettings() "BasicWindow", "ProjectorAlwaysOnTop"); ui->projectorAlwaysOnTop->setChecked(projectorAlwaysOnTop); + bool doubleClickSwitch = config_get_bool(GetGlobalConfig(), + "BasicWindow", "TransitionOnDoubleClick"); + ui->doubleClickSwitch->setChecked(doubleClickSwitch); + + bool studioPortraitLayout = config_get_bool(GetGlobalConfig(), + "BasicWindow", "StudioPortraitLayout"); + ui->studioPortraitLayout->setChecked(studioPortraitLayout); + + ui->multiviewLayout->addItem(QTStr( + "Basic.Settings.General.MultiviewLayout.Horizontal.Top"), + QT_UTF8("horizontaltop")); + ui->multiviewLayout->addItem(QTStr( + "Basic.Settings.General.MultiviewLayout.Horizontal.Bottom"), + QT_UTF8("horizontalbottom")); + ui->multiviewLayout->addItem(QTStr( + "Basic.Settings.General.MultiviewLayout.Vertical.Left"), + QT_UTF8("verticalleft")); + ui->multiviewLayout->addItem(QTStr( + "Basic.Settings.General.MultiviewLayout.Vertical.Right"), + QT_UTF8("verticalright")); + + const char *multiviewLayoutText = config_get_string(GetGlobalConfig(), + "BasicWindow", "MultiviewLayout"); + + if (astrcmpi(multiviewLayoutText, "horizontalbottom") == 0) + ui->multiviewLayout->setCurrentIndex(1); + else if (astrcmpi(multiviewLayoutText, "verticalleft") == 0) + ui->multiviewLayout->setCurrentIndex(2); + else if (astrcmpi(multiviewLayoutText, "verticalright") == 0) + ui->multiviewLayout->setCurrentIndex(3); + else + ui->multiviewLayout->setCurrentIndex(0); + loading = false; } @@ -1336,6 +1414,30 @@ void OBSBasicSettings::LoadVideoSettings() loading = false; } +static inline bool IsSurround(const char *speakers) +{ + static const char *surroundLayouts[] = { + "2.1", + "4.0", + "4.1", + "5.1", + "7.1", + nullptr + }; + + if (!speakers || !*speakers) + return false; + + const char **curLayout = surroundLayouts; + for (; *curLayout; ++curLayout) { + if (strcmp(*curLayout, speakers) == 0) { + return true; + } + } + + return false; +} + void OBSBasicSettings::LoadSimpleOutputSettings() { const char *path = config_get_string(main->Config(), "SimpleOutput", @@ -1391,6 +1493,13 @@ void OBSBasicSettings::LoadSimpleOutputSettings() int idx = ui->simpleOutRecFormat->findText(format); ui->simpleOutRecFormat->setCurrentIndex(idx); + const char *speakers = config_get_string(main->Config(), "Audio", + "ChannelSetup"); + + // restrict list of bitrates when multichannel is OFF + if (!IsSurround(speakers)) + RestrictResetBitrates({ui->simpleOutputABitrate}, 320); + SetComboByName(ui->simpleOutputABitrate, std::to_string(audioBitrate).c_str()); @@ -1494,6 +1603,8 @@ void OBSBasicSettings::LoadAdvOutputStreamingEncoderProperties() connect(streamEncoderProps, SIGNAL(Changed()), this, SLOT(UpdateStreamDelayEstimate())); + connect(streamEncoderProps, SIGNAL(Changed()), + this, SLOT(AdvReplayBufferChanged())); curAdvStreamEncoder = type; @@ -1560,6 +1671,8 @@ void OBSBasicSettings::LoadAdvOutputRecordingEncoderProperties() recordEncoderProps = CreateEncoderPropertyView(type, "recordEncoder.json"); ui->advOutRecStandard->layout()->addWidget(recordEncoderProps); + connect(recordEncoderProps, SIGNAL(Changed()), + this, SLOT(AdvReplayBufferChanged())); } curAdvRecordEncoder = type; @@ -1705,6 +1818,20 @@ void OBSBasicSettings::LoadAdvOutputAudioSettings() track5Bitrate = FindClosestAvailableAACBitrate(track5Bitrate); track6Bitrate = FindClosestAvailableAACBitrate(track6Bitrate); + // restrict list of bitrates when multichannel is OFF + const char *speakers = config_get_string(main->Config(), "Audio", + "ChannelSetup"); + + // restrict list of bitrates when multichannel is OFF + if (!IsSurround(speakers)) { + RestrictResetBitrates({ui->advOutTrack1Bitrate, + ui->advOutTrack2Bitrate, + ui->advOutTrack3Bitrate, + ui->advOutTrack4Bitrate, + ui->advOutTrack5Bitrate, + ui->advOutTrack6Bitrate}, 320); + } + SetComboByName(ui->advOutTrack1Bitrate, std::to_string(track1Bitrate).c_str()); SetComboByName(ui->advOutTrack2Bitrate, @@ -1993,6 +2120,8 @@ void OBSBasicSettings::LoadAudioSettings() "SampleRate"); const char *speakers = config_get_string(main->Config(), "Audio", "ChannelSetup"); + double meterDecayRate = config_get_double(main->Config(), "Audio", + "MeterDecayRate"); loading = true; @@ -2008,9 +2137,26 @@ void OBSBasicSettings::LoadAudioSettings() if (strcmp(speakers, "Mono") == 0) ui->channelSetup->setCurrentIndex(0); + else if (strcmp(speakers, "2.1") == 0) + ui->channelSetup->setCurrentIndex(2); + else if (strcmp(speakers, "4.0") == 0) + ui->channelSetup->setCurrentIndex(3); + else if (strcmp(speakers, "4.1") == 0) + ui->channelSetup->setCurrentIndex(4); + else if (strcmp(speakers, "5.1") == 0) + ui->channelSetup->setCurrentIndex(5); + else if (strcmp(speakers, "7.1") == 0) + ui->channelSetup->setCurrentIndex(6); else ui->channelSetup->setCurrentIndex(1); + if (meterDecayRate == VOLUME_METER_DECAY_MEDIUM) + ui->meterDecayRate->setCurrentIndex(1); + else if (meterDecayRate == VOLUME_METER_DECAY_SLOW) + ui->meterDecayRate->setCurrentIndex(2); + else + ui->meterDecayRate->setCurrentIndex(0); + LoadAudioDevices(); LoadAudioSources(); @@ -2025,7 +2171,7 @@ void OBSBasicSettings::LoadAdvancedSettings() "Video", "ColorSpace"); const char *videoColorRange = config_get_string(main->Config(), "Video", "ColorRange"); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO const char *monDevName = config_get_string(main->Config(), "Audio", "MonitoringDeviceName"); const char *monDevId = config_get_string(main->Config(), "Audio", @@ -2053,13 +2199,18 @@ void OBSBasicSettings::LoadAdvancedSettings() "RecRBPrefix"); const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - int idx; + bool replayBuf = config_get_bool(main->Config(), "AdvOut", + "RecRB"); + int rbTime = config_get_int(main->Config(), "AdvOut", + "RecRBTime"); + int rbSize = config_get_int(main->Config(), "AdvOut", + "RecRBSize"); loading = true; LoadRendererList(); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO if (!SetComboByValue(ui->monitoringDevice, monDevId)) SetInvalidValue(ui->monitoringDevice, monDevName, monDevId); #endif @@ -2069,6 +2220,10 @@ void OBSBasicSettings::LoadAdvancedSettings() ui->simpleRBPrefix->setText(rbPrefix); ui->simpleRBSuffix->setText(rbSuffix); + ui->advReplayBuf->setChecked(replayBuf); + ui->advRBSecMax->setValue(rbTime); + ui->advRBMegsMax->setValue(rbSize); + ui->reconnectEnable->setChecked(reconnect); ui->reconnectRetryDelay->setValue(retryDelay); ui->reconnectMaxRetries->setValue(maxRetries); @@ -2109,7 +2264,7 @@ void OBSBasicSettings::LoadAdvancedSettings() bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); - idx = ui->processPriority->findData(processPriority); + int idx = ui->processPriority->findData(processPriority); if (idx == -1) idx = ui->processPriority->findData("Normal"); ui->processPriority->setCurrentIndex(idx); @@ -2438,8 +2593,9 @@ void OBSBasicSettings::SaveGeneralSettings() string theme = themeData.toStdString(); if (WidgetChanged(ui->theme)) { - config_set_string(GetGlobalConfig(), "General", "Theme", + config_set_string(GetGlobalConfig(), "General", "CurrentTheme", theme.c_str()); + App()->SetTheme(theme); } @@ -2473,6 +2629,10 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_double(GetGlobalConfig(), "BasicWindow", "SnapDistance", ui->snapDistance->value()); + if (WidgetChanged(ui->doubleClickSwitch)) + config_set_bool(GetGlobalConfig(), "BasicWindow", + "TransitionOnDoubleClick", + ui->doubleClickSwitch->isChecked()); config_set_bool(GetGlobalConfig(), "BasicWindow", "WarnBeforeStartingStream", @@ -2525,6 +2685,22 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_bool(GetGlobalConfig(), "BasicWindow", "SaveProjectors", ui->saveProjectors->isChecked()); + + if (WidgetChanged(ui->studioPortraitLayout)) { + config_set_bool(GetGlobalConfig(), "BasicWindow", + "StudioPortraitLayout", + ui->studioPortraitLayout->isChecked()); + + main->ResetUI(); + } + + if (WidgetChanged(ui->multiviewLayout)) { + config_set_string(GetGlobalConfig(), "BasicWindow", + "MultiviewLayout", + QT_TO_UTF8(GetComboData(ui->multiviewLayout))); + + OBSProjector::UpdateMultiviewProjectors(); + } } void OBSBasicSettings::SaveStream1Settings() @@ -2622,7 +2798,7 @@ void OBSBasicSettings::SaveAdvancedSettings() SaveCombo(ui->colorFormat, "Video", "ColorFormat"); SaveCombo(ui->colorSpace, "Video", "ColorSpace"); SaveComboData(ui->colorRange, "Video", "ColorRange"); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO SaveCombo(ui->monitoringDevice, "Audio", "MonitoringDeviceName"); SaveComboData(ui->monitoringDevice, "Audio", "MonitoringDeviceId"); #endif @@ -2648,7 +2824,7 @@ void OBSBasicSettings::SaveAdvancedSettings() SaveSpinBox(ui->reconnectMaxRetries, "Output", "MaxRetries"); SaveComboData(ui->bindToIP, "Output", "BindIP"); -#if defined(_WIN32) || defined(__APPLE__) +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO QString newDevice = ui->monitoringDevice->currentData().toString(); if (lastMonitoringDevice != newDevice) { @@ -2858,6 +3034,10 @@ void OBSBasicSettings::SaveOutputSettings() SaveEdit(ui->advOutTrack5Name, "AdvOut", "Track5Name"); SaveEdit(ui->advOutTrack6Name, "AdvOut", "Track6Name"); + SaveCheckBox(ui->advReplayBuf, "AdvOut", "RecRB"); + SaveSpinBox(ui->advRBSecMax, "AdvOut", "RecRBTime"); + SaveSpinBox(ui->advRBMegsMax, "AdvOut", "RecRBSize"); + WriteJsonData(streamEncoderProps, "streamEncoder.json"); WriteJsonData(recordEncoderProps, "recordEncoder.json"); main->ResetOutputs(); @@ -2868,7 +3048,34 @@ void OBSBasicSettings::SaveAudioSettings() QString sampleRateStr = ui->sampleRate->currentText(); int channelSetupIdx = ui->channelSetup->currentIndex(); - const char *channelSetup = (channelSetupIdx == 0) ? "Mono" : "Stereo"; + const char *channelSetup; + switch (channelSetupIdx) { + case 0: + channelSetup = "Mono"; + break; + case 1: + channelSetup = "Stereo"; + break; + case 2: + channelSetup = "2.1"; + break; + case 3: + channelSetup = "4.0"; + break; + case 4: + channelSetup = "4.1"; + break; + case 5: + channelSetup = "5.1"; + break; + case 6: + channelSetup = "7.1"; + break; + + default: + channelSetup = "Stereo"; + break; + } int sampleRate = 44100; if (sampleRateStr == "48khz") @@ -2882,6 +3089,28 @@ void OBSBasicSettings::SaveAudioSettings() config_set_string(main->Config(), "Audio", "ChannelSetup", channelSetup); + if (WidgetChanged(ui->meterDecayRate)) { + double meterDecayRate; + switch (ui->meterDecayRate->currentIndex()) { + case 0: + meterDecayRate = VOLUME_METER_DECAY_FAST; + break; + case 1: + meterDecayRate = VOLUME_METER_DECAY_MEDIUM; + break; + case 2: + meterDecayRate = VOLUME_METER_DECAY_SLOW; + break; + default: + meterDecayRate = VOLUME_METER_DECAY_FAST; + break; + } + config_set_double(main->Config(), "Audio", "MeterDecayRate", + meterDecayRate); + + main->UpdateVolumeControlsDecayRate(); + } + for (auto &audioSource : audioSources) { auto source = OBSGetStrongRef(get<0>(audioSource)); if (!source) @@ -3188,6 +3417,8 @@ void OBSBasicSettings::on_advOutRecEncoder_currentIndexChanged(int idx) loadSettings ? "recordEncoder.json" : nullptr, true); ui->advOutRecStandard->layout()->addWidget(recordEncoderProps); + connect(recordEncoderProps, SIGNAL(Changed()), + this, SLOT(AdvReplayBufferChanged())); } } @@ -3384,6 +3615,85 @@ void OBSBasicSettings::ReloadAudioSources() LoadAudioSources(); } +#define MULTI_CHANNEL_WARNING "Basic.Settings.Audio.MultichannelWarning" + +void OBSBasicSettings::SpeakerLayoutChanged(int idx) +{ + QString speakerLayoutQstr = ui->channelSetup->itemText(idx); + std::string speakerLayout = QT_TO_UTF8(speakerLayoutQstr); + bool surround = IsSurround(speakerLayout.c_str()); + + if (surround) { + QString warning = + QTStr(MULTI_CHANNEL_WARNING ".Enabled") + + QStringLiteral("\n\n") + + QTStr(MULTI_CHANNEL_WARNING); + /* + * Display all bitrates + */ + ui->audioMsg_2->setText(warning); + PopulateAACBitrates({ui->simpleOutputABitrate, + ui->advOutTrack1Bitrate, + ui->advOutTrack2Bitrate, + ui->advOutTrack3Bitrate, + ui->advOutTrack4Bitrate, + ui->advOutTrack5Bitrate, + ui->advOutTrack6Bitrate}); + } else { + /* + * Reset audio bitrate for simple and adv mode, update list of + * bitrates and save setting. + */ + ui->audioMsg_2->setText(QString()); + RestrictResetBitrates({ui->simpleOutputABitrate, + ui->advOutTrack1Bitrate, + ui->advOutTrack2Bitrate, + ui->advOutTrack3Bitrate, + ui->advOutTrack4Bitrate, + ui->advOutTrack5Bitrate, + ui->advOutTrack6Bitrate}, 320); + + SaveCombo(ui->simpleOutputABitrate, "SimpleOutput", "ABitrate"); + SaveCombo(ui->advOutTrack1Bitrate, "AdvOut", "Track1Bitrate"); + SaveCombo(ui->advOutTrack2Bitrate, "AdvOut", "Track2Bitrate"); + SaveCombo(ui->advOutTrack3Bitrate, "AdvOut", "Track3Bitrate"); + SaveCombo(ui->advOutTrack4Bitrate, "AdvOut", "Track4Bitrate"); + SaveCombo(ui->advOutTrack5Bitrate, "AdvOut", "Track5Bitrate"); + SaveCombo(ui->advOutTrack6Bitrate, "AdvOut", "Track6Bitrate"); + } +} + +/* + * resets current bitrate if too large and restricts the number of bitrates + * displayed when multichannel OFF + */ + +void RestrictResetBitrates(initializer_list boxes, int maxbitrate) +{ + for (auto box : boxes) { + int idx = box->currentIndex(); + int max_bitrate = FindClosestAvailableAACBitrate(maxbitrate); + int count = box->count(); + int max_idx = box->findText(QT_UTF8(std::to_string + (max_bitrate).c_str())); + + for (int i = (count - 1); i > max_idx; i--) + box->removeItem(i); + + if (idx > max_idx) { + int default_bitrate = FindClosestAvailableAACBitrate( + maxbitrate / 2); + int default_idx = box->findText(QT_UTF8(std::to_string + (default_bitrate).c_str())); + + box->setCurrentIndex(default_idx); + box->setProperty("changed", QVariant(true)); + } else { + box->setCurrentIndex(idx); + } + } +} + void OBSBasicSettings::VideoChangedRestart() { if (!loading) { @@ -3734,8 +4044,15 @@ void OBSBasicSettings::SimpleStreamingEncoderChanged() void OBSBasicSettings::UpdateAutomaticReplayBufferCheckboxes() { - bool state = ui->simpleReplayBuf->isChecked() && - ui->outputMode->currentIndex() == 0; + bool state = false; + switch (ui->outputMode->currentIndex()) { + case 0: + state = ui->simpleReplayBuf->isChecked(); + break; + case 1: + state = ui->advReplayBuf->isChecked(); + break; + } ui->replayWhileStreaming->setEnabled(state); ui->keepReplayStreamStops->setEnabled(state && ui->replayWhileStreaming->isChecked()); @@ -3770,7 +4087,89 @@ void OBSBasicSettings::SimpleReplayBufferChanged() ui->simpleReplayBuf->setVisible(!lossless); UpdateAutomaticReplayBufferCheckboxes(); +} +void OBSBasicSettings::AdvReplayBufferChanged() +{ + obs_data_t *settings; + QString encoder = ui->advOutRecEncoder->currentText(); + bool useStream = QString::compare(encoder, TEXT_USE_STREAM_ENC) == 0; + + if (useStream && streamEncoderProps) { + settings = streamEncoderProps->GetSettings(); + } else if (!useStream && recordEncoderProps) { + settings = recordEncoderProps->GetSettings(); + } else { + if (useStream) + encoder = GetComboData(ui->advOutEncoder); + settings = obs_encoder_defaults(encoder.toUtf8().constData()); + + if (!settings) + return; + + char encoderJsonPath[512]; + int ret = GetProfilePath(encoderJsonPath, + sizeof(encoderJsonPath), "recordEncoder.json"); + if (ret > 0) { + obs_data_t *data = obs_data_create_from_json_file_safe( + encoderJsonPath, "bak"); + obs_data_apply(settings, data); + obs_data_release(data); + } + } + + int vbitrate = (int)obs_data_get_int(settings, "bitrate"); + const char *rateControl = obs_data_get_string(settings, "rate_control"); + + bool lossless = strcmp(rateControl, "lossless") == 0 || + ui->advOutRecType->currentIndex() == 1; + bool replayBufferEnabled = ui->advReplayBuf->isChecked(); + + int abitrate = 0; + if (ui->advOutRecTrack1->isChecked()) + abitrate += ui->advOutTrack1Bitrate->currentText().toInt(); + if (ui->advOutRecTrack2->isChecked()) + abitrate += ui->advOutTrack2Bitrate->currentText().toInt(); + if (ui->advOutRecTrack3->isChecked()) + abitrate += ui->advOutTrack3Bitrate->currentText().toInt(); + if (ui->advOutRecTrack4->isChecked()) + abitrate += ui->advOutTrack4Bitrate->currentText().toInt(); + if (ui->advOutRecTrack5->isChecked()) + abitrate += ui->advOutTrack5Bitrate->currentText().toInt(); + if (ui->advOutRecTrack6->isChecked()) + abitrate += ui->advOutTrack6Bitrate->currentText().toInt(); + + int seconds = ui->advRBSecMax->value(); + + int64_t memMB = int64_t(seconds) * int64_t(vbitrate + abitrate) * + 1000 / 8 / 1024 / 1024; + if (memMB < 1) + memMB = 1; + + if (!rateControl) + rateControl = ""; + + bool varRateControl = (astrcmpi(rateControl, "CBR") == 0 || + astrcmpi(rateControl, "VBR") == 0 || + astrcmpi(rateControl, "ABR") == 0); + if (vbitrate == 0) + varRateControl = false; + + ui->advRBMegsMax->setVisible(!varRateControl); + ui->advRBMegsMaxLabel->setVisible(!varRateControl); + + if (varRateControl) + ui->advRBEstimate->setText( + QTStr(ESTIMATE_STR).arg( + QString::number(int(memMB)))); + else + ui->advRBEstimate->setText(QTStr(ESTIMATE_UNKNOWN_STR)); + + ui->advReplayBufferGroupBox->setVisible(!lossless && replayBufferEnabled); + ui->line_4->setVisible(!lossless && replayBufferEnabled); + ui->advReplayBuf->setEnabled(!lossless); + + UpdateAutomaticReplayBufferCheckboxes(); } #define SIMPLE_OUTPUT_WARNING(str) \ @@ -3866,6 +4265,47 @@ void OBSBasicSettings::SimpleRecordingEncoderChanged() ui->simpleOutInfoLayout->addWidget(simpleOutRecWarning); } +void OBSBasicSettings::SurroundWarning(int idx) +{ + if (idx == lastChannelSetupIdx || idx == -1) + return; + + if (loading) { + lastChannelSetupIdx = idx; + return; + } + + QString speakerLayoutQstr = ui->channelSetup->itemText(idx); + bool surround = IsSurround(QT_TO_UTF8(speakerLayoutQstr)); + + QString lastQstr = ui->channelSetup->itemText(lastChannelSetupIdx); + bool wasSurround = IsSurround(QT_TO_UTF8(lastQstr)); + + if (surround && !wasSurround) { + QMessageBox::StandardButton button; + + QString warningString = + QTStr("Basic.Settings.ProgramRestart") + + QStringLiteral("\n\n") + + QTStr(MULTI_CHANNEL_WARNING) + + QStringLiteral("\n\n") + + QTStr(MULTI_CHANNEL_WARNING ".Confirm"); + + button = OBSMessageBox::question(this, + QTStr(MULTI_CHANNEL_WARNING ".Title"), + warningString); + + if (button == QMessageBox::No) { + QMetaObject::invokeMethod(ui->channelSetup, + "setCurrentIndex", Qt::QueuedConnection, + Q_ARG(int, lastChannelSetupIdx)); + return; + } + } + + lastChannelSetupIdx = idx; +} + void OBSBasicSettings::SimpleRecordingQualityLosslessWarning(int idx) { if (idx == lastSimpleRecQualityIdx || idx == -1) diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 5e56ec7..585e535 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -37,6 +37,10 @@ class OBSHotkeyWidget; #include "ui_OBSBasicSettings.h" +#define VOLUME_METER_DECAY_FAST 23.53 +#define VOLUME_METER_DECAY_MEDIUM 11.76 +#define VOLUME_METER_DECAY_SLOW 8.57 + class SilentUpdateCheckBox : public QCheckBox { Q_OBJECT @@ -98,6 +102,7 @@ private: std::string savedTheme; int lastSimpleRecQualityIdx = 0; + int lastChannelSetupIdx = 0; OBSFFFormatDesc formats; @@ -274,6 +279,8 @@ private slots: void AudioChanged(); void AudioChangedRestart(); void ReloadAudioSources(); + void SurroundWarning(int idx); + void SpeakerLayoutChanged(int idx); void OutputsChanged(); void Stream1Changed(); void VideoChanged(); @@ -295,6 +302,7 @@ private slots: void SimpleRecordingQualityLosslessWarning(int idx); void SimpleReplayBufferChanged(); + void AdvReplayBufferChanged(); void SimpleStreamingEncoderChanged(); diff --git a/UI/window-basic-source-select.cpp b/UI/window-basic-source-select.cpp index 4cb6f09..72b3558 100644 --- a/UI/window-basic-source-select.cpp +++ b/UI/window-basic-source-select.cpp @@ -137,7 +137,10 @@ static void AddExisting(const char *name, bool visible, bool duplicate) AddSourceData data; data.source = source; data.visible = visible; + + obs_enter_graphics(); obs_scene_atomic_update(scene, AddSource, &data); + obs_leave_graphics(); obs_source_release(source); } @@ -165,7 +168,10 @@ bool AddNew(QWidget *parent, const char *id, const char *name, AddSourceData data; data.source = source; data.visible = visible; + + obs_enter_graphics(); obs_scene_atomic_update(scene, AddSource, &data); + obs_leave_graphics(); newSource = source; diff --git a/UI/window-basic-stats.cpp b/UI/window-basic-stats.cpp index d3dcbe3..2a66c2d 100644 --- a/UI/window-basic-stats.cpp +++ b/UI/window-basic-stats.cpp @@ -57,15 +57,11 @@ OBSBasicStats::OBSBasicStats(QWidget *parent) cpuUsage = new QLabel(this); hddSpace = new QLabel(this); -#ifdef _WIN32 memUsage = new QLabel(this); -#endif newStat("CPUUsage", cpuUsage, 0); newStat("HDDSpaceAvailable", hddSpace, 0); -#ifdef _WIN32 newStat("MemoryUsage", memUsage, 0); -#endif fps = new QLabel(this); renderTime = new QLabel(this); @@ -240,7 +236,7 @@ void OBSBasicStats::Update() obs_output_release(strOutput); obs_output_release(recOutput); - if (!strOutput || !recOutput) + if (!strOutput && !recOutput) return; /* ------------------------------------------- */ @@ -300,12 +296,10 @@ void OBSBasicStats::Update() /* ------------------ */ -#ifdef _WIN32 - num = (long double)CurrentMemoryUsage() / (1024.0l * 1024.0l); + num = (long double)os_get_proc_resident_size() / (1024.0l * 1024.0l); str = QString::number(num, 'f', 1) + QStringLiteral(" MB"); memUsage->setText(str); -#endif /* ------------------ */ @@ -388,8 +382,8 @@ void OBSBasicStats::Update() /* ------------------------------------------- */ /* recording/streaming stats */ - outputLabels[0].Update(strOutput); - outputLabels[1].Update(recOutput); + outputLabels[0].Update(strOutput, false); + outputLabels[1].Update(recOutput, true); } void OBSBasicStats::Reset() @@ -411,15 +405,9 @@ void OBSBasicStats::Reset() Update(); } -void OBSBasicStats::OutputLabels::Update(obs_output_t *output) +void OBSBasicStats::OutputLabels::Update(obs_output_t *output, bool rec) { - if (!output) - return; - - const char *id = obs_obj_get_id(output); - bool rec = strcmp(id, "rtmp_output") != 0; - - uint64_t totalBytes = obs_output_get_total_bytes(output); + uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0; uint64_t curTime = os_gettime_ns(); uint64_t bytesSent = totalBytes; @@ -439,12 +427,17 @@ void OBSBasicStats::OutputLabels::Update(obs_output_t *output) QString str = QTStr("Basic.Stats.Status.Inactive"); QString themeID; + bool active = output ? obs_output_active(output) : false; if (rec) { - if (obs_output_active(output)) + if (active) str = QTStr("Basic.Stats.Status.Recording"); } else { - if (obs_output_active(output)) { - if (obs_output_reconnecting(output)) { + if (active) { + bool reconnecting = output + ? obs_output_reconnecting(output) + : false; + + if (reconnecting) { str = QTStr("Basic.Stats.Status.Reconnecting"); themeID = "error"; } else { @@ -465,8 +458,8 @@ void OBSBasicStats::OutputLabels::Update(obs_output_t *output) QString("%1 kb/s").arg(QString::number(kbps, 'f', 0))); if (!rec) { - int total = obs_output_get_total_frames(output); - int dropped = obs_output_get_frames_dropped(output); + int total = output ? obs_output_get_total_frames(output) : 0; + int dropped = output ? obs_output_get_frames_dropped(output) : 0; if (total < first_total || dropped < first_dropped) { first_total = 0; diff --git a/UI/window-basic-stats.hpp b/UI/window-basic-stats.hpp index d6502d5..a719b7d 100644 --- a/UI/window-basic-stats.hpp +++ b/UI/window-basic-stats.hpp @@ -42,7 +42,7 @@ class OBSBasicStats : public QWidget { int first_total = 0; int first_dropped = 0; - void Update(obs_output_t *output); + void Update(obs_output_t *output, bool rec); void Reset(obs_output_t *output); }; diff --git a/UI/window-namedialog.cpp b/UI/window-namedialog.cpp index 93193ac..0a90e97 100644 --- a/UI/window-namedialog.cpp +++ b/UI/window-namedialog.cpp @@ -37,11 +37,16 @@ static bool IsWhitespace(char ch) } bool NameDialog::AskForName(QWidget *parent, const QString &title, - const QString &text, string &str, const QString &placeHolder) + const QString &text, string &str, const QString &placeHolder, + int maxSize) { + if (maxSize <= 0 || maxSize > 32767) + maxSize = 256; + NameDialog dialog(parent); dialog.setWindowTitle(title); dialog.ui->label->setText(text); + dialog.ui->userText->setMaxLength(maxSize); dialog.ui->userText->setText(placeHolder); dialog.ui->userText->selectAll(); diff --git a/UI/window-namedialog.hpp b/UI/window-namedialog.hpp index 8431454..8bce854 100644 --- a/UI/window-namedialog.hpp +++ b/UI/window-namedialog.hpp @@ -34,5 +34,6 @@ public: static bool AskForName(QWidget *parent, const QString &title, const QString &text, std::string &str, - const QString &placeHolder = QString("")); + const QString &placeHolder = QString(""), + int maxSize = 256); }; diff --git a/UI/window-projector.cpp b/UI/window-projector.cpp index 134258d..0ad9755 100644 --- a/UI/window-projector.cpp +++ b/UI/window-projector.cpp @@ -7,7 +7,15 @@ #include "display-helpers.hpp" #include "qt-wrappers.hpp" #include "platform.hpp" -#include "obs-app.hpp" + +#define HORIZONTAL_TOP 0 +#define HORIZONTAL_BOTTOM 1 +#define VERTICAL_LEFT 2 +#define VERTICAL_RIGHT 3 + +static QList multiviewProjectors; +static bool updatingMultiview = false; +static int multiviewLayout = HORIZONTAL_TOP; OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, bool window) : OBSQTDisplay (widget, @@ -30,7 +38,10 @@ OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, bool window) auto addDrawCallback = [this] () { - obs_display_add_draw_callback(GetDisplay(), OBSRender, this); + bool isMultiview = type == ProjectorType::Multiview; + obs_display_add_draw_callback(GetDisplay(), + isMultiview ? OBSRenderMultiview : OBSRender, + this); obs_display_set_background_color(GetDisplay(), 0x000000); }; @@ -50,12 +61,79 @@ OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, bool window) OBSProjector::~OBSProjector() { + bool isMultiview = type == ProjectorType::Multiview; + obs_display_remove_draw_callback(GetDisplay(), + isMultiview ? OBSRenderMultiview : OBSRender, this); + if (source) obs_source_dec_showing(source); + + if (isMultiview) { + for (OBSWeakSource &weakSrc : multiviewScenes) { + OBSSource src = OBSGetStrongRef(weakSrc); + if (src) + obs_source_dec_showing(src); + } + + obs_enter_graphics(); + gs_vertexbuffer_destroy(outerBox); + gs_vertexbuffer_destroy(innerBox); + gs_vertexbuffer_destroy(leftVLine); + gs_vertexbuffer_destroy(rightVLine); + gs_vertexbuffer_destroy(leftLine); + gs_vertexbuffer_destroy(topLine); + gs_vertexbuffer_destroy(rightLine); + obs_leave_graphics(); + } + + if (type == ProjectorType::Multiview) + multiviewProjectors.removeAll(this); + App()->DecrementSleepInhibition(); } -void OBSProjector::Init(int monitor, bool window, QString title) +static OBSSource CreateLabel(const char *name, size_t h) +{ + obs_data_t *settings = obs_data_create(); + obs_data_t *font = obs_data_create(); + + std::string text; + text += " "; + text += name; + text += " "; + +#if defined(_WIN32) + obs_data_set_string(font, "face", "Arial"); +#elif defined(__APPLE__) + obs_data_set_string(font, "face", "Helvetica"); +#else + obs_data_set_string(font, "face", "Monospace"); +#endif + obs_data_set_int(font, "flags", 1); // Bold text + obs_data_set_int(font, "size", int(h / 9.81)); + + obs_data_set_obj(settings, "font", font); + obs_data_set_string(settings, "text", text.c_str()); + obs_data_set_bool(settings, "outline", false); + +#ifdef _WIN32 + const char *text_source_id = "text_gdiplus"; +#else + const char *text_source_id = "text_ft2_source"; +#endif + + OBSSource txtSource = obs_source_create_private(text_source_id, name, + settings); + obs_source_release(txtSource); + + obs_data_release(font); + obs_data_release(settings); + + return txtSource; +} + +void OBSProjector::Init(int monitor, bool window, QString title, + ProjectorType type_) { QScreen *screen = QGuiApplication::screens()[monitor]; @@ -81,16 +159,436 @@ void OBSProjector::Init(int monitor, bool window, QString title) addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(EscapeTriggered())); + activateWindow(); } savedMonitor = monitor; - isWindow = window; + isWindow = window; + type = type_; + + if (type == ProjectorType::Multiview) { + obs_enter_graphics(); + gs_render_start(true); + gs_vertex2f(0.001f, 0.001f); + gs_vertex2f(0.001f, 0.997f); + gs_vertex2f(0.997f, 0.997f); + gs_vertex2f(0.997f, 0.001f); + gs_vertex2f(0.001f, 0.001f); + outerBox = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(0.04f, 0.04f); + gs_vertex2f(0.04f, 0.96f); + gs_vertex2f(0.96f, 0.96f); + gs_vertex2f(0.96f, 0.04f); + gs_vertex2f(0.04f, 0.04f); + innerBox = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(0.15f, 0.04f); + gs_vertex2f(0.15f, 0.96f); + leftVLine = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(0.85f, 0.04f); + gs_vertex2f(0.85f, 0.96f); + rightVLine = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(0.0f, 0.5f); + gs_vertex2f(0.075f, 0.5f); + leftLine = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(0.5f, 0.0f); + gs_vertex2f(0.5f, 0.09f); + topLine = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(0.925f, 0.5f); + gs_vertex2f(1.0f, 0.5f); + rightLine = gs_render_save(); + obs_leave_graphics(); + + UpdateMultiview(); + + multiviewProjectors.push_back(this); + } + + ready = true; +} + +static inline void renderVB(gs_effect_t *effect, gs_vertbuffer_t *vb, + int cx, int cy) +{ + if (!vb) + return; + + matrix4 transform; + matrix4_identity(&transform); + transform.x.x = cx; + transform.y.y = cy; + + gs_load_vertexbuffer(vb); + + gs_matrix_push(); + gs_matrix_mul(&transform); + + while (gs_effect_loop(effect, "Solid")) + gs_draw(GS_LINESTRIP, 0, 0); + + gs_matrix_pop(); +} + +static inline uint32_t labelOffset(obs_source_t *label, uint32_t cx) +{ + uint32_t w = obs_source_get_width(label); + w = uint32_t(float(w) * 0.5f); + return (cx / 2) - w; +} + +void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy) +{ + OBSProjector *window = (OBSProjector *)data; + + if (updatingMultiview || !window->ready) + return; + + OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window(); + uint32_t targetCX, targetCY; + int x, y; + float fX, fY, halfCX, halfCY, sourceX, sourceY, labelX, labelY, + quarterCX, quarterCY, scale, targetCXF, targetCYF, + hiCX, hiCY, qiX, qiY, qiCX, qiCY, hiScaleX, hiScaleY, + qiScaleX, qiScaleY; + uint32_t offset; + + gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); + gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); + + struct obs_video_info ovi; + obs_get_video_info(&ovi); + targetCX = ovi.base_width; + targetCY = ovi.base_height; + + GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale); + + targetCXF = float(targetCX); + targetCYF = float(targetCY); + fX = float(x); + fY = float(y); + + halfCX = (targetCXF + 1) / 2; + halfCY = (targetCYF + 1) / 2; + hiCX = (halfCX - 4.0); + hiCY = (halfCY - 4.0); + hiScaleX = hiCX / targetCXF; + hiScaleY = hiCY / targetCYF; + + quarterCX = (halfCX + 1) / 2; + quarterCY = (halfCY + 1) / 2; + qiCX = (quarterCX - 8.0); + qiCY = (quarterCY - 8.0); + qiScaleX = qiCX / targetCXF; + qiScaleY = qiCY / targetCYF; + + OBSSource previewSrc = main->GetCurrentSceneSource(); + OBSSource programSrc = main->GetProgramSource(); + + bool studioMode = main->IsPreviewProgramMode(); + + auto drawBox = [solid, color] (float cx, float cy, + uint32_t colorVal) + { + gs_effect_set_color(color, colorVal); + while (gs_effect_loop(solid, "Solid")) + gs_draw_sprite(nullptr, 0, (uint32_t)cx, (uint32_t)cy); + }; + + auto setRegion = [fX, fY, scale] (float x, float y, float cx, float cy) + { + float vX = int(fX + x * scale); + float vY = int(fY + y * scale); + float vCX = int(cx * scale); + float vCY = int(cy * scale); + + float oL = x; + float oT = y; + float oR = (x + cx); + float oB = (y + cy); + + gs_projection_push(); + gs_viewport_push(); + gs_set_viewport(vX, vY, vCX, vCY); + gs_ortho(oL, oR, oT, oB, -100.0f, 100.0f); + }; + + auto resetRegion = [] () + { + gs_viewport_pop(); + gs_projection_pop(); + }; + + auto calcBaseSource = [&](size_t i) + { + switch (multiviewLayout) { + case VERTICAL_LEFT: + sourceX = halfCX; + sourceY = (i / 2 ) * quarterCY; + if (i % 2 != 0) + sourceX = halfCX + quarterCX; + break; + case VERTICAL_RIGHT: + sourceX = 0; + sourceY = (i / 2 ) * quarterCY; + if (i % 2 != 0) + sourceX = quarterCX; + break; + case HORIZONTAL_BOTTOM: + if (i < 4) { + sourceX = (float(i) * quarterCX); + sourceY = 0; + } else { + sourceX = (float(i - 4) * quarterCX); + sourceY = quarterCY; + } + break; + default: //HORIZONTAL_TOP: + if (i < 4) { + sourceX = (float(i) * quarterCX); + sourceY = halfCY; + } else { + sourceX = (float(i - 4) * quarterCX); + sourceY = halfCY + quarterCY; + } + } + }; + + auto calcPreviewProgram = [&](bool program) + { + switch (multiviewLayout) { + case VERTICAL_LEFT: + sourceX = 2.0f; + sourceY = halfCY + 2.0f; + labelX = offset; + labelY = halfCY * 1.8f; + if (program) { + sourceY = 2.0f; + labelY = halfCY * 0.8f; + } + break; + case VERTICAL_RIGHT: + sourceX = halfCX + 2.0f; + sourceY = halfCY + 2.0f; + labelX = halfCX + offset; + labelY = halfCY * 1.8f; + if (program) { + sourceY = 2.0f; + labelY = halfCY * 0.8f; + } + break; + case HORIZONTAL_BOTTOM: + sourceX = 2.0f; + sourceY = halfCY + 2.0f; + labelX = offset; + labelY = halfCY * 1.8f; + if (program) { + sourceX = halfCX + 2.0f; + labelX = halfCX + offset; + } + break; + default: //HORIZONTAL_TOP: + sourceX = 2.0f; + sourceY = 2.0f; + labelX = offset; + labelY = halfCY * 0.8f; + if (program) { + sourceX = halfCX + 2.0f; + labelX = halfCX + offset; + } + } + }; + + /* ----------------------------- */ + /* draw sources */ + + gs_projection_push(); + gs_viewport_push(); + gs_set_viewport(x, y, targetCX * scale, targetCY * scale); + gs_ortho(0.0f, targetCXF, 0.0f, targetCYF, -100.0f, 100.0f); + + for (size_t i = 0; i < 8; i++) { + OBSSource src = OBSGetStrongRef(window->multiviewScenes[i]); + obs_source *label = window->multiviewLabels[i + 2]; + + if (!src) + continue; + if (!label) + continue; + + calcBaseSource(i); + + qiX = sourceX + 4.0f; + qiY = sourceY + 4.0f; + + /* ----------- */ + + if (src == previewSrc || src == programSrc) { + uint32_t colorVal = src == programSrc + ? 0xFFFF0000 + : 0xFF00FF00; + + gs_matrix_push(); + gs_matrix_translate3f(sourceX, sourceY, 0.0f); + drawBox(quarterCX, quarterCY, colorVal); + gs_matrix_pop(); + + gs_matrix_push(); + gs_matrix_translate3f(qiX, qiY, 0.0f); + drawBox(qiCX, qiCY, 0xFF000000); + gs_matrix_pop(); + } + + /* ----------- */ + + gs_matrix_push(); + gs_matrix_translate3f(qiX, qiY, 0.0f); + gs_matrix_scale3f(qiScaleX, qiScaleY, 1.0f); + + setRegion(qiX, qiY, qiCX, qiCY); + obs_source_video_render(src); + resetRegion(); + + gs_effect_set_color(color, 0xFFFFFFFF); + renderVB(solid, window->outerBox, targetCX, targetCY); + + gs_matrix_pop(); + + /* ----------- */ + + offset = labelOffset(label, quarterCX); + cx = obs_source_get_width(label); + cy = obs_source_get_height(label); + + gs_matrix_push(); + gs_matrix_translate3f(sourceX + offset, + (quarterCY * 0.8f) + sourceY, 0.0f); + + drawBox(cx, cy + int(quarterCX * 0.015f), 0xD91F1F1F); + obs_source_video_render(label); + + gs_matrix_pop(); + } + + gs_effect_set_color(color, 0xFFFFFFFF); + + /* ----------------------------- */ + /* draw preview */ + + obs_source_t *previewLabel = window->multiviewLabels[0]; + offset = labelOffset(previewLabel, halfCX); + calcPreviewProgram(false); + + gs_matrix_push(); + gs_matrix_translate3f(sourceX, sourceY, 0.0f); + gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f); + + setRegion(sourceX, sourceY, hiCX, hiCY); + + if (studioMode) { + obs_source_video_render(previewSrc); + } else { + obs_render_main_texture(); + } + + resetRegion(); + + gs_matrix_pop(); + + /* ----------- */ + + gs_matrix_push(); + gs_matrix_translate3f(sourceX, sourceY, 0.0f); + gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f); + + renderVB(solid, window->outerBox, targetCX, targetCY); + renderVB(solid, window->innerBox, targetCX, targetCY); + renderVB(solid, window->leftVLine, targetCX, targetCY); + renderVB(solid, window->rightVLine, targetCX, targetCY); + renderVB(solid, window->leftLine, targetCX, targetCY); + renderVB(solid, window->topLine, targetCX, targetCY); + renderVB(solid, window->rightLine, targetCX, targetCY); + + gs_matrix_pop(); + + /* ----------- */ + + cx = obs_source_get_width(previewLabel); + cy = obs_source_get_height(previewLabel); + + gs_matrix_push(); + gs_matrix_translate3f(labelX, labelY, 0.0f); + + drawBox(cx, cy + int(halfCX * 0.015f), 0xD91F1F1F); + obs_source_video_render(previewLabel); + + gs_matrix_pop(); + + /* ----------------------------- */ + /* draw program */ + + obs_source_t *programLabel = window->multiviewLabels[1]; + offset = labelOffset(programLabel, halfCX); + calcPreviewProgram(true); + + gs_matrix_push(); + gs_matrix_translate3f(sourceX, sourceY, 0.0f); + gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f); + + setRegion(sourceX, sourceY, hiCX, hiCY); + obs_render_main_texture(); + resetRegion(); + + gs_matrix_pop(); + + /* ----------- */ + + gs_matrix_push(); + gs_matrix_translate3f(sourceX, sourceY, 0.0f); + gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f); + + renderVB(solid, window->outerBox, targetCX, targetCY); + + gs_matrix_pop(); + + /* ----------- */ + + cx = obs_source_get_width(programLabel); + cy = obs_source_get_height(programLabel); + + gs_matrix_push(); + gs_matrix_translate3f(labelX, labelY, 0.0f); + + drawBox(cx, cy + int(halfCX * 0.015f), 0xD91F1F1F); + obs_source_video_render(programLabel); + + gs_matrix_pop(); + + /* ----------------------------- */ + + gs_viewport_pop(); + gs_projection_pop(); } void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy) { OBSProjector *window = reinterpret_cast(data); + if (!window->ready) + return; + + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + uint32_t targetCX; uint32_t targetCY; int x, y; @@ -117,10 +615,24 @@ void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy) gs_ortho(0.0f, float(targetCX), 0.0f, float(targetCY), -100.0f, 100.0f); gs_set_viewport(x, y, newCX, newCY); - if (window->source) - obs_source_video_render(window->source); - else - obs_render_main_view(); + OBSSource source = window->source; + + if (window->type == ProjectorType::Preview && + main->IsPreviewProgramMode()) { + OBSSource curSource = main->GetCurrentSceneSource(); + + if (window->source != curSource) { + obs_source_dec_showing(window->source); + obs_source_inc_showing(curSource); + source = curSource; + } + } + + if (source) { + obs_source_video_render(source); + } else { + obs_render_main_texture(); + } gs_projection_pop(); gs_viewport_pop(); @@ -135,6 +647,129 @@ void OBSProjector::OBSSourceRemoved(void *data, calldata_t *params) UNUSED_PARAMETER(params); } +static int getSourceByPosition(int x, int y) +{ + struct obs_video_info ovi; + obs_get_video_info(&ovi); + float ratio = float(ovi.base_width) / float(ovi.base_height); + + QWidget *rec = QApplication::activeWindow(); + int cx = rec->width(); + int cy = rec->height(); + int minX = 0; + int minY = 0; + int maxX = cx; + int maxY = cy; + int halfX = cx / 2; + int halfY = cy / 2; + int pos = -1; + + switch (multiviewLayout) { + case VERTICAL_LEFT: + if (float(cx) / float(cy) > ratio) { + int validX = cy * ratio; + maxX = halfX + (validX / 2); + } else { + int validY = cx / ratio; + minY = halfY - (validY / 2); + maxY = halfY + (validY / 2); + } + + minX = halfX; + + if (x < minX || x > maxX || y < minY || y > maxY) + break; + + pos = 2 * ((y - minY) / ((maxY - minY) / 4)); + if (x > minX + ((maxX - minX) / 2)) + pos++; + break; + case VERTICAL_RIGHT: + if (float(cx) / float(cy) > ratio) { + int validX = cy * ratio; + minX = halfX - (validX / 2); + } else { + int validY = cx / ratio; + minY = halfY - (validY / 2); + maxY = halfY + (validY / 2); + } + + maxX = halfX; + + if (x < minX || x > maxX || y < minY || y > maxY) + break; + + pos = 2 * ((y - minY) / ((maxY - minY) / 4)); + if (x > minX + ((maxX - minX) / 2)) + pos++; + break; + case HORIZONTAL_BOTTOM: + if (float(cx) / float(cy) > ratio) { + int validX = cy * ratio; + minX = halfX - (validX / 2); + maxX = halfX + (validX / 2); + } else { + int validY = cx / ratio; + minY = halfY - (validY / 2); + } + + maxY = halfY; + + if (x < minX || x > maxX || y < minY || y > maxY) + break; + + pos = (x - minX) / ((maxX - minX) / 4); + if (y > minY + ((maxY - minY) / 2)) + pos += 4; + break; + default: // HORIZONTAL_TOP + if (float(cx) / float(cy) > ratio) { + int validX = cy * ratio; + minX = halfX - (validX / 2); + maxX = halfX + (validX / 2); + } else { + int validY = cx / ratio; + maxY = halfY + (validY / 2); + } + + minY = halfY; + + if (x < minX || x > maxX || y < minY || y > maxY) + break; + + pos = (x - minX) / ((maxX - minX) / 4); + if (y > minY + ((maxY - minY) / 2)) + pos += 4; + } + + return pos; +} + +void OBSProjector::mouseDoubleClickEvent(QMouseEvent *event) +{ + OBSQTDisplay::mouseDoubleClickEvent(event); + + if (!config_get_bool(GetGlobalConfig(), "BasicWindow", + "TransitionOnDoubleClick")) + return; + + OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window(); + if (!main->IsPreviewProgramMode()) + return; + + if (event->button() == Qt::LeftButton) { + int pos = getSourceByPosition(event->x(), event->y()); + if (pos < 0) + return; + OBSSource src = OBSGetStrongRef(multiviewScenes[pos]); + if (!src) + return; + + if (main->GetProgramSource() != src) + main->TransitionToScene(src); + } +} + void OBSProjector::mousePressEvent(QMouseEvent *event) { OBSQTDisplay::mousePressEvent(event); @@ -144,16 +779,98 @@ void OBSProjector::mousePressEvent(QMouseEvent *event) popup.addAction(QTStr("Close"), this, SLOT(EscapeTriggered())); popup.exec(QCursor::pos()); } + + if (event->button() == Qt::LeftButton) { + int pos = getSourceByPosition(event->x(), event->y()); + if (pos < 0) + return; + OBSSource src = OBSGetStrongRef(multiviewScenes[pos]); + if (!src) + return; + + OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window(); + if (main->GetCurrentSceneSource() != src) + main->SetCurrentScene(src, false); + } } void OBSProjector::EscapeTriggered() { if (!isWindow) { - OBSBasic *main = - reinterpret_cast(App()->GetMainWindow()); - + OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window(); main->RemoveSavedProjectors(savedMonitor); } deleteLater(); } + +void OBSProjector::UpdateMultiview() +{ + for (OBSWeakSource &val : multiviewScenes) + val = nullptr; + for (OBSSource &val : multiviewLabels) + val = nullptr; + + struct obs_video_info ovi; + obs_get_video_info(&ovi); + + uint32_t h = ovi.base_height; + + struct obs_frontend_source_list scenes = {}; + obs_frontend_get_scenes(&scenes); + + int curIdx = 0; + + multiviewLabels[0] = CreateLabel(Str("StudioMode.Preview"), h / 2); + multiviewLabels[1] = CreateLabel(Str("StudioMode.Program"), h / 2); + + for (size_t i = 0; i < scenes.sources.num && curIdx < 8; i++) { + obs_source_t *src = scenes.sources.array[i]; + OBSData data = obs_source_get_private_settings(src); + obs_data_release(data); + + obs_data_set_default_bool(data, "show_in_multiview", true); + if (!obs_data_get_bool(data, "show_in_multiview")) + continue; + + multiviewScenes[curIdx] = OBSGetWeakRef(src); + obs_source_inc_showing(src); + + std::string name; + name += std::to_string(curIdx + 1); + name += " - "; + name += obs_source_get_name(src); + + multiviewLabels[curIdx + 2] = CreateLabel(name.c_str(), h / 3); + + curIdx++; + } + + obs_frontend_source_list_free(&scenes); + + const char *multiviewLayoutText = config_get_string(GetGlobalConfig(), + "BasicWindow", "MultiviewLayout"); + + if (astrcmpi(multiviewLayoutText, "horizontalbottom") == 0) + multiviewLayout = HORIZONTAL_BOTTOM; + else if (astrcmpi(multiviewLayoutText, "verticalleft") == 0) + multiviewLayout = VERTICAL_LEFT; + else if (astrcmpi(multiviewLayoutText, "verticalright") == 0) + multiviewLayout = VERTICAL_RIGHT; + else + multiviewLayout = HORIZONTAL_TOP; +} + +void OBSProjector::UpdateMultiviewProjectors() +{ + obs_enter_graphics(); + updatingMultiview = true; + obs_leave_graphics(); + + for (auto &projector : multiviewProjectors) + projector->UpdateMultiview(); + + obs_enter_graphics(); + updatingMultiview = false; + obs_leave_graphics(); +} diff --git a/UI/window-projector.hpp b/UI/window-projector.hpp index 48205dd..606ab61 100644 --- a/UI/window-projector.hpp +++ b/UI/window-projector.hpp @@ -13,13 +13,28 @@ private: OBSSource source; OBSSignal removedSignal; + static void OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy); static void OBSRender(void *data, uint32_t cx, uint32_t cy); static void OBSSourceRemoved(void *data, calldata_t *params); void mousePressEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; int savedMonitor = 0; bool isWindow = false; + ProjectorType type = ProjectorType::Source; + OBSWeakSource multiviewScenes[8]; + OBSSource multiviewLabels[10]; + gs_vertbuffer_t *outerBox = nullptr; + gs_vertbuffer_t *innerBox = nullptr; + gs_vertbuffer_t *leftVLine = nullptr; + gs_vertbuffer_t *rightVLine = nullptr; + gs_vertbuffer_t *leftLine = nullptr; + gs_vertbuffer_t *topLine = nullptr; + gs_vertbuffer_t *rightLine = nullptr; + bool ready = false; + + void UpdateMultiview(); private slots: void EscapeTriggered(); @@ -28,5 +43,8 @@ public: OBSProjector(QWidget *parent, obs_source_t *source, bool window); ~OBSProjector(); - void Init(int monitor, bool window, QString title); + void Init(int monitor, bool window, QString title, + ProjectorType type = ProjectorType::Source); + + static void UpdateMultiviewProjectors(); }; diff --git a/appveyor.yml b/appveyor.yml index a7a8447..6d13f10 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,10 @@ environment: - CURL_VERSION: 7.39.0 + CURL_VERSION: 7.56.1 install: - git submodule update --init --recursive - - if not exist dependencies2015.zip curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C - - - if not exist vlc.zip curl -kLO https://obsproject.com/downloads/vlc.zip -f --retry 5 -C - + - if exist dependencies2015.zip (curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -z dependencies2015.zip) else (curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C -) + - if exist vlc.zip (curl -kLO https://obsproject.com/downloads/vlc.zip -f --retry 5 -z vlc.zip) else (curl -kLO https://obsproject.com/downloads/vlc.zip -f --retry 5 -C -) - 7z x dependencies2015.zip -odependencies2015 - 7z x vlc.zip -ovlc - set DepsPath32=%CD%\dependencies2015\win32 diff --git a/cmake/Modules/CopyMSVCBins.cmake b/cmake/Modules/CopyMSVCBins.cmake index 13ce4ea..6cba959 100644 --- a/cmake/Modules/CopyMSVCBins.cmake +++ b/cmake/Modules/CopyMSVCBins.cmake @@ -122,6 +122,16 @@ file(GLOB CURL_BIN_FILES "${CURL_INCLUDE_DIR}/bin/curl*.dll" ) +file(GLOB LUA_BIN_FILES + "${LUAJIT_INCLUDE_DIR}/../../bin${_bin_suffix}/lua*.dll" + "${LUAJIT_INCLUDE_DIR}/../../bin/lua*.dll" + "${LUAJIT_INCLUDE_DIR}/../bin${_bin_suffix}/lua*.dll" + "${LUAJIT_INCLUDE_DIR}/../bin/lua*.dll" + "${LUAJIT_INCLUDE_DIR}/bin${_bin_suffix}/lua*.dll" + "${LUAJIT_INCLUDE_DIR}/bin/lua*.dll" + "${LUAJIT_INCLUDE_DIR}/lua*.dll" + ) + if (ZLIB_LIB) GET_FILENAME_COMPONENT(ZLIB_BIN_PATH ${ZLIB_LIB} PATH) endif() @@ -166,6 +176,7 @@ set(ALL_BASE_BIN_FILES ${FFMPEG_BIN_FILES} ${X264_BIN_FILES} ${CURL_BIN_FILES} + ${LUA_BIN_FILES} ${SSL_BIN_FILES} ${ZLIB_BIN_FILES} ${LIBFDK_BIN_FILES} @@ -199,6 +210,7 @@ message(STATUS "x264 files: ${X264_BIN_FILES}") message(STATUS "Libfdk files: ${LIBFDK_BIN_FILES}") message(STATUS "Freetype files: ${FREETYPE_BIN_FILES}") message(STATUS "curl files: ${CURL_BIN_FILES}") +message(STATUS "lua files: ${LUA_BIN_FILES}") message(STATUS "ssl files: ${SSL_BIN_FILES}") message(STATUS "zlib files: ${ZLIB_BIN_FILES}") message(STATUS "QT Debug files: ${QT_DEBUG_BIN_FILES}") diff --git a/cmake/Modules/FindLuajit.cmake b/cmake/Modules/FindLuajit.cmake new file mode 100644 index 0000000..73e5e00 --- /dev/null +++ b/cmake/Modules/FindLuajit.cmake @@ -0,0 +1,84 @@ +# Once done these will be defined: +# +# LUAJIT_FOUND +# LUAJIT_INCLUDE_DIRS +# LUAJIT_LIBRARIES +# +# For use in OBS: +# +# LUAJIT_INCLUDE_DIR + +IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(_LIB_SUFFIX 64) +ELSE() + SET(_LIB_SUFFIX 32) +ENDIF() + +FIND_PATH(LUAJIT_INCLUDE_DIR + NAMES lua.h lualib.h + HINTS + ENV LuajitPath${_LIB_SUFFIX} + ENV LuajitPath + ENV DepsPath${_LIB_SUFFIX} + ENV DepsPath + ${LuajitPath${_LIB_SUFFIX}} + ${LuajitPath} + ${DepsPath${_LIB_SUFFIX}} + ${DepsPath} + ${_LUAJIT_INCLUDE_DIRS} + PATHS + /usr/include + /usr/local/include + /opt/local/include + /opt/local + /sw/include + ~/Library/Frameworks + /Library/Frameworks + PATH_SUFFIXES + include + luajit + luajit/src + include/luajit + include/luajit/src + luajit-2.0 + include/luajit-2.0 + luajit2.0 + include/luajit2.0 + ) + +find_library(LUAJIT_LIB + NAMES ${_LUAJIT_LIBRARIES} luajit luajit-51 luajit-5.1 lua51 + HINTS + ENV LuajitPath${_lib_suffix} + ENV LuajitPath + ENV DepsPath${_lib_suffix} + ENV DepsPath + ${LuajitPath${_lib_suffix}} + ${LuajitPath} + ${DepsPath${_lib_suffix}} + ${DepsPath} + ${_LUAJIT_LIBRARY_DIRS} + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /opt/local + /sw/lib + ~/Library/Frameworks + /Library/Frameworks + PATH_SUFFIXES + lib${_lib_suffix} lib + libs${_lib_suffix} libs + bin${_lib_suffix} bin + ../lib${_lib_suffix} ../lib + ../libs${_lib_suffix} ../libs + ../bin${_lib_suffix} ../bin) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Luajit DEFAULT_MSG LUAJIT_LIB LUAJIT_INCLUDE_DIR) +mark_as_advanced(LUAJIT_INCLUDE_DIR LUAJIT_LIB) + +if(LUAJIT_FOUND) + set(LUAJIT_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIR}) + set(LUAJIT_LIBRARIES ${LUAJIT_LIB}) +endif() diff --git a/cmake/Modules/FindPythonDeps.cmake b/cmake/Modules/FindPythonDeps.cmake new file mode 100644 index 0000000..8d1a53b --- /dev/null +++ b/cmake/Modules/FindPythonDeps.cmake @@ -0,0 +1,68 @@ +# Once done these will be defined: +# +# PYTHON_FOUND +# PYTHON_INCLUDE_DIRS +# PYTHON_LIBRARIES +# +# For use in OBS: +# +# PYTHON_INCLUDE_DIR + +if(NOT WIN32) + set(Python_ADDITIONAL_VERSIONS 3.4) + find_package(PythonLibs QUIET 3.4) + return() +endif() + +IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(_LIB_SUFFIX 64) +ELSE() + SET(_LIB_SUFFIX 32) +ENDIF() + +FIND_PATH(PYTHON_INCLUDE_DIR + NAMES Python.h + HINTS + ENV PythonPath${_LIB_SUFFIX} + ENV PythonPath + ENV DepsPath${_LIB_SUFFIX} + ENV DepsPath + ${PythonPath${_LIB_SUFFIX}} + ${PythonPath} + ${DepsPath${_LIB_SUFFIX}} + ${DepsPath} + ${_PYTHON_INCLUDE_DIRS} + PATH_SUFFIXES + include + include/python + ) + +find_library(PYTHON_LIB + NAMES ${_PYTHON_LIBRARIES} python36 + HINTS + ENV PythonPath${_lib_suffix} + ENV PythonPath + ENV DepsPath${_lib_suffix} + ENV DepsPath + ${PythonPath${_lib_suffix}} + ${PythonPath} + ${DepsPath${_lib_suffix}} + ${DepsPath} + ${_PYTHON_LIBRARY_DIRS} + PATH_SUFFIXES + lib${_lib_suffix} lib + libs${_lib_suffix} libs + bin${_lib_suffix} bin + ../lib${_lib_suffix} ../lib + ../libs${_lib_suffix} ../libs + ../bin${_lib_suffix} ../bin) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Python DEFAULT_MSG PYTHON_LIB PYTHON_INCLUDE_DIR) +mark_as_advanced(PYTHON_INCLUDE_DIR PYTHON_LIB) + +if(PYTHON_FOUND) + set(PYTHON_INCLUDE_DIRS ${PYTHON_INCLUDE_DIR}) + set(PYTHON_LIBRARIES ${PYTHON_LIB}) + set(PYTHONLIBS_FOUND TRUE) +endif() diff --git a/cmake/Modules/FindSwigDeps.cmake b/cmake/Modules/FindSwigDeps.cmake new file mode 100644 index 0000000..95a83cf --- /dev/null +++ b/cmake/Modules/FindSwigDeps.cmake @@ -0,0 +1,43 @@ +if(WIN32) + IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(_LIB_SUFFIX 64) + ELSE() + SET(_LIB_SUFFIX 32) + ENDIF() + + FIND_PATH(SWIG_DIR + NAMES swigrun.i + HINTS + ENV SwigPath${_LIB_SUFFIX} + ENV SwigPath + ENV DepsPath${_LIB_SUFFIX} + ENV DepsPath + ${SwigPath${_LIB_SUFFIX}} + ${SwigPath} + ${DepsPath${_LIB_SUFFIX}} + ${DepsPath} + ${_PYTHON_INCLUDE_DIRS} + PATH_SUFFIXES + ../swig/Lib + swig/Lib + ) + + find_program(SWIG_EXECUTABLE + NAMES swig + HINTS + ENV SwigPath${_LIB_SUFFIX} + ENV SwigPath + ENV DepsPath${_LIB_SUFFIX} + ENV DepsPath + ${SwigPath${_LIB_SUFFIX}} + ${SwigPath} + ${DepsPath${_LIB_SUFFIX}} + ${DepsPath} + ${_PYTHON_INCLUDE_DIRS} + PATH_SUFFIXES + ../swig + swig + ) +endif() + +find_package(SWIG QUIET 2) diff --git a/cmake/Modules/ObsHelpers.cmake b/cmake/Modules/ObsHelpers.cmake index 398fb71..c0073d7 100644 --- a/cmake/Modules/ObsHelpers.cmake +++ b/cmake/Modules/ObsHelpers.cmake @@ -60,6 +60,8 @@ if(NOT UNIX_STRUCTURE) set(OBS_DATA_PATH "../${OBS_DATA_DESTINATION}") set(OBS_INSTALL_PREFIX "") set(OBS_RELATIVE_PREFIX "../") + + set(OBS_SCRIPT_PLUGIN_DESTINATION "${OBS_DATA_DESTINATION}/obs-scripting/${_lib_suffix}bit") else() set(OBS_EXECUTABLE_DESTINATION "bin/${_lib_suffix}bit") set(OBS_EXECUTABLE32_DESTINATION "bin/32bit") @@ -74,10 +76,14 @@ if(NOT UNIX_STRUCTURE) set(OBS_DATA_PATH "../../${OBS_DATA_DESTINATION}") set(OBS_INSTALL_PREFIX "") set(OBS_RELATIVE_PREFIX "../../") + + set(OBS_SCRIPT_PLUGIN_DESTINATION "${OBS_DATA_DESTINATION}/obs-scripting/${_lib_suffix}bit") endif() set(OBS_CMAKE_DESTINATION "cmake") set(OBS_INCLUDE_DESTINATION "include") set(OBS_UNIX_STRUCTURE "0") + + set(OBS_SCRIPT_PLUGIN_PATH "${OBS_RELATIVE_PREFIX}${OBS_SCRIPT_PLUGIN_DESTINATION}") else() if(NOT OBS_MULTIARCH_SUFFIX AND DEFINED ENV{OBS_MULTIARCH_SUFFIX}) set(OBS_MULTIARCH_SUFFIX "$ENV{OBS_MULTIARCH_SUFFIX}") @@ -100,6 +106,9 @@ else() set(OBS_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/") set(OBS_RELATIVE_PREFIX "../") set(OBS_UNIX_STRUCTURE "1") + + set(OBS_SCRIPT_PLUGIN_DESTINATION "${OBS_LIBRARY_DESTINATION}/obs-scripting") + set(OBS_SCRIPT_PLUGIN_PATH "${OBS_INSTALL_PREFIX}${OBS_SCRIPT_PLUGIN_DESTINATION}") endif() function(obs_finish_bundle) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 33704b2..333dc6a 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -12,6 +12,7 @@ endif() add_subdirectory(media-playback) add_subdirectory(file-updater) +add_subdirectory(obs-scripting) if(WIN32) add_subdirectory(blake2) diff --git a/deps/file-updater/file-updater/file-updater.c b/deps/file-updater/file-updater/file-updater.c index 4743541..b8d649e 100644 --- a/deps/file-updater/file-updater/file-updater.c +++ b/deps/file-updater/file-updater/file-updater.c @@ -117,6 +117,7 @@ static bool do_http_request(struct update_info *info, const char *url, curl_easy_setopt(info->curl, CURLOPT_WRITEFUNCTION, http_write); curl_easy_setopt(info->curl, CURLOPT_WRITEDATA, info); curl_easy_setopt(info->curl, CURLOPT_FAILONERROR, true); + curl_easy_setopt(info->curl, CURLOPT_NOSIGNAL, 1); if (!info->remote_url) { // We only care about headers from the main package file @@ -523,3 +524,53 @@ update_info_t *update_info_create( return info; } + +static void *single_file_thread(void *data) +{ + struct update_info *info = data; + struct file_download_data download_data; + long response_code; + + info->curl = curl_easy_init(); + if (!info->curl) { + warn("Could not initialize Curl"); + return NULL; + } + + if (!do_http_request(info, info->url, &response_code)) + return NULL; + if (!info->file_data.array || !info->file_data.array[0]) + return NULL; + + download_data.name = info->url; + download_data.version = 0; + download_data.buffer.da = info->file_data.da; + info->callback(info->param, &download_data); + info->file_data.da = download_data.buffer.da; + return NULL; +} + +update_info_t *update_info_create_single( + const char *log_prefix, + const char *user_agent, + const char *file_url, + confirm_file_callback_t confirm_callback, + void *param) +{ + struct update_info *info; + + if (!log_prefix) + log_prefix = ""; + + info = bzalloc(sizeof(*info)); + info->log_prefix = bstrdup(log_prefix); + info->user_agent = bstrdup(user_agent); + info->url = bstrdup(file_url); + info->callback = confirm_callback; + info->param = param; + + if (pthread_create(&info->thread, NULL, single_file_thread, info) == 0) + info->thread_created = true; + + return info; +} diff --git a/deps/file-updater/file-updater/file-updater.h b/deps/file-updater/file-updater/file-updater.h index a1639b5..be07618 100644 --- a/deps/file-updater/file-updater/file-updater.h +++ b/deps/file-updater/file-updater/file-updater.h @@ -1,5 +1,7 @@ #pragma once +#include + struct update_info; typedef struct update_info update_info_t; @@ -21,4 +23,10 @@ update_info_t *update_info_create( const char *cache_dir, confirm_file_callback_t confirm_callback, void *param); +update_info_t *update_info_create_single( + const char *log_prefix, + const char *user_agent, + const char *file_url, + confirm_file_callback_t confirm_callback, + void *param); void update_info_destroy(update_info_t *info); diff --git a/deps/glad/CMakeLists.txt b/deps/glad/CMakeLists.txt index 0fe6a93..79c4d3b 100644 --- a/deps/glad/CMakeLists.txt +++ b/deps/glad/CMakeLists.txt @@ -20,6 +20,14 @@ elseif(NOT APPLE) include/glad/glad_glx.h) endif() +set(glad_include_dirs + PRIVATE ${OPENGL_INCLUDE_DIR}) + +if (UNIX AND NOT APPLE) +list (APPEND glad_include_dirs + PRIVATE ${X11_X11_INCLUDE_PATH}) +endif() + add_library(glad SHARED ${glad_SOURCES} ${glad_PLATFORM_SOURCES}) @@ -29,7 +37,7 @@ set_target_properties(glad PROPERTIES SOVERSION "0") target_include_directories(glad PUBLIC include - PRIVATE ${X11_X11_INCLUDE_PATH} ${OPENGL_INCLUDE_DIR}) + ${glad_include_dirs}) target_compile_definitions(glad PRIVATE GLAD_GLAPI_EXPORT_BUILD) diff --git a/deps/libff/libff/ff-util.c b/deps/libff/libff/ff-util.c index 03fe39a..4db53c0 100644 --- a/deps/libff/libff/ff-util.c +++ b/deps/libff/libff/ff-util.c @@ -16,11 +16,21 @@ #include "ff-util.h" +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244) +#pragma warning(disable : 4204) +#endif + #include #include #include #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include struct ff_format_desc { @@ -68,7 +78,7 @@ static bool get_codecs(const AVCodecDescriptor*** descs, unsigned int *size) unsigned int codec_count = 0; unsigned int i = 0; - while ((desc = avcodec_descriptor_next(desc))) + while ((desc = avcodec_descriptor_next(desc)) != NULL) codec_count++; codecs = av_calloc(codec_count, sizeof(AVCodecDescriptor *)); @@ -79,7 +89,7 @@ static bool get_codecs(const AVCodecDescriptor*** descs, unsigned int *size) return false; } - while ((desc = avcodec_descriptor_next(desc))) + while ((desc = avcodec_descriptor_next(desc)) != NULL) codecs[i++] = desc; *size = codec_count; @@ -89,7 +99,7 @@ static bool get_codecs(const AVCodecDescriptor*** descs, unsigned int *size) static const AVCodec *next_codec_for_id(enum AVCodecID id, const AVCodec *prev) { - while ((prev = av_codec_next(prev))) { + while ((prev = av_codec_next(prev)) != NULL) { if (prev->id == id && av_codec_is_encoder(prev)) return prev; } @@ -156,7 +166,7 @@ static void get_codecs_for_id(const struct ff_format_desc *format_desc, enum AVCodecID id, bool ignore_compatability) { const AVCodec *codec = NULL; - while ((codec = next_codec_for_id(id, codec))) + while ((codec = next_codec_for_id(id, codec)) != NULL) add_codec_to_list(format_desc, first, current, codec->id, codec, ignore_compatability); } @@ -277,7 +287,7 @@ const struct ff_format_desc *ff_format_supported() struct ff_format_desc *desc = NULL; struct ff_format_desc *current = NULL; - while ((output_format = av_oformat_next(output_format))) { + while ((output_format = av_oformat_next(output_format)) != NULL) { struct ff_format_desc *d; if (is_output_device(output_format->priv_class)) continue; diff --git a/deps/media-playback/media-playback/decode.c b/deps/media-playback/media-playback/decode.c index bd8f1ef..b82d329 100644 --- a/deps/media-playback/media-playback/decode.c +++ b/deps/media-playback/media-playback/decode.c @@ -24,8 +24,8 @@ static AVCodec *find_hardware_decoder(enum AVCodecID id) while (hwa) { if (hwa->id == id) { - if (hwa->pix_fmt == AV_PIX_FMT_VDA_VLD || - hwa->pix_fmt == AV_PIX_FMT_DXVA2_VLD || + if (hwa->pix_fmt == AV_PIX_FMT_VDTOOL || + hwa->pix_fmt == AV_PIX_FMT_DXVA2_VLD || hwa->pix_fmt == AV_PIX_FMT_VAAPI_VLD) { c = avcodec_find_decoder_by_name(hwa->name); if (c) @@ -142,8 +142,8 @@ bool mp_decode_init(mp_media_t *m, enum AVMediaType type, bool hw) return false; } - if (d->codec->capabilities & CODEC_CAP_TRUNCATED) - d->decoder->flags |= CODEC_FLAG_TRUNCATED; + if (d->codec->capabilities & CODEC_CAP_TRUNC) + d->decoder->flags |= CODEC_FLAG_TRUNC; return true; } @@ -167,14 +167,16 @@ void mp_decode_free(struct mp_decode *d) circlebuf_free(&d->packets); if (d->decoder) { - avcodec_close(d->decoder); #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) - av_free(d->decoder); + avcodec_free_context(&d->decoder); +#else + avcodec_close(d->decoder); #endif } - - if (d->frame) + if (d->frame) { + av_frame_unref(d->frame); av_free(d->frame); + } memset(d, 0, sizeof(*d)); } diff --git a/deps/media-playback/media-playback/decode.h b/deps/media-playback/media-playback/decode.h index 78a7842..f596485 100644 --- a/deps/media-playback/media-playback/decode.h +++ b/deps/media-playback/media-playback/decode.h @@ -36,6 +36,20 @@ extern "C" { #pragma warning(pop) #endif +#if LIBAVCODEC_VERSION_MAJOR >= 58 +#define CODEC_CAP_TRUNC AV_CODEC_CAP_TRUNCATED +#define CODEC_FLAG_TRUNC AV_CODEC_FLAG_TRUNCATED +#else +#define CODEC_CAP_TRUNC CODEC_CAP_TRUNCATED +#define CODEC_FLAG_TRUNC CODEC_FLAG_TRUNCATED +#endif + +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(54, 31, 100) +#define AV_PIX_FMT_VDTOOL AV_PIX_FMT_VIDEOTOOLBOX +#else +#define AV_PIX_FMT_VDTOOL AV_PIX_FMT_VDA_VLD +#endif + struct mp_media; struct mp_decode { diff --git a/deps/media-playback/media-playback/media.c b/deps/media-playback/media-playback/media.c index e6922b0..e2e0b04 100644 --- a/deps/media-playback/media-playback/media.c +++ b/deps/media-playback/media-playback/media.c @@ -61,6 +61,21 @@ static inline enum audio_format convert_sample_format(int f) return AUDIO_FORMAT_UNKNOWN; } +static inline enum speaker_layout convert_speaker_layout(uint8_t channels) +{ + switch (channels) { + case 0: return SPEAKERS_UNKNOWN; + case 1: return SPEAKERS_MONO; + case 2: return SPEAKERS_STEREO; + case 3: return SPEAKERS_2POINT1; + case 4: return SPEAKERS_4POINT0; + case 5: return SPEAKERS_4POINT1; + case 6: return SPEAKERS_5POINT1; + case 8: return SPEAKERS_7POINT1; + default: return SPEAKERS_UNKNOWN; + } +} + static inline enum video_colorspace convert_color_space(enum AVColorSpace s) { return s == AVCOL_SPC_BT709 ? VIDEO_CS_709 : VIDEO_CS_DEFAULT; @@ -257,7 +272,7 @@ static void mp_media_next_audio(mp_media_t *m) audio.data[i] = f->data[i]; audio.samples_per_sec = f->sample_rate; - audio.speakers = (enum speaker_layout)f->channels; + audio.speakers = convert_speaker_layout(f->channels); audio.format = convert_sample_format(f->format); audio.frames = f->nb_samples; audio.timestamp = m->base_ts + d->frame_pts - m->start_ts + @@ -357,7 +372,7 @@ static void mp_media_next_video(mp_media_t *m, bool preload) frame->height = f->height; frame->flip = flip; - if (m->is_network && !d->got_first_keyframe) { + if (!m->is_local_file && !d->got_first_keyframe) { if (!f->key_frame) return; @@ -407,18 +422,17 @@ static bool mp_media_reset(mp_media_t *m) ? av_rescale_q(seek_pos, AV_TIME_BASE_Q, stream->time_base) : seek_pos; - if (!m->is_network) { + if (m->is_local_file) { int ret = av_seek_frame(m->fmt, 0, seek_target, seek_flags); if (ret < 0) { blog(LOG_WARNING, "MP: Failed to seek: %s", av_err2str(ret)); - return false; } } - if (m->has_video && !m->is_network) + if (m->has_video && m->is_local_file) mp_decode_flush(&m->v); - if (m->has_audio && !m->is_network) + if (m->has_audio && m->is_local_file) mp_decode_flush(&m->a); int64_t next_ts = mp_media_get_base_pts(m); @@ -448,7 +462,7 @@ static bool mp_media_reset(mp_media_t *m) m->next_ns = 0; } - if (!active && !m->is_network && m->v_preload_cb) + if (!active && m->is_local_file && m->v_preload_cb) mp_media_next_video(m, true); if (stopping && m->stop_cb) m->stop_cb(m->opaque); @@ -528,7 +542,7 @@ static bool init_avformat(mp_media_t *m) } AVDictionary *opts = NULL; - if (m->buffering && m->is_network) + if (m->buffering && !m->is_local_file) av_dict_set_int(&opts, "buffer_size", m->buffering, 0); m->fmt = avformat_alloc_context(); @@ -674,6 +688,7 @@ bool mp_media_init(mp_media_t *media, mp_stop_cb stop_cb, mp_video_cb v_preload_cb, bool hw_decoding, + bool is_local_file, enum video_range_type force_range) { memset(media, 0, sizeof(*media)); @@ -685,9 +700,7 @@ bool mp_media_init(mp_media_t *media, media->v_preload_cb = v_preload_cb; media->force_range = force_range; media->buffering = buffering; - - if (path && *path) - media->is_network = !!strstr(path, "://"); + media->is_local_file = is_local_file; static bool initialized = false; if (!initialized) { diff --git a/deps/media-playback/media-playback/media.h b/deps/media-playback/media-playback/media.h index e7c39c6..d72c982 100644 --- a/deps/media-playback/media-playback/media.h +++ b/deps/media-playback/media-playback/media.h @@ -62,7 +62,7 @@ struct mp_media { struct mp_decode v; struct mp_decode a; - bool is_network; + bool is_local_file; bool has_video; bool has_audio; bool is_file; @@ -106,6 +106,7 @@ extern bool mp_media_init(mp_media_t *media, mp_stop_cb stop_cb, mp_video_cb v_preload_cb, bool hardware_decoding, + bool is_local_file, enum video_range_type force_range); extern void mp_media_free(mp_media_t *media); diff --git a/deps/obs-scripting/CMakeLists.txt b/deps/obs-scripting/CMakeLists.txt new file mode 100644 index 0000000..835c1b3 --- /dev/null +++ b/deps/obs-scripting/CMakeLists.txt @@ -0,0 +1,178 @@ +cmake_minimum_required(VERSION 2.8) + +if(NOT ENABLE_SCRIPTING) + return() +endif() + +project(obs-scripting) + +if(MSVC) + set(obs-scripting_PLATFORM_DEPS + w32-pthreads) +endif() + +find_package(Luajit QUIET) +find_package(PythonDeps QUIET) +find_package(SwigDeps QUIET 2) + +set(COMPILE_PYTHON FALSE CACHE BOOL "" FORCE) +set(COMPILE_LUA FALSE CACHE BOOL "" FORCE) + +if(NOT SWIG_FOUND) + message(STATUS "Scripting: SWIG not found; scripting disabled") + return() +endif() + +if(NOT PYTHONLIBS_FOUND AND NOT LUAJIT_FOUND) + message(STATUS "Scripting: Neither Python 3 nor Luajit was found; scripting plugin disabled") + return() +endif() + +if(NOT LUAJIT_FOUND) + message(STATUS "Scripting: Luajit not found; Luajit support disabled") +else() + message(STATUS "Scripting: Luajit supported") + set(COMPILE_LUA TRUE CACHE BOOL "" FORCE) +endif() + +if(NOT PYTHONLIBS_FOUND) + message(STATUS "Scripting: Python 3 not found; Python support disabled") + set(PYTHON_FOUND FALSE) + set(PYTHONLIBS_FOUND FALSE) +else() + message(STATUS "Scripting: Python 3 supported") + set(PYTHON_FOUND TRUE) + set(COMPILE_PYTHON TRUE CACHE BOOL "" FORCE) + + get_filename_component(PYTHON_LIB "${PYTHON_LIBRARIES}" NAME) + string(REGEX REPLACE "\\.[^.]*$" "" PYTHON_LIB ${PYTHON_LIB}) + if(WIN32) + string(REGEX REPLACE "_d" "" PYTHON_LIB "${PYTHON_LIB}") + endif() +endif() + +set(SCRIPTING_ENABLED ON CACHE BOOL "Interal global cmake variable" FORCE) + +if(UI_ENABLED) + set(EXTRA_LIBS obs-frontend-api) + include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/UI/obs-frontend-api") +endif() + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/obs-scripting-config.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/obs-scripting-config.h") + +include(${SWIG_USE_FILE}) + +include_directories(${CMAKE_SOURCE_DIR}/libobs) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +if(PYTHONLIBS_FOUND) + include_directories(${PYTHON_INCLUDE_DIR}) + + set(obs-scripting-python_SOURCES + obs-scripting-python.c + ) + set(obs-scripting-python_HEADERS + obs-scripting-python.h + obs-scripting-python-import.h + ) + + if(UI_ENABLED) + set(obs-scripting-python_SOURCES + ${obs-scripting-python_SOURCES} + obs-scripting-python-frontend.c + ) + endif() + if(WIN32 OR APPLE) + set(obs-scripting-python_SOURCES + ${obs-scripting-python_SOURCES} + obs-scripting-python-import.c + ) + else() + set(EXTRA_LIBS ${EXTRA_LIBS} ${PYTHON_LIBRARIES}) + endif() +endif() + +if(LUAJIT_FOUND) + include_directories(${LUAJIT_INCLUDE_DIR}) + + set(obs-scripting-lua_SOURCES + obs-scripting-lua.c + obs-scripting-lua-source.c + ) + set(obs-scripting-lua_HEADERS + obs-scripting-lua.h + ) + if(UI_ENABLED) + set(obs-scripting-lua_SOURCES + ${obs-scripting-lua_SOURCES} + obs-scripting-lua-frontend.c + ) + endif() +endif() + +set(obs-scripting_SOURCES + obs-scripting.c + obs-scripting-logging.c + cstrcache.cpp + ) +set(obs-scripting_HEADERS + ${CMAKE_CURRENT_BINARY_DIR}/obs-scripting-config.h + obs-scripting.h + obs-scripting-callback.h + cstrcache.h + ) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/swig) + +if(PYTHONLIBS_FOUND) + set(SWIG_PY_RUNTIME swig/swigpyrun.h) + add_custom_command(OUTPUT ${SWIG_PY_RUNTIME} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + PRE_BUILD + COMMAND ${SWIG_EXECUTABLE} -python -external-runtime ${SWIG_PY_RUNTIME} + COMMENT "Scripting plugin: Building Python SWIG interface header" + ) + set_source_files_properties(${SWIG_PY_RUNTIME} PROPERTIES GENERATED TRUE) +endif() + +if(LUAJIT_FOUND) + set(SWIG_LUA_RUNTIME swig/swigluarun.h) + add_custom_command(OUTPUT ${SWIG_LUA_RUNTIME} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + PRE_BUILD + COMMAND ${SWIG_EXECUTABLE} -lua -external-runtime ${SWIG_LUA_RUNTIME} + COMMENT "Scripting: Building Lua SWIG interface header" + ) + set_source_files_properties(${SWIG_LUA_RUNTIME} PROPERTIES GENERATED TRUE) +endif() + +add_library(obs-scripting SHARED + ${obs-scripting_SOURCES} + ${obs-scripting_HEADERS} + ${obs-scripting-python_SOURCES} + ${obs-scripting-python_HEADERS} + ${obs-scripting-lua_SOURCES} + ${obs-scripting-lua_HEADERS} + ${SWIG_PY_RUNTIME} + ${SWIG_LUA_RUNTIME} + ) + +target_link_libraries(obs-scripting + libobs + ${LUAJIT_LIBRARIES} + ${EXTRA_LIBS} + ${obs-scripting_PLATFORM_DEPS} + ) + +if(PYTHONLIBS_FOUND) + add_subdirectory(obspython) +endif() + +if(LUAJIT_FOUND) + add_subdirectory(obslua) +endif() + +install_obs_core(obs-scripting) diff --git a/deps/obs-scripting/cstrcache.cpp b/deps/obs-scripting/cstrcache.cpp new file mode 100644 index 0000000..7865837 --- /dev/null +++ b/deps/obs-scripting/cstrcache.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "cstrcache.h" + +using namespace std; + +struct const_string_table { + unordered_map strings; +}; + +static struct const_string_table table; + +const char *cstrcache_get(const char *str) +{ + if (!str || !*str) + return ""; + + auto &strings = table.strings; + auto pair = strings.find(str); + + if (pair == strings.end()) { + strings[str] = str; + pair = strings.find(str); + } + + return pair->second.c_str(); +} diff --git a/deps/obs-scripting/cstrcache.h b/deps/obs-scripting/cstrcache.h new file mode 100644 index 0000000..8934cf6 --- /dev/null +++ b/deps/obs-scripting/cstrcache.h @@ -0,0 +1,13 @@ +#pragma once + +/* simple constant string cache table using STL unordered_map as storage */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char *cstrcache_get(const char *str); + +#ifdef __cplusplus +} +#endif diff --git a/deps/obs-scripting/obs-scripting-callback.h b/deps/obs-scripting/obs-scripting-callback.h new file mode 100644 index 0000000..ee317f3 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-callback.h @@ -0,0 +1,91 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "obs-scripting-internal.h" + +extern pthread_mutex_t detach_mutex; +extern struct script_callback *detached_callbacks; + +struct script_callback { + struct script_callback *next; + struct script_callback **p_prev_next; + + void (*on_remove)(void *p_cb); + obs_script_t *script; + calldata_t extra; + + bool removed; +}; + +static inline void *add_script_callback( + struct script_callback **first, + obs_script_t *script, + size_t extra_size) +{ + struct script_callback *cb = bzalloc(sizeof(*cb) + extra_size); + cb->script = script; + + struct script_callback *next = *first; + cb->next = next; + cb->p_prev_next = first; + if (next) next->p_prev_next = &cb->next; + *first = cb; + + return cb; +} + +static inline void remove_script_callback(struct script_callback *cb) +{ + cb->removed = true; + + struct script_callback *next = cb->next; + if (next) next->p_prev_next = cb->p_prev_next; + *cb->p_prev_next = cb->next; + + pthread_mutex_lock(&detach_mutex); + next = detached_callbacks; + cb->next = next; + if (next) next->p_prev_next = &cb->next; + cb->p_prev_next = &detached_callbacks; + detached_callbacks = cb; + pthread_mutex_unlock(&detach_mutex); + + if (cb->on_remove) + cb->on_remove(cb); +} + +static inline void just_free_script_callback(struct script_callback *cb) +{ + calldata_free(&cb->extra); + bfree(cb); +} + +static inline void free_script_callback(struct script_callback *cb) +{ + pthread_mutex_lock(&detach_mutex); + struct script_callback *next = cb->next; + if (next) next->p_prev_next = cb->p_prev_next; + *cb->p_prev_next = cb->next; + pthread_mutex_unlock(&detach_mutex); + + just_free_script_callback(cb); +} diff --git a/deps/obs-scripting/obs-scripting-config.h.in b/deps/obs-scripting/obs-scripting-config.h.in new file mode 100644 index 0000000..4205126 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-config.h.in @@ -0,0 +1,23 @@ +#pragma once + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef ON +#define ON 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef OFF +#define OFF 0 +#endif + +#define SCRIPT_DIR "@OBS_SCRIPT_PLUGIN_PATH@" +#define PYTHON_LIB "@PYTHON_LIB@" +#define COMPILE_LUA @LUAJIT_FOUND@ +#define COMPILE_PYTHON @PYTHON_FOUND@ +#define UI_ENABLED @UI_ENABLED@ diff --git a/deps/obs-scripting/obs-scripting-internal.h b/deps/obs-scripting/obs-scripting-internal.h new file mode 100644 index 0000000..dade53a --- /dev/null +++ b/deps/obs-scripting/obs-scripting-internal.h @@ -0,0 +1,51 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include "obs-scripting.h" + +struct obs_script { + enum obs_script_lang type; + bool loaded; + + obs_data_t *settings; + + struct dstr path; + struct dstr file; + struct dstr desc; +}; + +struct script_callback; +typedef void (*defer_call_cb)(void *param); + +extern void defer_call_post(defer_call_cb call, void *cb); + +extern void script_log(obs_script_t *script, int level, const char *format, ...); +extern void script_log_va(obs_script_t *script, int level, const char *format, + va_list args); + +#define script_error(script, format, ...) \ + script_log(script, LOG_ERROR, format, ##__VA_ARGS__) +#define script_warn(script, format, ...) \ + script_log(script, LOG_WARNING, format, ##__VA_ARGS__) +#define script_info(script, format, ...) \ + script_log(script, LOG_INFO, format, ##__VA_ARGS__) +#define script_debug(script, format, ...) \ + script_log(script, LOG_DEBUG, format, ##__VA_ARGS__) diff --git a/deps/obs-scripting/obs-scripting-logging.c b/deps/obs-scripting/obs-scripting-logging.c new file mode 100644 index 0000000..ebae79d --- /dev/null +++ b/deps/obs-scripting/obs-scripting-logging.c @@ -0,0 +1,64 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-scripting-internal.h" +#include + +static scripting_log_handler_t callback = NULL; +static void *param = NULL; + +void script_log_va(obs_script_t *script, int level, const char *format, + va_list args) +{ + char msg[2048]; + const char *lang = "(Unknown)"; + size_t start_len; + + if (script) { + switch (script->type) { + case OBS_SCRIPT_LANG_UNKNOWN: lang = "(Unknown language)"; break; + case OBS_SCRIPT_LANG_LUA: lang = "Lua"; break; + case OBS_SCRIPT_LANG_PYTHON: lang = "Python"; break; + } + + start_len = snprintf(msg, sizeof(msg), "[%s: %s] ", + lang, script->file.array); + } else { + start_len = snprintf(msg, sizeof(msg), "[Unknown Script] "); + } + + vsnprintf(msg + start_len, sizeof(msg) - start_len, format, args); + + if (callback) + callback(param, script, level, msg + start_len); + blog(level, "%s", msg); +} + +void script_log(obs_script_t *script, int level, const char *format, ...) +{ + va_list args; + va_start(args, format); + script_log_va(script, level, format, args); + va_end(args); +} + +void obs_scripting_set_log_callback(scripting_log_handler_t handler, + void *log_param) +{ + callback = handler; + param = log_param; +} diff --git a/deps/obs-scripting/obs-scripting-lua-frontend.c b/deps/obs-scripting/obs-scripting-lua-frontend.c new file mode 100644 index 0000000..1053461 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-lua-frontend.c @@ -0,0 +1,315 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include + +#include "obs-scripting-lua.h" + +#define ls_get_libobs_obj(type, lua_index, obs_obj) \ + ls_get_libobs_obj_(script, #type " *", lua_index, obs_obj, \ + NULL, __FUNCTION__, __LINE__) +#define ls_push_libobs_obj(type, obs_obj, ownership) \ + ls_push_libobs_obj_(script, #type " *", obs_obj, ownership, \ + NULL, __FUNCTION__, __LINE__) +#define call_func(func, args, rets) \ + call_func_(script, cb->reg_idx, args, rets, #func, "frontend API") + +/* ----------------------------------- */ + +static int get_scene_names(lua_State *script) +{ + char **names = obs_frontend_get_scene_names(); + char **name = names; + int i = 0; + + lua_newtable(script); + + while (name && *name) { + lua_pushstring(script, *name); + lua_rawseti(script, -2, ++i); + name++; + } + + bfree(names); + return 1; +} + +static int get_scenes(lua_State *script) +{ + struct obs_frontend_source_list list = {0}; + obs_frontend_get_scenes(&list); + + lua_newtable(script); + + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + ls_push_libobs_obj(obs_source_t, source, false); + lua_rawseti(script, -2, (int)(i + 1)); + } + + da_free(list.sources); + return 1; +} + +static int get_current_scene(lua_State *script) +{ + obs_source_t *source = obs_frontend_get_current_scene(); + ls_push_libobs_obj(obs_source_t, source, false); + return 1; +} + +static int set_current_scene(lua_State *script) +{ + obs_source_t *source = NULL; + ls_get_libobs_obj(obs_source_t, 1, &source); + obs_frontend_set_current_scene(source); + return 0; +} + +static int get_transitions(lua_State *script) +{ + struct obs_frontend_source_list list = {0}; + obs_frontend_get_transitions(&list); + + lua_newtable(script); + + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + ls_push_libobs_obj(obs_source_t, source, false); + lua_rawseti(script, -2, (int)(i + 1)); + } + + da_free(list.sources); + return 1; +} + +static int get_current_transition(lua_State *script) +{ + obs_source_t *source = obs_frontend_get_current_transition(); + ls_push_libobs_obj(obs_source_t, source, false); + return 1; +} + +static int set_current_transition(lua_State *script) +{ + obs_source_t *source = NULL; + ls_get_libobs_obj(obs_source_t, 1, &source); + obs_frontend_set_current_transition(source); + return 0; +} + +static int get_scene_collections(lua_State *script) +{ + char **names = obs_frontend_get_scene_collections(); + char **name = names; + int i = 0; + + lua_newtable(script); + + while (name && *name) { + lua_pushstring(script, *name); + lua_rawseti(script, -2, ++i); + name++; + } + + bfree(names); + return 1; +} + +static int get_current_scene_collection(lua_State *script) +{ + char *name = obs_frontend_get_current_scene_collection(); + lua_pushstring(script, name); + bfree(name); + return 1; +} + +static int set_current_scene_collection(lua_State *script) +{ + if (lua_isstring(script, 1)) { + const char *name = lua_tostring(script, 1); + obs_frontend_set_current_scene_collection(name); + } + return 0; +} + +static int get_profiles(lua_State *script) +{ + char **names = obs_frontend_get_profiles(); + char **name = names; + int i = 0; + + lua_newtable(script); + + while (name && *name) { + lua_pushstring(script, *name); + lua_rawseti(script, -2, ++i); + name++; + } + + bfree(names); + return 1; +} + +static int get_current_profile(lua_State *script) +{ + char *name = obs_frontend_get_current_profile(); + lua_pushstring(script, name); + bfree(name); + return 1; +} + +static int set_current_profile(lua_State *script) +{ + if (lua_isstring(script, 1)) { + const char *name = lua_tostring(script, 1); + obs_frontend_set_current_profile(name); + } + return 0; +} + +/* ----------------------------------- */ + +static void frontend_event_callback(enum obs_frontend_event event, void *priv) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + obs_frontend_remove_event_callback(frontend_event_callback, cb); + return; + } + + lock_callback(); + + lua_pushinteger(script, (int)event); + call_func(frontend_event_callback, 1, 0); + + unlock_callback(); +} + +static int remove_event_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) { + remove_lua_obs_callback(cb); + } + return 0; +} + +static void add_event_callback_defer(void *cb) +{ + obs_frontend_add_event_callback(frontend_event_callback, cb); +} + +static int add_event_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 1); + defer_call_post(add_event_callback_defer, cb); + return 0; +} + +/* ----------------------------------- */ + +static void frontend_save_callback(obs_data_t *save_data, bool saving, + void *priv) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + obs_frontend_remove_save_callback(frontend_save_callback, cb); + return; + } + + lock_callback(); + + ls_push_libobs_obj(obs_data_t, save_data, false); + lua_pushboolean(script, saving); + call_func(frontend_save_callback, 2, 0); + + unlock_callback(); +} + +static int remove_save_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) { + remove_lua_obs_callback(cb); + } + return 0; +} + +static void add_save_callback_defer(void *cb) +{ + obs_frontend_add_save_callback(frontend_save_callback, cb); +} + +static int add_save_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 1); + defer_call_post(add_save_callback_defer, cb); + return 0; +} + +/* ----------------------------------- */ + +void add_lua_frontend_funcs(lua_State *script) +{ + lua_getglobal(script, "obslua"); + +#define add_func(name) \ + do { \ + lua_pushstring(script, "obs_frontend_" #name); \ + lua_pushcfunction(script, name); \ + lua_rawset(script, -3); \ + } while (false) + + add_func(get_scene_names); + add_func(get_scenes); + add_func(get_current_scene); + add_func(set_current_scene); + add_func(get_transitions); + add_func(get_current_transition); + add_func(set_current_transition); + add_func(get_scene_collections); + add_func(get_current_scene_collection); + add_func(set_current_scene_collection); + add_func(get_profiles); + add_func(get_current_profile); + add_func(set_current_profile); + add_func(remove_event_callback); + add_func(add_event_callback); + add_func(remove_save_callback); + add_func(add_save_callback); +#undef add_func + + lua_pop(script, 1); +} diff --git a/deps/obs-scripting/obs-scripting-lua-source.c b/deps/obs-scripting/obs-scripting-lua-source.c new file mode 100644 index 0000000..9865c1e --- /dev/null +++ b/deps/obs-scripting/obs-scripting-lua-source.c @@ -0,0 +1,762 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-scripting-lua.h" +#include "cstrcache.h" + +#include + +/* ========================================================================= */ + +static inline const char *get_table_string_(lua_State *script, int idx, + const char *name, const char *func) +{ + const char *str = ""; + + lua_pushstring(script, name); + lua_gettable(script, idx - 1); + if (!lua_isstring(script, -1)) + warn("%s: no item '%s' of type %s", func, name, "string"); + else + str = cstrcache_get(lua_tostring(script, -1)); + lua_pop(script, 1); + + return str; +} + +static inline int get_table_int_(lua_State *script, int idx, + const char *name, const char *func) +{ + int val = 0; + + lua_pushstring(script, name); + lua_gettable(script, idx - 1); + val = (int)lua_tointeger(script, -1); + lua_pop(script, 1); + + UNUSED_PARAMETER(func); + + return val; +} + +static inline void get_callback_from_table_(lua_State *script, int idx, + const char *name, int *p_reg_idx, const char *func) +{ + *p_reg_idx = LUA_REFNIL; + + lua_pushstring(script, name); + lua_gettable(script, idx - 1); + if (!lua_isfunction(script, -1)) { + if (!lua_isnil(script, -1)) { + warn("%s: item '%s' is not a function", func, name); + } + lua_pop(script, 1); + } else { + *p_reg_idx = luaL_ref(script, LUA_REGISTRYINDEX); + } +} + +#define get_table_string(script, idx, name) \ + get_table_string_(script, idx, name, __FUNCTION__) +#define get_table_int(script, idx, name) \ + get_table_int_(script, idx, name, __FUNCTION__) +#define get_callback_from_table(script, idx, name, p_reg_idx) \ + get_callback_from_table_(script, idx, name, p_reg_idx, __FUNCTION__) + +bool ls_get_libobs_obj_(lua_State * script, + const char *type, + int lua_idx, + void * libobs_out, + const char *id, + const char *func, + int line) +{ + swig_type_info *info = SWIG_TypeQuery(script, type); + if (info == NULL) { + warn("%s:%d: SWIG could not find type: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + int ret = SWIG_ConvertPtr(script, lua_idx, libobs_out, info, 0); + if (!SWIG_IsOK(ret)) { + warn("%s:%d: SWIG failed to convert lua object to obs " + "object: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + return true; +} + +#define ls_get_libobs_obj(type, lua_index, obs_obj) \ + ls_get_libobs_obj_(ls->script, #type " *", lua_index, obs_obj, \ + ls->id, __FUNCTION__, __LINE__) + +bool ls_push_libobs_obj_(lua_State * script, + const char *type, + void * libobs_in, + bool ownership, + const char *id, + const char *func, + int line) +{ + swig_type_info *info = SWIG_TypeQuery(script, type); + if (info == NULL) { + warn("%s:%d: SWIG could not find type: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + SWIG_NewPointerObj(script, libobs_in, info, (int)ownership); + return true; +} + +#define ls_push_libobs_obj(type, obs_obj, ownership) \ + ls_push_libobs_obj_(ls->script, #type " *", obs_obj, ownership, \ + ls->id, __FUNCTION__, __LINE__) + +/* ========================================================================= */ + +struct obs_lua_data; + +struct obs_lua_source { + struct obs_lua_script *data; + + lua_State * script; + const char *id; + const char *display_name; + int func_create; + int func_destroy; + int func_get_width; + int func_get_height; + int func_get_defaults; + int func_get_properties; + int func_update; + int func_activate; + int func_deactivate; + int func_show; + int func_hide; + int func_video_tick; + int func_video_render; + int func_save; + int func_load; + + pthread_mutex_t definition_mutex; + struct obs_lua_data *first_source; + + struct obs_lua_source *next; + struct obs_lua_source **p_prev_next; +}; + +extern pthread_mutex_t lua_source_def_mutex; +struct obs_lua_source *first_source_def = NULL; + +struct obs_lua_data { + obs_source_t * source; + struct obs_lua_source *ls; + int lua_data_ref; + + struct obs_lua_data *next; + struct obs_lua_data **p_prev_next; +}; + +#define call_func(name, args, rets) \ + call_func_(ls->script, ls->func_ ## name, args, rets, #name, \ + ls->display_name) +#define have_func(name) \ + (ls->func_ ## name != LUA_REFNIL) +#define ls_push_data() \ + lua_rawgeti(ls->script, LUA_REGISTRYINDEX, ld->lua_data_ref) +#define ls_pop(count) \ + lua_pop(ls->script, count) +#define lock_script() \ + struct obs_lua_script *__data = ls->data; \ + struct obs_lua_script *__prev_script = current_lua_script; \ + current_lua_script = __data; \ + pthread_mutex_lock(&__data->mutex); +#define unlock_script() \ + pthread_mutex_unlock(&__data->mutex); \ + current_lua_script = __prev_script; + +static const char *obs_lua_source_get_name(void *type_data) +{ + struct obs_lua_source *ls = type_data; + return ls->display_name; +} + +static void *obs_lua_source_create(obs_data_t *settings, obs_source_t *source) +{ + struct obs_lua_source *ls = obs_source_get_type_data(source); + struct obs_lua_data *data = NULL; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(create)) + goto fail; + + lock_script(); + + ls_push_libobs_obj(obs_data_t, settings, false); + ls_push_libobs_obj(obs_source_t, source, false); + call_func(create, 2, 1); + + int lua_data_ref = luaL_ref(ls->script, LUA_REGISTRYINDEX); + if (lua_data_ref != LUA_REFNIL) { + data = bmalloc(sizeof(*data)); + data->source = source; + data->ls = ls; + data->lua_data_ref = lua_data_ref; + } + + unlock_script(); + + if (data) { + struct obs_lua_data *next = ls->first_source; + data->next = next; + data->p_prev_next = &ls->first_source; + if (next) next->p_prev_next = &data->next; + ls->first_source = data; + } + +fail: + pthread_mutex_unlock(&ls->definition_mutex); + return data; +} + +static void call_destroy(struct obs_lua_data *ld) +{ + struct obs_lua_source *ls = ld->ls; + + ls_push_data(); + call_func(destroy, 1, 0); + luaL_unref(ls->script, LUA_REGISTRYINDEX, ld->lua_data_ref); + ld->lua_data_ref = LUA_REFNIL; +} + +static void obs_lua_source_destroy(void *data) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + struct obs_lua_data * next; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(destroy)) + goto fail; + + lock_script(); + call_destroy(ld); + unlock_script(); + +fail: + next = ld->next; + *ld->p_prev_next = next; + if (next) next->p_prev_next = ld->p_prev_next; + + bfree(data); + pthread_mutex_unlock(&ls->definition_mutex); +} + +static uint32_t obs_lua_source_get_width(void *data) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + uint32_t width = 0; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(get_width)) + goto fail; + + lock_script(); + + ls_push_data(); + if (call_func(get_width, 1, 1)) { + width = (uint32_t)lua_tointeger(ls->script, -1); + ls_pop(1); + } + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); + return width; +} + +static uint32_t obs_lua_source_get_height(void *data) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + uint32_t height = 0; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(get_height)) + goto fail; + + lock_script(); + + ls_push_data(); + if (call_func(get_height, 1, 1)) { + height = (uint32_t)lua_tointeger(ls->script, -1); + ls_pop(1); + } + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); + return height; +} + +static void obs_lua_source_get_defaults(void *type_data, obs_data_t *settings) +{ + struct obs_lua_source *ls = type_data; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(get_defaults)) + goto fail; + + lock_script(); + + ls_push_libobs_obj(obs_data_t, settings, false); + call_func(get_defaults, 1, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static obs_properties_t *obs_lua_source_get_properties(void *data) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + obs_properties_t * props = NULL; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(get_properties)) + goto fail; + + lock_script(); + + ls_push_data(); + if (call_func(get_properties, 1, 1)) { + ls_get_libobs_obj(obs_properties_t, -1, &props); + ls_pop(1); + } + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); + return props; +} + +static void obs_lua_source_update(void *data, obs_data_t *settings) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(update)) + goto fail; + + lock_script(); + + ls_push_data(); + ls_push_libobs_obj(obs_data_t, settings, false); + call_func(update, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +#define DEFINE_VOID_DATA_CALLBACK(name) \ + static void obs_lua_source_ ## name(void *data) \ + { \ + struct obs_lua_data * ld = data; \ + struct obs_lua_source *ls = ld->ls; \ + if (!have_func(name)) \ + return; \ + lock_script(); \ + ls_push_data(); \ + call_func(name, 1, 0); \ + unlock_script(); \ + } +DEFINE_VOID_DATA_CALLBACK(activate) +DEFINE_VOID_DATA_CALLBACK(deactivate) +DEFINE_VOID_DATA_CALLBACK(show) +DEFINE_VOID_DATA_CALLBACK(hide) +#undef DEFINE_VOID_DATA_CALLBACK + +static void obs_lua_source_video_tick(void *data, float seconds) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(video_tick)) + goto fail; + + lock_script(); + + ls_push_data(); + lua_pushnumber(ls->script, (double)seconds); + call_func(video_tick, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static void obs_lua_source_video_render(void *data, gs_effect_t *effect) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(video_render)) + goto fail; + + lock_script(); + + ls_push_data(); + ls_push_libobs_obj(gs_effect_t, effect, false); + call_func(video_render, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static void obs_lua_source_save(void *data, obs_data_t *settings) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(save)) + goto fail; + + lock_script(); + + ls_push_data(); + ls_push_libobs_obj(obs_data_t, settings, false); + call_func(save, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static void obs_lua_source_load(void *data, obs_data_t *settings) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(load)) + goto fail; + + lock_script(); + + ls_push_data(); + ls_push_libobs_obj(obs_data_t, settings, false); + call_func(load, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static void source_type_unload(struct obs_lua_source *ls) +{ +#define unref(name) \ + luaL_unref(ls->script, LUA_REGISTRYINDEX, name); \ + name = LUA_REFNIL + + unref(ls->func_create); + unref(ls->func_destroy); + unref(ls->func_get_width); + unref(ls->func_get_height); + unref(ls->func_get_defaults); + unref(ls->func_get_properties); + unref(ls->func_update); + unref(ls->func_activate); + unref(ls->func_deactivate); + unref(ls->func_show); + unref(ls->func_hide); + unref(ls->func_video_tick); + unref(ls->func_video_render); + unref(ls->func_save); + unref(ls->func_load); +#undef unref +} + +static void obs_lua_source_free_type_data(void *type_data) +{ + struct obs_lua_source *ls = type_data; + + pthread_mutex_lock(&ls->definition_mutex); + + if (ls->script) { + lock_script(); + source_type_unload(ls); + unlock_script(); + ls->script = NULL; + } + + pthread_mutex_unlock(&ls->definition_mutex); + pthread_mutex_destroy(&ls->definition_mutex); + bfree(ls); +} + +EXPORT void obs_enable_source_type(const char *name, bool enable); + +static inline struct obs_lua_source *find_existing(const char *id) +{ + struct obs_lua_source *existing = NULL; + + pthread_mutex_lock(&lua_source_def_mutex); + struct obs_lua_source *ls = first_source_def; + while (ls) { + /* can compare pointers here due to string table */ + if (ls->id == id) { + existing = ls; + break; + } + + ls = ls->next; + } + pthread_mutex_unlock(&lua_source_def_mutex); + + return existing; +} + +static int obs_lua_register_source(lua_State *script) +{ + struct obs_lua_source ls = {0}; + struct obs_lua_source *existing = NULL; + struct obs_lua_source *v = NULL; + struct obs_source_info info = {0}; + const char *id; + + if (!verify_args1(script, is_table)) + goto fail; + + id = get_table_string(script, -1, "id"); + if (!id || !*id) + goto fail; + + /* redefinition */ + existing = find_existing(id); + if (existing) { + if (existing->script) { + existing = NULL; + goto fail; + } + + pthread_mutex_lock(&existing->definition_mutex); + } + + v = existing ? existing : &ls; + + v->script = script; + v->id = id; + + info.id = v->id; + info.type = (enum obs_source_type)get_table_int(script, -1, "type"); + + info.output_flags = get_table_int(script, -1, "output_flags"); + + lua_pushstring(script, "get_name"); + lua_gettable(script, -2); + if (lua_pcall(script, 0, 1, 0) == 0) { + v->display_name = cstrcache_get(lua_tostring(script, -1)); + lua_pop(script, 1); + } + + if (!v->display_name || + !*v->display_name || + !*info.id || + !info.output_flags) + goto fail; + +#define get_callback(val) \ + do { \ + get_callback_from_table(script, -1, #val, &v->func_ ## val); \ + info.val = obs_lua_source_ ## val; \ + } while (false) + + get_callback(create); + get_callback(destroy); + get_callback(get_width); + get_callback(get_height); + get_callback(get_properties); + get_callback(update); + get_callback(activate); + get_callback(deactivate); + get_callback(show); + get_callback(hide); + get_callback(video_tick); + get_callback(video_render); + get_callback(save); + get_callback(load); +#undef get_callback + + get_callback_from_table(script, -1, "get_defaults", + &v->func_get_defaults); + + if (!existing) { + ls.data = current_lua_script; + + pthread_mutex_init(&ls.definition_mutex, NULL); + info.type_data = bmemdup(&ls, sizeof(ls)); + info.free_type_data = obs_lua_source_free_type_data; + info.get_name = obs_lua_source_get_name; + info.get_defaults2 = obs_lua_source_get_defaults; + obs_register_source(&info); + + pthread_mutex_lock(&lua_source_def_mutex); + v = info.type_data; + + struct obs_lua_source *next = first_source_def; + v->next = next; + if (next) next->p_prev_next = &v->next; + v->p_prev_next = &first_source_def; + first_source_def = v; + + pthread_mutex_unlock(&lua_source_def_mutex); + } else { + existing->script = script; + existing->data = current_lua_script; + obs_enable_source_type(id, true); + + struct obs_lua_data *ld = v->first_source; + while (ld) { + struct obs_lua_source *ls = v; + + if (have_func(create)) { + obs_source_t *source = ld->source; + obs_data_t *settings = obs_source_get_settings( + source); + + ls_push_libobs_obj(obs_data_t, settings, false); + ls_push_libobs_obj(obs_source_t, source, false); + call_func(create, 2, 1); + + ld->lua_data_ref = luaL_ref(ls->script, + LUA_REGISTRYINDEX); + obs_data_release(settings); + } + + ld = ld->next; + } + } + +fail: + if (existing) { + pthread_mutex_unlock(&existing->definition_mutex); + } + return 0; +} + +/* ========================================================================= */ + +void add_lua_source_functions(lua_State *script) +{ + lua_getglobal(script, "obslua"); + + lua_pushstring(script, "obs_register_source"); + lua_pushcfunction(script, obs_lua_register_source); + lua_rawset(script, -3); + + lua_pop(script, 1); +} + +static inline void undef_source_type(struct obs_lua_script *data, + struct obs_lua_source *ls) +{ + pthread_mutex_lock(&ls->definition_mutex); + pthread_mutex_lock(&data->mutex); + + obs_enable_source_type(ls->id, false); + + struct obs_lua_data *ld = ls->first_source; + while (ld) { + call_destroy(ld); + ld = ld->next; + } + + source_type_unload(ls); + ls->script = NULL; + + pthread_mutex_unlock(&data->mutex); + pthread_mutex_unlock(&ls->definition_mutex); +} + +void undef_lua_script_sources(struct obs_lua_script *data) +{ + pthread_mutex_lock(&lua_source_def_mutex); + + struct obs_lua_source *def = first_source_def; + while (def) { + if (def->script == data->script) + undef_source_type(data, def); + def = def->next; + } + + pthread_mutex_unlock(&lua_source_def_mutex); +} diff --git a/deps/obs-scripting/obs-scripting-lua.c b/deps/obs-scripting/obs-scripting-lua.c new file mode 100644 index 0000000..965e7e1 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-lua.c @@ -0,0 +1,1297 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-scripting-lua.h" +#include "obs-scripting-config.h" +#include +#include +#include + +#include + +/* ========================================================================= */ + +#if ARCH_BITS == 64 +# define ARCH_DIR "64bit" +#else +# define ARCH_DIR "32bit" +#endif + +#ifdef __APPLE__ +# define SO_EXT "dylib" +#elif _WIN32 +# define SO_EXT "dll" +#else +# define SO_EXT "so" +#endif + +static const char *startup_script_template = "\ +for val in pairs(package.preload) do\n\ + package.preload[val] = nil\n\ +end\n\ +package.cpath = package.cpath .. \";\" .. \"%s\" .. \"/?." SO_EXT "\"\n\ +require \"obslua\"\n"; + +static const char *get_script_path_func = "\ +function script_path()\n\ + return \"%s\"\n\ +end\n\ +package.path = package.path .. \";\" .. script_path() .. \"/?.lua\"\n"; + +static char *startup_script = NULL; + +static pthread_mutex_t tick_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct obs_lua_script *first_tick_script = NULL; + +pthread_mutex_t lua_source_def_mutex = PTHREAD_MUTEX_INITIALIZER; + +#define ls_get_libobs_obj(type, lua_index, obs_obj) \ + ls_get_libobs_obj_(script, #type " *", lua_index, obs_obj, \ + NULL, __FUNCTION__, __LINE__) +#define ls_push_libobs_obj(type, obs_obj, ownership) \ + ls_push_libobs_obj_(script, #type " *", obs_obj, ownership, \ + NULL, __FUNCTION__, __LINE__) +#define call_func(name, args, rets) \ + call_func_(script, cb->reg_idx, \ + args, rets, #name, __FUNCTION__) + +/* ========================================================================= */ + +static void add_hook_functions(lua_State *script); +static int obs_lua_remove_tick_callback(lua_State *script); +static int obs_lua_remove_main_render_callback(lua_State *script); + +#if UI_ENABLED +void add_lua_frontend_funcs(lua_State *script); +#endif + +static bool load_lua_script(struct obs_lua_script *data) +{ + struct dstr str = {0}; + bool success = false; + int ret; + + lua_State *script = luaL_newstate(); + if (!script) { + script_warn(&data->base, "Failed to create new lua state"); + goto fail; + } + + pthread_mutex_lock(&data->mutex); + + luaL_openlibs(script); + luaopen_ffi(script); + + if (luaL_dostring(script, startup_script) != 0) { + script_warn(&data->base, "Error executing startup script 1: %s", + lua_tostring(script, -1)); + goto fail; + } + + dstr_printf(&str, get_script_path_func, data->dir.array); + ret = luaL_dostring(script, str.array); + dstr_free(&str); + + if (ret != 0) { + script_warn(&data->base, "Error executing startup script 2: %s", + lua_tostring(script, -1)); + goto fail; + } + + current_lua_script = data; + + add_lua_source_functions(script); + add_hook_functions(script); +#if UI_ENABLED + add_lua_frontend_funcs(script); +#endif + + if (luaL_loadfile(script, data->base.path.array) != 0) { + script_warn(&data->base, "Error loading file: %s", + lua_tostring(script, -1)); + goto fail; + } + + if (lua_pcall(script, 0, LUA_MULTRET, 0) != 0) { + script_warn(&data->base, "Error running file: %s", + lua_tostring(script, -1)); + goto fail; + } + + ret = lua_gettop(script); + if (ret == 1 && lua_isboolean(script, -1)) { + bool success = lua_toboolean(script, -1); + + if (!success) { + goto fail; + } + } + + lua_getglobal(script, "script_tick"); + if (lua_isfunction(script, -1)) { + pthread_mutex_lock(&tick_mutex); + + struct obs_lua_script *next = first_tick_script; + data->next_tick = next; + data->p_prev_next_tick = &first_tick_script; + if (next) next->p_prev_next_tick = &data->next_tick; + first_tick_script = data; + + data->tick = luaL_ref(script, LUA_REGISTRYINDEX); + + pthread_mutex_unlock(&tick_mutex); + } + + lua_getglobal(script, "script_properties"); + if (lua_isfunction(script, -1)) + data->get_properties = luaL_ref(script, LUA_REGISTRYINDEX); + else + data->get_properties = LUA_REFNIL; + + lua_getglobal(script, "script_update"); + if (lua_isfunction(script, -1)) + data->update = luaL_ref(script, LUA_REGISTRYINDEX); + else + data->update = LUA_REFNIL; + + lua_getglobal(script, "script_save"); + if (lua_isfunction(script, -1)) + data->save = luaL_ref(script, LUA_REGISTRYINDEX); + else + data->save = LUA_REFNIL; + + lua_getglobal(script, "script_defaults"); + if (lua_isfunction(script, -1)) { + ls_push_libobs_obj(obs_data_t, data->base.settings, false); + if (lua_pcall(script, 1, 0, 0) != 0) { + script_warn(&data->base, "Error calling " + "script_defaults: %s", + lua_tostring(script, -1)); + } + } + + lua_getglobal(script, "script_description"); + if (lua_isfunction(script, -1)) { + if (lua_pcall(script, 0, 1, 0) != 0) { + script_warn(&data->base, "Error calling " + "script_defaults: %s", + lua_tostring(script, -1)); + } else { + const char *desc = lua_tostring(script, -1); + dstr_copy(&data->base.desc, desc); + } + } + + lua_getglobal(script, "script_load"); + if (lua_isfunction(script, -1)) { + ls_push_libobs_obj(obs_data_t, data->base.settings, false); + if (lua_pcall(script, 1, 0, 0) != 0) { + script_warn(&data->base, "Error calling " + "script_load: %s", + lua_tostring(script, -1)); + } + } + + data->script = script; + success = true; + +fail: + if (script) { + lua_settop(script, 0); + pthread_mutex_unlock(&data->mutex); + } + + if (!success && script) { + lua_close(script); + } + + current_lua_script = NULL; + return success; +} + +/* -------------------------------------------- */ + +THREAD_LOCAL struct lua_obs_callback *current_lua_cb = NULL; +THREAD_LOCAL struct obs_lua_script *current_lua_script = NULL; + +/* -------------------------------------------- */ + +struct lua_obs_timer { + struct lua_obs_timer *next; + struct lua_obs_timer **p_prev_next; + + uint64_t last_ts; + uint64_t interval; +}; + +static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct lua_obs_timer *first_timer = NULL; + +static inline void lua_obs_timer_init(struct lua_obs_timer *timer) +{ + pthread_mutex_lock(&timer_mutex); + + struct lua_obs_timer *next = first_timer; + timer->next = next; + timer->p_prev_next = &first_timer; + if (next) next->p_prev_next = &timer->next; + first_timer = timer; + + pthread_mutex_unlock(&timer_mutex); +} + +static inline void lua_obs_timer_remove(struct lua_obs_timer *timer) +{ + struct lua_obs_timer *next = timer->next; + if (next) next->p_prev_next = timer->p_prev_next; + *timer->p_prev_next = timer->next; +} + +static inline struct lua_obs_callback *lua_obs_timer_cb( + struct lua_obs_timer *timer) +{ + return &((struct lua_obs_callback *)timer)[-1]; +} + +static int timer_remove(lua_State *script) +{ + if (!is_function(script, 1)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void timer_call(struct script_callback *p_cb) +{ + struct lua_obs_callback *cb = (struct lua_obs_callback *)p_cb; + + if (p_cb->removed) + return; + + lock_callback(); + call_func_(cb->script, cb->reg_idx, 0, 0, "timer_cb", __FUNCTION__); + unlock_callback(); +} + +static void defer_timer_init(void *p_cb) +{ + struct lua_obs_callback *cb = p_cb; + struct lua_obs_timer *timer = lua_obs_callback_extra_data(cb); + lua_obs_timer_init(timer); +} + +static int timer_add(lua_State *script) +{ + if (!is_function(script, 1)) + return 0; + int ms = (int)lua_tointeger(script, 2); + if (!ms) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback_extra(script, 1, + sizeof(struct lua_obs_timer)); + struct lua_obs_timer *timer = lua_obs_callback_extra_data(cb); + + timer->interval = (uint64_t)ms * 1000000ULL; + timer->last_ts = obs_get_video_frame_time(); + + defer_call_post(defer_timer_init, cb); + return 0; +} + +/* -------------------------------------------- */ + +static void obs_lua_main_render_callback(void *priv, uint32_t cx, uint32_t cy) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + obs_remove_main_render_callback(obs_lua_main_render_callback, + cb); + return; + } + + lock_callback(); + + lua_pushinteger(script, (lua_Integer)cx); + lua_pushinteger(script, (lua_Integer)cy); + call_func(obs_lua_main_render_callback, 2, 0); + + unlock_callback(); +} + +static int obs_lua_remove_main_render_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void defer_add_render(void *cb) +{ + obs_add_main_render_callback(obs_lua_main_render_callback, cb); +} + +static int obs_lua_add_main_render_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 1); + defer_call_post(defer_add_render, cb); + return 0; +} + +/* -------------------------------------------- */ + +static void obs_lua_tick_callback(void *priv, float seconds) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + obs_remove_tick_callback(obs_lua_tick_callback, cb); + return; + } + + lock_callback(); + + lua_pushnumber(script, (lua_Number)seconds); + call_func(obs_lua_tick_callback, 2, 0); + + unlock_callback(); +} + +static int obs_lua_remove_tick_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void defer_add_tick(void *cb) +{ + obs_add_tick_callback(obs_lua_tick_callback, cb); +} + +static int obs_lua_add_tick_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 1); + defer_call_post(defer_add_tick, cb); + return 0; +} + +/* -------------------------------------------- */ + +static void calldata_signal_callback(void *priv, calldata_t *cd) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + signal_handler_remove_current(); + return; + } + + lock_callback(); + + ls_push_libobs_obj(calldata_t, cd, false); + call_func(calldata_signal_callback, 1, 0); + + unlock_callback(); +} + +static int obs_lua_signal_handler_disconnect(lua_State *script) +{ + signal_handler_t *handler; + const char *signal; + + if (!ls_get_libobs_obj(signal_handler_t, 1, &handler)) + return 0; + signal = lua_tostring(script, 2); + if (!signal) + return 0; + if (!is_function(script, 3)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 3); + while (cb) { + signal_handler_t *cb_handler = + calldata_ptr(&cb->base.extra, "handler"); + const char *cb_signal = + calldata_string(&cb->base.extra, "signal"); + + if (cb_signal && + strcmp(signal, cb_signal) != 0 && + handler == cb_handler) + break; + + cb = find_next_lua_obs_callback(script, cb, 3); + } + + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void defer_connect(void *p_cb) +{ + struct script_callback *cb = p_cb; + + signal_handler_t *handler = calldata_ptr(&cb->extra, "handler"); + const char *signal = calldata_string(&cb->extra, "signal"); + signal_handler_connect(handler, signal, calldata_signal_callback, cb); +} + +static int obs_lua_signal_handler_connect(lua_State *script) +{ + signal_handler_t *handler; + const char *signal; + + if (!ls_get_libobs_obj(signal_handler_t, 1, &handler)) + return 0; + signal = lua_tostring(script, 2); + if (!signal) + return 0; + if (!is_function(script, 3)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 3); + calldata_set_ptr(&cb->base.extra, "handler", handler); + calldata_set_string(&cb->base.extra, "signal", signal); + defer_call_post(defer_connect, cb); + return 0; +} + +/* -------------------------------------------- */ + +static void calldata_signal_callback_global(void *priv, const char *signal, + calldata_t *cd) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + signal_handler_remove_current(); + return; + } + + lock_callback(); + + lua_pushstring(script, signal); + ls_push_libobs_obj(calldata_t, cd, false); + call_func(calldata_signal_callback_global, 2, 0); + + unlock_callback(); +} + +static int obs_lua_signal_handler_disconnect_global(lua_State *script) +{ + signal_handler_t *handler; + + if (!ls_get_libobs_obj(signal_handler_t, 1, &handler)) + return 0; + if (!is_function(script, 2)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 3); + while (cb) { + signal_handler_t *cb_handler = + calldata_ptr(&cb->base.extra, "handler"); + + if (handler == cb_handler) + break; + + cb = find_next_lua_obs_callback(script, cb, 3); + } + + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void defer_connect_global(void *p_cb) +{ + struct script_callback *cb = p_cb; + + signal_handler_t *handler = calldata_ptr(&cb->extra, "handler"); + signal_handler_connect_global(handler, + calldata_signal_callback_global, cb); +} + +static int obs_lua_signal_handler_connect_global(lua_State *script) +{ + signal_handler_t *handler; + + if (!ls_get_libobs_obj(signal_handler_t, 1, &handler)) + return 0; + if (!is_function(script, 2)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 2); + calldata_set_ptr(&cb->base.extra, "handler", handler); + defer_call_post(defer_connect_global, cb); + return 0; +} + +/* -------------------------------------------- */ + +static bool enum_sources_proc(void *param, obs_source_t *source) +{ + lua_State *script = param; + + obs_source_get_ref(source); + ls_push_libobs_obj(obs_source_t, source, false); + + size_t idx = lua_rawlen(script, -2); + lua_rawseti(script, -2, (int)idx + 1); + return true; +} + +static int enum_sources(lua_State *script) +{ + lua_newtable(script); + obs_enum_sources(enum_sources_proc, script); + return 1; +} + +/* -------------------------------------------- */ + +static bool enum_items_proc(obs_scene_t *scene, obs_sceneitem_t *item, + void *param) +{ + lua_State *script = param; + + UNUSED_PARAMETER(scene); + + obs_sceneitem_addref(item); + ls_push_libobs_obj(obs_sceneitem_t, item, false); + lua_rawseti(script, -2, (int)lua_rawlen(script, -2) + 1); + return true; +} + +static int scene_enum_items(lua_State *script) +{ + obs_scene_t *scene; + if (!ls_get_libobs_obj(obs_scene_t, 1, &scene)) + return 0; + + lua_newtable(script); + obs_scene_enum_items(scene, enum_items_proc, script); + return 1; +} + +/* -------------------------------------------- */ + +static void defer_hotkey_unregister(void *p_cb) +{ + obs_hotkey_unregister((obs_hotkey_id)(uintptr_t)p_cb); +} + +static void on_remove_hotkey(void *p_cb) +{ + struct lua_obs_callback *cb = p_cb; + obs_hotkey_id id = (obs_hotkey_id)calldata_int(&cb->base.extra, "id"); + + if (id != OBS_INVALID_HOTKEY_ID) + defer_call_post(defer_hotkey_unregister, (void*)(uintptr_t)id); +} + +static void hotkey_pressed(void *p_cb, bool pressed) +{ + struct lua_obs_callback *cb = p_cb; + lua_State *script = cb->script; + + if (cb->base.removed) + return; + + lock_callback(); + + lua_pushboolean(script, pressed); + call_func(hotkey_pressed, 1, 0); + + unlock_callback(); +} + +static void defer_hotkey_pressed(void *p_cb) +{ + hotkey_pressed(p_cb, true); +} + +static void defer_hotkey_unpressed(void *p_cb) +{ + hotkey_pressed(p_cb, false); +} + +static void hotkey_callback(void *p_cb, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + struct lua_obs_callback *cb = p_cb; + + if (cb->base.removed) + return; + + if (pressed) + defer_call_post(defer_hotkey_pressed, cb); + else + defer_call_post(defer_hotkey_unpressed, cb); + + UNUSED_PARAMETER(hotkey); + UNUSED_PARAMETER(id); +} + +static int hotkey_unregister(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static int hotkey_register_frontend(lua_State *script) +{ + obs_hotkey_id id; + + const char *name = lua_tostring(script, 1); + if (!name) + return 0; + const char *desc = lua_tostring(script, 2); + if (!desc) + return 0; + if (!is_function(script, 3)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 3); + cb->base.on_remove = on_remove_hotkey; + id = obs_hotkey_register_frontend(name, desc, hotkey_callback, cb); + calldata_set_int(&cb->base.extra, "id", id); + lua_pushinteger(script, (lua_Integer)id); + + if (id == OBS_INVALID_HOTKEY_ID) + remove_lua_obs_callback(cb); + return 1; +} + +/* -------------------------------------------- */ + +static bool button_prop_clicked(obs_properties_t *props, obs_property_t *p, + void *p_cb) +{ + struct lua_obs_callback *cb = p_cb; + lua_State *script = cb->script; + bool ret = false; + + if (cb->base.removed) + return false; + + lock_callback(); + + if (!ls_push_libobs_obj(obs_properties_t, props, false)) + goto fail; + if (!ls_push_libobs_obj(obs_property_t, p, false)) { + lua_pop(script, 1); + goto fail; + } + + call_func(button_prop_clicked, 2, 1); + if (lua_isboolean(script, -1)) + ret = lua_toboolean(script, -1); + +fail: + unlock_callback(); + + return ret; +} + +static int properties_add_button(lua_State *script) +{ + obs_properties_t *props; + obs_property_t *p; + + if (!ls_get_libobs_obj(obs_properties_t, 1, &props)) + return 0; + const char *name = lua_tostring(script, 2); + if (!name) + return 0; + const char *text = lua_tostring(script, 3); + if (!text) + return 0; + if (!is_function(script, 4)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 4); + p = obs_properties_add_button2(props, name, text, button_prop_clicked, + cb); + + if (!p || !ls_push_libobs_obj(obs_property_t, p, false)) + return 0; + return 1; +} + +/* -------------------------------------------- */ + +static bool modified_callback(void *p_cb, obs_properties_t *props, + obs_property_t *p, obs_data_t *settings) +{ + struct lua_obs_callback *cb = p_cb; + lua_State *script = cb->script; + bool ret = false; + + if (cb->base.removed) + return false; + + lock_callback(); + if (!ls_push_libobs_obj(obs_properties_t, props, false)) { + goto fail; + } + if (!ls_push_libobs_obj(obs_property_t, p, false)) { + lua_pop(script, 1); + goto fail; + } + if (!ls_push_libobs_obj(obs_data_t, settings, false)) { + lua_pop(script, 2); + goto fail; + } + + call_func(modified_callback, 3, 1); + if (lua_isboolean(script, -1)) + ret = lua_toboolean(script, -1); + +fail: + unlock_callback(); + return ret; +} + +static int property_set_modified_callback(lua_State *script) +{ + obs_property_t *p; + + if (!ls_get_libobs_obj(obs_property_t, 1, &p)) + return 0; + if (!is_function(script, 2)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 2); + obs_property_set_modified_callback2(p, modified_callback, cb); + return 0; +} + +/* -------------------------------------------- */ + +static int remove_current_callback(lua_State *script) +{ + UNUSED_PARAMETER(script); + if (current_lua_cb) + remove_lua_obs_callback(current_lua_cb); + return 0; +} + +/* -------------------------------------------- */ + +static int calldata_source(lua_State *script) +{ + calldata_t *cd; + const char *str; + int ret = 0; + + if (!ls_get_libobs_obj(calldata_t, 1, &cd)) + goto fail; + str = lua_tostring(script, 2); + if (!str) + goto fail; + + obs_source_t *source = calldata_ptr(cd, str); + if (ls_push_libobs_obj(obs_source_t, source, false)) + ++ret; + +fail: + return ret; +} + +static int calldata_sceneitem(lua_State *script) +{ + calldata_t *cd; + const char *str; + int ret = 0; + + if (!ls_get_libobs_obj(calldata_t, 1, &cd)) + goto fail; + str = lua_tostring(script, 2); + if (!str) + goto fail; + + obs_sceneitem_t *sceneitem = calldata_ptr(cd, str); + if (ls_push_libobs_obj(obs_sceneitem_t, sceneitem, false)) + ++ret; + +fail: + return ret; +} + +/* -------------------------------------------- */ + +static int source_list_release(lua_State *script) +{ + size_t count = lua_rawlen(script, 1); + for (size_t i = 0; i < count; i++) { + obs_source_t *source; + + lua_rawgeti(script, 1, (int)i + 1); + ls_get_libobs_obj(obs_source_t, -1, &source); + lua_pop(script, 1); + + obs_source_release(source); + } + return 0; +} + +static int sceneitem_list_release(lua_State *script) +{ + size_t count = lua_rawlen(script, 1); + for (size_t i = 0; i < count; i++) { + obs_sceneitem_t *item; + + lua_rawgeti(script, 1, (int)i + 1); + ls_get_libobs_obj(obs_sceneitem_t, -1, &item); + lua_pop(script, 1); + + obs_sceneitem_release(item); + } + return 0; +} + +/* -------------------------------------------- */ + +static int hook_print(lua_State *script) +{ + struct obs_lua_script *data = current_lua_script; + const char *msg = lua_tostring(script, 1); + if (!msg) + return 0; + + script_info(&data->base, "%s", msg); + return 0; +} + +static int hook_error(lua_State *script) +{ + struct obs_lua_script *data = current_lua_script; + const char *msg = lua_tostring(script, 1); + if (!msg) + return 0; + + script_error(&data->base, "%s", msg); + return 0; +} + +/* -------------------------------------------- */ + +static int lua_script_log(lua_State *script) +{ + struct obs_lua_script *data = current_lua_script; + int log_level = (int)lua_tointeger(script, 1); + const char *msg = lua_tostring(script, 2); + + if (!msg) + return 0; + + /* ------------------- */ + + dstr_copy(&data->log_chunk, msg); + + const char *start = data->log_chunk.array; + char *endl = strchr(start, '\n'); + + while (endl) { + *endl = 0; + script_log(&data->base, log_level, "%s", start); + *endl = '\n'; + + start = endl + 1; + endl = strchr(start, '\n'); + } + + if (start && *start) + script_log(&data->base, log_level, "%s", start); + dstr_resize(&data->log_chunk, 0); + + /* ------------------- */ + + return 0; +} + +/* -------------------------------------------- */ + +static void add_hook_functions(lua_State *script) +{ +#define add_func(name, func) \ + do { \ + lua_pushstring(script, name); \ + lua_pushcfunction(script, func); \ + lua_rawset(script, -3); \ + } while (false) + + lua_getglobal(script, "_G"); + + add_func("print", hook_print); + add_func("error", hook_error); + + lua_pop(script, 1); + + /* ------------- */ + + lua_getglobal(script, "obslua"); + + add_func("script_log", lua_script_log); + add_func("timer_remove", timer_remove); + add_func("timer_add", timer_add); + add_func("obs_enum_sources", enum_sources); + add_func("obs_scene_enum_items", scene_enum_items); + add_func("source_list_release", source_list_release); + add_func("sceneitem_list_release", sceneitem_list_release); + add_func("calldata_source", calldata_source); + add_func("calldata_sceneitem", calldata_sceneitem); + add_func("obs_add_main_render_callback", + obs_lua_add_main_render_callback); + add_func("obs_remove_main_render_callback", + obs_lua_remove_main_render_callback); + add_func("obs_add_tick_callback", + obs_lua_add_tick_callback); + add_func("obs_remove_tick_callback", + obs_lua_remove_tick_callback); + add_func("signal_handler_connect", + obs_lua_signal_handler_connect); + add_func("signal_handler_disconnect", + obs_lua_signal_handler_disconnect); + add_func("signal_handler_connect_global", + obs_lua_signal_handler_connect_global); + add_func("signal_handler_disconnect_global", + obs_lua_signal_handler_disconnect_global); + add_func("obs_hotkey_unregister", + hotkey_unregister); + add_func("obs_hotkey_register_frontend", + hotkey_register_frontend); + add_func("obs_properties_add_button", + properties_add_button); + add_func("obs_property_set_modified_callback", + property_set_modified_callback); + add_func("remove_current_callback", + remove_current_callback); + + lua_pop(script, 1); +#undef add_func +} + +/* -------------------------------------------- */ + +static void lua_tick(void *param, float seconds) +{ + struct obs_lua_script *data; + struct lua_obs_timer *timer; + uint64_t ts = obs_get_video_frame_time(); + + /* --------------------------------- */ + /* process script_tick calls */ + + pthread_mutex_lock(&tick_mutex); + data = first_tick_script; + while (data) { + lua_State *script = data->script; + current_lua_script = data; + + pthread_mutex_lock(&data->mutex); + + lua_pushnumber(script, (double)seconds); + call_func_(script, data->tick, 1, 0, "tick", __FUNCTION__); + + pthread_mutex_unlock(&data->mutex); + + data = data->next_tick; + } + current_lua_script = NULL; + pthread_mutex_unlock(&tick_mutex); + + /* --------------------------------- */ + /* process timers */ + + pthread_mutex_lock(&timer_mutex); + timer = first_timer; + while (timer) { + struct lua_obs_timer *next = timer->next; + struct lua_obs_callback *cb = lua_obs_timer_cb(timer); + + if (cb->base.removed) { + lua_obs_timer_remove(timer); + } else { + uint64_t elapsed = ts - timer->last_ts; + + if (elapsed >= timer->interval) { + timer_call(&cb->base); + timer->last_ts += timer->interval; + } + } + + timer = next; + } + pthread_mutex_unlock(&timer_mutex); + + UNUSED_PARAMETER(param); +} + +/* -------------------------------------------- */ + +void obs_lua_script_update(obs_script_t *script, obs_data_t *settings); + +bool obs_lua_script_load(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + if (!data->base.loaded) { + data->base.loaded = load_lua_script(data); + if (data->base.loaded) + obs_lua_script_update(s, NULL); + } + + return data->base.loaded; +} + +obs_script_t *obs_lua_script_create(const char *path, obs_data_t *settings) +{ + struct obs_lua_script *data = bzalloc(sizeof(*data)); + + data->base.type = OBS_SCRIPT_LANG_LUA; + data->tick = LUA_REFNIL; + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutex_init_value(&data->mutex); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + if (pthread_mutex_init(&data->mutex, &attr) != 0) { + bfree(data); + return NULL; + } + + dstr_copy(&data->base.path, path); + + char *slash = path && *path ? strrchr(path, '/') : NULL; + if (slash) { + slash++; + dstr_copy(&data->base.file, slash); + dstr_left(&data->dir, &data->base.path, slash - path); + } else { + dstr_copy(&data->base.file, path); + } + + data->base.settings = obs_data_create(); + if (settings) + obs_data_apply(data->base.settings, settings); + + obs_lua_script_load((obs_script_t *)data); + return (obs_script_t *)data; +} + +extern void undef_lua_script_sources(struct obs_lua_script *data); + +void obs_lua_script_unload(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + + if (!s->loaded) + return; + + lua_State *script = data->script; + + /* ---------------------------- */ + /* undefine source types */ + + undef_lua_script_sources(data); + + /* ---------------------------- */ + /* unhook tick function */ + + if (data->p_prev_next_tick) { + pthread_mutex_lock(&tick_mutex); + + struct obs_lua_script *next = data->next_tick; + if (next) next->p_prev_next_tick = data->p_prev_next_tick; + *data->p_prev_next_tick = next; + + pthread_mutex_unlock(&tick_mutex); + + data->p_prev_next_tick = NULL; + data->next_tick = NULL; + } + + /* ---------------------------- */ + /* call script_unload */ + + pthread_mutex_lock(&data->mutex); + + lua_getglobal(script, "script_unload"); + lua_pcall(script, 0, 0, 0); + + /* ---------------------------- */ + /* remove all callbacks */ + + struct lua_obs_callback *cb = + (struct lua_obs_callback *)data->first_callback; + while (cb) { + struct lua_obs_callback *next = + (struct lua_obs_callback *)cb->base.next; + remove_lua_obs_callback(cb); + cb = next; + } + + pthread_mutex_unlock(&data->mutex); + + /* ---------------------------- */ + /* close script */ + + lua_close(script); + s->loaded = false; +} + +void obs_lua_script_destroy(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + + if (data) { + pthread_mutex_destroy(&data->mutex); + dstr_free(&data->base.path); + dstr_free(&data->base.file); + dstr_free(&data->base.desc); + obs_data_release(data->base.settings); + dstr_free(&data->log_chunk); + dstr_free(&data->dir); + bfree(data); + } +} + +void obs_lua_script_update(obs_script_t *s, obs_data_t *settings) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + lua_State *script = data->script; + + if (!s->loaded) + return; + if (data->update == LUA_REFNIL) + return; + + if (settings) + obs_data_apply(s->settings, settings); + + current_lua_script = data; + pthread_mutex_lock(&data->mutex); + + ls_push_libobs_obj(obs_data_t, s->settings, false); + call_func_(script, data->update, 1, 0, "script_update", __FUNCTION__); + + pthread_mutex_unlock(&data->mutex); + current_lua_script = NULL; +} + +obs_properties_t *obs_lua_script_get_properties(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + lua_State *script = data->script; + obs_properties_t *props = NULL; + + if (!s->loaded) + return NULL; + if (data->get_properties == LUA_REFNIL) + return NULL; + + current_lua_script = data; + pthread_mutex_lock(&data->mutex); + + call_func_(script, data->get_properties, 0, 1, "script_properties", + __FUNCTION__); + ls_get_libobs_obj(obs_properties_t, -1, &props); + + pthread_mutex_unlock(&data->mutex); + current_lua_script = NULL; + + return props; +} + +void obs_lua_script_save(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + lua_State *script = data->script; + + if (!s->loaded) + return; + if (data->save == LUA_REFNIL) + return; + + current_lua_script = data; + pthread_mutex_lock(&data->mutex); + + ls_push_libobs_obj(obs_data_t, s->settings, false); + call_func_(script, data->save, 1, 0, "script_save", __FUNCTION__); + + pthread_mutex_unlock(&data->mutex); + current_lua_script = NULL; +} + +/* -------------------------------------------- */ + +void obs_lua_load(void) +{ + struct dstr dep_paths = {0}; + struct dstr tmp = {0}; + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&tick_mutex, NULL); + pthread_mutex_init(&timer_mutex, &attr); + pthread_mutex_init(&lua_source_def_mutex, NULL); + + /* ---------------------------------------------- */ + /* Initialize Lua startup script */ + + dstr_printf(&tmp, startup_script_template, SCRIPT_DIR); + startup_script = tmp.array; + + dstr_free(&dep_paths); + + obs_add_tick_callback(lua_tick, NULL); +} + +void obs_lua_unload(void) +{ + obs_remove_tick_callback(lua_tick, NULL); + + bfree(startup_script); + pthread_mutex_destroy(&tick_mutex); + pthread_mutex_destroy(&timer_mutex); + pthread_mutex_destroy(&lua_source_def_mutex); +} diff --git a/deps/obs-scripting/obs-scripting-lua.h b/deps/obs-scripting/obs-scripting-lua.h new file mode 100644 index 0000000..caa60cf --- /dev/null +++ b/deps/obs-scripting/obs-scripting-lua.h @@ -0,0 +1,264 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +/* ---------------------------- */ + +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) +#pragma warning(disable : 4189) +#pragma warning(disable : 4244) +#pragma warning(disable : 4267) +#endif + +#define SWIG_TYPE_TABLE obslua +#include "swig/swigluarun.h" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +/* ---------------------------- */ + +#include +#include +#include + +#include "obs-scripting-internal.h" +#include "obs-scripting-callback.h" + +#define do_log(level, format, ...) \ + blog(level, "[Lua] " format, ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) +#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) + +/* ------------------------------------------------------------ */ + +struct obs_lua_script; +struct lua_obs_callback; + +extern THREAD_LOCAL struct lua_obs_callback *current_lua_cb; +extern THREAD_LOCAL struct obs_lua_script *current_lua_script; + +/* ------------------------------------------------------------ */ + +struct lua_obs_callback; + +struct obs_lua_script { + obs_script_t base; + + struct dstr dir; + struct dstr log_chunk; + + pthread_mutex_t mutex; + lua_State *script; + + struct script_callback *first_callback; + + int update; + int get_properties; + int save; + + int tick; + struct obs_lua_script *next_tick; + struct obs_lua_script **p_prev_next_tick; + + bool defined_sources; +}; + +#define lock_callback() \ + struct obs_lua_script *__last_script = current_lua_script; \ + struct lua_obs_callback *__last_callback = current_lua_cb; \ + current_lua_cb = cb; \ + current_lua_script = (struct obs_lua_script *)cb->base.script; \ + pthread_mutex_lock(¤t_lua_script->mutex); +#define unlock_callback() \ + pthread_mutex_unlock(¤t_lua_script->mutex); \ + current_lua_script = __last_script; \ + current_lua_cb = __last_callback; + +/* ------------------------------------------------ */ + +struct lua_obs_callback { + struct script_callback base; + + lua_State *script; + int reg_idx; +}; + +static inline struct lua_obs_callback *add_lua_obs_callback_extra( + lua_State *script, + int stack_idx, + size_t extra_size) +{ + struct obs_lua_script *data = current_lua_script; + struct lua_obs_callback *cb = add_script_callback( + &data->first_callback, + (obs_script_t *)data, + sizeof(*cb) + extra_size); + + lua_pushvalue(script, stack_idx); + cb->reg_idx = luaL_ref(script, LUA_REGISTRYINDEX); + cb->script = script; + return cb; +} + +static inline struct lua_obs_callback *add_lua_obs_callback( + lua_State *script, int stack_idx) +{ + return add_lua_obs_callback_extra(script, stack_idx, 0); +} + +static inline void *lua_obs_callback_extra_data(struct lua_obs_callback *cb) +{ + return (void*)&cb[1]; +} + +static inline struct obs_lua_script *lua_obs_callback_script( + struct lua_obs_callback *cb) +{ + return (struct obs_lua_script *)cb->base.script; +} + +static inline struct lua_obs_callback *find_next_lua_obs_callback( + lua_State *script, struct lua_obs_callback *cb, int stack_idx) +{ + struct obs_lua_script *data = current_lua_script; + + cb = cb ? (struct lua_obs_callback *)cb->base.next + : (struct lua_obs_callback *)data->first_callback; + + while (cb) { + lua_rawgeti(script, LUA_REGISTRYINDEX, cb->reg_idx); + bool match = lua_rawequal(script, -1, stack_idx); + lua_pop(script, 1); + + if (match) + break; + + cb = (struct lua_obs_callback *)cb->base.next; + } + + return cb; +} + +static inline struct lua_obs_callback *find_lua_obs_callback( + lua_State *script, int stack_idx) +{ + return find_next_lua_obs_callback(script, NULL, stack_idx); +} + +static inline void remove_lua_obs_callback(struct lua_obs_callback *cb) +{ + remove_script_callback(&cb->base); + luaL_unref(cb->script, LUA_REGISTRYINDEX, cb->reg_idx); +} + +static inline void just_free_lua_obs_callback(struct lua_obs_callback *cb) +{ + just_free_script_callback(&cb->base); +} + +static inline void free_lua_obs_callback(struct lua_obs_callback *cb) +{ + free_script_callback(&cb->base); +} + +/* ------------------------------------------------ */ + +static int is_ptr(lua_State *script, int idx) +{ + return lua_isuserdata(script, idx) || lua_isnil(script, idx); +} + +static int is_table(lua_State *script, int idx) +{ + return lua_istable(script, idx); +} + +static int is_function(lua_State *script, int idx) +{ + return lua_isfunction(script, idx); +} + +typedef int (*param_cb)(lua_State *script, int idx); + +static inline bool verify_args1_(lua_State *script, + param_cb param1_check, + const char *func) +{ + if (lua_gettop(script) != 1) { + warn("Wrong number of parameters for %s", func); + return false; + } + if (!param1_check(script, 1)) { + warn("Wrong parameter type for parameter %d of %s", 1, func); + return false; + } + + return true; +} + +#define verify_args1(script, param1_check) \ + verify_args1_(script, param1_check, __FUNCTION__) + +static inline bool call_func_(lua_State *script, + int reg_idx, int args, int rets, + const char *func, const char *display_name) +{ + if (reg_idx == LUA_REFNIL) + return false; + + struct obs_lua_script *data = current_lua_script; + + lua_rawgeti(script, LUA_REGISTRYINDEX, reg_idx); + lua_insert(script, -1 - args); + + if (lua_pcall(script, args, rets, 0) != 0) { + script_warn(&data->base, "Failed to call %s for %s: %s", func, + display_name, + lua_tostring(script, -1)); + lua_pop(script, 1); + return false; + } + + return true; +} + +bool ls_get_libobs_obj_(lua_State * script, + const char *type, + int lua_idx, + void * libobs_out, + const char *id, + const char *func, + int line); +bool ls_push_libobs_obj_(lua_State * script, + const char *type, + void * libobs_in, + bool ownership, + const char *id, + const char *func, + int line); + +extern void add_lua_source_functions(lua_State *script); diff --git a/deps/obs-scripting/obs-scripting-python-frontend.c b/deps/obs-scripting/obs-scripting-python-frontend.c new file mode 100644 index 0000000..1c70252 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python-frontend.c @@ -0,0 +1,361 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include + +#include "obs-scripting-python.h" + +#define libobs_to_py(type, obs_obj, ownership, py_obj) \ + libobs_to_py_(#type " *", obs_obj, ownership, py_obj, \ + NULL, __func__, __LINE__) +#define py_to_libobs(type, py_obj, libobs_out) \ + py_to_libobs_(#type " *", py_obj, libobs_out, \ + NULL, __func__, __LINE__) + +/* ----------------------------------- */ + +static PyObject *get_scene_names(PyObject *self, PyObject *args) +{ + char **names = obs_frontend_get_scene_names(); + char **name = names; + + PyObject *list = PyList_New(0); + + while (name && *name) { + PyObject *py_name = PyUnicode_FromString(*name); + if (py_name) { + PyList_Append(list, py_name); + Py_DECREF(py_name); + } + name++; + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + bfree(names); + return list; +} + +static PyObject *get_scenes(PyObject *self, PyObject *args) +{ + struct obs_frontend_source_list list = {0}; + obs_frontend_get_scenes(&list); + + PyObject *ret = PyList_New(0); + + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + PyObject *py_source; + + if (libobs_to_py(obs_source_t, source, false, &py_source)) { + PyList_Append(ret, py_source); + Py_DECREF(py_source); + } + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + da_free(list.sources); + return ret; +} + +static PyObject *get_current_scene(PyObject *self, PyObject *args) +{ + obs_source_t *source = obs_frontend_get_current_scene(); + + PyObject *py_source; + if (!libobs_to_py(obs_source_t, source, false, &py_source)) { + obs_source_release(source); + return python_none(); + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + return py_source; +} + +static PyObject *set_current_scene(PyObject *self, PyObject *args) +{ + PyObject *py_source; + obs_source_t *source = NULL; + + if (!parse_args(args, "O", &py_source)) + return python_none(); + if (!py_to_libobs(obs_source_t, py_source, &source)) + return python_none(); + + UNUSED_PARAMETER(self); + + obs_frontend_set_current_scene(source); + return python_none(); +} + +static PyObject *get_transitions(PyObject *self, PyObject *args) +{ + struct obs_frontend_source_list list = {0}; + obs_frontend_get_transitions(&list); + + PyObject *ret = PyList_New(0); + + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + PyObject *py_source; + + if (libobs_to_py(obs_source_t, source, false, &py_source)) { + PyList_Append(ret, py_source); + Py_DECREF(py_source); + } + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + da_free(list.sources); + return ret; +} + +static PyObject *get_current_transition(PyObject *self, PyObject *args) +{ + obs_source_t *source = obs_frontend_get_current_transition(); + + PyObject *py_source; + if (!libobs_to_py(obs_source_t, source, false, &py_source)) { + obs_source_release(source); + return python_none(); + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + return py_source; +} + +static PyObject *set_current_transition(PyObject *self, PyObject *args) +{ + PyObject *py_source; + obs_source_t *source = NULL; + + if (!parse_args(args, "O", &py_source)) + return python_none(); + if (!py_to_libobs(obs_source_t, py_source, &source)) + return python_none(); + + UNUSED_PARAMETER(self); + + obs_frontend_set_current_transition(source); + return python_none(); +} + +static PyObject *get_scene_collections(PyObject *self, PyObject *args) +{ + char **names = obs_frontend_get_scene_collections(); + char **name = names; + + PyObject *list = PyList_New(0); + + while (name && *name) { + PyObject *py_name = PyUnicode_FromString(*name); + if (py_name) { + PyList_Append(list, py_name); + Py_DECREF(py_name); + } + name++; + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + bfree(names); + return list; +} + +static PyObject *get_current_scene_collection(PyObject *self, PyObject *args) +{ + char *name = obs_frontend_get_current_scene_collection(); + PyObject *ret = PyUnicode_FromString(name); + bfree(name); + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + return ret; +} + +static PyObject *set_current_scene_collection(PyObject *self, PyObject *args) +{ + const char *name; + if (!parse_args(args, "s", &name)) + return python_none(); + + UNUSED_PARAMETER(self); + + obs_frontend_set_current_scene_collection(name); + return python_none(); +} + +static PyObject *get_profiles(PyObject *self, PyObject *args) +{ + char **names = obs_frontend_get_profiles(); + char **name = names; + + PyObject *list = PyList_New(0); + + while (name && *name) { + PyObject *py_name = PyUnicode_FromString(*name); + if (py_name) { + PyList_Append(list, py_name); + Py_DECREF(py_name); + } + name++; + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + bfree(names); + return list; +} + +static PyObject *get_current_profile(PyObject *self, PyObject *args) +{ + char *name = obs_frontend_get_current_profile(); + PyObject *ret = PyUnicode_FromString(name); + bfree(name); + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + return ret; +} + +static PyObject *set_current_profile(PyObject *self, PyObject *args) +{ + const char *name; + if (!parse_args(args, "s", &name)) + return python_none(); + + UNUSED_PARAMETER(self); + + obs_frontend_set_current_profile(name); + return python_none(); +} + +/* ----------------------------------- */ + +static void frontend_save_callback(obs_data_t *save_data, bool saving, + void *priv) +{ + struct python_obs_callback *cb = priv; + + if (cb->base.removed) { + obs_frontend_remove_save_callback(frontend_save_callback, cb); + return; + } + + lock_python(); + + PyObject *py_save_data; + + if (libobs_to_py(obs_data_t, save_data, false, &py_save_data)) { + PyObject *args = Py_BuildValue("(Op)", py_save_data, saving); + + struct python_obs_callback *last_cb = cur_python_cb; + cur_python_cb = cb; + cur_python_script = (struct obs_python_script *)cb->base.script; + + PyObject *py_ret = PyObject_CallObject(cb->func, args); + Py_XDECREF(py_ret); + py_error(); + + cur_python_script = NULL; + cur_python_cb = last_cb; + + Py_XDECREF(args); + Py_XDECREF(py_save_data); + } + + unlock_python(); +} + +static PyObject *remove_save_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static void add_save_callback_defer(void *cb) +{ + obs_frontend_add_save_callback(frontend_save_callback, cb); +} + +static PyObject *add_save_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + defer_call_post(add_save_callback_defer, cb); + return python_none(); +} + +/* ----------------------------------- */ + +void add_python_frontend_funcs(PyObject *module) +{ + static PyMethodDef funcs[] = { +#define DEF_FUNC(c) {"obs_frontend_" #c, c, METH_VARARGS, NULL} + + DEF_FUNC(get_scene_names), + DEF_FUNC(get_scenes), + DEF_FUNC(get_current_scene), + DEF_FUNC(set_current_scene), + DEF_FUNC(get_transitions), + DEF_FUNC(get_current_transition), + DEF_FUNC(set_current_transition), + DEF_FUNC(get_scene_collections), + DEF_FUNC(get_current_scene_collection), + DEF_FUNC(set_current_scene_collection), + DEF_FUNC(get_profiles), + DEF_FUNC(get_current_profile), + DEF_FUNC(set_current_profile), + DEF_FUNC(remove_save_callback), + DEF_FUNC(add_save_callback), + +#undef DEF_FUNC + {0} + }; + + add_functions_to_py_module(module, funcs); +} diff --git a/deps/obs-scripting/obs-scripting-python-import.c b/deps/obs-scripting/obs-scripting-python-import.c new file mode 100644 index 0000000..fa24c40 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python-import.c @@ -0,0 +1,149 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include + +#define NO_REDEFS +#include "obs-scripting-python-import.h" +#include "obs-scripting-config.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4152) +#endif + +#ifdef _WIN32 +#define SO_EXT ".dll" +#elif __APPLE__ +#define SO_EXT ".dylib" +#endif + +bool import_python(const char *python_path) +{ + struct dstr lib_path; + bool success = false; + void *lib; + + if (!python_path) + python_path = ""; + + dstr_init_copy(&lib_path, python_path); + dstr_replace(&lib_path, "\\", "/"); + if (!dstr_is_empty(&lib_path)) { + dstr_cat(&lib_path, "/"); + } + dstr_cat(&lib_path, PYTHON_LIB SO_EXT); + + lib = os_dlopen(lib_path.array); + if (!lib) { + blog(LOG_WARNING, "[Python] Could not load library: %s", + lib_path.array); + goto fail; + } + +#define IMPORT_FUNC(x) \ + do { \ + Import_##x = os_dlsym(lib, #x); \ + if (!Import_##x) { \ + blog(LOG_WARNING, "[Python] Failed to import: %s", \ + #x); \ + goto fail; \ + } \ + } while (false) + + IMPORT_FUNC(PyType_Ready); + IMPORT_FUNC(PyObject_GenericGetAttr); + IMPORT_FUNC(PyObject_IsTrue); + IMPORT_FUNC(Py_DecRef); + IMPORT_FUNC(PyObject_Malloc); + IMPORT_FUNC(PyObject_Free); + IMPORT_FUNC(PyObject_Init); + IMPORT_FUNC(PyUnicode_FromFormat); + IMPORT_FUNC(PyUnicode_Concat); + IMPORT_FUNC(PyLong_FromVoidPtr); + IMPORT_FUNC(PyBool_FromLong); + IMPORT_FUNC(PyGILState_Ensure); + IMPORT_FUNC(PyGILState_GetThisThreadState); + IMPORT_FUNC(PyErr_SetString); + IMPORT_FUNC(PyErr_Occurred); + IMPORT_FUNC(PyErr_Fetch); + IMPORT_FUNC(PyErr_Restore); + IMPORT_FUNC(PyErr_WriteUnraisable); + IMPORT_FUNC(PyArg_UnpackTuple); + IMPORT_FUNC(Py_BuildValue); + IMPORT_FUNC(PyRun_SimpleStringFlags); + IMPORT_FUNC(PyErr_Print); + IMPORT_FUNC(Py_SetPythonHome); + IMPORT_FUNC(Py_Initialize); + IMPORT_FUNC(Py_Finalize); + IMPORT_FUNC(Py_IsInitialized); + IMPORT_FUNC(PyEval_InitThreads); + IMPORT_FUNC(PyEval_ThreadsInitialized); + IMPORT_FUNC(PyEval_ReleaseThread); + IMPORT_FUNC(PySys_SetArgv); + IMPORT_FUNC(PyImport_ImportModule); + IMPORT_FUNC(PyObject_CallFunctionObjArgs); + IMPORT_FUNC(_Py_NotImplementedStruct); + IMPORT_FUNC(PyExc_TypeError); + IMPORT_FUNC(PyExc_RuntimeError); + IMPORT_FUNC(PyObject_GetAttr); + IMPORT_FUNC(PyUnicode_FromString); + IMPORT_FUNC(PyDict_GetItemString); + IMPORT_FUNC(PyDict_SetItemString); + IMPORT_FUNC(PyCFunction_NewEx); + IMPORT_FUNC(PyModule_GetDict); + IMPORT_FUNC(PyModule_GetNameObject); + IMPORT_FUNC(PyModule_AddObject); + IMPORT_FUNC(PyModule_AddStringConstant); + IMPORT_FUNC(PyImport_Import); + IMPORT_FUNC(PyObject_CallObject); + IMPORT_FUNC(_Py_FalseStruct); + IMPORT_FUNC(_Py_TrueStruct); + IMPORT_FUNC(PyGILState_Release); + IMPORT_FUNC(PyList_Append); + IMPORT_FUNC(PySys_GetObject); + IMPORT_FUNC(PyImport_ReloadModule); + IMPORT_FUNC(PyObject_GetAttrString); + IMPORT_FUNC(PyCapsule_New); + IMPORT_FUNC(PyCapsule_GetPointer); + IMPORT_FUNC(PyArg_ParseTuple); + IMPORT_FUNC(PyFunction_Type); + IMPORT_FUNC(PyObject_SetAttr); + IMPORT_FUNC(_PyObject_New); + IMPORT_FUNC(PyCapsule_Import); + IMPORT_FUNC(PyErr_Clear); + IMPORT_FUNC(PyObject_Call); + IMPORT_FUNC(PyList_New); + IMPORT_FUNC(PyList_Size); + IMPORT_FUNC(PyList_GetItem); + IMPORT_FUNC(PyUnicode_AsUTF8String); + IMPORT_FUNC(PyLong_FromUnsignedLongLong); + IMPORT_FUNC(PyArg_VaParse); + IMPORT_FUNC(_Py_NoneStruct); + +#undef IMPORT_FUNC + + success = true; + +fail: + if (!success && lib) + os_dlclose(lib); + + dstr_free(&lib_path); + + return success; +} diff --git a/deps/obs-scripting/obs-scripting-python-import.h b/deps/obs-scripting/obs-scripting-python-import.h new file mode 100644 index 0000000..941e580 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python-import.h @@ -0,0 +1,198 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include + +#if defined(_WIN32) || defined(__APPLE__) +#define RUNTIME_LINK 1 +#define Py_NO_ENABLE_SHARED +#else +#define RUNTIME_LINK 0 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4115) +#endif + +#if defined(_WIN32) && defined(_DEBUG) +# undef _DEBUG +# include +# define _DEBUG +#else +# include +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#if RUNTIME_LINK + +#ifdef NO_REDEFS +#define PY_EXTERN +#else +#define PY_EXTERN extern +#endif + +PY_EXTERN int (*Import_PyType_Ready)(PyTypeObject *); +PY_EXTERN PyObject *(*Import_PyObject_GenericGetAttr)(PyObject *, PyObject *); +PY_EXTERN int (*Import_PyObject_IsTrue)(PyObject *); +PY_EXTERN void (*Import_Py_DecRef)(PyObject *); +PY_EXTERN void *(*Import_PyObject_Malloc)(size_t size); +PY_EXTERN void (*Import_PyObject_Free)(void *ptr); +PY_EXTERN PyObject *(*Import_PyObject_Init)(PyObject *, PyTypeObject *); +PY_EXTERN PyObject *(*Import_PyUnicode_FromFormat)(const char *format, ...); +PY_EXTERN PyObject *(*Import_PyUnicode_Concat)(PyObject *left, PyObject *right); +PY_EXTERN PyObject *(*Import_PyLong_FromVoidPtr)(void *); +PY_EXTERN PyObject *(*Import_PyBool_FromLong)(long); +PY_EXTERN PyGILState_STATE (*Import_PyGILState_Ensure)(void); +PY_EXTERN PyThreadState *(*Import_PyGILState_GetThisThreadState)(void); +PY_EXTERN void (*Import_PyErr_SetString)(PyObject *exception, const char *string); +PY_EXTERN PyObject *(*Import_PyErr_Occurred)(void); +PY_EXTERN void (*Import_PyErr_Fetch)(PyObject **, PyObject **, PyObject **); +PY_EXTERN void (*Import_PyErr_Restore)(PyObject *, PyObject *, PyObject *); +PY_EXTERN void (*Import_PyErr_WriteUnraisable)(PyObject *); +PY_EXTERN int (*Import_PyArg_UnpackTuple)(PyObject *, const char *, Py_ssize_t, Py_ssize_t, ...); +PY_EXTERN PyObject *(*Import_Py_BuildValue)(const char *, ...); +PY_EXTERN int (*Import_PyRun_SimpleStringFlags)(const char *, PyCompilerFlags *); +PY_EXTERN void (*Import_PyErr_Print)(void); +PY_EXTERN void (*Import_Py_SetPythonHome)(wchar_t *); +PY_EXTERN void (*Import_Py_Initialize)(void); +PY_EXTERN void (*Import_Py_Finalize)(void); +PY_EXTERN int (*Import_Py_IsInitialized)(void); +PY_EXTERN void (*Import_PyEval_InitThreads)(void); +PY_EXTERN int (*Import_PyEval_ThreadsInitialized)(void); +PY_EXTERN void (*Import_PyEval_ReleaseThread)(PyThreadState *tstate); +PY_EXTERN void (*Import_PySys_SetArgv)(int, wchar_t **); +PY_EXTERN PyObject *(*Import_PyImport_ImportModule)(const char *name); +PY_EXTERN PyObject *(*Import_PyObject_CallFunctionObjArgs)(PyObject *callable, ...); +PY_EXTERN PyObject (*Import__Py_NotImplementedStruct); +PY_EXTERN PyObject *(*Import_PyExc_TypeError); +PY_EXTERN PyObject *(*Import_PyExc_RuntimeError); +PY_EXTERN PyObject *(*Import_PyObject_GetAttr)(PyObject *, PyObject *); +PY_EXTERN PyObject *(*Import_PyUnicode_FromString)(const char *u); +PY_EXTERN PyObject *(*Import_PyDict_GetItemString)(PyObject *dp, const char *key); +PY_EXTERN int (*Import_PyDict_SetItemString)(PyObject *dp, const char *key, PyObject *item); +PY_EXTERN PyObject *(*Import_PyCFunction_NewEx)(PyMethodDef *, PyObject *, PyObject *); +PY_EXTERN PyObject *(*Import_PyModule_GetDict)(PyObject *); +PY_EXTERN PyObject *(*Import_PyModule_GetNameObject)(PyObject *); +PY_EXTERN int (*Import_PyModule_AddObject)(PyObject *, const char *, PyObject *); +PY_EXTERN int (*Import_PyModule_AddStringConstant)(PyObject *, const char *, const char *); +PY_EXTERN PyObject *(*Import_PyImport_Import)(PyObject *name); +PY_EXTERN PyObject *(*Import_PyObject_CallObject)(PyObject *callable_object, PyObject *args); +PY_EXTERN struct _longobject (*Import__Py_FalseStruct); +PY_EXTERN struct _longobject (*Import__Py_TrueStruct); +PY_EXTERN void (*Import_PyGILState_Release)(PyGILState_STATE); +PY_EXTERN int (*Import_PyList_Append)(PyObject *, PyObject *); +PY_EXTERN PyObject *(*Import_PySys_GetObject)(const char *); +PY_EXTERN PyObject *(*Import_PyImport_ReloadModule)(PyObject *m); +PY_EXTERN PyObject *(*Import_PyObject_GetAttrString)(PyObject *, const char *); +PY_EXTERN PyObject *(*Import_PyCapsule_New)(void *pointer, const char *name, PyCapsule_Destructor destructor); +PY_EXTERN void *(*Import_PyCapsule_GetPointer)(PyObject *capsule, const char *name); +PY_EXTERN int (*Import_PyArg_ParseTuple)(PyObject *, const char *, ...); +PY_EXTERN PyTypeObject (*Import_PyFunction_Type); +PY_EXTERN int (*Import_PyObject_SetAttr)(PyObject *, PyObject *, PyObject *); +PY_EXTERN PyObject *(*Import__PyObject_New)(PyTypeObject *); +PY_EXTERN void *(*Import_PyCapsule_Import)(const char *name, int no_block); +PY_EXTERN void (*Import_PyErr_Clear)(void); +PY_EXTERN PyObject *(*Import_PyObject_Call)(PyObject *callable_object, PyObject *args, PyObject *kwargs); +PY_EXTERN PyObject *(*Import_PyList_New)(Py_ssize_t size); +PY_EXTERN Py_ssize_t (*Import_PyList_Size)(PyObject *); +PY_EXTERN PyObject *(*Import_PyList_GetItem)(PyObject *, Py_ssize_t); +PY_EXTERN PyObject *(*Import_PyUnicode_AsUTF8String)(PyObject *unicode); +PY_EXTERN PyObject *(*Import_PyLong_FromUnsignedLongLong)(unsigned long long); +PY_EXTERN int (*Import_PyArg_VaParse)(PyObject *, const char *, va_list); +PY_EXTERN PyObject (*Import__Py_NoneStruct); + +extern bool import_python(const char *python_path); + +# ifndef NO_REDEFS +# define PyType_Ready Import_PyType_Ready +# define PyObject_GenericGetAttr Import_PyObject_GenericGetAttr +# define PyObject_IsTrue Import_PyObject_IsTrue +# define Py_DecRef Import_Py_DecRef +# define PyObject_Malloc Import_PyObject_Malloc +# define PyObject_Free Import_PyObject_Free +# define PyObject_Init Import_PyObject_Init +# define PyUnicode_FromFormat Import_PyUnicode_FromFormat +# define PyUnicode_Concat Import_PyUnicode_Concat +# define PyLong_FromVoidPtr Import_PyLong_FromVoidPtr +# define PyBool_FromLong Import_PyBool_FromLong +# define PyGILState_Ensure Import_PyGILState_Ensure +# define PyGILState_GetThisThreadState Import_PyGILState_GetThisThreadState +# define PyErr_SetString Import_PyErr_SetString +# define PyErr_Occurred Import_PyErr_Occurred +# define PyErr_Fetch Import_PyErr_Fetch +# define PyErr_Restore Import_PyErr_Restore +# define PyErr_WriteUnraisable Import_PyErr_WriteUnraisable +# define PyArg_UnpackTuple Import_PyArg_UnpackTuple +# define Py_BuildValue Import_Py_BuildValue +# define PyRun_SimpleStringFlags Import_PyRun_SimpleStringFlags +# define PyErr_Print Import_PyErr_Print +# define Py_SetPythonHome Import_Py_SetPythonHome +# define Py_Initialize Import_Py_Initialize +# define Py_Finalize Import_Py_Finalize +# define Py_IsInitialized Import_Py_IsInitialized +# define PyEval_InitThreads Import_PyEval_InitThreads +# define PyEval_ThreadsInitialized Import_PyEval_ThreadsInitialized +# define PyEval_ReleaseThread Import_PyEval_ReleaseThread +# define PySys_SetArgv Import_PySys_SetArgv +# define PyImport_ImportModule Import_PyImport_ImportModule +# define PyObject_CallFunctionObjArgs Import_PyObject_CallFunctionObjArgs +# define _Py_NotImplementedStruct (*Import__Py_NotImplementedStruct) +# define PyExc_TypeError (*Import_PyExc_TypeError) +# define PyExc_RuntimeError (*Import_PyExc_RuntimeError) +# define PyObject_GetAttr Import_PyObject_GetAttr +# define PyUnicode_FromString Import_PyUnicode_FromString +# define PyDict_GetItemString Import_PyDict_GetItemString +# define PyDict_SetItemString Import_PyDict_SetItemString +# define PyCFunction_NewEx Import_PyCFunction_NewEx +# define PyModule_GetDict Import_PyModule_GetDict +# define PyModule_GetNameObject Import_PyModule_GetNameObject +# define PyModule_AddObject Import_PyModule_AddObject +# define PyModule_AddStringConstant Import_PyModule_AddStringConstant +# define PyImport_Import Import_PyImport_Import +# define PyObject_CallObject Import_PyObject_CallObject +# define _Py_FalseStruct (*Import__Py_FalseStruct) +# define _Py_TrueStruct (*Import__Py_TrueStruct) +# define PyGILState_Release Import_PyGILState_Release +# define PyList_Append Import_PyList_Append +# define PySys_GetObject Import_PySys_GetObject +# define PyImport_ReloadModule Import_PyImport_ReloadModule +# define PyObject_GetAttrString Import_PyObject_GetAttrString +# define PyCapsule_New Import_PyCapsule_New +# define PyCapsule_GetPointer Import_PyCapsule_GetPointer +# define PyArg_ParseTuple Import_PyArg_ParseTuple +# define PyFunction_Type (*Import_PyFunction_Type) +# define PyObject_SetAttr Import_PyObject_SetAttr +# define _PyObject_New Import__PyObject_New +# define PyCapsule_Import Import_PyCapsule_Import +# define PyErr_Clear Import_PyErr_Clear +# define PyObject_Call Import_PyObject_Call +# define PyList_New Import_PyList_New +# define PyList_Size Import_PyList_Size +# define PyList_GetItem Import_PyList_GetItem +# define PyUnicode_AsUTF8String Import_PyUnicode_AsUTF8String +# define PyLong_FromUnsignedLongLong Import_PyLong_FromUnsignedLongLong +# define PyArg_VaParse Import_PyArg_VaParse +# define _Py_NoneStruct (*Import__Py_NoneStruct) +# endif + +#endif diff --git a/deps/obs-scripting/obs-scripting-python.c b/deps/obs-scripting/obs-scripting-python.c new file mode 100644 index 0000000..766aa92 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python.c @@ -0,0 +1,1725 @@ +/****************************************************************************** + Copyright (C) 2015 by Andrew Skinner + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-scripting-python.h" +#include "obs-scripting-config.h" +#include +#include +#include +#include + +#include + +/* ========================================================================= */ + +// #define DEBUG_PYTHON_STARTUP + +static const char *startup_script = "\n\ +import sys\n\ +import os\n\ +import obspython\n\ +class stdout_logger(object):\n\ + def write(self, message):\n\ + obspython.script_log_no_endl(obspython.LOG_INFO, message)\n\ + def flush(self):\n\ + pass\n\ +class stderr_logger(object):\n\ + def write(self, message):\n\ + obspython.script_log_no_endl(obspython.LOG_ERROR, message)\n\ + def flush(self):\n\ + pass\n\ +os.environ['PYTHONUNBUFFERED'] = '1'\n\ +sys.stdout = stdout_logger()\n\ +sys.stderr = stderr_logger()\n"; + +#if RUNTIME_LINK +static wchar_t home_path[1024] = {0}; +#endif + +DARRAY(char*) python_paths; +static bool python_loaded = false; + +static pthread_mutex_t tick_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct obs_python_script *first_tick_script = NULL; + +static PyObject *py_obspython = NULL; +struct obs_python_script *cur_python_script = NULL; +struct python_obs_callback *cur_python_cb = NULL; + +/* -------------------------------------------- */ + +bool py_to_libobs_(const char *type, + PyObject * py_in, + void * libobs_out, + const char *id, + const char *func, + int line) +{ + swig_type_info *info = SWIG_TypeQuery(type); + if (info == NULL) { + warn("%s:%d: SWIG could not find type: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + int ret = SWIG_ConvertPtr(py_in, libobs_out, info, 0); + if (!SWIG_IsOK(ret)) { + warn("%s:%d: SWIG failed to convert python object to obs " + "object: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + return true; +} + +bool libobs_to_py_(const char *type, + void * libobs_in, + bool ownership, + PyObject ** py_out, + const char *id, + const char *func, + int line) +{ + swig_type_info *info = SWIG_TypeQuery(type); + if (info == NULL) { + warn("%s:%d: SWIG could not find type: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + *py_out = SWIG_NewPointerObj(libobs_in, info, (int)ownership); + if (*py_out == Py_None) { + warn("%s:%d: SWIG failed to convert obs object to python " + "object: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + return true; +} + + +#define libobs_to_py(type, obs_obj, ownership, py_obj) \ + libobs_to_py_(#type " *", obs_obj, ownership, py_obj, \ + NULL, __func__, __LINE__) +#define py_to_libobs(type, py_obj, libobs_out) \ + py_to_libobs_(#type " *", py_obj, libobs_out, \ + NULL, __func__, __LINE__) + +#define lock_callback(cb) \ + lock_python(); \ + struct obs_python_script *__last_script = cur_python_script; \ + struct python_obs_callback *__last_cb = cur_python_cb; \ + cur_python_script = (struct obs_python_script *)cb->base.script; \ + cur_python_cb = cb +#define unlock_callback() \ + cur_python_cb = __last_cb; \ + cur_python_script = __last_script; \ + unlock_python() + +/* ========================================================================= */ + +void add_functions_to_py_module(PyObject *module, PyMethodDef *method_list) +{ + PyObject *dict = PyModule_GetDict(module); + PyObject *name = PyModule_GetNameObject(module); + if (!dict || !name) { + return; + } + for (PyMethodDef *ml = method_list; ml->ml_name != NULL; ml++) { + PyObject *func = PyCFunction_NewEx(ml, module, name); + if (!func) { + continue; + } + PyDict_SetItemString(dict, ml->ml_name, func); + Py_DECREF(func); + } + Py_DECREF(name); +} + +/* -------------------------------------------- */ + +static PyObject *py_get_current_script_path(PyObject *self, PyObject *args) +{ + UNUSED_PARAMETER(args); + return PyDict_GetItemString(PyModule_GetDict(self), + "__script_dir__"); +} + +static void get_defaults(struct obs_python_script *data, PyObject *get_defs) +{ + PyObject *py_settings; + if (!libobs_to_py(obs_data_t, data->base.settings, false, &py_settings)) + return; + + PyObject *args = Py_BuildValue("(O)", py_settings); + PyObject *py_ret = PyObject_CallObject(get_defs, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_settings); +} + +static bool load_python_script(struct obs_python_script *data) +{ + PyObject *py_file = NULL; + PyObject *py_module = NULL; + PyObject *py_success = NULL; + PyObject *py_tick = NULL; + PyObject *py_load = NULL; + PyObject *py_defaults = NULL; + bool success = false; + int ret; + + cur_python_script = data; + + if (!data->module) { + py_file = PyUnicode_FromString(data->name.array); + py_module = PyImport_Import(py_file); + } else { + py_module = PyImport_ReloadModule(data->module); + } + + if (!py_module) { + py_error(); + goto fail; + } + + Py_XINCREF(py_obspython); + ret = PyModule_AddObject(py_module, "obspython", py_obspython); + if (py_error() || ret != 0) + goto fail; + + ret = PyModule_AddStringConstant(py_module, "__script_dir__", + data->dir.array); + if (py_error() || ret != 0) + goto fail; + + PyObject *py_data = PyCapsule_New(data, NULL, NULL); + ret = PyModule_AddObject(py_module, "__script_data__", py_data); + if (py_error() || ret != 0) + goto fail; + + static PyMethodDef global_funcs[] = { + {"script_path", + py_get_current_script_path, + METH_NOARGS, + "Gets the script path"}, + {0} + }; + + add_functions_to_py_module(py_module, global_funcs); + + data->update = PyObject_GetAttrString(py_module, "script_update"); + if (!data->update) + PyErr_Clear(); + + data->save = PyObject_GetAttrString(py_module, "script_save"); + if (!data->save) + PyErr_Clear(); + + data->get_properties = PyObject_GetAttrString(py_module, + "script_properties"); + if (!data->get_properties) + PyErr_Clear(); + + PyObject *func = PyObject_GetAttrString(py_module, "script_defaults"); + if (func) { + get_defaults(data, func); + Py_DECREF(func); + } else { + PyErr_Clear(); + } + + func = PyObject_GetAttrString(py_module, "script_description"); + if (func) { + PyObject *py_ret = PyObject_CallObject(func, NULL); + py_error(); + PyObject *py_desc = PyUnicode_AsUTF8String(py_ret); + if (py_desc) { + const char *desc = PyBytes_AS_STRING(py_desc); + if (desc) dstr_copy(&data->base.desc, desc); + Py_DECREF(py_desc); + } + Py_XDECREF(py_ret); + Py_DECREF(func); + } else { + PyErr_Clear(); + } + + py_tick = PyObject_GetAttrString(py_module, "script_tick"); + if (py_tick) { + pthread_mutex_lock(&tick_mutex); + + struct obs_python_script *next = first_tick_script; + data->next_tick = next; + data->p_prev_next_tick = &first_tick_script; + if (next) next->p_prev_next_tick = &data->next_tick; + first_tick_script = data; + + data->tick = py_tick; + py_tick = NULL; + + pthread_mutex_unlock(&tick_mutex); + } else { + PyErr_Clear(); + } + + py_load = PyObject_GetAttrString(py_module, "script_load"); + if (py_load) { + PyObject *py_s; + libobs_to_py(obs_data_t, data->base.settings, false, &py_s); + PyObject *args = Py_BuildValue("(O)", py_s); + PyObject *py_ret = PyObject_CallObject(py_load, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_s); + } else { + PyErr_Clear(); + } + + if (data->module) + Py_XDECREF(data->module); + data->module = py_module; + py_module = NULL; + + success = true; + +fail: + Py_XDECREF(py_load); + Py_XDECREF(py_tick); + Py_XDECREF(py_defaults); + Py_XDECREF(py_success); + Py_XDECREF(py_file); + if (!success) + Py_XDECREF(py_module); + cur_python_script = NULL; + return success; +} + +static void unload_python_script(struct obs_python_script *data) +{ + PyObject *py_module = data->module; + PyObject *py_func = NULL; + PyObject *py_ret = NULL; + + cur_python_script = data; + + py_func = PyObject_GetAttrString(py_module, "script_unload"); + if (PyErr_Occurred() || !py_func) { + PyErr_Clear(); + goto fail; + } + + py_ret = PyObject_CallObject(py_func, NULL); + if (py_error()) + goto fail; + +fail: + Py_XDECREF(py_ret); + Py_XDECREF(py_func); + + cur_python_script = NULL; +} + +static void add_to_python_path(const char *path) +{ + PyObject *py_path_str = NULL; + PyObject *py_path = NULL; + int ret; + + if (!path || !*path) + return; + + for (size_t i = 0; i < python_paths.num; i++) { + const char *python_path = python_paths.array[i]; + if (strcmp(path, python_path) == 0) + return; + } + + ret = PyRun_SimpleString("import sys"); + if (py_error() || ret != 0) + goto fail; + + /* borrowed reference here */ + py_path = PySys_GetObject("path"); + if (py_error() || !py_path) + goto fail; + + py_path_str = PyUnicode_FromString(path); + ret = PyList_Append(py_path, py_path_str); + if (py_error() || ret != 0) + goto fail; + + char *new_path = bstrdup(path); + da_push_back(python_paths, &new_path); + +fail: + Py_XDECREF(py_path_str); +} + +/* -------------------------------------------- */ + +struct python_obs_timer { + struct python_obs_timer *next; + struct python_obs_timer **p_prev_next; + + uint64_t last_ts; + uint64_t interval; +}; + +static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct python_obs_timer *first_timer = NULL; + +static inline void python_obs_timer_init(struct python_obs_timer *timer) +{ + pthread_mutex_lock(&timer_mutex); + + struct python_obs_timer *next = first_timer; + timer->next = next; + timer->p_prev_next = &first_timer; + if (next) next->p_prev_next = &timer->next; + first_timer = timer; + + pthread_mutex_unlock(&timer_mutex); +} + +static inline void python_obs_timer_remove(struct python_obs_timer *timer) +{ + struct python_obs_timer *next = timer->next; + if (next) next->p_prev_next = timer->p_prev_next; + *timer->p_prev_next = timer->next; +} + +static inline struct python_obs_callback *python_obs_timer_cb( + struct python_obs_timer *timer) +{ + return &((struct python_obs_callback *)timer)[-1]; +} + +static PyObject *timer_remove(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static void timer_call(struct script_callback *p_cb) +{ + struct python_obs_callback *cb = (struct python_obs_callback *)p_cb; + + if (p_cb->removed) + return; + + lock_callback(cb); + PyObject *py_ret = PyObject_CallObject(cb->func, NULL); + py_error(); + Py_XDECREF(py_ret); + unlock_callback(); +} + +static void defer_timer_init(void *p_cb) +{ + struct python_obs_callback *cb = p_cb; + struct python_obs_timer *timer = python_obs_callback_extra_data(cb); + python_obs_timer_init(timer); +} + +static PyObject *timer_add(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb; + int ms; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "Oi", &py_cb, &ms)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback_extra( + script, py_cb, sizeof(struct python_obs_timer)); + struct python_obs_timer *timer = python_obs_callback_extra_data(cb); + + timer->interval = (uint64_t)ms * 1000000ULL; + timer->last_ts = obs_get_video_frame_time(); + + defer_call_post(defer_timer_init, cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static void obs_python_tick_callback(void *priv, float seconds) +{ + struct python_obs_callback *cb = priv; + + if (cb->base.removed) { + obs_remove_tick_callback(obs_python_tick_callback, cb); + return; + } + + lock_callback(cb); + + PyObject *args = Py_BuildValue("(f)", seconds); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + + unlock_callback(); +} + +static PyObject *obs_python_remove_tick_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static PyObject *obs_python_add_tick_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + obs_add_tick_callback(obs_python_tick_callback, cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static void calldata_signal_callback(void *priv, calldata_t *cd) +{ + struct python_obs_callback *cb = priv; + + if (cb->base.removed) { + signal_handler_remove_current(); + return; + } + + lock_callback(cb); + + PyObject *py_cd; + + if (libobs_to_py(calldata_t, cd, false, &py_cd)) { + PyObject *args = Py_BuildValue("(O)", py_cd); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_cd); + } + + unlock_callback(); +} + +static PyObject *obs_python_signal_handler_disconnect( + PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_sh = NULL; + PyObject *py_cb = NULL; + const char *signal; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + signal_handler_t *handler; + + if (!parse_args(args, "OsO", &py_sh, &signal, &py_cb)) + return python_none(); + + if (!py_to_libobs(signal_handler_t, py_sh, &handler)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + while (cb) { + signal_handler_t *cb_handler = + calldata_ptr(&cb->base.extra, "handler"); + const char *cb_signal = + calldata_string(&cb->base.extra, "signal"); + + if (cb_signal && + strcmp(signal, cb_signal) != 0 && + handler == cb_handler) + break; + + cb = find_next_python_obs_callback(script, cb, py_cb); + } + + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static PyObject *obs_python_signal_handler_connect( + PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_sh = NULL; + PyObject *py_cb = NULL; + const char *signal; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + signal_handler_t *handler; + + if (!parse_args(args, "OsO", &py_sh, &signal, &py_cb)) + return python_none(); + if (!py_to_libobs(signal_handler_t, py_sh, &handler)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + calldata_set_ptr(&cb->base.extra, "handler", handler); + calldata_set_string(&cb->base.extra, "signal", signal); + signal_handler_connect(handler, signal, calldata_signal_callback, cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static void calldata_signal_callback_global(void *priv, const char *signal, + calldata_t *cd) +{ + struct python_obs_callback *cb = priv; + + if (cb->base.removed) { + signal_handler_remove_current(); + return; + } + + lock_callback(cb); + + PyObject *py_cd; + + if (libobs_to_py(calldata_t, cd, false, &py_cd)) { + PyObject *args = Py_BuildValue("(sO)", signal, py_cd); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_cd); + } + + unlock_callback(); +} + +static PyObject *obs_python_signal_handler_disconnect_global( + PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_sh = NULL; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + signal_handler_t *handler; + + if (!parse_args(args, "OO", &py_sh, &py_cb)) + return python_none(); + + if (!py_to_libobs(signal_handler_t, py_sh, &handler)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + while (cb) { + signal_handler_t *cb_handler = + calldata_ptr(&cb->base.extra, "handler"); + + if (handler == cb_handler) + break; + + cb = find_next_python_obs_callback(script, cb, py_cb); + } + + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static PyObject *obs_python_signal_handler_connect_global( + PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_sh = NULL; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + signal_handler_t *handler; + + if (!parse_args(args, "OO", &py_sh, &py_cb)) + return python_none(); + + if (!py_to_libobs(signal_handler_t, py_sh, &handler)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + calldata_set_ptr(&cb->base.extra, "handler", handler); + signal_handler_connect_global(handler, + calldata_signal_callback_global, cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static void defer_hotkey_unregister(void *p_cb) +{ + obs_hotkey_unregister((obs_hotkey_id)(uintptr_t)p_cb); +} + +static void on_remove_hotkey(void *p_cb) +{ + struct python_obs_callback *cb = p_cb; + obs_hotkey_id id = (obs_hotkey_id)calldata_int(&cb->base.extra, "id"); + + if (id != OBS_INVALID_HOTKEY_ID) + defer_call_post(defer_hotkey_unregister, (void*)(uintptr_t)id); +} + +static void hotkey_pressed(void *p_cb, bool pressed) +{ + struct python_obs_callback *cb = p_cb; + + if (cb->base.removed) + return; + + lock_callback(cb); + + PyObject *py_pressed = PyBool_FromLong(pressed); + PyObject *args = Py_BuildValue("(O)", py_pressed); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_pressed); + + unlock_callback(); +} + +static void defer_hotkey_pressed(void *p_cb) +{ + hotkey_pressed(p_cb, true); +} + +static void defer_hotkey_unpressed(void *p_cb) +{ + hotkey_pressed(p_cb, false); +} + +static inline PyObject *py_invalid_hotkey_id() +{ + return PyLong_FromUnsignedLongLong(OBS_INVALID_HOTKEY_ID); +} + +static void hotkey_callback(void *p_cb, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + struct python_obs_callback *cb = p_cb; + + if (cb->base.removed) + return; + + if (pressed) + defer_call_post(defer_hotkey_pressed, cb); + else + defer_call_post(defer_hotkey_unpressed, cb); + + UNUSED_PARAMETER(hotkey); + UNUSED_PARAMETER(id); +} + +static PyObject *hotkey_unregister(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + if (cb) remove_python_obs_callback(cb); + + UNUSED_PARAMETER(self); + return python_none(); +} + +static PyObject *hotkey_register_frontend(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + const char *name; + const char *desc; + obs_hotkey_id id; + PyObject *py_cb; + + if (!parse_args(args, "ssO", &name, &desc, &py_cb)) + return py_invalid_hotkey_id(); + if (!py_cb || !PyFunction_Check(py_cb)) + return py_invalid_hotkey_id(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + cb->base.on_remove = on_remove_hotkey; + id = obs_hotkey_register_frontend(name, desc, hotkey_callback, cb); + calldata_set_int(&cb->base.extra, "id", id); + + if (id == OBS_INVALID_HOTKEY_ID) + remove_python_obs_callback(cb); + + UNUSED_PARAMETER(self); + return PyLong_FromUnsignedLongLong(id); +} + +/* -------------------------------------------- */ + +static bool button_prop_clicked(obs_properties_t *props, obs_property_t *p, + void *p_cb) +{ + struct python_obs_callback *cb = p_cb; + bool ret = false; + + if (cb->base.removed) + return false; + + lock_callback(cb); + + PyObject *py_props = NULL; + PyObject *py_p = NULL; + + if (libobs_to_py(obs_properties_t, props, false, &py_props) && + libobs_to_py(obs_property_t, p, false, &py_p)) { + + PyObject *args = Py_BuildValue("(OO)", py_props, py_p); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + if (!py_error()) + ret = py_ret == Py_True; + Py_XDECREF(py_ret); + Py_XDECREF(args); + } + + Py_XDECREF(py_p); + Py_XDECREF(py_props); + + unlock_callback(); + + return ret; +} + +static PyObject *properties_add_button(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + obs_properties_t *props; + obs_property_t *p; + PyObject *py_props; + PyObject *py_ret; + const char *name; + const char *text; + PyObject *py_cb; + + if (!parse_args(args, "OssO", &py_props, &name, &text, &py_cb)) + return python_none(); + if (!py_to_libobs(obs_properties_t, py_props, &props)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + p = obs_properties_add_button2(props, name, text, button_prop_clicked, + cb); + + if (!p || !libobs_to_py(obs_property_t, p, false, &py_ret)) + return python_none(); + + UNUSED_PARAMETER(self); + return py_ret; +} + +/* -------------------------------------------- */ + +static bool modified_callback(void *p_cb, obs_properties_t *props, + obs_property_t *p, obs_data_t *settings) +{ + struct python_obs_callback *cb = p_cb; + bool ret = false; + + if (cb->base.removed) + return false; + + lock_callback(cb); + + PyObject *py_props = NULL; + PyObject *py_p = NULL; + PyObject *py_settings = NULL; + + if (libobs_to_py(obs_properties_t, props, false, &py_props) && + libobs_to_py(obs_property_t, p, false, &py_p) && + libobs_to_py(obs_data_t, settings, false, &py_settings)) { + + PyObject *args = Py_BuildValue("(OOO)", py_props, py_p, + py_settings); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + if (!py_error()) + ret = py_ret == Py_True; + Py_XDECREF(py_ret); + Py_XDECREF(args); + } + + Py_XDECREF(py_settings); + Py_XDECREF(py_p); + Py_XDECREF(py_props); + + unlock_callback(); + + return ret; +} + +static PyObject *property_set_modified_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_p; + PyObject *py_cb; + obs_property_t *p; + + if (!parse_args(args, "OO", &py_p, &py_cb)) + return python_none(); + if (!py_to_libobs(obs_property_t, py_p, &p)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + obs_property_set_modified_callback2(p, modified_callback, cb); + + UNUSED_PARAMETER(self); + return python_none(); +} + +/* -------------------------------------------- */ + +static PyObject *remove_current_callback(PyObject *self, PyObject *args) +{ + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + if (cur_python_cb) + remove_python_obs_callback(cur_python_cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static PyObject *calldata_source(PyObject *self, PyObject *args) +{ + PyObject *py_ret = NULL; + PyObject *py_cd = NULL; + + calldata_t *cd; + const char *name; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "Os", &py_cd, &name)) + goto fail; + if (!py_to_libobs(calldata_t, py_cd, &cd)) + goto fail; + + obs_source_t *source = calldata_ptr(cd, name); + libobs_to_py(obs_source_t, source, false, &py_ret); + +fail: + return py_ret; +} + +static PyObject *calldata_sceneitem(PyObject *self, PyObject *args) +{ + PyObject *py_ret = NULL; + PyObject *py_cd = NULL; + + calldata_t *cd; + const char *name; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "Os", &py_cd, &name)) + goto fail; + if (!py_to_libobs(calldata_t, py_cd, &cd)) + goto fail; + + obs_sceneitem_t *item = calldata_ptr(cd, name); + libobs_to_py(obs_sceneitem_t, item, false, &py_ret); + +fail: + return py_ret; +} + +/* -------------------------------------------- */ + +static bool enum_sources_proc(void *param, obs_source_t *source) +{ + PyObject *list = param; + PyObject *py_source; + + if (libobs_to_py(obs_source_t, source, false, &py_source)) { + obs_source_get_ref(source); + PyList_Append(list, py_source); + Py_DECREF(py_source); + } + return true; +} + +static PyObject *enum_sources(PyObject *self, PyObject *args) +{ + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + PyObject *list = PyList_New(0); + obs_enum_sources(enum_sources_proc, list); + return list; +} + +/* -------------------------------------------- */ + +static bool enum_items_proc(obs_scene_t *scene, obs_sceneitem_t *item, + void *param) +{ + PyObject *list = param; + PyObject *py_item; + + UNUSED_PARAMETER(scene); + + if (libobs_to_py(obs_sceneitem_t, item, false, &py_item)) { + obs_sceneitem_addref(item); + PyList_Append(list, py_item); + Py_DECREF(py_item); + } + return true; +} + +static PyObject *scene_enum_items(PyObject *self, PyObject *args) +{ + PyObject *py_scene; + obs_scene_t *scene; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_scene)) + return python_none(); + if (!py_to_libobs(obs_scene_t, py_scene, &scene)) + return python_none(); + + PyObject *list = PyList_New(0); + obs_scene_enum_items(scene, enum_items_proc, list); + return list; +} + +/* -------------------------------------------- */ + +static PyObject *source_list_release(PyObject *self, PyObject *args) +{ + PyObject *list; + if (!parse_args(args, "O", &list)) + return python_none(); + + Py_ssize_t count = PyList_Size(list); + for (Py_ssize_t i = 0; i < count; i++) { + PyObject *py_source = PyList_GetItem(list, i); + obs_source_t *source; + + if (py_to_libobs(obs_source_t, py_source, &source)) { + obs_source_release(source); + } + } + + UNUSED_PARAMETER(self); + return python_none(); +} + +static PyObject *sceneitem_list_release(PyObject *self, PyObject *args) +{ + PyObject *list; + if (!parse_args(args, "O", &list)) + return python_none(); + + Py_ssize_t count = PyList_Size(list); + for (Py_ssize_t i = 0; i < count; i++) { + PyObject *py_item = PyList_GetItem(list, i); + obs_sceneitem_t *item; + + if (py_to_libobs(obs_sceneitem_t, py_item, &item)) { + obs_sceneitem_release(item); + } + } + + UNUSED_PARAMETER(self); + return python_none(); +} + +/* -------------------------------------------- */ + +struct dstr cur_py_log_chunk = {0}; + +static PyObject *py_script_log_internal(PyObject *self, PyObject *args, + bool add_endl) +{ + static bool calling_self = false; + int log_level; + const char *msg; + + UNUSED_PARAMETER(self); + + if (calling_self) + return python_none(); + calling_self = true; + + /* ------------------- */ + + if (!parse_args(args, "is", &log_level, &msg)) + goto fail; + if (!msg || !*msg) + goto fail; + + dstr_cat(&cur_py_log_chunk, msg); + if (add_endl) + dstr_cat(&cur_py_log_chunk, "\n"); + + const char *start = cur_py_log_chunk.array; + char *endl = strchr(start, '\n'); + + while (endl) { + *endl = 0; + if (cur_python_script) + script_log(&cur_python_script->base, log_level, "%s", + start); + else + script_log(NULL, log_level, "%s", start); + *endl = '\n'; + + start = endl + 1; + endl = strchr(start, '\n'); + } + + if (start) { + size_t len = strlen(start); + if (len) memmove(cur_py_log_chunk.array, start, len); + dstr_resize(&cur_py_log_chunk, len); + } + + /* ------------------- */ + +fail: + calling_self = false; + return python_none(); +} + +static PyObject *py_script_log_no_endl(PyObject *self, PyObject *args) +{ + return py_script_log_internal(self, args, false); +} + +static PyObject *py_script_log(PyObject *self, PyObject *args) +{ + return py_script_log_internal(self, args, true); +} + +/* -------------------------------------------- */ + +static void add_hook_functions(PyObject *module) +{ + static PyMethodDef funcs[] = { +#define DEF_FUNC(n, c) {n, c, METH_VARARGS, NULL} + + DEF_FUNC("script_log_no_endl", py_script_log_no_endl), + DEF_FUNC("script_log", py_script_log), + DEF_FUNC("timer_remove", timer_remove), + DEF_FUNC("timer_add", timer_add), + DEF_FUNC("calldata_source", calldata_source), + DEF_FUNC("calldata_sceneitem", calldata_sceneitem), + DEF_FUNC("source_list_release", source_list_release), + DEF_FUNC("sceneitem_list_release", sceneitem_list_release), + DEF_FUNC("obs_enum_sources", enum_sources), + DEF_FUNC("obs_scene_enum_items", scene_enum_items), + DEF_FUNC("obs_remove_tick_callback", + obs_python_remove_tick_callback), + DEF_FUNC("obs_add_tick_callback", + obs_python_add_tick_callback), + DEF_FUNC("signal_handler_disconnect", + obs_python_signal_handler_disconnect), + DEF_FUNC("signal_handler_connect", + obs_python_signal_handler_connect), + DEF_FUNC("signal_handler_disconnect_global", + obs_python_signal_handler_disconnect_global), + DEF_FUNC("signal_handler_connect_global", + obs_python_signal_handler_connect_global), + DEF_FUNC("obs_hotkey_unregister", + hotkey_unregister), + DEF_FUNC("obs_hotkey_register_frontend", + hotkey_register_frontend), + DEF_FUNC("obs_properties_add_button", + properties_add_button), + DEF_FUNC("obs_property_set_modified_callback", + property_set_modified_callback), + DEF_FUNC("remove_current_callback", + remove_current_callback), + +#undef DEF_FUNC + {0} + }; + + add_functions_to_py_module(module, funcs); +} + +/* -------------------------------------------- */ + +void obs_python_script_update(obs_script_t *script, obs_data_t *settings); + +bool obs_python_script_load(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + if (python_loaded && !data->base.loaded) { + lock_python(); + if (!data->module) + add_to_python_path(data->dir.array); + data->base.loaded = load_python_script(data); + unlock_python(); + + if (data->base.loaded) + obs_python_script_update(s, NULL); + } + + return data->base.loaded; +} + +obs_script_t *obs_python_script_create(const char *path, obs_data_t *settings) +{ + struct obs_python_script *data = bzalloc(sizeof(*data)); + + data->base.type = OBS_SCRIPT_LANG_PYTHON; + + dstr_copy(&data->base.path, path); + dstr_replace(&data->base.path, "\\", "/"); + path = data->base.path.array; + + const char *slash = path && *path ? strrchr(path, '/') : NULL; + if (slash) { + slash++; + dstr_copy(&data->base.file, slash); + dstr_left(&data->dir, &data->base.path, slash - path); + } else { + dstr_copy(&data->base.file, path); + } + + path = data->base.file.array; + dstr_copy_dstr(&data->name, &data->base.file); + + const char *ext = strstr(path, ".py"); + if (ext) + dstr_resize(&data->name, ext - path); + + data->base.settings = obs_data_create(); + if (settings) + obs_data_apply(data->base.settings, settings); + + if (!python_loaded) + return (obs_script_t *)data; + + lock_python(); + add_to_python_path(data->dir.array); + data->base.loaded = load_python_script(data); + if (data->base.loaded) { + cur_python_script = data; + obs_python_script_update(&data->base, NULL); + cur_python_script = NULL; + } + unlock_python(); + + return (obs_script_t *)data; +} + +void obs_python_script_unload(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + + if (!s->loaded || !python_loaded) + return; + + /* ---------------------------- */ + /* unhook tick function */ + + if (data->p_prev_next_tick) { + pthread_mutex_lock(&tick_mutex); + + struct obs_python_script *next = data->next_tick; + if (next) next->p_prev_next_tick = data->p_prev_next_tick; + *data->p_prev_next_tick = next; + + pthread_mutex_unlock(&tick_mutex); + + data->p_prev_next_tick = NULL; + data->next_tick = NULL; + } + + lock_python(); + + Py_XDECREF(data->tick); + Py_XDECREF(data->save); + Py_XDECREF(data->update); + Py_XDECREF(data->get_properties); + data->tick = NULL; + data->save = NULL; + data->update = NULL; + data->get_properties = NULL; + + /* ---------------------------- */ + /* remove all callbacks */ + + struct script_callback *cb = data->first_callback; + while (cb) { + struct script_callback *next = cb->next; + remove_script_callback(cb); + cb = next; + } + + /* ---------------------------- */ + /* unload */ + + unload_python_script(data); + unlock_python(); + + s->loaded = false; +} + +void obs_python_script_destroy(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + + if (data) { + if (python_loaded) { + lock_python(); + Py_XDECREF(data->module); + unlock_python(); + } + + dstr_free(&data->base.path); + dstr_free(&data->base.file); + dstr_free(&data->base.desc); + obs_data_release(data->base.settings); + dstr_free(&data->dir); + dstr_free(&data->name); + bfree(data); + } +} + +void obs_python_script_update(obs_script_t *s, obs_data_t *settings) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + + if (!s->loaded || !python_loaded) + return; + if (!data->update) + return; + + if (settings) + obs_data_apply(s->settings, settings); + + lock_python(); + cur_python_script = data; + + PyObject *py_settings; + if (libobs_to_py(obs_data_t, s->settings, false, &py_settings)) { + PyObject *args = Py_BuildValue("(O)", py_settings); + PyObject *ret = PyObject_CallObject(data->update, args); + py_error(); + + Py_XDECREF(ret); + Py_XDECREF(args); + Py_XDECREF(py_settings); + } + + cur_python_script = NULL; + unlock_python(); +} + +obs_properties_t *obs_python_script_get_properties(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + obs_properties_t *props = NULL; + + if (!s->loaded || !python_loaded) + return NULL; + if (!data->get_properties) + return NULL; + + lock_python(); + cur_python_script = data; + + PyObject *ret = PyObject_CallObject(data->get_properties, NULL); + if (!py_error()) + py_to_libobs(obs_properties_t, ret, &props); + Py_XDECREF(ret); + + cur_python_script = NULL; + unlock_python(); + + return props; +} + +void obs_python_script_save(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + + if (!s->loaded || !python_loaded) + return; + if (!data->save) + return; + + lock_python(); + cur_python_script = data; + + PyObject *py_settings; + if (libobs_to_py(obs_data_t, s->settings, false, &py_settings)) { + PyObject *args = Py_BuildValue("(O)", py_settings); + PyObject *ret = PyObject_CallObject(data->save, args); + py_error(); + Py_XDECREF(ret); + Py_XDECREF(args); + Py_XDECREF(py_settings); + } + + cur_python_script = NULL; + unlock_python(); +} + +/* -------------------------------------------- */ + +static void python_tick(void *param, float seconds) +{ + struct obs_python_script *data; + bool valid; + uint64_t ts = obs_get_video_frame_time(); + + pthread_mutex_lock(&tick_mutex); + valid = !!first_tick_script; + pthread_mutex_unlock(&tick_mutex); + + /* --------------------------------- */ + /* process script_tick calls */ + + if (valid) { + lock_python(); + + PyObject *args = Py_BuildValue("(f)", seconds); + + pthread_mutex_lock(&tick_mutex); + data = first_tick_script; + while (data) { + cur_python_script = data; + + PyObject *py_ret = PyObject_CallObject(data->tick, args); + Py_XDECREF(py_ret); + py_error(); + + data = data->next_tick; + } + + cur_python_script = NULL; + + pthread_mutex_unlock(&tick_mutex); + + Py_XDECREF(args); + + unlock_python(); + } + + /* --------------------------------- */ + /* process timers */ + + pthread_mutex_lock(&timer_mutex); + struct python_obs_timer *timer = first_timer; + while (timer) { + struct python_obs_timer *next = timer->next; + struct python_obs_callback *cb = python_obs_timer_cb(timer); + + if (cb->base.removed) { + python_obs_timer_remove(timer); + } else { + uint64_t elapsed = ts - timer->last_ts; + + if (elapsed >= timer->interval) { + lock_python(); + timer_call(&cb->base); + unlock_python(); + + timer->last_ts += timer->interval; + } + } + + timer = next; + } + pthread_mutex_unlock(&timer_mutex); + + UNUSED_PARAMETER(param); +} + +/* -------------------------------------------- */ + +void obs_python_unload(void); + +bool obs_scripting_python_runtime_linked(void) +{ + return (bool)RUNTIME_LINK; +} + +bool obs_scripting_python_loaded(void) +{ + return python_loaded; +} + +void obs_python_load(void) +{ + da_init(python_paths); + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&tick_mutex, NULL); + pthread_mutex_init(&timer_mutex, &attr); + +} + +extern void add_python_frontend_funcs(PyObject *module); + +bool obs_scripting_load_python(const char *python_path) +{ + if (python_loaded) + return true; + + /* Use external python on windows and mac */ +#if RUNTIME_LINK +# if 0 + struct dstr old_path = {0}; + struct dstr new_path = {0}; +# endif + + if (!import_python(python_path)) + return false; + + if (python_path && *python_path) { + os_utf8_to_wcs(python_path, 0, home_path, 1024); + Py_SetPythonHome(home_path); +# if 0 + dstr_copy(&old_path, getenv("PATH")); + _putenv("PYTHONPATH="); + _putenv("PATH="); +# endif + } +#else + UNUSED_PARAMETER(python_path); +#endif + + Py_Initialize(); + if (!Py_IsInitialized()) + return false; + +#if 0 +# ifdef _DEBUG + if (pythondir && *pythondir) { + dstr_printf(&new_path, "PATH=%s", old_path.array); + _putenv(new_path.array); + } +# endif + + bfree(pythondir); + dstr_free(&new_path); + dstr_free(&old_path); +#endif + + PyEval_InitThreads(); + if (!PyEval_ThreadsInitialized()) + return false; + + /* ---------------------------------------------- */ + /* Must set arguments for guis to work */ + + wchar_t *argv[] = {L"", NULL}; + int argc = sizeof(argv) / sizeof(wchar_t*) - 1; + + PySys_SetArgv(argc, argv); + +#ifdef DEBUG_PYTHON_STARTUP + /* ---------------------------------------------- */ + /* Debug logging to file if startup is failing */ + + PyRun_SimpleString("import os"); + PyRun_SimpleString("import sys"); + PyRun_SimpleString("os.environ['PYTHONUNBUFFERED'] = '1'"); + PyRun_SimpleString("sys.stdout = open('./stdOut.txt','w',1)"); + PyRun_SimpleString("sys.stderr = open('./stdErr.txt','w',1)"); + PyRun_SimpleString("print(sys.version)"); +#endif + + /* ---------------------------------------------- */ + /* Load main interface module */ + + add_to_python_path(SCRIPT_DIR); + + py_obspython = PyImport_ImportModule("obspython"); + bool success = !py_error(); + if (!success) { + warn("Error importing obspython.py', unloading obs-python"); + goto out; + } + + python_loaded = PyRun_SimpleString(startup_script) == 0; + py_error(); + + add_hook_functions(py_obspython); + py_error(); + + add_python_frontend_funcs(py_obspython); + py_error(); + +out: + /* ---------------------------------------------- */ + /* Free data */ + + PyEval_ReleaseThread(PyGILState_GetThisThreadState()); + + if (!success) { + warn("Failed to load python plugin"); + obs_python_unload(); + } + + if (python_loaded) + obs_add_tick_callback(python_tick, NULL); + + return python_loaded; +} + +void obs_python_unload(void) +{ + if (python_loaded && Py_IsInitialized()) { + PyGILState_Ensure(); + + Py_XDECREF(py_obspython); + Py_Finalize(); + } + + /* ---------------------- */ + + obs_remove_tick_callback(python_tick, NULL); + + for (size_t i = 0; i < python_paths.num; i++) + bfree(python_paths.array[i]); + da_free(python_paths); + + pthread_mutex_destroy(&tick_mutex); + pthread_mutex_destroy(&timer_mutex); + dstr_free(&cur_py_log_chunk); +} diff --git a/deps/obs-scripting/obs-scripting-python.h b/deps/obs-scripting/obs-scripting-python.h new file mode 100644 index 0000000..15bb286 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python.h @@ -0,0 +1,244 @@ +/****************************************************************************** + Copyright (C) 2015 by Andrew Skinner + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +/* ---------------------------- */ + +#define SWIG_TYPE_TABLE obspython +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) +#pragma warning(disable : 4115) +#pragma warning(disable : 4204) +#endif + +#include "obs-scripting-python-import.h" + +#include +#include "swig/swigpyrun.h" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +/* ---------------------------- */ + +#include "obs-scripting-internal.h" +#include "obs-scripting-callback.h" + +#ifdef _WIN32 +#define __func__ __FUNCTION__ +#else +#include +#endif + +#include +#include +#include + +#define do_log(level, format, ...) \ + blog(level, "[Python] " format, ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) +#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) + +/* ------------------------------------------------------------ */ + +struct python_obs_callback; + +struct obs_python_script { + obs_script_t base; + + struct dstr dir; + struct dstr name; + + PyObject *module; + + PyObject *save; + PyObject *update; + PyObject *get_properties; + + struct script_callback *first_callback; + + PyObject *tick; + struct obs_python_script *next_tick; + struct obs_python_script **p_prev_next_tick; +}; + +/* ------------------------------------------------------------ */ + +struct python_obs_callback { + struct script_callback base; + + PyObject *func; +}; + +static inline struct python_obs_callback *add_python_obs_callback_extra( + struct obs_python_script *script, + PyObject *func, + size_t extra_size) +{ + struct python_obs_callback *cb = add_script_callback( + &script->first_callback, + (obs_script_t *)script, + sizeof(*cb) + extra_size); + + Py_XINCREF(func); + cb->func = func; + return cb; +} + +static inline struct python_obs_callback *add_python_obs_callback( + struct obs_python_script *script, + PyObject *func) +{ + return add_python_obs_callback_extra(script, func, 0); +} + +static inline void *python_obs_callback_extra_data( + struct python_obs_callback *cb) +{ + return (void*)&cb[1]; +} + +static inline struct obs_python_script *python_obs_callback_script( + struct python_obs_callback *cb) +{ + return (struct obs_python_script *)cb->base.script; +} + +static inline struct python_obs_callback *find_next_python_obs_callback( + struct obs_python_script *script, + struct python_obs_callback *cb, PyObject *func) +{ + cb = cb ? (struct python_obs_callback *)cb->base.next + : (struct python_obs_callback *)script->first_callback; + + while (cb) { + if (cb->func == func) + break; + cb = (struct python_obs_callback *)cb->base.next; + } + + return cb; +} + +static inline struct python_obs_callback *find_python_obs_callback( + struct obs_python_script *script, + PyObject *func) +{ + return find_next_python_obs_callback(script, NULL, func); +} + +static inline void remove_python_obs_callback(struct python_obs_callback *cb) +{ + remove_script_callback(&cb->base); + + Py_XDECREF(cb->func); + cb->func = NULL; +} + +static inline void just_free_python_obs_callback(struct python_obs_callback *cb) +{ + just_free_script_callback(&cb->base); +} + +static inline void free_python_obs_callback(struct python_obs_callback *cb) +{ + free_script_callback(&cb->base); +} + +/* ------------------------------------------------------------ */ + +static int parse_args_(PyObject *args, const char *func, const char *format, ...) +{ + char new_format[128]; + va_list va_args; + int ret; + + snprintf(new_format, sizeof(new_format), "%s:%s", format, func); + + va_start(va_args, format); + ret = PyArg_VaParse(args, new_format, va_args); + va_end(va_args); + + return ret; +} + +#define parse_args(args, format, ...) \ + parse_args_(args, __FUNCTION__, format, ##__VA_ARGS__) + +static inline bool py_error_(const char *func, int line) +{ + if (PyErr_Occurred()) { + warn("Python failure in %s:%d:", func, line); + PyErr_Print(); + return true; + } + return false; +} + +#define py_error() py_error_(__FUNCTION__, __LINE__) + +#define lock_python() \ + PyGILState_STATE gstate = PyGILState_Ensure() +#define unlock_python() \ + PyGILState_Release(gstate) + +struct py_source; +typedef struct py_source py_source_t; + +extern PyObject* py_libobs; +extern struct python_obs_callback *cur_python_cb; +extern struct obs_python_script *cur_python_script; + +extern void py_to_obs_source_info(py_source_t *py_info); +extern PyObject *py_obs_register_source(PyObject *self, PyObject *args); +extern PyObject *py_obs_get_script_config_path(PyObject *self, PyObject *args); +extern void add_functions_to_py_module(PyObject *module, + PyMethodDef *method_list); + +/* ------------------------------------------------------------ */ +/* Warning: the following functions expect python to be locked! */ + +extern bool py_to_libobs_(const char *type, + PyObject * py_in, + void * libobs_out, + const char *id, + const char *func, + int line); + +extern bool libobs_to_py_(const char *type, + void * libobs_in, + bool ownership, + PyObject ** py_out, + const char *id, + const char *func, + int line); + +extern bool py_call(PyObject *call, PyObject **ret, const char *arg_def, ...); +extern bool py_import_script(const char *name); + +static inline PyObject *python_none(void) +{ + PyObject *none = Py_None; + Py_INCREF(none); + return none; +} diff --git a/deps/obs-scripting/obs-scripting.c b/deps/obs-scripting/obs-scripting.c new file mode 100644 index 0000000..d9fc068 --- /dev/null +++ b/deps/obs-scripting/obs-scripting.c @@ -0,0 +1,457 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include +#include +#include +#include + +#include "obs-scripting-internal.h" +#include "obs-scripting-callback.h" +#include "obs-scripting-config.h" + +#if COMPILE_LUA +extern obs_script_t *obs_lua_script_create(const char *path, + obs_data_t *settings); +extern bool obs_lua_script_load(obs_script_t *s); +extern void obs_lua_script_unload(obs_script_t *s); +extern void obs_lua_script_destroy(obs_script_t *s); +extern void obs_lua_load(void); +extern void obs_lua_unload(void); + +extern obs_properties_t *obs_lua_script_get_properties(obs_script_t *script); +extern void obs_lua_script_update(obs_script_t *script, obs_data_t *settings); +extern void obs_lua_script_save(obs_script_t *script); +#endif + +#if COMPILE_PYTHON +extern obs_script_t *obs_python_script_create(const char *path, + obs_data_t *settings); +extern bool obs_python_script_load(obs_script_t *s); +extern void obs_python_script_unload(obs_script_t *s); +extern void obs_python_script_destroy(obs_script_t *s); +extern void obs_python_load(void); +extern void obs_python_unload(void); + +extern obs_properties_t *obs_python_script_get_properties(obs_script_t *script); +extern void obs_python_script_update(obs_script_t *script, obs_data_t *settings); +extern void obs_python_script_save(obs_script_t *script); +#endif + +pthread_mutex_t detach_mutex; +struct script_callback *detached_callbacks; + +static struct dstr file_filter = {0}; +static bool scripting_loaded = false; + +static const char *supported_formats[] = { +#if COMPILE_LUA + "lua", +#endif +#if COMPILE_PYTHON + "py", +#endif + NULL +}; + +/* -------------------------------------------- */ + +static pthread_mutex_t defer_call_mutex; +static struct circlebuf defer_call_queue; +static bool defer_call_exit = false; +static os_sem_t *defer_call_semaphore; +static pthread_t defer_call_thread; + +struct defer_call { + defer_call_cb call; + void *cb; +}; + +static void *defer_thread(void *unused) +{ + UNUSED_PARAMETER(unused); + + while (os_sem_wait(defer_call_semaphore) == 0) { + struct defer_call info; + + pthread_mutex_lock(&defer_call_mutex); + if (defer_call_exit) { + pthread_mutex_unlock(&defer_call_mutex); + return NULL; + } + + circlebuf_pop_front(&defer_call_queue, &info, sizeof(info)); + pthread_mutex_unlock(&defer_call_mutex); + + info.call(info.cb); + } + + return NULL; +} + +void defer_call_post(defer_call_cb call, void *cb) +{ + struct defer_call info; + info.call = call; + info.cb = cb; + + pthread_mutex_lock(&defer_call_mutex); + if (!defer_call_exit) + circlebuf_push_back(&defer_call_queue, &info, sizeof(info)); + pthread_mutex_unlock(&defer_call_mutex); + + os_sem_post(defer_call_semaphore); +} + +/* -------------------------------------------- */ + +bool obs_scripting_load(void) +{ + circlebuf_init(&defer_call_queue); + + if (pthread_mutex_init(&detach_mutex, NULL) != 0) { + return false; + } + if (pthread_mutex_init(&defer_call_mutex, NULL) != 0) { + pthread_mutex_destroy(&detach_mutex); + return false; + } + if (os_sem_init(&defer_call_semaphore, 0) != 0) { + pthread_mutex_destroy(&defer_call_mutex); + pthread_mutex_destroy(&detach_mutex); + return false; + } + + if (pthread_create(&defer_call_thread, NULL, defer_thread, NULL) != 0) { + os_sem_destroy(defer_call_semaphore); + pthread_mutex_destroy(&defer_call_mutex); + pthread_mutex_destroy(&detach_mutex); + return false; + } + +#if COMPILE_LUA + obs_lua_load(); +#endif + +#if COMPILE_PYTHON + obs_python_load(); +#ifndef _WIN32 /* don't risk python startup load issues on windows */ + obs_scripting_load_python(NULL); +#endif +#endif + + scripting_loaded = true; + return true; +} + +void obs_scripting_unload(void) +{ + if (!scripting_loaded) + return; + + /* ---------------------- */ + +#if COMPILE_LUA + obs_lua_unload(); +#endif + +#if COMPILE_PYTHON + obs_python_unload(); +#endif + + dstr_free(&file_filter); + + /* ---------------------- */ + + int total_detached = 0; + + pthread_mutex_lock(&detach_mutex); + + struct script_callback *cur = detached_callbacks; + while (cur) { + struct script_callback *next = cur->next; + just_free_script_callback(cur); + cur = next; + + ++total_detached; + } + + pthread_mutex_unlock(&detach_mutex); + pthread_mutex_destroy(&detach_mutex); + + blog(LOG_INFO, "[Scripting] Total detached callbacks: %d", + total_detached); + + /* ---------------------- */ + + pthread_mutex_lock(&defer_call_mutex); + + /* TODO */ + + defer_call_exit = true; + circlebuf_free(&defer_call_queue); + + pthread_mutex_unlock(&defer_call_mutex); + + os_sem_post(defer_call_semaphore); + pthread_join(defer_call_thread, NULL); + + pthread_mutex_destroy(&defer_call_mutex); + os_sem_destroy(defer_call_semaphore); +} + +const char **obs_scripting_supported_formats(void) +{ + return supported_formats; +} + +static inline bool pointer_valid(const void *x, const char *name, + const char *func) +{ + if (!x) { + blog(LOG_WARNING, "obs-scripting: [%s] %s is null", + func, name); + return false; + } + + return true; +} + +#define ptr_valid(x) pointer_valid(x, #x, __FUNCTION__) + +obs_script_t *obs_script_create(const char *path, obs_data_t *settings) +{ + obs_script_t *script = NULL; + const char *ext; + + if (!scripting_loaded) + return NULL; + if (!ptr_valid(path)) + return NULL; + + ext = strrchr(path, '.'); + if (!ext) + return NULL; + +#if COMPILE_LUA + if (strcmp(ext, ".lua") == 0) { + script = obs_lua_script_create(path, settings); + } else +#endif +#if COMPILE_PYTHON + if (strcmp(ext, ".py") == 0) { + script = obs_python_script_create(path, settings); + } else +#endif + { + blog(LOG_WARNING, "Unsupported/unknown script type: %s", path); + } + + return script; +} + +const char *obs_script_get_description(const obs_script_t *script) +{ + return ptr_valid(script) ? script->desc.array : NULL; +} + +const char *obs_script_get_path(const obs_script_t *script) +{ + const char *path = ptr_valid(script) ? script->path.array : ""; + return path ? path : ""; +} + +const char *obs_script_get_file(const obs_script_t *script) +{ + const char *file = ptr_valid(script) ? script->file.array : ""; + return file ? file : ""; +} + +enum obs_script_lang obs_script_get_lang(const obs_script_t *script) +{ + return ptr_valid(script) ? script->type : OBS_SCRIPT_LANG_UNKNOWN; +} + +obs_data_t *obs_script_get_settings(obs_script_t *script) +{ + obs_data_t *settings; + + if (!ptr_valid(script)) + return NULL; + + settings = script->settings; + obs_data_addref(settings); + return settings; +} + +obs_properties_t *obs_script_get_properties(obs_script_t *script) +{ + obs_properties_t *props = NULL; + + if (!ptr_valid(script)) + return NULL; +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + props = obs_lua_script_get_properties(script); + goto out; + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + props = obs_python_script_get_properties(script); + goto out; + } +#endif + +out: + if (!props) + props = obs_properties_create(); + return props; +} + +obs_data_t *obs_script_save(obs_script_t *script) +{ + obs_data_t *settings; + + if (!ptr_valid(script)) + return NULL; + +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + obs_lua_script_save(script); + goto out; + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + obs_python_script_save(script); + goto out; + } +#endif + +out: + settings = script->settings; + obs_data_addref(settings); + return settings; +} + +static void clear_queue_signal(void *p_event) +{ + os_event_t *event = p_event; + os_event_signal(event); +} + +static void clear_call_queue(void) +{ + os_event_t *event; + if (os_event_init(&event, OS_EVENT_TYPE_AUTO) != 0) + return; + + defer_call_post(clear_queue_signal, event); + + os_event_wait(event); + os_event_destroy(event); +} + +void obs_script_update(obs_script_t *script, obs_data_t *settings) +{ + if (!ptr_valid(script)) + return; +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + obs_lua_script_update(script, settings); + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + obs_python_script_update(script, settings); + } +#endif +} + +bool obs_script_reload(obs_script_t *script) +{ + if (!scripting_loaded) + return false; + if (!ptr_valid(script)) + return false; + +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + obs_lua_script_unload(script); + clear_call_queue(); + obs_lua_script_load(script); + goto out; + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + obs_python_script_unload(script); + clear_call_queue(); + obs_python_script_load(script); + goto out; + } +#endif + +out: + return script->loaded; +} + +bool obs_script_loaded(const obs_script_t *script) +{ + return ptr_valid(script) ? script->loaded : false; +} + +void obs_script_destroy(obs_script_t *script) +{ + if (!script) + return; + +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + obs_lua_script_unload(script); + obs_lua_script_destroy(script); + return; + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + obs_python_script_unload(script); + obs_python_script_destroy(script); + return; + } +#endif +} + +#if !COMPILE_PYTHON +bool obs_scripting_load_python(const char *python_path) +{ + UNUSED_PARAMETER(python_path); + return false; +} + +bool obs_scripting_python_loaded(void) +{ + return false; +} + +bool obs_scripting_python_runtime_linked(void) +{ + return (bool)true; +} +#endif diff --git a/deps/obs-scripting/obs-scripting.h b/deps/obs-scripting/obs-scripting.h new file mode 100644 index 0000000..d0e0b90 --- /dev/null +++ b/deps/obs-scripting/obs-scripting.h @@ -0,0 +1,73 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct obs_script; +typedef struct obs_script obs_script_t; + +enum obs_script_lang { + OBS_SCRIPT_LANG_UNKNOWN, + OBS_SCRIPT_LANG_LUA, + OBS_SCRIPT_LANG_PYTHON +}; + +EXPORT bool obs_scripting_load(void); +EXPORT void obs_scripting_unload(void); +EXPORT const char **obs_scripting_supported_formats(void); + +typedef void (*scripting_log_handler_t)( + void *p, + obs_script_t *script, + int lvl, + const char *msg); + +EXPORT void obs_scripting_set_log_callback( + scripting_log_handler_t handler, void *param); + +EXPORT bool obs_scripting_python_runtime_linked(void); +EXPORT bool obs_scripting_python_loaded(void); +EXPORT bool obs_scripting_load_python(const char *python_path); + +EXPORT obs_script_t *obs_script_create(const char *path, obs_data_t *settings); +EXPORT void obs_script_destroy(obs_script_t *script); + +EXPORT const char *obs_script_get_description(const obs_script_t *script); +EXPORT const char *obs_script_get_path(const obs_script_t *script); +EXPORT const char *obs_script_get_file(const obs_script_t *script); +EXPORT enum obs_script_lang obs_script_get_lang(const obs_script_t *script); + +EXPORT obs_properties_t *obs_script_get_properties(obs_script_t *script); +EXPORT obs_data_t *obs_script_save(obs_script_t *script); +EXPORT obs_data_t *obs_script_get_settings(obs_script_t *script); +EXPORT void obs_script_update(obs_script_t *script, obs_data_t *settings); + +EXPORT bool obs_script_loaded(const obs_script_t *script); +EXPORT bool obs_script_reload(obs_script_t *script); + +#ifdef __cplusplus +} +#endif diff --git a/deps/obs-scripting/obslua/CMakeLists.txt b/deps/obs-scripting/obslua/CMakeLists.txt new file mode 100644 index 0000000..eb30a88 --- /dev/null +++ b/deps/obs-scripting/obslua/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 2.8) +project(obslua) + +find_package(SWIG 2 REQUIRED) +include(${SWIG_USE_FILE}) + +add_definitions(-DSWIG_TYPE_TABLE=obslua -DSWIG_LUA_INTERPRETER_NO_DEBUG) + +if(MSVC) + add_compile_options("/wd4054") + add_compile_options("/wd4197") + add_compile_options("/wd4244") + add_compile_options("/wd4267") +endif() + +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +SWIG_ADD_MODULE(obslua lua obslua.i ../cstrcache.cpp ../cstrcache.h) +SWIG_LINK_LIBRARIES(obslua obs-scripting libobs ${LUA_LIBRARIES} ${EXTRA_LIBS}) + +function(install_plugin_bin_swig target additional_target) + if(APPLE) + set(_bit_suffix "") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_bit_suffix "64bit/") + else() + set(_bit_suffix "32bit/") + endif() + + set_target_properties(${additional_target} PROPERTIES + PREFIX "") + + install(TARGETS "${additional_target}" + LIBRARY DESTINATION "${OBS_SCRIPT_PLUGIN_DESTINATION}") + + add_custom_command(TARGET ${additional_target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + "${OBS_OUTPUT_DIR}/$/data/obs-scripting/${_bit_suffix}$" + VERBATIM) +endfunction() + +install_plugin_bin_swig(obs-scripting obslua) diff --git a/deps/obs-scripting/obslua/obslua.i b/deps/obs-scripting/obslua/obslua.i new file mode 100644 index 0000000..b71f2ac --- /dev/null +++ b/deps/obs-scripting/obslua/obslua.i @@ -0,0 +1,101 @@ +%module obslua +%{ +#define SWIG_FILE_WITH_INIT +#define DEPRECATED_START +#define DEPRECATED_END +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cstrcache.h" +#include "obs-scripting-config.h" + +#if UI_ENABLED +#include "obs-frontend-api.h" +#endif + +%} + +#define DEPRECATED_START +#define DEPRECATED_END +#define EXPORT + +%rename(blog) wrap_blog; +%inline %{ +static inline void wrap_blog(int log_level, const char *message) +{ + blog(log_level, "%s", message); +} +%} + +%include "stdint.i" + +/* Used to free when using %newobject functions. E.G.: + * %newobject obs_module_get_config_path; */ +%typemap(newfree) char * "bfree($1);"; + +%ignore blog; +%ignore blogva; +%ignore bcrash; +%ignore obs_source_info; +%ignore obs_register_source_s(const struct obs_source_info *info, size_t size); +%ignore obs_output_set_video(obs_output_t *output, video_t *video); +%ignore obs_output_video(const obs_output_t *output); +%ignore obs_add_tick_callback; +%ignore obs_remove_tick_callback; +%ignore obs_add_main_render_callback; +%ignore obs_remove_main_render_callback; +%ignore obs_enum_sources; +%ignore obs_properties_add_button; +%ignore obs_property_set_modified_callback; +%ignore signal_handler_connect; +%ignore signal_handler_disconnect; +%ignore signal_handler_connect_global; +%ignore signal_handler_disconnect_global; +%ignore signal_handler_remove_current; +%ignore obs_hotkey_register_frontend; +%ignore obs_hotkey_register_encoder; +%ignore obs_hotkey_register_output; +%ignore obs_hotkey_register_service; +%ignore obs_hotkey_register_source; +%ignore obs_hotkey_pair_register_frontend; +%ignore obs_hotkey_pair_register_encoder; +%ignore obs_hotkey_pair_register_output; +%ignore obs_hotkey_pair_register_service; +%ignore obs_hotkey_pair_register_source; + +%include "graphics/graphics.h" +%include "graphics/vec4.h" +%include "graphics/vec3.h" +%include "graphics/vec2.h" +%include "graphics/quat.h" +%include "graphics/image-file.h" +%include "obs-data.h" +%include "obs-source.h" +%include "obs-properties.h" +%include "obs-interaction.h" +%include "obs-hotkey.h" +%include "obs.h" +%include "callback/calldata.h" +%include "callback/proc.h" +%include "callback/signal.h" +%include "util/bmem.h" +%include "util/base.h" +%include "obs-scripting-config.h" + +#if UI_ENABLED +%include "obs-frontend-api.h" +#endif diff --git a/deps/obs-scripting/obspython/CMakeLists.txt b/deps/obs-scripting/obspython/CMakeLists.txt new file mode 100644 index 0000000..f5475e9 --- /dev/null +++ b/deps/obs-scripting/obspython/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 2.8) +project(obspython) + +find_package(SWIG 2 REQUIRED) +include(${SWIG_USE_FILE}) + +add_definitions(-DSWIG_TYPE_TABLE=obspython -DMS_NO_COREDLL -DPy_ENABLE_SHARED=1 -DSWIG_PYTHON_INTERPRETER_NO_DEBUG) + +if(MSVC) + add_compile_options("/wd4054") + add_compile_options("/wd4100") + add_compile_options("/wd4115") + add_compile_options("/wd4197") + add_compile_options("/wd4701") +endif() + +include_directories(${PYTHON_INCLUDE_DIR}) +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +#add_definitions( -DSWIG_TYPE_TABLE=libobs ) +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-modern") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-builtin") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-modernargs") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-includeall") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-importall") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-py3") + +if(WIN32) + string(REGEX REPLACE "_d" "" PYTHON_LIBRARIES "${PYTHON_LIBRARIES}") +endif() + +SWIG_ADD_MODULE(obspython python obspython.i ../cstrcache.cpp ../cstrcache.h) +SWIG_LINK_LIBRARIES(obspython obs-scripting libobs ${PYTHON_LIBRARIES}) + +function(install_plugin_bin_swig target additional_target) + if(APPLE) + set(_bit_suffix "") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_bit_suffix "64bit/") + else() + set(_bit_suffix "32bit/") + endif() + + set_target_properties(${additional_target} PROPERTIES + PREFIX "") + + if (APPLE) + set_property( + TARGET ${additional_target} + APPEND + PROPERTY INSTALL_RPATH + "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/" + "/Library/Frameworks/Python.framework/Versions/3.6/lib/" + "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/" + ) + endif() + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/obspython.py" + DESTINATION "${OBS_SCRIPT_PLUGIN_DESTINATION}") + install(TARGETS "${additional_target}" + LIBRARY DESTINATION "${OBS_SCRIPT_PLUGIN_DESTINATION}") + + add_custom_command(TARGET ${additional_target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_CURRENT_BINARY_DIR}/obspython.py" + "${OBS_OUTPUT_DIR}/$/data/obs-scripting/${_bit_suffix}/obspython.py" + VERBATIM) + add_custom_command(TARGET ${additional_target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + "${OBS_OUTPUT_DIR}/$/data/obs-scripting/${_bit_suffix}$" + VERBATIM) +endfunction() + +install_plugin_bin_swig(obs-scripting _obspython) diff --git a/deps/obs-scripting/obspython/obspython.i b/deps/obs-scripting/obspython/obspython.i new file mode 100644 index 0000000..3c71413 --- /dev/null +++ b/deps/obs-scripting/obspython/obspython.i @@ -0,0 +1,106 @@ +%module(threads="1") obspython +%nothread; +%{ +#define SWIG_FILE_WITH_INIT +#define DEPRECATED_START +#define DEPRECATED_END +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "obs-scripting-config.h" + +#if UI_ENABLED +#include "obs-frontend-api.h" +#endif + +%} + +#define DEPRECATED_START +#define DEPRECATED_END +#define EXPORT + +%rename(blog) wrap_blog; +%inline %{ +static inline void wrap_blog(int log_level, const char *message) +{ + blog(log_level, "%s", message); +} +%} + +%include "stdint.i" + +/* Used to free when using %newobject functions. E.G.: + * %newobject obs_module_get_config_path; */ +%typemap(newfree) char * "bfree($1);"; + +%ignore blog; +%ignore blogva; +%ignore bcrash; +%ignore obs_source_info; +%ignore obs_register_source_s(const struct obs_source_info *info, size_t size); +%ignore obs_output_set_video(obs_output_t *output, video_t *video); +%ignore obs_output_video(const obs_output_t *output); +%ignore obs_add_tick_callback; +%ignore obs_remove_tick_callback; +%ignore obs_add_main_render_callback; +%ignore obs_remove_main_render_callback; +%ignore obs_enum_sources; +%ignore obs_properties_add_button; +%ignore obs_property_set_modified_callback; +%ignore signal_handler_connect; +%ignore signal_handler_disconnect; +%ignore signal_handler_connect_global; +%ignore signal_handler_disconnect_global; +%ignore signal_handler_remove_current; +%ignore obs_hotkey_register_frontend; +%ignore obs_hotkey_register_encoder; +%ignore obs_hotkey_register_output; +%ignore obs_hotkey_register_service; +%ignore obs_hotkey_register_source; +%ignore obs_hotkey_pair_register_frontend; +%ignore obs_hotkey_pair_register_encoder; +%ignore obs_hotkey_pair_register_output; +%ignore obs_hotkey_pair_register_service; +%ignore obs_hotkey_pair_register_source; + +%include "graphics/graphics.h" +%include "graphics/vec4.h" +%include "graphics/vec3.h" +%include "graphics/vec2.h" +%include "graphics/quat.h" +%include "obs-data.h" +%include "obs-source.h" +%include "obs-properties.h" +%include "obs-interaction.h" +%include "obs-hotkey.h" +%include "obs.h" +%include "callback/calldata.h" +%include "callback/proc.h" +%include "callback/signal.h" +%include "util/bmem.h" +%include "util/base.h" +%include "obs-scripting-config.h" + +#if UI_ENABLED +%include "obs-frontend-api.h" +#endif + +/* declare these manually because mutex + GIL = deadlocks */ +%thread; +void obs_enter_graphics(void); //Should only block on entering mutex +%nothread; +%include "obs.h" diff --git a/docs/sphinx/Makefile b/docs/sphinx/Makefile new file mode 100644 index 0000000..6a2b501 --- /dev/null +++ b/docs/sphinx/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = OBSStudio +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/sphinx/_build/.gitignore b/docs/sphinx/_build/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/docs/sphinx/_static/.gitignore b/docs/sphinx/_static/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/docs/sphinx/_templates/.gitignore b/docs/sphinx/_templates/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/docs/sphinx/backend-design.rst b/docs/sphinx/backend-design.rst new file mode 100644 index 0000000..8602bed --- /dev/null +++ b/docs/sphinx/backend-design.rst @@ -0,0 +1,194 @@ +OBS Studio Backend Design +========================= +The OBS Studio backend is powered by the library libobs. Libobs +provides the main pipeline, the video/audio subsystems, and the general +framework for all plugins. + + +Libobs Plugin Objects +--------------------- +Libobs is designed to be modular, where adding modules will add custom +functionality. There are four libobs objects that you can make plugins +for: + +- :ref:`plugins_sources` -- Sources are used to render video and/or + audio on stream. Things such as capturing displays/games/audio, + playing a video, showing an image, or playing audio. Sources can also + be used to implement audio and video filters. + +- :ref:`plugins_outputs` -- Outputs allow the ability to output the + currently rendering audio/video. Streaming and recording are two + common examples of outputs, but not the only types of outputs. + Outputs can receive the raw data or receive encoded data. + +- :ref:`plugins_encoders` -- Encoders are OBS-specific implementations + of video/audio encoders, which are used with outputs that use + encoders. x264, NVENC, Quicksync are examples of encoder + implementations. + +- :ref:`plugins_services` -- Services are custom implementations of + streaming services, which are used with outputs that stream. For + example, you could have a custom implementation for streaming to + Twitch, and another for YouTube to allow the ability to log in and use + their APIs to do things such as get the RTMP servers or control the + channel. + +*(Author's note: the service API is incomplete as of this writing)* + + +Libobs Threads +-------------- +There are three primary threads spawned by libobs on initialization: + +- The obs_graphics_thread_ function used exclusively for rendering in + `libobs/obs-video.c`_ + +- The video_thread_ function used exclusively for video encoding/output + in `libobs/media-io/video-io.c`_ + +- The audio_thread_ function used for all audio + processing/encoding/output in `libobs/media-io/audio-io.c`_ + +*(Author's note: obs_graphics_thread was originally named +obs_video_thread; it was renamed as of this writing to prevent confusion +with video_thread)* + + +.. _output_channels: + +Output Channels +--------------- +Rendering video or audio starts from output channels. You assign a +source to an output channel via the :c:func:`obs_set_output_source()` +function. The *channel* parameter can be any number from +0..(MAX_CHANNELS_-1). You may initially think that this is how you +display multiple sources at once; however, sources are hierarchical. +Sources such as scenes or transitions can have multiple sub-sources, and +those sub-sources in turn can have sub-sources and so on (see +:ref:`displaying_sources` for more information). Typically, you would +use scenes to draw multiple sources as a group with specific transforms +for each source, as a scene is just another type of source. The +"channel" design allows for highly complex video presentation setups. +The OBS Studio front-end has yet to even fully utilize this back-end +design for its rendering, and currently only uses one output channel to +render one scene at a time. It does however utilize additional channels +for things such as global audio sources which are set in audio settings. + +*(Author's note: "Output channels" are not to be confused with output +objects or audio channels. Output channels are used to set the sources +you want to output, and output objects are used for actually +streaming/recording/etc.)* + + +General Video Pipeline Overview +------------------------------- +The video graphics pipeline is run from two threads: a dedicated +graphics thread that renders preview displays as well as the final mix +(the obs_graphics_thread_ function in `libobs/obs-video.c`_), and a +dedicated thread specific to video encoding/output (the video_thread_ +function in `libobs/media-io/video-io.c`_). + +Sources assigned to output channels will be drawn from channels +0..(MAX_CHANNELS_-1). They are drawn on to the final texture which will +be used for output `[1]`_. Once all sources are drawn, the final +texture is converted to whatever format that libobs is set to (typically +a YUV format). After being converted to the back-end video format, it's +then sent along with its timestamp to the current video handler, +`obs_core_video::video`_. + +It then puts that raw frame in a queue of MAX_CACHE_SIZE_ in the `video +output handler`_. A semaphore is posted, then the video-io thread will +process frames as it's able. If the video frame queue is full, it will +duplicate the last frame in the queue in an attempt to reduce video +encoding complexity (and thus CPU usage) `[2]`_. This is why you may +see frame skipping when the encoder can't keep up. Frames are sent to +any raw outputs or video encoders that are currently active `[3]`_. + +If it's sent to a video encoder object (`libobs/obs-encoder.c`_), it +encodes the frame and sends the encoded packet off to the outputs that +encoder is connected to (which can be multiple). If the output takes +both encoded video/audio, it puts the packets in an interleave queue to +ensure encoded packets are sent in monotonic timestamp order `[4]`_. + +The encoded packet or raw frame is then sent to the output. + + +General Audio Pipeline Overview +------------------------------- +The audio pipeline is run from a dedicated audio thread in the audio +handler (the `audio_thread`_ function in `libobs/media-io/audio-io.c`_); +assuming that AUDIO_OUTPUT_FRAMES_ is set to 1024, the audio thread +"ticks" (processes audio data) once every 1024 audio samples (around +every 21 millisecond intervals at 48khz), and calls the audio_callback_ +function in `libobs/obs-audio.c`_ where most of the audio processing is +accomplished. + +A source with audio will output its audio via the +obs_source_output_audio_ function, and that audio data will be appended +or inserted in to the circular buffer `obs_source::audio_input_buf`_. +If the sample rate or channel count does not match what the back-end is +set to, the audio is automatically remixed/resampled via swresample +`[5]`_. Before insertion, audio data is also run through any audio +filters attached to the source `[6]`_. + +Each audio tick, the audio thread takes a reference snapshot of the +audio source tree (stores references of all sources that output/process +audio) `[7]`_. On each audio leaf (audio source), it takes the closest +audio (relative to the current audio thread timestamp) stored in the +circular buffer `obs_source::audio_input_buf`_, and puts it in +`obs_source::audio_output_buf`_. + +Then, the audio samples stored in `obs_source::audio_output_buf`_ of the +leaves get sent through their parents in the source tree snapshot for +mixing or processing at each source node in the hierarchy `[8]`_. +Sources with multiple children such as scenes or transitions will +mix/process their children's audio themselves via the +`obs_source_info::audio_render`_ callback. This allows, for example, +transitions to fade in the audio of one source and fade in the audio of +a new source when they're transitioning between two sources. The mix or +processed audio data is then stored in `obs_source::audio_output_buf`_ +of that node similarly, and the process is repeated until the audio +reaches the root nodes of the tree. + +Finally, when the audio has reached the base of the snapshot tree, the +audio of all the sources in each output channel are mixed together for a +final mix `[9]`_. That final mix is then sent to any raw outputs or +audio encoders that are currently active `[10]`_. + +If it's sent to an audio encoder object (`libobs/obs-encoder.c`_), it +encodes the audio data and sends the encoded packet off to the outputs +that encoder is connected to (which can be multiple). If the output +takes both encoded video/audio, it puts the packets in an interleave +queue to ensure encoded packets are sent in monotonic timestamp order +`[4]`_. + +The encoded packet or raw audio data is then sent to the output. + +.. _obs_graphics_thread: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-video.c#L588-L651 +.. _libobs/obs-audio.c: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-audio.c +.. _libobs/obs-video.c: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-video.c +.. _video_thread: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/media-io/video-io.c#L169-L195 +.. _libobs/media-io/video-io.c: https://github.com/jp9000/obs-studio/blob/master/libobs/media-io/video-io.c +.. _video output handler: https://github.com/jp9000/obs-studio/blob/master/libobs/media-io/video-io.c +.. _audio_thread: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/media-io/audio-io.c#L241-L282 +.. _libobs/media-io/audio-io.c: https://github.com/jp9000/obs-studio/blob/master/libobs/media-io/audio-io.c +.. _MAX_CHANNELS: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-defs.h#L20-L21 +.. _[1]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-video.c#L99-L129 +.. _obs_core_video::video: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-internal.h#L250 +.. _MAX_CACHE_SIZE: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/media-io/video-io.c#L34 +.. _[2]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/media-io/video-io.c#L431-L434 +.. _[3]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/media-io/video-io.c#L115-L167 +.. _libobs/obs-encoder.c: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-encoder.c +.. _[4]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-output.c#L1382-L1439 +.. _AUDIO_OUTPUT_FRAMES: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/media-io/audio-io.h#L30 +.. _audio_callback: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-audio.c#L367-L485 +.. _obs_source_output_audio: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-source.c#L2578-L2608 +.. _obs_source::audio_input_buf: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-source.c#L1280-L1283 +.. _[5]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-source.c#L2561-L2563 +.. _[6]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-source.c#L2591 +.. _[7]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-audio.c#L393-L415 +.. _obs_source::audio_output_buf: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-internal.h#L580 +.. _[8]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-audio.c#L417-L423 +.. _obs_source_info::audio_render: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-source.h#L410-L412 +.. _[9]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/obs-audio.c#L436-L453 +.. _[10]: https://github.com/jp9000/obs-studio/blob/2c58185af3c85f4e594a4c067c9dfe5fa4b5b0a9/libobs/media-io/audio-io.c#L144-L165 diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py new file mode 100644 index 0000000..47ee2ec --- /dev/null +++ b/docs/sphinx/conf.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# OBS Studio documentation build configuration file, created by +# sphinx-quickstart on Wed Oct 25 00:03:21 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +primary_domain = 'c' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'OBS Studio' +copyright = '2017, Hugh Bailey' +author = 'Hugh Bailey' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '20.1.0' +# The full version, including alpha/beta/rc tags. +release = '20.1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'bizstyle' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'OBSStudiodoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'OBSStudio.tex', 'OBS Studio Documentation', + 'Hugh Bailey', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'obsstudio', 'OBS Studio Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'OBSStudio', 'OBS Studio Documentation', + author, 'OBSStudio', 'One line description of project.', + 'Miscellaneous'), +] + + + diff --git a/docs/sphinx/frontends.rst b/docs/sphinx/frontends.rst new file mode 100644 index 0000000..b375a2e --- /dev/null +++ b/docs/sphinx/frontends.rst @@ -0,0 +1,252 @@ +Frontends +========= + +Initialization and Shutdown +--------------------------- + +To initialize libobs, you must call :c:func:`obs_startup()`, +:c:func:`obs_reset_video()`, and then :c:func:`obs_reset_audio()`. +After that, modules typically should be loaded. + +You can load individual modules manually by calling +:c:func:`obs_open_module()`. After loading, the +:c:func:`obs_open_module()` function, you must then call +:c:func:`obs_init_module()` to initialize the module. + +You can load modules automatically via two functions: +:c:func:`obs_add_module_path()` and :c:func:`obs_load_all_modules()`. + +After all plugin modules have been loaded, call +:c:func:`obs_post_load_modules()`. + +Certain modules may optionally use a configuration storage directory, +which is set as a parameter to :c:func:`obs_startup()`. + +When it's time to shut down the frontend, make sure to release all +references to any objects, free any data, and then call +:c:func:`obs_shutdown()`. If for some reason any libobs objects have +not been released, they will be destroyed automatically and a warning +will be logged. + +To detect if any general memory allocations have not been freed, call +the :c:func:`bnum_allocs()` to get the number of allocations remaining. +If the number remaining is above 0, there are memory leaks. + +See :ref:`obs_init_shutdown_reference` for more information. + + +Reconfiguring Video +------------------- + +Any time after initialization, video settings can be reconfigured by +calling :c:func:`obs_reset_video()` as long as no outputs are active. +Audio was originally intended to have this capability as well, but +currently is not able to be reset once initialized; libobs must be fully +shutdown in order to reconfigure audio settings. + + +Displays +-------- + +Displays as the name implies are used for display/preview panes. To use +displays, you must have a native window handle or identifier to draw on. + +First you must call :c:func:`obs_display_create()` to initialize the +display, then you must assign a draw callback with +:c:func:`obs_display_add_draw_callback()`. If you need to remove a draw +callback, call :c:func:`obs_display_remove_draw_callback()` similarly. + +When drawing, to draw the main preview window (if any), call +:c:func:`obs_render_main_texture()`. If you need to render a specific +source on a secondary display, you can increment its "showing" state +with :c:func:`obs_source_inc_showing()` while it's showing in the +secondary display, draw it with :c:func:`obs_source_video_render()` in +the draw callback, then when it's no longer showing in the secondary +display, call :c:func:`obs_source_dec_showing()`. + +If the display needs to be resized, call :c:func:`obs_display_resize()`. + +If the display needs a custom background color other than black, call +:c:func:`obs_display_set_background_color()`. + +If the display needs to be temporarily disabled, call +:c:func:`obs_display_set_enabled()` to disable, and +:c:func:`obs_display_enabled()` to get its enabled/disabled state. + +Then call :c:func:`obs_display_destroy()` to destroy the display when +it's no longer needed. + +*(Important note: do not use more than one display widget within the +hierarchy of the same base window; this will cause presentation stalls +on Macs.)* + +For an example of how displays are used with Qt, see +`UI/qt-display.hpp`_ and `UI/qt-display.cpp`_. + +See :ref:`display_reference` for more information. + + +Saving/Loading Objects and Object Management +-------------------------------------------- + +The frontend is generally expected to manage its own objects, however +for sources, there are some helper functions to allow easier +saving/loading all sources: :c:func:`obs_save_sources()` and +:c:func:`obs_load_sources()`. With those functions, all sources that +aren't private will automatically be saved and loaded. You can also +save/load individual sources manually by using +:c:func:`obs_save_source()` and :c:func:`obs_load_source()`. + +*(Author's note: I should not have written those helper functions; the +downside is I had to add "private" sources that aren't savable via the* +:c:func:`obs_source_create_private()` *function. Just one of the many +minor design flaws that can occur during long-term development.)* + +For outputs, encoders, and services, there are no helper functions, so +usually you'd get their settings individually and save them as json. +(See :c:func:`obs_output_get_settings()`). You don't have to save each +object to different files individually; you'd save multiple objects +together in a bigger :c:type:`obs_data_t` object, then save that via +:c:func:`obs_data_save_json_safe()`, then load everything again via +:c:func:`obs_data_create_from_json_file_safe()`. + + +Signals +------- + +The core, as well as scenes and sources, have a set of standard signals +that are used to determine when something happens or changes. + +Typically the most important signals are the +:ref:`output_signal_handler_reference`: the **start**, **stop**, +**starting**, **stopping**, **reconnect**, **reconnect_success** +signals in particular. + +Most other signals for scenes/sources are optional if you are the only +thing controlling their state. However, it's generally recommended to +watch most signals when possible for consistency. See +:ref:`source_signal_handler_reference` and :ref:`scene_signal_reference` +for more information. + +For example, let's say you wanted to connect a callback to the **stop** +signal of an output. The **stop** signal has two parameters: *output* +and *code*. A callback for this signal would typically look something +like this: + +.. code:: cpp + + static void output_stopped(void *my_data, calldata_t *cd) + { + obs_output_t *output = calldata_ptr(cd, "output"); + int code = calldata_int(cd, "code"); + + [...] + } + +*(Note that callbacks are not thread-safe.)* + +Then to connect it to the **stop** signal, you use the +:c:func:`signal_handler_connect()` with the callback. In this case for +example: + +.. code:: cpp + + signal_handler_t *handler = obs_output_get_signal_handler(output); + signal_handler_connect(handler, "stop", output_stopped); + + +.. _displaying_sources: + +Displaying Sources +------------------ + +Sources are displayed on stream/recording via :ref:`output_channels` +with the :c:func:`obs_set_output_source()` function. There are 64 +channels that you can assign sources to, which will draw on top of each +other in ascending index order. Typically, a normal source shouldn't be +directly assigned with this function; you would use a scene or a +transition containing scenes. + +To draw one or more sources together with a specific transform applied +to them, scenes are used. To create a scene, you call +:c:func:`obs_scene_create()`. Child sources are referenced using scene +items, and then specific transforms are applied to those scene items. +Scene items are not sources but containers for sources; the same source +can be referenced by multiple scene items within the same scene, or can +be referenced in multiple scenes. To create a scene item that +references a source, you call :c:func:`obs_scene_add()`, which returns a +new reference to a scene item. + +To change the transform of a scene item, you typically would call a +function like :c:func:`obs_sceneitem_set_pos()` to change its position, +:c:func:`obs_sceneitem_set_rot()` to change its rotation, or +:c:func:`obs_sceneitem_set_scale()` to change its scaling. Scene items +can also force scaling in to a custom size constraint referred to as a +"bounding box"; a bounding box will force the source to be drawn at a +specific size and with specific scaling constraint within that size. To +use a bounding box, you call the +:c:func:`obs_sceneitem_set_bounds_type()`, +:c:func:`obs_sceneitem_set_bounds()`, and +:c:func:`obs_sceneitem_set_bounds_alignment()`. Though the easiest way +to handle everything related to transforms is to use the +:c:func:`obs_sceneitem_set_info()` and +:c:func:`obs_sceneitem_get_info()` functions. See +:ref:`scene_item_reference` for all the functions related to scene +items. + +Usually, a smooth transition between multiple scenes is required. To do +this, transitions are used. To create a transition, you use +:c:func:`obs_source_create()` or :c:func:`obs_source_create_private()` +like any other source. Then, to activate a transition, you call +:c:func:`obs_transition_start()`. When the transition is not active and +is only displaying one source, it performs a pass-through to the current +displaying source. See :ref:`transitions` for more functions related to +using transitions. + +The recommended way to set up your structure is to have a transition as +the source that is used as the main output source, then your scene as a +child of the transition, then your sources as children in the scene. +When you need to switch to a new scene, simply call +:c:func:`obs_transition_start()`. + + +Outputs, Encoders, and Services +------------------------------- + +Outputs, encoders, and services are all used together, and managed a bit +differently than sources. There currently is no global function to +save/load them, that must be accomplished manually for now via their +settings if needed. + +Encoders are used with outputs that expect encoded data (which is almost +all typical outputs), such as standard file recording or streaming. + +Services are used with outputs to a stream; the `RTMP output`_ is the +quintessential example of this. + +Here's an example of how an output would be used with encoders and +services: + +.. code:: cpp + + obs_encoder_set_video(my_h264_encoder, obs_get_video()); + obs_encoder_set_audio(my_aac_encoder, obs_get_audio()); + obs_output_set_video_encoder(my_output, my_h264_encoder); + obs_output_set_audio_encoder(my_output, my_aac_encoder); + obs_output_set_service(my_output, my_service); /* if a stream */ + obs_output_start(my_output); + +Once the output has started successfully, it automatically starts +capturing the video and/or audio from the current video/audio output +(i.e. any sources that are assigned to the :ref:`output_channels`). + +If the output fails to start up, it will send the **stop** signal with +an error code in the *code* parameter, possibly accompanied by a +translated error message stored that can be obtained via the +:c:func:`obs_output_get_last_error()` function. + +.. -------------------------------------------------------------------- + +.. _RTMP Output: https://github.com/jp9000/obs-studio/blob/master/plugins/obs-outputs/rtmp-stream.c +.. _UI/qt-display.hpp: https://github.com/jp9000/obs-studio/blob/master/UI/qt-display.hpp +.. _UI/qt-display.cpp: https://github.com/jp9000/obs-studio/blob/master/UI/qt-display.cpp diff --git a/docs/sphinx/graphics.rst b/docs/sphinx/graphics.rst new file mode 100644 index 0000000..c2c9184 --- /dev/null +++ b/docs/sphinx/graphics.rst @@ -0,0 +1,364 @@ +Rendering Graphics +================== + +Libobs has a custom-made programmable graphics subsystem that wraps both +Direct3D 11 and OpenGL. The reason why it was designed with a custom +graphics subsystem was to accommodate custom capture features only +available on specific operating systems. + +*(Author's note: In retrospect, I probably should have used something +like ANGLE, but I would have to modify it to accommodate my specific +use-cases.)* + +Most rendering is dependent upon effects. Effects are used by all video +objects in libobs; they're used to easily bundle related vertex/pixel +shaders in to one file. + +An effect file has a nearly identical syntax to Direct3D 11 HLSL effect +files. The only differences are as follows: + +- Sampler states are named "sampler_state" +- Position semantic is called "POSITION" rather than "SV_Position" +- Target semantic is called "TARGET" rather than "SV_Target" + +*(Author's note: I'm probably missing a few exceptions here, if I am +please let me know)* + + +The Graphics Context +-------------------- + +Using graphics functions isn't possible unless the current thread has +entered a graphics context, and the graphics context can only be used by +one thread at a time. To enter the graphics context, use +:c:func:`obs_enter_graphics()`, and to leave the graphics context, use +:c:func:`obs_leave_graphics()`. + +Certain callback will automatically be within the graphics context: +:c:member:`obs_source_info.video_render`, and the draw callback +parameter of :c:func:`obs_display_add_draw_callback()`, and +:c:func:`obs_add_main_render_callback()`. + + +Creating Effects +---------------- + +Effect Parameters +^^^^^^^^^^^^^^^^^ + +To create an effect, it's recommended to start with the uniforms +(parameters) of the effect. + +There are a number of different types of uniforms: + ++------------------+---------------+------------------+------------+------------+ +| Floating points: | **float** | **float2** | **float3** | **float4** | ++------------------+---------------+------------------+------------+------------+ +| Matrices: | **float3x3** | **float4x4** | | | ++------------------+---------------+------------------+------------+------------+ +| Integers: | **int** | **int2** | **int3** | **int4** | ++------------------+---------------+------------------+------------+------------+ +| Booleans: | **bool** | | | | ++------------------+---------------+------------------+------------+------------+ +| Textures: | **texture2d** | **texture_cube** | | | ++------------------+---------------+------------------+------------+------------+ + +To get the effect uniform parameters, you use +:c:func:`gs_effect_get_param_by_name()` or +:c:func:`gs_effect_get_param_by_idx()`. + +Then the uniforms are set through the following functions: + +- :c:func:`gs_effect_set_bool()` +- :c:func:`gs_effect_set_float()` +- :c:func:`gs_effect_set_int()` +- :c:func:`gs_effect_set_matrix4()` +- :c:func:`gs_effect_set_vec2()` +- :c:func:`gs_effect_set_vec3()` +- :c:func:`gs_effect_set_vec4()` +- :c:func:`gs_effect_set_texture()` + +There are two "universal" effect parameters that may be expected of +effects: **ViewProj**, and **image**. The **ViewProj** parameter +(which is a float4x4) is used for the primary view/projection matrix +combination. The **image** parameter (which is a texture2d) is a +commonly used parameter for the main texture; this parameter will be +used with the functions :c:func:`obs_source_draw()`, +:c:func:`gs_draw_sprite()`, and +:c:func:`obs_source_process_filter_end()`. + +Here is an example of effect parameters: + +.. code:: cpp + + uniform float4x4 ViewProj; + uniform texture2d image; + + uniform float4 my_color_param; + uniform float my_float_param; + +Effect parameters can also have default values. Default parameters of +elements that have multiple elements should be treated as an array. + +Here are some examples of default parameters: + +.. code:: cpp + + uniform float4x4 my_matrix = {1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0}; + + uniform float4 my_float4 = {1.0, 0.5, 0.25, 0.0}; + uniform float my_float = 4.0; + uniform int my_int = 5; + +Effect Sampler States +^^^^^^^^^^^^^^^^^^^^^ + +Then, if textures are used, sampler states should be defined. Sampler +states have certain sub-parameters: + +- **Filter** - The type of filtering to use. Can be one of the + following values: + + - **Anisotropy** + - **Point** + - **Linear** + - **MIN_MAG_POINT_MIP_LINEAR** + - **MIN_POINT_MAG_LINEAR_MIP_POINT** + - **MIN_POINT_MAG_MIP_LINEAR** + - **MIN_LINEAR_MAG_MIP_POINT** + - **MIN_LINEAR_MAG_POINT_MIP_LINEAR** + - **MIN_MAG_LINEAR_MIP_POINT** + +- **AddressU**, **AddressV** - Specifies how to handle the sampling + when the coordinate goes beyond 0.0..1.0. Can be one of the following + values: + + - **Wrap** or **Repeat** + - **Clamp** or **None** + - **Mirror** + - **Border** (uses *BorderColor* to fill the color) + - **MirrorOnce** + +- **BorderColor** - Specifies the border color if using the "Border" + address mode. This value should be a hexadecimal value representing + the color, in the format of: AARRGGBB. For example, 7FFF0000 would + have its alpha value at 127, its red value at 255, and blue and green + at 0. If *Border* is not used as an addressing type, this value is + ignored. + +Here is an example of writing a sampler state in an effect file: + +.. code:: cpp + + sampler_state defaultSampler { + Filter = Linear; + AddressU = Border; + AddressV = Border; + BorderColor = 7FFF0000; + }; + +This sampler state would use linear filtering, would use border +addressing for texture coordinate values beyond 0.0..1.0, and the border +color would be the color specified above. + +When a sampler state is used, it's used identically to the HLSL form: + +.. code:: cpp + + [...] + + uniform texture2d image; + + sampler_state defaultSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; + }; + + [...] + + float4 MyPixelShaderFunc(VertInOut vert_in) : TARGET + { + return image.Sample(def_sampler, vert_in.uv); + } + +Effect Vertex/Pixel Semantics +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Then structures should be defined for inputs and outputs vertex +semantics. + +Vertex components can have the following semantics: + +- **COLOR** - Color value (*float4*). +- **POSITION** - Position value (*float4*). +- **NORMAL** - Normal value (*float4*). +- **TANGENT** - Tangent value (*float4*). +- **TEXCOORD[0..7]** - Texture cooordinate value (*float2*, *float3*, or + *float4*). + +Here is an example of a vertex semantic structure: + +.. code:: cpp + + struct VertexIn { + float4 my_position : POSITION; + float2 my_texcoord : TEXCOORD0; + }; + +These semantic structures are then passed in as a parameter to the +primary shader entry point, and used as a return value for the vertex +shader. Note that the vertex shader is allowed to return different +semantics than it takes in; but the return type of the vertex shader and +the parameter of the pixel shader must match. + +The semantic structure used for the parameter to the vertex shader +function will require that the vertex buffer have those values, so if +you have POSITION and TEXCOORD0, the vertex buffer will have to have at +least a position buffer and a texture coordinate buffer in it. + +For pixel shaders, they need to return with a TARGET semantic (which is +a float4 RGBA value). Here is an example of how it's usually used with +a pixel shader function: + +.. code:: cpp + + float4 MyPixelShaderFunc(VertInOut vert_in) : TARGET + { + return image.Sample(def_sampler, vert_in.uv); + } + + +Effect Techniques +^^^^^^^^^^^^^^^^^ + +Techniques are used to define the primary vertex/pixel shader entry +functions per pass. One technique can have multiple passes or custom +pass setup. + +*(Author's note: These days, multiple passes aren't really needed; GPUs +are powerful enough to where you can perform all actions in the same +shader. Named passes can be useful for custom draw setups, but even +then you can just make it a separate technique. For that reason, it's +best to just ignore the extra pass functionality.)* + +If you're making an effect filter for video sources, typically you'd +name the pass **Draw**, and then +:c:func:`obs_source_process_filter_end()` will automatically call that +specific effect name. However, you can also use +:c:func:`obs_source_process_filter_tech_end()` to make the filter use a +specific technique by its name. + +The first parameter of the vertex/pixel shader functions in passes +should always be the name of its vertex semantic structure parameter. + +For techniques, it's better to show some examples of how techniques +would be used: + +.. code:: cpp + + uniform float4x4 ViewProj; + uniform texture2d image; + + struct VertInOut { + float4 my_position : POSITION; + float2 my_texcoord : TEXCOORD0; + }; + + VertInOut MyVertexShaderFunc(VertInOut vert_in) + { + VertInOut vert_out; + vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = vert_in.uv; + return vert_out; + } + + float4 MyPixelShaderFunc(VertInOut vert_in) : TARGET + { + return image.Sample(def_sampler, vert_in.uv); + } + + technique Draw + { + pass + { + vertex_shader = MyVertexShaderFunc(vert_in); + pixel_shader = MyPixelShaderFunc(vert_in); + } + }; + +Using Effects +------------- + +The recommended way to use effects is like so: + +.. code:: cpp + + for (gs_effect_loop(effect, "technique")) { + [draw calls go here] + } + +This will automatically handle loading/unloading of the effect and its +shaders for a given technique name. + + +Rendering Video Sources +----------------------- + +A synchronous video source renders in its +:c:member:`obs_source_info.video_render` callback. + +Sources can render with custom drawing (via the OBS_SOURCE_CUSTOM_DRAW +output capability flag), or without. When sources render without custom +rendering, it's recommended to render a single texture with +:c:func:`obs_source_draw()`. Otherwise the source is expected to +perform rendering on its own and manage its own effects. + +Libobs comes with a set of default/standard effects that can be accessed +via the :c:func:`obs_get_base_effect()` function. You can use these +effects to render, or you can create custom effects with +:c:func:`gs_effect_create_from_file()` and render with a custom effect. + + +Rendering Video Effect Filters +------------------------------ + +For most video effect filters, it comprises of adding a layer of +processing shaders to an existing image in its +:c:member:`obs_source_info.video_render` callback. When this is the +case, it's expected that the filter has its own effect created, and to +draw the effect, one would simply use the +:c:func:`obs_source_process_filter_begin()` function, set the parameters +on your custom effect, then call either +:c:func:`obs_source_process_filter_end()` or +:c:func:`obs_source_process_filter_tech_end()` to finish rendering the +filter. + +Here's an example of rendering a filter from the color key filter: + +.. code:: cpp + + static void color_key_render(void *data, gs_effect_t *effect) + { + struct color_key_filter_data *filter = data; + + if (!obs_source_process_filter_begin(filter->context, GS_RGBA, + OBS_ALLOW_DIRECT_RENDERING)) + return; + + gs_effect_set_vec4(filter->color_param, &filter->color); + gs_effect_set_float(filter->contrast_param, filter->contrast); + gs_effect_set_float(filter->brightness_param, filter->brightness); + gs_effect_set_float(filter->gamma_param, filter->gamma); + gs_effect_set_vec4(filter->key_color_param, &filter->key_color); + gs_effect_set_float(filter->similarity_param, filter->similarity); + gs_effect_set_float(filter->smoothness_param, filter->smoothness); + + obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + + UNUSED_PARAMETER(effect); + } + diff --git a/docs/sphinx/index.rst b/docs/sphinx/index.rst new file mode 100644 index 0000000..fd1008d --- /dev/null +++ b/docs/sphinx/index.rst @@ -0,0 +1,26 @@ +.. OBS Studio documentation master file, created by + sphinx-quickstart on Wed Oct 25 00:03:21 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. _contents: + +Welcome to OBS Studio's documentation! +====================================== + +.. toctree:: + :maxdepth: 3 + + backend-design + plugins + frontends + graphics + scripting + reference-core + reference-modules + reference-core-objects + reference-libobs-util + reference-libobs-callback + reference-libobs-graphics + reference-libobs-media-io + reference-frontend-api diff --git a/docs/sphinx/make.bat b/docs/sphinx/make.bat new file mode 100644 index 0000000..9635336 --- /dev/null +++ b/docs/sphinx/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=OBSStudio + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/sphinx/plugins.rst b/docs/sphinx/plugins.rst new file mode 100644 index 0000000..2a6dd71 --- /dev/null +++ b/docs/sphinx/plugins.rst @@ -0,0 +1,569 @@ +Plugins +======= +Almost all custom functionality is added through plugin modules, which +are typically dynamic libraries or scripts. The ability to capture +and/or output audio/video, make a recording, output to an RTMP stream, +encode in x264 are all examples of things that are accomplished via +plugin modules. + +Plugins can implement sources, outputs, encoders, and services. + + +Plugin Module Headers +--------------------- +These are some notable headers commonly used by plugins: + +- `libobs/obs-module.h`_ -- The primary header used for creating plugin + modules. This file automatically includes the following files: + + - `libobs/obs.h`_ -- The main libobs header. This file automatically + includes the following files: + + - `libobs/obs-source.h`_ -- Used for implementing sources in plugin + modules + + - `libobs/obs-output.h`_ -- Used for implementing outputs in plugin + modules + + - `libobs/obs-encoder.h`_ -- Used for implementing encoders in + plugin modules + + - `libobs/obs-service.h`_ -- Used for implementing services in + plugin modules + + - `libobs/obs-data.h`_ -- Used for managing settings for libobs + objects + + - `libobs/obs-properties.h`_ -- Used for generating properties for + libobs objects + + - `libobs/graphics/graphics.h`_ -- Used for graphics rendering + + +Common Directory Structure and CMakeLists.txt +--------------------------------------------- +The common way source files are organized is to have one file for plugin +initialization, and then specific files for each individual object +you're implementing. For example, if you were to create a plugin called +'my-plugin', you'd have something like my-plugin.c where plugin +initialization is done, my-source.c for the definition of a custom +source, my-output.c for the definition of a custom output, etc. (This +is not a rule of course) + +This is an example of a common directory structure for a native plugin +module:: + + my-plugin/data/locale/en-US.ini + my-plugin/CMakeLists.txt + my-plugin/my-plugin.c + my-plugin/my-source.c + my-plugin/my-output.c + my-plugin/my-encoder.c + my-plugin/my-service.c + +This would be an example of a common CMakeLists.txt file associated with +these files:: + + # my-plugin/CMakeLists.txt + + project(my-plugin) + + set(my-plugin_SOURCES + my-plugin.c + my-source.c + my-output.c + my-encoder.c + my-service.c) + + add_library(my-plugin MODULE + ${my-plugin_SOURCES}) + target_link_libraries(my-plugin + libobs) + + install_obs_plugin_with_data(my-plugin data) + + +Native Plugin Initialization +---------------------------- +To create a native plugin module, you will need to include the +`libobs/obs-module.h`_ header, use :c:func:`OBS_DECLARE_MODULE()` macro, +then create a definition of the function :c:func:`obs_module_load()`. +In your :c:func:`obs_module_load()` function, you then register any of +your custom sources, outputs, encoders, or services. See the +:doc:`reference-modules` for more information. + +The following is an example of my-plugin.c, which would register one +object of each type: + +.. code:: cpp + + /* my-plugin.c */ + #include + + /* Defines common functions (required) */ + OBS_DECLARE_MODULE() + + /* Implements common ini-based locale (optional) */ + OBS_MODULE_USE_DEFAULT_LOCALE("my-plugin", "en-US") + + extern struct obs_source_info my_source; /* Defined in my-source.c */ + extern struct obs_output_info my_output; /* Defined in my-output.c */ + extern struct obs_encoder_info my_encoder; /* Defined in my-encoder.c */ + extern struct obs_service_info my_service; /* Defined in my-service.c */ + + bool obs_module_load(void) + { + obs_register_source(&my_source); + obs_register_output(&my_output); + obs_register_encoder(&my_encoder); + obs_register_service(&my_service); + return true; + } + + +.. _plugins_sources: + +Sources +------- +Sources are used to render video and/or audio on stream. Things such as +capturing displays/games/audio, playing a video, showing an image, or +playing audio. Sources can also be used to implement audio and video +filters as well as transitions. The `libobs/obs-source.h`_ file is the +dedicated header for implementing sources. See the +:doc:`reference-sources` for more information. + +For example, to implement a source object, you need to define an +:c:type:`obs_source_info` structure and fill it out with information and +callbacks related to your source: + +.. code:: cpp + + /* my-source.c */ + + [...] + + struct obs_source_info my_source { + .id = "my_source", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = my_source_name, + .create = my_source_create, + .destroy = my_source_destroy, + .update = my_source_update, + .video_render = my_source_render, + .get_width = my_source_width, + .get_height = my_source_height + }; + +Then, in my-plugin.c, you would call :c:func:`obs_register_source()` in +:c:func:`obs_module_load()` to register the source with libobs. + +.. code:: cpp + + /* my-plugin.c */ + + [...] + + extern struct obs_source_info my_source; /* Defined in my-source.c */ + + bool obs_module_load(void) + { + obs_register_source(&my_source); + + [...] + + return true; + } + +Some simple examples of sources: + +- Synchronous video source: The `image source `_ +- Asynchronous video source: The `random texture test source `_ +- Audio source: The `sine wave test source `_ +- Video filter: The `test video filter `_ +- Audio filter: The `gain audio filter `_ + + +.. _plugins_outputs: + +Outputs +------- +Outputs allow the ability to output the currently rendering audio/video. +Streaming and recording are two common examples of outputs, but not the +only types of outputs. Outputs can receive the raw data or receive +encoded data. The `libobs/obs-output.h`_ file is the dedicated header +for implementing outputs. See the :doc:`reference-outputs` for more +information. + +For example, to implement an output object, you need to define an +:c:type:`obs_output_info` structure and fill it out with information and +callbacks related to your output: + +.. code:: cpp + + /* my-output.c */ + + [...] + + struct obs_output_info my_output { + .id = "my_output", + .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED, + .get_name = my_output_name, + .create = my_output_create, + .destroy = my_output_destroy, + .start = my_output_start, + .stop = my_output_stop, + .encoded_packet = my_output_data, + .get_total_bytes = my_output_total_bytes, + .encoded_video_codecs = "h264", + .encoded_audio_codecs = "aac" + }; + +Then, in my-plugin.c, you would call :c:func:`obs_register_output()` in +:c:func:`obs_module_load()` to register the output with libobs. + +.. code:: cpp + + /* my-plugin.c */ + + [...] + + extern struct obs_output_info my_output; /* Defined in my-output.c */ + + bool obs_module_load(void) + { + obs_register_output(&my_output); + + [...] + + return true; + } + +Some examples of outputs: + +- Encoded video/audio outputs: + + - The `FLV output `_ + - The `FFmpeg muxer output `_ + - The `RTMP stream output `_ + +- Raw video/audio outputs: + + - The `FFmpeg output `_ + + +.. _plugins_encoders: + +Encoders +-------- +Encoders are OBS-specific implementations of video/audio encoders, which +are used with outputs that use encoders. x264, NVENC, Quicksync are +examples of encoder implementations. The `libobs/obs-encoder.h`_ file +is the dedicated header for implementing encoders. See the +:doc:`reference-encoders` for more information. + +For example, to implement an encoder object, you need to define an +:c:type:`obs_encoder_info` structure and fill it out with information +and callbacks related to your encoder: + +.. code:: cpp + + /* my-encoder.c */ + + [...] + + struct obs_encoder_info my_encoder_encoder = { + .id = "my_encoder", + .type = OBS_ENCODER_VIDEO, + .codec = "h264", + .get_name = my_encoder_name, + .create = my_encoder_create, + .destroy = my_encoder_destroy, + .encode = my_encoder_encode, + .update = my_encoder_update, + .get_extra_data = my_encoder_extra_data, + .get_sei_data = my_encoder_sei, + .get_video_info = my_encoder_video_info + }; + +Then, in my-plugin.c, you would call :c:func:`obs_register_encoder()` +in :c:func:`obs_module_load()` to register the encoder with libobs. + +.. code:: cpp + + /* my-plugin.c */ + + [...] + + extern struct obs_encoder_info my_encoder; /* Defined in my-encoder.c */ + + bool obs_module_load(void) + { + obs_register_encoder(&my_encoder); + + [...] + + return true; + } + +**IMPORTANT NOTE:** Encoder settings currently have a few expected +common setting values that should have a specific naming convention: + + - **"bitrate"** - This value should be used for both video and audio + encoders: bitrate, in kilobits. + - **"rate_control"** - This is a setting used for video encoders. + It's generally expected to have at least a "CBR" rate control. + Other common rate controls are "VBR", "CQP". + - **"keyint_sec"** - For video encoders, sets the keyframe interval + value, in seconds, or closest possible approximation. *(Author's + note: This should have have been "keyint", in frames.)* + +Examples of encoders: + +- Video encoders: + + - The `x264 encoder `_ + - The `FFmpeg NVENC encoder `_ + - The `Quicksync encoder `_ + +- Audio encoders: + + - The `FFmpeg AAC/Opus encoder `_ + + +.. _plugins_services: + +Services +-------- +Services are custom implementations of streaming services, which are +used with outputs that stream. For example, you could have a custom +implementation for streaming to Twitch, and another for YouTube to allow +the ability to log in and use their APIs to do things such as get the +RTMP servers or control the channel. The `libobs/obs-service.h`_ file +is the dedicated header for implementing services. See the +:doc:`reference-services` for more information. + +*(Author's note: the service API is incomplete as of this writing)* + +For example, to implement a service object, you need to define an +:c:type:`obs_service_info` structure and fill it out with information +and callbacks related to your service: + +.. code:: cpp + + /* my-service.c */ + + [...] + + struct obs_service_info my_service_service = { + .id = "my_service", + .get_name = my_service_name, + .create = my_service_create, + .destroy = my_service_destroy, + .encode = my_service_encode, + .update = my_service_update, + .get_url = my_service_url, + .get_key = my_service_key + }; + +Then, in my-plugin.c, you would call :c:func:`obs_register_service()` in +:c:func:`obs_module_load()` to register the service with libobs. + +.. code:: cpp + + /* my-plugin.c */ + + [...] + + extern struct obs_service_info my_service; /* Defined in my-service.c */ + + bool obs_module_load(void) + { + obs_register_service(&my_service); + + [...] + + return true; + } + +The only two existing services objects are the "common RTMP services" +and "custom RTMP service" objects in `plugins/rtmp-services +`_ + + +Settings +-------- +Settings (see `libobs/obs-data.h`_) are used to get or set settings data +typically associated with libobs objects, and can then be saved and +loaded via Json text. See the :doc:`reference-settings` for more +information. + +The *obs_data_t* is the equivalent of a Json object, where it's a string +table of sub-objects, and the *obs_data_array_t* is similarly used to +store an array of *obs_data_t* objects, similar to Json arrays (though +not quite identical). + +To create an *obs_data_t* or *obs_data_array_t* object, you'd call the +:c:func:`obs_data_create()` or :c:func:`obs_data_array_create()` +functions. *obs_data_t* and *obs_data_array_t* objects are reference +counted, so when you are finished with the object, call +:c:func:`obs_data_release()` or :c:func:`obs_data_array_release()` to +release those references. Any time an *obs_data_t* or +*obs_data_array_t* object is returned by a function, their references +are incremented, so you must release those references each time. + +To set values for an *obs_data_t* object, you'd use one of the following +functions: + +.. code:: cpp + + /* Set functions */ + EXPORT void obs_data_set_string(obs_data_t *data, const char *name, const char *val); + EXPORT void obs_data_set_int(obs_data_t *data, const char *name, long long val); + EXPORT void obs_data_set_double(obs_data_t *data, const char *name, double val); + EXPORT void obs_data_set_bool(obs_data_t *data, const char *name, bool val); + EXPORT void obs_data_set_obj(obs_data_t *data, const char *name, obs_data_t *obj); + EXPORT void obs_data_set_array(obs_data_t *data, const char *name, obs_data_array_t *array); + +Similarly, to get a value from an *obs_data_t* object, you'd use one of +the following functions: + +.. code:: cpp + + /* Get functions */ + EXPORT const char *obs_data_get_string(obs_data_t *data, const char *name); + EXPORT long long obs_data_get_int(obs_data_t *data, const char *name); + EXPORT double obs_data_get_double(obs_data_t *data, const char *name); + EXPORT bool obs_data_get_bool(obs_data_t *data, const char *name); + EXPORT obs_data_t *obs_data_get_obj(obs_data_t *data, const char *name); + EXPORT obs_data_array_t *obs_data_get_array(obs_data_t *data, const char *name); + +Unlike typical Json data objects, the *obs_data_t* object can also set +default values. This allows the ability to control what is returned if +there is no value assigned to a specific string in an *obs_data_t* +object when that data is loaded from a Json string or Json file. Each +libobs object also has a *get_defaults* callback which allows setting +the default settings for the object on creation. + +These functions control the default values are as follows: + +.. code:: cpp + + /* Default value functions. */ + EXPORT void obs_data_set_default_string(obs_data_t *data, const char *name, const char *val); + EXPORT void obs_data_set_default_int(obs_data_t *data, const char *name, long long val); + EXPORT void obs_data_set_default_double(obs_data_t *data, const char *name, double val); + EXPORT void obs_data_set_default_bool(obs_data_t *data, const char *name, bool val); + EXPORT void obs_data_set_default_obj(obs_data_t *data, const char *name, obs_data_t *obj); + + +Properties +---------- +Properties (see `libobs/obs-properties.h`_) are used to automatically +generate user interface to modify settings for a libobs object (if +desired). Each libobs object has a *get_properties* callback which is +used to generate properties. The properties API defines specific +properties that are linked to the object's settings, and the front-end +uses those properties to generate widgets in order to allow the user to +modify the settings. For example, if you had a boolean setting, you +would use :c:func:`obs_properties_add_bool()` to allow the user to be +able to change that setting. See the :doc:`reference-properties` for +more information. + +An example of this: + +.. code:: cpp + + static obs_properties_t *my_source_properties(void *data) + { + obs_properties_t *ppts = obs_properties_create(); + obs_properties_add_bool(ppts, "my_bool", + obs_module_text("MyBool")); + UNUSED_PARAMETER(data); + return ppts; + } + + [...] + + struct obs_source_info my_source { + .get_properties = my_source_properties, + [...] + }; + +The *data* parameter is the object's data if the object is present. +Typically this is unused and probably shouldn't be used if possible. It +can be null if the properties are retrieved without an object associated +with it. + +Properties can also be modified depending on what settings are shown. +For example, you can mark certain properties as disabled or invisible +depending on what a particular setting is set to using the +:c:func:`obs_property_set_modified_callback()` function. + +For example, if you wanted boolean property A to hide text property B: + +.. code:: cpp + + static bool setting_a_modified(obs_properties_t *ppts, + obs_property_t *p, obs_data_t *settings) + { + bool enabled = obs_data_get_bool(settings, "setting_a"); + p = obs_properties_get(ppts, "setting_b"); + obs_property_set_enabled(p, enabled); + + /* return true to update property widgets, false + otherwise */ + return true; + } + + [...] + + static obs_properties_t *my_source_properties(void *data) + { + obs_properties_t *ppts = obs_properties_create(); + obs_property_t *p; + + p = obs_properties_add_bool(ppts, "setting_a", + obs_module_text("SettingA")); + obs_property_set_modified_callback(p, setting_a_modified); + + obs_properties_add_text(ppts, "setting_b", + obs_module_text("SettingB"), + OBS_TEXT_DEFAULT); + return ppts; + } + + +Localization +------------ +Typically, most plugins bundled with OBS Studio will use a simple +ini-file localization method, where each file is a different language. +When using this method, the :c:func:`OBS_MODULE_USE_DEFAULT_LOCALE()` +macro is used which will automatically load/destroy the locale data with +no extra effort on part of the plugin. Then the +:c:func:`obs_module_text()` function (which is automatically declared as +an extern by `libobs/obs-module.h`_) is used when text lookup is needed. + +There are two exports the module used to load/destroy locale: the +:c:func:`obs_module_set_locale()` export, and the +:c:func:`obs_module_free_locale()` export. The +:c:func:`obs_module_set_locale()` export is called by libobs to set the +current language, and then the :c:func:`obs_module_free_locale()` export +is called by libobs on destruction of the module. If you wish to +implement a custom locale implementation for your plugin, you'd want to +define these exports along with the :c:func:`obs_module_text()` extern +yourself instead of relying on the +:c:func:`OBS_MODULE_USE_DEFAULT_LOCALE()` macro. + + +.. --------------------------------------------------------------------------- + +.. _libobs/obs-module.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-module.h +.. _libobs/obs.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs.h +.. _libobs/obs-source.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-source.h +.. _libobs/obs-output.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-output.h +.. _libobs/obs-encoder.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-encoder.h +.. _libobs/obs-service.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-service.h +.. _libobs/obs-data.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-data.h +.. _libobs/obs-properties.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-properties.h +.. _libobs/graphics/graphics.h: https://github.com/jp9000/obs-studio/blob/master/libobs/graphics/graphics.h diff --git a/docs/sphinx/reference-core-objects.rst b/docs/sphinx/reference-core-objects.rst new file mode 100644 index 0000000..8949b58 --- /dev/null +++ b/docs/sphinx/reference-core-objects.rst @@ -0,0 +1,13 @@ +Core API Object Reference +========================= + +.. toctree:: + :maxdepth: 3 + + reference-sources + reference-scenes + reference-outputs + reference-encoders + reference-services + reference-settings + reference-properties diff --git a/docs/sphinx/reference-core.rst b/docs/sphinx/reference-core.rst new file mode 100644 index 0000000..d5655b8 --- /dev/null +++ b/docs/sphinx/reference-core.rst @@ -0,0 +1,652 @@ +OBS Core API Reference +====================== + +.. code:: cpp + + #include + + +.. _obs_init_shutdown_reference: + +Initialization, Shutdown, and Information +----------------------------------------- + +.. function:: bool obs_startup(const char *locale, const char *module_config_path, profiler_name_store_t *store) + + Initializes the OBS core context. + + :param locale: The locale to use for modules + (E.G. "en-US") + :param module_config_path: Path to module config storage directory + (or *NULL* if none) + :param store: The profiler name store for OBS to use or NULL + :return: *false* if already initialized or failed + to initialize + +--------------------- + +.. function:: void obs_shutdown(void) + + Releases all data associated with OBS and terminates the OBS context. + +--------------------- + +.. function:: bool obs_initialized(void) + + :return: true if the main OBS context has been initialized + +--------------------- + +.. function:: uint32_t obs_get_version(void) + + :return: The current core version + +--------------------- + +.. function:: const char *obs_get_version_string(void) + + :return: The current core version string + +--------------------- + +.. function:: void obs_set_locale(const char *locale) + + Sets a new locale to use for modules. This will call + obs_module_set_locale for each module with the new locale. + + :param locale: The locale to use for modules + +--------------------- + +.. function:: const char *obs_get_locale(void) + + :return: The current locale + +--------------------- + +.. function:: profiler_name_store_t *obs_get_profiler_name_store(void) + + :return: The profiler name store (see util/profiler.h) used by OBS, + which is either a name store passed to obs_startup, an + internal name store, or NULL in case obs_initialized() + returns false. + +--------------------- + +.. function:: int obs_reset_video(struct obs_video_info *ovi) + + Sets base video output base resolution/fps/format. + + Note: This data cannot be changed if an output is currently active. + + Note: The graphics module cannot be changed without fully destroying + the OBS context. + + :param ovi: Pointer to an obs_video_info structure containing the + specification of the graphics subsystem, + :return: | OBS_VIDEO_SUCCESS - Success + | OBS_VIDEO_NOT_SUPPORTED - The adapter lacks capabilities + | OBS_VIDEO_INVALID_PARAM - A parameter is invalid + | OBS_VIDEO_CURRENTLY_ACTIVE - Video is currently active + | OBS_VIDEO_MODULE_NOT_FOUND - The graphics module is not found + | OBS_VIDEO_FAIL - Generic failure + + Relevant data types used with this function: + +.. code:: cpp + + struct obs_video_info { + /** + * Graphics module to use (usually "libobs-opengl" or "libobs-d3d11") + */ + const char *graphics_module; + + uint32_t fps_num; /**< Output FPS numerator */ + uint32_t fps_den; /**< Output FPS denominator */ + + uint32_t base_width; /**< Base compositing width */ + uint32_t base_height; /**< Base compositing height */ + + uint32_t output_width; /**< Output width */ + uint32_t output_height; /**< Output height */ + enum video_format output_format; /**< Output format */ + + /** Video adapter index to use (NOTE: avoid for optimus laptops) */ + uint32_t adapter; + + /** Use shaders to convert to different color formats */ + bool gpu_conversion; + + enum video_colorspace colorspace; /**< YUV type (if YUV) */ + enum video_range_type range; /**< YUV range (if YUV) */ + + enum obs_scale_type scale_type; /**< How to scale if scaling */ + }; + +--------------------- + +.. function:: bool obs_reset_audio(const struct obs_audio_info *oai) + + Sets base audio output format/channels/samples/etc. + + Note: Cannot reset base audio if an output is currently active. + + :return: *true* if successful, *false* otherwise + + Relevant data types used with this function: + +.. code:: cpp + + struct obs_audio_info { + uint32_t samples_per_sec; + enum speaker_layout speakers; + }; + +--------------------- + +.. function:: bool obs_get_video_info(struct obs_video_info *ovi) + + Gets the current video settings. + + :return: *false* if no video + +--------------------- + +.. function:: bool obs_get_audio_info(struct obs_audio_info *oai) + + Gets the current audio settings. + + :return: *false* if no audio + +--------------------- + + +Libobs Objects +-------------- + +.. function:: bool obs_enum_source_types(size_t idx, const char **id) + + Enumerates all source types (inputs, filters, transitions, etc). + +--------------------- + +.. function:: bool obs_enum_input_types(size_t idx, const char **id) + + Enumerates all available inputs source types. + + Inputs are general source inputs (such as capture sources, device sources, + etc). + +--------------------- + +.. function:: bool obs_enum_filter_types(size_t idx, const char **id) + + Enumerates all available filter source types. + + Filters are sources that are used to modify the video/audio output of + other sources. + +--------------------- + +.. function:: bool obs_enum_transition_types(size_t idx, const char **id) + + Enumerates all available transition source types. + + Transitions are sources used to transition between two or more other + sources. + +--------------------- + +.. function:: bool obs_enum_output_types(size_t idx, const char **id) + + Enumerates all available output types. + +--------------------- + +.. function:: bool obs_enum_encoder_types(size_t idx, const char **id) + + Enumerates all available encoder types. + +--------------------- + +.. function:: bool obs_enum_service_types(size_t idx, const char **id) + + Enumerates all available service types. + +--------------------- + +.. function:: void obs_enum_sources(bool (*enum_proc)(void*, obs_source_t*), void *param) + + Enumerates all input sources. + + Callback function returns true to continue enumeration, or false to end + enumeration. + + Use :c:func:`obs_source_get_ref()` or + :c:func:`obs_source_get_weak_source()` if you want to retain a + reference after obs_enum_sources finishes. + +--------------------- + +.. function:: void obs_enum_outputs(bool (*enum_proc)(void*, obs_output_t*), void *param) + + Enumerates outputs. + +--------------------- + +.. function:: void obs_enum_encoders(bool (*enum_proc)(void*, obs_encoder_t*), void *param) + + Enumerates encoders. + +--------------------- + +.. function:: obs_source_t *obs_get_source_by_name(const char *name) + + Gets a source by its name. + + Increments the source reference counter, use + :c:func:`obs_source_release()` to release it when complete. + +--------------------- + +.. function:: obs_output_t *obs_get_output_by_name(const char *name) + + Gets an output by its name. + + Increments the output reference counter, use + :c:func:`obs_output_release()` to release it when complete. + +--------------------- + +.. function:: obs_encoder_t *obs_get_encoder_by_name(const char *name) + + Gets an encoder by its name. + + Increments the encoder reference counter, use + :c:func:`obs_encoder_release()` to release it when complete. + +--------------------- + +.. function:: obs_service_t *obs_get_service_by_name(const char *name) + + Gets an service by its name. + + Increments the service reference counter, use + :c:func:`obs_service_release()` to release it when complete. + +--------------------- + +.. function:: obs_data_t *obs_save_source(obs_source_t *source) + + :return: A new reference to a source's saved data + +--------------------- + +.. function:: obs_source_t *obs_load_source(obs_data_t *data) + + :return: A source created from saved data + +--------------------- + +.. function:: void obs_load_sources(obs_data_array_t *array, obs_load_source_cb cb, void *private_data) + + Helper function to load active sources from a data array. + + Relevant data types used with this function: + +.. code:: cpp + + typedef void (*obs_load_source_cb)(void *private_data, obs_source_t *source); + +--------------------- + +.. function:: obs_data_array_t *obs_save_sources(void) + + :return: A data array with the saved data of all active sources + +--------------------- + +.. function:: obs_data_array_t *obs_save_sources_filtered(obs_save_source_filter_cb cb, void *data) + + :return: A data array with the saved data of all active sources, + filtered by the *cb* function + + Relevant data types used with this function: + +.. code:: cpp + + typedef bool (*obs_save_source_filter_cb)(void *data, obs_source_t *source); + +--------------------- + + +Video, Audio, and Graphics +-------------------------- + +.. function:: void obs_enter_graphics(void) + + Helper function for entering the OBS graphics context. + +--------------------- + +.. function:: void obs_leave_graphics(void) + + Helper function for leaving the OBS graphics context. + +--------------------- + +.. function:: audio_t *obs_get_audio(void) + + :return: The main audio output handler for this OBS context + +--------------------- + +.. function:: video_t *obs_get_video(void) + + :return: The main video output handler for this OBS context + +--------------------- + +.. function:: void obs_set_output_source(uint32_t channel, obs_source_t *source) + + Sets the primary output source for a channel. + +--------------------- + +.. function:: obs_source_t *obs_get_output_source(uint32_t channel) + + Gets the primary output source for a channel and increments the reference + counter for that source. Use :c:func:`obs_source_release()` to release. + +--------------------- + +.. function:: gs_effect_t *obs_get_base_effect(enum obs_base_effect effect) + + Returns a commoinly used base effect. + + :param effect: | Can be one of the following values: + | OBS_EFFECT_DEFAULT - RGB/YUV + | OBS_EFFECT_DEFAULT_RECT - RGB/YUV (using texture_rect) + | OBS_EFFECT_OPAQUE - RGB/YUV (alpha set to 1.0) + | OBS_EFFECT_SOLID - RGB/YUV (solid color only) + | OBS_EFFECT_BICUBIC - Bicubic downscale + | OBS_EFFECT_LANCZOS - Lanczos downscale + | OBS_EFFECT_BILINEAR_LOWRES - Bilinear low resolution downscale + | OBS_EFFECT_PREMULTIPLIED_ALPHA - Premultiplied alpha + +--------------------- + +.. function:: void obs_render_main_view(void) + + Renders the main view. + + Note: This function is deprecated. + +--------------------- + +.. function:: void obs_render_main_texture(void) + + Renders the main output texture. Useful for rendering a preview pane + of the main output. + +--------------------- + +.. function:: void obs_set_master_volume(float volume) + + Sets the master user volume. + +--------------------- + +.. function:: float obs_get_master_volume(void) + + :return: The master user volume + +--------------------- + +.. function:: void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data) + + Enumerates audio devices which can be used for audio monitoring. + + Relevant data types used with this function: + +.. code:: cpp + + typedef bool (*obs_enum_audio_device_cb)(void *data, const char *name, const char *id); + +--------------------- + +.. function:: bool obs_set_audio_monitoring_device(const char *name, const char *id) + + Sets the current audio device for audio monitoring. + +--------------------- + +.. function:: void obs_get_audio_monitoring_device(const char **name, const char **id) + + Gets the current audio device for audio monitoring. + +--------------------- + +.. function:: void obs_add_main_render_callback(void (*draw)(void *param, uint32_t cx, uint32_t cy), void *param) + void obs_remove_main_render_callback(void (*draw)(void *param, uint32_t cx, uint32_t cy), void *param) + + Adds/removes a main rendering callback. Allows custom rendering to + the main stream/recording output. + + +Primary signal/procedure handlers +--------------------------------- + +.. function:: signal_handler_t *obs_get_signal_handler(void) + + :return: The primary obs signal handler + + See :ref:`core_signal_handler_reference` for more information on + core signals. + +--------------------- + +.. function:: proc_handler_t *obs_get_proc_handler(void) + + :return: The primary obs procedure handler + + +.. _core_signal_handler_reference: + +Core OBS Signals +---------------- + +**source_create** (ptr source) + + Called when a source has been created. + +**source_destroy** (ptr source) + + Called when a source has been destroyed. + +**source_remove** (ptr source) + + Called when a source has been removed (:c:func:`obs_source_remove()` + has been called on the source). + +**source_save** (ptr source) + + Called when a source is being saved. + +**source_load** (ptr source) + + Called when a source is being loaded. + +**source_activate** (ptr source) + + Called when a source has been activated in the main view (visible on + stream/recording). + +**source_deactivate** (ptr source) + + Called when a source has been deactivated from the main view (no + longer visible on stream/recording). + +**source_show** (ptr source) + + Called when a source is visible on any display and/or on the main + view. + +**source_hide** (ptr source) + + Called when a source is no longer visible on any display and/or on + the main view. + +**source_rename** (ptr source, string new_name, string prev_name) + + Called when a source has been renamed. + +**source_volume** (ptr source, in out float volume) + + Called when a source's volume has changed. + +**source_transition_start** (ptr source) + + Called when a transition has started its transition. + +**source_transition_video_stop** (ptr source) + + Called when a transition has stopped its video transitioning. + +**source_transition_stop** (ptr source) + + Called when a transition has stopped its transition. + +**channel_change** (int channel, in out ptr source, ptr prev_source) + + Called when :c:func:`obs_set_output_source()` has been called. + +**master_volume** (in out float volume) + + Called when the master volume has changed. + +**hotkey_layout_change** () + + Called when the hotkey layout has changed. + +**hotkey_register** (ptr hotkey) + + Called when a hotkey has been registered. + +**hotkey_unregister** (ptr hotkey) + + Called when a hotkey has been unregistered. + +**hotkey_bindings_changed** (ptr hotkey) + + Called when a hotkey's bindings has changed. + +--------------------- + + +.. _display_reference: + +Displays +-------- + +.. function:: obs_display_t *obs_display_create(const struct gs_init_data *graphics_data) + + Adds a new window display linked to the main render pipeline. This creates + a new swap chain which updates every frame. + + *(Important note: do not use more than one display widget within the + hierarchy of the same base window; this will cause presentation + stalls on Macs.)* + + :param graphics_data: The swap chain initialization data + :return: The new display context, or NULL if failed + + Relevant data types used with this function: + +.. code:: cpp + + enum gs_color_format { + [...] + GS_RGBA, + GS_BGRX, + GS_BGRA, + GS_RGBA16F, + GS_RGBA32F, + [...] + }; + + enum gs_zstencil_format { + GS_ZS_NONE, + GS_Z16, + GS_Z24_S8, + GS_Z32F, + GS_Z32F_S8X24 + }; + + struct gs_window { + #if defined(_WIN32) + void *hwnd; + #elif defined(__APPLE__) + __unsafe_unretained id view; + #elif defined(__linux__) || defined(__FreeBSD__) + uint32_t id; + void *display; + #endif + }; + + struct gs_init_data { + struct gs_window window; + uint32_t cx, cy; + uint32_t num_backbuffers; + enum gs_color_format format; + enum gs_zstencil_format zsformat; + uint32_t adapter; + }; + +--------------------- + +.. function:: void obs_display_destroy(obs_display_t *display) + + Destroys a display context. + +--------------------- + +.. function:: void obs_display_resize(obs_display_t *display, uint32_t cx, uint32_t cy) + + Changes the size of a display context. + +--------------------- + +.. function:: void obs_display_add_draw_callback(obs_display_t *display, void (*draw)(void *param, uint32_t cx, uint32_t cy), void *param) + + Adds a draw callback for a display context, which will be called + whenever the display is rendered. + + :param display: The display context + :param draw: The draw callback which is called each time a frame + updates + :param param: The user data to be associated with this draw callback + +--------------------- + +.. function:: void obs_display_remove_draw_callback(obs_display_t *display, void (*draw)(void *param, uint32_t cx, uint32_t cy), void *param) + + Removes a draw callback for a display context. + +--------------------- + +.. function:: void obs_display_set_enabled(obs_display_t *display, bool enable) + + Enables/disables a display context. + +--------------------- + +.. function:: bool obs_display_enabled(obs_display_t *display) + + :return: *true* if the display is enabled, *false* otherwise + +--------------------- + +.. function:: void obs_display_set_background_color(obs_display_t *display, uint32_t color) + + Sets the background (clear) color for the display context. diff --git a/docs/sphinx/reference-encoders.rst b/docs/sphinx/reference-encoders.rst new file mode 100644 index 0000000..37cde83 --- /dev/null +++ b/docs/sphinx/reference-encoders.rst @@ -0,0 +1,493 @@ +Encoder API Reference (obs_encoder_t) +===================================== + +Encoders are OBS-specific implementations of video/audio encoders, which +are used with outputs that use encoders. x264, NVENC, Quicksync are +examples of encoder implementations. The `libobs/obs-encoder.h`_ file +is the dedicated header for implementing encoders + +.. type:: obs_encoder_t + + A reference-counted encoder object. + +.. type:: obs_weak_encoder_t + + A weak reference to an encoder object. + +.. code:: cpp + + #include + + +Encoder Definition Structure (obs_encoder_info) +----------------------------------------------- + +.. type:: struct obs_encoder_info + + Encoder definition structure. + +.. member:: const char *obs_encoder_info.id + + Unique string identifier for the encoder (required). + +.. member:: enum obs_encoder_type obs_encoder_info.type + + Type of encoder. + + - **OBS_ENCODER_VIDEO** - Video encoder + - **OBS_ENCODER_AUDIO** - Audio encoder + +.. member:: const char *obs_encoder_info.codec + + The codec, in string form. For example, "h264" for an H.264 encoder. + +.. member:: const char *(*obs_encoder_info.get_name)(void *type_data) + + Get the translated name of the encoder type. + + :param type_data: The type_data variable of this structure + :return: The translated name of the encoder type + +.. member:: void *(*obs_encoder_info.create)(obs_data_t *settings, obs_encoder_t *encoder) + + Creates the implementation data for the encoder. + + :param settings: Settings to initialize the encoder with + :param encoder: Encoder that this data is associated with + :return: The implementation data associated with this encoder + +.. member:: void (*obs_encoder_info.destroy)(void *data) + + Destroys the implementation data for the encoder. + +.. member:: bool (*encode)(void *data, struct encoder_frame *frame, struct encoder_packet *packet, bool *received_packet) + + Called to encode video or audio and outputs packets as they become + available. + + :param frame: Raw audio/video data to encode + :param packet: Encoder packet output, if any + :param received_packet: Set to *true* if a packet was received, + *false* otherwise + :return: true if successful, false on critical failure + +.. member:: size_t (*get_frame_size)(void *data) + + :return: An audio encoder's frame size. For example, for AAC this + number would be 1024 + +.. member:: void (*obs_encoder_info.get_defaults)(obs_data_t *settings) + void (*obs_encoder_info.get_defaults2)(void *type_data, obs_data_t *settings) + + Sets the default settings for this encoder. + + :param settings: Default settings. Call obs_data_set_default* + functions on this object to set default setting + values + +.. member:: obs_properties_t *(*obs_encoder_info.get_properties)(void *data) + obs_properties_t *(*obs_encoder_info.get_properties2)(void *data, void *type_data) + + Gets the property information of this encoder. + + (Optional) + + :return: The properties of the encoder + +.. member:: void (*obs_encoder_info.update)(void *data, obs_data_t *settings) + + Updates the settings for this encoder. + + (Optional) + + :param settings: New settings for this encoder + +.. member:: bool (*obs_encoder_info.get_extra_data)(void *data, uint8_t **extra_data, size_t *size) + + Returns extra data associated with this encoder (usually header). + + (Optional) + + :param extra_data: Pointer to receive the extra data + :param size: Pointer to receive the size of the extra + data + :return: true if extra data available, false + otherwise + +.. member:: bool (*obs_encoder_info.get_sei_data)(void *data, uint8_t **sei_data, size_t *size) + + Gets the SEI data of a video encoder that has SEI data. + + (Optional) + + :param sei_data: Pointer to receive the SEI data + :param size: Pointer to receive the SEI data size + :return: true if SEI data available, false otherwise + +.. member:: void (*obs_encoder_info.get_audio_info)(void *data, struct audio_convert_info *info) + + Returns desired audio format and sample information. This callback + can be used to tell the back-end that the audio data needs to be + automatically converted to a different sample rate or audio format + before being sent to the encoder. + + (Optional) + + :param info: Audio format information + +.. member:: void (*obs_encoder_info.get_video_info)(void *data, struct video_scale_info *info) + + Returns desired video format information. This callback can be used + to tell the back-end that the video data needs to be automatically + converted to a different video format or specific size before being + sent to the encoder. + + :param info: Video format information + +.. member:: void *obs_encoder_info.type_data + void (*obs_encoder_info.free_type_data)(void *type_data) + + Private data associated with this entry. Note that this is not the + same as the implementation data; this is used to differentiate + between two different types if the same callbacks are used for more + than one different type. + +.. member:: uint32_t obs_encoder_info.caps + + Can be 0 or a bitwise OR combination of one or more of the following + values: + + - **OBS_ENCODER_CAP_DEPRECATED** - Encoder is deprecated + + +Encoder Packet Structure (encoder_packet) +----------------------------------------- + +.. type:: struct encoder_packet + + Encoder packet structure. + +.. member:: uint8_t *encoder_packet.data + + Packet data. + +.. member:: size_t encoder_packet.size + + Packet size. + +.. member:: int64_t encoder_packet.pts + int64_t encoder_packet.dts + + Packet presentation and decode timestamps. + +.. member:: int32_t encoder_packet.timebase_num + int32_t encoder_packet.timebase_den + + Packet time base. + +.. member:: enum obs_encoder_type encoder_packet.type + + Can be one of the following values: + + - **OBS_ENCODER_VIDEO** - Video data + - **OBS_ENCODER_AUDIO** - Audio data + +.. member:: bool encoder_packet.keyframe + + Packet is a keyframe. + +.. member:: int64_t encoder_packet.dts_usec + + The DTS in microseconds. + + (This should not be set by the encoder implementation) + +.. member:: int64_t encoder_packet.sys_dts_usec + + The system time of this packet in microseconds. + + (This should not be set by the encoder implementation) + +.. member:: int encoder_packet.priority + + Packet priority. This is no longer used. + + (This should not be set by the encoder implementation) + +.. member:: int encoder_packet.drop_priority + + Packet drop priority. + + If this packet needs to be dropped, the next packet must be of this + priority or higher to continue transmission. + + (This should not be set by the encoder implementation) + +.. member:: size_t encoder_packet.track_idx + + Audio track index. + + (This should not be set by the encoder implementation) + +.. member:: obs_encoder_t *encoder_packet.encoder + + Encoder object associated with this packet. + + (This should not be set by the encoder implementation) + + +Raw Frame Data Structure (encoder_frame) +---------------------------------------- + +.. type:: struct encoder_frame + + Raw frame data structure. + +.. member:: uint8_t *encoder_frame.data[MAX_AV_PLANES] + + Raw video/audio data. + +.. member:: uint32_t encoder_frame.linesize[MAX_AV_PLANES] + + Line size of each plane. + +.. member:: uint32_t encoder_frame.frames + + Number of audio frames (if audio). + +.. member:: int64_t encoder_frame.pts + + Presentation timestamp. + + +General Encoder Functions +------------------------- + +.. function:: void obs_register_encoder(struct obs_encoder_info *info) + + Registers an encoder type. Typically used in + :c:func:`obs_module_load()` or in the program's initialization phase. + +--------------------- + +.. function:: const char *obs_encoder_get_display_name(const char *id) + + Calls the :c:member:`obs_encoder_info.get_name` callback to get the + translated display name of an encoder type. + + :param id: The encoder type string identifier + :return: The translated display name of an encoder type + +--------------------- + +.. function:: obs_encoder_t *obs_video_encoder_create(const char *id, const char *name, obs_data_t *settings, obs_data_t *hotkey_data) + + Creates a video encoder with the specified settings. + + The "encoder" context is used for encoding video/audio data. Use + obs_encoder_release to release it. + + :param id: The encoder type string identifier + :param name: The desired name of the encoder. If this is + not unique, it will be made to be unique + :param settings: The settings for the encoder, or *NULL* if + none + :param hotkey_data: Saved hotkey data for the encoder, or *NULL* + if none + :return: A reference to the newly created encoder, or + *NULL* if failed + +--------------------- + +.. function:: obs_encoder_t *obs_audio_encoder_create(const char *id, const char *name, obs_data_t *settings, size_t mixer_idx, obs_data_t *hotkey_data) + + Creates an audio encoder with the specified settings. + + The "encoder" context is used for encoding video/audio data. Use + :c:func:`obs_encoder_release()` to release it. + + :param id: The encoder type string identifier + :param name: The desired name of the encoder. If this is + not unique, it will be made to be unique + :param settings: The settings for the encoder, or *NULL* if + none + :param mixer_idx: The audio mixer index this audio encoder + will capture audio from + :param hotkey_data: Saved hotkey data for the encoder, or *NULL* + if none + :return: A reference to the newly created encoder, or + *NULL* if failed + +--------------------- + +.. function:: void obs_encoder_addref(obs_encoder_t *encoder) + void obs_encoder_release(obs_encoder_t *encoder) + + Adds/releases a reference to an encoder. When the last reference is + released, the encoder is destroyed. + +--------------------- + +.. function:: obs_weak_encoder_t *obs_encoder_get_weak_encoder(obs_encoder_t *encoder) + obs_encoder_t *obs_weak_encoder_get_encoder(obs_weak_encoder_t *weak) + + These functions are used to get a weak reference from a strong encoder + reference, or a strong encoder reference from a weak reference. If + the encoder is destroyed, *obs_weak_encoder_get_encoder* will return + *NULL*. + +--------------------- + +.. function:: void obs_weak_encoder_addref(obs_weak_encoder_t *weak) + void obs_weak_encoder_release(obs_weak_encoder_t *weak) + + Adds/releases a weak reference to an encoder. + +--------------------- + +.. function:: void obs_encoder_set_name(obs_encoder_t *encoder, const char *name) + + Sets the name of an encoder. If the encoder is not private and the + name is not unique, it will automatically be given a unique name. + +--------------------- + +.. function:: const char *obs_encoder_get_name(const obs_encoder_t *encoder) + + :return: The name of the encoder + +--------------------- + +.. function:: const char *obs_encoder_get_codec(const obs_encoder_t *encoder) + const char *obs_get_encoder_codec(const char *id) + + :return: The codec identifier of the encoder + +--------------------- + +.. function:: enum obs_encoder_type obs_encoder_get_type(const obs_encoder_t *encoder) + enum obs_encoder_type obs_get_encoder_type(const char *id) + + :return: The encoder type: OBS_ENCODER_VIDEO or OBS_ENCODER_AUDIO + +--------------------- + +.. function:: void obs_encoder_set_scaled_size(obs_encoder_t *encoder, uint32_t width, uint32_t height) + + Sets the scaled resolution for a video encoder. Set width and height to 0 + to disable scaling. If the encoder is active, this function will trigger + a warning, and do nothing. + +--------------------- + +.. function:: uint32_t obs_encoder_get_width(const obs_encoder_t *encoder) + uint32_t obs_encoder_get_height(const obs_encoder_t *encoder) + + :return: The width/height of a video encoder's encoded image + +--------------------- + +.. function:: uint32_t obs_encoder_get_sample_rate(const obs_encoder_t *encoder) + + :return: The sample rate of an audio encoder's audio data + +--------------------- + +.. function:: void obs_encoder_set_preferred_video_format(obs_encoder_t *encoder, enum video_format format) + enum video_format obs_encoder_get_preferred_video_format(const obs_encoder_t *encoder) + + + Sets the preferred video format for a video encoder. If the encoder can use + the format specified, it will force a conversion to that format if the + obs output format does not match the preferred format. + + If the format is set to VIDEO_FORMAT_NONE, will revert to the default + functionality of converting only when absolutely necessary. + +--------------------- + +.. function:: obs_data_t *obs_encoder_defaults(const char *id) + + :return: An incremented reference to the encoder's default settings + +--------------------- + +.. function:: obs_properties_t *obs_encoder_properties(const obs_encoder_t *encoder) + obs_properties_t *obs_get_encoder_properties(const char *id) + + Use these functions to get the properties of an encoder or encoder + type. Properties are optionally used (if desired) to automatically + generate user interface widgets to allow users to update settings. + + :return: The properties list for a specific existing encoder. Free + with :c:func:`obs_properties_destroy()` + +--------------------- + +.. function:: void obs_encoder_update(obs_encoder_t *encoder, obs_data_t *settings) + + Updates the settings for this encoder context. + +--------------------- + +.. function:: obs_data_t *obs_encoder_get_settings(const obs_encoder_t *encoder) + + :return: An incremented reference to the encoder's settings + +--------------------- + +.. function:: signal_handler_t *obs_encoder_get_signal_handler(const obs_encoder_t *encoder) + + :return: The signal handler of the encoder + +--------------------- + +.. function:: proc_handler_t *obs_encoder_get_proc_handler(const obs_encoder_t *encoder) + + :return: The procedure handler of the encoder + +--------------------- + +.. function:: bool obs_encoder_get_extra_data(const obs_encoder_t *encoder, uint8_t **extra_data, size_t *size) + + Gets extra data (headers) associated with this encoder. + + :return: *true* if successful, *false* if no extra data associated + with this encoder + +--------------------- + +.. function:: void obs_encoder_set_video(obs_encoder_t *encoder, video_t *video) + void obs_encoder_set_audio(obs_encoder_t *encoder, audio_t *audio) + + Sets the video/audio handler to use with this video/audio encoder. + This is used to capture the raw video/audio data. + +--------------------- + +.. function:: video_t *obs_encoder_video(const obs_encoder_t *encoder) + audio_t *obs_encoder_audio(const obs_encoder_t *encoder) + + :return: The video/audio handler associated with this encoder, or + *NULL* if none or not a matching encoder type + +--------------------- + +.. function:: bool obs_encoder_active(const obs_encoder_t *encoder) + + :return: *true* if the encoder is active, *false* otherwise + +--------------------- + + +Functions used by encoders +-------------------------- + +.. function:: void obs_encoder_packet_ref(struct encoder_packet *dst, struct encoder_packet *src) + void obs_encoder_packet_release(struct encoder_packet *packet) + + Adds or releases a reference to an encoder packet. + +.. --------------------------------------------------------------------------- + +.. _libobs/obs-encoder.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-encoder.h diff --git a/docs/sphinx/reference-frontend-api.rst b/docs/sphinx/reference-frontend-api.rst new file mode 100644 index 0000000..3746135 --- /dev/null +++ b/docs/sphinx/reference-frontend-api.rst @@ -0,0 +1,489 @@ +OBS Studio Frontend API +======================= + +The OBS Studio frontend API is the API specific to OBS Studio itself. + +.. code:: cpp + + #include + + +Structures/Enumerations +----------------------- + +.. type:: enum obs_frontend_event + + Specifies a front-end event. Can be one of the following values: + + - **OBS_FRONTEND_EVENT_STREAMING_STARTING** + + Triggered when streaming is starting. + + - **OBS_FRONTEND_EVENT_STREAMING_STARTED** + + Triggered when streaming has successfully started. + + - **OBS_FRONTEND_EVENT_STREAMING_STOPPING** + + Triggered when streaming is stopping. + + - **OBS_FRONTEND_EVENT_STREAMING_STOPPED** + + Triggered when streaming has fully stopped. + + - **OBS_FRONTEND_EVENT_RECORDING_STARTING** + + Triggered when recording is starting. + + - **OBS_FRONTEND_EVENT_RECORDING_STARTED** + + Triggered when recording has successfully started. + + - **OBS_FRONTEND_EVENT_RECORDING_STOPPING** + + Triggered when recording is stopping. + + - **OBS_FRONTEND_EVENT_RECORDING_STOPPED** + + Triggered when recording has fully stopped. + + - **OBS_FRONTEND_EVENT_SCENE_CHANGED** + + Triggered when the current scene has changed. + + - **OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED** + + Triggered when a scenes has been added/removed/reordered by the + user. + + - **OBS_FRONTEND_EVENT_TRANSITION_CHANGED** + + Triggered when the current transition has changed by the user. + + - **OBS_FRONTEND_EVENT_TRANSITION_STOPPED** + + Triggered when a transition has completed. + + - **OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED** + + Triggered when the user adds/removes transitions. + + - **OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED** + + Triggered when the user has changed the current scene collection. + + - **OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED** + + Triggered when the user has added/removed/renamed scene + collections. + + - **OBS_FRONTEND_EVENT_PROFILE_CHANGED** + + Triggered when the user has changed the current profile. + + - **OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED** + + Triggered when the user has added/removed/renamed profiles. + + - **OBS_FRONTEND_EVENT_EXIT** + + Triggered when the program is about to exit. + + - **OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING** + + Triggered when the replay buffer is starting. + + - **OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED** + + Triggered when the replay buffer has successfully started. + + - **OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING** + + Triggered when the replay buffer is stopping. + + - **OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED** + + Triggered when the replay buffer has fully stopped. + + - **OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED** + + Triggered when the user has turned on studio mode. + + - **OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED** + + Triggered when the user has turned off studio mode. + + - **OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED** + + Triggered when the current preview scene has changed in studio + mode. + + - **OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP** + + Triggered when a scene collection has been completely unloaded, and + the program is either about to load a new scene collection, or the + program is about to exit. + +.. type:: struct obs_frontend_source_list + + - DARRAY(obs_source_t*) **sources** + + Example usage: + +.. code:: cpp + + struct obs_frontend_source_list scenes; + + obs_frontend_get_scenes(&scenes); + + for (size_t i = 0; i < scenes.num; i++) { + obs_source_t *source = scenes.sources.array[i]; + + [...] + } + + obs_frontend_source_list_free(&scenes); + +.. type:: typedef void (*obs_frontend_cb)(void *private_data) + + Frontend tool menu callback. + +.. type:: typedef void (*obs_frontend_event_cb)(enum obs_frontend_event event, void *private_data) + + Frontend event callback. + +.. type:: typedef void (*obs_frontend_save_cb)(obs_data_t *save_data, bool saving, void *private_data) + + Frontend save/load callback. + +.. type:: typedef bool (*obs_frontend_translate_ui_cb)(const char *text, const char **out) + + Translation callback. + + +Functions +--------- + +.. function:: void obs_frontend_source_list_free(struct obs_frontend_source_list *source_list) + + Releases sources within a source list and frees the list. + + :param source_list: Source list to free. + +--------------------------------------- + +.. function:: void *obs_frontend_get_main_window(void) + + :return: The QMainWindow pointer to the OBS Studio window. + +--------------------------------------- + +.. function:: void *obs_frontend_get_main_window_handle(void) + + :return: The native window handle of the OBS Studio window. + +--------------------------------------- + +.. function:: char **obs_frontend_get_scene_names(void) + + :return: The scene name list, ending with NULL. The list is stored + within one contiguous segment of memory, so freeing the + returned pointer with :c:func:`bfree()` will free the entire + list. + +--------------------------------------- + +.. function:: void obs_frontend_get_scenes(struct obs_frontend_source_list *sources) + + :param sources: Pointer to a :c:type:`obs_frontend_source_list` + structure to receive the list of + reference-incremented scenes. Release with + :c:func:`obs_frontend_source_list_free`. + +--------------------------------------- + +.. function:: obs_source_t *obs_frontend_get_current_scene(void) + + :return: A new reference to the currently active scene. + +--------------------------------------- + +.. function:: void obs_frontend_set_current_scene(obs_source_t *scene) + + :param scene: The scene to set as the current scene. + +--------------------------------------- + +.. function:: void obs_frontend_get_transitions(struct obs_frontend_source_list *sources) + + :param sources: Pointer to a :c:type:`obs_frontend_source_list` + structure to receive the list of + reference-incremented transitions. Release with + :c:func:`obs_frontend_source_list_free`. + +--------------------------------------- + +.. function:: obs_source_t *obs_frontend_get_current_transition(void) + + :return: A new reference to the currently active transition. + +--------------------------------------- + +.. function:: void obs_frontend_set_current_transition(obs_source_t *transition) + + :param transition: The transition to set as the current transition. + +--------------------------------------- + +.. function:: char **obs_frontend_get_scene_collections(void) + + :return: The list of profile names, ending with NULL. The list is + stored within one contiguous segment of memory, so freeing + the returned pointer with :c:func:`bfree()` will free the + entire list. + +--------------------------------------- + +.. function:: char *obs_frontend_get_current_scene_collection(void) + + :return: A new pointer to the current scene collection name. Free + with :c:func:`bfree()`. + +--------------------------------------- + +.. function:: void obs_frontend_set_current_scene_collection(const char *collection) + + :param profile: Name of the scene collection to activate. + +--------------------------------------- + +.. function:: char **obs_frontend_get_profiles(void) + + :return: The list of profile names, ending with NULL. The list is + stored within one contiguous segment of memory, so freeing + the returned pointer with :c:func:`bfree()` will free the + entire list. + +--------------------------------------- + +.. function:: char *obs_frontend_get_current_profile(void) + + :return: A new pointer to the current profile name. Free with + :c:func:`bfree()`. + +--------------------------------------- + +.. function:: void obs_frontend_set_current_profile(const char *profile) + + :param profile: Name of the profile to activate. + +--------------------------------------- + +.. function:: void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) + + Adds a callback that will be called when a frontend event occurs. + See :c:type:`obs_frontend_event` on what sort of events can be + triggered. + + :param callback: Callback to use when a frontend event occurs. + :param private_data: Private data associated with the callback. + +--------------------------------------- + +.. function:: void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data) + + Removes an event callback. + + :param callback: Callback to remove. + :param private_data: Private data associated with the callback. + +--------------------------------------- + +.. function:: void obs_frontend_add_save_callback(obs_frontend_save_cb callback, void *private_data) + + Adds a callback that will be called when the current scene collection + is being saved/loaded. + + :param callback: Callback to use when saving/loading a scene + collection. + :param private_data: Private data associated with the callback. + +--------------------------------------- + +.. function:: void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, void *private_data) + + Removes a save/load callback. + + :param callback: Callback to remove. + :param private_data: Private data associated with the callback. + +--------------------------------------- + +.. function:: void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, void *private_data) + + Adds a callback that will be called right before a scene collection + is loaded. Useful if you + + :param callback: Callback to use when pre-loading. + :param private_data: Private data associated with the callback. + +--------------------------------------- + +.. function:: void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, void *private_data) + + Removes a pre-load callback. + + :param callback: Callback to remove. + :param private_data: Private data associated with the callback. + +--------------------------------------- + +.. function:: void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) + + Pushes a UI translation callback. This allows a front-end plugin to + intercept when Qt is automatically generating translating data. + Typically this is just called with obs_module_get_string. + + :param translate: The translation callback to use. + +--------------------------------------- + +.. function:: void obs_frontend_pop_ui_translation(void) + + Pops the current UI translation callback. + +--------------------------------------- + +.. function:: void obs_frontend_streaming_start(void) + + Starts streaming. + +--------------------------------------- + +.. function:: void obs_frontend_streaming_stop(void) + + Stops streaming. + +--------------------------------------- + +.. function:: bool obs_frontend_streaming_active(void) + + :return: *true* if streaming active, *false* otherwise. + +--------------------------------------- + +.. function:: void obs_frontend_recording_start(void) + + Starts recording. + +--------------------------------------- + +.. function:: void obs_frontend_recording_stop(void) + + Stops recording. + +--------------------------------------- + +.. function:: bool obs_frontend_recording_active(void) + + :return: *true* if recording active, *false* otherwise. + +--------------------------------------- + +.. function:: void obs_frontend_replay_buffer_start(void) + + Starts replay buffer. + +--------------------------------------- + +.. function:: void obs_frontend_replay_buffer_stop(void) + + Stops replay buffer. + +--------------------------------------- + +.. function:: void obs_frontend_replay_buffer_save(void) + + Saves a replay if the replay buffer is active. + +--------------------------------------- + +.. function:: bool obs_frontend_replay_buffer_active(void) + + :return: *true* if replay buffer active, *false* otherwise. + +--------------------------------------- + +.. function:: void obs_frontend_save(void) + + Saves the current scene collection. + +--------------------------------------- + +.. function:: obs_output_t *obs_frontend_get_streaming_output(void) + + :return: A new reference to the current streaming output. + +--------------------------------------- + +.. function:: obs_output_t *obs_frontend_get_recording_output(void) + + :return: A new reference to the current srecording output. + +--------------------------------------- + +.. function:: obs_output_t *obs_frontend_get_replay_buffer_output(void) + + :return: A new reference to the current replay buffer output. + +--------------------------------------- + +.. function:: void obs_frontend_set_streaming_service(obs_service_t *service) + + Sets the current streaming service to stream with. + + :param service: The streaming service to set. + +--------------------------------------- + +.. function:: obs_service_t *obs_frontend_get_streaming_service(void) + + :return: A new reference to the current streaming service object. + +--------------------------------------- + +.. function:: void obs_frontend_save_streaming_service(void) + + Saves the current streaming service data. + +--------------------------------------- + +.. function:: bool obs_frontend_preview_program_mode_active(void) + + :return: *true* if studio mode is active, *false* otherwise. + +--------------------------------------- + +.. function:: void obs_frontend_set_preview_program_mode(bool enable) + + Activates/deactivates studio mode. + + :param enable: *true* to activate studio mode, *false* to deactivate + studio mode. + +--------------------------------------- + +.. function:: obs_source_t *obs_frontend_get_current_preview_scene(void) + + :return: A new reference to the current preview scene if studio mode + is active, or the current scene if studio mode is not + active. + +--------------------------------------- + +.. function:: void obs_frontend_set_current_preview_scene(obs_source_t *scene) + + Sets the current preview scene in studio mode, or the currently + active scene if not in studio mode. + + :param scene: The scene to set as the current preview. diff --git a/docs/sphinx/reference-libobs-callback.rst b/docs/sphinx/reference-libobs-callback.rst new file mode 100644 index 0000000..6a954dd --- /dev/null +++ b/docs/sphinx/reference-libobs-callback.rst @@ -0,0 +1,276 @@ +Callback API Reference (libobs/callback) +======================================== + + +Calldata +-------- + +The :c:type:`calldata_t` object is used to pass parameters from signal +handlers or to procedure handlers. + +.. type:: calldata_t + +--------------------- + +.. function:: void calldata_init(calldata_t *data) + + Initializes a calldata structure (zeroes it). + + :param data: Calldata structure + +--------------------- + +.. function:: void calldata_free(calldata_t *data) + + Frees a calldata structure. + + :param data: Calldata structure + +--------------------- + +.. function:: void calldata_set_int(calldata_t *data, const char *name, long long val) + + Sets an integer parameter. + + :param data: Calldata structure + :param name: Parameter name + :param val: Integer value + +--------------------- + +.. function:: void calldata_set_float(calldata_t *data, const char *name, double val) + + Sets a floating point parameter. + + :param data: Calldata structure + :param name: Parameter name + :param val: Floating point value + +--------------------- + +.. function:: void calldata_set_bool(calldata_t *data, const char *name, bool val) + + Sets a boolean parameter. + + :param data: Calldata structure + :param name: Parameter name + :param val: Boolean value + +--------------------- + +.. function:: void calldata_set_ptr(calldata_t *data, const char *name, void *ptr) + + Sets a pointer parameter. + + :param data: Calldata structure + :param name: Parameter name + :param val: Pointer value + +--------------------- + +.. function:: void calldata_set_string(calldata_t *data, const char *name, const char *str) + + Sets a string parameter. + + :param data: Calldata structure + :param name: Parameter name + :param val: String + +--------------------- + +.. function:: long long calldata_int(const calldata_t *data, const char *name) + + Gets an integer parameter. + + :param data: Calldata structure + :param name: Parameter name + :return: Integer value + +--------------------- + +.. function:: double calldata_float(const calldata_t *data, const char *name) + + Gets a floating point parameter. + + :param data: Calldata structure + :param name: Parameter name + :return: Floating point value + +--------------------- + +.. function:: bool calldata_bool(const calldata_t *data, const char *name) + + Gets a boolean parameter. + + :param data: Calldata structure + :param name: Parameter name + :return: Boolean value + +--------------------- + +.. function:: void *calldata_ptr(const calldata_t *data, const char *name) + + Gets a pointer parameter. + + :param data: Calldata structure + :param name: Parameter name + :return: Pointer value + +--------------------- + +.. function:: const char *calldata_string(const calldata_t *data, const char *name) + + Gets a string parameter. + + :param data: Calldata structure + :param name: Parameter name + :return: String value + +--------------------- + + +Signals +------- + +Signals are used for all event-based callbacks. + +.. code:: cpp + + #include + +.. type:: signal_handler_t + +--------------------- + +.. type:: typedef void (*signal_callback_t)(void *data, calldata_t *cd) + + Signal callback. + + :param data: Private data passed to this callback + :param cd: Calldata object + +--------------------- + +.. function:: signal_handler_t *signal_handler_create(void) + + Creates a new signal handler object. + + :return: A new signal handler object + +--------------------- + +.. function:: void signal_handler_destroy(signal_handler_t *handler) + + Destroys a signal handler. + + :param handler: Signal handler object + +--------------------- + +.. function:: bool signal_handler_add(signal_handler_t *handler, const char *signal_decl) + + Adds a signal to a signal handler. + + :param handler: Signal handler object + :param signal_decl: Signal declaration string + +--------------------- + +.. function:: bool signal_handler_add_array(signal_handler_t *handler, const char **signal_decls) + + Adds multiple signals to a signal handler. + + :param handler: Signal handler object + :param signal_decls: An array of signal declaration strings, + terminated by *NULL* + +--------------------- + +.. function:: void signal_handler_connect(signal_handler_t *handler, const char *signal, signal_callback_t callback, void *data) + + Connect a callback to a signal on a signal handler. + + :param handler: Signal handler object + :param callback: Signal callback + :param data: Private data passed the callback + +--------------------- + +.. function:: void signal_handler_disconnect(signal_handler_t *handler, const char *signal, signal_callback_t callback, void *data) + + Disconnects a callback from a signal on a signal handler. + + :param handler: Signal handler object + :param callback: Signal callback + :param data: Private data passed the callback + +--------------------- + +.. function:: void signal_handler_signal(signal_handler_t *handler, const char *signal, calldata_t *params) + + Triggers a signal, calling all connected callbacks. + + :param handler: Signal handler object + :param signal: Name of signal to trigger + :param params: Parameters to pass to the signal + +--------------------- + + +Procedure Handlers +------------------ + +Procedure handlers are used to call functions without having to have +direct access to declarations or callback pointers. + +.. code:: cpp + + #include + +.. type:: proc_handler_t + +--------------------- + +.. type:: typedef void (*proc_handler_proc_t)(void *data, calldata_t *cd) + + Procedure handler callback. + + :param data: Private data passed to this callback + :param cd: Calldata object + +--------------------- + +.. function:: proc_handler_t *proc_handler_create(void) + + Creates a new procedure handler. + + :return: A new procedure handler object + +--------------------- + +.. function:: void proc_handler_destroy(proc_handler_t *handler) + + Destroys a procedure handler object. + + :param handler: Procedure handler object + +--------------------- + +.. function:: void proc_handler_add(proc_handler_t *handler, const char *decl_string, proc_handler_proc_t proc, void *data) + + Adds a procedure to a procedure handler. + + :param handler: Procedure handler object + :param decl_string: Procedure declaration string + :param proc: Procedure callback + :param data: Private data to pass to the callback + +--------------------- + +.. function:: bool proc_handler_call(proc_handler_t *handler, const char *name, calldata_t *params) + + Calls a procedure within the procedure handler. + + :param handler: Procedure handler object + :param name: Name of procedure to call + :param params: Calldata structure to pass to the procedure diff --git a/docs/sphinx/reference-libobs-graphics-axisang.rst b/docs/sphinx/reference-libobs-graphics-axisang.rst new file mode 100644 index 0000000..5414cba --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-axisang.rst @@ -0,0 +1,65 @@ +Axis Angle +========== + +Provides a helper structure for conversion to quaternions. + +.. code:: cpp + + #include + +.. type:: struct axisang +.. member:: float axisang.x + + X axis + +.. member:: float axisang.y + + Y axis + +.. member:: float axisang.z + + Z axis + +.. member:: float axisang.w + + Angle + +.. member:: float axisang.ptr[4] + +--------------------- + +.. function:: void axisang_zero(struct axisang *dst) + + Zeroes the axis angle. + + :param dst: Axis angle + +--------------------- + +.. function:: void axisang_copy(struct axisang *dst, struct axisang *aa) + + Copies an axis angle. + + :param dst: Axis angle to copy to + :param aa: Axis angle to copy from + +--------------------- + +.. function:: void axisang_set(struct axisang *dst, float x, float y, float z, float w) + + Sets an axis angle. + + :param dst: Axis angle to set + :param x: X axis + :param y: Y axis + :param z: Z axis + :param w: Angle + +--------------------- + +.. function:: void axisang_from_quat(struct axisang *dst, const struct quat *q) + + Creates an axis angle from a quaternion. + + :param dst: Axis angle destination + :param q: Quaternion to convert diff --git a/docs/sphinx/reference-libobs-graphics-effects.rst b/docs/sphinx/reference-libobs-graphics-effects.rst new file mode 100644 index 0000000..1392b41 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-effects.rst @@ -0,0 +1,334 @@ +Effects (Shaders) +================= + +Effects are a single collection of related shaders. They're used for +easily writing vertex and pixel shaders together all in the same file in +HLSL format. + +.. code:: cpp + + #include + +.. type:: typedef struct gs_effect gs_effect_t + + Effect object. + +.. type:: typedef struct gs_effect_technique gs_technique_t + + Technique object. + +.. type:: typedef struct gs_effect_param gs_eparam_t + + Effect parameter object. + + +--------------------- + +.. function:: gs_effect_t *gs_effect_create_from_file(const char *file, char **error_string) + + Creates an effect from file. + + :param file: Path to the effect file + :param error_string: Receives a pointer to the error string, which + must be freed with :c:func:`bfree()`. If + *NULL*, this parameter is ignored. + :return: The effect object, or *NULL* on error + +--------------------- + +.. function:: gs_effect_t *gs_effect_create(const char *effect_string, const char *filename, char **error_string) + + Creates an effect from a string. + + :param effect_String: Effect string + :param error_string: Receives a pointer to the error string, which + must be freed with :c:func:`bfree()`. If + *NULL*, this parameter is ignored. + :return: The effect object, or *NULL* on error + +--------------------- + +.. function:: void gs_effect_destroy(gs_effect_t *effect) + + Destroys the effect + + :param effect: Effect object + +--------------------- + +.. function:: gs_technique_t *gs_effect_get_technique(const gs_effect_t *effect, const char *name) + + Gets a technique of the effect. + + :param effect: Effect object + :param name: Name of the technique + :return: Technique object, or *NULL* if not found + +--------------------- + +.. function:: gs_technique_t *gs_effect_get_current_technique(const gs_effect_t *effect) + + Gets the current active technique of the effect. + + :param effect: Effect object + :return: Technique object, or *NULL* if none currently active + +--------------------- + +.. function:: size_t gs_technique_begin(gs_technique_t *technique) + + Begins a technique. + + :param technique: Technique object + :return: Number of passes this technique uses + +--------------------- + +.. function:: void gs_technique_end(gs_technique_t *technique) + + Ends a technique. Make sure all active passes have been ended before + calling. + + :param technique: Technique object + +--------------------- + +.. function:: bool gs_technique_begin_pass(gs_technique_t *technique, size_t pass) + + Begins a pass. Automatically loads the vertex/pixel shaders + associated with this pass. Draw after calling this function. + + :param technique: Technique object + :param pass: Pass index + :return: *true* if the pass is valid, *false* otherwise + +--------------------- + +.. function:: bool gs_technique_begin_pass_by_name(gs_technique_t *technique, const char *name) + + Begins a pass by its name if the pass has a name. Automatically + loads the vertex/pixel shaders associated with this pass. Draw after + calling this function. + + :param technique: Technique object + :param name: Name of the pass + :return: *true* if the pass is valid, *false* otherwise + +--------------------- + +.. function:: void gs_technique_end_pass(gs_technique_t *technique) + + Ends a pass. + + :param technique: Technique object + +--------------------- + +.. function:: size_t gs_effect_get_num_params(const gs_effect_t *effect) + + Gets the number of parameters associated with the effect. + + :param effect: Effect object + :return: Number of parameters the effect has + +--------------------- + +.. function:: gs_eparam_t *gs_effect_get_param_by_idx(const gs_effect_t *effect, size_t param) + + Gets a parameter of an effect by its index. + + :param effect: Effect object + :param param: Parameter index + :return: The effect parameter object, or *NULL* if index + invalid + +--------------------- + +.. function:: gs_eparam_t *gs_effect_get_param_by_name(const gs_effect_t *effect, const char *name) + + Gets parameter of an effect by its name. + + :param effect: Effect object + :param name: Name of the parameter + :return: The effect parameter object, or *NULL* if not found + +--------------------- + +.. function:: bool gs_effect_loop(gs_effect_t *effect, const char *name) + + Helper function that automatically begins techniques/passes. + + :param effect: Effect object + :param name: Name of the technique to execute + :return: *true* to draw, *false* when complete + + Here is an example of how this function is typically used: + +.. code:: cpp + + for (gs_effect_loop(effect, "my_technique")) { + /* perform drawing here */ + [...] + } + +--------------------- + +.. function:: gs_eparam_t *gs_effect_get_viewproj_matrix(const gs_effect_t *effect) + + Gets the view/projection matrix parameter ("viewproj") of the effect. + + :param effect: Effect object + :return: The view/projection matrix parameter of the effect + +--------------------- + +.. function:: gs_eparam_t *gs_effect_get_world_matrix(const gs_effect_t *effect) + + Gets the world matrix parameter ("world") of the effect. + + :param effect: Effect object + :return: The world matrix parameter of the effect + +--------------------- + +.. function:: void gs_effect_get_param_info(const gs_eparam_t *param, struct gs_effect_param_info *info) + + Gets information about an effect parameter. + + :param param: Effect parameter + :param info: Pointer to receive the data + + Relevant data types used with this function: + +.. code:: cpp + + enum gs_shader_param_type { + GS_SHADER_PARAM_UNKNOWN, + GS_SHADER_PARAM_BOOL, + GS_SHADER_PARAM_FLOAT, + GS_SHADER_PARAM_INT, + GS_SHADER_PARAM_STRING, + GS_SHADER_PARAM_VEC2, + GS_SHADER_PARAM_VEC3, + GS_SHADER_PARAM_VEC4, + GS_SHADER_PARAM_INT2, + GS_SHADER_PARAM_INT3, + GS_SHADER_PARAM_INT4, + GS_SHADER_PARAM_MATRIX4X4, + GS_SHADER_PARAM_TEXTURE, + }; + + struct gs_effect_param_info { + const char *name; + enum gs_shader_param_type type; + } + +--------------------- + +.. function:: void gs_effect_set_bool(gs_eparam_t *param, bool val) + + Sets a boolean parameter. + + :param param: Effect parameter + :param val: Boolean value + +--------------------- + +.. function:: void gs_effect_set_float(gs_eparam_t *param, float val) + + Sets a floating point parameter. + + :param param: Effect parameter + :param val: Floating point value + +--------------------- + +.. function:: void gs_effect_set_int(gs_eparam_t *param, int val) + + Sets a integer parameter. + + :param param: Effect parameter + :param val: Integer value + +--------------------- + +.. function:: void gs_effect_set_matrix4(gs_eparam_t *param, const struct matrix4 *val) + + Sets a matrix parameter. + + :param param: Effect parameter + :param val: Matrix + +--------------------- + +.. function:: void gs_effect_set_vec2(gs_eparam_t *param, const struct vec2 *val) + + Sets a 2-component vector parameter. + + :param param: Effect parameter + :param val: Vector + +--------------------- + +.. function:: void gs_effect_set_vec3(gs_eparam_t *param, const struct vec3 *val) + + Sets a 3-component vector parameter. + + :param param: Effect parameter + :param val: Vector + +--------------------- + +.. function:: void gs_effect_set_vec4(gs_eparam_t *param, const struct vec4 *val) + + Sets a 4-component vector parameter. + + :param param: Effect parameter + :param val: Vector + +--------------------- + +.. function:: void gs_effect_set_color(gs_eparam_t *param, uint32_t argb) + + Convenience function for setting a color value via an integer value. + + :param param: Effect parameter + :param argb: Integer color value (i.e. hex value would be + 0xAARRGGBB) + +--------------------- + +.. function:: void gs_effect_set_texture(gs_eparam_t *param, gs_texture_t *val) + + Sets a texture parameter. + + :param param: Effect parameter + :param val: Texture + +--------------------- + +.. function:: void gs_effect_set_val(gs_eparam_t *param, const void *val, size_t size) + + Sets a parameter with data manually. + + :param param: Effect parameter + :param val: Pointer to data + :param size: Size of data + +--------------------- + +.. function:: void gs_effect_set_default(gs_eparam_t *param) + + Sets the parameter to its default value + + :param: Effect parameter + +--------------------- + +.. function:: void gs_effect_set_next_sampler(gs_eparam_t *param, gs_samplerstate_t *sampler) + + Manually changes the sampler for an effect parameter the next time + it's used. + + :param param: Effect parameter + :param sampler: Sampler state object diff --git a/docs/sphinx/reference-libobs-graphics-graphics.rst b/docs/sphinx/reference-libobs-graphics-graphics.rst new file mode 100644 index 0000000..d220750 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-graphics.rst @@ -0,0 +1,1462 @@ +Core Graphics API +================= + +.. code:: cpp + + #include + + +Graphics Enumerations +--------------------- + +.. type:: enum gs_draw_mode + + Draw mode. Can be one of the following values: + + - GS_POINTS - Draws points + - GS_LINES - Draws individual lines + - GS_LINESTRIP - Draws a line strip + - GS_TRIS - Draws individual triangles + - GS_TRISTRIP - Draws a triangle strip + +.. type:: enum gs_color_format + + Color format. Can be one of the following values: + + - GS_UNKNOWN - Unknown format + - GS_A8 - 8 bit alpha channel only + - GS_R8 - 8 bit red channel only + - GS_RGBA - RGBA, 8 bits per channel + - GS_BGRX - BGRX, 8 bits per channel + - GS_BGRA - BGRA, 8 bits per channel + - GS_R10G10B10A2 - RGBA, 10 bits per channel except alpha, which is 2 + bits + - GS_RGBA16 - RGBA, 16 bits per channel + - GS_R16 - 16 bit red channel only + - GS_RGBA16F - RGBA, 16 bit floating point per channel + - GS_RGBA32F - RGBA, 32 bit floating point per channel + - GS_RG16F - 16 bit floating point red and green channels only + - GS_RG32F - 32 bit floating point red and green channels only + - GS_R16F - 16 bit floating point red channel only + - GS_R32F - 32 bit floating point red channel only + - GS_DXT1 - Compressed DXT1 + - GS_DXT3 - Compressed DXT3 + - GS_DXT5 - Compressed DXT5 + +.. type:: enum gs_zstencil_format + + Z-Stencil buffer format. Can be one of the following values: + + - GS_ZS_NONE - No Z-stencil buffer + - GS_Z16 - 16 bit Z buffer + - GS_Z24_S8 - 24 bit Z buffer, 8 bit stencil + - GS_Z32F - 32 bit floating point Z buffer + - GS_Z32F_S8X24 - 32 bit floating point Z buffer, 8 bit stencil + +.. type:: enum gs_index_type + + Index buffer type. Can be one of the following values: + + - GS_UNSIGNED_SHORT - 16 bit index + - GS_UNSIGNED_LONG - 32 bit index + +.. type:: enum gs_cull_mode + + Cull mode. Can be one of the following values: + + - GS_BACK - Cull back faces + - GS_FRONT - Cull front faces + - GS_NEITHER - Cull neither + +.. type:: enum gs_blend_type + + Blend type. Can be one of the following values: + + - GS_BLEND_ZERO + - GS_BLEND_ONE + - GS_BLEND_SRCCOLOR + - GS_BLEND_INVSRCCOLOR + - GS_BLEND_SRCALPHA + - GS_BLEND_INVSRCALPHA + - GS_BLEND_DSTCOLOR + - GS_BLEND_INVDSTCOLOR + - GS_BLEND_DSTALPHA + - GS_BLEND_INVDSTALPHA + - GS_BLEND_SRCALPHASAT + +.. type:: enum gs_depth_test + + Depth test type. Can be one of the following values: + + - GS_NEVER + - GS_LESS + - GS_LEQUAL + - GS_EQUAL + - GS_GEQUAL + - GS_GREATER + - GS_NOTEQUAL + - GS_ALWAYS + +.. type:: enum gs_stencil_side + + Stencil side. Can be one of the following values: + + - GS_STENCIL_FRONT=1 + - GS_STENCIL_BACK + - GS_STENCIL_BOTH + +.. type:: enum gs_stencil_op_type + + Stencil operation type. Can be one of the following values: + + - GS_KEEP + - GS_ZERO + - GS_REPLACE + - GS_INCR + - GS_DECR + - GS_INVERT + +.. type:: enum gs_cube_sides + + Cubemap side. Can be one of the following values: + + - GS_POSITIVE_X + - GS_NEGATIVE_X + - GS_POSITIVE_Y + - GS_NEGATIVE_Y + - GS_POSITIVE_Z + - GS_NEGATIVE_Z + +.. type:: enum gs_sample_filter + + Sample filter type. Can be one of the following values: + + - GS_FILTER_POINT + - GS_FILTER_LINEAR + - GS_FILTER_ANISOTROPIC + - GS_FILTER_MIN_MAG_POINT_MIP_LINEAR + - GS_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT + - GS_FILTER_MIN_POINT_MAG_MIP_LINEAR + - GS_FILTER_MIN_LINEAR_MAG_MIP_POINT + - GS_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR + - GS_FILTER_MIN_MAG_LINEAR_MIP_POINT + +.. type:: enum gs_address_mode + + Address mode. Can be one of the following values: + + - GS_ADDRESS_CLAMP + - GS_ADDRESS_WRAP + - GS_ADDRESS_MIRROR + - GS_ADDRESS_BORDER + - GS_ADDRESS_MIRRORONCE + +.. type:: enum gs_texture_type + + Texture type. Can be one of the following values: + + - GS_TEXTURE_2D + - GS_TEXTURE_3D + - GS_TEXTURE_CUBE + + +Graphics Structures +------------------- + +.. type:: struct gs_monitor_info +.. member:: int gs_monitor_info.rotation_degrees +.. member:: long gs_monitor_info.x +.. member:: long gs_monitor_info.y +.. member:: long gs_monitor_info.cx +.. member:: long gs_monitor_info.cy + +--------------------- + +.. type:: struct gs_tvertarray +.. member:: size_t gs_tvertarray.width +.. member:: void *gs_tvertarray.array + +--------------------- + +.. type:: struct gs_vb_data +.. member:: size_t gs_vb_data.num +.. member:: struct vec3 *gs_vb_data.points +.. member:: struct vec3 *gs_vb_data.normals +.. member:: struct vec3 *gs_vb_data.tangents +.. member:: uint32_t *gs_vb_data.colors +.. member:: size_t gs_vb_data.num_tex +.. member:: struct gs_tvertarray *gs_vb_data.tvarray + +--------------------- + +.. type:: struct gs_sampler_info +.. member:: enum gs_sample_filter gs_sampler_info.filter +.. member:: enum gs_address_mode gs_sampler_info.address_u +.. member:: enum gs_address_mode gs_sampler_info.address_v +.. member:: enum gs_address_mode gs_sampler_info.address_w +.. member:: int gs_sampler_info.max_anisotropy +.. member:: uint32_t gs_sampler_info.border_color + +--------------------- + +.. type:: struct gs_display_mode +.. member:: uint32_t gs_display_mode.width +.. member:: uint32_t gs_display_mode.height +.. member:: uint32_t gs_display_mode.bits +.. member:: uint32_t gs_display_mode.freq + +--------------------- + +.. type:: struct gs_rect +.. member:: int gs_rect.x +.. member:: int gs_rect.y +.. member:: int gs_rect.cx +.. member:: int gs_rect.cy + +--------------------- + +.. type:: struct gs_window + + A window structure. This structure is used with a native widget. + +.. member:: void *gs_window.hwnd + + (Windows only) an HWND widget. + +.. member:: id gs_window.view + + (Mac only) A view ID. + +.. member:: uint32_t gs_window.id + void* gs_window.display + + (Linux only) Window ID and display + +--------------------- + +.. type:: struct gs_init_data + + Swap chain initialization data. + +.. member:: struct gs_window gs_init_data.window +.. member:: uint32_t gs_init_data.cx +.. member:: uint32_t gs_init_data.cy +.. member:: uint32_t gs_init_data.num_backbuffers +.. member:: enum gs_color_format gs_init_data.format +.. member:: enum gs_zstencil_format gs_init_data.zsformat +.. member:: uint32_t gs_init_data.adapter + +--------------------- + + +Initialization Functions +------------------------ + +.. function:: void gs_enum_adapters(bool (*callback)(void *param, const char *name, uint32_t id), void *param) + + Enumerates adapters (this really only applies on windows). + + :param callback: Enumeration callback + :param param: Private data passed to the callback + +--------------------- + +.. function:: int gs_create(graphics_t **graphics, const char *module, uint32_t adapter) + + Creates a graphics context + + :param graphics: Pointer to receive the graphics context + :param module: Module name + :param adapter: Adapter index + :return: Can return one of the following values: + + - GS_SUCCESS + - GS_ERROR_FAIL + - GS_ERROR_MODULE_NOT_FOUND + - GS_ERROR_NOT_SUPPORTED + +--------------------- + +.. function:: void gs_destroy(graphics_t *graphics) + + Destroys a graphics context + + :param graphics: Graphics context + +--------------------- + +.. function:: void gs_enter_context(graphics_t *graphics) + + Enters and locks the graphics context + + :param graphics: Graphics context + +--------------------- + +.. function:: void gs_leave_context(void) + + Leaves and unlocks the graphics context + + :param graphics: Graphics context + +--------------------- + +.. function:: graphics_t *gs_get_context(void) + + :return: The currently locked graphics context for this thread + +--------------------- + + +Matrix Stack Functions +---------------------- + +.. function:: void gs_matrix_push(void) + + Pushes the matrix stack and duplicates the current matrix. + +--------------------- + +.. function:: void gs_matrix_pop(void) + + Pops the current matrix from the matrix stack. + +--------------------- + +.. function:: void gs_matrix_identity(void) + + Sets the current matrix to an identity matrix. + +--------------------- + +.. function:: void gs_matrix_transpose(void) + + Transposes the current matrix. + +--------------------- + +.. function:: void gs_matrix_set(const struct matrix4 *matrix) + + Sets the current matrix. + + :param matrix: The matrix to set + +--------------------- + +.. function:: void gs_matrix_get(struct matrix4 *dst) + + Gets the current matrix + + :param dst: Destination matrix + +--------------------- + +.. function:: void gs_matrix_mul(const struct matrix4 *matrix) + + Multiplies the current matrix + + :param matrix: Matrix to multiply the current stack matrix with + +--------------------- + +.. function:: void gs_matrix_rotquat(const struct quat *rot) + + Multiplies the current matrix with a quaternion + + :param rot: Quaternion to multiple the current matrix stack with + +--------------------- + +.. function:: void gs_matrix_rotaa(const struct axisang *rot) + void gs_matrix_rotaa4f(float x, float y, float z, float angle) + + Multiplies the current matrix with an axis angle + + :param rot: Axis angle to multiple the current matrix stack with + +--------------------- + +.. function:: void gs_matrix_translate(const struct vec3 *pos) + void gs_matrix_translate3f(float x, float y, float z) + + Translates the current matrix + + :param pos: Vector to translate the current matrix stack with + +--------------------- + +.. function:: void gs_matrix_scale(const struct vec3 *scale) + void gs_matrix_scale3f(float x, float y, float z) + + Scales the current matrix + + :param scale: Scale value to scale the current matrix stack with + +--------------------- + + +Draw Functions +-------------- + +.. function:: gs_effect_t *gs_get_effect(void) + + :return: The currently active effect, or *NULL* if none active + +--------------------- + +.. function:: void gs_draw_sprite(gs_texture_t *tex, uint32_t flip, uint32_t width, uint32_t height) + + Draws a 2D sprite. Sets the "image" parameter of the current effect + to the texture and renders a quad. + + If width or height is 0, the width or height of the texture will be + used. The flip value specifies whether the texture should be flipped + on the U or V axis with GS_FLIP_U and GS_FLIP_V. + + :param tex: Texture to draw + :param flip: Can be 0 or a bitwise-OR combination of one of the + following values: + + - GS_FLIP_U - Flips the texture horizontally + - GS_FLIP_V - Flips the texture vertically + + :param width: Width + :param height: Height + +--------------------- + +.. function:: void gs_draw_sprite_subregion(gs_texture_t *tex, uint32_t flip, uint32_t x, uint32_t y, uint32_t cx, uint32_t cy) + + Draws a subregion of a 2D sprite. Sets the "image" parameter of the + current effect to the texture and renders a quad. + + :param tex: Texture to draw + :param flip: Can be 0 or a bitwise-OR combination of one of the + following values: + + - GS_FLIP_U - Flips the texture horizontally + - GS_FLIP_V - Flips the texture vertically + + :param x: X value within subregion + :param y: Y value within subregion + :param cx: CX value of subregion + :param cy: CY value of subregion + +--------------------- + +.. function:: void gs_reset_viewport(void) + + Sets the viewport to current swap chain size + +--------------------- + +.. function:: void gs_set_2d_mode(void) + + Sets the projection matrix to a default screen-sized orthographic + mode + +--------------------- + +.. function:: void gs_set_3d_mode(double fovy, double znear, double zfar) + + Sets the projection matrix to a default screen-sized perspective + mode + + :param fovy: Field of view (in degrees) + :param znear: Near plane + :param zfar: Far plane + +--------------------- + +.. function:: void gs_viewport_push(void) + + Pushes/stores the current viewport + +--------------------- + +.. function:: void gs_viewport_pop(void) + + Pops/recalls the last pushed viewport + +--------------------- + +.. function:: void gs_perspective(float fovy, float aspect, float znear, float zfar) + + Sets the projection matrix to a perspective mode + + :param fovy: Field of view (in degrees) + :param aspect: Aspect ratio + :param znear: Near plane + :param zfar: Far plane + +--------------------- + +.. function:: void gs_blend_state_push(void) + + Pushes/stores the current blend state + +--------------------- + +.. function:: void gs_blend_state_pop(void) + + Pops/restores the last blend state + +--------------------- + +.. function:: void gs_reset_blend_state(void) + + Sets the blend state to the default value: source alpha and invert + source alpha. + +--------------------- + + +Swap Chains +----------- + +.. function:: gs_swapchain_t *gs_swapchain_create(const struct gs_init_data *data) + + Creates a swap chain (display view on a native widget) + + :param data: Swap chain initialization data + :return: New swap chain object, or *NULL* if failed + +--------------------- + +.. function:: void gs_swapchain_destroy(gs_swapchain_t *swapchain) + + Destroys a swap chain + +--------------------- + +.. function:: void gs_resize(uint32_t cx, uint32_t cy) + + Resizes the currently active swap chain + + :param cx: New width + :param cy: New height + +--------------------- + +.. function:: void gs_get_size(uint32_t *cx, uint32_t *cy) + + Gets the size of the currently active swap chain + + :param cx: Pointer to receive width + :param cy: Pointer to receive height + +--------------------- + +.. function:: uint32_t gs_get_width(void) + + Gets the width of the currently active swap chain + +--------------------- + +.. function:: uint32_t gs_get_height(void) + + Gets the height of the currently active swap chain + +--------------------- + + +Resource Loading +---------------- + +.. function:: void gs_load_vertexbuffer(gs_vertbuffer_t *vertbuffer) + + Loads a vertex buffer + + :param vertbuffer: Vertex buffer to load, or NULL to unload + +--------------------- + +.. function:: void gs_load_indexbuffer(gs_indexbuffer_t *indexbuffer) + + Loads a index buffer + + :param indexbuffer: Index buffer to load, or NULL to unload + +--------------------- + +.. function:: void gs_load_texture(gs_texture_t *tex, int unit) + + Loads a texture (this is usually not called manually) + + :param tex: Texture to load, or NULL to unload + :param unit: Texture unit to load texture for + +--------------------- + +.. function:: void gs_load_samplerstate(gs_samplerstate_t *samplerstate, int unit) + + Loads a sampler state (this is usually not called manually) + + :param samplerstate: Sampler state to load, or NULL to unload + :param unit: Texture unit to load sampler state for + +--------------------- + +.. function:: void gs_load_swapchain(gs_swapchain_t *swapchain) + + Loads a swapchain + + :param swapchain: Swap chain to load, or NULL to unload + +--------------------- + + +Draw Functions +-------------- + +.. function:: gs_texture_t *gs_get_render_target(void) + + :return: The currently active render target + +--------------------- + +.. function:: gs_zstencil_t *gs_get_zstencil_target(void) + + :return: The currently active Z-stencil target + +--------------------- + +.. function:: void gs_set_render_target(gs_texture_t *tex, gs_zstencil_t *zstencil) + + Sets the active render target + + :param tex: Texture to set as the active render target + :param zstencil: Z-stencil to use as the active render target + +--------------------- + +.. function:: void gs_set_cube_render_target(gs_texture_t *cubetex, int side, gs_zstencil_t *zstencil) + + Sets a cubemap side as the active render target + + :param cubetex: Cubemap + :param side: Cubemap side + :param zstencil: Z-stencil buffer, or *NULL* if none + +--------------------- + +.. function:: void gs_copy_texture(gs_texture_t *dst, gs_texture_t *src) + + Copies a texture + + :param dst: Destination texture + :param src: Source texture + +--------------------- + +.. function:: void gs_stage_texture(gs_stagesurf_t *dst, gs_texture_t *src) + + Copies a texture to a staging surface and copies it to RAM. Ideally + best to give this a frame to process to prevent stalling. + + :param dst: Staging surface + :param src: Texture to stage + +--------------------- + +.. function:: void gs_begin_scene(void) + void gs_end_scene(void) + + Begins/ends a scene (this is automatically called by libobs, there's + no need to call this manually). + +--------------------- + +.. function:: void gs_draw(enum gs_draw_mode draw_mode, uint32_t start_vert, uint32_t num_verts) + + Draws a primitive or set of primitives. + + :param draw_mode: The primitive draw mode to use + :param start_vert: Starting vertex index + :param num_verts: Number of vertices + +--------------------- + +.. function:: void gs_clear(uint32_t clear_flags, const struct vec4 *color, float depth, uint8_t stencil) + + Clears color/depth/stencil buffers. + + :param clear_flags: Flags to clear with. Can be one of the following + values: + + - GS_CLEAR_COLOR - Clears color buffer + - GS_CLEAR_DEPTH - Clears depth buffer + - GS_CLEAR_STENCIL - Clears stencil buffer + + :param color: Color value to clear the color buffer with + :param depth: Depth value to clear the depth buffer with + :param stencil: Stencil value to clear the stencil buffer with + +--------------------- + +.. function:: void gs_present(void) + + Displays what was rendered on to the current render target + +--------------------- + +.. function:: void gs_flush(void) + + Flushes GPU calls + +--------------------- + +.. function:: void gs_set_cull_mode(enum gs_cull_mode mode) + + Sets the current cull mode. + + :param mode: Cull mode + +--------------------- + +.. function:: enum gs_cull_mode gs_get_cull_mode(void) + + :return: The current cull mode + +--------------------- + +.. function:: void gs_enable_blending(bool enable) + + Enables/disables blending + + :param enable: *true* to enable, *false* to disable + +--------------------- + +.. function:: void gs_enable_depth_test(bool enable) + + Enables/disables depth testing + + :param enable: *true* to enable, *false* to disable + +--------------------- + +.. function:: void gs_enable_stencil_test(bool enable) + + Enables/disables stencil testing + + :param enable: *true* to enable, *false* to disable + +--------------------- + +.. function:: void gs_enable_stencil_write(bool enable) + + Enables/disables stencil writing + + :param enable: *true* to enable, *false* to disable + +--------------------- + +.. function:: void gs_enable_color(bool red, bool green, bool blue, bool alpha) + + Enables/disables specific color channels + + :param red: *true* to enable red channel, *false* to disable + :param green: *true* to enable green channel, *false* to disable + :param blue: *true* to enable blue channel, *false* to disable + :param alpha: *true* to enable alpha channel, *false* to disable + +--------------------- + +.. function:: void gs_blend_function(enum gs_blend_type src, enum gs_blend_type dest) + + Sets the blend function + + :param src: Blend type for the source + :param dest: Blend type for the destination + +--------------------- + +.. function:: void gs_blend_function_separate(enum gs_blend_type src_c, enum gs_blend_type dest_c, enum gs_blend_type src_a, enum gs_blend_type dest_a) + + Sets the blend function for RGB and alpha separately + + :param src_c: Blend type for the source RGB + :param dest_c: Blend type for the destination RGB + :param src_a: Blend type for the source alpha + :param dest_a: Blend type for the destination alpha + +--------------------- + +.. function:: void gs_depth_function(enum gs_depth_test test) + + Sets the depth function + + :param test: Sets the depth test type + +--------------------- + +.. function:: void gs_stencil_function(enum gs_stencil_side side, enum gs_depth_test test) + + Sets the stencil function + + :param side: Stencil side + :param test: Depth test + +--------------------- + +.. function:: void gs_stencil_op(enum gs_stencil_side side, enum gs_stencil_op_type fail, enum gs_stencil_op_type zfail, enum gs_stencil_op_type zpass) + + Sets the stencil operation + + :param side: Stencil side + :param fail: Operation to perform on stencil test failure + :param zfail: Operation to perform on depth test failure + :param zpass: Operation to perform on depth test success + +--------------------- + +.. function:: void gs_set_viewport(int x, int y, int width, int height) + + Sets the current viewport + + :param x: X position relative to upper left + :param y: Y position relative to upper left + :param width: Width of the viewport + :param height: Height of the viewport + +--------------------- + +.. function:: void gs_get_viewport(struct gs_rect *rect) + + Gets the current viewport + + :param rect: Pointer to recieve viewport rectangle + +--------------------- + +.. function:: void gs_set_scissor_rect(const struct gs_rect *rect) + + Sets or clears the current scissor rectangle + + :rect: Scissor rectangle, or *NULL* to clear + +--------------------- + +.. function:: void gs_ortho(float left, float right, float top, float bottom, float znear, float zfar) + + Sets the projection matrix to an orthographic matrix + +--------------------- + +.. function:: void gs_frustum(float left, float right, float top, float bottom, float znear, float zfar) + + Sets the projection matrix to a frustum matrix + +--------------------- + +.. function:: void gs_projection_push(void) + + Pushes/stores the current projection matrix + +--------------------- + +.. function:: void gs_projection_pop(void) + + Pops/restores the last projection matrix pushed + +--------------------- + + +Texture Functions +----------------- + +.. function:: gs_texture_t *gs_texture_create(uint32_t width, uint32_t height, enum gs_color_format color_format, uint32_t levels, const uint8_t **data, uint32_t flags) + + Creates a texture. + + :param width: Width + :param height: Height + :param color_format: Color format + :param levels: Number of total texture levels. Set to 1 if no + mip-mapping + :param data: Pointer to array of texture data pointers + :param flags: Can be 0 or a bitwise-OR combination of one or + more of the following value: + + - GS_BUILD_MIPMAPS - Automatically builds + mipmaps (Note: not fully tested) + - GS_DYNAMIC - Dynamic + - GS_RENDER_TARGET - Render target + + :return: A new texture object + +--------------------- + +.. function:: gs_texture_t *gs_texture_create_from_file(const char *file) + + Creates a texture from a file. Note that this isn't recommended for + animated gifs -- instead use the :ref:`image_file_helper`. + + :param file: Image file to open + +--------------------- + +.. function:: void gs_texture_destroy(gs_texture_t *tex) + + Destroys a texture + + :param tex: Texture object + +--------------------- + +.. function:: uint32_t gs_texture_get_width(const gs_texture_t *tex) + + Gets the texture's width + + :param tex: Texture object + :return: The texture's width + +--------------------- + +.. function:: uint32_t gs_texture_get_height(const gs_texture_t *tex) + + Gets the texture's height + + :param tex: Texture object + :return: The texture's height + +--------------------- + +.. function:: enum gs_color_format gs_texture_get_color_format(const gs_texture_t *tex) + + Gets the texture's color format + + :param tex: Texture object + :return: The texture's color format + +--------------------- + +.. function:: bool gs_texture_map(gs_texture_t *tex, uint8_t **ptr, uint32_t *linesize) + + Maps a texture. + + :param tex: Texture object + :param ptr: Pointer to receive the pointer to the texture data + to write to + :param linesize: Pointer to receive the line size (pitch) of the + texture + +--------------------- + +.. function:: void gs_texture_unmap(gs_texture_t *tex) + + Unmaps a texture. + + :param tex: Texture object + +--------------------- + +.. function:: void gs_texture_set_image(gs_texture_t *tex, const uint8_t *data, uint32_t linesize, bool invert) + + Sets the image of a dynamic texture + + :param tex: Texture object + :param data: Data to set as the image + :param linesize: Line size (pitch) of the data + :param invert: *true* to invert vertically, *false* otherwise + +--------------------- + +.. function:: gs_texture_t *gs_texture_create_from_iosurface(void *iosurf) + + **Mac only:** Creates a texture from an IOSurface. + + :param iosurf: IOSurface object + +--------------------- + +.. function:: bool gs_texture_rebind_iosurface(gs_texture_t *texture, void *iosurf) + + **Mac only:** Rebinds a texture to another IOSurface + + :param texture: Texture object + :param iosuf: IOSurface object + +--------------------- + +.. function:: gs_texture_t *gs_texture_create_gdi(uint32_t width, uint32_t height) + + **Windows only:** Creates a GDI-interop texture + + :param width: Width + :param height: Height + +--------------------- + +.. function:: void *gs_texture_get_dc(gs_texture_t *gdi_tex) + + **Windows only:** Gets the HDC of a GDI-interop texture. Call + :c:func:`gs_texture_release_dc()` to release the HDC. + + :param gdi_tex: GDI-interop texture object + :return: HDC object + +--------------------- + +.. function:: void gs_texture_release_dc(gs_texture_t *gdi_tex) + + **Windows only:** Releases the HDC of the GDI-interop texture. + + :param gdi_tex: GDI-interop texture object + +--------------------- + +.. function:: gs_texture_t *gs_texture_open_shared(uint32_t handle) + + **Windows only:** Creates a texture from a shared texture handle. + + :param handle: Shared texture handle + :return: A texture object + +--------------------- + +.. function:: bool gs_gdi_texture_available(void) + + **Windows only:** Returns whether GDI-interop textures are available. + + :return: *true* if available, *false* otherwise + +--------------------- + +.. function:: bool gs_shared_texture_available(void) + + **Windows only:** Returns whether shared textures are available. + + :return: *true* if available, *false* otherwise + +--------------------- + + +Cube Texture Functions +---------------------- + +.. function:: gs_texture_t *gs_cubetexture_create(uint32_t size, enum gs_color_format color_format, uint32_t levels, const uint8_t **data, uint32_t flags) + + Creates a cubemap texture. + + :param size: Width/height/depth value + :param color_format: Color format + :param levels: Number of texture levels + :param data: Pointer to array of texture data pointers + :param flags: Can be 0 or a bitwise-OR combination of one or + more of the following value: + + - GS_BUILD_MIPMAPS - Automatically builds + mipmaps (Note: not fully tested) + - GS_DYNAMIC - Dynamic + - GS_RENDER_TARGET - Render target + + :return: A new cube texture object + +--------------------- + +.. function:: void gs_cubetexture_destroy(gs_texture_t *cubetex) + + Destroys a cube texture. + + :param cubetex: Cube texture object + +--------------------- + +.. function:: uint32_t gs_cubetexture_get_size(const gs_texture_t *cubetex) + + Get the width/height/depth value of a cube texture. + + :param cubetex: Cube texture object + :return: The width/height/depth value of the cube texture + +--------------------- + +.. function:: enum gs_color_format gs_cubetexture_get_color_format(const gs_texture_t *cubetex) + + Gets the color format of a cube texture. + + :param cubetex: Cube texture object + :return: The color format of the cube texture + +--------------------- + +.. function:: void gs_cubetexture_set_image(gs_texture_t *cubetex, uint32_t side, const void *data, uint32_t linesize, bool invert) + + Sets an image of a cube texture side. + + :param cubetex: Cube texture object + :param side: Side + :param data: Texture data to set + :param linesize: Line size (pitch) of the texture data + :param invert: *true* to invert texture data, *false* otherwise + +--------------------- + + +Staging Surface Functions +------------------------- + +Staging surfaces are used to efficiently copy textures from VRAM to RAM. + +.. function:: gs_stagesurf_t *gs_stagesurface_create(uint32_t width, uint32_t height, enum gs_color_format color_format) + + Creates a staging surface. + + :param width: Width + :param height: Height + :param color_format: Color format + :return: The staging surface object + +--------------------- + +.. function:: void gs_stagesurface_destroy(gs_stagesurf_t *stagesurf) + + Destroys a staging surface. + + :param stagesurf: Staging surface object + +--------------------- + +.. function:: uint32_t gs_stagesurface_get_width(const gs_stagesurf_t *stagesurf) + uint32_t gs_stagesurface_get_height(const gs_stagesurf_t *stagesurf) + + Gets the width/height of a staging surface object. + + :param stagesurf: Staging surface object + :return: Width/height of the staging surface + +--------------------- + +.. function:: enum gs_color_format gs_stagesurface_get_color_format(const gs_stagesurf_t *stagesurf) + + Gets the color format of a staging surface object. + + :param stagesurf: Staging surface object + :return: Color format of the staging surface + +--------------------- + +.. function:: bool gs_stagesurface_map(gs_stagesurf_t *stagesurf, uint8_t **data, uint32_t *linesize) + + Maps the staging surface texture (for reading). Call + :c:func:`gs_stagesurface_unmap()` to unmap when complete. + + :param stagesurf: Staging surface object + :param data: Pointer to receive texture data pointer + :param linesize: Pointer to receive line size (pitch) of the texture + data + :return: *true* if map successful, *false* otherwise + +--------------------- + +.. function:: void gs_stagesurface_unmap(gs_stagesurf_t *stagesurf) + + Unmaps a staging surface. + + :param stagesurf: Staging surface object + +--------------------- + + +Z-Stencil Functions +------------------- + +.. function:: gs_zstencil_t *gs_zstencil_create(uint32_t width, uint32_t height, enum gs_zstencil_format format) + + Creates a Z-stencil surface object. + + :param width: Width + :param height: Height + :param format: Format + :return: New Z-stencil surface object, or *NULL* if failed + +--------------------- + +.. function:: void gs_zstencil_destroy(gs_zstencil_t *zstencil) + + Destroys a Z-stencil buffer. + + :param zstencil: Z-stencil surface object + +--------------------- + + +Sampler State Functions +----------------------- + +.. function:: gs_samplerstate_t *gs_samplerstate_create(const struct gs_sampler_info *info) + + Creates a sampler state object. + + :param info: Sampler state information + :return: New sampler state object + +--------------------- + +.. function:: void gs_samplerstate_destroy(gs_samplerstate_t *samplerstate) + + Destroys a sampler state object. + + :param samplerstate: Sampler state object + +--------------------- + + +Vertex Buffer Functions +----------------------- + +.. function:: gs_vertbuffer_t *gs_vertexbuffer_create(struct gs_vb_data *data, uint32_t flags) + + Creates a vertex buffer. + + :param data: Vertex buffer data to create vertex buffer with. The + structure should be created with gs_vbdata_create(), + and then buffers in this structure should be allocated + with :c:func:`bmalloc()`, :c:func:`bzalloc()`, or + :c:func:`brealloc()`. The ownership of the gs_vb_data + pointer is then passed to the function, and they should + not be destroyed by the caller once passed + + :param flags: Creation flags. Can be 0 or a bitwise-OR combination + of any of the following values: + + - GS_DYNAMIC - Can be dynamically updated in real time. + - GS_DUP_BUFFER - Do not pass buffer ownership of the + structure or the buffer pointers within the + structure. + + :return: A new vertex buffer object, or *NULL* if failed + +--------------------- + +.. function:: void gs_vertexbuffer_destroy(gs_vertbuffer_t *vertbuffer) + + Destroys a vertex buffer object. + + :param vertbuffer: Vertex buffer object + +--------------------- + +.. function:: void gs_vertexbuffer_flush(gs_vertbuffer_t *vertbuffer) + + Flushes a vertex buffer to its interval vertex data object. To + modify its internal vertex data, call + :c:func:`gs_vertexbuffer_get_data()`. + + Can only be used with dynamic vertex buffer objects. + + :param vertbuffer: Vertex buffer object + +--------------------- + +.. function:: void gs_vertexbuffer_flush_direct(gs_vertbuffer_t *vertbuffer, const struct gs_vb_data *data) + + Directly flushes a vertex buffer to the specified vertex buffer data. + . + + Can only be used with dynamic vertex buffer objects. + + :param vertbuffer: Vertex buffer object + :param data: Vertex buffer data to flush. Components that + don't need to be flushed can be left *NULL* + +--------------------- + +.. function:: struct gs_vb_data *gs_vertexbuffer_get_data(const gs_vertbuffer_t *vertbuffer) + + Gets the vertex buffer data associated with a vertex buffer object. + This data can be changed and vertex buffer can be updated with + :c:func:`gs_vertexbuffer_flush()`. + + Can only be used with dynamic vertex buffer objects. + + :param vertbuffer: Vertex buffer object + :return: Vertex buffer data structure + +--------------------- + + +Index Buffer Functions +---------------------- + +.. function:: gs_indexbuffer_t *gs_indexbuffer_create(enum gs_index_type type, void *indices, size_t num, uint32_t flags) + + Creates an index buffer. + + :param type: Index buffer type + :param indices: Index buffer data. This buffer must be allocated + with :c:func:`bmalloc()`, :c:func:`bzalloc()`, or + :c:func:`bralloc()`, and ownership of this buffer is + passed to the index buffer object. + :param num: Number of indices in the buffer + + :param flags: Creation flags. Can be 0 or a bitwise-OR combination + of any of the following values: + + - GS_DYNAMIC - Can be dynamically updated in real time. + - GS_DUP_BUFFER - Do not pass buffer ownership + + :return: A new index buffer object, or *NULL* if failed + +--------------------- + +.. function:: void gs_indexbuffer_destroy(gs_indexbuffer_t *indexbuffer) + + Destroys an index buffer object. + + :param indexbuffer: Index buffer object + +--------------------- + +.. function:: void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer) + + Flushes a index buffer to its interval index data object. To modify + its internal index data, call :c:func:`gs_indexbuffer_get_data()`. + + Can only be used with dynamic index buffer objects. + + :param indexbuffer: Index buffer object + +--------------------- + +.. function:: void gs_indexbuffer_flush_direct(gs_indexbuffer_t *indexbuffer, const void *data) + + Flushes a index buffer to the specified index buffer data. + + Can only be used with dynamic index buffer objects. + + :param indexbuffer: Index buffer object + :param data: Index buffer data to flush + +--------------------- + +.. function:: void *gs_indexbuffer_get_data(const gs_indexbuffer_t *indexbuffer) + + Gets the index buffer data associated with a index buffer object. + This data can be changed and index buffer can be updated with + :c:func:`gs_indexbuffer_flush()`. + + Can only be used with dynamic index buffer objects. + + :param vertbuffer: Index buffer object + :return: Index buffer data pointer + +--------------------- + +.. function:: size_t gs_indexbuffer_get_num_indices(const gs_indexbuffer_t *indexbuffer) + + Gets the number of indices associated with this index buffer. + + :param indexbuffer: Index buffer object + :return: Number of indices the vertex buffer object has + +--------------------- + +.. function:: enum gs_index_type gs_indexbuffer_get_type(const gs_indexbuffer_t *indexbuffer) + + Gets the type of index buffer. + + :param indexbuffer: Index buffer object + :return: Index buffer type + +--------------------- + + +Display Duplicator (Windows Only) +--------------------------------- + +.. function:: gs_duplicator_t *gs_duplicator_create(int monitor_idx) + +--------------------- + +.. function:: void gs_duplicator_destroy(gs_duplicator_t *duplicator) + +--------------------- + +.. function:: bool gs_duplicator_update_frame(gs_duplicator_t *duplicator) + +--------------------- + +.. function:: gs_texture_t *gs_duplicator_get_texture(gs_duplicator_t *duplicator) + +--------------------- + +.. function:: bool gs_get_duplicator_monitor_info(int monitor_idx, struct gs_monitor_info *monitor_info) + +--------------------- + + +Render Helper Functions +----------------------- + +.. function:: void gs_render_start(bool b_new) + +--------------------- + +.. function:: void gs_render_stop(enum gs_draw_mode mode) + +--------------------- + +.. function:: gs_vertbuffer_t *gs_render_save(void) + +--------------------- + +.. function:: void gs_vertex2f(float x, float y) + +--------------------- + +.. function:: void gs_vertex3f(float x, float y, float z) + +--------------------- + +.. function:: void gs_normal3f(float x, float y, float z) + +--------------------- + +.. function:: void gs_color(uint32_t color) + +--------------------- + +.. function:: void gs_texcoord(float x, float y, int unit) + +--------------------- + +.. function:: void gs_vertex2v(const struct vec2 *v) + +--------------------- + +.. function:: void gs_vertex3v(const struct vec3 *v) + +--------------------- + +.. function:: void gs_normal3v(const struct vec3 *v) + +--------------------- + +.. function:: void gs_color4v(const struct vec4 *v) + +--------------------- + +.. function:: void gs_texcoord2v(const struct vec2 *v, int unit) + +--------------------- + + +Graphics Types +-------------- + +.. type:: typedef struct gs_duplicator gs_duplicator_t +.. type:: typedef struct gs_texture gs_texture_t +.. type:: typedef struct gs_stage_surface gs_stagesurf_t +.. type:: typedef struct gs_zstencil_buffer gs_zstencil_t +.. type:: typedef struct gs_vertex_buffer gs_vertbuffer_t +.. type:: typedef struct gs_index_buffer gs_indexbuffer_t +.. type:: typedef struct gs_sampler_state gs_samplerstate_t +.. type:: typedef struct gs_swap_chain gs_swapchain_t +.. type:: typedef struct gs_texture_render gs_texrender_t +.. type:: typedef struct gs_shader gs_shader_t +.. type:: typedef struct gs_shader_param gs_sparam_t +.. type:: typedef struct gs_device gs_device_t +.. type:: typedef struct graphics_subsystem graphics_t diff --git a/docs/sphinx/reference-libobs-graphics-image-file.rst b/docs/sphinx/reference-libobs-graphics-image-file.rst new file mode 100644 index 0000000..dd52db7 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-image-file.rst @@ -0,0 +1,71 @@ +.. _image_file_helper: + +Image File Helper +================= + +Helper functions/type for easily loading/managing image files, including +animated gif files. + +.. code:: cpp + + #include + +.. type:: struct gs_image_file + + Image file structure + +.. type:: gs_texture_t *gs_image_file.texture + + Texture + +.. type:: typedef struct gs_image_file gs_image_file_t + + Image file type + +--------------------- + +.. function:: void gs_image_file_init(gs_image_file_t *image, const char *file) + + Loads an initializes an image file helper. Does not initialize the + texture; call :c:func:`gs_image_file_init_texture()` to initialize + the texture. + + :param image: Image file helper to initialize + :param file: Path to the image file to load + +--------------------- + +.. function:: void gs_image_file_free(gs_image_file_t *image) + + Frees an image file helper + + :param image: Image file helper + +--------------------- + +.. function:: void gs_image_file_init_texture(gs_image_file_t *image) + + Initializes the texture of an image file helper. This is separate + from :c:func:`gs_image_file_init()` because it allows deferring the + graphics initialization if needed. + + :param image: Image file helper + +--------------------- + +.. function:: bool gs_image_file_tick(gs_image_file_t *image, uint64_t elapsed_time_ns) + + Performs a tick operation on the image file helper (used primarily + for animated file). Does not update the texture until + :c:func:`gs_image_file_update_texture()` is called. + + :param image: Image file helper + :param elapsed_time_ns: Elapsed time in nanoseconds + +--------------------- + +.. function:: void gs_image_file_update_texture(gs_image_file_t *image) + + Updates the texture (used primarily for animated files) + + :param image: Image file helper diff --git a/docs/sphinx/reference-libobs-graphics-math.rst b/docs/sphinx/reference-libobs-graphics-math.rst new file mode 100644 index 0000000..c5cffe4 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-math.rst @@ -0,0 +1,39 @@ +Extra Math Functions/Macros +=========================== + +.. code:: + + #include + +Helper functions/macros for graphics math. + +.. function:: RAD(val) + + Macro that converts a floating point degrees value to radians. + +.. function:: DEG(val) + + Macro that converts a floating point radians value to degrees. + +**LARGE_EPSILON** 1e-2f + + Large epsilon value. + +**EPSILON** 1e-4f + + Epsilon value. + +**TINY_EPSILON** 1e-5f + + Tiny Epsilon value. + +**M_INFINITE** 3.4e38f + + Infinite value + +--------------------- + +.. function:: float rand_float(int positive_only) + + Generates a random floating point value (from -1.0f..1.0f, or + 0.0f..1.0f if *positive_only* is set). diff --git a/docs/sphinx/reference-libobs-graphics-matrix4.rst b/docs/sphinx/reference-libobs-graphics-matrix4.rst new file mode 100644 index 0000000..6924cbc --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-matrix4.rst @@ -0,0 +1,151 @@ +Matrix +====== + +.. code:: cpp + + #include + +.. type:: struct matrix4 + + Matrix structure + +.. member:: struct vec4 matrix4.x + + X component vector + +.. member:: struct vec4 matrix4.y + + Y component vector + +.. member:: struct vec4 matrix4.z + + Z component vector + +.. member:: struct vec4 matrix4.w + + W component vector + +--------------------- + +.. function:: void matrix4_copy(struct matrix4 *dst, const struct matrix4 *m) + + Copies a matrix + + :param dst: Destination matrix + :param m: Matrix to copy + +--------------------- + +.. function:: void matrix4_identity(struct matrix4 *dst) + + Sets an identity matrix + + :param dst: Destination matrix + +--------------------- + +.. function:: void matrix4_from_quat(struct matrix4 *dst, const struct quat *q) + + Converts a quaternion to a matrix + + :param dst: Destination matrix + :param q: Quaternion to convert + +--------------------- + +.. function:: void matrix4_from_axisang(struct matrix4 *dst, const struct axisang *aa) + + Converts an axis angle to a matrix + + :param dst: Destination matrix + :param aa: Axis angle to convert + +--------------------- + +.. function:: void matrix4_mul(struct matrix4 *dst, const struct matrix4 *m1, const struct matrix4 *m2) + + Multiples two matrices + + :param dst: Destination matrix + :param m1: Matrix 1 + :param m2: Matrix 2 + +--------------------- + +.. function:: float matrix4_determinant(const struct matrix4 *m) + + Gets the determinant value of a matrix + + :param m: Matrix + :return: Determinant + +--------------------- + +.. function:: void matrix4_translate3v(struct matrix4 *dst, const struct matrix4 *m, const struct vec3 *v) + void matrix4_translate3f(struct matrix4 *dst, const struct matrix4 *m, float x, float y, float z) + + Translates the matrix by a 3-component vector + + :param dst: Destination matrix + :param m: Matrix to translate + :param v: Translation vector + +--------------------- + +.. function:: void matrix4_translate4v(struct matrix4 *dst, const struct matrix4 *m, const struct vec4 *v) + + Translates the matrix by a 4-component vector + + :param dst: Destination matrix + :param m: Matrix to translate + :param v: Translation vector + +--------------------- + +.. function:: void matrix4_rotate(struct matrix4 *dst, const struct matrix4 *m, const struct quat *q) + + Rotates a matrix by a quaternion + + :param dst: Destination matrix + :param m: Matrix to rotate + :param q: Rotation quaternion + +--------------------- + +.. function:: void matrix4_rotate_aa(struct matrix4 *dst, const struct matrix4 *m, const struct axisang *aa) + void matrix4_rotate_aa4f(struct matrix4 *dst, const struct matrix4 *m, float x, float y, float z, float rot) + + Rotates a matrix by an axis angle + + :param dst: Destination matrix + :param m: Matrix to rotate + :param aa: Rotation anxis angle + +--------------------- + +.. function:: void matrix4_scale(struct matrix4 *dst, const struct matrix4 *m, const struct vec3 *v) + void matrix4_scale3f(struct matrix4 *dst, const struct matrix4 *m, float x, float y, float z) + + Scales each matrix component by the components of a 3-component vector + + :param dst: Destination matrix + :param m: Matrix to scale + :param v: Scale vector + +--------------------- + +.. function:: bool matrix4_inv(struct matrix4 *dst, const struct matrix4 *m) + + Inverts a matrix + + :param dst: Destination matrix + :param m: Matrix to invert + +--------------------- + +.. function:: void matrix4_transpose(struct matrix4 *dst, const struct matrix4 *m) + + Transposes a matrix + + :param dst: Destination matrix + :param m: Matrix to transpose diff --git a/docs/sphinx/reference-libobs-graphics-quat.rst b/docs/sphinx/reference-libobs-graphics-quat.rst new file mode 100644 index 0000000..fbaa4f4 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-quat.rst @@ -0,0 +1,228 @@ +Quaternion +========== + +.. code:: cpp + + #include + +.. type:: struct quat + + Two component quaternion structure. + +.. member:: float quat.x + + X component + +.. member:: float quat.y + + Y component + +.. member:: float quat.z + + Z component + +.. member:: float quat.w + + W component + +.. member:: float quat.ptr[4] + + Unioned array of all components + +--------------------- + +.. function:: void quat_identity(struct quat *dst) + + Sets a quaternion to {0.0f, 0.0f, 0.0f, 1.0f}. + + :param dst: Destination + +--------------------- + +.. function:: void quat_set(struct quat *dst, float x, float y) + + Sets the individual components of a quaternion. + + :param dst: Destination + :param x: X component + :param y: Y component + :param y: Z component + :param w: W component + +--------------------- + +.. function:: void quat_copy(struct quat *dst, const struct quat *v) + + Copies a quaternion + + :param dst: Destination + :param v: Quaternion to copy + +--------------------- + +.. function:: void quat_add(struct quat *dst, const struct quat *v1, const struct quat *v2) + + Adds two quaternions + + :param dst: Destination + :param v1: Quaternion 1 + :param v2: Quaternion 2 + +--------------------- + +.. function:: void quat_sub(struct quat *dst, const struct quat *v1, const struct quat *v2) + + Subtracts two quaternions + + :param dst: Destination + :param v1: Quaternion being subtracted from + :param v2: Quaternion being subtracted + +--------------------- + +.. function:: void quat_mul(struct quat *dst, const struct quat *v1, const struct quat *v2) + + Multiplies two quaternions + + :param dst: Destination + :param v1: Quaternion 1 + :param v2: Quaternion 2 + +--------------------- + +.. function:: void quat_addf(struct quat *dst, const struct quat *v, float f) + + Adds a floating point to all components + + :param dst: Destination + :param dst: Quaternion + :param f: Floating point + +--------------------- + +.. function:: void quat_subf(struct quat *dst, const struct quat *v, float f) + + Subtracts a floating point from all components + + :param dst: Destination + :param v: Quaternion being subtracted from + :param f: Floating point being subtracted + +--------------------- + +.. function:: void quat_mulf(struct quat *dst, const struct quat *v, float f) + + Multiplies a floating point with all components + + :param dst: Destination + :param dst: Quaternion + :param f: Floating point + +--------------------- + +.. function:: void quat_inv(struct quat *dst, const struct quat *v) + + Inverts a quaternion + + :param dst: Destination + :param v: Quaternion to invert + +--------------------- + +.. function:: float quat_dot(const struct quat *v1, const struct quat *v2) + + Performs a dot product between two quaternions + + :param v1: Quaternion 1 + :param v2: Quaternion 2 + :return: Result of the dot product + +--------------------- + +.. function:: float quat_len(const struct quat *v) + + Gets the length of a quaternion + + :param v: Quaternion + :return: The quaternion's length + +--------------------- + +.. function:: float quat_dist(const struct quat *v1, const struct quat *v2) + + Gets the distance between two quaternions + + :param v1: Quaternion 1 + :param v2: Quaternion 2 + :return: Distance between the two quaternions + +--------------------- + +.. function:: void quat_from_axisang(struct quat *dst, const struct axisang *aa) + + Converts an axis angle to a quaternion + + :param dst: Destination quaternion + :param aa: Axis angle + +--------------------- + +.. function:: void quat_from_matrix4(struct quat *dst, const struct matrix4 *m) + + Converts the rotational properties of a matrix to a quaternion + + :param dst: Destination quaternion + :param m: Matrix to convert + +--------------------- + +.. function:: void quat_get_dir(struct vec3 *dst, const struct quat *q) + + Converts a quaternion to a directional vector + + :param dst: Destination 3-component vector + :param q: Quaternion + +--------------------- + +.. function:: void quat_set_look_dir(struct quat *dst, const struct vec3 *dir) + + Creates a quaternion from a specific "look" direction + + :param dst: Destination quaternion + :param dir: 3-component vector representing the look direction + +--------------------- + +.. function:: void quat_interpolate(struct quat *dst, const struct quat *q1, const struct quat *q2, float t) + + Linearly interpolates two quaternions + + :param dst: Destination quaternion + :param q1: Quaternion 1 + :param q2: Quaternion 2 + :param t: Time value (0.0f..1.0f) + +--------------------- + +.. function:: void quat_get_tangent(struct quat *dst, const struct quat *prev, const struct quat *q, const struct quat *next) + + Gets a tangent value for the center of three rotational values + + :param dst: Destination quaternion + :param prev: Previous rotation + :param q: Rotation to get tangent for + :param next: Next rotation + +--------------------- + +.. function:: void quat_interpolate_cubic(struct quat *dst, const struct quat *q1, const struct quat *q2, const struct quat *m1, const struct quat *m2, float t) + + Performs cubic interpolation between two quaternions + + :param dst: Destination quaternion + :param q1: Quaternion 1 + :param q2: Quaternion 2 + :param m1: Tangent 1 + :param m2: Tangent 2 + :param t: Time value (0.0f..1.0f) diff --git a/docs/sphinx/reference-libobs-graphics-vec2.rst b/docs/sphinx/reference-libobs-graphics-vec2.rst new file mode 100644 index 0000000..41c7c82 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-vec2.rst @@ -0,0 +1,253 @@ +2-Component Vector +================== + +.. code:: cpp + + #include + +.. type:: struct vec2 + + Two component vector structure. + +.. member:: float vec2.x + + X component + +.. member:: float vec2.y + + Y component + +.. member:: float vec2.ptr[2] + + Unioned array of both components + +--------------------- + +.. function:: void vec2_zero(struct vec2 *dst) + + Zeroes a vector + + :param dst: Destination + +--------------------- + +.. function:: void vec2_set(struct vec2 *dst, float x, float y) + + Sets the individual components of a 2-component vector. + + :param dst: Destination + :param x: X component + :param y: Y component + +--------------------- + +.. function:: void vec2_copy(struct vec2 *dst, const struct vec2 *v) + + Copies a vector + + :param dst: Destination + :param v: Vector to copy + +--------------------- + +.. function:: void vec2_add(struct vec2 *dst, const struct vec2 *v1, const struct vec2 *v2) + + Adds two vectors + + :param dst: Destination + :param v1: Vector 1 + :param v2: Vector 2 + +--------------------- + +.. function:: void vec2_sub(struct vec2 *dst, const struct vec2 *v1, const struct vec2 *v2) + + Subtracts two vectors + + :param dst: Destination + :param v1: Vector being subtracted from + :param v2: Vector being subtracted + +--------------------- + +.. function:: void vec2_mul(struct vec2 *dst, const struct vec2 *v1, const struct vec2 *v2) + + Multiplies two vectors + + :param dst: Destination + :param v1: Vector 1 + :param v2: Vector 2 + +--------------------- + +.. function:: void vec2_div(struct vec2 *dst, const struct vec2 *v1, const struct vec2 *v2) + + Divides two vectors + + :param dst: Destination + :param v1: Dividend + :param v2: Divisor + +--------------------- + +.. function:: void vec2_addf(struct vec2 *dst, const struct vec2 *v, float f) + + Adds a floating point to all components + + :param dst: Destination + :param dst: Vector + :param f: Floating point + +--------------------- + +.. function:: void vec2_subf(struct vec2 *dst, const struct vec2 *v, float f) + + Subtracts a floating point from all components + + :param dst: Destination + :param v: Vector being subtracted from + :param f: Floating point being subtracted + +--------------------- + +.. function:: void vec2_mulf(struct vec2 *dst, const struct vec2 *v, float f) + + Multiplies a floating point with all components + + :param dst: Destination + :param dst: Vector + :param f: Floating point + +--------------------- + +.. function:: void vec2_divf(struct vec2 *dst, const struct vec2 *v, float f) + + Divides a floating point from all components + + :param dst: Destination + :param v: Vector (dividend) + :param f: Floating point (divisor) + +--------------------- + +.. function:: void vec2_neg(struct vec2 *dst, const struct vec2 *v) + + Negates a vector + + :param dst: Destination + :param v: Vector to negate + +--------------------- + +.. function:: float vec2_dot(const struct vec2 *v1, const struct vec2 *v2) + + Performs a dot product between two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :return: Result of the dot product + +--------------------- + +.. function:: float vec2_len(const struct vec2 *v) + + Gets the length of a vector + + :param v: Vector + :return: The vector's length + +--------------------- + +.. function:: float vec2_dist(const struct vec2 *v1, const struct vec2 *v2) + + Gets the distance between two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :return: Distance between the two vectors + +--------------------- + +.. function:: void vec2_minf(struct vec2 *dst, const struct vec2 *v, float val) + + Gets the minimum values between a vector's components and a floating point + + :param dst: Destination + :param v: Vector + :param val: Floating point + +--------------------- + +.. function:: void vec2_min(struct vec2 *dst, const struct vec2 *v, const struct vec2 *min_v) + + Gets the minimum values between two vectors + + :param dst: Destination + :param v: Vector 1 + :param min_v: Vector 2 + +--------------------- + +.. function:: void vec2_maxf(struct vec2 *dst, const struct vec2 *v, float val) + + Gets the maximum values between a vector's components and a floating point + + :param dst: Destination + :param v: Vector + :param val: Floating point + +--------------------- + +.. function:: void vec2_max(struct vec2 *dst, const struct vec2 *v, const struct vec2 *max_v) + + Gets the maximum values between two vectors + + :param dst: Destination + :param v: Vector 1 + :param max_v: Vector 2 + +--------------------- + +.. function:: void vec2_abs(struct vec2 *dst, const struct vec2 *v) + + Gets the absolute values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: void vec2_floor(struct vec2 *dst, const struct vec2 *v) + + Gets the floor values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: void vec2_ceil(struct vec2 *dst, const struct vec2 *v) + + Gets the ceiling values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: int vec2_close(const struct vec2 *v1, const struct vec2 *v2, float epsilon) + + Compares two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :param epsilon: Maximum precision for comparison + +--------------------- + +.. function:: void vec2_norm(struct vec2 *dst, const struct vec2 *v) + + Normalizes a vector + + :param dst: Desination + :param v: Vector to normalize diff --git a/docs/sphinx/reference-libobs-graphics-vec3.rst b/docs/sphinx/reference-libobs-graphics-vec3.rst new file mode 100644 index 0000000..7a2e938 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-vec3.rst @@ -0,0 +1,306 @@ +3-Component Vector +================== + +.. code:: cpp + + #include + +.. type:: struct vec3 + + Two component vector structure. + +.. member:: float vec3.x + + X component + +.. member:: float vec3.y + + Y component + +.. member:: float vec3.z + + Z component + +.. member:: float vec3.ptr[3] + + Unioned array of all components + +--------------------- + +.. function:: void vec3_zero(struct vec3 *dst) + + Zeroes a vector + + :param dst: Destination + +--------------------- + +.. function:: void vec3_set(struct vec3 *dst, float x, float y) + + Sets the individual components of a 3-component vector. + + :param dst: Destination + :param x: X component + :param y: Y component + :param y: Z component + +--------------------- + +.. function:: void vec3_copy(struct vec3 *dst, const struct vec3 *v) + + Copies a vector + + :param dst: Destination + :param v: Vector to copy + +--------------------- + +.. function:: void vec3_from_vec4(struct vec3 *dst, const struct vec4 *v) + + Creates a 3-component vector from a 4-component vector + + :param dst: 3-component vector destination + :param v: 4-component vector + +--------------------- + +.. function:: void vec3_add(struct vec3 *dst, const struct vec3 *v1, const struct vec3 *v2) + + Adds two vectors + + :param dst: Destination + :param v1: Vector 1 + :param v2: Vector 2 + +--------------------- + +.. function:: void vec3_sub(struct vec3 *dst, const struct vec3 *v1, const struct vec3 *v2) + + Subtracts two vectors + + :param dst: Destination + :param v1: Vector being subtracted from + :param v2: Vector being subtracted + +--------------------- + +.. function:: void vec3_mul(struct vec3 *dst, const struct vec3 *v1, const struct vec3 *v2) + + Multiplies two vectors + + :param dst: Destination + :param v1: Vector 1 + :param v2: Vector 2 + +--------------------- + +.. function:: void vec3_div(struct vec3 *dst, const struct vec3 *v1, const struct vec3 *v2) + + Divides two vectors + + :param dst: Destination + :param v1: Dividend + :param v2: Divisor + +--------------------- + +.. function:: void vec3_addf(struct vec3 *dst, const struct vec3 *v, float f) + + Adds a floating point to all components + + :param dst: Destination + :param dst: Vector + :param f: Floating point + +--------------------- + +.. function:: void vec3_subf(struct vec3 *dst, const struct vec3 *v, float f) + + Subtracts a floating point from all components + + :param dst: Destination + :param v: Vector being subtracted from + :param f: Floating point being subtracted + +--------------------- + +.. function:: void vec3_mulf(struct vec3 *dst, const struct vec3 *v, float f) + + Multiplies a floating point with all components + + :param dst: Destination + :param dst: Vector + :param f: Floating point + +--------------------- + +.. function:: void vec3_divf(struct vec3 *dst, const struct vec3 *v, float f) + + Divides a floating point from all components + + :param dst: Destination + :param v: Vector (dividend) + :param f: Floating point (divisor) + +--------------------- + +.. function:: void vec3_neg(struct vec3 *dst, const struct vec3 *v) + + Negates a vector + + :param dst: Destination + :param v: Vector to negate + +--------------------- + +.. function:: float vec3_dot(const struct vec3 *v1, const struct vec3 *v2) + + Performs a dot product between two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :return: Result of the dot product + +--------------------- + +.. function:: void vec3_cross(struct vec3 *dst, const struct vec3 *v1, const struct vec3 *v2) + + Performs a cross product between two vectors + + :param dst: Destination + :param v1: Vector 1 + :param v2: Vector 2 + +--------------------- + +.. function:: float vec3_len(const struct vec3 *v) + + Gets the length of a vector + + :param v: Vector + :return: The vector's length + +--------------------- + +.. function:: float vec3_dist(const struct vec3 *v1, const struct vec3 *v2) + + Gets the distance between two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :return: Distance between the two vectors + +--------------------- + +.. function:: void vec3_minf(struct vec3 *dst, const struct vec3 *v, float val) + + Gets the minimum values between a vector's components and a floating point + + :param dst: Destination + :param v: Vector + :param val: Floating point + +--------------------- + +.. function:: void vec3_min(struct vec3 *dst, const struct vec3 *v, const struct vec3 *min_v) + + Gets the minimum values between two vectors + + :param dst: Destination + :param v: Vector 1 + :param min_v: Vector 2 + +--------------------- + +.. function:: void vec3_maxf(struct vec3 *dst, const struct vec3 *v, float val) + + Gets the maximum values between a vector's components and a floating point + + :param dst: Destination + :param v: Vector + :param val: Floating point + +--------------------- + +.. function:: void vec3_max(struct vec3 *dst, const struct vec3 *v, const struct vec3 *max_v) + + Gets the maximum values between two vectors + + :param dst: Destination + :param v: Vector 1 + :param max_v: Vector 2 + +--------------------- + +.. function:: void vec3_abs(struct vec3 *dst, const struct vec3 *v) + + Gets the absolute values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: void vec3_floor(struct vec3 *dst, const struct vec3 *v) + + Gets the floor values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: void vec3_ceil(struct vec3 *dst, const struct vec3 *v) + + Gets the ceiling values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: int vec3_close(const struct vec3 *v1, const struct vec3 *v2, float epsilon) + + Compares two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :param epsilon: Maximum precision for comparison + +--------------------- + +.. function:: void vec3_norm(struct vec3 *dst, const struct vec3 *v) + + Normalizes a vector + + :param dst: Desination + :param v: Vector to normalize + +--------------------- + +.. function:: void vec3_transform(struct vec3 *dst, const struct vec3 *v, const struct matrix4 *m) + + Transforms a vector + + :param dst: Destination + :param v: Vector + :param m: Matrix + +--------------------- + +.. function:: void vec3_rotate(struct vec3 *dst, const struct vec3 *v, const struct matrix3 *m) + + Rotates a vector + + :param dst: Destination + :param v: Vector + :param m: Matrix + +--------------------- + +.. function:: void vec3_rand(struct vec3 *dst, int positive_only) + + Generates a random vector + + :param dst: Destination + :param positive_only: *true* if positive only, *false* otherwise diff --git a/docs/sphinx/reference-libobs-graphics-vec4.rst b/docs/sphinx/reference-libobs-graphics-vec4.rst new file mode 100644 index 0000000..15725e8 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics-vec4.rst @@ -0,0 +1,282 @@ +4-Component Vector +================== + +.. code:: cpp + + #include + +.. type:: struct vec4 + + Two component vector structure. + +.. member:: float vec4.x + + X component + +.. member:: float vec4.y + + Y component + +.. member:: float vec4.z + + Z component + +.. member:: float vec4.w + + W component + +.. member:: float vec4.ptr[4] + + Unioned array of all components + +--------------------- + +.. function:: void vec4_zero(struct vec4 *dst) + + Zeroes a vector + + :param dst: Destination + +--------------------- + +.. function:: void vec4_set(struct vec4 *dst, float x, float y, float z, float w) + + Sets the individual components of a 4-component vector. + + :param dst: Destination + :param x: X component + :param y: Y component + :param z: Z component + :param w: W component + +--------------------- + +.. function:: void vec4_copy(struct vec4 *dst, const struct vec4 *v) + + Copies a vector + + :param dst: Destination + :param v: Vector to copy + +--------------------- + +.. function:: void vec4_from_vec3(struct vec4 *dst, const struct vec3 *v) + + Creates a 4-component vector from a 3-component vector + + :param dst: 4-component vector destination + :param v: 3-component vector + +--------------------- + +.. function:: void vec4_add(struct vec4 *dst, const struct vec4 *v1, const struct vec4 *v2) + + Adds two vectors + + :param dst: Destination + :param v1: Vector 1 + :param v2: Vector 2 + +--------------------- + +.. function:: void vec4_sub(struct vec4 *dst, const struct vec4 *v1, const struct vec4 *v2) + + Subtracts two vectors + + :param dst: Destination + :param v1: Vector being subtracted from + :param v2: Vector being subtracted + +--------------------- + +.. function:: void vec4_mul(struct vec4 *dst, const struct vec4 *v1, const struct vec4 *v2) + + Multiplies two vectors + + :param dst: Destination + :param v1: Vector 1 + :param v2: Vector 2 + +--------------------- + +.. function:: void vec4_div(struct vec4 *dst, const struct vec4 *v1, const struct vec4 *v2) + + Divides two vectors + + :param dst: Destination + :param v1: Dividend + :param v2: Divisor + +--------------------- + +.. function:: void vec4_addf(struct vec4 *dst, const struct vec4 *v, float f) + + Adds a floating point to all components + + :param dst: Destination + :param dst: Vector + :param f: Floating point + +--------------------- + +.. function:: void vec4_subf(struct vec4 *dst, const struct vec4 *v, float f) + + Subtracts a floating point from all components + + :param dst: Destination + :param v: Vector being subtracted from + :param f: Floating point being subtracted + +--------------------- + +.. function:: void vec4_mulf(struct vec4 *dst, const struct vec4 *v, float f) + + Multiplies a floating point with all components + + :param dst: Destination + :param dst: Vector + :param f: Floating point + +--------------------- + +.. function:: void vec4_divf(struct vec4 *dst, const struct vec4 *v, float f) + + Divides a floating point from all components + + :param dst: Destination + :param v: Vector (dividend) + :param f: Floating point (divisor) + +--------------------- + +.. function:: void vec4_neg(struct vec4 *dst, const struct vec4 *v) + + Negates a vector + + :param dst: Destination + :param v: Vector to negate + +--------------------- + +.. function:: float vec4_dot(const struct vec4 *v1, const struct vec4 *v2) + + Performs a dot product between two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :return: Result of the dot product + +--------------------- + +.. function:: float vec4_len(const struct vec4 *v) + + Gets the length of a vector + + :param v: Vector + :return: The vector's length + +--------------------- + +.. function:: float vec4_dist(const struct vec4 *v1, const struct vec4 *v2) + + Gets the distance between two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :return: Distance between the two vectors + +--------------------- + +.. function:: void vec4_minf(struct vec4 *dst, const struct vec4 *v, float val) + + Gets the minimum values between a vector's components and a floating point + + :param dst: Destination + :param v: Vector + :param val: Floating point + +--------------------- + +.. function:: void vec4_min(struct vec4 *dst, const struct vec4 *v, const struct vec4 *min_v) + + Gets the minimum values between two vectors + + :param dst: Destination + :param v: Vector 1 + :param min_v: Vector 2 + +--------------------- + +.. function:: void vec4_maxf(struct vec4 *dst, const struct vec4 *v, float val) + + Gets the maximum values between a vector's components and a floating point + + :param dst: Destination + :param v: Vector + :param val: Floating point + +--------------------- + +.. function:: void vec4_max(struct vec4 *dst, const struct vec4 *v, const struct vec4 *max_v) + + Gets the maximum values between two vectors + + :param dst: Destination + :param v: Vector 1 + :param max_v: Vector 2 + +--------------------- + +.. function:: void vec4_abs(struct vec4 *dst, const struct vec4 *v) + + Gets the absolute values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: void vec4_floor(struct vec4 *dst, const struct vec4 *v) + + Gets the floor values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: void vec4_ceil(struct vec4 *dst, const struct vec4 *v) + + Gets the ceiling values of each component + + :param dst: Destination + :param v: Vector + +--------------------- + +.. function:: int vec4_close(const struct vec4 *v1, const struct vec4 *v2, float epsilon) + + Compares two vectors + + :param v1: Vector 1 + :param v2: Vector 2 + :param epsilon: Maximum precision for comparison + +--------------------- + +.. function:: void vec4_norm(struct vec4 *dst, const struct vec4 *v) + + Normalizes a vector + + :param dst: Desination + :param v: Vector to normalize + +--------------------- + +.. function:: void vec4_transform(struct vec4 *dst, const struct vec4 *v, const struct matrix4 *m) + + Transforms a vector + + :param dst: Destination + :param v: Vector + :param m: Matrix diff --git a/docs/sphinx/reference-libobs-graphics.rst b/docs/sphinx/reference-libobs-graphics.rst new file mode 100644 index 0000000..d3553b2 --- /dev/null +++ b/docs/sphinx/reference-libobs-graphics.rst @@ -0,0 +1,17 @@ +Graphics API Reference (libobs/graphics) +======================================== + +.. toctree:: + :maxdepth: 2 + + reference-libobs-graphics-effects + reference-libobs-graphics-vec2 + reference-libobs-graphics-vec3 + reference-libobs-graphics-vec4 + reference-libobs-graphics-quat + reference-libobs-graphics-matrix4 + reference-libobs-graphics-math + reference-libobs-graphics-image-file + reference-libobs-graphics-axisang + reference-libobs-graphics-graphics + diff --git a/docs/sphinx/reference-libobs-media-io.rst b/docs/sphinx/reference-libobs-media-io.rst new file mode 100644 index 0000000..6e978ce --- /dev/null +++ b/docs/sphinx/reference-libobs-media-io.rst @@ -0,0 +1,463 @@ +Media I/O API Reference (libobs/media-io) +========================================= + +.. code:: cpp + + #include + + +Video Handler +------------- + +.. type:: video_t + + Video output handler object + +--------------------- + +.. type:: enum video_format + + Video format. Can be one of the following values: + + - VIDEO_FORMAT_I420 + - VIDEO_FORMAT_NV12 + + - VIDEO_FORMAT_YVYU + - VIDEO_FORMAT_YUY2 + - VIDEO_FORMAT_UYVY + + - VIDEO_FORMAT_RGBA + - VIDEO_FORMAT_BGRA + - VIDEO_FORMAT_BGRX + - VIDEO_FORMAT_Y800 + + - VIDEO_FORMAT_I444 + +--------------------- + +.. type:: enum video_colorspace + + YUV color space. Can be one of the following values: + + - VIDEO_CS_DEFAULT - Equivalent to VIDEO_CS_601 + - VIDEO_CS_601 - 601 color space + - VIDEO_CS_709 - 709 color space + +--------------------- + +.. type:: enum video_range_type + + YUV color range. + + - VIDEO_RANGE_DEFAULT - Equivalent to VIDEO_RANGE_PARTIAL + - VIDEO_RANGE_PARTIAL - Partial range + - VIDEO_RANGE_FULL - Full range + +--------------------- + +.. type:: struct video_data + + Video frame structure. + +.. member:: uint8_t *video_data.data[MAX_AV_PLANES] +.. member:: uint32_t video_data.linesize[MAX_AV_PLANES] +.. member:: uint64_t video_data.timestamp + +--------------------- + +.. type:: struct video_output_info + + Video output handler information + +.. member:: const char *video_output_info.name +.. member:: enum video_format video_output_info.format +.. member:: uint32_t video_output_info.fps_num +.. member:: uint32_t video_output_info.fps_den +.. member:: uint32_t video_output_info.width +.. member:: uint32_t video_output_info.height +.. member:: size_t video_output_info.cache_size +.. member:: enum video_colorspace video_output_info.colorspace +.. member:: enum video_range_type video_output_info.range + +--------------------- + +.. function:: enum video_format video_format_from_fourcc(uint32_t fourcc) + + Converts a fourcc value to a video format. + + :param forcecc: Fourcc value + :return: Video format + +--------------------- + +.. function:: bool video_format_get_parameters(enum video_colorspace color_space, enum video_range_type range, float matrix[16], float min_range[3], float max_range[3]) + + Converts a color space/range to matrix/min/max values. + + :param color_space: Color space to convert + :param range: Color range to convert + :param matrix: Pointer to the matrix + :param min_range: Pointer to get the minimum range value + :param max_range: Pointer to get the maximum range value + +--------------------- + +.. function:: bool video_output_connect(video_t *video, const struct video_scale_info *conversion, void (*callback)(void *param, struct video_data *frame), void *param) + + Connects a raw video callback to the video output handler. + + :param video: Video output handler object + :param callback: Callback to receive video data + :param param: Private data to pass to the callback + +--------------------- + +.. function:: void video_output_disconnect(video_t *video, void (*callback)(void *param, struct video_data *frame), void *param) + + Disconnects a raw video callback from the video output handler. + + :param video: Video output handler object + :param callback: Callback + :param param: Private data + +--------------------- + +.. function:: const struct video_output_info *video_output_get_info(const video_t *video) + + Gets the full video information of the video output handler. + + :param video: Video output handler object + :return: Video output info structure pointer + +--------------------- + +.. function:: uint64_t video_output_get_frame_time(const video_t *video) + + Gets the frame interval of the video output handler. + + :param video: Video output handler object + :return: Video frame interval in nanoseconds + +--------------------- + +.. function:: enum video_format video_output_get_format(const video_t *video) + + Gets the video format of the video output handler. + + :param video: Video output handler object + :return: Video format + +--------------------- + +.. function:: uint32_t video_output_get_width(const video_t *video) +.. function:: uint32_t video_output_get_height(const video_t *video) + + Gets the width/height of the video output handler. + + :param video: Video output handler object + :return: Width/height + +--------------------- + +.. function:: double video_output_get_frame_rate(const video_t *video) + + Gets the frame rate (as a floating point) of the video output + handler. + + :param video: Video output handler object + :return: Frame rate + +--------------------- + +.. function:: uint32_t video_output_get_skipped_frames(const video_t *video) + + Gets the skipped frame count of the video output handler. + + :param video: Video output handler object + :return: Skipped frame count + +--------------------- + +.. function:: uint32_t video_output_get_total_frames(const video_t *video) + + Gets the total frames processed of the video output handler. + + :param video: Video output handler object + :return: Total frames processed + +--------------------- + + +Audio Handler +------------- + +.. type:: audio_t + +--------------------- + +.. type:: enum audio_format + + Audio format. Can be one of the following values: + + - AUDIO_FORMAT_UNKNOWN + - AUDIO_FORMAT_U8BIT + - AUDIO_FORMAT_16BIT + - AUDIO_FORMAT_32BIT + - AUDIO_FORMAT_FLOAT + - AUDIO_FORMAT_U8BIT_PLANAR + - AUDIO_FORMAT_16BIT_PLANAR + - AUDIO_FORMAT_32BIT_PLANAR + - AUDIO_FORMAT_FLOAT_PLANAR + +--------------------- + +.. type:: enum speaker_layout + + Speaker layout. Can be one of the following values: + + - SPEAKERS_UNKNOWN + - SPEAKERS_MONO + - SPEAKERS_STEREO + - SPEAKERS_2POINT1 + - SPEAKERS_QUAD + - SPEAKERS_4POINT1 + - SPEAKERS_5POINT1 + - SPEAKERS_5POINT1_SURROUND + - SPEAKERS_7POINT1 + - SPEAKERS_7POINT1_SURROUND + - SPEAKERS_SURROUND + +--------------------- + +.. type:: struct audio_data + + Audio data structure. + +.. member:: uint8_t *audio_data.data[MAX_AV_PLANES] +.. member:: uint32_t audio_data.frames +.. member:: uint64_t audio_data.timestamp + +--------------------- + +.. type:: struct audio_output_data +.. member:: float *audio_output_data.data[MAX_AUDIO_CHANNELS] + +--------------------- + +.. type:: struct audio_output_info +.. member:: const char *audio_output_info.name +.. member:: uint32_t audio_output_info.samples_per_sec +.. member:: enum audio_format audio_output_info.format +.. member:: enum speaker_layout audio_output_info.speakers +.. member:: audio_input_callback_t audio_output_info.input_callback +.. member:: void *audio_output_info.input_param + +--------------------- + +.. type:: struct audio_convert_info +.. member:: uint32_t audio_convert_info.samples_per_sec +.. member:: enum audio_format audio_convert_info.format +.. member:: enum speaker_layout audio_convert_info.speakers + +--------------------- + +.. type:: typedef bool (*audio_input_callback_t)(void *param, uint64_t start_ts, uint64_t end_ts, uint64_t *new_ts, uint32_t active_mixers, struct audio_output_data *mixes) + + Audio input callback (typically used internally). + +--------------------- + +.. function:: uint32_t get_audio_channels(enum speaker_layout speakers) + + Converts a speaker layout to its audio channel count. + + :param speakers: Speaker layout enumeration + :return: Channel count + +--------------------- + +.. function:: size_t get_audio_bytes_per_channel(enum audio_format format) + + Gets the audio bytes per channel for a specific audio format. + + :param format: Audio format + :return: Bytes per channel + +--------------------- + +.. function:: bool is_audio_planar(enum audio_format format) + + Returns whether the audio format is a planar format. + + :param format: Audio format + :return: *true* if audio is planar, *false* otherwise + +--------------------- + +.. function:: size_t get_audio_planes(enum audio_format format, enum speaker_layout speakers) + + Gets the number of audio planes for a specific audio format and + speaker layout. + + :param format: Audio format + :param speakers: Speaker layout + :return: Number of audio planes + +--------------------- + +.. function:: size_t get_audio_size(enum audio_format format, enum speaker_layout speakers, uint32_t frames) + + Gets the audio block size for a specific frame out with the given + format and speaker layout. + + :param format: Audio format + :param speakers: Speaker layout + :param frames: Audio frame count + :return: Audio block size + +--------------------- + +.. function:: uint64_t audio_frames_to_ns(size_t sample_rate, uint64_t frames) + + Helper function to convert a specific number of audio frames to + nanoseconds based upon its sample rate. + + :param sample_rate: Sample rate + :param frames: Frame count + :return: Nanoseconds + +--------------------- + +.. function:: uint64_t ns_to_audio_frames(size_t sample_rate, uint64_t ns) + + Helper function to convert a specific number of nanoseconds to audio + frame count based upon its sample rate. + + :param sample_rate: Sample rate + :param ns: Nanoseconds + :return: Frame count + +--------------------- + +.. type:: typedef void (*audio_output_callback_t)(void *param, size_t mix_idx, struct audio_data *data) + + Audio output callback. Typically used internally. + +--------------------- + +.. function:: bool audio_output_connect(audio_t *audio, size_t mix_idx, const struct audio_convert_info *conversion, audio_output_callback_t callback, void *param) + + Connects a raw audio callback to the audio output handler. + Optionally allows audio conversion if necessary. + + :param audio: Audio output handler object + :param mix_idx: Mix index to get raw audio from + :param conversion: Audio conversion information, or *NULL* for no + conversion + :param callback: Raw audio callback + :param param: Private data to pass to the callback + +--------------------- + +.. function:: void audio_output_disconnect(audio_t *audio, size_t mix_idx, audio_output_callback_t callback, void *param) + + Disconnects a raw audio callback from the audio output handler. + + :param audio: Audio output handler object + :param mix_idx: Mix index to get raw audio from + :param callback: Raw audio callback + :param param: Private data to pass to the callback + +--------------------- + +.. function:: size_t audio_output_get_block_size(const audio_t *audio) + + Gets the audio block size of an audio output handler. + + :param audio: Audio output handler object + :return: Audio block size + +--------------------- + +.. function:: size_t audio_output_get_planes(const audio_t *audio) + + Gets the plane count of an audio output handler. + + :param audio: Audio output handler object + :return: Audio plane count + +--------------------- + +.. function:: size_t audio_output_get_channels(const audio_t *audio) + + Gets the channel count of an audio output handler. + + :param audio: Audio output handler object + :return: Audio channel count + +--------------------- + +.. function:: uint32_t audio_output_get_sample_rate(const audio_t *audio) + + Gets the sample rate of an audio output handler. + + :param audio: Audio output handler object + :return: Audio sample rate + +--------------------- + +.. function:: const struct audio_output_info *audio_output_get_info(const audio_t *audio) + + Gets all audio information for an audio output handler. + + :param audio: Audio output handler object + :return: Pointer to audio output information structure + +--------------------- + + +Resampler +--------- + +FFmpeg wrapper to resample audio. + +.. type:: typedef struct audio_resampler audio_resampler_t + +--------------------- + +.. type:: struct resample_info +.. member:: uint32_t resample_info.samples_per_sec +.. member:: enum audio_format resample_info.format +.. member:: enum speaker_layout resample_info.speakers + +--------------------- + +.. function:: audio_resampler_t *audio_resampler_create(const struct resample_info *dst, const struct resample_info *src) + + Creates an audio resampler. + + :param dst: Destination audio information + :param src: Source audio information + :return: Audio resampler object + +--------------------- + +.. function:: void audio_resampler_destroy(audio_resampler_t *resampler) + + Destroys an audio resampler. + + :param resampler: Audio resampler object + +--------------------- + +.. function:: bool audio_resampler_resample(audio_resampler_t *resampler, uint8_t *output[], uint32_t *out_frames, uint64_t *ts_offset, const uint8_t *const input[], uint32_t in_frames) + + Resamples audio frames. + + :param resampler: Audio resampler object + :param output: Pointer to receive converted audio frames + :param out_frames: Pointer to receive converted audio frame count + :param ts_offset: Pointer to receive timestamp offset (in + nanoseconds) + :param const input: Input frames to convert + :param in_frames: Input frame count diff --git a/docs/sphinx/reference-libobs-util-base.rst b/docs/sphinx/reference-libobs-util-base.rst new file mode 100644 index 0000000..a3d4744 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-base.rst @@ -0,0 +1,75 @@ +Logging +======= + +Functions for logging and getting log data. + +.. code:: cpp + + #include + + +Logging Levels +-------------- + +**LOG_ERROR** = 100 + + Use if there's a problem that can potentially affect the program, + but isn't enough to require termination of the program. + + Use in creation functions and core subsystem functions. Places that + should definitely not fail. + +**LOG_WARNING** = 200 + + Use if a problem occurs that doesn't affect the program and is + recoverable. + + Use in places where failure isn't entirely unexpected, and can + be handled safely. + +**LOG_INFO** = 300 + + Informative message to be displayed in the log. + +**LOG_DEBUG** = 400 + + Debug message to be used mostly by and for developers. + + +Logging Functions +----------------- + +.. type:: typedef void (*log_handler_t)(int lvl, const char *msg, va_list args, void *p) + + Logging callback. + +--------------------- + +.. function:: void base_set_log_handler(log_handler_t handler, void *param) + void base_get_log_handler(log_handler_t *handler, void **param) + + Sets/gets the current log handler. + +--------------------- + +.. function:: void base_set_crash_handler(void (*handler)(const char *, va_list, void *), void *param) + + Sets the current crash handler. + +--------------------- + +.. function:: void blogva(int log_level, const char *format, va_list args) + + Logging function (using a va_list). + +--------------------- + +.. function:: void blog(int log_level, const char *format, ...) + + Logging function. + +--------------------- + +.. function:: void bcrash(const char *format, ...) + + Crash function. diff --git a/docs/sphinx/reference-libobs-util-bmem.rst b/docs/sphinx/reference-libobs-util-bmem.rst new file mode 100644 index 0000000..36bc93c --- /dev/null +++ b/docs/sphinx/reference-libobs-util-bmem.rst @@ -0,0 +1,62 @@ +Memory Management +================= + +Various functions and helpers used for memory management. + +.. code:: cpp + + #include + + +Memory Functions +---------------- + +.. function:: void *bmalloc(size_t size) + + Allocates memory and increases the memory leak counter. + +--------------------- + +.. function:: void *brealloc(void *ptr, size_t size) + + Reallocates memory. Use only with memory that's been allocated by + :c:func:`bmalloc()`. + +--------------------- + +.. function:: void bfree(void *ptr) + + Frees memory allocated with :c:func:`bmalloc()` or :c:func:`bfree()`. + +--------------------- + +.. function:: long bnum_allocs(void) + + Returns current number of active allocations. + +--------------------- + +.. function:: void *bmemdup(const void *ptr, size_t size) + + Duplicates memory. + +--------------------- + +.. function:: void *bzalloc(size_t size) + + Inline function that allocates zeroed memory. + +--------------------- + +.. function:: char *bstrdup_n(const char *str, size_t n) + wchar_t *bwstrdup_n(const wchar_t *str, size_t n) + + Duplicates a string of *n* bytes and automatically zero-terminates + it. + +--------------------- + +.. function:: char *bstrdup(const char *str) + wchar_t *bwstrdup(const wchar_t *str) + + Duplicates a string. diff --git a/docs/sphinx/reference-libobs-util-circlebuf.rst b/docs/sphinx/reference-libobs-util-circlebuf.rst new file mode 100644 index 0000000..5d290a7 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-circlebuf.rst @@ -0,0 +1,158 @@ +Circular Buffers +================ + +A circular buffer that will automatically increase in size as necessary +as data is pushed to the front or back. + +.. code:: cpp + + #include + + +Circular Buffer Structure (struct circlebuf) +-------------------------------------------- + +.. type:: struct circlebuf +.. member:: void *circlebuf.data +.. member:: size_t circlebuf.size +.. member:: size_t circlebuf.start_pos +.. member:: size_t circlebuf.end_pos +.. member:: size_t circlebuf.capacity + + +Circular Buffer Inline Functions +-------------------------------- + +.. function:: void circlebuf_init(struct circlebuf *cb) + + Initializes a circular buffer (just zeroes out the entire structure). + + :param cb: The circular buffer + +--------------------- + +.. function:: void circlebuf_free(struct circlebuf *cb) + + Frees a circular buffer. + + :param cb: The circular buffer + +--------------------- + +.. function:: void circlebuf_reserve(struct circlebuf *cb, size_t capacity) + + Reserves a specific amount of buffer space to ensure minimum + upsizing. + + :param cb: The circular buffer + :param capacity: The new capacity, in bytes + +--------------------- + +.. function:: void circlebuf_upsize(struct circlebuf *cb, size_t size) + + Sets the current active (not just reserved) size. Any new data is + zeroed. + + :param cb: The circular buffer + :param size: The new size, in bytes + +--------------------- + +.. function:: void circlebuf_place(struct circlebuf *cb, size_t position, const void *data, size_t size) + + Places data at a specific positional index (relative to the starting + point) within the circular buffer. + + :param cb: The circular buffer + :param position: Positional index relative to starting point + :param data: Data to insert + :param size: Size of data to insert + +--------------------- + +.. function:: void circlebuf_push_back(struct circlebuf *cb, const void *data, size_t size) + + Pushes data to the end of the circular buffer. + + :param cb: The circular buffer + :param data: Data + :param size: Size of data + +--------------------- + +.. function:: void circlebuf_push_front(struct circlebuf *cb, const void *data, size_t size) + + Pushes data to the front of the circular buffer. + + :param cb: The circular buffer + :param data: Data + :param size: Size of data + +--------------------- + +.. function:: void circlebuf_push_back_zero(struct circlebuf *cb, size_t size) + + Pushes zeroed data to the end of the circular buffer. + + :param cb: The circular buffer + :param size: Size + +--------------------- + +.. function:: void circlebuf_push_front_zero(struct circlebuf *cb, size_t size) + + Pushes zeroed data to the front of the circular buffer. + + :param cb: The circular buffer + :param size: Size + +--------------------- + +.. function:: void circlebuf_peek_front(struct circlebuf *cb, void *data, size_t size) + + Peeks data at the front of the circular buffer. + + :param cb: The circular buffer + :param data: Buffer to store data in + :param size: Size of data to retrieve + +--------------------- + +.. function:: void circlebuf_peek_back(struct circlebuf *cb, void *data, size_t size) + + Peeks data at the back of the circular buffer. + + :param cb: The circular buffer + :param data: Buffer to store data in + :param size: Size of data to retrieve + +--------------------- + +.. function:: void circlebuf_pop_front(struct circlebuf *cb, void *data, size_t size) + + Pops data from the front of the circular buffer. + + :param cb: The circular buffer + :param data: Buffer to store data in, or *NULL* + :param size: Size of data to retrieve + +--------------------- + +.. function:: void circlebuf_pop_back(struct circlebuf *cb, void *data, size_t size) + + Pops data from the back of the circular buffer. + + :param cb: The circular buffer + :param data: Buffer to store data in, or *NULL* + :param size: Size of data to retrieve + +--------------------- + +.. function:: void *circlebuf_data(struct circlebuf *cb, size_t idx) + + Gets a direct pointer to data at a specific positional index within + the circular buffer, relative to the starting point. + + :param cb: The circular buffer + :param idx: Byte index relative to the starting point diff --git a/docs/sphinx/reference-libobs-util-config-file.rst b/docs/sphinx/reference-libobs-util-config-file.rst new file mode 100644 index 0000000..d2f8b90 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-config-file.rst @@ -0,0 +1,312 @@ +Config Files +============ + +The configuration file functions are a simple implementation of the INI +file format, with the addition of default values. + +.. code:: cpp + + #include + +.. type:: config_t + + +Config File Functions +--------------------- + +.. function:: config_t *config_create(const char *file) + + Creates a new configuration object and associates it with the + specified file name. + + :param file: Path to the new configuration file + :return: A new configuration file object + +---------------------- + +.. function:: int config_open(config_t **config, const char *file, enum config_open_type open_type) + + Opens a configuration file. + + :param config: Pointer that receives a pointer to a new configuration + file object (if successful) + :param file: Path to the configuration file + :param open_type: Can be one of the following values: + + - CONFIG_OPEN_EXISTING - Fail if the file doesn't + exist. + - CONFIG_OPEN_ALWAYS - Try to open the file. If + the file doesn't exist, create it. + + :return: Can return the following values: + + - CONFIG_SUCCESS - Successful + - CONFIG_FILENOTFOUND - File not found + - CONFIG_ERROR - Generic error + +---------------------- + +.. function:: int config_open_string(config_t **config, const char *str) + + Opens configuration data via a string rather than a file. + + :param config: Pointer that receives a pointer to a new configuration + file object (if successful) + :param str: Configuration string + + :return: Can return the following values: + + - CONFIG_SUCCESS - Successful + - CONFIG_FILENOTFOUND - File not found + - CONFIG_ERROR - Generic error + +---------------------- + +.. function:: int config_save(config_t *config) + + Saves configuration data to a file (if associated with a file). + + :param config: Configuration object + + :return: Can return the following values: + + - CONFIG_SUCCESS - Successful + - CONFIG_FILENOTFOUND - File not found + - CONFIG_ERROR - Generic error + +---------------------- + +.. function:: int config_save_safe(config_t *config, const char *temp_ext, const char *backup_ext) + + Saves configuration data and minimizes overwrite corruption risk. + Saves the file with the file name + + :param config: Configuration object + :param temp_ext: Temporary extension for the new file + :param backup_ext: Backup extension for the old file. Can be *NULL* + if no backup is desired. + + :return: Can return the following values: + + - CONFIG_SUCCESS - Successful + - CONFIG_FILENOTFOUND - File not found + - CONFIG_ERROR - Generic error + +---------------------- + +.. function:: void config_close(config_t *config) + + Closes the configuration object. + + :param config: Configuration object + +---------------------- + +.. function:: size_t config_num_sections(config_t *config) + + Returns the number of sections. + + :param config: Configuration object + :return: Number of configuration sections + +---------------------- + +.. function:: const char *config_get_section(config_t *config, size_t idx) + + Returns a section name based upon its index. + + :param config: Configuration object + :param idx: Index of the section + :return: The section's name + +Set/Get Functions +----------------- + +.. function:: void config_set_string(config_t *config, const char *section, const char *name, const char *value) + + Sets a string value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The string value + +---------------------- + +.. function:: void config_set_int(config_t *config, const char *section, const char *name, int64_t value) + + Sets an integer value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The integer value + +---------------------- + +.. function:: void config_set_uint(config_t *config, const char *section, const char *name, uint64_t value) + + Sets an unsigned integer value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The unsigned integer value + +---------------------- + +.. function:: void config_set_bool(config_t *config, const char *section, const char *name, bool value) + + Sets a boolean value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The boolean value + +---------------------- + +.. function:: void config_set_double(config_t *config, const char *section, const char *name, double value) + + Sets a floating point value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The floating point value + +---------------------- + +.. function:: const char *config_get_string(config_t *config, const char *section, const char *name) + + Gets a string value. If the value is not set, it will use the + default value. If there is no default value, it will return *NULL*. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :return: The string value + +---------------------- + +.. function:: int64_t config_get_int(config_t *config, const char *section, const char *name) + + Gets an integer value. If the value is not set, it will use the + default value. If there is no default value, it will return 0. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :return: The integer value + +---------------------- + +.. function:: uint64_t config_get_uint(config_t *config, const char *section, const char *name) + + Gets an unsigned integer value. If the value is not set, it will use + the default value. If there is no default value, it will return 0. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :return: The unsigned integer value + +---------------------- + +.. function:: bool config_get_bool(config_t *config, const char *section, const char *name) + + Gets a boolean value. If the value is not set, it will use the + default value. If there is no default value, it will return false. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :return: The boolean value + +---------------------- + +.. function:: double config_get_double(config_t *config, const char *section, const char *name) + + Gets a floating point value. If the value is not set, it will use + the default value. If there is no default value, it will return 0.0. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :return: The floating point value + +---------------------- + +.. function:: bool config_remove_value(config_t *config, const char *section, const char *name) + + Removes a value. Does not remove the default value if any. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + + +Default Value Functions +----------------------- + +.. function:: int config_open_defaults(config_t *config, const char *file) + + Opens a file and uses it for default values. + + :param config: Configuration object + :param file: The file to open for default values + +---------------------- + +.. function:: void config_set_default_string(config_t *config, const char *section, const char *name, const char *value) + + Sets a default string value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The string value + +---------------------- + +.. function:: void config_set_default_int(config_t *config, const char *section, const char *name, int64_t value) + + Sets a default integer value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The integer value + +---------------------- + +.. function:: void config_set_default_uint(config_t *config, const char *section, const char *name, uint64_t value) + + Sets a default unsigned integer value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The unsigned integer value + +---------------------- + +.. function:: void config_set_default_bool(config_t *config, const char *section, const char *name, bool value) + + Sets a default boolean value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The boolean value + +---------------------- + +.. function:: void config_set_default_double(config_t *config, const char *section, const char *name, double value) + + Sets a default floating point value. + + :param config: Configuration object + :param section: The section of the value + :param name: The value name + :param value: The floating point value diff --git a/docs/sphinx/reference-libobs-util-darray.rst b/docs/sphinx/reference-libobs-util-darray.rst new file mode 100644 index 0000000..13ca273 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-darray.rst @@ -0,0 +1,275 @@ +Dynamic Arrays +============== + +Dynamically resizing arrays (a C equivalent to std::vector). + +.. code:: cpp + + #include + +.. type:: struct darray + + The base dynamic array structure. + +.. type:: DARRAY(type) + + Macro for a dynamic array based upon an actual type. Use this with + da_* macros. + +.. member:: void *darray.array + + The array pointer. + +.. member:: size_t darray.num + + The number of items within the array. + +.. member:: size_t darray.capacity + + The capacity of the array. + + +Dynamic Array Macros +-------------------- + +These macro functions are used with variables created with the +**DARRAY** type macro. When using these functions, do not use the +dynamic array value with a reference (&) operator. For example: + +.. code:: cpp + + /* creates an array of integers: 0..9 */ + DARRAY(int) array_of_integers; + da_init(array_of_integers); + + for (size_t i = 0; i < 10; i++) + da_push_back(array_of_integers, &i); + + [...] + + /* free when complete */ + da_free(array_of_integers); + +.. function:: void da_init(da) + + Initializes a dynamic array. + + :param da: The dynamic array + +--------------------- + +.. function:: void da_free(da) + + Frees a dynamic array. + + :param da: The dynamic array + +--------------------- + +.. function:: void *da_end(da) + + Gets a pointer to the last value. + + :param da: The dynamic array + :return: The last value of a dynamic array, or *NULL* if empty. + +--------------------- + +.. function:: void da_reserve(da, size_t capacity) + + Reserves a specific amount of buffer space for the dynamic array. + + :param da: The dynamic array + :param capacity: New capacity of the dynamic array + +--------------------- + +.. function:: void da_resize(da, size_t new_size) + + Resizes the dynamic array with zeroed values. + + :param da: The dynamic array + :param size: New size of the dynamic array + +--------------------- + +.. function:: void da_copy(da_dst, da_src) + + Makes a copy of a dyanmic array. + + :param da_dst: The dynamic array to copy to + :param da_src: The dynamic array to copy from + +--------------------- + +.. function:: void da_copy_array(da, const void *src_array, size_t size) + + Makes a copy of an array pointer. + + :param da: The dynamic array + :param src_array: The array pointer to make a copy from + :param size: New size of the dynamic array + +--------------------- + +.. function:: void da_move(da_dst, da_src) + + Moves one dynamic array variable to another without allocating new + data. *da_dst* is freed before moving, *da_dst* is set to *da_src*, + then *da_src* is then zeroed. + + :param da_dst: Destination variable + :param da_src: Source variable + +--------------------- + +.. function:: size_t da_find(da, const void *item_data, size_t starting_idx) + + Finds a value based upon its data. If the value cannot be found, the + return value will be DARRAY_INVALID (-1). + + :param da: The dynamic array + :param item_data: The item data to find + :param starting_idx: The index to start from or 0 to search the + entire array + +--------------------- + +.. function:: void da_push_back(da, const void *data) + + Pushes data to the back of the array. + + :param da: The dynamic array + :param data: Pointer to the new data to push + +--------------------- + +.. function:: void *da_push_back_new(da) + + Pushes a zeroed value to the back of the array, and returns a pointer + to it. + + :param da: The dynamic array + :return: Pointer to the new value + +--------------------- + +.. function:: void da_push_back_array(da, const void *src_array, size_t item_count) + + Pushes an array of values to the back of the array. + + :param da: The dynamic array + :param src_array: Pointer of the array of values + :param item_count: Number of items to push back + +--------------------- + +.. function:: void da_insert(da, size_t idx, const void *data) + + Inserts a value at a given index. + + :param da: The dynamic array: + :param idx: Index where the new item will be inserted + :param data: Pointer to the item data to insert + +--------------------- + +.. function:: void *da_insert_new(da, size_t idx) + + Inserts a new zeroed value at a specific index, and returns a pointer + to it. + + :param da: The dynamic array + :param idx: Index to insert at + :return: Pointer to the new value + +--------------------- + +.. function:: void da_insert_da(da_dst, size_t idx, da_src) + + Inserts a dynamic array in to another dynamic array at a specific + index. + + :param da_dst: Destination dynamic array being inserted in to + :param idx: Index to insert the data at + :param da_src: The dynamic array to insert + +--------------------- + +.. function:: void da_erase(da, size_t idx) + + Erases an item at a specific index. + + :param da: The dynamic array + :param idx: The index of the value to remove + +--------------------- + +.. function:: void da_erase_item(da, const void *item_data) + + Erases an item that matches the value specified + + :param da: The dynamic array + :param item_data: Pointer to the data to remove + +--------------------- + +.. function:: void da_erase_range(da, size_t start_idx, size_t end_idx) + + Erases a range of values. + + :param da: The dynamic array + :param start_idx: The starting index + :param end_idx: The ending index + +--------------------- + +.. function:: void da_pop_back(da) + + Removes one item from the end of a dynamic array. + + :param da: The dynamic array + +--------------------- + +.. function:: void da_join(da_dst, da_src) + + Pushes *da_src* to the end of *da_dst* and frees *da_src*. + + :param da_dst: The destination dynamic array + :param da_src: The source dynamic array + +--------------------- + +.. function:: void da_split(da_dst1, da_dst2, da_src, size_t split_idx) + + Creates two dynamic arrays by splitting another dynamic array at a + specific index. If the destination arrays are not freed, they will + be freed before getting their new values. The array being split will + not be freed. + + :param da_dst1: Dynamic array that will get the lower half + :param da_dst2: Dynamic array that will get the upper half + :param da_src: Dynamic array to split + :param split_idx: Index to split *da_src* at + +--------------------- + +.. function:: void da_move_item(da, size_t src_idx, size_t dst_idx) + + Moves an item from one index to another, moving data between if + necessary. + + :param da: The dynamic array + :param src_idx: The index of the item to move + :param dst_idx: The new index of where the item will be moved to + +--------------------- + +.. function:: void da_swap(da, size_t idx1, size_t idx2) + + Swaps two values at the given indices. + + :param da: The dynamic array + :param idx1: Index of the first item to swap + :param idx2: Index of the second item to swap diff --git a/docs/sphinx/reference-libobs-util-dstr.rst b/docs/sphinx/reference-libobs-util-dstr.rst new file mode 100644 index 0000000..20fb911 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-dstr.rst @@ -0,0 +1,490 @@ +Dynamic Strings And String Helpers +================================== + +Provides string helper structures/functions (roughly equivalent to +std::string). + +.. code:: cpp + + #include + + +Dynamic String Structure (struct dstr) +-------------------------------------- + +.. type:: struct dstr +.. member:: char *dstr.array +.. member:: size_t dstr.len +.. member:: size_t dstr.capacity + + +General String Helper Functions +------------------------------- + +.. function:: int astrcmpi(const char *str1, const char *str2) + + Case insensitive string comparison function. + +---------------------- + +.. function:: int wstrcmpi(const wchar_t *str1, const wchar_t *str2) + + Case insensitive wide string comparison function. + +---------------------- + +.. function:: int astrcmp_n(const char *str1, const char *str2, size_t n) + + String comparison function for a specific number of characters. + +---------------------- + +.. function:: int wstrcmp_n(const wchar_t *str1, const wchar_t *str2, size_t n) + + Wide string comparison function for a specific number of characters. + +---------------------- + +.. function:: int astrcmpi_n(const char *str1, const char *str2, size_t n) + + Case insensitive string comparison function for a specific number of + characters. + +---------------------- + +.. function:: int wstrcmpi_n(const wchar_t *str1, const wchar_t *str2, size_t n) + + Case insensitive wide string comparison function for a specific + number of characters. + +---------------------- + +.. function:: char *astrstri(const char *str, const char *find) + + Case insensitive version of strstr. + +---------------------- + +.. function:: wchar_t *wstrstri(const wchar_t *str, const wchar_t *find) + + Case insensitive version of wcsstr. + +---------------------- + +.. function:: char *strdepad(char *str) + + Removes padding characters (tab, space, CR, LF) from the front and + end of a string. + +---------------------- + +.. function:: wchar_t *wcsdepad(wchar_t *str) + + Removes padding characters (tab, space, CR, LF) from the front and + end of a wide string. + +---------------------- + +.. function:: char **strlist_split(const char *str, char split_ch, bool include_empty) + + Splits a string in to a list of multiple sub-strings. Free with + :c:func:`strlist_free()`. + +---------------------- + +.. function:: void strlist_free(char **strlist) + + Frees a string list created with :c:func:`strlist_split()`. + +--------------------- + + +Dynamic String Functions +------------------------ + +.. function:: void dstr_init(struct dstr *dst) + + Initializes a dynamic string variable (just zeroes the variable). + + :param dst: Dynamic string to initialize + +---------------------- + +.. function:: void dstr_init_move(struct dstr *dst, struct dstr *src) + + Moves a *src* to *dst* without copying data and zeroes *src*. + + :param dst: Destination + :param src: Source + +---------------------- + +.. function:: void dstr_init_move_array(struct dstr *dst, char *str) + + Sets a bmalloc-allocated string as the dynamic string without + copying/reallocating. + + :param dst: Dynamic string to initialize + :param str: bmalloc-allocated string + +---------------------- + +.. function:: void dstr_init_copy(struct dstr *dst, const char *src) + + Initializes a dynamic string with a copy of a string + + :param dst: Dynamic string to initialize + :param src: String to copy + +---------------------- + +.. function:: void dstr_init_copy_dstr(struct dstr *dst, const struct dstr *src) + + Initializes a dynamic string with a copy of another dynamic string + + :param dst: Dynamic string to initialize + :param src: Dynamic string to copy + +---------------------- + +.. function:: void dstr_free(struct dstr *dst) + + Frees a dynamic string. + + :param dst: Dynamic string + +---------------------- + +.. function:: void dstr_copy(struct dstr *dst, const char *array) + + Copies a string. + + :param dst: Dynamic string + :param array: String to copy + +---------------------- + +.. function:: void dstr_copy_dstr(struct dstr *dst, const struct dstr *src) + + Copies another dynamic string. + + :param dst: Dynamic string + :param src: Dynamic string to copy + +---------------------- + +.. function:: void dstr_ncopy(struct dstr *dst, const char *array, const size_t len) + + Copies a specific number of characters from a string. + + :param dst: Dynamic string + :param array: String to copy + :param len: Number of characters to copy + +---------------------- + +.. function:: void dstr_ncopy_dstr(struct dstr *dst, const struct dstr *src, const size_t len) + + Copies a specific number of characters from another dynamic string. + + :param dst: Dynamic string + :param src: Dynamic tring to copy + :param len: Number of characters to copy + +---------------------- + +.. function:: void dstr_resize(struct dstr *dst, const size_t num) + + Sets the size of the dynamic string. If the new size is bigger than + current size, zeroes the new characters. + + :param dst: Dynamic string + :param num: New size + +---------------------- + +.. function:: void dstr_reserve(struct dstr *dst, const size_t num) + + Reserves a specific number of characters in the buffer (but does not + change the size). Does not work if the value is smaller than the + current reserve size. + + :param dst: Dynamic string + :param num: New reserve size + +---------------------- + +.. function:: bool dstr_is_empty(const struct dstr *str) + + Returns whether the dynamic string is empty. + + :param str: Dynamic string + :return: *true* if empty, *false* otherwise + +---------------------- + +.. function:: void dstr_cat(struct dstr *dst, const char *array) + + Concatenates a dynamic string. + + :param dst: Dynamic string + :param array: String to concatenate with + +---------------------- + +.. function:: void dstr_cat_dstr(struct dstr *dst, const struct dstr *str) + + Concatenates a dyanmic string with another dynamic string. + + :param dst: Dynamic string to concatenate to + :param str: Dynamic string to concatenate with + +---------------------- + +.. function:: void dstr_cat_ch(struct dstr *dst, char ch) + + Concatenates a dynamic string with a single character. + + :param dst: Dynamic string to concatenate to + :param ch: Character to concatenate + +---------------------- + +.. function:: void dstr_ncat(struct dstr *dst, const char *array, const size_t len) + + Concatenates a dynamic string with a specific number of characters + from a string. + + :param dst: Dynamic string to concatenate to + :param array: String to concatenate with + :param len: Number of characters to concatenate with + +---------------------- + +.. function:: void dstr_ncat_dstr(struct dstr *dst, const struct dstr *str, const size_t len) + + Concatenates a dynamic string with a specific number of characters + from another dynamic string. + + :param dst: Dynamic string to concatenate to + :param str: Dynamic string to concatenate with + :param len: Number of characters to concatenate with + +---------------------- + +.. function:: void dstr_insert(struct dstr *dst, const size_t idx, const char *array) + + Inserts a string in to a dynamic string at a specific index. + + :param dst: Dynamic string to insert in to + :param idx: Character index to insert at + :param array: String to insert + +---------------------- + +.. function:: void dstr_insert_dstr(struct dstr *dst, const size_t idx, const struct dstr *str) + + Inserts another dynamic string in to a dynamic string at a specific + index. + + :param dst: Dynamic string to insert in to + :param idx: Character index to insert at + :param str: Dynamic string to insert + +---------------------- + +.. function:: void dstr_insert_ch(struct dstr *dst, const size_t idx, const char ch) + + Inserts a character in to a dynamic string at a specific index. + + :param dst: Dynamic string to insert in to + :param idx: Character index to insert at + :param ch: Character to insert + +---------------------- + +.. function:: void dstr_remove(struct dstr *dst, const size_t idx, const size_t count) + + Removes a specific number of characters starting from a specific + index. + + :param dst: Dyanmic string + :param idx: Index to start removing characters at + :param count: Number of characters to remove + +---------------------- + +.. function:: void dstr_printf(struct dstr *dst, const char *format, ...) + void dstr_vprintf(struct dstr *dst, const char *format, va_list args) + + Sets a dynamic string to a formatted string. + + :param dst: Dynamic string + :param format: Format string + +---------------------- + +.. function:: void dstr_catf(struct dstr *dst, const char *format, ...) + void dstr_vcatf(struct dstr *dst, const char *format, va_list args) + + Concatenates a dynamic string with a formatted string. + + :param dst: Dynamic string + :param format: Format string + +---------------------- + +.. function:: const char *dstr_find_i(const struct dstr *str, const char *find) + + Finds a string within a dynamic string, case insensitive. + + :param str: Dynamic string + :param find: String to find + :return: Pointer to the first occurrence, or *NULL* if not found + +---------------------- + +.. function:: const char *dstr_find(const struct dstr *str, const char *find) + + Finds a string within a dynamic string. + + :param str: Dynamic string + :param find: String to find + :return: Pointer to the first occurrence, or *NULL* if not found + +---------------------- + +.. function:: void dstr_replace(struct dstr *str, const char *find, const char *replace) + + Replaces all occurrences of *find* with *replace*. + + :param str: Dynamic string + :param find: String to find + :param replace: Replacement string + +---------------------- + +.. function:: int dstr_cmp(const struct dstr *str1, const char *str2) + + Compares with a string. + + :param str1: Dynamic string + :param str2: String to compare + :return: 0 if equal, nonzero otherwise + +---------------------- + +.. function:: int dstr_cmpi(const struct dstr *str1, const char *str2) + + Compares with a string, case-insensitive. + + :param str1: Dynamic string + :param str2: String to compare + :return: 0 if equal, nonzero otherwise + +---------------------- + +.. function:: int dstr_ncmp(const struct dstr *str1, const char *str2, const size_t n) + + Compares a specific number of characters. + + :param str1: Dynamic string + :param str2: String to compare + :param n: Number of characters to compare + :return: 0 if equal, nonzero otherwise + +---------------------- + +.. function:: int dstr_ncmpi(const struct dstr *str1, const char *str2, const size_t n) + + Compares a specific number of characters, case-insensitive. + + :param str1: Dynamic string + :param str2: String to compare + :param n: Number of characters to compare + :return: 0 if equal, nonzero otherwise + +---------------------- + +.. function:: void dstr_depad(struct dstr *dst) + + Removes all padding characters (tabs, spaces, CR, LF) from the front + and ends of a dynamic string. + + :param dst: Dynamic string + +---------------------- + +.. function:: void dstr_left(struct dstr *dst, const struct dstr *str, const size_t pos) + + Copies a certain number of characters from the left side of one + dynamic string in to another. + + :param dst: Destination + :param str: Source + :param pos: Number of characters + +---------------------- + +.. function:: void dstr_mid(struct dstr *dst, const struct dstr *str, const size_t start, const size_t count) + + Copies a certain number of characters from the middle of one dynamic + string in to another. + + :param dst: Destination + :param str: Source + :param start: Starting index within *str* + :param count: Number of characters to copy + +---------------------- + +.. function:: void dstr_right(struct dstr *dst, const struct dstr *str, const size_t pos) + + Copies a certain number of characters from the right of one dynamic + string in to another. + + :param dst: Destination + :param str: Source + :param pos: Index of *str* to copy from + +---------------------- + +.. function:: char dstr_end(const struct dstr *str) + + :param str: Dynamic string + :return: The last character of a dynamic string + +---------------------- + +.. function:: void dstr_from_wcs(struct dstr *dst, const wchar_t *wstr) + + Copies a wide string in to a dynamic string and converts it to UTF-8. + + :param dst: Dynamic string + :param wstr: Wide string + +---------------------- + +.. function:: wchar_t *dstr_to_wcs(const struct dstr *str) + + Converts a dynamic array to a wide string. Free with + :c:func:`bfree()`. + + :param str: Dynamic string + :return: Wide string allocation. Free with :c:func:`bfree()` + +---------------------- + +.. function:: void dstr_to_upper(struct dstr *str) + + Converts all characters within a dynamic array to uppercase. + + :param str: Dynamic string + +---------------------- + +.. function:: void dstr_to_lower(struct dstr *str) + + Converts all characters within a dynamic array to lowercase. + + :param str: Dynamic string diff --git a/docs/sphinx/reference-libobs-util-platform.rst b/docs/sphinx/reference-libobs-util-platform.rst new file mode 100644 index 0000000..6c059d2 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-platform.rst @@ -0,0 +1,465 @@ +Platform Helpers +================ + +These functions/structures/types are used to perform actions that +typically don't have shared functions across different operating systems +and platforms. + +.. code:: cpp + + #include + + +File Functions +-------------- + +.. function:: FILE *os_wfopen(const wchar_t *path, const char *mode) + + Opens a file with a wide string path. + +---------------------- + +.. function:: FILE *os_fopen(const char *path, const char *mode) + + Opens a file with a UTF8 string path. + +---------------------- + +.. function:: int64_t os_fgetsize(FILE *file) + + Returns a file's size. + +---------------------- + +.. function:: int os_stat(const char *file, struct stat *st) + + Equivalent to the posix *stat* function. + +---------------------- + +.. function:: int os_fseeki64(FILE *file, int64_t offset, int origin) + + Equivalent to fseek. + +---------------------- + +.. function:: int64_t os_ftelli64(FILE *file) + + Gets the current file position. + +---------------------- + +.. function:: size_t os_fread_utf8(FILE *file, char **pstr) + + Reads a UTF8 encoded file and allocates a pointer to the UTF8 string. + +---------------------- + +.. function:: char *os_quick_read_utf8_file(const char *path) + + Reads a UTF8 encoded file and returns an allocated pointer to the + string. + +---------------------- + +.. function:: bool os_quick_write_utf8_file(const char *path, const char *str, size_t len, bool marker) + + Writes a UTF8 encoded file. + +---------------------- + +.. function:: bool os_quick_write_utf8_file_safe(const char *path, const char *str, size_t len, bool marker, const char *temp_ext, const char *backup_ext) + + Writes a UTF8 encoded file with overwrite corruption prevention. + +---------------------- + +.. function:: int64_t os_get_file_size(const char *path) + + Gets a file's size. + +---------------------- + +.. function:: int64_t os_get_free_space(const char *path) + + Gets free space of a specific file path. + +--------------------- + + +String Conversion Functions +--------------------------- + +.. function:: size_t os_utf8_to_wcs(const char *str, size_t len, wchar_t *dst, size_t dst_size) + + Converts a UTF8 string to a wide string. + +---------------------- + +.. function:: size_t os_wcs_to_utf8(const wchar_t *str, size_t len, char *dst, size_t dst_size) + + Converts a wide string to a UTF8 string. + +---------------------- + +.. function:: size_t os_utf8_to_wcs_ptr(const char *str, size_t len, wchar_t **pstr) + + Gets an bmalloc-allocated wide string converted from a UTF8 string. + +---------------------- + +.. function:: size_t os_wcs_to_utf8_ptr(const wchar_t *str, size_t len, char **pstr) + + Gets an bmalloc-allocated UTF8 string converted from a wide string. + +--------------------- + + +Number/String Conversion Functions +---------------------------------- + +.. function:: double os_strtod(const char *str) + + Converts a string to a double. + +---------------------- + +.. function:: int os_dtostr(double value, char *dst, size_t size) + + Converts a double to a string. + +--------------------- + + +Dynamic Link Library Functions +------------------------------ + +These functions are roughly equivalent to dlopen/dlsym/dlclose. + +.. function:: void *os_dlopen(const char *path) + + Opens a dynamic library. + +---------------------- + +.. function:: void *os_dlsym(void *module, const char *func) + + Returns a symbol from a dynamic library. + +---------------------- + +.. function:: void os_dlclose(void *module) + + Closes a dynamic library. + +--------------------- + + +CPU Usage Functions +------------------- + +.. function:: os_cpu_usage_info_t *os_cpu_usage_info_start(void) + + Creates a CPU usage information object. + +---------------------- + +.. function:: double os_cpu_usage_info_query(os_cpu_usage_info_t *info) + + Queries the current CPU usage. + +---------------------- + +.. function:: void os_cpu_usage_info_destroy(os_cpu_usage_info_t *info) + + Destroys a CPU usage information object. + +--------------------- + + +Sleep/Time Functions +-------------------- + +.. function:: bool os_sleepto_ns(uint64_t time_target) + + Sleeps to a specific time with high precision, in nanoseconds. + +--------------------- + +.. function:: void os_sleep_ms(uint32_t duration) + + Sleeps for a specific number of milliseconds. + +--------------------- + +.. function:: uint64_t os_gettime_ns(void) + + Gets the current high-precision system time, in nanoseconds. + +--------------------- + +Other Path/File Functions +------------------------- + +.. function:: int os_get_config_path(char *dst, size_t size, const char *name) + char *os_get_config_path_ptr(const char *name) + + Gets the user-specific application configuration data path. + +--------------------- + +.. function:: int os_get_program_data_path(char *dst, size_t size, const char *name) + char *os_get_program_data_path_ptr(const char *name) + + Gets the application configuration data path. + +--------------------- + +.. function:: bool os_file_exists(const char *path) + + Returns true if a file/directory exists, false otherwise. + +--------------------- + +.. function:: size_t os_get_abs_path(const char *path, char *abspath, size_t size) + char *os_get_abs_path_ptr(const char *path) + + Converts a relative path to an absolute path. + +--------------------- + +.. function:: const char *os_get_path_extension(const char *path) + + Returns the extension portion of a path string. + +--------------------- + +.. type:: typedef struct os_dir os_dir_t + + A directory object. + +.. type:: struct os_dirent + + A directory entry record. + +.. member:: char os_dirent.d_name[256] + + The directory entry name. + +.. member:: bool os_dirent.directory + + *true* if the entry is a directory. + +--------------------- + +.. function:: os_dir_t *os_opendir(const char *path) + + Opens a directory object to enumerate files within the directory. + +--------------------- + +.. function:: struct os_dirent *os_readdir(os_dir_t *dir) + + Returns the linked list of directory entries. + +--------------------- + +.. function:: void os_closedir(os_dir_t *dir) + + Closes a directory object. + +--------------------- + +.. type:: struct os_globent + + A glob entry. + +.. member:: char *os_globent.path + + The full path to the glob entry. + +.. member:: bool os_globent.directory + + *true* if the glob entry is a directory, *false* otherwise. + +.. type:: struct os_glob_info + + A glob object. + +.. member:: size_t os_glob_info.gl_pathc + + Number of glob entries. + +.. member:: struct os_globent *os_glob_info.gl_pathv + + Array of glob entries. + +.. type:: typedef struct os_glob_info os_glob_t + +--------------------- + +.. function:: int os_glob(const char *pattern, int flags, os_glob_t **pglob) + + Enumerates files based upon a glob string. + +--------------------- + +.. function:: void os_globfree(os_glob_t *pglob) + + Frees a glob object. + +--------------------- + +.. function:: int os_unlink(const char *path) + + Deletes a file. + +--------------------- + +.. function:: int os_rmdir(const char *path) + + Deletes a directory. + +--------------------- + +.. function:: char *os_getcwd(char *path, size_t size) + + Returns a new bmalloc-allocated path to the current working + directory. + +--------------------- + +.. function:: int os_chdir(const char *path) + + Changes the current working directory. + +--------------------- + +.. function:: int os_mkdir(const char *path) + + Creates a directory. + +--------------------- + +.. function:: int os_mkdirs(const char *path) + + Creates a full directory path if it doesn't exist. + +--------------------- + +.. function:: int os_rename(const char *old_path, const char *new_path) + + Renames a file. + +--------------------- + +.. function:: int os_copyfile(const char *file_in, const char *file_out) + + Copys a file. + +--------------------- + +.. function:: int os_safe_replace(const char *target_path, const char *from_path, const char *backup_path) + + Safely replaces a file. + +--------------------- + +.. function:: char *os_generate_formatted_filename(const char *extension, bool space, const char *format) + + Returns a new bmalloc-allocated filename generated from specific + formatting. + +--------------------- + + +Sleep-Inhibition Functions +-------------------------- + +These functions/types are used to inhibit the computer from going to +sleep. + +.. type:: struct os_inhibit_info +.. type:: typedef struct os_inhibit_info os_inhibit_t + +--------------------- + +.. function:: os_inhibit_t *os_inhibit_sleep_create(const char *reason) + + Creates a sleep inhibition object. + +--------------------- + +.. function:: bool os_inhibit_sleep_set_active(os_inhibit_t *info, bool active) + + Activates/deactivates a sleep inhibition object. + +--------------------- + +.. function:: void os_inhibit_sleep_destroy(os_inhibit_t *info) + + Destroys a sleep inhibition object. If the sleep inhibition object + was active, it will be deactivated. + +--------------------- + + +Other Functions +--------------- + +.. function:: void os_breakpoint(void) + + Triggers a debugger breakpoint (or crashes the program if no debugger + present). + +--------------------- + +.. function:: int os_get_physical_cores(void) + + Returns the number of physical cores available. + +--------------------- + +.. function:: int os_get_logical_cores(void) + + Returns the number of logical cores available. + +--------------------- + +.. function:: uint64_t os_get_sys_free_size(void) + + Returns the amount of memory available. + +--------------------- + +.. type:: struct os_proc_memory_usage + + Memory usage structure. + +.. member:: uint64_t os_proc_memory_usage.resident_size + + Resident size. + +.. member:: uint64_t os_proc_memory_usage.virtual_size + + Virtual size. + +.. type:: typedef struct os_proc_memory_usage os_proc_memory_usage_t + +--------------------- + +.. function:: bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage) + + Gets memory usage of the current process. + +--------------------- + +.. function:: uint64_t os_get_proc_resident_size(void) + + Returns the resident memory size of the current process. + +--------------------- + +.. function:: uint64_t os_get_proc_virtual_size(void) + + Returns the virtual memory size of the current process. diff --git a/docs/sphinx/reference-libobs-util-profiler.rst b/docs/sphinx/reference-libobs-util-profiler.rst new file mode 100644 index 0000000..b749bf5 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-profiler.rst @@ -0,0 +1,327 @@ +Profiler +======== + +The profiler is used to get information about program performance and +efficiency. + +.. type:: typedef struct profiler_snapshot profiler_snapshot_t +.. type:: typedef struct profiler_snapshot_entry profiler_snapshot_entry_t +.. type:: typedef struct profiler_name_store profiler_name_store_t +.. type:: typedef struct profiler_time_entry profiler_time_entry_t + +.. code:: cpp + + #include + + +Profiler Structures +------------------- + +.. type:: struct profiler_time_entry +.. member:: uint64_t profiler_time_entry.time_delta +.. member:: uint64_t profiler_time_entry.count + + +Profiler Control Functions +-------------------------- + +.. function:: void profiler_start(void) + + Starts the profiler. + +---------------------- + +.. function:: void profiler_stop(void) + + Stops the profiler. + +---------------------- + +.. function:: void profiler_print(profiler_snapshot_t *snap) + + Creates a profiler snapshot and saves it within *snap*. + + :param snap: A profiler snapshot object + +---------------------- + +.. function:: void profiler_print_time_between_calls(profiler_snapshot_t *snap) + + Creates a profiler snapshot of time between calls and saves it within + *snap*. + + :param snap: A profiler snapshot object + +---------------------- + +.. function:: void profiler_free(void) + + Frees the profiler. + +---------------------- + + +Profiling Functions +------------------- + +.. function:: void profile_register_root(const char *name, uint64_t expected_time_between_calls) + + Registers a root profile node. + + :param name: Name of the root profile node + :param expected_time_between_calls: The expected time between calls + of the profile root node, or 0 if + none. + +---------------------- + +.. function:: void profile_start(const char *name) + + Starts a profile node. This profile node will be a child of the last + node that was started. + + :param name: Name of the profile node + +---------------------- + +.. function:: void profile_end(const char *name) + + :param name: Name of the profile node + +---------------------- + +.. function:: void profile_reenable_thread(void) + + Because :c:func:`profiler_start()` can be called in a different + thread than the current thread, this is used to specify a point where + it's safe to re-enable profiling in the calling thread. Call this + when you have looped root profile nodes and need to specify a safe + point where the root profile node isn't active and the profiler can + start up in the current thread again. + +---------------------- + + +Profiler Name Storage Functions +------------------------------- + +.. function:: profiler_name_store_t *profiler_name_store_create(void) + + Creates a profiler name storage object. + + :return: Profiler name store object + +---------------------- + +.. function:: void profiler_name_store_free(profiler_name_store_t *store) + + Frees a profiler name storage object. + + :param store: Profiler name storage object + +---------------------- + +.. function:: const char *profile_store_name(profiler_name_store_t *store, const char *format, ...) + + Creates a formatted string and stores it within a profiler name + storage object. + + :param store: Profiler name storage object + :param format: Formatted string + :return: The string created from format specifications + +---------------------- + + +Profiler Data Access Functions +------------------------------ + +.. function:: profiler_snapshot_t *profile_snapshot_create(void) + + Creates a profile snapshot. Profiler snapshots are used to obtain + data about how the active profiles performed. + + :return: A profiler snapshot object + +---------------------- + +.. function:: void profile_snapshot_free(profiler_snapshot_t *snap) + + Frees a profiler snapshot object. + + :param snap: A profiler snapshot + +---------------------- + +.. function:: bool profiler_snapshot_dump_csv(const profiler_snapshot_t *snap, const char *filename) + + Creates a CSV file of the profiler snapshot. + + :param snap: A profiler snapshot + :param filename: The path to the CSV file to save + :return: *true* if successfuly written, *false* otherwise + +---------------------- + +.. function:: bool profiler_snapshot_dump_csv_gz(const profiler_snapshot_t *snap, const char *filename) + + Creates a gzipped CSV file of the profiler snapshot. + + :param snap: A profiler snapshot + :param filename: The path to the gzipped CSV file to save + :return: *true* if successfuly written, *false* otherwise + +---------------------- + +.. function:: size_t profiler_snapshot_num_roots(profiler_snapshot_t *snap) + + :param snap: A profiler snapshot + :return: Number of root profiler nodes in the snapshot + +---------------------- + +.. type:: typedef bool (*profiler_entry_enum_func)(void *context, profiler_snapshot_entry_t *entry) + + Profiler snapshot entry numeration callback + + :param context: Private data passed to this callback + :param entry: Profiler snapshot entry + :return: *true* to continue enumeration, *false* otherwise + +---------------------- + +.. function:: void profiler_snapshot_enumerate_roots(profiler_snapshot_t *snap, profiler_entry_enum_func func, void *context) + + Enumerates root profile nodes. + + :param snap: A profiler snapshot + :param func: Enumeration callback + :param context: Private data to pass to the callback + +---------------------- + +.. type:: typedef bool (*profiler_name_filter_func)(void *data, const char *name, bool *remove) + + Callback used to determine what profile nodes are removed/filtered. + + :param data: Private data passed to this callback + :param name: Profile node name to be filtered + :param remove: Used to determined whether the node should be removed + or not + :return: *true* to continue enumeration, *false* otherwise + +---------------------- + +.. function:: void profiler_snapshot_filter_roots(profiler_snapshot_t *snap, profiler_name_filter_func func, void *data) + + Removes/filters profile roots based upon their names. + + :param snap: A profiler snapshot + :param func: Enumeration callback to filter with + :param data: Private data to pass to the callback + +---------------------- + +.. function:: size_t profiler_snapshot_num_children(profiler_snapshot_entry_t *entry) + + :param entry: A profiler snapshot entry + :return: Number of children for the entry + +---------------------- + +.. function:: void profiler_snapshot_enumerate_children(profiler_snapshot_entry_t *entry, profiler_entry_enum_func func, void *context) + + Enumerates child entries of a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :param func: Enumeration callback + :param context: Private data passed to the callback + +---------------------- + +.. function:: const char *profiler_snapshot_entry_name(profiler_snapshot_entry_t *entry) + + :param entry: A profiler snapshot entry + :return: The name of the profiler snapshot entry + +---------------------- + +.. function:: profiler_time_entries_t *profiler_snapshot_entry_times(profiler_snapshot_entry_t *entry) + + Gets the time entries for a snapshot entry. + + :param entry: A profiler snapshot entry + :return: An array of profiler time entries + +---------------------- + +.. function:: uint64_t profiler_snapshot_entry_min_time(profiler_snapshot_entry_t *entry) + + Gets the minimum time for a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :return: The minimum time value for the snapshot entry + +---------------------- + +.. function:: uint64_t profiler_snapshot_entry_max_time(profiler_snapshot_entry_t *entry) + + Gets the maximum time for a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :return: The maximum time value for the snapshot entry + +---------------------- + +.. function:: uint64_t profiler_snapshot_entry_overall_count(profiler_snapshot_entry_t *entry) + + Gets the overall count for a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :return: The overall count value for the snapshot entry + +---------------------- + +.. function:: profiler_time_entries_t *profiler_snapshot_entry_times_between_calls(profiler_snapshot_entry_t *entry) + + Gets an array of time between calls for a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :return: An array of profiler time entries + +---------------------- + +.. function:: uint64_t profiler_snapshot_entry_expected_time_between_calls(profiler_snapshot_entry_t *entry) + + Gets the expected time between calls for a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :return: The expected time between calls for the snapshot entry, + or 0 if not set + +---------------------- + +.. function:: uint64_t profiler_snapshot_entry_min_time_between_calls(profiler_snapshot_entry_t *entry) + + Gets the minimum time seen between calls for a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :return: The minimum time seen between calls for the snapshot entry + +---------------------- + +.. function:: uint64_t profiler_snapshot_entry_max_time_between_calls(profiler_snapshot_entry_t *entry) + + Gets the maximum time seen between calls for a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :return: The maximum time seen between calls for the snapshot entry + +---------------------- + +.. function:: uint64_t profiler_snapshot_entry_overall_between_calls_count(profiler_snapshot_entry_t *entry) + + Gets the overall time between calls for a profiler snapshot entry. + + :param entry: A profiler snapshot entry + :return: The overall time between calls for the snapshot entry diff --git a/docs/sphinx/reference-libobs-util-serializers.rst b/docs/sphinx/reference-libobs-util-serializers.rst new file mode 100644 index 0000000..51c6a5f --- /dev/null +++ b/docs/sphinx/reference-libobs-util-serializers.rst @@ -0,0 +1,171 @@ +Serializer +========== + +General programmable serialization functions. (A shared interface to +various reading/writing to/from different inputs/outputs) + +.. code:: cpp + + #include + +Serializer Structure (struct serializer) +---------------------------------------- + +.. type:: struct serializer +.. member:: void *serializer.data +.. member:: size_t (*serializer.read)(void *, void *, size_t) +.. member:: size_t (*serializer.write)(void *, const void *, size_t) +.. member:: int64_t (*serializer.seek)(void *, int64_t, enum serialize_seek_type) +.. member:: int64_t (*serializer.get_pos)(void *) + +Serializer Inline Functions +--------------------------- + +.. function:: size_t s_read(struct serializer *s, void *data, size_t size) + +--------------------- + +.. function:: size_t s_write(struct serializer *s, const void *data, size_t size) + +--------------------- + +.. function:: size_t serialize(struct serializer *s, void *data, size_t len) + +--------------------- + +.. function:: int64_t serializer_seek(struct serializer *s, int64_t offset, enum serialize_seek_type seek_type) + +--------------------- + +.. function:: int64_t serializer_get_pos(struct serializer *s) + +--------------------- + +.. function:: void s_w8(struct serializer *s, uint8_t u8) + +--------------------- + +.. function:: void s_wl16(struct serializer *s, uint16_t u16) + +--------------------- + +.. function:: void s_wl32(struct serializer *s, uint32_t u32) + +--------------------- + +.. function:: void s_wl64(struct serializer *s, uint64_t u64) + +--------------------- + +.. function:: void s_wlf(struct serializer *s, float f) + +--------------------- + +.. function:: void s_wld(struct serializer *s, double d) + +--------------------- + +.. function:: void s_wb16(struct serializer *s, uint16_t u16) + +--------------------- + +.. function:: void s_wb24(struct serializer *s, uint32_t u24) + +--------------------- + +.. function:: void s_wb32(struct serializer *s, uint32_t u32) + +--------------------- + +.. function:: void s_wb64(struct serializer *s, uint64_t u64) + +--------------------- + +.. function:: void s_wbf(struct serializer *s, float f) + +--------------------- + +.. function:: void s_wbd(struct serializer *s, double d) + +--------------------- + + +Array Output Serializer +======================= + +Provides an output serializer used with dynamic arrays. + +.. code:: cpp + + #include + +Array Output Serializer Structure (struct array_output_data) +------------------------------------------------------------ + +.. type:: struct array_output_data + +.. member:: DARRAY(uint8_t) array_output_data.bytes + +Array Output Serializer Functions +--------------------------------- + +.. function:: void array_output_serializer_init(struct serializer *s, struct array_output_data *data) + +--------------------- + +.. function:: void array_output_serializer_free(struct array_output_data *data) + +--------------------- + + +File Input/Output Serializers +============================= + +Provides file reading/writing serializers. + +.. code:: cpp + + #include + + +File Input Serializer Functions +------------------------------- + +.. function:: bool file_input_serializer_init(struct serializer *s, const char *path) + + Initializes a file input serializer. + + :return: *true* if file opened successfully, *false* otherwise + +--------------------- + +.. function:: void file_input_serializer_free(struct serializer *s) + + Frees a file input serializer. + +--------------------- + + +File Output Serializer Functions +-------------------------------- + +.. function:: bool file_output_serializer_init(struct serializer *s, const char *path) + + Initializes a file output serializer. + + :return: *true* if file created successfully, *false* otherwise + +--------------------- + +.. function:: bool file_output_serializer_init_safe(struct serializer *s, const char *path, const char *temp_ext) + + Initializes and safely writes to a temporary file (determined by the + temporary extension) until freed. + + :return: *true* if file created successfully, *false* otherwise + +--------------------- + +.. function:: void file_output_serializer_free(struct serializer *s) + + Frees the file output serializer and saves the file. diff --git a/docs/sphinx/reference-libobs-util-text-lookup.rst b/docs/sphinx/reference-libobs-util-text-lookup.rst new file mode 100644 index 0000000..5b5f153 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-text-lookup.rst @@ -0,0 +1,56 @@ +Text Lookup Interface +===================== + +Used for storing and looking up localized strings. Uses an ini-file +like file format for localization lookup. + +.. type:: struct text_lookup lookup_t + +.. code:: cpp + + #include + + +Text Lookup Functions +--------------------- + +.. function:: lookup_t *text_lookup_create(const char *path) + + Creates a text lookup object from a text lookup file. + + :param path: Path to the localization file + :return: New lookup object, or *NULL* if an error occurred + +--------------------- + +.. function:: bool text_lookup_add(lookup_t *lookup, const char *path) + + Adds text lookup from a text lookup file and replaces any values. + For example, you would load a default fallback language such as + english with :c:func:`text_lookup_create()`, and then call this + function to load the actual desired language in case the desired + language isn't fully translated. + + :param lookup: Lookup object + :param path: Path to the localization file + :return: *true* if successful, *false* otherwise + +--------------------- + +.. function:: void text_lookup_destroy(lookup_t *lookup) + + Destroys a text lookup object. + + :param lookup: Lookup object + +--------------------- + +.. function:: bool text_lookup_getstr(lookup_t *lookup, const char *lookup_val, const char **out) + + Gets a localized text string. + + :param lookup: Lookup object + :param lookup_val: Value to look up + :param out: Pointer that receives the translated string + pointer + :return: *true* if the value exists, *false* otherwise diff --git a/docs/sphinx/reference-libobs-util-threading.rst b/docs/sphinx/reference-libobs-util-threading.rst new file mode 100644 index 0000000..badd029 --- /dev/null +++ b/docs/sphinx/reference-libobs-util-threading.rst @@ -0,0 +1,193 @@ +Threading +========= + +Libobs provides a number of helper functions/types specifically for +threading. The threading header will additionally provide access to +pthread functions even on windows. + +.. code:: cpp + + #include + + +Threading Types +--------------- + +.. type:: os_event_t +.. type:: os_sem_t + + +General Thread Functions +------------------------ + +.. function:: void os_set_thread_name(const char *name) + + Sets the name of the current thread. + +---------------------- + + +Event Functions +--------------- + +.. function:: int os_event_init(os_event_t **event, enum os_event_type type) + + Creates an event object. + + :param event: Pointer that receives a pointer to a new event object + :param type: Can be one of the following values: + + - OS_EVENT_TYPE_AUTO - Automatically resets when + signaled + - OS_EVENT_TYPE_MANUAL - Stays signaled until the + :c:func:`os_event_reset()` function is called + + :return: 0 if successful, negative otherwise + +---------------------- + +.. function:: void os_event_destroy(os_event_t *event) + + Destroys an event. + + :param event: An event object + +---------------------- + +.. function:: int os_event_wait(os_event_t *event) + + Waits for an event to signal. + + :param event: An event object + :return: 0 if successful, negative otherwise + +---------------------- + +.. function:: int os_event_timedwait(os_event_t *event, unsigned long milliseconds) + + Waits a specific duration for an event to signal. + + :param event: An event object + :param milliseconds: Milliseconds to wait + :return: Can be one of the following values: + + - 0 - successful + - ETIMEDOUT - Timed out + - EINVAL - An unexpected error occured + +---------------------- + +.. function:: int os_event_try(os_event_t *event) + + Checks for a signaled state without waiting. + + :param event: An event object + :return: Can be one of the following values: + + - 0 - successful + - EAGAIN - The event is not signaled + - EINVAL - An unexpected error occured + +---------------------- + +.. function:: int os_event_signal(os_event_t *event) + + Signals the event. + + :param event: An event object + :return: 0 if successful, negative otherwise + +---------------------- + +.. function:: void os_event_reset(os_event_t *event) + + Resets the signaled state of the event. + + :param event: An event object + +---------------------- + + +Semaphore Functions +------------------- + +.. function:: int os_sem_init(os_sem_t **sem, int value) + + Creates a semaphore object. + + :param sem: Pointer that receives a pointer to the semaphore object + :param value: Initial value of the semaphore + :return: 0 if successful, negative otherwise + +---------------------- + +.. function:: void os_sem_destroy(os_sem_t *sem) + + Destroys a sempahore object. + + :param sem: Semaphore object + +---------------------- + +.. function:: int os_sem_post(os_sem_t *sem) + + Increments the semaphore. + + :param sem: Semaphore object + :return: 0 if successful, negative otherwise + +---------------------- + +.. function:: int os_sem_wait(os_sem_t *sem) + + Decrements the semphore or waits until the semaphore has been + incremented. + + :param sem: Semaphore object + :return: 0 if successful, negative otherwise + +--------------------- + + +Atomic Inline Functions +----------------------- + +.. function:: long os_atomic_inc_long(volatile long *val) + + Increments a long variable atomically. + +--------------------- + +.. function:: long os_atomic_dec_long(volatile long *val) + + Decrements a long variable atomically. + +--------------------- + +.. function:: long os_atomic_set_long(volatile long *ptr, long val) + + Sets the value of a long variable atomically. + +--------------------- + +.. function:: long os_atomic_load_long(const volatile long *ptr) + + Gets the value of a long variable atomically. + +--------------------- + +.. function:: bool os_atomic_compare_swap_long(volatile long *val, long old_val, long new_val) + + Swaps the value of a long variable atomically if its value matches. + +--------------------- + +.. function:: bool os_atomic_set_bool(volatile bool *ptr, bool val) + + Sets the value of a boolean variable atomically. + +--------------------- + +.. function:: bool os_atomic_load_bool(const volatile bool *ptr) + + Gets the value of a boolean variable atomically. diff --git a/docs/sphinx/reference-libobs-util.rst b/docs/sphinx/reference-libobs-util.rst new file mode 100644 index 0000000..1490bc2 --- /dev/null +++ b/docs/sphinx/reference-libobs-util.rst @@ -0,0 +1,17 @@ +Platform/Utility API Reference (libobs/util) +============================================ + +.. toctree:: + :maxdepth: 2 + + reference-libobs-util-base + reference-libobs-util-bmem + reference-libobs-util-circlebuf + reference-libobs-util-config-file + reference-libobs-util-darray + reference-libobs-util-dstr + reference-libobs-util-platform + reference-libobs-util-profiler + reference-libobs-util-serializers + reference-libobs-util-text-lookup + reference-libobs-util-threading diff --git a/docs/sphinx/reference-modules.rst b/docs/sphinx/reference-modules.rst new file mode 100644 index 0000000..da569a3 --- /dev/null +++ b/docs/sphinx/reference-modules.rst @@ -0,0 +1,310 @@ +Module API Reference +==================== + +Modules add custom functionality to libobs: typically +:ref:`plugins_sources`, :ref:`plugins_outputs`, :ref:`plugins_encoders`, +and :ref:`plugins_services`. + +.. type:: obs_module_t + + A module object (not reference counted). + +.. code:: cpp + + #include + + +Module Macros +------------- + +These macros are used within custom plugin modules. + +.. function:: OBS_DECLARE_MODULE() + + Declares a libobs module. Exports important core module functions + related to the module itself, OBS version, etc. + +--------------------- + +.. function:: OBS_MODULE_USE_DEFAULT_LOCALE(module_name, default_locale) + + Helper macro that uses the standard ini file format for localization. + Automatically initializes and destroys localization data, and + automatically provides module externs such as + :c:func:`obs_module_text()` to be able to get a localized string with + little effort. + +--------------------- + +Module Exports +-------------- + +These are functions that plugin modules can optionally export in order +to communicate with libobs and front-ends. + +.. function:: bool obs_module_load(void) + + Required: Called when the module is loaded. Implement this function + to load all the sources/encoders/outputs/services for your module, or + anything else that may need loading. + + :return: Return true to continue loading the module, otherwise + false to indicate failure and unload the module + +--------------------- + +.. function:: void obs_module_unload(void) + + Optional: Called when the module is unloaded. + +--------------------- + +.. function:: void obs_module_post_load(void) + + Optional: Called when all modules have finished loading. + +--------------------- + +.. function:: void obs_module_set_locale(const char *locale) + + Called to set the locale language and load the locale data for the + module. + +--------------------- + +.. function:: void obs_module_free_locale(void) + + Called on module destruction to free locale data. + +--------------------- + +.. function:: const char *obs_module_name(void) + + (Optional) + + :return: The full name of the module + +--------------------- + +.. function:: const char *obs_module_description(void) + + (Optional) + + :return: A description of the module + +--------------------- + + +Module Externs +-------------- + +These functions are externs that are useable throughout the module. + +.. function:: const char *obs_module_text(const char *lookup_string) + + :return: A localized string + +--------------------- + +.. function:: bool obs_module_get_string(const char *lookup_string, const char **translated_string) + + Helper function for looking up locale. + + :return: *true* if text found, otherwise *false* + +--------------------- + +.. function:: obs_module_t *obs_current_module(void) + + :return: The current module + +--------------------- + +.. function:: char *obs_module_file(const char *file) + + Returns the location to a module data file associated with the + current module. Free with :c:func:`bfree()` when complete. + + Equivalent to: + +.. code:: cpp + + obs_find_module_file(obs_current_module(), file); + +--------------------- + +.. function:: char *obs_module_config_path(const char *file) + + Returns the location to a module config file associated with the + current module. Free with :c:func:`bfree()` when complete. Will + return NULL if configuration directory is not set. + + Equivalent to: + +.. code:: cpp + + obs_module_get_config_path(obs_current_module(), file); + +--------------------- + + +Frontend Module Functions +-------------------------- + +These are functions used by frontends to load and get information about +plugin modules. + +.. function:: int obs_open_module(obs_module_t **module, const char *path, const char *data_path) + + Opens a plugin module directly from a specific path. + + If the module already exists then the function will return successful, and + the module parameter will be given the pointer to the existing + module. + + This does not initialize the module, it only loads the module image. To + initialize the module, call :c:func:`obs_init_module()`. + + :param module: The pointer to the created module + :param path: Specifies the path to the module library file. If the + extension is not specified, it will use the extension + appropriate to the operating system + :param data_path: Specifies the path to the directory where the module's + data files are stored (or *NULL* if none) + :returns: | MODULE_SUCCESS - Successful + | MODULE_ERROR - A generic error occurred + | MODULE_FILE_NOT_FOUND - The module was not found + | MODULE_MISSING_EXPORTS - Required exports are missing + | MODULE_INCOMPATIBLE_VER - Incompatible version + +--------------------- + +.. function:: bool obs_init_module(obs_module_t *module) + + Initializes the module, which calls its obs_module_load export. + + :return: *true* if the module was loaded successfully + +--------------------- + +.. function:: void obs_log_loaded_modules(void) + + Logs loaded modules. + +--------------------- + +.. function:: const char *obs_get_module_file_name(obs_module_t *module) + + :return: The module file name + +--------------------- + +.. function:: const char *obs_get_module_name(obs_module_t *module) + + :return: The module full name (or *NULL* if none) + +--------------------- + +.. function:: void obs_get_module_author(obs_module_t *module) + + :return: The module author(s) + +--------------------- + +.. function:: const char *obs_get_module_description(obs_module_t *module) + + :return: The module description + +--------------------- + +.. function:: const char *obs_get_module_binary_path(obs_module_t *module) + + :return: The module binary path + +--------------------- + +.. function:: const char *obs_get_module_data_path(obs_module_t *module) + + :return: The module data path + +--------------------- + +.. function:: void obs_add_module_path(const char *bin, const char *data) + + Adds a module search path to be used with obs_find_modules. If the search + path strings contain %module%, that text will be replaced with the module + name when used. + + :param bin: Specifies the module's binary directory search path + :param data: Specifies the module's data directory search path + +--------------------- + +.. function:: void obs_load_all_modules(void) + + Automatically loads all modules from module paths (convenience function). + +--------------------- + +.. function:: void obs_post_load_modules(void) + + Notifies modules that all modules have been loaded. + +--------------------- + +.. function:: void obs_find_modules(obs_find_module_callback_t callback, void *param) + + Finds all modules within the search paths added by + :c:func:`obs_add_module_path()`. + + Relevant data types used with this function: + +.. code:: cpp + + struct obs_module_info { + const char *bin_path; + const char *data_path; + }; + + typedef void (*obs_find_module_callback_t)(void *param, + const struct obs_module_info *info); + +--------------------- + +.. function:: void obs_enum_modules(obs_enum_module_callback_t callback, void *param) + + Enumerates all loaded modules. + + Relevant data types used with this function: + +.. code:: cpp + + typedef void (*obs_enum_module_callback_t)(void *param, obs_module_t *module); + +--------------------- + +.. function:: char *obs_find_module_file(obs_module_t *module, const char *file) + + Returns the location of a plugin module data file. + + Note: Modules should use obs_module_file function defined in obs-module.h + as a more elegant means of getting their files without having to + specify the module parameter. + + :param module: The module associated with the file to locate + :param file: The file to locate + :return: Path string, or NULL if not found. Use bfree to free string + +--------------------- + +.. function:: char *obs_module_get_config_path(obs_module_t *module, const char *file) + + Returns the path of a plugin module config file (whether it exists or not). + + Note: Modules should use obs_module_config_path function defined in + obs-module.h as a more elegant means of getting their files without + having to specify the module parameter. + + :param module: The module associated with the path + :param file: The file to get a path to + :return: Path string, or NULL if not found. Use bfree to free string diff --git a/docs/sphinx/reference-outputs.rst b/docs/sphinx/reference-outputs.rst new file mode 100644 index 0000000..6d671fb --- /dev/null +++ b/docs/sphinx/reference-outputs.rst @@ -0,0 +1,778 @@ +Output API Reference (obs_output_t) +=================================== + +Outputs allow the ability to output the currently rendering audio/video. +Streaming and recording are two common examples of outputs, but not the +only types of outputs. Outputs can receive the raw data or receive +encoded data. The `libobs/obs-output.h`_ file is the dedicated header +for implementing outputs + +.. type:: obs_output_t + + A reference-counted output object. + +.. type:: obs_weak_output_t + + A weak reference to an output object. + +.. code:: cpp + + #include + + +Output Definition Structure (obs_output_info) +--------------------------------------------- + +.. type:: struct obs_output_info + + Output definition structure. + +.. member:: const char *obs_output_info.id + + Unique string identifier for the source (required). + +.. member:: uint32_t obs_output_info.flags + + Output capability flags (required). + + (Author's note: This should be renamed to "capability_flags") + + A bitwise OR combination of one or more of the following values: + + - **OBS_OUTPUT_VIDEO** - Can output video. + + - **OBS_OUTPUT_AUDIO** - Can output audio. + + - **OBS_OUTPUT_AV** - Combines OBS_OUTPUT_VIDEO and OBS_OUTPUT_AUDIO. + + - **OBS_OUTPUT_ENCODED** - Output is encoded. + + When this capability flag is used, the output must have encoders + assigned to it via the :c:func:`obs_output_set_video_encoder()` + and/or :c:func:`obs_output_set_audio_encoder()` functions in order + to be started. + + - **OBS_OUTPUT_SERVICE** - Output requires a service object. + + When this capability flag is used, the output must have a service + assigned to it via the :c:func:`obs_output_set_service()` function + in order to be started. + + This is usually used with live streaming outputs that stream to + specific services. + + - **OBS_OUTPUT_MULTI_TRACK** - Output supports multiple audio tracks. + + When this capability flag is used, specifies that this output + supports multiple encoded audio tracks simultaneously. + +.. member:: const char *(*obs_output_info.get_name)(void *type_data) + + Get the translated name of the output type. + + :param type_data: The type_data variable of this structure + :return: The translated name of the output type + +.. member:: void *(*obs_output_info.create)(obs_data_t *settings, obs_output_t *output) + + Creates the implementation data for the output. + + :param settings: Settings to initialize the output with + :param output: Output that this data is associated with + :return: The implementation data associated with this output + +.. member:: void (*obs_output_info.destroy)(void *data) + + Destroys the implementation data for the output. + +.. member:: bool (*obs_output_info.start)(void *data) + + Starts the output. If needed, this function can spawn a thread, + return *true* immediately, and then signal for failure later. + + :return: *true* if successful or deferring to a signal to indicate + failure, *false* on failure to start + +.. member:: void (*obs_output_info.stop)(void *data, uint64_t ts) + + Requests an output to stop at a specified time. The *ts* parameter + indicates when the stop should occur. Output will actually stop when + either the :c:func:`obs_output_end_data_capture()` or + :c:func:`obs_output_signal_stop()` functions are called. If *ts* is + 0, an immediate stop was requested. + + :param ts: The timestamp to stop. If 0, the output should attempt to + stop immediately rather than wait for any more data to + process + +.. member:: void (*obs_output_info.raw_video)(void *data, struct video_data *frame) + + This is called when the output receives raw video data. Only applies + to outputs that are not encoded. + + :param frame: The raw video frame + +.. member:: void (*obs_output_info.raw_audio)(void *data, struct audio_data *frames) + + This is called when the output recieves raw audio data. Only applies + to outputs that are not encoded. + + :param frames: The raw audio frames + +.. member:: void (*obs_output_info.encoded_packet)(void *data, struct encoder_packet *packet) + + This is called when the output receives encoded video/audio data. + Only applies to outputs that are encoded. Packets will always be + given in monotonic timestamp order. + + :param packet: The video or audio packet + +.. member:: void (*obs_output_info.update)(void *data, obs_data_t *settings) + + Updates the settings for this output. + + (Optional) + + :param settings: New settings for this output + +.. member:: void (*obs_output_info.get_defaults)(obs_data_t *settings) + void (*obs_output_info.get_defaults2)(void *type_data, obs_data_t *settings) + + Sets the default settings for this output. + + (Optional) + + :param settings: Default settings. Call obs_data_set_default* + functions on this object to set default setting + values + +.. member:: obs_properties_t *(*obs_output_info.get_properties)(void *data) + obs_properties_t *(*obs_output_info.get_properties2)(void *data, void *type_data) + + Gets the property information of this output. + + (Optional) + + :return: The properties of the output + +.. member:: void (*obs_output_info.pause)(void *data) + + Pauses the output (if the output supports pausing). + + (Author's note: This is currently unimplemented) + + (Optional) + +.. member:: uint64_t (*obs_output_info.get_total_bytes)(void *data) + + Returns the number of total bytes processed by this output. + + (Optional) + + :return: Total bytes processed by this output since it started + +.. member:: int (*obs_output_info.get_dropped_frames)(void *data) + + Returns the number of dropped frames. + + (Optional) + + :return: Number of dropped frames due to network congestion by this + output since it started + +.. member:: void *obs_output_info.type_data + void (*obs_output_info.free_type_data)(void *type_data) + + Private data associated with this entry. Note that this is not the + same as the implementation data; this is used to differentiate + between two different types if the same callbacks are used for more + than one different type. + + (Optional) + +.. member:: float (*obs_output_info.get_congestion)(void *data) + + This function is used to indicate how currently congested the output + is. Useful for visualizing how much data is backed up on streaming + outputs. + + (Optional) + + :return: Current congestion value (0.0f..1.0f) + +.. member:: int (*obs_output_info.get_connect_time_ms)(void *data) + + This function is used to determine how many milliseconds it took to + connect to its current server. + + (Optional) + + :return: Milliseconds it took to connect to its current server + +.. member:: const char *obs_output_info.encoded_video_codecs + const char *obs_output_info.encoded_audio_codecs + + This variable specifies which codecs are supported by an encoded + output, separated by semicolon. + + (Optional, though recommended) + +.. _output_signal_handler_reference: + +Output Signals +-------------- + +**start** (ptr output) + + Called when the output starts. + +**stop** (ptr output, int code) + + Called when the output stops. + + :Parameters: - **code** - Can be one of the following values: + + | OBS_OUTPUT_SUCCESS - Successfuly stopped + | OBS_OUTPUT_BAD_PATH - The specified path was invalid + | OBS_OUTPUT_CONNECT_FAILED - Failed to connect to a server + | OBS_OUTPUT_INVALID_STREAM - Invalid stream path + | OBS_OUTPUT_ERROR - Generic error + | OBS_OUTPUT_DISCONNECTED - Unexpectedly disconnected + | OBS_OUTPUT_UNSUPPORTED - The settings, video/audio format, or codecs are unsupported by this output + | OBS_OUTPUT_NO_SPACE - Ran out of disk space + +**starting** (ptr output) + + Called when the output is starting. + +**stopping** (ptr output) + + Called when the output is stopping. + +**activate** (ptr output) + + Called when the output activates (starts capturing data). + +**deactivate** (ptr output) + + Called when the output deactivates (stops capturing data). + +**reconnect** (ptr output) + + Called when the output is reconnecting. + +**reconnect_success** (ptr output) + + Called when the output has successfully reconnected. + +General Output Functions +------------------------ + +.. function:: void obs_register_output(struct obs_output_info *info) + + Registers an output type. Typically used in + :c:func:`obs_module_load()` or in the program's initialization phase. + +--------------------- + +.. function:: const char *obs_output_get_display_name(const char *id) + + Calls the :c:member:`obs_output_info.get_name` callback to get the + translated display name of an output type. + + :param id: The output type string identifier + :return: The translated display name of an output type + +--------------------- + +.. function:: obs_output_t *obs_output_create(const char *id, const char *name, obs_data_t *settings, obs_data_t *hotkey_data) + + Creates an output with the specified settings. + + The "output" context is used for anything related to outputting the + final video/audio mix (E.g. streaming or recording). Use + obs_output_release to release it. + + :param id: The output type string identifier + :param name: The desired name of the output. If this is + not unique, it will be made to be unique + :param settings: The settings for the output, or *NULL* if + none + :param hotkey_data: Saved hotkey data for the output, or *NULL* + if none + :return: A reference to the newly created output, or + *NULL* if failed + +--------------------- + +.. function:: void obs_output_addref(obs_output_t *output) + void obs_output_release(obs_output_t *output) + + Adds/releases a reference to an output. When the last reference is + released, the output is destroyed. + +--------------------- + +.. function:: obs_weak_output_t *obs_output_get_weak_output(obs_output_t *output) + obs_output_t *obs_weak_output_get_output(obs_weak_output_t *weak) + + These functions are used to get a weak reference from a strong output + reference, or a strong output reference from a weak reference. If + the output is destroyed, *obs_weak_output_get_output* will return + *NULL*. + +--------------------- + +.. function:: void obs_weak_output_addref(obs_weak_output_t *weak) + void obs_weak_output_release(obs_weak_output_t *weak) + + Adds/releases a weak reference to an output. + +--------------------- + +.. function:: const char *obs_output_get_name(const obs_output_t *output) + + :return: The name of the output + +--------------------- + +.. function:: bool obs_output_start(obs_output_t *output) + + Starts the output. + + :return: *true* if output successfuly started, *false* otherwise. If + the output failed to start, + :c:func:`obs_output_get_last_error()` may contain a specific + error string related to the reason + +--------------------- + +.. function:: void obs_output_stop(obs_output_t *output) + + Requests the output to stop. The output will wait until all data is + sent up until the time the call was made, then when the output has + successfully stopped, it will send the "stop" signal. See + :ref:`output_signal_handler_reference` for more information on output + signals. + +--------------------- + +.. function:: void obs_output_set_delay(obs_output_t *output, uint32_t delay_sec, uint32_t flags) + + Sets the current output delay, in seconds (if the output supports delay) + + If delay is currently active, it will set the delay value, but will not + affect the current delay, it will only affect the next time the output is + activated. + + :param delay_sec: Amount to delay the output, in seconds + :param flags: | Can be 0 or a combination of one of the following values: + | OBS_OUTPUT_DELAY_PRESERVE - On reconnection, start where it left of on reconnection. Note however that this option will consume extra memory to continually increase delay while waiting to reconnect + +--------------------- + +.. function:: uint32_t obs_output_get_delay(const obs_output_t *output) + + Gets the currently set delay value, in seconds. + +--------------------- + +.. function:: uint32_t obs_output_get_active_delay(const obs_output_t *output) + + If delay is active, gets the currently active delay value, in + seconds. The active delay can increase if the + OBS_OUTPUT_DELAY_PRESERVE flag was set when setting a delay. + +--------------------- + +.. function:: void obs_output_force_stop(obs_output_t *output) + + Attempts to get the output to stop immediately without waiting for + data to send. + +--------------------- + +.. function:: bool obs_output_active(const obs_output_t *output) + + :return: *true* if the output is currently active, *false* otherwise + +--------------------- + +.. function:: obs_data_t *obs_output_defaults(const char *id) + + :return: An incremented reference to the output's default settings + +--------------------- + +.. function:: obs_properties_t *obs_output_properties(const obs_output_t *output) + obs_properties_t *obs_get_output_properties(const char *id) + + Use these functions to get the properties of an output or output + type. Properties are optionally used (if desired) to automatically + generate user interface widgets to allow users to update settings. + + :return: The properties list for a specific existing output. Free + with :c:func:`obs_properties_destroy()` + +--------------------- + +.. function:: void obs_output_update(obs_output_t *output, obs_data_t *settings) + + Updates the settings for this output context. + +--------------------- + +.. function:: bool obs_output_can_pause(const obs_output_t *output) + + :return: *true* if the output can be paused, *false* otherwise + +--------------------- + +.. function:: void obs_output_pause(obs_output_t *output) + + Pause an output (if supported by the output). + + (Author's Note: Not yet implemented) + +--------------------- + +.. function:: obs_data_t *obs_output_get_settings(const obs_output_t *output) + + :return: An incremented reference to the output's settings + +--------------------- + +.. function:: signal_handler_t *obs_output_get_signal_handler(const obs_output_t *output) + + :return: The signal handler of the output + +--------------------- + +.. function:: proc_handler_t *obs_output_get_proc_handler(const obs_output_t *output) + + :return: The procedure handler of the output + +--------------------- + +.. function:: void obs_output_set_media(obs_output_t *output, video_t *video, audio_t *audio) + + Sets the current video/audio handlers for the output (typically + :c:func:`obs_get_video()` and :c:func:`obs_get_audio()`). Only used + with raw outputs so they can catch the raw video/audio frames. + +--------------------- + +.. function:: video_t *obs_output_video(const obs_output_t *output) + audio_t *obs_output_audio(const obs_output_t *output) + + Gets the current video/audio handlers for the output. + +--------------------- + +.. function:: void obs_output_set_mixer(obs_output_t *output, size_t mixer_idx) + size_t obs_output_get_mixer(const obs_output_t *output) + + Sets/gets the current audio mixer for non-encoded outputs. + +--------------------- + +.. function:: void obs_output_set_video_encoder(obs_output_t *output, obs_encoder_t *encoder) + void obs_output_set_audio_encoder(obs_output_t *output, obs_encoder_t *encoder, size_t idx) + + Sets the video/audio encoders for an encoded output. + + :param encoder: The video/audio encoder + :param idx: The audio encoder index if the output supports + multiple audio streams at once + +--------------------- + +.. function:: obs_encoder_t *obs_output_get_video_encoder(const obs_output_t *output) + obs_encoder_t *obs_output_get_audio_encoder(const obs_output_t *output, size_t idx) + + Gets the video/audio encoders for an encoded output. + + :param idx: The audio encoder index if the output supports + multiple audio streams at once + :return: The video/audio encoder. The reference is not + incremented + +--------------------- + +.. function:: void obs_output_set_service(obs_output_t *output, obs_service_t *service) + obs_service_t *obs_output_get_service(const obs_output_t *output) + + Sets/gets the service for outputs that require services (such as RTMP + outputs). *obs_output_get_service* does not return an incremented + reference. + +--------------------- + +.. function:: void obs_output_set_reconnect_settings(obs_output_t *output, int retry_count, int retry_sec); + + Sets the auto-reconnect settings for outputs that support it. The + retry time will double on each retry to prevent overloading services. + + :param retry_count: Maximum retry count. Set to 0 to disable + reconnecting + :param retry_sec: Starting retry wait duration, in seconds + +--------------------- + +.. function:: uint64_t obs_output_get_total_bytes(const obs_output_t *output) + + :return: Total bytes sent/processed + +--------------------- + +.. function:: int obs_output_get_frames_dropped(const obs_output_t *output) + + :return: Number of frames that were dropped due to network congestion + +--------------------- + +.. function:: int obs_output_get_total_frames(const obs_output_t *output) + + :return: Total frames sent/processed + +--------------------- + +.. function:: void obs_output_set_preferred_size(obs_output_t *output, uint32_t width, uint32_t height) + + Sets the preferred scaled resolution for this output. Set width and height + to 0 to disable scaling. + + If this output uses an encoder, it will call obs_encoder_set_scaled_size on + the encoder before the stream is started. If the encoder is already active, + then this function will trigger a warning and do nothing. + +--------------------- + +.. function:: uint32_t obs_output_get_width(const obs_output_t *output) + uint32_t obs_output_get_height(const obs_output_t *output) + + :return: The width/height of the output + +--------------------- + +.. function:: float obs_output_get_congestion(obs_output_t *output) + + :return: The congestion value. This value is used to visualize the + current congestion of a network output. For example, if + there is no congestion, the value will be 0.0f, if it's + fully congested, the value will be 1.0f + +--------------------- + +.. function:: int obs_output_get_connect_time_ms(obs_output_t *output) + + :return: How long the output took to connect to a server, in + milliseconds + +--------------------- + +.. function:: bool obs_output_reconnecting(const obs_output_t *output) + + :return: *true* if the output is currently reconnecting to a server, + *false* otherwise + +--------------------- + +.. function:: const char *obs_output_get_supported_video_codecs(const obs_output_t *output) + const char *obs_output_get_supported_audio_codecs(const obs_output_t *output) + + :return: Supported video/audio codecs of an encoded output, separated + by semicolen + +--------------------- + +Functions used by outputs +------------------------- + +.. function:: void obs_output_set_last_error(obs_output_t *output, const char *message) + const char *obs_output_get_last_error(obs_output_t *output) + + Sets/gets the translated error message that is presented to a user in + case of disconnection, inability to connect, etc. + +--------------------- + +.. function:: void obs_output_set_video_conversion(obs_output_t *output, const struct video_scale_info *conversion) + + Optionally sets the video conversion information. Only used by raw + outputs. + + Relevant data types used with this function: + +.. code:: cpp + + enum video_format { + VIDEO_FORMAT_NONE, + + /* planar 420 format */ + VIDEO_FORMAT_I420, /* three-plane */ + VIDEO_FORMAT_NV12, /* two-plane, luma and packed chroma */ + + /* packed 422 formats */ + VIDEO_FORMAT_YVYU, + VIDEO_FORMAT_YUY2, /* YUYV */ + VIDEO_FORMAT_UYVY, + + /* packed uncompressed formats */ + VIDEO_FORMAT_RGBA, + VIDEO_FORMAT_BGRA, + VIDEO_FORMAT_BGRX, + VIDEO_FORMAT_Y800, /* grayscale */ + + /* planar 4:4:4 */ + VIDEO_FORMAT_I444, + }; + + enum video_colorspace { + VIDEO_CS_DEFAULT, + VIDEO_CS_601, + VIDEO_CS_709, + }; + + enum video_range_type { + VIDEO_RANGE_DEFAULT, + VIDEO_RANGE_PARTIAL, + VIDEO_RANGE_FULL + }; + + struct video_scale_info { + enum video_format format; + uint32_t width; + uint32_t height; + enum video_range_type range; + enum video_colorspace colorspace; + }; + +--------------------- + +.. function:: void obs_output_set_audio_conversion(obs_output_t *output, const struct audio_convert_info *conversion) + + Optionally sets the audio conversion information. Only used by raw + outputs. + + Relevant data types used with this function: + +.. code:: cpp + + enum audio_format { + AUDIO_FORMAT_UNKNOWN, + + AUDIO_FORMAT_U8BIT, + AUDIO_FORMAT_16BIT, + AUDIO_FORMAT_32BIT, + AUDIO_FORMAT_FLOAT, + + AUDIO_FORMAT_U8BIT_PLANAR, + AUDIO_FORMAT_16BIT_PLANAR, + AUDIO_FORMAT_32BIT_PLANAR, + AUDIO_FORMAT_FLOAT_PLANAR, + }; + + enum speaker_layout { + SPEAKERS_UNKNOWN, + SPEAKERS_MONO, + SPEAKERS_STEREO, + SPEAKERS_2POINT1, + SPEAKERS_QUAD, + SPEAKERS_4POINT1, + SPEAKERS_5POINT1, + SPEAKERS_5POINT1_SURROUND, + SPEAKERS_7POINT1, + SPEAKERS_7POINT1_SURROUND, + SPEAKERS_SURROUND, + }; + + struct audio_convert_info { + uint32_t samples_per_sec; + enum audio_format format; + enum speaker_layout speakers; + }; + +--------------------- + +.. function:: bool obs_output_can_begin_data_capture(const obs_output_t *output, uint32_t flags) + + Determines whether video/audio capture (encoded or raw) is able to + start. Call this before initializing any output data to ensure that + the output can start. + + :param flags: Set to 0 to initialize both audio/video, otherwise a + bitwise OR combination of OBS_OUTPUT_VIDEO and/or + OBS_OUTPUT_AUDIO + :return: *true* if data capture can begin + +--------------------- + +.. function:: bool obs_output_initialize_encoders(obs_output_t *output, uint32_t flags) + + Initializes any encoders/services associated with the output. This + must be called for encoded outputs before calling + :c:func:`obs_output_begin_data_capture()`. + + :param flags: Set to 0 to initialize both audio/video, otherwise a + bitwise OR combination of OBS_OUTPUT_VIDEO and/or + OBS_OUTPUT_AUDIO + :return: *true* if successful, *false* otherwise + +--------------------- + +.. function:: bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags) + + Begins data capture from raw media or encoders. This is typically + when the output actually activates (starts) internally. Video/audio + data will start being sent to the callbacks of the output. + + :param flags: Set to 0 to initialize both audio/video, otherwise a + bitwise OR combination of OBS_OUTPUT_VIDEO and/or + OBS_OUTPUT_AUDIO + :return: *true* if successful, *false* otherwise. Typically the + return value does not need to be checked if + :c:func:`obs_output_can_begin_data_capture()` was + called + +--------------------- + +.. function:: void obs_output_end_data_capture(obs_output_t *output) + + Ends data capture of an output. This is typically when the output + actually intentionally deactivates (stops). Video/audio data will + stop being sent to the callbacks of the output. The output will + trigger the "stop" signal with the OBS_OUTPUT_SUCCESS code to + indicate that the output has stopped successfully. See + :ref:`output_signal_handler_reference` for more information on output + signals. + +--------------------- + +.. function:: void obs_output_signal_stop(obs_output_t *output, int code) + + Ends data capture of an output with an output code, indicating that + the output stopped unexpectedly. This is typically used if for + example the server was disconnected for some reason, or if there was + an error saving to file. The output will trigger the "stop" signal + with the the desired code to indicate that the output has stopped + successfully. See :ref:`output_signal_handler_reference` for more + information on output signals. + + :c:func:`obs_output_set_last_error()` may be used in conjunction with + these error codes to optionally relay more detailed error information + to the user + + :param code: | Can be one of the following values: + | OBS_OUTPUT_SUCCESS - Successfuly stopped + | OBS_OUTPUT_BAD_PATH - The specified path was invalid + | OBS_OUTPUT_CONNECT_FAILED - Failed to connect to a server + | OBS_OUTPUT_INVALID_STREAM - Invalid stream path + | OBS_OUTPUT_ERROR - Generic error + | OBS_OUTPUT_DISCONNECTED - Unexpectedly disconnected + | OBS_OUTPUT_UNSUPPORTED - The settings, video/audio format, or codecs are unsupported by this output + | OBS_OUTPUT_NO_SPACE - Ran out of disk space + +.. --------------------------------------------------------------------------- + +.. _libobs/obs-output.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-output.h diff --git a/docs/sphinx/reference-properties.rst b/docs/sphinx/reference-properties.rst new file mode 100644 index 0000000..d998789 --- /dev/null +++ b/docs/sphinx/reference-properties.rst @@ -0,0 +1,597 @@ +Properties API Reference (obs_properties_t) +=========================================== + +Properties are used to enumerate available settings for a libobs object. +Typically this is used to automatically generate user interface widgets, +though can be used to enumerate available and/or valid values for +specific settings as well. + +.. type:: obs_properties_t + + Properties object (not reference counted). + +.. type:: obs_property_t + + A property object (not reference counted). + +.. code:: cpp + + #include + + +General Functions +----------------- + +.. function:: obs_properties_t *obs_properties_create(void) + + :return: A new properties object. + +--------------------- + +.. function:: obs_properties_t *obs_properties_create_param(void *param, void (*destroy)(void *param)) + + Creates a new properties object with specific private data *param* + associated with the object, and is automatically freed with the + object when the properties are destroyed via the *destroy* function. + + :return: A new properties object. + +--------------------- + +.. function:: void obs_properties_destroy(obs_properties_t *props) + +--------------------- + +.. function:: void obs_properties_set_flags(obs_properties_t *props, uint32_t flags) + uint32_t obs_properties_get_flags(obs_properties_t *props) + + :param flags: 0 or a bitwise OR combination of one of the following + values: + + - OBS_PROPERTIES_DEFER_UPDATE - A hint that tells the + front-end to defers updating the settings until the + user has finished editing all properties rather than + immediately updating any settings + +--------------------- + +.. function:: void obs_properties_set_param(obs_properties_t *props, void *param, void (*destroy)(void *param)) + void *obs_properties_get_param(obs_properties_t *props) + + Sets custom data associated with this properties object. If private + data is already associated with the object, that private data will be + destroyed before assigning new private data to it. + +--------------------- + +.. function:: void obs_properties_apply_settings(obs_properties_t *props, obs_data_t *settings) + + Applies settings to the properties by calling all the necessary + modification callbacks + +--------------------- + + +Property Object Functions +------------------------- + +.. function:: obs_property_t *obs_properties_add_bool(obs_properties_t *props, const char *name, const char *description) + + Adds a boolean property. + + :param name: Setting identifier string + :param description: Localized name shown to user + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_int(obs_properties_t *props, const char *name, const char *description, int min, int max, int step) + + Adds an integer property. + + :param name: Setting identifier string + :param description: Localized name shown to user + :param min: Minimum value + :param max: Maximum value + :param step: Step value + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_float(obs_properties_t *props, const char *name, const char *description, double min, double max, double step) + + :param name: Setting identifier string + :param description: Localized name shown to user + :param min: Minimum value + :param max: Maximum value + :param step: Step value + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_int_slider(obs_properties_t *props, const char *name, const char *description, int min, int max, int step) + + :param name: Setting identifier string + :param description: Localized name shown to user + :param min: Minimum value + :param max: Maximum value + :param step: Step value + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_float_slider(obs_properties_t *props, const char *name, const char *description, double min, double max, double step) + + :param name: Setting identifier string + :param description: Localized name shown to user + :param min: Minimum value + :param max: Maximum value + :param step: Step value + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_text(obs_properties_t *props, const char *name, const char *description, enum obs_text_type type) + + :param name: Setting identifier string + :param description: Localized name shown to user + :param type: Can be one of the following values: + + - **OBS_TEXT_DEFAULT** - Single line of text + - **OBS_TEXT_PASSWORD** - Single line of text (passworded) + - **OBS_TEXT_MULTILINE** - Multi-line text + + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_path(obs_properties_t *props, const char *name, const char *description, enum obs_path_type type, const char *filter, const char *default_path) + + Adds a 'path' property. Can be a directory or a file. + + If target is a file path, the filters should be this format, separated by + double semi-colens, and extensions separated by space:: + + "Example types 1 and 2 (*.ex1 *.ex2);;Example type 3 (*.ex3)" + + :param name: Setting identifier string + :param description: Localized name shown to user + :param type: Can be one of the following values: + + - **OBS_PATH_FILE** - File (for reading) + - **OBS_PATH_FILE_SAVE** - File (for writing) + - **OBS_PATH_DIRECTORY** - Directory + + :param filter: If type is a file path, then describes the file filter + that the user can browse. Items are separated via + double semi-colens. If multiple file types in a + filter, separate with space. + :param default_path: The default path to start in, or *NULL* + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_list(obs_properties_t *props, const char *name, const char *description, enum obs_combo_type type, enum obs_combo_format format) + + Adds an integer/string/floating point item list. This would be + implemented as a combo box in user interface. + + :param name: Setting identifier string + :param description: Localized name shown to user + :param type: Can be one of the following values: + + - **OBS_COMBO_TYPE_EDITABLE** - Can be edited. + Only used with string lists. + - **OBS_COMBO_TYPE_LIST** - Not ediable. + + :param format: Can be one of the following values: + + - **OBS_COMBO_FORMAT_INT** - Integer list + - **OBS_COMBO_FORMAT_FLOAT** - Floating point + list + - **OBS_COMBO_FORMAT_STRING** - String list + + :return: The property + + Important Related Functions: + + - :c:func:`obs_property_list_add_string` + - :c:func:`obs_property_list_add_int` + - :c:func:`obs_property_list_add_float` + - :c:func:`obs_property_list_insert_string` + - :c:func:`obs_property_list_insert_int` + - :c:func:`obs_property_list_insert_float` + - :c:func:`obs_property_list_item_remove` + - :c:func:`obs_property_list_clear` + +--------------------- + +.. function:: obs_property_t *obs_properties_add_color(obs_properties_t *props, const char *name, const char *description) + + Adds a color property. + + :param name: Setting identifier string + :param description: Localized name shown to user + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_button(obs_properties_t *props, const char *name, const char *text, obs_property_clicked_t callback) + + Adds a button property. This property does not actually store any + settings; it's used to implement a button in user interface if the + properties are used to generate user interface. + + :param name: Setting identifier string + :param description: Localized name shown to user + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_font(obs_properties_t *props, const char *name, const char *description) + + Adds a font property. + + :param name: Setting identifier string + :param description: Localized name shown to user + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_editable_list(obs_properties_t *props, const char *name, const char *description, enum obs_editable_list_type type, const char *filter, const char *default_path) + + Adds a list in which the user can add/insert/remove items. + + :param name: Setting identifier string + :param description: Localized name shown to user + :param type: Can be one of the following values: + + - **OBS_EDITABLE_LIST_TYPE_STRINGS** - An + editable list of strings. + - **OBS_EDITABLE_LIST_TYPE_FILES** - An + editable list of files. + - **OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS** - + An editable list of files and URLs. + + :param filter: File filter to use if a file list + :param default_path: Default path if a file list + :return: The property + +--------------------- + +.. function:: obs_property_t *obs_properties_add_frame_rate(obs_properties_t *props, const char *name, const char *description) + + Adds a frame rate property. + + :param name: Setting identifier string + :param description: Localized name shown to user + :return: The property + + Important Related Functions: + + - :c:func:`obs_property_frame_rate_option_add` + - :c:func:`obs_property_frame_rate_fps_range_add` + - :c:func:`obs_property_frame_rate_option_insert` + - :c:func:`obs_property_frame_rate_fps_range_insert` + +--------------------- + + +Property Enumeration Functions +------------------------------ + +.. function:: obs_property_t *obs_properties_first(obs_properties_t *props) + + :return: The first property in the properties object. + +--------------------- + +.. function:: obs_property_t *obs_properties_get(obs_properties_t *props, const char *property) + + :param property: The name of the property to get + :return: A specific property or *NULL* if not found + +--------------------- + +.. function:: bool obs_property_next(obs_property_t **p) + + :param p: Pointer to the pointer of the next property + :return: *true* if successful, *false* if no more properties + +--------------------- + +.. function:: const char * obs_property_name(obs_property_t *p) + + :return: The setting identifier string of the property + + *(Author's Note: "name" was a bad name to use here. Should have been + "setting")* + +--------------------- + +.. function:: const char * obs_property_description(obs_property_t *p) + + :return: The actual localized display name of the property + + *(Author's note: This one should have been the "name")* + +--------------------- + +.. function:: const char * obs_property_long_description(obs_property_t *p) + + :return: A detailed description of what the setting is used for. + Usually used with things like tooltips. + +--------------------- + +.. function:: enum obs_property_type obs_property_get_type(obs_property_t *p) + + :return: One of the following values: + + - OBS_PROPERTY_INVALID + - OBS_PROPERTY_BOOL + - OBS_PROPERTY_INT + - OBS_PROPERTY_FLOAT + - OBS_PROPERTY_TEXT + - OBS_PROPERTY_PATH + - OBS_PROPERTY_LIST + - OBS_PROPERTY_COLOR + - OBS_PROPERTY_BUTTON + - OBS_PROPERTY_FONT + - OBS_PROPERTY_EDITABLE_LIST + - OBS_PROPERTY_FRAME_RATE + +--------------------- + +.. function:: bool obs_property_enabled(obs_property_t *p) + +--------------------- + +.. function:: bool obs_property_visible(obs_property_t *p) + +--------------------- + +.. function:: int obs_property_int_min(obs_property_t *p) + +--------------------- + +.. function:: int obs_property_int_max(obs_property_t *p) + +--------------------- + +.. function:: int obs_property_int_step(obs_property_t *p) + +--------------------- + +.. function:: enum obs_number_type obs_property_int_type(obs_property_t *p) + +--------------------- + +.. function:: double obs_property_float_min(obs_property_t *p) + +--------------------- + +.. function:: double obs_property_float_max(obs_property_t *p) + +--------------------- + +.. function:: double obs_property_float_step(obs_property_t *p) + +--------------------- + +.. function:: enum obs_number_type obs_property_float_type(obs_property_t *p) + +--------------------- + +.. function:: enum obs_text_type obs_proprety_text_type(obs_property_t *p) + +--------------------- + +.. function:: enum obs_path_type obs_property_path_type(obs_property_t *p) + +--------------------- + +.. function:: const char * obs_property_path_filter(obs_property_t *p) + +--------------------- + +.. function:: const char * obs_property_path_default_path(obs_property_t *p) + +--------------------- + +.. function:: enum obs_combo_type obs_property_list_type(obs_property_t *p) + +--------------------- + +.. function:: enum obs_combo_format obs_property_list_format(obs_property_t *p) + +--------------------- + +.. function:: bool obs_property_list_item_disabled(obs_property_t *p, size_t idx) + +--------------------- + +.. function:: size_t obs_property_list_item_count(obs_property_t *p) + +--------------------- + +.. function:: const char *obs_property_list_item_name(obs_property_t *p, size_t idx) + +--------------------- + +.. function:: const char *obs_property_list_item_string(obs_property_t *p, size_t idx) + +--------------------- + +.. function:: long long obs_property_list_item_int(obs_property_t *p, size_t idx) + +--------------------- + +.. function:: double obs_property_list_item_float(obs_property_t *p, size_t idx) + +--------------------- + +.. function:: enum obs_editable_list_type obs_property_editable_list_type(obs_property_t *p) + +--------------------- + +.. function:: const char *obs_property_editable_list_filter(obs_property_t *p) + +--------------------- + +.. function:: const char *obs_property_editable_list_default_path(obs_property_t *p) + +--------------------- + +.. function:: size_t obs_property_frame_rate_options_count(obs_property_t *p) + +--------------------- + +.. function:: const char *obs_property_frame_rate_option_name(obs_property_t *p, size_t idx) + +--------------------- + +.. function:: const char *obs_property_frame_rate_option_description( obs_property_t *p, size_t idx) + +--------------------- + +.. function:: size_t obs_property_frame_rate_fps_ranges_count(obs_property_t *p) + +--------------------- + +.. function:: struct media_frames_per_second obs_property_frame_rate_fps_range_min( obs_property_t *p, size_t idx) + +--------------------- + +.. function:: struct media_frames_per_second obs_property_frame_rate_fps_range_max( obs_property_t *p, size_t idx) + +--------------------- + + +Property Modification Functions +------------------------------- + +.. function:: void obs_property_set_modified_callback(obs_property_t *p, obs_property_modified_t modified) + + Allows the ability to change the properties depending on what + settings are used by the user. + + Relevant data types used with this function: + +.. code:: cpp + + typedef bool (*obs_property_clicked_t)(obs_properties_t *props, + obs_property_t *property, void *data); + +--------------------- + +.. function:: bool obs_property_modified(obs_property_t *p, obs_data_t *settings) + +--------------------- + +.. function:: bool obs_property_button_clicked(obs_property_t *p, void *obj) + +--------------------- + +.. function:: void obs_property_set_visible(obs_property_t *p, bool visible) + +--------------------- + +.. function:: void obs_property_set_enabled(obs_property_t *p, bool enabled) + +--------------------- + +.. function:: void obs_property_set_description(obs_property_t *p, const char *description) + + Sets the displayed localized name of the property, shown to the user. + +--------------------- + +.. function:: void obs_property_set_long_description(obs_property_t *p, const char *long_description) + + Sets the localized long description of the property, usually shown to + a user via tooltip. + +--------------------- + +.. function:: void obs_property_int_set_limits(obs_property_t *p, int min, int max, int step) + +--------------------- + +.. function:: void obs_property_float_set_limits(obs_property_t *p, double min, double max, double step) + +--------------------- + +.. function:: void obs_property_list_clear(obs_property_t *p) + +--------------------- + +.. function:: size_t obs_property_list_add_string(obs_property_t *p, const char *name, const char *val) + + Adds a string to a string list. + +--------------------- + +.. function:: size_t obs_property_list_add_int(obs_property_t *p, const char *name, long long val) + + Adds an integer to a integer list. + +--------------------- + +.. function:: size_t obs_property_list_add_float(obs_property_t *p, const char *name, double val) + + Adds a floating point to a floating point list. + +--------------------- + +.. function:: void obs_property_list_insert_string(obs_property_t *p, size_t idx, const char *name, const char *val) + + Inserts a string in to a string list. + +--------------------- + +.. function:: void obs_property_list_insert_int(obs_property_t *p, size_t idx, const char *name, long long val) + + Inserts an integer in to an integer list. + +--------------------- + +.. function:: void obs_property_list_insert_float(obs_property_t *p, size_t idx, const char *name, double val) + + Inserts a floating point in to a floating point list. + +--------------------- + +.. function:: void obs_property_list_item_disable(obs_property_t *p, size_t idx, + +--------------------- + +.. function:: void obs_property_list_item_remove(obs_property_t *p, size_t idx) + +--------------------- + +.. function:: size_t obs_property_frame_rate_option_add(obs_property_t *p, const char *name, const char *description) + +--------------------- + +.. function:: size_t obs_property_frame_rate_fps_range_add(obs_property_t *p, struct media_frames_per_second min, struct media_frames_per_second max) + +--------------------- + +.. function:: void obs_property_frame_rate_clear(obs_property_t *p) + +--------------------- + +.. function:: void obs_property_frame_rate_options_clear(obs_property_t *p) + +--------------------- + +.. function:: void obs_property_frame_rate_fps_ranges_clear(obs_property_t *p) + +--------------------- + +.. function:: void obs_property_frame_rate_option_insert(obs_property_t *p, size_t idx, const char *name, const char *description) + +--------------------- + +.. function:: void obs_property_frame_rate_fps_range_insert(obs_property_t *p, size_t idx, struct media_frames_per_second min, struct media_frames_per_second max) diff --git a/docs/sphinx/reference-scenes.rst b/docs/sphinx/reference-scenes.rst new file mode 100644 index 0000000..e01d8a0 --- /dev/null +++ b/docs/sphinx/reference-scenes.rst @@ -0,0 +1,414 @@ +Scene API Reference (obs_scene_t) +================================= + +A scene is a source which contains and renders other sources using +specific transforms and/or filtering + +.. type:: obs_scene_t + + A reference-counted scene object. + +.. type:: obs_sceneitem_t + + A reference-counted scene item object. + +.. code:: cpp + + #include + + +Scene Item Transform Structure (obs_transform_info) +--------------------------------------------------- + +.. type:: struct obs_transform_info + + Scene item transform structure. + +.. member:: struct vec2 obs_transform_info.pos + + Position. + +.. member:: float obs_transform_info.rot + + Rotation (degrees). + +.. member:: struct vec2 obs_transform_info.scale + + Scale. + +.. member:: uint32_t obs_transform_info.alignment + + The alignment of the scene item relative to its position. + + Can be 0 or a bitwise OR combination of one of the following values: + + - **OBS_ALIGN_CENTER** + - **OBS_ALIGN_LEFT** + - **OBS_ALIGN_RIGHT** + - **OBS_ALIGN_TOP** + - **OBS_ALIGN_BOTTOM** + +.. member:: enum obs_bounds_type obs_transform_info.bounds_type + + Can be one of the following values: + + - **OBS_BOUNDS_NONE** - No bounding box + - **OBS_BOUNDS_STRETCH** - Stretch to the bounding box without preserving aspect ratio + - **OBS_BOUNDS_SCALE_INNER** - Scales with aspect ratio to inner bounding box rectangle + - **OBS_BOUNDS_SCALE_OUTER** - Scales with aspect ratio to outer bounding box rectangle + - **OBS_BOUNDS_SCALE_TO_WIDTH** - Scales with aspect ratio to the bounding box width + - **OBS_BOUNDS_SCALE_TO_HEIGHT** - Scales with aspect ratio to the bounding box height + - **OBS_BOUNDS_MAX_ONLY** - Scales with aspect ratio, but only to the size of the source maximum + +.. member:: uint32_t obs_transform_info.bounds_alignment + + The alignment of the source within the bounding box. + + Can be 0 or a bitwise OR combination of one of the following values: + + - **OBS_ALIGN_CENTER** + - **OBS_ALIGN_LEFT** + - **OBS_ALIGN_RIGHT** + - **OBS_ALIGN_TOP** + - **OBS_ALIGN_BOTTOM** + +.. member:: struct vec2 obs_transform_info.bounds + + The bounding box (if a bounding box is enabled). + + +Scene Item Crop Structure (obs_sceneitem_crop) +---------------------------------------------- + +.. type:: struct obs_sceneitem_crop + + Scene item crop structure. + +.. member:: int obs_sceneitem_crop.left + + Left crop value. + +.. member:: int obs_sceneitem_crop.top + + Top crop value. + +.. member:: int obs_sceneitem_crop.right + + Right crop value. + +.. member:: int obs_sceneitem_crop.bottom + + Bottom crop value. + + +.. _scene_signal_reference: + +Scene Signals +------------- + +**item_add** (ptr scene, ptr item) + + Called when a scene item has been added to the scene. + +**item_remove** (ptr scene, ptr item) + + Called when a scene item has been removed from the scen. + +**reorder** (ptr scene) + + Called when scene items have been reoredered in the scene. + +**item_visible** (ptr scene, ptr item, bool visible) + + Called when a scene item's visibility state changes. + +**item_select** (ptr scene, ptr item) +**item_deselect** (ptr scene, ptr item) + + Called when a scene item has been selected/deselected. + + (Author's note: These should be replaced) + +**item_transform** (ptr scene, ptr item) + + Called when a scene item's transform has changed. + + +General Scene Functions +----------------------- + +.. function:: obs_scene_t *obs_scene_create(const char *name) + + :param name: Name of the scene source. If it's not unique, it will + be made unique + :return: A reference to a scene + +--------------------- + +.. function:: obs_scene_t *obs_scene_create_private(const char *name) + + :param name: Name of the scene source. Does not have to be unique, + or can be *NULL* + :return: A reference to a private scene + +--------------------- + +.. function:: obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name, enum obs_scene_duplicate_type type) + + Duplicates a scene. When a scene is duplicated, its sources can be + just referenced, or fully duplicated. + + :param name: Name of the new scene source + + :param type: | Type of duplication: + | OBS_SCENE_DUP_REFS - Duplicates the scene, but scene items are only duplicated with references + | OBS_SCENE_DUP_COPY - Duplicates the scene, and scene items are also fully duplicated when possible + | OBS_SCENE_DUP_PRIVATE_REFS - Duplicates with references, but the scene is a private source + | OBS_SCENE_DUP_PRIVATE_COPY - Fully duplicates scene items when possible, but the scene and duplicates sources are private sources + + :return: A reference to a new scene + +--------------------- + +.. function:: void obs_scene_addref(obs_scene_t *scene) + void obs_scene_release(obs_scene_t *scene) + + Adds/releases a reference to a scene. + +--------------------- + +.. function:: obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source) + + :return: A new scene item for a source within a scene. Does not + increment the reference + +--------------------- + +.. function:: obs_source_t *obs_scene_get_source(const obs_scene_t *scene) + + :return: The scene's source. Does not increment the reference + +--------------------- + +.. function:: obs_scene_t *obs_scene_from_source(const obs_source_t *source) + + :return: The scene context, or *NULL* if not a scene. Does not + increase the reference + +--------------------- + +.. function:: obs_sceneitem_t *obs_scene_find_source(obs_scene_t *scene, const char *name) + + :param name: The name of the source to find + :return: The scene item if found, otherwise *NULL* if not found + +--------------------- + +.. function:: obs_sceneitem_t *obs_scene_find_sceneitem_by_id(obs_scene_t *scene, int64_t id) + + :param id: The unique numeric identifier of the scene item + :return: The scene item if found, otherwise *NULL* if not found + +--------------------- + +.. function:: void obs_scene_enum_items(obs_scene_t *scene, bool (*callback)(obs_scene_t*, obs_sceneitem_t*, void*), void *param) + + Enumerates scene items within a scene. + +--------------------- + +.. function:: bool obs_scene_reorder_items(obs_scene_t *scene, obs_sceneitem_t * const *item_order, size_t item_order_size) + + Reorders items within a scene. + +--------------------- + + +.. _scene_item_reference: + +Scene Item Functions +-------------------- + +.. function:: void obs_sceneitem_addref(obs_sceneitem_t *item) + void obs_sceneitem_release(obs_sceneitem_t *item) + + Adds/releases a reference to a scene item. + +--------------------- + +.. function:: void obs_sceneitem_remove(obs_sceneitem_t *item) + + Removes the scene item from the scene. + +--------------------- + +.. function:: obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item) + + :return: The scene associated with the scene item. Does not + increment the reference + +--------------------- + +.. function:: obs_source_t *obs_sceneitem_get_source(const obs_sceneitem_t *item) + + :return: The source associated with the scene item. Does not + increment the reference + +--------------------- + +.. function:: void obs_sceneitem_set_pos(obs_sceneitem_t *item, const struct vec2 *pos) + void obs_sceneitem_get_pos(const obs_sceneitem_t *item, struct vec2 *pos) + + Sets/gets the position of a scene item. + +--------------------- + +.. function:: void obs_sceneitem_set_rot(obs_sceneitem_t *item, float rot_deg) + float obs_sceneitem_get_rot(const obs_sceneitem_t *item) + + Sets/gets the rotation of a scene item. + +--------------------- + +.. function:: void obs_sceneitem_set_scale(obs_sceneitem_t *item, const struct vec2 *scale) + void obs_sceneitem_get_scale(const obs_sceneitem_t *item, struct vec2 *scale) + + Sets/gets the scaling of the scene item. + +--------------------- + +.. function:: void obs_sceneitem_set_alignment(obs_sceneitem_t *item, uint32_t alignment) + uint32_t obs_sceneitem_get_alignment(const obs_sceneitem_t *item) + + Sets/gets the alignment of the scene item relative to its position. + + :param alignment: | Can be any bitwise OR combination of: + | OBS_ALIGN_CENTER + | OBS_ALIGN_LEFT + | OBS_ALIGN_RIGHT + | OBS_ALIGN_TOP + | OBS_ALIGN_BOTTOM + +--------------------- + +.. function:: void obs_sceneitem_set_order(obs_sceneitem_t *item, enum obs_order_movement movement) + + Changes the scene item's order relative to the other scene items + within the scene. + + :param movement: | Can be one of the following: + | OBS_ORDER_MOVE_UP + | OBS_ORDER_MOVE_DOWN + | OBS_ORDER_MOVE_TOP + | OBS_ORDER_MOVE_BOTTOM + +--------------------- + +.. function:: void obs_sceneitem_set_order_position(obs_sceneitem_t *item, int position) + + Changes the scene item's order index. + +--------------------- + +.. function:: void obs_sceneitem_set_bounds_type(obs_sceneitem_t *item, enum obs_bounds_type type) + enum obs_bounds_type obs_sceneitem_get_bounds_type(const obs_sceneitem_t *item) + + Sets/gets the bounding box type of a scene item. Bounding boxes are + used to stretch/position the source relative to a specific bounding + box of a specific size. + + :param type: | Can be one of the following values: + | OBS_BOUNDS_NONE - No bounding box + | OBS_BOUNDS_STRETCH - Stretch to the bounding box without preserving aspect ratio + | OBS_BOUNDS_SCALE_INNER - Scales with aspect ratio to inner bounding box rectangle + | OBS_BOUNDS_SCALE_OUTER - Scales with aspect ratio to outer bounding box rectangle + | OBS_BOUNDS_SCALE_TO_WIDTH - Scales with aspect ratio to the bounding box width + | OBS_BOUNDS_SCALE_TO_HEIGHT - Scales with aspect ratio to the bounding box height + | OBS_BOUNDS_MAX_ONLY - Scales with aspect ratio, but only to the size of the source maximum + +--------------------- + +.. function:: void obs_sceneitem_set_bounds_alignment(obs_sceneitem_t *item, uint32_t alignment) + uint32_t obs_sceneitem_get_bounds_alignment(const obs_sceneitem_t *item) + + Sets/gets the alignment of the source within the bounding box. + + :param alignment: | Can be any bitwise OR combination of: + | OBS_ALIGN_CENTER + | OBS_ALIGN_LEFT + | OBS_ALIGN_RIGHT + | OBS_ALIGN_TOP + | OBS_ALIGN_BOTTOM + +--------------------- + +.. function:: void obs_sceneitem_set_bounds(obs_sceneitem_t *item, const struct vec2 *bounds) + void obs_sceneitem_get_bounds(const obs_sceneitem_t *item, struct vec2 *bounds) + + Sets/gets the bounding box width/height of the scene item. + +--------------------- + +.. function:: void obs_sceneitem_set_info(obs_sceneitem_t *item, const struct obs_transform_info *info) + void obs_sceneitem_get_info(const obs_sceneitem_t *item, struct obs_transform_info *info) + + Sets/gets the transform information of the scene item. + +--------------------- + +.. function:: void obs_sceneitem_get_draw_transform(const obs_sceneitem_t *item, struct matrix4 *transform) + + Gets the transform matrix of the scene item used for drawing the + source. + +--------------------- + +.. function:: void obs_sceneitem_get_box_transform(const obs_sceneitem_t *item, struct matrix4 *transform) + + Gets the transform matrix of the scene item used for the bouding box + or edges of the scene item. + +--------------------- + +.. function:: bool obs_sceneitem_set_visible(obs_sceneitem_t *item, bool visible) + bool obs_sceneitem_visible(const obs_sceneitem_t *item) + + Sets/gets the visibility state of the scene item. + +--------------------- + +.. function:: void obs_sceneitem_set_crop(obs_sceneitem_t *item, const struct obs_sceneitem_crop *crop) + void obs_sceneitem_get_crop(const obs_sceneitem_t *item, struct obs_sceneitem_crop *crop) + + Sets/gets the cropping of the scene item. + +--------------------- + +.. function:: void obs_sceneitem_set_scale_filter(obs_sceneitem_t *item, enum obs_scale_type filter) + enum obs_scale_type obs_sceneitem_get_scale_filter( obs_sceneitem_t *item) + + Sets/gets the scale filter used for the scene item. + + :param filter: | Can be one of the following values: + | OBS_SCALE_DISABLE + | OBS_SCALE_POINT + | OBS_SCALE_BICUBIC + | OBS_SCALE_BILINEAR + | OBS_SCALE_LANCZOS + +--------------------- + +.. function:: void obs_sceneitem_defer_update_begin(obs_sceneitem_t *item) + void obs_sceneitem_defer_update_end(obs_sceneitem_t *item) + + Allows the ability to call any one of the transform functions without + updating the internal matrices until obs_sceneitem_defer_update_end + has been called. + +--------------------- + +.. function:: obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item) + + :return: An incremented reference to the private settings of the + scene item. Allows the front-end to set custom information + which is saved with the scene item diff --git a/docs/sphinx/reference-services.rst b/docs/sphinx/reference-services.rst new file mode 100644 index 0000000..fdfb85e --- /dev/null +++ b/docs/sphinx/reference-services.rst @@ -0,0 +1,276 @@ +Service API Reference (obs_service_t) +===================================== + +Services are custom implementations of streaming services, which are +used with outputs that stream. For example, you could have a custom +implementation for streaming to Twitch, and another for YouTube to allow +the ability to log in and use their APIs to do things such as get the +RTMP servers or control the channel. The `libobs/obs-service.h`_ file +is the dedicated header for implementing services. + +*(Author's note: the service API is incomplete as of this writing)* + +.. type:: obs_service_t + + A reference-counted service object. + +.. type:: obs_weak_service_t + + A weak reference to a service object. + +.. code:: cpp + + #include + + +Service Definition Structure +---------------------------- + +.. type:: struct obs_service_info + + Service definition structure. + +.. member:: const char *obs_service_info.id + + Unique string identifier for the service (required). + +.. member:: const char *(*obs_service_info.get_name)(void *type_data) + + Get the translated name of the service type. + + :param type_data: The type_data variable of this structure + :return: The translated name of the service type + +.. member:: void *(*obs_service_info.create)(obs_data_t *settings, obs_service_t *service) + + Creates the implementation data for the service. + + :param settings: Settings to initialize the service with + :param service: Source that this data is associated with + :return: The implementation data associated with this service + +.. member:: void (*obs_service_info.destroy)(void *data) + + Destroys the implementation data for the service. + +.. member:: void (*obs_service_info.get_defaults)(obs_data_t *settings) + void (*obs_service_info.get_defaults2)(void *type_data, obs_data_t *settings) + + Sets the default settings for this service. + + :param settings: Default settings. Call obs_data_set_default* + functions on this object to set default setting + values + +.. member:: obs_properties_t *(*obs_service_info.get_properties)(void *data) + obs_properties_t *(*obs_service_info.get_properties2)(void *data, void *type_data) + + Gets the property information of this service. + + (Optional) + + :return: The properties of the service + +.. member:: void (*obs_service_info.update)(void *data, obs_data_t *settings) + + Updates the settings for this service. + + (Optional) + + :param settings: New settings for this service + +.. member:: bool (*obs_service_info.initialize)(void *data, obs_output_t *output) + + Called when getting ready to start up an output, before the encoders + and output are initialized. + + (Optional) + + :param output: Output context to use this service with + :return: *true* to allow the output to start up, + *false* to prevent output from starting up + +.. member:: const char *(*obs_service_info.get_url)(void *data) + + :return: The stream URL + +.. member:: const char *(*obs_service_info.get_key)(void *data) + + :return: The stream key + +.. member:: const char *(*obs_service_info.get_username)(void *data) + + (Optional) + + :return: The username + +.. member:: const char *(*obs_service_info.get_password)(void *data) + + (Optional) + + :return: The password + +.. member:: void (*obs_service_info.apply_encoder_settings)(void *data, obs_data_t *video_encoder_settings, obs_data_t *audio_encoder_settings) + + This function is called to apply custom encoder settings specific to + this service. For example, if a service requires a specific keyframe + interval, or has a bitrate limit, the settings for the video and + audio encoders can be optionally modified if the front-end optionally + calls :c:func:`obs_service_apply_encoder_settings()`. + + (Optional) + + :param video_encoder_settings: The audio encoder settings to change + :param audio_encoder_settings: The video encoder settings to change + +.. member:: void *obs_service_info.type_data + void (*obs_service_info.free_type_data)(void *type_data) + + Private data associated with this entry. Note that this is not the + same as the implementation data; this is used to differentiate + between two different types if the same callbacks are used for more + than one different type. + + (Optional) + +.. member:: const char *(*obs_service_info.get_output_type)(void *data) + + (Optional) + + :return: The output type that should be used with this service + + +General Service Functions +------------------------- + +.. function:: void obs_register_service(struct obs_service_info *info) + + Registers a service type. Typically used in + :c:func:`obs_module_load()` or in the program's initialization phase. + +--------------------- + +.. function:: const char *obs_service_get_display_name(const char *id) + + Calls the :c:member:`obs_service_info.get_name` callback to get the + translated display name of a service type. + + :param id: The service type string identifier + :return: The translated display name of a service type + +--------------------- + +.. function:: obs_service_t *obs_service_create(const char *id, const char *name, obs_data_t *settings, obs_data_t *hotkey_data) + + Creates a service with the specified settings. + + The "service" context is used for encoding video/audio data. Use + obs_service_release to release it. + + :param id: The service type string identifier + :param name: The desired name of the service. If this is + not unique, it will be made to be unique + :param settings: The settings for the service, or *NULL* if + none + :param hotkey_data: Saved hotkey data for the service, or *NULL* + if none + :return: A reference to the newly created service, or + *NULL* if failed + +--------------------- + +.. function:: void obs_service_addref(obs_service_t *service) + void obs_service_release(obs_service_t *service) + + Adds/releases a reference to a service. When the last reference is + released, the service is destroyed. + +--------------------- + +.. function:: obs_weak_service_t *obs_service_get_weak_service(obs_service_t *service) + obs_service_t *obs_weak_service_get_service(obs_weak_service_t *weak) + + These functions are used to get a weak reference from a strong service + reference, or a strong service reference from a weak reference. If + the service is destroyed, *obs_weak_service_get_service* will return + *NULL*. + +--------------------- + +.. function:: void obs_weak_service_addref(obs_weak_service_t *weak) + void obs_weak_service_release(obs_weak_service_t *weak) + + Adds/releases a weak reference to a service. + +--------------------- + +.. function:: const char *obs_service_get_name(const obs_service_t *service) + + :return: The name of the service + +--------------------- + +.. function:: obs_data_t *obs_service_defaults(const char *id) + + :return: An incremented reference to the service's default settings + +--------------------- + +.. function:: obs_properties_t *obs_service_properties(const obs_service_t *service) + obs_properties_t *obs_get_service_properties(const char *id) + + Use these functions to get the properties of a service or service + type. Properties are optionally used (if desired) to automatically + generate user interface widgets to allow users to update settings. + + :return: The properties list for a specific existing service. Free + with :c:func:`obs_properties_destroy()` + +--------------------- + +.. function:: obs_data_t *obs_service_get_settings(const obs_service_t *service) + + :return: An incremented reference to the service's settings + +--------------------- + +.. function:: void obs_service_update(obs_service_t *service, obs_data_t *settings) + + Updates the settings for this service context. + +--------------------- + +.. function:: const char *obs_service_get_url(const obs_service_t *service) + + :return: The URL currently used for this service + +--------------------- + +.. function:: const char *obs_service_get_key(const obs_service_t *service) + + :return: Stream key (if any) currently used for this service + +--------------------- + +.. function:: const char *obs_service_get_username(const obs_service_t *service) + + :return: User name (if any) currently used for this service + +--------------------- + +.. function:: const char *obs_service_get_password(const obs_service_t *service) + + :return: Password (if any) currently used for this service + +--------------------- + +.. function:: void obs_service_apply_encoder_settings(obs_service_t *service, obs_data_t *video_encoder_settings, obs_data_t *audio_encoder_settings) + + Applies service-specific video encoder settings. + + :param video_encoder_settings: Video encoder settings. Can be *NULL* + :param audio_encoder_settings: Audio encoder settings. Can be *NULL* + +.. --------------------------------------------------------------------------- + +.. _libobs/obs-service.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-service.h diff --git a/docs/sphinx/reference-settings.rst b/docs/sphinx/reference-settings.rst new file mode 100644 index 0000000..91cd4ba --- /dev/null +++ b/docs/sphinx/reference-settings.rst @@ -0,0 +1,284 @@ +Data Settings API Reference (obs_data_t) +======================================== + +Data settings objects are reference-counted objects that store values in +a string-table or array. They're similar to Json objects, but +additionally allow additional functionality such as default or +auto-selection values. Data is saved/loaded to/from Json text and Json +text files. + +.. type:: obs_data_t + + A reference-counted data object. + +.. type:: obs_data_array_t + + A reference-counted data array object. + +.. code:: cpp + + #include + + +General Functions +----------------- + +.. function:: obs_data_t *obs_data_create() + + :return: A new reference to a data object. + +--------------------- + +.. function:: obs_data_t *obs_data_create_from_json(const char *json_string) + + Creates a data object from a Json string. + + :param json_string: Json string + :return: A new reference to a data object + +--------------------- + +.. function:: obs_data_t *obs_data_create_from_json_file(const char *json_file) + + Creates a data object from a Json file. + + :param json_file: Json file path + :return: A new reference to a data object + +--------------------- + +.. function:: obs_data_t *obs_data_create_from_json_file_safe(const char *json_file, const char *backup_ext) + + Creates a data object from a Json file, with a backup file in case + the original is corrupted or fails to load. + + :param json_file: Json file path + :param backup_ext: Backup file extension + :return: A new reference to a data object + +--------------------- + +.. function:: void obs_data_addref(obs_data_t *data) + void obs_data_release(obs_data_t *data) + + Adds/releases a reference to a data object. + +--------------------- + +.. function:: const char *obs_data_get_json(obs_data_t *data) + + :return: Json string for this object + +--------------------- + +.. function:: bool obs_data_save_json(obs_data_t *data, const char *file) + + Saves the data to a file as Json text. + + :param file: The file to save to + :return: *true* if successful, *false* otherwise + +--------------------- + +.. function:: bool obs_data_save_json_safe(obs_data_t *data, const char *file, const char *temp_ext, const char *backup_ext) + + Saves the data to a file as Json text, and if overwriting an old + file, backs up that old file to help prevent potential file + corruption. + + :param file: The file to save to + :param backup_ext: The backup extension to use for the overwritten + file if it exists + :return: *true* if successful, *false* otherwise + +--------------------- + +.. function:: void obs_data_apply(obs_data_t *target, obs_data_t *apply_data) + + Merges the data of *apply_data* in to *target*. + +--------------------- + +.. function:: void obs_data_erase(obs_data_t *data, const char *name) + + Erases the user data for item *name* within the data object. + +--------------------- + +.. function:: void obs_data_clear(obs_data_t *data) + + Clears all user data in the data object. + +--------------------- + + +Set Functions +------------- + +.. function:: void obs_data_set_string(obs_data_t *data, const char *name, const char *val) + +--------------------- + +.. function:: void obs_data_set_int(obs_data_t *data, const char *name, long long val) + +--------------------- + +.. function:: void obs_data_set_double(obs_data_t *data, const char *name, double val) + +--------------------- + +.. function:: void obs_data_set_bool(obs_data_t *data, const char *name, bool val) + +--------------------- + +.. function:: void obs_data_set_obj(obs_data_t *data, const char *name, obs_data_t *obj) + +--------------------- + +.. function:: void obs_data_set_array(obs_data_t *data, const char *name, obs_data_array_t *array) + +--------------------- + + +.. _obs_data_get_funcs: + +Get Functions +------------- + +.. function:: const char *obs_data_get_string(obs_data_t *data, const char *name) + +--------------------- + +.. function:: long long obs_data_get_int(obs_data_t *data, const char *name) + +--------------------- + +.. function:: double obs_data_get_double(obs_data_t *data, const char *name) + +--------------------- + +.. function:: bool obs_data_get_bool(obs_data_t *data, const char *name) + +--------------------- + +.. function:: obs_data_t *obs_data_get_obj(obs_data_t *data, const char *name) + + :return: An incremented reference to a data object. + +--------------------- + +.. function:: obs_data_array_t *obs_data_get_array(obs_data_t *data, const char *name) + + :return: An incremented reference to a data array object. + +--------------------- + + +.. _obs_data_default_funcs: + +Default Value Functions +----------------------- + +Default values are used to determine what value will be given if a value +is not set. + +.. function:: void obs_data_set_default_string(obs_data_t *data, const char *name, const char *val) + const char *obs_data_get_default_string(obs_data_t *data, const char *name) + +--------------------- + +.. function:: void obs_data_set_default_int(obs_data_t *data, const char *name, long long val) + long long obs_data_get_default_int(obs_data_t *data, const char *name) + +--------------------- + +.. function:: void obs_data_set_default_double(obs_data_t *data, const char *name, double val) + double obs_data_get_default_double(obs_data_t *data, const char *name) + +--------------------- + +.. function:: void obs_data_set_default_bool(obs_data_t *data, const char *name, bool val) + bool obs_data_get_default_bool(obs_data_t *data, const char *name) + +--------------------- + +.. function:: void obs_data_set_default_obj(obs_data_t *data, const char *name, obs_data_t *obj) + obs_data_t *obs_data_get_default_obj(obs_data_t *data, const char *name) + + :return: An incremented reference to a data object. + +--------------------- + + +Autoselect Functions +-------------------- + +Autoselect values are optionally used to determine what values should be +used to ensure functionality if the currently set values are +inappropriate or invalid. + +.. function:: void obs_data_set_autoselect_string(obs_data_t *data, const char *name, const char *val) + const char *obs_data_get_autoselect_string(obs_data_t *data, const char *name) + +--------------------- + +.. function:: void obs_data_set_autoselect_int(obs_data_t *data, const char *name, long long val) + long long obs_data_get_autoselect_int(obs_data_t *data, const char *name) + +--------------------- + +.. function:: void obs_data_set_autoselect_double(obs_data_t *data, const char *name, double val) + double obs_data_get_autoselect_double(obs_data_t *data, const char *name) + +--------------------- + +.. function:: void obs_data_set_autoselect_bool(obs_data_t *data, const char *name, bool val) + bool obs_data_get_autoselect_bool(obs_data_t *data, const char *name) + +--------------------- + +.. function:: void obs_data_set_autoselect_obj(obs_data_t *data, const char *name, obs_data_t *obj) + obs_data_t *obs_data_get_autoselect_obj(obs_data_t *data, const char *name) + + :return: An incremented reference to a data object. + +--------------------- + + +Array Functions +--------------- + +.. function:: obs_data_array_t *obs_data_array_create() + + :return: A new reference to a data array object. + +--------------------- + +.. function:: void obs_data_array_addref(obs_data_array_t *array) + +--------------------- + +.. function:: void obs_data_array_release(obs_data_array_t *array) + +--------------------- + +.. function:: size_t obs_data_array_count(obs_data_array_t *array) + +--------------------- + +.. function:: obs_data_t *obs_data_array_item(obs_data_array_t *array, size_t idx) + + :return: An incremented reference to the data object associated with + this array entry. + +--------------------- + +.. function:: size_t obs_data_array_push_back(obs_data_array_t *array, obs_data_t *obj) + +--------------------- + +.. function:: void obs_data_array_insert(obs_data_array_t *array, size_t idx, obs_data_t *obj) + +--------------------- + +.. function:: void obs_data_array_erase(obs_data_array_t *array, size_t idx) diff --git a/docs/sphinx/reference-sources.rst b/docs/sphinx/reference-sources.rst new file mode 100644 index 0000000..2b7233b --- /dev/null +++ b/docs/sphinx/reference-sources.rst @@ -0,0 +1,1363 @@ +Source API Reference (obs_source_t) +=================================== + +Sources are used to render video and/or audio on stream. Things such as +capturing displays/games/audio, playing a video, showing an image, or +playing audio. Sources can also be used to implement audio and video +filters as well as transitions. The `libobs/obs-source.h`_ file is the +dedicated header for implementing sources. + +.. type:: obs_source_t + + A reference-counted video/audio input source. + +.. type:: obs_weak_source_t + + A weak reference to an video/audio input source. + +.. code:: cpp + + #include + + +Source Definition Structure (obs_source_info) +--------------------------------------------- + +.. type:: struct obs_source_info + + Source definition structure. + +.. member:: const char *obs_source_info.id + + Unique string identifier for the source (required). + +.. member:: enum obs_source_type obs_source_info.type + + Type of source. + + - **OBS_SOURCE_TYPE_INPUT** - Video/Audio Input + - **OBS_SOURCE_TYPE_FILTER** - Filter + - **OBS_SOURCE_TYPE_TRANSITION** - Transition + +.. member:: uint32_t obs_source_info.output_flags + + Source output capability flags (required). + + (Author's note: This should be renamed to "capability_flags") + + A bitwise OR combination of one or more of the following values: + + - **OBS_SOURCE_VIDEO** - Source has video + + Unless SOURCE_ASYNC_VIDEO is specified, the source must include the + :c:member:`obs_source_info.video_render` callback in the source + definition structure. + + - **OBS_SOURCE_AUDIO** - Source has audio + + Use the :c:func:`obs_source_output_audio()` function to pass raw + audio data, which will be automatically converted and uploaded. If + used with OBS_SOURCE_ASYNC_VIDEO, audio will automatically be + synced up to the video output based upon their mutual timestamps. + + - **OBS_SOURCE_ASYNC** - Video is asynchronous (use + OBS_SOURCE_ASYNC_VIDEO instead to automatically combine this flag + with the OBS_SOURCE_VIDEO flag). + + - **OBS_SOURCE_ASYNC_VIDEO** - Source passes raw video data via RAM + + Use the :c:func:`obs_source_output_video()` function to pass raw + video data, which will be automatically drawn at a timing relative + to the provided timestamp. + + If audio is also present on the source, the audio will + automatically be synced to the video based upon their mutual + timestamps. + + - **OBS_SOURCE_CUSTOM_DRAW** - Source uses custom graphics calls, + rather than just rendering a single texture. + + This capability flag must be used if the source does not use + :c:func:`obs_source_draw()` to render a single texture. + + This capability flag is an important hint to turn off a specific + optimization that allows the first effect filter in the filter + chain to render the source directly with that effect filter. The + optimization does not work if there are custom graphics calls, and + the source must be rendered to a texture first before being sent to + the first filter in the filter chain. + + (Author's note: Ironically, not many sources render with that + optimization. I should have made it so that the optimization isn't + used by default, and a flag should have been used to turn on the + optimization -- not turn it off). + + - **OBS_SOURCE_INTERACTION** - Source can be interacted with by the + user. + + When this is used, the source will receive interaction events if + theese callbacks are provided: + :c:member:`obs_source_info.mouse_click`, + :c:member:`obs_source_info.mouse_move`, + :c:member:`obs_source_info.mouse_wheel`, + :c:member:`obs_source_info.focus`, and + :c:member:`obs_source_info.key_click`. + + - **OBS_SOURCE_COMPOSITE** - Source composites child sources + + When used, specifies that the source composites one or more child + sources. Scenes and transitions are examples of sources that + contain and render child sources. + + Sources that render sub-sources must implement the audio_render + callback in order to perform custom audio mixing of child sources. + + This capability flag is always set for transitions. + + - **OBS_SOURCE_DO_NOT_DUPLICATE** - Source should not be fully + duplicated. + + When this is used, specifies that the source should not be fully + duplicated, and should prefer to duplicate via holding references + rather than full duplication. + + When functions such as :c:func:`obs_source_duplicate()` or + :c:func:`obs_scene_duplicate()` are called, sources or child + sources with this flag will never be fully duplicated, and will + instead only be referenced. + + An example of the type of sources that should not be fully + duplicated are video devices, browsers, and video/audio captures, + as they will either not function correctly or will cause + performance or resource issues when duplicated. + + - **OBS_SOURCE_DEPRECATED** - Source is deprecated and should not be + used. + + - **OBS_SOURCE_DO_NOT_SELF_MONITOR** - Audio of this source should + not allow monitoring if the current monitoring device is the same + device being captured by the source. + + This flag is used as a hint to the back-end to prevent the source + from creating an audio feedback loop. This is primarily only used + with desktop audio capture sources. + +.. member:: const char *(*obs_source_info.get_name)(void *type_data) + + Get the translated name of the source type. + + :param type_data: The type_data variable of this structure + :return: The translated name of the source type + +.. member:: void *(*obs_source_info.create)(obs_data_t *settings, obs_source_t *source) + + Creates the implementation data for the source. + + :param settings: Settings to initialize the source with + :param source: Source that this data is associated with + :return: The implementation data associated with this source + +.. member:: void (*obs_source_info.destroy)(void *data) + + Destroys the implementation data for the source. + + Async sources must not call obs_source_output_video after returning + from destroy. + +.. member:: uint32_t (*obs_source_info.get_width)(void *data) + uint32_t (*obs_source_info.get_height)(void *data); + + Returns the width/height of the source. These callbacks are required + if this is a video source and is synchronous. + + (Author's note: These should really be consolidated in to one + function, not two) + + :return: The width/height of the video + +.. member:: void (*obs_source_info.get_defaults)(obs_data_t *settings) + void (*obs_source_info.get_defaults2)(void *type_data, obs_data_t *settings) + + Sets the default settings for this source. + + :param settings: Default settings. Call obs_data_set_default* + functions on this object to set default setting + values + +.. member:: obs_properties_t *(*obs_source_info.get_properties)(void *data) + obs_properties_t *(*obs_source_info.get_properties2)(void *data, void *type_data) + + Gets the property information of this source. + + (Optional) + + :return: The properties of the source + +.. member:: void (*obs_source_info.update)(void *data, obs_data_t *settings) + + Updates the settings for this source. + + (Optional) + + :param settings: New settings for this source + +.. member:: void (*obs_source_info.activate)(void *data) + + Called when the source has been activated in the main view (visible + on stream/recording). + + (Optional) + +.. member:: void (*obs_source_info.deactivate)(void *data) + + Called when the source has been deactivated from the main view (no + longer visible on stream/recording). + + (Optional) + +.. member:: void (*obs_source_info.show)(void *data) + + Called when the source is visible on any display and/or on the main + view. + + (Optional) + +.. member:: void (*obs_source_info.hide)(void *data) + + Called when the source is no longer visible on any display and/or on + the main view. + + (Optional) + +.. member:: void (*obs_source_info.video_tick)(void *data, float seconds) + + Called each video frame with the time elapsed. + + (Optional) + + :param seconds: Seconds elapsed since the last frame + +.. member:: void (*obs_source_info.video_render)(void *data, gs_effect_t *effect) + + Called when rendering the source with the graphics subsystem. + + If this is an input/transition source, this is called to draw the + source texture with the graphics subsystem. + + If this is a filter source, it wraps source draw calls (for example + applying a custom effect with custom parameters to a source). In + this case, it's highly recommended to use the + :c:func:`obs_source_process_filter_begin()` and + :c:func:`obs_source_process_filter_end()` functions to automatically + handle effect-based filter processing. However, you can implement + custom draw handling as desired as well. + + If the source output capability flags do not include + OBS_SOURCE_CUSTOM_DRAW, the source must use + :c:func:`obs_source_draw()` to render the source's texture. + + :param effect: This parameter is no longer used. Instead, call + :c:func:`obs_source_draw()` + +.. member:: struct obs_source_frame *(*obs_source_info.filter_video)(void *data, struct obs_source_frame *frame) + + Called to filter raw async video data. This function is only used + with asynchronous video filters. + + :param frame: Video frame to filter + :return: New video frame data. This can defer video data to + be drawn later if time is needed for processing + +.. member:: struct obs_audio_data *(*obs_source_info.filter_audio)(void *data, struct obs_audio_data *audio) + + Called to filter raw audio data. This function is only used with + audio filters. + + :param audio: Audio data to filter + :return: Modified or new audio data. You can directly modify + the data passed and return it, or you can defer audio + data for later if time is needed for processing. If + you are returning new data, that data must exist until + the next call to the + :c:member:`obs_source_info.filter_audio` callback or + until the filter is removed/destroyed + +.. member:: void (*obs_source_info.enum_active_sources)(void *data, obs_source_enum_proc_t enum_callback, void *param) + + Called to enumerate all active sources being used within this + source. If the source has children that render audio/video it must + implement this callback. Only used with sources that have tha + OBS_SOURCE_COMPOSITE output capability flag. + + :param enum_callback: Enumeration callback + :param param: User data to pass to callback + +.. member:: void (*obs_source_info.save)(void *data, obs_data_t *settings) + + Called when saving custom data for a source. This is a separate + function because sometimes a source needs to know when it is being + saved so it doesn't always have to update the current settings until + a certain point. + + (Optional) + + :param settings: Settings object to save data to + +.. member:: void (*obs_source_info.load)(void *data, obs_data_t *settings) + + Called when loading custom data from saved source data. This is + called after all the loading sources have actually been created, + allowing the ability to reference other sources if desired. + + (Optional) + + :param settings: Settings object to load data from + +.. member:: void (*obs_source_info.mouse_click)(void *data, const struct obs_mouse_event *event, int32_t type, bool mouse_up, uint32_t click_count) + + Called when interacting with a source and a mouse-down or mouse-up + occurs. Only used with sources that have the OBS_SOURCE_INTERACTION + output capability flag. + + (Optional) + + :param event: Mouse event properties + :param type: Mouse button pushed + :param mouse_up: Mouse event type (true if mouse-up) + :param click_count: Mouse click count (1 for single click, etc.) + +.. member:: void (*obs_source_info.mouse_move)(void *data, const struct obs_mouse_event *event, bool mouse_leave) + + Called when interacting with a source and a mouse-move occurs. Only + used with sources that have the OBS_SOURCE_INTERACTION output + capability flag. + + (Optional) + + :param event: Mouse event properties + :param mouse_leave: Mouse leave state (true if mouse left source) + +.. member:: void (*obs_source_info.mouse_wheel)(void *data, const struct obs_mouse_event *event, int x_delta, int y_delta) + + Called when interacting with a source and a mouse-wheel occurs. Only + used with sources that have the OBS_SOURCE_INTERACTION output + capability flag. + + (Optional) + + :param event: Mouse event properties + :param x_delta: Movement delta in the horizontal direction + :param y_delta: Movement delta in the vertical direction + + +.. member:: void (*obs_source_info.focus)(void *data, bool focus) + + Called when interacting with a source and gain focus/lost focus event + occurs. Only used with sources that have the OBS_SOURCE_INTERACTION + output capability flag. + + (Optional) + + :param focus: Focus state (true if focus gained) + +.. member:: void (*obs_source_info.key_click)(void *data, const struct obs_key_event *event, bool key_up) + + Called when interacting with a source and a key-up or key-down + occurs. Only used with sources that have the OBS_SOURCE_INTERACTION + output capability flag. + + (Optional) + + :param event: Key event properties + :param focus: Key event type (true if mouse-up) + +.. member:: void (*obs_source_info.filter_remove)(void *data, obs_source_t *source) + + Called when the filter is removed from a source. + + (Optional) + + :param data: Filter data + :param source: Source that the filter being removed from + +.. member:: void *obs_source_info.type_data + void (*obs_source_info.free_type_data)(void *type_data) + + Private data associated with this entry. Note that this is not the + same as the implementation data; this is used to differentiate + between two different types if the same callbacks are used for more + than one different type. + +.. member:: bool (*obs_source_info.audio_render)(void *data, uint64_t *ts_out, struct obs_source_audio_mix *audio_output, uint32_t mixers, size_t channels, size_t sample_rate) + + Called to render audio of composite sources. Only used with sources + that have tha OBS_SOURCE_COMPOSITE output capability flag. + +.. member:: void (*obs_source_info.enum_all_sources)(void *data, obs_source_enum_proc_t enum_callback, void *param) + + Called to enumerate all active and inactive sources being used + within this source. If this callback isn't implemented, + enum_active_sources will be called instead. Only used with sources + that have tha OBS_SOURCE_COMPOSITE output capability flag. + + This is typically used if a source can have inactive child sources. + + :param enum_callback: Enumeration callback + :param param: User data to pass to callback + +.. member:: void (*obs_source_info.transition_start)(void *data) + void (*obs_source_info.transition_stop)(void *data) + + Called on transition sources when the transition starts/stops. + + (Optional) + + +.. _source_signal_handler_reference: + +Source Signals +-------------- + +**destroy** (ptr *source*) + + This signal is called when the source is about to be destroyed. Do + not increment any references when using this signal. + +**remove** (ptr source) + + Called when the :c:func:`obs_source_remove()` function is called on + the source. + +**save** (ptr source) + + Called when the source is being saved. + +**load** (ptr source) + + Called when the source is being loaded. + +**activate** (ptr source) + + Called when the source has been activated in the main view (visible + on stream/recording). + +**deactivate** (ptr source) + + Called when the source has been deactivated from the main view (no + longer visible on stream/recording). + +**show** (ptr source) + + Called when the source is visible on any display and/or on the main + view. + +**hide** (ptr source) + + Called when the source is no longer visible on any display and/or on + the main view. + +**mute** (ptr source, bool muted) + + Called when the source is muted/unmuted. + +**push_to_mute_changed** (ptr source, bool enabled) + + Called when push-to-mute has been enabled/disabled. + +**push_to_mute_delay** (ptr source, int delay) + + Called when the push-to-mute delay value has changed. + +**push_to_talk_changed** (ptr source, bool enabled) + + Called when push-to-talk has been enabled/disabled. + +**push_to_talk_delay** (ptr source, int delay) + + Called when the push-to-talk delay value has changed. + +**enable** (ptr source, bool enabled) + + Called when the source has been disabled/enabled. + +**rename** (ptr source, string new_name, string prev_name) + + Called when the source has been renamed. + +**volume** (ptr source, in out float volume) + + Called when the volume of the source has changed. + +**update_properties** (ptr source) + + Called when the properties of the source have been updated. + +**update_flags** (ptr source, int flags) + + Called when the flags of the source have been changed. + +**audio_sync** (ptr source, int out int offset) + + Called when the audio sync offset has changed. + +**audio_mixers** (ptr source, in out int mixers) + + Called when the audio mixers have changed. + +**filter_add** (ptr source, ptr filter) + + Called when a filter has been added to the source. + +**filter_remove** (ptr source, ptr filter) + + Called when a filter has been removed from the source. + +**reorder_filters** (ptr source) + + Called when filters have been reordered. + +**transition_start** (ptr source) + + Called when a transition is starting. + +**transition_video_stop** (ptr source) + + Called when a transition's video transitioning has stopped. + +**transition_stop** (ptr source) + + Called when a transition has stopped. + + +General Source Functions +------------------------ + +.. function:: void obs_register_source(struct obs_source_info *info) + + Registers a source type. Typically used in + :c:func:`obs_module_load()` or in the program's initialization phase. + +--------------------- + +.. function:: const char *obs_source_get_display_name(const char *id) + + Calls the :c:member:`obs_source_info.get_name` callback to get the + translated display name of a source type. + + :param id: The source type string identifier + :return: The translated display name of a source type + +--------------------- + +.. function:: obs_source_t *obs_source_create(const char *id, const char *name, obs_data_t *settings, obs_data_t *hotkey_data) + + Creates a source of the specified type with the specified settings. + + The "source" context is used for anything related to presenting + or modifying video/audio. Use obs_source_release to release it. + + :param id: The source type string identifier + :param name: The desired name of the source. If this is + not unique, it will be made to be unique + :param settings: The settings for the source, or *NULL* if + none + :param hotkey_data: Saved hotkey data for the source, or *NULL* + if none + :return: A reference to the newly created source, or + *NULL* if failed + +--------------------- + +.. function:: obs_source_t *obs_source_create_private(const char *id, const char *name, obs_data_t *settings) + + Creates a 'private' source which is not enumerated by + :c:func:`obs_enum_sources()`, and is not saved by + :c:func:`obs_save_sources()`. + + Author's Note: The existence of this function is a result of design + flaw: the front-end should control saving/loading of sources, and + functions like :c:func:`obs_enum_sources()` and + :c:func:`obs_save_sources()` should not exist in the back-end. + + :param id: The source type string identifier + :param name: The desired name of the source. For private + sources, this does not have to be unique, + and can additionally be *NULL* if desired + :param settings: The settings for the source, or *NULL* if + none + :return: A reference to the newly created source, or + *NULL* if failed + +--------------------- + +.. function:: obs_source_t *obs_source_duplicate(obs_source_t *source, const char *desired_name, bool create_private) + + Duplicates a source. If the source has the + OBS_SOURCE_DO_NOT_DUPLICATE output flag set, this only returns a + new reference to the same source. + + :param source: The source to duplicate + :param desired_name: The desired name of the new source. If this is + not a private source and the name is not unique, + it will be made to be unique + :param create_private: If *true*, the new source will be a private + source if fully duplicated + :return: A new source reference + +--------------------- + +.. function:: void obs_source_addref(obs_source_t *source) + void obs_source_release(obs_source_t *source) + + Adds/releases a reference to a source. When the last reference is + released, the source is destroyed. + +--------------------- + +.. function:: obs_weak_source_t *obs_source_get_weak_source(obs_source_t *source) + obs_source_t *obs_weak_source_get_source(obs_weak_source_t *weak) + + These functions are used to get a weak reference from a strong source + reference, or a strong source reference from a weak reference. If + the source is destroyed, *obs_weak_source_get_source* will return + *NULL*. + +--------------------- + +.. function:: void obs_weak_source_addref(obs_weak_source_t *weak) + void obs_weak_source_release(obs_weak_source_t *weak) + + Adds/releases a weak reference to a source. + +--------------------- + +.. function:: void obs_source_remove(obs_source_t *source) + + Notifies all reference holders of the source (via + :c:func:`obs_source_removed()`) that the source should be released. + +--------------------- + +.. function:: bool obs_source_removed(const obs_source_t *source) + + :return: *true* if the source should be released + +--------------------- + +.. function:: uint32_t obs_source_get_output_flags(const obs_source_t *source) + uint32_t obs_get_source_output_flags(const char *id) + + :return: Capability flags of a source + + Author's Note: "Output flags" is poor wording in retrospect; this + should have been named "Capability flags", and the OBS_SOURCE_* + macros should really be OBS_SOURCE_CAP_* macros instead. + + See :c:member:`obs_source_info.output_flags` for more information. + +--------------------- + +.. function:: obs_data_t *obs_get_source_defaults(const char *id) + + Calls :c:member:`obs_source_info.get_defaults` to get the defaults + settings of the source type. + + :return: The default settings for a source type + +--------------------- + +.. function:: obs_properties_t *obs_source_properties(const obs_source_t *source) + obs_properties_t *obs_get_source_properties(const char *id) + + Use these functions to get the properties of a source or source type. + Properties are optionally used (if desired) to automatically generate + user interface widgets to allow users to update settings. + + :return: The properties list for a specific existing source. Free with + :c:func:`obs_properties_destroy()` + +--------------------- + +.. function:: bool obs_source_configurable(const obs_source_t *source) + bool obs_is_source_configurable(const char *id) + + :return: *true* if the the source has custom properties, *false* + otherwise + +--------------------- + +.. function:: void obs_source_update(obs_source_t *source, obs_data_t *settings) + + Updates the settings for a source and calls the + :c:member:`obs_source_info.update` callback of the source. If the + source is a video source, the :c:member:`obs_source_info.update` will + be not be called immediately; instead, it will be deferred to the + video thread to prevent threading issues. + +--------------------- + +.. function:: void obs_source_video_render(obs_source_t *source) + + Renders a video source. This will call the + :c:member:`obs_source_info.video_render` callback of the source. + +--------------------- + +.. function:: uint32_t obs_source_get_width(obs_source_t *source) + uint32_t obs_source_get_height(obs_source_t *source) + + Calls the :c:member:`obs_source_info.get_width` or + :c:member:`obs_source_info.get_height` of the source to get its width + and/or height. + + Author's Note: These functions should be consolidated in to a single + function/callback rather than having a function for both width and + height. + + :return: The width or height of the source + +--------------------- + +.. function:: obs_data_t *obs_source_get_settings(const obs_source_t *source) + + :return: The settings string for a source. The reference counter of the + returned settings data is incremented, so + :c:func:`obs_data_release()` must be called when the + settings are no longer used + +--------------------- + +.. function:: const char *obs_source_get_name(const obs_source_t *source) + + :return: The name of the source + +--------------------- + +.. function:: void obs_source_set_name(obs_source_t *source, const char *name) + + Sets the name of a source. If the source is not private and the name + is not unique, it will automatically be given a unique name. + +--------------------- + +.. function:: enum obs_source_type obs_source_get_type(const obs_source_t *source) + + :return: | OBS_SOURCE_TYPE_INPUT for inputs + | OBS_SOURCE_TYPE_FILTER for filters + | OBS_SOURCE_TYPE_TRANSITION for transitions + | OBS_SOURCE_TYPE_SCENE for scenes + +--------------------- + +.. function:: const char *obs_source_get_id(const obs_source_t *source) + + :return: The source's type identifier string + +--------------------- + +.. function:: signal_handler_t *obs_source_get_signal_handler(const obs_source_t *source) + + :return: The source's signal handler + + See the :ref:`source_signal_handler_reference` for more information + on signals that are available for sources. + +--------------------- + +.. function:: proc_handler_t *obs_source_get_proc_handler(const obs_source_t *source) + + :return: The procedure handler for a source + +--------------------- + +.. function:: void obs_source_set_volume(obs_source_t *source, float volume) + float obs_source_get_volume(const obs_source_t *source) + + Sets/gets the user volume for a source that has audio output. + +--------------------- + +.. function:: bool obs_source_muted(const obs_source_t *source) + void obs_source_set_muted(obs_source_t *source, bool muted) + + Sets/gets whether the source's audio is muted. + +--------------------- + +.. function:: bool obs_source_push_to_mute_enabled(const obs_source_t *source) + void obs_source_enable_push_to_mute(obs_source_t *source, bool enabled) + + Sets/gets whether push-to-mute is enabled. + +--------------------- + +.. function:: uint64_t obs_source_get_push_to_mute_delay(const obs_source_t *source) + void obs_source_set_push_to_mute_delay(obs_source_t *source, uint64_t delay) + + Sets/gets the push-to-mute delay. + +--------------------- + +.. function:: bool obs_source_push_to_talk_enabled(const obs_source_t *source) + void obs_source_enable_push_to_talk(obs_source_t *source, bool enabled) + + Sets/gets whether push-to-talk is enabled. + +--------------------- + +.. function:: uint64_t obs_source_get_push_to_talk_delay(const obs_source_t *source) + void obs_source_set_push_to_talk_delay(obs_source_t *source, uint64_t delay) + + Sets/gets the push-to-talk delay. + +--------------------- + +.. function:: void obs_source_set_sync_offset(obs_source_t *source, int64_t offset) + int64_t obs_source_get_sync_offset(const obs_source_t *source) + + Sets/gets the audio sync offset (in nanoseconds) for a source. + +--------------------- + +.. function:: void obs_source_enum_active_sources(obs_source_t *source, obs_source_enum_proc_t enum_callback, void *param) + void obs_source_enum_active_tree(obs_source_t *source, obs_source_enum_proc_t enum_callback, void *param) + + Enumerates active child sources or source tree used by this source. + + Relevant data types used with this function: + +.. code:: cpp + + typedef void (*obs_source_enum_proc_t)(obs_source_t *parent, + obs_source_t *child, void *param); + +--------------------- + +.. function:: bool obs_source_active(const obs_source_t *source) + + :return: *true* if active, *false* if not. A source is only + consdiered active if it's being shown on the final mix + +--------------------- + +.. function:: bool obs_source_showing(const obs_source_t *source) + + :return: *true* if showing, *false* if not. A source is considered + showing if it's being displayed anywhere at all, whether on + a display context or on the final output + +--------------------- + +.. function:: void obs_source_inc_showing(obs_source_t *source) + void obs_source_dec_showing(obs_source_t *source) + + Increments/decrements a source's "showing" state. Typically used + when drawing a source on a display manually. + +--------------------- + +.. function:: void obs_source_set_flags(obs_source_t *source, uint32_t flags) + uint32_t obs_source_get_flags(const obs_source_t *source) + + :param flags: OBS_SOURCE_FLAG_FORCE_MONO Forces audio to mono + +--------------------- + +.. function:: void obs_source_set_audio_mixers(obs_source_t *source, uint32_t mixers) + uint32_t obs_source_get_audio_mixers(const obs_source_t *source) + + Sets/gets the audio mixer channels that a source outputs to + (depending on what bits are set). Audio mixers allow filtering + specific using multiple audio encoders to mix different sources + together depending on what mixer channel they're set to. + + For example, to output to mixer 1 and 3, you would perform a bitwise + OR on bits 0 and 2: (1<<0) | (1<<2), or 0x5. + +--------------------- + +.. function:: void obs_source_enum_filters(obs_source_t *source, obs_source_enum_proc_t callback, void *param) + + Enumerates active filters on a source. + + Relevant data types used with this function: + +.. code:: cpp + + typedef void (*obs_source_enum_proc_t)(obs_source_t *parent, + obs_source_t *child, void *param); + +--------------------- + +.. function:: obs_source_t *obs_source_get_filter_by_name(obs_source_t *source, const char *name) + + :return: The desired filter, or *NULL* if not found. The reference + of the filter is incremented + +--------------------- + +.. function:: void obs_source_copy_filters(obs_source_t *dst, obs_source_t *src) + + Copies filters from the source to the destination. If filters by the + same name already exist in the destination source, the newer filters + will be given unique names. + +--------------------- + +.. function:: bool obs_source_enabled(const obs_source_t *source) + void obs_source_set_enabled(obs_source_t *source, bool enabled) + + Enables/disables a source, or returns the enabled state. + +--------------------- + +.. function:: void obs_source_add_audio_capture_callback(obs_source_t *source, obs_source_audio_capture_t callback, void *param) + void obs_source_remove_audio_capture_callback(obs_source_t *source, obs_source_audio_capture_t callback, void *param) + + Adds/removes an audio capture callback for a source. This allows the + ability to get the raw audio data of a source as it comes in. + + Relevant data types used with this function: + +.. code:: cpp + + typedef void (*obs_source_audio_capture_t)(void *param, obs_source_t *source, + const struct audio_data *audio_data, bool muted); + +--------------------- + +.. function:: void obs_source_set_deinterlace_mode(obs_source_t *source, enum obs_deinterlace_mode mode) + enum obs_deinterlace_mode obs_source_get_deinterlace_mode(const obs_source_t *source) + + Sets/gets the deinterlace mode. + + :param mode: | OBS_DEINTERLACE_MODE_DISABLE - Disables deinterlacing + | OBS_DEINTERLACE_MODE_DISCARD - Discard + | OBS_DEINTERLACE_MODE_RETRO - Retro + | OBS_DEINTERLACE_MODE_BLEND - Blend + | OBS_DEINTERLACE_MODE_BLEND_2X - Blend 2x + | OBS_DEINTERLACE_MODE_LINEAR - Linear + | OBS_DEINTERLACE_MODE_LINEAR_2X - Linear 2x + | OBS_DEINTERLACE_MODE_YADIF - Yadif + | OBS_DEINTERLACE_MODE_YADIF_2X - Yadif 2x + + +--------------------- + +.. function:: void obs_source_set_deinterlace_field_order(obs_source_t *source, enum obs_deinterlace_field_order order) + enum obs_deinterlace_field_order obs_source_get_deinterlace_field_order(const obs_source_t *source) + + Sets/gets the deinterlace field order. + + :param order: | OBS_DEINTERLACE_FIELD_ORDER_TOP - Start from top + | OBS_DEINTERLACE_FIELD_ORDER_BOTTOM - Start from bottom + +--------------------- + +.. function:: obs_data_t *obs_source_get_private_settings(obs_source_t *item) + + Gets private front-end settings data. This data is saved/loaded + automatically. Returns an incremented reference. + +--------------------- + +.. function:: void obs_source_send_mouse_click(obs_source_t *source, const struct obs_mouse_event *event, int32_t type, bool mouse_up, uint32_t click_count) + + Used for interacting with sources: sends a mouse down/up event to a + source. + +--------------------- + +.. function:: void obs_source_send_mouse_move(obs_source_t *source, const struct obs_mouse_event *event, bool mouse_leave) + + Used for interacting with sources: sends a mouse move event to a + source. + +--------------------- + +.. function:: void obs_source_send_mouse_wheel(obs_source_t *source, const struct obs_mouse_event *event, int x_delta, int y_delta) + + Used for interacting with sources: sends a mouse wheel event to a + source. + +--------------------- + +.. function:: void obs_source_send_focus(obs_source_t *source, bool focus) + + Used for interacting with sources: sends a got-focus or lost-focus + event to a source. + +--------------------- + +.. function:: void obs_source_send_key_click(obs_source_t *source, const struct obs_key_event *event, bool key_up) + + Used for interacting with sources: sends a key up/down event to a + source. + +--------------------- + + +Functions used by sources +------------------------- + +.. function:: void obs_source_draw_set_color_matrix(const struct matrix4 *color_matrix, const struct vec3 *color_range_min, const struct vec3 *color_range_max) + + Helper function to set the color matrix information when drawing the + source. + + :param color_matrix: The color matrix. Assigns to the 'color_matrix' + effect variable. + :param color_range_min: The minimum color range. Assigns to the + 'color_range_min' effect variable. If NULL, + {0.0f, 0.0f, 0.0f} is used. + :param color_range_max: The maximum color range. Assigns to the + 'color_range_max' effect variable. If NULL, + {1.0f, 1.0f, 1.0f} is used. + +--------------------- + +.. function:: void obs_source_draw(gs_texture_t *image, int x, int y, uint32_t cx, uint32_t cy, bool flip) + + Helper function to draw sprites for a source (synchronous video). + + :param image: The sprite texture to draw. Assigns to the 'image' variable + of the current effect. + :param x: X position of the sprite. + :param y: Y position of the sprite. + :param cx: Width of the sprite. If 0, uses the texture width. + :param cy: Height of the sprite. If 0, uses the texture height. + :param flip: Specifies whether to flip the image vertically. + +--------------------- + +.. function:: void obs_source_output_video(obs_source_t *source, const struct obs_source_frame *frame) + + Outputs asynchronous video data. Set to NULL to deactivate the texture. + + Relevant data types used with this function: + +.. code:: cpp + + enum video_format { + VIDEO_FORMAT_NONE, + + /* planar 420 format */ + VIDEO_FORMAT_I420, /* three-plane */ + VIDEO_FORMAT_NV12, /* two-plane, luma and packed chroma */ + + /* packed 422 formats */ + VIDEO_FORMAT_YVYU, + VIDEO_FORMAT_YUY2, /* YUYV */ + VIDEO_FORMAT_UYVY, + + /* packed uncompressed formats */ + VIDEO_FORMAT_RGBA, + VIDEO_FORMAT_BGRA, + VIDEO_FORMAT_BGRX, + VIDEO_FORMAT_Y800, /* grayscale */ + + /* planar 4:4:4 */ + VIDEO_FORMAT_I444, + }; + + struct obs_source_frame { + uint8_t *data[MAX_AV_PLANES]; + uint32_t linesize[MAX_AV_PLANES]; + uint32_t width; + uint32_t height; + uint64_t timestamp; + + enum video_format format; + float color_matrix[16]; + bool full_range; + float color_range_min[3]; + float color_range_max[3]; + bool flip; + }; + +--------------------- + +.. function:: void obs_source_preload_video(obs_source_t *source, const struct obs_source_frame *frame) + + Preloads a video frame to ensure a frame is ready for playback as + soon as video playback starts. + +--------------------- + +.. function:: void obs_source_show_preloaded_video(obs_source_t *source) + + Shows any preloaded video frame. + +--------------------- + +.. function:: void obs_source_output_audio(obs_source_t *source, const struct obs_source_audio *audio) + + Outputs audio data. + +--------------------- + +.. function:: void obs_source_update_properties(obs_source_t *source) + + Signal an update to any currently used properties. + +--------------------- + +.. function:: bool obs_source_add_active_child(obs_source_t *parent, obs_source_t *child) + + Adds an active child source. Must be called by parent sources on child + sources when the child is added and active. This ensures that the source is + properly activated if the parent is active. + + :return: *true* if source can be added, *false* if it causes recursion + +--------------------- + +.. function:: void obs_source_remove_active_child(obs_source_t *parent, obs_source_t *child) + + Removes an active child source. Must be called by parent sources on child + sources when the child is removed or inactive. This ensures that the source + is properly deactivated if the parent is no longer active. + +--------------------- + + +Filters +------- + +.. function:: obs_source_t *obs_filter_get_parent(const obs_source_t *filter) + + If the source is a filter, returns the parent source of the filter. + The parent source is the source being filtered. + + Only guaranteed to be valid inside of the video_render, filter_audio, + filter_video, and filter_remove callbacks. + +--------------------- + +.. function:: obs_source_t *obs_filter_get_target(const obs_source_t *filter) + + If the source is a filter, returns the target source of the filter. + The target source is the next source in the filter chain. + + Only guaranteed to be valid inside of the video_render, filter_audio, + filter_video, and filter_remove callbacks. + +--------------------- + +.. function:: void obs_source_default_render(obs_source_t *source) + + Can be used by filters to directly render a non-async parent source + without any filter processing. + +--------------------- + +.. function:: void obs_source_filter_add(obs_source_t *source, obs_source_t *filter) + void obs_source_filter_remove(obs_source_t *source, obs_source_t *filter) + + Adds/removes a filter to/from a source. + +--------------------- + +.. function:: void obs_source_filter_set_order(obs_source_t *source, obs_source_t *filter, enum obs_order_movement movement) + + Modifies the order of a specific filter. + + :param movement: | Can be one of the following: + | OBS_ORDER_MOVE_UP + | OBS_ORDER_MOVE_DOWN + | OBS_ORDER_MOVE_TOP + | OBS_ORDER_MOVE_BOTTOM + +--------------------- + + +Functions used by filters +------------------------- + +.. function:: bool obs_source_process_filter_begin(obs_source_t *filter, enum gs_color_format format, enum obs_allow_direct_render allow_direct) + + Default RGB filter handler for generic effect filters. Processes the + filter chain and renders them to texture if needed, then the filter is + drawn with. + + After calling this, set your parameters for the effect, then call + obs_source_process_filter_end to draw the filter. + + :return: *true* if filtering should continue, *false* if the filter + is bypassed for whatever reason + +--------------------- + +.. function:: void obs_source_process_filter_end(obs_source_t *filter, gs_effect_t *effect, uint32_t width, uint32_t height) + + Draws the filter using the effect's "Draw" technique. + + Before calling this function, first call obs_source_process_filter_begin and + then set the effect parameters, and then call this function to finalize the + filter. + +--------------------- + +.. function:: void obs_source_process_filter_tech_end(obs_source_t *filter, gs_effect_t *effect, uint32_t width, uint32_t height, const char *tech_name) + + Draws the filter with a specific technique in the effect. + + Before calling this function, first call obs_source_process_filter_begin and + then set the effect parameters, and then call this function to finalize the + filter. + +--------------------- + +.. function:: void obs_source_skip_video_filter(obs_source_t *filter) + + Skips the filter if the filter is invalid and cannot be rendered. + +--------------------- + + +.. _transitions: + +Transitions +----------- + +.. function:: obs_source_t *obs_transition_get_source(obs_source_t *transition, enum obs_transition_target target) + + :param target: | OBS_TRANSITION_SOURCE_A - Source being transitioned from, or the current source if not transitioning + | OBS_TRANSITION_SOURCE_B - Source being transitioned to + :return: An incremented reference to the source or destination + sources of the transition + +--------------------- + +.. function:: void obs_transition_clear(obs_source_t *transition) + + Clears the transition. + +--------------------- + +.. function:: obs_source_t *obs_transition_get_active_source(obs_source_t *transition) + + :return: An incremented reference to the currently active source of + the transition + +--------------------- + +.. function:: bool obs_transition_start(obs_source_t *transition, enum obs_transition_mode mode, uint32_t duration_ms, obs_source_t *dest) + + Starts the transition with the desired destination source. + + :param mode: Currently only OBS_TRANSITION_MODE_AUTO + :param duration_ms: Duration in milliseconds. If the transition has + a fixed duration set by + :c:func:`obs_transition_enable_fixed`, this + parameter will have no effect + :param dest: The destination source to transition to + +--------------------- + +.. function:: void obs_transition_set_size(obs_source_t *transition, uint32_t cx, uint32_t cy) + void obs_transition_get_size(const obs_source_t *transition, uint32_t *cx, uint32_t *cy) + + Sets/gets the dimensions of the transition. + +--------------------- + +.. function:: void obs_transition_set_scale_type(obs_source_t *transition, enum obs_transition_scale_type type) + enum obs_transition_scale_type obs_transition_get_scale_type( const obs_source_t *transition) + + Sets/gets the scale type for sources within the transition. + + :param type: | OBS_TRANSITION_SCALE_MAX_ONLY - Scale to aspect ratio, but only to the maximum size of each source + | OBS_TRANSITION_SCALE_ASPECT - Alwasy scale the sources, but keep aspect ratio + | OBS_TRANSITION_SCALE_STRETCH - Scale and stretch the sources to the size of the transision + +--------------------- + +.. function:: void obs_transition_set_alignment(obs_source_t *transition, uint32_t alignment) + uint32_t obs_transition_get_alignment(const obs_source_t *transition) + + Sets/gets the alignment used to draw the two sources within + transition the transition. + + :param alignment: | Can be any bitwise OR combination of: + | OBS_ALIGN_CENTER + | OBS_ALIGN_LEFT + | OBS_ALIGN_RIGHT + | OBS_ALIGN_TOP + | OBS_ALIGN_BOTTOM + +--------------------- + + +Functions used by transitions +----------------------------- + +.. function:: void obs_transition_enable_fixed(obs_source_t *transition, bool enable, uint32_t duration_ms) + bool obs_transition_fixed(obs_source_t *transition) + + Sets/gets whether the transition uses a fixed duration. Useful for + certain types of transitions such as stingers. If this is set, the + *duration_ms* parameter of :c:func:`obs_transition_start()` has no + effect. + +--------------------- + +.. function:: float obs_transition_get_time(obs_source_t *transition) + + :return: The current transition time value (0.0f..1.0f) + +--------------------- + +.. function:: void obs_transition_video_render(obs_source_t *transition, obs_transition_video_render_callback_t callback) + + Helper function used for rendering transitions. This function will + render two distinct textures for source A and source B of the + transition, allowing the ability to blend them together with a pixel + shader in a desired manner. + + The *a* and *b* parameters of *callback* are automatically rendered + textures of source A and source B, *t* is the time value + (0.0f..1.0f), *cx* and *cy* are the current dimensions of the + transition, and *data* is the implementation's private data. + + Relevant data types used with this function: + +.. code:: cpp + + typedef void (*obs_transition_video_render_callback_t)(void *data, + gs_texture_t *a, gs_texture_t *b, float t, + uint32_t cx, uint32_t cy); + +--------------------- + +.. function:: bool obs_transition_audio_render(obs_source_t *transition, uint64_t *ts_out, struct obs_source_audio_mix *audio, uint32_t mixers, size_t channels, size_t sample_rate, obs_transition_audio_mix_callback_t mix_a_callback, obs_transition_audio_mix_callback_t mix_b_callback) + + Helper function used for transitioning audio. Typically you'd call + this in the obs_source_info.audio_render callback with its + parameters, and use the mix_a_callback and mix_b_callback to + determine the the audio fading of source A and source B. + + Relevant data types used with this function: + +.. code:: cpp + + typedef float (*obs_transition_audio_mix_callback_t)(void *data, float t); + +--------------------- + +.. function:: void obs_transition_swap_begin(obs_source_t *tr_dest, obs_source_t *tr_source) + void obs_transition_swap_end(obs_source_t *tr_dest, obs_source_t *tr_source) + + Swaps two transitions. Call obs_transition_swap_begin, swap the + source, then call obs_transition_swap_end when complete. This allows + the ability to seamlessly swap two different transitions without it + affecting the output. + + For example, if a transition is assigned to output channel 0, you'd + call obs_transition_swap_begin, then you'd call obs_set_output_source + with the new transition, then call + :c:func:`obs_transition_swap_begin()`. + +.. --------------------------------------------------------------------------- + +.. _libobs/obs-source.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-source.h diff --git a/docs/sphinx/scripting.rst b/docs/sphinx/scripting.rst new file mode 100644 index 0000000..f19f013 --- /dev/null +++ b/docs/sphinx/scripting.rst @@ -0,0 +1,320 @@ +Python/Lua Scripting +==================== + +Scripting (21.0+) adds support for Python 3 and Luajit 2 (which is +roughly equivalent to Lua 5.2), allowing the ability to quickly extend, +add features, or automate the program without needing to build a native +plugin module. + +Scripting can be accessed in OBS Studio via the Tools menu -> Scripts +option, which will bring up the scripting dialog. Scripts can be added, +removed, and reloaded in real time while the program is running. + +**NOTE:** On windows, currently only Python 3.6 is supported. To use +Python on windows, you must download and install Python 3.6.x "x86-64" +for OBS 64bit (64bit is the default), or Python 3.6.x "x86" if using OBS +32bit. Then, in the scripting dialog, you must set the path to the +Python 3.6.x install in the "Python Settings" tab. + +All API bindings are provided through the **obspython** module in +Python, and the **obslua** module in Lua. + +Certain functions have been changed/replaced in order to provide script +callbacks, see :ref:`other_script_differences` for more information. + +**WARNING:** Because bindings to the entire API are provided, it is +possible to leak memory or crash the program with an improperly-written +script. Please exercise caution when making scripts and check the +memory leak counter in your log file to make sure scripts you write +aren't leaking memory. **Please treat the API bindings as though you +were writing a C program: read the documentation for functions you use, +and release/destroy objects you reference or create via the API.** + + +Script Function Exports +----------------------- + +There are a number of global functions that scripts can optionally +provide: + +.. py:function:: script_load(settings) + + Called on script startup with specific settings associated with the + script. The *settings* parameter provided is not typically used for + settings that are set by the user; instead the parameter is used for + any extra internal settings data that may be used in the script. + + :param settings: Settings associated with the script. + +.. py:function:: script_unload() + + Called when the script is being unloaded. + +.. py:function:: script_save(settings) + + Called when the script is being saved. This is not necessary for + settings that are set by the user; instead this is used for any + extra internal settings data that may be used in the script. + + :param settings: Settings associated with the script. + +.. py:function:: script_defaults(settings) + + Called to set default settings (if any) associated with the script. + You would typically call :ref:`obs_data_default_funcs` for the + on the settings in order to set its default values. + + :param settings: Settings associated with the script. + +.. py:function:: script_update(settings) + + Called when the script's settings (if any) have been changed by the + user. + + :param settings: Settings associated with the script. + +.. py:function:: script_properties() + + Called to define user properties associated with the script. These + properties are used to define how to show settings properties to a + user. + + :return: obs_properties_t object created via + :c:func:`obs_properties_create()`. + +.. py:function:: script_tick(seconds) + + Called every frame in case per-frame processing is needed. If a + timer is needed, please use :ref:`scripting_timers` instead, as + timers are more efficient if all that's needed is basic timer + functionality. Using this function in Python is not recommended due + to the global interpreter lock of Python. + + :param seconds: Seconds passed since previous frame. + + +Getting the Current Script's Path +--------------------------------- + +There is a function you can use to get the current script's path. This +function is automatically implemented in to each script before the +script is loaded, and is part of the script's namespace, not +obslua/obspython: + +.. py:function:: script_path() + + :return: The path to the script. + + +.. _scripting_timers: + +Script Timers +------------- + +Script timers provide an efficient means of providing timer callbacks +without necessarily having to lock scripts/interpreters every frame. +(These functions are part of the obspython/obslua modules/namespaces). + +.. py:function:: timer_add(callback, milliseconds) + + Adds an timer callback which triggers every *millseconds*. + +.. py:function:: timer_remove(callback) + + Removes a timer callback. (Note: You can also use + :py:func:`remove_current_callback()` to terminate the timer from the + timer callback) + + +Script Sources (Lua Only) +------------------------- + +It is possible to register sources in Lua. To do so, create a table, +and define its keys the same way you would define an +:c:type:`obs_source_info` structure: + +.. code:: lua + + local info = {} + info.id = "my_source_id" + info.type = obslua.OBS_SOURCE_TYPE_INPUT + info.output_flags = obslua.OBS_SOURCE_VIDEO + + info.get_name = function() + return "My Source" + end + + info.create = function(source, settings) + -- typically source data would be stored as a table + local my_source_data = {} + + [...] + + return my_source_data + end + + info.video_render = function(my_source_data, effect) + [...] + end + + info.get_width = function(my_source_data) + [...] + + -- assuming the source data contains a 'width' key + return my_source_data.width + end + + info.get_height = function(my_source_data) + [...] + + -- assuming the source data contains a 'height' key + return my_source_data.height + end + + -- register the source + obs_register_source(info) + + +.. _other_script_differences: + +Other Differences From the C API +-------------------------------- + +Certain functions are implemented differently from the C API due to how +callbacks work. (These functions are part of the obspython/obslua +modules/namespaces). + +.. py:function:: obs_enum_sources() + + Enumerates all sources. + + :return: An array of reference-incremented sources. Release with + :py:func:`source_list_release()`. + +.. py:function:: obs_scene_enum_items(scene) + + Enumerates scene items within a scene. + + :param scene: obs_scene_t object to enumerate items from. + :return: List of scene items. Release with + :py:func:`sceneitem_list_release()`. + +.. py:function:: obs_add_main_render_callback(callback) + + **Lua only:** Adds a primary output render callback. This callback + has no parameters. + + :param callback: Render callback. Use + :py:func:`obs_remove_main_render_callback()` or + :py:func:`remove_current_callback()` to remove the + callback. + +.. py:function:: obs_remove_main_render_callback(callback) + + **Lua only:** Removes a primary output render callback. + + :param callback: Render callback. + +.. py:function:: signal_handler_connect(handler, signal, callback) + + Adds a callback to a specific signal on a signal handler. This + callback has one parameter: the calldata_t object. + + :param handler: A signal_handler_t object. + :param signal: The signal on the signal handler (string) + :param callback: The callback to connect to the signal. Use + :py:func:`signal_handler_disconnect()` or + :py:func:`remove_current_callback()` to remove the + callback. + +.. py:function:: signal_handler_disconnect(handler, signal, callback) + + Removes a callback from a specific signal of a signal handler. + + :param handler: A signal_handler_t object. + :param signal: The signal on the signal handler (string) + :param callback: The callback to disconnect from the signal. + +.. py:function:: signal_handler_connect_global(handler, callback) + + Adds a global callback to a signal handler. This callback has two + parameters: the first parameter is the signal string, and the second + parameter is the calldata_t object. + + :param handler: A signal_handler_t object. + :param callback: The callback to connect. Use + :py:func:`signal_handler_disconnect_global()` or + :py:func:`remove_current_callback()` to remove the + callback. + +.. py:function:: signal_handler_disconnect_global(handler, callback) + + Removes a global callback from a signal handler. + + :param handler: A signal_handler_t object. + :param callback: The callback to disconnect. + +.. py:function:: obs_hotkey_register_frontend(name, description, callback) + + Adds a frontend hotkey. The callback takes one parameter: a boolean + 'pressed' parameter. + + :param name: Unique name identifier string of the hotkey. + :param description: Hotkey description shown to the user. + :param callback: Callback for the hotkey. Use + :py:func:`obs_hotkey_unregister()` or + :py:func:`remove_current_callback()` to remove + the callback. + +.. py:function:: obs_hotkey_unregister(callback) + + Unregisters the hotkey associated with the specified callback. + + :param callback: Callback of the hotkey to unregister. + +.. py:function:: obs_properties_add_button(properties, setting_name, text, callback) + + Adds a button properties to an obs_properties_t object. The callback + takes no parameters. + + :param properties: An obs_properties_t object. + :param setting_name: A setting identifier string. + :param text: Button text. + :param callback: Button callback. This callback is automatically + cleaned up. + +.. py:function:: remove_current_callback() + + Removes the current callback being executed. Does nothing if not + within a callback. + +.. py:function:: source_list_release(source_list) + + Releases the references of a source list. + + :param source_list: Array of sources to release. + + +.. py:function:: sceneitem_list_release(item_list) + + Releases the references of a scene item list. + + :param item_list: Array of scene items to release. + +.. py:function:: calldata_source(calldata, name) + + Casts a pointer parameter of a calldata_t object to an obs_source_t + object. + + :param calldata: A calldata_t object. + :param name: Name of the parameter. + :return: A borrowed reference to an obs_source_t object. + +.. py:function:: calldata_sceneitem(calldata, name) + + Casts a pointer parameter of a calldata_t object to an + obs_sceneitem_t object. + + :param calldata: A calldata_t object. + :param name: Name of the parameter. + :return: A borrowed reference to an obs_sceneitem_t object. diff --git a/libobs-d3d11/d3d11-duplicator.cpp b/libobs-d3d11/d3d11-duplicator.cpp index 5970b4a..5bebb87 100644 --- a/libobs-d3d11/d3d11-duplicator.cpp +++ b/libobs-d3d11/d3d11-duplicator.cpp @@ -16,6 +16,7 @@ ******************************************************************************/ #include "d3d11-subsystem.hpp" +#include static inline bool get_monitor(gs_device_t *device, int monitor_idx, IDXGIOutput **dxgiOutput) @@ -55,7 +56,9 @@ void gs_duplicator::Start() gs_duplicator::gs_duplicator(gs_device_t *device_, int monitor_idx) : gs_obj (device_, gs_type::gs_duplicator), texture (nullptr), - idx (monitor_idx) + idx (monitor_idx), + refs (1), + updated (false) { Start(); } @@ -116,13 +119,30 @@ EXPORT bool device_get_duplicator_monitor_info(gs_device_t *device, return true; } +static std::map instances; + +void reset_duplicators(void) +{ + for (auto &pair : instances) { + pair.second->updated = false; + } +} + EXPORT gs_duplicator_t *device_duplicator_create(gs_device_t *device, int monitor_idx) { gs_duplicator *duplicator = nullptr; + auto it = instances.find(monitor_idx); + if (it != instances.end()) { + duplicator = it->second; + duplicator->refs++; + return duplicator; + } + try { duplicator = new gs_duplicator(device, monitor_idx); + instances[monitor_idx] = duplicator; } catch (const char *error) { blog(LOG_DEBUG, "device_duplicator_create: %s", @@ -140,7 +160,10 @@ EXPORT gs_duplicator_t *device_duplicator_create(gs_device_t *device, EXPORT void gs_duplicator_destroy(gs_duplicator_t *duplicator) { - delete duplicator; + if (--duplicator->refs == 0) { + instances.erase(duplicator->idx); + delete duplicator; + } } static inline void copy_texture(gs_duplicator_t *d, ID3D11Texture2D *tex) @@ -171,6 +194,13 @@ EXPORT bool gs_duplicator_update_frame(gs_duplicator_t *d) ComPtr res; HRESULT hr; + if (!d->duplicator) { + return false; + } + if (d->updated) { + return true; + } + hr = d->duplicator->AcquireNextFrame(0, &info, res.Assign()); if (hr == DXGI_ERROR_ACCESS_LOST) { return false; @@ -195,6 +225,7 @@ EXPORT bool gs_duplicator_update_frame(gs_duplicator_t *d) copy_texture(d, tex); d->duplicator->ReleaseFrame(); + d->updated = true; return true; } diff --git a/libobs-d3d11/d3d11-rebuild.cpp b/libobs-d3d11/d3d11-rebuild.cpp index c0b1c13..376b8ea 100644 --- a/libobs-d3d11/d3d11-rebuild.cpp +++ b/libobs-d3d11/d3d11-rebuild.cpp @@ -40,6 +40,7 @@ void gs_texture_2d::RebuildSharedTextureFallback() td.Format = DXGI_FORMAT_B8G8R8A8_UNORM; td.ArraySize = 1; td.SampleDesc.Count = 1; + td.BindFlags = D3D11_BIND_SHADER_RESOURCE; width = td.Width; height = td.Height; @@ -304,7 +305,11 @@ try { ((gs_pixel_shader*)obj)->Rebuild(dev); break; case gs_type::gs_duplicator: - ((gs_duplicator*)obj)->Start(); + try { + ((gs_duplicator*)obj)->Start(); + } catch (...) { + ((gs_duplicator*)obj)->Release(); + } break; case gs_type::gs_swap_chain: ((gs_swap_chain*)obj)->Rebuild(dev); diff --git a/libobs-d3d11/d3d11-subsystem.cpp b/libobs-d3d11/d3d11-subsystem.cpp index 340b26d..41f5730 100644 --- a/libobs-d3d11/d3d11-subsystem.cpp +++ b/libobs-d3d11/d3d11-subsystem.cpp @@ -196,7 +196,9 @@ void gs_device::InitCompiler() ver--; } - throw "Could not find any D3DCompiler libraries"; + throw "Could not find any D3DCompiler libraries. Make sure you've " + "installed the " + "DirectX components that OBS Studio requires."; } void gs_device::InitFactory(uint32_t adapterIdx) @@ -339,7 +341,10 @@ ID3D11BlendState *gs_device::AddBlendState() bd.RenderTarget[i].DestBlendAlpha = ConvertGSBlendType(blendState.destFactorA); bd.RenderTarget[i].RenderTargetWriteMask = - D3D11_COLOR_WRITE_ENABLE_ALL; + (blendState.redEnabled ? D3D11_COLOR_WRITE_ENABLE_RED : 0) | + (blendState.greenEnabled ? D3D11_COLOR_WRITE_ENABLE_GREEN : 0) | + (blendState.blueEnabled ? D3D11_COLOR_WRITE_ENABLE_BLUE : 0) | + (blendState.alphaEnabled ? D3D11_COLOR_WRITE_ENABLE_ALPHA : 0) ; } SavedBlendState savedState(blendState, bd); @@ -1029,7 +1034,6 @@ void device_load_vertexshader(gs_device_t *device, gs_shader_t *vertshader) return; gs_vertex_shader *vs = static_cast(vertshader); - gs_vertex_buffer *curVB = device->curVertexBuffer; if (vertshader) { if (vertshader->type != GS_SHADER_VERTEX) { @@ -1452,9 +1456,12 @@ void device_present(gs_device_t *device) } } +extern "C" void reset_duplicators(void); + void device_flush(gs_device_t *device) { device->context->Flush(); + reset_duplicators(); } void device_set_cull_mode(gs_device_t *device, enum gs_cull_mode mode) @@ -1933,36 +1940,53 @@ void gs_vertexbuffer_destroy(gs_vertbuffer_t *vertbuffer) delete vertbuffer; } -void gs_vertexbuffer_flush(gs_vertbuffer_t *vertbuffer) +static inline void gs_vertexbuffer_flush_internal(gs_vertbuffer_t *vertbuffer, + const gs_vb_data *data) { + size_t num_tex = data->num_tex < vertbuffer->uvBuffers.size() + ? data->num_tex + : vertbuffer->uvBuffers.size(); + if (!vertbuffer->dynamic) { blog(LOG_ERROR, "gs_vertexbuffer_flush: vertex buffer is " "not dynamic"); return; } - vertbuffer->FlushBuffer(vertbuffer->vertexBuffer, - vertbuffer->vbd.data->points, sizeof(vec3)); + if (data->points) + vertbuffer->FlushBuffer(vertbuffer->vertexBuffer, + data->points, sizeof(vec3)); - if (vertbuffer->normalBuffer) + if (vertbuffer->normalBuffer && data->normals) vertbuffer->FlushBuffer(vertbuffer->normalBuffer, - vertbuffer->vbd.data->normals, sizeof(vec3)); + data->normals, sizeof(vec3)); - if (vertbuffer->tangentBuffer) + if (vertbuffer->tangentBuffer && data->tangents) vertbuffer->FlushBuffer(vertbuffer->tangentBuffer, - vertbuffer->vbd.data->tangents, sizeof(vec3)); + data->tangents, sizeof(vec3)); - if (vertbuffer->colorBuffer) + if (vertbuffer->colorBuffer && data->colors) vertbuffer->FlushBuffer(vertbuffer->colorBuffer, - vertbuffer->vbd.data->colors, sizeof(uint32_t)); + data->colors, sizeof(uint32_t)); - for (size_t i = 0; i < vertbuffer->uvBuffers.size(); i++) { - gs_tvertarray &tv = vertbuffer->vbd.data->tvarray[i]; + for (size_t i = 0; i < num_tex; i++) { + gs_tvertarray &tv = data->tvarray[i]; vertbuffer->FlushBuffer(vertbuffer->uvBuffers[i], tv.array, tv.width*sizeof(float)); } } +void gs_vertexbuffer_flush(gs_vertbuffer_t *vertbuffer) +{ + gs_vertexbuffer_flush_internal(vertbuffer, vertbuffer->vbd.data); +} + +void gs_vertexbuffer_flush_direct(gs_vertbuffer_t *vertbuffer, + const gs_vb_data *data) +{ + gs_vertexbuffer_flush_internal(vertbuffer, data); +} + struct gs_vb_data *gs_vertexbuffer_get_data(const gs_vertbuffer_t *vertbuffer) { return vertbuffer->vbd.data; @@ -1974,7 +1998,8 @@ void gs_indexbuffer_destroy(gs_indexbuffer_t *indexbuffer) delete indexbuffer; } -void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer) +static inline void gs_indexbuffer_flush_internal(gs_indexbuffer_t *indexbuffer, + const void *data) { HRESULT hr; @@ -1987,12 +2012,22 @@ void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer) if (FAILED(hr)) return; - memcpy(map.pData, indexbuffer->indices.data, - indexbuffer->num * indexbuffer->indexSize); + memcpy(map.pData, data, indexbuffer->num * indexbuffer->indexSize); indexbuffer->device->context->Unmap(indexbuffer->indexBuffer, 0); } +void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer) +{ + gs_indexbuffer_flush_internal(indexbuffer, indexbuffer->indices.data); +} + +void gs_indexbuffer_flush_direct(gs_indexbuffer_t *indexbuffer, + const void *data) +{ + gs_indexbuffer_flush_internal(indexbuffer, data); +} + void *gs_indexbuffer_get_data(const gs_indexbuffer_t *indexbuffer) { return indexbuffer->indices.data; diff --git a/libobs-d3d11/d3d11-subsystem.hpp b/libobs-d3d11/d3d11-subsystem.hpp index cf7ad6f..54070a1 100644 --- a/libobs-d3d11/d3d11-subsystem.hpp +++ b/libobs-d3d11/d3d11-subsystem.hpp @@ -561,6 +561,8 @@ struct gs_duplicator : gs_obj { ComPtr duplicator; gs_texture_2d *texture; int idx; + long refs; + bool updated; void Start(); diff --git a/libobs-opengl/gl-cocoa.m b/libobs-opengl/gl-cocoa.m index e4f83ff..0bcbca2 100644 --- a/libobs-opengl/gl-cocoa.m +++ b/libobs-opengl/gl-cocoa.m @@ -73,12 +73,14 @@ static NSOpenGLContext *gl_context_create(void) struct gl_platform *gl_platform_create(gs_device_t *device, uint32_t adapter) { struct gl_platform *plat = bzalloc(sizeof(struct gl_platform)); + GLint interval = 0; plat->context = gl_context_create(); if (!plat->context) goto fail; [plat->context makeCurrentContext]; + [plat->context setValues:&interval forParameter:NSOpenGLCPSwapInterval]; if (!gladLoadGL()) goto fail; diff --git a/libobs-opengl/gl-helpers.c b/libobs-opengl/gl-helpers.c index 73d1042..8a7a757 100644 --- a/libobs-opengl/gl-helpers.c +++ b/libobs-opengl/gl-helpers.c @@ -148,7 +148,7 @@ bool gl_create_buffer(GLenum target, GLuint *buffer, GLsizeiptr size, return success; } -bool update_buffer(GLenum target, GLuint buffer, void *data, size_t size) +bool update_buffer(GLenum target, GLuint buffer, const void *data, size_t size) { void *ptr; bool success = true; diff --git a/libobs-opengl/gl-helpers.h b/libobs-opengl/gl-helpers.h index 43a645d..f1f9122 100644 --- a/libobs-opengl/gl-helpers.h +++ b/libobs-opengl/gl-helpers.h @@ -157,5 +157,5 @@ extern bool gl_copy_texture(struct gs_device *device, extern bool gl_create_buffer(GLenum target, GLuint *buffer, GLsizeiptr size, const GLvoid *data, GLenum usage); -extern bool update_buffer(GLenum target, GLuint buffer, void *data, +extern bool update_buffer(GLenum target, GLuint buffer, const void *data, size_t size); diff --git a/libobs-opengl/gl-indexbuffer.c b/libobs-opengl/gl-indexbuffer.c index 2210fbc..187835e 100644 --- a/libobs-opengl/gl-indexbuffer.c +++ b/libobs-opengl/gl-indexbuffer.c @@ -70,15 +70,15 @@ void gs_indexbuffer_destroy(gs_indexbuffer_t *ib) } } -void gs_indexbuffer_flush(gs_indexbuffer_t *ib) +static inline void gs_indexbuffer_flush_internal(gs_indexbuffer_t *ib, + const void *data) { if (!ib->dynamic) { blog(LOG_ERROR, "Index buffer is not dynamic"); goto fail; } - if (!update_buffer(GL_ELEMENT_ARRAY_BUFFER, ib->buffer, ib->data, - ib->size)) + if (!update_buffer(GL_ELEMENT_ARRAY_BUFFER, ib->buffer, data, ib->size)) goto fail; return; @@ -87,6 +87,16 @@ fail: blog(LOG_ERROR, "gs_indexbuffer_flush (GL) failed"); } +void gs_indexbuffer_flush(gs_indexbuffer_t *ib) +{ + gs_indexbuffer_flush_internal(ib, ib->data); +} + +void gs_indexbuffer_flush_direct(gs_indexbuffer_t *ib, const void *data) +{ + gs_indexbuffer_flush_internal(ib, data); +} + void *gs_indexbuffer_get_data(const gs_indexbuffer_t *ib) { return ib->data; diff --git a/libobs-opengl/gl-vertexbuffer.c b/libobs-opengl/gl-vertexbuffer.c index 62f2476..a56e651 100644 --- a/libobs-opengl/gl-vertexbuffer.c +++ b/libobs-opengl/gl-vertexbuffer.c @@ -120,45 +120,51 @@ void gs_vertexbuffer_destroy(gs_vertbuffer_t *vb) } } -void gs_vertexbuffer_flush(gs_vertbuffer_t *vb) +static inline void gs_vertexbuffer_flush_internal(gs_vertbuffer_t *vb, + const struct gs_vb_data *data) { size_t i; + size_t num_tex = data->num_tex < vb->data->num_tex + ? data->num_tex + : vb->data->num_tex; if (!vb->dynamic) { blog(LOG_ERROR, "vertex buffer is not dynamic"); goto failed; } - if (!update_buffer(GL_ARRAY_BUFFER, vb->vertex_buffer, - vb->data->points, - vb->data->num * sizeof(struct vec3))) - goto failed; + if (data->points) { + if (!update_buffer(GL_ARRAY_BUFFER, vb->vertex_buffer, + data->points, + data->num * sizeof(struct vec3))) + goto failed; + } - if (vb->normal_buffer) { + if (vb->normal_buffer && data->normals) { if (!update_buffer(GL_ARRAY_BUFFER, vb->normal_buffer, - vb->data->normals, - vb->data->num * sizeof(struct vec3))) + data->normals, + data->num * sizeof(struct vec3))) goto failed; } - if (vb->tangent_buffer) { + if (vb->tangent_buffer && data->tangents) { if (!update_buffer(GL_ARRAY_BUFFER, vb->tangent_buffer, - vb->data->tangents, - vb->data->num * sizeof(struct vec3))) + data->tangents, + data->num * sizeof(struct vec3))) goto failed; } - if (vb->color_buffer) { + if (vb->color_buffer && data->colors) { if (!update_buffer(GL_ARRAY_BUFFER, vb->color_buffer, - vb->data->colors, - vb->data->num * sizeof(uint32_t))) + data->colors, + data->num * sizeof(uint32_t))) goto failed; } - for (i = 0; i < vb->data->num_tex; i++) { + for (i = 0; i < num_tex; i++) { GLuint buffer = vb->uv_buffers.array[i]; - struct gs_tvertarray *tv = vb->data->tvarray+i; - size_t size = vb->data->num * tv->width * sizeof(float); + struct gs_tvertarray *tv = data->tvarray+i; + size_t size = data->num * tv->width * sizeof(float); if (!update_buffer(GL_ARRAY_BUFFER, buffer, tv->array, size)) goto failed; @@ -170,6 +176,17 @@ failed: blog(LOG_ERROR, "gs_vertexbuffer_flush (GL) failed"); } +void gs_vertexbuffer_flush(gs_vertbuffer_t *vb) +{ + gs_vertexbuffer_flush_internal(vb, vb->data); +} + +void gs_vertexbuffer_flush_direct(gs_vertbuffer_t *vb, + const struct gs_vb_data *data) +{ + gs_vertexbuffer_flush_internal(vb, data); +} + struct gs_vb_data *gs_vertexbuffer_get_data(const gs_vertbuffer_t *vb) { return vb->data; diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index cd2b80e..00c0ebc 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -12,18 +12,32 @@ if (NOT "${FFMPEG_AVCODEC_LIBRARIES}" STREQUAL "") endif() if(UNIX) + if (NOT APPLE) + find_package(PulseAudio) + if (NOT "${PULSEAUDIO_LIBRARY}" STREQUAL "") + message(STATUS "Found PulseAudio - Audio Monitor enabled") + set(HAVE_PULSEAUDIO "1") + else() + set(HAVE_PULSEAUDIO "0") + endif() + else() + set(HAVE_PULSEAUDIO "0") + endif() find_package(DBus QUIET) if (NOT APPLE) find_package(X11_XCB REQUIRED) endif() else() set(HAVE_DBUS "0") + set(HAVE_PULSEAUDIO "0") endif() find_package(ImageMagick QUIET COMPONENTS MagickCore) if(NOT ImageMagick_MagickCore_FOUND AND NOT FFMPEG_AVCODEC_FOUND) - message(FATAL_ERROR "Either MagickCore or Libavcodec is required, but both were not found") + message(FATAL_ERROR "Either MagickCore or Libavcodec is required, but neither were found.") +elseif(NOT ImageMagick_MagickCore_FOUND AND LIBOBS_PREFER_IMAGEMAGICK) + message(FATAL_ERROR "ImageMagick support was requested, but was not found.") endif() option(LIBOBS_PREFER_IMAGEMAGICK "Prefer ImageMagick over ffmpeg for image loading" OFF) @@ -31,6 +45,12 @@ option(LIBOBS_PREFER_IMAGEMAGICK "Prefer ImageMagick over ffmpeg for image loadi if(NOT FFMPEG_AVCODEC_FOUND OR (ImageMagick_MagickCore_FOUND AND LIBOBS_PREFER_IMAGEMAGICK)) message(STATUS "Using ImageMagick for image loading in libobs") + if(${ImageMagick_VERSION_STRING} LESS 7) + set(LIBOBS_IMAGEMAGICK_DIR_STYLE LIBOBS_IMAGEMAGICK_DIR_STYLE_6L) + elseif(${ImageMagick_VERSION_STRING} GREATER_EQUAL 7) + set(LIBOBS_IMAGEMAGICK_DIR_STYLE LIBOBS_IMAGEMAGICK_DIR_STYLE_7GE) + endif() + set(libobs_image_loading_SOURCES graphics/graphics-magick.c) set(libobs_image_loading_LIBRARIES @@ -63,6 +83,7 @@ if(WIN32) util/platform-windows.c) set(libobs_PLATFORM_HEADERS util/threading-windows.h + util/windows/win-registry.h util/windows/win-version.h util/windows/ComPtr.hpp util/windows/CoTaskMemPtr.hpp @@ -75,7 +96,7 @@ if(WIN32) set(libobs_audio_monitoring_HEADERS audio-monitoring/win32/wasapi-output.h ) - set(libobs_PLATFORM_DEPS winmm) + set(libobs_PLATFORM_DEPS winmm psapi) if(MSVC) set(libobs_PLATFORM_DEPS ${libobs_PLATFORM_DEPS} @@ -145,12 +166,22 @@ elseif(UNIX) util/threading-posix.c util/pipe-posix.c util/platform-nix.c) + set(libobs_PLATFORM_HEADERS util/threading-posix.h) - set(libobs_audio_monitoring_SOURCES - audio-monitoring/null/null-audio-monitoring.c - ) + if(HAVE_PULSEAUDIO) + set(libobs_audio_monitoring_HEADERS + audio-monitoring/pulse/pulseaudio-wrapper.h) + + set(libobs_audio_monitoring_SOURCES + audio-monitoring/pulse/pulseaudio-wrapper.c + audio-monitoring/pulse/pulseaudio-enum-devices.c + audio-monitoring/pulse/pulseaudio-output.c) + else() + set(libobs_audio_monitoring_SOURCES + audio-monitoring/null/null-audio-monitoring.c) + endif() if(DBUS_FOUND) set(libobs_PLATFORM_SOURCES ${libobs_PLATFORM_SOURCES} util/platform-nix-dbus.c) @@ -168,6 +199,12 @@ elseif(UNIX) ${libobs_PLATFORM_DEPS} ${X11_XCB_LIBRARIES}) + if(HAVE_PULSEAUDIO) + set(libobs_PLATFORM_DEPS + ${libobs_PLATFORM_DEPS} + ${PULSEAUDIO_LIBRARY}) + endif() + if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") # use the sysinfo compatibility library on bsd find_package(Libsysinfo REQUIRED) @@ -358,7 +395,9 @@ set(libobs_SOURCES ${libobs_graphics_SOURCES} ${libobs_mediaio_SOURCES} ${libobs_util_SOURCES} - ${libobs_libobs_SOURCES}) + ${libobs_libobs_SOURCES} + ${libobs_audio_monitoring_SOURCES} + ) set(libobs_HEADERS ${libobs_config_HEADERS} @@ -367,7 +406,6 @@ set(libobs_HEADERS ${libobs_mediaio_HEADERS} ${libobs_util_HEADERS} ${libobs_libobs_HEADERS} - ${libobs_audio_monitoring_SOURCES} ${libobs_audio_monitoring_HEADERS} ) diff --git a/libobs/audio-monitoring/null/null-audio-monitoring.c b/libobs/audio-monitoring/null/null-audio-monitoring.c index 9d7b2a4..7926e84 100644 --- a/libobs/audio-monitoring/null/null-audio-monitoring.c +++ b/libobs/audio-monitoring/null/null-audio-monitoring.c @@ -1,4 +1,4 @@ -#include "../../obs-internal.h" +#include void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data) { diff --git a/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c b/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c new file mode 100644 index 0000000..f598829 --- /dev/null +++ b/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c @@ -0,0 +1,33 @@ +#include +#include "pulseaudio-wrapper.h" + +static void pulseaudio_output_info(pa_context *c, const pa_source_info *i, + int eol, void *userdata) +{ + UNUSED_PARAMETER(c); + if (eol != 0 || i->monitor_of_sink == PA_INVALID_INDEX) + goto skip; + + struct enum_cb *ecb = (struct enum_cb *) userdata; + if (ecb->cont) + ecb->cont = ecb->cb(ecb->data, i->description, i->name); + +skip: + pulseaudio_signal(0); +} + +void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, + void *data) +{ + struct enum_cb *ecb = bzalloc(sizeof(struct enum_cb)); + ecb->cb = cb; + ecb->data = data; + ecb->cont = 1; + + pulseaudio_init(); + pa_source_info_cb_t pa_cb = pulseaudio_output_info; + pulseaudio_get_source_info_list(pa_cb, (void *) ecb); + pulseaudio_unref(); + + bfree(ecb); +} diff --git a/libobs/audio-monitoring/pulse/pulseaudio-output.c b/libobs/audio-monitoring/pulse/pulseaudio-output.c new file mode 100644 index 0000000..aa791d4 --- /dev/null +++ b/libobs/audio-monitoring/pulse/pulseaudio-output.c @@ -0,0 +1,552 @@ +#include "obs-internal.h" +#include "pulseaudio-wrapper.h" + +#define PULSE_DATA(voidptr) struct audio_monitor *data = voidptr; +#define blog(level, msg, ...) blog(level, "pulse-am: " msg, ##__VA_ARGS__) + +struct audio_monitor { + obs_source_t *source; + pa_stream *stream; + char *device; + pa_buffer_attr attr; + enum speaker_layout speakers; + pa_sample_format_t format; + uint_fast32_t samples_per_sec; + uint_fast32_t bytes_per_frame; + uint_fast8_t channels; + + uint_fast32_t packets; + uint_fast64_t frames; + + struct circlebuf new_data; + audio_resampler_t *resampler; + size_t buffer_size; + size_t bytesRemaining; + size_t bytes_per_channel; + + bool ignore; + pthread_mutex_t playback_mutex; +}; + +static enum speaker_layout pulseaudio_channels_to_obs_speakers( + uint_fast32_t channels) +{ + switch (channels) { + case 0: return SPEAKERS_UNKNOWN; + case 1: return SPEAKERS_MONO; + case 2: return SPEAKERS_STEREO; + case 3: return SPEAKERS_2POINT1; + case 4: return SPEAKERS_4POINT0; + case 5: return SPEAKERS_4POINT1; + case 6: return SPEAKERS_5POINT1; + case 8: return SPEAKERS_7POINT1; + default: return SPEAKERS_UNKNOWN; + } +} + +static enum audio_format pulseaudio_to_obs_audio_format( + pa_sample_format_t format) +{ + switch (format) { + case PA_SAMPLE_U8: + return AUDIO_FORMAT_U8BIT; + case PA_SAMPLE_S16LE: + return AUDIO_FORMAT_16BIT; + case PA_SAMPLE_S32LE: + return AUDIO_FORMAT_32BIT; + case PA_SAMPLE_FLOAT32LE: + return AUDIO_FORMAT_FLOAT; + default: + return AUDIO_FORMAT_UNKNOWN; + } +} + +static pa_channel_map pulseaudio_channel_map(enum speaker_layout layout) +{ + pa_channel_map ret; + + ret.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + ret.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + ret.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + ret.map[3] = PA_CHANNEL_POSITION_LFE; + ret.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; + ret.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; + ret.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + ret.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + + switch (layout) { + case SPEAKERS_MONO: + ret.channels = 1; + ret.map[0] = PA_CHANNEL_POSITION_MONO; + break; + + case SPEAKERS_STEREO: + ret.channels = 2; + break; + + case SPEAKERS_2POINT1: + ret.channels = 3; + ret.map[2] = PA_CHANNEL_POSITION_LFE; + break; + + case SPEAKERS_4POINT0: + ret.channels = 4; + ret.map[3] = PA_CHANNEL_POSITION_REAR_CENTER; + break; + + case SPEAKERS_4POINT1: + ret.channels = 5; + ret.map[4] = PA_CHANNEL_POSITION_REAR_CENTER; + break; + + case SPEAKERS_5POINT1: + ret.channels = 6; + break; + + case SPEAKERS_7POINT1: + ret.channels = 8; + break; + + case SPEAKERS_UNKNOWN: + default: + ret.channels = 0; + break; + } + + return ret; +} + +static void process_byte(void *p, size_t frames, size_t channels, float vol) +{ + register char *cur = (char *) p; + register char *end = cur + frames * channels; + + while (cur < end) + *(cur++) *= vol; +} + +static void process_short(void *p, size_t frames, size_t channels, float vol) +{ + register short *cur = (short *) p; + register short *end = cur + frames * channels; + + while (cur < end) + *(cur++) *= vol; +} + +static void process_float(void *p, size_t frames, size_t channels, float vol) +{ + register float *cur = (float *) p; + register float *end = cur + frames * channels; + + while (cur < end) + *(cur++) *= vol; +} + +void process_volume(const struct audio_monitor *monitor, float vol, + uint8_t *const *resample_data, uint32_t resample_frames) +{ + switch (monitor->bytes_per_channel) { + case 1: + process_byte(resample_data[0], resample_frames, + monitor->channels, vol); + break; + case 2: + process_short(resample_data[0], resample_frames, + monitor->channels, vol); + break; + default: + process_float(resample_data[0], resample_frames, + monitor->channels, vol); + break; + } +} + +static void do_stream_write(void *param) +{ + PULSE_DATA(param); + uint8_t *buffer = NULL; + + while (data->new_data.size >= data->buffer_size && + data->bytesRemaining > 0) { + size_t bytesToFill = data->buffer_size; + + if (bytesToFill > data->bytesRemaining) + bytesToFill = data->bytesRemaining; + + pa_stream_begin_write(data->stream, (void **) &buffer, + &bytesToFill); + + circlebuf_pop_front(&data->new_data, buffer, bytesToFill); + + pulseaudio_lock(); + pa_stream_write(data->stream, buffer, bytesToFill, NULL, + 0LL, PA_SEEK_RELATIVE); + pulseaudio_unlock(); + + data->bytesRemaining -= bytesToFill; + } +} + +static void on_audio_playback(void *param, obs_source_t *source, + const struct audio_data *audio_data, bool muted) +{ + struct audio_monitor *monitor = param; + float vol = source->user_volume; + size_t bytes; + + uint8_t *resample_data[MAX_AV_PLANES]; + uint32_t resample_frames; + uint64_t ts_offset; + bool success; + + if (pthread_mutex_trylock(&monitor->playback_mutex) != 0) + return; + + if (os_atomic_load_long(&source->activate_refs) == 0) + goto unlock; + + success = audio_resampler_resample(monitor->resampler, resample_data, + &resample_frames, &ts_offset, + (const uint8_t *const *) audio_data->data, + (uint32_t) audio_data->frames); + + if (!success) + goto unlock; + + bytes = monitor->bytes_per_frame * resample_frames; + + if (muted) { + memset(resample_data[0], 0, bytes); + } else { + if (!close_float(vol, 1.0f, EPSILON)) { + process_volume(monitor, vol, resample_data, + resample_frames); + } + } + + circlebuf_push_back(&monitor->new_data, resample_data[0], bytes); + monitor->packets++; + monitor->frames += resample_frames; + +unlock: + pthread_mutex_unlock(&monitor->playback_mutex); + do_stream_write(param); +} + +static void pulseaudio_stream_write(pa_stream *p, size_t nbytes, void *userdata) +{ + UNUSED_PARAMETER(p); + PULSE_DATA(userdata); + + pthread_mutex_lock(&data->playback_mutex); + data->bytesRemaining += nbytes; + pthread_mutex_unlock(&data->playback_mutex); + + pulseaudio_signal(0); +} + +static void pulseaudio_underflow(pa_stream *p, void *userdata) +{ + UNUSED_PARAMETER(p); + PULSE_DATA(userdata); + + pthread_mutex_lock(&data->playback_mutex); + if (obs_source_active(data->source)) + data->attr.tlength = (data->attr.tlength * 3) / 2; + + pa_stream_set_buffer_attr(data->stream, &data->attr, NULL, NULL); + pthread_mutex_unlock(&data->playback_mutex); + + pulseaudio_signal(0); +} + +static void pulseaudio_server_info(pa_context *c, const pa_server_info *i, + void *userdata) +{ + UNUSED_PARAMETER(c); + UNUSED_PARAMETER(userdata); + + blog(LOG_INFO, "Server name: '%s %s'", i->server_name, + i->server_version); + + pulseaudio_signal(0); +} + +static void pulseaudio_source_info(pa_context *c, const pa_source_info *i, + int eol, void *userdata) +{ + UNUSED_PARAMETER(c); + PULSE_DATA(userdata); + // 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, %"PRIu8" channels", + pa_sample_format_to_string(i->sample_spec.format), + i->sample_spec.rate, i->sample_spec.channels); + + pa_sample_format_t format = i->sample_spec.format; + if (pulseaudio_to_obs_audio_format(format) == AUDIO_FORMAT_UNKNOWN) { + format = PA_SAMPLE_FLOAT32LE; + + blog(LOG_INFO, "Sample format %s not supported by OBS," + "using %s instead for recording", + pa_sample_format_to_string( + i->sample_spec.format), + pa_sample_format_to_string(format)); + } + + uint8_t channels = i->sample_spec.channels; + if (pulseaudio_channels_to_obs_speakers(channels) == SPEAKERS_UNKNOWN) { + channels = 2; + + blog(LOG_INFO, "%c channels not supported by OBS," + "using %c instead for recording", + i->sample_spec.channels, + channels); + } + + data->format = format; + data->samples_per_sec = i->sample_spec.rate; + data->channels = channels; +skip: + pulseaudio_signal(0); +} + +static void pulseaudio_stop_playback(struct audio_monitor *monitor) +{ + if (monitor->stream) { + pa_stream_disconnect(monitor->stream); + pa_stream_unref(monitor->stream); + monitor->stream = NULL; + } + + blog(LOG_INFO, "Stopped Monitoring in '%s'", monitor->device); + blog(LOG_INFO, "Got %"PRIuFAST32" packets with %"PRIuFAST64" frames", + monitor->packets, monitor->frames); + + monitor->packets = 0; + monitor->frames = 0; +} + +static bool audio_monitor_init(struct audio_monitor *monitor, + obs_source_t *source) +{ + pthread_mutex_init_value(&monitor->playback_mutex); + + monitor->source = source; + + const char *id = obs->audio.monitoring_device_id; + if (!id) + return false; + + if (source->info.output_flags & OBS_SOURCE_DO_NOT_SELF_MONITOR) { + obs_data_t *s = obs_source_get_settings(source); + const char *s_dev_id = obs_data_get_string(s, "device_id"); + bool match = devices_match(s_dev_id, id); + obs_data_release(s); + + if (match) { + monitor->ignore = true; + blog(LOG_INFO, "Prevented feedback-loop in '%s'", + s_dev_id); + return true; + } + } + + pulseaudio_init(); + + if (strcmp(id, "default") == 0) + get_default_id(&monitor->device); + else + monitor->device = bstrdup(id); + + if (!monitor->device) + return false; + + if (pulseaudio_get_server_info(pulseaudio_server_info, + (void *) monitor) < 0) { + blog(LOG_ERROR, "Unable to get server info !"); + return false; + } + + if (pulseaudio_get_source_info(pulseaudio_source_info, monitor->device, + (void *) monitor) < 0) { + blog(LOG_ERROR, "Unable to get source info !"); + return false; + } + if (monitor->format == PA_SAMPLE_INVALID) { + blog(LOG_ERROR, + "An error occurred while getting the source info!"); + return false; + } + + pa_sample_spec spec; + spec.format = monitor->format; + spec.rate = (uint32_t) monitor->samples_per_sec; + spec.channels = monitor->channels; + + if (!pa_sample_spec_valid(&spec)) { + blog(LOG_ERROR, "Sample spec is not valid"); + return false; + } + + const struct audio_output_info *info = audio_output_get_info( + obs->audio.audio); + + struct resample_info from = { + .samples_per_sec = info->samples_per_sec, + .speakers = info->speakers, + .format = AUDIO_FORMAT_FLOAT_PLANAR + }; + struct resample_info to = { + .samples_per_sec = (uint32_t) monitor->samples_per_sec, + .speakers = pulseaudio_channels_to_obs_speakers( + monitor->channels), + .format = pulseaudio_to_obs_audio_format + (monitor->format) + }; + + monitor->resampler = audio_resampler_create(&to, &from); + if (!monitor->resampler) { + blog(LOG_WARNING, "%s: %s", __FUNCTION__, + "Failed to create resampler"); + return false; + } + + monitor->bytes_per_channel = get_audio_bytes_per_channel( + pulseaudio_to_obs_audio_format(monitor->format)); + monitor->speakers = pulseaudio_channels_to_obs_speakers(spec.channels); + monitor->bytes_per_frame = pa_frame_size(&spec); + + pa_channel_map channel_map = pulseaudio_channel_map(monitor->speakers); + + monitor->stream = pulseaudio_stream_new( + obs_source_get_name(monitor->source), &spec, &channel_map); + if (!monitor->stream) { + blog(LOG_ERROR, "Unable to create stream"); + return false; + } + + monitor->attr.fragsize = (uint32_t) -1; + monitor->attr.maxlength = (uint32_t) -1; + monitor->attr.minreq = (uint32_t) -1; + monitor->attr.prebuf = (uint32_t) -1; + monitor->attr.tlength = pa_usec_to_bytes(25000, &spec); + + monitor->buffer_size = monitor->bytes_per_frame * + pa_usec_to_bytes(5000, &spec); + + pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_AUTO_TIMING_UPDATE; + + if (pthread_mutex_init(&monitor->playback_mutex, NULL) != 0) { + blog(LOG_WARNING, "%s: %s", __FUNCTION__, + "Failed to init mutex"); + return false; + } + + int_fast32_t ret = pulseaudio_connect_playback(monitor->stream, + monitor->device, &monitor->attr, flags); + if (ret < 0) { + pulseaudio_stop_playback(monitor); + blog(LOG_ERROR, "Unable to connect to stream"); + return false; + } + + blog(LOG_INFO, "Started Monitoring in '%s'", monitor->device); + return true; +} + +static void audio_monitor_init_final(struct audio_monitor *monitor) +{ + if (monitor->ignore) + return; + + obs_source_add_audio_capture_callback(monitor->source, + on_audio_playback, monitor); + + pulseaudio_write_callback(monitor->stream, pulseaudio_stream_write, + (void *) monitor); + + pulseaudio_set_underflow_callback(monitor->stream, pulseaudio_underflow, + (void *) monitor); +} + +static inline void audio_monitor_free(struct audio_monitor *monitor) +{ + if (monitor->ignore) + return; + + if (monitor->source) + obs_source_remove_audio_capture_callback(monitor->source, + on_audio_playback, monitor); + + audio_resampler_destroy(monitor->resampler); + circlebuf_free(&monitor->new_data); + + if (monitor->stream) + pulseaudio_stop_playback(monitor); + pulseaudio_unref(); + + bfree(monitor->device); +} + +struct audio_monitor *audio_monitor_create(obs_source_t *source) +{ + struct audio_monitor monitor = {0}; + struct audio_monitor *out; + + if (!audio_monitor_init(&monitor, source)) + goto fail; + + out = bmemdup(&monitor, sizeof(monitor)); + + pthread_mutex_lock(&obs->audio.monitoring_mutex); + da_push_back(obs->audio.monitors, &out); + pthread_mutex_unlock(&obs->audio.monitoring_mutex); + + audio_monitor_init_final(out); + return out; + +fail: + audio_monitor_free(&monitor); + return NULL; +} + +void audio_monitor_reset(struct audio_monitor *monitor) +{ + struct audio_monitor new_monitor = {0}; + bool success; + audio_monitor_free(monitor); + + pthread_mutex_lock(&monitor->playback_mutex); + success = audio_monitor_init(&new_monitor, monitor->source); + pthread_mutex_unlock(&monitor->playback_mutex); + + if (success) { + *monitor = new_monitor; + audio_monitor_init_final(monitor); + } else { + audio_monitor_free(&new_monitor); + } +} + +void audio_monitor_destroy(struct audio_monitor *monitor) +{ + if (monitor) { + audio_monitor_free(monitor); + + pthread_mutex_lock(&obs->audio.monitoring_mutex); + da_erase_item(obs->audio.monitors, &monitor); + pthread_mutex_unlock(&obs->audio.monitoring_mutex); + + bfree(monitor); + } +} diff --git a/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c new file mode 100644 index 0000000..fc41d7a --- /dev/null +++ b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c @@ -0,0 +1,341 @@ +/* +Copyright (C) 2014 by Leonhard Oelke +Copyright (C) 2017 by Fabio Madia + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include + +#include + +#include +#include + +#include "pulseaudio-wrapper.h" + +/* global data */ +static uint_fast32_t pulseaudio_refs = 0; +static pthread_mutex_t pulseaudio_mutex = PTHREAD_MUTEX_INITIALIZER; +static pa_threaded_mainloop *pulseaudio_mainloop = NULL; +static pa_context *pulseaudio_context = NULL; + +static void pulseaudio_default_devices(pa_context *c, const pa_server_info *i, + void *userdata) +{ + UNUSED_PARAMETER(c); + struct pulseaudio_default_output *d = + (struct pulseaudio_default_output *) userdata; + d->default_sink_name = bstrdup(i->default_sink_name); + pulseaudio_signal(0); +} + +void get_default_id(char **id) +{ + pulseaudio_init(); + struct pulseaudio_default_output *pdo = bzalloc( + sizeof(struct pulseaudio_default_output)); + pulseaudio_get_server_info( + (pa_server_info_cb_t) pulseaudio_default_devices, + (void *) pdo); + *id = bzalloc(strlen(pdo->default_sink_name) + 9); + strcat(*id, pdo->default_sink_name); + strcat(*id, ".monitor"); + bfree(pdo->default_sink_name); + bfree(pdo); + pulseaudio_unref(); +} + +bool devices_match(const char *id1, const char *id2) +{ + bool match; + char *name1 = NULL; + char *name2 = NULL; + + if (!id1 || !id2) + return false; + + if (strcmp(id1, "default") == 0) { + get_default_id(&name1); + id1 = name1; + } + if (strcmp(id2, "default") == 0) { + get_default_id(&name2); + id2 = name2; + } + + match = strcmp(id1, id2) == 0; + bfree(name1); + bfree(name2); + return match; +} + +/** + * context status change callback + * + * @todo this is currently a noop, we want to reconnect here if the connection + * is lost ... + */ +static void pulseaudio_context_state_changed(pa_context *c, void *userdata) +{ + UNUSED_PARAMETER(userdata); + UNUSED_PARAMETER(c); + + pulseaudio_signal(0); +} + +/** + * get the default properties + */ +static pa_proplist *pulseaudio_properties() +{ + pa_proplist *p = pa_proplist_new(); + + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, "OBS"); + pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, "obs"); + pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, "production"); + + return p; +} + +/** + * Initialize the pulse audio context with properties and callback + */ +static void pulseaudio_init_context() +{ + pulseaudio_lock(); + + pa_proplist *p = pulseaudio_properties(); + pulseaudio_context = pa_context_new_with_proplist( + pa_threaded_mainloop_get_api(pulseaudio_mainloop), + "OBS-Monitor", p); + + pa_context_set_state_callback(pulseaudio_context, + pulseaudio_context_state_changed, NULL); + + pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOAUTOSPAWN, + NULL); + pa_proplist_free(p); + + pulseaudio_unlock(); +} + +/** + * wait for context to be ready + */ +static int_fast32_t pulseaudio_context_ready() +{ + pulseaudio_lock(); + + if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulseaudio_context))) { + pulseaudio_unlock(); + return -1; + } + + while (pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) + pulseaudio_wait(); + + pulseaudio_unlock(); + return 0; +} + +int_fast32_t pulseaudio_init() +{ + pthread_mutex_lock(&pulseaudio_mutex); + + if (pulseaudio_refs == 0) { + pulseaudio_mainloop = pa_threaded_mainloop_new(); + pa_threaded_mainloop_start(pulseaudio_mainloop); + + pulseaudio_init_context(); + } + + pulseaudio_refs++; + + pthread_mutex_unlock(&pulseaudio_mutex); + + return 0; +} + +void pulseaudio_unref() +{ + pthread_mutex_lock(&pulseaudio_mutex); + + if (--pulseaudio_refs == 0) { + pulseaudio_lock(); + if (pulseaudio_context != NULL) { + pa_context_disconnect(pulseaudio_context); + pa_context_unref(pulseaudio_context); + pulseaudio_context = NULL; + } + pulseaudio_unlock(); + + if (pulseaudio_mainloop != NULL) { + pa_threaded_mainloop_stop(pulseaudio_mainloop); + pa_threaded_mainloop_free(pulseaudio_mainloop); + pulseaudio_mainloop = NULL; + } + } + + pthread_mutex_unlock(&pulseaudio_mutex); +} + +void pulseaudio_lock() +{ + pa_threaded_mainloop_lock(pulseaudio_mainloop); +} + +void pulseaudio_unlock() +{ + pa_threaded_mainloop_unlock(pulseaudio_mainloop); +} + +void pulseaudio_wait() +{ + pa_threaded_mainloop_wait(pulseaudio_mainloop); +} + +void pulseaudio_signal(int wait_for_accept) +{ + pa_threaded_mainloop_signal(pulseaudio_mainloop, wait_for_accept); +} + +void pulseaudio_accept() +{ + pa_threaded_mainloop_accept(pulseaudio_mainloop); +} + +int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb, + void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return -1; + + pulseaudio_lock(); + + pa_operation *op = pa_context_get_source_info_list( + pulseaudio_context, cb, userdata); + if (!op) { + pulseaudio_unlock(); + return -1; + } + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) + pulseaudio_wait(); + pa_operation_unref(op); + + pulseaudio_unlock(); + + return 0; +} + +int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb, + const char *name, void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return -1; + + pulseaudio_lock(); + + pa_operation *op = pa_context_get_source_info_by_name( + pulseaudio_context, name, cb, userdata); + if (!op) { + pulseaudio_unlock(); + return -1; + } + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) + pulseaudio_wait(); + pa_operation_unref(op); + + pulseaudio_unlock(); + + return 0; +} + +int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return -1; + + pulseaudio_lock(); + + pa_operation *op = pa_context_get_server_info( + pulseaudio_context, cb, userdata); + if (!op) { + pulseaudio_unlock(); + return -1; + } + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) + pulseaudio_wait(); + pa_operation_unref(op); + + pulseaudio_unlock(); + return 0; +} + +pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss, + const pa_channel_map *map) +{ + if (pulseaudio_context_ready() < 0) + return NULL; + + pulseaudio_lock(); + + pa_proplist *p = pulseaudio_properties(); + pa_stream *s = pa_stream_new_with_proplist( + pulseaudio_context, name, ss, map, p); + pa_proplist_free(p); + + pulseaudio_unlock(); + return s; +} + +int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name, + const pa_buffer_attr *attr, pa_stream_flags_t flags) +{ + if (pulseaudio_context_ready() < 0) + return -1; + + size_t dev_len = strlen(name) - 8; + char device[dev_len]; + memcpy(device, name, dev_len); + device[dev_len] = '\0'; + + pulseaudio_lock(); + int_fast32_t ret = pa_stream_connect_playback(s, device, attr, flags, + NULL, NULL); + pulseaudio_unlock(); + return ret; +} + +void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb, + void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return; + + pulseaudio_lock(); + pa_stream_set_write_callback(p, cb, userdata); + pulseaudio_unlock(); +} + +void pulseaudio_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, + void *userdata) +{ + if (pulseaudio_context_ready() < 0) + return; + + pulseaudio_lock(); + pa_stream_set_underflow_callback(p, cb, userdata); + pulseaudio_unlock(); +} diff --git a/libobs/audio-monitoring/pulse/pulseaudio-wrapper.h b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.h new file mode 100644 index 0000000..4baaa92 --- /dev/null +++ b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.h @@ -0,0 +1,185 @@ +/* +Copyright (C) 2014 by Leonhard Oelke +Copyright (C) 2017 by Fabio Madia + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#pragma once + +struct pulseaudio_default_output { + char *default_sink_name; +}; + +struct enum_cb { + obs_enum_audio_device_cb cb; + void *data; + int cont; +}; + +void get_default_id(char **id); + +bool devices_match(const char *id1, const char *id2); + +/** + * Initialize the pulseaudio mainloop and increase the reference count + */ +int_fast32_t pulseaudio_init(); + +/** + * Unreference the pulseaudio mainloop, when the reference count reaches + * zero the mainloop will automatically be destroyed + */ +void pulseaudio_unref(); + +/** + * Lock the mainloop + * + * In order to allow for multiple threads to use the same mainloop pulseaudio + * provides it's own locking mechanism. This function should be called before + * using any pulseaudio function that is in any way related to the mainloop or + * context. + * + * @note use of this function may cause deadlocks + * + * @warning do not use with pulseaudio_ wrapper functions + */ +void pulseaudio_lock(); + +/** + * Unlock the mainloop + * + * @see pulseaudio_lock() + */ +void pulseaudio_unlock(); + +/** + * Wait for events to happen + * + * This function should be called when waiting for an event to happen. + */ +void pulseaudio_wait(); + +/** + * Wait for accept signal from calling thread + * + * This function tells the pulseaudio mainloop wheter the data provided to + * the callback should be retained until the calling thread executes + * pulseaudio_accept() + * + * If wait_for_accept is 0 the function returns and the data is freed. + */ +void pulseaudio_signal(int wait_for_accept); + +/** + * Signal the waiting callback to return + * + * This function is used in conjunction with pulseaudio_signal() + */ +void pulseaudio_accept(); + +/** + * Request source information + * + * The function will block until the operation was executed and the mainloop + * called the provided callback function. + * + * @return negative on error + * + * @note The function will block until the server context is ready. + * + * @warning call without active locks + */ +int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb, + void *userdata); + +/** + * Request source information from a specific source + * + * The function will block until the operation was executed and the mainloop + * called the provided callback function. + * + * @param cb pointer to the callback function + * @param name the source name to get information for + * @param userdata pointer to userdata the callback will be called with + * + * @return negative on error + * + * @note The function will block until the server context is ready. + * + * @warning call without active locks + */ +int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb, + const char *name, void *userdata); + +/** + * Request server information + * + * The function will block until the operation was executed and the mainloop + * called the provided callback function. + * + * @return negative on error + * + * @note The function will block until the server context is ready. + * + * @warning call without active locks + */ +int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata); + +/** + * Create a new stream with the default properties + * + * @note The function will block until the server context is ready. + * + * @warning call without active locks + */ +pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss, + const pa_channel_map *map); + +/** + * Connect to a pulseaudio playback stream + * + * @param s pa_stream to connect to. NULL for default + * @param attr pa_buffer_attr + * @param name Device name. NULL for default device + * @param flags pa_stream_flags_t + * @return negative on error + */ +int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name, + const pa_buffer_attr *attr, pa_stream_flags_t flags); + +/** + * Sets a callback function for when data can be written to the stream + * + * @param p pa_stream to connect to. NULL for default + * @param cb pa_stream_request_cb_t + * @param userdata pointer to userdata the callback will be called with + */ +void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb, + void *userdata); + +/** + * Sets a callback function for when an underflow happen + * + * @param p pa_stream to connect to. NULL for default + * @param cb pa_stream_notify_cb_t + * @param userdata pointer to userdata the callback will be called with + */ +void pulseaudio_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, + void *userdata); diff --git a/libobs/audio-monitoring/win32/wasapi-output.c b/libobs/audio-monitoring/win32/wasapi-output.c index 750f9b5..c1eb506 100644 --- a/libobs/audio-monitoring/win32/wasapi-output.c +++ b/libobs/audio-monitoring/win32/wasapi-output.c @@ -165,7 +165,10 @@ static void on_audio_playback(void *param, obs_source_t *source, UINT32 pad = 0; monitor->client->lpVtbl->GetCurrentPadding(monitor->client, &pad); - if (monitor->source_has_video) { + bool decouple_audio = + source->async_unbuffered && source->async_decoupled; + + if (monitor->source_has_video && !decouple_audio) { uint64_t ts = audio_data->timestamp - ts_offset; if (!process_audio_delay(monitor, (float**)(&resample_data[0]), @@ -226,14 +229,11 @@ static inline void audio_monitor_free(struct audio_monitor *monitor) static enum speaker_layout convert_speaker_layout(DWORD layout, WORD channels) { switch (layout) { - case KSAUDIO_SPEAKER_QUAD: return SPEAKERS_QUAD; case KSAUDIO_SPEAKER_2POINT1: return SPEAKERS_2POINT1; + case KSAUDIO_SPEAKER_SURROUND: return SPEAKERS_4POINT0; case KSAUDIO_SPEAKER_4POINT1: return SPEAKERS_4POINT1; - case KSAUDIO_SPEAKER_SURROUND: return SPEAKERS_SURROUND; case KSAUDIO_SPEAKER_5POINT1: return SPEAKERS_5POINT1; - case KSAUDIO_SPEAKER_5POINT1_SURROUND: return SPEAKERS_5POINT1_SURROUND; case KSAUDIO_SPEAKER_7POINT1: return SPEAKERS_7POINT1; - case KSAUDIO_SPEAKER_7POINT1_SURROUND: return SPEAKERS_7POINT1_SURROUND; } return (enum speaker_layout)channels; diff --git a/libobs/audio-monitoring/win32/wasapi-output.h b/libobs/audio-monitoring/win32/wasapi-output.h index d43a418..439fd54 100644 --- a/libobs/audio-monitoring/win32/wasapi-output.h +++ b/libobs/audio-monitoring/win32/wasapi-output.h @@ -2,8 +2,11 @@ #include #include -#define KSAUDIO_SPEAKER_4POINT1 (KSAUDIO_SPEAKER_QUAD|SPEAKER_LOW_FREQUENCY) + #define KSAUDIO_SPEAKER_2POINT1 (KSAUDIO_SPEAKER_STEREO|SPEAKER_LOW_FREQUENCY) +#define KSAUDIO_SPEAKER_SURROUND_AVUTIL \ + (KSAUDIO_SPEAKER_STEREO|SPEAKER_FRONT_CENTER) +#define KSAUDIO_SPEAKER_4POINT1 (KSAUDIO_SPEAKER_SURROUND|SPEAKER_LOW_FREQUENCY) #define safe_release(ptr) \ do { \ diff --git a/libobs/callback/calldata.h b/libobs/callback/calldata.h index 8f81eb4..7acb8e8 100644 --- a/libobs/callback/calldata.h +++ b/libobs/callback/calldata.h @@ -88,6 +88,17 @@ static inline void calldata_clear(struct calldata *data) } } +static inline calldata_t *calldata_create(void) +{ + return (calldata_t*)bzalloc(sizeof(struct calldata)); +} + +static inline void calldata_destroy(calldata_t *cd) +{ + calldata_free(cd); + bfree(cd); +} + /* ------------------------------------------------------------------------- */ /* NOTE: 'get' functions return true only if parameter exists, and is the * same type. They return false otherwise. */ diff --git a/libobs/callback/signal.c b/libobs/callback/signal.c index 7cc9074..5b7c3ee 100644 --- a/libobs/callback/signal.c +++ b/libobs/callback/signal.c @@ -86,9 +86,19 @@ static inline size_t signal_get_callback_idx(struct signal_info *si, return DARRAY_INVALID; } +struct global_callback_info { + global_signal_callback_t callback; + void *data; + long signaling; + bool remove; +}; + struct signal_handler { struct signal_info *first; pthread_mutex_t mutex; + + DARRAY(struct global_callback_info) global_callbacks; + pthread_mutex_t global_callbacks_mutex; }; static struct signal_info *getsignal(signal_handler_t *handler, @@ -114,11 +124,24 @@ static struct signal_info *getsignal(signal_handler_t *handler, signal_handler_t *signal_handler_create(void) { - struct signal_handler *handler = bmalloc(sizeof(struct signal_handler)); + struct signal_handler *handler = bzalloc(sizeof(struct signal_handler)); handler->first = NULL; + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr) != 0) + return NULL; + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) + return NULL; + if (pthread_mutex_init(&handler->mutex, NULL) != 0) { - blog(LOG_ERROR, "Couldn't create signal handler!"); + blog(LOG_ERROR, "Couldn't create signal handler mutex!"); + bfree(handler); + return NULL; + } + if (pthread_mutex_init(&handler->global_callbacks_mutex, &attr) != 0) { + blog(LOG_ERROR, "Couldn't create signal handler global " + "callbacks mutex!"); + pthread_mutex_destroy(&handler->mutex); bfree(handler); return NULL; } @@ -136,6 +159,8 @@ void signal_handler_destroy(signal_handler_t *handler) sig = next; } + da_free(handler->global_callbacks); + pthread_mutex_destroy(&handler->global_callbacks_mutex); pthread_mutex_destroy(&handler->mutex); bfree(handler); } @@ -199,7 +224,7 @@ void signal_handler_connect(signal_handler_t *handler, const char *signal, idx = signal_get_callback_idx(sig, callback, data); if (idx == DARRAY_INVALID) da_push_back(sig->callbacks, &cb_data); - + pthread_mutex_unlock(&sig->mutex); } @@ -236,10 +261,21 @@ void signal_handler_disconnect(signal_handler_t *handler, const char *signal, else da_erase(sig->callbacks, idx); } - + pthread_mutex_unlock(&sig->mutex); } +static THREAD_LOCAL struct signal_callback *current_signal_cb = NULL; +static THREAD_LOCAL struct global_callback_info *current_global_cb = NULL; + +void signal_handler_remove_current(void) +{ + if (current_signal_cb) + current_signal_cb->remove = true; + else if (current_global_cb) + current_global_cb->remove = true; +} + void signal_handler_signal(signal_handler_t *handler, const char *signal, calldata_t *params) { @@ -253,8 +289,11 @@ void signal_handler_signal(signal_handler_t *handler, const char *signal, for (size_t i = 0; i < sig->callbacks.num; i++) { struct signal_callback *cb = sig->callbacks.array+i; - if (!cb->remove) + if (!cb->remove) { + current_signal_cb = cb; cb->callback(cb->data, params); + current_signal_cb = NULL; + } } for (size_t i = sig->callbacks.num; i > 0; i--) { @@ -265,4 +304,74 @@ void signal_handler_signal(signal_handler_t *handler, const char *signal, sig->signalling = false; pthread_mutex_unlock(&sig->mutex); + + pthread_mutex_lock(&handler->global_callbacks_mutex); + + if (handler->global_callbacks.num) { + for (size_t i = 0; i < handler->global_callbacks.num; i++) { + struct global_callback_info *cb = + handler->global_callbacks.array + i; + + if (!cb->remove) { + cb->signaling++; + current_global_cb = cb; + cb->callback(cb->data, signal, params); + current_global_cb = NULL; + cb->signaling--; + } + } + + for (size_t i = handler->global_callbacks.num; i > 0; i--) { + struct global_callback_info *cb = + handler->global_callbacks.array + (i - 1); + + if (cb->remove && !cb->signaling) + da_erase(handler->global_callbacks, i - 1); + } + } + + pthread_mutex_unlock(&handler->global_callbacks_mutex); +} + +void signal_handler_connect_global(signal_handler_t *handler, + global_signal_callback_t callback, void *data) +{ + struct global_callback_info cb_data = {callback, data, 0, false}; + size_t idx; + + if (!handler || !callback) + return; + + pthread_mutex_lock(&handler->global_callbacks_mutex); + + idx = da_find(handler->global_callbacks, &cb_data, 0); + if (idx == DARRAY_INVALID) + da_push_back(handler->global_callbacks, &cb_data); + + pthread_mutex_unlock(&handler->global_callbacks_mutex); +} + +void signal_handler_disconnect_global(signal_handler_t *handler, + global_signal_callback_t callback, void *data) +{ + struct global_callback_info cb_data = {callback, data, false}; + size_t idx; + + if (!handler || !callback) + return; + + pthread_mutex_lock(&handler->global_callbacks_mutex); + + idx = da_find(handler->global_callbacks, &cb_data, 0); + if (idx != DARRAY_INVALID) { + struct global_callback_info *cb = + handler->global_callbacks.array + idx; + + if (cb->signaling) + cb->remove = true; + else + da_erase(handler->global_callbacks, idx); + } + + pthread_mutex_unlock(&handler->global_callbacks_mutex); } diff --git a/libobs/callback/signal.h b/libobs/callback/signal.h index be9138b..ea4f0a9 100644 --- a/libobs/callback/signal.h +++ b/libobs/callback/signal.h @@ -33,6 +33,7 @@ extern "C" { struct signal_handler; typedef struct signal_handler signal_handler_t; +typedef void (*global_signal_callback_t)(void*, const char*, calldata_t*); typedef void (*signal_callback_t)(void*, calldata_t*); EXPORT signal_handler_t *signal_handler_create(void); @@ -60,6 +61,13 @@ EXPORT void signal_handler_connect(signal_handler_t *handler, EXPORT void signal_handler_disconnect(signal_handler_t *handler, const char *signal, signal_callback_t callback, void *data); +EXPORT void signal_handler_connect_global(signal_handler_t *handler, + global_signal_callback_t callback, void *data); +EXPORT void signal_handler_disconnect_global(signal_handler_t *handler, + global_signal_callback_t callback, void *data); + +EXPORT void signal_handler_remove_current(void); + EXPORT void signal_handler_signal(signal_handler_t *handler, const char *signal, calldata_t *params); diff --git a/libobs/data/format_conversion.effect b/libobs/data/format_conversion.effect index 6af8b08..edf1a9e 100644 --- a/libobs/data/format_conversion.effect +++ b/libobs/data/format_conversion.effect @@ -172,10 +172,25 @@ float4 PSPlanar420(VertInOut vert_in) : TARGET ch_u += width_i; ch_v += height_i; + /* set up coordinates for next chroma line, in case + * (width / 2) % 4 == 2, i.e. the current set of 4 pixels is split + * between the current and the next chroma line; do note that the next + * chroma line is two source lines below the current source line */ + float ch_u_n = 0. + width_i; + float ch_v_n = ch_v + height_i * 3; + sample_pos[0] = float2(ch_u, ch_v); sample_pos[1] = float2(ch_u += width_i2, ch_v); - sample_pos[2] = float2(ch_u += width_i2, ch_v); - sample_pos[3] = float2(ch_u + width_i2, ch_v); + + ch_u += width_i2; + // check if ch_u overflowed the current source and chroma line + if (ch_u > 1.0) { + sample_pos[2] = float2(ch_u_n, ch_v_n); + sample_pos[2] = float2(ch_u_n + width_i2, ch_v_n); + } else { + sample_pos[2] = float2(ch_u, ch_v); + sample_pos[3] = float2(ch_u + width_i2, ch_v); + } } float4x4 out_val = float4x4( diff --git a/libobs/graphics/effect.c b/libobs/graphics/effect.c index 737391d..4b2a716 100644 --- a/libobs/graphics/effect.c +++ b/libobs/graphics/effect.c @@ -368,6 +368,13 @@ void gs_effect_set_vec4(gs_eparam_t *param, const struct vec4 *val) effect_setval_inline(param, val, sizeof(struct vec4)); } +void gs_effect_set_color(gs_eparam_t *param, uint32_t argb) +{ + struct vec4 v_color; + vec4_from_bgra(&v_color, argb); + effect_setval_inline(param, &v_color, sizeof(struct vec4)); +} + void gs_effect_set_texture(gs_eparam_t *param, gs_texture_t *val) { effect_setval_inline(param, &val, sizeof(gs_texture_t*)); diff --git a/libobs/graphics/graphics-ffmpeg.c b/libobs/graphics/graphics-ffmpeg.c index 5b334d4..cbad19b 100644 --- a/libobs/graphics/graphics-ffmpeg.c +++ b/libobs/graphics/graphics-ffmpeg.c @@ -164,8 +164,19 @@ static bool ffmpeg_image_decode(struct ffmpeg_image *info, uint8_t *out, } while (!got_frame) { +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) + ret = avcodec_send_packet(info->decoder_ctx, &packet); + if (ret == 0) + ret = avcodec_receive_frame(info->decoder_ctx, frame); + + got_frame = (ret == 0); + + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) + ret = 0; +#else ret = avcodec_decode_video2(info->decoder_ctx, frame, &got_frame, &packet); +#endif if (ret < 0) { blog(LOG_WARNING, "Failed to decode frame for '%s': %s", info->file, av_err2str(ret)); @@ -176,7 +187,7 @@ static bool ffmpeg_image_decode(struct ffmpeg_image *info, uint8_t *out, success = ffmpeg_image_reformat_frame(info, frame, out, linesize); fail: - av_free_packet(&packet); + av_packet_unref(&packet); av_frame_free(&frame); return success; } diff --git a/libobs/graphics/graphics-imports.c b/libobs/graphics/graphics-imports.c index 6d00b9a..63c19ab 100644 --- a/libobs/graphics/graphics-imports.c +++ b/libobs/graphics/graphics-imports.c @@ -141,10 +141,12 @@ bool load_graphics_imports(struct gs_exports *exports, void *module, GRAPHICS_IMPORT(gs_vertexbuffer_destroy); GRAPHICS_IMPORT(gs_vertexbuffer_flush); + GRAPHICS_IMPORT(gs_vertexbuffer_flush_direct); GRAPHICS_IMPORT(gs_vertexbuffer_get_data); GRAPHICS_IMPORT(gs_indexbuffer_destroy); GRAPHICS_IMPORT(gs_indexbuffer_flush); + GRAPHICS_IMPORT(gs_indexbuffer_flush_direct); GRAPHICS_IMPORT(gs_indexbuffer_get_data); GRAPHICS_IMPORT(gs_indexbuffer_get_num_indices); GRAPHICS_IMPORT(gs_indexbuffer_get_type); diff --git a/libobs/graphics/graphics-internal.h b/libobs/graphics/graphics-internal.h index f009cc7..6be06d5 100644 --- a/libobs/graphics/graphics-internal.h +++ b/libobs/graphics/graphics-internal.h @@ -189,11 +189,15 @@ struct gs_exports { void (*gs_vertexbuffer_destroy)(gs_vertbuffer_t *vertbuffer); void (*gs_vertexbuffer_flush)(gs_vertbuffer_t *vertbuffer); + void (*gs_vertexbuffer_flush_direct)(gs_vertbuffer_t *vertbuffer, + const struct gs_vb_data *data); struct gs_vb_data *(*gs_vertexbuffer_get_data)( const gs_vertbuffer_t *vertbuffer); void (*gs_indexbuffer_destroy)(gs_indexbuffer_t *indexbuffer); void (*gs_indexbuffer_flush)(gs_indexbuffer_t *indexbuffer); + void (*gs_indexbuffer_flush_direct)(gs_indexbuffer_t *indexbuffer, + const void *data); void *(*gs_indexbuffer_get_data)(const gs_indexbuffer_t *indexbuffer); size_t (*gs_indexbuffer_get_num_indices)( const gs_indexbuffer_t *indexbuffer); diff --git a/libobs/graphics/graphics-magick.c b/libobs/graphics/graphics-magick.c index c106bfa..6e4e1e9 100644 --- a/libobs/graphics/graphics-magick.c +++ b/libobs/graphics/graphics-magick.c @@ -1,8 +1,14 @@ #include "graphics.h" +#include "obsconfig.h" #define MAGICKCORE_QUANTUM_DEPTH 16 #define MAGICKCORE_HDRI_ENABLE 0 + +#if LIBOBS_IMAGEMAGICK_DIR_STYLE == LIBOBS_IMAGEMAGICK_DIR_STYLE_6L #include +#elif LIBOBS_IMAGEMAGICK_DIR_STYLE == LIBOBS_IMAGEMAGICK_DIR_STYLE_7GE +#include +#endif void gs_init_image_deps() { diff --git a/libobs/graphics/graphics.c b/libobs/graphics/graphics.c index aa886fb..b6f5bd0 100644 --- a/libobs/graphics/graphics.c +++ b/libobs/graphics/graphics.c @@ -28,11 +28,7 @@ #include "effect-parser.h" #include "effect.h" -#ifdef _MSC_VER -static __declspec(thread) graphics_t *thread_graphics = NULL; -#else /* assume GCC or that other compiler we dare not mention */ -static __thread graphics_t *thread_graphics = NULL; -#endif +static THREAD_LOCAL graphics_t *thread_graphics = NULL; static inline bool gs_obj_valid(const void *obj, const char *f, const char *name) @@ -1468,6 +1464,46 @@ gs_vertbuffer_t *gs_vertexbuffer_create(struct gs_vb_data *data, if (!gs_valid("gs_vertexbuffer_create")) return NULL; + if (data && data->num && (flags & GS_DUP_BUFFER) != 0) { + struct gs_vb_data *new_data = gs_vbdata_create(); + + new_data->num = data->num; + +#define DUP_VAL(val) \ + do { \ + if (data->val) \ + new_data->val = bmemdup(data->val, \ + sizeof(*data->val) * \ + data->num); \ + } while (false) + + DUP_VAL(points); + DUP_VAL(normals); + DUP_VAL(tangents); + DUP_VAL(colors); +#undef DUP_VAL + + if (data->tvarray && data->num_tex) { + new_data->num_tex = data->num_tex; + new_data->tvarray = bzalloc( + sizeof(struct gs_tvertarray) * + data->num_tex); + + for (size_t i = 0; i < data->num_tex; i++) { + struct gs_tvertarray *tv = &data->tvarray[i]; + struct gs_tvertarray *new_tv = + &new_data->tvarray[i]; + size_t size = tv->width * sizeof(float); + + new_tv->width = tv->width; + new_tv->array = bmemdup(tv->array, + size * data->num); + } + } + + data = new_data; + } + return graphics->exports.device_vertexbuffer_create(graphics->device, data, flags); } @@ -1480,6 +1516,13 @@ gs_indexbuffer_t *gs_indexbuffer_create(enum gs_index_type type, if (!gs_valid("gs_indexbuffer_create")) return NULL; + if (indices && num && (flags & GS_DUP_BUFFER) != 0) { + size_t size = type == GS_UNSIGNED_SHORT + ? sizeof(unsigned short) + : sizeof(unsigned long); + indices = bmemdup(indices, size * num); + } + return graphics->exports.device_indexbuffer_create(graphics->device, type, indices, num, flags); } @@ -2430,6 +2473,16 @@ void gs_vertexbuffer_flush(gs_vertbuffer_t *vertbuffer) thread_graphics->exports.gs_vertexbuffer_flush(vertbuffer); } +void gs_vertexbuffer_flush_direct(gs_vertbuffer_t *vertbuffer, + const struct gs_vb_data *data) +{ + if (!gs_valid_p2("gs_vertexbuffer_flush_direct", vertbuffer, data)) + return; + + thread_graphics->exports.gs_vertexbuffer_flush_direct(vertbuffer, + data); +} + struct gs_vb_data *gs_vertexbuffer_get_data(const gs_vertbuffer_t *vertbuffer) { if (!gs_valid_p("gs_vertexbuffer_get_data", vertbuffer)) @@ -2458,6 +2511,15 @@ void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer) thread_graphics->exports.gs_indexbuffer_flush(indexbuffer); } +void gs_indexbuffer_flush_direct(gs_indexbuffer_t *indexbuffer, + const void *data) +{ + if (!gs_valid_p2("gs_indexbuffer_flush_direct", indexbuffer, data)) + return; + + thread_graphics->exports.gs_indexbuffer_flush_direct(indexbuffer, data); +} + void *gs_indexbuffer_get_data(const gs_indexbuffer_t *indexbuffer) { if (!gs_valid_p("gs_indexbuffer_get_data", indexbuffer)) diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h index ff11661..df9583f 100644 --- a/libobs/graphics/graphics.h +++ b/libobs/graphics/graphics.h @@ -291,6 +291,7 @@ enum gs_shader_param_type { GS_SHADER_PARAM_TEXTURE, }; +#ifndef SWIG struct gs_shader_param_info { enum gs_shader_param_type type; const char *name; @@ -327,6 +328,7 @@ EXPORT void gs_shader_set_val(gs_sparam_t *param, const void *val, size_t size); EXPORT void gs_shader_set_default(gs_sparam_t *param); EXPORT void gs_shader_set_next_sampler(gs_sparam_t *param, gs_samplerstate_t *sampler); +#endif /* --------------------------------------------------- * effect functions @@ -340,6 +342,7 @@ EXPORT void gs_shader_set_next_sampler(gs_sparam_t *param, GS_EFFECT_TEXTURE };*/ +#ifndef SWIG struct gs_effect_param_info { const char *name; enum gs_shader_param_type type; @@ -349,6 +352,7 @@ struct gs_effect_param_info { float min, max, inc, mul; */ }; +#endif EXPORT void gs_effect_destroy(gs_effect_t *effect); @@ -382,8 +386,11 @@ EXPORT void gs_effect_update_params(gs_effect_t *effect); EXPORT gs_eparam_t *gs_effect_get_viewproj_matrix(const gs_effect_t *effect); EXPORT gs_eparam_t *gs_effect_get_world_matrix(const gs_effect_t *effect); +#ifndef SWIG EXPORT void gs_effect_get_param_info(const gs_eparam_t *param, struct gs_effect_param_info *info); +#endif + EXPORT void gs_effect_set_bool(gs_eparam_t *param, bool val); EXPORT void gs_effect_set_float(gs_eparam_t *param, float val); EXPORT void gs_effect_set_int(gs_eparam_t *param, int val); @@ -398,6 +405,8 @@ EXPORT void gs_effect_set_default(gs_eparam_t *param); EXPORT void gs_effect_set_next_sampler(gs_eparam_t *param, gs_samplerstate_t *sampler); +EXPORT void gs_effect_set_color(gs_eparam_t *param, uint32_t argb); + /* --------------------------------------------------- * texture render helper functions * --------------------------------------------------- */ @@ -419,6 +428,8 @@ EXPORT gs_texture_t *gs_texrender_get_texture(const gs_texrender_t *texrender); #define GS_DYNAMIC (1<<1) #define GS_RENDER_TARGET (1<<2) #define GS_GL_DUMMYTEX (1<<3) /**<< texture with no allocated texture data */ +#define GS_DUP_BUFFER (1<<4) /**<< do not pass buffer ownership when + * creating a vertex/index buffer */ /* ---------------- */ /* global functions */ @@ -716,11 +727,15 @@ EXPORT void gs_samplerstate_destroy(gs_samplerstate_t *samplerstate); EXPORT void gs_vertexbuffer_destroy(gs_vertbuffer_t *vertbuffer); EXPORT void gs_vertexbuffer_flush(gs_vertbuffer_t *vertbuffer); +EXPORT void gs_vertexbuffer_flush_direct(gs_vertbuffer_t *vertbuffer, + const struct gs_vb_data *data); EXPORT struct gs_vb_data *gs_vertexbuffer_get_data( const gs_vertbuffer_t *vertbuffer); EXPORT void gs_indexbuffer_destroy(gs_indexbuffer_t *indexbuffer); EXPORT void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer); +EXPORT void gs_indexbuffer_flush_direct(gs_indexbuffer_t *indexbuffer, + const void *data); EXPORT void *gs_indexbuffer_get_data(const gs_indexbuffer_t *indexbuffer); EXPORT size_t gs_indexbuffer_get_num_indices( const gs_indexbuffer_t *indexbuffer); diff --git a/libobs/graphics/image-file.c b/libobs/graphics/image-file.c index b68539d..4262c0b 100644 --- a/libobs/graphics/image-file.c +++ b/libobs/graphics/image-file.c @@ -64,7 +64,7 @@ static bool init_animated_gif(gs_image_file_t *image, const char *path) bool is_animated_gif = true; gif_result result; uint64_t max_size; - size_t size; + size_t size, size_read; FILE *file; image->bitmap_callbacks.bitmap_create = bi_def_bitmap_create; @@ -87,7 +87,11 @@ static bool init_animated_gif(gs_image_file_t *image, const char *path) fseek(file, 0, SEEK_SET); image->gif_data = bmalloc(size); - fread(image->gif_data, 1, size, file); + size_read = fread(image->gif_data, 1, size, file); + if (size_read != size) { + blog(LOG_WARNING, "Failed to fully read gif file '%s'.", path); + goto fail; + } do { result = gif_initialise(&image->gif, size, image->gif_data); diff --git a/libobs/media-io/audio-io.h b/libobs/media-io/audio-io.h index 407749e..9b4b636 100644 --- a/libobs/media-io/audio-io.h +++ b/libobs/media-io/audio-io.h @@ -26,9 +26,13 @@ extern "C" { #endif #define MAX_AUDIO_MIXES 6 -#define MAX_AUDIO_CHANNELS 2 +#define MAX_AUDIO_CHANNELS 8 #define AUDIO_OUTPUT_FRAMES 1024 +#define TOTAL_AUDIO_SIZE \ + (MAX_AUDIO_MIXES * MAX_AUDIO_CHANNELS * \ + AUDIO_OUTPUT_FRAMES * sizeof(float)) + /* * Base audio output component. Use this to create an audio output track * for the media. @@ -51,18 +55,24 @@ enum audio_format { AUDIO_FORMAT_FLOAT_PLANAR, }; +/** + * The speaker layout describes where the speakers are located in the room. + * For OBS it dictates: + * * how many channels are available and + * * which channels are used for which speakers. + * + * Standard channel layouts where retrieved from ffmpeg documentation at: + * https://trac.ffmpeg.org/wiki/AudioChannelManipulation + */ enum speaker_layout { - SPEAKERS_UNKNOWN, - SPEAKERS_MONO, - SPEAKERS_STEREO, - SPEAKERS_2POINT1, - SPEAKERS_QUAD, - SPEAKERS_4POINT1, - SPEAKERS_5POINT1, - SPEAKERS_5POINT1_SURROUND, - SPEAKERS_7POINT1, - SPEAKERS_7POINT1_SURROUND, - SPEAKERS_SURROUND, + SPEAKERS_UNKNOWN, /**< Unknown setting, fallback is stereo. */ + SPEAKERS_MONO, /**< Channels: MONO */ + SPEAKERS_STEREO, /**< Channels: FL, FR */ + SPEAKERS_2POINT1, /**< Channels: FL, FR, LFE */ + SPEAKERS_4POINT0, /**< Channels: FL, FR, FC, RC */ + SPEAKERS_4POINT1, /**< Channels: FL, FR, FC, LFE, RC */ + SPEAKERS_5POINT1, /**< Channels: FL, FR, FC, LFE, RL, RR */ + SPEAKERS_7POINT1=8, /**< Channels: FL, FR, FC, LFE, RL, RR, SL, SR */ }; struct audio_data { @@ -102,13 +112,10 @@ static inline uint32_t get_audio_channels(enum speaker_layout speakers) case SPEAKERS_MONO: return 1; case SPEAKERS_STEREO: return 2; case SPEAKERS_2POINT1: return 3; - case SPEAKERS_SURROUND: - case SPEAKERS_QUAD: return 4; + case SPEAKERS_4POINT0: return 4; case SPEAKERS_4POINT1: return 5; - case SPEAKERS_5POINT1: - case SPEAKERS_5POINT1_SURROUND: return 6; + case SPEAKERS_5POINT1: return 6; case SPEAKERS_7POINT1: return 8; - case SPEAKERS_7POINT1_SURROUND: return 8; case SPEAKERS_UNKNOWN: return 0; } diff --git a/libobs/media-io/audio-resampler-ffmpeg.c b/libobs/media-io/audio-resampler-ffmpeg.c index cd27a88..7680125 100644 --- a/libobs/media-io/audio-resampler-ffmpeg.c +++ b/libobs/media-io/audio-resampler-ffmpeg.c @@ -63,14 +63,11 @@ static inline uint64_t convert_speaker_layout(enum speaker_layout layout) case SPEAKERS_UNKNOWN: return 0; case SPEAKERS_MONO: return AV_CH_LAYOUT_MONO; case SPEAKERS_STEREO: return AV_CH_LAYOUT_STEREO; - case SPEAKERS_2POINT1: return AV_CH_LAYOUT_2_1; - case SPEAKERS_QUAD: return AV_CH_LAYOUT_QUAD; + case SPEAKERS_2POINT1: return AV_CH_LAYOUT_SURROUND; + case SPEAKERS_4POINT0: return AV_CH_LAYOUT_4POINT0; case SPEAKERS_4POINT1: return AV_CH_LAYOUT_4POINT1; - case SPEAKERS_5POINT1: return AV_CH_LAYOUT_5POINT1; - case SPEAKERS_5POINT1_SURROUND: return AV_CH_LAYOUT_5POINT1_BACK; + case SPEAKERS_5POINT1: return AV_CH_LAYOUT_5POINT1_BACK; case SPEAKERS_7POINT1: return AV_CH_LAYOUT_7POINT1; - case SPEAKERS_7POINT1_SURROUND: return AV_CH_LAYOUT_7POINT1_WIDE_BACK; - case SPEAKERS_SURROUND: return AV_CH_LAYOUT_SURROUND; } /* shouldn't get here */ diff --git a/libobs/media-io/format-conversion.c b/libobs/media-io/format-conversion.c index 99f366c..1c6d341 100644 --- a/libobs/media-io/format-conversion.c +++ b/libobs/media-io/format-conversion.c @@ -208,7 +208,7 @@ void decompress_420( uint8_t *output, uint32_t out_linesize) { uint32_t start_y_d2 = start_y/2; - uint32_t width_d2 = min_uint32(in_linesize[0], out_linesize)/2; + uint32_t width_d2 = in_linesize[0]/2; uint32_t height_d2 = end_y/2; uint32_t y; @@ -221,18 +221,18 @@ void decompress_420( lum0 = input[0] + y * 2 * in_linesize[0]; lum1 = lum0 + in_linesize[0]; - output0 = (uint32_t*)(output + y * 2 * in_linesize[0]); - output1 = (uint32_t*)((uint8_t*)output0 + in_linesize[0]); + output0 = (uint32_t*)(output + y * 2 * out_linesize); + output1 = (uint32_t*)((uint8_t*)output0 + out_linesize); for (x = 0; x < width_d2; x++) { uint32_t out; - out = (*(chroma0++) << 8) | (*(chroma1++) << 16); + out = (*(chroma0++) << 8) | *(chroma1++); - *(output0++) = *(lum0++) | out; - *(output0++) = *(lum0++) | out; + *(output0++) = (*(lum0++) << 16) | out; + *(output0++) = (*(lum0++) << 16) | out; - *(output1++) = *(lum1++) | out; - *(output1++) = *(lum1++) | out; + *(output1++) = (*(lum1++) << 16) | out; + *(output1++) = (*(lum1++) << 16) | out; } } } diff --git a/libobs/media-io/media-io-defs.h b/libobs/media-io/media-io-defs.h index f0be367..28c970f 100644 --- a/libobs/media-io/media-io-defs.h +++ b/libobs/media-io/media-io-defs.h @@ -18,7 +18,3 @@ #pragma once #define MAX_AV_PLANES 8 - -/* time threshold in nanoseconds to ensure audio timing is as seamless as - * possible */ -#define TS_SMOOTHING_THRESHOLD 70000000ULL diff --git a/libobs/media-io/media-remux.c b/libobs/media-io/media-remux.c index a6652ee..fc19bb5 100644 --- a/libobs/media-io/media-remux.c +++ b/libobs/media-io/media-remux.c @@ -26,6 +26,12 @@ #include #include +#if LIBAVCODEC_VERSION_MAJOR >= 58 +#define CODEC_FLAG_GLOBAL_H AV_CODEC_FLAG_GLOBAL_HEADER +#else +#define CODEC_FLAG_GLOBAL_H CODEC_FLAG_GLOBAL_HEADER +#endif + struct media_remux_job { int64_t in_size; AVFormatContext *ifmt_ctx, *ofmt_ctx; @@ -86,7 +92,17 @@ static inline bool init_output(media_remux_job_t job, const char *out_filename) return false; } +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101) + AVCodecParameters *par = avcodec_parameters_alloc(); + ret = avcodec_parameters_from_context(par, in_stream->codec); + if (ret == 0) + ret = avcodec_parameters_to_context(out_stream->codec, + par); + avcodec_parameters_free(&par); +#else ret = avcodec_copy_context(out_stream->codec, in_stream->codec); +#endif + if (ret < 0) { blog(LOG_ERROR, "media_remux: Failed to copy context"); return false; @@ -95,7 +111,7 @@ static inline bool init_output(media_remux_job_t job, const char *out_filename) out_stream->codec->codec_tag = 0; if (job->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) - out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER; + out_stream->codec->flags |= CODEC_FLAG_GLOBAL_H; } #ifndef _NDEBUG @@ -188,7 +204,7 @@ static inline int process_packets(media_remux_job_t job, job->ofmt_ctx->streams[pkt.stream_index]); ret = av_interleaved_write_frame(job->ofmt_ctx, &pkt); - av_free_packet(&pkt); + av_packet_unref(&pkt); if (ret < 0) { blog(LOG_ERROR, "media_remux: Error muxing packet: %s", diff --git a/libobs/media-io/video-io.c b/libobs/media-io/video-io.c index b33ef91..f490ceb 100644 --- a/libobs/media-io/video-io.c +++ b/libobs/media-io/video-io.c @@ -302,6 +302,8 @@ static inline bool video_input_init(struct video_input *input, .format = video->info.format, .width = video->info.width, .height = video->info.height, + .range = video->info.range, + .colorspace = video->info.colorspace }; int ret = video_scaler_create(&input->scaler, diff --git a/libobs/obs-audio-controls.c b/libobs/obs-audio-controls.c index cae7c9e..4b0c79b 100644 --- a/libobs/obs-audio-controls.c +++ b/libobs/obs-audio-controls.c @@ -32,6 +32,8 @@ along with this program. If not, see . #pragma warning(disable : 4756) #endif +#define CLAMP(x, min, max) ((x) < min ? min : ((x) > max ? max : (x))) + typedef float (*obs_fader_conversion_t)(const float val); struct fader_cb { @@ -61,8 +63,6 @@ struct meter_cb { struct obs_volmeter { pthread_mutex_t mutex; - obs_fader_conversion_t pos_to_db; - obs_fader_conversion_t db_to_pos; obs_source_t *source; enum obs_fader_type type; float cur_db; @@ -70,20 +70,10 @@ struct obs_volmeter { pthread_mutex_t callback_mutex; DARRAY(struct meter_cb)callbacks; - unsigned int channels; unsigned int update_ms; - unsigned int update_frames; - unsigned int peakhold_ms; - unsigned int peakhold_frames; - unsigned int peakhold_count; - unsigned int ival_frames; - float ival_sum; - float ival_max; - - float vol_peak; - float vol_mag; - float vol_max; + float vol_magnitude[MAX_AUDIO_CHANNELS]; + float vol_peak[MAX_AUDIO_CHANNELS]; }; static float cubic_def_to_db(const float def) @@ -205,13 +195,14 @@ static void signal_volume_changed(struct obs_fader *fader, const float db) } static void signal_levels_updated(struct obs_volmeter *volmeter, - const float level, const float magnitude, const float peak, - bool muted) + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float input_peak[MAX_AUDIO_CHANNELS]) { pthread_mutex_lock(&volmeter->callback_mutex); for (size_t i = volmeter->callbacks.num; i > 0; i--) { struct meter_cb cb = volmeter->callbacks.array[i - 1]; - cb.callback(cb.param, level, magnitude, peak, muted); + cb.callback(cb.param, magnitude, peak, input_peak); } pthread_mutex_unlock(&volmeter->callback_mutex); } @@ -265,145 +256,84 @@ static void volmeter_source_destroyed(void *vptr, calldata_t *calldata) obs_volmeter_detach_source(volmeter); } -/* TODO: Separate for individual channels */ -static void volmeter_sum_and_max(float *data[MAX_AV_PLANES], size_t frames, - float *sum, float *max) -{ - float s = *sum; - float m = *max; - - for (size_t plane = 0; plane < MAX_AV_PLANES; plane++) { - if (!data[plane]) - break; - - for (float *c = data[plane]; c < data[plane] + frames; ++c) { - const float pow = *c * *c; - s += pow; - m = (m > pow) ? m : pow; - } - } - - *sum = s; - *max = m; -} - -/** - * @todo The IIR low pass filter has a different behavior depending on the - * update interval and sample rate, it should be replaced with something - * that is independent from both. - */ -static void volmeter_calc_ival_levels(obs_volmeter_t *volmeter) -{ - const unsigned int samples = volmeter->ival_frames * volmeter->channels; - const float alpha = 0.15f; - const float ival_max = sqrtf(volmeter->ival_max); - const float ival_rms = sqrtf(volmeter->ival_sum / (float)samples); - - if (ival_max > volmeter->vol_max) { - volmeter->vol_max = ival_max; - } else { - volmeter->vol_max = alpha * volmeter->vol_max + - (1.0f - alpha) * ival_max; - } - - if (volmeter->vol_max > volmeter->vol_peak || - volmeter->peakhold_count > volmeter->peakhold_frames) { - volmeter->vol_peak = volmeter->vol_max; - volmeter->peakhold_count = 0; - } else { - volmeter->peakhold_count += volmeter->ival_frames; - } - - volmeter->vol_mag = alpha * ival_rms + - volmeter->vol_mag * (1.0f - alpha); - - /* reset interval data */ - volmeter->ival_frames = 0; - volmeter->ival_sum = 0.0f; - volmeter->ival_max = 0.0f; -} - -static bool volmeter_process_audio_data(obs_volmeter_t *volmeter, +static void volmeter_process_audio_data(obs_volmeter_t *volmeter, const struct audio_data *data) { - bool updated = false; - size_t frames = 0; - size_t left = data->frames; - float *adata[MAX_AV_PLANES]; + int nr_samples = data->frames; + int channel_nr = 0; - for (size_t i = 0; i < MAX_AV_PLANES; i++) - adata[i] = (float*)data->data[i]; - - while (left) { - frames = (volmeter->ival_frames + left > - volmeter->update_frames) - ? volmeter->update_frames - volmeter->ival_frames - : left; - - volmeter_sum_and_max(adata, frames, &volmeter->ival_sum, - &volmeter->ival_max); - - volmeter->ival_frames += (unsigned int)frames; - left -= frames; - - for (size_t i = 0; i < MAX_AV_PLANES; i++) { - if (!adata[i]) - break; - adata[i] += frames; + for (size_t plane_nr = 0; plane_nr < MAX_AV_PLANES; plane_nr++) { + float *samples = (float *)data->data[plane_nr]; + if (!samples) { + // This plane does not contain data. + continue; } - /* break if we did not reach the end of the interval */ - if (volmeter->ival_frames != volmeter->update_frames) - break; + // For each plane calculate: + // * peak = the maximum-absolute of the sample values. + // * magnitude = root-mean-square of the sample values. + // A VU meter needs to integrate over 300ms, but this will + // be handled by the ballistics of the meter itself, + // reality. Which makes this calculation independent of + // sample rate or update rate. + float peak = 0.0; + float sum_of_squares = 0.0; + for (int sample_nr = 0; sample_nr < nr_samples; sample_nr++) { + float sample = samples[sample_nr]; - volmeter_calc_ival_levels(volmeter); - updated = true; + peak = fmaxf(peak, fabsf(sample)); + sum_of_squares += (sample * sample); + } + + volmeter->vol_magnitude[channel_nr] = sqrtf(sum_of_squares / + nr_samples); + volmeter->vol_peak[channel_nr] = peak; + channel_nr++; } - return updated; + // Clear audio channels that are not in use. + for (; channel_nr < MAX_AUDIO_CHANNELS; channel_nr++) { + volmeter->vol_magnitude[channel_nr] = 0.0; + volmeter->vol_peak[channel_nr] = 0.0; + } } static void volmeter_source_data_received(void *vptr, obs_source_t *source, const struct audio_data *data, bool muted) { struct obs_volmeter *volmeter = (struct obs_volmeter *) vptr; - bool updated = false; - float mul, level, mag, peak; + float mul; + float magnitude[MAX_AUDIO_CHANNELS]; + float peak[MAX_AUDIO_CHANNELS]; + float input_peak[MAX_AUDIO_CHANNELS]; pthread_mutex_lock(&volmeter->mutex); - updated = volmeter_process_audio_data(volmeter, data); + volmeter_process_audio_data(volmeter, data); - if (updated) { - mul = db_to_mul(volmeter->cur_db); - - level = volmeter->db_to_pos(mul_to_db(volmeter->vol_max * mul)); - mag = volmeter->db_to_pos(mul_to_db(volmeter->vol_mag * mul)); - peak = volmeter->db_to_pos( - mul_to_db(volmeter->vol_peak * mul)); + // Adjust magnitude/peak based on the volume level set by the user. + // And convert to dB. + mul = muted ? 0.0f : db_to_mul(volmeter->cur_db); + for (int channel_nr = 0; channel_nr < MAX_AUDIO_CHANNELS; + channel_nr++) { + magnitude[channel_nr] = mul_to_db( + volmeter->vol_magnitude[channel_nr] * mul); + peak[channel_nr] = mul_to_db( + volmeter->vol_peak[channel_nr] * mul); + input_peak[channel_nr] = mul_to_db( + volmeter->vol_peak[channel_nr]); } + // The input-peak is NOT adjusted with volume, so that the user + // can check the input-gain. + pthread_mutex_unlock(&volmeter->mutex); - if (updated) - signal_levels_updated(volmeter, level, mag, peak, muted); + signal_levels_updated(volmeter, magnitude, peak, input_peak); UNUSED_PARAMETER(source); } -static void volmeter_update_audio_settings(obs_volmeter_t *volmeter) -{ - audio_t *audio = obs_get_audio(); - const unsigned int sr = audio_output_get_sample_rate(audio); - uint32_t channels = (uint32_t)audio_output_get_channels(audio); - - pthread_mutex_lock(&volmeter->mutex); - volmeter->channels = channels; - volmeter->update_frames = volmeter->update_ms * sr / 1000; - volmeter->peakhold_frames = volmeter->peakhold_ms * sr / 1000; - pthread_mutex_unlock(&volmeter->mutex); -} - obs_fader_t *obs_fader_create(enum obs_fader_type type) { struct obs_fader *fader = bzalloc(sizeof(struct obs_fader)); @@ -634,28 +564,9 @@ obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type) if (pthread_mutex_init(&volmeter->callback_mutex, NULL) != 0) goto fail; - /* set conversion functions */ - switch(type) { - case OBS_FADER_CUBIC: - volmeter->pos_to_db = cubic_def_to_db; - volmeter->db_to_pos = cubic_db_to_def; - break; - case OBS_FADER_IEC: - volmeter->pos_to_db = iec_def_to_db; - volmeter->db_to_pos = iec_db_to_def; - break; - case OBS_FADER_LOG: - volmeter->pos_to_db = log_def_to_db; - volmeter->db_to_pos = log_db_to_def; - break; - default: - goto fail; - break; - } volmeter->type = type; obs_volmeter_set_update_interval(volmeter, 50); - obs_volmeter_set_peak_hold(volmeter, 1500); return volmeter; fail: @@ -739,8 +650,6 @@ void obs_volmeter_set_update_interval(obs_volmeter_t *volmeter, pthread_mutex_lock(&volmeter->mutex); volmeter->update_ms = ms; pthread_mutex_unlock(&volmeter->mutex); - - volmeter_update_audio_settings(volmeter); } unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter) @@ -755,28 +664,26 @@ unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter) return interval; } -void obs_volmeter_set_peak_hold(obs_volmeter_t *volmeter, const unsigned int ms) +int obs_volmeter_get_nr_channels(obs_volmeter_t *volmeter) { - if (!volmeter) - return; + int source_nr_audio_channels; + int obs_nr_audio_channels; - pthread_mutex_lock(&volmeter->mutex); - volmeter->peakhold_ms = ms; - pthread_mutex_unlock(&volmeter->mutex); + if (volmeter->source) { + source_nr_audio_channels = get_audio_channels( + volmeter->source->sample_info.speakers); + } else { + source_nr_audio_channels = 1; + } - volmeter_update_audio_settings(volmeter); -} + struct obs_audio_info audio_info; + if (obs_get_audio_info(&audio_info)) { + obs_nr_audio_channels = get_audio_channels(audio_info.speakers); + } else { + obs_nr_audio_channels = 2; + } -unsigned int obs_volmeter_get_peak_hold(obs_volmeter_t *volmeter) -{ - if (!volmeter) - return 0; - - pthread_mutex_lock(&volmeter->mutex); - const unsigned int peakhold = volmeter->peakhold_ms; - pthread_mutex_unlock(&volmeter->mutex); - - return peakhold; + return CLAMP(source_nr_audio_channels, 1, obs_nr_audio_channels); } void obs_volmeter_add_callback(obs_volmeter_t *volmeter, @@ -804,3 +711,4 @@ void obs_volmeter_remove_callback(obs_volmeter_t *volmeter, da_erase_item(volmeter->callbacks, &cb); pthread_mutex_unlock(&volmeter->callback_mutex); } + diff --git a/libobs/obs-audio-controls.h b/libobs/obs-audio-controls.h index fbf7481..158af7b 100644 --- a/libobs/obs-audio-controls.h +++ b/libobs/obs-audio-controls.h @@ -229,22 +229,15 @@ EXPORT void obs_volmeter_set_update_interval(obs_volmeter_t *volmeter, EXPORT unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter); /** - * @brief Set the peak hold time for the volume meter + * @brief Get the number of channels which are configured for this source. * @param volmeter pointer to the volume meter object - * @param ms peak hold time in ms */ -EXPORT void obs_volmeter_set_peak_hold(obs_volmeter_t *volmeter, - const unsigned int ms); +EXPORT int obs_volmeter_get_nr_channels(obs_volmeter_t *volmeter); -/** - * @brief Get the peak hold time for the volume meter - * @param volmeter pointer to the volume meter object - * @return the peak hold time in ms - */ -EXPORT unsigned int obs_volmeter_get_peak_hold(obs_volmeter_t *volmeter); - -typedef void (*obs_volmeter_updated_t)(void *param, float level, - float magnitude, float peak, float muted); +typedef void (*obs_volmeter_updated_t)(void *param, + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float input_peak[MAX_AUDIO_CHANNELS]); EXPORT void obs_volmeter_add_callback(obs_volmeter_t *volmeter, obs_volmeter_updated_t callback, void *param); diff --git a/libobs/obs-config.h b/libobs/obs-config.h index 505f839..e278702 100644 --- a/libobs/obs-config.h +++ b/libobs/obs-config.h @@ -27,7 +27,7 @@ /* * Increment if major breaking API changes */ -#define LIBOBS_API_MAJOR_VER 19 +#define LIBOBS_API_MAJOR_VER 21 /* * Increment if backward-compatible additions @@ -41,7 +41,7 @@ * * Reset to zero each major or minor version */ -#define LIBOBS_API_PATCH_VER 3 +#define LIBOBS_API_PATCH_VER 2 #define MAKE_SEMANTIC_VERSION(major, minor, patch) \ ((major << 24) | \ diff --git a/libobs/obs-data.c b/libobs/obs-data.c index efd406a..870de33 100644 --- a/libobs/obs-data.c +++ b/libobs/obs-data.c @@ -854,7 +854,7 @@ static inline void set_item_def(struct obs_data *data, obs_data_item_t **item, item = &actual_item; } - if (item && *item && (*item)->type == type) + if (item && *item && (*item)->type != type) return; set_item_data(data, item, name, ptr, size, type, true, false); diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index 4fd84bb..e4d1320 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -611,7 +611,7 @@ uint32_t obs_encoder_get_height(const obs_encoder_t *encoder) if (!encoder->media) return 0; - return encoder->scaled_width != 0 ? + return encoder->scaled_height != 0 ? encoder->scaled_height : video_output_get_height(encoder->media); } @@ -952,7 +952,6 @@ static bool buffer_audio(struct obs_encoder *encoder, struct audio_data *data) /* use currently buffered audio instead */ if (v_start_ts < data->timestamp) { start_from_buffer(encoder, v_start_ts); - goto skip_push; } } else if (!encoder->start_ts && !encoder->paired_encoder) { @@ -962,7 +961,6 @@ static bool buffer_audio(struct obs_encoder *encoder, struct audio_data *data) fail: push_back_audio(encoder, data, size, offset_size); -skip_push: profile_end(buffer_audio_name); return success; } diff --git a/libobs/obs-ffmpeg-compat.h b/libobs/obs-ffmpeg-compat.h index 9228917..0f3f265 100644 --- a/libobs/obs-ffmpeg-compat.h +++ b/libobs/obs-ffmpeg-compat.h @@ -20,3 +20,12 @@ # define av_frame_free avcodec_free_frame #endif +#if LIBAVCODEC_VERSION_MAJOR >= 58 +#define CODEC_CAP_TRUNC AV_CODEC_CAP_TRUNCATED +#define CODEC_FLAG_TRUNC AV_CODEC_FLAG_TRUNCATED +#define INPUT_BUFFER_PADDING_SIZE AV_INPUT_BUFFER_PADDING_SIZE +#else +#define CODEC_CAP_TRUNC CODEC_CAP_TRUNCATED +#define CODEC_FLAG_TRUNC CODEC_FLAG_TRUNCATED +#define INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE +#endif diff --git a/libobs/obs-hotkey.c b/libobs/obs-hotkey.c index 8ccc50c..7d41cff 100644 --- a/libobs/obs-hotkey.c +++ b/libobs/obs-hotkey.c @@ -206,7 +206,7 @@ obs_hotkey_id obs_hotkey_register_service(obs_service_t *service, obs_hotkey_id obs_hotkey_register_source(obs_source_t *source, const char *name, const char *description, obs_hotkey_func func, void *data) { - if (!source || !lock()) + if (!source || source->context.private || !lock()) return OBS_INVALID_HOTKEY_ID; obs_hotkey_id id = obs_hotkey_register_internal( diff --git a/libobs/obs-hotkey.h b/libobs/obs-hotkey.h index 7ff2bae..0385d65 100644 --- a/libobs/obs-hotkey.h +++ b/libobs/obs-hotkey.h @@ -22,9 +22,15 @@ extern "C" { #endif typedef size_t obs_hotkey_id; -#define OBS_INVALID_HOTKEY_ID (~(obs_hotkey_id)0) typedef size_t obs_hotkey_pair_id; + +#ifndef SWIG +#define OBS_INVALID_HOTKEY_ID (~(obs_hotkey_id)0) #define OBS_INVALID_HOTKEY_PAIR_ID (~(obs_hotkey_pair_id)0) +#else +const size_t OBS_INVALID_HOTKEY_ID = (size_t)-1; +const size_t OBS_INVALID_HOTKEY_PAIR_ID = (size_t)-1; +#endif enum obs_key { #define OBS_HOTKEY(x) x, @@ -68,6 +74,7 @@ EXPORT obs_hotkey_id obs_hotkey_binding_get_hotkey_id( EXPORT obs_hotkey_t *obs_hotkey_binding_get_hotkey( obs_hotkey_binding_t *binding); +#ifndef SWIG struct obs_hotkeys_translations { const char *insert; const char *del; @@ -115,6 +122,7 @@ struct obs_hotkeys_translations { * the default English translations for that specific operating system. */ EXPORT void obs_hotkeys_set_translations_s( struct obs_hotkeys_translations *translations, size_t size); +#endif #define obs_hotkeys_set_translations(translations) \ obs_hotkeys_set_translations_s(translations, \ @@ -277,7 +285,7 @@ EXPORT int obs_key_to_virtual_key(obs_key_t key); EXPORT const char *obs_key_to_name(obs_key_t key); EXPORT obs_key_t obs_key_from_name(const char *name); -inline bool obs_key_combination_is_empty(obs_key_combination_t combo) +static inline bool obs_key_combination_is_empty(obs_key_combination_t combo) { return !combo.modifiers && combo.key == OBS_KEY_NONE; } diff --git a/libobs/obs-hotkeys.h b/libobs/obs-hotkeys.h index 74b5e9e..dadb446 100644 --- a/libobs/obs-hotkeys.h +++ b/libobs/obs-hotkeys.h @@ -475,3 +475,5 @@ OBS_MOUSE_BUTTON(OBS_KEY_MOUSE29) #undef OBS_MOUSE_BUTTON #undef OBS_MOUSE_BUTTON_DEFAULT #endif + +OBS_HOTKEY(OBS_KEY_BACKSLASH_RT102) diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 4d3a75c..eede65b 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -44,6 +44,11 @@ static inline int64_t packet_dts_usec(struct encoder_packet *packet) return packet->dts * MICROSECOND_DEN / packet->timebase_den; } +struct tick_callback { + void (*tick)(void *param, float seconds); + void *param; +}; + struct draw_callback { void (*draw)(void *param, uint32_t cx, uint32_t cy); void *param; @@ -82,6 +87,7 @@ struct obs_module { bool (*load)(void); void (*unload)(void); + void (*post_load)(void); void (*set_locale)(const char *locale); void (*free_locale)(void); uint32_t (*ver)(void); @@ -318,6 +324,7 @@ struct obs_core_data { pthread_mutex_t audio_sources_mutex; pthread_mutex_t draw_callbacks_mutex; DARRAY(struct draw_callback) draw_callbacks; + DARRAY(struct tick_callback) tick_callbacks; struct obs_view main_view; @@ -393,7 +400,7 @@ struct obs_core { extern struct obs_core *obs; -extern void *obs_video_thread(void *param); +extern void *obs_graphics_thread(void *param); extern gs_effect_t *obs_load_effect(gs_effect_t **effect, const char *file); @@ -609,6 +616,7 @@ struct obs_source { bool async_active; bool async_update_texture; bool async_unbuffered; + bool async_decoupled; struct obs_source_frame *async_preload_frame; DARRAY(struct async_frame) async_cache; DARRAY(struct obs_source_frame*)async_frames; @@ -679,9 +687,11 @@ struct obs_source { struct audio_monitor *monitor; enum obs_monitoring_type monitoring_type; + + obs_data_t *private_settings; }; -extern const struct obs_source_info *get_source_info(const char *id); +extern struct obs_source_info *get_source_info(const char *id); extern bool obs_source_init_context(struct obs_source *source, obs_data_t *settings, const char *name, obs_data_t *hotkey_data, bool private); diff --git a/libobs/obs-module.c b/libobs/obs-module.c index 41c46a4..274f9fe 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -48,6 +48,7 @@ static int load_module_exports(struct obs_module *mod, const char *path) /* optional exports */ mod->unload = os_dlsym(mod->module, "obs_module_unload"); + mod->post_load = os_dlsym(mod->module, "obs_module_post_load"); mod->set_locale = os_dlsym(mod->module, "obs_module_set_locale"); mod->free_locale = os_dlsym(mod->module, "obs_module_free_locale"); mod->name = os_dlsym(mod->module, "obs_module_name"); @@ -88,7 +89,7 @@ int obs_open_module(obs_module_t **module, const char *path, mod.module = os_dlopen(path); if (!mod.module) { - blog(LOG_WARNING, "Module '%s' not found", path); + blog(LOG_WARNING, "Module '%s' not loaded", path); return MODULE_FILE_NOT_FOUND; } @@ -254,6 +255,13 @@ void obs_load_all_modules(void) profile_end(obs_load_all_modules_name); } +void obs_post_load_modules(void) +{ + for (obs_module_t *mod = obs->first_module; !!mod; mod = mod->next) + if (mod->post_load) + mod->post_load(); +} + static inline void make_data_dir(struct dstr *parsed_data_dir, const char *data_dir, const char *name) { diff --git a/libobs/obs-module.h b/libobs/obs-module.h index cf449f3..09f484e 100644 --- a/libobs/obs-module.h +++ b/libobs/obs-module.h @@ -97,6 +97,9 @@ MODULE_EXPORT bool obs_module_load(void); /** Optional: Called when the module is unloaded. */ MODULE_EXPORT void obs_module_unload(void); +/** Optional: Called when all modules have finished loading */ +MODULE_EXPORT void obs_module_post_load(void); + /** Called to set the current locale data for the module. */ MODULE_EXPORT void obs_module_set_locale(const char *locale); diff --git a/libobs/obs-nix.c b/libobs/obs-nix.c index 0099b00..e20e82e 100644 --- a/libobs/obs-nix.c +++ b/libobs/obs-nix.c @@ -16,6 +16,9 @@ along with this program. If not, see . ******************************************************************************/ +#if defined(__FreeBSD__) +#define _GNU_SOURCE +#endif #include #include #include @@ -87,55 +90,105 @@ char *find_libobs_data_file(const char *file) static void log_processor_cores(void) { - blog(LOG_INFO, "Processor: %lu logical cores", - sysconf(_SC_NPROCESSORS_ONLN)); + blog(LOG_INFO, "Physical Cores: %d, Logical Cores: %d", + os_get_physical_cores(), os_get_logical_cores()); } #if defined(__linux__) static void log_processor_info(void) { - FILE *fp; int physical_id = -1; int last_physical_id = -1; char *line = NULL; size_t linecap = 0; - struct dstr processor; + + FILE *fp; + struct dstr proc_name; + struct dstr proc_speed; fp = fopen("/proc/cpuinfo", "r"); if (!fp) return; - dstr_init(&processor); + dstr_init(&proc_name); + dstr_init(&proc_speed); while (getline(&line, &linecap, fp) != -1) { if (!strncmp(line, "model name", 10)) { char *start = strchr(line, ':'); if (!start || *(++start) == '\0') continue; - dstr_copy(&processor, start); - dstr_resize(&processor, processor.len - 1); - dstr_depad(&processor); + + dstr_copy(&proc_name, start); + dstr_resize(&proc_name, proc_name.len - 1); + dstr_depad(&proc_name); } if (!strncmp(line, "physical id", 11)) { char *start = strchr(line, ':'); if (!start || *(++start) == '\0') continue; + physical_id = atoi(start); } + if (!strncmp(line, "cpu MHz", 7)) { + char *start = strchr(line, ':'); + if (!start || *(++start) == '\0') + continue; + + dstr_copy(&proc_speed, start); + dstr_resize(&proc_speed, proc_speed.len - 1); + dstr_depad(&proc_speed); + } + if (*line == '\n' && physical_id != last_physical_id) { last_physical_id = physical_id; - blog(LOG_INFO, "Processor: %s", processor.array); + blog(LOG_INFO, "CPU Name: %s", proc_name.array); + blog(LOG_INFO, "CPU Speed: %sMHz", proc_speed.array); } } fclose(fp); - dstr_free(&processor); + dstr_free(&proc_name); + dstr_free(&proc_speed); free(line); } #elif defined(__FreeBSD__) -static void log_processor_info(void) +static void log_processor_speed(void) +{ + char *line = NULL; + size_t linecap = 0; + FILE *fp; + struct dstr proc_speed; + + fp = fopen("/var/run/dmesg.boot", "r"); + if (!fp) { + blog(LOG_INFO, "CPU: Missing /var/run/dmesg.boot !"); + return; + } + + dstr_init(&proc_speed); + + while (getline(&line, &linecap, fp) != -1) { + if (!strncmp(line, "CPU: ", 5)) { + char *start = strrchr(line, '('); + if (!start || *(++start) == '\0') + continue; + + size_t len = strcspn(start, "-"); + dstr_ncopy(&proc_speed, start, len); + } + } + + blog(LOG_INFO, "CPU Speed: %sMHz", proc_speed.array); + + fclose(fp); + dstr_free(&proc_speed); + free(line); +} + +static void log_processor_name(void) { int mib[2]; size_t len; @@ -150,10 +203,16 @@ static void log_processor_info(void) return; sysctl(mib, 2, proc, &len, NULL, 0); - blog(LOG_INFO, "Processor: %s", proc); + blog(LOG_INFO, "CPU Name: %s", proc); bfree(proc); } + +static void log_processor_info(void) +{ + log_processor_name(); + log_processor_speed(); +} #endif static void log_memory_info(void) @@ -162,8 +221,10 @@ static void log_memory_info(void) if (sysinfo(&info) < 0) return; - blog(LOG_INFO, "Physical Memory: %"PRIu64"MB Total", - (uint64_t)info.totalram * info.mem_unit / 1024 / 1024); + blog(LOG_INFO, "Physical Memory: %"PRIu64"MB Total, %"PRIu64"MB Free", + (uint64_t)info.totalram * info.mem_unit / 1024 / 1024, + ((uint64_t)info.freeram + (uint64_t)info.bufferram) * + info.mem_unit / 1024 / 1024); } static void log_kernel_version(void) @@ -222,10 +283,10 @@ static void log_distribution_info(void) void log_system_info(void) { - log_processor_cores(); #if defined(__linux__) || defined(__FreeBSD__) log_processor_info(); #endif + log_processor_cores(); log_memory_info(); log_kernel_version(); #if defined(__linux__) @@ -594,7 +655,7 @@ static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys) context->min_keycode = setup->min_keycode; cookie = xcb_get_keyboard_mapping(connection, - mincode, maxcode - mincode - 1); + mincode, maxcode - mincode + 1); reply = xcb_get_keyboard_mapping_reply(connection, cookie, &error); @@ -606,7 +667,7 @@ static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys) const xcb_keysym_t *keysyms = xcb_get_keyboard_mapping_keysyms(reply); int syms_per_code = (int)reply->keysyms_per_keycode; - context->num_keysyms = (maxcode - mincode) * syms_per_code; + context->num_keysyms = (maxcode - mincode + 1) * syms_per_code; context->syms_per_code = syms_per_code; context->keysyms = bmemdup(keysyms, sizeof(xcb_keysym_t) * context->num_keysyms); diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 81e64e2..4df3b9d 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -283,28 +283,36 @@ static void log_frame_info(struct obs_output *output) { struct obs_core_video *video = &obs->video; - uint32_t video_frames = video_output_get_total_frames(output->video); - - uint32_t total = video_frames - output->starting_frame_count; - uint32_t drawn = video->total_frames - output->starting_drawn_count; uint32_t lagged = video->lagged_frames - output->starting_lagged_count; int dropped = obs_output_get_frames_dropped(output); + int total = output->total_frames; double percentage_lagged = 0.0f; double percentage_dropped = 0.0f; - if (total) - percentage_dropped = (double)dropped / (double)total * 100.0; if (drawn) percentage_lagged = (double)lagged / (double)drawn * 100.0; + if (dropped) + percentage_dropped = (double)dropped / (double)total * 100.0; blog(LOG_INFO, "Output '%s': stopping", output->context.name); - blog(LOG_INFO, "Output '%s': Total encoded frames: %"PRIu32, - output->context.name, total); - blog(LOG_INFO, "Output '%s': Total drawn frames: %"PRIu32, - output->context.name, drawn); + if (!dropped || !total) + blog(LOG_INFO, "Output '%s': Total frames output: %d", + output->context.name, total); + else + blog(LOG_INFO, "Output '%s': Total frames output: %d" + " (%d attempted)", + output->context.name, total - dropped, total); + + if (!lagged || !drawn) + blog(LOG_INFO, "Output '%s': Total drawn frames: %"PRIu32, + output->context.name, drawn); + else + blog(LOG_INFO, "Output '%s': Total drawn frames: %"PRIu32 + " (%"PRIu32" attempted)", + output->context.name, drawn - lagged, drawn); if (drawn && lagged) blog(LOG_INFO, "Output '%s': Number of lagged frames due " @@ -1293,7 +1301,7 @@ static bool initialize_interleaved_packets(struct obs_output *output) } /* get new offsets */ - output->video_offset = video->dts; + output->video_offset = video->pts; for (size_t i = 0; i < audio_mixes; i++) output->audio_offsets[i] = audio[i]->dts; @@ -1329,8 +1337,12 @@ static inline void insert_interleaved_packet(struct obs_output *output, struct encoder_packet *cur_packet; cur_packet = output->interleaved_packets.array + idx; - if (out->dts_usec < cur_packet->dts_usec) + if (out->dts_usec == cur_packet->dts_usec && + out->type == OBS_ENCODER_VIDEO) { break; + } else if (out->dts_usec < cur_packet->dts_usec) { + break; + } } da_insert(output->interleaved_packets, idx, out); @@ -2155,3 +2167,15 @@ bool obs_output_reconnecting(const obs_output_t *output) return reconnecting(output); } + +const char *obs_output_get_supported_video_codecs(const obs_output_t *output) +{ + return obs_output_valid(output, __FUNCTION__) ? + output->info.encoded_video_codecs : NULL; +} + +const char *obs_output_get_supported_audio_codecs(const obs_output_t *output) +{ + return obs_output_valid(output, __FUNCTION__) ? + output->info.encoded_audio_codecs : NULL; +} diff --git a/libobs/obs-output.h b/libobs/obs-output.h index 6f79cc9..df50fa0 100644 --- a/libobs/obs-output.h +++ b/libobs/obs-output.h @@ -67,6 +67,10 @@ struct obs_output_info { float (*get_congestion)(void *data); int (*get_connect_time_ms)(void *data); + + /* only used with encoded outputs, separated with semicolon */ + const char *encoded_video_codecs; + const char *encoded_audio_codecs; }; EXPORT void obs_register_output_s(const struct obs_output_info *info, diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index 6f6b2fc..097a6be 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -143,9 +143,10 @@ static inline void frame_rate_data_free(struct frame_rate_data *data) struct obs_properties; struct obs_property { - const char *name; - const char *desc; - const char *long_desc; + char *name; + char *desc; + char *long_desc; + void *priv; enum obs_property_type type; bool visible; bool enabled; @@ -153,6 +154,7 @@ struct obs_property { struct obs_properties *parent; obs_property_modified_t modified; + obs_property_modified2_t modified2; struct obs_property *next; }; @@ -222,6 +224,9 @@ static void obs_property_destroy(struct obs_property *property) else if (property->type == OBS_PROPERTY_FRAME_RATE) frame_rate_data_free(get_property_data(property)); + bfree(property->name); + bfree(property->desc); + bfree(property->long_desc); bfree(property); } @@ -277,6 +282,8 @@ void obs_properties_apply_settings(obs_properties_t *props, obs_data_t *settings while (p) { if (p->modified) p->modified(props, p, settings); + else if (p->modified2) + p->modified2(p->priv, props, p, settings); p = p->next; } } @@ -323,8 +330,8 @@ static inline struct obs_property *new_prop(struct obs_properties *props, p->enabled = true; p->visible = true; p->type = type; - p->name = name; - p->desc = desc; + p->name = bstrdup(name); + p->desc = bstrdup(desc); propertes_add(props, p); return p; @@ -495,6 +502,20 @@ obs_property_t *obs_properties_add_button(obs_properties_t *props, return p; } +obs_property_t *obs_properties_add_button2(obs_properties_t *props, + const char *name, const char *text, + obs_property_clicked_t callback, void *priv) +{ + if (!props || has_prop(props, name)) return NULL; + + struct obs_property *p = new_prop(props, name, text, + OBS_PROPERTY_BUTTON); + struct button_data *data = get_property_data(p); + data->callback = callback; + p->priv = priv; + return p; +} + obs_property_t *obs_properties_add_font(obs_properties_t *props, const char *name, const char *desc) { @@ -571,10 +592,24 @@ void obs_property_set_modified_callback(obs_property_t *p, if (p) p->modified = modified; } +void obs_property_set_modified_callback2(obs_property_t *p, + obs_property_modified2_t modified2, void *priv) +{ + if (p) { + p->modified2 = modified2; + p->priv = priv; + } +} + bool obs_property_modified(obs_property_t *p, obs_data_t *settings) { - if (p && p->modified) - return p->modified(p->parent, p, settings); + if (p) { + if (p->modified) { + return p->modified(p->parent, p, settings); + } else if (p->modified2) { + return p->modified2(p->priv, p->parent, p, settings); + } + } return false; } @@ -584,9 +619,12 @@ bool obs_property_button_clicked(obs_property_t *p, void *obj) if (p) { struct button_data *data = get_type_data(p, OBS_PROPERTY_BUTTON); - if (data && data->callback) + if (data && data->callback) { + if (p->priv) + return data->callback(p->parent, p, p->priv); return data->callback(p->parent, p, (context ? context->data : NULL)); + } } return false; @@ -604,12 +642,22 @@ void obs_property_set_enabled(obs_property_t *p, bool enabled) void obs_property_set_description(obs_property_t *p, const char *description) { - if (p) p->desc = description; + if (p) { + bfree(p->desc); + p->desc = description && *description + ? bstrdup(description) + : NULL; + } } void obs_property_set_long_description(obs_property_t *p, const char *long_desc) { - if (p) p->long_desc = long_desc; + if (p) { + bfree(p->long_desc); + p->long_desc = long_desc && *long_desc + ? bstrdup(long_desc) + : NULL; + } } const char *obs_property_name(obs_property_t *p) diff --git a/libobs/obs-properties.h b/libobs/obs-properties.h index e280ab4..f5e7976 100644 --- a/libobs/obs-properties.h +++ b/libobs/obs-properties.h @@ -194,6 +194,10 @@ EXPORT obs_property_t *obs_properties_add_button(obs_properties_t *props, const char *name, const char *text, obs_property_clicked_t callback); +EXPORT obs_property_t *obs_properties_add_button2(obs_properties_t *props, + const char *name, const char *text, + obs_property_clicked_t callback, void *priv); + /** * Adds a font selection property. * @@ -223,9 +227,13 @@ EXPORT obs_property_t *obs_properties_add_frame_rate(obs_properties_t *props, */ typedef bool (*obs_property_modified_t)(obs_properties_t *props, obs_property_t *property, obs_data_t *settings); +typedef bool (*obs_property_modified2_t)(void *priv, obs_properties_t *props, + obs_property_t *property, obs_data_t *settings); EXPORT void obs_property_set_modified_callback(obs_property_t *p, obs_property_modified_t modified); +EXPORT void obs_property_set_modified_callback2(obs_property_t *p, + obs_property_modified2_t modified, void *priv); EXPORT bool obs_property_modified(obs_property_t *p, obs_data_t *settings); EXPORT bool obs_property_button_clicked(obs_property_t *p, void *obj); diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 3294021..2b7a7f0 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -486,7 +486,10 @@ static inline void render_item(struct obs_scene_item *item) -(float)item->crop.top, 0.0f); + gs_blend_state_push(); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); obs_source_video_render(item->source); + gs_blend_state_pop(); gs_texrender_end(item->item_render); } } @@ -590,6 +593,7 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data) const char *scale_filter_str; struct obs_scene_item *item; bool visible; + bool lock; if (!source) { blog(LOG_WARNING, "[scene_load_item] Source %s not found!", @@ -616,10 +620,18 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data) item->rot = (float)obs_data_get_double(item_data, "rot"); item->align = (uint32_t)obs_data_get_int(item_data, "align"); visible = obs_data_get_bool(item_data, "visible"); + lock = obs_data_get_bool(item_data, "locked"); obs_data_get_vec2(item_data, "pos", &item->pos); obs_data_get_vec2(item_data, "scale", &item->scale); + obs_data_release(item->private_settings); + item->private_settings = + obs_data_get_obj(item_data, "private_settings"); + if (!item->private_settings) + item->private_settings = obs_data_create(); + set_visibility(item, visible); + obs_sceneitem_set_locked(item, lock); item->bounds_type = (enum obs_bounds_type)obs_data_get_int(item_data, @@ -697,6 +709,7 @@ static void scene_save_item(obs_data_array_t *array, obs_data_set_string(item_data, "name", name); obs_data_set_bool (item_data, "visible", item->user_visible); + obs_data_set_bool (item_data, "locked", item->locked); obs_data_set_double(item_data, "rot", item->rot); obs_data_set_vec2 (item_data, "pos", &item->pos); obs_data_set_vec2 (item_data, "scale", &item->scale); @@ -723,6 +736,9 @@ static void scene_save_item(obs_data_array_t *array, obs_data_set_string(item_data, "scale_filter", scale_filter); + obs_data_set_obj(item_data, "private_settings", + item->private_settings); + obs_data_array_push_back(array, item_data); obs_data_release(item_data); } @@ -891,7 +907,7 @@ static bool scene_audio_render(void *data, uint64_t *ts_out, item = scene->first_item; while (item) { - if (!obs_source_audio_pending(item->source)) { + if (!obs_source_audio_pending(item->source) && item->visible) { uint64_t source_ts = obs_source_get_audio_timestamp(item->source); @@ -1079,6 +1095,11 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name, new_scene = make_private ? obs_scene_create_private(name) : obs_scene_create(name); + obs_source_copy_filters(new_scene->source, scene->source); + + obs_data_apply(new_scene->source->private_settings, + scene->source->private_settings); + for (size_t i = 0; i < items.num; i++) { item = items.array[i]; source = make_unique ? @@ -1112,8 +1133,19 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name, new_item->bounds_align = item->bounds_align; new_item->bounds = item->bounds; + new_item->toggle_visibility = + OBS_INVALID_HOTKEY_PAIR_ID; + obs_sceneitem_set_crop(new_item, &item->crop); + if (!new_item->item_render && + item_texture_enabled(new_item)) { + obs_enter_graphics(); + new_item->item_render = gs_texrender_create( + GS_RGBA, GS_ZS_NONE); + obs_leave_graphics(); + } + obs_source_release(source); } } @@ -1347,6 +1379,8 @@ obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source) item->align = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; item->actions_mutex = mutex; item->user_visible = true; + item->locked = false; + item->private_settings = obs_data_create(); os_atomic_set_long(&item->active_refs, 1); vec2_set(&item->scale, 1.0f, 1.0f); matrix4_identity(&item->draw_transform); @@ -1402,6 +1436,7 @@ static void obs_sceneitem_destroy(obs_sceneitem_t *item) gs_texrender_destroy(item->item_render); obs_leave_graphics(); } + obs_data_release(item->private_settings); obs_hotkey_pair_unregister(item->toggle_visibility); pthread_mutex_destroy(&item->actions_mutex); if (item->source) @@ -1773,6 +1808,27 @@ bool obs_sceneitem_set_visible(obs_sceneitem_t *item, bool visible) return true; } +bool obs_sceneitem_locked(const obs_sceneitem_t *item) +{ + return item ? item->locked : false; +} + +bool obs_sceneitem_set_locked(obs_sceneitem_t *item, bool lock) +{ + if (!item) + return false; + + if (item->locked == lock) + return false; + + if (!item->parent) + return false; + + item->locked = lock; + + return true; +} + static bool sceneitems_match(obs_scene_t *scene, obs_sceneitem_t * const *items, size_t size, bool *order_matches) { @@ -1962,3 +2018,12 @@ int64_t obs_sceneitem_get_id(const obs_sceneitem_t *item) return item->id; } + +obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item) +{ + if (!obs_ptr_valid(item, "obs_sceneitem_get_private_settings")) + return NULL; + + obs_data_addref(item->private_settings); + return item->private_settings; +} diff --git a/libobs/obs-scene.h b/libobs/obs-scene.h index d460221..06db0a6 100644 --- a/libobs/obs-scene.h +++ b/libobs/obs-scene.h @@ -41,6 +41,7 @@ struct obs_scene_item { bool user_visible; bool visible; bool selected; + bool locked; gs_texrender_t *item_render; struct obs_sceneitem_crop crop; @@ -67,6 +68,8 @@ struct obs_scene_item { obs_hotkey_pair_id toggle_visibility; + obs_data_t *private_settings; + pthread_mutex_t actions_mutex; DARRAY(struct item_action) audio_actions; diff --git a/libobs/obs-service.c b/libobs/obs-service.c index 9a4a4e9..db73ef9 100644 --- a/libobs/obs-service.c +++ b/libobs/obs-service.c @@ -393,3 +393,13 @@ const char *obs_service_get_id(const obs_service_t *service) return obs_service_valid(service, "obs_service_get_id") ? service->info.id : NULL; } + +const char *obs_service_get_output_type(const obs_service_t *service) +{ + if (!obs_service_valid(service, "obs_service_get_output_type")) + return NULL; + + if (service->info.get_output_type) + return service->info.get_output_type(service->context.data); + return NULL; +} diff --git a/libobs/obs-service.h b/libobs/obs-service.h index 2fea234..d5ab8a6 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -72,6 +72,8 @@ struct obs_service_info { void *type_data; void (*free_type_data)(void *type_data); + const char *(*get_output_type)(void *data); + /* TODO: more stuff later */ }; diff --git a/libobs/obs-source-transition.c b/libobs/obs-source-transition.c index d92be31..929204f 100644 --- a/libobs/obs-source-transition.c +++ b/libobs/obs-source-transition.c @@ -365,6 +365,9 @@ bool obs_transition_start(obs_source_t *transition, if (same_as_source && !active) return false; + if (transition->info.transition_start) + transition->info.transition_start(transition->context.data); + if (transition->transition_use_fixed_duration) duration_ms = transition->transition_fixed_duration; @@ -376,6 +379,10 @@ bool obs_transition_start(obs_source_t *transition, set_source(transition, OBS_TRANSITION_SOURCE_B, dest, activate_transition); + if (dest == NULL && same_as_dest && !same_as_source) { + transition->transitioning_video = true; + transition->transitioning_audio = true; + } obs_source_dosignal(transition, "source_transition_start", "transition_start"); @@ -442,6 +449,11 @@ static inline float get_video_time(obs_source_t *transition) return calc_time(transition, ts); } +float obs_transition_get_time(obs_source_t *transition) +{ + return get_video_time(transition); +} + static inline gs_texture_t *get_texture(obs_source_t *transition, enum obs_transition_target target) { @@ -645,6 +657,14 @@ static void obs_transition_stop(obs_source_t *transition) transition->transition_sources[1] = NULL; } +static inline void handle_stop(obs_source_t *transition) +{ + if (transition->info.transition_stop) + transition->info.transition_stop(transition->context.data); + obs_source_dosignal(transition, "source_transition_stop", + "transition_stop"); +} + void obs_transition_video_render(obs_source_t *transition, obs_transition_video_render_callback_t callback) { @@ -728,8 +748,61 @@ void obs_transition_video_render(obs_source_t *transition, obs_source_dosignal(transition, "source_transition_video_stop", "transition_video_stop"); if (stopped) - obs_source_dosignal(transition, "source_transition_stop", - "transition_stop"); + handle_stop(transition); +} + +bool obs_transition_video_render_direct(obs_source_t *transition, + enum obs_transition_target target) +{ + struct transition_state state; + struct matrix4 matrices[2]; + bool stopped = false; + bool video_stopped = false; + bool render_b = target == OBS_TRANSITION_SOURCE_B; + bool transitioning; + float t; + + if (!transition_valid(transition, "obs_transition_video_render")) + return false; + + t = get_video_time(transition); + + lock_transition(transition); + + if (t >= 1.0f && transition->transitioning_video) { + transition->transitioning_video = false; + video_stopped = true; + + if (!transition->transitioning_audio) { + obs_transition_stop(transition); + stopped = true; + } + } + copy_transition_state(transition, &state); + transitioning = state.transitioning_audio || state.transitioning_video; + matrices[0] = transition->transition_matrices[0]; + matrices[1] = transition->transition_matrices[1]; + + unlock_transition(transition); + + int idx = (transitioning && render_b) ? 1 : 0; + if (state.s[idx]) { + gs_matrix_push(); + gs_matrix_mul(&matrices[idx]); + obs_source_video_render(state.s[idx]); + gs_matrix_pop(); + } + + obs_source_release(state.s[0]); + obs_source_release(state.s[1]); + + if (video_stopped) + obs_source_dosignal(transition, "source_transition_video_stop", + "transition_video_stop"); + if (stopped) + handle_stop(transition); + + return transitioning; } static inline float get_sample_time(obs_source_t *transition, @@ -805,10 +878,6 @@ static inline uint64_t calc_min_ts(obs_source_t *sources[2]) return min_ts; } -#define TOTAL_AUDIO_SIZE \ - (MAX_AUDIO_MIXES * MAX_AUDIO_CHANNELS * \ - AUDIO_OUTPUT_FRAMES * sizeof(float)) - static inline bool stop_audio(obs_source_t *transition) { transition->transitioning_audio = false; @@ -882,8 +951,7 @@ bool obs_transition_audio_render(obs_source_t *transition, } if (stopped) - obs_source_dosignal(transition, "source_transition_stop", - "transition_stop"); + handle_stop(transition); *ts_out = min_ts; return !!min_ts; diff --git a/libobs/obs-source.c b/libobs/obs-source.c index d76ce23..8697117 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -39,7 +39,7 @@ static inline bool deinterlacing_enabled(const struct obs_source *source) return source->deinterlace_mode != OBS_DEINTERLACE_MODE_DISABLE; } -const struct obs_source_info *get_source_info(const char *id) +struct obs_source_info *get_source_info(const char *id) { for (size_t i = 0; i < obs->source_types.num; i++) { struct obs_source_info *info = &obs->source_types.array[i]; @@ -190,6 +190,8 @@ bool obs_source_init(struct obs_source *source) pthread_mutex_unlock(&obs->data.audio_sources_mutex); } + source->private_settings = obs_data_create(); + obs_context_data_insert(&source->context, &obs->data.sources_mutex, &obs->data.first_source); @@ -321,8 +323,13 @@ static obs_source_t *obs_source_create_internal(const char *id, private)) goto fail; - if (info && info->get_defaults) - info->get_defaults(source->context.settings); + if (info) { + if (info->get_defaults2) + info->get_defaults2(info->type_data, + source->context.settings); + else if (info->get_defaults) + info->get_defaults(source->context.settings); + } if (!obs_source_init(source)) goto fail; @@ -403,9 +410,11 @@ static void duplicate_filters(obs_source_t *dst, obs_source_t *src, obs_source_t *src_filter = filters.array[i - 1]; char *new_name = get_new_filter_name(dst, src_filter->context.name); + bool enabled = obs_source_enabled(src_filter); obs_source_t *dst_filter = obs_source_duplicate(src_filter, new_name, private); + obs_source_set_enabled(dst_filter, enabled); bfree(new_name); obs_source_filter_add(dst, dst_filter); @@ -418,11 +427,7 @@ static void duplicate_filters(obs_source_t *dst, obs_source_t *src, void obs_source_copy_filters(obs_source_t *dst, obs_source_t *src) { - duplicate_filters(dst, src, dst->context.private ? - OBS_SCENE_DUP_PRIVATE_COPY : - OBS_SCENE_DUP_COPY); - - obs_source_release(src); + duplicate_filters(dst, src, dst->context.private); } obs_source_t *obs_source_duplicate(obs_source_t *source, @@ -445,7 +450,6 @@ obs_source_t *obs_source_duplicate(obs_source_t *source, create_private ? OBS_SCENE_DUP_PRIVATE_COPY : OBS_SCENE_DUP_COPY); obs_source_t *new_source = obs_scene_get_source(new_scene); - duplicate_filters(new_source, source, create_private); return new_source; } @@ -464,6 +468,8 @@ obs_source_t *obs_source_duplicate(obs_source_t *source, new_source->muted = source->muted; new_source->flags = source->flags; + obs_data_apply(new_source->private_settings, source->private_settings); + if (source->info.type != OBS_SOURCE_TYPE_FILTER) duplicate_filters(new_source, source, create_private); @@ -582,6 +588,7 @@ void obs_source_destroy(struct obs_source *source) pthread_mutex_destroy(&source->audio_cb_mutex); pthread_mutex_destroy(&source->audio_mutex); pthread_mutex_destroy(&source->async_mutex); + obs_data_release(source->private_settings); obs_context_data_free(&source->context); if (source->owns_info_id) @@ -688,7 +695,9 @@ bool obs_source_removed(const obs_source_t *source) static inline obs_data_t *get_defaults(const struct obs_source_info *info) { obs_data_t *settings = obs_data_create(); - if (info->get_defaults) + if (info->get_defaults2) + info->get_defaults2(info->type_data, settings); + else if (info->get_defaults) info->get_defaults(settings); return settings; } @@ -708,14 +717,18 @@ obs_data_t *obs_get_source_defaults(const char *id) obs_properties_t *obs_get_source_properties(const char *id) { const struct obs_source_info *info = get_source_info(id); - if (info && info->get_properties) { + if (info && (info->get_properties || info->get_properties2)) { obs_data_t *defaults = get_defaults(info); - obs_properties_t *properties; + obs_properties_t *props; - properties = info->get_properties(NULL); - obs_properties_apply_settings(properties, defaults); + if (info->get_properties2) + props = info->get_properties2(NULL, info->type_data); + else + props = info->get_properties(NULL); + + obs_properties_apply_settings(props, defaults); obs_data_release(defaults); - return properties; + return props; } return NULL; } @@ -723,13 +736,13 @@ obs_properties_t *obs_get_source_properties(const char *id) bool obs_is_source_configurable(const char *id) { const struct obs_source_info *info = get_source_info(id); - return info && info->get_properties; + return info && (info->get_properties || info->get_properties2); } bool obs_source_configurable(const obs_source_t *source) { return data_valid(source, "obs_source_configurable") && - source->info.get_properties; + (source->info.get_properties || source->info.get_properties2); } obs_properties_t *obs_source_properties(const obs_source_t *source) @@ -737,7 +750,14 @@ obs_properties_t *obs_source_properties(const obs_source_t *source) if (!data_valid(source, "obs_source_properties")) return NULL; - if (source->info.get_properties) { + if (source->info.get_properties2) { + obs_properties_t *props; + props = source->info.get_properties2(source->context.data, + source->info.type_data); + obs_properties_apply_settings(props, source->context.settings); + return props; + + } else if (source->info.get_properties) { obs_properties_t *props; props = source->info.get_properties(source->context.data); obs_properties_apply_settings(props, source->context.settings); @@ -1044,6 +1064,9 @@ void obs_source_video_tick(obs_source_t *source, float seconds) static inline uint64_t conv_frames_to_time(const size_t sample_rate, const size_t frames) { + if (!sample_rate) + return 0; + return (uint64_t)frames * 1000000000ULL / (uint64_t)sample_rate; } @@ -1056,6 +1079,10 @@ static inline size_t conv_time_to_frames(const size_t sample_rate, /* maximum buffer size */ #define MAX_BUF_SIZE (1000 * AUDIO_OUTPUT_FRAMES * sizeof(float)) +/* time threshold in nanoseconds to ensure audio timing is as seamless as + * possible */ +#define TS_SMOOTHING_THRESHOLD 70000000ULL + static inline void reset_audio_timing(obs_source_t *source, uint64_t timestamp, uint64_t os_time) { @@ -1507,7 +1534,6 @@ static bool update_async_texrender(struct obs_source *source, uint32_t cy = source->async_height; float convert_width = (float)source->async_convert_width; - float convert_height = (float)source->async_convert_height; gs_effect_t *conv = obs->video.conversion_effect; gs_technique_t *tech = gs_effect_get_technique(conv, @@ -1666,9 +1692,13 @@ static void obs_source_update_async_video(obs_source_t *source) source->async_rendered = true; if (frame) { - source->timing_adjust = - os_gettime_ns() - frame->timestamp; - source->timing_set = true; + if (!source->async_decoupled || + !source->async_unbuffered) { + source->timing_adjust = + obs->video.video_time - + frame->timestamp; + source->timing_set = true; + } if (source->async_update_texture) { update_async_texture(source, frame, @@ -1731,8 +1761,11 @@ static bool ready_async_frame(obs_source_t *source, uint64_t sys_time); static inline void render_video(obs_source_t *source) { if (source->info.type != OBS_SOURCE_TYPE_FILTER && - (source->info.output_flags & OBS_SOURCE_VIDEO) == 0) + (source->info.output_flags & OBS_SOURCE_VIDEO) == 0) { + if (source->filter_parent) + obs_source_skip_video_filter(source); return; + } if (source->info.type == OBS_SOURCE_TYPE_INPUT && (source->info.output_flags & OBS_SOURCE_ASYNC) != 0 && @@ -1776,7 +1809,7 @@ void obs_source_video_render(obs_source_t *source) static uint32_t get_base_width(const obs_source_t *source) { - bool is_filter = (source->info.type == OBS_SOURCE_TYPE_FILTER); + bool is_filter = !!source->filter_parent; if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) { return source->enabled ? source->transition_actual_cx : 0; @@ -1784,7 +1817,7 @@ static uint32_t get_base_width(const obs_source_t *source) } else if (source->info.get_width && (!is_filter || source->enabled)) { return source->info.get_width(source->context.data); - } else if (source->info.type == OBS_SOURCE_TYPE_FILTER) { + } else if (is_filter) { return get_base_width(source->filter_target); } @@ -1793,7 +1826,7 @@ static uint32_t get_base_width(const obs_source_t *source) static uint32_t get_base_height(const obs_source_t *source) { - bool is_filter = (source->info.type == OBS_SOURCE_TYPE_FILTER); + bool is_filter = !!source->filter_parent; if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) { return source->enabled ? source->transition_actual_cy : 0; @@ -3857,13 +3890,17 @@ static void custom_audio_render(obs_source_t *source, uint32_t mixers, uint64_t ts; for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) { - for (size_t ch = 0; ch < channels; ch++) + for (size_t ch = 0; ch < channels; ch++) { audio_data.output[mix].data[ch] = source->audio_output_buf[mix][ch]; - } + } - memset(audio_data.output[0].data[0], 0, AUDIO_OUTPUT_FRAMES * - MAX_AUDIO_MIXES * channels * sizeof(float)); + if ((source->audio_mixers & mixers & (1 << mix)) != 0) { + memset(source->audio_output_buf[mix][0], 0, + sizeof(float) * AUDIO_OUTPUT_FRAMES * + channels); + } + } success = source->info.audio_render(source->context.data, &ts, &audio_data, mixers, channels, sample_rate); @@ -3874,11 +3911,15 @@ static void custom_audio_render(obs_source_t *source, uint32_t mixers, return; for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) { - if ((source->audio_mixers & (1 << mix)) == 0) { + uint32_t mix_bit = 1 << mix; + + if ((mixers & mix_bit) == 0) + continue; + + if ((source->audio_mixers & mix_bit) == 0) { memset(source->audio_output_buf[mix][0], 0, sizeof(float) * AUDIO_OUTPUT_FRAMES * channels); - continue; } } @@ -4051,3 +4092,45 @@ bool obs_source_async_unbuffered(const obs_source_t *source) return obs_source_valid(source, "obs_source_async_unbuffered") ? source->async_unbuffered : false; } + +obs_data_t *obs_source_get_private_settings(obs_source_t *source) +{ + if (!obs_ptr_valid(source, "obs_source_get_private_settings")) + return NULL; + + obs_data_addref(source->private_settings); + return source->private_settings; +} + +void obs_source_set_async_decoupled(obs_source_t *source, bool decouple) +{ + if (!obs_ptr_valid(source, "obs_source_set_async_decoupled")) + return; + + source->async_decoupled = decouple; + if (decouple) { + pthread_mutex_lock(&source->audio_buf_mutex); + source->timing_set = false; + reset_audio_data(source, 0); + pthread_mutex_unlock(&source->audio_buf_mutex); + } +} + +bool obs_source_async_decoupled(const obs_source_t *source) +{ + return obs_source_valid(source, "obs_source_async_decoupled") ? + source->async_decoupled : false; +} + +/* hidden/undocumented export to allow source type redefinition for scripts */ +EXPORT void obs_enable_source_type(const char *name, bool enable) +{ + struct obs_source_info *info = get_source_info(name); + if (!info) + return; + + if (enable) + info->output_flags &= ~OBS_SOURCE_CAP_DISABLED; + else + info->output_flags |= OBS_SOURCE_CAP_DISABLED; +} diff --git a/libobs/obs-source.h b/libobs/obs-source.h index 8b189c5..04215e9 100644 --- a/libobs/obs-source.h +++ b/libobs/obs-source.h @@ -130,6 +130,11 @@ enum obs_source_type { */ #define OBS_SOURCE_DO_NOT_SELF_MONITOR (1<<9) +/** + * Source type is currently disabled and should not be shown to the user + */ +#define OBS_SOURCE_CAP_DISABLED (1<<10) + /** @} */ typedef void (*obs_source_enum_proc_t)(obs_source_t *parent, @@ -201,6 +206,7 @@ struct obs_source_info { * Gets the default settings for this source * * @param[out] settings Data to assign default settings to + * @deprecated Use get_defaults2 if type_data is needed */ void (*get_defaults)(obs_data_t *settings); @@ -208,6 +214,7 @@ struct obs_source_info { * Gets the property information of this source * * @return The properties data + * @deprecated Use get_properties2 if type_data is needed */ obs_properties_t *(*get_properties)(void *data); @@ -425,6 +432,26 @@ struct obs_source_info { void (*enum_all_sources)(void *data, obs_source_enum_proc_t enum_callback, void *param); + + void (*transition_start)(void *data); + void (*transition_stop)(void *data); + + /** + * Gets the default settings for this source + * + * @param type_data The type_data variable of this structure + * @param[out] settings Data to assign default settings to + */ + void (*get_defaults2)(void *type_data, obs_data_t *settings); + + /** + * Gets the property information of this source + * + * @param data Source data + * @param type_data The type_data variable of this structure + * @return The properties data + */ + obs_properties_t *(*get_properties2)(void *data, void *type_data); }; EXPORT void obs_register_source_s(const struct obs_source_info *info, diff --git a/libobs/obs-video.c b/libobs/obs-video.c index 8bff747..d7641ad 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -15,6 +15,9 @@ along with this program. If not, see . ******************************************************************************/ +#include +#include + #include "obs.h" #include "obs-internal.h" #include "graphics/vec4.h" @@ -35,9 +38,24 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time) delta_time = cur_time - last_time; seconds = (float)((double)delta_time / 1000000000.0); + /* ------------------------------------- */ + /* call tick callbacks */ + + pthread_mutex_lock(&obs->data.draw_callbacks_mutex); + + for (size_t i = obs->data.tick_callbacks.num; i > 0; i--) { + struct tick_callback *callback; + callback = obs->data.tick_callbacks.array + (i - 1); + callback->tick(callback->param, seconds); + } + + pthread_mutex_unlock(&obs->data.draw_callbacks_mutex); + + /* ------------------------------------- */ + /* call the tick function of each source */ + pthread_mutex_lock(&data->sources_mutex); - /* call the tick function of each source */ source = data->first_source; while (source) { obs_source_video_tick(source, seconds); @@ -108,9 +126,9 @@ static inline void render_main_texture(struct obs_core_video *video, pthread_mutex_lock(&obs->data.draw_callbacks_mutex); - for (size_t i = 0; i < obs->data.draw_callbacks.num; i++) { + for (size_t i = obs->data.draw_callbacks.num; i > 0; i--) { struct draw_callback *callback; - callback = obs->data.draw_callbacks.array+i; + callback = obs->data.draw_callbacks.array + (i - 1); callback->draw(callback->param, video->base_width, video->base_height); @@ -300,7 +318,7 @@ static inline void stage_output_texture(struct obs_core_video *video, texture_ready = video->textures_converted[prev_texture]; } else { texture = video->output_textures[prev_texture]; - texture_ready = video->output_textures[prev_texture]; + texture_ready = video->textures_output[prev_texture]; } unmap_last_surface(video); @@ -582,7 +600,7 @@ static inline void output_frame(void) static const char *tick_sources_name = "tick_sources"; static const char *render_displays_name = "render_displays"; static const char *output_frame_name = "output_frame"; -void *obs_video_thread(void *param) +void *obs_graphics_thread(void *param) { uint64_t last_time = 0; uint64_t interval = video_output_get_frame_time(obs->video.video); @@ -596,9 +614,11 @@ void *obs_video_thread(void *param) const char *video_thread_name = profile_store_name(obs_get_profiler_name_store(), - "obs_video_thread(%g"NBSP"ms)", interval / 1000000.); + "obs_graphics_thread(%g"NBSP"ms)", interval / 1000000.); profile_register_root(video_thread_name, interval); + srand((unsigned int)time(NULL)); + while (!video_output_stopped(obs->video.video)) { uint64_t frame_start = os_gettime_ns(); uint64_t frame_time_ns; @@ -609,14 +629,14 @@ void *obs_video_thread(void *param) last_time = tick_sources(obs->video.video_time, last_time); profile_end(tick_sources_name); - profile_start(render_displays_name); - render_displays(); - profile_end(render_displays_name); - profile_start(output_frame_name); output_frame(); profile_end(output_frame_name); + profile_start(render_displays_name); + render_displays(); + profile_end(render_displays_name); + frame_time_ns = os_gettime_ns() - frame_start; profile_end(video_thread_name); diff --git a/libobs/obs-windows.c b/libobs/obs-windows.c index b8a139e..75a9278 100644 --- a/libobs/obs-windows.c +++ b/libobs/obs-windows.c @@ -15,6 +15,7 @@ along with this program. If not, see . ******************************************************************************/ +#include "util/windows/win-registry.h" #include "util/windows/win-version.h" #include "util/platform.h" #include "util/dstr.h" @@ -22,6 +23,8 @@ #include "obs-internal.h" #include +#include +#include static uint32_t win_ver = 0; @@ -189,6 +192,187 @@ static void log_aero(void) aeroMessage); } +#define WIN10_GAME_BAR_REG_KEY \ + L"Software\\Microsoft\\Windows\\CurrentVersion\\GameDVR" +#define WIN10_GAME_DVR_POLICY_REG_KEY \ + L"SOFTWARE\\Policies\\Microsoft\\Windows\\GameDVR" +#define WIN10_GAME_DVR_REG_KEY L"System\\GameConfigStore" +#define WIN10_GAME_MODE_REG_KEY L"Software\\Microsoft\\GameBar" + +static void log_gaming_features(void) +{ + if (win_ver < 0xA00) + return; + + struct reg_dword game_bar_enabled; + struct reg_dword game_dvr_allowed; + struct reg_dword game_dvr_enabled; + struct reg_dword game_dvr_bg_recording; + struct reg_dword game_mode_enabled; + + get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_BAR_REG_KEY, + L"AppCaptureEnabled", &game_bar_enabled); + get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_DVR_POLICY_REG_KEY, + L"AllowGameDVR", &game_dvr_allowed); + get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_DVR_REG_KEY, + L"GameDVR_Enabled", &game_dvr_enabled); + get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_BAR_REG_KEY, + L"HistoricalCaptureEnabled", &game_dvr_bg_recording); + get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_MODE_REG_KEY, + L"AllowAutoGameMode", &game_mode_enabled); + + blog(LOG_INFO, "Windows 10 Gaming Features:"); + if (game_bar_enabled.status == ERROR_SUCCESS) { + blog(LOG_INFO, "\tGame Bar: %s", + (bool)game_bar_enabled.return_value ? "On" : "Off"); + } + + if (game_dvr_allowed.status == ERROR_SUCCESS) { + blog(LOG_INFO, "\tGame DVR Allowed: %s", + (bool)game_dvr_allowed.return_value ? "Yes" : "No"); + } + + if (game_dvr_enabled.status == ERROR_SUCCESS) { + blog(LOG_INFO, "\tGame DVR: %s", + (bool)game_dvr_enabled.return_value ? "On" : "Off"); + } + + if (game_dvr_bg_recording.status == ERROR_SUCCESS) { + blog(LOG_INFO, "\tGame DVR Background Recording: %s", + (bool)game_dvr_bg_recording.return_value ? "On" : + "Off"); + } + + if (game_mode_enabled.status == ERROR_SUCCESS) { + blog(LOG_INFO, "\tGame Mode: %s", + (bool)game_mode_enabled.return_value ? "On" : "Off"); + } +} + +static const char *get_str_for_state(int state) +{ + switch (state) { + case WSC_SECURITY_PRODUCT_STATE_ON: + return "enabled"; + case WSC_SECURITY_PRODUCT_STATE_OFF: + return "disabled"; + case WSC_SECURITY_PRODUCT_STATE_SNOOZED: + return "temporarily disabled"; + case WSC_SECURITY_PRODUCT_STATE_EXPIRED: + return "expired"; + default: + return "unknown"; + } +} + +static const char *get_str_for_type(int type) +{ + switch (type) { + case WSC_SECURITY_PROVIDER_ANTIVIRUS: + return "AV"; + case WSC_SECURITY_PROVIDER_FIREWALL: + return "FW"; + case WSC_SECURITY_PROVIDER_ANTISPYWARE: + return "ASW"; + default: + return "unknown"; + } +} + +static void log_security_products_by_type(IWSCProductList *prod_list, int type) +{ + HRESULT hr; + LONG count = 0; + IWscProduct *prod; + BSTR name; + WSC_SECURITY_PRODUCT_STATE prod_state; + + hr = prod_list->lpVtbl->Initialize(prod_list, type); + + if (FAILED(hr)) + return; + + hr = prod_list->lpVtbl->get_Count(prod_list, &count); + if (FAILED(hr)) { + prod_list->lpVtbl->Release(prod_list); + return; + } + + for (int i = 0; i < count; i++) { + hr = prod_list->lpVtbl->get_Item(prod_list, i, &prod); + if (FAILED(hr)) + continue; + + hr = prod->lpVtbl->get_ProductName(prod, &name); + if (FAILED(hr)) + continue; + + hr = prod->lpVtbl->get_ProductState(prod, &prod_state); + if (FAILED(hr)) { + SysFreeString(name); + continue; + } + + blog(LOG_INFO, "\t%S: %s (%s)", name, + get_str_for_state(prod_state), + get_str_for_type(type)); + + SysFreeString(name); + prod->lpVtbl->Release(prod); + } + + prod_list->lpVtbl->Release(prod_list); +} + +static void log_security_products(void) +{ + IWSCProductList *prod_list = NULL; + HMODULE h_wsc; + HRESULT hr; + + /* We load the DLL rather than import wcsapi.lib because the clsid / + * iid only exists on Windows 8 or higher. */ + + h_wsc = LoadLibraryW(L"wscapi.dll"); + if (!h_wsc) + return; + + const CLSID *prod_list_clsid = + (const CLSID *)GetProcAddress(h_wsc, "CLSID_WSCProductList"); + const IID *prod_list_iid = + (const IID *)GetProcAddress(h_wsc, "IID_IWSCProductList"); + + if (prod_list_clsid && prod_list_iid) { + blog(LOG_INFO, "Sec. Software Status:"); + + hr = CoCreateInstance(prod_list_clsid, NULL, + CLSCTX_INPROC_SERVER, prod_list_iid, + &prod_list); + if (!FAILED(hr)) { + log_security_products_by_type(prod_list, + WSC_SECURITY_PROVIDER_ANTIVIRUS); + } + + hr = CoCreateInstance(prod_list_clsid, NULL, + CLSCTX_INPROC_SERVER, prod_list_iid, + &prod_list); + if (!FAILED(hr)) { + log_security_products_by_type(prod_list, + WSC_SECURITY_PROVIDER_FIREWALL); + } + + hr = CoCreateInstance(prod_list_clsid, NULL, + CLSCTX_INPROC_SERVER, prod_list_iid, + &prod_list); + if (!FAILED(hr)) { + log_security_products_by_type(prod_list, + WSC_SECURITY_PROVIDER_ANTISPYWARE); + } + } + + FreeLibrary(h_wsc); +} + void log_system_info(void) { struct win_version_info ver; @@ -202,6 +386,8 @@ void log_system_info(void) log_windows_version(); log_admin_status(); log_aero(); + log_gaming_features(); + log_security_products(); } @@ -328,12 +514,16 @@ static int get_virtual_key(obs_key_t key) case OBS_KEY_BRACKETRIGHT: return VK_OEM_6; case OBS_KEY_ASCIITILDE: return VK_OEM_3; + case OBS_KEY_HENKAN: return VK_CONVERT; + case OBS_KEY_MUHENKAN: return VK_NONCONVERT; case OBS_KEY_KANJI: return VK_KANJI; case OBS_KEY_TOUROKU: return VK_OEM_FJ_TOUROKU; case OBS_KEY_MASSYO: return VK_OEM_FJ_MASSHOU; case OBS_KEY_HANGUL: return VK_HANGUL; + case OBS_KEY_BACKSLASH_RT102: return VK_OEM_102; + case OBS_KEY_MOUSE1: return VK_LBUTTON; case OBS_KEY_MOUSE2: return VK_RBUTTON; case OBS_KEY_MOUSE3: return VK_MBUTTON; diff --git a/libobs/obs.c b/libobs/obs.c index b41c12a..44c04a3 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -385,7 +385,7 @@ static int obs_init_video(struct obs_video_info *ovi) gs_leave_context(); errorcode = pthread_create(&video->video_thread, NULL, - obs_video_thread, obs); + obs_graphics_thread, obs); if (errorcode != 0) return OBS_VIDEO_FAIL; @@ -560,7 +560,7 @@ static bool obs_init_data(void) goto fail; if (pthread_mutex_init(&data->services_mutex, &attr) != 0) goto fail; - if (pthread_mutex_init(&obs->data.draw_callbacks_mutex, NULL) != 0) + if (pthread_mutex_init(&obs->data.draw_callbacks_mutex, &attr) != 0) goto fail; if (!obs_view_init(&data->main_view)) goto fail; @@ -619,6 +619,7 @@ static void obs_free_data(void) pthread_mutex_destroy(&data->services_mutex); pthread_mutex_destroy(&data->draw_callbacks_mutex); da_free(data->draw_callbacks); + da_free(data->tick_callbacks); } static const char *obs_signals[] = { @@ -809,6 +810,7 @@ bool obs_startup(const char *locale, const char *module_config_path, void obs_shutdown(void) { struct obs_module *module; + struct obs_core *core; if (!obs) return; @@ -849,25 +851,27 @@ void obs_shutdown(void) obs->procs = NULL; obs->signals = NULL; - module = obs->first_module; + core = obs; + obs = NULL; + + module = core->first_module; while (module) { struct obs_module *next = module->next; free_module(module); module = next; } - obs->first_module = NULL; + core->first_module = NULL; - for (size_t i = 0; i < obs->module_paths.num; i++) - free_module_path(obs->module_paths.array+i); - da_free(obs->module_paths); + for (size_t i = 0; i < core->module_paths.num; i++) + free_module_path(core->module_paths.array+i); + da_free(core->module_paths); - if (obs->name_store_owned) - profiler_name_store_free(obs->name_store); + if (core->name_store_owned) + profiler_name_store_free(core->name_store); - bfree(obs->module_config_path); - bfree(obs->locale); - bfree(obs); - obs = NULL; + bfree(core->module_config_path); + bfree(core->locale); + bfree(core); #ifdef _WIN32 uninitialize_com(); @@ -884,6 +888,11 @@ uint32_t obs_get_version(void) return LIBOBS_API_VER; } +const char *obs_get_version_string(void) +{ + return OBS_VERSION; +} + void obs_set_locale(const char *locale) { struct obs_module *module; @@ -1425,6 +1434,32 @@ void obs_render_main_view(void) obs_view_render(&obs->data.main_view); } +void obs_render_main_texture(void) +{ + struct obs_core_video *video = &obs->video; + gs_texture_t *tex; + gs_effect_t *effect; + gs_eparam_t *param; + int last_tex; + + if (!obs) return; + + last_tex = video->cur_texture == 0 + ? NUM_TEXTURES - 1 + : video->cur_texture - 1; + + if (!video->textures_rendered[last_tex]) + return; + + tex = video->render_textures[last_tex]; + effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); + param = gs_effect_get_param_by_name(effect, "image"); + gs_effect_set_texture(param, tex); + + while (gs_effect_loop(effect, "Draw")) + gs_draw_sprite(tex, 0, 0, 0); +} + void obs_set_master_volume(float volume) { struct calldata data = {0}; @@ -1514,6 +1549,12 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data) obs_source_set_monitoring_type(source, (enum obs_monitoring_type)monitoring_type); + obs_data_release(source->private_settings); + source->private_settings = + obs_data_get_obj(source_data, "private_settings"); + if (!source->private_settings) + source->private_settings = obs_data_create(); + if (filters) { size_t count = obs_data_array_count(filters); @@ -1642,6 +1683,9 @@ obs_data_t *obs_save_source(obs_source_t *source) obs_data_set_int (source_data, "deinterlace_field_order", di_order); obs_data_set_int (source_data, "monitoring_type", m_type); + obs_data_set_obj(source_data, "private_settings", + source->private_settings); + if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) obs_transition_save(source, source_data); @@ -1898,7 +1942,7 @@ bool obs_set_audio_monitoring_device(const char *name, const char *id) if (!obs || !name || !id || !*name || !*id) return false; -#ifdef _WIN32 +#if defined(_WIN32) || HAVE_PULSEAUDIO pthread_mutex_lock(&obs->audio.monitoring_mutex); if (strcmp(id, obs->audio.monitoring_device_id) == 0) { @@ -1937,6 +1981,34 @@ void obs_get_audio_monitoring_device(const char **name, const char **id) *id = obs->audio.monitoring_device_id; } +void obs_add_tick_callback( + void (*tick)(void *param, float seconds), + void *param) +{ + if (!obs) + return; + + struct tick_callback data = {tick, param}; + + pthread_mutex_lock(&obs->data.draw_callbacks_mutex); + da_insert(obs->data.tick_callbacks, 0, &data); + pthread_mutex_unlock(&obs->data.draw_callbacks_mutex); +} + +void obs_remove_tick_callback( + void (*tick)(void *param, float seconds), + void *param) +{ + if (!obs) + return; + + struct tick_callback data = {tick, param}; + + pthread_mutex_lock(&obs->data.draw_callbacks_mutex); + da_erase_item(obs->data.tick_callbacks, &data); + pthread_mutex_unlock(&obs->data.draw_callbacks_mutex); +} + void obs_add_main_render_callback( void (*draw)(void *param, uint32_t cx, uint32_t cy), void *param) @@ -1947,7 +2019,7 @@ void obs_add_main_render_callback( struct draw_callback data = {draw, param}; pthread_mutex_lock(&obs->data.draw_callbacks_mutex); - da_push_back(obs->data.draw_callbacks, &data); + da_insert(obs->data.draw_callbacks, 0, &data); pthread_mutex_unlock(&obs->data.draw_callbacks_mutex); } diff --git a/libobs/obs.h b/libobs/obs.h index f9a5540..6a70a8b 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -145,6 +145,7 @@ struct obs_transform_info { struct vec2 bounds; }; +#ifndef SWIG /** * Video initialization structure */ @@ -175,6 +176,7 @@ struct obs_video_info { enum obs_scale_type scale_type; /**< How to scale if scaling */ }; +#endif /** * Audio initialization structure @@ -260,6 +262,9 @@ EXPORT bool obs_initialized(void); /** @return The current core version */ EXPORT uint32_t obs_get_version(void); +/** @return The current core version string */ +EXPORT const char *obs_get_version_string(void); + /** * Sets a new locale to use for modules. This will call obs_module_set_locale * for each module with the new locale. @@ -278,6 +283,7 @@ EXPORT const char *obs_get_locale(void); */ EXPORT profiler_name_store_t *obs_get_profiler_name_store(void); +#ifndef SWIG /** * Sets base video output base resolution/fps/format. * @@ -295,6 +301,7 @@ EXPORT profiler_name_store_t *obs_get_profiler_name_store(void); * OBS_VIDEO_FAIL for generic failure */ EXPORT int obs_reset_video(struct obs_video_info *ovi); +#endif /** * Sets base audio output format/channels/samples/etc @@ -303,8 +310,10 @@ EXPORT int obs_reset_video(struct obs_video_info *ovi); */ EXPORT bool obs_reset_audio(const struct obs_audio_info *oai); +#ifndef SWIG /** Gets the current video settings, returns false if no video */ EXPORT bool obs_get_video_info(struct obs_video_info *ovi); +#endif /** Gets the current audio settings, returns false if no audio */ EXPORT bool obs_get_audio_info(struct obs_audio_info *oai); @@ -374,6 +383,11 @@ EXPORT void obs_add_module_path(const char *bin, const char *data); /** Automatically loads all modules from module paths (convenience function) */ EXPORT void obs_load_all_modules(void); +/** Notifies modules that all modules have been loaded. This function should + * be called after all modules have been loaded. */ +EXPORT void obs_post_load_modules(void); + +#ifndef SWIG struct obs_module_info { const char *bin_path; const char *data_path; @@ -384,6 +398,7 @@ typedef void (*obs_find_module_callback_t)(void *param, /** Finds all modules within the search paths added by obs_add_module_path. */ EXPORT void obs_find_modules(obs_find_module_callback_t callback, void *param); +#endif typedef void (*obs_enum_module_callback_t)(void *param, obs_module_t *module); @@ -532,9 +547,11 @@ enum obs_base_effect { /** Returns a commonly used base effect */ EXPORT gs_effect_t *obs_get_base_effect(enum obs_base_effect effect); +#ifndef SWIG /* DEPRECATED: gets texture_rect default effect */ DEPRECATED EXPORT gs_effect_t *obs_get_default_rect_effect(void); +#endif /** Returns the primary obs signal handler */ EXPORT signal_handler_t *obs_get_signal_handler(void); @@ -542,8 +559,14 @@ EXPORT signal_handler_t *obs_get_signal_handler(void); /** Returns the primary obs procedure handler */ EXPORT proc_handler_t *obs_get_proc_handler(void); +#ifndef SWIG /** Renders the main view */ +DEPRECATED EXPORT void obs_render_main_view(void); +#endif + +/** Renders the last main output texture */ +EXPORT void obs_render_main_texture(void); /** Sets the master user volume */ EXPORT void obs_set_master_volume(float volume); @@ -591,6 +614,13 @@ EXPORT void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, EXPORT bool obs_set_audio_monitoring_device(const char *name, const char *id); EXPORT void obs_get_audio_monitoring_device(const char **name, const char **id); +EXPORT void obs_add_tick_callback( + void (*tick)(void *param, float seconds), + void *param); +EXPORT void obs_remove_tick_callback( + void (*tick)(void *param, float seconds), + void *param); + EXPORT void obs_add_main_render_callback( void (*draw)(void *param, uint32_t cx, uint32_t cy), void *param); @@ -950,6 +980,10 @@ EXPORT void obs_source_set_monitoring_type(obs_source_t *source, EXPORT enum obs_monitoring_type obs_source_get_monitoring_type( const obs_source_t *source); +/** Gets private front-end settings data. This data is saved/loaded + * automatically. Returns an incremented reference. */ +EXPORT obs_data_t *obs_source_get_private_settings(obs_source_t *item); + /* ------------------------------------------------------------------------- */ /* Functions used by sources */ @@ -1107,6 +1141,12 @@ EXPORT void obs_source_set_async_unbuffered(obs_source_t *source, bool unbuffered); EXPORT bool obs_source_async_unbuffered(const obs_source_t *source); +/** Used to decouple audio from video so that audio doesn't attempt to sync up + * with video. I.E. Audio acts independently. Only works when in unbuffered + * mode. */ +EXPORT void obs_source_set_async_decoupled(obs_source_t *source, bool decouple); +EXPORT bool obs_source_async_decoupled(const obs_source_t *source); + /* ------------------------------------------------------------------------- */ /* Transition-specific functions */ enum obs_transition_target { @@ -1165,9 +1205,16 @@ typedef void (*obs_transition_video_render_callback_t)(void *data, uint32_t cx, uint32_t cy); typedef float (*obs_transition_audio_mix_callback_t)(void *data, float t); +EXPORT float obs_transition_get_time(obs_source_t *transition); + EXPORT void obs_transition_video_render(obs_source_t *transition, obs_transition_video_render_callback_t callback); +/** Directly renders its sub-source instead of to texture. Returns false if no + * longer transitioning */ +EXPORT bool obs_transition_video_render_direct(obs_source_t *transition, + enum obs_transition_target target); + EXPORT bool obs_transition_audio_render(obs_source_t *transition, uint64_t *ts_out, struct obs_source_audio_mix *audio, uint32_t mixers, size_t channels, size_t sample_rate, @@ -1251,8 +1298,12 @@ EXPORT obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item); /** Gets the source of a scene item. */ EXPORT obs_source_t *obs_sceneitem_get_source(const obs_sceneitem_t *item); +/* FIXME: The following functions should be deprecated and replaced with a way + * to specify savable private user data. -Jim */ EXPORT void obs_sceneitem_select(obs_sceneitem_t *item, bool select); EXPORT bool obs_sceneitem_selected(const obs_sceneitem_t *item); +EXPORT bool obs_sceneitem_locked(const obs_sceneitem_t *item); +EXPORT bool obs_sceneitem_set_locked(obs_sceneitem_t *item, bool lock); /* Functions for getting/setting specific orientation of a scene item */ EXPORT void obs_sceneitem_set_pos(obs_sceneitem_t *item, const struct vec2 *pos); @@ -1320,6 +1371,10 @@ EXPORT enum obs_scale_type obs_sceneitem_get_scale_filter( EXPORT void obs_sceneitem_defer_update_begin(obs_sceneitem_t *item); EXPORT void obs_sceneitem_defer_update_end(obs_sceneitem_t *item); +/** Gets private front-end settings data. This data is saved/loaded + * automatically. Returns an incremented reference. */ +EXPORT obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item); + /* ------------------------------------------------------------------------- */ /* Outputs */ @@ -1519,6 +1574,12 @@ EXPORT bool obs_output_reconnecting(const obs_output_t *output); /** Pass a string of the last output error, for UI use */ EXPORT void obs_output_set_last_error(obs_output_t *output, const char *message); +EXPORT const char *obs_output_get_last_error(obs_output_t *output); + +EXPORT const char *obs_output_get_supported_video_codecs( + const obs_output_t *output); +EXPORT const char *obs_output_get_supported_audio_codecs( + const obs_output_t *output); /* ------------------------------------------------------------------------- */ /* Functions used by outputs */ @@ -1710,6 +1771,7 @@ EXPORT const char *obs_encoder_get_id(const obs_encoder_t *encoder); EXPORT uint32_t obs_get_encoder_caps(const char *encoder_id); +#ifndef SWIG /** Duplicates an encoder packet */ DEPRECATED EXPORT void obs_duplicate_encoder_packet(struct encoder_packet *dst, @@ -1717,6 +1779,7 @@ EXPORT void obs_duplicate_encoder_packet(struct encoder_packet *dst, DEPRECATED EXPORT void obs_free_encoder_packet(struct encoder_packet *packet); +#endif EXPORT void obs_encoder_packet_ref(struct encoder_packet *dst, struct encoder_packet *src); @@ -1800,6 +1863,10 @@ EXPORT void *obs_service_get_type_data(obs_service_t *service); EXPORT const char *obs_service_get_id(const obs_service_t *service); +/* NOTE: This function is temporary and should be removed/replaced at a later + * date. */ +EXPORT const char *obs_service_get_output_type(const obs_service_t *service); + /* ------------------------------------------------------------------------- */ /* Source frame allocation functions */ diff --git a/libobs/obsconfig.h.in b/libobs/obsconfig.h.in index f86962d..b978bdb 100644 --- a/libobs/obsconfig.h.in +++ b/libobs/obsconfig.h.in @@ -17,3 +17,7 @@ #define OBS_UNIX_STRUCTURE @OBS_UNIX_STRUCTURE@ #define BUILD_CAPTIONS @BUILD_CAPTIONS@ #define HAVE_DBUS @HAVE_DBUS@ +#define HAVE_PULSEAUDIO @HAVE_PULSEAUDIO@ +#define LIBOBS_IMAGEMAGICK_DIR_STYLE_6L 6 +#define LIBOBS_IMAGEMAGICK_DIR_STYLE_7GE 7 +#define LIBOBS_IMAGEMAGICK_DIR_STYLE @LIBOBS_IMAGEMAGICK_DIR_STYLE@ diff --git a/libobs/util/base.h b/libobs/util/base.h index 7d64b45..bf1b12f 100644 --- a/libobs/util/base.h +++ b/libobs/util/base.h @@ -76,7 +76,7 @@ EXPORT void base_set_crash_handler( EXPORT void blogva(int log_level, const char *format, va_list args); -#ifndef _MSC_VER +#if !defined(_MSC_VER) && !defined(SWIG) #define PRINTFATTR(f, a) __attribute__((__format__(__printf__, f, a))) #else #define PRINTFATTR(f, a) diff --git a/libobs/util/circlebuf.h b/libobs/util/circlebuf.h index 111f17a..e6eaca4 100644 --- a/libobs/util/circlebuf.h +++ b/libobs/util/circlebuf.h @@ -189,6 +189,48 @@ static inline void circlebuf_push_front(struct circlebuf *cb, const void *data, } } +static inline void circlebuf_push_back_zero(struct circlebuf *cb, size_t size) +{ + size_t new_end_pos = cb->end_pos + size; + + cb->size += size; + circlebuf_ensure_capacity(cb); + + if (new_end_pos > cb->capacity) { + size_t back_size = cb->capacity - cb->end_pos; + size_t loop_size = size - back_size; + + if (back_size) + memset((uint8_t*)cb->data + cb->end_pos, 0, back_size); + memset(cb->data, 0, loop_size); + + new_end_pos -= cb->capacity; + } else { + memset((uint8_t*)cb->data + cb->end_pos, 0, size); + } + + cb->end_pos = new_end_pos; +} + +static inline void circlebuf_push_front_zero(struct circlebuf *cb, size_t size) +{ + cb->size += size; + circlebuf_ensure_capacity(cb); + + if (cb->start_pos < size) { + size_t back_size = size - cb->start_pos; + + if (cb->start_pos) + memset(cb->data, 0, cb->start_pos); + + cb->start_pos = cb->capacity - back_size; + memset((uint8_t*)cb->data + cb->start_pos, 0, back_size); + } else { + cb->start_pos -= size; + memset((uint8_t*)cb->data + cb->start_pos, 0, size); + } +} + static inline void circlebuf_peek_front(struct circlebuf *cb, void *data, size_t size) { @@ -237,6 +279,11 @@ static inline void circlebuf_pop_front(struct circlebuf *cb, void *data, circlebuf_peek_front(cb, data, size); cb->size -= size; + if (!cb->size) { + cb->start_pos = cb->end_pos = 0; + return; + } + cb->start_pos += size; if (cb->start_pos >= cb->capacity) cb->start_pos -= cb->capacity; @@ -248,6 +295,11 @@ static inline void circlebuf_pop_back(struct circlebuf *cb, void *data, circlebuf_peek_front(cb, data, size); cb->size -= size; + if (!cb->size) { + cb->start_pos = cb->end_pos = 0; + return; + } + if (cb->end_pos <= size) cb->end_pos = cb->capacity - (size - cb->end_pos); else diff --git a/libobs/util/dstr.c b/libobs/util/dstr.c index ac56be7..9764504 100644 --- a/libobs/util/dstr.c +++ b/libobs/util/dstr.c @@ -262,49 +262,78 @@ wchar_t *wcsdepad(wchar_t *str) char **strlist_split(const char *str, char split_ch, bool include_empty) { - const char *cur_str = str; - const char *next_str; - const char *new_str; - DARRAY(char*) list; - - da_init(list); + const char *cur_str = str; + const char *next_str; + char * out = NULL; + size_t count = 0; + size_t total_size = 0; if (str) { + char **table; + char *offset; + size_t cur_idx = 0; + size_t cur_pos = 0; + next_str = strchr(str, split_ch); while (next_str) { size_t size = next_str - cur_str; if (size || include_empty) { - new_str = bstrdup_n(cur_str, size); - da_push_back(list, &new_str); + ++count; + total_size += size + 1; } - cur_str = next_str+1; + cur_str = next_str + 1; next_str = strchr(cur_str, split_ch); } if (*cur_str || include_empty) { - new_str = bstrdup(cur_str); - da_push_back(list, &new_str); + ++count; + total_size += strlen(cur_str) + 1; } + + /* ------------------ */ + + cur_pos = (count + 1) * sizeof(char *); + total_size += cur_pos; + out = bmalloc(total_size); + offset = out + cur_pos; + table = (char **)out; + + /* ------------------ */ + + next_str = strchr(str, split_ch); + cur_str = str; + + while (next_str) { + size_t size = next_str - cur_str; + + if (size || include_empty) { + table[cur_idx++] = offset; + strncpy(offset, cur_str, size); + offset[size] = 0; + offset += size + 1; + } + + cur_str = next_str + 1; + next_str = strchr(cur_str, split_ch); + } + + if (*cur_str || include_empty) { + table[cur_idx++] = offset; + strcpy(offset, cur_str); + } + + table[cur_idx] = NULL; } - new_str = NULL; - da_push_back(list, &new_str); - - return list.array; + return (char**)out; } void strlist_free(char **strlist) { - if (strlist) { - char **temp = strlist; - while (*temp) - bfree(*(temp++)); - - bfree(strlist); - } + bfree(strlist); } void dstr_init_copy_strref(struct dstr *dst, const struct strref *src) diff --git a/libobs/util/platform-cocoa.m b/libobs/util/platform-cocoa.m index b9f392c..f93341c 100644 --- a/libobs/util/platform-cocoa.m +++ b/libobs/util/platform-cocoa.m @@ -352,3 +352,68 @@ int os_get_logical_cores(void) os_get_cores_internal(); return logical_cores; } + +static inline bool os_get_sys_memory_usage_internal(vm_statistics_t vmstat) +{ + mach_msg_type_number_t out_count = HOST_VM_INFO_COUNT; + if (host_statistics(mach_host_self(), HOST_VM_INFO, + (host_info_t)vmstat, &out_count) != KERN_SUCCESS) + return false; + return true; +} + +uint64_t os_get_sys_free_size(void) +{ + vm_statistics_data_t vmstat = {}; + if (!os_get_sys_memory_usage_internal(&vmstat)) + return 0; + + return vmstat.free_count * vm_page_size; +} + +#ifndef MACH_TASK_BASIC_INFO +typedef task_basic_info_data_t mach_task_basic_info_data_t; +#endif + +static inline bool os_get_proc_memory_usage_internal( + mach_task_basic_info_data_t *taskinfo) +{ +#ifdef MACH_TASK_BASIC_INFO + const task_flavor_t flavor = MACH_TASK_BASIC_INFO; + mach_msg_type_number_t out_count = MACH_TASK_BASIC_INFO_COUNT; +#else + const task_flavor_t flavor = TASK_BASIC_INFO; + mach_msg_type_number_t out_count = TASK_BASIC_INFO_COUNT; +#endif + if (task_info(mach_task_self(), flavor, + (task_info_t)taskinfo, &out_count) != KERN_SUCCESS) + return false; + return true; +} + +bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage) +{ + mach_task_basic_info_data_t taskinfo = {}; + if (!os_get_proc_memory_usage_internal(&taskinfo)) + return false; + + usage->resident_size = taskinfo.resident_size; + usage->virtual_size = taskinfo.virtual_size; + return true; +} + +uint64_t os_get_proc_resident_size(void) +{ + mach_task_basic_info_data_t taskinfo = {}; + if (!os_get_proc_memory_usage_internal(&taskinfo)) + return 0; + return taskinfo.resident_size; +} + +uint64_t os_get_proc_virtual_size(void) +{ + mach_task_basic_info_data_t taskinfo = {}; + if (!os_get_proc_memory_usage_internal(&taskinfo)) + return 0; + return taskinfo.virtual_size; +} diff --git a/libobs/util/platform-nix.c b/libobs/util/platform-nix.c index 26f606d..b303e30 100644 --- a/libobs/util/platform-nix.c +++ b/libobs/util/platform-nix.c @@ -33,6 +33,16 @@ #if !defined(__APPLE__) #include #include +#ifdef __FreeBSD__ +#include +#include +#include +#include +#include +#include +#else +#include +#endif #include #endif @@ -628,8 +638,6 @@ static int physical_cores = 0; static int logical_cores = 0; static bool core_count_initialized = false; -/* return sysconf(_SC_NPROCESSORS_ONLN); */ - static void os_get_cores_internal(void) { if (core_count_initialized) @@ -639,29 +647,120 @@ static void os_get_cores_internal(void) logical_cores = sysconf(_SC_NPROCESSORS_ONLN); -#ifndef __linux__ - physical_cores = logical_cores; -#else - char *text = os_quick_read_utf8_file("/proc/cpuinfo"); - char *core_id = text; +#if defined(__linux__) + int physical_id = -1; + int last_physical_id = -1; + int core_count = 0; + char *line = NULL; + size_t linecap = 0; + + FILE *fp; + struct dstr proc_phys_id; + struct dstr proc_phys_ids; + + fp = fopen("/proc/cpuinfo", "r"); + if (!fp) + return; + + dstr_init(&proc_phys_id); + dstr_init(&proc_phys_ids); + + while (getline(&line, &linecap, fp) != -1) { + if (!strncmp(line, "physical id", 11)) { + char *start = strchr(line, ':'); + if (!start || *(++start) == '\0') + continue; + + physical_id = atoi(start); + dstr_free(&proc_phys_id); + dstr_init(&proc_phys_id); + dstr_catf(&proc_phys_id, "%d", physical_id); + } + + if (!strncmp(line, "cpu cores", 9)) { + char *start = strchr(line, ':'); + if (!start || *(++start) == '\0') + continue; + + if (dstr_is_empty(&proc_phys_ids) || + (!dstr_is_empty(&proc_phys_ids) && + !dstr_find(&proc_phys_ids, proc_phys_id.array))) { + dstr_cat_dstr(&proc_phys_ids, &proc_phys_id); + dstr_cat(&proc_phys_ids, " "); + core_count += atoi(start); + } + } + + if (*line == '\n' && physical_id != last_physical_id) { + last_physical_id = physical_id; + } + } + + if (core_count == 0) + physical_cores = logical_cores; + else + physical_cores = core_count; + + fclose(fp); + dstr_free(&proc_phys_ids); + dstr_free(&proc_phys_id); + free(line); +#elif defined(__FreeBSD__) + char *text = os_quick_read_utf8_file("/var/run/dmesg.boot"); + char *core_count = text; + int packages = 0; + int cores = 0; + + struct dstr proc_packages; + struct dstr proc_cores; + dstr_init(&proc_packages); + dstr_init(&proc_cores); if (!text || !*text) { physical_cores = logical_cores; return; } - for (;;) { - core_id = strstr(core_id, "\ncore id"); - if (!core_id) - break; - physical_cores++; - core_id++; - } + core_count = strstr(core_count, "\nFreeBSD/SMP: "); + if (!core_count) + goto FreeBSD_cores_cleanup; - if (physical_cores == 0) + core_count++; + core_count = strstr(core_count, "\nFreeBSD/SMP: "); + if (!core_count) + goto FreeBSD_cores_cleanup; + + core_count = strstr(core_count, ": "); + core_count += 2; + size_t len = strcspn(core_count, " "); + dstr_ncopy(&proc_packages, core_count, len); + + core_count = strstr(core_count, "package(s) x "); + if (!core_count) + goto FreeBSD_cores_cleanup; + + core_count += 13; + len = strcspn(core_count, " "); + dstr_ncopy(&proc_cores, core_count, len); + + FreeBSD_cores_cleanup: + if (!dstr_is_empty(&proc_packages)) + packages = atoi(proc_packages.array); + if (!dstr_is_empty(&proc_cores)) + cores = atoi(proc_cores.array); + + if (packages == 0) physical_cores = logical_cores; + else if (cores == 0) + physical_cores = packages; + else + physical_cores = packages * cores; + dstr_free(&proc_cores); + dstr_free(&proc_packages); bfree(text); +#else + physical_cores = logical_cores; #endif } @@ -678,6 +777,120 @@ int os_get_logical_cores(void) os_get_cores_internal(); return logical_cores; } + +#ifdef __FreeBSD__ +uint64_t os_get_sys_free_size(void) +{ + uint64_t mem_free = 0; + size_t length = sizeof(mem_free); + if (sysctlbyname("vm.stats.vm.v_free_count", &mem_free, &length, + NULL, 0) < 0) + return 0; + return mem_free; +} + +static inline bool os_get_proc_memory_usage_internal(struct kinfo_proc *kinfo) +{ + int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; + size_t length = sizeof(*kinfo); + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), kinfo, &length, + NULL, 0) < 0) + return false; + return true; +} + +bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage) +{ + struct kinfo_proc kinfo; + if (!os_get_proc_memory_usage_internal(&kinfo)) + return false; + + usage->resident_size = + (uint64_t)kinfo.ki_rssize * sysconf(_SC_PAGESIZE); + usage->virtual_size = (uint64_t)kinfo.ki_size; + return true; +} + +uint64_t os_get_proc_resident_size(void) +{ + struct kinfo_proc kinfo; + if (!os_get_proc_memory_usage_internal(&kinfo)) + return 0; + return (uint64_t)kinfo.ki_rssize * sysconf(_SC_PAGESIZE); +} + +uint64_t os_get_proc_virtual_size(void) +{ + struct kinfo_proc kinfo; + if (!os_get_proc_memory_usage_internal(&kinfo)) + return 0; + return (uint64_t)kinfo.ki_size; +} +#else +uint64_t os_get_sys_free_size(void) {return 0;} + +typedef struct +{ + unsigned long virtual_size; + unsigned long resident_size; + unsigned long share_pages; + unsigned long text; + unsigned long library; + unsigned long data; + unsigned long dirty_pages; +} statm_t; + +static inline bool os_get_proc_memory_usage_internal(statm_t *statm) +{ + const char *statm_path = "/proc/self/statm"; + + FILE *f = fopen(statm_path, "r"); + if (!f) + return false; + + if (fscanf(f, "%ld %ld %ld %ld %ld %ld %ld", + &statm->virtual_size, + &statm->resident_size, + &statm->share_pages, + &statm->text, + &statm->library, + &statm->data, + &statm->dirty_pages) != 7) { + fclose(f); + return false; + } + + fclose(f); + return true; +} + +bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage) +{ + statm_t statm = {}; + if (!os_get_proc_memory_usage_internal(&statm)) + return false; + + usage->resident_size = statm.resident_size; + usage->virtual_size = statm.virtual_size; + return true; +} + +uint64_t os_get_proc_resident_size(void) +{ + statm_t statm = {}; + if (!os_get_proc_memory_usage_internal(&statm)) + return 0; + return (uint64_t)statm.resident_size; +} + +uint64_t os_get_proc_virtual_size(void) +{ + statm_t statm = {}; + if (!os_get_proc_memory_usage_internal(&statm)) + return 0; + return (uint64_t)statm.virtual_size; +} +#endif #endif uint64_t os_get_free_disk_space(const char *dir) diff --git a/libobs/util/platform-windows.c b/libobs/util/platform-windows.c index 78ab492..5435b68 100644 --- a/libobs/util/platform-windows.c +++ b/libobs/util/platform-windows.c @@ -14,16 +14,19 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define PSAPI_VERSION 1 #include #include #include #include #include +#include #include "base.h" #include "platform.h" #include "darray.h" #include "dstr.h" +#include "windows/win-registry.h" #include "windows/win-version.h" #include "../../deps/w32-pthreads/pthread.h" @@ -47,7 +50,7 @@ static inline uint32_t get_winver(void) winver = (ver.major << 16) | ver.minor; } - return winver; + return winver; } void *os_dlopen(const char *path) @@ -798,6 +801,32 @@ bool is_64_bit_windows(void) #endif } +void get_reg_dword(HKEY hkey, LPCWSTR sub_key, LPCWSTR value_name, + struct reg_dword *info) +{ + struct reg_dword reg = {0}; + HKEY key; + LSTATUS status; + + status = RegOpenKeyEx(hkey, sub_key, 0, KEY_READ, &key); + + if (status != ERROR_SUCCESS) { + info->status = status; + info->size = 0; + info->return_value = 0; + return; + } + + reg.size = sizeof(reg.return_value); + + reg.status = RegQueryValueExW(key, value_name, NULL, NULL, + (LPBYTE)®.return_value, ®.size); + + RegCloseKey(key); + + *info = reg; +} + #define WINVER_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" void get_win_ver(struct win_version_info *info) @@ -812,7 +841,7 @@ void get_win_ver(struct win_version_info *info) get_dll_ver(L"kernel32", &ver); got_version = true; - if (ver.major == 10 && ver.revis == 0) { + if (ver.major == 10) { HKEY key; DWORD size, win10_revision; LSTATUS status; @@ -827,7 +856,8 @@ void get_win_ver(struct win_version_info *info) status = RegQueryValueExW(key, L"UBR", NULL, NULL, (LPBYTE)&win10_revision, &size); if (status == ERROR_SUCCESS) - ver.revis = (int)win10_revision; + ver.revis = (int)win10_revision > ver.revis ? + (int)win10_revision : ver.revis; RegCloseKey(key); } @@ -947,6 +977,55 @@ int os_get_logical_cores(void) return logical_cores; } +static inline bool os_get_sys_memory_usage_internal(MEMORYSTATUSEX *msex) +{ + if (!GlobalMemoryStatusEx(msex)) + return false; + return true; +} + +uint64_t os_get_sys_free_size(void) +{ + MEMORYSTATUSEX msex = {sizeof(MEMORYSTATUSEX)}; + if (!os_get_sys_memory_usage_internal(&msex)) + return 0; + return msex.ullAvailPhys; +} + +static inline bool os_get_proc_memory_usage_internal(PROCESS_MEMORY_COUNTERS *pmc) +{ + if (!GetProcessMemoryInfo(GetCurrentProcess(), pmc, sizeof(*pmc))) + return false; + return true; +} + +bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage) +{ + PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)}; + if (!os_get_proc_memory_usage_internal(&pmc)) + return false; + + usage->resident_size = pmc.WorkingSetSize; + usage->virtual_size = pmc.PagefileUsage; + return true; +} + +uint64_t os_get_proc_resident_size(void) +{ + PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)}; + if (!os_get_proc_memory_usage_internal(&pmc)) + return 0; + return pmc.WorkingSetSize; +} + +uint64_t os_get_proc_virtual_size(void) +{ + PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)}; + if (!os_get_proc_memory_usage_internal(&pmc)) + return 0; + return pmc.PagefileUsage; +} + uint64_t os_get_free_disk_space(const char *dir) { wchar_t *wdir = NULL; diff --git a/libobs/util/platform.c b/libobs/util/platform.c index 008cbbb..6826ea4 100644 --- a/libobs/util/platform.c +++ b/libobs/util/platform.c @@ -183,7 +183,8 @@ size_t os_fread_utf8(FILE *file, char **pstr) /* remove the ghastly BOM if present */ fseek(file, 0, SEEK_SET); - fread(bom, 1, 3, file); + size_t size_read = fread(bom, 1, 3, file); + (void)size_read; offset = (astrcmp_n(bom, "\xEF\xBB\xBF", 3) == 0) ? 3 : 0; diff --git a/libobs/util/platform.h b/libobs/util/platform.h index bf5e2cf..cd5eea2 100644 --- a/libobs/util/platform.h +++ b/libobs/util/platform.h @@ -181,6 +181,18 @@ EXPORT void os_breakpoint(void); EXPORT int os_get_physical_cores(void); EXPORT int os_get_logical_cores(void); +EXPORT uint64_t os_get_sys_free_size(void); + +struct os_proc_memory_usage { + uint64_t resident_size; + uint64_t virtual_size; +}; +typedef struct os_proc_memory_usage os_proc_memory_usage_t; + +EXPORT bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage); +EXPORT uint64_t os_get_proc_resident_size(void); +EXPORT uint64_t os_get_proc_virtual_size(void); + #ifdef _MSC_VER #define strtoll _strtoi64 #if _MSC_VER < 1900 diff --git a/libobs/util/profiler.c b/libobs/util/profiler.c index d82c4fd..9b57f2c 100644 --- a/libobs/util/profiler.c +++ b/libobs/util/profiler.c @@ -260,13 +260,8 @@ static bool enabled = false; static pthread_mutex_t root_mutex = PTHREAD_MUTEX_INITIALIZER; static DARRAY(profile_root_entry) root_entries; -#ifdef _MSC_VER -static __declspec(thread) profile_call *thread_context = NULL; -static __declspec(thread) bool thread_enabled = true; -#else -static __thread profile_call *thread_context = NULL; -static __thread bool thread_enabled = true; -#endif +static THREAD_LOCAL profile_call *thread_context = NULL; +static THREAD_LOCAL bool thread_enabled = true; void profiler_start(void) { diff --git a/libobs/util/threading.h b/libobs/util/threading.h index 5021292..0ae27a2 100644 --- a/libobs/util/threading.h +++ b/libobs/util/threading.h @@ -78,6 +78,12 @@ EXPORT int os_sem_wait(os_sem_t *sem); EXPORT void os_set_thread_name(const char *name); +#ifdef _MSC_VER +#define THREAD_LOCAL __declspec(thread) +#else +#define THREAD_LOCAL __thread +#endif + #ifdef __cplusplus } diff --git a/libobs/util/windows/win-registry.h b/libobs/util/windows/win-registry.h new file mode 100644 index 0000000..9e63a58 --- /dev/null +++ b/libobs/util/windows/win-registry.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015 Hugh Bailey + * Copyright (c) 2017 Ryan Foster + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include +#include "../c99defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct reg_dword { + LSTATUS status; + DWORD size; + DWORD return_value; +}; + +EXPORT void get_reg_dword(HKEY hkey, LPCWSTR sub_key, LPCWSTR value_name, + struct reg_dword *info); + +#ifdef __cplusplus +} +#endif diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8e90662..e6fa3e5 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,3 +1,8 @@ +option(DISABLE_PLUGINS "Disable building of OBS plugins" OFF) +if(DISABLE_PLUGINS) + message(STATUS "DISABLE_PLUGINS is set; building of plugins is disabled.") + return() +endif() if(WIN32) option(BUILD_CA_ENCODER "Build CoreAudio encoder module" ON) @@ -11,13 +16,10 @@ 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() + 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() if (MSVC) add_subdirectory(win-ivcam) diff --git a/plugins/coreaudio-encoder/data/locale/el-GR.ini b/plugins/coreaudio-encoder/data/locale/el-GR.ini index 49c41af..1cda548 100644 --- a/plugins/coreaudio-encoder/data/locale/el-GR.ini +++ b/plugins/coreaudio-encoder/data/locale/el-GR.ini @@ -1,4 +1,6 @@ CoreAudioAAC="Κωδικοποιητής AAC CoreAudio" Bitrate="Ρυθμός μετάδοσης bit" AllowHEAAC="Να Επιτρέπεται το HE-AAC" +OutputSamplerate="Ρυθμός Δειγματολειψίας Εξόδου" +UseInputSampleRate="Χρήση Ρυθμού Δειγματολειψίας Εισόδου (OBS) (μπορεί να περιλαμβάνει μη υποστηριζόμενα bitrates)" diff --git a/plugins/coreaudio-encoder/data/locale/sk-SK.ini b/plugins/coreaudio-encoder/data/locale/sk-SK.ini index a772419..59bbfc4 100644 --- a/plugins/coreaudio-encoder/data/locale/sk-SK.ini +++ b/plugins/coreaudio-encoder/data/locale/sk-SK.ini @@ -1,4 +1,6 @@ CoreAudioAAC="CoreAudio AAC enkodér" Bitrate="Dátový tok" AllowHEAAC="Povoliť HE-AAC" +OutputSamplerate="Vzorkovacia frekvencia výstupu" +UseInputSampleRate="Použiť vstupnú (OBS) vzorkovaciu frekvenciu (môže obsahovať nepodporované dátové toky)" diff --git a/plugins/coreaudio-encoder/data/locale/uk-UA.ini b/plugins/coreaudio-encoder/data/locale/uk-UA.ini index 6f81244..b6ae2f4 100644 --- a/plugins/coreaudio-encoder/data/locale/uk-UA.ini +++ b/plugins/coreaudio-encoder/data/locale/uk-UA.ini @@ -2,5 +2,5 @@ CoreAudioAAC="CoreAudio AAC енкодер" Bitrate="Бітрейт" AllowHEAAC="Дозволити HE-AAC" OutputSamplerate="Частота дискретизації виводу" -UseInputSampleRate="Використати частоту дискретизації вводу (OBS) (може відображатися непідтримуваний бітрейт)" +UseInputSampleRate="Використати частоту дискретизації вводу (до OBS) (може показувати непідтримуваний бітрейт)" diff --git a/plugins/coreaudio-encoder/data/locale/vi-VN.ini b/plugins/coreaudio-encoder/data/locale/vi-VN.ini new file mode 100644 index 0000000..26387b7 --- /dev/null +++ b/plugins/coreaudio-encoder/data/locale/vi-VN.ini @@ -0,0 +1,4 @@ +CoreAudioAAC="Bộ mã hóa CoreAudio AAC" +Bitrate="Bitrate" +AllowHEAAC="Cho phép HE-AAC" + diff --git a/plugins/coreaudio-encoder/encoder.cpp b/plugins/coreaudio-encoder/encoder.cpp index 9d6117a..972376d 100644 --- a/plugins/coreaudio-encoder/encoder.cpp +++ b/plugins/coreaudio-encoder/encoder.cpp @@ -546,7 +546,7 @@ static void *aac_create(obs_data_t *settings, obs_encoder_t *encoder) UInt32 rate_control = kAudioCodecBitRateControlMode_Constant; - if (obs_data_get_bool(settings, "allow he-aac")) { + if (obs_data_get_bool(settings, "allow he-aac") && ca->channels != 3) { ca->allowed_formats = &aac_formats; } else { ca->allowed_formats = &aac_lc_formats; @@ -601,6 +601,35 @@ static void *aac_create(obs_data_t *settings, obs_encoder_t *encoder) kAudioConverterCurrentOutputStreamDescription, &size, &out)); + /* + * Fix channel map differences between CoreAudio AAC, FFmpeg, Wav + * New channel mappings below assume 2.1, 4.1, 5.1, 7.1 resp. + */ + if (ca->channels == 3) { + SInt32 channelMap3[3] = {2, 0, 1}; + AudioConverterSetProperty(ca->converter, + kAudioConverterChannelMap, + sizeof(channelMap3), channelMap3); + + } else if (ca->channels == 5) { + SInt32 channelMap5[5] = {2, 0, 1, 3, 4}; + AudioConverterSetProperty(ca->converter, + kAudioConverterChannelMap, + sizeof(channelMap5), channelMap5); + + } else if (ca->channels == 6) { + SInt32 channelMap6[6] = {2, 0, 1, 4, 5, 3}; + AudioConverterSetProperty(ca->converter, + kAudioConverterChannelMap, + sizeof(channelMap6), channelMap6); + + } else if (ca->channels == 8) { + SInt32 channelMap8[8] = {2, 0, 1, 6, 7, 4, 5, 3}; + AudioConverterSetProperty(ca->converter, + kAudioConverterChannelMap, + sizeof(channelMap8), channelMap8); + } + ca->in_frame_size = in.mBytesPerFrame; ca->in_packets = out.mFramesPerPacket / in.mFramesPerPacket; ca->in_bytes_required = ca->in_packets * ca->in_frame_size; @@ -877,10 +906,9 @@ static bool aac_extra_data(void *data, uint8_t **extra_data, size_t *size) } static asbd_builder fill_common_asbd_fields(asbd_builder builder, - bool in=false) + bool in=false, UInt32 channels=2) { - UInt32 bytes_per_frame = 8; - UInt32 channels = 2; + UInt32 bytes_per_frame = sizeof(float) * channels; UInt32 bits_per_channel = bytes_per_frame / channels * 8; builder.channels_per_frame(channels); @@ -908,9 +936,9 @@ static AudioStreamBasicDescription get_default_in_asbd() .asbd; } -static asbd_builder get_default_out_asbd_builder() +static asbd_builder get_default_out_asbd_builder(UInt32 channels) { - return fill_common_asbd_fields(asbd_builder()) + return fill_common_asbd_fields(asbd_builder(), false, channels) .sample_rate(44100); } @@ -975,7 +1003,7 @@ static bool find_best_match(DStr &log, ca_encoder *ca, UInt32 bitrate, log_to_dstr(log, ca, "Trying %s (0x%x)\n", format_id_to_str(format_id), format_id); - auto out = get_default_out_asbd_builder() + auto out = get_default_out_asbd_builder(2) .format_id(format_id) .asbd; @@ -1206,6 +1234,11 @@ static vector get_bitrates(DStr &log, ca_encoder *ca, Float64 samplerate) { vector bitrates; + struct obs_audio_info aoi; + int channels; + + obs_get_audio_info(&aoi); + channels = get_audio_channels(aoi.speakers); auto handle_bitrate = [&](UInt32 bitrate) { @@ -1240,7 +1273,7 @@ static vector get_bitrates(DStr &log, ca_encoder *ca, static_cast(format_id), samplerate); - auto out = get_default_out_asbd_builder() + auto out = get_default_out_asbd_builder(channels) .format_id(format_id) .sample_rate(samplerate) .asbd; diff --git a/plugins/decklink/audio-repack.c b/plugins/decklink/audio-repack.c index f4bedfe..02d0879 100644 --- a/plugins/decklink/audio-repack.c +++ b/plugins/decklink/audio-repack.c @@ -21,52 +21,64 @@ int check_buffer(struct audio_repack *repack, } /* - Swap channel between LFE and FC, and - squash data array - - | FL | FR |LFE | FC | BL | BR |emp |emp | - | | x | | - | FL | FR | FC |LFE | BL | BR | + * Swap channel 3 & 4, 5 & 7, 6 & 8 and squash arrays + * 2.1: + * + * | FL | FR | LFE | emp | emp | emp |emp |emp | + * | | | + * | FL | FR | LFE | + * 4.0 (quad): + * + * | FL | FR | BR | BL | emp | emp |emp |emp | + * | | x | + * | FL | FR | BL | BC | + * + * 4.1: + * + * | FL | FR |LFE | FC | BC | emp |emp |emp | + * | | x | | + * | FL | FR | FC |LFE | BC | + * + * 5.1: + * + * | FL | FR |LFE | FC |(emp|emp)|(BL|BR)| + * | | x x + * | FL | FR | FC |LFE | BL | BR | + * + * 7.1: + * + * | FL | FR |LFE | FC |( SL | SR )|(BL |BR )| + * | | x X + * | FL | FR | FC |LFE |( BL | BR )|(SL |SR )| */ -int repack_8to6ch_swap23(struct audio_repack *repack, + +int repack_squash_swap(struct audio_repack *repack, const uint8_t *bsrc, uint32_t frame_count) { if (check_buffer(repack, frame_count) < 0) return -1; + int squash = repack->extra_dst_size; const __m128i *src = (__m128i *)bsrc; const __m128i *esrc = src + frame_count; - uint32_t *dst = (uint32_t *)repack->packet_buffer; - while (src != esrc) { - __m128i target = _mm_load_si128(src++); - __m128i buf = _mm_shufflelo_epi16(target, _MM_SHUFFLE(2, 3, 1, 0)); - _mm_storeu_si128((__m128i *)dst, buf); - dst += 3; - } - - return 0; -} - -/* - Swap channel between LFE and FC - - | FL | FR |LFE | FC | BL | BR |SBL |SBR | - | | x | | | | - | FL | FR | FC |LFE | BL | BR |SBL |SBR | - */ -int repack_8ch_swap23(struct audio_repack *repack, - const uint8_t *bsrc, uint32_t frame_count) -{ - if (check_buffer(repack, frame_count) < 0) - return -1; - - const __m128i *src = (__m128i *)bsrc; - const __m128i *esrc = src + frame_count; - __m128i *dst = (__m128i *)repack->packet_buffer; - while (src != esrc) { - __m128i target = _mm_load_si128(src++); - __m128i buf = _mm_shufflelo_epi16(target, _MM_SHUFFLE(2, 3, 1, 0)); - _mm_store_si128(dst++, buf); + uint16_t *dst = (uint16_t *)repack->packet_buffer; + /* 2.1 audio does not require re-ordering but still needs squashing + * in order to avoid sampling issues. + */ + if (squash == 5) { + while (src != esrc) { + __m128i target = _mm_load_si128(src++); + _mm_storeu_si128((__m128i *)dst, target); + dst += 8 - squash; + } + } else { + while (src != esrc) { + __m128i target = _mm_load_si128(src++); + __m128i buf = _mm_shufflelo_epi16(target, _MM_SHUFFLE(2, 3, 1, 0)); + __m128i buf2 = _mm_shufflehi_epi16(buf, _MM_SHUFFLE(1, 0, 3, 2)); + _mm_storeu_si128((__m128i *)dst, buf2); + dst += 8 - squash; + } } return 0; @@ -81,18 +93,38 @@ int audio_repack_init(struct audio_repack *repack, return -1; switch (repack_mode) { - case repack_mode_8to6ch_swap23: - repack->base_src_size = 8 * (16 / 8); - repack->base_dst_size = 6 * (16 / 8); - repack->extra_dst_size = 2; - repack->repack_func = &repack_8to6ch_swap23; + case repack_mode_8to3ch_swap23: + repack->base_src_size = 8 * (16 / 8); + repack->base_dst_size = 3 * (16 / 8); + repack->extra_dst_size = 5; + repack->repack_func = &repack_squash_swap; + break; + case repack_mode_8to4ch_swap23: + repack->base_src_size = 8 * (16 / 8); + repack->base_dst_size = 4 * (16 / 8); + repack->extra_dst_size = 4; + repack->repack_func = &repack_squash_swap; break; - case repack_mode_8ch_swap23: - repack->base_src_size = 8 * (16 / 8); - repack->base_dst_size = 8 * (16 / 8); + case repack_mode_8to5ch_swap23: + repack->base_src_size = 8 * (16 / 8); + repack->base_dst_size = 5 * (16 / 8); + repack->extra_dst_size = 3; + repack->repack_func = &repack_squash_swap; + break; + + case repack_mode_8to6ch_swap23: + repack->base_src_size = 8 * (16 / 8); + repack->base_dst_size = 6 * (16 / 8); + repack->extra_dst_size = 2; + repack->repack_func = &repack_squash_swap; + break; + + case repack_mode_8ch_swap23_swap46_swap57: + repack->base_src_size = 8 * (16 / 8); + repack->base_dst_size = 8 * (16 / 8); repack->extra_dst_size = 0; - repack->repack_func = &repack_8ch_swap23; + repack->repack_func = &repack_squash_swap; break; default: return -1; diff --git a/plugins/decklink/audio-repack.h b/plugins/decklink/audio-repack.h index b5116e7..e063e13 100644 --- a/plugins/decklink/audio-repack.h +++ b/plugins/decklink/audio-repack.h @@ -26,8 +26,11 @@ struct audio_repack { }; enum _audio_repack_mode { + repack_mode_8to3ch_swap23, + repack_mode_8to4ch_swap23, + repack_mode_8to5ch_swap23, repack_mode_8to6ch_swap23, - repack_mode_8ch_swap23, + repack_mode_8ch_swap23_swap46_swap57, }; typedef enum _audio_repack_mode audio_repack_mode_t; diff --git a/plugins/decklink/data/locale/ca-ES.ini b/plugins/decklink/data/locale/ca-ES.ini index 38f07f8..c6df023 100644 --- a/plugins/decklink/data/locale/ca-ES.ini +++ b/plugins/decklink/data/locale/ca-ES.ini @@ -3,10 +3,18 @@ Device="Dispositiu" Mode="Mode" Buffering="Usa memòria intermèdia" PixelFormat="Format de píxel" +ColorSpace="Espai de color YUV" +ColorSpace.Default="Per defecte" +ColorRange="Gamma de color YUV" +ColorRange.Default="Per defecte" +ColorRange.Partial="Parcial" +ColorRange.Full="Complet" ChannelFormat="Canal" ChannelFormat.None="Cap" ChannelFormat.2_0ch="Estèreo " -ChannelFormat.5_1ch="5.1" -ChannelFormat.5_1chBack="5.1 (posterior)" -ChannelFormat.7_1ch="7.1" +ChannelFormat.2_1ch="Canals 2.1" +ChannelFormat.4_0ch="4" +ChannelFormat.4_1ch="4.1" +ChannelFormat.5_1ch="Canals 5.1" +ChannelFormat.7_1ch="Canals 7.1" diff --git a/plugins/decklink/data/locale/cs-CZ.ini b/plugins/decklink/data/locale/cs-CZ.ini index ae74b40..081819c 100644 --- a/plugins/decklink/data/locale/cs-CZ.ini +++ b/plugins/decklink/data/locale/cs-CZ.ini @@ -3,10 +3,18 @@ Device="Zařízení" Mode="Mód" Buffering="Použít vyrovnávací paměť" PixelFormat="Formát pixelů" +ColorSpace="Prostor barev YUV" +ColorSpace.Default="Výchozí" +ColorRange="Rozsah barev YUV" +ColorRange.Default="Výchozí" +ColorRange.Partial="Částečný" +ColorRange.Full="Plný" ChannelFormat="Kanály" ChannelFormat.None="Žádný" ChannelFormat.2_0ch="Stereo" -ChannelFormat.5_1ch="5.1" -ChannelFormat.5_1chBack="5.1 (zadní)" -ChannelFormat.7_1ch="7.1" +ChannelFormat.2_1ch="2.1ch" +ChannelFormat.4_0ch="4ch" +ChannelFormat.4_1ch="4.1ch" +ChannelFormat.5_1ch="5.1ch" +ChannelFormat.7_1ch="7.1ch" diff --git a/plugins/decklink/data/locale/da-DK.ini b/plugins/decklink/data/locale/da-DK.ini index 66d9480..8ccdb60 100644 --- a/plugins/decklink/data/locale/da-DK.ini +++ b/plugins/decklink/data/locale/da-DK.ini @@ -2,11 +2,19 @@ BlackmagicDevice="Blackmagic-enhed" Device="Enhed" Mode="Tilstand" Buffering="Brug buffering" -PixelFormat="Pixel format" +PixelFormat="Pixelformat" +ColorSpace="YUV farverum" +ColorSpace.Default="Standard" +ColorRange="YUV-farveområde" +ColorRange.Default="Standard" +ColorRange.Partial="Delvis" +ColorRange.Full="Fuld" ChannelFormat="Kanal" ChannelFormat.None="Intet" ChannelFormat.2_0ch="2kan" -ChannelFormat.5_1ch="5.1kan" -ChannelFormat.5_1chBack="5.1kan (bag)" -ChannelFormat.7_1ch="7.1kan" +ChannelFormat.2_1ch="2.1 kasnals" +ChannelFormat.4_0ch="4 kanals" +ChannelFormat.4_1ch="4.1 kanals" +ChannelFormat.5_1ch="5.1 kanals" +ChannelFormat.7_1ch="7.1 kanals" diff --git a/plugins/decklink/data/locale/de-DE.ini b/plugins/decklink/data/locale/de-DE.ini index d9e7f2b..5694853 100644 --- a/plugins/decklink/data/locale/de-DE.ini +++ b/plugins/decklink/data/locale/de-DE.ini @@ -3,10 +3,18 @@ Device="Gerät" Mode="Modus" Buffering="Buffering benutzen" PixelFormat="Pixelformat" +ColorSpace="YUV-Farbmatrix" +ColorSpace.Default="Standard" +ColorRange="YUV-Farbbereich" +ColorRange.Default="Standard" +ColorRange.Partial="Begrenzt" +ColorRange.Full="Voll" ChannelFormat="Kanal" ChannelFormat.None="Keins" ChannelFormat.2_0ch="2 Kanal" +ChannelFormat.2_1ch="2.1 Kanal" +ChannelFormat.4_0ch="4 Kanal" +ChannelFormat.4_1ch="4.1 Kanal" ChannelFormat.5_1ch="5.1 Kanal" -ChannelFormat.5_1chBack="5.1 Kanal (hinten)" ChannelFormat.7_1ch="7.1 Kanal" diff --git a/plugins/decklink/data/locale/el-GR.ini b/plugins/decklink/data/locale/el-GR.ini index 9c7ecfd..a77c8fa 100644 --- a/plugins/decklink/data/locale/el-GR.ini +++ b/plugins/decklink/data/locale/el-GR.ini @@ -3,4 +3,13 @@ Device="Συσκευή" Mode="Λειτουργία" Buffering="Χρήση ενδιάμεσης μνήμης" PixelFormat="Μορφή pixel" +ColorSpace="Χώρος Χρωμάτων YUV" +ColorSpace.Default="Προεπιλογή" +ColorRange="Έκταση Χρωμάτων YUV" +ColorRange.Default="Προεπιλογή" +ColorRange.Partial="Μερική" +ColorRange.Full="Πλήρης" +ChannelFormat="Κανάλι" +ChannelFormat.None="Κανένα" +ChannelFormat.2_0ch="2 Καναλιών" diff --git a/plugins/decklink/data/locale/en-US.ini b/plugins/decklink/data/locale/en-US.ini index 202070d..a694a06 100644 --- a/plugins/decklink/data/locale/en-US.ini +++ b/plugins/decklink/data/locale/en-US.ini @@ -3,9 +3,17 @@ Device="Device" Mode="Mode" Buffering="Use Buffering" PixelFormat="Pixel Format" +ColorSpace="YUV Color Space" +ColorSpace.Default="Default" +ColorRange="YUV Color Range" +ColorRange.Default="Default" +ColorRange.Partial="Partial" +ColorRange.Full="Full" ChannelFormat="Channel" ChannelFormat.None="None" ChannelFormat.2_0ch="2ch" +ChannelFormat.2_1ch="2.1ch" +ChannelFormat.4_0ch="4ch" +ChannelFormat.4_1ch="4.1ch" ChannelFormat.5_1ch="5.1ch" -ChannelFormat.5_1chBack="5.1ch (Back)" ChannelFormat.7_1ch="7.1ch" diff --git a/plugins/decklink/data/locale/es-ES.ini b/plugins/decklink/data/locale/es-ES.ini index abbe8d6..a4d8415 100644 --- a/plugins/decklink/data/locale/es-ES.ini +++ b/plugins/decklink/data/locale/es-ES.ini @@ -3,10 +3,18 @@ Device="Dispositivo" Mode="Modo" Buffering="Utilizar el almacenamiento en búfer" PixelFormat="Formato de píxel" +ColorSpace="Espacio de color YUV" +ColorSpace.Default="Predeterminado" +ColorRange="Rango de color YUV" +ColorRange.Default="Predeterminado" +ColorRange.Partial="Parcial" +ColorRange.Full="Completo" ChannelFormat="Canal" ChannelFormat.None="Ninguno" ChannelFormat.2_0ch="Estéreo " -ChannelFormat.5_1ch="5.1" -ChannelFormat.5_1chBack="5.1 (posterior)" -ChannelFormat.7_1ch="7.1" +ChannelFormat.2_1ch="2.1 canales" +ChannelFormat.4_0ch="4 canales" +ChannelFormat.4_1ch="4.1 canales" +ChannelFormat.5_1ch="5.1 canales" +ChannelFormat.7_1ch="7.1 canales" diff --git a/plugins/decklink/data/locale/eu-ES.ini b/plugins/decklink/data/locale/eu-ES.ini index f1883a1..eb5aee2 100644 --- a/plugins/decklink/data/locale/eu-ES.ini +++ b/plugins/decklink/data/locale/eu-ES.ini @@ -3,10 +3,18 @@ Device="Gailua" Mode="Modua" Buffering="Erabili Bufferreratzea" PixelFormat="Pixel formatua" +ColorSpace="YUV kolore espazioa" +ColorSpace.Default="Lehenetsia" +ColorRange="YUV kolore tartea" +ColorRange.Default="Lehenetsia" +ColorRange.Partial="Partziala" +ColorRange.Full="Osoa" ChannelFormat="Kanala" ChannelFormat.None="Ezer ez" ChannelFormat.2_0ch="2k" -ChannelFormat.5_1ch="5.1k" -ChannelFormat.5_1chBack="5.1k (Atzera)" -ChannelFormat.7_1ch="7.1k" +ChannelFormat.2_1ch="2.1 kanala" +ChannelFormat.4_0ch="4kanala" +ChannelFormat.4_1ch="4.1kanala" +ChannelFormat.5_1ch="5.1 kanala" +ChannelFormat.7_1ch="7.1 kanala" diff --git a/plugins/decklink/data/locale/fi-FI.ini b/plugins/decklink/data/locale/fi-FI.ini index c1061e6..7439eb2 100644 --- a/plugins/decklink/data/locale/fi-FI.ini +++ b/plugins/decklink/data/locale/fi-FI.ini @@ -3,10 +3,18 @@ Device="Laite" Mode="Tila" Buffering="Käytä puskurointia" PixelFormat="Pikselimuoto" +ColorSpace="YUV väriavaruus" +ColorSpace.Default="Oletusarvo" +ColorRange="YUV värialue" +ColorRange.Default="Oletusarvo" +ColorRange.Partial="Osittainen" +ColorRange.Full="Täysi" ChannelFormat="Kanava" ChannelFormat.None="Ei mitään" ChannelFormat.2_0ch="2ch" +ChannelFormat.2_1ch="2.1ch" +ChannelFormat.4_0ch="4ch" +ChannelFormat.4_1ch="4.1ch" ChannelFormat.5_1ch="5.1ch" -ChannelFormat.5_1chBack="5.1ch (Taka)" ChannelFormat.7_1ch="7.1ch" diff --git a/plugins/decklink/data/locale/fr-FR.ini b/plugins/decklink/data/locale/fr-FR.ini index b356b1b..9248d58 100644 --- a/plugins/decklink/data/locale/fr-FR.ini +++ b/plugins/decklink/data/locale/fr-FR.ini @@ -3,10 +3,18 @@ Device="Périphérique" Mode="Mode" Buffering="Utiliser la mise en mémoire tampon" PixelFormat="Format de pixel" +ColorSpace="Espace de couleurs YUV" +ColorSpace.Default="Défaut" +ColorRange="Gamme de couleurs YUV" +ColorRange.Default="Défaut" +ColorRange.Partial="Partiel" +ColorRange.Full="Complète" ChannelFormat="Canaux audio" ChannelFormat.None="Aucun" -ChannelFormat.2_0ch="canal 2" -ChannelFormat.5_1ch="canal 5.1" -ChannelFormat.5_1chBack="canal (arrière) 5.1" -ChannelFormat.7_1ch="canal 7.1" +ChannelFormat.2_0ch="2 canaux" +ChannelFormat.2_1ch="2.1 canaux" +ChannelFormat.4_0ch="4 canaux" +ChannelFormat.4_1ch="4.1 canaux" +ChannelFormat.5_1ch="5.1 canaux" +ChannelFormat.7_1ch="7.1 canaux" diff --git a/plugins/decklink/data/locale/hu-HU.ini b/plugins/decklink/data/locale/hu-HU.ini index a81c7b0..7ed2de1 100644 --- a/plugins/decklink/data/locale/hu-HU.ini +++ b/plugins/decklink/data/locale/hu-HU.ini @@ -3,10 +3,18 @@ Device="Eszköz" Mode="Mód" Buffering="Pufferelés használata" PixelFormat="Képpont formátum" +ColorSpace="YUV színtér" +ColorSpace.Default="Alapértelmezett" +ColorRange="YUV színtartomány" +ColorRange.Default="Alapértelmezett" +ColorRange.Partial="Részleges" +ColorRange.Full="Teljes" ChannelFormat="Csatorna" ChannelFormat.None="Nincs" ChannelFormat.2_0ch="2cs" +ChannelFormat.2_1ch="2.1cs" +ChannelFormat.4_0ch="4cs" +ChannelFormat.4_1ch="4.1cs" ChannelFormat.5_1ch="5.1cs" -ChannelFormat.5_1chBack="5.1cs (Hátsó)" ChannelFormat.7_1ch="7.1cs" diff --git a/plugins/decklink/data/locale/it-IT.ini b/plugins/decklink/data/locale/it-IT.ini index 1f794d9..adac817 100644 --- a/plugins/decklink/data/locale/it-IT.ini +++ b/plugins/decklink/data/locale/it-IT.ini @@ -1,12 +1,20 @@ -BlackmagicDevice="Blackmagic Device" +BlackmagicDevice="Dispositivo Blackmagic" Device="Dispositivo" Mode="Modalità" Buffering="Usa buffer" PixelFormat="Formato pixel" +ColorSpace="Spazio colore YUV" +ColorSpace.Default="Predefinito" +ColorRange="Gamma di colore YUV" +ColorRange.Default="Predefinito" +ColorRange.Partial="Parziale" +ColorRange.Full="Intero" ChannelFormat="Canale" ChannelFormat.None="Nessuno" ChannelFormat.2_0ch="2 canali" +ChannelFormat.2_1ch="2.1 canali" +ChannelFormat.4_0ch="4 canali" +ChannelFormat.4_1ch="4.1 canali" ChannelFormat.5_1ch="5.1 canali" -ChannelFormat.5_1chBack="5.1 canali (retro)" ChannelFormat.7_1ch="7.1 canali" diff --git a/plugins/decklink/data/locale/ja-JP.ini b/plugins/decklink/data/locale/ja-JP.ini index 82c05b9..a519119 100644 --- a/plugins/decklink/data/locale/ja-JP.ini +++ b/plugins/decklink/data/locale/ja-JP.ini @@ -3,10 +3,18 @@ Device="デバイス" Mode="モード" Buffering="バッファリングを使用する" PixelFormat="ピクセルフォーマット" +ColorSpace="YUV 色空間" +ColorSpace.Default="既定" +ColorRange="YUV 色範囲" +ColorRange.Default="既定" +ColorRange.Partial="一部" +ColorRange.Full="全部" ChannelFormat="チャンネル" ChannelFormat.None="未設定" ChannelFormat.2_0ch="2ch" +ChannelFormat.2_1ch="2.1ch" +ChannelFormat.4_0ch="4 ch" +ChannelFormat.4_1ch="4.1ch" ChannelFormat.5_1ch="5.1ch" -ChannelFormat.5_1chBack="5.1ch (背部)" ChannelFormat.7_1ch="7.1ch" diff --git a/plugins/decklink/data/locale/ko-KR.ini b/plugins/decklink/data/locale/ko-KR.ini index 44e842c..e4450d8 100644 --- a/plugins/decklink/data/locale/ko-KR.ini +++ b/plugins/decklink/data/locale/ko-KR.ini @@ -3,10 +3,18 @@ Device="장치" Mode="방식" Buffering="버퍼링 사용" PixelFormat="픽셀 형식" +ColorSpace="YUV 색 공간" +ColorSpace.Default="기본값" +ColorRange="YUV 색상 범위" +ColorRange.Default="기본값" +ColorRange.Partial="부분" +ColorRange.Full="전체" ChannelFormat="채널" ChannelFormat.None="없음" ChannelFormat.2_0ch="2채널" -ChannelFormat.5_1ch="5.1채널" -ChannelFormat.5_1chBack="5.1채널 (후면)" -ChannelFormat.7_1ch="7.1채널" +ChannelFormat.2_1ch="2.1 채널" +ChannelFormat.4_0ch="4채널" +ChannelFormat.4_1ch="4.1채널" +ChannelFormat.5_1ch="5.1 채널" +ChannelFormat.7_1ch="7.1 채널" diff --git a/plugins/decklink/data/locale/nb-NO.ini b/plugins/decklink/data/locale/nb-NO.ini index 2eabbe0..d93b304 100644 --- a/plugins/decklink/data/locale/nb-NO.ini +++ b/plugins/decklink/data/locale/nb-NO.ini @@ -3,4 +3,18 @@ Device="Enhet" Mode="Modus" Buffering="Bruk bufring" PixelFormat="Pikselformat" +ColorSpace="YUV fargerom" +ColorSpace.Default="Standard" +ColorRange="YUV fargerom" +ColorRange.Default="Standard" +ColorRange.Partial="Delvis" +ColorRange.Full="Hel" +ChannelFormat="Kanal" +ChannelFormat.None="Ingen" +ChannelFormat.2_0ch="2ch" +ChannelFormat.2_1ch="2.1ch" +ChannelFormat.4_0ch="4ch" +ChannelFormat.4_1ch="4.1ch" +ChannelFormat.5_1ch="5.1ch" +ChannelFormat.7_1ch="7.1ch" diff --git a/plugins/decklink/data/locale/nl-NL.ini b/plugins/decklink/data/locale/nl-NL.ini index a430c05..e610e7c 100644 --- a/plugins/decklink/data/locale/nl-NL.ini +++ b/plugins/decklink/data/locale/nl-NL.ini @@ -3,10 +3,18 @@ Device="Apparaat" Mode="Modus" Buffering="Buffering Gebruiken" PixelFormat="Pixelindeling" +ColorSpace="YUV-Kleurruimte" +ColorSpace.Default="Standaard" +ColorRange="YUV Kleurbereik" +ColorRange.Default="Standaard" +ColorRange.Partial="Gedeeltelijk" +ColorRange.Full="Volledig" ChannelFormat="Kanaal" ChannelFormat.None="Geen" ChannelFormat.2_0ch="2ch" +ChannelFormat.2_1ch="2.1ch" +ChannelFormat.4_0ch="4ch" +ChannelFormat.4_1ch="4.1ch" ChannelFormat.5_1ch="5.1ch" -ChannelFormat.5_1chBack="5.1ch (Achter)" ChannelFormat.7_1ch="7.1ch" diff --git a/plugins/decklink/data/locale/pl-PL.ini b/plugins/decklink/data/locale/pl-PL.ini index eeb8ef7..92a226a 100644 --- a/plugins/decklink/data/locale/pl-PL.ini +++ b/plugins/decklink/data/locale/pl-PL.ini @@ -3,10 +3,18 @@ Device="Urządzenie" Mode="Tryb" Buffering="Użyj buforowania" PixelFormat="Format pikseli" +ColorSpace="Przestrzeń kolorów YUV" +ColorSpace.Default="Domyślne" +ColorRange="Zakres kolorów YUV" +ColorRange.Default="Domyślne" +ColorRange.Partial="Częściowy" +ColorRange.Full="Pełny" ChannelFormat="Kanały" ChannelFormat.None="Brak" ChannelFormat.2_0ch="2.0" +ChannelFormat.2_1ch="2.1" +ChannelFormat.4_0ch="4.0" +ChannelFormat.4_1ch="4.1" ChannelFormat.5_1ch="5.1" -ChannelFormat.5_1chBack="5.1 (tylne)" ChannelFormat.7_1ch="7.1" diff --git a/plugins/decklink/data/locale/pt-BR.ini b/plugins/decklink/data/locale/pt-BR.ini index e42a738..cabb030 100644 --- a/plugins/decklink/data/locale/pt-BR.ini +++ b/plugins/decklink/data/locale/pt-BR.ini @@ -3,10 +3,18 @@ Device="Dispositivo" Mode="Modo" Buffering="Utilizar Buffering" PixelFormat="Formato de Pixel" +ColorSpace="Espaço de cor YUV" +ColorSpace.Default="Padrão" +ColorRange="Intervalo de Cores YUV" +ColorRange.Default="Padrão" +ColorRange.Partial="Parcial" +ColorRange.Full="Completo" ChannelFormat="Canal" ChannelFormat.None="Nenhum" ChannelFormat.2_0ch="2.0" +ChannelFormat.2_1ch="2.1" +ChannelFormat.4_0ch="4.0" +ChannelFormat.4_1ch="4.1" ChannelFormat.5_1ch="5.1" -ChannelFormat.5_1chBack="5.1 (Traseiro)" ChannelFormat.7_1ch="7.1" diff --git a/plugins/decklink/data/locale/pt-PT.ini b/plugins/decklink/data/locale/pt-PT.ini index 18afbb8..cc47aa0 100644 --- a/plugins/decklink/data/locale/pt-PT.ini +++ b/plugins/decklink/data/locale/pt-PT.ini @@ -3,4 +3,7 @@ Device="Dispositivo" Mode="Modo" Buffering="Utilizar Buffering" PixelFormat="Formato de pixel" +ColorRange.Partial="Parcial" +ChannelFormat="Canal" +ChannelFormat.None="Nenhum" diff --git a/plugins/decklink/data/locale/ru-RU.ini b/plugins/decklink/data/locale/ru-RU.ini index aeed3b2..0dce592 100644 --- a/plugins/decklink/data/locale/ru-RU.ini +++ b/plugins/decklink/data/locale/ru-RU.ini @@ -3,10 +3,18 @@ Device="Устройство" Mode="Режим" Buffering="Использовать буферизацию" PixelFormat="Формат пикселей" +ColorSpace="Цветовое пространство YUV" +ColorSpace.Default="По умолчанию" +ColorRange="Цветовой диапазон YUV" +ColorRange.Default="По умолчанию" +ColorRange.Partial="Частичный" +ColorRange.Full="Полный" ChannelFormat="Конфигурация каналов" ChannelFormat.None="Нет" ChannelFormat.2_0ch="2-канальный" +ChannelFormat.2_1ch="2.1-канальный" +ChannelFormat.4_0ch="4-канальный" +ChannelFormat.4_1ch="4.1-канальный" ChannelFormat.5_1ch="5.1-канальный" -ChannelFormat.5_1chBack="5.1-канальный (Тыловой)" ChannelFormat.7_1ch="7.1-канальный" diff --git a/plugins/decklink/data/locale/sk-SK.ini b/plugins/decklink/data/locale/sk-SK.ini index 42d917a..439f7dd 100644 --- a/plugins/decklink/data/locale/sk-SK.ini +++ b/plugins/decklink/data/locale/sk-SK.ini @@ -1,4 +1,17 @@ +BlackmagicDevice="Blackmagic zariadenie" Device="Zariadenie" Mode="Mód" Buffering="Použiť vyrovnávaciu pamäť" +PixelFormat="Formát pixelov" +ColorSpace="Farebný priestor YUV" +ColorSpace.Default="Predvolený" +ColorRange="Rozsah farieb YUV" +ColorRange.Default="Predvolený" +ColorRange.Partial="Čiastočný" +ColorRange.Full="Plný" +ChannelFormat="Kanál" +ChannelFormat.None="Nič" +ChannelFormat.2_0ch="2ch" +ChannelFormat.4_0ch="4ch" +ChannelFormat.4_1ch="4.1ch" diff --git a/plugins/decklink/data/locale/sv-SE.ini b/plugins/decklink/data/locale/sv-SE.ini index 738984f..12f6e39 100644 --- a/plugins/decklink/data/locale/sv-SE.ini +++ b/plugins/decklink/data/locale/sv-SE.ini @@ -3,10 +3,18 @@ Device="Enhet" Mode="Läge" Buffering="Använd buffert" PixelFormat="Bildpunktsformat" +ColorSpace="YUV-färgrymd" +ColorSpace.Default="Standard" +ColorRange="YUV-färgområde" +ColorRange.Default="Standard" +ColorRange.Partial="Partiell" +ColorRange.Full="Full" ChannelFormat="Kanal" ChannelFormat.None="Ingen" ChannelFormat.2_0ch="2ch" -ChannelFormat.5_1ch="5.1ch" -ChannelFormat.5_1chBack="5.1ch (bakom)" -ChannelFormat.7_1ch="7.1ch" +ChannelFormat.2_1ch="2.1 kanaler" +ChannelFormat.4_0ch="4 kanaler" +ChannelFormat.4_1ch="4.1 kanaler" +ChannelFormat.5_1ch="5.1 kanaler" +ChannelFormat.7_1ch="7.1 kanaler" diff --git a/plugins/decklink/data/locale/tr-TR.ini b/plugins/decklink/data/locale/tr-TR.ini index 95c5452..1626abf 100644 --- a/plugins/decklink/data/locale/tr-TR.ini +++ b/plugins/decklink/data/locale/tr-TR.ini @@ -1,12 +1,20 @@ BlackmagicDevice="Blackmagic Aygıtı" Device="Aygıt" Mode="Mod" -Buffering="Arabelleği Kullan" +Buffering="Arabelleğe Almayı Kullan" PixelFormat="Piksel Biçimi" +ColorSpace="YUV Renk Alanı" +ColorSpace.Default="Varsayılan" +ColorRange="YUV Renk Aralığı" +ColorRange.Default="Varsayılan" +ColorRange.Partial="Kısmi" +ColorRange.Full="Tam" ChannelFormat="Kanal" ChannelFormat.None="Hiçbiri" ChannelFormat.2_0ch="2ch" +ChannelFormat.2_1ch="2.1ch" +ChannelFormat.4_0ch="4ch" +ChannelFormat.4_1ch="4.1ch" ChannelFormat.5_1ch="5.1ch" -ChannelFormat.5_1chBack="5.1ch (Arka)" ChannelFormat.7_1ch="7.1ch" diff --git a/plugins/decklink/data/locale/uk-UA.ini b/plugins/decklink/data/locale/uk-UA.ini index 7664abc..9a12869 100644 --- a/plugins/decklink/data/locale/uk-UA.ini +++ b/plugins/decklink/data/locale/uk-UA.ini @@ -3,10 +3,18 @@ Device="Пристрій" Mode="Режим" Buffering="Увімкнути буферизацію" PixelFormat="Формат пікселів" +ColorSpace="Колірний простір YUV" +ColorSpace.Default="За замовчуванням" +ColorRange="Колірний діапазон YUV" +ColorRange.Default="За замовчуванням" +ColorRange.Partial="Частковий" +ColorRange.Full="Повний" ChannelFormat="Звук (канали)" ChannelFormat.None="Немає" ChannelFormat.2_0ch="2-канальний" +ChannelFormat.2_1ch="2.1-канальний" +ChannelFormat.4_0ch="4-канальний" +ChannelFormat.4_1ch="4.1-канальний" ChannelFormat.5_1ch="5.1-канальний" -ChannelFormat.5_1chBack="5.1-канальний (Back)" ChannelFormat.7_1ch="7.1-канальний" diff --git a/plugins/decklink/data/locale/vi-VN.ini b/plugins/decklink/data/locale/vi-VN.ini new file mode 100644 index 0000000..4a63536 --- /dev/null +++ b/plugins/decklink/data/locale/vi-VN.ini @@ -0,0 +1,9 @@ +Mode="Chế độ" +ColorSpace.Default="Mặc định" +ColorRange.Default="Mặc định" +ColorRange.Partial="Một phần" +ColorRange.Full="Đầy đủ" +ChannelFormat="Kênh" +ChannelFormat.None="Không có" +ChannelFormat.2_0ch="2ch" + diff --git a/plugins/decklink/data/locale/zh-CN.ini b/plugins/decklink/data/locale/zh-CN.ini index e0c5cdb..e8b179d 100644 --- a/plugins/decklink/data/locale/zh-CN.ini +++ b/plugins/decklink/data/locale/zh-CN.ini @@ -3,10 +3,18 @@ Device="设备" Mode="模式" Buffering="使用缓冲" PixelFormat="像素格式" +ColorSpace="YUV 颜色空间" +ColorSpace.Default="默认" +ColorRange="YUV 颜色范围" +ColorRange.Default="默认" +ColorRange.Partial="局部" +ColorRange.Full="全部" ChannelFormat="频道" ChannelFormat.None="无" ChannelFormat.2_0ch="2ch" +ChannelFormat.2_1ch="2.1ch" +ChannelFormat.4_0ch="4ch" +ChannelFormat.4_1ch="4.1ch" ChannelFormat.5_1ch="5.1ch" -ChannelFormat.5_1chBack="5.1ch (后)" ChannelFormat.7_1ch="7.1ch" diff --git a/plugins/decklink/data/locale/zh-TW.ini b/plugins/decklink/data/locale/zh-TW.ini index d39629c..ebbfaaa 100644 --- a/plugins/decklink/data/locale/zh-TW.ini +++ b/plugins/decklink/data/locale/zh-TW.ini @@ -3,10 +3,18 @@ Device="裝置" Mode="模式" Buffering="使用緩衝" PixelFormat="像素格式" +ColorSpace="YUV 色彩空間" +ColorSpace.Default="預設" +ColorRange="YUV 色彩範圍" +ColorRange.Default="預設" +ColorRange.Partial="部分" +ColorRange.Full="完整" ChannelFormat="聲道" ChannelFormat.None="無" ChannelFormat.2_0ch="雙聲道" +ChannelFormat.2_1ch="2.1聲道" +ChannelFormat.4_0ch="4聲道" +ChannelFormat.4_1ch="4.1聲道" ChannelFormat.5_1ch="5.1聲道" -ChannelFormat.5_1chBack="5.1聲道(後置環繞喇叭)" ChannelFormat.7_1ch="7.1聲道" diff --git a/plugins/decklink/decklink-device-instance.cpp b/plugins/decklink/decklink-device-instance.cpp index 557713a..5dc674e 100644 --- a/plugins/decklink/decklink-device-instance.cpp +++ b/plugins/decklink/decklink-device-instance.cpp @@ -9,7 +9,11 @@ #define LOG(level, message, ...) blog(level, "%s: " message, \ obs_source_get_name(this->decklink->GetSource()), ##__VA_ARGS__) -#define ISSTEREO(flag) ((flag) == SPEAKERS_STEREO) +#ifdef _WIN32 +#define IS_WIN 1 +#else +#define IS_WIN 0 +#endif static inline enum video_format ConvertPixelFormat(BMDPixelFormat format) { @@ -26,8 +30,10 @@ static inline enum video_format ConvertPixelFormat(BMDPixelFormat format) static inline int ConvertChannelFormat(speaker_layout format) { switch (format) { + case SPEAKERS_2POINT1: + case SPEAKERS_4POINT0: + case SPEAKERS_4POINT1: case SPEAKERS_5POINT1: - case SPEAKERS_5POINT1_SURROUND: case SPEAKERS_7POINT1: return 8; @@ -40,13 +46,16 @@ static inline int ConvertChannelFormat(speaker_layout format) static inline audio_repack_mode_t ConvertRepackFormat(speaker_layout format) { switch (format) { + case SPEAKERS_2POINT1: + return repack_mode_8to3ch_swap23; + case SPEAKERS_4POINT0: + return repack_mode_8to4ch_swap23; + case SPEAKERS_4POINT1: + return repack_mode_8to5ch_swap23; case SPEAKERS_5POINT1: - case SPEAKERS_5POINT1_SURROUND: return repack_mode_8to6ch_swap23; - case SPEAKERS_7POINT1: - return repack_mode_8ch_swap23; - + return repack_mode_8ch_swap23_swap46_swap57; default: assert(false && "No repack requested"); return (audio_repack_mode_t)-1; @@ -83,15 +92,29 @@ void DeckLinkDeviceInstance::HandleAudioPacket( currentPacket.frames = frameCount; currentPacket.timestamp = timestamp; - if (!ISSTEREO(channelFormat)) { + if (decklink && !decklink->buffering) { + currentPacket.timestamp = os_gettime_ns(); + currentPacket.timestamp -= + (uint64_t)frameCount * 1000000000ULL / + (uint64_t)currentPacket.samples_per_sec; + } + + int maxdevicechannel = device->GetMaxChannel(); + bool isWin = IS_WIN; + + if (channelFormat != SPEAKERS_UNKNOWN && + channelFormat != SPEAKERS_MONO && + channelFormat != SPEAKERS_STEREO && + maxdevicechannel >= 8 && + isWin) { + if (audioRepacker->repack((uint8_t *)bytes, frameCount) < 0) { LOG(LOG_ERROR, "Failed to convert audio packet data"); return; } - - currentPacket.data[0] = (*audioRepacker)->packet_buffer; + currentPacket.data[0] = (*audioRepacker)->packet_buffer; } else { - currentPacket.data[0] = (uint8_t *)bytes; + currentPacket.data[0] = (uint8_t *)bytes; } nextAudioTS = timestamp + @@ -118,16 +141,15 @@ void DeckLinkDeviceInstance::HandleVideoFrame( currentFrame.height = (uint32_t)videoFrame->GetHeight(); currentFrame.timestamp = timestamp; - video_format_get_parameters(VIDEO_CS_601, VIDEO_RANGE_PARTIAL, - currentFrame.color_matrix, currentFrame.color_range_min, - currentFrame.color_range_max); - obs_source_output_video(decklink->GetSource(), ¤tFrame); } void DeckLinkDeviceInstance::FinalizeStream() { input->SetCallback(nullptr); + input->DisableVideoInput(); + if (channelFormat != SPEAKERS_UNKNOWN) + input->DisableAudioInput(); if (audioRepacker != nullptr) { @@ -138,6 +160,43 @@ void DeckLinkDeviceInstance::FinalizeStream() mode = nullptr; } +//#define LOG_SETUP_VIDEO_FORMAT 1 + +void DeckLinkDeviceInstance::SetupVideoFormat(DeckLinkDeviceMode *mode_) +{ + if (mode_ == nullptr) + return; + + currentFrame.format = ConvertPixelFormat(pixelFormat); + + colorSpace = decklink->GetColorSpace(); + if (colorSpace == VIDEO_CS_DEFAULT) { + const BMDDisplayModeFlags flags = mode_->GetDisplayModeFlags(); + if (flags & bmdDisplayModeColorspaceRec709) + activeColorSpace = VIDEO_CS_709; + else if (flags & bmdDisplayModeColorspaceRec601) + activeColorSpace = VIDEO_CS_601; + else + activeColorSpace = VIDEO_CS_DEFAULT; + } else { + activeColorSpace = colorSpace; + } + + colorRange = decklink->GetColorRange(); + currentFrame.full_range = colorRange == VIDEO_RANGE_FULL; + + video_format_get_parameters(activeColorSpace, colorRange, + currentFrame.color_matrix, currentFrame.color_range_min, + currentFrame.color_range_max); + +#ifdef LOG_SETUP_VIDEO_FORMAT + LOG(LOG_INFO, "Setup video format: %s, %s, %s", + pixelFormat == bmdFormat8BitYUV ? "YUV" : "RGB", + activeColorSpace == VIDEO_CS_709 ? "BT.709" : "BT.601", + colorRange == VIDEO_RANGE_FULL ? "full" : "limited"); +#endif +} + bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_) { if (mode != nullptr) @@ -150,33 +209,50 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_) if (!device->GetInput(&input)) return false; - pixelFormat = decklink->GetPixelFormat(); - currentFrame.format = ConvertPixelFormat(pixelFormat); + BMDVideoInputFlags flags; - const BMDDisplayMode displayMode = mode_->GetDisplayMode(); + bool isauto = mode_->GetName() == "Auto"; + if (isauto) { + displayMode = bmdModeNTSC; + pixelFormat = bmdFormat8BitYUV; + flags = bmdVideoInputEnableFormatDetection; + } else { + displayMode = mode_->GetDisplayMode(); + pixelFormat = decklink->GetPixelFormat(); + flags = bmdVideoInputFlagDefault; + } const HRESULT videoResult = input->EnableVideoInput(displayMode, - pixelFormat, bmdVideoInputFlagDefault); - + pixelFormat, flags); if (videoResult != S_OK) { LOG(LOG_ERROR, "Failed to enable video input"); return false; } + SetupVideoFormat(mode_); + channelFormat = decklink->GetChannelFormat(); currentPacket.speakers = channelFormat; + int maxdevicechannel = device->GetMaxChannel(); + bool isWin = IS_WIN; + if (channelFormat != SPEAKERS_UNKNOWN) { const int channel = ConvertChannelFormat(channelFormat); const HRESULT audioResult = input->EnableAudioInput( bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, channel); - if (audioResult != S_OK) LOG(LOG_WARNING, "Failed to enable audio input; continuing..."); - if (!ISSTEREO(channelFormat)) { - const audio_repack_mode_t repack_mode = ConvertRepackFormat(channelFormat); + if (channelFormat != SPEAKERS_UNKNOWN && + channelFormat != SPEAKERS_MONO && + channelFormat != SPEAKERS_STEREO && + maxdevicechannel >= 8 && + isWin) { + + const audio_repack_mode_t repack_mode = ConvertRepackFormat + (channelFormat); audioRepacker = new AudioRepacker(repack_mode); } } @@ -257,12 +333,41 @@ HRESULT STDMETHODCALLTYPE DeckLinkDeviceInstance::VideoInputFormatChanged( IDeckLinkDisplayMode *newMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags) { - UNUSED_PARAMETER(events); - UNUSED_PARAMETER(newMode); - UNUSED_PARAMETER(detectedSignalFlags); + input->PauseStreams(); - // There is no implementation for automatic format detection, so this - // method goes unused. + mode->SetMode(newMode); + + if (events & bmdVideoInputDisplayModeChanged) { + displayMode = mode->GetDisplayMode(); + } + + if (events & bmdVideoInputColorspaceChanged) { + switch (detectedSignalFlags) { + case bmdDetectedVideoInputRGB444: + pixelFormat = bmdFormat8BitBGRA; + break; + + default: + case bmdDetectedVideoInputYCbCr422: + pixelFormat = bmdFormat8BitYUV; + break; + } + } + + const HRESULT videoResult = input->EnableVideoInput(displayMode, + pixelFormat, bmdVideoInputEnableFormatDetection); + if (videoResult != S_OK) { + LOG(LOG_ERROR, "Failed to enable video input"); + input->StopStreams(); + FinalizeStream(); + + return E_FAIL; + } + + SetupVideoFormat(mode); + + input->FlushStreams(); + input->StartStreams(); return S_OK; } diff --git a/plugins/decklink/decklink-device-instance.hpp b/plugins/decklink/decklink-device-instance.hpp index c0af8f5..ffbed07 100644 --- a/plugins/decklink/decklink-device-instance.hpp +++ b/plugins/decklink/decklink-device-instance.hpp @@ -11,7 +11,11 @@ protected: DeckLink *decklink = nullptr; DeckLinkDevice *device = nullptr; DeckLinkDeviceMode *mode = nullptr; + BMDDisplayMode displayMode = bmdModeNTSC; BMDPixelFormat pixelFormat = bmdFormat8BitYUV; + video_colorspace colorSpace = VIDEO_CS_DEFAULT; + video_colorspace activeColorSpace = VIDEO_CS_DEFAULT; + video_range_type colorRange = VIDEO_RANGE_DEFAULT; ComPtr input; volatile long refCount = 1; int64_t audioOffset = 0; @@ -21,6 +25,7 @@ protected: speaker_layout channelFormat = SPEAKERS_STEREO; void FinalizeStream(); + void SetupVideoFormat(DeckLinkDeviceMode *mode_); void HandleAudioPacket(IDeckLinkAudioInputPacket *audioPacket, const uint64_t timestamp); @@ -38,6 +43,8 @@ public: } inline BMDPixelFormat GetActivePixelFormat() const {return pixelFormat;} + inline video_colorspace GetActiveColorSpace() const {return colorSpace;} + inline video_range_type GetActiveColorRange() const {return colorRange;} inline speaker_layout GetActiveChannelFormat() const {return channelFormat;} inline DeckLinkDeviceMode *GetMode() const {return mode;} diff --git a/plugins/decklink/decklink-device-mode.cpp b/plugins/decklink/decklink-device-mode.cpp index 8f53ac7..48d6eac 100644 --- a/plugins/decklink/decklink-device-mode.cpp +++ b/plugins/decklink/decklink-device-mode.cpp @@ -32,6 +32,14 @@ BMDDisplayMode DeckLinkDeviceMode::GetDisplayMode(void) const return bmdModeUnknown; } +BMDDisplayModeFlags DeckLinkDeviceMode::GetDisplayModeFlags(void) const +{ + if (mode != nullptr) + return mode->GetFlags(); + + return (BMDDisplayModeFlags)0; +} + long long DeckLinkDeviceMode::GetId(void) const { return id; @@ -41,3 +49,14 @@ const std::string& DeckLinkDeviceMode::GetName(void) const { return name; } + +void DeckLinkDeviceMode::SetMode(IDeckLinkDisplayMode *mode_) +{ + IDeckLinkDisplayMode *old = mode; + if (old != nullptr) + old->Release(); + + mode = mode_; + if (mode != nullptr) + mode->AddRef(); +} diff --git a/plugins/decklink/decklink-device-mode.hpp b/plugins/decklink/decklink-device-mode.hpp index 36bbc2f..8ff642d 100644 --- a/plugins/decklink/decklink-device-mode.hpp +++ b/plugins/decklink/decklink-device-mode.hpp @@ -4,6 +4,8 @@ #include +#define MODE_ID_AUTO -1 + class DeckLinkDeviceMode { protected: long long id; @@ -16,6 +18,9 @@ public: virtual ~DeckLinkDeviceMode(void); BMDDisplayMode GetDisplayMode(void) const; + BMDDisplayModeFlags GetDisplayModeFlags(void) const; long long GetId(void) const; const std::string& GetName(void) const; + + void SetMode(IDeckLinkDisplayMode *mode); }; diff --git a/plugins/decklink/decklink-device.cpp b/plugins/decklink/decklink-device.cpp index 3203247..d173053 100644 --- a/plugins/decklink/decklink-device.cpp +++ b/plugins/decklink/decklink-device.cpp @@ -29,6 +29,21 @@ ULONG DeckLinkDevice::Release() bool DeckLinkDevice::Init() { + ComPtr attributes; + const HRESULT result = device->QueryInterface(IID_IDeckLinkAttributes, + (void **)&attributes); + + if (result == S_OK) { + decklink_bool_t detectable = false; + if (attributes->GetFlag(BMDDeckLinkSupportsInputFormatDetection, + &detectable) == S_OK && !!detectable) { + DeckLinkDeviceMode *mode = + new DeckLinkDeviceMode("Auto", MODE_ID_AUTO); + modes.push_back(mode); + modeIdMap[MODE_ID_AUTO] = mode; + } + } + ComPtr input; if (device->QueryInterface(IID_IDeckLinkInput, (void**)&input) != S_OK) return false; @@ -66,9 +81,6 @@ bool DeckLinkDevice::Init() hash = displayName; - ComPtr attributes; - const HRESULT result = device->QueryInterface(IID_IDeckLinkAttributes, - (void **)&attributes); if (result != S_OK) return true; diff --git a/plugins/decklink/decklink.cpp b/plugins/decklink/decklink.cpp index c20ff32..ea0ec96 100644 --- a/plugins/decklink/decklink.cpp +++ b/plugins/decklink/decklink.cpp @@ -66,6 +66,8 @@ bool DeckLink::Activate(DeckLinkDevice *device, long long modeId) return false; if (instance->GetActiveModeId() == modeId && instance->GetActivePixelFormat() == pixelFormat && + instance->GetActiveColorSpace() == colorSpace && + instance->GetActiveColorRange() == colorRange && instance->GetActiveChannelFormat() == channelFormat) return false; } diff --git a/plugins/decklink/decklink.hpp b/plugins/decklink/decklink.hpp index 217637a..23f8b59 100644 --- a/plugins/decklink/decklink.hpp +++ b/plugins/decklink/decklink.hpp @@ -22,6 +22,8 @@ protected: volatile long activateRefs = 0; std::recursive_mutex deviceMutex; BMDPixelFormat pixelFormat = bmdFormat8BitYUV; + video_colorspace colorSpace = VIDEO_CS_DEFAULT; + video_range_type colorRange = VIDEO_RANGE_DEFAULT; speaker_layout channelFormat = SPEAKERS_STEREO; void SaveSettings(); @@ -42,6 +44,16 @@ public: { pixelFormat = format; } + inline video_colorspace GetColorSpace() const {return colorSpace;} + inline void SetColorSpace(video_colorspace format) + { + colorSpace = format; + } + inline video_range_type GetColorRange() const {return colorRange;} + inline void SetColorRange(video_range_type format) + { + colorRange = format; + } inline speaker_layout GetChannelFormat() const {return channelFormat;} inline void SetChannelFormat(speaker_layout format) { @@ -50,4 +62,6 @@ public: bool Activate(DeckLinkDevice *device, long long modeId); void Deactivate(); + + bool buffering = false; }; diff --git a/plugins/decklink/platform.hpp b/plugins/decklink/platform.hpp index 0be43f4..b5f7600 100644 --- a/plugins/decklink/platform.hpp +++ b/plugins/decklink/platform.hpp @@ -2,6 +2,7 @@ #if defined(_WIN32) #include +typedef BOOL decklink_bool_t; typedef BSTR decklink_string_t; IDeckLinkDiscovery *CreateDeckLinkDiscoveryInstance(void); #define IUnknownUUID IID_IUnknown @@ -10,9 +11,11 @@ typedef REFIID CFUUIDBytes; #elif defined(__APPLE__) #include "mac/decklink-sdk/DeckLinkAPI.h" #include +typedef bool decklink_bool_t; typedef CFStringRef decklink_string_t; #elif defined(__linux__) #include "linux/decklink-sdk/DeckLinkAPI.h" +typedef bool decklink_bool_t; typedef const char *decklink_string_t; #endif diff --git a/plugins/decklink/plugin-main.cpp b/plugins/decklink/plugin-main.cpp index 465eda0..78c9b25 100644 --- a/plugins/decklink/plugin-main.cpp +++ b/plugins/decklink/plugin-main.cpp @@ -13,16 +13,26 @@ OBS_MODULE_USE_DEFAULT_LOCALE("decklink", "en-US") #define MODE_NAME "mode_name" #define CHANNEL_FORMAT "channel_format" #define PIXEL_FORMAT "pixel_format" +#define COLOR_SPACE "color_space" +#define COLOR_RANGE "color_range" #define BUFFERING "buffering" #define TEXT_DEVICE obs_module_text("Device") #define TEXT_MODE obs_module_text("Mode") #define TEXT_PIXEL_FORMAT obs_module_text("PixelFormat") +#define TEXT_COLOR_SPACE obs_module_text("ColorSpace") +#define TEXT_COLOR_SPACE_DEFAULT obs_module_text("ColorSpace.Default") +#define TEXT_COLOR_RANGE obs_module_text("ColorRange") +#define TEXT_COLOR_RANGE_DEFAULT obs_module_text("ColorRange.Default") +#define TEXT_COLOR_RANGE_PARTIAL obs_module_text("ColorRange.Partial") +#define TEXT_COLOR_RANGE_FULL obs_module_text("ColorRange.Full") #define TEXT_CHANNEL_FORMAT obs_module_text("ChannelFormat") #define TEXT_CHANNEL_FORMAT_NONE obs_module_text("ChannelFormat.None") #define TEXT_CHANNEL_FORMAT_2_0CH obs_module_text("ChannelFormat.2_0ch") +#define TEXT_CHANNEL_FORMAT_2_1CH obs_module_text("ChannelFormat.2_1ch") +#define TEXT_CHANNEL_FORMAT_4_0CH obs_module_text("ChannelFormat.4_0ch") +#define TEXT_CHANNEL_FORMAT_4_1CH obs_module_text("ChannelFormat.4_1ch") #define TEXT_CHANNEL_FORMAT_5_1CH obs_module_text("ChannelFormat.5_1ch") -#define TEXT_CHANNEL_FORMAT_5_1CH_BACK obs_module_text("ChannelFormat.5_1chBack") #define TEXT_CHANNEL_FORMAT_7_1CH obs_module_text("ChannelFormat.7_1ch") #define TEXT_BUFFERING obs_module_text("Buffering") @@ -32,12 +42,14 @@ static void decklink_enable_buffering(DeckLink *decklink, bool enabled) { obs_source_t *source = decklink->GetSource(); obs_source_set_async_unbuffered(source, !enabled); + decklink->buffering = enabled; } static void *decklink_create(obs_data_t *settings, obs_source_t *source) { DeckLink *decklink = new DeckLink(source, deviceEnum); + obs_source_set_async_decoupled(source, true); decklink_enable_buffering(decklink, obs_data_get_bool(settings, BUFFERING)); @@ -58,8 +70,19 @@ static void decklink_update(void *data, obs_data_t *settings) long long id = obs_data_get_int(settings, MODE_ID); BMDPixelFormat pixelFormat = (BMDPixelFormat)obs_data_get_int(settings, PIXEL_FORMAT); - speaker_layout channelFormat = (speaker_layout)obs_data_get_int(settings, - CHANNEL_FORMAT); + video_colorspace colorSpace = (video_colorspace)obs_data_get_int(settings, + COLOR_SPACE); + video_range_type colorRange = (video_range_type)obs_data_get_int(settings, + COLOR_RANGE); + int chFmtInt = (int)obs_data_get_int(settings, CHANNEL_FORMAT); + + if (chFmtInt == 7) { + chFmtInt = SPEAKERS_5POINT1; + } else if (chFmtInt < SPEAKERS_UNKNOWN || chFmtInt > SPEAKERS_7POINT1) { + chFmtInt = 2; + } + + speaker_layout channelFormat = (speaker_layout)chFmtInt; decklink_enable_buffering(decklink, obs_data_get_bool(settings, BUFFERING)); @@ -68,14 +91,18 @@ static void decklink_update(void *data, obs_data_t *settings) device.Set(deviceEnum->FindByHash(hash)); decklink->SetPixelFormat(pixelFormat); + decklink->SetColorSpace(colorSpace); + decklink->SetColorRange(colorRange); decklink->SetChannelFormat(channelFormat); decklink->Activate(device, id); } static void decklink_get_defaults(obs_data_t *settings) { - obs_data_set_default_bool(settings, BUFFERING, true); + obs_data_set_default_bool(settings, BUFFERING, false); obs_data_set_default_int(settings, PIXEL_FORMAT, bmdFormat8BitYUV); + obs_data_set_default_int(settings, COLOR_SPACE, VIDEO_CS_DEFAULT); + obs_data_set_default_int(settings, COLOR_RANGE, VIDEO_RANGE_DEFAULT); obs_data_set_default_int(settings, CHANNEL_FORMAT, SPEAKERS_STEREO); } @@ -136,10 +163,14 @@ static bool decklink_device_changed(obs_properties_t *props, } if (device->GetMaxChannel() >= 8) { + obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_2_1CH, + SPEAKERS_2POINT1); + obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_4_0CH, + SPEAKERS_4POINT0); + obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_4_1CH, + SPEAKERS_4POINT1); obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_5_1CH, SPEAKERS_5POINT1); - obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_5_1CH_BACK, - SPEAKERS_5POINT1_SURROUND); obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_7_1CH, SPEAKERS_7POINT1); } @@ -162,6 +193,38 @@ static void fill_out_devices(obs_property_t *list) deviceEnum->Unlock(); } +static bool color_format_changed(obs_properties_t *props, + obs_property_t *list, obs_data_t *settings); + +static bool mode_id_changed(obs_properties_t *props, + obs_property_t *list, obs_data_t *settings) +{ + long long id = obs_data_get_int(settings, MODE_ID); + + list = obs_properties_get(props, PIXEL_FORMAT); + obs_property_set_visible(list, id != MODE_ID_AUTO); + + return color_format_changed(props, nullptr, settings); +} + +static bool color_format_changed(obs_properties_t *props, + obs_property_t *list, obs_data_t *settings) +{ + long long id = obs_data_get_int(settings, MODE_ID); + BMDPixelFormat pixelFormat = (BMDPixelFormat)obs_data_get_int(settings, + PIXEL_FORMAT); + + list = obs_properties_get(props, COLOR_SPACE); + obs_property_set_visible(list, + id != MODE_ID_AUTO && pixelFormat == bmdFormat8BitYUV); + + list = obs_properties_get(props, COLOR_RANGE); + obs_property_set_visible(list, + id == MODE_ID_AUTO || pixelFormat == bmdFormat8BitYUV); + + return true; +} + static obs_properties_t *decklink_get_properties(void *data) { obs_properties_t *props = obs_properties_create(); @@ -174,13 +237,28 @@ static obs_properties_t *decklink_get_properties(void *data) list = obs_properties_add_list(props, MODE_ID, TEXT_MODE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback(list, mode_id_changed); list = obs_properties_add_list(props, PIXEL_FORMAT, TEXT_PIXEL_FORMAT, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback(list, color_format_changed); + obs_property_list_add_int(list, "8-bit YUV", bmdFormat8BitYUV); obs_property_list_add_int(list, "8-bit BGRA", bmdFormat8BitBGRA); + list = obs_properties_add_list(props, COLOR_SPACE, TEXT_COLOR_SPACE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(list, TEXT_COLOR_SPACE_DEFAULT, VIDEO_CS_DEFAULT); + obs_property_list_add_int(list, "BT.601", VIDEO_CS_601); + obs_property_list_add_int(list, "BT.709", VIDEO_CS_709); + + list = obs_properties_add_list(props, COLOR_RANGE, TEXT_COLOR_RANGE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(list, TEXT_COLOR_RANGE_DEFAULT, VIDEO_RANGE_DEFAULT); + obs_property_list_add_int(list, TEXT_COLOR_RANGE_PARTIAL, VIDEO_RANGE_PARTIAL); + obs_property_list_add_int(list, TEXT_COLOR_RANGE_FULL, VIDEO_RANGE_FULL); + list = obs_properties_add_list(props, CHANNEL_FORMAT, TEXT_CHANNEL_FORMAT, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); @@ -188,6 +266,16 @@ static obs_properties_t *decklink_get_properties(void *data) SPEAKERS_UNKNOWN); obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_2_0CH, SPEAKERS_STEREO); + obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_2_1CH, + SPEAKERS_2POINT1); + obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_4_0CH, + SPEAKERS_4POINT0); + obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_4_1CH, + SPEAKERS_4POINT1); + obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_5_1CH, + SPEAKERS_5POINT1); + obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_7_1CH, + SPEAKERS_7POINT1); obs_properties_add_bool(props, BUFFERING, TEXT_BUFFERING); diff --git a/plugins/image-source/data/locale/ca-ES.ini b/plugins/image-source/data/locale/ca-ES.ini index 0da2942..16cae14 100644 --- a/plugins/image-source/data/locale/ca-ES.ini +++ b/plugins/image-source/data/locale/ca-ES.ini @@ -6,12 +6,28 @@ SlideShow="Presentació de diapositives" SlideShow.TransitionSpeed="Velocitat de la transició (mil·lisegons)" SlideShow.SlideTime="Temps entre diapositives (mil·lisegons)" SlideShow.Files="Fitxers d'imatge" +SlideShow.CustomSize="Relació d'aspecte" +SlideShow.CustomSize.Auto="Automàtic" SlideShow.Randomize="Reproducció aleatòria" +SlideShow.Loop="Bucle" SlideShow.Transition="Transició" SlideShow.Transition.Cut="Tall" SlideShow.Transition.Fade="Desaparèixer" SlideShow.Transition.Swipe="De cop" SlideShow.Transition.Slide="Diapositiva" +SlideShow.PlaybackBehavior="Comportament de la visibilitat" +SlideShow.PlaybackBehavior.StopRestart="Atura quan no sigui visible, reinicia quan sigui visible" +SlideShow.PlaybackBehavior.PauseUnpause="Pausa quan no sigui visible, reprèn quan sigui visible" +SlideShow.PlaybackBehavior.AlwaysPlay="Reprodueix sempre fins i tot quan no sigui visible" +SlideShow.SlideMode="Mode de diapositives" +SlideShow.SlideMode.Auto="Automàtic" +SlideShow.SlideMode.Manual="Manual (ús de tecles per controlar les diapositives)" +SlideShow.PlayPause="Reprodueix/Pausa" +SlideShow.Restart="Reinicia" +SlideShow.Stop="Atura" +SlideShow.NextSlide="Diapositiva següent" +SlideShow.PreviousSlide="Diapositiva anterior" +SlideShow.HideWhenDone="Amaga en finalitzar la presentació" ColorSource="Origen del color" ColorSource.Color="Color" diff --git a/plugins/image-source/data/locale/cs-CZ.ini b/plugins/image-source/data/locale/cs-CZ.ini index e3dc169..6d47a57 100644 --- a/plugins/image-source/data/locale/cs-CZ.ini +++ b/plugins/image-source/data/locale/cs-CZ.ini @@ -9,11 +9,25 @@ SlideShow.Files="Soubory obrázků" SlideShow.CustomSize="Poměr stran" SlideShow.CustomSize.Auto="Automatický" SlideShow.Randomize="Náhodné přehrávání" +SlideShow.Loop="Opakovat" SlideShow.Transition="Přechod" SlideShow.Transition.Cut="Střih" SlideShow.Transition.Fade="Slábnutí" SlideShow.Transition.Swipe="Tažení" SlideShow.Transition.Slide="Sklouznutí" +SlideShow.PlaybackBehavior="Závislost na viditelnosti" +SlideShow.PlaybackBehavior.StopRestart="Zastavit při skrytém, restartovat při obnovení" +SlideShow.PlaybackBehavior.PauseUnpause="Pozastavit při skrytém, pokračovat při obnovení" +SlideShow.PlaybackBehavior.AlwaysPlay="Přehrát vždy (i když není vidět)" +SlideShow.SlideMode="Režim prezentace" +SlideShow.SlideMode.Auto="Automatický" +SlideShow.SlideMode.Manual="Manuální (pro ovládání prezentace je nutné použít zkratky)" +SlideShow.PlayPause="Přehrát/Pozastavit" +SlideShow.Restart="Restartovat" +SlideShow.Stop="Zastavit" +SlideShow.NextSlide="Další snímek" +SlideShow.PreviousSlide="Předchozí snímek" +SlideShow.HideWhenDone="Skrýt na konci prezentace" ColorSource="Zdroj barvy" ColorSource.Color="Barva" diff --git a/plugins/image-source/data/locale/da-DK.ini b/plugins/image-source/data/locale/da-DK.ini index ef3ff4f..3623fab 100644 --- a/plugins/image-source/data/locale/da-DK.ini +++ b/plugins/image-source/data/locale/da-DK.ini @@ -9,11 +9,25 @@ SlideShow.Files="Billedfiler" SlideShow.CustomSize="Afgrænsningsstørrelse/Formatforhold" SlideShow.CustomSize.Auto="Automatisk" SlideShow.Randomize="Tilfældig afspilning" +SlideShow.Loop="Gentagelse" SlideShow.Transition="Overgang" SlideShow.Transition.Cut="Klip" SlideShow.Transition.Fade="Overgang" SlideShow.Transition.Swipe="Stryg" SlideShow.Transition.Slide="Glide" +SlideShow.PlaybackBehavior="Synlighedsadfærd" +SlideShow.PlaybackBehavior.StopRestart="Stop når ikke synlig, genstart når synlig" +SlideShow.PlaybackBehavior.PauseUnpause="Sæt på pause når ikke synlig, genoptag når synlig" +SlideShow.PlaybackBehavior.AlwaysPlay="Afspil altid også når usynlig" +SlideShow.SlideMode="Diasshowtilstand" +SlideShow.SlideMode.Auto="Automatisk" +SlideShow.SlideMode.Manual="Manuel (styr diasshow via genvejstaster)" +SlideShow.PlayPause="Afspil/pause" +SlideShow.Restart="Genstart" +SlideShow.Stop="Stop" +SlideShow.NextSlide="Næste dias" +SlideShow.PreviousSlide="Foregående dias" +SlideShow.HideWhenDone="Skjul når diasshow er færdigt" ColorSource="Farvekilde" ColorSource.Color="Farve" diff --git a/plugins/image-source/data/locale/de-DE.ini b/plugins/image-source/data/locale/de-DE.ini index 9c7220c..0914f65 100644 --- a/plugins/image-source/data/locale/de-DE.ini +++ b/plugins/image-source/data/locale/de-DE.ini @@ -3,17 +3,31 @@ File="Bilddatei" UnloadWhenNotShowing="Entlade Bild, wenn es nicht angezeigt wird" SlideShow="Diashow" -SlideShow.TransitionSpeed="Geschwindigkeit des Übergangs (Millisekunden)" +SlideShow.TransitionSpeed="Übergangsgeschwindigkeit (Millisekunden)" SlideShow.SlideTime="Zeit zwischen Bildern (Millisekunden)" SlideShow.Files="Bilddateien" SlideShow.CustomSize="Rahmen Größe/Seitenverhältnis" SlideShow.CustomSize.Auto="Automatisch" SlideShow.Randomize="Zufällige Wiedergabe" +SlideShow.Loop="Endlosschleife" SlideShow.Transition="Übergang" SlideShow.Transition.Cut="Schnitt" SlideShow.Transition.Fade="Überblenden" SlideShow.Transition.Swipe="Swipe" SlideShow.Transition.Slide="Slide" +SlideShow.PlaybackBehavior="Sichtbarkeitsverhalten" +SlideShow.PlaybackBehavior.StopRestart="Anhalten wenn nicht sichtbar, neu starten wenn sichtbar" +SlideShow.PlaybackBehavior.PauseUnpause="Pausieren wenn nicht sichtbar, fortsetzen wenn sichtbar" +SlideShow.PlaybackBehavior.AlwaysPlay="Immer abspielen, auch wenn nicht sichtbar" +SlideShow.SlideMode="Diashowmodus" +SlideShow.SlideMode.Auto="Automatisch" +SlideShow.SlideMode.Manual="Manuell (Hotkeys verwenden, um Diashow zu steuern)" +SlideShow.PlayPause="Abspielen/Pausieren" +SlideShow.Restart="Neu starten" +SlideShow.Stop="Stop" +SlideShow.NextSlide="Nächstes Bild" +SlideShow.PreviousSlide="Vorheriges Bild" +SlideShow.HideWhenDone="Verbergen, wenn die Diashow vorbei ist" ColorSource="Farbquelle" ColorSource.Color="Farbe" diff --git a/plugins/image-source/data/locale/el-GR.ini b/plugins/image-source/data/locale/el-GR.ini index 8d260a9..5922cbf 100644 --- a/plugins/image-source/data/locale/el-GR.ini +++ b/plugins/image-source/data/locale/el-GR.ini @@ -2,6 +2,21 @@ ImageInput="Εικόνα" File="Αρχείο εικόνας" UnloadWhenNotShowing="Ξεφόρτωση εικόνας όταν δεν εμφανίζεται" -SlideShow.Files="Αρχεία φωτογραφίας" +SlideShow="Παρουσίαση Εικόνων" +SlideShow.TransitionSpeed="Ταχύτητα μετάβασης (χιλιοστά δευτερολέπτου)" +SlideShow.SlideTime="Χρόνος μεταξύ των διαφανειών (χιλιοστά δευτερολέπτου)" +SlideShow.Files="Αρχεία εικόνων" +SlideShow.CustomSize="Μέγεθος Οριοθέτησης/Αναλογία" +SlideShow.CustomSize.Auto="Αυτόματο" +SlideShow.Randomize="Τυχαιοποίηση αναπαραγωγής" +SlideShow.Transition="Μετάβαση" +SlideShow.Transition.Cut="Αποκοπή" +SlideShow.Transition.Fade="Ξεθώριασμα" +SlideShow.Transition.Swipe="Σύρσιμο" +SlideShow.Transition.Slide="Ολίσθηση" +ColorSource="Πηγή χρώματος" +ColorSource.Color="Χρώμα" +ColorSource.Width="Πλάτος" +ColorSource.Height="Ύψος" diff --git a/plugins/image-source/data/locale/en-US.ini b/plugins/image-source/data/locale/en-US.ini index da74269..21c275e 100644 --- a/plugins/image-source/data/locale/en-US.ini +++ b/plugins/image-source/data/locale/en-US.ini @@ -9,11 +9,25 @@ SlideShow.Files="Image Files" SlideShow.CustomSize="Bounding Size/Aspect Ratio" SlideShow.CustomSize.Auto="Automatic" SlideShow.Randomize="Randomize Playback" +SlideShow.Loop="Loop" SlideShow.Transition="Transition" SlideShow.Transition.Cut="Cut" SlideShow.Transition.Fade="Fade" SlideShow.Transition.Swipe="Swipe" SlideShow.Transition.Slide="Slide" +SlideShow.PlaybackBehavior="Visibility Behavior" +SlideShow.PlaybackBehavior.StopRestart="Stop when not visible, restart when visible" +SlideShow.PlaybackBehavior.PauseUnpause="Pause when not visible, unpause when visible" +SlideShow.PlaybackBehavior.AlwaysPlay="Always play even when not visible" +SlideShow.SlideMode="Slide Mode" +SlideShow.SlideMode.Auto="Automatic" +SlideShow.SlideMode.Manual="Manual (Use hotkeys to control slideshow)" +SlideShow.PlayPause="Play/Pause" +SlideShow.Restart="Restart" +SlideShow.Stop="Stop" +SlideShow.NextSlide="Next Slide" +SlideShow.PreviousSlide="Previous Slide" +SlideShow.HideWhenDone="Hide when slideshow is done" ColorSource="Color Source" ColorSource.Color="Color" diff --git a/plugins/image-source/data/locale/es-ES.ini b/plugins/image-source/data/locale/es-ES.ini index 3c64264..3ca74fc 100644 --- a/plugins/image-source/data/locale/es-ES.ini +++ b/plugins/image-source/data/locale/es-ES.ini @@ -9,11 +9,25 @@ SlideShow.Files="Archivo de imagen" SlideShow.CustomSize="Relación de aspecto" SlideShow.CustomSize.Auto="Automático" SlideShow.Randomize="Reproducción aleatoria" +SlideShow.Loop="Bucle" SlideShow.Transition="Transición" SlideShow.Transition.Cut="Corte" SlideShow.Transition.Fade="Desvanecimiento" SlideShow.Transition.Swipe="Deslizar Rapido" SlideShow.Transition.Slide="Deslizar" +SlideShow.PlaybackBehavior="Comportamiento de visibilidad" +SlideShow.PlaybackBehavior.StopRestart="Detener cuando no sea visible, reiniciar cuando sea visible" +SlideShow.PlaybackBehavior.PauseUnpause="Pausar cuando no sea visible, reanudar cuando sea visible" +SlideShow.PlaybackBehavior.AlwaysPlay="Reproducir siempre incluso cuando no sea visible" +SlideShow.SlideMode="Modo de diapositivas" +SlideShow.SlideMode.Auto="Automático" +SlideShow.SlideMode.Manual="Manual (uso de teclas para controlar las diapositivas)" +SlideShow.PlayPause="Reproducir/Pausar" +SlideShow.Restart="Reiniciar" +SlideShow.Stop="Detener" +SlideShow.NextSlide="Siguiente diapositiva" +SlideShow.PreviousSlide="Diapositiva anterior" +SlideShow.HideWhenDone="Ocultar cuando se acabe la presentación" ColorSource="Origen de color" ColorSource.Color="Color" diff --git a/plugins/image-source/data/locale/eu-ES.ini b/plugins/image-source/data/locale/eu-ES.ini index e954f17..325300c 100644 --- a/plugins/image-source/data/locale/eu-ES.ini +++ b/plugins/image-source/data/locale/eu-ES.ini @@ -9,11 +9,25 @@ SlideShow.Files="Irudi fitxategiak" SlideShow.CustomSize="Markoaren tamaina/Aspektu-erlazioa" SlideShow.CustomSize.Auto="Automatikoa" SlideShow.Randomize="Ausazko erreprodukzioa" +SlideShow.Loop="Begizta" SlideShow.Transition="Trantsizioa" SlideShow.Transition.Cut="Ebaki" SlideShow.Transition.Fade="Iraungi" SlideShow.Transition.Swipe="Korritu" SlideShow.Transition.Slide="Irristatu" +SlideShow.PlaybackBehavior="Ikuste-jokabidea" +SlideShow.PlaybackBehavior.StopRestart="Ikusten ez bada gelditu, ikusten denean berrabiarazi" +SlideShow.PlaybackBehavior.PauseUnpause="Ikusten ez bada pausatu, ikusten denean jarraitu" +SlideShow.PlaybackBehavior.AlwaysPlay="Erreproduzitu beti nahiz eta ez ikusi" +SlideShow.SlideMode="Diapositiba modua" +SlideShow.SlideMode.Auto="Automatikoa" +SlideShow.SlideMode.Manual="Eskuz (erabili teklak kontrolatzeko aurkezpena)" +SlideShow.PlayPause=" Erreproduzitu/Pausarazi" +SlideShow.Restart="Berrabiarazi" +SlideShow.Stop="Gelditu" +SlideShow.NextSlide="Hurrengo diapositiba" +SlideShow.PreviousSlide="Aurreko diapositiba" +SlideShow.HideWhenDone="Ezkutatu aurkezpena bukatzean" ColorSource="Kolorearen iturburua" ColorSource.Color="Kolorea" diff --git a/plugins/image-source/data/locale/fi-FI.ini b/plugins/image-source/data/locale/fi-FI.ini index 64fdea6..b7dd9dc 100644 --- a/plugins/image-source/data/locale/fi-FI.ini +++ b/plugins/image-source/data/locale/fi-FI.ini @@ -9,11 +9,25 @@ SlideShow.Files="Kuvatiedostot" SlideShow.CustomSize="Rajauskoko/Kuvasuhde" SlideShow.CustomSize.Auto="Automaattinen" SlideShow.Randomize="Toista satunnaisesti" +SlideShow.Loop="Toista jatkuvasti" SlideShow.Transition="Siirtymä" SlideShow.Transition.Cut="Leikkaa" SlideShow.Transition.Fade="Häivytä" SlideShow.Transition.Swipe="Pyyhkäise" SlideShow.Transition.Slide="Liu'uta" +SlideShow.PlaybackBehavior="Näkyvyyskäyttäytyminen" +SlideShow.PlaybackBehavior.StopRestart="Pysäytä toisto kun lähde ei näy. Käynnistä toisto uudelleen, kun se on taas näkyvissä" +SlideShow.PlaybackBehavior.PauseUnpause="Keskeytä toisto kun lähde ei näy. Jatka toistoa, kun se on taas näkyvissä" +SlideShow.PlaybackBehavior.AlwaysPlay="Toista aina, vaikka lähde ei olisi näkyvissä" +SlideShow.SlideMode="Leikkaustila" +SlideShow.SlideMode.Auto="Automaattinen" +SlideShow.SlideMode.Manual="Manuaalinen (Käytä pikanäppäimiä ohjataksesi diashowta)" +SlideShow.PlayPause="Toista/Tauko" +SlideShow.Restart="Aloita alusta" +SlideShow.Stop="Pysäytä" +SlideShow.NextSlide="Seuraava dia" +SlideShow.PreviousSlide="Edellinen dia" +SlideShow.HideWhenDone="Piilota kun diaesitys on valmis" ColorSource="Värilähde" ColorSource.Color="Väri" diff --git a/plugins/image-source/data/locale/fr-FR.ini b/plugins/image-source/data/locale/fr-FR.ini index fdf7713..edb8113 100644 --- a/plugins/image-source/data/locale/fr-FR.ini +++ b/plugins/image-source/data/locale/fr-FR.ini @@ -9,11 +9,25 @@ SlideShow.Files="Fichiers image" SlideShow.CustomSize="Taille Limite/Ratio d'aspect" SlideShow.CustomSize.Auto="Automatique" SlideShow.Randomize="Lecture aléatoire" +SlideShow.Loop="Boucle" SlideShow.Transition="Transition" SlideShow.Transition.Cut="Coupure" SlideShow.Transition.Fade="Fondu" SlideShow.Transition.Swipe="Balayage" SlideShow.Transition.Slide="Glissement" +SlideShow.PlaybackBehavior="Comportement de Visibilité" +SlideShow.PlaybackBehavior.StopRestart="Arrêter si non visible, redémarrer si visible" +SlideShow.PlaybackBehavior.PauseUnpause="Suspendre si non visible, reprendre si visible" +SlideShow.PlaybackBehavior.AlwaysPlay="Toujours jouer même lorsqu'elle n'est pas visible" +SlideShow.SlideMode="Mode Diapo" +SlideShow.SlideMode.Auto="Automatique" +SlideShow.SlideMode.Manual="Manuel (utiliser les raccourcis clavier pour contrôler le diapo)" +SlideShow.PlayPause="Lire/Pause" +SlideShow.Restart="Relancer" +SlideShow.Stop="Arrêter" +SlideShow.NextSlide="Diapo suivante" +SlideShow.PreviousSlide="Diapo précédente" +SlideShow.HideWhenDone="Masquer lorsque le diaporama est terminé" ColorSource="Source de couleur" ColorSource.Color="Couleur" diff --git a/plugins/image-source/data/locale/hu-HU.ini b/plugins/image-source/data/locale/hu-HU.ini index 2b223ba..d730c1a 100644 --- a/plugins/image-source/data/locale/hu-HU.ini +++ b/plugins/image-source/data/locale/hu-HU.ini @@ -9,11 +9,25 @@ SlideShow.Files="Képfájlok" SlideShow.CustomSize="Befoglaló méret/Képarány" SlideShow.CustomSize.Auto="Automatikus" SlideShow.Randomize="Véletlenszerű lejátszás" +SlideShow.Loop="Ismétlés" SlideShow.Transition="Átmenet" SlideShow.Transition.Cut="Kivágás" SlideShow.Transition.Fade="Áttűnés" SlideShow.Transition.Swipe="Lapozás" SlideShow.Transition.Slide="Csúsztatás" +SlideShow.PlaybackBehavior="Láthatósági opció" +SlideShow.PlaybackBehavior.StopRestart="Leállítás, ha nem látható, újraindul, ha látható" +SlideShow.PlaybackBehavior.PauseUnpause="Szüneteltet, ha nem látható, folytatás, ha látható" +SlideShow.PlaybackBehavior.AlwaysPlay="Mindig lejátsza, akkor is, ha nem látható" +SlideShow.SlideMode="Csúsztatás mód" +SlideShow.SlideMode.Auto="Automatikus" +SlideShow.SlideMode.Manual="Kézi (Billentyűket használjon az irányításhoz)" +SlideShow.PlayPause="Lejátszás/Szünet" +SlideShow.Restart="Újraindítás" +SlideShow.Stop="Leállítás" +SlideShow.NextSlide="Következő dia" +SlideShow.PreviousSlide="Előző dia" +SlideShow.HideWhenDone="Elrejtés a diavetítés végén" ColorSource="Színforrás" ColorSource.Color="Szín" diff --git a/plugins/image-source/data/locale/it-IT.ini b/plugins/image-source/data/locale/it-IT.ini index 76be183..5b1f5ef 100644 --- a/plugins/image-source/data/locale/it-IT.ini +++ b/plugins/image-source/data/locale/it-IT.ini @@ -6,12 +6,28 @@ SlideShow="Presentazione immagini" SlideShow.TransitionSpeed="Velocità di transizione (millisecondi)" SlideShow.SlideTime="Tempo tra le diapositive (millisecondi)" SlideShow.Files="Files Immagini" +SlideShow.CustomSize="Dimensioni/proporzioni" +SlideShow.CustomSize.Auto="Automatico" SlideShow.Randomize="Randomizzare la riproduzione" +SlideShow.Loop="Loop" SlideShow.Transition="Transizione" SlideShow.Transition.Cut="Taglio" SlideShow.Transition.Fade="Dissolvenza" SlideShow.Transition.Swipe="Scorri" SlideShow.Transition.Slide="Scivola" +SlideShow.PlaybackBehavior="Comportamento visibilità" +SlideShow.PlaybackBehavior.StopRestart="Interrompi quando non visibile, riavvia quando visibile" +SlideShow.PlaybackBehavior.PauseUnpause="Pausa quando non visibile, riprendi quando visibile" +SlideShow.PlaybackBehavior.AlwaysPlay="Continua sempre anche quando non visibile" +SlideShow.SlideMode="Modalità Slide" +SlideShow.SlideMode.Auto="Automatico" +SlideShow.SlideMode.Manual="Manuale (usa i tasti di scelta rapida per controllare la presentazione)" +SlideShow.PlayPause="Play/Pausa" +SlideShow.Restart="Riavvia" +SlideShow.Stop="Stop" +SlideShow.NextSlide="Prossima Slide" +SlideShow.PreviousSlide="Slide Precedente" +SlideShow.HideWhenDone="Nascondi quando la presentazione è terminata" ColorSource="Origine del colore" ColorSource.Color="Colore" diff --git a/plugins/image-source/data/locale/ja-JP.ini b/plugins/image-source/data/locale/ja-JP.ini index de0bb8a..6ed4174 100644 --- a/plugins/image-source/data/locale/ja-JP.ini +++ b/plugins/image-source/data/locale/ja-JP.ini @@ -9,11 +9,25 @@ SlideShow.Files="画像ファイル" SlideShow.CustomSize="バウンディングサイズ/アスペクト比" SlideShow.CustomSize.Auto="自動" SlideShow.Randomize="ランダム再生" +SlideShow.Loop="繰り返し" SlideShow.Transition="トランジション" SlideShow.Transition.Cut="カット" SlideShow.Transition.Fade="フェード" SlideShow.Transition.Swipe="スワイプ" SlideShow.Transition.Slide="スライド" +SlideShow.PlaybackBehavior="表示の動作" +SlideShow.PlaybackBehavior.StopRestart="表示されていないときは停止し、表示されているときは再開する" +SlideShow.PlaybackBehavior.PauseUnpause="表示されていないときは一時停止し、表示されているときは一時停止しない" +SlideShow.PlaybackBehavior.AlwaysPlay="表示されていないときにも常に再生する" +SlideShow.SlideMode="スライドモード" +SlideShow.SlideMode.Auto="自動" +SlideShow.SlideMode.Manual="手動 (ホットキーを使用してスライドショーを制御する)" +SlideShow.PlayPause="再生/一時停止" +SlideShow.Restart="再開" +SlideShow.Stop="停止" +SlideShow.NextSlide="次のスライド" +SlideShow.PreviousSlide="前のスライド" +SlideShow.HideWhenDone="スライドショーが終わったら非表示にする" ColorSource="色ソース" ColorSource.Color="色" diff --git a/plugins/image-source/data/locale/ko-KR.ini b/plugins/image-source/data/locale/ko-KR.ini index 942e92c..d7d0a3e 100644 --- a/plugins/image-source/data/locale/ko-KR.ini +++ b/plugins/image-source/data/locale/ko-KR.ini @@ -9,11 +9,25 @@ SlideShow.Files="이미지 파일 형식" SlideShow.CustomSize="경계 크기/화면 비율" SlideShow.CustomSize.Auto="자동" SlideShow.Randomize="무작위 재생" +SlideShow.Loop="반복" SlideShow.Transition="전환 방식" SlideShow.Transition.Cut="자르기" SlideShow.Transition.Fade="서서히 사라지기" SlideShow.Transition.Swipe="밀어내기" SlideShow.Transition.Slide="슬라이드" +SlideShow.PlaybackBehavior="표시 동작 설정" +SlideShow.PlaybackBehavior.StopRestart="보이지 않을 때 중단, 보이면 재시작" +SlideShow.PlaybackBehavior.PauseUnpause="보이지 않을 때 일시 정지, 보이면 일시 정지 해제" +SlideShow.PlaybackBehavior.AlwaysPlay="보이지 않아도 항상 재생" +SlideShow.SlideMode="슬라이드 방식" +SlideShow.SlideMode.Auto="자동" +SlideShow.SlideMode.Manual="수동 (단축키로 슬라이드 쇼를 제어)" +SlideShow.PlayPause="재생/일시 정지" +SlideShow.Restart="재시작" +SlideShow.Stop="중단" +SlideShow.NextSlide="다음 슬라이드" +SlideShow.PreviousSlide="이전 슬라이드" +SlideShow.HideWhenDone="슬라이드 쇼가 끝나면 숨기기" ColorSource="색상 소스" ColorSource.Color="색상" diff --git a/plugins/image-source/data/locale/nb-NO.ini b/plugins/image-source/data/locale/nb-NO.ini index 1d66ae0..aa67de2 100644 --- a/plugins/image-source/data/locale/nb-NO.ini +++ b/plugins/image-source/data/locale/nb-NO.ini @@ -6,11 +6,31 @@ SlideShow="Lysbildefremvisning" SlideShow.TransitionSpeed="Overgangshastighet (millisekunder)" SlideShow.SlideTime="Tid mellom lysbilder (millisekunder)" SlideShow.Files="Bildefiler" +SlideShow.CustomSize="Markeringsboks/størrelsesforhold" +SlideShow.CustomSize.Auto="Automatisk" SlideShow.Randomize="Tilfeldig avspilling" +SlideShow.Loop="Repeter" SlideShow.Transition="Overgang" SlideShow.Transition.Cut="Kutt" SlideShow.Transition.Fade="Forløpning" SlideShow.Transition.Swipe="Sveip" SlideShow.Transition.Slide="Skyv" +SlideShow.PlaybackBehavior="Synlighetsvalg" +SlideShow.PlaybackBehavior.StopRestart="Stopp når ikke synlig, spill fra starten når synlig" +SlideShow.PlaybackBehavior.PauseUnpause="Stans når ikke synlig, spill når synlig" +SlideShow.PlaybackBehavior.AlwaysPlay="Alltid spill, selv når ikke synlig" +SlideShow.SlideMode="Lysbildemodus" +SlideShow.SlideMode.Auto="Automatisk" +SlideShow.SlideMode.Manual="Manuell (bruk hurtigtaster for å styre lysbildene)" +SlideShow.PlayPause="Spill av/pause" +SlideShow.Restart="Start på nytt" +SlideShow.Stop="Stopp" +SlideShow.NextSlide="Neste lysbilde" +SlideShow.PreviousSlide="Forrige lysbilde" +SlideShow.HideWhenDone="Skjul når lysbildefremvisning er ferdig" +ColorSource="Fargekilde" +ColorSource.Color="Farge" +ColorSource.Width="Bredde" +ColorSource.Height="Høyde" diff --git a/plugins/image-source/data/locale/nl-NL.ini b/plugins/image-source/data/locale/nl-NL.ini index 3b34c75..0c4d0ce 100644 --- a/plugins/image-source/data/locale/nl-NL.ini +++ b/plugins/image-source/data/locale/nl-NL.ini @@ -9,11 +9,25 @@ SlideShow.Files="Afbeeldingsbestanden" SlideShow.CustomSize="Randgrootte/Beeldverhouding" SlideShow.CustomSize.Auto="Automatisch" SlideShow.Randomize="Willekeurige Volgorde" +SlideShow.Loop="Herhalen" SlideShow.Transition="Overgang" SlideShow.Transition.Cut="Knippen" SlideShow.Transition.Fade="Vervagen" SlideShow.Transition.Swipe="Vegen" SlideShow.Transition.Slide="Slide" +SlideShow.PlaybackBehavior="Zichtbaarheidsgedrag" +SlideShow.PlaybackBehavior.StopRestart="Stop wanneer niet zichtbaar, herstart wanneer wel zichtbaar" +SlideShow.PlaybackBehavior.PauseUnpause="Pauzeer wanneer niet zichtbaar, hervat wanneer wel zichtbaar" +SlideShow.PlaybackBehavior.AlwaysPlay="Altijd spelen, zelfs wanneer niet zichtbaar" +SlideShow.SlideMode="Diamodus" +SlideShow.SlideMode.Auto="Automatisch" +SlideShow.SlideMode.Manual="Handmatig (gebruik sneltoetsen om diashow te bedienen)" +SlideShow.PlayPause="Afspelen/pauzeren" +SlideShow.Restart="Herstarten" +SlideShow.Stop="Stop" +SlideShow.NextSlide="Volgende dia" +SlideShow.PreviousSlide="Vorige dia" +SlideShow.HideWhenDone="Verbergen wanneer diashow afgelopen is" ColorSource="Kleurbron" ColorSource.Color="Kleur" diff --git a/plugins/image-source/data/locale/pl-PL.ini b/plugins/image-source/data/locale/pl-PL.ini index d947d35..88b15b7 100644 --- a/plugins/image-source/data/locale/pl-PL.ini +++ b/plugins/image-source/data/locale/pl-PL.ini @@ -9,11 +9,25 @@ SlideShow.Files="Pliki graficzne" SlideShow.CustomSize="Ograniczenie rozmiaru/Proporcje" SlideShow.CustomSize.Auto="Automatycznie" SlideShow.Randomize="Odtwarzanie losowe" +SlideShow.Loop="Pętla" SlideShow.Transition="Efekt przejścia" SlideShow.Transition.Cut="Cięcie" SlideShow.Transition.Fade="Zanikanie" SlideShow.Transition.Swipe="Przeciągnięcie" SlideShow.Transition.Slide="Przesunięcie" +SlideShow.PlaybackBehavior="Zachowanie" +SlideShow.PlaybackBehavior.StopRestart="Zatrzymaj, gdy niewidoczne. Odtwarzaj od początku, gdy widoczne." +SlideShow.PlaybackBehavior.PauseUnpause="Wstrzymaj, gdy niewidoczne. Wznów, gdy widoczne." +SlideShow.PlaybackBehavior.AlwaysPlay="Odtwarzaj cały czas bez względu na widoczność." +SlideShow.SlideMode="Tryb slajdu" +SlideShow.SlideMode.Auto="Automatycznie" +SlideShow.SlideMode.Manual="Ręcznie (użyj skrótów klawiszowych do kontroli slajdów)" +SlideShow.PlayPause="Odtwarzaj/Wstrzymaj" +SlideShow.Restart="Zrestartuj" +SlideShow.Stop="Zatrzymaj" +SlideShow.NextSlide="Następny slajd" +SlideShow.PreviousSlide="Poprzedni slajd" +SlideShow.HideWhenDone="Ukryj po zakończeniu slajdów" ColorSource="Kolor" ColorSource.Color="Kolor" diff --git a/plugins/image-source/data/locale/pt-BR.ini b/plugins/image-source/data/locale/pt-BR.ini index da5885e..f84e17e 100644 --- a/plugins/image-source/data/locale/pt-BR.ini +++ b/plugins/image-source/data/locale/pt-BR.ini @@ -9,11 +9,25 @@ SlideShow.Files="Arquivos de Imagem" SlideShow.CustomSize="Tamanho Delimitador/Proporção" SlideShow.CustomSize.Auto="Automático" SlideShow.Randomize="Reprodução aleatória" +SlideShow.Loop="Loop" SlideShow.Transition="Transição" SlideShow.Transition.Cut="Corte" SlideShow.Transition.Fade="Esmaecer" SlideShow.Transition.Swipe="Arrastar" SlideShow.Transition.Slide="Deslizar" +SlideShow.PlaybackBehavior="Comportamento de Visibilidade" +SlideShow.PlaybackBehavior.StopRestart="Parar quando não visível, reiniciar quando visível" +SlideShow.PlaybackBehavior.PauseUnpause="Pausa quando não visível, resumir quando visível" +SlideShow.PlaybackBehavior.AlwaysPlay="Sempre reproduzir, mesmo quando não visível" +SlideShow.SlideMode="Modo de slide" +SlideShow.SlideMode.Auto="Automático" +SlideShow.SlideMode.Manual="Manual (Usar as teclas de atalho para controlar a apresentação)" +SlideShow.PlayPause="Reproduzir/Pausar" +SlideShow.Restart="Reiniciar" +SlideShow.Stop="Parar" +SlideShow.NextSlide="Próximo Slide" +SlideShow.PreviousSlide="Slide Anterior" +SlideShow.HideWhenDone="Esconder quando terminar a apresentação" ColorSource="Fonte de Cor" ColorSource.Color="Cor" diff --git a/plugins/image-source/data/locale/ro-RO.ini b/plugins/image-source/data/locale/ro-RO.ini index 64afe87..8b5ba98 100644 --- a/plugins/image-source/data/locale/ro-RO.ini +++ b/plugins/image-source/data/locale/ro-RO.ini @@ -9,4 +9,7 @@ SlideShow.Transition="Tranziție" SlideShow.Transition.Cut="Decupare" SlideShow.Transition.Slide="Diapozitiv" +ColorSource.Color="Culoare" +ColorSource.Width="Lățime" +ColorSource.Height="Înălțime" diff --git a/plugins/image-source/data/locale/ru-RU.ini b/plugins/image-source/data/locale/ru-RU.ini index 35f708e..3f626a5 100644 --- a/plugins/image-source/data/locale/ru-RU.ini +++ b/plugins/image-source/data/locale/ru-RU.ini @@ -9,11 +9,25 @@ SlideShow.Files="Файлы изображений" SlideShow.CustomSize="Ограничение размера/Соотношение сторон" SlideShow.CustomSize.Auto="Автоматически" SlideShow.Randomize="Случайное воспроизведение" +SlideShow.Loop="Повтор" SlideShow.Transition="Переход" SlideShow.Transition.Cut="Обрезать" SlideShow.Transition.Fade="Затухание" SlideShow.Transition.Swipe="Перемещение" SlideShow.Transition.Slide="Сдвиг" +SlideShow.PlaybackBehavior="Поведение видимости" +SlideShow.PlaybackBehavior.StopRestart="Всегда воспроизводить, даже если не видимый" +SlideShow.PlaybackBehavior.PauseUnpause="Приостановить если не видимый, возобновить если видимый" +SlideShow.PlaybackBehavior.AlwaysPlay="Всегда проигрывать, даже если не видимый" +SlideShow.SlideMode="Режим перелистывания" +SlideShow.SlideMode.Auto="Автоматический" +SlideShow.SlideMode.Manual="Ручной (Используйте горячие клавиши для управления слайд-шоу)" +SlideShow.PlayPause="Воспроизвести/Пауза" +SlideShow.Restart="Перезапустить" +SlideShow.Stop="Остановить" +SlideShow.NextSlide="Следующий слайд" +SlideShow.PreviousSlide="Предыдущий слайд" +SlideShow.HideWhenDone="Скрыть после завершения слайд-шоу" ColorSource="Фоновый цвет" ColorSource.Color="Цвет" diff --git a/plugins/image-source/data/locale/sk-SK.ini b/plugins/image-source/data/locale/sk-SK.ini index 96d90d6..870ec21 100644 --- a/plugins/image-source/data/locale/sk-SK.ini +++ b/plugins/image-source/data/locale/sk-SK.ini @@ -6,11 +6,31 @@ SlideShow="Prezentácia obrázkov" SlideShow.TransitionSpeed="Rýchlosť prechodu (v milisekundách)" SlideShow.SlideTime="Čas medzi snímkami (v milisekundách)" SlideShow.Files="Obrázky" +SlideShow.CustomSize="Veľkosť/pomer strán" +SlideShow.CustomSize.Auto="Automatické" SlideShow.Randomize="Náhodné prehrávanie" +SlideShow.Loop="Slučka" SlideShow.Transition="Prechod" SlideShow.Transition.Cut="Strih" SlideShow.Transition.Fade="Miznutie" SlideShow.Transition.Swipe="Potiahnite" SlideShow.Transition.Slide="Posunutie" +SlideShow.PlaybackBehavior="Fungovanie podľa viditeľnosti" +SlideShow.PlaybackBehavior.StopRestart="Zastaviť, keď nie je viditeľná, reštartovať, keď je viditeľná" +SlideShow.PlaybackBehavior.PauseUnpause="Pozastaviť, keď nie je viditeľná, zrušiť pozastavenie, keď je viditeľná" +SlideShow.PlaybackBehavior.AlwaysPlay="Vždy prehrávať, aj keď nie je viditeľná" +SlideShow.SlideMode="Ovládanie prezentácie" +SlideShow.SlideMode.Auto="Automatické" +SlideShow.SlideMode.Manual="Manuálne (použite klávesy na ovládanie prezentácie)" +SlideShow.PlayPause="Prehrať/Pozastaviť" +SlideShow.Restart="Reštartovať" +SlideShow.Stop="Zastaviť" +SlideShow.NextSlide="Ďalšia snímka" +SlideShow.PreviousSlide="Predchádzajúca snímka" +SlideShow.HideWhenDone="Skryť, keď prezentácia skončí" +ColorSource="Farba" +ColorSource.Color="Farba" +ColorSource.Width="Šírka" +ColorSource.Height="Výška" diff --git a/plugins/image-source/data/locale/sv-SE.ini b/plugins/image-source/data/locale/sv-SE.ini index a4bf1c5..0782414 100644 --- a/plugins/image-source/data/locale/sv-SE.ini +++ b/plugins/image-source/data/locale/sv-SE.ini @@ -6,13 +6,28 @@ SlideShow="Bildspel" SlideShow.TransitionSpeed="Övergångshastighet (millisekunder)" SlideShow.SlideTime="Tid mellan bilder (millisekunder)" SlideShow.Files="Bildfiler" +SlideShow.CustomSize="Avgränsningsstorlek/bildformat" SlideShow.CustomSize.Auto="Automatisk" SlideShow.Randomize="Slumpa uppspelning" +SlideShow.Loop="Upprepa" SlideShow.Transition="Övergång" SlideShow.Transition.Cut="Klipp" SlideShow.Transition.Fade="Tona" SlideShow.Transition.Swipe="Svep" SlideShow.Transition.Slide="Glid" +SlideShow.PlaybackBehavior="Synlighetsbeteende" +SlideShow.PlaybackBehavior.StopRestart="Stoppa när den inte syns, starta om när den syns" +SlideShow.PlaybackBehavior.PauseUnpause="Pausa när den inte syns, återuppta när den syns" +SlideShow.PlaybackBehavior.AlwaysPlay="Spela alltid även när den inte syns" +SlideShow.SlideMode="Bildspelsläge" +SlideShow.SlideMode.Auto="Automatisk" +SlideShow.SlideMode.Manual="Manuellt (använd kortkommandon för att styra bildspelet)" +SlideShow.PlayPause="Spela/Pausa" +SlideShow.Restart="Starta om" +SlideShow.Stop="Stoppa" +SlideShow.NextSlide="Nästa bild" +SlideShow.PreviousSlide="Föregående bild" +SlideShow.HideWhenDone="Dölj när bildspelet är färdigt" ColorSource="Färgkälla" ColorSource.Color="Färg" diff --git a/plugins/image-source/data/locale/tr-TR.ini b/plugins/image-source/data/locale/tr-TR.ini index c300a74..f43af3a 100644 --- a/plugins/image-source/data/locale/tr-TR.ini +++ b/plugins/image-source/data/locale/tr-TR.ini @@ -9,11 +9,25 @@ SlideShow.Files="Görüntü Dosyaları" SlideShow.CustomSize="Sınırlayıcı Boyut/En-Boy Oranı" SlideShow.CustomSize.Auto="Otomatik" SlideShow.Randomize="Rastgele Gösterim" +SlideShow.Loop="Döngü" SlideShow.Transition="Geçiş" SlideShow.Transition.Cut="Cut" SlideShow.Transition.Fade="Fade" SlideShow.Transition.Swipe="Swipe" SlideShow.Transition.Slide="Slide" +SlideShow.PlaybackBehavior="Görünürlük Davranışı" +SlideShow.PlaybackBehavior.StopRestart="Görünür olmadığında durdur, görünür olduğunda yeniden başlat" +SlideShow.PlaybackBehavior.PauseUnpause="Görünür olmadığında duraklat, görünür olduğunda oynat" +SlideShow.PlaybackBehavior.AlwaysPlay="Görünür olmadığında bile her zaman oynat" +SlideShow.SlideMode="Slayt Modu" +SlideShow.SlideMode.Auto="Otomatik" +SlideShow.SlideMode.Manual="Elle (Slayt gösterisini kontrol etmek için kısayol tuşlarını kullanın)" +SlideShow.PlayPause="Oynat/Duraklat" +SlideShow.Restart="Yeniden Başlat" +SlideShow.Stop="Durdur" +SlideShow.NextSlide="Sonraki Slayt" +SlideShow.PreviousSlide="Önceki Slayt" +SlideShow.HideWhenDone="Slayt gösterisi tamamlandığında gizle" ColorSource="Renk Kaynağı" ColorSource.Color="Renk" diff --git a/plugins/image-source/data/locale/uk-UA.ini b/plugins/image-source/data/locale/uk-UA.ini index 9d38613..6853a94 100644 --- a/plugins/image-source/data/locale/uk-UA.ini +++ b/plugins/image-source/data/locale/uk-UA.ini @@ -1,6 +1,6 @@ ImageInput="Зображення" File="Файл зображення" -UnloadWhenNotShowing="Вивантажи зображення, коли не відображається" +UnloadWhenNotShowing="Вивантажувати зображення, коли воно не виводиться" SlideShow="Слайд-шоу" SlideShow.TransitionSpeed="Тривалість відео-переходу (мілісекунд)" @@ -9,11 +9,25 @@ SlideShow.Files="Файли зображень" SlideShow.CustomSize="Розмір рамки/пропорції" SlideShow.CustomSize.Auto="Автоматично" SlideShow.Randomize="Випадкове відтворення" +SlideShow.Loop="Циклічно відтворювати" SlideShow.Transition="Відео-перехід" SlideShow.Transition.Cut="Cut" SlideShow.Transition.Fade="Fade" SlideShow.Transition.Swipe="Swipe" SlideShow.Transition.Slide="Slide" +SlideShow.PlaybackBehavior="Видимість та відтворення" +SlideShow.PlaybackBehavior.StopRestart="Зупинити, коли не видимий. Грати з початку, коли видимий" +SlideShow.PlaybackBehavior.PauseUnpause="Призупинити, коли не видимий. Грати далі, коли видимий" +SlideShow.PlaybackBehavior.AlwaysPlay="Завжди грати навіть тоді, коли не видимий" +SlideShow.SlideMode="Режим слайдів" +SlideShow.SlideMode.Auto="Автоматично" +SlideShow.SlideMode.Manual="Вручну (використовувати Гарячі клавіші для контролю)" +SlideShow.PlayPause="Відтворення / Пауза" +SlideShow.Restart="Грати з початку" +SlideShow.Stop="Зупинити" +SlideShow.NextSlide="Наступний слайд" +SlideShow.PreviousSlide="Попередній слайд" +SlideShow.HideWhenDone="Не показувати джерело, коли відтворення слайд-шоу завершено" ColorSource="Щільний колір" ColorSource.Color="Колір" diff --git a/plugins/image-source/data/locale/vi-VN.ini b/plugins/image-source/data/locale/vi-VN.ini new file mode 100644 index 0000000..6e3ef27 --- /dev/null +++ b/plugins/image-source/data/locale/vi-VN.ini @@ -0,0 +1,36 @@ +ImageInput="Hình ảnh" +File="Tập tin hình ảnh" +UnloadWhenNotShowing="Tải lên hình ảnh khi không hiển thị" + +SlideShow="Trình chiếu hình ảnh" +SlideShow.TransitionSpeed="Tốc độ chuyển cảnh (mili giây)" +SlideShow.SlideTime="Thời gian giữa các slide (mili giây)" +SlideShow.Files="Tập tin hình ảnh" +SlideShow.CustomSize="Kích thước / Tỷ lệ co dãn" +SlideShow.CustomSize.Auto="Tự động" +SlideShow.Randomize="Phát lại ngẫu nhiên" +SlideShow.Loop="Lặp lại" +SlideShow.Transition="Chuyển cảnh" +SlideShow.Transition.Cut="Cắt" +SlideShow.Transition.Fade="Mờ dần" +SlideShow.Transition.Swipe="Vuốt" +SlideShow.Transition.Slide="Trượt" +SlideShow.PlaybackBehavior="Hành vi hiển thị" +SlideShow.PlaybackBehavior.StopRestart="Dừng khi không hiển thị, khởi động lại khi hiển thị" +SlideShow.PlaybackBehavior.PauseUnpause="Tạm dừng khi không hiển thị, bỏ tạm dừng khi hiển thị" +SlideShow.PlaybackBehavior.AlwaysPlay="Luôn luôn phát ngay cả khi không hiển thị" +SlideShow.SlideMode="Chế độ Slide" +SlideShow.SlideMode.Auto="Tự động" +SlideShow.SlideMode.Manual="Bằng tay (Dùng phím nóng để điều khiển trình chiếu)" +SlideShow.PlayPause="Phát/Tạm dừng" +SlideShow.Restart="Khởi động lại" +SlideShow.Stop="Dừng" +SlideShow.NextSlide="Slide kế" +SlideShow.PreviousSlide="Slide trước" +SlideShow.HideWhenDone="Ẩn khi trình chiếu xong" + +ColorSource="Nguồn màu" +ColorSource.Color="Màu" +ColorSource.Width="Chiều rộng" +ColorSource.Height="Chiều cao" + diff --git a/plugins/image-source/data/locale/zh-CN.ini b/plugins/image-source/data/locale/zh-CN.ini index c3eb2c6..1942e61 100644 --- a/plugins/image-source/data/locale/zh-CN.ini +++ b/plugins/image-source/data/locale/zh-CN.ini @@ -9,11 +9,25 @@ SlideShow.Files="图像文件" SlideShow.CustomSize="边框大小/高宽比" SlideShow.CustomSize.Auto="自动" SlideShow.Randomize="随机播放" +SlideShow.Loop="循环" SlideShow.Transition="转换" SlideShow.Transition.Cut="剪切" SlideShow.Transition.Fade="淡出" SlideShow.Transition.Swipe="滑动" SlideShow.Transition.Slide="幻灯片" +SlideShow.PlaybackBehavior="可见性的行为" +SlideShow.PlaybackBehavior.StopRestart="不可见时停止, 可见时重新开始" +SlideShow.PlaybackBehavior.PauseUnpause="不可见时暂停, 可见时取消暂停" +SlideShow.PlaybackBehavior.AlwaysPlay="即使在不可见时也保持播放" +SlideShow.SlideMode="幻灯片模式" +SlideShow.SlideMode.Auto="自动" +SlideShow.SlideMode.Manual="手动 (使用热键来控制幻灯片)" +SlideShow.PlayPause="播放/暂停" +SlideShow.Restart="重新开始" +SlideShow.Stop="停止" +SlideShow.NextSlide="下一张幻灯片" +SlideShow.PreviousSlide="上一张幻灯片" +SlideShow.HideWhenDone="幻灯片完成时隐藏" ColorSource="色源" ColorSource.Color="色彩" diff --git a/plugins/image-source/data/locale/zh-TW.ini b/plugins/image-source/data/locale/zh-TW.ini index 87d7ff9..f494558 100644 --- a/plugins/image-source/data/locale/zh-TW.ini +++ b/plugins/image-source/data/locale/zh-TW.ini @@ -9,11 +9,25 @@ SlideShow.Files="圖片檔案" SlideShow.CustomSize="邊框大小長寬比" SlideShow.CustomSize.Auto="自動" SlideShow.Randomize="隨機播放" +SlideShow.Loop="循環播放" SlideShow.Transition="變更特效" SlideShow.Transition.Cut="直接變更" SlideShow.Transition.Fade="淡入淡出" SlideShow.Transition.Swipe="滑出" SlideShow.Transition.Slide="推出" +SlideShow.PlaybackBehavior="播放行為" +SlideShow.PlaybackBehavior.StopRestart="不可見時停止,可見時重新開始" +SlideShow.PlaybackBehavior.PauseUnpause="不可見時暫停,可見時取消暫停" +SlideShow.PlaybackBehavior.AlwaysPlay="即使不可見一樣播放" +SlideShow.SlideMode="投影片模式" +SlideShow.SlideMode.Auto="自動" +SlideShow.SlideMode.Manual="手動 (使用熱鍵來控制幻燈片)" +SlideShow.PlayPause="播放/暫停" +SlideShow.Restart="重新開始" +SlideShow.Stop="停止" +SlideShow.NextSlide="下一張投影片" +SlideShow.PreviousSlide="前一張投影片" +SlideShow.HideWhenDone="完成時隱藏" ColorSource="色彩來源" ColorSource.Color="色彩" diff --git a/plugins/image-source/obs-slideshow.c b/plugins/image-source/obs-slideshow.c index cc57d1a..e805d69 100644 --- a/plugins/image-source/obs-slideshow.c +++ b/plugins/image-source/obs-slideshow.c @@ -15,7 +15,16 @@ #define S_SLIDE_TIME "slide_time" #define S_TRANSITION "transition" #define S_RANDOMIZE "randomize" +#define S_LOOP "loop" +#define S_HIDE "hide" #define S_FILES "files" +#define S_BEHAVIOR "playback_behavior" +#define S_BEHAVIOR_STOP_RESTART "stop_restart" +#define S_BEHAVIOR_PAUSE_UNPAUSE "pause_unpause" +#define S_BEHAVIOR_ALWAYS_PLAY "always_play" +#define S_MODE "slide_mode" +#define S_MODE_AUTO "mode_auto" +#define S_MODE_MANUAL "mode_manual" #define TR_CUT "cut" #define TR_FADE "fade" @@ -29,7 +38,16 @@ #define T_SLIDE_TIME T_("SlideTime") #define T_TRANSITION T_("Transition") #define T_RANDOMIZE T_("Randomize") +#define T_LOOP T_("Loop") +#define T_HIDE T_("HideWhenDone") #define T_FILES T_("Files") +#define T_BEHAVIOR T_("PlaybackBehavior") +#define T_BEHAVIOR_STOP_RESTART T_("PlaybackBehavior.StopRestart") +#define T_BEHAVIOR_PAUSE_UNPAUSE T_("PlaybackBehavior.PauseUnpause") +#define T_BEHAVIOR_ALWAYS_PLAY T_("PlaybackBehavior.AlwaysPlay") +#define T_MODE T_("SlideMode") +#define T_MODE_AUTO T_("SlideMode.Auto") +#define T_MODE_MANUAL T_("SlideMode.Manual") #define T_TR_(text) obs_module_text("SlideShow.Transition." text) #define T_TR_CUT T_TR_("Cut") @@ -44,10 +62,25 @@ struct image_file_data { obs_source_t *source; }; +enum behavior { + BEHAVIOR_STOP_RESTART, + BEHAVIOR_PAUSE_UNPAUSE, + BEHAVIOR_ALWAYS_PLAY, +}; + struct slideshow { obs_source_t *source; bool randomize; + bool loop; + bool restart_on_activate; + bool pause_on_deactivate; + bool restart; + bool manual; + bool hide; + bool use_cut; + bool paused; + bool stop; float slide_time; uint32_t tr_speed; const char *tr_name; @@ -61,6 +94,14 @@ struct slideshow { pthread_mutex_t mutex; DARRAY(struct image_file_data) files; + + enum behavior behavior; + + obs_hotkey_id play_pause_hotkey; + obs_hotkey_id restart_hotkey; + obs_hotkey_id stop_hotkey; + obs_hotkey_id next_hotkey; + obs_hotkey_id prev_hotkey; }; static obs_source_t *get_transition(struct slideshow *ss) @@ -179,6 +220,33 @@ static bool valid_extension(const char *ext) astrcmpi(ext, ".gif") == 0; } +static inline bool item_valid(struct slideshow *ss) +{ + return ss->files.num && ss->cur_item < ss->files.num; +} + +static void do_transition(void *data, bool to_null) +{ + struct slideshow *ss = data; + bool valid = item_valid(ss); + + if (valid && ss->use_cut) + obs_transition_set(ss->transition, + ss->files.array[ss->cur_item].source); + + else if (valid && !to_null) + obs_transition_start(ss->transition, + OBS_TRANSITION_MODE_AUTO, + ss->tr_speed, + ss->files.array[ss->cur_item].source); + + else + obs_transition_start(ss->transition, + OBS_TRANSITION_MODE_AUTO, + ss->tr_speed, + NULL); +} + static void ss_update(void *data, obs_data_t *settings) { DARRAY(struct image_file_data) new_files; @@ -193,12 +261,27 @@ static void ss_update(void *data, obs_data_t *settings) uint32_t cx = 0; uint32_t cy = 0; size_t count; + const char *behavior; + const char *mode; /* ------------------------------------- */ /* get settings data */ da_init(new_files); + behavior = obs_data_get_string(settings, S_BEHAVIOR); + + if (astrcmpi(behavior, S_BEHAVIOR_PAUSE_UNPAUSE) == 0) + ss->behavior = BEHAVIOR_PAUSE_UNPAUSE; + else if (astrcmpi(behavior, S_BEHAVIOR_ALWAYS_PLAY) == 0) + ss->behavior = BEHAVIOR_ALWAYS_PLAY; + else /* S_BEHAVIOR_STOP_RESTART */ + ss->behavior = BEHAVIOR_STOP_RESTART; + + mode = obs_data_get_string(settings, S_MODE); + + ss->manual = (astrcmpi(mode, S_MODE_MANUAL) == 0); + tr_name = obs_data_get_string(settings, S_TRANSITION); if (astrcmpi(tr_name, TR_CUT) == 0) tr_name = "cut_transition"; @@ -210,6 +293,8 @@ static void ss_update(void *data, obs_data_t *settings) tr_name = "fade_transition"; ss->randomize = obs_data_get_bool(settings, S_RANDOMIZE); + ss->loop = obs_data_get_bool(settings, S_LOOP); + ss->hide = obs_data_get_bool(settings, S_HIDE); if (!ss->tr_name || strcmp(tr_name, ss->tr_name) != 0) new_tr = obs_source_create_private(tr_name, NULL, NULL); @@ -347,13 +432,139 @@ static void ss_update(void *data, obs_data_t *settings) if (new_tr) obs_source_add_active_child(ss->source, new_tr); if (ss->files.num) - obs_transition_start(ss->transition, OBS_TRANSITION_MODE_AUTO, - ss->tr_speed, - ss->files.array[ss->cur_item].source); + do_transition(ss, false); obs_data_array_release(array); } +static void ss_play_pause(void *data) +{ + struct slideshow *ss = data; + + ss->paused = !ss->paused; + ss->manual = ss->paused; +} + +static void ss_restart(void *data) +{ + struct slideshow *ss = data; + + ss->elapsed = 0.0f; + ss->cur_item = 0; + + obs_transition_set(ss->transition, + ss->files.array[ss->cur_item].source); + + ss->stop = false; + ss->paused = false; +} + +static void ss_stop(void *data) +{ + struct slideshow *ss = data; + + ss->elapsed = 0.0f; + ss->cur_item = 0; + + do_transition(ss, true); + ss->stop = true; + ss->paused = false; +} + +static void ss_next_slide(void *data) +{ + struct slideshow *ss = data; + + if (!ss->files.num) + return; + + if (++ss->cur_item >= ss->files.num) + ss->cur_item = 0; + + do_transition(ss, false); +} + +static void ss_previous_slide(void *data) +{ + struct slideshow *ss = data; + + if (!ss->files.num) + return; + + if (ss->cur_item == 0) + ss->cur_item = ss->files.num - 1; + else + --ss->cur_item; + + do_transition(ss, false); +} + +static void play_pause_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct slideshow *ss = data; + + if (pressed && obs_source_active(ss->source)) + ss_play_pause(ss); +} + +static void restart_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct slideshow *ss = data; + + if (pressed && obs_source_active(ss->source)) + ss_restart(ss); +} + +static void stop_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct slideshow *ss = data; + + if (pressed && obs_source_active(ss->source)) + ss_stop(ss); +} + +static void next_slide_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct slideshow *ss = data; + + if (!ss->manual) + return; + + if (pressed && obs_source_active(ss->source)) + ss_next_slide(ss); +} + +static void previous_slide_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct slideshow *ss = data; + + if (!ss->manual) + return; + + if (pressed && obs_source_active(ss->source)) + ss_previous_slide(ss); +} + static void ss_destroy(void *data) { struct slideshow *ss = data; @@ -367,8 +578,38 @@ static void ss_destroy(void *data) static void *ss_create(obs_data_t *settings, obs_source_t *source) { struct slideshow *ss = bzalloc(sizeof(*ss)); + ss->source = source; + ss->manual = false; + ss->paused = false; + ss->stop = false; + + ss->play_pause_hotkey = obs_hotkey_register_source(source, + "SlideShow.PlayPause", + obs_module_text("SlideShow.PlayPause"), + play_pause_hotkey, ss); + + ss->restart_hotkey = obs_hotkey_register_source(source, + "SlideShow.Restart", + obs_module_text("SlideShow.Restart"), + restart_hotkey, ss); + + ss->stop_hotkey = obs_hotkey_register_source(source, + "SlideShow.Stop", + obs_module_text("SlideShow.Stop"), + stop_hotkey, ss); + + ss->prev_hotkey = obs_hotkey_register_source(source, + "SlideShow.NextSlide", + obs_module_text("SlideShow.NextSlide"), + next_slide_hotkey, ss); + + ss->prev_hotkey = obs_hotkey_register_source(source, + "SlideShow.PreviousSlide", + obs_module_text("SlideShow.PreviousSlide"), + previous_slide_hotkey, ss); + pthread_mutex_init_value(&ss->mutex); if (pthread_mutex_init(&ss->mutex, NULL) != 0) goto error; @@ -403,10 +644,33 @@ static void ss_video_tick(void *data, float seconds) if (!ss->transition || !ss->slide_time) return; + if (ss->restart_on_activate && !ss->randomize && ss->use_cut) { + ss->elapsed = 0.0f; + ss->cur_item = 0; + do_transition(ss, false); + ss->restart_on_activate = false; + ss->use_cut = false; + ss->stop = false; + return; + } + + if (ss->pause_on_deactivate || ss->manual || ss->stop || ss->paused) + return; + ss->elapsed += seconds; + if (ss->elapsed > ss->slide_time) { ss->elapsed -= ss->slide_time; + if (!ss->loop && ss->cur_item == ss->files.num - 1) { + if (ss->hide) + do_transition(ss, true); + else + do_transition(ss, false); + + return; + } + if (ss->randomize) { size_t next = ss->cur_item; if (ss->files.num > 1) { @@ -420,9 +684,7 @@ static void ss_video_tick(void *data, float seconds) } if (ss->files.num) - obs_transition_start(ss->transition, - OBS_TRANSITION_MODE_AUTO, ss->tr_speed, - ss->files.array[ss->cur_item].source); + do_transition(ss, false); } } @@ -506,6 +768,10 @@ static void ss_defaults(obs_data_t *settings) obs_data_set_default_int(settings, S_SLIDE_TIME, 8000); obs_data_set_default_int(settings, S_TR_SPEED, 700); obs_data_set_default_string(settings, S_CUSTOM_SIZE, T_CUSTOM_SIZE_AUTO); + obs_data_set_default_string(settings, S_BEHAVIOR, + S_BEHAVIOR_ALWAYS_PLAY); + obs_data_set_default_string(settings, S_MODE, S_MODE_AUTO); + obs_data_set_default_bool(settings, S_LOOP, true); } static const char *file_filter = @@ -538,6 +804,20 @@ static obs_properties_t *ss_properties(void *data) /* ----------------- */ + p = obs_properties_add_list(ppts, S_BEHAVIOR, T_BEHAVIOR, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, T_BEHAVIOR_ALWAYS_PLAY, + S_BEHAVIOR_ALWAYS_PLAY); + obs_property_list_add_string(p, T_BEHAVIOR_STOP_RESTART, + S_BEHAVIOR_STOP_RESTART); + obs_property_list_add_string(p, T_BEHAVIOR_PAUSE_UNPAUSE, + S_BEHAVIOR_PAUSE_UNPAUSE); + + p = obs_properties_add_list(ppts, S_MODE, T_MODE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, T_MODE_AUTO, S_MODE_AUTO); + obs_property_list_add_string(p, T_MODE_MANUAL, S_MODE_MANUAL); + p = obs_properties_add_list(ppts, S_TRANSITION, T_TRANSITION, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs_property_list_add_string(p, T_TR_CUT, TR_CUT); @@ -549,6 +829,8 @@ static obs_properties_t *ss_properties(void *data) 50, 3600000, 50); obs_properties_add_int(ppts, S_TR_SPEED, T_TR_SPEED, 0, 3600000, 50); + obs_properties_add_bool(ppts, S_LOOP, T_LOOP); + obs_properties_add_bool(ppts, S_HIDE, T_HIDE); obs_properties_add_bool(ppts, S_RANDOMIZE, T_RANDOMIZE); p = obs_properties_add_list(ppts, S_CUSTOM_SIZE, T_CUSTOM_SIZE, @@ -585,6 +867,26 @@ static obs_properties_t *ss_properties(void *data) return ppts; } +static void ss_activate(void *data) +{ + struct slideshow *ss = data; + + if (ss->behavior == BEHAVIOR_STOP_RESTART) { + ss->restart_on_activate = true; + ss->use_cut = true; + } else if (ss->behavior == BEHAVIOR_PAUSE_UNPAUSE) { + ss->pause_on_deactivate = false; + } +} + +static void ss_deactivate(void *data) +{ + struct slideshow *ss = data; + + if (ss->behavior == BEHAVIOR_PAUSE_UNPAUSE) + ss->pause_on_deactivate = true; +} + struct obs_source_info slideshow_info = { .id = "slideshow", .type = OBS_SOURCE_TYPE_INPUT, @@ -595,6 +897,8 @@ struct obs_source_info slideshow_info = { .create = ss_create, .destroy = ss_destroy, .update = ss_update, + .activate = ss_activate, + .deactivate = ss_deactivate, .video_render = ss_video_render, .video_tick = ss_video_tick, .audio_render = ss_audio_render, diff --git a/plugins/linux-alsa/alsa-input.c b/plugins/linux-alsa/alsa-input.c index e9fb200..72ce1d4 100644 --- a/plugins/linux-alsa/alsa-input.c +++ b/plugins/linux-alsa/alsa-input.c @@ -312,8 +312,6 @@ 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; @@ -326,6 +324,8 @@ obs_properties_t * alsa_get_properties(void *unused) ++hint; } + obs_property_list_add_string(devices, "Custom", "__custom__"); + snd_device_name_free_hint(hints); return props; @@ -622,13 +622,13 @@ enum audio_format _alsa_to_obs_audio_format(snd_pcm_format_t format) enum speaker_layout _alsa_channels_to_obs_speakers(unsigned int channels) { switch(channels) { - case 1: return SPEAKERS_MONO; - case 2: return SPEAKERS_STEREO; - case 3: return SPEAKERS_2POINT1; - case 4: return SPEAKERS_SURROUND; - case 5: return SPEAKERS_4POINT1; - case 6: return SPEAKERS_5POINT1; - case 8: return SPEAKERS_7POINT1; + case 1: return SPEAKERS_MONO; + case 2: return SPEAKERS_STEREO; + case 3: return SPEAKERS_2POINT1; + case 4: return SPEAKERS_4POINT0; + case 5: return SPEAKERS_4POINT1; + case 6: return SPEAKERS_5POINT1; + case 8: return SPEAKERS_7POINT1; } return SPEAKERS_UNKNOWN; diff --git a/plugins/linux-alsa/data/locale/el-GR.ini b/plugins/linux-alsa/data/locale/el-GR.ini new file mode 100644 index 0000000..72ef9b4 --- /dev/null +++ b/plugins/linux-alsa/data/locale/el-GR.ini @@ -0,0 +1,3 @@ +AlsaInput="Συσκευή καταγραφής ήχου (ALSA)" +Device="Συσκευή" + diff --git a/plugins/linux-capture/data/locale/da-DK.ini b/plugins/linux-capture/data/locale/da-DK.ini index 58f6b95..b1b3df3 100644 --- a/plugins/linux-capture/data/locale/da-DK.ini +++ b/plugins/linux-capture/data/locale/da-DK.ini @@ -1,16 +1,16 @@ -X11SharedMemoryScreenInput="Indfang skærm (XSHM)" +X11SharedMemoryScreenInput="Skærmoptagelse (XSHM)" Screen="Skærm" -CaptureCursor="Indfang markøren" -AdvancedSettings="Avancerede indstillinger" +CaptureCursor="Optag markør" +AdvancedSettings="Avancerede Indstillinger" XServer="X Server" -XCCapture="Indfang vindue (Xcomposite)" +XCCapture="Vinduesoptagelse (Xcomposite)" Window="Vindue" CropTop="Beskær top (pixels)" CropLeft="Beskær venstre (pixels)" CropRight="Beskær højre (pixels)" CropBottom="Beskær bund (pixels)" -SwapRedBlue="Swap rød og blå" -LockX="Lås X server ved optagelse" -IncludeXBorder="Inkluder X kant" -ExcludeAlpha="Brug alpha-mindre tekstur format (Mesa løsning)" +SwapRedBlue="Ombyt rød og blå" +LockX="Lås X server under optagelse" +IncludeXBorder="Inkluder X-kant" +ExcludeAlpha="Benyt alpha-fri teksturformat (Mesa løsning)" diff --git a/plugins/linux-capture/data/locale/el-GR.ini b/plugins/linux-capture/data/locale/el-GR.ini index 73304db..756bccb 100644 --- a/plugins/linux-capture/data/locale/el-GR.ini +++ b/plugins/linux-capture/data/locale/el-GR.ini @@ -12,4 +12,5 @@ CropBottom="Περικοπή Κάτω (pixels)" SwapRedBlue="Ανταλλαγή κόκκινου και μπλέ" LockX="Κλείδωμα X server κατά την σύλληψη" IncludeXBorder="Περίλαβε το περίγραμμα του X" +ExcludeAlpha="Χρησιμοποίηση της μορφής υφής χωρίς άλφα (Λύση για Mesa)" diff --git a/plugins/linux-capture/data/locale/ko-KR.ini b/plugins/linux-capture/data/locale/ko-KR.ini index 2af4dd1..9b05dd7 100644 --- a/plugins/linux-capture/data/locale/ko-KR.ini +++ b/plugins/linux-capture/data/locale/ko-KR.ini @@ -3,7 +3,7 @@ Screen="화면" CaptureCursor="커서 캡쳐" AdvancedSettings="고급 설정" XServer="X 서버" -XCCapture="윈도우 캡쳐(Xcomposite)" +XCCapture="윈도우 캡쳐 (Xcomposite)" Window="윈도우" CropTop="위쪽 자르기 (픽셀)" CropLeft="왼쪽 자르기 (픽셀)" diff --git a/plugins/linux-capture/data/locale/sk-SK.ini b/plugins/linux-capture/data/locale/sk-SK.ini index 5cc60c7..7016c66 100644 --- a/plugins/linux-capture/data/locale/sk-SK.ini +++ b/plugins/linux-capture/data/locale/sk-SK.ini @@ -12,4 +12,5 @@ CropBottom="Orezanie dole (pixely)" SwapRedBlue="Vymeniť červenú a modrú" LockX="Zamknúť X server počas snímania" IncludeXBorder="Zahŕňajú X okraj" +ExcludeAlpha="Použiť formát textúry bez alpha kanálu (riešenie pre Mesa problém)" diff --git a/plugins/linux-capture/xcompcap-helper.cpp b/plugins/linux-capture/xcompcap-helper.cpp index cf4f0f6..55657f8 100644 --- a/plugins/linux-capture/xcompcap-helper.cpp +++ b/plugins/linux-capture/xcompcap-helper.cpp @@ -267,6 +267,9 @@ namespace XCompcap if (ev.type == Expose) changedWindows.insert(ev.xexpose.window); + if (ev.type == VisibilityNotify) + changedWindows.insert(ev.xvisibility.window); + if (ev.type == DestroyNotify) changedWindows.insert(ev.xdestroywindow.event); } diff --git a/plugins/linux-capture/xcompcap-main.cpp b/plugins/linux-capture/xcompcap-main.cpp index 7a09ecb..0306b68 100644 --- a/plugins/linux-capture/xcompcap-main.cpp +++ b/plugins/linux-capture/xcompcap-main.cpp @@ -318,7 +318,10 @@ void XCompcapMain::updateSettings(obs_data_t *settings) } if (p->win) - XSelectInput(xdisp, p->win, StructureNotifyMask | ExposureMask); + XSelectInput(xdisp, p->win, + StructureNotifyMask + | ExposureMask + | VisibilityChangeMask); XSync(xdisp, 0); XWindowAttributes attr; @@ -458,6 +461,18 @@ void XCompcapMain::updateSettings(obs_data_t *settings) glXBindTexImageEXT(xdisp, p->glxpixmap, GLX_FRONT_LEFT_EXT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + if (!p->windowName.empty()) { + blog(LOG_INFO, "[window-capture: '%s'] update settings:\n" + "\ttitle: %s\n" + "\tclass: %s", + obs_source_get_name(p->source), + XCompcap::getWindowName(p->win).c_str(), + XCompcap::getWindowClass(p->win).c_str()); + blog(LOG_DEBUG, "\n" + "\tid: %s", + std::to_string((long long)p->win).c_str()); + } } void XCompcapMain::tick(float seconds) diff --git a/plugins/linux-jack/data/locale/vi-VN.ini b/plugins/linux-jack/data/locale/vi-VN.ini index 11d5f2a..3700af7 100644 --- a/plugins/linux-jack/data/locale/vi-VN.ini +++ b/plugins/linux-jack/data/locale/vi-VN.ini @@ -1,3 +1,4 @@ StartJACKServer="Bắt đầu JACK server" Channels="Số lượng các kênh" +JACKInput="Đầu vào JACK Client" diff --git a/plugins/linux-jack/jack-wrapper.c b/plugins/linux-jack/jack-wrapper.c index 25b125c..63309cc 100644 --- a/plugins/linux-jack/jack-wrapper.c +++ b/plugins/linux-jack/jack-wrapper.c @@ -40,7 +40,6 @@ static enum speaker_layout jack_channels_to_obs_speakers(uint_fast32_t channels) case 1: return SPEAKERS_MONO; case 2: return SPEAKERS_STEREO; case 3: return SPEAKERS_2POINT1; - case 4: return SPEAKERS_SURROUND; case 5: return SPEAKERS_4POINT1; case 6: return SPEAKERS_5POINT1; /* What should we do with 7 channels? */ diff --git a/plugins/linux-pulseaudio/data/locale/ko-KR.ini b/plugins/linux-pulseaudio/data/locale/ko-KR.ini index 4fb32ce..cbfd8d7 100644 --- a/plugins/linux-pulseaudio/data/locale/ko-KR.ini +++ b/plugins/linux-pulseaudio/data/locale/ko-KR.ini @@ -1,4 +1,4 @@ -PulseInput="오디오 입력 캡쳐(PulseAudio)" +PulseInput="오디오 입력 캡쳐 (PulseAudio)" PulseOutput="오디오 출력 캡쳐(PulseAudio)" Device="장치" diff --git a/plugins/linux-pulseaudio/data/locale/vi-VN.ini b/plugins/linux-pulseaudio/data/locale/vi-VN.ini new file mode 100644 index 0000000..c33fd59 --- /dev/null +++ b/plugins/linux-pulseaudio/data/locale/vi-VN.ini @@ -0,0 +1,4 @@ +PulseInput="Thu âm đầu vào (PulseAudio)" +PulseOutput="Thu âm đầu ra (PulseAudio)" +Device="Thiết bị" + diff --git a/plugins/linux-pulseaudio/pulse-input.c b/plugins/linux-pulseaudio/pulse-input.c index 15f0ed2..698b588 100644 --- a/plugins/linux-pulseaudio/pulse-input.c +++ b/plugins/linux-pulseaudio/pulse-input.c @@ -33,6 +33,7 @@ struct pulse_data { /* user settings */ char *device; + bool input; /* server info */ enum speaker_layout speakers; @@ -80,18 +81,73 @@ static enum speaker_layout pulse_channels_to_obs_speakers( uint_fast32_t channels) { switch(channels) { - case 1: return SPEAKERS_MONO; - case 2: return SPEAKERS_STEREO; - case 3: return SPEAKERS_2POINT1; - case 4: return SPEAKERS_SURROUND; - case 5: return SPEAKERS_4POINT1; - case 6: return SPEAKERS_5POINT1; - case 8: return SPEAKERS_7POINT1; + case 1: return SPEAKERS_MONO; + case 2: return SPEAKERS_STEREO; + case 3: return SPEAKERS_2POINT1; + case 4: return SPEAKERS_4POINT0; + case 5: return SPEAKERS_4POINT1; + case 6: return SPEAKERS_5POINT1; + case 8: return SPEAKERS_7POINT1; } return SPEAKERS_UNKNOWN; } +static pa_channel_map pulse_channel_map(enum speaker_layout layout) +{ + pa_channel_map ret; + + ret.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + ret.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + ret.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + ret.map[3] = PA_CHANNEL_POSITION_LFE; + ret.map[4] = PA_CHANNEL_POSITION_REAR_LEFT; + ret.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; + ret.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + ret.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + + switch (layout) { + case SPEAKERS_MONO: + ret.channels = 1; + ret.map[0] = PA_CHANNEL_POSITION_MONO; + break; + + case SPEAKERS_STEREO: + ret.channels = 2; + break; + + case SPEAKERS_2POINT1: + ret.channels = 3; + ret.map[2] = PA_CHANNEL_POSITION_LFE; + break; + + case SPEAKERS_4POINT0: + ret.channels = 4; + ret.map[3] = PA_CHANNEL_POSITION_REAR_CENTER; + break; + + case SPEAKERS_4POINT1: + ret.channels = 5; + ret.map[4] = PA_CHANNEL_POSITION_REAR_CENTER; + break; + + case SPEAKERS_5POINT1: + ret.channels = 6; + break; + + case SPEAKERS_7POINT1: + ret.channels = 8; + break; + + case SPEAKERS_UNKNOWN: + default: + ret.channels = 0; + break; + } + + return ret; +} + static inline uint64_t samples_to_ns(size_t frames, uint_fast32_t rate) { return frames * NSEC_PER_SEC / rate; @@ -164,11 +220,30 @@ static void pulse_server_info(pa_context *c, const pa_server_info *i, void *userdata) { UNUSED_PARAMETER(c); - UNUSED_PARAMETER(userdata); + PULSE_DATA(userdata); blog(LOG_INFO, "Server name: '%s %s'", i->server_name, i->server_version); + if (data->device && strcmp("default", data->device) == 0) { + if (data->input) { + bfree(data->device); + data->device = bstrdup(i->default_source_name); + + blog(LOG_DEBUG, "Default input device: '%s'", data->device); + } else { + char *monitor = bzalloc(strlen(i->default_sink_name) + 9); + strcat(monitor, i->default_sink_name); + strcat(monitor, ".monitor"); + + bfree(data->device); + data->device = bstrdup(monitor); + + blog(LOG_DEBUG, "Default output device: '%s'", data->device); + bfree(monitor); + } + } + pulse_signal(0); } @@ -200,7 +275,7 @@ static void pulse_source_info(pa_context *c, const pa_source_info *i, int eol, pa_sample_format_t format = i->sample_spec.format; if (pulse_to_obs_audio_format(format) == AUDIO_FORMAT_UNKNOWN) { - format = PA_SAMPLE_S16LE; + format = PA_SAMPLE_FLOAT32LE; blog(LOG_INFO, "Sample format %s not supported by OBS," "using %s instead for recording", @@ -266,8 +341,10 @@ static int_fast32_t pulse_start_recording(struct pulse_data *data) data->speakers = pulse_channels_to_obs_speakers(spec.channels); data->bytes_per_frame = pa_frame_size(&spec); + pa_channel_map channel_map = pulse_channel_map(data->speakers); + data->stream = pulse_stream_new(obs_source_get_name(data->source), - &spec, NULL); + &spec, &channel_map); if (!data->stream) { blog(LOG_ERROR, "Unable to create stream"); return -1; @@ -343,15 +420,15 @@ skip: /** * output info callback */ -static void pulse_output_info(pa_context *c, const pa_source_info *i, int eol, +static void pulse_output_info(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { UNUSED_PARAMETER(c); - if (eol != 0 || i->monitor_of_sink == PA_INVALID_INDEX) + if (eol != 0 || i->monitor_source == PA_INVALID_INDEX) goto skip; obs_property_list_add_string((obs_property_t*) userdata, - i->description, i->name); + i->description, i->monitor_source_name); skip: pulse_signal(0); @@ -368,10 +445,18 @@ static obs_properties_t *pulse_properties(bool input) OBS_COMBO_FORMAT_STRING); pulse_init(); - pa_source_info_cb_t cb = (input) ? pulse_input_info : pulse_output_info; - pulse_get_source_info_list(cb, (void *) devices); + if (input) + pulse_get_source_info_list(pulse_input_info, (void *) devices); + else + pulse_get_sink_info_list(pulse_output_info, (void *) devices); pulse_unref(); + size_t count = obs_property_list_item_count(devices); + + if (count > 0) + obs_property_list_insert_string(devices, 0, + obs_module_text("Default"), "default"); + return props; } @@ -389,61 +474,12 @@ static obs_properties_t *pulse_output_properties(void *unused) return pulse_properties(false); } -/** - * Server info callback - */ -static void pulse_input_device(pa_context *c, const pa_server_info *i, - void *userdata) -{ - UNUSED_PARAMETER(c); - obs_data_t *settings = (obs_data_t*) userdata; - - obs_data_set_default_string(settings, "device_id", - i->default_source_name); - blog(LOG_DEBUG, "Default input device: '%s'", i->default_source_name); - - pulse_signal(0); -} - -static void pulse_output_device(pa_context *c, const pa_server_info *i, - void *userdata) -{ - UNUSED_PARAMETER(c); - obs_data_t *settings = (obs_data_t*) userdata; - - char *monitor = bzalloc(strlen(i->default_sink_name) + 9); - strcat(monitor, i->default_sink_name); - strcat(monitor, ".monitor"); - - obs_data_set_default_string(settings, "device_id", monitor); - blog(LOG_DEBUG, "Default output device: '%s'", monitor); - bfree(monitor); - - pulse_signal(0); -} - /** * Get plugin defaults */ -static void pulse_defaults(obs_data_t *settings, bool input) +static void pulse_defaults(obs_data_t *settings) { - pulse_init(); - - pa_server_info_cb_t cb = (input) - ? pulse_input_device : pulse_output_device; - pulse_get_server_info(cb, (void *) settings); - - pulse_unref(); -} - -static void pulse_input_defaults(obs_data_t *settings) -{ - return pulse_defaults(settings, true); -} - -static void pulse_output_defaults(obs_data_t *settings) -{ - return pulse_defaults(settings, false); + obs_data_set_default_string(settings, "device_id", "default"); } /** @@ -508,10 +544,11 @@ static void pulse_update(void *vptr, obs_data_t *settings) /** * Create the plugin object */ -static void *pulse_create(obs_data_t *settings, obs_source_t *source) +static void *pulse_create(obs_data_t *settings, obs_source_t *source, bool input) { struct pulse_data *data = bzalloc(sizeof(struct pulse_data)); + data->input = input; data->source = source; pulse_init(); @@ -520,16 +557,26 @@ static void *pulse_create(obs_data_t *settings, obs_source_t *source) return data; } +static void *pulse_input_create(obs_data_t *settings, obs_source_t *source) +{ + return pulse_create(settings, source, true); +} + +static void *pulse_output_create(obs_data_t *settings, obs_source_t *source) +{ + return pulse_create(settings, source, false); +} + struct obs_source_info pulse_input_capture = { .id = "pulse_input_capture", .type = OBS_SOURCE_TYPE_INPUT, .output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE, .get_name = pulse_input_getname, - .create = pulse_create, + .create = pulse_input_create, .destroy = pulse_destroy, .update = pulse_update, - .get_defaults = pulse_input_defaults, + .get_defaults = pulse_defaults, .get_properties = pulse_input_properties }; @@ -540,9 +587,9 @@ struct obs_source_info pulse_output_capture = { OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_DO_NOT_SELF_MONITOR, .get_name = pulse_output_getname, - .create = pulse_create, + .create = pulse_output_create, .destroy = pulse_destroy, .update = pulse_update, - .get_defaults = pulse_output_defaults, + .get_defaults = pulse_defaults, .get_properties = pulse_output_properties }; diff --git a/plugins/linux-pulseaudio/pulse-wrapper.c b/plugins/linux-pulseaudio/pulse-wrapper.c index bbda717..77ae41a 100644 --- a/plugins/linux-pulseaudio/pulse-wrapper.c +++ b/plugins/linux-pulseaudio/pulse-wrapper.c @@ -185,6 +185,28 @@ int_fast32_t pulse_get_source_info_list(pa_source_info_cb_t cb, void* userdata) return 0; } +int_fast32_t pulse_get_sink_info_list(pa_sink_info_cb_t cb, void *userdata) +{ + if (pulse_context_ready() < 0) + return -1; + + pulse_lock(); + + pa_operation *op = pa_context_get_sink_info_list( + pulse_context, cb, userdata); + if (!op) { + pulse_unlock(); + return -1; + } + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) + pulse_wait(); + pa_operation_unref(op); + + pulse_unlock(); + + return 0; +} + int_fast32_t pulse_get_source_info(pa_source_info_cb_t cb, const char *name, void *userdata) { diff --git a/plugins/linux-pulseaudio/pulse-wrapper.h b/plugins/linux-pulseaudio/pulse-wrapper.h index 9c2a880..557b6fe 100644 --- a/plugins/linux-pulseaudio/pulse-wrapper.h +++ b/plugins/linux-pulseaudio/pulse-wrapper.h @@ -93,6 +93,20 @@ void pulse_accept(); */ int_fast32_t pulse_get_source_info_list(pa_source_info_cb_t cb, void *userdata); +/** + * Request sink information + * + * The function will block until the operation was executed and the mainloop + * called the provided callback function. + * + * @return negative on error + * + * @note The function will block until the server context is ready. + * + * @warning call without active locks + */ +int_fast32_t pulse_get_sink_info_list(pa_sink_info_cb_t cb, void *userdata); + /** * Request source information from a specific source * diff --git a/plugins/linux-v4l2/data/locale/ko-KR.ini b/plugins/linux-v4l2/data/locale/ko-KR.ini index 1302bfa..7554923 100644 --- a/plugins/linux-v4l2/data/locale/ko-KR.ini +++ b/plugins/linux-v4l2/data/locale/ko-KR.ini @@ -1,4 +1,4 @@ -V4L2Input="비디오 캡쳐 장치(V4L2)" +V4L2Input="비디오 캡쳐 장치 (V4L2)" Device="장치" Input="입력" VideoFormat="비디오 형식" diff --git a/plugins/linux-v4l2/data/locale/tr-TR.ini b/plugins/linux-v4l2/data/locale/tr-TR.ini index 7c578ec..544d6a1 100644 --- a/plugins/linux-v4l2/data/locale/tr-TR.ini +++ b/plugins/linux-v4l2/data/locale/tr-TR.ini @@ -7,5 +7,5 @@ DVTiming="Dijital Video Zamanlaması" Resolution="Çözünürlük" FrameRate="Kare Hızı" LeaveUnchanged="Değişmeden Bırak" -UseBuffering="Arabelleği Kullan" +UseBuffering="Arabelleğe Almayı Kullan" diff --git a/plugins/linux-v4l2/data/locale/vi-VN.ini b/plugins/linux-v4l2/data/locale/vi-VN.ini new file mode 100644 index 0000000..d13abee --- /dev/null +++ b/plugins/linux-v4l2/data/locale/vi-VN.ini @@ -0,0 +1,6 @@ +Device="Thiết bị" +VideoStandard="Video Tiêu chuẩn" +Resolution="Độ phân giải" +FrameRate="Tốc độ khung" +LeaveUnchanged="Giữ nguyên" + diff --git a/plugins/linux-v4l2/v4l2-helpers.h b/plugins/linux-v4l2/v4l2-helpers.h index 84bb95c..e25d0bb 100644 --- a/plugins/linux-v4l2/v4l2-helpers.h +++ b/plugins/linux-v4l2/v4l2-helpers.h @@ -101,6 +101,13 @@ static const int v4l2_framesizes[] = 1600<<16 | 900, 1920<<16 | 1080, 1920<<16 | 1200, + 2560<<16 | 1440, + 3840<<16 | 2160, + + /* 21:9 */ + 2560<<16 | 1080, + 3440<<16 | 1440, + 5120<<16 | 2160, /* tv */ 432<<16 | 520, diff --git a/plugins/mac-avcapture/data/locale/el-GR.ini b/plugins/mac-avcapture/data/locale/el-GR.ini index 6fabf55..51060b8 100644 --- a/plugins/mac-avcapture/data/locale/el-GR.ini +++ b/plugins/mac-avcapture/data/locale/el-GR.ini @@ -3,4 +3,12 @@ Device="Συσκευή" UsePreset="Χρησιμοποιήσε το Προκαθορισμένο" Preset="Προκαθορισμένο" Buffering="Χρήση ενδιάμεσης μνήμης" +FrameRate="Ρυθμός καρέ" +InputFormat="Μορφή Εισόδου" +ColorSpace="Χρωματικός χώρος" +VideoRange="Εύρος Βίντεο" +VideoRange.Partial="Μερικό" +VideoRange.Full="Πλήρες" +Auto="Αυτόματο" +Unknown="Άγνωστο (%1)" diff --git a/plugins/mac-avcapture/data/locale/eu-ES.ini b/plugins/mac-avcapture/data/locale/eu-ES.ini index 0ae77a4..6b9ff97 100644 --- a/plugins/mac-avcapture/data/locale/eu-ES.ini +++ b/plugins/mac-avcapture/data/locale/eu-ES.ini @@ -1,7 +1,7 @@ AVCapture="Bideoa kapturatzeko gailua" Device="Gailua" UsePreset="Erabili aurrezarpena" -Preset="Aurrezarpena" +Preset="Aurre-ezarpena" Buffering="Erabili bufferreratzea" FrameRate="Fotograma emaria" InputFormat="Sarrera formatua" diff --git a/plugins/mac-avcapture/data/locale/sk-SK.ini b/plugins/mac-avcapture/data/locale/sk-SK.ini index 67b8d7a..462f65c 100644 --- a/plugins/mac-avcapture/data/locale/sk-SK.ini +++ b/plugins/mac-avcapture/data/locale/sk-SK.ini @@ -5,6 +5,9 @@ Preset="Predvoľba" Buffering="Použiť vyrovnávaciu pamäť" FrameRate="Snímkovacia frekvencia" InputFormat="Vstupný formát" +ColorSpace="Farebný priestor" +VideoRange="Video rozsah" +VideoRange.Partial="Čiastočný" VideoRange.Full="Úplný" Auto="Automaticky" Unknown="Neznámy ($1)" diff --git a/plugins/mac-avcapture/data/locale/vi-VN.ini b/plugins/mac-avcapture/data/locale/vi-VN.ini new file mode 100644 index 0000000..48089de --- /dev/null +++ b/plugins/mac-avcapture/data/locale/vi-VN.ini @@ -0,0 +1,4 @@ +Device="Thiết bị" +Auto="Tự động" +Unknown="Không xác định ($1)" + diff --git a/plugins/mac-capture/data/locale/eu-ES.ini b/plugins/mac-capture/data/locale/eu-ES.ini index 98d163e..07f3938 100644 --- a/plugins/mac-capture/data/locale/eu-ES.ini +++ b/plugins/mac-capture/data/locale/eu-ES.ini @@ -1,8 +1,8 @@ CoreAudio.InputCapture="Audio sarreraren kaptura" CoreAudio.OutputCapture="Audio irteeraren kaptura" CoreAudio.Device="Gailua" -CoreAudio.Device.Default="Berezkoa" -DisplayCapture="Erakusleiho Harpena" +CoreAudio.Device.Default="Lehenetsia" +DisplayCapture="Pantaila-kaptura" DisplayCapture.Display="Pantaila" DisplayCapture.ShowCursor="Erakutsi kurtsorea" WindowCapture="Leiho-kaptura" diff --git a/plugins/mac-capture/data/locale/vi-VN.ini b/plugins/mac-capture/data/locale/vi-VN.ini new file mode 100644 index 0000000..93664a8 --- /dev/null +++ b/plugins/mac-capture/data/locale/vi-VN.ini @@ -0,0 +1,18 @@ +CoreAudio.InputCapture="Thiết bị âm thanh đầu vào" +CoreAudio.OutputCapture="Thiết bị âm thanh đầu ra" +CoreAudio.Device="Thiết bị" +CoreAudio.Device.Default="Mặc định" +DisplayCapture="Quay màn hình" +DisplayCapture.Display="Hiển thị" +DisplayCapture.ShowCursor="Hiển thị con trỏ" +WindowCapture="Quay cửa sổ" +WindowUtils.Window="Cửa sổ" +WindowUtils.ShowEmptyNames="Hiện thị cửa sổ mà không có tên" +CropMode="Cắt" +CropMode.None="Không" +CropMode.Manual="Thủ công" +Crop.origin.x="Cắt trái" +Crop.origin.y="Cắt trên" +Crop.size.width="Cắt phải" +Crop.size.height="Cắt dưới" + diff --git a/plugins/mac-capture/mac-audio.c b/plugins/mac-capture/mac-audio.c index 9819001..3684808 100644 --- a/plugins/mac-capture/mac-audio.c +++ b/plugins/mac-capture/mac-audio.c @@ -187,9 +187,15 @@ static inline enum audio_format convert_ca_format(UInt32 format_flags, static inline enum speaker_layout convert_ca_speaker_layout(UInt32 channels) { - /* directly map channel count to enum values */ - if (channels >= 1 && channels <= 8 && channels != 7) - return (enum speaker_layout)channels; + switch (channels) { + case 1: return SPEAKERS_MONO; + case 2: return SPEAKERS_STEREO; + case 3: return SPEAKERS_2POINT1; + case 4: return SPEAKERS_4POINT0; + case 5: return SPEAKERS_4POINT1; + case 6: return SPEAKERS_5POINT1; + case 8: return SPEAKERS_7POINT1; + } return SPEAKERS_UNKNOWN; } @@ -199,6 +205,14 @@ static bool coreaudio_init_format(struct coreaudio_data *ca) AudioStreamBasicDescription desc; OSStatus stat; UInt32 size = sizeof(desc); + struct obs_audio_info aoi; + int channels; + + if (!obs_get_audio_info(&aoi)) { + blog(LOG_WARNING, "No active audio"); + return false; + } + channels = get_audio_channels(aoi.speakers); stat = get_property(ca->unit, kAudioUnitProperty_StreamFormat, SCOPE_INPUT, BUS_INPUT, &desc, &size); @@ -207,13 +221,13 @@ static bool coreaudio_init_format(struct coreaudio_data *ca) /* Certain types of devices have no limit on channel count, and * there's no way to know the actual number of channels it's using, - * so if we encounter this situation just force to stereo */ - if (desc.mChannelsPerFrame > 8) { - desc.mChannelsPerFrame = 2; - desc.mBytesPerFrame = 2 * desc.mBitsPerChannel / 8; - desc.mBytesPerPacket = - desc.mFramesPerPacket * desc.mBytesPerFrame; - } + * so if we encounter this situation just force to what is defined in output */ + if (desc.mChannelsPerFrame > 8) { + desc.mChannelsPerFrame = channels; + desc.mBytesPerFrame = channels * desc.mBitsPerChannel / 8; + desc.mBytesPerPacket = + desc.mFramesPerPacket * desc.mBytesPerFrame; + } stat = set_property(ca->unit, kAudioUnitProperty_StreamFormat, SCOPE_OUTPUT, BUS_INPUT, &desc, size); diff --git a/plugins/mac-capture/mac-window-capture.m b/plugins/mac-capture/mac-window-capture.m index 3d1bb9c..48f2da7 100644 --- a/plugins/mac-capture/mac-window-capture.m +++ b/plugins/mac-capture/mac-window-capture.m @@ -177,6 +177,15 @@ static inline void window_capture_update_internal(struct window_capture *wc, kCGWindowImageDefault : kCGWindowImageBoundsIgnoreFraming; update_window(&wc->window, settings); + + if (wc->window.window_name.length) { + blog(LOG_INFO, "[window-capture: '%s'] update settings:\n" + "\twindow: %s\n" + "\towner: %s", + obs_source_get_name(wc->source), + [wc->window.window_name UTF8String], + [wc->window.owner_name UTF8String]); + } } static void window_capture_update(void *data, obs_data_t *settings) diff --git a/plugins/mac-syphon/data/locale/sk-SK.ini b/plugins/mac-syphon/data/locale/sk-SK.ini index b714aa8..c57f1b4 100644 --- a/plugins/mac-syphon/data/locale/sk-SK.ini +++ b/plugins/mac-syphon/data/locale/sk-SK.ini @@ -1,6 +1,7 @@ Syphon="Zachytávanie hry (Syphon)" Source="Zdroj" LaunchSyphonInject="Zapnúť SyphonInject" +Inject="Zaviesť" Application="Aplikácia" SyphonLicense="Syphon licenia" Crop="Orezať" diff --git a/plugins/mac-syphon/data/locale/vi-VN.ini b/plugins/mac-syphon/data/locale/vi-VN.ini new file mode 100644 index 0000000..d272325 --- /dev/null +++ b/plugins/mac-syphon/data/locale/vi-VN.ini @@ -0,0 +1,3 @@ +Source="Nguồn" +Application="Ứng dụng" + diff --git a/plugins/mac-vth264/data/locale/el-GR.ini b/plugins/mac-vth264/data/locale/el-GR.ini index 66152b8..c9fb4db 100644 --- a/plugins/mac-vth264/data/locale/el-GR.ini +++ b/plugins/mac-vth264/data/locale/el-GR.ini @@ -1,6 +1,14 @@ +VTH264EncHW="Apple VT H264 κωδικοποιητής υλικού" +VTH264EncSW="Apple VT H264 κωδικοποιητής λογισμικού" +VTEncoder="Κωδικοποιητής εργαλειοθήκης Βίντεο" Bitrate="Ρυθμός μετάδοσης bit" +UseMaxBitrate="Όριο bitrate" +MaxBitrate="Μέγιστο bitrate" +MaxBitrateWindow="Μέγιστο παράθυρο bitrate (δευτερόλεπτα)" +KeyframeIntervalSec="Συχνότητα Καρέ-Κλειδιού (δευτερόλεπτα, 0=αυτόματο)" Profile="Προφίλ" None="(Κανένα)" DefaultEncoder="(Προεπιλεγμένος κωδικοποιητής)" +UseBFrames="Χρήση Β-πλαισίων" diff --git a/plugins/mac-vth264/data/locale/sk-SK.ini b/plugins/mac-vth264/data/locale/sk-SK.ini index ecba3a1..16c50cf 100644 --- a/plugins/mac-vth264/data/locale/sk-SK.ini +++ b/plugins/mac-vth264/data/locale/sk-SK.ini @@ -1,7 +1,14 @@ +VTH264EncHW="Apple VT H264 Hardware Enkodér" +VTH264EncSW="Apple VT H264 Software Enkodér" +VTEncoder="VideoToolbox Enkodér" Bitrate="Dátový tok" +UseMaxBitrate="Obmedziť dátový tok" +MaxBitrate="Maximálny dátový tok" +MaxBitrateWindow="Maximálny dátový tok okna (v sekundách)" +KeyframeIntervalSec="Interval kľúčových snímok (sekúnd, 0 = automaticky)" Profile="Profil" None="(Žiadny)" -DefaultEncoder="(Predvolený enkoder)" +DefaultEncoder="(Predvolený enkodér)" UseBFrames="Použiť B-Frames" diff --git a/plugins/obs-ffmpeg/CMakeLists.txt b/plugins/obs-ffmpeg/CMakeLists.txt index 72c131f..4c04ab1 100644 --- a/plugins/obs-ffmpeg/CMakeLists.txt +++ b/plugins/obs-ffmpeg/CMakeLists.txt @@ -15,7 +15,7 @@ set(obs-ffmpeg_HEADERS closest-pixel-format.h) set(obs-ffmpeg_SOURCES obs-ffmpeg.c - obs-ffmpeg-aac.c + obs-ffmpeg-audio-encoders.c obs-ffmpeg-nvenc.c obs-ffmpeg-output.c obs-ffmpeg-mux.c diff --git a/plugins/obs-ffmpeg/data/locale/ar-SA.ini b/plugins/obs-ffmpeg/data/locale/ar-SA.ini index 4d1afe0..ec69c93 100644 --- a/plugins/obs-ffmpeg/data/locale/ar-SA.ini +++ b/plugins/obs-ffmpeg/data/locale/ar-SA.ini @@ -25,3 +25,4 @@ MediaFileFilter.AudioFiles="ملفات الصوت" MediaFileFilter.AllFiles="‮كل الملفات" + diff --git a/plugins/obs-ffmpeg/data/locale/bg-BG.ini b/plugins/obs-ffmpeg/data/locale/bg-BG.ini index 2ffda0d..f4957f4 100644 --- a/plugins/obs-ffmpeg/data/locale/bg-BG.ini +++ b/plugins/obs-ffmpeg/data/locale/bg-BG.ini @@ -6,3 +6,4 @@ Bitrate="Битрейт" + diff --git a/plugins/obs-ffmpeg/data/locale/bn-BD.ini b/plugins/obs-ffmpeg/data/locale/bn-BD.ini index 562c2ee..5fc90b2 100644 --- a/plugins/obs-ffmpeg/data/locale/bn-BD.ini +++ b/plugins/obs-ffmpeg/data/locale/bn-BD.ini @@ -16,3 +16,4 @@ FFmpegSource="মিডিয়া উৎস" + diff --git a/plugins/obs-ffmpeg/data/locale/ca-ES.ini b/plugins/obs-ffmpeg/data/locale/ca-ES.ini index 5dd84f3..a8fed0d 100644 --- a/plugins/obs-ffmpeg/data/locale/ca-ES.ini +++ b/plugins/obs-ffmpeg/data/locale/ca-ES.ini @@ -1,5 +1,6 @@ FFmpegOutput="Sortida FFmpeg" FFmpegAAC="Codificador FFmpeg AAC predeterminat" +FFmpegOpus="Codificador Opus FFmpeg" Bitrate="Taxa de bits" Preset="Valors predefinits" RateControl="Control de freqüència" @@ -23,14 +24,19 @@ LocalFile="Fitxer local" Looping="Bucle" Input="Entrada" InputFormat="Format d'entrada" +BufferingMB="Memòria intermèdia de xarxa (MB)" HardwareDecode="Usa la descodificació per maquinari si és disponible" ClearOnMediaEnd="Amaga l'origen en acabar la reproducció" Advanced="Avançat" RestartWhenActivated="Reinicia la reproducció quan la font estigui activa" +CloseFileWhenInactive="Tanca el fitxer quan estigui inactiu" +CloseFileWhenInactive.ToolTip="Tanca el fitxer quan l'origen no està sent mostrada en el directe o en l'enregistrament.\nAixò permet que el fitxer pugui ser canviat quan l'origen no està actiu,\nperò pot haver-hi una espera en l'inici quan es reactivi l'origen." ColorRange="Gamma de color YUV" ColorRange.Auto="Automàtic" ColorRange.Partial="Parcial" ColorRange.Full="Màxim" +RestartMedia="Reinicia els mitjans" +Seekable="Cercable" MediaFileFilter.AllMediaFiles="Tots els arxius multimèdia" MediaFileFilter.VideoFiles="Arxius de vídeo" @@ -40,3 +46,6 @@ MediaFileFilter.AllFiles="Tots els fitxers" ReplayBuffer="Memòria intermèdia de reproducció" ReplayBuffer.Save="Desa la repetició" +HelperProcessFailed="No s'ha pogut iniciar el procés d'assistència de gravació. Comproveu que els arxius d'OBS no han estat bloquejats o eliminats per qualsevol programa de seguretat de tercers." +UnableToWritePath="No s'ha pogut escriure a %1. Assegureu-vos que utilitzeu una ruta de gravació a la qual el vostre compte d'usuari tingui permisos per escriure i que hi hagi prou espai al disc." + diff --git a/plugins/obs-ffmpeg/data/locale/cs-CZ.ini b/plugins/obs-ffmpeg/data/locale/cs-CZ.ini index 35920ae..3561fdd 100644 --- a/plugins/obs-ffmpeg/data/locale/cs-CZ.ini +++ b/plugins/obs-ffmpeg/data/locale/cs-CZ.ini @@ -1,5 +1,6 @@ FFmpegOutput="Výstup FFmpegu" FFmpegAAC="Výchozí FFmpeg AAC enkodér" +FFmpegOpus="FFmpeg Opus enkodér" Bitrate="Bitrate" Preset="Předvolba" RateControl="Řízení toku" @@ -23,6 +24,7 @@ LocalFile="Místní soubor" Looping="Opakovat" Input="Vstup" InputFormat="Formát vstupu" +BufferingMB="Vyrovnávací paměť pro síť (MB)" HardwareDecode="Použít hardwarové dekódování, pokud je k dispozici" ClearOnMediaEnd="Skrýt zdroj po skončení přehrávání" Advanced="Pokročilé" @@ -34,6 +36,7 @@ ColorRange.Auto="Automatický" ColorRange.Partial="Částečný" ColorRange.Full="Celkový" RestartMedia="Restartovat mediální zdroj" +Seekable="Posouvatelné" MediaFileFilter.AllMediaFiles="Všechny mediální soubory" MediaFileFilter.VideoFiles="Video soubory" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Všechny soubory" ReplayBuffer="Záznam do paměti" ReplayBuffer.Save="Uložit záznam" +HelperProcessFailed="Nelze spustit pomocný proces nahrávání. Zkontrolujte prosím, zda nejsou soubory OBS blokovány či odstraněny některý z antivirových programů třetích stran." +UnableToWritePath="Nelze zapsat do %1. Zkontrolujte prosím, zda používáte úložiště, do kterého může váš uživatelský účet zapisovat a je zde dostatek volného místa." + diff --git a/plugins/obs-ffmpeg/data/locale/da-DK.ini b/plugins/obs-ffmpeg/data/locale/da-DK.ini index ce09905..892fd4a 100644 --- a/plugins/obs-ffmpeg/data/locale/da-DK.ini +++ b/plugins/obs-ffmpeg/data/locale/da-DK.ini @@ -1,5 +1,6 @@ -FFmpegOutput="FFmpeg Output" +FFmpegOutput="FFmpeg output" FFmpegAAC="FFmpeg Standard AAC Encoder" +FFmpegOpus="FFmpeg Opus-encoder" Bitrate="Bitrate" Preset="Forudindstillet" RateControl="Tempokontrol" @@ -23,6 +24,7 @@ LocalFile="Lokal fil" Looping="Gentagelse" Input="Input" InputFormat="Input format" +BufferingMB="Netværksbuffering (MB)" HardwareDecode="Brug hardwareafkodning når tilgængelige" ClearOnMediaEnd="Skjul kilde når afspilning slutter" Advanced="Avanceret" @@ -34,6 +36,7 @@ ColorRange.Auto="Auto" ColorRange.Partial="Delvis" ColorRange.Full="Fuld" RestartMedia="Genstart Media" +Seekable="Seekable" MediaFileFilter.AllMediaFiles="Alle mediefiler" MediaFileFilter.VideoFiles="Videofiler" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Alle filer" ReplayBuffer="Genafspilningsbuffer" ReplayBuffer.Save="Gem Genafspilning" +HelperProcessFailed="Kan ikke starte optagelseshjælperprocessen. Tjek at OBS-filer ikke blokeres eller er fjernet af noget 3. parts antivirus-/sikkerhedssoftware." +UnableToWritePath="Kan ikke skrive til %1. Tjek at du benytter en optagelsessti, som din brugerkonto har skriverettighed til, og at der er tilstrækkelig ledig diskplads." + diff --git a/plugins/obs-ffmpeg/data/locale/de-DE.ini b/plugins/obs-ffmpeg/data/locale/de-DE.ini index 65c07e6..3caf3e5 100644 --- a/plugins/obs-ffmpeg/data/locale/de-DE.ini +++ b/plugins/obs-ffmpeg/data/locale/de-DE.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg Ausgabe" FFmpegAAC="FFmpeg Standard AAC Codierer" +FFmpegOpus="FFmpeg Opus Codierer" Bitrate="Bitrate" Preset="Voreinstellung" RateControl="Qualitäts Regulierungsmethode" @@ -23,6 +24,7 @@ LocalFile="Lokale Datei" Looping="Endlosschleife" Input="Eingabe" InputFormat="Eingabeformat" +BufferingMB="Netzwerkpufferung (MB)" HardwareDecode="Verwende Hardwaredecodierung, falls verfügbar" ClearOnMediaEnd="Quelle verbergen, wenn Wiedergabe endet" Advanced="Erweitert" @@ -34,12 +36,16 @@ ColorRange.Auto="Automatisch" ColorRange.Partial="Teilweise" ColorRange.Full="Voll" RestartMedia="Medium neu starten" +Seekable="Durch­such­bar" MediaFileFilter.AllMediaFiles="Alle Mediendateien" -MediaFileFilter.VideoFiles="Video-Dateien" -MediaFileFilter.AudioFiles="Audio-Dateien" +MediaFileFilter.VideoFiles="Videodateien" +MediaFileFilter.AudioFiles="Audiodateien" MediaFileFilter.AllFiles="Alle Dateien" ReplayBuffer="Replaypuffer" ReplayBuffer.Save="Replay speichern" +HelperProcessFailed="Der Aufnahmehelferprozeß kann nicht gestartet werden. Überprüfen Sie, ob OBS-Dateien nicht von einer Drittanbieter Antiviren- / Sicherheitssoftware blockiert oder entfernt wurden." +UnableToWritePath="Kann nicht zu %1 schreiben. Vergewissern Sie sich, dass Sie einen Aufnahmepfad verwenden, für das Ihr Benutzerkonto Schreibrechte hat und dass genügend Speicherplatz zur Verfügung steht." + diff --git a/plugins/obs-ffmpeg/data/locale/el-GR.ini b/plugins/obs-ffmpeg/data/locale/el-GR.ini index ca56385..7a224d7 100644 --- a/plugins/obs-ffmpeg/data/locale/el-GR.ini +++ b/plugins/obs-ffmpeg/data/locale/el-GR.ini @@ -1,17 +1,47 @@ FFmpegOutput="Έξοδος FFmpeg" FFmpegAAC="FFmpeg προεπιλεγμένος κωδικοποιητής AAC" Bitrate="Ρυθμός μετάδοσης bit" +Preset="Προκαθορισμένο στυλ" +RateControl="Έλεγχος ρυθμού" +KeyframeIntervalSec="Συχνότητα Καρέ-Κλειδιού (δευτερόλεπτα, 0=αυτόματο)" +Lossless="Χωρίς απώλειες" +BFrames="Β-πλαίσια" +NVENC.Use2Pass="Χρήση κωδικοποίησης δύο περασμάτων" NVENC.Preset.default="Προεπιλογή" +NVENC.Preset.hq="Υψηλή Ποιότητα" +NVENC.Preset.hp="Υψηλές Επιδόσεις" +NVENC.Preset.bd="BluRay" +NVENC.Preset.ll="Χαμηλή Καθυστέρηση" +NVENC.Preset.llhq="Χαμηλή Καθυστέρηση Υψηλή Ποιότητα" +NVENC.Preset.llhp="Χαμηλή Καθυστέρηση Υψηλές Επιδόσεις" +NVENC.Level="Επίπεδο" +FFmpegSource="Πηγή Πολυμέσων" LocalFile="Τοπικό αρχείο" Looping="Επανάληψη" Input="Είσοδος" InputFormat="Μορφή Εισόδου" +BufferingMB="Μέγεθος προσωρινης αποθήκευσης Δικτύου (MB)" HardwareDecode="Χρήση αποκωδικοποίησης υλικού όταν είναι διαθέσιμη" ClearOnMediaEnd="Απόκρυψη πηγής όταν τελειώνει η αναπαραγωγή" Advanced="Σύνθετες επιλογές" - +RestartWhenActivated="Επανεκκίνηση της αναπαραγωγής όταν η πηγή γίνεται ξανά ενεργή" +CloseFileWhenInactive="Κλείσιμο του αρχείου όταν η πηγή είναι ανενεργή" +CloseFileWhenInactive.ToolTip="Κλείνει το αρχείο όταν η πηγή δεν προβάλεται στο stream ή\nκαταγράφεται. Αυτό επιτρέπει την αλλαγή του αρχείου όταν η πηγή δεν είναι ενεργή,\nαλλά μπορεί να προκαλέσει καθυστέρηση εκκίνησης όταν η πηγή επανενεργοποιηθεί." +ColorRange="Χώρος Χρωμάτων YUV" +ColorRange.Auto="Αυτόματο" +ColorRange.Partial="Μερικός" +ColorRange.Full="Πλήρης" +RestartMedia="Επανεκκίνηση Πολυμέσων" + +MediaFileFilter.AllMediaFiles="Όλα τα αρχεία πολυμέσων" +MediaFileFilter.VideoFiles="Αρχεία Βίντεο" +MediaFileFilter.AudioFiles="Αρχεία Ήχου" +MediaFileFilter.AllFiles="Όλα τα αρχεία" + +ReplayBuffer="Διάρκεια μνήμης Replay" +ReplayBuffer.Save="Αποθήκευση Replay" diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index c2ad7b6..d315cfc 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg Output" FFmpegAAC="FFmpeg Default AAC Encoder" +FFmpegOpus="FFmpeg Opus Encoder" Bitrate="Bitrate" Preset="Preset" RateControl="Rate Control" @@ -35,6 +36,7 @@ ColorRange.Auto="Auto" ColorRange.Partial="Partial" ColorRange.Full="Full" RestartMedia="Restart Media" +Seekable="Seekable" MediaFileFilter.AllMediaFiles="All Media Files" MediaFileFilter.VideoFiles="Video Files" @@ -43,3 +45,6 @@ MediaFileFilter.AllFiles="All Files" ReplayBuffer="Replay Buffer" ReplayBuffer.Save="Save Replay" + +HelperProcessFailed="Unable to start the recording helper process. Check that OBS files have not been blocked or removed by any 3rd party antivirus / security software." +UnableToWritePath="Unable to write to %1. Make sure you're using a recording path which your user account is allowed to write to and that there is sufficient disk space." diff --git a/plugins/obs-ffmpeg/data/locale/es-ES.ini b/plugins/obs-ffmpeg/data/locale/es-ES.ini index 816c4a9..f7999d4 100644 --- a/plugins/obs-ffmpeg/data/locale/es-ES.ini +++ b/plugins/obs-ffmpeg/data/locale/es-ES.ini @@ -1,5 +1,6 @@ FFmpegOutput="Salida de FFmpeg" FFmpegAAC="Codificador AAC FFmpeg predeterminado" +FFmpegOpus="Codificador Opus de FFmpeg" Bitrate="Tasa de bits" Preset="Preajuste" RateControl="Control de la frecuencia" @@ -23,6 +24,7 @@ LocalFile="Archivo local" Looping="Bucle" Input="Entrada" InputFormat="Formato de entrada" +BufferingMB="Almacenamiento búfer de Red (MB)" HardwareDecode="Utilizar la decodificación por hardware cuando esté disponible" ClearOnMediaEnd="Ocultar la fuente cuando finaliza la reproducción" Advanced="Avanzado" @@ -34,6 +36,7 @@ ColorRange.Auto="Automatico" ColorRange.Partial="Parcial" ColorRange.Full="Completo" RestartMedia="Reiniciar Medio" +Seekable="Buscable" MediaFileFilter.AllMediaFiles="Todos los archivos multimedia" MediaFileFilter.VideoFiles="Archivos de vídeo" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Todos los Archivos" ReplayBuffer="Búfer de reproducción" ReplayBuffer.Save="Guardar repetición" +HelperProcessFailed="No se pudo iniciar el proceso ayudante de grabación. Compruebe que los archivos de OBS no han sido bloqueados o eliminados por cualquier programa de seguridad externo." +UnableToWritePath="No se pudo escribir a %1. Asegúrese de que utiliza una ruta de grabación que su cuenta de usuario tenga permisos para escribir y que haya suficiente espacio en disco." + diff --git a/plugins/obs-ffmpeg/data/locale/et-EE.ini b/plugins/obs-ffmpeg/data/locale/et-EE.ini index 827cc9a..9fbb92d 100644 --- a/plugins/obs-ffmpeg/data/locale/et-EE.ini +++ b/plugins/obs-ffmpeg/data/locale/et-EE.ini @@ -36,3 +36,4 @@ MediaFileFilter.AllFiles="Kõik failid" ReplayBuffer="Taasesituse puhver" ReplayBuffer.Save="Salvesta Taasesitus" + diff --git a/plugins/obs-ffmpeg/data/locale/eu-ES.ini b/plugins/obs-ffmpeg/data/locale/eu-ES.ini index 760f171..2b100b1 100644 --- a/plugins/obs-ffmpeg/data/locale/eu-ES.ini +++ b/plugins/obs-ffmpeg/data/locale/eu-ES.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg irteera" FFmpegAAC="FFmpeg lehenetsitako AAC Kodetzailea" +FFmpegOpus="FFmpeg Opus kodetzailea" Bitrate="Bit-tasa" Preset="Aurrezarpena" RateControl="Tasaren kontrola" @@ -23,6 +24,7 @@ LocalFile="Tokiko fitxategia" Looping="Begizta" Input="Sarrera" InputFormat="Sarrera formatua" +BufferingMB="Sareko bufferreratzea (MB)" HardwareDecode="Erabili hardware deskodeketa eskuragarri dagoenean" ClearOnMediaEnd="Ezkutatu iturburua erreprodukzioa amaitzean" Advanced="Aurreratua" @@ -34,6 +36,7 @@ ColorRange.Auto="Auto" ColorRange.Partial="Partziala" ColorRange.Full="Osoa" RestartMedia="Berrabiarazi euskarria" +Seekable="Bilagai" MediaFileFilter.AllMediaFiles="Multimedia-fitxategi guztiak" MediaFileFilter.VideoFiles="Bideo-fitxategiak" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Fitxategi guztiak" ReplayBuffer="Erreprodukzio bufferra" ReplayBuffer.Save="Gorde erreprodukzioa" +HelperProcessFailed="Ezin izan da grabaziorako laguntza prozesua hasi. Begiratu hirugarren beten antibirusa edo seguritate-softwareak ez duela OBS fitxategiak blokeatzen edo ezabatzen." +UnableToWritePath="Ezin izan da idatzi %1-ean. Egiaztatu grabazio-tokian idazteko baimena duzula eta diskoan leku libre nahikoa dagoela." + diff --git a/plugins/obs-ffmpeg/data/locale/fi-FI.ini b/plugins/obs-ffmpeg/data/locale/fi-FI.ini index ca9af62..46ba85d 100644 --- a/plugins/obs-ffmpeg/data/locale/fi-FI.ini +++ b/plugins/obs-ffmpeg/data/locale/fi-FI.ini @@ -1,5 +1,6 @@ -FFmpegOutput="FFmpeg ulostulo" +FFmpegOutput="FFmpeg-ulostulo" FFmpegAAC="FFmpeg oletus AAC-enkooderi" +FFmpegOpus="FFmpeg Opus -enkooderi" Bitrate="Bitrate" Preset="Esiasetus" RateControl="Rate Control -tila" @@ -23,6 +24,7 @@ LocalFile="Paikallinen tiedosto" Looping="Toista jatkuvasti" Input="Sisääntulo" InputFormat="Sisääntulon muoto" +BufferingMB="Verkon puskurointi (MB)" HardwareDecode="Käytä laitteistotason purkua, kun mahdollista" ClearOnMediaEnd="Piilota lähde kun toisto päättyy" Advanced="Lisäasetukset" @@ -34,6 +36,7 @@ ColorRange.Auto="Automaattinen" ColorRange.Partial="Osittainen" ColorRange.Full="Täysi" RestartMedia="Uudelleenkäynnistä media" +Seekable="Haettava" MediaFileFilter.AllMediaFiles="Kaikki mediatiedostot" MediaFileFilter.VideoFiles="Videotiedostot" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Kaikki tiedostot" ReplayBuffer="Toistopuskuri" ReplayBuffer.Save="Tallenna uusinta" +HelperProcessFailed="Nauhoitusavustaja-prosessin käynnistäminen ei onnistunut. Tarkista, että OBS-tiedostoja ei ole estetty tai poistettu virustutkan tai muun turvallisuusohjelman johdosta." +UnableToWritePath="Ei voida kirjoittaa %1. Tarkista, että käytät oikeaa tiedostopolkua johon käyttäjätililläsi on kirjoitusoikeus. Tarkista myös, että levyllä on tarpeeksi tilaa." + diff --git a/plugins/obs-ffmpeg/data/locale/fr-FR.ini b/plugins/obs-ffmpeg/data/locale/fr-FR.ini index 5396931..7a84314 100644 --- a/plugins/obs-ffmpeg/data/locale/fr-FR.ini +++ b/plugins/obs-ffmpeg/data/locale/fr-FR.ini @@ -1,5 +1,6 @@ FFmpegOutput="Sortie FFmpeg" FFmpegAAC="Encodeur AAC FFmpeg par défaut" +FFmpegOpus="Encodeur FFmpeg Opus" Bitrate="Débit" Preset="Préréglage" RateControl="Contrôle du débit" @@ -23,6 +24,7 @@ LocalFile="Fichier local" Looping="En boucle" Input="Entrée" InputFormat="Format d'entrée" +BufferingMB="Mémoire tampon réseau (Mo)" HardwareDecode="Utiliser le décodage matériel si possible" ClearOnMediaEnd="Cacher la source lorsque la lecture est finie" Advanced="Options avancées" @@ -34,6 +36,7 @@ ColorRange.Auto="Auto" ColorRange.Partial="Partielle" ColorRange.Full="Complète" RestartMedia="Redémarrez Media" +Seekable="Navigable" MediaFileFilter.AllMediaFiles="Tous les fichiers multimédias" MediaFileFilter.VideoFiles="Fichiers vidéo" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Tous les fichiers" ReplayBuffer="Tampon de relecture" ReplayBuffer.Save="Sauvegarder la relecture" +HelperProcessFailed="Impossible de démarrer le processus d’assistance pour l’enregistrement. Vérifiez que les fichiers de OBS ne sont pas bloqués/supprimés par n’importe quel antivirus/logiciel de sécurité." +UnableToWritePath="Impossible d’écrire dans %1. Assurez-vous que vous utilisez un chemin d’enregistrement dont votre compte d’utilisateur est autorisé à écrire et qu’il y a suffisamment d’espace disque." + diff --git a/plugins/obs-ffmpeg/data/locale/gl-ES.ini b/plugins/obs-ffmpeg/data/locale/gl-ES.ini index 83e4029..1cde94a 100644 --- a/plugins/obs-ffmpeg/data/locale/gl-ES.ini +++ b/plugins/obs-ffmpeg/data/locale/gl-ES.ini @@ -16,3 +16,4 @@ Advanced="Avanzado" + diff --git a/plugins/obs-ffmpeg/data/locale/he-IL.ini b/plugins/obs-ffmpeg/data/locale/he-IL.ini index 44ca97e..3d1c0db 100644 --- a/plugins/obs-ffmpeg/data/locale/he-IL.ini +++ b/plugins/obs-ffmpeg/data/locale/he-IL.ini @@ -37,3 +37,4 @@ MediaFileFilter.AudioFiles="קבצי אודיו" MediaFileFilter.AllFiles="כל הקבצים" + diff --git a/plugins/obs-ffmpeg/data/locale/hi-IN.ini b/plugins/obs-ffmpeg/data/locale/hi-IN.ini index 175db22..10559ff 100644 --- a/plugins/obs-ffmpeg/data/locale/hi-IN.ini +++ b/plugins/obs-ffmpeg/data/locale/hi-IN.ini @@ -5,3 +5,4 @@ FFmpegOutput="FFmpeg आउटपुट" + diff --git a/plugins/obs-ffmpeg/data/locale/hr-HR.ini b/plugins/obs-ffmpeg/data/locale/hr-HR.ini index 004975d..839bc97 100644 --- a/plugins/obs-ffmpeg/data/locale/hr-HR.ini +++ b/plugins/obs-ffmpeg/data/locale/hr-HR.ini @@ -37,3 +37,4 @@ MediaFileFilter.AudioFiles="Zvučne datoteke" MediaFileFilter.AllFiles="Sve datoteke" + diff --git a/plugins/obs-ffmpeg/data/locale/hu-HU.ini b/plugins/obs-ffmpeg/data/locale/hu-HU.ini index 306e33b..7b1b598 100644 --- a/plugins/obs-ffmpeg/data/locale/hu-HU.ini +++ b/plugins/obs-ffmpeg/data/locale/hu-HU.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg kimenet" FFmpegAAC="FFmpeg alapértelmezett AAC kódoló" +FFmpegOpus="FFmpeg Opus kódoló" Bitrate="Bitsebesség" Preset="Készlet" RateControl="Sebesség Vezérlés" @@ -23,6 +24,7 @@ LocalFile="Helyi fájl" Looping="Ismétlés" Input="Bemenet" InputFormat="Bemeneti formátum" +BufferingMB="Hálózati pufferelés (MB)" HardwareDecode="Hardveres dekódolás használata, ha rendelkezésre áll" ClearOnMediaEnd="Forrás elrejtése a lejátszás végeztével" Advanced="Haladó" @@ -34,6 +36,7 @@ ColorRange.Auto="Auto" ColorRange.Partial="Részleges" ColorRange.Full="Teljes" RestartMedia="Media újraindítása" +Seekable="Kereshető" MediaFileFilter.AllMediaFiles="Minden médiafájl" MediaFileFilter.VideoFiles="Videofájlok" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Minden fájl" ReplayBuffer="Visszajátszás puffer" ReplayBuffer.Save="Visszajátszás mentése" +HelperProcessFailed="Nem lehet indítani a felvétel segítő folyamatot. Ellenőrizze, hogy az OBS fájlok nincsenek e blokkolva vagy eltávolítva bármilyen harmadik féltől származó antivírus / biztonsági szoftver által." +UnableToWritePath="Nem lehet megírni a %1. Győződjön meg róla, hogy a felhasználói fiókjának megvannak a szükséges engedélyei az íráshoz vagy rendelkezésre áll az elegendő lemezterület." + diff --git a/plugins/obs-ffmpeg/data/locale/it-IT.ini b/plugins/obs-ffmpeg/data/locale/it-IT.ini index 0398a83..623ba5a 100644 --- a/plugins/obs-ffmpeg/data/locale/it-IT.ini +++ b/plugins/obs-ffmpeg/data/locale/it-IT.ini @@ -1,5 +1,6 @@ FFmpegOutput="Uscita FFmpeg" FFmpegAAC="Codificatore FFmpeg predefinito AAC" +FFmpegOpus="FFmpeg Opus Encoder" Bitrate="Bitrate" Preset="Preset" RateControl="Controllo frequenza" @@ -23,14 +24,19 @@ LocalFile="File locale" Looping="Ripeti" Input="Input" InputFormat="Formato di input" +BufferingMB="Rete Buffering (MB)" HardwareDecode="Utilizza la decodifica hardware quando disponibile" ClearOnMediaEnd="Nascondi la fonte quando termina la riproduzione" Advanced="Avanzate" RestartWhenActivated="Riattiva playback quando la fonte torna attiva" +CloseFileWhenInactive="Chiudi file quando inattivo" +CloseFileWhenInactive.ToolTip="Chiude il file quando l'origine non viene visualizzato sullo stream o\nrecording il flusso. In questo modo permette di modificare il file quando la fonte non è attiva, \nma ci può essere qualche ritardo di avvio quando si riattiva l'origine." ColorRange="Gamma di colore YUV" ColorRange.Auto="Autom." ColorRange.Partial="Parziale" ColorRange.Full="Intero" +RestartMedia="Riavvia Media" +Seekable="Ricercabile" MediaFileFilter.AllMediaFiles="Tutti i file media" MediaFileFilter.VideoFiles="File video" @@ -40,3 +46,6 @@ MediaFileFilter.AllFiles="Tutti i file" ReplayBuffer="Buffer di Replay" ReplayBuffer.Save="Salva Replay" +HelperProcessFailed="Impossibile avviare il processo di supporto di registrazione. Verifica che i file di OBS non siano stati bloccati o rimossi da software di sicurezza od antivirus di terze parti." +UnableToWritePath="Impossibile scrivere su %1. Assicurati che il tuo account possa accedere al percorso di registrazione, e che ci sia spazio sufficiente su disco." + diff --git a/plugins/obs-ffmpeg/data/locale/ja-JP.ini b/plugins/obs-ffmpeg/data/locale/ja-JP.ini index 693b2c7..c789309 100644 --- a/plugins/obs-ffmpeg/data/locale/ja-JP.ini +++ b/plugins/obs-ffmpeg/data/locale/ja-JP.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg の出力" FFmpegAAC="FFmpeg 既定のAAC エンコーダ" +FFmpegOpus="FFmpeg Opus エンコーダ" Bitrate="ビットレート" Preset="プリセット" RateControl="レート制御" @@ -23,6 +24,7 @@ LocalFile="ローカルファイル" Looping="繰り返し" Input="入力" InputFormat="入力フォーマット" +BufferingMB="ネットワークバッファリング (MB)" HardwareDecode="可能な場合ハードウェアデコードを使用" ClearOnMediaEnd="再生終了時にソースを非表示にする" Advanced="高度な設定" @@ -34,6 +36,7 @@ ColorRange.Auto="自動" ColorRange.Partial="一部" ColorRange.Full="全部" RestartMedia="メディアを再開する" +Seekable="シーク可能" MediaFileFilter.AllMediaFiles="すべてのメディアファイル" MediaFileFilter.VideoFiles="ビデオファイル" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="すべてのファイル" ReplayBuffer="リプレイバッファー" ReplayBuffer.Save="リプレイ保存" +HelperProcessFailed="録画の補助プロセスを開始できません。 OBSのファイルがサードパーティ製のアンチウイルス/セキュリティソフトウェアによってブロックまたは削除されていないことを確認してください。" +UnableToWritePath="%1 に書き込めません。 あなたのユーザーアカウントが書き込みを許可されている録画のパスを使用していることと十分なディスク容量があることを確認してください。" + diff --git a/plugins/obs-ffmpeg/data/locale/ko-KR.ini b/plugins/obs-ffmpeg/data/locale/ko-KR.ini index fb8dec3..ddf5041 100644 --- a/plugins/obs-ffmpeg/data/locale/ko-KR.ini +++ b/plugins/obs-ffmpeg/data/locale/ko-KR.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg 출력" FFmpegAAC="FFmpeg 기본 AAC 인코더" +FFmpegOpus="FFmpeg Opus 인코더" Bitrate="비트레이트" Preset="사전 설정" RateControl="데이터율 제어" @@ -23,6 +24,7 @@ LocalFile="로컬 파일" Looping="반복" Input="입력" InputFormat="입력 형식" +BufferingMB="네트워크 버퍼링 (MB)" HardwareDecode="가능한 경우 하드웨어 디코딩 사용" ClearOnMediaEnd="재생이 끝나면 소스를 숨기기" Advanced="고급" @@ -34,6 +36,7 @@ ColorRange.Auto="자동" ColorRange.Partial="부분" ColorRange.Full="전체" RestartMedia="미디어 다시재생" +Seekable="탐색 가능" MediaFileFilter.AllMediaFiles="모든 미디어 파일" MediaFileFilter.VideoFiles="비디오 파일" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="모든 파일" ReplayBuffer="리플레이 버퍼" ReplayBuffer.Save="리플레이 저장" +HelperProcessFailed="녹화 도우미를 시작할 수 없습니다. 백신이나 보안 소프트웨어가 OBS 파일을 차단 혹은 제거하지 않았는지 확인하십시오." +UnableToWritePath="%1에 기록할 수 없습니다. 사용자 계정이 녹화 계정에 접근할 수 있는지 혹은 저장 공간이 충분한지 확인하십시오." + diff --git a/plugins/obs-ffmpeg/data/locale/nb-NO.ini b/plugins/obs-ffmpeg/data/locale/nb-NO.ini index 03c1131..38ad66c 100644 --- a/plugins/obs-ffmpeg/data/locale/nb-NO.ini +++ b/plugins/obs-ffmpeg/data/locale/nb-NO.ini @@ -1,11 +1,13 @@ FFmpegOutput="FFmpeg utdata" FFmpegAAC="Standard FFmpeg AAC-koder" +FFmpegOpus="FFmpeg Opus enkoder" Bitrate="Bitrate" Preset="Forhåndsinnstilling" RateControl="Hastighetskontroll" KeyframeIntervalSec="Nøkkelbildeintervall (sekunder, 0 = automatisk)" Lossless="Tapsfri" +BFrames="B-bilder" NVENC.Use2Pass="Bruk tostegskoding" NVENC.Preset.default="Standard" @@ -22,18 +24,28 @@ LocalFile="Lokal fil" Looping="Repeter" Input="Inngang" InputFormat="Inngangsformat" +BufferingMB="Nettverksbuffer (Mb)" HardwareDecode="Bruk maskinvaredekoding når tilgjengelig" ClearOnMediaEnd="Skjul kilde når avspilling ender" Advanced="Avansert" RestartWhenActivated="Start avspilling omigjen når kilde blir aktiv" +CloseFileWhenInactive="Lukk fil når inaktiv" +CloseFileWhenInactive.ToolTip="Lukker filen når kilden ikke vises på strømmen eller \nopptaket. Dette gjør at filen kan endres når kilden ikke er aktiv,\nmen det kan være noe forsinkelse på oppstart når kilden reaktiveres." ColorRange="YUV fargerom" ColorRange.Auto="Automatisk" ColorRange.Partial="Delvis" ColorRange.Full="Hel" +RestartMedia="Start media på nytt" +Seekable="Søkbar" MediaFileFilter.AllMediaFiles="Alle mediefiler" MediaFileFilter.VideoFiles="Videofiler" MediaFileFilter.AudioFiles="Lydfiler" MediaFileFilter.AllFiles="Alle filer" +ReplayBuffer="Spill av buffer på nytt" +ReplayBuffer.Save="Lagre replay" + +HelperProcessFailed="Kan ikke starte prosess for opptakshjelper. Sjekk at OBS ikke har noen blokkerte filer, eller at tredjeparts antivirus/sikkerhetsprogramvare har slettet noen filer." +UnableToWritePath="Kan ikke skrive til %1. Kontroller at du bruker en filbane som kontoen din har rettigheter til å skrive på, og at du har nok diskplass tilgjengelig." diff --git a/plugins/obs-ffmpeg/data/locale/nl-NL.ini b/plugins/obs-ffmpeg/data/locale/nl-NL.ini index 67b619c..d45cd8a 100644 --- a/plugins/obs-ffmpeg/data/locale/nl-NL.ini +++ b/plugins/obs-ffmpeg/data/locale/nl-NL.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg-uitvoer" FFmpegAAC="FFmpeg Standaard AAC Encoder" +FFmpegOpus="FFmpeg Opus Encoder" Bitrate="Bitrate" Preset="Preset" RateControl="Rate Control" @@ -23,6 +24,7 @@ LocalFile="Lokaal bestand" Looping="Herhalen" Input="Invoer" InputFormat="Invoerformaat" +BufferingMB="Netwerk Buffering (MB)" HardwareDecode="Gebruik hardware-decoding wanneer mogelijk" ClearOnMediaEnd="Verberg de bron na het afspelen" Advanced="Geavanceerd" @@ -34,6 +36,7 @@ ColorRange.Auto="Automatisch" ColorRange.Partial="Gedeeltelijk" ColorRange.Full="Volledig" RestartMedia="Media herstarten" +Seekable="Zoekbaar" MediaFileFilter.AllMediaFiles="Alle mediabestanden" MediaFileFilter.VideoFiles="Videobestanden" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Alle bestanden" ReplayBuffer="Replay Buffer" ReplayBuffer.Save="Replay Opslaan" +HelperProcessFailed="Kan het opnamehulp-proces niet starten. Controleer of er geen OBS bestanden geblokkeerd of verwijderd zijn door antivirus of beveiligingssoftware." +UnableToWritePath="Kan niet naar %1 schrijven. Controller of je een opnamepad gebruikt waar je gebruikersaccount naartoe kan schrijven, en dat er voldoende schijfruimte beschikbaar is." + diff --git a/plugins/obs-ffmpeg/data/locale/pl-PL.ini b/plugins/obs-ffmpeg/data/locale/pl-PL.ini index ae1f281..9215a44 100644 --- a/plugins/obs-ffmpeg/data/locale/pl-PL.ini +++ b/plugins/obs-ffmpeg/data/locale/pl-PL.ini @@ -1,5 +1,6 @@ FFmpegOutput="Wyjście FFmpeg" FFmpegAAC="Domyślny enkoder AAC w FFmpeg" +FFmpegOpus="Enkoder FFmpeg Opus" Bitrate="Przepływność bitowa" Preset="Profil ustawień" RateControl="Typ przepływności" @@ -23,6 +24,7 @@ LocalFile="Plik lokalny" Looping="Pętla" Input="Wejście" InputFormat="Format wejściowy" +BufferingMB="Bufor sieciowy (MB)" HardwareDecode="Użyj sprzętowego dekodowania gdy to możliwe" ClearOnMediaEnd="Ukryj źródło po zakończeniu odtwarzania" Advanced="Zaawansowane" @@ -34,6 +36,7 @@ ColorRange.Auto="Automatycznie" ColorRange.Partial="Częściowy" ColorRange.Full="Pełny" RestartMedia="Zrestartuj plik audio-wideo" +Seekable="Przeszukiwalny" MediaFileFilter.AllMediaFiles="Wszystkie pliki multimedialne" MediaFileFilter.VideoFiles="Pliki video" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Wszystkie pliki" ReplayBuffer="Bufor replay" ReplayBuffer.Save="Zapisz replay" +HelperProcessFailed="Nie można uruchomić procesu nagrywania. Sprawdź, czy pliki OBS nie są blokowane lub usunięte przez oprogramowanie zewnętrzne, np. antywirusowe lub chroniące system." +UnableToWritePath="Nie można zapisać do %1. Sprawdź poprawność ścieżki zapisu, prawa dostępu do niej oraz wolne miejsce na dysku." + diff --git a/plugins/obs-ffmpeg/data/locale/pt-BR.ini b/plugins/obs-ffmpeg/data/locale/pt-BR.ini index 49d4a95..fe084c4 100644 --- a/plugins/obs-ffmpeg/data/locale/pt-BR.ini +++ b/plugins/obs-ffmpeg/data/locale/pt-BR.ini @@ -1,5 +1,6 @@ FFmpegOutput="Saída do FFmpeg" FFmpegAAC="Codificador AAC Padrão do FFmpeg" +FFmpegOpus="Codificador FFmpeg Optus" Bitrate="Taxa de Bits" Preset="Predefinição" RateControl="Controle da Taxa de Bits" @@ -23,6 +24,7 @@ LocalFile="Arquivo Local" Looping="Loop" Input="Entrada" InputFormat="Formato de entrada" +BufferingMB="Buffer de Rede (MB)" HardwareDecode="Utilizar descodificação de hardware quando disponível" ClearOnMediaEnd="Ocultar fonte quando a reprodução terminar" Advanced="Avançado" @@ -34,6 +36,7 @@ ColorRange.Auto="Auto" ColorRange.Partial="Parcial" ColorRange.Full="Completo" RestartMedia="Reiniciar Mídia" +Seekable="Procurável" MediaFileFilter.AllMediaFiles="Todos Arquivos de Mídia" MediaFileFilter.VideoFiles="Arquivos de Vídeo" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Todos os Arquivos" ReplayBuffer="Buffer do Replay" ReplayBuffer.Save="Salvar Replay" +HelperProcessFailed="Não foi possível iniciar o processo auxiliar de gravação. Verifique se os arquivos do OBS não foram bloqueados ou removidos por qualquer outro software (Ex: antivírus)." +UnableToWritePath="Não foi possível escrever em %1. Certifique-se de que você está usando um caminho de gravação que sua conta de usuário possui permissão para gravar e que há espaço suficiente no disco." + diff --git a/plugins/obs-ffmpeg/data/locale/pt-PT.ini b/plugins/obs-ffmpeg/data/locale/pt-PT.ini index 471d343..54a5ba0 100644 --- a/plugins/obs-ffmpeg/data/locale/pt-PT.ini +++ b/plugins/obs-ffmpeg/data/locale/pt-PT.ini @@ -36,3 +36,4 @@ MediaFileFilter.AudioFiles="Arquivos de Áudio" MediaFileFilter.AllFiles="Todos os ficheiros" + diff --git a/plugins/obs-ffmpeg/data/locale/ro-RO.ini b/plugins/obs-ffmpeg/data/locale/ro-RO.ini index 6be3b7e..6ac99f2 100644 --- a/plugins/obs-ffmpeg/data/locale/ro-RO.ini +++ b/plugins/obs-ffmpeg/data/locale/ro-RO.ini @@ -28,3 +28,4 @@ MediaFileFilter.AudioFiles="Fișiere audio" MediaFileFilter.AllFiles="Toate fișierele" + diff --git a/plugins/obs-ffmpeg/data/locale/ru-RU.ini b/plugins/obs-ffmpeg/data/locale/ru-RU.ini index 0727f1d..ba5a81a 100644 --- a/plugins/obs-ffmpeg/data/locale/ru-RU.ini +++ b/plugins/obs-ffmpeg/data/locale/ru-RU.ini @@ -1,5 +1,6 @@ FFmpegOutput="Вывод FFmpeg" FFmpegAAC="Стандартный AAC-кодер FFmpeg" +FFmpegOpus="Кодировщик FFmpeg Opus" Bitrate="Битрейт" Preset="Пресет" RateControl="Управление битрейтом" @@ -23,6 +24,7 @@ LocalFile="Локальный файл" Looping="Повтор" Input="Ввод" InputFormat="Формат ввода" +BufferingMB="Сетевая буферизация (МБ)" HardwareDecode="Использовать аппаратное декодирование при наличии" ClearOnMediaEnd="Скрывать источник, когда воспроизведение заканчивается" Advanced="Дополнительно" @@ -34,6 +36,7 @@ ColorRange.Auto="Автоматически" ColorRange.Partial="Частичный" ColorRange.Full="Полный" RestartMedia="Перезапустить медиа" +Seekable="Перематываемый" MediaFileFilter.AllMediaFiles="Все медиа-файлы" MediaFileFilter.VideoFiles="Видеофайлы" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Все файлы" ReplayBuffer="Буфер повтора" ReplayBuffer.Save="Сохранить повтор" +HelperProcessFailed="Невозможно запустить вспомогательный процесс для записи. Проверьте, не были ли заблокированы или удалены файлы OBS сторонним антивирусом / защитным ПО." +UnableToWritePath="Невозможно записать в %1. Убедитесь, что для вашей учетной записи разрешена запись по этому пути, и что на диске достаточно свободного пространства." + diff --git a/plugins/obs-ffmpeg/data/locale/sk-SK.ini b/plugins/obs-ffmpeg/data/locale/sk-SK.ini index 54e7fa2..67fbe86 100644 --- a/plugins/obs-ffmpeg/data/locale/sk-SK.ini +++ b/plugins/obs-ffmpeg/data/locale/sk-SK.ini @@ -1,11 +1,33 @@ FFmpegOutput="Výstup FFmpeg" FFmpegAAC="Predvolený FFmpeg AAC enkodér" +FFmpegOpus="FFmpeg Opus enkodér" Bitrate="Bitrate" +Preset="Predvoľba" +NVENC.Preset.default="Predvolená" +NVENC.Preset.hq="Vysoká kvalita" +NVENC.Preset.hp="Vysoký výkon" +NVENC.Preset.bd="BluRay" +NVENC.Level="Úroveň" +FFmpegSource="Zdroj médií" +LocalFile="Lokálny súbor" Looping="Slučka" +Input="Vstup" +InputFormat="Vstupný formát" +BufferingMB="Sieťové zapisovanie do medzipamäte (MB)" +HardwareDecode="Použiť hardvérové dekódovanie podľa dostupnosti" Advanced="Rozšírené" +ColorRange="Rozsah farieb YUV" +ColorRange.Auto="Automaticky" +ColorRange.Partial="Čiastočný" +ColorRange.Full="Plný" + +MediaFileFilter.AllMediaFiles="Všetky mediálne súbory" +MediaFileFilter.VideoFiles="Video súbory" +MediaFileFilter.AudioFiles="Zvukové súbory" +MediaFileFilter.AllFiles="Všetky súbory" diff --git a/plugins/obs-ffmpeg/data/locale/sl-SI.ini b/plugins/obs-ffmpeg/data/locale/sl-SI.ini index 60e05a8..3d54ab6 100644 --- a/plugins/obs-ffmpeg/data/locale/sl-SI.ini +++ b/plugins/obs-ffmpeg/data/locale/sl-SI.ini @@ -15,3 +15,4 @@ Advanced="Napredno" + diff --git a/plugins/obs-ffmpeg/data/locale/sr-CS.ini b/plugins/obs-ffmpeg/data/locale/sr-CS.ini index 004975d..839bc97 100644 --- a/plugins/obs-ffmpeg/data/locale/sr-CS.ini +++ b/plugins/obs-ffmpeg/data/locale/sr-CS.ini @@ -37,3 +37,4 @@ MediaFileFilter.AudioFiles="Zvučne datoteke" MediaFileFilter.AllFiles="Sve datoteke" + diff --git a/plugins/obs-ffmpeg/data/locale/sr-SP.ini b/plugins/obs-ffmpeg/data/locale/sr-SP.ini index 3d4700d..ecfb53a 100644 --- a/plugins/obs-ffmpeg/data/locale/sr-SP.ini +++ b/plugins/obs-ffmpeg/data/locale/sr-SP.ini @@ -37,3 +37,4 @@ MediaFileFilter.AudioFiles="Звучне датотеке" MediaFileFilter.AllFiles="Све датотеке" + diff --git a/plugins/obs-ffmpeg/data/locale/sv-SE.ini b/plugins/obs-ffmpeg/data/locale/sv-SE.ini index 01fbaa3..82fbef2 100644 --- a/plugins/obs-ffmpeg/data/locale/sv-SE.ini +++ b/plugins/obs-ffmpeg/data/locale/sv-SE.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg-utmatning" FFmpegAAC="AAC-kodare (FFmpeg standard)" +FFmpegOpus="FFmpeg Opus-kodare" Bitrate="Bithastighet" Preset="Förinställning" RateControl="Hastighetskontroll" @@ -23,6 +24,7 @@ LocalFile="Lokal fil" Looping="Upprepa" Input="Infoga" InputFormat="Inmatningsformat" +BufferingMB="Nätverksbuffring (MB)" HardwareDecode="Använda hårdvareavkodning när tillgängligt" ClearOnMediaEnd="Dölja källa när uppspelningen slutar" Advanced="Avancerat" @@ -34,6 +36,7 @@ ColorRange.Auto="Automatisk" ColorRange.Partial="Delvis" ColorRange.Full="Full" RestartMedia="Starta om media" +Seekable="Sökbar" MediaFileFilter.AllMediaFiles="Alla mediafiler" MediaFileFilter.VideoFiles="Videofiler" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Alla filer" ReplayBuffer="Reprisbuffert" ReplayBuffer.Save="Spara repris" +HelperProcessFailed="Kan inte spela in hjälpprocessen. Kontrollera att OBS-filer inte har blockerats eller tagits bort av antivirus-/säkerhetsprogram från tredjepart." +UnableToWritePath="Kan inte skriva till %1. Se till att du använder en inspelningssökväg som ditt användarkonto har tillåtelse att skriva till och att det finns tillräckligt mycket diskutrymme." + diff --git a/plugins/obs-ffmpeg/data/locale/th-TH.ini b/plugins/obs-ffmpeg/data/locale/th-TH.ini index d47c30b..8e88f66 100644 --- a/plugins/obs-ffmpeg/data/locale/th-TH.ini +++ b/plugins/obs-ffmpeg/data/locale/th-TH.ini @@ -5,3 +5,4 @@ Bitrate="บิตเรท" + diff --git a/plugins/obs-ffmpeg/data/locale/tr-TR.ini b/plugins/obs-ffmpeg/data/locale/tr-TR.ini index 4ab85ca..22393a7 100644 --- a/plugins/obs-ffmpeg/data/locale/tr-TR.ini +++ b/plugins/obs-ffmpeg/data/locale/tr-TR.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg Çıkışı" FFmpegAAC="FFmpeg Varsayılan AAC Kodlayıcı" +FFmpegOpus="FFmpeg Opus Kodlayıcı" Bitrate="Bit hızı" Preset="Ön Tanımlı" RateControl="Oran Kontrolü" @@ -23,6 +24,7 @@ LocalFile="Yerel Dosya" Looping="Döngü" Input="Giriş" InputFormat="Giriş Biçimi" +BufferingMB="Ağ Arabelleğe Alma (MB)" HardwareDecode="Kullanılabilir ise, donanım kod çözmeyi kullan" ClearOnMediaEnd="Kayıttan yürütme bittiğinde kaynağı gizle" Advanced="Gelişmiş" @@ -34,6 +36,7 @@ ColorRange.Auto="Otomatik" ColorRange.Partial="Kısmi" ColorRange.Full="Tam" RestartMedia="Ortamı Yeniden Başlat" +Seekable="Aranabilir" MediaFileFilter.AllMediaFiles="Tüm Medya Dosyaları" MediaFileFilter.VideoFiles="Video Dosyaları" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Tüm Dosyalar" ReplayBuffer="Tekrar Oynatma Arabelleği" ReplayBuffer.Save="Yeniden Oynatmayı Kaydet" +HelperProcessFailed="Kayıt yardımcısı işlemi başlatılamadı. OBS dosyalarının herhangi bir 3. taraf antivirüs / güvenlik yazılımı tarafından engellenmediğini veya kaldırılmadığını kontrol edin." +UnableToWritePath="%1 yazılamadı. Kullanıcı hesabınızın yazmasına izin verilen bir kayıt konumu kullanıyor olduğunuzdan ve yeterli disk alanı olduğundan emin olun." + diff --git a/plugins/obs-ffmpeg/data/locale/uk-UA.ini b/plugins/obs-ffmpeg/data/locale/uk-UA.ini index 8187aa1..4ab367d 100644 --- a/plugins/obs-ffmpeg/data/locale/uk-UA.ini +++ b/plugins/obs-ffmpeg/data/locale/uk-UA.ini @@ -1,5 +1,6 @@ FFmpegOutput="Вивід FFmpeg" FFmpegAAC="FFmpeg AAC енкодер за замовчанням" +FFmpegOpus="FFmpeg Opus енкодер" Bitrate="Бітрейт" Preset="Шаблон" RateControl="Керування потоком" @@ -20,20 +21,22 @@ NVENC.Level="Рівень" FFmpegSource="Мультимедіа" LocalFile="Локальний файл" -Looping="Повторювати" +Looping="Циклічно відтворювати" Input="Вхід" InputFormat="Вхідний формат" +BufferingMB="Буферизація мережевого контенту (МБ)" HardwareDecode="Використовувати апаратне декодування, за наявності" -ClearOnMediaEnd="Не відображати джерело, коли відтворення завершено" +ClearOnMediaEnd="Не показувати джерело, коли відтворення завершено" Advanced="Розширені параметри" RestartWhenActivated="Грати з початку, коли джерело стає активним" CloseFileWhenInactive="Закрити файл, коли неактивен" -CloseFileWhenInactive.ToolTip="Завжди закриває файл, коли джерело не відображається у трансляції чи запису.\nЦе дозволяє редагувати файл, якщо джерело не є активним, але може виникнути\nдеяка затримка запуску, коли джерело повторно активується." +CloseFileWhenInactive.ToolTip="Завжди закриває файл, коли джерело не виводиться до трансляції чи запису.\nЦе дозволяє редагувати файл, якщо джерело не є активним, але може виникнути\nдеяка затримка запуску, коли джерело повторно активується." ColorRange="YUV, колірний діапазон" ColorRange.Auto="Автовизначення" ColorRange.Partial="Частковий" ColorRange.Full="Повний" RestartMedia="Перезапустити медіа" +Seekable="HTTP з перемотуванням" MediaFileFilter.AllMediaFiles="Файли мультимедіа" MediaFileFilter.VideoFiles="Відео" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="Всі файли" ReplayBuffer="Запис Повторів" ReplayBuffer.Save="Зберегти Повтор" +HelperProcessFailed="Не вдалося розпочати допоміжний процес для запису. Перевірте, що OBS файли не було заблоковано чи видалено антивірусом або будь-яким іншим програмним забезпеченням з безпеки." +UnableToWritePath="Не вдалося записати до %1. Переконайтеся, що ви використовуєте для запису шлях, до якого ваш обліковий запис має дозвіл на запис, і що там є достатньо вільного місця." + diff --git a/plugins/obs-ffmpeg/data/locale/vi-VN.ini b/plugins/obs-ffmpeg/data/locale/vi-VN.ini index 79ebf40..b558018 100644 --- a/plugins/obs-ffmpeg/data/locale/vi-VN.ini +++ b/plugins/obs-ffmpeg/data/locale/vi-VN.ini @@ -6,6 +6,7 @@ RateControl="Cách kiểm soát bitrate" KeyframeIntervalSec="Thời gian đặt Keyframe (giây, 0=tự động)" Lossless="Lossless" +BFrames="B-Frames" NVENC.Use2Pass="Sử dụng 2-Pass Encoding" NVENC.Preset.default="Mặc định" @@ -17,10 +18,25 @@ NVENC.Preset.llhq="Độ trễ thấp chất lượng cao" NVENC.Preset.llhp="Độ trễ thấp hiệu suất cao" NVENC.Level="Cấp độ" +FFmpegSource="Nguồn media" LocalFile="Tập tin cục bộ" Looping="Lặp lại" Input="Nhập" InputFormat="Định dạng đầu vào" - +HardwareDecode="Sử dụng phần cứng để giải mã khi có" +Advanced="Nâng cao" +CloseFileWhenInactive="Đóng tệp khi không hoạt động" +ColorRange="Phạm vi màu YUV" +ColorRange.Auto="Tự động" +ColorRange.Partial="Một phần" +ColorRange.Full="Đầy đủ" +RestartMedia="Khởi động lại media" + +MediaFileFilter.AllMediaFiles="Tất cả tập tin Media" +MediaFileFilter.VideoFiles="Tập tin video" +MediaFileFilter.AudioFiles="Tập tin âm thanh" +MediaFileFilter.AllFiles="Tất cả tập tin" + +ReplayBuffer="Replay Buffer" diff --git a/plugins/obs-ffmpeg/data/locale/zh-CN.ini b/plugins/obs-ffmpeg/data/locale/zh-CN.ini index 8e31c7d..da0beb4 100644 --- a/plugins/obs-ffmpeg/data/locale/zh-CN.ini +++ b/plugins/obs-ffmpeg/data/locale/zh-CN.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg 输出" FFmpegAAC="FFmpeg 默认 AAC 编码器" +FFmpegOpus="FFmpeg Opus 编码器" Bitrate="比特率" Preset="预设" RateControl="速率控制" @@ -23,6 +24,7 @@ LocalFile="本地文件" Looping="循环" Input="输入" InputFormat="输入格式" +BufferingMB="网络缓冲 (MB)" HardwareDecode="在可用时使用硬件解码" ClearOnMediaEnd="当播放结束时隐藏源" Advanced="高级" @@ -34,6 +36,7 @@ ColorRange.Auto="自动" ColorRange.Partial="局部" ColorRange.Full="全部" RestartMedia="重新启动媒体" +Seekable="可搜索" MediaFileFilter.AllMediaFiles="所有媒体文件" MediaFileFilter.VideoFiles="视频文件" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="所有文件" ReplayBuffer="回放缓存" ReplayBuffer.Save="保存回放" +HelperProcessFailed="无法启动录音助手进程。检查 OBS 文件未被任何第三方防病毒 / 安全软件阻止或删除。" +UnableToWritePath="无法写入到 %1。请确保您使用的录制路径您的用户帐户允许写入,并有足够的磁盘空间。" + diff --git a/plugins/obs-ffmpeg/data/locale/zh-TW.ini b/plugins/obs-ffmpeg/data/locale/zh-TW.ini index 846e7a0..80e0121 100644 --- a/plugins/obs-ffmpeg/data/locale/zh-TW.ini +++ b/plugins/obs-ffmpeg/data/locale/zh-TW.ini @@ -1,5 +1,6 @@ FFmpegOutput="FFmpeg 輸出" FFmpegAAC="FFmpeg 預設 AAC 編碼器" +FFmpegOpus="FFmpeg Opus 編碼器" Bitrate="位元率" Preset="預置" RateControl="位元率控制" @@ -23,6 +24,7 @@ LocalFile="本機檔案" Looping="循環" Input="輸入" InputFormat="輸入格式" +BufferingMB="網路緩衝 (MB)" HardwareDecode="盡可能使用硬體解碼" ClearOnMediaEnd="當播放結束時隱藏來源" Advanced="進階" @@ -34,6 +36,7 @@ ColorRange.Auto="自動" ColorRange.Partial="部分" ColorRange.Full="全部" RestartMedia="重新播放媒體" +Seekable="可查找" MediaFileFilter.AllMediaFiles="所有媒體檔案" MediaFileFilter.VideoFiles="影像檔" @@ -43,3 +46,6 @@ MediaFileFilter.AllFiles="所有檔案" ReplayBuffer="重播緩衝" ReplayBuffer.Save="儲存重播" +HelperProcessFailed="無法啟動錄影協助程式。請確定 OBS 檔案沒有被防毒/安全軟體所阻擋或移除。" +UnableToWritePath="無法寫入到 %1。請確定使用者帳戶可以寫入錄影檔路徑以及有足夠的磁碟空間。" + diff --git a/plugins/obs-ffmpeg/dynlink_cuda.h b/plugins/obs-ffmpeg/dynlink_cuda.h new file mode 100644 index 0000000..3a13611 --- /dev/null +++ b/plugins/obs-ffmpeg/dynlink_cuda.h @@ -0,0 +1,98 @@ +/* + * This copyright notice applies to this header file only: + * + * Copyright (c) 2016 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the software, and to permit persons to whom the + * software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#if !defined(AV_COMPAT_DYNLINK_CUDA_H) && !defined(CUDA_VERSION) +#define AV_COMPAT_DYNLINK_CUDA_H + +#include + +#define CUDA_VERSION 7050 + +#if defined(_WIN32) || defined(__CYGWIN__) +#define CUDAAPI __stdcall +#else +#define CUDAAPI +#endif + +#define CU_CTX_SCHED_BLOCKING_SYNC 4 + +typedef int CUdevice; +typedef void* CUarray; +typedef void* CUcontext; +typedef void* CUstream; +#if defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) +typedef unsigned long long CUdeviceptr; +#else +typedef unsigned int CUdeviceptr; +#endif + +typedef enum cudaError_enum { + CUDA_SUCCESS = 0 +} CUresult; + +typedef enum CUmemorytype_enum { + CU_MEMORYTYPE_HOST = 1, + CU_MEMORYTYPE_DEVICE = 2 +} CUmemorytype; + +typedef struct CUDA_MEMCPY2D_st { + size_t srcXInBytes; + size_t srcY; + CUmemorytype srcMemoryType; + const void *srcHost; + CUdeviceptr srcDevice; + CUarray srcArray; + size_t srcPitch; + + size_t dstXInBytes; + size_t dstY; + CUmemorytype dstMemoryType; + void *dstHost; + CUdeviceptr dstDevice; + CUarray dstArray; + size_t dstPitch; + + size_t WidthInBytes; + size_t Height; +} CUDA_MEMCPY2D; + +typedef CUresult CUDAAPI tcuInit(unsigned int Flags); +typedef CUresult CUDAAPI tcuDeviceGetCount(int *count); +typedef CUresult CUDAAPI tcuDeviceGet(CUdevice *device, int ordinal); +typedef CUresult CUDAAPI tcuDeviceGetName(char *name, int len, CUdevice dev); +typedef CUresult CUDAAPI tcuDeviceComputeCapability(int *major, int *minor, CUdevice dev); +typedef CUresult CUDAAPI tcuCtxCreate_v2(CUcontext *pctx, unsigned int flags, CUdevice dev); +typedef CUresult CUDAAPI tcuCtxPushCurrent_v2(CUcontext *pctx); +typedef CUresult CUDAAPI tcuCtxPopCurrent_v2(CUcontext *pctx); +typedef CUresult CUDAAPI tcuCtxDestroy_v2(CUcontext ctx); +typedef CUresult CUDAAPI tcuMemAlloc_v2(CUdeviceptr *dptr, size_t bytesize); +typedef CUresult CUDAAPI tcuMemFree_v2(CUdeviceptr dptr); +typedef CUresult CUDAAPI tcuMemcpy2D_v2(const CUDA_MEMCPY2D *pcopy); +typedef CUresult CUDAAPI tcuGetErrorName(CUresult error, const char** pstr); +typedef CUresult CUDAAPI tcuGetErrorString(CUresult error, const char** pstr); + +#endif diff --git a/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c b/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c index c8df627..755b67c 100644 --- a/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c +++ b/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c @@ -29,6 +29,12 @@ #include +#if LIBAVCODEC_VERSION_MAJOR >= 58 +#define CODEC_FLAG_GLOBAL_H AV_CODEC_FLAG_GLOBAL_HEADER +#else +#define CODEC_FLAG_GLOBAL_H CODEC_FLAG_GLOBAL_HEADER +#endif + /* ------------------------------------------------------------------------- */ struct resize_buf { @@ -312,7 +318,7 @@ static void create_video_stream(struct ffmpeg_mux *ffm) ffm->video_stream->time_base = context->time_base; if (ffm->output->oformat->flags & AVFMT_GLOBALHEADER) - context->flags |= CODEC_FLAG_GLOBAL_HEADER; + context->flags |= CODEC_FLAG_GLOBAL_H; } static void create_audio_stream(struct ffmpeg_mux *ffm, int idx) @@ -346,9 +352,14 @@ static void create_audio_stream(struct ffmpeg_mux *ffm, int idx) context->extradata_size = ffm->audio_header[idx].size; context->channel_layout = av_get_default_channel_layout(context->channels); - + //AVlib default channel layout for 4 channels is 4.0 ; fix for quad + if (context->channels == 4) + context->channel_layout = av_get_channel_layout("quad"); + //AVlib default channel layout for 5 channels is 5.0 ; fix for 4.1 + if (context->channels == 5) + context->channel_layout = av_get_channel_layout("4.1"); if (ffm->output->oformat->flags & AVFMT_GLOBALHEADER) - context->flags |= CODEC_FLAG_GLOBAL_HEADER; + context->flags |= CODEC_FLAG_GLOBAL_H; ffm->num_audio_streams++; } diff --git a/plugins/obs-ffmpeg/nvEncodeAPI.h b/plugins/obs-ffmpeg/nvEncodeAPI.h new file mode 100644 index 0000000..e662880 --- /dev/null +++ b/plugins/obs-ffmpeg/nvEncodeAPI.h @@ -0,0 +1,3324 @@ +/* + * This copyright notice applies to this header file only: + * + * Copyright (c) 2010-2017 NVIDIA Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the software, and to permit persons to whom the + * software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * \file nvEncodeAPI.h + * NVIDIA GPUs - beginning with the Kepler generation - contain a hardware-based encoder + * (referred to as NVENC) which provides fully-accelerated hardware-based video encoding. + * NvEncodeAPI provides the interface for NVIDIA video encoder (NVENC). + * \date 2011-2017 + * This file contains the interface constants, structure definitions and function prototypes. + */ + +#ifndef _NV_ENCODEAPI_H_ +#define _NV_ENCODEAPI_H_ + +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef _MSC_VER +#ifndef _STDINT +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +#endif +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup ENCODER_STRUCTURE NvEncodeAPI Data structures + * @{ + */ + +#if defined(_WIN32) || defined(__CYGWIN__) +#define NVENCAPI __stdcall +#else +#define NVENCAPI +#endif + +#ifdef _WIN32 +typedef RECT NVENC_RECT; +#else +// ========================================================================================= +#if !defined(GUID) && !defined(GUID_DEFINED) +/*! + * \struct GUID + * Abstracts the GUID structure for non-windows platforms. + */ +// ========================================================================================= +typedef struct +{ + uint32_t Data1; /**< [in]: Specifies the first 8 hexadecimal digits of the GUID. */ + uint16_t Data2; /**< [in]: Specifies the first group of 4 hexadecimal digits. */ + uint16_t Data3; /**< [in]: Specifies the second group of 4 hexadecimal digits. */ + uint8_t Data4[8]; /**< [in]: Array of 8 bytes. The first 2 bytes contain the third group of 4 hexadecimal digits. + The remaining 6 bytes contain the final 12 hexadecimal digits. */ +} GUID; +#endif // GUID + +/** + * \struct _NVENC_RECT + * Defines a Rectangle. Used in ::NV_ENC_PREPROCESS_FRAME. + */ +typedef struct _NVENC_RECT +{ + uint32_t left; /**< [in]: X coordinate of the upper left corner of rectangular area to be specified. */ + uint32_t top; /**< [in]: Y coordinate of the upper left corner of the rectangular area to be specified. */ + uint32_t right; /**< [in]: X coordinate of the bottom right corner of the rectangular area to be specified. */ + uint32_t bottom; /**< [in]: Y coordinate of the bottom right corner of the rectangular area to be specified. */ +} NVENC_RECT; + +#endif // _WIN32 + +/** @} */ /* End of GUID and NVENC_RECT structure grouping*/ + +typedef void* NV_ENC_INPUT_PTR; /**< NVENCODE API input buffer */ +typedef void* NV_ENC_OUTPUT_PTR; /**< NVENCODE API output buffer*/ +typedef void* NV_ENC_REGISTERED_PTR; /**< A Resource that has been registered with NVENCODE API*/ + +#define NVENCAPI_MAJOR_VERSION 8 +#define NVENCAPI_MINOR_VERSION 0 + +#define NVENCAPI_VERSION (NVENCAPI_MAJOR_VERSION | (NVENCAPI_MINOR_VERSION << 24)) + +/** + * Macro to generate per-structure version for use with API. + */ +#define NVENCAPI_STRUCT_VERSION(ver) ((uint32_t)NVENCAPI_VERSION | ((ver)<<16) | (0x7 << 28)) + + +#define NVENC_INFINITE_GOPLENGTH 0xffffffff + +#define NV_MAX_SEQ_HDR_LEN (512) + +// ========================================================================================= +// Encode Codec GUIDS supported by the NvEncodeAPI interface. +// ========================================================================================= + +// {6BC82762-4E63-4ca4-AA85-1E50F321F6BF} +static const GUID NV_ENC_CODEC_H264_GUID = +{ 0x6bc82762, 0x4e63, 0x4ca4, { 0xaa, 0x85, 0x1e, 0x50, 0xf3, 0x21, 0xf6, 0xbf } }; + +// {790CDC88-4522-4d7b-9425-BDA9975F7603} +static const GUID NV_ENC_CODEC_HEVC_GUID = +{ 0x790cdc88, 0x4522, 0x4d7b, { 0x94, 0x25, 0xbd, 0xa9, 0x97, 0x5f, 0x76, 0x3 } }; + + + +// ========================================================================================= +// * Encode Profile GUIDS supported by the NvEncodeAPI interface. +// ========================================================================================= + +// {BFD6F8E7-233C-4341-8B3E-4818523803F4} +static const GUID NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID = +{ 0xbfd6f8e7, 0x233c, 0x4341, { 0x8b, 0x3e, 0x48, 0x18, 0x52, 0x38, 0x3, 0xf4 } }; + +// {0727BCAA-78C4-4c83-8C2F-EF3DFF267C6A} +static const GUID NV_ENC_H264_PROFILE_BASELINE_GUID = +{ 0x727bcaa, 0x78c4, 0x4c83, { 0x8c, 0x2f, 0xef, 0x3d, 0xff, 0x26, 0x7c, 0x6a } }; + +// {60B5C1D4-67FE-4790-94D5-C4726D7B6E6D} +static const GUID NV_ENC_H264_PROFILE_MAIN_GUID = +{ 0x60b5c1d4, 0x67fe, 0x4790, { 0x94, 0xd5, 0xc4, 0x72, 0x6d, 0x7b, 0x6e, 0x6d } }; + +// {E7CBC309-4F7A-4b89-AF2A-D537C92BE310} +static const GUID NV_ENC_H264_PROFILE_HIGH_GUID = +{ 0xe7cbc309, 0x4f7a, 0x4b89, { 0xaf, 0x2a, 0xd5, 0x37, 0xc9, 0x2b, 0xe3, 0x10 } }; + +// {7AC663CB-A598-4960-B844-339B261A7D52} +static const GUID NV_ENC_H264_PROFILE_HIGH_444_GUID = +{ 0x7ac663cb, 0xa598, 0x4960, { 0xb8, 0x44, 0x33, 0x9b, 0x26, 0x1a, 0x7d, 0x52 } }; + +// {40847BF5-33F7-4601-9084-E8FE3C1DB8B7} +static const GUID NV_ENC_H264_PROFILE_STEREO_GUID = +{ 0x40847bf5, 0x33f7, 0x4601, { 0x90, 0x84, 0xe8, 0xfe, 0x3c, 0x1d, 0xb8, 0xb7 } }; + +// {CE788D20-AAA9-4318-92BB-AC7E858C8D36} +static const GUID NV_ENC_H264_PROFILE_SVC_TEMPORAL_SCALABILTY = +{ 0xce788d20, 0xaaa9, 0x4318, { 0x92, 0xbb, 0xac, 0x7e, 0x85, 0x8c, 0x8d, 0x36 } }; + +// {B405AFAC-F32B-417B-89C4-9ABEED3E5978} +static const GUID NV_ENC_H264_PROFILE_PROGRESSIVE_HIGH_GUID = +{ 0xb405afac, 0xf32b, 0x417b, { 0x89, 0xc4, 0x9a, 0xbe, 0xed, 0x3e, 0x59, 0x78 } }; + +// {AEC1BD87-E85B-48f2-84C3-98BCA6285072} +static const GUID NV_ENC_H264_PROFILE_CONSTRAINED_HIGH_GUID = +{ 0xaec1bd87, 0xe85b, 0x48f2, { 0x84, 0xc3, 0x98, 0xbc, 0xa6, 0x28, 0x50, 0x72 } }; + +// {B514C39A-B55B-40fa-878F-F1253B4DFDEC} +static const GUID NV_ENC_HEVC_PROFILE_MAIN_GUID = +{ 0xb514c39a, 0xb55b, 0x40fa, { 0x87, 0x8f, 0xf1, 0x25, 0x3b, 0x4d, 0xfd, 0xec } }; + +// {fa4d2b6c-3a5b-411a-8018-0a3f5e3c9be5} +static const GUID NV_ENC_HEVC_PROFILE_MAIN10_GUID = +{ 0xfa4d2b6c, 0x3a5b, 0x411a, { 0x80, 0x18, 0x0a, 0x3f, 0x5e, 0x3c, 0x9b, 0xe5 } }; + +// For HEVC Main 444 8 bit and HEVC Main 444 10 bit profiles only +// {51ec32b5-1b4c-453c-9cbd-b616bd621341} +static const GUID NV_ENC_HEVC_PROFILE_FREXT_GUID = +{ 0x51ec32b5, 0x1b4c, 0x453c, { 0x9c, 0xbd, 0xb6, 0x16, 0xbd, 0x62, 0x13, 0x41 } }; + +// ========================================================================================= +// * Preset GUIDS supported by the NvEncodeAPI interface. +// ========================================================================================= +// {B2DFB705-4EBD-4C49-9B5F-24A777D3E587} +static const GUID NV_ENC_PRESET_DEFAULT_GUID = +{ 0xb2dfb705, 0x4ebd, 0x4c49, { 0x9b, 0x5f, 0x24, 0xa7, 0x77, 0xd3, 0xe5, 0x87 } }; + +// {60E4C59F-E846-4484-A56D-CD45BE9FDDF6} +static const GUID NV_ENC_PRESET_HP_GUID = +{ 0x60e4c59f, 0xe846, 0x4484, { 0xa5, 0x6d, 0xcd, 0x45, 0xbe, 0x9f, 0xdd, 0xf6 } }; + +// {34DBA71D-A77B-4B8F-9C3E-B6D5DA24C012} +static const GUID NV_ENC_PRESET_HQ_GUID = +{ 0x34dba71d, 0xa77b, 0x4b8f, { 0x9c, 0x3e, 0xb6, 0xd5, 0xda, 0x24, 0xc0, 0x12 } }; + +// {82E3E450-BDBB-4e40-989C-82A90DF9EF32} +static const GUID NV_ENC_PRESET_BD_GUID = +{ 0x82e3e450, 0xbdbb, 0x4e40, { 0x98, 0x9c, 0x82, 0xa9, 0xd, 0xf9, 0xef, 0x32 } }; + +// {49DF21C5-6DFA-4feb-9787-6ACC9EFFB726} +static const GUID NV_ENC_PRESET_LOW_LATENCY_DEFAULT_GUID = +{ 0x49df21c5, 0x6dfa, 0x4feb, { 0x97, 0x87, 0x6a, 0xcc, 0x9e, 0xff, 0xb7, 0x26 } }; + +// {C5F733B9-EA97-4cf9-BEC2-BF78A74FD105} +static const GUID NV_ENC_PRESET_LOW_LATENCY_HQ_GUID = +{ 0xc5f733b9, 0xea97, 0x4cf9, { 0xbe, 0xc2, 0xbf, 0x78, 0xa7, 0x4f, 0xd1, 0x5 } }; + +// {67082A44-4BAD-48FA-98EA-93056D150A58} +static const GUID NV_ENC_PRESET_LOW_LATENCY_HP_GUID = +{ 0x67082a44, 0x4bad, 0x48fa, { 0x98, 0xea, 0x93, 0x5, 0x6d, 0x15, 0xa, 0x58 } }; + +// {D5BFB716-C604-44e7-9BB8-DEA5510FC3AC} +static const GUID NV_ENC_PRESET_LOSSLESS_DEFAULT_GUID = +{ 0xd5bfb716, 0xc604, 0x44e7, { 0x9b, 0xb8, 0xde, 0xa5, 0x51, 0xf, 0xc3, 0xac } }; + +// {149998E7-2364-411d-82EF-179888093409} +static const GUID NV_ENC_PRESET_LOSSLESS_HP_GUID = +{ 0x149998e7, 0x2364, 0x411d, { 0x82, 0xef, 0x17, 0x98, 0x88, 0x9, 0x34, 0x9 } }; + +/** + * \addtogroup ENCODER_STRUCTURE NvEncodeAPI Data structures + * @{ + */ + +/** + * Input frame encode modes + */ +typedef enum _NV_ENC_PARAMS_FRAME_FIELD_MODE +{ + NV_ENC_PARAMS_FRAME_FIELD_MODE_FRAME = 0x01, /**< Frame mode */ + NV_ENC_PARAMS_FRAME_FIELD_MODE_FIELD = 0x02, /**< Field mode */ + NV_ENC_PARAMS_FRAME_FIELD_MODE_MBAFF = 0x03 /**< MB adaptive frame/field */ +} NV_ENC_PARAMS_FRAME_FIELD_MODE; + +/** + * Rate Control Modes + */ +typedef enum _NV_ENC_PARAMS_RC_MODE +{ + NV_ENC_PARAMS_RC_CONSTQP = 0x0, /**< Constant QP mode */ + NV_ENC_PARAMS_RC_VBR = 0x1, /**< Variable bitrate mode */ + NV_ENC_PARAMS_RC_CBR = 0x2, /**< Constant bitrate mode */ + NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ = 0x8, /**< low-delay CBR, high quality */ + NV_ENC_PARAMS_RC_CBR_HQ = 0x10, /**< CBR, high quality (slower) */ + NV_ENC_PARAMS_RC_VBR_HQ = 0x20 /**< VBR, high quality (slower) */ +} NV_ENC_PARAMS_RC_MODE; + +#define NV_ENC_PARAMS_RC_VBR_MINQP (NV_ENC_PARAMS_RC_MODE)0x4 /**< Deprecated */ +#define NV_ENC_PARAMS_RC_2_PASS_QUALITY NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ /**< Deprecated */ +#define NV_ENC_PARAMS_RC_2_PASS_FRAMESIZE_CAP NV_ENC_PARAMS_RC_CBR_HQ /**< Deprecated */ +#define NV_ENC_PARAMS_RC_2_PASS_VBR NV_ENC_PARAMS_RC_VBR_HQ /**< Deprecated */ +#define NV_ENC_PARAMS_RC_CBR2 NV_ENC_PARAMS_RC_CBR /**< Deprecated */ + +/** + * Input picture structure + */ +typedef enum _NV_ENC_PIC_STRUCT +{ + NV_ENC_PIC_STRUCT_FRAME = 0x01, /**< Progressive frame */ + NV_ENC_PIC_STRUCT_FIELD_TOP_BOTTOM = 0x02, /**< Field encoding top field first */ + NV_ENC_PIC_STRUCT_FIELD_BOTTOM_TOP = 0x03 /**< Field encoding bottom field first */ +} NV_ENC_PIC_STRUCT; + +/** + * Input picture type + */ +typedef enum _NV_ENC_PIC_TYPE +{ + NV_ENC_PIC_TYPE_P = 0x0, /**< Forward predicted */ + NV_ENC_PIC_TYPE_B = 0x01, /**< Bi-directionally predicted picture */ + NV_ENC_PIC_TYPE_I = 0x02, /**< Intra predicted picture */ + NV_ENC_PIC_TYPE_IDR = 0x03, /**< IDR picture */ + NV_ENC_PIC_TYPE_BI = 0x04, /**< Bi-directionally predicted with only Intra MBs */ + NV_ENC_PIC_TYPE_SKIPPED = 0x05, /**< Picture is skipped */ + NV_ENC_PIC_TYPE_INTRA_REFRESH = 0x06, /**< First picture in intra refresh cycle */ + NV_ENC_PIC_TYPE_UNKNOWN = 0xFF /**< Picture type unknown */ +} NV_ENC_PIC_TYPE; + +/** + * Motion vector precisions + */ +typedef enum _NV_ENC_MV_PRECISION +{ + NV_ENC_MV_PRECISION_DEFAULT = 0x0, /** (if lookahead is enabled, input frames must remain available to the encoder until encode completion) */ + uint32_t disableIadapt :1; /**< [in]: Set this to 1 to disable adaptive I-frame insertion at scene cuts (only has an effect when lookahead is enabled) */ + uint32_t disableBadapt :1; /**< [in]: Set this to 1 to disable adaptive B-frame decision (only has an effect when lookahead is enabled) */ + uint32_t enableTemporalAQ :1; /**< [in]: Set this to 1 to enable temporal AQ for H.264 */ + uint32_t zeroReorderDelay :1; /**< [in]: Set this to 1 to indicate zero latency operation (no reordering delay, num_reorder_frames=0) */ + uint32_t enableNonRefP :1; /**< [in]: Set this to 1 to enable automatic insertion of non-reference P-frames (no effect if enablePTD=0) */ + uint32_t strictGOPTarget :1; /**< [in]: Set this to 1 to minimize GOP-to-GOP rate fluctuations */ + uint32_t aqStrength :4; /**< [in]: When AQ (Spatial) is enabled (i.e. NV_ENC_RC_PARAMS::enableAQ is set), this field is used to specify AQ strength. AQ strength scale is from 1 (low) - 15 (aggressive). If not set, strength is autoselected by driver. */ + uint32_t reservedBitFields :16; /**< [in]: Reserved bitfields and must be set to 0 */ + NV_ENC_QP minQP; /**< [in]: Specifies the minimum QP used for rate control. Client must set NV_ENC_CONFIG::enableMinQP to 1. */ + NV_ENC_QP maxQP; /**< [in]: Specifies the maximum QP used for rate control. Client must set NV_ENC_CONFIG::enableMaxQP to 1. */ + NV_ENC_QP initialRCQP; /**< [in]: Specifies the initial QP used for rate control. Client must set NV_ENC_CONFIG::enableInitialRCQP to 1. */ + uint32_t temporallayerIdxMask; /**< [in]: Specifies the temporal layers (as a bitmask) whose QPs have changed. Valid max bitmask is [2^NV_ENC_CAPS_NUM_MAX_TEMPORAL_LAYERS - 1] */ + uint8_t temporalLayerQP[8]; /**< [in]: Specifies the temporal layer QPs used for rate control. Temporal layer index is used as as the array index */ + uint8_t targetQuality; /**< [in]: Target CQ (Constant Quality) level for VBR mode (range 0-51 with 0-automatic) */ + uint8_t targetQualityLSB; /**< [in]: Fractional part of target quality (as 8.8 fixed point format) */ + uint16_t lookaheadDepth; /**< [in]: Maximum depth of lookahead with range 0-32 (only used if enableLookahead=1) */ + uint32_t reserved[9]; + } NV_ENC_RC_PARAMS; + +/** macro for constructing the version field of ::_NV_ENC_RC_PARAMS */ +#define NV_ENC_RC_PARAMS_VER NVENCAPI_STRUCT_VERSION(1) + + + +/** + * \struct _NV_ENC_CONFIG_H264_VUI_PARAMETERS + * H264 Video Usability Info parameters + */ +typedef struct _NV_ENC_CONFIG_H264_VUI_PARAMETERS +{ + uint32_t overscanInfoPresentFlag; /**< [in]: if set to 1 , it specifies that the overscanInfo is present */ + uint32_t overscanInfo; /**< [in]: Specifies the overscan info(as defined in Annex E of the ITU-T Specification). */ + uint32_t videoSignalTypePresentFlag; /**< [in]: If set to 1, it specifies that the videoFormat, videoFullRangeFlag and colourDescriptionPresentFlag are present. */ + uint32_t videoFormat; /**< [in]: Specifies the source video format(as defined in Annex E of the ITU-T Specification).*/ + uint32_t videoFullRangeFlag; /**< [in]: Specifies the output range of the luma and chroma samples(as defined in Annex E of the ITU-T Specification). */ + uint32_t colourDescriptionPresentFlag; /**< [in]: If set to 1, it specifies that the colourPrimaries, transferCharacteristics and colourMatrix are present. */ + uint32_t colourPrimaries; /**< [in]: Specifies color primaries for converting to RGB(as defined in Annex E of the ITU-T Specification) */ + uint32_t transferCharacteristics; /**< [in]: Specifies the opto-electronic transfer characteristics to use (as defined in Annex E of the ITU-T Specification) */ + uint32_t colourMatrix; /**< [in]: Specifies the matrix coefficients used in deriving the luma and chroma from the RGB primaries (as defined in Annex E of the ITU-T Specification). */ + uint32_t chromaSampleLocationFlag; /**< [in]: if set to 1 , it specifies that the chromaSampleLocationTop and chromaSampleLocationBot are present.*/ + uint32_t chromaSampleLocationTop; /**< [in]: Specifies the chroma sample location for top field(as defined in Annex E of the ITU-T Specification) */ + uint32_t chromaSampleLocationBot; /**< [in]: Specifies the chroma sample location for bottom field(as defined in Annex E of the ITU-T Specification) */ + uint32_t bitstreamRestrictionFlag; /**< [in]: if set to 1, it specifies the bitstream restriction parameters are present in the bitstream.*/ + uint32_t reserved[15]; +}NV_ENC_CONFIG_H264_VUI_PARAMETERS; + +typedef NV_ENC_CONFIG_H264_VUI_PARAMETERS NV_ENC_CONFIG_HEVC_VUI_PARAMETERS; + +/** + * \struct _NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE + * External motion vector hint counts per block type. + * H264 supports multiple hint while HEVC supports one hint for each valid candidate. + */ +typedef struct _NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE +{ + uint32_t numCandsPerBlk16x16 : 4; /**< [in]: Supported for H264,HEVC.It Specifies the number of candidates per 16x16 block. */ + uint32_t numCandsPerBlk16x8 : 4; /**< [in]: Supported for H264 only.Specifies the number of candidates per 16x8 block. */ + uint32_t numCandsPerBlk8x16 : 4; /**< [in]: Supported for H264 only.Specifies the number of candidates per 8x16 block. */ + uint32_t numCandsPerBlk8x8 : 4; /**< [in]: Supported for H264,HEVC.Specifies the number of candidates per 8x8 block. */ + uint32_t reserved : 16; /**< [in]: Reserved for padding. */ + uint32_t reserved1[3]; /**< [in]: Reserved for future use. */ +} NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE; + + +/** + * \struct _NVENC_EXTERNAL_ME_HINT + * External Motion Vector hint structure. + */ +typedef struct _NVENC_EXTERNAL_ME_HINT +{ + int32_t mvx : 12; /**< [in]: Specifies the x component of integer pixel MV (relative to current MB) S12.0. */ + int32_t mvy : 10; /**< [in]: Specifies the y component of integer pixel MV (relative to current MB) S10.0 .*/ + int32_t refidx : 5; /**< [in]: Specifies the reference index (31=invalid). Current we support only 1 reference frame per direction for external hints, so \p refidx must be 0. */ + int32_t dir : 1; /**< [in]: Specifies the direction of motion estimation . 0=L0 1=L1.*/ + int32_t partType : 2; /**< [in]: Specifies the block partition type.0=16x16 1=16x8 2=8x16 3=8x8 (blocks in partition must be consecutive).*/ + int32_t lastofPart : 1; /**< [in]: Set to 1 for the last MV of (sub) partition */ + int32_t lastOfMB : 1; /**< [in]: Set to 1 for the last MV of macroblock. */ +} NVENC_EXTERNAL_ME_HINT; + + +/** + * \struct _NV_ENC_CONFIG_H264 + * H264 encoder configuration parameters + */ +typedef struct _NV_ENC_CONFIG_H264 +{ + uint32_t enableTemporalSVC :1; /**< [in]: Set to 1 to enable SVC temporal*/ + uint32_t enableStereoMVC :1; /**< [in]: Set to 1 to enable stereo MVC*/ + uint32_t hierarchicalPFrames :1; /**< [in]: Set to 1 to enable hierarchical PFrames */ + uint32_t hierarchicalBFrames :1; /**< [in]: Set to 1 to enable hierarchical BFrames */ + uint32_t outputBufferingPeriodSEI :1; /**< [in]: Set to 1 to write SEI buffering period syntax in the bitstream */ + uint32_t outputPictureTimingSEI :1; /**< [in]: Set to 1 to write SEI picture timing syntax in the bitstream. When set for following rateControlMode : NV_ENC_PARAMS_RC_CBR, NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ, + NV_ENC_PARAMS_RC_CBR_HQ, filler data is inserted if needed to achieve hrd bitrate */ + uint32_t outputAUD :1; /**< [in]: Set to 1 to write access unit delimiter syntax in bitstream */ + uint32_t disableSPSPPS :1; /**< [in]: Set to 1 to disable writing of Sequence and Picture parameter info in bitstream */ + uint32_t outputFramePackingSEI :1; /**< [in]: Set to 1 to enable writing of frame packing arrangement SEI messages to bitstream */ + uint32_t outputRecoveryPointSEI :1; /**< [in]: Set to 1 to enable writing of recovery point SEI message */ + uint32_t enableIntraRefresh :1; /**< [in]: Set to 1 to enable gradual decoder refresh or intra refresh. If the GOP structure uses B frames this will be ignored */ + uint32_t enableConstrainedEncoding :1; /**< [in]: Set this to 1 to enable constrainedFrame encoding where each slice in the constarined picture is independent of other slices + Check support for constrained encoding using ::NV_ENC_CAPS_SUPPORT_CONSTRAINED_ENCODING caps. */ + uint32_t repeatSPSPPS :1; /**< [in]: Set to 1 to enable writing of Sequence and Picture parameter for every IDR frame */ + uint32_t enableVFR :1; /**< [in]: Set to 1 to enable variable frame rate. */ + uint32_t enableLTR :1; /**< [in]: Set to 1 to enable LTR (Long Term Reference) frame support. LTR can be used in two modes: "LTR Trust" mode and "LTR Per Picture" mode. + LTR Trust mode: In this mode, ltrNumFrames pictures after IDR are automatically marked as LTR. This mode is enabled by setting ltrTrustMode = 1. + Use of LTR Trust mode is strongly discouraged as this mode may be deprecated in future. + LTR Per Picture mode: In this mode, client can control whether the current picture should be marked as LTR. Enable this mode by setting + ltrTrustMode = 0 and ltrMarkFrame = 1 for the picture to be marked as LTR. This is the preferred mode + for using LTR. + Note that LTRs are not supported if encoding session is configured with B-frames */ + uint32_t qpPrimeYZeroTransformBypassFlag :1; /**< [in]: To enable lossless encode set this to 1, set QP to 0 and RC_mode to NV_ENC_PARAMS_RC_CONSTQP and profile to HIGH_444_PREDICTIVE_PROFILE. + Check support for lossless encoding using ::NV_ENC_CAPS_SUPPORT_LOSSLESS_ENCODE caps. */ + uint32_t useConstrainedIntraPred :1; /**< [in]: Set 1 to enable constrained intra prediction. */ + uint32_t reservedBitFields :15; /**< [in]: Reserved bitfields and must be set to 0 */ + uint32_t level; /**< [in]: Specifies the encoding level. Client is recommended to set this to NV_ENC_LEVEL_AUTOSELECT in order to enable the NvEncodeAPI interface to select the correct level. */ + uint32_t idrPeriod; /**< [in]: Specifies the IDR interval. If not set, this is made equal to gopLength in NV_ENC_CONFIG.Low latency application client can set IDR interval to NVENC_INFINITE_GOPLENGTH so that IDR frames are not inserted automatically. */ + uint32_t separateColourPlaneFlag; /**< [in]: Set to 1 to enable 4:4:4 separate colour planes */ + uint32_t disableDeblockingFilterIDC; /**< [in]: Specifies the deblocking filter mode. Permissible value range: [0,2] */ + uint32_t numTemporalLayers; /**< [in]: Specifies max temporal layers to be used for hierarchical coding. Valid value range is [1,::NV_ENC_CAPS_NUM_MAX_TEMPORAL_LAYERS] */ + uint32_t spsId; /**< [in]: Specifies the SPS id of the sequence header */ + uint32_t ppsId; /**< [in]: Specifies the PPS id of the picture header */ + NV_ENC_H264_ADAPTIVE_TRANSFORM_MODE adaptiveTransformMode; /**< [in]: Specifies the AdaptiveTransform Mode. Check support for AdaptiveTransform mode using ::NV_ENC_CAPS_SUPPORT_ADAPTIVE_TRANSFORM caps. */ + NV_ENC_H264_FMO_MODE fmoMode; /**< [in]: Specified the FMO Mode. Check support for FMO using ::NV_ENC_CAPS_SUPPORT_FMO caps. */ + NV_ENC_H264_BDIRECT_MODE bdirectMode; /**< [in]: Specifies the BDirect mode. Check support for BDirect mode using ::NV_ENC_CAPS_SUPPORT_BDIRECT_MODE caps.*/ + NV_ENC_H264_ENTROPY_CODING_MODE entropyCodingMode; /**< [in]: Specifies the entropy coding mode. Check support for CABAC mode using ::NV_ENC_CAPS_SUPPORT_CABAC caps. */ + NV_ENC_STEREO_PACKING_MODE stereoMode; /**< [in]: Specifies the stereo frame packing mode which is to be signalled in frame packing arrangement SEI */ + uint32_t intraRefreshPeriod; /**< [in]: Specifies the interval between successive intra refresh if enableIntrarefresh is set. Requires enableIntraRefresh to be set. + Will be disabled if NV_ENC_CONFIG::gopLength is not set to NVENC_INFINITE_GOPLENGTH. */ + uint32_t intraRefreshCnt; /**< [in]: Specifies the length of intra refresh in number of frames for periodic intra refresh. This value should be smaller than intraRefreshPeriod */ + uint32_t maxNumRefFrames; /**< [in]: Specifies the DPB size used for encoding. Setting it to 0 will let driver use the default dpb size. + The low latency application which wants to invalidate reference frame as an error resilience tool + is recommended to use a large DPB size so that the encoder can keep old reference frames which can be used if recent + frames are invalidated. */ + uint32_t sliceMode; /**< [in]: This parameter in conjunction with sliceModeData specifies the way in which the picture is divided into slices + sliceMode = 0 MB based slices, sliceMode = 1 Byte based slices, sliceMode = 2 MB row based slices, sliceMode = 3, numSlices in Picture + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting + When sliceMode == 0 and sliceModeData == 0 whole picture will be coded with one slice */ + uint32_t sliceModeData; /**< [in]: Specifies the parameter needed for sliceMode. For: + sliceMode = 0, sliceModeData specifies # of MBs in each slice (except last slice) + sliceMode = 1, sliceModeData specifies maximum # of bytes in each slice (except last slice) + sliceMode = 2, sliceModeData specifies # of MB rows in each slice (except last slice) + sliceMode = 3, sliceModeData specifies number of slices in the picture. Driver will divide picture into slices optimally */ + NV_ENC_CONFIG_H264_VUI_PARAMETERS h264VUIParameters; /**< [in]: Specifies the H264 video usability info pamameters */ + uint32_t ltrNumFrames; /**< [in]: Specifies the number of LTR frames. This parameter has different meaning in two LTR modes. + In "LTR Trust" mode (ltrTrustMode = 1), encoder will mark the first ltrNumFrames base layer reference frames within each IDR interval as LTR. + In "LTR Per Picture" mode (ltrTrustMode = 0 and ltrMarkFrame = 1), ltrNumFrames specifies maximum number of LTR frames in DPB. */ + uint32_t ltrTrustMode; /**< [in]: Specifies the LTR operating mode. See comments near NV_ENC_CONFIG_H264::enableLTR for description of the two modes. + Set to 1 to use "LTR Trust" mode of LTR operation. Clients are discouraged to use "LTR Trust" mode as this mode may + be deprecated in future releases. + Set to 0 when using "LTR Per Picture" mode of LTR operation. */ + uint32_t chromaFormatIDC; /**< [in]: Specifies the chroma format. Should be set to 1 for yuv420 input, 3 for yuv444 input. + Check support for YUV444 encoding using ::NV_ENC_CAPS_SUPPORT_YUV444_ENCODE caps.*/ + uint32_t maxTemporalLayers; /**< [in]: Specifies the max temporal layer used for hierarchical coding. */ + uint32_t reserved1[270]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_H264; + + +/** + * \struct _NV_ENC_CONFIG_HEVC + * HEVC encoder configuration parameters to be set during initialization. + */ +typedef struct _NV_ENC_CONFIG_HEVC +{ + uint32_t level; /**< [in]: Specifies the level of the encoded bitstream.*/ + uint32_t tier; /**< [in]: Specifies the level tier of the encoded bitstream.*/ + NV_ENC_HEVC_CUSIZE minCUSize; /**< [in]: Specifies the minimum size of luma coding unit.*/ + NV_ENC_HEVC_CUSIZE maxCUSize; /**< [in]: Specifies the maximum size of luma coding unit. Currently NVENC SDK only supports maxCUSize equal to NV_ENC_HEVC_CUSIZE_32x32.*/ + uint32_t useConstrainedIntraPred :1; /**< [in]: Set 1 to enable constrained intra prediction. */ + uint32_t disableDeblockAcrossSliceBoundary :1; /**< [in]: Set 1 to disable in loop filtering across slice boundary.*/ + uint32_t outputBufferingPeriodSEI :1; /**< [in]: Set 1 to write SEI buffering period syntax in the bitstream */ + uint32_t outputPictureTimingSEI :1; /**< [in]: Set 1 to write SEI picture timing syntax in the bitstream */ + uint32_t outputAUD :1; /**< [in]: Set 1 to write Access Unit Delimiter syntax. */ + uint32_t enableLTR :1; /**< [in]: Set to 1 to enable LTR (Long Term Reference) frame support. LTR can be used in two modes: "LTR Trust" mode and "LTR Per Picture" mode. + LTR Trust mode: In this mode, ltrNumFrames pictures after IDR are automatically marked as LTR. This mode is enabled by setting ltrTrustMode = 1. + Use of LTR Trust mode is strongly discouraged as this mode may be deprecated in future releases. + LTR Per Picture mode: In this mode, client can control whether the current picture should be marked as LTR. Enable this mode by setting + ltrTrustMode = 0 and ltrMarkFrame = 1 for the picture to be marked as LTR. This is the preferred mode + for using LTR. + Note that LTRs are not supported if encoding session is configured with B-frames */ + uint32_t disableSPSPPS :1; /**< [in]: Set 1 to disable VPS,SPS and PPS signalling in the bitstream. */ + uint32_t repeatSPSPPS :1; /**< [in]: Set 1 to output VPS,SPS and PPS for every IDR frame.*/ + uint32_t enableIntraRefresh :1; /**< [in]: Set 1 to enable gradual decoder refresh or intra refresh. If the GOP structure uses B frames this will be ignored */ + uint32_t chromaFormatIDC :2; /**< [in]: Specifies the chroma format. Should be set to 1 for yuv420 input, 3 for yuv444 input.*/ + uint32_t pixelBitDepthMinus8 :3; /**< [in]: Specifies pixel bit depth minus 8. Should be set to 0 for 8 bit input, 2 for 10 bit input.*/ + uint32_t reserved :18; /**< [in]: Reserved bitfields.*/ + uint32_t idrPeriod; /**< [in]: Specifies the IDR interval. If not set, this is made equal to gopLength in NV_ENC_CONFIG.Low latency application client can set IDR interval to NVENC_INFINITE_GOPLENGTH so that IDR frames are not inserted automatically. */ + uint32_t intraRefreshPeriod; /**< [in]: Specifies the interval between successive intra refresh if enableIntrarefresh is set. Requires enableIntraRefresh to be set. + Will be disabled if NV_ENC_CONFIG::gopLength is not set to NVENC_INFINITE_GOPLENGTH. */ + uint32_t intraRefreshCnt; /**< [in]: Specifies the length of intra refresh in number of frames for periodic intra refresh. This value should be smaller than intraRefreshPeriod */ + uint32_t maxNumRefFramesInDPB; /**< [in]: Specifies the maximum number of references frames in the DPB.*/ + uint32_t ltrNumFrames; /**< [in]: This parameter has different meaning in two LTR modes. + In "LTR Trust" mode (ltrTrustMode = 1), encoder will mark the first ltrNumFrames base layer reference frames within each IDR interval as LTR. + In "LTR Per Picture" mode (ltrTrustMode = 0 and ltrMarkFrame = 1), ltrNumFrames specifies maximum number of LTR frames in DPB. */ + uint32_t vpsId; /**< [in]: Specifies the VPS id of the video parameter set */ + uint32_t spsId; /**< [in]: Specifies the SPS id of the sequence header */ + uint32_t ppsId; /**< [in]: Specifies the PPS id of the picture header */ + uint32_t sliceMode; /**< [in]: This parameter in conjunction with sliceModeData specifies the way in which the picture is divided into slices + sliceMode = 0 CTU based slices, sliceMode = 1 Byte based slices, sliceMode = 2 CTU row based slices, sliceMode = 3, numSlices in Picture + When sliceMode == 0 and sliceModeData == 0 whole picture will be coded with one slice */ + uint32_t sliceModeData; /**< [in]: Specifies the parameter needed for sliceMode. For: + sliceMode = 0, sliceModeData specifies # of CTUs in each slice (except last slice) + sliceMode = 1, sliceModeData specifies maximum # of bytes in each slice (except last slice) + sliceMode = 2, sliceModeData specifies # of CTU rows in each slice (except last slice) + sliceMode = 3, sliceModeData specifies number of slices in the picture. Driver will divide picture into slices optimally */ + uint32_t maxTemporalLayersMinus1; /**< [in]: Specifies the max temporal layer used for hierarchical coding. */ + NV_ENC_CONFIG_HEVC_VUI_PARAMETERS hevcVUIParameters; /**< [in]: Specifies the HEVC video usability info pamameters */ + uint32_t ltrTrustMode; /**< [in]: Specifies the LTR operating mode. See comments near NV_ENC_CONFIG_HEVC::enableLTR for description of the two modes. + Set to 1 to use "LTR Trust" mode of LTR operation. Clients are discouraged to use "LTR Trust" mode as this mode may + be deprecated in future releases. + Set to 0 when using "LTR Per Picture" mode of LTR operation. */ + uint32_t reserved1[217]; /**< [in]: Reserved and must be set to 0.*/ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_HEVC; + +/** + * \struct _NV_ENC_CONFIG_H264_MEONLY + * H264 encoder configuration parameters for ME only Mode + * + */ +typedef struct _NV_ENC_CONFIG_H264_MEONLY +{ + uint32_t disablePartition16x16 :1; /**< [in]: Disable MotionEstimation on 16x16 blocks*/ + uint32_t disablePartition8x16 :1; /**< [in]: Disable MotionEstimation on 8x16 blocks*/ + uint32_t disablePartition16x8 :1; /**< [in]: Disable MotionEstimation on 16x8 blocks*/ + uint32_t disablePartition8x8 :1; /**< [in]: Disable MotionEstimation on 8x8 blocks*/ + uint32_t disableIntraSearch :1; /**< [in]: Disable Intra search during MotionEstimation*/ + uint32_t bStereoEnable :1; /**< [in]: Enable Stereo Mode for Motion Estimation where each view is independently executed*/ + uint32_t reserved :26; /**< [in]: Reserved and must be set to 0 */ + uint32_t reserved1 [255]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_H264_MEONLY; + + +/** + * \struct _NV_ENC_CONFIG_HEVC_MEONLY + * HEVC encoder configuration parameters for ME only Mode + * + */ +typedef struct _NV_ENC_CONFIG_HEVC_MEONLY +{ + uint32_t reserved [256]; /**< [in]: Reserved and must be set to 0 */ + void* reserved1[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_HEVC_MEONLY; + +/** + * \struct _NV_ENC_CODEC_CONFIG + * Codec-specific encoder configuration parameters to be set during initialization. + */ +typedef union _NV_ENC_CODEC_CONFIG +{ + NV_ENC_CONFIG_H264 h264Config; /**< [in]: Specifies the H.264-specific encoder configuration. */ + NV_ENC_CONFIG_HEVC hevcConfig; /**< [in]: Specifies the HEVC-specific encoder configuration. */ + NV_ENC_CONFIG_H264_MEONLY h264MeOnlyConfig; /**< [in]: Specifies the H.264-specific ME only encoder configuration. */ + NV_ENC_CONFIG_HEVC_MEONLY hevcMeOnlyConfig; /**< [in]: Specifies the HEVC-specific ME only encoder configuration. */ + uint32_t reserved[320]; /**< [in]: Reserved and must be set to 0 */ +} NV_ENC_CODEC_CONFIG; + + +/** + * \struct _NV_ENC_CONFIG + * Encoder configuration parameters to be set during initialization. + */ +typedef struct _NV_ENC_CONFIG +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_CONFIG_VER. */ + GUID profileGUID; /**< [in]: Specifies the codec profile guid. If client specifies \p NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID the NvEncodeAPI interface will select the appropriate codec profile. */ + uint32_t gopLength; /**< [in]: Specifies the number of pictures in one GOP. Low latency application client can set goplength to NVENC_INFINITE_GOPLENGTH so that keyframes are not inserted automatically. */ + int32_t frameIntervalP; /**< [in]: Specifies the GOP pattern as follows: \p frameIntervalP = 0: I, 1: IPP, 2: IBP, 3: IBBP If goplength is set to NVENC_INFINITE_GOPLENGTH \p frameIntervalP should be set to 1. */ + uint32_t monoChromeEncoding; /**< [in]: Set this to 1 to enable monochrome encoding for this session. */ + NV_ENC_PARAMS_FRAME_FIELD_MODE frameFieldMode; /**< [in]: Specifies the frame/field mode. + Check support for field encoding using ::NV_ENC_CAPS_SUPPORT_FIELD_ENCODING caps. + Using a frameFieldMode other than NV_ENC_PARAMS_FRAME_FIELD_MODE_FRAME for RGB input is not supported. */ + NV_ENC_MV_PRECISION mvPrecision; /**< [in]: Specifies the desired motion vector prediction precision. */ + NV_ENC_RC_PARAMS rcParams; /**< [in]: Specifies the rate control parameters for the current encoding session. */ + NV_ENC_CODEC_CONFIG encodeCodecConfig; /**< [in]: Specifies the codec specific config parameters through this union. */ + uint32_t reserved [278]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG; + +/** macro for constructing the version field of ::_NV_ENC_CONFIG */ +#define NV_ENC_CONFIG_VER (NVENCAPI_STRUCT_VERSION(6) | ( 1<<31 )) + + +/** + * \struct _NV_ENC_INITIALIZE_PARAMS + * Encode Session Initialization parameters. + */ +typedef struct _NV_ENC_INITIALIZE_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_INITIALIZE_PARAMS_VER. */ + GUID encodeGUID; /**< [in]: Specifies the Encode GUID for which the encoder is being created. ::NvEncInitializeEncoder() API will fail if this is not set, or set to unsupported value. */ + GUID presetGUID; /**< [in]: Specifies the preset for encoding. If the preset GUID is set then , the preset configuration will be applied before any other parameter. */ + uint32_t encodeWidth; /**< [in]: Specifies the encode width. If not set ::NvEncInitializeEncoder() API will fail. */ + uint32_t encodeHeight; /**< [in]: Specifies the encode height. If not set ::NvEncInitializeEncoder() API will fail. */ + uint32_t darWidth; /**< [in]: Specifies the display aspect ratio Width. */ + uint32_t darHeight; /**< [in]: Specifies the display aspect ratio height. */ + uint32_t frameRateNum; /**< [in]: Specifies the numerator for frame rate used for encoding in frames per second ( Frame rate = frameRateNum / frameRateDen ). */ + uint32_t frameRateDen; /**< [in]: Specifies the denominator for frame rate used for encoding in frames per second ( Frame rate = frameRateNum / frameRateDen ). */ + uint32_t enableEncodeAsync; /**< [in]: Set this to 1 to enable asynchronous mode and is expected to use events to get picture completion notification. */ + uint32_t enablePTD; /**< [in]: Set this to 1 to enable the Picture Type Decision is be taken by the NvEncodeAPI interface. */ + uint32_t reportSliceOffsets :1; /**< [in]: Set this to 1 to enable reporting slice offsets in ::_NV_ENC_LOCK_BITSTREAM. NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync must be set to 0 to use this feature. Client must set this to 0 if NV_ENC_CONFIG_H264::sliceMode is 1 on Kepler GPUs */ + uint32_t enableSubFrameWrite :1; /**< [in]: Set this to 1 to write out available bitstream to memory at subframe intervals */ + uint32_t enableExternalMEHints :1; /**< [in]: Set to 1 to enable external ME hints for the current frame. For NV_ENC_INITIALIZE_PARAMS::enablePTD=1 with B frames, programming L1 hints is optional for B frames since Client doesn't know internal GOP structure. + NV_ENC_PIC_PARAMS::meHintRefPicDist should preferably be set with enablePTD=1. */ + uint32_t enableMEOnlyMode :1; /**< [in]: Set to 1 to enable ME Only Mode .*/ + uint32_t enableWeightedPrediction :1; /**< [in]: Set this to 1 to enable weighted prediction. Not supported if encode session is configured for B-Frames( 'frameIntervalP' in NV_ENC_CONFIG is greater than 1).*/ + uint32_t reservedBitFields :27; /**< [in]: Reserved bitfields and must be set to 0 */ + uint32_t privDataSize; /**< [in]: Reserved private data buffer size and must be set to 0 */ + void* privData; /**< [in]: Reserved private data buffer and must be set to NULL */ + NV_ENC_CONFIG* encodeConfig; /**< [in]: Specifies the advanced codec specific structure. If client has sent a valid codec config structure, it will override parameters set by the NV_ENC_INITIALIZE_PARAMS::presetGUID parameter. If set to NULL the NvEncodeAPI interface will use the NV_ENC_INITIALIZE_PARAMS::presetGUID to set the codec specific parameters. + Client can also optionally query the NvEncodeAPI interface to get codec specific parameters for a presetGUID using ::NvEncGetEncodePresetConfig() API. It can then modify (if required) some of the codec config parameters and send down a custom config structure as part of ::_NV_ENC_INITIALIZE_PARAMS. + Even in this case client is recommended to pass the same preset guid it has used in ::NvEncGetEncodePresetConfig() API to query the config structure; as NV_ENC_INITIALIZE_PARAMS::presetGUID. This will not override the custom config structure but will be used to determine other Encoder HW specific parameters not exposed in the API. */ + uint32_t maxEncodeWidth; /**< [in]: Maximum encode width to be used for current Encode session. + Client should allocate output buffers according to this dimension for dynamic resolution change. If set to 0, Encoder will not allow dynamic resolution change. */ + uint32_t maxEncodeHeight; /**< [in]: Maximum encode height to be allowed for current Encode session. + Client should allocate output buffers according to this dimension for dynamic resolution change. If set to 0, Encode will not allow dynamic resolution change. */ + NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE maxMEHintCountsPerBlock[2]; /**< [in]: If Client wants to pass external motion vectors in NV_ENC_PIC_PARAMS::meExternalHints buffer it must specify the maximum number of hint candidates per block per direction for the encode session. + The NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[0] is for L0 predictors and NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[1] is for L1 predictors. + This client must also set NV_ENC_INITIALIZE_PARAMS::enableExternalMEHints to 1. */ + uint32_t reserved [289]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_INITIALIZE_PARAMS; + +/** macro for constructing the version field of ::_NV_ENC_INITIALIZE_PARAMS */ +#define NV_ENC_INITIALIZE_PARAMS_VER (NVENCAPI_STRUCT_VERSION(5) | ( 1<<31 )) + + +/** + * \struct _NV_ENC_RECONFIGURE_PARAMS + * Encode Session Reconfigured parameters. + */ +typedef struct _NV_ENC_RECONFIGURE_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_RECONFIGURE_PARAMS_VER. */ + NV_ENC_INITIALIZE_PARAMS reInitEncodeParams; /**< [in]: Encoder session re-initialization parameters. */ + uint32_t resetEncoder :1; /**< [in]: This resets the rate control states and other internal encoder states. This should be used only with an IDR frame. + If NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1, encoder will force the frame type to IDR */ + uint32_t forceIDR :1; /**< [in]: Encode the current picture as an IDR picture. This flag is only valid when Picture type decision is taken by the Encoder + [_NV_ENC_INITIALIZE_PARAMS::enablePTD == 1]. */ + uint32_t reserved :30; + +}NV_ENC_RECONFIGURE_PARAMS; + +/** macro for constructing the version field of ::_NV_ENC_RECONFIGURE_PARAMS */ +#define NV_ENC_RECONFIGURE_PARAMS_VER (NVENCAPI_STRUCT_VERSION(1) | ( 1<<31 )) + +/** + * \struct _NV_ENC_PRESET_CONFIG + * Encoder preset config + */ +typedef struct _NV_ENC_PRESET_CONFIG +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_PRESET_CONFIG_VER. */ + NV_ENC_CONFIG presetCfg; /**< [out]: preset config returned by the Nvidia Video Encoder interface. */ + uint32_t reserved1[255]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +}NV_ENC_PRESET_CONFIG; + +/** macro for constructing the version field of ::_NV_ENC_PRESET_CONFIG */ +#define NV_ENC_PRESET_CONFIG_VER (NVENCAPI_STRUCT_VERSION(4) | ( 1<<31 )) + + +/** + * \struct _NV_ENC_SEI_PAYLOAD + * User SEI message + */ +typedef struct _NV_ENC_SEI_PAYLOAD +{ + uint32_t payloadSize; /**< [in] SEI payload size in bytes. SEI payload must be byte aligned, as described in Annex D */ + uint32_t payloadType; /**< [in] SEI payload types and syntax can be found in Annex D of the H.264 Specification. */ + uint8_t *payload; /**< [in] pointer to user data */ +} NV_ENC_SEI_PAYLOAD; + +#define NV_ENC_H264_SEI_PAYLOAD NV_ENC_SEI_PAYLOAD + +/** + * \struct _NV_ENC_PIC_PARAMS_H264 + * H264 specific enc pic params. sent on a per frame basis. + */ +typedef struct _NV_ENC_PIC_PARAMS_H264 +{ + uint32_t displayPOCSyntax; /**< [in]: Specifies the display POC syntax This is required to be set if client is handling the picture type decision. */ + uint32_t reserved3; /**< [in]: Reserved and must be set to 0 */ + uint32_t refPicFlag; /**< [in]: Set to 1 for a reference picture. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t colourPlaneId; /**< [in]: Specifies the colour plane ID associated with the current input. */ + uint32_t forceIntraRefreshWithFrameCnt; /**< [in]: Forces an intra refresh with duration equal to intraRefreshFrameCnt. + When outputRecoveryPointSEI is set this is value is used for recovery_frame_cnt in recovery point SEI message + forceIntraRefreshWithFrameCnt cannot be used if B frames are used in the GOP structure specified */ + uint32_t constrainedFrame :1; /**< [in]: Set to 1 if client wants to encode this frame with each slice completely independent of other slices in the frame. + NV_ENC_INITIALIZE_PARAMS::enableConstrainedEncoding should be set to 1 */ + uint32_t sliceModeDataUpdate :1; /**< [in]: Set to 1 if client wants to change the sliceModeData field to specify new sliceSize Parameter + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting */ + uint32_t ltrMarkFrame :1; /**< [in]: Set to 1 if client wants to mark this frame as LTR */ + uint32_t ltrUseFrames :1; /**< [in]: Set to 1 if client allows encoding this frame using the LTR frames specified in ltrFrameBitmap */ + uint32_t reservedBitFields :28; /**< [in]: Reserved bit fields and must be set to 0 */ + uint8_t* sliceTypeData; /**< [in]: Deprecated. */ + uint32_t sliceTypeArrayCnt; /**< [in]: Deprecated. */ + uint32_t seiPayloadArrayCnt; /**< [in]: Specifies the number of elements allocated in seiPayloadArray array. */ + NV_ENC_SEI_PAYLOAD* seiPayloadArray; /**< [in]: Array of SEI payloads which will be inserted for this frame. */ + uint32_t sliceMode; /**< [in]: This parameter in conjunction with sliceModeData specifies the way in which the picture is divided into slices + sliceMode = 0 MB based slices, sliceMode = 1 Byte based slices, sliceMode = 2 MB row based slices, sliceMode = 3, numSlices in Picture + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting + When sliceMode == 0 and sliceModeData == 0 whole picture will be coded with one slice */ + uint32_t sliceModeData; /**< [in]: Specifies the parameter needed for sliceMode. For: + sliceMode = 0, sliceModeData specifies # of MBs in each slice (except last slice) + sliceMode = 1, sliceModeData specifies maximum # of bytes in each slice (except last slice) + sliceMode = 2, sliceModeData specifies # of MB rows in each slice (except last slice) + sliceMode = 3, sliceModeData specifies number of slices in the picture. Driver will divide picture into slices optimally */ + uint32_t ltrMarkFrameIdx; /**< [in]: Specifies the long term referenceframe index to use for marking this frame as LTR.*/ + uint32_t ltrUseFrameBitmap; /**< [in]: Specifies the the associated bitmap of LTR frame indices to use when encoding this frame. */ + uint32_t ltrUsageMode; /**< [in]: Not supported. Reserved for future use and must be set to 0. */ + uint32_t reserved [243]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[62]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_PIC_PARAMS_H264; + +/** + * \struct _NV_ENC_PIC_PARAMS_HEVC + * HEVC specific enc pic params. sent on a per frame basis. + */ +typedef struct _NV_ENC_PIC_PARAMS_HEVC +{ + uint32_t displayPOCSyntax; /**< [in]: Specifies the display POC syntax This is required to be set if client is handling the picture type decision. */ + uint32_t refPicFlag; /**< [in]: Set to 1 for a reference picture. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t temporalId; /**< [in]: Specifies the temporal id of the picture */ + uint32_t forceIntraRefreshWithFrameCnt; /**< [in]: Forces an intra refresh with duration equal to intraRefreshFrameCnt. + When outputRecoveryPointSEI is set this is value is used for recovery_frame_cnt in recovery point SEI message + forceIntraRefreshWithFrameCnt cannot be used if B frames are used in the GOP structure specified */ + uint32_t constrainedFrame :1; /**< [in]: Set to 1 if client wants to encode this frame with each slice completely independent of other slices in the frame. + NV_ENC_INITIALIZE_PARAMS::enableConstrainedEncoding should be set to 1 */ + uint32_t sliceModeDataUpdate :1; /**< [in]: Set to 1 if client wants to change the sliceModeData field to specify new sliceSize Parameter + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting */ + uint32_t ltrMarkFrame :1; /**< [in]: Set to 1 if client wants to mark this frame as LTR */ + uint32_t ltrUseFrames :1; /**< [in]: Set to 1 if client allows encoding this frame using the LTR frames specified in ltrFrameBitmap */ + uint32_t reservedBitFields :28; /**< [in]: Reserved bit fields and must be set to 0 */ + uint8_t* sliceTypeData; /**< [in]: Array which specifies the slice type used to force intra slice for a particular slice. Currently supported only for NV_ENC_CONFIG_H264::sliceMode == 3. + Client should allocate array of size sliceModeData where sliceModeData is specified in field of ::_NV_ENC_CONFIG_H264 + Array element with index n corresponds to nth slice. To force a particular slice to intra client should set corresponding array element to NV_ENC_SLICE_TYPE_I + all other array elements should be set to NV_ENC_SLICE_TYPE_DEFAULT */ + uint32_t sliceTypeArrayCnt; /**< [in]: Client should set this to the number of elements allocated in sliceTypeData array. If sliceTypeData is NULL then this should be set to 0 */ + uint32_t sliceMode; /**< [in]: This parameter in conjunction with sliceModeData specifies the way in which the picture is divided into slices + sliceMode = 0 CTU based slices, sliceMode = 1 Byte based slices, sliceMode = 2 CTU row based slices, sliceMode = 3, numSlices in Picture + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting + When sliceMode == 0 and sliceModeData == 0 whole picture will be coded with one slice */ + uint32_t sliceModeData; /**< [in]: Specifies the parameter needed for sliceMode. For: + sliceMode = 0, sliceModeData specifies # of CTUs in each slice (except last slice) + sliceMode = 1, sliceModeData specifies maximum # of bytes in each slice (except last slice) + sliceMode = 2, sliceModeData specifies # of CTU rows in each slice (except last slice) + sliceMode = 3, sliceModeData specifies number of slices in the picture. Driver will divide picture into slices optimally */ + uint32_t ltrMarkFrameIdx; /**< [in]: Specifies the long term reference frame index to use for marking this frame as LTR.*/ + uint32_t ltrUseFrameBitmap; /**< [in]: Specifies the associated bitmap of LTR frame indices to use when encoding this frame. */ + uint32_t ltrUsageMode; /**< [in]: Not supported. Reserved for future use and must be set to 0. */ + uint32_t seiPayloadArrayCnt; /**< [in]: Specifies the number of elements allocated in seiPayloadArray array. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0. */ + NV_ENC_SEI_PAYLOAD* seiPayloadArray; /**< [in]: Array of SEI payloads which will be inserted for this frame. */ + uint32_t reserved2 [244]; /**< [in]: Reserved and must be set to 0. */ + void* reserved3[61]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_PIC_PARAMS_HEVC; + + +/** + * Codec specific per-picture encoding parameters. + */ +typedef union _NV_ENC_CODEC_PIC_PARAMS +{ + NV_ENC_PIC_PARAMS_H264 h264PicParams; /**< [in]: H264 encode picture params. */ + NV_ENC_PIC_PARAMS_HEVC hevcPicParams; /**< [in]: HEVC encode picture params. */ + uint32_t reserved[256]; /**< [in]: Reserved and must be set to 0. */ +} NV_ENC_CODEC_PIC_PARAMS; + +/** + * \struct _NV_ENC_PIC_PARAMS + * Encoding parameters that need to be sent on a per frame basis. + */ +typedef struct _NV_ENC_PIC_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_PIC_PARAMS_VER. */ + uint32_t inputWidth; /**< [in]: Specifies the input buffer width */ + uint32_t inputHeight; /**< [in]: Specifies the input buffer height */ + uint32_t inputPitch; /**< [in]: Specifies the input buffer pitch. If pitch value is not known, set this to inputWidth. */ + uint32_t encodePicFlags; /**< [in]: Specifies bit-wise OR`ed encode pic flags. See ::NV_ENC_PIC_FLAGS enum. */ + uint32_t frameIdx; /**< [in]: Specifies the frame index associated with the input frame [optional]. */ + uint64_t inputTimeStamp; /**< [in]: Specifies presentation timestamp associated with the input picture. */ + uint64_t inputDuration; /**< [in]: Specifies duration of the input picture */ + NV_ENC_INPUT_PTR inputBuffer; /**< [in]: Specifies the input buffer pointer. Client must use a pointer obtained from ::NvEncCreateInputBuffer() or ::NvEncMapInputResource() APIs.*/ + NV_ENC_OUTPUT_PTR outputBitstream; /**< [in]: Specifies the pointer to output buffer. Client should use a pointer obtained from ::NvEncCreateBitstreamBuffer() API. */ + void* completionEvent; /**< [in]: Specifies an event to be signalled on completion of encoding of this Frame [only if operating in Asynchronous mode]. Each output buffer should be associated with a distinct event pointer. */ + NV_ENC_BUFFER_FORMAT bufferFmt; /**< [in]: Specifies the input buffer format. */ + NV_ENC_PIC_STRUCT pictureStruct; /**< [in]: Specifies structure of the input picture. */ + NV_ENC_PIC_TYPE pictureType; /**< [in]: Specifies input picture type. Client required to be set explicitly by the client if the client has not set NV_ENC_INITALIZE_PARAMS::enablePTD to 1 while calling NvInitializeEncoder. */ + NV_ENC_CODEC_PIC_PARAMS codecPicParams; /**< [in]: Specifies the codec specific per-picture encoding parameters. */ + NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE meHintCountsPerBlock[2]; /**< [in]: Specifies the number of hint candidates per block per direction for the current frame. meHintCountsPerBlock[0] is for L0 predictors and meHintCountsPerBlock[1] is for L1 predictors. + The candidate count in NV_ENC_PIC_PARAMS::meHintCountsPerBlock[lx] must never exceed NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[lx] provided during encoder intialization. */ + NVENC_EXTERNAL_ME_HINT *meExternalHints; /**< [in]: Specifies the pointer to ME external hints for the current frame. The size of ME hint buffer should be equal to number of macroblocks * the total number of candidates per macroblock. + The total number of candidates per MB per direction = 1*meHintCountsPerBlock[Lx].numCandsPerBlk16x16 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk16x8 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk8x8 + + 4*meHintCountsPerBlock[Lx].numCandsPerBlk8x8. For frames using bidirectional ME , the total number of candidates for single macroblock is sum of total number of candidates per MB for each direction (L0 and L1) */ + uint32_t reserved1[6]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[2]; /**< [in]: Reserved and must be set to NULL */ + int8_t *qpDeltaMap; /**< [in]: Specifies the pointer to signed byte array containing QP delta value per MB in raster scan order in the current picture. This QP modifier is applied on top of the QP chosen by rate control. */ + uint32_t qpDeltaMapSize; /**< [in]: Specifies the size in bytes of qpDeltaMap surface allocated by client and pointed to by NV_ENC_PIC_PARAMS::qpDeltaMap. Surface (array) should be picWidthInMbs * picHeightInMbs */ + uint32_t reservedBitFields; /**< [in]: Reserved bitfields and must be set to 0 */ + uint16_t meHintRefPicDist[2]; /**< [in]: Specifies temporal distance for reference picture (NVENC_EXTERNAL_ME_HINT::refidx = 0) used during external ME with NV_ENC_INITALIZE_PARAMS::enablePTD = 1 . meHintRefPicDist[0] is for L0 hints and meHintRefPicDist[1] is for L1 hints. + If not set, will internally infer distance of 1. Ignored for NV_ENC_INITALIZE_PARAMS::enablePTD = 0 */ + uint32_t reserved3[286]; /**< [in]: Reserved and must be set to 0 */ + void* reserved4[60]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_PIC_PARAMS; + +/** Macro for constructing the version field of ::_NV_ENC_PIC_PARAMS */ +#define NV_ENC_PIC_PARAMS_VER (NVENCAPI_STRUCT_VERSION(4) | ( 1<<31 )) + + +/** + * \struct _NV_ENC_MEONLY_PARAMS + * MEOnly parameters that need to be sent on a per motion estimation basis. + * NV_ENC_MEONLY_PARAMS::meExternalHints is supported for H264 only. + */ +typedef struct _NV_ENC_MEONLY_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to NV_ENC_MEONLY_PARAMS_VER.*/ + uint32_t inputWidth; /**< [in]: Specifies the input buffer width */ + uint32_t inputHeight; /**< [in]: Specifies the input buffer height */ + NV_ENC_INPUT_PTR inputBuffer; /**< [in]: Specifies the input buffer pointer. Client must use a pointer obtained from NvEncCreateInputBuffer() or NvEncMapInputResource() APIs. */ + NV_ENC_INPUT_PTR referenceFrame; /**< [in]: Specifies the reference frame pointer */ + NV_ENC_OUTPUT_PTR mvBuffer; /**< [in]: Specifies the pointer to motion vector data buffer allocated by NvEncCreateMVBuffer. Client must lock mvBuffer using ::NvEncLockBitstream() API to get the motion vector data. */ + NV_ENC_BUFFER_FORMAT bufferFmt; /**< [in]: Specifies the input buffer format. */ + void* completionEvent; /**< [in]: Specifies an event to be signalled on completion of motion estimation + of this Frame [only if operating in Asynchronous mode]. + Each output buffer should be associated with a distinct event pointer. */ + uint32_t viewID; /**< [in]: Specifies left,right viewID if NV_ENC_CONFIG_H264_MEONLY::bStereoEnable is set. + viewID can be 0,1 if bStereoEnable is set, 0 otherwise. */ + NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE + meHintCountsPerBlock[2]; /**< [in]: Specifies the number of hint candidates per block for the current frame. meHintCountsPerBlock[0] is for L0 predictors. + The candidate count in NV_ENC_PIC_PARAMS::meHintCountsPerBlock[lx] must never exceed NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[lx] provided during encoder intialization. */ + NVENC_EXTERNAL_ME_HINT *meExternalHints; /**< [in]: Specifies the pointer to ME external hints for the current frame. The size of ME hint buffer should be equal to number of macroblocks * the total number of candidates per macroblock. + The total number of candidates per MB per direction = 1*meHintCountsPerBlock[Lx].numCandsPerBlk16x16 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk16x8 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk8x8 + + 4*meHintCountsPerBlock[Lx].numCandsPerBlk8x8. For frames using bidirectional ME , the total number of candidates for single macroblock is sum of total number of candidates per MB for each direction (L0 and L1) */ + uint32_t reserved1[243]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[59]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_MEONLY_PARAMS; + +/** NV_ENC_MEONLY_PARAMS struct version*/ +#define NV_ENC_MEONLY_PARAMS_VER NVENCAPI_STRUCT_VERSION(3) + + +/** + * \struct _NV_ENC_LOCK_BITSTREAM + * Bitstream buffer lock parameters. + */ +typedef struct _NV_ENC_LOCK_BITSTREAM +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_LOCK_BITSTREAM_VER. */ + uint32_t doNotWait :1; /**< [in]: If this flag is set, the NvEncodeAPI interface will return buffer pointer even if operation is not completed. If not set, the call will block until operation completes. */ + uint32_t ltrFrame :1; /**< [out]: Flag indicating this frame is marked as LTR frame */ + uint32_t reservedBitFields :30; /**< [in]: Reserved bit fields and must be set to 0 */ + void* outputBitstream; /**< [in]: Pointer to the bitstream buffer being locked. */ + uint32_t* sliceOffsets; /**< [in,out]: Array which receives the slice offsets. This is not supported if NV_ENC_CONFIG_H264::sliceMode is 1 on Kepler GPUs. Array size must be equal to size of frame in MBs. */ + uint32_t frameIdx; /**< [out]: Frame no. for which the bitstream is being retrieved. */ + uint32_t hwEncodeStatus; /**< [out]: The NvEncodeAPI interface status for the locked picture. */ + uint32_t numSlices; /**< [out]: Number of slices in the encoded picture. Will be reported only if NV_ENC_INITIALIZE_PARAMS::reportSliceOffsets set to 1. */ + uint32_t bitstreamSizeInBytes; /**< [out]: Actual number of bytes generated and copied to the memory pointed by bitstreamBufferPtr. */ + uint64_t outputTimeStamp; /**< [out]: Presentation timestamp associated with the encoded output. */ + uint64_t outputDuration; /**< [out]: Presentation duration associates with the encoded output. */ + void* bitstreamBufferPtr; /**< [out]: Pointer to the generated output bitstream. + For MEOnly mode _NV_ENC_LOCK_BITSTREAM::bitstreamBufferPtr should be typecast to + NV_ENC_H264_MV_DATA/NV_ENC_HEVC_MV_DATA pointer respectively for H264/HEVC */ + NV_ENC_PIC_TYPE pictureType; /**< [out]: Picture type of the encoded picture. */ + NV_ENC_PIC_STRUCT pictureStruct; /**< [out]: Structure of the generated output picture. */ + uint32_t frameAvgQP; /**< [out]: Average QP of the frame. */ + uint32_t frameSatd; /**< [out]: Total SATD cost for whole frame. */ + uint32_t ltrFrameIdx; /**< [out]: Frame index associated with this LTR frame. */ + uint32_t ltrFrameBitmap; /**< [out]: Bitmap of LTR frames indices which were used for encoding this frame. Value of 0 if no LTR frames were used. */ + uint32_t reserved [236]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_LOCK_BITSTREAM; + +/** Macro for constructing the version field of ::_NV_ENC_LOCK_BITSTREAM */ +#define NV_ENC_LOCK_BITSTREAM_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * \struct _NV_ENC_LOCK_INPUT_BUFFER + * Uncompressed Input Buffer lock parameters. + */ +typedef struct _NV_ENC_LOCK_INPUT_BUFFER +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_LOCK_INPUT_BUFFER_VER. */ + uint32_t doNotWait :1; /**< [in]: Set to 1 to make ::NvEncLockInputBuffer() a unblocking call. If the encoding is not completed, driver will return ::NV_ENC_ERR_ENCODER_BUSY error code. */ + uint32_t reservedBitFields :31; /**< [in]: Reserved bitfields and must be set to 0 */ + NV_ENC_INPUT_PTR inputBuffer; /**< [in]: Pointer to the input buffer to be locked, client should pass the pointer obtained from ::NvEncCreateInputBuffer() or ::NvEncMapInputResource API. */ + void* bufferDataPtr; /**< [out]: Pointed to the locked input buffer data. Client can only access input buffer using the \p bufferDataPtr. */ + uint32_t pitch; /**< [out]: Pitch of the locked input buffer. */ + uint32_t reserved1[251]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_LOCK_INPUT_BUFFER; + +/** Macro for constructing the version field of ::_NV_ENC_LOCK_INPUT_BUFFER */ +#define NV_ENC_LOCK_INPUT_BUFFER_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * \struct _NV_ENC_MAP_INPUT_RESOURCE + * Map an input resource to a Nvidia Encoder Input Buffer + */ +typedef struct _NV_ENC_MAP_INPUT_RESOURCE +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_MAP_INPUT_RESOURCE_VER. */ + uint32_t subResourceIndex; /**< [in]: Deprecated. Do not use. */ + void* inputResource; /**< [in]: Deprecated. Do not use. */ + NV_ENC_REGISTERED_PTR registeredResource; /**< [in]: The Registered resource handle obtained by calling NvEncRegisterInputResource. */ + NV_ENC_INPUT_PTR mappedResource; /**< [out]: Mapped pointer corresponding to the registeredResource. This pointer must be used in NV_ENC_PIC_PARAMS::inputBuffer parameter in ::NvEncEncodePicture() API. */ + NV_ENC_BUFFER_FORMAT mappedBufferFmt; /**< [out]: Buffer format of the outputResource. This buffer format must be used in NV_ENC_PIC_PARAMS::bufferFmt if client using the above mapped resource pointer. */ + uint32_t reserved1[251]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[63]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_MAP_INPUT_RESOURCE; + +/** Macro for constructing the version field of ::_NV_ENC_MAP_INPUT_RESOURCE */ +#define NV_ENC_MAP_INPUT_RESOURCE_VER NVENCAPI_STRUCT_VERSION(4) + +/** + * \struct _NV_ENC_INPUT_RESOURCE_OPENGL_TEX + * NV_ENC_REGISTER_RESOURCE::resourceToRegister must be a pointer to a variable of this type, + * when NV_ENC_REGISTER_RESOURCE::resourceType is NV_ENC_INPUT_RESOURCE_TYPE_OPENGL_TEX + */ +typedef struct _NV_ENC_INPUT_RESOURCE_OPENGL_TEX +{ + uint32_t texture; /**< [in]: The name of the texture to be used. */ + uint32_t target; /**< [in]: Accepted values are GL_TEXTURE_RECTANGLE and GL_TEXTURE_2D. */ +} NV_ENC_INPUT_RESOURCE_OPENGL_TEX; + +/** + * \struct _NV_ENC_REGISTER_RESOURCE + * Register a resource for future use with the Nvidia Video Encoder Interface. + */ +typedef struct _NV_ENC_REGISTER_RESOURCE +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_REGISTER_RESOURCE_VER. */ + NV_ENC_INPUT_RESOURCE_TYPE resourceType; /**< [in]: Specifies the type of resource to be registered. + Supported values are + ::NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX, + ::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR, + ::NV_ENC_INPUT_RESOURCE_TYPE_OPENGL_TEX */ + uint32_t width; /**< [in]: Input buffer Width. */ + uint32_t height; /**< [in]: Input buffer Height. */ + uint32_t pitch; /**< [in]: Input buffer Pitch. */ + uint32_t subResourceIndex; /**< [in]: Subresource Index of the DirectX resource to be registered. Should be set to 0 for other interfaces. */ + void* resourceToRegister; /**< [in]: Handle to the resource that is being registered. */ + NV_ENC_REGISTERED_PTR registeredResource; /**< [out]: Registered resource handle. This should be used in future interactions with the Nvidia Video Encoder Interface. */ + NV_ENC_BUFFER_FORMAT bufferFormat; /**< [in]: Buffer format of resource to be registered. */ + uint32_t reserved1[248]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[62]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_REGISTER_RESOURCE; + +/** Macro for constructing the version field of ::_NV_ENC_REGISTER_RESOURCE */ +#define NV_ENC_REGISTER_RESOURCE_VER NVENCAPI_STRUCT_VERSION(3) + +/** + * \struct _NV_ENC_STAT + * Encode Stats structure. + */ +typedef struct _NV_ENC_STAT +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_STAT_VER. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0 */ + NV_ENC_OUTPUT_PTR outputBitStream; /**< [out]: Specifies the pointer to output bitstream. */ + uint32_t bitStreamSize; /**< [out]: Size of generated bitstream in bytes. */ + uint32_t picType; /**< [out]: Picture type of encoded picture. See ::NV_ENC_PIC_TYPE. */ + uint32_t lastValidByteOffset; /**< [out]: Offset of last valid bytes of completed bitstream */ + uint32_t sliceOffsets[16]; /**< [out]: Offsets of each slice */ + uint32_t picIdx; /**< [out]: Picture number */ + uint32_t reserved1[233]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_STAT; + +/** Macro for constructing the version field of ::_NV_ENC_STAT */ +#define NV_ENC_STAT_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * \struct _NV_ENC_SEQUENCE_PARAM_PAYLOAD + * Sequence and picture paramaters payload. + */ +typedef struct _NV_ENC_SEQUENCE_PARAM_PAYLOAD +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_INITIALIZE_PARAMS_VER. */ + uint32_t inBufferSize; /**< [in]: Specifies the size of the spsppsBuffer provied by the client */ + uint32_t spsId; /**< [in]: Specifies the SPS id to be used in sequence header. Default value is 0. */ + uint32_t ppsId; /**< [in]: Specifies the PPS id to be used in picture header. Default value is 0. */ + void* spsppsBuffer; /**< [in]: Specifies bitstream header pointer of size NV_ENC_SEQUENCE_PARAM_PAYLOAD::inBufferSize. It is the client's responsibility to manage this memory. */ + uint32_t* outSPSPPSPayloadSize; /**< [out]: Size of the sequence and picture header in bytes written by the NvEncodeAPI interface to the SPSPPSBuffer. */ + uint32_t reserved [250]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_SEQUENCE_PARAM_PAYLOAD; + +/** Macro for constructing the version field of ::_NV_ENC_SEQUENCE_PARAM_PAYLOAD */ +#define NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * Event registration/unregistration parameters. + */ +typedef struct _NV_ENC_EVENT_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_EVENT_PARAMS_VER. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0 */ + void* completionEvent; /**< [in]: Handle to event to be registered/unregistered with the NvEncodeAPI interface. */ + uint32_t reserved1[253]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_EVENT_PARAMS; + +/** Macro for constructing the version field of ::_NV_ENC_EVENT_PARAMS */ +#define NV_ENC_EVENT_PARAMS_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * Encoder Session Creation parameters + */ +typedef struct _NV_ENC_OPEN_ENCODE_SESSIONEX_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER. */ + NV_ENC_DEVICE_TYPE deviceType; /**< [in]: Specified the device Type */ + void* device; /**< [in]: Pointer to client device. */ + void* reserved; /**< [in]: Reserved and must be set to 0. */ + uint32_t apiVersion; /**< [in]: API version. Should be set to NVENCAPI_VERSION. */ + uint32_t reserved1[253]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS; +/** Macro for constructing the version field of ::_NV_ENC_OPEN_ENCODE_SESSIONEX_PARAMS */ +#define NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER NVENCAPI_STRUCT_VERSION(1) + +/** @} */ /* END ENCODER_STRUCTURE */ + + +/** + * \addtogroup ENCODE_FUNC NvEncodeAPI Functions + * @{ + */ + +// NvEncOpenEncodeSession +/** + * \brief Opens an encoding session. + * + * Deprecated. + * + * \return + * ::NV_ENC_ERR_INVALID_CALL\n + * + */ +NVENCSTATUS NVENCAPI NvEncOpenEncodeSession (void* device, uint32_t deviceType, void** encoder); + +// NvEncGetEncodeGuidCount +/** + * \brief Retrieves the number of supported encode GUIDs. + * + * The function returns the number of codec guids supported by the NvEncodeAPI + * interface. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [out] encodeGUIDCount + * Number of supported encode GUIDs. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeGUIDCount (void* encoder, uint32_t* encodeGUIDCount); + + +// NvEncGetEncodeGUIDs +/** + * \brief Retrieves an array of supported encoder codec GUIDs. + * + * The function returns an array of codec guids supported by the NvEncodeAPI interface. + * The client must allocate an array where the NvEncodeAPI interface can + * fill the supported guids and pass the pointer in \p *GUIDs parameter. + * The size of the array can be determined by using ::NvEncGetEncodeGUIDCount() API. + * The Nvidia Encoding interface returns the number of codec guids it has actually + * filled in the guid array in the \p GUIDCount parameter. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] guidArraySize + * Number of GUIDs to retrieved. Should be set to the number retrieved using + * ::NvEncGetEncodeGUIDCount. + * \param [out] GUIDs + * Array of supported Encode GUIDs. + * \param [out] GUIDCount + * Number of supported Encode GUIDs. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeGUIDs (void* encoder, GUID* GUIDs, uint32_t guidArraySize, uint32_t* GUIDCount); + + +// NvEncGetEncodeProfileGuidCount +/** + * \brief Retrieves the number of supported profile GUIDs. + * + * The function returns the number of profile GUIDs supported for a given codec. + * The client must first enumerate the codec guids supported by the NvEncodeAPI + * interface. After determining the codec guid, it can query the NvEncodeAPI + * interface to determine the number of profile guids supported for a particular + * codec guid. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * The codec guid for which the profile guids are being enumerated. + * \param [out] encodeProfileGUIDCount + * Number of encode profiles supported for the given encodeGUID. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeProfileGUIDCount (void* encoder, GUID encodeGUID, uint32_t* encodeProfileGUIDCount); + + +// NvEncGetEncodeProfileGUIDs +/** + * \brief Retrieves an array of supported encode profile GUIDs. + * + * The function returns an array of supported profile guids for a particular + * codec guid. The client must allocate an array where the NvEncodeAPI interface + * can populate the profile guids. The client can determine the array size using + * ::NvEncGetEncodeProfileGUIDCount() API. The client must also validiate that the + * NvEncodeAPI interface supports the GUID the client wants to pass as \p encodeGUID + * parameter. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * The encode guid whose profile guids are being enumerated. + * \param [in] guidArraySize + * Number of GUIDs to be retrieved. Should be set to the number retrieved using + * ::NvEncGetEncodeProfileGUIDCount. + * \param [out] profileGUIDs + * Array of supported Encode Profile GUIDs + * \param [out] GUIDCount + * Number of valid encode profile GUIDs in \p profileGUIDs array. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeProfileGUIDs (void* encoder, GUID encodeGUID, GUID* profileGUIDs, uint32_t guidArraySize, uint32_t* GUIDCount); + +// NvEncGetInputFormatCount +/** + * \brief Retrieve the number of supported Input formats. + * + * The function returns the number of supported input formats. The client must + * query the NvEncodeAPI interface to determine the supported input formats + * before creating the input surfaces. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the number of supported input formats + * is to be retrieved. + * \param [out] inputFmtCount + * Number of input formats supported for specified Encode GUID. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncGetInputFormatCount (void* encoder, GUID encodeGUID, uint32_t* inputFmtCount); + + +// NvEncGetInputFormats +/** + * \brief Retrieves an array of supported Input formats + * + * Returns an array of supported input formats The client must use the input + * format to create input surface using ::NvEncCreateInputBuffer() API. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the number of supported input formats + * is to be retrieved. + *\param [in] inputFmtArraySize + * Size input format count array passed in \p inputFmts. + *\param [out] inputFmts + * Array of input formats supported for this Encode GUID. + *\param [out] inputFmtCount + * The number of valid input format types returned by the NvEncodeAPI + * interface in \p inputFmts array. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetInputFormats (void* encoder, GUID encodeGUID, NV_ENC_BUFFER_FORMAT* inputFmts, uint32_t inputFmtArraySize, uint32_t* inputFmtCount); + + +// NvEncGetEncodeCaps +/** + * \brief Retrieves the capability value for a specified encoder attribute. + * + * The function returns the capability value for a given encoder attribute. The + * client must validate the encodeGUID using ::NvEncGetEncodeGUIDs() API before + * calling this function. The encoder attribute being queried are enumerated in + * ::NV_ENC_CAPS_PARAM enum. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the capability attribute is to be retrieved. + * \param [in] capsParam + * Used to specify attribute being queried. Refer ::NV_ENC_CAPS_PARAM for more + * details. + * \param [out] capsVal + * The value corresponding to the capability attribute being queried. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeCaps (void* encoder, GUID encodeGUID, NV_ENC_CAPS_PARAM* capsParam, int* capsVal); + + +// NvEncGetEncodePresetCount +/** + * \brief Retrieves the number of supported preset GUIDs. + * + * The function returns the number of preset GUIDs available for a given codec. + * The client must validate the codec guid using ::NvEncGetEncodeGUIDs() API + * before calling this function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the number of supported presets is to + * be retrieved. + * \param [out] encodePresetGUIDCount + * Receives the number of supported preset GUIDs. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodePresetCount (void* encoder, GUID encodeGUID, uint32_t* encodePresetGUIDCount); + + +// NvEncGetEncodePresetGUIDs +/** + * \brief Receives an array of supported encoder preset GUIDs. + * + * The function returns an array of encode preset guids available for a given codec. + * The client can directly use one of the preset guids based upon the use case + * or target device. The preset guid chosen can be directly used in + * NV_ENC_INITIALIZE_PARAMS::presetGUID parameter to ::NvEncEncodePicture() API. + * Alternately client can also use the preset guid to retrieve the encoding config + * parameters being used by NvEncodeAPI interface for that given preset, using + * ::NvEncGetEncodePresetConfig() API. It can then modify preset config parameters + * as per its use case and send it to NvEncodeAPI interface as part of + * NV_ENC_INITIALIZE_PARAMS::encodeConfig parameter for NvEncInitializeEncoder() + * API. + * + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the list of supported presets is to be + * retrieved. + * \param [in] guidArraySize + * Size of array of preset guids passed in \p preset GUIDs + * \param [out] presetGUIDs + * Array of supported Encode preset GUIDs from the NvEncodeAPI interface + * to client. + * \param [out] encodePresetGUIDCount + * Receives the number of preset GUIDs returned by the NvEncodeAPI + * interface. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodePresetGUIDs (void* encoder, GUID encodeGUID, GUID* presetGUIDs, uint32_t guidArraySize, uint32_t* encodePresetGUIDCount); + + +// NvEncGetEncodePresetConfig +/** + * \brief Returns a preset config structure supported for given preset GUID. + * + * The function returns a preset config structure for a given preset guid. Before + * using this function the client must enumerate the preset guids available for + * a given codec. The preset config structure can be modified by the client depending + * upon its use case and can be then used to initialize the encoder using + * ::NvEncInitializeEncoder() API. The client can use this function only if it + * wants to modify the NvEncodeAPI preset configuration, otherwise it can + * directly use the preset guid. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the list of supported presets is to be + * retrieved. + * \param [in] presetGUID + * Preset GUID, corresponding to which the Encoding configurations is to be + * retrieved. + * \param [out] presetConfig + * The requested Preset Encoder Attribute set. Refer ::_NV_ENC_CONFIG for +* more details. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodePresetConfig (void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_PRESET_CONFIG* presetConfig); + +// NvEncInitializeEncoder +/** + * \brief Initialize the encoder. + * + * This API must be used to initialize the encoder. The initialization parameter + * is passed using \p *createEncodeParams The client must send the following + * fields of the _NV_ENC_INITIALIZE_PARAMS structure with a valid value. + * - NV_ENC_INITIALIZE_PARAMS::encodeGUID + * - NV_ENC_INITIALIZE_PARAMS::encodeWidth + * - NV_ENC_INITIALIZE_PARAMS::encodeHeight + * + * The client can pass a preset guid directly to the NvEncodeAPI interface using + * NV_ENC_INITIALIZE_PARAMS::presetGUID field. If the client doesn't pass + * NV_ENC_INITIALIZE_PARAMS::encodeConfig structure, the codec specific parameters + * will be selected based on the preset guid. The preset guid must have been + * validated by the client using ::NvEncGetEncodePresetGUIDs() API. + * If the client passes a custom ::_NV_ENC_CONFIG structure through + * NV_ENC_INITIALIZE_PARAMS::encodeConfig , it will override the codec specific parameters + * based on the preset guid. It is recommended that even if the client passes a custom config, + * it should also send a preset guid. In this case, the preset guid passed by the client + * will not override any of the custom config parameters programmed by the client, + * it is only used as a hint by the NvEncodeAPI interface to determine certain encoder parameters + * which are not exposed to the client. + * + * There are two modes of operation for the encoder namely: + * - Asynchronous mode + * - Synchronous mode + * + * The client can select asynchronous or synchronous mode by setting the \p + * enableEncodeAsync field in ::_NV_ENC_INITIALIZE_PARAMS to 1 or 0 respectively. + *\par Asynchronous mode of operation: + * The Asynchronous mode can be enabled by setting NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync to 1. + * The client operating in asynchronous mode must allocate completion event object + * for each output buffer and pass the completion event object in the + * ::NvEncEncodePicture() API. The client can create another thread and wait on + * the event object to be signalled by NvEncodeAPI interface on completion of the + * encoding process for the output frame. This should unblock the main thread from + * submitting work to the encoder. When the event is signalled the client can call + * NvEncodeAPI interfaces to copy the bitstream data using ::NvEncLockBitstream() + * API. This is the preferred mode of operation. + * + * NOTE: Asynchronous mode is not supported on Linux. + * + *\par Synchronous mode of operation: + * The client can select synchronous mode by setting NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync to 0. + * The client working in synchronous mode can work in a single threaded or multi + * threaded mode. The client need not allocate any event objects. The client can + * only lock the bitstream data after NvEncodeAPI interface has returned + * ::NV_ENC_SUCCESS from encode picture. The NvEncodeAPI interface can return + * ::NV_ENC_ERR_NEED_MORE_INPUT error code from ::NvEncEncodePicture() API. The + * client must not lock the output buffer in such case but should send the next + * frame for encoding. The client must keep on calling ::NvEncEncodePicture() API + * until it returns ::NV_ENC_SUCCESS. \n + * The client must always lock the bitstream data in order in which it has submitted. + * This is true for both asynchronous and synchronous mode. + * + *\par Picture type decision: + * If the client is taking the picture type decision and it must disable the picture + * type decision module in NvEncodeAPI by setting NV_ENC_INITIALIZE_PARAMS::enablePTD + * to 0. In this case the client is required to send the picture in encoding + * order to NvEncodeAPI by doing the re-ordering for B frames. \n + * If the client doesn't want to take the picture type decision it can enable + * picture type decision module in the NvEncodeAPI interface by setting + * NV_ENC_INITIALIZE_PARAMS::enablePTD to 1 and send the input pictures in display + * order. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] createEncodeParams + * Refer ::_NV_ENC_INITIALIZE_PARAMS for details. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncInitializeEncoder (void* encoder, NV_ENC_INITIALIZE_PARAMS* createEncodeParams); + + +// NvEncCreateInputBuffer +/** + * \brief Allocates Input buffer. + * + * This function is used to allocate an input buffer. The client must enumerate + * the input buffer format before allocating the input buffer resources. The + * NV_ENC_INPUT_PTR returned by the NvEncodeAPI interface in the + * NV_ENC_CREATE_INPUT_BUFFER::inputBuffer field can be directly used in + * ::NvEncEncodePicture() API. The number of input buffers to be allocated by the + * client must be at least 4 more than the number of B frames being used for encoding. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] createInputBufferParams + * Pointer to the ::NV_ENC_CREATE_INPUT_BUFFER structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncCreateInputBuffer (void* encoder, NV_ENC_CREATE_INPUT_BUFFER* createInputBufferParams); + + +// NvEncDestroyInputBuffer +/** + * \brief Release an input buffers. + * + * This function is used to free an input buffer. If the client has allocated + * any input buffer using ::NvEncCreateInputBuffer() API, it must free those + * input buffers by calling this function. The client must release the input + * buffers before destroying the encoder using ::NvEncDestroyEncoder() API. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] inputBuffer + * Pointer to the input buffer to be released. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncDestroyInputBuffer (void* encoder, NV_ENC_INPUT_PTR inputBuffer); + + +// NvEncCreateBitstreamBuffer +/** + * \brief Allocates an output bitstream buffer + * + * This function is used to allocate an output bitstream buffer and returns a + * NV_ENC_OUTPUT_PTR to bitstream buffer to the client in the + * NV_ENC_CREATE_BITSTREAM_BUFFER::bitstreamBuffer field. + * The client can only call this function after the encoder session has been + * initialized using ::NvEncInitializeEncoder() API. The minimum number of output + * buffers allocated by the client must be at least 4 more than the number of B + * B frames being used for encoding. The client can only access the output + * bitsteam data by locking the \p bitstreamBuffer using the ::NvEncLockBitstream() + * function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] createBitstreamBufferParams + * Pointer ::NV_ENC_CREATE_BITSTREAM_BUFFER for details. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncCreateBitstreamBuffer (void* encoder, NV_ENC_CREATE_BITSTREAM_BUFFER* createBitstreamBufferParams); + + +// NvEncDestroyBitstreamBuffer +/** + * \brief Release a bitstream buffer. + * + * This function is used to release the output bitstream buffer allocated using + * the ::NvEncCreateBitstreamBuffer() function. The client must release the output + * bitstreamBuffer using this function before destroying the encoder session. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] bitstreamBuffer + * Pointer to the bitstream buffer being released. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncDestroyBitstreamBuffer (void* encoder, NV_ENC_OUTPUT_PTR bitstreamBuffer); + +// NvEncEncodePicture +/** + * \brief Submit an input picture for encoding. + * + * This function is used to submit an input picture buffer for encoding. The + * encoding parameters are passed using \p *encodePicParams which is a pointer + * to the ::_NV_ENC_PIC_PARAMS structure. + * + * If the client has set NV_ENC_INITIALIZE_PARAMS::enablePTD to 0, then it must + * send a valid value for the following fields. + * - NV_ENC_PIC_PARAMS::pictureType + * - NV_ENC_PIC_PARAMS_H264::displayPOCSyntax (H264 only) + * - NV_ENC_PIC_PARAMS_H264::frameNumSyntax(H264 only) + * - NV_ENC_PIC_PARAMS_H264::refPicFlag(H264 only) + * + * + *\par Asynchronous Encoding + * If the client has enabled asynchronous mode of encoding by setting + * NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync to 1 in the ::NvEncInitializeEncoder() + * API ,then the client must send a valid NV_ENC_PIC_PARAMS::completionEvent. + * Incase of asynchronous mode of operation, client can queue the ::NvEncEncodePicture() + * API commands from the main thread and then queue output buffers to be processed + * to a secondary worker thread. Before the locking the output buffers in the + * secondary thread , the client must wait on NV_ENC_PIC_PARAMS::completionEvent + * it has queued in ::NvEncEncodePicture() API call. The client must always process + * completion event and the output buffer in the same order in which they have been + * submitted for encoding. The NvEncodeAPI interface is responsible for any + * re-ordering required for B frames and will always ensure that encoded bitstream + * data is written in the same order in which output buffer is submitted. + *\code + The below example shows how asynchronous encoding in case of 1 B frames + ------------------------------------------------------------------------ + Suppose the client allocated 4 input buffers(I1,I2..), 4 output buffers(O1,O2..) + and 4 completion events(E1, E2, ...). The NvEncodeAPI interface will need to + keep a copy of the input buffers for re-ordering and it allocates following + internal buffers (NvI1, NvI2...). These internal buffers are managed by NvEncodeAPI + and the client is not responsible for the allocating or freeing the memory of + the internal buffers. + + a) The client main thread will queue the following encode frame calls. + Note the picture type is unknown to the client, the decision is being taken by + NvEncodeAPI interface. The client should pass ::_NV_ENC_PIC_PARAMS parameter + consisting of allocated input buffer, output buffer and output events in successive + ::NvEncEncodePicture() API calls along with other required encode picture params. + For example: + 1st EncodePicture parameters - (I1, O1, E1) + 2nd EncodePicture parameters - (I2, O2, E2) + 3rd EncodePicture parameters - (I3, O3, E3) + + b) NvEncodeAPI SW will receive the following encode Commands from the client. + The left side shows input from client in the form (Input buffer, Output Buffer, + Output Event). The right hand side shows a possible picture type decision take by + the NvEncodeAPI interface. + (I1, O1, E1) ---P1 Frame + (I2, O2, E2) ---B2 Frame + (I3, O3, E3) ---P3 Frame + + c) NvEncodeAPI interface will make a copy of the input buffers to its internal + buffersfor re-ordering. These copies are done as part of nvEncEncodePicture + function call from the client and NvEncodeAPI interface is responsible for + synchronization of copy operation with the actual encoding operation. + I1 --> NvI1 + I2 --> NvI2 + I3 --> NvI3 + + d) After returning from ::NvEncEncodePicture() call , the client must queue the output + bitstream processing work to the secondary thread. The output bitstream processing + for asynchronous mode consist of first waiting on completion event(E1, E2..) + and then locking the output bitstream buffer(O1, O2..) for reading the encoded + data. The work queued to the secondary thread by the client is in the following order + (I1, O1, E1) + (I2, O2, E2) + (I3, O3, E3) + Note they are in the same order in which client calls ::NvEncEncodePicture() API + in \p step a). + + e) NvEncodeAPI interface will do the re-ordering such that Encoder HW will receive + the following encode commands: + (NvI1, O1, E1) ---P1 Frame + (NvI3, O2, E2) ---P3 Frame + (NvI2, O3, E3) ---B2 frame + + f) After the encoding operations are completed, the events will be signalled + by NvEncodeAPI interface in the following order : + (O1, E1) ---P1 Frame ,output bitstream copied to O1 and event E1 signalled. + (O2, E2) ---P3 Frame ,output bitstream copied to O2 and event E2 signalled. + (O3, E3) ---B2 Frame ,output bitstream copied to O3 and event E3 signalled. + + g) The client must lock the bitstream data using ::NvEncLockBitstream() API in + the order O1,O2,O3 to read the encoded data, after waiting for the events + to be signalled in the same order i.e E1, E2 and E3.The output processing is + done in the secondary thread in the following order: + Waits on E1, copies encoded bitstream from O1 + Waits on E2, copies encoded bitstream from O2 + Waits on E3, copies encoded bitstream from O3 + + -Note the client will receive the events signalling and output buffer in the + same order in which they have submitted for encoding. + -Note the LockBitstream will have picture type field which will notify the + output picture type to the clients. + -Note the input, output buffer and the output completion event are free to be + reused once NvEncodeAPI interfaced has signalled the event and the client has + copied the data from the output buffer. + + * \endcode + * + *\par Synchronous Encoding + * The client can enable synchronous mode of encoding by setting + * NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync to 0 in ::NvEncInitializeEncoder() API. + * The NvEncodeAPI interface may return ::NV_ENC_ERR_NEED_MORE_INPUT error code for + * some ::NvEncEncodePicture() API calls when NV_ENC_INITIALIZE_PARAMS::enablePTD + * is set to 1, but the client must not treat it as a fatal error. The NvEncodeAPI + * interface might not be able to submit an input picture buffer for encoding + * immediately due to re-ordering for B frames. The NvEncodeAPI interface cannot + * submit the input picture which is decided to be encoded as B frame as it waits + * for backward reference from temporally subsequent frames. This input picture + * is buffered internally and waits for more input picture to arrive. The client + * must not call ::NvEncLockBitstream() API on the output buffers whose + * ::NvEncEncodePicture() API returns ::NV_ENC_ERR_NEED_MORE_INPUT. The client must + * wait for the NvEncodeAPI interface to return ::NV_ENC_SUCCESS before locking the + * output bitstreams to read the encoded bitstream data. The following example + * explains the scenario with synchronous encoding with 2 B frames. + *\code + The below example shows how synchronous encoding works in case of 1 B frames + ----------------------------------------------------------------------------- + Suppose the client allocated 4 input buffers(I1,I2..), 4 output buffers(O1,O2..) + and 4 completion events(E1, E2, ...). The NvEncodeAPI interface will need to + keep a copy of the input buffers for re-ordering and it allocates following + internal buffers (NvI1, NvI2...). These internal buffers are managed by NvEncodeAPI + and the client is not responsible for the allocating or freeing the memory of + the internal buffers. + + The client calls ::NvEncEncodePicture() API with input buffer I1 and output buffer O1. + The NvEncodeAPI decides to encode I1 as P frame and submits it to encoder + HW and returns ::NV_ENC_SUCCESS. + The client can now read the encoded data by locking the output O1 by calling + NvEncLockBitstream API. + + The client calls ::NvEncEncodePicture() API with input buffer I2 and output buffer O2. + The NvEncodeAPI decides to encode I2 as B frame and buffers I2 by copying it + to internal buffer and returns ::NV_ENC_ERR_NEED_MORE_INPUT. + The error is not fatal and it notifies client that it cannot read the encoded + data by locking the output O2 by calling ::NvEncLockBitstream() API without submitting + more work to the NvEncodeAPI interface. + + The client calls ::NvEncEncodePicture() with input buffer I3 and output buffer O3. + The NvEncodeAPI decides to encode I3 as P frame and it first submits I3 for + encoding which will be used as backward reference frame for I2. + The NvEncodeAPI then submits I2 for encoding and returns ::NV_ENC_SUCESS. Both + the submission are part of the same ::NvEncEncodePicture() function call. + The client can now read the encoded data for both the frames by locking the output + O2 followed by O3 ,by calling ::NvEncLockBitstream() API. + + The client must always lock the output in the same order in which it has submitted + to receive the encoded bitstream in correct encoding order. + + * \endcode + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] encodePicParams + * Pointer to the ::_NV_ENC_PIC_PARAMS structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_ENCODER_BUSY \n + * ::NV_ENC_ERR_NEED_MORE_INPUT \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncEncodePicture (void* encoder, NV_ENC_PIC_PARAMS* encodePicParams); + + +// NvEncLockBitstream +/** + * \brief Lock output bitstream buffer + * + * This function is used to lock the bitstream buffer to read the encoded data. + * The client can only access the encoded data by calling this function. + * The pointer to client accessible encoded data is returned in the + * NV_ENC_LOCK_BITSTREAM::bitstreamBufferPtr field. The size of the encoded data + * in the output buffer is returned in the NV_ENC_LOCK_BITSTREAM::bitstreamSizeInBytes + * The NvEncodeAPI interface also returns the output picture type and picture structure + * of the encoded frame in NV_ENC_LOCK_BITSTREAM::pictureType and + * NV_ENC_LOCK_BITSTREAM::pictureStruct fields respectively. If the client has + * set NV_ENC_LOCK_BITSTREAM::doNotWait to 1, the function might return + * ::NV_ENC_ERR_LOCK_BUSY if client is operating in synchronous mode. This is not + * a fatal failure if NV_ENC_LOCK_BITSTREAM::doNotWait is set to 1. In the above case the client can + * retry the function after few milliseconds. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] lockBitstreamBufferParams + * Pointer to the ::_NV_ENC_LOCK_BITSTREAM structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_LOCK_BUSY \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncLockBitstream (void* encoder, NV_ENC_LOCK_BITSTREAM* lockBitstreamBufferParams); + + +// NvEncUnlockBitstream +/** + * \brief Unlock the output bitstream buffer + * + * This function is used to unlock the output bitstream buffer after the client + * has read the encoded data from output buffer. The client must call this function + * to unlock the output buffer which it has previously locked using ::NvEncLockBitstream() + * function. Using a locked bitstream buffer in ::NvEncEncodePicture() API will cause + * the function to fail. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] bitstreamBuffer + * bitstream buffer pointer being unlocked + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncUnlockBitstream (void* encoder, NV_ENC_OUTPUT_PTR bitstreamBuffer); + + +// NvLockInputBuffer +/** + * \brief Locks an input buffer + * + * This function is used to lock the input buffer to load the uncompressed YUV + * pixel data into input buffer memory. The client must pass the NV_ENC_INPUT_PTR + * it had previously allocated using ::NvEncCreateInputBuffer()in the + * NV_ENC_LOCK_INPUT_BUFFER::inputBuffer field. + * The NvEncodeAPI interface returns pointer to client accessible input buffer + * memory in NV_ENC_LOCK_INPUT_BUFFER::bufferDataPtr field. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] lockInputBufferParams + * Pointer to the ::_NV_ENC_LOCK_INPUT_BUFFER structure + * + * \return + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_LOCK_BUSY \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncLockInputBuffer (void* encoder, NV_ENC_LOCK_INPUT_BUFFER* lockInputBufferParams); + + +// NvUnlockInputBuffer +/** + * \brief Unlocks the input buffer + * + * This function is used to unlock the input buffer memory previously locked for + * uploading YUV pixel data. The input buffer must be unlocked before being used + * again for encoding, otherwise NvEncodeAPI will fail the ::NvEncEncodePicture() + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] inputBuffer + * Pointer to the input buffer that is being unlocked. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + * + */ +NVENCSTATUS NVENCAPI NvEncUnlockInputBuffer (void* encoder, NV_ENC_INPUT_PTR inputBuffer); + + +// NvEncGetEncodeStats +/** + * \brief Get encoding statistics. + * + * This function is used to retrieve the encoding statistics. + * This API is not supported when encode device type is CUDA. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] encodeStats + * Pointer to the ::_NV_ENC_STAT structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeStats (void* encoder, NV_ENC_STAT* encodeStats); + + +// NvEncGetSequenceParams +/** + * \brief Get encoded sequence and picture header. + * + * This function can be used to retrieve the sequence and picture header out of + * band. The client must call this function only after the encoder has been + * initialized using ::NvEncInitializeEncoder() function. The client must + * allocate the memory where the NvEncodeAPI interface can copy the bitstream + * header and pass the pointer to the memory in NV_ENC_SEQUENCE_PARAM_PAYLOAD::spsppsBuffer. + * The size of buffer is passed in the field NV_ENC_SEQUENCE_PARAM_PAYLOAD::inBufferSize. + * The NvEncodeAPI interface will copy the bitstream header payload and returns + * the actual size of the bitstream header in the field + * NV_ENC_SEQUENCE_PARAM_PAYLOAD::outSPSPPSPayloadSize. + * The client must call ::NvEncGetSequenceParams() function from the same thread which is + * being used to call ::NvEncEncodePicture() function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] sequenceParamPayload + * Pointer to the ::_NV_ENC_SEQUENCE_PARAM_PAYLOAD structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetSequenceParams (void* encoder, NV_ENC_SEQUENCE_PARAM_PAYLOAD* sequenceParamPayload); + + +// NvEncRegisterAsyncEvent +/** + * \brief Register event for notification to encoding completion. + * + * This function is used to register the completion event with NvEncodeAPI + * interface. The event is required when the client has configured the encoder to + * work in asynchronous mode. In this mode the client needs to send a completion + * event with every output buffer. The NvEncodeAPI interface will signal the + * completion of the encoding process using this event. Only after the event is + * signalled the client can get the encoded data using ::NvEncLockBitstream() function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] eventParams + * Pointer to the ::_NV_ENC_EVENT_PARAMS structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncRegisterAsyncEvent (void* encoder, NV_ENC_EVENT_PARAMS* eventParams); + + +// NvEncUnregisterAsyncEvent +/** + * \brief Unregister completion event. + * + * This function is used to unregister completion event which has been previously + * registered using ::NvEncRegisterAsyncEvent() function. The client must unregister + * all events before destroying the encoder using ::NvEncDestroyEncoder() function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] eventParams + * Pointer to the ::_NV_ENC_EVENT_PARAMS structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncUnregisterAsyncEvent (void* encoder, NV_ENC_EVENT_PARAMS* eventParams); + + +// NvEncMapInputResource +/** + * \brief Map an externally created input resource pointer for encoding. + * + * Maps an externally allocated input resource [using and returns a NV_ENC_INPUT_PTR + * which can be used for encoding in the ::NvEncEncodePicture() function. The + * mapped resource is returned in the field NV_ENC_MAP_INPUT_RESOURCE::outputResourcePtr. + * The NvEncodeAPI interface also returns the buffer format of the mapped resource + * in the field NV_ENC_MAP_INPUT_RESOURCE::outbufferFmt. + * This function provides synchronization guarantee that any graphics or compute + * work submitted on the input buffer is completed before the buffer is used for encoding. + * The client should not access any input buffer while they are mapped by the encoder. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] mapInputResParams + * Pointer to the ::_NV_ENC_MAP_INPUT_RESOURCE structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_RESOURCE_NOT_REGISTERED \n + * ::NV_ENC_ERR_MAP_FAILED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncMapInputResource (void* encoder, NV_ENC_MAP_INPUT_RESOURCE* mapInputResParams); + + +// NvEncUnmapInputResource +/** + * \brief UnMaps a NV_ENC_INPUT_PTR which was mapped for encoding + * + * + * UnMaps an input buffer which was previously mapped using ::NvEncMapInputResource() + * API. The mapping created using ::NvEncMapInputResource() should be invalidated + * using this API before the external resource is destroyed by the client. The client + * must unmap the buffer after ::NvEncLockBitstream() API returns succuessfully for encode + * work submitted using the mapped input buffer. + * + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] mappedInputBuffer + * Pointer to the NV_ENC_INPUT_PTR + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_RESOURCE_NOT_REGISTERED \n + * ::NV_ENC_ERR_RESOURCE_NOT_MAPPED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncUnmapInputResource (void* encoder, NV_ENC_INPUT_PTR mappedInputBuffer); + +// NvEncDestroyEncoder +/** + * \brief Destroy Encoding Session + * + * Destroys the encoder session previously created using ::NvEncOpenEncodeSession() + * function. The client must flush the encoder before freeing any resources. In order + * to flush the encoder the client must pass a NULL encode picture packet and either + * wait for the ::NvEncEncodePicture() function to return in synchronous mode or wait + * for the flush event to be signaled by the encoder in asynchronous mode. + * The client must free all the input and output resources created using the + * NvEncodeAPI interface before destroying the encoder. If the client is operating + * in asynchronous mode, it must also unregister the completion events previously + * registered. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncDestroyEncoder (void* encoder); + +// NvEncInvalidateRefFrames +/** + * \brief Invalidate reference frames + * + * Invalidates reference frame based on the time stamp provided by the client. + * The encoder marks any reference frames or any frames which have been reconstructed + * using the corrupt frame as invalid for motion estimation and uses older reference + * frames for motion estimation. The encoded forces the current frame to be encoded + * as an intra frame if no reference frames are left after invalidation process. + * This is useful for low latency application for error resiliency. The client + * is recommended to set NV_ENC_CONFIG_H264::maxNumRefFrames to a large value so + * that encoder can keep a backup of older reference frames in the DPB and can use them + * for motion estimation when the newer reference frames have been invalidated. + * This API can be called multiple times. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] invalidRefFrameTimeStamp + * Timestamp of the invalid reference frames which needs to be invalidated. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncInvalidateRefFrames(void* encoder, uint64_t invalidRefFrameTimeStamp); + +// NvEncOpenEncodeSessionEx +/** + * \brief Opens an encoding session. + * + * Opens an encoding session and returns a pointer to the encoder interface in + * the \p **encoder parameter. The client should start encoding process by calling + * this API first. + * The client must pass a pointer to IDirect3DDevice9 device or CUDA context in the \p *device parameter. + * For the OpenGL interface, \p device must be NULL. An OpenGL context must be current when + * calling all NvEncodeAPI functions. + * If the creation of encoder session fails, the client must call ::NvEncDestroyEncoder API + * before exiting. + * + * \param [in] openSessionExParams + * Pointer to a ::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS structure. + * \param [out] encoder + * Encode Session pointer to the NvEncodeAPI interface. + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_NO_ENCODE_DEVICE \n + * ::NV_ENC_ERR_UNSUPPORTED_DEVICE \n + * ::NV_ENC_ERR_INVALID_DEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncOpenEncodeSessionEx (NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams, void** encoder); + +// NvEncRegisterResource +/** + * \brief Registers a resource with the Nvidia Video Encoder Interface. + * + * Registers a resource with the Nvidia Video Encoder Interface for book keeping. + * The client is expected to pass the registered resource handle as well, while calling ::NvEncMapInputResource API. + * + * \param [in] encoder + * Pointer to the NVEncodeAPI interface. + * + * \param [in] registerResParams + * Pointer to a ::_NV_ENC_REGISTER_RESOURCE structure + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_RESOURCE_REGISTER_FAILED \n + * ::NV_ENC_ERR_GENERIC \n + * ::NV_ENC_ERR_UNIMPLEMENTED \n + * + */ +NVENCSTATUS NVENCAPI NvEncRegisterResource (void* encoder, NV_ENC_REGISTER_RESOURCE* registerResParams); + +// NvEncUnregisterResource +/** + * \brief Unregisters a resource previously registered with the Nvidia Video Encoder Interface. + * + * Unregisters a resource previously registered with the Nvidia Video Encoder Interface. + * The client is expected to unregister any resource that it has registered with the + * Nvidia Video Encoder Interface before destroying the resource. + * + * \param [in] encoder + * Pointer to the NVEncodeAPI interface. + * + * \param [in] registeredResource + * The registered resource pointer that was returned in ::NvEncRegisterResource. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_RESOURCE_NOT_REGISTERED \n + * ::NV_ENC_ERR_GENERIC \n + * ::NV_ENC_ERR_UNIMPLEMENTED \n + * + */ +NVENCSTATUS NVENCAPI NvEncUnregisterResource (void* encoder, NV_ENC_REGISTERED_PTR registeredResource); + +// NvEncReconfigureEncoder +/** + * \brief Reconfigure an existing encoding session. + * + * Reconfigure an existing encoding session. + * The client should call this API to change/reconfigure the parameter passed during + * NvEncInitializeEncoder API call. + * Currently Reconfiguration of following are not supported. + * Change in GOP structure. + * Change in sync-Async mode. + * Change in MaxWidth & MaxHeight. + * Change in PTDmode. + * + * Resolution change is possible only if maxEncodeWidth & maxEncodeHeight of NV_ENC_INITIALIZE_PARAMS + * is set while creating encoder session. + * + * \param [in] encoder + * Pointer to the NVEncodeAPI interface. + * + * \param [in] reInitEncodeParams + * Pointer to a ::NV_ENC_RECONFIGURE_PARAMS structure. + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_NO_ENCODE_DEVICE \n + * ::NV_ENC_ERR_UNSUPPORTED_DEVICE \n + * ::NV_ENC_ERR_INVALID_DEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncReconfigureEncoder (void *encoder, NV_ENC_RECONFIGURE_PARAMS* reInitEncodeParams); + + + +// NvEncCreateMVBuffer +/** + * \brief Allocates output MV buffer for ME only mode. + * + * This function is used to allocate an output MV buffer. The size of the mvBuffer is + * dependent on the frame height and width of the last ::NvEncCreateInputBuffer() call. + * The NV_ENC_OUTPUT_PTR returned by the NvEncodeAPI interface in the + * ::NV_ENC_CREATE_MV_BUFFER::mvBuffer field should be used in + * ::NvEncRunMotionEstimationOnly() API. + * Client must lock ::NV_ENC_CREATE_MV_BUFFER::mvBuffer using ::NvEncLockBitstream() API to get the motion vector data. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] createMVBufferParams + * Pointer to the ::NV_ENC_CREATE_MV_BUFFER structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncCreateMVBuffer (void* encoder, NV_ENC_CREATE_MV_BUFFER* createMVBufferParams); + + +// NvEncDestroyMVBuffer +/** + * \brief Release an output MV buffer for ME only mode. + * + * This function is used to release the output MV buffer allocated using + * the ::NvEncCreateMVBuffer() function. The client must release the output + * mvBuffer using this function before destroying the encoder session. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] mvBuffer + * Pointer to the mvBuffer being released. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncDestroyMVBuffer (void* encoder, NV_ENC_OUTPUT_PTR mvBuffer); + + +// NvEncRunMotionEstimationOnly +/** + * \brief Submit an input picture and reference frame for motion estimation in ME only mode. + * + * This function is used to submit the input frame and reference frame for motion + * estimation. The ME parameters are passed using *meOnlyParams which is a pointer + * to ::_NV_ENC_MEONLY_PARAMS structure. + * Client must lock ::NV_ENC_CREATE_MV_BUFFER::mvBuffer using ::NvEncLockBitstream() API to get the motion vector data. + * to get motion vector data. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] meOnlyParams + * Pointer to the ::_NV_ENC_MEONLY_PARAMS structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_NEED_MORE_INPUT \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncRunMotionEstimationOnly (void* encoder, NV_ENC_MEONLY_PARAMS* meOnlyParams); + +// NvEncodeAPIGetMaxSupportedVersion +/** + * \brief Get the largest NvEncodeAPI version supported by the driver. + * + * This function can be used by clients to determine if the driver supports + * the NvEncodeAPI header the application was compiled with. + * + * \param [out] version + * Pointer to the requested value. The 4 least significant bits in the returned + * indicate the minor version and the rest of the bits indicate the major + * version of the largest supported version. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + */ +NVENCSTATUS NVENCAPI NvEncodeAPIGetMaxSupportedVersion (uint32_t* version); + + +/// \cond API PFN +/* + * Defines API function pointers + */ +typedef NVENCSTATUS (NVENCAPI* PNVENCOPENENCODESESSION) (void* device, uint32_t deviceType, void** encoder); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEGUIDCOUNT) (void* encoder, uint32_t* encodeGUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEGUIDS) (void* encoder, GUID* GUIDs, uint32_t guidArraySize, uint32_t* GUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPROFILEGUIDCOUNT) (void* encoder, GUID encodeGUID, uint32_t* encodeProfileGUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPROFILEGUIDS) (void* encoder, GUID encodeGUID, GUID* profileGUIDs, uint32_t guidArraySize, uint32_t* GUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETINPUTFORMATCOUNT) (void* encoder, GUID encodeGUID, uint32_t* inputFmtCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETINPUTFORMATS) (void* encoder, GUID encodeGUID, NV_ENC_BUFFER_FORMAT* inputFmts, uint32_t inputFmtArraySize, uint32_t* inputFmtCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODECAPS) (void* encoder, GUID encodeGUID, NV_ENC_CAPS_PARAM* capsParam, int* capsVal); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPRESETCOUNT) (void* encoder, GUID encodeGUID, uint32_t* encodePresetGUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPRESETGUIDS) (void* encoder, GUID encodeGUID, GUID* presetGUIDs, uint32_t guidArraySize, uint32_t* encodePresetGUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPRESETCONFIG) (void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_PRESET_CONFIG* presetConfig); +typedef NVENCSTATUS (NVENCAPI* PNVENCINITIALIZEENCODER) (void* encoder, NV_ENC_INITIALIZE_PARAMS* createEncodeParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCCREATEINPUTBUFFER) (void* encoder, NV_ENC_CREATE_INPUT_BUFFER* createInputBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCDESTROYINPUTBUFFER) (void* encoder, NV_ENC_INPUT_PTR inputBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCCREATEBITSTREAMBUFFER) (void* encoder, NV_ENC_CREATE_BITSTREAM_BUFFER* createBitstreamBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCDESTROYBITSTREAMBUFFER) (void* encoder, NV_ENC_OUTPUT_PTR bitstreamBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCENCODEPICTURE) (void* encoder, NV_ENC_PIC_PARAMS* encodePicParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCLOCKBITSTREAM) (void* encoder, NV_ENC_LOCK_BITSTREAM* lockBitstreamBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNLOCKBITSTREAM) (void* encoder, NV_ENC_OUTPUT_PTR bitstreamBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCLOCKINPUTBUFFER) (void* encoder, NV_ENC_LOCK_INPUT_BUFFER* lockInputBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNLOCKINPUTBUFFER) (void* encoder, NV_ENC_INPUT_PTR inputBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODESTATS) (void* encoder, NV_ENC_STAT* encodeStats); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETSEQUENCEPARAMS) (void* encoder, NV_ENC_SEQUENCE_PARAM_PAYLOAD* sequenceParamPayload); +typedef NVENCSTATUS (NVENCAPI* PNVENCREGISTERASYNCEVENT) (void* encoder, NV_ENC_EVENT_PARAMS* eventParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNREGISTERASYNCEVENT) (void* encoder, NV_ENC_EVENT_PARAMS* eventParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCMAPINPUTRESOURCE) (void* encoder, NV_ENC_MAP_INPUT_RESOURCE* mapInputResParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNMAPINPUTRESOURCE) (void* encoder, NV_ENC_INPUT_PTR mappedInputBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCDESTROYENCODER) (void* encoder); +typedef NVENCSTATUS (NVENCAPI* PNVENCINVALIDATEREFFRAMES) (void* encoder, uint64_t invalidRefFrameTimeStamp); +typedef NVENCSTATUS (NVENCAPI* PNVENCOPENENCODESESSIONEX) (NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams, void** encoder); +typedef NVENCSTATUS (NVENCAPI* PNVENCREGISTERRESOURCE) (void* encoder, NV_ENC_REGISTER_RESOURCE* registerResParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNREGISTERRESOURCE) (void* encoder, NV_ENC_REGISTERED_PTR registeredRes); +typedef NVENCSTATUS (NVENCAPI* PNVENCRECONFIGUREENCODER) (void* encoder, NV_ENC_RECONFIGURE_PARAMS* reInitEncodeParams); + +typedef NVENCSTATUS (NVENCAPI* PNVENCCREATEMVBUFFER) (void* encoder, NV_ENC_CREATE_MV_BUFFER* createMVBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCDESTROYMVBUFFER) (void* encoder, NV_ENC_OUTPUT_PTR mvBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCRUNMOTIONESTIMATIONONLY) (void* encoder, NV_ENC_MEONLY_PARAMS* meOnlyParams); + + +/// \endcond + + +/** @} */ /* END ENCODE_FUNC */ + +/** + * \ingroup ENCODER_STRUCTURE + * NV_ENCODE_API_FUNCTION_LIST + */ +typedef struct _NV_ENCODE_API_FUNCTION_LIST +{ + uint32_t version; /**< [in]: Client should pass NV_ENCODE_API_FUNCTION_LIST_VER. */ + uint32_t reserved; /**< [in]: Reserved and should be set to 0. */ + PNVENCOPENENCODESESSION nvEncOpenEncodeSession; /**< [out]: Client should access ::NvEncOpenEncodeSession() API through this pointer. */ + PNVENCGETENCODEGUIDCOUNT nvEncGetEncodeGUIDCount; /**< [out]: Client should access ::NvEncGetEncodeGUIDCount() API through this pointer. */ + PNVENCGETENCODEPRESETCOUNT nvEncGetEncodeProfileGUIDCount; /**< [out]: Client should access ::NvEncGetEncodeProfileGUIDCount() API through this pointer.*/ + PNVENCGETENCODEPRESETGUIDS nvEncGetEncodeProfileGUIDs; /**< [out]: Client should access ::NvEncGetEncodeProfileGUIDs() API through this pointer. */ + PNVENCGETENCODEGUIDS nvEncGetEncodeGUIDs; /**< [out]: Client should access ::NvEncGetEncodeGUIDs() API through this pointer. */ + PNVENCGETINPUTFORMATCOUNT nvEncGetInputFormatCount; /**< [out]: Client should access ::NvEncGetInputFormatCount() API through this pointer. */ + PNVENCGETINPUTFORMATS nvEncGetInputFormats; /**< [out]: Client should access ::NvEncGetInputFormats() API through this pointer. */ + PNVENCGETENCODECAPS nvEncGetEncodeCaps; /**< [out]: Client should access ::NvEncGetEncodeCaps() API through this pointer. */ + PNVENCGETENCODEPRESETCOUNT nvEncGetEncodePresetCount; /**< [out]: Client should access ::NvEncGetEncodePresetCount() API through this pointer. */ + PNVENCGETENCODEPRESETGUIDS nvEncGetEncodePresetGUIDs; /**< [out]: Client should access ::NvEncGetEncodePresetGUIDs() API through this pointer. */ + PNVENCGETENCODEPRESETCONFIG nvEncGetEncodePresetConfig; /**< [out]: Client should access ::NvEncGetEncodePresetConfig() API through this pointer. */ + PNVENCINITIALIZEENCODER nvEncInitializeEncoder; /**< [out]: Client should access ::NvEncInitializeEncoder() API through this pointer. */ + PNVENCCREATEINPUTBUFFER nvEncCreateInputBuffer; /**< [out]: Client should access ::NvEncCreateInputBuffer() API through this pointer. */ + PNVENCDESTROYINPUTBUFFER nvEncDestroyInputBuffer; /**< [out]: Client should access ::NvEncDestroyInputBuffer() API through this pointer. */ + PNVENCCREATEBITSTREAMBUFFER nvEncCreateBitstreamBuffer; /**< [out]: Client should access ::NvEncCreateBitstreamBuffer() API through this pointer. */ + PNVENCDESTROYBITSTREAMBUFFER nvEncDestroyBitstreamBuffer; /**< [out]: Client should access ::NvEncDestroyBitstreamBuffer() API through this pointer. */ + PNVENCENCODEPICTURE nvEncEncodePicture; /**< [out]: Client should access ::NvEncEncodePicture() API through this pointer. */ + PNVENCLOCKBITSTREAM nvEncLockBitstream; /**< [out]: Client should access ::NvEncLockBitstream() API through this pointer. */ + PNVENCUNLOCKBITSTREAM nvEncUnlockBitstream; /**< [out]: Client should access ::NvEncUnlockBitstream() API through this pointer. */ + PNVENCLOCKINPUTBUFFER nvEncLockInputBuffer; /**< [out]: Client should access ::NvEncLockInputBuffer() API through this pointer. */ + PNVENCUNLOCKINPUTBUFFER nvEncUnlockInputBuffer; /**< [out]: Client should access ::NvEncUnlockInputBuffer() API through this pointer. */ + PNVENCGETENCODESTATS nvEncGetEncodeStats; /**< [out]: Client should access ::NvEncGetEncodeStats() API through this pointer. */ + PNVENCGETSEQUENCEPARAMS nvEncGetSequenceParams; /**< [out]: Client should access ::NvEncGetSequenceParams() API through this pointer. */ + PNVENCREGISTERASYNCEVENT nvEncRegisterAsyncEvent; /**< [out]: Client should access ::NvEncRegisterAsyncEvent() API through this pointer. */ + PNVENCUNREGISTERASYNCEVENT nvEncUnregisterAsyncEvent; /**< [out]: Client should access ::NvEncUnregisterAsyncEvent() API through this pointer. */ + PNVENCMAPINPUTRESOURCE nvEncMapInputResource; /**< [out]: Client should access ::NvEncMapInputResource() API through this pointer. */ + PNVENCUNMAPINPUTRESOURCE nvEncUnmapInputResource; /**< [out]: Client should access ::NvEncUnmapInputResource() API through this pointer. */ + PNVENCDESTROYENCODER nvEncDestroyEncoder; /**< [out]: Client should access ::NvEncDestroyEncoder() API through this pointer. */ + PNVENCINVALIDATEREFFRAMES nvEncInvalidateRefFrames; /**< [out]: Client should access ::NvEncInvalidateRefFrames() API through this pointer. */ + PNVENCOPENENCODESESSIONEX nvEncOpenEncodeSessionEx; /**< [out]: Client should access ::NvEncOpenEncodeSession() API through this pointer. */ + PNVENCREGISTERRESOURCE nvEncRegisterResource; /**< [out]: Client should access ::NvEncRegisterResource() API through this pointer. */ + PNVENCUNREGISTERRESOURCE nvEncUnregisterResource; /**< [out]: Client should access ::NvEncUnregisterResource() API through this pointer. */ + PNVENCRECONFIGUREENCODER nvEncReconfigureEncoder; /**< [out]: Client should access ::NvEncReconfigureEncoder() API through this pointer. */ + void* reserved1; + PNVENCCREATEMVBUFFER nvEncCreateMVBuffer; /**< [out]: Client should access ::NvEncCreateMVBuffer API through this pointer. */ + PNVENCDESTROYMVBUFFER nvEncDestroyMVBuffer; /**< [out]: Client should access ::NvEncDestroyMVBuffer API through this pointer. */ + PNVENCRUNMOTIONESTIMATIONONLY nvEncRunMotionEstimationOnly; /**< [out]: Client should access ::NvEncRunMotionEstimationOnly API through this pointer. */ + void* reserved2[281]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENCODE_API_FUNCTION_LIST; + +/** Macro for constructing the version field of ::_NV_ENCODEAPI_FUNCTION_LIST. */ +#define NV_ENCODE_API_FUNCTION_LIST_VER NVENCAPI_STRUCT_VERSION(2) + +// NvEncodeAPICreateInstance +/** + * \ingroup ENCODE_FUNC + * Entry Point to the NvEncodeAPI interface. + * + * Creates an instance of the NvEncodeAPI interface, and populates the + * pFunctionList with function pointers to the API routines implemented by the + * NvEncodeAPI interface. + * + * \param [out] functionList + * + * \return + * ::NV_ENC_SUCCESS + * ::NV_ENC_ERR_INVALID_PTR + */ +NVENCSTATUS NVENCAPI NvEncodeAPICreateInstance(NV_ENCODE_API_FUNCTION_LIST *functionList); + +#ifdef __cplusplus +} +#endif + + +#endif + diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-aac.c b/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c similarity index 55% rename from plugins/obs-ffmpeg/obs-ffmpeg-aac.c rename to plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c index f2c34c2..e91f7e1 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-aac.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c @@ -26,17 +26,21 @@ #include "obs-ffmpeg-compat.h" #define do_log(level, format, ...) \ - blog(level, "[FFmpeg aac encoder: '%s'] " format, \ - obs_encoder_get_name(enc->encoder), ##__VA_ARGS__) + blog(level, "[FFmpeg %s encoder: '%s'] " format, \ + enc->type, \ + obs_encoder_get_name(enc->encoder), \ + ##__VA_ARGS__) #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) -struct aac_encoder { +struct enc_encoder { obs_encoder_t *encoder; - AVCodec *aac; + const char *type; + + AVCodec *codec; AVCodecContext *context; uint8_t *samples[MAX_AV_PLANES]; @@ -52,15 +56,54 @@ struct aac_encoder { int frame_size_bytes; }; +static inline uint64_t convert_speaker_layout(enum speaker_layout layout) +{ + switch (layout) { + case SPEAKERS_UNKNOWN: return 0; + case SPEAKERS_MONO: return AV_CH_LAYOUT_MONO; + case SPEAKERS_STEREO: return AV_CH_LAYOUT_STEREO; + case SPEAKERS_2POINT1: return AV_CH_LAYOUT_SURROUND; + case SPEAKERS_4POINT0: return AV_CH_LAYOUT_4POINT0; + case SPEAKERS_4POINT1: return AV_CH_LAYOUT_4POINT1; + case SPEAKERS_5POINT1: return AV_CH_LAYOUT_5POINT1_BACK; + case SPEAKERS_7POINT1: return AV_CH_LAYOUT_7POINT1; + } + + /* shouldn't get here */ + return 0; +} + +static inline enum speaker_layout convert_ff_channel_layout(uint64_t channel_layout) +{ + switch (channel_layout) { + case AV_CH_LAYOUT_MONO: return SPEAKERS_MONO; + case AV_CH_LAYOUT_STEREO: return SPEAKERS_STEREO; + case AV_CH_LAYOUT_SURROUND: return SPEAKERS_2POINT1; + case AV_CH_LAYOUT_4POINT0: return SPEAKERS_4POINT0; + case AV_CH_LAYOUT_4POINT1: return SPEAKERS_4POINT1; + case AV_CH_LAYOUT_5POINT1_BACK: return SPEAKERS_5POINT1; + case AV_CH_LAYOUT_7POINT1: return SPEAKERS_7POINT1; + } + + /* shouldn't get here */ + return SPEAKERS_UNKNOWN; +} + static const char *aac_getname(void *unused) { UNUSED_PARAMETER(unused); return obs_module_text("FFmpegAAC"); } -static void aac_destroy(void *data) +static const char *opus_getname(void *unused) { - struct aac_encoder *enc = data; + UNUSED_PARAMETER(unused); + return obs_module_text("FFmpegOpus"); +} + +static void enc_destroy(void *data) +{ + struct enc_encoder *enc = data; if (enc->samples[0]) av_freep(&enc->samples[0]); @@ -73,7 +116,7 @@ static void aac_destroy(void *data) bfree(enc); } -static bool initialize_codec(struct aac_encoder *enc) +static bool initialize_codec(struct enc_encoder *enc) { int ret; @@ -83,7 +126,7 @@ static bool initialize_codec(struct aac_encoder *enc) return false; } - ret = avcodec_open2(enc->context, enc->aac, NULL); + ret = avcodec_open2(enc->context, enc->codec, NULL); if (ret < 0) { warn("Failed to open AAC codec: %s", av_err2str(ret)); return false; @@ -105,7 +148,7 @@ static bool initialize_codec(struct aac_encoder *enc) return true; } -static void init_sizes(struct aac_encoder *enc, audio_t *audio) +static void init_sizes(struct enc_encoder *enc, audio_t *audio) { const struct audio_output_info *aoi; enum audio_format format; @@ -121,21 +164,28 @@ static void init_sizes(struct aac_encoder *enc, audio_t *audio) #define MIN(x, y) ((x) < (y) ? (x) : (y)) #endif -static void *aac_create(obs_data_t *settings, obs_encoder_t *encoder) +static void *enc_create(obs_data_t *settings, obs_encoder_t *encoder, + const char *type, const char *alt) { - struct aac_encoder *enc; + struct enc_encoder *enc; int bitrate = (int)obs_data_get_int(settings, "bitrate"); audio_t *audio = obs_encoder_audio(encoder); avcodec_register_all(); - enc = bzalloc(sizeof(struct aac_encoder)); + enc = bzalloc(sizeof(struct enc_encoder)); enc->encoder = encoder; - enc->aac = avcodec_find_encoder(AV_CODEC_ID_AAC); + enc->codec = avcodec_find_encoder_by_name(type); + enc->type = type; + + if (!enc->codec && alt) { + enc->codec = avcodec_find_encoder_by_name(alt); + enc->type = alt; + } blog(LOG_INFO, "---------------------------------"); - if (!enc->aac) { + if (!enc->codec) { warn("Couldn't find encoder"); goto fail; } @@ -145,21 +195,43 @@ static void *aac_create(obs_data_t *settings, obs_encoder_t *encoder) return NULL; } - enc->context = avcodec_alloc_context3(enc->aac); + enc->context = avcodec_alloc_context3(enc->codec); if (!enc->context) { warn("Failed to create codec context"); goto fail; } enc->context->bit_rate = bitrate * 1000; + const struct audio_output_info *aoi; + aoi = audio_output_get_info(audio); enc->context->channels = (int)audio_output_get_channels(audio); + enc->context->channel_layout = convert_speaker_layout(aoi->speakers); enc->context->sample_rate = audio_output_get_sample_rate(audio); - enc->context->sample_fmt = enc->aac->sample_fmts ? - enc->aac->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; + enc->context->sample_fmt = enc->codec->sample_fmts ? + enc->codec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; + + /* check to make sure sample rate is supported */ + if (enc->codec->supported_samplerates) { + const int *rate = enc->codec->supported_samplerates; + int cur_rate = enc->context->sample_rate; + int closest = 0; + + while (*rate) { + int dist = abs(cur_rate - *rate); + int closest_dist = abs(cur_rate - closest); + + if (dist < closest_dist) + closest = *rate; + rate++; + } + + if (closest) + enc->context->sample_rate = closest; + } /* if using FFmpeg's AAC encoder, at least set a cutoff value * (recommended by konverter) */ - if (strcmp(enc->aac->name, "aac") == 0) { + if (strcmp(enc->codec->name, "aac") == 0) { int cutoff1 = 4000 + (int)enc->context->bit_rate / 8; int cutoff2 = 12000 + (int)enc->context->bit_rate / 8; int cutoff3 = enc->context->sample_rate / 2; @@ -170,25 +242,37 @@ static void *aac_create(obs_data_t *settings, obs_encoder_t *encoder) enc->context->cutoff = cutoff; } - info("bitrate: %" PRId64 ", channels: %d", - enc->context->bit_rate / 1000, enc->context->channels); + info("bitrate: %" PRId64 ", channels: %d, channel_layout: %x\n", + (int64_t)enc->context->bit_rate / 1000, + (int)enc->context->channels, + (unsigned int)enc->context->channel_layout); init_sizes(enc, audio); /* enable experimental FFmpeg encoder if the only one available */ enc->context->strict_std_compliance = -2; - enc->context->flags = CODEC_FLAG_GLOBAL_HEADER; + enc->context->flags = CODEC_FLAG_GLOBAL_H; if (initialize_codec(enc)) return enc; fail: - aac_destroy(enc); + enc_destroy(enc); return NULL; } -static bool do_aac_encode(struct aac_encoder *enc, +static void *aac_create(obs_data_t *settings, obs_encoder_t *encoder) +{ + return enc_create(settings, encoder, "aac", NULL); +} + +static void *opus_create(obs_data_t *settings, obs_encoder_t *encoder) +{ + return enc_create(settings, encoder, "libopus", "opus"); +} + +static bool do_encode(struct enc_encoder *enc, struct encoder_packet *packet, bool *received_packet) { AVRational time_base = {1, enc->context->sample_rate}; @@ -211,8 +295,19 @@ static bool do_aac_encode(struct aac_encoder *enc, enc->total_samples += enc->frame_size; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) + ret = avcodec_send_frame(enc->context, enc->aframe); + if (ret == 0) + ret = avcodec_receive_packet(enc->context, &avpacket); + + got_packet = (ret == 0); + + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) + ret = 0; +#else ret = avcodec_encode_audio2(enc->context, &avpacket, enc->aframe, &got_packet); +#endif if (ret < 0) { warn("avcodec_encode_audio2 failed: %s", av_err2str(ret)); return false; @@ -236,51 +331,52 @@ static bool do_aac_encode(struct aac_encoder *enc, return true; } -static bool aac_encode(void *data, struct encoder_frame *frame, +static bool enc_encode(void *data, struct encoder_frame *frame, struct encoder_packet *packet, bool *received_packet) { - struct aac_encoder *enc = data; + struct enc_encoder *enc = data; for (size_t i = 0; i < enc->audio_planes; i++) memcpy(enc->samples[i], frame->data[i], enc->frame_size_bytes); - return do_aac_encode(enc, packet, received_packet); + return do_encode(enc, packet, received_packet); } -static void aac_defaults(obs_data_t *settings) +static void enc_defaults(obs_data_t *settings) { obs_data_set_default_int(settings, "bitrate", 128); } -static obs_properties_t *aac_properties(void *unused) +static obs_properties_t *enc_properties(void *unused) { UNUSED_PARAMETER(unused); obs_properties_t *props = obs_properties_create(); - obs_properties_add_int(props, "bitrate", - obs_module_text("Bitrate"), 64, 320, 32); + obs_module_text("Bitrate"), 64, 1024, 32); return props; } -static bool aac_extra_data(void *data, uint8_t **extra_data, size_t *size) +static bool enc_extra_data(void *data, uint8_t **extra_data, size_t *size) { - struct aac_encoder *enc = data; + struct enc_encoder *enc = data; *extra_data = enc->context->extradata; *size = enc->context->extradata_size; return true; } -static void aac_audio_info(void *data, struct audio_convert_info *info) +static void enc_audio_info(void *data, struct audio_convert_info *info) { - struct aac_encoder *enc = data; + struct enc_encoder *enc = data; info->format = convert_ffmpeg_sample_format(enc->context->sample_fmt); + info->samples_per_sec = (uint32_t)enc->context->sample_rate; + info->speakers = convert_ff_channel_layout(enc->context->channel_layout); } -static size_t aac_frame_size(void *data) +static size_t enc_frame_size(void *data) { - struct aac_encoder *enc =data; + struct enc_encoder *enc =data; return enc->frame_size; } @@ -290,11 +386,26 @@ struct obs_encoder_info aac_encoder_info = { .codec = "AAC", .get_name = aac_getname, .create = aac_create, - .destroy = aac_destroy, - .encode = aac_encode, - .get_frame_size = aac_frame_size, - .get_defaults = aac_defaults, - .get_properties = aac_properties, - .get_extra_data = aac_extra_data, - .get_audio_info = aac_audio_info + .destroy = enc_destroy, + .encode = enc_encode, + .get_frame_size = enc_frame_size, + .get_defaults = enc_defaults, + .get_properties = enc_properties, + .get_extra_data = enc_extra_data, + .get_audio_info = enc_audio_info +}; + +struct obs_encoder_info opus_encoder_info = { + .id = "ffmpeg_opus", + .type = OBS_ENCODER_AUDIO, + .codec = "opus", + .get_name = opus_getname, + .create = opus_create, + .destroy = enc_destroy, + .encode = enc_encode, + .get_frame_size = enc_frame_size, + .get_defaults = enc_defaults, + .get_properties = enc_properties, + .get_extra_data = enc_extra_data, + .get_audio_info = enc_audio_info }; diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-compat.h b/plugins/obs-ffmpeg/obs-ffmpeg-compat.h index ba35eba..1072d70 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-compat.h +++ b/plugins/obs-ffmpeg/obs-ffmpeg-compat.h @@ -23,3 +23,13 @@ #if LIBAVCODEC_VERSION_MAJOR >= 57 #define av_free_packet av_packet_unref #endif + +#if LIBAVCODEC_VERSION_MAJOR >= 58 +#define CODEC_CAP_TRUNC AV_CODEC_CAP_TRUNCATED +#define CODEC_FLAG_TRUNC AV_CODEC_FLAG_TRUNCATED +#define CODEC_FLAG_GLOBAL_H AV_CODEC_FLAG_GLOBAL_HEADER +#else +#define CODEC_CAP_TRUNC CODEC_CAP_TRUNCATED +#define CODEC_FLAG_TRUNC CODEC_FLAG_TRUNCATED +#define CODEC_FLAG_GLOBAL_H CODEC_FLAG_GLOBAL_HEADER +#endif diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c index 286ae13..9b65276 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c @@ -282,10 +282,31 @@ static bool ffmpeg_mux_start(void *data) settings = obs_output_get_settings(stream->output); path = obs_data_get_string(settings, "path"); + + /* ensure output path is writable to avoid generic error message */ + /* TODO: remove once ffmpeg-mux is refactored to pass errors back */ + FILE *test_file = os_fopen(path, "wb"); + if (!test_file) { + struct dstr error_message; + dstr_init_copy(&error_message, + obs_module_text("UnableToWritePath")); + dstr_replace(&error_message, "%1", path); + obs_output_set_last_error(stream->output, + error_message.array); + dstr_free(&error_message); + obs_data_release(settings); + return false; + } + + fclose(test_file); + os_unlink(path); + start_pipe(stream, path); obs_data_release(settings); if (!stream->pipe) { + obs_output_set_last_error(stream->output, + obs_module_text("HelperProcessFailed")); warn("Failed to create process pipe"); return false; } @@ -492,7 +513,7 @@ static const char *replay_buffer_getname(void *type) return obs_module_text("ReplayBuffer"); } -static bool replay_buffer_hotkey(void *data, obs_hotkey_id id, +static void replay_buffer_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) { UNUSED_PARAMETER(id); @@ -502,7 +523,6 @@ static bool replay_buffer_hotkey(void *data, obs_hotkey_id id, struct ffmpeg_muxer *stream = data; if (os_atomic_load_bool(&stream->active)) stream->save_ts = os_gettime_ns() / 1000LL; - return true; } static void save_replay_proc(void *data, calldata_t *cd) @@ -511,8 +531,16 @@ static void save_replay_proc(void *data, calldata_t *cd) UNUSED_PARAMETER(cd); } +static void get_last_replay(void *data, calldata_t *cd) +{ + struct ffmpeg_muxer *stream = data; + if (!os_atomic_load_bool(&stream->muxing)) + calldata_set_string(cd, "path", stream->path.array); +} + static void *replay_buffer_create(obs_data_t *settings, obs_output_t *output) { + UNUSED_PARAMETER(settings); struct ffmpeg_muxer *stream = bzalloc(sizeof(*stream)); stream->output = output; @@ -523,8 +551,9 @@ static void *replay_buffer_create(obs_data_t *settings, obs_output_t *output) proc_handler_t *ph = obs_output_get_proc_handler(output); proc_handler_add(ph, "void save()", save_replay_proc, stream); + proc_handler_add(ph, "void get_last_replay(out string path)", + get_last_replay, stream); - UNUSED_PARAMETER(settings); return stream; } diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c index 017d257..16cd32e 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c @@ -42,7 +42,6 @@ struct nvenc_encoder { AVCodec *nvenc; AVCodecContext *context; - AVPicture dst_picture; AVFrame *vframe; DARRAY(uint8_t) buffer; @@ -108,16 +107,13 @@ static bool nvenc_init_codec(struct nvenc_encoder *enc) enc->vframe->colorspace = enc->context->colorspace; enc->vframe->color_range = enc->context->color_range; - ret = avpicture_alloc(&enc->dst_picture, enc->context->pix_fmt, - enc->context->width, enc->context->height); + ret = av_frame_get_buffer(enc->vframe, base_get_alignment()); if (ret < 0) { - warn("Failed to allocate dst_picture: %s", av_err2str(ret)); + warn("Failed to allocate vframe: %s", av_err2str(ret)); return false; } enc->initialized = true; - - *((AVPicture*)enc->vframe) = enc->dst_picture; return true; } @@ -245,18 +241,23 @@ static void nvenc_destroy(void *data) int r_pkt = 1; while (r_pkt) { +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) + if (avcodec_receive_packet(enc->context, &pkt) < 0) + break; +#else if (avcodec_encode_video2(enc->context, &pkt, NULL, &r_pkt) < 0) break; +#endif if (r_pkt) - av_free_packet(&pkt); + av_packet_unref(&pkt); } } avcodec_close(enc->context); + av_frame_unref(enc->vframe); av_frame_free(&enc->vframe); - avpicture_free(&enc->dst_picture); da_free(enc->buffer); bfree(enc->header); bfree(enc->sei); @@ -300,7 +301,7 @@ fail: return NULL; } -static inline void copy_data(AVPicture *pic, const struct encoder_frame *frame, +static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame, int height, enum AVPixelFormat format) { int h_chroma_shift, v_chroma_shift; @@ -336,11 +337,22 @@ static bool nvenc_encode(void *data, struct encoder_frame *frame, av_init_packet(&av_pkt); - copy_data(&enc->dst_picture, frame, enc->height, enc->context->pix_fmt); + copy_data(enc->vframe, frame, enc->height, enc->context->pix_fmt); enc->vframe->pts = frame->pts; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) + ret = avcodec_send_frame(enc->context, enc->vframe); + if (ret == 0) + ret = avcodec_receive_packet(enc->context, &av_pkt); + + got_packet = (ret == 0); + + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) + ret = 0; +#else ret = avcodec_encode_video2(enc->context, &av_pkt, enc->vframe, &got_packet); +#endif if (ret < 0) { warn("nvenc_encode: Error encoding: %s", av_err2str(ret)); return false; @@ -374,7 +386,7 @@ static bool nvenc_encode(void *data, struct encoder_frame *frame, *received_packet = false; } - av_free_packet(&av_pkt); + av_packet_unref(&av_pkt); return true; } diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-output.c b/plugins/obs-ffmpeg/obs-ffmpeg-output.c index 6cee357..facc171 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-output.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-output.c @@ -63,7 +63,6 @@ struct ffmpeg_data { struct SwsContext *swscale; int64_t total_frames; - AVPicture dst_picture; AVFrame *vframe; int frame_size; @@ -198,15 +197,13 @@ static bool open_video_codec(struct ffmpeg_data *data) data->vframe->colorspace = data->config.color_space; data->vframe->color_range = data->config.color_range; - ret = avpicture_alloc(&data->dst_picture, context->pix_fmt, - context->width, context->height); + ret = av_frame_get_buffer(data->vframe, base_get_alignment()); if (ret < 0) { - blog(LOG_WARNING, "Failed to allocate dst_picture: %s", + blog(LOG_WARNING, "Failed to allocate vframe: %s", av_err2str(ret)); return false; } - *((AVPicture*)data->vframe) = data->dst_picture; return true; } @@ -260,7 +257,7 @@ static bool create_video_stream(struct ffmpeg_data *data) data->video->time_base = context->time_base; if (data->output->oformat->flags & AVFMT_GLOBALHEADER) - context->flags |= CODEC_FLAG_GLOBAL_HEADER; + context->flags |= CODEC_FLAG_GLOBAL_H; if (!open_video_codec(data)) return false; @@ -337,6 +334,11 @@ static bool create_audio_stream(struct ffmpeg_data *data) context->sample_rate = aoi.samples_per_sec; context->channel_layout = av_get_default_channel_layout(context->channels); + + //AVlib default channel layout for 5 channels is 5.0 ; fix for 4.1 + if (aoi.speakers == SPEAKERS_4POINT1) + context->channel_layout = av_get_channel_layout("4.1"); + context->sample_fmt = data->acodec->sample_fmts ? data->acodec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; @@ -348,7 +350,7 @@ static bool create_audio_stream(struct ffmpeg_data *data) data->audio_size = get_audio_size(data->audio_format, aoi.speakers, 1); if (data->output->oformat->flags & AVFMT_GLOBALHEADER) - context->flags |= CODEC_FLAG_GLOBAL_HEADER; + context->flags |= CODEC_FLAG_GLOBAL_H; return open_audio_codec(data); } @@ -373,20 +375,6 @@ static inline bool open_output_file(struct ffmpeg_data *data) AVOutputFormat *format = data->output->oformat; int ret; - if ((format->flags & AVFMT_NOFILE) == 0) { - ret = avio_open(&data->output->pb, data->config.url, - AVIO_FLAG_WRITE); - if (ret < 0) { - blog(LOG_WARNING, "Couldn't open '%s', %s", - data->config.url, av_err2str(ret)); - return false; - } - } - - strncpy(data->output->filename, data->config.url, - sizeof(data->output->filename)); - data->output->filename[sizeof(data->output->filename) - 1] = 0; - AVDictionary *dict = NULL; if ((ret = av_dict_parse_string(&dict, data->config.muxer_settings, "=", " ", 0))) { @@ -405,10 +393,25 @@ static inline bool open_output_file(struct ffmpeg_data *data) AV_DICT_IGNORE_SUFFIX))) dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value); - blog(LOG_INFO, "Using muxer settings:%s", str.array); + blog(LOG_INFO, "Using muxer settings: %s", str.array); dstr_free(&str); } + if ((format->flags & AVFMT_NOFILE) == 0) { + ret = avio_open2(&data->output->pb, data->config.url, + AVIO_FLAG_WRITE, NULL, &dict); + if (ret < 0) { + blog(LOG_WARNING, "Couldn't open '%s', %s", + data->config.url, av_err2str(ret)); + av_dict_free(&dict); + return false; + } + } + + strncpy(data->output->filename, data->config.url, + sizeof(data->output->filename)); + data->output->filename[sizeof(data->output->filename) - 1] = 0; + ret = avformat_write_header(data->output, &dict); if (ret < 0) { blog(LOG_WARNING, "Error opening '%s': %s", @@ -416,6 +419,18 @@ static inline bool open_output_file(struct ffmpeg_data *data) return false; } + if (av_dict_count(dict) > 0) { + struct dstr str = {0}; + + AVDictionaryEntry *entry = NULL; + while ((entry = av_dict_get(dict, "", entry, + AV_DICT_IGNORE_SUFFIX))) + dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value); + + blog(LOG_INFO, "Invalid muxer settings: %s", str.array); + dstr_free(&str); + } + av_dict_free(&dict); return true; @@ -424,7 +439,7 @@ static inline bool open_output_file(struct ffmpeg_data *data) static void close_video(struct ffmpeg_data *data) { avcodec_close(data->video->codec); - avpicture_free(&data->dst_picture); + av_frame_unref(data->vframe); // This format for some reason derefs video frame // too many times @@ -632,7 +647,7 @@ static void ffmpeg_output_destroy(void *data) } } -static inline void copy_data(AVPicture *pic, const struct video_data *frame, +static inline void copy_data(AVFrame *pic, const struct video_data *frame, int height, enum AVPixelFormat format) { int h_chroma_shift, v_chroma_shift; @@ -681,15 +696,15 @@ static void receive_video(void *param, struct video_data *frame) if (!!data->swscale) sws_scale(data->swscale, (const uint8_t *const *)frame->data, (const int*)frame->linesize, - 0, data->config.height, data->dst_picture.data, - data->dst_picture.linesize); + 0, data->config.height, data->vframe->data, + data->vframe->linesize); else - copy_data(&data->dst_picture, frame, context->height, context->pix_fmt); - + copy_data(data->vframe, frame, context->height, context->pix_fmt); +#if LIBAVFORMAT_VERSION_MAJOR < 58 if (data->output->flags & AVFMT_RAWPICTURE) { packet.flags |= AV_PKT_FLAG_KEY; packet.stream_index = data->video->index; - packet.data = data->dst_picture.data[0]; + packet.data = data->vframe->data[0]; packet.size = sizeof(AVPicture); pthread_mutex_lock(&output->write_mutex); @@ -698,9 +713,21 @@ static void receive_video(void *param, struct video_data *frame) os_sem_post(output->write_sem); } else { +#endif data->vframe->pts = data->total_frames; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) + ret = avcodec_send_frame(context, data->vframe); + if (ret == 0) + ret = avcodec_receive_packet(context, &packet); + + got_packet = (ret == 0); + + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) + ret = 0; +#else ret = avcodec_encode_video2(context, &packet, data->vframe, &got_packet); +#endif if (ret < 0) { blog(LOG_WARNING, "receive_video: Error encoding " "video: %s", av_err2str(ret)); @@ -723,8 +750,9 @@ static void receive_video(void *param, struct video_data *frame) } else { ret = 0; } +#if LIBAVFORMAT_VERSION_MAJOR < 58 } - +#endif if (ret != 0) { blog(LOG_WARNING, "receive_video: Error writing video: %s", av_err2str(ret)); @@ -758,8 +786,19 @@ static void encode_audio(struct ffmpeg_output *output, data->total_samples += data->frame_size; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101) + ret = avcodec_send_frame(context, data->aframe); + if (ret == 0) + ret = avcodec_receive_packet(context, &packet); + + got_packet = (ret == 0); + + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) + ret = 0; +#else ret = avcodec_encode_audio2(context, &packet, data->aframe, &got_packet); +#endif if (ret < 0) { blog(LOG_WARNING, "encode_audio: Error encoding audio: %s", av_err2str(ret)); diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-source.c b/plugins/obs-ffmpeg/obs-ffmpeg-source.c index d75ca79..da2a992 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-source.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-source.c @@ -31,9 +31,6 @@ #define FF_BLOG(level, format, ...) \ FF_LOG_S(s->source, level, format, ##__VA_ARGS__) -static bool video_frame(struct ff_frame *frame, void *opaque); -static bool video_format(AVCodecContext *codec_context, void *opaque); - struct ffmpeg_source { mp_media_t media; bool media_valid; @@ -58,6 +55,7 @@ struct ffmpeg_source { bool is_clear_on_media_end; bool restart_on_activate; bool close_when_inactive; + bool seekable; }; static bool is_local_file_modified(obs_properties_t *props, @@ -73,12 +71,14 @@ static bool is_local_file_modified(obs_properties_t *props, obs_property_t *looping = obs_properties_get(props, "looping"); obs_property_t *buffering = obs_properties_get(props, "buffering_mb"); obs_property_t *close = obs_properties_get(props, "close_when_inactive"); + obs_property_t *seekable = obs_properties_get(props, "seekable"); obs_property_set_visible(input, !enabled); obs_property_set_visible(input_format, !enabled); obs_property_set_visible(buffering, !enabled); obs_property_set_visible(close, enabled); obs_property_set_visible(local_file, enabled); obs_property_set_visible(looping, enabled); + obs_property_set_visible(seekable, !enabled); return true; } @@ -181,6 +181,8 @@ static obs_properties_t *ffmpeg_source_getproperties(void *data) obs_property_list_add_int(prop, obs_module_text("ColorRange.Full"), VIDEO_RANGE_FULL); + obs_properties_add_bool(props, "seekable", obs_module_text("Seekable")); + return props; } @@ -232,7 +234,7 @@ static void media_stopped(void *opaque) struct ffmpeg_source *s = opaque; if (s->is_clear_on_media_end) { obs_source_output_video(s->source, NULL); - if (s->close_when_inactive) + if (s->close_when_inactive && s->media_valid) s->destroy_media = true; } } @@ -244,11 +246,16 @@ static void ffmpeg_source_open(struct ffmpeg_source *s) s->input, s->input_format, s->buffering_mb * 1024 * 1024, s, get_frame, get_audio, media_stopped, - preload_frame, s->is_hw_decoding, s->range); + preload_frame, + s->is_hw_decoding, + s->is_local_file || s->seekable, + s->range); } static void ffmpeg_source_tick(void *data, float seconds) { + UNUSED_PARAMETER(seconds); + struct ffmpeg_source *s = data; if (s->destroy_media) { if (s->media_valid) { @@ -314,6 +321,7 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings) "color_range"); s->buffering_mb = (int)obs_data_get_int(settings, "buffering_mb"); s->is_local_file = is_local_file; + s->seekable = obs_data_get_bool(settings, "seekable"); if (s->media_valid) { mp_media_free(&s->media); @@ -335,7 +343,7 @@ static const char *ffmpeg_source_getname(void *unused) return obs_module_text("FFMpegSource"); } -static bool restart_hotkey(void *data, obs_hotkey_id id, +static void restart_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) { UNUSED_PARAMETER(id); @@ -345,7 +353,6 @@ static bool restart_hotkey(void *data, obs_hotkey_id id, struct ffmpeg_source *s = data; if (obs_source_active(s->source)) ffmpeg_source_start(s); - return true; } static void restart_proc(void *data, calldata_t *cd) @@ -354,6 +361,53 @@ static void restart_proc(void *data, calldata_t *cd) UNUSED_PARAMETER(cd); } +static void get_duration(void *data, calldata_t *cd) +{ + struct ffmpeg_source *s = data; + int64_t dur = 0; + if (s->media.fmt) + dur = s->media.fmt->duration; + + calldata_set_int(cd, "duration", dur * 1000); +} + +static void get_nb_frames(void *data, calldata_t *cd) +{ + struct ffmpeg_source *s = data; + int64_t frames = 0; + + if (!s->media.fmt) { + calldata_set_int(cd, "num_frames", frames); + return; + } + + int video_stream_index = av_find_best_stream(s->media.fmt, + AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + + if (video_stream_index < 0) { + FF_BLOG(LOG_WARNING, "Getting number of frames failed: No " + "video stream in media file!"); + calldata_set_int(cd, "num_frames", frames); + return; + } + + AVStream *stream = s->media.fmt->streams[video_stream_index]; + + if (stream->nb_frames > 0) { + frames = stream->nb_frames; + } else { + FF_BLOG(LOG_DEBUG, "nb_frames not set, estimating using frame " + "rate and duration"); + AVRational avg_frame_rate = stream->avg_frame_rate; + frames = (int64_t)ceil((double)s->media.fmt->duration / + (double)AV_TIME_BASE * + (double)avg_frame_rate.num / + (double)avg_frame_rate.den); + } + + calldata_set_int(cd, "num_frames", frames); +} + static void *ffmpeg_source_create(obs_data_t *settings, obs_source_t *source) { UNUSED_PARAMETER(settings); @@ -368,6 +422,10 @@ static void *ffmpeg_source_create(obs_data_t *settings, obs_source_t *source) proc_handler_t *ph = obs_source_get_proc_handler(source); proc_handler_add(ph, "void restart()", restart_proc, s); + proc_handler_add(ph, "void get_duration(out int duration)", + get_duration, s); + proc_handler_add(ph, "void get_nb_frames(out int num_frames)", + get_nb_frames, s); ffmpeg_source_update(s, settings); return s; diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c index ce6de77..3ccef9b 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg.c @@ -3,6 +3,7 @@ #include #include #include +#include #include OBS_DECLARE_MODULE() @@ -13,6 +14,7 @@ extern struct obs_output_info ffmpeg_output; extern struct obs_output_info ffmpeg_muxer; extern struct obs_output_info replay_buffer; extern struct obs_encoder_info aac_encoder_info; +extern struct obs_encoder_info opus_encoder_info; extern struct obs_encoder_info nvenc_encoder_info; static DARRAY(struct log_context { @@ -115,13 +117,23 @@ cleanup: destroy_log_context(log_context); } +#ifndef __APPLE__ + +static const char *nvenc_check_name = "nvenc_check"; + static bool nvenc_supported(void) { + av_register_all(); + + profile_start(nvenc_check_name); + AVCodec *nvenc = avcodec_find_encoder_by_name("nvenc_h264"); void *lib = NULL; + bool success = false; - if (!nvenc) - return false; + if (!nvenc) { + goto cleanup; + } #if defined(_WIN32) if (sizeof(void*) == 8) { @@ -132,10 +144,20 @@ static bool nvenc_supported(void) #else lib = os_dlopen("libnvidia-encode.so.1"); #endif - os_dlclose(lib); - return !!lib; + + /* ------------------------------------------- */ + + success = !!lib; + +cleanup: + if (lib) + os_dlclose(lib); + profile_end(nvenc_check_name); + return success; } +#endif + bool obs_module_load(void) { da_init(active_log_contexts); @@ -148,10 +170,13 @@ bool obs_module_load(void) obs_register_output(&ffmpeg_muxer); obs_register_output(&replay_buffer); obs_register_encoder(&aac_encoder_info); + obs_register_encoder(&opus_encoder_info); +#ifndef __APPLE__ if (nvenc_supported()) { blog(LOG_INFO, "NVENC supported"); obs_register_encoder(&nvenc_encoder_info); } +#endif return true; } diff --git a/plugins/obs-filters/CMakeLists.txt b/plugins/obs-filters/CMakeLists.txt index c88da31..ec4289c 100644 --- a/plugins/obs-filters/CMakeLists.txt +++ b/plugins/obs-filters/CMakeLists.txt @@ -18,6 +18,11 @@ set(obs-filters_config_HEADERS include_directories(${LIBSPEEXDSP_INCLUDE_DIRS} "${CMAKE_BINARY_DIR}/plugins/obs-filters/config") +if(MSVC) + set(obs-filters_PLATFORM_DEPS + w32-pthreads) +endif() + set(obs-filters_SOURCES obs-filters.c color-correction-filter.c @@ -41,6 +46,7 @@ add_library(obs-filters MODULE ${obs-filters_LIBSPEEXDSP_SOURCES}) target_link_libraries(obs-filters libobs + ${obs-filters_PLATFORM_DEPS} ${obs-filters_LIBSPEEXDSP_LIBRARIES}) install_obs_plugin_with_data(obs-filters data) diff --git a/plugins/obs-filters/color-grade-filter.c b/plugins/obs-filters/color-grade-filter.c index 8adfdbf..ffe7d57 100644 --- a/plugins/obs-filters/color-grade-filter.c +++ b/plugins/obs-filters/color-grade-filter.c @@ -34,6 +34,8 @@ static void color_grade_filter_update(void *data, obs_data_t *settings) bfree(filter->file); if (path) filter->file = bstrdup(path); + else + filter->file = NULL; obs_enter_graphics(); gs_image_file_free(&filter->image); @@ -75,8 +77,10 @@ static obs_properties_t *color_grade_filter_properties(void *data) if (s && s->file && *s->file) { dstr_copy(&path, s->file); } else { - dstr_copy(&path, obs_module_file("LUTs")); + char *lut_dir = obs_module_file("LUTs"); + dstr_copy(&path, lut_dir); dstr_cat_ch(&path, '/'); + bfree(lut_dir); } dstr_replace(&path, "\\", "/"); @@ -90,6 +94,7 @@ static obs_properties_t *color_grade_filter_properties(void *data) TEXT_AMOUNT, 0, 1, 0.01); dstr_free(&filter_str); + dstr_free(&path); UNUSED_PARAMETER(data); return props; diff --git a/plugins/obs-filters/compressor-filter.c b/plugins/obs-filters/compressor-filter.c index 4d561a4..821016b 100644 --- a/plugins/obs-filters/compressor-filter.c +++ b/plugins/obs-filters/compressor-filter.c @@ -4,6 +4,9 @@ #include #include +#include +#include +#include /* -------------------------------------------------------- */ @@ -27,6 +30,7 @@ #define S_ATTACK_TIME "attack_time" #define S_RELEASE_TIME "release_time" #define S_OUTPUT_GAIN "output_gain" +#define S_SIDECHAIN_SOURCE "sidechain_source" #define MT_ obs_module_text #define TEXT_RATIO MT_("Compressor.Ratio") @@ -34,6 +38,7 @@ #define TEXT_ATTACK_TIME MT_("Compressor.AttackTime") #define TEXT_RELEASE_TIME MT_("Compressor.ReleaseTime") #define TEXT_OUTPUT_GAIN MT_("Compressor.OutputGain") +#define TEXT_SIDECHAIN_SOURCE MT_("Compressor.SidechainSource") #define MIN_RATIO 1.0f #define MAX_RATIO 32.0f @@ -63,16 +68,66 @@ struct compressor_data { float output_gain; size_t num_channels; + size_t sample_rate; float envelope; float slope; + + pthread_mutex_t sidechain_update_mutex; + uint64_t sidechain_check_time; + obs_weak_source_t *weak_sidechain; + char *sidechain_name; + + pthread_mutex_t sidechain_mutex; + struct circlebuf sidechain_data[MAX_AUDIO_CHANNELS]; + float *sidechain_buf[MAX_AUDIO_CHANNELS]; + size_t max_sidechain_frames; }; /* -------------------------------------------------------- */ -static inline void resize_env_buffer(struct compressor_data *cd, size_t len) +static inline obs_source_t *get_sidechain(struct compressor_data *cd) +{ + if (cd->weak_sidechain) + return obs_weak_source_get_source(cd->weak_sidechain); + return NULL; +} + +static inline void get_sidechain_data(struct compressor_data *cd, + const uint32_t num_samples) +{ + size_t data_size = cd->envelope_buf_len * sizeof(float); + if (!data_size) + return; + + pthread_mutex_lock(&cd->sidechain_mutex); + if (cd->max_sidechain_frames < num_samples) + cd->max_sidechain_frames = num_samples; + + if (cd->sidechain_data[0].size < data_size) { + pthread_mutex_unlock(&cd->sidechain_mutex); + goto clear; + } + + for (size_t i = 0; i < cd->num_channels; i++) + circlebuf_pop_front(&cd->sidechain_data[i], + cd->sidechain_buf[i], data_size); + + pthread_mutex_unlock(&cd->sidechain_mutex); + return; + +clear: + for (size_t i = 0; i < cd->num_channels; i++) + memset(cd->sidechain_buf[i], 0, data_size); +} + +static void resize_env_buffer(struct compressor_data *cd, size_t len) { cd->envelope_buf_len = len; cd->envelope_buf = brealloc(cd->envelope_buf, len * sizeof(float)); + + for (size_t i = 0; i < cd->num_channels; i++) + cd->sidechain_buf[i] = brealloc(cd->sidechain_buf[i], + len * sizeof(float)); } static inline float gain_coefficient(uint32_t sample_rate, float time) @@ -86,6 +141,47 @@ static const char *compressor_name(void *unused) return obs_module_text("Compressor"); } +static void sidechain_capture(void *param, obs_source_t *source, + const struct audio_data *audio_data, bool muted) +{ + struct compressor_data *cd = param; + + UNUSED_PARAMETER(source); + + pthread_mutex_lock(&cd->sidechain_mutex); + + if (cd->max_sidechain_frames < audio_data->frames) + cd->max_sidechain_frames = audio_data->frames; + + size_t expected_size = cd->max_sidechain_frames * sizeof(float); + + if (!expected_size) + goto unlock; + + if (cd->sidechain_data[0].size > expected_size * 2) { + for (size_t i = 0; i < cd->num_channels; i++) { + circlebuf_pop_front(&cd->sidechain_data[i], NULL, + expected_size); + } + } + + if (muted) { + for (size_t i = 0; i < cd->num_channels; i++) { + circlebuf_push_back_zero(&cd->sidechain_data[i], + audio_data->frames * sizeof(float)); + } + } else { + for (size_t i = 0; i < cd->num_channels; i++) { + circlebuf_push_back(&cd->sidechain_data[i], + audio_data->data[i], + audio_data->frames * sizeof(float)); + } + } + +unlock: + pthread_mutex_unlock(&cd->sidechain_mutex); +} + static void compressor_update(void *data, obs_data_t *s) { struct compressor_data *cd = data; @@ -100,11 +196,8 @@ static void compressor_update(void *data, obs_data_t *s) (float)obs_data_get_int(s, S_RELEASE_TIME); const float output_gain_db = (float)obs_data_get_double(s, S_OUTPUT_GAIN); - - if (cd->envelope_buf_len <= 0) { - resize_env_buffer(cd, - sample_rate * DEFAULT_AUDIO_BUF_MS / MS_IN_S); - } + const char *sidechain_name = + obs_data_get_string(s, S_SIDECHAIN_SOURCE); cd->ratio = (float)obs_data_get_double(s, S_RATIO); cd->threshold = (float)obs_data_get_double(s, S_THRESHOLD); @@ -114,13 +207,76 @@ static void compressor_update(void *data, obs_data_t *s) release_time_ms / MS_IN_S_F); cd->output_gain = db_to_mul(output_gain_db); cd->num_channels = num_channels; + cd->sample_rate = sample_rate; cd->slope = 1.0f - (1.0f / cd->ratio); + + bool valid_sidechain = + *sidechain_name && strcmp(sidechain_name, "none") != 0; + obs_weak_source_t *old_weak_sidechain = NULL; + + pthread_mutex_lock(&cd->sidechain_update_mutex); + + if (!valid_sidechain) { + if (cd->weak_sidechain) { + old_weak_sidechain = cd->weak_sidechain; + cd->weak_sidechain = NULL; + } + + bfree(cd->sidechain_name); + cd->sidechain_name = NULL; + + } else { + if (!cd->sidechain_name || + strcmp(cd->sidechain_name, sidechain_name) != 0) { + if (cd->weak_sidechain) { + old_weak_sidechain = cd->weak_sidechain; + cd->weak_sidechain = NULL; + } + + bfree(cd->sidechain_name); + cd->sidechain_name = bstrdup(sidechain_name); + cd->sidechain_check_time = os_gettime_ns() - 3000000000; + } + } + + pthread_mutex_unlock(&cd->sidechain_update_mutex); + + if (old_weak_sidechain) { + obs_source_t *old_sidechain = + obs_weak_source_get_source(old_weak_sidechain); + + if (old_sidechain) { + obs_source_remove_audio_capture_callback(old_sidechain, + sidechain_capture, cd); + obs_source_release(old_sidechain); + } + + obs_weak_source_release(old_weak_sidechain); + } + + size_t sample_len = sample_rate * DEFAULT_AUDIO_BUF_MS / MS_IN_S; + if (cd->envelope_buf_len == 0) + resize_env_buffer(cd, sample_len); } static void *compressor_create(obs_data_t *settings, obs_source_t *filter) { struct compressor_data *cd = bzalloc(sizeof(struct compressor_data)); cd->context = filter; + + if (pthread_mutex_init(&cd->sidechain_mutex, NULL) != 0) { + blog(LOG_ERROR, "Failed to create mutex"); + bfree(cd); + return NULL; + } + + if (pthread_mutex_init(&cd->sidechain_update_mutex, NULL) != 0) { + pthread_mutex_destroy(&cd->sidechain_mutex); + blog(LOG_ERROR, "Failed to create mutex"); + bfree(cd); + return NULL; + } + compressor_update(cd, settings); return cd; } @@ -128,33 +284,89 @@ static void *compressor_create(obs_data_t *settings, obs_source_t *filter) static void compressor_destroy(void *data) { struct compressor_data *cd = data; + + if (cd->weak_sidechain) { + obs_source_t *sidechain = get_sidechain(cd); + if (sidechain) { + obs_source_remove_audio_capture_callback(sidechain, + sidechain_capture, cd); + obs_source_release(sidechain); + } + + obs_weak_source_release(cd->weak_sidechain); + } + + for (size_t i = 0; i < MAX_AUDIO_CHANNELS; i++) { + circlebuf_free(&cd->sidechain_data[i]); + bfree(cd->sidechain_buf[i]); + } + pthread_mutex_destroy(&cd->sidechain_mutex); + pthread_mutex_destroy(&cd->sidechain_update_mutex); + + bfree(cd->sidechain_name); bfree(cd->envelope_buf); bfree(cd); } -static inline void analyze_envelope(struct compressor_data *cd, - const float **samples, const uint32_t num_samples) +static void analyze_envelope(struct compressor_data *cd, + float **samples, const uint32_t num_samples) { if (cd->envelope_buf_len < num_samples) { resize_env_buffer(cd, num_samples); } + const float attack_gain = cd->attack_gain; + const float release_gain = cd->release_gain; + memset(cd->envelope_buf, 0, num_samples * sizeof(cd->envelope_buf[0])); for (size_t chan = 0; chan < cd->num_channels; ++chan) { - if (samples[chan]) { - float env = cd->envelope; - for (uint32_t i = 0; i < num_samples; ++i) { - const float env_in = fabsf(samples[chan][i]); - if (env < env_in) { - env = env_in + cd->attack_gain * - (env - env_in); - } else { - env = env_in + cd->release_gain * - (env - env_in); - } - cd->envelope_buf[i] = fmaxf( - cd->envelope_buf[i], env); + if (!samples[chan]) + continue; + + float *envelope_buf = cd->envelope_buf; + float env = cd->envelope; + for (uint32_t i = 0; i < num_samples; ++i) { + const float env_in = fabsf(samples[chan][i]); + if (env < env_in) { + env = env_in + attack_gain * (env - env_in); + } else { + env = env_in + release_gain * (env - env_in); } + envelope_buf[i] = fmaxf(envelope_buf[i], env); + } + } + cd->envelope = cd->envelope_buf[num_samples - 1]; +} + +static void analyze_sidechain(struct compressor_data *cd, + const uint32_t num_samples) +{ + if (cd->envelope_buf_len < num_samples) { + resize_env_buffer(cd, num_samples); + } + + get_sidechain_data(cd, num_samples); + + const float attack_gain = cd->attack_gain; + const float release_gain = cd->release_gain; + float **sidechain_buf = cd->sidechain_buf; + + memset(cd->envelope_buf, 0, num_samples * sizeof(cd->envelope_buf[0])); + for (size_t chan = 0; chan < cd->num_channels; ++chan) { + if (!sidechain_buf[chan]) + continue; + + float *envelope_buf = cd->envelope_buf; + float env = cd->envelope; + for (uint32_t i = 0; i < num_samples; ++i) { + const float env_in = fabsf(sidechain_buf[chan][i]); + + if (env < env_in) { + env = env_in + attack_gain * (env - env_in); + } else { + env = env_in + release_gain * (env - env_in); + } + envelope_buf[i] = fmaxf(envelope_buf[i], env); } } cd->envelope = cd->envelope_buf[num_samples - 1]; @@ -176,16 +388,70 @@ static inline void process_compression(const struct compressor_data *cd, } } +static void compressor_tick(void *data, float seconds) +{ + struct compressor_data *cd = data; + char *new_name = NULL; + + pthread_mutex_lock(&cd->sidechain_update_mutex); + + if (cd->sidechain_name && !cd->weak_sidechain) { + uint64_t t = os_gettime_ns(); + + if (t - cd->sidechain_check_time > 3000000000) { + new_name = bstrdup(cd->sidechain_name); + cd->sidechain_check_time = t; + } + } + + pthread_mutex_unlock(&cd->sidechain_update_mutex); + + if (new_name) { + obs_source_t *sidechain = new_name && *new_name ? + obs_get_source_by_name(new_name) : NULL; + obs_weak_source_t *weak_sidechain = sidechain ? + obs_source_get_weak_source(sidechain) : NULL; + + pthread_mutex_lock(&cd->sidechain_update_mutex); + + if (cd->sidechain_name && + strcmp(cd->sidechain_name, new_name) == 0) { + cd->weak_sidechain = weak_sidechain; + weak_sidechain = NULL; + } + + pthread_mutex_unlock(&cd->sidechain_update_mutex); + + if (sidechain) { + obs_source_add_audio_capture_callback(sidechain, + sidechain_capture, cd); + + obs_weak_source_release(weak_sidechain); + obs_source_release(sidechain); + } + + bfree(new_name); + } +} + static struct obs_audio_data *compressor_filter_audio(void *data, struct obs_audio_data *audio) { struct compressor_data *cd = data; + const uint32_t num_samples = audio->frames; float **samples = (float**)audio->data; - analyze_envelope(cd, samples, num_samples); - process_compression(cd, samples, num_samples); + pthread_mutex_lock(&cd->sidechain_update_mutex); + obs_weak_source_t *weak_sidechain = cd->weak_sidechain; + pthread_mutex_unlock(&cd->sidechain_update_mutex); + if (weak_sidechain) + analyze_sidechain(cd, num_samples); + else + analyze_envelope(cd, samples, num_samples); + + process_compression(cd, samples, num_samples); return audio; } @@ -196,11 +462,37 @@ static void compressor_defaults(obs_data_t *s) obs_data_set_default_int(s, S_ATTACK_TIME, 6); obs_data_set_default_int(s, S_RELEASE_TIME, 60); obs_data_set_default_double(s, S_OUTPUT_GAIN, 0.0f); + obs_data_set_default_string(s, S_SIDECHAIN_SOURCE, "none"); +} + +struct sidechain_prop_info { + obs_property_t *sources; + obs_source_t *parent; +}; + +static bool add_sources(void *data, obs_source_t *source) +{ + struct sidechain_prop_info *info = data; + uint32_t caps = obs_source_get_output_flags(source); + + if (source == info->parent) + return true; + if ((caps & OBS_SOURCE_AUDIO) == 0) + return true; + + const char *name = obs_source_get_name(source); + obs_property_list_add_string(info->sources, name, name); + return true; } static obs_properties_t *compressor_properties(void *data) { + struct compressor_data *cd = data; obs_properties_t *props = obs_properties_create(); + obs_source_t *parent = NULL; + + if (cd) + parent = obs_filter_get_parent(cd->context); obs_properties_add_float_slider(props, S_RATIO, TEXT_RATIO, MIN_RATIO, MAX_RATIO, 0.5f); @@ -213,6 +505,15 @@ static obs_properties_t *compressor_properties(void *data) obs_properties_add_float_slider(props, S_OUTPUT_GAIN, TEXT_OUTPUT_GAIN, MIN_OUTPUT_GAIN_DB, MAX_OUTPUT_GAIN_DB, 0.1f); + obs_property_t *sources = obs_properties_add_list(props, + S_SIDECHAIN_SOURCE, TEXT_SIDECHAIN_SOURCE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + + obs_property_list_add_string(sources, obs_module_text("None"), "none"); + + struct sidechain_prop_info info = {sources, parent}; + obs_enum_sources(add_sources, &info); + UNUSED_PARAMETER(data); return props; } @@ -226,6 +527,7 @@ struct obs_source_info compressor_filter = { .destroy = compressor_destroy, .update = compressor_update, .filter_audio = compressor_filter_audio, + .video_tick = compressor_tick, .get_defaults = compressor_defaults, .get_properties = compressor_properties, }; diff --git a/plugins/obs-filters/data/color_grade_filter.effect b/plugins/obs-filters/data/color_grade_filter.effect index bbc8b5e..02e02a8 100644 --- a/plugins/obs-filters/data/color_grade_filter.effect +++ b/plugins/obs-filters/data/color_grade_filter.effect @@ -53,7 +53,8 @@ float4 LUT(VertDataOut v_in) : TARGET float4 newColor2 = clut.Sample(textureSampler, texPos2); float4 luttedColor = lerp(newColor1, newColor2, frac(blueColor)); - return lerp(textureColor, luttedColor, clut_amount); + float4 final_color = lerp(textureColor, luttedColor, clut_amount); + return float4(final_color.rgb, textureColor.a); } technique Draw diff --git a/plugins/obs-filters/data/locale/ca-ES.ini b/plugins/obs-filters/data/locale/ca-ES.ini index 0229054..455357a 100644 --- a/plugins/obs-filters/data/locale/ca-ES.ini +++ b/plugins/obs-filters/data/locale/ca-ES.ini @@ -8,6 +8,7 @@ ChromaKeyFilter="Clau croma" ColorKeyFilter="Clau de color" SharpnessFilter="Agudesa" ScaleFilter="Escala/Relació d'Aspecte" +GPUDelayFilter="Retard de processament" UndistortCenter="No distorsionis el centre de la imatge en escalar des d'una ultrapanoràmica" NoiseGate="Porta de soroll" NoiseSuppress="Supressió de soroll" @@ -72,4 +73,5 @@ Compressor.Threshold="Llindar (dB)" Compressor.AttackTime="Atac (ms)" Compressor.ReleaseTime="Llançament (ms)" Compressor.OutputGain="Guany de sortida (dB)" +Compressor.SidechainSource="Font d'atenuació/reducció" diff --git a/plugins/obs-filters/data/locale/cs-CZ.ini b/plugins/obs-filters/data/locale/cs-CZ.ini index 66176e9..abbcaeb 100644 --- a/plugins/obs-filters/data/locale/cs-CZ.ini +++ b/plugins/obs-filters/data/locale/cs-CZ.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Práh (v dB)" Compressor.AttackTime="Stažení (v ms)" Compressor.ReleaseTime="Uvolnění (v ms)" Compressor.OutputGain="Síla výstupu (v dB)" +Compressor.SidechainSource="Zdroj pro side-chain/ducking" diff --git a/plugins/obs-filters/data/locale/da-DK.ini b/plugins/obs-filters/data/locale/da-DK.ini index 05026f5..e9a125b 100644 --- a/plugins/obs-filters/data/locale/da-DK.ini +++ b/plugins/obs-filters/data/locale/da-DK.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Grænse (dB)" Compressor.AttackTime="Attack (ms)" Compressor.ReleaseTime="Release (ms)" Compressor.OutputGain="Output øgning (dB)" +Compressor.SidechainSource="Sidechain/Ducking-kilde" diff --git a/plugins/obs-filters/data/locale/de-DE.ini b/plugins/obs-filters/data/locale/de-DE.ini index 40b17f2..d1ae53b 100644 --- a/plugins/obs-filters/data/locale/de-DE.ini +++ b/plugins/obs-filters/data/locale/de-DE.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Schwelle (dB)" Compressor.AttackTime="Angriff (ms)" Compressor.ReleaseTime="Freigabe (ms)" Compressor.OutputGain="Ausgangspegel (dB)" +Compressor.SidechainSource="Sidechain/Ducking Quelle" diff --git a/plugins/obs-filters/data/locale/el-GR.ini b/plugins/obs-filters/data/locale/el-GR.ini index dce045b..54d2059 100644 --- a/plugins/obs-filters/data/locale/el-GR.ini +++ b/plugins/obs-filters/data/locale/el-GR.ini @@ -1,4 +1,5 @@ ColorFilter="Διόρθωση Χρώματος" +ColorGradeFilter="Εφαρμογή LUT" AsyncDelayFilter="Καθυστέρηση Βίντεο (Ασύγχρονη)" ScrollFilter="Κύλιση" ChromaKeyFilter="Κλειδί Chroma" @@ -39,4 +40,11 @@ NoiseGate.AttackTime="Χρόνος προσβολής (msec)" NoiseGate.HoldTime="Χρόνος αναμονής (msec)" NoiseGate.ReleaseTime="Χρόνος διάχυσης (msec)" Gain.GainDB="Απολαβή (dB)" +Resolution="Ανάλυση" +None="Καμία" +Saturation="Κορεσμός" +HueShift="Μετατόπιση Απόχρωσης" +Amount="Ποσό" +Compressor.Ratio="Αναλογία (X:1)" +Compressor.Threshold="Κατώφλι (dB)" diff --git a/plugins/obs-filters/data/locale/en-US.ini b/plugins/obs-filters/data/locale/en-US.ini index f605d7d..241cbee 100644 --- a/plugins/obs-filters/data/locale/en-US.ini +++ b/plugins/obs-filters/data/locale/en-US.ini @@ -73,3 +73,4 @@ Compressor.Threshold="Threshold (dB)" Compressor.AttackTime="Attack (ms)" Compressor.ReleaseTime="Release (ms)" Compressor.OutputGain="Output Gain (dB)" +Compressor.SidechainSource="Sidechain/Ducking Source" diff --git a/plugins/obs-filters/data/locale/es-ES.ini b/plugins/obs-filters/data/locale/es-ES.ini index 445b890..25a6492 100644 --- a/plugins/obs-filters/data/locale/es-ES.ini +++ b/plugins/obs-filters/data/locale/es-ES.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Umbral (dB)" Compressor.AttackTime="Ataque (ms)" Compressor.ReleaseTime="Liberación (ms)" Compressor.OutputGain="Ganancia de salida (dB)" +Compressor.SidechainSource="Fuente de atenuación/reducción" diff --git a/plugins/obs-filters/data/locale/eu-ES.ini b/plugins/obs-filters/data/locale/eu-ES.ini index 7fa668d..d9e7d5c 100644 --- a/plugins/obs-filters/data/locale/eu-ES.ini +++ b/plugins/obs-filters/data/locale/eu-ES.ini @@ -4,7 +4,7 @@ MaskFilter="Irudi maskara/nahasketa" AsyncDelayFilter="Bideo atzerapena (Async)" CropFilter="Moztu/Bete" ScrollFilter="Korritu" -ChromaKeyFilter="Kroma gakoa" +ChromaKeyFilter="Kroma" ColorKeyFilter="Kolore gakoa" SharpnessFilter="Enfokea" ScaleFilter="Eskala/Aspektu-erlazioa" @@ -73,4 +73,5 @@ Compressor.Threshold="Atalasea (dB)" Compressor.AttackTime="Erasoa (ms)" Compressor.ReleaseTime="Askapena (ms)" Compressor.OutputGain="Irteerako irabazia (dB)" +Compressor.SidechainSource="Sidechain/Ducking iturburua" diff --git a/plugins/obs-filters/data/locale/fi-FI.ini b/plugins/obs-filters/data/locale/fi-FI.ini index 608f5d7..e456d6d 100644 --- a/plugins/obs-filters/data/locale/fi-FI.ini +++ b/plugins/obs-filters/data/locale/fi-FI.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Kynnysarvo (dB)" Compressor.AttackTime="Attack-aika (ms)" Compressor.ReleaseTime="Vapautumisaika (ms)" Compressor.OutputGain="Signaalin vahvistus (dB)" +Compressor.SidechainSource="Lähteen väistäminen" diff --git a/plugins/obs-filters/data/locale/fr-FR.ini b/plugins/obs-filters/data/locale/fr-FR.ini index 64bed1e..38855f7 100644 --- a/plugins/obs-filters/data/locale/fr-FR.ini +++ b/plugins/obs-filters/data/locale/fr-FR.ini @@ -8,6 +8,7 @@ ChromaKeyFilter="Clé chromatique" ColorKeyFilter="Couleur d'incrustation" SharpnessFilter="Accentuer" ScaleFilter="Mise à l’échelle / Ratio d'affichage" +GPUDelayFilter="Délai de rendu" UndistortCenter="Ne pas déformer le centre de l'image lors d'une mise à l'échelle ultra large" NoiseGate="Noise Gate" NoiseSuppress="Suppression du bruit" @@ -72,4 +73,5 @@ Compressor.Threshold="Seuil (dB)" Compressor.AttackTime="Attaque (ms)" Compressor.ReleaseTime="Libération (ms)" Compressor.OutputGain="Sortie Gain (dB)" +Compressor.SidechainSource="Source Sidechain/Ducking" diff --git a/plugins/obs-filters/data/locale/hu-HU.ini b/plugins/obs-filters/data/locale/hu-HU.ini index 0b44de2..32e9b79 100644 --- a/plugins/obs-filters/data/locale/hu-HU.ini +++ b/plugins/obs-filters/data/locale/hu-HU.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Küszöb (dB)" Compressor.AttackTime="Aktiválás (ms)" Compressor.ReleaseTime="Felengedés (ms)" Compressor.OutputGain="Kimeneti erősítés (dB)" +Compressor.SidechainSource="Oldallánc/Buktatott forrás" diff --git a/plugins/obs-filters/data/locale/it-IT.ini b/plugins/obs-filters/data/locale/it-IT.ini index 0012555..1c83914 100644 --- a/plugins/obs-filters/data/locale/it-IT.ini +++ b/plugins/obs-filters/data/locale/it-IT.ini @@ -8,7 +8,9 @@ ChromaKeyFilter="Chroma Key" ColorKeyFilter="Chiave Colore" SharpnessFilter="Nitidizza" ScaleFilter="Ridimensionamento/Aspect Ratio" -NoiseGate="Noise Gate" +GPUDelayFilter="Ritardo di rendering" +UndistortCenter="Rimuovi distorsione del centro immagine quando si scala da un rapporto molto alto" +NoiseGate="Sensibilità dell'ingresso" NoiseSuppress="Soppressione rumore" Gain="Incremento" DelayMs="Ritardo (millisecondi)" @@ -71,4 +73,5 @@ Compressor.Threshold="Soglia (dB)" Compressor.AttackTime="Attacco (ms)" Compressor.ReleaseTime="Rilascio (ms)" Compressor.OutputGain="Guadagno di uscita (dB)" +Compressor.SidechainSource="Sidechain/Ducking Sorgente" diff --git a/plugins/obs-filters/data/locale/ja-JP.ini b/plugins/obs-filters/data/locale/ja-JP.ini index ed1c205..1dd1194 100644 --- a/plugins/obs-filters/data/locale/ja-JP.ini +++ b/plugins/obs-filters/data/locale/ja-JP.ini @@ -73,4 +73,5 @@ Compressor.Threshold="閾値 (dB)" Compressor.AttackTime="アタックタイム (ms)" Compressor.ReleaseTime="リリースタイム (ms)" Compressor.OutputGain="出力ゲイン (dB)" +Compressor.SidechainSource="サイドチェーン/ダッキングソース" diff --git a/plugins/obs-filters/data/locale/ka-GE.ini b/plugins/obs-filters/data/locale/ka-GE.ini new file mode 100644 index 0000000..9b0122a --- /dev/null +++ b/plugins/obs-filters/data/locale/ka-GE.ini @@ -0,0 +1,4 @@ +Red="წითელი" +Green="მწვანე" +Blue="ლურჯი" + diff --git a/plugins/obs-filters/data/locale/ko-KR.ini b/plugins/obs-filters/data/locale/ko-KR.ini index 3b2439c..e03702b 100644 --- a/plugins/obs-filters/data/locale/ko-KR.ini +++ b/plugins/obs-filters/data/locale/ko-KR.ini @@ -73,4 +73,5 @@ Compressor.Threshold="임계값 (dB)" Compressor.AttackTime="신호 감지 후 반응까지 걸리는 시간 (ms)" Compressor.ReleaseTime="신호 세기가 감퇴 이후 증폭이 회복하는 시간 (ms)" Compressor.OutputGain="출력 증폭 (dB)" +Compressor.SidechainSource="사이드체인/더킹 소스" diff --git a/plugins/obs-filters/data/locale/nb-NO.ini b/plugins/obs-filters/data/locale/nb-NO.ini index eb1b94c..b7c7979 100644 --- a/plugins/obs-filters/data/locale/nb-NO.ini +++ b/plugins/obs-filters/data/locale/nb-NO.ini @@ -1,4 +1,5 @@ ColorFilter="Fargekorrigering" +ColorGradeFilter="Bruk LUT" MaskFilter="Bildemaske/-blanding" AsyncDelayFilter="Videoforsinkelse (asynkron)" CropFilter="Beskjæring/utfall" @@ -7,6 +8,8 @@ ChromaKeyFilter="Chromafilter" ColorKeyFilter="Fargefilter" SharpnessFilter="Skjerpe" ScaleFilter="Skalering/Aspekt Forhold" +GPUDelayFilter="Rendringsforsinkelse" +UndistortCenter="Fjern forstyrring av bildets midtområde, når det skaleres ned fra ultrabredhet" NoiseGate="Støyterskel" NoiseSuppress="Lyddemping" Gain="Forsterkning" @@ -57,7 +60,18 @@ Resolution="Oppløsning" None="Ingen" ScaleFiltering="Skala Filtrering" ScaleFiltering.Point="Punkt" +ScaleFiltering.Bilinear="Bilineær" +ScaleFiltering.Bicubic="Bikubisk" +ScaleFiltering.Lanczos="Lanczos" NoiseSuppress.SuppressLevel="Dempelse Nivå (dB)" Saturation="Metning" HueShift="Fargetone Skifte" +Amount="Mengde" +Compressor="Kompressor" +Compressor.Ratio="Forhold (X:1)" +Compressor.Threshold="Terskel (dB)" +Compressor.AttackTime="Angrep (ms)" +Compressor.ReleaseTime="Slipp (ms)" +Compressor.OutputGain="Utdataforsterkning (dB)" +Compressor.SidechainSource="Lydduppe-kilde" diff --git a/plugins/obs-filters/data/locale/nl-NL.ini b/plugins/obs-filters/data/locale/nl-NL.ini index fae5c38..386a24b 100644 --- a/plugins/obs-filters/data/locale/nl-NL.ini +++ b/plugins/obs-filters/data/locale/nl-NL.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Drempel (dB)" Compressor.AttackTime="Attack (ms)" Compressor.ReleaseTime="Release (ms)" Compressor.OutputGain="Uitvoergain (dB)" +Compressor.SidechainSource="Sidechain/Ducking Bron" diff --git a/plugins/obs-filters/data/locale/pl-PL.ini b/plugins/obs-filters/data/locale/pl-PL.ini index 656ce21..05fcefb 100644 --- a/plugins/obs-filters/data/locale/pl-PL.ini +++ b/plugins/obs-filters/data/locale/pl-PL.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Próg (dB)" Compressor.AttackTime="Atak (ms)" Compressor.ReleaseTime="Odpuszczenie (ms)" Compressor.OutputGain="Zysk na wyjściu (dB)" +Compressor.SidechainSource="Źródło poboczne" diff --git a/plugins/obs-filters/data/locale/pt-BR.ini b/plugins/obs-filters/data/locale/pt-BR.ini index 2bcfecf..886dd2a 100644 --- a/plugins/obs-filters/data/locale/pt-BR.ini +++ b/plugins/obs-filters/data/locale/pt-BR.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Limiar (dB)" Compressor.AttackTime="Ataque (ms)" Compressor.ReleaseTime="Liberação (ms)" Compressor.OutputGain="Ganho na saída (dB)" +Compressor.SidechainSource="Fonte de Cadeia Lateral/Oscilação de Áudio" diff --git a/plugins/obs-filters/data/locale/ru-RU.ini b/plugins/obs-filters/data/locale/ru-RU.ini index 1547423..ddc0c3e 100644 --- a/plugins/obs-filters/data/locale/ru-RU.ini +++ b/plugins/obs-filters/data/locale/ru-RU.ini @@ -10,7 +10,7 @@ SharpnessFilter="Увеличить резкость" ScaleFilter="Коэффициент Масштабирования/Аспект" GPUDelayFilter="Задержка отображения" UndistortCenter="Не искривлять центр изображения при масштабировании Ultrawide разрешения" -NoiseGate="Подавление шума" +NoiseGate="Пропускной уровень шума" NoiseSuppress="Шумоподавление" Gain="Усиление" DelayMs="Задержка (миллисекунд)" @@ -73,4 +73,5 @@ Compressor.Threshold="Порог срабатывания (дБ)" Compressor.AttackTime="Атака (мс)" Compressor.ReleaseTime="Спад (мс)" Compressor.OutputGain="Выходное усиление (дБ)" +Compressor.SidechainSource="Источник приглушения/сайдчейн-компрессии" diff --git a/plugins/obs-filters/data/locale/sk-SK.ini b/plugins/obs-filters/data/locale/sk-SK.ini index c3b5ed0..d9ddbdf 100644 --- a/plugins/obs-filters/data/locale/sk-SK.ini +++ b/plugins/obs-filters/data/locale/sk-SK.ini @@ -1,3 +1,26 @@ +ColorFilter="Korekcia farieb" +ColorGradeFilter="Použiť LUT" +AsyncDelayFilter="Oneskorenie videa (Async)" +CropFilter="Orezať/odsadiť" +ChromaKeyFilter="Chroma Key" +ColorKeyFilter="Farebný kľúč" +SharpnessFilter="Zaostriť" +ScaleFilter="Škálovanie/pomer strán" +NoiseSuppress="Potlačenie šumu" +Gain="Zosilnenie" +DelayMs="Oneskorenie (v milisekundách)" +Type="Typ" +Path="Cesta" +Color="Farba" +Opacity="Priehľadnosť" +Contrast="Kontrast" +Brightness="Jas" +Gamma="Gama" +BrowsePath.Images="Všetky obrázkové súbory" +BrowsePath.AllFiles="Všetky súbory" +KeyColorType="Typ kľúčovej farby" +KeyColor="Kľúčová farba" +Similarity="Podobnosť (1-1000)" Crop.Left="Vľavo" Crop.Right="Vpravo" Crop.Top="Hore" @@ -5,7 +28,25 @@ Crop.Bottom="Dole" Crop.Width="Šírka" Crop.Height="Výška" Crop.Relative="Relatívne" +ScrollFilter.LimitWidth="Obmedziť šírku" +ScrollFilter.LimitHeight="Obmedziť výšku" +CustomColor="Vlastná farba" Red="Červená" Green="Zelená" Blue="Modrá" +Magenta="Purpurová" +NoiseGate.OpenThreshold="Hladina otvorenia (dB)" +NoiseGate.CloseThreshold="Hladina zatvorenia (dB)" +Resolution="Rozlíšenie" +None="Žiadne" +ScaleFiltering="Filtrovanie rozsahu" +ScaleFiltering.Point="Bodové" +ScaleFiltering.Bilinear="Bilineárne" +ScaleFiltering.Bicubic="Bikubické" +ScaleFiltering.Lanczos="Lanczos" +Saturation="Sýtosť" +Amount="Množstvo" +Compressor="Kompresor" +Compressor.Ratio="Pomer (X:1)" +Compressor.Threshold="Prah (dB)" diff --git a/plugins/obs-filters/data/locale/sv-SE.ini b/plugins/obs-filters/data/locale/sv-SE.ini index 2edc1af..6562a77 100644 --- a/plugins/obs-filters/data/locale/sv-SE.ini +++ b/plugins/obs-filters/data/locale/sv-SE.ini @@ -9,6 +9,7 @@ ColorKeyFilter="Färgfilter" SharpnessFilter="Skärpa" ScaleFilter="Skalning/Bildförhållande" GPUDelayFilter="Renderingsfördröjning" +UndistortCenter="Återställ bildens centrum vid skalning från ultrawide" NoiseGate="Brusblockering" NoiseSuppress="Brusreducering" Gain="Förstärkning" @@ -72,4 +73,5 @@ Compressor.Threshold="Tröskel (dB)" Compressor.AttackTime="Attack (ms)" Compressor.ReleaseTime="Frigör (ms)" Compressor.OutputGain="Utmatningsförstärkning (dB)" +Compressor.SidechainSource="Sidechain/Ducking-källa" diff --git a/plugins/obs-filters/data/locale/tr-TR.ini b/plugins/obs-filters/data/locale/tr-TR.ini index 33e1d9f..ee2a738 100644 --- a/plugins/obs-filters/data/locale/tr-TR.ini +++ b/plugins/obs-filters/data/locale/tr-TR.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Eşik (dB)" Compressor.AttackTime="Atak (ms)" Compressor.ReleaseTime="Bırakma (ms)" Compressor.OutputGain="Çıkış Kazancı (dB)" +Compressor.SidechainSource="Yan-Zincir/Alçaltma Kaynağı" diff --git a/plugins/obs-filters/data/locale/uk-UA.ini b/plugins/obs-filters/data/locale/uk-UA.ini index 29755ae..3df87f8 100644 --- a/plugins/obs-filters/data/locale/uk-UA.ini +++ b/plugins/obs-filters/data/locale/uk-UA.ini @@ -73,4 +73,5 @@ Compressor.Threshold="Поріг (дБ)" Compressor.AttackTime="Атака (мс)" Compressor.ReleaseTime="Затухання (мс)" Compressor.OutputGain="Підсилення виводу (dB)" +Compressor.SidechainSource="Джерело Коригування/Приглушення" diff --git a/plugins/obs-filters/data/locale/vi-VN.ini b/plugins/obs-filters/data/locale/vi-VN.ini new file mode 100644 index 0000000..b4beb14 --- /dev/null +++ b/plugins/obs-filters/data/locale/vi-VN.ini @@ -0,0 +1,28 @@ +ColorFilter="Chỉnh sửa màu" +ColorGradeFilter="Áp dụng LUT" +AsyncDelayFilter="Độ trễ Video (Async)" +ScrollFilter="Cuộn" +Gain="Mức âm" +DelayMs="Delay (mili giây)" +Type="Loại" +Path="Đường dẫn" +Color="Màu" +Opacity="Độ trong suốt" +Contrast="Độ tương phản" +Brightness="Độ sáng" +BrowsePath.AllFiles="Tất cả tập tin" +KeyColorType="Loại màu khóa" +KeyColor="Màu khóa" +Crop.Left="Trái" +Crop.Right="Phải" +Crop.Top="Trên" +Crop.Bottom="Dưới" +Crop.Width="Rộng" +Crop.Height="Cao" +Red="Đỏ" +Green="Xanh" +Blue="Xanh nước biển" +Magenta="Đỏ tươi" +None="Không có" +Saturation="Độ bão hoà" + diff --git a/plugins/obs-filters/data/locale/zh-CN.ini b/plugins/obs-filters/data/locale/zh-CN.ini index c93bb40..cd4daad 100644 --- a/plugins/obs-filters/data/locale/zh-CN.ini +++ b/plugins/obs-filters/data/locale/zh-CN.ini @@ -73,4 +73,5 @@ Compressor.Threshold="阈值 (dB)" Compressor.AttackTime="攻击 (ms)" Compressor.ReleaseTime="释放 (ms)" Compressor.OutputGain="输出增益 (dB)" +Compressor.SidechainSource="避免来源" diff --git a/plugins/obs-filters/data/locale/zh-TW.ini b/plugins/obs-filters/data/locale/zh-TW.ini index b21e302..0925595 100644 --- a/plugins/obs-filters/data/locale/zh-TW.ini +++ b/plugins/obs-filters/data/locale/zh-TW.ini @@ -73,4 +73,5 @@ Compressor.Threshold="閾值 (dB)" Compressor.AttackTime="起始時間 (ms)" Compressor.ReleaseTime="釋放時間 (ms)" Compressor.OutputGain="輸出增益 (dB)" +Compressor.SidechainSource="側鏈/回避源" diff --git a/plugins/obs-filters/gain-filter.c b/plugins/obs-filters/gain-filter.c index 02c7e1c..13f4131 100644 --- a/plugins/obs-filters/gain-filter.c +++ b/plugins/obs-filters/gain-filter.c @@ -16,6 +16,7 @@ struct gain_data { obs_source_t *context; + size_t channels; float multiple; }; @@ -35,7 +36,7 @@ static void gain_update(void *data, obs_data_t *s) { struct gain_data *gf = data; double val = obs_data_get_double(s, S_GAIN_DB); - + gf->channels = audio_output_get_channels(obs_get_audio()); gf->multiple = db_to_mul((float)val); } @@ -51,11 +52,11 @@ static struct obs_audio_data *gain_filter_audio(void *data, struct obs_audio_data *audio) { struct gain_data *gf = data; - - float *adata[2] = {(float*)audio->data[0], (float*)audio->data[1]}; + const size_t channels = gf->channels; + float **adata = (float**)audio->data; const float multiple = gf->multiple; - for (size_t c = 0; c < 2; c++) { + for (size_t c = 0; c < channels; c++) { if (audio->data[c]) { for (size_t i = 0; i < audio->frames; i++) { adata[c][i] *= multiple; diff --git a/plugins/obs-filters/gpu-delay.c b/plugins/obs-filters/gpu-delay.c index c4628be..b35482b 100644 --- a/plugins/obs-filters/gpu-delay.c +++ b/plugins/obs-filters/gpu-delay.c @@ -172,8 +172,9 @@ static void gpu_delay_filter_destroy(void *data) static void gpu_delay_filter_tick(void *data, float t) { + UNUSED_PARAMETER(t); + struct gpu_delay_filter_data *f = data; - uint64_t cur_time = obs_get_video_frame_time(); f->processed_frame = false; diff --git a/plugins/obs-filters/noise-gate-filter.c b/plugins/obs-filters/noise-gate-filter.c index 9631133..2203338 100644 --- a/plugins/obs-filters/noise-gate-filter.c +++ b/plugins/obs-filters/noise-gate-filter.c @@ -109,7 +109,7 @@ static struct obs_audio_data *noise_gate_filter_audio(void *data, { struct noise_gate_data *ng = data; - float *adata[2] = {(float*)audio->data[0], (float*)audio->data[1]}; + float **adata = (float**)audio->data; const float close_threshold = ng->close_threshold; const float open_threshold = ng->open_threshold; const float sample_rate_i = ng->sample_rate_i; @@ -120,9 +120,10 @@ static struct obs_audio_data *noise_gate_filter_audio(void *data, const size_t channels = ng->channels; for (size_t i = 0; i < audio->frames; i++) { - float cur_level = (channels == 2) - ? fmaxf(fabsf(adata[0][i]), fabsf(adata[1][i])) - : fabsf(adata[0][i]); + float cur_level = fabsf(adata[0][i]); + for (size_t j = 0; j < channels; j++) { + cur_level = fmaxf(cur_level, fabsf(adata[j][i])); + } if (cur_level > open_threshold && !ng->is_open) { ng->is_open = true; diff --git a/plugins/obs-filters/noise-suppress-filter.c b/plugins/obs-filters/noise-suppress-filter.c index 9c9bb8c..26c65b3 100644 --- a/plugins/obs-filters/noise-suppress-filter.c +++ b/plugins/obs-filters/noise-suppress-filter.c @@ -27,7 +27,7 @@ #define MT_ obs_module_text #define TEXT_SUPPRESS_LEVEL MT_("NoiseSuppress.SuppressLevel") -#define MAX_PREPROC_CHANNELS 2 +#define MAX_PREPROC_CHANNELS 8 /* -------------------------------------------------------- */ @@ -120,12 +120,12 @@ static void noise_suppress_update(void *data, obs_data_t *s) /* One speex state for each channel (limit 2) */ ng->copy_buffers[0] = bmalloc(frames * channels * sizeof(float)); ng->segment_buffers[0] = bmalloc(frames * channels * sizeof(spx_int16_t)); - - if (channels == 2) { - ng->copy_buffers[1] = ng->copy_buffers[0] + frames; - ng->segment_buffers[1] = ng->segment_buffers[0] + frames; + for (size_t c = 1; c < channels; ++c) { + ng->copy_buffers[c] = ng->copy_buffers[c-1] + frames; + ng->segment_buffers[c] = ng->segment_buffers[c-1] + frames; } + for (size_t i = 0; i < channels; i++) alloc_channel(ng, sample_rate, i, frames); } @@ -155,9 +155,13 @@ static inline void process(struct noise_suppress_data *ng) /* Convert to 16bit */ for (size_t i = 0; i < ng->channels; i++) - for (size_t j = 0; j < ng->frames; j++) + for (size_t j = 0; j < ng->frames; j++) { + float s = ng->copy_buffers[i][j]; + if (s > 1.0f) s = 1.0f; + else if (s < -1.0f) s = -1.0f; ng->segment_buffers[i][j] = (spx_int16_t) - (ng->copy_buffers[i][j] * c_32_to_16); + (s * c_32_to_16); + } /* Execute */ for (size_t i = 0; i < ng->channels; i++) diff --git a/plugins/obs-libfdk/data/locale/vi-VN.ini b/plugins/obs-libfdk/data/locale/vi-VN.ini new file mode 100644 index 0000000..6dcfa3c --- /dev/null +++ b/plugins/obs-libfdk/data/locale/vi-VN.ini @@ -0,0 +1,2 @@ +Bitrate="Bitrate" + diff --git a/plugins/obs-libfdk/obs-libfdk.c b/plugins/obs-libfdk/obs-libfdk.c index 8313aac..d6eb496 100644 --- a/plugins/obs-libfdk/obs-libfdk.c +++ b/plugins/obs-libfdk/obs-libfdk.c @@ -72,7 +72,7 @@ static obs_properties_t *libfdk_properties(void *unused) obs_properties_t *props = obs_properties_create(); obs_properties_add_int(props, "bitrate", - obs_module_text("Bitrate"), 32, 256, 32); + obs_module_text("Bitrate"), 32, 1024, 32); obs_properties_add_bool(props, "afterburner", obs_module_text("Afterburner")); @@ -131,6 +131,16 @@ static void *libfdk_create(obs_data_t *settings, obs_encoder_t *encoder) case 6: mode = MODE_1_2_2_1; break; + + /* lib_fdk-aac > 1.3 required for 7.1 surround; + * uncomment if available on linux build + */ +#ifndef __linux__ + case 8: + mode = MODE_7_1_REAR_SURROUND; + break; +#endif + default: blog(LOG_ERROR, "Invalid channel count"); goto fail; diff --git a/plugins/obs-outputs/CMakeLists.txt b/plugins/obs-outputs/CMakeLists.txt index c8bbf81..4177efd 100644 --- a/plugins/obs-outputs/CMakeLists.txt +++ b/plugins/obs-outputs/CMakeLists.txt @@ -19,6 +19,63 @@ else() add_definitions(-DNO_CRYPTO) endif() +if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ftl-sdk/CMakeLists.txt") + find_package(Libcurl REQUIRED) + + include_directories(${LIBCURL_INCLUDE_DIRS}) + + set(ftl_SOURCES + ftl-stream.c + ftl-sdk/libftl/hmac/hmac.c + ftl-sdk/libftl/hmac/sha2.c + ftl-sdk/libftl/ftl-sdk.c + ftl-sdk/libftl/handshake.c + ftl-sdk/libftl/ingest.c + ftl-sdk/libftl/ftl_helpers.c + ftl-sdk/libftl/media.c + ftl-sdk/libftl/gettimeofday/gettimeofday.c + ftl-sdk/libftl/logging.c) + set(ftl_HEADERS + ftl-sdk/libftl/hmac/hmac.h + ftl-sdk/libftl/hmac/sha2.h + ftl-sdk/libftl/ftl.h + ftl-sdk/libftl/ftl_private.h) + set(ftl_IMPORTS + ${OBS_JANSSON_IMPORT} + ${LIBCURL_LIBRARIES}) + + if (WIN32) + list(APPEND ftl_SOURCES + ftl-sdk/libftl/win32/socket.c + ftl-sdk/libftl/gettimeofday/gettimeofday.c + ftl-sdk/libftl/win32/threads.c) + list(APPEND ftl_HEADERS + ftl-sdk/libftl/gettimeofday/gettimeofday.h + ftl-sdk/libftl/win32/threads.h) + + include_directories(ftl-sdk/libftl/win32) + else() + list(APPEND ftl_SOURCES + ftl-sdk/libftl/posix/socket.c + ftl-sdk/libftl/posix/threads.c) + list(APPEND ftl_HEADERS + ftl-sdk/libftl/posix/threads.h) + + include_directories(ftl-sdk/libftl/posix) + endif() + + include_directories(ftl-sdk/libftl) + + set(COMPILE_FTL TRUE) +else() + set(COMPILE_FTL FALSE) +endif() + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/obs-outputs-config.h.in" + "${CMAKE_BINARY_DIR}/plugins/obs-outputs/config/obs-outputs-config.h") + +include_directories("${CMAKE_BINARY_DIR}/plugins/obs-outputs/config") if(WIN32) set(obs-outputs_PLATFORM_DEPS @@ -60,13 +117,12 @@ if(NOT WIN32) endif() set(obs-outputs_HEADERS + "${CMAKE_BINARY_DIR}/plugins/obs-outputs/config/obs-outputs-config.h" obs-output-ver.h rtmp-helpers.h rtmp-stream.h net-if.h - flv-mux.h - flv-output.h - librtmp) + flv-mux.h) set(obs-outputs_SOURCES obs-outputs.c null-output.c @@ -77,14 +133,17 @@ set(obs-outputs_SOURCES net-if.c) add_library(obs-outputs MODULE + ${ftl_SOURCES} + ${ftl_HEADERS} ${obs-outputs_SOURCES} - ${obs-outputs_HEADER} + ${obs-outputs_HEADERS} ${obs-outputs_librtmp_SOURCES} ${obs-outputs_librtmp_HEADERS}) target_link_libraries(obs-outputs libobs ${SSL_LIBRARIES} ${ZLIB_LIBRARIES} + ${ftl_IMPORTS} ${obs-outputs_PLATFORM_DEPS}) install_obs_plugin_with_data(obs-outputs data) diff --git a/plugins/obs-outputs/data/locale/ca-ES.ini b/plugins/obs-outputs/data/locale/ca-ES.ini index ecd362e..ba0c81a 100644 --- a/plugins/obs-outputs/data/locale/ca-ES.ini +++ b/plugins/obs-outputs/data/locale/ca-ES.ini @@ -4,4 +4,11 @@ FLVOutput="Sortida del fitxer FLV" FLVOutput.FilePath="Camí del fitxer" Default="Per defecte" +ConnectionTimedOut="S'ha esgotat el temps de la connexió. Assegureu-vos que ha configurat un servei de transmissió vàlid i cap tallafocs està bloquejant la connexió." +PermissionDenied="La connexió ha estat bloquejada. Comproveu la configuració del tallafocs o antivirus per assegurar-se que OBS disposa de llibertat absoluta a Internet." +ConnectionAborted="La connexió ha estat avortada. Normalment això indica que hi ha problemes de connexió entre el vostre equip i el servei de transmissió." +ConnectionReset="La connexió s'ha acabat. Normalment això indica que hi ha problemes de connexió entre el vostre equip i el servei de transmissió." +HostNotFound="Nom d'amfitrió no trobat. Assegureu-vos que hi hagi configurat un servidor de transmissió vàlid i que la seva connexió a Internet / DNS estiguin funcionant correctament." +NoData="Nom d'amfitrió trobat, però no hi ha dades del tipus sol·licitat. Això pot passar si heu enllaçat a una adreça IPv6 i el seu servei de transmissió només té adreces IPv4 (veure configuració / avançada)." +AddressNotAvailable="Direcció no disponible. Potser heu intentat enllaçar amb una adreça IP no vàlida (veure configuració / avançada)." diff --git a/plugins/obs-outputs/data/locale/da-DK.ini b/plugins/obs-outputs/data/locale/da-DK.ini index 0a068fb..a3b9fe7 100644 --- a/plugins/obs-outputs/data/locale/da-DK.ini +++ b/plugins/obs-outputs/data/locale/da-DK.ini @@ -1,5 +1,5 @@ RTMPStream="RTMP Strøm" -RTMPStream.DropThreshold="Drop Tærskel (millisekunder)" +RTMPStream.DropThreshold="Tabstærskel (millisekunder)" FLVOutput="FLV File Output" FLVOutput.FilePath="Filsti" Default="Standard" diff --git a/plugins/obs-outputs/data/locale/es-ES.ini b/plugins/obs-outputs/data/locale/es-ES.ini index 5381632..8f4e0e1 100644 --- a/plugins/obs-outputs/data/locale/es-ES.ini +++ b/plugins/obs-outputs/data/locale/es-ES.ini @@ -4,7 +4,11 @@ FLVOutput="Archivo de salida FLV" FLVOutput.FilePath="Ruta de archivo" Default="Por defecto" +ConnectionTimedOut="Agotado el tiempo de la conexión. Asegúrese de que ha configurado un servicio de streaming válido y ningún firewall está bloqueando la conexión." PermissionDenied="La conexión ha sido bloqueada. Compruebe su configuración del cortafuegos o anti-virus para asegurarse de que OBS tiene acceso completo a Internet." ConnectionAborted="La conexión ha sido abortada. Normalmente esto indica que hay problemas de conexión entre tu equipo y el servicio de transmisión." +ConnectionReset="La conexión se ha terminado. Normalmente esto indica que hay problemas de conexión entre tu equipo y el servicio de transmisión." +HostNotFound="Nombre de host no encontrado. Asegúrese que haya configurado un servidor de transmisión valido y que su conexión a Internet / DNS estén funcionando correctamente." +NoData="Nombre de host encontrado, pero no hay datos del tipo solicitado. Esto puede ocurrir si has enlazado a una dirección IPv6 y su servicio de streaming sólo tiene direcciones IPv4 (ver Configuración / Avanzada)." AddressNotAvailable="Dirección no disponible. Puede que hayas intentado enlazar con una dirección IP no valida (vea Configuración / Avanzado)." diff --git a/plugins/obs-outputs/data/locale/fr-FR.ini b/plugins/obs-outputs/data/locale/fr-FR.ini index f63beec..d326fdd 100644 --- a/plugins/obs-outputs/data/locale/fr-FR.ini +++ b/plugins/obs-outputs/data/locale/fr-FR.ini @@ -7,4 +7,8 @@ Default="Interface par défaut" ConnectionTimedOut="La connexion à expiré. Assurez-vous que vous avez configuré un service de streaming valide et qu'aucun pare-feu ne bloque la connexion." PermissionDenied="La connexion a été bloquée. Vérifiez vos paramètres de pare-feu / antivirus pour vous assurer OBS est autorisé à avoir l'accès complet d'internet." ConnectionAborted="La connexion à été interrompue. Cela indique généralement des problèmes de connexion internet entre vous et le service de streaming." +ConnectionReset="La connexion à été interrompue. Cela indique généralement des problèmes de connexion internet entre vous et le service de diffusion." +HostNotFound="Nom d’hôte non trouvé. Assurez-vous que vous avez spécifié un serveur de diffusion valide et que votre connexion internet / DNS fonctionnent correctement." +NoData="Nom d’hôte trouvé, mais aucune donnée du type requis. Cela peut se produire si vous avez lié à une adresse IPv6 et votre service de diffusion ne possède que des adresses IPv4 (voir Paramètres / Avancé)." +AddressNotAvailable="Adresse non disponible. Vous avez peut-être essayé de la lier à une adresse IP non valide (voir Paramètres / Avancé)." diff --git a/plugins/obs-outputs/data/locale/it-IT.ini b/plugins/obs-outputs/data/locale/it-IT.ini index 28455fb..b9cf420 100644 --- a/plugins/obs-outputs/data/locale/it-IT.ini +++ b/plugins/obs-outputs/data/locale/it-IT.ini @@ -4,4 +4,11 @@ FLVOutput="Uscita file FLV" FLVOutput.FilePath="Destinazione file" Default="Predefinito" +ConnectionTimedOut="Timeout della connessione. Assicurarsi di aver configurato un valido servizio di streaming e nessun firewall sta bloccando la connessione." +PermissionDenied="La connessione è stata bloccata. Controlla il tuo firewall / impostazioni di anti-virus per assicurarsi che per OBS sia consentito accesso completo a internet." +ConnectionAborted="La connessione è stata interrotta. In genere indica problemi di connessione tra l'utente e il servizio di streaming." +ConnectionReset="La connessione è stata ripristinata dal peer. In genere indica problemi di connessione tra l'utente e il servizio di streaming." +HostNotFound="Nome host non trovato. Assicurarsi di aver inserito un valido server per lo streaming e che la connessione a internet / DNS funzioni correttamente." +NoData="Nome host trovato, ma nessun dato del tipo richiesto. Ciò può verificarsi se è stato associato a un indirizzo IPv6 e il servizio di streaming ha solo indirizzi IPv4 (vedere Impostazioni / avanzate)." +AddressNotAvailable="Indirizzo non disponibile. Si è cercato di associare un indirizzo IP non valido (vedere Impostazioni / avanzate)." diff --git a/plugins/obs-outputs/data/locale/nb-NO.ini b/plugins/obs-outputs/data/locale/nb-NO.ini index 50015aa..555a665 100644 --- a/plugins/obs-outputs/data/locale/nb-NO.ini +++ b/plugins/obs-outputs/data/locale/nb-NO.ini @@ -4,4 +4,11 @@ FLVOutput="FLV-filutgang" FLVOutput.FilePath="Filbane" Default="Standard" +ConnectionTimedOut="Forbindelsen ble tidsavbrutt. Sørg for at du har konfigurert en strømmingstjeneste og at den ikke blir blokkert av en brannmur." +PermissionDenied="Tilkoblingen ble blokkert. Sjekk at OBS har full internettilgang i brannmur/antivirusinnstillingene dine." +ConnectionAborted="Tilkoblingen ble avbrutt. Dette betyr vanligvis at det er problemer med nettverkskoblingen mellom deg og strømmetjenesten." +ConnectionReset="Tilkoblingen ble avbrutt. Dette betyr vanligvis at det er problemer med nettverkskoblingen mellom deg og strømmetjenesten." +HostNotFound="Tjeneren ble ikke funnet. Kontroller at du har angitt en gyldig streaming server og at tilkoblingen / DNS fungerer." +NoData="Tjeneren funnet, men ingen data for den forespurte typen. Dette kan skje hvis du har bundet til en IPv6-adresse og streaming tjeneste har bare IPv4-adresser (se Snnstillinger / Avansert)." +AddressNotAvailable="Adresse ikke tilgjengelig. Du prøvde å binde til en ugyldig IP-adresse (se Innstillinger / Avansert)." diff --git a/plugins/obs-outputs/data/locale/ru-RU.ini b/plugins/obs-outputs/data/locale/ru-RU.ini index d113e39..7cf4f6f 100644 --- a/plugins/obs-outputs/data/locale/ru-RU.ini +++ b/plugins/obs-outputs/data/locale/ru-RU.ini @@ -4,4 +4,11 @@ FLVOutput="Выходной файл FLV" FLVOutput.FilePath="Путь к файлу" Default="По умолчанию" +ConnectionTimedOut="Истекло время ожидания соединения. Убедитесь, что вы настроили действительный сервис вещания и брандмауэр не блокирует подключение." +PermissionDenied="Соединение было заблокировано. Проверьте параметры вашего брандмауэра/антивируса, чтобы убедиться, что OBS разрешен полный доступ к интернету." +ConnectionAborted="Соединение было прервано. Обычно это указывает на проблемы с интернет-соединением между вами и службой вещания." +ConnectionReset="Соединение было сброшено одноранговым узлом. Обычно это указывает на проблемы с интернет-соединением между вами и службой вещания." +HostNotFound="Имя узла не найдено. Убедитесь, что вы ввели действительный сервер вещания и ваше подключение к интернету/DNS работают правильно." +NoData="Имя узла найдено, но нет данных запрошенного типа. Такое может случиться, если вы привязаны к IPv6-адресу, а ваш сервис вещания имеет только IPv4-адреса (смотрите Настройки - Расширенные)." +AddressNotAvailable="Адрес недоступен. Возможно вы пытались привязаться к недействительному IP-адресу (смотрите Настройки - Расширенные)." diff --git a/plugins/obs-outputs/data/locale/sk-SK.ini b/plugins/obs-outputs/data/locale/sk-SK.ini index 56b7d7b..d2b5d11 100644 --- a/plugins/obs-outputs/data/locale/sk-SK.ini +++ b/plugins/obs-outputs/data/locale/sk-SK.ini @@ -1,6 +1,9 @@ RTMPStream="RTMP stream" +RTMPStream.DropThreshold="Prah strát (milisekundy)" FLVOutput="Výstup do súboru FLV" FLVOutput.FilePath="Cesta k súboru" -Default="Základné" +Default="Predvolené" +ConnectionReset="Pripojenie bolo resetované druhou stranou. Toto obvykle znamená, že nastali problémy s pripojením medzi vami a streaming službou." +AddressNotAvailable="Adresa nie je k dispozícii. Môžno ste sa snažili naviazať na neplatnú IP adresu (pozrite si Nastavenia / Rozšírené)." diff --git a/plugins/obs-outputs/data/locale/sv-SE.ini b/plugins/obs-outputs/data/locale/sv-SE.ini index 6b17e4d..f8b8d45 100644 --- a/plugins/obs-outputs/data/locale/sv-SE.ini +++ b/plugins/obs-outputs/data/locale/sv-SE.ini @@ -7,6 +7,7 @@ Default="Standard" ConnectionTimedOut="Anslutningen förlorades. Se till att du har konfigurerat en giltig strömningstjänst och att inga brandväggar blockerar anslutningen." PermissionDenied="Anslutningen blockerades. Kontrollera inställningarna för din brandvägg/antivirusprogram för att se till att OBS har fullständig åtkomst till Internet." ConnectionAborted="Anslutningen avbröts. Detta kan indikera problem med Internetanslutningen mellan dig och strömningstjänsten." +ConnectionReset="Anslutningen återställdes av en peer. Detta kan indikera problem med Internetanslutningen mellan dig och strömningstjänsten." HostNotFound="Värdnamnet hittades inte. Se till att du har angivit en giltigt strömningstjänst och att din Internetanslutning / DNS fungerar på rätt sätt." NoData="Värdnamnet hittades, men ingen data av den begärda typen. Detta kan hända om du ansluter till en IPv6-adress och din strömningstjänst endast har IPv4-adresser (gå till Inställningar / Avancerat)." AddressNotAvailable="Adressen är inte tillgänglig. Du kanske försökte ansluta till en ogiltig IP-adress (gå till Inställningar / Avancerat)." diff --git a/plugins/obs-outputs/data/locale/vi-VN.ini b/plugins/obs-outputs/data/locale/vi-VN.ini index f71ceb6..5b2e11f 100644 --- a/plugins/obs-outputs/data/locale/vi-VN.ini +++ b/plugins/obs-outputs/data/locale/vi-VN.ini @@ -4,8 +4,10 @@ FLVOutput="FLV tập tin đầu ra" FLVOutput.FilePath="Đường dẫn tệp" Default="Mặc định" +ConnectionTimedOut="Kết nối đã hết thời. Đảm bảo bạn đã định cấu hình dịch vụ phát trực tuyến hợp lệ và không có tường lửa nào đang chặn kết nối." PermissionDenied="Kết nối đã bị chặn. Hãy kiểm tra tường lửa / cài đặt chống virus để đảm bảo rằng OBS được cho phép truy cập internet đầy đủ." ConnectionAborted="Kết nối đã bị hủy bỏ. Điều này thường chỉ ra kết nối internet giữa bạn và dịch vụ trực tuyến có vấn đề." +ConnectionReset="Kết nối đã được đặt lại bởi peer. Điều này thường chỉ ra các sự cố kết nối Internet giữa bạn và dịch vụ truyền trực tuyến." HostNotFound="Tên máy chủ không tìm thấy. Đảm bảo rằng bạn đã nhập vào một máy chủ stream hợp lệ và kết nối internet của bạn / DNS đang hoạt động tốt." NoData="Tên máy chủ được tìm thấy nhưng không có dữ liệu được yêu cầu. Điều này có thể xảy ra nếu bạn sử dụng địa chỉ IPv6 và dịch vụ stream của bạn chỉ có địa chỉ IPv4 (xem Cài đặt / Nâng cao)." AddressNotAvailable="Địa chỉ không có sẵn. Bạn có thể đã cố gắng liên kết với một địa chỉ IP không hợp lệ (xem Cài đặt / Nâng cao)." diff --git a/plugins/obs-outputs/flv-mux.c b/plugins/obs-outputs/flv-mux.c index e74b2da..6c6c0eb 100644 --- a/plugins/obs-outputs/flv-mux.c +++ b/plugins/obs-outputs/flv-mux.c @@ -102,6 +102,18 @@ static bool build_flv_meta_data(obs_output_t *context, enc_bool_val(&enc, end, "stereo", audio_output_get_channels(audio) == 2); + enc_bool_val(&enc, end, "2.1", + audio_output_get_channels(audio) == 3); + enc_bool_val(&enc, end, "3.1", + audio_output_get_channels(audio) == 4); + enc_bool_val(&enc, end, "4.0", + audio_output_get_channels(audio) == 4); + enc_bool_val(&enc, end, "4.1", + audio_output_get_channels(audio) == 5); + enc_bool_val(&enc, end, "5.1", + audio_output_get_channels(audio) == 6); + enc_bool_val(&enc, end, "7.1", + audio_output_get_channels(audio) == 8); dstr_printf(&encoder_name, "%s (libobs version ", MODULE_NAME); @@ -164,7 +176,7 @@ bool flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size, s_write(&s, meta_data, meta_data_size); - s_wb32(&s, (uint32_t)serializer_get_pos(&s) - start_pos + 4 - 1); + s_wb32(&s, (uint32_t)serializer_get_pos(&s) - start_pos - 1); *output = data.bytes.array; *size = data.bytes.num; @@ -177,11 +189,11 @@ bool flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size, static int32_t last_time = 0; #endif -static void flv_video(struct serializer *s, struct encoder_packet *packet, - bool is_header) +static void flv_video(struct serializer *s, int32_t dts_offset, + struct encoder_packet *packet, bool is_header) { int64_t offset = packet->pts - packet->dts; - int32_t time_ms = get_ms_time(packet, packet->dts); + int32_t time_ms = get_ms_time(packet, packet->dts) - dts_offset; if (!packet->data || !packet->size) return; @@ -209,13 +221,13 @@ static void flv_video(struct serializer *s, struct encoder_packet *packet, s_write(s, packet->data, packet->size); /* write tag size (starting byte doesn't count) */ - s_wb32(s, (uint32_t)serializer_get_pos(s) + 4 - 1); + s_wb32(s, (uint32_t)serializer_get_pos(s) - 1); } -static void flv_audio(struct serializer *s, struct encoder_packet *packet, - bool is_header) +static void flv_audio(struct serializer *s, int32_t dts_offset, + struct encoder_packet *packet, bool is_header) { - int32_t time_ms = get_ms_time(packet, packet->dts); + int32_t time_ms = get_ms_time(packet, packet->dts) - dts_offset; if (!packet->data || !packet->size) return; @@ -242,10 +254,10 @@ static void flv_audio(struct serializer *s, struct encoder_packet *packet, s_write(s, packet->data, packet->size); /* write tag size (starting byte doesn't count) */ - s_wb32(s, (uint32_t)serializer_get_pos(s) + 4 - 1); + s_wb32(s, (uint32_t)serializer_get_pos(s) - 1); } -void flv_packet_mux(struct encoder_packet *packet, +void flv_packet_mux(struct encoder_packet *packet, int32_t dts_offset, uint8_t **output, size_t *size, bool is_header) { struct array_output_data data; @@ -254,9 +266,9 @@ void flv_packet_mux(struct encoder_packet *packet, array_output_serializer_init(&s, &data); if (packet->type == OBS_ENCODER_VIDEO) - flv_video(&s, packet, is_header); + flv_video(&s, dts_offset, packet, is_header); else - flv_audio(&s, packet, is_header); + flv_audio(&s, dts_offset, packet, is_header); *output = data.bytes.array; *size = data.bytes.num; diff --git a/plugins/obs-outputs/flv-mux.h b/plugins/obs-outputs/flv-mux.h index e4c655e..50c37e2 100644 --- a/plugins/obs-outputs/flv-mux.h +++ b/plugins/obs-outputs/flv-mux.h @@ -21,14 +21,14 @@ #define MILLISECOND_DEN 1000 -static uint32_t get_ms_time(struct encoder_packet *packet, int64_t val) +static int32_t get_ms_time(struct encoder_packet *packet, int64_t val) { - return (uint32_t)(val * MILLISECOND_DEN / packet->timebase_den); + return (int32_t)(val * MILLISECOND_DEN / packet->timebase_den); } extern void write_file_info(FILE *file, int64_t duration_ms, int64_t size); extern bool flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size, bool write_header, size_t audio_idx); -extern void flv_packet_mux(struct encoder_packet *packet, +extern void flv_packet_mux(struct encoder_packet *packet, int32_t dts_offset, uint8_t **output, size_t *size, bool is_header); diff --git a/plugins/obs-outputs/flv-output.c b/plugins/obs-outputs/flv-output.c index 8f191b0..680a954 100644 --- a/plugins/obs-outputs/flv-output.c +++ b/plugins/obs-outputs/flv-output.c @@ -32,14 +32,31 @@ #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) struct flv_output { - obs_output_t *output; - struct dstr path; - FILE *file; - bool active; - bool sent_headers; - int64_t last_packet_ts; + obs_output_t *output; + struct dstr path; + FILE *file; + volatile bool active; + volatile bool stopping; + uint64_t stop_ts; + bool sent_headers; + int64_t last_packet_ts; + + pthread_mutex_t mutex; + + bool got_first_video; + int32_t start_dts_offset; }; +static inline bool stopping(struct flv_output *stream) +{ + return os_atomic_load_bool(&stream->stopping); +} + +static inline bool active(struct flv_output *stream) +{ + return os_atomic_load_bool(&stream->active); +} + static const char *flv_output_getname(void *unused) { UNUSED_PARAMETER(unused); @@ -52,9 +69,7 @@ static void flv_output_destroy(void *data) { struct flv_output *stream = data; - if (stream->active) - flv_output_stop(data, 0); - + pthread_mutex_destroy(&stream->mutex); dstr_free(&stream->path); bfree(stream); } @@ -63,32 +78,12 @@ static void *flv_output_create(obs_data_t *settings, obs_output_t *output) { struct flv_output *stream = bzalloc(sizeof(struct flv_output)); stream->output = output; + pthread_mutex_init(&stream->mutex, NULL); UNUSED_PARAMETER(settings); return stream; } -static void flv_output_stop(void *data, uint64_t ts) -{ - struct flv_output *stream = data; - - if (stream->active) { - if (stream->file) { - write_file_info(stream->file, stream->last_packet_ts, - os_ftelli64(stream->file)); - - fclose(stream->file); - } - obs_output_end_data_capture(stream->output); - stream->active = false; - stream->sent_headers = false; - - info("FLV file output complete"); - } - - UNUSED_PARAMETER(ts); -} - static int write_packet(struct flv_output *stream, struct encoder_packet *packet, bool is_header) { @@ -98,10 +93,10 @@ static int write_packet(struct flv_output *stream, stream->last_packet_ts = get_ms_time(packet, packet->dts); - flv_packet_mux(packet, &data, &size, is_header); + flv_packet_mux(packet, is_header ? 0 : stream->start_dts_offset, + &data, &size, is_header); fwrite(data, 1, size, stream->file); bfree(data); - obs_encoder_packet_release(packet); return ret; } @@ -120,15 +115,13 @@ static void write_audio_header(struct flv_output *stream) { obs_output_t *context = stream->output; obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, 0); - uint8_t *header; struct encoder_packet packet = { .type = OBS_ENCODER_AUDIO, .timebase_den = 1 }; - obs_encoder_get_extra_data(aencoder, &header, &packet.size); - packet.data = bmemdup(header, packet.size); + obs_encoder_get_extra_data(aencoder, &packet.data, &packet.size); write_packet(stream, &packet, true); } @@ -148,13 +141,14 @@ static void write_video_header(struct flv_output *stream) obs_encoder_get_extra_data(vencoder, &header, &size); packet.size = obs_parse_avc_header(&packet.data, header, size); write_packet(stream, &packet, true); + bfree(packet.data); } static void write_headers(struct flv_output *stream) { write_meta_data(stream); - write_audio_header(stream); write_video_header(stream); + write_audio_header(stream); } static bool flv_output_start(void *data) @@ -168,6 +162,10 @@ static bool flv_output_start(void *data) if (!obs_output_initialize_encoders(stream->output, 0)) return false; + stream->got_first_video = false; + stream->sent_headers = false; + os_atomic_set_bool(&stream->stopping, false); + /* get path */ settings = obs_output_get_settings(stream->output); path = obs_data_get_string(settings, "path"); @@ -181,30 +179,73 @@ static bool flv_output_start(void *data) } /* write headers and start capture */ - stream->active = true; + os_atomic_set_bool(&stream->active, true); obs_output_begin_data_capture(stream->output, 0); info("Writing FLV file '%s'...", stream->path.array); return true; } +static void flv_output_stop(void *data, uint64_t ts) +{ + struct flv_output *stream = data; + stream->stop_ts = ts / 1000; + os_atomic_set_bool(&stream->stopping, true); +} + +static void flv_output_actual_stop(struct flv_output *stream) +{ + os_atomic_set_bool(&stream->active, false); + + if (stream->file) { + write_file_info(stream->file, stream->last_packet_ts, + os_ftelli64(stream->file)); + + fclose(stream->file); + } + obs_output_end_data_capture(stream->output); + + info("FLV file output complete"); +} + static void flv_output_data(void *data, struct encoder_packet *packet) { struct flv_output *stream = data; struct encoder_packet parsed_packet; + pthread_mutex_lock(&stream->mutex); + + if (!active(stream)) + goto unlock; + + if (stopping(stream)) { + if (packet->sys_dts_usec >= (int64_t)stream->stop_ts) { + flv_output_actual_stop(stream); + goto unlock; + } + } + if (!stream->sent_headers) { write_headers(stream); stream->sent_headers = true; } if (packet->type == OBS_ENCODER_VIDEO) { + if (!stream->got_first_video) { + stream->start_dts_offset = + get_ms_time(packet, packet->dts); + stream->got_first_video = true; + } + obs_parse_avc_packet(&parsed_packet, packet); write_packet(stream, &parsed_packet, false); obs_encoder_packet_release(&parsed_packet); } else { write_packet(stream, packet, false); } + +unlock: + pthread_mutex_unlock(&stream->mutex); } static obs_properties_t *flv_output_properties(void *unused) @@ -220,13 +261,15 @@ static obs_properties_t *flv_output_properties(void *unused) } struct obs_output_info flv_output_info = { - .id = "flv_output", - .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED, - .get_name = flv_output_getname, - .create = flv_output_create, - .destroy = flv_output_destroy, - .start = flv_output_start, - .stop = flv_output_stop, - .encoded_packet = flv_output_data, - .get_properties = flv_output_properties + .id = "flv_output", + .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED, + .encoded_video_codecs = "h264", + .encoded_audio_codecs = "aac", + .get_name = flv_output_getname, + .create = flv_output_create, + .destroy = flv_output_destroy, + .start = flv_output_start, + .stop = flv_output_stop, + .encoded_packet = flv_output_data, + .get_properties = flv_output_properties }; diff --git a/plugins/obs-outputs/ftl-stream.c b/plugins/obs-outputs/ftl-stream.c new file mode 100644 index 0000000..eeb98ed --- /dev/null +++ b/plugins/obs-outputs/ftl-stream.c @@ -0,0 +1,1166 @@ +/****************************************************************************** + Copyright (C) 2017 by Quinn Damerell + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include "ftl.h" +#include "flv-mux.h" +#include "net-if.h" + +#ifdef _WIN32 +#include +#else +#include +#define INFINITE 0xFFFFFFFF +#endif + +#define do_log(level, format, ...) \ + blog(level, "[ftl stream: '%s'] " format, \ + obs_output_get_name(stream->output), ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) +#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) + +#define OPT_DROP_THRESHOLD "drop_threshold_ms" +#define OPT_MAX_SHUTDOWN_TIME_SEC "max_shutdown_time_sec" +#define OPT_BIND_IP "bind_ip" + +typedef struct _nalu_t { + int len; + int dts_usec; + int send_marker_bit; + uint8_t *data; +} nalu_t; + +typedef struct _frame_of_nalus_t { + nalu_t nalus[100]; + int total; + int complete_frame; +} frame_of_nalus_t; + +struct ftl_stream { + obs_output_t *output; + + pthread_mutex_t packets_mutex; + struct circlebuf packets; + bool sent_headers; + int64_t frames_sent; + + volatile bool connecting; + pthread_t connect_thread; + pthread_t status_thread; + + volatile bool active; + volatile bool disconnected; + pthread_t send_thread; + + int max_shutdown_time_sec; + + os_sem_t *send_sem; + os_event_t *stop_event; + uint64_t stop_ts; + uint64_t shutdown_timeout_ts; + + struct dstr path; + uint32_t channel_id; + struct dstr username, password; + struct dstr encoder_name; + struct dstr bind_ip; + + /* frame drop variables */ + int64_t drop_threshold_usec; + int64_t pframe_drop_threshold_usec; + int min_priority; + float congestion; + + int64_t last_dts_usec; + + uint64_t total_bytes_sent; + uint64_t dropped_frames; + uint64_t last_nack_count; + + ftl_handle_t ftl_handle; + ftl_ingest_params_t params; + int peak_kbps; + uint32_t scale_width, scale_height, width, height; + frame_of_nalus_t coded_pic_buffer; +}; + +static void log_libftl_messages(ftl_log_severity_t log_level, + const char *message); +static int init_connect(struct ftl_stream *stream); +static void *connect_thread(void *data); +static void *status_thread(void *data); +static int _ftl_error_to_obs_error(int status); + +static const char *ftl_stream_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("FTLStream"); +} + +static void log_ftl(int level, const char *format, va_list args) +{ + blogva(LOG_INFO, format, args); + UNUSED_PARAMETER(level); +} + +static inline size_t num_buffered_packets(struct ftl_stream *stream); + +static inline void free_packets(struct ftl_stream *stream) +{ + size_t num_packets; + + pthread_mutex_lock(&stream->packets_mutex); + + num_packets = num_buffered_packets(stream); + if (num_packets) + info("Freeing %d remaining packets", (int)num_packets); + + while (stream->packets.size) { + struct encoder_packet packet; + circlebuf_pop_front(&stream->packets, &packet, sizeof(packet)); + obs_encoder_packet_release(&packet); + } + pthread_mutex_unlock(&stream->packets_mutex); +} + +static inline bool stopping(struct ftl_stream *stream) +{ + return os_event_try(stream->stop_event) != EAGAIN; +} + +static inline bool connecting(struct ftl_stream *stream) +{ + return os_atomic_load_bool(&stream->connecting); +} + +static inline bool active(struct ftl_stream *stream) +{ + return os_atomic_load_bool(&stream->active); +} + +static inline bool disconnected(struct ftl_stream *stream) +{ + return os_atomic_load_bool(&stream->disconnected); +} + +static void ftl_stream_destroy(void *data) +{ + struct ftl_stream *stream = data; + ftl_status_t status_code; + + info("ftl_stream_destroy"); + + if (stopping(stream) && !connecting(stream)) { + pthread_join(stream->send_thread, NULL); + + } else if (connecting(stream) || active(stream)) { + if (stream->connecting) { + info("wait for connect_thread to terminate"); + pthread_join(stream->status_thread, NULL); + pthread_join(stream->connect_thread, NULL); + info("wait for connect_thread to terminate: done"); + } + + stream->stop_ts = 0; + os_event_signal(stream->stop_event); + + if (active(stream)) { + os_sem_post(stream->send_sem); + obs_output_end_data_capture(stream->output); + pthread_join(stream->send_thread, NULL); + } + } + + info("ingest destroy"); + + status_code = ftl_ingest_destroy(&stream->ftl_handle); + if (status_code != FTL_SUCCESS) { + info("Failed to destroy from ingest %d", status_code); + } + + if (stream) { + free_packets(stream); + dstr_free(&stream->path); + dstr_free(&stream->username); + dstr_free(&stream->password); + dstr_free(&stream->encoder_name); + dstr_free(&stream->bind_ip); + os_event_destroy(stream->stop_event); + os_sem_destroy(stream->send_sem); + pthread_mutex_destroy(&stream->packets_mutex); + circlebuf_free(&stream->packets); + bfree(stream); + } +} + +static void *ftl_stream_create(obs_data_t *settings, obs_output_t *output) +{ + struct ftl_stream *stream = bzalloc(sizeof(struct ftl_stream)); + info("ftl_stream_create"); + + stream->output = output; + pthread_mutex_init_value(&stream->packets_mutex); + + stream->peak_kbps = -1; + ftl_init(); + + if (pthread_mutex_init(&stream->packets_mutex, NULL) != 0) { + goto fail; + } + if (os_event_init(&stream->stop_event, OS_EVENT_TYPE_MANUAL) != 0) { + goto fail; + } + + stream->coded_pic_buffer.total = 0; + stream->coded_pic_buffer.complete_frame = 0; + + UNUSED_PARAMETER(settings); + return stream; + +fail: + return NULL; +} + +static void ftl_stream_stop(void *data, uint64_t ts) +{ + struct ftl_stream *stream = data; + info("ftl_stream_stop"); + + if (stopping(stream) && ts != 0) { + return; + } + + if (connecting(stream)) { + pthread_join(stream->status_thread, NULL); + pthread_join(stream->connect_thread, NULL); + } + + stream->stop_ts = ts / 1000ULL; + + if (ts) { + stream->shutdown_timeout_ts = ts + + (uint64_t)stream->max_shutdown_time_sec * 1000000000ULL; + } + + if (active(stream)) { + os_event_signal(stream->stop_event); + if (stream->stop_ts == 0) + os_sem_post(stream->send_sem); + } else { + obs_output_signal_stop(stream->output, OBS_OUTPUT_SUCCESS); + } +} + +static inline bool get_next_packet(struct ftl_stream *stream, + struct encoder_packet *packet) +{ + bool new_packet = false; + + pthread_mutex_lock(&stream->packets_mutex); + if (stream->packets.size) { + circlebuf_pop_front(&stream->packets, packet, + sizeof(struct encoder_packet)); + new_packet = true; + } + pthread_mutex_unlock(&stream->packets_mutex); + + return new_packet; +} + +static int avc_get_video_frame(struct ftl_stream *stream, + struct encoder_packet *packet, bool is_header) +{ + int consumed = 0; + int len = (int)packet->size; + nalu_t *nalu; + + unsigned char *video_stream = packet->data; + + while ((size_t)consumed < packet->size) { + size_t total_max = sizeof(stream->coded_pic_buffer.nalus) / + sizeof(stream->coded_pic_buffer.nalus[0]); + + if ((size_t)stream->coded_pic_buffer.total >= total_max) { + warn("ERROR: cannot continue, nalu buffers are full"); + return -1; + } + + nalu = &stream->coded_pic_buffer.nalus + [stream->coded_pic_buffer.total]; + + if (is_header) { + if (consumed == 0) { + //first 6 bytes are some obs header with part + //of the sps + video_stream += 6; + consumed += 6; + } else { + //another spacer byte of 0x1 + video_stream += 1; + consumed += 1; + } + + len = video_stream[0] << 8 | video_stream[1]; + video_stream += 2; + consumed += 2; + } else { + len = video_stream[0] << 24 | + video_stream[1] << 16 | + video_stream[2] << 8 | + video_stream[3]; + + if ((size_t)len > (packet->size - (size_t)consumed)) { + warn("ERROR: got len of %d but packet only " + "has %d left", + len, + (int)(packet->size - consumed)); + } + + consumed += 4; + video_stream += 4; + } + + consumed += len; + + uint8_t nalu_type = video_stream[0] & 0x1F; + uint8_t nri = (video_stream[0] >> 5) & 0x3; + + if ((nalu_type != 12 && nalu_type != 6 && nalu_type != 9) || + nri) { + nalu->data = video_stream; + nalu->len = len; + nalu->send_marker_bit = 0; + stream->coded_pic_buffer.total++; + } + + video_stream += len; + } + + if (!is_header) { + size_t idx = stream->coded_pic_buffer.total - 1; + stream->coded_pic_buffer.nalus[idx].send_marker_bit = 1; + } + + return 0; +} + +static int send_packet(struct ftl_stream *stream, + struct encoder_packet *packet, bool is_header) +{ + int bytes_sent = 0; + int ret = 0; + + if (packet->type == OBS_ENCODER_VIDEO) { + stream->coded_pic_buffer.total = 0; + avc_get_video_frame(stream, packet, is_header); + + int i; + for (i = 0; i < stream->coded_pic_buffer.total; i++) { + nalu_t *nalu = &stream->coded_pic_buffer.nalus[i]; + bytes_sent += ftl_ingest_send_media_dts( + &stream->ftl_handle, + FTL_VIDEO_DATA, + packet->dts_usec, + nalu->data, + nalu->len, + nalu->send_marker_bit); + + if (nalu->send_marker_bit) { + stream->frames_sent++; + } + } + + } else if (packet->type == OBS_ENCODER_AUDIO) { + bytes_sent += ftl_ingest_send_media_dts( + &stream->ftl_handle, + FTL_AUDIO_DATA, + packet->dts_usec, + packet->data, + (int)packet->size, 0); + } else { + warn("Got packet type %d", packet->type); + } + + if (is_header) { + bfree(packet->data); + } + else { + obs_encoder_packet_release(packet); + } + + stream->total_bytes_sent += bytes_sent; + return ret; +} + +static void set_peak_bitrate(struct ftl_stream *stream) +{ + int speedtest_kbps = 15000; + int speedtest_duration = 1000; + speed_test_t results; + ftl_status_t status_code; + + status_code = ftl_ingest_speed_test_ex( + &stream->ftl_handle, + speedtest_kbps, + speedtest_duration, + &results); + + float percent_lost = 0; + + if (status_code == FTL_SUCCESS) { + percent_lost = (float)results.lost_pkts * 100.f / + (float)results.pkts_sent; + } else { + warn("Speed test failed with: %s", + ftl_status_code_to_string(status_code)); + } + + // Get what the user set the encoding bitrate to. + obs_encoder_t *video_encoder = obs_output_get_video_encoder(stream->output); + obs_data_t *video_settings = obs_encoder_get_settings(video_encoder); + int user_desired_bitrate = (int)obs_data_get_int(video_settings, "bitrate"); + obs_data_release(video_settings); + + // Report the results. + info("Speed test completed: User desired bitrate %d, Peak kbps %d, " + "initial rtt %d, " + "final rtt %d, %3.2f lost packets", + user_desired_bitrate, + results.peak_kbps, + results.starting_rtt, + results.ending_rtt, + percent_lost); + + // We still want to set the peak to about 1.2x what the target bitrate is, + // even if the speed test reported it should be lower. If we don't, FTL + // will queue data on the client and start adding latency. If the internet + // connection really can't handle the bitrate the user will see either lost frame + // and recovered frame counts go up, which is reflect in the dropped_frames count. + stream->peak_kbps = stream->params.peak_kbps = user_desired_bitrate * 12 / 10; + ftl_ingest_update_params(&stream->ftl_handle, &stream->params); +} + +static inline bool send_headers(struct ftl_stream *stream, int64_t dts_usec); + +static inline bool can_shutdown_stream(struct ftl_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 ftl_stream *stream = data; + ftl_status_t status_code; + + os_set_thread_name("ftl-stream: send_thread"); + + while (os_sem_wait(stream->send_sem) == 0) { + struct encoder_packet packet; + + if (stopping(stream) && stream->stop_ts == 0) { + break; + } + + if (!get_next_packet(stream, &packet)) + continue; + + if (stopping(stream)) { + if (can_shutdown_stream(stream, &packet)) { + obs_encoder_packet_release(&packet); + break; + } + } + + /* sends sps/pps on every key frame as this is typically + * required for webrtc */ + if (packet.keyframe) { + if (!send_headers(stream, packet.dts_usec)) { + os_atomic_set_bool(&stream->disconnected, true); + break; + } + } + + if (send_packet(stream, &packet, false) < 0) { + os_atomic_set_bool(&stream->disconnected, true); + break; + } + } + + if (disconnected(stream)) { + info("Disconnected from %s", stream->path.array); + } else { + info("User stopped the stream"); + } + + if (!stopping(stream)) { + pthread_detach(stream->send_thread); + obs_output_signal_stop(stream->output, OBS_OUTPUT_DISCONNECTED); + } else { + obs_output_end_data_capture(stream->output); + } + + info("ingest disconnect"); + + status_code = ftl_ingest_disconnect(&stream->ftl_handle); + if (status_code != FTL_SUCCESS) { + printf("Failed to disconnect from ingest %d", status_code); + } + + free_packets(stream); + os_event_reset(stream->stop_event); + os_atomic_set_bool(&stream->active, false); + stream->sent_headers = false; + return NULL; +} + +static bool send_video_header(struct ftl_stream *stream, int64_t dts_usec) +{ + obs_output_t *context = stream->output; + obs_encoder_t *vencoder = obs_output_get_video_encoder(context); + uint8_t *header; + size_t size; + + struct encoder_packet packet = { + .type = OBS_ENCODER_VIDEO, + .timebase_den = 1, + .keyframe = true, + .dts_usec = dts_usec + }; + + obs_encoder_get_extra_data(vencoder, &header, &size); + packet.size = obs_parse_avc_header(&packet.data, header, size); + return send_packet(stream, &packet, true) >= 0; +} + +static inline bool send_headers(struct ftl_stream *stream, int64_t dts_usec) +{ + stream->sent_headers = true; + + if (!send_video_header(stream, dts_usec)) + return false; + + return true; +} + +static inline bool reset_semaphore(struct ftl_stream *stream) +{ + os_sem_destroy(stream->send_sem); + return os_sem_init(&stream->send_sem, 0) == 0; +} + +#ifdef _WIN32 +#define socklen_t int +#endif + +static int init_send(struct ftl_stream *stream) +{ + int ret; + + reset_semaphore(stream); + + ret = pthread_create(&stream->send_thread, NULL, send_thread, stream); + if (ret != 0) { + warn("Failed to create send thread"); + return OBS_OUTPUT_ERROR; + } + + os_atomic_set_bool(&stream->active, true); + + obs_output_begin_data_capture(stream->output, 0); + + return OBS_OUTPUT_SUCCESS; +} + +static int try_connect(struct ftl_stream *stream) +{ + ftl_status_t status_code; + + if (dstr_is_empty(&stream->path)) { + warn("URL is empty"); + return OBS_OUTPUT_BAD_PATH; + } + + info("Connecting to FTL Ingest URL %s...", stream->path.array); + + stream->width = (int)obs_output_get_width(stream->output); + stream->height = (int)obs_output_get_height(stream->output); + + status_code = ftl_ingest_connect(&stream->ftl_handle); + if (status_code != FTL_SUCCESS) { + if (status_code == FTL_BAD_OR_INVALID_STREAM_KEY) { + blog(LOG_ERROR, "Invalid Key (%s)", + ftl_status_code_to_string(status_code)); + return OBS_OUTPUT_INVALID_STREAM; + } else { + warn("Ingest connect failed with: %s (%d)", + ftl_status_code_to_string(status_code), + status_code); + return _ftl_error_to_obs_error(status_code); + } + } + + info("Connection to %s successful", stream->path.array); + + // Always get the peak bitrate when we are starting. + set_peak_bitrate(stream); + + pthread_create(&stream->status_thread, NULL, status_thread, stream); + + return init_send(stream); +} + +static bool ftl_stream_start(void *data) +{ + struct ftl_stream *stream = data; + + info("ftl_stream_start"); + + // Mixer doesn't support bframes. So force them off. + obs_encoder_t *video_encoder = obs_output_get_video_encoder(stream->output); + obs_data_t *video_settings = obs_encoder_get_settings(video_encoder); + obs_data_set_int(video_settings, "bf", 0); + obs_data_release(video_settings); + + if (!obs_output_can_begin_data_capture(stream->output, 0)) { + return false; + } + if (!obs_output_initialize_encoders(stream->output, 0)) { + return false; + } + + stream->frames_sent = 0; + os_atomic_set_bool(&stream->connecting, true); + + return pthread_create(&stream->connect_thread, NULL, connect_thread, + stream) == 0; +} + +static inline bool add_packet(struct ftl_stream *stream, + struct encoder_packet *packet) +{ + circlebuf_push_back(&stream->packets, packet, + sizeof(struct encoder_packet)); + return true; +} + +static inline size_t num_buffered_packets(struct ftl_stream *stream) +{ + return stream->packets.size / sizeof(struct encoder_packet); +} + +static void drop_frames(struct ftl_stream *stream, const char *name, + int highest_priority, bool pframes) +{ + UNUSED_PARAMETER(pframes); + + struct circlebuf new_buf = {0}; + int num_frames_dropped = 0; + +#ifdef _DEBUG + int start_packets = (int)num_buffered_packets(stream); +#else + UNUSED_PARAMETER(name); +#endif + + circlebuf_reserve(&new_buf, sizeof(struct encoder_packet) * 8); + + while (stream->packets.size) { + struct encoder_packet packet; + circlebuf_pop_front(&stream->packets, &packet, sizeof(packet)); + + /* do not drop audio data or video keyframes */ + if (packet.type == OBS_ENCODER_AUDIO || + packet.drop_priority >= highest_priority) { + circlebuf_push_back(&new_buf, &packet, sizeof(packet)); + + } else { + num_frames_dropped++; + obs_encoder_packet_release(&packet); + } + } + + circlebuf_free(&stream->packets); + stream->packets = new_buf; + + if (stream->min_priority < highest_priority) + stream->min_priority = highest_priority; + if (!num_frames_dropped) + return; + + stream->dropped_frames += num_frames_dropped; +#ifdef _DEBUG + debug("Dropped %s, prev packet count: %d, new packet count: %d", + name, + start_packets, + (int)num_buffered_packets(stream)); +#endif +} + +static bool find_first_video_packet(struct ftl_stream *stream, + struct encoder_packet *first) +{ + size_t count = stream->packets.size / sizeof(*first); + + for (size_t i = 0; i < count; i++) { + struct encoder_packet *cur = circlebuf_data(&stream->packets, + i * sizeof(*first)); + if (cur->type == OBS_ENCODER_VIDEO && !cur->keyframe) { + *first = *cur; + return true; + } + } + + return false; +} + +static void check_to_drop_frames(struct ftl_stream *stream, bool pframes) +{ + struct encoder_packet first; + int64_t buffer_duration_usec; + 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 drop_threshold = pframes ? + stream->pframe_drop_threshold_usec : + stream->drop_threshold_usec; + + if (num_packets < 5) { + if (!pframes) + stream->congestion = 0.0f; + return; + } + + if (!find_first_video_packet(stream, &first)) + 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 (!pframes) { + stream->congestion = (float)buffer_duration_usec / + (float)drop_threshold; + } + + if (buffer_duration_usec > drop_threshold) { + debug("buffer_duration_usec: %" PRId64, buffer_duration_usec); + drop_frames(stream, name, priority, pframes); + } +} + +static bool add_video_packet(struct ftl_stream *stream, + struct encoder_packet *packet) +{ + check_to_drop_frames(stream, false); + check_to_drop_frames(stream, true); + + /* if currently dropping frames, drop packets until it reaches the + * desired priority */ + if (packet->priority < stream->min_priority) { + stream->dropped_frames++; + return false; + } else { + stream->min_priority = 0; + } + + stream->last_dts_usec = packet->dts_usec; + return add_packet(stream, packet); +} + + +static void ftl_stream_data(void *data, struct encoder_packet *packet) +{ + struct ftl_stream *stream = data; + + struct encoder_packet new_packet; + bool added_packet = false; + + if (disconnected(stream) || !active(stream)) + return; + + if (packet->type == OBS_ENCODER_VIDEO) + obs_parse_avc_packet(&new_packet, packet); + else + obs_encoder_packet_ref(&new_packet, packet); + + pthread_mutex_lock(&stream->packets_mutex); + + if (!disconnected(stream)) { + added_packet = (packet->type == OBS_ENCODER_VIDEO) ? + add_video_packet(stream, &new_packet) : + add_packet(stream, &new_packet); + } + + pthread_mutex_unlock(&stream->packets_mutex); + + if (added_packet) + os_sem_post(stream->send_sem); + else + obs_encoder_packet_release(&new_packet); +} + +static void ftl_stream_defaults(obs_data_t *defaults) +{ + UNUSED_PARAMETER(defaults); +} + + +static obs_properties_t *ftl_stream_properties(void *unused) +{ + UNUSED_PARAMETER(unused); + + obs_properties_t *props = obs_properties_create(); + obs_properties_add_int(props, "peak_bitrate_kbps", + obs_module_text("FTLStream.PeakBitrate"), + 1000, 10000, 500); + + return props; +} + + +static uint64_t ftl_stream_total_bytes_sent(void *data) +{ + struct ftl_stream *stream = data; + + return stream->total_bytes_sent; +} + +static int ftl_stream_dropped_frames(void *data) +{ + struct ftl_stream *stream = data; + return (int)stream->dropped_frames; +} + +static float ftl_stream_congestion(void *data) +{ + struct ftl_stream *stream = data; + return stream->min_priority > 0 ? 1.0f : stream->congestion; +} + +enum ret_type { + RET_CONTINUE, + RET_BREAK, + RET_EXIT, +}; + +static enum ret_type ftl_event(struct ftl_stream *stream, + ftl_status_msg_t status) +{ + if (status.msg.event.type != FTL_STATUS_EVENT_TYPE_DISCONNECTED) + return RET_CONTINUE; + + info("Disconnected from ingest with reason: %s", + ftl_status_code_to_string(status.msg.event.error_code)); + + if (status.msg.event.reason == FTL_STATUS_EVENT_REASON_API_REQUEST) { + return RET_BREAK; + } + + //tell OBS and it will trigger a reconnection + blog(LOG_WARNING, "Reconnecting to Ingest"); + obs_output_signal_stop(stream->output, OBS_OUTPUT_DISCONNECTED); + return RET_EXIT; +} + +static void *status_thread(void *data) +{ + struct ftl_stream *stream = data; + + ftl_status_msg_t status; + ftl_status_t status_code; + + while (!disconnected(stream)) { + status_code = ftl_ingest_get_status(&stream->ftl_handle, + &status, 1000); + + if (status_code == FTL_STATUS_TIMEOUT || + status_code == FTL_QUEUE_EMPTY) { + continue; + } else if (status_code == FTL_NOT_INITIALIZED) { + break; + } + + if (status.type == FTL_STATUS_EVENT) { + enum ret_type ret_type = ftl_event(stream, status); + if (ret_type == RET_EXIT) + return NULL; + else if (ret_type == RET_BREAK) + break; + + } else if(status.type == FTL_STATUS_LOG) { + blog(LOG_INFO, "[%d] %s", status.msg.log.log_level, + status.msg.log.string); + + } else if (status.type == FTL_STATUS_VIDEO_PACKETS) { + ftl_packet_stats_msg_t *p = &status.msg.pkt_stats; + + // Report nack requests as dropped frames + stream->dropped_frames += + p->nack_reqs -stream->last_nack_count; + stream->last_nack_count = p->nack_reqs; + + int log_level = p->nack_reqs > 2 ? LOG_INFO : LOG_DEBUG; + + blog(log_level, "Avg packet send per second %3.1f, " + "total nack requests %d", + (float)p->sent * 1000.f / p->period, + (int)p->nack_reqs); + + } else if (status.type == FTL_STATUS_VIDEO_PACKETS_INSTANT) { + ftl_packet_stats_instant_msg_t *p = + &status.msg.ipkt_stats; + + int log_level = p->avg_rtt > 200 ? LOG_INFO : LOG_DEBUG; + + blog(log_level, "avg transmit delay %dms " + "(min: %d, max: %d), " + "avg rtt %dms (min: %d, max: %d)", + p->avg_xmit_delay, + p->min_xmit_delay, p->max_xmit_delay, + p->avg_rtt, p->min_rtt, p->max_rtt); + + } else if (status.type == FTL_STATUS_VIDEO) { + ftl_video_frame_stats_msg_t *v = + &status.msg.video_stats; + + int log_level = v->queue_fullness > 5 ? + LOG_INFO : LOG_DEBUG; + + blog(log_level, "Queue an average of %3.2f fps " + "(%3.1f kbps), " + "sent an average of %3.2f fps " + "(%3.1f kbps), " + "queue fullness %d, " + "max frame size %d", + (float)v->frames_queued * 1000.f / v->period, + (float)v->bytes_queued / v->period * 8, + (float)v->frames_sent * 1000.f / v->period, + (float)v->bytes_sent / v->period * 8, + v->queue_fullness, v->max_frame_size); + } else { + blog(LOG_DEBUG, "Status: Got Status message of type " + "%d", status.type); + } + } + + blog(LOG_DEBUG, "status_thread: Exited"); + pthread_detach(stream->status_thread); + return NULL; +} + +static void *connect_thread(void *data) +{ + struct ftl_stream *stream = data; + int ret; + + os_set_thread_name("ftl-stream: connect_thread"); + + blog(LOG_WARNING, "ftl-stream: connect thread"); + + ret = init_connect(stream); + if (ret != OBS_OUTPUT_SUCCESS) { + obs_output_signal_stop(stream->output, ret); + return NULL; + } + + ret = try_connect(stream); + if (ret != OBS_OUTPUT_SUCCESS) { + obs_output_signal_stop(stream->output, ret); + info("Connection to %s failed: %d", stream->path.array, ret); + } + + if (!stopping(stream)) + pthread_detach(stream->connect_thread); + + os_atomic_set_bool(&stream->connecting, false); + return NULL; +} + +static void log_libftl_messages(ftl_log_severity_t log_level, + const char * message) +{ + UNUSED_PARAMETER(log_level); + blog(LOG_WARNING, "[libftl] %s", message); +} + +static int init_connect(struct ftl_stream *stream) +{ + obs_service_t *service; + obs_data_t *settings; + const char *bind_ip, *key; + ftl_status_t status_code; + + info("init_connect"); + + if (stopping(stream)) + pthread_join(stream->send_thread, NULL); + + free_packets(stream); + + service = obs_output_get_service(stream->output); + if (!service) { + return OBS_OUTPUT_ERROR; + } + + os_atomic_set_bool(&stream->disconnected, false); + stream->total_bytes_sent = 0; + stream->dropped_frames = 0; + stream->min_priority = 0; + + settings = obs_output_get_settings(stream->output); + obs_encoder_t *video_encoder = + obs_output_get_video_encoder(stream->output); + obs_data_t *video_settings = + obs_encoder_get_settings(video_encoder); + + dstr_copy(&stream->path, obs_service_get_url(service)); + key = obs_service_get_key(service); + + int target_bitrate = (int)obs_data_get_int(video_settings, "bitrate"); + int peak_bitrate = (int)((float)target_bitrate * 1.1f); + + //minimum overshoot tolerance of 10% + if (peak_bitrate < target_bitrate) { + peak_bitrate = target_bitrate; + } + + stream->params.stream_key = (char*)key; + stream->params.video_codec = FTL_VIDEO_H264; + stream->params.audio_codec = FTL_AUDIO_OPUS; + stream->params.ingest_hostname = stream->path.array; + stream->params.vendor_name = "OBS Studio"; + stream->params.vendor_version = OBS_VERSION; + stream->params.peak_kbps = + stream->peak_kbps < 0 ? 0 : stream->peak_kbps; + + //not required when using ftl_ingest_send_media_dts + stream->params.fps_num = 0; + stream->params.fps_den = 0; + + status_code = ftl_ingest_create(&stream->ftl_handle, &stream->params); + if (status_code != FTL_SUCCESS) { + if (status_code == FTL_BAD_OR_INVALID_STREAM_KEY) { + blog(LOG_ERROR, "Invalid Key (%s)", + ftl_status_code_to_string(status_code)); + return OBS_OUTPUT_INVALID_STREAM; + } + else { + blog(LOG_ERROR, "Failed to create ingest handle (%s)", + ftl_status_code_to_string(status_code)); + return OBS_OUTPUT_ERROR; + } + } + + dstr_copy(&stream->username, obs_service_get_username(service)); + dstr_copy(&stream->password, obs_service_get_password(service)); + dstr_depad(&stream->path); + + stream->drop_threshold_usec = + (int64_t)obs_data_get_int(settings, OPT_DROP_THRESHOLD) * 1000; + stream->max_shutdown_time_sec = + (int)obs_data_get_int(settings, OPT_MAX_SHUTDOWN_TIME_SEC); + + bind_ip = obs_data_get_string(settings, OPT_BIND_IP); + dstr_copy(&stream->bind_ip, bind_ip); + + obs_data_release(settings); + obs_data_release(video_settings); + return OBS_OUTPUT_SUCCESS; +} + +// Returns 0 on success +static int _ftl_error_to_obs_error(int status) +{ + /* Map FTL errors to OBS errors */ + + switch (status) { + case FTL_SUCCESS: + return OBS_OUTPUT_SUCCESS; + case FTL_SOCKET_NOT_CONNECTED: + case FTL_MALLOC_FAILURE: + case FTL_INTERNAL_ERROR: + case FTL_CONFIG_ERROR: + case FTL_NOT_ACTIVE_STREAM: + case FTL_NOT_CONNECTED: + case FTL_ALREADY_CONNECTED: + case FTL_STATUS_TIMEOUT: + case FTL_QUEUE_FULL: + case FTL_STATUS_WAITING_FOR_KEY_FRAME: + case FTL_QUEUE_EMPTY: + case FTL_NOT_INITIALIZED: + return OBS_OUTPUT_ERROR; + case FTL_BAD_REQUEST: + case FTL_DNS_FAILURE: + case FTL_CONNECT_ERROR: + case FTL_UNSUPPORTED_MEDIA_TYPE: + case FTL_OLD_VERSION: + case FTL_UNAUTHORIZED: + case FTL_AUDIO_SSRC_COLLISION: + case FTL_VIDEO_SSRC_COLLISION: + case FTL_STREAM_REJECTED: + case FTL_BAD_OR_INVALID_STREAM_KEY: + case FTL_CHANNEL_IN_USE: + case FTL_REGION_UNSUPPORTED: + case FTL_GAME_BLOCKED: + return OBS_OUTPUT_CONNECT_FAILED; + case FTL_NO_MEDIA_TIMEOUT: + return OBS_OUTPUT_DISCONNECTED; + case FTL_USER_DISCONNECT: + return OBS_OUTPUT_SUCCESS; + case FTL_UNKNOWN_ERROR_CODE: + default: + /* Unknown FTL error */ + return OBS_OUTPUT_ERROR; + } +} + +struct obs_output_info ftl_output_info = { + .id = "ftl_output", + .flags = OBS_OUTPUT_AV | + OBS_OUTPUT_ENCODED | + OBS_OUTPUT_SERVICE, + .encoded_video_codecs = "h264", + .encoded_audio_codecs = "opus", + .get_name = ftl_stream_getname, + .create = ftl_stream_create, + .destroy = ftl_stream_destroy, + .start = ftl_stream_start, + .stop = ftl_stream_stop, + .encoded_packet = ftl_stream_data, + .get_defaults = ftl_stream_defaults, + .get_properties = ftl_stream_properties, + .get_total_bytes = ftl_stream_total_bytes_sent, + .get_congestion = ftl_stream_congestion, + .get_dropped_frames = ftl_stream_dropped_frames +}; diff --git a/plugins/obs-outputs/net-if.c b/plugins/obs-outputs/net-if.c index 28dc087..f467f2a 100644 --- a/plugins/obs-outputs/net-if.c +++ b/plugins/obs-outputs/net-if.c @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +#define _WINSOCK_DEPRECATED_NO_WARNINGS + #include "net-if.h" #include #include diff --git a/plugins/obs-outputs/null-output.c b/plugins/obs-outputs/null-output.c index 6ea9e48..c63a6c4 100644 --- a/plugins/obs-outputs/null-output.c +++ b/plugins/obs-outputs/null-output.c @@ -82,7 +82,7 @@ static void null_output_stop(void *data, uint64_t ts) static void null_output_data(void *data, struct encoder_packet *packet) { - struct null_output *context = data; + UNUSED_PARAMETER(data); UNUSED_PARAMETER(packet); } diff --git a/plugins/obs-outputs/obs-outputs-config.h.in b/plugins/obs-outputs/obs-outputs-config.h.in new file mode 100644 index 0000000..60d7468 --- /dev/null +++ b/plugins/obs-outputs/obs-outputs-config.h.in @@ -0,0 +1,15 @@ +#pragma once + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef ON +#define ON 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#define COMPILE_FTL @COMPILE_FTL@ diff --git a/plugins/obs-outputs/obs-outputs.c b/plugins/obs-outputs/obs-outputs.c index d452330..43e0e89 100644 --- a/plugins/obs-outputs/obs-outputs.c +++ b/plugins/obs-outputs/obs-outputs.c @@ -1,5 +1,7 @@ #include +#include "obs-outputs-config.h" + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -11,6 +13,9 @@ OBS_MODULE_USE_DEFAULT_LOCALE("obs-outputs", "en-US") extern struct obs_output_info rtmp_output_info; extern struct obs_output_info null_output_info; extern struct obs_output_info flv_output_info; +#if COMPILE_FTL +extern struct obs_output_info ftl_output_info; +#endif bool obs_module_load(void) { @@ -22,6 +27,9 @@ bool obs_module_load(void) obs_register_output(&rtmp_output_info); obs_register_output(&null_output_info); obs_register_output(&flv_output_info); +#if COMPILE_FTL + obs_register_output(&ftl_output_info); +#endif return true; } diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index 3026b6a..d33969e 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -178,15 +178,17 @@ static void rtmp_stream_stop(void *data, uint64_t ts) pthread_join(stream->connect_thread, NULL); 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)) { + os_event_signal(stream->stop_event); if (stream->stop_ts == 0) os_sem_post(stream->send_sem); + } else { + obs_output_signal_stop(stream->output, OBS_OUTPUT_SUCCESS); } } @@ -293,6 +295,8 @@ static void droptest_cap_data_rate(struct rtmp_stream *stream, size_t size) static int socket_queue_data(RTMPSockBuf *sb, const char *data, int len, void *arg) { + UNUSED_PARAMETER(sb); + struct rtmp_stream *stream = arg; retry_send: @@ -345,7 +349,8 @@ static int send_packet(struct rtmp_stream *stream, } } - flv_packet_mux(packet, &data, &size, is_header); + flv_packet_mux(packet, is_header ? 0 : stream->start_dts_offset, + &data, &size, is_header); #ifdef TEST_FRAMEDROPS droptest_cap_data_rate(stream, size); @@ -656,6 +661,14 @@ static int init_send(struct rtmp_stream *stream) obs_data_t *params = obs_encoder_get_settings(vencoder); if (params) { int bitrate = obs_data_get_int(params, "bitrate"); + if (!bitrate) { + warn ("Video encoder didn't return a " + "valid bitrate, new network " + "code may function poorly. " + "Low latency mode disabled."); + stream->low_latency_mode = false; + bitrate = 10000; + } total_bitrate += bitrate; obs_data_release(params); } @@ -666,6 +679,8 @@ static int init_send(struct rtmp_stream *stream) obs_data_t *params = obs_encoder_get_settings(aencoder); if (params) { int bitrate = obs_data_get_int(params, "bitrate"); + if (!bitrate) + bitrate = 160; total_bitrate += bitrate; obs_data_release(params); } @@ -864,6 +879,7 @@ static bool init_connect(struct rtmp_stream *stream) stream->total_bytes_sent = 0; stream->dropped_frames = 0; stream->min_priority = 0; + stream->got_first_video = false; settings = obs_output_get_settings(stream->output); dstr_copy(&stream->path, obs_service_get_url(service)); @@ -951,8 +967,9 @@ static inline size_t num_buffered_packets(struct rtmp_stream *stream) static void drop_frames(struct rtmp_stream *stream, const char *name, int highest_priority, bool pframes) { + UNUSED_PARAMETER(pframes); + struct circlebuf new_buf = {0}; - uint64_t last_drop_dts_usec = 0; int num_frames_dropped = 0; #ifdef _DEBUG @@ -967,8 +984,6 @@ static void drop_frames(struct rtmp_stream *stream, const char *name, struct encoder_packet packet; circlebuf_pop_front(&stream->packets, &packet, sizeof(packet)); - last_drop_dts_usec = packet.dts_usec; - /* do not drop audio data or video keyframes */ if (packet.type == OBS_ENCODER_AUDIO || packet.drop_priority >= highest_priority) { @@ -1078,10 +1093,17 @@ static void rtmp_stream_data(void *data, struct encoder_packet *packet) if (disconnected(stream) || !active(stream)) return; - if (packet->type == OBS_ENCODER_VIDEO) + if (packet->type == OBS_ENCODER_VIDEO) { + if (!stream->got_first_video) { + stream->start_dts_offset = + get_ms_time(packet, packet->dts); + stream->got_first_video = true; + } + obs_parse_avc_packet(&new_packet, packet); - else + } else { obs_encoder_packet_ref(&new_packet, packet); + } pthread_mutex_lock(&stream->packets_mutex); @@ -1172,21 +1194,23 @@ static int rtmp_stream_connect_time(void *data) } struct obs_output_info rtmp_output_info = { - .id = "rtmp_output", - .flags = OBS_OUTPUT_AV | - OBS_OUTPUT_ENCODED | - OBS_OUTPUT_SERVICE | - OBS_OUTPUT_MULTI_TRACK, - .get_name = rtmp_stream_getname, - .create = rtmp_stream_create, - .destroy = rtmp_stream_destroy, - .start = rtmp_stream_start, - .stop = rtmp_stream_stop, - .encoded_packet = rtmp_stream_data, - .get_defaults = rtmp_stream_defaults, - .get_properties = rtmp_stream_properties, - .get_total_bytes = rtmp_stream_total_bytes_sent, - .get_congestion = rtmp_stream_congestion, - .get_connect_time_ms= rtmp_stream_connect_time, - .get_dropped_frames = rtmp_stream_dropped_frames + .id = "rtmp_output", + .flags = OBS_OUTPUT_AV | + OBS_OUTPUT_ENCODED | + OBS_OUTPUT_SERVICE | + OBS_OUTPUT_MULTI_TRACK, + .encoded_video_codecs = "h264", + .encoded_audio_codecs = "aac", + .get_name = rtmp_stream_getname, + .create = rtmp_stream_create, + .destroy = rtmp_stream_destroy, + .start = rtmp_stream_start, + .stop = rtmp_stream_stop, + .encoded_packet = rtmp_stream_data, + .get_defaults = rtmp_stream_defaults, + .get_properties = rtmp_stream_properties, + .get_total_bytes = rtmp_stream_total_bytes_sent, + .get_congestion = rtmp_stream_congestion, + .get_connect_time_ms = rtmp_stream_connect_time, + .get_dropped_frames = rtmp_stream_dropped_frames }; diff --git a/plugins/obs-outputs/rtmp-stream.h b/plugins/obs-outputs/rtmp-stream.h index 863eda6..1c4715a 100644 --- a/plugins/obs-outputs/rtmp-stream.h +++ b/plugins/obs-outputs/rtmp-stream.h @@ -51,6 +51,9 @@ struct rtmp_stream { struct circlebuf packets; bool sent_headers; + bool got_first_video; + int64_t start_dts_offset; + volatile bool connecting; pthread_t connect_thread; diff --git a/plugins/obs-text/data/locale/el-GR.ini b/plugins/obs-text/data/locale/el-GR.ini index b57170a..0dd9856 100644 --- a/plugins/obs-text/data/locale/el-GR.ini +++ b/plugins/obs-text/data/locale/el-GR.ini @@ -1,6 +1,19 @@ +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="Περίγραμμα" diff --git a/plugins/obs-text/data/locale/ko-KR.ini b/plugins/obs-text/data/locale/ko-KR.ini index ecc0e5f..1d2a0dd 100644 --- a/plugins/obs-text/data/locale/ko-KR.ini +++ b/plugins/obs-text/data/locale/ko-KR.ini @@ -25,7 +25,7 @@ Outline="외곽선" Outline.Size="외곽선 크기" Outline.Color="외곽선 색" Outline.Opacity="외곽선 투명도" -ChatlogMode="채팅기록 모드" +ChatlogMode="대화 기록 기능" ChatlogMode.Lines="채팅기록 줄 제한" UseCustomExtents="사용자 정의 텍스트 설정" UseCustomExtents.Wrap="자동 줄 바꿈" diff --git a/plugins/obs-text/data/locale/nb-NO.ini b/plugins/obs-text/data/locale/nb-NO.ini index 2366aa0..97eed0a 100644 --- a/plugins/obs-text/data/locale/nb-NO.ini +++ b/plugins/obs-text/data/locale/nb-NO.ini @@ -7,6 +7,10 @@ Filter.TextFiles="Tekstfiler" Filter.AllFiles="Alle filer" Color="Farge" Opacity="Gjennomsiktighet" +Gradient="Gradient" +Gradient.Color="Gradientfarge" +Gradient.Opacity="Gradientfasthet" +Gradient.Direction="Gradientens retning" BkColor="Bakgrunnsfarge" BkOpacity="Gjennomsiktighet bakgrunn" Alignment="Justering" @@ -17,7 +21,12 @@ Vertical="Vertikal" VerticalAlignment="Loddrett justering" VerticalAlignment.Top="Topp" VerticalAlignment.Bottom="Bunn" +Outline="Omriss" +Outline.Size="Omrissets størrelse" +Outline.Color="Omrissets farge" +Outline.Opacity="Omrissets fasthet" ChatlogMode="Chatlog modus" +ChatlogMode.Lines="Chathistorikkens linjegrense" UseCustomExtents="Bruk egendefinerte tekst-utvidelser" UseCustomExtents.Wrap="Ordbrytning" Width="Bredde" diff --git a/plugins/obs-text/data/locale/pt-PT.ini b/plugins/obs-text/data/locale/pt-PT.ini new file mode 100644 index 0000000..d2338c7 --- /dev/null +++ b/plugins/obs-text/data/locale/pt-PT.ini @@ -0,0 +1,17 @@ +Font="Fonte" +Text="Texto" +Filter.AllFiles="Todos os Ficheiros" +Color="Cor" +Opacity="Opacidade" +Gradient="Gradiente" +Gradient.Color="Cor do Gradiente" +Gradient.Opacity="Opacidade do Gradiente" +Gradient.Direction="Direção do Gradiente" +Alignment.Left="Esquerda" +Alignment.Center="Centro" +Alignment.Right="Direita" +Vertical="Vertical" +VerticalAlignment.Top="Topo" +Width="Largura" +Height="Altura" + diff --git a/plugins/obs-text/data/locale/sk-SK.ini b/plugins/obs-text/data/locale/sk-SK.ini index 4a9377a..9704ae3 100644 --- a/plugins/obs-text/data/locale/sk-SK.ini +++ b/plugins/obs-text/data/locale/sk-SK.ini @@ -1,3 +1,34 @@ +TextGDIPlus="Text (GDI+)" +Font="Písmo" +Text="Text" +ReadFromFile="Čítať zo súboru" +TextFile="Textový súbor (UTF-8)" +Filter.TextFiles="Textové súbory" +Filter.AllFiles="Všetky súbory" Color="Farba" -Opacity="Priehľadnosť" +Opacity="Nepriehľadnosť" +Gradient="Farebný Prechod" +Gradient.Color="Farba prechodu" +Gradient.Opacity="Nepriehľadnosť prechodu" +Gradient.Direction="Smer prechodu" +BkColor="Farba pozadia" +BkOpacity="Nepriehľadnosť pozadia" +Alignment="Zarovnanie" +Alignment.Left="Vľavo" +Alignment.Center="Na stred" +Alignment.Right="Vpravo" +Vertical="Vertikálne" +VerticalAlignment="Zvislé zarovnanie" +VerticalAlignment.Top="Hore" +VerticalAlignment.Bottom="Dole" +Outline="Obrys" +Outline.Size="Veľkosť obrysu" +Outline.Color="Farba obrysu" +Outline.Opacity="Nepriehľadnosť obrysu" +ChatlogMode="Chatlog režim" +ChatlogMode.Lines="Chatlog limit riadkov" +UseCustomExtents="Použiť vlastné rozsahy textu" +UseCustomExtents.Wrap="Zalomiť" +Width="Šírka" +Height="Výška" diff --git a/plugins/obs-text/data/locale/vi-VN.ini b/plugins/obs-text/data/locale/vi-VN.ini new file mode 100644 index 0000000..a4d2b45 --- /dev/null +++ b/plugins/obs-text/data/locale/vi-VN.ini @@ -0,0 +1,10 @@ +Font="Phông chữ" +Text="Văn bản" +TextFile="Tập tin văn bản (UTF-8)" +Filter.TextFiles="Tập tin văn bản" +Filter.AllFiles="Tất cả tập tin" +Color="Màu" +Opacity="Độ trong suốt" +VerticalAlignment.Top="Trên" +VerticalAlignment.Bottom="Dưới" + diff --git a/plugins/obs-text/gdiplus/obs-text.cpp b/plugins/obs-text/gdiplus/obs-text.cpp index 8a040be..2efc108 100644 --- a/plugins/obs-text/gdiplus/obs-text.cpp +++ b/plugins/obs-text/gdiplus/obs-text.cpp @@ -197,6 +197,7 @@ struct TextSource { bool read_from_file = false; string file; time_t file_timestamp = 0; + bool update_file = false; float update_time_elapsed = 0.0f; wstring text; @@ -775,10 +776,15 @@ inline void TextSource::Tick(float seconds) time_t t = get_modified_timestamp(file.c_str()); update_time_elapsed = 0.0f; - if (file_timestamp != t) { + if (update_file) { LoadFileText(); RenderText(); + update_file = false; + } + + if (file_timestamp != t) { file_timestamp = t; + update_file = true; } } } diff --git a/plugins/obs-transitions/CMakeLists.txt b/plugins/obs-transitions/CMakeLists.txt index 3b8838d..16e471b 100644 --- a/plugins/obs-transitions/CMakeLists.txt +++ b/plugins/obs-transitions/CMakeLists.txt @@ -8,6 +8,7 @@ set(obs-transitions_SOURCES transition-cut.c transition-fade-to-color.c transition-luma-wipe.c + transition-stinger.c ) add_library(obs-transitions MODULE diff --git a/plugins/obs-transitions/data/locale/ca-ES.ini b/plugins/obs-transitions/data/locale/ca-ES.ini index 142aeee..e5b943d 100644 --- a/plugins/obs-transitions/data/locale/ca-ES.ini +++ b/plugins/obs-transitions/data/locale/ca-ES.ini @@ -2,6 +2,7 @@ FadeTransition="Esvair" CutTransition="Talla" SwipeTransition="Lliscar" SlideTransition="Lliscar" +StingerTransition="Stinger" FadeToColorTransition="Esvair a Color" Direction="Direcció" Direction.Left="Esquerra" @@ -10,6 +11,15 @@ Direction.Up="Amunt" Direction.Down="Avall" SwipeIn="Lliscament" Color="Color" +VideoFile="Arxiu de vídeo" +TransitionPoint="Punt de la transició (mil·lisegons)" +TransitionPointFrame="Punt de transició (fotograma)" +TransitionPointType="Tipus de punt de transició" +TransitionPointTypeFrame="Fotograma" +TransitionPointTypeTime="Temps (en mil·lisegons)" +AudioFadeStyle="Estil de fos d'àudio" +AudioFadeStyle.FadeOutFadeIn="Es descolora al punt de transició i després s'esvaeix" +AudioFadeStyle.CrossFade="Transició" SwitchPoint="Punt de Color màxim (percentatge)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Imatge" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Ratlles verticals" LumaWipe.Type.Watercolor="Aquarel·la" LumaWipe.Type.ZigzagHorizontal="Ziga-zaga horitzontal" LumaWipe.Type.ZigzagVertical="Ziga-zaga vertical" +AudioMonitoring="Monitorització d'àudio" +AudioMonitoring.None="Monitorització desactivada" +AudioMonitoring.MonitorOnly="Només monitorització (sortida silenciosa)" +AudioMonitoring.Both="Monitorització i sortida" diff --git a/plugins/obs-transitions/data/locale/cs-CZ.ini b/plugins/obs-transitions/data/locale/cs-CZ.ini index 03893e8..ce20ef5 100644 --- a/plugins/obs-transitions/data/locale/cs-CZ.ini +++ b/plugins/obs-transitions/data/locale/cs-CZ.ini @@ -2,6 +2,7 @@ FadeTransition="Slábnutí" CutTransition="Střih" SwipeTransition="Tažení" SlideTransition="Sklouznutí" +StingerTransition="Stinger" FadeToColorTransition="Barevný přechod" Direction="Směr" Direction.Left="Vlevo" @@ -10,6 +11,15 @@ Direction.Up="Nahoru" Direction.Down="Dolů" SwipeIn="Vtáhnout" Color="Barva" +VideoFile="Video soubor" +TransitionPoint="Bod přechodu (ms)" +TransitionPointFrame="Bod přechodu (snímky)" +TransitionPointType="Typ bodu přechodu" +TransitionPointTypeFrame="Snímek" +TransitionPointTypeTime="Čas (ms)" +AudioFadeStyle="Styl přechodů" +AudioFadeStyle.FadeOutFadeIn="Zeslabovat do bodu přechodu, poté zesilovat" +AudioFadeStyle.CrossFade="Prolínání" SwitchPoint="Špičkový bod barvy (%)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Obrázek" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Strips Vertical" LumaWipe.Type.Watercolor="Watercolor" LumaWipe.Type.ZigzagHorizontal="Zigzag Horizontal" LumaWipe.Type.ZigzagVertical="Zigzag Vertical" +AudioMonitoring="Sledování zvuku" +AudioMonitoring.None="Sledování vypnuto" +AudioMonitoring.MonitorOnly="Pouze sledovat (ztlumit výstup)" +AudioMonitoring.Both="Sledovat i odesílat na výstup" diff --git a/plugins/obs-transitions/data/locale/da-DK.ini b/plugins/obs-transitions/data/locale/da-DK.ini index 025f383..52e60c6 100644 --- a/plugins/obs-transitions/data/locale/da-DK.ini +++ b/plugins/obs-transitions/data/locale/da-DK.ini @@ -2,6 +2,7 @@ FadeTransition="Overgang" CutTransition="Klip" SwipeTransition="Swipe" SlideTransition="Glide" +StingerTransition="Stinger-overgang" FadeToColorTransition="Fade til farve" Direction="Retning" Direction.Left="Venstre" @@ -10,6 +11,15 @@ Direction.Up="Op" Direction.Down="Ned" SwipeIn="Swipe ind" Color="Farve" +VideoFile="Videofil" +TransitionPoint="Overgangspunkt (millisekunder)" +TransitionPointFrame="Overgangspunkt (ramme)" +TransitionPointType="Overgangspunkttype" +TransitionPointTypeFrame="Billede" +TransitionPointTypeTime="Tid (ms)" +AudioFadeStyle="Lydudtoningsstil" +AudioFadeStyle.FadeOutFadeIn="Udtone til overgangspunkt, dernæst indtone" +AudioFadeStyle.CrossFade="Krydstoning (crossfade)" SwitchPoint="Farvepeakpunkt (procent)" LumaWipeTransition="Luma overgang" LumaWipe.Image="Billede" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Strimmel vertikal" LumaWipe.Type.Watercolor="Vandfarve" LumaWipe.Type.ZigzagHorizontal="Zigzag horisontal" LumaWipe.Type.ZigzagVertical="Zigzag vertical" +AudioMonitoring="Lydmonitering" +AudioMonitoring.None="Monitering Fra" +AudioMonitoring.MonitorOnly="Kun monitering (forstum output)" +AudioMonitoring.Both="Monitorering og output" diff --git a/plugins/obs-transitions/data/locale/de-DE.ini b/plugins/obs-transitions/data/locale/de-DE.ini index 757acb8..8913752 100644 --- a/plugins/obs-transitions/data/locale/de-DE.ini +++ b/plugins/obs-transitions/data/locale/de-DE.ini @@ -2,6 +2,7 @@ FadeTransition="Überblenden" CutTransition="Schnitt" SwipeTransition="Swipe" SlideTransition="Slide" +StingerTransition="Stinger" FadeToColorTransition="Fade to Color" Direction="Richtung" Direction.Left="Links" @@ -10,6 +11,15 @@ Direction.Up="Hoch" Direction.Down="Runter" SwipeIn="Swipe In" Color="Farbe" +VideoFile="Videodatei" +TransitionPoint="Übergangspunkt (Millisekunden)" +TransitionPointFrame="Übergangspunkt (Frame)" +TransitionPointType="Übergangspunkttyp" +TransitionPointTypeFrame="Frame" +TransitionPointTypeTime="Zeit (Millisekunden)" +AudioFadeStyle="Audio Überblendstil" +AudioFadeStyle.FadeOutFadeIn="Zu Übergangspunkt ausblenden und dann einblenden" +AudioFadeStyle.CrossFade="Überblendung" SwitchPoint="Peakfarbpunkt (Prozent)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Bild" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Vertikale Streifen" LumaWipe.Type.Watercolor="Aquarell" LumaWipe.Type.ZigzagHorizontal="Zigzag horizontal" LumaWipe.Type.ZigzagVertical="Zigzag vertikal" +AudioMonitoring="Audiomonitoring" +AudioMonitoring.None="Monitor aus" +AudioMonitoring.MonitorOnly="Nur Monitor (Ausgabe stumm schalten)" +AudioMonitoring.Both="Monitor und Ausgabe" diff --git a/plugins/obs-transitions/data/locale/el-GR.ini b/plugins/obs-transitions/data/locale/el-GR.ini index f8b75f3..34d22ba 100644 --- a/plugins/obs-transitions/data/locale/el-GR.ini +++ b/plugins/obs-transitions/data/locale/el-GR.ini @@ -3,4 +3,7 @@ CutTransition="Αποκοπή" Direction.Left="Αριστερά" Direction.Right="Δεξιά" Color="Χρώμα" +VideoFile="Αρχείο Βίντεο" +TransitionPointTypeFrame="Καρέ" +TransitionPointTypeTime="Χρόνος (χιλιοστά δευτερολέπτου)" diff --git a/plugins/obs-transitions/data/locale/en-US.ini b/plugins/obs-transitions/data/locale/en-US.ini index 3bf1684..411b49b 100644 --- a/plugins/obs-transitions/data/locale/en-US.ini +++ b/plugins/obs-transitions/data/locale/en-US.ini @@ -2,6 +2,7 @@ FadeTransition="Fade" CutTransition="Cut" SwipeTransition="Swipe" SlideTransition="Slide" +StingerTransition="Stinger" FadeToColorTransition="Fade to Color" Direction="Direction" Direction.Left="Left" @@ -10,6 +11,15 @@ Direction.Up="Up" Direction.Down="Down" SwipeIn="Swipe In" Color="Color" +VideoFile="Video File" +TransitionPoint="Transition Point (milliseconds)" +TransitionPointFrame="Transition Point (frame)" +TransitionPointType="Transition Point Type" +TransitionPointTypeFrame="Frame" +TransitionPointTypeTime="Time (milliseconds)" +AudioFadeStyle="Audio Fade Style" +AudioFadeStyle.FadeOutFadeIn="Fade out to transition point then fade in" +AudioFadeStyle.CrossFade="Crossfade" SwitchPoint="Peak Color Point (percentage)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Image" @@ -49,3 +59,7 @@ LumaWipe.Type.StripsVertical="Strips Vertical" LumaWipe.Type.Watercolor="Watercolor" LumaWipe.Type.ZigzagHorizontal="Zigzag Horizontal" LumaWipe.Type.ZigzagVertical="Zigzag Vertical" +AudioMonitoring="Audio Monitoring" +AudioMonitoring.None="Monitor Off" +AudioMonitoring.MonitorOnly="Monitor Only (mute output)" +AudioMonitoring.Both="Monitor and Output" diff --git a/plugins/obs-transitions/data/locale/es-ES.ini b/plugins/obs-transitions/data/locale/es-ES.ini index 23e3a02..4adf1a5 100644 --- a/plugins/obs-transitions/data/locale/es-ES.ini +++ b/plugins/obs-transitions/data/locale/es-ES.ini @@ -2,6 +2,7 @@ FadeTransition="Desvanecimiento" CutTransition="Corte" SwipeTransition="Deslizar" SlideTransition="Deslizar" +StingerTransition="Stinger" FadeToColorTransition="Desvanecer a Color" Direction="Dirección" Direction.Left="Izquierda" @@ -10,6 +11,15 @@ Direction.Up="Arriba" Direction.Down="Abajo" SwipeIn="Deslizamiento" Color="Color" +VideoFile="Archivo de vídeo" +TransitionPoint="Punto de transición (milisegundos)" +TransitionPointFrame="Punto de transición (fotograma)" +TransitionPointType="Tipo de punto de transición" +TransitionPointTypeFrame="Fotograma" +TransitionPointTypeTime="Tiempo (en milisegundos)" +AudioFadeStyle="Estilo de fundido de audio" +AudioFadeStyle.FadeOutFadeIn="Se descolora al punto de transición y luego se desvanece" +AudioFadeStyle.CrossFade="Transición suave" SwitchPoint="Punto de Color máximo (porcentaje)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Imagen" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Rayas verticales" LumaWipe.Type.Watercolor="Acuarela" LumaWipe.Type.ZigzagHorizontal="Zigzag horizontal" LumaWipe.Type.ZigzagVertical="Zigzag vertical" +AudioMonitoring="Monitoreo de audio" +AudioMonitoring.None="Monitoreo apagado" +AudioMonitoring.MonitorOnly="Solo monitoreo (salida silenciosa)" +AudioMonitoring.Both="Monitoreo y salida" diff --git a/plugins/obs-transitions/data/locale/eu-ES.ini b/plugins/obs-transitions/data/locale/eu-ES.ini index 1dafd8e..688e65b 100644 --- a/plugins/obs-transitions/data/locale/eu-ES.ini +++ b/plugins/obs-transitions/data/locale/eu-ES.ini @@ -2,6 +2,7 @@ FadeTransition="Iraungi" CutTransition="Etena" SwipeTransition="Korritu" SlideTransition="Irristatu" +StingerTransition="Eztena" FadeToColorTransition="Iraungi kolorera" Direction="Norabidea" Direction.Left="Ezker" @@ -10,6 +11,15 @@ Direction.Up="Gora" Direction.Down="Behera" SwipeIn="Korritu bertan" Color="Kolorea" +VideoFile="Bideo-fitxategia" +TransitionPoint="Trantsizio-puntua (milisegundo)" +TransitionPointFrame="Trantsizio-puntua (fotograma)" +TransitionPointType="Trantsizio-puntu mota" +TransitionPointTypeFrame="Fotograma" +TransitionPointTypeTime="Denbora (milisegundo)" +AudioFadeStyle="Audio desagertze estiloa" +AudioFadeStyle.FadeOutFadeIn="Desagertu trantsizio puntura eta orduan agertu" +AudioFadeStyle.CrossFade="Kateatua" SwitchPoint="Kolorearen gailur puntua (ehunekoa)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Irudia" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Banda bertikalak" LumaWipe.Type.Watercolor="Akuarela" LumaWipe.Type.ZigzagHorizontal="Sigi-saga horizontala" LumaWipe.Type.ZigzagVertical="Sigi-saga bertikala" +AudioMonitoring="Adioaren monitorizazioa" +AudioMonitoring.None="Monitorea itzalita" +AudioMonitoring.MonitorOnly="Monitorea bakarrik (isildu irteera)" +AudioMonitoring.Both="Monitorea eta irteera" diff --git a/plugins/obs-transitions/data/locale/fi-FI.ini b/plugins/obs-transitions/data/locale/fi-FI.ini index 4a380a0..f155234 100644 --- a/plugins/obs-transitions/data/locale/fi-FI.ini +++ b/plugins/obs-transitions/data/locale/fi-FI.ini @@ -2,6 +2,7 @@ FadeTransition="Häivytä" CutTransition="Leikkaa" SwipeTransition="Pyyhkäise" SlideTransition="Liu'uta" +StingerTransition="Stinger" FadeToColorTransition="Häivytä väriin" Direction="Suunta" Direction.Left="Vasemmalta" @@ -10,6 +11,15 @@ Direction.Up="Ylhäältä" Direction.Down="Alhaalta" SwipeIn="Pyyhkäise yli" Color="Väri" +VideoFile="Videotiedosto" +TransitionPoint="Siirtymäkohta (millisekuntia)" +TransitionPointFrame="Siirtymäpisteen tyyppi (frame)" +TransitionPointType="Siirtymäpisteen tyyppi" +TransitionPointTypeFrame="Kehys" +TransitionPointTypeTime="Aika (millisekuntia)" +AudioFadeStyle="Äänen häivytyksen tyyli" +AudioFadeStyle.FadeOutFadeIn="Häivytä ulos siirtymäpisteeseen asti ja sitten häivytä sisään" +AudioFadeStyle.CrossFade="Ristiinhäivytys" SwitchPoint="Korkein väripiste (prosentti)" LumaWipeTransition="Luma-pyyhkäisy" LumaWipe.Image="Kuva" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Strips pystytasossa" LumaWipe.Type.Watercolor="Watercolor" LumaWipe.Type.ZigzagHorizontal="Zigzag vaakatasossa" LumaWipe.Type.ZigzagVertical="Zigzag pystysuorassa" +AudioMonitoring="Äänen monitorointi" +AudioMonitoring.None="Monitorointi pois" +AudioMonitoring.MonitorOnly="Vain monitorointi (hiljennä ulostulo)" +AudioMonitoring.Both="Monitorointi ja ulostulo" diff --git a/plugins/obs-transitions/data/locale/fr-FR.ini b/plugins/obs-transitions/data/locale/fr-FR.ini index 8ad0f6c..e4ed43c 100644 --- a/plugins/obs-transitions/data/locale/fr-FR.ini +++ b/plugins/obs-transitions/data/locale/fr-FR.ini @@ -2,6 +2,7 @@ FadeTransition="Fondu" CutTransition="Coupure" SwipeTransition="Balayage" SlideTransition="Glissement" +StingerTransition="Stinger" FadeToColorTransition="Fondu avec couleur" Direction="Direction" Direction.Left="Gauche" @@ -10,6 +11,15 @@ Direction.Up="Haut" Direction.Down="Bas" SwipeIn="Recouvrement" Color="Couleur" +VideoFile="Fichier vidéo" +TransitionPoint="Point de transition (millisecondes)" +TransitionPointFrame="Point de transition (image)" +TransitionPointType="Type de transition" +TransitionPointTypeFrame="Image" +TransitionPointTypeTime="Durée (en millisecondes)" +AudioFadeStyle="Style de fondu audio" +AudioFadeStyle.FadeOutFadeIn="Fondu en fermeture jusqu'au point de transition puis fondu en ouverture" +AudioFadeStyle.CrossFade="Fondu enchaîné" SwitchPoint="Point de couleur maximal (pourcentage)" LumaWipeTransition="Luma" LumaWipe.Image="Image" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Bandes verticales" LumaWipe.Type.Watercolor="Aquarelle" LumaWipe.Type.ZigzagHorizontal="Zigzags à l'horizontale" LumaWipe.Type.ZigzagVertical="Zigzags à la verticale" +AudioMonitoring="Surveillance audio" +AudioMonitoring.None="Pas de surveillance" +AudioMonitoring.MonitorOnly="Surveillance seule (sortie en sourdine)" +AudioMonitoring.Both="Surveillance et sortie" diff --git a/plugins/obs-transitions/data/locale/hu-HU.ini b/plugins/obs-transitions/data/locale/hu-HU.ini index 698986f..cd72678 100644 --- a/plugins/obs-transitions/data/locale/hu-HU.ini +++ b/plugins/obs-transitions/data/locale/hu-HU.ini @@ -2,6 +2,7 @@ FadeTransition="Áttűnés" CutTransition="Kivágás" SwipeTransition="Lapozás" SlideTransition="Csúsztatás" +StingerTransition="Stinger" FadeToColorTransition="Színes áttűnés" Direction="Irány" Direction.Left="Bal" @@ -10,6 +11,15 @@ Direction.Up="Fel" Direction.Down="Le" SwipeIn="Belapozás" Color="Szín" +VideoFile="Videofájl" +TransitionPoint="Átmenetpont (ezredmásodperc)" +TransitionPointFrame="Átmenetpont (Képkocka)" +TransitionPointType="Átmenetpont típus" +TransitionPointTypeFrame="Képkocka" +TransitionPointTypeTime="Idő (Ezredmásodperc)" +AudioFadeStyle="Hangáttűnés stílusa" +AudioFadeStyle.FadeOutFadeIn="Átmenetponthoz halkítás és visszahangosítás" +AudioFadeStyle.CrossFade="Átkeverés" SwitchPoint="Színpont csúcs (százalék)" LumaWipeTransition="Luma törlés" LumaWipe.Image="Kép" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Függőleges csíkok" LumaWipe.Type.Watercolor="Akvarell" LumaWipe.Type.ZigzagHorizontal="Vízszintes cikkcakk" LumaWipe.Type.ZigzagVertical="Függőleges cikkcakk" +AudioMonitoring="Hangfigyelés" +AudioMonitoring.None="Figyelés kikapcsolása" +AudioMonitoring.MonitorOnly="Csak figyelés (kimenet némítása)" +AudioMonitoring.Both="Figyelés és kimenet" diff --git a/plugins/obs-transitions/data/locale/it-IT.ini b/plugins/obs-transitions/data/locale/it-IT.ini index 6cca678..dbf3438 100644 --- a/plugins/obs-transitions/data/locale/it-IT.ini +++ b/plugins/obs-transitions/data/locale/it-IT.ini @@ -2,6 +2,7 @@ FadeTransition="Dissolvenza" CutTransition="Taglio" SwipeTransition="Scorri" SlideTransition="Scivola" +StingerTransition="Stinger" FadeToColorTransition="Dissolvenza a colore" Direction="Direzione" Direction.Left="Sinistra" @@ -10,6 +11,15 @@ Direction.Up="Sù" Direction.Down="Giù" SwipeIn="Scorri verso l'alto" Color="Colore" +VideoFile="File video" +TransitionPoint="Punto di transizione (millisecondi)" +TransitionPointFrame="Punto di transizione (frame)" +TransitionPointType="Tipo di punto di transizione" +TransitionPointTypeFrame="Fotogramma" +TransitionPointTypeTime="Tempo (millisecondi)" +AudioFadeStyle="Stile dissolvenza audio" +AudioFadeStyle.FadeOutFadeIn="Dissolvenza fino al punto di transizione, poi dissolvenza in entrata" +AudioFadeStyle.CrossFade="Dissolvenza" SwitchPoint="Picco Punto Colore (percentuale)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Immagine" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Striscie verticali" LumaWipe.Type.Watercolor="Acquerello" LumaWipe.Type.ZigzagHorizontal="Zig-zag orizzontale" LumaWipe.Type.ZigzagVertical="Zig-zag verticale" +AudioMonitoring="Monitoraggio Audio" +AudioMonitoring.None="Monitoraggio off" +AudioMonitoring.MonitorOnly="Solo Monitoraggio (output mutato)" +AudioMonitoring.Both="Monitoraggio e Output" diff --git a/plugins/obs-transitions/data/locale/ja-JP.ini b/plugins/obs-transitions/data/locale/ja-JP.ini index 8b5388a..8c39964 100644 --- a/plugins/obs-transitions/data/locale/ja-JP.ini +++ b/plugins/obs-transitions/data/locale/ja-JP.ini @@ -2,6 +2,7 @@ FadeTransition="フェード" CutTransition="カット" SwipeTransition="スワイプ" SlideTransition="スライド" +StingerTransition="スティンガー" FadeToColorTransition="カラーにフェード" Direction="方向" Direction.Left="左" @@ -10,6 +11,15 @@ Direction.Up="上" Direction.Down="下" SwipeIn="スワイプイン" Color="色" +VideoFile="動画ファイル" +TransitionPoint="トランジションポイント (ミリ秒)" +TransitionPointFrame="トランジションポイント (フレーム)" +TransitionPointType="トランジションポイントの種類" +TransitionPointTypeFrame="フレーム" +TransitionPointTypeTime="時間 (ミリ秒)" +AudioFadeStyle="オーディオフェードスタイル" +AudioFadeStyle.FadeOutFadeIn="トランジションポイントまでフェードアウトしてからフェードイン" +AudioFadeStyle.CrossFade="クロスフェード" SwitchPoint="ピークカラーポイント (割合)" LumaWipeTransition="輝度ワイプ" LumaWipe.Image="画像" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="垂直ストライプ" LumaWipe.Type.Watercolor="水彩" LumaWipe.Type.ZigzagHorizontal="水平ジグザグ" LumaWipe.Type.ZigzagVertical="垂直ジグザグ" +AudioMonitoring="音声モニタリング" +AudioMonitoring.None="モニターオフ" +AudioMonitoring.MonitorOnly="モニターのみ (出力はミュート)" +AudioMonitoring.Both="モニターと出力" diff --git a/plugins/obs-transitions/data/locale/ko-KR.ini b/plugins/obs-transitions/data/locale/ko-KR.ini index 0f407dc..866aede 100644 --- a/plugins/obs-transitions/data/locale/ko-KR.ini +++ b/plugins/obs-transitions/data/locale/ko-KR.ini @@ -2,6 +2,7 @@ FadeTransition="서서히 사라지기" CutTransition="자르기" SwipeTransition="밀어내기" SlideTransition="슬라이드" +StingerTransition="스팅어" FadeToColorTransition="특정 색상으로 서서히 사라지기" Direction="방향" Direction.Left="왼쪽" @@ -10,6 +11,15 @@ Direction.Up="위쪽" Direction.Down="아래쪽" SwipeIn="덮기" Color="색상" +VideoFile="비디오 파일" +TransitionPoint="전환 지점 (밀리초)" +TransitionPointFrame="전환 지점 (프레임)" +TransitionPointType="전환 지점 형식" +TransitionPointTypeFrame="프레임" +TransitionPointTypeTime="시간 (밀리초)" +AudioFadeStyle="소리 점감 형식" +AudioFadeStyle.FadeOutFadeIn="전환 지점까지 서서히 작아졌다가 다시 커지기" +AudioFadeStyle.CrossFade="천천히 작아지기와 커지기 동시" SwitchPoint="최고조 색상 지점 (백분율)" LumaWipeTransition="루마 지우기" LumaWipe.Image="이미지 파일" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="줄무늬 (수직)" LumaWipe.Type.Watercolor="수채화" LumaWipe.Type.ZigzagHorizontal="지그재그 (수평)" LumaWipe.Type.ZigzagVertical="지그재그 (수직)" +AudioMonitoring="소리 감시" +AudioMonitoring.None="감시 끄기" +AudioMonitoring.MonitorOnly="감시만 하기 (출력은 음소거)" +AudioMonitoring.Both="감시와 출력 모두" diff --git a/plugins/obs-transitions/data/locale/nb-NO.ini b/plugins/obs-transitions/data/locale/nb-NO.ini index 75b0b73..bcf2280 100644 --- a/plugins/obs-transitions/data/locale/nb-NO.ini +++ b/plugins/obs-transitions/data/locale/nb-NO.ini @@ -2,6 +2,7 @@ FadeTransition="Forløpning" CutTransition="Kutt" SwipeTransition="Sveip" SlideTransition="Skyv" +StingerTransition="Stinger" FadeToColorTransition="Forløpning til farge" Direction="Retning" Direction.Left="Venstre" @@ -10,8 +11,56 @@ Direction.Up="Opp" Direction.Down="Ned" SwipeIn="Sveip inn" Color="Farge" +VideoFile="Videofil" +TransitionPoint="Overgangspunkt (millisekunder)" +TransitionPointFrame="Overgangspunkt (ramme)" +TransitionPointType="Overgangspunkttype" +TransitionPointTypeFrame="Ramme" +TransitionPointTypeTime="Tid (millisekunder)" +AudioFadeStyle="Lyduttoningsstil" +AudioFadeStyle.FadeOutFadeIn="Ton ut til overgangspunktet, og så ton inn igjen" +AudioFadeStyle.CrossFade="Kryssuttoning" SwitchPoint="Farge ved høydepunkt (prosent)" +LumaWipeTransition="Luminansfjerning" LumaWipe.Image="Bilde" LumaWipe.Invert="Inverter" LumaWipe.Softness="Mykhet" +LumaWipe.Type.BarndoorBottomLeft="Låvedør ned til venstre" +LumaWipe.Type.BarndoorHorizontal="Vannrett låvedør" +LumaWipe.Type.BarndoorTopLeft="Låvedør opp til venstre" +LumaWipe.Type.BarndoorVertical="Loddrett låvedør" +LumaWipe.Type.BlindsHorizontal="Vannrette persienner" +LumaWipe.Type.BoxBottomLeft="Boks ned til venstre" +LumaWipe.Type.BoxBottomRight="Boks ned til høyre" +LumaWipe.Type.BoxTopLeft="Boks opp til venstre" +LumaWipe.Type.BoxTopRight="Boks opp til høyre" +LumaWipe.Type.Burst="Sprekk" +LumaWipe.Type.CheckerboardSmall="Et lite dambrett" +LumaWipe.Type.Circles="Sirkler" +LumaWipe.Type.Clock="Klokke" +LumaWipe.Type.Cloud="Sky" +LumaWipe.Type.Curtain="Gardin" +LumaWipe.Type.Fan="Vifte" +LumaWipe.Type.Fractal="Fraktal" +LumaWipe.Type.Iris="Iris" +LumaWipe.Type.LinearHorizontal="Vannrett lineær" +LumaWipe.Type.LinearTopLeft="Lineær opp til venstre" +LumaWipe.Type.LinearTopRight="Lineær opp til høyre" +LumaWipe.Type.LinearVertical="Lineær loddrett" +LumaWipe.Type.ParallelZigzagHorizontal="Parallell vannrett sikksakk" +LumaWipe.Type.ParallelZigzagVertical="Parallell loddrett sikksakk" +LumaWipe.Type.Sinus9="Sinus 9" +LumaWipe.Type.Spiral="Spiral" +LumaWipe.Type.Square="Kvadrat" +LumaWipe.Type.Squares="Kvadrater" +LumaWipe.Type.Stripes="Striper" +LumaWipe.Type.StripsHorizontal="Vannrette strimler" +LumaWipe.Type.StripsVertical="Loddrette strimler" +LumaWipe.Type.Watercolor="Vannfarging" +LumaWipe.Type.ZigzagHorizontal="Vannrett sikksakk" +LumaWipe.Type.ZigzagVertical="Loddrett sikksakk" +AudioMonitoring="Lydovervåking" +AudioMonitoring.None="Av for skjermen" +AudioMonitoring.MonitorOnly="Kun skjermen (Demp utdataen)" +AudioMonitoring.Both="Både skjermen og utdataen" diff --git a/plugins/obs-transitions/data/locale/nl-NL.ini b/plugins/obs-transitions/data/locale/nl-NL.ini index 1f50a2f..f869fcf 100644 --- a/plugins/obs-transitions/data/locale/nl-NL.ini +++ b/plugins/obs-transitions/data/locale/nl-NL.ini @@ -2,6 +2,7 @@ FadeTransition="Vervagen" CutTransition="Knippen" SwipeTransition="Vegen" SlideTransition="Slide" +StingerTransition="Stinger" FadeToColorTransition="Vervagen naar Kleur" Direction="Richting" Direction.Left="Links" @@ -10,6 +11,15 @@ Direction.Up="Omhoog" Direction.Down="Omlaag" SwipeIn="Naar binnen vegen" Color="Kleur" +VideoFile="Videobestand" +TransitionPoint="Overgangspunt (milliseconden)" +TransitionPointFrame="Transitiepunt (frame)" +TransitionPointType="Transitiepunt-type" +TransitionPointTypeFrame="Frame" +TransitionPointTypeTime="Tijd (milliseconden)" +AudioFadeStyle="Audio Fade stijl" +AudioFadeStyle.FadeOutFadeIn="Overgang van punt dan fade-in uitfaden" +AudioFadeStyle.CrossFade="Crossfading" SwitchPoint="Wisselpunt (percentage)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Afbeelding" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Strips Verticaal" LumaWipe.Type.Watercolor="Waterverf" LumaWipe.Type.ZigzagHorizontal="Zigzag Horizontaal" LumaWipe.Type.ZigzagVertical="Zigzag Verticaal" +AudioMonitoring="Audio monitoring" +AudioMonitoring.None="Niet monitoren" +AudioMonitoring.MonitorOnly="Alleen monitoren (uitvoer gedempt)" +AudioMonitoring.Both="Monitoren en uitvoeren" diff --git a/plugins/obs-transitions/data/locale/pl-PL.ini b/plugins/obs-transitions/data/locale/pl-PL.ini index 4e55f4e..3154b2a 100644 --- a/plugins/obs-transitions/data/locale/pl-PL.ini +++ b/plugins/obs-transitions/data/locale/pl-PL.ini @@ -2,6 +2,7 @@ FadeTransition="Zanikanie" CutTransition="Cięcie" SwipeTransition="Przesunięcie" SlideTransition="Slajd" +StingerTransition="Stinger" FadeToColorTransition="Zanikanie do koloru" Direction="Kierunek" Direction.Left="W lewo" @@ -10,6 +11,15 @@ Direction.Up="W górę" Direction.Down="W dół" SwipeIn="Przesuwaj do środka" Color="Kolor" +VideoFile="Plik wideo" +TransitionPoint="Punkt przejścia (w milisekundach)" +TransitionPointFrame="Punkt przejścia (ramka)" +TransitionPointType="Typ punktu przejścia" +TransitionPointTypeFrame="Ramka" +TransitionPointTypeTime="Czas (w milisekundach)" +AudioFadeStyle="Styl przejścia dźwięku" +AudioFadeStyle.FadeOutFadeIn="Stopniowe wyciszenie do punktu przejścia a następnie stopniowe wzmocnienie" +AudioFadeStyle.CrossFade="Płynne przejście" SwitchPoint="Punkt szczytowy koloru (procent)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Obraz" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Paski pionowe" LumaWipe.Type.Watercolor="Akwarela" LumaWipe.Type.ZigzagHorizontal="Zygzak poziomy" LumaWipe.Type.ZigzagVertical="Zygzak pionowy" +AudioMonitoring="Monitorowanie urządzenia audio" +AudioMonitoring.None="Wyłączone" +AudioMonitoring.MonitorOnly="Tylko monitorowanie (wyjście wyłączone)" +AudioMonitoring.Both="Monitorowanie i przekazywanie na wyjście" diff --git a/plugins/obs-transitions/data/locale/pt-BR.ini b/plugins/obs-transitions/data/locale/pt-BR.ini index 3a55c32..634ab31 100644 --- a/plugins/obs-transitions/data/locale/pt-BR.ini +++ b/plugins/obs-transitions/data/locale/pt-BR.ini @@ -2,6 +2,7 @@ FadeTransition="Esmaecer" CutTransition="Cortar" SwipeTransition="Deslizar" SlideTransition="Deslizar" +StingerTransition="Stinger" FadeToColorTransition="Esmaecer para a Cor" Direction="Direção" Direction.Left="Esquerda" @@ -10,6 +11,15 @@ Direction.Up="Cima" Direction.Down="Baixo" SwipeIn="Deslizar para" Color="Cor" +VideoFile="Arquivo de Vídeo" +TransitionPoint="Ponto de Transição (milissegundos)" +TransitionPointFrame="Ponto de transição (quadro)" +TransitionPointType="Tipo de Ponto de Transição" +TransitionPointTypeFrame="Quadro" +TransitionPointTypeTime="Tempo (milissegundos)" +AudioFadeStyle="Estilo de Esmaecimento de Áudio" +AudioFadeStyle.FadeOutFadeIn="Esmaecer imagem até o ponto de transição e depois aparecer" +AudioFadeStyle.CrossFade="Transição Suave" SwitchPoint="Ponto de Pico de Cor (porcentagem)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Imagem" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Listras Verticais" LumaWipe.Type.Watercolor="Aquarela" LumaWipe.Type.ZigzagHorizontal="Zigue-zague Horizontal" LumaWipe.Type.ZigzagVertical="Zigue-zague Vertical" +AudioMonitoring="Monitoramento de Áudio" +AudioMonitoring.None="Não Monitorar" +AudioMonitoring.MonitorOnly="Apenas Monitorar (saída muda)" +AudioMonitoring.Both="Monitorar e Enviar Áudio" diff --git a/plugins/obs-transitions/data/locale/ru-RU.ini b/plugins/obs-transitions/data/locale/ru-RU.ini index bba5dfb..6b58436 100644 --- a/plugins/obs-transitions/data/locale/ru-RU.ini +++ b/plugins/obs-transitions/data/locale/ru-RU.ini @@ -2,6 +2,7 @@ FadeTransition="Затухание" CutTransition="Обрезать" SwipeTransition="Перемещение" SlideTransition="Сдвиг" +StingerTransition="Стингер" FadeToColorTransition="Затухание в цвет" Direction="Направление" Direction.Left="Влево" @@ -10,6 +11,15 @@ Direction.Up="Вверх" Direction.Down="Вниз" SwipeIn="Перемещение внутрь" Color="Цвет" +VideoFile="Файл видео" +TransitionPoint="Точка перехода (миллисекунды)" +TransitionPointFrame="Точка перехода (кадр)" +TransitionPointType="Тип точки перехода" +TransitionPointTypeFrame="Кадр" +TransitionPointTypeTime="Время (миллисекунд)" +AudioFadeStyle="Стиль затухания аудио" +AudioFadeStyle.FadeOutFadeIn="Затухание в точку перехода с последующим появлением" +AudioFadeStyle.CrossFade="Переход" SwitchPoint="Точка цветового пика (в процентах)" LumaWipeTransition="Выцветание" LumaWipe.Image="Изображение" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Вертикальные полосы" LumaWipe.Type.Watercolor="Акварель" LumaWipe.Type.ZigzagHorizontal="Горизонтальный зигзаг" LumaWipe.Type.ZigzagVertical="Вертикальный зигзаг" +AudioMonitoring="Аудио мониторинг" +AudioMonitoring.None="Выключить мониторинг" +AudioMonitoring.MonitorOnly="Только мониторинг (заглушить вывод)" +AudioMonitoring.Both="Мониторинг и вывод" diff --git a/plugins/obs-transitions/data/locale/sk-SK.ini b/plugins/obs-transitions/data/locale/sk-SK.ini new file mode 100644 index 0000000..6f80bac --- /dev/null +++ b/plugins/obs-transitions/data/locale/sk-SK.ini @@ -0,0 +1,29 @@ +FadeTransition="Miznutie" +CutTransition="Strih" +SwipeTransition="Potiahnutie" +SlideTransition="Posunutie" +StingerTransition="Stinger" +FadeToColorTransition="Miznutie do farby" +Direction="Smer" +Direction.Left="Vľavo" +Direction.Right="Vpravo" +Direction.Up="Nahor" +Direction.Down="Nadol" +Color="Farba" +VideoFile="Video súbor" +TransitionPoint="Bod prechodu (v milisekundách)" +TransitionPointFrame="Bod prechodu (v snímkoch)" +TransitionPointType="Typ bodu prechodu" +TransitionPointTypeFrame="Snímok" +TransitionPointTypeTime="Čas (v milisekundách)" +LumaWipe.Image="Obrázok" +LumaWipe.Invert="Invertovať" +LumaWipe.Softness="Mäkkosť" +LumaWipe.Type.Circles="Kruhy" +LumaWipe.Type.Clock="Hodiny" +LumaWipe.Type.Cloud="Oblak" +LumaWipe.Type.Curtain="Záves" +LumaWipe.Type.Fan="Ventilátor" +LumaWipe.Type.Fractal="Fractal" +LumaWipe.Type.Iris="Iris" + diff --git a/plugins/obs-transitions/data/locale/sv-SE.ini b/plugins/obs-transitions/data/locale/sv-SE.ini index 53b7383..967512e 100644 --- a/plugins/obs-transitions/data/locale/sv-SE.ini +++ b/plugins/obs-transitions/data/locale/sv-SE.ini @@ -2,6 +2,7 @@ FadeTransition="Tona" CutTransition="Klipp" SwipeTransition="Svep" SlideTransition="Glid" +StingerTransition="Stinger" FadeToColorTransition="Tona till färg" Direction="Riktning" Direction.Left="Vänster" @@ -10,6 +11,15 @@ Direction.Up="Upp" Direction.Down="Ned" SwipeIn="Svep in" Color="Färg" +VideoFile="Videofil" +TransitionPoint="Övergångspunkt (millisekunder)" +TransitionPointFrame="Övergångspunkt (bildruta)" +TransitionPointType="Typ av övergångspunkt" +TransitionPointTypeFrame="Bildruta" +TransitionPointTypeTime="Tid (millisekunder)" +AudioFadeStyle="Stil för ljuduttoning" +AudioFadeStyle.FadeOutFadeIn="Tona ut till övergångspunkten och sedan tona in" +AudioFadeStyle.CrossFade="Övertoning" SwitchPoint="Maxpunkt för färg (procent)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Bild" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Streck, vertikalt" LumaWipe.Type.Watercolor="Vattenfärg" LumaWipe.Type.ZigzagHorizontal="Sicksack, horisontalt" LumaWipe.Type.ZigzagVertical="Sicksack, vertikalt" +AudioMonitoring="Ljudövervakning" +AudioMonitoring.None="Övervaka inte" +AudioMonitoring.MonitorOnly="Övervaka endast (tysta utgång)" +AudioMonitoring.Both="Övervaka och mata ut" diff --git a/plugins/obs-transitions/data/locale/tr-TR.ini b/plugins/obs-transitions/data/locale/tr-TR.ini index 8a1862d..dc0020b 100644 --- a/plugins/obs-transitions/data/locale/tr-TR.ini +++ b/plugins/obs-transitions/data/locale/tr-TR.ini @@ -2,6 +2,7 @@ FadeTransition="Soldur" CutTransition="Kes" SwipeTransition="Kaydır" SlideTransition="Kaydır" +StingerTransition="Isırıcı" FadeToColorTransition="Fade to Color" Direction="Yönlendir" Direction.Left="Sol" @@ -10,6 +11,15 @@ Direction.Up="Yukarı" Direction.Down="Aşağı" SwipeIn="İçeri Kaydır" Color="Renk" +VideoFile="Video Dosyası" +TransitionPoint="Geçiş Noktası (milisaniye)" +TransitionPointFrame="Geçiş Noktası (kare)" +TransitionPointType="Geçiş Noktası Türü" +TransitionPointTypeFrame="Kare" +TransitionPointTypeTime="Süre (milisaniye)" +AudioFadeStyle="Ses Geçiş Stili" +AudioFadeStyle.FadeOutFadeIn="Geçiş noktasına doğru azalt sonra artır" +AudioFadeStyle.CrossFade="Çapraz Geçiş" SwitchPoint="En yüksek Renk Noktası (yüzde)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Görüntü" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Strips Vertical" LumaWipe.Type.Watercolor="Watercolor" LumaWipe.Type.ZigzagHorizontal="Zigzag Horizontal" LumaWipe.Type.ZigzagVertical="Zigzag Vertical" +AudioMonitoring="Ses İzleme" +AudioMonitoring.None="Ekran Kapalı" +AudioMonitoring.MonitorOnly="Sadece Ekran (sessiz çıkış)" +AudioMonitoring.Both="Ekran ve Çıkış" diff --git a/plugins/obs-transitions/data/locale/uk-UA.ini b/plugins/obs-transitions/data/locale/uk-UA.ini index 816710d..61fabb0 100644 --- a/plugins/obs-transitions/data/locale/uk-UA.ini +++ b/plugins/obs-transitions/data/locale/uk-UA.ini @@ -2,6 +2,7 @@ FadeTransition="Fade" CutTransition="Cut" SwipeTransition="Swipe" SlideTransition="Slide" +StingerTransition="Stinger" FadeToColorTransition="Fade to Color" Direction="Напрямок" Direction.Left="Ліворуч" @@ -10,6 +11,15 @@ Direction.Up="Вгору" Direction.Down="Вниз" SwipeIn="В кадр" Color="Колір" +VideoFile="Файл відео" +TransitionPoint="Точка відео-переходу (мілісекунд)" +TransitionPointFrame="Точка відео-переходу (кадр)" +TransitionPointType="Тип точки відео-переходу" +TransitionPointTypeFrame="Кадр" +TransitionPointTypeTime="Час (мілісекунд)" +AudioFadeStyle="Стиль затухання Аудіо" +AudioFadeStyle.FadeOutFadeIn="Затухання до точки відео-переходу, потім гучність наростає" +AudioFadeStyle.CrossFade="Плавний перехід (з початку)" SwitchPoint="Найвища точка для кольору (відсоток)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Зображення" @@ -39,7 +49,7 @@ LumaWipe.Type.LinearTopRight="Лінійно з правого верхньог LumaWipe.Type.LinearVertical="Лінійно по вертикалі" LumaWipe.Type.ParallelZigzagHorizontal="Паралельний зигзаг по горизонталі" LumaWipe.Type.ParallelZigzagVertical="Паралельний зигзаг по вертикалі" -LumaWipe.Type.Sinus9="Синус 9" +LumaWipe.Type.Sinus9="Плазма" LumaWipe.Type.Spiral="Спіраль" LumaWipe.Type.Square="Квадрат" LumaWipe.Type.Squares="Квадрати" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Ступінчасто по горизонталі LumaWipe.Type.Watercolor="Водні розмиви" LumaWipe.Type.ZigzagHorizontal="Зигзаг по горизонталі" LumaWipe.Type.ZigzagVertical="Зигзаг по вертикалі" +AudioMonitoring="Тестування Аудіо (на слух)" +AudioMonitoring.None="Тест вимкнено" +AudioMonitoring.MonitorOnly="Слухати, але не Виводити" +AudioMonitoring.Both="Слухати та Виводити" diff --git a/plugins/obs-transitions/data/locale/vi-VN.ini b/plugins/obs-transitions/data/locale/vi-VN.ini new file mode 100644 index 0000000..460eef1 --- /dev/null +++ b/plugins/obs-transitions/data/locale/vi-VN.ini @@ -0,0 +1,20 @@ +FadeTransition="Mờ dần" +CutTransition="Cắt" +SwipeTransition="Vuốt" +SlideTransition="Trượt" +Direction="Hướng" +Direction.Left="Trái" +Direction.Right="Phải" +Direction.Up="Trên" +Direction.Down="Xuống" +SwipeIn="Trượt lên" +Color="Màu" +VideoFile="Tập tin video" +TransitionPointTypeFrame="Khung hình" +AudioFadeStyle.CrossFade="Làm mờ ảnh" +LumaWipe.Image="Hình ảnh" +LumaWipe.Invert="Đảo ngược" +LumaWipe.Type.Square="Vuông" +LumaWipe.Type.Squares="Vuông" +LumaWipe.Type.Stripes="Sọc" + diff --git a/plugins/obs-transitions/data/locale/zh-CN.ini b/plugins/obs-transitions/data/locale/zh-CN.ini index 5edfdff..bfd17d7 100644 --- a/plugins/obs-transitions/data/locale/zh-CN.ini +++ b/plugins/obs-transitions/data/locale/zh-CN.ini @@ -2,6 +2,7 @@ FadeTransition="淡出" CutTransition="剪切" SwipeTransition="滑动" SlideTransition="滑动" +StingerTransition="毒刺" FadeToColorTransition="色彩淡入淡出" Direction="方向" Direction.Left="左" @@ -10,6 +11,15 @@ Direction.Up="上" Direction.Down="下" SwipeIn="向上滑动" Color="色彩" +VideoFile="视频文件" +TransitionPoint="转换点 (毫秒)" +TransitionPointFrame="转换点 (帧)" +TransitionPointType="转换点类型" +TransitionPointTypeFrame="帧" +TransitionPointTypeTime="时间 (毫秒)" +AudioFadeStyle="音频淡入淡出样式" +AudioFadeStyle.FadeOutFadeIn="淡出到过渡点然后淡入" +AudioFadeStyle.CrossFade="交叉淡入淡出" SwitchPoint="峰值颜色点(百分比)" LumaWipeTransition="亮度擦除" LumaWipe.Image="图像" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="条纹垂直" LumaWipe.Type.Watercolor="水彩" LumaWipe.Type.ZigzagHorizontal="Z字形水平" LumaWipe.Type.ZigzagVertical="Z字形垂直" +AudioMonitoring="音频监测" +AudioMonitoring.None="关闭监视" +AudioMonitoring.MonitorOnly="仅显示器(输出静音)" +AudioMonitoring.Both="显示器和输出" diff --git a/plugins/obs-transitions/data/locale/zh-TW.ini b/plugins/obs-transitions/data/locale/zh-TW.ini index 053913f..9db7a53 100644 --- a/plugins/obs-transitions/data/locale/zh-TW.ini +++ b/plugins/obs-transitions/data/locale/zh-TW.ini @@ -2,6 +2,7 @@ FadeTransition="淡入淡出" CutTransition="直接轉場" SwipeTransition="滑出" SlideTransition="推出" +StingerTransition="Stinger" FadeToColorTransition="淡出至指定色彩" Direction="方向" Direction.Left="左" @@ -10,6 +11,15 @@ Direction.Up="上" Direction.Down="下" SwipeIn="滑入" Color="顏色" +VideoFile="影片檔" +TransitionPoint="轉換點 (毫秒)" +TransitionPointFrame="轉換點 (訊框)" +TransitionPointType="轉換點類型" +TransitionPointTypeFrame="訊框" +TransitionPointTypeTime="時間 (毫秒)" +AudioFadeStyle="音訊淡入淡出風格" +AudioFadeStyle.FadeOutFadeIn="淡出至轉換點再淡入" +AudioFadeStyle.CrossFade="交叉式淡入淡出" SwitchPoint="顏色峰值點 (百分比)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="影像" @@ -49,4 +59,8 @@ LumaWipe.Type.StripsVertical="Strips Vertical" LumaWipe.Type.Watercolor="Watercolor" LumaWipe.Type.ZigzagHorizontal="Zigzag Horizontal" LumaWipe.Type.ZigzagVertical="Zigzag Vertical" +AudioMonitoring="音訊監測" +AudioMonitoring.None="關閉監測" +AudioMonitoring.MonitorOnly="僅監測(輸出靜音)" +AudioMonitoring.Both="監測並輸出" diff --git a/plugins/obs-transitions/obs-transitions.c b/plugins/obs-transitions/obs-transitions.c index 3841f48..22276d3 100644 --- a/plugins/obs-transitions/obs-transitions.c +++ b/plugins/obs-transitions/obs-transitions.c @@ -8,6 +8,7 @@ extern struct obs_source_info cut_transition; extern struct obs_source_info fade_transition; extern struct obs_source_info swipe_transition; extern struct obs_source_info slide_transition; +extern struct obs_source_info stinger_transition; extern struct obs_source_info fade_to_color_transition; extern struct obs_source_info luma_wipe_transition; @@ -17,6 +18,7 @@ bool obs_module_load(void) obs_register_source(&fade_transition); obs_register_source(&swipe_transition); obs_register_source(&slide_transition); + obs_register_source(&stinger_transition); obs_register_source(&fade_to_color_transition); obs_register_source(&luma_wipe_transition); return true; diff --git a/plugins/obs-transitions/transition-stinger.c b/plugins/obs-transitions/transition-stinger.c new file mode 100644 index 0000000..8234b0d --- /dev/null +++ b/plugins/obs-transitions/transition-stinger.c @@ -0,0 +1,370 @@ +#include + +#define TIMING_TIME 0 +#define TIMING_FRAME 1 + +enum fade_style { + FADE_STYLE_FADE_OUT_FADE_IN, + FADE_STYLE_CROSS_FADE +}; + +struct stinger_info { + obs_source_t *source; + + obs_source_t *media_source; + + uint64_t duration_ns; + uint64_t duration_frames; + uint64_t transition_point_ns; + uint64_t transition_point_frame; + float transition_point; + float transition_a_mul; + float transition_b_mul; + bool transitioning; + bool transition_point_is_frame; + int monitoring_type; + enum fade_style fade_style; + + float (*mix_a)(void *data, float t); + float (*mix_b)(void *data, float t); +}; + +static const char *stinger_get_name(void *type_data) +{ + UNUSED_PARAMETER(type_data); + return obs_module_text("StingerTransition"); +} + +static float mix_a_fade_in_out(void *data, float t); +static float mix_b_fade_in_out(void *data, float t); +static float mix_a_cross_fade(void *data, float t); +static float mix_b_cross_fade(void *data, float t); + +static void stinger_update(void *data, obs_data_t *settings) +{ + struct stinger_info *s = data; + const char *path = obs_data_get_string(settings, "path"); + + obs_data_t *media_settings = obs_data_create(); + obs_data_set_string(media_settings, "local_file", path); + + obs_source_release(s->media_source); + s->media_source = obs_source_create_private("ffmpeg_source", NULL, + media_settings); + obs_data_release(media_settings); + + int64_t point = obs_data_get_int(settings, "transition_point"); + + s->transition_point_is_frame = + obs_data_get_int(settings, "tp_type") == TIMING_FRAME; + + if (s->transition_point_is_frame) + s->transition_point_frame = (uint64_t)point; + else + s->transition_point_ns = (uint64_t)(point * 1000000LL); + + s->monitoring_type = (int)obs_data_get_int(settings,"audio_monitoring"); + obs_source_set_monitoring_type(s->media_source, s->monitoring_type); + + s->fade_style = (enum fade_style)obs_data_get_int(settings, + "audio_fade_style"); + + switch (s->fade_style) { + default: + case FADE_STYLE_FADE_OUT_FADE_IN: + s->mix_a = mix_a_fade_in_out; + s->mix_b = mix_b_fade_in_out; + break; + case FADE_STYLE_CROSS_FADE: + s->mix_a = mix_a_cross_fade; + s->mix_b = mix_b_cross_fade; + break; + } +} + +static void *stinger_create(obs_data_t *settings, obs_source_t *source) +{ + struct stinger_info *s = bzalloc(sizeof(*s)); + + s->source = source; + s->mix_a = mix_a_fade_in_out; + s->mix_b = mix_b_fade_in_out; + + obs_transition_enable_fixed(s->source, true, 0); + obs_source_update(source, settings); + return s; +} + +static void stinger_destroy(void *data) +{ + struct stinger_info *s = data; + obs_source_release(s->media_source); + bfree(s); +} + +static void stinger_video_render(void *data, gs_effect_t *effect) +{ + struct stinger_info *s = data; + + float t = obs_transition_get_time(s->source); + bool use_a = t < s->transition_point; + + enum obs_transition_target target = use_a + ? OBS_TRANSITION_SOURCE_A + : OBS_TRANSITION_SOURCE_B; + + if (!obs_transition_video_render_direct(s->source, target)) + return; + + /* --------------------- */ + + float source_cx = (float)obs_source_get_width(s->source); + float source_cy = (float)obs_source_get_height(s->source); + uint32_t media_cx = obs_source_get_width(s->media_source); + uint32_t media_cy = obs_source_get_height(s->media_source); + + if (!media_cx || !media_cy) + return; + + float scale_x = source_cx / (float)media_cx; + float scale_y = source_cy / (float)media_cy; + + gs_matrix_push(); + gs_matrix_scale3f(scale_x, scale_y, 1.0f); + obs_source_video_render(s->media_source); + gs_matrix_pop(); + + UNUSED_PARAMETER(effect); +} + +static inline float calc_fade(float t, float mul) +{ + t *= mul; + return t > 1.0f ? 1.0f : t; +} + +static float mix_a_fade_in_out(void *data, float t) +{ + struct stinger_info *s = data; + return 1.0f - calc_fade(t, s->transition_a_mul); +} + +static float mix_b_fade_in_out(void *data, float t) +{ + struct stinger_info *s = data; + return 1.0f - calc_fade(1.0f - t, s->transition_b_mul); +} + +static float mix_a_cross_fade(void *data, float t) +{ + UNUSED_PARAMETER(data); + return 1.0f - t; +} + +static float mix_b_cross_fade(void *data, float t) +{ + UNUSED_PARAMETER(data); + return t; +} + +static bool stinger_audio_render(void *data, uint64_t *ts_out, + struct obs_source_audio_mix *audio, uint32_t mixers, + size_t channels, size_t sample_rate) +{ + struct stinger_info *s = data; + uint64_t ts = 0; + + if (!obs_source_audio_pending(s->media_source)) { + ts = obs_source_get_audio_timestamp(s->media_source); + if (!ts) + return false; + } + + bool success = obs_transition_audio_render(s->source, ts_out, + audio, mixers, channels, sample_rate, s->mix_a, s->mix_b); + if (!ts) + return success; + + if (!*ts_out || ts < *ts_out) + *ts_out = ts; + + struct obs_source_audio_mix child_audio; + obs_source_get_audio_mix(s->media_source, &child_audio); + + for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) { + if ((mixers & (1 << mix)) == 0) + continue; + + for (size_t ch = 0; ch < channels; ch++) { + register float *out = audio->output[mix].data[ch]; + register float *in = child_audio.output[mix].data[ch]; + register float *end = in + AUDIO_OUTPUT_FRAMES; + + while (in < end) + *(out++) += *(in++); + } + } + + return true; +} + +static void stinger_transition_start(void *data) +{ + struct stinger_info *s = data; + + if (s->media_source) { + calldata_t cd = {0}; + + proc_handler_t *ph = + obs_source_get_proc_handler(s->media_source); + + if (s->transitioning) { + proc_handler_call(ph, "restart", &cd); + return; + } + + proc_handler_call(ph, "get_duration", &cd); + proc_handler_call(ph, "get_nb_frames", &cd); + s->duration_ns = (uint64_t)calldata_int(&cd, "duration"); + s->duration_frames = (uint64_t)calldata_int(&cd, "num_frames"); + + if (s->transition_point_is_frame) + s->transition_point = (float)( + (long double)s->transition_point_frame / + (long double)s->duration_frames); + else + s->transition_point = (float)( + (long double)s->transition_point_ns / + (long double)s->duration_ns); + + if (s->transition_point > 1.0f) + s->transition_point = 1.0f; + else if (s->transition_point < 0.001f) + s->transition_point = 0.001f; + + s->transition_a_mul = (1.0f / s->transition_point); + s->transition_b_mul = (1.0f / (1.0f - s->transition_point)); + + obs_transition_enable_fixed(s->source, true, + (uint32_t)(s->duration_ns / 1000000)); + + calldata_free(&cd); + + obs_source_add_active_child(s->source, s->media_source); + } + + s->transitioning = true; +} + +static void stinger_transition_stop(void *data) +{ + struct stinger_info *s = data; + + if (s->media_source) + obs_source_remove_active_child(s->source, s->media_source); + + s->transitioning = false; +} + +static void stinger_enum_active_sources(void *data, + obs_source_enum_proc_t enum_callback, void *param) +{ + struct stinger_info *s = data; + if (s->media_source && s->transitioning) + enum_callback(s->source, s->media_source, param); +} + +static void stinger_enum_all_sources(void *data, + obs_source_enum_proc_t enum_callback, void *param) +{ + struct stinger_info *s = data; + if (s->media_source) + enum_callback(s->source, s->media_source, param); +} + +#define FILE_FILTER \ + "Video Files (*.mp4 *.ts *.mov *.wmv *.flv *.mkv *.avi *.gif *.webm);;" + +static bool transition_point_type_modified(obs_properties_t *ppts, + obs_property_t *p, obs_data_t *s) +{ + int64_t type = obs_data_get_int(s, "tp_type"); + p = obs_properties_get(ppts, "transition_point"); + + if (type == TIMING_TIME) + obs_property_set_description(p, + obs_module_text("TransitionPoint")); + else + obs_property_set_description(p, + obs_module_text("TransitionPointFrame")); + return true; +} + +static obs_properties_t *stinger_properties(void *data) +{ + obs_properties_t *ppts = obs_properties_create(); + + obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE); + + obs_properties_add_path(ppts, "path", + obs_module_text("VideoFile"), + OBS_PATH_FILE, + FILE_FILTER, NULL); + obs_property_t *list = obs_properties_add_list(ppts, "tp_type", + obs_module_text("TransitionPointType"), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(list, + obs_module_text("TransitionPointTypeTime"), + TIMING_TIME); + obs_property_list_add_int(list, + obs_module_text("TransitionPointTypeFrame"), + TIMING_FRAME); + + obs_property_set_modified_callback(list, transition_point_type_modified); + + obs_properties_add_int(ppts, "transition_point", + obs_module_text("TransitionPoint"), + 0, 120000, 1); + + obs_property_t *monitor_list = obs_properties_add_list(ppts, + "audio_monitoring", obs_module_text("AudioMonitoring"), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(monitor_list, + obs_module_text("AudioMonitoring.None"), + OBS_MONITORING_TYPE_NONE); + obs_property_list_add_int(monitor_list, + obs_module_text("AudioMonitoring.MonitorOnly"), + OBS_MONITORING_TYPE_MONITOR_ONLY); + obs_property_list_add_int(monitor_list, + obs_module_text("AudioMonitoring.Both"), + OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT); + + obs_property_t *audio_fade_style = obs_properties_add_list(ppts, + "audio_fade_style", obs_module_text("AudioFadeStyle"), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(audio_fade_style, + obs_module_text("AudioFadeStyle.FadeOutFadeIn"), + FADE_STYLE_FADE_OUT_FADE_IN); + obs_property_list_add_int(audio_fade_style, + obs_module_text("AudioFadeStyle.CrossFade"), + FADE_STYLE_CROSS_FADE); + + UNUSED_PARAMETER(data); + return ppts; +} + +struct obs_source_info stinger_transition = { + .id = "obs_stinger_transition", + .type = OBS_SOURCE_TYPE_TRANSITION, + .get_name = stinger_get_name, + .create = stinger_create, + .destroy = stinger_destroy, + .update = stinger_update, + .video_render = stinger_video_render, + .audio_render = stinger_audio_render, + .get_properties = stinger_properties, + .enum_active_sources = stinger_enum_active_sources, + .enum_all_sources = stinger_enum_all_sources, + .transition_start = stinger_transition_start, + .transition_stop = stinger_transition_stop +}; diff --git a/plugins/obs-x264/data/locale/el-GR.ini b/plugins/obs-x264/data/locale/el-GR.ini index 9da15c7..ce9540d 100644 --- a/plugins/obs-x264/data/locale/el-GR.ini +++ b/plugins/obs-x264/data/locale/el-GR.ini @@ -1,6 +1,7 @@ Bitrate="Ρυθμός μετάδοσης bit" CustomBufsize="Χρήση Προσαρμοσμένου Μεγέθους Buffer" BufferSize="Μέγεθος buffer" +RateControl="Έλεγχος ρυθμού" CRF="CRF" KeyframeIntervalSec="Συχνότητα Καρέ-Κλειδιού (δευτερόλεπτα, 0=αυτόματο)" CPUPreset="Προφίλ Χρήσης CPU (υψηλότερο = λιγότερη CPU)" @@ -8,5 +9,5 @@ Profile="Προφίλ" Tune="Βελτιστοποίηση" None="(Κανένα)" EncoderOptions="Επιλογές x264 (διαχωρισμένες από κενό)" -VFR="Μεταβλητή ταχύτητα καρέ (VFR)" +VFR="Μεταβλητός Ρυθμός καρέ (VFR)" diff --git a/plugins/obs-x264/data/locale/ka-GE.ini b/plugins/obs-x264/data/locale/ka-GE.ini new file mode 100644 index 0000000..ea17e16 --- /dev/null +++ b/plugins/obs-x264/data/locale/ka-GE.ini @@ -0,0 +1,2 @@ +CRF="CRF" + diff --git a/plugins/obs-x264/data/locale/ko-KR.ini b/plugins/obs-x264/data/locale/ko-KR.ini index ee9a402..f00a1ce 100644 --- a/plugins/obs-x264/data/locale/ko-KR.ini +++ b/plugins/obs-x264/data/locale/ko-KR.ini @@ -4,7 +4,7 @@ BufferSize="버퍼 크기" RateControl="데이터율 제어" CRF="CRF" KeyframeIntervalSec="키프레임 간격 (초 단위, 0=자동)" -CPUPreset="CPU 사용량 사전설정 (높음 = 적은 CPU 부담)" +CPUPreset="CPU 사용량 (빠를수록 부담 적으나 화질 나쁨)" Profile="프로파일" Tune="조정" None="(없음)" diff --git a/plugins/obs-x264/obs-x264.c b/plugins/obs-x264/obs-x264.c index 5151c8c..d177a8e 100644 --- a/plugins/obs-x264/obs-x264.c +++ b/plugins/obs-x264/obs-x264.c @@ -35,6 +35,8 @@ #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) +//#define ENABLE_VFR + /* ------------------------------------------------------------------------- */ struct obs_x264 { @@ -96,7 +98,9 @@ static void obs_x264_defaults(obs_data_t *settings) obs_data_set_default_int (settings, "buffer_size", 2500); obs_data_set_default_int (settings, "keyint_sec", 0); obs_data_set_default_int (settings, "crf", 23); +#ifdef ENABLE_VFR obs_data_set_default_bool (settings, "vfr", false); +#endif obs_data_set_default_string(settings, "rate_control","CBR"); obs_data_set_default_string(settings, "preset", "veryfast"); @@ -202,7 +206,9 @@ static obs_properties_t *obs_x264_props(void *unused) obs_property_list_add_string(list, TEXT_NONE, ""); add_strings(list, x264_tune_names); +#ifdef ENABLE_VFR obs_properties_add_bool(props, "vfr", TEXT_VFR); +#endif obs_properties_add_text(props, "x264opts", TEXT_X264_OPTS, OBS_TEXT_DEFAULT); @@ -407,11 +413,15 @@ static void update_params(struct obs_x264 *obsx264, obs_data_t *settings, int crf = (int)obs_data_get_int(settings, "crf"); int width = (int)obs_encoder_get_width(obsx264->encoder); int height = (int)obs_encoder_get_height(obsx264->encoder); + int bf = (int)obs_data_get_int(settings, "bf"); bool use_bufsize = obs_data_get_bool(settings, "use_bufsize"); - bool vfr = obs_data_get_bool(settings, "vfr"); bool cbr_override= obs_data_get_bool(settings, "cbr"); enum rate_control rc; +#ifdef ENABLE_VFR + bool vfr = obs_data_get_bool(settings, "vfr"); +#endif + /* XXX: "cbr" setting has been deprecated */ if (cbr_override) { warn("\"cbr\" setting has been deprecated for all encoders! " @@ -446,7 +456,11 @@ static void update_params(struct obs_x264 *obsx264, obs_data_t *settings, if (!use_bufsize) buffer_size = bitrate; +#ifdef ENABLE_VFR obsx264->params.b_vfr_input = vfr; +#else + obsx264->params.b_vfr_input = false; +#endif obsx264->params.rc.i_vbv_max_bitrate = bitrate; obsx264->params.rc.i_vbv_buffer_size = buffer_size; obsx264->params.rc.i_bitrate = bitrate; @@ -458,6 +472,9 @@ static void update_params(struct obs_x264 *obsx264, obs_data_t *settings, obsx264->params.p_log_private = obsx264; obsx264->params.i_log_level = X264_LOG_WARNING; + if (obs_data_has_user_value(settings, "bf")) + obsx264->params.i_bframe = bf; + obsx264->params.vui.i_transfer = get_x264_cs_val(info.colorspace, x264_transfer_names); obsx264->params.vui.i_colmatrix = @@ -506,16 +523,14 @@ static void update_params(struct obs_x264 *obsx264, obs_data_t *settings, "\tfps_den: %d\n" "\twidth: %d\n" "\theight: %d\n" - "\tkeyint: %d\n" - "\tvfr: %s\n", + "\tkeyint: %d\n", rate_control, obsx264->params.rc.i_vbv_max_bitrate, obsx264->params.rc.i_vbv_buffer_size, (int)obsx264->params.rc.f_rf_constant, voi->fps_num, voi->fps_den, width, height, - obsx264->params.i_keyint_max, - vfr ? "on" : "off"); + obsx264->params.i_keyint_max); } static bool update_settings(struct obs_x264 *obsx264, obs_data_t *settings) diff --git a/plugins/rtmp-services/CMakeLists.txt b/plugins/rtmp-services/CMakeLists.txt index aedcc66..07e7250 100644 --- a/plugins/rtmp-services/CMakeLists.txt +++ b/plugins/rtmp-services/CMakeLists.txt @@ -3,16 +3,19 @@ project(rtmp-services) include_directories(${OBS_JANSSON_INCLUDE_DIRS}) set(rtmp-services_SOURCES + twitch.c rtmp-common.c rtmp-custom.c rtmp-services-main.c) set(rtmp-services_HEADERS + twitch.h rtmp-format-ver.h) set(RTMP_SERVICES_URL "https://obsproject.com/obs2_update/rtmp-services" CACHE STRING "Default services package URL") +option(CHECK_FOR_SERVICE_UPDATES "Checks for service updates" OFF) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/lookup-config.h.in" diff --git a/plugins/rtmp-services/data/locale/ca-ES.ini b/plugins/rtmp-services/data/locale/ca-ES.ini index 3c59a45..87c46f2 100644 --- a/plugins/rtmp-services/data/locale/ca-ES.ini +++ b/plugins/rtmp-services/data/locale/ca-ES.ini @@ -2,6 +2,7 @@ StreamingServices="Serveis de transmissió" CustomStreamingServer="Servidor de transmissió personalitzat" Service="Servei" Server="Servidor" +Server.Auto="Automàtic (recomanat)" StreamKey="Clau de la transmissió" UseAuth="Usa autenticació" Username="Nom d’usuari" diff --git a/plugins/rtmp-services/data/locale/cs-CZ.ini b/plugins/rtmp-services/data/locale/cs-CZ.ini index 09c6a03..23ea19b 100644 --- a/plugins/rtmp-services/data/locale/cs-CZ.ini +++ b/plugins/rtmp-services/data/locale/cs-CZ.ini @@ -2,6 +2,7 @@ StreamingServices="Streamovací služby" CustomStreamingServer="Vlastní streamovací server" Service="Služba" Server="Server" +Server.Auto="Automaticky (doporučeno)" StreamKey="Vysílací klíč" UseAuth="Použít ověření" Username="Uživatelské jméno" diff --git a/plugins/rtmp-services/data/locale/da-DK.ini b/plugins/rtmp-services/data/locale/da-DK.ini index 44ee8bb..0c413be 100644 --- a/plugins/rtmp-services/data/locale/da-DK.ini +++ b/plugins/rtmp-services/data/locale/da-DK.ini @@ -2,6 +2,7 @@ StreamingServices="Streaming Tjenester" CustomStreamingServer="Brugerdefineret Streaming Server" Service="Service" Server="Server" +Server.Auto="Auto (anbefalet)" StreamKey="Stream nøgle" UseAuth="Brug godkendelse" Username="Brugernavn" diff --git a/plugins/rtmp-services/data/locale/de-DE.ini b/plugins/rtmp-services/data/locale/de-DE.ini index a88cd61..cdfbf98 100644 --- a/plugins/rtmp-services/data/locale/de-DE.ini +++ b/plugins/rtmp-services/data/locale/de-DE.ini @@ -2,6 +2,7 @@ StreamingServices="Streaming-Plattformen" CustomStreamingServer="Benutzerdefinierter Streamingserver" Service="Plattform" Server="Server" +Server.Auto="Automatisch (empfohlen)" StreamKey="Streamschlüssel" UseAuth="Authentifizierung verwenden" Username="Benutzername" diff --git a/plugins/rtmp-services/data/locale/en-US.ini b/plugins/rtmp-services/data/locale/en-US.ini index bf5b528..da2debb 100644 --- a/plugins/rtmp-services/data/locale/en-US.ini +++ b/plugins/rtmp-services/data/locale/en-US.ini @@ -2,6 +2,7 @@ StreamingServices="Streaming Services" CustomStreamingServer="Custom Streaming Server" Service="Service" Server="Server" +Server.Auto="Auto (Recommended)" StreamKey="Stream key" UseAuth="Use authentication" Username="Username" diff --git a/plugins/rtmp-services/data/locale/es-ES.ini b/plugins/rtmp-services/data/locale/es-ES.ini index 71987c1..cdaa6dc 100644 --- a/plugins/rtmp-services/data/locale/es-ES.ini +++ b/plugins/rtmp-services/data/locale/es-ES.ini @@ -2,6 +2,7 @@ StreamingServices="Servicio de retransmisión" CustomStreamingServer="Personalizar el servidor de retransmisión" Service="Servicio" Server="Servidor" +Server.Auto="Automático (Recomendado)" StreamKey="Clave de retransmisión" UseAuth="Usar la autentificación" Username="Nombre de usuario" diff --git a/plugins/rtmp-services/data/locale/eu-ES.ini b/plugins/rtmp-services/data/locale/eu-ES.ini index 47cb9a7..ade2a83 100644 --- a/plugins/rtmp-services/data/locale/eu-ES.ini +++ b/plugins/rtmp-services/data/locale/eu-ES.ini @@ -2,6 +2,7 @@ StreamingServices="Transmisio zerbitzuak" CustomStreamingServer="Transmisio zerbitzari pertsonalizatua" Service="Zerbitzua" Server="Zerbitzaria" +Server.Auto="Auto (gomendatua)" StreamKey="Transmisio giltza" UseAuth="Erabili autentifikazioa" Username="Erabiltzaile-izena" diff --git a/plugins/rtmp-services/data/locale/fi-FI.ini b/plugins/rtmp-services/data/locale/fi-FI.ini index 5a13165..c47d462 100644 --- a/plugins/rtmp-services/data/locale/fi-FI.ini +++ b/plugins/rtmp-services/data/locale/fi-FI.ini @@ -2,6 +2,7 @@ StreamingServices="Palvelut" CustomStreamingServer="Valinnainen palvelin" Service="Palvelu" Server="Palvelin" +Server.Auto="Automaattinen (suositus)" StreamKey="Striimiavain" UseAuth="Käytä todennusta" Username="Käyttäjätunnus" diff --git a/plugins/rtmp-services/data/locale/fr-FR.ini b/plugins/rtmp-services/data/locale/fr-FR.ini index 210b272..2320f1e 100644 --- a/plugins/rtmp-services/data/locale/fr-FR.ini +++ b/plugins/rtmp-services/data/locale/fr-FR.ini @@ -2,6 +2,7 @@ StreamingServices="Services de streaming" CustomStreamingServer="Serveur de streaming personnalisé" Service="Service" Server="Serveur" +Server.Auto="Auto (Recommandé)" StreamKey="Clé de stream" UseAuth="Utiliser l'authentification" Username="Nom d'utilisateur" diff --git a/plugins/rtmp-services/data/locale/hu-HU.ini b/plugins/rtmp-services/data/locale/hu-HU.ini index 0cd0a76..ac7bade 100644 --- a/plugins/rtmp-services/data/locale/hu-HU.ini +++ b/plugins/rtmp-services/data/locale/hu-HU.ini @@ -2,6 +2,7 @@ StreamingServices="Stream kiszolgáló" CustomStreamingServer="Egyéni stream szerver" Service="Szolgáltatás" Server="Szerver" +Server.Auto="Automatikus (Ajánlott)" StreamKey="Stream kulcs" UseAuth="Hitelesítés használata" Username="Felhasználó" diff --git a/plugins/rtmp-services/data/locale/it-IT.ini b/plugins/rtmp-services/data/locale/it-IT.ini index b69ffb1..384d30d 100644 --- a/plugins/rtmp-services/data/locale/it-IT.ini +++ b/plugins/rtmp-services/data/locale/it-IT.ini @@ -2,6 +2,7 @@ StreamingServices="Servizi di streaming" CustomStreamingServer="Personalizza il server di streaming" Service="Servizio" Server="Server" +Server.Auto="Auto (consigliato)" StreamKey="Chiave Stream" UseAuth="Usa autenticazione" Username="Username" diff --git a/plugins/rtmp-services/data/locale/ja-JP.ini b/plugins/rtmp-services/data/locale/ja-JP.ini index 082962b..80623e5 100644 --- a/plugins/rtmp-services/data/locale/ja-JP.ini +++ b/plugins/rtmp-services/data/locale/ja-JP.ini @@ -2,6 +2,7 @@ StreamingServices="ストリーミングサービス" CustomStreamingServer="カスタムストリーミングサーバー" Service="サービス" Server="サーバー" +Server.Auto="自動 (推奨)" StreamKey="ストリームキー" UseAuth="認証を使用する" Username="ユーザー名" diff --git a/plugins/rtmp-services/data/locale/ko-KR.ini b/plugins/rtmp-services/data/locale/ko-KR.ini index 0681b9a..ee27f86 100644 --- a/plugins/rtmp-services/data/locale/ko-KR.ini +++ b/plugins/rtmp-services/data/locale/ko-KR.ini @@ -2,6 +2,7 @@ StreamingServices="방송 서비스" CustomStreamingServer="사용자 임의 방송 서버" Service="서비스" Server="서버" +Server.Auto="자동 (권장)" StreamKey="스트림 키" UseAuth="접속 시 인증 필요" Username="사용자 이름" diff --git a/plugins/rtmp-services/data/locale/nb-NO.ini b/plugins/rtmp-services/data/locale/nb-NO.ini index 9d91c41..b879776 100644 --- a/plugins/rtmp-services/data/locale/nb-NO.ini +++ b/plugins/rtmp-services/data/locale/nb-NO.ini @@ -2,6 +2,7 @@ StreamingServices="Strømmingstjenester" CustomStreamingServer="Egendefinert strømmingserver" Service="Tjeneste" Server="Tjener" +Server.Auto="Auto (Anbefales)" StreamKey="Strømingsnøkkel" UseAuth="Bruk godkjenning" Username="Brukernavn" diff --git a/plugins/rtmp-services/data/locale/nl-NL.ini b/plugins/rtmp-services/data/locale/nl-NL.ini index 83f82f1..3fb6e1c 100644 --- a/plugins/rtmp-services/data/locale/nl-NL.ini +++ b/plugins/rtmp-services/data/locale/nl-NL.ini @@ -2,6 +2,7 @@ StreamingServices="Streaming Diensten" CustomStreamingServer="Aangepaste Streaming Server" Service="Dienst" Server="Server" +Server.Auto="Automatisch (aanbevolen)" StreamKey="Stream key" UseAuth="Gebruik authenticatie" Username="Gebruikersnaam" diff --git a/plugins/rtmp-services/data/locale/pl-PL.ini b/plugins/rtmp-services/data/locale/pl-PL.ini index dc7d302..109f55a 100644 --- a/plugins/rtmp-services/data/locale/pl-PL.ini +++ b/plugins/rtmp-services/data/locale/pl-PL.ini @@ -2,6 +2,7 @@ StreamingServices="Serwisy strumieniowania" CustomStreamingServer="Własny serwer strumieniowania" Service="Serwis" Server="Serwer" +Server.Auto="Automatycznie (zalecane)" StreamKey="Klucz strumienia" UseAuth="Użyj uwierzytelniania" Username="Użytkownik" diff --git a/plugins/rtmp-services/data/locale/pt-BR.ini b/plugins/rtmp-services/data/locale/pt-BR.ini index 268f238..bb3073b 100644 --- a/plugins/rtmp-services/data/locale/pt-BR.ini +++ b/plugins/rtmp-services/data/locale/pt-BR.ini @@ -2,9 +2,10 @@ StreamingServices="Serviços de Streaming" CustomStreamingServer="Servidor de Streaming Personalizado" Service="Serviço" Server="Servidor" +Server.Auto="Automático (recomendado)" StreamKey="Chave da Stream" UseAuth="Utilizar autenticação" -Username="Usuario" +Username="Usuário" Password="Senha" ShowAll="Mostrar todos os serviços" diff --git a/plugins/rtmp-services/data/locale/ru-RU.ini b/plugins/rtmp-services/data/locale/ru-RU.ini index fd1e6bd..f0fe2cc 100644 --- a/plugins/rtmp-services/data/locale/ru-RU.ini +++ b/plugins/rtmp-services/data/locale/ru-RU.ini @@ -2,6 +2,7 @@ StreamingServices="Сервисы вещания" CustomStreamingServer="Пользовательский сервер вещания" Service="Сервис" Server="Сервер" +Server.Auto="Автоматически (Рекомендуется)" StreamKey="Ключ потока" UseAuth="Использовать авторизацию" Username="Имя пользователя" diff --git a/plugins/rtmp-services/data/locale/sk-SK.ini b/plugins/rtmp-services/data/locale/sk-SK.ini index 4a903f5..6e4e9bf 100644 --- a/plugins/rtmp-services/data/locale/sk-SK.ini +++ b/plugins/rtmp-services/data/locale/sk-SK.ini @@ -2,6 +2,7 @@ StreamingServices="Streamovacie služby" CustomStreamingServer="Vlastný stream server" Service="Služba" Server="Server" +Server.Auto="Automaticky (odporúča sa)" StreamKey="Stream kľúč" UseAuth="Použiť overenie" Username="Užívateľské meno" diff --git a/plugins/rtmp-services/data/locale/sv-SE.ini b/plugins/rtmp-services/data/locale/sv-SE.ini index b080b2d..54ae360 100644 --- a/plugins/rtmp-services/data/locale/sv-SE.ini +++ b/plugins/rtmp-services/data/locale/sv-SE.ini @@ -2,6 +2,7 @@ StreamingServices="Strömtjänster" CustomStreamingServer="Anpassad streamningsserver" Service="Tjänst" Server="Server" +Server.Auto="Auto (rekommenderad)" StreamKey="Streamnyckel" UseAuth="Använd autentisering" Username="Användarnamn" diff --git a/plugins/rtmp-services/data/locale/tr-TR.ini b/plugins/rtmp-services/data/locale/tr-TR.ini index e27187b..8ecff02 100644 --- a/plugins/rtmp-services/data/locale/tr-TR.ini +++ b/plugins/rtmp-services/data/locale/tr-TR.ini @@ -2,6 +2,7 @@ StreamingServices="Yayın Servisleri" CustomStreamingServer="Özel Yayın Sunucusu" Service="Servis" Server="Sunucu" +Server.Auto="Otomatik (Önerilen)" StreamKey="Yayın Anahtarı" UseAuth="Kimlik doğrulaması kullan" Username="Kullanıcı Adı" diff --git a/plugins/rtmp-services/data/locale/uk-UA.ini b/plugins/rtmp-services/data/locale/uk-UA.ini index 7fb548e..c7ab4df 100644 --- a/plugins/rtmp-services/data/locale/uk-UA.ini +++ b/plugins/rtmp-services/data/locale/uk-UA.ini @@ -2,6 +2,7 @@ StreamingServices="Сервіси трансляцій" CustomStreamingServer="Сервер користувача" Service="Сервіс" Server="Сервер" +Server.Auto="Автоматично (рекомендується)" StreamKey="Ключ трансляції" UseAuth="Використовувати авторизацію" Username="Логін" diff --git a/plugins/rtmp-services/data/locale/vi-VN.ini b/plugins/rtmp-services/data/locale/vi-VN.ini new file mode 100644 index 0000000..7694d88 --- /dev/null +++ b/plugins/rtmp-services/data/locale/vi-VN.ini @@ -0,0 +1,11 @@ +StreamingServices="Dịch vụ stream" +CustomStreamingServer="Tùy chọn máy chủ truyền trực tuyến" +Service="Dịch vụ" +Server="Máy chủ" +Server.Auto="Âm thanh (Khuyến nghị)" +StreamKey="Khóa stream" +UseAuth="Xác thực sử dụng" +Username="Tài khoản" +Password="Mật khẩu" +ShowAll="Hiển tất cả các dịch vụ" + diff --git a/plugins/rtmp-services/data/locale/zh-CN.ini b/plugins/rtmp-services/data/locale/zh-CN.ini index c2db436..aae7fdf 100644 --- a/plugins/rtmp-services/data/locale/zh-CN.ini +++ b/plugins/rtmp-services/data/locale/zh-CN.ini @@ -2,6 +2,7 @@ StreamingServices="流媒体服务" CustomStreamingServer="自定义流媒体服务器" Service="服务" Server="服务器" +Server.Auto="自动(推荐)" StreamKey="流名称" UseAuth="使用身份验证" Username="用户名" diff --git a/plugins/rtmp-services/data/locale/zh-TW.ini b/plugins/rtmp-services/data/locale/zh-TW.ini index cbd62a7..6cb2382 100644 --- a/plugins/rtmp-services/data/locale/zh-TW.ini +++ b/plugins/rtmp-services/data/locale/zh-TW.ini @@ -2,6 +2,7 @@ StreamingServices="串流服務" CustomStreamingServer="自訂串流伺服器" Service="服務商" Server="伺服器" +Server.Auto="自動 (建議)" StreamKey="串流金鑰" UseAuth="使用身份驗證" Username="使用者名稱" diff --git a/plugins/rtmp-services/data/package.json b/plugins/rtmp-services/data/package.json index 38890e9..a7112c6 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": 60, + "version": 77, "files": [ { "name": "services.json", - "version": 60 + "version": 77 } ] } diff --git a/plugins/rtmp-services/data/services.json b/plugins/rtmp-services/data/services.json index 69ce728..abc9fde 100644 --- a/plugins/rtmp-services/data/services.json +++ b/plugins/rtmp-services/data/services.json @@ -5,10 +5,6 @@ "name": "Twitch", "common": true, "servers": [ - { - "name": "US West: San Francisco, CA", - "url": "rtmp://live.twitch.tv/app" - }, { "name": "Asia: Hong Kong", "url": "rtmp://live-hkg.twitch.tv/app" @@ -37,10 +33,18 @@ "name": "EU: Amsterdam, NL", "url": "rtmp://live-ams.twitch.tv/app" }, + { + "name": "EU: Berlin, DE", + "url": "rtmp://live-ber.twitch.tv/app" + }, { "name": "EU: Frankfurt, DE", "url": "rtmp://live-fra.twitch.tv/app" }, + { + "name": "EU: Helsinki, FI", + "url": "rtmp://live-hel.twitch.tv/app" + }, { "name": "EU: Lisbon, Portugal", "url": "rtmp://live-lis.twitch.tv/app" @@ -53,6 +57,10 @@ "name": "EU: Madrid, Spain", "url": "rtmp://live-mad.twitch.tv/app" }, + { + "name": "EU: Marseille, FR", + "url": "rtmp://live-mrs.twitch.tv/app" + }, { "name": "EU: Milan, Italy", "url": "rtmp://live-mil.twitch.tv/app" @@ -81,6 +89,10 @@ "name": "NA: Mexico City", "url": "rtmp://live-qro.twitch.tv/app" }, + { + "name": "NA: Quebec, Canada", + "url": "rtmp://live-ymq.twitch.tv/app" + }, { "name": "NA: Toronto, Canada", "url": "rtmp://live-yto.twitch.tv/app" @@ -98,7 +110,7 @@ "url": "rtmp://live-lim.twitch.tv/app" }, { - "name": "South America: Medellin, Columbia", + "name": "South America: Medellin, Colombia", "url": "rtmp://live-mde.twitch.tv/app" }, { @@ -121,6 +133,10 @@ "name": "US Central: Houston, TX", "url": "rtmp://live-hou.twitch.tv/app" }, + { + "name": "US Central: Salt Lake City, UT", + "url": "rtmp://live-slc.twitch.tv/app" + }, { "name": "US East: Ashburn, VA", "url": "rtmp://live-iad.twitch.tv/app" @@ -149,6 +165,14 @@ "name": "US West: Phoenix, AZ", "url": "rtmp://live-phx.twitch.tv/app" }, + { + "name": "US West: Portland, Oregon", + "url": "rtmp://live-pdx.twitch.tv/app" + }, + { + "name": "US West: San Francisco, CA", + "url": "rtmp://live-sfo.twitch.tv/app" + }, { "name": "US West: San Jose, CA", "url": "rtmp://live-sjc.twitch.tv/app" @@ -273,93 +297,198 @@ } }, { - "name": "beam.pro", + "name": "Mixer.com - FTL", "common": true, "servers": [ { "name": "US: Dallas, TX", - "url": "rtmp://ingest-dal.beam.pro:1935/beam" + "url": "ingest-dal.mixer.com" }, { "name": "US: San Jose, CA", - "url": "rtmp://ingest-sjc.beam.pro:1935/beam" + "url": "ingest-sjc.mixer.com" }, { "name": "US: Seattle, WA", - "url": "rtmp://ingest-sea.beam.pro:1935/beam" + "url": "ingest-sea.mixer.com" }, { "name": "US: Washington DC", - "url": "rtmp://ingest-wdc.beam.pro:1935/beam" + "url": "ingest-wdc.mixer.com" }, { "name": "Canada: Toronto", - "url": "rtmp://ingest-tor.beam.pro:1935/beam" + "url": "ingest-tor.mixer.com" }, { "name": "EU: London", - "url": "rtmp://ingest-lon.beam.pro:1935/beam" + "url": "ingest-lon.mixer.com" }, { "name": "EU: Amsterdam", - "url": "rtmp://ingest-ams.beam.pro:1935/beam" + "url": "ingest-ams.mixer.com" }, { "name": "EU: Milan", - "url": "rtmp://ingest-mil.beam.pro:1935/beam" + "url": "ingest-mil.mixer.com" }, { "name": "EU: Paris", - "url": "rtmp://ingest-par.beam.pro:1935/beam" + "url": "ingest-par.mixer.com" }, { "name": "EU: Frankfurt", - "url": "rtmp://ingest-fra.beam.pro:1935/beam" + "url": "ingest-fra.mixer.com" }, { "name": "EU: Oslo", - "url": "rtmp://ingest-osl.beam.pro:1935/beam" + "url": "ingest-osl.mixer.com" }, { "name": "Brazil: Sao Paulo", - "url": "rtmp://ingest-sao.beam.pro:1935/beam" + "url": "ingest-sao.mixer.com" }, { "name": "Australia: Melbourne", - "url": "rtmp://ingest-mel.beam.pro:1935/beam" + "url": "ingest-mel.mixer.com" }, { "name": "Australia: Sydney", - "url": "rtmp://ingest-syd.beam.pro:1935/beam" + "url": "ingest-syd.mixer.com" }, { "name": "Mexico: Mexico City", - "url": "rtmp://ingest-mex.beam.pro:1935/beam" + "url": "ingest-mex.mixer.com" }, { "name": "Asia: Hong Kong", - "url": "rtmp://ingest-hkg.beam.pro:1935/beam" + "url": "ingest-hkg.mixer.com" }, { "name": "Asia: Tokyo", - "url": "rtmp://ingest-tok.beam.pro:1935/beam" + "url": "ingest-tok.mixer.com" }, { "name": "South Korea: Seoul", - "url": "rtmp://ingest-seo.beam.pro:1935/beam" + "url": "ingest-seo.mixer.com" }, { "name": "India: Chennai", - "url": "rtmp://ingest-che.beam.pro:1935/beam" + "url": "ingest-che.mixer.com" } ], "recommended": { - "keyint": 1, + "keyint": 3, + "output": "ftl_output", + "max audio bitrate": 160, + "max video bitrate": 10000, + "profile": "main", + "bframes": 0 + } + }, + { + "name": "Mixer.com - RTMP", + "common": true, + "servers": [ + { + "name": "US: Dallas, TX", + "url": "rtmp://ingest-dal.mixer.com:1935/beam" + }, + { + "name": "US: San Jose, CA", + "url": "rtmp://ingest-sjc.mixer.com:1935/beam" + }, + { + "name": "US: Seattle, WA", + "url": "rtmp://ingest-sea.mixer.com:1935/beam" + }, + { + "name": "US: Washington DC", + "url": "rtmp://ingest-wdc.mixer.com:1935/beam" + }, + { + "name": "Canada: Toronto", + "url": "rtmp://ingest-tor.mixer.com:1935/beam" + }, + { + "name": "EU: London", + "url": "rtmp://ingest-lon.mixer.com:1935/beam" + }, + { + "name": "EU: Amsterdam", + "url": "rtmp://ingest-ams.mixer.com:1935/beam" + }, + { + "name": "EU: Milan", + "url": "rtmp://ingest-mil.mixer.com:1935/beam" + }, + { + "name": "EU: Paris", + "url": "rtmp://ingest-par.mixer.com:1935/beam" + }, + { + "name": "EU: Frankfurt", + "url": "rtmp://ingest-fra.mixer.com:1935/beam" + }, + { + "name": "EU: Oslo", + "url": "rtmp://ingest-osl.mixer.com:1935/beam" + }, + { + "name": "Brazil: Sao Paulo", + "url": "rtmp://ingest-sao.mixer.com:1935/beam" + }, + { + "name": "Australia: Melbourne", + "url": "rtmp://ingest-mel.mixer.com:1935/beam" + }, + { + "name": "Australia: Sydney", + "url": "rtmp://ingest-syd.mixer.com:1935/beam" + }, + { + "name": "Mexico: Mexico City", + "url": "rtmp://ingest-mex.mixer.com:1935/beam" + }, + { + "name": "Asia: Hong Kong", + "url": "rtmp://ingest-hkg.mixer.com:1935/beam" + }, + { + "name": "Asia: Tokyo", + "url": "rtmp://ingest-tok.mixer.com:1935/beam" + }, + { + "name": "South Korea: Seoul", + "url": "rtmp://ingest-seo.mixer.com:1935/beam" + }, + { + "name": "India: Chennai", + "url": "rtmp://ingest-che.mixer.com:1935/beam" + } + ], + "recommended": { + "keyint": 3, "max audio bitrate": 160, "max video bitrate": 10000, "profile": "main" } }, + { + "name": "Mobcrush", + "servers": [ + { + "name": "Primary", + "url": "rtmp://live.mobcrush.net/mob" + } + ], + "recommended": { + "keyint": 2, + "profile": "main", + "max video bitrate": 6000, + "max audio bitrate": 160 + } + }, { "name": "DailyMotion", "common": true, @@ -394,51 +523,6 @@ "max audio bitrate": 160 } }, - { - "name": "Switchboard Live (Joicaster)", - "servers": [ - { - "name": "Default - Performance Global", - "url": "rtmp://ingest-global-a.switchboard.zone/live" - }, - { - "name": "U.S. - Dallas, TX", - "url": "rtmp://ingest-us-dal.switchboard.zone/live" - }, - { - "name": "U.S. - Seattle, WA", - "url": "rtmp://ingest-us-sea.switchboard.zone/live" - }, - { - "name": "U.S. - Washington, DC", - "url": "rtmp://ingest-us-wdc.switchboard.zone/live" - }, - { - "name": "C.A. - Toronto, Canada", - "url": "rtmp://ingest-cn-tor.switchboard.zone/live" - }, - { - "name": "E.U. - London, England", - "url": "rtmp://ingest-eu-lon.switchboard.zone/live" - }, - { - "name": "E.U. - Amsterdam, Netherlands", - "url": "rtmp://ingest-as-tok.switchboard.zone/live" - }, - { - "name": "A.S. - Tokyo, Japan", - "url": "rtmp://ingest-as-tok.switchboard.zone/live" - }, - { - "name": "A.U. - Melbourne, Australia", - "url": "rtmp://ingest-au-mel.switchboard.zone/live" - }, - { - "name": "S.A - São Paulo, Brazil", - "url": "rtmp://ingest-sa-sao.switchboard.zone/live" - } - ] - }, { "name": "GoodGame.ru", "servers": [ @@ -473,8 +557,8 @@ "url": "rtmp://live-den.vaughnsoft.net/live" }, { - "name": "US: Los Angeles, CA", - "url": "rtmp://live-lax.vaughnsoft.net/live" + "name": "US: New York, NY", + "url": "rtmp://live-nyc.vaughnsoft.net/live" }, { "name": "EU: Amsterdam, NL", @@ -503,8 +587,8 @@ "url": "rtmp://live-den.vaughnsoft.net/live" }, { - "name": "US: Los Angeles, CA", - "url": "rtmp://live-lax.vaughnsoft.net/live" + "name": "US: New York, NY", + "url": "rtmp://live-nyc.vaughnsoft.net/live" }, { "name": "EU: Amsterdam, NL", @@ -573,6 +657,10 @@ "name": "EU-West (Amsterdam, NL)", "url": "rtmp://eu-ams.restream.io/live" }, + { + "name": "EU-West (Luxembourg)", + "url": "rtmp://eu-luxembourg.restream.io/live" + }, { "name": "EU-Central (Frankfurt, DE)", "url": "rtmp://eu-central.restream.io/live" @@ -581,6 +669,10 @@ "name": "EU-East (Falkenstein, DE)", "url": "rtmp://eu-east.restream.io/live" }, + { + "name": "EU-South (Madrid, Spain)", + "url": "rtmp://eu-madrid.restream.io/live" + }, { "name": "Russia (Moscow)", "url": "rtmp://ru.restream.io/live" @@ -601,6 +693,10 @@ "name": "US-East (Washington, DC)", "url": "rtmp://us-east.restream.io/live" }, + { + "name": "US-East (Miami, FL)", + "url": "rtmp://us-miami.restream.io/live" + }, { "name": "NA-East (Toronto, Canada)", "url": "rtmp://na-toronto.restream.io/live" @@ -689,18 +785,6 @@ "max audio bitrate": 160 } }, - { - "name": "Coderwall", - "servers": [ - { - "name": "Primary", - "url": "rtmp://live.coderwall.com/coderwall" - } - ], - "recommended": { - "max video bitrate": 1500 - } - }, { "name": "Meridix Live Sports Platform", "servers": [ @@ -812,15 +896,22 @@ "name": "Picarto", "servers": [ { - "name": "USA/Canada", - "url": "rtmp://live.us.picarto.tv/golive" + "name": "US East (Chicago, USA)", + "url": "rtmp://live.us-east1.picarto.tv/golive" + }, + { + "name": "US West (Los Angeles, USA)", + "url": "rtmp://live.us-west1.picarto.tv/golive" + }, + { + "name": "EU West (Düsseldorf, Germany)", + "url": "rtmp://live.eu-west1.picarto.tv/golive" } ], "recommended": { "keyint": 2, "profile": "main", - "max video bitrate": 3500, - "max audio bitrate": 128 + "max video bitrate": 3500 } }, { @@ -833,13 +924,244 @@ ] }, { - "name": "LiveStream", + "name": "Livestream", "servers": [ { "name": "Primary", "url": "rtmp://rtmpin.livestreamingest.com/rtmpin" } ] + }, + { + "name": "Chaturbate", + "servers": [ + { + "name": "Default Global Auto Select - Recommended", + "url": "rtmp://live.stream.highwebmedia.com/live-origin" + }, + { + "name": "US West", + "url": "rtmp://live-us-west.stream.highwebmedia.com/live-origin" + }, + { + "name": "US Central", + "url": "rtmp://live-us-central.stream.highwebmedia.com/live-origin" + }, + { + "name": "US East", + "url": "rtmp://live-us-east.stream.highwebmedia.com/live-origin" + }, + { + "name": "Europe West", + "url": "rtmp://live-eu-west.stream.highwebmedia.com/live-origin" + }, + { + "name": "Europe East", + "url": "rtmp://live-eu-east.stream.highwebmedia.com/live-origin" + }, + { + "name": "Asia/Pacific South", + "url": "rtmp://live-as-south.stream.highwebmedia.com/live-origin" + }, + { + "name": "Asia/Pacific North-East", + "url": "rtmp://live-as-northeast.stream.highwebmedia.com/live-origin" + } + ], + "recommended": { + "keyint": 2, + "max video bitrate": 20000, + "max audio bitrate": 192 + } + }, + { + "name": "LiveEdu.tv", + "common": true, + "servers": [ + { + "name": "US", + "url": "rtmp://usmedia11.liveedu.tv/liveedutv" + }, + { + "name": "EU", + "url": "rtmp://eumedia8.liveedu.tv/liveedutv" + }, + { + "name": "Asia", + "url": "rtmp://apmedia1.liveedu.tv/liveedutv" + } + ] + }, + { + "name": "Twitter / Periscope", + "common": true, + "servers": [ + { + "name": "US West: California", + "url": "rtmp://ca.pscp.tv:80/x" + }, + { + "name": "US West: Oregon", + "url": "rtmp://or.pscp.tv:80/x" + }, + { + "name": "US East: Virginia", + "url": "rtmp://va.pscp.tv:80/x" + }, + { + "name": "South America: Brazil", + "url": "rtmp://br.pscp.tv:80/x" + }, + { + "name": "EU West: Ireland", + "url": "rtmp://ie.pscp.tv:80/x" + }, + { + "name": "EU Central: Germany", + "url": "rtmp://de.pscp.tv:80/x" + }, + { + "name": "Asia/Pacific: Australia", + "url": "rtmp://au.pscp.tv:80/x" + }, + { + "name": "Asia/Pacific: Japan", + "url": "rtmp://jp.pscp.tv:80/x" + }, + { + "name": "Asia/Pacific: Singapore", + "url": "rtmp://sg.pscp.tv:80/x" + } + ], + "recommended": { + "keyint": 2, + "max video bitrate": 800, + "max audio bitrate": 96 + } + }, + { + "name": "Switchboard Live (Joicaster)", + "servers": [ + { + "name": "Default - Performance Global", + "url": "rtmp://ingest-global-a.switchboard.zone/live" + }, + { + "name": "US West", + "url": "rtmp://ingest-us-west.a.switchboard.zone/live" + }, + { + "name": "US East", + "url": "rtmp://ingest-us-east.a.switchboard.zone/live" + }, + { + "name": "Europe West", + "url": "rtmp://ingest-eu-west.a.switchboard.zone/live" + }, + { + "name": "Europe Central", + "url": "rtmp://ingest-us-east.a.switchboard.zone/live" + }, + { + "name": "Australia East", + "url": "rtmp://ingest-au-east.a.switchboard.zone/live" + }, + { + "name": "Asia Central", + "url": "rtmp://ingest-as-central.a.switchboard.zone/live" + } + ] + }, + { + "name": "Looch", + "common": false, + "servers": [ + { + "name": "Primary Looch ingest server", + "url": "rtmp://ingest.looch.tv/live" + } + ], + "recommended": { + "keyint": 2, + "profile": "main", + "max video bitrate": 6000, + "max audio bitrate": 160 + } + }, + { + "name": "Stream.me", + "common": false, + "servers": [ + { + "name": "US, Central", + "url": "rtmp://uc-origin.stream.me/origin" + }, + { + "name": "US, East", + "url": "rtmp://ue-origin.stream.me/origin" + }, + { + "name": "US, West", + "url": "rtmp://uw-origin.stream.me/origin" + }, + { + "name": "Europe, West", + "url": "rtmp://ew-origin.stream.me/origin" + }, + { + "name": "Asia, East", + "url": "rtmp://ae-origin.stream.me/origin" + } + ], + "recommended": { + "keyint": 2, + "profile": "main", + "max video bitrate": 20000, + "max audio bitrate": 192 + } + }, + { + "name": "Eventials", + "servers": [ + { + "name": "Default", + "url": "rtmp://live.eventials.com/eventialsLiveOrigin" + } + ], + "recommended": { + "keyint": 1, + "profile": "baseline", + "max video bitrate": 900, + "max audio bitrate": 192 + } + }, + { + "name": "Lahzenegar - لحظه نگار", + "servers": [ + { + "name": "Primary", + "url": "rtmp://live.lahzenegar.com:80/pro" + } + ], + "recommended": { + "max video bitrate": 1000, + "max audio bitrate": 96 + } + }, + { + "name": "MyLive", + "servers": [ + { + "name": "Default", + "url": "rtmp://stream.mylive.in.th/live" + } + ], + "recommended": { + "keyint": 2, + "profile": "main", + "max video bitrate": 7000, + "max audio bitrate": 192 + } } ] } diff --git a/plugins/rtmp-services/lookup-config.h.in b/plugins/rtmp-services/lookup-config.h.in index 94f7c99..d1cb6ed 100644 --- a/plugins/rtmp-services/lookup-config.h.in +++ b/plugins/rtmp-services/lookup-config.h.in @@ -1,3 +1,20 @@ #pragma once +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef ON +#define ON 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef OFF +#define OFF 0 +#endif + #define RTMP_SERVICES_URL "@RTMP_SERVICES_URL@" +#define CHECK_FOR_SERVICE_UPDATES @CHECK_FOR_SERVICE_UPDATES@ diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index 5194345..9079e07 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -4,11 +4,14 @@ #include #include "rtmp-format-ver.h" +#include "twitch.h" struct rtmp_common { char *service; char *server; char *key; + + char *output; }; static const char *rtmp_common_getname(void *unused) @@ -17,17 +20,42 @@ static const char *rtmp_common_getname(void *unused) return obs_module_text("StreamingServices"); } +static json_t *open_services_file(void); +static inline json_t *find_service(json_t *root, const char *name); +static inline const char *get_string_val(json_t *service, const char *key); + +extern void twitch_ingests_refresh(int seconds); + static void rtmp_common_update(void *data, obs_data_t *settings) { struct rtmp_common *service = data; bfree(service->service); bfree(service->server); + bfree(service->output); bfree(service->key); service->service = bstrdup(obs_data_get_string(settings, "service")); service->server = bstrdup(obs_data_get_string(settings, "server")); service->key = bstrdup(obs_data_get_string(settings, "key")); + service->output = NULL; + + json_t *root = open_services_file(); + if (root) { + json_t *serv = find_service(root, service->service); + if (serv) { + json_t *rec = json_object_get(serv, "recommended"); + if (rec && json_is_object(rec)) { + const char *out = get_string_val(rec, "output"); + if (out) + service->output = bstrdup(out); + } + } + } + json_decref(root); + + if (!service->output) + service->output = bstrdup("rtmp_output"); } static void rtmp_common_destroy(void *data) @@ -36,6 +64,7 @@ static void rtmp_common_destroy(void *data) bfree(service->service); bfree(service->server); + bfree(service->output); bfree(service->key); bfree(service); } @@ -111,8 +140,6 @@ static void add_service(obs_property_t *list, json_t *service, bool show_all, obs_property_list_add_string(list, name, name); } -static inline json_t *find_service(json_t *root, const char *name); - static void add_services(obs_property_t *list, json_t *root, bool show_all, const char *cur_service) { @@ -161,9 +188,9 @@ static json_t *open_json_file(const char *file) format_ver = get_int_val(root, "format_version"); if (format_ver != RTMP_SERVICES_FORMAT_VERSION) { - blog(LOG_WARNING, "rtmp-common.c: [open_json_file] " - "Wrong format version (%d), expected %d", - format_ver, RTMP_SERVICES_FORMAT_VERSION); + blog(LOG_DEBUG, "rtmp-common.c: [open_json_file] " + "Wrong format version (%d), expected %d", + format_ver, RTMP_SERVICES_FORMAT_VERSION); json_decref(root); return NULL; } @@ -218,6 +245,35 @@ static void properties_data_destroy(void *data) json_decref(root); } +static bool fill_twitch_servers_locked(obs_property_t *servers_prop) +{ + size_t count = twitch_ingest_count(); + + obs_property_list_add_string(servers_prop, + obs_module_text("Server.Auto"), "auto"); + + if (count <= 1) + return false; + + for (size_t i = 0; i < count; i++) { + struct twitch_ingest ing = twitch_ingest(i); + obs_property_list_add_string(servers_prop, ing.name, ing.url); + } + + return true; +} + +static inline bool fill_twitch_servers(obs_property_t *servers_prop) +{ + bool success; + + twitch_ingests_lock(); + success = fill_twitch_servers_locked(servers_prop); + twitch_ingests_unlock(); + + return success; +} + static void fill_servers(obs_property_t *servers_prop, json_t *service, const char *name) { @@ -235,6 +291,15 @@ static void fill_servers(obs_property_t *servers_prop, json_t *service, return; } + if (strcmp(name, "Mixer.com - FTL") == 0) { + obs_property_list_add_string(servers_prop, + obs_module_text("Server.Auto"), "auto"); + } + if (name && strcmp(name, "Twitch") == 0) { + if (fill_twitch_servers(servers_prop)) + return; + } + json_array_foreach (servers, index, server) { const char *server_name = get_string_val(server, "name"); const char *url = get_string_val(server, "url"); @@ -363,6 +428,10 @@ static void apply_video_encoder_settings(obs_data_t *settings, } } + item = json_object_get(recommended, "bframes"); + if (item && json_is_integer(item)) + obs_data_set_int(settings, "bf", 0); + item = json_object_get(recommended, "x264opts"); if (item && json_is_string(item)) { const char *x264_settings = json_string_value(item); @@ -398,9 +467,10 @@ static void initialize_output(struct rtmp_common *service, json_t *root, json_t *recommended; if (!json_service) { - blog(LOG_WARNING, "rtmp-common.c: [initialize_output] " - "Could not find service '%s'", - service->service); + if (service->service && *service->service) + blog(LOG_WARNING, "rtmp-common.c: [initialize_output] " + "Could not find service '%s'", + service->service); return; } @@ -427,9 +497,30 @@ static void rtmp_common_apply_settings(void *data, } } +static const char *rtmp_common_get_output_type(void *data) +{ + struct rtmp_common *service = data; + return service->output; +} + static const char *rtmp_common_url(void *data) { struct rtmp_common *service = data; + + if (service->service && strcmp(service->service, "Twitch") == 0) { + if (service->server && strcmp(service->server, "auto") == 0) { + struct twitch_ingest ing; + + twitch_ingests_refresh(3); + + twitch_ingests_lock(); + ing = twitch_ingest(0); + twitch_ingests_unlock(); + + return ing.url; + } + } + return service->server; } @@ -449,4 +540,5 @@ struct obs_service_info rtmp_common_service = { .get_url = rtmp_common_url, .get_key = rtmp_common_key, .apply_encoder_settings = rtmp_common_apply_settings, + .get_output_type = rtmp_common_get_output_type, }; diff --git a/plugins/rtmp-services/rtmp-services-main.c b/plugins/rtmp-services/rtmp-services-main.c index f990ba5..145d5b1 100644 --- a/plugins/rtmp-services/rtmp-services-main.c +++ b/plugins/rtmp-services/rtmp-services-main.c @@ -18,6 +18,12 @@ extern struct obs_service_info rtmp_common_service; extern struct obs_service_info rtmp_custom_service; static update_info_t *update_info = NULL; +static struct dstr module_name = {0}; + +const char *get_module_name(void) +{ + return module_name.array; +} static bool confirm_service_file(void *param, struct file_download_data *file) { @@ -40,23 +46,55 @@ static bool confirm_service_file(void *param, struct file_download_data *file) return true; } +extern void init_twitch_data(void); +extern void load_twitch_data(void); +extern void unload_twitch_data(void); +extern void twitch_ingests_refresh(int seconds); + +static void refresh_callback(void *unused, calldata_t *cd) +{ + int seconds = (int)calldata_int(cd, "seconds"); + if (seconds <= 0) + seconds = 3; + if (seconds > 10) + seconds = 10; + + twitch_ingests_refresh(seconds); + + UNUSED_PARAMETER(unused); +} + bool obs_module_load(void) { + init_twitch_data(); + + dstr_copy(&module_name, "rtmp-services plugin (libobs "); + dstr_cat(&module_name, obs_get_version_string()); + dstr_cat(&module_name, ")"); + + proc_handler_t *ph = obs_get_proc_handler(); + proc_handler_add(ph, "void twitch_ingests_refresh(int seconds)", + refresh_callback, NULL); + +#if !defined(_WIN32) || CHECK_FOR_SERVICE_UPDATES char *local_dir = obs_module_file(""); char *cache_dir = obs_module_config_path(""); if (cache_dir) { update_info = update_info_create( RTMP_SERVICES_LOG_STR, - RTMP_SERVICES_VER_STR, + module_name.array, RTMP_SERVICES_URL, local_dir, cache_dir, confirm_service_file, NULL); } + load_twitch_data(); + bfree(local_dir); bfree(cache_dir); +#endif obs_register_service(&rtmp_common_service); obs_register_service(&rtmp_custom_service); @@ -66,4 +104,6 @@ bool obs_module_load(void) void obs_module_unload(void) { update_info_destroy(update_info); + unload_twitch_data(); + dstr_free(&module_name); } diff --git a/plugins/rtmp-services/twitch.c b/plugins/rtmp-services/twitch.c new file mode 100644 index 0000000..31fdd1f --- /dev/null +++ b/plugins/rtmp-services/twitch.c @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include + +#include "twitch.h" + +static update_info_t *twitch_update_info = NULL; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static bool ingests_refreshed = false; +static bool ingests_refreshing = false; +static bool ingests_loaded = false; + +struct ingest { + char *name; + char *url; +}; + +static DARRAY(struct ingest) cur_ingests; + +static void free_ingests(void) +{ + for (size_t i = 0; i < cur_ingests.num; i++) { + struct ingest *ingest = cur_ingests.array + i; + bfree(ingest->name); + bfree(ingest->url); + } + + da_free(cur_ingests); +} + +static bool load_ingests(const char *json, bool write_file) +{ + json_t *root; + json_t *ingests; + bool success = false; + char *cache_old; + char *cache_new; + size_t count; + + root = json_loads(json, 0, NULL); + if (!root) + goto finish; + + ingests = json_object_get(root, "ingests"); + if (!ingests) + goto finish; + + count = json_array_size(ingests); + if (count <= 1 && cur_ingests.num) + goto finish; + + free_ingests(); + + for (size_t i = 0; i < count; i++) { + json_t *item = json_array_get(ingests, i); + json_t *item_name = json_object_get(item, "name"); + json_t *item_url = json_object_get(item, "url_template"); + struct ingest ingest = {0}; + struct dstr url = {0}; + + if (!item_name || !item_url) + continue; + + const char *url_str = json_string_value(item_url); + const char *name_str = json_string_value(item_name); + + /* At the moment they currently mis-spell "deprecated", + * but that may change in the future, so blacklist both */ + if (strstr(name_str, "deprecated") != NULL || + strstr(name_str, "depracated") != NULL) + continue; + + dstr_copy(&url, url_str); + dstr_replace(&url, "/{stream_key}", ""); + + ingest.name = bstrdup(name_str); + ingest.url = url.array; + + da_push_back(cur_ingests, &ingest); + } + + if (!cur_ingests.num) + goto finish; + + success = true; + + if (!write_file) + goto finish; + + cache_old = obs_module_config_path("twitch_ingests.json"); + cache_new = obs_module_config_path("twitch_ingests.new.json"); + + os_quick_write_utf8_file(cache_new, json, strlen(json), false); + os_safe_replace(cache_old, cache_new, NULL); + + bfree(cache_old); + bfree(cache_new); + +finish: + if (root) + json_decref(root); + return success; +} + +static bool twitch_ingest_update(void *param, struct file_download_data *data) +{ + bool success; + + pthread_mutex_lock(&mutex); + success = load_ingests((const char *)data->buffer.array, true); + pthread_mutex_unlock(&mutex); + + if (success) { + os_atomic_set_bool(&ingests_refreshed, true); + os_atomic_set_bool(&ingests_loaded, true); + } + + UNUSED_PARAMETER(param); + return true; +} + +void twitch_ingests_lock(void) +{ + pthread_mutex_lock(&mutex); +} + +void twitch_ingests_unlock(void) +{ + pthread_mutex_unlock(&mutex); +} + +size_t twitch_ingest_count(void) +{ + return cur_ingests.num; +} + +struct twitch_ingest twitch_ingest(size_t idx) +{ + struct twitch_ingest ingest; + + if (cur_ingests.num <= idx) { + ingest.name = NULL; + ingest.url = NULL; + } else { + ingest = *(struct twitch_ingest*)(cur_ingests.array + idx); + } + + return ingest; +} + +void init_twitch_data(void) +{ + da_init(cur_ingests); + pthread_mutex_init(&mutex, NULL); +} + +extern const char *get_module_name(void); + +void twitch_ingests_refresh(int seconds) +{ + if (os_atomic_load_bool(&ingests_refreshed)) + return; + + if (!os_atomic_load_bool(&ingests_refreshing)) { + os_atomic_set_bool(&ingests_refreshing, true); + + twitch_update_info = update_info_create_single( + "[twitch ingest update] ", + get_module_name(), + "https://ingest.twitch.tv/api/v2/ingests", + twitch_ingest_update, NULL); + } + + /* wait five seconds max when loading ingests for the first time */ + if (!os_atomic_load_bool(&ingests_loaded)) { + for (int i = 0; i < seconds * 100; i++) { + if (os_atomic_load_bool(&ingests_refreshed)) { + break; + } + os_sleep_ms(10); + } + } +} + +void load_twitch_data(void) +{ + char *twitch_cache = obs_module_config_path("twitch_ingests.json"); + + struct ingest def = { + .name = bstrdup("Default"), + .url = bstrdup("rtmp://live.twitch.tv/app") + }; + + pthread_mutex_lock(&mutex); + da_push_back(cur_ingests, &def); + pthread_mutex_unlock(&mutex); + + if (os_file_exists(twitch_cache)) { + char *data = os_quick_read_utf8_file(twitch_cache); + bool success; + + pthread_mutex_lock(&mutex); + success = load_ingests(data, false); + pthread_mutex_unlock(&mutex); + + if (success) { + os_atomic_set_bool(&ingests_loaded, true); + } + + bfree(data); + } + + bfree(twitch_cache); +} + +void unload_twitch_data(void) +{ + update_info_destroy(twitch_update_info); + free_ingests(); + pthread_mutex_destroy(&mutex); +} diff --git a/plugins/rtmp-services/twitch.h b/plugins/rtmp-services/twitch.h new file mode 100644 index 0000000..6caf049 --- /dev/null +++ b/plugins/rtmp-services/twitch.h @@ -0,0 +1,11 @@ +#pragma once + +struct twitch_ingest { + const char *name; + const char *url; +}; + +extern void twitch_ingests_lock(void); +extern void twitch_ingests_unlock(void); +extern size_t twitch_ingest_count(void); +extern struct twitch_ingest twitch_ingest(size_t idx); diff --git a/plugins/text-freetype2/data/locale/el-GR.ini b/plugins/text-freetype2/data/locale/el-GR.ini index e1cca3c..6b9ade9 100644 --- a/plugins/text-freetype2/data/locale/el-GR.ini +++ b/plugins/text-freetype2/data/locale/el-GR.ini @@ -1,3 +1,4 @@ +TextFreetype2="Κείμενο (FreeType 2)" Font="Γραμματοσειρά" Text="Κείμενο" TextFile="Αρχείο κειμένου (UTF-8 ή UTF-16)" diff --git a/plugins/text-freetype2/data/locale/ko-KR.ini b/plugins/text-freetype2/data/locale/ko-KR.ini index df029a3..b52e35a 100644 --- a/plugins/text-freetype2/data/locale/ko-KR.ini +++ b/plugins/text-freetype2/data/locale/ko-KR.ini @@ -3,7 +3,7 @@ Font="글꼴" Text="텍스트" TextFile="텍스트 파일(UTF-8 혹은 UTF-16)" TextFileFilter="텍스트 파일 (*.txt);;" -ChatLogMode="대화 기록 모드 (최근 여섯 줄만 표시)" +ChatLogMode="대화 기록 시작 (최근 여섯 줄만 표시)" Color1="색상 1" Color2="색상 2" Outline="외곽선" diff --git a/plugins/text-freetype2/data/locale/vi-VN.ini b/plugins/text-freetype2/data/locale/vi-VN.ini new file mode 100644 index 0000000..40f2568 --- /dev/null +++ b/plugins/text-freetype2/data/locale/vi-VN.ini @@ -0,0 +1,14 @@ +TextFreetype2="Văn bản (FreeType 2)" +Font="Phông chữ" +Text="Văn bản" +TextFile="Tệp văn bản (UTF-8 hoặc UTF-16)" +TextFileFilter="Tệp văn bản (*.txt);;" +ChatLogMode="Chế độ bản ghi chat (cuối 6 dòng)" +Color1="Màu 1" +Color2="Màu 2" +Outline="Phác thảo" +DropShadow="Đổ bóng" +ReadFromFile="Đọc từ tệp" +CustomWidth="Tùy chỉnh độ rộng ký tự" +WordWrap="Bọc từ" + diff --git a/plugins/text-freetype2/text-freetype2.c b/plugins/text-freetype2/text-freetype2.c index 849d230..6fd8571 100644 --- a/plugins/text-freetype2/text-freetype2.c +++ b/plugins/text-freetype2/text-freetype2.c @@ -238,7 +238,7 @@ static void ft2_video_tick(void *data, float seconds) time_t t = get_modified_timestamp(srcdata->text_file); srcdata->last_checked = os_gettime_ns(); - if (srcdata->m_timestamp != t) { + if (srcdata->update_file) { if (srcdata->log_mode) read_from_end(srcdata, srcdata->text_file); else @@ -246,6 +246,12 @@ static void ft2_video_tick(void *data, float seconds) srcdata->text_file); cache_glyphs(srcdata, srcdata->text); set_up_vertex_buffer(srcdata); + srcdata->update_file = false; + } + + if (srcdata->m_timestamp != t) { + srcdata->m_timestamp = t; + srcdata->update_file = true; } } diff --git a/plugins/text-freetype2/text-freetype2.h b/plugins/text-freetype2/text-freetype2.h index dfe58a8..df2dee1 100644 --- a/plugins/text-freetype2/text-freetype2.h +++ b/plugins/text-freetype2/text-freetype2.h @@ -38,6 +38,7 @@ struct ft2_source { char *text_file; wchar_t *text; time_t m_timestamp; + bool update_file; uint64_t last_checked; uint32_t cx, cy, max_h, custom_width; diff --git a/plugins/text-freetype2/text-functionality.c b/plugins/text-freetype2/text-functionality.c index c92f4b3..0755c27 100644 --- a/plugins/text-freetype2/text-functionality.c +++ b/plugins/text-freetype2/text-functionality.c @@ -377,8 +377,6 @@ void load_text_from_file(struct ft2_source *srcdata, const char *filename) srcdata->text = bzalloc(filesize); bytes_read = fread(srcdata->text, filesize - 2, 1, tmp_file); - srcdata->m_timestamp = - get_modified_timestamp(srcdata->text_file); bfree(tmp_read); fclose(tmp_file); @@ -386,7 +384,6 @@ void load_text_from_file(struct ft2_source *srcdata, const char *filename) } fseek(tmp_file, 0, SEEK_SET); - srcdata->m_timestamp = get_modified_timestamp(srcdata->text_file); tmp_read = bzalloc(filesize + 1); bytes_read = fread(tmp_read, filesize, 1, tmp_file); @@ -464,8 +461,6 @@ void read_from_end(struct ft2_source *srcdata, const char *filename) tmp_file); remove_cr(srcdata->text); - srcdata->m_timestamp = - get_modified_timestamp(srcdata->text_file); bfree(tmp_read); fclose(tmp_file); @@ -485,7 +480,6 @@ void read_from_end(struct ft2_source *srcdata, const char *filename) srcdata->text, (strlen(tmp_read) + 1)); remove_cr(srcdata->text); - srcdata->m_timestamp = get_modified_timestamp(srcdata->text_file); bfree(tmp_read); } diff --git a/plugins/vlc-video/data/locale/ca-ES.ini b/plugins/vlc-video/data/locale/ca-ES.ini index 8722612..76247f9 100644 --- a/plugins/vlc-video/data/locale/ca-ES.ini +++ b/plugins/vlc-video/data/locale/ca-ES.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Comportament de la visibilitat" PlaybackBehavior.StopRestart="Aturar quan no sigui visible, reiniciar quan sigui visible" PlaybackBehavior.PauseUnpause="Pausa quan no sigui visible, reprendre quan sigui visible" PlaybackBehavior.AlwaysPlay="Reproduir sempre fins i tot quan no sigui visible" +NetworkCaching="Memòria cau de xarxa (ms)" +PlayPause="Reprodueix/Pausa" +Restart="Reinicia" +Stop="Atura" +PlaylistNext="Següent" +PlaylistPrev="Anterior" diff --git a/plugins/vlc-video/data/locale/cs-CZ.ini b/plugins/vlc-video/data/locale/cs-CZ.ini index f3e69c0..a7a10c7 100644 --- a/plugins/vlc-video/data/locale/cs-CZ.ini +++ b/plugins/vlc-video/data/locale/cs-CZ.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Závislost na viditelnosti" PlaybackBehavior.StopRestart="Zastavit při skrytém, restartovat při obnovení" PlaybackBehavior.PauseUnpause="Pozastavit při skrytém, pokračovat při obnovení" PlaybackBehavior.AlwaysPlay="Přehrát vždy (i když není vidět)" +NetworkCaching="Mezipaměť sítě (ms)" +PlayPause="Přehrát/Pozastavit" +Restart="Restartovat" +Stop="Zastavit" +PlaylistNext="Další" +PlaylistPrev="Předchozí" diff --git a/plugins/vlc-video/data/locale/da-DK.ini b/plugins/vlc-video/data/locale/da-DK.ini index dfa30a2..402a022 100644 --- a/plugins/vlc-video/data/locale/da-DK.ini +++ b/plugins/vlc-video/data/locale/da-DK.ini @@ -1,9 +1,15 @@ -VLCSource="VLC videokilde" +VLCSource="VLC-videokilde" Playlist="Afspilningsliste" -LoopPlaylist="Loop afspilningsliste" +LoopPlaylist="Gentag afspilningsliste" Shuffle="Bland playliste" -PlaybackBehavior="Synligheds opførsel" +PlaybackBehavior="Synlighedsadfærd" PlaybackBehavior.StopRestart="Stop når ikke synlig, genstart når synlig" -PlaybackBehavior.PauseUnpause="Paus når ikke synlig, genoptag når synlig" -PlaybackBehavior.AlwaysPlay="Afspil altid også når usynlig" +PlaybackBehavior.PauseUnpause="Pause når ikke synlig, genoptage når synlig" +PlaybackBehavior.AlwaysPlay="Afspil altid, selv når usynlig" +NetworkCaching="Netværkscaching (ms)" +PlayPause="Afspil/Pause" +Restart="Genstart" +Stop="Stop" +PlaylistNext="Næste" +PlaylistPrev="Forrige" diff --git a/plugins/vlc-video/data/locale/de-DE.ini b/plugins/vlc-video/data/locale/de-DE.ini index 51bf8c4..2834a70 100644 --- a/plugins/vlc-video/data/locale/de-DE.ini +++ b/plugins/vlc-video/data/locale/de-DE.ini @@ -3,7 +3,13 @@ Playlist="Wiedergabeliste" LoopPlaylist="Wiedergabeliste wiederholen" Shuffle="Wiedergabeliste zufällig wiedergeben" PlaybackBehavior="Sichtbarkeitsverhalten" -PlaybackBehavior.StopRestart="Anhalten wenn nicht sichtbar, neustarten wenn sichtbar" +PlaybackBehavior.StopRestart="Anhalten wenn nicht sichtbar, neu starten wenn sichtbar" PlaybackBehavior.PauseUnpause="Pausieren wenn nicht sichtbar, fortsetzen wenn sichtbar" PlaybackBehavior.AlwaysPlay="Immer abspielen, auch wenn nicht sichtbar" +NetworkCaching="Netzwerkpuffer (ms)" +PlayPause="Abspielen/Pausieren" +Restart="Neu starten" +Stop="Stop" +PlaylistNext="Weiter" +PlaylistPrev="Zurück" diff --git a/plugins/vlc-video/data/locale/el-GR.ini b/plugins/vlc-video/data/locale/el-GR.ini new file mode 100644 index 0000000..1bff90e --- /dev/null +++ b/plugins/vlc-video/data/locale/el-GR.ini @@ -0,0 +1,15 @@ +VLCSource="Πηγή βίντεο VLC" +Playlist="Λίστα αναπαραγωγής" +LoopPlaylist="Επανάληψη λίστας αναπαραγωγής" +Shuffle="Τυχαία αναπαραγωγή λίστας αναπαραγωγής" +PlaybackBehavior="Συμπεριφορά Ορατότητας" +PlaybackBehavior.StopRestart="Διακοπή όταν δεν είναι ορατή, επανεκκίνηση όταν είναι ορατή" +PlaybackBehavior.PauseUnpause="Παύση όταν δεν είναι ορατή, συνέχεια όταν είναι ορατή" +PlaybackBehavior.AlwaysPlay="Αναπαραγωγή πάντα, ακόμα και αν δεν είναι ορατή" +NetworkCaching="Προσωρινή αποθήκευση Δικτίου (ms)" +PlayPause="Αναπαραγωγή/Παύση" +Restart="Επανεκκίνηση" +Stop="Διακοπή" +PlaylistNext="Επόμενο" +PlaylistPrev="Προηγούμενο" + diff --git a/plugins/vlc-video/data/locale/en-US.ini b/plugins/vlc-video/data/locale/en-US.ini index 5846fe4..7bf2004 100644 --- a/plugins/vlc-video/data/locale/en-US.ini +++ b/plugins/vlc-video/data/locale/en-US.ini @@ -6,3 +6,9 @@ PlaybackBehavior="Visibility behavior" PlaybackBehavior.StopRestart="Stop when not visible, restart when visible" PlaybackBehavior.PauseUnpause="Pause when not visible, unpause when visible" PlaybackBehavior.AlwaysPlay="Always play even when not visible" +NetworkCaching="Network Caching (ms)" +PlayPause="Play/Pause" +Restart="Restart" +Stop="Stop" +PlaylistNext="Next" +PlaylistPrev="Previous" diff --git a/plugins/vlc-video/data/locale/es-ES.ini b/plugins/vlc-video/data/locale/es-ES.ini index 7449a94..8ff5597 100644 --- a/plugins/vlc-video/data/locale/es-ES.ini +++ b/plugins/vlc-video/data/locale/es-ES.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Comportamiento de la visibilidad" PlaybackBehavior.StopRestart="Detener cuando no sea visible, reiniciar cuando sea visible" PlaybackBehavior.PauseUnpause="Pausar cuando no sea visible, reanudar cuando sea visible" PlaybackBehavior.AlwaysPlay="Reproducir siempre incluso cuando no sea visible" +NetworkCaching="Caché de Red (ms)" +PlayPause="Reproducir/Pausar" +Restart="Reiniciar" +Stop="Detener" +PlaylistNext="Siguiente" +PlaylistPrev="Anterior" diff --git a/plugins/vlc-video/data/locale/eu-ES.ini b/plugins/vlc-video/data/locale/eu-ES.ini index 0e73630..5ba5fdb 100644 --- a/plugins/vlc-video/data/locale/eu-ES.ini +++ b/plugins/vlc-video/data/locale/eu-ES.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Ikuste-jokabidea" PlaybackBehavior.StopRestart="Ikusten ez bada gelditu, ikusten denean berrabiarazi" PlaybackBehavior.PauseUnpause="Ikusten ez bada pausatu, ikusten denean jarraitu" PlaybackBehavior.AlwaysPlay="Erreproduzitu beti nahiz eta ez ikusi" +NetworkCaching="Sarearen katxea (ms)" +PlayPause=" Erreproduzitu/Pausarazi" +Restart="Berrabiarazi" +Stop="Gelditu" +PlaylistNext="Hurrengoa" +PlaylistPrev="Aurrekoa" diff --git a/plugins/vlc-video/data/locale/fi-FI.ini b/plugins/vlc-video/data/locale/fi-FI.ini index b54bdcf..869f774 100644 --- a/plugins/vlc-video/data/locale/fi-FI.ini +++ b/plugins/vlc-video/data/locale/fi-FI.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Näkyvyyden käyttäytyminen" PlaybackBehavior.StopRestart="Pysäytä toisto kun lähde ei näy. Käynnistä toisto uudelleen, kun se on taas näkyvissä" PlaybackBehavior.PauseUnpause="Keskeytä toisto kun lähde ei näy. Jatka toistoa, kun se on taas näkyvissä" PlaybackBehavior.AlwaysPlay="Toista aina, vaikka lähde ei olisi näkyvissä" +NetworkCaching="Verkon välimuisti (ms)" +PlayPause="Toista/Tauko" +Restart="Aloita alusta" +Stop="Pysäytä" +PlaylistNext="Seuraava" +PlaylistPrev="Edellinen" diff --git a/plugins/vlc-video/data/locale/fr-FR.ini b/plugins/vlc-video/data/locale/fr-FR.ini index ca097ce..d5b6eea 100644 --- a/plugins/vlc-video/data/locale/fr-FR.ini +++ b/plugins/vlc-video/data/locale/fr-FR.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Comportement de visibilité" PlaybackBehavior.StopRestart="Arrêter quand elle n'est pas visible, redémarrer lorsqu'elle est visible" PlaybackBehavior.PauseUnpause="Suspendre lorsqu'elle n'est pas visible, reprendre lorsqu'elle est visible" PlaybackBehavior.AlwaysPlay="Toujours jouer même lorsqu'elle n'est pas visible" +NetworkCaching="Mise en cache réseau (ms)" +PlayPause="Lecture/Pause" +Restart="Redémarrer" +Stop="Arrêter" +PlaylistNext="Suivant" +PlaylistPrev="Précédent" diff --git a/plugins/vlc-video/data/locale/hu-HU.ini b/plugins/vlc-video/data/locale/hu-HU.ini index cb96564..1fa1ffe 100644 --- a/plugins/vlc-video/data/locale/hu-HU.ini +++ b/plugins/vlc-video/data/locale/hu-HU.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Láthatósági opció" PlaybackBehavior.StopRestart="Leállítás, ha nem látható, újraindul, ha látható" PlaybackBehavior.PauseUnpause="Szüneteltet, ha nem látható, folytatás, ha látható" PlaybackBehavior.AlwaysPlay="Mindig lejátsza, akkor is, ha nem látható" +NetworkCaching="Hálózati cache (ezredmásodperc)" +PlayPause="Lejátszás/Szünet" +Restart="Újraindítás" +Stop="Leállítás" +PlaylistNext="Következő" +PlaylistPrev="Előző" diff --git a/plugins/vlc-video/data/locale/it-IT.ini b/plugins/vlc-video/data/locale/it-IT.ini index 77bfc93..5a8e1cc 100644 --- a/plugins/vlc-video/data/locale/it-IT.ini +++ b/plugins/vlc-video/data/locale/it-IT.ini @@ -1,8 +1,15 @@ VLCSource="Source Video VLC" Playlist="Playlist" LoopPlaylist="Riproduci playlist di continuo" +Shuffle="Playlist casuale" PlaybackBehavior="Comportamento visibilità" PlaybackBehavior.StopRestart="Interrompi quando non visibile, riavvia quando visibile" PlaybackBehavior.PauseUnpause="Pausa quando non visibile, riprendi quando visibile" PlaybackBehavior.AlwaysPlay="Continua anche quando non visibile" +NetworkCaching="Network Caching (ms)" +PlayPause="Play/Pausa" +Restart="Riavvia" +Stop="Interrompi" +PlaylistNext="Successivo" +PlaylistPrev="Precedente" diff --git a/plugins/vlc-video/data/locale/ja-JP.ini b/plugins/vlc-video/data/locale/ja-JP.ini index ef8ac5b..0812205 100644 --- a/plugins/vlc-video/data/locale/ja-JP.ini +++ b/plugins/vlc-video/data/locale/ja-JP.ini @@ -6,4 +6,10 @@ PlaybackBehavior="表示の動作" PlaybackBehavior.StopRestart="表示されていないときに停止、表示時に再開" PlaybackBehavior.PauseUnpause="表示されていないときに一時停止、表示時に一時停止を解除" PlaybackBehavior.AlwaysPlay="表示されていないときにも常に再生" +NetworkCaching="ネットワークキャッシュ (ms)" +PlayPause="再生/一時停止" +Restart="再開" +Stop="停止" +PlaylistNext="次へ" +PlaylistPrev="前へ" diff --git a/plugins/vlc-video/data/locale/ko-KR.ini b/plugins/vlc-video/data/locale/ko-KR.ini index df7838f..20d85e0 100644 --- a/plugins/vlc-video/data/locale/ko-KR.ini +++ b/plugins/vlc-video/data/locale/ko-KR.ini @@ -6,4 +6,10 @@ PlaybackBehavior="표시 동작 설정" PlaybackBehavior.StopRestart="보이지 않을 때 중단, 보이면 재시작" PlaybackBehavior.PauseUnpause="보이지 않을 때 일시 중지, 보이면 일시 중지 해제" PlaybackBehavior.AlwaysPlay="보이지 않더라도 항상 재생" +NetworkCaching="네트워크 캐싱 (ms)" +PlayPause="재생/일시정지" +Restart="재시작" +Stop="중단" +PlaylistNext="다음" +PlaylistPrev="이전" diff --git a/plugins/vlc-video/data/locale/nb-NO.ini b/plugins/vlc-video/data/locale/nb-NO.ini index 587d99c..59c3280 100644 --- a/plugins/vlc-video/data/locale/nb-NO.ini +++ b/plugins/vlc-video/data/locale/nb-NO.ini @@ -1,8 +1,15 @@ VLCSource="VLC-videokilde" Playlist="Spilleliste" LoopPlaylist="Repeter spilleliste" +Shuffle="Shuffle spilleliste" PlaybackBehavior="Synlighetsadferd" PlaybackBehavior.StopRestart="Stans når ikke synlig, spill fra starten når synlig" PlaybackBehavior.PauseUnpause="Stans når ikke synlig, spill når synlig" PlaybackBehavior.AlwaysPlay="Spill alltid, selv når ikke synlig" +NetworkCaching="Nettverks Buffering (ms)" +PlayPause="Spill av/Pause" +Restart="Start på nytt" +Stop="Stopp avspilling" +PlaylistNext="Neste" +PlaylistPrev="Forrige" diff --git a/plugins/vlc-video/data/locale/nl-NL.ini b/plugins/vlc-video/data/locale/nl-NL.ini index 284f1d3..1516ac0 100644 --- a/plugins/vlc-video/data/locale/nl-NL.ini +++ b/plugins/vlc-video/data/locale/nl-NL.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Zichtbaarheidsgedrag" PlaybackBehavior.StopRestart="Stop wanneer niet zichtbaar, herstart wanneer zichtbaar" PlaybackBehavior.PauseUnpause="Pauzeer wanneer niet zichtbaar, hervat wanneer zichtbaar" PlaybackBehavior.AlwaysPlay="Altijd spelen, zelfs wanneer niet zichtbaar" +NetworkCaching="Network Caching (ms)" +PlayPause="Speel af/Pauzeer" +Restart="Herstart" +Stop="Stop" +PlaylistNext="Volgende" +PlaylistPrev="Vorige" diff --git a/plugins/vlc-video/data/locale/pl-PL.ini b/plugins/vlc-video/data/locale/pl-PL.ini index 404e825..60f8922 100644 --- a/plugins/vlc-video/data/locale/pl-PL.ini +++ b/plugins/vlc-video/data/locale/pl-PL.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Zachowanie" PlaybackBehavior.StopRestart="Zatrzymaj, gdy niewidoczne. Odtwarzaj od początku, gdy widoczne." PlaybackBehavior.PauseUnpause="Wstrzymaj, gdy niewidoczne. Wznów, gdy widoczne." PlaybackBehavior.AlwaysPlay="Odtwarzaj cały czas bez względu na widoczność." +NetworkCaching="Pamięć podręczna dla sieci (ms)" +PlayPause="Odtwarzaj/Wstrzymaj" +Restart="Zrestartuj" +Stop="Zatrzymaj" +PlaylistNext="Następny" +PlaylistPrev="Poprzedni" diff --git a/plugins/vlc-video/data/locale/pt-BR.ini b/plugins/vlc-video/data/locale/pt-BR.ini index d93756d..8cdc6eb 100644 --- a/plugins/vlc-video/data/locale/pt-BR.ini +++ b/plugins/vlc-video/data/locale/pt-BR.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Comportamento de visibilidade" PlaybackBehavior.StopRestart="Parar quando não visível, reiniciar quando visível" PlaybackBehavior.PauseUnpause="Pausa quando não visível, resumir quando visível" PlaybackBehavior.AlwaysPlay="Sempre reproduzir, mesmo quando não visível" +NetworkCaching="Cache de Rede (ms)" +PlayPause="Reproduzir/Pausar" +Restart="Reiniciar" +Stop="Parar" +PlaylistNext="Próximo" +PlaylistPrev="Anterior" diff --git a/plugins/vlc-video/data/locale/ru-RU.ini b/plugins/vlc-video/data/locale/ru-RU.ini index 588153a..a6c6b52 100644 --- a/plugins/vlc-video/data/locale/ru-RU.ini +++ b/plugins/vlc-video/data/locale/ru-RU.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Поведение видимости" PlaybackBehavior.StopRestart="Остановить, когда не видно, перезагрузить, когда видно" PlaybackBehavior.PauseUnpause="Пауза, когда не видно, возобновить, когда видно" PlaybackBehavior.AlwaysPlay="Всегда играть, даже когда не видно" +NetworkCaching="Сетевое кэширование (мс)" +PlayPause="Воспроизвести/Пауза" +Restart="Перезапустить" +Stop="Остановить" +PlaylistNext="Следующий" +PlaylistPrev="Предыдущий" diff --git a/plugins/vlc-video/data/locale/sk-SK.ini b/plugins/vlc-video/data/locale/sk-SK.ini index 163e36b..516690b 100644 --- a/plugins/vlc-video/data/locale/sk-SK.ini +++ b/plugins/vlc-video/data/locale/sk-SK.ini @@ -1,2 +1,15 @@ +VLCSource="VLC Video zdroj" Playlist="Playlist" +LoopPlaylist="Opakovať zoznam skladieb" +Shuffle="Prehrať zoznam skladieb náhodne" +PlaybackBehavior="Fungovanie podľa viditeľnosti" +PlaybackBehavior.StopRestart="Zastaviť, keď nie je viditeľná, reštartovať, keď je viditeľná" +PlaybackBehavior.PauseUnpause="Pozastaviť, keď nie je viditeľná, zrušiť pozastavenie, keď je viditeľná" +PlaybackBehavior.AlwaysPlay="Vždy prehrávať, aj keď nie je viditeľná" +NetworkCaching="Medzi-pamäť siete (ms)" +PlayPause="Prehrať/Pozastaviť" +Restart="Reštartovať" +Stop="Zastaviť" +PlaylistNext="Ďalší" +PlaylistPrev="Predchádzajúci" diff --git a/plugins/vlc-video/data/locale/sv-SE.ini b/plugins/vlc-video/data/locale/sv-SE.ini index d3b333c..3659b6d 100644 --- a/plugins/vlc-video/data/locale/sv-SE.ini +++ b/plugins/vlc-video/data/locale/sv-SE.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Synlighetsbeteende" PlaybackBehavior.StopRestart="Stoppa när den inte syns, starta om när den syns" PlaybackBehavior.PauseUnpause="Pausa när den inte syns, återuppta när den syns" PlaybackBehavior.AlwaysPlay="Spela alltid även när den inte syns" +NetworkCaching="Närverkscaching (ms)" +PlayPause="Spela/Pausa" +Restart="Starta om" +Stop="Stoppa" +PlaylistNext="Nästa" +PlaylistPrev="Föregående" diff --git a/plugins/vlc-video/data/locale/tr-TR.ini b/plugins/vlc-video/data/locale/tr-TR.ini index b72d8b3..6613990 100644 --- a/plugins/vlc-video/data/locale/tr-TR.ini +++ b/plugins/vlc-video/data/locale/tr-TR.ini @@ -6,4 +6,10 @@ PlaybackBehavior="Görünürlük davranışı" PlaybackBehavior.StopRestart="Görünür değilken durdur, görünür olunca yeniden başlat" PlaybackBehavior.PauseUnpause="Görünür değilken duraklat, görünür olunca oynat" PlaybackBehavior.AlwaysPlay="Görünür değilken bile oynat" +NetworkCaching="Ağ Önbelleğe Alma (ms)" +PlayPause="Oynat/Duraklat" +Restart="Yeniden Başlat" +Stop="Durdur" +PlaylistNext="Sonraki" +PlaylistPrev="Önceki" diff --git a/plugins/vlc-video/data/locale/uk-UA.ini b/plugins/vlc-video/data/locale/uk-UA.ini index 7bbca4e..0ee636f 100644 --- a/plugins/vlc-video/data/locale/uk-UA.ini +++ b/plugins/vlc-video/data/locale/uk-UA.ini @@ -1,9 +1,15 @@ VLCSource="VLC-відео" Playlist="Список відтворення" -LoopPlaylist="Повторювати список відтворювання" +LoopPlaylist="Повтор списку відтворення" Shuffle="Перемішати список відтворення" PlaybackBehavior="Видимість та відтворення" PlaybackBehavior.StopRestart="Зупинити, коли не видимий. Грати з початку, коли видимий" -PlaybackBehavior.PauseUnpause="Пизупинити, коли не видимий. Грати далі, коли видимий" +PlaybackBehavior.PauseUnpause="Призупинити, коли не видимий. Грати далі, коли видимий" PlaybackBehavior.AlwaysPlay="Завжди грати навіть тоді, коли не видимий" +NetworkCaching="Кешування мережевого контенту (мс)" +PlayPause="Відтворення / Пауза" +Restart="Грати з початку" +Stop="Зупинити" +PlaylistNext="Наступний" +PlaylistPrev="Попередній" diff --git a/plugins/vlc-video/data/locale/vi-VN.ini b/plugins/vlc-video/data/locale/vi-VN.ini new file mode 100644 index 0000000..d69b95f --- /dev/null +++ b/plugins/vlc-video/data/locale/vi-VN.ini @@ -0,0 +1,15 @@ +VLCSource="Nguồn VLC Video" +Playlist="Danh sách phát" +LoopPlaylist="Lặp lại danh sách phát" +Shuffle="Ngẫu nhiên hóa danh sách phát" +PlaybackBehavior="Hành vi hiển thị" +PlaybackBehavior.StopRestart="Dừng khi không hiển thị, khởi động lại khi hiển thị" +PlaybackBehavior.PauseUnpause="Tạm dừng khi không hiển thị, bỏ tạm dừng khi hiển thị" +PlaybackBehavior.AlwaysPlay="Luôn luôn chơi ngay cả khi không nhìn thấy được" +NetworkCaching="Lưu trữ mạng (ms)" +PlayPause="Phát/Tạm dừng" +Restart="Khởi động lại" +Stop="Dừng" +PlaylistNext="Tiếp" +PlaylistPrev="Trước" + diff --git a/plugins/vlc-video/data/locale/zh-CN.ini b/plugins/vlc-video/data/locale/zh-CN.ini index dccca5f..d033d57 100644 --- a/plugins/vlc-video/data/locale/zh-CN.ini +++ b/plugins/vlc-video/data/locale/zh-CN.ini @@ -6,4 +6,10 @@ PlaybackBehavior="可见性的行为" PlaybackBehavior.StopRestart="不可见时停止, 可见时重启" PlaybackBehavior.PauseUnpause="不可见时暂停, 可见时取消暂停" PlaybackBehavior.AlwaysPlay="即使在不可见时也保持播放" +NetworkCaching="网络缓存 (ms)" +PlayPause="播放/暂停" +Restart="重启" +Stop="停止" +PlaylistNext="下一个" +PlaylistPrev="上一个" diff --git a/plugins/vlc-video/data/locale/zh-TW.ini b/plugins/vlc-video/data/locale/zh-TW.ini index bd8ea40..c5c2664 100644 --- a/plugins/vlc-video/data/locale/zh-TW.ini +++ b/plugins/vlc-video/data/locale/zh-TW.ini @@ -6,4 +6,10 @@ PlaybackBehavior="播放行為" PlaybackBehavior.StopRestart="不可見時停止,可見時重新開始" PlaybackBehavior.PauseUnpause="不可見時暫停,可見時取消暫停" PlaybackBehavior.AlwaysPlay="即使不可見一樣播放" +NetworkCaching="網路快取 (ms)" +PlayPause="播放/暫停" +Restart="重新開始" +Stop="停止" +PlaylistNext="下一個" +PlaylistPrev="前一個" diff --git a/plugins/vlc-video/vlc-video-plugin.c b/plugins/vlc-video/vlc-video-plugin.c index 49c4e48..4160f91 100644 --- a/plugins/vlc-video/vlc-video-plugin.c +++ b/plugins/vlc-video/vlc-video-plugin.c @@ -53,6 +53,8 @@ LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_PLAYER libvlc_media_list_player_set_media_pla LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_LIST libvlc_media_list_player_set_media_list_; LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER libvlc_media_list_player_event_manager_; LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE libvlc_media_list_player_set_playback_mode_; +LIBVLC_MEDIA_LIST_PLAYER_NEXT libvlc_media_list_player_next_; +LIBVLC_MEDIA_LIST_PLAYER_PREVIOUS libvlc_media_list_player_previous_; void *libvlc_module = NULL; libvlc_instance_t *libvlc = NULL; @@ -115,6 +117,8 @@ static bool load_vlc_funcs(void) LOAD_VLC_FUNC(libvlc_media_list_player_set_media_list); LOAD_VLC_FUNC(libvlc_media_list_player_event_manager); LOAD_VLC_FUNC(libvlc_media_list_player_set_playback_mode); + LOAD_VLC_FUNC(libvlc_media_list_player_next); + LOAD_VLC_FUNC(libvlc_media_list_player_previous); return true; } diff --git a/plugins/vlc-video/vlc-video-plugin.h b/plugins/vlc-video/vlc-video-plugin.h index 53cb255..189b3f8 100644 --- a/plugins/vlc-video/vlc-video-plugin.h +++ b/plugins/vlc-video/vlc-video-plugin.h @@ -111,6 +111,10 @@ typedef libvlc_event_manager_t *(*LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER)( typedef void (*LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE)( libvlc_media_list_player_t *p_mlp, libvlc_playback_mode_t e_mode); +typedef int (*LIBVLC_MEDIA_LIST_PLAYER_NEXT)( + libvlc_media_list_player_t *p_mlp); +typedef int (*LIBVLC_MEDIA_LIST_PLAYER_PREVIOUS)( + libvlc_media_list_player_t *p_mlp); /* -------------------------------------------------------------------- */ @@ -159,6 +163,8 @@ extern LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_PLAYER libvlc_media_list_player_set_me extern LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_LIST libvlc_media_list_player_set_media_list_; extern LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER libvlc_media_list_player_event_manager_; extern LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE libvlc_media_list_player_set_playback_mode_; +extern LIBVLC_MEDIA_LIST_PLAYER_NEXT libvlc_media_list_player_next_; +extern LIBVLC_MEDIA_LIST_PLAYER_PREVIOUS libvlc_media_list_player_previous_; #define EXTENSIONS_AUDIO \ "*.3ga;" \ diff --git a/plugins/vlc-video/vlc-video-source.c b/plugins/vlc-video/vlc-video-source.c index 2fe8945..09c6da4 100644 --- a/plugins/vlc-video/vlc-video-source.c +++ b/plugins/vlc-video/vlc-video-source.c @@ -17,6 +17,7 @@ #define S_BEHAVIOR_STOP_RESTART "stop_restart" #define S_BEHAVIOR_PAUSE_UNPAUSE "pause_unpause" #define S_BEHAVIOR_ALWAYS_PLAY "always_play" +#define S_NETWORK_CACHING "network_caching" #define T_(text) obs_module_text(text) #define T_PLAYLIST T_("Playlist") @@ -26,6 +27,7 @@ #define T_BEHAVIOR_STOP_RESTART T_("PlaybackBehavior.StopRestart") #define T_BEHAVIOR_PAUSE_UNPAUSE T_("PlaybackBehavior.PauseUnpause") #define T_BEHAVIOR_ALWAYS_PLAY T_("PlaybackBehavior.AlwaysPlay") +#define T_NETWORK_CACHING T_("NetworkCaching") /* ------------------------------------------------------------------------- */ @@ -55,6 +57,12 @@ struct vlc_source { enum behavior behavior; bool loop; bool shuffle; + + obs_hotkey_id play_pause_hotkey; + obs_hotkey_id restart_hotkey; + obs_hotkey_id stop_hotkey; + obs_hotkey_id playlist_next_hotkey; + obs_hotkey_id playlist_prev_hotkey; }; static libvlc_media_t *get_media(struct darray *array, const char *path) @@ -395,7 +403,7 @@ static int vlcs_audio_setup(void **p_data, char *format, unsigned *rate, } static void add_file(struct vlc_source *c, struct darray *array, - const char *path) + const char *path, int network_caching) { DARRAY(struct media_file_data) new_files; struct media_file_data data; @@ -420,9 +428,14 @@ static void add_file(struct vlc_source *c, struct darray *array, new_media = create_media_from_file(path); if (new_media) { - if (is_url) + if (is_url) { + struct dstr network_caching_option = {0}; + dstr_catf(&network_caching_option, + ":network-caching=%d", network_caching); libvlc_media_add_option_(new_media, - ":network-caching=100"); + network_caching_option.array); + dstr_free(&network_caching_option); + } data.path = new_path.array; data.media = new_media; @@ -476,6 +489,7 @@ static void vlcs_update(void *data, obs_data_t *settings) obs_data_array_t *array; const char *behavior; size_t count; + int network_caching; da_init(new_files); da_init(old_files); @@ -487,6 +501,8 @@ static void vlcs_update(void *data, obs_data_t *settings) behavior = obs_data_get_string(settings, S_BEHAVIOR); + network_caching = (int)obs_data_get_int(settings, S_NETWORK_CACHING); + if (astrcmpi(behavior, S_BEHAVIOR_PAUSE_UNPAUSE) == 0) { c->behavior = BEHAVIOR_PAUSE_UNPAUSE; } else if (astrcmpi(behavior, S_BEHAVIOR_ALWAYS_PLAY) == 0) { @@ -523,13 +539,14 @@ static void vlcs_update(void *data, obs_data_t *settings) dstr_copy(&dir_path, path); dstr_cat_ch(&dir_path, '/'); dstr_cat(&dir_path, ent->d_name); - add_file(c, &new_files.da, dir_path.array); + add_file(c, &new_files.da, dir_path.array, + network_caching); } dstr_free(&dir_path); os_closedir(dir); } else { - add_file(c, &new_files.da, path); + add_file(c, &new_files.da, path, network_caching); } obs_data_release(item); @@ -551,14 +568,27 @@ static void vlcs_update(void *data, obs_data_t *settings) c->shuffle = obs_data_get_bool(settings, S_SHUFFLE); if (c->files.num > 1 && c->shuffle) { - for (size_t i = 0; i < c->files.num - 1; i++) { - size_t j = i + rand() / (RAND_MAX - / (c->files.num - i) + 1); + DARRAY(struct media_file_data) new_files; + DARRAY(size_t) idxs; - struct media_file_data t = c->files.array[j]; - c->files.array[j] = c->files.array[i]; - c->files.array[i] = t; + da_init(new_files); + da_init(idxs); + da_resize(idxs, c->files.num); + da_reserve(new_files, c->files.num); + + for (size_t i = 0; i < c->files.num; i++) { + idxs.array[i] = i; } + for (size_t i = idxs.num; i > 0; i--) { + size_t val = rand() % i; + size_t idx = idxs.array[val]; + da_push_back(new_files, &c->files.array[idx]); + da_erase(idxs, val); + } + + da_free(c->files); + da_free(idxs); + c->files.da = new_files.da; } /* ------------------------------------- */ @@ -600,11 +630,133 @@ static void vlcs_stopped(const struct libvlc_event_t *event, void *data) UNUSED_PARAMETER(event); } +static void vlcs_play_pause(void *data) +{ + struct vlc_source *c = data; + + libvlc_media_list_player_pause_(c->media_list_player); +} + +static void vlcs_restart(void *data) +{ + struct vlc_source *c = data; + + libvlc_media_list_player_stop_(c->media_list_player); + libvlc_media_list_player_play_(c->media_list_player); +} + +static void vlcs_stop(void *data) +{ + struct vlc_source *c = data; + + libvlc_media_list_player_stop_(c->media_list_player); + obs_source_output_video(c->source, NULL); +} + +static void vlcs_playlist_next(void *data) +{ + struct vlc_source *c = data; + + libvlc_media_list_player_next_(c->media_list_player); +} + +static void vlcs_playlist_prev(void *data) +{ + struct vlc_source *c = data; + + libvlc_media_list_player_previous_(c->media_list_player); +} + +static void vlcs_play_pause_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_active(c->source)) + vlcs_play_pause(c); +} + +static void vlcs_restart_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_active(c->source)) + vlcs_restart(c); +} + +static void vlcs_stop_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_active(c->source)) + vlcs_stop(c); +} + +static void vlcs_playlist_next_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_active(c->source)) + vlcs_playlist_next(c); +} + +static void vlcs_playlist_prev_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_active(c->source)) + vlcs_playlist_prev(c); +} + static void *vlcs_create(obs_data_t *settings, obs_source_t *source) { struct vlc_source *c = bzalloc(sizeof(*c)); c->source = source; + c->play_pause_hotkey = obs_hotkey_register_source(source, + "VLCSource.PlayPause", + obs_module_text("PlayPause"), + vlcs_play_pause_hotkey, c); + + c->restart_hotkey = obs_hotkey_register_source(source, + "VLCSource.Restart", + obs_module_text("Restart"), + vlcs_restart_hotkey, c); + + c->stop_hotkey = obs_hotkey_register_source(source, + "VLCSource.Stop", + obs_module_text("Stop"), + vlcs_stop_hotkey, c); + + c->playlist_next_hotkey = obs_hotkey_register_source(source, + "VLCSource.PlaylistNext", + obs_module_text("PlaylistNext"), + vlcs_playlist_next_hotkey, c); + + c->playlist_prev_hotkey = obs_hotkey_register_source(source, + "VLCSource.PlaylistPrev", + obs_module_text("PlaylistPrev"), + vlcs_playlist_prev_hotkey, c); + pthread_mutex_init_value(&c->mutex); if (pthread_mutex_init(&c->mutex, NULL) != 0) goto error; @@ -680,6 +832,7 @@ static void vlcs_defaults(obs_data_t *settings) obs_data_set_default_bool(settings, S_SHUFFLE, false); obs_data_set_default_string(settings, S_BEHAVIOR, S_BEHAVIOR_STOP_RESTART); + obs_data_set_default_int(settings, S_NETWORK_CACHING, 400); } static obs_properties_t *vlcs_properties(void *data) @@ -691,6 +844,7 @@ static obs_properties_t *vlcs_properties(void *data) struct dstr path = {0}; obs_property_t *p; + obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE); obs_properties_add_bool(ppts, S_LOOP, T_LOOP); obs_properties_add_bool(ppts, S_SHUFFLE, T_SHUFFLE); @@ -746,13 +900,18 @@ static obs_properties_t *vlcs_properties(void *data) dstr_free(&filter); dstr_free(&exts); + obs_properties_add_int(ppts, S_NETWORK_CACHING, T_NETWORK_CACHING, + 100, 60000, 10); + return ppts; } struct obs_source_info vlc_source_info = { .id = "vlc_source", .type = OBS_SOURCE_TYPE_INPUT, - .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO, + .output_flags = OBS_SOURCE_ASYNC_VIDEO | + OBS_SOURCE_AUDIO | + OBS_SOURCE_DO_NOT_DUPLICATE, .get_name = vlcs_get_name, .create = vlcs_create, .destroy = vlcs_destroy, diff --git a/test/osx/test.mm b/test/osx/test.mm index 9a9381a..136b513 100644 --- a/test/osx/test.mm +++ b/test/osx/test.mm @@ -146,7 +146,7 @@ static SceneContext SetupScene() obs_display_add_draw_callback(display.get(), [](void *, uint32_t, uint32_t) { - obs_render_main_view(); + obs_render_main_texture(); }, nullptr); } catch (char const *error) { diff --git a/test/test-input/CMakeLists.txt b/test/test-input/CMakeLists.txt index 596e637..31e26ae 100644 --- a/test/test-input/CMakeLists.txt +++ b/test/test-input/CMakeLists.txt @@ -12,6 +12,10 @@ set(test-input_SOURCES test-filter.c test-input.c test-sinewave.c + sync-async-source.c + sync-audio-buffering.c + sync-pair-vid.c + sync-pair-aud.c test-random.c) add_library(test-input MODULE diff --git a/test/test-input/sync-async-source.c b/test/test-input/sync-async-source.c new file mode 100644 index 0000000..8aa6dd1 --- /dev/null +++ b/test/test-input/sync-async-source.c @@ -0,0 +1,143 @@ +#include +#include +#include +#include + +struct async_sync_test { + obs_source_t *source; + os_event_t *stop_signal; + pthread_t thread; + bool initialized; +}; + +/* middle C */ +static const double rate = 261.63/48000.0; + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define M_PI_X2 M_PI*2 + +static const char *ast_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return "Sync Test (Async Video/Audio Source)"; +} + +static void ast_destroy(void *data) +{ + struct async_sync_test *ast = data; + + if (ast->initialized) { + os_event_signal(ast->stop_signal); + pthread_join(ast->thread, NULL); + } + + os_event_destroy(ast->stop_signal); + bfree(ast); +} + +static inline void fill_texture(uint32_t *pixels, uint32_t color) +{ + size_t x, y; + + for (y = 0; y < 20; y++) { + for (x = 0; x < 20; x++) { + pixels[y*20 + x] = color; + } + } +} + +static void *video_thread(void *data) +{ + struct async_sync_test *ast = data; + + uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); + + uint32_t *pixels = bmalloc(20 * 20 * sizeof(uint32_t)); + float *samples = bmalloc(sample_rate * sizeof(float)); + uint64_t cur_time = os_gettime_ns(); + bool whitelist = false; + double cos_val = 0.0; + uint64_t start_time = cur_time; + + struct obs_source_frame frame = { + .data = {[0] = (uint8_t*)pixels}, + .linesize = {[0] = 20*4}, + .width = 20, + .height = 20, + .format = VIDEO_FORMAT_BGRX + }; + struct obs_source_audio audio = { + .speakers = SPEAKERS_MONO, + .data = {[0] = (uint8_t*)samples}, + .samples_per_sec = sample_rate, + .frames = sample_rate, + .format = AUDIO_FORMAT_FLOAT + }; + + while (os_event_try(ast->stop_signal) == EAGAIN) { + fill_texture(pixels, whitelist ? 0xFFFFFFFF : 0xFF000000); + + frame.timestamp = cur_time - start_time; + audio.timestamp = cur_time - start_time; + + if (whitelist) { + for (size_t i = 0; i < sample_rate; i++) { + cos_val += rate * M_PI_X2; + if (cos_val > M_PI_X2) + cos_val -= M_PI_X2; + + samples[i] = (float)(cos(cos_val) * 0.5); + } + } else { + for (size_t i = 0; i < sample_rate; i++) + samples[i] = 0.0f; + } + + obs_source_output_video(ast->source, &frame); + obs_source_output_audio(ast->source, &audio); + + os_sleepto_ns(cur_time += 1000000000); + + whitelist = !whitelist; + } + + bfree(pixels); + bfree(samples); + + return NULL; +} + +static void *ast_create(obs_data_t *settings, obs_source_t *source) +{ + struct async_sync_test *ast = bzalloc(sizeof(struct async_sync_test)); + ast->source = source; + + if (os_event_init(&ast->stop_signal, OS_EVENT_TYPE_MANUAL) != 0) { + ast_destroy(ast); + return NULL; + } + + if (pthread_create(&ast->thread, NULL, video_thread, ast) != 0) { + ast_destroy(ast); + return NULL; + } + + ast->initialized = true; + + UNUSED_PARAMETER(settings); + UNUSED_PARAMETER(source); + return ast; +} + +struct obs_source_info async_sync_test = { + .id = "async_sync_test", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_ASYNC_VIDEO | + OBS_SOURCE_AUDIO, + .get_name = ast_getname, + .create = ast_create, + .destroy = ast_destroy, +}; diff --git a/test/test-input/sync-audio-buffering.c b/test/test-input/sync-audio-buffering.c new file mode 100644 index 0000000..24010f7 --- /dev/null +++ b/test/test-input/sync-audio-buffering.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include + +struct buffering_async_sync_test { + obs_source_t *source; + os_event_t *stop_signal; + pthread_t thread; + bool initialized; + bool buffer_audio; +}; + +#define CYCLE_COUNT 7 + +static const double aud_rates[CYCLE_COUNT] = { + 220.00/48000.0, /* A */ + 233.08/48000.0, /* A# */ + 246.94/48000.0, /* B */ + 261.63/48000.0, /* C */ + 277.18/48000.0, /* C# */ + 293.67/48000.0, /* D */ + 311.13/48000.0, /* D# */ +}; + +#define MAKE_COL_CHAN(x, y) (((0xFF / 7) * x) << (y * 8)) +#define MAKE_GRAYSCALE(x) \ + (MAKE_COL_CHAN(x, 0) | \ + MAKE_COL_CHAN(x, 1) | \ + MAKE_COL_CHAN(x, 2)) + +static const uint32_t vid_colors[CYCLE_COUNT] = { + 0xFF000000, + 0xFF000000 + MAKE_GRAYSCALE(1), + 0xFF000000 + MAKE_GRAYSCALE(2), + 0xFF000000 + MAKE_GRAYSCALE(3), + 0xFF000000 + MAKE_GRAYSCALE(4), + 0xFF000000 + MAKE_GRAYSCALE(5), + 0xFF000000 + MAKE_GRAYSCALE(6), +}; + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define M_PI_X2 M_PI*2 + +static const char *bast_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return "Audio Buffering Sync Test (Async Video/Audio Source)"; +} + +static void bast_destroy(void *data) +{ + struct buffering_async_sync_test *bast = data; + + if (bast->initialized) { + os_event_signal(bast->stop_signal); + pthread_join(bast->thread, NULL); + } + + os_event_destroy(bast->stop_signal); + bfree(bast); +} + +static inline void fill_texture(uint32_t *pixels, uint32_t color) +{ + size_t x, y; + + for (y = 0; y < 20; y++) { + for (x = 0; x < 20; x++) { + pixels[y*20 + x] = color; + } + } +} + +static void *video_thread(void *data) +{ + struct buffering_async_sync_test *bast = data; + + uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); + + uint32_t *pixels = bmalloc(20 * 20 * sizeof(uint32_t)); + float *samples = bmalloc(sample_rate * sizeof(float)); + uint64_t cur_time = os_gettime_ns(); + int cur_vid_pos = 0; + int cur_aud_pos = 0; + double cos_val = 0.0; + uint64_t start_time = cur_time; + bool audio_buffering_enabled = false; + + struct obs_source_frame frame = { + .data = {[0] = (uint8_t*)pixels}, + .linesize = {[0] = 20*4}, + .width = 20, + .height = 20, + .format = VIDEO_FORMAT_BGRX + }; + struct obs_source_audio audio = { + .speakers = SPEAKERS_MONO, + .data = {[0] = (uint8_t*)samples}, + .samples_per_sec = sample_rate, + .frames = sample_rate / 4, + .format = AUDIO_FORMAT_FLOAT + }; + + while (os_event_try(bast->stop_signal) == EAGAIN) { + fill_texture(pixels, vid_colors[cur_vid_pos]); + + if (!audio_buffering_enabled && bast->buffer_audio) { + audio_buffering_enabled = true; + blog(LOG_DEBUG, "okay, buffering audio: now"); + + /* 1 second = 4 cycles when running at + * 250ms per cycle */ + cur_aud_pos -= 4; + if (cur_aud_pos < 0) + cur_aud_pos += CYCLE_COUNT; + } + + /* should cause approximately 750 milliseconds of audio + * buffering */ + frame.timestamp = cur_time - start_time; + audio.timestamp = cur_time - start_time - + (audio_buffering_enabled ? 1000000000 : 0); + + const double rate = aud_rates[cur_aud_pos]; + + for (size_t i = 0; i < sample_rate / 4; i++) { + cos_val += rate * M_PI_X2; + if (cos_val > M_PI_X2) + cos_val -= M_PI_X2; + + samples[i] = (float)(cos(cos_val) * 0.5); + } + + obs_source_output_video(bast->source, &frame); + obs_source_output_audio(bast->source, &audio); + + os_sleepto_ns(cur_time += 250000000); + + if (++cur_vid_pos == CYCLE_COUNT) + cur_vid_pos = 0; + if (++cur_aud_pos == CYCLE_COUNT) + cur_aud_pos = 0; + } + + bfree(pixels); + bfree(samples); + + return NULL; +} + +static void bast_buffer_audio(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + struct buffering_async_sync_test *bast = data; + + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + if (pressed) + bast->buffer_audio = true; +} + +static void *bast_create(obs_data_t *settings, obs_source_t *source) +{ + struct buffering_async_sync_test *bast = bzalloc(sizeof(*bast)); + bast->source = source; + + if (os_event_init(&bast->stop_signal, OS_EVENT_TYPE_MANUAL) != 0) { + bast_destroy(bast); + return NULL; + } + + if (pthread_create(&bast->thread, NULL, video_thread, bast) != 0) { + bast_destroy(bast); + return NULL; + } + + obs_hotkey_register_source(source, "AudioBufferingSyncTest.Buffer", + "Buffer Audio", bast_buffer_audio, bast); + + bast->initialized = true; + + UNUSED_PARAMETER(settings); + UNUSED_PARAMETER(source); + return bast; +} + +struct obs_source_info buffering_async_sync_test = { + .id = "buffering_async_sync_test", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_ASYNC_VIDEO | + OBS_SOURCE_AUDIO, + .get_name = bast_getname, + .create = bast_create, + .destroy = bast_destroy, +}; diff --git a/test/test-input/sync-pair-aud.c b/test/test-input/sync-pair-aud.c new file mode 100644 index 0000000..52a66c1 --- /dev/null +++ b/test/test-input/sync-pair-aud.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include + +struct sync_pair_aud { + bool initialized_thread; + pthread_t thread; + os_event_t *event; + obs_source_t *source; +}; + +/* middle C */ +static const double rate = 261.63/48000.0; + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define M_PI_X2 M_PI*2 + +extern uint64_t starting_time; + +static inline bool whitelist_time(uint64_t ts, uint64_t interval, + uint64_t fps_num, uint64_t fps_den) +{ + if (!starting_time) + return false; + + uint64_t count = (ts - starting_time) / interval; + uint64_t sec = count * fps_den / fps_num; + return sec % 2 == 1; +} + +static void *sync_pair_aud_thread(void *pdata) +{ + struct sync_pair_aud *spa = pdata; + uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); + uint32_t frames = sample_rate / 100; + uint64_t last_time = obs_get_video_frame_time(); + double cos_val = 0.0; + float *samples = malloc(frames * sizeof(float)); + + uint64_t interval = video_output_get_frame_time(obs_get_video()); + const struct video_output_info *voi = + video_output_get_info(obs_get_video()); + uint64_t fps_num = voi->fps_num; + uint64_t fps_den = voi->fps_den; + + while (os_event_try(spa->event) == EAGAIN) { + if (!os_sleepto_ns(last_time += 10000000)) + last_time = obs_get_video_frame_time(); + + for (uint64_t i = 0; i < frames; i++) { + uint64_t ts = last_time + + i * 1000000000ULL / sample_rate; + + if (whitelist_time(ts, interval, fps_num, fps_den)) { + cos_val += rate * M_PI_X2; + if (cos_val > M_PI_X2) + cos_val -= M_PI_X2; + + samples[i] = (float)(cos(cos_val) * 0.5); + } else { + samples[i] = 0.0f; + } + } + + struct obs_source_audio data; + data.data[0] = (uint8_t*)samples; + data.frames = frames; + data.speakers = SPEAKERS_MONO; + data.samples_per_sec = sample_rate; + data.timestamp = last_time; + data.format = AUDIO_FORMAT_FLOAT; + obs_source_output_audio(spa->source, &data); + } + + free(samples); + + return NULL; +} + +/* ------------------------------------------------------------------------- */ + +static const char *sync_pair_aud_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return "Sync Test Pair (Audio)"; +} + +static void sync_pair_aud_destroy(void *data) +{ + struct sync_pair_aud *spa = data; + + if (spa) { + if (spa->initialized_thread) { + void *ret; + os_event_signal(spa->event); + pthread_join(spa->thread, &ret); + } + + os_event_destroy(spa->event); + bfree(spa); + } +} + +static void *sync_pair_aud_create(obs_data_t *settings, + obs_source_t *source) +{ + struct sync_pair_aud *spa = bzalloc(sizeof(struct sync_pair_aud)); + spa->source = source; + + if (os_event_init(&spa->event, OS_EVENT_TYPE_MANUAL) != 0) + goto fail; + if (pthread_create(&spa->thread, NULL, sync_pair_aud_thread, spa) != 0) + goto fail; + + spa->initialized_thread = true; + + UNUSED_PARAMETER(settings); + return spa; + +fail: + sync_pair_aud_destroy(spa); + return NULL; +} + +struct obs_source_info sync_audio = { + .id = "sync_audio", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_AUDIO, + .get_name = sync_pair_aud_getname, + .create = sync_pair_aud_create, + .destroy = sync_pair_aud_destroy, +}; diff --git a/test/test-input/sync-pair-vid.c b/test/test-input/sync-pair-vid.c new file mode 100644 index 0000000..ebfc32c --- /dev/null +++ b/test/test-input/sync-pair-vid.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include + +struct sync_pair_vid { + obs_source_t *source; + gs_texture_t *tex; + gs_texture_t *white; + gs_texture_t *black; +}; + +uint64_t starting_time = 0; +uint64_t last_frame = 0; + +static const char *sync_pair_vid_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return "Sync Test Pair (Video)"; +} + +static void sync_pair_vid_destroy(void *data) +{ + struct sync_pair_vid *spv = data; + + obs_enter_graphics(); + gs_texture_destroy(spv->tex); + gs_texture_destroy(spv->white); + gs_texture_destroy(spv->black); + obs_leave_graphics(); + + bfree(spv); +} + +static inline void fill_texture(uint32_t *pixels, uint32_t pixel) +{ + size_t x, y; + + for (y = 0; y < 32; y++) { + for (x = 0; x < 32; x++) { + pixels[y*32 + x] = pixel; + } + } +} + +static void *sync_pair_vid_create(obs_data_t *settings, obs_source_t *source) +{ + struct sync_pair_vid *spv = bzalloc(sizeof(struct sync_pair_vid)); + spv->source = source; + + obs_enter_graphics(); + spv->tex = gs_texture_create(32, 32, GS_RGBA, 1, NULL, GS_DYNAMIC); + spv->white = gs_texture_create(32, 32, GS_RGBA, 1, NULL, GS_DYNAMIC); + spv->black = gs_texture_create(32, 32, GS_RGBA, 1, NULL, GS_DYNAMIC); + + uint8_t *ptr; + uint32_t linesize; + if (gs_texture_map(spv->white, &ptr, &linesize)) { + fill_texture((uint32_t*)ptr, 0xFFFFFFFF); + gs_texture_unmap(spv->white); + } + if (gs_texture_map(spv->black, &ptr, &linesize)) { + fill_texture((uint32_t*)ptr, 0xFF000000); + gs_texture_unmap(spv->black); + } + + obs_leave_graphics(); + + return spv; +} + +static inline bool whitelist_time(uint64_t ts, uint64_t interval, + uint64_t fps_num, uint64_t fps_den) +{ + if (!starting_time) + return false; + + uint64_t count = (ts - starting_time) / interval; + uint64_t sec = count * fps_den / fps_num; + return sec % 2 == 1; +} + +static void sync_pair_vid_render(void *data, gs_effect_t *effect) +{ + struct sync_pair_vid *spv = data; + + uint64_t ts = obs_get_video_frame_time(); + if (!starting_time) + starting_time = ts; + + uint64_t interval = video_output_get_frame_time(obs_get_video()); + const struct video_output_info *voi = + video_output_get_info(obs_get_video()); + uint64_t fps_num = voi->fps_num; + uint64_t fps_den = voi->fps_den; + + bool whitelist = whitelist_time(ts, interval, fps_num, fps_den); + +#if 0 + if (last_frame != ts) { + uint8_t *ptr; + uint32_t linesize; + if (gs_texture_map(spv->tex, &ptr, &linesize)) { + fill_texture((uint32_t*)ptr, whitelist ? 0xFFFFFFFF : 0xFF000000); + gs_texture_unmap(spv->tex); + } + last_frame = ts; + } + + obs_source_draw(spv->tex, 0, 0, 0, 0, 0); +#else + obs_source_draw(whitelist ? spv->white : spv->black, 0, 0, 0, 0, 0); +#endif +} + +static uint32_t sync_pair_vid_size(void *data) +{ + return 32; +} + +struct obs_source_info sync_video = { + .id = "sync_video", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = sync_pair_vid_getname, + .create = sync_pair_vid_create, + .destroy = sync_pair_vid_destroy, + .video_render = sync_pair_vid_render, + .get_width = sync_pair_vid_size, + .get_height = sync_pair_vid_size, +}; diff --git a/test/test-input/test-input.c b/test/test-input/test-input.c index 187398f..2116381 100644 --- a/test/test-input/test-input.c +++ b/test/test-input/test-input.c @@ -5,11 +5,19 @@ OBS_DECLARE_MODULE() extern struct obs_source_info test_random; extern struct obs_source_info test_sinewave; extern struct obs_source_info test_filter; +extern struct obs_source_info async_sync_test; +extern struct obs_source_info buffering_async_sync_test; +extern struct obs_source_info sync_video; +extern struct obs_source_info sync_audio; bool obs_module_load(void) { obs_register_source(&test_random); obs_register_source(&test_sinewave); obs_register_source(&test_filter); + obs_register_source(&async_sync_test); + obs_register_source(&buffering_async_sync_test); + obs_register_source(&sync_video); + obs_register_source(&sync_audio); return true; } diff --git a/test/win/test.cpp b/test/win/test.cpp index c0e60ca..5f966c6 100644 --- a/test/win/test.cpp +++ b/test/win/test.cpp @@ -150,7 +150,7 @@ static HWND CreateTestWindow(HINSTANCE instance) static void RenderWindow(void *data, uint32_t cx, uint32_t cy) { - obs_render_main_view(); + obs_render_main_texture(); UNUSED_PARAMETER(data); UNUSED_PARAMETER(cx);