Beispiel #1
0
    def test_get_File(self):
        """ Test the File.get method """

        file = File.get(id=self.file_ids[1])
        self.assertTrue(file)

        # Do not find a File
        file = File.get(id="invalidID")
        self.assertEqual(file, None)
Beispiel #2
0
    def test_get_File(self):
        """ Test the File.get method """

        # Import additional classes that need the app defined first
        from classes.query import File

        # Find a File named file1
        file = File.get(id=TestQueryClass.file_ids[1])
        self.assertTrue(file)

        # Do not find a File
        file = File.get(id="invalidID")
        self.assertEqual(file, None)
Beispiel #3
0
    def value_updated(self, item):
        """ Name or tags updated """
        # Get translation method
        _ = get_app()._tr

        # Determine what was changed
        file_id = self.files_model.model.item(item.row(), 5).text()
        name = self.files_model.model.item(item.row(), 1).text()
        tags = self.files_model.model.item(item.row(), 2).text()

        # Get file object and update friendly name and tags attribute
        f = File.get(id=file_id)
        if name != f.data["path"]:
            f.data["name"] = name
        else:
            f.data["name"] = ""
        if "tags" in f.data.keys():
            if tags != f.data["tags"]:
                f.data["tags"] = tags
        elif tags:
            f.data["tags"] = tags

        # Tell file model to ignore updates (since this treeview will already be updated)
        self.files_model.ignore_update_signal = True

        # Save File
        f.save()

        # Re-enable updates
        self.files_model.ignore_update_signal = False
Beispiel #4
0
    def test_update_File(self):
        """ Test the File.save method """

        update_id = self.file_ids[0]
        file = File.get(id=update_id)
        self.assertTrue(file)

        # Update File
        file.data["height"] = 1080
        file.data["width"] = 1920
        file.save()

        # Verify updated data
        file = File.get(id=update_id)
        self.assertEqual(file.data["height"], 1080)
        self.assertEqual(file.data["width"], 1920)
Beispiel #5
0
    def update_file_thumbnail(self, file_id):
        """Update/re-generate the thumbnail of a specific file"""
        file = File.get(id=file_id)
        path, filename = os.path.split(file.data["path"])
        name = file.data.get("name", filename)

        # Refresh thumbnail for updated file
        self.ignore_updates = True
        m = self.model

        if file_id in self.model_ids:
            # Look up stored index to ID column
            id_index = self.model_ids[file_id]
            if not id_index.isValid():
                return

            # Update thumb for file
            thumb_path = self.get_thumb_path(file_id, 1, clear_cache=True)
            thumb_index = id_index.sibling(id_index.row(), 0)
            item = m.itemFromIndex(thumb_index)
            item.setIcon(QIcon(thumb_path))
            item.setText(name)

            # Emit signal when model is updated
            self.ModelRefreshed.emit()

        self.ignore_updates = False
Beispiel #6
0
    def contextMenuEvent(self, event):
        # Update selection
        self.updateSelection()

        # Set context menu mode
        app = get_app()
        app.context_menu_object = "files"

        menu = QMenu(self)

        menu.addAction(self.win.actionImportFiles)
        menu.addAction(self.win.actionThumbnailView)
        if self.selected:
            # If file selected, show file related options
            menu.addSeparator()

            # Add edit title option (if svg file)
            selected_file_id = self.win.selected_files[0]
            file = File.get(id=selected_file_id)
            if file and file.data.get("path").endswith(".svg"):
                menu.addAction(self.win.actionEditTitle)
                menu.addAction(self.win.actionDuplicateTitle)
                menu.addSeparator()

            menu.addAction(self.win.actionPreview_File)
            menu.addAction(self.win.actionSplitClip)
            menu.addAction(self.win.actionAdd_to_Timeline)
            menu.addAction(self.win.actionFile_Properties)
            menu.addSeparator()
            menu.addAction(self.win.actionRemove_from_Project)
            menu.addSeparator()

        # Show menu
        menu.exec_(QCursor.pos())
    def add_file(self, filepath):
        # Add file into project

        app = get_app()
        _ = app._tr

        # Check for this path in our existing project data
        # ["1F595-1F3FE",
        # "openshot-qt-git/src/emojis/color/svg/1F595-1F3FE.svg"]
        file = File.get(path=filepath)

        # If this file is already found, exit
        if file:
            return file

        # Load filepath in libopenshot clip object (which will try multiple readers to open it)
        clip = openshot.Clip(filepath)

        # Get the JSON for the clip's internal reader
        try:
            reader = clip.Reader()
            file_data = json.loads(reader.Json())

            # Determine media type
            file_data["media_type"] = "image"

            # Save new file to the project data
            file = File()
            file.data = file_data
            file.save()
            return file

        except Exception as ex:
            # Log exception
            log.warning("Failed to import file: {}".format(str(ex)))
Beispiel #8
0
    def test_delete_File(self):
        """ Test the File.delete method """

        delete_id = self.file_ids[4]
        file = File.get(id=delete_id)
        self.assertTrue(file)

        file.delete()

        # Verify deleted data
        deleted_file = File.get(id=delete_id)
        self.assertFalse(deleted_file)

        # Delete File again (should do nothing)
        file.delete()
        deleted_file = File.get(id=delete_id)
        self.assertFalse(deleted_file)
Beispiel #9
0
    def rect_select_clicked(self, widget, param):
        """Rect select button clicked"""
        self.context[param["setting"]].update({"button-clicked": True})

        # show dialog
        from windows.region import SelectRegion
        from classes.query import File, Clip

        c = Clip.get(id=self.clip_id)
        reader_path = c.data.get('reader', {}).get('path','')
        f = File.get(path=reader_path)
        if f:
            win = SelectRegion(f, self.clip_instance)
            # Run the dialog event loop - blocking interaction on this window during that time
            result = win.exec_()
            if result == QDialog.Accepted:
                # self.first_frame = win.current_frame
                # Region selected (get coordinates if any)
                topLeft = win.videoPreview.regionTopLeftHandle
                bottomRight = win.videoPreview.regionBottomRightHandle
                viewPortSize = win.viewport_rect
                curr_frame_size = win.videoPreview.curr_frame_size

                x1 = topLeft.x() / curr_frame_size.width()
                y1 = topLeft.y() / curr_frame_size.height()
                x2 = bottomRight.x() / curr_frame_size.width()
                y2 = bottomRight.y() / curr_frame_size.height()

                # Get QImage of region
                if win.videoPreview.region_qimage:
                    region_qimage = win.videoPreview.region_qimage

                    # Resize QImage to match button size
                    resized_qimage = region_qimage.scaled(widget.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)

                    # Draw Qimage onto QPushButton (to display region selection to user)
                    palette = widget.palette()
                    palette.setBrush(widget.backgroundRole(), QBrush(resized_qimage))
                    widget.setFlat(True)
                    widget.setAutoFillBackground(True)
                    widget.setPalette(palette)

                    # Remove button text (so region QImage is more visible)
                    widget.setText("")

                # If data found, add to context
                if topLeft and bottomRight:
                    self.context[param["setting"]].update({"normalized_x": x1, "normalized_y": y1,
                                                           "normalized_width": x2-x1,
                                                           "normalized_height": y2-y1,
                                                           "first-frame": win.current_frame,
                                                           })
                    log.info(self.context)

        else:
            log.error('No file found with path: %s' % reader_path)
Beispiel #10
0
    def test_update_File(self):
        """ Test the File.save method """

        # Import additional classes that need the app defined first
        from classes.query import File

        # Find a File named file1
        update_id = TestQueryClass.file_ids[0]
        file = File.get(id=update_id)
        self.assertTrue(file)

        # Update File
        file.data["height"] = 1080
        file.data["width"] = 1920
        file.save()

        # Verify updated data
        # Get File again
        file = File.get(id=update_id)
        self.assertEqual(file.data["height"], 1080)
        self.assertEqual(file.data["width"], 1920)
Beispiel #11
0
    def test_delete_File(self):
        """ Test the File.delete method """

        # Import additional classes that need the app defined first
        from classes.query import File

        # Find a File named file1
        delete_id = TestQueryClass.file_ids[4]
        file = File.get(id=delete_id)
        self.assertTrue(file)

        # Delete File
        file.delete()

        # Verify deleted data
        deleted_file = File.get(id=delete_id)
        self.assertFalse(deleted_file)

        # Delete File again (should do nothing
        file.delete()

        # Verify deleted data
        deleted_file = File.get(id=delete_id)
        self.assertFalse(deleted_file)
    def contextMenuEvent(self, event):

        # Set context menu mode
        app = get_app()
        app.context_menu_object = "files"

        index = self.indexAt(event.pos())

        # Build menu
        menu = QMenu(self)

        menu.addAction(self.win.actionImportFiles)
        menu.addAction(self.win.actionThumbnailView)

        if index.isValid():
            # Look up the model item and our unique ID
            item = self.files_model.model.itemFromIndex(index)
            file_id = self.files_model.model.item(item.row(), 5).text()

            try:
                # Check whether we know the item is selected
                i = self.win.selected_files.index(file_id)
            except ValueError:
                # Add to our list, if it's not already there
                self.win.selected_files.append(file_id)

            # If a valid file is selected, show file related options
            menu.addSeparator()

            # Add edit title option (if svg file)
            file = File.get(id=file_id)
            if file and file.data.get("path").endswith(".svg"):
                menu.addAction(self.win.actionEditTitle)
                menu.addAction(self.win.actionDuplicateTitle)
                menu.addSeparator()

            menu.addAction(self.win.actionPreview_File)
            menu.addAction(self.win.actionSplitClip)
            menu.addAction(self.win.actionAdd_to_Timeline)
            menu.addAction(self.win.actionFile_Properties)
            menu.addSeparator()
            menu.addAction(self.win.actionRemove_from_Project)
            menu.addSeparator()

        # Show menu
        menu.exec_(event.globalPos())
Beispiel #13
0
    def add_file(self, filepath):
        path, filename = os.path.split(filepath)

        # Add file into project
        app = get_app()
        _ = get_app()._tr

        # Check for this path in our existing project data
        file = File.get(path=filepath)

        # If this file is already found, exit
        if file:
            return

        # Load filepath in libopenshot clip object (which will try multiple readers to open it)
        clip = openshot.Clip(filepath)

        # Get the JSON for the clip's internal reader
        try:
            reader = clip.Reader()
            file_data = json.loads(reader.Json())

            # Determine media type
            if file_data["has_video"] and not self.is_image(file_data):
                file_data["media_type"] = "video"
            elif file_data["has_video"] and self.is_image(file_data):
                file_data["media_type"] = "image"
            elif file_data["has_audio"] and not file_data["has_video"]:
                file_data["media_type"] = "audio"

            # Save new file to the project data
            file = File()
            file.data = file_data
            file.save()
            return True

        except:
            # Handle exception
            msg = QMessageBox()
            msg.setText(
                _("{} is not a valid video, audio, or image file.".format(
                    filename)))
            msg.exec_()
            return False
Beispiel #14
0
    def contextMenuEvent(self, event):
        event.accept()

        # Set context menu mode
        app = get_app()
        app.context_menu_object = "files"

        index = self.indexAt(event.pos())

        # Build menu
        menu = QMenu(self)

        menu.addAction(self.win.actionImportFiles)
        menu.addAction(self.win.actionDetailsView)

        if index.isValid():
            # Look up the model item and our unique ID
            model = self.model()

            # Look up file_id from 5th column of row
            id_index = index.sibling(index.row(), 5)
            file_id = model.data(id_index, Qt.DisplayRole)

            # If a valid file selected, show file related options
            menu.addSeparator()

            # Add edit title option (if svg file)
            file = File.get(id=file_id)
            if file and file.data.get("path").endswith(".svg"):
                menu.addAction(self.win.actionEditTitle)
                menu.addAction(self.win.actionDuplicateTitle)
                menu.addSeparator()

            menu.addAction(self.win.actionPreview_File)
            menu.addAction(self.win.actionSplitClip)
            menu.addAction(self.win.actionAdd_to_Timeline)
            menu.addAction(self.win.actionFile_Properties)
            menu.addSeparator()
            menu.addAction(self.win.actionRemove_from_Project)
            menu.addSeparator()

        # Show menu
        menu.popup(event.globalPos())
    def add_file(self, filepath):
        """ Add an animation to the project file tree """
        path, filename = os.path.split(filepath)

        # Add file into project
        app = get_app()
        _ = get_app()._tr

        # Check for this path in our existing project data
        file = File.get(path=filepath)

        # If this file is already found, exit
        if file:
            return

        # Get the JSON for the clip's internal reader
        try:
            # Open image sequence in FFmpegReader
            reader = openshot.FFmpegReader(filepath)
            reader.Open()

            # Serialize JSON for the reader
            file_data = json.loads(reader.Json())

            # Set media type
            file_data["media_type"] = "video"

            # Save new file to the project data
            file = File()
            file.data = file_data
            file.save()
            return True

        except:
            # Handle exception
            msg = QMessageBox()
            msg.setText(
                _("{} is not a valid video, audio, or image file.".format(
                    filename)))
            msg.exec_()
            return False
    def add_file(self, filepath):
        path, filename = os.path.split(filepath)

        # Add file into project
        app = get_app()
        _ = get_app()._tr

        # Check for this path in our existing project data
        file = File.get(path=filepath)

        # If this file is already found, exit
        if file:
            return

        # Load filepath in libopenshot clip object (which will try multiple readers to open it)
        clip = openshot.Clip(filepath)

        # Get the JSON for the clip's internal reader
        reader = clip.Reader()
        file_data = json.loads(reader.Json())

        print("file_data:", file_data)

        # Determine media type
        if file_data["has_video"]:
            file_data["media_type"] = "video"
        elif file_data["has_audio"] and not file_data["has_video"]:
            file_data["media_type"] = "audio"

        # Save new file to the project data
        file = File()
        file.data = file_data

        # Save file
        file.save()

        # open in timeline added by yanght======
        self.timeline.addNewClip(file)

        return True
Beispiel #17
0
    def add_file(self, filepath):
        filename = os.path.basename(filepath)

        # Add file into project
        app = get_app()
        _ = get_app()._tr

        # Check for this path in our existing project data
        file = File.get(path=filepath)

        # If this file is already found, exit
        if file:
            return

        # Load filepath in libopenshot clip object (which will try multiple readers to open it)
        clip = openshot.Clip(filepath)

        # Get the JSON for the clip's internal reader
        try:
            reader = clip.Reader()
            file_data = json.loads(reader.Json())

            # Set media type
            file_data["media_type"] = "image"

            # Save new file to the project data
            file = File()
            file.data = file_data
            file.save()
            return True

        except Exception as ex:
            # Handle exception
            log.error('Could not import {}: {}'.format(filename, str(ex)))
            msg = QMessageBox()
            msg.setText(_("{} is not a valid video, audio, or image file.".format(filename)))
            msg.exec_()
            return False
Beispiel #18
0
    def value_updated(self, item):
        """ Name or tags updated """
        if self.files_model.ignore_updates:
            return

        # Get translation method
        _ = get_app()._tr

        # Determine what was changed
        file_id = self.files_model.model.item(item.row(), 5).text()
        name = self.files_model.model.item(item.row(), 1).text()
        tags = self.files_model.model.item(item.row(), 2).text()

        # Get file object and update friendly name and tags attribute
        f = File.get(id=file_id)
        f.data.update({"name": name or os.path.basename(f.data.get("path"))})
        if "tags" in f.data or tags:
            f.data.update({"tags": tags})

        # Save File
        f.save()

        # Update file thumbnail
        self.win.FileUpdated.emit(file_id)
Beispiel #19
0
 def selected_files(self):
     """ Get a list of File objects representing the current selection """
     files = []
     for id in self.selected_file_ids():
         files.append(File.get(id=id))
     return files
