prepare("SELECT * FROM video_sources WHERE id=? AND video_id=?"); $src->execute([$sid, $id]); $s = $src->fetch(); if ($s) { if (source_is_local($s)) { $f = MEDIA_DIR . $s['file_path']; if ($s['file_path'] && file_exists($f)) unlink($f); } $db->prepare("DELETE FROM video_sources WHERE id=?")->execute([$sid]); $msg = 'Source removed.'; } $video = get_video_by_id($id); // refresh } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? 'save'; if ($action === 'save') { $title = trim($_POST['title'] ?? ''); $description = trim($_POST['description'] ?? ''); $duration = (int)($_POST['duration'] ?? 0); $sort_order = (int)($_POST['sort_order'] ?? 0); $published = isset($_POST['published']) ? 1 : 0; if (!$title) { $error = 'Title is required.'; } else { $slug = make_slug($title, $id); // Handle thumbnail replacement $thumbnail = $video['thumbnail']; if (!empty($_FILES['thumbnail']['tmp_name'])) { $ext = strtolower(pathinfo($_FILES['thumbnail']['name'], PATHINFO_EXTENSION)); $allowed = ['jpg','jpeg','png','webp','gif']; if (in_array($ext, $allowed)) { $thumb_dir = MEDIA_DIR . 'thumbs/'; if (!is_dir($thumb_dir)) mkdir($thumb_dir, 0755, true); // Remove old thumb if ($thumbnail && file_exists(MEDIA_DIR . $thumbnail)) unlink(MEDIA_DIR . $thumbnail); $fname = $slug . '_thumb_' . time() . '.' . $ext; if (move_uploaded_file($_FILES['thumbnail']['tmp_name'], $thumb_dir . $fname)) { $thumbnail = 'thumbs/' . $fname; } } else { $error = 'Thumbnail must be JPG, PNG, WebP, or GIF.'; } } if (!$error) { $db->prepare(" UPDATE videos SET title=?, description=?, slug=?, thumbnail=?, duration=?, sort_order=?, published=?, updated_at=datetime('now') WHERE id=? ")->execute([$title, $description, $slug, $thumbnail, $duration, $sort_order, $published, $id]); // Update existing sources sort/label/quality $upd_labels = $_POST['src_label'] ?? []; $upd_quality = $_POST['src_quality'] ?? []; $upd_sort = $_POST['src_sort'] ?? []; $upd_urls = $_POST['src_url'] ?? []; $upd_types = $_POST['src_type'] ?? []; $upd_ids = $_POST['src_id'] ?? []; foreach ($upd_ids as $i => $sid) { $source_type = ($upd_types[$i] ?? 'local') === 'remote' ? 'remote' : 'local'; $source_url = ''; if ($source_type === 'remote') { $source_url = normalize_media_url($upd_urls[$i] ?? ''); if ($source_url === '') { $error = 'External video URLs must start with http:// or https://.'; break; } } $params = [ $upd_labels[$i] ?? '', $upd_quality[$i] ?? '', (int)($upd_sort[$i] ?? $i), ]; $sql = "UPDATE video_sources SET label=?, quality=?, sort_order=?"; if ($source_type === 'remote') { $sql .= ", source_url=?, mime_type=?"; $params[] = $source_url; $params[] = source_storage_mime_from_url($source_url); } $sql .= " WHERE id=? AND video_id=?"; $params[] = (int)$sid; $params[] = $id; $db->prepare($sql)->execute($params); } // Add new sources if (!$error) { $new_types = $_POST['new_source_type'] ?? []; $new_labels = $_POST['new_source_label'] ?? []; $new_quality = $_POST['new_source_quality'] ?? []; $new_sort = $_POST['new_source_sort'] ?? []; $new_urls = $_POST['new_source_url'] ?? []; $new_files = $_FILES['new_source_file'] ?? []; $videos_dir = MEDIA_DIR . 'videos/'; if (!is_dir($videos_dir)) mkdir($videos_dir, 0755, true); $allowed_video = ['mp4','webm','ogv','ogg','mov','mkv','avi']; $new_count = max( count($new_types), count($new_labels), count($new_urls), isset($new_files['tmp_name']) && is_array($new_files['tmp_name']) ? count($new_files['tmp_name']) : 0 ); $moved_files = []; $inserted_source_ids = []; for ($i = 0; $i < $new_count; $i++) { $type = ($new_types[$i] ?? 'local') === 'remote' ? 'remote' : 'local'; $label = trim($new_labels[$i] ?? ''); $quality = trim($new_quality[$i] ?? '720p'); $sort = (int)($new_sort[$i] ?? $i); if ($type === 'remote') { $url = normalize_media_url($new_urls[$i] ?? ''); if ($url === '') { if (trim($new_urls[$i] ?? '') !== '') $error = 'External video URLs must start with http:// or https://.'; continue; } $db->prepare(" INSERT INTO video_sources (video_id, label, source_type, file_path, source_url, mime_type, quality, sort_order) VALUES (?,?,?,?,?,?,?,?) ")->execute([$id, $label, 'remote', '', $url, source_storage_mime_from_url($url), $quality, $sort]); $inserted_source_ids[] = (int)$db->lastInsertId(); continue; } $tmp = $new_files['tmp_name'][$i] ?? ''; if (empty($tmp) || !is_uploaded_file($tmp)) continue; $orig = $new_files['name'][$i] ?? ''; $ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION)); if (!in_array($ext, $allowed_video, true)) continue; $fname = $slug . '_' . time() . '_' . $i . '.' . $ext; if (move_uploaded_file($tmp, $videos_dir . $fname)) { $moved_files[] = $videos_dir . $fname; $db->prepare(" INSERT INTO video_sources (video_id, label, source_type, file_path, source_url, mime_type, quality, sort_order) VALUES (?,?,?,?,?,?,?,?) ")->execute([$id, $label, 'local', 'videos/' . $fname, '', mime_from_ext($fname), $quality, $sort]); $inserted_source_ids[] = (int)$db->lastInsertId(); } } if ($error) { foreach ($inserted_source_ids as $inserted_id) { $db->prepare("DELETE FROM video_sources WHERE id=? AND video_id=?")->execute([$inserted_id, $id]); } foreach ($moved_files as $moved) { if (file_exists($moved)) unlink($moved); } } else { header('Location: edit.php?id=' . $id . '&saved=1'); exit; } } } } } } // Refresh $video = get_video_by_id($id); render_head('Edit Video — Admin', ' .form-grid { display:grid; grid-template-columns:1fr; gap:1.2rem; } @media(min-width:700px) { .form-grid { grid-template-columns:1fr 1fr; } } .form-grid .span2 { grid-column:1/-1; } .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; } .source-list { display:flex; flex-direction:column; gap:.75rem; } .source-item { background:var(--surface-2); border:1px solid var(--border); border-radius:var(--r); padding:.85rem 1rem; } .source-item-header { display:flex; align-items:center; justify-content:space-between; gap:.75rem; margin-bottom:.75rem; flex-wrap:wrap; } .source-item-file { font-family:"Share Tech Mono",monospace; font-size:.68rem; color:var(--text-2); word-break:break-all; } .source-item-mime { font-family:"Share Tech Mono",monospace; font-size:.6rem; color:var(--text-3); } .source-item-fields { display:grid; grid-template-columns:1.5fr 1fr 1fr 80px; gap:.6rem; } .source-item-fields .span4 { grid-column:1/-1; } @media(max-width:760px) { .source-item-fields { grid-template-columns:1fr 1fr; } } @media(max-width:560px) { .source-item-fields { grid-template-columns:1fr; } } .source-badge { display:inline-flex; align-items:center; padding:.16em .5em; border-radius:99px; font-family:"Share Tech Mono",monospace; font-size:.56rem; letter-spacing:.08em; text-transform:uppercase; border:1px solid var(--border-2); color:var(--text-3); margin-bottom:.25rem; } .source-badge-remote { color:var(--blue); border-color:rgba(0,200,255,.35); background:rgba(0,200,255,.08); } .source-badge-local { color:var(--green); border-color:rgba(0,232,122,.3); background:rgba(0,232,122,.08); } .add-source-row { background:var(--surface-2); border:1px solid var(--border-2); border-radius:var(--r); padding:1rem; display:grid; grid-template-columns:140px minmax(220px,1.5fr) 1fr 1fr 80px auto; gap:.75rem; align-items:end; } @media(max-width:900px) { .add-source-row { grid-template-columns:1fr 1fr; } } @media(max-width:560px) { .add-source-row { grid-template-columns:1fr; } } .source-location-panel[hidden] { display:none; } .admin-layout { display:grid; grid-template-columns:1fr; gap:1.5rem; } @media(min-width:900px) { .admin-layout { grid-template-columns:220px 1fr; } } .admin-nav { background:var(--surface); border:1px solid var(--border); border-radius:calc(var(--r)+2px); padding:1.2rem; display:flex; flex-direction:column; gap:.4rem; align-self:start; position:sticky; top:1rem; } .admin-nav-label { font-family:"Share Tech Mono",monospace; font-size:.58rem; letter-spacing:.18em; text-transform:uppercase; color:var(--text-3); margin-bottom:.3rem; padding-left:.5rem; } .admin-nav a { display:flex; align-items:center; gap:.6rem; padding:.55rem .75rem; border-radius:var(--r); font-size:.82rem; color:var(--text-2); text-decoration:none; transition:background .15s,color .15s; } .admin-nav a:hover { background:rgba(255,255,255,.05); color:var(--text); } .nav-divider { border:none; border-top:1px solid var(--border); margin:.4rem 0; } .thumb-preview { width:100%; max-width:240px; aspect-ratio:16/9; object-fit:cover; border-radius:var(--r); border:1px solid var(--border); background:var(--surface-2); display:block; margin-bottom:.5rem; } '); ?> Ty Clifford admin / edit video Preview Logout = h($msg) ?> = h($error) ?> Editing = h($video['title']) ?> ← Back Video Details Title * Description = h($video['description']) ?> Slug (auto-generated) Duration (seconds) Sort Order Published > Published Thumbnail Current: = h($video['thumbnail']) ?> Upload new image to replace current thumbnail. Existing Sources $s): ?> = h(source_provider_label($s)) ?> = h(source_location($s)) ?> = h($s['mime_type']) ?> Remove External URL Quality >= $q ?: '—' ?> Label Sort Add New Sources Upload another file, link a direct media file, or paste a provider page URL such as YouTube or Vimeo. Source Upload External URL / Embed Video File External URL Quality 1080p 720p 480p 360p 4K — Label Sort Add Another Source Save Changes Cancel
Editing
Video Details
Existing Sources
Add New Sources
Upload another file, link a direct media file, or paste a provider page URL such as YouTube or Vimeo.