Skip to content

Commit 613bfa3

Browse files
committed
Don't merge - just playing with assembly ai
1 parent f15c3ac commit 613bfa3

File tree

2 files changed

+191
-0
lines changed

2 files changed

+191
-0
lines changed

pages/video-transcription.jsx

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { useEffect, useRef, useState } from "react"
2+
import Head from "next/head"
3+
import styles from "../styles/VideoTranscription.module.css"
4+
import { firestore } from "../components/firebase"
5+
import { doc, getDoc } from "firebase/firestore"
6+
7+
export default function VideoTranscription({ videoUrl, utterances }) {
8+
const [currentTime, setCurrentTime] = useState(0)
9+
const videoRef = useRef(null)
10+
const transcriptionRef = useRef(null)
11+
const utteranceRefs = useRef({})
12+
13+
// Update current time when video plays
14+
const handleTimeUpdate = () => {
15+
if (videoRef.current) {
16+
setCurrentTime(videoRef.current.currentTime * 1000) // Convert to ms
17+
}
18+
}
19+
20+
// Scroll to the current utterance
21+
useEffect(() => {
22+
const currentUtterance = utterances.find(
23+
utterance =>
24+
currentTime >= utterance.start && currentTime <= utterance.end
25+
)
26+
27+
if (currentUtterance && utteranceRefs.current[currentUtterance.start]) {
28+
const element = utteranceRefs.current[currentUtterance.start]
29+
const container = transcriptionRef.current
30+
31+
if (container) {
32+
container.scrollTop = element.offsetTop - container.offsetTop - 100 // Offset for better visibility
33+
}
34+
}
35+
}, [currentTime, utterances])
36+
37+
// Click on transcription to seek video
38+
const seekToTime = startTime => {
39+
if (videoRef.current) {
40+
videoRef.current.currentTime = startTime / 1000 // Convert ms to seconds
41+
}
42+
}
43+
44+
return (
45+
<div className={styles.container}>
46+
<Head>
47+
<title>Video Transcription</title>
48+
<meta
49+
name="description"
50+
content="Video with synchronized transcription"
51+
/>
52+
</Head>
53+
54+
<main className={styles.main}>
55+
<div className={styles.videoContainer}>
56+
<video
57+
ref={videoRef}
58+
src={videoUrl}
59+
controls
60+
onTimeUpdate={handleTimeUpdate}
61+
className={styles.video}
62+
/>
63+
</div>
64+
65+
<div className={styles.transcriptionContainer} ref={transcriptionRef}>
66+
<h2>Transcription</h2>
67+
<div className={styles.transcription}>
68+
{utterances.map(utterance => {
69+
const isActive =
70+
currentTime >= utterance.start && currentTime <= utterance.end
71+
return (
72+
<div
73+
key={utterance.start}
74+
ref={el => (utteranceRefs.current[utterance.start] = el)}
75+
className={`${styles.utterance} ${
76+
isActive ? styles.active : ""
77+
}`}
78+
onClick={() => seekToTime(utterance.start)}
79+
>
80+
<span className={styles.timestamp}>
81+
{formatTime(utterance.start)} - {formatTime(utterance.end)}
82+
</span>
83+
<p>{utterance.text}</p>
84+
</div>
85+
)
86+
})}
87+
</div>
88+
</div>
89+
</main>
90+
</div>
91+
)
92+
}
93+
94+
// Helper function to format milliseconds to MM:SS format
95+
function formatTime(ms) {
96+
const totalSeconds = Math.floor(ms / 1000)
97+
const minutes = Math.floor(totalSeconds / 60)
98+
const seconds = totalSeconds % 60
99+
return `${minutes.toString().padStart(2, "0")}:${seconds
100+
.toString()
101+
.padStart(2, "0")}`
102+
}
103+
104+
export async function getServerSideProps() {
105+
const exampleTranscriptionId = "17c91397-c023-4f28-a621-4cef45c70749"
106+
const transcription = await getDoc(
107+
doc(firestore, `transcriptions/${exampleTranscriptionId}`)
108+
)
109+
console.log(transcription.data())
110+
111+
const videoUrl = transcription.data().audio_url
112+
const utterances = transcription.data().utterances
113+
114+
return {
115+
props: {
116+
videoUrl,
117+
utterances
118+
}
119+
}
120+
}

styles/VideoTranscription.module.css

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
.container {
2+
padding: 2rem;
3+
max-width: 1200px;
4+
margin: 0 auto;
5+
}
6+
7+
.main {
8+
display: flex;
9+
flex-direction: column;
10+
gap: 2rem;
11+
}
12+
13+
@media (min-width: 768px) {
14+
.main {
15+
flex-direction: row;
16+
}
17+
}
18+
19+
.videoContainer {
20+
flex: 1;
21+
}
22+
23+
.video {
24+
width: 100%;
25+
border-radius: 8px;
26+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
27+
}
28+
29+
.transcriptionContainer {
30+
flex: 1;
31+
max-height: 600px;
32+
overflow-y: auto;
33+
padding: 1rem;
34+
border: 1px solid #e5e5e5;
35+
border-radius: 8px;
36+
background-color: #f9f9f9;
37+
}
38+
39+
.transcription {
40+
display: flex;
41+
flex-direction: column;
42+
gap: 1rem;
43+
}
44+
45+
.utterance {
46+
padding: 0.75rem;
47+
border-radius: 6px;
48+
cursor: pointer;
49+
transition: background-color 0.2s ease;
50+
}
51+
52+
.utterance:hover {
53+
background-color: #f0f0f0;
54+
}
55+
56+
.active {
57+
background-color: #e6f7ff;
58+
border-left: 3px solid #1890ff;
59+
}
60+
61+
.timestamp {
62+
font-size: 0.8rem;
63+
color: #666;
64+
display: block;
65+
margin-bottom: 0.25rem;
66+
}
67+
68+
.utterance p {
69+
margin: 0;
70+
line-height: 1.5;
71+
}

0 commit comments

Comments
 (0)