From a222bcb59262a403e1ec01f0fffe6c64fa9d1bc7 Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Fri, 19 Nov 2010 22:35:30 +0000 Subject: refactoring of audio/video based on Jukebox code (#1613) --- (limited to 'TurtleArt') diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py index df87da3..d44e6bb 100644 --- a/TurtleArt/taconstants.py +++ b/TurtleArt/taconstants.py @@ -138,8 +138,8 @@ PALETTES = [['clean', 'forward', 'back', 'show', 'left', 'right', 'sandwichtop_no_label', 'sandwichbottom'], ['kbinput', 'keyboard', 'readpixel', 'see', 'sound', 'volume', 'pitch'], - ['journal', 'audio', 'description', 'hideblocks', 'showblocks', - 'fullscreen', 'savepix', 'savesvg', 'picturelist', + ['journal', 'audio', 'video', 'description', 'hideblocks', + 'showblocks', 'fullscreen', 'savepix', 'savesvg', 'picturelist', 'picture1x1a', 'picture1x1', 'picture2x2', 'picture2x1', 'picture1x2'], ['empty', 'restoreall']] @@ -223,7 +223,7 @@ BOX_STYLE = ['number', 'xcor', 'ycor', 'heading', 'pensize', 'color', 'shade', 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'white', 'black', 'titlex', 'titley', 'leftx', 'topy', 'rightx', 'bottomy', 'sound', 'volume', 'pitch', 'voltage', 'resistance', 'gray', 'see'] -BOX_STYLE_MEDIA = ['description', 'audio', 'journal'] +BOX_STYLE_MEDIA = ['description', 'audio', 'journal', 'video'] NUMBER_STYLE = ['plus2', 'product2', 'myfunc'] NUMBER_STYLE_VAR_ARG = ['myfunc1arg', 'myfunc2arg', 'myfunc3arg'] NUMBER_STYLE_BLOCK = ['random'] @@ -276,13 +276,14 @@ OLD_DOCK = ['and', 'or', 'plus', 'minus', 'division', 'product', 'remainder'] # # Blocks that contain media # -CONTENT_BLOCKS = ['number', 'string', 'description', 'audio', 'journal'] +CONTENT_BLOCKS = ['number', 'string', 'description', 'audio', 'video', + 'journal'] # # These blocks get a special skin # BLOCKS_WITH_SKIN = ['journal', 'audio', 'description', 'nop', 'userdefined', - 'userdefined2args', 'userdefined3args'] + 'video', 'userdefined2args', 'userdefined3args'] PYTHON_SKIN = ['nop', 'userdefined', 'userdefined2args', 'userdefined3args'] @@ -450,6 +451,7 @@ BLOCK_NAMES = { 'userdefined': [_(' ')], 'userdefined2args': [_(' ')], 'userdefined3args': [_(' ')], + 'video': [' '], 'voltage': [_('voltage')], 'volume': [_('volume')], 'vspace': [' '], @@ -666,6 +668,7 @@ DEFAULTS = { 'userdefined': [100], 'userdefined2args': [100, 100], 'userdefined3args': [100, 100, 100], + 'video': [None], 'wait': [1], 'write': [_('text'), 32]} @@ -688,6 +691,7 @@ CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1', # MEDIA_SHAPES = ['audiooff', 'audioon', 'audiosmall', + 'videooff', 'videoon', 'videosmall', 'journaloff', 'journalon', 'journalsmall', 'descriptionoff', 'descriptionon', 'descriptionsmall', 'pythonoff', 'pythonon', 'pythonsmall', @@ -765,6 +769,7 @@ SPECIAL_NAMES = { 'template2x2': _('presentation 2x2'), 'templatelist': _('presentation bulleted list'), 'textsize': _('text size'), + 'video': _('video'), 'vspace': _('vertical space')} # @@ -915,6 +920,7 @@ HELP_STRINGS = { 'userdefined': _("runs code found in the tamyblock.py module found in the Journal"), 'userdefined2args': _("runs code found in the tamyblock.py module found in the Journal"), 'userdefined3args': _("runs code found in the tamyblock.py module found in the Journal"), + 'video': _("Sugar Journal video object"), 'voltage': _("sensor voltage"), 'volume': _("microphone input volume"), 'vspace': _("jogs stack down"), diff --git a/TurtleArt/tagplay.py b/TurtleArt/tagplay.py index 5c86f62..83f9df5 100644 --- a/TurtleArt/tagplay.py +++ b/TurtleArt/tagplay.py @@ -1,85 +1,363 @@ -#Copyright (c) 2009, Walter Bender (on behalf of Sugar Labs) - -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: - -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. - -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. - """ -Video and audio playback - -Based on code snippets from -http://wiki.sugarlabs.org/go/Development_Team/Almanac/GStreamer + tagplay.py + refactored based on Jukebox Activity + Copyright (C) 2007 Andy Wingo + Copyright (C) 2007 Red Hat, Inc. + Copyright (C) 2008-2010 Kushal Das + Copyright (C) 2010 Walter Bender """ -import gtk + +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + + +import logging +from gettext import gettext as _ +import os + +from sugar.activity import activity +from sugar.graphics.objectchooser import ObjectChooser +from sugar import mime + import pygtk pygtk.require('2.0') + +import sys + +import gobject + import pygst pygst.require('0.10') import gst import gst.interfaces -import gobject -gobject.threads_init() +import gtk -try: - from sugar.datastore import datastore -except ImportError: - pass +import urllib +from ConfigParser import ConfigParser +cf = ConfigParser() -class Gplay: - def __init__(self): - self.window = None +def play_audio(lc, file_path): + """ Called from Show block of audio media """ + if lc.gplay is not None and lc.gplay.player is not None: + if lc.gplay.player.playing: + lc.gplay.player.stop() + if lc.gplay.bin is not None: + lc.gplay.bin.destroy() + + lc.gplay = Gplay(lc, lc.tw.canvas.width, lc.tw.canvas.height, 4, 3) + lc.gplay.start(file_path) + + +def play_movie_from_file(lc, filepath, x, y, w, h): + """ Called from Show block of video media """ + if lc.gplay is not None and lc.gplay.player is not None: + if lc.gplay.player.playing: + lc.gplay.player.stop() + if lc.gplay.bin is not None: + lc.gplay.bin.destroy() + + lc.gplay = Gplay(lc, x, y, w, h) + lc.gplay.start(filepath) + + +def stop_media(lc): + """ Called from Clean block and toolbar Stop button """ + if lc.gplay == None: + return + + if lc.gplay.player is not None: + lc.gplay.player.stop() + if lc.gplay.bin != None: + lc.gplay.bin.destroy() + + lc.gplay = None + + +def media_playing(lc): + + if lc.gplay == None: + return False + return lc.gplay.player.playing + + +class Gplay(): + UPDATE_INTERVAL = 500 + + def __init__(self, lc, x, y, w, h): + + self.update_id = -1 + self.changed_id = -1 + self.seek_timeout_id = -1 + self.player = None + self.uri = None + self.playlist = [] + self.jobjectlist = [] + self.playpath = None + self.only_audio = False + self.got_stream_info = False + self.currentplaying = 0 + self.p_position = gst.CLOCK_TIME_NONE + self.p_duration = gst.CLOCK_TIME_NONE + + self.bin = gtk.Window() + + self.videowidget = VideoWidget() + self.bin.add(self.videowidget) + self.bin.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL) + self.bin.set_decorated(False) + if lc.tw.running_sugar: + self.bin.set_transient_for(lc.tw.activity) + + self.bin.move(x, y+108) + self.bin.resize(w, h) + self.bin.show_all() + + self._want_document = True + + def _player_eos_cb(self, widget): + logging.debug("end of stream") + pass + + def _player_error_cb(self, widget, message, detail): + self.player.stop() + self.player.set_uri(None) + logging.debug("Error: %s - %s" % (message, detail)) + + def _player_stream_info_cb(self, widget, stream_info): + if not len(stream_info) or self.got_stream_info: + return + + GST_STREAM_TYPE_UNKNOWN = 0 + GST_STREAM_TYPE_AUDIO = 1 + GST_STREAM_TYPE_VIDEO = 2 + GST_STREAM_TYPE_TEXT = 3 + + only_audio = True + for item in stream_info: + if item.props.type == GST_STREAM_TYPE_VIDEO: + only_audio = False + self.only_audio = only_audio + self.got_stream_info = True + + def read_file(self, file_path): + self.uri = os.path.abspath(file_path) + if os.path.islink(self.uri): + self.uri = os.path.realpath(self.uri) + gobject.idle_add(self.start, self.uri) + + def getplaylist(self, links): + result = [] + for x in links: + if x.startswith('http://'): + result.append(x) + elif x.startswith('#'): + continue + else: + result.append('file://' + \ + urllib.quote(os.path.join(self.playpath,x))) + return result + + def start(self, uri=None): + self._want_document = False + self.playpath = os.path.dirname(uri) + if not uri: + return False + # FIXME: parse m3u files and extract actual URL + if uri.endswith(".m3u") or uri.endswith(".m3u8"): + self.playlist.extend(self.getplaylist([line.strip() \ + for line in open(uri).readlines()])) + elif uri.endswith('.pls'): + try: + cf.readfp(open(uri)) + x = 1 + while True: + self.playlist.append(cf.get("playlist",'File'+str(x))) + x += 1 + except: + #read complete + pass + else: + self.playlist.append("file://" + urllib.quote(os.path.abspath(uri))) + if not self.player: + # lazy init the player so that videowidget is realized + # and has a valid widget allocation + self.player = GstPlayer(self.videowidget) + self.player.connect("eos", self._player_eos_cb) + self.player.connect("error", self._player_error_cb) + self.player.connect("stream-info", self._player_stream_info_cb) + + try: + if not self.currentplaying: + logging.info("Playing: " + self.playlist[0]) + self.player.set_uri(self.playlist[0]) + self.currentplaying = 0 + self.play_toggled() + self.show_all() + else: + pass + except: + pass + return False + + def play_toggled(self): + if self.player.is_playing(): + self.player.pause() + else: + if self.player.error: + pass + else: + self.player.play() + if self.update_id == -1: + self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL, + self.update_scale_cb) + + def volume_changed_cb(self, widget, value): + if self.player: + self.player.player.set_property('volume', value) + + def scale_button_press_cb(self, widget, event): + self.was_playing = self.player.is_playing() + if self.was_playing: + self.player.pause() + + # don't timeout-update position during seek + if self.update_id != -1: + gobject.source_remove(self.update_id) + self.update_id = -1 + + def scale_value_changed_cb(self, scale): + # see seek.c:seek_cb + real = long(scale.get_value() * self.p_duration / 100) # in ns + self.player.seek(real) + # allow for a preroll + self.player.get_state(timeout=50 * gst.MSECOND) # 50 ms + + def scale_button_release_cb(self, widget, event): + # see seek.cstop_seek + widget.disconnect(self.changed_id) + self.changed_id = -1 + + if self.seek_timeout_id != -1: + gobject.source_remove(self.seek_timeout_id) + self.seek_timeout_id = -1 + else: + if self.was_playing: + self.player.play() + + if self.update_id != -1: + self.error('Had a previous update timeout id') + else: + self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL, + self.update_scale_cb) + + def update_scale_cb(self): + self.p_position, self.p_duration = self.player.query_position() + if self.p_position != gst.CLOCK_TIME_NONE: + value = self.p_position * 100.0 / self.p_duration + + return True + + +class GstPlayer(gobject.GObject): + __gsignals__ = { + 'error': (gobject.SIGNAL_RUN_FIRST, None, [str, str]), + 'eos' : (gobject.SIGNAL_RUN_FIRST, None, []), + 'stream-info' : (gobject.SIGNAL_RUN_FIRST, None, [object]) + } + + def __init__(self, videowidget): + gobject.GObject.__init__(self) + self.playing = False + self.error = False + + self.player = gst.element_factory_make("playbin", "player") + + self.videowidget = videowidget + self._init_video_sink() - self.player = gst.element_factory_make("playbin", "playbin") - xis = gst.element_factory_make("xvimagesink", "xvimagesink") - self.player.set_property("video-sink", xis) bus = self.player.get_bus() bus.enable_sync_message_emission() bus.add_signal_watch() - self.SYNC_ID = bus.connect('sync-message::element', \ - self._onSyncMessageCb) + bus.connect('sync-message::element', self.on_sync_message) + bus.connect('message', self.on_message) - def _onSyncMessageCb(self, bus, message): + def set_uri(self, uri): + self.player.set_property('uri', uri) + + def on_sync_message(self, bus, message): if message.structure is None: - return True + return if message.structure.get_name() == 'prepare-xwindow-id': - if self.window is None: - return True - self.window.set_sink(message.src) + self.videowidget.set_sink(message.src) message.src.set_property('force-aspect-ratio', True) - return True + + def on_message(self, bus, message): + t = message.type + if t == gst.MESSAGE_ERROR: + err, debug = message.parse_error() + logging.debug("Error: %s - %s" % (err, debug)) + self.error = True + self.emit("eos") + self.playing = False + self.emit("error", str(err), str(debug)) + elif t == gst.MESSAGE_EOS: + self.emit("eos") + self.playing = False + elif t == gst.MESSAGE_STATE_CHANGED: + old, new, pen = message.parse_state_changed() + if old == gst.STATE_READY and new == gst.STATE_PAUSED: + self.emit('stream-info', + self.player.props.stream_info_value_array) - def setFile(self, path): - uri = "file://" + str(path) - if (self.player.get_property('uri') == uri): - self.seek(gst.SECOND*0) - return + def _init_video_sink(self): + self.bin = gst.Bin() + videoscale = gst.element_factory_make('videoscale') + self.bin.add(videoscale) + pad = videoscale.get_pad("sink") + ghostpad = gst.GhostPad("sink", pad) + self.bin.add_pad(ghostpad) + videoscale.set_property("method", 0) - self.player.set_state(gst.STATE_READY) - self.player.set_property('uri', uri) - ext = uri[len(uri)-3:] - if (ext == "jpg"): - self.pause() + caps_string = "video/x-raw-yuv, " + r = self.videowidget.get_allocation() + if r.width > 500 and r.height > 500: + # Sigh... xvimagesink on the XOs will scale the video to fit + # but ximagesink in Xephyr does not. So we live with unscaled + # video in Xephyr so that the XO can work right. + w = 480 + h = float(w) / float(float(r.width) / float(r.height)) + caps_string += "width=%d, height=%d" % (w, h) else: - self.play() + caps_string += "width=480, height=360" + + caps = gst.Caps(caps_string) + self.filter = gst.element_factory_make("capsfilter", "filter") + self.bin.add(self.filter) + self.filter.set_property("caps", caps) + + conv = gst.element_factory_make ("ffmpegcolorspace", "conv"); + self.bin.add(conv) + videosink = gst.element_factory_make('autovideosink') + self.bin.add(videosink) + gst.element_link_many(videoscale, self.filter, conv, videosink) + self.player.set_property("video-sink", self.bin) - def queryPosition(self): - #"Returns a (position, duration) tuple" + def query_position(self): + "Returns a (position, duration) tuple" try: position, format = self.player.query_position(gst.FORMAT_TIME) except: @@ -89,31 +367,38 @@ class Gplay: duration, format = self.player.query_duration(gst.FORMAT_TIME) except: duration = gst.CLOCK_TIME_NONE + return (position, duration) - def seek(self, time): - event = gst.event_new_seek(1.0,\ - gst.FORMAT_TIME,\ - gst.SEEK_FLAG_FLUSH|gst.SEEK_FLAG_ACCURATE,\ - gst.SEEK_TYPE_SET,\ - time,\ - gst.SEEK_TYPE_NONE, 0) + def seek(self, location): + """ + @param location: time to seek to, in nanoseconds + """ + event = gst.event_new_seek(1.0, gst.FORMAT_TIME, + gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, + gst.SEEK_TYPE_SET, location, + gst.SEEK_TYPE_NONE, 0) + res = self.player.send_event(event) if res: self.player.set_new_stream_time(0L) + else: + logging.debug("seek to %r failed" % location) def pause(self): - self.playing = False + logging.debug("pausing player") self.player.set_state(gst.STATE_PAUSED) + self.playing = False def play(self): - self.playing = True + logging.debug("playing player") self.player.set_state(gst.STATE_PLAYING) - + self.playing = True + self.error = False + def stop(self): - self.playing = False self.player.set_state(gst.STATE_NULL) - # self.nextMovie() + logging.debug("stopped player") def get_state(self, timeout=1): return self.player.get_state(timeout=timeout) @@ -121,56 +406,44 @@ class Gplay: def is_playing(self): return self.playing -class PlayVideoWindow(gtk.Window): + +class VideoWidget(gtk.DrawingArea): def __init__(self): - gtk.Window.__init__(self) + gtk.DrawingArea.__init__(self) + self.set_events(gtk.gdk.EXPOSURE_MASK) self.imagesink = None self.unset_flags(gtk.DOUBLE_BUFFERED) self.set_flags(gtk.APP_PAINTABLE) - def set_sink(self, sink): - if (self.imagesink != None): - assert self.window.xid - self.imagesink = None - del self.imagesink + def do_expose_event(self, event): + if self.imagesink: + self.imagesink.expose() + return False + else: + return True + def set_sink(self, sink): + assert self.window.xid self.imagesink = sink - if self.window is not None: - self.imagesink.set_xwindow_id(self.window.xid) + self.imagesink.set_xwindow_id(self.window.xid) -def play_audio(lc, filepath): - print "loading audio id: " + filepath - if lc.gplay == None: - lc.gplay = Gplay() - lc.gplay.setFile("file:///" + filepath) -def play_movie_from_file(lc, filepath, x, y, w, h): - if lc.gplay == None: - lc.gplay = Gplay() - # wait for current movie to stop playing - if lc.gplay.is_playing: - print "already playing..." - lc.gplay.setFile("file:///" + filepath) - if lc.gplay.window == None: - gplayWin = PlayVideoWindow() - lc.gplay.window = gplayWin - gplayWin.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) - gplayWin.set_decorated(False) - if lc.tw.running_sugar: - gplayWin.set_transient_for(lc.tw.activity) - # y position is too high for some reason (toolbox?) adding offset - gplayWin.move(x, y+108) - gplayWin.resize(w, h) - gplayWin.show_all() +if __name__ == '__main__': + window = gtk.Window() + + view = VideoWidget() + + player = GstPlayer(view) + + window.add(view) + + player.set_uri('http://78.46.73.237:8000/prog') + player.play() + window.show_all() + + + + gtk.main() -def stop_media(lc): - if lc.gplay == None: - return - lc.gplay.stop() - if lc.gplay.window != None: - # We need to destroy the video window - # print dir(lc.gplay.window) - lc.gplay.window.destroy() - lc.gplay = None diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index 30fd617..7a717d4 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -285,6 +285,7 @@ def stop_logo(tw): """ Stop logo is called from the Stop button on the toolbar """ tw.step_time = 0 tw.lc.step = _just_stop() + stop_media(tw.lc) tw.active_turtle.show() @@ -442,7 +443,7 @@ class LogoCode: 'userdefined2': [2, lambda self, x, y: self._prim_myblock([x, y])], 'userdefined3': [3, lambda self, x, y, z: self._prim_myblock([x, y, z])], - 'video': [1, lambda self, x: self._play_movie(x)], + 'video': [1, lambda self, x: self._play_video(x)], 'voltage': [0, lambda self: self._get_voltage()], 'volume': [0, lambda self: self._get_volume()], 'vres': [0, lambda self: CONSTANTS['height']], @@ -599,6 +600,11 @@ class LogoCode: code.append('#saudio_' + str(blk.values[0])) else: code.append('#saudio_None') + elif blk.name == 'video': + if blk.values[0] is not None: + code.append('#svideo_' + str(blk.values[0])) + else: + code.append('#svideo_None') else: return ['%nothing%'] else: @@ -1247,6 +1253,8 @@ class LogoCode: self._insert_desc(string) elif string[0:6] == 'audio_': self._play_sound(string) + elif string[0:6] == 'video_': + self._play_video(string) else: if center: y -= self.tw.canvas.textsize @@ -1300,6 +1308,26 @@ class LogoCode: else: play_audio(self, audio[6:]) + def _play_video(self, video): + """ Movie file from Journal """ + if video == "" or video[6:] == "": + raise logoerror("#nomedia") + w = int((self.tw.canvas.width * self.scale) / 100.) + h = int((self.tw.canvas.height * self.scale) / 100.) + x = self.tw.canvas.width / 2 + int(self.tw.canvas.xcor) + y = self.tw.canvas.height / 2 - int(self.tw.canvas.ycor) + if self.tw.running_sugar: + if video[6:] != "None": + try: + dsobject = datastore.get(video[6:]) + play_movie_from_file(self, dsobject.file_path, + int(x), int(y), int(w), int(h)) + except: + _logger.debug("Couldn't open id: %s" % (str(video[6:]))) + else: + play_movie_from_file(self, video[6:], + int(x), int(y), int(w), int(h)) + def _show_picture(self, media, x, y, w, h, show=True): """ Image file from Journal """ if w < 1 or h < 1: @@ -1312,13 +1340,9 @@ class LogoCode: if self.tw.running_sugar: try: dsobject = datastore.get(media[6:]) - if movie_media_type(dsobject.file_path): - play_movie_from_file(self, dsobject.file_path, - int(x), int(y), int(w), int(h)) - else: - self.filepath = dsobject.file_path - pixbuf = get_pixbuf_from_journal(dsobject, - int(w), int(h)) + self.filepath = dsobject.file_path + pixbuf = get_pixbuf_from_journal(dsobject, + int(w), int(h)) dsobject.destroy() except: # Maybe it is a pathname instead. @@ -1333,13 +1357,9 @@ class LogoCode: (media[6:])) else: try: - if movie_media_type(media): - play_movie_from_file(self, media[6:], int(x), int(y), - int(w), int(h)) - else: - self.filepath = media[6:] - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( - media[6:], int(w), int(h)) + self.filepath = media[6:] + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( + media[6:], int(w), int(h)) except: self.filepath = None self.tw.showlabel('nofile', media[6:]) diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py index 2bd03c2..1845b3c 100644 --- a/TurtleArt/tautils.py +++ b/TurtleArt/tautils.py @@ -300,12 +300,13 @@ def image_to_base64(pixbuf, activity): def movie_media_type(name): """ Is it movie media? """ - return name.endswith(('.ogv', '.vob', '.mp4', '.wmv', '.mov', '.mpeg')) + return name.endswith(('.ogv', '.vob', '.mp4', '.wmv', '.mov', '.mpeg', + 'ogg')) def audio_media_type(name): """ Is it audio media? """ - return name.endswith(('.ogg', '.oga', '.m4a')) + return name.endswith(('.oga', '.m4a')) def image_media_type(name): diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py index 65176f8..5287899 100644 --- a/TurtleArt/tawindow.py +++ b/TurtleArt/tawindow.py @@ -72,6 +72,7 @@ from tautils import magnitude, get_load_name, get_save_name, data_from_file, \ dock_dx_dy, data_to_string, journal_check, chooser, \ get_hardware from tasprite_factory import SVG, svg_str_to_pixbuf, svg_from_file +from tagplay import stop_media from sprites import Sprites, Sprite from audiograb import AudioGrab_Unknown, AudioGrab_XO1, AudioGrab_XO15 @@ -1830,10 +1831,13 @@ class TurtleArtWindow(): def _update_media_icon(self, blk, name, value=''): """ Update the icon on a 'loaded' media block. """ + print blk.name, name if blk.name == 'journal': self._load_image_thumb(name, blk) elif blk.name == 'audio': self._block_skin('audioon', blk) + elif blk.name == 'video': + self._block_skin('videoon', blk) else: self._block_skin('descriptionon', blk) if value == '': @@ -1854,7 +1858,8 @@ class TurtleArtWindow(): pixbuf = get_pixbuf_from_journal(picture, w, h) else: if movie_media_type(picture): - self._block_skin('journalon', blk) + self._block_skin('videoon', blk) + blk.name = 'video' elif audio_media_type(picture): self._block_skin('audioon', blk) blk.name = 'audio' @@ -1894,6 +1899,7 @@ class TurtleArtWindow(): elif keyname == 'q': if self.audio_started: self.audiograb.stop_grabbing() + stop_media(self.lc) exit() elif self.selected_blk is not None: @@ -2378,7 +2384,7 @@ class TurtleArtWindow(): if len(blk.values) == 0 or blk.values[0] == 'None' or \ blk.values[0] is None: self._block_skin(btype + 'off', blk) - elif btype == 'audio' or btype == 'description': + elif btype == 'video' or btype == 'audio' or btype == 'description': self._block_skin(btype + 'on', blk) elif self.running_sugar: try: -- cgit v0.9.1