Merge remote-tracking branch 'remotes/jwandborg/master'

This commit is contained in:
Christopher Allan Webber 2011-11-24 15:24:58 -06:00
commit 99d2ac1d83
29 changed files with 2974 additions and 236 deletions

View File

@ -50,6 +50,9 @@ allow_attachments = boolean(default=False)
# Cookie stuff
csrf_cookie_name = string(default='mediagoblin_csrftoken')
# Media types
enable_video = boolean(default=False)
[storage:publicstore]
storage_class = string(default="mediagoblin.storage.filestorage:BasicFileStorage")
base_dir = string(default="%(here)s/user_dev/media/public")

View File

@ -100,3 +100,11 @@ def user_add_forgot_password_token_and_expires(database):
"""
add_table_field(database, 'users', 'fp_verification_key', None)
add_table_field(database, 'users', 'fp_token_expire', None)
@RegisterMigration(7)
def media_type_image_to_multimedia_type_image(database):
database['media_entries'].update(
{'media_type': 'image'},
{'$set': {'media_type': 'mediagoblin.media_types.image'}},
multi=True)

View File

@ -211,10 +211,12 @@ def _export_media(db, args):
_log.info(u'Exporting {0} - {1}'.format(
entry['title'],
name))
mc_file = media_cache.get_file(path, mode='wb')
mc_file.write(
mg_globals.public_store.get_file(path, mode='rb').read())
try:
mc_file = media_cache.get_file(path, mode='wb')
mc_file.write(
mg_globals.public_store.get_file(path, mode='rb').read())
except e:
_log.error('Failed: {0}'.format(e))
_log.info('...Media exported')

View File

@ -18,7 +18,7 @@ import os
import sys
MANDATORY_CELERY_IMPORTS = ['mediagoblin.process_media']
MANDATORY_CELERY_IMPORTS = ['mediagoblin.processing']
DEFAULT_SETTINGS_MODULE = 'mediagoblin.init.celery.dummy_settings_module'

View File

@ -0,0 +1,73 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
from mediagoblin import mg_globals
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
class FileTypeNotSupported(Exception):
pass
class InvalidFileType(Exception):
pass
# This should be more dynamic in the future. Perhaps put it in the .ini?
# -- Joar
MEDIA_TYPES = [
'mediagoblin.media_types.image']
if mg_globals.app_config['enable_video']:
MEDIA_TYPES.append('mediagoblin.media_types.video')
def get_media_types():
'''
Generator that returns the available media types
'''
for media_type in MEDIA_TYPES:
yield media_type
def get_media_managers():
'''
Generator that returns all available media managers
'''
for media_type in get_media_types():
__import__(media_type)
yield media_type, sys.modules[media_type].MEDIA_MANAGER
def get_media_manager(_media_type = None):
for media_type, manager in get_media_managers():
if media_type in _media_type:
return manager
def get_media_type_and_manager(filename):
for media_type, manager in get_media_managers():
if filename.find('.') > 0:
ext = os.path.splitext(filename)[1].lower()
else:
raise InvalidFileType(
_('Could not find any file extension in "{filename}"').format(
filename=filename))
if ext[1:] in manager['accepted_extensions']:
return media_type, manager

View File

@ -0,0 +1,26 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.media_types.image.processing import process_image
MEDIA_MANAGER = {
"human_readable": "Image",
"processor": process_image, # alternately a string,
# 'mediagoblin.media_types.image.processing'?
"display_template": "mediagoblin/media_displays/image.html",
"default_thumb": "images/media_thumbs/image.jpg",
"accepted_extensions": ["jpg", "jpeg", "png", "gif", "tiff"]}

View File

@ -14,104 +14,23 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import Image
import os
import Image
from celery.task import Task
from celery import registry
from mediagoblin.db.util import ObjectId
from mediagoblin import mg_globals as mgg
from mediagoblin.process_media.errors import BaseProcessingFail, BadMediaFail
THUMB_SIZE = 180, 180
MEDIUM_SIZE = 640, 640
def create_pub_filepath(entry, filename):
return mgg.public_store.get_unique_filepath(
['media_entries',
unicode(entry._id),
filename])
from mediagoblin.processing import BaseProcessingFail, \
mark_entry_failed, BadMediaFail, create_pub_filepath, THUMB_SIZE, \
MEDIUM_SIZE
################################
# Media processing initial steps
################################
class ProcessMedia(Task):
"""
Pass this entry off for processing.
"""
def run(self, media_id):
"""
Pass the media entry off to the appropriate processing function
(for now just process_image...)
"""
entry = mgg.database.MediaEntry.one(
{'_id': ObjectId(media_id)})
# Try to process, and handle expected errors.
try:
process_image(entry)
except BaseProcessingFail, exc:
mark_entry_failed(entry._id, exc)
return
entry['state'] = u'processed'
entry.save()
def on_failure(self, exc, task_id, args, kwargs, einfo):
"""
If the processing failed we should mark that in the database.
Assuming that the exception raised is a subclass of
BaseProcessingFail, we can use that to get more information
about the failure and store that for conveying information to
users about the failure, etc.
"""
entry_id = args[0]
mark_entry_failed(entry_id, exc)
process_media = registry.tasks[ProcessMedia.name]
def mark_entry_failed(entry_id, exc):
"""
Mark a media entry as having failed in its conversion.
Uses the exception that was raised to mark more information. If
the exception is a derivative of BaseProcessingFail then we can
store extra information that can be useful for users telling them
why their media failed to process.
Args:
- entry_id: The id of the media entry
"""
# Was this a BaseProcessingFail? In other words, was this a
# type of error that we know how to handle?
if isinstance(exc, BaseProcessingFail):
# Looks like yes, so record information about that failure and any
# metadata the user might have supplied.
mgg.database['media_entries'].update(
{'_id': entry_id},
{'$set': {u'state': u'failed',
u'fail_error': exc.exception_path,
u'fail_metadata': exc.metadata}})
else:
# Looks like no, so just mark it as failed and don't record a
# failure_error (we'll assume it wasn't handled) and don't record
# metadata (in fact overwrite it if somehow it had previous info
# here)
mgg.database['media_entries'].update(
{'_id': entry_id},
{'$set': {u'state': u'failed',
u'fail_error': None,
u'fail_metadata': {}}})
def process_image(entry):
"""

View File

@ -0,0 +1,27 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.media_types.video.processing import process_video
MEDIA_MANAGER = {
"human_readable": "Video",
"processor": process_video, # alternately a string,
# 'mediagoblin.media_types.image.processing'?
"display_template": "mediagoblin/media_displays/video.html",
"default_thumb": "images/media_thumbs/video.jpg",
"accepted_extensions": [
"mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv", "ogg"]}

View File

