From 8013824cb97b266c88a5bddb52aa96e263038035 Mon Sep 17 00:00:00 2001 From: flavio Date: Fri, 14 Dec 2012 11:49:36 +0000 Subject: Corrections --- diff --git a/aplay.py b/aplay.py index d418c5d..db3ebfe 100644 --- a/aplay.py +++ b/aplay.py @@ -11,6 +11,7 @@ # 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import gi gi.require_version('Gst', '1.0') from gi.repository import Gst @@ -23,18 +24,23 @@ import constants logger = logging.getLogger('record:aplay.py') def play(file, done_cb=None): + player.set_state(Gst.State.NULL) def eos_cb(bus, message): + bus.disconnect_by_func(eos_cb) player.set_state(Gst.State.NULL) + if done_cb is not None: done_cb() def error_cb(bus, message): + err, debug = message.parse_error() logger.error('play_pipe: %s %s' % (err, debug)) player.set_state(Gst.State.NULL) + if done_cb is not None: done_cb() diff --git a/button.py b/button.py index 6c9048e..74dc588 100644 --- a/button.py +++ b/button.py @@ -1,21 +1,27 @@ import gi from gi.repository import GObject from gi.repository import Gtk +from gi.repository import GdkPixbuf + from gettext import gettext as _ from sugar3.graphics.palette import Palette from sugar3.graphics.tray import TrayButton + import constants import utils class RecdButton(TrayButton): + __gsignals__ = { 'remove-requested': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, ()), 'copy-clipboard-requested': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, ()), } def __init__(self, recd): + super(RecdButton, self).__init__() + self._recd = recd self.set_icon_widget(self.get_image()) @@ -32,6 +38,7 @@ class RecdButton(TrayButton): self._add_copy_menu_item() def _add_copy_menu_item( self ): + if self._recd.buddy and not self._recd.downloadedFromBuddy: return @@ -41,27 +48,37 @@ class RecdButton(TrayButton): self._copy_menu_item.show() def get_recd(self): + return self._recd def get_image(self): + img = Gtk.Image() ipb = self._recd.getThumbPixbuf() + if self._recd.type == constants.TYPE_PHOTO: path = 'object-photo.svg' + elif self._recd.type == constants.TYPE_VIDEO: path = 'object-video.svg' + elif self._recd.type == constants.TYPE_AUDIO: path = 'object-audio.svg' pixbuf = utils.load_colored_svg(path, self._recd.colorStroke, self._recd.colorFill) + if ipb: - ipb.composite(pixbuf, 8, 8, ipb.get_width(), ipb.get_height(), 8, 8, 1, 1, gtk.gdk.INTERP_BILINEAR, 255) + ipb.composite(pixbuf, 8, 8, ipb.get_width(), + ipb.get_height(), 8, 8, 1, 1, GdkPixbuf.InterpType.BILINEAR, 255) + img.set_from_pixbuf(pixbuf) img.show() return img def cleanup(self): + self._rem_menu_item.disconnect(self._rem_menu_item_handler) + if self._copy_menu_item_handler != None: self._copy_menu_item.disconnect(self._copy_menu_item_handler) diff --git a/collab.py b/collab.py index d951f7c..78e8a17 100644 --- a/collab.py +++ b/collab.py @@ -19,30 +19,42 @@ from recorded import Recorded logger = logging.getLogger('collab') -class RecordCollab(object): +class RecordCollab(GObject.GObject): + def __init__(self, activity_obj, model): + + GObject.GObject.__init__(self) + self.activity = activity_obj self.model = model self._tube = None self._collab_timeout = 10000 def set_activity_shared(self): + self._setup() self._tubes_channel.OfferDBusTube(constants.DBUS_SERVICE, {}) def share_recd(self, recd): + if not self._tube: return + xmlstr = serialize.getRecdXmlMeshString(recd) self._tube.notifyBudsOfNewRecd(Instance.keyHashPrintable, xmlstr) def joined(self): + if not self.activity.get_shared_activity(): return + self._setup() - self._tubes_channel.ListTubes(reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) + self._tubes_channel.ListTubes( + reply_handler = self._list_tubes_reply_cb, + error_handler = self._list_tubes_error_cb) def request_download(self, recd): + if recd.meshDownloading: logger.debug("meshInitRoundRobin: we are in midst of downloading this file...") return @@ -53,14 +65,17 @@ class RecordCollab(object): self._req_recd_from_buddy(recd, recd.recorderHash, recd.recorderName) def _list_tubes_reply_cb(self, tubes): + for tube_info in tubes: self._new_tube_cb(*tube_info) @staticmethod def _list_tubes_error_cb(e): + logger.error('ListTubes() failed: %s', e) def _setup(self): + # sets up the tubes... if not self.activity.get_shared_activity(): logger.error('_setup: Failed to share or join activity') @@ -70,6 +85,7 @@ class RecordCollab(object): try: name, path = pservice.get_preferred_connection() self._connection = telepathy.client.Connection(name, path) + except: logger.error('_setup: Failed to get_preferred_connection') @@ -78,16 +94,21 @@ class RecordCollab(object): room = None tubes_chan = None text_chan = None + for channel_path in channel_paths: channel = telepathy.client.Channel(bus_name, channel_path) htype, handle = channel.GetHandle() + if htype == telepathy.HANDLE_TYPE_ROOM: - logger.debug('Found our room: it has handle#%d "%s"', handle, self._connection.InspectHandles(htype, [handle])[0]) + logger.debug('Found our room: it has handle#%d "%s"', + handle, self._connection.InspectHandles(htype, [handle])[0]) room = handle ctype = channel.GetChannelType() + if ctype == telepathy.CHANNEL_TYPE_TUBES: logger.debug('Found our Tubes channel at %s', channel_path) tubes_chan = channel + elif ctype == telepathy.CHANNEL_TYPE_TEXT: logger.debug('Found our Text channel at %s', channel_path) text_chan = channel @@ -95,6 +116,7 @@ class RecordCollab(object): if not room: logger.error("Presence service didn't create a room") return + if not text_chan: logger.error("Presence service didn't create a text channel") return @@ -102,7 +124,9 @@ class RecordCollab(object): # Make sure we have a Tubes channel - PS doesn't yet provide one if not tubes_chan: logger.debug("Didn't find our Tubes channel, requesting one...") - tubes_chan = self._connection.request_channel(telepathy.CHANNEL_TYPE_TUBES, telepathy.HANDLE_TYPE_ROOM, room, True) + tubes_chan = self._connection.request_channel( + telepathy.CHANNEL_TYPE_TUBES, + telepathy.HANDLE_TYPE_ROOM, room, True) self._tubes_channel = tubes_chan[telepathy.CHANNEL_TYPE_TUBES] self._text_channel = text_chan[telepathy.CHANNEL_INTERFACE_GROUP] @@ -110,13 +134,17 @@ class RecordCollab(object): self._tubes_channel.connect_to_signal('NewTube', self._new_tube_cb) def _new_tube_cb(self, id, initiator, type, service, params, state): - logger.debug('New tube: ID=%d initator=%d type=%d service=%s params=%r state=%d', id, initiator, type, service, params, state) + + logger.debug('New tube: ID=%d initator=%d type=%d service=%s params=%r state=%d', + id, initiator, type, service, params, state) if type != telepathy.TUBE_TYPE_DBUS or service != constants.DBUS_SERVICE: return if state == telepathy.TUBE_STATE_LOCAL_PENDING: self._tubes_channel.AcceptDBusTube(id) - tube_connection = TubeConnection(self._connection, self._tubes_channel, id, group_iface=self._text_channel) + + tube_connection = TubeConnection(self._connection, + self._tubes_channel, id, group_iface=self._text_channel) self._tube = RecordTube(tube_connection) self._tube.connect("new-recd", self._new_recd_cb) self._tube.connect("recd-request", self._recd_request_cb) @@ -124,17 +152,22 @@ class RecordCollab(object): self._tube.connect("recd-unavailable", self._recd_unavailable_cb) def _new_recd_cb(self, remote_object, recorder, xmlstr): + logger.debug('new_recd_cb') dom = None + try: dom = xml.dom.minidom.parseString(xmlstr) + except: logger.error('Unable to parse mesh xml') + if not dom: return recd = Recorded() recd = serialize.fillRecdFromNode(recd, dom.documentElement) + if not recd: logger.debug('_newRecdCb: recd is None. Unable to parse XML') return @@ -145,6 +178,7 @@ class RecordCollab(object): self.model.add_recd(recd) def _req_recd_from_buddy(self, recd, sender, nick): + recd.triedMeshBuddies.append(sender) recd.meshDownloadingFrom = sender recd.meshDownloadingFromNick = nick @@ -156,23 +190,29 @@ class RecordCollab(object): self._tube.requestRecdBits(Instance.keyHashPrintable, sender, recd.mediaMd5) def _next_round_robin_buddy(self, recd): + logger.debug('meshNextRoundRobinBuddy') + if recd.meshReqCallbackId: GObject.source_remove(recd.meshReqCallbackId) recd.meshReqCallbackId = 0 # delete any stub of a partially downloaded file path = recd.getMediaFilepath() + if path and os.path.exists(path): os.remove(path) good_buddy_obj = None buds = self.activity._shared_activity.get_joined_buddies() + for buddy_obj in buds: buddy = util.sha_data(buddy_obj.props.key) buddy = util.printable_hash(buddy) + if recd.triedMeshBuddies.count(buddy) > 0: logger.debug('mnrrb: weve already tried bud ' + buddy_obj.props.nick) + else: logger.debug('mnrrb: ask next buddy: ' + buddy_obj.props.nick) good_buddy_obj = buddy_obj @@ -182,6 +222,7 @@ class RecordCollab(object): buddy = util.sha_data(good_buddy_obj.props.key) buddy = util.printable_hash(buddy) self._req_recd_from_buddy(recd, buddy, good_buddy_obj.props.nick) + else: logger.debug('weve tried all buddies here, and no one has this recd') recd.meshDownloading = False @@ -190,9 +231,11 @@ class RecordCollab(object): self.activity.update_download_progress(recd) def _recd_request_cb(self, remote_object, remote_person, md5sum): + #if we are here, it is because someone has been told we have what they want. #we need to send them that thing, whatever that thing is recd = self.model.get_recd_by_md5(md5sum) + if not recd: logger.debug('_recdRequestCb: we dont have the recd they asked for') self._tube.unavailableRecd(md5sum, Instance.keyHashPrintable, remote_person) @@ -223,6 +266,7 @@ class RecordCollab(object): self._tube.broadcastRecd(recd.mediaMd5, path, remote_person) recd.meshUploading = False + #if you were deleted while uploading, now throw away those bits now if recd.deleted: recd.doDeleteRecorded(recd) @@ -232,21 +276,28 @@ class RecordCollab(object): if recd.downloadedFromBuddy: logger.debug('_meshCheckOnRecdRequest: recdRequesting.downloadedFromBuddy') + if recd.meshReqCallbackId: GObject.source_remove(recd.meshReqCallbackId) recd.meshReqCallbackId = 0 + return False + if recd.deleted: logger.debug('_meshCheckOnRecdRequest: recdRequesting.deleted') + if recd.meshReqCallbackId: GObject.source_remove(recd.meshReqCallbackId) recd.meshReqCallbackId = 0 + return False + if recd.meshDownloadingProgress: logger.debug('_meshCheckOnRecdRequest: recdRequesting.meshDownloadingProgress') #we've received some bits since last we checked, so keep waiting... they'll all get here eventually! recd.meshDownloadingProgress = False return True + else: logger.debug('_meshCheckOnRecdRequest: ! recdRequesting.meshDownloadingProgress') #that buddy we asked info from isn't responding; next buddy! @@ -255,19 +306,25 @@ class RecordCollab(object): return False def _recd_bits_arrived_cb(self, remote_object, md5sum, part, num_parts, bytes, sender): + recd = self.model.get_recd_by_md5(md5sum) + if not recd: logger.debug('_recdBitsArrivedCb: thx 4 yr bits, but we dont even have that photo') return + if recd.deleted: logger.debug('_recdBitsArrivedCb: thx 4 yr bits, but we deleted that photo') return + if recd.downloadedFromBuddy: logger.debug('_recdBitsArrivedCb: weve already downloadedFromBuddy') return + if not recd.buddy: logger.debug('_recdBitsArrivedCb: uh, we took this photo, so dont need your bits') return + if recd.meshDownloadingFrom != sender: logger.debug('_recdBitsArrivedCb: wrong bits ' + str(sender) + ", exp:" + str(recd.meshDownloadingFrom)) return @@ -285,6 +342,7 @@ class RecordCollab(object): if part > num_parts: logger.error('More parts than required have arrived') return + if part != num_parts: return @@ -294,6 +352,7 @@ class RecordCollab(object): recd.meshDownloading = False recd.meshDownlodingPercent = 1.0 recd.downloadedFromBuddy = True + if recd.type == constants.TYPE_AUDIO: path = recd.getMediaFilepath() bundle_path = os.path.join(Instance.instancePath, "audioBundle") @@ -319,21 +378,26 @@ class RecordCollab(object): self.activity.remote_recd_available(recd) def _recd_unavailable_cb(self, remote_object, md5sum, sender): + logger.debug('_recdUnavailableCb: sux, we want to see that photo') recd = self.model.get_recd_by_md5(md5sum) + if not recd: logger.debug('_recdUnavailableCb: actually, we dont even know about that one..') return + if recd.deleted: logger.debug('_recdUnavailableCb: actually, since we asked, we deleted.') return + if not recd.buddy: logger.debug('_recdUnavailableCb: uh, odd, we took that photo and have it already.') return + if recd.downloadedFromBuddy: logger.debug('_recdUnavailableCb: we already downloaded it... you might have been slow responding.') return + if recd.meshDownloadingFrom != sender: logger.debug('_recdUnavailableCb: we arent asking you for a copy now. slow response, pbly.') return - diff --git a/glive.py b/glive.py index 0747e86..48e28d1 100644 --- a/glive.py +++ b/glive.py @@ -39,8 +39,8 @@ import utils logger = logging.getLogger('glive') OGG_TRAITS = { - 0: { 'width': 160, 'height': 120, 'quality': 16 }, - 1: { 'width': 400, 'height': 300, 'quality': 16 } } + 0: { 'width': 160, 'height': 120, 'quality': 16 }, + 1: { 'width': 400, 'height': 300, 'quality': 16 } } class Glive: PHOTO_MODE_PHOTO = 0 @@ -50,6 +50,7 @@ class Glive: self.activity = activity_obj self.model = model + self._eos_cb = None self._has_camera = False self._can_limit_framerate = False @@ -68,6 +69,7 @@ class Glive: self._detect_camera() self._pipeline = Gst.Pipeline() + self._create_photobin() self._create_audiobin() self._create_videobin() @@ -86,7 +88,7 @@ class Glive: v4l2src = Gst.ElementFactory.make('v4l2src', 'v4l2src') - if v4l2src.props.device_name is None: + if v4l2src.get_property('device-name') is None: return self._has_camera = True @@ -97,15 +99,25 @@ class Glive: # can't find a way to do this (at this time, XO-1 cafe camera driver # doesn't support framerate changes, but gstreamer caps suggest # otherwise) + pipeline = Gst.Pipeline() + + # WARNING **: 0.10-style raw video caps are being created. Should be video/x-raw,format=(string) caps = Gst.Caps.from_string('video/x-raw-yuv,framerate=10/1') + camerafilter = Gst.ElementFactory.make("capsfilter", "capsfilter") + camerafilter.set_property("caps", caps) + fsink = Gst.ElementFactory.make('fakesink', 'fakesink') + pipeline.add(v4l2src) - # FIXME: TypeError: argument dest: Expected Gst.Element, but got gi.repository.Gst.Caps - #pipeline.add(caps) + pipeline.add(camerafilter) pipeline.add(fsink) - v4l2src.link(fsink) + + v4l2src.link(camerafilter) + camerafilter.link(fsink) + self._can_limit_framerate = pipeline.set_state(Gst.State.PAUSED) != Gst.StateChangeReturn.FAILURE + pipeline.set_state(Gst.State.NULL) def get_has_camera(self): @@ -115,7 +127,7 @@ class Glive: def _create_photobin(self): queue = Gst.ElementFactory.make("queue", "pbqueue") - queue.set_property("leaky", True) + queue.set_property("leaky", 1) queue.set_property("max-size-buffers", 1) colorspace = Gst.ElementFactory.make("videoconvert", "pbcolorspace") @@ -151,8 +163,9 @@ class Glive: if not hwdev_available: src.set_property("device", "default") + # WARNING **: 0.10-style raw audio caps are being created. Should be audio/x-raw,format=(string) srccaps = Gst.Caps.from_string("audio/x-raw-int,rate=16000,channels=1,depth=16") - + # guarantee perfect stream, important for A/V sync rate = Gst.ElementFactory.make("audiorate", 'audiorate') @@ -161,7 +174,7 @@ class Glive: # (possibly a gstreamer/ALSA bug -- even if it gets caught up, it # should be able to resync without problem) queue = Gst.ElementFactory.make("queue", "audioqueue") - queue.set_property("leaky", True) # prefer fresh data + queue.set_property("leaky", 1) # prefer fresh data queue.set_property("max-size-time", 5000000000) # 5 seconds queue.set_property("max-size-buffers", 500) queue.connect("overrun", self._log_queue_overrun) @@ -172,13 +185,14 @@ class Glive: sink.set_property("location", os.path.join(Instance.instancePath, "output.wav")) self._audiobin = Gst.Bin() + self._audiobin.add(src) self._audiobin.add(rate) self._audiobin.add(queue) self._audiobin.add(enc) self._audiobin.add(sink) - rate.link(rate) + src.link(rate) rate.link(queue) queue.link(enc) enc.link(sink) @@ -192,9 +206,9 @@ class Glive: scale = Gst.ElementFactory.make("videoscale", "vbscale") - scalecapsfilter = Gst.ElementFactory.make("capsfilter", "scalecaps") - + # WARNING **: 0.10-style raw video caps are being created. Should be video/x-raw,format=(string) scalecaps = Gst.Caps.from_string('video/x-raw-yuv,width=160,height=120') + scalecapsfilter = Gst.ElementFactory.make("capsfilter", "scalecaps") scalecapsfilter.set_property("caps", scalecaps) colorspace = Gst.ElementFactory.make("videoconvert", "vbcolorspace") @@ -208,6 +222,7 @@ class Glive: sink.set_property("location", os.path.join(Instance.instancePath, "output.ogg")) self._videobin = Gst.Bin() + self._videobin.add(queue) self._videobin.add(scale) self._videobin.add(scalecapsfilter) @@ -225,7 +240,7 @@ class Glive: pad = queue.get_static_pad("sink") self._videobin.add_pad(Gst.GhostPad.new("sink", pad)) - + def _create_xbin(self): scale = Gst.ElementFactory.make("videoscale", 'videoscale') @@ -237,6 +252,7 @@ class Glive: xsink.set_property("sync", False) self._xbin = Gst.Bin() + self._xbin.add(scale) self._xbin.add(cspace) self._xbin.add(xsink) @@ -260,13 +276,6 @@ class Glive: return src = Gst.ElementFactory.make("v4l2src", "camsrc") - - try: - # old gst-plugins-good does not have this property - src.set_property("queue-size", 2) - - except: - pass # if possible, it is important to place the framerate limit directly # on the v4l2src so that it gets communicated all the way down to the @@ -286,23 +295,27 @@ class Glive: # the FPS value. rate = Gst.ElementFactory.make("videorate", 'videorate') ratecaps = Gst.Caps.from_string('video/x-raw-yuv,framerate=10/1') - + tee = Gst.ElementFactory.make("tee", "tee") queue = Gst.ElementFactory.make("queue", "dispqueue") # prefer fresh frames - queue.set_property("leaky", True) + queue.set_property("leaky", 1) queue.set_property("max-size-buffers", 2) self._pipeline.add(src) self._pipeline.add(rate) self._pipeline.add(tee) self._pipeline.add(queue) + src.link(rate) - # FIXME: TypeError: argument dest: Expected Gst.Element, but got gi.repository.Gst.Caps + # FIXME: TypeError: Expected Gst.Element, but got GObjectMeta #rate.link(srccaps) + rate.link(tee) + # FIXME: TypeError: Expected Gst.Element, but got GObjectMeta #tee.link(ratecaps) + tee.link(queue) self._xvsink = Gst.ElementFactory.make("xvimagesink", "xsink") @@ -406,6 +419,7 @@ class Glive: return self._pipeline.get_state(0)[1] def stop_recording_audio(self): + # We should be able to simply pause and remove the audiobin, but # this seems to cause a gstreamer segfault. So we stop the whole # pipeline while manipulating it. @@ -423,16 +437,16 @@ class Glive: self.model.still_ready(self._audio_pixbuf) line = 'filesrc location=' + audio_path + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink' - audioline = gst.parse_launch(line) + audioline = Gst.parse_launch(line) taglist = self._get_tags(constants.TYPE_AUDIO) if self._audio_pixbuf: pixbuf_b64 = utils.getStringFromPixbuf(self._audio_pixbuf) - taglist[Gst.TAG_EXTENDED_COMMENT] = "coverart=" + pixbuf_b64 + #taglist[Gst.TAG_EXTENDED_COMMENT] = "coverart=" + pixbuf_b64 vorbis_enc = audioline.get_by_name('audioVorbisenc') - vorbis_enc.merge_tags(taglist, Gst.TAG_MERGE_REPLACE_ALL) + #vorbis_enc.merge_tags(taglist, Gst.TagMergeMode.REPLACE_ALL) audioFilesink = audioline.get_by_name('audioFilesink') audioOggFilepath = os.path.join(Instance.instancePath, "output.ogg") @@ -442,11 +456,13 @@ class Glive: audioBus.add_signal_watch() self._audio_transcode_handler = audioBus.connect('message', self._onMuxedAudioMessageCb, audioline) self._transcode_id = GObject.timeout_add(200, self._transcodeUpdateCb, audioline) + audioline.set_state(Gst.State.PLAYING) def _get_tags(self, type): tl = Gst.TagList() + """ tl[Gst.TAG_ARTIST] = self.model.get_nickname() tl[Gst.TAG_COMMENT] = "olpc" #this is unfortunately, unreliable @@ -457,7 +473,7 @@ class Glive: # Translators: photo by photographer, e.g. "Photo by Mary" tl[Gst.TAG_TITLE] = _('%(type)s by %(name)s') % {'type': stringType, - 'name': self.model.get_nickname()} + 'name': self.model.get_nickname()}""" return tl def _take_photo(self, photo_mode): @@ -510,10 +526,11 @@ class Glive: return self._ogg_quality = quality + self._config_videobin(OGG_TRAITS[quality]['quality'], OGG_TRAITS[quality]['width'], OGG_TRAITS[quality]['height']) - + # If we use pad blocking and adjust the pipeline on-the-fly, the # resultant video has bad A/V sync :( # If we pause the pipeline while adjusting it, the A/V sync is better @@ -552,7 +569,7 @@ class Glive: self._eos_cb = self._video_eos self._pipeline.get_by_name('camsrc').send_event(Gst.Event.new_eos()) self._audiobin.get_by_name('absrc').send_event(Gst.Event.new_eos()) - + def _video_eos(self): self._pipeline.set_state(Gst.State.NULL) @@ -561,7 +578,7 @@ class Glive: self._pipeline.remove(self._audiobin) self.model.shutter_sound() - + if len(self._thumb_pipes) > 0: thumbline = self._thumb_pipes[-1] thumbline.get_by_name('thumb_fakesink').disconnect(self._thumb_handoff_handler) @@ -572,10 +589,12 @@ class Glive: # FIXME: inform model of failure? return - line = 'filesrc location=' + ogg_path + ' name=thumbFilesrc ! oggdemux name=thumbOggdemux ! theoradec name=thumbTheoradec ! tee name=thumb_tee ! queue name=thumb_queue ! ffmpegcolorspace name=thumbFfmpegcolorspace ! jpegenc name=thumbJPegenc ! fakesink name=thumb_fakesink' + line = 'filesrc location=' + ogg_path + ' name=thumbFilesrc ! oggdemux name=thumbOggdemux ! theoradec name=thumbTheoradec ! tee name=thumb_tee ! queue name=thumb_queue ! videoconvert name=thumbFfmpegcolorspace ! jpegenc name=thumbJPegenc ! fakesink name=thumb_fakesink' + thumbline = Gst.parse_launch(line) + thumb_queue = thumbline.get_by_name('thumb_queue') - thumb_queue.set_property("leaky", True) + thumb_queue.set_property("leaky", 1) thumb_queue.set_property("max-size-buffers", 1) thumb_tee = thumbline.get_by_name('thumb_tee') thumb_fakesink = thumbline.get_by_name('thumb_fakesink') @@ -584,18 +603,19 @@ class Glive: self._thumb_pipes.append(thumbline) self._thumb_exposure_open = True thumbline.set_state(Gst.State.PLAYING) - + def copyThumbPic(self, fsink, buffer, pad, user_data=None): if not self._thumb_exposure_open: return self._thumb_exposure_open = False - loader = GdkPibuf.PixbufLoader.new_with_mime_type("image/jpeg") - loader.write(buffer) - loader.close() - self.thumbBuf = loader.get_pixbuf() - self.model.still_ready(self.thumbBuf) + #loader = GdkPixbuf.PixbufLoader.new_with_mime_type("image/jpeg") + # FIXME: TypeError: Must be sequence, not Buffer + #loader.write(buffer) + #loader.close() + #self.thumbBuf = loader.get_pixbuf() + #self.model.still_ready(self.thumbBuf) self._thumb_element('thumb_tee').unlink(self._thumb_element('thumb_queue')) @@ -604,9 +624,10 @@ class Glive: muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") muxline = Gst.parse_launch('filesrc location=' + str(oggFilepath) + ' name=muxVideoFilesrc ! oggdemux name=muxOggdemux ! theoraparse ! oggmux name=muxOggmux ! filesink location=' + str(muxFilepath) + ' name=muxFilesink filesrc location=' + str(wavFilepath) + ' name=muxAudioFilesrc ! wavparse name=muxWavparse ! audioconvert name=muxAudioconvert ! vorbisenc name=muxVorbisenc ! muxOggmux.') + taglist = self._get_tags(constants.TYPE_VIDEO) vorbis_enc = muxline.get_by_name('muxVorbisenc') - vorbis_enc.merge_tags(taglist, Gst.TAG_MERGE_REPLACE_ALL) + #vorbis_enc.merge_tags(taglist, Gst.TagMergeMode.REPLACE_ALL) muxBus = muxline.get_bus() muxBus.add_signal_watch() @@ -630,13 +651,13 @@ class Glive: def _query_position(self, pipe): try: - position, format = pipe.query_position(Gst.FORMAT_TIME) + position, format = pipe.query_position(Gst.Format.TIME) except: - position = Gste.CLOCK_TIME_NONE + position = Gst.CLOCK_TIME_NONE try: - duration, format = pipe.query_duration(Gst.FORMAT_TIME) + duration, format = pipe.query_duration(Gst.Format.TIME) except: duration = Gst.CLOCK_TIME_NONE @@ -645,7 +666,7 @@ class Glive: def _onMuxedVideoMessageCb(self, bus, message, pipe): - if message.type != Gst.MESSAGE_EOS: + if message.type != Gst.MessageType.EOS: return True GObject.source_remove(self._video_transcode_handler) @@ -667,7 +688,7 @@ class Glive: def _onMuxedAudioMessageCb(self, bus, message, pipe): - if message.type != Gst.MESSAGE_EOS: + if message.type != Gst.MessageType.EOS: return True GObject.source_remove(self._audio_transcode_handler) @@ -682,6 +703,7 @@ class Glive: oggFilepath = os.path.join(Instance.instancePath, "output.ogg") os.remove( wavFilepath ) self.model.save_audio(oggFilepath, self._audio_pixbuf) + return False def _bus_message_handler(self, bus, message): diff --git a/gplay.py b/gplay.py index 5cc34f3..52d34cf 100644 --- a/gplay.py +++ b/gplay.py @@ -74,17 +74,19 @@ class Gplay(GObject.GObject): duration = self._player.query_duration(Gst.Format.TIME, None)[0] location = duration * (position / 100) - event = gst.event_new_seek( + event = Gst.Event.new_seek( 1.0, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE, - gst.SeekType.SET, location, - gst.SeekType.NONE, 0) + Gst.SeekType.SET, location, + Gst.SeekType.NONE, 0) res = self._player.send_event(event) if res: - self._player.set_new_stream_time(0L) + # FIXME: 'Pipeline' object has no attribute 'new_stream_time' + #self._player.set_new_stream_time(0L) + pass def pause(self): @@ -112,7 +114,7 @@ class Gplay(GObject.GObject): position = self._player.query_position(Gst.Format.TIME)[0] duration = self._player.query_duration(Gst.Format.TIME)[0] - except gst.QueryError: + except: return True value = (float(position) / float(duration)) * 100.0 diff --git a/hw.py b/hw.py index d678e37..0f9f325 100644 --- a/hw.py +++ b/hw.py @@ -18,20 +18,27 @@ import os def _get_dmi(node): + path = os.path.join('/sys/class/dmi/id', node) + try: return open(path).readline().strip() + except: return None def get_xo_version(): + if _get_dmi('product_name') != 'XO': return 0 + version = _get_dmi('product_version') + if version == '1': return 1 + if version == '1.5': return 1.5 + else: return 0 - diff --git a/iconcombobox.py b/iconcombobox.py index 077fa7a..18d7f82 100644 --- a/iconcombobox.py +++ b/iconcombobox.py @@ -24,10 +24,11 @@ from gi.repository import Gtk from sugar3.graphics.combobox import ComboBox from sugar3.graphics import style - class IconComboBox(Gtk.ToolItem): - def __init__(self, icon_name, **kwargs): - Gtk.ToolItem.__init__(self, **kwargs) + + def __init__(self, icon_name): + + Gtk.ToolItem.__init__(self) self.icon_name = icon_name self.set_border_width(style.DEFAULT_PADDING) @@ -39,4 +40,5 @@ class IconComboBox(Gtk.ToolItem): self.add(self.combo) def append_item(self, i, text): + self.combo.append_item(i, text, icon_name=self.icon_name) diff --git a/instance.py b/instance.py index 259bc2d..61d8747 100644 --- a/instance.py +++ b/instance.py @@ -4,6 +4,7 @@ from sugar3 import profile from sugar3 import util class Instance: + key = profile.get_pubkey() keyHash = util.sha_data(key) @@ -12,11 +13,13 @@ class Instance: instancePath = None def __init__(self, ca): + self.__class__.instancePath = os.path.join(ca.get_activity_root(), "instance") recreateTmp() def recreateTmp(): + if (not os.path.exists(Instance.instancePath)): os.makedirs(Instance.instancePath) diff --git a/mediaview.py b/mediaview.py index 4832a4a..afc7ada 100644 --- a/mediaview.py +++ b/mediaview.py @@ -21,6 +21,7 @@ class XoIcon(Gtk.Image): super(XoIcon, self).__init__() def set_colors(self, stroke, fill): + pixbuf = utils.load_colored_svg('xo-guy.svg', stroke, fill) self.set_from_pixbuf(pixbuf) @@ -296,13 +297,6 @@ class MediaView(Gtk.EventBox): MODE_STILL = 3 MODE_INFO_PHOTO = 4 MODE_INFO_VIDEO = 5 - - @staticmethod - def _raise_widget(widget): - - widget.show() - widget.realize() - widget.get_property('window').raise_() def __init__(self): @@ -377,13 +371,13 @@ class MediaView(Gtk.EventBox): def _show_controls(self): if self._mode in (MediaView.MODE_LIVE, MediaView.MODE_VIDEO, MediaView.MODE_PHOTO, MediaView.MODE_STILL): - self._raise_widget(self._full_button) + self._full_button.show() if self._mode in (MediaView.MODE_VIDEO, MediaView.MODE_PHOTO): - self._raise_widget(self._info_button) + self._info_button.show() if self._mode in (MediaView.MODE_VIDEO, MediaView.MODE_PHOTO): - self._raise_widget(self._video) + self._video.show() def _hide_controls(self): diff --git a/model.py b/model.py index be1c2c2..771f08f 100644 --- a/model.py +++ b/model.py @@ -125,6 +125,7 @@ class Model: self._mode = mode self.activity.remove_all_thumbnails() + for recd in self.mediaHashs[mode]: self.activity.add_thumbnail(recd, True) @@ -272,6 +273,7 @@ class Model: return True def do_shutter(self): + # if recording, stop if self._state == constants.STATE_RECORDING: self._stop_media_capture() @@ -291,6 +293,7 @@ class Model: # called from gstreamer thread def still_ready(self, pixbuf): + GObject.idle_add(self.activity.show_still, pixbuf) def add_recd(self, recd): @@ -305,6 +308,7 @@ class Model: # called from gstreamer thread def save_photo(self, pixbuf): + recd = self.createNewRecorded(constants.TYPE_PHOTO) imgpath = os.path.join(Instance.instancePath, recd.mediaFilename) @@ -315,9 +319,8 @@ class Model: #now that we've saved both the image and its pixbuf, we get their md5s self.createNewRecordedMd5Sums( recd ) - GObject.idle_add(self.add_recd, recd, priority=GObject.PRIORITY_HIGH) - GObject.idle_add(self.activity.set_shutter_sensitive, - True, priority=GObject.PRIORITY_HIGH) + GObject.idle_add(self.add_recd, recd) + GObject.idle_add(self.activity.set_shutter_sensitive, True) # called from gstreamer thread def save_video(self, path, still): @@ -330,7 +333,7 @@ class Model: self.createNewRecordedMd5Sums( recd ) - GObject.idle_add(self.add_recd, recd, priority=GObject.PRIORITY_HIGH) + GObject.idle_add(self.add_recd, recd) GObject.idle_add(self.set_state, constants.STATE_READY) def save_audio(self, path, still): @@ -349,7 +352,7 @@ class Model: self.createNewRecordedMd5Sums( recd ) - GObject.idle_add(self.add_recd, recd, priority=GObject.PRIORITY_HIGH) + GObject.idle_add(self.add_recd, recd) GObject.idle_add(self.set_state, constants.STATE_READY) def _playback_status_changed(self, widget, status, value): @@ -384,12 +387,15 @@ class Model: self.activity.set_paused(False) def start_seek(self): + self.gplay.pause() def do_seek(self, position): + self.gplay.seek(position) def end_seek(self): + self.gplay.play() def get_recd_by_md5(self, md5): @@ -431,6 +437,7 @@ class Model: recd.colorFill = color.get_fill_color() logger.debug('createNewRecorded: ' + str(recd)) + return recd def createNewRecordedMd5Sums( self, recd ): diff --git a/record.py b/record.py index 271a7a5..302a783 100644 --- a/record.py +++ b/record.py @@ -132,7 +132,7 @@ class Record(activity.Activity): def _visibility_changed(self, widget, event): self.model.set_visible(event.state != Gdk.VisibilityState.FULLY_OBSCURED) - + def _shared_cb(self, activity): self.model.collab.set_activity_shared() @@ -389,7 +389,8 @@ class Record(activity.Activity): self._title_label.show() self._record_container.set_title_visible(True) - func(recd.recorderName, recd.colorStroke, recd.colorFill, utils.getDateString(recd.time), recd.tags) + func(recd.recorderName, recd.colorStroke, + recd.colorFill, utils.getDateString(recd.time), recd.tags) def _media_view_full_clicked(self, widget): @@ -428,6 +429,7 @@ class Record(activity.Activity): def set_state(self, state): radio_state = (state == constants.STATE_READY) + for item in (self._photo_button, self._audio_button, self._video_button): if item: item.set_sensitive(radio_state) @@ -504,7 +506,8 @@ class Record(activity.Activity): media_path = recd.getMediaFilepath() tmp_path = utils.getUniqueFilepath(media_path, 0) shutil.copyfile(media_path, tmp_path) - Gtk.Clipboard().set_with_data([('text/uri-list', 0, 0)], self._clipboard_get, self._clipboard_clear, tmp_path) + Gtk.Clipboard().set_with_data([('text/uri-list', 0, 0)], + self._clipboard_get, self._clipboard_clear, tmp_path) def _clipboard_get(self, clipboard, selection_data, info, path): @@ -543,7 +546,6 @@ class Record(activity.Activity): for child in self._thumb_tray.get_children(): self._remove_thumbnail(child) - pass def show_still(self, pixbuf): @@ -786,6 +788,8 @@ class CountdownImage(Gtk.Image): w = 55 h = w + # FIXME: No more Pixmap + """ pixmap = GdK.Pixmap(self.get_window(), w, h, -1) ctx = pixmap.cairo_create() ctx.rectangle(0, 0, w, h) @@ -815,7 +819,7 @@ class CountdownImage(Gtk.Image): ctx.translate(-3, 0) pctx.show_layout(play) - return pixmap + return pixmap""" def set_value(self, num): @@ -824,7 +828,6 @@ class CountdownImage(Gtk.Image): self.set_from_pixmap(self._countdown_images[num], None) - class ShutterButton(Gtk.Button): def __init__(self): @@ -864,7 +867,6 @@ class ShutterButton(Gtk.Button): self.set_image(self._rec_red_image) - class PlayButton(Gtk.Button): def __init__(self): @@ -891,7 +893,6 @@ class PlayButton(Gtk.Button): self.set_image(self._pause_image) - class RecordControl(): def __init__(self, toolbar): @@ -971,7 +972,6 @@ class RecordControl(): self.quality.combo.set_active(idx) - class TimerCombo(IconComboBox): TIMERS = (0, 5, 10) @@ -1007,7 +1007,6 @@ class TimerCombo(IconComboBox): return ngettext('%s second', '%s seconds', x) % x - class DurationCombo(IconComboBox): DURATIONS = (2, 4, 6) diff --git a/recorded.py b/recorded.py index 8343b35..3ed5ffc 100644 --- a/recorded.py +++ b/recorded.py @@ -29,7 +29,9 @@ import utils import serialize class Recorded: + def __init__( self ): + self.type = -1 self.time = None self.recorderName = None @@ -75,27 +77,29 @@ class Recorded: self.deleted = False - def setTitle( self, newTitle ): + if self.title == newTitle: return + self.title = newTitle self.metaChange = True - def setTags( self, newTags ): + self.tags = newTags self.metaChange = True - def isClipboardCopyable( self ): + copyme = True + if (self.buddy): if (not self.downloadedFromBuddy): return False + return copyme - #scenarios: #launch, your new thumb -- Journal/session #launch, your new media -- Journal/session @@ -107,57 +111,69 @@ class Recorded: #relaunch, their old media -- datastoreObject->file (hold onto the datastore object, delete if deleted) | ([request->]) Journal/session/buddy def getThumbPixbuf( self ): + thumbFilepath = self.getThumbFilepath() + if thumbFilepath and os.path.isfile(thumbFilepath): return GdkPixbuf.Pixbuf.new_from_file(thumbFilepath) + else: return None - def getThumbFilepath( self ): + if not self.thumbFilename: return None + return os.path.join(Instance.instancePath, self.thumbFilename) def make_thumb_path(self): + thumbFilename = self.mediaFilename + "_thumb.jpg" thumbFilepath = os.path.join(Instance.instancePath, thumbFilename) thumbFilepath = utils.getUniqueFilepath(thumbFilepath, 0) self.thumbFilename = os.path.basename(thumbFilepath) + return self.getThumbFilepath() def getAudioImagePixbuf( self ): + audioPixbuf = None if self.audioImageFilename == None: audioPixbuf = self.getThumbPixbuf() + else: audioFilepath = self.getAudioImageFilepath() + if (audioFilepath != None): audioPixbuf = GdkPixbuf.Pixbuf.new_from_file(audioFilepath) return audioPixbuf - def getAudioImageFilepath( self ): + if (self.audioImageFilename != None): audioFilepath = os.path.join(Instance.instancePath, self.audioImageFilename) return os.path.abspath(audioFilepath) + else: return self.getThumbFilepath() - def getMediaFilepath(self): + if (self.datastoreId == None): if (not self.buddy): #just taken by you, so it is in the tempSessionDir mediaFilepath = os.path.join(Instance.instancePath, self.mediaFilename) return os.path.abspath(mediaFilepath) + else: if (self.downloadedFromBuddy): #the user has requested the high-res version, and it has downloaded mediaFilepath = os.path.join(Instance.instancePath, self.mediaFilename) return os.path.abspath(mediaFilepath) + else: if self.mediaFilename == None: #creating a new filepath, probably just got here from the mesh @@ -167,6 +183,7 @@ class Recorded: self.mediaFilename = os.path.basename(recdPath) mediaFilepath = os.path.join(Instance.instancePath, self.mediaFilename) return os.path.abspath(mediaFilepath) + else: mediaFilepath = os.path.join(Instance.instancePath, self.mediaFilename) return os.path.abspath(mediaFilepath) @@ -175,6 +192,7 @@ class Recorded: #first, get the datastoreObject and hold the reference in this Recorded instance if (self.datastoreOb == None): self.datastoreOb = serialize.getMediaFromDatastore( self ) + if (self.datastoreOb == None): print("RecordActivity error -- unable to get datastore object in getMediaFilepath") return None diff --git a/recordtube.py b/recordtube.py index b3c82d2..c443070 100644 --- a/recordtube.py +++ b/recordtube.py @@ -25,102 +25,131 @@ class RecordTube(ExportedGObject): def __init__(self, tube): + super(RecordTube, self).__init__(tube, constants.DBUS_PATH) + self.tube = tube - self.idNotify = self.tube.add_signal_receiver(self._newRecdTubeCb, 'notifyBudsOfNewRecd', constants.DBUS_IFACE, path=constants.DBUS_PATH, sender_keyword='sender') - self.idRequest = self.tube.add_signal_receiver(self._reqRecdTubeCb, 'requestRecdBits', constants.DBUS_IFACE, path=constants.DBUS_PATH, sender_keyword='sender') - self.idBroadcast = self.tube.add_signal_receiver(self._getRecdTubeCb, 'broadcastRecdBits', constants.DBUS_IFACE, path=constants.DBUS_PATH, sender_keyword='sender', byte_arrays=True) - self.idUnavailable = self.tube.add_signal_receiver(self._unavailableRecdTubeCb, 'unavailableRecd', constants.DBUS_IFACE, path=constants.DBUS_PATH, sender_keyword='sender') - + self.idNotify = self.tube.add_signal_receiver( + self._newRecdTubeCb, 'notifyBudsOfNewRecd', + constants.DBUS_IFACE, path=constants.DBUS_PATH, + sender_keyword='sender') + + self.idRequest = self.tube.add_signal_receiver( + self._reqRecdTubeCb, 'requestRecdBits', + constants.DBUS_IFACE, path=constants.DBUS_PATH, + sender_keyword='sender') + + self.idBroadcast = self.tube.add_signal_receiver( + self._getRecdTubeCb, 'broadcastRecdBits', + constants.DBUS_IFACE, path=constants.DBUS_PATH, + sender_keyword='sender', byte_arrays=True) + + self.idUnavailable = self.tube.add_signal_receiver( + self._unavailableRecdTubeCb, 'unavailableRecd', + constants.DBUS_IFACE, path=constants.DBUS_PATH, + sender_keyword='sender') @signal(dbus_interface=constants.DBUS_IFACE, signature='ss') #dual s for 2x strings def notifyBudsOfNewRecd(self, recorder, recdXml): + logger.debug('Ive taken a new pho-ideo-audio! I hereby send you an xml thumb of said media via this interface.') - def _newRecdTubeCb(self, recorder, recdXml, sender=None): + def _newRecdTubeCb(self, recorder, recdXml, sender = None): + logger.debug("_newRecdTubeCb from " + recorder ) + if sender == self.tube.get_unique_name(): logger.debug("_newRecdTubeCb: sender is my bus name, so ignore my own signal") return + elif (recorder == Instance.keyHashPrintable): logger.debug('_newRecdTubeCb: excuse me? you are asking me to share a photo with myself?') return self.emit( "new-recd", str(recorder), str(recdXml) ) - @signal(dbus_interface=constants.DBUS_IFACE, signature='sss') #triple s for 3x strings def requestRecdBits(self, whoWantsIt, whoTheyWantItFrom, recdMd5sumOfIt ): + logger.debug('I am requesting a high-res version of someones media.') - - def _reqRecdTubeCb(self, whoWantsIt, whoTheyWantItFrom, recdMd5sumOfIt, sender=None): + def _reqRecdTubeCb(self, whoWantsIt, whoTheyWantItFrom, recdMd5sumOfIt, sender = None): + if sender == self.tube.get_unique_name(): logger.debug("_reqRecdTubeCb: sender is my bus name, so ignore my own signal") return + elif (whoWantsIt == Instance.keyHashPrintable): logger.debug('_reqRecdTubeCb: excuse me? you are asking me to share a photo with myself?') return + elif (whoTheyWantItFrom != Instance.keyHashPrintable): logger.debug('_reqRecdTubeCb: ive overhead someone wants a photo, but not from me') return self.emit( "recd-request", str(whoWantsIt), str(recdMd5sumOfIt) ) - def broadcastRecd(self, md5, filepath, sendThisTo ): + size = os.path.getsize(filepath) f = open(filepath) chunk_size = 1000 chunks = size / chunk_size + if (size%chunk_size != 0): chunks += 1 for chunk in range(chunks): bytes = f.read(chunk_size) + if chunk == 0: logger.debug("sending " + str(chunk+1) + " of " + str(chunks) + " to " + sendThisTo ) + if chunk == chunks-1: logger.debug("sending " + str(chunk+1) + " of " + str(chunks) + " to " + sendThisTo ) + self.broadcastRecdBits(md5, chunk+1, chunks, bytes, sendThisTo, Instance.keyHashPrintable) f.close() return True - @signal(dbus_interface=constants.DBUS_IFACE, signature='suuayss') def broadcastRecdBits(self, md5, part, numparts, bytes, sendTo, fromWho ): pass - def _getRecdTubeCb(self, md5, part, numparts, bytes, sentTo, fromWho, sender=None): + if sender == self.tube.get_unique_name(): #record.Record.log.debug("_reqRecdTubeCb: sender is my bus name, so ignore my own signal") return + if (fromWho == Instance.keyHashPrintable): #record.Record.log.debug('_getRecdTubeCb: i dont want bits from meself, thx anyway. schizophrenic?') return + if (sentTo != Instance.keyHashPrintable): #record.Record.log.debug('_getRecdTubeCb: ive overhead someone sending bits, but not to me!') return self.emit( "recd-bits-arrived", md5, part, numparts, bytes, fromWho ) - @signal(dbus_interface=constants.DBUS_IFACE, signature='sss') #triple s for 3x strings def unavailableRecd(self, md5sumOfIt, whoDoesntHaveIt, whoAskedForIt): + logger.debug('unavailableRecd: id love to share this photo, but i am without a copy meself chum') - - def _unavailableRecdTubeCb( self, md5sumOfIt, whoDoesntHaveIt, whoAskedForIt, sender=None): + def _unavailableRecdTubeCb( self, md5sumOfIt, whoDoesntHaveIt, whoAskedForIt, sender = None): + if sender == self.tube.get_unique_name(): logger.debug("_unavailableRecdTubeCb: sender is my bus name, so ignore my own signal") return + if whoDoesntHaveIt == Instance.keyHashPrintable: logger.debug('_unavailableRecdTubeCb: yes, i know i dont have it, i just told you/me/us.') return + if whoAskedForIt != Instance.keyHashPrintable: logger.debug('_unavailableRecdTubeCb: ive overheard someone doesnt have a photo, but i didnt ask for that one anyways') return diff --git a/serialize.py b/serialize.py index bd451cd..a52c6ca 100644 --- a/serialize.py +++ b/serialize.py @@ -16,29 +16,38 @@ import utils logger = logging.getLogger('serialize') def fillMediaHash(doc, mediaHashs): + for key, value in constants.MEDIA_INFO.items(): + recdElements = doc.documentElement.getElementsByTagName(value['name']) + for el in recdElements: _loadMediaIntoHash( el, mediaHashs[key] ) def _loadMediaIntoHash(el, hash): + addToHash = True recd = recorded.Recorded() recd = fillRecdFromNode(recd, el) + if recd: if recd.datastoreId: #quickly check: if you have a datastoreId that the file hasn't been deleted, #cause if you do, we need to flag your removal #2904 trac recd.datastoreOb = getMediaFromDatastore( recd ) + if not recd.datastoreOb: addToHash = False + else: #name might have been changed in the journal, so reflect that here if recd.title != recd.datastoreOb.metadata['title']: recd.setTitle(recd.datastoreOb.metadata['title']) + if recd.tags != recd.datastoreOb.metadata['tags']: recd.setTags(recd.datastoreOb.metadata['tags']) + if recd.buddy: recd.downloadedFromBuddy = True @@ -48,6 +57,7 @@ def _loadMediaIntoHash(el, hash): hash.append(recd ) def getMediaFromDatastore(recd): + if not recd.datastoreId: return None @@ -56,12 +66,15 @@ def getMediaFromDatastore(recd): return recd.datastoreOb mediaObject = None + try: mediaObject = datastore.get(recd.datastoreId) + finally: return mediaObject def removeMediaFromDatastore(recd): + #before this method is called, the media are removed from the file if not recd.datastoreId or not recd.datastoreOb: return @@ -72,11 +85,13 @@ def removeMediaFromDatastore(recd): recd.datastoreId = None recd.datastoreOb = None + finally: #todo: add error message here pass def fillRecdFromNode(recd, el): + if el.getAttributeNode('type'): recd.type = int(el.getAttribute('type')) @@ -91,6 +106,7 @@ def fillRecdFromNode(recd, el): if el.getAttributeNode('tags'): recd.tags = el.getAttribute('tags') + else: recd.tags = "" @@ -119,6 +135,7 @@ def fillRecdFromNode(recd, el): recd.thumbBytes = el.getAttribute('thumbBytes') bt = el.getAttributeNode('base64Thumb') + if bt: try: thumbPath = os.path.join(Instance.instancePath, "datastoreThumb.jpg") @@ -127,10 +144,12 @@ def fillRecdFromNode(recd, el): thumbImg.save(thumbPath, "jpeg", {"quality":"85"} ) recd.thumbFilename = os.path.basename(thumbPath) logger.debug("saved thumbFilename") + except: logger.error("unable to getRecdBase64Thumb") ai = el.getAttributeNode('audioImage') + if (not ai == None): try: audioImagePath = os.path.join(Instance.instancePath, "audioImage.png") @@ -139,10 +158,12 @@ def fillRecdFromNode(recd, el): audioImage.save(audioImagePath, "png", {} ) recd.audioImageFilename = os.path.basename(audioImagePath) logger.debug("loaded audio image and set audioImageFilename") + except: logger.error("unable to load audio image") datastoreNode = el.getAttributeNode('datastoreId') + if datastoreNode: recd.datastoreId = datastoreNode.nodeValue @@ -150,6 +171,7 @@ def fillRecdFromNode(recd, el): def getRecdXmlMeshString(recd): + impl = getDOMImplementation() recdXml = impl.createDocument(None, 'recd', None) root = recdXml.documentElement @@ -160,10 +182,12 @@ def getRecdXmlMeshString(recd): return writer.getvalue() def _addRecdXmlAttrs(el, recd, forMeshTransmit): + el.setAttribute('type', str(recd.type)) if (recd.type == constants.TYPE_AUDIO) and (not forMeshTransmit): aiPixbuf = recd.getAudioImagePixbuf() + if aiPixbuf: aiPixbufString = str(utils.getStringFromPixbuf(aiPixbuf)) el.setAttribute('audioImage', aiPixbufString) @@ -190,19 +214,23 @@ def _addRecdXmlAttrs(el, recd, forMeshTransmit): el.setAttribute('version', '54') pixbuf = recd.getThumbPixbuf() + if pixbuf: thumb64 = str(utils.getStringFromPixbuf(pixbuf)) el.setAttribute('base64Thumb', thumb64) def saveMediaHash(mediaHashs, activity): + impl = getDOMImplementation() album = impl.createDocument(None, 'album', None) root = album.documentElement #flag everything for saving... atLeastOne = False + for type, value in constants.MEDIA_INFO.items(): typeName = value['name'] + for recd in mediaHashs[type]: recd.savedXml = False recd.savedMedia = False @@ -212,6 +240,7 @@ def saveMediaHash(mediaHashs, activity): if atLeastOne: for type, value in constants.MEDIA_INFO.items(): typeName = value['name'] + for recd in mediaHashs[type]: mediaEl = album.createElement(typeName) root.appendChild(mediaEl) @@ -220,14 +249,17 @@ def saveMediaHash(mediaHashs, activity): return album def _saveMedia(el, recd, activity): + if recd.buddy == True and recd.datastoreId == None and not recd.downloadedFromBuddy: recd.savedMedia = True _saveXml(el, recd) + else: recd.savedMedia = False _saveMediaToDatastore(el, recd, activity) def _saveXml(el, recd): + _addRecdXmlAttrs(el, recd, False) recd.savedXml = True @@ -240,9 +272,11 @@ def _saveMediaToDatastore(el, recd, activity): #However, they might have changed the name of the file if recd.metaChange: recd.datastoreOb = getMediaFromDatastore(recd) + if recd.datastoreOb.metadata['title'] != recd.title: recd.datastoreOb.metadata['title'] = recd.title datastore.write(recd.datastoreOb) + if recd.datastoreOb.metadata['tags'] != recd.tags: recd.datastoreOb.metadata['tags'] = recd.tags datastore.write(recd.datastoreOb) @@ -264,6 +298,7 @@ def _saveMediaToDatastore(el, recd, activity): datastorePreviewPixbuf = recd.getThumbPixbuf() if recd.type == constants.TYPE_AUDIO: datastorePreviewPixbuf = recd.getAudioImagePixbuf() + elif recd.type == constants.TYPE_PHOTO: datastorePreviewFilepath = recd.getMediaFilepath() datastorePreviewPixbuf = GdkPixbuf.Pixbuf.new_from_file(datastorePreviewFilepath) @@ -271,8 +306,11 @@ def _saveMediaToDatastore(el, recd, activity): if datastorePreviewPixbuf: datastorePreviewWidth = 300 datastorePreviewHeight = 225 + if datastorePreviewPixbuf.get_width() != datastorePreviewWidth: - datastorePreviewPixbuf = datastorePreviewPixbuf.scale_simple(datastorePreviewWidth, datastorePreviewHeight, GdkPixbuf.InterpType.NEAREST) + datastorePreviewPixbuf = datastorePreviewPixbuf.scale_simple( + datastorePreviewWidth, datastorePreviewHeight, + GdkPixbuf.InterpType.NEAREST) datastorePreviewBase64 = utils.getStringFromPixbuf(datastorePreviewPixbuf) mediaObject.metadata['preview'] = datastorePreviewBase64 -- cgit v0.9.1