From 54d3fa0b8ddd7055349b768d6ab2449de8446f89 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Sun, 26 Apr 2020 16:47:36 -0700 Subject: [PATCH 01/29] Revert "Merge pull request #1786 from brittneysclark/qsv_texture_based_encoding" This reverts commit 05b66262c8aab7b973d7fca0751433091c9be17c, reversing changes made to 01df98b3aa52391e50548ff3ff05c20fc02b65c2. --- plugins/obs-qsv11/CMakeLists.txt | 1 - plugins/obs-qsv11/QSV_Encoder.cpp | 17 ------ plugins/obs-qsv11/QSV_Encoder.h | 2 - plugins/obs-qsv11/QSV_Encoder_Internal.cpp | 64 --------------------- plugins/obs-qsv11/QSV_Encoder_Internal.h | 3 - plugins/obs-qsv11/common_directx11.cpp | 44 +-------------- plugins/obs-qsv11/common_utils.h | 2 - plugins/obs-qsv11/obs-qsv11-plugin-main.c | 2 - plugins/obs-qsv11/obs-qsv11.c | 66 +--------------------- 9 files changed, 4 insertions(+), 197 deletions(-) diff --git a/plugins/obs-qsv11/CMakeLists.txt b/plugins/obs-qsv11/CMakeLists.txt index 6e5c6411ee62fb..a9b598fa3bcecf 100644 --- a/plugins/obs-qsv11/CMakeLists.txt +++ b/plugins/obs-qsv11/CMakeLists.txt @@ -87,7 +87,6 @@ target_link_libraries(obs-qsv11 d3d11 dxva2 dxgi - dxguid ) target_compile_definitions(obs-qsv11 PRIVATE DX11_D3D) diff --git a/plugins/obs-qsv11/QSV_Encoder.cpp b/plugins/obs-qsv11/QSV_Encoder.cpp index bad3521063c946..91d990cbf0b65a 100644 --- a/plugins/obs-qsv11/QSV_Encoder.cpp +++ b/plugins/obs-qsv11/QSV_Encoder.cpp @@ -273,23 +273,6 @@ int qsv_encoder_encode(qsv_t *pContext, uint64_t ts, uint8_t *pDataY, return -1; } -int qsv_encoder_encode_tex(qsv_t *pContext, uint64_t ts, uint32_t tex_handle, - uint64_t lock_key, uint64_t *next_key, - mfxBitstream **pBS) -{ - QSV_Encoder_Internal *pEncoder = (QSV_Encoder_Internal *)pContext; - mfxStatus sts = MFX_ERR_NONE; - - sts = pEncoder->Encode_tex(ts, tex_handle, lock_key, next_key, pBS); - - if (sts == MFX_ERR_NONE) - return 0; - else if (sts == MFX_ERR_MORE_DATA) - return 1; - else - return -1; -} - int qsv_encoder_close(qsv_t *pContext) { QSV_Encoder_Internal *pEncoder = (QSV_Encoder_Internal *)pContext; diff --git a/plugins/obs-qsv11/QSV_Encoder.h b/plugins/obs-qsv11/QSV_Encoder.h index 146b34c747d805..50dd8e0288888c 100644 --- a/plugins/obs-qsv11/QSV_Encoder.h +++ b/plugins/obs-qsv11/QSV_Encoder.h @@ -133,8 +133,6 @@ void qsv_encoder_version(unsigned short *major, unsigned short *minor); qsv_t *qsv_encoder_open(qsv_param_t *); int qsv_encoder_encode(qsv_t *, uint64_t, uint8_t *, uint8_t *, uint32_t, uint32_t, mfxBitstream **pBS); -int qsv_encoder_encode_tex(qsv_t *, uint64_t, uint32_t, uint64_t, uint64_t *, - mfxBitstream **pBS); int qsv_encoder_headers(qsv_t *, uint8_t **pSPS, uint8_t **pPPS, uint16_t *pnSPS, uint16_t *pnPPS); enum qsv_cpu_platform qsv_get_cpu_platform(); diff --git a/plugins/obs-qsv11/QSV_Encoder_Internal.cpp b/plugins/obs-qsv11/QSV_Encoder_Internal.cpp index 4de63b43e32eab..6031b6815f6ac8 100644 --- a/plugins/obs-qsv11/QSV_Encoder_Internal.cpp +++ b/plugins/obs-qsv11/QSV_Encoder_Internal.cpp @@ -598,70 +598,6 @@ mfxStatus QSV_Encoder_Internal::Encode(uint64_t ts, uint8_t *pDataY, return sts; } -mfxStatus QSV_Encoder_Internal::Encode_tex(uint64_t ts, uint32_t tex_handle, - uint64_t lock_key, - uint64_t *next_key, - mfxBitstream **pBS) -{ - mfxStatus sts = MFX_ERR_NONE; - *pBS = NULL; - int nTaskIdx = GetFreeTaskIndex(m_pTaskPool, m_nTaskPool); - int nSurfIdx = GetFreeSurfaceIndex(m_pmfxSurfaces, m_nSurfNum); - - while (MFX_ERR_NOT_FOUND == nTaskIdx || MFX_ERR_NOT_FOUND == nSurfIdx) { - // No more free tasks or surfaces, need to sync - sts = m_session.SyncOperation( - m_pTaskPool[m_nFirstSyncTask].syncp, 60000); - MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts); - - mfxU8 *pTemp = m_outBitstream.Data; - memcpy(&m_outBitstream, &m_pTaskPool[m_nFirstSyncTask].mfxBS, - sizeof(mfxBitstream)); - - m_pTaskPool[m_nFirstSyncTask].mfxBS.Data = pTemp; - m_pTaskPool[m_nFirstSyncTask].mfxBS.DataLength = 0; - m_pTaskPool[m_nFirstSyncTask].mfxBS.DataOffset = 0; - m_pTaskPool[m_nFirstSyncTask].syncp = NULL; - nTaskIdx = m_nFirstSyncTask; - m_nFirstSyncTask = (m_nFirstSyncTask + 1) % m_nTaskPool; - *pBS = &m_outBitstream; - - nSurfIdx = GetFreeSurfaceIndex(m_pmfxSurfaces, m_nSurfNum); - } - - mfxFrameSurface1 *pSurface = m_pmfxSurfaces[nSurfIdx]; - //copy to default surface directly - pSurface->Data.TimeStamp = ts; - if (m_bUseD3D11 || m_bD3D9HACK) { - sts = simple_copytex(m_mfxAllocator.pthis, pSurface->Data.MemId, - tex_handle, lock_key, next_key); - MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts); - } - - for (;;) { - // Encode a frame asynchronously (returns immediately) - sts = m_pmfxENC->EncodeFrameAsync(NULL, pSurface, - &m_pTaskPool[nTaskIdx].mfxBS, - &m_pTaskPool[nTaskIdx].syncp); - - if (MFX_ERR_NONE < sts && !m_pTaskPool[nTaskIdx].syncp) { - // Repeat the call if warning and no output - if (MFX_WRN_DEVICE_BUSY == sts) - MSDK_SLEEP( - 1); // Wait if device is busy, then repeat the same call - } else if (MFX_ERR_NONE < sts && m_pTaskPool[nTaskIdx].syncp) { - sts = MFX_ERR_NONE; // Ignore warnings if output is available - break; - } else if (MFX_ERR_NOT_ENOUGH_BUFFER == sts) { - // Allocate more bitstream buffer memory here if needed... - break; - } else - break; - } - - return sts; -} - mfxStatus QSV_Encoder_Internal::Drain() { mfxStatus sts = MFX_ERR_NONE; diff --git a/plugins/obs-qsv11/QSV_Encoder_Internal.h b/plugins/obs-qsv11/QSV_Encoder_Internal.h index 712244b836c9be..7f4a7c98dbfe1c 100644 --- a/plugins/obs-qsv11/QSV_Encoder_Internal.h +++ b/plugins/obs-qsv11/QSV_Encoder_Internal.h @@ -70,9 +70,6 @@ class QSV_Encoder_Internal { mfxStatus Encode(uint64_t ts, uint8_t *pDataY, uint8_t *pDataUV, uint32_t strideY, uint32_t strideUV, mfxBitstream **pBS); - mfxStatus Encode_tex(uint64_t ts, uint32_t tex_handle, - uint64_t lock_key, uint64_t *next_key, - mfxBitstream **pBS); mfxStatus ClearData(); mfxStatus Reset(qsv_param_t *pParams); diff --git a/plugins/obs-qsv11/common_directx11.cpp b/plugins/obs-qsv11/common_directx11.cpp index 3df68716ac3fcd..41120af598f850 100644 --- a/plugins/obs-qsv11/common_directx11.cpp +++ b/plugins/obs-qsv11/common_directx11.cpp @@ -57,8 +57,8 @@ IDXGIAdapter *GetIntelDeviceAdapterHandle(mfxSession session) } } - HRESULT hres = CreateDXGIFactory1(__uuidof(IDXGIFactory2), - (void **)(&g_pDXGIFactory)); + HRESULT hres = CreateDXGIFactory(__uuidof(IDXGIFactory2), + (void **)(&g_pDXGIFactory)); if (FAILED(hres)) return NULL; @@ -433,46 +433,6 @@ mfxStatus simple_unlock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr) return MFX_ERR_NONE; } -mfxStatus simple_copytex(mfxHDL pthis, mfxMemId mid, mfxU32 tex_handle, - mfxU64 lock_key, mfxU64 *next_key) -{ - pthis; // To suppress warning for this unused parameter - - CustomMemId *memId = (CustomMemId *)mid; - ID3D11Texture2D *pSurface = (ID3D11Texture2D *)memId->memId; - - IDXGIKeyedMutex *km; - ID3D11Texture2D *input_tex; - HRESULT hr; - - hr = g_pD3D11Device->OpenSharedResource((HANDLE)(uintptr_t)tex_handle, - IID_ID3D11Texture2D, - (void **)&input_tex); - if (FAILED(hr)) { - return MFX_ERR_INVALID_HANDLE; - } - - hr = input_tex->QueryInterface(IID_IDXGIKeyedMutex, (void **)&km); - if (FAILED(hr)) { - input_tex->Release(); - return MFX_ERR_INVALID_HANDLE; - } - - input_tex->SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM); - - km->AcquireSync(lock_key, INFINITE); - - D3D11_TEXTURE2D_DESC desc = {0}; - input_tex->GetDesc(&desc); - D3D11_BOX SrcBox = {0, 0, 0, desc.Width, desc.Height, 1}; - g_pD3D11Ctx->CopySubresourceRegion(pSurface, 0, 0, 0, 0, input_tex, 0, - &SrcBox); - - km->ReleaseSync(*next_key); - - return MFX_ERR_NONE; -} - mfxStatus simple_gethdl(mfxHDL pthis, mfxMemId mid, mfxHDL *handle) { pthis; // To suppress warning for this unused parameter diff --git a/plugins/obs-qsv11/common_utils.h b/plugins/obs-qsv11/common_utils.h index 25568ebe7bcca1..a29e48f32feac0 100644 --- a/plugins/obs-qsv11/common_utils.h +++ b/plugins/obs-qsv11/common_utils.h @@ -95,8 +95,6 @@ mfxStatus simple_lock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr); mfxStatus simple_unlock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr); mfxStatus simple_gethdl(mfxHDL pthis, mfxMemId mid, mfxHDL *handle); mfxStatus simple_free(mfxHDL pthis, mfxFrameAllocResponse *response); -mfxStatus simple_copytex(mfxHDL pthis, mfxMemId mid, mfxU32 tex_handle, - mfxU64 lock_key, mfxU64 *next_key); // ================================================================= // Utility functions, not directly tied to Media SDK functionality diff --git a/plugins/obs-qsv11/obs-qsv11-plugin-main.c b/plugins/obs-qsv11/obs-qsv11-plugin-main.c index c22d77038bfcef..8fa1f85d3c344f 100644 --- a/plugins/obs-qsv11/obs-qsv11-plugin-main.c +++ b/plugins/obs-qsv11/obs-qsv11-plugin-main.c @@ -64,7 +64,6 @@ MODULE_EXPORT const char *obs_module_description(void) } extern struct obs_encoder_info obs_qsv_encoder; -extern struct obs_encoder_info obs_qsv_encoder_tex; bool obs_module_load(void) { @@ -77,7 +76,6 @@ bool obs_module_load(void) if (sts == MFX_ERR_NONE) { obs_register_encoder(&obs_qsv_encoder); - obs_register_encoder(&obs_qsv_encoder_tex); MFXClose(session); } else { impl = MFX_IMPL_HARDWARE_ANY | MFX_IMPL_VIA_D3D9; diff --git a/plugins/obs-qsv11/obs-qsv11.c b/plugins/obs-qsv11/obs-qsv11.c index 186c568cd2c0e4..25feeea5315db0 100644 --- a/plugins/obs-qsv11/obs-qsv11.c +++ b/plugins/obs-qsv11/obs-qsv11.c @@ -60,8 +60,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include -#include #ifndef _STDINT_H_INCLUDED #define _STDINT_H_INCLUDED @@ -972,51 +970,8 @@ static bool obs_qsv_encode(void *data, struct encoder_frame *frame, return true; } -static bool obs_qsv_encode_tex(void *data, uint32_t handle, int64_t pts, - uint64_t lock_key, uint64_t *next_key, - struct encoder_packet *packet, - bool *received_packet) -{ - struct obs_qsv *obsqsv = data; - - if (handle == GS_INVALID_HANDLE) { - warn("Encode failed: bad texture handle"); - *next_key = lock_key; - return false; - } - - if (!packet || !received_packet) - return false; - - EnterCriticalSection(&g_QsvCs); - - video_t *video = obs_encoder_video(obsqsv->encoder); - const struct video_output_info *voi = video_output_get_info(video); - - mfxBitstream *pBS = NULL; - - int ret; - - mfxU64 qsvPTS = pts * 90000 / voi->fps_num; - - ret = qsv_encoder_encode_tex(obsqsv->context, qsvPTS, handle, lock_key, - next_key, &pBS); - - if (ret < 0) { - warn("encode failed"); - LeaveCriticalSection(&g_QsvCs); - return false; - } - - parse_packet(obsqsv, packet, pBS, voi->fps_num, received_packet); - - LeaveCriticalSection(&g_QsvCs); - - return true; -} - struct obs_encoder_info obs_qsv_encoder = { - .id = "obs_qsv11_soft", + .id = "obs_qsv11", .type = OBS_ENCODER_VIDEO, .codec = "h264", .get_name = obs_qsv_getname, @@ -1029,22 +984,5 @@ struct obs_encoder_info obs_qsv_encoder = { .get_extra_data = obs_qsv_extra_data, .get_sei_data = obs_qsv_sei, .get_video_info = obs_qsv_video_info, - .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL, -}; - -struct obs_encoder_info obs_qsv_encoder_tex = { - .id = "obs_qsv11", - .type = OBS_ENCODER_VIDEO, - .codec = "h264", - .get_name = obs_qsv_getname, - .create = obs_qsv_create_tex, - .destroy = obs_qsv_destroy, - .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_PASS_TEXTURE, - .encode_texture = obs_qsv_encode_tex, - .update = obs_qsv_update, - .get_properties = obs_qsv_props, - .get_defaults = obs_qsv_defaults, - .get_extra_data = obs_qsv_extra_data, - .get_sei_data = obs_qsv_sei, - .get_video_info = obs_qsv_video_info, + .caps = OBS_ENCODER_CAP_DYN_BITRATE, }; From dba582874f31ee060025677ffd7e67ccba1a8ff5 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Wed, 26 Feb 2020 13:05:08 +0100 Subject: [PATCH 02/29] rtmp-services: Add Caffeine service Requires the Caffeine plugin to be built and included. --- plugins/rtmp-services/data/services.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/rtmp-services/data/services.json b/plugins/rtmp-services/data/services.json index dadf71091ab8fa..c96463099ea2f9 100644 --- a/plugins/rtmp-services/data/services.json +++ b/plugins/rtmp-services/data/services.json @@ -1878,6 +1878,23 @@ "max audio bitrate": 192, "x264opts": "tune=zerolatency scenecut=0" } + }, + { + "name": "Caffeine", + "common": true, + "servers": [ + { + "name": "Primary", + "url": "http://www.caffeine.tv" + } + ], + "recommended": { + "keyint": 3, + "output": "caffeine_output", + "profile": "main", + "max video bitrate": 2000, + "max audio bitrate": 128 + } } ] } From 1b1e5955e4a866cedd74a0539f41f713a3154e49 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Wed, 26 Feb 2020 13:08:00 +0100 Subject: [PATCH 03/29] libobs: Add flags to disable bandwidth test and hw encode --- libobs/obs-output.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libobs/obs-output.h b/libobs/obs-output.h index 51093f49074502..65d1f4b2fafce7 100644 --- a/libobs/obs-output.h +++ b/libobs/obs-output.h @@ -28,6 +28,8 @@ extern "C" { #define OBS_OUTPUT_SERVICE (1 << 3) #define OBS_OUTPUT_MULTI_TRACK (1 << 4) #define OBS_OUTPUT_CAN_PAUSE (1 << 5) +#define OBS_OUTPUT_BANDWIDTH_TEST_DISABLED (1 << 6) +#define OBS_OUTPUT_HARDWARE_ENCODING_DISABLED (1 << 7) struct encoder_packet; From 411664578599b33c3a26eff63f497ff1f832671d Mon Sep 17 00:00:00 2001 From: prateekshadas <61755734+prateekshadas@users.noreply.github.com> Date: Fri, 13 Mar 2020 10:39:18 -0700 Subject: [PATCH 04/29] cmake: Fix backslashes and fix bin and data not copying --- cmake/Modules/ObsHelpers.cmake | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cmake/Modules/ObsHelpers.cmake b/cmake/Modules/ObsHelpers.cmake index 2119eb88794ff4..013d34e8982731 100644 --- a/cmake/Modules/ObsHelpers.cmake +++ b/cmake/Modules/ObsHelpers.cmake @@ -23,7 +23,7 @@ if(INSTALLER_RUN AND NOT DEFINED ENV{obsInstallerTempDir}) endif() if(DEFINED ENV{obsInstallerTempDir}) - file(TO_CMAKE_PATH "$ENV{obsInstallerTempDir}" ENV{obsInstallerTempDir}) + file(TO_CMAKE_PATH "$ENV{obsInstallerTempDir}" obsInstallerTempDir) endif() if(DEFINED ENV{obsAdditionalInstallFiles}) @@ -33,7 +33,7 @@ else() endif() list(APPEND CMAKE_INCLUDE_PATH - "$ENV{obsAdditionalInstallFiles}/include${_lib_suffix}" + "$ENV{obsAdditionalInstallFiles}/include${_lib_suffix}" "$ENV{obsAdditionalInstallFiles}/include") list(APPEND CMAKE_LIBRARY_PATH @@ -123,7 +123,7 @@ function(obs_finish_bundle) endfunction() function(obs_generate_multiarch_installer) - install(DIRECTORY "$ENV{obsInstallerTempDir}/" + install(DIRECTORY "${obsInstallerTempDir}/" DESTINATION "." USE_SOURCE_PERMISSIONS) endfunction() @@ -359,7 +359,7 @@ function(install_obs_pdb ttype target) obs_debug_copy_helper(${target} "${OBS_OUTPUT_DIR}/$/obs-plugins/${_bit_suffix}") if(DEFINED ENV{obsInstallerTempDir}) - obs_debug_copy_helper(${target} "$ENV{obsInstallerTempDir}/${OBS_PLUGIN_DESTINATION}") + obs_debug_copy_helper(${target} "${obsInstallerTempDir}/${OBS_PLUGIN_DESTINATION}") endif() install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pdbs/" @@ -369,7 +369,7 @@ function(install_obs_pdb ttype target) obs_debug_copy_helper(${target} "${OBS_OUTPUT_DIR}/$/bin/${_bit_suffix}") if(DEFINED ENV{obsInstallerTempDir}) - obs_debug_copy_helper(${target} "$ENV{obsInstallerTempDir}/${OBS_EXECUTABLE_DESTINATION}") + obs_debug_copy_helper(${target} "${obsInstallerTempDir}/${OBS_EXECUTABLE_DESTINATION}") endif() install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pdbs/" @@ -412,7 +412,7 @@ function(install_obs_core target) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "$" - "$ENV{obsInstallerTempDir}/${tmp_target_dir}/$" + "${obsInstallerTempDir}/${tmp_target_dir}/$" VERBATIM) endif() @@ -446,11 +446,11 @@ function(install_obs_bin target mode) install(FILES "${bin}" DESTINATION "${OBS_EXECUTABLE_DESTINATION}") - if(DEFINED ENV{obsInstallerTempDir}) + if(DEFINED ${obsInstallerTempDir}) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "${bin}" - "$ENV{obsInstallerTempDir}/${OBS_EXECUTABLE_DESTINATION}/${fname}" + "${obsInstallerTempDir}/${OBS_EXECUTABLE_DESTINATION}/${fname}" VERBATIM) endif() endforeach() @@ -480,7 +480,7 @@ function(install_obs_plugin target) if(DEFINED ENV{obsInstallerTempDir}) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy - "$" "$ENV{obsInstallerTempDir}/${OBS_PLUGIN_DESTINATION}/$" + "$" "${obsInstallerTempDir}/${OBS_PLUGIN_DESTINATION}/$" VERBATIM) endif() @@ -496,10 +496,10 @@ function(install_obs_data target datadir datadest) "${CMAKE_CURRENT_SOURCE_DIR}/${datadir}" "${OBS_OUTPUT_DIR}/$/data/${datadest}" VERBATIM) - if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND DEFINED ENV{obsInstallerTempDir}) + if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND DEFINED ${obsInstallerTempDir}) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${CMAKE_CURRENT_SOURCE_DIR}/${datadir}" "$ENV{obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}" + "${CMAKE_CURRENT_SOURCE_DIR}/${datadir}" "${obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}" VERBATIM) endif() endfunction() @@ -536,11 +536,11 @@ function(install_obs_data_file target datafile datadest) if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND DEFINED ENV{obsInstallerTempDir}) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory - "$ENV{obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}" + "${obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}" VERBATIM) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_CURRENT_SOURCE_DIR}/${datafile}" "$ENV{obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}" + "${CMAKE_CURRENT_SOURCE_DIR}/${datafile}" "${obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}" VERBATIM) endif() endfunction() @@ -559,7 +559,7 @@ function(install_obs_datatarget target datadest) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "$" - "$ENV{obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}/$" + "${obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}/$" VERBATIM) endif() endfunction() From 7351485d83db274481566e26d87c8b59e61fa75c Mon Sep 17 00:00:00 2001 From: Matthew Orlando Date: Tue, 2 Jul 2019 08:17:11 +0200 Subject: [PATCH 05/29] cmake: Add find_package module for libcaffeine --- cmake/Modules/FindLibcaffeine.cmake | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 cmake/Modules/FindLibcaffeine.cmake diff --git a/cmake/Modules/FindLibcaffeine.cmake b/cmake/Modules/FindLibcaffeine.cmake new file mode 100644 index 00000000000000..e171fbb35339c9 --- /dev/null +++ b/cmake/Modules/FindLibcaffeine.cmake @@ -0,0 +1,74 @@ +# find_package handler for libcaffeine +# +# Parameters +# - LIBCAFFEINE_DIR: Path to libcaffeine source or cpack package +# +# Variables: +# - LIBCAFFEINE_FOUND: Boolean, TRUE if libcaffeine was FOUND_VAR +# - All variables defined by libcaffeineConfig.cmake +# +# Defined Targets: +# - libcaffeine +# + +set(LIBCAFFEINE_DIR "" CACHE PATH "Path to libcaffeine") +set(LIBCAFFEINE_FOUND FALSE) + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(LIBCAFFEINE_REQUIRES_LIBRARY FALSE) +else() + set(LIBCAFFEINE_REQUIRES_LIBRARY TRUE) +endif() + +function(find_libcaffeine_cpack) + math(EXPR BITS "8*${CMAKE_SIZEOF_VOID_P}") + + find_file(LIBCAFFEINE_CMAKE_FILE + NAMES + libcaffeineConfig.cmake + HINTS + ${DepsPath} + ENV DepsPath + ${DepsPath${BITS}} + ENV DepsPath${BITS} + ${LIBCAFFEINE_DIR} + ENV LIBCAFFEINE_DIR + ${LIBCAFFEINE_DIR${BITS}} + ENV LIBCAFFEINE_DIR${BITS} + PATHS + /usr/include + /usr/local/include + /opt/local/include + /opt/local + /sw/include + ~/Library/Frameworks + /Library/Frameworks + PATH_SUFFIXES + lib + lib${BITS} + lib/cmake + lib${BITS}/cmake + lib/cmake${BITS} + lib${BITS}/cmake${BITS} + cmake + cmake${BITS} + ) + + if(EXISTS "${LIBCAFFEINE_CMAKE_FILE}") + set(LIBCAFFEINE_FOUND TRUE PARENT_SCOPE) + + return() + endif() +endfunction() +include(FindPackageHandleStandardArgs) + +# Find by CPack +find_libcaffeine_cpack() +find_package_handle_standard_args( + LIBCAFFEINE + FOUND_VAR LIBCAFFEINE_FOUND + REQUIRED_VARS + LIBCAFFEINE_CMAKE_FILE +) + +include("${LIBCAFFEINE_CMAKE_FILE}") From 882f15184a3f6bd877209b4eaae2e261c2a5bfaa Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Wed, 26 Feb 2020 13:02:32 +0100 Subject: [PATCH 06/29] caffeine: Add Caffeine output plugin --- plugins/CMakeLists.txt | 4 + plugins/caffeine/CMakeLists.txt | 41 ++ plugins/caffeine/README.md | 9 + .../caffeine/caffeine-foreground-process.c | 43 ++ .../caffeine/caffeine-foreground-process.h | 3 + plugins/caffeine/caffeine-module.c | 45 ++ plugins/caffeine/caffeine-output.c | 451 ++++++++++++++++++ plugins/caffeine/caffeine-settings.h | 4 + plugins/caffeine/data/locale/en-US.ini | 9 + 9 files changed, 609 insertions(+) create mode 100644 plugins/caffeine/CMakeLists.txt create mode 100644 plugins/caffeine/README.md create mode 100644 plugins/caffeine/caffeine-foreground-process.c create mode 100644 plugins/caffeine/caffeine-foreground-process.h create mode 100644 plugins/caffeine/caffeine-module.c create mode 100644 plugins/caffeine/caffeine-output.c create mode 100644 plugins/caffeine/caffeine-settings.h create mode 100644 plugins/caffeine/data/locale/en-US.ini diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 900bd8f31f9e04..0de5efca85c835 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -76,6 +76,10 @@ else() message(STATUS "obs-vst submodule not found! Please fetch/update submodules. obs-vst plugin disabled.") endif() +if(CAFFEINE_ENABLED) + add_subdirectory(caffeine) +endif() + add_subdirectory(image-source) add_subdirectory(obs-x264) add_subdirectory(obs-libfdk) diff --git a/plugins/caffeine/CMakeLists.txt b/plugins/caffeine/CMakeLists.txt new file mode 100644 index 00000000000000..8710d989af3d32 --- /dev/null +++ b/plugins/caffeine/CMakeLists.txt @@ -0,0 +1,41 @@ +project(caffeine) + +find_package(libcaffeine REQUIRED) +find_package(Threads REQUIRED) + +if(MSVC) + set(caffeine_PLATFORM_DEPS + shlwapi.lib + w32-pthreads) +endif() + +set(caffeine_HEADERS + caffeine-foreground-process.h + caffeine-settings.h) + +set(caffeine_SOURCES + caffeine-foreground-process.c + caffeine-module.c + caffeine-output.c) + +add_library(caffeine MODULE + ${caffeine_HEADERS} + ${caffeine_SOURCES}) + +target_link_libraries(caffeine + ${caffeine_PLATFORM_DEPS} + ${THREADS_LIBRARIES} + libobs + libcaffeine) + +install_obs_plugin_with_data(caffeine data) + +# TODO: There may be a better way to do this, but generator expression didn't +# work with the install_obs_bin helper +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(copy_bin ${LIBCAFFEINE_BINARY_DEBUG}) +else() + set(copy_bin ${LIBCAFFEINE_BINARY_RELWITHDEBINFO}) +endif() + +install_obs_bin(caffeine "" ${copy_bin}) diff --git a/plugins/caffeine/README.md b/plugins/caffeine/README.md new file mode 100644 index 00000000000000..2f15affab5e059 --- /dev/null +++ b/plugins/caffeine/README.md @@ -0,0 +1,9 @@ +Caffeine Plugin +=============== + +Building +-------- + +1. Download a [pre-built](https://github.com/caffeinetv/libcaffeine/releases) version of libcaffeine or check out [libcaffeine](https://github.com/caffeinetv/libcaffeine) and build it. +2. Set the `LIBCAFFEINE_DIR` environment variable to the directory where you extracted the zip or cloned the repository +3. Run CMake for the OBS project. The caffeine plugin and UI components should be enabled diff --git a/plugins/caffeine/caffeine-foreground-process.c b/plugins/caffeine/caffeine-foreground-process.c new file mode 100644 index 00000000000000..372ae4b3b3f973 --- /dev/null +++ b/plugins/caffeine/caffeine-foreground-process.c @@ -0,0 +1,43 @@ +#include "caffeine-foreground-process.h" + +#include + +#ifndef _WIN32 +char *get_foreground_process_name() +{ + return NULL; +} +#else + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +char *get_foreground_process_name() +{ + HWND window = GetForegroundWindow(); + if (!window) + return NULL; + DWORD pid; + if (!GetWindowThreadProcessId(window, &pid)) + return NULL; + HANDLE process = + OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (!process) + return NULL; + + DWORD const buffer_size = 4096; + char *filename = NULL; + char *full_path = bzalloc(buffer_size); + if (GetProcessImageFileNameA(process, full_path, buffer_size)) { + PathRemoveExtensionA(full_path); + filename = bstrdup(PathFindFileNameA(full_path)); + } + + bfree(full_path); + CloseHandle(process); + return filename; +} + +#endif // _WIN32 diff --git a/plugins/caffeine/caffeine-foreground-process.h b/plugins/caffeine/caffeine-foreground-process.h new file mode 100644 index 00000000000000..c9dcbabfefcb0e --- /dev/null +++ b/plugins/caffeine/caffeine-foreground-process.h @@ -0,0 +1,3 @@ +#pragma once + +char *get_foreground_process_name(); diff --git a/plugins/caffeine/caffeine-module.c b/plugins/caffeine/caffeine-module.c new file mode 100644 index 00000000000000..8575d0602f3048 --- /dev/null +++ b/plugins/caffeine/caffeine-module.c @@ -0,0 +1,45 @@ +#include +#include "caffeine.h" + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("caffeine", "en-US") + +MODULE_EXPORT char const *obs_module_description(void) +{ + return obs_module_text("CaffeineModule"); +} + +extern struct obs_output_info caffeine_output_info; + +/* Converts libcaffeine log levels to OBS levels */ +static int caffeine_to_obs_log_level(caff_LogLevel level) +{ + switch (level) { + case caff_LogLevelAll: + case caff_LogLevelDebug: + return LOG_DEBUG; + case caff_LogLevelWarning: + return LOG_WARNING; + case caff_LogLevelError: + return LOG_ERROR; + case caff_LogLevelNone: + default: + /* Do not log */ + return 0; + } +} + +static void caffeine_log(caff_LogLevel level, char const *message) +{ + blog(caffeine_to_obs_log_level(level), "[libcaffeine] %s", message); +} + +bool obs_module_load(void) +{ + obs_register_output(&caffeine_output_info); + caff_Result result = caff_initialize("obs", OBS_VERSION, + caff_LogLevelDebug, caffeine_log); + return result == caff_ResultSuccess; +} + +void obs_module_unload(void) {} diff --git a/plugins/caffeine/caffeine-output.c b/plugins/caffeine/caffeine-output.c new file mode 100644 index 00000000000000..a14575ebe514db --- /dev/null +++ b/plugins/caffeine/caffeine-output.c @@ -0,0 +1,451 @@ + +#include + +#include +#include +#include +#include + +#include + +#include "caffeine-foreground-process.h" +#include "caffeine-settings.h" + +/* Uncomment this to log each call to raw_audio/video +#define TRACE_FRAMES +/**/ + +#define do_log(level, format, ...) \ + blog(level, "[caffeine output] " format, ##__VA_ARGS__) + +#define log_error(format, ...) do_log(LOG_ERROR, format, ##__VA_ARGS__) +#define log_warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define log_info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) +#define log_debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) + +#define trace() log_debug("%s", __func__) + +#define set_error(output, fmt, ...) \ + do { \ + struct dstr message; \ + dstr_init(&message); \ + dstr_printf(&message, (fmt), ##__VA_ARGS__); \ + log_error("%s", message.array); \ + obs_output_set_last_error((output), message.array); \ + dstr_free(&message); \ + } while (false) + +struct caffeine_output { + obs_output_t *output; + caff_InstanceHandle instance; + struct obs_video_info video_info; + uint64_t start_timestamp; + size_t audio_planes; + size_t audio_size; + + volatile bool is_online; + pthread_t monitor_thread; + char *foreground_process; + char *game_id; +}; + +static const char *caffeine_get_name(void *data) +{ + UNUSED_PARAMETER(data); + + return obs_module_text("CaffeineOutput"); +} + +static int caffeine_to_obs_error(caff_Result error) +{ + switch (error) { + case caff_ResultSuccess: + return OBS_OUTPUT_SUCCESS; + case caff_ResultOutOfCapacity: + case caff_ResultFailure: + case caff_ResultBroadcastFailed: + return OBS_OUTPUT_CONNECT_FAILED; + case caff_ResultDisconnected: + return OBS_OUTPUT_DISCONNECTED; + case caff_ResultTakeover: + default: + return OBS_OUTPUT_ERROR; + } +} + +caff_VideoFormat obs_to_caffeine_format(enum video_format format) +{ + switch (format) { + case VIDEO_FORMAT_I420: + return caff_VideoFormatI420; + case VIDEO_FORMAT_NV12: + return caff_VideoFormatNv12; + case VIDEO_FORMAT_YUY2: + return caff_VideoFormatYuy2; + case VIDEO_FORMAT_UYVY: + return caff_VideoFormatUyvy; + case VIDEO_FORMAT_BGRA: + return caff_VideoFormatBgra; + + case VIDEO_FORMAT_RGBA: + case VIDEO_FORMAT_I444: + case VIDEO_FORMAT_Y800: + case VIDEO_FORMAT_BGRX: + case VIDEO_FORMAT_YVYU: + default: + return caff_VideoFormatUnknown; + } +} + +static void *caffeine_create(obs_data_t *settings, obs_output_t *output) +{ + trace(); + UNUSED_PARAMETER(settings); + + struct caffeine_output *context = + bzalloc(sizeof(struct caffeine_output)); + context->output = output; + + /* TODO: can we get this from the CaffeineAuth object somehow? */ + context->instance = caff_createInstance(); + + return context; +} + +static void caffeine_stream_started(void *data); +static void caffeine_stream_failed(void *data, caff_Result error); + +static int const enforced_height = 720; + +static bool caffeine_authenticate(struct caffeine_output *context) +{ + trace(); + + obs_output_t *output = context->output; + + obs_service_t *service = obs_output_get_service(output); + char const *refresh_token = obs_service_get_key(service); + + switch (caff_refreshAuth(context->instance, refresh_token)) { + case caff_ResultSuccess: + return true; + case caff_ResultInfoIncorrect: + set_error(output, "%s", obs_module_text("SigninFailed")); + return false; + case caff_ResultRefreshTokenRequired: + set_error(output, "%s", obs_module_text("ErrorMustSignIn")); + return false; + case caff_ResultFailure: + case caff_ResultAlreadyBroadcasting: + log_warn("%s", obs_module_text("WarningAlreadyStreaming")); + return false; + default: + set_error(output, "%s", obs_module_text("SigninFailed")); + return false; + } +} + +static bool caffeine_start(void *data) +{ + trace(); + struct caffeine_output *context = data; + obs_output_t *output = context->output; + + switch (caff_checkVersion()) { + case caff_ResultSuccess: + break; + case caff_ResultOldVersion: + set_error(output, "%s", obs_module_text("ErrorOldVersion")); + return false; + default: + log_warn("Failed to complete Caffeine version check"); + } + + if (!caffeine_authenticate(context)) + return false; + + if (!obs_get_video_info(&context->video_info)) { + set_error(output, "Failed to get video info"); + return false; + } + + if (context->video_info.output_height > enforced_height) + log_warn("For best video quality and reduced CPU usage," + " set output resolution to 720p or below"); + + caff_VideoFormat format = + obs_to_caffeine_format(context->video_info.output_format); + + if (format == caff_VideoFormatUnknown) { + set_error(output, "%s %s", obs_module_text("ErrorVideoFormat"), + get_video_format_name( + context->video_info.output_format)); + return false; + } + + struct audio_convert_info conversion = {.format = AUDIO_FORMAT_16BIT, + .speakers = SPEAKERS_STEREO, + .samples_per_sec = 48000}; + obs_output_set_audio_conversion(output, &conversion); + + context->audio_planes = + get_audio_planes(conversion.format, conversion.speakers); + context->audio_size = + get_audio_size(conversion.format, conversion.speakers, 1); + + if (!obs_output_can_begin_data_capture(output, 0)) + return false; + + obs_service_t *service = obs_output_get_service(output); + obs_data_t *settings = obs_service_get_settings(service); + char const *title = obs_data_get_string(settings, BROADCAST_TITLE_KEY); + + caff_Rating rating = + (caff_Rating)obs_data_get_int(settings, BROADCAST_RATING_KEY); + obs_data_release(settings); + + caff_Result result = caff_startBroadcast(context->instance, context, + title, rating, NULL, + caffeine_stream_started, + caffeine_stream_failed); + + if (result) { + set_error(output, "%s", caff_resultString(result)); + return false; + } + + return true; +} + +static void enumerate_games(void *data, char const *process_name, + char const *game_id, char const *game_name) +{ + struct caffeine_output *context = data; + if (strcmp(process_name, context->foreground_process) == 0) { + log_debug("Detected game [%s]: %s", game_id, game_name); + bfree(context->game_id); + context->game_id = bstrdup(game_id); + } +} + +static void *monitor_thread(void *data) +{ + trace(); + struct caffeine_output *context = data; + const uint64_t millisPerNano = 1000000; + const uint64_t stop_interval = 100 /*ms*/ * millisPerNano; + const uint64_t game_interval = 5000 /*ms*/ * millisPerNano; + const uint64_t service_interval = 1000 /*ms*/ * millisPerNano; + + uint64_t last_ts = os_gettime_ns(); + uint64_t cur_game_interval = 0; + uint64_t cur_service_interval = 0; + while (context->is_online) { + os_sleepto_ns(last_ts + stop_interval); + + // Update Timers + uint64_t now_ts = os_gettime_ns(); + int64_t delta_ts = now_ts - last_ts; + last_ts = now_ts; + cur_game_interval += delta_ts; + cur_service_interval += delta_ts; + + // Game Update + if (cur_game_interval > game_interval) { + cur_game_interval = 0; + context->foreground_process = + get_foreground_process_name(); + if (context->foreground_process) { + caff_enumerateGames(context->instance, context, + enumerate_games); + bfree(context->foreground_process); + context->foreground_process = NULL; + } + caff_setGameId(context->instance, context->game_id); + bfree(context->game_id); + context->game_id = NULL; + } + + // Service Update + if (cur_service_interval > service_interval) { + cur_service_interval = 0; + obs_service_t *service = + obs_output_get_service(context->output); + if (service) { + obs_data_t *data = + obs_service_get_settings(service); + caff_setTitle(context->instance, + obs_data_get_string( + data, + BROADCAST_TITLE_KEY)); + caff_setRating( + context->instance, + obs_data_get_int(data, + BROADCAST_RATING_KEY)); + obs_data_release(data); + } + } + } + + return NULL; +} + +static void caffeine_stream_started(void *data) +{ + trace(); + struct caffeine_output *context = data; + context->is_online = true; + pthread_create(&context->monitor_thread, NULL, monitor_thread, context); + obs_output_begin_data_capture(context->output, 0); +} + +static void caffeine_stream_failed(void *data, caff_Result error) +{ + struct caffeine_output *context = data; + + if (!obs_output_get_last_error(context->output)) { + set_error(context->output, "%s: [%d] %s", + obs_module_text("ErrorStartStream"), error, + caff_resultString(error)); + } + + if (context->is_online) { + context->is_online = false; + pthread_join(context->monitor_thread, NULL); + } + + obs_output_signal_stop(context->output, caffeine_to_obs_error(error)); +} + +static void caffeine_raw_video(void *data, struct video_data *frame) +{ +#ifdef TRACE_FRAMES + trace(); +#endif + struct caffeine_output *context = data; + + uint32_t width = context->video_info.output_width; + uint32_t height = context->video_info.output_height; + size_t total_bytes = frame->linesize[0] * height; + caff_VideoFormat format = + obs_to_caffeine_format(context->video_info.output_format); + int64_t timestampMicros = frame->timestamp / 1000; + + if (!context->start_timestamp) + context->start_timestamp = frame->timestamp; + + caff_sendVideo(context->instance, format, frame->data[0], total_bytes, + width, height, timestampMicros); +} + +/* This fixes an issue where unencoded outputs have video & audio out of sync + * + * Copied/adapted from obs-outputs/flv-output + */ +static bool prepare_audio(struct caffeine_output *context, + const struct audio_data *frame, + struct audio_data *output) +{ + *output = *frame; + + const uint64_t NANOSECONDS = 1000000000; + const uint64_t SAMPLES = 48000; + + if (frame->timestamp < context->start_timestamp) { + uint64_t duration = + (uint64_t)frame->frames * NANOSECONDS / SAMPLES; + uint64_t end_ts = (frame->timestamp + duration); + uint64_t cutoff; + + if (end_ts <= context->start_timestamp) + return false; + + cutoff = context->start_timestamp - frame->timestamp; + output->timestamp += cutoff; + + cutoff = cutoff * SAMPLES / NANOSECONDS; + + for (size_t i = 0; i < context->audio_planes; i++) + output->data[i] += + context->audio_size * (uint32_t)cutoff; + output->frames -= (uint32_t)cutoff; + } + + return true; +} + +static void caffeine_raw_audio(void *data, struct audio_data *frames) +{ +#ifdef TRACE_FRAMES + trace(); +#endif + struct caffeine_output *context = data; + struct audio_data in; + + if (!context->start_timestamp) + return; + if (!prepare_audio(context, frames, &in)) + return; + + caff_sendAudio(context->instance, in.data[0], in.frames); +} + +static void caffeine_stop(void *data, uint64_t ts) +{ + trace(); + /* TODO: do something with this? */ + UNUSED_PARAMETER(ts); + + struct caffeine_output *context = data; + obs_output_t *output = context->output; + + if (context->is_online) { + context->is_online = false; + pthread_join(context->monitor_thread, NULL); + } + + caff_endBroadcast(context->instance); + + obs_output_end_data_capture(output); +} + +static void caffeine_destroy(void *data) +{ + trace(); + struct caffeine_output *context = data; + caff_freeInstance(&context->instance); + + bfree(data); +} + +static float caffeine_get_congestion(void *data) +{ + struct caffeine_output *context = data; + + caff_ConnectionQuality quality = + caff_getConnectionQuality(context->instance); + + switch (quality) { + case caff_ConnectionQualityGood: + return 0.f; + case caff_ConnectionQualityPoor: + return 1.f; + default: + return 0.5f; + } +} + +struct obs_output_info caffeine_output_info = { + .id = "caffeine_output", + .flags = OBS_OUTPUT_AV | OBS_OUTPUT_SERVICE | + OBS_OUTPUT_BANDWIDTH_TEST_DISABLED | + OBS_OUTPUT_HARDWARE_ENCODING_DISABLED, + .get_name = caffeine_get_name, + .create = caffeine_create, + .start = caffeine_start, + .raw_video = caffeine_raw_video, + .raw_audio = caffeine_raw_audio, + .stop = caffeine_stop, + .destroy = caffeine_destroy, + .get_congestion = caffeine_get_congestion, +}; diff --git a/plugins/caffeine/caffeine-settings.h b/plugins/caffeine/caffeine-settings.h new file mode 100644 index 00000000000000..edc32c8071f460 --- /dev/null +++ b/plugins/caffeine/caffeine-settings.h @@ -0,0 +1,4 @@ +#pragma once + +#define BROADCAST_RATING_KEY "rating" +#define BROADCAST_TITLE_KEY "broadcast_title" diff --git a/plugins/caffeine/data/locale/en-US.ini b/plugins/caffeine/data/locale/en-US.ini new file mode 100644 index 00000000000000..18c7ad53e897c9 --- /dev/null +++ b/plugins/caffeine/data/locale/en-US.ini @@ -0,0 +1,9 @@ +CaffeineOutput="Caffeine.tv" +CaffeineModule="Caffeine.tv output" +SigninFailed="There was an error signing in." +ErrorMustSignIn="Go to Settings > Stream to sign into Caffeine." +ErrorOldVersion="Update to the latest version of OBS with Caffeine to broadcast." +ErrorAspectRatio="Caffeine requires using an output resolution of 1280x720. Go to Settings > Video to change the resolution." +ErrorVideoFormat="Caffeine requires using NV12 or I420 formats. Go to Settings > Advanced to change the Color Format." +ErrorStartStream="Failed to start stream. Try again." +WarningAlreadyStreaming="Caffeine is already broadcasting" From e074f3d54c16c727b2aea5f6d7feae9ac9a07d55 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Wed, 26 Feb 2020 13:05:59 +0100 Subject: [PATCH 07/29] UI: Add support for Caffeine This addition requires the Caffeine output plugin, as well as the CEF browser integration and will be disabled without it. * COBS-83: Update Signin , ViewOn web links to check if environment variable CAFFEINE_DOMAIN is set, Updated env var to be LIBCAFFEINE_DOMAIN(used in libcaffeine) instead of CAFFEINE_DOMAIN * COBS-51 Crash selecting invalid "Caffeine" entry in docks window, Fixed a Caffeine docks becoming inaccessible. * COBS-75 Incorrect error messages are displayed when clicking on continue after signing-in with a 2FA account and leaving the verification code box empty * COBS-75 Added disabled state button colors * Improved QPushButton focus rect. * Updated the color scheme for disabled button * UI: Fix compatibility for older Qt versions --- CMakeLists.txt | 12 + UI/CMakeLists.txt | 54 ++- UI/auth-base.cpp | 27 ++ UI/auth-base.hpp | 8 +- UI/auth-caffeine.cpp | 318 +++++++++++++ UI/auth-caffeine.hpp | 46 ++ UI/auth-oauth.cpp | 8 + UI/auth-oauth.hpp | 4 + UI/data/locale/en-US.ini | 31 +- UI/forms/Caffeine.qrc | 9 + UI/forms/CaffeinePanel.ui | 577 +++++++++++++++++++++++ UI/forms/CaffeineSignIn.ui | 645 ++++++++++++++++++++++++++ UI/forms/OBSBasicSettings.ui | 27 ++ UI/forms/fonts/Poppins-Bold.ttf | Bin 0 -> 140012 bytes UI/forms/fonts/Poppins-ExtraLight.ttf | Bin 0 -> 146648 bytes UI/forms/fonts/Poppins-License.txt | 43 ++ UI/forms/fonts/Poppins-Light.ttf | Bin 0 -> 145180 bytes UI/forms/fonts/Poppins-Regular.ttf | Bin 0 -> 144064 bytes UI/forms/images/CaffeineLogo.svg | 19 + UI/obs-app.cpp | 4 +- UI/ui-config.h.in | 2 + UI/window-basic-auto-config-test.cpp | 9 + UI/window-basic-auto-config.cpp | 151 +++--- UI/window-basic-main.cpp | 17 + UI/window-basic-main.hpp | 1 + UI/window-basic-settings-stream.cpp | 151 ++++-- UI/window-basic-settings.cpp | 7 + UI/window-basic-settings.hpp | 4 + UI/window-caffeine.cpp | 120 +++++ UI/window-caffeine.hpp | 40 ++ cmake/Modules/CopyMSVCBins.cmake | 14 +- 31 files changed, 2237 insertions(+), 111 deletions(-) create mode 100644 UI/auth-caffeine.cpp create mode 100644 UI/auth-caffeine.hpp create mode 100644 UI/forms/Caffeine.qrc create mode 100644 UI/forms/CaffeinePanel.ui create mode 100644 UI/forms/CaffeineSignIn.ui create mode 100644 UI/forms/fonts/Poppins-Bold.ttf create mode 100644 UI/forms/fonts/Poppins-ExtraLight.ttf create mode 100644 UI/forms/fonts/Poppins-License.txt create mode 100644 UI/forms/fonts/Poppins-Light.ttf create mode 100644 UI/forms/fonts/Poppins-Regular.ttf create mode 100644 UI/forms/images/CaffeineLogo.svg create mode 100644 UI/window-caffeine.cpp create mode 100644 UI/window-caffeine.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 426d0628b9effd..05c9fd5372d21b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,6 +253,18 @@ if(NOT INSTALLER_RUN) endif() find_package(Qt5Widgets ${FIND_MODE}) + + option(BUILD_CAFFEINE "Enables streaming to Caffeine.tv" ON) + if (BUILD_CAFFEINE) + find_package(libcaffeine) + if (LIBCAFFEINE_FOUND) + set(CAFFEINE_ENABLED TRUE) + else() + set(CAFFEINE_ENABLED FALSE) + endif() + else() + set(CAFFEINE_ENABLED FALSE) + endif() endif() add_subdirectory(deps) diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index ae881a3f657bf1..31bd03f7fb2c6a 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -137,7 +137,41 @@ elseif(UNIX) endif() endif() -if(BROWSER_AVAILABLE_INTERNAL) +if(CAFFEINE_ENABLED) + find_package(libcaffeine REQUIRED) + find_package(Threads REQUIRED) + add_definitions(${THREADS_DEFINITIONS}) + + if(WIN32) + set(caffeine_PLATFORM_DEPS + shlwapi.lib + w32-pthreads) + endif() + + include_directories( + ${LIBCAFFEINE_INCLUDE_DIR} + ) + set(caffeine_HEADERS + auth-caffeine.hpp + ) + set(caffeine_SOURCES + auth-caffeine.cpp + ) + list(APPEND obs_PLATFORM_SOURCES + ${caffeine_SOURCES} + ) + list(APPEND obs_PLATFORM_HEADERS + ${caffeine_HEADERS} + ) + set(obs_PLATFORM_LIBRARIES + ${obs_PLATFORM_LIBRARIES} + ${caffeine_PLATFORM_DEPS} + libcaffeine + ${THREADS_LIBRARIES}) +endif() + +if(CAFFEINE_ENABLED OR BROWSER_AVAILABLE_INTERNAL) + add_definitions(-DAUTH_ENABLED) list(APPEND obs_PLATFORM_SOURCES obf.c auth-oauth.cpp @@ -150,7 +184,9 @@ if(BROWSER_AVAILABLE_INTERNAL) window-dock-browser.hpp window-extra-browsers.hpp ) +endif() +if(BROWSER_AVAILABLE_INTERNAL) if(TWITCH_ENABLED) list(APPEND obs_PLATFORM_SOURCES auth-twitch.cpp @@ -369,6 +405,22 @@ set(obs_UI set(obs_QRC forms/obs.qrc) +if(CAFFEINE_ENABLED) + list(APPEND obs_UI + forms/CaffeineSignIn.ui + forms/CaffeinePanel.ui + ) + list(APPEND obs_HEADERS + window-caffeine.hpp + ) + list(APPEND obs_SOURCES + window-caffeine.cpp + ) + list(APPEND obs_QRC + forms/Caffeine.qrc + ) +endif() + qt5_wrap_ui(obs_UI_HEADERS ${obs_UI}) qt5_add_resources(obs_QRC_SOURCES ${obs_QRC}) diff --git a/UI/auth-base.cpp b/UI/auth-base.cpp index 89c30e2c057079..08b1a42463c9cd 100644 --- a/UI/auth-base.cpp +++ b/UI/auth-base.cpp @@ -4,6 +4,9 @@ #include #include +struct QCef; +extern QCef *cef; + struct AuthInfo { Auth::Def def; Auth::create_cb create; @@ -39,6 +42,30 @@ Auth::Type Auth::AuthType(const std::string &service) return Type::None; } +bool Auth::IsKeyHidden(const std::string &service) +{ + for (auto &a : authDefs) { + if (service.find(a.def.service) != std::string::npos) { + return a.def.key_hidden; + } + } + + return false; +} + +bool Auth::CanAuthService(const std::string &service) +{ + switch (Auth::AuthType(service)) { + case Auth::Type::Custom: + return true; + case Auth::Type::OAuth_StreamKey: + return !!cef; + case Auth::Type::None: + default: + return false; + } +} + void Auth::Load() { OBSBasic *main = OBSBasic::Get(); diff --git a/UI/auth-base.hpp b/UI/auth-base.hpp index f683ce1f18f629..7b2c2f88fe59eb 100644 --- a/UI/auth-base.hpp +++ b/UI/auth-base.hpp @@ -24,14 +24,12 @@ class Auth : public QObject { }; public: - enum class Type { - None, - OAuth_StreamKey, - }; + enum class Type { None, OAuth_StreamKey, Custom }; struct Def { std::string service; Type type; + bool key_hidden; }; typedef std::function()> create_cb; @@ -48,6 +46,8 @@ class Auth : public QObject { static std::shared_ptr Create(const std::string &service); static Type AuthType(const std::string &service); + static bool IsKeyHidden(const std::string &service); + static bool CanAuthService(const std::string &service); static void Load(); static void Save(); diff --git a/UI/auth-caffeine.cpp b/UI/auth-caffeine.cpp new file mode 100644 index 00000000000000..1abd326b7ad6cd --- /dev/null +++ b/UI/auth-caffeine.cpp @@ -0,0 +1,318 @@ +#include "auth-caffeine.hpp" + +#include +#include + +#include + +#include "window-basic-main.hpp" +#include "window-caffeine.hpp" + +#include "ui-config.h" + +#include "ui_CaffeineSignIn.h" + +static Auth::Def caffeineDef = {"Caffeine", Auth::Type::Custom, true}; +const int otpLength = 6; +// Get the Caffeine URL default or staging +static const char *getCaffeineURL = getenv("LIBCAFFEINE_DOMAIN") == NULL + ? "caffeine.tv" + : getenv("LIBCAFFEINE_DOMAIN"); + +/* ------------------------------------------------------------------------- */ + +static int addFonts() +{ + QFontDatabase::addApplicationFont( + ":/caffeine/fonts/Poppins-Regular.ttf"); + QFontDatabase::addApplicationFont(":/caffeine/fonts/Poppins-Bold.ttf"); + QFontDatabase::addApplicationFont(":/caffeine/fonts/Poppins-Light.ttf"); + QFontDatabase::addApplicationFont( + ":/caffeine/fonts/Poppins-ExtraLight.ttf"); + return 0; +} + +CaffeineAuth::CaffeineAuth(const Def &d) : OAuthStreamKey(d) +{ + UNUSED_PARAMETER(d); + instance = caff_createInstance(); +} + +CaffeineAuth::~CaffeineAuth() +{ + caff_freeInstance(&instance); +} + +bool CaffeineAuth::GetChannelInfo() +try { + key_ = refresh_token; + + if (caff_isSignedIn(instance)) { + username = caff_getUsername(instance); + } else { + switch (caff_refreshAuth(instance, refresh_token.c_str())) { + case caff_ResultSuccess: + username = caff_getUsername(instance); + break; + case caff_ResultRefreshTokenRequired: + case caff_ResultInfoIncorrect: + throw ErrorInfo(Str("Caffeine.Auth.Unauthorized"), + Str("Caffeine.Auth.IncorrectRefresh")); + default: + throw ErrorInfo(Str("Caffeine.Auth.Failed"), + Str("Caffeine.Auth.SigninFailed")); + } + } + + OBSBasic *main = OBSBasic::Get(); + obs_service_t *service = main->GetService(); + obs_data_t *settings = obs_service_get_settings(service); + obs_data_set_string(settings, "username", username.c_str()); + obs_data_release(settings); + + return true; +} catch (ErrorInfo info) { + QString title = QTStr("Auth.ChannelFailure.Title"); + QString text = QTStr("Auth.ChannelFailure.Text") + .arg(service(), info.message.c_str(), + info.error.c_str()); + + QMessageBox::warning(OBSBasic::Get(), title, text); + + blog(LOG_WARNING, "%s: %s: %s", __FUNCTION__, info.message.c_str(), + info.error.c_str()); + return false; +} + +void CaffeineAuth::SaveInternal() +{ + OBSBasic *main = OBSBasic::Get(); + config_set_string(main->Config(), service(), "Username", + username.c_str()); + if (uiLoaded) { + config_set_string(main->Config(), service(), "DockState", + main->saveState().toBase64().constData()); + } + OAuthStreamKey::SaveInternal(); +} + +static inline std::string get_config_str(OBSBasic *main, const char *section, + const char *name) +{ + const char *val = config_get_string(main->Config(), section, name); + return val ? val : ""; +} + +bool CaffeineAuth::LoadInternal() +{ + OBSBasic *main = OBSBasic::Get(); + username = get_config_str(main, service(), "Username"); + firstLoad = false; + return OAuthStreamKey::LoadInternal(); +} + +class CaffeineChat : public OBSDock { +public: + inline CaffeineChat() : OBSDock() {} +}; + +void CaffeineAuth::LoadUI() +{ + if (uiLoaded) + return; + if (!GetChannelInfo()) + return; + /* TODO: Chat */ + + // Panel + panelDock = QSharedPointer( + new CaffeineInfoPanel(this, instance)) + .dynamicCast(); + + uiLoaded = true; + return; +} + +void CaffeineAuth::OnStreamConfig() +{ + OBSBasic *main = OBSBasic::Get(); + obs_service_t *service = main->GetService(); + obs_data_t *data = obs_service_get_settings(service); + QSharedPointer panel2 = + panelDock.dynamicCast(); + obs_data_set_string(data, "broadcast_title", + panel2->getTitle().c_str()); + obs_data_set_int(data, "rating", + static_cast(panel2->getRating())); + obs_service_update(service, data); + obs_data_release(data); +} + +bool CaffeineAuth::RetryLogin() +{ + std::shared_ptr ptr = Login(OBSBasic::Get()); + return ptr != nullptr; +} + +void CaffeineAuth::TryAuth(Ui::CaffeineSignInDialog *ui, QDialog *dialog, + std::string &passwordForOtp) +{ + std::string username = ui->usernameEdit->text().toStdString(); + std::string otp; + std::string password; + if (passwordForOtp.empty()) { + password = ui->passwordEdit->text().toStdString(); + } else { + otp = ui->passwordEdit->text().toStdString(); + password = passwordForOtp; + } + + auto result = caff_signIn(instance, username.c_str(), password.c_str(), + otp.c_str()); + switch (result) { + case caff_ResultSuccess: + refresh_token = caff_getRefreshToken(instance); + dialog->accept(); + return; + case caff_ResultMfaOtpRequired: + ui->messageLabel->setText( + QString(R"(%1 )") + .arg(QTStr("Caffeine.Auth.EnterCode"), + getCaffeineURL)); + ui->usernameEdit->hide(); + passwordForOtp = ui->passwordEdit->text().toStdString(); + ui->passwordEdit->clear(); + ui->passwordEdit->setPlaceholderText( + Str("Caffeine.Auth.VerificationPlaceholder")); + ui->signInButton->setText(Str("Caffeine.Auth.Continue")); + ui->newUserFooter->hide(); + ui->forgotPasswordLabel->setOpenExternalLinks(false); + ui->forgotPasswordLabel->setText( + QString(R"(

+ %1
%3

)") + .arg(QTStr("Caffeine.Auth.HavingProblems"), + getCaffeineURL, + QTStr("Caffeine.Auth.SendNewCode"))); + + // Disable the signInButton upfront + ui->signInButton->setEnabled(false); + ui->passwordEdit->setMaxLength(otpLength); + // Enable the signInButton when user has entered 6 digit opt + connect(ui->passwordEdit, &QLineEdit::textChanged, + [=](const QString &enteredOtp) { + if (enteredOtp.length() == otpLength) { + ui->signInButton->setEnabled(true); + } else { + ui->signInButton->setEnabled(false); + } + }); + return; + case caff_ResultMfaOtpIncorrect: + ui->passwordEdit->clear(); + ui->messageLabel->setText( + Str("Caffeine.Auth.IncorrectVerification")); + return; + case caff_ResultUsernameRequired: + ui->messageLabel->setText( + Str("Caffeine.Auth.UsernameRequired")); + return; + case caff_ResultPasswordRequired: + ui->messageLabel->setText( + Str("Caffeine.Auth.PasswordRequired")); + return; + case caff_ResultInfoIncorrect: + ui->messageLabel->setText(Str("Caffeine.Auth.IncorrectInfo")); + return; + case caff_ResultLegalAcceptanceRequired: + ui->messageLabel->setText( + Str("Caffeine.Auth.TosAcceptanceRequired")); + return; + case caff_ResultEmailVerificationRequired: + ui->messageLabel->setText( + Str("Caffeine.Auth.EmailVerificationRequired")); + return; + case caff_ResultFailure: + default: + ui->messageLabel->setText(Str("Caffeine.Auth.SigninFailed")); + return; + } +} + +std::shared_ptr CaffeineAuth::Login(QWidget *parent) +{ + static int once = addFonts(); + UNUSED_PARAMETER(once); + const auto flags = Qt::WindowTitleHint | Qt::WindowSystemMenuHint | + Qt::WindowCloseButtonHint; + + QDialog dialog(parent, flags); + auto ui = new Ui::CaffeineSignInDialog; + ui->setupUi(&dialog); + QIcon icon(":/caffeine/images/CaffeineLogo.svg"); + ui->logo->setPixmap(icon.pixmap(76, 66)); + + // Set up text and point href depending on the env var + ui->forgotPasswordLabel->setText(QString(R"(

+ forgot something?
reset your password

)") + .arg(getCaffeineURL)); + ui->newUserFooter->setText( + QString(R"(New to Caffeine? sign up)") + .arg(getCaffeineURL)); + + // Don't highlight text boxes + for (auto edit : dialog.findChildren()) { + edit->setAttribute(Qt::WA_MacShowFocusRect, false); + } + + ui->messageLabel->clear(); + + std::shared_ptr auth = + std::make_shared(caffeineDef); + + std::string origPassword; + connect(ui->signInButton, &QPushButton::clicked, + [&](bool) { auth->TryAuth(ui, &dialog, origPassword); }); + + // Only used for One-time-password "resend email" link. resending the + // email is just attempting the sign-in without one-time password + // included + connect(ui->forgotPasswordLabel, &QLabel::linkActivated, + [&](const QString &) { + // Do this only for OTP + if (!ui->newUserFooter->isVisible()) { + auto username = + ui->usernameEdit->text().toStdString(); + caff_signIn(auth->instance, username.c_str(), + origPassword.c_str(), nullptr); + } + }); + + if (dialog.exec() == QDialog::Rejected) + return nullptr; + + if (auth->GetChannelInfo()) + return auth; + + return nullptr; +} + +std::string CaffeineAuth::GetUsername() +{ + return this->username; +} + +static std::shared_ptr CreateCaffeineAuth() +{ + return std::make_shared(caffeineDef); +} + +static void DeleteCookies() {} + +void RegisterCaffeineAuth() +{ + OAuth::RegisterOAuth(caffeineDef, CreateCaffeineAuth, + CaffeineAuth::Login, DeleteCookies); +} diff --git a/UI/auth-caffeine.hpp b/UI/auth-caffeine.hpp new file mode 100644 index 00000000000000..245535fd623777 --- /dev/null +++ b/UI/auth-caffeine.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "auth-oauth.hpp" +#include "caffeine.h" +#include "window-dock.hpp" + +class CaffeineChat; +class QWidget; +class QDialog; + +namespace Ui { +class CaffeineSignInDialog; +} + +class CaffeineAuth : public OAuthStreamKey { + Q_OBJECT + caff_InstanceHandle instance; + + bool uiLoaded = false; + QSharedPointer panelDock; + QSharedPointer chat; + QSharedPointer chatMenu; + + std::string username; + + void TryAuth(Ui::CaffeineSignInDialog *ui, QDialog *dialog, + std::string &origPassword); + virtual bool RetryLogin() override; + + virtual void SaveInternal() override; + virtual bool LoadInternal() override; + + bool GetChannelInfo(); + + virtual void LoadUI() override; + + virtual void OnStreamConfig() override; + +public: + CaffeineAuth(const Def &d); + virtual ~CaffeineAuth(); + + static std::shared_ptr Login(QWidget *parent); + + std::string GetUsername(); +}; diff --git a/UI/auth-oauth.cpp b/UI/auth-oauth.cpp index a27c49c1749327..47533abcdf3430 100644 --- a/UI/auth-oauth.cpp +++ b/UI/auth-oauth.cpp @@ -16,15 +16,18 @@ using namespace json11; +#ifdef BROWSER_AVAILABLE #include extern QCef *cef; extern QCefCookieManager *panel_cookies; +#endif /* ------------------------------------------------------------------------- */ OAuthLogin::OAuthLogin(QWidget *parent, const std::string &url, bool token) : QDialog(parent), get_token(token) { +#ifdef BROWSER_AVAILABLE if (!cef) { return; } @@ -61,18 +64,23 @@ OAuthLogin::OAuthLogin(QWidget *parent, const std::string &url, bool token) QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(cefWidget); topLayout->addLayout(bottomLayout); +#endif } OAuthLogin::~OAuthLogin() { +#ifdef BROWSER_AVAILABLE delete cefWidget; +#endif } int OAuthLogin::exec() { +#ifdef BROWSER_AVAILABLE if (cefWidget) { return QDialog::exec(); } +#endif return QDialog::Rejected; } diff --git a/UI/auth-oauth.hpp b/UI/auth-oauth.hpp index 7cf7dc403e37ac..b7f07cbe354df1 100644 --- a/UI/auth-oauth.hpp +++ b/UI/auth-oauth.hpp @@ -6,12 +6,16 @@ #include "auth-base.hpp" +#ifdef BROWSER_AVAILABLE class QCefWidget; +#endif class OAuthLogin : public QDialog { Q_OBJECT +#ifdef BROWSER_AVAILABLE QCefWidget *cefWidget = nullptr; +#endif QString code; bool get_token = false; bool fail = false; diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 09205f7b2f529a..80854dee2cd286 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -87,6 +87,8 @@ ShowInMultiview="Show in Multiview" VerticalLayout="Vertical Layout" Group="Group" DoNotShowAgain="Do not show again" +Required="Required" +Optional="Optional" Default="(Default)" Calculating="Calculating..." Fullscreen="Fullscreen" @@ -169,7 +171,7 @@ Basic.AutoConfig.VideoPage.FPS.PreferHighRes="Either 60 or 30, but prefer high r Basic.AutoConfig.VideoPage.CanvasExplanation="Note: The canvas (base) resolution is not necessarily the same as the resolution you will stream or record with. Your actual stream/recording resolution may be scaled down from the canvas resolution to reduce resource usage or bitrate requirements." Basic.AutoConfig.StreamPage="Stream Information" Basic.AutoConfig.StreamPage.SubTitle="Please enter your stream information" -Basic.AutoConfig.StreamPage.ConnectAccount="Connect Account (recommended)" +Basic.AutoConfig.StreamPage.ConnectAccount="Connect Account (%1)" Basic.AutoConfig.StreamPage.DisconnectAccount="Disconnect Account" Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Title="Disconnect Account?" Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Text="This change will apply immediately. Are you sure you want to disconnect your account?" @@ -700,6 +702,7 @@ Basic.Settings.General.MultiviewLayout.Horizontal.Extended.Top="Horizontal, Top Basic.Settings.Stream="Stream" Basic.Settings.Stream.StreamType="Stream Type" Basic.Settings.Stream.Custom.UseAuthentication="Use authentication" +Basic.Settings.Stream.Custom.SignedInAs="Signed in as" Basic.Settings.Stream.Custom.Username="Username" Basic.Settings.Stream.Custom.Password="Password" Basic.Settings.Stream.BandwidthTestMode="Enable Bandwidth Test Mode" @@ -1079,3 +1082,29 @@ ContextBar.MediaControls.PlaylistNext="Next in Playlist" ContextBar.MediaControls.PlaylistPrevious="Previous in Playlist" ContextBar.MediaControls.MediaProperties="Media Properties" ContextBar.MediaControls.BlindSeek="Media Seek Widget" + +# Caffeine +Caffeine.Auth.Unauthorized="Unauthorized" +Caffeine.Auth.Failed="Failed" +Caffeine.Auth.UsernameRequired="Username is required" +Caffeine.Auth.PasswordRequired="Password is required" +Caffeine.Auth.IncorrectInfo="The username or password is incorrect." +Caffeine.Auth.EnterCode="Enter the two-factor authentication code that was emailed to you." +Caffeine.Auth.SendNewCode="send a new code" +Caffeine.Auth.HavingProblems="having problems?" +Caffeine.Auth.ResendEmail="Resend email." +Caffeine.Auth.VerificationPlaceholder="Enter code" +Caffeine.Auth.Continue="Continue" +Caffeine.Auth.IncorrectVerification="An incorrect code was entered." +Caffeine.Auth.IncorrectRefresh="You have been signed out of Caffeine." +Caffeine.Auth.TosAcceptanceRequired="You must accept the Caffeine Terms of Use before broadcasting" +Caffeine.Auth.EmailVerificationRequired="Verify your email address before broadcasting" +Caffeine.Auth.SigninFailed="There was an error signing in. Try again." +Caffeine.Dock="Caffeine" +Caffeine.Dock.RatingAndTitle="Set your broadcast rating and title:" +Caffeine.Dock.UpdateRatingAndTitle="Update rating and title" +Caffeine.Dock.ViewOnWeb="View on Web" +Caffeine.Dock.ViewOnWebDescription="View on web to see credits, viewers,\nand chat with your audience." +Caffeine.Title="LIVE on Caffeine!" +Caffeine.Rating.None="0+" +Caffeine.Rating.SeventeenPlus="17+" diff --git a/UI/forms/Caffeine.qrc b/UI/forms/Caffeine.qrc new file mode 100644 index 00000000000000..8043fe283c700a --- /dev/null +++ b/UI/forms/Caffeine.qrc @@ -0,0 +1,9 @@ + + + images/CaffeineLogo.svg + fonts/Poppins-Bold.ttf + fonts/Poppins-Regular.ttf + fonts/Poppins-Light.ttf + fonts/Poppins-ExtraLight.ttf + + diff --git a/UI/forms/CaffeinePanel.ui b/UI/forms/CaffeinePanel.ui new file mode 100644 index 00000000000000..d01e457d4c7fa6 --- /dev/null +++ b/UI/forms/CaffeinePanel.ui @@ -0,0 +1,577 @@ + + + CaffeinePanel + + + + 0 + 0 + 388 + 251 + + + + font-family: "Segoe UI"; +font-size: 14px; + + + true + + + QDockWidget::AllDockWidgetFeatures + + + Caffeine + + + + + 0 + 0 + + + + background: rgb(34, 34, 34); + + + + 0 + + + 24 + + + 24 + + + 24 + + + 24 + + + + + true + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + Segoe UI + -1 + + + + font-family: "Segoe UI"; +font-size: 14px; +color: rgb(139, 139, 139); + + + Set your broadcasts rating and title: + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + + 0 + 0 + + + + + 12 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 1 + 0 + + + + + 76 + 32 + + + + + Segoe UI + -1 + true + + + + border: 1px solid rgb(72, 72, 72); +color: rgb(255, 255, 255); + + + + Test + + + + + + + + + 3 + 0 + + + + + 0 + 32 + + + + + Segoe UI + -1 + + + + border: 1px solid rgb(72, 72, 72); +color: rgb(255, 255, 255); + + + LIVE on Caffeine! + + + false + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + Segoe UI + -1 + + + + PointingHandCursor + + + QPushButton { + font-size: 12px; + color: rgb(0, 159, 224); +} + +QPushButton:hover { + color: rgb(255, 255, 255); +} + +QPushButton:pressed { + color: rgb(0, 159, 224); +} + + + Update rating and title + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 5 + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 16 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 0 + 0 + + + + + + + + true + + + + 0 + 0 + + + + + 120 + 32 + + + + + 16777215 + 32 + + + + + Segoe UI + -1 + + + + PointingHandCursor + + + border-radius: 5px; +border: 1px solid rgb(151, 151, 151); +color: rgb(255, 255, 255); + + + + View on web + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 0 + 0 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + + 0 + 0 + + + + + Segoe UI + -1 + + + + color: rgb(139, 139, 139); +line-height: 14px; +text-align: center; +font-size: 10px; +padding-left: 40px; +padding-right: 40px; + + + <html><body><p>View on web to see credits, viewers, and chat with your audience.</p></body></html> + + + Qt::AlignCenter + + + true + + + + + + + + + + + + + + + + + + diff --git a/UI/forms/CaffeineSignIn.ui b/UI/forms/CaffeineSignIn.ui new file mode 100644 index 00000000000000..b2ab4a552d2643 --- /dev/null +++ b/UI/forms/CaffeineSignIn.ui @@ -0,0 +1,645 @@ + + + CaffeineSignInDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 672 + 691 + + + + + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 159 + 224 + + + + + + + 0 + 159 + 224 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 159 + 224 + + + + + + + 0 + 159 + 224 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 159 + 224 + + + + + + + 0 + 159 + 224 + + + + + + + + Caffeine + + + * { + color: black; + font-size: 14px; + font-family: Poppins, "Segoe UI", Sans; + background-color: white; +} + +QLineEdit { + padding: 5px 20px; + font-family: "Poppins Light"; + border-radius: 0px; + border: 1px solid #f2f2f2; + border-top: 4px solid #f2f2f2; +} + +QPushButton { + font-size: 24px; + background-color: rgb(0, 159, 224); + color:white; + border-radius: 40px; + border: 0px solid black; +} + +QPushButton::disabled { + color: rgb(116, 116, 116); + background-color: rgb(231, 231, 239); +} + +QPushButton::focus { + border-radius: 40px; + border: 1px solid black; + outline: 0px solid black; +} + +QPushButton::hover { + color: white; + background-color: rgb(0, 0, 0); + border: 1px solid black; +} + +QLabel#forgotPasswordLabel { + line-height: 75%; + font-family: 'Poppins Light'; + font-size: 14px; +} + + + + true + + + + 0 + + + QLayout::SetFixedSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 195 + + + 195 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 32 + + + + + + + + + + + 76 + 66 + + + + + 76 + 66 + + + + + + + :/caffeine/images/CaffeineLogo.svg + + + true + + + Qt::AlignCenter + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 28 + + + + + + + + + + + Poppins + -1 + + + + <p style="line-height: 92%; font-size: 32px; text-align:center; font-family: 'Poppins ExtraLight'">Sign in to<br/>Caffeine</p> + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + 282 + 42 + + + + + 282 + 42 + + + + font-family: 'Poppins Light'; + font-size: 14px; + color: #8b8b8b; + + + This is where messages will appear. There can be up to 2 lines + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + 280 + 0 + + + + + + + username + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + QLineEdit::Password + + + password + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 64 + + + + + + + + + 280 + 80 + + + + + 280 + 80 + + + + PointingHandCursor + + + sign in + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 14 + + + + + + + + padding-bottom: 5px; + + + Qt::AlignCenter + + + true + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 69 + + + + + + + + + 0 + 62 + + + + + 16777215 + 62 + + + + background-color: rgb(0, 159, 224); color: white; padding: 18px 0px 16px 0px; + + + Qt::AlignCenter + + + true + + + + + + + + + + diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index fd53049d407f7f..47365422bc56d3 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -970,6 +970,33 @@ + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 170 + 19 + + + + + + + + + + TextLabel + + + + + diff --git a/UI/forms/fonts/Poppins-Bold.ttf b/UI/forms/fonts/Poppins-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1170ea9d81eaf0096a2df7d6abbc49b5540f322a GIT binary patch literal 140012 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�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 literal 0 HcmV?d00001 diff --git a/UI/forms/fonts/Poppins-ExtraLight.ttf b/UI/forms/fonts/Poppins-ExtraLight.ttf new file mode 100644 index 0000000000000000000000000000000000000000..20fe902c462cdfafb24fa0dfa980a09bdf107247 GIT binary patch literal 146648 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 literal 0 HcmV?d00001 diff --git a/UI/forms/fonts/Poppins-License.txt b/UI/forms/fonts/Poppins-License.txt new file mode 100644 index 00000000000000..88294838696e15 --- /dev/null +++ b/UI/forms/fonts/Poppins-License.txt @@ -0,0 +1,43 @@ +Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + +—————————————————————————————- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +—————————————————————————————- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS +“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +“Reserved Font Name” refers to any names specified as such after the copyright statement(s). + +“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/UI/forms/fonts/Poppins-Light.ttf b/UI/forms/fonts/Poppins-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1e74b8a2780787dbed93b058b7b015a60de08a36 GIT binary patch literal 145180 zcmce<2Yg%A**~s(uST3WPCR5gcI4B+{!rQBki-@<@!t@h>K};jX)W zdlzvO|86E9^H-6NDFXrK)c`{hOdxPF0DmT`6N#u6@(Ym;-pF&~bK~=qd%H+u@s(7m9i1opY=`PD~g{ig5?oCJ_ z3TxM`E45~M4dy1ou%)AQ%4*6;{et|6_WOu-qO_uQLe*uk`s%G|szkC-om;WS}zieSiN?IO-oi+*V@mg=?7p$_{lF)pTUTvctn?zW;~(~MHJZwBg%(& zh%1OWK{j&3;h3o5Mnk-9Q&lyn2kliGO{P+Bc}-hRt*$7yx`wGD_Z=db<``o6Os!== zs~fSDx{JD;1@7_+w_%#5w;~?E6RGpyi7<2Mi984LH7R(~a&yl-hHN<+!|S8xZEQFcEwL{&{~_3FA}Q-8F;nQflRhEifxujWibt9FeOb@{m+Xa{-+=5k}{pH#(J^A0`ds3=Wi+qO%xqVWCc6R#aMSFqNm%E8_JT zj&5b%`tl~dWWmvwv!lN;zpx-*kyl)hA0t&Pi7{J>^)X}&U1Zc>z7Q#xmfuo011)w$ z3z~5mPeF*$+^Uts(!k3%d>#Zq-n^SRbJ``5IzdoJqqx+}>K>X5P7B=T71W ztnrNSM_x*~yuS}Ffye&#$|Bl*9ea+nuu2WT_RQ`Sx`aaUo zxtxYZpPvw=U`~h#BXB|nwigo}pSx+6=s02}kI`TY*l$pM)LWue@b@6VSV)mNkbyLS zdH{6}N`Z9WHEHdg^_=%GF?+;1J4?KNGxe5wx;cKOqitQw6x<2!cW^rhRG z77gye$vwI^QZRBkVT8L}sxp~0me#KBCTy4B4n?-+W}CjEXY;1s0=Ny+!@LVLt`f;% zY=J$c; zM5&@g{$8OqgBJYj6a6jeDf&v6Zf$N(Df9bAUrl$JO{v!|Q^mqxzsS@E{s@1)j{9q^ zhEr|4-V5E0Re*1D+N)wMET+<8yqMDHl?nRQOs&OItm8CPesHNQl`3UR;YkCGT69g| zGm#N2^!L<6*pl&T?EJ*EWPP2XT&yTpsEd_lN{vOoY-!xm#*RiiZCpw&v*@gK%cP}B zO&-%LE0UUYWmZXiipE}NUAD|xXQ%Z}vVVAA4qIenEC-W>!&gw*Ox;$$E*bq%5mI zO3Kz}6&J-PFG5LrM#k#(X<4h&ff^0@e&8sq`(!DC&*|%|G*K0G z$(q%}Gl+Ht`Zj2RWW455a!W~8q?1$FSIkY`&Kj-JWihVL(_5;QN~c9rkS~tO+HnbwypiTAEyy+*M`l)6#v$HJJ$+ z)tyD|5^Zg+DY?JeJgBW0GFSJcFf#F4|FCD>T92-@uB6XJ9{Wsf(T&=j6Iz*C((6;# z7iyYHn1|k0S#%T5s<8^Q@?%hUU#lAnh~frSVZFMk54Z)M2tz#zV^0;8i!5mLNZ+9` z=CjTR^0|?_D5AA^p>p04^3ZdU6<-Tu7YvQYw|3=(mi{1=QmjT|<{q=@{3i6oW_nE=o*KYMKA(wa@%{1L;tJG9$ zE2~vqdi>PD*W_*R)B=$PQxa5yIJ7~*gdmc@J}ZtSL|F5)@5$T#cuZGsSC_Qy*f>0M zE@>gUT53G)_JZ2canB*9f!wibRbnFi0Pzpt(4xJzd$P^aU{l$&RW4WMVVZe;RYGEF zYGMMI2bQ;|U`#neK43;*>xsN%>`7o2XOpL9t?A=7!=Rp4JVQ)mtGz`7HR`5P|1YT| zME{#=Q+c;V-?>VyE~+i7ZC14^i58CI8I%W}lfp)R!B_!1<{ZoA+4FhMN}C+Q1w1Hn zva-4Nj;YI!`K+}45H-Pk!0dE*lI5)}7w_o0v@utgRq9C4CN!AIV;3x)>>N1i*)rj5 zTS_i*JGapECYw!DXY#dKcQ&e=xn@hLN~Q8NE#de;^~3nmSw6&rxWlplQQ##ExvQFN zs{e@f4O}dy?x6Rg2%Y}}O)CI>++@nXqC4Tq{qOiHL#hi8P zH(HmutisAgkW4o*jfH^odaQUf>{xQ+$S{FJ>PVK$hC?b?P1%(7h;_4s9~-cA^sI9Lbh~yaS$@ z1$q;S5mJtv>P7KBQi{PynTR<@-K{fpsm!*W^`70;wFg_Wn0E5nce{vhCx$CJlkd-9W3z2k*JxGRw8cwe)&=+9Y1a=MXqeV82i$jClVpVEBcCbOU zQAx8_`L6c;N2!tST{f-WF4jv%eYCIm;>~3%$(c#x2*ea%P5!QJxNKiaYGsSY;V`Vo zT$SP*D)p&{2Z*6zrig~<4E0`x=$}iWBR(mEj+iCi4L@T)Yu(#$_ zDI0P?V6F{D-(hAfUPXRzi8W)Fw|7^qLR;Hi>uw{Igh8b?wYV$j8h5wTIH-FO@%VGI zv$M%mC!=D0)y}R~N7AxYrDmJL=u}(PCM(@go*U`Z^1q;Ac}@rF2tqS#yQHrSt*X|3|k$ z?{*svF7BDwmd1faVV}u)FhSD|vrgW2#G2Y}8sE5seEK-^n+9KP3%+wniQ>?fPigwo z&f#7MPXV%mg_xs@`*iW42XO`qJxP|*U1jLk(*4G@X~}7hj-uL<3U{tGrAaq5zC)bk ze}y^esjF@vkA12(SB%1S3iY z?TJFepr#y(@SPF2;6rTO0_5@{kQA+fm4{IsT% zZd5aW&d0+1y2`5G=yXmQD%Bs+uPB()t4x`SjtYNkiIR{gYhVB(AFQbYkh46If*V87 zFyi$Yxvb=JNY3&JAzDKf%vz6i`n&^UzEP#Rq(W>Jx4WAgk9{_*bJ&y`10Du(Y0Kbv zugBXxR8?!Dlaf}))pbt~bP`$2T^5bn>ZxM6L1h6qlE8BXFPxPz&O-Bw0+y3XB6xPz zto2#+?D57i<}%83;@M}JzzBY>5eotTSR@BD_arRKSs<+$;aQA93nOi>-(`s_=URiM+eD5cbA){n}>Qyv21LoqC=1E z$SG0-PlEU*k@I4av%z^CtRIeS2VA+vW-(NoN85UaHnepy)1>d*&C^8B<<3@b<;u98 zsT~8)baYJ8^yKCpSgU%eTj0Lna<<92pBrY(?q8}rrnZ|GURynAD>$2SHS zEYps$y&YCV%aThV8OLf1@<|w9GRg`pAlE2H62uwglSeG8Mr!Qi2IGXyHN49I1#zu! zeSVWk*`yfrVkvqX;`9y9stKJ%{WseGOWIdd+N3FNq?-qk9QOylp{yXs1;~a*=k`2N z2EOCb#oz33n4FgJj-d@#w#|4fmRiapp6c$Nxo`tnal}TrcDW!oupH)L=g7~SywzT> zJtbA1-ZecvFg6W2G_Zb9$YEFX&*5d9C;>+@$byDc36xMYQd&zONg#hdYf0@ePfYJ1 ze|wyHgs2(!u4JC{HDR$WEKwfb1~SWhx%_rRIT8C%_Xy@{;1pF4chAIJMfb+OG!mnE zLd8mOHdQ}sWs+@HBZyXe@6cdt53_^XaN>{a*Am(IQ5$Y{wl>(7#dU7jF?hVsH%QZi z`gRnAfe&;YIR;My&ldYYDKruusD1?3pH_t(Or-wVP%`A|eOy_Rm!8!(wcH%jSXtlJ zR&QyHH7}{?HrI@44D@oc{9!=lJa&}T23!}*e+iwy|Jyen~hgl;e6mNkof~Yn$Y|!L?Zy42&TI#3csrczS z>!=>yc*m2eq+W+#DK7BOba%OHdy75gq^8&9?(VKF_ka_A{_;JYwh>*E#nPl3u~m&% zG+GSx(t{9#|Et$k*WXvK@D%>LufNXKTj&8*N{T#z<5Vg6C5#xkLOAo7EtSQA3yb}z zpud2;M zrF5kk>ndQg4s)0rYpB=*!A!is@5NyfrJJ?&)mnPxW`nHHV)u?Wt|0dKcd!F^ zC#Bu2Z7I_h+(pDyxtpx+7R~iEg9zEDEQ8Z{9_J??)oG7&UpKdS@~ib)wt1>-A1)tU0$B=9CvlmeY&qx= zX|5PcC95NU)Zb)oNmf>D>+j!A513c2&e+n(Wq+s!X7x}bvA)IOY-w>iT8JJ<=U5c0 zdMv$DQ@xgMg~Z;(<$Y+#_0%*sI~?$Xt=8X(&tPQ9z_+OBoowQ>Lt)vSB@k3Zrgl(F zF-11QeLzkyePNz2&idil0w<&3i9uuT*YPMGPc7zf; zC_nfi^JnaV=AaxB$iRN$cY(LD42+Y5FapRy0xdAX2|*pCjm)X2$N~RB@&I9+aau-= z;W-s8)CU;lw6(0;8kSIjn*x<=Hie(XFe<}SCZL$P>?GF3C;^Y@zBNkPWW=fPDTL`2 z@>sN#No!MCuRS8Qf^CrK67qgbIr7Qn*%~5B7KOauUrxUA&rlAf3{(Qmyd}(^U<>mA zu4t78qwH)s4;5HAe-f;@6C2)HAaz1M_a;|$S47X6K*)_}afrMM(}FT%Knv(x;POIQ z6Y?s5J@M16F;`r^K*|IWdH2?KZDg_pOHv>W=SgxzKrNP}4@Il6mdV8VjT{{LBMEz7 zl)*_J%0Td1Y(Mcr;B9>Uf5Wuce&UP3zcDROktp{Ti*n<^)rI1A86UTkp7U_jPEI_* z1?}?!cH|qAWG3bb+=l|qAc+jvG|15D~C|-XE2>&V=scUhZK1QAkz8Gj`g2gPH zRg6xa>pakK=!yjbbnCE==y7c_UA8}ZbY9k`W7q6EH>m96;HD$r@F01V<1FM2kh~>; z@c(8IU$OV&-fd8fyldb;C9ekQB&=lFGw;+KBLY zc-q!n9}0@8yBVz@DBd&C*cB}l7KvfR!=xT_ClUNxK)kT)kymT1HBI0n1{tw77z^wD zcM|u`(E1rj|JtmZYVJ)eZa3#urxDrAEp(@bkA>-AjdBDK3(t^SKt{5#c!Btr4@oh{XTThx z8ABFD4r+)C6Zz0rP6YjK4RPsv?*+qN@)G8wV9fg|%>~*hdU%hEc#%BpB*)?IS+F7m zac$661_NL41c(-hZMP2T`n86tz25UKV`EDRR-zvA0y=1u)r)@{0Z}n{Vw9( zsNv^03QS*#9(dxQ6!Z8+5+S3OW8I(4Yh1L_Nj}_6U=9;|)q|B&+jfv;S2ZnRe&cHf z=jyrj^(quJwx_dzxl` zbct=)`8cx75mm7ons_X52Mm4__BJ5`3b;PmT+ zEEwA*k9#fn4Hxw_@-ZIqxTlzn^3cpa#)o#6#)|POF5IgK#&;0e)#DHkX$y$&$XyWM z6^bC~Av(*zPM)17i0lZ>VnN+MnB%wdQ5~`SO!1sze<+;8Qc+C)8m~O8QL;4>p{a=o z>BwLE*AXj;7n$G959xp{^zSwEqB+*9c^D(bazucTK{1jbmP0W}j;N8l&c|{zjsS8{ z))9X{7|UU=G6!Y7@pUei0~tVeC`SYd5PThCGw>X3lq+x(bsLLJKxG8By85&9^rR&C zpr8FqAu z>C6dum6o2E2xtr7R3-5~Ag*AxxN=5*Dd;6Ak#Ob06kPnu&29XoxJ++U>Ixfe&puqN zGMLH?>uVhUyK8;s+Ddhi&b8jC)n?@9RjbzPo%t#RUP*V0!BiKnncT~r>NKQJa2?60($1p zv>4D*C=JWyO!?XoKp>DsfZCJ*svHJW=JBNHYZ#~u**CR{R7XlqmKr37$Zy|qexA{k zKHaXbw(6VPVzOqsEJGE>HoLvSU7wc+64qceH5k?1dR?c=YF;Hb8H^86m&#YJkjq!B zlw*OTb8~4VaHjoxXMga6rF6{3najKSJm#K;GE3(ahCKW1`WEdvW46Uzt=Bdcmo#h5 z?aN8=GTILF168vph#N@}#0IIr7nVA96$nFq;>OOJa!+B8tEQ9ru$tCXR%+;KVpT_h ztJKli=~PwP)NoA=l6n)|al38#C79KnKy_$ z=1=64oBFP~;%es0^-0U(GEC~q=AK4%6m8s-Ud~*2HW(C!2gmy7&qh` zuy+PQBA>?Nk}04?w5SFK|3gZjcy{)OQr)ic%1Vo+k{Iq78riW;A!Z&X+{}Yy+AJAI z6<62ijJUkDH3Q?*qn9OVb;Z zd}bo>OqQ?B*QRJwWbj{Eb#_l*c3*#1e}6W)ckU)?z=bdLW%c)gA&(U)!K!zVtFf-i zg=o_ta>APF6KxRf6de-%Fnq-%3s#R%GyK1PEqpGK1_%~}Vj1QBWdhs_Ld#!iZRU+--9xoiOY=e3djdBF zz5}}}1sjkLg=1BU`|``Hf6;RHz;)N%e?NXg;3Uq>(jq9o1~Z#Deh=1`V3bm+3?(^b za1UOWAd|`>>>=qc@TH+#sZEIve#;?D@!5ZL-Y$(Z4OR#ca6d2P0dP4vvC@S=aPQ`) zzxZRKVyv3S!7*!EN`f%B*jTi5z>l5A8RZPXVu7a_szc?Y!E5m7p>)EQ;l3XnE;T;2 z;HB!Tl9qixNSv5n_dD@X^1#$t!Qzl7{yENl1J04H9}u}js5%=&#(~NKWSp6K-%U(B z;i->+#}Uh!cZngQ?-PWPlP_IU3_C+UW6?`5V1(Q%wC4rYTSJoyen&8Ux&TUU#p^xWw=f2v*va45;DS=i95ll0+GKR1*&y z6;ev!`BkkJf&zpL#IUuL>=QLVU6K$ET`SKgi9!XBUi;JfRnK2Xly#Xy`PO7IwM|X5 zu(gSU7{QhUK0Wd`uOxo}u`?Rc2NbF`AWs?@E0_iiAE3`ij~SW2)ewEJ4I&7cRjacT z3H~|`LMCJ0B1ZR?A{^P!kj3M%3_Zlo#5L4SaQ|Ywy9LoK%T3heYu0hz$imrB3Tl8{ zVn9XRbj19K{WQUxS|Bea#fE}u32*WNwr6DCt0l$(2GugKm%4z-4Z@&^IOdPU zCZg+u){PrmKcMNq8EZpGs8@i8Jo+%o9wjWtuK?5DY9zMFN2=5<3afErwQDrf{|@;D zuGi)=e-;xXbX9SSzILO|Qc8$n_mQY2@F2!TL9Kw(ax%b4uo?~)&Dp}E5()|CJ?}e9 z;+uBc7D&PdQBozjpNj7zY`cBYld&vR3ay+M$^Aezs-LiU3a|($-V$mXw629XK@6D6 zp~&1~gC!>MUFR{`@(^z7IvzH)f+7+s%K$kQpZMy}2zs=N2TuWZD)1D>PNBVGtOikW z){0kbToiZ)098T;@m0-3@{BX1sMcnD@^f*AZ-G2852Qj;_AAN({KyDuH@ubvDPY73 zenjDam2uU=_Fdee^e5&$qW|ZR69&L;6i^j<{x?|X zCIsbBDkE4!E`UUXNkq7+m8%%W`RTYtS%bG zd%y`~86|jVL=rg)#wsE8bj_co%g+vHRhpH=_}?bZoe^TKAXkI1R%uXmhD;>CN}~8M z3*fC*q`vF8BYF8*V6I-vSoP`+q<(S{*cD`&K@LoiBP`@_;=EE7W_{-Hk})tE{H@GA>~Uo||(1jZ^HHkF3OG``8aPxS1m>YMCvdRhkngQWos z)(TJ8^guh2$=q#FmK#_M7K{pEuu?^xIJt(VUON(lhV5h|w218}UdG80&X0 zyui$jX&qJ-Z9uB)E$~8mk2d#D^tip9Cu#bm8q(7icr4bv&tOE>st{IR#_KOmeW4W* z1(ubV&AeL=BD(+L=-8|;*QS3axsSN!JU&?+!e?PyFoJPZIcTQ@viFo0c0wUpew9S{ zr8MjrvADcw{w`jk*d@lnAb2AP#+0<~M9t4)5`|Ev_mw=7seCO#phTnZoubuG|A^2{ zI(R_S2c6|So++LHu87ouIZBPI5s++RB^Dfa%{ufe0xu$y#1mRR{V9_sr%>u6mZs0# zgzVm5xlVfOOPVX7&S9`ngoJrvg;M3v(+-lPq2dA~xvS(Y>EHW)81s|Dk;?MM@=|Y^ zxwoA9A?)bBYuP%tO6jUD(|WT+*rWc2MEIrzsNo}@6yzgb3NyvI(x}=PfpLmo&G?<~ zEm;{I=v0&?XI>-D--@agU&P(6QxLHa@>r)AsB_>+IYB*1fzP#YCK@@ZkgKN7nXP!! zLL~a%(^n{Tx;A%fCyRNy;djg_NK|8N=s%b@i4D3(Oto%v?25M0$+izez$av}&coSh zs2&P%PoOA*Fms3)7KJ{Q=C4lkOeE|!?M-wlRG#&njTYZ>9fy80Y1ga6&`)d2mr5M! zU3)Uum-`f+5ldk~O|f~xYTuZ?(p<&?pinIHGR8lx2YrE3dhBj+SxPPl1B~oH1VMGW zZmfnN@cN3nxbKUEYTTZ1%XQb?vfQIQD>xLQ;3RCS{s?mAE`;O$zl$#+mxoh!VA@rr zRj|=2J`*-t0YIsjPvP*)o?Pql zbU(7SgX4FLYXQ^z2X~#r%M1y569H~+DFbkGNqP!1Cp5aB1(|{L=}BBCC_*{E;LP)w zssAjUz(S{@rV*&enS>~5gb;-4%;^I(=4p(h!sxw)a8$B+c?4W8TO3T4nNlds9h@0Z zRk2o(H3$P$VO#Y%sRqeN=Jw9(VVyMln4P=Xk{>$3Nixu5PzeL z07AvQ5SYcZp5$;>xiCcV2ol25P~o`&6lHMOtJ0$5H9tv6ijKe14`)ArEfopD!t0dH z2B2}EJOGKh3*fc*K@bClqB(hV6cmA&j*(Dbhf)D$%=yGV<~T5(X5KCm}}$HRR)Xt@GGh6~IK!4jGl0D3{@mB*ZyiEszBu|MbN@zx?oKc01C704e{CM85Mq zOzr*T4KNxCs^>vmMi3_@*v}Y@|3TU)@&>rb=NKw`x^)ofban^HP)b=7xnx1 z5w+FKX{<+?mjrP?nhS{gu}r)lIDuv6gdqM$pF{kQKYuT9oRg_g9sqp~c>w(R6Muf(2tN+XctounE@$*P(}dF_2tjwbO3QDP6o6R-OQIj zU|1r6SxpSPVCz1TW%vbqWr42<4+$~^=(EV20BQ5zLOgvqnYjri1P1MNtI}%S;zDdY zG1kcZ1X2N7JEQ_!8v(%!S%CX6>I&grkyplLIL{{hSCQu`ikUxqKS+yP41c8~vInxi z6hAK+EX%k+%FQsF-&qX3wLb4n`|U{?XGU;odb3a7 zAYK^6MWM(#h~a{rAffdN^2Imp;IN$CWe{65YK6% zM$#HKjAk|yT^0t`HI^_Q(}nES8Bks5>BKA`yZ9IyA-lK$dJv4p3G&!UbtryjzGLHO zW`fviVW77cJntEd@&Z|g)fL>mgybqh8K7VqU_An;S7{;hcKtiau?yh6Dh9H@klaf~ zV!o~yM9-i(arBH<``-fBVftU@kXI=o%Mnd3cvv4AGHi{}0Q&0KjHa;QRp*^at7|Y0 z%jCk1Ym5WQ8T!L`bGnWI<%} z_W^;mqQE74dnOvX=`#gu7>6g4Er-!n0w!qy&22DZi=NF5?s`VE3(;rrJ;v*? zbz!`niLDZ&Ei;k(VGwjrST~le5ybKH8m6>XHF_d?r^pIVf7Owe(Pw~8Dt>KJtDU(u zw1b8+hh8rf+_P;}ygeIgBU`NOQk9mfpxMgDDigCo*lk7l8Z<7f`iH~#wqJD7{|)cB z@;bZMDCJDN6N*TdAUEf~k z?SO^`#vKNp<--nQq@j6!VO+F!!Bq@NR#cqg3(g2PDUM)`BGk1T8bW|mP?lKUGTl!Onoy?Qrt&UDB^yd->mibBT(jzHn00m%v}gmYV1-$ zoxhS;dHT(8YM^CI+th?Fu0D!d0d+zTnpfgxGR)7z2x2Lc`~oOai)c!=A@U+ywya2vBB1nx z+5 z-rci|TIw^*GZxYvb5~cdVvZ-ZRuT{4V14c%A#vdv7ItBLq{gJYga_@CA3_kX4| z?PJ@Vgp-E+a6r?J0BxumACGMy(!xj`X^`U=R2@(};i`EE;){YLl)%EiIoX{u>^2YU zbfcAPv$Eyh{*wAKvoGH?JHBgjNS?X#h(Yxj@tyw}@j>J#YOMOn>e`KZDEbe#Y8s0* z4Mj|VxVyKfb5Ol+#xb~x{vFzvk7C}3CBKA`p*_@fq7I(xVOzYkXn+gyo?kIg!&l9- z;!>ft`K6$zi-f2**VRNSgV&DxixZnHl|3u+w!5#NI}n)a`sXp_PenOmPpkWHa)!~JAR z{|q#x_`Q%xoSuP<1>ix=qIymqgjpz!f;V(gt*1~NfFqI0Q}X91QP<#i=?WrOyet!lz~rGmTId)V<}}$g%QZjz824ZElB)? zaQ2Z74PSwa&AtRLWlr_5(~s!o`2!*p=_kmvd{2wybNKC&q}W|GfBu{OPp|1vL)(kf zcPBQQt!;6+2bzf+L;WvKKNQXqwiSl1jN5e?o4k2yo(%jmXB{-kfYsq^RH>M|g7IXLv9caCaW`;?t$2gRg8hPvC}QN7vP(YLWeJuDtKxEVWDu4D%OYh$y8MVW zd1uG|qf45N^E5N{zv*kBCDSFiCDR4~8~KlqtkiTtzow)#Mib%IlOJjQO}Jf?%uq8= zNymhBZMwI<5_NQv5y)AZnn!=&-x3s2quMIknG(vKgrpv=gay?iGPi57Mu4_Y(Wn+n z`6f{Et_lD%MYFayUo*@6q%$pNx7+8RiljBvDe6Ri8FY%0L^HQ{Yv^*dX8XKI1?zqw ziv}7?u+ImT3|zerHF-FLLm^!0^PHr$yv;z~FfC~NL`0!e+H`#G9w4u>Cuwz)l4zJC zLb6R|2*6B4s5{gOSu}Ei&Proe4s*M3K2wAHV6m4W*$XtU1FC6}gQFP(_VX8!YccO% z@OhO;8J6tDO{cmim&H|=&s)usANdcLGM^-`O;rT{uv1pRivUvT)X|C%Bm?$tFJ`yZ0U;lf6`RNsp09yL^=qo+XX$RGftxXjI${ITF}rdWtFg@SNrI)74_PaS=D^)YEg76Yw zu=|?l9udy$!i~S8&NjWL__j|}#yz+p81t=Aidh7~!u3?q=A|bkrlaJ+7@ti5#fUxQSAnPN!D~kmSEp!{)fRj*Yf<%&9m7y|i*MMLBa2iV z!ENiphOd%W`F3tmw58;q{-XBOH8Z%++X~ZU06_ z(VfS%O05x^!XZ;}2ip`*!Tee%fjhV!am>>pK0&qxn56_hq5ifySgXyPPUX??Y8ECH zJqw7~gnC4wr463*Jl9|y=Zry&Bvo>)-D0Vh%6al5_0GLLl%Q~p`5#0ax#`(w=cFO| zp%lUW@Stkh9&qb2-R<+F zlJH-!fq5q=0IWrYvOQ#uRDeTj3ST2^dVk@$hs+8;_YgIOQW}xryX0GFLA<4*X@^7w2F2(raKe_cGol)3G5Xi?}pQq$UWsDt0!Ekdp=-o`I}iMwOoGNC_) z#(uC&DpbW~9oA2UN`kfA5>)NU1C3C{>jPeEF5)sr=*%;OQj+KSP%4^^{K&6uUzNf3 z4a`7R<*`ucS5@a#1Ek3!v)ZY$Xn}wiwgUB0*on zu_@|~7Ys8o2O|wFbs1`$VJ0S^rDGKCP)tS+ba&iH>W!DkOQB(u;x6QL6XWJSOCkU|! zaqL2tR1&hBlJ!Td&!O%x<;3+;a@Wo6ftzbRIy>Wa`|d zXKW?-9sNi1&|C?*r54dp_)5`)e1`v+cGxkF)=+j>8cyg&oZ>QY z_acs{nAaEKNO2zVD)XNB@o(?oIYOR#skxguNM6OK;{0XMzS*XPT@_$m-hj5R=izoc z^=IOcE5lERnk^owZXKM2eF)2No7gp+vq2$liT}Y#2tr5)t1_@N=WP2%B6dI$IYK^? zLMh6B9I>WvYS=$}dQndNp=sa*dGG0acur7<-*`#b@Ak;fC1(%{R|ZfiAbRuQQ%hoc zr%+#9meNOnQX6EU!}Coj3vss6!1jZISRFy);ksMy}k}|4b)1qj6k)3?E%U==V3>kAtUly|* zc9FGqF7DhNDQ7?D{M2@Na7E_CLVM)Hmbq{r+!&bGfJg9+Ip1 z2O<=eor!l3^zpo_vmIW5cc)};1Mh6Wy;0pw65jLH#DMP^4kI-(-#RQ$J;Ev+A~Z9Z2IF@vm`bZ&qN-CnlKjzMN?m7sL5e{ zdSE_sp?=*0*tAUoFNqDj@bAd}#(ZxNWH{NFi%0fk2^w#sq(6UMSkORKJwcIA+Jn$O z26^J@Ee)gRE=_M#2)iRo_naE5zc>7iE9l1op_{TwsE(kxe0Z0)O?Ovv3)cP&Hkw4+)GtKCKhR%e6=Mw^^) zTj|suYwwiHR$Roq#8ZdSn6;RqeCRm$>%bEtl2`&Z62+K|AK|$OEsmP%*ts-|_ zh}@uj*HM2Q!{~qJ^at#U_lzgjTm3h%22pu(h#T38Fc~ixnI%zLES9vP{JgSv7*)27AqCnGlI! zkTDPoR}X`|pR&V~OTzUb$TIN~B4h~#heY9nL;5$65z3FcBW(?TfXLiIR{yomEbD>u zMIh7Ue;o6N=;T*O=!lV-FiptimjLNNeuQ|06=Fv{&NHz*3aWjcFNppeA|Ci5G{o5f-c94ft*Gqbyb_)PLVFMlJ3}TxSjdokB?v!zhKY2aLTDhQ zD)39jlqipY{SDl8wu3mrf>5G-;-~y|iB$0YA_8`|vEP?u3a%^p z`vm?iE)rbV5O>4#Pm{(-lS@w-B?~iD1%kprSn-QHKQxvN&x2Fu+$uJI9k-a0^hA&$s+Q{UmTS z5hPPOj+JoR?#%SvZR|F{Gm_fp9GoIaIJxO5^0pJ`;3i0c_u+$^KpDHM-7Uebf@dV( zY&n0Uc;)JOCq3caoKgdDqYc-ML-;8i1pGJL&}z&itD@n=)^JDEfYovRwiL}HBYRPc z`ZKa(!1e1?(llm8X)Yh34(Vn-x#g|C>iclx3DP!9qeNHuX-wQ2dRHL(TH--|tw`?) zT_^70*9xpfgw)~Jq8_gm%vbTtz*i0UuAs|Nk}Q}uMyVdO8f0)1I#TUOY&dO?I%(k* z6Mk5k_~lvaJ&+0Rhr`NL>I6CSZrC!pJT0ra;hxV%Dxhgil#|P3^A<<&3^NUCWW(a< zE`!n5fbTiLYFN$=&l@y|G(M?tu>~T>+3nye;Oj#IuS|Lw!0q8=vWti#D8U^%gDvx^ zFv>1V&uVVGtEu}9^05qSm2%6vv> z1@RsDd^X>XHO$v;qIx-Q1@u#4w*&FB^Nwt*vOvQ*ONkCQ1`6+jAUwqlkKZeS6Uc&e zS&(dUbfNv7JiS)_df=4!pMvXlWItHx5TocM3w%^NOL2S%e;2MeQ4$}8U~GuS=|3%s z{=u(C9^IdCsQ*g&2ZrmH00h=V9uI*Z#O~ zJ084*^k)y&jQKBj*6U1&fV%!9F_{_dY(Il)lVI%3wa!oZ^L#2j%L(XC7ABj&#d89f z1+eKR;$>C_L1t&+I>P-3We_`v*o*-wXv0g;1R>P|9%43o3FWV~;#quTvTH`ZddOuO z)WIP?WFUBO@?5U?F8_};#mtv*lrnTRbay16tRC!iaat`kk$oqi5hZhNT{0Z4tf*Pn z50Gr612eFHQI4>wq5M;*)EwRv6Z#ONqj4c*$6E=qBj;ZM#+>P7X3(3=g_uGkRMSyDlEqoz zeWFOQ<9UE4N6{zcd(%rv;m9~%nIhsl8+6IdMqP(eTjm{G@t-jvzS8Y!X|^e=bJ%04 z`)2bQ>-9y-(v}K+d-6)J@jqY<6O%+X*zNVjRk^Gjh{Oncqe9)vBG9|hd4r`BypJdy z1;yX;n#0(gnmS}R4u{VBE_M|=I;8l>*3%D3lA|8ksw&ai%DRH*fR{Vgb=b(rLtL>% z6#HS!C4gBq@}m|+V%(0iur-FX9DjOln6B_+U7>GDO7FEA`zwNHX?5Bit+7joU1M_( zNYbJn0;(xhR+M)I57~0H0Q}gEniul5YSz<0)~ghsF9@1V5JAr$C0b!@V~uF`=xuC| zT6brrZS`12kUn4!Qtx%tk4unsn)_7z?L3R*uJU@r)B&+^vwVKvI&Rm-P8e%C*b$@? zB1X&mo2(j+T&53GGWP`!K_kCCJt`T&^_(YD+AZ4FGVS_1@zIDjPs^5h zC(8rL=!7|x%@WQp+K|dVKKLRuj^Kc}Q6V=eSoxO?Vxin~F`0G)+RykxaAbrN?_QL3 z^?x@kJLh{cZIkKk%4r$)LrXfkMx zM2it0yAQ?0*p%nbKh1*+*Q@{=*3zUki5`(&mHY&le$bUzx_STM4cZ7yVuJV@b zpL2^Zh<=`Z!V2LS(^eg(xzp`M;%{J#s6P$%83ok`7}o+zO8m_cTw54S&fIlml=(O~ z3i6KA4@>02(&)BJ3{yxzfA&)(pg+Z@#RF&lEk^lL4L3_zK7yyNq7Y*dhQQj%r3&~> zJM78rX)gwcK&aM^8hvv~;%A~A<6c=NWJ7k&c6_EgwlR`g7!g8GbDRijnFYp!%u86o zrIxqbaH_iIwcvor?@o_M#==eYljv|bUkNksI^)jEdK&dU6eL z4BKu=JzQT0<5lzH<30l({sa;4aiP2f(Vqjccf#W z+wCbCL=$=SRcIowdV!{2&{*M!@G2AM0Z9t*h?MMj_~<&yL`Ktrk^<0yTN?IU0y;1> z9BJ3U+>H{)e8neFdEv^Aw<~BYIfMuN(>Sf5gtkgvCHS_7vH%MQRnz`2b^P! zkt0k`W<-k=+iQq|mdX}$)~(-YUFNb1D_LD#Yd@c6u5)~f=_00sz92hSc|cV$WSzWW zl6Gc_gt-fzEH+CXj5#EE*c5l{&3IxVs_g!@B{+>S%hE-yjy!U{p(gq?G2QD6u+zwq z6>%axKtN&?-##{KFMmMVg0mYf3YN%l*sJe1kT)*d+TXJ|*0kJf^R|Wh`&SKX zsGF9k)^qMJ?)Y%}PRUv>{w-}Yw#^JW$|}d|${Q7^+rMGRR9uy$Rzvt(+YF5!AR6W) zV6=mqhHNt*9bzvC)X+l5wIWRY_X~rM;xPE~V@vleaKP)})|eaeY!D5%Nq%_dXn4y+ zZd&9~uv^;;mMtC*v-%i!=g2`j^ufa|B2k|wlrQ^6(gE2;H|hvM@Xj8%f1E>z!{exP zn4-eRoN%QBHt)!1Na3&9yej!iu9h{lVTWwnaFppB*t&G*l~BL3SZXPYxXiDiax&NP zH7*`1jn66^>fRMLuUp#UQN_w@Pf3-hH)&}hY8v}69`XdtZyC_ZtrFZ0k98A3lS9*s zP#yrn8#X>fz7#=>j&=|9o6kMaI)z`Hx4&_l*=4X)Uoc(Gc*#${JC9sa>##R+ zmGxCVc(GH}ZK`fdOzE=rjm^+BlyMMu>&V~381jQ&9dhnb^E7UcGna)3dwN11x5VMA z1UWL&-8*cuTP=6y2I(SFn5idqiEAsv?)xMEpFHL%G!f1Sxmbmn|Z6#f0Q7gm3vDgTBpC(TJ}$` z$t|XHCWb3IlyGXQYqQ0&)z!S+aDet-y=#!@Jnuq%1sT)Wi@BIWqWpe7@)!hj8JWuo z=VFkF5KR%}=x9&hh{>%_ZZ%c3D=li9ifvLE$ zSXJJtGj}H?4{-Cz+)`g>VA z6I+LOn)N!%gA1W)VcDmiWt4$M?%zcs~G|Q#L;P1K`S{j(c>TaE`Th0DPbN-apymMT> zEVqcx$sDuQz$F8|y1w>xzn3o9ZfEZS|TeMTst7VQWf_uT^O5 zisFj(Dw|CYdg%sWVc<*Vw`4c))Fm9Vj89uBJY#x=34+wT2IH z*|Z^PMV%&s0HjyH7Ngx#Xt$_Py2G`YDmZij|M3KHx}H^Ms-QX(-*v>osR| z#JsKp={4rB0Nj0#cP@7z*t{uvsw$>S)-8)!xeUop zxoiDUFI>a|6)Qs*y&zEZgv+-fb=9-d5VDUcmo=D}M>kz!*icbT(gDS9iFzl(DmR z@~8jqm;eom?c}3s{l01WQb}9I+8vv1qq;_`(xxq55*rt1ad+0}+RLl<`8xNR_JM58 zbQ2@{jkV?q0vSzCx~>Dta9wb*`HGY`p(m682MFSfBr>9Cf?#DAmsG$6HB?nM6t36G z8?+5$_6n!(ifxrl75VVf8|bp9>kQ@AHSDz5G=&;|TI$S2)yYizEd|PcueE*Us+oFr z8ko*YjgF$KwHIJNRROJ_QKSn*fS+1Q#&VfhHLi2=*UL~o0q6g|7H=1Pzno0rzQ0`j zgy6b@%;T<$B$o=VE6KIN>*a#$Msg*8UHqNkx``x$*V(!>J5DPJjRQG(Is)HkcdfGf zXplI7qy+c(=3%4C7XxsPhFvdw%Pv)D7g9Ffdjzt_{t?Ut_2b3bSjzYCO@XdIbW=u? z0y-_u9hBVZ9~MHaKT})acys53R;HHp`qcG>nx+zdGqa&&u)k+ieeUjkd$V()eIiZ! zzYg1WgKM^UCy7o}4cO2Tp#~1_GCfP`*iBa8A%Ta<_Pq+ZpOM>+WCt)#CvpdORElNS z|L5De=W*byHg==A51f>sxvzm8HsGfKNo*^ld2^3f9N((^%<~`C#T1=>8C?Xzj;)Bw z6_A7Pc@*Y6Ud%h61Yt!XRpkM?E5O%mjqGv38i4O34!|`8n+NtqfRhl6RJamk9)261 zU^lLi@S8Jy>;jNY{8yW~jU`-9tbO~k@R~TgZQV#Bjsw!@OOOcE(*h%nqHjdxjY~4$ zG7ia33b^ISyr3h1d0CRF&^~1h+Ct7PfR9Yfdy!iZ8+)7j+0Jn1P7aT+NUbtCy4U$1 zA+OKOQ#L6J^WJ6*!QF^x2O@EB-rmE4)_uc$SP$$zXKVYK;;dy|HP#Y(>3X+H;mjZu zp~_F?+N=iaQKsX4e>bYaG8ro#^P%*?+}$k=9O)HCQy5lR=h<5&KTR$ zFmo`bv9@Q7CGp6PmJM=qtzR$M>OavUFBfcy5DzpFwrD#c4$O=c+4wo#U#TXmTdo8? z2zEbQKiM_m?LQ*i^nhcLX-uaA{7#fL;%lT~hreP~jVK7J^H5po`wyU>0t+7lDIBemHRNTFBX1Ii126EFzr{*@IzZU-(_=z)Mto+~{3E0GFOr3v9rTE83 zU|Y;uMH>yZDfM|F?}nV5ix)r26{qUWSkiu-Qd3Gdm6Ww;o0a_X-uEc&e~GV5p*0M_ z%M6SNoxxNioa6kklt@GqN2rZJI*u1Y8AvD=K{V-{Tu5gyY!YhW&jp8jaAN!Tz^XO7 zj$D6;9PjEs-7Y6juq|R1Af+CS_ZDTc3K*maSW?4M;%$ zN4Dt@6p7#W==C1A(NN3DX%bMe22f_~x@0r85kksgUO=m&0c*rbxX0Yc6xkDEWFNCy ztT@i@f5cpbMsf#c1nlFEh&uzKfKQ(^{NS;V3 z>Z({Bp0yA9)@T&++|aBjwQ?kC`S@4VA##0lFoU%0f>ue+Im6D!k&O`?l-`WH^O zB#$k=moc2ls3A^pmm)jE0PXk)aI6Tev4}Sb8~NiGnf%Z}sMPiad#VkNMbINT2*1kjED8lNL?^ZaK%UFr1x1XFG8<)eyjX{<`88m9WAqz6&;nAxw3_-iaNonZN!J zvDLps{QL>rP$bkbgSZhl6mb-Hw72%EcW>Ud^(~ryi-%yb-l=pq@UZ|^yg4FY#Fu!X zRz$1WJ#j@iEjzBm=QgJ_w8f*|ArY%P7|U0Y#v|F=gbl6_)D`x zlfp3_?8=I6$nV30n!YfOdApI=OeFVLa)(ki@6T|1{c`b3fNL8L_6m7QQ@M4Rc}GkP zQWCX|J*ukF4!yT7py{K~17+#Jj1nL<*iw=h1B!O2viMKX1833|(E~E%kE5a`k_)*w zZDIZ3p3tQ22x-?_)Xiv*^K#hqyhH+r)-IwFEYl8$I;iv%9aaOV1R9?q4;CL`JaVAR zg$S}DRsv3e!20-<_;a|f=F#p^x_z)dbfiCutO0M$P+yDBk60ObrPbgtkGA&?=el(v z_u4y=YHaP9dqWa){55DN09aIr7`iWWwl>(7#dU7jF}OKhpU)3_zpkv$x0$9l8`{%g zq!0m}lD-8aM0I?qJVx;Vc-zG97UM&_1xJiR1l%hj46Pu2i$^PfpFlE}#c7d#bKGwL zVr0ofwgn&yV2GC`oduQ33ITY5qxe>!SRWA!Mc=m1dk?%QBPpj$Jzj^Zo z_pew=_H2Nv3_qj&Kp*P$AdG>)okP(Z_A}0QE}^L<7wyB8qI{1a;FI?A(LLii1Cjgv zM-z58!v-Y(gV4$$Bbr~mWN}iNU(|QQo+Bi+UkkQ}!Wu)npCv`$ZKHluk@CyMw`V>L z4}iTuDmE%%z^fu&XaO+2W^d#9S5YI~(3o0p7waXXKJdFQMqR77PPJ}df}zv?Psk6$ zU0$xAv8vQyzwDfpRMe2#VOWv5D#bU%Hme@q(NRN0f~Z-##Qy@ijQr^qoZiN+M5s;} zj33ZUv6UdUF*jQ>#(PST84aBcCjJ+e&W=498T;GMyF4mIQxoIci1@Dl-;u}WZJ(>J zsc&ECEWpEC^XF}p<7jylXtCfqLV1?&NenwbIPny@B~JV(mP5~`Rcrn~*1iL<&FWfM z_tPiIN$e!vj_t(pl=qS~ENfcUvgEz@NW5ZaCr&~V2m}&F!b*7TS)hzUAG6FtD6~+Z z>;f&L6bb}NS)~DNefiJ5-_R#nvXj2Q?$)Ee;63p4Aj^Cd-dY(*doJSw$rD!13^LE3_n@;qnb{*B#CE_L74S5TD8Ve+gr6#CvrPh=KS z`0+-|1{Zh9iR&d!uZpy6egWm`f~Ku zQmgm^O_-{lmA?GUuFlQwa;awk+8ndceZL+qwfVyv=e2uCEOrTPQ zYzHTSBMuyV+bl2RY#?sH!Kjds1HR)IJDt|vKC`XP=Izv&V!LZv$E}dV+uUbPV|Gx( z*Bqu+m1+ufrTWrJW2=|>=S{hqc8|^(K`p9Qdu?b97)-=YI?2)!Y6VV(l8Ny-1U#&? zc(M`5MBEpij4_M7bt1BXVHZ-%tQ7*;Qb+CHb!tpfk_BQuVC(uxU9J+g z1@3{}pi+sJg6ct{lPf9g11P}<03}AKoIpHyfxMWo+(;7ACxWR9n}FqtL@<;njaEqz z{b4xIo|ok)((2;dblQ%xs#3Le|E4a+M*Yv#M+cj9>g+Egr!2AGt!%R3$CYc&z_06t z8%iol+I5vJv2mTo{$c#6m`@97Y3g;WR=vSonYHTXjPmL_3@7s=ffH}Afx7izIC08= zjhoB^s#XgnYQfM(ue!75yd_}(-kwTP*@<&t&Plp?&pc2sp`x=0+&Bvd^j{0|HzSDt zRa6y6{~RgaOS=-{YC@GVFElXI5J|>y53Z8ty)l(|lcx1z^W)7PSNlL+>vZe*NZS}Q zE$x5#g&V6JuBy8q|4(O`Nmp1>LESsM6IEKf2b%4*4O5No<~qzJL;YchFZ%KLmdH8i*PWlBl7ZJ>lD(+X8T++ z*lkUHY$&m}Tgt`~Jylg65=$iXkRhOl6{2dwZL_cQw8(&?XQ6uRWQas_bj|UrrrXw> zfu{=e)&|Xv7;tt^Euv&2^`p#j>QgSr>Vqf{`U^UO+2>KW?bj5=sLg00(gCgeMT9~;In1AV`fE8OuF1B`AF)Ek|xR@y9 zvkX4~f<78N0AdbGr#0|lE>=&^H{U_VfE3KYlVl*qoP|!ne^dB>kxGvf5hmepr3kTq zOR^!qh0ja)7zlgEM4zXNa}DrFv0@Q~CfjuF4;9nDJ#0=HuQm=!2{9=CmuRSRe8=rdm~$D?t4#&+2hQ^ zN*T_R7eW0$sSpsLQ$i!M@INr*@Q{eTdO9uFV#ku6sBZ1MbNwavO5>;OD>J$5NpDqE z@6oIndx6&GJ5x4+%gycH#Dw$-<7^==sJTE8UG z#U&`fo9yb&E>TDowN8ljY_L{W)SH?+_&&@+wMtdjqF^o)gf5OgkF zU!*h!>o#pb`UQ(rPMTm#OpF`#Ltv*{;fF9ajZF{rR-p?#+gHy{(f7fe)i1b<3T}Gq zHsc;9l4=1=G`Q)Fd0GrBR2SL+R+1lL?P0hLP9UmCaWv){*87?ZbvUqJ30+Jm`<)p4 z_7&d3y|bI?!6SDt1|I&O=N1&k#))xAO2kcZ;y4r=Eq)fT5M|!{Rt%KI4&JCB(XCt`Ol76!ZpcM65C=VSJ~U zYd|Vr3%L3F@PwUricm}f?|Y5}x)!gtYew4>*4C7_ulSP9BX*Zp^yyLM>LI;*Y$bFD zJ~Df!2w--R-KfS^S^^0~bxUz^t5)A$Qe0{-MzqFv6hl~xbB1gz7S9S{r**NhtW${C z1>J-y9gC*m__6pBJ_;90Zesq;r$&K^ie+qM`R9-23>#UVDG?(UCd9nont-na%i4}n zQMCDe_Ac~Xc2p(>4QVE^Es=%{ya?bao_7NJSIAozQsm+{A(N6tb9*kKp7vvcZhS+? zmrqfjl6?6^o6XclvTQDj#|ejmV6)s6XOl!Pt1B)ovpTBcR>Y-})Omf`P#>2%_gyR~ z4({E20{Gy`=HqM>Qf$eH>{hHih%w5&iMe=XED*jEqbvVJ6w2Fo8FfsIv=FFN`X;H* zQZxBzWysjxOc758HuZ&re1~_7ZqgsP#H8T&?1hN;G2aRo%~9qqpiCoVM#z+ppxEDk zLz?-5fnq*~YVj2#E;cZD_K^UNxIsi5V2PEugo1=}!$t~cQ+Wc3LWZY6p)k=yy+9<; z1kkfQ3Gbf76z0Zv!YE(AEgz_Tzv9DVZ2$7g*F@44ZOK|&**x1wpAQ|(K8DB09~k?S zvK00VR;$HQHElI^(3wYAh^yShAAXTu2N3J=emxFisC$G20g2ZHluicOO=O5RNzw(I z4FvEEDV14RWnL8fq4Qov6f8{)4gbM8;4hP+nCcSd*q@?f-@FxRMr#&8g2-DFjk_#x z1vYfog`hkib2??)Co90~r{139_Ah-#d>!4^eciKz^scL~V1#m${R_kc@-xJjV z-|%-+6c8oA`ka0dUSB=zHY57Pug&MNXN7{saPA4*b9BJWSo+0_EUR`j^zDiq_lm9A zk;_&_YtpoyG~c<@^Zt2+hPGOr#^vdC8HRN)&hFyb5G2o+CPz=R!qu@zY|A#dQZs1m zI9KLt_}PS(a)YzNQem`c22D_SAdVjpD+bG)UXVCD#CUUlCD=0mC+z2QIRoTRfcqIt znoCzjYgWPhzCh9#+dP;wq30LQjEP7k3^EKRSG_A`4!Nvlgj``}89vIh501QjeDWE> zGc7om%~nt*4s7{X0*KM7>VKaAhF({4;Ci@w@4+ISe!p zMdmUPYX$EG{^@}U@Y6s8k$v+2g9zcBd-0T6`ChT3e_E6Y8vvMf^ZdiAM4bDqpO zo6KWX#s2{$iD;2*z9UI2GR5IW0d7(yFUOrEY~3NQ2ltyQ0cPo3@I|r3P?d*bY~(-B zsY28UMmP9B(mI&P^Rgn5FGHM3k(U3LTMuS%b`mWgRE8hGQf1T$QNR> z%IW|_v5!w`P3g|aMc-K>i1GH@{aaArJ+Os~oV^H! z1<}br64jOa z-ir$&19%ReL@3uSK4|EySb$|7w%m&+wHE%sKNaw<`&AW1T9~M?%D0yK2cFb=?-yM0 zq3<333bB+%*ReH-#iFoHdrhrT6P=j4)KrM4D%7D^<`Ec}WA@q}5U!A`to#)4!q#oD zUn;7sJVo@dkb{!?z02Ug;7vud5i zBL931rUdkyaFpl{J*W0TOcfGj;i&Q;Pz6^)Gs3aO$|<`GMmO7#&0n^{RB}U|FSkotqj=E14doTn1E+lE^Sy9~hoOpf7+y0!oF& zFG0_(DGYoGvpZxRFNw;A11>%k|G%%%ypJbD1u#6jqjNDpn+v%HBiGX<=y3Hs91l=) z6^k7-FCcdKg&wk}Qe0LqsWuVDlra&B4R@6uA>& z{;`n%33^}vs_>G6`*pHJTtq)bEMw^1Ne;*?>`rSdgBrt`uj%*1y*rFTM0B=^$5JL% ze`)D+rkMxWvnNlGtdN?`H;@;=8${wd%HW9|BttY$7*^&Ltx4!#co`1Kt)h6fVPa z+Gyd_kDLUeKqTPpd%SLh*7}sdERcBfUKm{EZ-3bHm9TGb_B9UU;Pg>r9{ zm+bi|fVaJ$ScIj617eXKP*<>7vPb-GMDoE;1}>?DOP=SwUR(*xl7U-H7$`@?bP*|q zMFDaon$hAiS|h>*-eD-TNcrbx}^r>Vfr9 zrbSKF^&K7c=B7wfgs#`*8dFzka?7ZI$ZlVptN4=H0ea)5kNFdmzWkER(k1e~>U?Xz z!`Wxa&vzH;*BUKrlcSBr5lh8#BYDNy8qIyo69R&4PFM-}rNgAZP&r{foJQuv#b?r- zAi#-ZEp@)=`8&2b6)AP4lN+b$nh$%J@2}px+ILA)v&OqT;B9NyT34;!zXfVU9x59@ z6MC&G3W=txpmA+lB2*mUIlb|#C=fQ7(b%prVwJ)a2B>a<<_}zJ8$1??`lfo)Xy55^ zY;h@cgBXf_pSB?~erJ>K^QLBMAaD}W0flwF-Mz)E8Mc}TFifXKA4NC^&Y6eVsoP3tyH)7>xmavPf~YL}-TUVANR;NbT^ zQpE%a`10~Rx0}`cvLE0(Ga9;|FUT_ zEu+r@=;JxxfV)lBGw@%fe<1|OgrGT}^jT!ir@KF*xW0F2n!e7b>M@S_KDXNo&C98{ zP!m0yY!$mFf6_2_tH1cErMEN>H3$PdIh%MVxy!7;s!@@k0yzNzNY*UFvLa|oC%uzs znW242$!+?vscHI(+1WZdg|)#phNr!qa;KZ0j-i@h>FDrHZe;x@z%MI5{kB&kZvwkr*>{5Hb;p6+ z37o_0XGtQ1u>xe@OHjWMttDoA+s>HehH^~WR-Xn|jPC8ieL;|YMo;5Vv3uyOw?!;93njimzPe--F*e_ru8fd87aoMkbNYm~{H#08@O0 zrxe5Y23e8)Y+YTohsq;HI1$WZ+q-&o27wRn!3Nk`jWoLmoAZ3+xS9+{S)rskz;ouz zWd2zbAr0@D?GA+Z%!vZNaU@;tJ!E+u2DlHl+nYR=#Fol6_`qz7~Sz{xj{2*T8;-U-_A`o zCeUV}XF%8Ex%merNAZMX*;a^&k^7hZ3a}}H+8|p|b>?xE$qXg#Ec~wm{J6saD%{xH2&}i#gn)j|r9h5brINiWQs5NbQ6~cX ztC?RCprx<-qzT26mMlw7z0Bhh9h{CeBL*6VD zUIr+@z?UMyk_NC4)Us?pQOjNHq9VNpjWdJirJH8{P2fkbshHQ|+s%|bP)QGW>P%s7 zq^RsypO+bSvs#;_{+j@+9$tZmof<^Ac>%p03Jt8m*&CUI2mMJ{P2MpQ8R@O^<0k+1 z=U)WihQ41|v9IaU1mdegi-LT!}WQx^TKOyXi-OAw?~llxBvVvg0%Dibg3VUZ#5cP z6H+>KxNm)-hIP89e}mWm68-o5H1w{2R%Xyt6qIyXSdackc{;?!@fKAZ^j-k|oR$=( zO|yxQ0Ih|Q!A2jOGyz8gn^ySez=#J-g{zkCbHdV8(|FgK$@UsSut>j2$$(kXQ&i<) zi|?+5V$kQ zhBnZ3bU9cZerby*u-5W-$5mEx@qE^{4~3UF?#;3?4A{ZU8HA_&O%l%EeIfO57;N#D zobTn|zWmOg$Qsd^FT>ysA$?e#6AJZnge$% z>53#~c9mCIOja%Qq77sx!M!RZQ@J2jz-km4>wAdj;7y>=K{Cp9ko-X7KHE%*8a>W` zPP``xeE=j=J?^aWYnRX@fbL4J50@XyH-8ly&+YU9vP#!AnXqcYR$0 zlTk)$1X@&6fNQm0p!WOA*tl9Pwh(D%M$@fnfESR1DfCa1Q>#+xb(1dF2E&q~#mPy` zOipR3Q65yr#?@tURY*)}TTvO3t6=OEu~_9gz#3N}ZN<`9P^(J(lK-#R&r^lOlSHob zmmt+|JZ-v!Ekl}lQ6QDS4oOpS>f|z+pAK1uw1Xob$76W%N~T0hJQ~7dSRa6@J0CvJ z06nJ#jSw)OI)J9wHesDvbvmtpNf<P)1B_VTXzNP7ope>TV5@oJ@^ahQ6(`*bI6*CqXL>w(5#cG_RI1 zGb@a%%DQySM}i2cYA*soTJAFN0+q;3`fcEyGMK33WH(3x_wY3h!DxcOAR1d451N1| zbSP$!XtjP+1)U(uUeIsDUfr%{T}xcrgq=lpcxSAtW3(KW?8ZV>!;OinV6(38n+OVp zP!l)6#$9W&I+NN}!4$l<2_;iKJEQ-5lPVQjAq(}U`5P9 ze@JvgZ($jWS)YrmL+D7kdoHs>mp@pzvO>5o+(UvZD2amN-#nWtWjz*Inuyz)Vu2L`uFU&ZO1gox&DSBjr6TqRZ5yqM2mLqmp6`EFZE=E2=)0P- z6K}9lV-%dg4@78#52&AVX0Y|V4ZXOCKwk}h;1{2=PGH$foa<-g_zSAsfTeCFJYNcT z<|US-SaOrB$~-s`i;>K;gfai~I|?F1<+ld<3?g*p$7b$ZwcSHph}l2UwF`R?%-=*L z@VgR0WMJKjFTQA<&#mBnMDY7z(*wTabV&i^iA8gU1A0}(1_!Y7>D=?Oqz@f;Bt-q! zcm3zo+9(BBx)Gyk-@#3WK{Bf^g#fd&KrJDAJ{VStq(t2>x2IG#M=Y zv%tC3Ye{L?qi;mdl~=^ZGJi1e7lpu?x2VLKRV=beh%d6d)3l zk-rQ$;JgFwRKQxCyNF=B7|I>1QBbzW@1wwG|t0U{~fP4i+`J?E~&+aDsl<<|w8B6DtAutt1m zh%Q|Eg1<0jR>#u;@mG!s5@4e^ivvi)nkWGm@B3O*#KQGm2zO5?B})cbwTi2)3a%ar zGDESTf?vSyV%tE3w=9_yT0y7D3&K^)w6ojLZ<%W9spp>a$vC9Y&pdWm=Uz%t>T7^D8DwioU@rn6eiM=s3wBw2E$*xUUx2wx78$mkf&|)yDeVnC;aexb z3Lx>NO94(3Xhoby`0wq0AU0Qz#q9xuek1I-N^6%lup>GmC;Y z{c9c58p6O#s(Mt4(xAI^Z{H)lRLK|=OPldl0o_AkeLpNHkVICW7?q|M1%ZK&t z$vU>NisU1&p&bBqC9xz~UnP-PzF{lemExM)aCX zAG^1s<`O31(I0p@6~3;0WfhiMa5-N=HM>H4_E*9!kc=ArJ>Zij?qw4qPHf~1v?>k^ zl9(C9)?l0m%Y9lLY$Aa{?h@$ld0z%bWUk!g-gzEb-Tty{`AfcHC=#6&n~w6I=+>}=O)_-fHkS7Wc=q2WB(KLErj5aH*zhRc2nc03O)6kiIk3vjcH z!=V7O=?^5z+qrvX%9fVKX)_Bk{mJYnqcZa6%>elNe(KKPD%X0KqtU*vjRn{D$1Qzv zzRBjd$4SK}Wd4?w`jl|`(TB-l#nP=SL#y+f{ zmUc9#8xm7SEU>e3rbGVbsqd_mS2s@fiDLht!^U=ZSu9;;#cD&?9hMPy0&Val#!1O; zR$}r*;85YG@A744p^`&ksUq}50HN60Gt4f)2SBXg&6W{&60;Z#f4r&FRJ5sn~k{OLtp?+xJsEGh90ND-^a`wm&+ z8})HN-F#nT7>w0#w8v~V&A{e{x#-(65ULw1l(1q2> znbCj=R96&JSk5o_%o1QDG?u_vS<7xeY<{}dYjws(ttij0v4?y^&0bASZcJoeUTN;^ zBH8aSHq^Zk|L(Qw3o0@~VPowqa8>3Mnu<{>USv-o{s|ziX5WO6)F+SP6d;ag6&krk z0Qx7WEMME+I!K{vi_IAl=2nlkwb-iF+0gQzAC@N&HTKk|&Z+TsPe&+-EnUzlx76zO zn(lanY{xnIt_(w*K_QUJxTZWE-ZjRgOx6jXLk$Lz)$HR_@e@bYWvmsq;3Yts(=WHB zj|NtiJM3XuPT!W0d0zYKnLA|LKkNyvE0=cnxI8a(n15Hq$0o-`Y@!SUU}s zDwE=%M#DTB(A9=@==U`C=wJp8I};6povdLRxTjrN0sn`yi3@XG;1%r-HEGq{zZ9Y5 z$uOw4cncj#aoxJTGtbE)kKBRgj9e|PppX=8427ztGb<-V+eOi#*NNNqG*OEF zu~10Ded?{YJsQnjoQ%MoP+TNCnzLECB}SyM=k%^kwo{Y^ks^=T;SR}>ZC*=%O!gTK z*Uo5VR}ewL_c5+Wh*;JVMqIe;#l^6Al$CAwLyQQEW`AsGp5lif^^Qd`zAyzmDSVAA z+p+^SK}gH)KWt8bWtx{NY?ZFiHcoqeP4{vwo(`FuFxxA=CM8YNTyzm$@;@$`_9cYAcolq#0$fyKJr_yd1(=gU zR$NXZ^!(DF|Aqh?CdRf=&`+X5XXyJb zFVjaKc#= z)t{j8Qjd?l&V2>m(Btfis#a#WkQwq*84SzFM?)8Wn{b?6Hydz|$4 zLQ^gK#^e_Qv`u8<<@`Psx0UnM-W@?iG{)+hupK&Wqmj$3Wnr=Nl+2!x|B|2AHQ)Bm zp^jorXq3yJ&}i5O9TL@$?FEcY@NSyKa^=5si-by7A)zVT%V#m=5IGAiHrwF!B(2(t zvzjxdvTOW>uD|0Gn^;n?`Am6rb7-t!M;gv@1h3=cv z($ZoL5j3|;_6nEwstIQ{w%1j6E??m(CS|U(UDBht;PcmC;sh98hJmvIT;!aZ2&1U$ z>QmEjDvYmgy^BkQv3L0?xl12(xr7wqS!0z|Xixtkv z^8q(ja{L0Y(Fg8vK6Q1D;}$F)qC}qT7lEaUAp#ZMji&bH_06-HvZn(y6_gBJx;wZK zQ3yuNtv@6dNrN2E_*erk>ohV3rx~UZ7MIHE=gl6_E_0X%*j}e=PF&Gr;C7k*{3h(s zUSOALv@N9fZ0%H+m8tpVrrgVmXdyJ%ES4|x-s9;L09RCmyzq#Y@E3k+1X;>4#7|oz zHk}efEI^u2wW_S{k!>cg7kh((efM9<;hf+VKo5qwHew3@j{s z+0j=rYg0Jkt257;gC!wZfbSI^z$y>sM#25h>k$S-P@&u!yhh?i&C4nfbqxqx#sq|r zplRvtQ0`*w=(u<9PZNr9Sj17bQLWk7=-OnG&qEvy5NDgm2x+QjqpGc}v{gOViL7zE ztFfPGshO;*oUDOidSU5RQLM8t!GOdibhJPe3uYAOqbN zVR{&_FyM0y{SM&M#ohb-(R7IJpQ0@l5U}Z*H_)C{N-xvphDJ5A(b-TG-Kf1}=3V-S zdBg9^G|KG!_2T+-0cA^8HVMeH`JoR0+ib9P=hnNiZW?wt3BSVcpqg;;u{hv~1E72` z0RrtG@HwKY-lWvATHsLgMn~)Vq~tCgYMgnEo|(O|S*dI?s#<{`E6{I51FHCvxAG#5 z4fqf?kTzO!@}b<=zk^iQT<9NE$7PFf?N=IP8AE4_c+f+s)f~Q8TZn?QMP1E1(vsHU z#|5BwY>sbi^JUVi_(d^QErm@QfL;xchRDqZSv`3*rrMJmF!{@Q$aZtapGan6=Uu`j(4OK%ea|$b z0gu_VmDV6F06>cp_2j-F$A+bX9(RcoeK4O~(6SnUJezusV?99%=ul)&CcA0@? zLU&mz)QAqA{87Ffyo)@jKbw+BV)-Ds&>qf*mH=8(U&ciVq4Awu)!Y_5f|!0UUVFMd**%x3De1BTV9%U3zN3TlgVwHe05eursTt6O7& zR2;KG`Xouk)#*y=iUw+hK9j!nJ4WutJ@E~;#H9F?o(&s@#x|glFI48rQJZr@tEiCm zH&@i>tDB3Nhd-#WR8BZ+#&jmtagE0JeMM6qDr{8c*H^R*K!-Na=Qo5t8PrO+bKIeQ zHAopsQgQMh(qd|s^xg%;>?*X1Om%i*ynw^yF4~VU1)1zUVFm%T75%Ni%!>8q&Fie^I}v*xl^xkQj`{U&VkR> zRIab7NR2rp|1wUtba`%SzG}#ud+8++H)ao*iabSyo3a<^V;HEYjKI)T3F#( zN$~nnwj1s}L$V(5l1lQa68LWl^8@_^;Rop7ICXBw@+T36JOw70}vO7oNS|1 z&SjuPRv{Br9LP$fw~-5c(=+To({_!nYsJ5xa=!)4)T)ElJH6>13QxOj^!=N9XSQld zxyqr}I4G*pjDD=E6E=B*GA7$hi*olKS#)de+UnA#(&B~^Q(sAE++t{Ie^A5R6Orvv zmAmaF+Qzg`gdvI&8sCCUOK!o$)-#@UJeTI}ZGkM`MoNLIFtUWC}P9q8+cs8s_5*|P4Z6f0YHwTG2Fl#5&8~R8Os<+zr zPif~uD9>LzIDSUAS>GOUk%oP5Cyx{9oduQ%wix|67yPWWA>LcCz=o12HJby3#3D_rivZ9U6C7ptfX3a^`eFxQ@tmTE|FjiEJurUx^ z6C%AiY!OYv=IG=$&ALs~vR};(ZS;bU_NkD4P5lzYJloug9` z#w|t6!|$og-3{r=^R~g`d!Y#`WSFn7)hlT^ihQ_tWGFy4lUniDWf)&P^ToMP+#3em zsO5aq!-6;m)D6Kp?8MM$XCPyA`diLl%9qw{j!BsZcV9k_dPYX1JRJHTDpOv(0d;s+ zhoOMFtw=CVA|s`&OIcP8`Ym4;k7ih^t6I8lzLO_V{^ zdr)*NFA>2iW7|;Rok1)iy&M}&Y@H1UxBUFWrljtg@r@|O z;cPZDca5A?)}hsPlo`!iJhqK?MQTGi;32712YBC6ZFn7Sc*(~7qARt#;$JFi|M%`y0)a$54P5tK!$l?m4tH}-E8dnf5 zfj6K?%v5H=5-B{h7yAS3Gn@AsHuPO(zf5|m{p#j*i%;b))?N^O{Z&`p7`>@!p8GhN zNN2!(;0*zlxLbyeW-)+@3*2xSh6^ZqNEEMt6Oos!tAuTW%1o#q*fJA4X81~>M#F#XX>4np9SV)7ulX6RX~%f{lFR~42GrQNHk$>MnFlb& zzE|NXw4Z-KUx^lby3P%OKJG<4c_q0grR1pep_4yKDB@qRHTtguRT7{^7I#=mSX+;) zKJ(w73`M6yO&S&tP?pGEBK`Uy%Ns-ORju(!U4~JZRQmPIbEpIre^af2nB2_E_>!v!(8B=)D}M|f>?gm&uRf;>K38Ecji4m@P5NB3=*hRRCT;bz=)-7Bw!Q^ zlcv*w?^xPmnu<6}hCm3Vr*nS3K-x%>px&oW=(AwT=FMJ&@(@4MnJM>!hZkC{Dbllq z^+iPE>CXL*J9sbv3h*E?6iLvBk%K3wlN%OxS)CFn`~oX;PjW(^bpe35tK7((W*Pvw4YNlg1oZOl{+=m7p|Tw? zkY7GCwW79Y%=eVk6SyQ|<{9TQIYqF!MbxFkFQciqYFKDzMs?23HiURa5 z3G6k(_{B+5;SN+{+{8D)HC4bB_**JO0&P1H6#Zu;9z?ka9bg_o zuJMND%&}&OqWKAk@{7tV%J}ESL@fV;`B&60t4fK3!M`zV)18pV0$euOnKB44%`Yoh zmIlvJ%ZpY2bWJ{)CAe0Y5nvuoP-jARSf>i&ortjrn?Huz`Fn@E5B9H~g>@eY zC7hCH1#!;h5u()}FOihj^NrwU;jK9*1KcZ2gVzZSoKO!-=7og!B?Ek`)1$*2M~ej!r-%0rNVDown^J{C{dF!h>Q@jV;+cRVgiAu z-+R&bNN(1DQrElOqpJuHXEE>5Xhc(Et_+5h?0c&;nZeCqwRS(Dzf;mKrax9mr_3ZU z+J=+D+yw+lVtGF=c@xeW$C;nBormTykbJlAcWGS}I1jyo<0EvvU)o}<*t(?3lHsAg z(TaV$<}%i|8QCCH@g?NvF?Wd#C$8cq5n{kPwgX3CHD!42$7lsL29O10eBA~&J|te2 zNV)~~TtYGCz^kEtUYWtQ1)z=V3?l~LkTL`Hy=m3zZM0sd{E$Sop-te>u3P+Kl04x> z#myC?!)SO5=?ZwQJ>_b(!%?+t)rv%C$uM_!Cjg%sh~oxKfMgZJX`sah81XPUZoL|- zk@@K;?2I42y;-A{pCE1o+aR#-vM@LoUWi*E)=!LYL(!xY;OB>wnW!K4OK_;uXJU(; z%*n>L3yuYD4!C&F?S4YM(%(GjZ;v={s}i>YywaY6`zCl=3=WD+dUDS{(1(DhN%uS- zwF~SHn8)hGodBXeh0>*gO#vuaC(?I92f*I3X8=H#uIMv_={JY1 z9Epi@fW`n9l!#i+S?K9U0D8QEoA;?%KP3Ea96Ub_%N(}NAZCt_$2?Kn4*dlfiD!ZasBt#A5wOXH zT&OTLMQAaY@6GzRe}au7p1~tCavAtoMjdz=vhLZ9@L)^Iop_Pwv!P4vi@AlJOYy!= zqCH;kOwRgZcvz^hXT7kc0Ch2FKr`q-=xP+%TfAL;YS4#_If>$AilzQYQ^QbnFiwhs zpKk!1it%#ND(aQMxRb9mM6{{j%kLx&KrzG$(lkEeMBbH54j5;VDiIO3^XSHK3r1yf zHbQ*?5-k-arnxLL)Cy6|UBR0}`HKnK6w28qcL1a=Tt9m{-2?Mo7X(nCngBl>e)tf5 ztEhOuJ*UH7%G3n$-g9K`kTW;yWc(UCu5_HYsu2oh6Jmqq9hp0XAClSLWsz`l`554e zWdW!2PFoTL8JP4j-oQkRHxLE-1K-hv=?3h;KMYbJHIzlUEX4bF;xHTroACwMkt|m= z0Q}`pdBp2%JiI2@9Stv_aU}$~7hzma!(lU1fF~oS(DQ%J z%S0KNz0fB5X1}R)z39(`(U>O&n|oO?JY3n9Ot{QJVSQZznnn_XgCe9FlXT`Oflo-b zm88QHwT24l~GDC@c%KWs#$ha$}FSy|9lKXUahxVbu8*LLtsu+3pHPmj+h_1@l z8_t1h%VY9qm8GT1XChcmxrX?ycs?c9Cn4HirsVt+SQifUfO{wcT{yRUYaz6_MVWroAg3l)CG;1ZDtW#k999Zn-hge4 zd7Q)S6)HKTTJeO&in-bR#eFPd$gf}sf2Bz;BY1H$$2ngf4@zQPrf|%K)3#uElllgh zCqZiA7bU&yaAi_&b>BF(__9X5bFIp-sjmM*w?Ug*-sXIg$&%j7eAM~+r)8#`46U_w zeVe<*LESt1^vycc`qm{0Jr%{qiE8&&o3w;#y0EQocUH8q05jU`$2$k8<@YJ`R~JSl zINW9fCATz^E(SY=BbBhz%l|%La0huD3vCy`b$47{Iq~iR{sdLLoV+3KC6VGokQM2F zHz_l;&cW%J{BoN)QZ8GPUy_raovT!4WhhJXm&oN&CYwQ(Um8Klv(lmmYIkg}+TiXw z&skZiEN^q5DBqjZH;0&)$jd5P64z>%S^`4SiE@qFq7p5sbdZqvdl5)x zcVIk&^NBNwdji1?)~w|8B2F#9&9L1w32@ycZX~J-O;E9A^c-UHSYiu!HmzQ<&R)ME zS{l8f&N8akkD43C6Ql{_4d#)Qmd=?6@8+R!LTJ1KR`!2?^PaGbD(dPj1d^h?Qd$k0ioghUAnp$SQY$%c;uMR5=6t1ES9 zQ>x063S-rkiHXtCNlDRCeQTf6o|mt)8HQTmBm8k9RNuL!ZpkmDw?ozLE(C%d-Wh&l zA4p%8EQ9YMhbl7Jt$6X7MJ|icK~4d_|qLCH5?;9O(eQq9ZZUOQWKrqX>L8`53-7Z*(O7C^8z(ef#7?)cex!*h*rF z|44{D3X8FXxh6nW72yyp(csJRmy%In_zw^{;D1m}o!^EkkiJw?dE@oJ#+$JUJEjNP z>wA_ODivKGtE018!?j6XjQKF@|v0V|HefEBLV z4rUXaN>-skL zW)%Ph@i!eM--IjbPU*YEq(TjLT8)a7c_jhgn;U5=hFv+BwSX>f)R}Ti^s4$G0D5wY z&LNCOXhGgXGrWfk3O2*|X+b_e4?aH?TC5I13-0kCiCs~p3L=K^j?O8Dm82B;uA}6; zPK6re+vcGL|IMgIQC$&^9DWc=t9WvpJjtl3k0es*tH2qnfEseiuC+c1BA*Vyr|`;S zgu8-hA55|Wwt$c)DV0~H+G#CSl$~D|Z`3-iI%STj7GzhoRr-q8m{Y2)$WwOIC8%R- z-0~#`2wPC(-0jjgpn^e(PmbqjKZ;MruY}X5l}JB+opCACf&x$?wl z=^I{izS^KEV!x`|5ov6j@Jkk^4Dee-F9Q71Sp3!j-;+=K;8Owx&-ZbOVle!uVt`iE z&?t+yP&5zp&+qsKnkc+ zZ-lxAG3MdFGZYbG=s90+a(OD;4yizoIup=CtIUcJfsxSKPZ?T8bUBH0DF$-y@j#bM z$tr~DgC)cMlRl6b`0w`#e=nuR@!xZwAB7vtfNkv{nyCrybG*~TKXcNi&23@^7o}RwRd&;8 zN8g&sjvnZTx|FF#Z&mfQcsmpoa=k`pmfh%VZ?GRmrQ^io&n8dx&K!LbjYkGEyoP^HN& zBQ6hhuD?B2->b^5DeSYmdYe7&*3m(Ct+u>EQ#ow1tXUClEavA^!aXrJ5Llr~Oav=_ zdLmd^nh_fxW@yF}ca!%qgFMYl4&vb=WJKvsfVs0_<6h0$W~0+svM4z@%HEZiZ7_ud zx~jJ##|}U{`;P8S&0M>+3i@KHGOEE*WRF`hVl|6F2goIn)Cf?yfP_9CW}D)g7N1*u6xx zSGy~-GBb+nChF|WpB{6+?yYBAma5EBBzy4?N@Q+X#dl?Zo4lJd)?A#Go3|uyiAk@q zQ4@Oy?Nc!9&~t86pg)6eRYJW9_6S(T5)ib6^DtNn!T7<}&c82eGu3XWT6+mP4Cc;Z z^y~f7zgBE$jb85P$ZlKD+8O6;Cvpgc;UejK#J=K=3qFmif82EamK|4cXFYs{^u67C zVxmX;+BUE!?H5imOW&XOq}|V&Z#!`Q_1sBM-XVQ|&zZ}YjrX^6C!M#AhiRMigLzN- z<;&(@Uv>3u+)2+pD*a$DoHWtj!Jj0+bmsymz5cfO(Yx;c6?f8$FTqJ?!AY=&nZ@pa z0Mq-@522D(A`u2YiKuR(RMPiN$DTa%rWWp`!6%POKV+6IT{oE3?q!ea*#P36+#ww+ z{Tq9S@$pJ7g5bYGd|3W*^B-Tm@a{J5wBZ+SlK$Y75sU7rGKCQyt8M`@Bi?Nr@PqmsF4pI zmHz$gbC#jVskKVsyp6(n_e(#9>G%LBi!Wx`AnM(NWahQ+-uJ&Rc5_FLeg1pt$LH){ ziXvUTMM_v4LG1IMjcOjo&!xDg6wU<3#Y7Bo=r%-cF^_%vuRJ-52lX;c- z4>QXgpWx1(MrxEIJzl+|a|v4H8dMi|x!J>eH{v8Ker56=@O^lWBXEZ}{Vddez`Tcf zjycYJ%X~1!oxT&9QJ(Y*+s=+90J)~Pi-Wv(lb$>KGVuTTv%^7t8(MRL`7GuUpvpg( z*Eeuy??$z#Li%OR&dw!NglkA!%0u3>2~;o(`R}FwT=4W4(Z)mObC_Q-uP}dMp5G*8 zAIx6Vj?B`3+IMv>MiH)IU1>K*m!8eS=`+%=7Cil1wEGhCdCZN>v&_rPlUumc&q9N! zPWqMOjLyZB!Zlo3#>3vb2`_KQR9BQr|FzKRvP5+7a`XAj5#~3{G3Nel-05edNz^6% zm+OoUfZe%9U*7HF7-ln+wy~#=O21z4bQ3yqh2=u#AafV1)xASDG(ob~87?`Pc5^PCpNwgEmROaqsC|M9G~a22~Fa z`xY=&D9Hdwz9*=cA@-zz-d$;?G!~432bSoTbo5a#`%X}ce22Tc;pg(G9?2(CJl_HT zoFY>RI|!6h5oL=XhhNgvWAGpNsdPBae#QjlNqnVAX| z)s}tf4aK(N4~wgdufIF{nmbCWOFk^Il^prx!|V^y@9(j^3;(g)WBCC7W4Q;uS$*~1ww3{_JpUE@nU5mwz@ZU<%cep~BtNYQ#_qph?}-Pp8f!duiB zfszuk>#EgF#T}iBL0fKh1oM4&RU%SY^vnvk&R~vbzLv-&nI}(BwbUOaOCWlOJ$=^W zWwS?MBFkX$5G}YP@^}g7q+s*e@21G(ziMbOni?8RXa_UmK-VzSWx2WK<+-_K=r;Hp zBmC+r%T<<_D|5@JKh|3;_4rqW&qO_QpITjzuhHZ)@4!b54e;@!8V&gbZYb;IPt;A+ z^FTp(cIis^a)mhNo0tQGhX51}I8TW<_MwK!#8}JVWEjfg2lQaCl4`#fNh5|`RRfyR z=CathShRX|ZoRg>Q!b-y2pKiymB}gSOy;t>LT65;yVdxVY|+vfTbruiWa=)KC7>*G zVXmW~t2!xJzGyM6HC5$hmZdSZnmR+iL+Pri!9g>^cUuVG5w6>^q$~<^8~jb#NjKUJ zmLkMRV`8+BNTA~5ViF;!LaECxovu0eoO8~tnZA^2MMb?4lgxV$Jiz>QQqhYFpMe)U z3;)*1{@W}HMj>co)D@Hx0+%uI$a(3s{k(I|InO?Q>1)iJy^2Yc{lEh#Ycisjc@zGv zM1{uD4b*#*XaFx!k*H86W@;5mtx}r^Zvvf>b;T7~dt%+o&&oM+dG?vh-7(a)mP(za z_l6q=sw?%@zU!`oGcqMxsdQ>H@Q4JmQ<-WUZB)gpsPy;k@4aV#A2omYq2(*Hb<|1FL| z?1t($sYIfMFWKGK4Nn<9UwX0&jYxlmKS#igXoUHy2E|B!HFFQ$f@Om7R^?lwsv?urd%rg=BA#NES5mRK&wV7?2-cMV^Ml4+UD7? z!6{?@gv#&IqrWj(rn~M!zhib|c)cfQsAB35_{LCrG>X&99^1dz9@Smb-qG1y*^yX-enVZr%w!dwGdy|Wra^~yq@}UDG#?$na8(06R6vgu zLJ{^o;~*|eTb$Ph=>so56R3s5MTGkDc=WQ@);oljZmFN#S-!5}%8fhEADUWhV;;0T)XXuED9&iqC z-aPCah>qzn)HlkoZC3JCbk`lH|p% z_1gLe(9*5->von;G+eo9`@!K&i>i^gzn_q-O}*E1_}Y}E*7|)wG4o@$Jo3AZJ!oiS zZ*$i`winbIh2yf4{nT=x3D&ikec;#r-rinnd3QHc+ueB2$BK4OuY@S6NTdBcYqY$-i7o zVJNo7|HUgN_%?;V+?Ml#u!2(LWRaVR!S}%hsWfWp%hx7Nlcoxz)N{FcSmQmnsdijV zCsAoD^PL5~51oDapErH_O4M;?=HSe27j(852Q}SwRl|B|+a@_FoU5{lSH=y(z7t|8xARpD$iuhws)+*6L?(sjc%i-0*E@cf(D$puxfFwh8Lk zK%>1SYK7fURpWcuZMArqFY(=~K^nXOHUOSKMT{hlTKFwNtkh3x3f-Pjn|jWmdq+*p z4)@?WskIrmpEYz#1Ylv5}vDk$fZMc-f!Mj7BfQ(#56BcTV^j=6fyk z1pEMp`(HrPW`w|9qXppjZtprSfhED#=F3BO>)S~S(+V9s$m(V5gS zpBdFN_fyB1KOo$`gYl3YfqwydITn8U2!IZ{5!*T|PX2@r!Joj^L2UmxyJ-o^LeQas zmYz|E-c+T6A3xqWG&N+YvzAi8fGiBF;NJlc#)76NARyIisIG%~31Fz`K*P)p$iqAY zKs@?|!NtI0(z# zkEs^`Tks-S{q5(;rC{a}4S@M6Clmeax~{J4hKI@Tb-SI;-F58m)C;|E6SwvD-Uj#4 zYsH*;z{UQ~bLkR7nOK(E>>C4vgL_&>oNw&dzH4h67z76npd98e)UgeN!;=P&!{uS# zBT#Ooz5ys>L8eM%N=b9^!L4(d7bKlmo#^`oBw?n3r;0hpP?-yc6paxzX%l9QA1PEK-P-denIQ}Nc^MVs?BV<-du!1~t#7xR(zFY!^tMashs zX)#8*Bc+2(DjeAH?Qa*HzgV>8?SczeSy$6VHk|c0eabwsW3kGT&Kv2V=fSA+P&)2? zGwoKv)~%>aAx1a{w!!o&s~~J&Shs+UUPHZ+ey!Erv6hWBC9&bA*VsF8anU?Cj>f>g z=Kh)qYkKe$s|9%yVydWEE8dBH;1O#m1V#o{{b$vx-v*Nr!?VVzEP0)w=&yGX8-Vm@ zRC;ImOA%qM1CV}gthgTg*9Iz!m-aT+XPb$uQJ!Fvrx=-fr`I>B``+fZ^?$O>gyUyk z#(m8CiKCtvik2?nT_F{2)Z10=R+CWU zL@xXLtXij(6sOvv%-V#630awA#}14g6cI5fmUr!)wz&Q}E>k8?FX=r!sYh*PWo=|d zLP7X+m+%pz!cPv4g6s z2l1|$_)DfIBP;w$<@&VVJyZ^q7uAyL(j9gAuQMxqV_wUQrb9C#`bFS>#?Yo2E$5BM zosyh9C6{-t9*~d~T#=q$5uBAUpc*-%I*puasDm>~uB)G^mC-*q<(Qi4`0t!zed{~^ zE2~s{^h6!#H5JY4;RS&l+HBo1TyXj%qvK2V)KL4(mUH#O=ht>OnBrMf->}fWQJl>* zg!c^(%k0@_w5mnYxWtamDT4yoM*#zplLlf4QlmjA|BZS&&PE;a!Gvx>AH?C2&W^Xc z&zqN8P%v~9{~#vGsR!Q3z15?`z<~zryP>3}-=yTY(VHMja`aYJUrqQ@{=em@S53yK zd5)=V%Cf^tLjI#noz5QLb9mqQ)WE*+ul=iZsd+XG(uKJMYhjdMen62!3K!coDW!jI zVOd(5V|5x2m{M!n$71@GRt+=Zkz~{M)J&=k_G7fy_^)!`2wy+?e1yd>wy#>pcXD>_ z*wNX!Q=c?`#l^+R(Z$7aP5pI_CgSEVO(yLvnbHY7)W7BE+eFZ)PQ#|B=a5c{!V74! z)VYEDB^O60N$0^j4?n)Nz8v3)vL5Ux>VfrwyXqQIVS=%7pQfc;xj&kI zHr-X&Sg+i&TCd!y?+Cx7BewDYwaR*kAB~Jrw|M9)R3y}#~#xN;f2oo(AY!0!Gzw>#WKRlaa7%f`q$B6&LizoQrgwzXH85>nwXU{Zrp&F zL6MPzpx&0B^4x=K?8Vfgl*fyUr>Atsi(KO)DiRYbB6JBU1IICELPn+fpRC5)BeR|J zMqn*>w^w$_5Yu_?@d&VKt#Jr7m-{fZ9jkT{<&r}GcD8)YTb3Ryb9A#LV^+_|?!K`h z@fqP|`GY4nO*R&$hR1e^3yVt&EidL>2j@qmLjmV!wzUJ+k_-gzis2-QDzY ztUuDmoA2Q}%tO+!jIpvK$?b;L+%pv&dB`MeFBFz>kNWF#(lV#ivL2?x{n4qJ2Pj}P z*;SiLG;-v zQ!w{1`LfGoETiTrq^0!-9+$-W!x>%Gfz4PvKWFpi{1@isY}$6?25ZmkE?+kNfQ@OC zsW~LX32CrKkn17`G2r9(a&dRC`ZkEiutR9}CcGmsv5&=G2IfQSjuEVzX%_P^-DXws z@iApAnYw_j0Z5~^Z7?c?07L?K2t93?@ zTNHd1vCVBeOek;*@eK2fD{9VB?iBMfZN6}(5y=^yYkq2KrFazKz9?lhN*Sic$0)hV z_rZoQ{m!7=N^q7XbC!J*8^S$Md~Ahd4vLD{PsQ|(c2(sJNJt1!<#dhqRt23|5RD>) zxJ3?1Y}HuO%vN44vl>g;Ub6f1o$WPp;hI10jh6y%7?Gu|$8Q?Q)+O%Ol1Ew@hY&s}0 zv?ydmZboLW-XjeoM>Tn>y86i`Ln{nby?bT)_vtxUu2cifCggE}R;-b7v({HOJGxxM zwoPOIcgDcs&Na?IzNdd|=Qt0q?w#WU;$s8iUE_ToO-u|5>De;`O3|^daULE( z>z0t%EzULG%`M(FE-2~A)cClx)cEcx;3-BltcLfJCI>@pY-!yBuF??W3rl_uu(>b3 z*Z%O~)We5eHoUm0aO>85RvQ}Z8TQdfVV=REEZg)V8%_1-52;-(r4~KY0gE~Z$Zr2M z`X`)C&(_~#6Mz+J?!%k%FV)G7mu2hGGJL9I8>%_29IRNaworX&;IeRm{W!GW;EDDr zwwW0by)p+38{Ip9{_y^T#y3qhWTizVW(}wuU7YuApPaCC?>;$MC7I>D3I-QNr3MBD z#B}IXkQFzeX+(Liyun3bnSuTRaTFBWuSHd|M~r)PayoQ1P*(m3^T9V}Q~FM_Udv#% zvuNc-gtm8RmfYPq7Qwo583L0mtrcn6#fCIyAMia#%%0+OT1h z-_!rep0C~J>)8g{&B2#;1a^AYr_`Th8xAjw{>OQd0kgHafzyHX8pq%FUa%*RQqv@^ zxZB1kUn8Y&D731PYJ|I1?APTMCNCdW`b>8AGo^~j?d;HtuM=4SX#CyKYvkK>B2lVDbeP@{{3uEa?HR!egW@NZQSt^`UJj< zMtAJJ8a=UzIud7#k?cQzr zycbH>g$;qsACw~kc&+9bu&VJ8%hs0pJecI}>E6N3+YoI?=^hy$8eYtv3G^A(Dcrqt zXJ4PNN=wVyGjhZhl=O9*xLKtfFEq9sC^<+;EPw&L|o(aZSgHLA< zZ-0-72oGPc&OMq%8WVc(gHZvU{hA0nfA=on#ZQK9g!4Pgs<}<1-S!h)iN_VJuuYt zrtmV|9x-~vP)6$_N`C{p2raN~kso?Eo0o%D7`qWSB(6#Aq{$Pm^J(?7nETj%{l_AO z2Mf@q;}{FA?f||{AH-(ktN_x0ubv;VmcdF(F@|^{MG%wP+bO*w`WI$+`h;{#3+xsc z>L20UVNwB7d9oqei`a*3ID_|@!;Q&auK7>lVeU)wPDlWAEh8)jPYNHjYX8vVig zl^<`X1_AMlEOxq z&cq-G^FQU6?<1vyZpg}1_ivJHz^yrrkCBob0KE;ULH*D2Ov(}tYF^c=`^Wz+;jGrQ zGKG~*1tVU|2p#_Tr;<*k%QLKK>Z%rvP&nqmSyAd^7u#d+fofy4>lj7OQb% z<^x(~@Uz-DZRJ11g;6tGh0n9#iv!wntMCC0;dR=JR^cNo;rza~*ed*}C0rcUKGIe~ zmauau;vpF=TwK3avs)jc1E)n`)My7MwOSJUr2{hCr{3!v#2pEOIUI& zOR9gI&ZJC##hU(7JmCWcwI2?WG+R-pt(9e#rxmiy`zRdpvE$#ug^^`gg;QN5K6rK8 zDxB!6@H*|fRXEjA(Z8>KhC0%I>n8@P6Dx3#Yay|I49 z`gK9%ek1UW_0;=AakBCy)@AK6%hBeW9604ZsC-deuAs1bj0)47)eBrkM=xKd7F&Ft ze>a@8BuxlR4IU(&i4%fSyVbwai_P*J9Dx%I?wVklmN|x81B0K)#NkUom+WD|GqP|1 z6VN5cibw{UQeTZI$%BtE#e(kh&|r0Cq& zmdbG2r}vih>tw4LwX0G>Ajw!w)80R3BxuKACq`eHs%0_)7Nw;=(|82*GENF!98+3W z6{DX3VVBGN8}HA*hm2Ze%vxc?*SB#E;D{ByT$s~$udOrh*rQufWZy~_&wIqj_3(>a z>E-3#BP=$NrHw4js|bl`Hl6h-O$g2jsOTG=k`(Bl>{aRE&?eM7JRvEr{uK8rAJI2G zIHjFU0XQi4&jm-#&!HTgpRJ)D{BWoM|CsFAYPX&4{}HwT|5jLq?`jCI!zk212NI*> zV|03@FGV9w(S>Z73!PNf$U@dfwT#2N^9K2C(dC^q&<8Oe9g)+Q9IvI|}CgSW5%RC$aqR%_p z^1m(bc~Q#L{DMbQlBVXRRkv#q7xC;w=WYpJt!9qwH6T24Kvc}Y@Q4AioYxJ1E-hnL zpMt4L**RI0V?x}zI{Ub`40Q9Ze`0P~xq*5nJ+>u!*_p`uLjGeZP zgQpX5lm<|qkB#H1%wWG{4o>|5%F4$D!gu4D)5krf3J(wR^7Hc=`^C2W1I0cjwdPctUyFieqf9M!%OPwQ%Ijv{i;1SzJSnx=Ze>@yDqB zCR;_}(+sEZkKns#rKC@1j>Ip8e+26Z;(@}S(|~_ayJUs`bp!rEy$@2~d{GeoHd&s7 z+EL7uazy9fU<7G{F(d#nP^bRexg$2C8uU{2t9trK!e-T4pFy&116`cD6`1TEI<1r6)BCyoM{9^UwF-;}> zm88b=(4tz$_3KlUOt%}R3;t;RQ&v;OkKRd1^@|&)2I47rg!M12(Y8w-G2HjFv_Vff zuF1JUu(6HUB!vq5ur|aBo1{?A0phUsylgQ=omyDoAJMj1;ZqAM{3F^|3g4W^Zh|Wo z{A1|v-6VIOQQ=QGAAynWePvf(u_f z=6%FjGsYLZ$0lR}+-R;2Am8Q*Pb#A)QKzl-@Eo?5$F652Z`HEM-~c z=cfy+g%`5*6)%o3N7E;Icw11j@ZyNJ*mNE(4ebTC7WmES_WuM=M)3Y$iD zxjx6pzSN5PlMVRCSraRKYIlWyT%*#G-stZJC#-~A4fPwi$-bcqdDwxQaS#p-)J`Q^{T4Z2af5# zJEneP`t1Jw=A@_3>DPaDx>_w6Trl@~<9iEAN)7r`>fS)Z0_DGb`Kt33QA&zob4L_U zbLsF{vC=y!eyoGbwBiOmt8h6ZKPkfcg`0**WfY~DB9*R+va*VlP71jmQoc$afbU+V zqN3DQ^wxKh5=~#DH0FG7X?^yDl;|%JPTvFgmPK7j7A+d7ok^yxl~SRsPCBVp;>exy zE&8duNIP{SB+}1HMKx^E;`%g;Fw&hBZ>BU&u$q5J*TjlugW3rVq*l5oCKr7uwe(ac zDb=c82etVP^(vE6P4(j9V6c)>=*LPf9MrjRPGV!7!463-VE&U_*r}w{WRm){9@ycg zaC?$Ql6HiX?u!Rk`1aZxx>OT=>SK!jLG6)7`m!{HkJ9v|QK^kNN9xLAE@3oULY>}n zp*zt86jiZhtUw03m$iwU+pIbkzNnT)RKnEA6j(mL#>4rL>Bom+s<1H__>m<}k z3AO@gTa?zyN-YM!(~)&c=^5F-Fw@gFBsf^tk%>-dfs>S=5P6c)EGxB9U#3SUYTE|o z`$pCZ>j22kLHs%$?N&P=JPkZ)Ds{OVJ`FDN9+>FO}Y7W*zs$TS++FO37dVqy5%e{AOD4}H!Fm)Dy z864V6w5xDOa`}MvhpZQUdwWa#I)$%)sjKgL!oQ1fE)I;MTopF0)+9FeAZjERd{O0F zvKM0gD90i-vvfrG%d!`KCv!FG`;-cw=qUUnIyWW0{5n?nNA%WG_|yv({z2S?#`;s& zeEJ&UgpXbrh78Tu%-L)(|BG~WG;HAF?z6x~nkW4hSCj()4tsI;vqQ4cILkOIx=Ct^ ztBtFtn;|yKkYltPQD(zG#ws$t{_CWk-8*&R?cyU!^14&)n}CP$R7=rL)siicrNI1Y z=t{K|(W;h=cuX^#x30S%g}FuT<^2!Z`&Mg=|51^D87=(&2W@_nxnWgEWmCF-;V7#` z4TH2W=mNoi(Dt)hZT=66f0=SQ`d_vG>Yr*{jkiIm)LjD_p)khLsEK_$eO(_WFCMUV zlCXaLz-o2ufYeHd5UZ_!(;|d&k%g4CLZKrqwjS0h9Iwx8Xmu~1sZGZ$%JHTQ3Z=R8 zN*0J-WJ4>@h$v^rb+c#B=G%+H3JXd45MP1#uca1;t#5>L6bl5xY`z8YMMWe>7R?BZ zT{^{~EExR^eH8k@mb*gu!5^dN@YF8B-MR&LO-tZIGoo#~`1^OUjm|(WKKv2war6|d zf2}okd|cX)Mot@-2IU;cp*t_fxtl+5X>OL&k~+n=J|o{48|&{M6XV~}%c~<9;U5(g z7!?&56y@pV>gq*?YUgTp(|$L9OXv8|8&pbKYG%NR$%!EEu3f$R42nrgjBxBi8*A8U z!(*nDjt^qpvBpHtY4b1ob5b8-cBfV?+qG!jPIR^D=IP+trghuqUdFDr{+@hSi)Kxm zwQ~*#Y0Ri-S5GMmz39jcXq%U%Y zg*p$zHIjvKaeB;ORxy9ohv+e$dfy=_wc0iDlnVzw&3l-KONVwjMBUQM2vc@a^>sR3 zePf>YQhOn?_vm-*afg;lr%@iiqkk&tw#o&Y`3?ItD#x({TpWW68uAVBv*eGL7C_~X z1}au1u>R&%rJ1QpZz)s52Q&EWWnPGRH5)S^zP##FQ7=!d=FW(wQvQ4$JTYR>!vj(5 z%r4s$9@q*HvMH&Zq@FgstEWo`2J*gve-nEO;;U8%1 zVn3SvY-1^HtS!5ss6>NGuy&O>vwf7a^#~E16daP26cU`opaCBeoR}Dla$~$?*YWm% zC+;V223G;;N)z_;SHrfYS2Dx`$ZX>)89lWu>d9)RAVs!l1gD?IDhG_2kK{RIs+ zou1Oj&+B2Tw7jLk5<`6Hoekgr{;I~`U)S)rys|-W8BEqLN_`()Pfuy+N!>hZ59kAF z*HpaH2GaF_NC61(X2>CX0$<70GoS*4U(KydW6PAL4W)N5|0!&+ZVa;i!o)fr>q2^F z1uuW$a8Ie*)58h)nsf$$J|ypiOT6yurcZsRV^z69lnrmye@@Z#b?$V_ciz1i$j`j^zAz@Gou8p1pg$y1q1@Ek1v~wb{R$|Kz}+z@M&%b_<;x z(k{g+XQEF^qm5u-+IIgG)mj+3~dVAop%9Zvw)J zn|ZFLipWa7A(T(pN)Bs`9c`gH5k_Ma8#W zUfXweX4ahEwaeSaM|r(AYQ(C(eOHZ;{J|diG3YzWTjGX>^IW`O=8lIhXjSTL^mLGU z+v0bIy_faLSv+F+%X#&mbsdqFF)_t+&Z<4`!xNImc(UcTd9REdzOJnQqLS`!M1=QB z_IaKM2I1xJb!dk!&~Tt%2x%`htnqUQ(EYTHz%eIvzs8efdGLo^rGhaVS=@vb4D<90 z3mQ|K`b6)@zF|Ek=7skSPLGQ3ADrBi`=$4Peq~d?<}24ev3Eq+u+08T2gha1$So|$ zdOWM70J)+y_#Eg6+R*x#(bT|%yTBt%9r4@m9nZ0wzu;c+Y-}w~t)I>Pi1#Ef??MtW zGO0D5J}19hkY?J7IeA}HeJ!6)hF+xTV&2L&gMKq$yECcR8NnFdrPyrRgm(QIKm0V2 zH8K4GCEjVKE77J?QTZ&G1?Evso_J&B&BmFDy=j_CGZUoC@~R5wYt+oF>n(C&2f4#- z54_KPNeonV@+6HNXT~p?+*~L2aGo}nZ4v_?Ogio9le%Aoq2IUFj`O)#^*ckJx*@$j z-l=UQ8XeKW@zH^wFp;fH$6J;M*=?zx$K)0Fiq7-yP!O4vSCkNy?bE(E%5*icy+@Qc z9^MRKkAwvWc1dz^i|xYhWF;l$M1=&!W+x_P$Aow5aVOZnTemR(uAw~K&o4AA$RDDu zzJUV7LYhM``r{J_s;yREK`$r8I~g5aTn&S4d-P~;Y~RVrW{@a}>)yfR!FGK1bnp}D z)CWB=hwSIRSZ)5KU4ypNPpI4L#8+)5&f+WN3`Ij0y*PpQqHy082kOwh9aF_RncmM2 z$e*=kgy|;B$q&qbify0YYt*P-(xP60S9Nf$p5DFy$JOf>)aw{DMf?5rm%+5*(%yah z_69~r{~b3fij*-a+b;L-_p7fT%!*9!>d4d!v>ImS8E6%(3mF>D);Y?TneTr4-SVZc zE@Okf{r2wN<;!0MCe5$k;wGL0eWg1{A(V?lb(zrz+EiBa)|)IO?M`Zbe)-6(IOeV2 z&y%-(Zt3?N2Z$alCTWX{qhtH_j*c$GKl+OK47Vjc_+@ZT@EZ(^9sI+(-yopZ&VLdE#i_e z89EsX4X@eQ+qm1L+7#LhwwY!#$L338FJqPQb>sK89=0{M%WS`B(y>W)lQ~WH*m1i6 zyCS=n?T*;Dvd^@C#D0f;eba=d6Pv!>^kOsLW@XJ@Yj&o&UGx0rZ?#}8B3dN0$Z659 zMP-XeT0GWbUW=DneAzOzW$%{rTfW@#NUOH3Vp~mZwXxN%R)<@iYxP-cLu=pGm8~CX zy{YwAZEV~0YV%~9J#Bt)aB~Q77~`DsbxOROa-(vy-!jbGY+D=Y!5?oquY#t=;K% zKegxWN47uK{=Q4BORCG0F3-8_?$EMBL5D#dsyftmc%s9m4mUambR6FCgO0y;a_=;* z({@+k+S7HK>m1jkogF%-cb?Vxlv{|~n{Hd(K5;wc-pzfU`==gu9!VZW9#4BL_BiO- z+%v~>kmnfB$2_0${Lu3YFJG@PuPI*Jy{>zA@~-rL%liu-7au>LDxY;e7kpdzy7>C| zF7$oX_buOTU5s7UbRE?7cfV4G(G62Zlk)L3GNkqH26Zu zjL^2B3&Prl%@6MszBnQzVpF7l{*-vC2&GE{q&iN?UGk1LM@w~vig?V4+ z7v}FSa4GO92rcMQkXg{XpuC{EU~0kL!pVid7Clk)TX9wK&64IN{Y&2I&3k9{-q8D- zK7M^x_Ic1Zsqgx}zw}G!_eQ@>{dxas{l6VhG2rCDh=HpIemSVypce<79~?0_W$=>0 zzm?jQ=9a!xdVNUtkaJ}TW$%=?EB7rAE1z3otQc6ad#K&eNkb1;cCUP6Sm?0*!#fS1 zG5n_yqpO-$Eg0!Pa?Z#HqcTUW7~Nv@Q=@+zGp)LH^@!^0V?)O-8hdZtq(@vISyj`j zrl@9h&DHVI<7bZFJ;8OthzVP2oodT#-9r*Of zr}xayoqun^%mr7U34f;7GpnC%_UyE0zk4p@x$O(n7ryzt!}BAbzxhI+7mh3nUi89? zZC_mflEX`HE^fVe?&7asPJVgUD;-~%y~JsW$C8;#o?Y_e(u}3=FLPNoeK}h`Wcldj zw_Y9g>ZTRGE2gZtzA|j(=9M3>601h9dT-VJ)upR9ukl#($eIJMwSH~U>z!Z!=#BU{ zo_^!T+JLny*4|xLzV7Jy-s|6av+0}t-+b>ax3}iJ^~Ku*-ro3*&pWfg}htn?DTtaL|X3eE8Id@9!|~DB1DujvYHL z?YOtI-Oj+BNjtN4_Srdn=c1i!b{^hk*yXk>f7i3S*6jNBBj1l=K1%th-)y>9pA-S_u|?&-bf<)A|5ObjDz=#8n9oT>1)Ipbn$@t4V_{71T2md_O`B3Pg zxra6!wmlqnIQ#H`!xe|$KfLGg?IZk%-;tq59y#*Tkqt+7965jF*3ni+yB;k$`rOf5 zN54OM|CsSun`5rW{EkH)8+dHQu?fd!99wYgm1D0T+jwl(vBSrHK4z-3uXC#Ns0*r# ztt+m3weHQjEp>bAj@MnT`|Eg*;~B?Gj+Y%Db9~D2S;uc5|M__R3A+=HC)`g2oQOJ+ zcp~S-izil{c>Bb*6Q7(odE)AcUr$D#Ogfo&a=^*qC&!;$dvepsk4_#wdG6$Ar&^qH zIpuRI^i+>inWuW6DnC_yYU8Qxr+z)Hoo;&C`LyThZl~i;r=5QG^wQI7Pj5c`@#&+d zFPv$2X4IKUXJ(#x_RNwqZ=Bh9X6KoMXU?3tan^XY>)D92314Nii8&|%%YJWBGYUI`Y ztK+ZEySn6BlWXm+b-5OOZQ8Z<*UnzM`YHc3`qR;$uKRS)rx!o{?K;11f4$B1uGed> zzjS@|^-r!}zW&1v!;Od=334SM_;caIj+d9VjQHbJ zSmkD&%{O(E52cSmFwkr>?SVN4Q>L2;z!2aS!(a(+dWX50Pl`LBvjX%l!W@N}2Xh5s z-C&YnvSBLY_Oe+)u?rSjZ;GYHf zI-sXEgg*-y0)Hmp4=}B@Y|#mHT zHg5~RRe5CopoOoN1mZ6=0j#?4>Su0`o+DcZZtu(j*=V`jhWc~(b2A@yAQ-OALqP$Gm);#;yHk=9m zSlSTvY~U%{mcI`}Z3v#oHtY)cAKI{?{hYOM*?#4~)%js)gFJLOqINS7udI0UU)##6 zT(Ye;fcO7nTT!{Fo!DJ1{O|1~98eBjUZ{NpWW)HAZWfzw+KhlZ5%4)c$c`Z$?&ap2 zwk3dj09^oogc*kT_JD(xF`{oua}fS*a67_)2R7FLA!jykA?-78Lq=?xYT3psTDAdu zzJ_IR`yuWX81US7Hw?;dkA7n81>9oY><2_1(mV}FCQmop0m+<4d6Ac4G|V0N8DI;T zQJ|Fx;|ljaz!v~ZVK&1(9uRpN7ARvA23QDl76y454#T_+GfvTyG{1oVGZ^s0roS@A z)_@oXr13``QeZUrt6{)1!+S7UFjrw9Pd34TrvTCCZJH@#I0P68gS0kwi2nwV_>MNQ zL0j;RFdrcND?rqNFM%6v$H6aN05iz^y#YK1yvbMUcqo(MWf-bUUzpcGs|E&gVE7aU zb+u$Mg*!(#Jps|L zj3~Q=;h@nRe&WjnWh^ph_!8kqU|s=EC&2Fze;o$xK=~S1pd1J@evL4+p`j@Z%5Lb4 z_)CB&hoK4FRF86)0Wg2SkZhnGjYI>6`kl@D$`~O7vY%~59E{B|K*+Yy0Mj1nzK20O z7`Gt~@@GI>7&^cVMHs=8Fr#30z(Cfhji_C1W8p4^L4BwWHZ8S6!wF?MCj0k1 z;9b*A93bS_a2gQ2vl#&M63SZ(Xs;X8i+EsD219a+I@rucSO927!CegeKoxG-30iw$ zP-g09hJ2J^7u>&SL&#u`4YwJr4Kb{Q*$;CQeiy*+5dV@M-xY4uN!AN>G7>)l4QKbM1N^jkT1{{Vc9U6ik}4`5#ySH%5-@ZXiO-3ADmv7xw?Fb9GAC?N4T0q{QH z6~JoXK!yyvB`46U)S{>g7N^*y~W9 zWs4T@zmAiF8}Ogi+VgIJ>kys{-1UIYh!4_X48nr62=5sq@JbiiXZDBEAif0X9H$7%7{3U4jqwU+D#=y+<)+531GVIVZjPiZM^Fv{{3 z>QC$Uj#??sCPPuKK)fKF!Uy3l(_dPD-W@diYN4V7Xnq9T_Yr>uVc@+CdkO6@2Wvm# zWftB$NQ1ux>asv9;vu+4xdwOjTjMtXb|U_5nLZSCj^=M@vEWS{`y4zVzW<96Bef>f z7P1|vEp(&Zz+GXq1>hZ+d(fq8Vrd6z3v0uE(!Aggm2E-oVKLMeXfwR&kgfBbc=(Vp z41k~70_|XJw7a;OcULO{{0Zg(pM`i!J5XC#8}Nlc0okIqur_E5+13Q9?v(f6A;}@- z|91%4#9gIOS?@Jy3-CqC%71}Uj*(9TYzg}CgNL7?9=8Dx0UihZ8t@0eo610c9`)t1 zXamR|$-e9hkbS@;_94Pr>ES;C?$GQZJNDEjk}q3ysQYDq0gwI;4axXkC_=r^he(cj zf`o|;e$}t2uTlSMfYjGW-YrnJG4Yz(`u~EjXt@+m{ge8L?4Q&gmVS!9BKxZqWI5(* zB$rY)x~pVdvdl8tNDMko(s)AAI}ZL^f2}T`fwf6VoasJi<`_X`I9(#GMTT@SRwoQYQ)2E zU!?QKIMWFy5I9XRxR};xL+wTV&wtLccayscYJ))9IEgTbJ~;n^MiYOU_eEfqf*6NF zF>c@=1|t3644kSo{>vy%&4cEo_7_)q7{>DPZaB9k46gLj-DG0gnZWrt$}434Z8Zbm z0RLibhGxXcu?GErH|=o-y*wPDhp;G?j#pwTaWU@&R?j=|UHlN=?BX3IW#lzLZ-y_{)h)1ROQSZ9HIL>eA zAC=2pTgB}08%JGOB#U9tF2t`&yucpd4Xs`LAj)+KfR>*(et%N3)S>%)JPt2N5izM))0Ww{v2 z#mv896)>I`;(lu`^h{HE3Qy#{cn|K+_ObQQyf3}!iuO3PiScVLII(Sq^Kuuh z19ZP!wa)0n)Y^~XY`QtLOFKcYYJv8wxGS@2q75VIHK4NETo~nMNIh3~bJ;glIPM>7 z8})F56^@NIQTt2}H)<}>+g122u&*#qOVY-I7gL~DTcr)xMradIo={KY(`3y&z(a5_7V^)wXU9c91G8H5LJh&cC9cY!hH&WMtVio99$*t4p zW9D{4O^G7?#i9OV!2v2$HMmA`k%%P|0e-}xq-EMDP$aBj2ptW&<=_&<6J7-VVnFFX zmxnk!0riZXYmyMR!_gT$bkk`Nb!-%Re&V(lL z#JUYLVpMCw>@b2fWzAS~)`GQUtypW;29HO#Wsb}VdRp!9_@fKPzK*OD#+lB{4Z02< z7zw?YH}hfmS$NhJzk}w_0$3mmV%?zO5W+(7FmyPJz?c-pqA@PTvN#rxpBU|d-yQ44 z5?CTjV#zFprLr`PrWyFfuq=#SIV_juv3#5-7qTK&3=N6itPfsN>c{$H#2v^6VRS3S z>sV#1oK>))7=wr5S?m$4ij9O)$7nW&RkN{d9Dci|hK*+vSS_1~-^7~Crm(4O8heyI zhB13Oo57x7Gue}P0c{qW&E~MVcv0zTHlHnE&#-6Nb8I1d9wX=?_9AVJ!~&V!cXvK;{kS%9b$*s5q6XvV|DB}JHbw}Q|vT5!_KmE z>^!@`F0xDPGP}aAvTN*9cAec|pRv!`7wjgx#lB>>*;njq_6@tkzGZjWckFxi1N)Kv z#C~SKuwU74>>m4_{lRcgg#E=HuzF^K{(;6B=Ui|Dx525sEpNi@@EbQxc{ARex8N;# zE8d#7;SRhl$K49ss_*D~M-i3GNe%zl2@Id_b zOgA2kAAt?U-Lh~V!6WguVKk4yi(hd(9=FSTKsU4(Pr!_jgjd;9Fgv92bo|myCePy8 zJO?vI9_Eb#%pOI&7_ZRw=6!fy-Vd*%4B!L#AU>FvLeHR#m-7lf6u&1s48Mjuf>-g8 zd=wvzx1p->KJGaF2(Q5mG=bOhiF^{Dj5pk;@@f20{uqB8H|S^ZC-_YMB!7y}; zUcQfi!uRt7n4u5x!evY5#7x+bfiC>0x+*N*!f6A}( z8~ii=IsXE`Yj6uM@Z82bdtc*L{T==-bjH5p-}4{vTLM4vpZPERSNr$7Aq>tz; z`icHxfEXwSiNT^&3=w6bTvUjmqEZZlj>!m7B}R%-Vzd||s>N6_PCO!N#CS15)QX8> zl9()}h^bmXt@~Y^Kqh5i20~kE75x6c2r-jAM|4fXan&k)?nN|8KRYm7q!>$2FEPC^tD*~ z8mpIQv08XldsBN|yrey?UD2MzI{0U3az7&$V}@ePOIH}cX$MK5CH<;7TVn5)t_8w-RAMx(M1??R6NJePiYTs$| zw2_#dMqv&djoEP(=BP(7kI~#(i`i?UHc7h!y;ZgPc?_$a?b>Q>I@Ucipiy)ecLjIg zp3o~|iC8L@iRD^7evjZ)u>!B$YGS2WB~~*g)`-`{>*5WuR;&~2#hc=yTu-{SNlS{DfWp^#C~x= z92AGdVR1wp6~{!KIF1$4CUHWX6sN>#aYmdK=frt&L0rUL(=Wv(aamjuSH(5)skkm~ zh|k35;tO$8+!9}k+u|$nwfIKd5#MS%#a;28_#VIMcw75QTPuDPKZ&2kFXC76o4AK} zG(H!)xwKwA)(pGJowqN@|+k)59 zX5xnI9PMrGAat;HYr6~vgN?yxu+p2Isf zFKHj*t7xwww0F2siSepo!VilbuaSDKe?V z&y&s)dkR%#$*Jc6+^hs3WTjI!Y2-{xCp$Nd0+Ps?B;zv4nW^IXk-NA!0T~t-%b?_B z@+2p#_*_a`ERkf|WO5=7p)x9;{Q1(KNX|s*%%S*xWEGN?O;&G;E|OG>iYP!@(x083 zV2gs6A{;QYG*^}?YcdriD}6F0Op|t|3`$O(On4}w^yVv!!pYQlSsjBZ-1D;mDN1{opDt25HnA&g5h`K@!f~Tp$)xG^I$UNODkUK0Nv4NrWSj962!k zU`T{WDtaTLh^Q15!AFKX+0>#`K>(^Bg0dzfL(~q=G_o?ukGh~q6RK;+Pb{k`YmP!o zi7KzE9$E{r>RmOytY&x_p-e0rUQ;%rtO*6yl#M9^Lvm|JO(;VK{VQw6f>vI2CG6r! zV-Zz6qNcJEQK@5VYY0?Lq`0E0$%reeoLE^6m{K)-!~{gv+6Gl{ztsXfR z)M{Eb1Sx;Bp<^dZs2o~RRZ}sVid9%SZUpt8DV3wk#*aX?3jrq7lu?$5&MGaG0+J30 zZ<_L^k(a7Xegr2eZ<3Ccsl1sMtbWR0Tnq@Vq19DDa+i zScuh;@>OWQj+CgpiMlrjUf@#J0ND!kRyaj^c11-hSO0$>2bl&yk@35X#c z5G$yr@Dc|Ahye(d^(Um#EXk+Ir5HB|yA%VV3{O%ZQ-xxzld-wEDp+|Zmln&^rNyQF zEDptYg!I-^>h$FJm{qE$&sXTW9AuSZETra|Ob&>&bf^MNE=^Qf=16*zOLLUBp90wm z^j7qGqa3oF7%_4qEs{q9=V5;(_gIl_|>W)&q20(#Jk1L|0rj@1<5dg{A zv{KnbX{D-Z(n^z6EM^;21KxC1Yw4181u78)K#U}qsFGk7l1U(WdW7V48hjGJ&=M?} zo`y2Z)N=Ykr;(KflywA@G?Ge_h&X^GPEe&wQlgNg+A|5*gr*mkx|>R$2?0eenW!bo z0O`eKhC;$m^Bo1j)!>jahY*<;VJNbh)TkhzNlhxL$*CJ&<sRS)k6^MvZ3Th#l zLXp;Mi4j275>TQcqa-Q}K`9Jy$N~eZ=EEqV1WP(34qP%ZU~dvC8bM%)4mo9|$RQ;H z!wSldnoEaF50^|2DAUuB14HS_DbtffrpGv>@|O;Y1eYQy9hM-75$cT~SwTQm!Ca65 zABxp@0ic%;4mB~r(VNOgQEEj3muv@uK$5BDcmY;eQtQJ6LZmPkE71f1f=nO>ielOC zh%a!+RSz6`0@<~SOLHs<6x%G~Qw(%TM=yPm;lDb==en_J?ag1%lb?HE5A&c zj7~>$Ijk{q(F$RRr|9I}N70tbbVEB&7jya-soL zLkZ!?_D@tpKY+{*5XD2G;gE$+L}sW;qLNVn$#H_J;EAfLiI7&AO7S0Fy}Kr=+9s;} z0W5wgw~167NnXkl63HrA8jpzR#i0sN270zRs#rN{^vzL)$We>{fP8>zj_N`HAOc>J zLkbQYvKj;t1%^wP6hG;bB9Ws;aa~+;)F_aHYNH%EdcgrzPO=*PR5@tIXx6Z%e%K@N zvQ;MpKqRQap@*jtwQSWU0J0IYb#Wsox5M;Fog0Q4yK~x;cRuT!I zr<1LgP3(}^Qb6@4%2rJVV4)?)=Iqkml)XfkbQM)9MLvy%o25DBE@Pe>Rs1M8(q9;_+fB;<@id1i*iAz$D z!-iUqQqgpwC{k+}TJ%b~y8P=yV3BHAn(8DC*>BNpB}wYO2&J}?K#Bsjl=R6`;lo05 z75gwjW!Yn^hqqMUprw{C?-q?8;X!3%GQey4L?x%g-!Lf*AFLS})@ebv!HcxyrSwQ^ zaW+J^RbiHlRdHH1BC3G)jR6fQRSfbTlhP0dnS+n=!pFPrYKTOo#w0vkFnBEm)13|2 zL^!X(M}+el2rE}Zq$FI@;H4TTlr$1n&W32>g1&wAP?)}t)er`0C`>3FQ-b!Fuml0= zdVqu!lZ=3gh1At3XksCP8XRNLxWOR^k&aM{Bh2Cmw>Tm!j!26m%HoK&IAU~1NU+6W zp&VkN9AcpyVxb&jp&VkN9AcpyVxb&jp&V+V9BQE)YM~rzp&S}+TU%Wf9GnoW!(;^$ zBWx4KlvUJ>t+vHp5J6)C?c7uv%OtQ(7(0Ayb>%4AG6n6Eume+8QBhev!M>uwX-uvt zqohL>NCus<3AQOZ;YyuwiXvPofqhCtYWvCtr)`R!U8RCXv^Np1l)yf%0nvVVgHu){ zBs5fqVaALJRilSi8mlC*&Cp9xr9)yvNNAFdoot(-=UAmf86Os@;=`hh*<}^A6Dp0P zCD20?b(mz#R^=Nlfgu}(GK_|0%vGtXtx|V(3C+h^`;3JupBk%tBJ{FHL>UWJJ~avyDcteaxOy%TsVcWfos0N& ztYVdDf&wLqr?rZwC64g| zF~dezPHsBM%H7Y(J*ClY?62xGRRa6|4FX|5wZW-sm8!RPsuKFpkZ2hm8WN+!1Rbh& z4NX+|p-C~uKG-ZpXPBfwAC==I3GDg|t*Wf49A7owZj!}CbV5>8dcb63Kb3Hb1S&RF z<{pxg8e>SQtr<&!p}|?t@e63~8m4E7876F3hpg*_a%h6GM~$0M8}tv$}m z9@j*2YId>Kc4+0qP|X8&8BX1#q8Rca4GHL#Q9jbg;BNGBI`u0npEySIv9`-cPa2~I zS=&fXlqgResY{CXHYWS0TG&f!Ox~VvhEqQVO@hyW*Fv5SddQl*fYMu^T#rXQ{UY{% zK5O*CIig>~`620!&{-j!5vDhs3>G(>1D4?gZXC{fmT1eM^}Gt_YiqQ%INy6uo*r%0 zcHqqD0M3pM;Z&~<=XO`L&v6!Z8)t>z%X7hBwLeKq1o|QEn3vS;2$eb<-Jz{Pni{01 zp|mteN25_gqYQc(q<=BdO3#9{EapPPVxg{6p|mJSYhtsmA+ZZu5&NYUL>)T^J%?M+ zYWNj;3(#0-&{H4{h33#paD+yJi>`}6ng~9+_JPtp2!X}{=@}$Is~{VC1jSN&pivKC z8d^ca39E*a)qfg#L7U<%=3yLWeikp7F!OWLUI=?L^iM`ZC!+>`lkxXBbV{h6i=ivB zhHb!e2_NvO(B-%UoLkTVS`_TW`AVc6cXjbv-5%Xk59?Y%h7U9`Lh%<3ZHr`RR^&sAVjwgoMnF4a0yH6}@)_bjG#?f~%i$Gh zG^~Io!&}f=*owb>&{Eh3jf4}>KDYu+gWDpT-^Gc2FK86}fj<+p2JE3B-~?>|51jmW z!HIu3PWP7rClx37g*dGrEMCOf{79U`kHh)|EQ@_C3>GwDv{S~L6_t7zp=s)Siah6Orv`WZ+S%yrJZrLgZye zw_+JS5~uf!pCKDs1F+c^iP>J-Bc*LC?H{DwOWNJZW;N12Che=zzD+i`1ly*wbPpuk za97$tOZykHZC)eWctP6Vl5Hn{#ahsBTQftJwBw{La}?i^TS$CE3mHyQ#n>5XpOvIU)-9C$&zAxRM zl5J=u?JBaxMrnT{ZCPs@$se&#hGdWp%@e|$O}3%6w2w<0+A84BEV9Ka8B!?SvZjUw z(yiKPs*I)I4rd10z6QnZr)7xjdxj89@3^?bzp{f$rfv*EhR)q zS%O{+g^!Uq^o}?avcBRM8PZMKuaga3CbA`cXi34%wo3b=v`@>}p0H^Kv5fgnaAiUl z0}2aDX9@E|6X=8B)-lH99heh7XEwNNYm4`PCPCM1Gh@&et7jh2&T7L(qaXKYGccoQ zaQApR~vhvGWSa^!LldJpTxj|La%uY;}r z)+2T~LYGr4><8kOfs1=^{iy#Dg*wF2A5m@e-4S^8($UF{$#u-Z;Bi$5&7^Jv`l3!P8L9wmkrl=z~a$YRf$b1xMh!Sc- zltBMFd=yR;PzU@G7l?o0q@<5L^mx=3F4PgJegx(R$cIXEN77PdB${XuYobVQaDZ%tsJc-dWtk94X)jSc-1p!{+esJ6A<4=?$_F7R zG3+|<6D5X=FqI4(M7ipa*LtL<9MHy)V#;F!a2iSgH~EQ2AAs%$vaZ)rU*gyv`A4lm z?M7{J2NX%NEZn<}Jc)Z$3-zaXNB#$pcB%oS9j)Sm(O?U1)iCJwoxzB-j@Q9m!ZC{4 z#N$0+2E9Uri>~}M>_z+(?7?K)*ix+F3~qHWafF}5IM$jUL-;kajcIt#kO`9vpGIy& z7`Y9v624&s+0gC-P0aSli`|hq)*yGQQAoUkF~bS#UJlp}x@mZE7`G+f`d9Ou%^%E- z`9Oy^6#BY7j2^}=(9g|;E^cq=-Ihbgwi^1hQ=vOM8+x)YuvZYj0vfPy;SHx$V>a8! zXBrEQeT{>SgQ4@f3;L~x*$LxJtmdv5UoSKbT0KY8!8xXiKZPLf+T*13uVxi0v@jVbJ~@ z2~E$5wogH?bEfTLXmLJky9PR&OM$ZrdYSLo)%z&<9I(S@dei`;& z^B&lbnbGEKn)xE^DjB}ayb-Oj!u%^j%FR9D{t0@5jJL<{O9IDq5B4%MzHqoL?nzPj z4+yC?2f^NJ?hE@d^H|u^%wu3z$?#?7G+?&GeMO>-7dJrJ7w(_T>mVteiL&7rqGt0N zwz*kTctS*)g5F`i<@SF#qfOuvEV9BEIJb`{>rpwd19dxdEn zFz3lsOR1(}hD^JcYARMx`E4GTxww$c9n7&v^@lkSv64%oN~X<~ss5r`;I1>3AxHA( z0lE48|5x3)$5&Ncdwj3SNeD4KRFFpughLV_!GK91A>@z)iO5UD6cG^-P$LTR)`AtQ zh?e5BD2Rv_5m68k-^f)&i->3uky@@rFI7ZJQL(lZ5y}0onZumT$9n(2pM3J$zdbX1 z_Uzd+Yi8D-wT|W-Io0NKi-tr#$39O|=h%=7EH1Kmhhj=^du^IxrL|BtIQ zw_{v)io1Q{Ketk|JH4qPl~aPZ+90?G$rF-FR`NKhG}g+c`Tt2WIl06x{Fj)i9Zkq0 zq>;)Zl|xPn$#IoH>~&8_AC*1qQjfDE-AWpEnkOU-d&rS6Dqr|ptP%$KVkOK8`GVYN z!w&B4NeRO~Y$siiFHX9sY*D$QGDRf{JDrE}H-g>Fb?jW8$}Z)b_*=-XX8>lZS2UiehJ~D z>Sx0b>Ih@vHaw=7+Sc-smN(a{BG{pW-`SAgYkAUc*V3llrsZtdLPN0B*Ocd4ey=`J zq~r15fN{MlqGFj6{$~0xlUi;Lu>o1@rwml_{8{Xf(k?)n8!?p~!+k0i|y&|(T zJo33M$<4MT@7Ve|&&J+oLw>L!zp=d1<|c{}_24a$PBw=QHs`~(-umj(As@A#AGA3S z&};GUq0hd}UVDy)nD;G@YMPXxwOhH~WWOu&)j&*ZpI$4xgC9sQE?Op?NYQ%VaI`g4c*v>hRmM&iPed*-W zm|gw)`{=FR4t{ih(}FE0;rMYAgB@d%_%YhRx?oQT_ra$H2g5jS%!In&A0a#npC~~X zC$fE2(!w|ywX9^Ggwv)^lV+1cICcEgI%%E2$>ZuIXKDzqA2a1|#Zs(XYLQ%PQ?{nw z-XJIAV9UO3TDO_o=8-m=+pcYUIA?gy=60*vZEClp-JW(|w>uWih_;O8M@yr9qC=vi zq7$MwMCanya(VQ*=*H-_=2?)wqPb=^G0pf_J7awV_a^g<>YGtts}c0th8+FfOlwL}izeY3tR`k- z#jzMI?sMvO6?==%(A6GALwg!lc?DS7^+78;0*&NU^pNw=L85^~=eQaDeC6?0$Sj*br>Vt_^lzyRr}apQET8 z8%xXV{phed(-z}0AiE9=PPIm#isj|)XvG(2vsR&WL+rhjsgLeiHmugOUwC$*XTvIyWF!YJiF4fI?t~1Y@BCTdp6#)37%c+*+kDKc{atfsh&;q zY`SOHd3L>Lzw~T|W%|u2m$x2Ro3M__w=Q$7%ovG<*5s@g_-Zx>xv?l%mbD>kOV*C8 z-N9JLx$|c?rT5adyXMRnquRdi~Vl7^vOEJ7oK4<8mrpr;-u<-`Mc|HM7nqO znr9n4+vwRFp1tYWA3WRS**l(X_3T~G{^;2@&))ZJyJsJGw!^azJ=^KopFI1>vp;*b z%d`LX>@S|}_UseSKK1N>JlkWL%5>HBItLZDpJ8h^2rIiMk>IcL?QJ_>-S#5GkMN}} zlkqbLDPD?&bU&=0MmmMe-sf`D#`(8?h4Ji7elb7UdY0yG{AplgqlL9BbM^ zeB&97Mr1k`u5Bhu}%r%*sacBj^}4)jao9t8;O2& z9(}ME_U1RDO>D;MemHCS`&qRgihL9~gs=OX(zZ7!ZE#ci8R_NeyV4Ide6Qi*jNuu_ zPT6wG(acC@$IRl)D>4^mKAE{S^HAo|Mv+F1S)Ww!U4Ibvy(1ZcCvfK*8G+{_#qVeS zx0D&+Y9z=yM&M1z@a@b5cQY5{%e|ygzDZnDg?()bmcTKrY4sS#vr-B<#YFchObUwB z{{zRa60ye;zDDW!N_GM0sPZ|Ohio!fE`I?QGR7PB9()NFb0_^>m9M~3q!+&>7kmSj zF$*x*GXDeYfeo?2p7{tE3%&=du)sBx;~3am1gyqV*aXY5J2u?60jsk!c=9N_9E$KZ z(tyxBNe4?L11ytMz)HNwlA|;M<8mt4n-xhuS9E7yqy8^yv4$Q+4e0KN>yz~+t$Dt& zT$yE$amnU*Ip;YXuj1H@yz-?v*g?(!3#A2EEG@xq(hBU3w=*qA8?Zv!f<2`jSSe93 zCb?jh#Cx08NE+e(X{9qMhjCQ_ah;?PEReInGARNp zr3)BSsVrSBYr^`cBP)~c)NmhWeuHT#BZAS4_Y<(Oo*~`I?;=W6W{+_x=BivN0Sl!Z z>?{>vk@N(+N+nn#F|eET0?VWdtUzO__wEhGq#BG%H&9nO?x|`xE0c4;O6d#6XnPs% zEy}r^canZ!f%FFpWdPValwu&~MWU;*E_lAudkhBSKKJu&%n%!MfsIM#Udwq``2~2k z47DjPv?)%MY?zI?$i@t}G3m6DmaO`7nbq2QlZy#2k`Z7RJZx!xBf+!fQm|M?ft7L@ z7?aUp9Ib$BVS{;7qo`^1`EnC>^>?yjTf~^YoV_(`nOD7vRrVI_<99N)?~$=Q&k*vs z!XD#tHAnS1c#S>Ayr#=$}H;@qT^AKEC9R8La+p1llq1q2D`~pusdrr zt(6sExjYH>Ku4za`!rZ7&ww#m3&!PHuv%UOYh)3)Tpj^eh|aQ}z#|{;Tvmds?E6i~ zUPhmIQkLSU&(+0Cq$`q_!7j26ERpqKndrQwTwVox;OkLKumOz8>tK~^1mp4sSdFGA z@qUx7NOZnZD4W0{c?&F&x4|;m0+!1=U=Mj0jET-Vs^pJgSmyh7Nd1qaS59ZWbF=H0 zcpY?d@FR};c0S=ao}At%k3!i2b{3t5l*mr7oBRpvj?Znq|1PjXbe_>ubY>Bg-C&h` z3dZq^siW8)uv)f*1Lb3Ibx6MbmGd(B42;QMuu8rFa+il|_@|>)uvm25uH)Ko zIBHA!)*j=sk6cRROR$^l2g~GdV7dGqtdOt4o_GV)x9}Yp!`r^z`(I!jPk&nKe}mO> z1gwz*;7a)ld`b?1tNBk0GDS!1GC2yy(57l>`_V7{?@ycp~rKTIWl999+8PSIkpf)`3U_6`?%tI0{ z#Mj6&WbqobMC*~pn@ulDGlYC~PJ~=F=h$Q1=xjpU#`zqzw3Xzai#J(q6LGN6^ahJe zAF#yq1xrmou$$=*mYD%yxfulZFz104W-wT3hJZ110a#^f!MM@cLbZv33rr2T+zbR) z+L@S_N@gfw#pXh=%nSo7%|&3$3%-dj!w ztNGX7tF;vqeZvDo5@|E%C9(~ymOp5mYy}4=o`P`~kEAKQy_g)c#~RKxpJQN+oqcL6 zcJoke4US#s+2@uSq`dmd3-C>JHF=N6<4-I6{mfwQbsvBEcqDoapGa@;*OArBR#q50 z_$#2~0WGUL(tAy?4_!ztKH65}|LiatjtOQbo}*T?8+2%7PGo&#Z)&sDeyNjFRU6GW zhz!+Z@|_*t9BfU_C;TkwOhx@BozG6_6Z@cF_{k3D;mllIPWpabPO9P0v_ARE5dM1BFq_$r2KBf0o$#a$m-;JP!SKt-x=TGrRWV|pi=Us> zcy4+ocor{h9TNBAt6HLrd=KR-nDawb%qUt-`&h!<^)dSDE6mR9Gc{EGynCiBSO)vc zAI9_0i}=jDRbIxs+u8I9m$I2pd8SR7ho7p2!OLi>eqcvWp?ZUJDbo-5l-aOUnUe4g z*E12n)yKDiohmK)j-flr)tjGs;ag|D=WRs$b;Rx=s7PEl-(S}j){O5Ud3Klj#dr(6 z2G4)@Bs})Lh~K?W;-haDzWMg44?cDjw6Z?(%IqHWe(pXE4*L7FgB75TKU3OD zJG8CNccOYu?83gQY8h?4%HGZXs)e!?t=}7X{@abmzc29a_Z8m!j#_Vgt=TPAjLxsd zd(Ep&+-bJIQxw)2k6CB1r%SyuR^oT@T>L8z!Jpz~>{OqHzr(xOkG~kth0j=@gMYTW zBh;_pe!K`e|AF7L`(Awn>fZbIc>C+iUUGc$CGM7d|LwV3+u%oOfyc)lXfWf}!((4G znWNDCFUK$9vwVg6D|_G%sYgQVX)p)9Nj|;<`{VQP0_3x8Q)wIh6Xe=F_cDY38qLDD z%;LWb{%L(xHenvGJ|@+FWPkRk)!_qj4t^XrvFA)ZDgKC0L){Cnt4aDy8oL}4Ejka@ z$XYu5&HB1jAC={DsR`IUe=6gSOJC{J^HrY}?cb5mM_E0bf|k~K##FzJ?^;iZr!lK= zSEc!@I>0i}j~qdR`wIRY@#|^$tJHeU+Uq}OKEjf6*oQ7A+2iQGa&yN4Dcw8*EBdcCB!(USXYZ!bk*=UoO z;JH$F;eBg#B-V68e7Ztd2J57m?6P?fZtfjWnRz7qc%eNr{ju-YHlX71ig!n%mK1p>t65Ln*9PHEj{)y z4H=1|lz9ZJ{P|W2ze&1?8BKa!o2RRu!(0xVAIvmkG}Md*u}VtB=}KSYuCj6CPKuk5 zh`YwdjXx=FQX=kJ8#nQ!xT%S_$u@3^jnh(1({o$C>7I>Du!d;XT3EZI9KH{#SHf_O z=r81I`zYo4wf1y|n8GL)+NaK3M`uuCZZzsSTZo?-V7){QyLuHnhiFWV={I>T&rH(m z+?lzE(fL73b3LfzRm9w2;}UuX^Gh2$Lu1XaYz%1_b9*s5)r8)hYcV5yDTOD=W#**1 zuLZ1L)t*Ur#_6{#l@i5v^q9goN3}9~H6axmS=p?6(439XAT1GI^v7Tub9v}_*3-vw znL(5XeV9883r3?&oX+faE_Jtrxd3|%Nv(6mS(fQ+(6LKA)B1N|mwKk{#D(dbcTC@< zW3PMmmS;L=bFpuGw#BpeJo`AonuKOPT8lNISv5IWn%Z*=%PgRMqqC@0#|t7cN@Y>k;5 zI$LU{gwE!hsiCtu^nzqA*PH2~vvIV-WZVc^WAbbeZ7_M(%c$HuQI@W>6( QY2QgYJ-L_beXh;^FF>jiu>b%7 literal 0 HcmV?d00001 diff --git a/UI/forms/fonts/Poppins-Regular.ttf b/UI/forms/fonts/Poppins-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..850d03e68a16f57f3c011f94531813465518483b GIT binary patch literal 144064 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
    =c3jf5lf=g4TlI9S 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 literal 0 HcmV?d00001 diff --git a/UI/forms/images/CaffeineLogo.svg b/UI/forms/images/CaffeineLogo.svg new file mode 100644 index 00000000000000..26d99859d1af25 --- /dev/null +++ b/UI/forms/images/CaffeineLogo.svg @@ -0,0 +1,19 @@ + + + + path-1 + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 8e421a27a1c10a..762545c1c6d594 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -1518,10 +1518,10 @@ QString OBSTranslator::translate(const char *context, const char *sourceText, { const char *out = nullptr; if (!App()->TranslateString(sourceText, &out)) - return QString(sourceText); + if (!App()->TranslateString(disambiguation, &out)) + return QString(sourceText); UNUSED_PARAMETER(context); - UNUSED_PARAMETER(disambiguation); UNUSED_PARAMETER(n); return QT_UTF8(out); } diff --git a/UI/ui-config.h.in b/UI/ui-config.h.in index 0fcc4845ade1de..7cb6b8d96fdbac 100644 --- a/UI/ui-config.h.in +++ b/UI/ui-config.h.in @@ -24,4 +24,6 @@ #define RESTREAM_CLIENTID "@RESTREAM_CLIENTID@" #define RESTREAM_HASH 0x@RESTREAM_HASH@ +#define CAFFEINE_ENABLED @CAFFEINE_ENABLED@ + #define DEFAULT_THEME "Dark" diff --git a/UI/window-basic-auto-config-test.cpp b/UI/window-basic-auto-config-test.cpp index a5ba9bda0319fb..ad8d5ace45e2b6 100644 --- a/UI/window-basic-auto-config-test.cpp +++ b/UI/window-basic-auto-config-test.cpp @@ -281,6 +281,13 @@ void AutoConfigTestPage::TestBandwidthThread() OBSOutput output = obs_output_create(output_type, "test_stream", nullptr, nullptr); obs_output_release(output); + uint32_t flags = obs_output_get_flags(output); + if (flags & OBS_OUTPUT_HARDWARE_ENCODING_DISABLED) + wiz->hardwareEncodingAvailable = false; + if (flags & OBS_OUTPUT_BANDWIDTH_TEST_DISABLED) { + QMetaObject::invokeMethod(this, "NextStage"); + return; + } obs_output_update(output, output_settings); const char *audio_codec = obs_output_get_supported_audio_codecs(output); @@ -1113,6 +1120,8 @@ void AutoConfigTestPage::FinalizeResults() new QLabel(scaleRes, ui->finishPage)); form->addRow(newLabel("Basic.Settings.Video.FPS"), new QLabel(fpsStr, ui->finishPage)); + + QTimer::singleShot(0, [this]() { wiz->adjustSize(); }); } #define STARTING_SEPARATOR \ diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index 03cf065b7c61d5..4a3265786d701f 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -7,6 +7,7 @@ #include "window-basic-main.hpp" #include "qt-wrappers.hpp" #include "obs-app.hpp" +#include "ui-config.h" #include "url-push-button.hpp" #include "ui_AutoConfigStartPage.h" @@ -15,14 +16,11 @@ #ifdef BROWSER_AVAILABLE #include -#include "auth-oauth.hpp" #endif -struct QCef; -struct QCefCookieManager; - -extern QCef *cef; -extern QCefCookieManager *panel_cookies; +#ifdef AUTH_ENABLED +#include "auth-oauth.hpp" +#endif #define wiz reinterpret_cast(wizard()) @@ -188,9 +186,7 @@ AutoConfigVideoPage::~AutoConfigVideoPage() int AutoConfigVideoPage::nextId() const { - return wiz->type == AutoConfig::Type::Recording - ? AutoConfig::TestPage - : AutoConfig::StreamPage; + return AutoConfig::TestPage; } bool AutoConfigVideoPage::validatePage() @@ -231,6 +227,17 @@ bool AutoConfigVideoPage::validatePage() break; } + if (wiz->service != AutoConfig::Service::Twitch && wiz->bandwidthTest) { + QMessageBox::StandardButton button; +#define WARNING_TEXT(x) QTStr("Basic.AutoConfig.StreamPage.StreamWarning." x) + button = OBSMessageBox::question(this, WARNING_TEXT("Title"), + WARNING_TEXT("Text")); +#undef WARNING_TEXT + + if (button == QMessageBox::No) + return false; + } + return true; } @@ -312,7 +319,7 @@ bool AutoConfigStreamPage::isComplete() const int AutoConfigStreamPage::nextId() const { - return AutoConfig::TestPage; + return AutoConfig::VideoPage; } inline bool AutoConfigStreamPage::IsCustomService() const @@ -340,7 +347,9 @@ bool AutoConfigStreamPage::validatePage() obs_service_release(service); int bitrate = 10000; - if (!ui->doBandwidthTest->isChecked()) { + bool doBandwidthTest = ui->doBandwidthTest->isChecked() && + ui->doBandwidthTest->isEnabled(); + if (!doBandwidthTest) { bitrate = ui->bitrate->value(); wiz->idealBitrate = bitrate; } @@ -358,7 +367,7 @@ bool AutoConfigStreamPage::validatePage() wiz->server = QT_TO_UTF8(ui->server->currentData().toString()); } - wiz->bandwidthTest = ui->doBandwidthTest->isChecked(); + wiz->bandwidthTest = doBandwidthTest; wiz->startingBitrate = (int)obs_data_get_int(settings, "bitrate"); wiz->idealBitrate = wiz->startingBitrate; wiz->regionUS = ui->regionUS->isChecked(); @@ -366,8 +375,10 @@ bool AutoConfigStreamPage::validatePage() wiz->regionAsia = ui->regionAsia->isChecked(); wiz->regionOther = ui->regionOther->isChecked(); wiz->serviceName = QT_TO_UTF8(ui->service->currentText()); - if (ui->preferHardware) - wiz->preferHardware = ui->preferHardware->isChecked(); + if (ui->preferHardware) { + wiz->preferHardware = ui->preferHardware->isChecked() && + ui->preferHardware->isEnabled(); + } wiz->key = QT_TO_UTF8(ui->key->text()); if (!wiz->customServer) { @@ -379,17 +390,6 @@ bool AutoConfigStreamPage::validatePage() wiz->service = AutoConfig::Service::Other; } - if (wiz->service != AutoConfig::Service::Twitch && wiz->bandwidthTest) { - QMessageBox::StandardButton button; -#define WARNING_TEXT(x) QTStr("Basic.AutoConfig.StreamPage.StreamWarning." x) - button = OBSMessageBox::question(this, WARNING_TEXT("Title"), - WARNING_TEXT("Text")); -#undef WARNING_TEXT - - if (button == QMessageBox::No) - return false; - } - return true; } @@ -406,7 +406,7 @@ void AutoConfigStreamPage::on_show_clicked() void AutoConfigStreamPage::OnOAuthStreamKeyConnected() { -#ifdef BROWSER_AVAILABLE +#ifdef AUTH_ENABLED OAuthStreamKey *a = reinterpret_cast(auth.get()); if (a) { @@ -431,14 +431,14 @@ void AutoConfigStreamPage::OnAuthConnected() std::string service = QT_TO_UTF8(ui->service->currentText()); Auth::Type type = Auth::AuthType(service); - if (type == Auth::Type::OAuth_StreamKey) { + if (type != Auth::Type::None) { OnOAuthStreamKeyConnected(); } } void AutoConfigStreamPage::on_connectAccount_clicked() { -#ifdef BROWSER_AVAILABLE +#ifdef AUTH_ENABLED std::string service = QT_TO_UTF8(ui->service->currentText()); OAuth::DeleteCookies(service); @@ -467,17 +467,21 @@ void AutoConfigStreamPage::on_disconnectAccount_clicked() OBSBasic *main = OBSBasic::Get(); +#if !CAFFEINE_ENABLED main->auth.reset(); auth.reset(); +#endif std::string service = QT_TO_UTF8(ui->service->currentText()); -#ifdef BROWSER_AVAILABLE +#ifdef AUTH_ENABLED OAuth::DeleteCookies(service); #endif - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); + bool hidden_key = Auth::IsKeyHidden(service); + + ui->streamKeyWidget->setVisible(!hidden_key); + ui->streamKeyLabel->setVisible(!hidden_key); ui->connectAccount2->setVisible(true); ui->disconnectAccount->setVisible(false); ui->key->setText(""); @@ -489,11 +493,6 @@ void AutoConfigStreamPage::on_useStreamKey_clicked() UpdateCompleted(); } -static inline bool is_auth_service(const std::string &service) -{ - return Auth::AuthType(service) != Auth::Type::None; -} - void AutoConfigStreamPage::ServiceChanged() { bool showMore = ui->service->currentData().toInt() == @@ -508,24 +507,59 @@ void AutoConfigStreamPage::ServiceChanged() ui->disconnectAccount->setVisible(false); -#ifdef BROWSER_AVAILABLE - if (cef) { - if (lastService != service.c_str()) { - bool can_auth = is_auth_service(service); - int page = can_auth ? (int)Section::Connect - : (int)Section::StreamKey; - - ui->stackedWidget->setCurrentIndex(page); - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->connectAccount2->setVisible(can_auth); - auth.reset(); - - if (lastService.isEmpty()) - lastService = service.c_str(); +#ifdef AUTH_ENABLED + bool can_auth = Auth::CanAuthService(service); + bool hidden_auth = Auth::IsKeyHidden(service); + QString connectString = + QTStr("Basic.AutoConfig.StreamPage.ConnectAccount") + .arg(hidden_auth ? QTStr("Required") + : QTStr("Optional")); + ui->connectAccount->setText(connectString); + ui->connectAccount2->setText(connectString); + + const char *service_id = wiz->customServer ? "rtmp_custom" + : "rtmp_common"; + OBSData settings = obs_data_create(); + obs_data_release(settings); + + if (!wiz->customServer) + obs_data_set_string(settings, "service", service.c_str()); + + OBSService tService = obs_service_create(service_id, "temp_service", + settings, nullptr); + uint32_t flags = + obs_get_output_flags(obs_service_get_output_type(tService)); + obs_service_release(tService); + if (ui->preferHardware) { + if (flags & OBS_OUTPUT_HARDWARE_ENCODING_DISABLED) { + ui->preferHardware->setDisabled(true); + ui->preferHardware->setVisible(false); + } else { + ui->preferHardware->setDisabled(false); + ui->preferHardware->setVisible(true); } + } + if (flags & OBS_OUTPUT_BANDWIDTH_TEST_DISABLED) { + ui->doBandwidthTest->setDisabled(true); + ui->doBandwidthTest->setVisible(false); } else { - ui->connectAccount2->setVisible(false); + ui->doBandwidthTest->setDisabled(false); + ui->doBandwidthTest->setVisible(true); + } + + if (lastService != service.c_str()) { + int page = can_auth || hidden_auth ? (int)Section::Connect + : (int)Section::StreamKey; + ui->useStreamKey->setVisible(!hidden_auth); + + ui->stackedWidget->setCurrentIndex(page); + ui->streamKeyWidget->setVisible(true); + ui->streamKeyLabel->setVisible(true); + ui->connectAccount2->setVisible(can_auth); + auth.reset(); + + if (lastService.isEmpty()) + lastService = service.c_str(); } #else ui->connectAccount2->setVisible(false); @@ -562,7 +596,7 @@ void AutoConfigStreamPage::ServiceChanged() ui->bitrateLabel->setHidden(testBandwidth); ui->bitrate->setHidden(testBandwidth); -#ifdef BROWSER_AVAILABLE +#ifdef AUTH_ENABLED OBSBasic *main = OBSBasic::Get(); if (!!main->auth && @@ -686,11 +720,14 @@ void AutoConfigStreamPage::LoadServices(bool showAll) 0, QTStr("Basic.AutoConfig.StreamPage.Service.Custom"), QVariant((int)ListOpt::Custom)); - if (!lastService.isEmpty()) { - int idx = ui->service->findText(lastService); - if (idx != -1) - ui->service->setCurrentIndex(idx); + int idx = -1; + if (lastService.isEmpty()) { + idx = ui->service->findText("Twitch"); + } else { + idx = ui->service->findText(lastService); } + if (idx != -1) + ui->service->setCurrentIndex(idx); obs_properties_destroy(props); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 2f25a33ed86058..175925a5611cd4 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -188,6 +188,7 @@ void assignDockToggle(QDockWidget *dock, QAction *action) } extern void RegisterTwitchAuth(); +extern void RegisterCaffeineAuth(); extern void RegisterRestreamAuth(); OBSBasic::OBSBasic(QWidget *parent) @@ -201,6 +202,9 @@ OBSBasic::OBSBasic(QWidget *parent) #if TWITCH_ENABLED RegisterTwitchAuth(); #endif +#if CAFFEINE_ENABLED + RegisterCaffeineAuth(); +#endif #if RESTREAM_ENABLED RegisterRestreamAuth(); #endif @@ -8067,6 +8071,9 @@ QAction *OBSBasic::AddDockWidget(QDockWidget *dock) { QAction *action = ui->viewMenuDocks->addAction(dock->windowTitle()); action->setCheckable(true); + if (dock->isVisible()) { + action->setChecked(true); + } assignDockToggle(dock, action); extraDocks.push_back(dock); @@ -8089,6 +8096,16 @@ QAction *OBSBasic::AddDockWidget(QDockWidget *dock) return action; } +void OBSBasic::RemoveCaffeineDockWidget(QDockWidget *dock) +{ + for (auto &it : ui->viewMenuDocks->actions()) { + if (it->text() == dock->windowTitle()) { + ui->viewMenuDocks->removeAction(it); + return; + } + } +} + OBSBasic *OBSBasic::Get() { return reinterpret_cast(App()->GetMainWindow()); diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 4f0ad6b3a14ecb..2c6d95e4a7043c 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -827,6 +827,7 @@ private slots: void CreateFiltersWindow(obs_source_t *source); QAction *AddDockWidget(QDockWidget *dock); + void RemoveCaffeineDockWidget(QDockWidget *dock); static OBSBasic *Get(); diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 2189ed5a8e25cd..148a1bbd328a3c 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -10,14 +10,15 @@ #ifdef BROWSER_AVAILABLE #include -#include "auth-oauth.hpp" #endif -struct QCef; -struct QCefCookieManager; +#ifdef AUTH_ENABLED +#include "auth-oauth.hpp" +#endif -extern QCef *cef; -extern QCefCookieManager *panel_cookies; +#if CAFFEINE_ENABLED +#include "auth-caffeine.hpp" +#endif enum class ListOpt : int { ShowAll = 1, @@ -39,6 +40,8 @@ void OBSBasicSettings::InitStreamPage() ui->connectAccount2->setVisible(false); ui->disconnectAccount->setVisible(false); ui->bandwidthTestEnable->setVisible(false); + ui->authSignedInAs->setVisible(false); + //ui->authSignedInAsLabel->setVisible(false); ui->twitchAddonDropdown->setVisible(false); ui->twitchAddonLabel->setVisible(false); @@ -104,14 +107,13 @@ void OBSBasicSettings::LoadStream1Settings() const char *service = obs_data_get_string(settings, "service"); const char *server = obs_data_get_string(settings, "server"); const char *key = obs_data_get_string(settings, "key"); + const char *username = obs_data_get_string(settings, "username"); if (strcmp(type, "rtmp_custom") == 0) { ui->service->setCurrentIndex(0); ui->customServer->setText(server); bool use_auth = obs_data_get_bool(settings, "use_auth"); - const char *username = - obs_data_get_string(settings, "username"); const char *password = obs_data_get_string(settings, "password"); ui->authUsername->setText(QT_UTF8(username)); @@ -120,9 +122,16 @@ void OBSBasicSettings::LoadStream1Settings() } else { int idx = ui->service->findText(service); if (idx == -1) { - if (service && *service) + if (service && *service) { + // Insert placeholder for unrecognized service ui->service->insertItem(1, service); - idx = 1; + idx = 1; + } else { + // Default to twitch or first non-custom service + idx = ui->service->findText("Twitch"); + if (idx == -1) + idx = 1; + } } ui->service->setCurrentIndex(idx); @@ -143,6 +152,18 @@ void OBSBasicSettings::LoadStream1Settings() idx = 0; } ui->server->setCurrentIndex(idx); + +#if CAFFEINE_ENABLED + if (username && username[0]) { + lastSignedInAs = username; + //ui->authSignedInAsLabel->setVisible(true); + ui->authSignedInAs->setVisible(true); + ui->authSignedInAs->setText(QT_UTF8(username)); + } else { + //ui->authSignedInAsLabel->setVisible(false); + ui->authSignedInAs->setVisible(false); + } +#endif } ui->key->setText(key); @@ -186,6 +207,12 @@ void OBSBasicSettings::SaveStream1Settings() obs_data_set_string( settings, "server", QT_TO_UTF8(ui->server->currentData().toString())); +#if CAFFEINE_ENABLED + if (ui->service->currentText() == "Caffeine") { + obs_data_set_string(settings, "username", + QT_TO_UTF8(lastSignedInAs)); + } +#endif } else { obs_data_set_string(settings, "server", QT_TO_UTF8(ui->customServer->text())); @@ -349,11 +376,6 @@ void OBSBasicSettings::LoadServices(bool showAll) ui->service->blockSignals(false); } -static inline bool is_auth_service(const std::string &service) -{ - return Auth::AuthType(service) != Auth::Type::None; -} - void OBSBasicSettings::on_service_currentIndexChanged(int) { bool showMore = ui->service->currentData().toInt() == @@ -369,23 +391,52 @@ void OBSBasicSettings::on_service_currentIndexChanged(int) ui->twitchAddonDropdown->setVisible(false); ui->twitchAddonLabel->setVisible(false); -#ifdef BROWSER_AVAILABLE - if (cef) { - if (lastService != service.c_str()) { - QString key = ui->key->text(); - bool can_auth = is_auth_service(service); - int page = can_auth && (!loading || key.isEmpty()) - ? (int)Section::Connect - : (int)Section::StreamKey; - - ui->streamStackWidget->setCurrentIndex(page); - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->connectAccount2->setVisible(can_auth); - } +#ifdef AUTH_ENABLED + auth.reset(); + + if (!!main->auth && + service.find(main->auth->service()) != std::string::npos) { + auth = main->auth; + OnAuthConnected(); + } + + bool can_auth = Auth::CanAuthService(service); + bool hidden_key = Auth::IsKeyHidden(service); + QString connectString = + QTStr("Basic.AutoConfig.StreamPage.ConnectAccount") + .arg(hidden_key ? QTStr("Required") + : QTStr("Optional")); + ui->connectAccount->setText(connectString); + ui->connectAccount2->setText(connectString); + + if (lastService.isEmpty()) { + lastService = service.c_str(); + lastServiceKey = ui->key->text(); + } else if (lastService != service.c_str()) { + // Don't show the stream key from the previous service + ui->key->clear(); } else { - ui->connectAccount2->setVisible(false); + ui->key->setText(lastServiceKey); } + QString key = ui->key->text(); + bool authenticated = !key.isEmpty(); + int page = can_auth && (!loading || key.isEmpty()) + ? (int)Section::Connect + : (int)Section::StreamKey; + if (hidden_key) + page = (int)Section::StreamKey; + ui->useStreamKey->setVisible(!hidden_key); + ui->streamStackWidget->setCurrentIndex(page); + ui->streamKeyWidget->setVisible(!hidden_key); + ui->streamKeyLabel->setVisible(!hidden_key); +#if CAFFEINE_ENABLED + bool isCaffeine = service == "Caffeine"; + ui->authSignedInAs->setVisible(authenticated && isCaffeine); + ui->authSignedInAs->setText(lastSignedInAs); +#endif + ui->connectAccount->setVisible(can_auth && !authenticated); + ui->disconnectAccount->setVisible(can_auth && authenticated); + ui->connectAccount2->setVisible(can_auth && !authenticated); #else ui->connectAccount2->setVisible(false); #endif @@ -408,15 +459,7 @@ void OBSBasicSettings::on_service_currentIndexChanged(int) ui->serverStackedWidget->setCurrentIndex(0); } -#ifdef BROWSER_AVAILABLE - auth.reset(); - - if (!!main->auth && - service.find(main->auth->service()) != std::string::npos) { - auth = main->auth; - OnAuthConnected(); - } -#endif + update(); } void OBSBasicSettings::UpdateServerList() @@ -429,8 +472,6 @@ void OBSBasicSettings::UpdateServerList() LoadServices(true); ui->service->showPopup(); return; - } else { - lastService = serviceName; } obs_properties_t *props = obs_get_service_properties("rtmp_common"); @@ -507,7 +548,7 @@ OBSService OBSBasicSettings::SpawnTempService() void OBSBasicSettings::OnOAuthStreamKeyConnected() { -#ifdef BROWSER_AVAILABLE +#ifdef AUTH_ENABLED OAuthStreamKey *a = reinterpret_cast(auth.get()); if (a) { @@ -516,11 +557,24 @@ void OBSBasicSettings::OnOAuthStreamKeyConnected() if (validKey) ui->key->setText(QT_UTF8(a->key().c_str())); + lastService = a->service(); + lastServiceKey = a->key().c_str(); ui->streamKeyWidget->setVisible(false); ui->streamKeyLabel->setVisible(false); + ui->connectAccount->setVisible(false); ui->connectAccount2->setVisible(false); ui->disconnectAccount->setVisible(true); +#if CAFFEINE_ENABLED + if (std::string(a->service()) == "Caffeine") { + auto caffeine = dynamic_cast(a); + lastSignedInAs = caffeine->GetUsername().c_str(); + //ui->authSignedInAsLabel->setVisible(true); + ui->authSignedInAs->setVisible(true); + ui->authSignedInAs->setText(lastSignedInAs); + } +#endif + if (strcmp(a->service(), "Twitch") == 0) { ui->bandwidthTestEnable->setVisible(true); ui->twitchAddonLabel->setVisible(true); @@ -531,6 +585,7 @@ void OBSBasicSettings::OnOAuthStreamKeyConnected() } ui->streamStackWidget->setCurrentIndex((int)Section::StreamKey); + update(); #endif } @@ -539,7 +594,7 @@ void OBSBasicSettings::OnAuthConnected() std::string service = QT_TO_UTF8(ui->service->currentText()); Auth::Type type = Auth::AuthType(service); - if (type == Auth::Type::OAuth_StreamKey) { + if (type != Auth::Type::None) { OnOAuthStreamKeyConnected(); } @@ -551,7 +606,7 @@ void OBSBasicSettings::OnAuthConnected() void OBSBasicSettings::on_connectAccount_clicked() { -#ifdef BROWSER_AVAILABLE +#ifdef AUTH_ENABLED std::string service = QT_TO_UTF8(ui->service->currentText()); OAuth::DeleteCookies(service); @@ -578,13 +633,18 @@ void OBSBasicSettings::on_disconnectAccount_clicked() return; } + // Remove the auth here if caffeine account is not associated +#if !CAFFEINE_ENABLED main->auth.reset(); auth.reset(); +#endif std::string service = QT_TO_UTF8(ui->service->currentText()); -#ifdef BROWSER_AVAILABLE + bool hidden_key = false; +#ifdef AUTH_ENABLED OAuth::DeleteCookies(service); + hidden_key = Auth::IsKeyHidden(service); #endif ui->bandwidthTestEnable->setChecked(false); @@ -592,8 +652,11 @@ void OBSBasicSettings::on_disconnectAccount_clicked() ui->streamKeyWidget->setVisible(true); ui->streamKeyLabel->setVisible(true); ui->connectAccount2->setVisible(true); + ui->connectAccount->setVisible(true); ui->disconnectAccount->setVisible(false); ui->bandwidthTestEnable->setVisible(false); + ui->authSignedInAs->setVisible(false); + //ui->authSignedInAsLabel->setVisible(false);# ui->twitchAddonDropdown->setVisible(false); ui->twitchAddonLabel->setVisible(false); ui->key->setText(""); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index d0ee1a1d636950..24edeffba2fc22 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -3702,6 +3702,13 @@ bool OBSBasicSettings::QueryChanges() if (button == QMessageBox::Cancel) { return false; } else if (button == QMessageBox::Yes) { + // If Caffeine account was linked and disconnected reset the auth here. +#if CAFFEINE_ENABLED + if (ui->connectAccount2->isVisible()) { + main->auth.reset(); + auth.reset(); + } +#endif SaveSettings(); } else { if (savedTheme != App()->GetTheme()) diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 4b5d352f20c42a..0a5867723c2650 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -250,6 +250,10 @@ class OBSBasicSettings : public QDialog { QString lastService; int prevLangIndex; bool prevBrowserAccel; + QString lastServiceKey; +#if CAFFEINE_ENABLED + QString lastSignedInAs; +#endif private slots: void UpdateServerList(); void UpdateKeyLink(); diff --git a/UI/window-caffeine.cpp b/UI/window-caffeine.cpp new file mode 100644 index 00000000000000..ffff9a932b9605 --- /dev/null +++ b/UI/window-caffeine.cpp @@ -0,0 +1,120 @@ +#include "window-caffeine.hpp" +#include "window-basic-main.hpp" + +#include "ui_CaffeinePanel.h" + +#include +#include + +void CaffeineInfoPanel::registerDockWidget() +{ + this->setObjectName(QStringLiteral("caffeineDock")); + this->setWindowTitle(QTStr("Caffeine.Dock")); + this->setVisible(true); + OBSBasic::Get()->AddDockWidget(this); +} + +void CaffeineInfoPanel::updateClicked(bool) +{ + std::string title = std::string(ui->title->text().toUtf8().data()); + caff_Rating rating = ui->rating->itemData(ui->rating->currentIndex()) + .value(); + + setTitle(title); + setRating(rating); + + obs_service_t *service = OBSBasic::Get()->GetService(); + if (service) { + obs_data_t *data = obs_service_get_settings(service); + obs_data_set_string(data, "broadcast_title", + getTitle().c_str()); + obs_data_set_int(data, "rating", + static_cast(getRating())); + obs_service_update(service, data); + obs_data_release(data); + } +} + +void CaffeineInfoPanel::viewOnWebClicked(bool) +{ + QUrl url; + // Set the Caffeine URL default or staging + const char *host = getenv("LIBCAFFEINE_DOMAIN") == NULL + ? "caffeine.tv" + : "www.staging.caffeine.tv"; + url.setHost(host); + url.setPath(QString::fromStdString("/" + owner->GetUsername())); + url.setScheme("https"); + QDesktopServices::openUrl(url); +} + +CaffeineInfoPanel::CaffeineInfoPanel(CaffeineAuth *owner, + caff_InstanceHandle instance) + : OBSDock(OBSBasic::Get()), + owner(owner), + ui(new Ui::CaffeinePanel), + caffeineInstance(instance) +{ + ui->setupUi(this); + + // Set up ratings. + ui->rating->clear(); + ui->rating->addItem(QTStr("Caffeine.Rating.None"), + QVariant(caff_RatingNone)); + ui->rating->addItem(QTStr("Caffeine.Rating.SeventeenPlus"), + QVariant(caff_RatingSeventeenPlus)); + ui->rating->setCurrentIndex(static_cast(getRating())); + + // Set up title + ui->title->setText(QString::fromStdString(getTitle())); + ui->title->setAttribute(Qt::WA_MacShowFocusRect, false); + + // Buttons + connect(ui->updateButton, SIGNAL(clicked(bool)), + SLOT(updateClicked(bool))); + connect(ui->viewOnWebBtn, SIGNAL(clicked(bool)), + SLOT(viewOnWebClicked(bool))); + + this->registerDockWidget(); +} + +CaffeineInfoPanel::~CaffeineInfoPanel() +{ + // Remove the Panel from OBS + OBSBasic::Get()->RemoveCaffeineDockWidget(this); +} + +std::string CaffeineInfoPanel::getTitle() +{ + OBSBasic *main = OBSBasic::Get(); + config_set_default_string(main->Config(), "Caffeine", "Title", + tr("Caffeine.Title").toUtf8().data()); + const char *title = + config_get_string(main->Config(), "Caffeine", "Title"); + if (!title) { + return std::string(tr("Caffeine.Title").toUtf8().data()); + } + return title; +} + +void CaffeineInfoPanel::setTitle(std::string title) +{ + OBSBasic *main = OBSBasic::Get(); + config_set_string(main->Config(), "Caffeine", "Title", title.c_str()); +} + +caff_Rating CaffeineInfoPanel::getRating() +{ + OBSBasic *main = OBSBasic::Get(); + config_set_default_int(main->Config(), "Caffeine", "Rating", + caff_RatingNone); + return static_cast( + config_get_int(main->Config(), "Caffeine", "Rating")); +} + +void CaffeineInfoPanel::setRating(caff_Rating rating) +{ + OBSBasic *main = OBSBasic::Get(); + config_set_int(main->Config(), "Caffeine", "Rating", + static_cast(rating)); +} diff --git a/UI/window-caffeine.hpp b/UI/window-caffeine.hpp new file mode 100644 index 00000000000000..0230fc0c67537b --- /dev/null +++ b/UI/window-caffeine.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "window-dock.hpp" +#include "auth-caffeine.hpp" + +#include + +Q_DECLARE_METATYPE(caff_Rating); + +namespace Ui { +class CaffeinePanel; +} + +class CaffeineInfoPanel : public OBSDock { + Q_OBJECT + +private: + CaffeineAuth *owner; + std::shared_ptr ui; + caff_InstanceHandle caffeineInstance; + + void registerDockWidget(); + +public slots: + void updateClicked(bool); + + void viewOnWebClicked(bool); + +public: + CaffeineInfoPanel(CaffeineAuth *owner, caff_InstanceHandle instance); + virtual ~CaffeineInfoPanel() override; + + std::string getTitle(); + void setTitle(std::string title); + + caff_Rating getRating(); + void setRating(caff_Rating rating); +}; diff --git a/cmake/Modules/CopyMSVCBins.cmake b/cmake/Modules/CopyMSVCBins.cmake index 3bebaae5000079..76bf1d9cd0fbb2 100644 --- a/cmake/Modules/CopyMSVCBins.cmake +++ b/cmake/Modules/CopyMSVCBins.cmake @@ -19,6 +19,13 @@ else() set(_bin_suffix 32) endif() +set(LIBCAFFEINE_BIN_REL_FILES + ${LIBCAFFEINE_BINARY_RELWITHDEBINFO} +) +set(LIBCAFFEINE_BIN_DBG_FILES + ${LIBCAFFEINE_BINARY_DEBUG} +) + file(GLOB FFMPEG_BIN_FILES "${FFMPEG_avcodec_INCLUDE_DIR}/../bin/avcodec-*.dll" "${FFMPEG_avcodec_INCLUDE_DIR}/../bin${_bin_suffix}/avcodec-*.dll" @@ -209,10 +216,12 @@ set(ALL_BASE_BIN_FILES ${QT_ICU_BIN_FILES}) set(ALL_REL_BIN_FILES - ${QT_BIN_FILES}) + ${QT_BIN_FILES} + ${LIBCAFFEINE_BIN_REL_FILES}) set(ALL_DBG_BIN_FILES - ${QT_DEBUG_BIN_FILES}) + ${QT_DEBUG_BIN_FILES} + ${LIBCAFFEINE_BIN_DBG_FILES}) set(ALL_PLATFORM_BIN_FILES) set(ALL_PLATFORM_REL_BIN_FILES @@ -249,6 +258,7 @@ foreach(list endif() endforeach() +message(STATUS "Libcaffeine files: ${LIBCAFFEINE_BIN_REL_FILES} ${LIBCAFFEINE_BIN_DBG_FILES}") message(STATUS "FFmpeg files: ${FFMPEG_BIN_FILES}") message(STATUS "x264 files: ${X264_BIN_FILES}") message(STATUS "Libfdk files: ${LIBFDK_BIN_FILES}") From 3394b6e597f04b9a40089dbe1542282455834fa0 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Tue, 5 May 2020 13:15:43 -0700 Subject: [PATCH 08/29] COBS-63 Create build script (#70) * COBS-63 Create build script Created Build script with 4 options: -help -check -build -cmbuild build and cmbuild used for configuring and generating project * Updated the README Updated the README to include steps to run automated build script. * Updated README * Updated README Added more details of environment variables to be set * Updated README Included Mac build steps from source * COBS-63 Create Install Script Fixed Errorlevel * Update the script * Update the script --- README.rst | 97 ++++++++++++++++++++++++++++++++++ build.bat | 80 ++++++++++++++++++++++++++++ cmake/Modules/ObsHelpers.cmake | 4 +- 3 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 build.bat diff --git a/README.rst b/README.rst index 682b3abe13e71a..33b55dd78a1086 100644 --- a/README.rst +++ b/README.rst @@ -39,6 +39,103 @@ Quick Links - Bug Tracker: https://github.com/obsproject/obs-studio/issues +Building from source +-------------------- + + Clone the repository and submodules + .. code-block:: text + + git clone --recursive https://github.com/obsproject/obs-studio.git + +**Windows:** + + Install the following prerequisites: + + 1. `Qt 5.14.1 `_ + + 2. `Cmake `_ + + 3. Pre-built windows dependencies for VS2017 https://obsproject.com/downloads/dependencies2017.zip + 4. `Visual Studio 2019 `_ + 5. `LLVM `_ + 6. `Embedded chrome browser library `_ version 08/29/2018 - CEF 3.3440.1806.g65046b7 / Chromium 68.0.3440.106 + + After installing the `prerequisites `_ .Create the following environment variables: + + #. **QTDIR** - Path pointing Qt 5.14.1 msvc2017_64 folder + + #. **obsInstallerTempDir** - Empty directory path + + #. **DepsPath** - Path to pre-built windows dependencies win64/include + + #. **LIBCAFFEINE_DIR** - Path to `prebuilt libcaffeine `_ folder + + #. **CEF_ROOT_DIR** - Path to the embedded chrome browser library + + Run the automated build script: ``build.bat [OPTION]`` + + .. csv-table:: + :header: "Option", "Usage" + :widths: 20, 80 + + "*-help*", "To display the supported options." + "*-check*", "To verify project prerequisites are set." + "*-build*", "To build 64 bit version of obs-studio." + "*-package*", "To build package." + + +**Mac:** + + Install following prerequisites: + + - Homebrew + - `Qt `_ + + Build steps: + + Open Terminal + + 1. Install FFmpeg + + .. code-block:: text + + brew tap homebrew-ffmpeg/ffmpeg + + brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-srt + + 2. Set environment variable for Qt + + .. code-block:: text + + export QTDIR="path/to/Qt" + + export DYLID_FRAMEWORK_PATH="path/to/Qt/5.14.1/clang_64/lib" + + 3. Install cmake + + .. code-block:: text + + brew install cmake + + 4. Change directory obs-studio directory + + .. code-block:: text + + mkdir build + + cd build + + cmake .. & make + + 5. After it built successfully then run the app + + .. code-block:: text + + cd rundir/RelWithDebInfo/bin + + ./obs + + Contributing ------------ diff --git a/build.bat b/build.bat new file mode 100644 index 00000000000000..79871b4df2996a --- /dev/null +++ b/build.bat @@ -0,0 +1,80 @@ +@echo off + +setlocal +set ERRORLEVEL=0 +endlocal + +goto :MAIN + +:: @function to check environment variables/dependencies for this project +:-check + echo Checking required dependencies... + if not defined QTDIR goto :error + if not defined obsInstallerTempDir goto :error + if not defined DepsPath goto :error + if not defined LIBCAFFEINE_DIR goto :error + if not defined CEF_ROOT_DIR goto :error + echo Successfully found all the dependencies. + goto:EOF + + +:: @function to clean the build directories +:clean + IF EXIST %~1 ( + del /q /f %~1\* && for /d %%x in (%~1\*) do @rd /s /q "%%x" + ) ELSE ( + mkdir %~1 + ) + goto:EOF + + +:: @function to build 64 bit version of COBS +:-build + call :-check + if ERRORLEVEL 1 goto:EOF + call :clean build64 + echo Building 64 bit version + cmake -H. -Bbuild64 -G "Visual Studio 16 2019" -A"x64" -T"host=x64" -DCOPIED_DEPENDENCIES=OFF -DENABLE_SCRIPTING=ON -DENABLE_UI=ON -DCOMPILE_D3D12_HOOK=ON -DDepsPath="%DepsPath%" -DQTDIR="%QTDIR%" -DLIBCAFFEINE_DIR="%LIBCAFFEINE_DIR%" -DCEF_ROOT_DIR="%CEF_ROOT_DIR%" -DBUILD_BROWSER=ON + cmake --build build64 --config RelWithDebInfo + echo Built 64 bit COBS + goto:EOF + + +:: @function to create package COBS +:-package + call :-check + if ERRORLEVEL 1 goto:EOF + call :clean cmbuild + echo Configuring and generating cmbuild... + cmake -H. -Bcmbuild -G "Visual Studio 16 2019" -A"x64" -T"host=x64" -DINSTALLER_RUN=ON -DCMAKE_INSTALL_PREFIX=%obsInstallerTempDir% + cmake --build cmbuild --config RelWithDebInfo --target PACKAGE + echo Build complete. + goto:EOF + + +:: @function to print the supported options +:-help + echo Supported options are: + echo -help : Print this output. + echo -check : Checks environment variables/ dependencies for this project. + echo -build : Builds 64 bit version of obs. + echo -package : Builds package. + goto:EOF + + +:: @function for error handling +:error + echo Error missing dependencies.Check if environment variables are set for QTDIR, obsInstallerTempDir, DepsPath, LIBCAFFEINE_DIR or CEF_ROOT_DIR. + EXIT /b 1 + goto:EOF + + +:: @function entry point for the script +:MAIN + if [%1]==[] ( + echo Please choose from supported options: + call:-help + goto:EOF + ) + call :%~1 + goto:EOF diff --git a/cmake/Modules/ObsHelpers.cmake b/cmake/Modules/ObsHelpers.cmake index 013d34e8982731..63efccf5b4bd89 100644 --- a/cmake/Modules/ObsHelpers.cmake +++ b/cmake/Modules/ObsHelpers.cmake @@ -446,7 +446,7 @@ function(install_obs_bin target mode) install(FILES "${bin}" DESTINATION "${OBS_EXECUTABLE_DESTINATION}") - if(DEFINED ${obsInstallerTempDir}) + if(DEFINED ENV{obsInstallerTempDir}) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "${bin}" @@ -496,7 +496,7 @@ function(install_obs_data target datadir datadest) "${CMAKE_CURRENT_SOURCE_DIR}/${datadir}" "${OBS_OUTPUT_DIR}/$/data/${datadest}" VERBATIM) - if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND DEFINED ${obsInstallerTempDir}) + if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND DEFINED ENV{obsInstallerTempDir}) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/${datadir}" "${obsInstallerTempDir}/${OBS_DATA_DESTINATION}/${datadest}" From ebfa86676bac7beca622b942054f72cf4adf3765 Mon Sep 17 00:00:00 2001 From: prateekshadas <61755734+prateekshadas@users.noreply.github.com> Date: Mon, 9 Mar 2020 11:41:16 -0700 Subject: [PATCH 09/29] COBS-62 Help link in COBS should link to our help center Changed the help link to point to caffeine help center --- UI/window-basic-main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 175925a5611cd4..387e93b99ab52b 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -6454,7 +6454,7 @@ void OBSBasic::on_settingsButton_clicked() void OBSBasic::on_actionHelpPortal_triggered() { - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); + QUrl url = QUrl("https://caffeine.custhelp.com", QUrl::TolerantMode); QDesktopServices::openUrl(url); } From 26452b48d9903506c1e31bbeb048f3d6f2c2a2ac Mon Sep 17 00:00:00 2001 From: Michael Fabian Dirks Date: Tue, 16 Jun 2020 19:32:39 +0200 Subject: [PATCH 10/29] caffeine: Handle audio in a secondary thread (#74) By moving all audio processing into another thread, we can avoid putting excessive load on the audio thread which isn't expecting this additional work load at all. This fixes the issue where using Caffeine would increase the audio buffer continuously until it couldn't be increased anymore. --- plugins/caffeine/caffeine-output.c | 231 +++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 47 deletions(-) diff --git a/plugins/caffeine/caffeine-output.c b/plugins/caffeine/caffeine-output.c index a14575ebe514db..73a7a5fbfb9996 100644 --- a/plugins/caffeine/caffeine-output.c +++ b/plugins/caffeine/caffeine-output.c @@ -35,6 +35,20 @@ dstr_free(&message); \ } while (false) +#define CAFF_AUDIO_FORMAT AUDIO_FORMAT_16BIT +#define CAFF_AUDIO_FORMAT_TYPE int16_t +#define CAFF_AUDIO_LAYOUT SPEAKERS_STEREO +#define CAFF_AUDIO_LAYOUT_MUL 2 +#define CAFF_AUDIO_SAMPLERATE 48000ul +#define NANOSECONDS 1000000000ull + +static int const enforced_height = 720; + +struct caffeine_audio { + struct audio_data *frames; + struct caffeine_audio *next; +}; + struct caffeine_output { obs_output_t *output; caff_InstanceHandle instance; @@ -47,8 +61,41 @@ struct caffeine_output { pthread_t monitor_thread; char *foreground_process; char *game_id; + + pthread_t audio_thread; + struct caffeine_audio *audio_queue; + pthread_mutex_t audio_lock; + pthread_cond_t audio_cond; + bool audio_stop; }; +static void audio_data_copy(struct audio_data *left, struct audio_data *right) +{ + for (int idx = 0; idx < MAX_AV_PLANES; idx++) { + if (!left->data[idx]) { + right->data[idx] = NULL; + } else { + size_t size = left->frames * + sizeof(CAFF_AUDIO_FORMAT_TYPE) * + CAFF_AUDIO_LAYOUT_MUL; + right->data[idx] = bmalloc(size); + memcpy(right->data[idx], left->data[idx], size); + } + } + right->frames = left->frames; + right->timestamp = left->timestamp; +} + +static void audio_data_free(struct audio_data *ptr) +{ + for (int idx = 0; idx < MAX_AV_PLANES; idx++) { + if (!ptr->data[idx]) + continue; + bfree(ptr->data[idx]); + } + bfree(ptr); +} + static const char *caffeine_get_name(void *data) { UNUSED_PARAMETER(data); @@ -97,6 +144,75 @@ caff_VideoFormat obs_to_caffeine_format(enum video_format format) } } +static bool prepare_audio(struct caffeine_output *context, + const struct audio_data *frame, + struct audio_data *output) +{ + /* This fixes an issue where unencoded outputs have video & audio out of sync + * + * Copied/adapted from obs-outputs/flv-output + */ + + *output = *frame; + + if (frame->timestamp < context->start_timestamp) { + uint64_t duration = ((uint64_t)frame->frames) * NANOSECONDS / + CAFF_AUDIO_SAMPLERATE; + uint64_t end_ts = (frame->timestamp + duration); + uint64_t cutoff; + + if (end_ts <= context->start_timestamp) + return false; + + cutoff = context->start_timestamp - frame->timestamp; + output->timestamp += cutoff; + + cutoff = cutoff * CAFF_AUDIO_SAMPLERATE / NANOSECONDS; + + for (size_t i = 0; i < context->audio_planes; i++) + output->data[i] += + context->audio_size * (uint32_t)cutoff; + output->frames -= (uint32_t)cutoff; + } + + return true; +} + +static void *PTW32_CDECL caffeine_handle_audio(void *ptr) +{ + struct caffeine_output *context = (struct caffeine_output *)ptr; + + pthread_mutex_lock(&context->audio_lock); + while (!context->audio_stop) { + // Wait for a signal. + pthread_cond_wait(&context->audio_cond, &context->audio_lock); + if (context->audio_stop || !context->audio_queue) + continue; + + // Dequeue the front element. + while (context->audio_queue) { + // Grab the first element and unlock the mutex. + struct caffeine_audio *here = context->audio_queue; + context->audio_queue = here->next; + pthread_mutex_unlock(&context->audio_lock); + + // Send off the audio. + caff_sendAudio(context->instance, here->frames->data[0], + here->frames->frames); + + // Delete the dequeued element. + audio_data_free(here->frames); + bfree(here); + + // Lock the mutex again. + pthread_mutex_lock(&context->audio_lock); + } + } + pthread_mutex_unlock(&context->audio_lock); + + return NULL; +} + static void *caffeine_create(obs_data_t *settings, obs_output_t *output) { trace(); @@ -115,8 +231,6 @@ static void *caffeine_create(obs_data_t *settings, obs_output_t *output) static void caffeine_stream_started(void *data); static void caffeine_stream_failed(void *data, caff_Result error); -static int const enforced_height = 720; - static bool caffeine_authenticate(struct caffeine_output *context) { trace(); @@ -183,9 +297,10 @@ static bool caffeine_start(void *data) return false; } - struct audio_convert_info conversion = {.format = AUDIO_FORMAT_16BIT, - .speakers = SPEAKERS_STEREO, - .samples_per_sec = 48000}; + struct audio_convert_info conversion = {.format = CAFF_AUDIO_FORMAT, + .speakers = CAFF_AUDIO_LAYOUT, + .samples_per_sec = + CAFF_AUDIO_SAMPLERATE}; obs_output_set_audio_conversion(output, &conversion); context->audio_planes = @@ -196,6 +311,15 @@ static bool caffeine_start(void *data) if (!obs_output_can_begin_data_capture(output, 0)) return false; + { // Initialize Audio + context->audio_stop = false; + context->audio_queue = NULL; + pthread_mutex_init(&context->audio_lock, NULL); + pthread_cond_init(&context->audio_cond, NULL); + pthread_create(&context->audio_thread, NULL, + &caffeine_handle_audio, context); + } + obs_service_t *service = obs_output_get_service(output); obs_data_t *settings = obs_service_get_settings(service); char const *title = obs_data_get_string(settings, BROADCAST_TITLE_KEY); @@ -338,56 +462,47 @@ static void caffeine_raw_video(void *data, struct video_data *frame) width, height, timestampMicros); } -/* This fixes an issue where unencoded outputs have video & audio out of sync - * - * Copied/adapted from obs-outputs/flv-output - */ -static bool prepare_audio(struct caffeine_output *context, - const struct audio_data *frame, - struct audio_data *output) -{ - *output = *frame; - - const uint64_t NANOSECONDS = 1000000000; - const uint64_t SAMPLES = 48000; - - if (frame->timestamp < context->start_timestamp) { - uint64_t duration = - (uint64_t)frame->frames * NANOSECONDS / SAMPLES; - uint64_t end_ts = (frame->timestamp + duration); - uint64_t cutoff; - - if (end_ts <= context->start_timestamp) - return false; - - cutoff = context->start_timestamp - frame->timestamp; - output->timestamp += cutoff; - - cutoff = cutoff * SAMPLES / NANOSECONDS; - - for (size_t i = 0; i < context->audio_planes; i++) - output->data[i] += - context->audio_size * (uint32_t)cutoff; - output->frames -= (uint32_t)cutoff; - } - - return true; -} - static void caffeine_raw_audio(void *data, struct audio_data *frames) { #ifdef TRACE_FRAMES trace(); #endif struct caffeine_output *context = data; - struct audio_data in; + // Ensure that everything is initialized and still available. + if (context->audio_stop || !context->audio_lock || !context->audio_cond) + return; + + // Ensure that we are actually live and have started streaming. if (!context->start_timestamp) return; - if (!prepare_audio(context, frames, &in)) + + // Cut off or abort audio data if it does not start at our intended time. + struct audio_data in; + if (!prepare_audio(context, frames, &in)) { return; + } - caff_sendAudio(context->instance, in.data[0], in.frames); + // Create a copy of the audio data for queuing. + // ToDo: Can this be optimized to use circlebuf for data? + struct caffeine_audio *ca = + (struct caffeine_audio *)bmalloc(sizeof(struct caffeine_audio)); + ca->next = NULL; + ca->frames = (struct audio_data *)bmalloc(sizeof(struct audio_data)); + audio_data_copy(&in, ca->frames); + + // Enqueue Audio + pthread_mutex_lock(&context->audio_lock); + { + // Find the last element that we can write to. This looks a bit weird, + // but it is a pointer to the last 'next' entry that is valid. + struct caffeine_audio **tgt = &context->audio_queue; + while (((*tgt) != NULL) && ((*tgt)->next != NULL)) + tgt = &((*tgt)->next); + *tgt = ca; + } + pthread_cond_signal(&context->audio_cond); + pthread_mutex_unlock(&context->audio_lock); } static void caffeine_stop(void *data, uint64_t ts) @@ -404,6 +519,25 @@ static void caffeine_stop(void *data, uint64_t ts) pthread_join(context->monitor_thread, NULL); } + { // Clean up Audio + { // Signal thread to stop and join with it. + pthread_mutex_lock(&context->audio_lock); + context->audio_stop = true; + pthread_cond_signal(&context->audio_cond); + pthread_mutex_unlock(&context->audio_lock); + pthread_join(context->audio_thread, NULL); + pthread_mutex_destroy(&context->audio_lock); + pthread_cond_destroy(&context->audio_cond); + } + while (context->audio_queue) { + // clean up any remaining data. + struct caffeine_audio *here = context->audio_queue; + context->audio_queue = here->next; + audio_data_free(here->frames); + bfree(here); + } + } + caff_endBroadcast(context->instance); obs_output_end_data_capture(output); @@ -440,12 +574,15 @@ struct obs_output_info caffeine_output_info = { .flags = OBS_OUTPUT_AV | OBS_OUTPUT_SERVICE | OBS_OUTPUT_BANDWIDTH_TEST_DISABLED | OBS_OUTPUT_HARDWARE_ENCODING_DISABLED, + .get_name = caffeine_get_name, + .create = caffeine_create, + .destroy = caffeine_destroy, + .start = caffeine_start, + .stop = caffeine_stop, + .raw_video = caffeine_raw_video, .raw_audio = caffeine_raw_audio, - .stop = caffeine_stop, - .destroy = caffeine_destroy, - .get_congestion = caffeine_get_congestion, }; From 63e90c67f6cf87ddfb46e5ed39114c253a4b42fb Mon Sep 17 00:00:00 2001 From: turbodavidson <43543791+turbodavidson@users.noreply.github.com> Date: Tue, 16 Jun 2020 13:34:11 -0400 Subject: [PATCH 11/29] Caffeine: Always update video and audio pointers (#75) --- plugins/caffeine/caffeine-output.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/caffeine/caffeine-output.c b/plugins/caffeine/caffeine-output.c index 73a7a5fbfb9996..be1ac16e06ea67 100644 --- a/plugins/caffeine/caffeine-output.c +++ b/plugins/caffeine/caffeine-output.c @@ -265,6 +265,8 @@ static bool caffeine_start(void *data) struct caffeine_output *context = data; obs_output_t *output = context->output; + obs_output_set_media(output, obs_get_video(), obs_get_audio()); + switch (caff_checkVersion()) { case caff_ResultSuccess: break; From 3d8275d61b0ea30c87b532cd88e6d22ee819546c Mon Sep 17 00:00:00 2001 From: Michael Fabian Dirks Date: Wed, 24 Jun 2020 01:28:18 +0200 Subject: [PATCH 12/29] caffeine: Don't use windows only syntax for unix pthreads (#77) --- plugins/caffeine/caffeine-output.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/plugins/caffeine/caffeine-output.c b/plugins/caffeine/caffeine-output.c index be1ac16e06ea67..6bd08cc15f8983 100644 --- a/plugins/caffeine/caffeine-output.c +++ b/plugins/caffeine/caffeine-output.c @@ -178,7 +178,7 @@ static bool prepare_audio(struct caffeine_output *context, return true; } -static void *PTW32_CDECL caffeine_handle_audio(void *ptr) +static void *__cdecl caffeine_handle_audio(void *ptr) { struct caffeine_output *context = (struct caffeine_output *)ptr; @@ -222,10 +222,27 @@ static void *caffeine_create(obs_data_t *settings, obs_output_t *output) bzalloc(sizeof(struct caffeine_output)); context->output = output; + // Create mutex and condvar. + if (pthread_mutex_init(&context->audio_lock, NULL) != 0) { + goto fail; + } + if (pthread_cond_init(&context->audio_cond, NULL) != 0) { + goto fail; + } + /* TODO: can we get this from the CaffeineAuth object somehow? */ context->instance = caff_createInstance(); + if (!context->instance) { + goto fail; + } return context; +fail: + pthread_mutex_destroy(&context->audio_lock); + pthread_cond_destroy(&context->audio_cond); + caff_freeInstance(context->instance); + bfree(context); + return NULL; } static void caffeine_stream_started(void *data); @@ -316,8 +333,6 @@ static bool caffeine_start(void *data) { // Initialize Audio context->audio_stop = false; context->audio_queue = NULL; - pthread_mutex_init(&context->audio_lock, NULL); - pthread_cond_init(&context->audio_cond, NULL); pthread_create(&context->audio_thread, NULL, &caffeine_handle_audio, context); } @@ -472,7 +487,7 @@ static void caffeine_raw_audio(void *data, struct audio_data *frames) struct caffeine_output *context = data; // Ensure that everything is initialized and still available. - if (context->audio_stop || !context->audio_lock || !context->audio_cond) + if (context->audio_stop) return; // Ensure that we are actually live and have started streaming. @@ -528,8 +543,6 @@ static void caffeine_stop(void *data, uint64_t ts) pthread_cond_signal(&context->audio_cond); pthread_mutex_unlock(&context->audio_lock); pthread_join(context->audio_thread, NULL); - pthread_mutex_destroy(&context->audio_lock); - pthread_cond_destroy(&context->audio_cond); } while (context->audio_queue) { // clean up any remaining data. @@ -551,6 +564,10 @@ static void caffeine_destroy(void *data) struct caffeine_output *context = data; caff_freeInstance(&context->instance); + // Free mutex and condvar. + pthread_mutex_destroy(&context->audio_lock); + pthread_cond_destroy(&context->audio_cond); + bfree(data); } From dc8cb4168058ead85c10271970204e25b2bdb145 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Thu, 25 Jun 2020 08:24:33 -0700 Subject: [PATCH 13/29] =?UTF-8?q?COBS-77=20Inaccurate=20error=20message=20?= =?UTF-8?q?is=20displayed=20when=20attempting=20to=20=E2=80=98Start=20Stre?= =?UTF-8?q?aming=E2=80=99=20without=20being=20connected=20to=20a=20Caffein?= =?UTF-8?q?e=20account=20(#78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UI/ui-validation.cpp | 10 +++++++++- plugins/rtmp-services/rtmp-common.c | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/UI/ui-validation.cpp b/UI/ui-validation.cpp index e441cbb6a49125..082a5ac26780ad 100644 --- a/UI/ui-validation.cpp +++ b/UI/ui-validation.cpp @@ -1,5 +1,6 @@ #include "ui-validation.hpp" +#include "ui-config.h" #include #include #include @@ -75,7 +76,13 @@ UIValidation::StreamSettingsConfirmation(QWidget *parent, OBSService service) return StreamSettingsAction::ContinueStream; QString msg; - +#if CAFFEINE_ENABLED + if (hasStreamUrl) { + return StreamSettingsAction::ContinueStream; + } else { + msg = QTStr("Basic.Settings.Stream.MissingUrl"); + } +#else if (!hasStreamUrl && !hasStreamKey) { msg = QTStr("Basic.Settings.Stream.MissingUrlAndApiKey"); } else if (!hasStreamKey) { @@ -83,6 +90,7 @@ UIValidation::StreamSettingsConfirmation(QWidget *parent, OBSService service) } else { msg = QTStr("Basic.Settings.Stream.MissingUrl"); } +#endif QMessageBox messageBox(parent); messageBox.setWindowTitle( diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index 2284a6c541f8d8..1d930d6b49a05d 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -315,11 +315,15 @@ static json_t *open_services_file(void) char *file; json_t *root = NULL; + // Don't use the file from user config. + // Temporary fix until we land caffeine service in OBS mainline. + /* file = obs_module_config_path("services.json"); if (file) { root = open_json_file(file); bfree(file); } + */ if (!root) { file = obs_module_file("services.json"); From 63808e3c7c7c6a73173bb234183cec6ed416363f Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Mon, 27 Jul 2020 09:29:55 -0700 Subject: [PATCH 14/29] Updated the COBS to include Github Actions (#80) Added to libcaffeine lib to build for cobs --- .github/workflows/main.yml | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f86883fc1d353..91ed86ca2fdf39 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,19 +5,18 @@ on: paths-ignore: - '**.md' branches: - - master - tags: - - '*' + - caffeine pull_request: paths-ignore: - '**.md' branches: - - master + - caffeine env: MACOS_CEF_BUILD_VERSION: '4183' LINUX_CEF_BUILD_VERSION: '3770' CEF_VERSION: '75.1.16+g16a67c4+chromium-75.0.3770.100' + LIBCAFFEINE_VERSION: '0.6.3' jobs: macos64: @@ -137,13 +136,19 @@ jobs: make -j4 mkdir libcef_dll cd ../../ + - name: 'Install prerequisites: Libcaffeine' + shell: bash + run: | + curl -L -O https://github.com/caffeinetv/libcaffeine/releases/download/v${{ env.LIBCAFFEINE_VERSION }}/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-macos.7z + brew install p7zip + 7z x libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-macos.7z -o${{ github.workspace }}/cmbuild - name: 'Configure' shell: bash run: | mkdir ./build cd ./build LEGACY_BROWSER="$(test "${{ env.MACOS_CEF_BUILD_VERSION }}" -le 3770 && echo "ON" || echo "OFF")" - cmake -DENABLE_UNIT_TESTS=YES -DENABLE_SPARKLE_UPDATER=ON -DDISABLE_PYTHON=ON -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ env.MIN_MACOS_VERSION }} -DQTDIR="/tmp/obsdeps" -DSWIGDIR="/tmp/obsdeps" -DDepsPath="/tmp/obsdeps" -DVLCPath="${{ github.workspace }}/cmbuild/vlc-${{ env.VLC_VERSION }}" -DENABLE_VLC=ON -DBUILD_BROWSER=ON -DBROWSER_LEGACY=$LEGACY_BROWSER -DWITH_RTMPS=ON -DCEF_ROOT_DIR="${{ github.workspace }}/cmbuild/cef_binary_${{ env.MACOS_CEF_BUILD_VERSION }}_macosx64" .. + cmake -DENABLE_UNIT_TESTS=YES -DENABLE_SPARKLE_UPDATER=ON -DDISABLE_PYTHON=ON -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ env.MIN_MACOS_VERSION }} -DQTDIR="/tmp/obsdeps" -DSWIGDIR="/tmp/obsdeps" -DDepsPath="/tmp/obsdeps" -DVLCPath="${{ github.workspace }}/cmbuild/vlc-${{ env.VLC_VERSION }}" -DENABLE_VLC=ON -DBUILD_BROWSER=ON -DBROWSER_LEGACY=$LEGACY_BROWSER -DWITH_RTMPS=ON -DCEF_ROOT_DIR="${{ github.workspace }}/cmbuild/cef_binary_${{ env.MACOS_CEF_BUILD_VERSION }}_macosx64" -DLIBCAFFEINE_DIR="${{ github.workspace }}/cmbuild/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-macos" .. - name: 'Build' shell: bash working-directory: ${{ github.workspace }}/build @@ -556,12 +561,16 @@ jobs: run: | curl -kL https://cdn-fastly.obsproject.com/downloads/cef_binary_${{ env.CEF_VERSION }}_windows64_minimal.zip -f --retry 5 -o cef.zip 7z x cef.zip -o"${{ github.workspace }}/cmbuild" + - name: 'Installing prerequisites: Libcaffeine' + run: | + curl -kLO https://github.com/caffeinetv/libcaffeine/releases/download/v${{ env.LIBCAFFEINE_VERSION }}/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z + 7z x libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z -o"${{ github.workspace }}/cmbuild/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows" - name: 'Configure' run: | mkdir ./build mkdir ./build64 cd ./build64 - cmake -G"${{ env.CMAKE_GENERATOR }}" -A"x64" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=true -DCOMPILE_D3D12_HOOK=true -DVLCPath="${{ github.workspace }}/cmbuild/vlc" -DDepsPath="${{ github.workspace }}/cmbuild/deps/win64" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2019_64" -DENABLE_VLC=ON -DCEF_ROOT_DIR="${{ github.workspace }}/cmbuild/cef_binary_${{ env.CEF_VERSION }}_windows64_minimal" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE -DVIRTUALCAM_GUID=${{ env.VIRTUALCAM-GUID }} .. + cmake -G"${{ env.CMAKE_GENERATOR }}" -A"x64" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=true -DCOMPILE_D3D12_HOOK=true -DVLCPath="${{ github.workspace }}/cmbuild/vlc" -DDepsPath="${{ github.workspace }}/cmbuild/deps/win64" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2019_64" -DENABLE_VLC=ON -DCEF_ROOT_DIR="${{ github.workspace }}/cmbuild/cef_binary_${{ env.CEF_VERSION }}_windows64_minimal" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE -DVIRTUALCAM_GUID=${{ env.VIRTUALCAM-GUID }} -DLIBCAFFEINE_DIR="${{ github.workspace }}/cmbuild/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows" .. - name: 'Build' run: msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-studio.sln - name: 'Package' @@ -668,12 +677,16 @@ jobs: run: | curl -kL https://cdn-fastly.obsproject.com/downloads/cef_binary_${{ env.CEF_VERSION }}_windows32_minimal.zip -f --retry 5 -o cef.zip 7z x cef.zip -o"${{ github.workspace }}/cmbuild" + - name: 'Installing prerequisites: Libcaffeine' + run: | + curl -kLO https://github.com/caffeinetv/libcaffeine/releases/download/v${{ env.LIBCAFFEINE_VERSION }}/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z + 7z x libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z -o"${{ github.workspace }}/cmbuild/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows" - name: 'Configure' run: | mkdir ./build mkdir ./build32 cd ./build32 - cmake -G"${{ env.CMAKE_GENERATOR }}" -A"Win32" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DENABLE_VLC=ON -DBUILD_BROWSER=true -DCOMPILE_D3D12_HOOK=true -DVLCPath="${{ github.workspace }}/cmbuild/vlc" -DDepsPath="${{ github.workspace }}/cmbuild/deps/win32" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2019" -DCEF_ROOT_DIR="${{ github.workspace }}/cmbuild/cef_binary_${{ env.CEF_VERSION }}_windows32_minimal" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE -DVIRTUALCAM_GUID=${{ env.VIRTUALCAM-GUID }} .. + cmake -G"${{ env.CMAKE_GENERATOR }}" -A"Win32" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DENABLE_VLC=ON -DBUILD_BROWSER=true -DCOMPILE_D3D12_HOOK=true -DVLCPath="${{ github.workspace }}/cmbuild/vlc" -DDepsPath="${{ github.workspace }}/cmbuild/deps/win32" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2019" -DCEF_ROOT_DIR="${{ github.workspace }}/cmbuild/cef_binary_${{ env.CEF_VERSION }}_windows32_minimal" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE -DVIRTUALCAM_GUID=${{ env.VIRTUALCAM-GUID }} -DLIBCAFFEINE_DIR="${{ github.workspace }}/cmbuild/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows" .. - name: 'Build' run: msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-studio.sln - name: 'Package' @@ -689,4 +702,4 @@ jobs: uses: actions/upload-artifact@v2.2.0 with: name: '${{ env.FILE_NAME }}' - path: '*-win32.zip' + path: '*-win32.zip' \ No newline at end of file From eedd0a92e2993e77b9053d685246b24349bb1639 Mon Sep 17 00:00:00 2001 From: turbodavidson <43543791+turbodavidson@users.noreply.github.com> Date: Tue, 28 Jul 2020 14:25:01 -0400 Subject: [PATCH 15/29] Fixed queue traversal (#84) --- plugins/caffeine/caffeine-output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/caffeine/caffeine-output.c b/plugins/caffeine/caffeine-output.c index 6bd08cc15f8983..b7cc4c534bc75b 100644 --- a/plugins/caffeine/caffeine-output.c +++ b/plugins/caffeine/caffeine-output.c @@ -514,7 +514,7 @@ static void caffeine_raw_audio(void *data, struct audio_data *frames) // Find the last element that we can write to. This looks a bit weird, // but it is a pointer to the last 'next' entry that is valid. struct caffeine_audio **tgt = &context->audio_queue; - while (((*tgt) != NULL) && ((*tgt)->next != NULL)) + while ((*tgt) != NULL) tgt = &((*tgt)->next); *tgt = ca; } From 540b2891d3d288e4b38b2e675365ec5302f7f61a Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Thu, 30 Jul 2020 16:40:04 -0700 Subject: [PATCH 16/29] COBS-66 Caffeine dock is unlisted when opening COBS with a saved (#86) --- UI/window-basic-auto-config.cpp | 9 +++++---- UI/window-basic-settings-stream.cpp | 8 ++++---- UI/window-basic-settings.cpp | 6 ++++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index 4a3265786d701f..909ba840a61303 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -467,10 +467,11 @@ void AutoConfigStreamPage::on_disconnectAccount_clicked() OBSBasic *main = OBSBasic::Get(); -#if !CAFFEINE_ENABLED - main->auth.reset(); - auth.reset(); -#endif + // Remove the auth here if caffeine account is not associated + if (ui->service->currentText() != "Caffeine") { + main->auth.reset(); + auth.reset(); + } std::string service = QT_TO_UTF8(ui->service->currentText()); diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 148a1bbd328a3c..8b94513266be7e 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -634,10 +634,10 @@ void OBSBasicSettings::on_disconnectAccount_clicked() } // Remove the auth here if caffeine account is not associated -#if !CAFFEINE_ENABLED - main->auth.reset(); - auth.reset(); -#endif + if (ui->service->currentText() != "Caffeine") { + main->auth.reset(); + auth.reset(); + } std::string service = QT_TO_UTF8(ui->service->currentText()); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 24edeffba2fc22..f8d40727590602 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -3769,6 +3769,12 @@ void OBSBasicSettings::on_buttonBox_clicked(QAbstractButton *button) if (val == QDialogButtonBox::ApplyRole || val == QDialogButtonBox::AcceptRole) { +#if CAFFEINE_ENABLED + if (ui->connectAccount2->isVisible() && main->auth) { + main->auth.reset(); + auth.reset(); + } +#endif SaveSettings(); ClearChanged(); } From 3f70ac73c9af37b92ec4b693590482d6265e6244 Mon Sep 17 00:00:00 2001 From: Adam Roach Date: Mon, 16 Nov 2020 18:30:04 -0600 Subject: [PATCH 17/29] Updating macOS brew dependency handling on CI (+4 squashed commits) Squashed commits: [a2e3d017] Fixing deprecated set-env directives from CI [f59fcb4a] Removing clang-format tests from Linux [f2cf2b27] Downgrading clang-format on mac to 8 on CI to match Linux version [b4d7668e] Updating per ./formatcode.sh (+1 squashed commit) Squashed commits: [fda039b5] Update to support newer brew on macOS --- .github/workflows/main.yml | 2 +- CI/scripts/macos/Brewfile | 2 +- build.bat | 13 +++-- plugins/obs-qsv11/CMakeLists.txt | 1 + plugins/obs-qsv11/QSV_Encoder.cpp | 17 ++++++ plugins/obs-qsv11/QSV_Encoder.h | 2 + plugins/obs-qsv11/QSV_Encoder_Internal.cpp | 66 +++++++++++++++++++++- plugins/obs-qsv11/QSV_Encoder_Internal.h | 3 + plugins/obs-qsv11/common_directx11.cpp | 44 ++++++++++++++- plugins/obs-qsv11/common_utils.h | 2 + plugins/obs-qsv11/obs-qsv11.c | 66 +++++++++++++++++++++- 11 files changed, 206 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 91ed86ca2fdf39..cb46cc8ac30f54 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -702,4 +702,4 @@ jobs: uses: actions/upload-artifact@v2.2.0 with: name: '${{ env.FILE_NAME }}' - path: '*-win32.zip' \ No newline at end of file + path: '*-win32.zip' diff --git a/CI/scripts/macos/Brewfile b/CI/scripts/macos/Brewfile index eff533552dacb5..c7b8c062de3e15 100644 --- a/CI/scripts/macos/Brewfile +++ b/CI/scripts/macos/Brewfile @@ -2,4 +2,4 @@ tap "akeru-inc/tap" brew "cmake" brew "freetype" brew "cmocka" -brew "akeru-inc/tap/xcnotary" \ No newline at end of file +brew "akeru-inc/tap/xcnotary" diff --git a/build.bat b/build.bat index 79871b4df2996a..53c6eb262ed158 100644 --- a/build.bat +++ b/build.bat @@ -13,7 +13,8 @@ goto :MAIN if not defined obsInstallerTempDir goto :error if not defined DepsPath goto :error if not defined LIBCAFFEINE_DIR goto :error - if not defined CEF_ROOT_DIR goto :error + if not defined CEF_ROOT_DIR goto :error + if not defined VLCPATH goto :error echo Successfully found all the dependencies. goto:EOF @@ -34,8 +35,10 @@ goto :MAIN if ERRORLEVEL 1 goto:EOF call :clean build64 echo Building 64 bit version - cmake -H. -Bbuild64 -G "Visual Studio 16 2019" -A"x64" -T"host=x64" -DCOPIED_DEPENDENCIES=OFF -DENABLE_SCRIPTING=ON -DENABLE_UI=ON -DCOMPILE_D3D12_HOOK=ON -DDepsPath="%DepsPath%" -DQTDIR="%QTDIR%" -DLIBCAFFEINE_DIR="%LIBCAFFEINE_DIR%" -DCEF_ROOT_DIR="%CEF_ROOT_DIR%" -DBUILD_BROWSER=ON - cmake --build build64 --config RelWithDebInfo + cd build64 + cmake -G"Visual Studio 16 2019" -A"x64" -DCMAKE_SYSTEM_VERSION="10.0.18363.657" -DBUILD_BROWSER=true -DCOMPILE_D3D12_HOOK=true -DDepsPath="%DepsPath%" -DQTDIR="%QTDIR%" -DENABLE_VLC=ON -DVLCPath="%VLCPATH%" -DCEF_ROOT_DIR="%CEF_ROOT_DIR%" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE -DLIBCAFFEINE_DIR="%LIBCAFFEINE_DIR%" .. + cd .. + msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-studio.sln echo Built 64 bit COBS goto:EOF @@ -64,7 +67,7 @@ goto :MAIN :: @function for error handling :error - echo Error missing dependencies.Check if environment variables are set for QTDIR, obsInstallerTempDir, DepsPath, LIBCAFFEINE_DIR or CEF_ROOT_DIR. + echo Error missing dependencies.Check if environment variables are set for QTDIR, obsInstallerTempDir, DepsPath, LIBCAFFEINE_DIR, CEF_ROOT_DIR or VLCPATH. EXIT /b 1 goto:EOF @@ -77,4 +80,4 @@ goto :MAIN goto:EOF ) call :%~1 - goto:EOF + goto:EOF \ No newline at end of file diff --git a/plugins/obs-qsv11/CMakeLists.txt b/plugins/obs-qsv11/CMakeLists.txt index a9b598fa3bcecf..6e5c6411ee62fb 100644 --- a/plugins/obs-qsv11/CMakeLists.txt +++ b/plugins/obs-qsv11/CMakeLists.txt @@ -87,6 +87,7 @@ target_link_libraries(obs-qsv11 d3d11 dxva2 dxgi + dxguid ) target_compile_definitions(obs-qsv11 PRIVATE DX11_D3D) diff --git a/plugins/obs-qsv11/QSV_Encoder.cpp b/plugins/obs-qsv11/QSV_Encoder.cpp index 91d990cbf0b65a..bad3521063c946 100644 --- a/plugins/obs-qsv11/QSV_Encoder.cpp +++ b/plugins/obs-qsv11/QSV_Encoder.cpp @@ -273,6 +273,23 @@ int qsv_encoder_encode(qsv_t *pContext, uint64_t ts, uint8_t *pDataY, return -1; } +int qsv_encoder_encode_tex(qsv_t *pContext, uint64_t ts, uint32_t tex_handle, + uint64_t lock_key, uint64_t *next_key, + mfxBitstream **pBS) +{ + QSV_Encoder_Internal *pEncoder = (QSV_Encoder_Internal *)pContext; + mfxStatus sts = MFX_ERR_NONE; + + sts = pEncoder->Encode_tex(ts, tex_handle, lock_key, next_key, pBS); + + if (sts == MFX_ERR_NONE) + return 0; + else if (sts == MFX_ERR_MORE_DATA) + return 1; + else + return -1; +} + int qsv_encoder_close(qsv_t *pContext) { QSV_Encoder_Internal *pEncoder = (QSV_Encoder_Internal *)pContext; diff --git a/plugins/obs-qsv11/QSV_Encoder.h b/plugins/obs-qsv11/QSV_Encoder.h index 50dd8e0288888c..146b34c747d805 100644 --- a/plugins/obs-qsv11/QSV_Encoder.h +++ b/plugins/obs-qsv11/QSV_Encoder.h @@ -133,6 +133,8 @@ void qsv_encoder_version(unsigned short *major, unsigned short *minor); qsv_t *qsv_encoder_open(qsv_param_t *); int qsv_encoder_encode(qsv_t *, uint64_t, uint8_t *, uint8_t *, uint32_t, uint32_t, mfxBitstream **pBS); +int qsv_encoder_encode_tex(qsv_t *, uint64_t, uint32_t, uint64_t, uint64_t *, + mfxBitstream **pBS); int qsv_encoder_headers(qsv_t *, uint8_t **pSPS, uint8_t **pPPS, uint16_t *pnSPS, uint16_t *pnPPS); enum qsv_cpu_platform qsv_get_cpu_platform(); diff --git a/plugins/obs-qsv11/QSV_Encoder_Internal.cpp b/plugins/obs-qsv11/QSV_Encoder_Internal.cpp index 6031b6815f6ac8..757975dc845542 100644 --- a/plugins/obs-qsv11/QSV_Encoder_Internal.cpp +++ b/plugins/obs-qsv11/QSV_Encoder_Internal.cpp @@ -598,6 +598,70 @@ mfxStatus QSV_Encoder_Internal::Encode(uint64_t ts, uint8_t *pDataY, return sts; } +mfxStatus QSV_Encoder_Internal::Encode_tex(uint64_t ts, uint32_t tex_handle, + uint64_t lock_key, + uint64_t *next_key, + mfxBitstream **pBS) +{ + mfxStatus sts = MFX_ERR_NONE; + *pBS = NULL; + int nTaskIdx = GetFreeTaskIndex(m_pTaskPool, m_nTaskPool); + int nSurfIdx = GetFreeSurfaceIndex(m_pmfxSurfaces, m_nSurfNum); + + while (MFX_ERR_NOT_FOUND == nTaskIdx || MFX_ERR_NOT_FOUND == nSurfIdx) { + // No more free tasks or surfaces, need to sync + sts = m_session.SyncOperation( + m_pTaskPool[m_nFirstSyncTask].syncp, 60000); + MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts); + + mfxU8 *pTemp = m_outBitstream.Data; + memcpy(&m_outBitstream, &m_pTaskPool[m_nFirstSyncTask].mfxBS, + sizeof(mfxBitstream)); + + m_pTaskPool[m_nFirstSyncTask].mfxBS.Data = pTemp; + m_pTaskPool[m_nFirstSyncTask].mfxBS.DataLength = 0; + m_pTaskPool[m_nFirstSyncTask].mfxBS.DataOffset = 0; + m_pTaskPool[m_nFirstSyncTask].syncp = NULL; + nTaskIdx = m_nFirstSyncTask; + m_nFirstSyncTask = (m_nFirstSyncTask + 1) % m_nTaskPool; + *pBS = &m_outBitstream; + + nSurfIdx = GetFreeSurfaceIndex(m_pmfxSurfaces, m_nSurfNum); + } + + mfxFrameSurface1 *pSurface = m_pmfxSurfaces[nSurfIdx]; + //copy to default surface directly + pSurface->Data.TimeStamp = ts; + if (m_bUseD3D11 || m_bD3D9HACK) { + sts = simple_copytex(m_mfxAllocator.pthis, pSurface->Data.MemId, + tex_handle, lock_key, next_key); + MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts); + } + + for (;;) { + // Encode a frame asynchronously (returns immediately) + sts = m_pmfxENC->EncodeFrameAsync(NULL, pSurface, + &m_pTaskPool[nTaskIdx].mfxBS, + &m_pTaskPool[nTaskIdx].syncp); + + if (MFX_ERR_NONE < sts && !m_pTaskPool[nTaskIdx].syncp) { + // Repeat the call if warning and no output + if (MFX_WRN_DEVICE_BUSY == sts) + MSDK_SLEEP( + 1); // Wait if device is busy, then repeat the same call + } else if (MFX_ERR_NONE < sts && m_pTaskPool[nTaskIdx].syncp) { + sts = MFX_ERR_NONE; // Ignore warnings if output is available + break; + } else if (MFX_ERR_NOT_ENOUGH_BUFFER == sts) { + // Allocate more bitstream buffer memory here if needed... + break; + } else + break; + } + + return sts; +} + mfxStatus QSV_Encoder_Internal::Drain() { mfxStatus sts = MFX_ERR_NONE; @@ -670,4 +734,4 @@ mfxStatus QSV_Encoder_Internal::Reset(qsv_param_t *pParams) MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts); return sts; -} +} \ No newline at end of file diff --git a/plugins/obs-qsv11/QSV_Encoder_Internal.h b/plugins/obs-qsv11/QSV_Encoder_Internal.h index 7f4a7c98dbfe1c..712244b836c9be 100644 --- a/plugins/obs-qsv11/QSV_Encoder_Internal.h +++ b/plugins/obs-qsv11/QSV_Encoder_Internal.h @@ -70,6 +70,9 @@ class QSV_Encoder_Internal { mfxStatus Encode(uint64_t ts, uint8_t *pDataY, uint8_t *pDataUV, uint32_t strideY, uint32_t strideUV, mfxBitstream **pBS); + mfxStatus Encode_tex(uint64_t ts, uint32_t tex_handle, + uint64_t lock_key, uint64_t *next_key, + mfxBitstream **pBS); mfxStatus ClearData(); mfxStatus Reset(qsv_param_t *pParams); diff --git a/plugins/obs-qsv11/common_directx11.cpp b/plugins/obs-qsv11/common_directx11.cpp index 41120af598f850..3df68716ac3fcd 100644 --- a/plugins/obs-qsv11/common_directx11.cpp +++ b/plugins/obs-qsv11/common_directx11.cpp @@ -57,8 +57,8 @@ IDXGIAdapter *GetIntelDeviceAdapterHandle(mfxSession session) } } - HRESULT hres = CreateDXGIFactory(__uuidof(IDXGIFactory2), - (void **)(&g_pDXGIFactory)); + HRESULT hres = CreateDXGIFactory1(__uuidof(IDXGIFactory2), + (void **)(&g_pDXGIFactory)); if (FAILED(hres)) return NULL; @@ -433,6 +433,46 @@ mfxStatus simple_unlock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr) return MFX_ERR_NONE; } +mfxStatus simple_copytex(mfxHDL pthis, mfxMemId mid, mfxU32 tex_handle, + mfxU64 lock_key, mfxU64 *next_key) +{ + pthis; // To suppress warning for this unused parameter + + CustomMemId *memId = (CustomMemId *)mid; + ID3D11Texture2D *pSurface = (ID3D11Texture2D *)memId->memId; + + IDXGIKeyedMutex *km; + ID3D11Texture2D *input_tex; + HRESULT hr; + + hr = g_pD3D11Device->OpenSharedResource((HANDLE)(uintptr_t)tex_handle, + IID_ID3D11Texture2D, + (void **)&input_tex); + if (FAILED(hr)) { + return MFX_ERR_INVALID_HANDLE; + } + + hr = input_tex->QueryInterface(IID_IDXGIKeyedMutex, (void **)&km); + if (FAILED(hr)) { + input_tex->Release(); + return MFX_ERR_INVALID_HANDLE; + } + + input_tex->SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM); + + km->AcquireSync(lock_key, INFINITE); + + D3D11_TEXTURE2D_DESC desc = {0}; + input_tex->GetDesc(&desc); + D3D11_BOX SrcBox = {0, 0, 0, desc.Width, desc.Height, 1}; + g_pD3D11Ctx->CopySubresourceRegion(pSurface, 0, 0, 0, 0, input_tex, 0, + &SrcBox); + + km->ReleaseSync(*next_key); + + return MFX_ERR_NONE; +} + mfxStatus simple_gethdl(mfxHDL pthis, mfxMemId mid, mfxHDL *handle) { pthis; // To suppress warning for this unused parameter diff --git a/plugins/obs-qsv11/common_utils.h b/plugins/obs-qsv11/common_utils.h index a29e48f32feac0..25568ebe7bcca1 100644 --- a/plugins/obs-qsv11/common_utils.h +++ b/plugins/obs-qsv11/common_utils.h @@ -95,6 +95,8 @@ mfxStatus simple_lock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr); mfxStatus simple_unlock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr); mfxStatus simple_gethdl(mfxHDL pthis, mfxMemId mid, mfxHDL *handle); mfxStatus simple_free(mfxHDL pthis, mfxFrameAllocResponse *response); +mfxStatus simple_copytex(mfxHDL pthis, mfxMemId mid, mfxU32 tex_handle, + mfxU64 lock_key, mfxU64 *next_key); // ================================================================= // Utility functions, not directly tied to Media SDK functionality diff --git a/plugins/obs-qsv11/obs-qsv11.c b/plugins/obs-qsv11/obs-qsv11.c index 25feeea5315db0..186c568cd2c0e4 100644 --- a/plugins/obs-qsv11/obs-qsv11.c +++ b/plugins/obs-qsv11/obs-qsv11.c @@ -60,6 +60,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include #ifndef _STDINT_H_INCLUDED #define _STDINT_H_INCLUDED @@ -970,8 +972,51 @@ static bool obs_qsv_encode(void *data, struct encoder_frame *frame, return true; } +static bool obs_qsv_encode_tex(void *data, uint32_t handle, int64_t pts, + uint64_t lock_key, uint64_t *next_key, + struct encoder_packet *packet, + bool *received_packet) +{ + struct obs_qsv *obsqsv = data; + + if (handle == GS_INVALID_HANDLE) { + warn("Encode failed: bad texture handle"); + *next_key = lock_key; + return false; + } + + if (!packet || !received_packet) + return false; + + EnterCriticalSection(&g_QsvCs); + + video_t *video = obs_encoder_video(obsqsv->encoder); + const struct video_output_info *voi = video_output_get_info(video); + + mfxBitstream *pBS = NULL; + + int ret; + + mfxU64 qsvPTS = pts * 90000 / voi->fps_num; + + ret = qsv_encoder_encode_tex(obsqsv->context, qsvPTS, handle, lock_key, + next_key, &pBS); + + if (ret < 0) { + warn("encode failed"); + LeaveCriticalSection(&g_QsvCs); + return false; + } + + parse_packet(obsqsv, packet, pBS, voi->fps_num, received_packet); + + LeaveCriticalSection(&g_QsvCs); + + return true; +} + struct obs_encoder_info obs_qsv_encoder = { - .id = "obs_qsv11", + .id = "obs_qsv11_soft", .type = OBS_ENCODER_VIDEO, .codec = "h264", .get_name = obs_qsv_getname, @@ -984,5 +1029,22 @@ struct obs_encoder_info obs_qsv_encoder = { .get_extra_data = obs_qsv_extra_data, .get_sei_data = obs_qsv_sei, .get_video_info = obs_qsv_video_info, - .caps = OBS_ENCODER_CAP_DYN_BITRATE, + .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL, +}; + +struct obs_encoder_info obs_qsv_encoder_tex = { + .id = "obs_qsv11", + .type = OBS_ENCODER_VIDEO, + .codec = "h264", + .get_name = obs_qsv_getname, + .create = obs_qsv_create_tex, + .destroy = obs_qsv_destroy, + .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_PASS_TEXTURE, + .encode_texture = obs_qsv_encode_tex, + .update = obs_qsv_update, + .get_properties = obs_qsv_props, + .get_defaults = obs_qsv_defaults, + .get_extra_data = obs_qsv_extra_data, + .get_sei_data = obs_qsv_sei, + .get_video_info = obs_qsv_video_info, }; From d404c080cdb77eef036441d23d2f796f43d96991 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Tue, 15 Dec 2020 10:15:14 -0800 Subject: [PATCH 18/29] COBS-116 Caffeine account username is no longer visible in Settings->Stream screen (#90) --- UI/forms/OBSBasicSettings.ui | 31 +++++++++++++++++++---------- UI/window-basic-settings-stream.cpp | 21 ++++++++++--------- UI/window-basic-settings.hpp | 1 + 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index 47365422bc56d3..5bd19a84decd5b 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -986,17 +986,6 @@ - - - - - - TextLabel - - - - - @@ -1286,6 +1275,26 @@ + + + + QFrame::NoFrame + + + Basic.Settings.Stream.Custom.SignedInAs + + + + + + + QFrame::NoFrame + + + SomeUser + + + diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 8b94513266be7e..64697f2c48ea0b 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -41,7 +41,7 @@ void OBSBasicSettings::InitStreamPage() ui->disconnectAccount->setVisible(false); ui->bandwidthTestEnable->setVisible(false); ui->authSignedInAs->setVisible(false); - //ui->authSignedInAsLabel->setVisible(false); + ui->authSignedInAsLabel->setVisible(false); ui->twitchAddonDropdown->setVisible(false); ui->twitchAddonLabel->setVisible(false); @@ -156,12 +156,9 @@ void OBSBasicSettings::LoadStream1Settings() #if CAFFEINE_ENABLED if (username && username[0]) { lastSignedInAs = username; - //ui->authSignedInAsLabel->setVisible(true); + ui->authSignedInAsLabel->setVisible(true); ui->authSignedInAs->setVisible(true); ui->authSignedInAs->setText(QT_UTF8(username)); - } else { - //ui->authSignedInAsLabel->setVisible(false); - ui->authSignedInAs->setVisible(false); } #endif } @@ -433,12 +430,12 @@ void OBSBasicSettings::on_service_currentIndexChanged(int) bool isCaffeine = service == "Caffeine"; ui->authSignedInAs->setVisible(authenticated && isCaffeine); ui->authSignedInAs->setText(lastSignedInAs); + ui->authSignedInAsLabel->setVisible(authenticated && isCaffeine); #endif ui->connectAccount->setVisible(can_auth && !authenticated); ui->disconnectAccount->setVisible(can_auth && authenticated); ui->connectAccount2->setVisible(can_auth && !authenticated); -#else - ui->connectAccount2->setVisible(false); + #endif ui->useAuth->setVisible(custom); @@ -569,7 +566,7 @@ void OBSBasicSettings::OnOAuthStreamKeyConnected() if (std::string(a->service()) == "Caffeine") { auto caffeine = dynamic_cast(a); lastSignedInAs = caffeine->GetUsername().c_str(); - //ui->authSignedInAsLabel->setVisible(true); + ui->authSignedInAsLabel->setVisible(true); ui->authSignedInAs->setVisible(true); ui->authSignedInAs->setText(lastSignedInAs); } @@ -656,10 +653,16 @@ void OBSBasicSettings::on_disconnectAccount_clicked() ui->disconnectAccount->setVisible(false); ui->bandwidthTestEnable->setVisible(false); ui->authSignedInAs->setVisible(false); - //ui->authSignedInAsLabel->setVisible(false);# + ui->authSignedInAsLabel->setVisible(false); ui->twitchAddonDropdown->setVisible(false); ui->twitchAddonLabel->setVisible(false); ui->key->setText(""); + +// Hide stream keys for Caffeine we do not support it. +#if CAFFEINE_ENABLED + ui->streamKeyWidget->setVisible(false); + ui->streamKeyLabel->setVisible(false); +#endif } void OBSBasicSettings::on_useStreamKey_clicked() diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 0a5867723c2650..cff0ae54c87735 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -29,6 +29,7 @@ #include #include "auth-base.hpp" +#include "ui-config.h" class OBSBasic; class QAbstractButton; From a51331f5fbda248c6322b970d6ecf147aefc8228 Mon Sep 17 00:00:00 2001 From: adamroach Date: Thu, 17 Dec 2020 11:08:16 -0600 Subject: [PATCH 19/29] Adding ./bootstrap.sh to help set up OS X build (#91) * Adding ./bootstrap.sh to help set up OS X build * Renaming to match OS --- .gitignore | 5 ++ bootstrap-osx.sh | 166 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100755 bootstrap-osx.sh diff --git a/.gitignore b/.gitignore index 747a7c65a9fcfb..bf3092b2bc23d2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,11 @@ .ninja* .dirstamp +#dependencies +/libcaffeine-v0.6.1-macos/ +/vlc-3.0.8/ +/CI/scripts/macos/Brewfile.lock.json + #xcode *.xcodeproj/ diff --git a/bootstrap-osx.sh b/bootstrap-osx.sh new file mode 100755 index 00000000000000..437b1de8830ffe --- /dev/null +++ b/bootstrap-osx.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +set -e + +VLC_VERSION=3.0.8 +SPARKLE_VERSION=1.23.0 +LIBCAFFEINE_VERSION=0.6.1 +QT_VERSION=5.14.1 +CEF_BUILD_VERSION=75.1.14+gc81164e+chromium-75.0.3770.100 +OBSDEPS_VERSION=2020-04-24 + +if ! which brew >> /dev/null +then + echo Installing homebrew + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +fi + +echo Installing dependencies from homebrew +brew update --preinstall +brew bundle --file ./CI/scripts/macos/Brewfile + + +echo Checking for VLC +VLC_PATH=$PWD/vlc-${VLC_VERSION} +if [ ! -d $VLC_PATH ] +then + [ -e ./vlc-${VLC_VERSION}.tar.xz ] || \ + curl -L -O https://downloads.videolan.org/vlc/${VLC_VERSION}/vlc-${VLC_VERSION}.tar.xz + echo Uncompressing VLC + tar xf vlc-${VLC_VERSION}.tar.xz + rm vlc-${VLC_VERSION}.tar.xz +fi + +echo Checking for Sparkle +if [ ! -d cmbuild/sparkle/Sparkle.framework ] +then + mkdir -p cmbuild/sparkle + [ -e ./sparkle.tar.bz2 ] || \ + curl -L -o sparkle.tar.bz2 https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.bz2 + echo Uncompressing sparkle + tar xf ./sparkle.tar.bz2 -C cmbuild/sparkle + rm ./sparkle.tar.bz2 +fi + +[ -d /Library/Frameworks/Sparkle.framework ] || \ + sudo cp -R cmbuild/sparkle/Sparkle.framework /Library/Frameworks/Sparkle.framework + +echo Checking for OBS Project Dependencies +OBSDEPS_DIR=$PWD/cmbuild/obsdeps +if [ ! -d $OBSDEPS_DIR ] +then + pushd cmbuild + [ -e ./osx-deps-${OBSDEPS_VERSION}.tar.gz ] || \ + curl -L -O https://github.com/obsproject/obs-deps/releases/download/$OBSDEPS_VERSION/osx-deps-${OBSDEPS_VERSION}.tar.gz + echo Uncompressing OBS dependencies + tar xf ./osx-deps-${OBSDEPS_VERSION}.tar.gz + rm ./osx-deps-${OBSDEPS_VERSION}.tar.gz + popd +fi +[ -e /tmp/obsdeps ] || ln -s $OBSDEPS_DIR /tmp/obsdeps + +echo Checking for libcaffeine +LIBCAFFEINE_DIR=$PWD/libcaffeine-v${LIBCAFFEINE_VERSION}-macos +if [ ! -d $LIBCAFFEINE_DIR ] +then + [ -e ./libcaffeine-v${LIBCAFFEINE_VERSION}-macos.7z ] || \ + curl -L -O https://github.com/caffeinetv/libcaffeine/releases/download/v${LIBCAFFEINE_VERSION}/libcaffeine-v${LIBCAFFEINE_VERSION}-macos.7z + which 7z >> /dev/null || brew install p7zip + echo Uncompressing libcaffeine + 7z x libcaffeine-v${LIBCAFFEINE_VERSION}-macos.7z + rm libcaffeine-v${LIBCAFFEINE_VERSION}-macos.7z +fi + +CEF_BASENAME=cef_binary_${CEF_BUILD_VERSION}_macosx64 +CEF_BASENAME_MINIMAL=${CEF_BASENAME}_minimal + +if [ -z "${CEF_ROOT_DIR}" ] +then + CEF_ROOT_DIR=$PWD/cmbuild/${CEF_BASENAME_MINIMAL} + echo "export CEF_ROOT_DIR=$CEF_ROOT_DIR" >> $HOME/.zshrc +fi + +which cmake >> /dev/null || brew install cmake + +if [ ! -e ./cmbuild/${CEF_BASENAME} ] +then + if [ ! -e ${CEF_ROOT_DIR} ] + then + echo "Downloading and building CEF (this will take a while)" + pushd cmbuild + CEF_TARFILE=`echo ${CEF_BASENAME_MINIMAL}.tar.bz2 | sed 's/+/%2B/g'` + if [ ! -e ./${CEF_TARFILE} ] + then + echo Getting http://opensource.spotify.com/cefbuilds/$CEF_TARFILE + curl -L -O http://opensource.spotify.com/cefbuilds/$CEF_TARFILE + fi + echo "Uncompressing CEF" + tar xf ./${CEF_TARFILE} + rm ${CEF_TARFILE} + mkdir ${CEF_BASENAME_MINIMAL}/build + cd ${CEF_BASENAME_MINIMAL}/build + echo "Building CEF" + cmake -DCMAKE_CXX_FLAGS="-std=c++11 -stdlib=libc++" -DCMAKE_EXE_LINKER_FLAGS="-std=c++11 -stdlib=libc++" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 .. + make -j + popd + fi + ln -s ${CEF_ROOT_DIR} ./cmbuild/${CEF_BASENAME} +fi + +echo Checking for QT +QT_DIR=/usr/local/Cellar/qt/$QT_VERSION +if [ ! -e $QT_DIR ] +then + pushd /tmp + curl -O https://gist.githubusercontent.com/DDRBoxman/9c7a2b08933166f4b61ed9a44b242609/raw/ef4de6c587c6bd7f50210eccd5bd51ff08e6de13/qt.rb + brew install ./qt.rb + popd +fi + +echo Checking for SWIG +if [ ! -e /usr/local/Cellar/swig/ ] +then + pushd /tmp + curl -O https://gist.githubusercontent.com/DDRBoxman/4cada55c51803a2f963fa40ce55c9d3e/raw/572c67e908bfbc1bcb8c476ea77ea3935133f5b5/swig.rb + brew install ./swig.rb + popd +fi + +echo Configuring and building +mkdir -p build +pushd build +cmake \ + -DENABLE_SPARKLE_UPDATER=ON \ + -DDISABLE_PYTHON=ON \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \ + -DQTDIR="$QT_DIR" \ + -DDepsPath="$OBSDEPS_DIR" \ + -DVLCPath="$VLC_PATH" \ + -DENABLE_VLC=ON \ + -DBUILD_BROWSER=ON \ + -DBROWSER_DEPLOY=ON \ + -DBUILD_CAPTIONS=ON \ + -DWITH_RTMPS=ON \ + -DCEF_ROOT_DIR="$CEF_ROOT_DIR" \ + -DLIBCAFFEINE_DIR="$LIBCAFFEINE_DIR" \ + .. +make -j +popd + + +cat < Date: Fri, 22 Jan 2021 10:28:49 -0800 Subject: [PATCH 20/29] COBS-96 Fixed Caffeine dock location not saved (#93) --- UI/auth-caffeine.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/UI/auth-caffeine.cpp b/UI/auth-caffeine.cpp index 1abd326b7ad6cd..115bb2b10fbdfb 100644 --- a/UI/auth-caffeine.cpp +++ b/UI/auth-caffeine.cpp @@ -124,11 +124,23 @@ void CaffeineAuth::LoadUI() return; /* TODO: Chat */ + OBSBasic *main = OBSBasic::Get(); // Panel panelDock = QSharedPointer( new CaffeineInfoPanel(this, instance)) .dynamicCast(); + // Set min size of panel so it doesn't get clipped when docked + panelDock->setMinimumSize(268, 270); + if (firstLoad) { + panelDock->setVisible(true); + } else { + const char *dockStateStr = config_get_string( + main->Config(), service(), "DockState"); + QByteArray dockState = + QByteArray::fromBase64(QByteArray(dockStateStr)); + main->restoreState(dockState); + } uiLoaded = true; return; } From 1d941ed5ffe209a2b205bca610eb439420400b41 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Wed, 10 Feb 2021 13:49:40 -0800 Subject: [PATCH 21/29] COBS-137 Appearance of Caffeine dock does not update when changing themes (#94) Removed the hardcoded stylesheet which now allows the dock window to consume obs-studio theme --- UI/forms/CaffeinePanel.ui | 50 ++++++++------------------------------- 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/UI/forms/CaffeinePanel.ui b/UI/forms/CaffeinePanel.ui index d01e457d4c7fa6..055633ca29bb69 100644 --- a/UI/forms/CaffeinePanel.ui +++ b/UI/forms/CaffeinePanel.ui @@ -11,8 +11,7 @@ - font-family: "Segoe UI"; -font-size: 14px; + true @@ -31,7 +30,7 @@ font-size: 14px; - background: rgb(34, 34, 34); + @@ -111,13 +110,10 @@ font-size: 14px; Segoe UI - -1 - font-family: "Segoe UI"; -font-size: 14px; -color: rgb(139, 139, 139); + Set your broadcasts rating and title: @@ -181,13 +177,11 @@ color: rgb(139, 139, 139); Segoe UI - -1 true - border: 1px solid rgb(72, 72, 72); -color: rgb(255, 255, 255); + @@ -213,12 +207,10 @@ color: rgb(255, 255, 255); Segoe UI - -1 - border: 1px solid rgb(72, 72, 72); -color: rgb(255, 255, 255); + LIVE on Caffeine! @@ -252,31 +244,19 @@ color: rgb(255, 255, 255); Segoe UI - -1 PointingHandCursor - QPushButton { - font-size: 12px; - color: rgb(0, 159, 224); -} - -QPushButton:hover { - color: rgb(255, 255, 255); -} - -QPushButton:pressed { - color: rgb(0, 159, 224); -} + Update rating and title - true + false @@ -472,23 +452,19 @@ QPushButton:pressed { Segoe UI - -1 PointingHandCursor - border-radius: 5px; -border: 1px solid rgb(151, 151, 151); -color: rgb(255, 255, 255); - + View on web - true + false @@ -538,16 +514,10 @@ color: rgb(255, 255, 255); Segoe UI - -1 - color: rgb(139, 139, 139); -line-height: 14px; -text-align: center; -font-size: 10px; -padding-left: 40px; -padding-right: 40px; + <html><body><p>View on web to see credits, viewers, and chat with your audience.</p></body></html> From 42d640b8ebd0229b0160d3a43d6cd371b909d510 Mon Sep 17 00:00:00 2001 From: turbodavidson <43543791+turbodavidson@users.noreply.github.com> Date: Thu, 18 Feb 2021 18:25:05 -0500 Subject: [PATCH 22/29] COBS-61 (#96) * Added sample logger * Removed windows header * Updated CMakeLists.txt * Added video frame window based on latest processed audio sample * Expanded sync window to +-3 Frames at 30fps. Seems to help dropping unneccisarily for small variations * Enhanced logging, set av timestamp to sample range center, drop video packets out of range, stall video pipeline if av desync causes drop * Format code * Updated the functions to work with macos * Formated the code * Fixed build error on windows * Added CAFFEINE_COBS_TEST_FRAME_DROP_PERCENT for dropping testing and dropping a percentage of frames * clang-format ran * added caff_min and caff_max for xp compatibility * clang-format again (caff_min name change put line just over length limit) Co-authored-by: prateekshadas <61755734+prateekshadas@users.noreply.github.com> --- plugins/caffeine/CMakeLists.txt | 8 +- plugins/caffeine/caffeine-output.c | 188 +++++++++++++++++++++- plugins/caffeine/caffeine-sample-logger.c | 104 ++++++++++++ plugins/caffeine/caffeine-sample-logger.h | 25 +++ plugins/caffeine/caffeine-stopwatch.c | 66 ++++++++ plugins/caffeine/caffeine-stopwatch.h | 19 +++ 6 files changed, 401 insertions(+), 9 deletions(-) create mode 100644 plugins/caffeine/caffeine-sample-logger.c create mode 100644 plugins/caffeine/caffeine-sample-logger.h create mode 100644 plugins/caffeine/caffeine-stopwatch.c create mode 100644 plugins/caffeine/caffeine-stopwatch.h diff --git a/plugins/caffeine/CMakeLists.txt b/plugins/caffeine/CMakeLists.txt index 8710d989af3d32..d0c11ddeb75de5 100644 --- a/plugins/caffeine/CMakeLists.txt +++ b/plugins/caffeine/CMakeLists.txt @@ -11,12 +11,16 @@ endif() set(caffeine_HEADERS caffeine-foreground-process.h - caffeine-settings.h) + caffeine-settings.h + caffeine-sample-logger.h + caffeine-stopwatch.h) set(caffeine_SOURCES caffeine-foreground-process.c caffeine-module.c - caffeine-output.c) + caffeine-output.c + caffeine-sample-logger.c + caffeine-stopwatch.c) add_library(caffeine MODULE ${caffeine_HEADERS} diff --git a/plugins/caffeine/caffeine-output.c b/plugins/caffeine/caffeine-output.c index b7cc4c534bc75b..a8830b8bb6a2e7 100644 --- a/plugins/caffeine/caffeine-output.c +++ b/plugins/caffeine/caffeine-output.c @@ -10,10 +10,21 @@ #include "caffeine-foreground-process.h" #include "caffeine-settings.h" +#include "caffeine-stopwatch.h" +#include "caffeine-sample-logger.h" -/* Uncomment this to log each call to raw_audio/video -#define TRACE_FRAMES -/**/ +/* Uncomment this to log each call to raw_audio/video */ + +//#define TRACE_FRAMES + +/* Uncomment these lines and change path to use the sample log +This is a simple debug tool, so I didn't add code to auto-create directories, etc +So you'll need to make sure the directory path is available before use. +This causes a little bit of macro salsa, but we can remove it later */ + +//#define USE_SAMPLE_LOG +//#define VIDEO_SAMPLE_LOG_FILE ("C:\\Users\\jond\\Desktop\\obs_logs\\caffeine_raw_video_samples_log.csv") +//#define AUDIO_SAMPLE_LOG_FILE ("C:\\Users\\jond\\Desktop\\obs_logs\\caffeine_raw_audio_samples_log.csv") #define do_log(level, format, ...) \ blog(level, "[caffeine output] " format, ##__VA_ARGS__) @@ -35,6 +46,9 @@ dstr_free(&message); \ } while (false) +#define caff_max(a, b) (((a) > (b)) ? (a) : (b)) +#define caff_min(a, b) (((a) < (b)) ? (a) : (b)) + #define CAFF_AUDIO_FORMAT AUDIO_FORMAT_16BIT #define CAFF_AUDIO_FORMAT_TYPE int16_t #define CAFF_AUDIO_LAYOUT SPEAKERS_STEREO @@ -43,6 +57,8 @@ #define NANOSECONDS 1000000000ull static int const enforced_height = 720; +static int const slow_connection_wait_ms = 66; +static uint64_t const av_sync_tolerance_window_ms = 105UL; struct caffeine_audio { struct audio_data *frames; @@ -54,6 +70,10 @@ struct caffeine_output { caff_InstanceHandle instance; struct obs_video_info video_info; uint64_t start_timestamp; + uint64_t timestamp_window_pos; + uint64_t timestamp_window_neg; + uint64_t video_last_timestamp; + uint64_t audio_last_timestamp; size_t audio_planes; size_t audio_size; @@ -67,6 +87,19 @@ struct caffeine_output { pthread_mutex_t audio_lock; pthread_cond_t audio_cond; bool audio_stop; + + bool is_slow_connection; + caffeine_stopwatch_t slow_connection_stopwatch; + + int test_frame_drop_percent; + +#ifdef USE_SAMPLE_LOG + caffeine_stopwatch_t sample_stopwatch; + uint64_t raw_video_left_func_timestamp_ns; + uint64_t raw_audio_left_func_timestamp_ns; + caffeine_sample_logger_t raw_video_sample_logger; + caffeine_sample_logger_t raw_audio_sample_logger; +#endif }; static void audio_data_copy(struct audio_data *left, struct audio_data *right) @@ -297,6 +330,44 @@ static bool caffeine_start(void *data) if (!caffeine_authenticate(context)) return false; + context->is_slow_connection = false; + const char *caffeine_slow_connection = + getenv("CAFFEINE_SLOW_CONNECTION"); + if (NULL != caffeine_slow_connection) { + context->is_slow_connection = + (1 == + caff_min(caff_max(0, atoi(caffeine_slow_connection)), + 1)); + } + + context->test_frame_drop_percent = 0; + + const char *test_frame_drop_percent = + getenv("CAFFEINE_COBS_TEST_FRAME_DROP_PERCENT"); + if (NULL != test_frame_drop_percent) { + context->test_frame_drop_percent = caff_min( + caff_max(0, atoi(test_frame_drop_percent)), 99); + } + if (context->test_frame_drop_percent > 0) { + srand(caff_max(0, (os_gettime_ns() % INT32_MAX) - 1)); + } + + caffeine_stopwatch_init(&context->slow_connection_stopwatch); + if (context->is_slow_connection) { + caffeine_stopwatch_start(&context->slow_connection_stopwatch); + } + +#ifdef USE_SAMPLE_LOG + context->raw_video_left_func_timestamp_ns = 0UL; + context->raw_audio_left_func_timestamp_ns = 0UL; + caffeine_stopwatch_init(&context->sample_stopwatch); + caffeine_stopwatch_start(&context->sample_stopwatch); + caffeine_sample_logger_init(&context->raw_audio_sample_logger, + AUDIO_SAMPLE_LOG_FILE); + caffeine_sample_logger_init(&context->raw_video_sample_logger, + VIDEO_SAMPLE_LOG_FILE); +#endif + if (!obs_get_video_info(&context->video_info)) { set_error(output, "Failed to get video info"); return false; @@ -463,8 +534,60 @@ static void caffeine_raw_video(void *data, struct video_data *frame) #ifdef TRACE_FRAMES trace(); #endif + struct caffeine_output *context = data; +#ifdef USE_SAMPLE_LOG + uint64_t func_called_timestamp_ns = + caffeine_stopwatch_get_elapsed_ns(&context->sample_stopwatch); +#endif + + const char *reason_none = ""; + const char *reason_slow_connection = "connection throttle"; + const char *reason_av_desync = "av desync"; + const char *reason_test_drop_frame_percent = "test frame drop percent"; + const char *send_frame_reason = reason_none; + + bool send_frame = true; + if (context->is_slow_connection) { + uint64_t slow_connection_ms = caffeine_stopwatch_get_elapsed_ms( + &context->slow_connection_stopwatch); + if (slow_connection_ms < slow_connection_wait_ms) { + send_frame_reason = reason_slow_connection; + send_frame = false; + } else { + caffeine_stopwatch_reset( + &context->slow_connection_stopwatch); + } + } + + if (!context->start_timestamp) + context->start_timestamp = frame->timestamp; + + uint64_t last_pair_timestamp_ns = context->audio_last_timestamp; + context->video_last_timestamp = frame->timestamp; + + bool should_stall = false; + uint64_t timestamp_window_pos = context->timestamp_window_pos; + uint64_t timestamp_window_neg = context->timestamp_window_neg; + if ((timestamp_window_pos > 0) && (timestamp_window_neg > 0)) { + if (frame->timestamp > context->timestamp_window_pos) { + send_frame_reason = reason_av_desync; + send_frame = false; + should_stall = true; + } else if (frame->timestamp < context->timestamp_window_neg) { + send_frame_reason = reason_av_desync; + send_frame = false; + } + } + + if (send_frame && (context->test_frame_drop_percent > 0)) { + send_frame = (rand() % 100) > context->test_frame_drop_percent; + if (!send_frame) { + send_frame_reason = reason_test_drop_frame_percent; + } + } + uint32_t width = context->video_info.output_width; uint32_t height = context->video_info.output_height; size_t total_bytes = frame->linesize[0] * height; @@ -472,11 +595,29 @@ static void caffeine_raw_video(void *data, struct video_data *frame) obs_to_caffeine_format(context->video_info.output_format); int64_t timestampMicros = frame->timestamp / 1000; - if (!context->start_timestamp) - context->start_timestamp = frame->timestamp; + if (send_frame) { + caff_sendVideo(context->instance, format, frame->data[0], + total_bytes, width, height, timestampMicros); + } + +#ifdef USE_SAMPLE_LOG + uint64_t func_complete_timestamp_ns = + caffeine_stopwatch_get_elapsed_ns(&context->sample_stopwatch); + uint64_t func_time_ns = + func_complete_timestamp_ns - func_called_timestamp_ns; + uint64_t obs_app_time_ns = func_called_timestamp_ns - + context->raw_video_left_func_timestamp_ns; + caffeine_sample_logger_log_sample( + &context->raw_video_sample_logger, send_frame, + send_frame_reason, func_called_timestamp_ns, frame->timestamp, + last_pair_timestamp_ns, func_time_ns, obs_app_time_ns); + context->raw_video_left_func_timestamp_ns = + caffeine_stopwatch_get_elapsed_ns(&context->sample_stopwatch); +#endif - caff_sendVideo(context->instance, format, frame->data[0], total_bytes, - width, height, timestampMicros); + if (should_stall) { // +105ms ahead. let's stall for 84ms since audio samples are coming in at 21.333 average + os_sleep_ms(84); + } } static void caffeine_raw_audio(void *data, struct audio_data *frames) @@ -486,6 +627,11 @@ static void caffeine_raw_audio(void *data, struct audio_data *frames) #endif struct caffeine_output *context = data; +#ifdef USE_SAMPLE_LOG + uint64_t func_called_timestamp_ns = + caffeine_stopwatch_get_elapsed_ns(&context->sample_stopwatch); +#endif + // Ensure that everything is initialized and still available. if (context->audio_stop) return; @@ -500,6 +646,18 @@ static void caffeine_raw_audio(void *data, struct audio_data *frames) return; } + uint64_t last_pair_timestamp_ns = context->video_last_timestamp; + + uint64_t duration = ((uint64_t)frames->frames) * NANOSECONDS / + CAFF_AUDIO_SAMPLERATE; + uint64_t end_ts = (frames->timestamp + duration); + uint64_t center_samples_ts = (frames->timestamp + end_ts) / 2; + context->audio_last_timestamp = center_samples_ts; + + const uint64_t timestamp_adj = av_sync_tolerance_window_ms * 1000000UL; + context->timestamp_window_pos = center_samples_ts + timestamp_adj; + context->timestamp_window_neg = center_samples_ts - timestamp_adj; + // Create a copy of the audio data for queuing. // ToDo: Can this be optimized to use circlebuf for data? struct caffeine_audio *ca = @@ -520,6 +678,22 @@ static void caffeine_raw_audio(void *data, struct audio_data *frames) } pthread_cond_signal(&context->audio_cond); pthread_mutex_unlock(&context->audio_lock); + +#ifdef USE_SAMPLE_LOG + uint64_t func_complete_timestamp_ns = + caffeine_stopwatch_get_elapsed_ns(&context->sample_stopwatch); + uint64_t func_time_ns = + func_complete_timestamp_ns - func_called_timestamp_ns; + uint64_t obs_app_time_ns = func_called_timestamp_ns - + context->raw_audio_left_func_timestamp_ns; + caffeine_sample_logger_log_sample(&context->raw_audio_sample_logger, + true, "", func_called_timestamp_ns, + center_samples_ts, + last_pair_timestamp_ns, func_time_ns, + obs_app_time_ns); + context->raw_audio_left_func_timestamp_ns = + caffeine_stopwatch_get_elapsed_ns(&context->sample_stopwatch); +#endif } static void caffeine_stop(void *data, uint64_t ts) diff --git a/plugins/caffeine/caffeine-sample-logger.c b/plugins/caffeine/caffeine-sample-logger.c new file mode 100644 index 00000000000000..45f2888489db71 --- /dev/null +++ b/plugins/caffeine/caffeine-sample-logger.c @@ -0,0 +1,104 @@ +#include "caffeine-sample-logger.h" +#include +#include + +void caffeine_sample_logger_log(caffeine_sample_logger_t *lpsl, + const char *format, ...) +{ + if (false == lpsl->is_ok) { + return; + } + va_list args; + memset(lpsl->line_buff, 0, sizeof(lpsl->line_buff)); + va_start(args, format); + int num_chars = vsnprintf(lpsl->line_buff, sizeof(lpsl->line_buff), + format, args); + va_end(args); + FILE *file_handle = fopen(lpsl->file_name, "a+b"); + if (file_handle) { + fwrite(lpsl->line_buff, 1, num_chars, file_handle); + fclose(file_handle); + } +} + +bool caffeine_sample_logger_init(caffeine_sample_logger_t *lpsl, + const char *file_name) +{ + memset(lpsl, 0, sizeof(caffeine_sample_logger_t)); + strncpy(lpsl->file_name, file_name, sizeof(lpsl->file_name)); + FILE *file_handle = fopen(lpsl->file_name, "w+b"); + if (file_handle) { + fclose(file_handle); + lpsl->is_ok = true; + } + caffeine_sample_logger_log(lpsl, + "sample" + ",sent to libcaffeine" + ",reason" + ",wall time (ms)" + ",wall time delta (ms)" + ",sample obs timestamp (ms)" + ",sample obs timestamp delta (ms)" + ",sample obs timestamp delta from pair (ms)" + ",caffeine func time (ms)" + ",caffeine func time delta (ms)" + ",obs app time (ms)" + ",obs app time delta (ms)" + "\r\n"); + return lpsl->is_ok; +} + +void caffeine_sample_logger_log_sample( + caffeine_sample_logger_t *lpsl, bool sample_sent_to_libcaffeine, + const char *reason_not_sent_to_libcaffeine, uint64_t wall_time_ns, + uint64_t sample_obs_timestamp_ns, uint64_t pair_obs_last_timestamp_ns, + uint64_t caffeine_func_time_ns, uint64_t obs_app_time_ns) +{ + double wall_time_ms = (double)wall_time_ns / 1000000.0; + double sample_obs_timestamp_ms = + (double)sample_obs_timestamp_ns / 1000000.0; + double caffeine_func_time_ms = + (double)caffeine_func_time_ns / 1000000.0; + double obs_app_time_ms = (double)obs_app_time_ns / 1000000.0; + const char *sample_sent_yn = (true == sample_sent_to_libcaffeine) ? "Y" + : "N"; + double pair_obs_last_timestamp_ms = + (double)pair_obs_last_timestamp_ns / 1000000.0; + double pair_obs_timestamp_delta_from_pair_ms = + sample_obs_timestamp_ms - pair_obs_last_timestamp_ms; + + if (lpsl->sample_cnt == 0) { + caffeine_sample_logger_log( + lpsl, "%d,%s,%s,%0.3f,0,%0.3f,0,0,%0.3f,0,%0.3f,0\r\n", + lpsl->sample_cnt, sample_sent_yn, + reason_not_sent_to_libcaffeine, wall_time_ms, + sample_obs_timestamp_ms, caffeine_func_time_ms, + obs_app_time_ms); + } else { + double wall_time_ms_delta = + wall_time_ms - lpsl->prev_wall_time_ms; + double sample_obs_timestamp_ms_delta = + sample_obs_timestamp_ms - + lpsl->prev_sample_obs_timestamp_ms; + double caffeine_func_time_ms_delta = + caffeine_func_time_ms - + lpsl->prev_caffeine_func_time_ms; + double obs_app_time_ms_delta = + obs_app_time_ms - lpsl->prev_obs_app_time_ms; + caffeine_sample_logger_log( + lpsl, + "%d,%s,%s,%0.3f,%0.3f,%0.3f,%0.3f,%0.3f,%0.3f,%0.3f,%0.3f,%0.3f\r\n", + lpsl->sample_cnt, sample_sent_yn, + reason_not_sent_to_libcaffeine, wall_time_ms, + wall_time_ms_delta, sample_obs_timestamp_ms, + sample_obs_timestamp_ms_delta, + pair_obs_timestamp_delta_from_pair_ms, + caffeine_func_time_ms, caffeine_func_time_ms_delta, + obs_app_time_ms, obs_app_time_ms_delta); + } + lpsl->prev_wall_time_ms = wall_time_ms; + lpsl->prev_sample_obs_timestamp_ms = sample_obs_timestamp_ms; + lpsl->prev_caffeine_func_time_ms = caffeine_func_time_ms; + lpsl->prev_obs_app_time_ms = obs_app_time_ms; + lpsl->sample_cnt++; +} diff --git a/plugins/caffeine/caffeine-sample-logger.h b/plugins/caffeine/caffeine-sample-logger.h new file mode 100644 index 00000000000000..b5493785f3108b --- /dev/null +++ b/plugins/caffeine/caffeine-sample-logger.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +typedef struct caffeine_sample_logger { + char file_name[1000]; + char line_buff[1000]; + bool is_ok; + int sample_cnt; + double prev_wall_time_ms; + double prev_sample_obs_timestamp_ms; + double prev_caffeine_func_time_ms; + double prev_obs_app_time_ms; +} caffeine_sample_logger_t; + +bool caffeine_sample_logger_init(caffeine_sample_logger_t *lpsl, + const char *file_name); + +void caffeine_sample_logger_log_sample( + caffeine_sample_logger_t *lpsl, bool sample_sent_to_libcaffeine, + const char *reason_not_sent_to_libcaffeine, uint64_t wall_time_ns, + uint64_t sample_obs_timestamp_ns, uint64_t pair_obs_last_timestamp_ns, + uint64_t caffeine_func_time_ns, uint64_t obs_app_time_ns); diff --git a/plugins/caffeine/caffeine-stopwatch.c b/plugins/caffeine/caffeine-stopwatch.c new file mode 100644 index 00000000000000..10200107727e2b --- /dev/null +++ b/plugins/caffeine/caffeine-stopwatch.c @@ -0,0 +1,66 @@ +#include "caffeine-stopwatch.h" +#include +#include + +void caffeine_stopwatch_init(caffeine_stopwatch_t *lpsw) +{ + memset(lpsw, 0, sizeof(caffeine_stopwatch_t)); +} + +void caffeine_stopwatch_start(caffeine_stopwatch_t *lpsw) +{ + uint64_t time_ns = 0UL; + if (!lpsw->running) { + time_ns = os_gettime_ns(); + lpsw->start_timestamp = time_ns; + lpsw->value_timestamp = time_ns; + lpsw->running = true; + } +} + +void caffeine_stopwatch_stop(caffeine_stopwatch_t *lpsw) +{ + uint64_t time_ns = 0UL; + if (!lpsw->running) { + time_ns = os_gettime_ns(); + lpsw->accumulator += + (lpsw->value_timestamp - lpsw->start_timestamp); + lpsw->start_timestamp = 0L; + lpsw->value_timestamp = 0L; + lpsw->running = false; + } +} + +void caffeine_stopwatch_reset(caffeine_stopwatch_t *lpsw) +{ + bool is_running = lpsw->running; + caffeine_stopwatch_init(lpsw); + if (is_running) { + caffeine_stopwatch_start(lpsw); + } +} + +uint64_t caffeine_stopwatch_get_elapsed_ns(caffeine_stopwatch_t *lpsw) +{ + uint64_t time_ns = 0UL; + uint64_t accumulation_total = lpsw->accumulator; + if (lpsw->running) { + time_ns = os_gettime_ns(); + lpsw->value_timestamp = time_ns; + accumulation_total += + lpsw->value_timestamp - lpsw->start_timestamp; + } + return accumulation_total; +} + +uint64_t caffeine_stopwatch_get_elapsed_ms(caffeine_stopwatch_t *lpsw) +{ + uint64_t elapsed_ns = caffeine_stopwatch_get_elapsed_ns(lpsw); + return elapsed_ns / 1000000UL; +} + +void caffeine_stopwatch_copy_state(caffeine_stopwatch_t *lpsw_dest, + caffeine_stopwatch_t *lpsw_src) +{ + memcpy(lpsw_dest, lpsw_src, sizeof(caffeine_stopwatch_t)); +} diff --git a/plugins/caffeine/caffeine-stopwatch.h b/plugins/caffeine/caffeine-stopwatch.h new file mode 100644 index 00000000000000..9ba38d5eb1030a --- /dev/null +++ b/plugins/caffeine/caffeine-stopwatch.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +typedef struct caffeine_stopwatch { + bool running; + uint64_t accumulator; + uint64_t start_timestamp; + uint64_t value_timestamp; +} caffeine_stopwatch_t; + +void caffeine_stopwatch_init(caffeine_stopwatch_t *lpsw); +void caffeine_stopwatch_start(caffeine_stopwatch_t *lpsw); +void caffeine_stopwatch_stop(caffeine_stopwatch_t *lpsw); +void caffeine_stopwatch_reset(caffeine_stopwatch_t *lpsw); +uint64_t caffeine_stopwatch_get_elapsed_ns(caffeine_stopwatch_t *lpsw); +uint64_t caffeine_stopwatch_get_elapsed_ms(caffeine_stopwatch_t *lpsw); +void caffeine_stopwatch_copy_state(caffeine_stopwatch_t *lpsw_dest, + caffeine_stopwatch_t *lpsw_src); From b92283b19353e9c98f1203c80417575ec56f17f9 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Thu, 18 Feb 2021 15:41:50 -0800 Subject: [PATCH 23/29] CONTENT-72 Implemented the error messages in cobs (#95) * CONTENT-72 Implemented the error messages in cobs * Fix clang formating * Added updated libcaffeine version * Updated CI libcaffeine folder --- .github/workflows/main.yml | 4 ++-- UI/auth-caffeine.cpp | 4 ++++ UI/data/locale/en-US.ini | 2 ++ plugins/caffeine/caffeine-output.c | 23 +++++++++++++++++++---- plugins/caffeine/data/locale/en-US.ini | 1 + 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cb46cc8ac30f54..abe582869de132 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -564,7 +564,7 @@ jobs: - name: 'Installing prerequisites: Libcaffeine' run: | curl -kLO https://github.com/caffeinetv/libcaffeine/releases/download/v${{ env.LIBCAFFEINE_VERSION }}/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z - 7z x libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z -o"${{ github.workspace }}/cmbuild/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows" + 7z x libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z -o"${{ github.workspace }}/cmbuild" - name: 'Configure' run: | mkdir ./build @@ -680,7 +680,7 @@ jobs: - name: 'Installing prerequisites: Libcaffeine' run: | curl -kLO https://github.com/caffeinetv/libcaffeine/releases/download/v${{ env.LIBCAFFEINE_VERSION }}/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z - 7z x libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z -o"${{ github.workspace }}/cmbuild/libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows" + 7z x libcaffeine-v${{ env.LIBCAFFEINE_VERSION }}-windows.7z -o"${{ github.workspace }}/cmbuild" - name: 'Configure' run: | mkdir ./build diff --git a/UI/auth-caffeine.cpp b/UI/auth-caffeine.cpp index 115bb2b10fbdfb..575f5fa7c144c7 100644 --- a/UI/auth-caffeine.cpp +++ b/UI/auth-caffeine.cpp @@ -244,6 +244,10 @@ void CaffeineAuth::TryAuth(Ui::CaffeineSignInDialog *ui, QDialog *dialog, ui->messageLabel->setText( Str("Caffeine.Auth.EmailVerificationRequired")); return; + case caff_ResultInternetDisconnected: + ui->messageLabel->setText( + Str("Caffeine.InternetDisconnected.Text")); + return; case caff_ResultFailure: default: ui->messageLabel->setText(Str("Caffeine.Auth.SigninFailed")); diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 80854dee2cd286..665bc029e079fe 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -1108,3 +1108,5 @@ Caffeine.Dock.ViewOnWebDescription="View on web to see credits, viewers,\nand ch Caffeine.Title="LIVE on Caffeine!" Caffeine.Rating.None="0+" Caffeine.Rating.SeventeenPlus="17+" +Caffeine.InternetDisconnected.Title="Internet Connection Lost" +Caffeine.InternetDisconnected.Text="You have lost your connection. Please reconnect" diff --git a/plugins/caffeine/caffeine-output.c b/plugins/caffeine/caffeine-output.c index a8830b8bb6a2e7..0ba0f38c5f59b5 100644 --- a/plugins/caffeine/caffeine-output.c +++ b/plugins/caffeine/caffeine-output.c @@ -145,8 +145,10 @@ static int caffeine_to_obs_error(caff_Result error) case caff_ResultFailure: case caff_ResultBroadcastFailed: return OBS_OUTPUT_CONNECT_FAILED; - case caff_ResultDisconnected: + case caff_ResultInternetDisconnected: return OBS_OUTPUT_DISCONNECTED; + case caff_ResultCaffeineUnreachable: + return OBS_OUTPUT_CONNECT_FAILED; case caff_ResultTakeover: default: return OBS_OUTPUT_ERROR; @@ -323,6 +325,13 @@ static bool caffeine_start(void *data) case caff_ResultOldVersion: set_error(output, "%s", obs_module_text("ErrorOldVersion")); return false; + case caff_ResultFailure: + if (caff_checkInternetConnection() == + caff_ResultInternetDisconnected) { + set_error(output, "%s", + obs_module_text("ErrorInternetDisconnected")); + return false; + } default: log_warn("Failed to complete Caffeine version check"); } @@ -516,9 +525,15 @@ static void caffeine_stream_failed(void *data, caff_Result error) struct caffeine_output *context = data; if (!obs_output_get_last_error(context->output)) { - set_error(context->output, "%s: [%d] %s", - obs_module_text("ErrorStartStream"), error, - caff_resultString(error)); + if (caff_checkInternetConnection() == + caff_ResultInternetDisconnected) { + set_error(context->output, "%s", + obs_module_text("ErrorInternetDisconnected")); + } else { + set_error(context->output, "%s: [%d] %s", + obs_module_text("ErrorStartStream"), error, + caff_resultString(error)); + } } if (context->is_online) { diff --git a/plugins/caffeine/data/locale/en-US.ini b/plugins/caffeine/data/locale/en-US.ini index 18c7ad53e897c9..7326df79f0c3de 100644 --- a/plugins/caffeine/data/locale/en-US.ini +++ b/plugins/caffeine/data/locale/en-US.ini @@ -7,3 +7,4 @@ ErrorAspectRatio="Caffeine requires using an output resolution of 1280x720. Go t ErrorVideoFormat="Caffeine requires using NV12 or I420 formats. Go to Settings > Advanced to change the Color Format." ErrorStartStream="Failed to start stream. Try again." WarningAlreadyStreaming="Caffeine is already broadcasting" +ErrorInternetDisconnected="Internet Connection Lost.\nYou have lost your connection. Please reconnect" From 388eb150ec19ffa5171e08e0ea59e3689dc84772 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Tue, 2 Mar 2021 08:05:24 -0800 Subject: [PATCH 24/29] COBS-163 Implement copy for error message for system overload (#97) * COBS-163 Implement copy for error message for system overload * Changed plugin to C++ * Implemented System Overload messaging * Added the Pop up message * Added frames tracking logic in separate files --- UI/data/locale/en-US.ini | 3 + UI/window-caffeine.cpp | 33 ++++- UI/window-caffeine.hpp | 7 + build.bat | 2 +- plugins/caffeine/CMakeLists.txt | 6 +- .../caffeine/caffeine-foreground-process.h | 8 ++ plugins/caffeine/caffeine-module.c | 4 +- ...{caffeine-output.c => caffeine-output.cpp} | 133 ++++++++++++------ plugins/caffeine/caffeine-sample-logger.h | 8 ++ plugins/caffeine/caffeine-stopwatch.h | 8 ++ plugins/caffeine/caffeine-tracked-frames.cpp | 66 +++++++++ plugins/caffeine/caffeine-tracked-frames.hpp | 25 ++++ 12 files changed, 256 insertions(+), 47 deletions(-) rename plugins/caffeine/{caffeine-output.c => caffeine-output.cpp} (86%) create mode 100644 plugins/caffeine/caffeine-tracked-frames.cpp create mode 100644 plugins/caffeine/caffeine-tracked-frames.hpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 665bc029e079fe..a894467e4bd0fe 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -1110,3 +1110,6 @@ Caffeine.Rating.None="0+" Caffeine.Rating.SeventeenPlus="17+" Caffeine.InternetDisconnected.Title="Internet Connection Lost" Caffeine.InternetDisconnected.Text="You have lost your connection. Please reconnect" +Caffeine.SystemOverload.Title="System Overload" +Caffeine.SystemOverload.Text="System under too much load. Broadcast quality may be degraded." +Caffeine.SystemOverload.CheckBox.Text="Message will not show again during this app session" \ No newline at end of file diff --git a/UI/window-caffeine.cpp b/UI/window-caffeine.cpp index ffff9a932b9605..4b025e007e2b76 100644 --- a/UI/window-caffeine.cpp +++ b/UI/window-caffeine.cpp @@ -53,7 +53,8 @@ CaffeineInfoPanel::CaffeineInfoPanel(CaffeineAuth *owner, : OBSDock(OBSBasic::Get()), owner(owner), ui(new Ui::CaffeinePanel), - caffeineInstance(instance) + caffeineInstance(instance), + checkDroppedFramesTimer(this) { ui->setupUi(this); @@ -75,12 +76,30 @@ CaffeineInfoPanel::CaffeineInfoPanel(CaffeineAuth *owner, connect(ui->viewOnWebBtn, SIGNAL(clicked(bool)), SLOT(viewOnWebClicked(bool))); + // Set timer + checkDroppedFramesTimer.setInterval(500); + connect(&checkDroppedFramesTimer, SIGNAL(timeout()), this, + SLOT(checkDroppedFrames())); + checkDroppedFramesTimer.start(); + + // Set up warning popup message + showWarningMessageBox.setWindowTitle( + QTStr("Caffeine.SystemOverload.Title")); + showWarningMessageBox.setText(QTStr("Caffeine.SystemOverload.Text")); + showWarningMessageBox.setModal(false); + showWarningMessageBox.setIcon(QMessageBox::Warning); + showWarningMessageBox.addButton(QMessageBox::Ok); + checkBox = + new QCheckBox(QTStr("Caffeine.SystemOverload.CheckBox.Text")); + showWarningMessageBox.setCheckBox(checkBox); + this->registerDockWidget(); } CaffeineInfoPanel::~CaffeineInfoPanel() { // Remove the Panel from OBS + checkDroppedFramesTimer.stop(); OBSBasic::Get()->RemoveCaffeineDockWidget(this); } @@ -118,3 +137,15 @@ void CaffeineInfoPanel::setRating(caff_Rating rating) config_set_int(main->Config(), "Caffeine", "Rating", static_cast(rating)); } + +void CaffeineInfoPanel::checkDroppedFrames() +{ + obs_service_t *service = OBSBasic::Get()->GetService(); + // Check OBS bool data variable - frames_dropped_above_threshold data variable + if (obs_data_get_bool(obs_service_get_settings(service), + "frames_dropped_above_threshold")) { + if (checkBox->checkState() == Qt::Unchecked) { + showWarningMessageBox.show(); + } + } +} diff --git a/UI/window-caffeine.hpp b/UI/window-caffeine.hpp index 0230fc0c67537b..7953e80e263e90 100644 --- a/UI/window-caffeine.hpp +++ b/UI/window-caffeine.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include +#include +#include #include "window-dock.hpp" #include "auth-caffeine.hpp" @@ -20,6 +23,9 @@ class CaffeineInfoPanel : public OBSDock { CaffeineAuth *owner; std::shared_ptr ui; caff_InstanceHandle caffeineInstance; + QTimer checkDroppedFramesTimer; + QMessageBox showWarningMessageBox; + QCheckBox *checkBox; void registerDockWidget(); @@ -27,6 +33,7 @@ public slots: void updateClicked(bool); void viewOnWebClicked(bool); + void checkDroppedFrames(); public: CaffeineInfoPanel(CaffeineAuth *owner, caff_InstanceHandle instance); diff --git a/build.bat b/build.bat index 53c6eb262ed158..d74bb33ef4d072 100644 --- a/build.bat +++ b/build.bat @@ -36,7 +36,7 @@ goto :MAIN call :clean build64 echo Building 64 bit version cd build64 - cmake -G"Visual Studio 16 2019" -A"x64" -DCMAKE_SYSTEM_VERSION="10.0.18363.657" -DBUILD_BROWSER=true -DCOMPILE_D3D12_HOOK=true -DDepsPath="%DepsPath%" -DQTDIR="%QTDIR%" -DENABLE_VLC=ON -DVLCPath="%VLCPATH%" -DCEF_ROOT_DIR="%CEF_ROOT_DIR%" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE -DLIBCAFFEINE_DIR="%LIBCAFFEINE_DIR%" .. + cmake -G"Visual Studio 16 2019" -A"x64" -DCMAKE_SYSTEM_VERSION="10.0.18363.657" -DBUILD_BROWSER=true -DCOMPILE_D3D12_HOOK=true -DDepsPath="%DepsPath%" -DQTDIR="%QTDIR%" -DENABLE_VLC=ON -DVLCPath="%VLCPATH%" -DCEF_ROOT_DIR="%CEF_ROOT_DIR%" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE -DVIRTUALCAM_GUID="A3FCE0F5-3493-419F-958A-ABA1250EC20B" -DLIBCAFFEINE_DIR="%LIBCAFFEINE_DIR%" .. cd .. msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-studio.sln echo Built 64 bit COBS diff --git a/plugins/caffeine/CMakeLists.txt b/plugins/caffeine/CMakeLists.txt index d0c11ddeb75de5..f327137d83bd67 100644 --- a/plugins/caffeine/CMakeLists.txt +++ b/plugins/caffeine/CMakeLists.txt @@ -13,12 +13,14 @@ set(caffeine_HEADERS caffeine-foreground-process.h caffeine-settings.h caffeine-sample-logger.h - caffeine-stopwatch.h) + caffeine-stopwatch.h + caffeine-tracked-frames.hpp) set(caffeine_SOURCES caffeine-foreground-process.c caffeine-module.c - caffeine-output.c + caffeine-output.cpp + caffeine-tracked-frames.cpp caffeine-sample-logger.c caffeine-stopwatch.c) diff --git a/plugins/caffeine/caffeine-foreground-process.h b/plugins/caffeine/caffeine-foreground-process.h index c9dcbabfefcb0e..f34d7f03a2ad07 100644 --- a/plugins/caffeine/caffeine-foreground-process.h +++ b/plugins/caffeine/caffeine-foreground-process.h @@ -1,3 +1,11 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + char *get_foreground_process_name(); + +#ifdef __cplusplus +} +#endif diff --git a/plugins/caffeine/caffeine-module.c b/plugins/caffeine/caffeine-module.c index 8575d0602f3048..07ab92dce088d4 100644 --- a/plugins/caffeine/caffeine-module.c +++ b/plugins/caffeine/caffeine-module.c @@ -9,7 +9,8 @@ MODULE_EXPORT char const *obs_module_description(void) return obs_module_text("CaffeineModule"); } -extern struct obs_output_info caffeine_output_info; +struct obs_output_info get_caffeine_output_info(); +struct obs_output_info caffeine_output_info; /* Converts libcaffeine log levels to OBS levels */ static int caffeine_to_obs_log_level(caff_LogLevel level) @@ -36,6 +37,7 @@ static void caffeine_log(caff_LogLevel level, char const *message) bool obs_module_load(void) { + caffeine_output_info = get_caffeine_output_info(); obs_register_output(&caffeine_output_info); caff_Result result = caff_initialize("obs", OBS_VERSION, caff_LogLevelDebug, caffeine_log); diff --git a/plugins/caffeine/caffeine-output.c b/plugins/caffeine/caffeine-output.cpp similarity index 86% rename from plugins/caffeine/caffeine-output.c rename to plugins/caffeine/caffeine-output.cpp index 0ba0f38c5f59b5..f0c5b96604ac89 100644 --- a/plugins/caffeine/caffeine-output.c +++ b/plugins/caffeine/caffeine-output.cpp @@ -12,19 +12,20 @@ #include "caffeine-settings.h" #include "caffeine-stopwatch.h" #include "caffeine-sample-logger.h" +#include "caffeine-tracked-frames.hpp" /* Uncomment this to log each call to raw_audio/video */ -//#define TRACE_FRAMES +// #define TRACE_FRAMES /* Uncomment these lines and change path to use the sample log This is a simple debug tool, so I didn't add code to auto-create directories, etc So you'll need to make sure the directory path is available before use. This causes a little bit of macro salsa, but we can remove it later */ -//#define USE_SAMPLE_LOG -//#define VIDEO_SAMPLE_LOG_FILE ("C:\\Users\\jond\\Desktop\\obs_logs\\caffeine_raw_video_samples_log.csv") -//#define AUDIO_SAMPLE_LOG_FILE ("C:\\Users\\jond\\Desktop\\obs_logs\\caffeine_raw_audio_samples_log.csv") +// #define USE_SAMPLE_LOG +// #define VIDEO_SAMPLE_LOG_FILE ("C:\\Users\\Caffeine\\Desktop\\OBS_LOGS\\caffeine_raw_video_samples.csv") +// #define AUDIO_SAMPLE_LOG_FILE ("C:\\Users\\Caffeine\\Desktop\\OBS_LOGS\\caffeine_raw_audio_samples.csv") #define do_log(level, format, ...) \ blog(level, "[caffeine output] " format, ##__VA_ARGS__) @@ -48,6 +49,7 @@ This causes a little bit of macro salsa, but we can remove it later */ #define caff_max(a, b) (((a) > (b)) ? (a) : (b)) #define caff_min(a, b) (((a) < (b)) ? (a) : (b)) +#define caff_ms_to_ns(ms) (ms * 1000000UL) #define CAFF_AUDIO_FORMAT AUDIO_FORMAT_16BIT #define CAFF_AUDIO_FORMAT_TYPE int16_t @@ -93,6 +95,8 @@ struct caffeine_output { int test_frame_drop_percent; + CaffeineFramesTracker *frames_tracker; + #ifdef USE_SAMPLE_LOG caffeine_stopwatch_t sample_stopwatch; uint64_t raw_video_left_func_timestamp_ns; @@ -111,7 +115,8 @@ static void audio_data_copy(struct audio_data *left, struct audio_data *right) size_t size = left->frames * sizeof(CAFF_AUDIO_FORMAT_TYPE) * CAFF_AUDIO_LAYOUT_MUL; - right->data[idx] = bmalloc(size); + right->data[idx] = + static_cast(bmalloc(size)); memcpy(right->data[idx], left->data[idx], size); } } @@ -252,9 +257,9 @@ static void *caffeine_create(obs_data_t *settings, obs_output_t *output) { trace(); UNUSED_PARAMETER(settings); - struct caffeine_output *context = - bzalloc(sizeof(struct caffeine_output)); + reinterpret_cast( + bzalloc(sizeof(struct caffeine_output))); context->output = output; // Create mutex and condvar. @@ -275,7 +280,7 @@ static void *caffeine_create(obs_data_t *settings, obs_output_t *output) fail: pthread_mutex_destroy(&context->audio_lock); pthread_cond_destroy(&context->audio_cond); - caff_freeInstance(context->instance); + caff_freeInstance(&context->instance); bfree(context); return NULL; } @@ -314,7 +319,8 @@ static bool caffeine_authenticate(struct caffeine_output *context) static bool caffeine_start(void *data) { trace(); - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); obs_output_t *output = context->output; obs_output_set_media(output, obs_get_video(), obs_get_audio()); @@ -366,6 +372,11 @@ static bool caffeine_start(void *data) caffeine_stopwatch_start(&context->slow_connection_stopwatch); } + if (nullptr == context->frames_tracker) { + context->frames_tracker = new CaffeineFramesTracker(); + } + context->frames_tracker->caffeine_set_next_check_dropped_frames(0); + #ifdef USE_SAMPLE_LOG context->raw_video_left_func_timestamp_ns = 0UL; context->raw_audio_left_func_timestamp_ns = 0UL; @@ -396,10 +407,11 @@ static bool caffeine_start(void *data) return false; } - struct audio_convert_info conversion = {.format = CAFF_AUDIO_FORMAT, - .speakers = CAFF_AUDIO_LAYOUT, - .samples_per_sec = - CAFF_AUDIO_SAMPLERATE}; + struct audio_convert_info conversion = {}; + conversion.format = CAFF_AUDIO_FORMAT; + conversion.speakers = CAFF_AUDIO_LAYOUT; + conversion.samples_per_sec = CAFF_AUDIO_SAMPLERATE; + obs_output_set_audio_conversion(output, &conversion); context->audio_planes = @@ -434,14 +446,14 @@ static bool caffeine_start(void *data) set_error(output, "%s", caff_resultString(result)); return false; } - return true; } static void enumerate_games(void *data, char const *process_name, char const *game_id, char const *game_name) { - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); if (strcmp(process_name, context->foreground_process) == 0) { log_debug("Detected game [%s]: %s", game_id, game_name); bfree(context->game_id); @@ -452,7 +464,8 @@ static void enumerate_games(void *data, char const *process_name, static void *monitor_thread(void *data) { trace(); - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); const uint64_t millisPerNano = 1000000; const uint64_t stop_interval = 100 /*ms*/ * millisPerNano; const uint64_t game_interval = 5000 /*ms*/ * millisPerNano; @@ -501,8 +514,10 @@ static void *monitor_thread(void *data) BROADCAST_TITLE_KEY)); caff_setRating( context->instance, - obs_data_get_int(data, - BROADCAST_RATING_KEY)); + static_cast( + obs_data_get_int( + data, + BROADCAST_RATING_KEY))); obs_data_release(data); } } @@ -514,7 +529,8 @@ static void *monitor_thread(void *data) static void caffeine_stream_started(void *data) { trace(); - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); context->is_online = true; pthread_create(&context->monitor_thread, NULL, monitor_thread, context); obs_output_begin_data_capture(context->output, 0); @@ -522,7 +538,8 @@ static void caffeine_stream_started(void *data) static void caffeine_stream_failed(void *data, caff_Result error) { - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); if (!obs_output_get_last_error(context->output)) { if (caff_checkInternetConnection() == @@ -550,7 +567,8 @@ static void caffeine_raw_video(void *data, struct video_data *frame) trace(); #endif - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); #ifdef USE_SAMPLE_LOG uint64_t func_called_timestamp_ns = @@ -576,8 +594,11 @@ static void caffeine_raw_video(void *data, struct video_data *frame) } } - if (!context->start_timestamp) + if (!context->start_timestamp) { context->start_timestamp = frame->timestamp; + context->frames_tracker->caffeine_set_next_check_dropped_frames( + context->start_timestamp + caff_ms_to_ns(10000UL)); + } uint64_t last_pair_timestamp_ns = context->audio_last_timestamp; context->video_last_timestamp = frame->timestamp; @@ -609,12 +630,25 @@ static void caffeine_raw_video(void *data, struct video_data *frame) caff_VideoFormat format = obs_to_caffeine_format(context->video_info.output_format); int64_t timestampMicros = frame->timestamp / 1000; - if (send_frame) { caff_sendVideo(context->instance, format, frame->data[0], total_bytes, width, height, timestampMicros); } + context->frames_tracker->caffeine_add_frame(frame->timestamp, + send_frame); + if (context->frames_tracker->caffeine_did_frames_drop()) { + obs_data_set_bool( + obs_service_get_settings( + obs_output_get_service(context->output)), + "frames_dropped_above_threshold", true); + } else { + obs_data_set_bool( + obs_service_get_settings( + obs_output_get_service(context->output)), + "frames_dropped_above_threshold", false); + } + #ifdef USE_SAMPLE_LOG uint64_t func_complete_timestamp_ns = caffeine_stopwatch_get_elapsed_ns(&context->sample_stopwatch); @@ -640,7 +674,8 @@ static void caffeine_raw_audio(void *data, struct audio_data *frames) #ifdef TRACE_FRAMES trace(); #endif - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); #ifdef USE_SAMPLE_LOG uint64_t func_called_timestamp_ns = @@ -669,7 +704,8 @@ static void caffeine_raw_audio(void *data, struct audio_data *frames) uint64_t center_samples_ts = (frames->timestamp + end_ts) / 2; context->audio_last_timestamp = center_samples_ts; - const uint64_t timestamp_adj = av_sync_tolerance_window_ms * 1000000UL; + const uint64_t timestamp_adj = + caff_ms_to_ns(av_sync_tolerance_window_ms); context->timestamp_window_pos = center_samples_ts + timestamp_adj; context->timestamp_window_neg = center_samples_ts - timestamp_adj; @@ -717,7 +753,8 @@ static void caffeine_stop(void *data, uint64_t ts) /* TODO: do something with this? */ UNUSED_PARAMETER(ts); - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); obs_output_t *output = context->output; if (context->is_online) { @@ -743,14 +780,19 @@ static void caffeine_stop(void *data, uint64_t ts) } caff_endBroadcast(context->instance); - obs_output_end_data_capture(output); + + context->frames_tracker->caffeine_set_next_check_dropped_frames(0); + obs_data_set_bool(obs_service_get_settings( + obs_output_get_service(context->output)), + "frames_dropped_above_threshold", false); } static void caffeine_destroy(void *data) { trace(); - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); caff_freeInstance(&context->instance); // Free mutex and condvar. @@ -762,7 +804,8 @@ static void caffeine_destroy(void *data) static float caffeine_get_congestion(void *data) { - struct caffeine_output *context = data; + struct caffeine_output *context = + reinterpret_cast(data); caff_ConnectionQuality quality = caff_getConnectionQuality(context->instance); @@ -777,20 +820,26 @@ static float caffeine_get_congestion(void *data) } } -struct obs_output_info caffeine_output_info = { - .id = "caffeine_output", - .flags = OBS_OUTPUT_AV | OBS_OUTPUT_SERVICE | - OBS_OUTPUT_BANDWIDTH_TEST_DISABLED | - OBS_OUTPUT_HARDWARE_ENCODING_DISABLED, +extern "C" { +struct obs_output_info get_caffeine_output_info() +{ + struct obs_output_info output_info = {}; + memset(&output_info, 0, sizeof(obs_output_info)); + output_info.id = "caffeine_output"; + output_info.flags = OBS_OUTPUT_AV | OBS_OUTPUT_SERVICE | + OBS_OUTPUT_BANDWIDTH_TEST_DISABLED | + OBS_OUTPUT_HARDWARE_ENCODING_DISABLED; - .get_name = caffeine_get_name, + output_info.get_name = caffeine_get_name; - .create = caffeine_create, - .destroy = caffeine_destroy, + output_info.create = caffeine_create; + output_info.destroy = caffeine_destroy; - .start = caffeine_start, - .stop = caffeine_stop, + output_info.start = caffeine_start; + output_info.stop = caffeine_stop; - .raw_video = caffeine_raw_video, - .raw_audio = caffeine_raw_audio, -}; + output_info.raw_video = caffeine_raw_video; + output_info.raw_audio = caffeine_raw_audio; + return output_info; +} +} diff --git a/plugins/caffeine/caffeine-sample-logger.h b/plugins/caffeine/caffeine-sample-logger.h index b5493785f3108b..a1b138c4e7ad41 100644 --- a/plugins/caffeine/caffeine-sample-logger.h +++ b/plugins/caffeine/caffeine-sample-logger.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct caffeine_sample_logger { char file_name[1000]; char line_buff[1000]; @@ -23,3 +27,7 @@ void caffeine_sample_logger_log_sample( const char *reason_not_sent_to_libcaffeine, uint64_t wall_time_ns, uint64_t sample_obs_timestamp_ns, uint64_t pair_obs_last_timestamp_ns, uint64_t caffeine_func_time_ns, uint64_t obs_app_time_ns); + +#ifdef __cplusplus +} +#endif diff --git a/plugins/caffeine/caffeine-stopwatch.h b/plugins/caffeine/caffeine-stopwatch.h index 9ba38d5eb1030a..3632d6f3d12847 100644 --- a/plugins/caffeine/caffeine-stopwatch.h +++ b/plugins/caffeine/caffeine-stopwatch.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct caffeine_stopwatch { bool running; uint64_t accumulator; @@ -17,3 +21,7 @@ uint64_t caffeine_stopwatch_get_elapsed_ns(caffeine_stopwatch_t *lpsw); uint64_t caffeine_stopwatch_get_elapsed_ms(caffeine_stopwatch_t *lpsw); void caffeine_stopwatch_copy_state(caffeine_stopwatch_t *lpsw_dest, caffeine_stopwatch_t *lpsw_src); + +#ifdef __cplusplus +} +#endif diff --git a/plugins/caffeine/caffeine-tracked-frames.cpp b/plugins/caffeine/caffeine-tracked-frames.cpp new file mode 100644 index 00000000000000..566b55b5eff5b2 --- /dev/null +++ b/plugins/caffeine/caffeine-tracked-frames.cpp @@ -0,0 +1,66 @@ +#include "caffeine-tracked-frames.hpp" + +#define caff_ms_to_ns(ms) (ms * 1000000UL) +static int const threshold_frames_dropped_percent = 25; + +CaffeineFramesTracker::CaffeineFramesTracker() + : frames_list(new std::list()), + next_check_dropped_frames(0), + isFramesDropped(false) +{ +} + +CaffeineFramesTracker::~CaffeineFramesTracker() +{ + delete frames_list; +} + +void CaffeineFramesTracker::caffeine_add_frame(uint64_t timestamp, bool sent) +{ + frames_list->push_back({timestamp, sent}); + if (timestamp >= next_check_dropped_frames) { + caffeine_remove_old_frames(timestamp); + if (caffeine_get_drop_percent() >= + threshold_frames_dropped_percent) { + isFramesDropped = true; + } else { + isFramesDropped = false; + } + next_check_dropped_frames = timestamp + caff_ms_to_ns(1000UL); + } +} + +void CaffeineFramesTracker::caffeine_remove_old_frames(uint64_t timestamp) +{ + uint64_t frame_time_lower_bound = timestamp - caff_ms_to_ns(10000UL); + // remove old frames , Lower bound is 10 seconds + frames_list->remove_if( + [frame_time_lower_bound]( + const caffeine_tracked_frame &list_frame) -> bool { + return list_frame.timestamp < frame_time_lower_bound; + }); +} + +uint32_t CaffeineFramesTracker::caffeine_get_drop_percent() +{ + float frames_dropped = 0.0f; + float total_frames = 0.0f; + for (auto f = frames_list->begin(); f != frames_list->end(); f++) { + if (!f->sent) { + frames_dropped = frames_dropped + 1.0f; + } + total_frames = total_frames + 1.0f; + } + return (uint32_t)((frames_dropped / total_frames) * 100.0f); +} + +void CaffeineFramesTracker::caffeine_set_next_check_dropped_frames( + uint64_t next_check) +{ + next_check_dropped_frames = next_check; +} + +bool CaffeineFramesTracker::caffeine_did_frames_drop() +{ + return isFramesDropped; +} diff --git a/plugins/caffeine/caffeine-tracked-frames.hpp b/plugins/caffeine/caffeine-tracked-frames.hpp new file mode 100644 index 00000000000000..a26241b69ad416 --- /dev/null +++ b/plugins/caffeine/caffeine-tracked-frames.hpp @@ -0,0 +1,25 @@ +#ifndef CAFFEINE_TRACKED_FRAMES_H +#define CAFFEINE_TRACKED_FRAMES_H + +#include +struct caffeine_tracked_frame { + uint64_t timestamp; + bool sent; +}; + +class CaffeineFramesTracker { +public: + CaffeineFramesTracker(); + ~CaffeineFramesTracker(); + void caffeine_add_frame(uint64_t timestamp, bool sent); + void caffeine_set_next_check_dropped_frames(uint64_t next_check); + bool caffeine_did_frames_drop(); + +private: + std::list *frames_list; + uint64_t next_check_dropped_frames; + bool isFramesDropped; + void caffeine_remove_old_frames(uint64_t timestamp); + uint32_t caffeine_get_drop_percent(); +}; +#endif \ No newline at end of file From 2ed8f0a4d532b82bda82c506c3e371c69c039197 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Tue, 9 Mar 2021 13:51:59 -0800 Subject: [PATCH 25/29] Fixes after rebase (#100) Fixed Qt DockWiget AllDockWidgetFeature deprecation warning Updated the build scripts for macos --- CI/full-build-macos.sh | 52 +++++++++++++++++++++++++-------------- UI/forms/CaffeinePanel.ui | 3 ++- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/CI/full-build-macos.sh b/CI/full-build-macos.sh index 80a73fc5b137f7..f74f0148c8b118 100755 --- a/CI/full-build-macos.sh +++ b/CI/full-build-macos.sh @@ -22,7 +22,7 @@ # # Environment Variables (optional): # MACOS_DEPS_VERSION : Pre-compiled macOS dependencies version -# CEF_BUILD_VERSION : Chromium Embedded Framework version +# MACOS_CEF_BUILD_VERSION : Chromium Embedded Framework version # VLC_VERISON : VLC version # SPARKLE_VERSION : Sparke Framework version # BUILD_DIR : Alternative directory to build OBS in @@ -41,20 +41,22 @@ BUILD_DIR="${BUILD_DIR:-build}" BUILD_CONFIG=${BUILD_CONFIG:-RelWithDebInfo} CI_SCRIPTS="${CHECKOUT_DIR}/CI/scripts/macos" CI_WORKFLOW="${CHECKOUT_DIR}/.github/workflows/main.yml" -CI_CEF_VERSION=$(cat ${CI_WORKFLOW} | sed -En "s/[ ]+CEF_BUILD_VERSION: '([0-9]+)'/\1/p") +CI_MACOS_CEF_VERSION=$(cat ${CI_WORKFLOW} | sed -En "s/[ ]+MACOS_CEF_BUILD_VERSION: '([0-9]+)'/\1/p") CI_DEPS_VERSION=$(cat ${CI_WORKFLOW} | sed -En "s/[ ]+MACOS_DEPS_VERSION: '([0-9\-]+)'/\1/p") CI_VLC_VERSION=$(cat ${CI_WORKFLOW} | sed -En "s/[ ]+VLC_VERSION: '([0-9\.]+)'/\1/p") CI_SPARKLE_VERSION=$(cat ${CI_WORKFLOW} | sed -En "s/[ ]+SPARKLE_VERSION: '([0-9\.]+)'/\1/p") CI_QT_VERSION=$(cat ${CI_WORKFLOW} | sed -En "s/[ ]+QT_VERSION: '([0-9\.]+)'/\1/p" | head -1) CI_MIN_MACOS_VERSION=$(cat ${CI_WORKFLOW} | sed -En "s/[ ]+MIN_MACOS_VERSION: '([0-9\.]+)'/\1/p") +CI_LIBCAFFEINE_VERSION=$(cat ${CI_WORKFLOW} | sed -En "s/[ ]+LIBCAFFEINE_VERSION: '([0-9\.]+)'/\1/p") NPROC="${NPROC:-$(sysctl -n hw.ncpu)}" BUILD_DEPS=( - "obs-deps ${MACOS_DEPS_VERSION:-${CI_DEPS_VERSION}}" - "qt-deps ${QT_VERSION:-${CI_QT_VERSION}} ${MACOS_DEPS_VERSION:-${CI_DEPS_VERSION}}" - "cef ${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}" + "obs_deps ${MACOS_DEPS_VERSION:-${CI_DEPS_VERSION}}" + "qt_deps ${QT_VERSION:-${CI_QT_VERSION}} ${MACOS_DEPS_VERSION:-${CI_DEPS_VERSION}}" + "cef ${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}" "vlc ${VLC_VERSION:-${CI_VLC_VERSION}}" "sparkle ${SPARKLE_VERSION:-${CI_SPARKLE_VERSION}}" + "libcaffeine ${LIBCAFFEINE_VERSION:-${CI_LIBCAFFEINE_VERSION}}" ) if [ -n "${TERM-}" ]; then @@ -165,7 +167,7 @@ check_ccache() { info "${CCACHE_STATUS}" } -install_obs-deps() { +install_obs_deps() { hr "Setting up pre-built macOS OBS dependencies v${1}" ensure_dir ${DEPS_BUILD_DIR} step "Download..." @@ -174,7 +176,7 @@ install_obs-deps() { tar -xf ./macos-deps-${1}.tar.gz -C /tmp } -install_qt-deps() { +install_qt_deps() { hr "Setting up pre-built dependency QT v${1}" ensure_dir ${DEPS_BUILD_DIR} step "Download..." @@ -208,6 +210,16 @@ install_sparkle() { fi } +install_libcaffeine() { + hr "Setting up dependency libcaffeine v${1}" + ensure_dir ${DEPS_BUILD_DIR} + step "Download..." + ${CURLCMD} --progress-bar -L -C - -O https://github.com/caffeinetv/libcaffeine/releases/download/v${1}/libcaffeine-v${1}-macos.7z + step "Unpack ..." + brew install p7zip + 7za x libcaffeine-v${1}-macos.7z +} + install_cef() { hr "Building dependency CEF v${1}" ensure_dir ${DEPS_BUILD_DIR} @@ -218,7 +230,7 @@ install_cef() { cd ./cef_binary_${1}_macosx64 step "Fix tests..." sed -i '.orig' '/add_subdirectory(tests\/ceftests)/d' ./CMakeLists.txt - sed -i '.orig' 's/"'$(test "${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}" -le 3770 && echo "10.9" || echo "10.10")'"/"'${MIN_MACOS_VERSION:-${CI_MIN_MACOS_VERSION}}'"/' ./cmake/cef_variables.cmake + sed -i '.orig' 's/"'$(test "${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}" -le 3770 && echo "10.9" || echo "10.10")'"/"'${MIN_MACOS_VERSION:-${CI_MIN_MACOS_VERSION}}'"/' ./cmake/cef_variables.cmake ensure_dir ./build step "Run CMAKE..." cmake \ @@ -277,11 +289,13 @@ configure_obs_build() { -DSWIGDIR="/tmp/obsdeps" \ -DDepsPath="/tmp/obsdeps" \ -DVLCPath="${DEPS_BUILD_DIR}/vlc-${VLC_VERSION:-${CI_VLC_VERSION}}" \ + -DENABLE_VLC=ON \ -DBUILD_BROWSER=ON \ - -DBROWSER_LEGACY="$(test "${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}" -le 3770 && echo "ON" || echo "OFF")" \ + -DBROWSER_LEGACY="$(test "${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}" -le 3770 && echo "ON" || echo "OFF")" \ -DWITH_RTMPS=ON \ - -DCEF_ROOT_DIR="${DEPS_BUILD_DIR}/cef_binary_${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}_macosx64" \ + -DCEF_ROOT_DIR="${DEPS_BUILD_DIR}/cef_binary_${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}_macosx64" \ -DCMAKE_BUILD_TYPE="${BUILD_CONFIG}" \ + -DLIBCAFFEINE_DIR="${DEPS_BUILD_DIR}/libcaffeine-v${LIBCAFFEINE_VERSION:-${CI_LIBCAFFEINE_VERSION}}-macos" \ .. } @@ -329,7 +343,7 @@ bundle_dylibs() { ./OBS.app/Contents/PlugIns/text-freetype2.so ./OBS.app/Contents/PlugIns/obs-outputs.so ) - if ! [ "${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}" -le 3770 ]; then + if ! [ "${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}" -le 3770 ]; then ${CI_SCRIPTS}/app/dylibbundler -cd -of -a ./OBS.app -q -f \ -s ./OBS.app/Contents/MacOS \ -s "${DEPS_BUILD_DIR}/sparkle/Sparkle.framework" \ @@ -371,7 +385,7 @@ install_frameworks() { hr "Adding Chromium Embedded Framework" step "Copy Framework..." - cp -R "${DEPS_BUILD_DIR}/cef_binary_${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}_macosx64/Release/Chromium Embedded Framework.framework" ./OBS.app/Contents/Frameworks/ + cp -R "${DEPS_BUILD_DIR}/cef_binary_${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}_macosx64/Release/Chromium Embedded Framework.framework" ./OBS.app/Contents/Frameworks/ chown -R $(whoami) ./OBS.app/Contents/Frameworks/ } @@ -395,7 +409,7 @@ prepare_macos_bundle() { cp rundir/${BUILD_CONFIG}/bin/obs ./OBS.app/Contents/MacOS cp rundir/${BUILD_CONFIG}/bin/obs-ffmpeg-mux ./OBS.app/Contents/MacOS cp rundir/${BUILD_CONFIG}/bin/libobsglad.0.dylib ./OBS.app/Contents/MacOS - if ! [ "${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}" -le 3770 ]; then + if ! [ "${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}" -le 3770 ]; then cp -R "rundir/${BUILD_CONFIG}/bin/OBS Helper.app" "./OBS.app/Contents/Frameworks/OBS Helper.app" cp -R "rundir/${BUILD_CONFIG}/bin/OBS Helper (GPU).app" "./OBS.app/Contents/Frameworks/OBS Helper (GPU).app" cp -R "rundir/${BUILD_CONFIG}/bin/OBS Helper (Plugin).app" "./OBS.app/Contents/Frameworks/OBS Helper (Plugin).app" @@ -524,7 +538,7 @@ codesign_bundle() { codesign --force --options runtime --sign "${CODESIGN_IDENT}" "./OBS.app/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libswiftshader_libEGL.dylib" codesign --force --options runtime --sign "${CODESIGN_IDENT}" "./OBS.app/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libGLESv2.dylib" codesign --force --options runtime --sign "${CODESIGN_IDENT}" "./OBS.app/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libswiftshader_libGLESv2.dylib" - if ! [ "${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}" -le 3770 ]; then + if ! [ "${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}" -le 3770 ]; then codesign --force --options runtime --sign "${CODESIGN_IDENT}" "./OBS.app/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libvk_swiftshader.dylib" fi @@ -540,7 +554,7 @@ codesign_bundle() { codesign --force --options runtime --entitlements "${CI_SCRIPTS}/app/entitlements.plist" --sign "${CODESIGN_IDENT}" --deep ./OBS.app echo -n "${COLOR_RESET}" - if ! [ "${CEF_BUILD_VERSION:-${CI_CEF_VERSION}}" -le 3770 ]; then + if ! [ "${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}" -le 3770 ]; then step "Code-sign CEF helper apps..." echo -n "${COLOR_ORANGE}" codesign --force --options runtime --sign "${CODESIGN_IDENT}" --deep "./OBS.app/Contents/Frameworks/OBS Helper.app" @@ -578,7 +592,7 @@ codesign_image() { } ## BUILD FROM SOURCE META FUNCTION ## -full-build-macos() { +full_build_macos() { if [ -n "${SKIP_BUILD}" ]; then step "Skipping full build"; return; fi if [ ! -n "${SKIP_DEPS}" ]; then @@ -664,7 +678,7 @@ print_usage() { exit 0 } -obs-build-main() { +obs_build_main() { ensure_dir ${CHECKOUT_DIR} check_macos_version step "Fetching OBS tags..." @@ -701,7 +715,7 @@ obs-build-main() { esac done - full-build-macos + full_build_macos bundle_macos codesign_bundle package_macos @@ -711,4 +725,4 @@ obs-build-main() { cleanup } -obs-build-main $* +obs_build_main $* diff --git a/UI/forms/CaffeinePanel.ui b/UI/forms/CaffeinePanel.ui index 055633ca29bb69..9e99e126f17487 100644 --- a/UI/forms/CaffeinePanel.ui +++ b/UI/forms/CaffeinePanel.ui @@ -17,7 +17,8 @@ true - QDockWidget::AllDockWidgetFeatures + QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable + Caffeine From 2ee1721243b7c17a3a5cadbb13ec2986de688b24 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Thu, 11 Mar 2021 10:54:28 -0800 Subject: [PATCH 26/29] Cobs 167 (#101) * COBS-167 error message "System under too much load. Broadcast quality may be degraded" occurs when user start streaming in COBS * Clang format update --- .github/workflows/clang-format.yml | 16 ++---------- .github/workflows/main.yml | 2 +- formatcode.sh | 4 ++- plugins/caffeine/caffeine-output.cpp | 23 ++++++----------- plugins/caffeine/caffeine-tracked-frames.cpp | 26 ++++++++++---------- plugins/caffeine/caffeine-tracked-frames.hpp | 7 +++--- 6 files changed, 30 insertions(+), 48 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 1b74e7006dc481..597242aa22790a 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -13,20 +13,8 @@ jobs: - name: Install clang format run: | - # gets us newer clang - sudo bash -c "cat >> /etc/apt/sources.list" << LLVMAPT - # 3.8 - deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main - deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main - LLVMAPT - - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - - - sudo apt-get -qq update - - sudo apt-get install -y clang-format-8 - + sudo apt-get install -y clang-format-10 - name: Check the Formatting run: | ./formatcode.sh - ./CI/check-format.sh + ./CI/check-format.sh \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index abe582869de132..2e8cbb0e4808ac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -351,7 +351,7 @@ jobs: path: ./release/*.dmg ubuntu64: name: 'Linux/Ubuntu 64-bit' - runs-on: [ubuntu-latest] + runs-on: [ubuntu-18.04] steps: - name: 'Checkout' uses: actions/checkout@v2.3.3 diff --git a/formatcode.sh b/formatcode.sh index 9a0c1338be6b1f..f776cf229396cc 100755 --- a/formatcode.sh +++ b/formatcode.sh @@ -19,7 +19,9 @@ if [[ $OS = "Linux" || $OS = "Darwin" ]] ; then fi # Discover clang-format -if type clang-format-8 2> /dev/null ; then +if type clang-format-10 2> /dev/null ; then + CLANG_FORMAT=clang-format-10 +elif type clang-format-8 2> /dev/null ; then CLANG_FORMAT=clang-format-8 else CLANG_FORMAT=clang-format diff --git a/plugins/caffeine/caffeine-output.cpp b/plugins/caffeine/caffeine-output.cpp index f0c5b96604ac89..371d0d08999483 100644 --- a/plugins/caffeine/caffeine-output.cpp +++ b/plugins/caffeine/caffeine-output.cpp @@ -49,7 +49,7 @@ This causes a little bit of macro salsa, but we can remove it later */ #define caff_max(a, b) (((a) > (b)) ? (a) : (b)) #define caff_min(a, b) (((a) < (b)) ? (a) : (b)) -#define caff_ms_to_ns(ms) (ms * 1000000UL) +#define caff_ms_to_ns(ms) (ms * 1000000ULL) #define CAFF_AUDIO_FORMAT AUDIO_FORMAT_16BIT #define CAFF_AUDIO_FORMAT_TYPE int16_t @@ -373,7 +373,9 @@ static bool caffeine_start(void *data) } if (nullptr == context->frames_tracker) { - context->frames_tracker = new CaffeineFramesTracker(); + context->frames_tracker = + new CaffeineFramesTracker(obs_service_get_settings( + obs_output_get_service(context->output))); } context->frames_tracker->caffeine_set_next_check_dropped_frames(0); @@ -597,7 +599,7 @@ static void caffeine_raw_video(void *data, struct video_data *frame) if (!context->start_timestamp) { context->start_timestamp = frame->timestamp; context->frames_tracker->caffeine_set_next_check_dropped_frames( - context->start_timestamp + caff_ms_to_ns(10000UL)); + context->start_timestamp + caff_ms_to_ns(10000ULL)); } uint64_t last_pair_timestamp_ns = context->audio_last_timestamp; @@ -626,7 +628,7 @@ static void caffeine_raw_video(void *data, struct video_data *frame) uint32_t width = context->video_info.output_width; uint32_t height = context->video_info.output_height; - size_t total_bytes = frame->linesize[0] * height; + size_t total_bytes = (size_t)frame->linesize[0] * (size_t)height; caff_VideoFormat format = obs_to_caffeine_format(context->video_info.output_format); int64_t timestampMicros = frame->timestamp / 1000; @@ -637,17 +639,6 @@ static void caffeine_raw_video(void *data, struct video_data *frame) context->frames_tracker->caffeine_add_frame(frame->timestamp, send_frame); - if (context->frames_tracker->caffeine_did_frames_drop()) { - obs_data_set_bool( - obs_service_get_settings( - obs_output_get_service(context->output)), - "frames_dropped_above_threshold", true); - } else { - obs_data_set_bool( - obs_service_get_settings( - obs_output_get_service(context->output)), - "frames_dropped_above_threshold", false); - } #ifdef USE_SAMPLE_LOG uint64_t func_complete_timestamp_ns = @@ -782,7 +773,7 @@ static void caffeine_stop(void *data, uint64_t ts) caff_endBroadcast(context->instance); obs_output_end_data_capture(output); - context->frames_tracker->caffeine_set_next_check_dropped_frames(0); + delete context->frames_tracker; obs_data_set_bool(obs_service_get_settings( obs_output_get_service(context->output)), "frames_dropped_above_threshold", false); diff --git a/plugins/caffeine/caffeine-tracked-frames.cpp b/plugins/caffeine/caffeine-tracked-frames.cpp index 566b55b5eff5b2..7f85cdcce17425 100644 --- a/plugins/caffeine/caffeine-tracked-frames.cpp +++ b/plugins/caffeine/caffeine-tracked-frames.cpp @@ -1,12 +1,13 @@ #include "caffeine-tracked-frames.hpp" +#include -#define caff_ms_to_ns(ms) (ms * 1000000UL) -static int const threshold_frames_dropped_percent = 25; +#define caff_ms_to_ns(ms) (ms * 1000000ULL) +static uint32_t const threshold_frames_dropped_percent = 25; -CaffeineFramesTracker::CaffeineFramesTracker() +CaffeineFramesTracker::CaffeineFramesTracker(obs_data_t *data) : frames_list(new std::list()), next_check_dropped_frames(0), - isFramesDropped(false) + data(data) { } @@ -18,21 +19,25 @@ CaffeineFramesTracker::~CaffeineFramesTracker() void CaffeineFramesTracker::caffeine_add_frame(uint64_t timestamp, bool sent) { frames_list->push_back({timestamp, sent}); + obs_data_set_bool(data, "frames_dropped_above_threshold", false); if (timestamp >= next_check_dropped_frames) { caffeine_remove_old_frames(timestamp); if (caffeine_get_drop_percent() >= threshold_frames_dropped_percent) { - isFramesDropped = true; + obs_data_set_bool( + data, "frames_dropped_above_threshold", true); } else { - isFramesDropped = false; + obs_data_set_bool( + data, "frames_dropped_above_threshold", false); } - next_check_dropped_frames = timestamp + caff_ms_to_ns(1000UL); + // Next check in 1 second + next_check_dropped_frames = timestamp + caff_ms_to_ns(1000ULL); } } void CaffeineFramesTracker::caffeine_remove_old_frames(uint64_t timestamp) { - uint64_t frame_time_lower_bound = timestamp - caff_ms_to_ns(10000UL); + uint64_t frame_time_lower_bound = timestamp - caff_ms_to_ns(10000ULL); // remove old frames , Lower bound is 10 seconds frames_list->remove_if( [frame_time_lower_bound]( @@ -59,8 +64,3 @@ void CaffeineFramesTracker::caffeine_set_next_check_dropped_frames( { next_check_dropped_frames = next_check; } - -bool CaffeineFramesTracker::caffeine_did_frames_drop() -{ - return isFramesDropped; -} diff --git a/plugins/caffeine/caffeine-tracked-frames.hpp b/plugins/caffeine/caffeine-tracked-frames.hpp index a26241b69ad416..566ce3b39df917 100644 --- a/plugins/caffeine/caffeine-tracked-frames.hpp +++ b/plugins/caffeine/caffeine-tracked-frames.hpp @@ -2,6 +2,8 @@ #define CAFFEINE_TRACKED_FRAMES_H #include +#include + struct caffeine_tracked_frame { uint64_t timestamp; bool sent; @@ -9,17 +11,16 @@ struct caffeine_tracked_frame { class CaffeineFramesTracker { public: - CaffeineFramesTracker(); + CaffeineFramesTracker(obs_data_t *data); ~CaffeineFramesTracker(); void caffeine_add_frame(uint64_t timestamp, bool sent); void caffeine_set_next_check_dropped_frames(uint64_t next_check); - bool caffeine_did_frames_drop(); private: std::list *frames_list; uint64_t next_check_dropped_frames; - bool isFramesDropped; void caffeine_remove_old_frames(uint64_t timestamp); uint32_t caffeine_get_drop_percent(); + obs_data_t *data; }; #endif \ No newline at end of file From ed478e331b64716cf48707de5317821994a97b72 Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Mon, 15 Mar 2021 09:25:42 -0700 Subject: [PATCH 27/29] COBS 172 (#102) * COBS-172 Crash occurs when attempting to start a new broadcast after having already broadcast once Fixed deleting of pointer * Fix resetting * Added safe_delete macro and cleanup on caffeine_start and caffeine_end * Fix clang formating Co-authored-by: turbodavidson --- CI/full-build-macos.sh | 2 +- plugins/caffeine/caffeine-output.cpp | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CI/full-build-macos.sh b/CI/full-build-macos.sh index f74f0148c8b118..eda6c9cc7d29a0 100755 --- a/CI/full-build-macos.sh +++ b/CI/full-build-macos.sh @@ -216,7 +216,7 @@ install_libcaffeine() { step "Download..." ${CURLCMD} --progress-bar -L -C - -O https://github.com/caffeinetv/libcaffeine/releases/download/v${1}/libcaffeine-v${1}-macos.7z step "Unpack ..." - brew install p7zip + brew list p7zip || brew install p7zip 7za x libcaffeine-v${1}-macos.7z } diff --git a/plugins/caffeine/caffeine-output.cpp b/plugins/caffeine/caffeine-output.cpp index 371d0d08999483..22a091ad063b40 100644 --- a/plugins/caffeine/caffeine-output.cpp +++ b/plugins/caffeine/caffeine-output.cpp @@ -37,6 +37,14 @@ This causes a little bit of macro salsa, but we can remove it later */ #define trace() log_debug("%s", __func__) +#define safe_delete(x) \ + do { \ + if (nullptr != x) { \ + delete x; \ + x = nullptr; \ + } \ + } while (0) + #define set_error(output, fmt, ...) \ do { \ struct dstr message; \ @@ -372,11 +380,11 @@ static bool caffeine_start(void *data) caffeine_stopwatch_start(&context->slow_connection_stopwatch); } - if (nullptr == context->frames_tracker) { - context->frames_tracker = - new CaffeineFramesTracker(obs_service_get_settings( - obs_output_get_service(context->output))); - } + context->start_timestamp = 0ULL; + safe_delete(context->frames_tracker); + context->frames_tracker = + new CaffeineFramesTracker(obs_service_get_settings( + obs_output_get_service(context->output))); context->frames_tracker->caffeine_set_next_check_dropped_frames(0); #ifdef USE_SAMPLE_LOG @@ -773,10 +781,12 @@ static void caffeine_stop(void *data, uint64_t ts) caff_endBroadcast(context->instance); obs_output_end_data_capture(output); - delete context->frames_tracker; obs_data_set_bool(obs_service_get_settings( obs_output_get_service(context->output)), "frames_dropped_above_threshold", false); + + context->start_timestamp = 0ULL; + safe_delete(context->frames_tracker); } static void caffeine_destroy(void *data) From cc3ed2c8c5e8902c890291d325233ff38d73edee Mon Sep 17 00:00:00 2001 From: Prateeksha Das <61755734+prateekshadas@users.noreply.github.com> Date: Tue, 23 Mar 2021 14:16:59 -0700 Subject: [PATCH 28/29] Fix ex bad access (#103) handled bad access when we deleted the FrameTracker on stopping the stream but video thread was still using it --- plugins/caffeine/caffeine-output.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/caffeine/caffeine-output.cpp b/plugins/caffeine/caffeine-output.cpp index 22a091ad063b40..eeeba967fd6675 100644 --- a/plugins/caffeine/caffeine-output.cpp +++ b/plugins/caffeine/caffeine-output.cpp @@ -645,8 +645,10 @@ static void caffeine_raw_video(void *data, struct video_data *frame) total_bytes, width, height, timestampMicros); } - context->frames_tracker->caffeine_add_frame(frame->timestamp, - send_frame); + if (context->frames_tracker != nullptr) { + context->frames_tracker->caffeine_add_frame(frame->timestamp, + send_frame); + } #ifdef USE_SAMPLE_LOG uint64_t func_complete_timestamp_ns = From 79283d18c4a37df40ddc2fca4a922add24d9300d Mon Sep 17 00:00:00 2001 From: coleW Date: Fri, 26 Mar 2021 17:39:06 -0400 Subject: [PATCH 29/29] base patch for 1080p streaming --- plugins/caffeine/caffeine-output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/caffeine/caffeine-output.cpp b/plugins/caffeine/caffeine-output.cpp index eeeba967fd6675..8561b77438fbd4 100644 --- a/plugins/caffeine/caffeine-output.cpp +++ b/plugins/caffeine/caffeine-output.cpp @@ -66,7 +66,7 @@ This causes a little bit of macro salsa, but we can remove it later */ #define CAFF_AUDIO_SAMPLERATE 48000ul #define NANOSECONDS 1000000000ull -static int const enforced_height = 720; +static int const enforced_height = 1080; static int const slow_connection_wait_ms = 66; static uint64_t const av_sync_tolerance_window_ms = 105UL;