通过Youtube API上传大型视频导致内存不足

时间:2010-01-24 07:42:16

标签: php memory-management curl youtube-api

我正在使用PHP通过直接上传到Youtube发送视频。它适用于较小尺寸的视频,但在尝试发送390 MB视频时,我收到以下错误:

  

PHP致命错误:内存不足   (已分配3932160)(试图分配   390201902字节)

我尝试增加memory_limit,但这没有帮助。

    if ($isFile) {
        ini_set('memory_limit', '2G')
        $data = file_get_contents($data);
    }

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

    $out = curl_exec($ch);
    curl_close($ch);

    return $out;

我也试过通过exec()运行卷曲,但是甚至更奇怪的事情发生了:

  

卷曲   http://uploads.gdata.youtube.com/feeds/api/users/default/uploads   -H'POST / feeds / api / users / default / uploads   HTTP / 1.1'-H'主机:   uploads.gdata.youtube.com'-H   '授权:OAuth [snip oauth   info]“'-H'GData-Version:2'-H   'X-GData-Client:www.mywebsite.com'   -H'X-GData-Key:key = [snip]'-H'Slug:video.AVI'-H'Content-Type:   多部分/相关;   boundary =“iUI5C0hzisAHkx9SvaRJ”' - H   '内容长度:390193710'-H   '连接:关闭'-d   /tmp/youtube.xml

/tmp/youtube.xml是我保存要上传的数据文件的地方。也许这种用法是错误的?

这将需要大约6分钟,所以看起来文件正在发送但是我得到一个空的回复:

% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed

 0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
 0     0    0     0    0     0      0      0 --:--:--  0:00:02 --:--:--     0
 ...
 0     0    0     0    0     0      0      0 --:--:--  0:06:00 --:--:--     0
curl: (52) Empty reply from server

编辑:

我正在使用OAuth,所以我不能使用普通的PHP API库。我必须上传一个XML文件,其中包含XML文件中包含的视频二进制数据outlined here

found another person with the same problem提供了代码来读取并以块的形式发送文件。但是,在尝试此方法时,Youtube将返回411页,说“Content-Length”标题是必需的。我正在设置内容长度标题,所以这可能是一个错误。此方法使用fsockopen()函数而不是cURL。 [实际上,再次查看代码,我意识到我只是用“\ n”而不是“\ r \ n”来分隔标题。这可能是个问题。我会尝试回车]

编辑2:

我认为“\ r \ n”有效,但现在有了代码,我再次收到Youtube的回复。

那里有任何卷曲专家可以帮我搞定这个吗?我完全被这个难过了。

4 个答案:

答案 0 :(得分:3)

尝试在发送之前不要将整个文件读入内存。卷曲恕我直言,支持在上传之前阅读文件,因此应该在内存边框内工作。有关示例,请参阅以下博客文章:http://dtbaker.com.au/random-bits/uploading-a-file-using-curl-in-php.html

我还没有使用youtube直接上传API,但经过快速浏览后,我发现这似乎不是正常的html表单文件上传,而是更复杂的数据格式。我不确定你是否可以用普通的cURL做这个,而不用自己在内存中构建整个POST数据。

如果您有这么小的内存限制(大约4 MB的RAM),您可以尝试在PHP的流API之上构建自己的简单HTTP客户端:创建一个临时文件并将您的POST请求数据写入该文件句柄(使用fwrite()表示普通字符串,使用stream_copy_to_stream()表示直接文件到文件的数据传输。请求准备就绪后,将临时文件回滚到开头,然后将该流复制到与youtube http服务器的连接中(再次使用stream_copy_to_stream())。

由于流是以小块的形式复制的,因此即使对于大型文件,也应该能够使用少于4 MB的RAM进行复制。

编辑:

遵循伪php-code-mashup应该会有所帮助;)

$xmlAPIRequest = /* fill with the XML-API-Request */
$boundaryString = /* fill me with some random data */

