- Done. The media platform now tracks sources as either local uploads or remote URLs.

Changed:
[includes/db.php (line 
43)](/Users/tyemeclifford/Documents/GH/mediaplayer/includes/db.php:43) 
adds source_type and source_url, with an automatic migration for 
existing SQLite databases.
[admin/add.php (line 
260)](/Users/tyemeclifford/Documents/GH/mediaplayer/admin/add.php:260) 
and [admin/edit.php (line 
383)](/Users/tyemeclifford/Documents/GH/mediaplayer/admin/edit.php:383) 
now let each source be an upload or an external URL.
[index.php (line 
226)](/Users/tyemeclifford/Documents/GH/mediaplayer/index.php:226) and 
[embed.php (line 
193)](/Users/tyemeclifford/Documents/GH/mediaplayer/embed.php:193) 
resolve playback URLs based on where the source lives.
[catalogue.php (line 
79)](/Users/tyemeclifford/Documents/GH/mediaplayer/catalogue.php:79) now 
has public search for published videos only.
[README.md (line 
63)](/Users/tyemeclifford/Documents/GH/mediaplayer/README.md:63) 
documents local vs remote sources and public search.
This commit is contained in:
Ty Clifford
2026-06-24 11:58:29 -04:00
parent 7c33de810f
commit b24f0dc416
9 changed files with 392 additions and 98 deletions
+85 -8
View File
@@ -44,7 +44,9 @@ function get_db(): PDO {
id INTEGER PRIMARY KEY AUTOINCREMENT,
video_id INTEGER NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
label TEXT NOT NULL DEFAULT '',
file_path TEXT NOT NULL,
source_type TEXT NOT NULL DEFAULT 'local',
file_path TEXT NOT NULL DEFAULT '',
source_url TEXT NOT NULL DEFAULT '',
mime_type TEXT NOT NULL DEFAULT 'video/mp4',
quality TEXT NOT NULL DEFAULT '720p',
sort_order INTEGER NOT NULL DEFAULT 0
@@ -52,9 +54,13 @@ function get_db(): PDO {
CREATE INDEX IF NOT EXISTS idx_videos_published ON videos(published);
CREATE INDEX IF NOT EXISTS idx_videos_sort ON videos(sort_order, id);
CREATE INDEX IF NOT EXISTS idx_videos_title ON videos(title);
CREATE INDEX IF NOT EXISTS idx_sources_video ON video_sources(video_id);
");
ensure_video_source_schema($pdo);
$pdo->exec("CREATE INDEX IF NOT EXISTS idx_sources_type ON video_sources(source_type)");
// Default settings
$defaults = [
'catalogue_per_page' => '10',
@@ -68,6 +74,20 @@ function get_db(): PDO {
return $pdo;
}
function ensure_video_source_schema(PDO $pdo): void {
$cols = $pdo->query("PRAGMA table_info(video_sources)")->fetchAll();
$names = array_column($cols, 'name');
if (!in_array('source_type', $names, true)) {
$pdo->exec("ALTER TABLE video_sources ADD COLUMN source_type TEXT NOT NULL DEFAULT 'local'");
}
if (!in_array('source_url', $names, true)) {
$pdo->exec("ALTER TABLE video_sources ADD COLUMN source_url TEXT NOT NULL DEFAULT ''");
}
$pdo->exec("UPDATE video_sources SET source_type='local' WHERE source_type='' OR source_type IS NULL");
}
function setting(string $key, string $fallback = ''): string {
$row = get_db()->prepare("SELECT value FROM settings WHERE key=?");
$row->execute([$key]);
@@ -94,18 +114,55 @@ function make_slug(string $title, int $id = 0): string {
}
function mime_from_ext(string $path): string {
$url_path = parse_url($path, PHP_URL_PATH);
if (is_string($url_path) && $url_path !== '') {
$path = $url_path;
}
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
return match($ext) {
'mp4' => 'video/mp4',
'm4v' => 'video/mp4',
'webm' => 'video/webm',
'ogv' => 'video/ogg',
'ogg' => 'video/ogg',
'mov' => 'video/quicktime',
'mkv' => 'video/x-matroska',
'avi' => 'video/x-msvideo',
'm3u8' => 'application/vnd.apple.mpegurl',
'mpd' => 'application/dash+xml',
default => 'video/mp4',
};
}
function normalize_media_url(string $url): string {
$url = trim($url);
if ($url === '') return '';
if (str_starts_with($url, '//')) $url = 'https:' . $url;
if (!filter_var($url, FILTER_VALIDATE_URL)) return '';
$scheme = strtolower((string)parse_url($url, PHP_URL_SCHEME));
return in_array($scheme, ['http', 'https'], true) ? $url : '';
}
function source_is_remote(array $source): bool {
return ($source['source_type'] ?? 'local') === 'remote';
}
function source_is_local(array $source): bool {
return !source_is_remote($source);
}
function source_location(array $source): string {
return source_is_remote($source) ? ($source['source_url'] ?? '') : ($source['file_path'] ?? '');
}
function source_public_url(array $source, string $media_base = MEDIA_URL): string {
if (source_is_remote($source)) {
return $source['source_url'] ?? '';
}
return rtrim($media_base, '/') . '/' . ltrim($source['file_path'] ?? '', '/');
}
function format_duration(int $seconds): string {
if ($seconds <= 0) return '';
$h = intdiv($seconds, 3600);
@@ -119,27 +176,47 @@ function h(string $s): string {
return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function get_videos_paginated(int $page, int $per_page, bool $published_only = true): array {
function get_videos_paginated(int $page, int $per_page, bool $published_only = true, string $search = ''): array {
$db = get_db();
$where = $published_only ? 'WHERE v.published=1' : '';
$where_parts = [];
$bind = [];
$search = trim($search);
if ($published_only) {
$where_parts[] = 'v.published=1';
}
if ($search !== '') {
$where_parts[] = '(v.title LIKE :q OR v.description LIKE :q OR v.slug LIKE :q)';
$bind[':q'] = '%' . $search . '%';
}
$where = $where_parts ? 'WHERE ' . implode(' AND ', $where_parts) : '';
$count = $db->prepare("SELECT COUNT(*) FROM videos v $where");
foreach ($bind as $key => $value) $count->bindValue($key, $value);
$count->execute();
$total = $count->fetchColumn();
$total_pages = (int)ceil($total / $per_page);
if ($total_pages > 0) {
$page = min($page, $total_pages);
}
$offset = ($page - 1) * $per_page;
$total = $db->query("SELECT COUNT(*) FROM videos v $where")->fetchColumn();
$rows = $db->prepare("
SELECT v.*, GROUP_CONCAT(vs.file_path,'||') AS source_paths
SELECT v.*, GROUP_CONCAT(CASE WHEN vs.source_type='remote' THEN vs.source_url ELSE vs.file_path END,'||') AS source_paths
FROM videos v
LEFT JOIN video_sources vs ON vs.video_id = v.id
$where
GROUP BY v.id
ORDER BY v.sort_order ASC, v.id DESC
LIMIT ? OFFSET ?
LIMIT :limit OFFSET :offset
");
$rows->execute([$per_page, $offset]);
foreach ($bind as $key => $value) $rows->bindValue($key, $value);
$rows->bindValue(':limit', $per_page, PDO::PARAM_INT);
$rows->bindValue(':offset', $offset, PDO::PARAM_INT);
$rows->execute();
return [
'videos' => $rows->fetchAll(),
'total' => (int)$total,
'page' => $page,
'per_page' => $per_page,
'total_pages' => (int)ceil($total / $per_page),
'total_pages' => $total_pages,
];
}