From f8c75f418dd9404e960c532610af2b13e2552169 Mon Sep 17 00:00:00 2001 From: Christian Haschek Date: Wed, 25 Nov 2015 10:11:47 +0100 Subject: [PATCH] extended mp4 support and rendered in html like imgur does with "gifv" --- backend.php | 2 + inc/core.php | 116 +++++++++++++++++++++++++++++++++++--- models/pictsharemodel.php | 115 +++++++++++++++++++++++++++++++++++-- template_mp4.php | 47 +++++++++++++++ 4 files changed, 267 insertions(+), 13 deletions(-) create mode 100644 template_mp4.php diff --git a/backend.php b/backend.php index cb30daa..6b26f02 100644 --- a/backend.php +++ b/backend.php @@ -42,5 +42,7 @@ else if($_REQUEST['base64']) } else if($_REQUEST['geturlinfo']) echo json_encode($pm->getURLInfo($_REQUEST['geturlinfo'])); +else if($_REQUEST['a']=='oembed') + echo json_encode($pm->oembed($_REQUEST['url'],$_REQUEST['t'])); else echo json_encode(array('status'=>'ERR')); diff --git a/inc/core.php b/inc/core.php index 6b50171..a8f060b 100644 --- a/inc/core.php +++ b/inc/core.php @@ -165,15 +165,27 @@ function renderImage($data) case 'mp4': if(!$cached) { - $path = $pm->resizeMP4($data,$cachepath); + $pm->resizeMP4($data,$cachepath); + $path = $cachepath; } - header('Content-type: video/mp4'); - //header('Content-type: video/mpeg'); - header('Content-disposition: inline'); - header("Content-Transfer-Encoding:­ binary"); - header("Content-Length: ".filesize($path)); - - readfile($path); + + if(filesize($path)==0) //if there was an error and the file is 0 bytes, use the original + $path = ROOT.DS.'upload'.DS.$hash.DS.$hash; + + if($data['raw']) + { + serveFile($path, '/raw/'.$hash,'video/mp4'); + } + else if($data['preview']) + { + $file = $path.'.jpg'; + if(!file_exists($file)) + $pm->saveFirstFrameOfMP4($path); + header ("Content-type: image/jpeg"); + readfile($file); + } + else + renderMP4($path,$data); break; } @@ -201,4 +213,92 @@ function render($variables=null) if(is_array($variables)) extract($variables); include (ROOT . DS . 'template.php'); +} + +function renderMP4($path,$data) +{ + $pm = new PictshareModel; + $hash = $data['hash']; + if($data['size']) + $hash = $data['size'].'/'.$hash; + $info = $pm->getSizeOfMP4($path); + $width = $info['width']; + $height = $info['height']; + include (ROOT . DS . 'template_mp4.php'); +} + +// +// from: https://stackoverflow.com/questions/25975943/php-serve-mp4-chrome-provisional-headers-are-shown-request-is-not-finished-ye +// +function serveFile($filename, $filename_output = false, $mime = 'application/octet-stream') +{ + $buffer_size = 8192; + $expiry = 90; //days + + if(!file_exists($filename)) + { + throw new Exception('File not found: ' . $filename); + } + if(!is_readable($filename)) + { + throw new Exception('File not readable: ' . $filename); + } + + header_remove('Cache-Control'); + header_remove('Pragma'); + + $byte_offset = 0; + $filesize_bytes = $filesize_original = filesize($filename); + + header('Accept-Ranges: bytes', true); + header('Content-Type: ' . $mime, true); + + if($filename_output) + { + header('Content-Disposition: attachment; filename="' . $filename_output . '"'); + } + + // Content-Range header for byte offsets + if (isset($_SERVER['HTTP_RANGE']) && preg_match('%bytes=(\d+)-(\d+)?%i', $_SERVER['HTTP_RANGE'], $match)) + { + $byte_offset = (int) $match[1];//Offset signifies where we should begin to read the file + if (isset($match[2]))//Length is for how long we should read the file according to the browser, and can never go beyond the file size + { + $filesize_bytes = min((int) $match[2], $filesize_bytes - $byte_offset); + } + header("HTTP/1.1 206 Partial content"); + header(sprintf('Content-Range: bytes %d-%d/%d', $byte_offset, $filesize_bytes - 1, $filesize_original)); ### Decrease by 1 on byte-length since this definition is zero-based index of bytes being sent + } + + $byte_range = $filesize_bytes - $byte_offset; + + header('Content-Length: ' . $byte_range); + header('Expires: ' . date('D, d M Y H:i:s', time() + 60 * 60 * 24 * $expiry) . ' GMT'); + + $buffer = ''; + $bytes_remaining = $byte_range; + + $handle = fopen($filename, 'r'); + if(!$handle) + { + throw new Exception("Could not get handle for file: " . $filename); + } + if (fseek($handle, $byte_offset, SEEK_SET) == -1) + { + throw new Exception("Could not seek to byte offset %d", $byte_offset); + } + + while ($bytes_remaining > 0) + { + $chunksize_requested = min($buffer_size, $bytes_remaining); + $buffer = fread($handle, $chunksize_requested); + $chunksize_real = strlen($buffer); + if ($chunksize_real == 0) + { + break; + } + $bytes_remaining -= $chunksize_real; + echo $buffer; + flush(); + } } \ No newline at end of file diff --git a/models/pictsharemodel.php b/models/pictsharemodel.php index 6e527ae..a4f519e 100644 --- a/models/pictsharemodel.php +++ b/models/pictsharemodel.php @@ -21,8 +21,19 @@ class PictshareModel extends Model $path = ROOT.DS.'upload'.DS.$hash.DS.$file; if(file_exists($path)) { + $type = $this->getTypeOfHash($hash); $byte = filesize($path); - return array('status'=>'ok','hash'=>$hash,'cachename'=>$file,'size'=>$byte,'humansize'=>$html->renderSize($byte)); + if($type=='mp4') + { + $info = $this->getSizeOfMP4($path); + $width = intval($info['width']); + $height = intval($info['height']); + } + else + { + list($width, $height) = getimagesize($path); + } + return array('status'=>'ok','hash'=>$hash,'cachename'=>$file,'size'=>$byte,'humansize'=>$html->renderSize($byte),'width'=>$width,'height'=>$height,'type'=>$type); } else @@ -44,8 +55,8 @@ class PictshareModel extends Model if($this->isImage($el)) $data['hash']=$el; - else if($el=='mp4') - $data['mp4'] = 1; + else if($el=='mp4' || $el=='raw' || $el=='preview' || $el=='webm') + $data[$el] = 1; else if($this->isSize($el)) $data['size'] = $el; else if($this->isRotation($el)) @@ -179,6 +190,9 @@ class PictshareModel extends Model function getCacheName($data) { ksort($data); + unset($data['raw']); + unset($data['preview']); + unset($data['raw']); $name = false; foreach($data as $key=>$val) { @@ -190,7 +204,6 @@ class PictshareModel extends Model foreach($val as $valdata) $name[] = $valdata; } - } if(is_array($name)) @@ -332,7 +345,14 @@ class PictshareModel extends Model mkdir(ROOT.DS.'upload'.DS.$hash); $file = ROOT.DS.'upload'.DS.$hash.DS.$hash; - $status = file_put_contents($file, file_get_contents($url)); + + + if($type=='mp4') + { + $this->saveFirstFrameOfMP4($file); + } + + file_put_contents($file, file_get_contents($url)); //remove all exif data from jpeg if($type=='jpg') @@ -679,4 +699,89 @@ class PictshareModel extends Model return $mp4file; } + + function saveAsMP4($source,$target) + { + $bin = escapeshellcmd(ROOT.DS.'bin'.DS.'ffmpeg'); + $source = escapeshellarg($source); + $target = escapeshellarg($target); + $cmd = "$bin -y -i $source -c:v libx264 $target"; + + system($cmd); + } + + function saveFirstFrameOfMP4($path) + { + //$path = ROOT.DS.'upload'.DS.$hash.DS.$hash; + $jpgpath = $path.'.jpg'; + $bin = escapeshellcmd(ROOT.DS.'bin'.DS.'ffmpeg'); + $file = escapeshellarg($path); + $cmd = "$bin -y -i $file -vframes 1 -f image2 $jpgpath"; + + system($cmd); + } + + //from https://stackoverflow.com/questions/4847752/how-to-get-video-duration-dimension-and-size-in-php + function getSizeOfMP4($video) + { + $video = escapeshellarg($video); + $bin = escapeshellcmd(ROOT.DS.'bin'.DS.'ffmpeg'); + $command = $bin . ' -i ' . $video . ' -vstats 2>&1'; + $output = shell_exec($command); + + $regex_sizes = "/Video: ([^,]*), ([^,]*), ([0-9]{1,4})x([0-9]{1,4})/"; + if (preg_match($regex_sizes, $output, $regs)) { + $codec = $regs [1] ? $regs [1] : null; + $width = $regs [3] ? $regs [3] : null; + $height = $regs [4] ? $regs [4] : null; + } + + $regex_duration = "/Duration: ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}).([0-9]{1,2})/"; + if (preg_match($regex_duration, $output, $regs)) { + $hours = $regs [1] ? $regs [1] : null; + $mins = $regs [2] ? $regs [2] : null; + $secs = $regs [3] ? $regs [3] : null; + $ms = $regs [4] ? $regs [4] : null; + } + + return array ('codec' => $codec, + 'width' => $width, + 'height' => $height, + 'hours' => $hours, + 'mins' => $mins, + 'secs' => $secs, + 'ms' => $ms + ); + + } + + function oembed($url,$type) + { + $data = $this->getURLInfo($url); + $rawurl = $url.'/raw'; + switch($type) + { + case 'json': + header('Content-Type: application/json'); + return array( "version"=> "1.0", + "type"=> "video", + "thumbnail_url"=>$url.'/preview', + "thumbnail_width"=>$data['width'], + "thumbnail_height"=>$data['height'], + "width"=> $data['width'], + "height"=> $data['height'], + "title"=> "PictShare", + //"url"=> $url.'/raw', + "provider_name"=> "PictShare", + "provider_url"=> DOMAINPATH, + "html"=> ''); + break; + + case 'xml': + + break; + } + } } diff --git a/template_mp4.php b/template_mp4.php new file mode 100644 index 0000000..c60c2b0 --- /dev/null +++ b/template_mp4.php @@ -0,0 +1,47 @@ + + + + PictShare + + PictShare" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +