PHP x86 ¿Cómo obtener tamaño de archivo de> 2 GB sin programa externo?



6 Answers

Empecé un proyecto llamado https://github.com/jkuchar/BigFileTools . Está proven que funciona en Linux, Mac y Windows (incluso en variantes de 32 bits). Proporciona resultados precisos por bytes incluso para archivos de gran tamaño (> 4 GB). Internamente usa brick/math - biblioteca aritmética de precisión arbitraria.

Instálalo usando el composer .

composer install jkuchar/BigFileTools

y úsala:

<?php
$file = BigFileTools\BigFileTools::createDefault()->getFile(__FILE__);
echo $file->getSize() . " bytes\n";

El resultado es BigInteger para que pueda calcular con los resultados

$sizeInBytes = $file->getSize();
$sizeInMegabytes = $sizeInBytes->toBigDecimal()->dividedBy(1024*1024, 2, \Brick\Math\RoundingMode::HALF_DOWN);    
echo "Size is $sizeInMegabytes megabytes\n";

Big File Tools internamente utiliza controladores para determinar de manera confiable el tamaño exacto del archivo en todas las plataformas. Aquí está la lista de controladores disponibles (actualizado el 2016-02-05)

| Driver           | Time (s) ↓          | Runtime requirements | Platform 
| ---------------  | ------------------- | --------------       | ---------
| CurlDriver       | 0.00045299530029297 | CURL extension       | -
| NativeSeekDriver | 0.00052094459533691 | -                    | -
| ComDriver        | 0.0031449794769287  | COM+.NET extension   | Windows only
| ExecDriver       | 0.042937040328979   | exec() enabled       | Windows, Linux, OS X
| NativeRead       | 2.7670161724091     | -                    | -

Puede utilizar BigFileTools con cualquiera de estos o el más rápido disponible se elige de forma predeterminada ( BigFileTools::createDefault() )

 use BigFileTools\BigFileTools;
 use BigFileTools\Driver;
 $bigFileTools = new BigFileTools(new Driver\CurlDriver());
Question

Necesito obtener el tamaño de archivo de un archivo de más de 2 GB de tamaño. (prueba en un archivo de 4.6 GB). ¿Hay alguna manera de hacer esto sin un programa externo?

Estado actual:

  • filesize() , stat() y fseek() fallan
  • fread() y feof() funciona

Existe la posibilidad de obtener el tamaño del archivo leyendo el contenido del archivo (¡extremadamente lento!).

$size = (float) 0;
$chunksize = 1024 * 1024;
while (!feof($fp)) {
    fread($fp, $chunksize);
    $size += (float) $chunksize;
}
return $size;

Sé cómo obtenerlo en plataformas de 64 bits (usando fseek($fp, 0, SEEK_END) y ftell() ), pero necesito una solución para la plataforma de 32 bits.

Solución: comencé un proyecto de código abierto para esto.

Big File Tools

Big File Tools es una colección de hacks que se necesitan para manipular archivos de más de 2 GB en PHP (incluso en sistemas de 32 bits).




Escribí una función que devuelve el tamaño del archivo exactamente y es bastante rápido:

function file_get_size($file) {
    //open file
    $fh = fopen($file, "r"); 
    //declare some variables
    $size = "0";
    $char = "";
    //set file pointer to 0; I'm a little bit paranoid, you can remove this
    fseek($fh, 0, SEEK_SET);
    //set multiplicator to zero
    $count = 0;
    while (true) {
        //jump 1 MB forward in file
        fseek($fh, 1048576, SEEK_CUR);
        //check if we actually left the file
        if (($char = fgetc($fh)) !== false) {
            //if not, go on
            $count ++;
        } else {
            //else jump back where we were before leaving and exit loop
            fseek($fh, -1048576, SEEK_CUR);
            break;
        }
    }
    //we could make $count jumps, so the file is at least $count * 1.000001 MB large
    //1048577 because we jump 1 MB and fgetc goes 1 B forward too
    $size = bcmul("1048577", $count);
    //now count the last few bytes; they're always less than 1048576 so it's quite fast
    $fine = 0;
    while(false !== ($char = fgetc($fh))) {
        $fine ++;
    }
    //and add them
    $size = bcadd($size, $fine);
    fclose($fh);
    return $size;
}



Si tiene un servidor FTP, puede usar fsockopen:

$socket = fsockopen($hostName, 21);
$t = fgets($socket, 128);
fwrite($socket, "USER $myLogin\r\n");
$t = fgets($socket, 128);
fwrite($socket, "PASS $myPass\r\n");
$t = fgets($socket, 128);
fwrite($socket, "SIZE $fileName\r\n");
$t = fgets($socket, 128);
$fileSize=floatval(str_replace("213 ","",$t));
echo $fileSize;
fwrite($socket, "QUIT\r\n");
fclose($socket); 

(Se encuentra como un comentario en la página ftp_size )




Encontré una buena solución delgada para Linux / Unix solo para obtener el tamaño de archivo de archivos grandes con php de 32 bits.

$file = "/path/to/my/file.tar.gz";
$filesize = exec("stat -c %s ".$file);

Debería manejar $filesize como cadena. Intentar convertir como int resulta en un tamaño de archivo = PHP_INT_MAX si el tamaño del archivo es mayor que PHP_INT_MAX.

Pero aunque se maneja como una cadena, funciona el siguiente algoritmo legible por humanos:

formatBytes($filesize);

public function formatBytes($size, $precision = 2) {
    $base = log($size) / log(1024);
    $suffixes = array('', 'k', 'M', 'G', 'T');
    return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}

entonces mi salida para un archivo de más de 4 Gb es:

4.46G



Una opción sería buscar la marca de 2 gb y luego leer la longitud desde allí ...

function getTrueFileSize($filename) {
    $size = filesize($filename);
    if ($size === false) {
        $fp = fopen($filename, 'r');
        if (!$fp) {
            return false;
        }
        $offset = PHP_INT_MAX - 1;
        $size = (float) $offset;
        if (!fseek($fp, $offset)) {
            return false;
        }
        $chunksize = 8192;
        while (!feof($fp)) {
            $size += strlen(fread($fp, $chunksize));
        }
    } elseif ($size < 0) {
        // Handle overflowed integer...
        $size = sprintf("%u", $size);
    }
    return $size;
}

Así que, básicamente, busca el entero con signo positivo más grande representable en PHP (2 gb para un sistema de 32 bits), y luego lee a partir de entonces utilizando bloques de 8 kb (lo que debería ser una compensación justa para la mejor eficiencia de memoria frente a la eficiencia de transferencia de disco).

También tenga en cuenta que no estoy agregando $chunksize al tamaño. La razón es que fread puede devolver más o menos bytes que $chunksize dependiendo de una cantidad de posibilidades. Por lo tanto, use strlen para determinar la longitud de la cadena analizada.




Repetí la clase / respuesta de BigFileTools :
-opción para deshabilitar el método curl porque algunas plataformas (Synology NAS por ejemplo) no son compatibles con el protocolo FTP para Curl
-extra non posix, pero más precisa, la implementación de sizeExec , en lugar de tamaño en el disco, el tamaño real del archivo se devuelve utilizando stat en lugar de du
- resultados de tamaño correcto para archivos grandes (> 4 GB) y casi tan rápido para sizeNativeSeek
-opción de mensajes de descarte

<?php

/**
 * Class for manipulating files bigger than 2GB
 * (currently supports only getting filesize)
 *
 * @author Honza Kuchař
 * @license New BSD
 * @encoding UTF-8
 * @copyright Copyright (c) 2013, Jan Kuchař
 */
class BigFileTools {

    /**
     * Absolute file path
     * @var string
     */
    protected $path;

    /**
     * Use in BigFileTools::$mathLib if you want to use BCMath for mathematical operations
     */
    const MATH_BCMATH = "BCMath";

    /**
     * Use in BigFileTools::$mathLib if you want to use GMP for mathematical operations
     */
    const MATH_GMP = "GMP";

    /**
     * Which mathematical library use for mathematical operations
     * @var string (on of constants BigFileTools::MATH_*)
     */
    public static $mathLib;

    /**
     * If none of fast modes is available to compute filesize, BigFileTools uses to compute size very slow
     * method - reading file from 0 byte to end. If you want to enable this behavior,
     * switch fastMode to false (default is true)
     * @var bool
     */
    public static $fastMode = true;

  //on some platforms like Synology NAS DS214+ DSM 5.1 FTP Protocol for curl is not working or disabled
  // you will get an error like "Protocol file not supported or disabled in libcurl"
    public static $FTPProtocolCurlEnabled = false; 
  public static $debug=false; //shows some debug/error messages
  public static $posix=true; //more portable but it shows size on disk not actual filesize so it's less accurate: 0..clustersize in bytes inaccuracy

    /**
     * Initialization of class
     * Do not call directly.
     */
    static function init() {
        if (function_exists("bcadd")) {
            self::$mathLib = self::MATH_BCMATH;
        } elseif (function_exists("gmp_add")) {
            self::$mathLib = self::MATH_GMP;
        } else {
            throw new BigFileToolsException("You have to install BCMath or GMP. There mathematical libraries are used for size computation.");
        }
    }

    /**
     * Create BigFileTools from $path
     * @param string $path
     * @return BigFileTools
     */
    static function fromPath($path) {
        return new self($path);
    }

    static function debug($msg) {
        if (self::$debug) echo $msg;
    }

    /**
     * Gets basename of file (example: for file.txt will return "file")
     * @return string
     */
    public function getBaseName() {
        return pathinfo($this->path, PATHINFO_BASENAME);
    }

    /**
     * Gets extension of file (example: for file.txt will return "txt")
     * @return string
     */
    public function getExtension() {
        return pathinfo($this->path, PATHINFO_EXTENSION);
    }


    /**
     * Gets extension of file (example: for file.txt will return "file.txt")
     * @return string
     */
    public function getFilename() {
        return pathinfo($this->path, PATHINFO_FILENAME);
    }

    /**
     * Gets path to file of file (example: for file.txt will return path to file.txt, e.g. /home/test/)
     * ! This will call absolute path!
     * @return string
     */
    public function getDirname() {
        return pathinfo($this->path, PATHINFO_DIRNAME);
    }

    /**
     * Gets md5 checksum of file content
     * @return string
     */
    public function getMd5() {
        return md5_file($this->path);
    }

    /**
     * Gets sha1 checksum of file content
     * @return string
     */
    public function getSha1() {
        return sha1_file($this->path);
    }

    /**
     * Constructor - do not call directly
     * @param string $path
     */
    function __construct($path, $absolutizePath = true) {
        if (!static::isReadableFile($path)) {
            throw new BigFileToolsException("File not found at $path");
        }

        if($absolutizePath) {
            $this->setPath($path);
        }else{
            $this->setAbsolutePath($path);
        }
    }

    /**
     * Tries to absolutize path and than updates instance state
     * @param string $path
     */
    function setPath($path) {

        $this->setAbsolutePath(static::absolutizePath($path));
    }

    /**
     * Setts absolute path
     * @param string $path
     */
    function setAbsolutePath($path) {
        $this->path = $path;
    }

    /**
     * Gets current filepath
     * @return string
     */
    function getPath($a = "") {
        if(a != "") {
            trigger_error("getPath with absolutizing argument is deprecated!", E_USER_DEPRECATED);
        }

        return $this->path;
    }

    /**
     * Converts relative path to absolute
     */
    static function absolutizePath($path) {

        $path = realpath($path);
        if(!$path) {
            // TODO: use hack like http://.com/questions/4049856/replace-phps-realpath or http://www.php.net/manual/en/function.realpath.php#84012
            //       probaly as optinal feature that can be turned on when you know, what are you doing

            throw new BigFileToolsException("Not possible to resolve absolute path.");
        }
        return $path;
    }

    static function isReadableFile($file) {
        // Do not use is_file
        // @link https://bugs.php.net/bug.php?id=27792
        // $readable = is_readable($file); // does not always return correct value for directories

        $fp = @fopen($file, "r"); // must be file and must be readable
        if($fp) {
            fclose($fp);
            return true;
        }
        return false;
    }

    /**
     * Moves file to new location / rename
     * @param string $dest
     */
    function move($dest) {
        if (move_uploaded_file($this->path, $dest)) {
            $this->setPath($dest);
            return TRUE;
        } else {
            @unlink($dest); // needed in PHP < 5.3 & Windows; intentionally @
            if (rename($this->path, $dest)) {
                $this->setPath($dest);
                return TRUE;
            } else {
                if (copy($this->path, $dest)) {
                    unlink($this->path); // delete file
                    $this->setPath($dest);
                    return TRUE;
                }
                return FALSE;
            }
        }
    }

    /**
     * Changes path of this file object
     * @param string $dest
     */
    function relocate($dest) {
        trigger_error("Relocate is deprecated!", E_USER_DEPRECATED);
        $this->setPath($dest);
    }

    /**
     * Size of file
     *
     * Profiling results:
     *  sizeCurl        0.00045299530029297
     *  sizeNativeSeek  0.00052094459533691
     *  sizeCom         0.0031449794769287
     *  sizeExec        0.042937040328979
     *  sizeNativeRead  2.7670161724091
     *
     * @return string | float
     * @throws BigFileToolsException
     */
    public function getSize($float = false) {
        if ($float == true) {
            return (float) $this->getSize(false);
        }

        $return = $this->sizeCurl();
        if ($return) {
      $this->debug("sizeCurl succeeded");
            return $return;
        }
    $this->debug("sizeCurl failed");

        $return = $this->sizeNativeSeek();
        if ($return) {
      $this->debug("sizeNativeSeek succeeded");
            return $return;
        }
    $this->debug("sizeNativeSeek failed");

        $return = $this->sizeCom();
        if ($return) {
      $this->debug("sizeCom succeeded");
            return $return;
        }
    $this->debug("sizeCom failed");

        $return = $this->sizeExec();
        if ($return) {
      $this->debug("sizeExec succeeded");
            return $return;
        }
    $this->debug("sizeExec failed");

        if (!self::$fastMode) {
            $return = $this->sizeNativeRead();
            if ($return) {
        $this->debug("sizeNativeRead succeeded");
                return $return;
            }
      $this->debug("sizeNativeRead failed");
        }

        throw new BigFileToolsException("Can not size of file $this->path !");
    }

    // <editor-fold defaultstate="collapsed" desc="size* implementations">
    /**
     * Returns file size by using native fseek function
     * @see http://www.php.net/manual/en/function.filesize.php#79023
     * @see http://www.php.net/manual/en/function.filesize.php#102135
     * @return string | bool (false when fail)
     */
    protected function sizeNativeSeek() {
        $fp = fopen($this->path, "rb");
        if (!$fp) {
            return false;
        }

        flock($fp, LOCK_SH);
    $result= fseek($fp, 0, SEEK_END);

    if ($result===0) {
      if (PHP_INT_SIZE < 8) {
        // 32bit
        $return = 0.0;
        $step = 0x7FFFFFFF;
        while ($step > 0) {
          if (0 === fseek($fp, - $step, SEEK_CUR)) {
            $return += floatval($step);
          } else {
            $step >>= 1;
          }
        }
      }
      else { //64bit
        $return = ftell($fp);
      }
    }
    else $return = false;

    flock($fp, LOCK_UN);
    fclose($fp);
    return $return;
    }

    /**
     * Returns file size by using native fread function
     * @see http://.com/questions/5501451/php-x86-how-to-get-filesize-of-2gb-file-without-external-program/5504829#5504829
     * @return string | bool (false when fail)
     */
    protected function sizeNativeRead() {
        $fp = fopen($this->path, "rb");
        if (!$fp) {
            return false;
        }
        flock($fp, LOCK_SH);

        rewind($fp);
        $offset = PHP_INT_MAX - 1;

        $size = (string) $offset;
        if (fseek($fp, $offset) !== 0) {
            flock($fp, LOCK_UN);
            fclose($fp);
            return false;
        }
        $chunksize = 1024 * 1024;
        while (!feof($fp)) {
            $read = strlen(fread($fp, $chunksize));
            if (self::$mathLib == self::MATH_BCMATH) {
                $size = bcadd($size, $read);
            } elseif (self::$mathLib == self::MATH_GMP) {
                $size = gmp_add($size, $read);
            } else {
                throw new BigFileToolsException("No mathematical library available");
            }
        }
        if (self::$mathLib == self::MATH_GMP) {
            $size = gmp_strval($size);
        }
        flock($fp, LOCK_UN);
        fclose($fp);
        return $size;
    }

