Google+ Followers

2009年12月27日 星期日

2009 Taiwan Presentation @ Pardubice

非常非常棒的影片,影片來源 [雪兒] 第一次覺得台灣竟然這麼美 @ 批踢踢 Share 板Ptt Web BBS - 文章






 






























2009年12月24日 星期四

[Java] Pairwise Vector Similarity by Cosine Similarity @ Hadoop 0.20.1

Source: http://en.wikipedia.org/wiki/Cosine_similarity

source: http://en.wikipedia.org/wiki/Cosine_similarity


前陣子在 「台灣區 Hadoop 使用者社群會議」看到 Image Selection for Large-Scale Flickr Photos using Hadoop 中,使用 Cosine Similarity 計算向量的相似程度,才想起原來 MapReduce 的架構也可以用這啊!第一次聽到 Cosine Similarity 可能是大一的線性代數或是微積分,但第一次專題用到的實做,則是在大四 Datamining 的課程上,當時我們正在作一個找出相似遊戲玩家的專題,因此使用 Vector 跟 Cosine 來計算相似程度,但那次並不是我實做這類東西,我只負責用 Perl 去撈別人家的資料庫,哈。


最近讀 paper 有點悶,就先自己想了一下 Cosine Similarity 實作方式,發現有些地方卡卡的,另外,覺得做出來的還是要負擔一次把所有資料讀進記憶體的花費,這應該不是好的解法,所以就使用 "Cosine Similarity" 跟 "Hadoop" 或 "MapReduce" 關鍵字來搜尋一下,就找到以下幾篇:



當我了解實做上採用的演算法時,不禁感嘆這演算法的美好,所以決定來實做一下。另外,有個插曲是我請教強者同事關於連續做兩個有相依性的 job 問題時,發現他也實做過這個演算法 :D 還好我也先實做完才問,不然應該會很偷懶吧,哈



然後我有點龜毛地跟同事閒聊為什摩上頭提到的 Document Similarity 的計算都沒有完整做完 Cosine Similarity 的動作,就只是作向量的內積部份( Dot product , Inner product space ),後來討論時也才想到,其實 Document Similarity 重點在於 keyword 是否有 match 到,因此向量內積的算法,恰巧可以用這樣個觀點上,只是我還是覺得些不妥,因為呈現的數值沒有正規劃,假設有兩組向量的比較



  1. ( 1 , 1 ) , ( 1 , 100 )

  2. ( 1 , 1 ) , ( 1 , 1 )


以單純向量內積結果,第一組數值是 101 , 第二組是 2 ,但我覺得相似程度應該是第二組最好,完全 match 啊,以 Cosine Similarity 的作法,就會變成 1 ~ -1 之間,即 0.714142143 和 1 ,就可以很輕易看出第二組最好囉。只是實用上,可能計算量大而不適用!但我還是打算實做完 Cosine Similarity 啦,若單純想用 Document Similarity 可以到 Pairwise Document Similarity in Large Collections with MapReduce 逛逛,上頭有完整的 Java 程式囉!另外,若又想要降低計算量,又想計算 Cosine Similarity ,那可以先將向量正規化,讓他們的長度都為 1 ,接著再用 Document Similarity 算,就可以是正解啦!


對於 Cosine Similarity 的實做,採用 Sparse Matrix/Vector 的方式紀錄各物件的屬性,流程如下:


物件 (Source) -> 取出特性 (1st Map) -> 依此特性進行收集 (1st Reduce) => 以特性為主體,決定有多少物件要計算內積 (2nd Map) -> 以內積項目進行收集與累積數值 (2nd Reduce)


第一次是以屬性(feature)作為丟給 Reducer 的分群,第二次則以兩內積作為丟給 Reducer 的分群依據。


透過這樣的架構,一開始在對物件取資料時,就可以用多個 Mapper 同時取資料做事,至於缺點部份,則是第二次 Map 在決定有多少項目要作內積相乘時,必須一次將資料讀完才行,以 MapReduce 的架構,就是指一列資料。這是因為在作兩兩向量內積時,需要產生 N 階乘的項目,必須一口氣先得知有幾個項目才行,假設共有一千萬的項目,結果共有 900,000,000 的項目同樣擁有某個屬性時(feature),這將導致第一次 Reduce 產生會有一列擁有 900,000,000 的資料,並且在第二次 Map 時,記憶體一開始得讀入九百萬的項目,接著在產生 900,000,000! 等待相乘計算內積的數量。


只是,如果資料真的是這樣時,可能要去想想那個 Feature 是不是要捨棄掉,或是反向去建新的屬性,以此例來說,乾脆去考慮為剩下的一百萬項目建立一個屬性,這樣要作內積的計算也就從 900,000,000 ! 降到 100,000,000! 囉。


以下是我自己寫得粗略程式碼 XD


Makefile (太習慣寫 C 了, 這也是當初比賽前弄的架構, 只是最後改用 streaming 實做啦):


CC = javac
HADOOP = /usr/local/hadoop/bin/hadoop
CFLAGS =
CURR_DIR = $(.CURDIR)
SRC_BASE = .
OUT_BASE = .

PATH_CLASSPATH = /usr/local/hadoop
PATH_SRC = .
PATH_BIN = .

OBJ_DIR = $(OUT_BASE)/out
EXE = out.jar
LIB = $(PATH_CLASSPATH)/hadoop-0.20.1-core.jar
SRC = CosineSimilarity.java
OBJ = $(SRC:.java=.class)

HADOOP_INPUT = tmp_input
HADOOP_TEMP = tmp_tmp
HADOOP_OUTPUT = tmp_output

.SUFFIXES: .java .class

.java.class:
        $(CC) -classpath $(LIB) -d $(OBJ_DIR) $(PATH_SRC)/$<

all: $(OBJ_DIR) $(OBJ)

jar: $(OBJ_DIR) $(OBJ) $(EXE)

$(OBJ_DIR):
        @rm -rf $(OBJ_DIR) ;
        @mkdir $(OBJ_DIR) ;

hadoop: jar
        $(HADOOP) dfs -mkdir $(HADOOP_INPUT)
        $(HADOOP) dfs -put test.data $(HADOOP_INPUT)/
        $(HADOOP) jar $(EXE) org.changyy.CosineSimilarity $(HADOOP_INPUT) $(HADOOP_TEMP) $(HADOOP_OUTPUT)
        $(HADOOP) dfs -get $(HADOOP_OUTPUT)/part-* . ;

$(EXE):
        jar -cvf $(EXE) -C $(OBJ_DIR) $(OUT_BASE) ;

clean:
        @rm -rf $(OBJ_DIR) ;
        @rm -rf $(OUT_BASE)/$(EXE) ;
        @rm -rf part-*;
        @$(HADOOP) dfs -rmr $(HADOOP_INPUT) $(HADOOP_TEMP) $(HADOOP_OUTPUT) || echo "Pass : $(HADOOP) dfs -rmr $(HADOOP_INPUT) $(HADOOP_TEMP) $(HADOOP_OUTPUT)"


程式碼(其中在 main 裡連續作兩個 job 我是照抄同事的程式碼 XD Pairwise Document Similarity in Large Collections with MapReduce ):


package org.changyy;
        
