2010年9月30日 星期四

安裝與修正 Hinedo 線上廣播軟體 @ Ubuntu 10.04

Hinedo Hinedo


Hinedo 是一套國人開發的免費廣播軟體,個人覺得十分夠用。然而在 Ubuntu 10.04 上安裝後,跑起來會有問題,其錯誤訊息:


$ /usr/bin/hinedo
Traceback (most recent call last):
  File "/usr/lib/hinedo/update", line 119, in <module>
    os.execl( dir_path + 'update_menu' )
  File "/usr/lib/python2.6/os.py", line 312, in execl
    execv(file, args)
ValueError: execv() arg 2 must not be empty


Hinedo


這應該只是 Python 版本的問題,僅需稍微修正即可:


$ sudo vim /usr/lib/hinedo/update
將最後一行 os.execl( dir_path + 'update_menu' ) 更新成


os.execl( dir_path + 'update_menu' , '' )


只是多加一個空白的參數而已,如此一來就能正常使用囉


另外,安裝上若想要編原始碼,那就安裝一下其他部分:


$ sudo apt-get install build-essential libgtk2.0-dev
$ wget http://www.openfoundry.org/of/download_path/hinedo/2007.11.18/hinedo-0.4.tar.bz2
$ tar -xvf hinedo-0.4.tar.bz2 && cd hinedo-0.4 && make && make install


2010年9月27日 星期一

[Python] MARC21 與 ISO 2709 筆記

這陣子接觸圖書館服務,其中關於書目清單底層匯出的格式採用 MARC 格式,也是 ISO 2709 格式,相關資料如下:



花一點時間,總算看懂了。請看 Library of Congress >> MARC >> Authority >> LeaderMARC的結構 來對照,因為有時我竟然看不太懂中文!


圖書館系統理論上都支援 MARC 的匯出,其中匯出的資料採用 ISO 2709 格式,而 ISO 2709 就是以前磁帶備份的格式。MARC 匯出的資料格式,如同 Wikipedia - ISO_2709 底部那個看不懂的範例,因為 MARC 本身就叫 MAchine-Readable Cataloging 而非 Human-Readable Cataloging,但也有接近人眼看得懂得 MARC XML 格式,但不在這篇的討論。


MARC的結構 看看老故事,得知資料都是 Sequence 並且每一筆前 24 bytes 就等同於 record begin delimiter。而 MARC 每一筆 Record 共分成 header + dictionary + data 三個部份。而 header 裡 12-16 bytes 就是紀錄接下來的 dictionary 的大小是多少,當然也可以用它計算出直接取得 data 位置。而 dictionary 主要都是 12 bytes 為單位,分別是 3 bytes, 4 bytes, 5 bytes,但 dictionary 紀錄的大小是 "12 的倍數 + 1",細節可在 MARC的結構 得知。


切 Records:


def pre_process():
        target = 'marc_data'
        f = open( target , 'rb' )
        rec_cnt = 0
        total_size = 0
        print "### 012345678901234567890123 ###"
        while True:
                header = f.read(24)
                total_size  = total_size + len( header )
                if not header:
                        break

                record_size = int( header[0:5] )
                record_data = f.read( record_size - 24 )

                total_size  = total_size + len( record_data )
                rec_cnt = rec_cnt + 1

                print "---",header,"---",record_size
                if False :
                        o = open( '/tmp/marc.'+str(rec_cnt) , 'wb' )
                        o.write( header )
                        o.write( record_data )
                        o.close()
                #print record_data

        print "Total:",total_size,", Record Cnt:",rec_cnt
        f.close


從 header 這 24 bytes 資料,其前五個 Bytes 記錄的就是該 Record 大小(包括header)


對指定的 Record 分析 Header & 回傳指定 field 的 values:


def getFieldValue( rawdata , field = None , dictField = None ):
        if dictField is None:

                header = rawdata[0:24]
                field_length = int( header[20:21] )
                field_offset = int( header[21:22] )
                data_begin_offset = int( header[12:17] )
                raw_field_info = rawdata[24:data_begin_offset - 1]      # skip field end delimiter

                dictField = {}
                for i in range( 0 , len(raw_field_info) , 12 ):
                        begin = i
                        end = i+3
                        sub_field_name = raw_field_info[ begin : end ]

                        begin = end
                        end = begin + field_length
                        sub_field_data_length = raw_field_info[ begin : end ]

                        begin = end
                        end = begin + field_offset
                        sub_field_data_offset = raw_field_info[ begin : end ]

                        if sub_field_name not in dictField:
                                dictField[ sub_field_name ] = []
                        dictField[ sub_field_name ].append( [ int(sub_field_data_length) , int(sub_field_data_offset) + data_begin_offset ] )

        out = []
        if field is not None and field in dictField:
                #print dictField[field]
                for data_length_and_offset in dictField[field]:
                        out.append( rawdata[ data_length_and_offset[1] : data_length_and_offset[0] + data_length_and_offset[1] ] )

        return ( out , dictField )


用法:


tmp = None
value , tmp = getFieldValue( rawdata , '003' , tmp )
value , tmp = getFieldValue( rawdata , '005' , tmp )

...


其中 rawdata 是完整的資料,包括 header + dinctionary + data 三部分;value 是一個 array ,因為有些指定的 field name 可能出現多次,所以就用 array 記錄; tmp 是用來暫存 dictionary 資料,可以省下重新處理來增加效率的


建個 class 使用:


class MARC( object ):
        def __init__ ( self , file_list=[] ):
                self.file_list = file_list if file_list is not None and len(file_list) > 0 else []
                self.fd = None
                self.RE_FIELD_DATA = re.compile( '\x1f.([^\x1e\x1f]+)' )

        def get_raw_entries( self , cnt = None ):
                out = []
                cnt = int(cnt) if cnt is not None else 0
                while True:
                        if self.fd is None:
                                if  self.file_list is None or len( self.file_list ) == 0 :
                                        return out
                                try:
                                        self.fd = open( self.file_list[0] , 'rb' )
                                        self.file_list = self.file_list[1:]
                                except Exception as inst:
                                        print inst
                                        return out
                        try:
                                header = self.fd.read( 24 )
    
                                if not header:  # EOF
                                        self.fd.close()
                                        self.fd = None
                                else:
                                        record_size = int( header[0:5] )
                                        record_data = self.fd.read( record_size - 24 )
                                        out.append( header + record_data )
                        except Exception as inst:
                                print inst
                                return out
    
                        if cnt != 0 and len(out) == cnt:
                                return out

        def get_field_value( self , rawdata , field , dictField = None ):
                if dictField is None:

                        header = rawdata[0:24]
                        field_length = int( header[20:21] )
                        field_offset = int( header[21:22] )
                        data_begin_offset = int( header[12:17] )
                        raw_field_info = rawdata[24:data_begin_offset - 1]      # skip field end delimiter

                        dictField = {}
                        for i in range( 0 , len(raw_field_info) , 12 ):
                                begin = i
                                end = i+3
                                sub_field_name = raw_field_info[ begin : end ]

                                begin = end
                                end = begin + field_length
                                sub_field_data_length = raw_field_info[ begin : end ]

                                begin = end
                                end = begin + field_offset
                                sub_field_data_offset = raw_field_info[ begin : end ]

                                if sub_field_name not in dictField:
                                        dictField[ sub_field_name ] = []
                                raw_value = [ int(sub_field_data_length) , int(sub_field_data_offset) + data_begin_offset ]
                                dictField[ sub_field_name ].append( raw_value )

                out = []
                if field is not None and field in dictField:
                        for data_length_and_offset in dictField[field]:
                                out.append( rawdata[ data_length_and_offset[1] : data_length_and_offset[0] + data_length_and_offset[1] ] )

                return ( out , dictField )


使用方式:


marc = MARC( [target_file] )

for rawdata in marc.get_raw_entries():
        tmp = None
        value , tmp = marc.get_field_value( rawdata , 'FIELD_ID' , tmp )
        if len(value) > 0:
                for raw in re.findall( marc.RE_FIELD_DATA , value[0] ):
                        print raw
                        break


最後一提,其實有 pymarc libary 可以用:http://pypi.python.org/pypi/pymarc/,而我要做的事也差不多搞定,所以就不用那個 lib 囉


2010年9月23日 星期四

[Python] OPDS Catalog 產生器

之前看了 opds-toolscalibre2opds ,前者是 Python 寫的,後者是 Java 版本。但是前者有點隱藏功能或是在等待 OPDS Catalog 1.0 ,並沒有完整實做,可以看到一些程式碼被註解起來,但是打開註解又找不到對應的程式碼;後者卻綁在 Calibre 的資料庫格式,所以我選擇先對 opds-tools 開刀看看。


