...
 
Commits (2)
<?php
/**
* Client based upload
*
* @author Mark Harding
*/
namespace Minds\Controllers\api\v2\media;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Interfaces;
use Minds\Core\Media\ClientUpload\ClientUploadLease;
class upload implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public function get($pages)
{
return Factory::response([]);
}
public function post($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public function put($pages)
{
$manager = Di::_()->get("Media\ClientUpload\Manager");
switch ($pages[0]) {
case 'prepare':
$mediaType = $pages[1] ?? 'not-set';
$lease = $manager->prepare($mediaType);
return Factory::response([
'lease' => $lease->export(),
]);
break;
case 'complete':
$mediaType = $pages[1] ?? 'not-set';
$guid = $pages[2] ?? null;
$lease = new ClientUploadLease();
$lease->setGuid($guid)
->setMediaType($mediaType);
$manager->complete($lease);
break;
}
return Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
return Factory::response([]);
}
}
<?php
namespace Minds\Core\Media\ClientUpload;
use Minds\Traits\MagicAttributes;
/**
* Class ClientUploadLease
* @package Minds\Core\Media\ClientUpload
* @method string getGuid()
* @method string getMediaType()
* @method string getPresignedUrl()
*/
class ClientUploadLease
{
use MagicAttributes;
/** @var string $guid */
private $guid;
/** @var string $presignedUrl */
private $presignedUrl;
/** @var string $mediaType */
private $mediaType;
/**
* Export to API
* @param array $extra
* @return array
*/
public function export($extra = [])
{
return [
'guid' => (string) $this->getGuid(),
'presigned_url' => $this->getPresignedUrl(),
'media_type' => $this->getMediaType(),
];
}
}
<?php
/**
* Client Upload, direct from browser to storage
*/
namespace Minds\Core\Media\ClientUpload;
use Minds\Core\Media\Services\FFMpeg;
use Minds\Core\GuidBuilder;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Di\Di;
use Minds\Entities\Video;
class Manager
{
/** @var FFMepg */
private $ffmpeg;
/** @var Guid $guid */
private $guid;
/** @var Save $save */
private $save;
public function __construct(
FFMpeg $FFMpeg = null,
GuidBuilder $guid = null,
Save $save = null
)
{
$this->ffmpeg = $FFMpeg ?: Di::_()->get('Media\Services\FFMpeg');
$this->guid = $guid ?: new GuidBuilder();
$this->save = $save ?: new Save();
}
/**
* Prepare an upload, return a lease
* @param $type - the media type
* @return ClientUploadLease
*/
public function prepare($type = 'video')
{
if ($type != 'video') {
throw new \Exception("$type is not currently supported for client based uploads");
}
$guid = $this->guid->build();
$this->ffmpeg->setKey($guid);
$preSignedUrl = $this->ffmpeg->getPresignedUrl();
$lease = new ClientUploadLease();
$lease->setGuid($guid)
->setMediaType($type)
->setPresignedUrl($preSignedUrl);
return $lease;
}
/**
* Complete the client based upload
* @param ClientUploadLease $lease
* @return boolean
*/
public function complete(ClientUploadLease $lease)
{
if ($lease->getMediaType() !== 'video') {
throw new \Exception("{$lease->getMediaType()} is not currently supported for client based uploads");
}
$video = new Video();
$video->set('guid', $lease->getGuid());
$video->set('cinemr_guid', $lease->getGuid());
$video->set('access_id', 0); // Hide until published
// Save the video
$this->save->setEntity($video)->save();
$this->ffmpeg->setKey($lease->getGuid());
// Start the transcoding process
$this->ffmpeg->transcode();
return true;
}
}
......@@ -59,5 +59,17 @@ class MediaProvider extends Provider
$this->di->bind('Media\Imagick\Manager', function ($di) {
return new Imagick\Manager();
}, ['useFactory' => false]);
// ClientUpload
$this->di->bind('Media\ClientUpload\Manager', function ($di) {
return new ClientUpload\Manager();
}, ['useFactory' => true]);
// Services
$this->di->bind('Media\Services\FFMpeg', function ($di) {
return new Services\FFMpeg();
}, ['useFactory' => false]);
}
}
......@@ -78,6 +78,20 @@ class FFMpeg implements ServiceInterface
return $this;
}
/**
* Create a PresignedUr for client based uploads
* @return string
*/
public function getPresignedUrl()
{
$cmd = $this->s3->getCommand('PutObject', [
'Bucket' => 'cinemr',
'Key' => "$this->dir/$this->key/source",
]);
return (string) $this->s3->createPresignedRequest($cmd, '+20 minutes')->getUri();
}
public function saveToFilestore($file)
{
try {
......
<?php
namespace Spec\Minds\Core\Media\ClientUpload;
use Minds\Core\Media\ClientUpload\Manager;
use Minds\Core\Media\ClientUpload\ClientUploadLease;
use Minds\Core\Media\Services\FFMpeg;
use Minds\Core\GuidBuilder;
use Minds\Core\Entities\Actions\Save;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
private $ffmpeg;
private $guid;
private $save;
function let(FFMpeg $FFMpeg, GuidBuilder $guid, Save $save)
{
$this->beConstructedWith($FFMpeg, $guid, $save);
$this->ffmpeg = $FFMpeg;
$this->guid = $guid;
$this->save = $save;
}
function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
function it_should_return_an_upload_lease()
{
$this->guid->build()
->willReturn(123);
$this->ffmpeg->setKey(123)
->shouldBeCalled();
$this->ffmpeg->getPresignedUrl()
->willReturn('s3-url-here');
$lease = $this->prepare('video');
$lease->getMediaType()
->shouldBe('video');
$lease->getGuid()
->shouldBe(123);
$lease->getPresignedUrl()
->shouldBe('s3-url-here');
}
function it_should_complete_an_upload(ClientUploadLease $lease)
{
$lease->getMediaType()
->willReturn('video');
$lease->getGuid()
->willReturn(456);
$this->save->setEntity(Argument::that(function ($video) {
return $video->guid == 456
&& $video->access_id == 0;
}))
->shouldBeCalled()
->willReturn($this->save);
$this->save->save()
->shouldBeCalled();
$this->ffmpeg->setKey(456)
->shouldBeCalled();
$this->ffmpeg->transcode()
->shouldBeCalled();
$this->complete($lease)
->shouldReturn(true);
}
}
<?php
namespace Spec\Minds\Core\Media\Services;
use Minds\Core\Media\Services\FFMpeg;
use FFMpeg\FFMpeg as FFMpegClient;
use FFMpeg\FFProbe as FFProbeClient;
use Minds\Core\Queue\Interfaces\QueueClient;
use Psr\Http\Message\RequestInterface;
use Aws\S3\S3Client;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class FFMpegSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(FFMpeg::class);
}
function it_should_get_a_presigned_urn(
QueueClient $queue,
FFMpegClient $ffmpeg,
FFProbeClient $ffprobe,
S3Client $s3,
\Aws\CommandInterface $cmd,
RequestInterface $request
)
{
$this->beConstructedWith($queue, $ffmpeg, $ffprobe, $s3);
$s3->getCommand('PutObject', [
'Bucket' => 'cinemr',
'Key' => "/123/source",
])
->shouldBeCalled()
->willReturn($cmd);
$s3->createPresignedRequest(Argument::any(), Argument::any())
->willReturn($request);
$request->getUri()
->willReturn('aws-signed-url');
$this->setKey(123);
$this->getPresignedUrl()
->shouldReturn('aws-signed-url');
}
}