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 吧