forked from openlp/openlp
699 lines
32 KiB
Python
699 lines
32 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
##########################################################################
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
# ---------------------------------------------------------------------- #
|
|
# Copyright (c) 2008-2020 OpenLP Developers #
|
|
# ---------------------------------------------------------------------- #
|
|
# This program is free software: you can redistribute it and/or modify #
|
|
# it under the terms of the GNU General Public License as published by #
|
|
# the Free Software Foundation, either version 3 of the License, or #
|
|
# (at your option) any later version. #
|
|
# #
|
|
# This program is distributed in the hope that it will be useful, #
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
|
# GNU General Public License for more details. #
|
|
# #
|
|
# You should have received a copy of the GNU General Public License #
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
|
##########################################################################
|
|
import logging
|
|
import os
|
|
import re
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from time import sleep
|
|
|
|
from PyQt5 import QtCore, QtWidgets
|
|
|
|
from openlp.core.common import is_linux, is_macosx, is_win
|
|
from openlp.core.common.i18n import translate
|
|
from openlp.core.common.mixins import RegistryProperties
|
|
from openlp.core.lib.ui import critical_error_message_box
|
|
from openlp.core.ui.icons import UiIcons
|
|
from openlp.core.ui.media.vlcplayer import get_vlc
|
|
from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector
|
|
|
|
|
|
if is_win():
|
|
from win32com.client import Dispatch
|
|
|
|
if is_linux():
|
|
import dbus
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryProperties):
|
|
"""
|
|
Class to manage the clip selection
|
|
"""
|
|
log.info('{name} MediaClipSelectorForm loaded'.format(name=__name__))
|
|
|
|
def __init__(self, media_item, parent, manager):
|
|
"""
|
|
Constructor
|
|
"""
|
|
super(MediaClipSelectorForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
|
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
|
self.vlc_instance = None
|
|
self.vlc_media_player = None
|
|
self.vlc_media = None
|
|
self.timer = None
|
|
self.audio_cd_tracks = None
|
|
self.audio_cd = False
|
|
self.playback_length = 0
|
|
self.media_item = media_item
|
|
self.setup_ui(self)
|
|
# setup play/pause icon
|
|
self.play_icon = UiIcons().play
|
|
self.pause_icon = UiIcons().pause
|
|
|
|
def reject(self):
|
|
"""
|
|
Exit Dialog and do not save
|
|
"""
|
|
log.debug('MediaClipSelectorForm.reject')
|
|
# Tear down vlc
|
|
if self.vlc_media_player:
|
|
self.vlc_media_player.stop()
|
|
self.vlc_media_player.release()
|
|
self.vlc_media_player = None
|
|
if self.vlc_instance:
|
|
self.vlc_instance.release()
|
|
self.vlc_instance = None
|
|
if self.vlc_media:
|
|
self.vlc_media.release()
|
|
self.vlc_media = None
|
|
return QtWidgets.QDialog.reject(self)
|
|
|
|
def exec(self):
|
|
"""
|
|
Start dialog
|
|
"""
|
|
self.reset_ui()
|
|
self.setup_vlc()
|
|
return QtWidgets.QDialog.exec(self)
|
|
|
|
def reset_ui(self):
|
|
"""
|
|
Reset the UI to default values
|
|
"""
|
|
self.playback_length = 0
|
|
self.position_slider.setMinimum(0)
|
|
self.disable_all()
|
|
self.toggle_disable_load_media(False)
|
|
self.subtitle_tracks_combobox.clear()
|
|
self.audio_tracks_combobox.clear()
|
|
self.titles_combo_box.clear()
|
|
time = QtCore.QTime(0, 0, 0)
|
|
self.start_position_edit.setTime(time)
|
|
self.end_timeedit.setTime(time)
|
|
self.position_timeedit.setTime(time)
|
|
|
|
def setup_vlc(self):
|
|
"""
|
|
Setup VLC instance and mediaplayer
|
|
"""
|
|
vlc = get_vlc()
|
|
self.vlc_instance = vlc.Instance()
|
|
# creating an empty vlc media player
|
|
self.vlc_media_player = self.vlc_instance.media_player_new()
|
|
# The media player has to be 'connected' to the QFrame.
|
|
# (otherwise a video would be displayed in it's own window)
|
|
# This is platform specific!
|
|
# You have to give the id of the QFrame (or similar object)
|
|
# to vlc, different platforms have different functions for this.
|
|
win_id = int(self.preview_frame.winId())
|
|
if is_win():
|
|
self.vlc_media_player.set_hwnd(win_id)
|
|
elif is_macosx():
|
|
# We have to use 'set_nsobject' since Qt5 on OSX uses Cocoa
|
|
# framework and not the old Carbon.
|
|
self.vlc_media_player.set_nsobject(win_id)
|
|
else:
|
|
# for Linux using the X Server
|
|
self.vlc_media_player.set_xwindow(win_id)
|
|
self.vlc_media = None
|
|
# Setup timer every 100 ms to update position
|
|
self.timer = QtCore.QTimer(self)
|
|
self.timer.timeout.connect(self.update_position)
|
|
self.timer.start(100)
|
|
self.find_optical_devices()
|
|
self.audio_cd = False
|
|
self.audio_cd_tracks = None
|
|
|
|
def detect_audio_cd(self, path):
|
|
"""
|
|
Detects is the given path is an audio CD
|
|
|
|
:param path: Path to the device to be tested.
|
|
:return: True if it was an audio CD else False.
|
|
"""
|
|
vlc = get_vlc()
|
|
# Detect by trying to play it as a CD
|
|
self.vlc_media = self.vlc_instance.media_new_location('cdda://' + path)
|
|
self.vlc_media_player.set_media(self.vlc_media)
|
|
self.vlc_media_player.play()
|
|
# Wait for media to start playing. In this case VLC actually returns an error.
|
|
self.media_state_wait(vlc.State.Playing)
|
|
self.vlc_media_player.set_pause(1)
|
|
# If subitems exists, this is a CD
|
|
self.audio_cd_tracks = self.vlc_media.subitems()
|
|
if not self.audio_cd_tracks or self.audio_cd_tracks.count() < 1:
|
|
return False
|
|
# Insert into titles_combo_box
|
|
self.titles_combo_box.clear()
|
|
for i in range(self.audio_cd_tracks.count()):
|
|
item = self.audio_cd_tracks.item_at_index(i)
|
|
item_title = item.get_meta(vlc.Meta.Title)
|
|
self.titles_combo_box.addItem(item_title, i)
|
|
self.vlc_media_player.set_media(self.audio_cd_tracks.item_at_index(0))
|
|
self.audio_cd = True
|
|
self.titles_combo_box.setDisabled(False)
|
|
self.titles_combo_box.setCurrentIndex(0)
|
|
self.on_titles_combo_box_currentIndexChanged(0)
|
|
|
|
return True
|
|
|
|
@QtCore.pyqtSlot(bool)
|
|
def on_load_disc_button_clicked(self, clicked):
|
|
"""
|
|
Load the media when the load-button has been clicked
|
|
|
|
:param clicked: Given from signal, not used.
|
|
"""
|
|
log.debug('on_load_disc_button_clicked')
|
|
vlc = get_vlc()
|
|
self.disable_all()
|
|
self.application.set_busy_cursor()
|
|
path = self.media_path_combobox.currentText()
|
|
# Check if given path is non-empty and exists before starting VLC
|
|
if not path:
|
|
log.debug('no given path')
|
|
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm', 'No path was given'))
|
|
self.toggle_disable_load_media(False)
|
|
self.application.set_normal_cursor()
|
|
return
|
|
if not os.path.exists(path):
|
|
log.debug('Given path does not exist')
|
|
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
|
|
'Given path does not exist'))
|
|
self.toggle_disable_load_media(False)
|
|
self.application.set_normal_cursor()
|
|
return
|
|
# VLC behaves a bit differently on windows and linux when loading, which creates problems when trying to
|
|
# detect if we're dealing with a DVD or CD, so we use different loading approaches depending on the OS.
|
|
if is_win():
|
|
# If the given path is in the format "D:\" or "D:", prefix it with "/" to make VLC happy
|
|
pattern = re.compile(r'^\w:\\\\*$')
|
|
if pattern.match(path):
|
|
path = '/' + path
|
|
self.vlc_media = self.vlc_instance.media_new_location('dvd://' + path)
|
|
else:
|
|
self.vlc_media = self.vlc_instance.media_new_path(path)
|
|
if not self.vlc_media:
|
|
log.debug('vlc media player is none')
|
|
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
|
|
'An error happened during initialization of VLC player'))
|
|
self.toggle_disable_load_media(False)
|
|
self.application.set_normal_cursor()
|
|
return
|
|
# put the media in the media player
|
|
self.vlc_media_player.set_media(self.vlc_media)
|
|
self.vlc_media_player.audio_set_mute(True)
|
|
# start playback to get vlc to parse the media
|
|
if self.vlc_media_player.play() < 0:
|
|
log.debug('vlc play returned error')
|
|
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
|
|
'VLC player failed playing the media'))
|
|
self.toggle_disable_load_media(False)
|
|
self.application.set_normal_cursor()
|
|
self.vlc_media_player.audio_set_mute(False)
|
|
return
|
|
self.vlc_media_player.audio_set_mute(True)
|
|
if not self.media_state_wait(vlc.State.Playing):
|
|
# Tests if this is an audio CD
|
|
if not self.detect_audio_cd(path):
|
|
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
|
|
'VLC player failed playing the media'))
|
|
self.toggle_disable_load_media(False)
|
|
self.application.set_normal_cursor()
|
|
self.vlc_media_player.audio_set_mute(False)
|
|
return
|
|
# pause
|
|
self.vlc_media_player.set_time(0)
|
|
self.vlc_media_player.set_pause(1)
|
|
self.media_state_wait(vlc.State.Paused)
|
|
self.toggle_disable_load_media(False)
|
|
self.application.set_normal_cursor()
|
|
self.vlc_media_player.audio_set_mute(False)
|
|
if not self.audio_cd:
|
|
# Temporarily disable signals
|
|
self.blockSignals(True)
|
|
# Get titles, insert in combobox
|
|
titles = self.vlc_media_player.video_get_title_description()
|
|
self.titles_combo_box.clear()
|
|
for title in titles:
|
|
self.titles_combo_box.addItem(title.name.decode(), title.id)
|
|
# Re-enable signals
|
|
self.blockSignals(False)
|
|
# Main title is usually title #1
|
|
if len(titles) > 1:
|
|
self.titles_combo_box.setCurrentIndex(1)
|
|
# Enable audio track combobox if anything is in it
|
|
if len(titles) > 0:
|
|
self.titles_combo_box.setDisabled(False)
|
|
log.debug('load_disc_button end - '
|
|
'vlc_media_player state: {state}'.format(state=self.vlc_media_player.get_state()))
|
|
|
|
@QtCore.pyqtSlot(bool)
|
|
def on_play_button_clicked(self, clicked):
|
|
"""
|
|
Toggle the playback
|
|
|
|
:param clicked: Given from signal, not used.
|
|
"""
|
|
vlc = get_vlc()
|
|
if self.vlc_media_player.get_state() == vlc.State.Playing:
|
|
self.vlc_media_player.pause()
|
|
self.play_button.setIcon(self.play_icon)
|
|
else:
|
|
self.vlc_media_player.play()
|
|
self.media_state_wait(vlc.State.Playing)
|
|
self.play_button.setIcon(self.pause_icon)
|
|
|
|
@QtCore.pyqtSlot(bool)
|
|
def on_set_start_button_clicked(self, clicked):
|
|
"""
|
|
Copy the current player position to start_position_edit
|
|
|
|
:param clicked: Given from signal, not used.
|
|
"""
|
|
vlc_ms_pos = self.vlc_media_player.get_time()
|
|
time = QtCore.QTime(0, 0, 0)
|
|
new_pos_time = time.addMSecs(vlc_ms_pos)
|
|
self.start_position_edit.setTime(new_pos_time)
|
|
# If start time is after end time, update end time.
|
|
end_time = self.end_timeedit.time()
|
|
if end_time < new_pos_time:
|
|
self.end_timeedit.setTime(new_pos_time)
|
|
|
|
@QtCore.pyqtSlot(bool)
|
|
def on_set_end_button_clicked(self, clicked):
|
|
"""
|
|
Copy the current player position to end_timeedit
|
|
|
|
:param clicked: Given from signal, not used.
|
|
"""
|
|
vlc_ms_pos = self.vlc_media_player.get_time()
|
|
time = QtCore.QTime(0, 0, 0)
|
|
new_pos_time = time.addMSecs(vlc_ms_pos)
|
|
self.end_timeedit.setTime(new_pos_time)
|
|
# If start time is after end time, update start time.
|
|
start_time = self.start_position_edit.time()
|
|
if start_time > new_pos_time:
|
|
self.start_position_edit.setTime(new_pos_time)
|
|
|
|
@QtCore.pyqtSlot(QtCore.QTime)
|
|
def on_start_timeedit_timeChanged(self, new_time):
|
|
"""
|
|
Called when start_position_edit is changed manually
|
|
|
|
:param new_time: The new time
|
|
"""
|
|
# If start time is after end time, update end time.
|
|
end_time = self.end_timeedit.time()
|
|
if end_time < new_time:
|
|
self.end_timeedit.setTime(new_time)
|
|
|
|
@QtCore.pyqtSlot(QtCore.QTime)
|
|
def on_end_timeedit_timeChanged(self, new_time):
|
|
"""
|
|
Called when end_timeedit is changed manually
|
|
|
|
:param new_time: The new time
|
|
"""
|
|
# If start time is after end time, update start time.
|
|
start_time = self.start_position_edit.time()
|
|
if start_time > new_time:
|
|
self.start_position_edit.setTime(new_time)
|
|
|
|
@QtCore.pyqtSlot(bool)
|
|
def on_jump_end_button_clicked(self, clicked):
|
|
"""
|
|
Set the player position to the position stored in end_timeedit
|
|
|
|
:param clicked: Given from signal, not used.
|
|
"""
|
|
end_time = self.end_timeedit.time()
|
|
end_time_ms = end_time.hour() * 60 * 60 * 1000 + \
|
|
end_time.minute() * 60 * 1000 + \
|
|
end_time.second() * 1000 + \
|
|
end_time.msec()
|
|
self.vlc_media_player.set_time(end_time_ms)
|
|
|
|
@QtCore.pyqtSlot(bool)
|
|
def on_jump_start_button_clicked(self, clicked):
|
|
"""
|
|
Set the player position to the position stored in start_position_edit
|
|
|
|
:param clicked: Given from signal, not used.
|
|
"""
|
|
start_time = self.start_position_edit.time()
|
|
start_time_ms = start_time.hour() * 60 * 60 * 1000 + \
|
|
start_time.minute() * 60 * 1000 + \
|
|
start_time.second() * 1000 + \
|
|
start_time.msec()
|
|
self.vlc_media_player.set_time(start_time_ms)
|
|
|
|
@QtCore.pyqtSlot(int)
|
|
def on_titles_combo_box_currentIndexChanged(self, index):
|
|
"""
|
|
When a new title is chosen, it is loaded by VLC and info about audio and subtitle tracks is reloaded
|
|
|
|
:param index: The index of the newly chosen title track.
|
|
"""
|
|
log.debug('in on_titles_combo_box_changed, index: {index:d}'.format(index=index))
|
|
vlc = get_vlc()
|
|
if not self.vlc_media_player:
|
|
log.error('vlc_media_player was None')
|
|
return
|
|
self.application.set_busy_cursor()
|
|
if self.audio_cd:
|
|
self.vlc_media = self.audio_cd_tracks.item_at_index(index)
|
|
self.vlc_media_player.set_media(self.vlc_media)
|
|
self.vlc_media_player.set_time(0)
|
|
self.vlc_media_player.play()
|
|
if not self.media_state_wait(vlc.State.Playing):
|
|
log.error('Could not start playing audio cd, needed to get track info')
|
|
self.application.set_normal_cursor()
|
|
return
|
|
self.vlc_media_player.audio_set_mute(True)
|
|
# pause
|
|
self.vlc_media_player.set_time(0)
|
|
self.vlc_media_player.set_pause(1)
|
|
self.vlc_media_player.audio_set_mute(False)
|
|
self.application.set_normal_cursor()
|
|
self.toggle_disable_player(False)
|
|
else:
|
|
self.vlc_media_player.set_title(index)
|
|
self.vlc_media_player.set_time(0)
|
|
self.vlc_media_player.play()
|
|
if not self.media_state_wait(vlc.State.Playing):
|
|
log.error('Could not start playing dvd, needed to get track info')
|
|
self.application.set_normal_cursor()
|
|
return
|
|
self.vlc_media_player.audio_set_mute(True)
|
|
# Get audio tracks
|
|
audio_tracks = self.vlc_media_player.audio_get_track_description()
|
|
log.debug('number of audio tracks: {tracks:d}'.format(tracks=len(audio_tracks)))
|
|
# Clear the audio track combobox, insert new tracks
|
|
self.audio_tracks_combobox.clear()
|
|
for audio_track in audio_tracks:
|
|
self.audio_tracks_combobox.addItem(audio_track[1].decode(), audio_track[0])
|
|
# Enable audio track combobox if anything is in it
|
|
if len(audio_tracks) > 0:
|
|
self.audio_tracks_combobox.setDisabled(False)
|
|
# First track is "deactivated", so set to next if it exists
|
|
if len(audio_tracks) > 1:
|
|
self.audio_tracks_combobox.setCurrentIndex(1)
|
|
# Get subtitle tracks, insert in combobox
|
|
subtitles_tracks = self.vlc_media_player.video_get_spu_description()
|
|
self.subtitle_tracks_combobox.clear()
|
|
for subtitle_track in subtitles_tracks:
|
|
self.subtitle_tracks_combobox.addItem(subtitle_track[1].decode(), subtitle_track[0])
|
|
# Enable subtitle track combobox is anything in it
|
|
if len(subtitles_tracks) > 0:
|
|
self.subtitle_tracks_combobox.setDisabled(False)
|
|
self.vlc_media_player.audio_set_mute(False)
|
|
self.vlc_media_player.set_pause(1)
|
|
# If a title or audio track is available the player is enabled
|
|
if self.titles_combo_box.count() > 0 or len(audio_tracks) > 0:
|
|
self.toggle_disable_player(False)
|
|
# Set media length info
|
|
self.playback_length = self.vlc_media_player.get_length()
|
|
log.debug('playback_length: {length:d} ms'.format(length=self.playback_length))
|
|
# if length is 0, wait a bit, maybe vlc will change its mind...
|
|
loop_count = 0
|
|
while self.playback_length == 0 and loop_count < 20:
|
|
sleep(0.1)
|
|
self.playback_length = self.vlc_media_player.get_length()
|
|
loop_count += 1
|
|
log.debug('in loop, playback_length: {length:d} ms'.format(length=self.playback_length))
|
|
self.position_slider.setMaximum(self.playback_length)
|
|
# setup start and end time
|
|
rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0)
|
|
time = QtCore.QTime(0, 0, 0)
|
|
playback_length_time = time.addMSecs(rounded_vlc_ms_length)
|
|
self.start_position_edit.setMaximumTime(playback_length_time)
|
|
self.end_timeedit.setMaximumTime(playback_length_time)
|
|
self.end_timeedit.setTime(playback_length_time)
|
|
# Pause once again, just to make sure
|
|
loop_count = 0
|
|
while self.vlc_media_player.get_state() == vlc.State.Playing and loop_count < 20:
|
|
sleep(0.1)
|
|
self.vlc_media_player.set_pause(1)
|
|
loop_count += 1
|
|
log.debug('titles_combo_box end - '
|
|
'vlc_media_player state: {state}'.format(state=self.vlc_media_player.get_state()))
|
|
self.application.set_normal_cursor()
|
|
|
|
@QtCore.pyqtSlot(int)
|
|
def on_audio_tracks_combobox_currentIndexChanged(self, index):
|
|
"""
|
|
When a new audio track is chosen update audio track bing played by VLC
|
|
|
|
:param index: The index of the newly chosen audio track.
|
|
"""
|
|
if not self.vlc_media_player:
|
|
return
|
|
audio_track = self.audio_tracks_combobox.itemData(index)
|
|
log.debug('in on_audio_tracks_combobox_currentIndexChanged, '
|
|
'index: {index:d} audio_track: {tracks}'.format(index=index, tracks=audio_track))
|
|
if audio_track and int(audio_track) > 0:
|
|
self.vlc_media_player.audio_set_track(int(audio_track))
|
|
|
|
@QtCore.pyqtSlot(int)
|
|
def on_subtitle_tracks_combobox_currentIndexChanged(self, index):
|
|
"""
|
|
When a new subtitle track is chosen update subtitle track bing played by VLC
|
|
|
|
:param index: The index of the newly chosen subtitle.
|
|
"""
|
|
if not self.vlc_media_player:
|
|
return
|
|
subtitle_track = self.subtitle_tracks_combobox.itemData(index)
|
|
if subtitle_track:
|
|
self.vlc_media_player.video_set_spu(int(subtitle_track))
|
|
|
|
def on_position_slider_sliderMoved(self, position):
|
|
"""
|
|
Set player position according to new slider position.
|
|
|
|
:param position: Position to seek to.
|
|
"""
|
|
self.vlc_media_player.set_time(position)
|
|
|
|
def update_position(self):
|
|
"""
|
|
Update slider position and displayed time according to VLC player position.
|
|
"""
|
|
if self.vlc_media_player:
|
|
vlc_ms_pos = self.vlc_media_player.get_time()
|
|
rounded_vlc_ms_pos = int(round(vlc_ms_pos / 100.0) * 100.0)
|
|
time = QtCore.QTime(0, 0, 0)
|
|
new_pos_time = time.addMSecs(rounded_vlc_ms_pos)
|
|
self.position_timeedit.setTime(new_pos_time)
|
|
self.position_slider.setSliderPosition(vlc_ms_pos)
|
|
|
|
def disable_all(self):
|
|
"""
|
|
Disable all elements in the dialog
|
|
"""
|
|
self.toggle_disable_load_media(True)
|
|
self.titles_combo_box.setDisabled(True)
|
|
self.audio_tracks_combobox.setDisabled(True)
|
|
self.subtitle_tracks_combobox.setDisabled(True)
|
|
self.toggle_disable_player(True)
|
|
|
|
def toggle_disable_load_media(self, action):
|
|
"""
|
|
Enable/disable load media combobox and button.
|
|
|
|
:param action: If True elements are disabled, if False they are enabled.
|
|
"""
|
|
self.media_path_combobox.setDisabled(action)
|
|
self.load_disc_button.setDisabled(action)
|
|
|
|
def toggle_disable_player(self, action):
|
|
"""
|
|
Enable/disable player elements.
|
|
|
|
:param action: If True elements are disabled, if False they are enabled.
|
|
"""
|
|
self.play_button.setDisabled(action)
|
|
self.position_slider.setDisabled(action)
|
|
self.position_timeedit.setDisabled(action)
|
|
self.start_position_edit.setDisabled(action)
|
|
self.set_start_button.setDisabled(action)
|
|
self.jump_start_button.setDisabled(action)
|
|
self.end_timeedit.setDisabled(action)
|
|
self.set_end_button.setDisabled(action)
|
|
self.jump_end_button.setDisabled(action)
|
|
self.save_button.setDisabled(action)
|
|
|
|
def accept(self):
|
|
"""
|
|
Saves the current media and trackinfo as a clip to the mediamanager
|
|
"""
|
|
log.debug('in MediaClipSelectorForm.accept')
|
|
start_time = self.start_position_edit.time()
|
|
start_time_ms = start_time.hour() * 60 * 60 * 1000 + \
|
|
start_time.minute() * 60 * 1000 + \
|
|
start_time.second() * 1000 + \
|
|
start_time.msec()
|
|
end_time = self.end_timeedit.time()
|
|
end_time_ms = end_time.hour() * 60 * 60 * 1000 + \
|
|
end_time.minute() * 60 * 1000 + \
|
|
end_time.second() * 1000 + \
|
|
end_time.msec()
|
|
title = self.titles_combo_box.itemData(self.titles_combo_box.currentIndex())
|
|
path = self.media_path_combobox.currentText()
|
|
optical = ''
|
|
if self.audio_cd:
|
|
# Check for load problems
|
|
if start_time_ms is None or end_time_ms is None or title is None:
|
|
critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm', 'CD not loaded correctly'),
|
|
translate('MediaPlugin.MediaClipSelectorForm',
|
|
'The CD was not loaded correctly, please re-load and try again.'))
|
|
return
|
|
optical = 'optical:{title:d}:-1:-1:{start:d}:{end:d}:'.format(title=title,
|
|
start=start_time_ms,
|
|
end=end_time_ms)
|
|
else:
|
|
audio_track = self.audio_tracks_combobox.itemData(self.audio_tracks_combobox.currentIndex())
|
|
subtitle_track = self.subtitle_tracks_combobox.itemData(self.subtitle_tracks_combobox.currentIndex())
|
|
# Check for load problems
|
|
if start_time_ms is None or end_time_ms is None or title is None or audio_track is None\
|
|
or subtitle_track is None:
|
|
critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm', 'DVD not loaded correctly'),
|
|
translate('MediaPlugin.MediaClipSelectorForm',
|
|
'The DVD was not loaded correctly, please re-load and try again.'))
|
|
return
|
|
optical = 'optical:{title:d}:{audio:d}:{sub:d}:{start:d}:{end:d}:'.format(title=title,
|
|
audio=audio_track,
|
|
sub=subtitle_track,
|
|
start=start_time_ms,
|
|
end=end_time_ms)
|
|
# Ask for an alternative name for the mediaclip
|
|
while True:
|
|
new_optical_name, ok = QtWidgets.QInputDialog.getText(self, translate('MediaPlugin.MediaClipSelectorForm',
|
|
'Set name of mediaclip'),
|
|
translate('MediaPlugin.MediaClipSelectorForm',
|
|
'Name of mediaclip:'),
|
|
QtWidgets.QLineEdit.Normal)
|
|
# User pressed cancel, don't save the clip
|
|
if not ok:
|
|
return
|
|
# User pressed ok, but the input text is blank
|
|
if not new_optical_name:
|
|
critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm',
|
|
'Enter a valid name or cancel'),
|
|
translate('MediaPlugin.MediaClipSelectorForm',
|
|
'Enter a valid name or cancel'))
|
|
# The entered new name contains a colon, which we don't allow because colons is used to seperate clip info
|
|
elif new_optical_name.find(':') >= 0:
|
|
critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm', 'Invalid character'),
|
|
translate('MediaPlugin.MediaClipSelectorForm',
|
|
'The name of the mediaclip must not contain the character ":"'))
|
|
# New name entered and we use it
|
|
else:
|
|
break
|
|
# Append the new name to the optical string and the path
|
|
optical += new_optical_name + ':' + path
|
|
self.media_item.add_optical_clip(Path(optical))
|
|
|
|
def media_state_wait(self, media_state):
|
|
"""
|
|
Wait for the video to change its state
|
|
Wait no longer than 15 seconds. (loading an optical disc takes some time)
|
|
|
|
:param media_state: VLC media state to wait for.
|
|
:return: True if state was reached within 15 seconds, False if not or error occurred.
|
|
"""
|
|
vlc = get_vlc()
|
|
start = datetime.now()
|
|
while media_state != self.vlc_media_player.get_state():
|
|
sleep(0.1)
|
|
if self.vlc_media_player.get_state() == vlc.State.Error:
|
|
return False
|
|
if (datetime.now() - start).seconds > 15:
|
|
return False
|
|
return True
|
|
|
|
def find_optical_devices(self):
|
|
"""
|
|
Attempt to autodetect optical devices on the computer, and add them to the media-dropdown
|
|
:return:
|
|
"""
|
|
# Clear list first
|
|
self.media_path_combobox.clear()
|
|
if is_win():
|
|
# use win api to find optical drives
|
|
fso = Dispatch('scripting.filesystemobject')
|
|
for drive in fso.Drives:
|
|
log.debug('Drive {drive} has type {types:d}'.format(drive=drive.DriveLetter, types=drive.DriveType))
|
|
# if type is 4, it is a cd-rom drive
|
|
if drive.DriveType == 4:
|
|
self.media_path_combobox.addItem('{drive}:\\'.format(drive=drive.DriveLetter))
|
|
elif is_linux():
|
|
# Get disc devices from dbus and find the ones that are optical
|
|
bus = dbus.SystemBus()
|
|
try:
|
|
udev_manager_obj = bus.get_object('org.freedesktop.UDisks', '/org/freedesktop/UDisks')
|
|
udev_manager = dbus.Interface(udev_manager_obj, 'org.freedesktop.UDisks')
|
|
for dev in udev_manager.EnumerateDevices():
|
|
device_obj = bus.get_object("org.freedesktop.UDisks", dev)
|
|
device_props = dbus.Interface(device_obj, dbus.PROPERTIES_IFACE)
|
|
if device_props.Get('org.freedesktop.UDisks.Device', 'DeviceIsDrive'):
|
|
drive_props = device_props.Get('org.freedesktop.UDisks.Device', 'DriveMediaCompatibility')
|
|
if any('optical' in prop for prop in drive_props):
|
|
self.media_path_combobox.addItem(device_props.Get('org.freedesktop.UDisks.Device',
|
|
'DeviceFile'))
|
|
return
|
|
except dbus.exceptions.DBusException:
|
|
log.debug('could not use udisks, will try udisks2')
|
|
udev_manager_obj = bus.get_object('org.freedesktop.UDisks2', '/org/freedesktop/UDisks2')
|
|
udev_manager = dbus.Interface(udev_manager_obj, 'org.freedesktop.DBus.ObjectManager')
|
|
for k, v in udev_manager.GetManagedObjects().items():
|
|
drive_info = v.get('org.freedesktop.UDisks2.Drive', {})
|
|
drive_props = drive_info.get('MediaCompatibility')
|
|
if drive_props and any('optical' in prop for prop in drive_props):
|
|
for device in udev_manager.GetManagedObjects().values():
|
|
if dbus.String('org.freedesktop.UDisks2.Block') in device:
|
|
if device[dbus.String('org.freedesktop.UDisks2.Block')][dbus.String('Drive')] == k:
|
|
block_file = ''
|
|
for c in device[dbus.String('org.freedesktop.UDisks2.Block')][
|
|
dbus.String('PreferredDevice')]:
|
|
if chr(c) != '\x00':
|
|
block_file += chr(c)
|
|
self.media_path_combobox.addItem(block_file)
|
|
elif is_macosx():
|
|
# Look for DVD folders in devices to find optical devices
|
|
volumes = os.listdir('/Volumes')
|
|
for volume in volumes:
|
|
if volume.startswith('.'):
|
|
continue
|
|
dirs = os.listdir('/Volumes/' + volume)
|
|
# Detect DVD
|
|
if 'VIDEO_TS' in dirs:
|
|
self.media_path_combobox.addItem('/Volumes/' + volume)
|
|
# Detect audio cd
|
|
files = [f for f in dirs if os.path.isfile(f)]
|
|
for file in files:
|
|
if file.endswith('aiff'):
|
|
self.media_path_combobox.addItem('/Volumes/' + volume)
|
|
break
|