2013-02-26 08:37

[JSX] 串接圖片

由於工作上的需要將圖片分類串接成好幾組圖片,所以寫了一個 PhotoShop Scripting 來完成這件事。

圖片名稱是:
a001-01.jpg
a001-02.jpg
a002-01.jpg
a002-02.jpg
b001-01.jpg
b001-02.jpg
b001-03.jpg

而希望可以依 a001.jpg, a002.jpg, b001.jpg 分類串接在一起。

執行方式:
  1. 檔案 -> 指令碼 -> 瀏覽 -> 選擇 photo_join.jsx
  2. 在選擇多張要串接的圖片

/** 圖片串接 for (PhotoShop Script)
 *  author : Jax
 *  email : weskerjax[at]gmail.com
 *  website : http://jax-work-archive.blogspot.com/
 *  history : 2013.02.23
 */
// photo_join.jsx
//#target photoshop


/**水平邊界px*/
var levMargin = 10;

/**垂直邊界px*/
var verMargin = 10;

var WebJpegOptions = new ExportOptionsSaveForWeb()
WebJpegOptions.format = SaveDocumentType.JPEG;
WebJpegOptions.quality=80;

function main(){
    /*取得多個的檔案路徑*/
    var fileList = File.openDialog(
        "開啟 JPG 檔案",
        "JPG Files:*.jpg",
        true
    );
    if(fileList.length == 0){ alert("沒有任何檔案"); return; }

    /*依檔名排序*/
    fileList.sort();

    /*檔案依前綴分群*/
    var groupList = {};
    var filePath, i=0;
    while(filePath=fileList[i++]){
        var prefix=filePath.name.match(/([a-z0-9]+)[-_].+\.jpg/i);
        if(!prefix || !prefix[1]){continue;}
        if(!groupList[prefix[1]]){ groupList[prefix[1]]=[]; }
        groupList[prefix[1]].push(filePath);
    }

    /*依分群串接圖片*/
    for(prefix in groupList){
        photoJoin(prefix,groupList[prefix]);
    }
}


function copyArt(toArt, fromArt){
    /*複製圖層*/
    app.activeDocument=fromArt;
    fromArt.selection.selectAll()
    fromArt.selection.copy()

    /*貼上圖層*/
    app.activeDocument=toArt;
    return toArt.paste();
}


function photoJoin(prefix, fileList){
    /*新增目標圖片文件*/
    var newPic = app.documents.add(
        1, 1, 72,
        prefix,
        NewDocumentMode.RGB,
        DocumentFill.TRANSPARENT
    );

    workSpace=fileList[0].parent.fsName.replace(/\\/ig,'/');;

    var height=0;
    var width = newPic.width;
    var correspond = [];

    /*複製所有圖檔至新建立的圖檔*/
    for (var i=0; i<fileList.length; i++){
        try {
            var atDoc=open(new File(fileList[i]));
        } catch (e) { continue; }

        /*累計高度*/
        height += atDoc.height.value + verMargin;
        /*最大寬度*/
        width = Math.max(width,atDoc.width);

        /*複製圖層*/
        var layer = copyArt(newPic, atDoc);
        correspond.push({
            'layer':layer,
            'height':atDoc.height.value
        });

        /*關閉檔案*/
        atDoc.close(SaveOptions.DONOTSAVECHANGES);
    };

    app.activeDocument=newPic;

    /*變更圖片大小*/
    newPic.resizeCanvas(
        width+levMargin*2,
        height+verMargin,
        AnchorPosition.TOPLEFT
    );

    /*變更圖層定位*/
    var top=verMargin;
    for (var i=0,l=correspond.length; i<l; i++){
        if(correspond[i].layer===undefined){continue;}

        correspond[i].layer.translate(levMargin, top); /*移動圖層*/
        top += correspond[i].height+verMargin; /*定位累計*/
    };

    /*匯出網頁JPEG*/
    newPic.exportDocument(
        new File(File.encode(workSpace+"/"+prefix+".jpg")),
        ExportType.SAVEFORWEB,
        WebJpegOptions
    );

    /*關閉 newArt 文件*/
    newPic.close(SaveOptions.DONOTSAVECHANGES);
}

/*把Photoshop推到最上層*/
app.bringToFront();
/*設定使用的單位為「像素(Pixel)」*/
app.preferences.rulerUnits = Units.PIXELS;
/*執行主程式*/
main();

alert('成功執行!');


2012-09-30 22:24

[轉載][PHP] preg_replace 效能測試 (將兩個空白字元以上取代成一個)

轉載自:[PHP] preg_replace 效能測試 (將兩個空白字元以上取代成一個) 小惡魔 – 電腦技術 – 工作筆記 – AppleBOY

preg_replace 可以使用正規語法來取代字串任何字元,,今天探討取代空白字元的效能,雖然這是個不起眼的效能評估,一般人不太會這樣去改,不過這是國外 PHP Framework 有人提出來修正的,經過許多人的測試一致同意。功能就是一篇文章內如果有多餘的空白能空取代成一個,一般人都會用 \s+ 正規語法,畢竟大家都知道 \s 代表單一空白或 \r 等符號,但是國外有人提出用 {2,} 方式來取代空白。程式碼如下,大家可以測試看看。

<?php
$nb = 10000;
$str = str_repeat('Hi, I am appleboy  ' . "\n", 10);
$t1 = microtime(true);
for ($i = $nb; $i--; ) {
    preg_replace('/\s+/', ' ', $str);
}
$t2 = microtime(true);
for ($i = $nb; $i--; ) {
    preg_replace('/ {2,}/', ' ', 
        str_replace(array("\r","\n","\t","\x0B","\x0C"),' ',$str)
    );
}
$t3 = microtime(true);

echo $t2 - $t1;
echo "\n";
echo $t3 - $t2;

測試結果(1萬次)

PHP 5.3.3
old: 0.13053798675537
new: 0.058536052703857

PHP 5.3.15
old: 0.11732506752014
new: 0.071418046951294

PHP 5.3.17
old: 0.11612010002136
new: 0.07065486907959

PHP 5.4.5
old: 0.1185781955719
new: 0.066012859344482

PHP 5.4.7
old: 0.11343121528625
new: 0.066931962966919

結論至少快蠻多的,如果整體資料量再大一點,我想差別會更大,那至於要不要用呢,就看個人了 XD。
2012-09-05 13:32

Apache 消失的 ServerLimit 參數

在某些版本的 Apache 中如果單純加大 MaxClients 的設定值,就會得到以下的錯誤訊息:
WARNING: MaxClients of 300 exceeds ServerLimit value of 256 servers,
  lowering MaxClients to 256. To increase, please see the ServerLimit
  directive.

這時必須再增加 ServerLimit 這個設定,而這個設定在設定檔中並沒有提示,所以需要額外加入。
<IfModule mpm_prefork_module>
    ServerLimit         500 # 額外加入
    StartServers         10
    MinSpareServers      10
    MaxSpareServers      20
    MaxClients          300
    MaxRequestsPerChild   0
</IfModule>

停止並啟動(單純 restart 無法套用設定)
service apache2 stop
service apache2 start

參考來源:
Gilbert.tw > Apache 之謎樣 ServerLimit 指令 (directive)
2012-09-05 13:01

[Ubuntu] 用 logrotate 定期切割 Apache log 檔

Ubuntu 預設已經為 Apache 建立了 log 分割管理,但在建立虛擬主機(Virtual Host),但又很龜毛的將 log 獨立到個別的子目錄下的話,預設的 logrotate 是沒有辦法處理到的,所以請記得為這龜毛的動作加上額外設定。

編輯屬於 Apache 的設定檔,在檔案最後面加上額外的設定。
vim /etc/logrotate.d/apache2
/var/log/apache2/blog/*.log {
    daily
    missingok
    rotate 52
    dateext
    compress
    delaycompress
    notifempty
    create 640 root adm
    sharedscripts
    postrotate
        /etc/init.d/apache2 reload > /dev/null
    endscript
}

參數說明:
daily 每天整理,其他還有 weekly, monthly, yearly
missingok 允許 log 檔不存在
nomissingok 不允許 log 檔不存在,會丟出錯誤 (預設)
rotate 7 保留 7 次的分割
size 1M 大小超過 1M 就執行分割,單位可以是 1024, 100k, 4M, 1G
dateext 用日期來建立分割檔名,而非流水號
dateformat -%Y%m%d 日期格式
compress 壓縮分割,預設用 gzip (預設)
nocompress 不壓縮分割
delaycompress 延後壓縮直到下一次 rotate
notifempty 如果 log 是空的就不執行
copytruncate 以[複製清空]的方式分割 log,這樣可以用不到 postrotate 設定
mail xxx@address 將分割內容用 mail 寄出
prerotate/endscript 分割前要執行的指令
postrotate/endscript 分割後要執行的指令


測試設定檔是否正確
logrotate -f /etc/logrotate.conf


參考來源:
使用 logrotate 定期整理 Rails Log 檔案
[ubuntu] 使用 logrotate 定期切割壓縮 apache2 的 log 檔
2012-09-05 11:46

[轉載] [MySQL] QueryCache原理

轉載自:PHP 程式 學習 筆記本 [Mysql]QueryCache原理

原理
QueryCache(下面簡稱QC)是根據SQL語句來cache的。一個SQL查詢如果以select開頭,那麼MySQL服務器將嘗試對其使用QC。每個Cache都是以SQL文本作為key來存的。在應用QC之前,SQL文本不會被作任何處理。也就是說,兩個SQL語句,只要相差哪怕是一個字符(例如大小寫不一樣;多一個空格等),那麼這兩個SQL將使用不同的一個CACHE。

不過SQL文本有可能會被客戶端做一些處理。例如在官方的命令行客戶端裡,在發送SQL給服務器之前,會做如下處理:
  • 過濾所有註釋
  • 去掉SQL文本前後的空格,TAB等字符。注意,是文本前面和後面的。中間的不會被去掉。


下面的三條SQL裡,因為SELECT大小寫的關係,最後一條和其他兩條在QC裡肯定是用的不一樣的存儲位置。而第一條和第二條,區別在於後者有個註釋,在不同客戶端,會有不一樣的結果。所以,保險起見,請儘量不要使用動態的註釋。在PHP的mysql擴展裡,SQL的註釋是不會被去掉的。也就是三條SQL會被存儲在三個不同的緩存裡,雖然它們的結果都是一樣的
SELECT * FROM people where name='surfchen';
SELECT * FROM people where /*hey~*/name='surfchen';
SELECT * FROM people where name='surfchen';


目前只有select語句會被cache,其他類似show,use的語句則不會被cache。

因為QC是如此前端,如此簡單的一個緩存系統,所以如果一個表被更新,那麼和這個表相關的SQL的所有QC都會被失效。假設一個聯合查詢裡涉及到了表A和表B,如果表A或者表B的其中一個被更新(update或者delete),這個查詢的QC將會失效。

也就是說,如果一個表被頻繁更新,那麼就要考慮清楚究竟是否應該對相關的一些SQL進行QC了。一個被頻繁更新的表如果被應用了QC,可能會加重數據庫的負擔,而不是減輕負擔。我一般的做法是默認打開QC,而對一些涉及頻繁更新的表的SQL語句加上SQL_NO_CACHE關鍵詞來對其禁用CACHE。這樣可以儘可能避免不必要的內存操作,儘可能保持內存的連續性。

那些查詢很分散的SQL語句,也不應該使用QC。例如用來查詢用戶和密碼的語句——「select pass from user where name='surfchen'」。這樣的語句,在一個系統裡,很有可能只在一個用戶登陸的時候被使用。每個用戶的登陸所用到的查詢,都是不一樣的SQL文本,QC在這裡就幾乎不起作用了,因為緩存的數據幾乎是不會被用到的,它們只會在內存裡佔地方。