import java.io.IOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.util.*;
import java.lang.Math;
        
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;
import org.apache.hadoop.util.*;
import org.apache.hadoop.mapred.jobcontrol.Job;
import org.apache.hadoop.mapred.jobcontrol.JobControl;


public class CosineSimilarity extends Configured implements Tool
{
    public static class ExtractFeature_Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, Text>
    {
        // Src:    node_name(str)    feature(str)    value(str)    ..
        public void map(LongWritable key, Text value, OutputCollector<Text, Text> output, Reporter reporter) throws IOException
        {
            String tmp_key = null;
            String line = value.toString();
            StringTokenizer tokenizer = new StringTokenizer(line);
            String tmp_feature = null , tmp_value = null;

            List<String> feature_list = new ArrayList<String>();
            List<String> value_list = new ArrayList<String>();

            boolean switch_flag = true;
            double length_calculate = 0 , tmp_double = 0;
            if( tokenizer.hasMoreTokens() )
                tmp_key = tokenizer.nextToken();
            while (tokenizer.hasMoreTokens())
            {
                if( switch_flag )
                {
                    tmp_feature = tokenizer.nextToken();
                    switch_flag = false;
                }
                else
                {
                    tmp_value = tokenizer.nextToken();
                    value.set( tmp_value );
                    switch_flag = true;
                    try
                    {
                        tmp_double = Double.parseDouble( tmp_value );
                    }
                    catch( Exception e )
                    {
                        tmp_double = 0;
                    }
                    // to list
                    if( tmp_double != 0 )
                    {
                        length_calculate += tmp_double*tmp_double;
                        feature_list.add( tmp_feature );
                        value_list.add( ( new Double( tmp_double ) ).toString() );
                    }
                }
            }
            if( length_calculate > 0 )
            {
                Text out_key = new Text();
                Text out_value = new Text();
                String length = ( new Double( Math.sqrt( length_calculate ) ) ).toString();
                Iterator iterator_feature = feature_list.iterator();
                Iterator iterator_value = value_list.iterator();
                while( iterator_feature.hasNext() ) // && iterator_value.hasNext() ) // for performance
                {
                    out_key.set( (String) iterator_feature.next() );
                    out_value.set( (String) iterator_value.next() + "\t" + tmp_key + "\t" + length  );

                    // Out: Feature \t Value \t Node \t Length
                    output.collect( out_key , out_value );
                }
            }
        }
    }
        
    public static class ExtractFeature_Reduce extends MapReduceBase implements Reducer<Text, Text, Text, Text>
    {
        // Out: Feature \t Value \t Node \t Length
        public void reduce(Text key, Iterator<Text> values, OutputCollector<Text, Text> output, Reporter reporter) throws IOException
        {
            Text out_value = new Text();
            String tmp_value = new String();
            if( values.hasNext() )
                tmp_value += values.next();
            while (values.hasNext())
                tmp_value += "\t" + values.next();

            out_value.set( tmp_value );
            output.collect( key, out_value );
            //int sum = 0;
            //while (values.hasNext())
            //    sum += values.next().get();
            //output.collect(key, new IntWritable(sum));
        }
    }

    public static class InnerProduct_Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, Text>
    {
        // Src:    feature \t value \t node1 \t length \t value \t node2 \t lenght ...
        public void map(LongWritable key, Text value, OutputCollector<Text, Text> output, Reporter reporter) throws IOException
        {
            StringTokenizer tokenizer = new StringTokenizer( (String) value.toString() );

            List<String> node_list = new ArrayList<String>();
            List<Double> value_list = new ArrayList<Double>();
            List<Double> length_list = new ArrayList<Double>();

            int switch_flag = 0;
            String tmp_string ;
            if( tokenizer.hasMoreTokens() )
                 tmp_string = tokenizer.nextToken();     // skip the first element

            double tmp_double = 0;
            while (tokenizer.hasMoreTokens())
            {
                switch( switch_flag )
                {
                    case 0:
                        try
                        {
                            tmp_double = Double.parseDouble( (String) tokenizer.nextToken() );
                        }
                        catch( Exception e )
                        {
                            tmp_double = 1;
                        }
                        value_list.add( new Double( tmp_double ) );
                        switch_flag = 1;
                        break;
                    case 1:
                        node_list.add( tokenizer.nextToken() );
                        switch_flag = 2;
                        break;
                    case 2:
                        try
                        {
                            tmp_double = Double.parseDouble( (String) tokenizer.nextToken() );
                        }
                        catch( Exception e )
                        {
                            tmp_double = 1;
                        }
                        length_list.add( new Double( tmp_double ) );
                        switch_flag = 0;
                        break;
                }
            }
            Text out_key = new Text();
            Text out_value = new Text();
            String tmp_string_2 = null;
            for( int i=0 , j=0 , size=node_list.size() ; i<size ; ++i )
            {
                for( j=i+1; j<size ; ++j )
                {
                    tmp_string = node_list.get(i);
                    tmp_string_2 = node_list.get(j);
                    
                    if( tmp_string.compareTo( tmp_string_2 ) < 0)
                        tmp_string += "," + tmp_string_2;
                    else
                        tmp_string = tmp_string_2 + "," + tmp_string;

                    out_value.set( ""+ ( value_list.get(i)*value_list.get(j) / ( length_list.get(i)*length_list.get(j) ) ) );
                    out_key.set( "("+tmp_string+")" );

                    // Out: Node1_Node2 \t Value
                    output.collect( out_key , out_value );
                }
            }
        }
    }
        
    public static class InnerProduct_Reduce extends MapReduceBase implements Reducer<Text, Text, Text, Text>
    {
        // Out: (Node1,Node2) \t Value
        public void reduce(Text key, Iterator<Text> values, OutputCollector<Text, Text> output, Reporter reporter) throws IOException
        {
            double tmp_value=0 ;
            while (values.hasNext())
                tmp_value += Double.parseDouble( (String) values.next().toString() );

            Text out_value = new Text();
            out_value.set( ( new Double( tmp_value ) ).toString() );
            output.collect( key, out_value );
        }
    }

