Esempio n. 1
0
 def get_trim_preview_pipeline(self, uri):
     try:
         trim_pipeline, sink_widget = self.trim_pipelines_cache[uri]
         self.debug("Reusing temporary pipeline for clip %s", uri)
     except KeyError:
         self.debug("Creating temporary pipeline for clip %s", uri)
         trim_pipeline = AssetPipeline(uri)
         unused_video_sink, sink_widget = trim_pipeline.create_sink()
     self.trim_pipelines_cache[uri] = trim_pipeline, sink_widget
     if len(self.trim_pipelines_cache) > 4:
         # Pop the first inserted item.
         expired_uri, (expired_pipeline, unused_expired_widget) = self.trim_pipelines_cache.popitem(last=False)
         self.debug("Releasing temporary pipeline for clip %s", expired_uri)
         expired_pipeline.release()
     return trim_pipeline, sink_widget
Esempio n. 2
0
    def clipTrimPreview(self, tl_obj, position):
        """
        While a clip is being trimmed, show a live preview of it.
        """
        if isinstance(tl_obj,
                      GES.TitleClip) or tl_obj.props.is_image or not hasattr(
                          tl_obj, "get_uri"):
            self.log("%s is an image or has no URI, so not previewing trim" %
                     tl_obj)
            return False

        clip_uri = tl_obj.props.uri
        cur_time = time()
        if self.pipeline == self.app.project_manager.current_project.pipeline:
            self.debug("Creating temporary pipeline for clip %s, position %s",
                       clip_uri, format_ns(position))
            self._oldTimelinePos = self.pipeline.getPosition()
            self.setPipeline(AssetPipeline(tl_obj))
            self._lastClipTrimTime = cur_time

        if (cur_time - self._lastClipTrimTime
            ) > 0.2 and self.pipeline.getState() == Gst.State.PAUSED:
            # Do not seek more than once every 200 ms (for performance)
            self.pipeline.simple_seek(position)
            self._lastClipTrimTime = cur_time
Esempio n. 3
0
 def get_trim_preview_pipeline(self, uri):
     try:
         trim_pipeline, sink_widget = self.trim_pipelines_cache[uri]
         self.debug("Reusing temporary pipeline for clip %s", uri)
     except KeyError:
         self.debug("Creating temporary pipeline for clip %s", uri)
         trim_pipeline = AssetPipeline(uri)
         unused_video_sink, sink_widget = trim_pipeline.create_sink()
     self.trim_pipelines_cache[uri] = trim_pipeline, sink_widget
     if len(self.trim_pipelines_cache) > 4:
         # Pop the first inserted item.
         expired_uri, (
             expired_pipeline,
             unused_expired_widget) = self.trim_pipelines_cache.popitem(
                 last=False)
         self.debug("Releasing temporary pipeline for clip %s", expired_uri)
         expired_pipeline.release()
     return trim_pipeline, sink_widget
Esempio n. 4
0
    def clipTrimPreview(self, clip, position):
        """Shows a live preview of a clip being trimmed."""
        if not hasattr(clip, "get_uri") or isinstance(clip, GES.TitleClip) or clip.props.is_image:
            self.log(
                "%s is an image or has no URI, so not previewing trim" % clip)
            return False

        clip_uri = clip.props.uri
        cur_time = time()
        if self.pipeline == self.app.project_manager.current_project.pipeline:
            self.debug("Creating temporary pipeline for clip %s, position %s",
                       clip_uri, format_ns(position))
            self._oldTimelinePos = self.pipeline.getPosition(False)
            self.pipeline.set_state(Gst.State.NULL)
            self.setPipeline(AssetPipeline(clip))
            self.__owning_pipeline = True
            self._lastClipTrimTime = cur_time

        if (cur_time - self._lastClipTrimTime) > 0.2 and self.pipeline.getState() == Gst.State.PAUSED:
            # Do not seek more than once every 200 ms (for performance)
            self.pipeline.simple_seek(position)
            self._lastClipTrimTime = cur_time
Esempio n. 5
0
    def __init__(self, settings, minimal=False, discover_sync=False):
        Gtk.Grid.__init__(self)
        Loggable.__init__(self)

        self.log("Init PreviewWidget")
        self.settings = settings
        self.error_message = None

        # playbin for play pics
        self.player = AssetPipeline(name="preview-player")
        self.player.connect('eos', self._pipeline_eos_cb)
        self.player.connect('error', self._pipeline_error_cb)
        self.player._bus.connect('message::tag', self._tag_found_cb)

        # some global variables for preview handling
        self.is_playing = False
        self.at_eos = False
        self.original_dims = (PREVIEW_WIDTH, PREVIEW_HEIGHT)
        self.countinuous_seek = False
        self.slider_being_used = False
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.play_on_discover = False
        self.description = ""

        # Gui elements:
        # Drawing area for video output
        unused_sink, sink_widget = self.player.create_sink()
        self.preview_video = ViewerWidget(sink_widget)
        self.preview_video.props.hexpand = minimal
        self.preview_video.props.vexpand = minimal
        self.preview_video.show_all()
        self.attach(self.preview_video, 0, 0, 1, 1)

        # An image for images and audio
        self.preview_image = Gtk.Image()
        self.preview_image.set_size_request(self.settings.FCpreviewWidth,
                                            self.settings.FCpreviewHeight)
        self.preview_image.show()
        self.attach(self.preview_image, 0, 1, 1, 1)

        # Play button
        self.bbox = Gtk.Box()
        self.bbox.set_orientation(Gtk.Orientation.HORIZONTAL)
        self.play_button = Gtk.ToolButton()
        self.play_button.set_icon_name("media-playback-start")
        self.play_button.connect("clicked", self._on_start_stop_clicked_cb)
        self.bbox.pack_start(self.play_button, False, False, 0)

        # Scale for position handling
        self.pos_adj = Gtk.Adjustment()
        self.seeker = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.pos_adj)
        self.seeker.connect('button-press-event', self._on_seeker_press_cb)
        self.seeker.connect('button-release-event', self._on_seeker_press_cb)
        self.seeker.connect('motion-notify-event', self._on_motion_notify_cb)
        self.seeker.set_draw_value(False)
        self.seeker.show()
        self.bbox.pack_start(self.seeker, True, True, 0)

        # Zoom buttons
        self.b_zoom_in = Gtk.ToolButton()
        self.b_zoom_in.set_icon_name("zoom-in")
        self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1)
        self.b_zoom_out = Gtk.ToolButton()
        self.b_zoom_out.set_icon_name("zoom-out")
        self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1)
        self.bbox.pack_start(self.b_zoom_in, False, False, 0)
        self.bbox.pack_start(self.b_zoom_out, False, False, 0)
        self.bbox.show_all()
        self.attach(self.bbox, 0, 2, 1, 1)

        # Label for metadata tags
        self.l_tags = Gtk.Label()
        self.l_tags.set_justify(Gtk.Justification.LEFT)
        self.l_tags.set_ellipsize(Pango.EllipsizeMode.END)
        self.l_tags.show()
        self.attach(self.l_tags, 0, 3, 1, 1)

        # Error handling
        vbox = Gtk.Box()
        vbox.set_orientation(Gtk.Orientation.VERTICAL)
        vbox.set_spacing(SPACING)
        self.l_error = Gtk.Label(label=_("Pitivi can not preview this file."))
        self.b_details = Gtk.Button.new_with_label(_("More info"))
        self.b_details.connect('clicked', self._on_b_details_clicked_cb)
        vbox.pack_start(self.l_error, True, True, 0)
        vbox.pack_start(self.b_details, False, False, 0)
        vbox.show()
        self.attach(vbox, 0, 4, 1, 1)

        if minimal:
            self.remove(self.l_tags)
            self.bbox.remove(self.b_zoom_in)
            self.bbox.remove(self.b_zoom_out)

        self.clear_preview()
        self._discover_sync = discover_sync
