為 Kodi 自製遙控器
Kodi 原名叫做 XBMC,在你閱讀這篇文章的時候,XBMC 已經成為歷史。因為法律原因(因為名字 XBMC 或 X-Box Media Center 里引用了不再支持的過時硬體)項目組決定使用新的名字 Kodi。不過,除了名字,其他的都會保持原樣。或者說除開通常新版本中所期待的大量新改進。這一般不會影響到遙控軟體,它應該能在已有的 XBMC 系統和新的 Kodi 系統上都能工作。
我們目前已經配置好了一個用於播放音樂的 Kodi 系統,不過我們找到的所有 Kodi 遙控沒一個好用的,特別是和媒體中心連接的電視沒打開的時候。它們都有點太複雜了,集成了太多功能在手機的小屏幕上。我們希望能有這樣的系統,從最開始就是設計成只用於訪問音樂庫和電台插件,所以我們決定自己實現一個。它不需要用到 Kodi 的所有功能,因為除了音樂以外的任務,我們可以簡單地切換使用通用的 Kodi 遙控。我們的測試系統是一個刷了 RaspBMC 發行版的樹莓派,但是我們要做的工具並不受限於樹莓派或Kodi那個發行版,它應該可以匹配任何安裝了相關插件的基於 Linux 的 Kodi 系統。
首先,遙控程序需要一個用戶界面。大多數 Kodi 遙控程序都是獨立的 app。不過對於我們要做的這個音樂控制程序,我們希望用戶可以不用安裝任何東西就可以使用。顯然我們需要使用網頁界面。Kodi 本身自帶網頁伺服器,但是為了獲得更多許可權,我們還是使用了獨立的網頁框架。在同一台電腦上跑兩個以上網頁伺服器沒有問題,只不過它們不能使用相同的埠。
有幾個網頁框架可以使用。而我們選用 Bottle 是因為它是一個簡單高效的框架,而且我們也確實用不到任何高級功能。Bottle 是一個 Python 模塊,所以這也將是我們編寫伺服器模塊的語言。
你應該能在軟體包管理器里找到 Bottle。在基於 Debian 的系統(包括 RaspBMC)里,你可以通過下面的命令安裝:
sudo apt-get install python-bottle
遙控程序實際上只是連接用戶和系統的中間層。Bottle 提供了和用戶交互的方式,而我們將通過 JSON API 來和 Kodi 交互。這樣可以讓我們通過發送 JSON 格式消息的方式去控制媒體播放器。
我們將用到一個叫做 xbmcjson 的簡單 XBMC JASON API 封裝。足夠用來發送控制請求,而不需要關心實際的 JSON 格式以及和伺服器通訊的無聊事。它沒有包含在 PIP 包管理中,所以你得直接從 GitHub 安裝:
git clone https://github.com/jcsaaddupuy/python-xbmc.git
cd python-xbmc
sudo python setup.py install
萬事俱備,只欠代碼。
先從 Bottle 開始
我們程序的基本結構:
from xbmcjson import XBMC
from bottle import route, run, template, redirect, static_file, request
import os
xbmc = XBMC("http://192.168.0.5/jsonrpc", "xbmc", "xbmc")
@route('/hello/<name>')
def index(name):
return template('<h1>Hello {{name}}!</h1>', name=name)
run(host="0.0.0.0", port=8000)
這樣程序將連接到 Kodi(不過實際上用不到);然後 Bottle 會開始提供網站服務。在我們的代碼里,它將監聽主機 0.0.0.0(意味著允許所有主機連接)的埠 8000。它只設定了一個站點,就是 /hello/XXXX,這裡的 XXXX 可以是任何內容。不管 XXXX 是什麼都將作為參數名傳遞給 index()。然後再替換進去 HTML 網頁模版。
你可以先試著把上面內容寫到一個文件(我們取的名字是 remote.py),然後用下面的命令啟動:
python remote.py
然後你可以在瀏覽器里訪問 localhost:8000/hello/world 看看模版生效的效果。
@route() 用來設定網頁伺服器的路徑,而函數 index() 會返回該路徑的數據。通常是返回由模版生成的 HTML 頁面,但是並不是說只能這樣(後面會看到)。
隨後,我們將給應用添加更多頁面入口,讓它變成一個全功能的 Kodi 遙控,但仍將採用相同代碼結構。
XBMC JSON API 介面可以從和 Kodi 機器同網段的任意電腦上訪問。也就是說你可以在自己的筆記本上開發,然後再布置到媒體中心上,而不需要浪費時間上傳每次改動。
模版 - 比如前面例子里的那個簡單模版 - 是一種結合 Python 和 HTML 來控制輸出的方式。理論上,這倆能做很多很多事,但是會非常混亂。我們將只是用它們來生成正確格式的數據。不過,在開始動手之前,我們先得準備點數據。
Paste
Bottle 自帶網頁伺服器,我們用它來測試遙控程序。不過,我們發現它性能有時不夠好。當我們的遙控程序正式上線時,我們希望頁面能更快一點顯示出來。Bottle 可以和很多不同的網頁伺服器配合工作,而我們發現 Paste 用起來非常不錯。而要使用的話,只要簡單地安裝(Debian 系統里的 python-paste 包),然後修改一下代碼里的 run 調用:
run(host=hostname, port=hostport, server="paste")
你可以在 http://bottlepy.org/docs/dev/deployment.html 找到如何使用其他伺服器的相關細節。
從 Kodi 獲取數據
XBMC JSON API 分成 14 個命名空間:JSONRPC, Player, Playlist, Files, AudioLibrary, VideoLibrary, Input, Application, System, Favourites, Profiles, Settings, Textures 和 XBMC。每個都可以通過 Python 的 XBMC 對象訪問(Favourites 除外,明顯是個疏忽)。每個命名空間都包含許多方法用於對程序的控制。例如,Playlist.GetItems() 可以用來獲取某個特定播放列表的內容。伺服器會返回給我們 JSON 格式的數據,但 xbmcjson 模塊會為我們轉化成 Python 詞典。
我們需要用到 Kodi 里的兩個組件來控制播放:播放器和播放列表。播放器處理播放列表並在每首歌結束時從列表裡取下一首。為了查看當前正在播放的內容,我們需要獲取正在工作的播放器的 ID,然後根據它找到當前播放列表的 ID。這個可以通過下面的代碼來實現:
def get_playlistid():
player = xbmc.Player.GetActivePlayers()
if len(player['result']) > 0:
playlist_data = xbmc.Player.GetProperties({"playerid":0, "properties":["playlistid"]})
if len(playlist_data['result']) > 0 and "playlistid" in playlist_data['result'].keys():
return playlist_data['result']['playlistid']
return -1
如果當前沒有播放器在工作(就是說,返回數據的結果部分的長度是 0),或者當前播放器沒有處理播放列表,這樣的話函數會返回 -1。其他時候,它會返回當前播放列表的數字 ID。
當我們拿到當前播放列表的 ID 後,就可以獲取該列表的細節內容。按照我們的需求,有兩個重要的地方:播放列表裡包含的項,以及當前播放所處的位置(已經播放過的項並不會從播放列表移除,只是移動當前播放位置)。
def get_playlist():
playlistid = get_playlistid()
if playlistid >= 0:
data = xbmc.Playlist.GetItems({"playlistid":playlistid, "properties": ["title", "album", "artist", "file"]})
position_data = xbmc.Player.GetProperties({"playerid":0, 'properties':["position"]})
position = int(position_data['result']['position'])
return data['result']['items'][position:], position
return [], -1
這樣可以返回正在播放的項開始的列表(因為我們並不關心已經播放過的內容),而且也包含了用來從列表裡移除項的位置信息。
API 文檔在這裡:http://wiki.xbmc.org/?title=JSON-RPC_API/v6。它列出了所有支持的函數,但是關於具體如何使用的描述有點太簡單了。
JSON
JSON 是 JavaScript Object Notation 的縮寫,最初設計用於 JavaScript 對象的序列化。目前仍然起到這個作用,但是它也是用來編碼任意數據的一種很好用的方式。
JSON 對象都是這樣的格式:
{property1:value1, property2:value2, property3:value3}
支持任意數目的屬性/值配對。對 Python 程序員來說,看上去和字典數據結構很相似,不過這兩個確實很像。
在字典數據結構里,值本身可以是另一個 JSON 對象,或者一個列表,所以下面的格式也是正確的:
{"name":"Ben", "jobs":["cook", "bottle-washer"], "appearance": {"height":195, "skin":"fair"}}
JSON 通常在網路服務中用來發送和接收數據,並且大多數編程語言都能很好地支持,所以如果你熟悉 Python 的話,你應該可以使用你熟悉的編程語言調用相同的介面來輕鬆地控制 Kodi。
整合到一起
把之前的功能連接到 HTML 頁面很簡單:
@route('/juke')
def index():
current_playlist, position = get_playlist()
return template('list', playlist=current_playlist, offset = position)
只需要抓取播放列表(調用我們之前定義的函數),然後將結果傳遞給負責顯示的模版。
負責顯示列表數據的模版的主要部分是:
<h2>Currently Playing:</h2>
% if playlist is not None:
% position = offset
% for song in playlist:
<strong> {{song['title']}} </strong>
% if song['type'] == 'unknown':
Radio
% else:
{{song['artist'][0]}}
% end
% if position != offset:
<a href="/remove/{{position}}">remove</a>
% else:
<a href="/skip/{{position}}">skip</a>
% end
<br>
% position += 1
% end
可以看到,模版大部分是用 HTML 寫的,只有一小部分用來控制輸出的其他代碼。用兩個大括弧括起來的變數是輸出位置(像我們在第一個 'hello world' 例子里看到的)。你也可以嵌入以百分號開頭的 Python 代碼。因為沒有縮進,你需要用一個 % end
來結束當前的代碼塊(就像循環或 if 語句)。
這個模版首先檢查列表是否為空,然後遍歷裡面的每一項。每一項會用粗體顯示歌曲名字,然後是藝術家名字,然後是一個是否跳過(如果是當前正在播的歌曲)或從列表移除的鏈接。所有歌曲的類型都是 'song',如果類型是 'unknown',那就不是歌曲而是網路電台。
/remove/ 和 /skip/ 路徑只是簡單地封裝了 XBMC 控制功能,在改動生效後重新載入 /juke:
@route('/skip/<position>')
def index(position):
print xbmc.Player.GoTo({'playerid':0, 'to':'next'})
redirect("/juke")
@route('/remove/<position>')
def index(position):
playlistid = get_playlistid()
if playlistid >= 0:
xbmc.Playlist.Remove({'playlistid':int(playlistid), 'position':int(position)})
redirect("/juke")
當然,如果不能往列表裡添加歌曲的話那這個列表管理功能也不行。
因為一旦播放列表結束,它就消失了,所以你需要重新創建一個,這會讓事情複雜一些。而且有點讓人迷惑的是,播放列表是通過調用 Playlist.Clear() 方法來創建的。這個方法也還用來刪除包含網路電台(類型是 unknown)的播放列表。另一個麻煩的地方是列表裡的網路電台開始播放後就不會停,所以如果當前在播網路電台,也會需要清除播放列表。
這些頁面包含了指向 /play/
@route('/play/<id>')
def index(id):
playlistid = get_playlistid()
playlist, not_needed= get_playlist()
if playlistid < 0 or playlist[0]['type'] == 'unknown':
xbmc.Playlist.Clear({"playlistid":0})
xbmc.Playlist.Add({"playlistid":0, "item":{"songid":int(id)}})
xbmc.Player.open({"item":{"playlistid":0}})
playlistid = 0
else:
xbmc.Playlist.Add({"playlistid":playlistid, "item":{"songid":int(id)}})
remove_duplicates(playlistid)
redirect("/juke")
最後一件事情是實現 remove_duplicates 調用。這並不是必須的 - 而且還有人並不喜歡這個 - 不過可以保證同一首歌不會多次出現在播放列表裡。
我們也實現了一些頁面用來列出收藏歌曲里所有藝術家,以及列出指定藝術家的歌曲和專輯。這些都非常簡單,和 /juke 頁面基本類似。
還需要處理一下 UI,不過功能已經有了。
日誌
通常拿到 XBMC JSON API 並不清楚能用來做什麼,而且它的文檔也有點模糊。找出如何使用的一種方式是看別的遙控程序是怎麼做的。如果打開日誌功能,就可以在使用其他遙控程序的時候看到哪個 API 被調用了,然後就可以應用到在自己的代碼里。
要打開日誌功能,把 Kodi 媒體中心 接到顯示器上,再依次進入設置 > 系統 > 調試,打開允許調試日誌。在打開日誌功能後,還需要登錄到 Kodi 機器上(比如通過 SSH),然後就可以查看日誌了。日誌文件的位置應該顯示在 Kodi 界面左上角。在 RaspBMC 系統里,文件位置是 /home/pi/.xbmc/temp/xbmc.log。你可以通過下面的命令實時監視哪個 API 介面被調用了:
cd /home/pi/.xbmc/temp tail -f xbmc.log | grep "JSON"
增加功能
上面的代碼都是用來播放 Kodi 媒體庫里的歌曲的,但我們還希望能播放網路電台。每個插件都有自己的獨立 URL 可以通過普通的 XBMC JSON 命令來獲取信息。舉個例子,要從電台插件里獲取選中的電台,可以使用;
@route('/radio/')
def index():
my_stations = xbmc.Files.GetDirectory({"directory":"plugin://plugin.audio.radio_de/stations/my/", "properties":
["title","thumbnail","playcount","artist","album","episode","season","showtitle"]})
if 'result' in my_stations.keys():
return template('radio', stations=my_stations['result']['files'])
else:
return template('error', error='radio')
這樣可以返回一個可以和歌曲一樣能添加到播放列表的文件。不過,這些文件能一直播下去,所以(之前說過)在添加其他歌曲的時候需要重新創建列表。
共享歌曲
除了伺服頁面模版,Bottle 還支持靜態文件,方便用於那些不會因為用戶輸入而改變的內容。可以是 CSS 文件,一張圖片或是一首 MP3 歌曲。在我們的簡單遙控程序里(目前)還沒有任何用來美化的 CSS 或圖片,不過我們增加了一個下載歌曲的途徑。這個可以讓媒體中心變成一個存放歌曲的 NAS 盒子。在需要傳輸大量數據的時候,最好還是用類似 Samba 的功能,但只是下幾首歌到手機上的話使用靜態文件也是很好的方式。
通過歌曲 ID 來下載的 Bottle 代碼:
@route('/download/<id>')
def index(id):
data = xbmc.AudioLibrary.GetSongDetails({"songid":int(id), "properties":["file"]})
full_filename = data['result']['songdetails']['file']
path, filename = os.path.split(full_filename)
return static_file(filename, root=path, download=True)
應用的時候,只需要為 /songsby/ 頁面里的相應 ID 加個鏈接。
我們已經把所有的代碼過了一遍,不過還需要一點工作來把它們集合到一起。可以自己去 GitHub 頁面 https://github.com/ben-ev/xbmc-remote 看下。
設置
我們的遙控程序已經開發完成,還需要保證讓它在媒體中心每次開機的時候都能啟動。有幾種方式,最簡單的是在 /etc/rc.local 里增加一行命令來啟動。我們的文件位置在 /opt/xbmc-remote/remote.py,其他文件也和它一起。然後在 /etc/rc.local 最後的 exit 0 之前增加了下面一行。cd /opt/xbmc-remote && python remote.py &
GitHub
這個項目目前還只是個架子,但是 - 我們運營雜誌就意味著沒有太多自由時間來編程。不過,我們啟動了一個 GitHub 項目,希望能持續完善, 而如果你覺得這個項目有用的話,歡迎做出貢獻。要查看最新的進展,請訪問 https://github.com/ben-ev/xbmc-remote 看看所處的狀態。你可以從頁面里獲取最新的代碼,或者通過命令行複製。
如果你希望改善它,可以複製項目到你自己的分支開發,然後在功能完成後發起合併請求。關於如何使用 GitHub 的更多信息,請訪問 https://github.com/features。
via: http://www.linuxvoice.com/xbmc-build-a-remote-control/
作者:Ben Everard 譯者:zpl1025 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive