2015-07-11 77 views
0

所以我在我的Raspberry Pi上有這個程序,應該定期將我的博客備份到它上面。我目前正在手動運行它。今天我的互聯網連接非常緩慢,所以我在下載過程中殺死了該程序。但它保存了所有下載的數據,現在我的程序讀取圖像存在並跳過它。當然,我可以刪除它,讓程序爲我重新下載它,但我想確保這種情況在未來不會再發生。如何檢查圖像是否不完整(缺少數據)?

我在服務器端使用PHP。我用來保存圖像的命令是

copy($url, $path); 

我正在做一個非常簡單的檢查,如果文件存在。

if(!file_exists($path)) 

我的服務器上的圖像文件是PNG和JPG文件格式。


啞巴我,我忘了寫我曾試過的。我發現了像這樣的多個問題,但他們的解決方案似乎不起作用。他們都聲稱在這些情況下imagecreatefromtype($img)應該返回false。

PHP手冊:

成功返回的圖像資源標識符,在錯誤FALSE。

我得到了「JPEG文件過早結束」,它似乎應該返回false,但它沒有。它返回相同的值,如果圖像未損壞,

Resource id #6 

這將是巨大的,具有某種確定的快捷方式,如果圖像是全部或沒有。

+0

我不知道代碼的哪一部分決定是否下載文件,但您可以嘗試檢查文件大小以及修改時間。這至少應該抓住部分寫道。 –

+0

我只是使用if(!file_exists($ path))。現在通過檢查文件大小是什麼意思?是否可以在不完全下載的情況下知道外部圖像的文件大小?我有這樣的想法,即運行多個測試來計算文件大小,通過寬度,高度和位圖來提供寬度,高度和位圖。但是這也可能會導致錯誤的結果,因爲我不知道確切的位深度。或者我可以用相同的格式計算所有圖像的平均比特深度,然後將其與其他圖像進行比較,看看它們有多少不同。但不同的壓縮壽命。 – Aistis

+1

你可以做一個頭部請求來獲取實際的文件大小,並使用php文件大小在本地進行檢查。這也可以爲您提供修改日期。 –

回答

2

您可以下載到臨時文件(在同一分區上),並在下載完成後重命名該文件。重命名文件是一個原子操作 - 只要源和目標位於同一分區上 - 這將確保圖像有效。

像這樣:

// Create a tempfile 
$tempfile = tempnam("/path/to/tempfolder", "download"); 

// Download to a tempfile 
$ret = copy($url, $tempfile); 

if($ret) { 
    // Move tempfile to final location. 
    // This is an atomic operation (with the restrictions named above) 
    rename($tempfile, "path/to/image.png"); 
} else { 
    unlink($tempfile); 
    die("Download broken"); 
} 

即使副本未完成 - 因爲停電或什麼的,最終的圖像不會在一個破碎的狀態越來越創建的。

+0

這是一個簡單而強大的解決方案。這將真正防止未來發生這種情況。這對我很有幫助,但最初的問題是如何確定圖像是否損壞/缺失數據。儘管我想接受這個答案,但它並不回答給定的問題:c – Aistis

+0

爲什麼它應該在'copy'成功時被破壞? – hek2mgl

+0

我不是說它不會,我只是有興趣看看是否有方法來檢查圖像是否損壞或不是原來的問題。我完全同意你的代碼可以防止這種情況在將來再次發生,但是我有興趣知道如果圖像已經在我的服務器上被破壞,如何減少這種情況。我目前有大約7000張圖像,這些圖像以非常原始的方式下載。也可能有其他類似的圖像,所以最好不要單獨下載它們中的每一個,並檢查它們是否匹配。 – Aistis

1

在你的問題中,你使用了一個PNG圖像,它有一個可以檢查的校驗和(CRC32)。
如果校驗和通過,那麼很可能圖像是整體。

的PNG規格:http://www.w3.org/TR/PNG/#5CRC-algorithm

+0

我的服務器上大部分都是JPG格式。與Google顯示的一樣,JPG文件在其中沒有任何校驗和。但是這對於PNG文件來說是個好主意。 – Aistis

+0

您可以隨時將自己的校驗和添加到JPEG中。 –

+1

這可能會起作用。如果我下載一個圖像,並在下載完成後,我會進行校驗並將該記錄添加到數據庫中。然後我會知道該圖像是否已成功下載。雖然這需要在數據庫中添加一個表格,並且該表格將來幾乎沒有用處。 hek2mgl的答案在這種情況下可能仍然更好,儘管這樣做仍然只能防止未來發生,並且不會幫助我識別我(可能)在我的服務器上已經存在的破碎圖像。 – Aistis

0

我想我設法想出一個解決方案。這是一個應該檢測不完整圖像文件的代碼。 僅支持PNG和JPEG格式,因爲我目前不需要其他格式支持。它通過檢查JPEG的SOI和EOI以及PNG的IDHR和IEND來工作。

您可以將兩個參數傳遞給此函數 - 文件名以及JPEG本身是否有更多的JPEG。

如果在調用函數時指定了$jpeg_in_jpeg,則會執行較慢的腳本來檢查SOI計數是否等於EOI計數,因此文件是整個文件。雖然這隻會在JPEG內部EOI文件結束時才需要,但是您需要非常不幸纔會發生這種情況。


更新:我意識到解析所有的數據爲字節和到一個數組,然後比較值是極其緩慢的。 1.145 MB的JPEG圖像將在26秒內被檢查!但現在我把它改爲preg_match_all(),現在它快了800倍。有問題的數字是(以秒爲單位):

26,64180707931471(舊方法)/ 0.032716035842896(新方法)= 814,3348175570528(倍數更快)。

如果您真的需要速度,並且不認爲您會因爲文件以內部JPEG的EOI結束而感到非常不幸,請使用更快速的方法,方法是不指定$jpeg_in_jpeg。與新的$jpeg_in_jpeg方法相比,速度將增加約2,6倍。的數字是(秒):

0.032716035842896($jpeg_in_jpeg = true)/ 0.012523889541626($jpeg_in_jpeg未指定)= 2,612290353907259(倍的速度)

記住這是所有測試在樹莓裨乙模型。在普通服務器上,該函數的執行時間應該縮短很多。

function isImageComplete($file_name, $jpeg_in_jpeg = null){ 
    $image_type = @exif_imagetype($file_name); 

    if($image_type) 
     $data = file_get_contents($file_name); 

    if($image_type == IMAGETYPE_JPEG){ 
     if($jpeg_in_jpeg){ 
      #Note: Some JPEG images have even more JPEGs inside of them (have multiple SOI and EOI). This check is slow, though eliminates the very small chance of detecting thumbnail's EOI as the file's ending. 

      $soi = chr(255).chr(216); 
      $eoi = chr(255).chr(217); 

      $results = preg_match_all("/$soi|$eoi/", $data, $out, PREG_PATTERN_ORDER); 

      $soi_count = 0; 
      $eoi_count = 0; 

      foreach($out[0] as $o) 
       if(ord($o[0]).ord($o[1]) == "255216") 
        $soi_count++; 
       elseif(ord($o[0]).ord($o[1]) == "255217") 
        $eoi_count++; 

      if($soi_count == $eoi_count && $soi_count > 1) 
       return 1; 
      else 
       return 0; 
     } 
     else{ 
      $soi = substr($data, 0, 2); 
      $eoi = substr($data, -2); 

      $pair_count = 0; 

      if(ord($soi[0]).ord($soi[1]) == "255216") 
       $pair_count++; 
      if(ord($eoi[0]).ord($eoi[1]) == "255217") 
       $pair_count++; 

      if($pair_count == 2) 
       return 1; 
      else 
       return 0; 
     } 
    } 
    elseif($image_type == IMAGETYPE_PNG) { 
     $a_idhr = array(); 
     $a_iend = array(); 

     $idhr = substr($data, 0, 8); 
     $iend = substr($data, -12); 

     foreach(str_split($idhr) as $char){ 
      array_push($a_idhr, ord($char)); 
     } 

     foreach(str_split($iend) as $char){ 
      array_push($a_iend, ord($char)); 
     } 

     if(implode('', $a_idhr) == '13780787113102610' && implode('', $a_iend) == '0000736978681746696130') 
      return 1; 
     else 
      return 0; 
    } 
    else{ 
     return -1; #File format not supported by the function. 
    } 
} 

雖然我RPI玩弄,我發現我有JPEG文件內的另一個JPEG圖像,雖然exif_thumbnail()沒有返回任何東西。我想你可以試着檢查一下exif_thumbnail()是否會返回一些內容,然後再使用較慢的$jpeg_in_jpeg。但是,正如我發現的,它不會返回內部的JPEG。這可能是因爲它沒有被當作縮略圖,而是被當作別的東西。請記住,這是我第一次鑽研圖像文件格式,所以我知道的很少。