77
88import sys
99import re
10+ import subprocess
1011from enum import Flag
11- from PyQt6 .QtWidgets import QApplication , QVBoxLayout , QHBoxLayout , QWidget , QTableWidget , QTableWidgetItem , QPushButton , QLabel , QFileDialog , QHeaderView , QStyle , QSlider , QStyleOptionSlider
12+ from PyQt6 .QtWidgets import QApplication , QVBoxLayout , QHBoxLayout , QWidget , QTableWidget , QTableWidgetItem , QPushButton , QLabel , QFileDialog , QHeaderView , QStyle , QSlider , QStyleOptionSlider , QSplitter , QToolTip
1213from PyQt6 .QtMultimedia import QMediaPlayer , QAudioOutput
1314from PyQt6 .QtMultimediaWidgets import QVideoWidget
1415from PyQt6 .QtCore import Qt , QUrl , QTimer
@@ -94,7 +95,7 @@ def pixelPosToRangeValue(self, pos):
9495class VideoPlayer (QWidget ):
9596 def __init__ (self , video_files ):
9697 super ().__init__ ()
97- self .setWindowTitle ("Video Player " )
98+ self .setWindowTitle ("Reolink Video Review GUI " )
9899
99100 # Create media player
100101 self .media_player = QMediaPlayer ()
@@ -120,6 +121,9 @@ def __init__(self, video_files):
120121 self .play_button = QPushButton ()
121122 self .play_button .setIcon (self .style ().standardIcon (QStyle .StandardPixmap .SP_MediaPlay ))
122123 self .play_button .clicked .connect (self .play_pause )
124+
125+ self .mpv_button = QPushButton ("MPV" )
126+ self .mpv_button .clicked .connect (self .open_in_mpv )
123127
124128 # Create seek slider
125129 self .seek_slider = ClickableSlider (Qt .Orientation .Horizontal )
@@ -138,26 +142,43 @@ def __init__(self, video_files):
138142 speed_label = QLabel ("Speed: 2.0x" )
139143 self .speed_slider .valueChanged .connect (lambda v : speed_label .setText (f"Speed: { v / 100 :.1f} x" ))
140144
141- # Set up layout
142145 main_layout = QHBoxLayout ()
143- left_layout = QVBoxLayout ()
146+ # Create a splitter
147+ splitter = QSplitter (Qt .Orientation .Horizontal )
148+
149+ # Left side (table and open button)
150+ left_widget = QWidget ()
151+ left_layout = QVBoxLayout (left_widget )
144152 left_layout .addWidget (self .video_table )
145153 left_layout .addWidget (self .open_button )
146- main_layout . addLayout ( left_layout )
154+ splitter . addWidget ( left_widget )
147155
148- right_layout = QVBoxLayout ()
149- right_layout .addWidget (self .video_widget )
156+ # Right side (video player and controls)
157+ right_widget = QWidget ()
158+ right_layout = QVBoxLayout (right_widget )
159+ right_layout .addWidget (self .video_widget , 1 )
160+
161+ controls_widget = QWidget ()
162+ controls_layout = QVBoxLayout (controls_widget )
163+
150164 control_layout = QHBoxLayout ()
151165 control_layout .addWidget (self .play_button )
152166 control_layout .addWidget (self .seek_slider )
153- right_layout .addLayout (control_layout )
167+ control_layout .addWidget (self .mpv_button )
168+ controls_layout .addLayout (control_layout )
169+
154170 speed_layout = QHBoxLayout ()
155171 speed_layout .addWidget (QLabel ("Speed:" ))
156172 speed_layout .addWidget (self .speed_slider )
157173 speed_layout .addWidget (speed_label )
158- right_layout .addLayout (speed_layout )
159- main_layout .addLayout (right_layout )
174+ controls_layout .addLayout (speed_layout )
175+ right_layout .addWidget (controls_widget )
176+ splitter .addWidget (right_widget )
177+
178+ # Set initial sizes
179+ splitter .setSizes ([300 , 700 ]) # Adjust these values as needed
160180
181+ main_layout .addWidget (splitter )
161182 self .setLayout (main_layout )
162183 self .add_initial_videos (video_files )
163184
@@ -183,7 +204,14 @@ def add_video(self, video_path):
183204 start_datetime_str = parsed_data ['start_datetime' ].strftime ("%Y-%m-%d %H:%M:%S" )
184205 start_datetime_item = QTableWidgetItem (start_datetime_str )
185206 start_datetime_item .setData (Qt .ItemDataRole .UserRole , parsed_data ['start_datetime' ])
186- self .video_table .setItem (row_position , 0 , QTableWidgetItem (base_file_name ))
207+
208+ # Create the item for the first column with the base file name
209+ file_name_item = QTableWidgetItem (base_file_name )
210+
211+ # Set the full path as tooltip
212+ file_name_item .setToolTip (base_file_name )
213+
214+ self .video_table .setItem (row_position , 0 , file_name_item )
187215 self .video_table .setItem (row_position , 1 , start_datetime_item )
188216 self .video_table .setItem (row_position , 2 , QTableWidgetItem (parsed_data ['end_time' ]))
189217 self .video_table .setItem (row_position , 3 , QTableWidgetItem (f"{ parsed_data ['channel' ]} " ))
@@ -196,14 +224,24 @@ def add_video(self, video_path):
196224 self .video_table .setItem (row_position , 8 , QTableWidgetItem ("✓" if parsed_data ['triggers' ] & VOD_trigger .TIMER else "" ))
197225 self .video_table .setItem (row_position , 9 , QTableWidgetItem (parsed_data ['unknown_hex' ]))
198226
227+ # Set smaller default column widths
228+ self .video_table .setColumnWidth (0 , 120 ) # Video Path
229+ self .video_table .setColumnWidth (1 , 130 ) # Start Datetime
230+ self .video_table .setColumnWidth (2 , 80 ) # End Time
231+ self .video_table .setColumnWidth (3 , 35 ) # Channel
232+ self .video_table .setColumnWidth (4 , 35 ) # Person
233+ self .video_table .setColumnWidth (5 , 35 ) # Vehicle
234+ self .video_table .setColumnWidth (6 , 35 ) # Pet
235+ self .video_table .setColumnWidth (7 , 35 ) # Motion
236+ self .video_table .setColumnWidth (8 , 30 ) # Timer
237+ self .video_table .setColumnWidth (9 , 20 ) # Unknown Hex
238+
199239 # Make the fields non-editable
200240 for column in range (self .video_table .columnCount ()):
201241 item = self .video_table .item (row_position , column )
202242 item .setFlags (item .flags () & ~ Qt .ItemFlag .ItemIsEditable )
203243 else :
204244 print (f"Could not parse file { video_path } " )
205- for i in range (1 , self .video_table .columnCount ()):
206- self .video_table .resizeColumnToContents (i )
207245
208246 def play_video (self , row , column ):
209247 video_path = self .video_table .item (row , 0 ).text ()
@@ -254,6 +292,18 @@ def update_duration(self, duration):
254292 def set_speed (self , speed ):
255293 self .media_player .setPlaybackRate (speed / 100 )
256294
295+ def open_in_mpv (self ):
296+ current_video = self .media_player .source ().toString ()
297+ if current_video :
298+ # Remove the 'file://' prefix if present
299+ video_path = current_video .replace ('file://' , '' )
300+ try :
301+ subprocess .Popen (['mpv' , video_path ])
302+ except FileNotFoundError :
303+ print ("Error: MPV player not found. Make sure it's installed and in your system PATH." )
304+ else :
305+ print ("No video is currently selected." )
306+
257307def read_config (props_path : str ) -> dict :
258308 """Reads in a properties file into variables.
259309
0 commit comments