2012年8月21日 星期二

[python]GUI 開發,使用 tkinter 與 wxpython 的感想

本來首選是 tkinter,他是 python 官方附帶的模組。但我有兩個問題沒有辦法克服。

第一個是文件不好找。python docs 有一篇大約講一下 tk 文件跟 tkinter 對應的規則。這也不能怪人,相同的問題也出現在 jython 上。只是 java 風格的文件比較容易看懂,jython 又比較多人用,文件也比較多。

第二個是,在mac上,有方塊字的問題。連 IDLE 的 python shell 都一樣,應該是無解。也許這就是為什麼 google app engine 在 windows 有提供 GUI 程式,但 mac 沒有的原因吧…。

後來轉而使用 wxpython。文件比較好找[註1]。容易看懂的原因是裡面就是講 python 話。在 mac 使用上也沒有方塊字的問題。只是要多裝 wxpython,安裝檔就 50多 MB。

wxpython 有一個 html 的模組[註2],可惜不能 javascript 互動。如果可以的話,擺一個 html window,剩下就是 html 與 javascript 的事,可以少學很多事情哩。

 

註1:http://wxpython.org/docs/api/wx-module.html

註2:http://docs.wxwidgets.org/2.8/wx_wxhtml.html, http://www.indicthreads.com/1106/incorporating-html-into-wxpython-part-1/, http://wiki.wxpython.org/wxHTML

2012年5月30日 星期三

[python] json 與中文

現場狀況是這樣的,在 json 編碼中,文字部份一定要 utf-8,大家都很清楚了,如果想搞別的編碼,就自己 escape,這種作法算偷吃步。在 python 的 json 模組裡面,算是很貼心(還是很雞婆?),預設就是幫大家把文字都 escape 了。當然使用 ascii 的人都不會有感覺(我想大家也很無奈)。

在 json.dump 裡面,有預設一個 ensure_ascii=True,就是強迫文字 escape 的選項。如果沒去動他的話,以下是出現的結果:

"\u53f0\u6ce5"

如果你確定你的文字是 utf-8,可設定成 ensure_ascii=False,像是:

id_name[stockid].decode('cp950').encode('utf8')

那結果就會是:

"台泥"

這樣就很清楚了。

只是,我光看 escape 完的字串,感覺像是 unicode 編碼而不是 utf-8 編碼耶…。是 json 故意又轉成 unicode 再 escape 的嗎?

2012年5月27日 星期日

[html5]在伺服器端怎麼支援 video 這個標籤

今天遇到的是,如果網頁中有個 video 標籤,伺服器該怎麼辦?一般人也許不會想到這個問題,而我卻遇到了。原因是我自己有寫一個小小 http server 的程式,只是為了要接收幾個按鈕,查詢並執行 ffmpeg 程式幫忙轉檔。小事情就自己動手了。所以,這種事情當然就要自己來處理囉。

從來自 chrome 的 request 之 headers 裡,有看到一個 range: 0- 這個,經過查詢(我當然還是查給人看的,而不是給超人看的文章),這個是通知伺服器說,從檔案的位置 0 開始給,結束位置隨便。感覺很隨便?其實是為了彈性。如此,伺服器該怎麼回應呢?

在 response 裡,要回覆的有

  1. Accept-Ranges: bytes
  2. Content-Range: xxx-ooo/sss

第一個是告訴瀏覽器說,我接受,而且是用 byte 當計算。 (有人說的,官方文件我真的看不懂,太像是給編譯器看的。) 而第二個就是告訴瀏覽器,從位置 xxx 開始(位置是從 0 開始算),到第 ooo 結束。整個檔案的大小是 sss。

舉例來說,一個 256 bytes 的小檔案,一次全部傳送完,Content-Range,就是 0-255/256。如果像大影片檔,就最好是一點一點的傳送,而就沒辦法一次全傳送了。

另外一個重點是回應碼,不是 200,而是 206 (參考),這樣瀏覽器才會再送一次 request 來要求其他的部份。像我提供 mp4 檔案的話,瀏覽器很聰明的先要了前面的部份之後,接下來要求最後的部份,據說是要影片的描述。有支援這種 partial content 的伺服器,大檔案傳送時應該很好用。

除了支援 range 之外,只要把 Content-Type 換成 video/mp4,chrome 就可以看影片。可以拉著時間軸跳到任意時間觀看。

程式的部份長這樣(我是用 python 寫):

self.send_response( 206 )
self.send_header( "Accept-Ranges", 'bytes' )
self.send_header( "Content-Type", 'video/mp4' )
self.send_header( "Content-Range", 'bytes %s-%s/%s' % (offset, end, size) )
self.log_message( 'bytes %s-%s/%s' % (offset, end, size))
self.end_headers()
f = open(src,'rb')
f.seek(offset, os.SEEK_SET )
self.wfile.write( f.read(block) )
f.close()
self.wfile.close()

2012年5月25日 星期五

[html5][gae]Server-Side Event

