Diffs
Wozozo_WWW_YouTube/tags/release-0.0.1-20100416003302/Wozozo/WWW/YouTube.php
@@ -0,0 +1,243 @@
+<?php
+require_once 'Wozozo/WWW/YouTube/VideoInfo.php';
+class Wozozo_WWW_YouTube
+{
+ const PATH_INFO = 'http://www.youtube.com/get_video_info?video_id=%s';
+ const PATH_DOWNLOAD = 'http://www.youtube.com/get_video?video_id=%s&t=%s&fmt=%s';
+
+ /**
+ * @var Zend_Http_Client
+ */
+ protected $_httpClient;
+
+ /**
+ * @var array
+ */
+ protected $_config = array('fmt' => 18,
+ 'save' => 'PWD', //'TMP' will use getcwd();
+ 'download_stream' => true, //output stream
+ 'download_response_cleanup' => true
+ );
+
+ public function __construct($config = null)
+ {
+ if ($config) $this->setConfig($config);
+ }
+
+ public function setConfig($config = array())
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+
+ } elseif (! is_array($config)) {
+ throw new Exception('Array or Zend_Config object expected, got ' . gettype($config));
+ }
+
+ foreach ($config as $k => $v) {
+ $this->_config[strtolower($k)] = $v;
+ }
+
+ return $this;
+ }
+
+ /**
+ *
+ * @param string|Zend_Uri $videoId
+ * @param array|Zend_Config $config
+ * @param $path
+ * @return Wozozo_WWW_YouTube_VideoInfo
+ */
+ public static function download($videoId, $config = array())
+ {
+ $videoId = self::detectVideoId($videoId);
+
+ $self = new self($config);
+
+ $videoInfo = $self->getVideoInfo($videoId);
+ $self->downloadByVideoInfo($videoInfo);
+
+ return $videoInfo;
+ }
+
+ protected function _putVideo($response, Wozozo_WWW_YouTube_VideoInfo $videoInfo, $config)
+ {
+
+ if (is_string($config['save'])) {
+ $path = $this->suggestSavePath($videoInfo);
+ } else {
+
+ return call_user_func($config['save'], $response, $videoInfo, $config);
+ }
+
+ $ret = @file_put_contents($path, $response->getRawBody());
+ if ($ret === false) {
+ throw new Exception('cannot write at' . $path);
+ }
+ }
+
+ /**
+ * request & get videoinfo
+ *
+ * @param string $videoId
+ */
+ public function getVideoInfo($videoId)
+ {
+ $client = $this->getHttpClient();
+
+ $client->setUri(sprintf(self::PATH_INFO, $videoId));
+ $response = $client->request();
+
+ parse_str($response->getBody(), $parse);
+
+ return new Wozozo_WWW_YouTube_VideoInfo($parse);
+ }
+
+ /**
+ * Request video file
+ *
+ * @param Wozozo_WWW_YouTube_VideoInfo
+ * @return Zend_Http_Response_Stream
+ */
+ public function requestVideo(Wozozo_WWW_YouTube_VideoInfo $videoInfo)
+ {
+ if ($videoInfo['status'] !== 'ok') {
+ if ($videoInfo['status'] === 'fail') {
+ throw new Exception($videoInfo['reason'], $videoInfo['errorcode']);
+ } else {
+ throw new Exception('error raise by unknown status'.$videoInfo['status']);
+ }
+ }
+
+ // retrive url & save-file-path
+ $url = $videoInfo->makeDownloadUrl($this->_config['fmt']);
+
+ $client = $this->getHttpClient();
+
+ try {
+ $client->setUri($url);
+ $response = $client->request();
+ $response->setCleanup($this->_config['download_response_cleanup']);
+
+ return $response;
+ } catch (Zend_Http_Client_Exception $e) {
+ $uri = $client->getUri();
+ throw new Exception("request faild {$client->getUri()} - ".$e->getMessage(), $e->getCode());
+ }
+ }
+
+ public function downloadByVideoInfo(Wozozo_WWW_YouTube_VideoInfo $videoInfo)
+ {
+ $this->setupClientStream();
+ $response = $this->requestVideo($videoInfo);
+
+ $this->_putVideo($response, $videoInfo, $this->_config);
+ }
+
+ public function setupClientStream()
+ {
+ $stream = $this->_config['download_stream'];
+ $client = clone $this->getHttpClient();
+ // @see Zend_Http_Client::_openTempStream
+ // If original client's stream is set
+ if (is_string($stream)) {
+ $client->setStream($stream);
+ } else if ($stream == true) {
+ if ($client->getStream() == false) {
+ $client->setStream();
+ }
+ }
+
+ $this->setHttpClient($client);
+ }
+
+ public function getHttpClient()
+ {
+ if (!$this->_httpClient) {
+ require_once 'Zend/Http/Client.php';
+ $this->_httpClient = new Zend_Http_Client(null, array('useragent' => __CLASS__));
+ }
+
+ return $this->_httpClient;
+ }
+
+ public function setHttpClient(Zend_Http_Client $client)
+ {
+ $this->_httpClient = $client;
+ }
+
+ public function suggestSavePath($videoInfo)
+ {
+ $dir = $this->_config['save'];
+ $fmt = $this->_config['fmt'];
+ if ('PWD' === $dir) {
+ $dir = $_SERVER['PWD'];
+ } else {
+ if(!is_dir($dir)) {
+ throw new InvalidArgumentException('Invalid dir'.$dir);
+ }
+ }
+ $path = $dir . DIRECTORY_SEPARATOR . $videoInfo['video_id'] . self::detectSuffix($fmt);
+
+ return $path;
+ }
+
+ /**
+ * Request video file
+ *
+ * @param Wozozo_WWW_YouTube_VideoInfo
+
+ /**
+ * borrowed from WWW::YouTube::Download
+ */
+ public static function detectSuffix($fmt)
+ {
+ switch ($fmt) {
+ case '18' :
+ case '22' :
+ case '37' :
+ return '.mp4';
+ case '13' :
+ case '17' :
+ return '.3gp';
+ default :
+ return '.flv';
+ }
+ }
+
+ /**
+ * detect videoId
+ *
+ * @param string $var (url)
+ * @return string|false
+ */
+ public static function detectVideoId($var)
+ {
+ if (is_string($var)) {
+ if (!preg_match('#^h*(?:ttp\:\/\/)(.+\/watch\?v=.*)#', $var, $match)) {
+ return trim($var);
+ }
+ //uri
+ require_once 'Zend/Uri.php';
+ $var = Zend_Uri::factory('http://'.$match[1]);
+ }
+
+ if ($var instanceof Zend_Uri_Http) {
+ $query = $var->getQueryAsArray();
+ return $query['v'];
+ }
+
+ return false;
+ }
+
+ /*
+ public function __destruct()
+ {
+ if (is_string($s = $this->_config['output_stream']) && $this->_config['download_response_cleanup']) {
+ if (file_exists($s)) {
+ unlink($s);
+ }
+ }
+ }
+ */
+}
+
Wozozo_WWW_YouTube/tags/release-0.0.1-20100416003302/Wozozo/WWW/YouTube/HttpSocketProgressBar.php
@@ -0,0 +1,231 @@
+<?php
+
+require_once 'Zend/Http/Client/Adapter/Socket.php';
+class Wozozo_WWW_YouTube_HttpSocketProgressBar extends Zend_Http_Client_Adapter_Socket
+{
+ private $_max = 0;
+ private $_progressBar;
+
+ public function setMax($max)
+ {
+ $this->_max = $max;
+ }
+
+ public function getMax()
+ {
+ return $this->_max;
+ }
+
+ private function getProgressBar()
+ {
+ if (PHP_SAPI != 'cli') throw new RuntimeException();
+
+ if (!$this->_progressBar) {
+ require_once 'Zend/ProgressBar/Adapter/Console.php';
+ require_once 'Zend/ProgressBar.php';
+ $adapter = new Zend_ProgressBar_Adapter_Console();
+ $this->_progressBar = new Zend_ProgressBar($adapter, 0, $this->getMax());
+ }
+
+ return $this->_progressBar;
+
+ }
+
+ /**
+ * Read response from server
+ *
+ * @return string
+ */
+ public function read()
+ {
+ // First, read headers only
+ $response = '';
+ $gotStatus = false;
+ $stream = !empty($this->config['stream']);
+
+ while (($line = @fgets($this->socket)) !== false) {
+ $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
+ if ($gotStatus) {
+ $response .= $line;
+ if (rtrim($line) === '') break;
+ }
+ }
+
+ $this->_checkSocketReadTimeout();
+
+ $statusCode = Zend_Http_Response::extractCode($response);
+
+ // Handle 100 and 101 responses internally by restarting the read again
+ if ($statusCode == 100 || $statusCode == 101) return $this->read();
+
+ // Check headers to see what kind of connection / transfer encoding we have
+ $headers = Zend_Http_Response::extractHeaders($response);
+
+ /**
+ * Responses to HEAD requests and 204 or 304 responses are not expected
+ * to have a body - stop reading here
+ */
+ if ($statusCode == 304 || $statusCode == 204 ||
+ $this->method == Zend_Http_Client::HEAD) {
+
+ // Close the connection if requested to do so by the server
+ if (isset($headers['connection']) && $headers['connection'] == 'close') {
+ $this->close();
+ }
+ return $response;
+ }
+
+ // If we got a 'transfer-encoding: chunked' header
+ if (isset($headers['transfer-encoding'])) {
+
+ if (strtolower($headers['transfer-encoding']) == 'chunked') {
+
+ do {
+ $line = @fgets($this->socket);
+ $this->_checkSocketReadTimeout();
+
+ $chunk = $line;
+
+ // Figure out the next chunk size
+ $chunksize = trim($line);
+ if (! ctype_xdigit($chunksize)) {
+ $this->close();
+ require_once 'Zend/Http/Client/Adapter/Exception.php';
+ throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' .
+ $chunksize . '" unable to read chunked body');
+ }
+
+ // Convert the hexadecimal value to plain integer
+ $chunksize = hexdec($chunksize);
+
+ // Read next chunk
+ $read_to = ftell($this->socket) + $chunksize;
+
+ do {
+ $current_pos = ftell($this->socket);
+ if ($current_pos >= $read_to) break;
+
+ if($this->out_stream) {
+ if(stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ }
+ } else {
+ $line = @fread($this->socket, $read_to - $current_pos);
+ if ($line === false || strlen($line) === 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ }
+ $chunk .= $line;
+ }
+ } while (! feof($this->socket));
+
+ $chunk .= @fgets($this->socket);
+ $this->_checkSocketReadTimeout();
+
+ if(!$this->out_stream) {
+ $response .= $chunk;
+ }
+ } while ($chunksize > 0);
+ } else {
+ $this->close();
+ throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' .
+ $headers['transfer-encoding'] . '" transfer encoding');
+ }
+
+ // We automatically decode chunked-messages when writing to a stream
+ // this means we have to disallow the Zend_Http_Response to do it again
+ if ($this->out_stream) {
+ $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response);
+ }
+ // Else, if we got the content-length header, read this number of bytes
+ } elseif (isset($headers['content-length'])) {
+
+ // If we got more than one Content-Length header (see ZF-9404) use
+ // the last value sent
+ if (is_array($headers['content-length'])) {
+ $contentLength = $headers['content-length'][count($headers['content-length']) - 1];
+ } else {
+ $contentLength = $headers['content-length'];
+ }
+
+ if ($contentLength != 0) $this->setMax($contentLength);
+
+ $current_pos = ftell($this->socket);
+ $chunk = '';
+
+ $startProgressBar = false;
+ for ($read_to = $current_pos + $contentLength;
+ $read_to > $current_pos;
+ $current_pos = ftell($this->socket)) {
+
+ if ($startProgressBar === false) {
+ $this->setMax($read_to);
+ $startProgressBar = true;
+ }
+ $this->getProgressBar()->update($current_pos);
+
+ //if($this->out_stream) {
+
+ /*
+ if(@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ }
+ */
+ //} else {
+ $chunk = @fread($this->socket, $read_to - $current_pos);
+ if ($chunk === false || strlen($chunk) === 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ }
+
+ if ($this->out_stream) {
+ $fwrite = fwrite($this->out_stream, $chunk);
+ if ($fwrite === false) {
+ throw new Exception();
+ }
+ } else {
+ $response .= $chunk;
+ }
+ //}
+
+ // Break if the connection ended prematurely
+ if (feof($this->socket)) break;
+ }
+ if ($startProgressBar) $this->getProgressBar()->finish();
+
+ // Fallback: just read the response until EOF
+ } else {
+
+ do {
+ if($this->out_stream) {
+ if(@stream_copy_to_stream($this->socket, $this->out_stream) == 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ }
+ } else {
+ $buff = @fread($this->socket, 8192);
+ if ($buff === false || strlen($buff) === 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ } else {
+ $response .= $buff;
+ }
+ }
+
+ } while (feof($this->socket) === false);
+
+ $this->close();
+ }
+
+ // Close the connection if requested to do so by the server
+ if (isset($headers['connection']) && $headers['connection'] == 'close') {
+ $this->close();
+ }
+
+ return $response;
+ }
+
+}
+
Wozozo_WWW_YouTube/tags/release-0.0.1-20100416003302/Wozozo/WWW/YouTube/VideoInfo.php
@@ -0,0 +1,44 @@
+<?php
+
+class Wozozo_WWW_YouTube_VideoInfo implements ArrayAccess
+{
+ private $_parsedVideoInfo;
+
+ public function __construct(array $parsedVideoInfo)
+ {
+ $this->_parsedVideoInfo = $parsedVideoInfo;
+ }
+
+ public function makeDownloadUrl($fmt = '18')
+ {
+ $videoId = $this->_parsedVideoInfo['video_id'];
+ $token = $this->_parsedVideoInfo['token'];
+
+ return sprintf(Wozozo_WWW_YouTube::PATH_DOWNLOAD, $videoId, $token, $fmt);
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->_parsedVideoInfo[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->_parsedVideoInfo[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ $this->_parsedVideoInfo[$offset] = $value;
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->_parsedVideoInfo[$offset]);
+ }
+
+ public function toArray()
+ {
+ return $this->_parsedVideoInfo;
+ }
+}
Wozozo_WWW_YouTube/tags/release-0.0.1-20100416003302/Wozozo/WWW/YouTube/Tool/YoutubeProvider.php
@@ -0,0 +1,75 @@
+<?php
+require_once 'Wozozo/WWW/YouTube.php';
+require_once 'Zend/Tool/Framework/Provider/Abstract.php';
+class Wozozo_WWW_YouTube_Tool_YoutubeProvider extends Zend_Tool_Framework_Provider_Abstract
+{
+ protected $_specialties = array('Download');
+
+ public function echoDownload($id)
+ {
+ $videoId = Wozozo_WWW_YouTube::detectVideoId($id);
+ $youtube = $this->_loadYoutube();
+ $url = $youtube->getVideoInfo($videoId)->makeDownloadUrl();
+
+ $this->_out($url);
+ }
+
+ public function runDownload($id, $path = 'PWD')
+ {
+ $videoId = Wozozo_WWW_YouTube::detectVideoId($id);
+ $this->_out("Video ID :$videoId");
+
+ $youtube = $this->_loadYoutube();
+ $videoInfo = $youtube->getVideoInfo($videoId);
+ $this->_out("Status :". $videoInfo['status']);
+ if ($videoInfo['status'] != 'ok') {
+ throw new Exception("Status is not ok". implode(' ', $videoInfo->toArray()));
+ }
+ $this->_out("Title : ". $videoInfo['title']);
+ $this->_out("Length Seconds : ". $videoInfo['length_seconds']);
+
+ $client = $youtube->getHttpClient();
+ //ensure load adapter
+ $client->setAdapter('Wozozo_WWW_YouTube_HttpSocketProgressBar');
+
+ $youtube->setHttpClient($client);
+ if ($path) $youtube->setConfig(array('save' => $path));
+ $path = $youtube->suggestSavePath($videoInfo);
+
+ $this->_out("Downloading ..: ". $path);
+ $youtube->downloadByVideoInfo($videoInfo);
+ }
+
+ protected function _loadYoutube()
+ {
+ $youtube = new Wozozo_WWW_YouTube();
+ if ($config = $this->_loadConfig('youtube')) {
+ if ($config->httpClient) {
+ require_once 'Zend/Http/Client.php';
+ $client = new Zend_Http_Client(null, $config->httpClient);
+ $youtube->setHttpClient($client);
+ }
+ $youtube->setConfig($config);
+ }
+
+ return $youtube;
+ }
+
+ protected function _loadConfig($key)
+ {
+ return false;
+
+ $userConfig = $this->_registry->getConfig();
+
+ return $userConfig->$key;
+ }
+
+ protected function _out($string)
+ {
+ //if ()
+ $this->_registry->getResponse()->appendContent($string);
+
+ //echo $string, PHP_EOL;
+ }
+}
+