Esempio n. 6
0
class PreviewWidget(Gtk.Grid, Loggable):
    """Widget for displaying a GStreamer sink with playback controls.

    Args:
        settings (GlobalSettings): The settings of the app.
    """
    def __init__(self, settings, minimal=False, discover_sync=False):
        Gtk.Grid.__init__(self)
        Loggable.__init__(self)

        self.log("Init PreviewWidget")
        self.settings = settings
        self.error_message = None

        # playbin for play pics
        self.player = AssetPipeline(name="preview-player")
        self.player.connect('eos', self._pipeline_eos_cb)
        self.player.connect('error', self._pipeline_error_cb)
        self.player._bus.connect('message::tag', self._tag_found_cb)

        # some global variables for preview handling
        self.is_playing = False
        self.at_eos = False
        self.original_dims = (PREVIEW_WIDTH, PREVIEW_HEIGHT)
        self.countinuous_seek = False
        self.slider_being_used = False
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.play_on_discover = False
        self.description = ""

        # Gui elements:
        # Drawing area for video output
        unused_sink, sink_widget = self.player.create_sink()
        self.preview_video = ViewerWidget(sink_widget)
        self.preview_video.props.hexpand = minimal
        self.preview_video.props.vexpand = minimal
        self.preview_video.show_all()
        self.attach(self.preview_video, 0, 0, 1, 1)

        # An image for images and audio
        self.preview_image = Gtk.Image()
        self.preview_image.set_size_request(self.settings.FCpreviewWidth,
                                            self.settings.FCpreviewHeight)
        self.preview_image.show()
        self.attach(self.preview_image, 0, 1, 1, 1)

        # Play button
        self.bbox = Gtk.Box()
        self.bbox.set_orientation(Gtk.Orientation.HORIZONTAL)
        self.play_button = Gtk.ToolButton()
        self.play_button.set_icon_name("media-playback-start")
        self.play_button.connect("clicked", self._on_start_stop_clicked_cb)
        self.bbox.pack_start(self.play_button, False, False, 0)

        # Scale for position handling
        self.pos_adj = Gtk.Adjustment()
        self.seeker = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.pos_adj)
        self.seeker.connect('button-press-event', self._on_seeker_press_cb)
        self.seeker.connect('button-release-event', self._on_seeker_press_cb)
        self.seeker.connect('motion-notify-event', self._on_motion_notify_cb)
        self.seeker.set_draw_value(False)
        self.seeker.show()
        self.bbox.pack_start(self.seeker, True, True, 0)

        # Zoom buttons
        self.b_zoom_in = Gtk.ToolButton()
        self.b_zoom_in.set_icon_name("zoom-in")
        self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1)
        self.b_zoom_out = Gtk.ToolButton()
        self.b_zoom_out.set_icon_name("zoom-out")
        self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1)
        self.bbox.pack_start(self.b_zoom_in, False, False, 0)
        self.bbox.pack_start(self.b_zoom_out, False, False, 0)
        self.bbox.show_all()
        self.attach(self.bbox, 0, 2, 1, 1)

        # Label for metadata tags
        self.l_tags = Gtk.Label()
        self.l_tags.set_justify(Gtk.Justification.LEFT)
        self.l_tags.set_ellipsize(Pango.EllipsizeMode.END)
        self.l_tags.show()
        self.attach(self.l_tags, 0, 3, 1, 1)

        # Error handling
        vbox = Gtk.Box()
        vbox.set_orientation(Gtk.Orientation.VERTICAL)
        vbox.set_spacing(SPACING)
        self.l_error = Gtk.Label(label=_("Pitivi can not preview this file."))
        self.b_details = Gtk.Button.new_with_label(_("More info"))
        self.b_details.connect('clicked', self._on_b_details_clicked_cb)
        vbox.pack_start(self.l_error, True, True, 0)
        vbox.pack_start(self.b_details, False, False, 0)
        vbox.show()
        self.attach(vbox, 0, 4, 1, 1)

        if minimal:
            self.remove(self.l_tags)
            self.bbox.remove(self.b_zoom_in)
            self.bbox.remove(self.b_zoom_out)

        self.clear_preview()
        self._discover_sync = discover_sync

    def update_preview_cb(self, file_chooser):
        """Previews the URI of the specified file chooser.

        Args:
            file_chooser (Gtk.FileChooser): The file chooser providing the URI.
        """
        uri = file_chooser.get_preview_uri()
        previewable = uri and uri_is_valid(uri)
        if not previewable:
            self.clear_preview()
            return
        self.preview_uri(uri)

    def preview_uri(self, uri):
        self.log("Preview request for %s", uri)
        self.clear_preview()
        self.current_selected_uri = uri

        if self._discover_sync:
            self._handle_new_asset(uri=uri)
        else:
            GES.UriClipAsset.new(uri, None, self.__asset_loaded_cb)

    def _handle_new_asset(self, async_result=None, uri=None):
        try:
            if uri:
                asset = GES.UriClipAsset.request_sync(uri)
            else:
                asset = GES.Asset.request_finish(async_result)
                uri = asset.get_id()
        except GLib.Error as error:
            self.log("Failed discovering %s: %s", uri, error.message)
            self._show_error(error.message)
            return

        self.log("Discovered %s", uri)
        if not self._show_preview(uri, asset.get_info()):
            return
        if self.play_on_discover:
            self.play_on_discover = False
            self.play()

    def __asset_loaded_cb(self, source, res):
        self._handle_new_asset(async_result=res)

    def _show_preview(self, uri, info):
        self.log("Show preview for %s", uri)
        duration = info.get_duration()
        pretty_duration = beautify_length(duration)

        videos = info.get_video_streams()
        if videos:
            video = videos[0]
            if video.is_image():
                self.current_preview_type = 'image'
                self.preview_video.hide()
                path = Gst.uri_get_location(uri)
                try:
                    pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
                except GLib.Error as error:
                    self.debug("Failed loading image because: %s", error)
                    self._show_error(error.message)
                    return False
                pixbuf_w = pixbuf.get_width()
                pixbuf_h = pixbuf.get_height()
                w, h = self.__get_best_size(pixbuf_w, pixbuf_h)
                pixbuf = pixbuf.scale_simple(w, h,
                                             GdkPixbuf.InterpType.NEAREST)
                self.preview_image.set_from_pixbuf(pixbuf)
                self.preview_image.set_size_request(
                    self.settings.FCpreviewWidth,
                    self.settings.FCpreviewHeight)
                self.preview_image.show()
                self.bbox.show()
                self.play_button.hide()
                self.seeker.hide()
                self.b_zoom_in.show()
                self.b_zoom_out.show()
            else:
                self.current_preview_type = 'video'
                self.preview_image.hide()
                self.player.uri = self.current_selected_uri
                self.player.set_simple_state(Gst.State.PAUSED)
                self.pos_adj.props.upper = duration
                video_width = video.get_natural_width()
                video_height = video.get_natural_height()
                w, h = self.__get_best_size(video_width, video_height)
                self.preview_video.set_size_request(w, h)
                self.preview_video.props.ratio = video_width / video_height
                self.preview_video.show()
                self.bbox.show()
                self.play_button.show()
                self.seeker.show()
                self.b_zoom_in.show()
                self.b_zoom_out.show()
                self.description = "\n".join([
                    _("<b>Resolution</b>: %d×%d") %
                    (video_width, video_height),
                    _("<b>Duration</b>: %s") % pretty_duration
                ])
        else:
            self.current_preview_type = 'audio'
            self.preview_video.hide()
            audio = info.get_audio_streams()
            if not audio:
                self.debug("Audio has no streams")
                return False

            audio = audio[0]
            self.pos_adj.props.upper = duration
            self.preview_image.set_from_icon_name("audio-x-generic",
                                                  Gtk.IconSize.DIALOG)
            self.preview_image.show()
            self.preview_image.set_size_request(PREVIEW_WIDTH, PREVIEW_HEIGHT)
            self.description = "\n".join([
                beautify_stream(audio),
                _("<b>Duration</b>: %s") % pretty_duration
            ])
            self.player.set_simple_state(Gst.State.NULL)
            self.player.uri = self.current_selected_uri
            self.player.set_simple_state(Gst.State.PAUSED)
            self.play_button.show()
            self.seeker.show()
            self.b_zoom_in.hide()
            self.b_zoom_out.hide()
            self.bbox.show()
        return True

    def _show_error(self, error_message):
        self.error_message = error_message
        self.l_error.show()
        self.b_details.show()

    def play(self):
        if not self.current_preview_type:
            self.play_on_discover = True
            return
        if self.at_eos:
            # The content played once already and the pipeline is at the end.
            self.at_eos = False
            self.player.simple_seek(0)
        self.player.set_simple_state(Gst.State.PLAYING)
        self.is_playing = True
        self.play_button.set_icon_name("media-playback-pause")
        GLib.timeout_add(250, self._update_position)
        self.debug("Preview started")

    def pause(self, state=Gst.State.PAUSED):
        if state is not None:
            self.player.set_simple_state(state)
        self.is_playing = False
        self.play_button.set_icon_name("media-playback-start")
        self.log("Preview paused")

    def toggle_playback(self):
        if self.is_playing:
            self.pause()
        else:
            self.play()

    def clear_preview(self):
        self.log("Reset PreviewWidget")
        self.seeker.set_value(0)
        self.bbox.hide()
        self.l_error.hide()
        self.b_details.hide()
        self.description = ""
        self.l_tags.set_markup("")
        self.pause(state=Gst.State.NULL)
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.preview_image.hide()
        self.preview_video.hide()

    def _on_seeker_press_cb(self, widget, event):
        self.slider_being_used = True
        if event.type == Gdk.EventType.BUTTON_PRESS:
            self.countinuous_seek = True
            if self.is_playing:
                self.player.set_simple_state(Gst.State.PAUSED)
        elif event.type == Gdk.EventType.BUTTON_RELEASE:
            self.countinuous_seek = False
            value = int(widget.get_value())
            self.player.simple_seek(value)
            self.at_eos = False
            if self.is_playing:
                self.player.set_simple_state(Gst.State.PLAYING)
            # Now, allow gobject timeout to continue updating the slider pos:
            self.slider_being_used = False

    def _on_motion_notify_cb(self, widget, event):
        if self.countinuous_seek:
            value = int(widget.get_value())
            self.player.simple_seek(value)
            self.at_eos = False

    def _pipeline_eos_cb(self, unused_pipeline):
        self._update_position()
        self.pause()
        # The pipeline is at the end. Leave it like that so the last frame
        # is displayed.
        self.at_eos = True

    def _pipeline_error_cb(self, unused_pipeline, unused_message,
                           unused_detail):
        self.pause(state=Gst.State.NULL)

    def _update_position(self, *unused_args):
        if self.is_playing and not self.slider_being_used:
            curr_pos = self.player.get_position()
            self.pos_adj.set_value(int(curr_pos))
        return self.is_playing

    def _on_start_stop_clicked_cb(self, button):
        self.toggle_playback()

    def _on_zoom_clicked_cb(self, button, increment):
        if self.current_preview_type == 'video':
            w, h = self.preview_video.get_size_request()
            if increment > 0:
                w *= 1.2
                h *= 1.2
            else:
                w *= 0.8
                h *= 0.8
                if (w, h) < self.original_dims:
                    (w, h) = self.original_dims
            self.preview_video.set_size_request(int(w), int(h))
            self.settings.FCpreviewWidth = int(w)
            self.settings.FCpreviewHeight = int(h)
        elif self.current_preview_type == 'image':
            pixbuf = self.preview_image.get_pixbuf()
            w = pixbuf.get_width()
            h = pixbuf.get_height()
            if increment > 0:
                w *= 1.2
                h *= 1.2
            else:
                w *= 0.8
                h *= 0.8
                if (w, h) < self.original_dims:
                    (w, h) = self.original_dims
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                Gst.uri_get_location(self.current_selected_uri))
            pixbuf = pixbuf.scale_simple(int(w), int(h),
                                         GdkPixbuf.InterpType.BILINEAR)

            self.preview_image.set_size_request(int(w), int(h))
            self.preview_image.set_from_pixbuf(pixbuf)
            self.preview_image.show()
            self.settings.FCpreviewWidth = int(w)
            self.settings.FCpreviewHeight = int(h)

    def _append_tag(self, taglist, tag, tags):
        if tag in ACCEPTABLE_TAGS:
            tag_name = Gst.tag_get_nick(tag)
            tag_type = Gst.tag_get_type(tag)
            type_getters = {
                GObject.TYPE_STRING: 'get_string',
                GObject.TYPE_DOUBLE: 'get_double',
                GObject.TYPE_FLOAT: 'get_float',
                GObject.TYPE_INT: 'get_int',
                GObject.TYPE_UINT: 'get_uint'
            }
            if tag_type in type_getters:
                res, value = getattr(taglist, type_getters[tag_type])(tag)
                assert res
                if not tag_type == GObject.TYPE_STRING:
                    value = str(value)
                tags[tag_name] = value

    def _tag_found_cb(self, unused_bus, message):
        tag_list = message.parse_tag()
        tags = {}
        tag_list.foreach(self._append_tag, tags)
        items = list(tags.items())
        items.sort()
        text = self.description + "\n\n"
        for key, value in items:
            escaped = html.escape(value)
            text = text + "<b>%s</b>: %s\n" % (key, escaped)
        self.l_tags.set_markup(text)

    def _on_b_details_clicked_cb(self, unused_button):
        if not self.error_message:
            return

        dialog = Gtk.MessageDialog(transient_for=None,
                                   modal=True,
                                   message_type=Gtk.MessageType.WARNING,
                                   buttons=Gtk.ButtonsType.OK,
                                   text=self.error_message)
        dialog.set_icon_name("pitivi")
        dialog.set_title(_("Error while analyzing a file"))
        dialog.run()
        dialog.destroy()

    def do_destroy(self):
        """Handles the destruction of the widget."""
        self.player.release()
        self.is_playing = False

    def __get_best_size(self, width_in, height_in):
        if width_in > height_in:
            if self.settings.FCpreviewWidth < width_in:
                w = self.settings.FCpreviewWidth
                h = height_in * w / width_in
                return (w, h)
        else:
            if self.settings.FCpreviewHeight < height_in:
                h = self.settings.FCpreviewHeight
                w = width_in * h / height_in
                return (w, h)
        return (width_in, height_in)
    def __init__(self, settings, minimal=False):
        Gtk.Grid.__init__(self)
        Loggable.__init__(self)

        self.log("Init PreviewWidget")
        self.connect('destroy', self._destroy_cb)

        self.settings = settings
        self.preview_cache = {}
        self.preview_cache_errors = {}

        self.discoverer = GstPbutils.Discoverer.new(Gst.SECOND)

        # playbin for play pics
        self.player = AssetPipeline(clip=None, name="preview-player")
        self.player.connect('eos', self._pipelineEosCb)
        self.player.connect('error', self._pipelineErrorCb)
        self.player._bus.connect('message::tag', self._tag_found_cb)

        # some global variables for preview handling
        self.is_playing = False
        self.original_dims = (PREVIEW_WIDTH, PREVIEW_HEIGHT)
        self.countinuous_seek = False
        self.slider_being_used = False
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.description = ""
        self.tags = {}

        # Gui elements:
        # Drawing area for video output
        self.preview_video = ViewerWidget(
            realizedCb=self._on_preview_video_realize_cb)
        self.preview_video.sink = self.player.video_sink
        self.preview_video.props.hexpand = minimal
        self.preview_video.props.vexpand = minimal
        self.attach(self.preview_video, 0, 0, 1, 1)

        # An image for images and audio
        self.preview_image = Gtk.Image()
        self.preview_image.set_size_request(
            self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
        self.preview_image.show()
        self.attach(self.preview_image, 0, 1, 1, 1)

        # Play button
        self.bbox = Gtk.HBox()
        self.play_button = Gtk.ToolButton()
        self.play_button.set_icon_name("media-playback-start")
        self.play_button.connect("clicked", self._on_start_stop_clicked_cb)
        self.bbox.pack_start(self.play_button, False, False, 0)

        # Scale for position handling
        self.pos_adj = Gtk.Adjustment()
        self.seeker = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.pos_adj)
        self.seeker.connect('button-press-event', self._on_seeker_press_cb)
        self.seeker.connect('button-release-event', self._on_seeker_press_cb)
        self.seeker.connect('motion-notify-event', self._on_motion_notify_cb)
        self.seeker.set_draw_value(False)
        self.seeker.show()
        self.bbox.pack_start(self.seeker, True, True, 0)

        # Zoom buttons
        self.b_zoom_in = Gtk.ToolButton()
        self.b_zoom_in.set_icon_name("zoom-in")
        self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1)
        self.b_zoom_out = Gtk.ToolButton()
        self.b_zoom_out.set_icon_name("zoom-out")
        self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1)
        self.bbox.pack_start(self.b_zoom_in, False, True, 0)
        self.bbox.pack_start(self.b_zoom_out, False, True, 0)
        self.bbox.show_all()
        self.attach(self.bbox, 0, 2, 1, 1)

        # Label for metadata tags
        self.l_tags = Gtk.Label()
        self.l_tags.set_justify(Gtk.Justification.LEFT)
        self.l_tags.set_ellipsize(Pango.EllipsizeMode.END)
        self.l_tags.show()
        self.attach(self.l_tags, 0, 3, 1, 1)

        # Error handling
        vbox = Gtk.VBox()
        vbox.set_spacing(SPACING)
        self.l_error = Gtk.Label(label=_("Pitivi can not preview this file."))
        self.b_details = Gtk.Button.new_with_label(_("More info"))
        self.b_details.connect('clicked', self._on_b_details_clicked_cb)
        vbox.pack_start(self.l_error, True, True, 0)
        vbox.pack_start(self.b_details, False, False, 0)
        vbox.show()
        self.attach(vbox, 0, 4, 1, 1)

        if minimal:
            self.remove(self.l_tags)
            self.bbox.remove(self.b_zoom_in)
            self.bbox.remove(self.b_zoom_out)
