- Fixed the Imgur embed path.

Imgur now uses a local wrapper page, 
[provider_embed.php](/Users/tyemeclifford/Documents/GH/mediaplayer/provider_embed.php), 
that renders Imgur’s official blockquote + 
https://s.imgur.com/min/embed.js snippet instead of trying to iframe 
Imgur directly. That should avoid the “refuses to connect” issue for 
albums, galleries, images, GIFs, GIFV, and videos.
Also updated:
[includes/db.php](/Users/tyemeclifford/Documents/GH/mediaplayer/includes/db.php): 
Imgur embed URLs now point to the wrapper, and Imgur view-count scraping 
has dedicated fallback patterns.
[admin/index.php](/Users/tyemeclifford/Documents/GH/mediaplayer/admin/index.php): 
Upkeeping → Correct Views now explicitly counts Imgur-backed items in 
the candidate summary.
[README.md](/Users/tyemeclifford/Documents/GH/mediaplayer/README.md): 
documented the Imgur wrapper behavior.
This commit is contained in:
Ty Clifford
2026-06-25 08:36:40 -04:00
parent 173fe3917e
commit 4425716d6f
4 changed files with 116 additions and 3 deletions
+2
View File
@@ -79,6 +79,8 @@ Styled to match the existing `gate.php` / `index.php` dark theme exactly.
External URLs must use `http://` or `https://`. Uploaded files and remote URLs can be mixed on the same video. Recognized provider URLs such as YouTube, Vimeo, Dailymotion, Imgur, Twitch, Facebook, TikTok, Instagram, X/Twitter, SoundCloud, Spotify, Google Drive, Wistia, Streamable, and Loom render through their embed players. External URLs must use `http://` or `https://`. Uploaded files and remote URLs can be mixed on the same video. Recognized provider URLs such as YouTube, Vimeo, Dailymotion, Imgur, Twitch, Facebook, TikTok, Instagram, X/Twitter, SoundCloud, Spotify, Google Drive, Wistia, Streamable, and Loom render through their embed players.
Imgur links use the official Imgur embed script inside a local wrapper, so album, gallery, image, GIF, GIFV, and video URLs can render without direct iframe blocking.
When adding an external URL, the admin form can fetch title, description, duration, thumbnail metadata, and readable provider view counts through oEmbed, page metadata, and JSON-LD. The save action repeats the metadata lookup as a fallback, so blank titles can be filled from supported external pages even if the browser autofill was not used. When adding an external URL, the admin form can fetch title, description, duration, thumbnail metadata, and readable provider view counts through oEmbed, page metadata, and JSON-LD. The save action repeats the metadata lookup as a fallback, so blank titles can be filled from supported external pages even if the browser autofill was not used.
If no thumbnail image is uploaded, the add/edit forms can auto-grab one from the first supported source. YouTube and Dailymotion use direct thumbnail URLs; Vimeo, Wistia, TikTok, SoundCloud, and Spotify use oEmbed when available. Local uploaded videos can generate a first-frame thumbnail when `ffmpeg` is installed on the server. If no thumbnail image is uploaded, the add/edit forms can auto-grab one from the first supported source. YouTube and Dailymotion use direct thumbnail URLs; Vimeo, Wistia, TikTok, SoundCloud, and Spotify use oEmbed when available. Local uploaded videos can generate a first-frame thumbnail when `ffmpeg` is installed on the server.
+3 -1
View File
@@ -95,6 +95,7 @@ $list_stmt->execute();
$videos = $list_stmt->fetchAll(); $videos = $list_stmt->fetchAll();
$youtube_timestamp_candidates = media_youtube_timestamp_candidate_count(); $youtube_timestamp_candidates = media_youtube_timestamp_candidate_count();
$external_view_candidates = media_external_view_count_candidate_count(); $external_view_candidates = media_external_view_count_candidate_count();
$imgur_view_candidates = media_imgur_view_count_candidate_count();
$admin_total_views = total_video_views(false); $admin_total_views = total_video_views(false);
$qs = fn($p) => '?' . http_build_query(array_filter(['q' => $search, 'page' => $p])); $qs = fn($p) => '?' . http_build_query(array_filter(['q' => $search, 'page' => $p]));
@@ -241,7 +242,8 @@ render_head('Admin — TyClifford.com', '
<h2 class="card-title">Correct external view counts</h2> <h2 class="card-title">Correct external view counts</h2>
<p class="hint"> <p class="hint">
Scrapes readable counts from supported providers and adds them to the media-site totals alongside local plays. Scrapes readable counts from supported providers and adds them to the media-site totals alongside local plays.
<?= $external_view_candidates ?> external video<?= $external_view_candidates === 1 ? '' : 's' ?> can be checked. <?= $external_view_candidates ?> external video<?= $external_view_candidates === 1 ? '' : 's' ?> can be checked,
including <?= $imgur_view_candidates ?> Imgur-backed item<?= $imgur_view_candidates === 1 ? '' : 's' ?>.
Current database total: <?= h(view_count_label((int)$admin_total_views)) ?>. Current database total: <?= h(view_count_label((int)$admin_total_views)) ?>.
</p> </p>
</div> </div>
+43 -2
View File
@@ -543,8 +543,12 @@ function media_imgur_page_url(string $url): string {
} }
function media_imgur_embed_url(string $url): string { function media_imgur_embed_url(string $url): string {
$page = media_imgur_page_url($url); $path = media_imgur_path_from_url($url);
return $page !== '' ? rtrim($page, '/') . '/embed' : ''; if ($path === '') return '';
return media_append_query(public_url_path('/provider_embed.php'), [
'provider' => 'imgur',
'id' => $path,
]);
} }
function media_normalize_datetime(string $value): string { function media_normalize_datetime(string $value): string {
@@ -677,6 +681,26 @@ function media_view_count_from_html(string $html): int {
return 0; return 0;
} }
function media_imgur_view_count_from_html(string $html): int {
$count = media_view_count_from_html($html);
if ($count > 0) return $count;
$html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$patterns = [
'/\bviews?\b[^0-9]{0,80}([0-9][0-9,.\s]*\s*[KMBkmb]?)/i',
'/([0-9][0-9,.\s]*\s*[KMBkmb]?)\s+\bviews?\b/i',
'/"views_count"\s*:\s*"?(\d+)"?/i',
'/"viewsCount"\s*:\s*"?(\d+)"?/i',
];
foreach ($patterns as $pattern) {
if (!preg_match($pattern, $html, $match)) continue;
$count = media_count_from_text($match[1]);
if ($count > 0) return $count;
}
return 0;
}
function media_youtube_view_count_from_html(string $html): int { function media_youtube_view_count_from_html(string $html): int {
return media_view_count_from_html($html); return media_view_count_from_html($html);
} }
@@ -708,6 +732,10 @@ function media_external_view_count_from_url(string $url): int {
$fetch = media_http_get($url, 15, 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'); $fetch = media_http_get($url, 15, 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');
if ($fetch['body'] === '' || $fetch['error'] !== '') return 0; if ($fetch['body'] === '' || $fetch['error'] !== '') return 0;
if (media_host_matches($host, 'imgur.com') || media_host_matches($host, 'i.imgur.com')) {
return media_imgur_view_count_from_html($fetch['body']);
}
return media_view_count_from_html($fetch['body']); return media_view_count_from_html($fetch['body']);
} }
@@ -784,6 +812,19 @@ function media_external_view_count_candidate_count(): int {
")->fetchColumn(); ")->fetchColumn();
} }
function media_imgur_view_count_candidate_count(): int {
return (int)get_db()->query("
SELECT COUNT(DISTINCT v.id)
FROM videos v
INNER JOIN video_sources vs ON vs.video_id = v.id
WHERE vs.source_type='remote'
AND (
vs.source_url LIKE '%imgur.com%'
OR vs.source_url LIKE '%i.imgur.com%'
)
")->fetchColumn();
}
function media_restore_external_view_counts(int $limit = 25): array { function media_restore_external_view_counts(int $limit = 25): array {
$result = [ $result = [
'checked' => 0, 'checked' => 0,
+68
View File
@@ -0,0 +1,68 @@
<?php
require_once __DIR__ . '/includes/db.php';
$provider = strtolower(trim((string)($_GET['provider'] ?? '')));
$id = trim((string)($_GET['id'] ?? ''));
if ($provider !== 'imgur') {
http_response_code(404);
exit('Unsupported provider.');
}
$parts = array_values(array_filter(explode('/', $id), 'strlen'));
$data_id = '';
if (isset($parts[0], $parts[1]) && in_array($parts[0], ['a', 'gallery'], true)) {
$clean = media_clean_token($parts[1]);
if ($clean !== '') $data_id = $parts[0] . '/' . $clean;
} elseif (isset($parts[0])) {
$clean = media_clean_token($parts[0]);
if ($clean !== '') $data_id = $clean;
}
if ($data_id === '') {
http_response_code(400);
exit('Invalid Imgur item.');
}
$imgur_url = 'https://imgur.com/' . $data_id;
$title = 'Imgur media';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer-when-downgrade">
<title><?= h($title) ?></title>
<style>
html, body {
margin:0;
width:100%;
min-height:100%;
background:#000;
color:#fff;
font-family:Inter, system-ui, sans-serif;
}
body {
display:flex;
align-items:center;
justify-content:center;
overflow:auto;
}
.imgur-embed-pub {
width:100%;
max-width:100%;
margin:0 auto !important;
}
.imgur-embed-pub iframe {
max-width:100% !important;
}
</style>
</head>
<body>
<blockquote class="imgur-embed-pub" lang="en" data-id="<?= h($data_id) ?>">
<a href="<?= h($imgur_url) ?>" target="_blank" rel="noopener">View on Imgur</a>
</blockquote>
<script async src="https://s.imgur.com/min/embed.js" charset="utf-8"></script>
</body>
</html>