@ -0,0 +1,505 @@
{
"make": "Generic",
"model": "Web Browser (Advanced)",
"description": "Media for World Wide Web",
"version": "0.1",
"author": {
"name": "Dionisio E Alonso",
"email": "dealonso@gmail.com"
},
"icon": "file://web.svg",
"default": "WebM 480p",
"presets": [
{
"name": "H.264 720p",
"extension": "mp4",
"container": "qtmux",
"vcodec": {
"name": "x264enc",
"container": "qtmux",
"width": [
960, 1280
],
"height": [
720, 720
],
"rate": [
1, 30
],
"passes": [
"pass=qual quantizer=23 subme=6 cabac=0 threads=0"
]
},
"acodec": {
"name": "faac",
"container": "qtmux",
"width": [
8, 24
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"bitrate=131072 profile=LC"
]
}
},
{
"name": "WebM 720p",
"extension": "webm",
"container": "webmmux",
"icon": "file://web-webm.svg",
"vcodec": {
"name": "vp8enc",
"container": "webmmux",
"width": [
960, 1280
],
"height": [
720, 720
],
"rate": [
1, 30
],
"passes": [
"quality=5.75 threads=%(threads)s speed=2"
]
},
"acodec": {
"name": "vorbisenc",
"container": "webmmux",
"width": [
8, 32
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"quality=0.3"
]
}
},
{
"name": "Flash Video 720p",
"extension": "flv",
"icon": "file://web-flv.png",
"container": "flvmux",
"vcodec": {
"name": "x264enc",
"container": "flvmux",
"width": [
960, 1280
],
"height": [
720, 720
],
"rate": [
1, 30
],
"passes": [
"pass=qual quantizer=23 subme=6 cabac=0 threads=0"
]
},
"acodec": {
"name": "faac",
"container": "flvmux",
"width": [
8, 24
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"bitrate=131072 profile=LC"
]
}
},
{
"name": "H.264 576p",
"extension": "mp4",
"container": "qtmux",
"vcodec": {
"name": "x264enc",
"container": "qtmux",
"width": [
768, 1024
],
"height": [
576, 576
],
"rate": [
1, 30
],
"passes": [
"pass=qual quantizer=23 subme=6 cabac=0 threads=0"
]
},
"acodec": {
"name": "faac",
"container": "qtmux",
"width": [
8, 24
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"bitrate=131072 profile=LC"
]
}
},
{
"name": "WebM 576p",
"extension": "webm",
"container": "webmmux",
"icon": "file://web-webm.svg",
"vcodec": {
"name": "vp8enc",
"container": "webmmux",
"width": [
768, 1024
],
"height": [
576, 576
],
"rate": [
1, 30
],
"passes": [
"quality=5.75 threads=%(threads)s speed=2"
]
},
"acodec": {
"name": "vorbisenc",
"container": "webmmux",
"width": [
8, 32
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"quality=0.3"
]
}
},
{
"name": "Flash Video 576p",
"extension": "flv",
"icon": "file://web-flv.png",
"container": "flvmux",
"vcodec": {
"name": "x264enc",
"container": "flvmux",
"width": [
768, 1024
],
"height": [
576, 576
],
"rate": [
1, 30
],
"passes": [
"pass=qual quantizer=23 subme=6 cabac=0 threads=0"
]
},
"acodec": {
"name": "faac",
"container": "flvmux",
"width": [
8, 24
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"bitrate=131072 profile=LC"
]
}
},
{
"name": "H.264 480p",
"extension": "mp4",
"container": "qtmux",
"vcodec": {
"name": "x264enc",
"container": "qtmux",
"width": [
640, 854
],
"height": [
480, 480
],
"rate": [
1, 30
],
"passes": [
"pass=qual quantizer=23 subme=6 cabac=0 threads=0"
]
},
"acodec": {
"name": "faac",
"container": "qtmux",
"width": [
8, 24
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"bitrate=131072 profile=LC"
]
}
},
{
"name": "WebM 480p",
"extension": "webm",
"container": "webmmux",
"icon": "file://web-webm.svg",
"vcodec": {
"name": "vp8enc",
"container": "webmmux",
"width": [
640, 854
],
"height": [
480, 480
],
"rate": [
1, 30
],
"passes": [
"quality=5.75 threads=%(threads)s speed=2"
]
},
"acodec": {
"name": "vorbisenc",
"container": "webmmux",
"width": [
8, 32
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"quality=0.3"
]
}
},
{
"name": "Flash Video 480p",
"extension": "flv",
"icon": "file://web-flv.png",
"container": "flvmux",
"vcodec": {
"name": "x264enc",
"container": "flvmux",
"width": [
640, 854
],
"height": [
480, 480
],
"rate": [
1, 30
],
"passes": [
"pass=qual quantizer=23 subme=6 cabac=0 threads=0"
]
},
"acodec": {
"name": "faac",
"container": "flvmux",
"width": [
8, 24
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"bitrate=131072 profile=LC"
]
}
},
{
"name": "H.264 360p",
"extension": "mp4",
"container": "qtmux",
"vcodec": {
"name": "x264enc",
"container": "qtmux",
"width": [
480, 640
],
"height": [
360, 360
],
"rate": [
1, 30
],
"passes": [
"pass=qual quantizer=23 subme=6 cabac=0 threads=0"
]
},
"acodec": {
"name": "faac",
"container": "qtmux",
"width": [
8, 24
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"bitrate=131072 profile=LC"
]
}
},
{
"name": "WebM 360p",
"extension": "webm",
"container": "webmmux",
"icon": "file://web-webm.svg",
"vcodec": {
"name": "vp8enc",
"container": "webmmux",
"width": [
480, 640
],
"height": [
360, 360
],
"rate": [
1, 30
],
"passes": [
"quality=5.75 threads=%(threads)s speed=2"
]
},
"acodec": {
"name": "vorbisenc",
"container": "webmmux",
"width": [
8, 32
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"quality=0.3"
]
}
},
{
"name": "Flash Video 360p",
"extension": "flv",
"icon": "file://web-flv.png",
"container": "flvmux",
"vcodec": {
"name": "x264enc",
"container": "flvmux",
"width": [
480, 640
],
"height": [
360, 360
],
"rate": [
1, 30
],
"passes": [
"pass=qual quantizer=23 subme=6 cabac=0 threads=0"
]
},
"acodec": {
"name": "faac",
"container": "flvmux",
"width": [
8, 24
],
"depth": [
8, 24
],
"rate": [
8000, 96000
],
"channels": [
1, 2
],
"passes": [
"bitrate=131072 profile=LC"
]
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,259 @@
<?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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48px"
height="48px"
id="svg2816"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="web-webm.svg">
<defs
id="defs2818">
<linearGradient
id="linearGradient3656">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3658" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop3660" />
</linearGradient>
<linearGradient
id="linearGradient3632">
<stop
style="stop-color:#ffffff;stop-opacity:0.54901963;"
offset="0"
id="stop3634" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3636" />
</linearGradient>
<linearGradient
id="linearGradient3622">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3624" />
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="1"
id="stop3626" />
</linearGradient>
<linearGradient
id="linearGradient3600">
<stop
style="stop-color:#8ae234;stop-opacity:1;"
offset="0"
id="stop3602" />
<stop
style="stop-color:#4e9a06;stop-opacity:1;"
offset="1"
id="stop3604" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective2824" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3600"
id="linearGradient3606"
x1="20.256382"
y1="2.546674"
x2="20.256382"
y2="46.881901"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3622"
id="linearGradient3628"
x1="21.2349"
y1="7.948472"
x2="21.2349"
y2="40.191879"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3632"
id="linearGradient3638"
x1="6.4826794"
y1="4.543263"
x2="25.363527"
y2="35.227882"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-0.35355339)" />
<inkscape:perspective
id="perspective3693"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3600-5"
id="linearGradient3606-9"
x1="20.256382"
y1="2.546674"
x2="20.256382"
y2="46.881901"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3600-5">
<stop
style="stop-color:#8ae234;stop-opacity:1;"
offset="0"
id="stop3602-7" />
<stop
style="stop-color:#4e9a06;stop-opacity:1;"
offset="1"
id="stop3604-2" />
</linearGradient>
<filter
inkscape:collect="always"
id="filter3731">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.82730657"
id="feGaussianBlur3733" />
</filter>
<inkscape:perspective
id="perspective3749"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3782"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="8"
inkscape:cx="20.51741"
inkscape:cy="22.534228"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1099"
inkscape:window-height="834"
inkscape:window-x="801"
inkscape:window-y="106"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid3608" />
</sodipodi:namedview>
<metadata
id="metadata2821">
<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>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="star"
style="fill:#000000;fill-opacity:0.2869955;stroke:none;filter:url(#filter3731)"
id="path3598-4"
sodipodi:sides="3"
sodipodi:cx="13.857143"
sodipodi:cy="24.714287"
sodipodi:r1="25.596954"
sodipodi:r2="12.798477"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 39.454098,24.714287 20.256381,35.798093 1.0586662,46.8819 l 0,-22.167614 0,-22.1676119 19.1977168,11.0838069 19.197715,11.083806 z"
transform="matrix(1.0537808,0,0,1.0537808,3.6163385,-1.9600717)" />
<path
sodipodi:type="star"
style="fill:url(#linearGradient3606);fill-opacity:1;stroke:#366a04;stroke-width:1.05497880999999993;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-linejoin:round"
id="path3598"
sodipodi:sides="3"
sodipodi:cx="13.857143"
sodipodi:cy="24.714287"
sodipodi:r1="25.596954"
sodipodi:r2="12.798477"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 39.454098,24.714287 20.256381,35.798093 1.0586662,46.8819 l 0,-22.167614 0,-22.1676119 19.1977168,11.0838069 19.197715,11.083806 z"
transform="matrix(0.94788634,0,0,0.94788634,5.0257749,0.56128794)" />
<path
style="fill:url(#linearGradient3628);stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="m 6.5304575,9.646791 8.7347075,20.091724 4.674611,-18.160553 4.525987,2.612472 3.885316,12.559503 4.403755,-7.765833 1.744319,1.009296 -2.127799,9.211229 -6.155446,3.554753 -4.028978,-9.439016 -2.255629,13.086534 -5.852703,3.373025 -7.5584205,-9.989634 0.01028,-20.1435 z"
id="path3620"
sodipodi:nodetypes="cccccccccccccc" />
<path
style="fill:none;stroke:url(#linearGradient3638);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 6.9826793,42.785087 0,-38.0953773 32.9068657,18.9987873"
id="path3630"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
d="M 6.6184028,8.6135689 15.026019,28.134068 19.45616,10.995613"
id="path3739"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
d="m 25.081121,14.552251 3.345117,11.020499 3.93014,-6.825955"
id="path3739-5"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
d="m 6.6291261,30.85266 7.0710679,9.280777"
id="path3772"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
d="m 34.736621,20.290253 -2.032932,8.794642"
id="path3772-6"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
d="m 20.594485,35.934991 1.811961,-10.650796 3.270369,7.778174"
id="path3796"
sodipodi:nodetypes="ccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,982 @@
<?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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48px"
height="48px"
id="svg3440"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/apps"
sodipodi:docname="internet-web-browser.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs3">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective156" />
<linearGradient
id="linearGradient4750">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4752" />
<stop
style="stop-color:#fefefe;stop-opacity:1.0000000;"
offset="0.37931034"
id="stop4758" />
<stop
style="stop-color:#1d1d1d;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop4754" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4350">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4352" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4354" />
</linearGradient>
<linearGradient
id="linearGradient4126">
<stop
style="stop-color:#ffffff;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop4128" />
<stop
style="stop-color:#ffffff;stop-opacity:0.16494845;"
offset="1.0000000"
id="stop4130" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4114">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4116" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop4118" />
</linearGradient>
<linearGradient
id="linearGradient3962">
<stop
style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop3964" />
<stop
style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
offset="0.15517241"
id="stop4134" />
<stop
style="stop-color:#4074ae;stop-opacity:1.0000000;"
offset="0.75000000"
id="stop4346" />
<stop
style="stop-color:#36486c;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop3966" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3962"
id="radialGradient3968"
gradientTransform="scale(0.999989,1.000011)"
cx="18.247644"
cy="15.716079"
fx="18.247644"
fy="15.716079"
r="29.993349"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4114"
id="radialGradient4120"
gradientTransform="scale(1.643990,0.608276)"
cx="15.115514"
cy="63.965388"
fx="15.115514"
fy="63.965388"
r="12.289036"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4126"
id="radialGradient4132"
gradientTransform="scale(0.999989,1.000011)"
cx="15.601279"
cy="12.142302"
fx="15.601279"
fy="12.142302"
r="43.526714"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4350"
id="radialGradient4356"
gradientTransform="scale(1.179536,0.847791)"
cx="11.826907"
cy="10.476453"
fx="11.826907"
fy="10.476453"
r="32.664848"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4750"
id="radialGradient4756"
gradientTransform="scale(1.036822,0.964486)"
cx="18.633780"
cy="17.486208"
fx="18.934305"
fy="17.810213"
r="40.692665"
gradientUnits="userSpaceOnUse" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1460"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1462"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1466"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1468"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1470"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1474"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1476"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1478"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1482"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1484"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1486"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1490"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1492"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1494"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1498"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1500"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1502"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1506"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1508"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1510"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1514"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1516"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1518"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1522"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1524"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1526"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1528"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1530"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1532"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1534"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1536"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1538"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1540"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1542"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1544"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1546"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1550"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1552"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1554"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
<radialGradient
r="40.692665"
fy="17.810213"
fx="18.934305"
cy="17.486208"
cx="18.633780"
gradientTransform="scale(1.036822,0.964486)"
gradientUnits="userSpaceOnUse"
id="radialGradient1558"
xlink:href="#linearGradient4750"
inkscape:collect="always" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0.17254902"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="9.8994949"
inkscape:cx="25.799661"
inkscape:cy="24.622653"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1440"
inkscape:window-height="823"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:showpageshadow="false" />
<metadata
id="metadata4">
<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>Globe</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>Tuomas Kuosmanen</dc:title>
</cc:Agent>
</dc:contributor>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
<dc:source>http://jimmac.musichall.cz</dc:source>
<dc:subject>
<rdf:Bag>
<rdf:li>globe</rdf:li>
<rdf:li>international</rdf:li>
<rdf:li>web</rdf:li>
<rdf:li>www</rdf:li>
<rdf:li>internet</rdf:li>
<rdf:li>network</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="arc"
style="fill:url(#radialGradient4120);fill-opacity:1.0000000;stroke:none;stroke-opacity:1.0000000"
id="path4112"
sodipodi:cx="24.849752"
sodipodi:cy="38.908627"
sodipodi:rx="20.203051"
sodipodi:ry="7.4751287"
d="M 45.052803 38.908627 A 20.203051 7.4751287 0 1 1 4.6467018,38.908627 A 20.203051 7.4751287 0 1 1 45.052803 38.908627 z"
transform="matrix(1.000000,0.000000,0.000000,1.243244,0.000000,-10.27241)" />
<path
style="fill:url(#radialGradient3968);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#39396c;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
d="M 43.959853,23.485499 C 43.959853,34.195217 35.277750,42.877222 24.569505,42.877222 C 13.860279,42.877222 5.1786663,34.195119 5.1786663,23.485499 C 5.1786663,12.776272 13.860279,4.0951517 24.569505,4.0951517 C 35.277750,4.0951517 43.959853,12.776272 43.959853,23.485499 L 43.959853,23.485499 z "
id="path3214" />
<path
sodipodi:type="arc"
style="opacity:0.42159382;fill:url(#radialGradient4356);fill-opacity:1.0000000;stroke:none;stroke-opacity:1.0000000"
id="path4348"
sodipodi:cx="17.778685"
sodipodi:cy="15.271057"
sodipodi:rx="12.929953"
sodipodi:ry="9.2934036"
d="M 30.708637 15.271057 A 12.929953 9.2934036 0 1 1 4.8487320,15.271057 A 12.929953 9.2934036 0 1 1 30.708637 15.271057 z"
transform="matrix(0.835938,0.000000,0.000000,1.000000,9.886868,0.000000)" />
<g
id="g4136"
style="fill:#000000;fill-opacity:0.71345031;fill-rule:nonzero;stroke:none;stroke-miterlimit:4.0000000"
transform="matrix(0.982371,0.000000,0.000000,0.982371,0.121079,0.232914)">
<g
id="g4138">
<g
id="g4142">
<path
d="M 44.071300,20.714400 C 44.071300,20.977100 44.071300,20.714400 44.071300,20.714400 L 43.526400,21.331600 C 43.192400,20.938000 42.817400,20.607000 42.436600,20.261300 L 41.600700,20.384300 L 40.837000,19.521000 L 40.837000,20.589400 L 41.491300,21.084500 L 41.926800,21.577700 L 42.508800,20.919500 C 42.655300,21.193900 42.799800,21.468300 42.945300,21.742700 L 42.945300,22.565000 L 42.290000,23.305200 L 41.090800,24.128400 L 40.182600,25.034700 L 39.600600,24.374500 L 39.891600,23.634300 L 39.310500,22.976100 L 38.329100,20.878400 L 37.493200,19.933100 L 37.274400,20.179200 L 37.602500,21.372600 L 38.219700,22.071800 C 38.572200,23.089400 38.920900,24.062000 39.383800,25.034700 C 40.101600,25.034700 40.778300,24.958500 41.491200,24.868700 L 41.491200,25.444900 L 40.619100,27.584100 L 39.819300,28.488400 L 39.165000,29.888800 C 39.165000,30.656400 39.165000,31.424000 39.165000,32.191500 L 39.383800,33.097800 L 39.020500,33.508000 L 38.219700,34.002100 L 37.383800,34.701300 L 38.075200,35.482600 L 37.129900,36.306800 L 37.311500,36.840000 L 35.893500,38.445500 L 34.949200,38.445500 L 34.149400,38.939600 L 33.639600,38.939600 L 33.639600,38.281400 L 33.422800,36.963000 C 33.141500,36.136800 32.848600,35.316500 32.550700,34.496200 C 32.550700,33.890700 32.586800,33.291100 32.623000,32.685700 L 32.987300,31.863400 L 32.477500,30.875100 L 32.514600,29.517700 L 31.823200,28.736400 L 32.168900,27.605500 L 31.606400,26.967300 L 30.624000,26.967300 L 30.296900,26.597200 L 29.315500,27.214900 L 28.916100,26.761300 L 28.006900,27.543000 C 27.389700,26.843300 26.771500,26.144100 26.153400,25.444900 L 25.426800,23.716400 L 26.081100,22.730100 L 25.717800,22.319000 L 26.516600,20.425400 C 27.172900,19.609000 27.858400,18.825800 28.551800,18.039700 L 29.788100,17.710600 L 31.169000,17.546500 L 32.114300,17.793600 L 33.459000,19.150000 L 33.931700,18.615800 L 34.585000,18.533800 L 35.821300,18.944900 L 36.766600,18.944900 L 37.420900,18.368700 L 37.711900,17.957600 L 37.056600,17.546500 L 35.965800,17.464500 C 35.663100,17.044600 35.381800,16.603200 35.022400,16.230100 L 34.658100,16.394200 L 34.512600,17.464500 L 33.858300,16.724300 L 33.713800,15.900100 L 32.987200,15.325900 L 32.695200,15.325900 L 33.422700,16.148200 L 33.131700,16.888400 L 32.550600,17.052500 L 32.913900,16.312300 L 32.258600,15.984200 L 31.678500,15.326000 L 30.586700,15.572100 L 30.442200,15.900200 L 29.787900,16.312300 L 29.424600,17.217600 L 28.516400,17.669700 L 28.116000,17.217600 L 27.680500,17.217600 L 27.680500,15.736200 L 28.625800,15.242100 L 29.352400,15.242100 L 29.205900,14.666900 L 28.625800,14.090700 L 29.606300,13.884600 L 30.151200,13.268400 L 30.586700,12.527200 L 31.387500,12.527200 L 31.168700,11.952000 L 31.678500,11.622900 L 31.678500,12.281100 L 32.768300,12.527200 L 33.858100,11.622900 L 33.931300,11.210800 L 34.875600,10.553100 C 34.533800,10.595600 34.192000,10.626800 33.858000,10.717700 L 33.858000,9.9766000 L 34.221300,9.1538000 L 33.858000,9.1538000 L 33.059600,9.8940000 L 32.840800,10.305600 L 33.059600,10.882300 L 32.695300,11.868600 L 32.114200,11.539500 L 31.606400,10.964300 L 30.805600,11.539500 L 30.514600,10.223600 L 31.895500,9.3188000 L 31.895500,8.8247000 L 32.768500,8.2490000 L 34.149400,7.9194000 L 35.094700,8.2490000 L 36.838800,8.5781000 L 36.403300,9.0713000 L 35.458000,9.0713000 L 36.403300,10.058600 L 37.129900,9.2363000 L 37.350600,8.8745000 C 37.350600,8.8745000 40.137700,11.372500 41.730500,14.105000 C 43.323300,16.838400 44.071300,20.060100 44.071300,20.714400 z "
id="path4144" />
</g>
</g>
<g
id="g4146">
<g
id="g4150">
<path
d="M 26.070300,9.2363000 L 25.997100,9.7295000 L 26.506900,10.058600 L 27.378000,9.4829000 L 26.942500,8.9892000 L 26.360500,9.3188000 L 26.070500,9.2363000"
id="path4152" />
</g>
</g>
<g
id="g4154">
<g
id="g4158">
<path
d="M 26.870100,5.8633000 L 24.979500,5.1226000 L 22.799800,5.3692000 L 20.109400,6.1094000 L 19.600600,6.6035000 L 21.272500,7.7549000 L 21.272500,8.4131000 L 20.618200,9.0713000 L 21.491200,10.800300 L 22.071300,10.470200 L 22.799800,9.3188000 C 23.922800,8.9716000 24.929700,8.5781000 25.997100,8.0844000 L 26.870100,5.8632000"
id="path4160" />
</g>
</g>
<g
id="g4162">
<g
id="g4166">
<path
d="M 28.833000,12.774900 L 28.542000,12.033700 L 28.032200,12.198700 L 28.178700,13.103000 L 28.833000,12.774900"
id="path4168" />
</g>
</g>
<g
id="g4170">
<g
id="g4174">
<path
d="M 29.123000,12.608900 L 28.977500,13.597200 L 29.777300,13.432200 L 30.358400,12.857000 L 29.849600,12.362900 C 29.678700,11.907800 29.482400,11.483000 29.268500,11.046500 L 28.833000,11.046500 L 28.833000,11.539700 L 29.123000,11.868800 L 29.123000,12.609000"
id="path4176" />
</g>
</g>
<g
id="g4178">
<g
id="g4182">
<path
d="M 18.365200,28.242200 L 17.783200,27.089900 L 16.692900,26.843300 L 16.111400,25.280800 L 14.657800,25.444900 L 13.422400,24.540600 L 12.113300,25.692000 L 12.113300,25.873600 C 11.717300,25.759300 11.230500,25.743700 10.877900,25.526900 L 10.586900,24.704600 L 10.586900,23.799300 L 9.7148000,23.881300 C 9.7876000,23.305100 9.8598000,22.729900 9.9331000,22.153800 L 9.4238000,22.153800 L 8.9155000,22.812000 L 8.4062000,23.058100 L 7.6791000,22.647900 L 7.6063000,21.742600 L 7.7518000,20.755300 L 8.8426000,19.933000 L 9.7147000,19.933000 L 9.8597000,19.438900 L 10.950000,19.685000 L 11.749800,20.673300 L 11.895300,19.026800 L 13.276600,17.875400 L 13.785400,16.641000 L 14.803000,16.229900 L 15.384500,15.407600 L 16.692600,15.159600 L 17.347400,14.173300 C 16.693100,14.173300 16.038800,14.173300 15.384500,14.173300 L 16.620300,13.597100 L 17.491900,13.597100 L 18.728200,13.185000 L 18.873700,12.692800 L 18.437200,12.280700 L 17.928400,12.115700 L 18.073900,11.622500 L 17.710600,10.882300 L 16.838000,11.210400 L 16.983500,10.552700 L 15.965900,9.9765000 L 15.166600,11.374400 L 15.238900,11.868500 L 14.439600,12.198600 L 13.930300,13.267900 L 13.712500,12.280600 L 12.331200,11.704400 L 12.112900,10.964200 L 13.930300,9.8939000 L 14.730100,9.1537000 L 14.802900,8.2489000 L 14.366900,8.0018000 L 13.785400,7.9193000 L 13.422100,8.8246000 C 13.422100,8.8246000 12.814200,8.9437000 12.657900,8.9823000 C 10.661800,10.821700 6.6286000,14.792400 5.6916000,22.288500 C 5.7287000,22.462300 6.3708000,23.470100 6.3708000,23.470100 L 7.8972000,24.374400 L 9.4236000,24.786500 L 10.078400,25.609700 L 11.095500,26.349900 L 11.677000,26.267900 L 12.113000,26.464200 L 12.113000,26.597000 L 11.531900,28.160000 L 11.095400,28.818200 L 11.240900,29.148300 L 10.877600,30.380700 L 12.186200,32.767400 L 13.494300,33.919700 L 14.076300,34.742000 L 14.003100,36.470500 L 14.439600,37.456800 L 14.003100,39.349400 C 14.003100,39.349400 13.968900,39.337700 14.024600,39.527100 C 14.080800,39.716600 16.353700,40.978300 16.498200,40.870900 C 16.642200,40.761500 16.765300,40.665800 16.765300,40.665800 L 16.620300,40.255600 L 17.201400,39.679400 L 17.419700,39.103200 L 18.365000,38.773100 L 19.091600,36.962600 L 18.873800,36.470400 L 19.381600,35.730200 L 20.472400,35.482200 L 21.054400,34.165800 L 20.908900,32.521300 L 21.781000,31.286900 L 21.926500,30.052500 C 20.733100,29.460700 19.549500,28.851300 18.365000,28.242000"
id="path4184" />
</g>
</g>
<g
id="g4186">
<g
id="g4190">
<path
d="M 16.765600,9.5649000 L 17.492200,10.058600 L 18.074200,10.058600 L 18.074200,9.4829000 L 17.347600,9.1538000 L 16.765600,9.5649000"
id="path4192" />
</g>
</g>
<g
id="g4194">
<g
id="g4198">
<path
d="M 14.876000,8.9072000 L 14.512200,9.8120000 L 15.239300,9.8120000 L 15.603100,8.9892000 C 15.916600,8.7675000 16.228600,8.5444000 16.547900,8.3310000 L 17.275000,8.5781000 C 17.759400,8.9072000 18.243800,9.2363000 18.728600,9.5649000 L 19.456100,8.9072000 L 18.655800,8.5781000 L 18.292000,7.8374000 L 16.911100,7.6728000 L 16.838300,7.2612000 L 16.184000,7.4262000 L 15.893600,8.0020000 L 15.529800,7.2613000 L 15.384800,7.5904000 L 15.457600,8.4132000 L 14.876000,8.9072000"
id="path4200" />
</g>
</g>
<g
id="g4202">
<g
style="opacity:0.75000000"
id="g4204">
<path
id="path4206"
d="" />
</g>
<g
id="g4208">
<path
id="path4210"
d="" />
</g>
</g>
<g
id="g4212">
<g
style="opacity:0.75000000"
id="g4214">
<path
id="path4216"
d="" />
</g>
<g
id="g4218">
<path
id="path4220"
d="" />
</g>
</g>
<g
id="g4222">
<g
id="g4226">
<path
d="M 17.492200,6.8496000 L 17.856000,6.5210000 L 18.583100,6.3564000 C 19.081100,6.1142000 19.581100,5.9511000 20.109500,5.7802000 L 19.819500,5.2865000 L 18.881000,5.4213000 L 18.437600,5.8632000 L 17.706600,5.9692000 L 17.056700,6.2744000 L 16.740800,6.4272000 L 16.547900,6.6855000 L 17.492200,6.8496000"
id="path4228" />
</g>
</g>
<g
id="g4230">
<g
id="g4234">
<path
d="M 18.728500,14.666500 L 19.165000,14.008300 L 18.510200,13.515100 L 18.728500,14.666500"
id="path4236" />
</g>
</g>
</g>
<g
id="g3216"
style="color:#000000;fill:url(#radialGradient1460);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:1.0179454;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
transform="matrix(0.982371,0.000000,0.000000,0.982371,-8.095179e-2,3.088300e-2)">
<g
id="g3218"
style="color:#000000;fill:url(#radialGradient1462);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3222"
style="color:#000000;fill:url(#radialGradient1466);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 44.071300,20.714400 C 44.071300,20.977100 44.071300,20.714400 44.071300,20.714400 L 43.526400,21.331600 C 43.192400,20.938000 42.817400,20.607000 42.436600,20.261300 L 41.600700,20.384300 L 40.837000,19.521000 L 40.837000,20.589400 L 41.491300,21.084500 L 41.926800,21.577700 L 42.508800,20.919500 C 42.655300,21.193900 42.799800,21.468300 42.945300,21.742700 L 42.945300,22.565000 L 42.290000,23.305200 L 41.090800,24.128400 L 40.182600,25.034700 L 39.600600,24.374500 L 39.891600,23.634300 L 39.310500,22.976100 L 38.329100,20.878400 L 37.493200,19.933100 L 37.274400,20.179200 L 37.602500,21.372600 L 38.219700,22.071800 C 38.572200,23.089400 38.920900,24.062000 39.383800,25.034700 C 40.101600,25.034700 40.778300,24.958500 41.491200,24.868700 L 41.491200,25.444900 L 40.619100,27.584100 L 39.819300,28.488400 L 39.165000,29.888800 C 39.165000,30.656400 39.165000,31.424000 39.165000,32.191500 L 39.383800,33.097800 L 39.020500,33.508000 L 38.219700,34.002100 L 37.383800,34.701300 L 38.075200,35.482600 L 37.129900,36.306800 L 37.311500,36.840000 L 35.893500,38.445500 L 34.949200,38.445500 L 34.149400,38.939600 L 33.639600,38.939600 L 33.639600,38.281400 L 33.422800,36.963000 C 33.141500,36.136800 32.848600,35.316500 32.550700,34.496200 C 32.550700,33.890700 32.586800,33.291100 32.623000,32.685700 L 32.987300,31.863400 L 32.477500,30.875100 L 32.514600,29.517700 L 31.823200,28.736400 L 32.168900,27.605500 L 31.606400,26.967300 L 30.624000,26.967300 L 30.296900,26.597200 L 29.315500,27.214900 L 28.916100,26.761300 L 28.006900,27.543000 C 27.389700,26.843300 26.771500,26.144100 26.153400,25.444900 L 25.426800,23.716400 L 26.081100,22.730100 L 25.717800,22.319000 L 26.516600,20.425400 C 27.172900,19.609000 27.858400,18.825800 28.551800,18.039700 L 29.788100,17.710600 L 31.169000,17.546500 L 32.114300,17.793600 L 33.459000,19.150000 L 33.931700,18.615800 L 34.585000,18.533800 L 35.821300,18.944900 L 36.766600,18.944900 L 37.420900,18.368700 L 37.711900,17.957600 L 37.056600,17.546500 L 35.965800,17.464500 C 35.663100,17.044600 35.381800,16.603200 35.022400,16.230100 L 34.658100,16.394200 L 34.512600,17.464500 L 33.858300,16.724300 L 33.713800,15.900100 L 32.987200,15.325900 L 32.695200,15.325900 L 33.422700,16.148200 L 33.131700,16.888400 L 32.550600,17.052500 L 32.913900,16.312300 L 32.258600,15.984200 L 31.678500,15.326000 L 30.586700,15.572100 L 30.442200,15.900200 L 29.787900,16.312300 L 29.424600,17.217600 L 28.516400,17.669700 L 28.116000,17.217600 L 27.680500,17.217600 L 27.680500,15.736200 L 28.625800,15.242100 L 29.352400,15.242100 L 29.205900,14.666900 L 28.625800,14.090700 L 29.606300,13.884600 L 30.151200,13.268400 L 30.586700,12.527200 L 31.387500,12.527200 L 31.168700,11.952000 L 31.678500,11.622900 L 31.678500,12.281100 L 32.768300,12.527200 L 33.858100,11.622900 L 33.931300,11.210800 L 34.875600,10.553100 C 34.533800,10.595600 34.192000,10.626800 33.858000,10.717700 L 33.858000,9.9766000 L 34.221300,9.1538000 L 33.858000,9.1538000 L 33.059600,9.8940000 L 32.840800,10.305600 L 33.059600,10.882300 L 32.695300,11.868600 L 32.114200,11.539500 L 31.606400,10.964300 L 30.805600,11.539500 L 30.514600,10.223600 L 31.895500,9.3188000 L 31.895500,8.8247000 L 32.768500,8.2490000 L 34.149400,7.9194000 L 35.094700,8.2490000 L 36.838800,8.5781000 L 36.403300,9.0713000 L 35.458000,9.0713000 L 36.403300,10.058600 L 37.129900,9.2363000 L 37.350600,8.8745000 C 37.350600,8.8745000 40.137700,11.372500 41.730500,14.105000 C 43.323300,16.838400 44.071300,20.060100 44.071300,20.714400 z "
id="path3224"
style="color:#000000;fill:url(#radialGradient1468);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3226"
style="color:#000000;fill:url(#radialGradient1470);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3230"
style="color:#000000;fill:url(#radialGradient1474);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 26.070300,9.2363000 L 25.997100,9.7295000 L 26.506900,10.058600 L 27.378000,9.4829000 L 26.942500,8.9892000 L 26.360500,9.3188000 L 26.070500,9.2363000"
id="path3232"
style="color:#000000;fill:url(#radialGradient1476);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3234"
style="color:#000000;fill:url(#radialGradient1478);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3238"
style="color:#000000;fill:url(#radialGradient1482);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 26.870100,5.8633000 L 24.979500,5.1226000 L 22.799800,5.3692000 L 20.109400,6.1094000 L 19.600600,6.6035000 L 21.272500,7.7549000 L 21.272500,8.4131000 L 20.618200,9.0713000 L 21.491200,10.800300 L 22.071300,10.470200 L 22.799800,9.3188000 C 23.922800,8.9716000 24.929700,8.5781000 25.997100,8.0844000 L 26.870100,5.8632000"
id="path3240"
style="color:#000000;fill:url(#radialGradient1484);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3242"
style="color:#000000;fill:url(#radialGradient1486);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3246"
style="color:#000000;fill:url(#radialGradient1490);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 28.833000,12.774900 L 28.542000,12.033700 L 28.032200,12.198700 L 28.178700,13.103000 L 28.833000,12.774900"
id="path3248"
style="color:#000000;fill:url(#radialGradient1492);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3250"
style="color:#000000;fill:url(#radialGradient1494);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3254"
style="color:#000000;fill:url(#radialGradient1498);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 29.123000,12.608900 L 28.977500,13.597200 L 29.777300,13.432200 L 30.358400,12.857000 L 29.849600,12.362900 C 29.678700,11.907800 29.482400,11.483000 29.268500,11.046500 L 28.833000,11.046500 L 28.833000,11.539700 L 29.123000,11.868800 L 29.123000,12.609000"
id="path3256"
style="color:#000000;fill:url(#radialGradient1500);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3258"
style="color:#000000;fill:url(#radialGradient1502);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3262"
style="color:#000000;fill:url(#radialGradient1506);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 18.365200,28.242200 L 17.783200,27.089900 L 16.692900,26.843300 L 16.111400,25.280800 L 14.657800,25.444900 L 13.422400,24.540600 L 12.113300,25.692000 L 12.113300,25.873600 C 11.717300,25.759300 11.230500,25.743700 10.877900,25.526900 L 10.586900,24.704600 L 10.586900,23.799300 L 9.7148000,23.881300 C 9.7876000,23.305100 9.8598000,22.729900 9.9331000,22.153800 L 9.4238000,22.153800 L 8.9155000,22.812000 L 8.4062000,23.058100 L 7.6791000,22.647900 L 7.6063000,21.742600 L 7.7518000,20.755300 L 8.8426000,19.933000 L 9.7147000,19.933000 L 9.8597000,19.438900 L 10.950000,19.685000 L 11.749800,20.673300 L 11.895300,19.026800 L 13.276600,17.875400 L 13.785400,16.641000 L 14.803000,16.229900 L 15.384500,15.407600 L 16.692600,15.159600 L 17.347400,14.173300 C 16.693100,14.173300 16.038800,14.173300 15.384500,14.173300 L 16.620300,13.597100 L 17.491900,13.597100 L 18.728200,13.185000 L 18.873700,12.692800 L 18.437200,12.280700 L 17.928400,12.115700 L 18.073900,11.622500 L 17.710600,10.882300 L 16.838000,11.210400 L 16.983500,10.552700 L 15.965900,9.9765000 L 15.166600,11.374400 L 15.238900,11.868500 L 14.439600,12.198600 L 13.930300,13.267900 L 13.712500,12.280600 L 12.331200,11.704400 L 12.112900,10.964200 L 13.930300,9.8939000 L 14.730100,9.1537000 L 14.802900,8.2489000 L 14.366900,8.0018000 L 13.785400,7.9193000 L 13.422100,8.8246000 C 13.422100,8.8246000 12.814200,8.9437000 12.657900,8.9823000 C 10.661800,10.821700 6.6286000,14.792400 5.6916000,22.288500 C 5.7287000,22.462300 6.3708000,23.470100 6.3708000,23.470100 L 7.8972000,24.374400 L 9.4236000,24.786500 L 10.078400,25.609700 L 11.095500,26.349900 L 11.677000,26.267900 L 12.113000,26.464200 L 12.113000,26.597000 L 11.531900,28.160000 L 11.095400,28.818200 L 11.240900,29.148300 L 10.877600,30.380700 L 12.186200,32.767400 L 13.494300,33.919700 L 14.076300,34.742000 L 14.003100,36.470500 L 14.439600,37.456800 L 14.003100,39.349400 C 14.003100,39.349400 13.968900,39.337700 14.024600,39.527100 C 14.080800,39.716600 16.353700,40.978300 16.498200,40.870900 C 16.642200,40.761500 16.765300,40.665800 16.765300,40.665800 L 16.620300,40.255600 L 17.201400,39.679400 L 17.419700,39.103200 L 18.365000,38.773100 L 19.091600,36.962600 L 18.873800,36.470400 L 19.381600,35.730200 L 20.472400,35.482200 L 21.054400,34.165800 L 20.908900,32.521300 L 21.781000,31.286900 L 21.926500,30.052500 C 20.733100,29.460700 19.549500,28.851300 18.365000,28.242000"
id="path3264"
style="color:#000000;fill:url(#radialGradient1508);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3266"
style="color:#000000;fill:url(#radialGradient1510);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3270"
style="color:#000000;fill:url(#radialGradient1514);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 16.765600,9.5649000 L 17.492200,10.058600 L 18.074200,10.058600 L 18.074200,9.4829000 L 17.347600,9.1538000 L 16.765600,9.5649000"
id="path3272"
style="color:#000000;fill:url(#radialGradient1516);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3274"
style="color:#000000;fill:url(#radialGradient1518);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3278"
style="color:#000000;fill:url(#radialGradient1522);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 14.876000,8.9072000 L 14.512200,9.8120000 L 15.239300,9.8120000 L 15.603100,8.9892000 C 15.916600,8.7675000 16.228600,8.5444000 16.547900,8.3310000 L 17.275000,8.5781000 C 17.759400,8.9072000 18.243800,9.2363000 18.728600,9.5649000 L 19.456100,8.9072000 L 18.655800,8.5781000 L 18.292000,7.8374000 L 16.911100,7.6728000 L 16.838300,7.2612000 L 16.184000,7.4262000 L 15.893600,8.0020000 L 15.529800,7.2613000 L 15.384800,7.5904000 L 15.457600,8.4132000 L 14.876000,8.9072000"
id="path3280"
style="color:#000000;fill:url(#radialGradient1524);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3282"
style="color:#000000;fill:url(#radialGradient1526);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
style="opacity:0.75000000;color:#000000;fill:url(#radialGradient1528);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
id="g3284">
<path
d=""
style="color:#000000;fill:url(#radialGradient1530);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
id="path3286" />
</g>
<g
id="g3288"
style="color:#000000;fill:url(#radialGradient1532);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d=""
id="path3290"
style="color:#000000;fill:url(#radialGradient1534);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3292"
style="color:#000000;fill:url(#radialGradient1536);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
style="opacity:0.75000000;color:#000000;fill:url(#radialGradient1538);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
id="g3294">
<path
d=""
style="color:#000000;fill:url(#radialGradient1540);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
id="path3296" />
</g>
<g
id="g3298"
style="color:#000000;fill:url(#radialGradient1542);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d=""
id="path3300"
style="color:#000000;fill:url(#radialGradient1544);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3302"
style="color:#000000;fill:url(#radialGradient1546);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3306"
style="color:#000000;fill:url(#radialGradient1550);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 17.492200,6.8496000 L 17.856000,6.5210000 L 18.583100,6.3564000 C 19.081100,6.1142000 19.581100,5.9511000 20.109500,5.7802000 L 19.819500,5.2865000 L 18.881000,5.4213000 L 18.437600,5.8632000 L 17.706600,5.9692000 L 17.056700,6.2744000 L 16.740800,6.4272000 L 16.547900,6.6855000 L 17.492200,6.8496000"
id="path3308"
style="color:#000000;fill:url(#radialGradient1552);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
id="g3310"
style="color:#000000;fill:url(#radialGradient1554);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<g
id="g3314"
style="color:#000000;fill:url(#radialGradient1558);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
<path
d="M 18.728500,14.666500 L 19.165000,14.008300 L 18.510200,13.515100 L 18.728500,14.666500"
id="path3316"
style="color:#000000;fill:url(#radialGradient4756);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
</g>
<path
style="fill:none;fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#radialGradient4132);stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
d="M 42.975093,23.485534 C 42.975093,33.651354 34.733915,41.892440 24.569493,41.892440 C 14.404139,41.892440 6.1634261,33.651261 6.1634261,23.485534 C 6.1634261,13.320180 14.404139,5.0799340 24.569493,5.0799340 C 34.733915,5.0799340 42.975093,13.320180 42.975093,23.485534 L 42.975093,23.485534 z "
id="path4122" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,118 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import tempfile
import logging
import os
from mediagoblin import mg_globals as mgg
from mediagoblin.processing import mark_entry_failed, \
THUMB_SIZE, MEDIUM_SIZE, create_pub_filepath
from . import transcoders
logging.basicConfig()
_log = logging.getLogger(__name__)
_log.setLevel(logging.DEBUG)
def process_video(entry):
"""
Code to process a video
Much of this code is derived from the arista-transcoder script in
the arista PyPI package and changed to match the needs of
MediaGoblin
This function sets up the arista video encoder in some kind of new thread
and attaches callbacks to that child process, hopefully, the
entry-complete callback will be called when the video is done.
"""
workbench = mgg.workbench_manager.create_workbench()
queued_filepath = entry['queued_media_file']
queued_filename = workbench.localized_file(
mgg.queue_store, queued_filepath,
'source')
medium_filepath = create_pub_filepath(
entry,
'{original}-640p.webm'.format(
original=os.path.splitext(
queued_filepath[-1])[0] # Select the
))
thumbnail_filepath = create_pub_filepath(
entry, 'thumbnail.jpg')
# Create a temporary file for the video destination
tmp_dst = tempfile.NamedTemporaryFile()
with tmp_dst:
# Transcode queued file to a VP8/vorbis file that fits in a 640x640 square
transcoder = transcoders.VideoTranscoder(queued_filename, tmp_dst.name)
# Push transcoded video to public storage
_log.debug('Saving medium...')
mgg.public_store.get_file(medium_filepath, 'wb').write(
tmp_dst.read())
_log.debug('Saved medium')
entry['media_files']['webm_640'] = medium_filepath
# Save the width and height of the transcoded video
entry['media_data']['video'] = {
u'width': transcoder.dst_data.videowidth,
u'height': transcoder.dst_data.videoheight}
# Create a temporary file for the video thumbnail
tmp_thumb = tempfile.NamedTemporaryFile()
with tmp_thumb:
# Create a thumbnail.jpg that fits in a 180x180 square
transcoders.VideoThumbnailer(queued_filename, tmp_thumb.name)
# Push the thumbnail to public storage
_log.debug('Saving thumbnail...')
mgg.public_store.get_file(thumbnail_filepath, 'wb').write(
tmp_thumb.read())
_log.debug('Saved thumbnail')
entry['media_files']['thumb'] = thumbnail_filepath
# Push original file to public storage
queued_file = file(queued_filename, 'rb')
with queued_file:
original_filepath = create_pub_filepath(
entry,
queued_filepath[-1])
with mgg.public_store.get_file(original_filepath, 'wb') as \
original_file:
_log.debug('Saving original...')
original_file.write(queued_file.read())
_log.debug('Saved original')
entry['media_files']['original'] = original_filepath
mgg.queue_store.delete_file(queued_filepath)
# Save the MediaEntry
entry.save()

View File

@ -0,0 +1,658 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division
import os
os.putenv('GST_DEBUG_DUMP_DOT_DIR', '/tmp')
import sys
import logging
import pdb
import urllib
_log = logging.getLogger(__name__)
logging.basicConfig()
_log.setLevel(logging.DEBUG)
CPU_COUNT = 2
try:
import multiprocessing
try:
CPU_COUNT = multiprocessing.cpu_count()
except NotImplementedError:
_log.warning('multiprocessing.cpu_count not implemented')
pass
except ImportError:
_log.warning('Could not import multiprocessing, defaulting to 2 CPU cores')
pass
try:
import gtk
except:
raise Exception('Could not find pygtk')
try:
import gobject
gobject.threads_init()
except:
raise Exception('gobject could not be found')
try:
import pygst
pygst.require('0.10')
import gst
from gst.extend import discoverer
except:
raise Exception('gst/pygst 0.10 could not be found')
class VideoThumbnailer:
# Declaration of thumbnailer states
STATE_NULL = 0
STATE_HALTING = 1
STATE_PROCESSING = 2
# The current thumbnailer state
state = STATE_NULL
# This will contain the thumbnailing pipeline
thumbnail_pipeline = None
buffer_probes = {}
errors = []
def __init__(self, source_path, dest_path):
'''
Set up playbin pipeline in order to get video properties.
Initializes and runs the gobject.MainLoop()
'''
self.source_path = source_path
self.dest_path = dest_path
self.loop = gobject.MainLoop()
# Set up the playbin. It will be used to discover certain
# properties of the input file
self.playbin = gst.element_factory_make('playbin')
self.videosink = gst.element_factory_make('fakesink', 'videosink')
self.playbin.set_property('video-sink', self.videosink)
self.audiosink = gst.element_factory_make('fakesink', 'audiosink')
self.playbin.set_property('audio-sink', self.audiosink)
self.bus = self.playbin.get_bus()
self.bus.add_signal_watch()
self.watch_id = self.bus.connect('message', self._on_bus_message)
self.playbin.set_property('uri', 'file:{0}'.format(
urllib.pathname2url(self.source_path)))
self.playbin.set_state(gst.STATE_PAUSED)
self.run()
def run(self):
self.loop.run()
def _on_bus_message(self, bus, message):
_log.debug(' BUS MESSAGE: {0}'.format(message))
if message.type == gst.MESSAGE_ERROR:
gobject.idle_add(self._on_bus_error)
elif message.type == gst.MESSAGE_STATE_CHANGED:
# The pipeline state has changed
# Parse state changing data
_prev, state, _pending = message.parse_state_changed()
_log.debug('State changed: {0}'.format(state))
if state == gst.STATE_PAUSED:
if message.src == self.playbin:
gobject.idle_add(self._on_bus_paused)
def _on_bus_paused(self):
'''
Set up thumbnailing pipeline
'''
current_video = self.playbin.get_property('current-video')
if current_video == 0:
_log.debug('Found current video from playbin')
else:
_log.error('Could not get any current video from playbin!')
self.duration = self._get_duration(self.playbin)
_log.info('Video length: {0}'.format(self.duration / gst.SECOND))
_log.info('Setting up thumbnailing pipeline')
self.thumbnail_pipeline = gst.parse_launch(
'filesrc location="{0}" ! decodebin ! '
'ffmpegcolorspace ! videoscale ! '
'video/x-raw-rgb,depth=24,bpp=24,pixel-aspect-ratio=1/1,width=180 ! '
'fakesink signal-handoffs=True'.format(self.source_path))
self.thumbnail_bus = self.thumbnail_pipeline.get_bus()
self.thumbnail_bus.add_signal_watch()
self.thumbnail_watch_id = self.thumbnail_bus.connect(
'message', self._on_thumbnail_bus_message)
self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
#gobject.timeout_add(3000, self._on_timeout)
return False
def _on_thumbnail_bus_message(self, bus, message):
_log.debug('Thumbnail bus called, message: {0}'.format(message))
if message.type == gst.MESSAGE_ERROR:
_log.error(message)
gobject.idle_add(self._on_bus_error)
if message.type == gst.MESSAGE_STATE_CHANGED:
_prev, state, _pending = message.parse_state_changed()
if (state == gst.STATE_PAUSED and
not self.state == self.STATE_PROCESSING and
message.src == self.thumbnail_pipeline):
_log.info('Pipeline paused, processing')
self.state = self.STATE_PROCESSING
for sink in self.thumbnail_pipeline.sinks():
name = sink.get_name()
factoryname = sink.get_factory().get_name()
if factoryname == 'fakesink':
sinkpad = sink.get_pad('sink')
self.buffer_probes[name] = sinkpad.add_buffer_probe(
self.buffer_probe_handler, name)
_log.info('Added buffer probe')
break
# Apply the wadsworth constant, fallback to 1 second
seek_amount = max(self.duration / 100 * 30, 1 * gst.SECOND)
_log.debug('seek amount: {0}'.format(seek_amount))
seek_result = self.thumbnail_pipeline.seek(
1.0,
gst.FORMAT_TIME,
gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
gst.SEEK_TYPE_SET,
seek_amount,
gst.SEEK_TYPE_NONE,
0)
if not seek_result:
self.errors.append('COULD_NOT_SEEK')
_log.error('Couldn\'t seek! result: {0}'.format(
seek_result))
_log.info(message)
self.shutdown()
else:
pass
#self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
#pdb.set_trace()
def buffer_probe_handler_real(self, pad, buff, name):
'''
Capture buffers as gdk_pixbufs when told to.
'''
try:
caps = buff.caps
if caps is None:
_log.error('No caps passed to buffer probe handler!')
self.shutdown()
return False
_log.debug('caps: {0}'.format(caps))
filters = caps[0]
width = filters["width"]
height = filters["height"]
pixbuf = gtk.gdk.pixbuf_new_from_data(
buff.data, gtk.gdk.COLORSPACE_RGB, False, 8,
width, height, width * 3)
# NOTE: 200x136 is sort of arbitrary. it's larger than what
# the ui uses at the time of this writing.
# new_width, new_height = scaled_size((width, height), (200, 136))
#pixbuf = pixbuf.scale_simple(
#new_width, new_height, gtk.gdk.INTERP_BILINEAR)
pixbuf.save(self.dest_path, 'jpeg')
_log.info('Saved thumbnail')
del pixbuf
self.shutdown()
except gst.QueryError:
pass
return False
def buffer_probe_handler(self, pad, buff, name):
'''
Proxy function for buffer_probe_handler_real
'''
gobject.idle_add(
lambda: self.buffer_probe_handler_real(pad, buff, name))
return True
def _get_duration(self, pipeline, retries=0):
'''
Get the duration of a pipeline.
Retries 5 times.
'''
if retries == 5:
return 0
try:
return pipeline.query_duration(gst.FORMAT_TIME)[0]
except gst.QueryError:
return self._get_duration(pipeline, retries + 1)
def _on_timeout(self):
_log.error('TIMEOUT! DROP EVERYTHING!')
self.shutdown()
def _on_bus_error(self, *args):
_log.error('AHAHAHA! Error! args: {0}'.format(args))
def shutdown(self):
'''
Tell gobject to call __halt when the mainloop is idle.
'''
_log.info('Shutting down')
self.__halt()
def __halt(self):
'''
Halt all pipelines and shut down the main loop
'''
_log.info('Halting...')
self.state = self.STATE_HALTING
self.__disconnect()
gobject.idle_add(self.__halt_final)
def __disconnect(self):
_log.debug('Disconnecting...')
if not self.playbin is None:
self.playbin.set_state(gst.STATE_NULL)
for sink in self.playbin.sinks():
name = sink.get_name()
factoryname = sink.get_factory().get_name()
_log.debug('Disconnecting {0}'.format(name))
if factoryname == "fakesink":
pad = sink.get_pad("sink")
pad.remove_buffer_probe(self.buffer_probes[name])
del self.buffer_probes[name]
self.playbin = None
if self.bus is not None:
self.bus.disconnect(self.watch_id)
self.bus = None
def __halt_final(self):
_log.info('Done')
if self.errors:
_log.error(','.join(self.errors))
self.loop.quit()
class VideoTranscoder:
'''
Video transcoder
Transcodes the SRC video file to a VP8 WebM video file at DST
- Does the same thing as VideoThumbnailer, but produces a WebM vp8
and vorbis video file.
- The VideoTranscoder exceeds the VideoThumbnailer in the way
that it was refined afterwards and therefore is done more
correctly.
'''
def __init__(self, src, dst, **kwargs):
_log.info('Initializing VideoTranscoder...')
self.loop = gobject.MainLoop()
self.source_path = src
self.destination_path = dst
# Options
self.destination_dimensions = kwargs.get('dimensions') or (640, 640)
self._progress_callback = kwargs.get('progress_callback') or None
if not type(self.destination_dimensions) == tuple:
raise Exception('dimensions must be tuple: (width, height)')
self._setup()
self._run()
def _setup(self):
self._setup_discover()
self._setup_pipeline()
def _run(self):
_log.info('Discovering...')
self.discoverer.discover()
_log.info('Done')
_log.debug('Initializing MainLoop()')
self.loop.run()
def _setup_discover(self):
_log.debug('Setting up discoverer')
self.discoverer = discoverer.Discoverer(self.source_path)
# Connect self.__discovered to the 'discovered' event
self.discoverer.connect('discovered', self.__discovered)
def __discovered(self, data, is_media):
'''
Callback for media discoverer.
'''
if not is_media:
self.__stop()
raise Exception('Could not discover {0}'.format(self.source_path))
_log.debug('__discovered, data: {0}'.format(data.__dict__))
self.data = data
# Launch things that should be done after discovery
self._link_elements()
self.__setup_videoscale_capsfilter()
# Tell the transcoding pipeline to start running
self.pipeline.set_state(gst.STATE_PLAYING)
_log.info('Transcoding...')
def _setup_pipeline(self):
_log.debug('Setting up transcoding pipeline')
# Create the pipeline bin.
self.pipeline = gst.Pipeline('VideoTranscoderPipeline')
# Create all GStreamer elements, starting with
# filesrc & decoder
self.filesrc = gst.element_factory_make('filesrc', 'filesrc')
self.filesrc.set_property('location', self.source_path)
self.pipeline.add(self.filesrc)
self.decoder = gst.element_factory_make('decodebin2', 'decoder')
self.decoder.connect('new-decoded-pad', self._on_dynamic_pad)
self.pipeline.add(self.decoder)
# Video elements
self.videoqueue = gst.element_factory_make('queue', 'videoqueue')
self.pipeline.add(self.videoqueue)
self.videorate = gst.element_factory_make('videorate', 'videorate')
self.pipeline.add(self.videorate)
self.ffmpegcolorspace = gst.element_factory_make(
'ffmpegcolorspace', 'ffmpegcolorspace')
self.pipeline.add(self.ffmpegcolorspace)
self.videoscale = gst.element_factory_make('ffvideoscale', 'videoscale')
#self.videoscale.set_property('method', 2) # I'm not sure this works
#self.videoscale.set_property('add-borders', 0)
self.pipeline.add(self.videoscale)
self.capsfilter = gst.element_factory_make('capsfilter', 'capsfilter')
self.pipeline.add(self.capsfilter)
self.vp8enc = gst.element_factory_make('vp8enc', 'vp8enc')
self.vp8enc.set_property('quality', 6)
self.vp8enc.set_property('threads', 2)
self.pipeline.add(self.vp8enc)
# Audio elements
self.audioqueue = gst.element_factory_make('queue', 'audioqueue')
self.pipeline.add(self.audioqueue)
self.audiorate = gst.element_factory_make('audiorate', 'audiorate')
self.audiorate.set_property('tolerance', 80000000)
self.pipeline.add(self.audiorate)
self.audioconvert = gst.element_factory_make('audioconvert', 'audioconvert')
self.pipeline.add(self.audioconvert)
self.audiocapsfilter = gst.element_factory_make('capsfilter', 'audiocapsfilter')
audiocaps = ['audio/x-raw-float']
self.audiocapsfilter.set_property(
'caps',
gst.caps_from_string(
','.join(audiocaps)))
self.pipeline.add(self.audiocapsfilter)
self.vorbisenc = gst.element_factory_make('vorbisenc', 'vorbisenc')
self.vorbisenc.set_property('quality', 1)
self.pipeline.add(self.vorbisenc)
# WebMmux & filesink
self.webmmux = gst.element_factory_make('webmmux', 'webmmux')
self.pipeline.add(self.webmmux)
self.filesink = gst.element_factory_make('filesink', 'filesink')
self.filesink.set_property('location', self.destination_path)
self.pipeline.add(self.filesink)
# Progressreport
self.progressreport = gst.element_factory_make(
'progressreport', 'progressreport')
# Update every second
self.progressreport.set_property('update-freq', 1)
self.progressreport.set_property('silent', True)
self.pipeline.add(self.progressreport)
def _link_elements(self):
'''
Link all the elements
This code depends on data from the discoverer and is called
from __discovered
'''
_log.debug('linking elements')
# Link the filesrc element to the decoder. The decoder then emits
# 'new-decoded-pad' which links decoded src pads to either a video
# or audio sink
self.filesrc.link(self.decoder)
# Link all the video elements in a row to webmmux
gst.element_link_many(
self.videoqueue,
self.videorate,
self.ffmpegcolorspace,
self.videoscale,
self.capsfilter,
self.vp8enc,
self.webmmux)
if self.data.is_audio:
# Link all the audio elements in a row to webmux
gst.element_link_many(
self.audioqueue,
self.audiorate,
self.audioconvert,
self.audiocapsfilter,
self.vorbisenc,
self.webmmux)
gst.element_link_many(
self.webmmux,
self.progressreport,
self.filesink)
# Setup the message bus and connect _on_message to the pipeline
self._setup_bus()
def _on_dynamic_pad(self, dbin, pad, islast):
'''
Callback called when ``decodebin2`` has a pad that we can connect to
'''
# Intersect the capabilities of the video sink and the pad src
# Then check if they have no common capabilities.
if self.ffmpegcolorspace.get_pad_template('sink')\
.get_caps().intersect(pad.get_caps()).is_empty():
# It is NOT a video src pad.
pad.link(self.audioqueue.get_pad('sink'))
else:
# It IS a video src pad.
pad.link(self.videoqueue.get_pad('sink'))
def _setup_bus(self):
self.bus = self.pipeline.get_bus()
self.bus.add_signal_watch()
self.bus.connect('message', self._on_message)
def __setup_videoscale_capsfilter(self):
'''
Sets up the output format (width, height) for the video
'''
caps = ['video/x-raw-yuv', 'pixel-aspect-ratio=1/1', 'framerate=30/1']
if self.data.videoheight > self.data.videowidth:
# Whoa! We have ourselves a portrait video!
caps.append('height={0}'.format(
self.destination_dimensions[1]))
else:
# It's a landscape, phew, how normal.
caps.append('width={0}'.format(
self.destination_dimensions[0]))
self.capsfilter.set_property(
'caps',
gst.caps_from_string(
','.join(caps)))
def _on_message(self, bus, message):
_log.debug((bus, message, message.type))
t = message.type
if t == gst.MESSAGE_EOS:
self._discover_dst_and_stop()
_log.info('Done')
elif t == gst.MESSAGE_ELEMENT:
if message.structure.get_name() == 'progress':
data = dict(message.structure)
if self._progress_callback:
self._progress_callback(data)
_log.info('{percent}% done...'.format(
percent=data.get('percent')))
_log.debug(data)
elif t == gst.MESSAGE_ERROR:
_log.error((bus, message))
self.__stop()
def _discover_dst_and_stop(self):
self.dst_discoverer = discoverer.Discoverer(self.destination_path)
self.dst_discoverer.connect('discovered', self.__dst_discovered)
self.dst_discoverer.discover()
def __dst_discovered(self, data, is_media):
self.dst_data = data
self.__stop()
def __stop(self):
_log.debug(self.loop)
# Stop executing the pipeline
self.pipeline.set_state(gst.STATE_NULL)
# This kills the loop, mercifully
gobject.idle_add(self.__stop_mainloop)
def __stop_mainloop(self):
'''
Wrapper for gobject.MainLoop.quit()
This wrapper makes us able to see if self.loop.quit has been called
'''
_log.info('Terminating MainLoop')
self.loop.quit()
if __name__ == '__main__':
os.nice(19)
from optparse import OptionParser
parser = OptionParser(
usage='%prog [-v] -a [ video | thumbnail ] SRC DEST')
parser.add_option('-a', '--action',
dest='action',
help='One of "video" or "thumbnail"')
parser.add_option('-v',
dest='verbose',
action='store_true',
help='Output debug information')
parser.add_option('-q',
dest='quiet',
action='store_true',
help='Dear program, please be quiet unless *error*')
(options, args) = parser.parse_args()
if options.verbose:
_log.setLevel(logging.DEBUG)
else:
_log.setLevel(logging.INFO)
if options.quiet:
_log.setLevel(logging.ERROR)
_log.debug(args)
if not len(args) == 2:
parser.print_help()
sys.exit()
if options.action == 'thumbnail':
VideoThumbnailer(*args)
elif options.action == 'video':
def cb(data):
print('I\'m a callback!')
transcoder = VideoTranscoder(*args, progress_callback=cb)

View File

@ -1,45 +0,0 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
class BaseProcessingFail(Exception):
"""
Base exception that all other processing failure messages should
subclass from.
You shouldn't call this itself; instead you should subclass it
and provid the exception_path and general_message applicable to
this error.
"""
general_message = u''
@property
def exception_path(self):
return u"%s:%s" % (
self.__class__.__module__, self.__class__.__name__)
def __init__(self, **metadata):
self.metadata = metadata or {}
class BadMediaFail(BaseProcessingFail):
"""
Error that should be raised when an inappropriate file was given
for the media type specified.
"""
general_message = _(u'Invalid file given for media type.')

143
mediagoblin/processing.py Normal file
View File

@ -0,0 +1,143 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from celery.task import Task
from mediagoblin.db.util import ObjectId
from mediagoblin import mg_globals as mgg
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.media_types import get_media_manager
THUMB_SIZE = 180, 180
MEDIUM_SIZE = 640, 640
def create_pub_filepath(entry, filename):
return mgg.public_store.get_unique_filepath(
['media_entries',
unicode(entry._id),
filename])
################################
# Media processing initial steps
################################
class ProcessMedia(Task):
"""
DEPRECATED -- This now resides in the individual media plugins
Pass this entry off for processing.
"""
def run(self, media_id):
"""
Pass the media entry off to the appropriate processing function
(for now just process_image...)
"""
entry = mgg.database.MediaEntry.one(
{'_id': ObjectId(media_id)})
# Try to process, and handle expected errors.
try:
#__import__(entry['media_type'])
manager = get_media_manager(entry['media_type'])
manager['processor'](entry)
except BaseProcessingFail, exc:
mark_entry_failed(entry._id, exc)
return
except ImportError, exc:
mark_entry_failed(entry[u'_id'], exc)
entry['state'] = u'processed'
entry.save()
def on_failure(self, exc, task_id, args, kwargs, einfo):
"""
If the processing failed we should mark that in the database.
Assuming that the exception raised is a subclass of
BaseProcessingFail, we can use that to get more information
about the failure and store that for conveying information to
users about the failure, etc.
"""
entry_id = args[0]
mark_entry_failed(entry_id, exc)
def mark_entry_failed(entry_id, exc):
"""
Mark a media entry as having failed in its conversion.
Uses the exception that was raised to mark more information. If
the exception is a derivative of BaseProcessingFail then we can
store extra information that can be useful for users telling them
why their media failed to process.
Args:
- entry_id: The id of the media entry
"""
# Was this a BaseProcessingFail? In other words, was this a
# type of error that we know how to handle?
if isinstance(exc, BaseProcessingFail):
# Looks like yes, so record information about that failure and any
# metadata the user might have supplied.
mgg.database['media_entries'].update(
{'_id': entry_id},
{'$set': {u'state': u'failed',
u'fail_error': exc.exception_path,
u'fail_metadata': exc.metadata}})
else:
# Looks like no, so just mark it as failed and don't record a
# failure_error (we'll assume it wasn't handled) and don't record
# metadata (in fact overwrite it if somehow it had previous info
# here)
mgg.database['media_entries'].update(
{'_id': entry_id},
{'$set': {u'state': u'failed',
u'fail_error': None,
u'fail_metadata': {}}})
class BaseProcessingFail(Exception):
"""
Base exception that all other processing failure messages should
subclass from.
You shouldn't call this itself; instead you should subclass it
and provid the exception_path and general_message applicable to
this error.
"""
general_message = u''
@property
def exception_path(self):
return u"%s:%s" % (
self.__class__.__module__, self.__class__.__name__)
def __init__(self, **metadata):
self.metadata = metadata or {}
class BadMediaFail(BaseProcessingFail):
"""
Error that should be raised when an inappropriate file was given
for the media type specified.
"""
general_message = _(u'Invalid file given for media type.')

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -98,8 +98,14 @@ class CloudFilesStorage(StorageInterface):
def delete_file(self, filepath):
# TODO: Also delete unused directories if empty (safely, with
# checks to avoid race conditions).
self.container.delete_object(
self._resolve_filepath(filepath))
try:
self.container.delete_object(
self._resolve_filepath(filepath))
except cloudfiles.container.ResponseError:
pass
finally:
pass
def file_url(self, filepath):
return '/'.join([

View File

@ -19,6 +19,8 @@ import uuid
from os.path import splitext
from cgi import FieldStorage
from celery import registry
from werkzeug.utils import secure_filename
from mediagoblin.db.util import ObjectId
@ -27,8 +29,9 @@ from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.decorators import require_active_login
from mediagoblin.submit import forms as submit_forms, security
from mediagoblin.process_media import process_media, mark_entry_failed
from mediagoblin.processing import mark_entry_failed, ProcessMedia
from mediagoblin.messages import add_message, SUCCESS
from mediagoblin.media_types import get_media_type_and_manager, InvalidFileType
@require_active_login
@ -44,86 +47,90 @@ def submit_start(request):
and request.POST['file'].file):
submit_form.file.errors.append(
_(u'You must provide a file.'))
elif not security.check_filetype(request.POST['file']):
submit_form.file.errors.append(
_(u"The file doesn't seem to be an image!"))
else:
filename = request.POST['file'].filename
# create entry and save in database
entry = request.db.MediaEntry()
entry['_id'] = ObjectId()
entry['title'] = (
unicode(request.POST['title'])
or unicode(splitext(filename)[0]))
entry['description'] = unicode(request.POST.get('description'))
entry['description_html'] = cleaned_markdown_conversion(
entry['description'])
entry['media_type'] = u'image' # heh
entry['uploader'] = request.user._id
# Process the user's folksonomy "tags"
entry['tags'] = convert_to_tag_list_of_dicts(
request.POST.get('tags'))
# Generate a slug from the title
entry.generate_slug()
# Now store generate the queueing related filename
queue_filepath = request.app.queue_store.get_unique_filepath(
['media_entries',
unicode(entry._id),
secure_filename(filename)])
# queue appropriately
queue_file = request.app.queue_store.get_file(
queue_filepath, 'wb')
with queue_file:
queue_file.write(request.POST['file'].file.read())
# Add queued filename to the entry
entry['queued_media_file'] = queue_filepath
# We generate this ourselves so we know what the taks id is for
# retrieval later.
# (If we got it off the task's auto-generation, there'd be
# a risk of a race condition when we'd save after sending
# off the task)
task_id = unicode(uuid.uuid4())
entry['queued_task_id'] = task_id
# Save now so we have this data before kicking off processing
entry.save(validate=True)
# Pass off to processing
#
# (... don't change entry after this point to avoid race
# conditions with changes to the document via processing code)
try:
process_media.apply_async(
[unicode(entry._id)], {},
task_id=task_id)
except BaseException as exc:
# The purpose of this section is because when running in "lazy"
# or always-eager-with-exceptions-propagated celery mode that
# the failure handling won't happen on Celery end. Since we
# expect a lot of users to run things in this way we have to
# capture stuff here.
filename = request.POST['file'].filename
media_type, media_manager = get_media_type_and_manager(filename)
# create entry and save in database
entry = request.db.MediaEntry()
entry['_id'] = ObjectId()
entry['media_type'] = unicode(media_type)
entry['title'] = (
unicode(request.POST['title'])
or unicode(splitext(filename)[0]))
entry['description'] = unicode(request.POST.get('description'))
entry['description_html'] = cleaned_markdown_conversion(
entry['description'])
entry['uploader'] = request.user['_id']
# Process the user's folksonomy "tags"
entry['tags'] = convert_to_tag_list_of_dicts(
request.POST.get('tags'))
# Generate a slug from the title
entry.generate_slug()
# Now store generate the queueing related filename
queue_filepath = request.app.queue_store.get_unique_filepath(
['media_entries',
unicode(entry._id),
secure_filename(filename)])
# queue appropriately
queue_file = request.app.queue_store.get_file(
queue_filepath, 'wb')
with queue_file:
queue_file.write(request.POST['file'].file.read())
# Add queued filename to the entry
entry['queued_media_file'] = queue_filepath
# We generate this ourselves so we know what the taks id is for
# retrieval later.
# (If we got it off the task's auto-generation, there'd be
# a risk of a race condition when we'd save after sending
# off the task)
task_id = unicode(uuid.uuid4())
entry['queued_task_id'] = task_id
# Save now so we have this data before kicking off processing
entry.save(validate=True)
# Pass off to processing
#
# ... not completely the diaper pattern because the
# exception is re-raised :)
mark_entry_failed(entry._id, exc)
# re-raise the exception
raise
# (... don't change entry after this point to avoid race
# conditions with changes to the document via processing code)
process_media = registry.tasks[ProcessMedia.name]
try:
process_media.apply_async(
[unicode(entry._id)], {},
task_id=task_id)
except BaseException as exc:
# The purpose of this section is because when running in "lazy"
# or always-eager-with-exceptions-propagated celery mode that
# the failure handling won't happen on Celery end. Since we
# expect a lot of users to run things in this way we have to
# capture stuff here.
#
# ... not completely the diaper pattern because the
# exception is re-raised :)
mark_entry_failed(entry._id, exc)
# re-raise the exception
raise
add_message(request, SUCCESS, _('Woohoo! Submitted!'))
add_message(request, SUCCESS, _('Woohoo! Submitted!'))
return redirect(request, "mediagoblin.user_pages.user_home",
user=request.user['username'])
return redirect(request, "mediagoblin.user_pages.user_home",
user=request.user['username'])
except InvalidFileType, exc:
submit_form.file.errors.append(
_(u'Invalid file type.'))
return render_to_response(
request,

View File

@ -28,8 +28,15 @@
href="{{ request.staticdirect('/css/extlib/960_16_col.css') }}"/>
<link rel="stylesheet" type="text/css"
href="{{ request.staticdirect('/css/base.css') }}"/>
<link rel="stylesheet" type="text/css"
href="{{ request.staticdirect('/css/video-js.css') }}"/>
<link rel="shortcut icon"
href="{{ request.staticdirect('/images/goblin.ico') }}" />
<script type="text/javascript"
src="{{ request.staticdirect('/js/lib/video.js') }}"></script>
<script type="text/javascript"
src="{{ request.staticdirect('/js/video.js') }}"></script>
<script type="text/javascript" src="http://html5.kaltura.org/js" > </script>
{% block mediagoblin_head %}
{% endblock mediagoblin_head %}
</head>

View File

@ -0,0 +1 @@
{% extends 'mediagoblin/user_pages/media.html' %}

View File

@ -0,0 +1,25 @@
{% extends 'mediagoblin/user_pages/media.html' %}
{% block mediagoblin_media %}
<div class="video-player" style="position: relative;">
<video class="video-js vjs-default-skin"
width="{{ media.media_data.video.width }}"
height="{{ media.media_data.video.height }}"
controls="controls"
preload="auto"
data-setup="">
<source src="{{ request.app.public_store.file_url(
media['media_files']['webm_640']) }}"
type="video/webm; codecs=&quot;vp8, vorbis&quot;" />
</video>
</div>
{% if 'original' in media.media_files %}
<p>
<a href="{{ request.app.public_store.file_url(
media['media_files']['original']) }}">
{%- trans -%}
Original
{%- endtrans -%}
</a>
</p>
{% endif %}
{% endblock %}

