From 38cc06788a03bbf6c26dffc774266f9d8a98df4f Mon Sep 17 00:00:00 2001 From: Aaron Hedges Date: Mon, 22 Jun 2015 17:04:15 -0400 Subject: [PATCH] port api3's upload workflow --- vimeo.php | 193 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 108 insertions(+), 85 deletions(-) diff --git a/vimeo.php b/vimeo.php index 501243f..f938570 100644 --- a/vimeo.php +++ b/vimeo.php @@ -401,7 +401,9 @@ public function upload($file_path, $use_multiple_chunks = false, $chunk_temp_dir } // Get an upload ticket - $params = array(); + $params = array( + 'upload_method' => 'streaming' + ); if ($replace_id) { $params['video_id'] = $replace_id; @@ -416,75 +418,7 @@ public function upload($file_path, $use_multiple_chunks = false, $chunk_temp_dir throw new VimeoAPIException('File exceeds maximum allowed size.', 710); } - // Split up the file if using multiple pieces - $chunks = array(); - if ($use_multiple_chunks) { - if (!is_writeable($chunk_temp_dir)) { - throw new Exception('Could not write chunks. Make sure the specified folder has write access.'); - } - - // Create pieces - $number_of_chunks = ceil(filesize($file_path) / $size); - for ($i = 0; $i < $number_of_chunks; $i++) { - $chunk_file_name = "{$chunk_temp_dir}/{$file_name}.{$i}"; - - // Break it up - $chunk = file_get_contents($file_path, FILE_BINARY, null, $i * $size, $size); - $file = file_put_contents($chunk_file_name, $chunk); - - $chunks[] = array( - 'file' => realpath($chunk_file_name), - 'size' => filesize($chunk_file_name) - ); - } - } - else { - $chunks[] = array( - 'file' => realpath($file_path), - 'size' => filesize($file_path) - ); - } - - // Upload each piece - foreach ($chunks as $i => $chunk) { - $params = array( - 'oauth_consumer_key' => $this->_consumer_key, - 'oauth_token' => $this->_token, - 'oauth_signature_method' => 'HMAC-SHA1', - 'oauth_timestamp' => time(), - 'oauth_nonce' => $this->_generateNonce(), - 'oauth_version' => '1.0', - 'ticket_id' => $ticket, - 'chunk_id' => $i - ); - - // Generate the OAuth signature - $params = array_merge($params, array( - 'oauth_signature' => $this->_generateSignature($params, 'POST', self::API_REST_URL), - 'file_data' => '@'.$chunk['file'] // don't include the file in the signature - )); - - // Post the file - $curl = curl_init($endpoint); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $params); - $rsp = curl_exec($curl); - curl_close($curl); - } - - // Verify - $verify = $this->call('vimeo.videos.upload.verifyChunks', array('ticket_id' => $ticket)); - - // Make sure our file sizes match up - foreach ($verify->ticket->chunks as $chunk_check) { - $chunk = $chunks[$chunk_check->id]; - - if ($chunk['size'] != $chunk_check->size) { - // size incorrect, uh oh - echo "Chunk {$chunk_check->id} is actually {$chunk['size']} but uploaded as {$chunk_check->size}
"; - } - } + $this->_perform_upload($file_path, $endpoint); // Complete the upload $complete = $this->call('vimeo.videos.upload.complete', array( @@ -492,13 +426,6 @@ public function upload($file_path, $use_multiple_chunks = false, $chunk_temp_dir 'ticket_id' => $ticket )); - // Clean up - if (count($chunks) > 1) { - foreach ($chunks as $chunk) { - unlink($chunk['file']); - } - } - // Confirmation successful, return video id if ($complete->stat == 'ok') { return $complete->ticket->video_id; @@ -508,15 +435,46 @@ public function upload($file_path, $use_multiple_chunks = false, $chunk_temp_dir } } - /** - * Upload a video in multiple pieces. - * - * @deprecated - */ - public function uploadMulti($file_name, $size = 1048576) + public function _perform_upload($file_path, $upload_endpoint) { - // for compatibility with old library - return $this->upload($file_name, true, '.', $size); + // We need a handle on the input file since we may have to send segments multiple times. + $file = fopen($file_path, 'r'); + // PUTs a file in a POST....do for the streaming when we get there. + $curl_opts = array( + CURLOPT_PUT => true, + CURLOPT_INFILE => $file, + CURLOPT_INFILESIZE => filesize($file_path), + CURLOPT_UPLOAD => true, + CURLOPT_HTTPHEADER => array('Expect: ', 'Content-Range: replaced...') + ); + + // These are the options that set up the validate call. + $curl_opts_check_progress = array( + CURLOPT_PUT => true, + CURLOPT_HTTPHEADER => array('Content-Length: 0', 'Content-Range: bytes */*') + ); + + // Perform the upload by streaming as much to the server as possible and ending when we reach the filesize on the server. + $size = filesize($file_path); + $server_at = 0; + do { + // The last HTTP header we set MUST be the Content-Range, since we need to remove it and replace it with a proper one. + array_pop($curl_opts[CURLOPT_HTTPHEADER]); + $curl_opts[CURLOPT_HTTPHEADER][] = 'Content-Range: bytes ' . $server_at . '-' . $size . '/' . $size; + + fseek($file, $server_at); // Put the FP at the point where the server is. + + try { + var_dump($this->_request3($upload_endpoint, $curl_opts)); //Send what we can. + } catch (\Vimeo\Exceptions\VimeoRequestException $exception) { + // ignored, it's likely a timeout, and we should only consider a failure from the progress check as a legit failure + } + + $progress_check = $this->_request3($upload_endpoint, $curl_opts_check_progress); // Check on what the server has. + // Figure out how much is on the server. + list(, $server_at) = explode('-', $progress_check['headers']['Range']); + $server_at = (int)$server_at; + } while ($server_at < $size); } /** @@ -539,6 +497,71 @@ public static function url_encode_rfc3986($input) } } + /** + * Internal function to handle requests, both authenticated and by the upload function. + * + * @param string $url + * @param array $curl_opts + * @return array + */ + private function _request3($url, $curl_opts = array()) { + // Merge the options (custom options take precedence). + $curl_opts = $curl_opts + array( + CURLOPT_HEADER => 1, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 30, + ); + + // Call the API. + $curl = curl_init($url); + curl_setopt_array($curl, $curl_opts); + $response = curl_exec($curl); + $curl_info = curl_getinfo($curl); + + if(isset($curl_info['http_code']) && $curl_info['http_code'] === 0){ + $curl_error = curl_error($curl); + $curl_error = !empty($curl_error) ? '[' . $curl_error .']' : ''; + throw new VimeoRequestException('Unable to complete request.' . $curl_error); + } + + curl_close($curl); + + // Retrieve the info + $header_size = $curl_info['header_size']; + $headers = substr($response, 0, $header_size); + $body = substr($response, $header_size); + + // Return it raw. + return array( + 'body' => $body, + 'status' => $curl_info['http_code'], + 'headers' => self::parse_headers($headers) + ); + + } + + /** + * Convert the raw headers string into an associated array + * + * @param string $headers + * @return array + */ + public static function parse_headers($headers) + { + $final_headers = array(); + $list = explode("\n", trim($headers)); + + $http = array_shift($list); + + foreach ($list as $header) { + $parts = explode(':', $header); + $final_headers[trim($parts[0])] = isset($parts[1]) ? trim($parts[1]) : ''; + } + + return $final_headers; + } + + } class VimeoAPIException extends Exception {}