powered by nequal
Home » Wozozo_WWW_YouTube » Timeline » 1797

Changeset 1797 -- 2010-04-16 00:33:02

Comment
[Package Release] Wozozo_WWW_YouTube

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;
+    }
+}
+