經過一陣子的修改,的確可以把 opds-tools 改到可以動了,但是殘缺的程式碼不禁讓我覺得不自在,在加上丟 issue 沒回應、該程式還要安裝 Genshi - Python toolkit for generation of output for the web 這套 framework,所以,最後就跳下去作了 XD 主要是我想要有可以丟進 MapReduce 的彈性架構,因此很在意可攜性,加上功能殘缺,所以就寫吧!(之前送 issue 出去,還被大主管唸說要送 Patch 才對!但是,這個 Patch 就等於重寫一個出來,所以我就自行建立一個出來啦)


http://code.google.com/p/opds-builder/


以下是在 Stanza 這個 iPhone 上的電子書閱讀器瀏覽的成果:


目前的範例只會產生 4 個



點選 Alphabetical 可查看分頁功能,但測資只有三項,每一頁只有一項 XD





至於原先提到的 MapReduce 則是因為需求面還沒到,所以就把實做一半的東西刪掉了。另外,可以透過 http://opds-builder.googlecode.com/hg/demo_out/index.xml (或 http://tinyurl.com/opds1 、http://tinyurl.com/opds-builder)查看 Demo 的效果。


2010年9月19日 星期日

[動畫] 夏日大作戰


來源:夏日大作戰 - 維基百科,自由的百科全書


心情煩悶,無意間看了這齣長達快兩小時的動畫,看完也有幾番感觸。故事是跟虛擬網路世界有關,看完很感概,讓我第一個想到的是 Facebook API,為什麼呢?因為現在他的使用者人數龐大,外加一堆個人隱私,並且很多應用都漸漸與 Facebook API 綁在一起。那豈不是跟這電影動畫的一些情節很像?另外還有雲端服務,哪天雲端資安出了大問題,那雲端一定比本地端還慘啊


很難得看了動畫還能帶出這點與近況相似東西,動畫裡還看得到 iPhone!剩下多說了就是故事情節了,就到此吧。


2010年9月16日 星期四

[Python] 編碼效能測試

想要作 MapReduce 的工作,大概拿 Hadoop Streaming 試試,於是想要把資料弄成 line-based 模式,接著想到資料壓縮處理,然後就想測一下到底哪種比較合適



  • base64

  • json

  • bz2

  • gzip


雖然腦子裡大概有譜了,但還是測一下好了


#!/usr/bin/env python

from timeit import Timer
import json
import base64
import bz2
import zlib

s = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()_+|'
def do_base64():
        encoded = base64.b64encode( s )
        decoded = base64.b64decode( encoded )

def do_json():
        encoded = json.dumps( s )
        decoded = json.loads( encoded )

def do_bz2():
        encoded = bz2.compress( s )
        decoded = bz2.decompress( encoded )

def do_gzip():
        encoded = zlib.compress( s )
        decoded = zlib.decompress( encoded )

if __name__ == '__main__':
        t1 = Timer( "do_base64()" , "from __main__ import do_base64" )
        try:
                print "Encode & Decode By base64: " + str( t1.timeit() )
        except:
                t1.print_exc()
        t2 = Timer( "do_json()" , "from __main__ import do_json" )
        try:
                print "Encode & Decode By json: " + str( t2.timeit() )
        except:
                t2.print_exc()

        t3 = Timer( "do_bz2()" , "from __main__ import do_bz2" )
        try:
                print "Encode & Decode By bz2: " + str( t3.timeit() )
        except:
                t3.print_exc()

        t4 = Timer( "do_gzip()" , "from __main__ import do_gzip" )
        try:
                print "Encode & Decode By gzip: " + str( t4.timeit() )
        except:
                t4.print_exc()


在 AMD x4 955 + 4 GB DDR3 1200 搭配 Ubuntu 10.04 i386:


$ python t.py
Encode & Decode By base64: 2.40118098259
Encode & Decode By json: 12.9051868916
Encode & Decode By bz2: 105.709769011
Encode & Decode By gzip: 19.3650279045


看來 base64 還是挺不錯的選擇,過去對他的印象是資料編碼後大小會長 50% 左右。另外,timeit 預設是跑 1,000,000 次。(由於測資沒有重複性,大概對壓縮類的不公平 XD)


相關資料