View File

@ -26,24 +26,26 @@
{% if media %}
<div class="grid_11 alpha">
<div class="media_image_container">
{% set display_media = request.app.public_store.file_url(
media.get_display_media(media.media_files)) %}
{% block mediagoblin_media %}
{% set display_media = request.app.public_store.file_url(
media.get_display_media(media.media_files)) %}
{# if there's a medium file size, that means the medium size
# isn't the original... so link to the original!
#}
{% if media['media_files'].has_key('medium') %}
<a href="{{ request.app.public_store.file_url(
media['media_files']['original']) }}">
{# if there's a medium file size, that means the medium size
# isn't the original... so link to the original!
#}
{% if media['media_files'].has_key('medium') %}
<a href="{{ request.app.public_store.file_url(
media['media_files']['original']) }}">
<img class="media_image"
src="{{ display_media }}"
alt="Image for {{ media.title }}" />
</a>
{% else %}
<img class="media_image"
src="{{ display_media }}"
alt="Image for {{ media.title }}" />
</a>
{% else %}
<img class="media_image"
src="{{ display_media }}"
alt="Image for {{ media.title }}" />
{% endif %}
{% endif %}
{% endblock %}
</div>
<h2 class="media_title">

View File

@ -50,7 +50,7 @@ def test_setup_celery_from_config():
assert isinstance(fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION, float)
assert fake_celery_module.CELERY_RESULT_PERSISTENT is True
assert fake_celery_module.CELERY_IMPORTS == [
'foo.bar.baz', 'this.is.an.import', 'mediagoblin.process_media']
'foo.bar.baz', 'this.is.an.import', 'mediagoblin.processing']
assert fake_celery_module.CELERY_MONGODB_BACKEND_SETTINGS == {
'database': 'mediagoblin'}
assert fake_celery_module.CELERY_RESULT_BACKEND == 'mongodb'
@ -74,7 +74,7 @@ def test_setup_celery_from_config():
assert isinstance(fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION, float)
assert fake_celery_module.CELERY_RESULT_PERSISTENT is False
assert fake_celery_module.CELERY_IMPORTS == [
'baz.bar.foo', 'import.is.a.this', 'mediagoblin.process_media']
'baz.bar.foo', 'import.is.a.this', 'mediagoblin.processing']
assert fake_celery_module.CELERY_MONGODB_BACKEND_SETTINGS == {
'database': 'captain_lollerskates',
'host': 'mongodb.example.org',

View File

@ -222,7 +222,7 @@ class TestSubmission:
context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/submit/start.html']
form = context['submit_form']
assert form.file.errors == ['The file doesn\'t seem to be an image!']
assert form.file.errors == [u'Invalid file type.']
# NOTE: The following 2 tests will ultimately fail, but they
# *will* pass the initial form submission step. Instead,
@ -246,7 +246,7 @@ class TestSubmission:
assert_equal(entry['state'], 'failed')
assert_equal(
entry['fail_error'],
u'mediagoblin.process_media.errors:BadMediaFail')
u'mediagoblin.processing:BadMediaFail')
# Test non-supported file with .png extension
# -------------------------------------------
@ -266,4 +266,4 @@ class TestSubmission:
assert_equal(entry['state'], 'failed')
assert_equal(
entry['fail_error'],
u'mediagoblin.process_media.errors:BadMediaFail')
u'mediagoblin.processing:BadMediaFail')

