Esempio n. 1
0
def dialog_open_subtitle_files(parent=None):
    """
    Dialog which is used to choose multiple subtitle files.

    :param parent: the parent widget
    :return: the qc document paths or None if user aborts
    """

    dialog = Gtk.FileChooserNative.new(title=_("Choose subtitle files"),
                                       parent=parent,
                                       action=Gtk.FileChooserAction.OPEN)
    dialog.add_filter(_FILE_FILTERS.filter_subs)
    dialog.set_select_multiple(True)

    latest_directory = get_settings().latest_paths_import_subtitle_directory
    if path.isdir(latest_directory):
        dialog.set_current_folder(latest_directory)

    subtitles = None
    if dialog.run() == Gtk.ResponseType.ACCEPT:
        subtitles = dialog.get_filenames()

        if subtitles:
            get_settings().latest_paths_import_subtitle_directory = str(
                path.dirname(subtitles[0]))

    dialog.destroy()
    return subtitles
Esempio n. 2
0
def dialog_open_video(parent=None):
    """
    Dialog which is used to choose a video file.

    :param parent: the parent widget
    :return: the chosen video or None if user aborts
    """

    dialog = Gtk.FileChooserNative.new(title=_("Choose a video file"),
                                       parent=parent,
                                       action=Gtk.FileChooserAction.OPEN)
    dialog.add_filter(_FILE_FILTERS.filter_vids)
    dialog.set_select_multiple(False)

    latest_directory = get_settings().latest_paths_import_video_directory
    if path.isdir(latest_directory):
        dialog.set_current_folder(latest_directory)

    video = None
    if dialog.run() == Gtk.ResponseType.ACCEPT:
        video = dialog.get_filename()

        get_settings().latest_paths_import_video_directory = str(
            path.dirname(video))

    dialog.destroy()
    return video
Esempio n. 3
0
    def on_restore_default_clicked(self):
        """
        Called whenever the user presses restore and this preference page is visible.
        """

        get_settings().reset_config_file_mpv_content()

        self.__set_initial_values()
Esempio n. 4
0
    def __set_up_combo_box_header_subtitle(self):
        """
        Sets up the combo box regarding the display subtitle.
        """

        self.__combo_model.append([_("Display nothing")])
        self.__combo_model.append([_("Current file name")])
        self.__combo_model.append([_("Current file path")])
        renderer = Gtk.CellRendererText()
        self.cbox_header.pack_start(renderer, True)
        self.cbox_header.add_attribute(renderer, "text", 0)
        self.cbox_header.set_model(self.__combo_model)
        get_settings().bind_header_subtitle_format(self.cbox_header, "active")
Esempio n. 5
0
    def __on_video_timer_timeout(self, *_):
        """
        Updates the status bar periodically.
        Kick off in 'on_mpv_player_realized'

        :return: True if continue timer, False else
        """

        s = get_settings()
        p_value = s.status_bar_percentage
        tf_value = s.status_bar_time_format
        video_loaded = self.__time_current

        if tf_value == 0 and video_loaded:
            time = "{0}/{1}".format(self.__time_current, self.__time_duration)
        elif tf_value == 1 and video_loaded:
            time = self.__time_current
        elif tf_value == 2 and video_loaded:
            time = "-" + self.__time_remaining
        else:
            time = ""

        if p_value:
            percent = self.__percent
        else:
            percent = ""

        self.label_button_time.set_label(
            "{}      {}".format(percent, time).strip() + ("   " if video_loaded and (time or p_value) else "")
        )

        return True
Esempio n. 6
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.init_template()

        self.label_backup_directory_path.set_text(
            str(get_app_paths().dir_backup))

        self.list_export_settings_header.set_header_func(
            list_header_nested_func, None)
        self.list_export_settings.set_header_func(list_header_func, None)
        self.list_auto_save_interval.set_header_func(list_header_nested_func,
                                                     None)
        self.list_open_back_directory.set_header_func(list_header_nested_func,
                                                      None)

        # Bind settings to widgets
        s = get_settings()
        s.bind_import_open_video_automatically(
            self.switch_load_video_automatically, "active")
        s.bind_export_qc_document_nick(self.label_nick, "label")
        s.bind_export_append_nick(self.switch_append_nick, "active")
        s.bind_export_write_header(self.switch_write_header, "active")
        s.bind_export_write_header(self.revealer_header_section,
                                   "reveal-child")
        s.bind_export_write_date(self.switch_write_date, "active")
        s.bind_export_write_generator(self.switch_write_generator, "active")
        s.bind_export_write_nick(self.switch_write_nick, "active")
        s.bind_export_write_path(self.switch_write_path, "active")
        s.bind_auto_save_enabled(self.switch_auto_save, "active")
        s.bind_auto_save_enabled(self.revealer_auto_save, "reveal-child")
        s.bind_auto_save_interval(self.spin_btn_auto_save_interval, "value")
