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
294
Merge Requests
40
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
ecd407f5
Commit
ecd407f5
authored
3 hours ago
by
Mark Harding
Browse files
Options
Download
(feat): Initial workings of the new transcoder
parent
1359daf0
No related merge requests found
Pipeline
#100596489
failed with stages
in 2 minutes and 55 seconds
Changes
32
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
1830 additions
and
10 deletions
+1830
-10
Core/Media/Services/FFMpeg.php
View file @
ecd407f5
<?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/Transcoder/Delegates/QueueDelegate.php
0 → 100644
View file @
ecd407f5
<?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\SQS'
);
}
/**
* 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 @
ecd407f5
<?php
/**
* Transcoder manager
*/
namespace
Minds\Core\Media\Video\Transcoder
;
use
Minds\Core\Media\Video\Transcoder\Delegates\QueueDelegate
;
use
Minds\Entities\Video
;
use
Minds\Traits\MagicAttributes
;
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 Repository */
private
$repository
;
/** @var QueueDelegate */
private
$queueDelegate
;
/** @var TranscodeStorage\TranscodeStorageInterface */
private
$transcodeStorage
;
/** @var TranscodeExecutors\TranscodeExecutorInterfsce */
private
$transcodeExecutor
;
public
function
__construct
(
$repository
=
null
,
$queueDelegate
=
null
,
$transcodeStorage
=
null
,
$transcodeExecutor
=
null
)
{
$this
->
repository
=
$repository
??
new
Repository
();
$this
->
queueDelegate
=
$queueDelegate
??
new
QueueDelegate
();
$this
->
transcodeStorage
=
$transcodeStorage
??
new
TranscodeStorage\S3Storage
;
$this
->
transcodeExecutor
=
$transcodeExecutor
??
new
TranscodeExecutors\FFMpegExecutor
;
}
/**
* Return a list of transcodes
* @return Response
*/
public
function
getList
(
$opts
)
:
array
{
$opts
=
array_merge
([
'guid'
=>
null
,
'profileId'
=>
null
,
'status'
=>
null
,
],
$opts
);
return
$this
->
repository
->
getList
(
$opts
);
}
/**
* 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
);
}
/**
* 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
);
// 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)
* @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
->
setProgress
(
$pct
);
$this
->
update
(
$transcode
,
'progress'
);
});
if
(
!
$success
)
{
// This is actually unkown as an exception should have been thrown
throw
new
TranscodeExecutors\FailedTranscodeException
();
}
$transcode
->
setStatus
(
'completed'
);
}
catch
(
TranscodeExecutors\FailedTranscodeException
$e
)
{
$transcode
->
setStatus
(
'failed'
);
}
finally
{
$this
->
update
(
$transcode
,
[
'progress'
,
'status'
]);
}
// Was this the last transcode to complete?
// if ($this->isLastToTrancode($transcode)) {
// // Sent a notification to the user saying the transcode is completed
// }
}
// protected function isLastToTrancode(Transcode $transcode): bool
// {
// }
}
This diff is collapsed.
Core/Media/Video/Transcoder/Repository.php
0 → 100644
View file @
ecd407f5
<?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 * 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 * 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) VALUES (?, ?)"
;
$values
=
[
new
Bigint
(
$transcode
->
getGuid
()),
$transcode
->
getProfile
()
->
getId
(),
];
$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
= ?"
;
},
$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'
]
->
value
())
->
setLastEventTimestampMs
(
round
(
$row
[
'last_event_timestamp_ms'
]
->
microtime
(
true
)
*
1000
))
->
setLength
(
$row
[
'length_secs'
]
->
value
())
->
setBytes
(
$row
[
'bytes'
]
->
value
());
return
$transcode
;
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/Transcode.php
0 → 100644
View file @
ecd407f5
<?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
->
getOwnerEntity
()
->
isPro
())
{
throw
new
TranscodeProfiles\UnavailableTranscodeProfileException
();
}
$this
->
profile
=
$profile
;
return
$this
;
}
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 @
ecd407f5
<?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
->
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
)
{
// $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
->
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 @
ecd407f5
<?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 @
ecd407f5
<?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 @
ecd407f5
<?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
(
'\\'
,
__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 @
ecd407f5
<?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 @
ecd407f5
<?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 @
ecd407f5
<?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 @
ecd407f5
<?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 @
ecd407f5
<?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 @
ecd407f5
<?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 @
ecd407f5
<?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
=
'1080p.webm'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/Webm_360p.php
0 → 100644
View file @
ecd407f5
<?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
=
'360p.webm'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/Webm_720p.php
0 → 100644
View file @
ecd407f5
<?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
=
'720p.webm'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/X264_1080p.php
0 → 100644
View file @
ecd407f5
<?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
=
'1080p.mp4'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/X264_360p.php
0 → 100644
View file @
ecd407f5
<?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
=
'360p.mp4'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeProfiles/X264_720p.php
0 → 100644
View file @
ecd407f5
<?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
=
'720p.mp4'
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeStorage/S3Storage.php
0 → 100644
View file @
ecd407f5
<?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'
),
]);
}
/**
* @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
;
}
}
This diff is collapsed.
Core/Media/Video/Transcoder/TranscodeStorage/TranscodeStorageInterface.php
0 → 100644
View file @
ecd407f5
<?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
;
/**
* @param Transcode $transcode
* @return string
*/
public
function
downloadToTmp
(
Transcode
$transcode
)
:
string
;
}
This diff is collapsed.
Core/Media/Video/Transcoder/Transcodes.php
0 → 100644
View file @
ecd407f5
<?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/Queue/Runners/Transcode.php
View file @
ecd407f5
...
...
@@ -14,11 +14,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
();
$transcoderManeger
=
Di
::
_
()
->
get
(
'Media\Video\Transcoder\Manager'
);
$transcoderManager
->
transcode
(
$transcode
);
},
[
'max_messages'
=>
1
]);
}
}
This diff is collapsed.
Entities/Video.php
View file @
ecd407f5
...
...
@@ -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/Video/Transcoder/Delegates/QueueDelegateSpec.php
0 → 100644
View file @
ecd407f5
<?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 @
ecd407f5
<?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\Entities\Video
;
use
Minds\Entities\User
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
ManagerSpec
extends
ObjectBehavior
{
private
$repository
;
private
$queueDelegate
;
private
$transcodeStorage
;
private
$transcodeExecutor
;
public
function
let
(
Repository
$repository
,
QueueDelegate
$queueDelegate
,
TranscodeStorageInterface
$transcodeStorage
,
TranscodeExecutorInterface
$transcodeExecutor
)
{
$this
->
beConstructedWith
(
$repository
,
$queueDelegate
,
$transcodeStorage
,
$transcodeExecutor
);
$this
->
repository
=
$repository
;
$this
->
queueDelegate
=
$queueDelegate
;
$this
->
transcodeStorage
=
$transcodeStorage
;
$this
->
transcodeExecutor
=
$transcodeExecutor
;
}
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_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
();
$this
->
repository
->
update
(
Argument
::
that
(
function
(
$transcode
)
{
//return true;
return
$transcode
->
getStatus
()
===
'transcoding'
;
}),
[
'status'
])
->
shouldBeCalled
();
$this
->
transcodeExecutor
->
transcode
(
$transcode
,
Argument
::
any
())
->
shouldBeCalled
()
->
willReturn
(
true
);
// $this->repository->update(Argument::type(Transcode::class), [ 'progress' ])
// ->shouldBeCalled();
$this
->
repository
->
update
(
Argument
::
that
(
function
(
$transcode
)
{
return
$transcode
->
getStatus
()
===
'completed'
;
}),
[
'progress'
,
'status'
])
->
shouldBeCalled
();
$this
->
transcode
(
$transcode
);
}
}
This diff is collapsed.
Spec/Core/Media/Video/Transcoder/RepositorySpec.php
0 → 100644
View file @
ecd407f5
<?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
());
$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
),
'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
),
'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
),
'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
),
7
))
->
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 @
ecd407f5
<?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 = new Transcode();
$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
->
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 = new Transcode();
$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-360p.mp4'
)
->
shouldBeCalled
();
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-360p.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 = new Transcode();
$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-360p.mp4'
)
->
shouldBeCalled
()
->
willThrow
(
new
\FFMpeg\Exception\RuntimeException
());
$this
->
transcodeStorage
->
add
(
$transcode
,
'/tmp/fake-path-for-source-360p.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/TranscodeStorage/S3StorageSpec.php
0 → 100644
View file @
ecd407f5
<?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
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_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 @
ecd407f5
...
...
@@ -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.
Please
register
or
sign in
to comment