diff --git a/pkgs/apps/ffmpeg/default.nix b/pkgs/apps/ffmpeg/default.nix index 0f3de1d..9188e39 100644 --- a/pkgs/apps/ffmpeg/default.nix +++ b/pkgs/apps/ffmpeg/default.nix @@ -1,4 +1,4 @@ import ./generic.nix rec { - version = "6.0"; - sha256 = "sha256-RVbgsafIbeUUNXmUbDQ03ZN42oaUo0njqROo7KOQgv0="; + version = "6.1"; + sha256 = "sha256-NzhD2D16bCVCyCXo0TRwZYp3Ta5eFSfoQPa+iRkeNZg="; } \ No newline at end of file diff --git a/pkgs/apps/ffmpeg/generic.nix b/pkgs/apps/ffmpeg/generic.nix index 62571de..befc5ab 100644 --- a/pkgs/apps/ffmpeg/generic.nix +++ b/pkgs/apps/ffmpeg/generic.nix @@ -94,7 +94,7 @@ , withVoAmrwbenc ? withFullDeps # AMR-WB encoder , withVorbis ? withHeadlessDeps # Vorbis de/encoding, native encoder exists , withVpx ? withHeadlessDeps && stdenv.buildPlatform == stdenv.hostPlatform # VP8 & VP9 de/encoding -, withVulkan ? withFullDeps && !stdenv.isDarwin +, withVulkan ? false , withWebp ? withFullDeps # WebP encoder , withX264 ? withHeadlessDeps # H.264/AVC encoder , withX265 ? withHeadlessDeps # H.265/HEVC encoder @@ -130,7 +130,6 @@ , withMultithread ? true # Multithreading via pthreads/win32 threads , withNetwork ? withHeadlessDeps # Network support , withPixelutils ? withHeadlessDeps # Pixel utils in libavutil -, withLTO ? false # build with link-time optimization /* * Program options */ @@ -235,8 +234,6 @@ , libXext , libxml2 , xz -, nv-codec-headers -, nv-codec-headers-11 , openal , ocl-icd # OpenCL ICD , opencl-headers # OpenCL headers @@ -352,10 +349,6 @@ stdenv.mkDerivation (finalAttrs: { --replace /usr/local/lib/frei0r-1 ${frei0r}/lib/frei0r-1 substituteInPlace doc/filters.texi \ --replace /usr/local/lib/frei0r-1 ${frei0r}/lib/frei0r-1 - '' + lib.optionalString withVulkan '' - # FIXME: horrible hack, remove for next release - substituteInPlace libavutil/hwcontext_vulkan.c \ - --replace VK_EXT_VIDEO_DECODE VK_KHR_VIDEO_DECODE ''; patches = map (patch: fetchpatch patch) (extraPatches @@ -389,7 +382,6 @@ stdenv.mkDerivation (finalAttrs: { (enableFeature withSmallBuild "small") (enableFeature withRuntimeCPUDetection "runtime-cpudetect") - (enableFeature withLTO "lto") (enableFeature withGrayscale "gray") (enableFeature withSwscaleAlpha "swscale-alpha") (enableFeature withHardcodedTables "hardcoded-tables") @@ -557,9 +549,25 @@ stdenv.mkDerivation (finalAttrs: { nativeBuildInputs = [ removeReferencesTo addOpenGLRunpath perl pkg-config texinfo yasm ]; # TODO This was always in buildInputs before, why? - buildInputs = optionals withFullDeps [ libdc1394 ] + buildInputs = + let + nv-codec-headers = stdenv.mkDerivation rec { + pname = "nv-codec-headers"; + version = "12.1.14.0"; + src = fetchgit { + url = "https://git.videolan.org/git/ffmpeg/nv-codec-headers.git"; + rev = "n${version}"; + sha256 = "sha256-WJYuFmMGSW+B32LwE7oXv/IeTln6TNEeXSkquHh85Go="; + }; + makeFlags = [ + "PREFIX=$(out)" + ]; + +}; + in + optionals withFullDeps [ libdc1394 ] ++ optionals (withFullDeps && !stdenv.isDarwin) [ libraw1394 ] # TODO where does this belong to - ++ optionals (withNvdec || withNvenc) [ (if (lib.versionAtLeast version "6") then nv-codec-headers-11 else nv-codec-headers) ] + ++ optionals (withNvdec || withNvenc || withCuda) [ nv-codec-headers ] ++ optionals withAmf [ amf-headers ] ++ optionals withAlsa [ alsa-lib ] ++ optionals withAom [ libaom ] diff --git a/pkgs/apps/obs/av1-vaapi.patch b/pkgs/apps/obs/av1-vaapi.patch new file mode 100644 index 0000000..3b7710d --- /dev/null +++ b/pkgs/apps/obs/av1-vaapi.patch @@ -0,0 +1,3826 @@ +diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss +index e7c70496a..a4be95b8c 100644 +--- a/UI/data/themes/Acri.qss ++++ b/UI/data/themes/Acri.qss +@@ -166,7 +166,7 @@ QMenu::item:disabled { + } + + QMenu::right-arrow { +- image: url(./Dark/expand.svg); ++ image: url(theme:Dark/expand.svg); + } + + /* Top Menu Bar Items */ +@@ -304,8 +304,8 @@ QDockWidget { + font-size: 10.5pt; + font-weight: bold; + +- titlebar-close-icon: url('./Dark/close.svg'); +- titlebar-normal-icon: url('./Dark/popout.svg'); ++ titlebar-close-icon: url(theme:Dark/close.svg); ++ titlebar-normal-icon: url(theme:Dark/popout.svg); + } + + QDockWidget::title { +@@ -453,11 +453,11 @@ QScrollBar::handle:horizontal { + } + + QPushButton#sourcePropertiesButton { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + QPushButton#sourceFiltersButton { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + /* Scenes and Sources toolbar */ +@@ -493,55 +493,55 @@ QToolButton:pressed { + } + + * [themeID="addIconSmall"] { +- qproperty-icon: url(./Dark/plus.svg); ++ qproperty-icon: url(theme:Dark/plus.svg); + } + + * [themeID="removeIconSmall"] { +- qproperty-icon: url(./Dark/trash.svg); ++ qproperty-icon: url(theme:Dark/trash.svg); + } + + * [themeID="clearIconSmall"] { +- qproperty-icon: url(./Dark/entry-clear.svg); ++ qproperty-icon: url(theme:Dark/entry-clear.svg); + } + + * [themeID="propertiesIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="configIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="menuIconSmall"] { +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + * [themeID="refreshIconSmall"] { +- qproperty-icon: url(./Dark/refresh.svg); ++ qproperty-icon: url(theme:Dark/refresh.svg); + } + + * [themeID="cogsIcon"] { +- qproperty-icon: url(./Dark/cogs.svg); ++ qproperty-icon: url(theme:Dark/cogs.svg); + } + + #sourceInteractButton { +- qproperty-icon: url(./Dark/interact.svg); ++ qproperty-icon: url(theme:Dark/interact.svg); + } + + * [themeID="upArrowIconSmall"] { +- qproperty-icon: url(./Dark/up.svg); ++ qproperty-icon: url(theme:Dark/up.svg); + } + + * [themeID="downArrowIconSmall"] { +- qproperty-icon: url(./Dark/down.svg); ++ qproperty-icon: url(theme:Dark/down.svg); + } + + * [themeID="pauseIconSmall"] { +- qproperty-icon: url(./Dark/media-pause.svg); ++ qproperty-icon: url(theme:Dark/media-pause.svg); + } + + * [themeID="filtersIcon"] { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + QToolBarExtension { +@@ -551,7 +551,7 @@ QToolBarExtension { + padding: 4px 0px; + margin-left: 0px; + +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + +@@ -647,7 +647,7 @@ QDateTimeEdit::drop-down { + QComboBox::down-arrow, + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/updown.svg); ++ image: url(theme:Dark/updown.svg); + width: 100%; + } + +@@ -669,7 +669,7 @@ QDateTimeEdit::drop-down:editable { + QComboBox::down-arrow:editable, + QDateTimeEdit::down-arrow:editable { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 8%; + } + +@@ -757,13 +757,13 @@ QDoubleSpinBox::up-button:disabled, QDoubleSpinBox::up-button:off, QDoubleSpinBo + } + + QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { +- image: url(./Dark/up.svg); ++ image: url(theme:Dark/up.svg); + width: 100%; + margin: 2px; + } + + QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + padding: 2px; + } +@@ -844,7 +844,7 @@ QPushButton:disabled, QToolButton:disabled { + } + + QPushButton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + subcontrol-origin: padding; + width: 25px; +@@ -995,15 +995,15 @@ QHeaderView::section { + /* Mute CheckBox */ + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:indeterminate { +- image: url(./Dark/unassigned.svg); ++ image: url(theme:Dark/unassigned.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + OBSHotkeyLabel[hotkeyPairHover=true] { +@@ -1102,14 +1102,14 @@ OBSBasicFilters #widget_2 QPushButton { + /* Settings Icons */ + + OBSBasicSettings { +- qproperty-generalIcon: url(./Dark/settings/general.svg); +- qproperty-streamIcon: url(./Dark/settings/stream.svg); +- qproperty-outputIcon: url(./Dark/settings/output.svg); +- qproperty-audioIcon: url(./Dark/settings/audio.svg); +- qproperty-videoIcon: url(./Dark/settings/video.svg); +- qproperty-hotkeysIcon: url(./Dark/settings/hotkeys.svg); +- qproperty-accessibilityIcon: url(./Dark/settings/accessibility.svg); +- qproperty-advancedIcon: url(./Dark/settings/advanced.svg); ++ qproperty-generalIcon: url(theme:Dark/settings/general.svg); ++ qproperty-streamIcon: url(theme:Dark/settings/stream.svg); ++ qproperty-outputIcon: url(theme:Dark/settings/output.svg); ++ qproperty-audioIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-videoIcon: url(theme:Dark/settings/video.svg); ++ qproperty-hotkeysIcon: url(theme:Dark/settings/hotkeys.svg); ++ qproperty-accessibilityIcon: url(theme:Dark/settings/accessibility.svg); ++ qproperty-advancedIcon: url(theme:Dark/settings/advanced.svg); + } + + /* Checkboxes */ +@@ -1129,34 +1129,34 @@ QGroupBox::indicator { + + QCheckBox::indicator:unchecked, + QGroupBox::indicator:unchecked { +- image: url(./Yami/checkbox_unchecked.svg); ++ image: url(theme:Yami/checkbox_unchecked.svg); + } + + QCheckBox::indicator:unchecked:hover, + QGroupBox::indicator:unchecked:hover { + border: none; +- image: url(./Yami/checkbox_unchecked_focus.svg); ++ image: url(theme:Yami/checkbox_unchecked_focus.svg); + } + + QCheckBox::indicator:checked, + QGroupBox::indicator:checked { +- image: url(./Yami/checkbox_checked.svg); ++ image: url(theme:Yami/checkbox_checked.svg); + } + + QCheckBox::indicator:checked:hover, + QGroupBox::indicator:checked:hover { + border: none; +- image: url(./Yami/checkbox_checked_focus.svg); ++ image: url(theme:Yami/checkbox_checked_focus.svg); + } + + QCheckBox::indicator:checked:disabled, + QGroupBox::indicator:checked:disabled { +- image: url(./Yami/checkbox_checked_disabled.svg); ++ image: url(theme:Yami/checkbox_checked_disabled.svg); + } + + QCheckBox::indicator:unchecked:disabled, + QGroupBox::indicator:unchecked:disabled { +- image: url(./Yami/checkbox_unchecked_disabled.svg); ++ image: url(theme:Yami/checkbox_unchecked_disabled.svg); + } + + /* Locked CheckBox */ +@@ -1173,7 +1173,7 @@ LockedCheckBox::indicator { + + LockedCheckBox::indicator:checked, + LockedCheckBox::indicator:checked:hover { +- image: url(./Dark/locked.svg); ++ image: url(theme:Dark/locked.svg); + } + + LockedCheckBox::indicator:unchecked, +@@ -1195,7 +1195,7 @@ VisibilityCheckBox::indicator { + + VisibilityCheckBox::indicator:checked, + VisibilityCheckBox::indicator:checked:hover { +- image: url(./Dark/visible.svg); ++ image: url(theme:Dark/visible.svg); + } + + VisibilityCheckBox::indicator:unchecked, +@@ -1204,7 +1204,7 @@ VisibilityCheckBox::indicator:unchecked:hover { + } + + * [themeID="revertIcon"] { +- qproperty-icon: url(./Dark/revert.svg); ++ qproperty-icon: url(theme:Dark/revert.svg); + } + + QPushButton#extraPanelDelete { +@@ -1233,35 +1233,35 @@ MuteCheckBox::indicator { + } + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:hover { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:focus { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:checked:hover { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:checked:focus { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:checked:disabled { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:unchecked:disabled { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + #hotkeyFilterReset { +@@ -1291,46 +1291,46 @@ OBSHotkeyWidget QPushButton { + + /* Sources List Group Collapse Checkbox */ + +-SourceTreeSubItemCheckBox { ++QCheckBox[sourceTreeSubItem=true] { + background: transparent; + outline: none; + padding: 0px; + } + +-SourceTreeSubItemCheckBox::indicator { ++QCheckBox[sourceTreeSubItem=true]::indicator { + width: 12px; + height: 12px; + } + +-SourceTreeSubItemCheckBox::indicator:checked, +-SourceTreeSubItemCheckBox::indicator:checked:hover { +- image: url(./Dark/expand.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:checked, ++QCheckBox[sourceTreeSubItem=true]::indicator:checked:hover { ++ image: url(theme:Dark/expand.svg); + } + +-SourceTreeSubItemCheckBox::indicator:unchecked, +-SourceTreeSubItemCheckBox::indicator:unchecked:hover { +- image: url(./Dark/collapse.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked, ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked:hover { ++ image: url(theme:Dark/collapse.svg); + } + + /* Source Icons */ + + OBSBasic { +- qproperty-imageIcon: url(./Dark/sources/image.svg); +- qproperty-colorIcon: url(./Dark/sources/brush.svg); +- qproperty-slideshowIcon: url(./Dark/sources/slideshow.svg); +- qproperty-audioInputIcon: url(./Dark/sources/microphone.svg); +- qproperty-audioOutputIcon: url(./Dark/settings/audio.svg); +- qproperty-desktopCapIcon: url(./Dark/settings/video.svg); +- qproperty-windowCapIcon: url(./Dark/sources/window.svg); +- qproperty-gameCapIcon: url(./Dark/sources/gamepad.svg); +- qproperty-cameraIcon: url(./Dark/sources/camera.svg); +- qproperty-textIcon: url(./Dark/sources/text.svg); +- qproperty-mediaIcon: url(./Dark/sources/media.svg); +- qproperty-browserIcon: url(./Dark/sources/globe.svg); +- qproperty-groupIcon: url(./Dark/sources/group.svg); +- qproperty-sceneIcon: url(./Dark/sources/scene.svg); +- qproperty-defaultIcon: url(./Dark/sources/default.svg); +- qproperty-audioProcessOutputIcon: url(./Dark/sources/windowaudio.svg); ++ qproperty-imageIcon: url(theme:Dark/sources/image.svg); ++ qproperty-colorIcon: url(theme:Dark/sources/brush.svg); ++ qproperty-slideshowIcon: url(theme:Dark/sources/slideshow.svg); ++ qproperty-audioInputIcon: url(theme:Dark/sources/microphone.svg); ++ qproperty-audioOutputIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-desktopCapIcon: url(theme:Dark/settings/video.svg); ++ qproperty-windowCapIcon: url(theme:Dark/sources/window.svg); ++ qproperty-gameCapIcon: url(theme:Dark/sources/gamepad.svg); ++ qproperty-cameraIcon: url(theme:Dark/sources/camera.svg); ++ qproperty-textIcon: url(theme:Dark/sources/text.svg); ++ qproperty-mediaIcon: url(theme:Dark/sources/media.svg); ++ qproperty-browserIcon: url(theme:Dark/sources/globe.svg); ++ qproperty-groupIcon: url(theme:Dark/sources/group.svg); ++ qproperty-sceneIcon: url(theme:Dark/sources/scene.svg); ++ qproperty-defaultIcon: url(theme:Dark/sources/default.svg); ++ qproperty-audioProcessOutputIcon: url(theme:Dark/sources/windowaudio.svg); + } + + /* Scene Tree Grid Mode */ +@@ -1366,7 +1366,7 @@ SceneTree { + /* Save icon */ + + * [themeID="replayIconSmall"] { +- qproperty-icon: url(./Dark/save.svg); ++ qproperty-icon: url(theme:Dark/save.svg); + } + + /* Studio Mode T-Bar */ +@@ -1396,32 +1396,32 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { + /* Media icons */ + + * [themeID="playIcon"] { +- qproperty-icon: url(./Dark/media/media_play.svg); ++ qproperty-icon: url(theme:Dark/media/media_play.svg); + } + + * [themeID="pauseIcon"] { +- qproperty-icon: url(./Dark/media/media_pause.svg); ++ qproperty-icon: url(theme:Dark/media/media_pause.svg); + } + + * [themeID="restartIcon"] { +- qproperty-icon: url(./Dark/media/media_restart.svg); ++ qproperty-icon: url(theme:Dark/media/media_restart.svg); + } + + * [themeID="stopIcon"] { +- qproperty-icon: url(./Dark/media/media_stop.svg); ++ qproperty-icon: url(theme:Dark/media/media_stop.svg); + } + + * [themeID="nextIcon"] { +- qproperty-icon: url(./Dark/media/media_next.svg); ++ qproperty-icon: url(theme:Dark/media/media_next.svg); + } + + * [themeID="previousIcon"] { +- qproperty-icon: url(./Dark/media/media_previous.svg); ++ qproperty-icon: url(theme:Dark/media/media_previous.svg); + } + + /* YouTube Integration */ + OBSYoutubeActions { +- qproperty-thumbPlaceholder: url(./Dark/sources/image.svg); ++ qproperty-thumbPlaceholder: url(theme:Dark/sources/image.svg); + } + + #ytEventList QLabel { +@@ -1449,7 +1449,7 @@ OBSYoutubeActions { + /* Calendar Widget */ + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + } + +@@ -1472,7 +1472,7 @@ QCalendarWidget QToolButton { + } + + #qt_calendar_monthbutton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + padding-top: 2px; + padding-right: 6px; +@@ -1482,13 +1482,13 @@ QCalendarWidget QToolButton { + + QCalendarWidget #qt_calendar_prevmonth { + padding: 2px; +- qproperty-icon: url(./Dark/left.svg); ++ qproperty-icon: url(theme:Dark/left.svg); + icon-size: 16px, 16px; + } + + QCalendarWidget #qt_calendar_nextmonth { + padding: 2px; +- qproperty-icon: url(./Dark/right.svg); ++ qproperty-icon: url(theme:Dark/right.svg); + icon-size: 16px, 16px; + } + +diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss +index 6c96a902c..a2e262d4e 100644 +--- a/UI/data/themes/Dark.qss ++++ b/UI/data/themes/Dark.qss +@@ -132,8 +132,8 @@ QMainWindow::separator { + /* Dock Widget */ + + QDockWidget { +- titlebar-close-icon: url('./Dark/close.svg'); +- titlebar-normal-icon: url('./Dark/popout.svg'); ++ titlebar-close-icon: url(theme:Dark/close.svg); ++ titlebar-normal-icon: url(theme:Dark/popout.svg); + } + + QDockWidget::title { +@@ -242,15 +242,15 @@ QScrollBar::left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBa + } + + #contextContainer QPushButton#sourcePropertiesButton { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + #contextContainer QPushButton#sourceFiltersButton { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + #contextContainer QPushButton#sourceInteractButton { +- qproperty-icon: url(./Dark/interact.svg); ++ qproperty-icon: url(theme:Dark/interact.svg); + } + + /* Scenes and Sources toolbar */ +@@ -286,51 +286,51 @@ QToolButton:pressed { + } + + * [themeID="addIconSmall"] { +- qproperty-icon: url(./Dark/plus.svg); ++ qproperty-icon: url(theme:Dark/plus.svg); + } + + * [themeID="removeIconSmall"] { +- qproperty-icon: url(./Dark/minus.svg); ++ qproperty-icon: url(theme:Dark/minus.svg); + } + + * [themeID="clearIconSmall"] { +- qproperty-icon: url(./Dark/entry-clear.svg); ++ qproperty-icon: url(theme:Dark/entry-clear.svg); + } + + * [themeID="propertiesIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="configIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="refreshIconSmall"] { +- qproperty-icon: url(./Dark/refresh.svg); ++ qproperty-icon: url(theme:Dark/refresh.svg); + } + + * [themeID="upArrowIconSmall"] { +- qproperty-icon: url(./Dark/up.svg); ++ qproperty-icon: url(theme:Dark/up.svg); + } + + * [themeID="downArrowIconSmall"] { +- qproperty-icon: url(./Dark/down.svg); ++ qproperty-icon: url(theme:Dark/down.svg); + } + + * [themeID="pauseIconSmall"] { +- qproperty-icon: url(./Dark/media-pause.svg); ++ qproperty-icon: url(theme:Dark/media-pause.svg); + } + + * [themeID="menuIconSmall"] { +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + * [themeID="cogsIcon"] { +- qproperty-icon: url(./Dark/cogs.svg); ++ qproperty-icon: url(theme:Dark/cogs.svg); + } + + * [themeID="filtersIcon"] { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + /* Tab Widget */ +@@ -407,7 +407,7 @@ QComboBox::drop-down { + QDateTimeEdit::down-arrow, + QComboBox::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/updown.svg); ++ image: url(theme:Dark/updown.svg); + width: 100%; + } + +@@ -431,7 +431,7 @@ QComboBox::drop-down:editable { + QDateTimeEdit::down-arrow:editable, + QComboBox::down-arrow:editable { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 8%; + } + +@@ -497,12 +497,12 @@ QDoubleSpinBox::up-button:disabled, QDoubleSpinBox::up-button:off, QDoubleSpinBo + } + + QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { +- image: url(./Dark/up.svg); ++ image: url(theme:Dark/up.svg); + width: 100%; + } + + QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + } + +@@ -540,7 +540,7 @@ QPushButton:disabled, QToolButton:disabled { + } + + QPushButton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + subcontrol-origin: padding; + width: 25px; +@@ -680,15 +680,15 @@ MuteCheckBox { + } + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:indeterminate { +- image: url(./Dark/unassigned.svg); ++ image: url(theme:Dark/unassigned.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + OBSHotkeyLabel[hotkeyPairHover=true] { +@@ -697,22 +697,22 @@ OBSHotkeyLabel[hotkeyPairHover=true] { + + /* Group Collapse Checkbox */ + +-SourceTreeSubItemCheckBox { ++QCheckBox[sourceTreeSubItem=true] { + background: transparent; + outline: none; + } + +-SourceTreeSubItemCheckBox::indicator { ++QCheckBox[sourceTreeSubItem=true]::indicator { + width: 10px; + height: 10px; + } + +-SourceTreeSubItemCheckBox::indicator:checked { +- image: url(./Dark/expand.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:checked { ++ image: url(theme:Dark/expand.svg); + } + +-SourceTreeSubItemCheckBox::indicator:unchecked { +- image: url(./Dark/collapse.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked { ++ image: url(theme:Dark/collapse.svg); + } + + +@@ -780,14 +780,14 @@ OBSQTDisplay { + /* Settings Icons */ + + OBSBasicSettings { +- qproperty-generalIcon: url(./Dark/settings/general.svg); +- qproperty-streamIcon: url(./Dark/settings/stream.svg); +- qproperty-outputIcon: url(./Dark/settings/output.svg); +- qproperty-audioIcon: url(./Dark/settings/audio.svg); +- qproperty-videoIcon: url(./Dark/settings/video.svg); +- qproperty-hotkeysIcon: url(./Dark/settings/hotkeys.svg); +- qproperty-accessibilityIcon: url(./Dark/settings/accessibility.svg); +- qproperty-advancedIcon: url(./Dark/settings/advanced.svg); ++ qproperty-generalIcon: url(theme:Dark/settings/general.svg); ++ qproperty-streamIcon: url(theme:Dark/settings/stream.svg); ++ qproperty-outputIcon: url(theme:Dark/settings/output.svg); ++ qproperty-audioIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-videoIcon: url(theme:Dark/settings/video.svg); ++ qproperty-hotkeysIcon: url(theme:Dark/settings/hotkeys.svg); ++ qproperty-accessibilityIcon: url(theme:Dark/settings/accessibility.svg); ++ qproperty-advancedIcon: url(theme:Dark/settings/advanced.svg); + } + + OBSBasicSettings QListWidget::item { +@@ -803,7 +803,7 @@ LockedCheckBox { + } + + LockedCheckBox::indicator:checked { +- image: url(./Dark/locked.svg); ++ image: url(theme:Dark/locked.svg); + } + + LockedCheckBox::indicator:unchecked { +@@ -818,7 +818,7 @@ VisibilityCheckBox { + } + + VisibilityCheckBox::indicator:checked { +- image: url(./Dark/visible.svg); ++ image: url(theme:Dark/visible.svg); + } + + VisibilityCheckBox::indicator:unchecked { +@@ -826,7 +826,7 @@ VisibilityCheckBox::indicator:unchecked { + } + + * [themeID="revertIcon"] { +- qproperty-icon: url(./Dark/revert.svg); ++ qproperty-icon: url(theme:Dark/revert.svg); + } + + QPushButton#extraPanelDelete { +@@ -842,28 +842,28 @@ QPushButton#extraPanelDelete:pressed { + } + + OBSMissingFiles { +- qproperty-warningIcon: url(./Dark/alert.svg); ++ qproperty-warningIcon: url(theme:Dark/alert.svg); + } + + /* Source Icons */ + + OBSBasic { +- qproperty-imageIcon: url(./Dark/sources/image.svg); +- qproperty-colorIcon: url(./Dark/sources/brush.svg); +- qproperty-slideshowIcon: url(./Dark/sources/slideshow.svg); +- qproperty-audioInputIcon: url(./Dark/sources/microphone.svg); +- qproperty-audioOutputIcon: url(./Dark/settings/audio.svg); +- qproperty-desktopCapIcon: url(./Dark/settings/video.svg); +- qproperty-windowCapIcon: url(./Dark/sources/window.svg); +- qproperty-gameCapIcon: url(./Dark/sources/gamepad.svg); +- qproperty-cameraIcon: url(./Dark/sources/camera.svg); +- qproperty-textIcon: url(./Dark/sources/text.svg); +- qproperty-mediaIcon: url(./Dark/sources/media.svg); +- qproperty-browserIcon: url(./Dark/sources/globe.svg); +- qproperty-groupIcon: url(./Dark/sources/group.svg); +- qproperty-sceneIcon: url(./Dark/sources/scene.svg); +- qproperty-defaultIcon: url(./Dark/sources/default.svg); +- qproperty-audioProcessOutputIcon: url(./Dark/sources/windowaudio.svg); ++ qproperty-imageIcon: url(theme:Dark/sources/image.svg); ++ qproperty-colorIcon: url(theme:Dark/sources/brush.svg); ++ qproperty-slideshowIcon: url(theme:Dark/sources/slideshow.svg); ++ qproperty-audioInputIcon: url(theme:Dark/sources/microphone.svg); ++ qproperty-audioOutputIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-desktopCapIcon: url(theme:Dark/settings/video.svg); ++ qproperty-windowCapIcon: url(theme:Dark/sources/window.svg); ++ qproperty-gameCapIcon: url(theme:Dark/sources/gamepad.svg); ++ qproperty-cameraIcon: url(theme:Dark/sources/camera.svg); ++ qproperty-textIcon: url(theme:Dark/sources/text.svg); ++ qproperty-mediaIcon: url(theme:Dark/sources/media.svg); ++ qproperty-browserIcon: url(theme:Dark/sources/globe.svg); ++ qproperty-groupIcon: url(theme:Dark/sources/group.svg); ++ qproperty-sceneIcon: url(theme:Dark/sources/scene.svg); ++ qproperty-defaultIcon: url(theme:Dark/sources/default.svg); ++ qproperty-audioProcessOutputIcon: url(theme:Dark/sources/windowaudio.svg); + } + + /* Scene Tree */ +@@ -903,7 +903,7 @@ SceneTree { + /* Save icon */ + + * [themeID="replayIconSmall"] { +- qproperty-icon: url(./Dark/save.svg); ++ qproperty-icon: url(theme:Dark/save.svg); + } + + /* Studio Mode T-Bar */ +@@ -933,32 +933,32 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { + /* Media icons */ + + * [themeID="playIcon"] { +- qproperty-icon: url(./Dark/media/media_play.svg); ++ qproperty-icon: url(theme:Dark/media/media_play.svg); + } + + * [themeID="pauseIcon"] { +- qproperty-icon: url(./Dark/media/media_pause.svg); ++ qproperty-icon: url(theme:Dark/media/media_pause.svg); + } + + * [themeID="restartIcon"] { +- qproperty-icon: url(./Dark/media/media_restart.svg); ++ qproperty-icon: url(theme:Dark/media/media_restart.svg); + } + + * [themeID="stopIcon"] { +- qproperty-icon: url(./Dark/media/media_stop.svg); ++ qproperty-icon: url(theme:Dark/media/media_stop.svg); + } + + * [themeID="nextIcon"] { +- qproperty-icon: url(./Dark/media/media_next.svg); ++ qproperty-icon: url(theme:Dark/media/media_next.svg); + } + + * [themeID="previousIcon"] { +- qproperty-icon: url(./Dark/media/media_previous.svg); ++ qproperty-icon: url(theme:Dark/media/media_previous.svg); + } + + /* YouTube Integration */ + OBSYoutubeActions { +- qproperty-thumbPlaceholder: url(./Dark/sources/image.svg); ++ qproperty-thumbPlaceholder: url(theme:Dark/sources/image.svg); + } + + #ytEventList QLabel { +@@ -981,7 +981,7 @@ OBSYoutubeActions { + /* Calendar Widget */ + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + } + +@@ -1004,7 +1004,7 @@ QCalendarWidget QToolButton { + } + + #qt_calendar_monthbutton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + padding-top: 2px; + padding-right: 6px; +@@ -1014,13 +1014,13 @@ QCalendarWidget QToolButton { + + QCalendarWidget #qt_calendar_prevmonth { + padding: 2px; +- qproperty-icon: url(./Dark/left.svg); ++ qproperty-icon: url(theme:Dark/left.svg); + icon-size: 16px, 16px; + } + + QCalendarWidget #qt_calendar_nextmonth { + padding: 2px; +- qproperty-icon: url(./Dark/right.svg); ++ qproperty-icon: url(theme:Dark/right.svg); + icon-size: 16px, 16px; + } + +diff --git a/UI/data/themes/Grey.qss b/UI/data/themes/Grey.qss +index 0748deffa..b19434bfd 100644 +--- a/UI/data/themes/Grey.qss ++++ b/UI/data/themes/Grey.qss +@@ -166,7 +166,7 @@ QMenu::item:disabled { + } + + QMenu::right-arrow { +- image: url(./Dark/expand.svg); ++ image: url(theme:Dark/expand.svg); + } + + /* Top Menu Bar Items */ +@@ -304,8 +304,8 @@ QDockWidget { + font-size: 10.5pt; + font-weight: bold; + +- titlebar-close-icon: url('./Dark/close.svg'); +- titlebar-normal-icon: url('./Dark/popout.svg'); ++ titlebar-close-icon: url(theme:Dark/close.svg); ++ titlebar-normal-icon: url(theme:Dark/popout.svg); + } + + QDockWidget::title { +@@ -453,11 +453,11 @@ QScrollBar::handle:horizontal { + } + + QPushButton#sourcePropertiesButton { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + QPushButton#sourceFiltersButton { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + /* Scenes and Sources toolbar */ +@@ -491,55 +491,55 @@ QToolButton:pressed { + } + + * [themeID="addIconSmall"] { +- qproperty-icon: url(./Dark/plus.svg); ++ qproperty-icon: url(theme:Dark/plus.svg); + } + + * [themeID="removeIconSmall"] { +- qproperty-icon: url(./Dark/trash.svg); ++ qproperty-icon: url(theme:Dark/trash.svg); + } + + * [themeID="clearIconSmall"] { +- qproperty-icon: url(./Dark/entry-clear.svg); ++ qproperty-icon: url(theme:Dark/entry-clear.svg); + } + + * [themeID="propertiesIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="configIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="menuIconSmall"] { +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + * [themeID="refreshIconSmall"] { +- qproperty-icon: url(./Dark/refresh.svg); ++ qproperty-icon: url(theme:Dark/refresh.svg); + } + + * [themeID="cogsIcon"] { +- qproperty-icon: url(./Dark/cogs.svg); ++ qproperty-icon: url(theme:Dark/cogs.svg); + } + + #sourceInteractButton { +- qproperty-icon: url(./Dark/interact.svg); ++ qproperty-icon: url(theme:Dark/interact.svg); + } + + * [themeID="upArrowIconSmall"] { +- qproperty-icon: url(./Dark/up.svg); ++ qproperty-icon: url(theme:Dark/up.svg); + } + + * [themeID="downArrowIconSmall"] { +- qproperty-icon: url(./Dark/down.svg); ++ qproperty-icon: url(theme:Dark/down.svg); + } + + * [themeID="pauseIconSmall"] { +- qproperty-icon: url(./Dark/media-pause.svg); ++ qproperty-icon: url(theme:Dark/media-pause.svg); + } + + * [themeID="filtersIcon"] { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + QToolBarExtension { +@@ -549,7 +549,7 @@ QToolBarExtension { + padding: 4px 0px; + margin-left: 0px; + +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + +@@ -645,7 +645,7 @@ QDateTimeEdit::drop-down { + QComboBox::down-arrow, + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/updown.svg); ++ image: url(theme:Dark/updown.svg); + width: 100%; + } + +@@ -667,7 +667,7 @@ QDateTimeEdit::drop-down:editable { + QComboBox::down-arrow:editable, + QDateTimeEdit::down-arrow:editable { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 8%; + } + +@@ -755,13 +755,13 @@ QDoubleSpinBox::up-button:disabled, QDoubleSpinBox::up-button:off, QDoubleSpinBo + } + + QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { +- image: url(./Dark/up.svg); ++ image: url(theme:Dark/up.svg); + width: 100%; + margin: 2px; + } + + QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + padding: 2px; + } +@@ -833,7 +833,7 @@ QPushButton:disabled, QToolButton:disabled { + } + + QPushButton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + subcontrol-origin: padding; + width: 25px; +@@ -983,15 +983,15 @@ QHeaderView::section { + /* Mute CheckBox */ + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:indeterminate { +- image: url(./Dark/unassigned.svg); ++ image: url(theme:Dark/unassigned.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + OBSHotkeyLabel[hotkeyPairHover=true] { +@@ -1090,14 +1090,14 @@ OBSBasicFilters #widget_2 QPushButton { + /* Settings Icons */ + + OBSBasicSettings { +- qproperty-generalIcon: url(./Dark/settings/general.svg); +- qproperty-streamIcon: url(./Dark/settings/stream.svg); +- qproperty-outputIcon: url(./Dark/settings/output.svg); +- qproperty-audioIcon: url(./Dark/settings/audio.svg); +- qproperty-videoIcon: url(./Dark/settings/video.svg); +- qproperty-hotkeysIcon: url(./Dark/settings/hotkeys.svg); +- qproperty-accessibilityIcon: url(./Dark/settings/accessibility.svg); +- qproperty-advancedIcon: url(./Dark/settings/advanced.svg); ++ qproperty-generalIcon: url(theme:Dark/settings/general.svg); ++ qproperty-streamIcon: url(theme:Dark/settings/stream.svg); ++ qproperty-outputIcon: url(theme:Dark/settings/output.svg); ++ qproperty-audioIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-videoIcon: url(theme:Dark/settings/video.svg); ++ qproperty-hotkeysIcon: url(theme:Dark/settings/hotkeys.svg); ++ qproperty-accessibilityIcon: url(theme:Dark/settings/accessibility.svg); ++ qproperty-advancedIcon: url(theme:Dark/settings/advanced.svg); + } + + /* Checkboxes */ +@@ -1117,34 +1117,34 @@ QGroupBox::indicator { + + QCheckBox::indicator:unchecked, + QGroupBox::indicator:unchecked { +- image: url(./Yami/checkbox_unchecked.svg); ++ image: url(theme:Yami/checkbox_unchecked.svg); + } + + QCheckBox::indicator:unchecked:hover, + QGroupBox::indicator:unchecked:hover { + border: none; +- image: url(./Yami/checkbox_unchecked_focus.svg); ++ image: url(theme:Yami/checkbox_unchecked_focus.svg); + } + + QCheckBox::indicator:checked, + QGroupBox::indicator:checked { +- image: url(./Yami/checkbox_checked.svg); ++ image: url(theme:Yami/checkbox_checked.svg); + } + + QCheckBox::indicator:checked:hover, + QGroupBox::indicator:checked:hover { + border: none; +- image: url(./Yami/checkbox_checked_focus.svg); ++ image: url(theme:Yami/checkbox_checked_focus.svg); + } + + QCheckBox::indicator:checked:disabled, + QGroupBox::indicator:checked:disabled { +- image: url(./Yami/checkbox_checked_disabled.svg); ++ image: url(theme:Yami/checkbox_checked_disabled.svg); + } + + QCheckBox::indicator:unchecked:disabled, + QGroupBox::indicator:unchecked:disabled { +- image: url(./Yami/checkbox_unchecked_disabled.svg); ++ image: url(theme:Yami/checkbox_unchecked_disabled.svg); + } + + /* Locked CheckBox */ +@@ -1161,7 +1161,7 @@ LockedCheckBox::indicator { + + LockedCheckBox::indicator:checked, + LockedCheckBox::indicator:checked:hover { +- image: url(./Dark/locked.svg); ++ image: url(theme:Dark/locked.svg); + } + + LockedCheckBox::indicator:unchecked, +@@ -1183,7 +1183,7 @@ VisibilityCheckBox::indicator { + + VisibilityCheckBox::indicator:checked, + VisibilityCheckBox::indicator:checked:hover { +- image: url(./Dark/visible.svg); ++ image: url(theme:Dark/visible.svg); + } + + VisibilityCheckBox::indicator:unchecked, +@@ -1192,7 +1192,7 @@ VisibilityCheckBox::indicator:unchecked:hover { + } + + * [themeID="revertIcon"] { +- qproperty-icon: url(./Dark/revert.svg); ++ qproperty-icon: url(theme:Dark/revert.svg); + } + + QPushButton#extraPanelDelete { +@@ -1221,35 +1221,35 @@ MuteCheckBox::indicator { + } + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:hover { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:focus { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:checked:hover { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:checked:focus { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:checked:disabled { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:unchecked:disabled { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + #hotkeyFilterReset { +@@ -1279,46 +1279,46 @@ OBSHotkeyWidget QPushButton { + + /* Sources List Group Collapse Checkbox */ + +-SourceTreeSubItemCheckBox { ++QCheckBox[sourceTreeSubItem=true] { + background: transparent; + outline: none; + padding: 0px; + } + +-SourceTreeSubItemCheckBox::indicator { ++QCheckBox[sourceTreeSubItem=true]::indicator { + width: 12px; + height: 12px; + } + +-SourceTreeSubItemCheckBox::indicator:checked, +-SourceTreeSubItemCheckBox::indicator:checked:hover { +- image: url(./Dark/expand.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:checked, ++QCheckBox[sourceTreeSubItem=true]::indicator:checked:hover { ++ image: url(theme:Dark/expand.svg); + } + +-SourceTreeSubItemCheckBox::indicator:unchecked, +-SourceTreeSubItemCheckBox::indicator:unchecked:hover { +- image: url(./Dark/collapse.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked, ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked:hover { ++ image: url(theme:Dark/collapse.svg); + } + + /* Source Icons */ + + OBSBasic { +- qproperty-imageIcon: url(./Dark/sources/image.svg); +- qproperty-colorIcon: url(./Dark/sources/brush.svg); +- qproperty-slideshowIcon: url(./Dark/sources/slideshow.svg); +- qproperty-audioInputIcon: url(./Dark/sources/microphone.svg); +- qproperty-audioOutputIcon: url(./Dark/settings/audio.svg); +- qproperty-desktopCapIcon: url(./Dark/settings/video.svg); +- qproperty-windowCapIcon: url(./Dark/sources/window.svg); +- qproperty-gameCapIcon: url(./Dark/sources/gamepad.svg); +- qproperty-cameraIcon: url(./Dark/sources/camera.svg); +- qproperty-textIcon: url(./Dark/sources/text.svg); +- qproperty-mediaIcon: url(./Dark/sources/media.svg); +- qproperty-browserIcon: url(./Dark/sources/globe.svg); +- qproperty-groupIcon: url(./Dark/sources/group.svg); +- qproperty-sceneIcon: url(./Dark/sources/scene.svg); +- qproperty-defaultIcon: url(./Dark/sources/default.svg); +- qproperty-audioProcessOutputIcon: url(./Dark/sources/windowaudio.svg); ++ qproperty-imageIcon: url(theme:Dark/sources/image.svg); ++ qproperty-colorIcon: url(theme:Dark/sources/brush.svg); ++ qproperty-slideshowIcon: url(theme:Dark/sources/slideshow.svg); ++ qproperty-audioInputIcon: url(theme:Dark/sources/microphone.svg); ++ qproperty-audioOutputIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-desktopCapIcon: url(theme:Dark/settings/video.svg); ++ qproperty-windowCapIcon: url(theme:Dark/sources/window.svg); ++ qproperty-gameCapIcon: url(theme:Dark/sources/gamepad.svg); ++ qproperty-cameraIcon: url(theme:Dark/sources/camera.svg); ++ qproperty-textIcon: url(theme:Dark/sources/text.svg); ++ qproperty-mediaIcon: url(theme:Dark/sources/media.svg); ++ qproperty-browserIcon: url(theme:Dark/sources/globe.svg); ++ qproperty-groupIcon: url(theme:Dark/sources/group.svg); ++ qproperty-sceneIcon: url(theme:Dark/sources/scene.svg); ++ qproperty-defaultIcon: url(theme:Dark/sources/default.svg); ++ qproperty-audioProcessOutputIcon: url(theme:Dark/sources/windowaudio.svg); + } + + /* Scene Tree Grid Mode */ +@@ -1354,7 +1354,7 @@ SceneTree { + /* Save icon */ + + * [themeID="replayIconSmall"] { +- qproperty-icon: url(./Dark/save.svg); ++ qproperty-icon: url(theme:Dark/save.svg); + } + + /* Studio Mode T-Bar */ +@@ -1384,32 +1384,32 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { + /* Media icons */ + + * [themeID="playIcon"] { +- qproperty-icon: url(./Dark/media/media_play.svg); ++ qproperty-icon: url(theme:Dark/media/media_play.svg); + } + + * [themeID="pauseIcon"] { +- qproperty-icon: url(./Dark/media/media_pause.svg); ++ qproperty-icon: url(theme:Dark/media/media_pause.svg); + } + + * [themeID="restartIcon"] { +- qproperty-icon: url(./Dark/media/media_restart.svg); ++ qproperty-icon: url(theme:Dark/media/media_restart.svg); + } + + * [themeID="stopIcon"] { +- qproperty-icon: url(./Dark/media/media_stop.svg); ++ qproperty-icon: url(theme:Dark/media/media_stop.svg); + } + + * [themeID="nextIcon"] { +- qproperty-icon: url(./Dark/media/media_next.svg); ++ qproperty-icon: url(theme:Dark/media/media_next.svg); + } + + * [themeID="previousIcon"] { +- qproperty-icon: url(./Dark/media/media_previous.svg); ++ qproperty-icon: url(theme:Dark/media/media_previous.svg); + } + + /* YouTube Integration */ + OBSYoutubeActions { +- qproperty-thumbPlaceholder: url(./Dark/sources/image.svg); ++ qproperty-thumbPlaceholder: url(theme:Dark/sources/image.svg); + } + + #ytEventList QLabel { +@@ -1437,7 +1437,7 @@ OBSYoutubeActions { + /* Calendar Widget */ + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + } + +@@ -1460,7 +1460,7 @@ QCalendarWidget QToolButton { + } + + #qt_calendar_monthbutton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + padding-top: 2px; + padding-right: 6px; +@@ -1470,13 +1470,13 @@ QCalendarWidget QToolButton { + + QCalendarWidget #qt_calendar_prevmonth { + padding: 2px; +- qproperty-icon: url(./Dark/left.svg); ++ qproperty-icon: url(theme:Dark/left.svg); + icon-size: 16px, 16px; + } + + QCalendarWidget #qt_calendar_nextmonth { + padding: 2px; +- qproperty-icon: url(./Dark/right.svg); ++ qproperty-icon: url(theme:Dark/right.svg); + icon-size: 16px, 16px; + } + +diff --git a/UI/data/themes/Light.qss b/UI/data/themes/Light.qss +index e6c9a6be0..87795ed52 100644 +--- a/UI/data/themes/Light.qss ++++ b/UI/data/themes/Light.qss +@@ -166,7 +166,7 @@ QMenu::item:disabled { + } + + QMenu::right-arrow { +- image: url(./Light/expand.svg); ++ image: url(theme:Light/expand.svg); + } + + /* Top Menu Bar Items */ +@@ -304,8 +304,8 @@ QDockWidget { + font-size: 10.5pt; + font-weight: bold; + +- titlebar-close-icon: url('./Light/close.svg'); +- titlebar-normal-icon: url('./Light/popout.svg'); ++ titlebar-close-icon: url(theme:Light/close.svg); ++ titlebar-normal-icon: url(theme:Light/popout.svg); + } + + QDockWidget::title { +@@ -453,11 +453,11 @@ QScrollBar::handle:horizontal { + } + + QPushButton#sourcePropertiesButton { +- qproperty-icon: url(./Light/settings/general.svg); ++ qproperty-icon: url(theme:Light/settings/general.svg); + } + + QPushButton#sourceFiltersButton { +- qproperty-icon: url(./Light/filter.svg); ++ qproperty-icon: url(theme:Light/filter.svg); + } + + /* Scenes and Sources toolbar */ +@@ -491,55 +491,55 @@ QToolButton:pressed { + } + + * [themeID="addIconSmall"] { +- qproperty-icon: url(./Light/plus.svg); ++ qproperty-icon: url(theme:Light/plus.svg); + } + + * [themeID="removeIconSmall"] { +- qproperty-icon: url(./Light/trash.svg); ++ qproperty-icon: url(theme:Light/trash.svg); + } + + * [themeID="clearIconSmall"] { +- qproperty-icon: url(./Light/entry-clear.svg); ++ qproperty-icon: url(theme:Light/entry-clear.svg); + } + + * [themeID="propertiesIconSmall"] { +- qproperty-icon: url(./Light/settings/general.svg); ++ qproperty-icon: url(theme:Light/settings/general.svg); + } + + * [themeID="configIconSmall"] { +- qproperty-icon: url(./Light/settings/general.svg); ++ qproperty-icon: url(theme:Light/settings/general.svg); + } + + * [themeID="menuIconSmall"] { +- qproperty-icon: url(./Light/dots-vert.svg); ++ qproperty-icon: url(theme:Light/dots-vert.svg); + } + + * [themeID="refreshIconSmall"] { +- qproperty-icon: url(./Light/refresh.svg); ++ qproperty-icon: url(theme:Light/refresh.svg); + } + + * [themeID="cogsIcon"] { +- qproperty-icon: url(./Light/cogs.svg); ++ qproperty-icon: url(theme:Light/cogs.svg); + } + + #sourceInteractButton { +- qproperty-icon: url(./Light/interact.svg); ++ qproperty-icon: url(theme:Light/interact.svg); + } + + * [themeID="upArrowIconSmall"] { +- qproperty-icon: url(./Light/up.svg); ++ qproperty-icon: url(theme:Light/up.svg); + } + + * [themeID="downArrowIconSmall"] { +- qproperty-icon: url(./Light/down.svg); ++ qproperty-icon: url(theme:Light/down.svg); + } + + * [themeID="pauseIconSmall"] { +- qproperty-icon: url(./Light/media-pause.svg); ++ qproperty-icon: url(theme:Light/media-pause.svg); + } + + * [themeID="filtersIcon"] { +- qproperty-icon: url(./Light/filter.svg); ++ qproperty-icon: url(theme:Light/filter.svg); + } + + QToolBarExtension { +@@ -549,7 +549,7 @@ QToolBarExtension { + padding: 4px 0px; + margin-left: 0px; + +- qproperty-icon: url(./Light/dots-vert.svg); ++ qproperty-icon: url(theme:Light/dots-vert.svg); + } + + +@@ -645,7 +645,7 @@ QDateTimeEdit::drop-down { + QComboBox::down-arrow, + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Light/updown.svg); ++ image: url(theme:Light/updown.svg); + width: 100%; + } + +@@ -667,7 +667,7 @@ QDateTimeEdit::drop-down:editable { + QComboBox::down-arrow:editable, + QDateTimeEdit::down-arrow:editable { + qproperty-alignment: AlignTop; +- image: url(./Light/down.svg); ++ image: url(theme:Light/down.svg); + width: 8%; + } + +@@ -755,13 +755,13 @@ QDoubleSpinBox::up-button:disabled, QDoubleSpinBox::up-button:off, QDoubleSpinBo + } + + QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { +- image: url(./Light/up.svg); ++ image: url(theme:Light/up.svg); + width: 100%; + margin: 2px; + } + + QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { +- image: url(./Light/down.svg); ++ image: url(theme:Light/down.svg); + width: 100%; + padding: 2px; + } +@@ -833,7 +833,7 @@ QPushButton:disabled, QToolButton:disabled { + } + + QPushButton::menu-indicator { +- image: url(./Light/down.svg); ++ image: url(theme:Light/down.svg); + subcontrol-position: right; + subcontrol-origin: padding; + width: 25px; +@@ -983,15 +983,15 @@ QHeaderView::section { + /* Mute CheckBox */ + + MuteCheckBox::indicator:checked { +- image: url(./Light/mute.svg); ++ image: url(theme:Light/mute.svg); + } + + MuteCheckBox::indicator:indeterminate { +- image: url(./Dark/unassigned.svg); ++ image: url(theme:Dark/unassigned.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Light/settings/audio.svg); ++ image: url(theme:Light/settings/audio.svg); + } + + OBSHotkeyLabel[hotkeyPairHover=true] { +@@ -1090,14 +1090,14 @@ OBSBasicFilters #widget_2 QPushButton { + /* Settings Icons */ + + OBSBasicSettings { +- qproperty-generalIcon: url(./Light/settings/general.svg); +- qproperty-streamIcon: url(./Light/settings/stream.svg); +- qproperty-outputIcon: url(./Light/settings/output.svg); +- qproperty-audioIcon: url(./Light/settings/audio.svg); +- qproperty-videoIcon: url(./Light/settings/video.svg); +- qproperty-hotkeysIcon: url(./Light/settings/hotkeys.svg); +- qproperty-accessibilityIcon: url(./Light/settings/accessibility.svg); +- qproperty-advancedIcon: url(./Light/settings/advanced.svg); ++ qproperty-generalIcon: url(theme:Light/settings/general.svg); ++ qproperty-streamIcon: url(theme:Light/settings/stream.svg); ++ qproperty-outputIcon: url(theme:Light/settings/output.svg); ++ qproperty-audioIcon: url(theme:Light/settings/audio.svg); ++ qproperty-videoIcon: url(theme:Light/settings/video.svg); ++ qproperty-hotkeysIcon: url(theme:Light/settings/hotkeys.svg); ++ qproperty-accessibilityIcon: url(theme:Light/settings/accessibility.svg); ++ qproperty-advancedIcon: url(theme:Light/settings/advanced.svg); + } + + /* Checkboxes */ +@@ -1117,34 +1117,34 @@ QGroupBox::indicator { + + QCheckBox::indicator:unchecked, + QGroupBox::indicator:unchecked { +- image: url(./Light/checkbox_unchecked.svg); ++ image: url(theme:Light/checkbox_unchecked.svg); + } + + QCheckBox::indicator:unchecked:hover, + QGroupBox::indicator:unchecked:hover { + border: none; +- image: url(./Light/checkbox_unchecked_focus.svg); ++ image: url(theme:Light/checkbox_unchecked_focus.svg); + } + + QCheckBox::indicator:checked, + QGroupBox::indicator:checked { +- image: url(./Light/checkbox_checked.svg); ++ image: url(theme:Light/checkbox_checked.svg); + } + + QCheckBox::indicator:checked:hover, + QGroupBox::indicator:checked:hover { + border: none; +- image: url(./Light/checkbox_checked_focus.svg); ++ image: url(theme:Light/checkbox_checked_focus.svg); + } + + QCheckBox::indicator:checked:disabled, + QGroupBox::indicator:checked:disabled { +- image: url(./Light/checkbox_checked_disabled.svg); ++ image: url(theme:Light/checkbox_checked_disabled.svg); + } + + QCheckBox::indicator:unchecked:disabled, + QGroupBox::indicator:unchecked:disabled { +- image: url(./Light/checkbox_unchecked_disabled.svg); ++ image: url(theme:Light/checkbox_unchecked_disabled.svg); + } + + /* Locked CheckBox */ +@@ -1161,7 +1161,7 @@ LockedCheckBox::indicator { + + LockedCheckBox::indicator:checked, + LockedCheckBox::indicator:checked:hover { +- image: url(./Light/locked.svg); ++ image: url(theme:Light/locked.svg); + } + + LockedCheckBox::indicator:unchecked, +@@ -1183,7 +1183,7 @@ VisibilityCheckBox::indicator { + + VisibilityCheckBox::indicator:checked, + VisibilityCheckBox::indicator:checked:hover { +- image: url(./Light/visible.svg); ++ image: url(theme:Light/visible.svg); + } + + VisibilityCheckBox::indicator:unchecked, +@@ -1192,7 +1192,7 @@ VisibilityCheckBox::indicator:unchecked:hover { + } + + * [themeID="revertIcon"] { +- qproperty-icon: url(./Light/revert.svg); ++ qproperty-icon: url(theme:Light/revert.svg); + } + + QPushButton#extraPanelDelete { +@@ -1221,35 +1221,35 @@ MuteCheckBox::indicator { + } + + MuteCheckBox::indicator:checked { +- image: url(./Light/mute.svg); ++ image: url(theme:Light/mute.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Light/settings/audio.svg); ++ image: url(theme:Light/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:hover { +- image: url(./Light/settings/audio.svg); ++ image: url(theme:Light/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:focus { +- image: url(./Light/settings/audio.svg); ++ image: url(theme:Light/settings/audio.svg); + } + + MuteCheckBox::indicator:checked:hover { +- image: url(./Light/mute.svg); ++ image: url(theme:Light/mute.svg); + } + + MuteCheckBox::indicator:checked:focus { +- image: url(./Light/mute.svg); ++ image: url(theme:Light/mute.svg); + } + + MuteCheckBox::indicator:checked:disabled { +- image: url(./Light/mute.svg); ++ image: url(theme:Light/mute.svg); + } + + MuteCheckBox::indicator:unchecked:disabled { +- image: url(./Light/settings/audio.svg); ++ image: url(theme:Light/settings/audio.svg); + } + + #hotkeyFilterReset { +@@ -1279,46 +1279,46 @@ OBSHotkeyWidget QPushButton { + + /* Sources List Group Collapse Checkbox */ + +-SourceTreeSubItemCheckBox { ++QCheckBox[sourceTreeSubItem=true] { + background: transparent; + outline: none; + padding: 0px; + } + +-SourceTreeSubItemCheckBox::indicator { ++QCheckBox[sourceTreeSubItem=true]::indicator { + width: 12px; + height: 12px; + } + +-SourceTreeSubItemCheckBox::indicator:checked, +-SourceTreeSubItemCheckBox::indicator:checked:hover { +- image: url(./Light/expand.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:checked, ++QCheckBox[sourceTreeSubItem=true]::indicator:checked:hover { ++ image: url(theme:Light/expand.svg); + } + +-SourceTreeSubItemCheckBox::indicator:unchecked, +-SourceTreeSubItemCheckBox::indicator:unchecked:hover { +- image: url(./Light/collapse.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked, ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked:hover { ++ image: url(theme:Light/collapse.svg); + } + + /* Source Icons */ + + OBSBasic { +- qproperty-imageIcon: url(./Light/sources/image.svg); +- qproperty-colorIcon: url(./Light/sources/brush.svg); +- qproperty-slideshowIcon: url(./Light/sources/slideshow.svg); +- qproperty-audioInputIcon: url(./Light/sources/microphone.svg); +- qproperty-audioOutputIcon: url(./Light/settings/audio.svg); +- qproperty-desktopCapIcon: url(./Light/settings/video.svg); +- qproperty-windowCapIcon: url(./Light/sources/window.svg); +- qproperty-gameCapIcon: url(./Light/sources/gamepad.svg); +- qproperty-cameraIcon: url(./Light/sources/camera.svg); +- qproperty-textIcon: url(./Light/sources/text.svg); +- qproperty-mediaIcon: url(./Light/sources/media.svg); +- qproperty-browserIcon: url(./Light/sources/globe.svg); +- qproperty-groupIcon: url(./Light/sources/group.svg); +- qproperty-sceneIcon: url(./Light/sources/scene.svg); +- qproperty-defaultIcon: url(./Light/sources/default.svg); +- qproperty-audioProcessOutputIcon: url(./Light/sources/windowaudio.svg); ++ qproperty-imageIcon: url(theme:Light/sources/image.svg); ++ qproperty-colorIcon: url(theme:Light/sources/brush.svg); ++ qproperty-slideshowIcon: url(theme:Light/sources/slideshow.svg); ++ qproperty-audioInputIcon: url(theme:Light/sources/microphone.svg); ++ qproperty-audioOutputIcon: url(theme:Light/settings/audio.svg); ++ qproperty-desktopCapIcon: url(theme:Light/settings/video.svg); ++ qproperty-windowCapIcon: url(theme:Light/sources/window.svg); ++ qproperty-gameCapIcon: url(theme:Light/sources/gamepad.svg); ++ qproperty-cameraIcon: url(theme:Light/sources/camera.svg); ++ qproperty-textIcon: url(theme:Light/sources/text.svg); ++ qproperty-mediaIcon: url(theme:Light/sources/media.svg); ++ qproperty-browserIcon: url(theme:Light/sources/globe.svg); ++ qproperty-groupIcon: url(theme:Light/sources/group.svg); ++ qproperty-sceneIcon: url(theme:Light/sources/scene.svg); ++ qproperty-defaultIcon: url(theme:Light/sources/default.svg); ++ qproperty-audioProcessOutputIcon: url(theme:Light/sources/windowaudio.svg); + } + + /* Scene Tree Grid Mode */ +@@ -1354,7 +1354,7 @@ SceneTree { + /* Save icon */ + + * [themeID="replayIconSmall"] { +- qproperty-icon: url(./Light/save.svg); ++ qproperty-icon: url(theme:Light/save.svg); + } + + /* Studio Mode Labels */ +@@ -1390,32 +1390,32 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { + /* Media icons */ + + * [themeID="playIcon"] { +- qproperty-icon: url(./Light/media/media_play.svg); ++ qproperty-icon: url(theme:Light/media/media_play.svg); + } + + * [themeID="pauseIcon"] { +- qproperty-icon: url(./Light/media/media_pause.svg); ++ qproperty-icon: url(theme:Light/media/media_pause.svg); + } + + * [themeID="restartIcon"] { +- qproperty-icon: url(./Light/media/media_restart.svg); ++ qproperty-icon: url(theme:Light/media/media_restart.svg); + } + + * [themeID="stopIcon"] { +- qproperty-icon: url(./Light/media/media_stop.svg); ++ qproperty-icon: url(theme:Light/media/media_stop.svg); + } + + * [themeID="nextIcon"] { +- qproperty-icon: url(./Light/media/media_next.svg); ++ qproperty-icon: url(theme:Light/media/media_next.svg); + } + + * [themeID="previousIcon"] { +- qproperty-icon: url(./Light/media/media_previous.svg); ++ qproperty-icon: url(theme:Light/media/media_previous.svg); + } + + /* YouTube Integration */ + OBSYoutubeActions { +- qproperty-thumbPlaceholder: url(./Light/sources/image.svg); ++ qproperty-thumbPlaceholder: url(theme:Light/sources/image.svg); + } + + #ytEventList QLabel { +@@ -1443,7 +1443,7 @@ OBSYoutubeActions { + /* Calendar Widget */ + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Light/down.svg); ++ image: url(theme:Light/down.svg); + width: 100%; + } + +@@ -1466,7 +1466,7 @@ QCalendarWidget QToolButton { + } + + #qt_calendar_monthbutton::menu-indicator { +- image: url(./Light/down.svg); ++ image: url(theme:Light/down.svg); + subcontrol-position: right; + padding-top: 2px; + padding-right: 6px; +@@ -1476,13 +1476,13 @@ QCalendarWidget QToolButton { + + QCalendarWidget #qt_calendar_prevmonth { + padding: 2px; +- qproperty-icon: url(./Light/left.svg); ++ qproperty-icon: url(theme:Light/left.svg); + icon-size: 16px, 16px; + } + + QCalendarWidget #qt_calendar_nextmonth { + padding: 2px; +- qproperty-icon: url(./Light/right.svg); ++ qproperty-icon: url(theme:Light/right.svg); + icon-size: 16px, 16px; + } + +diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss +index 92880cfd0..579737ea9 100644 +--- a/UI/data/themes/Rachni.qss ++++ b/UI/data/themes/Rachni.qss +@@ -168,7 +168,7 @@ QMenu::item:disabled { + } + + QMenu::right-arrow { +- image: url(./Dark/expand.svg); ++ image: url(theme:Dark/expand.svg); + } + + /* Top Menu Bar Items */ +@@ -306,8 +306,8 @@ QDockWidget { + font-size: 10.5pt; + font-weight: bold; + +- titlebar-close-icon: url('./Dark/close.svg'); +- titlebar-normal-icon: url('./Dark/popout.svg'); ++ titlebar-close-icon: url(theme:Dark/close.svg); ++ titlebar-normal-icon: url(theme:Dark/popout.svg); + } + + QDockWidget::title { +@@ -456,11 +456,11 @@ QScrollBar::handle:horizontal { + } + + QPushButton#sourcePropertiesButton { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + QPushButton#sourceFiltersButton { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + /* Scenes and Sources toolbar */ +@@ -499,55 +499,55 @@ QToolButton:pressed { + } + + * [themeID="addIconSmall"] { +- qproperty-icon: url(./Dark/plus.svg); ++ qproperty-icon: url(theme:Dark/plus.svg); + } + + * [themeID="removeIconSmall"] { +- qproperty-icon: url(./Dark/trash.svg); ++ qproperty-icon: url(theme:Dark/trash.svg); + } + + * [themeID="clearIconSmall"] { +- qproperty-icon: url(./Dark/entry-clear.svg); ++ qproperty-icon: url(theme:Dark/entry-clear.svg); + } + + * [themeID="propertiesIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="configIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="menuIconSmall"] { +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + * [themeID="refreshIconSmall"] { +- qproperty-icon: url(./Dark/refresh.svg); ++ qproperty-icon: url(theme:Dark/refresh.svg); + } + + * [themeID="cogsIcon"] { +- qproperty-icon: url(./Dark/cogs.svg); ++ qproperty-icon: url(theme:Dark/cogs.svg); + } + + #sourceInteractButton { +- qproperty-icon: url(./Dark/interact.svg); ++ qproperty-icon: url(theme:Dark/interact.svg); + } + + * [themeID="upArrowIconSmall"] { +- qproperty-icon: url(./Dark/up.svg); ++ qproperty-icon: url(theme:Dark/up.svg); + } + + * [themeID="downArrowIconSmall"] { +- qproperty-icon: url(./Dark/down.svg); ++ qproperty-icon: url(theme:Dark/down.svg); + } + + * [themeID="pauseIconSmall"] { +- qproperty-icon: url(./Dark/media-pause.svg); ++ qproperty-icon: url(theme:Dark/media-pause.svg); + } + + * [themeID="filtersIcon"] { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + QToolBarExtension { +@@ -557,7 +557,7 @@ QToolBarExtension { + padding: 4px 0px; + margin-left: 0px; + +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + +@@ -652,7 +652,7 @@ QDateTimeEdit::drop-down { + QComboBox::down-arrow, + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/updown.svg); ++ image: url(theme:Dark/updown.svg); + width: 100%; + } + +@@ -674,7 +674,7 @@ QDateTimeEdit::drop-down:editable { + QComboBox::down-arrow:editable, + QDateTimeEdit::down-arrow:editable { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 8%; + } + +@@ -759,13 +759,13 @@ QDoubleSpinBox::up-button:disabled, QDoubleSpinBox::up-button:off, QDoubleSpinBo + } + + QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { +- image: url(./Dark/up.svg); ++ image: url(theme:Dark/up.svg); + width: 100%; + margin: 2px; + } + + QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + padding: 2px; + } +@@ -837,7 +837,7 @@ QPushButton:disabled, QToolButton:disabled { + } + + QPushButton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + subcontrol-origin: padding; + width: 25px; +@@ -987,15 +987,15 @@ QHeaderView::section { + /* Mute CheckBox */ + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:indeterminate { +- image: url(./Dark/unassigned.svg); ++ image: url(theme:Dark/unassigned.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + OBSHotkeyLabel[hotkeyPairHover=true] { +@@ -1094,14 +1094,14 @@ OBSBasicFilters #widget_2 QPushButton { + /* Settings Icons */ + + OBSBasicSettings { +- qproperty-generalIcon: url(./Dark/settings/general.svg); +- qproperty-streamIcon: url(./Dark/settings/stream.svg); +- qproperty-outputIcon: url(./Dark/settings/output.svg); +- qproperty-audioIcon: url(./Dark/settings/audio.svg); +- qproperty-videoIcon: url(./Dark/settings/video.svg); +- qproperty-hotkeysIcon: url(./Dark/settings/hotkeys.svg); +- qproperty-accessibilityIcon: url(./Dark/settings/accessibility.svg); +- qproperty-advancedIcon: url(./Dark/settings/advanced.svg); ++ qproperty-generalIcon: url(theme:Dark/settings/general.svg); ++ qproperty-streamIcon: url(theme:Dark/settings/stream.svg); ++ qproperty-outputIcon: url(theme:Dark/settings/output.svg); ++ qproperty-audioIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-videoIcon: url(theme:Dark/settings/video.svg); ++ qproperty-hotkeysIcon: url(theme:Dark/settings/hotkeys.svg); ++ qproperty-accessibilityIcon: url(theme:Dark/settings/accessibility.svg); ++ qproperty-advancedIcon: url(theme:Dark/settings/advanced.svg); + } + + /* Checkboxes */ +@@ -1121,34 +1121,34 @@ QGroupBox::indicator { + + QCheckBox::indicator:unchecked, + QGroupBox::indicator:unchecked { +- image: url(./Yami/checkbox_unchecked.svg); ++ image: url(theme:Yami/checkbox_unchecked.svg); + } + + QCheckBox::indicator:unchecked:hover, + QGroupBox::indicator:unchecked:hover { + border: none; +- image: url(./Yami/checkbox_unchecked_focus.svg); ++ image: url(theme:Yami/checkbox_unchecked_focus.svg); + } + + QCheckBox::indicator:checked, + QGroupBox::indicator:checked { +- image: url(./Yami/checkbox_checked.svg); ++ image: url(theme:Yami/checkbox_checked.svg); + } + + QCheckBox::indicator:checked:hover, + QGroupBox::indicator:checked:hover { + border: none; +- image: url(./Yami/checkbox_checked_focus.svg); ++ image: url(theme:Yami/checkbox_checked_focus.svg); + } + + QCheckBox::indicator:checked:disabled, + QGroupBox::indicator:checked:disabled { +- image: url(./Yami/checkbox_checked_disabled.svg); ++ image: url(theme:Yami/checkbox_checked_disabled.svg); + } + + QCheckBox::indicator:unchecked:disabled, + QGroupBox::indicator:unchecked:disabled { +- image: url(./Yami/checkbox_unchecked_disabled.svg); ++ image: url(theme:Yami/checkbox_unchecked_disabled.svg); + } + + /* Locked CheckBox */ +@@ -1165,7 +1165,7 @@ LockedCheckBox::indicator { + + LockedCheckBox::indicator:checked, + LockedCheckBox::indicator:checked:hover { +- image: url(./Dark/locked.svg); ++ image: url(theme:Dark/locked.svg); + } + + LockedCheckBox::indicator:unchecked, +@@ -1187,7 +1187,7 @@ VisibilityCheckBox::indicator { + + VisibilityCheckBox::indicator:checked, + VisibilityCheckBox::indicator:checked:hover { +- image: url(./Dark/visible.svg); ++ image: url(theme:Dark/visible.svg); + } + + VisibilityCheckBox::indicator:unchecked, +@@ -1196,7 +1196,7 @@ VisibilityCheckBox::indicator:unchecked:hover { + } + + * [themeID="revertIcon"] { +- qproperty-icon: url(./Dark/revert.svg); ++ qproperty-icon: url(theme:Dark/revert.svg); + } + + QPushButton#extraPanelDelete { +@@ -1225,35 +1225,35 @@ MuteCheckBox::indicator { + } + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:hover { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:focus { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:checked:hover { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:checked:focus { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:checked:disabled { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:unchecked:disabled { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + #hotkeyFilterReset { +@@ -1283,46 +1283,46 @@ OBSHotkeyWidget QPushButton { + + /* Sources List Group Collapse Checkbox */ + +-SourceTreeSubItemCheckBox { ++QCheckBox[sourceTreeSubItem=true] { + background: transparent; + outline: none; + padding: 0px; + } + +-SourceTreeSubItemCheckBox::indicator { ++QCheckBox[sourceTreeSubItem=true]::indicator { + width: 12px; + height: 12px; + } + +-SourceTreeSubItemCheckBox::indicator:checked, +-SourceTreeSubItemCheckBox::indicator:checked:hover { +- image: url(./Dark/expand.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:checked, ++QCheckBox[sourceTreeSubItem=true]::indicator:checked:hover { ++ image: url(theme:Dark/expand.svg); + } + +-SourceTreeSubItemCheckBox::indicator:unchecked, +-SourceTreeSubItemCheckBox::indicator:unchecked:hover { +- image: url(./Dark/collapse.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked, ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked:hover { ++ image: url(theme:Dark/collapse.svg); + } + + /* Source Icons */ + + OBSBasic { +- qproperty-imageIcon: url(./Dark/sources/image.svg); +- qproperty-colorIcon: url(./Dark/sources/brush.svg); +- qproperty-slideshowIcon: url(./Dark/sources/slideshow.svg); +- qproperty-audioInputIcon: url(./Dark/sources/microphone.svg); +- qproperty-audioOutputIcon: url(./Dark/settings/audio.svg); +- qproperty-desktopCapIcon: url(./Dark/settings/video.svg); +- qproperty-windowCapIcon: url(./Dark/sources/window.svg); +- qproperty-gameCapIcon: url(./Dark/sources/gamepad.svg); +- qproperty-cameraIcon: url(./Dark/sources/camera.svg); +- qproperty-textIcon: url(./Dark/sources/text.svg); +- qproperty-mediaIcon: url(./Dark/sources/media.svg); +- qproperty-browserIcon: url(./Dark/sources/globe.svg); +- qproperty-groupIcon: url(./Dark/sources/group.svg); +- qproperty-sceneIcon: url(./Dark/sources/scene.svg); +- qproperty-defaultIcon: url(./Dark/sources/default.svg); +- qproperty-audioProcessOutputIcon: url(./Dark/sources/windowaudio.svg); ++ qproperty-imageIcon: url(theme:Dark/sources/image.svg); ++ qproperty-colorIcon: url(theme:Dark/sources/brush.svg); ++ qproperty-slideshowIcon: url(theme:Dark/sources/slideshow.svg); ++ qproperty-audioInputIcon: url(theme:Dark/sources/microphone.svg); ++ qproperty-audioOutputIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-desktopCapIcon: url(theme:Dark/settings/video.svg); ++ qproperty-windowCapIcon: url(theme:Dark/sources/window.svg); ++ qproperty-gameCapIcon: url(theme:Dark/sources/gamepad.svg); ++ qproperty-cameraIcon: url(theme:Dark/sources/camera.svg); ++ qproperty-textIcon: url(theme:Dark/sources/text.svg); ++ qproperty-mediaIcon: url(theme:Dark/sources/media.svg); ++ qproperty-browserIcon: url(theme:Dark/sources/globe.svg); ++ qproperty-groupIcon: url(theme:Dark/sources/group.svg); ++ qproperty-sceneIcon: url(theme:Dark/sources/scene.svg); ++ qproperty-defaultIcon: url(theme:Dark/sources/default.svg); ++ qproperty-audioProcessOutputIcon: url(theme:Dark/sources/windowaudio.svg); + } + + /* Scene Tree Grid Mode */ +@@ -1358,7 +1358,7 @@ SceneTree { + /* Save icon */ + + * [themeID="replayIconSmall"] { +- qproperty-icon: url(./Dark/save.svg); ++ qproperty-icon: url(theme:Dark/save.svg); + } + + /* Studio Mode T-Bar */ +@@ -1388,32 +1388,32 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { + /* Media icons */ + + * [themeID="playIcon"] { +- qproperty-icon: url(./Dark/media/media_play.svg); ++ qproperty-icon: url(theme:Dark/media/media_play.svg); + } + + * [themeID="pauseIcon"] { +- qproperty-icon: url(./Dark/media/media_pause.svg); ++ qproperty-icon: url(theme:Dark/media/media_pause.svg); + } + + * [themeID="restartIcon"] { +- qproperty-icon: url(./Dark/media/media_restart.svg); ++ qproperty-icon: url(theme:Dark/media/media_restart.svg); + } + + * [themeID="stopIcon"] { +- qproperty-icon: url(./Dark/media/media_stop.svg); ++ qproperty-icon: url(theme:Dark/media/media_stop.svg); + } + + * [themeID="nextIcon"] { +- qproperty-icon: url(./Dark/media/media_next.svg); ++ qproperty-icon: url(theme:Dark/media/media_next.svg); + } + + * [themeID="previousIcon"] { +- qproperty-icon: url(./Dark/media/media_previous.svg); ++ qproperty-icon: url(theme:Dark/media/media_previous.svg); + } + + /* YouTube Integration */ + OBSYoutubeActions { +- qproperty-thumbPlaceholder: url(./Dark/sources/image.svg); ++ qproperty-thumbPlaceholder: url(theme:Dark/sources/image.svg); + } + + #ytEventList QLabel { +@@ -1441,7 +1441,7 @@ OBSYoutubeActions { + /* Calendar Widget */ + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + } + +@@ -1464,7 +1464,7 @@ QCalendarWidget QToolButton { + } + + #qt_calendar_monthbutton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + padding-top: 2px; + padding-right: 6px; +@@ -1474,13 +1474,13 @@ QCalendarWidget QToolButton { + + QCalendarWidget #qt_calendar_prevmonth { + padding: 2px; +- qproperty-icon: url(./Dark/left.svg); ++ qproperty-icon: url(theme:Dark/left.svg); + icon-size: 16px, 16px; + } + + QCalendarWidget #qt_calendar_nextmonth { + padding: 2px; +- qproperty-icon: url(./Dark/right.svg); ++ qproperty-icon: url(theme:Dark/right.svg); + icon-size: 16px, 16px; + } + +diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss +index 94c446673..a2476d367 100644 +--- a/UI/data/themes/System.qss ++++ b/UI/data/themes/System.qss +@@ -71,28 +71,28 @@ MuteCheckBox::indicator:checked { + } + + MuteCheckBox::indicator:indeterminate { +- image: url(./Dark/unassigned.svg); ++ image: url(theme:Dark/unassigned.svg); + } + + MuteCheckBox::indicator:unchecked { + image: url(:/settings/images/settings/audio.svg); + } + +-SourceTreeSubItemCheckBox { ++QCheckBox[sourceTreeSubItem=true] { + background: transparent; + outline: none; + } + +-SourceTreeSubItemCheckBox::indicator { ++QCheckBox[sourceTreeSubItem=true]::indicator { + width: 10px; + height: 10px; + } + +-SourceTreeSubItemCheckBox::indicator:checked { ++QCheckBox[sourceTreeSubItem=true]::indicator:checked { + image: url(:/res/images/expand.svg); + } + +-SourceTreeSubItemCheckBox::indicator:unchecked { ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked { + image: url(:/res/images/collapse.svg); + } + +@@ -378,7 +378,7 @@ QCalendarWidget QToolButton { + } + + #qt_calendar_monthbutton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + padding-top: 2px; + padding-right: 2px; +@@ -393,13 +393,13 @@ QCalendarWidget QToolButton { + + QCalendarWidget #qt_calendar_prevmonth { + padding: 2px; +- qproperty-icon: url(./Dark/left.svg); ++ qproperty-icon: url(theme:Dark/left.svg); + icon-size: 16px, 16px; + } + + QCalendarWidget #qt_calendar_nextmonth { + padding: 2px; +- qproperty-icon: url(./Dark/right.svg); ++ qproperty-icon: url(theme:Dark/right.svg); + icon-size: 16px, 16px; + } + +diff --git a/UI/data/themes/Yami.qss b/UI/data/themes/Yami.qss +index b32a7b47d..819b507ec 100644 +--- a/UI/data/themes/Yami.qss ++++ b/UI/data/themes/Yami.qss +@@ -166,7 +166,7 @@ QMenu::item:disabled { + } + + QMenu::right-arrow { +- image: url(./Dark/expand.svg); ++ image: url(theme:Dark/expand.svg); + } + + /* Top Menu Bar Items */ +@@ -308,8 +308,8 @@ QDockWidget { + font-size: 10.5pt; + font-weight: bold; + +- titlebar-close-icon: url('./Dark/close.svg'); +- titlebar-normal-icon: url('./Dark/popout.svg'); ++ titlebar-close-icon: url(theme:Dark/close.svg); ++ titlebar-normal-icon: url(theme:Dark/popout.svg); + } + + QDockWidget::title { +@@ -457,11 +457,11 @@ QScrollBar::handle:horizontal { + } + + QPushButton#sourcePropertiesButton { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + QPushButton#sourceFiltersButton { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + /* Scenes and Sources toolbar */ +@@ -495,55 +495,55 @@ QToolButton:pressed { + } + + * [themeID="addIconSmall"] { +- qproperty-icon: url(./Dark/plus.svg); ++ qproperty-icon: url(theme:Dark/plus.svg); + } + + * [themeID="removeIconSmall"] { +- qproperty-icon: url(./Dark/trash.svg); ++ qproperty-icon: url(theme:Dark/trash.svg); + } + + * [themeID="clearIconSmall"] { +- qproperty-icon: url(./Dark/entry-clear.svg); ++ qproperty-icon: url(theme:Dark/entry-clear.svg); + } + + * [themeID="propertiesIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="configIconSmall"] { +- qproperty-icon: url(./Dark/settings/general.svg); ++ qproperty-icon: url(theme:Dark/settings/general.svg); + } + + * [themeID="menuIconSmall"] { +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + * [themeID="refreshIconSmall"] { +- qproperty-icon: url(./Dark/refresh.svg); ++ qproperty-icon: url(theme:Dark/refresh.svg); + } + + * [themeID="cogsIcon"] { +- qproperty-icon: url(./Dark/cogs.svg); ++ qproperty-icon: url(theme:Dark/cogs.svg); + } + + #sourceInteractButton { +- qproperty-icon: url(./Dark/interact.svg); ++ qproperty-icon: url(theme:Dark/interact.svg); + } + + * [themeID="upArrowIconSmall"] { +- qproperty-icon: url(./Dark/up.svg); ++ qproperty-icon: url(theme:Dark/up.svg); + } + + * [themeID="downArrowIconSmall"] { +- qproperty-icon: url(./Dark/down.svg); ++ qproperty-icon: url(theme:Dark/down.svg); + } + + * [themeID="pauseIconSmall"] { +- qproperty-icon: url(./Dark/media-pause.svg); ++ qproperty-icon: url(theme:Dark/media-pause.svg); + } + + * [themeID="filtersIcon"] { +- qproperty-icon: url(./Dark/filter.svg); ++ qproperty-icon: url(theme:Dark/filter.svg); + } + + QToolBarExtension { +@@ -553,7 +553,7 @@ QToolBarExtension { + padding: 4px 0px; + margin-left: 0px; + +- qproperty-icon: url(./Dark/dots-vert.svg); ++ qproperty-icon: url(theme:Dark/dots-vert.svg); + } + + +@@ -649,7 +649,7 @@ QDateTimeEdit::drop-down { + QComboBox::down-arrow, + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/updown.svg); ++ image: url(theme:Dark/updown.svg); + width: 100%; + } + +@@ -671,7 +671,7 @@ QDateTimeEdit::drop-down:editable { + QComboBox::down-arrow:editable, + QDateTimeEdit::down-arrow:editable { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 8%; + } + +@@ -759,13 +759,13 @@ QDoubleSpinBox::up-button:disabled, QDoubleSpinBox::up-button:off, QDoubleSpinBo + } + + QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { +- image: url(./Dark/up.svg); ++ image: url(theme:Dark/up.svg); + width: 100%; + margin: 2px; + } + + QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + padding: 2px; + } +@@ -837,7 +837,7 @@ QPushButton:disabled, QToolButton:disabled { + } + + QPushButton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + subcontrol-origin: padding; + width: 25px; +@@ -987,15 +987,15 @@ QHeaderView::section { + /* Mute CheckBox */ + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:indeterminate { +- image: url(./Dark/unassigned.svg); ++ image: url(theme:Dark/unassigned.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + OBSHotkeyLabel[hotkeyPairHover=true] { +@@ -1094,14 +1094,14 @@ OBSBasicFilters #widget_2 QPushButton { + /* Settings Icons */ + + OBSBasicSettings { +- qproperty-generalIcon: url(./Dark/settings/general.svg); +- qproperty-streamIcon: url(./Dark/settings/stream.svg); +- qproperty-outputIcon: url(./Dark/settings/output.svg); +- qproperty-audioIcon: url(./Dark/settings/audio.svg); +- qproperty-videoIcon: url(./Dark/settings/video.svg); +- qproperty-hotkeysIcon: url(./Dark/settings/hotkeys.svg); +- qproperty-accessibilityIcon: url(./Dark/settings/accessibility.svg); +- qproperty-advancedIcon: url(./Dark/settings/advanced.svg); ++ qproperty-generalIcon: url(theme:Dark/settings/general.svg); ++ qproperty-streamIcon: url(theme:Dark/settings/stream.svg); ++ qproperty-outputIcon: url(theme:Dark/settings/output.svg); ++ qproperty-audioIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-videoIcon: url(theme:Dark/settings/video.svg); ++ qproperty-hotkeysIcon: url(theme:Dark/settings/hotkeys.svg); ++ qproperty-accessibilityIcon: url(theme:Dark/settings/accessibility.svg); ++ qproperty-advancedIcon: url(theme:Dark/settings/advanced.svg); + } + + /* Checkboxes */ +@@ -1121,34 +1121,34 @@ QGroupBox::indicator { + + QCheckBox::indicator:unchecked, + QGroupBox::indicator:unchecked { +- image: url(./Yami/checkbox_unchecked.svg); ++ image: url(theme:Yami/checkbox_unchecked.svg); + } + + QCheckBox::indicator:unchecked:hover, + QGroupBox::indicator:unchecked:hover { + border: none; +- image: url(./Yami/checkbox_unchecked_focus.svg); ++ image: url(theme:Yami/checkbox_unchecked_focus.svg); + } + + QCheckBox::indicator:checked, + QGroupBox::indicator:checked { +- image: url(./Yami/checkbox_checked.svg); ++ image: url(theme:Yami/checkbox_checked.svg); + } + + QCheckBox::indicator:checked:hover, + QGroupBox::indicator:checked:hover { + border: none; +- image: url(./Yami/checkbox_checked_focus.svg); ++ image: url(theme:Yami/checkbox_checked_focus.svg); + } + + QCheckBox::indicator:checked:disabled, + QGroupBox::indicator:checked:disabled { +- image: url(./Yami/checkbox_checked_disabled.svg); ++ image: url(theme:Yami/checkbox_checked_disabled.svg); + } + + QCheckBox::indicator:unchecked:disabled, + QGroupBox::indicator:unchecked:disabled { +- image: url(./Yami/checkbox_unchecked_disabled.svg); ++ image: url(theme:Yami/checkbox_unchecked_disabled.svg); + } + + /* Locked CheckBox */ +@@ -1165,7 +1165,7 @@ LockedCheckBox::indicator { + + LockedCheckBox::indicator:checked, + LockedCheckBox::indicator:checked:hover { +- image: url(./Dark/locked.svg); ++ image: url(theme:Dark/locked.svg); + } + + LockedCheckBox::indicator:unchecked, +@@ -1187,7 +1187,7 @@ VisibilityCheckBox::indicator { + + VisibilityCheckBox::indicator:checked, + VisibilityCheckBox::indicator:checked:hover { +- image: url(./Dark/visible.svg); ++ image: url(theme:Dark/visible.svg); + } + + VisibilityCheckBox::indicator:unchecked, +@@ -1196,7 +1196,7 @@ VisibilityCheckBox::indicator:unchecked:hover { + } + + * [themeID="revertIcon"] { +- qproperty-icon: url(./Dark/revert.svg); ++ qproperty-icon: url(theme:Dark/revert.svg); + } + + QPushButton#extraPanelDelete { +@@ -1225,35 +1225,35 @@ MuteCheckBox::indicator { + } + + MuteCheckBox::indicator:checked { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:unchecked { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:hover { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:unchecked:focus { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + MuteCheckBox::indicator:checked:hover { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:checked:focus { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:checked:disabled { +- image: url(./Dark/mute.svg); ++ image: url(theme:Dark/mute.svg); + } + + MuteCheckBox::indicator:unchecked:disabled { +- image: url(./Dark/settings/audio.svg); ++ image: url(theme:Dark/settings/audio.svg); + } + + #hotkeyFilterReset { +@@ -1283,46 +1283,46 @@ OBSHotkeyWidget QPushButton { + + /* Sources List Group Collapse Checkbox */ + +-SourceTreeSubItemCheckBox { ++QCheckBox[sourceTreeSubItem=true] { + background: transparent; + outline: none; + padding: 0px; + } + +-SourceTreeSubItemCheckBox::indicator { ++QCheckBox[sourceTreeSubItem=true]::indicator { + width: 12px; + height: 12px; + } + +-SourceTreeSubItemCheckBox::indicator:checked, +-SourceTreeSubItemCheckBox::indicator:checked:hover { +- image: url(./Dark/expand.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:checked, ++QCheckBox[sourceTreeSubItem=true]::indicator:checked:hover { ++ image: url(theme:Dark/expand.svg); + } + +-SourceTreeSubItemCheckBox::indicator:unchecked, +-SourceTreeSubItemCheckBox::indicator:unchecked:hover { +- image: url(./Dark/collapse.svg); ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked, ++QCheckBox[sourceTreeSubItem=true]::indicator:unchecked:hover { ++ image: url(theme:Dark/collapse.svg); + } + + /* Source Icons */ + + OBSBasic { +- qproperty-imageIcon: url(./Dark/sources/image.svg); +- qproperty-colorIcon: url(./Dark/sources/brush.svg); +- qproperty-slideshowIcon: url(./Dark/sources/slideshow.svg); +- qproperty-audioInputIcon: url(./Dark/sources/microphone.svg); +- qproperty-audioOutputIcon: url(./Dark/settings/audio.svg); +- qproperty-desktopCapIcon: url(./Dark/settings/video.svg); +- qproperty-windowCapIcon: url(./Dark/sources/window.svg); +- qproperty-gameCapIcon: url(./Dark/sources/gamepad.svg); +- qproperty-cameraIcon: url(./Dark/sources/camera.svg); +- qproperty-textIcon: url(./Dark/sources/text.svg); +- qproperty-mediaIcon: url(./Dark/sources/media.svg); +- qproperty-browserIcon: url(./Dark/sources/globe.svg); +- qproperty-groupIcon: url(./Dark/sources/group.svg); +- qproperty-sceneIcon: url(./Dark/sources/scene.svg); +- qproperty-defaultIcon: url(./Dark/sources/default.svg); +- qproperty-audioProcessOutputIcon: url(./Dark/sources/windowaudio.svg); ++ qproperty-imageIcon: url(theme:Dark/sources/image.svg); ++ qproperty-colorIcon: url(theme:Dark/sources/brush.svg); ++ qproperty-slideshowIcon: url(theme:Dark/sources/slideshow.svg); ++ qproperty-audioInputIcon: url(theme:Dark/sources/microphone.svg); ++ qproperty-audioOutputIcon: url(theme:Dark/settings/audio.svg); ++ qproperty-desktopCapIcon: url(theme:Dark/settings/video.svg); ++ qproperty-windowCapIcon: url(theme:Dark/sources/window.svg); ++ qproperty-gameCapIcon: url(theme:Dark/sources/gamepad.svg); ++ qproperty-cameraIcon: url(theme:Dark/sources/camera.svg); ++ qproperty-textIcon: url(theme:Dark/sources/text.svg); ++ qproperty-mediaIcon: url(theme:Dark/sources/media.svg); ++ qproperty-browserIcon: url(theme:Dark/sources/globe.svg); ++ qproperty-groupIcon: url(theme:Dark/sources/group.svg); ++ qproperty-sceneIcon: url(theme:Dark/sources/scene.svg); ++ qproperty-defaultIcon: url(theme:Dark/sources/default.svg); ++ qproperty-audioProcessOutputIcon: url(theme:Dark/sources/windowaudio.svg); + } + + /* Scene Tree Grid Mode */ +@@ -1358,7 +1358,7 @@ SceneTree { + /* Save icon */ + + * [themeID="replayIconSmall"] { +- qproperty-icon: url(./Dark/save.svg); ++ qproperty-icon: url(theme:Dark/save.svg); + } + + /* Studio Mode T-Bar */ +@@ -1388,32 +1388,32 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { + /* Media icons */ + + * [themeID="playIcon"] { +- qproperty-icon: url(./Dark/media/media_play.svg); ++ qproperty-icon: url(theme:Dark/media/media_play.svg); + } + + * [themeID="pauseIcon"] { +- qproperty-icon: url(./Dark/media/media_pause.svg); ++ qproperty-icon: url(theme:Dark/media/media_pause.svg); + } + + * [themeID="restartIcon"] { +- qproperty-icon: url(./Dark/media/media_restart.svg); ++ qproperty-icon: url(theme:Dark/media/media_restart.svg); + } + + * [themeID="stopIcon"] { +- qproperty-icon: url(./Dark/media/media_stop.svg); ++ qproperty-icon: url(theme:Dark/media/media_stop.svg); + } + + * [themeID="nextIcon"] { +- qproperty-icon: url(./Dark/media/media_next.svg); ++ qproperty-icon: url(theme:Dark/media/media_next.svg); + } + + * [themeID="previousIcon"] { +- qproperty-icon: url(./Dark/media/media_previous.svg); ++ qproperty-icon: url(theme:Dark/media/media_previous.svg); + } + + /* YouTube Integration */ + OBSYoutubeActions { +- qproperty-thumbPlaceholder: url(./Dark/sources/image.svg); ++ qproperty-thumbPlaceholder: url(theme:Dark/sources/image.svg); + } + + #ytEventList QLabel { +@@ -1441,7 +1441,7 @@ OBSYoutubeActions { + /* Calendar Widget */ + QDateTimeEdit::down-arrow { + qproperty-alignment: AlignTop; +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + width: 100%; + } + +@@ -1464,7 +1464,7 @@ QCalendarWidget QToolButton { + } + + #qt_calendar_monthbutton::menu-indicator { +- image: url(./Dark/down.svg); ++ image: url(theme:Dark/down.svg); + subcontrol-position: right; + padding-top: 2px; + padding-right: 6px; +@@ -1474,13 +1474,13 @@ QCalendarWidget QToolButton { + + QCalendarWidget #qt_calendar_prevmonth { + padding: 2px; +- qproperty-icon: url(./Dark/left.svg); ++ qproperty-icon: url(theme:Dark/left.svg); + icon-size: 16px, 16px; + } + + QCalendarWidget #qt_calendar_nextmonth { + padding: 2px; +- qproperty-icon: url(./Dark/right.svg); ++ qproperty-icon: url(theme:Dark/right.svg); + icon-size: 16px, 16px; + } + +diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp +index c98408c4a..887d6bb63 100644 +--- a/UI/obs-app.cpp ++++ b/UI/obs-app.cpp +@@ -31,6 +31,7 @@ + #include + #include + ++#include + #include + #include + #include +@@ -1272,6 +1273,19 @@ bool OBSApp::InitTheme() + defaultPalette = palette(); + setStyle(new OBSProxyStyle()); + ++ /* Set search paths for custom 'theme:' URI prefix */ ++ string searchDir; ++ if (GetDataFilePath("themes", searchDir)) { ++ auto installSearchDir = filesystem::u8path(searchDir); ++ QDir::addSearchPath("theme", absolute(installSearchDir)); ++ } ++ ++ char userDir[512]; ++ if (GetConfigPath(userDir, sizeof(userDir), "obs-studio/themes")) { ++ auto configSearchDir = filesystem::u8path(userDir); ++ QDir::addSearchPath("theme", absolute(configSearchDir)); ++ } ++ + const char *themeName = + config_get_string(globalConfig, "General", "CurrentTheme3"); + if (!themeName) +diff --git a/UI/source-tree.cpp b/UI/source-tree.cpp +index 3cfdcd5ed..66f44da71 100644 +--- a/UI/source-tree.cpp ++++ b/UI/source-tree.cpp +@@ -577,7 +577,8 @@ void SourceTreeItem::Update(bool force) + boxLayout->insertItem(0, spacer); + + } else if (type == Type::Group) { +- expand = new SourceTreeSubItemCheckBox(); ++ expand = new QCheckBox(); ++ expand->setProperty("sourceTreeSubItem", true); + expand->setSizePolicy(QSizePolicy::Maximum, + QSizePolicy::Maximum); + expand->setMaximumSize(10, 16); +@@ -1664,11 +1665,8 @@ void SourceTree::AddGroup() + + void SourceTree::UpdateNoSourcesMessage() + { +- std::string darkPath; +- GetDataFilePath("themes/Dark/no_sources.svg", darkPath); +- + QString file = !App()->IsThemeDark() ? ":res/images/no_sources.svg" +- : darkPath.c_str(); ++ : "theme:Dark/no_sources.svg"; + iconNoSources.load(file); + + QTextOption opt(Qt::AlignHCenter); +diff --git a/UI/source-tree.hpp b/UI/source-tree.hpp +index 0ab3ad80f..c4b419686 100644 +--- a/UI/source-tree.hpp ++++ b/UI/source-tree.hpp +@@ -22,10 +22,6 @@ class LockedCheckBox; + class VisibilityCheckBox; + class VisibilityItemWidget; + +-class SourceTreeSubItemCheckBox : public QCheckBox { +- Q_OBJECT +-}; +- + class SourceTreeItem : public QFrame { + Q_OBJECT + +diff --git a/UI/window-basic-status-bar.cpp b/UI/window-basic-status-bar.cpp +index 300b769c5..5f08ba78b 100644 +--- a/UI/window-basic-status-bar.cpp ++++ b/UI/window-basic-status-bar.cpp +@@ -569,19 +569,9 @@ void OBSBasicStatusBar::RecordingUnpaused() + + static QPixmap GetPixmap(const QString &filename) + { +- bool darkTheme = obs_frontend_is_theme_dark(); +- QString path; +- +- if (darkTheme) { +- std::string darkPath; +- QString themePath = QString("themes/Dark/") + filename; +- GetDataFilePath(QT_TO_UTF8(themePath), darkPath); +- path = QT_UTF8(darkPath.c_str()); +- } else { +- path = QString(":/res/images/" + filename); +- } +- +- return QIcon(path).pixmap(QSize(16, 16)); ++ QString path = obs_frontend_is_theme_dark() ? "theme:Dark/" ++ : ":/res/images/"; ++ return QIcon(path + filename).pixmap(QSize(16, 16)); + } + + void OBSBasicStatusBar::UpdateIcons() +diff --git a/deps/obs-scripting/obs-scripting-lua.c b/deps/obs-scripting/obs-scripting-lua.c +index c322e0850..9b3c44441 100644 +--- a/deps/obs-scripting/obs-scripting-lua.c ++++ b/deps/obs-scripting/obs-scripting-lua.c +@@ -52,6 +52,7 @@ static const char *get_script_path_func = "\ + function script_path()\n\ + return \"%s\"\n\ + end\n\ ++package.cpath = package.cpath .. \";\" .. script_path() .. \"/?." SO_EXT "\"\n\ + package.path = package.path .. \";\" .. script_path() .. \"/?.lua\"\n"; + + static char *startup_script = NULL; +diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt +index 39bfa1fd0..279dab4c1 100644 +--- a/libobs/CMakeLists.txt ++++ b/libobs/CMakeLists.txt +@@ -33,6 +33,8 @@ target_sources( + obs-audio-controls.c + obs-audio-controls.h + obs-audio.c ++ obs-av1.c ++ obs-av1.h + obs-avc.c + obs-avc.h + obs-data.c +diff --git a/libobs/cmake/legacy.cmake b/libobs/cmake/legacy.cmake +index 5f13c4c0f..dd77cdfb9 100644 +--- a/libobs/cmake/legacy.cmake ++++ b/libobs/cmake/legacy.cmake +@@ -32,6 +32,8 @@ target_sources( + obs-audio.c + obs-audio-controls.c + obs-audio-controls.h ++ obs-av1.c ++ obs-av1.h + obs-avc.c + obs-avc.h + obs-data.c +diff --git a/libobs/obs-av1.c b/libobs/obs-av1.c +new file mode 100644 +index 000000000..a01513053 +--- /dev/null ++++ b/libobs/obs-av1.c +@@ -0,0 +1,123 @@ ++// SPDX-FileCopyrightText: 2023 David Rosca ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "obs-av1.h" ++ ++#include "obs.h" ++ ++static inline uint64_t leb128(const uint8_t *buf, size_t size, size_t *len) ++{ ++ uint64_t value = 0; ++ uint8_t leb128_byte; ++ ++ *len = 0; ++ ++ for (int i = 0; i < 8; i++) { ++ if (size-- < 1) ++ break; ++ (*len)++; ++ leb128_byte = buf[i]; ++ value |= (leb128_byte & 0x7f) << (i * 7); ++ if (!(leb128_byte & 0x80)) ++ break; ++ } ++ ++ return value; ++} ++ ++static inline unsigned int get_bits(uint8_t val, unsigned int n, ++ unsigned int count) ++{ ++ return (val >> (8 - n - count)) & ((1 << (count - 1)) * 2 - 1); ++} ++ ++static void parse_obu_header(const uint8_t *buf, size_t size, size_t *obu_start, ++ size_t *obu_size, int *obu_type) ++{ ++ int extension_flag, has_size_field; ++ size_t size_len = 0; ++ ++ *obu_start = 0; ++ *obu_size = 0; ++ *obu_type = 0; ++ ++ if (size < 1) ++ return; ++ ++ *obu_type = get_bits(*buf, 1, 4); ++ extension_flag = get_bits(*buf, 5, 1); ++ has_size_field = get_bits(*buf, 6, 1); ++ ++ if (extension_flag) ++ (*obu_start)++; ++ ++ (*obu_start)++; ++ ++ if (has_size_field) ++ *obu_size = (size_t)leb128(buf + *obu_start, size - *obu_start, ++ &size_len); ++ else ++ *obu_size = size - 1; ++ ++ *obu_start += size_len; ++} ++ ++bool obs_av1_keyframe(const uint8_t *data, size_t size) ++{ ++ const uint8_t *start = data, *end = data + size; ++ ++ while (start < end) { ++ size_t obu_start, obu_size; ++ int obu_type; ++ parse_obu_header(start, end - start, &obu_start, &obu_size, ++ &obu_type); ++ ++ if (obu_size) { ++ if (obu_type == OBS_OBU_FRAME || ++ obu_type == OBS_OBU_FRAME_HEADER) { ++ uint8_t val = *(start + obu_start); ++ if (!get_bits(val, 0, 1)) // show_existing_frame ++ return get_bits(val, 1, 2) == ++ 0; // frame_type ++ return false; ++ } ++ } ++ ++ start += obu_start + obu_size; ++ } ++ ++ return false; ++} ++ ++void obs_extract_av1_headers(const uint8_t *packet, size_t size, ++ uint8_t **new_packet_data, size_t *new_packet_size, ++ uint8_t **header_data, size_t *header_size) ++{ ++ DARRAY(uint8_t) new_packet; ++ DARRAY(uint8_t) header; ++ const uint8_t *start = packet, *end = packet + size; ++ ++ da_init(new_packet); ++ da_init(header); ++ ++ while (start < end) { ++ size_t obu_start, obu_size; ++ int obu_type; ++ parse_obu_header(start, end - start, &obu_start, &obu_size, ++ &obu_type); ++ ++ if (obu_type == OBS_OBU_METADATA || ++ obu_type == OBS_OBU_SEQUENCE_HEADER) { ++ da_push_back_array(header, start, obu_start + obu_size); ++ } ++ da_push_back_array(new_packet, start, obu_start + obu_size); ++ ++ start += obu_start + obu_size; ++ } ++ ++ *new_packet_data = new_packet.array; ++ *new_packet_size = new_packet.num; ++ *header_data = header.array; ++ *header_size = header.num; ++} +diff --git a/libobs/obs-av1.h b/libobs/obs-av1.h +new file mode 100644 +index 000000000..031299da0 +--- /dev/null ++++ b/libobs/obs-av1.h +@@ -0,0 +1,35 @@ ++// SPDX-FileCopyrightText: 2023 David Rosca ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++#include "util/c99defs.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++enum { ++ OBS_OBU_SEQUENCE_HEADER = 1, ++ OBS_OBU_TEMPORAL_DELIMITER = 2, ++ OBS_OBU_FRAME_HEADER = 3, ++ OBS_OBU_TILE_GROUP = 4, ++ OBS_OBU_METADATA = 5, ++ OBS_OBU_FRAME = 6, ++ OBS_OBU_REDUNDANT_FRAME_HEADER = 7, ++ OBS_OBU_TILE_LIST = 8, ++ OBS_OBU_PADDING = 15, ++}; ++ ++/* Helpers for parsing AV1 OB units. */ ++ ++EXPORT bool obs_av1_keyframe(const uint8_t *data, size_t size); ++EXPORT void obs_extract_av1_headers(const uint8_t *packet, size_t size, ++ uint8_t **new_packet_data, ++ size_t *new_packet_size, ++ uint8_t **header_data, size_t *header_size); ++ ++#ifdef __cplusplus ++} ++#endif +diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c +index 25c5f7900..b7e5d5edd 100644 +--- a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c ++++ b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + #ifdef ENABLE_HEVC + #include + #endif +@@ -51,8 +52,15 @@ + #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) + #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) + ++enum codec_type { ++ CODEC_H264, ++ CODEC_HEVC, ++ CODEC_AV1, ++}; ++ + struct vaapi_encoder { + obs_encoder_t *encoder; ++ enum codec_type codec; + + AVBufferRef *vadevice_ref; + AVBufferRef *vaframes_ref; +@@ -83,59 +91,48 @@ static const char *h264_vaapi_getname(void *unused) + return "FFmpeg VAAPI H.264"; + } + +-#ifdef ENABLE_HEVC +-static const char *hevc_vaapi_getname(void *unused) ++static const char *av1_vaapi_getname(void *unused) + { + UNUSED_PARAMETER(unused); +- return "FFmpeg VAAPI HEVC"; +-} +-#endif +- +-static inline bool h264_valid_format(enum video_format format) +-{ +- return format == VIDEO_FORMAT_NV12; ++ return "FFmpeg VAAPI AV1"; + } + + #ifdef ENABLE_HEVC +-static inline bool hevc_valid_format(enum video_format format) ++static const char *hevc_vaapi_getname(void *unused) + { +- return (format == VIDEO_FORMAT_NV12) || (format == VIDEO_FORMAT_P010); ++ UNUSED_PARAMETER(unused); ++ return "FFmpeg VAAPI HEVC"; + } + #endif + +-static void h264_vaapi_video_info(void *data, struct video_scale_info *info) ++static inline bool vaapi_valid_format(struct vaapi_encoder *enc, ++ enum video_format format) + { +- struct vaapi_encoder *enc = data; +- enum video_format pref_format; +- +- pref_format = obs_encoder_get_preferred_video_format(enc->encoder); +- +- if (!h264_valid_format(pref_format)) { +- pref_format = h264_valid_format(info->format) +- ? info->format +- : VIDEO_FORMAT_NV12; ++ if (enc->codec == CODEC_H264) { ++ return format == VIDEO_FORMAT_NV12; ++ } else if (enc->codec == CODEC_HEVC || enc->codec == CODEC_AV1) { ++ return (format == VIDEO_FORMAT_NV12) || ++ (format == VIDEO_FORMAT_P010); ++ } else { ++ return false; + } +- +- info->format = pref_format; + } + +-#ifdef ENABLE_HEVC +-static void hevc_vaapi_video_info(void *data, struct video_scale_info *info) ++static void vaapi_video_info(void *data, struct video_scale_info *info) + { + struct vaapi_encoder *enc = data; + enum video_format pref_format; + + pref_format = obs_encoder_get_preferred_video_format(enc->encoder); + +- if (!hevc_valid_format(pref_format)) { +- pref_format = hevc_valid_format(info->format) ++ if (!vaapi_valid_format(enc, pref_format)) { ++ pref_format = vaapi_valid_format(enc, info->format) + ? info->format + : VIDEO_FORMAT_NV12; + } + + info->format = pref_format; + } +-#endif + + static bool vaapi_init_codec(struct vaapi_encoder *enc, const char *path) + { +@@ -232,7 +229,7 @@ static const rc_mode_t *get_rc_mode(const char *name) + return rc_mode ? rc_mode : RC_MODES; + } + +-static bool vaapi_update(void *data, obs_data_t *settings, bool hevc) ++static bool vaapi_update(void *data, obs_data_t *settings) + { + struct vaapi_encoder *enc = data; + +@@ -247,7 +244,7 @@ static bool vaapi_update(void *data, obs_data_t *settings, bool hevc) + int bf = (int)obs_data_get_int(settings, "bf"); + int qp = rc_mode->qp ? (int)obs_data_get_int(settings, "qp") : 0; + +- av_opt_set_int(enc->context->priv_data, "qp", qp, 0); ++ enc->context->global_quality = enc->codec == CODEC_AV1 ? qp * 5 : qp; + + int level = (int)obs_data_get_int(settings, "level"); + int bitrate = rc_mode->bitrate +@@ -277,21 +274,15 @@ static bool vaapi_update(void *data, obs_data_t *settings, bool hevc) + info.range = voi->range; + + #ifdef ENABLE_HEVC +- if (hevc) { ++ if (enc->codec == CODEC_HEVC) { + if ((profile == FF_PROFILE_HEVC_MAIN) && + (info.format == VIDEO_FORMAT_P010)) { + warn("Forcing Main10 for P010"); + profile = FF_PROFILE_HEVC_MAIN_10; + } +- +- hevc_vaapi_video_info(enc, &info); +- } else +-#else +- UNUSED_PARAMETER(hevc); +-#endif +- { +- h264_vaapi_video_info(enc, &info); + } ++#endif ++ vaapi_video_info(enc, &info); + + enc->context->profile = profile; + enc->context->max_b_frames = bf; +@@ -418,16 +409,28 @@ static void vaapi_destroy(void *data) + bfree(enc); + } + ++static inline const char *vaapi_encoder_name(enum codec_type codec) ++{ ++ if (codec == CODEC_H264) { ++ return "h264_vaapi"; ++ } else if (codec == CODEC_HEVC) { ++ return "hevc_vaapi"; ++ } else if (codec == CODEC_AV1) { ++ return "av1_vaapi"; ++ } ++ return NULL; ++} ++ + static void *vaapi_create_internal(obs_data_t *settings, obs_encoder_t *encoder, +- bool hevc) ++ enum codec_type codec) + { + struct vaapi_encoder *enc; + + enc = bzalloc(sizeof(*enc)); + enc->encoder = encoder; + +- const char *const name = hevc ? "hevc_vaapi" : "h264_vaapi"; +- enc->vaapi = avcodec_find_encoder_by_name(name); ++ enc->codec = codec; ++ enc->vaapi = avcodec_find_encoder_by_name(vaapi_encoder_name(codec)); + + enc->first_packet = true; + +@@ -444,7 +447,7 @@ static void *vaapi_create_internal(obs_data_t *settings, obs_encoder_t *encoder, + goto fail; + } + +- if (!vaapi_update(enc, settings, hevc)) ++ if (!vaapi_update(enc, settings)) + goto fail; + + return enc; +@@ -456,13 +459,18 @@ fail: + + static void *h264_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder) + { +- return vaapi_create_internal(settings, encoder, false); ++ return vaapi_create_internal(settings, encoder, CODEC_H264); ++} ++ ++static void *av1_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder) ++{ ++ return vaapi_create_internal(settings, encoder, CODEC_AV1); + } + + #ifdef ENABLE_HEVC + static void *hevc_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder) + { +- return vaapi_create_internal(settings, encoder, true); ++ return vaapi_create_internal(settings, encoder, CODEC_HEVC); + } + #endif + +@@ -492,9 +500,8 @@ static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame, + } + } + +-static bool vaapi_encode_internal(void *data, struct encoder_frame *frame, +- struct encoder_packet *packet, +- bool *received_packet, bool hevc) ++static bool vaapi_encode(void *data, struct encoder_frame *frame, ++ struct encoder_packet *packet, bool *received_packet) + { + struct vaapi_encoder *enc = data; + AVFrame *hwframe = NULL; +@@ -556,22 +563,26 @@ static bool vaapi_encode_internal(void *data, struct encoder_frame *frame, + + enc->first_packet = false; + #ifdef ENABLE_HEVC +- if (hevc) { ++ if (enc->codec == CODEC_HEVC) { + obs_extract_hevc_headers( + enc->packet->data, enc->packet->size, + &new_packet, &size, &enc->header, + &enc->header_size, &enc->sei, + &enc->sei_size); + } else +-#else +- UNUSED_PARAMETER(hevc); + #endif +- { ++ if (enc->codec == CODEC_H264) { + obs_extract_avc_headers( + enc->packet->data, enc->packet->size, + &new_packet, &size, &enc->header, + &enc->header_size, &enc->sei, + &enc->sei_size); ++ } else if (enc->codec == CODEC_AV1) { ++ obs_extract_av1_headers(enc->packet->data, ++ enc->packet->size, ++ &new_packet, &size, ++ &enc->header, ++ &enc->header_size); + } + + da_copy_array(enc->buffer, new_packet, size); +@@ -587,14 +598,17 @@ static bool vaapi_encode_internal(void *data, struct encoder_frame *frame, + packet->size = enc->buffer.num; + packet->type = OBS_ENCODER_VIDEO; + #ifdef ENABLE_HEVC +- if (hevc) { ++ if (enc->codec == CODEC_HEVC) { + packet->keyframe = + obs_hevc_keyframe(packet->data, packet->size); + } else + #endif +- { ++ if (enc->codec == CODEC_H264) { + packet->keyframe = + obs_avc_keyframe(packet->data, packet->size); ++ } else if (enc->codec == CODEC_AV1) { ++ packet->keyframe = ++ obs_av1_keyframe(packet->data, packet->size); + } + *received_packet = true; + } else { +@@ -610,58 +624,60 @@ fail: + return false; + } + +-static bool h264_vaapi_encode(void *data, struct encoder_frame *frame, +- struct encoder_packet *packet, +- bool *received_packet) ++static void set_visible(obs_properties_t *ppts, const char *name, bool visible) + { +- return vaapi_encode_internal(data, frame, packet, received_packet, +- false); ++ obs_property_t *p = obs_properties_get(ppts, name); ++ obs_property_set_visible(p, visible); + } + +-#ifdef ENABLE_HEVC +-static bool hevc_vaapi_encode(void *data, struct encoder_frame *frame, +- struct encoder_packet *packet, +- bool *received_packet) ++static inline VAProfile vaapi_profile(enum codec_type codec) + { +- return vaapi_encode_internal(data, frame, packet, received_packet, +- true); +-} ++ if (codec == CODEC_H264) { ++ return VAProfileH264ConstrainedBaseline; ++ } else if (codec == CODEC_AV1) { ++ return VAProfileAV1Profile0; ++#if ENABLE_HEVC ++ } else if (codec == CODEC_HEVC) { ++ return VAProfileHEVCMain; + #endif +- +-static void set_visible(obs_properties_t *ppts, const char *name, bool visible) +-{ +- obs_property_t *p = obs_properties_get(ppts, name); +- obs_property_set_visible(p, visible); ++ } ++ return VAProfileNone; + } + +-static void vaapi_defaults_internal(obs_data_t *settings, bool hevc) ++static inline const char *vaapi_default_device(enum codec_type codec) + { +-#ifdef ENABLE_HEVC +- const char *device = hevc ? vaapi_get_hevc_default_device() +- : vaapi_get_h264_default_device(); +-#else +- const char *const device = vaapi_get_h264_default_device(); ++ if (codec == CODEC_H264) { ++ return vaapi_get_h264_default_device(); ++ } else if (codec == CODEC_AV1) { ++ return vaapi_get_av1_default_device(); ++#if ENABLE_HEVC ++ } else if (codec == CODEC_HEVC) { ++ return vaapi_get_hevc_default_device(); + #endif ++ } ++ return NULL; ++} + ++static void vaapi_defaults_internal(obs_data_t *settings, enum codec_type codec) ++{ ++ const char *const device = vaapi_default_device(codec); + obs_data_set_default_string(settings, "vaapi_device", device); + #ifdef ENABLE_HEVC +- if (hevc) { ++ if (codec == CODEC_HEVC) + obs_data_set_default_int(settings, "profile", + FF_PROFILE_HEVC_MAIN); +- +- } else +-#else +- UNUSED_PARAMETER(hevc); ++ else + #endif +- { ++ if (codec == CODEC_H264) + obs_data_set_default_int(settings, "profile", +- FF_PROFILE_H264_CONSTRAINED_BASELINE); +- } +- obs_data_set_default_int(settings, "level", 40); ++ FF_PROFILE_H264_HIGH); ++ else if (codec == CODEC_AV1) ++ obs_data_set_default_int(settings, "profile", ++ FF_PROFILE_AV1_MAIN); ++ obs_data_set_default_int(settings, "level", FF_LEVEL_UNKNOWN); + obs_data_set_default_int(settings, "bitrate", 2500); + obs_data_set_default_int(settings, "keyint_sec", 0); + obs_data_set_default_int(settings, "bf", 0); +- obs_data_set_default_int(settings, "rendermode", 0); + obs_data_set_default_int(settings, "qp", 20); + obs_data_set_default_int(settings, "maxrate", 0); + +@@ -670,12 +686,7 @@ static void vaapi_defaults_internal(obs_data_t *settings, bool hevc) + if (!va_dpy) + return; + +-#ifdef ENABLE_HEVC +- const VAProfile profile = hevc ? VAProfileHEVCMain +- : VAProfileH264ConstrainedBaseline; +-#else +- const VAProfile profile = VAProfileH264ConstrainedBaseline; +-#endif ++ const VAProfile profile = vaapi_profile(codec); + if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_CBR, device)) + obs_data_set_default_string(settings, "rate_control", "CBR"); + else if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_VBR, device)) +@@ -688,12 +699,17 @@ static void vaapi_defaults_internal(obs_data_t *settings, bool hevc) + + static void h264_vaapi_defaults(obs_data_t *settings) + { +- vaapi_defaults_internal(settings, false); ++ vaapi_defaults_internal(settings, CODEC_H264); ++} ++ ++static void av1_vaapi_defaults(obs_data_t *settings) ++{ ++ vaapi_defaults_internal(settings, CODEC_AV1); + } + + static void hevc_vaapi_defaults(obs_data_t *settings) + { +- vaapi_defaults_internal(settings, true); ++ vaapi_defaults_internal(settings, CODEC_HEVC); + } + + static bool vaapi_device_modified(obs_properties_t *ppts, obs_property_t *p, +@@ -729,6 +745,11 @@ static bool vaapi_device_modified(obs_properties_t *ppts, obs_property_t *p, + goto fail; + profile = VAProfileH264High; + break; ++ case FF_PROFILE_AV1_MAIN: ++ if (!vaapi_display_av1_supported(va_dpy, device)) ++ goto fail; ++ profile = VAProfileAV1Profile0; ++ break; + #ifdef ENABLE_HEVC + case FF_PROFILE_HEVC_MAIN: + if (!vaapi_display_hevc_supported(va_dpy, device)) +@@ -744,7 +765,7 @@ static bool vaapi_device_modified(obs_properties_t *ppts, obs_property_t *p, + } + + if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_CBR, device)) +- obs_property_list_add_string(rc_p, "CBR (default)", "CBR"); ++ obs_property_list_add_string(rc_p, "CBR", "CBR"); + + if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_VBR, device)) + obs_property_list_add_string(rc_p, "VBR", "VBR"); +@@ -752,6 +773,8 @@ static bool vaapi_device_modified(obs_properties_t *ppts, obs_property_t *p, + if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_CQP, device)) + obs_property_list_add_string(rc_p, "CQP", "CQP"); + ++ set_visible(ppts, "bf", vaapi_device_bframe_supported(profile, va_dpy)); ++ + fail: + vaapi_close_device(&drm_fd, va_dpy); + return true; +@@ -800,7 +823,7 @@ static bool get_device_name_from_pci(struct pci_access *pacc, char *pci_slot, + return false; + } + +-static obs_properties_t *vaapi_properties_internal(bool hevc) ++static obs_properties_t *vaapi_properties_internal(enum codec_type codec) + { + obs_properties_t *props = obs_properties_create(); + obs_property_t *list; +@@ -891,16 +914,17 @@ static obs_properties_t *vaapi_properties_internal(bool hevc) + obs_module_text("Profile"), + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); +- if (hevc) { ++ if (codec == CODEC_HEVC) { + obs_property_list_add_int(list, "Main", FF_PROFILE_HEVC_MAIN); + obs_property_list_add_int(list, "Main10", + FF_PROFILE_HEVC_MAIN_10); +- } else { +- obs_property_list_add_int(list, +- "Constrained Baseline (default)", ++ } else if (codec == CODEC_H264) { ++ obs_property_list_add_int(list, "Constrained Baseline", + FF_PROFILE_H264_CONSTRAINED_BASELINE); + obs_property_list_add_int(list, "Main", FF_PROFILE_H264_MAIN); + obs_property_list_add_int(list, "High", FF_PROFILE_H264_HIGH); ++ } else if (codec == CODEC_AV1) { ++ obs_property_list_add_int(list, "Main", FF_PROFILE_AV1_MAIN); + } + + obs_property_set_modified_callback(list, vaapi_device_modified); +@@ -909,15 +933,33 @@ static obs_properties_t *vaapi_properties_internal(bool hevc) + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(list, "Auto", FF_LEVEL_UNKNOWN); +- obs_property_list_add_int(list, "3.0", 30); +- obs_property_list_add_int(list, "3.1", 31); +- obs_property_list_add_int(list, "4.0 (default) (Compatibility mode)", +- 40); +- obs_property_list_add_int(list, "4.1", 41); +- obs_property_list_add_int(list, "4.2", 42); +- obs_property_list_add_int(list, "5.0", 50); +- obs_property_list_add_int(list, "5.1", 51); +- obs_property_list_add_int(list, "5.2", 52); ++ if (codec == CODEC_H264) { ++ obs_property_list_add_int(list, "3.0", 30); ++ obs_property_list_add_int(list, "3.1", 31); ++ obs_property_list_add_int(list, "4.0", 40); ++ obs_property_list_add_int(list, "4.1", 41); ++ obs_property_list_add_int(list, "4.2", 42); ++ obs_property_list_add_int(list, "5.0", 50); ++ obs_property_list_add_int(list, "5.1", 51); ++ obs_property_list_add_int(list, "5.2", 52); ++ } else if (codec == CODEC_HEVC) { ++ obs_property_list_add_int(list, "3.0", 90); ++ obs_property_list_add_int(list, "3.1", 93); ++ obs_property_list_add_int(list, "4.0", 120); ++ obs_property_list_add_int(list, "4.1", 123); ++ obs_property_list_add_int(list, "5.0", 150); ++ obs_property_list_add_int(list, "5.1", 153); ++ obs_property_list_add_int(list, "5.2", 156); ++ } else if (codec == CODEC_AV1) { ++ obs_property_list_add_int(list, "3.0", 4); ++ obs_property_list_add_int(list, "3.1", 5); ++ obs_property_list_add_int(list, "4.0", 8); ++ obs_property_list_add_int(list, "4.1", 9); ++ obs_property_list_add_int(list, "5.0", 12); ++ obs_property_list_add_int(list, "5.1", 13); ++ obs_property_list_add_int(list, "5.2", 14); ++ obs_property_list_add_int(list, "5.3", 15); ++ } + + list = obs_properties_add_list(props, "rate_control", + obs_module_text("RateControl"), +@@ -942,6 +984,9 @@ static obs_properties_t *vaapi_properties_internal(bool hevc) + 20, 1); + obs_property_int_set_suffix(p, " s"); + ++ obs_properties_add_int(props, "bf", obs_module_text("BFrames"), 0, 4, ++ 1); ++ + obs_properties_add_text(props, "ffmpeg_opts", + obs_module_text("FFmpegOpts"), + OBS_TEXT_DEFAULT); +@@ -952,14 +997,20 @@ static obs_properties_t *vaapi_properties_internal(bool hevc) + static obs_properties_t *h264_vaapi_properties(void *unused) + { + UNUSED_PARAMETER(unused); +- return vaapi_properties_internal(false); ++ return vaapi_properties_internal(CODEC_H264); ++} ++ ++static obs_properties_t *av1_vaapi_properties(void *unused) ++{ ++ UNUSED_PARAMETER(unused); ++ return vaapi_properties_internal(CODEC_AV1); + } + + #ifdef ENABLE_HEVC + static obs_properties_t *hevc_vaapi_properties(void *unused) + { + UNUSED_PARAMETER(unused); +- return vaapi_properties_internal(true); ++ return vaapi_properties_internal(CODEC_HEVC); + } + #endif + +@@ -988,12 +1039,27 @@ struct obs_encoder_info h264_vaapi_encoder_info = { + .get_name = h264_vaapi_getname, + .create = h264_vaapi_create, + .destroy = vaapi_destroy, +- .encode = h264_vaapi_encode, ++ .encode = vaapi_encode, + .get_defaults = h264_vaapi_defaults, + .get_properties = h264_vaapi_properties, + .get_extra_data = vaapi_extra_data, + .get_sei_data = vaapi_sei_data, +- .get_video_info = h264_vaapi_video_info, ++ .get_video_info = vaapi_video_info, ++}; ++ ++struct obs_encoder_info av1_vaapi_encoder_info = { ++ .id = "av1_ffmpeg_vaapi", ++ .type = OBS_ENCODER_VIDEO, ++ .codec = "av1", ++ .get_name = av1_vaapi_getname, ++ .create = av1_vaapi_create, ++ .destroy = vaapi_destroy, ++ .encode = vaapi_encode, ++ .get_defaults = av1_vaapi_defaults, ++ .get_properties = av1_vaapi_properties, ++ .get_extra_data = vaapi_extra_data, ++ .get_sei_data = vaapi_sei_data, ++ .get_video_info = vaapi_video_info, + }; + + #ifdef ENABLE_HEVC +@@ -1004,11 +1070,11 @@ struct obs_encoder_info hevc_vaapi_encoder_info = { + .get_name = hevc_vaapi_getname, + .create = hevc_vaapi_create, + .destroy = vaapi_destroy, +- .encode = hevc_vaapi_encode, ++ .encode = vaapi_encode, + .get_defaults = hevc_vaapi_defaults, + .get_properties = hevc_vaapi_properties, + .get_extra_data = vaapi_extra_data, + .get_sei_data = vaapi_sei_data, +- .get_video_info = hevc_vaapi_video_info, ++ .get_video_info = vaapi_video_info, + }; + #endif +diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c +index 7eb9a876d..981bb9c65 100644 +--- a/plugins/obs-ffmpeg/obs-ffmpeg.c ++++ b/plugins/obs-ffmpeg/obs-ffmpeg.c +@@ -47,6 +47,7 @@ extern struct obs_encoder_info aom_av1_encoder_info; + + #ifdef LIBAVUTIL_VAAPI_AVAILABLE + extern struct obs_encoder_info h264_vaapi_encoder_info; ++extern struct obs_encoder_info av1_vaapi_encoder_info; + #ifdef ENABLE_HEVC + extern struct obs_encoder_info hevc_vaapi_encoder_info; + #endif +@@ -337,6 +338,17 @@ static bool h264_vaapi_supported(void) + * that support H264. */ + return vaapi_get_h264_default_device() != NULL; + } ++static bool av1_vaapi_supported(void) ++{ ++ const AVCodec *vaenc = avcodec_find_encoder_by_name("av1_vaapi"); ++ ++ if (!vaenc) ++ return false; ++ ++ /* NOTE: If default device is NULL, it means there is no device ++ * that support AV1. */ ++ return vaapi_get_av1_default_device() != NULL; ++} + #ifdef ENABLE_HEVC + static bool hevc_vaapi_supported(void) + { +@@ -447,6 +459,13 @@ bool obs_module_load(void) + blog(LOG_INFO, "FFmpeg VAAPI H264 encoding not supported"); + } + ++ if (av1_vaapi_supported()) { ++ blog(LOG_INFO, "FFmpeg VAAPI AV1 encoding supported"); ++ obs_register_encoder(&av1_vaapi_encoder_info); ++ } else { ++ blog(LOG_INFO, "FFmpeg VAAPI AV1 encoding not supported"); ++ } ++ + #ifdef ENABLE_HEVC + if (hevc_vaapi_supported()) { + blog(LOG_INFO, "FFmpeg VAAPI HEVC encoding supported"); +diff --git a/plugins/obs-ffmpeg/vaapi-utils.c b/plugins/obs-ffmpeg/vaapi-utils.c +index 4198ee59c..2ca7e468c 100644 +--- a/plugins/obs-ffmpeg/vaapi-utils.c ++++ b/plugins/obs-ffmpeg/vaapi-utils.c +@@ -185,6 +185,38 @@ bool vaapi_device_rc_supported(VAProfile profile, VADisplay dpy, uint32_t rc, + return false; + } + ++static bool vaapi_display_ep_bframe_supported(VAProfile profile, ++ VAEntrypoint entrypoint, ++ VADisplay dpy) ++{ ++ bool ret = false; ++ VAStatus va_status; ++ VAConfigAttrib attrib[1]; ++ attrib->type = VAConfigAttribEncMaxRefFrames; ++ ++ va_status = vaGetConfigAttributes(dpy, profile, entrypoint, attrib, 1); ++ ++ if (va_status == VA_STATUS_SUCCESS && ++ attrib->value != VA_ATTRIB_NOT_SUPPORTED) ++ return attrib->value >> 16; ++ ++ return false; ++} ++ ++bool vaapi_device_bframe_supported(VAProfile profile, VADisplay dpy) ++{ ++ bool ret = vaapi_display_ep_bframe_supported(profile, ++ VAEntrypointEncSlice, dpy); ++ if (ret) ++ return true; ++ ret = vaapi_display_ep_bframe_supported(profile, VAEntrypointEncSliceLP, ++ dpy); ++ if (ret) ++ return true; ++ ++ return false; ++} ++ + #define CHECK_PROFILE(ret, profile, va_dpy, device_path) \ + if (vaapi_display_ep_combo_supported(profile, VAEntrypointEncSlice, \ + va_dpy, device_path)) { \ +@@ -261,6 +293,61 @@ const char *vaapi_get_h264_default_device() + return default_h264_device; + } + ++bool vaapi_display_av1_supported(VADisplay dpy, const char *device_path) ++{ ++ bool ret = false; ++ ++ CHECK_PROFILE(ret, VAProfileAV1Profile0, dpy, device_path); ++ ++ if (!ret) { ++ CHECK_PROFILE_LP(ret, VAProfileAV1Profile0, dpy, device_path); ++ } ++ ++ return ret; ++} ++ ++bool vaapi_device_av1_supported(const char *device_path) ++{ ++ bool ret = false; ++ VADisplay va_dpy; ++ ++ int drm_fd = -1; ++ ++ va_dpy = vaapi_open_device(&drm_fd, device_path, ++ "vaapi_device_av1_supported"); ++ if (!va_dpy) ++ return false; ++ ++ ret = vaapi_display_av1_supported(va_dpy, device_path); ++ ++ vaapi_close_device(&drm_fd, va_dpy); ++ ++ return ret; ++} ++ ++const char *vaapi_get_av1_default_device() ++{ ++ static const char *default_av1_device = NULL; ++ ++ if (!default_av1_device) { ++ bool ret = false; ++ char path[32] = "/dev/dri/renderD1"; ++ for (int i = 28;; i++) { ++ sprintf(path, "/dev/dri/renderD1%d", i); ++ if (access(path, F_OK) != 0) ++ break; ++ ++ ret = vaapi_device_av1_supported(path); ++ if (ret) { ++ default_av1_device = strdup(path); ++ break; ++ } ++ } ++ } ++ ++ return default_av1_device; ++} ++ + #ifdef ENABLE_HEVC + + bool vaapi_display_hevc_supported(VADisplay dpy, const char *device_path) +diff --git a/plugins/obs-ffmpeg/vaapi-utils.h b/plugins/obs-ffmpeg/vaapi-utils.h +index b90b23947..d458bad2c 100644 +--- a/plugins/obs-ffmpeg/vaapi-utils.h ++++ b/plugins/obs-ffmpeg/vaapi-utils.h +@@ -14,11 +14,16 @@ void vaapi_close_device(int *fd, VADisplay dpy); + + bool vaapi_device_rc_supported(VAProfile profile, VADisplay dpy, uint32_t rc, + const char *device_path); ++bool vaapi_device_bframe_supported(VAProfile profile, VADisplay dpy); + + bool vaapi_display_h264_supported(VADisplay dpy, const char *device_path); + bool vaapi_device_h264_supported(const char *device_path); + const char *vaapi_get_h264_default_device(void); + ++bool vaapi_display_av1_supported(VADisplay dpy, const char *device_path); ++bool vaapi_device_av1_supported(const char *device_path); ++const char *vaapi_get_av1_default_device(void); ++ + #ifdef ENABLE_HEVC + bool vaapi_display_hevc_supported(VADisplay dpy, const char *device_path); + bool vaapi_device_hevc_supported(const char *device_path); \ No newline at end of file diff --git a/pkgs/apps/obs/default.nix b/pkgs/apps/obs/default.nix index ad35002..40bd0ad 100644 --- a/pkgs/apps/obs/default.nix +++ b/pkgs/apps/obs/default.nix @@ -61,7 +61,7 @@ let in stdenv.mkDerivation rec { pname = "obs-studio-amf"; - version = "30.0.0"; + version = "30.0.2"; src = fetchFromGitHub { owner = "obsproject"; repo = "obs-studio"; @@ -74,6 +74,8 @@ stdenv.mkDerivation rec { ./obs-amf-patch.patch # OBS AMF Patch ./Enable-file-access-and-universal-access-for-file-URL.patch ./fix-nix-plugin-path.patch + ./av1-vaapi.patch + ./ffmpeg61.patch ]; nativeBuildInputs = [ diff --git a/pkgs/apps/obs/ffmpeg61.patch b/pkgs/apps/obs/ffmpeg61.patch new file mode 100644 index 0000000..e697952 --- /dev/null +++ b/pkgs/apps/obs/ffmpeg61.patch @@ -0,0 +1,286 @@ +From 7ed9b1f3a6b5e73a39daf906c8e8c19e93fce4b4 Mon Sep 17 00:00:00 2001 +From: Stephen Seo +Date: Wed, 29 Nov 2023 22:06:09 +0900 +Subject: [PATCH 1/4] deps/media-playback: Use new (nb_)coded_side_data FFmpeg + 6.1 API + +Fixes for using FFmpeg 6.1 due to deprecations. Uses `#if` macros to +allow builds for using older versions of FFmpeg. + +The change in deps/media-playback/media-playback/decode.c is due to +FFmpeg moving "side_data" into AVCodecParameters which is mentioned in +commit [1] in FFmpeg's repository. + +In summary of the "side_data" change, AVStream.side_data is deprecated +and replaced with AVStream.codecpar->coded_side_data, and +AVStream.nb_side_data is replaced with +AVStream.codecpar->nb_coded_side_data. + +[1]: avcodec/codec_par: add side data to AVCodecParameters +https://github.com/FFmpeg/FFmpeg/commit/21d7cc6fa9a26e94965fa71b25655d07568450fe +--- + deps/media-playback/media-playback/decode.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/deps/media-playback/media-playback/decode.c b/deps/media-playback/media-playback/decode.c +index 55b91c140ace4..40853f171bde7 100644 +--- a/deps/media-playback/media-playback/decode.c ++++ b/deps/media-playback/media-playback/decode.c +@@ -114,8 +114,14 @@ static uint16_t get_max_luminance(const AVStream *stream) + { + uint32_t max_luminance = 0; + ++#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) + for (int i = 0; i < stream->nb_side_data; i++) { + const AVPacketSideData *const sd = &stream->side_data[i]; ++#else ++ for (int i = 0; i < stream->codecpar->nb_coded_side_data; i++) { ++ const AVPacketSideData *const sd = ++ &stream->codecpar->coded_side_data[i]; ++#endif + switch (sd->type) { + case AV_PKT_DATA_MASTERING_DISPLAY_METADATA: { + const AVMasteringDisplayMetadata *mastering = + +From 92fc9f69ccff2cb12bb8ef877e9238f5d46588a8 Mon Sep 17 00:00:00 2001 +From: Stephen Seo +Date: Wed, 29 Nov 2023 22:08:42 +0900 +Subject: [PATCH 2/4] deps/media-playback: In check for key-frame, use new + FFmpeg 6.1 API + +Fixes for using FFmpeg 6.1 due to deprecations. Uses `#if` macros to +allow builds for using older versions of FFmpeg. + +AVFrame.key_frame was replaced with a flag in AVFrame.flags. The commit +adding the flag is [1] in FFmpeg's repository, and the deprecation is in +commit [2]. + +In summary of the "key_frame" change, AVFrame.key_frame is deprecated, +and AVFrame.flags indicates with a bit flag if it is a key frame (with +the enum/defined AV_FRAME_FLAG_KEY). + +[1]: avutil/frame: add a keyframe flag to AVFrame +https://github.com/FFmpeg/FFmpeg/commit/cc11191fda0471017b03c1434d6d8cb79f6914e5 + +[2]: avutil/frame: deprecate key_frame +https://github.com/FFmpeg/FFmpeg/commit/3e06f6f04020bef32fa42bc9d7f96e76a46453aa +--- + deps/media-playback/media-playback/media.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/deps/media-playback/media-playback/media.c b/deps/media-playback/media-playback/media.c +index 566eb9e35c9ef..07b040dd8235c 100644 +--- a/deps/media-playback/media-playback/media.c ++++ b/deps/media-playback/media-playback/media.c +@@ -504,7 +504,12 @@ void mp_media_next_video(mp_media_t *m, bool preload) + } + + if (!m->is_local_file && !d->got_first_keyframe) { ++ ++#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(58, 29, 100) + if (!f->key_frame) ++#else ++ if (!(f->flags & AV_FRAME_FLAG_KEY)) ++#endif + return; + + d->got_first_keyframe = true; + +From ed1e0795acde8349ab18b631c545f56e5a3acb9d Mon Sep 17 00:00:00 2001 +From: Stephen Seo +Date: Wed, 29 Nov 2023 20:07:10 +0900 +Subject: [PATCH 3/4] libobs: Fence off unnecessary code due to FFmpeg v6.1 + changes + +Fixes for using FFmpeg 6.1 due to deprecations. Uses `#if` macros to +allow builds for using older versions of FFmpeg. + +This commit prevents obs from using the "fenced" code if using FFmpeg +6.1, since in FFmpeg commit [1] the "side_data" is added to +`AVCodecParameters`, and therefore the existing/following +`avcodec_parameters_copy(...)` will account for the metadata. + +[1]: avcodec/codec_par: add side data to AVCodecParameters +https://github.com/FFmpeg/FFmpeg/commit/21d7cc6fa9a26e94965fa71b25655d07568450fe +--- + libobs/media-io/media-remux.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/libobs/media-io/media-remux.c b/libobs/media-io/media-remux.c +index 7d5eead200012..827d4e59b18ed 100644 +--- a/libobs/media-io/media-remux.c ++++ b/libobs/media-io/media-remux.c +@@ -91,6 +91,7 @@ static inline bool init_output(media_remux_job_t job, const char *out_filename) + return false; + } + ++#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) + #if FF_API_BUFFER_SIZE_T + int content_size; + #else +@@ -125,6 +126,7 @@ static inline bool init_output(media_remux_job_t job, const char *out_filename) + mastering_size); + } + } ++#endif + + ret = avcodec_parameters_copy(out_stream->codecpar, + in_stream->codecpar); + +From 6d0381f98ce0a5624901b0042d624ea972a10c2b Mon Sep 17 00:00:00 2001 +From: Stephen Seo +Date: Wed, 29 Nov 2023 20:09:08 +0900 +Subject: [PATCH 4/4] obs-ffmpeg: Use new side-data FFmpeg 6.1 API + +Fixes for using FFmpeg 6.1 due to deprecations. Uses `#if` macros to +allow builds for using older versions of FFmpeg. + +This commit replaces usage of `av_stream_add_side_data(...)` with +`av_packet_side_data_add(...)`, as the former was deprecated in favor of +the latter. + +The FFmpeg commit that deprecated `av_stream_add_side_data(...)` is [1]. + +The FFmpeg commit that introduced `av_packet_side_data_add(...)` is [2]. + +Note that the deprecation commit is after the new API function. The +commit in between [3] appears to be changes that migrates to the usage +of the new API function. + +[1]: avformat/avformat: use the side data from AVStream.codecpar +https://github.com/FFmpeg/FFmpeg/commit/5432d2aacad5fa7420fe2d9369ed061d521e92d6 + +[2]: avcodec/packet: add generic side data helpers +https://github.com/FFmpeg/FFmpeg/commit/74279227dd28d01b447edb8e617a545982171c2c + +[3]: avcodec/codec_par: add side data to AVCodecParameters +https://github.com/FFmpeg/FFmpeg/commit/21d7cc6fa9a26e94965fa71b25655d07568450fe +--- + plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c | 16 ++++++++++++++++ + plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c | 16 ++++++++++++++++ + plugins/obs-ffmpeg/obs-ffmpeg-output.c | 16 ++++++++++++++++ + 3 files changed, 48 insertions(+) + +diff --git a/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c b/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c +index 740b60e5bf46e..8fb84aa7c4e0b 100644 +--- a/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c ++++ b/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c +@@ -498,9 +498,17 @@ static void create_video_stream(struct ffmpeg_mux *ffm) + av_content_light_metadata_alloc(&content_size); + content->MaxCLL = max_luminance; + content->MaxFALL = max_luminance; ++#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) + av_stream_add_side_data(ffm->video_stream, + AV_PKT_DATA_CONTENT_LIGHT_LEVEL, + (uint8_t *)content, content_size); ++#else ++ av_packet_side_data_add( ++ &ffm->video_stream->codecpar->coded_side_data, ++ &ffm->video_stream->codecpar->nb_coded_side_data, ++ AV_PKT_DATA_CONTENT_LIGHT_LEVEL, (uint8_t *)content, ++ content_size, 0); ++#endif + + AVMasteringDisplayMetadata *const mastering = + av_mastering_display_metadata_alloc(); +@@ -516,10 +524,18 @@ static void create_video_stream(struct ffmpeg_mux *ffm) + mastering->max_luminance = av_make_q(max_luminance, 1); + mastering->has_primaries = 1; + mastering->has_luminance = 1; ++#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) + av_stream_add_side_data(ffm->video_stream, + AV_PKT_DATA_MASTERING_DISPLAY_METADATA, + (uint8_t *)mastering, + sizeof(*mastering)); ++#else ++ av_packet_side_data_add( ++ &ffm->video_stream->codecpar->coded_side_data, ++ &ffm->video_stream->codecpar->nb_coded_side_data, ++ AV_PKT_DATA_MASTERING_DISPLAY_METADATA, ++ (uint8_t *)mastering, sizeof(*mastering), 0); ++#endif + } + + if (ffm->output->oformat->flags & AVFMT_GLOBALHEADER) +diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c b/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c +index f33ee77365274..a56dd91bcc38e 100644 +--- a/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c ++++ b/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c +@@ -168,9 +168,17 @@ static bool create_video_stream(struct ffmpeg_output *stream, + av_content_light_metadata_alloc(&content_size); + content->MaxCLL = hdr_nominal_peak_level; + content->MaxFALL = hdr_nominal_peak_level; ++#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) + av_stream_add_side_data(data->video, + AV_PKT_DATA_CONTENT_LIGHT_LEVEL, + (uint8_t *)content, content_size); ++#else ++ av_packet_side_data_add( ++ &data->video->codecpar->coded_side_data, ++ &data->video->codecpar->nb_coded_side_data, ++ AV_PKT_DATA_CONTENT_LIGHT_LEVEL, (uint8_t *)content, ++ content_size, 0); ++#endif + + AVMasteringDisplayMetadata *const mastering = + av_mastering_display_metadata_alloc(); +@@ -186,10 +194,18 @@ static bool create_video_stream(struct ffmpeg_output *stream, + mastering->max_luminance = av_make_q(hdr_nominal_peak_level, 1); + mastering->has_primaries = 1; + mastering->has_luminance = 1; ++#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) + av_stream_add_side_data(data->video, + AV_PKT_DATA_MASTERING_DISPLAY_METADATA, + (uint8_t *)mastering, + sizeof(*mastering)); ++#else ++ av_packet_side_data_add( ++ &data->video->codecpar->coded_side_data, ++ &data->video->codecpar->nb_coded_side_data, ++ AV_PKT_DATA_MASTERING_DISPLAY_METADATA, ++ (uint8_t *)mastering, sizeof(*mastering), 0); ++#endif + } + context = avcodec_alloc_context3(NULL); + context->codec_type = codec->type; +diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-output.c b/plugins/obs-ffmpeg/obs-ffmpeg-output.c +index ec02b59cda9d8..c7786153392e5 100644 +--- a/plugins/obs-ffmpeg/obs-ffmpeg-output.c ++++ b/plugins/obs-ffmpeg/obs-ffmpeg-output.c +@@ -213,9 +213,17 @@ static bool create_video_stream(struct ffmpeg_data *data) + av_content_light_metadata_alloc(&content_size); + content->MaxCLL = hdr_nominal_peak_level; + content->MaxFALL = hdr_nominal_peak_level; ++#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) + av_stream_add_side_data(data->video, + AV_PKT_DATA_CONTENT_LIGHT_LEVEL, + (uint8_t *)content, content_size); ++#else ++ av_packet_side_data_add( ++ &data->video->codecpar->coded_side_data, ++ &data->video->codecpar->nb_coded_side_data, ++ AV_PKT_DATA_CONTENT_LIGHT_LEVEL, (uint8_t *)content, ++ content_size, 0); ++#endif + + AVMasteringDisplayMetadata *const mastering = + av_mastering_display_metadata_alloc(); +@@ -231,10 +239,18 @@ static bool create_video_stream(struct ffmpeg_data *data) + mastering->max_luminance = av_make_q(hdr_nominal_peak_level, 1); + mastering->has_primaries = 1; + mastering->has_luminance = 1; ++#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) + av_stream_add_side_data(data->video, + AV_PKT_DATA_MASTERING_DISPLAY_METADATA, + (uint8_t *)mastering, + sizeof(*mastering)); ++#else ++ av_packet_side_data_add( ++ &data->video->codecpar->coded_side_data, ++ &data->video->codecpar->nb_coded_side_data, ++ AV_PKT_DATA_MASTERING_DISPLAY_METADATA, ++ (uint8_t *)mastering, sizeof(*mastering), 0); ++#endif + } + + closest_format = data->config.format; \ No newline at end of file