Files
SunShineMusic/admin_music_upload.php
2025-09-24 14:14:45 +00:00

305 lines
15 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// 管理员音乐审核页面支持BV号显示和时长验证
require_once 'db_config.php';
$message = '';
$message_type = '';
// 获取音频实际时长的函数
function getActualAudioDuration($file_path) {
// 方法1: 使用ffmpeg推荐更准确
if (function_exists('shell_exec')) {
$command = "ffmpeg -i " . escapeshellarg($file_path) . " 2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//";
$output = shell_exec($command);
if ($output) {
$duration = trim($output);
// 转换为 MM:SS 格式
list($hours, $minutes, $seconds) = explode(':', $duration);
$seconds = floor((float)$seconds);
if ((int)$hours > 0) {
$minutes = (int)$minutes + (int)$hours * 60;
}
return sprintf('%d:%02d', $minutes, $seconds);
}
}
// 方法2: 使用getid3库如果安装了
if (class_exists('getID3')) {
$getID3 = new getID3;
$fileInfo = $getID3->analyze($file_path);
if (!empty($fileInfo['playtime_seconds'])) {
$seconds = floor($fileInfo['playtime_seconds']);
$minutes = floor($seconds / 60);
$seconds = $seconds % 60;
return sprintf('%d:%02d', $minutes, $seconds);
}
}
return false; // 无法获取时长
}
// 处理审核操作
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['approve_id'])) {
$id = $_POST['approve_id'];
try {
// 1. 获取待审核音乐信息包含BV号和用户输入的时长
$stmt = $conn->prepare("SELECT * FROM pending_music WHERE id = ?");
$stmt->execute([$id]);
$music = $stmt->fetch(PDO::FETCH_ASSOC);
if ($music) {
// 2. 验证音频时长(如果可能)
$duration_mismatch = false;
$actual_duration = getActualAudioDuration($music['file_path']);
if ($actual_duration && $actual_duration !== $music['duration']) {
// 时长不匹配,设置标志但仍继续处理(只是提醒管理员)
$duration_mismatch = true;
$message .= "注意:用户填写的时长({$music['duration']})与实际音频时长({$actual_duration})不匹配。";
}
// 3. 将信息插入正式音乐表包含BV号字段
// 如果有实际时长,使用实际时长覆盖用户输入
$final_duration = $actual_duration ?: $music['duration'];
$stmt_insert = $conn->prepare("INSERT INTO music
(title, artist, category, description, file_path, duration, upload_time, uploader_name, bvid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt_insert->execute([
$music['title'], $music['artist'], $music['category'],
$music['description'], $music['file_path'], $final_duration,
$music['upload_time'], $music['uploader_name'], $music['bvid'] ?? ''
]);
// 4. 从待审核表中删除
$stmt_delete = $conn->prepare("DELETE FROM pending_music WHERE id = ?");
$stmt_delete->execute([$id]);
// 构建成功消息
$base_message = "音乐《" . htmlspecialchars($music['title']) . "》已通过审核!";
if ($duration_mismatch) {
$message = $base_message . " " . $message;
} else {
$message = $base_message;
}
$message_type = "success";
}
} catch (PDOException $e) {
$message = "数据库错误: " . $e->getMessage();
$message_type = "error";
}
} elseif (isset($_POST['reject_id'])) {
$id = $_POST['reject_id'];
try {
// 获取文件路径用于删除
$stmt = $conn->prepare("SELECT file_path, title FROM pending_music WHERE id = ?");
$stmt->execute([$id]);
$music = $stmt->fetch(PDO::FETCH_ASSOC);
if ($music) {
// 删除服务器上的文件
if (file_exists($music['file_path'])) {
unlink($music['file_path']);
}
// 从待审核表中删除记录
$stmt_delete = $conn->prepare("DELETE FROM pending_music WHERE id = ?");
$stmt_delete->execute([$id]);
$message = "音乐《" . htmlspecialchars($music['title']) . "》已驳回并删除。";
$message_type = "success";
}
} catch (PDOException $e) {
$message = "数据库错误: " . $e->getMessage();
$message_type = "error";
}
}
}
// 获取所有待审核音乐包含BV号并计算实际时长
try {
$stmt = $conn->query("SELECT * FROM pending_music ORDER BY upload_time DESC");
$pending_music = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 为每条音乐获取实际时长并检查是否匹配
foreach ($pending_music as &$music) {
$actual_duration = getActualAudioDuration($music['file_path']);
$music['actual_duration'] = $actual_duration;
$music['duration_match'] = ($actual_duration === $music['duration']);
}
unset($music); // 解除引用
} catch (PDOException $e) {
$message = "无法加载待审核列表: " . $e->getMessage();
$message_type = "error";
$pending_music = [];
}
$categories = [
'cantonese' => '粤语歌曲', 'mandarin' => '国语歌曲', 'waiyu' => '外语歌曲',
'classic' => '经典老歌', 'other' => '其他音乐'
];
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理员审核 - 音乐分享平台</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body { font-family: 'Arial', sans-serif; background-color: #f7f9fc; margin: 0; }
.container { max-width: 1600px; margin: 2rem auto; padding: 0 1rem; }
h1 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 0.5rem; }
.message { padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center; }
.success { background-color: #d4edda; color: #155724; }
.error { background-color: #f8d7da; color: #721c24; }
.warning { background-color: #fff3cd; color: #856404; }
table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #f1f2f6; }
th { background-color: #2c3e50; color: white; }
tr:hover { background-color: #fafafa; }
.btn { padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem; transition: background-color 0.3s; margin-right: 5px; }
.btn-approve { background-color: #2ecc71; color: white; }
.btn-approve:hover { background-color: #27ae60; }
.btn-reject { background-color: #e74c3c; color: white; }
.btn-reject:hover { background-color: #c0392b; }
.audio-player { width: 100%; max-width: 250px; }
.action-form { display: inline; }
.header-actions { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
.back-link { color: #2c3e50; text-decoration: none; font-weight: 500; }
.back-link:hover { text-decoration: underline; }
.bvid-link { color: #3498db; text-decoration: none; }
.bvid-link:hover { text-decoration: underline; }
.no-bvid { color: #95a5a6; font-style: italic; }
.duration-match { color: #27ae60; font-weight: 500; }
.duration-mismatch { color: #e74c3c; font-weight: 500; }
.duration-info { display: flex; flex-direction: column; gap: 4px; }
.duration-note { font-size: 0.8rem; color: #7f8c8d; }
.tooltip { position: relative; display: inline-block; }
.tooltip .tooltip-text {
visibility: hidden;
width: 200px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
font-size: 0.8rem;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
</style>
</head>
<body>
<div class="container">
<div class="header-actions">
<h1><i class="fas fa-tasks"></i> 音乐审核管理</h1>
<a href="index.php" class="back-link"><i class="fas fa-arrow-left"></i> 返回首页</a>
</div>
<?php if ($message): ?>
<div class="message <?php echo $message_type; ?>"><?php echo $message; ?></div>
<?php endif; ?>
<?php if (empty($pending_music)): ?>
<p class="message success">当前没有待审核的音乐。</p>
<?php else: ?>
<table>
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>歌手</th>
<th>上传者</th>
<th>分类</th>
<th>BV号</th>
<th>时长信息</th> <!-- 改进为时长信息列,包含用户输入和实际时长 -->
<th>上传时间</th>
<th>预览</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($pending_music as $music): ?>
<tr>
<td><?php echo $music['id']; ?></td>
<td><?php echo htmlspecialchars($music['title']); ?></td>
<td><?php echo htmlspecialchars($music['artist']); ?></td>
<td><?php echo htmlspecialchars($music['uploader_name']); ?></td>
<td><?php echo $categories[$music['category']]; ?></td>
<td>
<?php if (!empty($music['bvid'])): ?>
<a href="https://www.bilibili.com/video/<?php echo htmlspecialchars($music['bvid']); ?>"
target="_blank" class="bvid-link">
<?php echo htmlspecialchars($music['bvid']); ?>
</a>
<?php else: ?>
<span class="no-bvid">无</span>
<?php endif; ?>
</td>
<td>
<div class="duration-info">
<?php if ($music['actual_duration']): ?>
<div>
用户输入: <span><?php echo $music['duration']; ?></span>
</div>
<div>
实际时长: <span class="<?php echo $music['duration_match'] ? 'duration-match' : 'duration-mismatch'; ?>">
<?php echo $music['actual_duration']; ?>
</span>
</div>
<?php if (!$music['duration_match']): ?>
<div class="tooltip">
<i class="fas fa-exclamation-triangle"></i>
<span class="tooltip-text">时长不匹配,通过审核后将使用实际时长</span>
</div>
<?php endif; ?>
<?php else: ?>
<div>
用户输入: <span><?php echo $music['duration']; ?></span>
</div>
<div class="duration-note">
<i class="fas fa-info-circle"></i> 无法获取实际音频时长
</div>
<?php endif; ?>
</div>
</td>
<td><?php echo $music['upload_time']; ?></td>
<td><audio controls class="audio-player">
<source src="<?php echo htmlspecialchars($music['file_path']); ?>" type="audio/mpeg">
您的浏览器不支持音频播放。
</audio></td>
<td>
<form class="action-form" method="post" onsubmit="
<?php if (!$music['duration_match'] && $music['actual_duration']): ?>
return confirm('注意:用户填写的时长与实际时长不匹配!\n用户输入: <?php echo $music['duration']; ?>\n实际时长: <?php echo $music['actual_duration']; ?>\n\n确定要通过这首音乐吗将使用实际时长。');
<?php else: ?>
return confirm('确定要通过这首音乐吗?');
<?php endif; ?>
">
<button type="submit" name="approve_id" value="<?php echo $music['id']; ?>" class="btn btn-approve"><i class="fas fa-check"></i> 通过</button>
</form>
<form class="action-form" method="post" onsubmit="return confirm('确定要驳回并删除这首音乐吗?此操作不可恢复!');">
<button type="submit" name="reject_id" value="<?php echo $music['id']; ?>" class="btn btn-reject"><i class="fas fa-times"></i> 驳回</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</body>
</html>