예제 #1
0
 def _parse_buffer_opened(self, message):
     """Parse a WeeChat message with a new buffer (opened)."""
     for obj in message.objects:
         if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
             continue
         for item in obj.value['items']:
             buf = Buffer(self.config, item)
             self.buffers.append(buf)
             buf.connect("messageToWeechat", self.on_send_message)
             self.buffers.show(buf.pointer())
             while Gtk.events_pending():
                 Gtk.main_iteration()
예제 #2
0
 def _parse_listbuffers(self, message):
     """Parse a WeeChat with list of buffers."""
     for obj in message.objects:
         if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
             continue
         self.buffers.clear()
         for item in obj.value['items']:
             buf = Buffer(self.config, item)
             self.buffers.append(buf)
             buf.connect("messageToWeechat", self.on_send_message)
             active_node = STATE.get_active_node()
             if buf.pointer() == active_node:
                 self.buffers.show(buf.pointer())
     self.expand_buffers()
     self.request_hotlist()
예제 #3
0
class YouTubePlayer(cream.Module):
    state = STATE_NULL
    fullscreen = False

    _current_video_id = None
    _seek_timeout = None

    def __init__(self):

        cream.Module.__init__(self)
        self.videos = {}

        # Connect to YouTube:
        self.youtube = youtube.API(YOUTUBE_DEVELOPER_KEY)

        self.threadlock = Lock(self)

        # Build GTK+ interface:
        self.ui = cream.gui.builder.GtkBuilderInterface(INTERFACE_FILE)

        self.ui.throbber = Throbber()

        self.ui.slider = cream.gui.widgets.slider.Slider()
        self.ui.slider.append_widget(self.ui.search_box)
        self.ui.slider.append_widget(self.ui.info_box)
        self.ui.slider.set_size_request(240, 300)

        self.ui.sidebar.add(self.ui.slider)

        self.ui.fullscreen_window.fullscreen()
        self.ui.fullscreen_video_area.set_app_paintable(True)
        self.ui.fullscreen_video_area.connect("button-press-event", self.video_area_click_cb)
        self.ui.fullscreen_video_area.connect("expose-event", self.expose_cb)

        self.ui.video_area.set_app_paintable(True)
        self.ui.video_area.connect("expose-event", self.expose_cb)
        self.ui.video_area.connect("button-press-event", self.video_area_click_cb)

        self.ui.play_pause_image.set_from_icon_name("media-playback-start", gtk.ICON_SIZE_BUTTON)
        self.ui.play_pause_button.connect("clicked", self.play_pause_cb)

        self.ui.search_entry.connect("activate", self.search_cb)
        self.ui.search_entry.connect("icon-release", lambda *args: self.ui.sort_by_menu.popup(None, None, None, 1, 0))

        self.ui.resolution_chooser.connect("changed", self.resolution_changed_cb)
        self.ui.sort_by_menu.connect("selection-done", self.search_cb)
        self.ui.back_to_search_button.connect("clicked", self.back_to_search_button_clicked_cb)
        self.ui.info_label_description.connect(
            "size-allocate", lambda source, allocation: source.set_size_request(allocation.width - 2, -1)
        )
        self.ui.show_subtitles_btn.connect("toggled", self.subtitles_toggled_cb)
        self.ui.progress_scale.connect("change-value", self.seek_cb)

        def _reset_timeout(*args):
            self.ui.slider.try_reset_slide_timeout(self.ui.info_box)

        self.ui.search_entry.connect("changed", _reset_timeout)
        self.ui.search_entry.connect("motion-notify-event", _reset_timeout)

        self.ui.search_results_treeview.connect("motion-notify-event", _reset_timeout)
        self.ui.search_results_treeview.connect("row-activated", self.row_activated_cb)
        self.ui.search_results_treeview.connect("size-allocate", self.treeview_size_allocate_cb)

        self.buffer = Buffer()
        self.buffer.connect("update", self.buffer_update_cb)
        self.buffer.connect("ready", lambda *args: self.set_state(STATE_BUFFERING))

        # Initialize GStreamer stuff:
        self.player = gst.Pipeline("player")

        self.playbin = gst.element_factory_make("playbin2", "playbin")
        self.video_sink = gst.element_factory_make("xvimagesink", "vsink")
        self.playbin.set_property("video-sink", self.video_sink)
        self.playbin.set_property("buffer-duration", 10000000000)
        self.playbin.set_property("buffer-size", 2000000000)
        self.player.add(self.playbin)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)

        self.ui.window.connect("destroy", lambda *args: self.quit())
        self.ui.window.show_all()

        gobject.timeout_add(200, self.update_progressbar)

    def buffer_update_cb(self, source, position):

        self.ui.progress_scale.set_fill_level(max(0, position - 1))

        if position == -1:
            self.ui.throbber.set_mode(MODE_SPINNING)
        else:
            self.ui.throbber.set_mode(MODE_STATIC)

            try:
                duration = self.playbin.query_duration(gst.FORMAT_TIME, None)[0]
            except gst.QueryError:
                duration = 0

            buffer_length = (position * duration) / 100000000000
            self.ui.throbber.set_progress(buffer_length / 5.0)

            if buffer_length >= 5 and self.state == STATE_BUFFERING:
                self.set_state(STATE_PLAYING)

    def _seek(self, position):

        self.player.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, position)

        return False

    def seek_cb(self, source, scroll, value):

        fill_level = self.ui.progress_scale.get_fill_level()

        position = min(value, fill_level)

        try:
            duration_ns = self.player.query_duration(gst.FORMAT_TIME, None)[0]
        except gst.QueryError:
            duration_ns = 0

        position_ns = (duration_ns / 100.0) * position

        if self._seek_timeout:
            gobject.source_remove(self._seek_timeout)
        self._seek_timeout = gobject.timeout_add(10, lambda *args: self._seek(position_ns))

    def back_to_search_button_clicked_cb(self, source):
        self.ui.slider.slide_to(self.ui.search_box)
        self.ui.slider.try_remove_slide_timeout(self.ui.info_box)
        self.ui.slider.add_slide_timeout(self.ui.info_box, 5)

    def treeview_size_allocate_cb(self, source, allocation):

        self.ui.cellrenderer_info.set_property("width", allocation.width - ICON_SIZE - 8)

    def video_area_click_cb(self, source, event):

        if event.type == gtk.gdk._2BUTTON_PRESS:
            self.toggle_fullscreen()

    def toggle_fullscreen(self):
        def set_video_area(area):
            self.video_sink.set_xwindow_id(area.window.xid)

        if not self.fullscreen:
            # activate fullscreen and continue playback on fullscreen video area.
            self.ui.fullscreen_window.show_all()
            if self.ui.fullscreen_window.window:
                set_video_area(self.ui.fullscreen_video_area)
            else:
                self.ui.fullscreen_window.connect("map", lambda *args: set_video_area(self.ui.fullscreen_video_area))
            self.fullscreen = True
        else:
            # deactivate fullscreen and continue playback on normal video area.
            self.ui.fullscreen_window.hide()
            set_video_area(self.ui.video_area)
            self.fullscreen = False

    def expose_cb(self, source, event):
        self.draw()

    def draw(self):

        if self.fullscreen:
            video_area = self.ui.fullscreen_video_area
        else:
            video_area = self.ui.video_area

        width = video_area.get_allocation().width
        height = video_area.get_allocation().height

        if self.fullscreen:
            ctx = video_area.window.cairo_create()
        else:
            ctx = video_area.window.cairo_create()

        ctx.set_source_rgb(0, 0, 0)
        ctx.paint()

        if self.state == STATE_NULL:
            logo_width = logo_height = min(0.4 * width, 0.4 * height)

            logo_x = (width - logo_width) / 2.0
            logo_y = (height - logo_height) / 2.0

            thumbnail = gtk.gdk.pixbuf_new_from_file_at_size(PLAYER_LOGO, int(logo_width), int(logo_height))
            ctx.set_source_pixbuf(thumbnail, logo_x, logo_y)
            ctx.paint()

    def search_cb(self, source):

        search_string = self.ui.search_entry.get_text()
        self.search(search_string)
        self.ui.slider.try_reset_slide_timeout(self.ui.info_box)

    def subtitles_toggled_cb(self, *args):

        video = self.videos[self._current_video_id]
        video.request_subtitle_list()

        lang_code = "en"  # TODO

        try:
            tempfile = video.download_subtitle(lang_code, format="mpl2")
        except youtube.YouTubeError:
            # it is possible that the subtitle file is empty
            # even if the language was listed to be available,
            # so fail silently here
            # TODO: ausgrau()
            pass
        else:
            self.playbin.set_property("suburi", "file://%s" % tempfile)
            self.playbin.set_property("subtitle-font-desc", "Sans 14")

    def row_activated_cb(self, source, iter, path):

        selection = self.ui.search_results_treeview.get_selection()
        model, iter = selection.get_selected()
        id = model.get_value(iter, 0)
        thread.start_new_thread(self.load_video, (id,))

    def play_pause_cb(self, source):

        if self.state == STATE_NULL:
            selection = self.ui.search_results_treeview.get_selection()
            model, iter = selection.get_selected()
            id = model.get_value(iter, 0)
            thread.start_new_thread(self.load_video, (id,))
        elif self.state == STATE_PAUSED:
            self.set_state(STATE_PLAYING)
        else:
            self.ui.slider.remove_slide_timeout(self.ui.info_box)  # stop sliding to the info box
            self.set_state(STATE_PAUSED)

    def resolution_changed_cb(self, resolution_combobox):
        if self.state == STATE_PLAYING:
            gtk_iter = resolution_combobox.get_active_iter()
            self.config.preferred_resolution = self.ui.resolutions_store.get_value(gtk_iter, 0)
            # replay the currently played video with the selected quality.
            # TODO: Remember the seek here and re-seek to that point.
            thread.start_new_thread(self.load_video, (self._current_video_id,))

    def search(self, search_string):

        sort_by = youtube.SORT_BY_RELEVANCE
        if self.ui.sort_by_published.get_active():
            sort_by = youtube.SORT_BY_PUBLISHED

        self.ui.search_results_liststore.clear()
        thread.start_new_thread(self._search, (search_string, sort_by))

    def _search(self, search_string, sort_by, **search_args):

        search_result = self.youtube.search(search_string, sort_by, **search_args)

        for video in search_result:
            self.videos.setdefault(video.video_id, video)

            title = cleanup_markup(video.title)
            description = "" if video.description is None else cleanup_markup(video.description)

            info = "<b>{title}</b>\n{description}\n{duration}".format(
                title=title,
                description=cream.util.string.crop_string(description, 100),
                duration=convert_ns(int(video.duration) * 1000000000),
            )

            with gtk.gdk.lock:
                video._tree_iter = self.ui.search_results_liststore.append((video.video_id, info, None, True))

        for row in self.ui.search_results_liststore:
            try:
                video = self.videos[row[0]]
            except TypeError:
                print "row[0]:", row[0]
            if self._request_video_info(video):
                thumbnail = gtk.gdk.pixbuf_new_from_file(video.download_thumbnail()).scale_simple(
                    ICON_SIZE, ICON_SIZE, gtk.gdk.INTERP_HYPER
                )
            else:
                thumbnail = DEFAULT_THUMBNAIL
            row[2] = thumbnail

    def _request_video_info(self, video):
        try:
            video.request_video_info()
            return True
        except youtube.YouTubeError, exc:
            self.ui.search_results_liststore.set_value(video._tree_iter, 1, cleanup_markup(exc.reason))
            self.ui.search_results_liststore.set_value(video._tree_iter, 3, False)
            return False