import sys
from PySide6.QtCore import Qt, QUrl
from PySide6.QtGui import QAction, QKeySequence, QPalette, QColor
from PySide6.QtWidgets import (
QApplication, QWidget, QFileDialog, QHBoxLayout, QVBoxLayout, QLabel,
QPushButton, QSlider, QMessageBox, QSizePolicy
)
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
from PySide6.QtMultimediaWidgets import QVideoWidget
def apply_dark_palette(app: QApplication):
app.setStyle("Fusion")
p = QPalette()
p.setColor(QPalette.Window, QColor(37, 37, 38))
p.setColor(QPalette.WindowText, Qt.white)
p.setColor(QPalette.Base, QColor(30, 30, 30))
p.setColor(QPalette.AlternateBase, QColor(45, 45, 48))
p.setColor(QPalette.ToolTipBase, Qt.white)
p.setColor(QPalette.ToolTipText, Qt.white)
p.setColor(QPalette.Text, Qt.white)
p.setColor(QPalette.Button, QColor(45, 45, 48))
p.setColor(QPalette.ButtonText, Qt.white)
p.setColor(QPalette.BrightText, Qt.red)
p.setColor(QPalette.Highlight, QColor(14, 99, 156))
p.setColor(QPalette.HighlightedText, Qt.white)
app.setPalette(p)
app.setStyleSheet("""
QWidget { color: #ddd; }
QPushButton { background:#3c3c3c; border:1px solid #555; padding:6px 10px; border-radius:6px; }
QPushButton:hover { background:#4a4a4a; }
QPushButton:pressed { background:#2f2f2f; }
QSlider::groove:horizontal { height:6px; background:#2b2b2b; border-radius:3px; }
QSlider::handle:horizontal { width:14px; height:14px; margin:-5px 0; border-radius:7px; background:#aaaaaa; }
QLabel { color:#ccc; }
""")
def ms_to_hhmmss(ms: int) -> str:
if ms < 0:
ms = 0
s = ms // 1000
h, m, s = s // 3600, (s % 3600) // 60, s % 60
return f"{h:02d}:{m:02d}:{s:02d}" if h else f"{m:02d}:{s:02d}"
class VideoWindow(QWidget):
def __init__(self, path: str | None = None):
super().__init__()
self.setWindowTitle("影片播放器")
self.resize(960, 540)
# 視訊與媒體核心
self.video = QVideoWidget()
self.player = QMediaPlayer(self)
self.audio = QAudioOutput(self)
self.player.setVideoOutput(self.video)
self.player.setAudioOutput(self.audio)
self.audio.setVolume(0.6)
# 控制列元件(單列)
self.btn_open = QPushButton("開啟")
self.btn_play = QPushButton("播放")
self.btn_stop = QPushButton("停止")
self.btn_mute = QPushButton("靜音")
self.progress = QSlider(Qt.Horizontal)
self.progress.setRange(0, 0)
self.progress.setSingleStep(1000) # 1 秒
self.progress.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.time_label = QLabel("00:00 / 00:00")
self.time_label.setMinimumWidth(140)
self.vol_label = QLabel("音量")
self.vol = QSlider(Qt.Horizontal)
self.vol.setRange(0, 100)
self.vol.setValue(int(self.audio.volume() * 100))
self.vol.setFixedWidth(120)
self.btn_full = QPushButton("全螢幕")
# 版面配置:上方影片、下方單列控制
root = QVBoxLayout(self)
root.setContentsMargins(8, 8, 8, 8)
root.addWidget(self.video)
bar = QHBoxLayout()
bar.setSpacing(8)
bar.addWidget(self.btn_open)
bar.addWidget(self.btn_play)
bar.addWidget(self.btn_stop)
bar.addSpacing(6)
bar.addWidget(self.progress, 1)
bar.addWidget(self.time_label)
bar.addSpacing(6)
bar.addWidget(self.btn_mute)
bar.addWidget(self.vol_label)
bar.addWidget(self.vol)
bar.addSpacing(6)
bar.addWidget(self.btn_full)
root.addLayout(bar)
# 事件連結
self.btn_open.clicked.connect(self.open_file)
self.btn_play.clicked.connect(self.toggle_play)
self.btn_stop.clicked.connect(self.stop)
self.btn_mute.clicked.connect(self.toggle_mute)
self.btn_full.clicked.connect(self.toggle_fullscreen)
self.progress.sliderMoved.connect(self.seek_to)
self.progress.sliderPressed.connect(self._pause_for_scrub)
self.progress.sliderReleased.connect(self._resume_after_scrub)
self.vol.valueChanged.connect(self.on_volume)
self.player.positionChanged.connect(self.on_position)
self.player.durationChanged.connect(self.on_duration)
self.player.errorOccurred.connect(self.on_error)
# 快捷鍵 & 視窗雙擊全螢幕
self._make_shortcuts()
self.video.mouseDoubleClickEvent = lambda e: self.toggle_fullscreen()
if path:
self.load_and_play(path)
# ---- 控制 ----
def open_file(self):
path, _ = QFileDialog.getOpenFileName(
self, "選擇影片檔案", "", "影片檔案 (*.mp4 *.mov *.mkv *.avi *.m4v);;所有檔案 (*)"
)
if path:
self.load_and_play(path)
def load_and_play(self, path: str):
self.player.setSource(QUrl.fromLocalFile(path))
self.player.play()
self.btn_play.setText("暫停")
self.setWindowTitle(f"影片播放器 - {path}")
def toggle_play(self):
if self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
self.player.pause()
self.btn_play.setText("播放")
else:
self.player.play()
self.btn_play.setText("暫停")
def stop(self):
self.player.stop()
self.btn_play.setText("播放")
def toggle_mute(self):
self.audio.setMuted(not self.audio.isMuted())
self.btn_mute.setText("解除靜音" if self.audio.isMuted() else "靜音")
def on_volume(self, v: int):
self.audio.setVolume(v / 100.0)
if v == 0 and not self.audio.isMuted():
self.audio.setMuted(True)
self.btn_mute.setText("解除靜音")
elif v > 0 and self.audio.isMuted():
self.audio.setMuted(False)
self.btn_mute.setText("靜音")
def seek_to(self, slider_value: int):
self.player.setPosition(slider_value)
def _pause_for_scrub(self):
self._was_playing = (
self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState
)
if self._was_playing:
self.player.pause()
def _resume_after_scrub(self):
if getattr(self, "_was_playing", False):
self.player.play()
def on_position(self, pos_ms: int):
if not self.progress.isSliderDown():
self.progress.setValue(pos_ms)
self.time_label.setText(
f"{ms_to_hhmmss(pos_ms)} / {ms_to_hhmmss(self.player.duration())}"
)
def on_duration(self, dur_ms: int):
self.progress.setRange(0, dur_ms)
def on_error(self, err, what):
if err != QMediaPlayer.Error.NoError:
QMessageBox.critical(self, "播放錯誤", f"發生錯誤:{what}")
def toggle_fullscreen(self):
self.video.setFullScreen(not self.video.isFullScreen())
# ---- 快捷鍵 ----
def _make_shortcuts(self):
def add_shortcut(key, func):
a = QAction(self); a.setShortcut(QKeySequence(key)); a.triggered.connect(func); self.addAction(a)
add_shortcut(Qt.Key_Space, self.toggle_play)
add_shortcut(Qt.Key_F, self.toggle_fullscreen)
add_shortcut(Qt.Key_Left, lambda: self.player.setPosition(max(0, self.player.position() - 5000)))
add_shortcut(Qt.Key_Right, lambda: self.player.setPosition(min(self.player.duration(), self.player.position() + 5000)))
add_shortcut(Qt.Key_Up, lambda: self.vol.setValue(min(100, self.vol.value() + 5)))
add_shortcut(Qt.Key_Down, lambda: self.vol.setValue(max(0, self.vol.value() - 5)))
if __name__ == "__main__":
app = QApplication(sys.argv)
apply_dark_palette(app)
path = sys.argv[1] if len(sys.argv) > 1 else None
w = VideoWindow(path)
w.show()
sys.exit(app.exec())