feat: Add video description support for Bilibili and YtDlp
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user