2010年7月24日 星期六

影片字幕的處理

射手網 收集了許影片的字幕,用來搭配影片檔案,造福了廣大人民。

有些熱門的片子,字幕就很齊全,簡繁英俱全,
有的影片卻只找得到簡體字版,
雖然簡體字看起來實在不習慣,不過多懂一些還是有益處,
所以會嘗試去接受這類的字幕,如果真的受不了(簡體字有點醜)
可以使用下列指令轉換成順眼悅目的正體字:

iconv -f gb2312 -t big5 gb.srt | iconv -f big5 -t utf-8 > utf8.srt

(* 人生不是所有事情都是完美的 *)

有時候,中文字幕是標準的正體沒錯,但顯示的時間就是不對,
答非所問,荒腔走板,空有字幕檔也沒有用,仰天長嘆,莫可奈何。
沒辦法還是要正向思考,提昇一下層次,學習看英文字幕好了(還是那句話:"多懂一些還是有益處"的 )
問題是,有的影片雅俗共賞,講得也都是"人"話,不會天馬行空,那就勉強可以看下去。
但,天不從人願,有的影片的編劇擺明就是火星來的,生字一堆,講得又快,不是看不懂,就是看得不夠快。

今天碰到的狀況是這個樣子,英文字幕OK,有中文字幕,但時間不對。
想辦法將兩個字幕合併,保留英文字幕的時間,再用中文字幕的文字來取代英文。

步驟有三:
(1) 讀取中文字幕,去除時間,保留中文字 ,寫入YAML檔。
(2) 目視比對中英文字幕,調整差異處。
(3) 讀取英文字幕,去除英文,代入中文。

* 中文字幕
24
00:02:47,444 --> 00:02:50,572
我們在讀《雙城記》,還不錯吧?

25
00:02:50,814 --> 00:02:53,009
那不是狄更斯的代表作
學校裡比較流行

26
00:02:53,216 --> 00:02:57,880
我認為《大衛·科波維爾》
和《遠大前程》內容更好

* 轉成YAML格式檔
- - 21
  - 我們在讀《雙城記》,還不錯吧?
- - 22
  - 那不是狄更斯的代表作
  - 學校裡比較流行
- - 23
  - 我認為《大衛·科波維爾》
  - 和《遠大前程》內容更好

* 轉換YAML程式
use strict;

use Encode qw/decode/;
use YAML qw/DumpFile/;

open SRT, '<', 'big.srt';

my @srt;
my @srtline;
my $index;

while (<SRT>) {

    chomp;
    s/\r//;

    my $line = decode('utf-8', $_);

    if ($line =~ /^\d+\b/) {
        next
    }
    elsif ($line =~ /^\s*$/) {
        $index++;
        push @srt, [$index, @srtline];
        @srtline = ();
    }
    else {
        push @srtline, $line;
    } 
}

DumpFile('big.yml', \@srt);


* 合併程式
  
use strict;

use utf8;

use Encode qw/decode/;
use YAML qw/LoadFile/;

open SRT, '<', 'eng.srt';
binmode STDOUT, ":utf8";

my @srt = @{LoadFile('big.yml')};
while (<SRT>) {

    my $line = decode('utf-8', $_);

    if ($line =~ /^\d+\b/) {
        print;
    }
    elsif ($line =~ /^\s*$/) {
        my @line = @{ shift @srt };
        shift @line;
        print "$_\n" for @line;
        print "\n";
    }
    else {
        * just ignore them
    } 
}

一點小小的加工,得到合理的結果,太完美了 :-) 。

ps. 影片名稱:《The Squid and the Whale》

2010年7月12日 星期一

Perl學習之旅的回顧(之四)

從大學時代初次學習Pascal程式語言開始,以及後來陸續學習的Fortran、C、Basic、FORTH以及Perl都是屬於指令式程式設計 (Imperative programming),也就是一步一步告訴電腦如何執行我們所設計的程序。

到今天我還記得Pascal教科書上讓我印象深刻的那句話:「Algorithms + Data Structures = Programs」。非常古典的看法,它宣告著,有了演算法加上適當的資料結構,等於擁有了通往電腦魔法世界的鑰匙;在當時,這似乎是永恆的真理,畢竟,美國太空總署透過Fortran,神奇地把太空人送上了月球,不是嗎?