    public int run(String[] args) throws Exception
    {
        //JobConf conf = new JobConf(CosineSimilarity.class);
        JobConf conf = new JobConf( getConf() , CosineSimilarity.class);
        conf.setJobName("CosineSimilarity_ExtractFeature");
        
        conf.setOutputKeyClass(Text.class);
        conf.setOutputValueClass(Text.class);
        
        conf.setMapperClass(ExtractFeature_Map.class);
        conf.setCombinerClass(ExtractFeature_Reduce.class);
        conf.setReducerClass(ExtractFeature_Reduce.class);
        
        conf.setInputFormat(TextInputFormat.class);
        conf.setOutputFormat(TextOutputFormat.class);
        
        FileInputFormat.setInputPaths(conf, new Path(args[0]));
        FileOutputFormat.setOutputPath(conf, new Path(args[1]));
        
        Job job = new Job(conf);

        //JobConf conf2 = new JobConf(CosineSimilarity.class);
        JobConf conf2 = new JobConf( getConf() , CosineSimilarity.class);
        conf2.setJobName("CosineSimilarity_InnerProduct");
        
        conf2.setOutputKeyClass(Text.class);
        conf2.setOutputValueClass(Text.class);
        
        conf2.setMapperClass(InnerProduct_Map.class);
        conf2.setCombinerClass(InnerProduct_Reduce.class);
        conf2.setReducerClass(InnerProduct_Reduce.class);
        
        conf2.setInputFormat(TextInputFormat.class);
        conf2.setOutputFormat(TextOutputFormat.class);
        
        FileInputFormat.setInputPaths(conf2, new Path(args[1]+"/part*"));
        FileOutputFormat.setOutputPath(conf2, new Path(args[2]));
        
        Job job2 = new Job(conf2);

        job2.addDependingJob(job);
        JobControl controller = new JobControl("CosineSimilarity");
        controller.addJob(job);
        controller.addJob(job2);

        
        new Thread(controller).start();

        while (!controller.allFinished())
        {
            System.out.println("Jobs in waiting state: "+ controller.getWaitingJobs().size());
            System.out.println("Jobs in ready state: "+ controller.getReadyJobs().size());
            System.out.println("Jobs in running state: "+ controller.getRunningJobs().size());
            System.out.println("Jobs in success state: "+ controller.getSuccessfulJobs().size());
            System.out.println("Jobs in failed state: "+ controller.getFailedJobs().size());
            System.out.println();

            try
            {
                Thread.sleep(20000);
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }

        //JobClient.runJob(conf2);
        return 0;
    }
    public static void main(String[] args) throws Exception
    {
        int status = ToolRunner.run(new Configuration(), new CosineSimilarity(), args);
        System.exit( status );
    }
}


測資(test.data):


node1   f1      10      f2      1       f3      4       f5      2
node2   f2      50      f4      30      f5      10
node3   f1      50      f3      10
node4   f5      20      f1      10
node5   f6      1       f3      10
node6   f6      20
node7   f6      20


執行:


# make clean
# make hadoop

其中會使用三個目錄 tmp_input , tmp_output , tmp_tmp ,第一步就是先清掉,接著 make hadoop 就是編譯成 jar 檔以及執行, 最後再從 tmp_output 撈 part* 出來

觀看結果
# cat part*


原先也想寫個 Writable 等等的東西,但寫好了卻編譯有問題,就是找不到 Orz 可能是我對 jar 檔架構不熟,因此最後就改用字串啦 :P 就像寫 streaming 一樣,全部都用字串來處理。有機會再來慢慢熟悉 jar 環境了!


2009年12月20日 星期日

工作三個月

若單純用月跟日相減計算,工作差不多滿三個月了。這三個月的日子,仔細想想還算豐富,生活型態跟碩班差不多,大概是碩班生活就是不斷實作吧。


從一開始先使用 Ubuntu Desktop 環境,接著用用 VMWare Workstation,翻翻 Django BookPython 教學文件,接著忘記怎樣開始的,就用用 Facebook 以及嘗試在上面開發一個簡單的 Quiz ,在這之前則是使用 Google Map API 設計一些東西,盡管後來都沒派上用場,而其中有一些空窗期則是將工作上的系統製成 VMWare Image 或是幫忙弄弄 Django 程式,大部分都不難,所以常把這當作雜事。


除此之外,偶爾也會想一些有的沒有的,像前陣子就常試想要使用 MapReduce 架構去處理照片串成影片,小影片再串成大影片,結果,英文太差,隔行如隔山,用錯關鍵字找了三、四天,放棄前忍不住到論壇發文詢問,一問之下才發現別人給的回應有點怪,最後才發現我用錯了關鍵字,恰好用錯的字也是該領域中的一種情境。第一次覺得英文差很浪費時間,在論壇上可是瞬間就解決問題了!


隨後又被分配要介紹 Amazon EC2 ,在這之前則是被丟一些投影片或文件看看,原來很多事很早就規劃。然後花了兩、三天看看別人寫的教學,接著兩、三天摸摸 EC2 ,直到報告前三天開始弄投影片,又恰好碰到 EC2 的改版,讓預計中的進度有點變亂。明明 35 頁的投影片,再加上 15 頁的圖片,僅剩幾頁文字介紹,就這樣被主管至少五次來回地改來改去,原來用字真的要分外小心,雖然別人對此題目不熟,或是不是這領域的,但他們對報告流程都很熟悉,而一丁點的用字錯誤,就可能會被認為做事態度不佳。大概在報告前兩天時,開始有小小的壓力,畢竟我頂多只熟悉跟電腦溝通的英文字句吧!慶幸的,報告的過程還算流暢,主管也幫我回應許多問題,整個單位 60 人,共有 24 人左右捧場,扣除一些認識的,這份簡介大概對 10 ~ 15 人左右有幫助吧?其間有三位長官。經過這次簡短的報告,我覺得長官們吸收能力很強,明明覺得沒什麼的簡介,但他們也能聽得很仔細,並且問出其中重要的問題。偶後某長官也有特別關心一下,讓我覺得在上位者,都有十足的功力。


除了工作外,也跟同事以及大學同學去試試今年才新設立的運動館,由於一年有補助五百元,嘗試打打桌球、撞球,甚至電玩、投籃機和打擊場,只是電玩要撤掉,連帶投籃機跟打擊場變成要投幣使用,而補助的錢卻不能用這在些,久了只剩桌球和撞球,或許明年可以考慮游泳。


在這邊生活倒還愜意,一個月吃飯加住宿大概六千元,前陣子也因此狠下心買了一台電腦,只是時間不太夠,很難執行一些計畫,但也感覺這樣才是完整的生活,許多事都是平行地進行著,不再像學生時代,可以一段時間只專心處理一件事了。


接下來就是把看文件的功力練好,增加自己的吸收能力囉。


2009年12月18日 星期五

宅男最後的 120 小時








剛在 Facebook 上看到的,沒想到 YouTuBe 已經可以這樣玩了!我覺得影片拍得不錯,有些配樂也很耳熟,整體感還挺不賴的。


腦中突然一閃,前陣子恰好把圖片和影片串接當成一個練習 MapReduce 架構的題目,若真的無聊的話,或許可以把所有片段抓下來串接成所有故事劇情 XD 有興趣的還是從投完一遍吧,網路上已經有不少攻略,因為常常選錯看不到最後一幕囉,在此擺在煞風景的結局。其他的倒是隨著故事可以看到許多香港風景,挺不錯的。


Nami 結局









國企匡結局









讓本機可連虛擬機器,虛擬機器可連外 - VirtualBox 網路設定

前陣子開始用 VirtualBox ,因為上次抓的 VMWare Server 版無法在 Windows 7 x64 安裝,是連安裝檔都無法正常執行,所以,就開始嘗試 VirtualBox 啦。


我覺得它小而巧,我挺喜歡整個都包在一個檔,可以很方便地搬動。但有一點我一直很不解,為什麼我把虛擬機器設成 NAT 時,它的確可以透過我本機電腦的網路服務連出去,但本機電腦卻連不進虛擬機器,我對這點很疑惑。這樣的 NAT 服務有點怪,跟我在 VMWare 上的使用或是在 FreeBSD 上架設 NAT 的服務有所差別,之後我就一直沒再理他,直到最近工作的事靠一段落後,又再次看到這篇文章 營造 Linux 學習環境的好用工具 VirtualBox !第二次看到時,我終於想通該怎樣搞了。


那就是為虛擬機器加網卡,第一張是 Network Address Translation ,第二張是 Host-only networking ,如此一來,第一張網卡提供 NAT 服務,讓虛擬機器可以與連出去外面,而第二張網卡使用"僅限主機",將使得本機端電腦可以連到虛擬機器。


測試的成果很順利,NAT 那張網卡取得的是 10.x.x.x 開頭的 IP ,而 Host-only networking 那張則是 192.168.x.x  的 IP


2009年12月17日 星期四

Amazon EC2 使用教學 - Create Image (EBS AMI) 系統備份

這週發現,原先在 Amazon EC2 上,從 Quick Start 開啟的機器,有一些不一樣了。開啟的機器無論 Linux 或 Windows 系統,它們的一開始只被分配小量的硬碟空間,如 15GB 等,跟之前不太一樣,上周開 Linux 機器時,至少提供 140 GB 的暫存空間。而以前用 Bundle 建立的系統依舊還可以透過原先開機器的方式進行。


幾天前,我還以為 Create Image (EBS AMI) 跟 Bundle Instance (S3 AMI) 是相同的服務,直到今天中午才發現是不一樣的,雖然它們有共同的功能,那就是備份系統,但整個過程是不一樣的。主要是現在從 Quick Start 開啟的新機器,它們的系統碟已經換成 EBS Volume 了,簡單的說,建立一台機器時,會自動在 Volume 區建立一顆硬碟並且使用它。目前我還沒搞清楚計費的差別,畢竟現在這種新的方式,多用了一個 EBS Volume 服務,感覺會多收費 :P 而使用舊有的 AMI ,在建立機器時,其 root device 是 instance-store 的型式,只有 EBS 型式的才能使用 Create Image (EBS AMI) 的方式備份喔。而流程上是將系統碟製作 snapshot ,如此一來,新開機器的系統碟就是透過 snapshot 來複製出來,以此達成開啟新機器的動作。


回歸主題,關於 Create an EBS Image 的備份方式,無論 Windows 或 Linux 系統都是一樣的,可以完全在 AWS Management Console 這 Web UI 上完成操作,不需像 Bundle 時還要下下指令,以及準備相關的 Key 等等。在此以 Windows 機器當作範例,特別留意的,在製作 EBS AMI 時,系統會有重開機的現象,跟 Bundle Image 是不一樣的,Bundle 時並沒有重開機。在製作 Windows EBS AMI 時,別忘了記住或修改 Admin Password,這幾天我測試的結果,透過建立的 AMI 來開機器時,無法用 Private Key 來取得管理者密碼,而 Linux EBS AMI 倒還可以用 Private Key 進行登入。


以下是對 Windows 系統的操作


使用 Create Image (EBS AMI) 備份系統是很方便的,只需要在 AWS Management Console 操作就行了


EC2-Bundle-Windows 01 @ 20091214


接著只需打打名字


EC2-Bundle-Windows 02 @ 20091214


等待一會就會蹦出此訊息囉。


EC2-Bundle-Windows 03 @ 20091214


最後在 AMIs 就可以看到製作好的 AMI 囉!



EC2-Bundle-Windows 04 @ 20091214


Amazon EC2 使用教學 - Bundle Instance 系統備份

既然寫了 Amazon EC2 使用教學 - 以 EBS Volumes & Snapshots 增加和管理永久的硬碟空間 備份資料的方式,接著就要介紹一下備份系統。


EC2 機器關機後,除了資料不會儲存外,對系統的更動也不會保留,因此,辛苦設定好的系統環境必須要有備份的機制,這就是 Bundle 的服務。這個服務能夠讓你先專心設定一台機器,接著安置好環境後,可以在用 Create Instance 的方式,一次建立多台機器,有點類似以前用 Norton Ghost  安裝大量機器的概念(主要建立在硬體規格一樣),另外,Clonezilla 再生龍 是免費的軟體,也可以負責這類服務 。


在此以 Linux 機器作為範例,而 Windows 部分據說可以透過 Web UI 的 Bundle Instance (S3 AMI) 製作,但這部分我沒試。另外,一樣可以用指令來備份,有需要在參考這篇 Windows > Bundling an AMI


 


EC2 Linux 機器


備份 Linux 系統需要使用 EC2 提供的 tools ,過程較為繁瑣。在 EC2 上的 Linux 要備份系統前,必須先準備一下 Amazon Web Services > Your Account > Security Credentials > X.509 Certificate ,這個用在使用 ec2 tools 要做認證用的,類似確認使用者的身份,需要有 cert-*.pem 跟 pk-*.pem 兩個檔案囉,若真的忘記只好再去申請一組啦,我操作的機器: Getting Started on Fedora Core 8 (AMI Id: ami-3c47a355) - Minimal Fedora Core 8, 32-bit architecture, Apache 2.0, and Amazon EC2 AMI Tools ,更多詳細資訊請參考 Linux and UNIX > Bundling an AMI


X.509 Certificate


接著,把這 cert-*.pem 跟 pk-*.pem 從本機端複製到 EC2 上要備份系統的 Linux 機器,而此台機器假設是使用 demo.pem 作為認證的,


My Desktop $ scp -i /path/demo.pem /path/cert-*.pem /path/pk-*.pem [email protected]##-##-##-##.compute-1.amazonaws.com:/mnt


登入 EC2 要備份的 Linux 機器


My Desktop $ ssh -i /path/demo.pem [email protected]##-##-##-##.compute-1.amazonaws.com


進行系統備份,需留意 -u  接的是一組數字組成的 AWS ID 可以在 AWS > Your Account > Security Credentials 查看或是在 AWS Management Console 挑一台機器看 Owner 也行,而 -r 接的是系統的資訊 32-bit  ,此過程需要一些時間


EC2 $ cd /mnt
EC2 $ ec2-bundle-vol -d /mnt -k /mnt/pk-*.pem -c /mnt/cert-*-.pem -u ############ -r i386


EC2-Bundle-Linux ec2-bundle-vol 01 @ 20091214


........


EC2-Bundle-Linux ec2-bundle-vol 02 @ 20091214


接著將備份好的系統上傳至 S3,其中 -b 接得就是你要擺在 s3 的那個目錄裡,若該目錄不存在則會自動建立,而 -a 跟 -s 分是認證使用者的 Access Key ID 和 Secret Access Key
,可以在 Amazon Web Services > Your Account > Security Credentials > Access Keys 查到。


EC2 $ ec2-upload-bundle -b your-s3-bucket -m /mnt/image.manifest.xml -a **** -s ****


最後,則是註冊這個備份系統,有兩種方式可以作,一種是一樣透過 EC2 tools 中的 ec2-register ,另一種則是在 AWS Management Console > AMIs > Register New AMI 上註冊。使用 EC2 tools 則需要在自己的機器上安裝 EC2 tools ,並且要有 JAVA 運作環境,因此,用 AWS Management Console 可以省下很多事囉。


使用 ec2-register


My Desktop $ ec2-register -K /path/pk-*.pem -C /path/cert-*.pem your-s3-bucket/image.manifest.xml


使用 AWS Management Console


EC2-Register New AMI @ 20091214


 


備份完系統後,使用上就在 AWS Management Console > Instance > Launch Instance 時,可以切換到 My AMIs 上,就可以看到自己備份好的系統,剩下的流程就像建立新機器一樣。


EC2-Bundle-Windows 05 @ 20091214


相關問題



  • Client.InvalidInstanceType: Instance does not support bundling

    • 一開始在自己的桌機上(Unbuntu 9.04 desktop i386),備份 EC2 上的 Linux 機器,一直在試 ec2-bundle-instance ,但只會有這個訊息,最後發現應該是只適用在備份 Windows 機器啦

    • 參考資訊:[ec2-beta] Bundling image?



  • The specified bucket is not S3 v2 safe (see S3 documentation for details)

    • 當要在 EC2 Linux 機器上打算上傳資料至 Amazon S3 時,會蹦出這個訊息,主要是我使用的 bucket name 包含底線 "_",最後我換成 "-",

    • 詳細資訊:Bucket Restrictions and Limitations




2009年12月11日 星期五

Amazon EC2 使用教學 - 以 EBS Volumes & Snapshots 增加和管理永久的硬碟空間

Amazon EC2 可以很方便地叫幾台電腦出來,然而每一台關機後資料是不會留下來的,如其名,機器只在來計算為主。目前我用便宜的機器大概有 140 GB 的空間可用,大部分的應用都很充足,但為了能夠把實驗資料留下,就必須使用 EBS(ELASTIC BLOCK STORE)服務,其中  Volumes 用來增加儲存空間,而 Snapshots 能對Volumes 進行硬碟備份。


在使用 EBS Volumes 服務前,需要留意自己開的機器所在的區域,因為增加的硬碟要跟機器在同一區才行。以下則簡單列出 Linux 與 Windows 上掛載新硬碟的過程。


EC2 - EBS 01 @ 20091210


其中左上角是在新增機器可以選擇的區域,區域位置主要是網路回應可量,當然各區域的計費也是不同的,這些都需留意。右下角則是在 Instance 查看某一台機器所在的區域位置。接著切換到 Volumes 頁面,則可按下 Create Volume 新增一顆硬碟,這時就會問你硬碟要建在那個區域,而 Snapshot 先不用選,這是用在類似複製某顆硬碟出來用的。


EC2 - EBS 02 @ 20091210


Windows 新增硬蝶


在還沒安裝硬碟之前,Windows 機器上只會有 C 槽 ,從下面可以更快速查看 Disk 狀態(只有30GB)


[Start]->[Administrative Tools]->[Computer Management]->[Storage]->[Disk Management]


EC2 - EBS Windows 01 @ 20091210


接著,畫面切回 AWS Management Console 頁面,可以在左邊 ELASTIC BLOCK STORE 之 Volumes 上選擇哪顆硬碟來加到機器上(Attach Volume),其中 Instances 就是要選你要加的機器代號,可以回 Instances 頁面查詢,這邊也會列出所在位置讓你配對,而 Device 我是輸入 1 ,之前很直覺地試 D ,想說當作 D 槽,則是會出錯。


EC2 - EBS Windows 02 @ 20091210


當你一按下 Attach 後,不一會兒就可以在 Windows 上看到硬碟加入的樣貌,並且可以按右鍵來進行處理,Windows 有操作精靈帶你走,如格式化硬碟等,很方便的。


EC2 - EBS Windows 03 @ 20091210


不一會兒的功夫,硬碟加好囉。


EC2 - EBS Windows 04 @ 20091210


以上就完成加入硬碟的過程。


Linux 新增硬碟


EC2 - EBS Linux 01 @ 20091210


增加硬碟的過程跟 Windows 差不多,但在 Attach Volume 時,Device 代號可以用選單選的,此例為 /dev/sdf ,而被加到機器的硬碟會呈現 in-use 的狀態,這跟 Windows 都一樣,只是我忘了補上 Windows 操作的圖。


EC2 - EBS Linux 02 @ 20091210


但接下來,就沒有 Windows 操作的輕鬆,當然也可能是因為我沒使用 X-Window 的關係,首先使用 Terminal 登入機器,我用 PuTTY 登入,並且用以下指令查詢新加入的硬碟狀態


# ls -l /dev/sdf
# fdisk -l /dev/sdf


Linux - fdisk -l


接著可以使用 fdisk 來建 partition table ,此例是全部分配


# fdisk /dev/sdf


Linux - fdisk


接著格式化


# mkfs.ext3 /dev/sdf1


Linux - mkfs.ext3


最後就掛載到 /data 囉


Linux - mount


最後,講講 Snapshot 的部份,它是一個可以用來複製或備份硬碟服務,當我們在 Volume 增加硬碟後,如果某顆硬碟以儲存的資料想要分享到其他台機器時,這時就可以使用 Snapshot 服務,製作一個 Snapspot ,爾後在新增硬碟時(Create Volume),可以以某個 Snapshot 製作出來,以下是簡單的製作流程以及新增 Volume 時採用 Snapshot 的設定。


選取某顆硬碟製作 Snapshot ,此例使用 demo 這個名字。


EC2 - Snapshots 01 @ 20091210


 


過一陣子後,則可以在 Snapshots 頁面看到製作好的資訊。


EC2 - Snapshots 02 @ 20091210


另外,還可以設定這個 Snapshot 的存取權限,例如分享給大家用。


EC2 - Snapshots 03 @ 20091210


最後,回到 Volumes 頁面,新增 Volume 時,就可以挑選 Snapshot 啦,它就會以指定的 Snapshot 內容弄出一顆硬碟囉


EC2 - Snapshots 04 @ 20091210


2009年12月10日 星期四

Google Letter

Google Letter


今早收信,發現有封神秘字樣,原來我的廣告費已經達 10 美金,我掛 Google Adsense 也快一年了。這封應該只是確認一下身分而已,離目標 100 美金還遠得很。好像只要達 10 美金,就可以去設定收款的方式,看大家推薦都用西聯匯款,除了速度較快外,也可以免手續費的樣子,剩下等我哪一天真的達到 100 元再來分享吧。


目前我的廣告帳號是跟 Pixnet 綁在一起吧?也就是會付 20% 給 Pixnet 囉,至於為何會綁在一起,實在是以前寫的網誌都偏心情為主,申請時都沒過,過了一陣子後恰好也搬到 Pixnet ,可能是文章比較多了,後來用 Pixnet 這邊申請也就剛好過啦。


Amazon EC2 使用教學 - 以 SSH/PuTTY 連線 Linux 機器

EC2 Instance list @ 20091210


關於如何在 EC2 上新增一台機器,可以參考這篇 Amazon Elastic Compute Cloud (EC2) 使用教學 - 以 Windows Server 2003 與 RDP 為例 ,其中只要在選 Instance 時,改選 Linux 機器就行啦。


接著要怎連線呢?若你是在 Unix 上的機器並有 ssh client 可以用的話,那沒問題,就搭配使用一開始跑機器所給的 Key Pair (此例為 demo.pem ),另外在 Security Groups 上可別忘了要打開 22 port


# ssh -i demo.pem [email protected]##-##-##-##.compute-1.amazonaws.com


其中上述的 "ec2-##-##-##-##.compute-1.amazonaws.com" 就是 EC2 配給你的連線位置


PuTTY PuTTYgen


只是,如果想從 Windows 機器連進 EC2 上開的機器,滿常用的是 PuTTY 軟體,只是登入機器的帳密問題需要稍微處理一下,必需搭配 PuTTYgen 軟體,一樣可以在 PuTTY 網站下載囉!


Putty - AWS


登入前要先切換到 [Connection]->[SSH]->[Auth] 上,需要用 Private key file for authentication 的登入方式。


Putty - Auth


然而,我們手上只有 *.pem 檔案,上述要的 *.ppk ,這時就需要靠 PuTTYgen 幫忙轉換


PuTTYgen - Import Key


PuTTYgen - Save private key


透過上述兩步,可以將 *.pem 轉成 *.ppk 檔,就可以在 PuTTY - Private key file for authentication 使用,開啟連線後,輸入 root 完就自動建立連線啦。


Putty - root@EC2


2009年12月9日 星期三

ASUS Eee PC 1000H WinXP 版 - 關閉觸控面板

最近常用 Eee PC 打打字,發現很容易不小心按到觸控面板,結果就打字的東西就會亂飄,然而上次仔細一看卻沒發現任何開關它的熱鍵,就不了了之。


剛剛又不小心弄到,就上網找一下資料,發現有關閉的方式,但我不確定原先舊有的是否就有,因為我已經是先更新了觸控面板的驅動程式才去做設定。所以,可以先試著去找看看,若沒有下面所提到的設定再去安裝新的觸控面板驅動程式吧!


首先,上 ASUS 官網下載驅動軟體 ASUS技術支援 -> [檔案下載] -> [Eee Family] -> [Eee PC] -> [Eee PC 1000H XP] 按下搜尋後,再選 WinXP 系統。


接著看 [觸控板驅動程式] ->  [觸控板驅動程式 V7.0.4.3版] 上頭恰好有列出



