Skip to content

Commit 1c14663

Browse files
Added video support in transcription playback #906 (#1295)
Co-authored-by: Raivis Dejus <[email protected]>
1 parent 11e59db commit 1c14663

File tree

6 files changed

+833
-143
lines changed

6 files changed

+833
-143
lines changed

buzz/transcriber/file_transcriber.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,9 @@ def to_timestamp(ms: float, ms_separator=".") -> str:
203203
sec = int(ms / 1000)
204204
ms = int(ms - sec * 1000)
205205
return f"{hr:02d}:{min:02d}:{sec:02d}{ms_separator}{ms:03d}"
206+
207+
# To detect when transcription source is a video
208+
VIDEO_EXTENSIONS = {".mp4", ".mov", ".mkv", ".avi", ".m4v", ".webm", ".ogm", ".wmv"}
209+
210+
def is_video_file(path: str) -> bool:
211+
return Path(path).suffix.lower() in VIDEO_EXTENSIONS

buzz/widgets/audio_player.py

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
from PyQt6 import QtGui
55
from PyQt6.QtCore import QTime, QUrl, Qt, pyqtSignal
66
from PyQt6.QtMultimedia import QAudioOutput, QMediaPlayer
7-
from PyQt6.QtWidgets import QWidget, QSlider, QPushButton, QLabel, QHBoxLayout
7+
from PyQt6.QtWidgets import QWidget, QSlider, QPushButton, QLabel, QHBoxLayout, QVBoxLayout
88

99
from buzz.widgets.icon import PlayIcon, PauseIcon
1010
from buzz.settings.settings import Settings
11+
from buzz.transcriber.file_transcriber import is_video_file
1112

1213

1314
class AudioPlayer(QWidget):
@@ -21,17 +22,27 @@ def __init__(self, file_path: str):
2122
self.duration_ms = 0
2223
self.invalid_media = None
2324
self.is_looping = False # Flag to prevent recursive position changes
25+
self.is_slider_dragging = False # Flag to track if use is dragging slider
2426

2527
# Initialize settings
2628
self.settings = Settings()
2729

30+
self.is_video = is_video_file(file_path)
31+
2832
self.audio_output = QAudioOutput()
2933
self.audio_output.setVolume(100)
3034

3135
self.media_player = QMediaPlayer()
3236
self.media_player.setSource(QUrl.fromLocalFile(file_path))
3337
self.media_player.setAudioOutput(self.audio_output)
3438

39+
if self.is_video:
40+
from PyQt6.QtMultimediaWidgets import QVideoWidget
41+
self.video_widget = QVideoWidget(self)
42+
self.media_player.setVideoOutput(self.video_widget)
43+
else:
44+
self.video_widget = None
45+
3546
# Speed control moved to transcription viewer - just set default rate
3647
saved_rate = self.settings.value(Settings.Key.AUDIO_PLAYBACK_RATE, 1.0, float)
3748
saved_rate = max(0.1, min(5.0, saved_rate)) # Ensure valid range
@@ -40,6 +51,11 @@ def __init__(self, file_path: str):
4051
self.scrubber = QSlider(Qt.Orientation.Horizontal)
4152
self.scrubber.setRange(0, 0)
4253
self.scrubber.sliderMoved.connect(self.on_slider_moved)
54+
self.scrubber.sliderPressed.connect(self.on_slider_pressed)
55+
self.scrubber.sliderReleased.connect(self.on_slider_released)
56+
57+
# Track if user is dragging the slider
58+
self.is_slider_dragging = False
4359

4460
self.play_icon = PlayIcon(self)
4561
self.pause_icon = PauseIcon(self)
@@ -54,10 +70,23 @@ def __init__(self, file_path: str):
5470
self.time_label.setAlignment(Qt.AlignmentFlag.AlignRight)
5571

5672
# Create main layout - simplified without speed controls
57-
main_layout = QHBoxLayout()
58-
main_layout.addWidget(self.play_button, alignment=Qt.AlignmentFlag.AlignVCenter)
59-
main_layout.addWidget(self.scrubber, alignment=Qt.AlignmentFlag.AlignVCenter)
60-
main_layout.addWidget(self.time_label, alignment=Qt.AlignmentFlag.AlignVCenter)
73+
if self.is_video:
74+
#Vertical layout for video
75+
main_layout = QVBoxLayout()
76+
main_layout.addWidget(self.video_widget, stretch=1) # As video takes more space
77+
78+
controls_layout = QHBoxLayout()
79+
controls_layout.addWidget(self.play_button, alignment=Qt.AlignmentFlag.AlignVCenter)
80+
controls_layout.addWidget(self.scrubber, alignment=Qt.AlignmentFlag.AlignVCenter)
81+
controls_layout.addWidget(self.time_label, alignment=Qt.AlignmentFlag.AlignVCenter)
82+
83+
main_layout.addLayout(controls_layout)
84+
else:
85+
# Horizontal layout for audio only
86+
main_layout = QHBoxLayout()
87+
main_layout.addWidget(self.play_button, alignment=Qt.AlignmentFlag.AlignVCenter)
88+
main_layout.addWidget(self.scrubber, alignment=Qt.AlignmentFlag.AlignVCenter)
89+
main_layout.addWidget(self.time_label, alignment=Qt.AlignmentFlag.AlignVCenter)
6190

6291
self.setLayout(main_layout)
6392

@@ -75,7 +104,12 @@ def on_duration_changed(self, duration_ms: int):
75104
self.update_time_label()
76105

77106
def on_position_changed(self, position_ms: int):
78-
self.scrubber.setValue(position_ms)
107+
# Don't update slider if user is currently dragging it
108+
if not self.is_slider_dragging:
109+
self.scrubber.blockSignals(True)
110+
self.scrubber.setValue(position_ms)
111+
self.scrubber.blockSignals(False)
112+
79113
self.position_ms = position_ms
80114
self.position_ms_changed.emit(self.position_ms)
81115
self.update_time_label()
@@ -150,6 +184,16 @@ def on_slider_moved(self, position_ms: int):
150184
if position_ms < (start_range_ms - 2000) or position_ms > (end_range_ms + 2000):
151185
self.range_ms = None
152186

187+
def on_slider_pressed(self):
188+
"""Called when the user starts dragging the slider"""
189+
self.is_slider_dragging = True
190+
191+
def on_slider_released(self):
192+
"""Called when user releases the slider"""
193+
self.is_slider_dragging = False
194+
# Update the position where user released
195+
self.set_position(self.scrubber.value())
196+
153197
def set_position(self, position_ms: int):
154198
self.media_player.setPosition(position_ms)
155199

0 commit comments

Comments
 (0)