存儲塊
在本節裡「存儲塊」和「block」是同一個意思
QC緩存一個查詢結果的時候,一般情況下不是一次性地分配足夠多的內存來緩存結果的。而是在查詢結果獲得的過程中,逐塊存儲。當一個存儲塊被填滿之後,一個新的存儲塊將會被創建,並分配內存(allocate)。單個存儲塊的內存分配大小通過query_cache_min_res_unit參數控制,默認為4KB。最後一個存儲塊,如果不能被全部利用,那麼沒使用的內存將會被釋放。如果被緩存的結果很大,那麼會可能會導致分配內存操作太頻繁,系統系能也隨之下降;而如果被緩存的結果都很小,那麼可能會導致內存碎片過多,這些碎片如果太小,就很有可能不能再被分配使用。

除了查詢結果需要存儲塊之外,每個SQL文本也需要一個存儲塊,而涉及到的表也需要一個存儲塊(表的存儲塊是所有線程共享的,每個表只需要一個存儲塊)。存儲塊總數量=查詢結果數量*2+涉及的數據庫表數量。也就是說,第一個緩存生成的時候,至少需要三個存儲塊:表信息存儲塊,SQL文本存儲塊,查詢結果存儲塊。而第二個查詢如果用的是同一個表,那麼最少只需要兩個存儲塊:SQL文本存儲塊,查詢結果存儲塊。

通過觀察Qcache_queries_in_cacheQcache_total_blocks可以知道平均每個緩存結果占用的存儲塊。它們的比例如果接近1:2,則說明當前的query_cache_min_res_unit參數已經足夠大了。如果Qcache_total_blocks比Qcache_queries_in_cache多很多,則需要增加query_cache_min_res_unit的大小。

Qcache_queries_in_cache * query_cache_min_res_unit(sql文本和表信息所在的block佔用的內存很小,可以忽略)如果遠遠大於query_cache_size-Qcache_free_memory,那麼可以嘗試減小query_cache_min_res_unit的值。


調整大小
如果Qcache_lowmem_prunes增長迅速,意味著很多緩存因為內存不夠而被釋放,而不是因為相關表被更新。嘗試加大query_cache_size,儘量使Qcache_lowmem_prunes零增長。


啟動參數
show variables like 'query_cache%' 可以看到這些信息。
query_cache_limit:如果單個查詢結果大於這個值,則不Cache
query_cache_size:分配給QC的內存。如果設為0,則相當于禁用QC。要注意QC必須使用大約40KB來存儲它的結構,如果設定小於40KB,則相當于禁用QC。QC存儲的最小單位是1024 byte,所以如果你設定了一個不是1024的倍數的值,這個值會被四捨五入到最接近當前值的等於1024的倍數的值。
query_cache_type:
0 完全禁止QC,不受SQL語句控制(另外可能要注意的是,即使這裡禁用,上面一個參數所設定的內存大小還是會被分配);
1啟用QC,可以在SQL語句使用SQL_NO_CACHE禁用
2可以在SQL語句使用SQL_CACHE啟用。

query_cache_min_res_unit:每次給QC結果分配內存的大小


狀態
show status like 'Qcache%' 可以看到這些信息。
Qcache_free_blocks:當一個表被更新之後,和它相關的cache blocks將被free。但是這個block依然可能存在隊列中,除非是在隊列的尾部。這些blocks將會被統計到這個值來。可以用FLUSH QUERY CACHE語句來清空free blocks。

其他幾個狀態變量的意義:
Qcache_free_memory 表示查詢緩存區現在還有多少的可用內存,如果很小,考慮增加query_cache_size
Qcache_hits 表示查詢緩存區的命中個數,也就是直接從查詢緩存區作出響應處理的查詢個數
Qcache_inserts 表示查詢緩存區此前總過緩存過多少條查詢命令的結果
Qcache_lowmem_prunes 表示查詢緩存區已滿而從其中溢出和刪除的查詢結果的個數
Qcache_not_cached 自mysql進程啟動起,沒有被cache的只讀查詢數量(包括select,show,use,desc等)
Qcache_queries_in_cache 當前被cache的SQL數量
Qcache_total_blocks:在QC中的blocks數。一個query可能被多個blocks存儲,而這幾個blocks中的最後一個,未用滿的內存將會被釋放掉。例如一個QC結果要佔6KB內存,如果query_cache_min_res_unit是4KB,則最後將會生成3個blocks,第一個block用來存儲sql語句文本,這個不會被統計到query+cache_size裡,第二個block為4KB,第三個block為2KB(先allocate4KB,然後釋放多餘的2KB)。每個表,當第一個和它有關的SQL查詢被CACHE的時候,會使用一個block來存儲表信息。也就是說,block會被用在三處地方:表信息,SQL文本,查詢結果。

