noobs 0.0.89 → 0.0.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/dist/data/obs-plugins/obs-filters/LUTs/black_and_white.png +0 -0
  2. package/dist/data/obs-plugins/obs-filters/LUTs/grayscale.cube +32769 -0
  3. package/dist/data/obs-plugins/obs-filters/LUTs/grayscale.png +0 -0
  4. package/dist/data/obs-plugins/obs-filters/LUTs/invert.png +0 -0
  5. package/dist/data/obs-plugins/obs-filters/LUTs/original.cube +33 -0
  6. package/dist/data/obs-plugins/obs-filters/LUTs/original.png +0 -0
  7. package/dist/data/obs-plugins/obs-filters/LUTs/posterize.png +0 -0
  8. package/dist/data/obs-plugins/obs-filters/LUTs/red_isolated.png +0 -0
  9. package/dist/data/obs-plugins/obs-filters/LUTs/teal_lows_orange_highs.png +0 -0
  10. package/dist/data/obs-plugins/obs-filters/blend_add_filter.effect +54 -0
  11. package/dist/data/obs-plugins/obs-filters/blend_mul_filter.effect +54 -0
  12. package/dist/data/obs-plugins/obs-filters/blend_sub_filter.effect +54 -0
  13. package/dist/data/obs-plugins/obs-filters/chroma_key_filter.effect +99 -0
  14. package/dist/data/obs-plugins/obs-filters/chroma_key_filter_v2.effect +111 -0
  15. package/dist/data/obs-plugins/obs-filters/color.effect +95 -0
  16. package/dist/data/obs-plugins/obs-filters/color_correction_filter.effect +74 -0
  17. package/dist/data/obs-plugins/obs-filters/color_grade_filter.effect +177 -0
  18. package/dist/data/obs-plugins/obs-filters/color_key_filter.effect +65 -0
  19. package/dist/data/obs-plugins/obs-filters/color_key_filter_v2.effect +77 -0
  20. package/dist/data/obs-plugins/obs-filters/crop_filter.effect +95 -0
  21. package/dist/data/obs-plugins/obs-filters/hdr_tonemap_filter.effect +97 -0
  22. package/dist/data/obs-plugins/obs-filters/locale/af-ZA.ini +89 -0
  23. package/dist/data/obs-plugins/obs-filters/locale/ar-SA.ini +133 -0
  24. package/dist/data/obs-plugins/obs-filters/locale/az-AZ.ini +1 -0
  25. package/dist/data/obs-plugins/obs-filters/locale/ba-RU.ini +19 -0
  26. package/dist/data/obs-plugins/obs-filters/locale/be-BY.ini +134 -0
  27. package/dist/data/obs-plugins/obs-filters/locale/bg-BG.ini +71 -0
  28. package/dist/data/obs-plugins/obs-filters/locale/bn-BD.ini +106 -0
  29. package/dist/data/obs-plugins/obs-filters/locale/ca-ES.ini +128 -0
  30. package/dist/data/obs-plugins/obs-filters/locale/cs-CZ.ini +130 -0
  31. package/dist/data/obs-plugins/obs-filters/locale/da-DK.ini +119 -0
  32. package/dist/data/obs-plugins/obs-filters/locale/de-DE.ini +113 -0
  33. package/dist/data/obs-plugins/obs-filters/locale/el-GR.ini +128 -0
  34. package/dist/data/obs-plugins/obs-filters/locale/en-GB.ini +12 -0
  35. package/dist/data/obs-plugins/obs-filters/locale/en-US.ini +138 -0
  36. package/dist/data/obs-plugins/obs-filters/locale/eo-UY.ini +1 -0
  37. package/dist/data/obs-plugins/obs-filters/locale/es-ES.ini +129 -0
  38. package/dist/data/obs-plugins/obs-filters/locale/et-EE.ini +132 -0
  39. package/dist/data/obs-plugins/obs-filters/locale/eu-ES.ini +127 -0
  40. package/dist/data/obs-plugins/obs-filters/locale/fa-IR.ini +137 -0
  41. package/dist/data/obs-plugins/obs-filters/locale/fi-FI.ini +127 -0
  42. package/dist/data/obs-plugins/obs-filters/locale/fil-PH.ini +111 -0
  43. package/dist/data/obs-plugins/obs-filters/locale/fr-FR.ini +124 -0
  44. package/dist/data/obs-plugins/obs-filters/locale/gd-GB.ini +94 -0
  45. package/dist/data/obs-plugins/obs-filters/locale/gl-ES.ini +132 -0
  46. package/dist/data/obs-plugins/obs-filters/locale/he-IL.ini +135 -0
  47. package/dist/data/obs-plugins/obs-filters/locale/hi-IN.ini +134 -0
  48. package/dist/data/obs-plugins/obs-filters/locale/hr-HR.ini +80 -0
  49. package/dist/data/obs-plugins/obs-filters/locale/hu-HU.ini +131 -0
  50. package/dist/data/obs-plugins/obs-filters/locale/hy-AM.ini +130 -0
  51. package/dist/data/obs-plugins/obs-filters/locale/id-ID.ini +112 -0
  52. package/dist/data/obs-plugins/obs-filters/locale/it-IT.ini +130 -0
  53. package/dist/data/obs-plugins/obs-filters/locale/ja-JP.ini +134 -0
  54. package/dist/data/obs-plugins/obs-filters/locale/ka-GE.ini +133 -0
  55. package/dist/data/obs-plugins/obs-filters/locale/kaa.ini +41 -0
  56. package/dist/data/obs-plugins/obs-filters/locale/kab-KAB.ini +56 -0
  57. package/dist/data/obs-plugins/obs-filters/locale/kmr-TR.ini +126 -0
  58. package/dist/data/obs-plugins/obs-filters/locale/ko-KR.ini +134 -0
  59. package/dist/data/obs-plugins/obs-filters/locale/ms-MY.ini +131 -0
  60. package/dist/data/obs-plugins/obs-filters/locale/nb-NO.ini +115 -0
  61. package/dist/data/obs-plugins/obs-filters/locale/nl-NL.ini +111 -0
  62. package/dist/data/obs-plugins/obs-filters/locale/nn-NO.ini +19 -0
  63. package/dist/data/obs-plugins/obs-filters/locale/pl-PL.ini +127 -0
  64. package/dist/data/obs-plugins/obs-filters/locale/pt-BR.ini +128 -0
  65. package/dist/data/obs-plugins/obs-filters/locale/pt-PT.ini +130 -0
  66. package/dist/data/obs-plugins/obs-filters/locale/ro-RO.ini +125 -0
  67. package/dist/data/obs-plugins/obs-filters/locale/ru-RU.ini +135 -0
  68. package/dist/data/obs-plugins/obs-filters/locale/si-LK.ini +44 -0
  69. package/dist/data/obs-plugins/obs-filters/locale/sk-SK.ini +131 -0
  70. package/dist/data/obs-plugins/obs-filters/locale/sl-SI.ini +133 -0
  71. package/dist/data/obs-plugins/obs-filters/locale/sr-CS.ini +90 -0
  72. package/dist/data/obs-plugins/obs-filters/locale/sr-SP.ini +97 -0
  73. package/dist/data/obs-plugins/obs-filters/locale/sv-SE.ini +129 -0
  74. package/dist/data/obs-plugins/obs-filters/locale/szl-PL.ini +94 -0
  75. package/dist/data/obs-plugins/obs-filters/locale/ta-IN.ini +28 -0
  76. package/dist/data/obs-plugins/obs-filters/locale/th-TH.ini +132 -0
  77. package/dist/data/obs-plugins/obs-filters/locale/tl-PH.ini +64 -0
  78. package/dist/data/obs-plugins/obs-filters/locale/tr-TR.ini +129 -0
  79. package/dist/data/obs-plugins/obs-filters/locale/tt-RU.ini +27 -0
  80. package/dist/data/obs-plugins/obs-filters/locale/ug-CN.ini +133 -0
  81. package/dist/data/obs-plugins/obs-filters/locale/uk-UA.ini +135 -0
  82. package/dist/data/obs-plugins/obs-filters/locale/vi-VN.ini +131 -0
  83. package/dist/data/obs-plugins/obs-filters/locale/zh-CN.ini +138 -0
  84. package/dist/data/obs-plugins/obs-filters/locale/zh-TW.ini +136 -0
  85. package/dist/data/obs-plugins/obs-filters/luma_key_filter.effect +52 -0
  86. package/dist/data/obs-plugins/obs-filters/luma_key_filter_v2.effect +54 -0
  87. package/dist/data/obs-plugins/obs-filters/mask_alpha_filter.effect +54 -0
  88. package/dist/data/obs-plugins/obs-filters/mask_color_filter.effect +55 -0
  89. package/dist/data/obs-plugins/obs-filters/rtx_greenscreen.effect +183 -0
  90. package/dist/data/obs-plugins/obs-filters/sharpness.effect +76 -0
  91. package/dist/data/obs-plugins/obs-nvenc/locale/ar-SA.ini +60 -0
  92. package/dist/data/obs-plugins/obs-nvenc/locale/be-BY.ini +59 -0
  93. package/dist/data/obs-plugins/obs-nvenc/locale/bg-BG.ini +2 -0
  94. package/dist/data/obs-plugins/obs-nvenc/locale/ca-ES.ini +59 -0
  95. package/dist/data/obs-plugins/obs-nvenc/locale/cs-CZ.ini +55 -0
  96. package/dist/data/obs-plugins/obs-nvenc/locale/da-DK.ini +54 -0
  97. package/dist/data/obs-plugins/obs-nvenc/locale/de-DE.ini +55 -0
  98. package/dist/data/obs-plugins/obs-nvenc/locale/en-GB.ini +9 -0
  99. package/dist/data/obs-plugins/obs-nvenc/locale/en-US.ini +75 -0
  100. package/dist/data/obs-plugins/obs-nvenc/locale/es-ES.ini +59 -0
  101. package/dist/data/obs-plugins/obs-nvenc/locale/et-EE.ini +36 -0
  102. package/dist/data/obs-plugins/obs-nvenc/locale/fa-IR.ini +60 -0
  103. package/dist/data/obs-plugins/obs-nvenc/locale/fi-FI.ini +59 -0
  104. package/dist/data/obs-plugins/obs-nvenc/locale/fr-FR.ini +59 -0
  105. package/dist/data/obs-plugins/obs-nvenc/locale/gl-ES.ini +60 -0
  106. package/dist/data/obs-plugins/obs-nvenc/locale/he-IL.ini +60 -0
  107. package/dist/data/obs-plugins/obs-nvenc/locale/hi-IN.ini +60 -0
  108. package/dist/data/obs-plugins/obs-nvenc/locale/hu-HU.ini +60 -0
  109. package/dist/data/obs-plugins/obs-nvenc/locale/id-ID.ini +55 -0
  110. package/dist/data/obs-plugins/obs-nvenc/locale/is-IS.ini +2 -0
  111. package/dist/data/obs-plugins/obs-nvenc/locale/it-IT.ini +60 -0
  112. package/dist/data/obs-plugins/obs-nvenc/locale/ja-JP.ini +59 -0
  113. package/dist/data/obs-plugins/obs-nvenc/locale/ka-GE.ini +60 -0
  114. package/dist/data/obs-plugins/obs-nvenc/locale/kaa.ini +13 -0
  115. package/dist/data/obs-plugins/obs-nvenc/locale/kmr-TR.ini +20 -0
  116. package/dist/data/obs-plugins/obs-nvenc/locale/ko-KR.ini +58 -0
  117. package/dist/data/obs-plugins/obs-nvenc/locale/ms-MY.ini +58 -0
  118. package/dist/data/obs-plugins/obs-nvenc/locale/nb-NO.ini +24 -0
  119. package/dist/data/obs-plugins/obs-nvenc/locale/nl-NL.ini +54 -0
  120. package/dist/data/obs-plugins/obs-nvenc/locale/nn-NO.ini +1 -0
  121. package/dist/data/obs-plugins/obs-nvenc/locale/pl-PL.ini +55 -0
  122. package/dist/data/obs-plugins/obs-nvenc/locale/pt-BR.ini +58 -0
  123. package/dist/data/obs-plugins/obs-nvenc/locale/pt-PT.ini +60 -0
  124. package/dist/data/obs-plugins/obs-nvenc/locale/ru-RU.ini +60 -0
  125. package/dist/data/obs-plugins/obs-nvenc/locale/sk-SK.ini +56 -0
  126. package/dist/data/obs-plugins/obs-nvenc/locale/sv-SE.ini +58 -0
  127. package/dist/data/obs-plugins/obs-nvenc/locale/th-TH.ini +59 -0
  128. package/dist/data/obs-plugins/obs-nvenc/locale/tr-TR.ini +60 -0
  129. package/dist/data/obs-plugins/obs-nvenc/locale/tt-RU.ini +2 -0
  130. package/dist/data/obs-plugins/obs-nvenc/locale/ug-CN.ini +60 -0
  131. package/dist/data/obs-plugins/obs-nvenc/locale/uk-UA.ini +60 -0
  132. package/dist/data/obs-plugins/obs-nvenc/locale/vi-VN.ini +60 -0
  133. package/dist/data/obs-plugins/obs-nvenc/locale/zh-CN.ini +60 -0
  134. package/dist/data/obs-plugins/obs-nvenc/locale/zh-TW.ini +60 -0
  135. package/dist/data/obs-plugins/obs-qsv11/locale/af-ZA.ini +19 -0
  136. package/dist/data/obs-plugins/obs-qsv11/locale/ar-SA.ini +20 -0
  137. package/dist/data/obs-plugins/obs-qsv11/locale/ba-RU.ini +2 -0
  138. package/dist/data/obs-plugins/obs-qsv11/locale/be-BY.ini +20 -0
  139. package/dist/data/obs-plugins/obs-qsv11/locale/bg-BG.ini +7 -0
  140. package/dist/data/obs-plugins/obs-qsv11/locale/bn-BD.ini +8 -0
  141. package/dist/data/obs-plugins/obs-qsv11/locale/ca-ES.ini +20 -0
  142. package/dist/data/obs-plugins/obs-qsv11/locale/cs-CZ.ini +19 -0
  143. package/dist/data/obs-plugins/obs-qsv11/locale/da-DK.ini +19 -0
  144. package/dist/data/obs-plugins/obs-qsv11/locale/de-DE.ini +19 -0
  145. package/dist/data/obs-plugins/obs-qsv11/locale/el-GR.ini +20 -0
  146. package/dist/data/obs-plugins/obs-qsv11/locale/en-GB.ini +1 -0
  147. package/dist/data/obs-plugins/obs-qsv11/locale/en-US.ini +21 -0
  148. package/dist/data/obs-plugins/obs-qsv11/locale/es-ES.ini +19 -0
  149. package/dist/data/obs-plugins/obs-qsv11/locale/et-EE.ini +20 -0
  150. package/dist/data/obs-plugins/obs-qsv11/locale/eu-ES.ini +11 -0
  151. package/dist/data/obs-plugins/obs-qsv11/locale/fa-IR.ini +20 -0
  152. package/dist/data/obs-plugins/obs-qsv11/locale/fi-FI.ini +20 -0
  153. package/dist/data/obs-plugins/obs-qsv11/locale/fil-PH.ini +11 -0
  154. package/dist/data/obs-plugins/obs-qsv11/locale/fr-FR.ini +20 -0
  155. package/dist/data/obs-plugins/obs-qsv11/locale/gd-GB.ini +8 -0
  156. package/dist/data/obs-plugins/obs-qsv11/locale/gl-ES.ini +20 -0
  157. package/dist/data/obs-plugins/obs-qsv11/locale/he-IL.ini +20 -0
  158. package/dist/data/obs-plugins/obs-qsv11/locale/hi-IN.ini +20 -0
  159. package/dist/data/obs-plugins/obs-qsv11/locale/hr-HR.ini +14 -0
  160. package/dist/data/obs-plugins/obs-qsv11/locale/hu-HU.ini +20 -0
  161. package/dist/data/obs-plugins/obs-qsv11/locale/hy-AM.ini +13 -0
  162. package/dist/data/obs-plugins/obs-qsv11/locale/id-ID.ini +18 -0
  163. package/dist/data/obs-plugins/obs-qsv11/locale/is-IS.ini +1 -0
  164. package/dist/data/obs-plugins/obs-qsv11/locale/it-IT.ini +20 -0
  165. package/dist/data/obs-plugins/obs-qsv11/locale/ja-JP.ini +16 -0
  166. package/dist/data/obs-plugins/obs-qsv11/locale/ka-GE.ini +20 -0
  167. package/dist/data/obs-plugins/obs-qsv11/locale/kaa.ini +10 -0
  168. package/dist/data/obs-plugins/obs-qsv11/locale/kab-KAB.ini +5 -0
  169. package/dist/data/obs-plugins/obs-qsv11/locale/kmr-TR.ini +12 -0
  170. package/dist/data/obs-plugins/obs-qsv11/locale/ko-KR.ini +20 -0
  171. package/dist/data/obs-plugins/obs-qsv11/locale/ms-MY.ini +20 -0
  172. package/dist/data/obs-plugins/obs-qsv11/locale/nb-NO.ini +16 -0
  173. package/dist/data/obs-plugins/obs-qsv11/locale/nl-NL.ini +18 -0
  174. package/dist/data/obs-plugins/obs-qsv11/locale/nn-NO.ini +2 -0
  175. package/dist/data/obs-plugins/obs-qsv11/locale/oc-FR.ini +1 -0
  176. package/dist/data/obs-plugins/obs-qsv11/locale/pl-PL.ini +16 -0
  177. package/dist/data/obs-plugins/obs-qsv11/locale/pt-BR.ini +20 -0
  178. package/dist/data/obs-plugins/obs-qsv11/locale/pt-PT.ini +20 -0
  179. package/dist/data/obs-plugins/obs-qsv11/locale/ro-RO.ini +20 -0
  180. package/dist/data/obs-plugins/obs-qsv11/locale/ru-RU.ini +20 -0
  181. package/dist/data/obs-plugins/obs-qsv11/locale/si-LK.ini +8 -0
  182. package/dist/data/obs-plugins/obs-qsv11/locale/sk-SK.ini +19 -0
  183. package/dist/data/obs-plugins/obs-qsv11/locale/sl-SI.ini +20 -0
  184. package/dist/data/obs-plugins/obs-qsv11/locale/sr-CS.ini +6 -0
  185. package/dist/data/obs-plugins/obs-qsv11/locale/sr-SP.ini +10 -0
  186. package/dist/data/obs-plugins/obs-qsv11/locale/sv-SE.ini +20 -0
  187. package/dist/data/obs-plugins/obs-qsv11/locale/ta-IN.ini +2 -0
  188. package/dist/data/obs-plugins/obs-qsv11/locale/th-TH.ini +20 -0
  189. package/dist/data/obs-plugins/obs-qsv11/locale/tl-PH.ini +5 -0
  190. package/dist/data/obs-plugins/obs-qsv11/locale/tr-TR.ini +20 -0
  191. package/dist/data/obs-plugins/obs-qsv11/locale/tt-RU.ini +3 -0
  192. package/dist/data/obs-plugins/obs-qsv11/locale/ug-CN.ini +20 -0
  193. package/dist/data/obs-plugins/obs-qsv11/locale/uk-UA.ini +20 -0
  194. package/dist/data/obs-plugins/obs-qsv11/locale/vi-VN.ini +20 -0
  195. package/dist/data/obs-plugins/obs-qsv11/locale/zh-CN.ini +20 -0
  196. package/dist/data/obs-plugins/obs-qsv11/locale/zh-TW.ini +20 -0
  197. package/dist/data/obs-plugins/win-capture/get-graphics-offsets32.exe +0 -0
  198. package/dist/data/obs-plugins/win-capture/get-graphics-offsets64.exe +0 -0
  199. package/dist/data/obs-plugins/win-capture/graphics-hook32.dll +0 -0
  200. package/dist/data/obs-plugins/win-capture/graphics-hook64.dll +0 -0
  201. package/dist/data/obs-plugins/win-capture/inject-helper32.exe +0 -0
  202. package/dist/data/obs-plugins/win-capture/inject-helper64.exe +0 -0
  203. package/dist/noobs.node +0 -0
  204. package/dist/obs-plugins/image-source.dll +0 -0
  205. package/dist/obs-plugins/obs-filters.dll +0 -0
  206. package/dist/obs-plugins/obs-nvenc.dll +0 -0
  207. package/dist/obs-plugins/obs-qsv11.dll +0 -0
  208. package/dist/obs-plugins/obs-x264.dll +0 -0
  209. package/dist/obs-plugins/win-capture.dll +0 -0
  210. package/dist/obs-plugins/win-wasapi.dll +0 -0
  211. package/index.d.ts +24 -18
  212. package/package.json +1 -1
  213. package/src/main.cpp +215 -33
  214. package/src/obs_interface.cpp +598 -288
  215. package/src/obs_interface.h +85 -38