Esempio n. 7
0
def dialog_save_qc_document(video_file, parent=None):
    """
    Dialog which is used to save a qc document.

    :param video_file: the file path of the video
    :param parent: the parent widget
    :return: the file path to save under or None if user aborts
    """

    dialog = Gtk.FileChooserNative.new(title=_("Choose a file name"),
                                       parent=parent,
                                       action=Gtk.FileChooserAction.SAVE)
    dialog.add_filter(_FILE_FILTERS.filter_docs)
    dialog.set_current_name(generate_file_name_proposal(video_file))
    dialog.set_select_multiple(False)
    dialog.set_do_overwrite_confirmation(True)

    directory = path.dirname(video_file) if video_file else str(Path.home())
    dialog.set_current_folder(directory)

    file_name = None
    if dialog.run() == Gtk.ResponseType.ACCEPT:
        file_name = dialog.get_filename()
        file_name = file_name if file_name.endswith(
            ".txt") else file_name + ".txt"

        get_settings().latest_paths_export_qc_directory = str(
            path.dirname(file_name))

    # todo escape specific file name characters

    dialog.destroy()
    return file_name
Esempio n. 8
0
def get_file_content(video_path: Optional[str], comments: Tuple[Comment]):
    """
    Will take into account all user settings provided as arguments to build and return the qc file content.

    :param video_path: the path of the video
    :param comments: a list of comments objects
    :return: the file content of the qc document as a string
    """

    metadata = get_app_metadata()
    s = get_settings()

    b_header = s.export_write_header

    b_date = s.export_write_date
    v_date = str(datetime.now().replace(microsecond=0))

    b_generator = s.export_write_generator
    v_generator = "{} {}".format(metadata.app_name, metadata.get_app_version_str(short=True))

    b_nick = s.export_write_nick
    v_nick = s.export_qc_document_nick

    b_path = s.export_write_path
    v_path = video_path if video_path else ""

    return __prepare_file_content(b_header,
                                  b_date, v_date,
                                  b_generator, v_generator,
                                  b_nick, v_nick,
                                  b_path, v_path,
                                  comments)
Esempio n. 9
0
    def __update_recent_files_list(self):
        """
        Updates the recent files list.
        """

        for child in self.__list_recent_files.get_children():
            self.__list_recent_files.remove(child)

        recent_files = get_settings().latest_paths_recent_files

        if recent_files:
            rows_displayed = min(5, len(recent_files))

            # Fit max 5 rows, add space for separators
            self.scrolled_container.set_min_content_height(
                _RECENT_FILES_ROW_HEIGHT * rows_displayed + rows_displayed)

            for recent_file in recent_files:
                row = create_row(
                    file_type=0 if is_qc_document(recent_file) else 1,
                    file_name=path.basename(recent_file),
                    file_path=recent_file)
                self.__list_recent_files.add(row)

            self.revealer_recent_files.set_reveal_child(True)
        else:
            self.revealer_recent_files.set_reveal_child(False)
Esempio n. 10
0
    def __create_context_menu(self, button, time) -> None:
        """
        Creates a new context menu filled with all current comment types.

        :param button: The button which requested the event.
        :param time: The time of the event.
        """

        if not self.__mpv.is_video_loaded():
            return

        self.__mpv.pause()

        def __on_clicked(value) -> None:
            self.emit(signals.MPVQC_CREATE_NEW_COMMENT, self.__mpv.position_current()[1], value.get_label())

        menu = Gtk.Menu()
        menu.attach_to_widget(self, None)

        for item in get_settings().comment_types:
            menu_item = Gtk.MenuItem()
            menu_item.set_label(str(item))
            menu_item.connect("activate", __on_clicked)
            menu.add(menu_item)

        menu.show_all()
        menu.popup(None, None, None, data=None, button=button, activate_time=time)
Esempio n. 11
0
    def __set_comment_type_content(self):
        """
        Set initial values from settings.
        """

        self.__comment_type_model.remove_all()

        for comment_type in get_settings().comment_types:
            self.__ct_add_item(comment_type)
