diff --git a/.gitignore b/.gitignore index f78f47a..c88721b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ ipch/ GeneratedFiles/ .moc/ /UI/obs.rc +.vscode/ /other/ diff --git a/CI/before-deploy-osx.sh b/CI/before-deploy-osx.sh index 4bb6188..7c092c4 100755 --- a/CI/before-deploy-osx.sh +++ b/CI/before-deploy-osx.sh @@ -8,9 +8,12 @@ hr() { set -e # Generate file name variables +export GIT_TAG=$(git describe --abbrev=0) export GIT_HASH=$(git rev-parse --short HEAD) export FILE_DATE=$(date +%Y-%m-%d.%H-%M-%S) -export FILENAME=$FILE_DATE-$GIT_HASH-$TRAVIS_BRANCH-osx.pkg +export FILENAME=$FILE_DATE-$GIT_HASH-$TRAVIS_BRANCH-osx.dmg + +echo "git tag: $GIT_TAG" cd ./build @@ -19,9 +22,9 @@ hr "Moving OBS LUA" mv ./rundir/RelWithDebInfo/data/obs-scripting/obslua.so ./rundir/RelWithDebInfo/bin/ # Move obspython -# hr "Moving OBS Python" -# mv ./rundir/RelWithDebInfo/data/obs-scripting/_obspython.so ./rundir/RelWithDebInfo/bin/ -# mv ./rundir/RelWithDebInfo/data/obs-scripting/obspython.py ./rundir/RelWithDebInfo/bin/ +hr "Moving OBS Python" +mv ./rundir/RelWithDebInfo/data/obs-scripting/_obspython.so ./rundir/RelWithDebInfo/bin/ +mv ./rundir/RelWithDebInfo/data/obs-scripting/obspython.py ./rundir/RelWithDebInfo/bin/ # Package everything into a nice .app hr "Packaging .app" @@ -30,32 +33,44 @@ if [ -n "${TRAVIS_TAG}" ]; then STABLE=true fi -sudo python ../CI/install/osx/build_app.py --public-key ../CI/install/osx/OBSPublicDSAKey.pem --sparkle-framework ../../sparkle/Sparkle.framework --stable=$STABLE +#sudo python ../CI/install/osx/build_app.py --public-key ../CI/install/osx/OBSPublicDSAKey.pem --sparkle-framework ../../sparkle/Sparkle.framework --stable=$STABLE + +../CI/install/osx/packageApp.sh + +# fix obs outputs +cp /usr/local/opt/mbedtls/lib/libmbedtls.12.dylib ./OBS.app/Contents/Frameworks/ +cp /usr/local/opt/mbedtls/lib/libmbedcrypto.3.dylib ./OBS.app/Contents/Frameworks/ +cp /usr/local/opt/mbedtls/lib/libmbedx509.0.dylib ./OBS.app/Contents/Frameworks/ +install_name_tool -change /usr/local/opt/mbedtls/lib/libmbedtls.12.dylib @executable_path/../Frameworks/libmbedtls.12.dylib ./OBS.app/Contents/Plugins/obs-outputs.so +install_name_tool -change /usr/local/opt/mbedtls/lib/libmbedcrypto.3.dylib @executable_path/../Frameworks/libmbedcrypto.3.dylib ./OBS.app/Contents/Plugins/obs-outputs.so +install_name_tool -change /usr/local/opt/mbedtls/lib/libmbedx509.0.dylib @executable_path/../Frameworks/libmbedx509.0.dylib ./OBS.app/Contents/Plugins/obs-outputs.so +install_name_tool -change /usr/local/opt/curl/lib/libcurl.4.dylib @executable_path/../Frameworks/libcurl.4.dylib ./OBS.app/Contents/Plugins/obs-outputs.so +install_name_tool -change @rpath/libobs.0.dylib @executable_path/../Frameworks/libobs.0.dylib ./OBS.app/Contents/Plugins/obs-outputs.so + +# copy sparkle into the app +hr "Copying Sparkle.framework" +cp -r ../../sparkle/Sparkle.framework ./OBS.app/Contents/Frameworks/ +install_name_tool -change @rpath/Sparkle.framework/Versions/A/Sparkle @executable_path/../Frameworks/Sparkle.framework/Versions/A/Sparkle ./OBS.app/Contents/MacOS/obs # Copy Chromium embedded framework to app Frameworks directory hr "Copying Chromium Embedded Framework.framework" sudo mkdir -p OBS.app/Contents/Frameworks sudo cp -r ../../cef_binary_${CEF_BUILD_VERSION}_macosx64/Release/Chromium\ Embedded\ Framework.framework OBS.app/Contents/Frameworks/ -sudo install_name_tool -change \ - @rpath/Frameworks/Chromium\ Embedded\ Framework.framework/Chromium\ Embedded\ Framework \ - ../../Frameworks/Chromium\ Embedded\ Framework.framework/Chromium\ Embedded\ Framework \ - OBS.app/Contents/Resources/obs-plugins/obs-browser.so -sudo install_name_tool -change \ - @executable_path/../Frameworks/Chromium\ Embedded\ Framework.framework/Chromium\ Embedded\ Framework \ - ../../Frameworks/Chromium\ Embedded\ Framework.framework/Chromium\ Embedded\ Framework \ - OBS.app/Contents/Resources/obs-plugins/obs-browser.so -sudo install_name_tool -change \ - @rpath/Frameworks/Chromium\ Embedded\ Framework.framework/Chromium\ Embedded\ Framework \ - ../../Frameworks/Chromium\ Embedded\ Framework.framework/Chromium\ Embedded\ Framework \ - OBS.app/Contents/Resources/obs-plugins/obs-browser-page -sudo install_name_tool -change \ - @executable_path/../Frameworks/Chromium\ Embedded\ Framework.framework/Chromium\ Embedded\ Framework \ - ../../Frameworks/Chromium\ Embedded\ Framework.framework/Chromium\ Embedded\ Framework \ - OBS.app/Contents/Resources/obs-plugins/obs-browser-page -# Package app -hr "Generating .pkg" -packagesbuild ../CI/install/osx/CMakeLists.pkgproj +install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ./OBS.app/Contents/Plugins/obs-browser.so +install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./OBS.app/Contents/Plugins/obs-browser.so +install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ./OBS.app/Contents/Plugins/obs-browser.so + +cp ../CI/install/osx/OBSPublicDSAKey.pem OBS.app/Contents/Resources + +# edit plist +plutil -insert CFBundleVersion -string $GIT_TAG ./OBS.app/Contents/Info.plist +plutil -insert CFBundleShortVersionString -string $GIT_TAG ./OBS.app/Contents/Info.plist +plutil -insert OBSFeedsURL -string https://obsproject.com/osx_update/feeds.xml ./OBS.app/Contents/Info.plist +plutil -insert SUFeedURL -string https://obsproject.com/osx_update/stable/updates.xml ./OBS.app/Contents/Info.plist +plutil -insert SUPublicDSAKeyFile -string OBSPublicDSAKey.pem ./OBS.app/Contents/Info.plist + +dmgbuild -s ../CI/install/osx/settings.json "OBS" obs.dmg if [ -v "$TRAVIS" ]; then # Signing stuff @@ -70,12 +85,10 @@ if [ -v "$TRAVIS" ]; then security import ./Certificates.p12 -k build.keychain -T /usr/bin/productsign -P "" # macOS 10.12+ security set-key-partition-list -S apple-tool:,apple: -s -k mysecretpassword build.keychain - hr "Signing Package" - productsign --sign 2MMRE5MTB8 ./OBS.pkg ./$FILENAME -else - cp ./OBS.pkg ./$FILENAME fi +cp ./OBS.dmg ./$FILENAME + # Move to the folder that travis uses to upload artifacts from hr "Moving package to nightly folder for distribution" mkdir ../nightly diff --git a/CI/install-dependencies-linux.sh b/CI/install-dependencies-linux.sh index 391c181..ca8419b 100755 --- a/CI/install-dependencies-linux.sh +++ b/CI/install-dependencies-linux.sh @@ -1,7 +1,6 @@ #!/bin/sh set -ex -sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y curl -L https://packagecloud.io/github/git-lfs/gpgkey | sudo apt-key add - # gets us newer clang diff --git a/CI/install-dependencies-osx.sh b/CI/install-dependencies-osx.sh index c37d588..ad398e4 100755 --- a/CI/install-dependencies-osx.sh +++ b/CI/install-dependencies-osx.sh @@ -16,6 +16,8 @@ else /bin/bash -c "sudo xcode-select -s /Applications/Xcode_9.4.1.app/Contents/Developer" fi +git fetch origin --tags + # Leave obs-studio folder cd ../ @@ -28,10 +30,12 @@ sudo installer -pkg ./Packages.pkg -target / brew update #Base OBS Deps and ccache -brew install jack speexdsp ccache mbedtls clang-format +brew install jack speexdsp ccache mbedtls clang-format freetype fdk-aac brew install https://gist.githubusercontent.com/DDRBoxman/b3956fab6073335a4bf151db0dcbd4ad/raw/ed1342a8a86793ea8c10d8b4d712a654da121ace/qt.rb brew install https://gist.githubusercontent.com/DDRBoxman/4cada55c51803a2f963fa40ce55c9d3e/raw/572c67e908bfbc1bcb8c476ea77ea3935133f5b5/swig.rb +pip install dmgbuild + export PATH=/usr/local/opt/ccache/libexec:$PATH ccache -s || echo "CCache is not available." diff --git a/CI/install/osx/Info.plist b/CI/install/osx/Info.plist new file mode 100644 index 0000000..7eed962 --- /dev/null +++ b/CI/install/osx/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleIconFile + obs.icns + CFBundleName + OBS + CFBundleGetInfoString + OBS - Free and Open Source Streaming/Recording Software + CFBundleExecutable + OBS + CFBundleIdentifier + com.obsproject.obs-studio + CFBundlePackageType + APPL + CFBundleSignature + ???? + LSMinimumSystemVersion + 10.8.5 + NSHighResolutionCapable + + LSAppNapIsDisabled + + NSCameraUsageDescription + OBS needs to access the camera to enable camera sources to work. + NSMicrophoneUsageDescription + OBS needs to access the microphone to enable audio input. + + diff --git a/CI/install/osx/background.png b/CI/install/osx/background.png new file mode 100644 index 0000000..02c4d55 Binary files /dev/null and b/CI/install/osx/background.png differ diff --git a/CI/install/osx/background.pxd/QuickLook/Icon.tiff b/CI/install/osx/background.pxd/QuickLook/Icon.tiff new file mode 100644 index 0000000..4ef96a4 Binary files /dev/null and b/CI/install/osx/background.pxd/QuickLook/Icon.tiff differ diff --git a/CI/install/osx/background.pxd/QuickLook/Preview.tiff b/CI/install/osx/background.pxd/QuickLook/Preview.tiff new file mode 100644 index 0000000..b28ab1e Binary files /dev/null and b/CI/install/osx/background.pxd/QuickLook/Preview.tiff differ diff --git a/CI/install/osx/background.pxd/QuickLook/Thumbnail.tiff b/CI/install/osx/background.pxd/QuickLook/Thumbnail.tiff new file mode 100644 index 0000000..1bd46a7 Binary files /dev/null and b/CI/install/osx/background.pxd/QuickLook/Thumbnail.tiff differ diff --git a/CI/install/osx/background.pxd/data/556CF265-5721-4F18-BE83-8CF39483B4C2 b/CI/install/osx/background.pxd/data/556CF265-5721-4F18-BE83-8CF39483B4C2 new file mode 100644 index 0000000..3036891 Binary files /dev/null and b/CI/install/osx/background.pxd/data/556CF265-5721-4F18-BE83-8CF39483B4C2 differ diff --git a/CI/install/osx/background.pxd/data/8CA689C3-ED2A-459E-952C-E08026CFCD07 b/CI/install/osx/background.pxd/data/8CA689C3-ED2A-459E-952C-E08026CFCD07 new file mode 100644 index 0000000..52d94b9 Binary files /dev/null and b/CI/install/osx/background.pxd/data/8CA689C3-ED2A-459E-952C-E08026CFCD07 differ diff --git a/CI/install/osx/background.pxd/metadata.info b/CI/install/osx/background.pxd/metadata.info new file mode 100644 index 0000000..6054a58 Binary files /dev/null and b/CI/install/osx/background.pxd/metadata.info differ diff --git a/CI/install/osx/background.tiff b/CI/install/osx/background.tiff new file mode 100644 index 0000000..4548740 Binary files /dev/null and b/CI/install/osx/background.tiff differ diff --git a/CI/install/osx/background@2x.png b/CI/install/osx/background@2x.png new file mode 100644 index 0000000..a001edf Binary files /dev/null and b/CI/install/osx/background@2x.png differ diff --git a/CI/install/osx/buildDMG b/CI/install/osx/buildDMG new file mode 100755 index 0000000..af2c798 --- /dev/null +++ b/CI/install/osx/buildDMG @@ -0,0 +1 @@ +dmgbuild -s ./settings.json "OBS" obs.dmg diff --git a/CI/install/osx/dylibBundler b/CI/install/osx/dylibBundler new file mode 100755 index 0000000..a28f209 Binary files /dev/null and b/CI/install/osx/dylibBundler differ diff --git a/CI/install/osx/makeRetinaBG b/CI/install/osx/makeRetinaBG new file mode 100755 index 0000000..5d7f309 --- /dev/null +++ b/CI/install/osx/makeRetinaBG @@ -0,0 +1 @@ +tiffutil -cathidpicheck background.png background@2x.png -out background.tiff diff --git a/CI/install/osx/obs.icns b/CI/install/osx/obs.icns new file mode 100644 index 0000000..6f878d6 Binary files /dev/null and b/CI/install/osx/obs.icns differ diff --git a/CI/install/osx/packageApp.sh b/CI/install/osx/packageApp.sh new file mode 100755 index 0000000..e3175b9 --- /dev/null +++ b/CI/install/osx/packageApp.sh @@ -0,0 +1,67 @@ +rm -rf ./OBS.app + +mkdir OBS.app +mkdir OBS.app/Contents +mkdir OBS.app/Contents/MacOS +mkdir OBS.app/Contents/Plugins +mkdir OBS.app/Contents/Resources + +cp -r rundir/RelWithDebInfo/bin/ ./OBS.app/Contents/MacOS +cp -r rundir/RelWithDebInfo/data ./OBS.app/Contents/Resources +cp ../CI/install/osx/obs.icns ./OBS.app/Contents/Resources +cp -r rundir/RelWithDebInfo/obs-plugins/ ./OBS.app/Contents/Plugins +cp ../CI/install/osx/Info.plist ./OBS.app/Contents + +../CI/install/osx/dylibBundler -b -cd -d ./OBS.app/Contents/Frameworks -p @executable_path/../Frameworks/ \ +-s ./OBS.app/Contents/MacOS \ +-s /usr/local/opt/mbedtls/lib/ \ +-x ./OBS.app/Contents/Plugins/coreaudio-encoder.so \ +-x ./OBS.app/Contents/Plugins/decklink-ouput-ui.so \ +-x ./OBS.app/Contents/Plugins/frontend-tools.so \ +-x ./OBS.app/Contents/Plugins/image-source.so \ +-x ./OBS.app/Contents/Plugins/linux-jack.so \ +-x ./OBS.app/Contents/Plugins/mac-avcapture.so \ +-x ./OBS.app/Contents/Plugins/mac-capture.so \ +-x ./OBS.app/Contents/Plugins/mac-decklink.so \ +-x ./OBS.app/Contents/Plugins/mac-syphon.so \ +-x ./OBS.app/Contents/Plugins/mac-vth264.so \ +-x ./OBS.app/Contents/Plugins/obs-browser.so \ +-x ./OBS.app/Contents/Plugins/obs-browser-page \ +-x ./OBS.app/Contents/Plugins/obs-ffmpeg.so \ +-x ./OBS.app/Contents/Plugins/obs-filters.so \ +-x ./OBS.app/Contents/Plugins/obs-transitions.so \ +-x ./OBS.app/Contents/Plugins/obs-vst.so \ +-x ./OBS.app/Contents/Plugins/rtmp-services.so \ +-x ./OBS.app/Contents/MacOS/obs \ +-x ./OBS.app/Contents/MacOS/obs-ffmpeg-mux \ +-x ./OBS.app/Contents/MacOS/obslua.so \ +-x ./OBS.app/Contents/MacOS/_obspython.so \ +-x ./OBS.app/Contents/Plugins/obs-x264.so \ +-x ./OBS.app/Contents/Plugins/text-freetype2.so \ +-x ./OBS.app/Contents/Plugins/obs-libfdk.so +# -x ./OBS.app/Contents/Plugins/obs-outputs.so \ + +/usr/local/Cellar/qt/5.10.1/bin/macdeployqt ./OBS.app + +mv ./OBS.app/Contents/MacOS/libobs-opengl.so ./OBS.app/Contents/Frameworks + +# put qt network in here becasuse streamdeck uses it +cp -r /usr/local/opt/qt/lib/QtNetwork.framework ./OBS.app/Contents/Frameworks +chmod +w ./OBS.app/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork +install_name_tool -change /usr/local/Cellar/qt/5.10.1/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./OBS.app/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork + +# decklink ui qt +install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ./OBS.app/Contents/Plugins/decklink-ouput-ui.so +install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./OBS.app/Contents/Plugins/decklink-ouput-ui.so +install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ./OBS.app/Contents/Plugins/decklink-ouput-ui.so + +# frontend tools qt +install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ./OBS.app/Contents/Plugins/frontend-tools.so +install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./OBS.app/Contents/Plugins/frontend-tools.so +install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ./OBS.app/Contents/Plugins/frontend-tools.so + +# vst qt +install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ./OBS.app/Contents/Plugins/obs-vst.so +install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./OBS.app/Contents/Plugins/obs-vst.so +install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ./OBS.app/Contents/Plugins/obs-vst.so +install_name_tool -change /usr/local/opt/qt/lib/QtMacExtras.framework/Versions/5/QtMacExtras @executable_path/../Frameworks/QtMacExtras.framework/Versions/5/QtMacExtras ./OBS.app/Contents/Plugins/obs-vst.so diff --git a/CI/install/osx/settings.json b/CI/install/osx/settings.json new file mode 100644 index 0000000..34fbc2f --- /dev/null +++ b/CI/install/osx/settings.json @@ -0,0 +1,13 @@ +{ + "title": "OBS", + "background": "../CI/install/osx/background.tiff", + "format": "UDZO", + "compression-level": 9, + "window": { "position": { "x": 100, "y": 100 }, + "size": { "width": 540, "height": 380 } }, + "contents": [ + { "x": 120, "y": 180, "type": "file", + "path": "./OBS.app" }, + { "x": 420, "y": 180, "type": "link", "path": "/Applications" } + ] +} diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index a33f9e1..51b771e 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -373,14 +373,6 @@ if (APPLE) target_link_libraries(obs Qt5::MacExtras) set_target_properties(obs PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") - set_property( - TARGET obs - APPEND - PROPERTY INSTALL_RPATH - "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/" - "/Library/Frameworks/Python.framework/Versions/3.6/lib/" - "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/" - ) endif() define_graphic_modules(obs) diff --git a/UI/auth-twitch.cpp b/UI/auth-twitch.cpp index 3cd8b79..ce46c60 100644 --- a/UI/auth-twitch.cpp +++ b/UI/auth-twitch.cpp @@ -216,7 +216,8 @@ void TwitchAuth::LoadUI() chat->SetWidget(browser); cef->add_force_popup_url(moderation_tools_url, chat.data()); - script = bttv_script; + script = "localStorage.setItem('twilight.theme', 1);"; + script += bttv_script; script += ffz_script; browser->setStartupScript(script); diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index a10e482..37d9297 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -3685,19 +3685,19 @@ - 44.1khz + 44.1 kHz 0 - 44.1khz + 44.1 kHz - 48khz + 48 kHz diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher-nix.cpp b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-nix.cpp index 57d507e..fd3c307 100644 --- a/UI/frontend-plugins/frontend-tools/auto-scene-switcher-nix.cpp +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-nix.cpp @@ -126,10 +126,17 @@ static std::string GetWindowTitle(size_t i) if (status >= Success && name != nullptr) { std::string str(name); windowTitle = str; + XFree(name); + } else { + XTextProperty xtp_new_name; + if (XGetWMName(disp(), w, &xtp_new_name) != 0 && + xtp_new_name.value != nullptr) { + std::string str((const char *)xtp_new_name.value); + windowTitle = str; + XFree(xtp_new_name.value); + } } - XFree(name); - return windowTitle; } diff --git a/UI/platform-osx.mm b/UI/platform-osx.mm index bc400f5..3d0c533 100644 --- a/UI/platform-osx.mm +++ b/UI/platform-osx.mm @@ -28,11 +28,29 @@ using namespace std; +bool isInBundle() +{ + NSRunningApplication *app = [NSRunningApplication currentApplication]; + return [app bundleIdentifier] != nil; +} + bool GetDataFilePath(const char *data, string &output) { - stringstream str; - str << OBS_DATA_PATH "/obs-studio/" << data; - output = str.str(); + if (isInBundle()) { + NSRunningApplication *app = + [NSRunningApplication currentApplication]; + NSURL *bundleURL = [app bundleURL]; + NSString *path = [NSString + stringWithFormat:@"Contents/Resources/data/obs-studio/%@", + [NSString stringWithUTF8String:data]]; + NSURL *dataURL = [bundleURL URLByAppendingPathComponent:path]; + output = [[dataURL path] UTF8String]; + } else { + stringstream str; + str << OBS_DATA_PATH "/obs-studio/" << data; + output = str.str(); + } + return !access(output.c_str(), R_OK); } diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index 313a2e1..7c22872 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -1820,7 +1820,7 @@ void WidgetInfo::ControlChanged() break; case OBS_PROPERTY_GROUP: GroupChanged(setting); - return; + break; } if (view->callback && !view->deferUpdate) diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index a72768c..02eff19 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -417,6 +417,8 @@ void AutoConfigStreamPage::on_connectAccount_clicked() #ifdef BROWSER_AVAILABLE std::string service = QT_TO_UTF8(ui->service->currentText()); + OAuth::DeleteCookies(service); + auth = OAuthStreamKey::Login(this, service); if (!!auth) OnAuthConnected(); diff --git a/UI/window-basic-interaction.cpp b/UI/window-basic-interaction.cpp index 8b37b4d..147aafa 100644 --- a/UI/window-basic-interaction.cpp +++ b/UI/window-basic-interaction.cpp @@ -59,6 +59,10 @@ OBSBasicInteraction::OBSBasicInteraction(QWidget *parent, OBSSource source_) const char *name = obs_source_get_name(source); setWindowTitle(QTStr("Basic.InteractionWindow").arg(QT_UTF8(name))); + Qt::WindowFlags flags = windowFlags(); + Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; + setWindowFlags(flags & (~helpFlag)); + auto addDrawCallback = [this]() { obs_display_add_draw_callback(ui->preview->GetDisplay(), OBSBasicInteraction::DrawPreview, diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 6ee726b..ede9733 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -643,6 +643,7 @@ void SimpleOutput::UpdateRecordingSettings() } else if (videoEncoder == SIMPLE_ENCODER_NVENC) { UpdateRecordingSettings_nvenc(crf); } + UpdateRecordingAudioSettings(); } inline void SimpleOutput::SetupOutputs() diff --git a/UI/window-basic-main-scene-collections.cpp b/UI/window-basic-main-scene-collections.cpp index cceef02..e0ea3d1 100644 --- a/UI/window-basic-main-scene-collections.cpp +++ b/UI/window-basic-main-scene-collections.cpp @@ -88,6 +88,38 @@ static bool SceneCollectionExists(const char *findName) return found; } +static bool GetUnusedSceneCollectionFile(std::string &name, std::string &file) +{ + char path[512]; + size_t len; + int ret; + + if (!GetFileSafeName(name.c_str(), file)) { + blog(LOG_WARNING, "Failed to create safe file name for '%s'", + name.c_str()); + return false; + } + + ret = GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes/"); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get scene collection config path"); + return false; + } + + len = file.size(); + file.insert(0, path); + + if (!GetClosestUnusedFileName(file, "json")) { + blog(LOG_WARNING, "Failed to get closest file name for %s", + file.c_str()); + return false; + } + + file.erase(file.size() - 5, 5); + file.erase(0, file.size() - len); + return true; +} + static bool GetSceneCollectionName(QWidget *parent, std::string &name, std::string &file, const char *oldName = nullptr) @@ -95,9 +127,6 @@ static bool GetSceneCollectionName(QWidget *parent, std::string &name, bool rename = oldName != nullptr; const char *title; const char *text; - char path[512]; - size_t len; - int ret; if (rename) { title = Str("Basic.Main.RenameSceneCollection.Title"); @@ -128,29 +157,10 @@ static bool GetSceneCollectionName(QWidget *parent, std::string &name, break; } - if (!GetFileSafeName(name.c_str(), file)) { - blog(LOG_WARNING, "Failed to create safe file name for '%s'", - name.c_str()); + if (!GetUnusedSceneCollectionFile(name, file)) { return false; } - ret = GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes/"); - if (ret <= 0) { - blog(LOG_WARNING, "Failed to get scene collection config path"); - return false; - } - - len = file.size(); - file.insert(0, path); - - if (!GetClosestUnusedFileName(file, "json")) { - blog(LOG_WARNING, "Failed to get closest file name for %s", - file.c_str()); - return false; - } - - file.erase(file.size() - 5, 5); - file.erase(0, file.size() - len); return true; } @@ -166,6 +176,10 @@ bool OBSBasic::AddSceneCollection(bool create_new, const QString &qname) name = QT_TO_UTF8(qname); if (SceneCollectionExists(name.c_str())) return false; + + if (!GetUnusedSceneCollectionFile(name, file)) { + return false; + } } SaveProjectNow(); diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index f095084..ae47eb2 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -466,6 +466,8 @@ void OBSBasicSettings::on_connectAccount_clicked() #ifdef BROWSER_AVAILABLE std::string service = QT_TO_UTF8(ui->service->currentText()); + OAuth::DeleteCookies(service); + auth = OAuthStreamKey::Login(this, service); if (!!auth) OnAuthConnected(); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 4d10e72..d62f312 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -2189,9 +2189,9 @@ void OBSBasicSettings::LoadAudioSettings() const char *str; if (sampleRate == 48000) - str = "48khz"; + str = "48 kHz"; else - str = "44.1khz"; + str = "44.1 kHz"; int sampleRateIdx = ui->sampleRate->findText(str); if (sampleRateIdx != -1) @@ -3253,7 +3253,7 @@ void OBSBasicSettings::SaveAudioSettings() } int sampleRate = 44100; - if (sampleRateStr == "48khz") + if (sampleRateStr == "48 kHz") sampleRate = 48000; if (WidgetChanged(ui->sampleRate)) diff --git a/UI/window-extra-browsers.cpp b/UI/window-extra-browsers.cpp index f6ff2d5..e90d172 100644 --- a/UI/window-extra-browsers.cpp +++ b/UI/window-extra-browsers.cpp @@ -533,6 +533,24 @@ void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, dock->SetWidget(browser); + /* Add support for Twitch Dashboard panels */ + if (url.contains("twitch.tv/popout") && + url.contains("dashboard/live")) { + QRegularExpression re("twitch.tv\/popout\/([^/]+)\/"); + QRegularExpressionMatch match = re.match(url); + QString username = match.captured(1); + if (username.length() > 0) { + std::string script; + script = + "Object.defineProperty(document, 'referrer', { get: () => '"; + script += "https://twitch.tv/"; + script += QT_TO_UTF8(username); + script += "/dashboard/live"; + script += "'});"; + browser->setStartupScript(script); + } + } + addDockWidget(Qt::RightDockWidgetArea, dock); if (firstCreate) { diff --git a/UI/window-projector.cpp b/UI/window-projector.cpp index 6dd74ab..afdb373 100644 --- a/UI/window-projector.cpp +++ b/UI/window-projector.cpp @@ -653,6 +653,9 @@ void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy) source = curSource; window->source = source; } + } else if (window->type == ProjectorType::Preview && + !main->IsPreviewProgramMode()) { + window->source = nullptr; } if (source) diff --git a/cmake/Modules/ObsHelpers.cmake b/cmake/Modules/ObsHelpers.cmake index 627aadf..cb63ba6 100644 --- a/cmake/Modules/ObsHelpers.cmake +++ b/cmake/Modules/ObsHelpers.cmake @@ -61,7 +61,7 @@ if(NOT UNIX_STRUCTURE) set(OBS_INSTALL_PREFIX "") set(OBS_RELATIVE_PREFIX "../") - set(OBS_SCRIPT_PLUGIN_DESTINATION "${OBS_DATA_DESTINATION}/obs-scripting/${_lib_suffix}bit") + set(OBS_SCRIPT_PLUGIN_DESTINATION "${OBS_DATA_DESTINATION}/obs-scripting") else() set(OBS_EXECUTABLE_DESTINATION "bin/${_lib_suffix}bit") set(OBS_EXECUTABLE32_DESTINATION "bin/32bit") diff --git a/deps/media-playback/media-playback/media.c b/deps/media-playback/media-playback/media.c index 35554eb..e819840 100644 --- a/deps/media-playback/media-playback/media.c +++ b/deps/media-playback/media-playback/media.c @@ -139,7 +139,7 @@ static int mp_media_next_packet(mp_media_t *media) int ret = av_read_frame(media->fmt, &pkt); if (ret < 0) { - if (ret != AVERROR_EOF) + if (ret != AVERROR_EOF && ret != AVERROR_EXIT) blog(LOG_WARNING, "MP: av_read_frame failed: %s (%d)", av_err2str(ret), ret); return ret; @@ -230,7 +230,7 @@ static bool mp_media_prepare_frames(mp_media_t *m) while (!mp_media_ready_to_start(m)) { if (!m->eof) { int ret = mp_media_next_packet(m); - if (ret == AVERROR_EOF) + if (ret == AVERROR_EOF || ret == AVERROR_EXIT) m->eof = true; else if (ret < 0) return false; @@ -577,8 +577,10 @@ static bool init_avformat(mp_media_t *m) av_dict_set_int(&opts, "buffer_size", m->buffering, 0); m->fmt = avformat_alloc_context(); - m->fmt->interrupt_callback.callback = interrupt_callback; - m->fmt->interrupt_callback.opaque = m; + if (!m->is_local_file) { + m->fmt->interrupt_callback.callback = interrupt_callback; + m->fmt->interrupt_callback.opaque = m; + } int ret = avformat_open_input(&m->fmt, m->path, format, opts ? &opts : NULL); diff --git a/deps/obs-scripting/CMakeLists.txt b/deps/obs-scripting/CMakeLists.txt index 92fb1b9..340eb30 100644 --- a/deps/obs-scripting/CMakeLists.txt +++ b/deps/obs-scripting/CMakeLists.txt @@ -12,6 +12,11 @@ if(MSVC) w32-pthreads) endif() +if(APPLE) + set(obs-scripting_PLATFORM_DEPS + objc) +endif() + option(DISABLE_LUA "Disable Lua scripting support" OFF) option(DISABLE_PYTHON "Disable Python scripting support" OFF) diff --git a/deps/obs-scripting/obs-scripting-lua.c b/deps/obs-scripting/obs-scripting-lua.c index a626555..ebf6a1f 100644 --- a/deps/obs-scripting/obs-scripting-lua.c +++ b/deps/obs-scripting/obs-scripting-lua.c @@ -43,7 +43,8 @@ static const char *startup_script_template = "\ for val in pairs(package.preload) do\n\ package.preload[val] = nil\n\ end\n\ -package.cpath = package.cpath .. \";\" .. \"%s\" .. \"/?." SO_EXT "\"\n\ +package.cpath = package.cpath .. \";\" .. \"%s/Contents/MacOS/?.so\" .. \";\" .. \"%s\" .. \"/?." SO_EXT + "\"\n\ require \"obslua\"\n"; static const char *get_script_path_func = "\ @@ -1310,7 +1311,31 @@ void obs_lua_load(void) /* ---------------------------------------------- */ /* Initialize Lua startup script */ - dstr_printf(&tmp, startup_script_template, SCRIPT_DIR); + char *bundlePath = "./"; + +#ifdef __APPLE__ + Class nsRunningApplication = objc_lookUpClass("NSRunningApplication"); + SEL currentAppSel = sel_getUid("currentApplication"); + + typedef id (*running_app_func)(Class, SEL); + running_app_func operatingSystemName = (running_app_func)objc_msgSend; + id app = operatingSystemName(nsRunningApplication, currentAppSel); + + typedef id (*bundle_url_func)(id, SEL); + bundle_url_func bundleURL = (bundle_url_func)objc_msgSend; + id url = bundleURL(app, sel_getUid("bundleURL")); + + typedef id (*url_path_func)(id, SEL); + url_path_func urlPath = (url_path_func)objc_msgSend; + + id path = urlPath(url, sel_getUid("path")); + + typedef id (*string_func)(id, SEL); + string_func utf8String = (string_func)objc_msgSend; + bundlePath = (char *)utf8String(path, sel_registerName("UTF8String")); +#endif + + dstr_printf(&tmp, startup_script_template, bundlePath, SCRIPT_DIR); startup_script = tmp.array; dstr_free(&dep_paths); diff --git a/deps/obs-scripting/obs-scripting-python-import.c b/deps/obs-scripting/obs-scripting-python-import.c index 5a60681..c8f74ef 100644 --- a/deps/obs-scripting/obs-scripting-python-import.c +++ b/deps/obs-scripting/obs-scripting-python-import.c @@ -32,6 +32,12 @@ #define SO_EXT ".dylib" #endif +#ifdef __APPLE__ +#define PYTHON_LIB_SUBDIR "lib/" +#else +#define PYTHON_LIB_SUBDIR "" +#endif + bool import_python(const char *python_path) { struct dstr lib_path; @@ -44,7 +50,7 @@ bool import_python(const char *python_path) dstr_init_copy(&lib_path, python_path); dstr_replace(&lib_path, "\\", "/"); if (!dstr_is_empty(&lib_path)) { - dstr_cat(&lib_path, "/"); + dstr_cat(&lib_path, "/" PYTHON_LIB_SUBDIR); } dstr_cat(&lib_path, PYTHON_LIB SO_EXT); diff --git a/deps/obs-scripting/obs-scripting-python.c b/deps/obs-scripting/obs-scripting-python.c index d02ac80..8899df8 100644 --- a/deps/obs-scripting/obs-scripting-python.c +++ b/deps/obs-scripting/obs-scripting-python.c @@ -1651,6 +1651,13 @@ bool obs_scripting_load_python(const char *python_path) add_to_python_path(SCRIPT_DIR); +#if __APPLE__ + char *exec_path = os_get_executable_path_ptr(""); + if (exec_path) + add_to_python_path(exec_path); + bfree(exec_path); +#endif + py_obspython = PyImport_ImportModule("obspython"); bool success = !py_error(); if (!success) { diff --git a/deps/obs-scripting/obspython/CMakeLists.txt b/deps/obs-scripting/obspython/CMakeLists.txt index 57b65bd..0973273 100644 --- a/deps/obs-scripting/obspython/CMakeLists.txt +++ b/deps/obs-scripting/obspython/CMakeLists.txt @@ -42,7 +42,12 @@ if(CMAKE_VERSION VERSION_GREATER 3.7.2) else() SWIG_ADD_MODULE(obspython python obspython.i ../cstrcache.cpp ../cstrcache.h) endif() -SWIG_LINK_LIBRARIES(obspython obs-scripting libobs ${PYTHON_LIBRARIES}) + +IF(APPLE) + SWIG_LINK_LIBRARIES(obspython obs-scripting libobs) +ELSE() + SWIG_LINK_LIBRARIES(obspython obs-scripting libobs ${PYTHON_LIBRARIES}) +ENDIF() function(install_plugin_bin_swig target additional_target) if(APPLE) @@ -57,14 +62,7 @@ function(install_plugin_bin_swig target additional_target) PREFIX "") if (APPLE) - set_property( - TARGET ${additional_target} - APPEND - PROPERTY INSTALL_RPATH - "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/" - "/Library/Frameworks/Python.framework/Versions/3.6/lib/" - "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/" - ) + SET_TARGET_PROPERTIES(${additional_target} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") endif() install(FILES "${CMAKE_CURRENT_BINARY_DIR}/obspython.py" diff --git a/formatcode.sh b/formatcode.sh index 58c63d0..6b8d703 100755 --- a/formatcode.sh +++ b/formatcode.sh @@ -27,5 +27,14 @@ else CLANG_FORMAT=clang-format fi -find . -type d \( -path ./deps -o -path ./cmake -o -path ./plugins/decklink/win -o -path ./plugins/decklink/mac -o -path ./plugins/decklink/linux -o -path ./build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \ +find . -type d \( -path ./deps \ +-o -path ./cmake \ +-o -path ./plugins/decklink/win \ +-o -path ./plugins/decklink/mac \ +-o -path ./plugins/decklink/linux \ +-o -path ./plugins/enc-amf \ +-o -path ./plugins/mac-syphon/syphon-framework \ +-o -path ./plugins/obs-outputs/ftl-sdk \ +-o -path ./plugins/obs-vst \ +-o -path ./build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \ | xargs -I{} -P ${NPROC} ${CLANG_FORMAT} -i -style=file -fallback-style=none {} diff --git a/libobs-d3d11/d3d11-rebuild.cpp b/libobs-d3d11/d3d11-rebuild.cpp index db7dc3d..58cb6d9 100644 --- a/libobs-d3d11/d3d11-rebuild.cpp +++ b/libobs-d3d11/d3d11-rebuild.cpp @@ -178,10 +178,13 @@ void gs_vertex_shader::Rebuild(ID3D11Device *dev) if (FAILED(hr)) throw HRError("Failed to create vertex shader", hr); - hr = dev->CreateInputLayout(layoutData.data(), (UINT)layoutData.size(), - data.data(), data.size(), &layout); - if (FAILED(hr)) - throw HRError("Failed to create input layout", hr); + const UINT layoutSize = (UINT)layoutData.size(); + if (layoutSize > 0) { + hr = dev->CreateInputLayout(layoutData.data(), layoutSize, + data.data(), data.size(), &layout); + if (FAILED(hr)) + throw HRError("Failed to create input layout", hr); + } if (constantSize) { hr = dev->CreateBuffer(&bd, NULL, &constants); diff --git a/libobs-d3d11/d3d11-subsystem.cpp b/libobs-d3d11/d3d11-subsystem.cpp index 3e4d77f..4377128 100644 --- a/libobs-d3d11/d3d11-subsystem.cpp +++ b/libobs-d3d11/d3d11-subsystem.cpp @@ -796,6 +796,53 @@ bool device_enum_adapters(bool (*callback)(void *param, const char *name, } } +static bool GetMonitorTarget(const MONITORINFOEX &info, + DISPLAYCONFIG_TARGET_DEVICE_NAME &target) +{ + bool found = false; + + UINT32 numPath, numMode; + if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPath, + &numMode) == ERROR_SUCCESS) { + std::vector paths(numPath); + std::vector modes(numMode); + if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPath, + paths.data(), &numMode, modes.data(), + nullptr) == ERROR_SUCCESS) { + paths.resize(numPath); + for (size_t i = 0; i < numPath; ++i) { + const DISPLAYCONFIG_PATH_INFO &path = paths[i]; + + DISPLAYCONFIG_SOURCE_DEVICE_NAME + source; + source.header.type = + DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + source.header.size = sizeof(source); + source.header.adapterId = + path.sourceInfo.adapterId; + source.header.id = path.sourceInfo.id; + if (DisplayConfigGetDeviceInfo( + &source.header) == ERROR_SUCCESS && + wcscmp(info.szDevice, + source.viewGdiDeviceName) == 0) { + target.header.type = + DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + target.header.size = sizeof(target); + target.header.adapterId = + path.sourceInfo.adapterId; + target.header.id = path.targetInfo.id; + found = DisplayConfigGetDeviceInfo( + &target.header) == + ERROR_SUCCESS; + break; + } + } + } + } + + return found; +} + static inline void LogAdapterMonitors(IDXGIAdapter1 *adapter) { UINT i; @@ -806,15 +853,41 @@ static inline void LogAdapterMonitors(IDXGIAdapter1 *adapter) if (FAILED(output->GetDesc(&desc))) continue; - RECT rect = desc.DesktopCoordinates; + unsigned refresh = 0; + + bool target_found = false; + DISPLAYCONFIG_TARGET_DEVICE_NAME target; + + MONITORINFOEX info; + info.cbSize = sizeof(info); + if (GetMonitorInfo(desc.Monitor, &info)) { + target_found = GetMonitorTarget(info, target); + + DEVMODE mode; + mode.dmSize = sizeof(mode); + mode.dmDriverExtra = 0; + if (EnumDisplaySettings(info.szDevice, + ENUM_CURRENT_SETTINGS, &mode)) { + refresh = mode.dmDisplayFrequency; + } + } + + if (!target_found) { + target.monitorFriendlyDeviceName[0] = 0; + } + + const RECT &rect = desc.DesktopCoordinates; blog(LOG_INFO, "\t output %u: " "pos={%d, %d}, " "size={%d, %d}, " - "attached=%s", + "attached=%s, " + "refresh=%u, " + "name=%ls", i, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, - desc.AttachedToDesktop ? "true" : "false"); + desc.AttachedToDesktop ? "true" : "false", refresh, + target.monitorFriendlyDeviceName); } } @@ -853,6 +926,25 @@ static inline void LogD3DAdapters() blog(LOG_INFO, "\t Shared VRAM: %u", desc.SharedSystemMemory); + /* driver version */ + LARGE_INTEGER umd; + hr = adapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), + &umd); + if (SUCCEEDED(hr)) { + const uint64_t version = umd.QuadPart; + const uint16_t aa = (version >> 48) & 0xffff; + const uint16_t bb = (version >> 32) & 0xffff; + const uint16_t ccccc = (version >> 16) & 0xffff; + const uint16_t ddddd = version & 0xffff; + blog(LOG_INFO, + "\t Driver Version: %" PRIu16 ".%" PRIu16 + ".%" PRIu16 ".%" PRIu16, + aa, bb, ccccc, ddddd); + } else { + blog(LOG_INFO, "\t Driver Version: Unknown (0x%X)", + (unsigned)hr); + } + LogAdapterMonitors(adapter); } } diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 8681148..0eabf60 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -110,7 +110,7 @@ if(WIN32) endif() elseif(APPLE) set(libobs_PLATFORM_SOURCES - obs-cocoa.c + obs-cocoa.m util/threading-posix.c util/pipe-posix.c util/platform-nix.c diff --git a/libobs/obs-cocoa.c b/libobs/obs-cocoa.m similarity index 94% rename from libobs/obs-cocoa.c rename to libobs/obs-cocoa.m index 30d0964..7f261d0 100644 --- a/libobs/obs-cocoa.c +++ b/libobs/obs-cocoa.m @@ -29,6 +29,14 @@ #include #include +#import + +bool is_in_bundle() +{ + NSRunningApplication *app = [NSRunningApplication currentApplication]; + return [app bundleIdentifier] != nil; +} + const char *get_module_extension(void) { return ".so"; @@ -51,12 +59,45 @@ void add_default_module_paths(void) { for (int i = 0; i < module_patterns_size; i++) obs_add_module_path(module_bin[i], module_data[i]); + + if (is_in_bundle()) { + NSRunningApplication *app = + [NSRunningApplication currentApplication]; + NSURL *bundleURL = [app bundleURL]; + NSURL *pluginsURL = [bundleURL + URLByAppendingPathComponent:@"Contents/Plugins"]; + NSURL *dataURL = [bundleURL + URLByAppendingPathComponent: + @"Contents/Resources/data/obs-plugins/%module%"]; + + const char *binPath = [[pluginsURL path] + cStringUsingEncoding:NSUTF8StringEncoding]; + const char *dataPath = [[dataURL path] + cStringUsingEncoding:NSUTF8StringEncoding]; + + obs_add_module_path(binPath, dataPath); + } } char *find_libobs_data_file(const char *file) { struct dstr path; - dstr_init_copy(&path, OBS_INSTALL_DATA_PATH "/libobs/"); + + if (is_in_bundle()) { + NSRunningApplication *app = + [NSRunningApplication currentApplication]; + NSURL *bundleURL = [app bundleURL]; + NSURL *libobsDataURL = + [bundleURL URLByAppendingPathComponent: + @"Contents/Resources/data/libobs/"]; + const char *libobsDataPath = [[libobsDataURL path] + cStringUsingEncoding:NSUTF8StringEncoding]; + dstr_init_copy(&path, libobsDataPath); + dstr_cat(&path, "/"); + } else { + dstr_init_copy(&path, OBS_INSTALL_DATA_PATH "/libobs/"); + } + dstr_cat(&path, file); return path.array; } @@ -111,13 +152,20 @@ static void log_available_memory(void) memory_available / 1024 / 1024); } -static void log_os_name(id pi, SEL UTF8String) +static void log_os_name(id pi, SEL UTF8StringSel) { - unsigned long os_id = (unsigned long)objc_msgSend( + typedef int (*os_func)(id, SEL); + os_func operatingSystem = (os_func)objc_msgSend; + unsigned long os_id = (unsigned long)operatingSystem( pi, sel_registerName("operatingSystem")); - id os = objc_msgSend(pi, sel_registerName("operatingSystemName")); - const char *name = (const char *)objc_msgSend(os, UTF8String); + typedef id (*os_name_func)(id, SEL); + os_name_func operatingSystemName = (os_name_func)objc_msgSend; + id os = operatingSystemName(pi, + sel_registerName("operatingSystemName")); + typedef const char *(*utf8_func)(id, SEL); + utf8_func UTF8String = (utf8_func)objc_msgSend; + const char *name = UTF8String(os, UTF8StringSel); if (os_id == 5 /*NSMACHOperatingSystem*/) { blog(LOG_INFO, "OS Name: Mac OS X (%s)", name); @@ -127,11 +175,15 @@ static void log_os_name(id pi, SEL UTF8String) blog(LOG_INFO, "OS Name: %s", name ? name : "Unknown"); } -static void log_os_version(id pi, SEL UTF8String) +static void log_os_version(id pi, SEL UTF8StringSel) { - id vs = objc_msgSend(pi, - sel_registerName("operatingSystemVersionString")); - const char *version = (const char *)objc_msgSend(vs, UTF8String); + typedef id (*version_func)(id, SEL); + version_func operatingSystemVersionString = (version_func)objc_msgSend; + id vs = operatingSystemVersionString( + pi, sel_registerName("operatingSystemVersionString")); + typedef const char *(*utf8_func)(id, SEL); + utf8_func UTF8String = (utf8_func)objc_msgSend; + const char *version = UTF8String(vs, UTF8StringSel); blog(LOG_INFO, "OS Version: %s", version ? version : "Unknown"); } @@ -139,8 +191,9 @@ static void log_os_version(id pi, SEL UTF8String) static void log_os(void) { Class NSProcessInfo = objc_getClass("NSProcessInfo"); - id pi = objc_msgSend((id)NSProcessInfo, - sel_registerName("processInfo")); + typedef id (*func)(id, SEL); + func processInfo = (func)objc_msgSend; + id pi = processInfo((id)NSProcessInfo, sel_registerName("processInfo")); SEL UTF8String = sel_registerName("UTF8String"); @@ -1673,9 +1726,11 @@ static bool mouse_button_pressed(obs_key_t key, bool *pressed) } Class NSEvent = objc_getClass("NSEvent"); - SEL pressedMouseButtons = sel_registerName("pressedMouseButtons"); - NSUInteger buttons = - (NSUInteger)objc_msgSend((id)NSEvent, pressedMouseButtons); + SEL pressedMouseButtonsSel = sel_registerName("pressedMouseButtons"); + typedef int (*func)(id, SEL); + func pressedMouseButtons = (func)objc_msgSend; + NSUInteger buttons = (NSUInteger)pressedMouseButtons( + (id)NSEvent, pressedMouseButtonsSel); *pressed = (buttons & (1 << button)) != 0; return true; diff --git a/libobs/obs-config.h b/libobs/obs-config.h index 644c2b6..acc5d73 100644 --- a/libobs/obs-config.h +++ b/libobs/obs-config.h @@ -41,7 +41,7 @@ * * Reset to zero each major or minor version */ -#define LIBOBS_API_PATCH_VER 3 +#define LIBOBS_API_PATCH_VER 5 #define MAKE_SEMANTIC_VERSION(major, minor, patch) \ ((major << 24) | (minor << 16) | patch) diff --git a/libobs/obs-video-gpu-encode.c b/libobs/obs-video-gpu-encode.c index 808b55c..bec537d 100644 --- a/libobs/obs-video-gpu-encode.c +++ b/libobs/obs-video-gpu-encode.c @@ -58,9 +58,10 @@ static void *gpu_encode_thread(void *unused) video_output_inc_texture_frames(video->video); for (size_t i = 0; i < video->gpu_encoders.num; i++) { - obs_encoder_t *encoder = video->gpu_encoders.array[i]; - da_push_back(encoders, &encoder); - obs_encoder_addref(encoder); + obs_encoder_t *encoder = obs_encoder_get_ref( + video->gpu_encoders.array[i]); + if (encoder) + da_push_back(encoders, &encoder); } pthread_mutex_unlock(&video->gpu_encoder_mutex); diff --git a/plugins/linux-capture/xcompcap-main.cpp b/plugins/linux-capture/xcompcap-main.cpp index a705c56..9d3b153 100644 --- a/plugins/linux-capture/xcompcap-main.cpp +++ b/plugins/linux-capture/xcompcap-main.cpp @@ -664,7 +664,8 @@ void XCompcapMain::render(gs_effect_t *effect) effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); while (gs_effect_loop(effect, "Draw")) { - xcursor_render(p->cursor); + xcursor_render(p->cursor, -p->cur_cut_left, + -p->cur_cut_top); } } } diff --git a/plugins/linux-capture/xcursor.c b/plugins/linux-capture/xcursor.c index 85bb73a..021fd42 100644 --- a/plugins/linux-capture/xcursor.c +++ b/plugins/linux-capture/xcursor.c @@ -103,7 +103,7 @@ void xcursor_tick(xcursor_t *data) XFree(xc); } -void xcursor_render(xcursor_t *data) +void xcursor_render(xcursor_t *data, int x_offset, int y_offset) { if (!data->tex) return; @@ -117,7 +117,8 @@ void xcursor_render(xcursor_t *data) gs_enable_color(true, true, true, false); gs_matrix_push(); - gs_matrix_translate3f(data->render_x, data->render_y, 0.0f); + gs_matrix_translate3f(data->render_x + x_offset, + data->render_y + y_offset, 0.0f); gs_draw_sprite(data->tex, 0, 0, 0); gs_matrix_pop(); diff --git a/plugins/linux-capture/xcursor.h b/plugins/linux-capture/xcursor.h index 6631561..3c03a7b 100644 --- a/plugins/linux-capture/xcursor.h +++ b/plugins/linux-capture/xcursor.h @@ -61,7 +61,7 @@ void xcursor_tick(xcursor_t *data); * * This needs to be executed within a valid render context */ -void xcursor_render(xcursor_t *data); +void xcursor_render(xcursor_t *data, int x_offset, int y_offset); /** * Specify offset for the cursor diff --git a/plugins/linux-capture/xhelpers.c b/plugins/linux-capture/xhelpers.c index d73d7da..a6e8884 100644 --- a/plugins/linux-capture/xhelpers.c +++ b/plugins/linux-capture/xhelpers.c @@ -108,7 +108,6 @@ int randr_screen_count(xcb_connection_t *xcb) { if (!xcb) return 0; - xcb_screen_t *screen; screen = xcb_setup_roots_iterator(xcb_get_setup(xcb)).data; diff --git a/plugins/linux-capture/xshm-input.c b/plugins/linux-capture/xshm-input.c index c7db74d..1ff3ebb 100644 --- a/plugins/linux-capture/xshm-input.c +++ b/plugins/linux-capture/xshm-input.c @@ -324,7 +324,9 @@ static bool xshm_server_changed(obs_properties_t *props, obs_property_t *p, ")", i, w, h, x, y); - obs_property_list_add_int(screens, screen_info.array, i); + if (h > 0 && w > 0) + obs_property_list_add_int(screens, screen_info.array, + i); } /* handle missing screen */ diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-output.c b/plugins/obs-ffmpeg/obs-ffmpeg-output.c index 834090f..ab288c0 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-output.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-output.c @@ -183,7 +183,8 @@ static bool parse_params(AVCodecContext *context, char **opts) *assign = 0; value = assign + 1; - if (av_opt_set(context->priv_data, name, value, 0)) { + if (av_opt_set(context, name, value, + AV_OPT_SEARCH_CHILDREN)) { blog(LOG_WARNING, "Failed to set %s=%s", name, value); ret = false; diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index 810ae7f..d528758 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -1342,7 +1342,7 @@ static void check_to_drop_frames(struct rtmp_stream *stream, bool pframes) return; } - if (buffer_duration_usec >= DBR_TRIGGER_USEC) { + if ((uint64_t)buffer_duration_usec >= DBR_TRIGGER_USEC) { pthread_mutex_lock(&stream->dbr_mutex); bitrate_changed = dbr_bitrate_lowered(stream); pthread_mutex_unlock(&stream->dbr_mutex); diff --git a/plugins/rtmp-services/CMakeLists.txt b/plugins/rtmp-services/CMakeLists.txt index 07e7250..a799f00 100644 --- a/plugins/rtmp-services/CMakeLists.txt +++ b/plugins/rtmp-services/CMakeLists.txt @@ -1,15 +1,21 @@ project(rtmp-services) +find_package(Libcurl REQUIRED) + +include_directories(${LIBCURL_INCLUDE_DIRS}) + include_directories(${OBS_JANSSON_INCLUDE_DIRS}) set(rtmp-services_SOURCES twitch.c + younow.c rtmp-common.c rtmp-custom.c rtmp-services-main.c) set(rtmp-services_HEADERS twitch.h + younow.h rtmp-format-ver.h) set(RTMP_SERVICES_URL @@ -28,10 +34,12 @@ add_library(rtmp-services MODULE ${rtmp-services_SOURCES} ${rtmp-services_HEADERS} ${rtmp-services_config_HEADERS}) + target_link_libraries(rtmp-services libobs file-updater - ${OBS_JANSSON_IMPORT}) + ${OBS_JANSSON_IMPORT} + ${LIBCURL_LIBRARIES}) target_include_directories(rtmp-services PUBLIC diff --git a/plugins/rtmp-services/data/package.json b/plugins/rtmp-services/data/package.json old mode 100644 new mode 100755 index 56470f7..a297b2d --- a/plugins/rtmp-services/data/package.json +++ b/plugins/rtmp-services/data/package.json @@ -1,10 +1,10 @@ { "url": "https://obsproject.com/obs2_update/rtmp-services", - "version": 112, + "version": 114, "files": [ { "name": "services.json", - "version": 112 + "version": 114 } ] } diff --git a/plugins/rtmp-services/data/services.json b/plugins/rtmp-services/data/services.json old mode 100644 new mode 100755 index 17587f9..724e6e2 --- a/plugins/rtmp-services/data/services.json +++ b/plugins/rtmp-services/data/services.json @@ -1465,6 +1465,20 @@ "max audio bitrate": 192 } }, + { + "name": "ChathostessModels", + "servers": [ + { + "name": "ChathostessModels - Default", + "url": "rtmp://wowza01.foobarweb.com/cmschatsys_video" + } + ], + "recommended": { + "keyint": 2, + "max video bitrate": 3000, + "max audio bitrate": 128 + } + }, { "name": "Camplace", "servers": [ @@ -1500,6 +1514,24 @@ "x264opts": "tune=zerolatency" } }, + { + "name": "YouNow", + "common": false, + "servers": [ + { + "name": "younow.com", + "url": "https://signaling-api.younow-prod.video.propsproject.com/api/v1/ingest/server/" + } + ], + "recommended": { + "keyint": 2, + "output": "ftl_output", + "max audio bitrate": 160, + "max video bitrate": 7000, + "profile": "main", + "bframes": 0 + } + }, { "name": "Steam", "common": false, @@ -1559,6 +1591,23 @@ "max video bitrate": 7000, "max audio bitrate": 128 } + }, + { + "name": "Stars.AVN.com", + "servers": [ + { + "name": "Default", + "url": "rtmp://alpha.gateway.stars.avn.com/live" + } + ], + "recommended": { + "keyint": 2, + "profile": "main", + "max video bitrate": 2500, + "max audio bitrate": 192, + "bframes": 0, + "x264opts": "tune=zerolatency" + } } ] } diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index bf0b178..5734716 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -5,6 +5,7 @@ #include "rtmp-format-ver.h" #include "twitch.h" +#include "younow.h" struct rtmp_common { char *service; @@ -485,7 +486,9 @@ static void apply_video_encoder_settings(obs_data_t *settings, obs_data_set_string(settings, "rate_control", "CBR"); item = json_object_get(recommended, "profile"); - if (json_is_string(item)) { + obs_data_item_t *enc_item = obs_data_item_byname(settings, "profile"); + if (json_is_string(item) && + obs_data_item_gettype(enc_item) == OBS_DATA_STRING) { const char *profile = json_string_value(item); obs_data_set_string(settings, "profile", profile); } @@ -594,6 +597,12 @@ static const char *rtmp_common_url(void *data) } } + if (service->service && strcmp(service->service, "YouNow") == 0) { + if (service->server && service->key) { + return younow_get_ingest(service->server, service->key); + } + } + return service->server; } diff --git a/plugins/rtmp-services/younow.c b/plugins/rtmp-services/younow.c new file mode 100644 index 0000000..253660f --- /dev/null +++ b/plugins/rtmp-services/younow.c @@ -0,0 +1,113 @@ +#include +#include +#include + +#include +#include "util/base.h" +#include "younow.h" + +struct younow_mem_struct { + char *memory; + size_t size; +}; + +static char *current_ingest = NULL; + +static size_t younow_write_cb(void *contents, size_t size, size_t nmemb, + void *userp) +{ + size_t realsize = size * nmemb; + struct younow_mem_struct *mem = (struct younow_mem_struct *)userp; + + mem->memory = realloc(mem->memory, mem->size + realsize + 1); + if (mem->memory == NULL) { + blog(LOG_WARNING, "yyounow_write_cb: realloc returned NULL"); + return 0; + } + + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +const char *younow_get_ingest(const char *server, const char *key) +{ + CURL *curl_handle; + CURLcode res; + struct younow_mem_struct chunk; + struct dstr uri; + long response_code; + + // find the delimiter in stream key + const char *delim = strchr(key, '_'); + if (delim == NULL) { + blog(LOG_WARNING, + "younow_get_ingest: delimiter not found in stream key"); + return server; + } + + curl_handle = curl_easy_init(); + + chunk.memory = malloc(1); /* will be grown as needed by realloc */ + chunk.size = 0; /* no data at this point */ + + dstr_init(&uri); + dstr_copy(&uri, server); + dstr_ncat(&uri, key, delim - key); + + curl_easy_setopt(curl_handle, CURLOPT_URL, uri.array); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, true); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2L); + curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 3L); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, younow_write_cb); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk); + +#if LIBCURL_VERSION_NUM >= 0x072400 + // A lot of servers don't yet support ALPN + curl_easy_setopt(curl_handle, CURLOPT_SSL_ENABLE_ALPN, 0); +#endif + + res = curl_easy_perform(curl_handle); + dstr_free(&uri); + + if (res != CURLE_OK) { + blog(LOG_WARNING, + "younow_get_ingest: curl_easy_perform() failed: %s", + curl_easy_strerror(res)); + curl_easy_cleanup(curl_handle); + free(chunk.memory); + return server; + } + + curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code != 200) { + blog(LOG_WARNING, + "younow_get_ingest: curl_easy_perform() returned code: %ld", + response_code); + curl_easy_cleanup(curl_handle); + free(chunk.memory); + return server; + } + + curl_easy_cleanup(curl_handle); + + if (chunk.size == 0) { + blog(LOG_WARNING, + "younow_get_ingest: curl_easy_perform() returned empty response"); + free(chunk.memory); + return server; + } + + if (current_ingest) { + free(current_ingest); + current_ingest = NULL; + } + + current_ingest = strdup(chunk.memory); + free(chunk.memory); + blog(LOG_INFO, "younow_get_ingest: returning ingest: %s", + current_ingest); + return current_ingest; +} diff --git a/plugins/rtmp-services/younow.h b/plugins/rtmp-services/younow.h new file mode 100644 index 0000000..427ebfe --- /dev/null +++ b/plugins/rtmp-services/younow.h @@ -0,0 +1,3 @@ +#pragma once + +extern const char *younow_get_ingest(const char *server, const char *key); \ No newline at end of file