# -*- coding: utf-8 -*- # # Epiphany extension: Video downloader; # Based on Creative Commons license viewer [Version 0.2 (27/07/2006)] # Copyright (C) 2006 Jaime Frutos Morales # Copyright (C) 2008 Adam Schmalhofer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ An extension for the GNOME Web Browser 'Epiphany'. It searches the page as well as the URL in the address bar for known video hosting sites. These embeded videos then can be via clive. """ OPEN_WITH_DOWNLOAD = 0 OPEN_WITH_TAB = 1 OPEN_WITH_TOTEM = 2 OPEN_WITH_URL_DEBUG = 3 DEFAULT_OPEN = OPEN_WITH_DOWNLOAD import re import gconf from subprocess import Popen, PIPE from os import path import epiphany import gobject import pygtk pygtk.require('2.0') import gtk import pynotify from pynotify import Notification from gettext import gettext as _, ngettext as N_ GCONF_KEY_DOWNLOAD_FOLDER = '/apps/epiphany/directories/downloads_folder' GCONF_KEY_USE_PROXY = '/system/http_proxy/use_http_proxy' GCONF_KEY_PROXY_PORT = '/system/http_proxy/port' GCONF_KEY_PROXY_HOST = '/system/http_proxy/host' GCONF_KEY_USE_PROXY_AUTH = '/system/http_proxy/use_authentication' GCONF_KEY_PROXY_USER = '/system/http_proxy/authentication_user' GCONF_KEY_PROXY_PASSWORD = '/system/http_proxy/authentication_password' # Each element in EMBEDED_VIDEO_RE_LIST must contain exactly one set of braces. EMBEDED_VIDEO_RE_LIST = [ r'(?:[a-z]{2,3}\.)?youtube\.[a-z]{2,3}/[^\'"#&]*v[=/]([\w-]{11})', r'video\.google\.com/googleplayer\.swf\?docid=(-?\d{18,})', r'www\.dailymotion\.com/swf/(\w{18})', r'www\.guba\.com/f/root\.swf\?video_url=http://free\.guba\.com/uploaditem/(\d{10})/flash\.flv', r'(?:[a-z]{2,3}\.)?sevenload\.[a-z]{2,3}/pl/(\w{7})'] # One element in VIDEO_PAGE_BASE for each in EMBEDED_VIDEO_RE_LIST. # %s is replaced with the string matching the braces in EMBEDED_VIDEO_RE_LIST. VIDEO_PAGE_BASE = [ r'http://youtube.com/watch?v=%s', r'http://video.google.com/videoplay?docid=%s', r'http://www.dailymotion.com/video/%s', r'http://www.guba.com/watch/%s', r'http://sevenload.com/videos/%s' ] URL_VIDEO_RE_BASE_LIST = [ r'[a-z]{2,3}\.youtube\.com/watch\?v=[\w-]{11}', r'video\.google\.com/videoplay\?docid=-?\d{18,}', r'[a-z]{2,3}\.sevenload\.com/(videos|sendungen/[a-z-]+/folgen|shows/[a-z-]+/episodes)/\w', r'break\.com/[a-z-]+/[a-z-]+\.html', r'www\.liveleak\.com/view\?i=\d{3}_\d{10}', r'www\.evisor\.tv/tv/[a-z0-9_-]+/[a-z0-9_-]+\d{4}\.htm', r'www\.dailymotion\.com(/.*)?/video/', # redtube, cctv: to be added r'www\.guba\.com/watch/', ] URL_VIDEO_RE = re.compile('^http://(%s)' % '|'.join(URL_VIDEO_RE_BASE_LIST), re.I) EMBEDED_VIDEO_RE = re.compile('http://(%s)' % '|'.join(EMBEDED_VIDEO_RE_LIST), re.I) def _switch_page_cb(notebook, page, page_num, window): ui_show(window, window.get_active_child()) def _load_status_cb(embed, data, window): detect_videos_in_address(window, embed) if not embed.get_property('load-status') and not embed._has_video: # page is loaded and url isn't a video hosting page detect_videos_in_page(window, embed) ui_show(window, embed) def _video_button_pressed_cb(widget, event, window): if event.button not in [1,2]: return embed = window.get_active_child() if pynotify.init('Basics') and event.button == 1: n = Notification(_('Started downloading.'), N_('Started to download %s video.', 'Started to download %s videos.', len(embed._video_urls)) % \ len(embed._video_urls) ) n.set_urgency(pynotify.URGENCY_LOW) x, y = embed.get_pointer() forget, x, y, forget = gtk.gdk.\ screen_get_default().get_display().get_pointer() n.set_hint('x', x) n.set_hint('y', y) n.show() if event.button == 2: open_with = OPEN_WITH_TAB else: open_with = DEFAULT_OPEN download_videos(window, embed, embed._video_urls, open_with) def ui_init(window): cc_image = gtk.Image() icon_theme = gtk.icon_theme_get_default() pixbuf = icon_theme.load_icon('video', gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)[0], 0) cc_image.set_from_pixbuf(pixbuf) cc_image.show() eventbox = gtk.EventBox() eventbox.set_visible_window(True) eventbox.connect ("button-press-event", _video_button_pressed_cb, window); # Pack the widgets eventbox.add(cc_image) eventbox.show() statusbar = window.get_statusbar() statusbar.add_widget(eventbox) statusbar._video_eventbox = eventbox def ui_show(window, embed): if embed != window.get_active_child(): return statusbar = window.get_statusbar() eventbox = statusbar._video_eventbox try: if embed._has_video: n = len(embed._video_urls) eventbox.set_tooltip_text( N_("%s embeded video", "%s embeded videos", n) % n) eventbox.show() else: eventbox.hide() except: eventbox.hide() def ui_destroy(window): statusbar = window.get_statusbar() eventbox = statusbar._video_eventbox del statusbar._video_eventbox statusbar.remove_widget(eventbox) def _gconf_new_downloads_folder_cb(client, gconf_id, gconf_entry, **kwargs): update_downloads_folder() def _gconf_proxy_changed_cb(client, gconf_id, gconf_entry, **kwargs): update_proxy_settings() def update_proxy_settings(): global proxy g = gconf_client if g.get_bool(GCONF_KEY_USE_PROXY): # XXX: Should check if string is a valid proxy. proxy = '%s:%s' % (g.get_string(GCONF_KEY_PROXY_HOST), \ g.get_int(GCONF_KEY_PROXY_PORT)) if g.get_bool(GCONF_KEY_USE_PROXY_AUTH): auth = '%s:%s@'%(g.get_string(GCONF_KEY_PROXY_USER), \ g.get_string(GCONF_KEY_PROXY_PASSWORD)) else: auth = '' proxy = 'http://%s%s:%i'%(auth, g.get_string(GCONF_KEY_PROXY_HOST), \ g.get_int(GCONF_KEY_PROXY_PORT)) else: proxy = None def update_downloads_folder(): global downloads_folder new_folder = path.join(path.expanduser('~'), path.expanduser( gconf_client.get_string(GCONF_KEY_DOWNLOAD_FOLDER))) if path.isdir(new_folder): downloads_folder = new_folder return True return False def download_videos(window, embed, video_urls, open_with): clive_cmd_base = [ 'clive', '--emit-csv', ] if proxy != None: clive_cmd_base.append('--proxy=%s' % proxy) for video_url in video_urls: clive_cmd = clive_cmd_base + [ video_url ] try: new_download_ps = Popen(clive_cmd, stdout=PIPE, stderr=PIPE) except OSError, e: print 'OSError executing clive', e if e.args[0] == 2: error_text = _("Video-downloader extension could not execute \ 'clive'. Please make sure clive is installed \ in a directory listed in the $PATH variable.") else: error_text = str(e) message = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=error_text) message.run() message.destroy() else: gobject.child_watch_add(new_download_ps.pid, _clive_finished_cb, data=(window, embed, new_download_ps.stdout, new_download_ps.stderr, video_url, open_with)) def detect_videos_in_address(window, embed): url = embed.get_address() if URL_VIDEO_RE.findall(url) != []: embed._video_urls = [url] embed._has_video = True else: embed._has_video = False def open_video_download(window, embed, url, file_name): """ Lets Epiphany download a video via EmbedPersist given the output of clive (as a string array). Doesn't work with Webkit-backend. """ persist = epiphany.ephy_embed_factory_new_object(epiphany.EmbedPersist) persist.set_source(url) persist.set_fc_parent(window) persist.set_fc_title(_('Save Video As')) persist.set_dest(path.join(downloads_folder, file_name[1:-1])) persist.set_embed(embed) persist.set_flags(epiphany.EMBED_PERSIST_ASK_DESTINATION) if not persist.save(): # Failed to start downloading the video error_text = _("Failed to start the download. Please report this \ problem as a comment at http://blauebirke.wordpress.com/2008/10/04/new-improvements-in-epiphany-video-downloader-extension/ \ or send an email backend to Adam.Schmalhofer@gmx.de.") message = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=error_text) message.run() message.destroy() def open_video_inside(window, embed, url, file_name): tab = epiphany.ephy_shell_get_default().\ new_tab(window, window.get_active_child(), "about:blank", epiphany.NEW_TAB_IN_EXISTING_WINDOW) window.set_active_child(tab) window.load_url(url) def open_video_totem(window, embed, url, file_name): Popen(['totem', '--enqueue', url]) def open_video_debug_url(window, embed, url, file_name): """ It displays a dialog with the URL which otherwise would be downloaded. Used for debugging only. """ message_text = "Url: %s\nFilename: %s" % (url, file_name) message = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=message_text) message.run() message.destroy() def _clive_finished_cb(pid, condition, data): """ Processes the results from the clive process. If succeded starts the download. Otherwise displays an error message to the user. """ window, embed, clive_stdout, clive_stderr, video_url, open_with = data open_methods = [ open_video_download, open_video_inside, open_video_totem, open_video_debug_url ] output = clive_stdout.readlines() if condition == 0: try: fields = output[-1].split(',') if len(fields) > 8: url, file_name = fields[1:3] else: file_name, id, url = fields[0:3] file_name = file_name[4:] # to remove the "csv:" url = url[1:-1] # to remove the quotes open_methods[open_with](window, embed, url, file_name) except IndexError: print output raise else: title_text = _('Video Downloader extension: Extracting video information via clive failed:') error_text = ''.join(['Exited with status %s.\n' % condition] + clive_stderr.readlines() + [ '==========\n' ] + clive_stdout.readlines()) message = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=title_text) message.format_secondary_text(error_text) message.run() message.destroy() def detect_videos_in_page(window, embed): # Get the HTML code persist = epiphany.ephy_embed_factory_new_object(epiphany.EmbedPersist) persist.set_flags( epiphany.EMBED_PERSIST_NO_VIEW | epiphany.EMBED_PERSIST_COPY_PAGE) persist.set_embed(embed) page_string = persist.to_string() video_urls = get_video_urls(page_string) if video_urls: embed._video_urls = video_urls embed._has_video = True else : embed._has_video = False def get_video_urls(page_string): """ Finds all unique videos represented by a plain text link from the page (something starting with a "http://"). Returns a set of url-strings. """ video_urls = set() for packed_url in EMBEDED_VIDEO_RE.findall(page_string): for video_id, page_base in zip(packed_url[1:], VIDEO_PAGE_BASE): if video_id != '': video_urls.add(page_base % video_id) return video_urls def attach_window(window): notebook = window.get_notebook() ui_init(window) signal_tab_switch = notebook.connect_after( "switch_page", _switch_page_cb, window); notebook._video_signal_tab_switch = signal_tab_switch def detach_window(window): notebook = window.get_notebook() notebook.disconnect(notebook._video_signal_tab_switch) del notebook._video_signal_tab_switch ui_destroy(window) def attach_tab(window, embed): signal_load_status = embed.connect_after( "notify::load-status", _load_status_cb, window) embed._video_signal_load_status = signal_load_status def detach_tab(window, embed): embed.disconnect(embed._video_signal_load_status) del embed._video_signal_load_status gconf_client = gconf.client_get_default() gconf_client.notify_add(GCONF_KEY_DOWNLOAD_FOLDER, _gconf_new_downloads_folder_cb) for key in [ GCONF_KEY_USE_PROXY, GCONF_KEY_PROXY_HOST, GCONF_KEY_PROXY_PORT, GCONF_KEY_USE_PROXY_AUTH, GCONF_KEY_PROXY_USER, GCONF_KEY_PROXY_PASSWORD ]: gconf_client.notify_add(key, _gconf_proxy_changed_cb) downloads_folder = path.expanduser('~') update_downloads_folder() update_proxy_settings() def debug_video_urls(window, embed, video_urls, open_inside): """ A drop-in replacement for the function download_videos. It displays a dialog with the URLs which otherwise would be downloaded. Used for debugging only. """ message_text = "\n".join(video_urls) message = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=message_text) message.run() message.destroy() debug_real_download_videos(window, embed, video_urls, OPEN_WITH_URL_DEBUG) # DEBUG #debug_real_download_videos=download_videos; download_videos=debug_video_urls