  1. 新增支援多點觸控功能

  2. 新增支援防誤觸功能


所以下載安裝完後,可以從視窗右下角常駐程式中去點選[觸控面板右鍵]->[Smart-Pad內容]->[Elan Smart-Pad] 頁面,就有"停止裝置" 的選項,當然這樣關閉觸控面板是可以的,但倘若哪一天忘了帶滑鼠,一開機連去點 Smart-Pad 設定的能力都沒有,那豈不是更慘?好像沒有組合鍵可以關閉打開它,但至少在 Smart-Pad 設定頁面上,有一個選項”當外部USB滑鼠插入時停用",可以恰恰好用一下囉。如果常駐程式找不到觸控面板的設定,那就從[開始]->[設定]->[控制台]->[滑鼠]->[Elan Smart-Pad] 也可以設定。


2009年12月8日 星期二

快閃台北行

高鐵車票


這張是第二次搭高鐵留下來的,第一張其實就是從新竹到台北,只是被收走報帳。回程這張打 65 折,只要 185 元,去程則是 85 折,要 245 元。這個價錢其實還能接受,因為台鐵新竹到台北,自強要 180 元,一小時車程,區間車 116 元花一小時二十分,而高鐵只要30分鐘左右,在加上從宿舍到竹北高鐵站其實比到新竹火車站還近。


由於是第一次搭車,搭車前我還特地去逛一下知識+,擔心自己太門外漢,事實證明,我連插卡片都還真的插反了!一開始我找到的文章都是 2007 年的,還有說要把票根留下來得在購票時跟售票員說,或是自動購票時,有選項可以選,但我用自動販賣買票也都沒看到選項,最後問服務人員才知道現在都自動留票根啦,難怪當時找到的文章只到 2007 年。


記得大五那年,同寢學弟一聽到高鐵開始營運,就去搭乘一次,過了兩年後我也終於搭過高鐵,扣除乘車時間外,跟台鐵差不了多少。要說不一樣的話,至少看到高鐵的服務員拖著行李時,會覺得像空姐,但台鐵的服務員就只會覺得像清潔員吧。哈,當然不只這些,高鐵站真的華麗很多,而且軌道架在空中,等車時可以看看車站外的夜景,真的不錯。另外,搭高鐵時,逢下班時間,感覺外國人比較多,我搭台鐵那麼多次,都沒看到幾次外國人,大概外國人都真的只搭高鐵吧。


或許該找找機會也帶家人去搭乘一次吧!


Amazon EC2 使用教學 - 以 Windows Server 2003 與 RDP 為例

Amazon Elastic Compute Cloud 簡稱 Amazon EC2 或 EC2 ,它是眾多雲端運算平台之一。至於何時會需要用到雲端計算呢?我覺得可以思考一些情境,例如突然需要一堆電腦來幫計算研究數據,或是想要開發網站服務但手邊的機器不足,然後又非常急迫,這時候就可以考慮使用這類平台,可以很輕鬆快速的得到一群電腦!當然,手邊沒幾個銅板是不行的(還有信用卡)!


雲端運算是當下很紅的名詞,最近被要求一下要熟悉 EC2 ,所以就來強迫自己寫一篇使用教學,在 Google 上使用 "EC 教學" 關鍵字,其實已經可以找到不錯的文章: Amazon EC2 簡易操作筆記 (使用 EC2 Console)  與 Amazon EC2 使用操作筆記 (使用 Elasticfox) ,另外則是官方的介紹 Amazon Elastic Compute Cloud - Getting Started Guide (API Version 2009-08-15)


網站位置:http://aws.amazon.com/ec2/ ,有興趣的可以再多逛逛 Amazon Elastic Compute Cloud - Wikipedia, the free encyclopedia ,這是我從同事身上學到的,凡事要留意一下歷史背景。EC2 在 2006 年 08 月開啟,在 2008 年 10 月 23 日後,已提供完整的架構與服務。


若以一個初學者,可能跟我一樣連 Amazon 帳號都沒 :P 所以,先來申請一下帳號吧!請透過網站上的 Create an AWS Account 建立帳號(AWS:Amazon Web Services) ,強烈建議,一切動作都從官網開始,以免不小心點到釣魚網站。