// create temporary file handle
$pdh = tmpfile();
fwrite($pdh, "--$boundaryString\r\n");
fwrite($pdh, "Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n");
fwrite($pdh, $xmlAPIRequest."\r\n");
fwrite($pdh, "--$boundaryString\r\n");
fwrite($pdh, "Content-Type: <video_content_type>\r\nContent-Transfer-Encoding: binary\r\n\r\n");

$videoFile = fopen("/path/to/video", "r");
stream_copy_to_stream($videoFile, $pdh);
fclose($videoFile);

fwrite($pdh, "--$boundaryString--\r\n");
/* not quite sure, whether there needs to be another linebreak before the boundary string */

$info = fstat($pdh);
rewind($pdh);

$contentLength = $info['size'];

$conn = fsockopen("hostname", 80);
/* write http request to $conn and use $contentLength for Content-Length header */
/* after last header you put another line break to tell them that now the body follows */

// write post data from stream to stream
stream_copy_to_stream($pdh, $conn);

// ... process response... etc...

这段代码肯定存在很多错误,但由于它只是一个简短的例子,我认为我们可以忍受这一点。 ;)

答案 1 :(得分:0)

尝试做:

ini_set('memory_limit', -1);

有效吗?

答案 2 :(得分:0)

怎么样

$filename = "--REMOTE FILE--";
$localfile = "/storage/local.flv";

$handle = fopen($filename, "r");

while ($contents = fread($handle, 10485760)) { // thats 10 MB

$localhandle = fopen($localfile, "a");
fwrite ($localhandle, $contents);
fclose($localhandle);

}

fclose($handle);

答案 3 :(得分:0)

这是我用来实现这个的代码:

public function uploadVideo(Model_Row_Video $video)
{
        $request = $this->getOAuthRequest(self::URL_UPLOAD, 'POST');

        $boundary = 'RANDOM BOUNDARY STRING';

        $videoFile = $video->getAbsolutePath();
        $contentType = $this->getContentType($videoFile);

        $xml = $this->getAtom($video);
        $data = <<<EOD
--{$boundary}
Content-Type: application/atom+xml; charset=UTF-8

{$xml}
--{$boundary}
Content-Type: {$contentType}
Content-Transfer-Encoding: binary\r\n\r\n
EOD;
        $footer = "\r\n--{$boundary}--\r\n";

        $pdh = tmpfile();
        fwrite($pdh, $data);
        $f_video = fopen($videoFile, "r");
        stream_copy_to_stream($f_video, $pdh);
        fclose($f_video);
        fwrite($pdh, $footer);

        $info = fstat($pdh);

        $headers = array(
            "POST /feeds/api/users/default/uploads HTTP/1.1",
            'Host: uploads.gdata.youtube.com',
            $request->to_header(),
            'GData-Version: 2',
            'X-GData-Client: ' . self::CONSUMER_KEY,
            'X-GData-Key: key=' . self::DEVELOPER_KEY,
            'Slug: ' . $video['local'],
            'Content-Type: multipart/related; boundary="' . $boundary . '"',
            'Content-Length: ' . $info['size'],
            'Connection: close'
        );

        $headers_str = implode("\r\n", $headers) . "\r\n\r\n";
        rewind($pdh);

        $conn = fsockopen('uploads.gdata.youtube.com', 80, $errno, $errstr, 30);
        fputs($conn, $headers_str);
        stream_copy_to_stream($pdh, $conn);

        $return = '';
        while (!feof($conn)) {
            $return .= fgets($conn, 128);
        }

        fclose($conn);

        echo "errno: $errno\n";
        echo "errstr: $errstr\n";
        $out = strstr($return, '<?xml');

        $xml = simplexml_load_string($out);

        if (!$xml) {
            echo $out;
        }

        $xml->registerXPathNamespace('yt','http://gdata.youtube.com/schemas/2007');
        $id = $xml->xpath('//yt:videoid');

        return $id[0];
}