優化提示:如果Qcache_lowmem_prunes 值比較大,表示查詢緩存區大小設置太小,需要增大。如果Qcache_free_blocks 較多,表示內存碎片較多,需要清理,flush query cache

根據我看的 《High Performance MySQL》中所述,關於query_cache_min_res_unit大小的調優,書中給出了一個計算公式,可以供調優設置參考:
query_cache_min_res_unit = (query_cache_size - Qcache_free_memory) / Qcache_queries_in_cache

還要注意一點的是,FLUSH QUERY CACHE 命令可以用來整理查詢緩存區的碎片,改善內存使用狀況,但不會清理查詢緩存區的內容,這個要和RESET QUERY CACHE相區別,不要混淆,後者才是清除查詢緩存區中
的所有的內容。
2012-06-04 01:29

[轉載] 自適應網頁設計

轉載自:自适应网页设计(Responsive Web Design) 阮一峰

随着 3G 的普及,越来越多的人使用手机上网。

移动设备正超过桌面设备,成为访问互联网的最常见终端。于是,网页设计师不得不面对一个难题:如何才能在不同大小的设备上呈现同样的网页?



手机的屏幕比较小,宽度通常在 600 像素以下,PC 的屏幕宽度,一般都在 1000 像素以上(目前主流宽度是1366×768),有的还达到了 2000 像素。同样的内容,要在大小迥异的屏幕上,都呈现出满意的效果,并不是一件容易的事。

很多网站的解决方法,是为不同的设备提供不同的网页,比如专门提供一个 mobile 版本,或者 iPhone / iPad 版本。这样做固然保证了效果,但是比较麻烦,同时要维护好几个版本,而且如果一个网站有多个 portal(入口),会大大增加架构设计的复杂度。

于是,很早就有人设想,能不能"一次设计,普遍适用",让同一张网页自动适应不同大小的屏幕,根据屏幕宽度,自动调整布局(layout)?




一、"自适应网页设计"的概念

2010 年,Ethan Marcotte 提出了"自适应网页设计"(Responsive Web Design)这个名词,指可以自动识别屏幕宽度、并做出相应调整的网页设计。

他制作了一个范例,里面是《福尔摩斯历险记》六个主人公的头像。如果屏幕宽度大于 1300 像素,则 6 张图片并排在一行。



如果屏幕宽度在 600 像素到 1300 像素之间,则 6 张图片分成两行。



如果屏幕宽度在 400 像素到 600 像素之间,则导航栏移到网页头部。



如果屏幕宽度在 400 像素以下,则 6 张图片分成三行。



mediaqueri.es 上面有更多这样的例子。

这里还有一个测试小工具,可以在一张网页上,同时显示不同分辨率屏幕的测试效果,我推荐安装。


二、允许网页宽度自动调整

"自适应网页设计"到底是怎么做到的?其实并不难。

首先,在网页代码的头部,加入一行 viewport元标签

<meta name="viewport" content="width=device-width, initial-scale=1" />

viewport 是网页默认的宽度和高度,上面这行代码的意思是,网页宽度默认等于屏幕宽度(width=device-width),原始缩放比例(initial-scale=1)为 1.0,即网页初始大小占屏幕面积的 100%。

所有主流浏览器都支持这个设置,包括 IE9。对于那些老式浏览器(主要是 IE6、7、8),需要使用 css3-mediaqueries.js

<!--[if lt IE 9]>
    <script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script>
<![endif]-->


三、不使用绝对宽度

由于网页会根据屏幕宽度调整布局,所以不能使用绝对宽度的布局,也不能使用具有绝对宽度的元素。这一条非常重要。

具体说,CSS代码不能指定像素宽度:

width: xxx px;

只能指定百分比宽度:

width: xx%;

或者

width: auto;


四、相对大小的字体

字体也不能使用绝对大小(px),而只能使用相对大小(em)。