@@ -8,46 +8,34 @@
8
8
  #include <graphics/vec4.h>
9
9
  #include <util/platform.h>
10
10
 
11
- std::vector<std::string> ObsInterface::get_available_video_encoders()
12
- {
13
- std::vector<std::string> encoders;
14
- size_t idx = 0;
15
- const char *encoder_type;
16
-
17
- while (obs_enum_encoder_types(idx++, &encoder_type)) {
18
- bool video = obs_get_encoder_type(encoder_type) == OBS_ENCODER_VIDEO;
11
+ void call_jscb(Napi::Env env, Napi::Function cb, SignalData* sd) {
12
+ Napi::Object obj = Napi::Object::New(env);
13
+ obj.Set("type", Napi::String::New(env, sd->type));
14
+ obj.Set("id", Napi::String::New(env, sd->id));
15
+ obj.Set("code", Napi::Number::New(env, sd->code));
19
16
 
20
- if (video)
21
- encoders.emplace_back(encoder_type);
17
+ if (sd->value.has_value()) {
18
+ obj.Set("value", Napi::Number::New(env, sd->value.value()));
22
19
  }
23
20
 
24
- return encoders;
21
+ cb.Call({ obj });
22
+ delete sd;
25
23
  }
26
24
 
27
25
  void ObsInterface::list_encoders(obs_encoder_type type)
28
26
  {
29
- blog(LOG_INFO, "List encoders");
30
- blog(LOG_INFO, "List encoders of type: %d", type);
31
-
27
+ blog(LOG_INFO, "Encoders:");
32
28
  size_t idx = 0;
33
29
  const char *encoder_type;
34
30
 
35
31
  while (obs_enum_encoder_types(idx++, &encoder_type)) {
36
- if (obs_get_encoder_type(encoder_type) != type) {
37
- continue;
38
- }
39
-
40
- // if (obs_get_encoder_caps(encoder_type) & hide_flags) {
41
- // continue;
42
- // }
43
-
44
32
  blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type));
45
33
  }
46
34
  };
