此篇為新增字幕閱讀器新影音平台支援的開發指南,適合學習過 Python 且想與我一起讓字幕閱讀器更加進步的你。
對於修正平台失效的做法也可參考該指南。
首先,在你想要支援的影片上,再聽見人物對白時暫停影片,然後使用瀏覽模式在網頁上尋找字幕文字。
若順利找到了字幕文字,那恭喜你,該影音平台通常可支援。
若很遺憾找不到字幕,除非能研究出其他讓 NVDA 可抓取字幕的方法,否則通常該平台無法支援。
建議使用 Chromium 瀏覽器,因為完成之後可以直接支援很多瀏覽器喔。
找到 subtitleExtractors 資料夾,建議可以複製你熟悉的平台 extractor 來改,或者複製代碼比較少的平台 extractor 來改,比如 netflix. 將複製來的檔案重新命名為新平台的名稱,比如 myPlatform.py 開啟檔案,將類別名稱也改成新平台的名稱,記得首字母一定要大寫,如 class MyPlatform(SubtitleExtractor):
直接往下,可以看到 info 屬性,這是一個字典,其中包含平台名稱、平台網址,以及支援狀態等三個部分。 請將他們改為新平台的名稱與網址。至於支援狀態,畢竟測試成功才會上線,所以保持 supported 即可。
接下來是辨認這個平台的方式,有視窗標題與網址兩種,請二選一即可。 建議優先選擇視窗標題,因為網址不一定所有的瀏覽器都能找的到。 請觀察該平台在播放不同影片時,視窗標題保持相同的部分,通常是平台名稱,然後在 windowTitle 屬性上,使用正規表達式將它標示出來。 若您不了解正規表達式,可以參考其他 extractor 或者以下舉最常見的範例說明: 假如視窗標題為 My Movie - My Platform 你可以寫 .+ - My Platform .+ 就是多個任意字元的意思。 下一行是 url, 使用方法與視窗標題相同,也是觀察不同影片當中相同的部分,建議視窗標題找不到特徵在使用。
首先,回到播放影片的網頁,將焦點停在暫停/播放的按鈕上,或者停在其他地方也可以,但請停在播放器的範圍內。 然後你可以按 NVDA+CTRL+Z 打開 Python 主控台,用這個快速鍵開啟的主控台,會有兩個變數可以使用,一個是 focus 也就是你現在的焦點,一個是 nav 也就是導覽物件所在的項目。 這些都是 NVDAObject, 你可以對他們呼叫 parent, firstChild, previous, next 等等,且可多次呼叫,直到出現 None 為止。 接著你可以用 focus.parent 然後查看他的屬性,尤其可以查看 IA2Attributes 屬性,其中的 id 與 class 可能會包含需要的資訊,比如 player 等等,利用這些來找出播放器的物件。 focus 可以不斷的 .parent 直到找到為止。 假如網頁結構很簡單,完全只有播放器,你也可以將 NVDAObject 的 role 屬性為 role('document') 時,當作是影片播放器。 找到之後,我們回到代碼,找到 def getVideoPlayer 在這裡,要找到焦點所在的物件,我們可以用 self.main.focusObject 然後可以用 find 來像一個方向尋找播放器,比如假設播放器的 id 為 'movie_player' 可以這樣寫: obj = find(focusObject, 'parent', 'id', 'movie_player') 然後將找到的結果直接回傳即可。 find 若找不到,會回傳 None, 而在取得影片播放器的方法當中,回傳 None 表示沒有播放器,所以無需繼續尋找字幕。 比較特別的是 find 若找的是 class, 由於 class 有時會包含每次都不一樣的英數字與固定的內容混合,所以可以只找固定的部分即可, find 會使用 in 來判斷 class 是否符合。
首先可以先從 self.main.videoPlayer 取得影片播放器。 接著回到影片網頁,這裡建議將 NVDA 檢閱游標當中的簡易檢閱模式取消,這樣使用物件瀏覽就與操作 NVDAObject 的 parent, next 相同了。 接著從播放/暫停開始,使用物件瀏覽找到影片播放器,在從影片播放器找到字幕文字,找到之後請記住這個流程。 然後將找到字幕的步驟往回退一格,在這個物件上開啟主控台,此時我們就可以用 nav 這個變數了,將 nav 用另一個變數將它存起來。 然後回到網頁開始播放影片,直到下一個對白出現時暫停影片。 回到主控台,觀察剛剛存放的變數 role 屬性是否變為 UNKNOWN, 如果式的話,回到使用物件瀏覽找到字幕的狀態,再往回多退一格,直到播放影片,字幕更換之後 role 屬性不會變成 UNKNOWN 為止,就可以將這個物件回傳作為字幕容器了。
我們同樣先從 self.main.subtitleContainer 取得字幕容器。 還記得字幕容器與字幕之間的位置嗎?如果字幕容器只要一職 firstChild 就能找到字幕,那恭喜你,可以直接 return super().getSubtitle(obj) 如果不是,那就先找到字幕文字的 parent 物件,在將物件傳給基礎類別的 getSubtitle 並將回傳值直接回傳出去,這樣就大功告成啦。