body {
    font: normal 100% Helvetica, Arial, sans-serif;
}

上面的代码指定,字体大小是页面默认大小的 100%,即 16 像素。

h1 {
    font-size: 1.5em; 
}

然后,h1 的大小是默认大小的 1.5 倍,即 24 像素(24/16=1.5)。

small {
    font-size: 0.875em;
}

small 元素的大小是默认大小的 0.875 倍,即 14 像素(14/16=0.875)。


五、流动布局(fluid grid)

"流动布局"的含义是,各个区块的位置都是浮动的,不是固定不变的。

.main {
    float: right;
    width: 70%; 
}

.leftBar {
    float: left;
    width: 25%;
}

float 的好处是,如果宽度太小,放不下两个元素,后面的元素会自动滚动到前面元素的下方,不会在水平方向 overflow(溢出),避免了水平滚动条的出现。

另外,绝对定位(position: absolute)的使用,也要非常小心。


六、选择加载CSS

"自适应网页设计"的核心,就是 CSS3 引入的 Media Query 模块。

它的意思就是,自动探测屏幕宽度,然后加载相应的 CSS 文件。

<link rel="stylesheet" type="text/css" media="screen and (max-device-width: 400px)" href="tinyScreen.css" />

上面的代码意思是,如果屏幕宽度小于 400 像素(max-device-width: 400px),就加载 tinyScreen.css 文件。

<link rel="stylesheet" type="text/css" media="screen and (min-width: 400px) and (max-device-width: 600px)" href="smallScreen.css" />

如果屏幕宽度在 400 像素到 600 像素之间,则加载 smallScreen.css 文件。

除了用 html 标签加载 CSS 文件,还可以在现有 CSS 文件中加载。

@import url("tinyScreen.css") screen and (max-device-width: 400px);


七、CSS 的 @media 规则

同一个 CSS 文件中,也可以根据不同的屏幕分辨率,选择应用不同的 CSS 规则。

@media screen and (max-device-width: 400px) {
    .column {
        float: none;
        width: auto;
    }
    #sidebar {
        display: none;
    }
}

上面的代码意思是,如果屏幕宽度小于 400 像素,则 column 块取消浮动(float:none)、宽度自动调节(width:auto),sidebar 块不显示(display:none)。


八、图片的自适应(fluid image)

除了布局和文本,"自适应网页设计"还必须实现图片的自动缩放

这只要一行 CSS 代码:

img { max-width: 100%; }

这行代码对于大多数嵌入网页的视频也有效,所以可以写成:

img, object { max-width: 100%; }

老版本的 IE 不支持 max-width,所以只好写成:

img { width: 100%; }

此外,windows 平台缩放图片时,可能出现图像失真现象。这时,可以尝试使用 IE 的专有命令:

img { -ms-interpolation-mode: bicubic; }

或者,Ethan Marcotte 的 imgSizer.js

addLoadEvent(function() {
    var imgs = document.getElementById("content").getElementsByTagName("img");
    imgSizer.collate(imgs);
});

不过,有条件的话,最好还是根据不同大小的屏幕,加载不同分辨率的图片。有很多方法可以做到这一条,服务器端和客户端都可以实现。

(完)
2012-05-05 01:53

[轉載] 让PHP更快的提供文件下载

作者:Laruence
本文地址:http://www.laruence.com/2012/05/02/2613.html

一般来说, 我们可以通过直接让URL指向一个位于Document Root下面的文件, 来引导用户下载文件.

但是, 这样做, 就没办法做一些统计, 权限检查, 等等的工作. 于是, 很多时候, 我们采用让PHP来做转发, 为用户提供文件下载.

<?php
    $file = "/tmp/dummy.tar.gz";
    header("Content-type: application/octet-stream");
    header('Content-Disposition: attachment; filename="' 
        . basename($file) . '"');
    header("Content-Length: ". filesize($file));
    readfile($file);

但是这个有一个问题, 就是如果文件是中文名的话, 有的用户可能下载后的文件名是乱码.

