You've already forked bilibili-subtitle
章节显示
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import {MutableRefObject, useCallback, useEffect, useMemo, useRef} from 'react'
|
import {MutableRefObject, useCallback, useEffect, useMemo, useRef} from 'react'
|
||||||
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
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 classNames from 'classnames'
|
||||||
import {FaClipboardList, FaComments} from 'react-icons/fa'
|
import {FaClipboardList, FaComments} from 'react-icons/fa'
|
||||||
import {SUMMARIZE_THRESHOLD, SUMMARIZE_TYPES} from '../consts/const'
|
import {SUMMARIZE_THRESHOLD, SUMMARIZE_TYPES} from '../consts/const'
|
||||||
@@ -159,6 +159,7 @@ const SegmentCard = (props: {
|
|||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
}, [curSummaryType, segment.summaries])
|
}, [curSummaryType, segment.summaries])
|
||||||
|
const {move} = useSubtitle()
|
||||||
|
|
||||||
const onFold = useCallback(() => {
|
const onFold = useCallback(() => {
|
||||||
dispatch(setSegmentFold({
|
dispatch(setSegmentFold({
|
||||||
@@ -167,6 +168,21 @@ const SegmentCard = (props: {
|
|||||||
}))
|
}))
|
||||||
}, [dispatch, segment.fold, segment.startIdx])
|
}, [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
|
// 检测设置floatKeyPointsSegIdx
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (summarizeFloat) { // 已启用
|
if (summarizeFloat) { // 已启用
|
||||||
@@ -212,6 +228,10 @@ const SegmentCard = (props: {
|
|||||||
|
|
||||||
return <div
|
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')}>
|
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]'>
|
<div className='relative flex justify-center min-h-[20px]'>
|
||||||
{segments != null && segments.length > 0 &&
|
{segments != null && segments.length > 0 &&
|
||||||
<div className='absolute left-0 top-0 bottom-0 text-xs select-none flex-center desc'>
|
<div className='absolute left-0 top-0 bottom-0 text-xs select-none flex-center desc'>
|
||||||
|
@@ -31,6 +31,7 @@ const useSubtitleService = () => {
|
|||||||
const envReady = useAppSelector(state => state.env.envReady)
|
const envReady = useAppSelector(state => state.env.envReady)
|
||||||
const envData = useAppSelector((state: RootState) => state.env.envData)
|
const envData = useAppSelector((state: RootState) => state.env.envData)
|
||||||
const data = useAppSelector((state: RootState) => state.env.data)
|
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 currentTime = useAppSelector((state: RootState) => state.currentTime.currentTime)
|
||||||
const curIdx = useAppSelector((state: RootState) => state.env.curIdx)
|
const curIdx = useAppSelector((state: RootState) => state.env.curIdx)
|
||||||
const eventBus = useContext(EventBusContext)
|
const eventBus = useContext(EventBusContext)
|
||||||
@@ -158,24 +159,78 @@ const useSubtitleService = () => {
|
|||||||
size = Math.max(size, WORDS_MIN)
|
size = Math.max(size, WORDS_MIN)
|
||||||
|
|
||||||
segments = []
|
segments = []
|
||||||
let transcriptItems: TranscriptItem[] = []
|
|
||||||
let totalLength = 0
|
// 如果有章节信息,按章节分割
|
||||||
for (let i = 0; i < items.length; i++) {
|
if (chapters && chapters.length > 0) {
|
||||||
const item = items[i]
|
for (let chapterIdx = 0; chapterIdx < chapters.length; chapterIdx++) {
|
||||||
transcriptItems.push(item)
|
const chapter = chapters[chapterIdx]
|
||||||
totalLength += item.content.length
|
const nextChapter = chapters[chapterIdx + 1]
|
||||||
if (totalLength >= size || i === items.length-1) { // new segment or last
|
|
||||||
// add
|
// 找到属于当前章节的字幕项
|
||||||
segments.push({
|
const chapterItems = items.filter(item => {
|
||||||
items: transcriptItems,
|
const itemTime = item.from
|
||||||
startIdx: transcriptItems[0].idx,
|
return itemTime >= chapter.from && (nextChapter ? itemTime < nextChapter.from : true)
|
||||||
endIdx: transcriptItems[transcriptItems.length - 1].idx,
|
|
||||||
text: getWholeText(transcriptItems.map(item => item.content)),
|
|
||||||
summaries: {},
|
|
||||||
})
|
})
|
||||||
// reset
|
|
||||||
transcriptItems = []
|
if (chapterItems.length === 0) continue
|
||||||
totalLength = 0
|
|
||||||
|
// 如果章节内容过长,需要进一步分割
|
||||||
|
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 { // 都放一个分段
|
} else { // 都放一个分段
|
||||||
@@ -189,7 +244,7 @@ const useSubtitleService = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch(setSegments(segments))
|
dispatch(setSegments(segments))
|
||||||
}, [data?.body, dispatch, envData])
|
}, [data?.body, dispatch, envData, chapters])
|
||||||
|
|
||||||
// 每0.5秒更新当前视频时间
|
// 每0.5秒更新当前视频时间
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
|
1
src/typings.d.ts
vendored
1
src/typings.d.ts
vendored
@@ -100,6 +100,7 @@ interface Segment {
|
|||||||
endIdx: number
|
endIdx: number
|
||||||
text: string
|
text: string
|
||||||
fold?: boolean
|
fold?: boolean
|
||||||
|
chapterTitle?: string // 章节标题
|
||||||
summaries: {
|
summaries: {
|
||||||
[type: string]: Summary
|
[type: string]: Summary
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user