Compare commits

...

1 Commits

Author SHA1 Message Date
Xinrea
e3bb014644 fix: crashed by loading large amount of cover data 2025-06-24 00:23:56 +08:00
7 changed files with 100 additions and 33 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "bili-shadowreplay",
"private": true,
"version": "2.7.0",
"version": "2.7.1",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -20,15 +20,31 @@ pub struct VideoRow {
pub platform: String,
}
#[derive(Debug, Clone, serde::Serialize, sqlx::FromRow)]
pub struct VideoNoCover {
pub id: i64,
pub room_id: u64,
pub file: String,
pub length: i64,
pub size: i64,
pub status: i64,
pub bvid: String,
pub title: String,
pub desc: String,
pub tags: String,
pub area: i64,
pub created_at: String,
pub platform: String,
}
impl Database {
pub async fn get_videos(&self, room_id: u64) -> Result<Vec<VideoRow>, DatabaseError> {
pub async fn get_videos(&self, room_id: u64) -> Result<Vec<VideoNoCover>, DatabaseError> {
let lock = self.db.read().await.clone().unwrap();
Ok(
sqlx::query_as::<_, VideoRow>("SELECT * FROM videos WHERE room_id = $1;")
.bind(room_id as i64)
.fetch_all(&lock)
.await?,
)
let videos = sqlx::query_as::<_, VideoNoCover>("SELECT * FROM videos WHERE room_id = $1;")
.bind(room_id as i64)
.fetch_all(&lock)
.await?;
Ok(videos)
}
pub async fn get_video(&self, id: i64) -> Result<VideoRow, DatabaseError> {
@@ -100,12 +116,21 @@ impl Database {
Ok(())
}
pub async fn get_all_videos(&self) -> Result<Vec<VideoRow>, DatabaseError> {
pub async fn get_all_videos(&self) -> Result<Vec<VideoNoCover>, DatabaseError> {
let lock = self.db.read().await.clone().unwrap();
Ok(
sqlx::query_as::<_, VideoRow>("SELECT * FROM videos ORDER BY created_at DESC;")
let videos =
sqlx::query_as::<_, VideoNoCover>("SELECT * FROM videos ORDER BY created_at DESC;")
.fetch_all(&lock)
.await?,
)
.await?;
Ok(videos)
}
pub async fn get_video_cover(&self, id: i64) -> Result<String, DatabaseError> {
let lock = self.db.read().await.clone().unwrap();
let video = sqlx::query_as::<_, VideoRow>("SELECT * FROM videos WHERE id = $1")
.bind(id)
.fetch_one(&lock)
.await?;
Ok(video.cover)
}
}

View File

@@ -1,5 +1,5 @@
use crate::database::task::TaskRow;
use crate::database::video::VideoRow;
use crate::database::video::{VideoNoCover, VideoRow};
use crate::ffmpeg;
use crate::handlers::utils::get_disk_info_inner;
use crate::progress_reporter::{
@@ -301,7 +301,7 @@ pub async fn get_video(state: state_type!(), id: i64) -> Result<VideoRow, String
}
#[cfg_attr(feature = "gui", tauri::command)]
pub async fn get_videos(state: state_type!(), room_id: u64) -> Result<Vec<VideoRow>, String> {
pub async fn get_videos(state: state_type!(), room_id: u64) -> Result<Vec<VideoNoCover>, String> {
state
.db
.get_videos(room_id)
@@ -310,10 +310,19 @@ pub async fn get_videos(state: state_type!(), room_id: u64) -> Result<Vec<VideoR
}
#[cfg_attr(feature = "gui", tauri::command)]
pub async fn get_all_videos(state: state_type!()) -> Result<Vec<VideoRow>, String> {
pub async fn get_all_videos(state: state_type!()) -> Result<Vec<VideoNoCover>, String> {
state.db.get_all_videos().await.map_err(|e| e.to_string())
}
#[cfg_attr(feature = "gui", tauri::command)]
pub async fn get_video_cover(state: state_type!(), id: i64) -> Result<String, String> {
state
.db
.get_video_cover(id)
.await
.map_err(|e| e.to_string())
}
#[cfg_attr(feature = "gui", tauri::command)]
pub async fn delete_video(state: state_type!(), id: i64) -> Result<(), String> {
// get video info from dbus

View File

@@ -3,8 +3,12 @@ use std::fmt::{self, Display};
use crate::{
config::Config,
database::{
account::AccountRow, message::MessageRow, record::RecordRow, recorder::RecorderRow,
task::TaskRow, video::VideoRow,
account::AccountRow,
message::MessageRow,
record::RecordRow,
recorder::RecorderRow,
task::TaskRow,
video::{VideoNoCover, VideoRow},
},
handlers::{
account::{
@@ -27,8 +31,8 @@ use crate::{
utils::{console_log, get_disk_info, DiskInfo},
video::{
cancel, clip_range, delete_video, encode_video_subtitle, generate_video_subtitle,
get_all_videos, get_video, get_video_subtitle, get_video_typelist, get_videos,
update_video_cover, update_video_subtitle, upload_procedure,
get_all_videos, get_video, get_video_cover, get_video_subtitle, get_video_typelist,
get_videos, update_video_cover, update_video_subtitle, upload_procedure,
},
AccountInfo,
},
@@ -656,21 +660,36 @@ async fn handler_get_video(
struct GetVideosRequest {
room_id: u64,
}
async fn handler_get_videos(
state: axum::extract::State<State>,
Json(param): Json<GetVideosRequest>,
) -> Result<Json<ApiResponse<Vec<VideoRow>>>, ApiError> {
) -> Result<Json<ApiResponse<Vec<VideoNoCover>>>, ApiError> {
let videos = get_videos(state.0, param.room_id).await?;
Ok(Json(ApiResponse::success(videos)))
}
async fn handler_get_all_videos(
state: axum::extract::State<State>,
) -> Result<Json<ApiResponse<Vec<VideoRow>>>, ApiError> {
) -> Result<Json<ApiResponse<Vec<VideoNoCover>>>, ApiError> {
let videos = get_all_videos(state.0).await?;
Ok(Json(ApiResponse::success(videos)))
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct GetVideoCoverRequest {
id: i64,
}
async fn handler_get_video_cover(
state: axum::extract::State<State>,
Json(param): Json<GetVideoCoverRequest>,
) -> Result<Json<ApiResponse<String>>, ApiError> {
let video_cover = get_video_cover(state.0, param.id).await?;
Ok(Json(ApiResponse::success(video_cover)))
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DeleteVideoRequest {
@@ -1236,6 +1255,7 @@ pub async fn start_api_server(state: State) {
.route("/api/clip_range", post(handler_clip_range))
.route("/api/get_video", post(handler_get_video))
.route("/api/get_videos", post(handler_get_videos))
.route("/api/get_video_cover", post(handler_get_video_cover))
.route("/api/get_all_videos", post(handler_get_all_videos))
.route("/api/get_video_typelist", post(handler_get_video_typelist))
.route("/api/get_video_subtitle", post(handler_get_video_subtitle))

View File

@@ -433,6 +433,7 @@ fn setup_invoke_handlers(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<
crate::handlers::video::get_video,
crate::handlers::video::get_videos,
crate::handlers::video::get_all_videos,
crate::handlers::video::get_video_cover,
crate::handlers::video::delete_video,
crate::handlers::video::get_video_typelist,
crate::handlers::video::update_video_cover,

View File

@@ -213,15 +213,19 @@
});
}
function find_video(e) {
async function find_video(e) {
if (!e.target) {
selected_video = null;
return;
}
const id = parseInt(e.target.value);
selected_video = videos.find((v) => {
let target_video = videos.find((v) => {
return v.value == id;
});
if (target_video) {
target_video.cover = await invoke("get_video_cover", { id: id });
}
selected_video = target_video;
console.log("video selected", videos, selected_video, e, id);
}

View File

@@ -34,6 +34,8 @@
await loadVideos();
});
let cover_cache: Map<number, string> = new Map();
async function loadVideos() {
loading = true;
try {
@@ -44,6 +46,16 @@
const roomIdsSet = new Set<number>();
const platformsSet = new Set<string>();
const tempVideos = await invoke<VideoItem[]>("get_all_videos");
for (const video of tempVideos) {
if (cover_cache.has(video.id)) {
video.cover = cover_cache.get(video.id) || "";
} else {
video.cover = await invoke<string>("get_video_cover", {
id: video.id,
});
cover_cache.set(video.id, video.cover);
}
}
for (const video of tempVideos) {
roomIdsSet.add(video.room_id);
@@ -536,15 +548,11 @@
<div
class="w-12 h-8 rounded-lg overflow-hidden bg-gray-100 dark:bg-gray-700 flex items-center justify-center flex-shrink-0"
>
{#if video.cover}
<img
src={video.cover}
alt="封面"
class="w-full h-full object-cover"
/>
{:else}
<Video class="w-4 h-4 text-gray-400" />
{/if}
<img
src={video.cover}
alt="封面"
class="w-full h-full object-cover"
/>
</div>
<div class="min-w-0 flex-1 w-64">
<p