于是, 我们做一下修改(参考: :

<?php
    $file = "/tmp/中文名.tar.gz";
 
    $filename = basename($file);
 
    header("Content-type: application/octet-stream");
 
    //处理中文文件名
    $ua = $_SERVER["HTTP_USER_AGENT"];
    $encoded_filename = urlencode($filename);
    $encoded_filename = str_replace("+", "%20", $encoded_filename);
    if (preg_match("/MSIE/", $ua)) {
        header('Content-Disposition: attachment; filename="'
            . $encoded_filename . '"');
    } else if (preg_match("/Firefox/", $ua)) {
        header("Content-Disposition: attachment; filename*=\"utf8''"
            . $filename . '"');
    } else {
        header('Content-Disposition: attachment; filename="'
            . $filename . '"');
    }
 
    header('Content-Disposition: attachment; filename="'
        . $filename . '"');
    header("Content-Length: ". filesize($file));
    readfile($file);

恩, 现在看起来好多了, 不过还有一个问题, 那就是readfile, 虽然PHP的readfile尝试实现的尽量高效, 不占用PHP本身的内存, 但是实际上它还是需要采用MMAP(如果支持), 或者是一个固定的buffer去循环读取文件, 直接输出.

输出的时候, 如果是Apache + PHP mod, 那么还需要发送到Apache的输出缓冲区. 最后才发送给用户. 而对于Nginx + fpm如果他们分开部署的话, 那还会带来额外的网络IO.

那么, 能不能不经过PHP这层, 直接让Webserver直接把文件发送给用户呢?

今天, 我看到了一个有意思的文章: How I PHP: X-SendFile.

我们可以使用Apache的module mod_xsendfile, 让Apache直接发送这个文件给用户:

<?php
    $file = "/tmp/中文名.tar.gz";
 
    $filename = basename($file);
 
    header("Content-type: application/octet-stream");
 
    //处理中文文件名
    $ua = $_SERVER["HTTP_USER_AGENT"];
    $encoded_filename = urlencode($filename);
    $encoded_filename = str_replace("+", "%20", $encoded_filename);
    if (preg_match("/MSIE/", $ua)) {
        header('Content-Disposition: attachment; filename="'
            . $encoded_filename . '"');
    } else if (preg_match("/Firefox/", $ua)) {
        header("Content-Disposition: attachment; filename*=\"utf8''"
            . $filename . '"');
    } else {
        header('Content-Disposition: attachment; filename="'
            . $filename . '"');
    }
 
    header('Content-Disposition: attachment; filename="'
            . basename($file) . '"');
 
    //让Xsendfile发送文件
    header("X-Sendfile: $file");

X-Sendfile头将被Apache处理, 并且把响应的文件直接发送给Client.

Lighttpd和Nginx也有类似的模块, 大家有兴趣的可以去找找看


mod-xsendfile 在 Ubuntu 的安裝方法:
sudo apt-get install libapache2-mod-xsendfile
2012-05-05 01:27

[PHP] 使用 php5-ffmpeg 擷取影片圖片

前幾天在玩 FFmpeg 的時後,突然發現 Ubuntu 上多了 php5-ffmpeg 這個擴充套件,就想來玩玩看,看好不好用,有兩個結論:
  1. 讀取影片取決於 FFmpeg 的支援性,如果想要什麼格式都支援的話,建議自己重新編譯 FFmpeg。
  2. 效率並沒有我想像中的快,兩分鐘的影片取十張圖,大約 30 秒。
安裝方法:
sudo apt-get install ffmpeg php5-ffmpeg php5-gd

擷圖測試範例:
<?php
$page = 10;
$prefix = 'screencap';

$mov = new ffmpeg_movie('gt.avi');
$count = $mov->getFrameCount();
$range = (int)round($count/($page+1));

for($i=1; $i<=$page; $i++){
    $frameNum = $range*$i;
    $imgFile = $prefix.'_'.$i.'.png';

    $frame = $mov->getFrame($frameNum);
    if(!$frame){ continue; }

    $gdImage = $frame->toGDImage();
    if(!$gdImage){ continue; }

    imagepng($gdImage, $imgFile);
    imagedestroy($gdImage);

    echo '<img src="'.$imgFile.'" border="1" /><br />';
}

參考文件:
ffmpeg_movie object methods
FFmpeg and PHP