You've already forked bilibili-subtitle
Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
af80b8a51c | ||
![]() |
404ab904cc | ||
![]() |
d53884269a |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "bilibili-subtitle",
|
||||
"version": "1.13.1",
|
||||
"version": "1.14.0",
|
||||
"type": "module",
|
||||
"description": "哔哩哔哩字幕列表",
|
||||
"main": "index.js",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {MutableRefObject, useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
||||
import {setFloatKeyPointsSegIdx, setSegmentFold, setTempData} from '../redux/envReducer'
|
||||
import {setFloatKeyPointsSegIdx, setNeedScroll, setSegmentFold, setTempData} from '../redux/envReducer'
|
||||
import classNames from 'classnames'
|
||||
import {FaClipboardList, FaComments} from 'react-icons/fa'
|
||||
import {SUMMARIZE_THRESHOLD, SUMMARIZE_TYPES} from '../consts/const'
|
||||
@@ -159,6 +159,7 @@ const SegmentCard = (props: {
|
||||
}
|
||||
return undefined
|
||||
}, [curSummaryType, segment.summaries])
|
||||
const {move} = useSubtitle()
|
||||
|
||||
const onFold = useCallback(() => {
|
||||
dispatch(setSegmentFold({
|
||||
@@ -167,6 +168,21 @@ const SegmentCard = (props: {
|
||||
}))
|
||||
}, [dispatch, segment.fold, segment.startIdx])
|
||||
|
||||
const onChapterClick = useCallback(() => {
|
||||
if (segment.items && segment.items.length > 0) {
|
||||
// 展开当前segment
|
||||
if (segment.fold) {
|
||||
dispatch(setSegmentFold({
|
||||
segmentStartIdx: segment.startIdx,
|
||||
fold: false
|
||||
}))
|
||||
}
|
||||
|
||||
const firstItem = segment.items[0]
|
||||
move(firstItem.from, false)
|
||||
}
|
||||
}, [dispatch, move, segment.fold, segment.items, segment.startIdx])
|
||||
|
||||
// 检测设置floatKeyPointsSegIdx
|
||||
useEffect(() => {
|
||||
if (summarizeFloat) { // 已启用
|
||||
@@ -212,6 +228,10 @@ const SegmentCard = (props: {
|
||||
|
||||
return <div
|
||||
className={classNames('border border-base-300 bg-base-200/25 rounded flex flex-col m-1.5 p-1.5 gap-1', showCurrent && 'shadow shadow-md')}>
|
||||
{/* 章节标题 */}
|
||||
{segment.chapterTitle && <div className='text-center py-1 px-2 bg-primary/10 rounded text-sm font-semibold text-primary border-b border-primary/20 cursor-pointer hover:bg-primary/20 transition-colors' onClick={onChapterClick}>
|
||||
{segment.chapterTitle}
|
||||
</div>}
|
||||
<div className='relative flex justify-center min-h-[20px]'>
|
||||
{segments != null && segments.length > 0 &&
|
||||
<div className='absolute left-0 top-0 bottom-0 text-xs select-none flex-center desc'>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { setAuthor, setCtime, setCurFetched, setCurInfo, setData, setInfos, setTitle, setUrl } from '@/redux/envReducer'
|
||||
import { setAuthor, setChapters, setCtime, setCurFetched, setCurInfo, setData, setInfos, setTitle, setUrl } from '@/redux/envReducer'
|
||||
import { useAppDispatch, useAppSelector } from './redux'
|
||||
import { AllAPPMessages, AllExtensionMessages, AllInjectMessages } from '@/message-typings'
|
||||
import { useMessaging, useMessagingService } from '@kky002/kky-message'
|
||||
@@ -19,6 +19,7 @@ const useMessageService = () => {
|
||||
dispatch(setData(undefined))
|
||||
},
|
||||
SET_VIDEO_INFO: async (params, context: MethodContext) => {
|
||||
dispatch(setChapters(params.chapters))
|
||||
dispatch(setInfos(params.infos))
|
||||
dispatch(setUrl(params.url))
|
||||
dispatch(setTitle(params.title))
|
||||
|
@@ -31,6 +31,7 @@ const useSubtitleService = () => {
|
||||
const envReady = useAppSelector(state => state.env.envReady)
|
||||
const envData = useAppSelector((state: RootState) => state.env.envData)
|
||||
const data = useAppSelector((state: RootState) => state.env.data)
|
||||
const chapters = useAppSelector((state: RootState) => state.env.chapters)
|
||||
const currentTime = useAppSelector((state: RootState) => state.currentTime.currentTime)
|
||||
const curIdx = useAppSelector((state: RootState) => state.env.curIdx)
|
||||
const eventBus = useContext(EventBusContext)
|
||||
@@ -158,24 +159,78 @@ const useSubtitleService = () => {
|
||||
size = Math.max(size, WORDS_MIN)
|
||||
|
||||
segments = []
|
||||
let transcriptItems: TranscriptItem[] = []
|
||||
let totalLength = 0
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
transcriptItems.push(item)
|
||||
totalLength += item.content.length
|
||||
if (totalLength >= size || i === items.length-1) { // new segment or last
|
||||
// add
|
||||
segments.push({
|
||||
items: transcriptItems,
|
||||
startIdx: transcriptItems[0].idx,
|
||||
endIdx: transcriptItems[transcriptItems.length - 1].idx,
|
||||
text: getWholeText(transcriptItems.map(item => item.content)),
|
||||
summaries: {},
|
||||
|
||||
// 如果有章节信息,按章节分割
|
||||
if (chapters && chapters.length > 0) {
|
||||
for (let chapterIdx = 0; chapterIdx < chapters.length; chapterIdx++) {
|
||||
const chapter = chapters[chapterIdx]
|
||||
const nextChapter = chapters[chapterIdx + 1]
|
||||
|
||||
// 找到属于当前章节的字幕项
|
||||
const chapterItems = items.filter(item => {
|
||||
const itemTime = item.from
|
||||
return itemTime >= chapter.from && (nextChapter ? itemTime < nextChapter.from : true)
|
||||
})
|
||||
// reset
|
||||
transcriptItems = []
|
||||
totalLength = 0
|
||||
|
||||
if (chapterItems.length === 0) continue
|
||||
|
||||
// 如果章节内容过长,需要进一步分割
|
||||
const chapterText = getWholeText(chapterItems.map(item => item.content))
|
||||
if (chapterText.length <= size) {
|
||||
// 章节内容不长,作为一个segment
|
||||
segments.push({
|
||||
items: chapterItems,
|
||||
startIdx: chapterItems[0].idx,
|
||||
endIdx: chapterItems[chapterItems.length - 1].idx,
|
||||
text: chapterText,
|
||||
chapterTitle: chapter.content,
|
||||
summaries: {},
|
||||
})
|
||||
} else {
|
||||
// 章节内容过长,需要分割成多个segment
|
||||
let transcriptItems: TranscriptItem[] = []
|
||||
let totalLength = 0
|
||||
for (let i = 0; i < chapterItems.length; i++) {
|
||||
const item = chapterItems[i]
|
||||
transcriptItems.push(item)
|
||||
totalLength += item.content.length
|
||||
if (totalLength >= size || i === chapterItems.length - 1) {
|
||||
segments.push({
|
||||
items: transcriptItems,
|
||||
startIdx: transcriptItems[0].idx,
|
||||
endIdx: transcriptItems[transcriptItems.length - 1].idx,
|
||||
text: getWholeText(transcriptItems.map(item => item.content)),
|
||||
chapterTitle: chapter.content,
|
||||
summaries: {},
|
||||
})
|
||||
// reset
|
||||
transcriptItems = []
|
||||
totalLength = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 没有章节信息,按原来的逻辑分割
|
||||
let transcriptItems: TranscriptItem[] = []
|
||||
let totalLength = 0
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
transcriptItems.push(item)
|
||||
totalLength += item.content.length
|
||||
if (totalLength >= size || i === items.length-1) { // new segment or last
|
||||
// add
|
||||
segments.push({
|
||||
items: transcriptItems,
|
||||
startIdx: transcriptItems[0].idx,
|
||||
endIdx: transcriptItems[transcriptItems.length - 1].idx,
|
||||
text: getWholeText(transcriptItems.map(item => item.content)),
|
||||
summaries: {},
|
||||
})
|
||||
// reset
|
||||
transcriptItems = []
|
||||
totalLength = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // 都放一个分段
|
||||
@@ -189,7 +244,7 @@ const useSubtitleService = () => {
|
||||
}
|
||||
}
|
||||
dispatch(setSegments(segments))
|
||||
}, [data?.body, dispatch, envData])
|
||||
}, [data?.body, dispatch, envData, chapters])
|
||||
|
||||
// 每0.5秒更新当前视频时间
|
||||
useInterval(() => {
|
||||
|
@@ -161,6 +161,21 @@ const debug = (...args: any[]) => {
|
||||
if (aidOrBvid) {
|
||||
// aid,pages
|
||||
let cid: string | undefined
|
||||
/**
|
||||
* [
|
||||
{
|
||||
"type": 2,
|
||||
"from": 0,
|
||||
"to": 152, //单位秒
|
||||
"content": "发现美",
|
||||
"imgUrl": "http://i0.hdslb.com/bfs/vchapter/29168372111_0.jpg",
|
||||
"logoUrl": "",
|
||||
"team_type": "",
|
||||
"team_name": ""
|
||||
}
|
||||
]
|
||||
*/
|
||||
let chapters: any[] = []
|
||||
let subtitles
|
||||
if (aidOrBvid.toLowerCase().startsWith('av')) { // avxxx
|
||||
aid = parseInt(aidOrBvid.slice(2))
|
||||
@@ -170,6 +185,7 @@ const debug = (...args: any[]) => {
|
||||
author = pages[0].owner?.name
|
||||
title = pages[0].part
|
||||
await fetch(`https://api.bilibili.com/x/player/wbi/v2?aid=${aid}&cid=${cid!}`, { credentials: 'include' }).then(async res => await res.json()).then(res => {
|
||||
chapters = res.data.view_points ?? []
|
||||
subtitles = res.data.subtitle.subtitles
|
||||
})
|
||||
} else { // bvxxx
|
||||
@@ -182,10 +198,14 @@ const debug = (...args: any[]) => {
|
||||
pages = res.data.pages
|
||||
})
|
||||
await fetch(`https://api.bilibili.com/x/player/wbi/v2?aid=${aid!}&cid=${cid!}`, { credentials: 'include' }).then(async res => await res.json()).then(res => {
|
||||
chapters = res.data.view_points ?? []
|
||||
subtitles = res.data.subtitle.subtitles
|
||||
})
|
||||
}
|
||||
|
||||
//筛选chapters里type为2的
|
||||
chapters = chapters.filter(chapter => chapter.type === 2)
|
||||
|
||||
// pagesMap
|
||||
pagesMap = {}
|
||||
pages.forEach(page => {
|
||||
@@ -202,6 +222,7 @@ const debug = (...args: any[]) => {
|
||||
ctime,
|
||||
author,
|
||||
pages,
|
||||
chapters,
|
||||
infos: subtitles,
|
||||
})
|
||||
}
|
||||
|
2
src/message-typings.d.ts
vendored
2
src/message-typings.d.ts
vendored
@@ -80,7 +80,7 @@ interface AppSetInfosMessage extends AppMessage<{ infos: any }> {
|
||||
method: 'SET_INFOS'
|
||||
}
|
||||
|
||||
interface AppSetVideoInfoMessage extends AppMessage<{ url: string, title: string, aid: number | null, ctime: number | null, author?: string, pages: any, infos: any }> {
|
||||
interface AppSetVideoInfoMessage extends AppMessage<{ url: string, title: string, aid: number | null, ctime: number | null, author?: string, pages: any, chapters: any, infos: any }> {
|
||||
method: 'SET_VIDEO_INFO'
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,7 @@ interface EnvState {
|
||||
totalHeight: number
|
||||
curIdx?: number // 从0开始
|
||||
needScroll?: boolean
|
||||
chapters?: Chapter[]
|
||||
infos?: any[]
|
||||
curInfo?: any
|
||||
curFetched?: boolean
|
||||
@@ -274,6 +275,9 @@ export const slice = createSlice({
|
||||
setAuthor: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.author = action.payload
|
||||
},
|
||||
setChapters: (state, action: PayloadAction<Chapter[]>) => {
|
||||
state.chapters = action.payload
|
||||
},
|
||||
setInfos: (state, action: PayloadAction<any[]>) => {
|
||||
state.infos = action.payload
|
||||
},
|
||||
@@ -346,6 +350,7 @@ export const {
|
||||
mergeAskInfo,
|
||||
setCtime,
|
||||
setAuthor,
|
||||
setChapters,
|
||||
} = slice.actions
|
||||
|
||||
export default slice.reducer
|
||||
|
7
src/typings.d.ts
vendored
7
src/typings.d.ts
vendored
@@ -88,12 +88,19 @@ interface TranscriptItem {
|
||||
idx: number
|
||||
}
|
||||
|
||||
interface Chapter {
|
||||
from: number
|
||||
to: number
|
||||
content: string // 标题
|
||||
}
|
||||
|
||||
interface Segment {
|
||||
items: TranscriptItem[]
|
||||
startIdx: number // 从1开始
|
||||
endIdx: number
|
||||
text: string
|
||||
fold?: boolean
|
||||
chapterTitle?: string // 章节标题
|
||||
summaries: {
|
||||
[type: string]: Summary
|
||||
}
|
||||
|
Reference in New Issue
Block a user