Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TurtleArt/taconstants.py16
-rw-r--r--TurtleArt/tagplay.py495
-rw-r--r--TurtleArt/talogo.py50
-rw-r--r--TurtleArt/tautils.py5
-rw-r--r--TurtleArt/tawindow.py10
-rw-r--r--images/videooff.svg17
-rw-r--r--images/videoon.svg69
-rw-r--r--images/videosmall.svg76
8 files changed, 603 insertions, 135 deletions
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 <wingo@pobox.com>
+ Copyright (C) 2007 Red Hat, Inc.
+ Copyright (C) 2008-2010 Kushal Das <kushal@fedoraproject.org>
+ 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:
diff --git a/images/videooff.svg b/images/videooff.svg
new file mode 100644
index 0000000..a20349c
--- /dev/null
+++ b/images/videooff.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="clipping-video">
+ <g display="inline">
+ <g>
+ <polygon fill="&fill_color;" points="48.788,43.944 48.788,23.002 36.849,11.058 5.962,11.058 5.962,43.944 " stroke="&stroke_color;" stroke-width="3.5"/>
+ <polyline fill="none" points="36.849,11.058 36.849,23.002 48.788,23.002 " stroke="&stroke_color;" stroke-width="3.5"/>
+ </g>
+ </g>
+ <path d="M27.504,24.842c-4.757,0-8.72,4.744-8.72,4.744s3.963,4.767,8.72,4.764 c4.757-0.004,8.722-4.77,8.722-4.77S32.262,24.839,27.504,24.842z M27.504,32.932c-1.842,0-3.335-1.494-3.335-3.336 c0-1.839,1.493-3.336,3.335-3.336c1.839,0,3.333,1.497,3.333,3.336C30.838,31.438,29.344,32.932,27.504,32.932z" display="inline" fill="&stroke_color;"/>
+ <circle cx="27.505" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+ <circle cx="14.875" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+ <circle cx="10.375" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+ <circle cx="43.875" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+ <circle cx="39.375" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+</g></svg> \ No newline at end of file
diff --git a/images/videoon.svg b/images/videoon.svg
new file mode 100644
index 0000000..59d0f31
--- /dev/null
+++ b/images/videoon.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata29"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs27">
+
+
+
+
+
+
+
+</defs><g
+ id="g5"
+ style="display:inline">
+ <g
+ id="g7">
+ <polygon
+ points="5.962,11.058 5.962,43.944 48.788,43.944 48.788,23.002 36.849,11.058 "
+ id="polygon9"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5" />
+ <polyline
+ style="fill:none;stroke:#010101;stroke-width:3.5"
+ id="polyline11"
+ points="36.849,11.058 36.849,23.002 48.788,23.002 " />
+ </g>
+ </g><path
+ d="m 27.504,24.842 c -4.757,0 -8.72,4.744 -8.72,4.744 0,0 3.963,4.767 8.72,4.764 4.757,-0.004 8.722,-4.77 8.722,-4.77 0,0 -3.964,-4.741 -8.722,-4.738 z m 0,8.09 c -1.842,0 -3.335,-1.494 -3.335,-3.336 0,-1.839 1.493,-3.336 3.335,-3.336 1.839,0 3.333,1.497 3.333,3.336 0.001,1.842 -1.493,3.336 -3.333,3.336 z"
+ id="path13"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="27.504999"
+ cy="29.597"
+ r="1.5140001"
+ id="circle15"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="14.875"
+ cy="29.597"
+ r="1.5140001"
+ id="circle17"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="10.375"
+ cy="29.597"
+ r="1.5140001"
+ id="circle19"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="43.875"
+ cy="29.597"
+ r="1.5140001"
+ id="circle21"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="39.375"
+ cy="29.597"
+ r="1.5140001"
+ id="circle23"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /></svg> \ No newline at end of file
diff --git a/images/videosmall.svg b/images/videosmall.svg
new file mode 100644
index 0000000..d7758f5
--- /dev/null
+++ b/images/videosmall.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata29"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs27">
+
+
+
+
+
+
+
+</defs><g
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="g5"
+ style="stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none;display:inline">
+ <g
+ id="g7"
+ style="stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none">
+ <polygon
+ points="48.788,43.944 48.788,23.002 36.849,11.058 5.962,11.058 5.962,43.944 "
+ id="polygon9"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none" />
+ <polyline
+ style="fill:none;stroke:#010101;stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none"
+ id="polyline11"
+ points="36.849,11.058 36.849,23.002 48.788,23.002 " />
+ </g>
+ </g><path
+ d="m 27.4395,26.1715 c -2.3785,0 -4.36,2.372 -4.36,2.372 0,0 1.9815,2.3835 4.36,2.382 2.3785,-0.002 4.361,-2.385 4.361,-2.385 0,0 -1.982,-2.3705 -4.361,-2.369 z m 0,4.045 c -0.921,0 -1.6675,-0.747 -1.6675,-1.668 0,-0.9195 0.7465,-1.668 1.6675,-1.668 0.9195,0 1.6665,0.7485 1.6665,1.668 5e-4,0.921 -0.7465,1.668 -1.6665,1.668 z"
+ id="path13"
+ style="fill:#010101;display:inline" /><circle
+ cx="27.504999"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle15"
+ style="fill:#010101;display:inline" /><circle
+ cx="14.875"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle17"
+ style="fill:#010101;display:inline" /><circle
+ cx="10.375"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle19"
+ style="fill:#010101;display:inline" /><circle
+ cx="43.875"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle21"
+ style="fill:#010101;display:inline" /><circle
+ cx="39.375"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle23"
+ style="fill:#010101;display:inline" /></svg> \ No newline at end of file