    /**
     * Returns file size using curl module
     * @see http://www.php.net/manual/en/function.filesize.php#100434
     * @return string | bool (false when fail or cUrl module not available)
     */
    protected function sizeCurl() {
        // curl solution - cross platform and really cool :)
        if (self::$FTPProtocolCurlEnabled && function_exists("curl_init")) {
            $ch = curl_init("file://" . $this->path);
            curl_setopt($ch, CURLOPT_NOBODY, true);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HEADER, true);
            $data = curl_exec($ch);
      if ($data=="" || empty($data)) $this->debug(stripslashes(curl_error($ch)));
            curl_close($ch);
            if ($data !== false && preg_match('/Content-Length: (\d+)/', $data, $matches)) {
                return (string) $matches[1];
            }
        } else {
            return false;
        }
    }

    /**
     * Returns file size by using external program (exec needed)
     * @see http://.com/questions/5501451/php-x86-how-to-get-filesize-of-2gb-file-without-external-program/5502328#5502328
     * @return string | bool (false when fail or exec is disabled)
     */
    protected function sizeExec() {
        // filesize using exec
        if (function_exists("exec")) {

            if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { // Windows
                // Try using the NT substition modifier %~z
        $escapedPath = escapeshellarg($this->path);
                $size = trim(exec("for %F in ($escapedPath) do @echo %~zF"));
            }else{ // other OS
                // If the platform is not Windows, use the stat command (should work for *nix and MacOS)
        if (self::$posix) {
          $tmpsize=trim(exec("du \"".$this->path."\" | cut -f1")); 
          //du returns blocks/KB
          $size=(int)$tmpsize*1024; //make it bytes
        }
        else $size=trim(exec('stat "'.$this->path.'" | grep -i -o -E "Size: ([0-9]+)" | cut -d" " -f2'));

        if (self::$debug) var_dump($size);
        return $size;
            }

        }
        return false;
    }

    /**
     * Returns file size by using Windows COM interface
     * @see http://.com/questions/5501451/php-x86-how-to-get-filesize-of-2gb-file-without-external-program/5502328#5502328
     * @return string | bool (false when fail or COM not available)
     */
    protected function sizeCom() {
        if (class_exists("COM")) {
            // Use the Windows COM interface
            $fsobj = new COM('Scripting.FileSystemObject');
            if (dirname($this->path) == '.')
                $this->path = ((substr(getcwd(), -1) == DIRECTORY_SEPARATOR) ? getcwd() . basename($this->path) : getcwd() . DIRECTORY_SEPARATOR . basename($this->path));
            $f = $fsobj->GetFile($this->path);
            return (string) $f->Size;
        }
    }

    // </editor-fold>
}

BigFileTools::init();

class BigFileToolsException extends Exception{}



No se puede obtener de manera confiable el tamaño de un archivo en un sistema de 32 bits comprobando si filesize () arroja resultados negativos, como sugieren algunas respuestas. Esto se debe a que si un archivo tiene entre 4 y 6 gigas en un sistema de 32 bits, el tamaño del archivo indicará un número positivo, luego negativo de 6 a 8, luego positivo de 8 a 10, y así sucesivamente. Bucle, como una manera de hablar.

Entonces estás atrapado usando un comando externo que funciona de manera confiable en tu sistema de 32 bits.

Sin embargo, una herramienta muy útil es la capacidad de comprobar si el tamaño del archivo es mayor que un determinado tamaño y puede hacerlo de forma fiable incluso en archivos muy grandes.

Lo siguiente busca 50 megas e intenta leer un byte. Es muy rápido en mi máquina de prueba de baja especificación y funciona de manera confiable incluso cuando el tamaño es mucho mayor que 2 gigas.

Puede usar esto para verificar si un archivo es mayor que 2147483647 bytes (2147483648 es máximo int en sistemas de 32 bits) y luego manejar el archivo de manera diferente o hacer que su aplicación emita una advertencia.

function isTooBig($file){
        $fh = @fopen($file, 'r');
        if(! $fh){ return false; }
        $offset = 50 * 1024 * 1024; //50 megs
        $tooBig = false;
        if(fseek($fh, $offset, SEEK_SET) === 0){
                if(strlen(fread($fh, 1)) === 1){
                        $tooBig = true;
                }
        } //Otherwise we couldn't seek there so it must be smaller

        fclose($fh);
        return $tooBig;
}



Related