開發者指南

此篇為新增字幕閱讀器新影音平台支援的開發指南,適合學習過 Python 且想與我一起讓字幕閱讀器更加進步的你。
對於修正平台失效的做法也可參考該指南。

第一步 檢查 NVDA 是否可找到字幕

首先,在你想要支援的影片上,再聽見人物對白時暫停影片,然後使用瀏覽模式在網頁上尋找字幕文字。 若順利找到了字幕文字,那恭喜你,該影音平台通常可支援。
若很遺憾找不到字幕,除非能研究出其他讓 NVDA 可抓取字幕的方法,否則通常該平台無法支援。 建議使用 Chromium 瀏覽器,因為完成之後可以直接支援很多瀏覽器喔。

第二步 建立 SubtitleExtractor 的子類別

找到 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 並將回傳值直接回傳出去,這樣就大功告成啦。