  1. 點選 Create an AWS Account 進入登入畫面

    • My e-mail address is: your_email

    • 勾選 I am a new user.

    • 點選 Sign in using our secure server



  2. 接著輸入基本帳號資訊

    • My name is: your_name

    • My e-mail address is: your_email

    • Type it again: your_email

    • Enter a new password: your_password

    • Type it again: your_password

    • 點選 Create account



  3. 接著填寫個人資訊

    • Address Line

    • City

    • State, Province or Region

    • ZIP or Postal Code

    • Country

    • Phone number

    • 接著點選下面的同意跟認證碼後就可以完成申請帳號囉!



  4. 接著可以透過上個頁面顯示的 Amazon Elastic Compute Cloud 連回去。並且點選 Sign Up For Amzaon EC2 ,並且下頭要你輸入信用卡資訊。由於我沒信用卡 XD 所以後面就不 demo 啦。此部份也可以從 Your Account -> Payment Method 進行設定。


接著,Amazon Elastic Compute Cloud 頁面,可以在右上角 Your Account 選單中看到不少項目:



  • Account Activity

  • Usage Reports

  • Security Credentials

  • Personal Information

  • Payment Method

  • AWS Management Console

  • DevPay Activity


其中比較特別的是 Security Credentials 和 AWS Management Console 。前者用意就像帳號密碼的認證管理,可分為



  • AWS Access Key Identifiers

    • make secure REST or Query protocol requests to any AWS service API



  • X.509 Certificates

    • make secure SOAP protocol requests to AWS service APIs



  • Key Pairs

    •  for Amazon CloudFront and Amazon EC2





圖片來源:About AWS Security Credentials


 AWS 管理是透過 API 指令進行的,例如在本機端用 cmd mode 一個個敲出來,或是用管理介面的進行管理,這些動作要跟 AWS 進行溝通,將透過 REST 或 SOAP 等協定進行,因此,就有上述幾種方便的帳密確認方式,簡言之,就不必使用登入 Web 介面的 email / password 來管理,換個角度想,以 Security Credentials 為例,新增一組用來登入使用服務,若這組帳密不慎外流,可以快速更換,而損失能夠降低,倘若登入都用 email / password 管理,一旦帳密被得知,受到的傷害可能足以影響使用者其他網路服務的使用。另外,如果有多人要管理 EC2 的機器時,使用 Security Credentials 分給管理者,也比使用 email/password 來的好。更多細節請參考 About AWS Security Credentials


AWS Management Console @ 20091208


假設你完成信用卡的設定,那就可以點選 AWS Management Console 進入 Web 端管理介面,若是使用 Security Credentials 的人,可考慮使用 Elasticfox Firefox Extension for Amazon EC2 (Resources ->  Developer Tools -> Other Developer Tools -> Elasticfox)。在此以 Web 管理介面介紹。接著,來新增一台雲端機器吧!


在新增機器之前,需要留意管理介面左下角的兩項:Security Groups 和 Key Pairs 。前者就像設定一台機器的防火牆,例如新增一台機器,若要能夠遠端,以 Windows 來說,就要打開 3389 port ,而 Unix 則是 22 port 等,而 Security Groups 就是在做這件事,設定好後,若未來還要再新增機器時,可直接套用之前以建立好的條例。Key Pairs 部份,就像前面所提到的 Security Credentials ,只是對象轉成一台雲端機器登入的帳號密碼。


AWS Management Console - Security Groups @ 20091208


因此,在新增機器前,可以先到這兩個頁面作點事情工作,其中 Key Pairs 只要點一下 "Create Key Pair" ,輸入名稱跟敘述就可以完成,此過程會將建立的 *.pem 下載回來;Security Groups 會稍微多一點步驟,除了名字跟敘述外,還需要一項項地增加你想要開放的 port ,例如 Windows 就要試一下 RDP (Remote Desktop Protocol) ,這樣才可以遠端登入囉!


假設上頭已新增好 Key Pair : demo.pem 和 Security Groups : demo ,接著就來新增 Windows 機器啦。請點選左邊的 Instances ,這個意思就類似你要開幾台電腦的地方,另外也有一些資訊可以提供你掌控機器狀況。


AWS Management Console - Instance @ 20091208


選一台 Windows 來用用,就選 Basic Microsoft Windows Server 2003 吧!


AWS Management Console - Instance Setup @ 20091208


選好後,除了要決定開幾台機器外,最重要的是 Key Pair 和 Security Groups 的設定,此例就用剛剛新增好的 demo 系列吧!接著按下 Launch 就啟動機器啦!當然也開始收費囉!EC2 的計費是以小時為單位的,使用上要留意,也就是使用 1 分鐘跟 59 分鐘是一樣的價錢,使用上要小心啦。


AWS Management Console - Instance Setup @ 20091208


機器開起來後,接下來就是去使用它,使用 Windows 的方式最簡單就是遠端桌面了,但該主機位置在哪?登入的帳號密碼在哪?就先選好 instance 後再點 Instance Actions


AWS Management Console - Instance Menu @ 20091208


其中有一項就是 Get Windows Admin Password ,就用它來查看帳號密碼囉


AWS Management Console - Get Password @ 20091208 demo.pem


其中那欄 Private Key ,就是之前設定 Key Pairs 時下載的 demo.pem 啦,用編輯軟體打開它並且全部複製貼上到上頭的框框內,就可以按下 Decrypt Password 啦,最後就會告訴你連線的位置、登入帳號和密碼囉,只要之前的 Security Groups 的設定有打開 RDP 3389 Port ,那就可以遠端進入囉!


AWS Management Console - Get Password @ 20091208


剩下的行為,就像遠端一台電腦一樣,只是這台電腦是 EC2 上的囉!


2009年12月5日 星期六

[PHP] Official Plurk API 之 PHP - cURL 使用教學

Plurk 一陣子,官方終於將 Plurk API 弄出來囉,如此有更統一的 API 來把玩,最棒的是不必擔心用的 Unofficial Plurk API 會不會哪一天掛掉,當然並不是說官方的就不會更改 API 架構,只是換個角度想,API 的維護交給官方,讓製作 Plurk 架構的人處理,比自行摸索要來的更有效率。(...突然回想起當初去hack某家網誌發文的辛苦歷程...)


Plurk API 上有 Java 跟 Python 的範例程式,但竟然沒有我最擅長使用的 PHP 。所以,就來試試 PHP 吧!



  1. 申請 Plurk API Key

    • 幾乎大家都用 API Key 的方式管理,申請方式就只要在上述網頁 Get an API key 中,填寫 "What are you planning to make?" & "Your full name" & "Your email" ,最重要的就是 "Your email" 啦,因為 API Key 會用寄信的方信寄到指定的信箱,而其他兩個資料,我想只是一種統計吧?



  2. 由於一些動作都是要使用 HTTP 甚至 HTTPS 進行溝通,在 PHP 上滿建議使用 cURL 函數,先建一個小function吧。

    • function do_act( $target_url , $type , $data , $cookie_file = NULL )
      {
              $ch = curl_init();

              if( $type == 'GET' )    // GET
              {

                      $target_url .= http_build_query( $data );
                      curl_setopt($ch, CURLOPT_URL, $target_url );

              }
              else                    // POST
              {
                      curl_setopt( $ch , CURLOPT_URL , $target_url );
                      curl_setopt( $ch , CURLOPT_POST , true );
                      curl_setopt( $ch , CURLOPT_POSTFIELDS , http_build_query( $data ) );
              }

              if( isset( $cookie_file ) )     // cookie
              {
                      curl_setopt( $ch , CURLOPT_COOKIEFILE , $cookie_file );
                      curl_setopt( $ch , CURLOPT_COOKIEJAR , $cookie_file );
              }

              curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true );
              //curl_setopt( $ch , CURLOPT_FOLLOWLOCATION , true );
              //curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER , false );

              $result = curl_exec( $ch );
              curl_close( $ch );
              return $result;
      }



  3. 連續範例動作

    • // Users/login
      print_r(
              do_act(
                      'http://www.plurk.com/API/Users/login' ,
                      'POST' ,
                      array(
                              'api_key'       => 'Your_Plurk_API_Key' ,
                              'username'      => 'Your_Account' ,
                              'password'      => 'Your_Password'
                      ) ,
                      '/tmp/plurk_cookie'
              )
      );

      echo "\n\n";

      // Timeline/getPlurks
      print_r(
              do_act(
                      'http://www.plurk.com/API/Timeline/getPlurks' ,
                      'POST' ,
                      array(
                              'api_key'       => 'Your_Plurk_API_Key' ,
                              'offset'        => '2009-6-20T21:55:34'
                      ) ,
                      '/tmp/plurk_cookie'
              )
      );

      echo "\n\n";

      // Timeline/plurkAdd
      print_r(
              do_act(
                      'http://www.plurk.com/API/Timeline/plurkAdd' ,
                      'POST' ,
                      array(
                              'api_key'       => 'Your_Plurk_API_Key' ,
                              'qualifier'     => 'likes' , // loves , likes , shares , ...
                              'content'       => '(LOL)' ,
                      ) ,
                      '/tmp/plurk_cookie'
              )
      );