Esempio n. 12
0
    def on_restore_default_clicked(self):
        """
        Called whenever the user presses restore and this preference page is visible.
        """

        s = get_settings()
        s.reset_header_subtitle_format()
        s.reset_comment_types()

        self.__set_comment_type_content()
Esempio n. 13
0
    def __update_comment_type_setting(self):
        """
        Updates the comment type setting.
        """

        new_comment_types = []
        for idx, __ in enumerate(self.list_ct):
            new_comment_types.append(self.__comment_type_model.get_item(idx).text)

        get_settings().comment_types = new_comment_types
Esempio n. 14
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.init_template()

        self.message_stack = MessageStack()
        self.pack_start(self.message_stack, expand=True, fill=True, padding=0)

        # Updated by player signals
        self.__time_duration = ""
        self.__time_remaining = ""
        self.__time_current = ""
        self.__percent = ""

        # State variables for formatting
        self.__duration = None
        self.__short = False

        # Updated by table model signals
        self.__comment_count = None
        self.__comment_selected = None

        # Set up radio selection
        self.__time_menu = {
            0: self.time_menu_default,
            1: self.time_menu_current,
            2: self.time_menu_remaining,
            3: self.time_menu_no,
        }
        self.__time_menu.get(get_settings().status_bar_time_format, 0).set_property("active", True)

        for idx, entry in self.__time_menu.items():
            entry.set_property("role", Gtk.ButtonRole.RADIO)
            entry.connect("clicked", lambda w, v=idx: self.__on_time_format_item_clicked(v))

        # Set up check selection
        self.time_menu_percentage.set_property("active", get_settings().status_bar_percentage)
        self.time_menu_percentage.set_property("role", Gtk.ButtonRole.CHECK)
        self.time_menu_percentage.connect("clicked", self.__on_percentage_item_clicked)

        # Set up timer
        self.connect("destroy", self.__on_destroy)
        self.__time_update_timer = None
Esempio n. 15
0
    def validate(self, current_text):
        if not bool(_REGEX_EMPTY.match(current_text)):
            return False, _("A type must not be empty")

        if current_text in get_settings().comment_types:
            return False, _("A type with that name already exists")

        if not bool(_REGEX_COMMENT_TYPE.match(current_text)):
            return False, _("Allowed characters are letters, spaces and minus")

        return True, ""
Esempio n. 16
0
    def __on_percentage_item_clicked(self, *_):
        """
        Updates the percentage setting. With the next timeout the setting gets applied.

        :param nr: the new key of the time_menu to apply
        """

        new_value = not self.time_menu_percentage.get_property("active")

        get_settings().status_bar_percentage = new_value
        self.time_menu_percentage.set_property("active", new_value)
Esempio n. 17
0
    def __init__(self, mpvqc_window: MpvqcWindow, **kwargs):
        super().__init__(**kwargs)
        self.init_template()

        s = get_settings()

        # Dark theme
        self._button_dark_theme.set_property("role", Gtk.ButtonRole.CHECK)
        self._button_dark_theme.set_property("active", s.prefer_dark_theme)
        Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", s.prefer_dark_theme)

        # Widgets
        from mpvqc.ui.contentmainmpv import ContentMainMpv
        self.__video_widget = ContentMainMpv(self, mpvqc_window)
        self.__table_widget = ContentMainTable(self.__video_widget)
        self.__qc_manager = QcManager(mpvqc_window, self.__video_widget, self.__table_widget)
        self.__popover_open = PopoverOpen(self.__qc_manager)
        self.__status_bar = StatusBar()
        self.__search_frame = SearchFrame(self.__table_widget)

        # Widget composition of ui templates
        self._scrolled_window.add(self.__table_widget)
        self._overlay.add(self._scrolled_window)
        self._overlay.add_overlay(self.__search_frame)
        self._paned.pack1(self.__video_widget, resize=True, shrink=False)
        self._paned.pack2(self._overlay, resize=True, shrink=False)
        self._box.pack_start(self.__status_bar, expand=False, fill=True, padding=0)

        # Set up drag and drop
        target = Gtk.TargetEntry.new(target="text/uri-list", flags=Gtk.TargetFlags.OTHER_APP, info=0)
        self._paned.drag_dest_set(Gtk.DestDefaults.ALL, [target], Gdk.DragAction.COPY)
        self._paned.connect("drag-data-received", self.__on_drag_data_received)

        # Connect events: Player goes first
        self.__video_widget.connect("realize", self.__status_bar.on_mpv_player_realized)
        # Connect events: Key event order
        self.__table_widget.connect("key-press-event", self.__table_widget.on_key_press_event)
        self.__table_widget.connect("key-press-event", self.__video_widget.on_key_press_event)
        # Connect events: Statusbar
        self.__table_widget.get_selection().connect("changed", self.__status_bar.on_comments_selection_change)
        self.__table_widget.get_model().connect("row-changed", self.__status_bar.on_comments_row_changed)
        self.__table_widget.get_model().connect("row-deleted", self.__status_bar.on_comments_row_changed)
        self.__table_widget.get_model().connect("row-inserted", self.__status_bar.on_comments_row_changed)
        # Connect events: QC-Manager
        self.__qc_manager.connect(signals.MPVQC_STATUSBAR_UPDATE, self.__status_bar.update_statusbar_message)
        self.__qc_manager.connect(signals.MPVQC_QC_STATE_CHANGED, self.__update_title)
        self.__qc_manager.connect(signals.MPVQC_QC_STATE_CHANGED, self.__search_frame.clear_current_matches)

        # Class variables
        self.__is_fullscreen = False
        self.__video_file_name = ""
        self.__video_file_path = ""

        self.__on_mpv_player_realized()