在 html5 的規格中,有看到一個 Server-Side Event (縮寫 SSE),這是什麼東西呢?是不是我最愛的伺器器主動通知呢?我好奇看了一下 w3schools 的說明,著實令我心動啊。

A server-sent event is when a web page automatically gets updates from a server.

This was also possible before, but the web page would have to ask if any updates were available. With server-sent events, the updates come automatically.

Examples: Facebook/Twitter updates, stock price updates, news feeds, sport results, etc.

這麼多大公司都用了,那我就可以放心的用吧。我們先來看怎麼用,再來講講我的感想吧。

因為我的環境是 Google App Engine,那麼我們就來改寫 w3schools 的範例吧。原來的網頁的程式碼,長這樣:

<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>

<script>
if(typeof(EventSource)!=="undefined")
  {
  var source=new EventSource("demo_sse.php");
  source.onmessage=function(event)
    {
    document.getElementById("result").innerHTML+=event.data + "<br />";
    };
  }
else
  {
  document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
  }
</script>

</body>
</html>

我們只要把 var source=new EventSource("demo_sse.php") 換成 var source=new EventSource("demo_sse")。整個網頁的 javascript 的精神就是,有 onmessage 事件發生的時候,就把事件裡的 data 值取出來,加到 id 為 result 的元素的內容去。所以改完變成:

<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>

<script>
if(typeof(EventSource)!=="undefined")
  {
  var source=new EventSource("time");
  source.onmessage=function(event)
    {
    document.getElementById("result").innerHTML+=event.data + "<br />";
    };
  }
else
  {
  document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
  }
</script>

</body>
</html>

我把上面的內容存成 main.html,放在跟 main.py 同一個目錄下。然後,main.py 的內容修改一下,在 MainHandler 的 get 方法,就改成直讀 main.html 在寫出到 out。

class MainHandler(webapp.RequestHandler):
    def get(self):
        path = os.path.join(os.path.dirname(__file__), 'main.html')
        render_content = codecs.open(path, 'r', encoding='utf8').read()
        self.response.out.write(render_content)

因為網頁會送出 /time 的要求,所以要處理他,必須要接受他。所以在 main() 要修改多一個處理

def main():
    application = webapp.WSGIApplication([('/', MainHandler),
                                          ('/time', TimeHandler),
                                          ],
                                         debug=True)

接下來寫一個 TimeHandler 接收來自網頁的要求,

