3

我正在使用 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;

我也尝试过 curl ,exec()但后来发生了更奇怪的事情:

curl 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 '内容类型:多部分/相关;边界 =“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 文件,如此处所述

发现另一个有同样问题的人提供了代码来分块读取和发送文件。但是,当尝试此方法时,Youtube 会返回一个 411 页面,说明需要“Content-Length”标头。我正在设置内容长度标头,所以这可能是一个错误。此方法使用fsockopen()函数而不是 cURL。[实际上,再次查看代码,我意识到我只是用“\n”而不是“\r\n”分隔标题。这可能是问题所在。我也会尝试回车]

编辑2:

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

那里有任何 Curl 专家可以帮助我完成这项工作吗?我完全被这个难住了。

4

4 回答 4

3

发送前尽量不要将整个文件读入内存。curl 恕我直言,支持在上传之前读取文件本身,因此应该在内存边界内工作。有关示例,请参阅以下博客文章: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...

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

于 2010-01-24T18:51:26.840 回答
0

尝试做:

ini_set('memory_limit', -1);

它有效吗?

于 2010-01-24T07:49:40.233 回答
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);
于 2010-01-24T08:28:57.307 回答
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];
}
于 2013-06-20T00:40:15.560 回答