Download issue in php - php

I have a php script for downloading mp4 videp files . It is working properly now for files having small size (upto 60 MB is tested) . When i try for a big one (300 MB) it displays an error related with "memory limit" . So that done some edits in ini file for increasing the memory limit upto 400 MB (memory_limit = 400M) . Then i try to run my script , But the download box is displaying a content size of 189 bytes. I tried it with an another mp4 file (small size) , its working well . I dont understand the reason for it . So please kindly direct me . My script is as follows..
$file = $_SERVER['DOCUMENT_ROOT']."/folder_name/filename.mp4";
$filesize = filesize($file);
$fileName=$file;
$offset = 0;
$length = $filesize;
if ( isset($_SERVER['HTTP_RANGE']) ) {
$partialContent = true;
preg_match('/bytes=(\d+)-?/', $_SERVER['HTTP_RANGE'], $matches);
$offset = intval($matches[1]);
$length = $filesize - $offset;
}else {
$partialContent = false;
}
$file = fopen($file, 'r');
fseek($file, $offset);
$data = fread($file, $length);
fclose($file);
if ( $partialContent ) {
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $filesize);
}
header("Content-type: video/mp4");
header('Content-Length: ' . $filesize);
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Accept-Ranges: bytes');
print($data);
The download box displaying is..

Please check with your server sometime your server not support this many memory allocation to your script.
Please add following line on top of script.
ini_set('memory_limit', '-1');
ini_set('max_execution_time', '0');
then let me know if that not working.

It isnt good to change all directives without a good reason, so the first step is to know what specific problem you have.
When you run a script for downloading files, like in this case, mp4 video files, and the download box is displaying a content size much smaller than the actual file, its easy to get a clue about what was wrong:
You just need to download that file and, no matter it looks like a video, open it with a text editor.
Right click > open with... and then select any text editor.
You will find that the actual content of the file is the information about the error occurred in the server. With this you will have better information about what happened, so you can figure out for example, if it is related to the memory_limit, to the max_execution_time, or to any other reason.
Now you can decide to change one or more of the directives, or probably change your script to get a better performance.
Note: if the file you downloaded is empty, or doesnt contain any error information, probably you have the error reporting disabled. To enable it you can add this two lines at the begining of the script:
ini_set("display_errors","1");
error_reporting(E_ALL);

Related

Unable to read files which are greater than 2GB

I am using this below code to stream a movie on my localhost. The movie which is lesser then 2GB works fine but when it comes to movie greater than 2gb then the movie doesn't play.
Please help me what should I do to read larger files.
This is the code I am using for streaming movie
<?php
//Determine file path according to extension
if (!isset($_GET['ext']) || $_GET['ext'] == 'mp4') {
$path ='movies/'.$_GET['movie'];
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($path);
header('Content-Type: ' . $mime);
$size = filesize($path);
if (isset($_SERVER['HTTP_RANGE'])) {
list($specifier, $value) = explode('=', $_SERVER['HTTP_RANGE']);
if ($specifier != 'bytes') {
header('HTTP/1.1 400 Bad Request');
return;
}
list($from, $to) = explode('-', $value);
if (!$to) {
$to = $size - 1;
}
$fp = fopen($path, 'rb');
if (!$fp) {
header('HTTP/1.1 500 Internal Server Error');
return;
}
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges: bytes');
header('Content-Length: ' . ($to - $from));
header("Content-Range: bytes {$from}-{$to}/{$size}");
fseek($fp, $from);
while(true){
if(ftell($fp) >= $to){
break;
}
echo fread($fp, 8192);
// Flush do buffer
ob_flush();
flush();
}
}
else {
header('Content-Length: ' . $size);
readfile($path);
}
I have hosted the files on localhost using xammpp as a server.
This could have different reasons.
fopen() reads a file into your RAM. So the limitation seems to come from there.
First, make sure you have min 4GB installed in your system.
If you need more also make sure you dont use a Win 32-Bit system since this only supports up to 4GB ram.
Then you can try to increase the memory_limit of PHP. Open the xampp/php/php.ini file and change the memory_limit to what ever you need OR set the ini value directly in your php file like this
ini_set(”memory_limit”,”16M”);
Hope this helps. For a more specific answer it would be helpful if you have any error message.

Why can't I cast an MP4 file served by PHP from outside of the document root?

Ok, I give up... Something very strange is going on and, after days of messing with this, I have to ask for help. I have a PHP script that serves an MP4 file from outside of the document root. This script works great, except for one very important (to me at least) detail: it will not give me the option to cast the content. On the same server, when I access an MP4 file that IS inside the document root, I load the page and when I click the three dots in the bottom right corner of the Chrome video player, I have the option to Download or Cast to my Chromecast. Using my script, I only have the option to Download, and I REALLLLY need to CAST! I have tweaked this so much that the headers output from either method are all but identical. Here is my code...
<?php
$file=$_GET['file'];
//validate
if($file=="." || $file==".."){$file="";}
$mediaRoot="../../../hostMedia";
$file=$mediaRoot . DIRECTORY_SEPARATOR . $file;
$file=str_replace('\\',"/",$file);
$filesize = filesize($file);
$offset = 0;
$length = $filesize;
// find the requested range
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
$offset = intval($matches[1]);
$length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;
// output the right headers for partial content
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length-1) . '/' . $filesize);
header('Content-Type: video/mp4');
header('Content-Length: ' . $filesize);
header('Accept-Ranges: bytes');
header('Cache-Control: max-age=0');
// open file for reading
$file = fopen($file, 'r');
// seek to the requested offset, this is 0 if it's not a partial content request
fseek($file, $offset);
// populate $data with all except the last byte of the file
$numBytes=($filesize-1);
$dataLen=0;
while($dataLen<$numBytes){
$lenGrab=($numBytes-$dataLen);
if($lenGrab>(1024*2700)){$lenGrab=(1024*2700);}
$data=fread($file, $lenGrab);
print($data);
$dataLen+=strlen($data);
}
// close file
fclose($file);
?>
A thousand "thank-you"s to whoever solves this one!
UPDATE
Ok, taking #Brian Heward's advice, I have spent countless hours making sure that the headers are ABSOLUTELY IDENTICAL!!! I was so sure it would work, but alas, it still fails to give me the option to cast. Here is my updated PHP...
<?php
session_start();
$accessCode=$_SESSION['accessCode'];
$file=$_GET['file'];
//handle injection
if($file=="." || $file==".."){$file="";}
if($accessCode=="blahblahblah8"){
$mediaRoot="../../../hostMedia";
$file=$mediaRoot . DIRECTORY_SEPARATOR . $file;
$file=str_replace('\\',"/",$file);
$filesize = filesize($file);
$offset = 0;
$length = $filesize;
$lastMod=filemtime($file);
if ( isset($_SERVER['HTTP_RANGE']) ) {
// if the HTTP_RANGE header is set we're dealing with partial content
$partialContent = true;
// find the requested range
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
$offset = intval($matches[1]);
$length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;
} else {
$partialContent = false;
}
if ( $partialContent ) {
// output the right headers for partial content
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length-1) . '/' . $filesize);
}else{
header('HTTP/1.1 200 OK');
}
// output the regular HTTP headers
header('Content-Type: video/mp4');
header('Content-Length: ' . $length);
header('Accept-Ranges: bytes');
header('ETag: "3410d79f-576de84c004aa"');
header('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T', $lastMod));
// don't forget to send the data too
$file = fopen($file, 'r');
// seek to the requested offset, this is 0 if it's not a partial content request
fseek($file, $offset);
//populate $data with all except the last byte of the file
$numBytes=($length);
$dataLen=0;
while($dataLen<$numBytes){
$lenGrab=($numBytes-$dataLen);
if($lenGrab>(1024*2700)){$lenGrab=(1024*2700);}
$data=fread($file, $lenGrab);
print($data);
$dataLen+=strlen($data);
}
fclose($file);
}else{
echo "You are not authorized to view this media.";
}
?>
If someone can get this thing to work, you are seriously a superhero!
FINAL UPDATE (for now...)
Well, after many, many hours of frustration, I had to abandon the approach and try something different. Luckily, there are usually more than one way to accomplish something, and I have found another way. I am hosting the .mp4 files inside the doc root in a folder protected using HTTP Basic Auth. Very similar to what I was trying to achieve and it is working for me. Thanks for your advice and direction!
Your headers are "all but identical" and there is the problem. Make them identical :P
Use the developer tools on your browser, (F12) and check the network headers each request is making. The most likely causes are the following lines I used on a similar project and you seem to be missing:
header('Content-Description: File Transfer');
header('Content-Disposition: inline; filename=' . basename($file));
alternately it might want
header('Content-Disposition: attachment; filename=' . basename($file));
This script might be what you're looking for, it handles video serving via PHP really well, Source
<?php
// disable zlib so that progress bar of player shows up correctly
if(ini_get('zlib.output_compression')) {
ini_set('zlib.output_compression', 'Off');
}
$folder = '.';
$filename = 'video.mp4';
$path = $folder.'/'.$filename;
// from: http://l...content-available-to-author-only...n.net/post/stream-videos-php/
if (file_exists($path)) {
// Clears the cache and prevent unwanted output
ob_clean();
$mime = "video/mp4"; // The MIME type of the file, this should be replaced with your own.
$size = filesize($path); // The size of the file
// Send the content type header
header('Content-type: ' . $mime);
// Check if it's a HTTP range request
if(isset($_SERVER['HTTP_RANGE'])){
// Parse the range header to get the byte offset
$ranges = array_map(
'intval', // Parse the parts into integer
explode(
'-', // The range separator
substr($_SERVER['HTTP_RANGE'], 6) // Skip the `bytes=` part of the header
)
);
// If the last range param is empty, it means the EOF (End of File)
if(!$ranges[1]){
$ranges[1] = $size - 1;
}
// Send the appropriate headers
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges: bytes');
header('Content-Length: ' . ($ranges[1] - $ranges[0])); // The size of the range
// Send the ranges we offered
header(
sprintf(
'Content-Range: bytes %d-%d/%d', // The header format
$ranges[0], // The start range
$ranges[1], // The end range
$size // Total size of the file
)
);
// It's time to output the file
$f = fopen($path, 'rb'); // Open the file in binary mode
$chunkSize = 8192; // The size of each chunk to output
// Seek to the requested start range
fseek($f, $ranges[0]);
// Start outputting the data
while(true){
// Check if we have outputted all the data requested
if(ftell($f) >= $ranges[1]){
break;
}
// Output the data
echo fread($f, $chunkSize);
// Flush the buffer immediately
#ob_flush();
flush();
}
}
else {
// It's not a range request, output the file anyway
header('Content-Length: ' . $size);
// Read the file
#readfile($path);
// and flush the buffer
#ob_flush();
flush();
}
}
die();
?>

How to check if PHP download was canceled?

I need to log total downloads of an specific file. Download function is working fine, but can't define if user canceled (clicking "cancel" on browser dialog) or if connection was aborted latter.
I understand it's not simple to know when a file download was finished, so I'm trying to get this by two ways. None works:
Get total bytes sent, latter I will compare it with total file size: this way $bytes_sent var always is set with total file size, no matter if user click cancel button of download dialog or if cancel download process latter.
Trigger connection_aborted() function: Have not found the way this function happen and define my session var...
(I'm not shure if the fact Im working with sessions is relevant).
I appreciate your help :)
<?php
if(is_file($filepath)){
$handle = fopen($filepath, "r");
header("Content-Type: $mime_type");
header("Content-Length: ". filesize($filepath).";");
header("Content-disposition: attachment; filename=" . $name);
while(!feof($handle)){
ignore_user_abort(true);
set_time_limit(0);
$data = fread($handle, filesize($filepath));
print $data;
$_SESSION['download'] = 'Successful download';
//Always is set as total file lenght, even when cancel a large file download before it finish:
bytes_sent = ftell($handle);
flush();
ob_flush();
//Can't trigger connection aborted, in any case:
if(connection_aborted()){
$_SESSION['download'] = 'Canceled download';
}
}
}
PHP Version 5.3.29
You need to read the file in small chunks, rather than reading it all in at once.
$chunk_size = 1000;
ignore_user_abort();
$canceled = false;
while ($chunk = fread($handle, $chunk_size)) {
print $chunk;
ob_flush();
$bytes_sent += strlen($chunk);
if (connection_aborted()) {
$canceled = true;
break;
}
}
$_SESSION['download'] = $canceled ? "Download canceled" : "Download successful";

Streaming video/mp4 with php

I've got problem streaming a mp4 file throught php.
The downloading of the files hangs on 1MB. You can see it here:
I've tried many headers and read many threads, but lots of them are unresolved or didnt help me, like this one: MP4 plays when accessed directly, but not when read through PHP, on iOS . Any help please?
There is my code:
<?
$root=getenv("DOCUMENT_ROOT");
$file = "$root/".$_GET['get'];
header("Content-Type: video/mp4");
//header("Content-Type: application/octet-stream"); // downloads the file
$start=0;
$size = filesize($file);
$length = $size;
$end = $size - 1;
//header('HTTP/1.1 206 Partial Content');
header("Accept-Ranges: $start-$end");
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");
$data = fopen("$file", "r");
fseek($data, $start);
$bytesleft = $size-$start + 1;
$buffer = 1024 * 256; // 256kb
while(!feof($data)){
if($bytesleft > $buffer){
echo fread($data, $buffer);
flush();
}else{
echo fread($data, $bytesleft);
flush();
}
//sleep(1); // Speedlimit = one buffer per second
}
fclose($data);
exit;
?>
Thanks in advance
Nevermind. I downloaded the file from the server and compared it with original stored on server in hex.
The downloaded one had one more byte on begining: 0A and that was the problem. Everything works now.

Increase Efficiency in Zip Reader in PHP

I have some php code that reads the first file of a remote zip file containing mp3s. I know that in the zip file format, each file has a compressed size in its header. However, when I use fread to that number after going through the header, it only goes to about 5454 characters. I have made a fix, but it is very slow.
$comp = "";
while(strlen($comp) <= $cpsz){
/*$cpsz is the compressed size*/
$comp .= fread($fh, 1);
}
header("Accept-Ranges:bytes");
header("Connection: Keep-Alive");
header("Content-Type:audio/mpeg");
header("Content-Length:" . $ucps);
header("Content-Range:bytes 0-" . ($ucps-1) . "/" . $ucps);
header("ETag: xyz");
http_response_code (206);
$file = gzinflate($comp);
echo $file;
Is there anyway to make this more efficient than reading one byte at a time?

Categories