Esempio n. 8
0
class PreviewWidget(Gtk.Grid, Loggable):

    """
    Widget for displaying a GStreamer sink with playback controls.

    @ivar settings: The settings of the app.
    @type settings: L{GlobalSettings}
    """

    def __init__(self, settings, minimal=False):
        Gtk.Grid.__init__(self)
        Loggable.__init__(self)

        self.log("Init PreviewWidget")
        self.connect('destroy', self._destroy_cb)

        self.settings = settings
        self.preview_cache = {}
        self.preview_cache_errors = {}

        self.discoverer = GstPbutils.Discoverer.new(Gst.SECOND)

        # playbin for play pics
        self.player = AssetPipeline(clip=None, name="preview-player")
        self.player.connect('eos', self._pipelineEosCb)
        self.player.connect('error', self._pipelineErrorCb)
        self.player._bus.connect('message::tag', self._tag_found_cb)

        # some global variables for preview handling
        self.is_playing = False
        self.original_dims = (PREVIEW_WIDTH, PREVIEW_HEIGHT)
        self.countinuous_seek = False
        self.slider_being_used = False
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.description = ""
        self.tags = {}

        # Gui elements:
        # Drawing area for video output
        self.preview_video = ViewerWidget(
            realizedCb=self._on_preview_video_realize_cb)
        self.preview_video.sink = self.player.video_sink
        self.preview_video.props.hexpand = minimal
        self.preview_video.props.vexpand = minimal
        self.attach(self.preview_video, 0, 0, 1, 1)

        # An image for images and audio
        self.preview_image = Gtk.Image()
        self.preview_image.set_size_request(
            self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
        self.preview_image.show()
        self.attach(self.preview_image, 0, 1, 1, 1)

        # Play button
        self.bbox = Gtk.HBox()
        self.play_button = Gtk.ToolButton()
        self.play_button.set_icon_name("media-playback-start")
        self.play_button.connect("clicked", self._on_start_stop_clicked_cb)
        self.bbox.pack_start(self.play_button, False, False, 0)

        # Scale for position handling
        self.pos_adj = Gtk.Adjustment()
        self.seeker = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.pos_adj)
        self.seeker.connect('button-press-event', self._on_seeker_press_cb)
        self.seeker.connect('button-release-event', self._on_seeker_press_cb)
        self.seeker.connect('motion-notify-event', self._on_motion_notify_cb)
        self.seeker.set_draw_value(False)
        self.seeker.show()
        self.bbox.pack_start(self.seeker, True, True, 0)

        # Zoom buttons
        self.b_zoom_in = Gtk.ToolButton()
        self.b_zoom_in.set_icon_name("zoom-in")
        self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1)
        self.b_zoom_out = Gtk.ToolButton()
        self.b_zoom_out.set_icon_name("zoom-out")
        self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1)
        self.bbox.pack_start(self.b_zoom_in, False, True, 0)
        self.bbox.pack_start(self.b_zoom_out, False, True, 0)
        self.bbox.show_all()
        self.attach(self.bbox, 0, 2, 1, 1)

        # Label for metadata tags
        self.l_tags = Gtk.Label()
        self.l_tags.set_justify(Gtk.Justification.LEFT)
        self.l_tags.set_ellipsize(Pango.EllipsizeMode.END)
        self.l_tags.show()
        self.attach(self.l_tags, 0, 3, 1, 1)

        # Error handling
        vbox = Gtk.VBox()
        vbox.set_spacing(SPACING)
        self.l_error = Gtk.Label(label=_("Pitivi can not preview this file."))
        self.b_details = Gtk.Button.new_with_label(_("More info"))
        self.b_details.connect('clicked', self._on_b_details_clicked_cb)
        vbox.pack_start(self.l_error, True, True, 0)
        vbox.pack_start(self.b_details, False, False, 0)
        vbox.show()
        self.attach(vbox, 0, 4, 1, 1)

        if minimal:
            self.remove(self.l_tags)
            self.bbox.remove(self.b_zoom_in)
            self.bbox.remove(self.b_zoom_out)

    def add_preview_request(self, dialogbox):
        """add a preview request """
        uri = dialogbox.get_preview_uri()
        if uri is None or not uri_is_valid(uri):
            return
        self.previewUri(uri)

    def previewUri(self, uri):
        self.log("Preview request for %s", uri)
        self.clear_preview()
        self.current_selected_uri = uri
        if uri in self.preview_cache:  # Already discovered
            self.log(uri + " already in cache")
            self.show_preview(uri, None)
        elif uri in self.preview_cache_errors:
            self.log(uri + " already in error cache")
            self.show_error(uri)
        else:
            self.log("Call discoverer for " + uri)
            self.fixme("Use a GESAsset here, and discover async with it")
            try:
                info = self.discoverer.discover_uri(uri)
            except Exception as e:
                self.preview_cache_errors[uri] = e
                if self.current_selected_uri == uri:
                    self.show_error(uri)
                return

            if self.current_selected_uri == uri:
                self.show_preview(uri, info)

    def show_preview(self, uri, info):
        if info:
            self.preview_cache[uri] = info
        else:
            self.log("Show preview for " + uri)
            info = self.preview_cache.get(uri, None)

        if info is None:
            self.log("No preview for " + uri)
            return

        duration = info.get_duration()
        pretty_duration = beautify_length(duration)

        videos = info.get_video_streams()
        if videos:
            video = videos[0]
            if video.is_image():
                self.current_preview_type = 'image'
                self.preview_video.hide()
                pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                    Gst.uri_get_location(uri))
                pixbuf_w = pixbuf.get_width()
                pixbuf_h = pixbuf.get_height()
                w, h = self.__get_best_size(pixbuf_w, pixbuf_h)
                pixbuf = pixbuf.scale_simple(
                    w, h, GdkPixbuf.InterpType.NEAREST)
                self.preview_image.set_from_pixbuf(pixbuf)
                self.preview_image.set_size_request(
                    self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
                self.preview_image.show()
                self.bbox.show()
                self.play_button.hide()
                self.seeker.hide()
                self.b_zoom_in.show()
                self.b_zoom_out.show()
            else:
                self.current_preview_type = 'video'
                self.preview_image.hide()
                self.player.setClipUri(self.current_selected_uri)
                self.player.setState(Gst.State.PAUSED)
                self.pos_adj.props.upper = duration
                video_width = (
                    video.get_par_num() / video.get_par_denom()) * video.get_width()
                video_height = video.get_height()
                w, h = self.__get_best_size(video_width, video_height)
                self.preview_video.set_size_request(w, h)
                self.preview_video.setDisplayAspectRatio(
                    float(video_width) / video_height)
                self.preview_video.show()
                self.bbox.show()
                self.play_button.show()
                self.seeker.show()
                self.b_zoom_in.show()
                self.b_zoom_out.show()
                self.description = "\n".join([
                    _("<b>Resolution</b>: %d×%d") % (
                        video_width, video_height),
                    _("<b>Duration</b>: %s") % pretty_duration])
        else:
            self.current_preview_type = 'audio'
            self.preview_video.hide()
            audio = info.get_audio_streams()

            if not audio:
                return

            audio = audio[0]
            self.pos_adj.props.upper = duration
            self.preview_image.set_from_icon_name(
                "audio-x-generic", Gtk.IconSize.DIALOG)
            self.preview_image.show()
            self.preview_image.set_size_request(PREVIEW_WIDTH, PREVIEW_HEIGHT)
            self.description = "\n".join([
                beautify_stream(audio),
                _("<b>Duration</b>: %s") % pretty_duration])
            self.player.setState(Gst.State.NULL)
            self.player.setClipUri(self.current_selected_uri)
            self.player.setState(Gst.State.PAUSED)
            self.play_button.show()
            self.seeker.show()
            self.b_zoom_in.hide()
            self.b_zoom_out.hide()
            self.bbox.show()

    def show_error(self, unused_uri):
        self.l_error.show()
        self.b_details.show()

    def play(self):
        self.player.setState(Gst.State.PLAYING)
        self.is_playing = True
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PAUSE)
        GLib.timeout_add(250, self._update_position)
        self.debug("Preview started")

    def pause(self):
        self.player.setState(Gst.State.PAUSED)
        self.is_playing = False
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
        self.log("Preview paused")

    def togglePlayback(self):
        if self.is_playing:
            self.pause()
        else:
            self.play()

    def clear_preview(self):
        self.log("Reset PreviewWidget")
        self.seeker.set_value(0)
        self.bbox.hide()
        self.l_error.hide()
        self.b_details.hide()
        self.description = ""
        self.l_tags.set_markup("")
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
        self.player.setState(Gst.State.NULL)
        self.is_playing = False
        self.tags = {}
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.preview_image.hide()
        self.preview_video.hide()

    def _on_seeker_press_cb(self, widget, event):
        self.slider_being_used = True
        if event.type == Gdk.EventType.BUTTON_PRESS:
            self.countinuous_seek = True
            if self.is_playing:
                self.player.setState(Gst.State.PAUSED)
        elif event.type == Gdk.EventType.BUTTON_RELEASE:
            self.countinuous_seek = False
            value = int(widget.get_value())
            self.player.simple_seek(value)
            if self.is_playing:
                self.player.setState(Gst.State.PLAYING)
            # Now, allow gobject timeout to continue updating the slider pos:
            self.slider_being_used = False

    def _on_motion_notify_cb(self, widget, event):
        if self.countinuous_seek:
            value = int(widget.get_value())
            self.player.simple_seek(value)

    def _pipelineEosCb(self, unused_pipeline):
        self.player.setState(Gst.State.NULL)
        self.is_playing = False
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
        self.pos_adj.set_value(0)

    def _pipelineErrorCb(self, unused_message, unused_detail):
        self.player.setState(Gst.State.NULL)
        self.is_playing = False

    def _update_position(self, *unused_args):
        if self.is_playing and not self.slider_being_used:
            curr_pos = self.player.getPosition()
            self.pos_adj.set_value(int(curr_pos))
        return self.is_playing

    def _on_preview_video_realize_cb(self, unused_drawing_area, unused_widget):
        if self.current_preview_type == 'video':
            self.player.connectWithViewer(self.preview_video)

    def _on_start_stop_clicked_cb(self, button):
        if self.is_playing:
            self.pause()
        else:
            self.play()

    def _on_zoom_clicked_cb(self, button, increment):
        if self.current_preview_type == 'video':
            w, h = self.preview_video.get_size_request()
            if increment > 0:
                w *= 1.2
                h *= 1.2
            else:
                w *= 0.8
                h *= 0.8
                if (w, h) < self.original_dims:
                    (w, h) = self.original_dims
            self.preview_video.set_size_request(int(w), int(h))
            self.settings.FCpreviewWidth = int(w)
            self.settings.FCpreviewHeight = int(h)
        elif self.current_preview_type == 'image':
            pixbuf = self.preview_image.get_pixbuf()
            w = pixbuf.get_width()
            h = pixbuf.get_height()
            if increment > 0:
                w *= 1.2
                h *= 1.2
            else:
                w *= 0.8
                h *= 0.8
                if (w, h) < self.original_dims:
                    (w, h) = self.original_dims
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                Gst.uri_get_location(self.current_selected_uri))
            pixbuf = pixbuf.scale_simple(
                int(w), int(h), GdkPixbuf.InterpType.BILINEAR)

            w = max(w, self.settings.FCpreviewWidth)
            h = max(h, self.settings.FCpreviewHeight)
            self.preview_image.set_size_request(int(w), int(h))
            self.preview_image.set_from_pixbuf(pixbuf)
            self.preview_image.show()
            self.settings.FCpreviewWidth = int(w)
            self.settings.FCpreviewHeight = int(h)

    def _appendTag(self, taglist, tag, unused_udata):
        if tag in acceptable_tags:
            name = Gst.tag_get_nick(tag)
            type = Gst.tag_get_type(tag)
            type_getters = {GObject.TYPE_STRING: 'get_string',
                            GObject.TYPE_DOUBLE: 'get_double',
                            GObject.TYPE_FLOAT: 'get_float',
                            GObject.TYPE_INT: 'get_int',
                            GObject.TYPE_UINT: 'get_uint'}
            if type in type_getters:
                if type == GObject.TYPE_STRING:
                    value = getattr(taglist, type_getters[type])(tag)[1]
                    value = value.replace('<', ' ').replace('>', ' ')
                else:
                    value = str(getattr(taglist, type_getters[type])(tag)[1])
                self.tags[name] = value

    def _tag_found_cb(self, abus, mess):
        tag_list = mess.parse_tag()
        tag_list.foreach(self._appendTag, None)
        keys = list(self.tags.keys())
        keys.sort()
        text = self.description + "\n\n"
        for key in keys:
            text = text + "<b>" + \
                key.capitalize() + "</b>: " + self.tags[key] + "\n"
        self.l_tags.set_markup(text)

    def _on_b_details_clicked_cb(self, unused_button):
        mess = self.preview_cache_errors.get(self.current_selected_uri, None)
        if mess is not None:
            dialog = Gtk.MessageDialog(transient_for=None,
                                       modal=True,
                                       message_type=Gtk.MessageType.WARNING,
                                       buttons=Gtk.ButtonsType.OK,
                                       text=str(mess))
            dialog.set_icon_name("pitivi")
            dialog.set_title(_("Error while analyzing a file"))
            dialog.run()
            dialog.destroy()

    def _destroy_cb(self, widget):
        self.player.setState(Gst.State.NULL)
        self.is_playing = False

    def __get_best_size(self, width_in, height_in):
        if width_in > height_in:
            if self.settings.FCpreviewWidth < width_in:
                w = self.settings.FCpreviewWidth
                h = height_in * w / width_in
                return (w, h)
        else:
            if self.settings.FCpreviewHeight < height_in:
                h = self.settings.FCpreviewHeight
                w = width_in * h / height_in
                return (w, h)
        return (width_in, height_in)
