2015-07-11 78 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。这可能是因为它没有被当作缩略图,而是被当作别的东西。请记住,这是我第一次钻研图像文件格式,所以我知道的很少。