Beispiel #20
0
    def do_GET(self):
        # Pause processing of request (since we don't currently use thread pooling, this allows
        # the threads to be processed without choking the CPU as much
        # TODO: Make HTTPServer work with a limited thread pool and remove this sleep() hack.
        time.sleep(0.01)
        """ Process each GET request and return a value (image or file path)"""
        # Parse URL
        url_output = REGEX_THUMBNAIL_URL.match(self.path)
        if url_output and len(url_output.groups()) == 3:
            # Path is expected to have 3 matched components (third is optional though)
            #   /thumbnails/FILE-ID/FRAME-NUMBER/   or
            #   /thumbnails/FILE-ID/FRAME-NUMBER/path/
            self.send_response_only(200)
        else:
            self.send_error(404)
            return

        # Get URL parts
        file_id = url_output.group('file_id')
        file_frame = int(url_output.group('file_frame'))
        only_path = url_output.group('only_path')

        try:
            # Look up file data
            file = File.get(id=file_id)

            # Ensure file location is an absolute path
            file_path = file.absolute_path()
        except AttributeError:
            # Couldn't match file ID
            self.send_error(404)
            return

        # Send headers
        if not only_path:
            self.send_header('Content-type', 'image/png')
        else:
            self.send_header('Content-type', 'text/html')
        self.end_headers()

        # Locate thumbnail
        thumb_path = os.path.join(info.THUMBNAIL_PATH, file_id,
                                  "%s.png" % file_frame)
        if not os.path.exists(thumb_path) and file_frame == 1:
            # Try ID with no frame # (for backwards compatibility)
            thumb_path = os.path.join(info.THUMBNAIL_PATH, "%s.png" % file_id)
        if not os.path.exists(thumb_path) and file_frame != 1:
            # Try with ID and frame # in filename (for backwards compatibility)
            thumb_path = os.path.join(info.THUMBNAIL_PATH,
                                      "%s-%s.png" % (file_id, file_frame))

        if not os.path.exists(thumb_path):
            # Generate thumbnail (since we can't find it)

            # Determine if video overlay should be applied to thumbnail
            overlay_path = ""
            if file.data["media_type"] == "video":
                overlay_path = os.path.join(info.IMAGES_PATH, "overlay.png")

            # Create thumbnail image
            GenerateThumbnail(file_path, thumb_path, file_frame, 98, 64,
                              os.path.join(info.IMAGES_PATH, "mask.png"),
                              overlay_path)

        # Send message back to client
        if os.path.exists(thumb_path):
            if not only_path:
                self.wfile.write(open(thumb_path, 'rb').read())
            else:
                self.wfile.write(bytes(thumb_path, "utf-8"))
        return
Beispiel #21
0
    def add_file(self, filepath):
        path, filename = os.path.split(filepath)

        # Add file into project
        app = get_app()
        _ = get_app()._tr

        # Check for this path in our existing project data
        file = File.get(path=filepath)

        # If this file is already found, exit
        if file:
            return

        # Load filepath in libopenshot clip object (which will try multiple readers to open it)
        clip = openshot.Clip(filepath)

        # Get the JSON for the clip's internal reader
        try:
            reader = clip.Reader()
            file_data = json.loads(reader.Json())

            # Determine media type
            if file_data["has_video"] and not self.is_image(file_data):
                file_data["media_type"] = "video"
            elif file_data["has_video"] and self.is_image(file_data):
                file_data["media_type"] = "image"
            elif file_data["has_audio"] and not file_data["has_video"]:
                file_data["media_type"] = "audio"

            # Save new file to the project data
            file = File()
            file.data = file_data

            # Is this file an image sequence / animation?
            image_seq_details = self.get_image_sequence_details(filepath)
            if image_seq_details:
                # Update file with correct path
                folder_path = image_seq_details["folder_path"]
                file_name = image_seq_details["file_path"]
                base_name = image_seq_details["base_name"]
                fixlen = image_seq_details["fixlen"]
                digits = image_seq_details["digits"]
                extension = image_seq_details["extension"]

                if not fixlen:
                    zero_pattern = "%d"
                else:
                    zero_pattern = "%%0%sd" % digits

                # Generate the regex pattern for this image sequence
                pattern = "%s%s.%s" % (base_name, zero_pattern, extension)

                # Split folder name
                (parentPath, folderName) = os.path.split(folder_path)
                if not base_name:
                    # Give alternate name
                    file.data["name"] = "%s (%s)" % (folderName, pattern)

                # Load image sequence (to determine duration and video_length)
                image_seq = openshot.Clip(os.path.join(folder_path, pattern))

                # Update file details
                file.data["path"] = os.path.join(folder_path, pattern)
                file.data["media_type"] = "video"
                file.data["duration"] = image_seq.Reader().info.duration
                file.data["video_length"] = image_seq.Reader(
                ).info.video_length

            # Save file
            file.save()
            return True

        except:
            # Handle exception
            msg = QMessageBox()
            msg.setText(
                _("{} is not a valid video, audio, or image file.".format(
                    filename)))
            msg.exec_()
            return False
Beispiel #22
0
    def accept(self):
        """ Start exporting video """

        # get translations
        _ = get_app()._tr

        # Init progress bar
        self.progressExportVideo.setMinimum(self.txtStartFrame.value())
        self.progressExportVideo.setMaximum(self.txtEndFrame.value())
        self.progressExportVideo.setValue(self.txtStartFrame.value())

        # Prompt error message
        if self.txtStartFrame.value() == self.txtEndFrame.value():
            msg = QMessageBox()
            msg.setWindowTitle(_("Export Error"))
            msg.setText(
                _("Sorry, please select a valid range of frames to export"))
            msg.exec_()

            # Do nothing
            self.enableControls()
            self.exporting = False
            return

        # Disable controls
        self.disableControls()
        self.exporting = True

        # Determine type of export (video+audio, video, audio, image sequences)
        # _("Video & Audio"), _("Video Only"), _("Audio Only"), _("Image Sequence")
        export_type = self.cboExportTo.currentText()

        # Determine final exported file path (and replace blank paths with default ones)
        default_filename = "Untitled Project"
        default_folder = os.path.join(info.HOME_PATH)
        if export_type == _("Image Sequence"):
            file_name_with_ext = "%s%s" % (self.txtFileName.text().strip()
                                           or default_filename,
                                           self.txtImageFormat.text().strip())
        else:
            file_ext = self.txtVideoFormat.text().strip()
            file_name_with_ext = self.txtFileName.text().strip(
            ) or default_filename
            # Append extension, if not already present
            if not file_name_with_ext.endswith(file_ext):
                file_name_with_ext = '{}.{}'.format(file_name_with_ext,
                                                    file_ext)

        export_file_path = os.path.join(
            self.txtExportFolder.text().strip() or default_folder,
            file_name_with_ext)
        log.info("Export path: %s" % export_file_path)

        # Check if filename is valid (by creating a blank file in a temporary place)
        try:
            open(os.path.join(tempfile.gettempdir(), file_name_with_ext), 'w')
        except OSError:
            # Invalid path detected, so use default file name instead
            file_name_with_ext = "%s.%s" % (default_filename,
                                            self.txtVideoFormat.text().strip())
            export_file_path = os.path.join(
                self.txtExportFolder.text().strip() or default_folder,
                file_name_with_ext)
            log.info("Invalid export path detected, changing to: %s" %
                     export_file_path)

        file = File.get(path=export_file_path)
        if file:
            ret = QMessageBox.question(
                self, _("Export Video"),
                _("%s is an input file.\nPlease choose a different name.") %
                file_name_with_ext, QMessageBox.Ok)
            self.enableControls()
            self.exporting = False
            return

        # Handle exception
        if os.path.exists(export_file_path) and export_type in [
                _("Video & Audio"),
                _("Video Only"),
                _("Audio Only")
        ]:
            # File already exists! Prompt user
            ret = QMessageBox.question(
                self, _("Export Video"),
                _("%s already exists.\nDo you want to replace it?") %
                file_name_with_ext, QMessageBox.No | QMessageBox.Yes)
            if ret == QMessageBox.No:
                # Stop and don't do anything
                # Re-enable controls
                self.enableControls()
                self.exporting = False
                return

        # Init export settings
        video_settings = {
            "vformat":
            self.txtVideoFormat.text(),
            "vcodec":
            self.txtVideoCodec.text(),
            "fps": {
                "num": self.txtFrameRateNum.value(),
                "den": self.txtFrameRateDen.value()
            },
            "width":
            self.txtWidth.value(),
            "height":
            self.txtHeight.value(),
            "pixel_ratio": {
                "num": self.txtPixelRatioNum.value(),
                "den": self.txtPixelRatioDen.value()
            },
            "video_bitrate":
            int(self.convert_to_bytes(self.txtVideoBitRate.text())),
            "start_frame":
            self.txtStartFrame.value(),
            "end_frame":
            self.txtEndFrame.value()
        }

        audio_settings = {
            "acodec":
            self.txtAudioCodec.text(),
            "sample_rate":
            self.txtSampleRate.value(),
            "channels":
            self.txtChannels.value(),
            "channel_layout":
            self.cboChannelLayout.currentData(),
            "audio_bitrate":
            int(self.convert_to_bytes(self.txtAudioBitrate.text()))
        }

        # Override vcodec and format for Image Sequences
        if export_type == _("Image Sequence"):
            image_ext = os.path.splitext(
                self.txtImageFormat.text().strip())[1].replace(".", "")
            video_settings["vformat"] = image_ext
            if image_ext in ["jpg", "jpeg"]:
                video_settings["vcodec"] = "mjpeg"
            else:
                video_settings["vcodec"] = image_ext

        # Store updated export folder path in project file
        get_app().updates.update_untracked(["export_path"],
                                           os.path.dirname(export_file_path))
        # Mark project file as unsaved
        get_app().project.has_unsaved_changes = True

        # Set MaxSize (so we don't have any downsampling)
        self.timeline.SetMaxSize(video_settings.get("width"),
                                 video_settings.get("height"))

        # Set lossless cache settings (temporarily)
        export_cache_object = openshot.CacheMemory(500)
        self.timeline.SetCache(export_cache_object)

        # Rescale all keyframes (if needed)
        if self.export_fps_factor != 1.0:
            # Get a copy of rescaled project data (this does not modify the active project)
            rescaled_app_data = get_app().project.rescale_keyframes(
                self.export_fps_factor)

            # Load the "export" Timeline reader with the JSON from the real timeline
            self.timeline.SetJson(json.dumps(rescaled_app_data))

            # Re-update the timeline FPS again (since the timeline just got clobbered)
            self.updateFrameRate()

        # Create FFmpegWriter
        try:
            w = openshot.FFmpegWriter(export_file_path)

            # Set video options
            if export_type in [
                    _("Video & Audio"),
                    _("Video Only"),
                    _("Image Sequence")
            ]:
                w.SetVideoOptions(
                    True, video_settings.get("vcodec"),
                    openshot.Fraction(
                        video_settings.get("fps").get("num"),
                        video_settings.get("fps").get("den")),
                    video_settings.get("width"), video_settings.get("height"),
                    openshot.Fraction(
                        video_settings.get("pixel_ratio").get("num"),
                        video_settings.get("pixel_ratio").get("den")), False,
                    False, video_settings.get("video_bitrate"))

            # Set audio options
            if export_type in [_("Video & Audio"), _("Audio Only")]:
                w.SetAudioOptions(True, audio_settings.get("acodec"),
                                  audio_settings.get("sample_rate"),
                                  audio_settings.get("channels"),
                                  audio_settings.get("channel_layout"),
                                  audio_settings.get("audio_bitrate"))

            # Prepare the streams
            w.PrepareStreams()

            # These extra options should be set in an extra method
            # No feedback is given to the user
            # TODO: Tell user if option is not available
            if export_type in [_("Audio Only")]:
                # Muxing options for mp4/mov
                w.SetOption(openshot.AUDIO_STREAM, "muxing_preset",
                            "mp4_faststart")
            else:
                # Muxing options for mp4/mov
                w.SetOption(openshot.VIDEO_STREAM, "muxing_preset",
                            "mp4_faststart")
                # Set the quality in case crf was selected
                if "crf" in self.txtVideoBitRate.text():
                    w.SetOption(openshot.VIDEO_STREAM, "crf",
                                str(int(video_settings.get("video_bitrate"))))

            # Open the writer
            w.Open()

            # Notify window of export started
            title_message = ""
            get_app().window.ExportStarted.emit(
                export_file_path, video_settings.get("start_frame"),
                video_settings.get("end_frame"))

            progressstep = max(
                1,
                round((video_settings.get("end_frame") -
                       video_settings.get("start_frame")) / 1000))
            start_time_export = time.time()
            start_frame_export = video_settings.get("start_frame")
            end_frame_export = video_settings.get("end_frame")
            # Write each frame in the selected range
            for frame in range(video_settings.get("start_frame"),
                               video_settings.get("end_frame") + 1):
                # Update progress bar (emit signal to main window)
                if (frame % progressstep) == 0:
                    end_time_export = time.time()
                    if (((frame - start_frame_export) != 0) &
                        ((end_time_export - start_time_export) != 0)):
                        seconds_left = round(
                            (start_time_export - end_time_export) *
                            (frame - end_frame_export) /
                            (frame - start_frame_export))
                        fps_encode = ((frame - start_frame_export) /
                                      (end_time_export - start_time_export))
                        title_message = _(
                            "%(hours)d:%(minutes)02d:%(seconds)02d Remaining (%(fps)5.2f FPS)"
                        ) % {
                            'hours': seconds_left / 3600,
                            'minutes': (seconds_left / 60) % 60,
                            'seconds': seconds_left % 60,
                            'fps': fps_encode
                        }

                    # Emit frame exported
                    get_app().window.ExportFrame.emit(
                        title_message, video_settings.get("start_frame"),
                        video_settings.get("end_frame"), frame)

                    # Process events (to show the progress bar moving)
                    QCoreApplication.processEvents()

                # Write the frame object to the video
                w.WriteFrame(self.timeline.GetFrame(frame))

                # Check if we need to bail out
                if not self.exporting:
                    break

            # Close writer
            w.Close()

            # Emit final exported frame (with elapsed time)
            seconds_run = round((end_time_export - start_time_export))
            title_message = _(
                "%(hours)d:%(minutes)02d:%(seconds)02d Elapsed (%(fps)5.2f FPS)"
            ) % {
                'hours': seconds_run / 3600,
                'minutes': (seconds_run / 60) % 60,
                'seconds': seconds_run % 60,
                'fps': fps_encode
            }

            get_app().window.ExportFrame.emit(
                title_message, video_settings.get("start_frame"),
                video_settings.get("end_frame"), frame)

        except Exception as e:
            # TODO: Find a better way to catch the error. This is the only way I have found that
            # does not throw an error
            error_type_str = str(e)
            log.info("Error type string: %s" % error_type_str)

            if "InvalidChannels" in error_type_str:
                log.info("Error setting invalid # of channels (%s)" %
                         (audio_settings.get("channels")))
                track_metric_error("invalid-channels-%s-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec"),
                                    audio_settings.get("channels")))

            elif "InvalidSampleRate" in error_type_str:
                log.info("Error setting invalid sample rate (%s)" %
                         (audio_settings.get("sample_rate")))
                track_metric_error("invalid-sample-rate-%s-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec"),
                                    audio_settings.get("sample_rate")))

            elif "InvalidFormat" in error_type_str:
                log.info("Error setting invalid format (%s)" %
                         (video_settings.get("vformat")))
                track_metric_error("invalid-format-%s" %
                                   (video_settings.get("vformat")))

            elif "InvalidCodec" in error_type_str:
                log.info("Error setting invalid codec (%s/%s/%s)" %
                         (video_settings.get("vformat"),
                          video_settings.get("vcodec"),
                          audio_settings.get("acodec")))
                track_metric_error("invalid-codec-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec")))

            elif "ErrorEncodingVideo" in error_type_str:
                log.info("Error encoding video frame (%s/%s/%s)" %
                         (video_settings.get("vformat"),
                          video_settings.get("vcodec"),
                          audio_settings.get("acodec")))
                track_metric_error("video-encode-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec")))

            # Show friendly error
            friendly_error = error_type_str.split("> ")[0].replace("<", "")

            # Prompt error message
            msg = QMessageBox()
            msg.setWindowTitle(_("Export Error"))
            msg.setText(
                _("Sorry, there was an error exporting your video: \n%s") %
                friendly_error)
            msg.exec_()

        # Notify window of export started
        get_app().window.ExportEnded.emit(export_file_path)

        # Close timeline object
        self.timeline.Close()

        # Clear all cache
        self.timeline.ClearAllCache()

        # Re-set OMP thread enabled flag
        if self.s.get("omp_threads_enabled"):
            openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK = False
        else:
            openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK = True

        # Return scale mode to lower quality scaling (for faster previews)
        openshot.Settings.Instance().HIGH_QUALITY_SCALING = False

        # Handle end of export (for non-canceled exports)
        if self.s.get("show_finished_window") and self.exporting:
            # Hide cancel and export buttons
            self.cancel_button.setVisible(False)
            self.export_button.setVisible(False)

            # Reveal done button
            self.close_button.setVisible(True)

            # Make progress bar green (to indicate we are done)
            from PyQt5.QtGui import QPalette
            p = QPalette()
            p.setColor(QPalette.Highlight, Qt.green)
            self.progressExportVideo.setPalette(p)

            # Raise the window
            self.show()
        else:
            # Accept dialog
            super(Export, self).accept()
Beispiel #23
0
def import_xml():
    """Import final cut pro XML file"""
    app = get_app()
    _ = app._tr

    # Get FPS info
    fps_num = app.project.get("fps").get("num", 24)
    fps_den = app.project.get("fps").get("den", 1)
    fps_float = float(fps_num / fps_den)

    # Get XML path
    recommended_path = app.project.current_filepath or ""
    if not recommended_path:
        recommended_path = info.HOME_PATH
    else:
        recommended_path = os.path.dirname(recommended_path)
    file_path = QFileDialog.getOpenFileName(app.window, _("Import XML..."),
                                            recommended_path,
                                            _("Final Cut Pro (*.xml)"),
                                            _("Final Cut Pro (*.xml)"))[0]

    if not file_path or not os.path.exists(file_path):
        # User canceled dialog
        return

    # Parse XML file
    xmldoc = minidom.parse(file_path)

    # Get video tracks
    video_tracks = []
    for video_element in xmldoc.getElementsByTagName("video"):
        for video_track in video_element.getElementsByTagName("track"):
            video_tracks.append(video_track)
    audio_tracks = []
    for audio_element in xmldoc.getElementsByTagName("audio"):
        for audio_track in audio_element.getElementsByTagName("track"):
            audio_tracks.append(audio_track)

    # Loop through tracks
    track_index = 0
    for tracks in [audio_tracks, video_tracks]:
        for track_element in tracks:
            # Get clipitems on this track (if any)
            clips_on_track = track_element.getElementsByTagName("clipitem")
            if not clips_on_track:
                continue

            # Get # of tracks
            track_index += 1
            all_tracks = app.project.get("layers")
            track_number = list(
                reversed(sorted(
                    all_tracks,
                    key=itemgetter('number'))))[0].get("number") + 1000000

            # Create new track above existing layer(s)
            track = Track()
            is_locked = False
            if track_element.getElementsByTagName(
                    "locked")[0].childNodes[0].nodeValue == "TRUE":
                is_locked = True
            track.data = {
                "number": track_number,
                "y": 0,
                "label": "XML Import %s" % track_index,
                "lock": is_locked
            }
            track.save()

            # Loop through clips
            for clip_element in clips_on_track:
                # Get clip path
                xml_file_id = clip_element.getElementsByTagName(
                    "file")[0].getAttribute("id")
                clip_path = ""
                if clip_element.getElementsByTagName("pathurl"):
                    clip_path = clip_element.getElementsByTagName(
                        "pathurl")[0].childNodes[0].nodeValue
                else:
                    # Skip clipitem if no clippath node found
                    # This usually happens for linked audio clips (which OpenShot combines audio and thus ignores this)
                    continue

                clip_path, is_modified, is_skipped = find_missing_file(
                    clip_path)
                if is_skipped:
                    continue

                # Check for this path in our existing project data
                file = File.get(path=clip_path)

                # Load filepath in libopenshot clip object (which will try multiple readers to open it)
                clip_obj = openshot.Clip(clip_path)

                if not file:
                    # Get the JSON for the clip's internal reader
                    try:
                        reader = clip_obj.Reader()
                        file_data = json.loads(reader.Json())

                        # Determine media type
                        if file_data["has_video"] and not is_image(file_data):
                            file_data["media_type"] = "video"
                        elif file_data["has_video"] and is_image(file_data):
                            file_data["media_type"] = "image"
                        elif file_data[
                                "has_audio"] and not file_data["has_video"]:
                            file_data["media_type"] = "audio"

                        # Save new file to the project data
                        file = File()
                        file.data = file_data

                        # Save file
                        file.save()
                    except Exception:
                        # Ignore errors for now
                        pass

                if (file.data["media_type"] == "video"
                        or file.data["media_type"] == "image"):
                    # Determine thumb path
                    thumb_path = os.path.join(info.THUMBNAIL_PATH,
                                              "%s.png" % file.data["id"])
                else:
                    # Audio file
                    thumb_path = os.path.join(info.PATH, "images",
                                              "AudioThumbnail.png")

                # Create Clip object
                clip = Clip()
                clip.data = json.loads(clip_obj.Json())
                clip.data["file_id"] = file.id
                clip.data["title"] = clip_element.getElementsByTagName(
                    "name")[0].childNodes[0].nodeValue
                clip.data["layer"] = track.data.get("number", 1000000)
                clip.data["image"] = thumb_path
                clip.data["position"] = float(
                    clip_element.getElementsByTagName("start")
                    [0].childNodes[0].nodeValue) / fps_float
                clip.data["start"] = float(
                    clip_element.getElementsByTagName("in")
                    [0].childNodes[0].nodeValue) / fps_float
                clip.data["end"] = float(
                    clip_element.getElementsByTagName("out")
                    [0].childNodes[0].nodeValue) / fps_float

                # Loop through clip's effects
                for effect_element in clip_element.getElementsByTagName(
                        "effect"):
                    effectid = effect_element.getElementsByTagName(
                        "effectid")[0].childNodes[0].nodeValue
                    keyframes = effect_element.getElementsByTagName("keyframe")
                    if effectid == "opacity":
                        clip.data["alpha"] = {"Points": []}
                        for keyframe_element in keyframes:
                            keyframe_time = float(
                                keyframe_element.getElementsByTagName("when")
                                [0].childNodes[0].nodeValue)
                            keyframe_value = float(
                                keyframe_element.getElementsByTagName("value")
                                [0].childNodes[0].nodeValue) / 100.0
                            clip.data["alpha"]["Points"].append({
                                "co": {
                                    "X": round(keyframe_time),
                                    "Y": keyframe_value
                                },
                                "interpolation":
                                1  # linear
                            })
                    elif effectid == "audiolevels":
                        clip.data["volume"] = {"Points": []}
                        for keyframe_element in keyframes:
                            keyframe_time = float(
                                keyframe_element.getElementsByTagName("when")
                                [0].childNodes[0].nodeValue)
                            keyframe_value = float(
                                keyframe_element.getElementsByTagName("value")
                                [0].childNodes[0].nodeValue) / 100.0
                            clip.data["volume"]["Points"].append({
                                "co": {
                                    "X": round(keyframe_time),
                                    "Y": keyframe_value
                                },
                                "interpolation":
                                1  # linear
                            })

                # Save clip
                clip.save()

            # Update the preview and reselect current frame in properties
            app.window.refreshFrameSignal.emit()
            app.window.propertyTableView.select_frame(
                app.window.preview_thread.player.Position())
Beispiel #24
0
    def add_files(self, files, image_seq_details=None, quiet=False):
        # Access translations
        app = get_app()
        _ = app._tr

        # Make sure we're working with a list of files
        if not isinstance(files, (list, tuple)):
            files = [files]

        start_count = len(files)
        for count, filepath in enumerate(files):
            (dir_path, filename) = os.path.split(filepath)

            # Check for this path in our existing project data
            new_file = File.get(path=filepath)

            # If this file is already found, exit
            if new_file:
                del new_file
                continue

            try:
                # Load filepath in libopenshot clip object (which will try multiple readers to open it)
                clip = openshot.Clip(filepath)

                # Get the JSON for the clip's internal reader
                reader = clip.Reader()
                file_data = json.loads(reader.Json())

                # Determine media type
                if file_data["has_video"] and not is_image(file_data):
                    file_data["media_type"] = "video"
                elif file_data["has_video"] and is_image(file_data):
                    file_data["media_type"] = "image"
                elif file_data["has_audio"] and not file_data["has_video"]:
                    file_data["media_type"] = "audio"
                else:
                    # If none set, just assume video
                    file_data["media_type"] = "video"

                # Save new file to the project data
                new_file = File()
                new_file.data = file_data

                # Is this an image sequence / animation?
                seq_info = image_seq_details or self.get_image_sequence_details(filepath)

                if seq_info:
                    # Update file with correct path
                    folder_path = seq_info["folder_path"]
                    base_name = seq_info["base_name"]
                    fixlen = seq_info["fixlen"]
                    digits = seq_info["digits"]
                    extension = seq_info["extension"]

                    if not fixlen:
                        zero_pattern = "%d"
                    else:
                        zero_pattern = "%%0%sd" % digits

                    # Generate the regex pattern for this image sequence
                    pattern = "%s%s.%s" % (base_name, zero_pattern, extension)

                    # Split folder name
                    folderName = os.path.basename(folder_path)
                    if not base_name:
                        # Give alternate name
                        new_file.data["name"] = "%s (%s)" % (folderName, pattern)

                    # Load image sequence (to determine duration and video_length)
                    image_seq = openshot.Clip(os.path.join(folder_path, pattern))

                    # Update file details
                    new_file.data["path"] = os.path.join(folder_path, pattern)
                    new_file.data["media_type"] = "video"
                    new_file.data["duration"] = image_seq.Reader().info.duration
                    new_file.data["video_length"] = image_seq.Reader().info.video_length

                    log.info('Imported {} as image sequence {}'.format(
                        filepath, pattern))

                    # Remove any other image sequence files from the list we're processing
                    match_glob = "{}{}.{}".format(base_name, '[0-9]*', extension)
                    log.debug("Removing files from import list with glob: {}".format(match_glob))
                    for seq_file in glob.iglob(os.path.join(folder_path, match_glob)):
                        # Don't remove the current file, or we mess up the for loop
                        if seq_file in files and seq_file != filepath:
                            files.remove(seq_file)

                if not seq_info:
                    # Log our not-an-image-sequence import
                    log.info("Imported media file {}".format(filepath))

                # Save file
                new_file.save()

                if start_count > 15:
                    message = _("Importing %(count)d / %(total)d") % {
                            "count": count,
                            "total": len(files) - 1
                            }
                    app.window.statusBar.showMessage(message, 15000)

                # Let the event loop run to update the status bar
                get_app().processEvents()

                prev_path = app.project.get("import_path")
                if dir_path != prev_path:
                    app.updates.update_untracked(["import_path"], dir_path)

            except Exception as ex:
                # Log exception
                log.warning("Failed to import {}: {}".format(filepath, ex))

                if not quiet:
                    # Show message box to user
                    app.window.invalidImage(filename)

        # Reset list of ignored paths
        self.ignore_image_sequence_paths = []

        message = _("Imported %(count)d files") % {"count": len(files) - 1}
        app.window.statusBar.showMessage(message, 3000)
Beispiel #25
0
    def accept(self):
        """ Start exporting video """

        # get translations
        app = get_app()
        _ = app._tr

        # Disable controls
        self.txtFileName.setEnabled(False)
        self.txtExportFolder.setEnabled(False)
        self.tabWidget.setEnabled(False)
        self.export_button.setEnabled(False)
        self.exporting = True

        # Determine type of export (video+audio, video, audio, image sequences)
        # _("Video & Audio"), _("Video Only"), _("Audio Only"), _("Image Sequence")
        export_type = self.cboExportTo.currentText()

        # Determine final exported file path
        if export_type != _("Image Sequence"):
            file_name_with_ext = "%s.%s" % (self.txtFileName.text().strip(),
                                            self.txtVideoFormat.text().strip())
        else:
            file_name_with_ext = "%s%s" % (self.txtFileName.text().strip(),
                                           self.txtImageFormat.text().strip())
        export_file_path = os.path.join(self.txtExportFolder.text().strip(),
                                        file_name_with_ext)
        log.info(export_file_path)

        # Translate object
        _ = get_app()._tr

        file = File.get(path=export_file_path)
        if file:
            ret = QMessageBox.question(
                self, _("Export Video"),
                _("%s is an input file.\nPlease choose a different name.") %
                file_name_with_ext, QMessageBox.Ok)
            self.txtFileName.setEnabled(True)
            self.txtExportFolder.setEnabled(True)
            self.tabWidget.setEnabled(True)
            self.export_button.setEnabled(True)
            self.exporting = False
            return

        # Handle exception
        if os.path.exists(export_file_path) and export_type in [
                _("Video & Audio"),
                _("Video Only"),
                _("Audio Only")
        ]:
            # File already exists! Prompt user
            ret = QMessageBox.question(
                self, _("Export Video"),
                _("%s already exists.\nDo you want to replace it?") %
                file_name_with_ext, QMessageBox.No | QMessageBox.Yes)
            if ret == QMessageBox.No:
                # Stop and don't do anything
                # Re-enable controls
                self.txtFileName.setEnabled(True)
                self.txtExportFolder.setEnabled(True)
                self.tabWidget.setEnabled(True)
                self.export_button.setEnabled(True)
                self.exporting = False
                return

        # Init export settings
        video_settings = {
            "vformat":
            self.txtVideoFormat.text(),
            "vcodec":
            self.txtVideoCodec.text(),
            "fps": {
                "num": self.txtFrameRateNum.value(),
                "den": self.txtFrameRateDen.value()
            },
            "width":
            self.txtWidth.value(),
            "height":
            self.txtHeight.value(),
            "pixel_ratio": {
                "num": self.txtPixelRatioNum.value(),
                "den": self.txtPixelRatioDen.value()
            },
            "video_bitrate":
            int(self.convert_to_bytes(self.txtVideoBitRate.text())),
            "start_frame":
            self.txtStartFrame.value(),
            "end_frame":
            self.txtEndFrame.value() + 1
        }

        audio_settings = {
            "acodec":
            self.txtAudioCodec.text(),
            "sample_rate":
            self.txtSampleRate.value(),
            "channels":
            self.txtChannels.value(),
            "channel_layout":
            self.cboChannelLayout.currentData(),
            "audio_bitrate":
            int(self.convert_to_bytes(self.txtAudioBitrate.text()))
        }

        # Override vcodec and format for Image Sequences
        if export_type == _("Image Sequence"):
            image_ext = os.path.splitext(
                self.txtImageFormat.text().strip())[1].replace(".", "")
            video_settings["vformat"] = image_ext
            if image_ext in ["jpg", "jpeg"]:
                video_settings["vcodec"] = "mjpeg"
            else:
                video_settings["vcodec"] = image_ext

        # Set MaxSize (so we don't have any downsampling)
        self.timeline.SetMaxSize(video_settings.get("width"),
                                 video_settings.get("height"))

        # Set lossless cache settings (temporarily)
        export_cache_object = openshot.CacheMemory(250)
        self.timeline.SetCache(export_cache_object)

        # Create FFmpegWriter
        try:
            w = openshot.FFmpegWriter(export_file_path)

            # Set video options
            if export_type in [
                    _("Video & Audio"),
                    _("Video Only"),
                    _("Image Sequence")
            ]:
                w.SetVideoOptions(
                    True, video_settings.get("vcodec"),
                    openshot.Fraction(
                        video_settings.get("fps").get("num"),
                        video_settings.get("fps").get("den")),
                    video_settings.get("width"), video_settings.get("height"),
                    openshot.Fraction(
                        video_settings.get("pixel_ratio").get("num"),
                        video_settings.get("pixel_ratio").get("den")), False,
                    False, video_settings.get("video_bitrate"))

            # Set audio options
            if export_type in [_("Video & Audio"), _("Audio Only")]:
                w.SetAudioOptions(True, audio_settings.get("acodec"),
                                  audio_settings.get("sample_rate"),
                                  audio_settings.get("channels"),
                                  audio_settings.get("channel_layout"),
                                  audio_settings.get("audio_bitrate"))

            # Open the writer
            w.Open()

            # Notify window of export started
            get_app().window.ExportStarted.emit(
                export_file_path, video_settings.get("start_frame"),
                video_settings.get("end_frame"))

            progressstep = max(
                1,
                round((video_settings.get("end_frame") -
                       video_settings.get("start_frame")) / 100))
            # Write each frame in the selected range
            for frame in range(video_settings.get("start_frame"),
                               video_settings.get("end_frame")):
                # Update progress bar (emit signal to main window)
                if (frame % progressstep) == 0:
                    get_app().window.ExportFrame.emit(
                        export_file_path, video_settings.get("start_frame"),
                        video_settings.get("end_frame"), frame)

                # Process events (to show the progress bar moving)
                QCoreApplication.processEvents()

                # Write the frame object to the video
                w.WriteFrame(self.timeline.GetFrame(frame))

                # Check if we need to bail out
                if not self.exporting:
                    break

            # Close writer
            w.Close()

        except Exception as e:
            # TODO: Find a better way to catch the error. This is the only way I have found that
            # does not throw an error
            error_type_str = str(e)
            log.info("Error type string: %s" % error_type_str)

            if "InvalidChannels" in error_type_str:
                log.info("Error setting invalid # of channels (%s)" %
                         (audio_settings.get("channels")))
                track_metric_error("invalid-channels-%s-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec"),
                                    audio_settings.get("channels")))

            elif "InvalidSampleRate" in error_type_str:
                log.info("Error setting invalid sample rate (%s)" %
                         (audio_settings.get("sample_rate")))
                track_metric_error("invalid-sample-rate-%s-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec"),
                                    audio_settings.get("sample_rate")))

            elif "InvalidFormat" in error_type_str:
                log.info("Error setting invalid format (%s)" %
                         (video_settings.get("vformat")))
                track_metric_error("invalid-format-%s" %
                                   (video_settings.get("vformat")))

            elif "InvalidCodec" in error_type_str:
                log.info("Error setting invalid codec (%s/%s/%s)" %
                         (video_settings.get("vformat"),
                          video_settings.get("vcodec"),
                          audio_settings.get("acodec")))
                track_metric_error("invalid-codec-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec")))

            elif "ErrorEncodingVideo" in error_type_str:
                log.info("Error encoding video frame (%s/%s/%s)" %
                         (video_settings.get("vformat"),
                          video_settings.get("vcodec"),
                          audio_settings.get("acodec")))
                track_metric_error("video-encode-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec")))

            # Show friendly error
            friendly_error = error_type_str.split("> ")[0].replace("<", "")

            # Prompt error message
            msg = QMessageBox()
            _ = get_app()._tr
            msg.setWindowTitle(_("Export Error"))
            msg.setText(
                _("Sorry, there was an error exporting your video: \n%s") %
                friendly_error)
            msg.exec_()

        # Notify window of export started
        get_app().window.ExportEnded.emit(export_file_path)

        # Close timeline object
        self.timeline.Close()

        # Clear all cache
        self.timeline.ClearAllCache()

        # Accept dialog
        super(Export, self).accept()
Beispiel #26
0
def create_clip(context, track):
    """Create a new clip based on this context dict"""
    app = get_app()
    _ = app._tr

    # Get FPS info
    fps_num = app.project.get("fps").get("num", 24)
    fps_den = app.project.get("fps").get("den", 1)
    fps_float = float(fps_num / fps_den)

    # Get clip path (and prompt user if path not found)
    clip_path, is_modified, is_skipped = find_missing_file(
        context.get("clip_path", ""))
    if is_skipped:
        return

    # Get video context
    video_ctx = context.get("AX", {}).get("V", {})
    audio_ctx = context.get("AX", {}).get("A", {})

    # Check for this path in our existing project data
    file = File.get(path=clip_path)

    # Load filepath in libopenshot clip object (which will try multiple readers to open it)
    clip_obj = openshot.Clip(clip_path)

    if not file:
        # Get the JSON for the clip's internal reader
        try:
            reader = clip_obj.Reader()
            file_data = json.loads(reader.Json())

            # Determine media type
            if file_data["has_video"] and not is_image(file_data):
                file_data["media_type"] = "video"
            elif file_data["has_video"] and is_image(file_data):
                file_data["media_type"] = "image"
            elif file_data["has_audio"] and not file_data["has_video"]:
                file_data["media_type"] = "audio"

            # Save new file to the project data
            file = File()
            file.data = file_data

            # Save file
            file.save()
        except:
            log.warning('Error building File object for %s' % clip_path,
                        exc_info=1)

    if (file.data["media_type"] == "video"
            or file.data["media_type"] == "image"):
        # Determine thumb path
        thumb_path = os.path.join(info.THUMBNAIL_PATH,
                                  "%s.png" % file.data["id"])
    else:
        # Audio file
        thumb_path = os.path.join(info.PATH, "images", "AudioThumbnail.png")

    # Create Clip object
    clip = Clip()
    clip.data = json.loads(clip_obj.Json())
    clip.data["file_id"] = file.id
    clip.data["title"] = context.get("clip_path", "")
    clip.data["layer"] = track.data.get("number", 1000000)
    if video_ctx and not audio_ctx:
        # Only video
        clip.data["position"] = timecodeToSeconds(
            video_ctx.get("timeline_position", "00:00:00:00"), fps_num,
            fps_den)
        clip.data["start"] = timecodeToSeconds(
            video_ctx.get("clip_start_time", "00:00:00:00"), fps_num, fps_den)
        clip.data["end"] = timecodeToSeconds(
            video_ctx.get("clip_end_time", "00:00:00:00"), fps_num, fps_den)
        clip.data["has_audio"] = {
            "Points": [{
                "co": {
                    "X": 1.0,
                    "Y": 0.0  # Disable audio
                },
                "interpolation": 2
            }]
        }
    elif audio_ctx and not video_ctx:
        # Only audio
        clip.data["position"] = timecodeToSeconds(
            audio_ctx.get("timeline_position", "00:00:00:00"), fps_num,
            fps_den)
        clip.data["start"] = timecodeToSeconds(
            audio_ctx.get("clip_start_time", "00:00:00:00"), fps_num, fps_den)
        clip.data["end"] = timecodeToSeconds(
            audio_ctx.get("clip_end_time", "00:00:00:00"), fps_num, fps_den)
        clip.data["has_video"] = {
            "Points": [{
                "co": {
                    "X": 1.0,
                    "Y": 0.0  # Disable video
                },
                "interpolation": 2
            }]
        }
    else:
        # Both video and audio
        clip.data["position"] = timecodeToSeconds(
            video_ctx.get("timeline_position", "00:00:00:00"), fps_num,
            fps_den)
        clip.data["start"] = timecodeToSeconds(
            video_ctx.get("clip_start_time", "00:00:00:00"), fps_num, fps_den)
        clip.data["end"] = timecodeToSeconds(
            video_ctx.get("clip_end_time", "00:00:00:00"), fps_num, fps_den)

    # Add volume keyframes
    if context.get("volume"):
        clip.data["volume"] = {"Points": []}
        for keyframe in context.get("volume", []):
            clip.data["volume"]["Points"].append({
                "co": {
                    "X":
                    round(
                        timecodeToSeconds(keyframe.get("time", 0.0), fps_num,
                                          fps_den) * fps_float),
                    "Y":
                    keyframe.get("value", 0.0)
                },
                "interpolation": 1  # linear
            })

    # Add alpha keyframes
    if context.get("opacity"):
        clip.data["alpha"] = {"Points": []}
        for keyframe in context.get("opacity", []):
            clip.data["alpha"]["Points"].append({
                "co": {
                    "X":
                    round(
                        timecodeToSeconds(keyframe.get("time", 0.0), fps_num,
                                          fps_den) * fps_float),
                    "Y":
                    keyframe.get("value", 0.0)
                },
                "interpolation": 1  # linear
            })

    # Save clip
    clip.save()
Beispiel #27
0
 def current_file(self):
     """ Get the File object for the current files-view item, or the first selection """
     cur_id = self.current_file_id()
     if cur_id:
         return File.get(id=cur_id)
    def accept(self):
        """ Start exporting video """

        # get translations
        app = get_app()
        _ = app._tr

        # Disable controls
        self.txtFileName.setEnabled(False)
        self.txtExportFolder.setEnabled(False)
        self.tabWidget.setEnabled(False)
        self.export_button.setEnabled(False)
        self.exporting = True

        # Determine type of export (video+audio, video, audio, image sequences)
        # _("Video & Audio"), _("Video Only"), _("Audio Only"), _("Image Sequence")
        export_type = self.cboExportTo.currentText()

        # Determine final exported file path
        if export_type != _("Image Sequence"):
            file_name_with_ext = "%s.%s" % (self.txtFileName.text().strip(),
                                            self.txtVideoFormat.text().strip())
        else:
            file_name_with_ext = "%s%s" % (self.txtFileName.text().strip(),
                                           self.txtImageFormat.text().strip())
        export_file_path = os.path.join(self.txtExportFolder.text().strip(),
                                        file_name_with_ext)
        log.info(export_file_path)

        # Translate object
        _ = get_app()._tr

        file = File.get(path=export_file_path)
        if file:
            ret = QMessageBox.question(
                self, _("Export Video"),
                _("%s is an input file.\nPlease choose a different name.") %
                file_name_with_ext, QMessageBox.Ok)
            self.txtFileName.setEnabled(True)
            self.txtExportFolder.setEnabled(True)
            self.tabWidget.setEnabled(True)
            self.export_button.setEnabled(True)
            self.exporting = False
            return

        # Handle exception
        if os.path.exists(export_file_path) and export_type in [
                _("Video & Audio"),
                _("Video Only"),
                _("Audio Only")
        ]:
            # File already exists! Prompt user
            ret = QMessageBox.question(
                self, _("Export Video"),
                _("%s already exists.\nDo you want to replace it?") %
                file_name_with_ext, QMessageBox.No | QMessageBox.Yes)
            if ret == QMessageBox.No:
                # Stop and don't do anything
                # Re-enable controls
                self.txtFileName.setEnabled(True)
                self.txtExportFolder.setEnabled(True)
                self.tabWidget.setEnabled(True)
                self.export_button.setEnabled(True)
                self.exporting = False
                return

        # Init export settings
        video_settings = {
            "vformat":
            self.txtVideoFormat.text(),
            "vcodec":
            self.txtVideoCodec.text(),
            "fps": {
                "num": self.txtFrameRateNum.value(),
                "den": self.txtFrameRateDen.value()
            },
            "width":
            self.txtWidth.value(),
            "height":
            self.txtHeight.value(),
            "pixel_ratio": {
                "num": self.txtPixelRatioNum.value(),
                "den": self.txtPixelRatioDen.value()
            },
            "video_bitrate":
            int(self.convert_to_bytes(self.txtVideoBitRate.text())),
            "start_frame":
            self.txtStartFrame.value(),
            "end_frame":
            self.txtEndFrame.value() + 1
        }

        audio_settings = {
            "acodec":
            self.txtAudioCodec.text(),
            "sample_rate":
            self.txtSampleRate.value(),
            "channels":
            self.txtChannels.value(),
            "channel_layout":
            self.cboChannelLayout.currentData(),
            "audio_bitrate":
            int(self.convert_to_bytes(self.txtAudioBitrate.text()))
        }

        # Override vcodec and format for Image Sequences
        if export_type == _("Image Sequence"):
            image_ext = os.path.splitext(
                self.txtImageFormat.text().strip())[1].replace(".", "")
            video_settings["vformat"] = image_ext
            if image_ext in ["jpg", "jpeg"]:
                video_settings["vcodec"] = "mjpeg"
            else:
                video_settings["vcodec"] = image_ext

        # Set MaxSize (so we don't have any downsampling)
        self.timeline.SetMaxSize(video_settings.get("width"),
                                 video_settings.get("height"))

        # Set lossless cache settings (temporarily)
        export_cache_object = openshot.CacheMemory(250)
        self.timeline.SetCache(export_cache_object)

        # Rescale all keyframes and reload project
        if self.export_fps_factor != 1.0:
            self.keyframes_rescaled = True
            get_app().project.rescale_keyframes(self.export_fps_factor)

            # Load the "export" Timeline reader with the JSON from the real timeline
            json_timeline = json.dumps(get_app().project._data)
            self.timeline.SetJson(json_timeline)

            # Re-update the timeline FPS again (since the timeline just got clobbered)
            self.updateFrameRate()

        # Create FFmpegWriter
        try:
            w = openshot.FFmpegWriter(export_file_path)

            # Set video options
            if export_type in [
                    _("Video & Audio"),
                    _("Video Only"),
                    _("Image Sequence")
            ]:
                w.SetVideoOptions(
                    True, video_settings.get("vcodec"),
                    openshot.Fraction(
                        video_settings.get("fps").get("num"),
                        video_settings.get("fps").get("den")),
                    video_settings.get("width"), video_settings.get("height"),
                    openshot.Fraction(
                        video_settings.get("pixel_ratio").get("num"),
                        video_settings.get("pixel_ratio").get("den")), False,
                    False, video_settings.get("video_bitrate"))

            # Set audio options
            if export_type in [_("Video & Audio"), _("Audio Only")]:
                w.SetAudioOptions(True, audio_settings.get("acodec"),
                                  audio_settings.get("sample_rate"),
                                  audio_settings.get("channels"),
                                  audio_settings.get("channel_layout"),
                                  audio_settings.get("audio_bitrate"))

            # Prepare the streams
            w.PrepareStreams()

            # These extra options should be set in an extra method
            # No feedback is given to the user
            # TODO: Tell user if option is not avaliable
            # Set the quality in case crf was selected
            if "crf" in self.txtVideoBitRate.text():
                w.SetOption(openshot.VIDEO_STREAM, "crf",
                            str(int(video_settings.get("video_bitrate"))))

            # Open the writer
            w.Open()

            # Notify window of export started
            export_file_path = ""
            get_app().window.ExportStarted.emit(
                export_file_path, video_settings.get("start_frame"),
                video_settings.get("end_frame"))

            progressstep = max(
                1,
                round((video_settings.get("end_frame") -
                       video_settings.get("start_frame")) / 1000))
            start_time_export = time.time()
            start_frame_export = video_settings.get("start_frame")
            end_frame_export = video_settings.get("end_frame")
            # Write each frame in the selected range
            for frame in range(video_settings.get("start_frame"),
                               video_settings.get("end_frame")):
                # Update progress bar (emit signal to main window)
                if (frame % progressstep) == 0:
                    end_time_export = time.time()
                    if (((frame - start_frame_export) != 0) &
                        ((end_time_export - start_time_export) != 0)):
                        seconds_left = round(
                            (start_time_export - end_time_export) *
                            (frame - end_frame_export) /
                            (frame - start_frame_export))
                        fps_encode = ((frame - start_frame_export) /
                                      (end_time_export - start_time_export))
                        export_file_path = _(
                            "%(hours)d:%(minutes)02d:%(seconds)02d Remaining (%(fps)5.2f FPS)"
                        ) % {
                            'hours': seconds_left / 3600,
                            'minutes': (seconds_left / 60) % 60,
                            'seconds': seconds_left % 60,
                            'fps': fps_encode
                        }
                    get_app().window.ExportFrame.emit(
                        export_file_path, video_settings.get("start_frame"),
                        video_settings.get("end_frame"), frame)

                # Process events (to show the progress bar moving)
                QCoreApplication.processEvents()

                # Write the frame object to the video
                w.WriteFrame(self.timeline.GetFrame(frame))

                # Check if we need to bail out
                if not self.exporting:
                    break

            # Close writer
            w.Close()

        except Exception as e:
            # TODO: Find a better way to catch the error. This is the only way I have found that
            # does not throw an error
            error_type_str = str(e)
            log.info("Error type string: %s" % error_type_str)

            if "InvalidChannels" in error_type_str:
                log.info("Error setting invalid # of channels (%s)" %
                         (audio_settings.get("channels")))
                track_metric_error("invalid-channels-%s-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec"),
                                    audio_settings.get("channels")))

            elif "InvalidSampleRate" in error_type_str:
                log.info("Error setting invalid sample rate (%s)" %
                         (audio_settings.get("sample_rate")))
                track_metric_error("invalid-sample-rate-%s-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec"),
                                    audio_settings.get("sample_rate")))

            elif "InvalidFormat" in error_type_str:
                log.info("Error setting invalid format (%s)" %
                         (video_settings.get("vformat")))
                track_metric_error("invalid-format-%s" %
                                   (video_settings.get("vformat")))

            elif "InvalidCodec" in error_type_str:
                log.info("Error setting invalid codec (%s/%s/%s)" %
                         (video_settings.get("vformat"),
                          video_settings.get("vcodec"),
                          audio_settings.get("acodec")))
                track_metric_error("invalid-codec-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec")))

            elif "ErrorEncodingVideo" in error_type_str:
                log.info("Error encoding video frame (%s/%s/%s)" %
                         (video_settings.get("vformat"),
                          video_settings.get("vcodec"),
                          audio_settings.get("acodec")))
                track_metric_error("video-encode-%s-%s-%s" %
                                   (video_settings.get("vformat"),
                                    video_settings.get("vcodec"),
                                    audio_settings.get("acodec")))

            # Show friendly error
            friendly_error = error_type_str.split("> ")[0].replace("<", "")

            # Prompt error message
            msg = QMessageBox()
            _ = get_app()._tr
            msg.setWindowTitle(_("Export Error"))
            msg.setText(
                _("Sorry, there was an error exporting your video: \n%s") %
                friendly_error)
            msg.exec_()

        # Notify window of export started
        get_app().window.ExportEnded.emit(export_file_path)

        # Close timeline object
        self.timeline.Close()

        # Clear all cache
        self.timeline.ClearAllCache()

        # Re-set OMP thread enabled flag
        if self.s.get("omp_threads_enabled"):
            openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK = False
        else:
            openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK = True

        # Return scale mode to lower quality scaling (for faster previews)
        openshot.Settings.Instance().HIGH_QUALITY_SCALING = False

        # Return keyframes to preview scaling
        if self.keyframes_rescaled:
            get_app().project.rescale_keyframes(self.original_fps_factor)

        # Accept dialog
        super(Export, self).accept()