Esempio n. 9
0
class PreviewWidget(Gtk.Grid, Loggable):
    """Widget for displaying a GStreamer sink with playback controls.

    Args:
        settings (GlobalSettings): The settings of the app.
    """

    def __init__(self, settings, minimal=False):
        Gtk.Grid.__init__(self)
        Loggable.__init__(self)

        self.log("Init PreviewWidget")
        self.connect('destroy', self._destroy_cb)

        self.settings = settings
        self.error_message = None

        # playbin for play pics
        self.player = AssetPipeline(clip=None, name="preview-player")
        self.player.connect('eos', self._pipelineEosCb)
        self.player.connect('error', self._pipelineErrorCb)
        self.player._bus.connect('message::tag', self._tag_found_cb)

        # some global variables for preview handling
        self.is_playing = False
        self.at_eos = False
        self.original_dims = (PREVIEW_WIDTH, PREVIEW_HEIGHT)
        self.countinuous_seek = False
        self.slider_being_used = False
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.play_on_discover = False
        self.description = ""

        # Gui elements:
        # Drawing area for video output
        self.preview_video = ViewerWidget(self.player.sink_widget)
        self.preview_video.props.hexpand = minimal
        self.preview_video.props.vexpand = minimal
        self.preview_video.show_all()
        self.attach(self.preview_video, 0, 0, 1, 1)

        # An image for images and audio
        self.preview_image = Gtk.Image()
        self.preview_image.set_size_request(
            self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
        self.preview_image.show()
        self.attach(self.preview_image, 0, 1, 1, 1)

        # Play button
        self.bbox = Gtk.Box()
        self.bbox.set_orientation(Gtk.Orientation.HORIZONTAL)
        self.play_button = Gtk.ToolButton()
        self.play_button.set_icon_name("media-playback-start")
        self.play_button.connect("clicked", self._on_start_stop_clicked_cb)
        self.bbox.pack_start(self.play_button, False, False, 0)

        # Scale for position handling
        self.pos_adj = Gtk.Adjustment()
        self.seeker = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.pos_adj)
        self.seeker.connect('button-press-event', self._on_seeker_press_cb)
        self.seeker.connect('button-release-event', self._on_seeker_press_cb)
        self.seeker.connect('motion-notify-event', self._on_motion_notify_cb)
        self.seeker.set_draw_value(False)
        self.seeker.show()
        self.bbox.pack_start(self.seeker, True, True, 0)

        # Zoom buttons
        self.b_zoom_in = Gtk.ToolButton()
        self.b_zoom_in.set_icon_name("zoom-in")
        self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1)
        self.b_zoom_out = Gtk.ToolButton()
        self.b_zoom_out.set_icon_name("zoom-out")
        self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1)
        self.bbox.pack_start(self.b_zoom_in, False, False, 0)
        self.bbox.pack_start(self.b_zoom_out, False, False, 0)
        self.bbox.show_all()
        self.attach(self.bbox, 0, 2, 1, 1)

        # Label for metadata tags
        self.l_tags = Gtk.Label()
        self.l_tags.set_justify(Gtk.Justification.LEFT)
        self.l_tags.set_ellipsize(Pango.EllipsizeMode.END)
        self.l_tags.show()
        self.attach(self.l_tags, 0, 3, 1, 1)

        # Error handling
        vbox = Gtk.Box()
        vbox.set_orientation(Gtk.Orientation.VERTICAL)
        vbox.set_spacing(SPACING)
        self.l_error = Gtk.Label(label=_("Pitivi can not preview this file."))
        self.b_details = Gtk.Button.new_with_label(_("More info"))
        self.b_details.connect('clicked', self._on_b_details_clicked_cb)
        vbox.pack_start(self.l_error, True, True, 0)
        vbox.pack_start(self.b_details, False, False, 0)
        vbox.show()
        self.attach(vbox, 0, 4, 1, 1)

        if minimal:
            self.remove(self.l_tags)
            self.bbox.remove(self.b_zoom_in)
            self.bbox.remove(self.b_zoom_out)

    def update_preview_cb(self, file_chooser):
        """Previews the URI of the specified file chooser.

        Args:
            file_chooser (Gtk.FileChooser): The file chooser providing the URI.
        """
        uri = file_chooser.get_preview_uri()
        if uri is None or not uri_is_valid(uri):
            return
        self.previewUri(uri)

    def previewUri(self, uri):
        self.log("Preview request for %s", uri)
        self.clear_preview()
        self.current_selected_uri = uri
        GES.UriClipAsset.new(uri, None, self.__asset_loaded_cb)

    def __asset_loaded_cb(self, source, res):
        uri = source.get_id()
        try:
            asset = GES.Asset.request_finish(res)
        except GLib.Error as error:
            self.log("Failed discovering %s: %s", uri, error.message)
            self.error_message = error.message
            self._show_error(uri)
            return

        self.log("Discovered %s", uri)
        self.error_message = None
        self._show_preview(uri, asset.get_info())
        if self.play_on_discover:
            self.play_on_discover = False
            self.play()

    def _show_preview(self, uri, info):
        self.log("Show preview for %s", uri)
        duration = info.get_duration()
        pretty_duration = beautify_length(duration)

        videos = info.get_video_streams()
        if videos:
            video = videos[0]
            if video.is_image():
                self.current_preview_type = 'image'
                self.preview_video.hide()
                pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                    Gst.uri_get_location(uri))
                pixbuf_w = pixbuf.get_width()
                pixbuf_h = pixbuf.get_height()
                w, h = self.__get_best_size(pixbuf_w, pixbuf_h)
                pixbuf = pixbuf.scale_simple(
                    w, h, GdkPixbuf.InterpType.NEAREST)
                self.preview_image.set_from_pixbuf(pixbuf)
                self.preview_image.set_size_request(
                    self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
                self.preview_image.show()
                self.bbox.show()
                self.play_button.hide()
                self.seeker.hide()
                self.b_zoom_in.show()
                self.b_zoom_out.show()
            else:
                self.current_preview_type = 'video'
                self.preview_image.hide()
                self.player.setClipUri(self.current_selected_uri)
                self.player.setState(Gst.State.PAUSED)
                self.pos_adj.props.upper = duration
                video_width = (
                    video.get_par_num() / video.get_par_denom()) * video.get_width()
                video_height = video.get_height()
                w, h = self.__get_best_size(video_width, video_height)
                self.preview_video.set_size_request(w, h)
                aspect_ratio = video_width / video_height
                self.preview_video.setDisplayAspectRatio(aspect_ratio)
                self.preview_video.show()
                self.bbox.show()
                self.play_button.show()
                self.seeker.show()
                self.b_zoom_in.show()
                self.b_zoom_out.show()
                self.description = "\n".join([
                    _("<b>Resolution</b>: %d×%d") % (
                        video_width, video_height),
                    _("<b>Duration</b>: %s") % pretty_duration])
        else:
            self.current_preview_type = 'audio'
            self.preview_video.hide()
            audio = info.get_audio_streams()
            if not audio:
                return

            audio = audio[0]
            self.pos_adj.props.upper = duration
            self.preview_image.set_from_icon_name(
                "audio-x-generic", Gtk.IconSize.DIALOG)
            self.preview_image.show()
            self.preview_image.set_size_request(PREVIEW_WIDTH, PREVIEW_HEIGHT)
            self.description = "\n".join([
                beautify_stream(audio),
                _("<b>Duration</b>: %s") % pretty_duration])
            self.player.setState(Gst.State.NULL)
            self.player.setClipUri(self.current_selected_uri)
            self.player.setState(Gst.State.PAUSED)
            self.play_button.show()
            self.seeker.show()
            self.b_zoom_in.hide()
            self.b_zoom_out.hide()
            self.bbox.show()

    def _show_error(self, unused_uri):
        self.l_error.show()
        self.b_details.show()

    def play(self):
        if not self.current_preview_type:
            self.play_on_discover = True
            return
        if self.at_eos:
            # The content played once already and the pipeline is at the end.
            self.at_eos = False
            self.player.simple_seek(0)
        self.player.setState(Gst.State.PLAYING)
        self.is_playing = True
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PAUSE)
        GLib.timeout_add(250, self._update_position)
        self.debug("Preview started")

    def pause(self, state=Gst.State.PAUSED):
        if state is not None:
            self.player.setState(state)
        self.is_playing = False
        self.play_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
        self.log("Preview paused")

    def togglePlayback(self):
        if self.is_playing:
            self.pause()
        else:
            self.play()

    def clear_preview(self):
        self.log("Reset PreviewWidget")
        self.seeker.set_value(0)
        self.bbox.hide()
        self.l_error.hide()
        self.b_details.hide()
        self.description = ""
        self.l_tags.set_markup("")
        self.pause(state=Gst.State.NULL)
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.preview_image.hide()
        self.preview_video.hide()

    def _on_seeker_press_cb(self, widget, event):
        self.slider_being_used = True
        if event.type == Gdk.EventType.BUTTON_PRESS:
            self.countinuous_seek = True
            if self.is_playing:
                self.player.setState(Gst.State.PAUSED)
        elif event.type == Gdk.EventType.BUTTON_RELEASE:
            self.countinuous_seek = False
            value = int(widget.get_value())
            self.player.simple_seek(value)
            self.at_eos = False
            if self.is_playing:
                self.player.setState(Gst.State.PLAYING)
            # Now, allow gobject timeout to continue updating the slider pos:
            self.slider_being_used = False

    def _on_motion_notify_cb(self, widget, event):
        if self.countinuous_seek:
            value = int(widget.get_value())
            self.player.simple_seek(value)
            self.at_eos = False

    def _pipelineEosCb(self, unused_pipeline):
        self._update_position()
        self.pause()
        # The pipeline is at the end. Leave it like that so the last frame
        # is displayed.
        self.at_eos = True

    def _pipelineErrorCb(self, unused_pipeline, unused_message, unused_detail):
        self.pause(state=Gst.State.NULL)

    def _update_position(self, *unused_args):
        if self.is_playing and not self.slider_being_used:
            curr_pos = self.player.getPosition()
            self.pos_adj.set_value(int(curr_pos))
        return self.is_playing

    def _on_start_stop_clicked_cb(self, button):
        self.togglePlayback()

    def _on_zoom_clicked_cb(self, button, increment):
        if self.current_preview_type == 'video':
            w, h = self.preview_video.get_size_request()
            if increment > 0:
                w *= 1.2
                h *= 1.2
            else:
                w *= 0.8
                h *= 0.8
                if (w, h) < self.original_dims:
                    (w, h) = self.original_dims
            self.preview_video.set_size_request(int(w), int(h))
            self.settings.FCpreviewWidth = int(w)
            self.settings.FCpreviewHeight = int(h)
        elif self.current_preview_type == 'image':
            pixbuf = self.preview_image.get_pixbuf()
            w = pixbuf.get_width()
            h = pixbuf.get_height()
            if increment > 0:
                w *= 1.2
                h *= 1.2
            else:
                w *= 0.8
                h *= 0.8
                if (w, h) < self.original_dims:
                    (w, h) = self.original_dims
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                Gst.uri_get_location(self.current_selected_uri))
            pixbuf = pixbuf.scale_simple(
                int(w), int(h), GdkPixbuf.InterpType.BILINEAR)

            w = max(w, self.settings.FCpreviewWidth)
            h = max(h, self.settings.FCpreviewHeight)
            self.preview_image.set_size_request(int(w), int(h))
            self.preview_image.set_from_pixbuf(pixbuf)
            self.preview_image.show()
            self.settings.FCpreviewWidth = int(w)
            self.settings.FCpreviewHeight = int(h)

    def _append_tag(self, taglist, tag, tags):
        if tag in ACCEPTABLE_TAGS:
            name = Gst.tag_get_nick(tag)
            type = Gst.tag_get_type(tag)
            type_getters = {GObject.TYPE_STRING: 'get_string',
                            GObject.TYPE_DOUBLE: 'get_double',
                            GObject.TYPE_FLOAT: 'get_float',
                            GObject.TYPE_INT: 'get_int',
                            GObject.TYPE_UINT: 'get_uint'}
            if type in type_getters:
                res, value = getattr(taglist, type_getters[type])(tag)
                assert res
                if not type == GObject.TYPE_STRING:
                    value = str(value)
                tags[name] = value

    def _tag_found_cb(self, unused_bus, message):
        tag_list = message.parse_tag()
        tags = {}
        tag_list.foreach(self._append_tag, tags)
        items = list(tags.items())
        items.sort()
        text = self.description + "\n\n"
        for key, value in items:
            capitalized = key.capitalize()
            escaped = html.escape(value)
            text = text + "<b>%s</b>: %s\n" % (key, escaped)
        self.l_tags.set_markup(text)

    def _on_b_details_clicked_cb(self, unused_button):
        if not self.error_message:
            return

        dialog = Gtk.MessageDialog(transient_for=None,
                                   modal=True,
                                   message_type=Gtk.MessageType.WARNING,
                                   buttons=Gtk.ButtonsType.OK,
                                   text=self.error_message)
        dialog.set_icon_name("pitivi")
        dialog.set_title(_("Error while analyzing a file"))
        dialog.run()
        dialog.destroy()

    def _destroy_cb(self, widget):
        self.player.release()
        self.is_playing = False

    def __get_best_size(self, width_in, height_in):
        if width_in > height_in:
            if self.settings.FCpreviewWidth < width_in:
                w = self.settings.FCpreviewWidth
                h = height_in * w / width_in
                return (w, h)
        else:
            if self.settings.FCpreviewHeight < height_in:
                h = self.settings.FCpreviewHeight
                w = width_in * h / height_in
                return (w, h)
        return (width_in, height_in)