至於更多的 Plurk API 請上官網看囉,用法應該很簡單,只需把上頭的 array 中的變數改一下就行啦,至於回傳資料是 JSON 格式,有需要的請自行去處理囉,上述範例可完成發一則噗浪囉,有些 API 是需要 Login 的,那就要記得先完成一次 Users/login ,將資料存在 cookie 中,才能接著去嘗試其他 API 啦。


剩下的有空再來玩。


實作範例(@2010/01/08更新)



// Users/login
print_r(
        do_act(
                'http://www.plurk.com/API/Users/login' ,
                'POST' ,
                array(
                        'api_key'       => 'cGRgp6VPBY0oNwP47iudVmVHzOlDqzIj' ,
                        'username'      => 'changyy_paper' ,
                        'password'      => 'qazwsxed'
                ) ,
                '/tmp/frank'
        )
);

echo "\n\n";

// Timeline/getPlurks
print_r(
        do_act(
                'http://www.plurk.com/API/Timeline/getPlurks' ,
                'POST' ,
                array(
                        'api_key'       => 'cGRgp6VPBY0oNwP47iudVmVHzOlDqzIj' ,
                        'offset'        => '2009-6-20T21:55:34'
                ) ,
                '/tmp/frank'
        )
);

echo "\n\n";

// Timeline/plurkAdd
print_r(
        do_act(
                'http://www.plurk.com/API/Timeline/plurkAdd' ,
                'POST' ,
                array(
                        'api_key'       => 'cGRgp6VPBY0oNwP47iudVmVHzOlDqzIj' ,
                        'qualifier'     => 'likes' , // loves , likes , shares , ...
                        'content'       => '(LOL)' ,
                ) ,
                '/tmp/frank'
        )
);


2009年12月1日 星期二

[Java] 將連續的 Images (JPEG) 轉成 AVI/MOV

花了非常多的時間看資料,結果成效不彰啊!主要是在找 Java 版的程式庫,但偶爾也會看到幾眼 C 語言版本,兩者交互測試。我想做的事有兩件,第一件事是將連續的 Jpeg 串成影片,第二件事,就是將兩個影片再合併成一個影片,主要用在一些演算法上。


以下是我參考的相關討論,其中 Java Media Framework 中,有很完整的範例程式 JMF 2.1.1 Solutions ,它可以將 jpeg 弄成 mov 影片,並且又提供 merge 的範例,但無論我怎樣試,都沒辦法把兩個 mov 再串成一個 mov ,儘管程式在編譯或執行上都沒有顯示任何問題(除了範例程式使用 Vector 沒有指定 type),但合併的結果卻不是正常的,真讓我十分灰心。





最後,關於 Jpeg 製成 AVI 的部分,我使用 Writing AVI videos using pure Java 這篇提到的 AVIDemo.jar 程式庫,寫得滿乾淨又簡單!


import ch.randelshofer.media.avi.AVIOutputStream;
import java.io.*;
import java.awt.image.BufferedImage ;
import javax.imageio.ImageIO;

class test
{
        public static void main( String[] args ) throws Exception
        {
                if( args.length < 2 )
                {
                        System.out.println( "Usage> test file_out 1.jpg" );
                        System.exit( -1 );
                }
                AVIOutputStream out = null;
                out = new AVIOutputStream( new File( args[0] ) , AVIOutputStream.VideoFormat.JPG );
                out.setVideoCompressionQuality( 1f );
                out.setTimeScale(1);
                out.setFrameRate(1);

                for( int i=1 ; i <args.length ; ++i )
                {
                        out.writeFrame( ImageIO.read(new File( args[i] ) ) );
                }
                out.close();
        }
}


如此一來,就能夠用 java test out.avi 1.jpg 2.jpg 3.jpg 來產生影片啦!


關於 Merge 的部分,暫時沒有 Java 版的成果,以下是相關的參考資料,但我尚未測試成功。



關於將兩個影片串在一起,其實早就有範例可用啦!



看來我的英文真的有待加強!隔行如隔山!下錯關鍵字,成果差很大啦,用上述的範例程式就可以成功把我作的影片合併再一起囉!


如果是 C 的版本,那有 Transcode 可以用喔,裡頭有一支 tool 叫 avimerge ,它可以很成功的將我用上述 Java 程式產生影片合併串在一起。最後,若純粹只是想把 jpeg 串成影片,或將影片再合併起來,滿推薦使用 mencoderTranscode - avimerge ,前者有需要可以再參考這篇 製作 Motion JPEG (Mjpeg) 影片 - 將連續的照片串成影片


[PHP] Motion JPEG - MJPEG/MJPG HTTP streaming

當你有一串連續的 JPEG 圖片時,想串成影片該怎麼辦呢?


因為工作上的需要,稍稍研究一下,從 Motion JPEG - Wikipedia, the free encyclopedia 可以看到關於 M-JPEG over HTTP 的簡介,於是我又試了一下。


PHP 搭配 Apache Web Server 範例:


@img_show.php
<?php
$boundary = 'ca3cdghdujhihal3';
$delay = 1;

header( "Content-Type: multipart/x-mixed-replace; boundary=\"$boundary\"\r\n" );

echo "--$boundary\n" ;

for( $i=1 ; $i<= 10 ; $i++ )
{
        echo 'Content-Type: image/jpeg'."\n\n";

        echo file_get_contents( "/path/images/$i.jpg" );

        echo "\n--$boundary\n" ;

        flush();
        sleep( $delay );
}
?>



此例將會依序呈現出 /path/images/1.jpg ~ /path/images/10.jpg 這些圖片囉,只是很可惜的,IE瀏覽器尚不支援,我在 IE8 也還是不會動。


可以直接用 Firefox 去瀏覽 http://host/path/img_show.php 看到成果,或是用 <img src="http://host/path/img_show.php" /> ,只是 HTML 部份我在 Ubuntun 9.04 + Firefox Shiretoko 3.5.5 此 img tag 並不會看到效果,但在 Windows XP with SP3 + Firefox 3.5.5 倒是正常。


另外,可以再參考 Cambozola Streaming Image Viewer ,這是一個 Java 版 Server Socket 範例,其 source code 中,可以在 src/com/charliemouse/cambozola/server 看到 MJPEG Streaming Server 的範例程式,只需要將該目錄下的 testImages 和 TestServer.java 複製出來,前者是照片的來源,後者是程式,接著把 TestServer.java 最上面的 package 定義清掉,再拿掉 AppID 的部份,基上就變成很簡單的 java code 啦,編譯完執行它,預設在 port 2020  開啟此服務,接著一樣用 http://localhost:2020/ 就會看到動畫效果囉。