Linux中國

為 Kodi 自製遙控器

XBMC

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(&apos;/hello/<name>&apos;)
def index(name):
return template(&apos;<h1>Hello {{name}}!</h1>&apos;, 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[&apos;result&apos;]) > 0:
playlist_data = xbmc.Player.GetProperties({"playerid":0, "properties":["playlistid"]})
if len(playlist_data[&apos;result&apos;]) > 0 and "playlistid" in playlist_data[&apos;result&apos;].keys():
return playlist_data[&apos;result&apos;][&apos;playlistid&apos;]
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, &apos;properties&apos;:["position"]})
position = int(position_data[&apos;result&apos;][&apos;position&apos;])
return data[&apos;result&apos;][&apos;items&apos;][position:], position
return [], -1

這樣可以返回正在播放的項開始的列表(因為我們並不關心已經播放過的內容),而且也包含了用來從列表裡移除項的位置信息。

Image

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(&apos;/juke&apos;)
def index():
current_playlist, position = get_playlist()
return template(&apos;list&apos;, playlist=current_playlist, offset = position)

只需要抓取播放列表(調用我們之前定義的函數),然後將結果傳遞給負責顯示的模版。

負責顯示列表數據的模版的主要部分是:

<h2>Currently Playing:</h2>
% if playlist is not None:
% position = offset
% for song in playlist:
<strong> {{song[&apos;title&apos;]}} </strong>
% if song[&apos;type&apos;] == &apos;unknown&apos;:
Radio
% else:
{{song[&apos;artist&apos;][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(&apos;/skip/<position>&apos;)
def index(position):
print xbmc.Player.GoTo({&apos;playerid&apos;:0, &apos;to&apos;:&apos;next&apos;})
redirect("/juke")
@route(&apos;/remove/<position>&apos;)
def index(position):
playlistid = get_playlistid()
if playlistid >= 0:
xbmc.Playlist.Remove({&apos;playlistid&apos;:int(playlistid), &apos;position&apos;:int(position)})
redirect("/juke")

當然,如果不能往列表裡添加歌曲的話那這個列表管理功能也不行。

因為一旦播放列表結束,它就消失了,所以你需要重新創建一個,這會讓事情複雜一些。而且有點讓人迷惑的是,播放列表是通過調用 Playlist.Clear() 方法來創建的。這個方法也還用來刪除包含網路電台(類型是 unknown)的播放列表。另一個麻煩的地方是列表裡的網路電台開始播放後就不會停,所以如果當前在播網路電台,也會需要清除播放列表。

這些頁面包含了指向 /play/ 的鏈接來播放歌曲。通過下面的代碼處理:

@route(&apos;/play/<id>&apos;)
def index(id):
playlistid = get_playlistid()
playlist, not_needed= get_playlist()
if playlistid < 0 or playlist[0][&apos;type&apos;] == &apos;unknown&apos;:
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 頁面基本類似。

Image

還需要處理一下 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(&apos;/radio/&apos;)
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 &apos;result&apos; in my_stations.keys():
return template(&apos;radio&apos;, stations=my_stations[&apos;result&apos;][&apos;files&apos;])
else:
return template(&apos;error&apos;, error=&apos;radio&apos;)

這樣可以返回一個可以和歌曲一樣能添加到播放列表的文件。不過,這些文件能一直播下去,所以(之前說過)在添加其他歌曲的時候需要重新創建列表。

共享歌曲

除了伺服頁面模版,Bottle 還支持靜態文件,方便用於那些不會因為用戶輸入而改變的內容。可以是 CSS 文件,一張圖片或是一首 MP3 歌曲。在我們的簡單遙控程序里(目前)還沒有任何用來美化的 CSS 或圖片,不過我們增加了一個下載歌曲的途徑。這個可以讓媒體中心變成一個存放歌曲的 NAS 盒子。在需要傳輸大量數據的時候,最好還是用類似 Samba 的功能,但只是下幾首歌到手機上的話使用靜態文件也是很好的方式。

通過歌曲 ID 來下載的 Bottle 代碼:

@route(&apos;/download/<id>&apos;)
def index(id):
data = xbmc.AudioLibrary.GetSongDetails({"songid":int(id), "properties":["file"]})
full_filename = data[&apos;result&apos;][&apos;songdetails&apos;][&apos;file&apos;]
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

本文由 LCTT 原創翻譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國