Skip to content

Commit 47583f1

Browse files
MaxeMaxe
authored andcommitted
Updated to 3.7
1 parent 58906ea commit 47583f1

10 files changed

Lines changed: 277 additions & 17 deletions

File tree

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ addon/doc/*.css
22
*_docHandler.py
33
*.html
44
manifest.ini
5-
# 暫時不忽略此檔案,下個版本刪除。
6-
!addon/manifest.ini
5+
addon/manifest.ini
76
*.mo
87
*.pot
98
*.py[co]

addon/doc/zh_TW/changelog.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
# 更新日誌
22

3+
## V3.7, 2026.3.6
4+
5+
版本代號:The Story of Us
6+
7+
來源:
8+
https://youtu.be/D1elWWJEUO4
9+
10+
### 更新項目
11+
12+
1. 新增 Twitch 聊天室支援。
13+
目前僅支援保留直播檔的聊天回放,測試流程如下:
14+
1. 打開測試直播檔:
15+
[最愛的週五23:00芙Z蓮同步視聽](https://www.twitch.tv/videos/2690557740)
16+
2. 使用瀏覽模式,直接到最下方往上一行,檢查聊天室是否展開。
17+
3. 將焦點移至播放器,可使用 o 來找到它。
18+
P.S 圖奇並非所有表情符號都標示為圖片,有些表情符號與訂閱里程碑居然與聊天訊息一樣標示為靜態文字,並且沒有別的方法能區分出來,所以無計可施。
19+
目前設計僅閱讀訊息,不閱讀訊息發送者與他們附帶又多又長的徽章,下個版本再考慮是否加入閱讀這些資訊的開關。
20+
另外圖奇的直播聊天室,很神奇的與回放聊天室介面大不相同,且使用了 aria-live 導致除了文字訊息,連發送者、徽章、表情符號、發燒列車等等全都朗讀,導致不重要的資訊量爆炸。是否能夠不手動抓訊息,改成攔截並改變 aria-live 行為還要再研究。
21+
2. Youtube 聊天室的橫幅出現時,將提示改為「聊天橫幅」。
22+
3. 將 Crunchyroll 支援狀態改為「失效」,感謝 PlatinumTsuki 詳細的技術測試報告,建議 Crunchyroll 使用者集合起來像官方報告此問題。 Issue #55
23+
4. 完成 NVDA 附加元件樣板更新,在版本代號 心訊 Heart Signal 及其之前的版本已經無法再檢查更新,若需要新版字幕閱讀器,可從商店進行更新。
24+
5. 修正字幕閱讀器介面的英文翻譯,感謝 Kostenkov-2021
25+
6. 更新烏克蘭語翻譯,感謝 balaraz
26+
7. 更新西語翻譯,感謝 nicolas 與 Nickbryan24
27+
28+
---
29+
330
## V3.6, 2025.12.4
431

532
版本代號:Snow halation

addon/globalPlugins/subtitle_reader/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from . import gui
2222
from .config import conf
2323
from .youtube import Youtube
24+
from .twitch import Twitch
2425
from .marumaruX import MarumaruX
2526
from .disney_plus import DisneyPlus
2627
from .netflix import Netflix
@@ -62,6 +63,7 @@ def __init__(self, *args, **kwargs):
6263
super(GlobalPlugin, self).__init__(*args, **kwargs)
6364
self.subtitleAlgs = {
6465
'.+ - YouTube': Youtube(self),
66+
'.+ - Twitch': Twitch(self),
6567
'.+ \| 唱歌學.+ \| marumaru': MarumaruX(self),
6668
'.+ \| Disney\+': DisneyPlus(self),
6769
'.*?Netflix': Netflix(self),

addon/globalPlugins/subtitle_reader/crunchyroll.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Crunchyroll(SubtitleAlg):
1010
info = {
1111
'name': 'Crunchyroll',
1212
'url': 'https://www.crunchyroll.com/',
13-
'status': SupportStatus.supported,
13+
'status': SupportStatus.invalid,
1414
}
1515
def getVideoPlayer(self):
1616
obj = self.main.focusObject
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#coding=utf-8
2+
3+
from __future__ import unicode_literals
4+
5+
import os
6+
import re
7+
8+
import ui
9+
from logHandler import log
10+
11+
from .sound import play
12+
from .config import conf
13+
from .subtitle_alg import SubtitleAlg, SupportStatus
14+
from .object_finder import find, search
15+
from .compatible import role
16+
17+
class Twitch(SubtitleAlg):
18+
info = {
19+
'name': 'Twitch',
20+
'url': 'https://www.twitch.tv/',
21+
'status': SupportStatus.supported,
22+
}
23+
def __init__(self, *args, **kwargs):
24+
super(Twitch, self).__init__(*args, **kwargs)
25+
self.chatRoom = None
26+
self.chatContainer = None
27+
self.chatObject = None
28+
self.chatSender = ''
29+
self.chatSenderIsOwner = False
30+
self.chatSenderIsAdmin = ''
31+
self.chatSenderIsVerified = ''
32+
self.chat = ''
33+
self.chatContainerSearchObject = None
34+
self.chatBannerSearchObject = None
35+
self.chatSearchObject = None
36+
37+
def getVideoPlayer(self):
38+
obj = self.main.focusObject
39+
return find(obj, 'parent', 'tag', 'main')
40+
41+
def getSubtitleContainer(self):
42+
self.getChatContainer()
43+
return True
44+
45+
def getChatContainer(self):
46+
obj = self.main.videoPlayer.next
47+
48+
self.chatRoom = None
49+
self.chatContainer = None
50+
self.chatObject = None
51+
if self.chatContainerSearchObject:
52+
self.chatContainerSearchObject.cancel()
53+
54+
self.chatContainerSearchObject = search(obj, lambda obj: obj.role == role('list'), self.onFoundChatContainer)
55+
56+
if self.chatBannerSearchObject:
57+
self.chatBannerSearchObject.cancel()
58+
59+
if self.chatSearchObject:
60+
self.chatSearchObject.cancel()
61+
62+
63+
def onFoundChatContainer(self, obj):
64+
self.chatContainer = obj
65+
#self.readChatBanner()
66+
67+
def getSubtitle(self):
68+
return ''
69+
70+
def onReadingSubtitle(self):
71+
self.readChat()
72+
73+
74+
def readChat(self):
75+
if not self.chatContainer:
76+
return
77+
78+
if self.chatSearchObject and not self.chatSearchObject.isStopped:
79+
return
80+
81+
self.chatObject = self.chatContainer.lastChild if not self.chatObject or not self.chatObject.role else self.chatObject
82+
nextChat = self.chatObject.next
83+
if nextChat is None:
84+
return
85+
86+
self.chatObject = nextChat
87+
self.chatSender = ''
88+
self.chatSenderIsOwner = False
89+
self.chatSenderIsAdmin = ''
90+
self.chatSenderIsVerified = ''
91+
92+
chat = self.chatObject.firstChild.next.firstChild
93+
chat = find(chat, 'next', 'name', ':')
94+
text = ''
95+
while chat:
96+
chat = chat.next
97+
text += getattr(chat, 'name', '')
98+
99+
text = text.strip()
100+
if not text or text == self.chat:
101+
return
102+
103+
self.chat = text
104+
ui.message(text)
105+
106+
def chatCondition(self, obj):
107+
if 'ytd-sponsorships-live-chat-gift-redemption-announcement-renderer' in obj.IA2Attributes.get('class'):
108+
return
109+
110+
matchIds = [
111+
'message',
112+
'primary-text',
113+
'author-name',
114+
'chat-badges',
115+
]
116+
117+
return obj.IA2Attributes.get('id') in matchIds
118+
119+
def onFoundChatObject(self, chat):
120+
id = chat.IA2Attributes.get('id')
121+
if id == 'author-name':
122+
name = chat.firstChild.name
123+
self.chatSender = name if name else ''
124+
self.chatSenderIsOwner = 'owner' in chat.IA2Attributes.get('class', '')
125+
verified = chat.firstChild.next
126+
if verified:
127+
self.chatSenderIsVerified = verified.firstChild.name
128+
129+
return
130+
131+
if id == 'chat-badges':
132+
if chat.childCount >= 2 or chat.firstChild.firstChild.firstChild.role != role('GRAPHIC'):
133+
name = chat.firstChild.name
134+
self.chatSenderIsAdmin = name if name else ''
135+
136+
return
137+
138+
self.chatSearchObject.cancel()
139+
140+
if conf['onlyReadManagersChat'] and not self.chatSenderIsOwner and not self.chatSenderIsAdmin:
141+
return
142+
143+
chat = getattr(chat, 'firstChild', None)
144+
if not chat:
145+
return
146+
147+
text = ''
148+
loopingChat = chat
149+
while loopingChat:
150+
chat = loopingChat
151+
loopingChat = loopingChat.next
152+
if conf['omitChatGraphic'] and chat.role == role('graphic'):
153+
continue
154+
155+
text += chat.name if chat.name else ''
156+
157+
158+
text = text.strip()
159+
if not text:
160+
return
161+
162+
if text == self.chat:
163+
return
164+
165+
self.chat = text
166+
167+
sender = '{name}。\r\n{isVerified}。\r\n{isAdmin}。\r\n'.format(name=self.chatSender, isVerified=self.chatSenderIsVerified, isAdmin=self.chatSenderIsAdmin)
168+
if conf['readChatSender'] or self.chatSenderIsOwner or self.chatSenderIsVerified or self.chatSenderIsAdmin:
169+
text = sender + text
170+
171+
ui.message(text)
172+
173+
174+
def readChatBanner(self):
175+
if not self.chatContainer:
176+
return
177+
178+
obj = self.chatContainer.parent.previous
179+
banner = None
180+
while obj:
181+
banner = find(obj, 'firstChild', 'id', 'banner-container')
182+
if banner:
183+
break
184+
185+
obj = obj.previous
186+
187+
if not banner:
188+
return
189+
190+
self.chatBannerSearchObject = search(banner, lambda obj: bool(obj.name) and obj.role != role('LINK'), lambda banner: ui.message(banner.name if banner.role != role('BUTTON') else '聊天室橫幅'), continueOnFound=True)
191+
192+
def readVoting(self):
193+
if not self.chatRoom:
194+
return
195+
196+
votingObj = self.chatRoom.lastChild
197+
votingObj = find(votingObj, 'firstChild', 'class', 'style-scope yt-live-chat-poll-renderer')
198+
if bool(votingObj) != self.voting:
199+
self.voting = bool(votingObj)
200+
if self.voting:
201+
ui.message(_('意見調查:'))
202+
search(votingObj, lambda obj: True, self.onSearchVoting, continueOnFound=True)
203+
204+
205+
206+
def onSearchVoting(self, obj):
207+
msg = getattr(obj, 'name', '') or ''
208+
# 掠過有 id 或 name 為 • 的原件
209+
if msg == ' • ' or obj.IA2Attributes.get('id'):
210+
return
211+
212+
if msg:
213+
ui.message(msg)
214+
215+

addon/globalPlugins/subtitle_reader/youtube.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ def readChatBanner(self):
307307
if not banner:
308308
return
309309

310-
self.chatBannerSearchObject = search(banner, lambda obj: bool(obj.name) and obj.role != role('LINK'), lambda banner: ui.message(banner.name if banner.role != role('BUTTON') else '聊天室橫幅'), continueOnFound=True)
310+
self.chatBannerSearchObject = search(banner, lambda obj: bool(obj.name) and obj.role != role('LINK'), lambda banner: ui.message(banner.name if banner.role != role('BUTTON') else '聊天橫幅'), continueOnFound=True)
311311

312312
def readVoting(self):
313313
if not self.chatRoom:

addon/manifest.ini

Lines changed: 0 additions & 11 deletions
This file was deleted.

buildVars.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
# 2. 新的影音平台支援為小數點後一位,並且將小數點第二位歸零。
3030
# 3. 版本號會自然進位,故支援 10 個影音平台主版本號 +1.
3131
# 4. 附加元件商店的版本號,小數點後仍是整數比較,故小數點後為 0 也不可省略。
32-
addon_version="3.60",
32+
addon_version="3.70",
3333
# Brief changelog for this version
3434
# Translators: what's new content for the add-on version to be shown in the add-on store
3535
addon_changelog=_(""""""),

changelog.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,29 @@
1-
Use this file to explain what has changed in your add-on since the previous release. This will be included automatically in the release description when used with GitHub actions.
1+
# 更新日誌
2+
3+
## V3.7, 2026.3.6
4+
5+
版本代號:The Story of Us
6+
7+
來源:
8+
https://youtu.be/D1elWWJEUO4
9+
10+
### 更新項目
11+
12+
1. 新增 Twitch 聊天室支援。
13+
目前僅支援保留直播檔的聊天回放,測試流程如下:
14+
1. 打開測試直播檔:
15+
[最愛的週五23:00芙Z蓮同步視聽](https://www.twitch.tv/videos/2690557740)
16+
2. 使用瀏覽模式,直接到最下方往上一行,檢查聊天室是否展開。
17+
3. 將焦點移至播放器,可使用 o 來找到它。
18+
P.S 圖奇並非所有表情符號都標示為圖片,有些表情符號與訂閱里程碑居然與聊天訊息一樣標示為靜態文字,並且沒有別的方法能區分出來,所以無計可施。
19+
目前設計僅閱讀訊息,不閱讀訊息發送者與他們附帶又多又長的徽章,下個版本再考慮是否加入閱讀這些資訊的開關。
20+
另外圖奇的直播聊天室,很神奇的與回放聊天室介面大不相同,且使用了 aria-live 導致除了文字訊息,連發送者、徽章、表情符號、發燒列車等等全都朗讀,導致不重要的資訊量爆炸。是否能夠不手動抓訊息,改成攔截並改變 aria-live 行為還要再研究。
21+
2. Youtube 聊天室的橫幅出現時,將提示改為「聊天橫幅」。
22+
3. 將 Crunchyroll 支援狀態改為「失效」,感謝 PlatinumTsuki 詳細的技術測試報告,建議 Crunchyroll 使用者集合起來像官方報告此問題。 Issue #55
23+
4. 完成 NVDA 附加元件樣板更新,在版本代號 心訊 Heart Signal 及其之前的版本已經無法再檢查更新,若需要新版字幕閱讀器,可從商店進行更新。
24+
5. 修正字幕閱讀器介面的英文翻譯,感謝 Kostenkov-2021
25+
6. 更新烏克蘭語翻譯,感謝 balaraz
26+
7. 更新西語翻譯,感謝 nicolas 與 Nickbryan24
27+
28+
---
29+

msgfmt.exe

-85.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)