feat: Add video description support for Bilibili and YtDlp

This commit is contained in:
Peifan Li
2025-12-09 13:29:33 -05:00
parent cbdecc9dc2
commit 1de24668b2
6 changed files with 89 additions and 16 deletions

View File

@@ -18,6 +18,7 @@ export interface BilibiliVideoInfo {
date: string;
thumbnailUrl: string | null;
thumbnailSaved: boolean;
description?: string;
error?: string;
}
@@ -226,6 +227,7 @@ export class BilibiliDownloader {
if (response.data && response.data.data) {
const videoInfo = response.data.data;
thumbnailUrl = videoInfo.pic;
const description = videoInfo.desc || "";
console.log("Got video info from API:", {
title: videoInfo.title,
@@ -262,6 +264,7 @@ export class BilibiliDownloader {
date: new Date().toISOString().slice(0, 10).replace(/-/g, ""),
thumbnailUrl: thumbnailUrl,
thumbnailSaved,
description,
};
}
}
@@ -516,8 +519,8 @@ export class BilibiliDownloader {
// Set full paths for video and thumbnail
const videoPath = path.join(VIDEOS_DIR, videoFilename);
const thumbnailPath = path.join(IMAGES_DIR, thumbnailFilename);
let videoTitle, videoAuthor, videoDate, thumbnailUrl, thumbnailSaved;
let videoTitle, videoAuthor, videoDate, videoDescription, thumbnailUrl, thumbnailSaved;
let finalVideoFilename = videoFilename;
let finalThumbnailFilename = thumbnailFilename;
@@ -545,6 +548,7 @@ export class BilibiliDownloader {
videoDate =
bilibiliInfo.date ||
new Date().toISOString().slice(0, 10).replace(/-/g, "");
videoDescription = bilibiliInfo.description || "";
thumbnailUrl = bilibiliInfo.thumbnailUrl;
thumbnailSaved = bilibiliInfo.thumbnailSaved;
@@ -610,6 +614,7 @@ export class BilibiliDownloader {
id: timestamp.toString(),
title: videoTitle,
author: videoAuthor,
description: videoDescription,
date: videoDate,
source: "bilibili",
sourceUrl: url,

View File

@@ -174,7 +174,7 @@ export class YtDlpDownloader {
const videoFilename = `${safeBaseFilename}.mp4`;
const thumbnailFilename = `${safeBaseFilename}.jpg`;
let videoTitle, videoAuthor, videoDate, thumbnailUrl, thumbnailSaved, source;
let videoTitle, videoAuthor, videoDate, videoDescription, thumbnailUrl, thumbnailSaved, source;
let finalVideoFilename = videoFilename;
let finalThumbnailFilename = thumbnailFilename;
let subtitles: Array<{ language: string; filename: string; path: string }> = [];
@@ -205,6 +205,7 @@ export class YtDlpDownloader {
videoAuthor = customAuthor;
}
}
videoDescription = info.description || "";
videoDate =
info.upload_date ||
new Date().toISOString().slice(0, 10).replace(/-/g, "");
@@ -399,6 +400,7 @@ export class YtDlpDownloader {
id: timestamp.toString(),
title: videoTitle || "Video",
author: videoAuthor || "Unknown",
description: videoDescription,
date:
videoDate || new Date().toISOString().slice(0, 10).replace(/-/g, ""),
source: source, // Use extracted source

View File

@@ -2,22 +2,22 @@ import { and, desc, eq, lt } from "drizzle-orm";
import fs from "fs-extra";
import path from "path";
import {
DATA_DIR,
IMAGES_DIR,
STATUS_DATA_PATH,
SUBTITLES_DIR,
UPLOADS_DIR,
VIDEOS_DIR,
DATA_DIR,
IMAGES_DIR,
STATUS_DATA_PATH,
SUBTITLES_DIR,
UPLOADS_DIR,
VIDEOS_DIR,
} from "../config/paths";
import { db, sqlite } from "../db";
import {
collections,
collectionVideos,
downloadHistory,
downloads,
settings,
videoDownloads,
videos,
collections,
collectionVideos,
downloadHistory,
downloads,
settings,
videoDownloads,
videos,
} from "../db/schema";
import { formatVideoFilename } from "../utils/helpers";
@@ -33,6 +33,7 @@ export interface Video {
viewCount?: number;
progress?: number;
fileSize?: string;
description?: string;
[key: string]: any;
}
@@ -215,6 +216,14 @@ export function initializeStorage(): void {
console.log("Migration successful: subtitles added.");
}
if (!columns.includes("description")) {
console.log(
"Migrating database: Adding description column to videos table..."
);
sqlite.prepare("ALTER TABLE videos ADD COLUMN description TEXT").run();
console.log("Migration successful: description added.");
}
// Check downloads table columns
const downloadsTableInfo = sqlite
.prepare("PRAGMA table_info(downloads)")

View File

@@ -56,6 +56,16 @@ if [ -n "$VERSION" ]; then
$DOCKER_PATH push $FRONTEND_VERSION_TAG
fi
# Clean up local images
echo "🧹 Cleaning up local images..."
$DOCKER_PATH rmi $BACKEND_LATEST
$DOCKER_PATH rmi $FRONTEND_LATEST
if [ -n "$VERSION" ]; then
$DOCKER_PATH rmi $BACKEND_VERSION_TAG
$DOCKER_PATH rmi $FRONTEND_VERSION_TAG
fi
echo "✅ Successfully built and pushed images to Docker Hub!"
echo "Backend image: $BACKEND_LATEST"
echo "Frontend image: $FRONTEND_LATEST"

View File

@@ -75,6 +75,10 @@ const VideoInfo: React.FC<VideoInfoProps> = ({
const [showExpandButton, setShowExpandButton] = useState(false);
const titleRef = useRef<HTMLHeadingElement>(null);
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false);
const [showDescriptionExpandButton, setShowDescriptionExpandButton] = useState(false);
const descriptionRef = useRef<HTMLParagraphElement>(null);
useEffect(() => {
const checkOverflow = () => {
const element = titleRef.current;
@@ -88,6 +92,19 @@ const VideoInfo: React.FC<VideoInfoProps> = ({
return () => window.removeEventListener('resize', checkOverflow);
}, [video.title, isTitleExpanded]);
useEffect(() => {
const checkDescriptionOverflow = () => {
const element = descriptionRef.current;
if (element && !isDescriptionExpanded) {
setShowDescriptionExpandButton(element.scrollHeight > element.clientHeight);
}
};
checkDescriptionOverflow();
window.addEventListener('resize', checkDescriptionOverflow);
return () => window.removeEventListener('resize', checkDescriptionOverflow);
}, [video.description, isDescriptionExpanded]);
const handleStartEditingTitle = () => {
setEditedTitle(video.title);
setIsEditingTitle(true);
@@ -380,6 +397,35 @@ const VideoInfo: React.FC<VideoInfoProps> = ({
</Alert>
)}
{video.description && (
<Box sx={{ mt: 2 }}>
<Typography
ref={descriptionRef}
variant="body2"
color="text.primary"
sx={{
whiteSpace: 'pre-wrap',
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: isDescriptionExpanded ? 'unset' : 3,
}}
>
{video.description}
</Typography>
{showDescriptionExpandButton && (
<Button
size="small"
onClick={() => setIsDescriptionExpanded(!isDescriptionExpanded)}
startIcon={isDescriptionExpanded ? <ExpandLess /> : <ExpandMore />}
sx={{ mt: 0.5, p: 0, minWidth: 'auto', textTransform: 'none' }}
>
{isDescriptionExpanded ? t('collapse') : t('expand')}
</Button>
)}
</Box>
)}
<Divider sx={{ my: 2 }} />
<Box sx={{ bgcolor: 'background.paper', p: 2, borderRadius: 2 }}>

View File

@@ -22,6 +22,7 @@ export interface Video {
fileSize?: string; // Size in bytes as string
lastPlayedAt?: number;
subtitles?: Array<{ language: string; filename: string; path: string }>;
description?: string;
[key: string]: any;
}