Esempio n. 10
0
class PreviewWidget(Gtk.Grid, Loggable):
    """
    Widget for displaying a GStreamer sink with playback controls.

    @ivar settings: The settings of the app.
    @type settings: L{GlobalSettings}
    """

    def __init__(self, settings, minimal=False):
        Gtk.Grid.__init__(self)
        Loggable.__init__(self)

        self.log("Init PreviewWidget")
        self.connect('destroy', self._destroy_cb)

        self.settings = settings
        self.preview_cache = {}
        self.preview_cache_errors = {}

        self.discoverer = GstPbutils.Discoverer.new(Gst.SECOND)

        #playbin for play pics
        self.player = AssetPipeline(clip=None, name="preview-player")
        self.player.connect('eos', self._pipelineEosCb)
        self.player.connect('error', self._pipelineErrorCb)
        self.player._bus.connect('message::tag', self._tag_found_cb)

        #some global variables for preview handling
        self.is_playing = False
        self.original_dims = (PREVIEW_WIDTH, PREVIEW_HEIGHT)
        self.countinuous_seek = False
        self.slider_being_used = False
        self.current_selected_uri = ""
        self.current_preview_type = ""
        self.description = ""
        self.tags = {}

        # Gui elements:
        # Drawing area for video output
        self.preview_video = ViewerWidget(realizedCb=self._on_preview_video_realize_cb)
        self.preview_video.props.hexpand = minimal
        self.preview_video.props.vexpand = minimal
        self.attach(self.preview_video, 0, 0, 1, 1)

        # An image for images and audio
        self.preview_image = Gtk.Image()
        self.preview_image.set_size_request(self.settings.FCpreviewWidth, self.settings.FCpreviewHeight)
        self.preview_image.show()
        self.attach(self.preview_image, 0, 1, 1, 1)

        # Play button
        self.bbox = Gtk.HBox()
        self.play_button = Gtk.ToolButton()
        self.play_button.set_icon_name("media-playback-start")
        self.play_button.connect("clicked", self._on_start_stop_clicked_cb)
        self.bbox.pack_start(self.play_button, False, False, 0)

        #Scale for position handling
        self.pos_adj = Gtk.Adjustment()
        self.seeker = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.pos_adj)
        self.seeker.connect('button-press-event', self._on_seeker_press_cb)
        self.seeker.connect('button-release-event', self._on_seeker_press_cb)
        self.seeker.connect('motion-notify-event', self._on_motion_notify_cb)
        self.seeker.set_draw_value(False)
        self.seeker.show()
        self.bbox.pack_start(self.seeker, True, True, 0)

        # Zoom buttons
        self.b_zoom_in = Gtk.ToolButton()
        self.b_zoom_in.set_icon_name("zoom-in")
        self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1)
        self.b_zoom_out = Gtk.ToolButton()
        self.b_zoom_out.set_icon_name("zoom-out")
        self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1)
        self.bbox.pack_start(self.b_zoom_in, False, True, 0)
        self.bbox.pack_start(self.b_zoom_out, False, True, 0)
        self.bbox.show_all()
        self.attach(self.bbox, 0, 2, 1, 1)

        # Label for metadata tags
        self.l_tags = Gtk.Label()
        self.l_tags.set_justify(Gtk.Justification.LEFT)
        self.l_tags.set_ellipsize(Pango.EllipsizeMode.END)
        self.l_tags.show()
        self.attach(self.l_tags, 0, 3, 1, 1)

        # Error handling
        vbox = Gtk.VBox()
        vbox.set_spacing(SPACING)
        self.l_error = Gtk.Label(label=_("Pitivi can not preview this file."))
        self.b_details = Gtk.Button.new_with_label(_("More info"))
        self.b_details.connect('clicked', self._on_b_details_clicked_cb)
        vbox.pack_start(self.l_error, True, True, 0)
        vbox.pack_start(self.b_details, False, False, 0)
        vbox.show()
        self.attach(vbox, 0, 4, 1, 1)

        if minimal:
            self.remove(self.l_tags)
            self.bbox.remove(self.b_zoom_in)
            self.bbox.remove(self.b_zoom_out)

    def add_preview_request(self, dialogbox):
        """add a preview request """
        uri = dialogbox.get_preview_uri()
        if uri is None or not uri_is_valid(uri):
            return
        self.previewUri(uri)

    def previewUri(self, uri):
        self.log("Preview request for %s", uri)
        self.clear_preview()
        self.current_selected_uri = uri
        if uri in self.preview_cache:  # Already discovered
            self.log(uri + " already in cache")
            self.show_preview(uri, None)
        elif uri in self.preview_cache_errors:
            self.log(uri + " already in error cache")
            self.show_error(uri)
        else:
            self.log("Call discoverer for " + uri)
            self.fixme("Use a GESAsset here, and discover async with it")
            try:
                info = self.discoverer.discover_uri(uri)
            except Exception, e:
                self.preview_cache_errors[uri] = e
                if self.current_selected_uri == uri:
                    self.show_error(uri)
                return

            if self.current_selected_uri == uri:
                self.show_preview(uri, info)