Skip to content

Commit

Permalink
Merge pull request #174 from roballsopp/dev
Browse files Browse the repository at this point in the history
support setting cue options for vtt files
  • Loading branch information
roballsopp authored Nov 8, 2023
2 parents 21c9f3c + 13e8a33 commit 72db85f
Show file tree
Hide file tree
Showing 8 changed files with 510 additions and 109 deletions.
123 changes: 104 additions & 19 deletions src/common/cue-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const CuesContext = React.createContext({
changeCueEnd: () => {},
changeCueText: () => {},
changeCueTiming: () => {},
changeCueVert: () => {},
changeCueHoriz: () => {},
changeCueLinePos: () => {},
changeCueAlign: () => {},
setCues: () => {},
setCuesLoading: () => {},
})
Expand All @@ -29,7 +33,7 @@ export function CuesProvider({children}) {
const toast = useToast()
const {videoRef} = useVideoDom()

const saveCuesToStorage = React.useCallback(cues => {
const saveCuesToStorage = React.useCallback((cues) => {
try {
storeCues(cues)
} catch (e) {
Expand Down Expand Up @@ -69,13 +73,13 @@ export function CuesProvider({children}) {
}, [cues, saveCuesToStorage])

const addCue = React.useCallback(() => {
setCues(cues => {
setCues((cues) => {
const newCues = cues.slice()

if (videoRef) {
// if we have a video loaded, insert the new cue at the current video time
const newCue = new VTTCue(videoRef.currentTime, videoRef.currentTime + 2, '')
const newIndex = newCues.findIndex(c => c.startTime > newCue.startTime)
const newIndex = newCues.findIndex((c) => c.startTime > newCue.startTime)
if (newIndex === -1) {
newCues.push(newCue)
} else {
Expand All @@ -94,10 +98,10 @@ export function CuesProvider({children}) {
})
}, [videoRef])

const removeCue = React.useCallback(id => {
setCues(cues => {
const removeCue = React.useCallback((id) => {
setCues((cues) => {
const newCues = cues.slice()
const idx = newCues.findIndex(c => c.id === id)
const idx = newCues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('removeCue: could not find cue in list'))
return newCues
Expand All @@ -108,50 +112,50 @@ export function CuesProvider({children}) {
}, [])

const changeCueStart = React.useCallback((id, newStartTime) => {
setCues(cues => {
setCues((cues) => {
const newCues = cues.slice()
const idx = newCues.findIndex(c => c.id === id)
const idx = newCues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('changeCueStart: could not find cue in list'))
return newCues
}
const oldCue = cues[idx]
newCues[idx] = new VTTCue(newStartTime, oldCue.endTime, oldCue.text, oldCue.id)
newCues[idx] = new VTTCue(newStartTime, oldCue.endTime, oldCue.text, oldCue)
return sortBy(newCues, ['startTime'])
})
}, [])

const changeCueEnd = React.useCallback((id, newEndTime) => {
setCues(cues => {
setCues((cues) => {
const newCues = cues.slice()
const idx = newCues.findIndex(c => c.id === id)
const idx = newCues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('changeCueEnd: could not find cue in list'))
return newCues
}
const oldCue = cues[idx]
newCues[idx] = new VTTCue(oldCue.startTime, newEndTime, oldCue.text, oldCue.id)
newCues[idx] = new VTTCue(oldCue.startTime, newEndTime, oldCue.text, oldCue)
return newCues
})
}, [])

const changeCueText = React.useCallback((id, newText) => {
setCues(cues => {
setCues((cues) => {
const newCues = cues.slice()
const idx = newCues.findIndex(c => c.id === id)
const idx = newCues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('changeCueText: could not find cue in list'))
return newCues
}
const oldCue = cues[idx]
newCues[idx] = new VTTCue(oldCue.startTime, oldCue.endTime, newText, oldCue.id)
newCues[idx] = new VTTCue(oldCue.startTime, oldCue.endTime, newText, oldCue)
return newCues
})
}, [])

const changeCueTiming = React.useCallback((id, {startDelta = 0, endDelta = 0}) => {
setCues(cues => {
const idx = cues.findIndex(c => c.id === id)
setCues((cues) => {
const idx = cues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('changeCueTiming: could not find cue in list'))
return cues
Expand All @@ -170,12 +174,76 @@ export function CuesProvider({children}) {
}

const newCues = cues.slice()
newCues[idx] = new VTTCue(newStartTime, newEndTime, oldCue.text, oldCue.id)
newCues[idx] = new VTTCue(newStartTime, newEndTime, oldCue.text, oldCue)

return sortBy(newCues, ['startTime'])
})
}, [])

const changeCueVert = React.useCallback((id, newVert) => {
setCues((cues) => {
const newCues = cues.slice()
const idx = newCues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('changeCueVert: could not find cue in list'))
return newCues
}
const oldCue = cues[idx]
const newCue = new VTTCue(oldCue.startTime, oldCue.endTime, oldCue.text, oldCue)
newCue.vertical = newVert
newCues[idx] = newCue
return newCues
})
}, [])

const changeCueLinePos = React.useCallback((id, newLine) => {
setCues((cues) => {
const newCues = cues.slice()
const idx = newCues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('changeCueLinePos: could not find cue in list'))
return newCues
}
const oldCue = cues[idx]
const newCue = new VTTCue(oldCue.startTime, oldCue.endTime, oldCue.text, oldCue)
newCue.line = newLine
newCues[idx] = newCue
return newCues
})
}, [])

const changeCueHoriz = React.useCallback((id, newHoriz) => {
setCues((cues) => {
const newCues = cues.slice()
const idx = newCues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('changeCueHoriz: could not find cue in list'))
return newCues
}
const oldCue = cues[idx]
const newCue = new VTTCue(oldCue.startTime, oldCue.endTime, oldCue.text, oldCue)
newCue.position = newHoriz
newCues[idx] = newCue
return newCues
})
}, [])

const changeCueAlign = React.useCallback((id, newAlign) => {
setCues((cues) => {
const newCues = cues.slice()
const idx = newCues.findIndex((c) => c.id === id)
if (idx === -1) {
handleError(new Error('changeCueAlign: could not find cue in list'))
return newCues
}
const oldCue = cues[idx]
const newCue = new VTTCue(oldCue.startTime, oldCue.endTime, oldCue.text, oldCue)
newCue.align = newAlign
newCues[idx] = newCue
return newCues
})
}, [])

return (
<CuesContext.Provider
value={React.useMemo(
Expand All @@ -188,10 +256,27 @@ export function CuesProvider({children}) {
changeCueEnd,
changeCueText,
changeCueTiming,
changeCueVert,
changeCueHoriz,
changeCueLinePos,
changeCueAlign,
setCues,
setCuesLoading: setLoading,
}),
[cues, loading, addCue, removeCue, changeCueStart, changeCueEnd, changeCueText, changeCueTiming]
[
cues,
loading,
addCue,
removeCue,
changeCueStart,
changeCueEnd,
changeCueText,
changeCueTiming,
changeCueVert,
changeCueHoriz,
changeCueLinePos,
changeCueAlign,
]
)}>
{children}
</CuesContext.Provider>
Expand Down
2 changes: 1 addition & 1 deletion src/editor/TranslateButton/TranslationDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export default function TranslationDialog({translationCost, open, onRequestClose
const url = new URL(data.translateCues.translation.translationDownloadLinkVTT, ApiURL)
saveAs(`${url.href}?token=${token}`)
})
// const translatedCues = cues.map((c, i) => new VTTCue(c.startTime, c.endTime, data.translateText.text[i], c.id))
// const translatedCues = cues.map((c, i) => new VTTCue(c.startTime, c.endTime, data.translateText.text[i], c))
// saveAs(getVTTFromCues(translatedCues), 'translated_captions.vtt', 'text/vtt')
toast.success('Translation successful!')
onRequestClose()
Expand Down
16 changes: 14 additions & 2 deletions src/polyfills.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,21 @@ import {VTTCue as BaseCue} from 'vtt.js'
import {v4 as uuid} from 'uuid'

class VTTCue extends BaseCue {
constructor(startTime, endTime, text, id) {
constructor(startTime, endTime, text, existingCue) {
super(startTime, endTime, text)
this.id = id || uuid()
this.id = uuid()
if (existingCue) {
this.id = existingCue.id
this.align = existingCue.align
this.line = existingCue.line
this.lineAlign = existingCue.lineAlign
this.position = existingCue.position
this.positionAlign = existingCue.positionAlign
this.region = existingCue.region
this.size = existingCue.size
this.snapToLines = existingCue.snapToLines
this.vertical = existingCue.vertical
}
}
}

Expand Down
46 changes: 43 additions & 3 deletions src/services/vtt.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,20 @@ export function getVTTFromCues(cueList, title = 'Made with VTT Creator') {
const vttParts = cueList.map((nextCue) => {
const start = formatSeconds(nextCue.startTime)
const end = formatSeconds(nextCue.endTime)
return `${start} --> ${end}\n${nextCue.text}\n\n`
const settings = []
if (nextCue.vertical) {
settings.push(`vertical:${nextCue.vertical}`)
}
if (Number.isFinite(nextCue.position)) {
settings.push(`position:${nextCue.position}%`)
}
if (nextCue.align) {
settings.push(`align:${nextCue.align}`)
}
if (Number.isFinite(nextCue.line)) {
settings.push(`line:${nextCue.line}%`)
}
return `${start} --> ${end} ${settings.join(' ')}\n${nextCue.text}\n\n`
})

vttParts.unshift(`WEBVTT - ${title}\n\n`)
Expand Down Expand Up @@ -137,13 +150,40 @@ export function getCuesFromVTT(file) {
}

export function storeCues(cues) {
const reducedCues = cues.map((c) => ({id: c.id, startTime: c.startTime, endTime: c.endTime, text: c.text}))
const reducedCues = cues.map((c) => ({
id: c.id,
startTime: c.startTime,
endTime: c.endTime,
text: c.text,
align: c.align,
line: c.line,
lineAlign: c.lineAlign,
position: c.position,
positionAlign: c.positionAlign,
region: c.region,
size: c.size,
snapToLines: c.snapToLines,
vertical: c.vertical,
}))
localStorage.setItem(CUE_STORAGE_KEY, JSON.stringify(reducedCues))
}

export function getCuesFromStorage() {
const cueStr = localStorage.getItem(CUE_STORAGE_KEY)
if (!cueStr) return null
const parsed = JSON.parse(cueStr)
return parsed.map((c) => new VTTCue(c.startTime, c.endTime, c.text, c.id))
return parsed.map((c) => {
const cue = new VTTCue(c.startTime, c.endTime, c.text)
cue.id = c.id
cue.align = c.align
cue.line = c.line
cue.lineAlign = c.lineAlign
cue.position = c.position
cue.positionAlign = c.positionAlign
cue.region = c.region
cue.size = c.size
cue.snapToLines = c.snapToLines
cue.vertical = c.vertical
return cue
})
}
Loading

0 comments on commit 72db85f

Please sign in to comment.