但一般程式語言所提供的資料結構是平凡、單調而且僵固的。Pascal的資料結構尤其讓我倒胃口,不但要事先宣告型態與大小,而且又有許多的語法限制,在產生神奇的魔法效果之前,得先經過編譯器(Compiler)黑森林的重重刁難之後,才能勉強執行,更困難的是,想要把現實世界的東西,塞進電腦世界的資料結構裡面,沒有添加一點想像力,耐心的迂迴前進是不容易辦到的。

Perl 的資料結構,基本上說來是蠻單純的,但神奇的是,透過Reference的使用,可以建構成相當複雜有用的資料結構,除了之前所提到的聖經內容,也可以是一個網頁內容的樹狀結構,或者,把一間公司所有網路設備的資料放入資料結構裡。使用過這樣強大又具彈性的資料處理能力,再參考以下範例,終於可以體會這句話的意思:Algorithms + Data Structures = Programs。
use strict;
use LWP::Simple;
use HTML::TreeBuilder;

my $html=get("http://www.apress.com");
my $tree=HTML::TreeBuilder->new;

$tree->parse($html);
$tree->eof;

my @pnodes=$tree->find_by_tag_name('meta');

foreach my $node (@pnodes) {
    print $node->as_HTML;
    print "\n";
}

$tree->delete;

變化是無止盡的,接下來「物件導向程式設計」概念,衝擊著習慣第三代程式語言的程式設計師們,然而Perl並沒有拋棄我們,程式設計的原理原則是不會變的,它只是不斷地融合再生,就像上面的範例,就是不折不扣的物件導向程式呢。

為什麼需要「物件導向程式設計」,我認為最重要的原因之一是,藉由抽象所帶來的單純化,讓程式碼容易撰寫(模組化)、容易維護及reuse。我們不需要知道,如何去剖析組成網頁的HTML內容,也不用知道剖析後的資料結構,只要知道如何透過物件的介面去使用就可以了。

Moose 是Perl的一個模組,它讓我們可以撰寫 Perl物件導向程式。我試著把之前所建立DBM::Deep裡的聖經資料,加上物件的介面,我們便可以提供一個間單的讀經介面。

# Bible.pm
package Bible;

use Moose;

use DBM::Deep;
use List::Util qw(max min);

# 透過變數$bible連結DBM::Deep檔
my $bible = DBM::Deep->new( "bible_ary.dmd" )->{bible};

#properties
has readbook   => (is => 'ro', isa => 'DBM::Deep', writer => 'set_readbook');
has opened     => (is => 'ro', isa => 'Str',       writer => 'set_opened');
has pointer    => (is => 'ro', isa => 'Int',       writer => 'set_pointer');

