diff --git a/pitivi/ui/mainwindow.py b/pitivi/ui/mainwindow.py
index 8b2cf54e..efbb2215 100644
--- a/pitivi/ui/mainwindow.py
+++ b/pitivi/ui/mainwindow.py
@@ -33,6 +33,19 @@
from urllib import unquote
import webbrowser
+try:
+ import gconf
+except:
+ HAVE_GCONF = False
+else:
+ HAVE_GCONF = True
+
+try:
+ import gdata.youtube.client
+ HAVE_GDATA_2 = True
+except:
+ HAVE_GDATA_2 = False
+
from gettext import gettext as _
from gtk import RecentManager
@@ -744,7 +757,8 @@ def _projectManagerNewProjectLoadedCb(self, projectManager, project):
self._connectToProjectSources(project.sources)
can_render = project.timeline.duration > 0
self.render_button.set_sensitive(can_render)
- self.publish_button.set_sensitive(can_render)
+ if HAVE_GDATA_2:
+ self.publish_button.set_sensitive(can_render)
self._syncDoUndo(self.app.action_log)
if self._missingUriOnLoading:
@@ -1062,7 +1076,8 @@ def _timelineDurationChangedCb(self, timeline, duration):
else:
sensitive = False
self.render_button.set_sensitive(sensitive)
- self.publish_button.set_sensitive(sensitive)
+ if HAVE_GDATA_2:
+ self.publish_button.set_sensitive(sensitive)
## other
diff --git a/pitivi/ui/publishtoyoutubedialog.glade b/pitivi/ui/publishtoyoutubedialog.glade
index fffc66dd..933d1edd 100644
--- a/pitivi/ui/publishtoyoutubedialog.glade
+++ b/pitivi/ui/publishtoyoutubedialog.glade
@@ -18,6 +18,9 @@
+
+
+
True
@@ -180,7 +183,7 @@
True
- 5
+ 7
2
@@ -322,6 +325,42 @@
5
+
+
+ True
+ True
+ ●
+
+
+ 1
+ 2
+ 6
+ 7
+
+
+
+
+ True
+ Folder :
+
+
+ 5
+ 6
+
+
+
+
+ True
+ File name:
+
+
+ 6
+ 7
+
+
+
+
+
@@ -339,17 +378,19 @@
True
-
+
+
+
+
True
- Initializing ..
+ False
+ False
+ end
0
-
-
-
summary
diff --git a/pitivi/ui/publishtoyoutubedialog.py b/pitivi/ui/publishtoyoutubedialog.py
index c6a04c1f..70f7655d 100644
--- a/pitivi/ui/publishtoyoutubedialog.py
+++ b/pitivi/ui/publishtoyoutubedialog.py
@@ -2,7 +2,7 @@
#
# ui/publishtoyoutubedialog.py
#
-# Copyright (c) 2010, Magnus Hoff
+# Copyright (c) 2010, Mathieu Duponchelle
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -23,16 +23,18 @@
Dialog for publishing to YouTube
"""
-import tempfile, os, gtk
+import gtk
+import time
+import thread
+from gst import SECOND
from pitivi.log.loggable import Loggable
from pitivi.ui.glade import GladeWindow
from pitivi.actioner import Renderer
-from projectsettings import ProjectSettingsDialog
-from pitivi.youtube_glib import AsyncYT, PipeWrapper
+from pitivi.youtube_glib import YTUploader
from gettext import gettext as _
-from gtk import ProgressBar
from gobject import timeout_add
from string import ascii_lowercase, ascii_uppercase, maketrans, translate
+from pitivi.utils import beautify_length
try :
import gnomekeyring as gk
unsecure_storing = False
@@ -51,6 +53,7 @@ def __init__(self, app, project, pipeline=None):
self.app = app
self.pipeline = pipeline
+ self.project = project
# UI widgets
self.login = self.widgets["login"]
@@ -79,48 +82,47 @@ def __init__(self, app, project, pipeline=None):
self.password.set_text(item_info.get_secret())
gk.lock_sync('pitivi')
- self.remember_me = False
+ self.uploader = YTUploader()
self.description = self.widgets["description"]
self.tags = self.widgets["tags"]
self.categories = gtk.combo_box_new_text()
self.widgets["table2"].attach(self.categories, 1, 2, 3, 4)
self.categories.show()
self.categories.set_title("Choose a category")
- self.hbox = gtk.HBox()
- self.progressbar = gtk.ProgressBar()
+ for e in catlist:
+ self.categories.append_text(e)
+
+ self.renderbar = self.widgets["renderbar"]
+ self.uploadbar = gtk.ProgressBar()
self.stopbutton = gtk.ToolButton(gtk.STOCK_CANCEL)
- self.hbox.pack_start(self.progressbar)
- self.hbox.pack_start(self.stopbutton)
+ self.hbox = gtk.HBox()
+ self.hbox.pack_start(self.uploadbar)
+ self.hbox.pack_end(self.stopbutton)
self.taglist = []
+ self.fileentry = self.widgets["fileentry"]
+ self.updateFilename(self.project.name)
+ self.filebutton = gtk.FileChooserButton("Select a file")
+ self.widgets["table2"].attach(self.filebutton, 1, 2, 5, 6)
+ self.filebutton.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ self.filebutton.set_current_folder(self.app.settings.lastExportFolder)
+ self.filebutton.show()
+
+ self.remember_me = False
+
# Assistant pages
self.login_page = self.window.get_nth_page(0)
self.metadata_page = self.window.get_nth_page(1)
self.render_page = self.window.get_nth_page(2)
self.announce_page = self.window.get_nth_page(3)
- for e in catlist:
- self.categories.append_text(e)
-
self.description.get_buffer().connect("changed", self._descriptionChangedCb)
self.categories.connect("changed", self._categoryChangedCb)
self.stopbutton.connect('clicked', self._finishCb)
self.mainquitsignal = self.app.connect('destroy', self._mainQuitCb)
-
+ self.connect("eos", self._renderingDoneCb)
self.window.connect("delete-event", self._deleteEventCb)
- self.tmpdir = tempfile.mkdtemp()
- self.fifoname = os.path.join(self.tmpdir, 'pitivi_rendering_fifo')
- os.mkfifo(self.fifoname)
-
- # TODO: This is probably not the best way to build an URL
- #self.outfile = 'file:/home/mag/test.webm' #'file:' + self.fifoname
- outfile = 'file://' + self.fifoname
-
- Renderer.__init__(self, project, pipeline, outfile = outfile)
-
- # YouTube integration
- self.yt = AsyncYT()
self.metadata = {
"title": "",
"description": "",
@@ -130,27 +132,8 @@ def __init__(self, app, project, pipeline=None):
}
self.has_started_rendering = False
- def _shutDown(self):
- self.debug("shutting down")
- self.app.project.setSettings(self.oldsettings)
- self.app.publish_button.set_sensitive(True)
- self.app.handler_disconnect(self.mainquitsignal)
-
- try:
- os.remove(self.fifoname)
- except OSError:
- pass
-
- try:
- os.rmdir(self.tmpdir)
- except OSError:
- pass
-
- # Abort recording
- self.removeAction()
- self.yt.stop()
- self.window.destroy()
- self.destroy()
+ def updateFilename(self, name):
+ self.fileentry.set_text(name + ".avi")
def _storePassword(self):
if unsecure_storing:
@@ -168,18 +151,51 @@ def _storePassword(self):
a = gk.item_create_sync('pitivi', gk.ITEM_GENERIC_SECRET,
self.username.get_text(), atts, self.password.get_text(), True)
- def _mainQuitCb(self, ignored):
- self.yt.stop()
- self.window.destroy()
- self.destroy()
+ def _update_metadata_page_complete(self):
+ is_complete = all([
+ len(self.metadata["title"]) != 0,
+ len(self.metadata["description"]) != 0,
+ ])
+ self.window.set_page_complete(self.metadata_page, is_complete)
- def _deleteEventCb(self, window, event):
- self.debug("delete event")
- self._shutDown()
+ def _startRendering(self):
- def _cancelCb(self, ignored):
- self.debug("cancel event")
- self._shutDown()
+ self.has_started_rendering = True
+
+ # Start rendering:
+ self.filename = self.filebutton.get_uri() + "/" + self.fileentry.get_text()
+ Renderer.__init__(self, self.project, self.pipeline, outfile =
+ self.filename)
+ self.app.set_sensitive(False)
+ self.startAction()
+ self.renderbar.set_fraction(0)
+ self.visible = True
+
+ def updatePosition(self, fraction, estimated, uploading = False):
+ if not uploading:
+ self.renderbar.set_fraction(fraction)
+ self.app.set_title(_("%d%% Rendered") % int(100 * fraction))
+ else:
+ self.uploadbar.set_fraction(fraction)
+ if estimated and not uploading:
+ self.renderbar.set_text(_("About %s left in rendering") % estimated)
+ elif estimated and uploading:
+ self.uploadbar.set_text(_("About %s left in uploading") % estimated)
+
+ def _shutDown(self):
+ self.debug("shutting down")
+ if self.uploader.uploader:
+ self.uploader.uploader.run = False
+ self.app.project.setSettings(self.oldsettings)
+ self.app.set_sensitive(True)
+ self.app.publish_button.set_sensitive(True)
+ self.app.handler_disconnect(self.mainquitsignal)
+
+ # Abort recording
+ if self.has_started_rendering:
+ self.removeAction()
+ self.window.destroy()
+ self.destroy()
def _rememberMeCb(self, button):
self.remember_me = False
@@ -190,16 +206,14 @@ def _loginClickedCb(self, *args):
self.debug("login clicked")
self.login_status.set_text("Logging in...")
# TODO: This should activate a throbber
- self.yt.authenticate_with_password(self.username.get_text(), self.password.get_text(), self._loginResultCb)
+ thread.start_new_thread (self.uploader.authenticate_with_password, (self.username.get_text(),
+ self.password.get_text(), self._loginResultCb))
def _loginResultCb(self, result):
# TODO: The throbber should now be deactivated
- status = result[0]
- if status == 'good':
- status, login_token = result
+ if result == 'good':
if self.remember_me:
self._storePassword()
- self.login_status.set_text("Logged in")
self.window.set_page_complete(self.login_page, True)
self.window.set_current_page(self.window.get_current_page() + 1)
else:
@@ -207,13 +221,6 @@ def _loginResultCb(self, result):
self.login_status.set_text(str(exception))
self.window.set_page_complete(self.login_page, False)
- def _update_metadata_page_complete(self):
- is_complete = all([
- len(self.metadata["title"]) != 0,
- len(self.metadata["description"]) != 0,
- ])
- self.window.set_page_complete(self.metadata_page, is_complete)
-
def _titleChangedCb(self, entry):
self.metadata["title"] = entry.get_text()
self._update_metadata_page_complete()
@@ -246,67 +253,58 @@ def _newTagCb(self, entry):
def _categoryChangedCb(self, combo):
self.metadata["category"] = combo.get_active_text()
- def _prepareCb(self, assistant, page):
- if page == self.render_page and not self.has_started_rendering:
- self._startRenderAndUpload()
-
- def _destroyCb (self, ignored):
- self._shutDown()
-
def _changeStatusCb (self, button):
if button.get_active():
self.metadata["private"] = True
else:
self.metadata["private"] = False
- def _startRenderAndUpload(self):
-
- self.has_started_rendering = True
-
- # Start uploading:
- self.yt.upload(lambda: PipeWrapper(open(self.fifoname, 'rb')), self.metadata, self._uploadDoneCb)
+ def _prepareCb(self, assistant, page):
+ if page == self.render_page and not self.has_started_rendering:
+ self._startRendering()
- # Start rendering:
- self.startAction()
+ def _renderingDoneCb(self, data):
+ self.app.set_sensitive(True)
+ self.app.set_title(_("PiTiVi"))
+ self.timestarted = time.time()
+ self.window.hide()
self.app.sourcelist.pack_end(self.hbox, False, False)
self.hbox.show_all()
- self.progressbar.set_fraction(0)
- self.visible = True
+ self.filename = self.filename.split("://")[1]
+ self.uploader.upload(self.filename, self.metadata, self._uploadProgressCb, self._uploadDoneCb)
+
+ def _uploadProgressCb(self, done, total):
+ timediff = time.time() - self.timestarted
+ fraction = float(min(done, total)) / float(total)
+ if timediff > 3.0:
+ totaltime = (timediff * float(total) / float(done)) - timediff
+ text = beautify_length(int(totaltime * SECOND))
+ self.updatePosition(fraction, text, uploading = True)
+
+ def _uploadDoneCb(self, video_entry):
+ print "done !"
+ self.entry = gtk.Entry()
+ link = video_entry.find_html_link().split("&")[0]
+ self.entry.set_text(link)
+ self.uploadbar.destroy()
+ self.hbox.pack_start (self.entry)
+ self.entry.show()
- def updatePosition(self, fraction, text):
- self.progressbar.set_fraction(fraction)
- if self.visible:
- self.window.destroy()
- self.visible = False
- if text is not None and fraction < 0.99:
- self.progressbar.set_text(_("About %s left") % text)
- elif fraction < 0.05:
- self.progressbar.set_text(_("Starting rendering"))
- elif fraction > 0.99:
- self.progressbar.set_text(_("Rendering done, finishing uploading"))
- self.progressbar.set_pulse_step (0.05)
- self.over = False
- timeout_add (400, self._pulseCb)
-
- def _pulseCb(self):
- self.progressbar.pulse()
- if not self.over :
- timeout_add (400, self._pulseCb)
- return False
+ def _mainQuitCb(self, ignored):
+ self.window.destroy()
+ self.destroy()
+
+ def _deleteEventCb(self, window, event):
+ self.debug("delete event")
+ self._shutDown()
+
+ def _cancelCb(self, ignored):
+ self.debug("cancel event")
+ self._shutDown()
+
+ def _destroyCb (self, ignored):
+ self._shutDown()
def _finishCb(self, unused):
self.hbox.destroy()
self._shutDown()
-
- def _uploadDoneCb(self, result):
- self.entry = gtk.Entry()
- if result[0] == "good":
- status, new_entry = result
- self.entry.set_text(new_entry.GetSwfUrl().split ("?")[0])
- else:
- status, exception = result
- self.entry.set_text("error : " + status + exception)
- self.over = True
- self.progressbar.destroy()
- self.hbox.pack_start (self.entry)
- self.entry.show()
diff --git a/pitivi/youtube_glib.py b/pitivi/youtube_glib.py
index ae643a4f..56276e8f 100644
--- a/pitivi/youtube_glib.py
+++ b/pitivi/youtube_glib.py
@@ -2,7 +2,7 @@
#
# youtube_glib.py
#
-# Copyright (c) 2010, Magnus Hoff
+# Copyright (c) 2010, Mathieu Duponchelle
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -26,105 +26,93 @@
"""
import gobject
-import gdata.youtube, gdata.youtube.service
-import threading
-from Queue import Queue
+import gdata.youtube
+import gdata.youtube.client
+import gdata.client
+import gdata.youtube.data
+from os.path import getsize
+import thread
+
+APP_NAME = 'PiTiVi'
+DEVELOPER_KEY = 'AI39si5DzhNX8NS0iEZl2Xg3uYj54QG57atp6v5w-FDikhMRYseN6MOtR8Bfvss4C0rTSqyJaTvgN8MHAszepFXz-zg4Zg3XNQ'
+CREATE_SESSION_URI = '/resumable/feeds/api/users/default/uploads'
+class ResumableYouTubeUploader(object):
+ def __init__(self, filepath, client):
-CLIENT_ID = 'PiTiVi'
-DEVELOPER_KEY = 'AI39si5DzhNX8NS0iEZl2Xg3uYj54QG57atp6v5w-FDikhMRYseN6MOtR8Bfvss4C0rTSqyJaTvgN8MHAszepFXz-zg4Zg3XNQ'
+ self.client = client
+ self.client.host = "uploads.gdata.youtube.com"
-class PipeWrapper:
- """Helper class to make gdata work with pipes"""
- def __init__(self, f):
- self._f = f
- def read(self, *args):
- return self._f.read(*args)
-
-
-def upload(yt_service, metadata, filename):
- text = metadata['category'] if metadata['category'] != None else 'Film'
- print text
- my_media_group = gdata.media.Group(
- keywords = gdata.media.Keywords(text=metadata["tags"]),
- title = gdata.media.Title(text=metadata["title"]),
- description = gdata.media.Description(description_type='plain', text=metadata["description"]),
- category = [
- gdata.media.Category(
- text = text,
- scheme = 'http://gdata.youtube.com/schemas/2007/categories.cat',
- ),
- ],
- private = gdata.media.Private() if metadata["private"] else None,
- )
- print metadata['category']
- video_entry = gdata.youtube.YouTubeVideoEntry(
- media = my_media_group,
- )
- new_entry = yt_service.InsertVideoEntry(video_entry, filename)
- print "c'est fait petit"
- return new_entry
-
-
-class YTThread(threading.Thread):
- def __init__(self, queue):
- threading.Thread.__init__(self)
- self._queue = queue
-
- def run(self):
- self._yt_service = gdata.youtube.service.YouTubeService()
- self._yt_service.source = CLIENT_ID
- self._yt_service.developer_key = DEVELOPER_KEY
- self._yt_service.client_id = CLIENT_ID
-
- self._running = True
- while self._running:
- task = self._queue.get()
- task()
-
- def yt_stop(self):
- self._running = False
+ self.f = open(filepath)
+ file_size = getsize(self.f.name)
- def authenticate_with_password(self, username, password, callback):
- try:
- self._yt_service.email = username
- self._yt_service.password = password
- self._yt_service.ProgrammaticLogin()
- gobject.idle_add(callback, ("good", self._yt_service.GetClientLoginToken()))
- except Exception, e:
- gobject.idle_add(callback, ("bad", e))
+ self.uploader = gdata.client.ResumableUploader(
+ self.client, self.f, "video/avi", file_size,
+ chunk_size=1024*64, desired_class=gdata.youtube.data.VideoEntry)
- def authenticate_with_token(self, token, callback):
- self._yt_service.SetClientLoginToken(token)
- gobject.idle_add(callback, token)
+ def __del__(self):
+ if self.uploader is not None:
+ self.uploader.file_handle.close()
- def upload(self, filename, metadata, callback):
- try:
- new_entry = upload(self._yt_service, metadata, filename())
- gobject.idle_add(callback, ("good", new_entry))
- except Exception, e:
- gobject.idle_add(callback, ("bad", e))
+ def uploadInManualChunks(self, new_entry, on_chunk_complete, callback):
+ uri = CREATE_SESSION_URI
+ self.run = True
-class AsyncYT:
- def __init__(self):
- self._queue = Queue()
- self._yt_thread = YTThread(self._queue)
- self._yt_thread.start()
+ self.uploader._InitSession(uri, entry=new_entry, headers={"X-GData-Key": "key=" + DEVELOPER_KEY,
+ "Slug" : None})
- def __del__(self):
- """This is an absolute last resort. Please call stop() manually instead"""
- self.stop()
+ start_byte = 0
+ entry = None
- def authenticate_with_password(self, username, password, callback):
- self._queue.put(lambda: self._yt_thread.authenticate_with_password(username, password, callback))
+ while not entry and self.run:
+ entry = self.uploader.UploadChunk(start_byte, self.uploader.file_handle.read(self.uploader.chunk_size))
+ start_byte += self.uploader.chunk_size
+ on_chunk_complete(start_byte, self.uploader.total_file_size)
+ callback(entry)
+
+
+class UploadBase():
+ def __init__(self):
+ self.uploader = None
- def authenticate_with_token(self, token, callback):
- self._queue.put(lambda: self._yt_thread.authenticate_with_token(token, callback))
+ def authenticate_with_password(self, username, password, callback):
+ pass
def upload(self, filename, metadata, callback):
- self._queue.put(lambda: self._yt_thread.upload(filename, metadata, callback))
+ pass
+
+class YTUploader(UploadBase):
+ def __init__(self):
+ UploadBase.__init__(self)
+
+ def authenticate_with_password(self, username, password, callback):
+ try:
+ self.client = gdata.youtube.client.YouTubeClient(source=APP_NAME)
+ self.client.ssl = False
+ self.client.http_client.debug = False
+ self.convert = None
+ self.client.ClientLogin(username, password, self.client.source)
+ gobject.idle_add(callback, ("good"))
+ except Exception, e:
+ gobject.idle_add(callback, ("bad", e))
- def stop(self):
- self._queue.put(self._yt_thread.yt_stop)
- self._yt_thread.join()
+ def upload(self, filename, metadata, progressCb, doneCb):
+ self.uploader = ResumableYouTubeUploader(filename, self.client)
+
+ text = metadata['category'] if metadata['category'] != None else 'Film'
+ print text
+ my_media_group = gdata.media.Group(
+ keywords = gdata.media.Keywords(text=metadata["tags"]),
+ title = gdata.media.Title(text=metadata["title"]),
+ description = gdata.media.Description(description_type='plain', text=metadata["description"]),
+ category = [
+ gdata.media.Category(
+ text = text,
+ scheme = 'http://gdata.youtube.com/schemas/2007/categories.cat',
+ ),
+ ],
+ private = gdata.media.Private() if metadata["private"] else None,
+ )
+ new_entry = gdata.youtube.YouTubeVideoEntry(media=my_media_group)
+ thread.start_new_thread(self.uploader.uploadInManualChunks, (new_entry, progressCb, doneCb))