# -*- coding: utf-8 -*-

##########################################################################
# OpenLP - Open Source Lyrics Projection                                 #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2022 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.i18n import translate
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.platform import is_linux, is_macosx, is_win
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()
            titles = get_vlc().track_description_list(titles)
            self.titles_combo_box.clear()
            for title in titles:
                self.titles_combo_box.addItem(title[1].decode(), title[0])
            # 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
