mirror of
https://github.com/friendica/friendica
synced 2025-11-13 08:35:21 +01:00
Embedding Support for Twitter videos / automatic height adjust
This commit is contained in:
parent
5c7d4126ac
commit
a8f3800621
16 changed files with 308 additions and 85 deletions
|
|
@ -1,6 +1,6 @@
|
|||
-- ------------------------------------------
|
||||
-- Friendica 2025.07-rc (Interrupted Fern)
|
||||
-- DB_UPDATE_VERSION 1581
|
||||
-- DB_UPDATE_VERSION 1582
|
||||
-- ------------------------------------------
|
||||
|
||||
|
||||
|
|
@ -1432,6 +1432,10 @@ CREATE TABLE IF NOT EXISTS `post-media` (
|
|||
`player-url` varbinary(383) COMMENT 'URL of the embedded player for this media',
|
||||
`player-height` smallint unsigned COMMENT 'Height of the embedded player',
|
||||
`player-width` smallint unsigned COMMENT 'Width of the embedded player',
|
||||
`embed-type` varchar(10) COMMENT 'Type of the embed (e.g. rich or video)',
|
||||
`embed-html` text COMMENT 'HTML embed code for this media',
|
||||
`embed-height` smallint unsigned COMMENT 'Height of the embed',
|
||||
`embed-width` smallint unsigned COMMENT 'Width of the embed',
|
||||
`language` char(3) COMMENT 'Language information about this media in the ISO 639 format',
|
||||
`published` datetime COMMENT 'Publification date of this media',
|
||||
`modified` datetime COMMENT 'Modification date of this media',
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ Fields
|
|||
| player-url | URL of the embedded player for this media | varbinary(383) | YES | | NULL | |
|
||||
| player-height | Height of the embedded player | smallint unsigned | YES | | NULL | |
|
||||
| player-width | Width of the embedded player | smallint unsigned | YES | | NULL | |
|
||||
| embed-type | Type of the embed (e.g. rich or video) | varchar(10) | YES | | NULL | |
|
||||
| embed-html | HTML embed code for this media | text | YES | | NULL | |
|
||||
| embed-height | Height of the embed | smallint unsigned | YES | | NULL | |
|
||||
| embed-width | Width of the embed | smallint unsigned | YES | | NULL | |
|
||||
| language | Language information about this media in the ISO 639 format | char(3) | YES | | NULL | |
|
||||
| published | Publification date of this media | datetime | YES | | NULL | |
|
||||
| modified | Modification date of this media | datetime | YES | | NULL | |
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ use Psr\Http\Message\UriInterface;
|
|||
* @property-read ?UriInterface $playerUrl
|
||||
* @property-read ?int $playerWidth
|
||||
* @property-read ?int $playerHeight
|
||||
* @property-read ?string $embedType
|
||||
* @property-read ?string $embedHtml
|
||||
* @property-read ?int $embedWidth
|
||||
* @property-read ?int $embedHeight
|
||||
* @property-read ?int $attachId
|
||||
* @property-read ?string $language
|
||||
* @property-read ?string $published
|
||||
|
|
@ -119,6 +123,14 @@ class PostMedia extends BaseEntity
|
|||
protected $published;
|
||||
/** @var ?string (Datetime) */
|
||||
protected $modified;
|
||||
/** @var ?string */
|
||||
protected $embedType;
|
||||
/** @var ?string */
|
||||
protected $embedHtml;
|
||||
/** @var ?int In pixels */
|
||||
protected $embedWidth;
|
||||
/** @var ?int In pixels */
|
||||
protected $embedHeight;
|
||||
|
||||
public function __construct(
|
||||
int $uriId,
|
||||
|
|
@ -148,7 +160,11 @@ class PostMedia extends BaseEntity
|
|||
?int $attachId = null,
|
||||
?string $language = null,
|
||||
?string $published = null,
|
||||
?string $modified = null
|
||||
?string $modified = null,
|
||||
?string $embedType = null,
|
||||
?string $embedHtml = null,
|
||||
?int $embedWidth = null,
|
||||
?int $embedHeight = null
|
||||
) {
|
||||
$this->uriId = $uriId;
|
||||
$this->url = $url;
|
||||
|
|
@ -178,6 +194,10 @@ class PostMedia extends BaseEntity
|
|||
$this->language = $language;
|
||||
$this->published = $published;
|
||||
$this->modified = $modified;
|
||||
$this->embedType = $embedType;
|
||||
$this->embedHtml = $embedHtml;
|
||||
$this->embedWidth = $embedWidth;
|
||||
$this->embedHeight = $embedHeight;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -290,6 +310,10 @@ class PostMedia extends BaseEntity
|
|||
$this->language,
|
||||
$this->published,
|
||||
$this->modified,
|
||||
$this->embedType,
|
||||
$this->embedHtml,
|
||||
$this->embedWidth,
|
||||
$this->embedHeight
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -324,6 +348,10 @@ class PostMedia extends BaseEntity
|
|||
$this->language,
|
||||
$this->published,
|
||||
$this->modified,
|
||||
$this->embedType,
|
||||
$this->embedHtml,
|
||||
$this->embedWidth,
|
||||
$this->embedHeight
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
|
|||
$row['language'],
|
||||
$row['published'],
|
||||
$row['modified'],
|
||||
$row['embed-type'],
|
||||
$row['embed-html'],
|
||||
$row['embed-width'],
|
||||
$row['embed-height']
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +137,10 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
|
|||
'player-url' => $attachment['player_url'] ?? null,
|
||||
'player-width' => $attachment['player_width'] ?? null,
|
||||
'player-height' => $attachment['player_height'] ?? null,
|
||||
'embed-type' => $attachment['embed_type'] ?? null,
|
||||
'embed-html' => $attachment['embed_html'] ?? null,
|
||||
'embed-width' => $attachment['embed_width'] ?? null,
|
||||
'embed-height' => $attachment['embed_height'] ?? null,
|
||||
'attach-id' => null,
|
||||
'language' => null,
|
||||
'published' => null,
|
||||
|
|
@ -178,6 +186,10 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
|
|||
'player-url' => $data['player']['embed'] ?? null,
|
||||
'player-width' => $data['player']['width'] ?? null,
|
||||
'player-height' => $data['player']['height'] ?? null,
|
||||
'embed-type' => $data['embed']['type'] ?? null,
|
||||
'embed-html' => $data['embed']['html'] ?? null,
|
||||
'embed-width' => $data['embed']['width'] ?? null,
|
||||
'embed-height' => $data['embed']['height'] ?? null,
|
||||
'attach-id' => null,
|
||||
'language' => $data['language'] ?? null,
|
||||
'published' => $data['published'] ?? null,
|
||||
|
|
|
|||
|
|
@ -146,6 +146,10 @@ class PostMedia extends BaseRepository
|
|||
'player-url' => $PostMedia->playerUrl,
|
||||
'player-height' => $PostMedia->playerHeight,
|
||||
'player-width' => $PostMedia->playerWidth,
|
||||
'embed-type' => $PostMedia->embedType,
|
||||
'embed-html' => $PostMedia->embedHtml,
|
||||
'embed-height' => $PostMedia->embedHeight,
|
||||
'embed-width' => $PostMedia->embedWidth,
|
||||
'attach-id' => $PostMedia->attachId,
|
||||
'language' => $PostMedia->language,
|
||||
'published' => $PostMedia->published,
|
||||
|
|
@ -365,6 +369,8 @@ class PostMedia extends BaseRepository
|
|||
$player = $this->getVideoAttachment($media, $uid);
|
||||
} elseif ($allow_embed && !empty($media->playerUrl)) {
|
||||
$player = $this->getPlayerIframe($media);
|
||||
} elseif ($allow_embed && !empty($media->embedHtml)) {
|
||||
$player = '<span class="embedded-media">' . $this->getEmbedIframe($media) . '</span>';
|
||||
} else {
|
||||
$player = $this->getLinkAttachment($media);
|
||||
}
|
||||
|
|
@ -395,7 +401,7 @@ class PostMedia extends BaseRepository
|
|||
}
|
||||
|
||||
if (($postMedia->height ?? 0) > ($postMedia->width ?? 0)) {
|
||||
$height = min($this->config->get('system', 'max_video_height') ?: '100%', $postMedia->height);
|
||||
$height = min($this->config->get('system', 'max_height') ?: '100%', $postMedia->height);
|
||||
$width = 'auto';
|
||||
} else {
|
||||
$height = 'auto';
|
||||
|
|
@ -404,6 +410,8 @@ class PostMedia extends BaseRepository
|
|||
|
||||
if ($this->pConfig->get($uid, 'system', 'embed_media', false) && ($postMedia->playerUrl != '') && ($postMedia->playerHeight > 0)) {
|
||||
$media = $this->getPlayerIframe($postMedia);
|
||||
} elseif ($this->pConfig->get($uid, 'system', 'embed_media', false) && ($postMedia->embedHtml != '')) {
|
||||
$media = $this->getEmbedIframe($postMedia);
|
||||
} else {
|
||||
/// @todo Move the template to /content as well
|
||||
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate($postMedia->type == Post\Media::HLS ? 'hls_top.tpl' : 'video_top.tpl'), [
|
||||
|
|
@ -428,26 +436,38 @@ class PostMedia extends BaseRepository
|
|||
return '';
|
||||
}
|
||||
|
||||
$attributes = ' src="' . $postMedia->playerUrl . '"';
|
||||
$max_height = $this->config->get('system', 'max_video_height') ?: $postMedia->playerHeight;
|
||||
$div_style = '';
|
||||
$iframe_style = '';
|
||||
$height = min($this->config->get('system', 'max_height'), $postMedia->playerHeight);
|
||||
|
||||
if ($postMedia->playerWidth != 0 && $postMedia->playerHeight != 0) {
|
||||
if ($postMedia->playerHeight > $postMedia->playerWidth) {
|
||||
$factor = 100;
|
||||
$height_attr = min($max_height, $postMedia->playerHeight);
|
||||
} else {
|
||||
$factor = round($postMedia->playerHeight / $postMedia->playerWidth, 2) * 100;
|
||||
$height_attr = '100%';
|
||||
}
|
||||
$attributes .= ' height="' . $height_attr. '" style="position:absolute;left:0px;top:0px"';
|
||||
$return = '<div style="position:relative;padding-bottom:' . $factor . '%;margin-bottom:1em">';
|
||||
} else {
|
||||
$attributes .= ' height="' . min($max_height, $postMedia->playerHeight) . '"';
|
||||
$return = '<div style="position:relative">';
|
||||
if ($postMedia->playerWidth != 0 && $postMedia->playerHeight != 0 && $postMedia->playerWidth > $this->config->get('system', 'max_width') && $postMedia->playerWidth > $postMedia->playerHeight) {
|
||||
$factor = round($postMedia->playerHeight / $postMedia->playerWidth, 2) * 100;
|
||||
$height = '100%';
|
||||
$iframe_style .= 'position:absolute;left:0px;top:0px;';
|
||||
$div_style .= 'position:relative;padding-bottom:' . $factor . '%;';
|
||||
}
|
||||
|
||||
$return .= '<iframe ' . $attributes . ' width="100%" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen sandbox="allow-same-origin allow-scripts"></iframe></div>';
|
||||
return $return;
|
||||
return Renderer::replaceMacros(Renderer::getMarkupTemplate('content/iframe.tpl'), [
|
||||
'src' => $postMedia->playerUrl,
|
||||
'height' => $height,
|
||||
'width' => $postMedia->embedWidth && $postMedia->embedWidth <= $this->config->get('system', 'max_width') ? $postMedia->embedWidth : '100%',
|
||||
'div_style' => $div_style,
|
||||
'iframe_style' => $iframe_style,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getEmbedIframe(PostMediaEntity $postMedia): string
|
||||
{
|
||||
if ($postMedia->embedHtml == '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Renderer::replaceMacros(Renderer::getMarkupTemplate($postMedia->embedHeight ? 'content/embed-iframe.tpl' : 'content/embed-iframe-resize.tpl'), [
|
||||
'id' => 'iframe-' . hash('md5', $postMedia->embedHtml),
|
||||
'src' => $postMedia->embedHtml,
|
||||
'height' => $postMedia->embedHeight + 20,
|
||||
'width' => $postMedia->embedWidth && $postMedia->embedWidth <= $this->config->get('system', 'max_width') ? $postMedia->embedWidth : '100%',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getAudioAttachment(PostMediaEntity $postMedia): string
|
||||
|
|
|
|||
|
|
@ -443,10 +443,19 @@ class BBCode
|
|||
$return = sprintf('<div class="type-%s">', $data['type']);
|
||||
}
|
||||
|
||||
if ($embed && $data['player_url'] != '' && $data['player_height'] != 0) {
|
||||
if ($embed && (($data['player_url'] != '' && $data['player_height'] != 0) || $data['embed_html'] != '')) {
|
||||
$media = DI::postMediaFactory()->createFromAttachment($data, $uriid);
|
||||
$return .= DI::postMediaRepository()->getPlayerIframe($media);
|
||||
if ($data['player_url'] != '' && $data['player_height'] != 0) {
|
||||
$return .= DI::postMediaRepository()->getPlayerIframe($media);
|
||||
} else {
|
||||
$return .= DI::postMediaRepository()->getEmbedIframe($media);
|
||||
}
|
||||
$preview_mode = self::PREVIEW_NO_IMAGE;
|
||||
unset($data['title']);
|
||||
unset($data['url']);
|
||||
unset($data['description']);
|
||||
unset($data['provider_url']);
|
||||
unset($data['provider_name']);
|
||||
}
|
||||
|
||||
if ($preview_mode == self::PREVIEW_NO_IMAGE) {
|
||||
|
|
|
|||
|
|
@ -3405,6 +3405,10 @@ class Item
|
|||
'player_url' => (string)$attachment->playerUrl,
|
||||
'player_width' => $attachment->playerWidth,
|
||||
'player_height' => $attachment->playerHeight,
|
||||
'embed_type' => $attachment->embedType,
|
||||
'embed_html' => $attachment->embedHtml,
|
||||
'embed_width' => $attachment->embedWidth,
|
||||
'embed_height' => $attachment->embedHeight,
|
||||
];
|
||||
|
||||
if ($preview && $attachment->preview) {
|
||||
|
|
|
|||
|
|
@ -473,6 +473,10 @@ class Media
|
|||
$media['player-url'] = $data['player']['embed'] ?? null;
|
||||
$media['player-height'] = $data['player']['height'] ?? null;
|
||||
$media['player-width'] = $data['player']['width'] ?? null;
|
||||
$media['embed-type'] = $data['embed']['type'] ?? null;
|
||||
$media['embed-html'] = $data['embed']['html'] ?? null;
|
||||
$media['embed-height'] = $data['embed']['height'] ?? null;
|
||||
$media['embed-width'] = $data['embed']['width'] ?? null;
|
||||
$media['language'] = $data['language'] ?? null;
|
||||
$media['published'] = $data['published'] ?? null;
|
||||
$media['modified'] = $data['modified'] ?? null;
|
||||
|
|
|
|||
|
|
@ -1926,14 +1926,17 @@ class Receiver
|
|||
$attachment['player-height'] = $player['height'] ?? null;
|
||||
$attachment['player-width'] = $player['width'] ?? null;
|
||||
|
||||
foreach ($attachments as $media) {
|
||||
if (isset($media['height']) && isset($media['width'])) {
|
||||
if ($media['height'] > $attachment['player-height'] || $media['width'] > $attachment['player-width']) {
|
||||
$attachment['player-height'] = $media['height'];
|
||||
$attachment['player-width'] = $media['width'];
|
||||
if (is_null($attachment['player-height']) && is_null($attachment['player-width'])) {
|
||||
foreach ($attachments as $media) {
|
||||
if (isset($media['height']) && isset($media['width'])) {
|
||||
if ($media['height'] > $attachment['player-height'] || $media['width'] > $attachment['player-width']) {
|
||||
$attachment['player-height'] = $media['height'];
|
||||
$attachment['player-width'] = $media['width'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$height && !$width) {
|
||||
$attachment['height'] = $attachment['player-height'];
|
||||
$attachment['width'] = $attachment['player-width'];
|
||||
|
|
|
|||
|
|
@ -494,8 +494,6 @@ class ParseUrl
|
|||
}
|
||||
}
|
||||
|
||||
$siteinfo = self::getOembedInfo($xpath, $siteinfo);
|
||||
|
||||
$list = $xpath->query("//script[@type='application/ld+json']");
|
||||
foreach ($list as $node) {
|
||||
if (!empty($node->nodeValue)) {
|
||||
|
|
@ -506,6 +504,8 @@ class ParseUrl
|
|||
}
|
||||
}
|
||||
|
||||
$siteinfo = self::getOembedInfo($xpath, $siteinfo);
|
||||
|
||||
if (!empty($siteinfo['player']['stream'])) {
|
||||
// Only add player data to media arrays if there is no duplicate
|
||||
$content_urls = array_merge(array_column($siteinfo['audio'] ?? [], 'content'), array_column($siteinfo['video'] ?? [], 'content'));
|
||||
|
|
@ -1299,50 +1299,58 @@ class ParseUrl
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetch additional information via oEmbed
|
||||
* Fetch oEmbed data
|
||||
*
|
||||
* @param DOMXPath $xpath
|
||||
* @param array $siteinfo
|
||||
* @param string $url
|
||||
*
|
||||
* @return array siteinfo
|
||||
* @return array oEmbed data
|
||||
*/
|
||||
private static function getOembedInfo(DOMXPath $xpath, array $siteinfo): array
|
||||
private static function getOembedData(DOMXPath $xpath, string $url): array
|
||||
{
|
||||
$oembed = '';
|
||||
foreach ($xpath->query("//link[@type='application/json+oembed']") as $link) {
|
||||
/** @var DOMElement $link */
|
||||
$href = $link->getAttributeNode('href')->nodeValue;
|
||||
$oembed = $href;
|
||||
DI::logger()->debug('Found oEmbed JSON', ['url' => $href]);
|
||||
$data = [];
|
||||
|
||||
if (in_array(parse_url(Strings::normaliseLink($url), PHP_URL_HOST), ['twitter.com', 'x.com'])) {
|
||||
$oembed = 'https://publish.twitter.com/oembed?url=' . urlencode($url) . '&dnt=true';
|
||||
|
||||
$systemLanguage = DI::config()->get('system', 'language');
|
||||
if ($systemLanguage) {
|
||||
$oembed .= '&lang=' . $systemLanguage;
|
||||
}
|
||||
DI::logger()->debug('Using Twitter oEmbed', ['url' => $url, 'oembed' => $oembed]);
|
||||
}
|
||||
|
||||
if (empty($oembed)) {
|
||||
$embera = new Embera();
|
||||
$urldata = $embera->getUrlData([$siteinfo['url']]);
|
||||
if (empty($urldata)) {
|
||||
return $siteinfo;
|
||||
}
|
||||
$data = current($urldata);
|
||||
DI::logger()->debug('Found oEmbed JSON from Embera', ['url' => $siteinfo['url']]);
|
||||
} else {
|
||||
$result = DI::httpClient()->get($oembed, HttpClientAccept::DEFAULT, [HttpClientOptions::REQUEST => HttpClientRequest::SITEINFO]);
|
||||
if (!$result->isSuccess()) {
|
||||
return $siteinfo;
|
||||
}
|
||||
$json_string = $result->getBodyString();
|
||||
if (empty($json_string)) {
|
||||
return $siteinfo;
|
||||
if (!$oembed) {
|
||||
foreach ($xpath->query("//link[@type='application/json+oembed']") as $link) {
|
||||
/** @var DOMElement $link */
|
||||
$oembed = $link->getAttributeNode('href')->nodeValue;
|
||||
DI::logger()->debug('Found oEmbed JSON from page', ['url' => $url, 'oembed' => $oembed]);
|
||||
}
|
||||
}
|
||||
|
||||
$data = json_decode($json_string, true);
|
||||
if ($oembed) {
|
||||
$oembed .= '&maxwidth=' . DI::config()->get('system', 'max_width') . '&maxheight=' . DI::config()->get('system', 'max_height') . '&format=json';
|
||||
$result = DI::httpClient()->get($oembed, HttpClientAccept::DEFAULT, [HttpClientOptions::REQUEST => HttpClientRequest::SITEINFO]);
|
||||
if ($result->isSuccess() && $result->getBodyString()) {
|
||||
$data = json_decode($result->getBodyString(), true);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($data) || !is_array($data)) {
|
||||
return $siteinfo;
|
||||
$embera = new Embera(['maxwidth' => DI::config()->get('system', 'max_width'), 'maxheight' => DI::config()->get('system', 'max_height')]);
|
||||
$urldata = $embera->getUrlData($url);
|
||||
if (empty($urldata)) {
|
||||
return [];
|
||||
}
|
||||
$data = current($urldata);
|
||||
DI::logger()->debug('Found oEmbed JSON from Embera', ['url' => $url]);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
DI::logger()->debug('Got oEmbed data', ['url' => $siteinfo['url'], 'type' => $data['type'], 'data' => $data]);
|
||||
|
||||
private static function getSiteinfoFromoEmbed(array $siteinfo, array $data): array
|
||||
{
|
||||
// Youtube provides only basic information to some IP ranges.
|
||||
// Dailymotion only provices "Dailymotion" as title in their meta tags, so oEmbed is better
|
||||
// @todo We have to decide if we always trust oEmbed more than the meta tags
|
||||
|
|
@ -1382,6 +1390,8 @@ class ParseUrl
|
|||
if (isset($data[$key]) && (empty($siteinfo[$value]) || $overwrite)) {
|
||||
if ($value == 'published') {
|
||||
$siteinfo[$value] = DateTimeFormat::utc($data[$key]);
|
||||
} elseif (is_string($value)) {
|
||||
$siteinfo[$value] = trim(strip_tags(html_entity_decode($data[$key], ENT_COMPAT, 'UTF-8')));
|
||||
} else {
|
||||
$siteinfo[$value] = $data[$key];
|
||||
}
|
||||
|
|
@ -1391,26 +1401,36 @@ class ParseUrl
|
|||
if (!empty($unknown_fields)) {
|
||||
DI::logger()->debug('Unknown oEmbed fields', ['url' => $siteinfo['url'], 'fields' => $unknown_fields]);
|
||||
}
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
if (!empty($data['html']) && empty($siteinfo['player'])) {
|
||||
$siteinfo = self::setPlayer($data, $siteinfo);
|
||||
private static function getOembedInfo(DOMXPath $xpath, array $siteinfo): array
|
||||
{
|
||||
$data = self::getOembedData($xpath, $siteinfo['url']);
|
||||
if (empty($data) || !is_array($data)) {
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
$siteinfo = self::getSiteinfoFromoEmbed($siteinfo, $data);
|
||||
|
||||
if ($data['type'] == 'video' & empty($siteinfo['player']) && ($data['provider_url'] ?? '') == 'https://www.tiktok.com' && isset($data['embed_product_id']) && isset($data['thumbnail_width']) && isset($data['thumbnail_height'])) {
|
||||
$siteinfo['embed']['type'] = $data['type'];
|
||||
$siteinfo['embed']['html'] = trim($data['html']);
|
||||
$siteinfo['embed']['width'] = is_numeric($data['width'] ?? '') ? $data['width'] : $data['thumbnail_width'];
|
||||
$siteinfo['embed']['height'] = is_numeric($data['height'] ?? '') ? $data['height'] : $data['thumbnail_height'];
|
||||
$siteinfo['player']['embed'] = 'https://www.tiktok.com/player/v1/' . $data['embed_product_id'] . '?description=1&rel=0';
|
||||
$siteinfo['player']['width'] = $data['thumbnail_width'];
|
||||
$siteinfo['player']['height'] = $data['thumbnail_height'];
|
||||
$siteinfo['player']['width'] = $siteinfo['embed']['width'];
|
||||
$siteinfo['player']['height'] = $siteinfo['embed']['height'];
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
if (!empty($siteinfo['player'])) {
|
||||
foreach (['width' => 'width', 'height' => 'height', 'fixedWidth' => 'width'] as $key => $value) {
|
||||
if (empty($siteinfo['player'][$value]) && !empty($data[$key])) {
|
||||
$siteinfo['player'][$value] = $data[$key];
|
||||
}
|
||||
}
|
||||
if (!isset($data['html'])) {
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
if ($data['type'] == 'rich' && isset($data['html']) && !isset($siteinfo['text'])) {
|
||||
unset($siteinfo['player']);
|
||||
|
||||
if ($data['type'] == 'rich' && !isset($siteinfo['text'])) {
|
||||
$bbcode = HTML::toBBCode($data['html'] ?? '');
|
||||
$bbcode = preg_replace("(\[url\](.*?)\[\/url\])ism", "", $bbcode);
|
||||
|
||||
|
|
@ -1418,6 +1438,46 @@ class ParseUrl
|
|||
DI::logger()->debug('Text is fetched from oEmbed HTML', ['url' => $siteinfo['url'], 'text' => $siteinfo['text']]);
|
||||
}
|
||||
|
||||
$siteinfo = self::setPlayer($data, $siteinfo);
|
||||
|
||||
if (!empty($siteinfo['player'])) {
|
||||
$siteinfo['embed']['type'] = $data['type'];
|
||||
$siteinfo['embed']['html'] = trim($data['html']);
|
||||
$siteinfo['embed']['width'] = $siteinfo['player']['width'];
|
||||
$siteinfo['embed']['height'] = $siteinfo['player']['height'];
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
if (($data['provider_url'] ?? '') == 'https://twitter.com') {
|
||||
if (preg_match_all('#https?://t\.co/[a-zA-Z0-9]+#', $data['html'], $matches)) {
|
||||
$links = array_unique($matches[0]);
|
||||
foreach ($links as $link) {
|
||||
$curlResult = DI::httpClient()->head($link);
|
||||
$redirect = $curlResult->getRedirectUrl();
|
||||
if (preg_match('#/(video|broadcasts)/#', $redirect)) {
|
||||
$siteinfo['embed']['type'] = $data['type'];
|
||||
$siteinfo['embed']['html'] = trim(str_replace('<blockquote class="twitter-tweet"', '<blockquote class="twitter-tweet" data-media-max-width="560"', $data['html']));
|
||||
$siteinfo['embed']['width'] = is_numeric($data['width'] ?? '') ? $data['width'] : null;
|
||||
$siteinfo['embed']['height'] = is_numeric($data['height'] ?? '') ? $data['height'] : null;
|
||||
DI::logger()->debug('Fetched Twitter video oEmbed HTML', ['url' => $siteinfo['url']]);
|
||||
return $siteinfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We don't embed regular Twitter posts, since this doesn't add any additional value
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
if ($data['type'] != 'video' && ($siteinfo['pagetype'] ?? '') != 'video') {
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
$siteinfo['embed']['type'] = $data['type'];
|
||||
$siteinfo['embed']['html'] = trim($data['html']);
|
||||
$siteinfo['embed']['width'] = is_numeric($data['width'] ?? '') ? $data['width'] : null;
|
||||
$siteinfo['embed']['height'] = is_numeric($data['height'] ?? '') ? $data['height'] : null;
|
||||
DI::logger()->debug('Fetched oEmbed HTML', ['provider' => $data['provider_url'], 'url' => $siteinfo['url']]);
|
||||
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
|
|
@ -1431,13 +1491,11 @@ class ParseUrl
|
|||
*/
|
||||
private static function setPlayer(array $data, array $siteinfo): array
|
||||
{
|
||||
// iframe content is most likely not a video but an embedded website
|
||||
if (($data['type'] == 'rich') && ($data['asset_type'] ?? '') == 'article') {
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
if (isset($data['iframe_url'])) {
|
||||
$siteinfo['player']['embed'] = $data['iframe_url'];
|
||||
$siteinfo['player']['embed'] = $data['iframe_url'];
|
||||
$siteinfo['player']['width'] = is_numeric($data['width'] ?? '') ? $data['width'] : null;
|
||||
$siteinfo['player']['height'] = is_numeric($data['height'] ?? '') ? $data['height'] : null;
|
||||
DI::logger()->debug('Found oEmbed iframe_url parameter', ['embed' => $siteinfo['player']['embed'], 'width' => $siteinfo['player']['width'], 'height' => $siteinfo['player']['height']]);
|
||||
return $siteinfo;
|
||||
}
|
||||
|
||||
|
|
@ -1464,7 +1522,9 @@ class ParseUrl
|
|||
return $siteinfo;
|
||||
}
|
||||
|
||||
$siteinfo['player']['embed'] = $src;
|
||||
$siteinfo['player']['embed'] = $src;
|
||||
$siteinfo['player']['width'] = is_numeric($data['width'] ?? '') ? $data['width'] : null;
|
||||
$siteinfo['player']['height'] = is_numeric($data['height'] ?? '') ? $data['height'] : null;
|
||||
|
||||
$width = $iframe->getAttributeNode('width')->nodeValue ?? null;
|
||||
if (!empty($width) && is_numeric($width)) {
|
||||
|
|
@ -1476,8 +1536,7 @@ class ParseUrl
|
|||
$siteinfo['player']['height'] = $height;
|
||||
}
|
||||
|
||||
DI::logger()->debug('Found oEmbed iframe', ['embed' => $siteinfo['player']['embed'] ?? '', 'width' => $siteinfo['player']['width'] ?? '', 'height' => $siteinfo['player']['height'] ?? '']);
|
||||
|
||||
DI::logger()->debug('Found oEmbed iframe', ['embed' => $siteinfo['player']['embed'], 'width' => $siteinfo['player']['width'], 'height' => $siteinfo['player']['height']]);
|
||||
return $siteinfo;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ use Friendica\Database\DBA;
|
|||
|
||||
// This file is required several times during the test in DbaDefinition which justifies this condition
|
||||
if (!defined('DB_UPDATE_VERSION')) {
|
||||
define('DB_UPDATE_VERSION', 1581);
|
||||
define('DB_UPDATE_VERSION', 1582);
|
||||
}
|
||||
|
||||
return [
|
||||
|
|
@ -1437,6 +1437,10 @@ return [
|
|||
"player-url" => ["type" => "varbinary(383)", "comment" => "URL of the embedded player for this media"],
|
||||
"player-height" => ["type" => "smallint unsigned", "comment" => "Height of the embedded player"],
|
||||
"player-width" => ["type" => "smallint unsigned", "comment" => "Width of the embedded player"],
|
||||
"embed-type" => ["type" => "varchar(10)", "comment" => "Type of the embed (e.g. rich or video)"],
|
||||
"embed-html" => ["type" => "text", "comment" => "HTML embed code for this media"],
|
||||
"embed-height" => ["type" => "smallint unsigned", "comment" => "Height of the embed"],
|
||||
"embed-width" => ["type" => "smallint unsigned", "comment" => "Width of the embed"],
|
||||
"language" => ["type" => "char(3)", "comment" => "Language information about this media in the ISO 639 format"],
|
||||
"published" => ["type" => "datetime", "comment" => "Publification date of this media"],
|
||||
"modified" => ["type" => "datetime", "comment" => "Modification date of this media"],
|
||||
|
|
|
|||
|
|
@ -388,6 +388,10 @@ return [
|
|||
// Maximum number of "fetchreplies" activities in the callstack. The higher, the more complete a thread will be.
|
||||
'max_fetchreplies_depth' => 2,
|
||||
|
||||
// max_height (Integer)
|
||||
// Maximum height for OEmbed and videos
|
||||
'max_height' => 620,
|
||||
|
||||
// max_image_length (Integer)
|
||||
// An alternate way of limiting picture upload sizes.
|
||||
// Specify the maximum pixel length that pictures are allowed to be (for non-square pictures, it will apply to the longest side).
|
||||
|
|
@ -415,9 +419,9 @@ return [
|
|||
// Maximum recursion depth when fetching posts until the job is delegated to a worker task or finished.
|
||||
'max_recursion_depth' => 50,
|
||||
|
||||
// max_video_height (Integer)
|
||||
// Maximum height of videos in portrait mode.
|
||||
'max_video_height' => 640,
|
||||
// max_width (Integer)
|
||||
// Maximum width for OEmbed and videos
|
||||
'max_width' => 620,
|
||||
|
||||
// memcache_host (String)
|
||||
// Host name of the memcache daemon.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
namespace Friendica\Test\src\Content\Text;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Test\FixtureTestCase;
|
||||
|
|
@ -726,8 +727,11 @@ Lucas: For the right price, yes.[/share]',
|
|||
{
|
||||
return [
|
||||
'player' => [
|
||||
'expected' => 'text <div class="type-link"><div style="position:relative;padding-bottom:75%;margin-bottom:1em"><iframe src="http://domain.tld/player" height="100%" style="position:absolute;left:0px;top:0px" width="100%" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen sandbox="allow-same-origin allow-scripts"></iframe></div><h4><a href="http://domain.tld/page" target="_blank" rel="noopener noreferrer">title</a></h4><blockquote>description</blockquote><sup><a href="http://domain.tld/provider_url" target="_blank" rel="noopener noreferrer">author_name (provider_name)</a></sup></div>',
|
||||
'data' => [
|
||||
'expected' => 'text <div class="type-link"><div style="">' . "\n" .
|
||||
' <iframe src="http://domain.tld/player" style="" height="480" width="100%" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen sandbox="allow-same-origin allow-scripts"></iframe>' . "\n" .
|
||||
"</div>\n" .
|
||||
'</div>',
|
||||
'data' => [
|
||||
'author_name' => 'author_name',
|
||||
'author_url' => 'http://domain.tld/author_url',
|
||||
'description' => 'description',
|
||||
|
|
@ -740,10 +744,31 @@ Lucas: For the right price, yes.[/share]',
|
|||
'type' => 'link',
|
||||
'url' => 'http://domain.tld/page',
|
||||
'player_url' => 'http://domain.tld/player',
|
||||
'player_width' => 640,
|
||||
'player_width' => 620,
|
||||
'player_height' => 480,
|
||||
],
|
||||
],
|
||||
'embed' => [
|
||||
'expected' => 'text <div class="type-link"><iframe srcdoc="<iframe src="http://domain.tld/player"></iframe>" height="500" width="620" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen sandbox="allow-same-origin allow-scripts"></iframe>' . "\n" .
|
||||
'</div>',
|
||||
'data' => [
|
||||
'author_name' => 'author_name',
|
||||
'author_url' => 'http://domain.tld/author_url',
|
||||
'description' => 'description',
|
||||
'image' => 'http://domain.tld/image',
|
||||
'preview' => 'http://domain.tld/preview',
|
||||
'provider_name' => 'provider_name',
|
||||
'provider_url' => 'http://domain.tld/provider_url',
|
||||
'text' => 'text',
|
||||
'title' => 'title',
|
||||
'type' => 'link',
|
||||
'url' => 'http://domain.tld/page',
|
||||
'player_url' => '',
|
||||
'embed_html' => '<iframe src="http://domain.tld/player"></iframe>',
|
||||
'embed_width' => 620,
|
||||
'embed_height' => 480,
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -755,6 +780,8 @@ Lucas: For the right price, yes.[/share]',
|
|||
*/
|
||||
public function testConvertAttachment(string $expected, array $data)
|
||||
{
|
||||
Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine');
|
||||
|
||||
$actual = BBCode::convertAttachment('', BBCode::INTERNAL, $data, 0, BBCode::PREVIEW_LARGE, true);
|
||||
|
||||
self::assertEquals($expected, $actual);
|
||||
|
|
|
|||
25
view/templates/content/embed-iframe-resize.tpl
Normal file
25
view/templates/content/embed-iframe-resize.tpl
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{{*
|
||||
* Copyright (C) 2010-2024, the Friendica project
|
||||
* SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*}}
|
||||
<iframe id="{{$id}}" srcdoc="{{$src}}<script>
|
||||
function sendResize() {
|
||||
parent.postMessage({type:'resize',height:document.body.scrollHeight},'*');
|
||||
}
|
||||
window.onload = function() {
|
||||
sendResize();
|
||||
setTimeout(sendResize, 2000);
|
||||
};
|
||||
</script>" width="{{$width}}" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen sandbox="allow-same-origin allow-scripts"></iframe>
|
||||
<script>
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.data && event.data.type === 'resize') {
|
||||
var iframe = document.getElementById('{{$id}}');
|
||||
if (iframe) {
|
||||
iframe.style.height = (event.data.height + 20) + 'px';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
7
view/templates/content/embed-iframe.tpl
Normal file
7
view/templates/content/embed-iframe.tpl
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{{*
|
||||
* Copyright (C) 2010-2024, the Friendica project
|
||||
* SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*}}
|
||||
<iframe srcdoc="{{$src}}" height="{{$height}}" width="{{$width}}" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen sandbox="allow-same-origin allow-scripts"></iframe>
|
||||
9
view/templates/content/iframe.tpl
Normal file
9
view/templates/content/iframe.tpl
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{{*
|
||||
* Copyright (C) 2010-2024, the Friendica project
|
||||
* SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*}}
|
||||
<div style="{{$div_style}}">
|
||||
<iframe src="{{$src}}" style="{{$iframe_style}}" height="{{$height}}" width="{{$width}}" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen sandbox="allow-same-origin allow-scripts"></iframe>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue