class LoadZeroPointView(QDialog):
    def __init__(self, zeroPointManager: ZeroPointManager):
        super().__init__()
        self.setWindowTitle("Load Zero Point")

        self._zeroPointManager = zeroPointManager
        self._listWidget = QListWidget()

        for zeroPoint in self._zeroPointManager.getZeroPoints():
            listItem = QListWidgetItem(
                f"{zeroPoint.name} : {zeroPoint.offset}")
            self._listWidget.addItem(listItem)

        buttonBox = QDialogButtonBox(QDialogButtonBox.Ok
                                     | QDialogButtonBox.Cancel)
        buttonBox.accepted.connect(self.setZeroPoint)
        buttonBox.rejected.connect(self.reject)

        layout = QFormLayout()
        layout.addRow(self._listWidget)
        layout.addRow(buttonBox)

        self.setLayout(layout)

    def setZeroPoint(self):
        index = self._listWidget.indexFromItem(
            self._listWidget.selectedItems()[0]).row()
        self._zeroPointManager.setNewActiveZeroPoint(index)
        self.close()
Exemple #2
0
class SimpleTodoPlus(QWidget):
    def __init__(self):
        QWidget.__init__(self)

        self.todo_list_widget = QListWidget(self)
        self.todo_list_widget.itemChanged.connect(self.onItemChanged)

        self.add_todo_btn = QPushButton("Add Todo", self)
        self.add_todo_btn.clicked.connect(self.add_todo_btn_clicked)

        self.remove_todo_btn = QPushButton("Remove Todo", self)
        self.remove_todo_btn.clicked.connect(self.remove_todo_btn_clicked)

        self.clear_todo_btn = QPushButton("Clear Todo", self)
        self.clear_todo_btn.clicked.connect(self.clear_todo_btn_clicked)

        vbox_layout = QVBoxLayout(self)
        vbox_layout.addWidget(self.todo_list_widget)

        hbox_layout = QHBoxLayout()
        hbox_layout.addWidget(self.add_todo_btn)
        hbox_layout.addWidget(self.remove_todo_btn)
        hbox_layout.addWidget(self.clear_todo_btn)

        vbox_layout.addLayout(hbox_layout)

    def add_todo_btn_clicked(self):
        item = QListWidgetItem(f"Todo {self.todo_list_widget.count() + 1}")
        item.setFlags(item.flags() | Qt.ItemIsUserCheckable
                      | Qt.ItemIsEditable)
        item.setCheckState(Qt.Unchecked)
        self.todo_list_widget.addItem(item)
        self.todo_list_widget.edit(self.todo_list_widget.indexFromItem(item))

    def remove_todo_btn_clicked(self):
        if self.todo_list_widget.count():
            self.todo_list_widget.takeItem(self.todo_list_widget.currentRow())

    def clear_todo_btn_clicked(self):
        self.todo_list_widget.clear()

    def onItemChanged(self, item):
        font = item.font()
        font.setStrikeOut(item.checkState() == Qt.Checked)
        item.setFont(font)
Exemple #3
0
class VODCutter(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)

        self.twitch_client_id = config.TWITCH_API_CLIENT_ID
        self.twitch_oauth_token = config.TWITCH_API_OAUTH_TOKEN

        self.twitch_interface = TwitchInterface(
            api_client_id=config.TWITCH_API_CLIENT_ID,
            api_oauth_token=config.TWITCH_API_OAUTH_TOKEN,
            browser_client_id=config.TWITCH_BROWSER_OAUTH_TOKEN,
            browser_oauth_token=config.TWITCH_BROWSER_OAUTH_TOKEN)

        self.vlc_interface = VLCInterface(config.VLC_PATH)

        self.loaded_video = None

        self.main_layout = QVBoxLayout()

        self.launch_vlc_btn = QPushButton("Launch VLC")

        self.info_layout = QGridLayout()

        self.file_picker_layout = QHBoxLayout()

        self.file_path_field = QLineEdit()
        self.file_browser_btn = QPushButton(text="...")

        self.file_picker_layout.addWidget(self.file_path_field)
        self.file_picker_layout.addWidget(self.file_browser_btn)

        vod_filepath_label = QLabel("VOD Filepath")
        id_twitch_label = QLabel("ID Twitch")
        created_at_label = QLabel("Created at")
        duration_label = QLabel("Duration")
        title_label = QLabel("Title")
        streamer_label = QLabel("Streamer")

        self.id_twitch_field = QLineEdit()
        self.created_at_field = QLineEdit()
        self.duration_field = QLineEdit()
        self.title_field = QLineEdit()
        self.streamer_field = QLineEdit()

        self.id_twitch_field.setEnabled(False)
        self.created_at_field.setEnabled(False)
        self.duration_field.setEnabled(False)
        self.title_field.setEnabled(False)
        self.streamer_field.setEnabled(False)

        self.info_layout.addWidget(vod_filepath_label, 0, 0)
        self.info_layout.addWidget(id_twitch_label, 1, 0)
        self.info_layout.addWidget(created_at_label, 2, 0)
        self.info_layout.addWidget(duration_label, 3, 0)
        self.info_layout.addWidget(title_label, 4, 0)
        self.info_layout.addWidget(streamer_label, 5, 0)

        self.info_layout.addLayout(self.file_picker_layout, 0, 1)
        self.info_layout.addWidget(self.id_twitch_field, 1, 1)
        self.info_layout.addWidget(self.created_at_field, 2, 1)
        self.info_layout.addWidget(self.duration_field, 3, 1)
        self.info_layout.addWidget(self.title_field, 4, 1)
        self.info_layout.addWidget(self.streamer_field, 5, 1)

        self.segments_create_btn = QPushButton("Import Chapters")
        self.download_thumbnails_btn = QPushButton("Download Thumbnails")
        self.download_chatlog_btn = QPushButton("Download Chat Log")

        self.segments_list = QListWidget()

        self.segments_add_btn = QPushButton(text="+")
        self.segments_delete_btn = QPushButton(text="-")

        self.jump_start_btn = QPushButton(text="Jump To Start")
        self.jump_end_btn = QPushButton(text="Jump To End")

        self.set_start_btn = QPushButton(text="Set Start")
        self.set_end_btn = QPushButton(text="Set End")

        self.split_btn = QPushButton(text="Split")

        self.process_selected_btn = QPushButton(
            text="Process Selected Segment")
        self.process_all_btn = QPushButton(text="Process All Segments")

        self.jump_layout = QHBoxLayout()

        self.jump_layout.addWidget(self.jump_start_btn)
        self.jump_layout.addWidget(self.jump_end_btn)

        self.set_layout = QHBoxLayout()

        self.set_layout.addWidget(self.set_start_btn)
        self.set_layout.addWidget(self.set_end_btn)

        self.main_layout.addWidget(self.launch_vlc_btn)
        self.main_layout.addLayout(self.file_picker_layout)
        self.main_layout.addLayout(self.info_layout)
        self.main_layout.addWidget(self.segments_create_btn)
        self.main_layout.addWidget(self.download_thumbnails_btn)
        self.main_layout.addWidget(self.download_chatlog_btn)
        self.main_layout.addWidget(self.segments_list)
        self.main_layout.addWidget(self.segments_add_btn)
        self.main_layout.addWidget(self.segments_delete_btn)
        self.main_layout.addLayout(self.jump_layout)
        self.main_layout.addLayout(self.set_layout)
        self.main_layout.addWidget(self.split_btn)
        self.main_layout.addWidget(self.process_selected_btn)
        self.main_layout.addWidget(self.process_all_btn)

        self.main_widget = QWidget()
        self.main_widget.setLayout(self.main_layout)

        self.setCentralWidget(self.main_widget)

        self.segments_list.itemDoubleClicked.connect(
            self.on_segments_list_doubleclick)

        self.jump_start_btn.clicked.connect(self.jump_to_segment_start)
        self.jump_end_btn.clicked.connect(self.jump_to_segment_end)
        self.set_start_btn.clicked.connect(self.set_segment_start)
        self.set_end_btn.clicked.connect(self.set_segment_end)

        self.download_thumbnails_btn.clicked.connect(self.download_thumbnails)
        self.segments_add_btn.clicked.connect(self.create_segment)
        self.segments_delete_btn.clicked.connect(self.delete_segment)
        self.split_btn.clicked.connect(self.split_selected_segment)
        self.launch_vlc_btn.clicked.connect(self.on_launch_vlc)
        self.file_path_field.returnPressed.connect(self.on_video_url_changed)
        self.file_browser_btn.clicked.connect(self.on_filebrowse_btn_click)
        self.process_selected_btn.clicked.connect(
            self.process_selected_segment)
        self.process_all_btn.clicked.connect(self.process_all_segments)

    def on_launch_vlc(self):
        self.vlc_interface.launch()

    def on_filebrowse_btn_click(self):
        filename = QFileDialog.getOpenFileName(self, "Select a video file")
        if filename[0]:
            self.set_video_file(filename[0])

    def on_video_url_changed(self):
        self.set_video_file(self.file_path_field.text())

    def on_segments_list_doubleclick(self, item):
        current_segment = item.get_segment()
        if current_segment:
            self.vlc_interface.set_current_time(int(
                current_segment.start_time))

    def set_video_file(self, filepath=None):
        self.file_path_field.setText("" if filepath is None else filepath)

        if filepath:
            self.loaded_video = InputVideo()

            if re.search(r"^(?:/|[a-z]:[\\/])", filepath, re.I):
                file_url = "file://" + filepath
                self.loaded_video.is_local = True
            else:
                file_url = filepath

            if not self.loaded_video.is_local:
                streams = streamlink.streams(file_url)
                if streams:
                    self.loaded_video.filepath = streams["best"].url
                else:
                    self.loaded_video.filepath = file_url
            else:
                self.loaded_video.filepath = file_url

            try:
                self.update_twitch_metadatas()
            except requests.exceptions.ConnectionError:
                print("<!!> Can't connect to Twitch API.")

            try:
                self.vlc_interface.open_url(self.loaded_video.filepath)
            except requests.exceptions.ConnectionError:
                print("<!!> Can't connect to local VLC instance.")

    def get_twitch_id_from_filepath(self):
        filename = self.file_path_field.text()

        parsed_filename = re.search("([0-9]+)\.mp4$", filename, re.I)

        if parsed_filename:
            video_id = parsed_filename.group(1)
            return int(video_id)
        else:
            parsed_url = re.search("videos/([0-9]+)", filename, re.I)
            if parsed_url:
                video_id = parsed_url.group(1)
                return int(video_id)
            else:
                raise Exception(
                    f"<!!> Can't find video Twitch id in video filename ({filename})"
                )

    def create_segment_before(self, segment_obj):
        pass

    def create_segment_after(self, segment_obj):
        pass

    def update_twitch_metadatas(self):
        twitch_video_id = self.get_twitch_id_from_filepath()
        metadatas = self.twitch_interface.get_twitch_metadatas(twitch_video_id)

        self.loaded_video.metadatas = metadatas

        duration = parse_duration(metadatas["duration"])

        self.id_twitch_field.setText(metadatas["id"])
        self.created_at_field.setText(str(metadatas["created_at"]))
        self.duration_field.setText(format_time(duration.seconds))
        self.title_field.setText(metadatas["title"])
        self.streamer_field.setText(metadatas["user_login"])

        for moment in self.twitch_interface.get_video_games_list(
                metadatas["id"]):
            s = Segment()

            s.name = f"{moment['description']} ({moment['type']})"
            s.start_time = moment['positionMilliseconds'] / 1000
            s.end_time = (moment['positionMilliseconds'] +
                          moment['durationMilliseconds']) / 1000

            self.segments_list.addItem(SegmentListItem(s))

    def create_segment(self):
        s = Segment()

        s.name = f"Segment {self.segments_list.count()}"
        s.start_time = 0
        s.end_time = self.vlc_interface.get_duration()

        self.segments_list.addItem(SegmentListItem(s))

    def delete_segment(self):
        for item in self.segments_list.selectedItems():
            idx = self.segments_list.indexFromItem(item)
            item = self.segments_list.takeItem(idx.row())
            del item

    def split_selected_segment(self):
        current_time = self.vlc_interface.get_current_time()

        for segment_item in self.segments_list.selectedItems():
            current_segment = segment_item.get_segment()
            if current_segment:
                new_segment = segment_item.split(
                    current_time,
                    name="Splitted " + current_segment.name,
                    split_mode=SPLIT_MODE.ABSOLUTE)
                self.segments_list.addItem(SegmentListItem(new_segment))

    def get_selected_segments(self):
        return list(
            map(lambda item: item.get_segment(),
                self.segments_list.selectedItems()))

    def jump_to_segment_start(self):
        selected_segments = self.get_selected_segments()
        if selected_segments:
            self.vlc_interface.set_current_time(
                math.floor(selected_segments[0].start_time))

    def jump_to_segment_end(self):
        selected_segments = self.get_selected_segments()
        if selected_segments:
            self.vlc_interface.set_current_time(
                math.floor(selected_segments[0].end_time))

    def set_segment_start(self):
        current_time = self.vlc_interface.get_current_time()
        selected_segments = self.segments_list.selectedItems()
        if selected_segments:
            selected_segments[0].get_segment().start_time = current_time
            selected_segments[0].update()

    def set_segment_end(self):
        current_time = self.vlc_interface.get_current_time()
        selected_segments = self.segments_list.selectedItems()
        if selected_segments:
            selected_segments[0].get_segment().end_time = current_time
            selected_segments[0].update()

    def process_selected_segment(self):
        for segment in self.get_selected_segments():
            self.process_segment(segment)

    def process_all_segments(self):
        for idx in range(self.segments_list.count()):
            segment_item = self.segments_list.item(idx)
            self.process_segment(segment_item.get_segment())

    def process_segment(self, segment_obj):
        if not self.loaded_video:
            raise Exception("<!!> No video loaded")

        video_id = self.loaded_video.metadatas.get("id", None)
        created_at = self.loaded_video.metadatas.get("created_at", None)
        user_login = self.loaded_video.metadatas.get("user_login", None)

        if not (video_id and created_at and user_login):
            raise Exception("<!!> Missing video metadatas")

        created_at_timestamp = int(datetime.datetime.timestamp(created_at))

        if self.loaded_video.is_local:
            cmd = f'ffmpeg -i "{self.loaded_video.filepath}" -ss {segment_obj.start_time} -to {segment_obj.end_time} -c:v copy -c:a copy "{user_login}_{created_at_timestamp}_{video_id}.mp4"'
        else:
            cmd = f'streamlink -f --hls-start-offset {format_time(segment_obj.start_time)} --hls-duration {format_time(segment_obj.end_time - segment_obj.start_time)} --player-passthrough hls "{self.loaded_video.filepath}" best -o "{user_login}_{created_at_timestamp}_{video_id}.mp4"'

        print(cmd)

        os.system(cmd)

    def download_thumbnails(self):
        twitch_video_id_str = self.id_twitch_field.text()
        if twitch_video_id_str:
            thumbnails_manifest_url = self.twitch_interface.get_video_thumbnails_manifest_url(
                int(twitch_video_id_str))
            thumbnails_manifest, images_url_list = self.twitch_interface.get_thumbnails_url_from_manifest(
                thumbnails_manifest_url)

            for img in images_url_list:
                r = requests.get(images_url_list[img])
                fp = open(img, "wb")
                fp.write(r.content)
                fp.close()
Exemple #4
0
class ImageToPdfWidget(QWidget):
    def __init__(self, status_link):
        super(ImageToPdfWidget, self).__init__()
        LABEL_WIDTH = 80
        self.last_selected_items = []
        self.options_mode = 0
        self.update_status_combobox = False
        layout = QHBoxLayout()
        self.status_bar = status_link
        self.list_view = QListWidget()
        self.list_view.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.list_view.setAlternatingRowColors(True)
        self.list_view.itemClicked.connect(self.click_item_signal)
        self.list_view.itemEntered.connect(self.click_item_signal)
        self.list_view.itemSelectionChanged.connect(
            self.change_selection_signal)

        controls_layout = QVBoxLayout()
        controls_layout.setAlignment(Qt.AlignTop)

        select_zone = SelectWidget(self.click_move_up, self.click_move_down,
                                   self.click_invert, self.click_delete)

        # options zone -------------------------------------
        options_zone = QGroupBox("Options")
        self.options_zone_layout = QVBoxLayout()

        self.options_mode_combobox = QComboBox()
        self._add_items_to_mode_combobox(self.options_mode_combobox)
        options_mode_label = QLabel("Mode")
        options_mode_label.setMaximumWidth(LABEL_WIDTH)
        options_mode_layout = QHBoxLayout()
        options_mode_layout.addWidget(options_mode_label)
        options_mode_layout.addWidget(self.options_mode_combobox)
        self.options_zone_layout.addLayout(options_mode_layout)
        self.option_source_widget = OptionsFromSourceWidget(
            label_width=LABEL_WIDTH, status_bar=self.status_bar)
        self.options_a_widget = OptionsAWidget(label_width=LABEL_WIDTH,
                                               status_bar=self.status_bar)
        self.options_mode_combobox.currentIndexChanged.connect(
            self.change_options_mode_signal)
        self.change_options_mode_signal(self.options_mode)

        options_zone.setLayout(self.options_zone_layout)

        # Add files button and final structures ---------------------------
        add_file_button = QPushButton("Add Files")
        add_file_button.clicked.connect(self.click_add_files)
        controls_layout.addWidget(select_zone)
        controls_layout.addWidget(options_zone)
        controls_layout.addWidget(add_file_button)

        # image preview ---------------------------------------------------
        image_prev_layout = QVBoxLayout()
        image_prev_layout.setContentsMargins(0, 0, 4, 0)
        self.IMG_PREVIEW_WIDTH = 256
        self.img_label = QLabel()
        self.img_label.setAlignment(Qt.AlignCenter)
        self.select_text = "Click item to preview"
        self.last_created_pix_path = ""
        self.img_label.setText(self.select_text)
        image_prev_layout.addWidget(self.img_label)
        # slider for the preview scale
        self.img_scale_slider = QSlider()
        self.img_scale_slider.setMinimum(6)
        self.img_scale_slider.setMaximum(2048)
        self.img_scale_slider.setValue(self.IMG_PREVIEW_WIDTH)
        self.img_scale_slider.setOrientation(Qt.Horizontal)
        self.img_scale_slider.valueChanged.connect(
            self.change_scale_slider_signal)
        image_prev_layout.addWidget(self.img_scale_slider)
        self._update_preview()

        layout.addLayout(image_prev_layout)
        layout.addWidget(self.list_view)
        layout.addLayout(controls_layout)
        self.setLayout(layout)
        self.update_status_combobox = True

    def _pillow_to_pixmap(self, img):
        if img.mode == "RGB":
            r, g, b = img.split()
            img = Image.merge("RGB", (b, g, r))
        elif img.mode == "RGBA":
            r, g, b, a = img.split()
            img = Image.merge("RGBA", (b, g, r, a))
        elif img.mode == "L":
            img = img.convert("RGBA")
        img2 = img.convert("RGBA")
        data = img2.tobytes("raw", "RGBA")
        qim = QImage(data, img.size[0], img.size[1], QImage.Format_ARGB32)
        pixmap = QPixmap.fromImage(qim)
        return pixmap

    def _update_preview(self):
        self.img_label.setMinimumWidth(self.IMG_PREVIEW_WIDTH)
        self.img_label.setMaximumWidth(
            max(self.IMG_PREVIEW_WIDTH, self.img_scale_slider.width()))
        if len(self.last_created_pix_path) > 0:
            img = Image.open(self.last_created_pix_path)
            img_pix = self._pillow_to_pixmap(img)
            img_pix = img_pix.scaled(
                QSize(self.IMG_PREVIEW_WIDTH, self.IMG_PREVIEW_WIDTH),
                Qt.KeepAspectRatio)
            self.img_label.setPixmap(img_pix)

    def _set_preview(self, img_path):
        if img_path is None:
            self.last_created_pix_path = ""
            self.img_label.setText(self.select_text)
        else:
            if img_path != self.last_created_pix_path:
                self.last_created_pix_path = img_path
                self._update_preview()

    def _add_items_to_mode_combobox(self, combobox):
        combobox.addItem("From Source")
        combobox.addItem("A4")
        # combobox.addItem("A5")
        # combobox.addItem("A6")
        # combobox.addItem("Letter")

    def _get_filtered_string(self, string):
        to_return_array = []
        for s in string:
            if s.isdigit():
                to_return_array.append(s)
        return "".join(to_return_array)

    def get_images_to_save(self):
        path_array = []
        for i in range(self.list_view.count()):
            path_array.append(self.list_view.item(i).get_data())
        return path_array

    def get_image_parameters(self):  # return as dictionary
        if self.options_mode == 0:
            return {
                "mode": 0,
                "pixels": self.option_source_widget.get_pixel_value(),
                "margin": self.option_source_widget.get_margin_value(),
                "background": self.option_source_widget.get_background_value()
            }
        else:
            return {
                "mode": self.options_mode,
                "align": self.options_a_widget.get_align_value(),
                "margin": self.options_a_widget.get_margin_value(),
                "background": self.options_a_widget.get_background_value()
            }

    def add_items(self, array):
        added_names = []
        for a in array:
            new_name = os.path.basename(a)
            new_item = ImageListItem(new_name, a)
            added_names.append(new_name)
            self.list_view.addItem(new_item)
        self.status_bar.showMessage("Add items: " + ", ".join(added_names))

    def change_scale_slider_signal(self, value):
        self.IMG_PREVIEW_WIDTH = value
        self._update_preview()
        self.status_bar.showMessage("Set preview scale to " + str(value))

    def click_item_signal(self, item):
        pass
        # self._set_preview(item.get_data())
        # self.status_bar.showMessage("Select " + str(item.text()))

    def _get_first_new_index(self, current, last):
        for v in current:
            if v not in last:
                return v
        return current[0]

    def change_selection_signal(self):
        if len(self.list_view.selectedItems()) == 0:
            self._set_preview(None)  # nothing selected
        else:
            selected_indexes = [
                self.list_view.indexFromItem(sel).row()
                for sel in self.list_view.selectedItems()
            ]
            item = self.list_view.item(
                self._get_first_new_index(selected_indexes,
                                          self.last_selected_items))
            self._set_preview(item.get_data())
            self.status_bar.showMessage("Select " + str(item.text()))
            self.last_selected_items = selected_indexes

    def change_options_mode_signal(self, index):
        self.options_mode = index
        if self.options_mode == 0:
            self.options_zone_layout.removeWidget(self.options_a_widget)
            self.options_a_widget.setParent(None)
            self.options_zone_layout.addWidget(self.option_source_widget)
        else:
            self.options_zone_layout.removeWidget(self.option_source_widget)
            self.option_source_widget.setParent(None)
            self.options_zone_layout.addWidget(self.options_a_widget)
        if self.update_status_combobox:
            self.status_bar.showMessage(
                "Set combine mode to \"" +
                self.options_mode_combobox.itemText(index) + "\"")

    def resizeEvent(self, size):
        # self._update_preview()
        pass

    def click_move_up(self):
        selected = self.list_view.selectedItems()
        selected_indexes = [
            self.list_view.indexFromItem(sel).row() for sel in selected
        ]
        selected_indexes.sort()
        if len(selected_indexes) > 0 and selected_indexes[0] > 0:
            for index in selected_indexes:
                prev_item = self.list_view.takeItem(index - 1)
                self.list_view.insertItem(index, prev_item)
            self.status_bar.showMessage("Move " + str(len(selected_indexes)) +
                                        " items")
        else:
            self.status_bar.showMessage("Nothing to move")

    def click_move_down(self):
        selected = self.list_view.selectedItems()
        selected_indexes = [
            self.list_view.indexFromItem(sel).row() for sel in selected
        ]
        selected_indexes.sort()
        sel_count = len(selected_indexes)
        if len(selected_indexes) > 0 and selected_indexes[
                sel_count - 1] < self.list_view.count() - 1:
            for i_index in range(sel_count):
                next_item = self.list_view.takeItem(
                    selected_indexes[sel_count - i_index - 1] + 1)
                self.list_view.insertItem(
                    selected_indexes[sel_count - i_index - 1], next_item)
            self.status_bar.showMessage("Move " + str(len(selected_indexes)) +
                                        " items")
        else:
            self.status_bar.showMessage("Nothing to move")

    def click_invert(self):
        selected = self.list_view.selectedItems()
        selected_indexes = []
        for sel in selected:
            selected_indexes.append(self.list_view.indexFromItem(sel).row())
        total_indexes = [i for i in range(self.list_view.count())]
        new_indexes = []
        for i in total_indexes:
            if i not in selected_indexes:
                new_indexes.append(i)
        self.list_view.clearSelection()
        for i in new_indexes:
            self.list_view.item(i).setSelected(True)
        self.status_bar.showMessage("Invert selection: " + str(new_indexes))

    def click_delete(self):
        selected = self.list_view.selectedItems()
        delete_names = []
        for s in selected:
            s_index = self.list_view.indexFromItem(s).row()
            del_item = self.list_view.takeItem(s_index)
            delete_names.append(del_item.text())
        if len(delete_names) == 0:
            self.status_bar.showMessage("Nothing to delete")
        else:
            self.status_bar.showMessage("Delete items: " +
                                        ", ".join(delete_names))

    def click_add_files(self):
        files_dialog = QFileDialog()
        files_dialog.setNameFilter("Images (*.jpg *.jpeg *.bmp *.png *.tiff)")
        files_dialog.setFileMode(QFileDialog.ExistingFiles)
        if files_dialog.exec_():
            files = files_dialog.selectedFiles()
            self.add_items(files)
Exemple #5
0
class PiecesPlayer(QWidget):
    """ main widget of application (used as widget inside PiecesMainWindow) """
    def __init__(self, parent):
        """ standard constructor: set up class variables, ui elements
            and layout """

        # TODO: split current piece info into separate lineedits for title, album name and length
        # TODO: make time changeable by clicking next to the slider (not only
        #       by dragging the slider)
        # TODO: add "about" action to open info dialog in new "help" menu
        # TODO: add option to loop current piece (?)
        # TODO: more documentation
        # TODO: add some "whole piece time remaining" indicator? (complicated)
        # TODO: implement a playlist of pieces that can be edited and enable
        #       going back to the previous piece (also un- and re-shuffling?)
        # TODO: implement debug dialog as menu action (if needed)

        if not isinstance(parent, PiecesMainWindow):
            raise ValueError('Parent widget must be a PiecesMainWindow')

        super(PiecesPlayer, self).__init__(parent=parent)

        # -- declare and setup variables for storing information --
        # various data
        self._set_str = ''  # string of currently loaded directory sets
        self._pieces = {}  # {<piece1>: [<files piece1 consists of>], ...}
        self._playlist = []  # list of keys of self._pieces (determines order)
        self._shuffled = True  # needed for (maybe) reshuffling when looping
        # doc for self._history:
        # key: timestamp ('HH:MM:SS'),
        # value: info_str of piece that started playing at that time
        self._history = {}
        self._status = 'Paused'
        self._current_piece = {'title': '', 'files': [], 'play_next': 0}
        self._default_volume = 60  # in percent from 0 - 100
        self._volume_before_muted = self._default_volume
        # set to true by self.__event_movement_ended and used by self.__update
        self._skip_to_next = False
        # vlc-related variables
        self._vlc_instance = VLCInstance()
        self._vlc_mediaplayer = self._vlc_instance.media_player_new()
        self._vlc_mediaplayer.audio_set_volume(self._default_volume)
        self._vlc_medium = None
        self._vlc_events = self._vlc_mediaplayer.event_manager()

        # -- create and setup ui elements --
        # buttons
        self._btn_play_pause = QPushButton(QIcon(get_icon_path('play')), '')
        self._btn_previous = QPushButton(QIcon(get_icon_path('previous')), '')
        self._btn_next = QPushButton(QIcon(get_icon_path('next')), '')
        self._btn_volume = QPushButton(QIcon(get_icon_path('volume-high')), '')
        self._btn_loop = QPushButton(QIcon(get_icon_path('loop')), '')
        self._btn_loop.setCheckable(True)
        self._btn_play_pause.clicked.connect(self.__action_play_pause)
        self._btn_previous.clicked.connect(self.__action_previous)
        self._btn_next.clicked.connect(self.__action_next)
        self._btn_volume.clicked.connect(self.__action_volume_clicked)
        # labels
        self._lbl_current_piece = QLabel('Current piece:')
        self._lbl_movements = QLabel('Movements:')
        self._lbl_time_played = QLabel('00:00')
        self._lbl_time_left = QLabel('-00:00')
        self._lbl_volume = QLabel('100%')
        # needed so that everything has the same position
        # independent of the number of digits of volume
        self._lbl_volume.setMinimumWidth(55)
        # sliders
        self._slider_time = QSlider(Qt.Horizontal)
        self._slider_volume = QSlider(Qt.Horizontal)
        self._slider_time.sliderReleased.connect(
            self.__event_time_changed_by_user)
        self._slider_volume.valueChanged.connect(self.__event_volume_changed)
        self._slider_time.setRange(0, 100)
        self._slider_volume.setRange(0, 100)
        self._slider_volume.setValue(self._default_volume)
        self._slider_volume.setMinimumWidth(100)
        # other elements
        self._checkbox_loop_playlist = QCheckBox('Loop playlist')
        self._lineedit_current_piece = QLineEdit()
        self._lineedit_current_piece.setReadOnly(True)
        self._lineedit_current_piece.textChanged.connect(
            self.__event_piece_text_changed)
        self._listwidget_movements = QListWidget()
        self._listwidget_movements.itemClicked.connect(
            self.__event_movement_selected)

        # -- create layout and insert ui elements--
        self._layout = QVBoxLayout(self)
        # row 0 (name of current piece)
        self._layout_piece_name = QHBoxLayout()
        self._layout_piece_name.addWidget(self._lbl_current_piece)
        self._layout_piece_name.addWidget(self._lineedit_current_piece)
        self._layout.addLayout(self._layout_piece_name)
        # rows 1 - 5 (movements of current piece)
        self._layout.addWidget(self._lbl_movements)
        self._layout.addWidget(self._listwidget_movements)
        # row 6 (time)
        self._layout_time = QHBoxLayout()
        self._layout_time.addWidget(self._lbl_time_played)
        self._layout_time.addWidget(self._slider_time)
        self._layout_time.addWidget(self._lbl_time_left)
        self._layout.addLayout(self._layout_time)
        # row 7 (buttons and volume)
        self._layout_buttons_and_volume = QHBoxLayout()
        self._layout_buttons_and_volume.addWidget(self._btn_play_pause)
        self._layout_buttons_and_volume.addWidget(self._btn_previous)
        self._layout_buttons_and_volume.addWidget(self._btn_next)
        self._layout_buttons_and_volume.addWidget(self._btn_loop)
        self._layout_buttons_and_volume.addSpacing(40)
        # distance between loop and volume buttons: min. 40, but stretchable
        self._layout_buttons_and_volume.addStretch()
        self._layout_buttons_and_volume.addWidget(self._btn_volume)
        self._layout_buttons_and_volume.addWidget(self._slider_volume)
        self._layout_buttons_and_volume.addWidget(self._lbl_volume)
        self._layout.addLayout(self._layout_buttons_and_volume)

        # -- setup hotkeys --
        self._KEY_CODES_PLAY_PAUSE = [269025044]
        self._KEY_CODES_NEXT = [269025047]
        self._KEY_CODES_PREVIOUS = [269025046]
        self._keyboard_listener = keyboard.Listener(on_press=self.__on_press)
        self._keyboard_listener.start()
        QShortcut(QKeySequence('Space'), self, self.__action_play_pause)

        # -- various setup --
        self._timer = QTimer(self)
        self._timer.timeout.connect(self.__update)
        self._timer.start(100)  # update every 100ms
        self.setMinimumWidth(900)
        self.setMinimumHeight(400)
        # get directory set(s) input and set up self._pieces
        # (exec_ means we'll wait for the user input before continuing)
        DirectorySetChooseDialog(self, self.set_pieces_and_playlist).exec_()
        # skip to next movement / next piece when current one has ended
        self._vlc_events.event_attach(VLCEventType.MediaPlayerEndReached,
                                      self.__event_movement_ended)

    def __action_next(self):
        """ switches to next file in self._current_piece['files']
            or to the next piece, if the current piece has ended """

        reset_pause_after_current = False

        # current movement is last of the current piece
        if self._current_piece['play_next'] == -1:
            if len(self._playlist) == 0:  # reached end of playlist
                if self._btn_loop.isChecked():
                    self._playlist = list(self._pieces.keys())
                    if self._shuffled:
                        shuffle(self._playlist)
                    return

                if self._status == 'Playing':
                    self.__action_play_pause()
                self._current_piece['title'] = ''
                self._current_piece['files'] = []
                self._current_piece['play_next'] = -1
                self._lineedit_current_piece.setText('')
                self.__update_movement_list()
                self.parentWidget().update_status_bar(
                    self._status, 'End of playlist reached.')
                return
            else:
                if self.parentWidget().get_exit_after_current():
                    self.parentWidget().exit()
                if self.parentWidget().get_pause_after_current():
                    self.__action_play_pause()
                    reset_pause_after_current = True
                    # reset of the menu action will be at the end of this
                    # function, or else we won't stay paused

                self._current_piece['title'] = self._playlist.pop(0)
                self._current_piece['files'] = [
                    p[1:-1] for p in self._pieces[self._current_piece['title']]
                ]
                # some pieces only have one movement
                self._current_piece['play_next'] = \
                    1 if len(self._current_piece['files']) > 1 else -1
                self.__update_vlc_medium(0)
                self._lineedit_current_piece.setText(
                    create_info_str(self._current_piece['title'],
                                    self._current_piece['files']))
                self.__update_movement_list()
                self._history[datetime.now().strftime('%H:%M:%S')] = \
                    self._lineedit_current_piece.text()
        else:
            self.__update_vlc_medium(self._current_piece['play_next'])
            # next is last movement
            if self._current_piece['play_next'] == \
               len(self._current_piece['files']) - 1:
                self._current_piece['play_next'] = -1
            else:  # there are at least two movements of current piece left
                self._current_piece['play_next'] += 1
        if self._status == 'Paused' and not reset_pause_after_current:
            self.__action_play_pause()
        elif reset_pause_after_current:
            self.parentWidget().set_pause_after_current(False)
        else:
            self._vlc_mediaplayer.play()
        self.parentWidget().update_status_bar(
            self._status,
            f'{len(self._pieces) - len(self._playlist)}/{len(self._pieces)}')

    def __action_play_pause(self):
        """ (gets called when self._btn_play_pause is clicked)
            toggles playing/pausing music and updates everything as needed """

        # don't do anything now (maybe end of playlist reached?)
        if self._current_piece['title'] == '':
            return

        if self._status == 'Paused':
            if not self._vlc_medium:
                self.__action_next()
            self._vlc_mediaplayer.play()
            self._btn_play_pause.setIcon(QIcon(get_icon_path('pause')))
            self._status = 'Playing'
        else:
            self._vlc_mediaplayer.pause()
            self._btn_play_pause.setIcon(QIcon(get_icon_path('play')))
            self._status = 'Paused'
        self.parentWidget().update_status_bar(
            self._status,
            f'{len(self._pieces) - len(self._playlist)}/{len(self._pieces)}')

    def __action_previous(self):
        """ (called when self._btn_previous ist clicked)
            goes back one movement of the current piece, if possible
            (cannot go back to previous piece) """

        # can't go back to previous piece, but current one has no or one movement
        if len(self._current_piece['files']) <= 1:
            pass
        # currently playing first movement, so nothing to do as well
        elif self._current_piece['play_next'] == 1:
            pass
        else:  # we can go back one movement
            # currently at last movement
            if self._current_piece['play_next'] == -1:
                # set play_next to last movement
                self._current_piece['play_next'] = \
                    len(self._current_piece['files']) - 1
            else:  # currently before last movement
                # set play_next to current movement
                self._current_piece['play_next'] -= 1
            self._vlc_mediaplayer.stop()
            self.__update_vlc_medium(self._current_piece['play_next'] - 1)
            self._vlc_mediaplayer.play()

    def __action_volume_clicked(self):
        """ (called when self._btn_volume is clicked)
            (un)mutes volume """

        if self._slider_volume.value() == 0:  # unmute volume
            self._slider_volume.setValue(self._volume_before_muted)
        else:  # mute volume
            self._volume_before_muted = self._slider_volume.value()
            self._slider_volume.setValue(0)

    def __event_movement_ended(self, event):
        """ (called when self._vlc_media_player emits a MediaPlayerEndReached
            event)
            sets self._skip_to_next to True so the next self.__update call
            will trigger self.__action_next """

        self._skip_to_next = True

    def __event_movement_selected(self):
        """ (called when self._listwidget_movements emits itemClicked)
            skips to the newly selected movement """

        index = self._listwidget_movements.indexFromItem(
            self._listwidget_movements.currentItem()).row()
        # user selected a movement different from the current one
        if index != self.__get_current_movement_index():
            self._current_piece['play_next'] = index
            self.__action_next()

    def __event_piece_text_changed(self):
        """ (called when self._lineedit_current_piece emits textChanged)
            ensures that the user sees the beginning of the text in
            self._lineedit_current_piece (if text is too long, the end will be
            cut off and the user must scroll manually to see it) """

        self._lineedit_current_piece.setCursorPosition(0)

    def __event_volume_changed(self):
        """ (called when value of self._slider_volume changes)
            updates text of self._lbl_volume to new value of self._slider_value
            and sets icon of self._btn_volume to a fitting one """

        volume = self._slider_volume.value()
        self._lbl_volume.setText(f'{volume}%')
        if volume == 0:
            self._btn_volume.setIcon(QIcon(get_icon_path('volume-muted')))
        elif volume < 34:
            self._btn_volume.setIcon(QIcon(get_icon_path('volume-low')))
        elif volume < 67:
            self._btn_volume.setIcon(QIcon(get_icon_path('volume-medium')))
        else:
            self._btn_volume.setIcon(QIcon(get_icon_path('volume-high')))
        self._vlc_mediaplayer.audio_set_volume(volume)

    def __event_time_changed_by_user(self):
        """ (called when user releases self._slider_time)
            synchronizes self._vlc_mediaplayer's position to the new value
            of self._slider_time """

        self._vlc_mediaplayer.set_position(self._slider_time.value() / 100)

    def __get_current_movement_index(self):
        """ returns the index of the current movement in
            self._current_piece['files'] """

        play_next = self._current_piece['play_next']
        if play_next == -1:
            return len(self._current_piece['files']) - 1
        else:
            return play_next - 1

    def __on_press(self, key):
        """ (called by self._keyboard_listener when a key is pressed)
            looks up key code corresponding to key and calls the appropriate
            action function """

        try:  # key is not always of the same type (why would it be?!)
            key_code = key.vk
        except AttributeError:
            key_code = key.value.vk
        if key_code in self._KEY_CODES_PLAY_PAUSE:
            self.__action_play_pause()
        elif key_code in self._KEY_CODES_NEXT:
            self.__action_next()
        elif key_code in self._KEY_CODES_PREVIOUS:
            self.__action_previous()

    def __update_movement_list(self):
        """ removes all items currently in self._listwidget_movements and adds
            everything in self._current_piece['files] """

        # TODO: use ID3 title instead of filename

        while self._listwidget_movements.count() > 0:
            self._listwidget_movements.takeItem(0)
        files = self._current_piece['files']
        if os_name == 'nt':  # windows paths look different than posix paths
            # remove path to file, title number and .mp3 ending
            files = [i[i.rfind('\\') + 3:-4] for i in files]
        else:
            files = [i[i.rfind('/') + 4:-4] for i in files]
        self._listwidget_movements.addItems(files)

    def __update(self):
        """ (periodically called when self._timer emits timeout signal)
            updates various ui elements"""

        # -- select currently playing movement in self._listwidget_movements --
        if self._listwidget_movements.count() > 0:
            self._listwidget_movements.item(
                self.__get_current_movement_index()).setSelected(True)

        # -- update text of self._lbl_time_played and self._lbl_time_left,
        # if necessary --
        if self._vlc_medium:
            try:
                time_played = self._vlc_mediaplayer.get_time()
                medium_duration = self._vlc_medium.get_duration()
                # other values don't make sense (but do occur)
                if (time_played >= 0) and (time_played <= medium_duration):
                    self._lbl_time_played.setText(
                        get_time_str_from_ms(time_played))
                else:
                    self._lbl_time_played.setText(get_time_str_from_ms(0))
                self._lbl_time_left.setText(
                    f'-{get_time_str_from_ms(medium_duration - time_played)}')
            except OSError:  # don't know why that occurs sometimes
                pass

        # -- update value of self._slider_time --
        # don't reset slider to current position if user is dragging it
        if not self._slider_time.isSliderDown():
            try:
                self._slider_time.setValue(
                    self._vlc_mediaplayer.get_position() * 100)
            except OSError:  # don't know why that occurs sometimes
                pass

        if self._skip_to_next:
            self._skip_to_next = False
            self.__action_next()

    def __update_vlc_medium(self, files_index):
        old_medium = self._vlc_medium
        self._vlc_medium = self._vlc_instance.media_new(
            self._current_piece['files'][files_index])
        self._vlc_medium.parse()
        self._vlc_mediaplayer.set_media(self._vlc_medium)
        if old_medium:  # only release if not None
            old_medium.release()

    def get_history(self):
        """ getter function for parent widget """

        return self._history

    def get_set_str(self):
        """ getter function for parent widget """

        return self._set_str if self._set_str != '' \
            else 'No directory set loaded.'

    def set_pieces_and_playlist(self, pieces, playlist, set_str, shuffled):
        """ needed so that DirectorySetChooseDialog can set our self._pieces
            and self._playlist """

        # just to be sure
        if isinstance(pieces, dict) and isinstance(playlist, list):
            self._vlc_mediaplayer.stop()
            self._set_str = set_str
            self._pieces = pieces
            self._playlist = playlist
            self._shuffled = shuffled
            self._current_piece['title'] = self._playlist.pop(0)
            self._current_piece['files'] = [
                p.replace('"', '')
                for p in self._pieces[self._current_piece['title']]
            ]
            self._current_piece['play_next'] = \
                1 if len(self._current_piece['files']) > 1 else -1
            self._lineedit_current_piece.setText(
                create_info_str(self._current_piece['title'],
                                self._current_piece['files']))
            self.__update_movement_list()
            self.__update_vlc_medium(0)
            self._history[datetime.now().strftime('%H:%M:%S')] = \
                self._lineedit_current_piece.text()

    def exit(self):
        """ exits cleanly """

        try:  # don't know why that occurs sometimes
            self._vlc_mediaplayer.stop()
            self._vlc_mediaplayer.release()
            self._vlc_instance.release()
        except OSError:
            pass

        self._keyboard_listener.stop()
Exemple #6
0
class SynthPdfWidget(QWidget):
    def __init__(self, status_bar):
        super(SynthPdfWidget, self).__init__()
        self.status_bar = status_bar
        layout = QHBoxLayout()
        self.list_view = QListWidget()
        self.list_view.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.list_view.setAlternatingRowColors(True)
        self.list_view.itemClicked.connect(self.click_item_signal)
        self.list_view.itemEntered.connect(self.click_item_signal)
        self.list_view.itemSelectionChanged.connect(
            self.change_selection_signal)

        controls_layout = QVBoxLayout()
        controls_layout.setAlignment(Qt.AlignTop)

        select_zone = SelectWidget(self.click_move_up, self.click_move_down,
                                   self.click_invert, self.click_delete)
        select_zone.setMinimumWidth(250)

        add_pages_button = QPushButton("Add Pages From PDF")
        add_pages_button.clicked.connect(self.add_pdf)

        controls_layout.addWidget(select_zone)
        controls_layout.addWidget(add_pages_button)

        layout.addWidget(self.list_view)
        layout.addLayout(controls_layout)
        self.setLayout(layout)

    def _add_pages_from_pdf(self, pdf_path):
        with open(pdf_path, "rb") as file:
            reader = PdfFileReader(file)
            file_name = os.path.basename(pdf_path)
            pages_count = reader.getNumPages()
            for i in range(pages_count):
                new_item = ImageListItem(
                    "page " + str(i + 1) + " (" + file_name + ")",
                    (pdf_path, i))
                self.list_view.addItem(new_item)
            self.status_bar.showMessage("Add " + str(pages_count) + " pages")

    # ----------external methods from create command
    def extern_get_files_and_pages(
        self
    ):  # return selected pages in the form {file1: [p1, p2, ...], file2: [p1, p2, ...], ...}
        to_return = {}
        if len(self.list_view.selectedItems()
               ) == 0:  # nothing selected, add all pages
            items_count = self.list_view.count()
            for item_index in range(items_count):
                item = self.list_view.item(item_index)
                item_data = item.get_data()
                file_name = item_data[0]
                page_index = item_data[1]
                if file_name in to_return:
                    to_return[file_name].append(page_index)
                else:
                    to_return[file_name] = [page_index]
        else:
            for s in self.list_view.selectedItems():
                s_data = s.get_data()
                file_name = s_data[0]
                page_index = s_data[1]
                if file_name in to_return:
                    to_return[file_name].append(page_index)
                else:
                    to_return[file_name] = [page_index]
        return to_return

    # ----------add button-----------------------
    def add_pdf(self):
        files_dialog = QFileDialog()
        files_dialog.setNameFilter("PDF (*.pdf)")
        files_dialog.setFileMode(QFileDialog.ExistingFiles)
        if files_dialog.exec_():
            files = files_dialog.selectedFiles()
            for f_path in files:
                self._add_pages_from_pdf(f_path)

    # ----------List_view signals----------------
    def click_item_signal(self, item):
        self.status_bar.showMessage("Select " + str(item.text()))

    def change_selection_signal(self):
        if len(self.list_view.selectedItems()) == 0:
            # self._set_preview(None)  # nothing selected
            pass

    # ----------list_view commands---------------
    def click_move_up(self):
        selected = self.list_view.selectedItems()
        selected_indexes = [
            self.list_view.indexFromItem(sel).row() for sel in selected
        ]
        selected_indexes.sort()
        if len(selected_indexes) > 0 and selected_indexes[0] > 0:
            for index in selected_indexes:
                prev_item = self.list_view.takeItem(index - 1)
                self.list_view.insertItem(index, prev_item)
            self.status_bar.showMessage("Move " + str(len(selected_indexes)) +
                                        " items")
        else:
            self.status_bar.showMessage("Nothing to move")

    def click_move_down(self):
        selected = self.list_view.selectedItems()
        selected_indexes = [
            self.list_view.indexFromItem(sel).row() for sel in selected
        ]
        selected_indexes.sort()
        sel_count = len(selected_indexes)
        if len(selected_indexes) > 0 and selected_indexes[
                sel_count - 1] < self.list_view.count() - 1:
            for i_index in range(sel_count):
                next_item = self.list_view.takeItem(
                    selected_indexes[sel_count - i_index - 1] + 1)
                self.list_view.insertItem(
                    selected_indexes[sel_count - i_index - 1], next_item)
            self.status_bar.showMessage("Move " + str(len(selected_indexes)) +
                                        " items")
        else:
            self.status_bar.showMessage("Nothing to move")

    def click_invert(self):
        selected = self.list_view.selectedItems()
        selected_indexes = []
        for sel in selected:
            selected_indexes.append(self.list_view.indexFromItem(sel).row())
        total_indexes = [i for i in range(self.list_view.count())]
        new_indexes = []
        for i in total_indexes:
            if i not in selected_indexes:
                new_indexes.append(i)
        self.list_view.clearSelection()
        for i in new_indexes:
            self.list_view.item(i).setSelected(True)
        self.status_bar.showMessage("Invert selection: " + str(new_indexes))

    def click_delete(self):
        selected = self.list_view.selectedItems()
        delete_names = []
        for s in selected:
            s_index = self.list_view.indexFromItem(s).row()
            del_item = self.list_view.takeItem(s_index)
            delete_names.append(del_item.text())
        if len(delete_names) == 0:
            self.status_bar.showMessage("Nothing to delete")
        else:
            self.status_bar.showMessage("Delete items: " +
                                        ", ".join(delete_names))