AliyunVodUploader.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. <?php
  2. /**
  3. * Created by Aliyun ApsaraVideo VoD.
  4. * User: https://www.aliyun.com/product/vod
  5. */
  6. use vod\Request\V20170321 as vod;
  7. use OSS\OssClient;
  8. use OSS\Core\OssUtil;
  9. class AliyunVodUploader
  10. {
  11. public function __construct($accessKeyId, $accessKeySecret, $apiRegionId=null)
  12. {
  13. $this->accessKeyId = $accessKeyId;
  14. $this->accessKeySecret = $accessKeySecret;
  15. if (empty($apiRegionId)) {
  16. // VoD的接入地址,国内为cn-shanghai,参考:https://help.aliyun.com/document_detail/98194.html
  17. $this->apiRegionId = 'cn-beijing';
  18. }
  19. else {
  20. $this->apiRegionId = $apiRegionId;
  21. }
  22. // VOD和OSS的连接超时时间,单位秒
  23. $this->connTimeout = 1;
  24. // 设置VOD请求超时时间,单位秒
  25. $this->vodTimeout = 3;
  26. // 设置OSS请求超时时间,单位秒,默认是7天, 建议不要设置太小,如果上传文件很大,消耗的时间会比较长
  27. $this->ossTimeout = 86400*7;
  28. // 失败后的最大重试次数
  29. $this->maxRetryTimes = 3;
  30. $this->multipartThreshold = 10 * 1024 * 1024;
  31. $this->multipartPartSize = 10 * 1024 * 1024;
  32. $this->multipartThreadsNum = 3;
  33. $this->vodClient = $this->initVodClient();
  34. $this->ossClient = null;
  35. $this->ecsRegionId = null;
  36. $this->enableSSL = false;
  37. }
  38. /**
  39. * 设置上传脚本部署的ECS区域(如有),当检测上传区域和点播存储区域位于同一区域时,会自动开启内网上传,能节省流量费用且上传速度更快
  40. * @param string, $regionId 可选值参考:https://help.aliyun.com/document_detail/40654.html,如cn-shanghai
  41. */
  42. public function setEcsRegionId($regionId)
  43. {
  44. $this->ecsRegionId = $regionId;
  45. }
  46. /**
  47. * 是否启用SSL(网络请求使用HTTPS),默认不启用,以避免相关扩展未安装或配置异常时无法使用
  48. * @param bool, $isEnable 可选值参考:https://help.aliyun.com/document_detail/40654.html,如cn-shanghai
  49. */
  50. public function setEnableSSL($isEnable)
  51. {
  52. $this->enableSSL = $isEnable;
  53. }
  54. /**
  55. * 上传本地视频或音频文件到点播,最大支持48.8TB的单个文件,暂不支持断点续传
  56. * @param UploadVideoRequest $uploadVideoRequest: UploadVideoRequest类的实例,注意filePath为本地文件的绝对路径
  57. * @return string, $videoId
  58. * @throws Exception
  59. */
  60. public function uploadLocalVideo($uploadVideoRequest)
  61. {
  62. $uploadInfo = $this->createUploadVideo($uploadVideoRequest);
  63. $headers = $this->getUploadHeaders($uploadVideoRequest);
  64. $this->uploadOssObject($uploadVideoRequest->getFilePath(), $uploadInfo->UploadAddress->FileName, $uploadInfo, $headers);
  65. return $uploadInfo->VideoId;
  66. }
  67. /**
  68. * 上传网络视频或音频文件到点播,最大支持48.8TB的单个文件(需本地磁盘空间足够);会先下载到本地临时目录,再上传到点播存储
  69. * @param UploadVideoRequest $uploadVideoRequest: UploadVideoRequest类的实例,注意filePath为网络文件的URL地址
  70. * @return string, $videoId
  71. * @throws Exception
  72. */
  73. public function uploadWebVideo($uploadVideoRequest)
  74. {
  75. // 下载文件
  76. $uploadVideoRequest = $this->downloadWebMedia($uploadVideoRequest);
  77. // 上传到点播
  78. $uploadInfo = $this->createUploadVideo($uploadVideoRequest);
  79. $headers = $this->getUploadHeaders($uploadVideoRequest);
  80. $this->uploadOssObject($uploadVideoRequest->getFilePath(), $uploadInfo->UploadAddress->FileName, $uploadInfo, $headers);
  81. // 删除下载的本地视频
  82. unlink($uploadVideoRequest->getFilePath());
  83. return $uploadInfo->VideoId;
  84. }
  85. /**
  86. * 上传本地图片文件到点播,最大支持48.8TB的单个文件
  87. * @param UploadImageRequest $uploadImageRequest: UploadImageRequest类的实例,注意filePath为本地文件的绝对路径
  88. * @return array, array('ImageId'=>'xxxxx', 'ImageURL'=>'http://xxx.jpg')
  89. * @throws Exception
  90. */
  91. public function uploadLocalImage($uploadImageRequest)
  92. {
  93. $uploadInfo = $this->createUploadImage($uploadImageRequest);
  94. $this->uploadOssObject($uploadImageRequest->getFilePath(), $uploadInfo->UploadAddress->FileName, $uploadInfo);
  95. return array('ImageId'=>$uploadInfo->ImageId, 'ImageURL'=>$uploadInfo->ImageURL);
  96. }
  97. /**
  98. * 上传网络图片文件到点播,最大支持48.8TB的单个文件(需本地磁盘空间足够);会先下载到本地临时目录,再上传到点播存储
  99. * @param UploadImageRequest $uploadVideoRequest: UploadImageRequest类的实例,注意filePath为网络文件的URL地址
  100. * @return array, array('ImageId'=>'xxxxx', 'ImageURL'=>'http://xxx.jpg')
  101. * @throws Exception
  102. */
  103. public function uploadWebImage($uploadImageRequest)
  104. {
  105. // 下载文件
  106. $uploadImageRequest = $this->downloadWebMedia($uploadImageRequest);
  107. // 上传到点播
  108. $uploadInfo = $this->createUploadImage($uploadImageRequest);
  109. $this->uploadOssObject($uploadImageRequest->getFilePath(), $uploadInfo->UploadAddress->FileName, $uploadInfo);
  110. // 删除下载的本地图片
  111. unlink($uploadImageRequest->getFilePath());
  112. return array('ImageId'=>$uploadInfo->ImageId, 'ImageURL'=>$uploadInfo->ImageURL);
  113. }
  114. /**
  115. * 上传本地辅助媒资文件(如水印、字幕等)到点播,最大支持48.8TB的单个文件
  116. * @param UploadAttachedMediaRequest $uploadAttachedRequest: UploadAttachedMediaRequest类的实例,注意filePath为本地文件的绝对路径
  117. * @return array, array('MediaId'=>'xxxxx', 'MediaURL'=>'http://xxx.jpg', 'FileURL'=>'http://xxx.jpg')
  118. * @throws Exception
  119. */
  120. public function uploadLocalAttachedMedia($uploadAttachedRequest)
  121. {
  122. $uploadInfo = $this->createUploadAttachedMedia($uploadAttachedRequest);
  123. $this->uploadOssObject($uploadAttachedRequest->getFilePath(), $uploadInfo->UploadAddress->FileName, $uploadInfo);
  124. return array('MediaId'=>$uploadInfo->MediaId, 'MediaURL'=>$uploadInfo->MediaURL, 'FileURL'=>$uploadInfo->FileURL);
  125. }
  126. /**
  127. * 上传网络辅助媒资文件(如水印、字幕等)到点播,最大支持48.8TB的单个文件(需本地磁盘空间足够);会先下载到本地临时目录,再上传到点播存储
  128. * @param UploadAttachedMediaRequest $uploadAttachedRequest: UploadAttachedMediaRequest类的实例,注意filePath为网络文件的URL地址
  129. * @return array, array('MediaId'=>'xxxxx', 'MediaURL'=>'http://xxx.jpg', 'FileURL'=>'http://xxx.jpg')
  130. * @throws Exception
  131. */
  132. public function uploadWebAttachedMedia($uploadAttachedRequest)
  133. {
  134. // 下载文件
  135. $uploadAttachedRequest = $this->downloadWebMedia($uploadAttachedRequest);
  136. // 上传到点播
  137. $uploadInfo = $this->createUploadAttachedMedia($uploadAttachedRequest);
  138. $this->uploadOssObject($uploadAttachedRequest->getFilePath(), $uploadInfo->UploadAddress->FileName, $uploadInfo);
  139. // 删除下载的本地文件
  140. unlink($uploadAttachedRequest->getFilePath());
  141. return array('MediaId'=>$uploadInfo->MediaId, 'MediaURL'=>$uploadInfo->MediaURL, 'FileURL'=>$uploadInfo->FileURL);
  142. }
  143. /**
  144. * 上传本地m3u8视频或音频文件到点播,m3u8文件和分片文件默认在同一目录($sliceFiles为null时,会按照同一目录去解析分片地址)
  145. * @param UploadVideoRequest $uploadVideoRequest: 注意filePath为本地m3u8索引文件的绝对路径,
  146. * 且m3u8文件的分片信息必须是相对地址,不能含有URL或本地绝对路径
  147. * @param array $sliceFiles: ts分片文件的绝对路径列表,如指定则以此为准,若不指定,则自动解析$uploadVideoRequest里的m3u8文件
  148. * @return string, $videoId
  149. * @throws Exception
  150. */
  151. public function uploadLocalM3u8($uploadVideoRequest, $sliceFiles)
  152. {
  153. if (!is_array($sliceFiles) || empty($sliceFiles)) {
  154. throw new Exception("m3u8 slice files invalid", AliyunVodError::VOD_INVALID_M3U8_SLICE_FILE);
  155. }
  156. // 上传到点播的m3u8索引文件会重写,以此确保分片地址都为相对地址
  157. $downloader = new AliyunVodDownloader();
  158. $m3u8LocalDir = $downloader->getSaveLocalDir() . md5($uploadVideoRequest->getFileName()) . '/';
  159. $downloader->setSaveLocalDir($m3u8LocalDir);
  160. $m3u8LocalPath = $m3u8LocalDir . basename($uploadVideoRequest->getFileName());
  161. $this->rewriteM3u8File($uploadVideoRequest->getFilePath(), $m3u8LocalPath);
  162. // 解析分片文件地址和文件名
  163. $sliceList = array();
  164. if (isset($sliceFiles)) {
  165. foreach ($sliceFiles as $sliceFilePath) {
  166. $arr = AliyunVodUtils::getFileName($sliceFilePath);
  167. $sliceList[] = array($sliceFilePath, $arr[1]);
  168. }
  169. }
  170. // 获取上传地址和凭证
  171. $uploadVideoRequest->setFilePath($m3u8LocalPath);
  172. $uploadInfo = $this->createUploadVideo($uploadVideoRequest);
  173. $headers = $this->getUploadHeaders($uploadVideoRequest);
  174. // 依次上传分片文件
  175. foreach ($sliceList as $slice) {
  176. $this->uploadOssObject($slice[0], $uploadInfo->UploadAddress->ObjectPrefix . $slice[1], $uploadInfo, $headers);
  177. }
  178. // 上传m3u8索引文件
  179. $this->uploadOssObject($uploadVideoRequest->getFilePath(), $uploadInfo->UploadAddress->FileName, $uploadInfo, $headers);
  180. // 删除重写到本地的m3u8文件
  181. unlink($m3u8LocalPath);
  182. return $uploadInfo->VideoId;
  183. }
  184. /**
  185. * 上传网络m3u8视频或音频文件到点播,需本地磁盘空间足够,会先下载到本地临时目录,再上传到点播存储
  186. * @param UploadVideoRequest $uploadVideoRequest: 注意filePath为网络m3u8索引文件的URL地址,
  187. 且m3u8文件的分片信息必须是相对地址,不能含有URL或本地绝对路径
  188. * @param array $sliceFileUrls: ts分片文件的URL地址列表;可自行拼接ts分片的URL地址列表,或者使用parseWebM3u8解析
  189. * @return string, $videoId
  190. * @throws Exception
  191. */
  192. public function uploadWebM3u8($uploadVideoRequest, $sliceFileUrls)
  193. {
  194. if (!is_array($sliceFileUrls) || empty($sliceFileUrls)) {
  195. throw new Exception("m3u8 slice files invalid", AliyunVodError::VOD_INVALID_M3U8_SLICE_FILE);
  196. }
  197. // 下载m3u8文件和所有ts分片文件到本地;上传到点播的m3u8索引文件会重写,确保分片地址都为相对地址
  198. $downloader = new AliyunVodDownloader();
  199. $m3u8LocalDir = $downloader->getSaveLocalDir() . md5($uploadVideoRequest->getFileName()) . '/';
  200. $downloader->setSaveLocalDir($m3u8LocalDir);
  201. $m3u8LocalPath = $m3u8LocalDir . basename($uploadVideoRequest->getFileName());
  202. $this->rewriteM3u8File($uploadVideoRequest->getFilePath(), $m3u8LocalPath);
  203. $sliceList = array();
  204. foreach ($sliceFileUrls as $sliceFileUrl) {
  205. $arr = AliyunVodUtils::getFileName($sliceFileUrl);
  206. $sliceLocalPath = $downloader->downloadFile($sliceFileUrl, $arr[1]);
  207. if ($sliceLocalPath === false) {
  208. throw new Exception("ts file download fail: ".$sliceFileUrl, AliyunVodError::VOD_ERR_FILE_DOWNLOAD);
  209. }
  210. $sliceList[] = array($sliceLocalPath, $arr[1]);
  211. }
  212. // 获取上传地址和凭证
  213. $uploadVideoRequest->setFilePath($m3u8LocalPath);
  214. $uploadInfo = $this->createUploadVideo($uploadVideoRequest);
  215. $headers = $this->getUploadHeaders($uploadVideoRequest);
  216. // 依次上传分片文件
  217. foreach ($sliceList as $slice) {
  218. $this->uploadOssObject($slice[0], $uploadInfo->UploadAddress->ObjectPrefix . $slice[1], $uploadInfo, $headers);
  219. }
  220. // 上传m3u8索引文件
  221. $this->uploadOssObject($uploadVideoRequest->getFilePath(), $uploadInfo->UploadAddress->FileName, $uploadInfo, $headers);
  222. // 删除下载到本地的文件
  223. unlink($m3u8LocalPath);
  224. foreach ($sliceList as $slice) {
  225. unlink($slice[0]);
  226. }
  227. rmdir($m3u8LocalDir);
  228. return $uploadInfo->VideoId;
  229. }
  230. /**
  231. * 解析m3u8文件得到所有分片文件地址列表,原理是将m3u8地址前缀拼接ts分片名称作为后者的地址;同时支持本地、网络m3u8文件的解析
  232. 本函数解析时会默认分片文件和m3u8文件位于同一目录,如不是则请自行拼接分片文件的地址列表
  233. * @param m3u8FileUrl: string, m3u8网络文件url,例如:/opt/sample.m3u8 或 http://host/sample.m3u8
  234. * @return array, $sliceFileUrls
  235. * @throws Exception
  236. */
  237. public function parseM3u8File($m3u8FilePath) {
  238. $sliceFileUrls = array();
  239. $str = @file_get_contents(OssUtil::encodePath($m3u8FilePath));
  240. if ($str === false) {
  241. throw new Exception("m3u8 file access fail: ".$m3u8FilePath, AliyunVodError::VOD_ERR_FILE_READ);
  242. }
  243. $lines = explode("\n", $str);
  244. foreach ($lines as $line) {
  245. $sliceFileName = trim($line);
  246. if (strlen($sliceFileName) <= 0) {
  247. continue;
  248. }
  249. if (AliyunVodUtils::startsWith($sliceFileName,'#')) {
  250. continue;
  251. }
  252. $sliceFile = AliyunVodUtils::replaceFileName($m3u8FilePath, $sliceFileName);
  253. if ($sliceFile === false) {
  254. throw new Exception("m3u8 file invalid", AliyunVodError::VOD_INVALID_M3U8_SLICE_FILE);
  255. }
  256. $sliceFileUrls[] = $sliceFile;
  257. }
  258. return $sliceFileUrls;
  259. }
  260. private function rewriteM3u8File($srcM3u8FilePath, $dstM3u8FilePath) {
  261. $str = @file_get_contents(OssUtil::encodePath($srcM3u8FilePath));
  262. if ($str === false) {
  263. throw new Exception("m3u8 file access fail: ".$srcM3u8FilePath, AliyunVodError::VOD_ERR_M3U8_FILE_REWRITE);
  264. }
  265. $lines = explode("\n", $str);
  266. $newM3u8Text = '';
  267. foreach ($lines as $line) {
  268. $sliceFileName = trim($line);
  269. if (strlen($sliceFileName) <= 0) {
  270. continue;
  271. }
  272. if (AliyunVodUtils::startsWith($sliceFileName,'#')) {
  273. $newM3u8Text .= $sliceFileName . "\n";
  274. continue;
  275. }
  276. $arr = AliyunVodUtils::getFileName($sliceFileName);
  277. $newM3u8Text .= $arr[1] . "\n";
  278. }
  279. $res = AliyunVodUtils::mkDir($dstM3u8FilePath);
  280. if ($res === false) {
  281. throw new Exception("m3u8 file mkdir fail: ".$dstM3u8FilePath, AliyunVodError::VOD_ERR_M3U8_FILE_REWRITE);
  282. }
  283. $res = @file_put_contents(OssUtil::encodePath($dstM3u8FilePath), $newM3u8Text);
  284. if ($res === false) {
  285. throw new Exception("m3u8 file rewrite fail: ".$dstM3u8FilePath, AliyunVodError::VOD_ERR_M3U8_FILE_REWRITE);
  286. }
  287. return true;
  288. }
  289. private function downloadWebMedia($request)
  290. {
  291. // 下载视频文件到本地临时目录
  292. $downloader = new AliyunVodDownloader();
  293. $localFileName = sprintf("%s.%s", md5($request->getFileName()), $request->getMediaExt());
  294. $webFilePath = $request->getFilePath();
  295. $localFilePath = $downloader->downloadFile($webFilePath, $localFileName);
  296. if ($localFilePath === false) {
  297. throw new Exception("file download fail: ".$webFilePath, AliyunVodError::VOD_ERR_FILE_DOWNLOAD);
  298. }
  299. // 重新设置上传请求对象
  300. $request->setFilePath($localFilePath);
  301. return $request;
  302. }
  303. private function initOssClient($uploadAuth, $uploadAddress)
  304. {
  305. $endpoint = AliyunVodUtils::convertOssInternal($uploadAddress->Endpoint, $this->ecsRegionId, $this->enableSSL);
  306. //printf("oss endpoint: %s\n", $endpoint);
  307. $this->ossClient = new OssClient($uploadAuth->AccessKeyId, $uploadAuth->AccessKeySecret, $endpoint,
  308. false, $uploadAuth->SecurityToken);
  309. $this->ossClient->setTimeout($this->ossTimeout);
  310. $this->ossClient->setConnectTimeout($this->connTimeout);
  311. return $this->ossClient;
  312. }
  313. private function initVodClient()
  314. {
  315. HttpHelper::$connectTimeout = $this->connTimeout;
  316. HttpHelper::$readTimeout = $this->vodTimeout;
  317. $profile = DefaultProfile::getProfile($this->apiRegionId, $this->accessKeyId, $this->accessKeySecret, $this->securityToken);
  318. //DefaultProfile::addEndpoint($this->apiRegionId, $this->apiRegionId, "vod", "vod.".$this->apiRegionId.".aliyuncs.com");
  319. return new DefaultAcsClient($profile);
  320. }
  321. private function getUploadHeaders($uploadVideoRequest) {
  322. $switch = $uploadVideoRequest->getWatermarkSwitch();
  323. if (is_null($switch)) {
  324. return null;
  325. }
  326. $userData = sprintf("{\"Vod\":{\"UserData\":{\"IsShowWaterMark\": \"%s\"}}}", $switch);
  327. return array('x-oss-notification' => base64_encode($userData));
  328. }
  329. private function createUploadVideo($uploadVideoRequest)
  330. {
  331. $request = new vod\CreateUploadVideoRequest();
  332. $title = AliyunVodUtils::subString($uploadVideoRequest->getTitle(), AliyunVodUtils::VOD_MAX_TITLE_LENGTH);
  333. $request->setTitle($title);
  334. $request->setFileName($uploadVideoRequest->getFileName());
  335. if (!is_null($uploadVideoRequest->getDescription())) {
  336. $request->setDescription($uploadVideoRequest->getDescription());
  337. }
  338. if (!is_null($uploadVideoRequest->getCateId())) {
  339. $request->setCateId($uploadVideoRequest->getCateId());
  340. }
  341. if (!is_null($uploadVideoRequest->getTags())) {
  342. $request->setTags($uploadVideoRequest->getTags());
  343. }
  344. if (!is_null($uploadVideoRequest->getCoverURL())) {
  345. $request->setCoverURL($uploadVideoRequest->getCoverURL());
  346. }
  347. if (!is_null($uploadVideoRequest->getTemplateGroupId())) {
  348. $request->setTemplateGroupId($uploadVideoRequest->getTemplateGroupId());
  349. }
  350. if (!is_null($uploadVideoRequest->getStorageLocation())) {
  351. $request->setStorageLocation($uploadVideoRequest->getStorageLocation());
  352. }
  353. if (!is_null($uploadVideoRequest->getUserData())) {
  354. $request->setUserData($uploadVideoRequest->getUserData());
  355. }
  356. if (!is_null($uploadVideoRequest->getAppId())) {
  357. $request->setAppId($uploadVideoRequest->getAppId());
  358. }
  359. if (!is_null($uploadVideoRequest->getWorkflowId())) {
  360. $request->setWorkflowId($uploadVideoRequest->getWorkflowId());
  361. }
  362. $data = $this->requestUploadInfo($request, 'video');
  363. AliyunVodLog::printLog("CreateUploadVideo, FilePath: %s, VideoId: %s", $uploadVideoRequest->getFilePath(), $data->VideoId);
  364. return $data;
  365. }
  366. private function requestUploadInfo($request, $mediaType)
  367. {
  368. $request->setAcceptFormat('JSON');
  369. $data = $this->vodClient->getAcsResponse($request, null, null, true, $this->maxRetryTimes);
  370. $data->OriUploadAddress = $data->UploadAddress;
  371. $data->OriUploadAuth = $data->UploadAuth;
  372. $data->UploadAddress = json_decode(base64_decode($data->OriUploadAddress));
  373. $data->UploadAuth = json_decode(base64_decode($data->OriUploadAuth));
  374. $data->MediaType = $mediaType;
  375. if ($mediaType == 'video') {
  376. $data->MediaId = $data->VideoId;
  377. }
  378. elseif ($mediaType == 'image') {
  379. $data->MediaId = $data->ImageId;
  380. $data->MediaURL = $data->ImageURL;
  381. }
  382. return $data;
  383. }
  384. private function refreshUploadVideo($videoId)
  385. {
  386. $request = new vod\RefreshUploadVideoRequest();
  387. $request->setVideoId($videoId);
  388. $data = $this->requestUploadInfo($request, 'video');
  389. AliyunVodLog::printLog("RefreshUploadVideo, VideoId: %s", $data->VideoId);
  390. return $data;
  391. }
  392. private function createUploadImage($uploadImageRequest)
  393. {
  394. $request = new vod\CreateUploadImageRequest();
  395. $request->setImageType($uploadImageRequest->getImageType());
  396. $request->setImageExt($uploadImageRequest->getImageExt());
  397. if (!is_null($uploadImageRequest->getTitle())) {
  398. $title = AliyunVodUtils::subString($uploadImageRequest->getTitle(), AliyunVodUtils::VOD_MAX_TITLE_LENGTH);
  399. $request->setTitle($title);
  400. }
  401. if (!is_null($uploadImageRequest->getDescription())) {
  402. $request->setDescription($uploadImageRequest->getDescription());
  403. }
  404. if (!is_null($uploadImageRequest->getCateId())) {
  405. $request->setCateId($uploadImageRequest->getCateId());
  406. }
  407. if (!is_null($uploadImageRequest->getTags())) {
  408. $request->setTags($uploadImageRequest->getTags());
  409. }
  410. if (!is_null($uploadImageRequest->getStorageLocation())) {
  411. $request->setStorageLocation($uploadImageRequest->getStorageLocation());
  412. }
  413. if (!is_null($uploadImageRequest->getUserData())) {
  414. $request->setUserData($uploadImageRequest->getUserData());
  415. }
  416. if (!is_null($uploadImageRequest->getAppId())) {
  417. $request->setAppId($uploadImageRequest->getAppId());
  418. }
  419. if (!is_null($uploadImageRequest->getWorkflowId())) {
  420. $request->setWorkflowId($uploadImageRequest->getWorkflowId());
  421. }
  422. $data = $this->requestUploadInfo($request, 'image');
  423. AliyunVodLog::printLog("CreateUploadImage, FilePath: %s, ImageId: %s, ImageURL: %s",
  424. $uploadImageRequest->getFilePath(), $data->ImageId, $data->ImageURL);
  425. return $data;
  426. }
  427. private function createUploadAttachedMedia($uploadAttachedRequest)
  428. {
  429. $request = new vod\CreateUploadAttachedMediaRequest();
  430. $request->setBusinessType($uploadAttachedRequest->getBusinessType());
  431. $request->setMediaExt($uploadAttachedRequest->getMediaExt());
  432. if (!is_null($uploadAttachedRequest->getTitle())) {
  433. $title = AliyunVodUtils::subString($uploadAttachedRequest->getTitle(), AliyunVodUtils::VOD_MAX_TITLE_LENGTH);
  434. $request->setTitle($title);
  435. }
  436. if (!is_null($uploadAttachedRequest->getDescription())) {
  437. $request->setDescription($uploadAttachedRequest->getDescription());
  438. }
  439. if (!is_null($uploadAttachedRequest->getCateId())) {
  440. $request->setCateId($uploadAttachedRequest->getCateId());
  441. }
  442. if (!is_null($uploadAttachedRequest->getTags())) {
  443. $request->setTags($uploadAttachedRequest->getTags());
  444. }
  445. if (!is_null($uploadAttachedRequest->getStorageLocation())) {
  446. $request->setStorageLocation($uploadAttachedRequest->getStorageLocation());
  447. }
  448. if (!is_null($uploadAttachedRequest->getFileSize())) {
  449. $request->setFileSize($uploadAttachedRequest->getFileSize());
  450. }
  451. if (!is_null($uploadAttachedRequest->getUserData())) {
  452. $request->setUserData($uploadAttachedRequest->getUserData());
  453. }
  454. if (!is_null($uploadAttachedRequest->getAppId())) {
  455. $request->setAppId($uploadAttachedRequest->getAppId());
  456. }
  457. if (!is_null($uploadAttachedRequest->getWorkflowId())) {
  458. $request->setWorkflowId($uploadAttachedRequest->getWorkflowId());
  459. }
  460. $data = $this->requestUploadInfo($request, 'attached');
  461. AliyunVodLog::printLog("CreateUploadAttachedMedia, FilePath: %s, MediaId: %s, MediaURL: %s",
  462. $uploadAttachedRequest->getFilePath(), $data->MediaId, $data->MediaURL);
  463. return $data;
  464. }
  465. private function uploadOssObject($filePath, $object, $uploadInfo, $headers=null)
  466. {
  467. $this->initOssClient($uploadInfo->UploadAuth, $uploadInfo->UploadAddress);
  468. $this->multipartUploadMediaFile($filePath, $object, $uploadInfo, $headers);
  469. $bucketHost = str_replace('://', '://'.$uploadInfo->UploadAddress->Bucket.".",
  470. $uploadInfo->UploadAddress->Endpoint);
  471. AliyunVodLog::printLog("UploadFile %s Finish, MediaId: %s, FilePath: %s, Destination: %s/%s",
  472. $uploadInfo->MediaType, $uploadInfo->MediaId, $filePath, $bucketHost, $object);
  473. }
  474. // 定义进度条回调函数;$consumedBytes: 已上传的数据量,$totalBytes:总数据量
  475. public function uploadProgressCallback($mediaId, $consumedBytes, $totalBytes)
  476. {
  477. if ($totalBytes > 0) {
  478. $rate = 100 * (floatval(($consumedBytes) / floatval($totalBytes)));
  479. }
  480. else {
  481. $rate = 0;
  482. }
  483. /*printf("[%s]UploadProgress of Media %s, uploaded %s bytes, percent %s%s\n",
  484. AliyunVodUtils::getCurrentTimeStr(), $mediaId, $consumedBytes, round($rate, 1), '%');*/
  485. flush();
  486. }
  487. // 分片上传函数
  488. private function multipartUploadMediaFile($filePath, $object, $uploadInfo, $headers=null)
  489. {
  490. $uploadFile = OssUtil::encodePath($filePath);
  491. if (!file_exists($uploadFile)) {
  492. throw new Exception("file not exists: ".$uploadFile, AliyunVodError::VOD_ERR_FILE_READ);
  493. }
  494. $fileSize = @filesize($uploadFile);
  495. if ($fileSize === false || $fileSize < 0) {
  496. throw new Exception('The size of file cannot be determined: '.$filePath,AliyunVodError::VOD_ERR_FILE_READ);
  497. }
  498. $this->filePartHash = null;
  499. $bucket = $uploadInfo->UploadAddress->Bucket;
  500. // 文件大小未超过分片上传阈值,或不到一个分片的大小,则直接简单上传
  501. if ($fileSize <= $this->multipartThreshold || $fileSize < $this->multipartPartSize) {
  502. $res = $this->ossClient->uploadFile($bucket, $object, $uploadFile);
  503. if ($uploadInfo->MediaType == 'video') {
  504. $this->reportVideoUploadProgress('put', $uploadInfo, $filePath, $fileSize, 0, $fileSize,
  505. 1, 1, $fileSize);
  506. }
  507. return $res;
  508. }
  509. // 初始化分片
  510. $uploadId = $this->ossClient->initiateMultipartUpload($bucket, $object);
  511. $pieces = $this->ossClient->generateMultiuploadParts($fileSize, $this->multipartPartSize);
  512. $resUploadPart = array();
  513. $uploadPosition = 0;
  514. $isCheckMd5 = false;
  515. $totalPart = count($pieces);
  516. // 上传凭证有效期3000秒,如果是音视频,则需要提前刷新
  517. $startTime = time();
  518. $expireSeconds = 2500;
  519. // 逐个上传分片
  520. foreach ($pieces as $i => $piece) {
  521. $fromPos = $uploadPosition + (integer)$piece[OssClient::OSS_SEEK_TO];
  522. $toPos = (integer)$piece[OssClient::OSS_LENGTH] + $fromPos - 1;
  523. $upOptions = array(
  524. OssClient::OSS_FILE_UPLOAD => $uploadFile,
  525. OssClient::OSS_PART_NUM => ($i + 1),
  526. OssClient::OSS_SEEK_TO => $fromPos,
  527. OssClient::OSS_LENGTH => $toPos - $fromPos + 1,
  528. OssClient::OSS_CHECK_MD5 => $isCheckMd5,
  529. );
  530. if ($isCheckMd5) {
  531. $contentMd5 = OssUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos);
  532. $upOptions[OssClient::OSS_CONTENT_MD5] = $contentMd5;
  533. }
  534. // 上传分片
  535. $resUploadPart[] = $this->ossClient->uploadPart($bucket, $object, $uploadId, $upOptions);
  536. /*AliyunVodLog::printLog("UploadPart, FilePath: %s, MediaId: %s, MediaType: %s, UploadId: %s, PartNumber: %s, PartSize: %s",
  537. $uploadFile, $uploadInfo->MediaId, $uploadInfo->MediaType, $uploadId, $upOptions[OssClient::OSS_PART_NUM], $upOptions[OssClient::OSS_LENGTH]);*/
  538. // 上传进度回调
  539. $this->uploadProgressCallback($uploadInfo->MediaId, $toPos, $fileSize);
  540. if ($uploadInfo->MediaType == 'video') {
  541. // 上报上传进度
  542. $this->reportVideoUploadProgress('multipart', $uploadInfo, $uploadFile, $fileSize,
  543. $uploadId, $this->multipartPartSize, $totalPart, $upOptions[OssClient::OSS_PART_NUM], $toPos);
  544. // 检测视频上传凭证是否过期
  545. $nowTime = time();
  546. if ($nowTime - $startTime >= $expireSeconds) {
  547. $uploadInfo = $this->refreshUploadVideo($uploadInfo->MediaId);
  548. $this->initOssClient($uploadInfo->UploadAuth, $uploadInfo->UploadAddress);
  549. $startTime = $nowTime;
  550. }
  551. }
  552. }
  553. $uploadParts = array();
  554. foreach ($resUploadPart as $i => $eTag) {
  555. $uploadParts[] = array(
  556. 'PartNumber' => ($i + 1),
  557. 'ETag' => $eTag,
  558. );
  559. }
  560. // 完成上传
  561. $options = is_null($headers) ? null : array(OssClient::OSS_HEADERS => $headers);
  562. $res = $this->ossClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts, $options);
  563. if ($uploadInfo->MediaType == 'video') {
  564. $this->reportVideoUploadProgress('multipart', $uploadInfo, $filePath, $fileSize, $uploadId,
  565. $this->multipartPartSize, $totalPart, $totalPart, $fileSize);
  566. }
  567. return $res;
  568. }
  569. private function reportVideoUploadProgress($uploadMethod, $uploadInfo, $filePath, $fileSize, $uploadId, $partSize,
  570. $totalPart, $donePartsCount, $doneBytes)
  571. {
  572. if (is_null($this->filePartHash)) {
  573. $this->filePartHash = AliyunVodReportUpload::generateFilePartHash($this->accessKeyId, $filePath, $fileSize);
  574. }
  575. $uploadPoint = array('upMethod'=>$uploadMethod, 'threshold'=>$this->multipartThreshold,
  576. 'partSize'=>$this->multipartPartSize, 'doneBytes'=>$doneBytes);
  577. $progressInfo = array('FileName'=>$filePath, 'FileHash'=>$this->filePartHash, 'FileSize'=>$fileSize, 'UploadId'=>$uploadId,
  578. 'PartSize'=>$partSize, 'TotalPart'=>$totalPart, 'DonePartsCount'=>$donePartsCount,
  579. 'UploadPoint'=>json_encode($uploadPoint), 'UploadAddress'=>$uploadInfo->OriUploadAddress);
  580. AliyunVodReportUpload::reportUploadProgress($this->accessKeyId, $uploadInfo->MediaId, $progressInfo);
  581. }
  582. private $accessKeyId;
  583. private $accessKeySecret;
  584. private $apiRegionId;
  585. private $ecsRegionId;
  586. private $connTimeout;
  587. private $ossTimeout;
  588. private $vodTimeout;
  589. private $vodClient;
  590. private $ossClient;
  591. private $maxRetryTimes;
  592. private $filePartHash;
  593. private $enableSSL;
  594. private $securityToken = null;
  595. // 分片上传的阈值,超过此值开启分片上传
  596. private $multipartThreshold;
  597. // 分片大小,单位byte
  598. private $multipartPartSize;
  599. // 分片上传时并行上传的线程数,暂时为串行上传,不支持并行,后续会支持。
  600. private $multipartThreadsNum;
  601. }