# Streamlab Alert Box 研究筆記 (舊版) <font size=2>written by [門特魯 (zatd39)](https://www.plurk.com/zatd39)</font> >【緊急事態】 >Streamlabs Alert Box 大改, >這篇文所用到的斷句技巧已成歷史, >作者自行開發的Alert Box 2.0 正在盡可能還原以前的玩法, >完工日期未定。 >近日會先生出新版 Streamlabs Alert Box 的說明文件。 >![AmonWeird] ## 研究來由 2020/06/18 剛換日,一樣和平的彩學聊天室。 鴉片與史考在討論小奇點丟注音的斷點問題, 此時里斯丟出了兩段Code, 在旁圍觀的我試用後驚為天人, 於是乎,三天後 [**小奇點斷句模擬器**](https://m3ntru.github.io/tgm3-bit-simulator/) 就誕生了。 當初為什麼會想寫這個,我忘了, 東西都寫出來了,還是希望能把功能做到最完善。 但礙於作者我本人的定性不足,進度緩慢。 有幸最近向某位友人(´・ω・`)借用了該實況台的Alert Box進行測試驗證, 總算是研究出了個大概,在這裡做點分享。 --- ## Alert Box運作流程 <font size=5>**軍火製作說明請直接電梯向下前往斷句 & 發音說明 配合斷句模擬器製作使用效果更佳![tgm3amonBig]**</font> ``` 這一部分是針對Alert Box對於Bits通知程式運作的流程說明, 閱讀這部分可能需要一點程式概念,有興趣再看就好。 ``` 先介紹我們實況中看見的通知。 彩學與大多數實況主使用的通知為 ![streamlabs](https://cdn.streamlabs.com/static/imgs/logos/kevin-logo.svg) 提供的 **Alert Box**, streamlabs的Alert Box基本上可以包含所有實況相關的通知。 (Follow、Sub、Host、Raid、Bits、Paypal Donate) ``` PS: 以上所有通知基本上都是要排隊出現的。 PS2: 歐付寶斗內使用的是另一個Alert Box,這就是為什麼歐付寶可以阻止內鬼 ``` 而在Alert Box視窗中,[Alert.js](https://streamlabs.com/mixed/assets/widgets/js/alertbox.js) 這個檔案囊括了絕大部分通知演出的Code, 以下將開始依Bits通知演出的執行順序做說明。 ``` 各段標題為原始碼參考連結。 ``` ### 1. [onDisplay](https://github.com/m3ntru/tgm3-bit-simulator/blob/master/src/example/onDisplay.js) Alert Box接收到Bits通知會進到這個function, 首先做音量設定(line 6 - 11), 接著會將接收到的原始內容轉換成顯示用的格式與TTS用的字串(line 12 - 42), 如下方範例: - 原始 >cheer87 骯骯骯骯骯骯骯骯骯骯骯骯骯骯骯 tgm3AMON - 顯示用 >![bit1]87 骯骯骯骯骯骯骯骯骯骯骯骯骯骯骯 ![tgm3amon] - TTS (Text-to-Speech) >骯骯骯骯骯骯骯骯骯骯骯骯骯骯骯 tgm3AMON ```c 有時候文字內容很長且cheer放在後面時cheer會念出來, 這個算是 Alert Box 的Bug,這裡舉個例子。 今天有一個原始內容如下: "cheer87 骯ㄤ骯ㄤ骯ㄤ(中間略)骯ㄤ骯ㄤ骯ㄤ tgm3cheer87",總長度500。 運作到 line 18 這裡,由於總長度大於300, 所以作為顯示用的 u 會等於 "cheer87 骯ㄤ(中間略)骯ㄤ..." , 長度296之後的字元會全部省略變為 "..."。 運作到下面的正規表示式(RegEx)會把所有cheer抓出來,但是是用 u 去抓, 所以他只會找到 "cheer87" 而找不到 "tgm3cheer87" , 以致於後面要處理TTS用的字串 l 時,只會把 "cheer87" 移除,"tgm3cheer87" 就留下來了。 ``` 取得字串後,首先撥放Bits音效 (彩學的Bits通知沒有音效), 接著進入 ```javascript=61 this.playTTS(l, n, c) ``` 繼續發音流程。 ### 2. [playTTS](https://github.com/m3ntru/tgm3-bit-simulator/blob/master/src/example/playTTS.js) 首先看到重點: ```javascript=10 switch (e.ttsSecurity) { case 1: a.maxDuration = 16500; break; case 2: a.maxDuration = 12750; break; case 3: a.maxDuration = 9375 } ``` **maxDuration** 決定了到底會不會唸出來的長度(單位為 ms), 只要跟Google TTS拿到的音檔超出這個長度, 後續到```playSounds()```做播放時,就會直接不撥放。 **ttsSecurity** 為streamlabs後臺設定的洗頻防護(Spam Security)等級, 同時也影響了maxDuration的長度。 目前實測彩學應該是設在等級一,所以為 **16.5** 秒 --- 接著是將字串內的網址與前後空格移除後, 將結果丟入```getSpeakUrl()```來取得TTS語音檔連結。 取得連結後,將得到的連結放入佇列中, 呼叫```playSounds()```播放 ```javascript=20 t = t.replace(/((?:https?|ftp):\/\/[\n\S]+)|(<([^>]+)>)+/gi, "").trim(), Mo && Mo.hasVoiceId(o) ? Mo.getSpeakUrl(t, o).then((function(t) { a.url = t, r.sounds.push(a), r.playSounds() })) : Bo.getSpeakUrl(t, o).then((function(t) { var e, n = _i(t); try { for (n.s(); !(e = n.n()).done;) { var o = e.value, s = Object(i.cloneDeep)(a); s.url = o, r.sounds.push(s) } } catch (t) { n.e(t) } finally { n.f() } r.playSounds() })) ``` ### 3. [getSpeakUrl](https://github.com/m3ntru/tgm3-bit-simulator/blob/master/src/example/getSpeakUrl.js) 里斯當初丟出的Code之一。 首先將整個字串、作為分隔的標點符號、每個斷句的最大長度丟進斷句 function```splitTextV1()```。 ```javascript=17 var a, s = No(n.splitTextV1(t, [".", "!", "?", ":", ";", ",", " "], 90)); ``` 會得到斷句後的結果,輸出為陣列。 接著將得到的斷句各個接上Google TTS的Http GET Method , 回傳到```playTTS()```。 ```javascript=18 try { for (s.s(); !(a = s.n()).done;) { var c = a.value; i.push("http://translate.google.com/translate_tts?ie=UTF-8&total=1&idx=0&textlen=".concat(c.length, "&client=tw-ob&q=").concat(encodeURIComponent(c), "&tl=").concat(e)) } } ``` ``` PS: Google TTS沒辦法直接在瀏覽器直接用ajax拿到音檔, 會有CORS (Cross-Origin Resource Sharing 跨域資源存取) 的問題。 為了解決這個問題,斷句模擬器我架了個代理的API來拿檔案。 PS2: 玩弄Google TTS的時候,不要短時間太多次。 不然就會像我一樣,我看這個畫面已經3 4個月了,那個機器人驗證根本沒用= = ``` ![= =](https://raw.githubusercontent.com/m3ntru/tgm3-bit-simulator/master/src/example/google.png) ### 4. [splitTextV1](https://github.com/m3ntru/tgm3-bit-simulator/blob/master/src/example/splitTextV1.js) 里斯當初丟出的Code之一,斷句模擬器的核心。 功能就是將字串以輸入的條件斷句,簡單明瞭, 解釋這段Code的運作過於花時間,大家看看就好, 請直接移駕下方斷句規則。 --- ## 詳解斷句的規則 & 運用 ### 1. 斷句前的處理 上面的運作流程有提到, 在斷句之前,原始輸入的訊息會先被做處理。 首先在聊天室輸入Bits訊息後送出,會先做第一次處理才會送進Alert Box。 **把連續的半形空格取代為一個半形空格**。 以下是範例: - 處理前 ``` cheer87 骯 骯骯 骯骯骯 cheer87 cheer87 骯骯骯骯骯 ``` - 處理後 ``` cheer87 骯 骯骯 骯骯骯 cheer87 cheer87 骯骯骯骯骯 ``` 進入Alert Box後,會把所有Bits的觸發字串(cheer)移除。 這裡注意幾點, (1) **字串長度超過300字元時,建議不要將cheer放在後面。** 會有沒移除到而被唸出來的情形, 詳細原因在上面流程 ```1. onDisplay```有說明。 (2) **移除cheer後,只會把頭尾的空格去除**,所以cheer放在中間時,空格會留著且不會合併成一個,可能會間接影響到斷句。 **結論就是cheer放前面一定穩。** 以下是範例: - 處理前 ``` cheer87 骯 骯骯 骯骯骯 cheer87 cheer87 骯骯骯骯骯 ``` - 處理後 ``` 骯 骯骯 骯骯骯 骯骯骯骯骯 ``` 這裡處理後的字串,將會開始進行斷句。 ### 2. 斷句的規則 斷句function有兩個參數,**分隔字元(標點符號) E** 及 **斷句長度 N**。 下圖為流程圖: ![Flow](https://raw.githubusercontent.com/m3ntru/tgm3-bit-simulator/master/src/example/flow.png) streamlabs Alert Box 的分隔字元及斷句長度是寫死的, 分別為: ```javascript E = [".", "!", "?", ":", ";", ",", " "] //分隔字元 N = 90 //斷句長度 ``` ``` 半形全形是不同的字元。 ``` 以下直接拿例子來做說明: ><font size=2>cheer87 臣路言:彩學闖關未半,而中道崩殂;今天下三八,荷包疲敝,此誠危急存亡之秋也.然整骨之臣,不懈于轟;大門之士,忘身于摔者:蓋追老路之殊遇,欲報之于彩學也。誠宜開張聖聽,以光老路遺德,恢宏觀眾之氣;不宜妄自菲薄,都不洗澡,以塞握手之路也.呸頗、歐付,俱為一體,陟尬臧嘴,不宜異同。若有作死犯蠢,及為賀尬者,宜付莫得,論其刑賞,以昭彩學懶覺之理,不宜偏私,使內外異法也。</font> <font size=1>(出路表 by Append?)</font> 移除cheer後,總長度為182字元。 --- 首先取出前90個字元。 ><font size=2>臣路言:彩學闖關未半,而中道崩殂<b><font color=#FF0000>;</font></b>今天下三八,荷包疲敝,此誠危急存亡之秋也<b><font color=#FF0000>.</font></b>然整骨之臣,不懈于轟;大門之士,忘身于摔者:蓋追老路之殊遇,欲報之于彩學也。誠宜開張聖聽,以光老路遺德, </font>    我們可以看到裡面有兩個分隔字元 <b><font color=#FF0000>;</font></b> 與 <b><font color=#FF0000>.</font></b>, 根據流程圖,我們於後方的 <b><font color=#FF0000>.</font></b> 將字串分開。 ><font size=2>(1)臣路言:彩學闖關未半,而中道崩殂;今天下三八,荷包疲敝,此誠危急存亡之秋也. (2)然整骨之臣,不懈于轟;大門之士,忘身于摔者:蓋追老路之殊遇,欲報之于彩學也。誠宜開張聖聽,以光老路遺德,</font>     將 (1) 存入陣列,(2) 接回後續內容。 ><font size=2>[ >"臣路言:彩學闖關未半,而中道崩殂;今天下三八,荷包疲敝,此誠危急存亡之秋也." >]</font> ><font size=2>然整骨之臣,不懈于轟;大門之士,忘身于摔者:蓋追老路之殊遇,欲報之于彩學也。誠宜開張聖聽,以光老路遺德,恢宏觀眾之氣;不宜妄自菲薄,都不洗澡,以塞握手之路也.呸頗、歐付,俱為一體,陟尬臧嘴,不宜異同。若有作死犯蠢,及為賀尬者,宜付莫得,論其刑賞,以昭彩學懶覺之理,不宜偏私,使內外異法也。</font> --- 再取出前90個字元。 ><font size=2>然整骨之臣,不懈于轟;大門之士,忘身于摔者:蓋追老路之殊遇,欲報之于彩學也。誠宜開張聖聽,以光老路遺德,恢宏觀眾之氣;不宜妄自菲薄,都不洗澡,以塞握手之路也<b><font color=#FF0000>.</font></b>呸頗、歐付,俱為一體,</font>    裡面有分隔字元 <b><font color=#FF0000>.</font></b>, 根據流程圖,於 <b><font color=#FF0000>.</font></b> 將字串分開。 ><font size=2>(1)然整骨之臣,不懈于轟;大門之士,忘身于摔者:蓋追老路之殊遇,欲報之于彩學也。誠宜開張聖聽,以光老路遺德,恢宏觀眾之氣;不宜妄自菲薄,都不洗澡,以塞握手之路也. (2)呸頗、歐付,俱為一體,</font>     將 (1) 存入陣列,(2) 接回後續內容 ><font size=2>[ "臣路言:彩學闖關未半,而中道崩殂;今天下三八,荷包疲敝,此誠危急存亡之秋也." , >"然整骨之臣,不懈于轟;大門之士,忘身于摔者:蓋追老路之殊遇,欲報之于彩學也。誠宜開張聖聽,以光老路遺德,恢宏觀眾之氣;不宜妄自菲薄,都不洗澡,以塞握手之路也." >]</font> ><font size=2>呸頗、歐付,俱為一體,陟尬臧嘴,不宜異同。若有作死犯蠢,及為賀尬者,宜付莫得,論其刑賞,以昭彩學懶覺之理,不宜偏私,使內外異法也。</font> --- 剩餘的內容以不足90個字元, ><font size=2>呸頗、歐付,俱為一體,陟尬臧嘴,不宜異同。若有作死犯蠢,及為賀尬者,宜付莫得,論其刑賞,以昭彩學懶覺之理,不宜偏私,使內外異法也。</font>     將剩餘的內容放入陣列,斷句完成。 ><font size=2>[ >"臣路言:彩學闖關未半,而中道崩殂;今天下三八,荷包疲敝,此誠危急存亡之秋也." , >"然整骨之臣,不懈于轟;大門之士,忘身于摔者:蓋追老路之殊遇,欲報之于彩學也。誠宜開張聖聽,以光老路遺德,恢宏觀眾之氣;不宜妄自菲薄,都不洗澡,以塞握手之路也." , >"呸頗、歐付,俱為一體,陟尬臧嘴,不宜異同。若有作死犯蠢,及為賀尬者,宜付莫得,論其刑賞,以昭彩學懶覺之理,不宜偏私,使內外異法也。" >]</font> ![split1](https://github.com/m3ntru/tgm3-bit-simulator/blob/master/src/example/split_1.png?raw=true) --- ## 估狗小姐(Google TTS)到底怎麼念 斷句斷完了,接著下一個環節, **估狗小姐到底會怎麼念?** ### ~~1. 估狗小姐念不念?~~(目前播放機制有修改,驗證中) ~~在上面運作流程的```2. playTTS```有提到, 目前測出的結果,彩學Alert Box的洗頻防護(Spam Security)等級為1, 代表每一句斷句的長度只要超過 **16.5秒**,那一句估狗小姐就不會幫你念。 每個斷句之間會有大概 8/60 ~ 10/60 秒的間隔。~~ 以下拿兩個例子來做說明: **(1)** <iframe width="640" height="400" src="https://www.youtube.com/embed/htapKVwgdww" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> ![split2](https://github.com/m3ntru/tgm3-bit-simulator/blob/master/src/example/split_2.png?raw=true) <font size=1>(張大人追撞黑色高級車 by lory3324)</font> <font size=1>(影片左聲道為原clip,右聲道為比對用音檔)</font> 總計六句,每一句都沒有超出16.5秒,全部都會唸出來。 而每個斷句之間的空白時間,從影片中可以看到, 估狗小姐產出的檔案在最後面本身就會夾帶一些空白, 且每個斷句之間會有大概 8/60 ~ 10/60 秒的間隔。 --- **(2)** <iframe width="640" height="400" src="https://www.youtube.com/embed/2__4qet0r7E" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <font size=1>(影片左聲道為原clip,右聲道為比對用音檔)</font> ![split3](https://github.com/m3ntru/tgm3-bit-simulator/blob/master/src/example/split_3.png?raw=true) 總計六句,第2.4.5句都超出16.5秒,所以不會唸出來(不撥放)。 且每個斷句之間會有大概 8/60 ~ 10/60 秒的間隔。 所以播放流程會是這樣: ><font size=2>"骯包." (0.888s) -> > >間隔1(0.13~0.16s) -> 骯骯骯...(0s) -> 間隔2(0.13~0.16s) -> > >"骯包." (0.888s) -> > >間隔3(0.13~0.16s) -> 骯骯骯...(0s) -> 間隔4(0.13~0.16s) -> 骯骯骯...(0s) -> 間隔5(0.13~0.16s) -> > >"骯包" (0.864s) > ></font> 第一聲骯包與第二聲骯包之間會有大概2個間隔長度的無聲時間(0.26~0.32s), 第二聲骯包與第三聲骯包之間會有大概3個間隔長度的無聲時間(0.39~0.48s)。 --- ### 2. 估狗小姐怎麼念? 這個部分,相信大家有在聊天室塞過的各位應該很熟了。 彩學使用的TTS語言是 **Chinese (Simplified)**, 也就是你直接使用google翻譯, **左邊格子使用中文** 跟 **右邊格子使用中文(簡體)** 的聆聽聽到的結果是一樣的, 在這裡說明幾個容易出現的問題及技巧: #### **● 注音** 一開始大家發現可以使用注音讓估狗小姐唸出超長的句子不會消音, 原因是因為注音念一個字需要用到2~4個字元, 所以一個斷句念超出16.5秒的機會很低,又有可以直接控制聲調的優點,簡單暴力。 注音每個字由兩個部分組成,**[注音符號]**與**{聲調}**。 >聲調的一聲可以是一個空格或是-,二三四輕聲就一樣用ˊˇˋ˙,沒有聲調就當一聲。 這裡以新觀眾塞包常會發生的情況做說明: --- **(1) ㄤㄤㄤ** 這連續三個ㄤ中間沒有任何聲調,所以他會視為[ㄤㄤㄤ]{一聲}, 會變為一個字長度的無聲,骯不出來。 **(2) 骯ㄤ骯ㄤ骯ㄤ** ㄤ後面接了一個不是注音符號也不是聲調的字元,所以會視為骯[ㄤ]{一聲},骯得出來。 **(3)ㄤ ㄤ ㄤ 或 ㄤ-ㄤ-ㄤ-** ㄤ後面接了一個空白或-,所以會視為[ㄤ]{一聲}[ㄤ]{一聲}[ㄤ]{一聲},骯得出來。 --- 了解上面的規則後,可以用來當技巧運用。 像是常見的內鬼無聲字"ㄙㄟ", "ㄙㄟㄙㄟㄙㄟㄙㄟㄙㄟ" 全部連在一起,只會被視為一個字,所以產出的音檔就只有一個無聲字的長度。 "ㄙㄟ ㄙㄟ ㄙㄟ ㄙㄟ ㄙㄟ " 每個ㄙㄟ都有搭配到一個空格,所以產出的音檔就有五個無聲字的長度。 ``` PS: 由於空格也是斷句分隔字元, 所以使用空格的一聲字可能會對斷句造成影響, 建議一聲字改使用-以確保穩定。 ``` #### **● 延遲咒文(無聲字) & 連續字** ``` 目前google念不出來的中文:瓰,㔫,鿌,鿀,鿊,龾,龳,甅,嗧,龫,龻 (待補充) (ghostermoma提供) ``` 無聲字也是很常拿來控制延遲的手段之一, 但使用上要注意,**某些音的字連續超過三個以上,可能只會念三次。**, 骯骯包沒這個問題,但是像是呱呱包就會有這個問題,**無聲字也是其中之一**, 哪些音的字會造成這個情形,就要自己測試看看,目前無跡可尋。 簡單的解決方法有兩種: (1) 在每個字之間加上全形標點符號(全形標點符號也會有一點點停頓): ``` 㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫,㔫 ``` (2) 使用同音的字做交錯: ``` 呱刮呱刮呱刮呱刮呱刮呱刮呱刮呱刮呱刮呱刮呱刮呱刮呱刮呱刮 ``` #### **● Emoji** 說到Emoji,大家朗朗上口的應該都是這句: >👲🐤ㄐ🕡,不🛀有🧓臭 但是Emoji是個可能只占1-3字元,但唸出來要花3-4秒的東西, 很容易突破16.5秒的上限,請小心使用。 >💹,🖇, 🛻🔀,🔉,🔰,➿,🔡,🔯,⏳ <font size=1>(Append提供)</font> >這邊10個Emoji加上標點符號共27字元,就長達26.464秒。 ## 結語 看到這裡,希望你已經成功了解軍火的製作方法與注意事項了, 也希望我寫的 [**小奇點斷句模擬器**](https://m3ntru.github.io/tgm3-bit-simulator/) 能幫助你製作軍火時能少一點麻煩。 說到底,如何做出有效果的軍火還是各憑本事, 畢竟彩學與聊天室對軍火的反應才是做軍火的醍醐味。 最後,不免俗的還是要來提醒各位: <font size=6>**所有你在彩學台做出的 訂閱,斗內,Bits 等等消費行為, 這些都是算是娛樂消費, 請遵循量力而為原則, 先讓自己有充裕的生活,再來塞包, 沒辦法的話,其他有辦法的觀眾會幫你塞![tgm3amonBig]。**</font> ## 特別感謝 - 我們的最大苦主 吳彩學 [TetrisTheGRANDMAster3](https://www.twitch.tv/tetristhegrandmaster3/) - [ChRiS38c28](https://github.com/crs38c28) 提供本研究一切的起源 [Alert.js](https://streamlabs.com/mixed/assets/widgets/js/alertbox.js) - 啟發我進行研究的最大軍火商 [Append](https://www.twitch.tv/append) - 小奇點測試環境提供 [ICKID (´・ω・`)](https://www.youtube.com/c/ICKID) - 本文引用的資料原作者 [lory3324](https://www.twitch.tv/lory3324)、[ghostermoma](https://www.twitch.tv/ghostermoma) - 彩學台聊天室的所有塞包眾 [tgm3amon]: https://static-cdn.jtvnw.net/emoticons/v1/78084/1.0 "tgm3amon" [tgm3amonBig]: https://static-cdn.jtvnw.net/emoticons/v1/78084/2.0 "tgm3amonB" [bit1]: https://d3aqoihi2n8ty8.cloudfront.net/actions/cheer/dark/animated/1/1.gif "bits1" [AmonWeird]: https://cdn.discordapp.com/emojis/809889506089893888.png?v=1