...
 
Commits (18)
......@@ -18,11 +18,12 @@ class Transcode extends Cli\Controller implements Interfaces\CliControllerInterf
{
$this->out('TBD');
}
public function exec()
{
$transcoder = new Core\Media\Services\FFMpeg;
$transcoder->setKey($this->getOpt('guid'));
$transcoder->transcode();
$transcoder->setFullHD($this->getOpt('full_hd') ?? false);
$transcoder->onQueue();
}
}
......@@ -36,6 +36,13 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
return Factory::response(['status' => 'error']);
}
if (!in_array($this->getType($entity), ['object:video', 'object:image'], true)) {
return Factory::response([
'status' => 'error',
'message' => 'Entity is not a media entity',
]);
}
switch ($entity->subtype) {
case "video":
// Helpers\Counters::increment($pages[0], 'plays');
......@@ -43,10 +50,12 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
if (isset($pages[1]) && $pages[1] == 'play') {
http_response_code(302);
$res = !empty($_GET['res']) && in_array($_GET['res'], ['360', '720', '1080'], true) ?$_GET['res'] : '360';
if ($entity->subtype == 'audio') {
\forward($entity->getSourceUrl('128.mp3'));
} else {
\forward($entity->getSourceUrl('360.mp4'));
\forward($entity->getSourceUrl("{$res}.mp4"));
}
exit;
......@@ -58,8 +67,11 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
$response = $entities[0];
$response['transcodes'] = [
'360.mp4' => $entity->getSourceUrl('360.mp4'),
'720.mp4' => $entity->getSourceUrl('720.mp4')
'720.mp4' => $entity->getSourceUrl('720.mp4'),
];
if ($entity->getFlag('full_hd')) {
$response['transcodes']['1080.mp4'] = $entity->getSourceUrl('1080.mp4');
}
}
if (method_exists($entity, 'getWireThreshold')) {
......@@ -158,6 +170,7 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
$body = $req['body'];
fwrite($fp, $body);
$video->access_id = 0;
$video->patch(['full_hd', Core\Session::getLoggedinUser()->isPro()]);
$video->upload($tmpFilename);
$guid = $video->save();
fclose($fp);
......@@ -258,7 +271,8 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
'access_id' => 0,
'owner_guid' => $user->guid,
'hidden' => $container_guid !== null,
'container_guid' => $container_guid
'container_guid' => $container_guid,
'full_hd' => $user->isPro(),
]);
$assets = Core\Media\AssetsFactory::build($entity);
......@@ -267,7 +281,7 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
$entity->setAssets($assets->upload($media, $data));
// Save initial entity
$success = $save
->setEntity($entity)
->save(true);
......@@ -356,4 +370,9 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
return $response;
}
private function getType($entity): string
{
return $entity->subtype ? "{$entity->type}:{$entity->subtype}" : $entity->type;
}
}
......@@ -9,6 +9,7 @@ namespace Minds\Controllers\api\v1\minds;
use Minds;
use Minds\Core;
use Minds\Core\Rewards\Contributions\ContributionValues;
use Minds\Interfaces;
use Minds\Api\Factory;
......@@ -44,6 +45,7 @@ class config implements Interfaces\Api, Interfaces\ApiIgnorePam
'pro' => Minds\Core\Di\Di::_()->get('Config')->get('pro')['handler'] ?? null,
],
'upgrades' => Minds\Core\Di\Di::_()->get('Config')->get('upgrades'),
'contribution_values' => ContributionValues::export(),
];
return Factory::response($minds);
......
......@@ -358,7 +358,8 @@ class newsfeed implements Interfaces\Api
->setCustom('video', [
'thumbnail_src' => $embeded->getIconUrl(),
'guid' => $embeded->guid,
'mature' => $embeded instanceof Flaggable ? $embeded->getFlag('mature') : false
'mature' => $embeded instanceof Flaggable ? $embeded->getFlag('mature') : false,
'full_hd' => $embeded->getFlag('full_hd') ?? false,
])
->setTitle($embeded->title)
->setBlurb($embeded->description)
......@@ -525,7 +526,7 @@ class newsfeed implements Interfaces\Api
]);
}
}
$save->setEntity($activity)
->save();
......
......@@ -10,6 +10,8 @@ namespace Minds\Controllers\api\v2\media;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Media\ClientUpload\Manager;
use Minds\Core\Session;
use Minds\Interfaces;
use Minds\Core\Media\ClientUpload\ClientUploadLease;
......@@ -38,6 +40,7 @@ class upload implements Interfaces\Api
*/
public function put($pages)
{
/** @var Manager $manager */
$manager = Di::_()->get("Media\ClientUpload\Manager");
switch ($pages[0]) {
case 'prepare':
......@@ -55,7 +58,9 @@ class upload implements Interfaces\Api
$lease->setGuid($guid)
->setMediaType($mediaType);
$manager->complete($lease);
$manager
->setFullHD(Session::getLoggedinUser()->isPro())
->complete($lease);
break;
}
return Factory::response([]);
......
<?php
/**
* Engagement Dashboard
*/
namespace Minds\Core\Analytics\Dashboards;
use Minds\Entities\User;
use Minds\Traits\MagicAttributes;
/**
* @method EngagementDashboard setTimespanId(string $timespanId)
* @method EngagementDashboard setFilterIds(array $filtersIds)
* @method EngagementDashboard setUser(User $user)
*/
class EngagementDashboard implements DashboardInterface
{
use MagicAttributes;
/** @var string */
private $timespanId = '30d';
/** @var string[] */
private $filterIds = [ 'platform::browser' ];
/** @var string */
private $metricId = 'votes_up';
/** @var Timespans\TimespansCollection */
private $timespansCollection;
/** @var Metrics\MetricsCollection */
private $metricsCollection;
/** @var Filters\FiltersCollection */
private $filtersCollection;
/** @var User */
private $user;
public function __construct(
$timespansCollection = null,
$metricsCollection = null,
$filtersCollection = null
) {
$this->timespansCollection = $timespansCollection ?? new Timespans\TimespansCollection();
$this->metricsCollection = $metricsCollection ?? new Metrics\MetricsCollection();
$this->filtersCollection = $filtersCollection ?? new Filters\FiltersCollection();
}
/**
* Build the dashboard
* @return self
*/
public function build(): self
{
$this->timespansCollection
->setSelectedId($this->timespanId)
->addTimespans(
new Timespans\TodayTimespan(),
new Timespans\_30dTimespan(),
new Timespans\_1yTimespan(),
new Timespans\MtdTimespan(),
new Timespans\YtdTimespan()
);
$this->filtersCollection
->setSelectedIds($this->filterIds)
->setUser($this->user)
->addFilters(
new Filters\ChannelFilter()
);
$this->metricsCollection
->setTimespansCollection($this->timespansCollection)
->setFiltersCollection($this->filtersCollection)
->setSelectedId($this->metricId)
->setUser($this->user)
->addMetrics(
new Metrics\Engagement\VotesUpMetric(),
new Metrics\Engagement\CommentsMetric(),
new Metrics\Engagement\RemindsMetric(),
new Metrics\Engagement\SubscribersMetric()
)
->build();
return $this;
}
/**
* Export
* @param array $extras
* @return array
*/
public function export(array $extras = []): array
{
$this->build();
return [
'category' => 'engagement',
'label' => 'Engagement',
'description' => '',
'timespan' => $this->timespansCollection->getSelected()->getId(),
'timespans' => $this->timespansCollection->export(),
'metric' => $this->metricsCollection->getSelected()->getId(),
'metrics' => $this->metricsCollection->export(),
'filter' => $this->filtersCollection->getSelectedIds(),
'filters' => $this->filtersCollection->export(),
];
}
}
......@@ -7,6 +7,7 @@ class Manager
'traffic' => TrafficDashboard::class,
'trending' => TrendingDashboard::class,
'earnings' => EarningsDashboard::class,
'engagement' => EngagementDashboard::class,
];
/**
......
......@@ -39,7 +39,7 @@ class ActiveUsersMetric extends AbstractMetric
$timespan = $this->timespansCollection->getSelected();
$filters = $this->filtersCollection->getSelected();
$comparisonTsMs = strtotime("-{$timespan->getComparisonInterval()} days", $timespan->getFromTsMs() / 1000) * 1000;
$comparisonTsMs = strtotime("midnight -{$timespan->getComparisonInterval()} days", $timespan->getFromTsMs() / 1000) * 1000;
$currentTsMs = $timespan->getFromTsMs();
// Field name to use for the aggregation
......@@ -76,10 +76,14 @@ class ActiveUsersMetric extends AbstractMetric
],
];
$must[]['exists'] = [
'field' => 'active::total',
];
$must[]['range'] = [
'@timestamp' => [
'gte' => $tsMs,
'lte' => strtotime("midnight +{$timespan->getComparisonInterval()} days", $tsMs / 1000) * 1000,
'lt' => strtotime("midnight tomorrow +{$timespan->getComparisonInterval()} days", $tsMs / 1000) * 1000,
],
];
......@@ -145,6 +149,10 @@ class ActiveUsersMetric extends AbstractMetric
'entity_urn' => 'urn:metric:global'
];
$must[]['exists'] = [
'field' => 'active::total',
];
// Specify the resolution to avoid duplicates
$must[] = [
'term' => [
......
......@@ -54,7 +54,7 @@ abstract class AbstractEarningsMetric extends AbstractMetric
$must[]['range'] = [
'@timestamp' => [
'gte' => $tsMs,
'lte' => strtotime("midnight +{$timespan->getComparisonInterval()} days", $tsMs / 1000) * 1000,
'lt' => strtotime("midnight tomorrow +{$timespan->getComparisonInterval()} days", $tsMs / 1000) * 1000,
],
];
......@@ -66,6 +66,12 @@ abstract class AbstractEarningsMetric extends AbstractMetric
];
}
$must[] = [
'exists' => [
'field' => $this->aggField,
],
];
$query = [
'index' => 'minds-entitycentric-*',
'size' => 0,
......@@ -127,6 +133,12 @@ abstract class AbstractEarningsMetric extends AbstractMetric
];
}
$must[] = [
'exists' => [
'field' => $this->aggField,
],
];
// Do the query
$query = [
'index' => 'minds-entitycentric-*',
......
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics\Engagement;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Analytics\Dashboards\Metrics\AbstractMetric;
use Minds\Core\Analytics\Dashboards\Metrics\MetricSummary;
use Minds\Core\Analytics\Dashboards\Metrics\Visualisations;
abstract class AbstractEngagementMetric extends AbstractMetric
{
/** @var Elasticsearch\Client */
private $es;
/** @var string */
protected $id = '';
/** @var string */
protected $label = '';
/** @var string */
protected $description = '';
/** @var array */
protected $permissions = [ 'user', 'admin' ];
/** @var string */
protected $unit = 'number';
/** @var string */
protected $aggField = '';
public function __construct($es = null)
{
$this->es = $es ?? Di::_()->get('Database\ElasticSearch');
}
/**
* Build the metrics
* @return self
*/
public function buildSummary(): self
{
$timespan = $this->timespansCollection->getSelected();
$filters = $this->filtersCollection->getSelected();
$comparisonTsMs = strtotime("midnight -{$timespan->getComparisonInterval()} days", $timespan->getFromTsMs() / 1000) * 1000;
$currentTsMs = $timespan->getFromTsMs();
$values = [];
foreach ([ 'value' => $currentTsMs, 'comparison' => $comparisonTsMs ] as $key => $tsMs) {
$must = [];
$maxTs = strtotime("midnight tomorrow +{$timespan->getComparisonInterval()} days", $tsMs / 1000);
$must[]['range'] = [
'@timestamp' => [
'gte' => $tsMs,
'lt' => $maxTs * 1000,
],
];
if ($userGuid = $this->getUserGuid()) {
$must[] = [
'term' => [
'owner_guid' => $userGuid,
],
];
}
$must[] = [
'exists' => [
'field' => $this->aggField,
],
];
$indexes = implode(',', [
'minds-entitycentric-' . date('m-Y', $tsMs / 1000),
'minds-entitycentric-' . date('m-Y', $maxTs),
]);
$query = [
'index' => 'minds-entitycentric-*',
'size' => 0,
'body' => [
'query' => [
'bool' => [
'must' => $must,
],
],
'aggs' => [
'1' => [
'sum' => [
'field' => $this->aggField,
],
],
],
],
];
// Query elasticsearch
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$response = $this->es->request($prepared);
$values[$key] = $response['aggregations']['1']['value'];
}
$this->summary = new MetricSummary();
$this->summary
->setValue($values['value'])
->setComparisonValue($values['comparison'])
->setComparisonInterval($timespan->getComparisonInterval())
->setComparisonPositivity(true);
return $this;
}
/**
* Build a visualisation for the metric
* @return self
*/
public function buildVisualisation(): self
{
$timespan = $this->timespansCollection->getSelected();
$filters = $this->filtersCollection->getSelected();
$must = [];
// Range must be from previous period
$must[]['range'] = [
'@timestamp' => [
'gte' => $timespan->getFromTsMs(),
],
];
if ($userGuid = $this->getUserGuid()) {
$must[] = [
'term' => [
'owner_guid' => $userGuid,
],
];
}
$must[] = [
'exists' => [
'field' => $this->aggField,
],
];
// Do the query
$query = [
'index' => 'minds-entitycentric-*',
'size' => 0,
'body' => [
'query' => [
'bool' => [
'must' => $must,
],
],
'aggs' => [
'1' => [
'date_histogram' => [
'field' => '@timestamp',
'interval' => $timespan->getInterval(),
'min_doc_count' => 0,
'extended_bounds' => [
'min' => $timespan->getFromTsMs(),
'max' => time() * 1000,
],
],
'aggs' => [
'2' => [
'sum' => [
'field' => $this->aggField,
],
],
],
],
],
],
];
// Query elasticsearch
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$response = $this->es->request($prepared);
$buckets = [];
foreach ($response['aggregations']['1']['buckets'] as $bucket) {
$date = date(Visualisations\ChartVisualisation::DATE_FORMAT, $bucket['key'] / 1000);
$buckets[] = [
'key' => $bucket['key'],
'date' => date('c', $bucket['key'] / 1000),
'value' => $bucket['2']['value']
];
}
$this->visualisation = (new Visualisations\ChartVisualisation())
->setXLabel('Date')
->setYLabel('Count')
->setBuckets($buckets);
return $this;
}
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics\Engagement;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Analytics\Dashboards\Metrics\AbstractMetric;
use Minds\Core\Analytics\Dashboards\Metrics\MetricSummary;
use Minds\Core\Analytics\Dashboards\Metrics\Visualisations;
class CommentsMetric extends AbstractEngagementMetric
{
/** @var string */
protected $id = 'comments';
/** @var string */
protected $label = 'Comments';
/** @var string */
protected $description = "Number of comments you have received on your content";
/** @var array */
protected $permissions = [ 'user', 'admin' ];
/** @var string */
protected $aggField = 'comment::total';
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics\Engagement;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Analytics\Dashboards\Metrics\AbstractMetric;
use Minds\Core\Analytics\Dashboards\Metrics\MetricSummary;
use Minds\Core\Analytics\Dashboards\Metrics\Visualisations;
class RemindsMetric extends AbstractEngagementMetric
{
/** @var string */
protected $id = 'reminds';
/** @var string */
protected $label = 'Reminds';
/** @var string */
protected $description = "Number of reminds you have received on your content";
/** @var array */
protected $permissions = [ 'user', 'admin' ];
/** @var string */
protected $aggField = 'remind::total';
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics\Engagement;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Analytics\Dashboards\Metrics\AbstractMetric;
use Minds\Core\Analytics\Dashboards\Metrics\MetricSummary;
use Minds\Core\Analytics\Dashboards\Metrics\Visualisations;
class SubscribersMetric extends AbstractEngagementMetric
{
/** @var string */
protected $id = 'subscribers';
/** @var string */
protected $label = 'Subscribes';
/** @var string */
protected $description = "Number of subscribers your channel has gained";
/** @var array */
protected $permissions = [ 'user', 'admin' ];
/** @var string */
protected $aggField = 'subscribe::total';
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics\Engagement;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Analytics\Dashboards\Metrics\AbstractMetric;
use Minds\Core\Analytics\Dashboards\Metrics\MetricSummary;
use Minds\Core\Analytics\Dashboards\Metrics\Visualisations;
class VotesUpMetric extends AbstractEngagementMetric
{
/** @var string */
protected $id = 'votes_up';
/** @var string */
protected $label = 'Votes up';
/** @var string */
protected $description = "Number of votes up you have received on your content";
/** @var array */
protected $permissions = [ 'user', 'admin' ];
/** @var string */
protected $aggField = 'vote:up::total';
}
......@@ -54,7 +54,7 @@ class SignupsMetric extends AbstractMetric
$must[]['range'] = [
'@timestamp' => [
'gte' => $tsMs,
'lte' => strtotime("midnight +{$timespan->getComparisonInterval()} days", $tsMs / 1000) * 1000,
'lt' => strtotime("midnight tomorrow +{$timespan->getComparisonInterval()} days", $tsMs / 1000) * 1000,
],
];
......
......@@ -151,7 +151,7 @@ class ViewsTableMetric extends AbstractMetric
->setColumns([
[
'id' => 'entity',
'label' => '',
'label' => 'Content',
'order' => 0,
],
[
......
......@@ -16,7 +16,7 @@ class MtdTimespan extends AbstractTimespan
protected $fromTsMs;
/** @var int */
protected $comparisonInterval = 28;
protected $comparisonInterval = 30;
public function __construct()
{
......
......@@ -16,7 +16,7 @@ class _30dTimespan extends AbstractTimespan
protected $fromTsMs;
/** @var int */
protected $comparisonInterval = 28;
protected $comparisonInterval = 30;
public function __construct()
{
......
<?php
namespace Minds\Core\Analytics\EntityCentric;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Di\Di;
use DateTime;
use Exception;
class EngagementSynchroniser
{
/** @var array */
private $records = [];
/** @var ElasticSearch\Client */
private $es;
public function __construct($es = null)
{
$this->es = $es ?? Di::_()->get('Database\ElasticSearch');
}
/**
* @param int $from
* @return self
*/
public function setFrom($from): self
{
$this->from = $from;
return $this;
}
/**
* Convert to records
* @return iterable
*/
public function toRecords(): iterable
{
$date = (new DateTime())->setTimestamp($this->from);
$now = new DateTime();
$days = (int) $date->diff($now)->format('%a');
$months = round($days / 28);
$i = 0;
foreach ($this->getEntitiesMetrics() as $buckets) {
$urn = null;
$ownerGuid = null;
if (!$buckets['type']['buckets'][0]['key'] && $buckets['metrics']['buckets'][0]['key'] === 'subscribe') {
$urn = "urn:user:{$buckets['key']}";
$ownerGuid = (string) $buckets['key'];
} elseif (!$buckets['type']['buckets'][0]['key']) {
echo "\nEngagement: skipping as no type";
continue;
} else {
$urn = "urn:{$buckets['type']['buckets'][0]['key']}:{$buckets['key']}";
$ownerGuid = (string) $buckets['owner']['buckets'][0]['key'];
if ($buckets['type']['buckets'][0]['key'] === 'object') {
$urn = "urn:{$buckets['subtype']['buckets'][0]['key']}:{$buckets['key']}";
}
}
$record = new EntityCentricRecord();
$record->setEntityUrn($urn)
->setOwnerGuid($ownerGuid)
->setTimestamp($this->from)
->setResolution('day');
foreach ($buckets['metrics']['buckets'] as $metrics) {
$record->incrementSum($metrics['key'] . '::total', (int) $metrics['doc_count']);
}
$this->records[] = $record;
++$i;
error_log("Engagement: $i");
}
foreach ($this->records as $record) {
yield $record;
}
}
private function getEntitiesMetrics()
{
$opts = array_merge([
'fields' => [],
'from' => time(),
], []);
$must = [];
// $must[] = [
// 'term' => [
// 'action.keyword' => 'subscribe',
// ],
//];
$must[] = [
'range' => [
'@timestamp' => [
'gte' => $this->from * 1000,
'lt' => strtotime('+1 day', $this->from) * 1000,
],
],
];
$partition = 0;
$partitions = 50;
$partitionSize = 5000; // Allows for 250,000 entities
$index = 'minds-metrics-' . date('m-Y', $this->from);
while (++$partition < $partitions) {
// Do the query
$query = [
'index' => $index,
'size' => 0,
'body' => [
'query' => [
'bool' => [
'must' => $must,
],
],
'aggs' => [
'1' => [
'terms' => [
'field' => 'entity_guid.keyword',
'min_doc_count' => 1,
'size' => $partitionSize,
'include' => [
'partition' => $partition,
'num_partitions' => $partitions,
],
],
'aggs' => [
'metrics' => [
'terms' => [
'field' => 'action.keyword',
'min_doc_count' => 1,
],
],
'owner' => [
'terms' => [
'field' => 'entity_owner_guid.keyword',
'min_doc_count' => 1,
],
],
'type' => [
'terms' => [
'field' => 'entity_type.keyword',
],
],
'subtype' => [
'terms' => [
'field' => 'entity_subtype.keyword',
]
],
],
],
],
],
];
// Query elasticsearch
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$response = $this->es->request($prepared);
foreach ($response['aggregations']['1']['buckets'] as $bucket) {
yield $bucket;
}
}
}
}
......@@ -13,6 +13,7 @@ class Manager
{
/** @var array */
const SYNCHRONISERS = [
EngagementSynchroniser::class,
PartnerEarningsSynchroniser::class,
SignupsSynchroniser::class,
ActiveUsersSynchroniser::class,
......
......@@ -5,6 +5,7 @@ namespace Minds\Core\Blogs;
use Minds\Core\Di\Di;
use Minds\Core\Events\Event;
use Minds\Core\Events\EventsDispatcher;
use Minds\Core\Session;
class Events
{
......@@ -37,5 +38,29 @@ class Events
$manager = Di::_()->get('Blogs\Manager');
$event->setResponse($manager->update($blog));
});
$this->eventsDispatcher->register('export:extender', 'blog', function (Event $event) {
$params = $event->getParameters();
/** @var Core\Blogs\Blog $blog */
$blog = $params['entity'];
$export = $event->response() ?: [];
$currentUser = Session::getLoggedInUserGuid();
$dirty = false;
if ($blog->isPaywall() && $blog->owner_guid != $currentUser) {
$export['description'] = '';
$export['body'] = '';
$dirty = true;
}
if ($dirty) {
return $event->setResponse($export);
}
if (!$currentUser) {
return;
}
});
}
}
......@@ -86,7 +86,7 @@ class Repository
$opts['parent_guid_l1'] = $parent_guids[0] ?? 0;
$opts['parent_guid_l2'] = $parent_guids[1] ?? 0;
$opts['parent_guid_l3'] = 0; //do not support l3 yet
$cql = "SELECT * from comments";
$values = [];
$cqlOpts = [];
......@@ -94,16 +94,25 @@ class Repository
$where = [];
if ($opts['entity_guid']) {
if (!is_numeric($opts['entity_guid'])) {
return new Response();
}
$where[] = 'entity_guid = ?';
$values[] = new Varint($opts['entity_guid']);
}
if ($opts['parent_guid_l1'] !== null) {
if (!is_numeric($opts['parent_guid_l1'])) {
return new Response();
}
$where[] = 'parent_guid_l1 = ?';
$values[] = new Varint((int) $opts['parent_guid_l1']);
}
if ($opts['parent_guid_l2'] !== null) {
if (!is_numeric($opts['parent_guid_l2'])) {
return new Response();
}
$where[] = 'parent_guid_l2 = ?';
$values[] = new Varint((int) $opts['parent_guid_l2']);
}
......
......@@ -12,15 +12,29 @@ use Minds\Entities\Video;
class Manager
{
/** @var FFMepg */
/** @var FFMpeg */
private $ffmpeg;
/** @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,
GuidBuilder $guid = null,
......@@ -70,12 +84,16 @@ 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();
......
......@@ -127,13 +127,38 @@ class Resize
$params = $this->getResizeParameters();
// First crop the image
$this->image->cropImage($params['selectionwidth'], $params['selectionheight'], $params['xoffset'],
$params['yoffset']);
// If selected with / height differ from selection width/height, then we need to resize
if ($params['selectionwidth'] !== $params['newwidth'] || $params['selectionheight'] !== $params['newheight']) {
$this->image->thumbnailImage($params['newwidth'], $params['newheight']);
// If is animated,
if ($this->image->getNumberImages() > 1) {
foreach ($this->image as $frame) {
// Crop into square.
$frame->cropImage(
$params['selectionwidth'],
$params['selectionheight'],
$params['xoffset'],
$params['yoffset']
);
// Resize canvas to new image
$frame->setImagePage(0, 0, 0, 0);
// If selected with / height differ from selection width/height, then we need to resize
if ($params['selectionwidth'] !== $params['newwidth'] || $params['selectionheight'] !== $params['newheight']) {
$frame->thumbnailImage($params['newwidth'], $params['newheight']);
}
}
} else {
// Crop the image to selection dimensions
$this->image->cropImage(
$params['selectionwidth'],
$params['selectionheight'],
$params['xoffset'],
$params['yoffset']
);
// If selected with / height differ from selection width/height, then we need to resize
if ($params['selectionwidth'] !== $params['newwidth'] || $params['selectionheight'] !== $params['newheight']) {
$this->image->thumbnailImage($params['newwidth'], $params['newheight']);
}
}
$this->output = $this->image;
......
......@@ -38,6 +38,9 @@ class FFMpeg implements ServiceInterface
/** @var string $dir */
private $dir = 'cinemr_data';
/** @var bool $full_hd */
private $full_hd = false;
public function __construct(
$queue = null,
$ffmpeg = null,
......@@ -71,6 +74,10 @@ class FFMpeg implements ServiceInterface
$this->dir = $this->config->get('transcoder')['dir'];
}
/**
* @param $key
* @return FFMpeg
*/
public function setKey($key)
{
$this->key = $key;
......@@ -78,6 +85,16 @@ class FFMpeg implements ServiceInterface
return $this;
}
/**
* @param bool $value
* @return FFMpeg
*/
public function setFullHD(bool $value)
{
$this->full_hd = $value;
return $this;
}
/**
* Create a PresignedUr for client based uploads
* @return string
......@@ -136,6 +153,7 @@ class FFMpeg implements ServiceInterface
->setQueue('Transcode')
->send([
'key' => $this->key,
'full_hd' => $this->full_hd,
]);
return $this;
......@@ -207,6 +225,10 @@ class FFMpeg implements ServiceInterface
'formats' => ['mp4', 'webm'],
], $opts);
if ($opts['pro'] && !$this->full_hd) {
continue;
}
if ($rotated) {
$ratio = $videostream->get('width') / $videostream->get('height');
$width = round($opts['height'] * $ratio);
......
<?php
namespace Minds\Core\Media;
use Minds\Core;
......@@ -23,8 +24,13 @@ class Thumbnails
$loggedInUser = Core\Session::getLoggedinUser();
if (!Di::_()->get('Wire\Thresholds')->isAllowed($loggedInUser, $entity)) {
return false;
try {
if (!Di::_()->get('Wire\Thresholds')->isAllowed($loggedInUser, $entity)) {
return false;
}
} catch (\Exception $e) {
error_log('[Core/Media/Thumbnails::get] ' . $e->getMessage());
// don't do anything if the entity cannot be paywalled
}
$user = $entity->getOwnerEntity(false);
......
......@@ -13,9 +13,11 @@ class Transcode implements Interfaces\QueueRunner
$client = Core\Queue\Client::Build();
$client->setQueue("Transcode")
->receive(function ($data) {
$d = $data->getData();
echo "Received a transcode request \n";
$transcoder = new Core\Media\Services\FFMpeg();
$transcoder->setKey($data->getData()['key']);
$transcoder->setKey($d['key']);
$transcoder->setFullHD($d['full_hd']);
$transcoder->onQueue();
}, [ 'max_messages' => 1 ]);
}
......
......@@ -12,5 +12,15 @@ class ContributionValues
'referrals_welcome' => 50,
'checkin' => 2,
'jury_duty' => 25,
'onchain_tx' => 10,
];
/**
* Public export of values
* @return array
*/
public static function export()
{
return static::$multipliers;
}
}
......@@ -63,16 +63,7 @@ class Defaults
Manager::add('/crypto', function ($slugs = []) {
return [
'title' => 'The Minds Token',
'description' => 'Coming soon',
'og:title' => 'The Minds Token',
'og:description' => 'Coming soon',
'og:url' => '/token',
'og:image' => $this->config->cdn_assets_url . 'assets/videos/space-1/space.jpg',
'og:image:width' => 2000,
'og:image:height' => 1000,
'twitter:site' => '@minds',
'twitter:card' => 'summary',
'og:url' => '/token', // Open graph and twitter redirection
];
});
......@@ -337,6 +328,23 @@ class Defaults
return $meta;
});
Manager::add('/analytics', function ($slugs = []) {
$meta = [
'title' => 'Analytics',
'description' => 'Track your traffic, earnings, engagement and trending analytics',
'og:title' => 'Analytics',
'og:description' => 'Track your traffic, earnings, engagement and trending analytics',
'og:url' => sprintf("%sanalytics/%s", $this->config->site_url, implode('/', $slugs)),
'og:image' => $this->config->cdn_assets_url . 'assets/photos/network.jpg',
'og:image:width' => 2000,
'og:image:height' => 1000,
'twitter:site' => '@minds',
'twitter:card' => 'summary',
];
return $meta;
});
Manager::add('/wallet/tokens/referrals', function ($slugs = []) {
$meta = [
'title' => 'Referrals',
......@@ -361,30 +369,18 @@ class Defaults
'image' => 'assets/photos/graph.jpg'
],
'wire' => [
'title' => 'Wire',
'description' => 'Exchange tokens with other channels on Minds',
'image' => 'assets/photos/blown-bulb.jpg'
'url' => 'pay'
],
'branding' => [
'title' => 'Branding',
'description' => 'Logos, assets and styling guides',
'image' => 'assets/logos/placeholder.jpg',
],
'boost' => [
'title' => 'Boost',
'description' => 'Boost your channel or content to gain more views and reach new audiences',
'image' => 'assets/photos/rocket.jpg'
],
'localization' => [
'title' => 'Localization',
'description' => 'Help translate Minds into every global language',
'image' => 'assets/photos/satellite.jpg'
],
'token' => [
'title' => 'The Minds Token',
'description' => 'Earn crypto for your contributions to the network',
'image' => 'assets/photos/globe.jpg'
],
'faq' => [
'title' => 'FAQ',
'description' => 'Everything you need to know about Minds',
......@@ -422,19 +418,44 @@ class Defaults
],
'upgrades' => [
'title' => 'Upgrade your Minds experience',
'description' => 'Minds offers a unique range of powerful upgrades that will supercharge your experience.',
'description' => 'Minds offers a unique range of powerful upgrades that will supercharge your Minds experience',
'image' => 'assets/marketing/upgrades-1.jpg',
],
'plus' => [
'title' => 'Minds Plus',
'description' => 'Upgrade your channel and unlock premium features.',
'image' => 'assets/photos/browsing-mobileapp-discovery.jpg',
'description' => 'Upgrade your channel and unlock premium features',
'image' => 'assets/product-pages/plus/plus-1.jpg',
],
'pro' => [
'title' => 'Minds Pro',
'description' => 'The ultimate platform for creators and brands.',
'image' => 'assets/photos/podcast-people.jpg',
]
'description' => 'The ultimate platform for creators and brands',
'image' => 'assets/product-pages/pro/pro-1.jpg',
],
'nodes' => [
'title' => 'Minds Nodes',
'description' => 'Launch your own social networking app',
'image' => 'assets/product-pages/nodes/nodes-1.jpg',
],
'boost' => [
'title' => 'Boost',
'description' => 'Expand your reach and gain thousands of views',
'image' => 'assets/product-pages/boost/boost-1.jpg',
],
'pay' => [
'title' => 'Minds Pay',
'description' => 'Send and receive payments in USD, BTC, ETH and Tokens',
'image' => 'assets/product-pages/pay/pay-1.jpg',
],
'rewards' => [
'title' => 'Minds Rewards',
'description' => 'Earn tokens for your contributions to the network',
'image' => 'assets/product-pages/rewards/rewards-1.jpg',
],
'token' => [
'title' => 'Tokens',
'description' => 'The fuel of the Minds network',
'image' => 'assets/product-pages/token/token-1.jpg',
],
];
foreach ($marketing as $uri => $page) {
......@@ -444,7 +465,7 @@ class Defaults
'description' => $page['description'],
'og:title' => $page['title'],
'og:description' => $page['description'],
'og:url' => $this->config->site_url . $uri,
'og:url' => $this->config->site_url . (($page['url'] ?? null) ?: $uri),
'og:image' => $this->config->cdn_assets_url . $page['image'],
'og:image:width' => 2000,
'og:image:height' => 1000,
......
......@@ -64,33 +64,6 @@ class Events
}
});
/**
* Blogs need more exportable fields for paywall
*/
Dispatcher::register('export:extender', 'blog', function (Event $event) {
$params = $event->getParameters();
/** @var Core\Blogs\Blog $blog */
$blog = $params['entity'];
$export = $event->response() ?: [];
$currentUser = Session::getLoggedInUserGuid();
$dirty = false;
if ($blog->isPaywall() && $blog->owner_guid != $currentUser) {
$export['description'] = '';
$export['body'] = '';
$dirty = true;
}
if ($dirty) {
return $event->setResponse($export);
}
if (!$currentUser) {
return;
}
});
/**
* Wire paywall hooks. Allows access if sent wire or is plus
*/
......
......@@ -55,8 +55,9 @@ class Video extends Object
$transcoder = ServiceFactory::build('FFMpeg');
$transcoder->setKey($this->getGuid())
->saveToFilestore($filepath)
->transcode();
->setFullHD($this->getFlag('full_hd'))
->saveToFilestore($filepath)
->transcode();
$this->cinemr_guid = $this->getGuid();
}
......@@ -193,6 +194,7 @@ class Video extends Object
'container_guid' => null,
'rating' => 2, //open by default
'time_sent' => time(),
'full_hd' => false,
], $data);
$allowed = [
......@@ -205,7 +207,8 @@ class Video extends Object
'mature',
'boost_rejection_reason',
'rating',
'time_sent'
'time_sent',
'full_hd',
];
foreach ($allowed as $field) {
......@@ -215,8 +218,8 @@ class Video extends Object
if ($field == 'access_id') {
$data[$field] = (int) $data[$field];
} elseif ($field == 'mature') {
$this->setFlag('mature', !!$data['mature']);
} elseif (in_array($field, ['mature', 'full_hd'], true)) {
$this->setFlag($field, !!$data[$field]);
continue;
}
......@@ -253,6 +256,7 @@ class Video extends Object
'thumbnail_src' => $this->getIconUrl(),
'guid' => $this->guid,
'mature' => $this->getFlag('mature'),
'full_hd' => $this->getFlag('full_hd'),
'license' => $this->license ?? '',
]
];
......
......@@ -58,7 +58,7 @@ class ManagerSpec extends ObjectBehavior
$lease->getGuid()
->willReturn(456);
$this->save->setEntity(Argument::that(function ($video) {
return $video->guid == 456
&& $video->access_id == 0;
......@@ -72,10 +72,14 @@ class ManagerSpec extends ObjectBehavior
$this->ffmpeg->setKey(456)
->shouldBeCalled();
$this->ffmpeg->setFullHD(false)
->shouldBeCalled();
$this->ffmpeg->transcode()
->shouldBeCalled();
$this->complete($lease)
$this->setFullHD(false)
->complete($lease)
->shouldReturn(true);
}
}
......@@ -540,7 +540,7 @@ $CONFIG->set('transcoder', [
'bitrate' => 500,
'audio_bitrate' => 80,
'formats' => [ 'mp4', 'webm' ],
'charge' => false,
'pro' => false,
],
[
'width' => 1280,
......@@ -548,7 +548,7 @@ $CONFIG->set('transcoder', [
'bitrate' => 2000,
'audio_bitrate' => 128,
'formats' => [ 'mp4', 'webm' ],
'charge' => false,
'pro' => false,
],
[
'width' => 1920,
......@@ -556,7 +556,7 @@ $CONFIG->set('transcoder', [
'bitrate' => 2000,
'audio_bitrate' => 128,
'formats' => [ 'mp4', 'webm' ],
'charge' => true,
'pro' => true,
],
]
]);
......