Skip to content
Projects
Groups
Snippets
Help
Sign in / Register
Toggle navigation
Minds Backend - Engine
Project overview
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Locked Files
Issues
284
Merge Requests
38
CI / CD
Security & Compliance
Packages
Wiki
Snippets
Members
Collapse sidebar
Close sidebar
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Minds
Minds Backend - Engine
Commits
465c2658
Commit
465c2658
authored
35 minutes ago
by
Mark Harding
1
Browse files
Options
Download
(feat): new transcoder
parent
7d248310
fix/stripe-requirements
1 merge request
!420
Additional stripe steps and ability to add phone number
Pipeline
#102372159
passed with stages
in 7 minutes and 4 seconds
Changes
46
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
46 changed files
with
2749 additions
and
72 deletions
+2749
-72
Controllers/Cli/Transcode.php
View file @
465c2658
...
...
@@ -21,9 +21,14 @@ class Transcode extends Cli\Controller implements Interfaces\CliControllerInterf
public
function
exec
()
{
$transcoder
=
new
Core\Media\Services\FFMpeg
;
$transcoder
->
setKey
(
$this
->
getOpt
(
'guid'
));
$transcoder
->
setFullHD
(
$this
->
getOpt
(
'full_hd'
)
??
false
);
$transcoder
->
onQueue
();
$entity
=
Di
::
_
()
->
get
(
'EntitiesBuilder'
)
->
single
(
$this
->
getOpt
(
'guid'
));
if
(
!
$entity
)
{
$this
->
out
(
'Entity not found'
);
return
;
}
$manager
=
Di
::
_
()
->
get
(
'Media\Video\Transcoder\Manager'
);
$manager
->
createTranscodes
(
$entity
);
}
}
This diff is collapsed.
Controllers/api/v2/media/upload.php
View file @
465c2658
...
...
@@ -58,9 +58,7 @@ class upload implements Interfaces\Api
$lease
->
setGuid
(
$guid
)
->
setMediaType
(
$mediaType
);
$manager
->
setFullHD
(
Session
::
getLoggedinUser
()
->
isPro
())
->
complete
(
$lease
);
$manager
->
complete
(
$lease
);
break
;
}
return
Factory
::
response
([]);
...
...
This diff is collapsed.
Controllers/api/v2/media/video.php
0 → 100644
View file @
465c2658
<?php
/**
* Minds Video Controller
*
* @author Mark Harding
*/
namespace
Minds\Controllers\api\v2\media
;
use
Minds\Api\Factory
;
use
Minds\Core\Di\Di
;
use
Minds\Core\Media\Video\Transcoder\TranscodeStates
;
use
Minds\Interfaces
;
class
video
implements
Interfaces\Api
,
Interfaces\ApiIgnorePam
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public
function
get
(
$pages
)
{
$videoManager
=
Di
::
_
()
->
get
(
'Media\Video\Manager'
);
$transcodeStates
=
Di
::
_
()
->
get
(
'Media\Video\Transcoder\TranscodeStates'
);
$video
=
$videoManager
->
get
(
$pages
[
0
]);
$sources
=
$videoManager
->
getSources
(
$video
);
$status
=
$transcodeStates
->
getStatus
(
$video
);
// Currently not efficient as no caching
if
(
$status
===
TranscodeStates
::
FAILED
&&
count
(
$sources
))
{
$status
=
TranscodeStates
::
COMPLETED
;
}
Factory
::
response
([
'entity'
=>
$video
->
export
(),
'sources'
=>
Factory
::
exportable
(
$sources
),
'poster'
=>
$video
->
getIconUrl
(),
'transcode_status'
=>
$status
,
]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
*/
public
function
post
(
$pages
)
{
http_response_code
(
501
);
exit
;
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public
function
put
(
$pages
)
{
http_response_code
(
501
);
exit
;
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public
function
delete
(
$pages
)
{
http_response_code
(
501
);
exit
;
}
}
This diff is collapsed.
Core/Media/ClientUpload/Manager.php
View file @
465c2658
...
...
@@ -4,7 +4,7 @@
*/
namespace
Minds\Core\Media\ClientUpload
;
use
Minds\Core\Media\
Services\FFMpeg
;
use
Minds\Core\Media\
Video\Transcoder\Manager
as
TranscoderManager
;
use
Minds\Core\GuidBuilder
;
use
Minds\Core\Entities\Actions\Save
;
use
Minds\Core\Di\Di
;
...
...
@@ -12,35 +12,22 @@ use Minds\Entities\Video;
class
Manager
{
/** @var
FFMpeg
*/
private
$
ffmpeg
;
/** @var
TranscoderManager
*/
private
$
transcoderManager
;
/** @var Guid $guid */
private
$guid
;
/** @var bool */
private
$full_hd
;
/** @var Save $save */
private
$save
;
/**
* @param bool $value
* @return Manager
*/
public
function
setFullHD
(
bool
$value
)
:
Manager
{
$this
->
full_hd
=
$value
;
return
$this
;
}
public
function
__construct
(
FFMpeg
$FFMpeg
=
null
,
TranscoderManager
$transcoderManager
=
null
,
GuidBuilder
$guid
=
null
,
Save
$save
=
null
)
{
$this
->
ffmpeg
=
$FFMpeg
?:
Di
::
_
()
->
get
(
'Media\Services\FFMpeg
'
);
$this
->
transcoderManager
=
$transcoderManager
?:
Di
::
_
()
->
get
(
'Media\Video\Transcoder\Manager
'
);
$this
->
guid
=
$guid
?:
new
GuidBuilder
();
$this
->
save
=
$save
?:
new
Save
();
}
...
...
@@ -56,13 +43,13 @@ class Manager
throw
new
\Exception
(
"
$type
is not currently supported for client based uploads"
);
}
$guid
=
$this
->
guid
->
build
();
$video
=
new
Video
();
$video
->
set
(
'guid'
,
$this
->
guid
->
build
());
$this
->
ffmpeg
->
setKey
(
$guid
);
$preSignedUrl
=
$this
->
ffmpeg
->
getPresignedUrl
();
$preSignedUrl
=
$this
->
transcoderManager
->
getClientSideUploadUrl
(
$video
);
$lease
=
new
ClientUploadLease
();
$lease
->
setGuid
(
$
guid
)
$lease
->
setGuid
(
$
video
->
getGuid
()
)
->
setMediaType
(
$type
)
->
setPresignedUrl
(
$preSignedUrl
);
...
...
@@ -84,18 +71,12 @@ class Manager
$video
->
set
(
'guid'
,
$lease
->
getGuid
());
$video
->
set
(
'cinemr_guid'
,
$lease
->
getGuid
());
$video
->
set
(
'access_id'
,
0
);
// Hide until published
$video
->
setFlag
(
'full_hd'
,
$this
->
full_hd
);
// Save the video
$this
->
save
->
setEntity
(
$video
)
->
save
();
$this
->
ffmpeg
->
setKey
(
$lease
->
getGuid
());
// Set the full hd flag
$this
->
ffmpeg
->
setFullHD
(
$this
->
full_hd
);
// Start the transcoding process
$this
->
ffmpeg
->
transcode
();
// Kick off the transcoder
$this
->
transcoderManager
->
createTranscodes
(
$video
);
return
true
;
}
...
...
This diff is collapsed.
Core/Media/MediaProvider.php
View file @
465c2658
...
...
@@ -72,10 +72,24 @@ class MediaProvider extends Provider
return
new
ClientUpload\Manager
();
},
[
'useFactory'
=>
true
]);
// Services
// Services
(deprecated)
$this
->
di
->
bind
(
'Media\Services\FFMpeg'
,
function
(
$di
)
{
return
new
Services\FFMpeg
();
},
[
'useFactory'
=>
false
]);
// Transcoder
$this
->
di
->
bind
(
'Media\Video\Transcoder\Manager'
,
function
(
$di
)
{
return
new
Video\Transcoder\Manager
();
},
[
'useFactory'
=>
false
]);
$this
->
di
->
bind
(
'Media\Video\Transcoder\TranscodeStates'
,
function
(
$di
)
{
return
new
Video\Transcoder\TranscodeStates
();
},
[
'useFactory'
=>
false
]);
$this
->
di
->
bind
(
'Media\Video\Transcode\TranscodeStorage'
,
function
(
$di
)
{
return
new
Video\Transcoder\TranscodeStorage\S3Storage
();
},
[
'useFactory'
=>
false
]);
}
}
This diff is collapsed.
Core/Media/Services/FFMpeg.php
View file @
465c2658
<?php
/**
* Minds FFMpeg.
* Minds FFMpeg.
(This now deprecated in favour of Core/Media/Video/Transcoder/Manager)
*/
namespace
Minds\Core\Media\Services
;
...
...
This diff is collapsed.
Core/Media/Video/Manager.php
View file @
465c2658
...
...
@@ -11,17 +11,29 @@ use Minds\Core\Di\Di;
use
Minds\Entities\Activity
;
use
Minds\Entities\Entity
;
use
Minds\Entities\Video
;
use
Minds\Core\EntitiesBuilder
;
use
Minds\Common\Repository\Response
;
class
Manager
{
/** @var Config
$config
*/
/** @var Config */
private
$config
;
/** @var S3Client
$s3
*/
/** @var S3Client */
private
$s3
;
public
function
__construct
(
$config
=
null
,
$s3
=
null
)
{
/** @var EntitiesBuilder */
private
$entitiesBuilder
;
/** @var Transcoder\Manager */
private
$transcoderManager
;
public
function
__construct
(
$config
=
null
,
$s3
=
null
,
$entitiesBuilder
=
null
,
$transcoderManager
=
null
)
{
$this
->
config
=
$config
??
Di
::
_
()
->
get
(
'Config'
);
// AWS
...
...
@@ -36,6 +48,64 @@ class Manager
];
}
$this
->
s3
=
$s3
?:
new
S3Client
(
array_merge
([
'version'
=>
'2006-03-01'
],
$opts
));
$this
->
entitiesBuilder
=
$entitiesBuilder
??
Di
::
_
()
->
get
(
'EntitiesBuilder'
);
$this
->
transcoderManager
=
$transcoderManager
??
Di
::
_
()
->
get
(
'Media\Video\Transcoder\Manager'
);
}
/**
* Return a video
* @param string $guid
* @return Video
*/
public
function
get
(
$guid
)
:
Video
{
return
$this
->
entitiesBuilder
->
single
(
$guid
);
}
/**
* Return transcodes
* @param Video $video
* @return Source[]
*/
public
function
getSources
(
Video
$video
)
:
array
{
$transcodes
=
$this
->
transcoderManager
->
getList
([
'guid'
=>
$video
->
getGuid
(),
'legacyPolyfill'
=>
true
,
]);
$sources
=
[];
foreach
(
$transcodes
as
$transcode
)
{
if
(
$transcode
->
getStatus
()
!=
Transcoder\TranscodeStates
::
COMPLETED
)
{
continue
;
}
if
(
$transcode
->
getProfile
()
instanceof
Transcoder\TranscodeProfiles\Thumbnails
)
{
continue
;
}
$source
=
new
Source
();
$source
->
setGuid
(
$transcode
->
getGuid
())
->
setType
(
$transcode
->
getProfile
()
->
getFormat
())
->
setLabel
(
$transcode
->
getProfile
()
->
getId
())
->
setSize
(
$transcode
->
getProfile
()
->
getHeight
())
->
setSrc
(
implode
(
'/'
,
[
$this
->
config
->
get
(
'transcoder'
)[
'cdn_url'
]
??
'https://cdn-cinemr.minds.com'
,
$this
->
config
->
get
(
'transcoder'
)[
'dir'
],
$transcode
->
getGuid
(),
$transcode
->
getProfile
()
->
getStorageName
()
]));
$sources
[]
=
$source
;
}
// Sort the array so that mp4's are first
usort
(
$sources
,
function
(
$a
,
$b
)
{
if
(
$a
->
getType
()
===
'video/mp4'
)
{
return
-
1
;
}
return
1
;
});
return
$sources
;
}
/**
...
...
This diff is collapsed.
Core/Media/Video/Source.php
0 → 100644
View file @
465c2658
<?php
namespace
Minds\Core\Media\Video
;
use
Minds\Traits\MagicAttributes
;
/**
* @method string getGuid()
* @method Source setGuid(string $guid)
* @method string getSrc()
* @method Source setSrc(string $src)
* @method string getType()
* @method Source setType(string $type)
* @method int getSize()
* @method Source setSize(int $size)
* @method string getLabel()
* @method Source setLabel(string $label)
*/
class
Source
{
use
MagicAttributes
;
/** @var string */
protected
$guid
;
/** @var string */
protected
$src
;
/** @var string */
protected
$type
;
/** @var int */
protected
$size
;
/** @var string */
protected
$label
;
/**
* Export source
* @param array $extras
* @return array
*/
public
function
export
(
$extras
=
[])
:
array
{
return
[
'guid'
=>
$this
->
guid
,
'src'
=>
$this
->
src
,
'type'
=>
$this
->
type
,
'size'
=>
$this
->
size
,
'label'
=>
$this
->
label
,
];
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/Delegates/NotificationDelegate.php
0 → 100644
View file @
465c2658
<?php
namespace
Minds\Core\Media\Video\Transcoder\Delegates
;
use
Minds\Core\Di\Di
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Media\Video\Transcoder\TranscodeStates
;
use
Minds\Entities\Video
;
use
Minds\Core\Events\EventsDispatcher
;
use
Minds\Core\EntitiesBuilder
;
class
NotificationDelegate
{
/** @var TranscodeStates */
private
$transcodeStates
;
/** @var EventsDispatcher */
private
$eventsDispatcher
;
/** @var EntitiesBuilder */
private
$entitiesBuilder
;
public
function
__construct
(
$transcodeStates
=
null
,
$eventsDispatcher
=
null
,
$entitiesBuilder
=
null
)
{
$this
->
transcodeStates
=
$transcodeStates
??
new
TranscodeStates
();
$this
->
eventsDispatcher
=
$eventsDispatcher
??
Di
::
_
()
->
get
(
'EventsDispatcher'
);
$this
->
entitiesBuilder
=
$entitiesBuilder
??
Di
::
_
()
->
get
(
'EntitiesBuilder'
);
}
/**
* Add a transcode to the queue
* @param Transcode $transcode
* @return void
*/
public
function
onTranscodeCompleted
(
Transcode
$transcode
)
:
void
{
$video
=
$this
->
entitiesBuilder
->
single
(
$transcode
->
getGuid
());
if
(
!
$video
||
!
$video
instanceof
Video
)
{
error_log
(
"Video (
{
$transcode
->
getGuid
()
}
not found"
);
return
;
// TODO: Tell sentry?
}
$status
=
$this
->
transcodeStates
->
getStatus
(
$video
);
if
(
$status
===
TranscodeStates
::
COMPLETED
)
{
// $this->emitCompletedNotification($video);
}
elseif
(
$status
===
TranscodeStates
::
FAILED
)
{
// $this->emitFailedNotification($video);
}
}
/**
* @var Video $video
* @return void
*/
private
function
emitCompletedNotification
(
Video
$video
)
:
void
{
$this
->
eventsDispatcher
->
trigger
(
'notification'
,
'transcoder'
,
[
'to'
=>
[
$video
->
getOwnerGuid
()
],
'from'
=>
100000000000000519
,
'notification_view'
=>
'transcode_completed'
,
'entity'
=>
$video
,
]);
}
/**
* @var Video $video
* @return void
*/
private
function
emitFailedNotification
(
Video
$video
)
:
void
{
$this
->
eventsDispatcher
->
trigger
(
'notification'
,
'transcoder'
,
[
'to'
=>
[
$video
->
getOwnerGuid
()
],
'from'
=>
100000000000000519
,
'notification_view'
=>
'transcode_failed'
,
'entity'
=>
$video
,
]);
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/Delegates/QueueDelegate.php
0 → 100644
View file @
465c2658
<?php
namespace
Minds\Core\Media\Video\Transcoder\Delegates
;
use
Minds\Core\Di\Di
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Queue\Interfaces\QueueClient
;
class
QueueDelegate
{
/** @var QueueClient */
private
$queueClient
;
public
function
__construct
(
$queueClient
=
null
)
{
$this
->
queueClient
=
$queueClient
??
Di
::
_
()
->
get
(
'Queue'
);
}
/**
* Add a transcode to the queue
* @param Transcode $transcode
* @return void
*/
public
function
onAdd
(
Transcode
$transcode
)
:
void
{
$this
->
queueClient
->
setQueue
(
'Transcode'
)
->
send
([
'transcode'
=>
serialize
(
$transcode
),
]);
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/Manager.php
0 → 100644
View file @
465c2658
<?php
/**
* Transcoder manager
*/
namespace
Minds\Core\Media\Video\Transcoder
;
use
Minds\Core\Media\Video\Transcoder\Delegates\QueueDelegate
;
use
Minds\Core\Media\Video\Transcoder\Delegates\NotificationDelegate
;
use
Minds\Entities\Video
;
use
Minds\Traits\MagicAttributes
;
use
Minds\Common\Repository\Response
;
use
Minds\Core\Media\Video\Source
;
class
Manager
{
/** @var TranscodeProfileInterface[] */
const
TRANSCODE_PROFILES
=
[
TranscodeProfiles\Thumbnails
::
class
,
TranscodeProfiles\X264_360p
::
class
,
TranscodeProfiles\X264_720p
::
class
,
TranscodeProfiles\X264_1080p
::
class
,
TranscodeProfiles\Webm_360p
::
class
,
TranscodeProfiles\Webm_720p
::
class
,
TranscodeProfiles\Webm_1080p
::
class
,
];
/** @var int */
const
TRANSCODER_TIMEOUT_SECS
=
600
;
// 10 minutes with not progress
/** @var Repository */
private
$repository
;
/** @var QueueDelegate */
private
$queueDelegate
;
/** @var TranscodeStorage\TranscodeStorageInterface */
private
$transcodeStorage
;
/** @var TranscodeExecutors\TranscodeExecutorInterfsce */
private
$transcodeExecutor
;
/** @var NotificationDelegate */
private
$notificationDelegate
;
public
function
__construct
(
$repository
=
null
,
$queueDelegate
=
null
,
$transcodeStorage
=
null
,
$transcodeExecutor
=
null
,
$notificationDelegate
=
null
)
{
$this
->
repository
=
$repository
??
new
Repository
();
$this
->
queueDelegate
=
$queueDelegate
??
new
QueueDelegate
();
$this
->
transcodeStorage
=
$transcodeStorage
??
new
TranscodeStorage\S3Storage
();
$this
->
transcodeExecutor
=
$transcodeExecutor
??
new
TranscodeExecutors\FFMpegExecutor
();
$this
->
notificationDelegate
=
$notificationDelegate
??
new
NotificationDelegate
();
}
/**
* Return a list of transcodes
* @return Response
*/
public
function
getList
(
$opts
)
:
?
Response
{
$opts
=
array_merge
([
'guid'
=>
null
,
'profileId'
=>
null
,
'status'
=>
null
,
'legacyPolyfill'
=>
false
,
],
$opts
);
$response
=
$this
->
repository
->
getList
(
$opts
);
if
(
$opts
[
'legacyPolyfill'
]
&&
!
$response
->
count
())
{
$response
=
$this
->
getLegacyPolyfill
(
$opts
);
}
return
$response
;
}
/**
* Return a list of legacy transcodes by reading from storage
* @param array
* @return Response
*/
private
function
getLegacyPolyfill
(
array
$opts
)
:
?
Response
{
$files
=
$this
->
transcodeStorage
->
ls
(
$opts
[
'guid'
]);
if
(
!
$files
)
{
return
null
;
}
$response
=
new
Response
();
foreach
(
$files
as
$fileName
)
{
// Loop through each profile to see if fileName is a match
foreach
(
self
::
TRANSCODE_PROFILES
as
$profile
)
{
$profile
=
new
$profile
();
if
(
$profile
->
getStorageName
()
&&
strpos
(
$fileName
,
$profile
->
getStorageName
())
!==
false
)
{
$transcode
=
new
Transcode
();
$transcode
->
setGuid
(
$opts
[
'guid'
])
->
setProfile
(
$profile
)
->
setStatus
(
TranscodeStates
::
COMPLETED
);
$response
[]
=
$transcode
;
}
}
}
return
$response
;
}
/**
* Return transcodes for a video by urn
* @param string $urn
* @return Transcodes[]
*/
public
function
getTranscodesByUrn
(
string
$urn
)
:
array
{
return
[];
}
/**
* Upload the source file to storage
* Note: This does not register any transcodes. createTranscodes should be called
* @param Video $video
* @param string $path
* @return bool
*/
public
function
uploadSource
(
Video
$video
,
string
$path
)
:
bool
{
// Upload the source file to storage
$source
=
new
Transcode
();
$source
->
setVideo
(
$video
)
->
setProfile
(
new
TranscodeProfiles\Source
());
return
(
bool
)
$this
->
transcodeStorage
->
add
(
$source
,
$path
);
}
/**
* This will return a url that can be used by an HTTP client
* to upload the source file
* @param Video $video
* @return string
*/
public
function
getClientSideUploadUrl
(
Video
$video
)
:
string
{
$source
=
new
Transcode
();
$source
->
setVideo
(
$video
)
->
setProfile
(
new
TranscodeProfiles\Source
());
return
$this
->
transcodeStorage
->
getClientSideUploadUrl
(
$source
);
}
/**
* Create the transcodes from from
* @param Video $video
* @return void
*/
public
function
createTranscodes
(
Video
$video
)
:
void
{
foreach
(
self
::
TRANSCODE_PROFILES
as
$profile
)
{
try
{
$transcode
=
new
Transcode
();
$transcode
->
setVideo
(
$video
)
->
setProfile
(
new
$profile
)
->
setStatus
(
TranscodeStates
::
CREATED
);
// Add the transcode to database and queue
$this
->
add
(
$transcode
);
}
catch
(
TranscodeProfiles\UnavailableTranscodeProfileException
$e
)
{
continue
;
// Silently fail and just skip
}
}
}
/**
* Add transcode to the queue
* @param Transcode $transcode
* @return void
*/
public
function
add
(
Transcode
$transcode
)
:
void
{
// Add to repository
$this
->
repository
->
add
(
$transcode
);
// Notify the background queue
$this
->
queueDelegate
->
onAdd
(
$transcode
);
}
/**
* Update the transcode entity
* @param Transcode $transcode
* @param array $dirty
* @return bool
*/
public
function
update
(
Transcode
$transcode
,
array
$dirty
=
[])
:
bool
{
return
$this
->
repository
->
update
(
$transcode
,
$dirty
);
}
/**
* Run the transcoder (this is called from Core\QueueRunner\Transcode hook)
* @param Transcode $transcode
* @return void
*/
public
function
transcode
(
Transcode
$transcode
)
:
void
{
// Update the background so everyone knows this is inprogress
$transcode
->
setStatus
(
'transcoding'
);
$this
->
update
(
$transcode
,
[
'status'
]);
// Perform the transcode
try
{
$ref
=
$this
;
$success
=
$this
->
transcodeExecutor
->
transcode
(
$transcode
,
function
(
$progress
)
use
(
$ref
,
$transcode
)
{
$transcode
->
setProgress
(
$progress
);
$this
->
update
(
$transcode
,
[
'progress'
]);
});
if
(
!
$success
)
{
// This is actually unkown as an exception should have been thrown
throw
new
TranscodeExecutors\FailedTranscodeException
();
}
$transcode
->
setProgress
(
100
);
// If completed should be assumed 100%
$transcode
->
setStatus
(
'completed'
);
}
catch
(
TranscodeExecutors\FailedTranscodeException
$e
)
{
$transcode
->
setStatus
(
'failed'
);
}
finally
{
$this
->
update
(
$transcode
,
[
'progress'
,
'status'
]);
}
$this
->
notificationDelegate
->
onTranscodeCompleted
(
$transcode
);
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/Repository.php
0 → 100644
View file @
465c2658
<?php
/**
* Transcoder manager
*/
namespace
Minds\Core\Media\Video\Transcoder
;
use
Minds\Core\Media\Video\Transcode\Delegates\QueueDelegate
;
use
Minds\Entities\Video
;
use
Minds\Traits\MagicAttributes
;
use
Minds\Common\Repository\Response
;
use
Minds\Common\Urn
;
use
Minds\Core\Di\Di
;
use
Minds\Core\Data\Cassandra\Client
;
use
Minds\Core\Data\Cassandra\Prepared\Custom
;
use
Cassandra\Bigint
;
use
Cassandra\Timestamp
;
class
Repository
{
/** @var Client */
private
$db
;
public
function
__construct
(
$db
=
null
)
{
$this
->
db
=
$db
??
Di
::
_
()
->
get
(
'Database\Cassandra\Cql'
);
}
/**
* Return a list of transcodes
* @param array $opts
* @return Response
*/
public
function
getList
(
array
$opts
)
:
Response
{
$opts
=
array_merge
([
'guid'
=>
null
,
'profileId'
=>
null
,
'status'
=>
null
,
],
$opts
);
$statement
=
"SELECT
guid,
profile_id,
progress,
status,
last_event_timestamp_ms,
bytes
FROM video_transcodes"
;
$where
=
[];
$values
=
[];
if
(
$opts
[
'guid'
])
{
$where
[]
=
"guid = ?"
;
$values
[]
=
new
Bigint
(
$opts
[
'guid'
]);
}
if
(
$opts
[
'profileId'
])
{
$where
[]
=
"profile_id = ?"
;
$values
[]
=
$opts
[
'profile_id'
];
}
if
(
$opts
[
'status'
])
{
$where
[]
=
"status = ?"
;
$values
[]
=
$opts
[
'status'
];
}
$statement
.=
" WHERE "
.
implode
(
' AND '
,
$where
);
$prepared
=
new
Custom
();
$prepared
->
query
(
$statement
,
$values
);
try
{
$result
=
$this
->
db
->
request
(
$prepared
);
}
catch
(
\Exception
$e
)
{
return
new
Response
();
// TODO: make sure error is attached to response
}
$response
=
new
Response
;
foreach
(
$result
as
$row
)
{
$response
[]
=
$this
->
buildTranscodeFromRow
(
$row
);
}
return
$response
;
}
/**
* Return a single transcode
* @param string $urn
* @return Transcode
*/
public
function
get
(
string
$urn
)
:
?
Transcode
{
$urn
=
Urn
::
_
(
$urn
);
list
(
$guid
,
$profile
)
=
explode
(
'-'
,
$urn
->
getNss
());
$statement
=
"SELECT
guid,
profile_id,
progress,
status,
last_event_timestamp_ms,
bytes
FROM video_transcodes
WHERE guid = ?
AND profile = ?"
;
$values
=
[
new
Bigint
(
$guid
),
$profile
];
$prepared
=
new
Custom
();
$prepared
->
query
(
$statement
,
$values
);
try
{
$result
=
$this
->
db
->
request
(
$prepared
);
}
catch
(
\Exception
$e
)
{
return
null
;
}
$row
=
$result
[
0
];
$transcode
=
$this
->
buildTranscodeFromRow
(
$row
);
return
$transcode
;
}
/**
* Add a transcode to the database
* @param Transcode $transcode
* @return bool
*/
public
function
add
(
Transcode
$transcode
)
:
bool
{
$statement
=
"INSERT INTO video_transcodes (guid, profile_id, status) VALUES (?, ?, ?)"
;
$values
=
[
new
Bigint
(
$transcode
->
getGuid
()),
$transcode
->
getProfile
()
->
getId
(),
$transcode
->
getStatus
(),
];
$prepared
=
new
Custom
();
$prepared
->
query
(
$statement
,
$values
);
try
{
$this
->
db
->
request
(
$prepared
);
}
catch
(
\Exception
$e
)
{
return
false
;
}
return
true
;
}
/**
* Update the transcode
* @param Transcode $transcode
* @param array $dirty - list of fields that have changed
* @return bool
*/
public
function
update
(
Transcode
$transcode
,
array
$dirty
=
[])
:
bool
{
// Always update lastEventTimestampMs
$transcode
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
));
$dirty
[]
=
'lastEventTimestampMs'
;
$statement
=
"UPDATE video_transcodes"
;
$set
=
[];
foreach
(
$dirty
as
$field
)
{
switch
(
$field
)
{
case
'progress'
:
$set
[
'progress'
]
=
(
int
)
$transcode
->
getProgress
();
// This is a percentage basedf off 100
break
;
case
'status'
:
$set
[
'status'
]
=
(
string
)
$transcode
->
getStatus
();
break
;
case
'lastEventTimestampMs'
:
$set
[
'last_event_timestamp_ms'
]
=
new
Timestamp
(
$transcode
->
getLastEventTimestampMs
()
/
1000
);
break
;
case
'lengthSecs'
:
$set
[
'length_secs'
]
=
(
int
)
$transcode
->
getLengthSecs
();
break
;
case
'bytes'
:
$set
[
'bytes'
]
=
(
int
)
$transcode
->
getBytes
();
break
;
}
}
// Convert our $set to statement
$statement
.=
" SET "
.
implode
(
' , '
,
array_map
(
function
(
$field
)
{
return
"
$field
= ?"
;
},
array_keys
(
$set
)));
// Move to values array
$values
=
array_values
(
$set
);
// Say what we are updating
$statement
.=
" WHERE guid = ? AND profile_id = ?"
;
$values
[]
=
new
Bigint
(
$transcode
->
getGuid
());
$values
[]
=
(
string
)
$transcode
->
getProfile
()
->
getId
();
// Prepared statement
$prepared
=
new
Custom
();
$prepared
->
query
(
$statement
,
$values
);
try
{
$this
->
db
->
request
(
$prepared
);
}
catch
(
\Exception
$e
)
{
return
false
;
}
return
true
;
}
/**
* Delete a transcode
* @param Transcode $transcode
* @return bool
*/
public
function
delete
(
Transcode
$transcode
)
:
bool
{
$statement
=
"DELETE FROM video_transcodes WHERE guid = ? and profile_id = ?"
;
$values
=
[
new
Bigint
(
$transcode
->
getGuid
()),
(
string
)
$transcode
->
getProfile
()
->
getId
(),
];
// Prepared statement
$prepared
=
new
Custom
();
$prepared
->
query
(
$statement
,
$values
);
try
{
$this
->
db
->
request
(
$prepared
);
}
catch
(
\Exception
$e
)
{
return
false
;
}
return
true
;
}
/**
* Build transcode from an array of data
* @param array $row
* @return Transcode
*/
protected
function
buildTranscodeFromRow
(
array
$row
)
:
Transcode
{
$transcode
=
new
Transcode
();
$transcode
->
setGuid
((
string
)
$row
[
'guid'
])
->
setProfile
(
TranscodeProfiles\Factory
::
build
((
string
)
$row
[
'profile_id'
]))
->
setProgress
(
$row
[
'progress'
])
->
setStatus
(
$row
[
'status'
])
->
setLastEventTimestampMs
(
$row
[
'last_event_timestamp_ms'
]
?
round
(
$row
[
'last_event_timestamp_ms'
]
->
microtime
(
true
)
*
1000
)
:
null
)
->
setLengthSecs
(
$row
[
'length_secs'
])
->
setBytes
(
$row
[
'bytes'
]);
return
$transcode
;
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/Transcode.php
0 → 100644
View file @
465c2658
<?php
/**
* Transcode model
*/
namespace
Minds\Core\Media\Video\Transcoder
;
use
Minds\Entities\Video
;
use
Minds\Traits\MagicAttributes
;
/**
* @method Transcode setGuid(string $guid)
* @method string getGuid()
* @method Transcode setVideo(Video $video)
* @method Video getVideo()
* @method Transcode setProgress(int $progress)
* @method int getProgress()
* @method Transcode setStatus(string $status)
* @method string getStatus()
* @method TranscodeProfiles\TranscodeProfileInterface getProfile()
* @method int getLastEventTimestampMs()
* @method Transcode setLastEventTimestampMs(int $lastEventTimestampMs)
* @method Transcode setLengthSecs(int $secs)
* @method int getLengthSecs()
* @method Transcode setBytes(int $bytes)
* @method int getBytes()
*/
class
Transcode
{
use
MagicAttributes
;
/** @var string */
const
TRANSCODE_STATES
=
[
'created'
,
'transcoding'
,
'failed'
,
'completed'
,
];
/** @var string */
private
$guid
;
/** @var Video */
private
$video
;
/** @var TranscodeProfiles\TranscodeProfileInterface */
private
$profile
;
/** @var int */
private
$progress
=
0
;
/** @var string */
protected
$status
;
/** @var int */
private
$lastEventTimestampMs
;
/** @var int */
private
$lengthSecs
;
/** @var int */
private
$bytes
;
/**
* @param Video $video
* @return self
*/
public
function
setVideo
(
Video
$video
)
:
self
{
$this
->
video
=
$video
;
$this
->
guid
=
$video
->
getGuid
();
return
$this
;
}
/**
* Set the profile
* @param TranscodeProfiles\TranscodeProfileInterface $profile
* @throws TranscodeProfiles\UnavailableTranscodeProfileException
* @return self
*/
public
function
setProfile
(
TranscodeProfiles\TranscodeProfileInterface
$profile
)
:
self
{
if
(
$profile
->
isProOnly
()
&&
$this
->
video
&&
!
$this
->
video
->
getOwnerEntity
()
->
isPro
())
{
throw
new
TranscodeProfiles\UnavailableTranscodeProfileException
();
}
$this
->
profile
=
$profile
;
return
$this
;
}
/**
* Export
* @param array $extras
* @return array
*/
public
function
export
(
$extras
=
[])
:
array
{
return
[
'guid'
=>
$this
->
guid
,
'profile'
=>
$this
->
profile
->
export
(),
'progress'
=>
(
int
)
$this
->
progress
,
'completed'
=>
(
bool
)
$this
->
completed
,
'last_event_timestamp_ms'
=>
(
int
)
$this
->
lastEventTimestampMs
,
'length_secs'
=>
(
int
)
$this
->
lengthSecs
,
'bytes'
=>
(
int
)
$this
->
bytes
,
];
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeExecutors/FFMpegExecutor.php
0 → 100644
View file @
465c2658
<?php
/**
* Minds FFMpeg.
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeExecutors
;
use
FFMpeg\FFMpeg
as
FFMpegClient
;
use
FFMpeg\FFProbe
as
FFProbeClient
;
use
FFMpeg\Media\Video
as
FFMpegVideo
;
use
FFMpeg\Filters\Video\ResizeFilter
;
use
Minds\Core
;
use
Minds\Core\Config
;
use
Minds\Entities\Video
;
use
Minds\Core\Di\Di
;
use
Minds\Core\Media\TranscodingStatus
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Media\Video\Transcoder\TranscodeStorage\TranscodeStorageInterface
;
use
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
FFMpegExecutor
implements
TranscodeExecutorInterface
{
/** @var Config */
private
$config
;
/** @var FFMpeg */
private
$ffmpeg
;
/** @var FFProbe */
private
$ffprobe
;
/** @var TranscodeStorageInterface */
private
$transcodeStorage
;
public
function
__construct
(
$config
=
null
,
$ffmpeg
=
null
,
$ffprobe
=
null
,
$transcodeStorage
=
null
)
{
$this
->
config
=
$config
?:
Di
::
_
()
->
get
(
'Config'
);
$this
->
ffmpeg
=
$ffmpeg
?:
FFMpegClient
::
create
([
'ffmpeg.binaries'
=>
'/usr/bin/ffmpeg'
,
'ffprobe.binaries'
=>
'/usr/bin/ffprobe'
,
'ffmpeg.threads'
=>
$this
->
config
->
get
(
'transcoder'
)[
'threads'
],
'timeout'
=>
0
,
]);
$this
->
ffprobe
=
$ffprobe
?:
FFProbeClient
::
create
([
'ffprobe.binaries'
=>
'/usr/bin/ffprobe'
,
]);
$this
->
transcodeStorage
=
$transcodeStorage
??
Di
::
_
()
->
get
(
'Media\Video\Transcode\TranscodeStorage'
);
}
/**
* Transcode the video
* @param Transcode $transcode (pass by reference)
* @param callable $progressCallback
* @return bool
*/
public
function
transcode
(
Transcode
&
$transcode
,
callable
$progressCallback
)
:
bool
{
// This is the profile that will be used for the transcode
$transcodeProfiler
=
$transcode
->
getProfile
();
// Prepare the source of this transcode
$source
=
new
Transcode
();
$source
->
setGuid
(
$transcode
->
getGuid
())
->
setProfile
(
new
TranscodeProfiles\Source
());
// Simply change the source
// Download the source
$sourcePath
=
$this
->
transcodeStorage
->
downloadToTmp
(
$source
);
// Open the resource
/** @var \FFMpeg\Media\Video; */
$video
=
$this
->
ffmpeg
->
open
(
$sourcePath
);
if
(
!
$video
)
{
throw
new
FailedTranscodeException
(
"Source error"
);
}
$tags
=
null
;
try
{
$videostream
=
$this
->
ffprobe
->
streams
(
$sourcePath
)
->
videos
()
->
first
();
// get video metadata
$tags
=
$videostream
->
get
(
'tags'
);
}
catch
(
\Exception
$e
)
{
error_log
(
'Error getting videostream information'
);
}
// Thumbnails are treated differently to other transcodes
if
(
$transcode
->
getProfile
()
instanceof
TranscodeProfiles\Thumbnails
)
{
return
$this
->
transcodeThumbnails
(
$transcode
,
$sourcePath
,
$video
);
}
// Target height
$width
=
$transcodeProfiler
->
getWidth
();
$height
=
$transcodeProfiler
->
getHeight
();
// Logic for rotated videos
$rotated
=
isset
(
$tags
[
'rotate'
])
&&
in_array
(
$tags
[
'rotate'
],
[
270
,
90
],
true
);
if
(
$rotated
)
{
$ratio
=
$videostream
->
get
(
'width'
)
/
$videostream
->
get
(
'height'
);
// Invert width and height
$width
=
$height
;
$height
=
round
(
$height
*
$ratio
);
}
// Resize the video
$video
->
filters
()
->
resize
(
new
\FFMpeg\Coordinate\Dimension
(
$width
,
$height
),
$rotated
?
ResizeFilter
::
RESIZEMODE_FIT
:
ResizeFilter
::
RESIZEMODE_SCALE_WIDTH
)
->
synchronize
();
$pfx
=
$transcodeProfiler
->
getStorageName
();
$path
=
$sourcePath
.
'-'
.
$pfx
;
$format
=
$transcodeProfiler
->
getFormat
();
$formatMap
=
[
'video/mp4'
=>
(
new
\FFMpeg\Format\Video\X264
())
->
setAudioCodec
(
'aac'
),
'video/webm'
=>
new
\FFMpeg\Format\Video\WebM
(),
];
try
{
// $this->logger->info("Transcoding: $path ({$transcode->getGuid()})");
// Update our progress
$formatMap
[
$format
]
->
on
(
'progress'
,
function
(
$a
,
$b
,
$pct
)
use
(
$progressCallback
)
{
// $this->logger->info("$pct% transcoded");
$progressCallback
(
$pct
);
});
$formatMap
[
$format
]
->
setKiloBitRate
(
$transcodeProfiler
->
getBitrate
())
->
setAudioKiloBitrate
(
$transcodeProfiler
->
getAudioBitrate
());
// Run the transcode
$video
->
save
(
$formatMap
[
$format
],
$path
);
// Save to storage
$this
->
transcodeStorage
->
add
(
$transcode
,
$path
);
// Completed!
// $this->logger->info("Completed: $path ({$transcode->getGuid()})");
$transcode
->
setStatus
(
'completed'
);
}
catch
(
\Exception
$e
)
{
// $this->logger->out("Failed {$e->getMessage()}");
$transcode
->
setStatus
(
'failed'
);
// TODO: Should we also save the failure reason in the db?
// Throw a new 'failed' exception
throw
new
FailedTranscodeException
(
$e
->
getMessage
());
}
finally
{
// Cleanup our path
@
unlink
(
$path
);
}
// Cleanup our sourcefile
@
unlink
(
$sourcePath
);
return
true
;
}
/**
* Thumbnail transcodes are treated differently as they we extract frames
* @param Transcode $transcode
* @return bool
*/
protected
function
transcodeThumbnails
(
Transcode
&
$transcode
,
string
$sourcePath
,
FFMpegVideo
$video
)
:
bool
{
try
{
// Create a temporary directory for out thumbnails
$thumbnailsDir
=
$sourcePath
.
'-thumbnails'
;
@
mkdir
(
$thumbnailsDir
,
0600
,
true
);
// Create thumbnails
$length
=
round
((
int
)
$this
->
ffprobe
->
format
(
$sourcePath
)
->
get
(
'duration'
));
$secs
=
[
0
,
1
,
round
(
$length
/
2
),
$length
-
1
,
$length
];
foreach
(
$secs
as
$sec
)
{
$frame
=
$video
->
frame
(
\FFMpeg\Coordinate\TimeCode
::
fromSeconds
(
$sec
));
$pad
=
str_pad
(
$sec
,
5
,
'0'
,
STR_PAD_LEFT
);
$path
=
$thumbnailsDir
.
'/'
.
"thumbnail-
$pad
.png"
;
$frame
->
save
(
$path
);
// Hack the profile storage name, as there are multiple thumbnails
$transcode
->
getProfile
()
->
setStorageName
(
"thumbnail-
$pad
.png"
);
// Upload to filestore
$this
->
transcodeStorage
->
add
(
$transcode
,
$path
);
// Cleanup tmp
@
unlink
(
$path
);
}
$transcode
->
setProgress
(
100
);
$transcode
->
setStatus
(
'completed'
);
}
catch
(
\Exception
$e
)
{
$transcode
->
setStatus
(
'failed'
);
// TODO: Should we also save the failure reason in the db?
// Throw a new 'failed' exception
throw
new
FailedTranscodeException
(
$e
->
getMessage
());
}
finally
{
// Cleanup the temporary directory we made
@
unlink
(
$thumbnailsDir
);
}
return
true
;
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeExecutors/FailedTranscodeException.php
0 → 100644
View file @
465c2658
<?php
namespace
Minds\Core\Media\Video\Transcoder\TranscodeExecutors
;
class
FailedTranscodeException
extends
\Exception
{
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeExecutors/TranscodeExecutorInterface.php
0 → 100644
View file @
465c2658
<?php
namespace
Minds\Core\Media\Video\Transcoder\TranscodeExecutors
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
interface
TranscodeExecutorInterface
{
/**
* @param Transcode $transcode
* @return bool
*/
public
function
transcode
(
Transcode
&
$transcode
,
callable
$progressCallback
)
:
bool
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/AbstractTranscodeProfile.php
0 → 100644
View file @
465c2658
<?php
/**
* Abstract class for transcode profiles
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
use
Minds\Traits\MagicAttributes
;
/**
* @method string getFormat()
* @method int getWidth()
* @method int getHeight()
* @method int getBitrate()
* @method int getAudioBitrate()
* @method bool isProOnly()
* @method string getStorageName()
* @method TranscodeProfileInterface setStorageName(string $storageName)
*/
abstract
class
AbstractTranscodeProfile
implements
TranscodeProfileInterface
{
use
MagicAttributes
;
/** @var string */
protected
$format
;
/** @var int */
protected
$width
;
/** @var int */
protected
$height
;
/** @var int */
protected
$bitrate
;
/** @var int */
protected
$audioBitrate
;
/** @var bool */
protected
$proOnly
=
false
;
/** @var string */
protected
$storageName
;
/**
* Returns the ID of the transcode (this will usually be the classname)
* @return string
*/
public
function
getId
()
:
string
{
$path
=
explode
(
'\\'
,
get_called_class
());
return
array_pop
(
$path
);
}
/**
* Export the profile
* @param array $extras
* @return array
*/
public
function
export
(
$extras
=
[])
:
array
{
return
[
'id'
=>
$this
->
getId
(),
'format'
=>
$this
->
format
,
'width'
=>
(
int
)
$this
->
width
,
'height'
=>
(
int
)
$this
->
height
,
'bitrate'
=>
(
int
)
$this
->
bitrate
,
'audio_bitrate'
=>
(
int
)
$this
->
audioBitrarte
,
];
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/Factory.php
0 → 100644
View file @
465c2658
<?php
/**
* Source. This is the source file.
* It will not be transcoded but will be stored on the filestore
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
Factory
{
/**
* Build a TranscodeProfileInterface from ID
* @param string $profileId
* @return TranscodeProfile
* @throws TranscodeProfileNotFoundException
*/
public
static
function
build
(
string
$profileId
)
:
TranscodeProfileInterface
{
$class
=
"Minds
\\
Core
\\
Media
\\
Video
\\
Transcoder
\\
TranscodeProfiles
\\
$profileId
"
;
if
(
class_exists
(
$class
))
{
return
new
$class
;
}
throw
new
TranscodeProfileNotFoundException
(
"
$profileId
does not have a valid profile instance"
);
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/Source.php
0 → 100644
View file @
465c2658
<?php
/**
* Source. This is the source file.
* It will not be transcoded but will be stored on the filestore
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
Source
extends
AbstractTranscodeProfile
{
/** @var string */
protected
$format
=
'video/*'
;
/** @var int */
protected
$width
=
0
;
/** @var int */
protected
$height
=
0
;
/** @var string */
protected
$storageName
=
'source'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/Thumbnails.php
0 → 100644
View file @
465c2658
<?php
/**
* Thumbnails
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
Thumbnails
extends
AbstractTranscodeProfile
{
/** @var string */
protected
$format
=
'image/png'
;
/** @var int */
protected
$width
=
1920
;
/** @var int */
protected
$height
=
1080
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/TranscodeProfileInterface.php
0 → 100644
View file @
465c2658
<?php
/**
* Transcode Profile Interface
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
interface
TranscodeProfileInterface
{
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/TranscodeProfileNotFoundException.php
0 → 100644
View file @
465c2658
<?php
/**
* Unavailable Transcode Profile Exceptioon
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
TranscodeProfileNotFoundException
extends
\Exception
{
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/UnavailableTranscodeProfileException.php
0 → 100644
View file @
465c2658
<?php
/**
* Unavailable Transcode Profile Exceptioon
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
UnavailableTranscodeProfileException
extends
\Exception
{
/** @var string */
protected
$message
=
'This transcode profile is not available'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/Webm_1080p.php
0 → 100644
View file @
465c2658
<?php
/**
* 1080p Webm (pro only)
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
Webm_1080p
extends
AbstractTranscodeProfile
{
/** @var string */
protected
$format
=
'video/webm'
;
/** @var int */
protected
$width
=
1920
;
/** @var int */
protected
$height
=
1080
;
/** @var int */
protected
$bitrate
=
2000
;
/** @var int */
protected
$audioBitrate
=
128
;
/** @var bool */
protected
$proOnly
=
true
;
/** @var string */
protected
$storageName
=
'1080.webm'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/Webm_360p.php
0 → 100644
View file @
465c2658
<?php
/**
* 360p Webm
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
Webm_360p
extends
AbstractTranscodeProfile
{
/** @var string */
protected
$format
=
'video/webm'
;
/** @var int */
protected
$width
=
640
;
/** @var int */
protected
$height
=
360
;
/** @var int */
protected
$bitrate
=
500
;
/** @var int */
protected
$audioBitrate
=
80
;
/** @var bool */
protected
$proOnly
=
false
;
/** @var string */
protected
$storageName
=
'360.webm'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/Webm_720p.php
0 → 100644
View file @
465c2658
<?php
/**
* 720p Webm
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
Webm_720p
extends
AbstractTranscodeProfile
{
/** @var string */
protected
$format
=
'video/webm'
;
/** @var int */
protected
$width
=
1280
;
/** @var int */
protected
$height
=
720
;
/** @var int */
protected
$bitrate
=
1000
;
/** @var int */
protected
$audioBitrate
=
128
;
/** @var bool */
protected
$proOnly
=
false
;
/** @var string */
protected
$storageName
=
'720.webm'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/X264_1080p.php
0 → 100644
View file @
465c2658
<?php
/**
* 1080p MP4 (pro only)
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
X264_1080p
extends
AbstractTranscodeProfile
{
/** @var string */
protected
$format
=
'video/mp4'
;
/** @var int */
protected
$width
=
1920
;
/** @var int */
protected
$height
=
1080
;
/** @var int */
protected
$bitrate
=
2000
;
/** @var int */
protected
$audioBitrate
=
128
;
/** @var bool */
protected
$proOnly
=
true
;
/** @var string */
protected
$storageName
=
'1080.mp4'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/X264_360p.php
0 → 100644
View file @
465c2658
<?php
/**
* 360p MP4
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
X264_360p
extends
AbstractTranscodeProfile
{
/** @var string */
protected
$format
=
'video/mp4'
;
/** @var int */
protected
$width
=
640
;
/** @var int */
protected
$height
=
360
;
/** @var int */
protected
$bitrate
=
500
;
/** @var int */
protected
$audioBitrate
=
80
;
/** @var bool */
protected
$proOnly
=
false
;
/** @var string */
protected
$storageName
=
'360.mp4'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/X264_720p.php
0 → 100644
View file @
465c2658
<?php
/**
* 720p MP4
*/
namespace
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
class
X264_720p
extends
AbstractTranscodeProfile
{
/** @var string */
protected
$format
=
'video/mp4'
;
/** @var int */
protected
$width
=
1280
;
/** @var int */
protected
$height
=
720
;
/** @var int */
protected
$bitrate
=
1000
;
/** @var int */
protected
$audioBitrate
=
128
;
/** @var bool */
protected
$proOnly
=
false
;
/** @var string */
protected
$storageName
=
'720.mp4'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeStates.php
0 → 100644
View file @
465c2658
<?php
namespace
Minds\Core\Media\Video\Transcoder
;
use
Minds\Entities\Video
;
class
TranscodeStates
{
/** @var string */
public
const
CREATED
=
'created'
;
/** @var string */
public
const
TRANSCODING
=
'transcoding'
;
/** @var string */
public
const
FAILED
=
'failed'
;
/** @var string */
public
const
COMPLETED
=
'completed'
;
/** @var Repository */
private
$repository
;
public
function
__construct
(
$repository
=
null
)
{
// NOTE: We are using repository as this is called via
// Delegates\NotificationDelegate and it causes an infinite loop
// with the manager
$this
->
repository
=
$repository
??
new
Repository
();
}
/**
* Return the overral transcoding status
* MH: I don't love this function at all!
* @param Video $video
* @return string
*/
public
function
getStatus
(
Video
$video
)
:
string
{
$transcodes
=
$this
->
repository
->
getList
([
'guid'
=>
$video
->
getGuid
(),
]);
$total
=
0
;
$created
=
0
;
$failures
=
0
;
$completed
=
0
;
foreach
(
$transcodes
as
$transcode
)
{
if
(
$transcode
instanceof
TranscodeProfiles\Thumbnails
)
{
continue
;
// We skip thumbnails as these are likely to succeed
}
++
$total
;
switch
(
$transcode
->
getStatus
())
{
case
TranscodeStates
::
TRANSCODING
:
if
(
$transcode
->
getLastEventTimestampMs
()
>=
(
time
()
-
Manager
::
TRANSCODER_TIMEOUT_SECS
)
*
1000
)
{
// Still transcoding
return
TranscodeStates
::
TRANSCODING
;
}
else
{
++
$failures
;
}
break
;
case
TranscodeStates
::
CREATED
:
// If not started to transcode then we are in a created state
++
$created
;
break
;
case
TranscodeStates
::
FAILED
:
++
$failures
;
// We should allow failures for some transcodes
break
;
case
TranscodeStates
::
COMPLETED
:
++
$completed
;
break
;
}
}
if
(
$created
>
(
$completed
+
$failures
))
{
return
TranscodeStates
::
CREATED
;
}
if
(
$total
<
(
$completed
+
$failures
))
{
return
TranscodeStates
::
CREATED
;
}
// If we have more completions then failures the declare completed
if
(
$failures
<
$completed
)
{
return
TranscodeStates
::
COMPLETED
;
}
return
TranscodeStates
::
FAILED
;
// Our default state is failed?
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeStorage/S3Storage.php
0 → 100644
View file @
465c2658
<?php
namespace
Minds\Core\Media\Video\Transcoder\TranscodeStorage
;
use
Aws\S3\S3Client
;
use
Minds\Core\Config
;
use
Minds\Core\Di\Di
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
class
S3Storage
implements
TranscodeStorageInterface
{
/** @var string */
private
$dir
=
'cinemr_data'
;
/** @var Config */
private
$config
;
/** @var S3Client */
private
$s3
;
public
function
__construct
(
$config
=
null
,
$s3
=
null
)
{
$this
->
config
=
$config
??
Di
::
_
()
->
get
(
'Config'
);
$this
->
dir
=
$this
->
config
->
get
(
'transcoder'
)[
'dir'
];
$awsConfig
=
$this
->
config
->
get
(
'aws'
);
$opts
=
[
'region'
=>
$awsConfig
[
'region'
],
];
if
(
!
isset
(
$awsConfig
[
'useRoles'
])
||
!
$awsConfig
[
'useRoles'
])
{
$opts
[
'credentials'
]
=
[
'key'
=>
$awsConfig
[
'key'
],
'secret'
=>
$awsConfig
[
'secret'
],
];
}
$this
->
s3
=
$s3
?:
new
S3Client
(
array_merge
([
'version'
=>
'2006-03-01'
],
$opts
));
}
/**
* Add a transcode to storage
* @param Transcode $transcode
* @param string $path
* @return bool
*/
public
function
add
(
Transcode
$transcode
,
string
$path
)
:
bool
{
return
(
bool
)
$this
->
s3
->
putObject
([
'ACL'
=>
'public-read'
,
'Bucket'
=>
'cinemr'
,
'Key'
=>
"
$this->dir
/
{
$transcode
->
getGuid
()
}
/
{
$transcode
->
getProfile
()
->
getStorageName
()
}
"
,
'Body'
=>
fopen
(
$path
,
'r'
),
]);
}
/**
* This will return a url that can be used by an HTTP client
* to upload the source file
* @param Transcode $transcode
* @return string
*/
public
function
getClientSideUploadUrl
(
Transcode
$transcode
)
:
string
{
$cmd
=
$this
->
s3
->
getCommand
(
'PutObject'
,
[
'Bucket'
=>
'cinemr'
,
'Key'
=>
"
$this->dir
/
{
$transcode
->
getGuid
()
}
/
{
$transcode
->
getProfile
()
->
getStorageName
()
}
"
,
]);
return
(
string
)
$this
->
s3
->
createPresignedRequest
(
$cmd
,
'+20 minutes'
)
->
getUri
();
}
/**
* @param Transcode $transcode
* @return string
*/
public
function
downloadToTmp
(
Transcode
$transcode
)
:
string
{
// Create a temporary file where our source file will go
$sourcePath
=
tempnam
(
sys_get_temp_dir
(),
"
{
$transcode
->
getGuid
()
}
-
{
$transcode
->
getProfile
()
->
getStorageName
()
}
"
);
// Grab from S3
$this
->
s3
->
getObject
([
'Bucket'
=>
'cinemr'
,
'Key'
=>
"
$this->dir
/
{
$transcode
->
getGuid
()
}
/
{
$transcode
->
getProfile
()
->
getStorageName
()
}
"
,
'SaveAs'
=>
$sourcePath
,
]);
return
$sourcePath
;
}
/**
* Return a list of files from storage
* @param string $guid
* @return array
*/
public
function
ls
(
string
$guid
)
:
array
{
$awsResult
=
$this
->
s3
->
listObjects
([
'Bucket'
=>
'cinemr'
,
'Prefix'
=>
"
{
$this
->
dir
}
/
{
$guid
}
"
,
]);
$s3Contents
=
$awsResult
[
'Contents'
];
return
array_column
(
$s3Contents
,
'Key'
);
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeStorage/TranscodeStorageInterface.php
0 → 100644
View file @
465c2658
<?php
namespace
Minds\Core\Media\Video\Transcoder\TranscodeStorage
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
interface
TranscodeStorageInterface
{
/**
* @param Transcode $transcode
* @param string $path
* @return bool
*/
public
function
add
(
Transcode
$transcode
,
string
$path
)
:
bool
;
/**
* This will return a url that can be used by an HTTP client
* to upload the source file
* @param Transcode $transcode
* @return string
*/
public
function
getClientSideUploadUrl
(
Transcode
$transcode
)
:
string
;
/**
* @param Transcode $transcode
* @return string
*/
public
function
downloadToTmp
(
Transcode
$transcode
)
:
string
;
/**
* Return a list of files from storage
* @param string $guid
* @return array
*/
public
function
ls
(
string
$guid
)
:
array
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/Transcodes.php
0 → 100644
View file @
465c2658
<?php
/**
* Transcode model
*/
namespace
Minds\Core\Media\Video\Transcoder
;
use
Minds\Traits\MagicAttributes
;
class
Transcodes
{
use
MagicAttributes
;
/** @var string */
private
$guid
;
/** @var Transcode[] */
private
$transcodes
;
public
function
export
(
$extras
=
[])
:
array
{
return
[
'guid'
=>
$this
->
guid
,
'transcodes'
=>
$this
->
transcodes
?
array_map
(
function
(
$transcode
)
{
return
$transcode
->
export
();
},
$this
->
transcodes
)
:
null
];
}
}
This diff is collapsed.
Core/Provisioner/Provisioners/cassandra-provision.cql
View file @
465c2658
...
...
@@ -1533,3 +1533,14 @@ CREATE MATERIALIZED VIEW minds.withdrawals_by_status AS
WHERE status IS NOT NULL AND user_guid IS NOT NULL AND timestamp IS NOT NULL AND tx IS NOT NULL
PRIMARY KEY (status, timestamp, user_guid, tx)
WITH CLUSTERING ORDER BY (timestamp ASC, user_guid ASC, tx ASC);
CREATE TABLE minds.video_transcodes (
guid bigint,
profile_id text,
progress int,
status text,
last_event_timestamp_ms timestamp,
length_secs int,
bytes int,
PRIMARY KEY (guid, profile_id)
);
\ No newline at end of file
This diff is collapsed.
Core/Queue/Runners/Transcode.php
View file @
465c2658
...
...
@@ -2,6 +2,7 @@
namespace
Minds\Core\Queue\Runners
;
use
Minds\Core
;
use
Minds\Core\Di\Di
;
use
Minds\Core\Queue\Interfaces
;
class
Transcode
implements
Interfaces\QueueRunner
...
...
@@ -14,11 +15,12 @@ class Transcode implements Interfaces\QueueRunner
$client
->
setQueue
(
"Transcode"
)
->
receive
(
function
(
$data
)
{
$d
=
$data
->
getData
();
$transcode
=
unserialize
(
$d
[
'transcode'
]);
echo
"Received a transcode request
\n
"
;
$transcoder
=
new
Core\Media\Services\FFMpeg
();
$transcoder
->
setKey
(
$d
[
'key'
]);
$transcoder
->
setFullHD
(
$d
[
'full_hd'
]);
$transcoder
->
onQueue
();
$transcoderManager
=
Di
::
_
()
->
get
(
'Media\Video\Transcoder\Manager'
);
$transcoderManager
->
transcode
(
$transcode
);
},
[
'max_messages'
=>
1
]);
}
}
This diff is collapsed.
Entities/Video.php
View file @
465c2658
...
...
@@ -51,14 +51,15 @@ class Video extends MindsObject
public
function
upload
(
$filepath
)
{
// TODO: Confirm why this is still here
$this
->
generateGuid
();
$transcoder
=
ServiceFactory
::
build
(
'FFMpeg'
);
$transcoder
->
setKey
(
$this
->
getGuid
())
->
setFullHD
(
$this
->
getFlag
(
'full_hd'
))
->
saveToFilestore
(
$filepath
)
->
transcode
();
// Upload the source and start the transcoder pipeline
$transcoderManager
=
Di
::
_
()
->
get
(
'Media\Video\Transcoder\Manager'
);
$transcoderManager
->
uploadSource
(
$this
,
$filepath
)
->
createTranscodes
(
$this
);
// Legacy support
$this
->
cinemr_guid
=
$this
->
getGuid
();
}
...
...
This diff is collapsed.
Spec/Core/Media/ClientUpload/ManagerSpec.php
View file @
465c2658
...
...
@@ -4,23 +4,24 @@ 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\Media\
Video\Transcoder
;
use
Minds\Core\GuidBuilder
;
use
Minds\Core\Entities\Actions\Save
;
use
Minds\Entities\Video
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
ManagerSpec
extends
ObjectBehavior
{
private
$
ffmpeg
;
private
$
transcoderManager
;
private
$guid
;
private
$save
;
public
function
let
(
FFMpeg
$FFMpeg
,
GuidBuilder
$guid
,
Save
$save
)
public
function
let
(
Transcoder\Manager
$transcoderManager
,
GuidBuilder
$guid
,
Save
$save
)
{
$this
->
beConstructedWith
(
$
FFMpeg
,
$guid
,
$save
);
$this
->
ffmpeg
=
$FFMpeg
;
$this
->
beConstructedWith
(
$
transcoderManager
,
$guid
,
$save
);
$this
->
transcoderManager
=
$transcoderManager
;
$this
->
guid
=
$guid
;
$this
->
save
=
$save
;
}
...
...
@@ -35,10 +36,8 @@ class ManagerSpec extends ObjectBehavior
$this
->
guid
->
build
()
->
willReturn
(
123
);
$this
->
ffmpeg
->
setKey
(
123
)
->
shouldBeCalled
();
$this
->
ffmpeg
->
getPresignedUrl
()
$this
->
transcoderManager
->
getClientSideUploadUrl
(
Argument
::
type
(
Video
::
class
))
->
shouldBeCalled
()
->
willReturn
(
's3-url-here'
);
$lease
=
$this
->
prepare
(
'video'
);
...
...
@@ -69,17 +68,10 @@ class ManagerSpec extends ObjectBehavior
$this
->
save
->
save
()
->
shouldBeCalled
();
$this
->
ffmpeg
->
setKey
(
456
)
->
shouldBeCalled
();
$this
->
ffmpeg
->
setFullHD
(
false
)
->
shouldBeCalled
();
$this
->
ffmpeg
->
transcode
()
$this
->
transcoderManager
->
createTranscodes
(
Argument
::
type
(
Video
::
class
))
->
shouldBeCalled
();
$this
->
setFullHD
(
false
)
->
complete
(
$lease
)
$this
->
complete
(
$lease
)
->
shouldReturn
(
true
);
}
}
This diff is collapsed.
Spec/Core/Media/Video/ManagerSpec.php
View file @
465c2658
...
...
@@ -5,7 +5,10 @@ namespace Spec\Minds\Core\Media\Video;
use
Minds\Core\Config
;
use
Aws\S3\S3Client
;
use
Minds\Core\Media\Video\Manager
;
use
Minds\Core\Media\Video\Transcoder
;
use
Minds\Entities\Video
;
use
Minds\Core\EntitiesBuilder
;
use
Minds\Common\Repository\Response
;
use
Psr\Http\Message\RequestInterface
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
...
...
@@ -14,12 +17,16 @@ class ManagerSpec extends ObjectBehavior
{
private
$config
;
private
$s3
;
private
$entitiesBuilder
;
private
$transcoderManager
;
public
function
let
(
Config
$config
,
S3Client
$s3
)
public
function
let
(
Config
$config
,
S3Client
$s3
,
EntitiesBuilder
$entitiesBuilder
,
Transcoder\Manager
$transcoderManager
)
{
$this
->
beConstructedWith
(
$config
,
$s3
);
$this
->
beConstructedWith
(
$config
,
$s3
,
$entitiesBuilder
,
$transcoderManager
);
$this
->
config
=
$config
;
$this
->
s3
=
$s3
;
$this
->
entitiesBuilder
=
$entitiesBuilder
;
$this
->
transcoderManager
=
$transcoderManager
;
}
public
function
it_is_initializable
()
...
...
@@ -27,6 +34,55 @@ class ManagerSpec extends ObjectBehavior
$this
->
shouldHaveType
(
Manager
::
class
);
}
public
function
it_should_return_a_video
()
{
$video
=
new
Video
();
$this
->
entitiesBuilder
->
single
(
123
)
->
shouldBeCalled
()
->
willReturn
(
$video
);
$this
->
get
(
123
)
->
shouldReturn
(
$video
);
}
public
function
it_should_return_available_sources
()
{
$video
=
new
Video
();
$video
->
set
(
'guid'
,
'123'
);
$this
->
transcoderManager
->
getList
([
'guid'
=>
'123'
,
'legacyPolyfill'
=>
true
,
])
->
shouldBeCalled
()
->
willReturn
(
new
Response
([
(
new
Transcoder\Transcode
())
->
setGuid
(
'123'
)
->
setProfile
(
new
Transcoder\TranscodeProfiles\X264_720p
())
->
setStatus
(
'created'
),
(
new
Transcoder\Transcode
())
->
setGuid
(
'123'
)
->
setProfile
(
new
Transcoder\TranscodeProfiles\X264_360p
())
->
setStatus
(
'completed'
),
(
new
Transcoder\Transcode
())
->
setGuid
(
'123'
)
->
setProfile
(
new
Transcoder\TranscodeProfiles\Webm_360p
())
->
setStatus
(
'completed'
),
]));
$sources
=
$this
->
getSources
(
$video
);
$sources
->
shouldHaveCount
(
2
);
$sources
[
0
]
->
getType
()
->
shouldBe
(
'video/mp4'
);
$sources
[
0
]
->
getSize
(
360
);
$sources
[
1
]
->
getType
()
->
shouldBe
(
'video/webm'
);
$sources
[
1
]
->
getSize
(
360
);
}
public
function
it_should_get_a_signed_720p_video_url
(
RequestInterface
$request
,
\Aws\CommandInterface
$cmd
)
{
$this
->
config
->
get
(
'transcoder'
)
...
...
This diff is collapsed.
Spec/Core/Media/Video/Transcoder/Delegates/NotificationDelegateSpec.php
0 → 100644
View file @
465c2658
<?php
namespace
Spec\Minds\Core\Media\Video\Transcoder\Delegates
;
use
Minds\Core\Media\Video\Transcoder\Delegates\NotificationDelegate
;
use
Minds\Core\Media\Video\Transcoder\TranscodeStates
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Events\EventsDispatcher
;
use
Minds\Core\EntitiesBuilder
;
use
Minds\Entities\Video
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
NotificationDelegateSpec
extends
ObjectBehavior
{
private
$transcodeStates
;
private
$eventsDispatcher
;
private
$entitiesBuilder
;
public
function
let
(
TranscodeStates
$transcodeStates
,
EventsDispatcher
$eventsDispatcher
,
EntitiesBuilder
$entitiesBuilder
)
{
$this
->
beConstructedWith
(
$transcodeStates
,
$eventsDispatcher
,
$entitiesBuilder
);
$this
->
transcodeStates
=
$transcodeStates
;
$this
->
eventsDispatcher
=
$eventsDispatcher
;
$this
->
entitiesBuilder
=
$entitiesBuilder
;
}
private
function
mockFetchVideo
()
{
$this
->
entitiesBuilder
->
single
(
'123'
)
->
shouldBeCalled
()
->
willReturn
(
(
new
Video
)
->
set
(
'guid'
,
'123'
)
->
set
(
'owner_guid'
,
'456'
)
);
}
public
function
it_is_initializable
()
{
$this
->
shouldHaveType
(
NotificationDelegate
::
class
);
}
// public function it_should_send_notification_of_completed()
// {
// $transcode = new Transcode();
// $transcode->setGuid('123');
// $this->mockFetchVideo();
// $this->transcodeStates->getStatus(Argument::that(function ($video) {
// return $video->getGuid() === '123';
// }))
// ->shouldBeCalled()
// ->willReturn('completed');
// $this->eventsDispatcher->trigger('notification', 'transcoder', Argument::type('array'))
// ->shouldBeCalled();
// $this->onTranscodeCompleted($transcode);
// }
// public function it_should_send_notification_of_failed()
// {
// $transcode = new Transcode();
// $transcode->setGuid('123');
// $this->mockFetchVideo();
// $this->transcodeStates->getStatus(Argument::that(function ($video) {
// return $video->getGuid() === '123';
// }))
// ->shouldBeCalled()
// ->willReturn('failed');
// $this->eventsDispatcher->trigger('notification', 'transcoder', Argument::type('array'))
// ->shouldBeCalled();
// $this->onTranscodeCompleted($transcode);
// }
public
function
it_should_do_nothing
()
{
$transcode
=
new
Transcode
();
$transcode
->
setGuid
(
'123'
);
$this
->
mockFetchVideo
();
$this
->
transcodeStates
->
getStatus
(
Argument
::
that
(
function
(
$video
)
{
return
$video
->
getGuid
()
===
'123'
;
}))
->
shouldBeCalled
()
->
willReturn
(
'transcoding'
);
$this
->
eventsDispatcher
->
trigger
(
'notification'
,
'transcoder'
,
Argument
::
type
(
'array'
))
->
shouldNotBeCalled
();
$this
->
onTranscodeCompleted
(
$transcode
);
}
}
This diff is collapsed.
Spec/Core/Media/Video/Transcoder/Delegates/QueueDelegateSpec.php
0 → 100644
View file @
465c2658
<?php
namespace
Spec\Minds\Core\Media\Video\Transcoder\Delegates
;
use
Minds\Core\Media\Video\Transcoder\Delegates\QueueDelegate
;
use
Minds\Core\Queue\Interfaces\QueueClient
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
QueueDelegateSpec
extends
ObjectBehavior
{
private
$queueClient
;
public
function
let
(
QueueClient
$queueClient
)
{
$this
->
beConstructedWith
(
$queueClient
);
$this
->
queueClient
=
$queueClient
;
}
public
function
it_is_initializable
()
{
$this
->
shouldHaveType
(
QueueDelegate
::
class
);
}
public
function
it_should_add_to_queue
()
{
$transcode
=
new
Transcode
();
$this
->
queueClient
->
setQueue
(
'Transcode'
)
->
shouldBeCalled
()
->
willReturn
(
$this
->
queueClient
);
$this
->
queueClient
->
send
(
Argument
::
that
(
function
(
$message
)
use
(
$transcode
)
{
return
unserialize
(
$message
[
'transcode'
])
==
$transcode
;
}))
->
shouldBeCalled
();
$this
->
onAdd
(
$transcode
);
}
}
This diff is collapsed.
Spec/Core/Media/Video/Transcoder/ManagerSpec.php
0 → 100644
View file @
465c2658
<?php
namespace
Spec\Minds\Core\Media\Video\Transcoder
;
use
Minds\Core\Media\Video\Transcoder\Manager
;
use
Minds\Core\Media\Video\Transcoder\Repository
;
use
Minds\Core\Media\Video\Transcoder\Delegates\QueueDelegate
;
use
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
use
Minds\Core\Media\Video\Transcoder\TranscodeStorage\TranscodeStorageInterface
;
use
Minds\Core\Media\Video\Transcoder\TranscodeExecutors\TranscodeExecutorInterface
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Media\Video\Transcoder\Delegates\NotificationDelegate
;
use
Minds\Entities\Video
;
use
Minds\Entities\User
;
use
Minds\Common\Repository\Response
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
ManagerSpec
extends
ObjectBehavior
{
private
$repository
;
private
$queueDelegate
;
private
$transcodeStorage
;
private
$transcodeExecutor
;
private
$notificationDelegate
;
public
function
let
(
Repository
$repository
,
QueueDelegate
$queueDelegate
,
TranscodeStorageInterface
$transcodeStorage
,
TranscodeExecutorInterface
$transcodeExecutor
,
NotificationDelegate
$notificationDelegate
)
{
$this
->
beConstructedWith
(
$repository
,
$queueDelegate
,
$transcodeStorage
,
$transcodeExecutor
,
$notificationDelegate
);
$this
->
repository
=
$repository
;
$this
->
queueDelegate
=
$queueDelegate
;
$this
->
transcodeStorage
=
$transcodeStorage
;
$this
->
transcodeExecutor
=
$transcodeExecutor
;
$this
->
notificationDelegate
=
$notificationDelegate
;
}
public
function
it_is_initializable
()
{
$this
->
shouldHaveType
(
Manager
::
class
);
}
public
function
it_should_upload_source_file_to_transcoder_storage
()
{
$video
=
new
Video
();
$video
->
guid
=
123
;
$this
->
transcodeStorage
->
add
(
Argument
::
that
(
function
(
$transcode
)
use
(
$video
)
{
return
$transcode
->
getProfile
()
instanceof
TranscodeProfiles\Source
&&
$transcode
->
getVideo
()
->
getGuid
()
===
$video
->
getGuid
();
}),
'/tmp/my-fake-video'
)
->
shouldBeCalled
()
->
willReturn
(
true
);
$this
->
uploadSource
(
$video
,
'/tmp/my-fake-video'
)
->
shouldReturn
(
true
);
}
public
function
it_should_return_a_signed_url_for_client_upload
()
{
$this
->
transcodeStorage
->
getClientSideUploadUrl
(
Argument
::
type
(
Transcode
::
class
))
->
shouldBeCalled
()
->
willReturn
(
'signed-url-here'
);
$this
->
getClientSideUploadUrl
(
new
Video
())
->
shouldBe
(
'signed-url-here'
);
}
public
function
it_should_create_transcodes_from_video
()
{
$video
=
new
Video
();
$video
->
guid
=
123
;
$user
=
new
User
();
$user
->
pro
=
0
;
$video
->
set
(
'owner'
,
$user
);
foreach
([
TranscodeProfiles\Thumbnails
::
class
,
TranscodeProfiles\X264_360p
::
class
,
TranscodeProfiles\X264_720p
::
class
,
TranscodeProfiles\Webm_360p
::
class
,
TranscodeProfiles\Webm_720p
::
class
,
]
as
$i
=>
$profile
)
{
// Should be added to repo
$this
->
repository
->
add
(
Argument
::
that
(
function
(
$transcode
)
use
(
$video
,
$profile
)
{
return
$transcode
->
getProfile
()
instanceof
$profile
&&
$transcode
->
getGuid
()
===
$video
->
getGuid
();
}))
->
shouldBeCalled
();
// And queue
$this
->
queueDelegate
->
onAdd
(
Argument
::
that
(
function
(
$transcode
)
use
(
$video
,
$profile
)
{
return
$transcode
->
getProfile
()
instanceof
$profile
&&
$transcode
->
getGuid
()
===
$video
->
getGuid
();
}))
->
shouldBeCalled
();
}
// Should not add 1080p
$this
->
repository
->
add
(
Argument
::
that
(
function
(
$transcode
)
use
(
$video
,
$profile
)
{
return
$transcode
->
getProfile
()
instanceof
TranscodeProfiles\X264_1080p
&&
$transcode
->
getGuid
()
===
$video
->
getGuid
();
}))
->
shouldNotBeCalled
();
$this
->
createTranscodes
(
$video
);
}
public
function
it_should_add_transcode
()
{
$transcode
=
new
Transcode
();
$this
->
repository
->
add
(
$transcode
)
->
shouldBeCalled
();
$this
->
queueDelegate
->
onAdd
(
$transcode
)
->
shouldBeCalled
();
$this
->
add
(
$transcode
);
}
public
function
it_should_update
()
{
$transcode
=
new
Transcode
();
$this
->
repository
->
update
(
$transcode
,
[
'myDirtyField'
])
->
shouldBeCalled
();
$this
->
update
(
$transcode
,
[
'myDirtyField'
]);
}
public
function
it_should_execute_the_transcode
()
{
$transcode
=
new
Transcode
();
$transcode
->
setGuid
(
'123'
);
$this
->
repository
->
update
(
Argument
::
that
(
function
(
$transcode
)
{
return
$transcode
->
getStatus
()
===
'transcoding'
;
}),
[
'status'
])
->
shouldBeCalled
();
$this
->
transcodeExecutor
->
transcode
(
$transcode
,
Argument
::
any
())
->
shouldBeCalled
()
->
willReturn
(
true
);
$this
->
repository
->
update
(
Argument
::
that
(
function
(
$transcode
)
{
return
$transcode
->
getStatus
()
===
'completed'
;
}),
[
'progress'
,
'status'
])
->
shouldBeCalled
();
// Check for future transcodes is called
$this
->
notificationDelegate
->
onTranscodeCompleted
(
$transcode
)
->
shouldBeCalled
();
$this
->
transcode
(
$transcode
);
}
public
function
it_should_get_legacy_files
()
{
$this
->
repository
->
getList
([
'guid'
=>
'123'
,
'profileId'
=>
null
,
'status'
=>
null
,
'legacyPolyfill'
=>
true
,
])
->
shouldBeCalled
()
->
willReturn
(
new
Response
());
$this
->
transcodeStorage
->
ls
(
'123'
)
->
shouldBeCalled
()
->
willReturn
([
'/my-dir/123/360.mp4'
,
'/my-dir/123/720.mp4'
,
'/my-dir/123/360.webm'
,
]);
$transcodes
=
$this
->
getList
([
'guid'
=>
'123'
,
'legacyPolyfill'
=>
true
,
]);
$transcodes
->
shouldHaveCount
(
3
);
$transcodes
[
0
]
->
getProfile
()
->
getStorageName
(
'360.mp4'
);
}
}
This diff is collapsed.
Spec/Core/Media/Video/Transcoder/RepositorySpec.php
0 → 100644
View file @
465c2658
<?php
namespace
Spec\Minds\Core\Media\Video\Transcoder
;
use
Minds\Core\Media\Video\Transcoder\Repository
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
use
Minds\Core\Data\Cassandra\Client
;
use
Spec\Minds\Mocks\Cassandra\Rows
;
use
Cassandra\Bigint
;
use
Cassandra\Timestamp
;
use
Cassandra\Varint
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
RepositorySpec
extends
ObjectBehavior
{
private
$db
;
public
function
let
(
Client
$db
)
{
$this
->
beConstructedWith
(
$db
);
$this
->
db
=
$db
;
}
public
function
it_is_initializable
()
{
$this
->
shouldHaveType
(
Repository
::
class
);
}
public
function
it_should_add
(
Transcode
$transcode
)
{
$transcode
->
getGuid
()
->
shouldBeCalled
()
->
willReturn
(
"123"
);
$transcode
->
getProfile
()
->
shouldBeCalled
()
->
willReturn
(
new
TranscodeProfiles\X264_360p
());
$transcode
->
getStatus
()
->
shouldBeCalled
()
->
willReturn
(
'created'
);
$this
->
db
->
request
(
Argument
::
that
(
function
(
$prepared
)
{
return
true
;
}))
->
shouldBeCalled
();
$this
->
add
(
$transcode
)
->
shouldReturn
(
true
);
}
public
function
it_should_get_single
()
{
$this
->
db
->
request
(
Argument
::
that
(
function
(
$prepared
)
{
return
true
;
}))
->
shouldBeCalled
()
->
willReturn
(
new
Rows
([
[
'guid'
=>
new
Bigint
(
123
),
'profile_id'
=>
'X264_360p'
,
'last_event_timestamp_ms'
=>
new
Timestamp
(
microtime
(
true
)),
'progress'
=>
new
Varint
(
0
),
'status'
=>
null
,
'length_secs'
=>
new
Varint
(
0
),
'bytes'
=>
new
Varint
(
0
),
]
],
null
));
$transcode
=
$this
->
get
(
"urn:transcode:123-X264_360p"
);
$transcode
->
getGuid
()
->
shouldBe
(
"123"
);
}
public
function
it_should_get_list
()
{
$this
->
db
->
request
(
Argument
::
that
(
function
(
$prepared
)
{
return
true
;
}))
->
shouldBeCalled
()
->
willReturn
(
new
Rows
([
[
'guid'
=>
new
Bigint
(
123
),
'profile_id'
=>
'X264_360p'
,
'last_event_timestamp_ms'
=>
new
Timestamp
(
microtime
(
true
)),
'progress'
=>
new
Varint
(
0
),
'status'
=>
null
,
'length_secs'
=>
new
Varint
(
0
),
'bytes'
=>
new
Varint
(
0
),
],
[
'guid'
=>
new
Bigint
(
456
),
'profile_id'
=>
'X264_720p'
,
'last_event_timestamp_ms'
=>
new
Timestamp
(
microtime
(
true
)),
'progress'
=>
new
Varint
(
0
),
'status'
=>
null
,
'length_secs'
=>
new
Varint
(
0
),
'bytes'
=>
new
Varint
(
0
),
]
],
null
));
$rows
=
$this
->
getList
([]);
$X264_360Transcode
=
$rows
[
0
];
$X264_360Transcode
->
getGuid
()
->
shouldBe
(
"123"
);
$X264_720Transcode
=
$rows
[
1
];
$X264_720Transcode
->
getGuid
()
->
shouldBe
(
"456"
);
}
public
function
it_should_update
(
Transcode
$transcode
)
{
$transcode
->
getGuid
()
->
shouldBeCalled
()
->
willReturn
(
"123"
);
$transcode
->
getProfile
()
->
shouldBeCalled
()
->
willReturn
(
new
TranscodeProfiles\X264_360p
());
$transcode
->
setLastEventTimestampMs
(
Argument
::
approximate
(
round
(
microtime
(
true
)
*
1000
),
-
4
))
->
shouldBeCalled
();
$transcode
->
getLastEventTimestampMs
()
->
shouldBeCalled
()
->
willReturn
(
round
(
microtime
(
true
)
*
1000
));
$this
->
db
->
request
(
Argument
::
that
(
function
(
$prepared
)
{
return
true
;
}))
->
shouldBeCalled
();
$this
->
update
(
$transcode
,
[])
->
shouldReturn
(
true
);
}
public
function
it_should_delete
(
Transcode
$transcode
)
{
$transcode
->
getGuid
()
->
shouldBeCalled
()
->
willReturn
(
"123"
);
$transcode
->
getProfile
()
->
shouldBeCalled
()
->
willReturn
(
new
TranscodeProfiles\X264_360p
());
$this
->
db
->
request
(
Argument
::
that
(
function
(
$prepared
)
{
return
true
;
}))
->
shouldBeCalled
();
$this
->
delete
(
$transcode
)
->
shouldReturn
(
true
);
}
}
This diff is collapsed.
Spec/Core/Media/Video/Transcoder/TranscodeExecutors/FFMpegExecutorSpec.php
0 → 100644
View file @
465c2658
<?php
namespace
Spec\Minds\Core\Media\Video\Transcoder\TranscodeExecutors
;
use
Minds\Core\Media\Video\Transcoder\TranscodeExecutors\FFMpegExecutor
;
use
Minds\Core\Media\Video\Transcoder\TranscodeStorage\TranscodeStorageInterface
;
use
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Media\Video\Transcoder\TranscodeExecutors\FailedTranscodeException
;
use
FFMpeg\FFMpeg
as
FFMpegClient
;
use
FFMpeg\FFProbe
as
FFProbeClient
;
use
FFMpeg\Filters\Video\ResizeFilter
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
FFMpegExecutorSpec
extends
ObjectBehavior
{
private
$ffmpeg
;
private
$ffprobe
;
private
$transcodeStorage
;
public
function
let
(
FFMpegClient
$ffmpeg
,
FFProbeClient
$ffprobe
,
TranscodeStorageInterface
$transcodeStorage
)
{
$this
->
beConstructedWith
(
null
,
$ffmpeg
,
$ffprobe
,
$transcodeStorage
);
$this
->
ffmpeg
=
$ffmpeg
;
$this
->
ffprobe
=
$ffprobe
;
$this
->
transcodeStorage
=
$transcodeStorage
;
}
public
function
it_is_initializable
()
{
$this
->
shouldHaveType
(
FFMpegExecutor
::
class
);
}
public
function
it_should_transcode_thumbnails
(
Transcode
$transcode
,
\FFMpeg\Media\Video
$ffmpegVideo
,
\FFMpeg\FFProbe\DataMapping\Format
$ffprobeFormat
,
\FFMpeg\Media\Frame
$ffmpegFrame
)
{
$transcode
->
getGuid
()
->
shouldBeCalled
()
->
willReturn
(
'123'
);
$transcode
->
getProfile
()
->
shouldBeCalled
()
->
willReturn
(
new
TranscodeProfiles\Thumbnails
());
$this
->
transcodeStorage
->
downloadToTmp
(
Argument
::
type
(
Transcode
::
class
))
->
willReturn
(
'/tmp/fake-path-for-source'
);
$this
->
ffmpeg
->
open
(
'/tmp/fake-path-for-source'
)
->
shouldBeCalled
()
->
willReturn
(
$ffmpegVideo
);
$this
->
ffprobe
->
streams
(
'/tmp/fake-path-for-source'
)
->
shouldBeCalled
()
->
willReturn
(
$this
->
ffprobe
);
$this
->
ffprobe
->
format
(
'/tmp/fake-path-for-source'
)
->
shouldBeCalled
()
->
willReturn
(
$ffprobeFormat
);
$ffprobeFormat
->
get
(
'duration'
)
->
willReturn
(
120
);
$ffmpegVideo
->
frame
(
Argument
::
any
())
->
shouldBeCalled
()
->
willReturn
(
$ffmpegFrame
);
$ffmpegFrame
->
save
(
Argument
::
any
())
->
shouldBeCalled
();
// These are all the thumbnails thast should have been called
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-thumbnails/thumbnail-00000.png'
)
->
shouldBeCalled
();
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-thumbnails/thumbnail-00001.png'
)
->
shouldBeCalled
();
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-thumbnails/thumbnail-00060.png'
)
->
shouldBeCalled
();
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-thumbnails/thumbnail-00119.png'
)
->
shouldBeCalled
();
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-thumbnails/thumbnail-00120.png'
)
->
shouldBeCalled
();
$transcode
->
setProgress
(
100
)
->
shouldBeCalled
();
$transcode
->
setStatus
(
'completed'
)
->
shouldBeCalled
();
$wrapped
=
$transcode
->
getWrappedObject
();
$this
->
getWrappedObject
()
->
transcode
(
$wrapped
,
function
(
$progress
)
{
});
}
public
function
it_should_transcode_video
(
Transcode
$transcode
,
\FFMpeg\Media\Video
$ffmpegVideo
,
\FFMpeg\FFProbe\DataMapping\Format
$ffprobeFormat
,
\FFMpeg\Filters\Video\VideoFilters
$ffmpegVideoFilters
)
{
$transcode
->
getGuid
()
->
shouldBeCalled
()
->
willReturn
(
'123'
);
$transcode
->
getProfile
()
->
shouldBeCalled
()
->
willReturn
(
new
TranscodeProfiles\X264_360p
());
$this
->
transcodeStorage
->
downloadToTmp
(
Argument
::
type
(
Transcode
::
class
))
->
willReturn
(
'/tmp/fake-path-for-source'
);
$this
->
ffmpeg
->
open
(
'/tmp/fake-path-for-source'
)
->
shouldBeCalled
()
->
willReturn
(
$ffmpegVideo
);
$this
->
ffprobe
->
streams
(
'/tmp/fake-path-for-source'
)
->
shouldBeCalled
()
->
willReturn
(
$this
->
ffprobe
);
$ffmpegVideo
->
filters
()
->
shouldBeCalled
()
->
willReturn
(
$ffmpegVideoFilters
);
$ffmpegVideoFilters
->
resize
(
Argument
::
any
(),
ResizeFilter
::
RESIZEMODE_SCALE_WIDTH
)
->
shouldBeCalled
()
->
willReturn
(
$ffmpegVideoFilters
);
$ffmpegVideoFilters
->
synchronize
()
->
shouldBeCalled
();
$ffmpegVideo
->
save
(
Argument
::
that
(
function
(
$format
)
{
return
$format
->
getKiloBitRate
()
===
500
&&
$format
->
getAudioKiloBitrate
()
===
80
;
}),
'/tmp/fake-path-for-source-360.mp4'
)
->
shouldBeCalled
();
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-360.mp4'
)
->
shouldBeCalled
();
$transcode
->
setStatus
(
'completed'
)
->
shouldBeCalled
();
$wrapped
=
$transcode
->
getWrappedObject
();
$this
->
getWrappedObject
()
->
transcode
(
$wrapped
,
function
(
$progress
)
{
});
}
public
function
it_should_transcode_video_but_register_failure
(
Transcode
$transcode
,
\FFMpeg\Media\Video
$ffmpegVideo
,
\FFMpeg\FFProbe\DataMapping\Format
$ffprobeFormat
,
\FFMpeg\Filters\Video\VideoFilters
$ffmpegVideoFilters
)
{
$transcode
->
getGuid
()
->
shouldBeCalled
()
->
willReturn
(
'123'
);
$transcode
->
getProfile
()
->
shouldBeCalled
()
->
willReturn
(
new
TranscodeProfiles\X264_360p
());
$this
->
transcodeStorage
->
downloadToTmp
(
Argument
::
type
(
Transcode
::
class
))
->
willReturn
(
'/tmp/fake-path-for-source'
);
$this
->
ffmpeg
->
open
(
'/tmp/fake-path-for-source'
)
->
shouldBeCalled
()
->
willReturn
(
$ffmpegVideo
);
$this
->
ffprobe
->
streams
(
'/tmp/fake-path-for-source'
)
->
shouldBeCalled
()
->
willReturn
(
$this
->
ffprobe
);
$ffmpegVideo
->
filters
()
->
shouldBeCalled
()
->
willReturn
(
$ffmpegVideoFilters
);
$ffmpegVideoFilters
->
resize
(
Argument
::
any
(),
ResizeFilter
::
RESIZEMODE_SCALE_WIDTH
)
->
shouldBeCalled
()
->
willReturn
(
$ffmpegVideoFilters
);
$ffmpegVideoFilters
->
synchronize
()
->
shouldBeCalled
();
$ffmpegVideo
->
save
(
Argument
::
that
(
function
(
$format
)
{
return
$format
->
getKiloBitRate
()
===
500
&&
$format
->
getAudioKiloBitrate
()
===
80
;
}),
'/tmp/fake-path-for-source-360.mp4'
)
->
shouldBeCalled
()
->
willThrow
(
new
\FFMpeg\Exception\RuntimeException
());
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-360.mp4'
)
->
shouldNotBeCalled
();
$transcode
->
setStatus
(
'failed'
)
->
shouldBeCalled
();
$wrapped
=
$transcode
->
getWrappedObject
();
try
{
$this
->
getWrappedObject
()
->
transcode
(
$wrapped
,
function
(
$progress
)
{
});
throw
new
\Exception
(
"An exception should have been thrown doe failed transcode"
);
}
catch
(
FailedTranscodeException
$e
)
{
// We throw a new exception above if the one we are expecting isn't called
}
}
}
This diff is collapsed.
Spec/Core/Media/Video/Transcoder/TranscodeStatesSpec.php
0 → 100644
View file @
465c2658
<?php
namespace
Spec\Minds\Core\Media\Video\Transcoder
;
use
Minds\Core\Media\Video\Transcoder\TranscodeStates
;
use
Minds\Core\Media\Video\Transcoder\Repository
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
use
Minds\Entities\Video
;
use
Minds\Common\Repository\Response
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
TranscodeStatesSpec
extends
ObjectBehavior
{
private
$repository
;
public
function
let
(
Repository
$repository
)
{
$this
->
beConstructedWith
(
$repository
);
$this
->
repository
=
$repository
;
}
public
function
it_is_initializable
()
{
$this
->
shouldHaveType
(
TranscodeStates
::
class
);
}
public
function
it_should_return_transcoding_status
()
{
$video
=
new
Video
();
$video
->
set
(
'guid'
,
'123'
);
$this
->
repository
->
getList
([
'guid'
=>
'123'
])
->
shouldBeCalled
()
->
willReturn
(
new
Response
([
(
new
Transcode
())
->
setProfile
(
new
TranscodeProfiles\X264_360p
())
->
setStatus
(
'transcoding'
)
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
)),
(
new
Transcode
())
->
setProfile
(
new
TranscodeProfiles\X264_720p
())
->
setStatus
(
'completed'
)
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
))
]));
$this
->
getStatus
(
$video
)
->
shouldReturn
(
'transcoding'
);
}
public
function
it_should_declare_a_timeout
()
{
$video
=
new
Video
();
$video
->
set
(
'guid'
,
'123'
);
$this
->
repository
->
getList
([
'guid'
=>
'123'
])
->
shouldBeCalled
()
->
willReturn
(
new
Response
([
(
new
Transcode
())
->
setProfile
(
new
TranscodeProfiles\X264_360p
())
->
setStatus
(
'transcoding'
)
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
)
-
700000
),
(
new
Transcode
())
->
setProfile
(
new
TranscodeProfiles\X264_720p
())
->
setStatus
(
'transcoding'
)
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
)
-
700000
)
]));
$this
->
getStatus
(
$video
)
->
shouldReturn
(
'failed'
);
}
public
function
it_should_return_completed_status
()
{
$video
=
new
Video
();
$video
->
set
(
'guid'
,
'123'
);
$this
->
repository
->
getList
([
'guid'
=>
'123'
])
->
shouldBeCalled
()
->
willReturn
(
new
Response
([
(
new
Transcode
())
->
setProfile
(
new
TranscodeProfiles\X264_360p
())
->
setStatus
(
'completed'
)
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
)),
(
new
Transcode
())
->
setProfile
(
new
TranscodeProfiles\X264_720p
())
->
setStatus
(
'completed'
)
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
))
]));
$this
->
getStatus
(
$video
)
->
shouldReturn
(
'completed'
);
}
public
function
it_should_return_failed_status
()
{
$video
=
new
Video
();
$video
->
set
(
'guid'
,
'123'
);
$this
->
repository
->
getList
([
'guid'
=>
'123'
])
->
shouldBeCalled
()
->
willReturn
(
new
Response
([
(
new
Transcode
())
->
setProfile
(
new
TranscodeProfiles\X264_360p
())
->
setStatus
(
'failed'
)
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
)),
(
new
Transcode
())
->
setProfile
(
new
TranscodeProfiles\X264_720p
())
->
setStatus
(
'failed'
)
->
setLastEventTimestampMs
(
round
(
microtime
(
true
)
*
1000
))
]));
$this
->
getStatus
(
$video
)
->
shouldReturn
(
'failed'
);
}
}
This diff is collapsed.
Spec/Core/Media/Video/Transcoder/TranscodeStorage/S3StorageSpec.php
0 → 100644
View file @
465c2658
<?php
namespace
Spec\Minds\Core\Media\Video\Transcoder\TranscodeStorage
;
use
Minds\Core\Media\Video\Transcoder\TranscodeStorage\S3Storage
;
use
Minds\Core\Media\Video\Transcoder\Transcode
;
use
Minds\Core\Media\Video\Transcoder\TranscodeProfiles
;
use
Psr\Http\Message\RequestInterface
;
use
Aws\S3\S3Client
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
S3StorageSpec
extends
ObjectBehavior
{
private
$s3
;
public
function
let
(
S3Client
$s3
)
{
$this
->
beConstructedWith
(
null
,
$s3
);
$this
->
s3
=
$s3
;
}
public
function
it_is_initializable
()
{
$this
->
shouldHaveType
(
S3Storage
::
class
);
}
public
function
it_should_upload_file
(
Transcode
$transcode
)
{
$transcode
->
getGuid
()
->
willReturn
(
123
);
$transcode
->
getProfile
()
->
willReturn
(
new
TranscodeProfiles\X264_360p
());
$this
->
s3
->
putObject
(
Argument
::
that
(
function
(
$args
)
{
return
true
;
}))
->
shouldBeCalled
()
->
willReturn
(
true
);
$this
->
add
(
$transcode
,
tempnam
(
sys_get_temp_dir
(),
'my-fake-path'
));
}
public
function
it_should_return_a_signed_url_for_client_side_uploads
(
Transcode
$transcode
,
\Aws\CommandInterface
$cmd
,
RequestInterface
$request
)
{
$transcode
->
getGuid
()
->
willReturn
(
123
);
$transcode
->
getProfile
()
->
willReturn
(
new
TranscodeProfiles\Source
());
$this
->
s3
->
getCommand
(
'PutObject'
,
[
'Bucket'
=>
'cinemr'
,
'Key'
=>
"/123/source"
,
])
->
shouldBeCalled
()
->
willReturn
(
$cmd
);
$this
->
s3
->
createPresignedRequest
(
Argument
::
any
(),
Argument
::
any
())
->
willReturn
(
$request
);
$request
->
getUri
()
->
willReturn
(
'aws-signed-url'
);
$this
->
getClientSideUploadUrl
(
$transcode
)
->
shouldReturn
(
'aws-signed-url'
);
}
public
function
it_should_download_file
(
Transcode
$transcode
)
{
$transcode
->
getGuid
()
->
willReturn
(
123
);
$transcode
->
getProfile
()
->
willReturn
(
new
TranscodeProfiles\Source
());
$this
->
downloadToTmp
(
$transcode
)
->
shouldContain
(
"123-source"
);
}
}
This diff is collapsed.
Spec/bootstrap.php
View file @
465c2658
...
...
@@ -114,6 +114,11 @@ class Mock
return
(
int
)
$this
->
a
;
}
public
function
microtime
()
{
return
(
int
)
$this
->
a
;
}
public
function
toInt
()
{
return
(
int
)
$this
->
a
;
...
...
This diff is collapsed.
Mark Harding
@markeharding
mentioned in merge request
!414 (closed)
·
35 minutes ago
mentioned in merge request
!414 (closed)
mentioned in merge request !414
Toggle commit list
Please
register
or
sign in
to comment