47
35
 
48
36
  void ObsInterface::list_source_types()
49
37
  {
50
- blog(LOG_INFO, "List src types");
38
+ blog(LOG_INFO, "Sources:");
51
39
  size_t idx = 0;
52
40
  const char *src = nullptr;
53
41
 
@@ -56,20 +44,9 @@ void ObsInterface::list_source_types()
56
44
  }
57
45
  }
58
46
 
59
- void ObsInterface::list_input_types()
60
- {
61
- blog(LOG_INFO, "List input types");
62
- size_t idx = 0;
63
- const char *src = nullptr;
64
-
65
- while (obs_enum_input_types(idx++, &src)) {
66
- blog(LOG_INFO, "\t- %s", src);
67
- }
68
- }
69
-
70
47
  void ObsInterface::list_output_types()
71
48
  {
72
- blog(LOG_INFO, "List output types");
49
+ blog(LOG_INFO, "Outputs:");
73
50
  size_t idx = 0;
74
51
  const char *src = nullptr;
75
52
 
@@ -78,9 +55,10 @@ void ObsInterface::list_output_types()
78
55
  }
79
56
  }
80
57
 
81
- void ObsInterface::load_module(const char* module, const char* data) {
58
+ void ObsInterface::load_module(const char* module, const char* data, bool allowFail) {
82
59
  blog(LOG_INFO, "Loading module: %s", module);
83
60
  blog(LOG_INFO, "Data path: %s", data);
61
+ blog(LOG_INFO, "Allow fail: %d", allowFail);
84
62
 
85
63
  obs_module_t *ptr = NULL;
86
64
  int success = obs_open_module(&ptr, module, data);
@@ -92,22 +70,60 @@ void ObsInterface::load_module(const char* module, const char* data) {
92
70
 
93
71
  bool initmod = obs_init_module(ptr);
94
72
 
95
- if (!initmod) {
96
- blog(LOG_ERROR, "Failed to initialize module!");
73
+ if (initmod) {
74
+ blog(LOG_INFO, "Module initialized successfully!");
75
+ } else if (allowFail) {
76
+ blog(LOG_INFO, "Module initialization failed, but allowed to fail: %s", module);
77
+ } else {
78
+ blog(LOG_ERROR, "Failed to initialize module: %s", module);
97
79
  throw std::runtime_error("Module initialization failed!");
98
80
  }
99
81
  }
100
82
 
101
- void ObsInterface::reset_video() {
102
- blog(LOG_INFO, "Setup video info");
83
+ void ObsInterface::setVideoContext(int fps, int width, int height) {
84
+ blog(LOG_INFO, "Reset video context");
85
+
86
+ blog(LOG_INFO, "FPS: %d", fps);
87
+ blog(LOG_INFO, "Width: %d", width);
88
+ blog(LOG_INFO, "Height: %d", height);
89
+
90
+ if (fps <= 10) {
91
+ blog(LOG_WARNING, "Invalid FPS provided for reset, using default 10");
92
+ fps = 60;
93
+ }
94
+
95
+ if (width <= 32 || height <= 32) {
96
+ blog(LOG_WARNING, "Invalid width or height provided for reset, using default 1920x1080");
97
+ width = 1920;
98
+ height = 1080;
99
+ }
100
+
101
+ int ret = reset_video(fps, width, height);
102
+
103
+ if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) {
104
+ blog(LOG_WARNING, "Can't reset video as currently active");
105
+ return;
106
+ }
107
+
108
+ if (ret != OBS_VIDEO_SUCCESS) {
109
+ blog(LOG_ERROR, "Failed to reset video context: %d", ret);
110
+ throw std::runtime_error("Failed to reset video context");
111
+ }
103
112
 
113
+ // Recreate the encoders as they are tied to the video context.
114
+ create_video_encoders();
115
+ }
116
+
117
+
118
+ int ObsInterface::reset_video(int fps, int width, int height) {
119
+ blog(LOG_INFO, "Reset video");
104
120
  obs_video_info ovi = {};
105
121
 
106
- ovi.base_width = 1920;
107
- ovi.base_height = 1080;
108
- ovi.output_width = 1920;
109
- ovi.output_height = 1080;
110
- ovi.fps_num = 60;
122
+ ovi.base_width = width;
123
+ ovi.base_height = height;
124
+ ovi.output_width = width;
125
+ ovi.output_height = height;
126
+ ovi.fps_num = fps;
111
127
  ovi.fps_den = 1;
112
128
 
113
129
  ovi.output_format = VIDEO_FORMAT_NV12;
@@ -118,29 +134,14 @@ void ObsInterface::reset_video() {
118
134
  ovi.gpu_conversion = true;
119
135
  ovi.graphics_module = "libobs-d3d11.dll";
120
136
 
121
- int success = obs_reset_video(&ovi);
122
-
123
- obs_enter_graphics();
124
- int dt = gs_get_device_type();
125
- blog(LOG_INFO, "Device type = %d", dt); // should be 1 for D3D11
126
- obs_leave_graphics();
127
-
128
- if (success != OBS_VIDEO_SUCCESS) {
129
- blog(LOG_ERROR, "Failed to reset video!");
130
- throw std::runtime_error("Failed to reset video!");
131
- }
137
+ return obs_reset_video(&ovi);
132
138
  }
133
139
 
134
- void ObsInterface::reset_audio() {
140
+ bool ObsInterface::reset_audio() {
135
141
  struct obs_audio_info oai = {0};
136
142
  oai.samples_per_sec = 48000;
137
143
  oai.speakers = SPEAKERS_STEREO;
138
- bool reset = obs_reset_audio(&oai);
139
-
140
- if (!reset) {
141
- blog(LOG_ERROR, "Failed to reset audio!");
142
- throw std::runtime_error("Failed to reset audio!");
143
- }
144
+ return obs_reset_audio(&oai);
144
145
  }
145
146
 
146
147
  void ObsInterface::init_obs(const std::string& distPath) {
@@ -166,10 +167,12 @@ void ObsInterface::init_obs(const std::string& distPath) {
166
167
 
167
168
  std::string effectsPath = basePath + "data/effects/";
168
169
  std::string pluginPath = basePath + "obs-plugins/";
170
+ std::string pluginDataPath = basePath + "data/obs-plugins/";
169
171
 
170
172
  blog(LOG_INFO, "Base path: %s", basePath.c_str());
171
173
  blog(LOG_INFO, "Effects path: %s", effectsPath.c_str());
172
174
  blog(LOG_INFO, "Plugin path: %s", pluginPath.c_str());
175
+ blog(LOG_INFO, "Data path: %s", pluginDataPath.c_str());
173
176
 
174
177
  // Add the effects path. We need this before resetting video and audio
175
178
  // to ensure the effects are available. The function is deprecated in
@@ -177,28 +180,43 @@ void ObsInterface::init_obs(const std::string& distPath) {
177
180
  obs_add_data_path(effectsPath.c_str());
178
181
 
179
182
  // This must come before loading modules to initialize D3D11.
180
- reset_video();
181
- reset_audio();
183
+ // Choose some sensible defaults that can be reconfigured.
184
+ int rc = reset_video(60, 1920, 1080);
185
+
186
+ if (rc != OBS_VIDEO_SUCCESS) {
187
+ blog(LOG_ERROR, "Failed to reset video!");
188
+ throw std::runtime_error("Failed to reset video!");
189
+ }
190
+
191
+ if (!reset_audio()) {
192
+ blog(LOG_ERROR, "Failed to reset audio!");
193
+ throw std::runtime_error("Failed to reset audio!");
194
+ }
182
195
 
183
196
  std::vector<std::string> modules = {
184
- "obs-x264",
185
- "obs-ffmpeg",
197
+ "obs-x264", // Software encoder.
198
+ "obs-ffmpeg", // Contains AMF (AMD) encoder support.
186
199
  "win-capture", // Required for basically all forms of capture on Windows.
187
200
  "image-source", // Required for image sources.
188
- "win-wasapi" // Required for WASAPI audio input.
201
+ "win-wasapi", // Required for WASAPI audio input.
202
+ "obs-nvenc", // Required for NVENC video encoding.
203
+ "obs-qsv11", // Required for QSV video encoding.
204
+ "obs-filters" // Required for audio filters.
189
205
  };
190
206
 
191
207
  for (const auto& module : modules) {
192
208
  std::string modulePath = pluginPath + module + ".dll";
193
- std::string moduleDataPath = pluginPath + module;
194
- load_module(modulePath.c_str(), moduleDataPath.c_str());
209
+ std::string moduleDataPath = pluginDataPath + module;
210
+
211
+ // NVENC fails if there is no NVENC hardware support.
212
+ bool allowFail = module == "obs-nvenc";
213
+ load_module(modulePath.c_str(), moduleDataPath.c_str(), allowFail);
195
214
  }
196
215
 
197
216
  obs_post_load_modules();
198
217
 
199
218
  list_encoders();
200
219
  list_source_types();
201
- list_input_types();
202
220
  list_output_types();
203
221
 
204
222
  blog(LOG_INFO, "Exit init_obs");
@@ -207,158 +225,119 @@ void ObsInterface::init_obs(const std::string& distPath) {
207
225
  void ObsInterface::create_output() {
208
226
  blog(LOG_INFO, "Create outputs");
209
227
 
210
- blog(LOG_INFO, "Creating replay buffer output");
211
- buffer_output = obs_output_create("replay_buffer", "Buffer Output", NULL, NULL);
228
+ const char* type = buffering ? "replay_buffer" : "ffmpeg_muxer";
229
+ const char* name = buffering ? "Buffer Output" : "File Output";
212
230
 
213
- if (!buffer_output) {
214
- blog(LOG_ERROR, "Failed to create buffer output!");
215
- throw std::runtime_error("Failed to create buffer output!");
231
+ if (output) {
232
+ blog(LOG_DEBUG, "Releasing existing output");
233
+ obs_output_release(output);
216
234
  }
217
235
 
218
- blog(LOG_INFO, "Creating file output");
219
- file_output = obs_output_create("ffmpeg_muxer", "File Output", NULL, NULL);
236
+ blog(LOG_INFO, "Creating replay buffer output");
237
+ output = obs_output_create(type, name, NULL, NULL);
220
238
 
221
- if (!file_output) {
222
- blog(LOG_ERROR, "Failed to create file output!");
223
- throw std::runtime_error("Failed to create file output!");
239
+ if (!output) {
240
+ blog(LOG_ERROR, "Failed to create output!");
241
+ throw std::runtime_error("Failed to create output!");
224
242
  }
225
243
 
226
- obs_data_t *buffer_settings = obs_data_create();
227
- blog(LOG_INFO, "Set replay_buffer settings");
228
- obs_data_set_int(buffer_settings, "max_time_sec", 60);
229
- obs_data_set_int(buffer_settings, "max_size_mb", 1024);
230
- obs_data_set_string(buffer_settings, "directory", recording_path.c_str());
231
- obs_data_set_string(buffer_settings, "format", "%CCYY-%MM-%DD %hh-%mm-%ss");
232
- obs_data_set_string(buffer_settings, "extension", "mp4");
233
- obs_output_update(buffer_output, buffer_settings);
234
- obs_data_release(buffer_settings);
244
+ obs_data_t *settings = obs_data_create();
235
245
 
236
- blog(LOG_INFO, "Set ffmpeg_muxer settings");
237
- obs_data_t *ffmpeg_settings = obs_data_create();
238
- // Need to specify the exact path for ffmpeg_muxer.
239
- std::string filename = recording_path + "\\" + get_current_date_time() + ".mp4";
240
- obs_data_set_string(ffmpeg_settings, "path", filename.c_str());
241
- recording_path = filename;
242
-
243
- // Apply and release the settings.
244
- obs_output_update(file_output, ffmpeg_settings);
245
- obs_data_release(ffmpeg_settings);
246
+ if (buffering) {
247
+ blog(LOG_INFO, "Set replay buffer settings");
248
+ obs_data_set_int(settings, "max_time_sec", 60);
249
+ obs_data_set_int(settings, "max_size_mb", 1024);
250
+ obs_data_set_string(settings, "directory", recording_path.c_str());
251
+ obs_data_set_string(settings, "format", "%CCYY-%MM-%DD %hh-%mm-%ss");
252
+ obs_data_set_string(settings, "extension", "mp4");
253
+ } else {
254
+ blog(LOG_INFO, "Set ffmpeg_muxer settings");
255
+ // Need to specify the exact path for ffmpeg_muxer. We will write this again at start recording.
256
+ std::string filename = recording_path + "\\" + get_current_date_time() + ".mp4";
257
+ obs_data_set_string(settings, "path", filename.c_str());
258
+ unbuffered_output_filename = filename;
259
+ }
246
260
 
247
- // Add the signal handler callback.
248
- connect_signal_handlers(buffer_output);
249
- connect_signal_handlers(file_output);
261
+ obs_output_update(output, settings);
262
+ obs_data_release(settings);
263
+ connect_signal_handlers(output);
250
264
  }
251
265
 
252
266
  void ObsInterface::setRecordingDir(const std::string& recordingPath) {
253
- blog(LOG_INFO, "Set recording directory");
254
- // TODO make this work for file output also.
255
-
256
- obs_output_t *output = buffering ? buffer_output : file_output;
257
-
258
- if (!output) {
259
- blog(LOG_ERROR, "No output to update recording directory");
260
- throw std::runtime_error("Output not initialized");
261
- }
267
+ blog(LOG_INFO, "Set recording directory. Path: %s", recordingPath.c_str());
262
268
 
263
- // check its not active
264
269
  if (obs_output_active(output)) {
265
270
  blog(LOG_ERROR, "Output is active, cannot update recording path");
266
271
  throw std::runtime_error("Output is active, cannot update recording path");
267
272
  }
268
273
 
269
- obs_data_t *settings = obs_output_get_settings(output);
270
-
271
- if (!settings) {
272
- blog(LOG_ERROR, "Failed to get output settings");
273
- throw std::runtime_error("Failed to get output settings");
274
- }
274
+ recording_path = recordingPath;
275
+ create_output();
275
276
 
276
- obs_data_set_string(settings, "directory", recordingPath.c_str());
277
- obs_output_update(output, settings);
278
- obs_data_release(settings);
277
+ create_video_encoders();
278
+ create_audio_encoders();
279
279
  }
280
280
 
281
281
  void ObsInterface::create_video_encoders() {
282
- blog(LOG_INFO, "Create video encoder");
282
+ blog(LOG_INFO, "Set video encoder: %s", video_encoder_id.c_str());
283
283
 
284
- file_video_encoder = obs_video_encoder_create("obs_x264", "h264_stream_file", NULL, NULL);
284
+ if (video_encoder) {
285
+ blog(LOG_DEBUG, "Releasing file video encoder");
286
+ obs_encoder_release(video_encoder);
287
+ video_encoder = nullptr;
288
+ }
285
289
 
290
+ video_encoder = obs_video_encoder_create(
291
+ video_encoder_id.c_str(),
292
+ "noobs_file_encoder",
293
+ video_encoder_settings,
294
+ NULL
295
+ );
286
296
 
287
- if (!file_video_encoder) {
297
+ if (!video_encoder) {
288
298
  blog(LOG_ERROR, "Failed to create video encoder!");
289
299
  throw std::runtime_error("Failed to create video encoder!");
290
300
  }
291
301
 
292
- buffer_video_encoder = obs_video_encoder_create("obs_x264", "h264_stream_buffer", NULL, NULL);
293
-
294
- if (!buffer_video_encoder) {
295
- blog(LOG_ERROR, "Failed to create buffer video encoder!");
296
- throw std::runtime_error("Failed to create buffer video encoder!");
297
- }
298
-
299
- blog(LOG_INFO, "Set file video encoder settings");
300
- obs_data_t* venc_settings = obs_data_create();
301
- // obs_data_set_string(venc_settings, "preset", "speed"); // Faster preset
302
- obs_data_set_string(venc_settings, "rate_control", "CRF");
303
- obs_data_set_int(venc_settings, "crf", 22);
304
- obs_data_set_string(venc_settings, "profile", "main");
305
- obs_data_set_int(venc_settings, "keyint_sec", 1); // Set keyframe interval to 1 second
306
-
307
- obs_encoder_update(file_video_encoder, venc_settings);
308
- obs_encoder_update(buffer_video_encoder, venc_settings);
309
- obs_data_release(venc_settings);
310
-
311
- obs_output_set_video_encoder(file_output, file_video_encoder);
312
- obs_encoder_set_video(file_video_encoder, obs_get_video());
313
- obs_output_set_video_encoder(buffer_output, buffer_video_encoder);
314
- obs_encoder_set_video(buffer_video_encoder, obs_get_video());
302
+ obs_output_set_video_encoder(output, video_encoder);
303
+ obs_encoder_set_video(video_encoder, obs_get_video());
315
304
  }
316
305
 
317
306
  void ObsInterface::create_audio_encoders() {
318
307
  blog(LOG_INFO, "Create audio encoder");
319
308
 
320
- // if (!output) {
321
- // blog(LOG_ERROR, "No output on create_audio_encoders");
322
- // throw std::runtime_error("Failed to create audio encoder!");
323
- // }
324
-
325
- // if (audio_encoder) {
326
- // blog(LOG_DEBUG, "Releasing audio encoder");
327
- // obs_encoder_release(audio_encoder);
328
- // audio_encoder = nullptr;
329
- // }
309
+ if (audio_encoder) {
310
+ blog(LOG_DEBUG, "Releasing audio encoder");
311
+ obs_encoder_release(audio_encoder);
312
+ audio_encoder = nullptr;
313
+ }
330
314
 
331
- file_audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "aac_file", NULL, 0, NULL);
315
+ audio_encoder = obs_audio_encoder_create(
316
+ "ffmpeg_aac",
317
+ "aac_file",
318
+ NULL,
319
+ 0,
320
+ NULL
321
+ );
332
322
 
333
- if (!file_audio_encoder) {
323
+ if (!audio_encoder) {
334
324
  blog(LOG_ERROR, "Failed to create audio encoder!");
335
325
  throw std::runtime_error("Failed to create audio encoder!");
336
326
  }
337
327
 
338
- buffer_audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "aac_buffer", NULL, 0, NULL);
339
-
340
- if (!buffer_audio_encoder) {
341
- blog(LOG_ERROR, "Failed to create buffer audio encoder!");
342
- throw std::runtime_error("Failed to create buffer audio encoder!");
343
- }
344
-
345
328
  blog(LOG_INFO, "Set audio encoder settings");
346
329
  obs_data_t *aenc_settings = obs_data_create();
347
330
  obs_data_set_int(aenc_settings, "bitrate", 128);
348
- obs_encoder_update(file_audio_encoder, aenc_settings);
349
- obs_encoder_update(buffer_audio_encoder, aenc_settings);
331
+ obs_encoder_update(audio_encoder, aenc_settings);
350
332
  obs_data_release(aenc_settings);
351
333
 
352
- obs_output_set_audio_encoder(file_output, file_audio_encoder, 0);
353
- obs_encoder_set_audio(file_audio_encoder, obs_get_audio());
354
-
355
- obs_output_set_audio_encoder(buffer_output, buffer_audio_encoder, 0);
356
- obs_encoder_set_audio(buffer_audio_encoder, obs_get_audio());
334
+ obs_output_set_audio_encoder(output, audio_encoder, 0);
335
+ obs_encoder_set_audio(audio_encoder, obs_get_audio());
357
336
  }
358
337
 
359
338
  void ObsInterface::create_scene() {
360
339
  blog(LOG_INFO, "Create scene");
361
- scene = obs_scene_create("WCR Scene");
340
+ scene = obs_scene_create("Base Scene");
362
341
 
363
342
  if (!scene) {
364
343
  blog(LOG_ERROR, "Failed to create scene!");
@@ -375,12 +354,34 @@ void ObsInterface::create_scene() {
375
354
  obs_set_output_source(0, scene_source); // 0 = video track
376
355
  }
377
356
 
378
- void ObsInterface::createSource(std::string name, std::string type) {
357
+ void ObsInterface::volmeter_callback(void *data,
358
+ const float magnitude[MAX_AUDIO_CHANNELS],
359
+ const float peak[MAX_AUDIO_CHANNELS],
360
+ const float inputPeak[MAX_AUDIO_CHANNELS])
361
+ {
362
+ // blog(LOG_DEBUG, "Volmeter callback triggered: %f %f %f",
363
+ // obs_db_to_mul(magnitude[0]),
364
+ // obs_db_to_mul(peak[0]),
365
+ // obs_db_to_mul(inputPeak[0])
366
+ // );
367
+
368
+ SignalContext* ctx = static_cast<SignalContext*>(data);
369
+ ObsInterface* self = ctx->self;
370
+
371
+ if (!self->volmeter_enabled) {
372
+ return;
373
+ }
374
+
375
+ SignalData* sd = new SignalData{ "volmeter", ctx->id.c_str(), 0, obs_db_to_mul(peak[0]) };
376
+ self->jscb.NonBlockingCall(sd, call_jscb);
377
+ }
378
+
379
+ std::string ObsInterface::createSource(std::string name, std::string type) {
379
380
  blog(LOG_INFO, "Create source: %s of type %s", name.c_str(), type.c_str());
380
381
 
381
382
  obs_source_t *source = obs_source_create(
382
- type.c_str(),
383
- name.c_str(),
383
+ type.c_str(), // Type of source, e.g. "wasapi_input_capture"
384
+ name.c_str(), // Name of the source, e.g. "My Audio Input"
384
385
  NULL, // No settings.
385
386
  NULL // No hotkey data.
386
387
  );
@@ -390,23 +391,102 @@ void ObsInterface::createSource(std::string name, std::string type) {
390
391
  throw std::runtime_error("Failed to create source!");
391
392
  }
392
393
 
394
+ // The name might not match what we asked for if there is a duplicate.
395
+ // So pass it back to the client to avoid potential for a mismatch.
396
+ std::string real_name = obs_source_get_name(source);
397
+
398
+ if (type == AUDIO_OUTPUT || type == AUDIO_INPUT || type == AUDIO_PROCESS) {
399
+ blog(LOG_INFO, "Creating volmeter for source: %s", real_name.c_str());
400
+
401
+ obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_CUBIC);
402
+ obs_volmeter_attach_source(volmeter, source);
403
+
404
+ SignalContext* ctx = new SignalContext{ this, real_name }; // TODO don't leak this.
405
+ obs_volmeter_add_callback(volmeter, volmeter_callback, ctx);
406
+
407
+ // Store the volmeter in the volmeters map.
408
+ volmeters[real_name] = volmeter;
409
+ }
410
+
411
+ if (type == AUDIO_INPUT && force_mono) {
412
+ blog(LOG_INFO, "Setting force mono for new source: %s", real_name.c_str());
413
+ uint32_t flags = obs_source_get_flags(source);
414
+ obs_source_set_flags(source, flags | OBS_SOURCE_FLAG_FORCE_MONO);
415
+ }
416
+
417
+ if (type == AUDIO_INPUT && audio_suppression) {
418
+ blog(LOG_INFO, "Setting up filter for new source: %s", real_name.c_str());
419
+ std::string filter_name = "Filter for " + real_name;
420
+
421
+
422
+ obs_source_t *filter = obs_source_create(
423
+ "noise_suppress_filter_v2",
424
+ filter_name.c_str(),
425
+ nullptr, // Defaults are sensible.
426
+ nullptr
427
+ );
428
+
429
+ if (!filter) {
430
+ blog(LOG_ERROR, "Failed to create filter for source: %s", real_name.c_str());
431
+ throw std::runtime_error("Failed to create filter!");
432
+ }
433
+
434
+ filters[real_name] = filter;
435
+ obs_source_filter_add(source, filter);
436
+ }
437
+
393
438
  // Store the source in the sources map.
394
- sources[name] = source;
439
+ sources[real_name] = source;
440
+
441
+ // Store the dimensions so we can fire a callback if they change.
442
+ uint32_t w = obs_source_get_width(source);
443
+ uint32_t h = obs_source_get_height(source);
444
+ sizes[real_name] = { w, h };
445
+
446
+ return real_name;
395
447
  }
396
448
 
397
449
  void ObsInterface::deleteSource(std::string name) {
398
450
  blog(LOG_INFO, "Delete source: %s", name.c_str());
399
451
 
452
+ // First release a volmeter if there is one present.
453
+ // Only audio sources have volmeters ofcourse.
454
+ auto vol_it = volmeters.find(name);
455
+
456
+ if (vol_it != volmeters.end()) {
457
+ obs_volmeter_t* volmeter = vol_it->second;
458
+ obs_volmeter_remove_callback(volmeter, volmeter_callback, this);
459
+ obs_volmeter_detach_source(volmeter);
460
+ obs_volmeter_destroy(volmeter);
461
+ blog(LOG_INFO, "Volmeter deleted for source: %s", name.c_str());
462
+ volmeters.erase(name);
463
+ }
464
+
465
+ // Now deal with the source itself.
400
466
  auto it = sources.find(name);
401
467
 
402
468
  if (it == sources.end()) {
403
- blog(LOG_WARNING, "Source %s not found", name.c_str());
469
+ blog(LOG_WARNING, "Source %s not found when deleting", name.c_str());
404
470
  return;
405
471
  }
406
472
 
407
473
  obs_source_t* source = it->second;
474
+
475
+ // Remove and release any filters.
476
+ auto filter_it = filters.find(name);
477
+
478
+ if (filter_it != filters.end()) {
479
+ obs_source_t* filter = filter_it->second;
480
+ obs_source_filter_remove(source, filter);
481
+ obs_source_release(filter);
482
+ filters.erase(name);
483
+ blog(LOG_INFO, "Filter deleted for source: %s", name.c_str());
484
+ }
485
+
486
+ obs_source_remove(source); // ???
408
487
  obs_source_release(source);
409
488
  sources.erase(name);
489
+ sizes.erase(name);
410
490
  blog(LOG_INFO, "Source deleted: %s", name.c_str());
411
491
  }
412
492
 
@@ -416,7 +496,7 @@ obs_data_t* ObsInterface::getSourceSettings(std::string name) {
416
496
  auto it = sources.find(name);
417
497
 
418
498
  if (it == sources.end()) {
419
- blog(LOG_WARNING, "Source %s not found", name.c_str());
499
+ blog(LOG_WARNING, "Source %s not found when getting settings", name.c_str());
420
500
  throw std::runtime_error("Source not found!");
421
501
  }
422
502
 
@@ -437,13 +517,27 @@ void ObsInterface::setSourceSettings(std::string name, obs_data_t* settings) {
437
517
  auto it = sources.find(name);
438
518
 
439
519
  if (it == sources.end()) {
440
- blog(LOG_WARNING, "Source %s not found", name.c_str());
520
+ blog(LOG_WARNING, "Source %s not found when setting settings", name.c_str());
441
521
  throw std::runtime_error("Source not found!");
442
522
  }
443
523
 
444
524
  obs_source_t* source = it->second;
445
-
446
525
  obs_source_update(source, settings);
526
+
527
+ // If this is an audio source, it may have an attached volmeter.
528
+ auto vol_it = volmeters.find(name);
529
+
530
+ if (vol_it != volmeters.end()) {
531
+ // Rebind it. This avoids leaving it attached to stale audio stream
532
+ // in the event of a device change.
533
+ blog(LOG_INFO, "Rebinding volmeter for source: %s", name.c_str());
534
+ obs_volmeter_t* volmeter = vol_it->second;
535
+ obs_volmeter_attach_source(volmeter, source);
536
+
537
+ // Flush the volmeter: send a zero signal in-case it never triggers any
538
+ // more callbacks. That can happen on selecting a device with no audio.
539
+ zeroVolmeter(name);
540
+ }
447
541
  }
448
542
 
449
543
  obs_properties_t* ObsInterface::getSourceProperties(std::string name) {
@@ -451,7 +545,7 @@ obs_properties_t* ObsInterface::getSourceProperties(std::string name) {
451
545
  auto it = sources.find(name);
452
546
 
453
547
  if (it == sources.end()) {
454
- blog(LOG_WARNING, "Source %s not found", name.c_str());
548
+ blog(LOG_WARNING, "Source %s not found when getting properties", name.c_str());
455
549
  throw std::runtime_error("Source not found!");
456
550
  }
457
551
 
@@ -466,65 +560,30 @@ obs_properties_t* ObsInterface::getSourceProperties(std::string name) {
466
560
  return props;
467
561
  }
468
562
 
469
- void call_jscb(Napi::Env env, Napi::Function cb, SignalData* sd) {
470
- Napi::Object obj = Napi::Object::New(env);
471
- obj.Set("id", Napi::String::New(env, sd->id));
472
- obj.Set("code", Napi::Number::New(env, sd->code));
473
- cb.Call({ obj });
474
- delete sd;
475
- }
476
-
477
- void ObsInterface::output_signal_handler_starting(void *data, calldata_t *cd) {
478
- long long code = calldata_int(cd, "code");
479
- ObsInterface* self = static_cast<ObsInterface*>(data);
480
- SignalData* sd = new SignalData{ "starting", code };
481
- self->jscb.NonBlockingCall(sd, call_jscb);
482
- }
483
-
484
- void ObsInterface::output_signal_handler_start(void *data, calldata_t *cd) {
485
- long long code = calldata_int(cd, "code");
486
- ObsInterface* self = static_cast<ObsInterface*>(data);
487
- SignalData* sd = new SignalData{ "start", code };
488
- self->jscb.NonBlockingCall(sd, call_jscb);
489
- }
490
-
491
- void ObsInterface::output_signal_handler_stop(void *data, calldata_t *cd) {
563
+ void ObsInterface::output_signal_handler(void *data, calldata_t *cd) {
492
564
  long long code = calldata_int(cd, "code");
493
- ObsInterface* self = static_cast<ObsInterface*>(data);
494
- SignalData* sd = new SignalData{ "stop", code };
495
- self->jscb.NonBlockingCall(sd, call_jscb);
496
- }
497
565
 
498
- void ObsInterface::output_signal_handler_stopping(void *data, calldata_t *cd) {
499
- long long code = calldata_int(cd, "code");
500
- ObsInterface* self = static_cast<ObsInterface*>(data);
501
- SignalData* sd = new SignalData{ "stopping", code };
502
- self->jscb.NonBlockingCall(sd, call_jscb);
503
- }
566
+ SignalContext* ctx = static_cast<SignalContext*>(data);
567
+ ObsInterface* self = ctx->self;
504
568
 
505
- void ObsInterface::output_signal_handler_saved(void *data, calldata_t *cd) {
506
- long long code = calldata_int(cd, "code");
507
- ObsInterface* self = static_cast<ObsInterface*>(data);
508
- SignalData* sd = new SignalData{ "saved", code };
569
+ SignalData* sd = new SignalData{ "output", ctx->id.c_str(), code };
509
570
  self->jscb.NonBlockingCall(sd, call_jscb);
510
571
  }
511
572
 
512
573
  void ObsInterface::connect_signal_handlers(obs_output_t *output) {
513
574
  signal_handler_t *sh = obs_output_get_signal_handler(output);
514
- signal_handler_connect(sh, "starting", output_signal_handler_starting, this);
515
- signal_handler_connect(sh, "start", output_signal_handler_start, this);
516
- signal_handler_connect(sh, "stopping", output_signal_handler_stopping, this);
517
- signal_handler_connect(sh, "stop", output_signal_handler_stop, this);
518
- signal_handler_connect(sh, "saved", output_signal_handler_saved, this);
575
+ signal_handler_connect(sh, "start", output_signal_handler, start_ctx);
576
+ signal_handler_connect(sh, "starting", output_signal_handler, starting_ctx);
577
+ signal_handler_connect(sh, "stopping", output_signal_handler, stopping_ctx);
578
+ signal_handler_connect(sh, "stop", output_signal_handler, stop_ctx);
519
579
  }
520
580
 
521
581
  void ObsInterface::disconnect_signal_handlers(obs_output_t *output) {
522
582
  signal_handler_t *sh = obs_output_get_signal_handler(output);
523
- signal_handler_disconnect(sh, "starting", output_signal_handler_starting, this);
524
- signal_handler_disconnect(sh, "start", output_signal_handler_start, this);
525
- signal_handler_disconnect(sh, "stopping", output_signal_handler_stopping, this);
526
- signal_handler_disconnect(sh, "stop", output_signal_handler_stop, this);
527
- signal_handler_disconnect(sh, "saved", output_signal_handler_saved, this);
583
+ signal_handler_disconnect(sh, "starting", output_signal_handler, starting_ctx);
584
+ signal_handler_disconnect(sh, "start", output_signal_handler, start_ctx);
585
+ signal_handler_disconnect(sh, "stopping", output_signal_handler, stopping_ctx);
586
+ signal_handler_disconnect(sh, "stop", output_signal_handler, stop_ctx);
528
587
  }
529
588
 
530
589
  bool draw_source_outline(obs_scene_t *scene, obs_sceneitem_t *item, void *p) {
@@ -582,6 +641,12 @@ bool draw_source_outline(obs_scene_t *scene, obs_sceneitem_t *item, void *p) {
582
641
  gs_draw_sprite(nullptr, 0, 4.0f, height);
583
642
  gs_matrix_pop();
584
643
 
644
+ // Dragging point box (25x25 pixels in bottom-right corner)
645
+ gs_matrix_push();
646
+ gs_matrix_translate3f(pos.x + width - 25.0f, pos.y + height - 25.0f, 0.0f);
647
+ gs_draw_sprite(nullptr, 0, 25.0f, 25.0f);
648
+ gs_matrix_pop();
649
+
585
650
  gs_matrix_pop();
586
651
 
587
652
  gs_technique_end_pass(tech);
@@ -622,15 +687,30 @@ void draw_callback(void* data, uint32_t cx, uint32_t cy) {
622
687
  // Renders the scene now the graphics context is setup.
623
688
  obs_render_main_texture();
624
689
 
625
- // Draw boxes around sources.
626
- obs_scene_t* scene = obs_get_scene_by_name("WCR Scene");
690
+ // Draw boxes around sources, if enabled.
627
691
  if (obsInterface->getDrawSourceOutlineEnabled()) {
692
+ obs_scene_t* scene = obs_get_scene_by_name("Base Scene");
628
693
  obs_scene_enum_items(scene, draw_source_outline, NULL);
694
+ obs_scene_release(scene);
629
695
  }
630
- obs_scene_release(scene);
631
696
 
632
697
  gs_projection_pop();
633
698
  gs_viewport_pop();
699
+
700
+ // Iterate over the sources and check for changes to size.
701
+ for (const auto& [name, source] : obsInterface->sources) {
702
+ SourceSize last = obsInterface->sizes[name];
703
+
704
+ uint32_t w = obs_source_get_width(source);
705
+ uint32_t h = obs_source_get_height(source);
706
+
707
+ if (w != last.width || h != last.height) {
708
+ blog(LOG_INFO, "Source %s changed size from (%d x %d) to (%d x %d)",
709
+ name.c_str(), last.width, last.height, w, h);
710
+ obsInterface->sourceCallback(name);
711
+ obsInterface->sizes[name] = { w, h };
712
+ }
713
+ }
634
714
  }
635
715
 
636
716
  void ObsInterface::initPreview(HWND parent) {
@@ -683,15 +763,15 @@ void ObsInterface::initPreview(HWND parent) {
683
763
  obs_display_set_enabled(display, false);
684
764
  }
685
765
 
686
- void ObsInterface::showPreview(int x, int y, int width, int height) {
687
- blog(LOG_INFO, "ObsInterface::showPreview");
766
+ void ObsInterface::configurePreview(int x, int y, int width, int height) {
767
+ blog(LOG_INFO, "ObsInterface::configurePreview");
688
768
 
689
769
  if (!preview_hwnd || !display) {
690
770
  blog(LOG_ERROR, "Preview window not initialized");
691
771
  return;
692
772
  }
693
773
 
694
- blog(LOG_INFO, "Showing preview child window at (%d, %d) with size (%d x %d)", x, y, width, height);
774
+ blog(LOG_INFO, "Moving preview child window to (%d, %d) with size (%d x %d)", x, y, width, height);
695
775
 
696
776
  // Resize and move the existing child window.
697
777
  bool success = SetWindowPos(
@@ -710,8 +790,18 @@ void ObsInterface::showPreview(int x, int y, int width, int height) {
710
790
  uint32_t w, h;
711
791
  obs_display_size(display, &w, &h); // Get the display size to match the video context.
712
792
  blog(LOG_INFO, "Current Display size set to (%d x %d)", w, h);
713
-
714
793
  obs_display_resize(display, width, height);
794
+ obs_display_set_enabled(display, true);
795
+ }
796
+
797
+ void ObsInterface::showPreview() {
798
+ blog(LOG_INFO, "ObsInterface::showPreview");
799
+
800
+ if (!preview_hwnd || !display) {
801
+ blog(LOG_ERROR, "Preview window not initialized");
802
+ return;
803
+ }
804
+
715
805
  ShowWindow(preview_hwnd, SW_SHOW);
716
806
  obs_display_set_enabled(display, true);
717
807
  }
@@ -723,14 +813,24 @@ void ObsInterface::hidePreview() {
723
813
  ShowWindow(preview_hwnd, SW_HIDE);
724
814
  blog(LOG_INFO, "Preview child window hidden");
725
815
  }
816
+ }
817
+
818
+ void ObsInterface::disablePreview() {
819
+ blog(LOG_INFO, "ObsInterface::disablePreview");
820
+
821
+ if (!display) {
822
+ blog(LOG_ERROR, "Preview window not initialized");
823
+ return;
824
+ }
726
825
 
826
+ hidePreview();
727
827
  obs_display_set_enabled(display, false);
728
828
  }
729
829
 
730
- float ObsInterface::getPreviewScaleFactor() {
830
+ PreviewInfo ObsInterface::getPreviewInfo() {
731
831
  if (!display) {
732
- blog(LOG_WARNING, "Display not initialized");
733
- return 1.0f; // Default scale
832
+ blog(LOG_WARNING, "Display not initialized when calling getPreviewInfo");
833
+ return { 1920, 1080, 1920, 1080 }; // Default values
734
834
  }
735
835
 
736
836
  obs_video_info ovi;
@@ -739,19 +839,14 @@ float ObsInterface::getPreviewScaleFactor() {
739
839
  uint32_t width, height;
740
840
  obs_display_size(display, &width, &height);
741
841
 
742
- float scaleX = float(width) / float(ovi.base_width);
743
- float scaleY = float(height) / float(ovi.base_height);
744
-
745
- float previewScale;
746
-
747
- // Pick the limiting scale factor.
748
- if (scaleX < scaleY) {
749
- previewScale = scaleX;
750
- } else {
751
- previewScale = scaleY;
752
- }
842
+ PreviewInfo info = {
843
+ ovi.base_width,
844
+ ovi.base_height,
845
+ width,
846
+ height,
847
+ };
753
848
 
754
- return previewScale;
849
+ return info;
755
850
  }
756
851
 
757
852
  void ObsInterface::setDrawSourceOutline(bool enabled) {
@@ -765,7 +860,6 @@ bool ObsInterface::getDrawSourceOutlineEnabled() {
765
860
  ObsInterface::ObsInterface(
766
861
  const std::string& distPath,
767
862
  const std::string& logPath,
768
- const std::string& recordingPath,
769
863
  Napi::ThreadSafeFunction cb
770
864
  ) {
771
865
  // Setup logs first so we have logs for the initialization.
@@ -777,12 +871,16 @@ ObsInterface::ObsInterface(
777
871
 
778
872
  // Setup callback function.
779
873
  jscb = cb;
780
- recording_path = recordingPath;
874
+
875
+ // Contexts for signal callbacks.
876
+ starting_ctx = new SignalContext{ this, "starting" };
877
+ start_ctx = new SignalContext{ this, "start" };
878
+ stopping_ctx = new SignalContext{ this, "stopping" };
879
+ stop_ctx = new SignalContext{ this, "stop" };
781
880
 
782
881
  // Create the resources we rely on.
783
- create_output();
784
882
  create_scene();
785
-
883
+ create_output();
786
884
  create_video_encoders();
787
885
  create_audio_encoders();
788
886
  }
@@ -790,16 +888,37 @@ ObsInterface::ObsInterface(
790
888
  ObsInterface::~ObsInterface() {
791
889
  blog(LOG_DEBUG, "Destroying ObsInterface");
792
890
 
793
- if (jscb) {
794
- blog(LOG_DEBUG, "Releasing JavaScript callback");
795
- jscb.Release();
891
+ for (auto& kv : volmeters) {
892
+ obs_volmeter_t* volmeter = kv.second;
893
+ obs_volmeter_remove_callback(volmeter, volmeter_callback, this);
894
+ obs_volmeter_detach_source(volmeter);
895
+ obs_volmeter_destroy(volmeter);
896
+ blog(LOG_INFO, "Volmeter deleted for source: %s", kv.first.c_str());
897
+ volmeters.erase(kv.first);
796
898
  }
797
899
 
900
+ delete starting_ctx;
901
+ delete start_ctx;
902
+ delete stopping_ctx;
903
+ delete stop_ctx;
904
+
798
905
  for (auto& kv : sources) {
799
906
  std::string name = kv.first;
800
907
  obs_source_t* source = kv.second;
908
+
909
+ auto filter_it = filters.find(name);
910
+
911
+ if (filter_it != filters.end()) {
912
+ obs_source_t* filter = filter_it->second;
913
+ obs_source_filter_remove(source, filter);
914
+ obs_source_release(filter);
915
+ filters.erase(name);
916
+ blog(LOG_INFO, "Filter removed for source: %s on shutdown", name.c_str());
917
+ }
918
+
801
919
  blog(LOG_DEBUG, "Releasing source: %s", name.c_str());
802
920
  obs_source_release(source);
921
+ sources.erase(name);
803
922
  }
804
923
 
805
924
  if (scene) {
@@ -807,24 +926,14 @@ ObsInterface::~ObsInterface() {
807
926
  obs_scene_release(scene);
808
927
  }
809
928
 
810
- if (buffer_output) {
811
- if (obs_output_active(buffer_output)) {
812
- blog(LOG_DEBUG, "Force stopping output");
813
- obs_output_force_stop(buffer_output);
814
- }
815
-
816
- blog(LOG_DEBUG, "Releasing output");
817
- obs_output_release(buffer_output);
818
- }
819
-
820
- if (file_output) {
821
- if (obs_output_active(file_output)) {
929
+ if (output) {
930
+ if (obs_output_active(output)) {
822
931
  blog(LOG_DEBUG, "Force stopping output");
823
- obs_output_force_stop(file_output);
932
+ obs_output_force_stop(output);
824
933
  }
825
934
 
826
935
  blog(LOG_DEBUG, "Releasing output");
827
- obs_output_release(file_output);
936
+ obs_output_release(output);
828
937
  }
829
938
 
830
939
  // if (video_encoder) {
@@ -839,18 +948,21 @@ ObsInterface::~ObsInterface() {
839
948
 
840
949
  blog(LOG_DEBUG, "Now shutting down OBS");
841
950
  obs_shutdown();
842
- }
843
951
 
844
- bool ObsInterface::setBuffering(bool value) {
845
- obs_output_t* output = buffering ? buffer_output : file_output;
952
+ if (jscb) {
953
+ blog(LOG_DEBUG, "Releasing JavaScript callback");
954
+ jscb.Release();
955
+ }
956
+ }
846
957
 
958
+ void ObsInterface::setBuffering(bool value) {
847
959
  if (obs_output_active(output)) {
848
960
  blog(LOG_ERROR, "Cannot change buffering state while output is active");
849
- return false;
961
+ throw new std::runtime_error("Cannot change buffering state while output is active");
850
962
  }
851
963
 
852
964
  buffering = value;
853
- return buffering;
965
+ create_output();
854
966
  }
855
967
 
856
968
  void ObsInterface::startBuffering() {
@@ -861,8 +973,6 @@ void ObsInterface::startBuffering() {
861
973
  throw std::runtime_error("Buffering is not enabled!");
862
974
  }
863
975
 
864
- obs_output_t* output = buffer_output;
865
-
866
976
  if (!output) {
867
977
  blog(LOG_ERROR, "Output is not initialized!");
868
978
  throw std::runtime_error("Output is not initialized!");
@@ -887,7 +997,11 @@ void ObsInterface::startBuffering() {
887
997
 
888
998
  void ObsInterface::startRecording(int offset) {
889
999
  blog(LOG_INFO, "ObsInterface::startRecording enter");
890
- obs_output_t* output = buffering ? buffer_output : file_output;
1000
+
1001
+ if (recording_path == "") {
1002
+ blog(LOG_ERROR, "Recording path is not set");
1003
+ throw std::runtime_error("Recording path is not set");
1004
+ }
891
1005
 
892
1006
  if (buffering) {
893
1007
  bool is_active = obs_output_active(output);
@@ -906,9 +1020,17 @@ void ObsInterface::startRecording(int offset) {
906
1020
  calldata_free(&cd);
907
1021
 
908
1022
  if (!success) {
1023
+ blog(LOG_ERROR, "Failed to call convert procedure handler");
909
1024
  throw std::runtime_error("Failed to call convert procedure handler");
910
1025
  }
911
1026
  } else {
1027
+ obs_data_t *ffmpeg_settings = obs_data_create();
1028
+ std::string filename = recording_path + "\\" + get_current_date_time() + ".mp4";
1029
+ obs_data_set_string(ffmpeg_settings, "path", filename.c_str());
1030
+ obs_output_update(output, ffmpeg_settings);
1031
+ obs_data_release(ffmpeg_settings);
1032
+ unbuffered_output_filename = filename;
1033
+
912
1034
  blog(LOG_INFO, "Starting ffmpeg_muxer output");
913
1035
 
914
1036
  bool is_active = obs_output_active(output);
@@ -933,7 +1055,6 @@ void ObsInterface::startRecording(int offset) {
933
1055
 
934
1056
  void ObsInterface::stopRecording() {
935
1057
  blog(LOG_INFO, "ObsInterface::stopRecording enter");
936
- obs_output_t* output = buffering ? buffer_output : file_output;
937
1058
  bool is_active = obs_output_active(output);
938
1059
 
939
1060
  if (!is_active) {
@@ -945,19 +1066,31 @@ void ObsInterface::stopRecording() {
945
1066
  blog(LOG_INFO, "ObsInterface::stopRecording exited");
946
1067
  }
947
1068
 
1069
+ void ObsInterface::forceStopRecording() {
1070
+ blog(LOG_INFO, "ObsInterface::forceStopRecording enter");
1071
+ bool is_active = obs_output_active(output);
1072
+
1073
+ if (!is_active) {
1074
+ blog(LOG_WARNING, "Output is not active");
1075
+ return;
1076
+ }
1077
+
1078
+ obs_output_force_stop(output);
1079
+ blog(LOG_INFO, "ObsInterface::forceStopRecording exited");
1080
+ }
1081
+
948
1082
  std::string ObsInterface::getLastRecording() {
949
1083
  blog(LOG_INFO, "calling get last replay proc handler");
950
1084
  calldata cd;
951
1085
  calldata_init(&cd);
952
1086
 
953
- obs_output_t* output = buffering ? buffer_output : file_output;
954
1087
  proc_handler_t *ph = obs_output_get_proc_handler(output);
955
1088
 
956
1089
  const char* type = obs_output_get_id(output);
957
1090
 
958
1091
  if (!buffering) {
959
1092
  blog(LOG_INFO, "Getting last recording path from ffmpeg_muxer");
960
- return recording_path;
1093
+ return unbuffered_output_filename;
961
1094
  }
962
1095
 
963
1096
  bool success = proc_handler_call(ph, "get_last_replay", &cd);
@@ -984,7 +1117,7 @@ void ObsInterface::addSourceToScene(std::string name) {
984
1117
  auto it = sources.find(name);
985
1118
 
986
1119
  if (it == sources.end()) {
987
- blog(LOG_WARNING, "Source %s not found", name.c_str());
1120
+ blog(LOG_WARNING, "Source %s not found when adding to scene", name.c_str());
988
1121
  throw std::runtime_error("Source not found!");
989
1122
  }
990
1123
 
@@ -1021,7 +1154,7 @@ void ObsInterface::getSourcePos(std::string name, vec2* pos, vec2* size, vec2* s
1021
1154
  auto it = sources.find(name);
1022
1155
 
1023
1156
  if (it == sources.end()) {
1024
- blog(LOG_WARNING, "Source %s not found", name.c_str());
1157
+ blog(LOG_WARNING, "Source %s not found when getting source position", name.c_str());
1025
1158
  throw std::runtime_error("Source not found!");
1026
1159
  }
1027
1160
 
@@ -1057,4 +1190,181 @@ void ObsInterface::setSourcePos(std::string name, vec2* pos, vec2* scale) {
1057
1190
 
1058
1191
  obs_sceneitem_set_pos(item, pos);
1059
1192
  obs_sceneitem_set_scale(item, scale);
1060
- }
1193
+ }
1194
+
1195
+ std::vector<std::string> ObsInterface::listAvailableVideoEncoders()
1196
+ {
1197
+ std::vector<std::string> encoders;
1198
+ size_t idx = 0;
1199
+ const char *encoder_type;
1200
+
1201
+ while (obs_enum_encoder_types(idx++, &encoder_type)) {
1202
+ bool video = obs_get_encoder_type(encoder_type) == OBS_ENCODER_VIDEO;
1203
+
1204
+ if (video)
1205
+ encoders.emplace_back(encoder_type);
1206
+ }
1207
+
1208
+ return encoders;
1209
+ }
1210
+
1211
+ void ObsInterface::setVideoEncoder(std::string id, obs_data_t* settings) {
1212
+ if (obs_output_active(output)) {
1213
+ blog(LOG_WARNING, "Cannot change video encoder while output is active");
1214
+ throw new std::runtime_error("Output is active when trying to change encoder");
1215
+ }
1216
+
1217
+ video_encoder_id = id;
1218
+ obs_data_release(video_encoder_settings);
1219
+ video_encoder_settings = settings;
1220
+ create_video_encoders();
1221
+ }
1222
+
1223
+ void ObsInterface::setMuteAudioInputs(bool mute) {
1224
+ // Loop over all sources, and set the mute state if they are of type "wasapi_input_capture".
1225
+ for (const auto& kv : sources) {
1226
+ const std::string& name = kv.first;
1227
+ obs_source_t* source = kv.second;
1228
+
1229
+ if (!source) {
1230
+ blog(LOG_WARNING, "Source %s not found when muting audio inputs", name.c_str());
1231
+ continue;
1232
+ }
1233
+
1234
+ const char* type = obs_source_get_id(source);
1235
+
1236
+ if (strcmp(type, AUDIO_INPUT) == 0) {
1237
+ obs_source_set_muted(source, mute);
1238
+ }
1239
+ }
1240
+ }
1241
+
1242
+ void ObsInterface::setSourceVolume(std::string name, float volume) {
1243
+ blog(LOG_INFO, "Setting source %s volume to %f", name.c_str(), volume);
1244
+
1245
+ auto it = sources.find(name);
1246
+
1247
+ if (it == sources.end()) {
1248
+ blog(LOG_WARNING, "Source %s not found when setting volume", name.c_str());
1249
+ return;
1250
+ }
1251
+
1252
+ obs_source_t* source = it->second;
1253
+ const char* type = obs_source_get_id(source);
1254
+
1255
+ bool audio =
1256
+ strcmp(type, AUDIO_OUTPUT) == 0 ||
1257
+ strcmp(type, AUDIO_INPUT) == 0 ||
1258
+ strcmp(type, AUDIO_PROCESS) == 0;
1259
+
1260
+ if (!audio) {
1261
+ blog(LOG_WARNING, "Source %s is not a valid audio source", name.c_str());
1262
+ return;
1263
+ }
1264
+
1265
+ obs_source_set_volume(source, volume);
1266
+ }
1267
+
1268
+ void ObsInterface::setVolmeterEnabled(bool enabled) {
1269
+ blog(LOG_INFO, "Setting volmeter enabled: %d", enabled);
1270
+ volmeter_enabled = enabled;
1271
+ }
1272
+
1273
+ void ObsInterface::setForceMono(bool enabled) {
1274
+ blog(LOG_INFO, "%s force mono on all input sources", enabled ? "Enabling" : "Disabling");
1275
+ force_mono = enabled;
1276
+
1277
+ // Loop over existing sources and update the force mono flags.
1278
+ for (const auto& kv : sources) {
1279
+ const std::string& name = kv.first;
1280
+ obs_source_t* source = kv.second;
1281
+
1282
+ if (!source) {
1283
+ blog(LOG_WARNING, "Source %s not found when setting force mono", name.c_str());
1284
+ continue;
1285
+ }
1286
+
1287
+ const char* type = obs_source_get_id(source);
1288
+
1289
+ if (strcmp(type, AUDIO_INPUT) != 0) {
1290
+ // Force mono is only applicable to microphones, skip other types.
1291
+ continue;
1292
+ }
1293
+
1294
+ if (enabled) {
1295
+ blog(LOG_INFO, "Setting force mono flag on source %s", name.c_str());
1296
+ uint32_t flags = obs_source_get_flags(source);
1297
+ obs_source_set_flags(source, flags | OBS_SOURCE_FLAG_FORCE_MONO);
1298
+ } else {
1299
+ blog(LOG_INFO, "Unsetting force mono flag on source %s", name.c_str());
1300
+ uint32_t flags = obs_source_get_flags(source);
1301
+ obs_source_set_flags(source, flags & ~OBS_SOURCE_FLAG_FORCE_MONO);
1302
+ }
1303
+ }
1304
+ }
1305
+
1306
+ void ObsInterface::setAudioSuppression(bool enabled) {
1307
+ blog(LOG_INFO, "%s audio suppression on all input devices", enabled ? "Enabling" : "Disabling");
1308
+ audio_suppression = enabled;
1309
+
1310
+ // Loop over existing sources and add filters to any that need it.
1311
+ for (const auto& kv : sources) {
1312
+ const std::string& name = kv.first;
1313
+ obs_source_t* source = kv.second;
1314
+
1315
+ if (!source) {
1316
+ blog(LOG_WARNING, "Source %s not found when adding filters", name.c_str());
1317
+ continue;
1318
+ }
1319
+
1320
+ const char* type = obs_source_get_id(source);
1321
+
1322
+ if (strcmp(type, AUDIO_INPUT) != 0) {
1323
+ // Don't care about non-input sources. This is purely for suppressing
1324
+ // microphone background noise.
1325
+ continue;
1326
+ }
1327
+
1328
+ // Check for a filter existing and add or remove it as appropriate.
1329
+ auto filter_it = filters.find(name);
1330
+
1331
+ if (audio_suppression && filter_it == filters.end()) {
1332
+ blog(LOG_INFO, "Setting up filter for source: %s", name.c_str());
1333
+
1334
+ std::string filter_name = "Filter for " + name;
1335
+
1336
+ obs_source_t *filter = obs_source_create(
1337
+ "noise_suppress_filter_v2",
1338
+ filter_name.c_str(),
1339
+ nullptr, // Defaults are sensible.
1340
+ nullptr
1341
+ );
1342
+
1343
+ if (!filter) {
1344
+ blog(LOG_ERROR, "Failed to create filter for source: %s", name.c_str());
1345
+ throw std::runtime_error("Failed to create filter!");
1346
+ }
1347
+
1348
+ filters[name] = filter;
1349
+ obs_source_filter_add(source, filter);
1350
+ } else if (!audio_suppression && filter_it != filters.end()) {
1351
+ blog(LOG_INFO, "Removing filters for source: %s", name.c_str());
1352
+ obs_source_t* filter = filter_it->second;
1353
+ obs_source_filter_remove(source, filter);
1354
+ filters.erase(name);
1355
+ obs_source_release(filter);
1356
+ }
1357
+ }
1358
+ }
1359
+
1360
+ void ObsInterface::sourceCallback(std::string name) {
1361
+ blog(LOG_INFO, "Source callback triggered for %s", name.c_str());
1362
+ SignalData* sd = new SignalData{ "source", name.c_str(), 0 };
1363
+ jscb.NonBlockingCall(sd, call_jscb);
1364
+ }
1365
+
1366
+ void ObsInterface::zeroVolmeter(std::string name) {
1367
+ blog(LOG_INFO, "Zeroing volmeter for %s", name.c_str());
1368
+ SignalData* sd = new SignalData{ "volmeter", name.c_str(), 0, 0 };
1369
+ jscb.NonBlockingCall(sd, call_jscb);
1370
+ }