class TimeHandler(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/event-stream'
        self.response.headers['Cache-Control'] = 'no-cache'
        p = "data: " + str(datetime.datetime.now()) + "\n\n"
        self.response.out.write(p)
    def post(self):
        pass

好,這樣就行了。試跑看看是不是有新的時間一直新增呢?

image

接下來是感想時間。

這個 Server-Side Event,我原本以為需要很新的 http Server 程式,例如新的 apache 或新的 IIS 或等等的。其實不用。看我的 TimeHandler 就知道,只要你會改 response 的 Content-Type 及 Cache-Control 的內容,就可以使用這個功能。一點也不難。要用得很難也行,進階版的用法請看 html5rocks

再來,他真的是 Server 主動通知嗎?,讓我們來看看網路活動吧,打開 chrome 的檢查元素,點網路。

image

會發現到,每隔幾秒就會有網路活動,從瀏覽器送出來。有寫過的人就會想說,這我自己用 interval 或 timer 寫不就一樣?對,我也覺得一樣。既然一樣,那用哪個都好,html5 提供了這個功能,讓大家不用傷腦筋去管理 timer,也是有方便之處。也同時提醒一下不清楚的人,不要以為這就是伺服器主動通知的事件,這實際上還是 client 端 polling 的方法。

最後啊,我要提醒兩下,

(1)這個 text/event-stream 一律使用 utf-8 編碼,跟 json 一模一樣,所以中文的朋友一定要先轉好。

(2)看文件真的不是一件容易的事,尤其規格的文件這麼大一份,參考來參考去的。越是低階的規格就越大本,我從來不知道在哪裡可以找到正式文件說,遇到兩個換行,就代表傳送結束,所以,如果你真要結束,就一定要連續傳兩個換行。如果你不想要結束,傳送內容前,一定要檢查有沒有兩個換行。當然,有些 http server 也許會幫你加兩個換行在後面。如果出問題,請記得你的內容,有沒有兩個換行在你不想要的地方。

2012年5月5日 星期六

[ffmpeg]轉檔 mp4 (x264 + aac)

有鑑於目前,公認最流行的影片格式是 mp4 ( h.264 + aac )(需要證據),所以我也使用這個格式來儲存新的影片。

mp4 其實算是一種影音容器,在影片部份的編碼可以使用 MPEG 4 或 MPEG AVC (也就是所謂的 h.264,由VideoLan組織寫好的編碼器叫 x264,因此我的標題是寫 x264),在聲音部份的編碼使用 aac。

以 Canon G11 錄下來的影音為 MOV 容器,影像就是 h.264,然聲音部份是 PCM,所以檔案大很多。

在轉檔的部份,vlc 在目前的版本 2.0.1 還沒好很好的轉換。而 ffmpeg 使用了 x264 編碼器及它內建的實驗的 aac 編碼器,我試用過還不錯。所以我用的 ffmpeg 的參數如下:

ffmpeg.exe -y -i 來源檔 -filter:v yadif -acodec aac -ar 44100 -ab 128k -ac 2 -vcodec libx264 -preset slow -crf 20 -async 512 -strict -2 -threads 1 輸出檔

參數說明:

-y 是指覆蓋舊檔,如果有的話
-filter:v yadif 效果如同之前的 deinterlace,沒加的話,我的經驗是顏色會怪怪的
-acodec aac -ar 44100 -ab 128k -ac 2 是說聲音部份用 aac 編碼,採樣 44100 赫茲,聲音頻寬128k,2 聲道
-vcodec libx264 -preset slow -crf 20 是指影像部份用 x264 編碼,使用慢速組態(我想要好一點的畫質),-crf 20 是想要品質固定為優先。
-async 512 是說影音部份的同步,每 512 個聲音採樣就對齊一次
-strict –2 有這個才能使用 aac 編碼,因為目前還在實驗中才會需要這個參數
-threads 1 指定只用一個 thread,這樣在雙核電腦、作業系統為 win 時,只用 25% 的 cpu。因為我的電腦容易熱當,所以用這方式降速,這參數放在 –i 的前面反而無作用。如果沒熱當問題,拿這此參數或指定高一點的數字可以加速編碼時間。

在我喜歡用 python 來幫我輸入參數,因此使用以下的程式來幫忙:

def transcode(inputfile, inputfolder, outfolder):
    exepath = os.path.abspath(u"D:/bin/ffmpeg-20120426-git-a4b58fd-win64-shared/bin/ffmpeg.exe")
    inputfilepath = os.path.join(unicode(inputfolder),unicode(inputfile))
    outputfilepath = os.path.join(unicode(outputfolder),'%s.m4v' % unicode(inputfile))
    cmd = u"%s -y -i \"%s\" -filter:v yadif -acodec aac -ar 44100 -ab 128k -ac 2 -vcodec libx264 -preset slow -crf 20 -async 512 -strict -2 -threads 1 \"%s\"" % (exepath,inputfilepath,outputfilepath)
    os.system(cmd.encode('cp950'))

for i in os.listdir(mov_folder):
    transcode( i, mov_folder, mov_folder)

這樣就很方便了。

想要用圖形介面的人,也可以使用 Handbrake,http://handbrake.fr/
其實就是用 ffmpeg 來轉碼,但我不用是因為Handbrake 就是無法強迫降速轉碼,導致熱當。
我猜只是它的 –thread 參數放在前面導致的。因為我下指令時放在前面就跟沒放一樣。
因此,電腦不熱當,而且轉檔參數常換或喜歡用 gui 來操作的人,就用 Handbrake 吧

2012年3月12日 星期一

[xml]遇到escape也無用,才知道 CDATA 的好

我以前非常的直觀,覺得只要有 escape 特殊符號,就可以解決任何事,
(http://codebeta.blogspot.com/2010/09/xmlescape.html)
直到我遇到一個情況:
在 WebService (http + soap) 裡,內容是xml,裡面又一層 xml。
當微軟的元件(我只用過微軟的開發工具,但我想其他的工具也許也一樣),
把 soap 內容(第一層的 xml)轉回來的時候,也裡面那層的也一起轉換了。
所以內容的 DOM 結構跟預期的不同。

<a><b></b><c>&lt;c1&gt;abcd&lt;/c1&gt;</c><d></d></a>  <!--本來的樣子 c 的內容是個 c1 element 已跳脫—>

<a><b></b><c><c1>abcd</c1></c><d></d></a>  <!--實際的樣子 導致取 c 內容(innerText)不如預期的 &lt;c1&gt;abcd&lt;/c1&gt; 影響後續程式動作-->

這時,應該就是使用 CDATA 來標示內容為文字,避免錯誤。
像這樣:

<a><b></b><c><![CDATA[<c1>abcd</c1>]]></c><d></d></a>

2012年3月7日 星期三

[mongodb]mongod.exe run as service (像服務一樣執行 mongod.exe)

mongodb 最簡單的執行方法,就是直接執行 mongod.exe (在 windows)
如此一來就會在 27017 這個 tcp port 接聽 client 的要求。

若有需求是如同服務一樣,一開機就等著接聽,當然有種方式是放個捷徑在啟動。
但一般,我們還是希望能跟 windows 的服務一樣。
mongod.exe 自己就有辦法做到安裝成服務。

最少最少的指令是

mongod --logpath d:\mongo\logs\logfilename.log 
 --dbpath d:\mongo\data --install

--logpath, --dbpath 都很直白,就不解釋。
--install 就是安裝成服務的意思。與服務有關參數還有:
--remove 移除服務。
--reinstall 重新安裝成服務。


其他較進階的指令,就請看參考
http://www.mongodb.org/display/DOCS/Windows+Service


真的是很貼心啊。