has nBooks     => (is => 'ro', isa => 'Int', default => $#{$bible});
has booklines  => (is => 'ro', isa => 'Int', default => 0, writer => 'set_booklines');

#methods
sub open {
    my $self = shift;
    my $book = shift || '創世紀';
 
    if ($self->findbook($book)) {
        print $self->opened, " opened\n";
    }
    else {
        print "$book not found!!\n";
    }
}

sub findbook {
# 由篇名尋找
    my $self   = shift;
    my $tofind = shift;

    for my $index ( 0 .. $self->nBooks )  {
        if (   $bible->[$index]{bookname}{chn} eq $tofind 
            or $bible->[$index]{bookname}{eng} eq $tofind ) {

            $self->set_readbook ( $bible->[$index]{words} );
            $self->set_opened   ( $tofind );
            $self->set_booklines( $#{$self->readbook} );
            $self->set_pointer  ( 0 );

            return 1;
        }
    }

    # mark no book opened 
    $self->set_booklines( 0 );
 
    return;
}

sub read {
    my $self  = shift;
    my $nline = shift || 10; # 一目十行 

    my $from;
    my $to;

    return unless $self->booklines;
 
    $nline = 10 if $nline < 0;
 
    for (0 .. $nline - 1) {
        $self->readline;
        $self->incre_pointer;
        last if $self->pointer == $self->booklines;
    }

}

sub incre_pointer {
    my $self = shift;
    $self->set_pointer( min( $self->pointer+1, $self->booklines ) );
}

sub readline {
# 顯示現在pointer所指向的那一行
    my $self = shift;
 
    printf "%-5s %s\n",
            $self->readbook->[$self->pointer]{index},
            $self->readbook->[$self->pointer]{word},;
}

sub lookup {
# 翻閱至特定章節。 例.chapter(3,2) 第三章第二節
    my ($self, $chap, $chap_sub) = @_;
 
    for my $line (0 .. $self->booklines) {
        my ($r_chap, $r_chap_sub) = split /:/, $self->readbook->[$line]{index};
        if ($r_chap >= $chap && $r_chap_sub >= $chap_sub) {
            $self->set_pointer($line);
            $self->readline;
            last;
        }
    }
    return;
}

1;


有了Bible這個物件之後,我們的程式就不需要知道DBM::Deep或者YAML裡聖經章節的資料結構,只要透過物件去使用就可以了(就算以後我把聖經放到雲端資料庫裡面也是一樣!)。

use Bible;

my $bible = new Bible;

$bible->open('馬太福音');
$bible->read();

$bible->lookup(2,5);
$bible->read();

物件導向不是萬靈丹,它是程序導向方式的演化,良好的抽象介面,讓內部的細節隱藏,以便因應日趨複雜的資訊系統。

"Easy things should be easy and hard things should be possible".

這幾年來,我從學習Perl中得到許多知識與樂趣,也由於資訊領域快速發展的特性,讓我可以經常的藉由Perl去探索新的事物。學習是無止盡的,如果學習過程能夠充滿了樂趣,我們會高興知道這個事實,並且樂在其中。

後記. 之所以會寫這幾篇Perl學習之旅的網誌,是因為前兩個星期,無意間知道這個模組Number::RecordLocator 所引起的。

2010年7月5日 星期一

Perl學習之旅的回顧(之三)

YAML檔透過縮排來表示資料間的相互結構關係,但每次要使用的時候,還是要透過LoadFile函式來剖析並載入記憶體才能夠使用。
所以會有使用前處理的overhead以及記憶體的限制,雖然對於幾萬筆(或10幾萬筆)的資料,這個問題還不算嚴重,但畢竟還是個限制。
另外還有一個問題,就是當我們要透過程式來更改YAML檔的內容時,就會有鎖定(Lock)的問題,因為實際上,這個YAML檔也可能被其他人(程式)所讀取。

以上所描述並不是YAML的問題,應該說是它的特性(與XML類似),所以YAML檔大部分用於設定參數檔(configuration file),由人去目視編輯內容(類似WIndows的INI設定檔,又比XML具可讀性),再由程式讀取使用。

但是有時候,我們就是需要處理幾十萬到幾百萬筆以上的資料,又要能讀能寫,記憶體又有限制的時候,到底該怎麼辦? 除了使用資料庫之外,Perl提供了一種方法:DBM::Deep

簡單的說,DBM::Deep是一個Perl位於CPAN上的模組,經由它所建立的純量變數,可以神奇將Perl的資料結構對應到磁碟上的一個檔案,可讀可寫,還有lock的功能,同時也提供Transaction的機制,最重要的是,不管資料量的大小,都不會有記憶體限制的問題(當然,我們不會用這個方法來處理幾GB以上的資料)。

舉個例來說,我們由YAML檔將整本聖經讀入之後,可以存成DBM::Deep的格式,之後,我們就可以將讀經的心得,寫入到DBM::Deep檔案內,讓心得跟聖經經文之間產生連結,如以下程式碼:

use strict;
use DBM::Deep;
use YAML qw /LoadFile DumpFile/;

my $db = DBM::Deep->new( "foo.db" );

# 將所有舊約內容讀入$bible純量變數內
my $bible = LoadFile('bible_old.yaml');

my $db = DBM::Deep->new( "bible.db" );
# 執行以下程式碼時, bible.db已存有所有舊約內容
$db->{bible_old} = $bible;

# 加入我對於箴言的心得, 就這樣!! 所有改變已經存於硬碟上了
$db->{bible_old}{'old-20'}{memo} = '我最喜歡的經文'; 

DBM::Deep的資料結構屬於 hierarchical階層式資料形式,透過節點(key)的方式往下尋找下一階層的節點,直到找到所需要的資料為止。






















但與一般階層式資料庫不同的是,DBM::Deep可以動態產生資料結構,而又能擁有資料的持續性(persistence),這種特性可以應用在不同程式之間的資料儲存,交換,這是一種簡易型,輕量級又極富彈性的資料庫。

Perl再一次拓展了我對於資料處理的知識與視野。

2010年6月30日 星期三

Perl學習之旅的回顧(之二)

要檢索文字,必須知道什麼是「常規表示式」(Regular Expression),這是我透過Perl認識的一樣奇特的知識。
也因為能夠在Perl程式中簡單隨意的使用常規表示式,所以很快的便摸熟了一些基本的用法,這種可以方便實驗(Edit & Run)的學習模式,提昇了吸收新東西的速度。

雖然之前已經能夠快速的讀入聖經文字檔,使用常規表示法來檢索聖經內容,但是我沒有辦法區分新約與舊約、列出特定連續章節、中英文對照等等,原因是,原始文字檔案只有文字本身而沒有結構!

YAML 」是另外一個奇特的東西。

簡單的說,YAML是human-readable的類XML,透過資料的縮排,來代表(imply,也符合人類視覺的直覺)資料間的從屬、順序、對應等關係。

我將原來的檔案整理成YAML檔結果就像下面這個樣子。
<<舊約>>












<<卷名>>














有了YAML文件後,如果要知道舊約有幾篇,新約又有幾篇,可以使用以下程式碼:
use strict;
use YAML qw /LoadFile DumpFile/;

# 將聖經目錄讀入$bindx純量變數內
my $bindx = LoadFile('bible_index.yaml');

my $nOld = grep { /old/ } keys %$bindx;
my $nNew = grep { /new/ } keys %$bindx;

printf "聖經裡面,舊約有%d篇,而新約有%d篇。\n", $nOld, $nNew; 
輸出結果:

聖經裡面,舊約有39篇,而新約有27篇。

如果要列出《箴言》內容,可以使用以下程式碼:
use strict;
use YAML qw /LoadFile DumpFile/;

# 將所有舊約內容讀入$bible純量變數內
my $bible = LoadFile('bible_old.yaml');
my $bindx = LoadFile('bible_index.yaml');

my $old_20 = $bible->{'old-20'};

printf "《%s》\n", 
$bindx->{'old-20'}{chn};

for my $index ( sort keys %{$old_20} ) {
    printf "%s %s\n", 
            $index, 
            $old_20->{$index}{word};
}
輸出結果:

《箴言》
001:001 以色列王大衛兒子所羅門的箴言.
001:002 要使人曉得智慧和訓誨.分辨通達的言語.
001:003 使人處事.領受智慧、仁義、公平、正直的訓誨.
001:004 使愚人靈明、使少年人有知識和謀略.
001:005 使智慧人聽見、增長學問、使聰明人得著智謀、
001:006 使人明白箴言和譬喻、懂得智慧人的言詞和謎語。
001:007 敬畏耶和華是知識的開端.愚妄人藐視智慧和訓誨。
......
......

透過YAML建立了Perl的內部資料結構之後,如果有需要轉成XML格式檔,也是相當直覺與容易的,如下。
use XML::Simple;
# 存成XML格式檔
open my $fh, '>', 'bible_old_20.xml' 
    or die "$!";
XMLout($old_20, OutputFile => $fh);

close $fh;
XML檔案內容:


<001:001 index="1:1" word="以色列王大衛兒子所羅門的箴言." />
<001:002 index="1:2" word="要使人曉得智慧和訓誨.分辨通達的言語." />
<001:003 index="1:3" word="使人處事.領受智慧、仁義、公平、正直的訓誨." />
<001:004 index="1:4" word="使愚人靈明、使少年人有知識和謀略." />
<001:005 index="1:5" word="使智慧人聽見、增長學問、使聰明人得著智謀、" />
<001:006 index="1:6" word="使人明白箴言和譬喻、懂得智慧人的言詞和謎語。" />
<001:007 index="1:7" word="敬畏耶和華是知識的開端.愚妄人藐視智慧和訓誨。" />
......
......

在學習Perl的過程中,我知道了YAML以及XML這兩種新的文字資料格式,雖然這兩種格式背後仍有許多細節、應用與注意事項,但經由這些小小的練習,讓我有了熟悉的感覺,建立起學習更多細節的信心,而不再是難以入門的鬱卒。(這是我曾經看了一本「XML入門」之後的感覺)

參考文章: XML Matters: YAML improves on XML

2010年6月28日 星期一

Perl學習之旅的回顧(之一)

自從2001年接觸Perl這個程式語言,許多方面的知識跟Perl產生了連結之後,好像會變得比較容易理解,對我而言,這是個感覺奇妙的學習之旅。

從一開始,Perl的資料結構:scalar純量、array陣列以及hash雜湊,簡化了程式的撰寫與資料處理的力量,經由這三類資料型態,可以處理大部分的問題。

Perl的處理速度以及記憶體管理也讓我對於資料處理有著深刻的印象。

舉個例子來說,我初學Perl的時候,由於已有其他程式語言的經驗,
所以看了一些語法之後,想要寫個程式來試試看,但又不想寫什麼迴圈九九乘法表之類的,
於是想到"聖經",聖經可以線上下載取得,中英文都有,內容文字量也夠多,
處理起來應該比較趨近於實務狀況。

實際下載後,文字檔大小約3.5MB(31,102行),摘錄部份內容如下:

Gen 1:1 起初 神創造天地。
Gen 1:2 地是空虛混沌.淵面黑暗. 神的靈運行在水面上。
Gen 1:3  神說、要有光、就有了光。
Gen 1:4  神看光是好的、就把光暗分開了。
Gen 1:5  神稱光為晝、稱暗為夜.有晚上、有早晨、這是頭一日。
Gen 1:6  神說、諸水之間要有空氣、將水分為上下。
Gen 1:7  神就造出空氣、將空氣以下的水、空氣以上的水分開了.事就這樣成了。
Gen 1:8  神稱空氣為天.有晚上、有早晨、是第二日。
Gen 1:9  神說、天下的水要聚在一處、使旱地露出來.事就這樣成了。
Gen 1:10  神稱旱地為地、稱水的聚處為海. 神看著是好的。

我就試著寫一個聖經全文檢索程式,而整個程式碼及執行結果讓我覺得很神奇,如此簡潔、如此快速,程式如下。
use strict;

open BB ,'<', 'hb5_utf8.txt'
    or die "$!";

# 將檔案內容全部置於陣列變數@bible)內
my @bible = ;
close BB;

while (1) {

    print "請輸入查詢文字(q=結束):";
    my $search = ;

    chomp $search;

    exit if $search =~ /q/i;

    for my $words (@bible) {
        print "$words" if $words =~ /$search/o;
    }

}


執行結果

perl bible_demo.pl

請輸入查詢文字(q=結束):憂傷

Gen 6:6 耶和華就後悔造人在地上、心中憂傷
1Sa 2:33 我必不從我壇前滅盡你家中的人.那未滅的必使你眼目乾癟、心中憂傷.你家中所生的人都必死在中年。
2Sa 12:18 到第七日、孩子死了。大衛的臣僕不敢告訴他孩子死了、因他們說、孩子還活著的時候、我們勸他、他尚且不肯聽我們的話、若告訴他孩子死了、豈不更加憂傷麼。
Psm 51:17  神所要的祭、就是憂傷的靈. 神阿、憂傷痛悔的心、你必不輕看。
Psm 69:29 但我是困苦憂傷的. 神阿、願你的救恩將我安置在高處。
Pro 17:22 喜樂的心、乃是良藥.憂傷的靈、使骨枯乾。
Pro 18:14 人有疾病、心能忍耐.心靈憂傷、誰能承當呢。
Ecc 1:18 因為多有智慧、就多有愁煩.加增知識的、就加增憂傷
Isa 16:7 因此、摩押人必為摩押哀號.人人都要哀號.你們摩押人要為吉珥哈列設的葡萄餅哀歎、極其憂傷
Isa 54:6 耶和華召你、如召被離棄心中憂傷的妻、就是幼年所娶被棄的妻.這是你 神所說的。

請輸入查詢文字(q=結束):

有了這麼一點小成果之後,我對於Unix世界充滿了信心,
自以為有了Perl的協助,自己將無所不能,
直到我發現自己苦心撰寫的程式,
其實在Shell底下,只要一行指令就可以做到一樣的結果時,
我有點傻了。

    grep 憂傷 hb5_utf8.txt

那時突然覺得資訊的世界不僅僅是DOS/Windows的世界,
這世界的範圍、力量,遠比我當時可以想像的大多了。

2010年6月24日 星期四

國泰醫院牙周記

今天到國泰牙接受牙周手術第二番,
因為已有過一次經驗,這回比較不緊張。
倒是牙醫師隨口提了一句,說過程中如需填充骨粉得自費,
可能萬把塊左右,讓我心驚了3秒鐘。

手術過程還好,反正麻藥一打,
手術區域都沒感覺了,
但最不舒服的就是照相,
動刀前後要照,
刮除成果要照,
術後傷口縫線後他還要照,
每次都是外側照一次,內側再照一次,
最難的是,張開大口還要求我違反直覺的用鼻子呼吸,
以免呼氣把反射鏡面弄霧了讓相片效果不好,
試了幾回,我只好乾脆就閉氣不呼吸,
但這麼一折騰,心跳又加快了。

9:00上手術椅,11:20結束,由於過程緊湊,倒也一下子就過了。
只是下次進門前千萬記得先上廁所,尿急啊!!

ps. 後來醫生說沒用到骨粉,因為看起來用了也沒用,
      我回問說那怎麼辦? 靠運氣嗎?!! 

      他點點頭。

2010年6月23日 星期三

再一次探討使用Perl處理中文的正確方法(Windows平台)

由於big5碼中部分中文字
內含特殊字元(如"許功蓋")
如果程式不去面對並處理這個問題
那執行結果是否正確就要靠一點運氣了

舉例說明

需求:「硬碟越來越大,檔案數量膨脹,尋找不易,
利用程式產生檔案清單(檔名可能中英文夾雜),方便尋找。」

use strict;

use Cwd;

use File::Basename;
use File::Find::Rule;

my $path = getcwd();

my @ftypes = qw/*.doc *.pdf *.zip *.chm *.exe *.rar *.iso *.xls/;
my $rule   = File::Find::Rule->file->name(@ftypes)->start( "$path" );

while ( my $file = $rule->match ) {
    printf "%s \t %s\n", dirname($file), basename($file);
}

(部分輸出結果-許不見了!!)

> F:/資料/038?     名人-危機處理.doc
> F:/資料/038?     名人-員工問題.doc

為了正確處理big5碼中文,釐清Perl的處理方式,結論如下:

1. 一律使用use encoding 'big5'
2. 使用decode轉換外來資料,使用encode轉換外送資料

use strict;

use encoding 'big5';

use Cwd;

use File::Basename;
use File::Find::Rule;

use Encode qw/decode encode/;

my $path = getcwd();

my @ftypes = qw/*.doc *.pdf *.zip *.chm *.exe *.rar *.iso *.xls/;
my $rule   = File::Find::Rule->file->name(@ftypes)->start( "$path" );

while ( my $file = $rule->match ) {
    my $bfile = decode('big5', $file);
    printf "%s \t %s\n", dirname($bfile), basename($bfile);
}

(部分輸出結果-正確)

< F:/資料      038許名人-危機處理.doc
< F:/資料      038許名人-員工問題.doc

後記.

資料來自四面八方,處理編碼這類的問題前提是,
必須知道資料的編碼,才能正確的處理輸入及輸出。
Perl由語言內部解決了多國編碼的問題,但只提供了工具,
如何正確的處理還是有賴於正確的觀念與寫法。