View File

@ -30,6 +30,8 @@ from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
from werkzeug.contrib.atom import AtomFeed
from mediagoblin.media_types import get_media_manager
@uses_pagination
def user_home(request, page):
@ -120,9 +122,11 @@ def media_home(request, media, page, **kwargs):
comment_form = user_forms.MediaCommentForm(request.POST)
media_template_name = get_media_manager(media['media_type'])['display_template']
return render_to_response(
request,
'mediagoblin/user_pages/media.html',
media_template_name,
{'media': media,
'comments': comments,
'pagination': pagination,

View File

@ -14,11 +14,15 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
from mediagoblin import mg_globals
from mediagoblin.tools.pagination import Pagination
from mediagoblin.tools.response import render_to_response
from mediagoblin.db.util import DESCENDING
from mediagoblin.decorators import uses_pagination
from mediagoblin import media_types
@uses_pagination
@ -28,12 +32,12 @@ def root_view(request, page):
pagination = Pagination(page, cursor)
media_entries = pagination()
return render_to_response(
request, 'mediagoblin/root.html',
{'media_entries': media_entries,
'allow_registration': mg_globals.app_config["allow_registration"],
'pagination': pagination})
'pagination': pagination,
'sys': sys})
def simple_template_render(request):

View File

@ -47,7 +47,10 @@ class Workbench(object):
return str(self.dir)
def __repr__(self):
return repr(self.dir)
try:
return str(self)
except AttributeError:
return 'None'
def joinpath(self, *args):
return os.path.join(self.dir, *args)

View File

@ -66,6 +66,7 @@ setup(
## their package managers.
# 'lxml',
],
requires=['gst'],
test_suite='nose.collector',
entry_points="""\
[console_scripts]