├── counter.gif ├── LICENSE ├── README.md └── hotkey_counter.py /counter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upgradeQ/Counter/HEAD/counter.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 HQupgradeHQ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obscounter 2 | 3 | Python implementation of hotkey counter in OBS Studio. 4 | 5 |  6 | 7 | # Requirements 8 | 9 | For Windows install [python3.6](https://www.python.org/downloads/release/python-368/) 64 or 32 bit depending on your OBS. Since 28 version OBS Studio supports most 3.x Python versions. 10 | 11 | # Installation 12 | 13 | 1. Download [source code](https://github.com/upgradeQ/Obscounter/archive/master.zip). 14 | 2. Unzip the file to be able to access `hotkey_counter.py`. Remember the file location because you will need this later. 15 | 16 | # Usage 17 | 18 | 1. On OBS, under Sources, click + to create a new text source (below, you will see Text GDI+). You may leave it blank. 19 | > [!NOTE] 20 | > You can also use FreeType2 (which is hidden in Depricated) as it's more efficient 21 | 22 |  23 | 24 | 2. On the OBS menu, click `Tools > Scripts` 25 | 26 |  27 | 28 | 3. Under the `Python Settings` tab, make sure the Python path is configured. 29 | 30 |  31 | 32 | 4. Under the `Scripts` tab, click the + sign to add this script. 33 | 34 |  35 | 36 | 5. Close the scripts window, and return to your OBS window and configure OBS settings by clicking `File > Settings`. 37 | 38 | 6. Configure the hotkeys. 39 | 40 |  41 | 42 |  43 | 44 | ## How do I use more counters? 45 | 46 | If you need additional counters, duplicate the `hotkey_counter.py` file and save it with a new filename (e.g. `hotkey_counter2.py`), and repeat the process. 47 | 48 | 49 | # Alternatives 50 | 51 | There is a [`Lua alternative`](https://gist.github.com/tid-kijyun/477c723ea42d22903ebe6b6cee3f77a1) with helpful blogpost (JP) 52 | on how to install and setup those kinds of scripts in OBS. Lua support in OBS Studio is built-in , so you don't need install anything extra. 53 | 54 | # Contribute 55 | 56 | Contributions are welcome. 57 | -------------------------------------------------------------------------------- /hotkey_counter.py: -------------------------------------------------------------------------------- 1 | import obspython as S 2 | 3 | __version__ = "1.2.0" 4 | 5 | 6 | class TextContent: 7 | def __init__(self, source_name=None, text_string="This is default text", suffix=""): 8 | self.source_name = source_name 9 | self.counter_text = text_string # prefix 10 | self.counter_suffix = suffix # suffix 11 | self.counter = 0 12 | 13 | def update_text(self, prefix, suffix, counter_value=0): 14 | source = S.obs_get_source_by_name(self.source_name) 15 | settings = S.obs_data_create() 16 | if counter_value == 1: 17 | self.counter += 1 18 | if counter_value == -1: 19 | self.counter -= 1 20 | if counter_value == 0: 21 | self.counter = 0 22 | if isinstance(counter_value, str): 23 | self.counter = int(counter_value) 24 | 25 | self.text_string = f"{prefix}{self.counter}{suffix}" 26 | S.obs_data_set_string(settings, "text", self.text_string) 27 | S.obs_source_update(source, settings) 28 | S.obs_data_release(settings) 29 | S.obs_source_release(source) 30 | 31 | 32 | class Driver(TextContent): 33 | def increment(self): 34 | self.update_text(self.counter_text, self.counter_suffix, 1) 35 | 36 | def decrement(self): 37 | self.update_text(self.counter_text, self.counter_suffix, -1) 38 | 39 | def reset(self): 40 | self.update_text(self.counter_text, self.counter_suffix, 0) 41 | 42 | def do_custom(self, val): 43 | self.update_text(self.counter_text, self.counter_suffix, str(val)) 44 | 45 | 46 | class Hotkey: 47 | def __init__(self, callback, obs_settings, _id): 48 | self.obs_data = obs_settings 49 | self.hotkey_id = S.OBS_INVALID_HOTKEY_ID 50 | self.hotkey_saved_key = None 51 | self.callback = callback 52 | self._id = _id 53 | 54 | self.load_hotkey() 55 | self.register_hotkey() 56 | self.save_hotkey() 57 | 58 | def register_hotkey(self): 59 | description = "Htk " + str(self._id) 60 | self.hotkey_id = S.obs_hotkey_register_frontend( 61 | "htk_id" + str(self._id), description, self.callback 62 | ) 63 | S.obs_hotkey_load(self.hotkey_id, self.hotkey_saved_key) 64 | 65 | def load_hotkey(self): 66 | self.hotkey_saved_key = S.obs_data_get_array( 67 | self.obs_data, "htk_id" + str(self._id) 68 | ) 69 | S.obs_data_array_release(self.hotkey_saved_key) 70 | 71 | def save_hotkey(self): 72 | self.hotkey_saved_key = S.obs_hotkey_save(self.hotkey_id) 73 | S.obs_data_set_array( 74 | self.obs_data, "htk_id" + str(self._id), self.hotkey_saved_key 75 | ) 76 | S.obs_data_array_release(self.hotkey_saved_key) 77 | 78 | 79 | class HotkeyDataHolder: 80 | htk_copy = None # this attribute will hold instance of Hotkey 81 | 82 | 83 | hotkeys_counter_1 = Driver() 84 | hotkeys_counter_2 = Driver() 85 | 86 | h01 = HotkeyDataHolder() 87 | h02 = HotkeyDataHolder() 88 | h03 = HotkeyDataHolder() 89 | h11 = HotkeyDataHolder() 90 | h12 = HotkeyDataHolder() 91 | h13 = HotkeyDataHolder() 92 | # ------------------------------------------------------------------------------ 93 | 94 | 95 | def callback_up1(pressed): 96 | if pressed: 97 | return hotkeys_counter_1.increment() 98 | 99 | 100 | def callback_down1(pressed): 101 | if pressed: 102 | return hotkeys_counter_1.decrement() 103 | 104 | 105 | def callback_custom1(*args): 106 | hotkeys_counter_1.do_custom(S.obs_data_get_int(args[2], "counter_1")) 107 | return True 108 | 109 | 110 | def callback_reset1(pressed): 111 | if pressed: 112 | return hotkeys_counter_1.reset() 113 | 114 | 115 | def callback_up2(pressed): 116 | if pressed: 117 | return hotkeys_counter_2.increment() 118 | 119 | 120 | def callback_down2(pressed): 121 | if pressed: 122 | return hotkeys_counter_2.decrement() 123 | 124 | 125 | def callback_reset2(pressed): 126 | if pressed: 127 | return hotkeys_counter_2.reset() 128 | 129 | 130 | def callback_custom2(*args): 131 | hotkeys_counter_2.do_custom(S.obs_data_get_int(args[2], "counter_2")) 132 | return True 133 | 134 | 135 | def script_description(): 136 | return "COUNTER 2" 137 | 138 | 139 | def script_update(settings): 140 | hotkeys_counter_1.source_name = S.obs_data_get_string(settings, "source1") 141 | hotkeys_counter_1.counter_text = S.obs_data_get_string(settings, "counter_text1") 142 | hotkeys_counter_1.counter_suffix = S.obs_data_get_string(settings, "counter_suffix1") 143 | 144 | hotkeys_counter_2.source_name = S.obs_data_get_string(settings, "source2") 145 | hotkeys_counter_2.counter_text = S.obs_data_get_string(settings, "counter_text2") 146 | hotkeys_counter_2.counter_suffix = S.obs_data_get_string(settings, "counter_suffix2") 147 | 148 | 149 | def script_properties(): 150 | props = S.obs_properties_create() 151 | 152 | # Counter 1 properties 153 | S.obs_properties_add_text( 154 | props, "counter_text1", "[1] Set counter prefix", S.OBS_TEXT_DEFAULT 155 | ) 156 | S.obs_properties_add_text( 157 | props, "counter_suffix1", "[1] Set counter suffix", S.OBS_TEXT_DEFAULT 158 | ) 159 | p = S.obs_properties_add_int( 160 | props, "counter_1", "Set custom value", -999999, 999999, 1 161 | ) 162 | S.obs_property_set_modified_callback(p, callback_custom1) 163 | p1 = S.obs_properties_add_list( 164 | props, 165 | "source1", 166 | "[1]Text Source", 167 | S.OBS_COMBO_TYPE_EDITABLE, 168 | S.OBS_COMBO_FORMAT_STRING, 169 | ) 170 | 171 | # Counter 2 properties 172 | S.obs_properties_add_text( 173 | props, "counter_text2", "[2] Set counter prefix", S.OBS_TEXT_DEFAULT 174 | ) 175 | S.obs_properties_add_text( 176 | props, "counter_suffix2", "[2] Set counter suffix", S.OBS_TEXT_DEFAULT 177 | ) 178 | p = S.obs_properties_add_int( 179 | props, "counter_2", "Set custom value", -999999, 999999, 1 180 | ) 181 | S.obs_property_set_modified_callback(p, callback_custom2) 182 | p2 = S.obs_properties_add_list( 183 | props, 184 | "source2", 185 | "[2]Text Source", 186 | S.OBS_COMBO_TYPE_EDITABLE, 187 | S.OBS_COMBO_FORMAT_STRING, 188 | ) 189 | sources = S.obs_enum_sources() 190 | if sources is not None: 191 | for source in sources: 192 | source_id = S.obs_source_get_unversioned_id(source) 193 | if source_id == "text_gdiplus" or source_id == "text_ft2_source": 194 | name = S.obs_source_get_name(source) 195 | S.obs_property_list_add_string(p1, name, name) 196 | S.obs_property_list_add_string(p2, name, name) 197 | 198 | S.source_list_release(sources) 199 | return props 200 | 201 | 202 | def script_load(settings): 203 | hotkeys_counter_1.counter = S.obs_data_get_int(settings, "counter1") 204 | hotkeys_counter_2.counter = S.obs_data_get_int(settings, "counter2") 205 | h01.htk_copy = Hotkey(callback_up1, settings, "count_up1") 206 | h02.htk_copy = Hotkey(callback_down1, settings, "count_down1") 207 | h03.htk_copy = Hotkey(callback_reset1, settings, "reset1") 208 | 209 | h11.htk_copy = Hotkey(callback_up2, settings, "count_up2") 210 | h12.htk_copy = Hotkey(callback_down2, settings, "count_down2") 211 | h13.htk_copy = Hotkey(callback_reset2, settings, "reset2") 212 | 213 | 214 | def script_save(settings): 215 | S.obs_data_set_int(settings, "counter1", hotkeys_counter_1.counter) 216 | S.obs_data_set_int(settings, "counter2", hotkeys_counter_2.counter) 217 | for h in [h01, h02, h03, h11, h12, h13]: 218 | h.htk_copy.save_hotkey() 219 | 220 | 221 | description = """ 222 |