Esempio n. 18
0
    def __update_subtitle(self) -> None:
        value = get_settings().header_subtitle_format

        subtitle_enabled = value != 0 and self.__video_file_name and self.__video_file_path

        self._header_bar.set_property("has-subtitle", subtitle_enabled)

        if value == 0:
            self._header_bar.set_subtitle("")
        elif value == 1:
            self._header_bar.set_subtitle(self.__video_file_name)
        elif value == 2:
            self._header_bar.set_subtitle(self.__video_file_path)
Esempio n. 19
0
    def on_save_as_pressed(self, a: AppWindow, t: Table,
                           m: MpvContainer) -> 'State':
        """
        Called when the user presses the 'Save As...' button
        """

        m.player.pause()
        doc = d.dialog_save_qc_document(self.__vid, a)
        comments = t.get_all_comments()
        r = hs.do_save(doc, self.__vid, comments)

        if r.abort:
            return self.copy()
        elif r.save_error:
            md.message_dialog_document_save_failed(parent=a)
            return self.copy()

        get_settings().latest_paths_recent_files_add(doc)

        return self._state_saved(doc=r.doc_new,
                                 vid=r.vid_new,
                                 comments=comments,
                                 message=sm.get_save_m(as_new_name=True))
Esempio n. 20
0
    def __on_time_format_item_clicked(self, nr):
        """
        Updates the time format setting and manages the toggle group. With the next timeout the setting gets applied.

        :param nr: the new key of the time_menu to apply
        """

        get_settings().status_bar_time_format = nr

        for idx, entry in self.__time_menu.items():
            if idx == nr:
                entry.set_property("active", True)
            else:
                entry.set_property("active", False)
Esempio n. 21
0
    def recalculate_preferred_width(self):
        """
        Recalculate preferred width based on current model and on comment types (from settings entry) width.

        :return: a tuple (width, width)
        """

        layout = Gtk.Label().get_layout()
        layout.set_markup(
            max(get_longest_string_from(self.__model, 2),
                get_settings().comment_types_longest,
                key=len))
        pixel_size = layout.get_pixel_size()
        size = pixel_size.width + (self.get_padding()[0] * 4)
        self.__preferred_width = size, size
Esempio n. 22
0
    def on_restore_default_clicked(self):
        """
        Called whenever the user presses restore and this preference page is visible.
        """

        s = get_settings()
        s.reset_qc_document_nick()
        s.reset_export_append_nick()
        s.reset_export_write_header()
        s.reset_export_write_date()
        s.reset_export_write_generator()
        s.reset_export_write_nick()
        s.reset_export_write_path()
        s.reset_import_open_video_automatically()
        s.reset_auto_save_enabled()
        s.reset_auto_save_interval()
Esempio n. 23
0
    def do_delete_event(self, *args, **kwargs):
        """
        Invoked when user presses the close button or CTRL + Q.

        :return: True to stop other handlers from being invoked for the event. False to propagate the event further.
        """

        can_quit = self.__content_main.can_quit
        if not can_quit:
            return True

        s = get_settings()
        s.write_config_file_input_content()
        s.write_config_file_mpv_content()

        return False
