- Implemented:

Added view_count database migration/defaults and shared helpers in 
[includes/db.php](/Users/tyemeclifford/Documents/GH/mediaplayer/includes/db.php).
Public player and embed.php now increment views.
Homepage and catalogue can display per-video views, total views, and 
public stats based on admin settings.
Added Admin → Settings toggles for public view/stat display.
Added Admin → Upkeeping action to restore external view counts from 
readable provider pages, with YouTube scraping support first.
Added manual per-video view-count editing in 
[admin/edit.php](/Users/tyemeclifford/Documents/GH/mediaplayer/admin/edit.php) 
for older local uploads or corrections.
Updated 
[README.md](/Users/tyemeclifford/Documents/GH/mediaplayer/README.md) 
with the new behavior.
This commit is contained in:
Ty Clifford
2026-06-25 04:16:34 -04:00
parent dc71d26c2a
commit 86d1fedcb3
8 changed files with 472 additions and 29 deletions
+59 -19
View File
@@ -49,6 +49,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($result['not_found']) $msg .= ', ' . $result['not_found'] . ' without a readable original date';
$msg .= '.';
}
if ($action === 'restore_external_view_counts') {
$limit = max(1, min(100, (int)($_POST['external_view_limit'] ?? 25)));
$result = media_restore_external_view_counts($limit);
$msg = 'Upkeeping checked ' . $result['checked'] . ' external video' . ($result['checked'] === 1 ? '' : 's') . ': ';
$msg .= $result['restored'] . ' restored';
if ($result['already_current']) $msg .= ', ' . $result['already_current'] . ' already current';
if ($result['not_found']) $msg .= ', ' . $result['not_found'] . ' without a readable external count';
$msg .= '.';
}
}
// Search + pagination
@@ -68,7 +78,7 @@ $offset = ($page - 1) * $per_page;
$list_stmt = $db->prepare("
SELECT v.id, v.title, v.slug, v.thumbnail, v.duration, v.published, v.sort_order,
v.created_at,
v.created_at, v.view_count,
COUNT(vs.id) AS source_count,
SUM(CASE WHEN vs.source_type='remote' THEN 1 ELSE 0 END) AS remote_count
FROM videos v
@@ -84,6 +94,8 @@ $list_stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$list_stmt->execute();
$videos = $list_stmt->fetchAll();
$youtube_timestamp_candidates = media_youtube_timestamp_candidate_count();
$external_view_candidates = media_external_view_count_candidate_count();
$admin_total_views = total_video_views(false);
$qs = fn($p) => '?' . http_build_query(array_filter(['q' => $search, 'page' => $p]));
@@ -143,7 +155,9 @@ render_head('Admin — TyClifford.com', '
.rec-count { font-family:"Share Tech Mono",monospace; font-size:.63rem; color:var(--text-3); letter-spacing:.06em; }
.section-heading { font-family:"Share Tech Mono",monospace; font-size:.65rem; letter-spacing:.15em; text-transform:uppercase; color:var(--text-3); margin-bottom:.75rem; }
.hint { font-size:.72rem; color:var(--text-3); margin-top:.2rem; line-height:1.5; }
.upkeep-stack { display:flex; flex-direction:column; gap:1rem; }
.upkeep-row { display:flex; align-items:flex-end; justify-content:space-between; gap:1rem; flex-wrap:wrap; }
.upkeep-row + .upkeep-row { border-top:1px solid var(--border); padding-top:1rem; }
.upkeep-copy { max-width:620px; }
.upkeep-form { display:flex; align-items:flex-end; gap:.65rem; flex-wrap:wrap; }
.upkeep-form .form-group { min-width:120px; }
@@ -202,24 +216,46 @@ render_head('Admin — TyClifford.com', '
<div class="card">
<p class="section-heading">Upkeeping</p>
<div class="upkeep-row">
<div class="upkeep-copy">
<h2 class="card-title">Correct YouTube timestamps</h2>
<p class="hint">
Fetches original YouTube publish dates and updates the database timestamp shown in the admin list and public catalogue.
<?= $youtube_timestamp_candidates ?> YouTube-backed video<?= $youtube_timestamp_candidates === 1 ? '' : 's' ?> currently match this upkeep task.
</p>
</div>
<form class="upkeep-form" method="post" action="index.php">
<input type="hidden" name="action" value="correct_youtube_timestamps">
<div class="form-group">
<label class="form-label" for="timestamp_limit">Batch Size</label>
<input class="form-input" type="number" id="timestamp_limit" name="timestamp_limit" value="25" min="1" max="100">
<div class="upkeep-stack">
<div class="upkeep-row">
<div class="upkeep-copy">
<h2 class="card-title">Correct YouTube timestamps</h2>
<p class="hint">
Fetches original YouTube publish dates and updates the database timestamp shown in the admin list and public catalogue.
<?= $youtube_timestamp_candidates ?> YouTube-backed video<?= $youtube_timestamp_candidates === 1 ? '' : 's' ?> currently match this upkeep task.
</p>
</div>
<button class="btn btn-secondary btn-sm" type="submit">
Correct Dates
</button>
</form>
<form class="upkeep-form" method="post" action="index.php">
<input type="hidden" name="action" value="correct_youtube_timestamps">
<div class="form-group">
<label class="form-label" for="timestamp_limit">Batch Size</label>
<input class="form-input" type="number" id="timestamp_limit" name="timestamp_limit" value="25" min="1" max="100">
</div>
<button class="btn btn-secondary btn-sm" type="submit">
Correct Dates
</button>
</form>
</div>
<div class="upkeep-row">
<div class="upkeep-copy">
<h2 class="card-title">Restore external view counts</h2>
<p class="hint">
Scrapes readable counts from supported external providers and stores the higher value between the provider and local database.
<?= $external_view_candidates ?> external video<?= $external_view_candidates === 1 ? '' : 's' ?> can be checked.
Current database total: <?= h(view_count_label((int)$admin_total_views)) ?>.
</p>
</div>
<form class="upkeep-form" method="post" action="index.php">
<input type="hidden" name="action" value="restore_external_view_counts">
<div class="form-group">
<label class="form-label" for="external_view_limit">Batch Size</label>
<input class="form-input" type="number" id="external_view_limit" name="external_view_limit" value="25" min="1" max="100">
</div>
<button class="btn btn-secondary btn-sm" type="submit">
Restore Views
</button>
</form>
</div>
</div>
</div>
@@ -253,6 +289,7 @@ render_head('Admin — TyClifford.com', '
<th class="title-cell">Title / Slug</th>
<th>Duration</th>
<th>Sources</th>
<th>Views</th>
<th>Status</th>
<th>Date</th>
<th class="actions-cell">Actions</th>
@@ -260,7 +297,7 @@ render_head('Admin — TyClifford.com', '
</thead>
<tbody>
<?php if (empty($videos)): ?>
<tr><td colspan="7" style="text-align:center;padding:3rem;font-family:'Share Tech Mono',monospace;font-size:.7rem;color:var(--text-3);">
<tr><td colspan="8" style="text-align:center;padding:3rem;font-family:'Share Tech Mono',monospace;font-size:.7rem;color:var(--text-3);">
<?= $search ? 'No videos match "' . h($search) . '"' : 'No videos yet. <a href="add.php" style="color:var(--purple)">Add one!</a>' ?>
</td></tr>
<?php else: ?>
@@ -286,6 +323,9 @@ render_head('Admin — TyClifford.com', '
<span style="color:var(--text-3);"> · <?= (int)$v['remote_count'] ?> URL<?= (int)$v['remote_count'] != 1 ? 's' : '' ?></span>
<?php endif; ?>
</td>
<td style="font-family:'Share Tech Mono',monospace;font-size:.7rem;color:var(--text-2);">
<?= h(format_count((int)$v['view_count'])) ?>
</td>
<td>
<span class="badge <?= $v['published'] ? 'badge-green' : 'badge-gray' ?>">
<?= $v['published'] ? 'Published' : 'Draft' ?>