Includes basic Video editing, OBS intergration, game detecting and clip editor
299 lines
11 KiB
Python
299 lines
11 KiB
Python
import sys
|
|
from PySide6.QtCore import QStandardPaths, Qt, Slot
|
|
from PySide6.QtGui import QAction, QIcon, QKeySequence, QScreen
|
|
from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog,
|
|
QMainWindow, QSlider, QStyle, QToolBar, QPushButton)
|
|
from PySide6.QtMultimedia import (QAudio, QAudioOutput, QMediaFormat,
|
|
QMediaPlayer)
|
|
from PySide6.QtMultimediaWidgets import QVideoWidget
|
|
from moviepy.editor import *
|
|
from youtube_upload import upload_video
|
|
import uuid
|
|
import subprocess
|
|
import os
|
|
|
|
AVI = "video/x-msvideo" # AVI
|
|
|
|
|
|
MP4 = 'video/mp4'
|
|
|
|
|
|
def get_supported_mime_types():
|
|
result = []
|
|
for f in QMediaFormat().supportedFileFormats(QMediaFormat.Decode):
|
|
mime_type = QMediaFormat(f).mimeType()
|
|
result.append(mime_type.name())
|
|
return result
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
|
|
def __init__(self, fileUrl):
|
|
super().__init__()
|
|
|
|
self._url = fileUrl
|
|
|
|
self._playlist = [] # FIXME 6.3: Replace by QMediaPlaylist?
|
|
self._playlist_index = -1
|
|
self._audio_output = QAudioOutput()
|
|
self._player = QMediaPlayer()
|
|
self._player.setAudioOutput(self._audio_output)
|
|
|
|
self._player.errorOccurred.connect(self._player_error)
|
|
|
|
tool_bar = QToolBar()
|
|
self.addToolBar(tool_bar)
|
|
|
|
file_menu = self.menuBar().addMenu("&File")
|
|
icon = QIcon.fromTheme("document-open")
|
|
open_action = QAction(icon, "&Open...", self,
|
|
shortcut=QKeySequence.Open, triggered=self.open)
|
|
file_menu.addAction(open_action)
|
|
tool_bar.addAction(open_action)
|
|
icon = QIcon.fromTheme("application-exit")
|
|
exit_action = QAction(icon, "E&xit", self,
|
|
shortcut="Ctrl+Q", triggered=self.close)
|
|
file_menu.addAction(exit_action)
|
|
|
|
play_menu = self.menuBar().addMenu("&Play")
|
|
style = self.style()
|
|
icon = QIcon.fromTheme("media-playback-start.png",
|
|
style.standardIcon(QStyle.SP_MediaPlay))
|
|
self._play_action = tool_bar.addAction(icon, "Play")
|
|
self._play_action.triggered.connect(self._player.play)
|
|
play_menu.addAction(self._play_action)
|
|
|
|
icon = QIcon.fromTheme("media-skip-backward-symbolic.svg",
|
|
style.standardIcon(QStyle.SP_MediaSkipBackward))
|
|
self._previous_action = tool_bar.addAction(icon, "Previous")
|
|
self._previous_action.triggered.connect(self.previous_clicked)
|
|
play_menu.addAction(self._previous_action)
|
|
|
|
icon = QIcon.fromTheme("media-playback-pause.png",
|
|
style.standardIcon(QStyle.SP_MediaPause))
|
|
self._pause_action = tool_bar.addAction(icon, "Pause")
|
|
self._pause_action.triggered.connect(self._player.pause)
|
|
play_menu.addAction(self._pause_action)
|
|
|
|
icon = QIcon.fromTheme("media-skip-forward-symbolic.svg",
|
|
style.standardIcon(QStyle.SP_MediaSkipForward))
|
|
self._next_action = tool_bar.addAction(icon, "Next")
|
|
self._next_action.triggered.connect(self.next_clicked)
|
|
play_menu.addAction(self._next_action)
|
|
|
|
icon = QIcon.fromTheme("media-playback-stop.png",
|
|
style.standardIcon(QStyle.SP_MediaStop))
|
|
self._stop_action = tool_bar.addAction(icon, "Stop")
|
|
self._stop_action.triggered.connect(self._ensure_stopped)
|
|
play_menu.addAction(self._stop_action)
|
|
|
|
self._scrub_slider = QSlider()
|
|
self._scrub_slider.setOrientation(Qt.Horizontal)
|
|
self._scrub_slider.setMinimum(0)
|
|
self._scrub_slider.setMaximum(100)
|
|
available_width = self.screen().availableGeometry().width()
|
|
self._scrub_slider.setFixedWidth(available_width / 8)
|
|
#self._scrub_slider.setValue(self._audio_output.volume())
|
|
self._scrub_slider.setTickInterval(10)
|
|
#self._scrub_slider.setTickPosition(QSlider.TicksBelow)
|
|
self._scrub_slider.setToolTip("Scrub")
|
|
self._scrub_slider.sliderMoved.connect(self.set_position)
|
|
tool_bar.addWidget(self._scrub_slider)
|
|
|
|
self._audio_output.setVolume(10)
|
|
self._volume_slider = QSlider()
|
|
self._volume_slider.setOrientation(Qt.Horizontal)
|
|
self._volume_slider.setMinimum(0)
|
|
self._volume_slider.setMaximum(100)
|
|
available_width = self.screen().availableGeometry().width()
|
|
self._volume_slider.setFixedWidth(available_width / 10)
|
|
self._volume_slider.setValue(self._audio_output.volume())
|
|
self._volume_slider.setTickInterval(10)
|
|
self._volume_slider.setTickPosition(QSlider.TicksBelow)
|
|
self._volume_slider.setToolTip("Volume")
|
|
self._volume_slider.valueChanged.connect(self.volume_change)
|
|
tool_bar.addWidget(self._volume_slider)
|
|
|
|
self._15SecondClip = QPushButton()
|
|
self._15SecondClip.setText("+ 15 Clip")
|
|
self._15SecondClip.setToolTip("+15 Second Clip")
|
|
self._15SecondClip.pressed.connect(self.clip_15_seconds)
|
|
tool_bar.addWidget(self._15SecondClip)
|
|
|
|
self._30SecondClip = QPushButton()
|
|
self._30SecondClip.setText("+ 30 Clip")
|
|
self._30SecondClip.setToolTip("+30 Second Clip")
|
|
self._30SecondClip.pressed.connect(self.clip_30_seconds)
|
|
tool_bar.addWidget(self._30SecondClip)
|
|
|
|
self._60SecondClip = QPushButton()
|
|
self._60SecondClip.setText("+ 60 Clip")
|
|
self._60SecondClip.setToolTip("+60 Second Clip")
|
|
self._60SecondClip.pressed.connect(self.clip_60_seconds)
|
|
tool_bar.addWidget(self._60SecondClip)
|
|
|
|
self._upload = QPushButton()
|
|
self._upload.setText("YT")
|
|
self._upload.setToolTip("yt")
|
|
self._upload.pressed.connect(self.upload_video_youtube)
|
|
tool_bar.addWidget(self._upload)
|
|
|
|
self._copy = QPushButton()
|
|
self._copy.setText("Copy")
|
|
self._copy.setToolTip("Copy")
|
|
self._copy.pressed.connect(self.copy)
|
|
tool_bar.addWidget(self._copy)
|
|
|
|
about_menu = self.menuBar().addMenu("&About")
|
|
about_qt_act = QAction("About &Qt", self, triggered=qApp.aboutQt)
|
|
about_menu.addAction(about_qt_act)
|
|
|
|
self._video_widget = QVideoWidget()
|
|
self.setCentralWidget(self._video_widget)
|
|
self._player.playbackStateChanged.connect(self.update_buttons)
|
|
self._player.setVideoOutput(self._video_widget)
|
|
|
|
self.update_buttons(self._player.playbackState())
|
|
self._mime_types = []
|
|
|
|
self._player.positionChanged.connect(self.position_changed)
|
|
self._player.durationChanged.connect(self.duration_changed)
|
|
|
|
if self._url is not None:
|
|
self._playlist.append(self._url )
|
|
self._playlist_index = len(self._playlist) - 1
|
|
self._player.setSource(self._url)
|
|
self._player.play()
|
|
self._player.pause()
|
|
|
|
def closeEvent(self, event):
|
|
self._ensure_stopped()
|
|
event.accept()
|
|
|
|
def position_changed(self, position):
|
|
self._scrub_slider.setValue(position)
|
|
|
|
def duration_changed(self, duration):
|
|
self._scrub_slider.setRange(0, duration)
|
|
|
|
def set_position(self):
|
|
self._player.setPosition(self._scrub_slider.value())
|
|
|
|
def volume_change(self, position):
|
|
self._audio_output.setVolume(position / 100)
|
|
|
|
def closeEvent(self, event):
|
|
event.ignore()
|
|
self._player.stop()
|
|
self.hide()
|
|
|
|
@Slot()
|
|
def open(self):
|
|
self._ensure_stopped()
|
|
file_dialog = QFileDialog(self)
|
|
|
|
is_windows = sys.platform == 'win32'
|
|
if not self._mime_types:
|
|
self._mime_types = get_supported_mime_types()
|
|
if (is_windows and AVI not in self._mime_types):
|
|
self._mime_types.append(AVI)
|
|
elif MP4 not in self._mime_types:
|
|
self._mime_types.append(MP4)
|
|
|
|
file_dialog.setMimeTypeFilters(self._mime_types)
|
|
|
|
default_mimetype = AVI if is_windows else MP4
|
|
if default_mimetype in self._mime_types:
|
|
file_dialog.selectMimeTypeFilter(default_mimetype)
|
|
|
|
movies_location = QStandardPaths.writableLocation(QStandardPaths.MoviesLocation)
|
|
file_dialog.setDirectory(movies_location)
|
|
if file_dialog.exec() == QDialog.Accepted:
|
|
self._url = file_dialog.selectedUrls()[0].toString()
|
|
self._playlist.append(self._url )
|
|
self._playlist_index = len(self._playlist) - 1
|
|
self._player.setSource(self._url)
|
|
self._player.play()
|
|
|
|
@Slot()
|
|
def _ensure_stopped(self):
|
|
if self._player.playbackState() != QMediaPlayer.StoppedState:
|
|
self._player.stop()
|
|
|
|
@Slot()
|
|
def previous_clicked(self):
|
|
# Go to previous track if we are within the first 5 seconds of playback
|
|
# Otherwise, seek to the beginning.
|
|
if self._player.position() <= 5000 and self._playlist_index > 0:
|
|
self._playlist_index -= 1
|
|
self._playlist.previous()
|
|
self._player.setSource(self._playlist[self._playlist_index])
|
|
else:
|
|
self._player.setPosition(0)
|
|
|
|
@Slot()
|
|
def next_clicked(self):
|
|
if self._playlist_index < len(self._playlist) - 1:
|
|
self._playlist_index += 1
|
|
self._player.setSource(self._playlist[self._playlist_index])
|
|
|
|
@Slot("QMediaPlayer::PlaybackState")
|
|
def update_buttons(self, state):
|
|
media_count = len(self._playlist)
|
|
self._play_action.setEnabled(media_count > 0
|
|
and state != QMediaPlayer.PlayingState)
|
|
self._pause_action.setEnabled(state == QMediaPlayer.PlayingState)
|
|
self._stop_action.setEnabled(state != QMediaPlayer.StoppedState)
|
|
self._previous_action.setEnabled(self._player.position() > 0)
|
|
self._next_action.setEnabled(media_count > 1)
|
|
|
|
def show_status_message(self, message):
|
|
self.statusBar().showMessage(message, 5000)
|
|
|
|
@Slot("QMediaPlayer::Error", str)
|
|
def _player_error(self, error, error_string):
|
|
print(error_string, file=sys.stderr)
|
|
self.show_status_message(error_string)
|
|
|
|
def clip_15_seconds(self):
|
|
self.clip_video(15)
|
|
|
|
def clip_30_seconds(self):
|
|
self.clip_video(30)
|
|
|
|
def clip_60_seconds(self):
|
|
self.clip_video(60)
|
|
|
|
def clip_video(self, clip_length):
|
|
start_time = self._player.position() / 1000
|
|
|
|
dirname = os.path.dirname(__file__)
|
|
output_filepath = os.path.join(os.path.expanduser('~'), "Videos/gameplay/clips/"+str(uuid.uuid4())+".mp4")
|
|
|
|
# Perform the saving operation with the selected file path
|
|
clip = VideoFileClip(self._url).subclip(start_time, start_time + clip_length)
|
|
clip.write_videofile(output_filepath)
|
|
clip.close()
|
|
|
|
self.clip_window = MainWindow(output_filepath)
|
|
self.clip_window.show()
|
|
|
|
def upload_video_youtube(self):
|
|
upload_video(self._url)
|
|
|
|
def copy(self):
|
|
video_type = self._url.split(".")[-1]
|
|
subprocess.run("""echo "{url}" | xclip -sel clip -t text/uri-list -i""".format(video_type=video_type, url=self._url),shell=True)
|
|
|
|
|
|
def open_media_screen():
|
|
app = QApplication(sys.argv)
|
|
main_win = MainWindow('~/Videos/gameplay/2023-07-02 21-17-35.mkv')
|
|
available_geometry = main_win.screen().availableGeometry()
|
|
main_win.resize(available_geometry.width() / 3,
|
|
available_geometry.height() / 2)
|
|
main_win.show()
|
|
sys.exit(app.exec())
|
|
|
|
if __name__ == "__main__":
|
|
open_media_screen() |