Esempio n. 24
0
    def __init__(self, current_text, **properties):
        super().__init__(**properties)
        self.init_template()

        text_unknown = True
        for item in get_settings().comment_types:
            lbl = str(item)
            btn = self.__create_new_button(lbl)
            btn.show()

            if lbl == current_text:
                btn.set_property("active", True)
                text_unknown = False

            self.box.pack_start(btn, expand=False, fill=True, padding=0)

        if text_unknown:
            self.__create_additional_entry(current_text)
Esempio n. 25
0
    def on_export_row_activated(self, __, row, *___):
        """
        Handles the editing of the nick.
        """

        if row == self.row_nick:

            def __apply(____, new_value):
                self.label_nick.set_text(new_value)

            pop = InputPopover(
                label=_("New nickname:"),
                validator=NicknameValidator(),
                placeholder=_("Enter nickname"),
                current_text=get_settings().export_qc_document_nick)
            pop.set_relative_to(self.label_nick)
            pop.connect(signals.MPVQC_APPLY, __apply)
            pop.popup()
            return True
Esempio n. 26
0
    def on_import(self, docs: Optional[List[str]], vids: Optional[List[str]],
                  subs: Optional[List[str]], a: AppWindow, t: Table,
                  m: MpvContainer) -> 'State':
        """
        Called when the user imports something
        (no matter if it's by d&d or just by selecting something in the file manager).
        """

        if not docs and not vids and not subs:
            return self.copy()

        s = get_settings()

        def _handle_docs_valid(valid_docs: List[str]) -> None:
            for vd in valid_docs:
                s.latest_paths_recent_files_add(vd)

        def _handle_docs_invalid(invalid_docs: List[str]) -> None:
            if invalid_docs:
                md.message_dialog_imported_files_are_not_valid(
                    not_valid_files=invalid_docs, parent=a)

        def _handle_comments(comments: Tuple[Comment]) -> bool:
            """
            Returns True if abort import, False else
            """

            if t.get_all_comments():
                response = md.message_dialog_what_to_do_with_existing_comments(
                )
                if response == 0:  # Keep comments
                    pass
                elif response == 1:  # Delete comments
                    t.clear_all_comments()
                elif response == 2:
                    return True

            t.add_comments(comments)
            return False

        def _handle_vids(vid: str) -> bool:
            """
            Returns True, if video actually was opened
            """

            do_open = vid and (s.import_open_video_automatically
                               or Gtk.ResponseType.YES
                               == md.message_dialog_video_found_ask_to_open(
                                   file=vid, parent=a))
            if do_open:
                __open_video(vid)
                return True
            return False

        def __open_video(vid: str) -> None:
            m.player.open_video(vid)
            s.latest_paths_recent_files_add(vid)

        hir, data = hi.do_import(self.__vid, docs, vids)
        vid_new = hir.vid_new

        if docs:
            abort_import = _handle_comments(hir.comments)
            if abort_import:
                return self.copy()

            _handle_docs_valid(hir.docs_valid)
            _handle_docs_invalid(hir.docs_invalid)

            if not vids and vid_new:
                opened = _handle_vids(vid_new)
                if not opened:
                    vid_new = None
                    data.vid_new = None

            if not vids and not subs and not hir.docs_valid:
                return self.copy()

        if vids:
            __open_video(vid_new)

        if subs:
            for sub in subs:
                m.player.add_sub_files(sub)

        return self.__on_import_handle_state(data=data,
                                             imp_docs=hir.docs_valid,
                                             imp_vid=vid_new,
                                             imp_subs=subs)
Esempio n. 27
0
 def on_mpv_text_buffer_changed(self, *_):
     start_iter = self.mpv_text_buffer.get_start_iter()
     end_iter = self.mpv_text_buffer.get_end_iter()
     get_settings().config_file_mpv_content = self.mpv_text_buffer.get_text(
         start_iter, end_iter, True)
Esempio n. 28
0
    def __set_initial_values(self):
        """
        Set initial values from settings.
        """

        self.mpv_text_buffer.set_text(get_settings().config_file_mpv_content)
Esempio n. 29
0
def generate_file_name_proposal(video_file):
    nick = "_" + get_settings().export_qc_document_nick if get_settings(
    ).export_append_nick else ""
    video = path.splitext(
        path.basename(video_file))[0] if video_file else _("untitled")
    return "[QC]_{0}{1}.txt".format(video, nick)
Esempio n. 30
0
    def _on_button_dark_theme_clicked(self, *_) -> None:
        s = get_settings()
        s.prefer_dark_theme = not s.prefer_dark_theme

        self._button_dark_theme.set_property("active", s.prefer_dark_theme)
        Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", s.prefer_dark_theme)