aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin')
-rw-r--r--mediagoblin/api/views.py12
-rw-r--r--mediagoblin/auth/views.py7
-rw-r--r--mediagoblin/db/migration_tools.py5
-rw-r--r--mediagoblin/db/migrations/alembic.ini56
-rw-r--r--mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py36
-rw-r--r--mediagoblin/db/models.py38
-rw-r--r--mediagoblin/decorators.py3
-rw-r--r--mediagoblin/edit/routing.py2
-rw-r--r--mediagoblin/edit/views.py36
-rw-r--r--mediagoblin/gmg_commands/__init__.py5
-rw-r--r--mediagoblin/gmg_commands/batchaddmedia.py19
-rw-r--r--mediagoblin/init/config.py1
-rw-r--r--mediagoblin/media_types/blog/views.py4
-rw-r--r--mediagoblin/media_types/video/processing.py21
-rw-r--r--mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html.orig60
-rw-r--r--mediagoblin/plugins/subtitles/__init__.py50
-rw-r--r--mediagoblin/plugins/subtitles/forms.py29
-rw-r--r--mediagoblin/plugins/subtitles/models.py49
-rw-r--r--mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/custom_subtitles.html48
-rw-r--r--mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitle_media_block.html50
-rw-r--r--mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitles.html69
-rw-r--r--mediagoblin/plugins/subtitles/tools.py42
-rw-r--r--mediagoblin/plugins/subtitles/views.py186
-rw-r--r--mediagoblin/static/css/lightbox.css21
-rw-r--r--mediagoblin/static/css/subtitles.css4
-rw-r--r--mediagoblin/static/js/lightbox.js70
-rw-r--r--mediagoblin/templates/mediagoblin/api/host-meta.xml (renamed from mediagoblin/templates/mediagoblin/federation/host-meta.xml)0
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/video.html4
-rw-r--r--mediagoblin/templates/mediagoblin/moderation/media_panel.html1
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media.html1
-rw-r--r--mediagoblin/tests/.gitignore1
-rw-r--r--mediagoblin/tests/fake_carrot_conf_good.ini2
-rw-r--r--mediagoblin/tests/resources.py1
-rw-r--r--mediagoblin/tests/test_api.py90
-rw-r--r--mediagoblin/tests/test_auth.py2
-rw-r--r--mediagoblin/tests/test_config.py4
-rw-r--r--mediagoblin/tests/test_exif.py17
-rw-r--r--mediagoblin/tests/test_exif/bad-gps.jpgbin0 -> 141096 bytes
-rw-r--r--mediagoblin/tests/test_subtitles.py68
-rw-r--r--mediagoblin/themes/airy/assets/css/airy.css4
-rw-r--r--mediagoblin/tools/exif.py25
-rw-r--r--mediagoblin/tools/files.py7
-rw-r--r--mediagoblin/tools/licenses.py37
-rw-r--r--mediagoblin/tools/subtitles.py20
-rw-r--r--mediagoblin/tools/validator.py26
-rw-r--r--mediagoblin/user_pages/routing.py2
-rw-r--r--mediagoblin/user_pages/views.py22
47 files changed, 1097 insertions, 160 deletions
diff --git a/mediagoblin/api/views.py b/mediagoblin/api/views.py
index 74181fde..dfa9dfa2 100644
--- a/mediagoblin/api/views.py
+++ b/mediagoblin/api/views.py
@@ -115,8 +115,16 @@ def uploads_endpoint(request):
)
mimetype = request.headers["Content-Type"]
- filename = mimetypes.guess_all_extensions(mimetype)
- filename = 'unknown' + filename[0] if filename else filename
+
+ if "X-File-Name" in request.headers:
+ filename = request.headers["X-File-Name"]
+ else:
+ filenames = sorted(mimetypes.guess_all_extensions(mimetype))
+ if not filenames:
+ return json_error('Unknown mimetype: {}'.format(mimetype),
+ status=415)
+ filename = 'unknown{0}'.format(filenames[0])
+
file_data = FileStorage(
stream=io.BytesIO(request.data),
filename=filename,
diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py
index 2f95fd81..593d588d 100644
--- a/mediagoblin/auth/views.py
+++ b/mediagoblin/auth/views.py
@@ -14,6 +14,8 @@
# 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 logging
+
import six
from itsdangerous import BadSignature
@@ -29,6 +31,8 @@ from mediagoblin.tools.pluginapi import hook_handle
from mediagoblin.auth.tools import (send_verification_email, register_user,
check_login_simple)
+_log = logging.getLogger(__name__)
+
@allow_registration
@auth_enabled
@@ -105,6 +109,9 @@ def login(request):
return redirect(request, "index")
login_failed = True
+ remote_addr = (request.access_route and request.access_route[-1]
+ or request.remote_addr)
+ _log.warn("Failed login attempt from %r", remote_addr)
return render_to_response(
request,
diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py
index f4273fa0..852f35ee 100644
--- a/mediagoblin/db/migration_tools.py
+++ b/mediagoblin/db/migration_tools.py
@@ -365,9 +365,8 @@ def build_alembic_config(global_config, cmd_options, session):
configuration. Initialize the database session appropriately
as well.
"""
- root_dir = os.path.abspath(os.path.dirname(os.path.dirname(
- os.path.dirname(__file__))))
- alembic_cfg_path = os.path.join(root_dir, 'alembic.ini')
+ alembic_dir = os.path.join(os.path.dirname(__file__), 'migrations')
+ alembic_cfg_path = os.path.join(alembic_dir, 'alembic.ini')
cfg = Config(alembic_cfg_path,
cmd_opts=cmd_options)
cfg.attributes["session"] = session
diff --git a/mediagoblin/db/migrations/alembic.ini b/mediagoblin/db/migrations/alembic.ini
new file mode 100644
index 00000000..4f7fc115
--- /dev/null
+++ b/mediagoblin/db/migrations/alembic.ini
@@ -0,0 +1,56 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = %(here)s
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# max length of characters to apply to the
+# "slug" field
+#truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py b/mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py
new file mode 100644
index 00000000..565d4864
--- /dev/null
+++ b/mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py
@@ -0,0 +1,36 @@
+"""Subtitle plugin initial migration
+
+Revision ID: afd3d1da5e29
+Revises: 228916769bd2
+Create Date: 2016-06-03 11:48:03.369079
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'afd3d1da5e29'
+down_revision = '228916769bd2'
+branch_labels = ('subtitles_plugin',)
+depends_on = None
+
+from alembic import op
+import sqlalchemy as sa
+from mediagoblin.db.extratypes import PathTupleWithSlashes
+
+def upgrade():
+ ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('core__subtitle_files',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('media_entry', sa.Integer(), nullable=False),
+ sa.Column('name', sa.Unicode(), nullable=False),
+ sa.Column('filepath', PathTupleWithSlashes(), nullable=True),
+ sa.Column('created', sa.DateTime(), nullable=False),
+ sa.ForeignKeyConstraint(['media_entry'], [u'core__media_entries.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ ### end Alembic commands ###
+
+
+def downgrade():
+ ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('core__subtitle_files')
+ ### end Alembic commands ###
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index b2dcb6ad..0974676a 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -43,6 +43,7 @@ from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
from mediagoblin.tools.files import delete_media_files
from mediagoblin.tools.common import import_component
from mediagoblin.tools.routing import extract_url_arguments
+from mediagoblin.tools.text import convert_to_tag_list_of_dicts
import six
from six.moves.urllib.parse import urljoin
@@ -574,6 +575,15 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
name=v["name"], filepath=v["filepath"])
)
+ subtitle_files_helper = relationship("MediaSubtitleFile",
+ cascade="all, delete-orphan",
+ order_by="MediaSubtitleFile.created"
+ )
+ subtitle_files = association_proxy("subtitle_files_helper", "dict_view",
+ creator=lambda v: MediaSubtitleFile(
+ name=v["name"], filepath=v["filepath"])
+ )
+
tags_helper = relationship("MediaTag",
cascade="all, delete-orphan" # should be automatically deleted
)
@@ -771,7 +781,6 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
"self": {
"href": public_id,
},
-
}
}
@@ -787,6 +796,12 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
if self.location:
context["location"] = self.get_location.serialize(request)
+ # Always show tags, even if empty list
+ if self.tags:
+ context["tags"] = [tag['name'] for tag in self.tags]
+ else:
+ context["tags"] = []
+
if show_comments:
comments = [
l.comment().serialize(request) for l in self.get_comments()]
@@ -834,6 +849,9 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
if "location" in data:
License.create(data["location"], self)
+ if "tags" in data:
+ self.tags = convert_to_tag_list_of_dicts(', '.join(data["tags"]))
+
return True
class FileKeynames(Base):
@@ -899,6 +917,22 @@ class MediaAttachmentFile(Base):
"""A dict like view on this object"""
return DictReadAttrProxy(self)
+class MediaSubtitleFile(Base):
+ __tablename__ = "core__subtitle_files"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id),
+ nullable=False)
+ name = Column(Unicode, nullable=False)
+ filepath = Column(PathTupleWithSlashes)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+
+ @property
+ def dict_view(self):
+ """A dict like view on this object"""
+ return DictReadAttrProxy(self)
+
class Tag(Base):
__tablename__ = "core__tags"
@@ -1610,7 +1644,7 @@ class Graveyard(Base):
return context
MODELS = [
LocalUser, RemoteUser, User, MediaEntry, Tag, MediaTag, Comment, TextComment,
- Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile,
+ Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile, MediaSubtitleFile,
ProcessingMetaData, Notification, Client, CommentSubscription, Report,
UserBan, Privilege, PrivilegeUserAssociation, RequestToken, AccessToken,
NonceTimestamp, Activity, Generator, Location, GenericModelReference, Graveyard]
diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py
index daeddb3f..2b8343b8 100644
--- a/mediagoblin/decorators.py
+++ b/mediagoblin/decorators.py
@@ -268,8 +268,7 @@ def get_media_entry_by_id(controller):
@wraps(controller)
def wrapper(request, *args, **kwargs):
media = MediaEntry.query.filter_by(
- id=request.matchdict['media_id'],
- state=u'processed').first()
+ id=request.matchdict['media_id']).first()
# Still no media? Okay, 404.
if not media:
return render_404(request)
diff --git a/mediagoblin/edit/routing.py b/mediagoblin/edit/routing.py
index b349975d..d3ae5465 100644
--- a/mediagoblin/edit/routing.py
+++ b/mediagoblin/edit/routing.py
@@ -29,4 +29,4 @@ add_route('mediagoblin.edit.verify_email', '/edit/verify_email/',
add_route('mediagoblin.edit.email', '/edit/email/',
'mediagoblin.edit.views:change_email')
add_route('mediagoblin.edit.deauthorize_applications', '/edit/deauthorize/',
- 'mediagoblin.edit.views:deauthorize_applications')
+ 'mediagoblin.edit.views:deauthorize_applications') \ No newline at end of file
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index b15fb2e7..717241e8 100644
--- a/mediagoblin/edit/views.py
+++ b/mediagoblin/edit/views.py
@@ -1,4 +1,4 @@
-# GNU MediaGoblin -- federated, autonomous media hosting
+
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
@@ -34,7 +34,7 @@ from mediagoblin.edit.lib import may_edit_media
from mediagoblin.decorators import (require_active_login, active_user_from_url,
get_media_entry_by_id, user_may_alter_collection,
get_user_collection, user_has_privilege,
- user_not_banned)
+ user_not_banned, user_may_delete_media)
from mediagoblin.tools.crypto import get_timed_signer_url
from mediagoblin.tools.metadata import (compact_and_validate, DEFAULT_CHECKER,
DEFAULT_SCHEMA)
@@ -55,6 +55,10 @@ import mimetypes
@get_media_entry_by_id
@require_active_login
def edit_media(request, media):
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
if not may_edit_media(request, media):
raise Forbidden("User may not edit this media")
@@ -66,7 +70,7 @@ def edit_media(request, media):
license=media.license)
form = forms.EditForm(
- request.form,
+ request.method=='POST' and request.form or None,
**defaults)
if request.method == 'POST' and form.validate():
@@ -115,6 +119,10 @@ UNSAFE_MIMETYPES = [
@get_media_entry_by_id
@require_active_login
def edit_attachments(request, media):
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
if mg_globals.app_config['allow_attachments']:
form = forms.EditAttachmentsForm()
@@ -211,7 +219,8 @@ def edit_profile(request, url_user=None):
else:
location = user.get_location.name
- form = forms.EditProfileForm(request.form,
+ form = forms.EditProfileForm(
+ request.method == 'POST' and request.form or None,
url=user.url,
bio=user.bio,
location=location)
@@ -227,6 +236,8 @@ def edit_profile(request, url_user=None):
location = user.get_location
location.name = six.text_type(form.location.data)
location.save()
+ else:
+ user.location = None
user.save()
@@ -252,7 +263,8 @@ EMAIL_VERIFICATION_TEMPLATE = (
@require_active_login
def edit_account(request):
user = request.user
- form = forms.EditAccountForm(request.form,
+ form = forms.EditAccountForm(
+ request.method == 'POST' and request.form or None,
wants_comment_notification=user.wants_comment_notification,
license_preference=user.license_preference,
wants_notifications=user.wants_notifications)
@@ -350,7 +362,7 @@ def edit_collection(request, collection):
description=collection.description)
form = forms.EditCollectionForm(
- request.form,
+ request.method == 'POST' and request.form or None,
**defaults)
if request.method == 'POST' and form.validate():
@@ -446,7 +458,8 @@ def verify_email(request):
@require_active_login
def change_email(request):
""" View to change the user's email """
- form = forms.ChangeEmailForm(request.form)
+ form = forms.ChangeEmailForm(
+ request.method == 'POST' and request.form or None)
user = request.user
# If no password authentication, no need to enter a password
@@ -499,7 +512,12 @@ def change_email(request):
@require_active_login
@get_media_entry_by_id
def edit_metadata(request, media):
- form = forms.EditMetaDataForm(request.form)
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
+ form = forms.EditMetaDataForm(
+ request.method == 'POST' and request.form or None)
if request.method == "POST" and form.validate():
metadata_dict = dict([(row['identifier'],row['value'])
for row in form.media_metadata.data])
@@ -520,4 +538,4 @@ def edit_metadata(request, media):
request,
'mediagoblin/edit/metadata.html',
{'form':form,
- 'media':media})
+ 'media':media}) \ No newline at end of file
diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py
index 98b097a6..0034fd98 100644
--- a/mediagoblin/gmg_commands/__init__.py
+++ b/mediagoblin/gmg_commands/__init__.py
@@ -145,7 +145,10 @@ def main_cli():
os.path.join(parent_directory, "mediagoblin.example.ini"),
os.path.join(parent_directory, "mediagoblin.ini"))
- args.func(args)
+ try:
+ args.func(args)
+ except AttributeError: # no subcommand or no func of subcommand
+ parser.print_help()
if __name__ == '__main__':
diff --git a/mediagoblin/gmg_commands/batchaddmedia.py b/mediagoblin/gmg_commands/batchaddmedia.py
index 274d72bc..55ed865b 100644
--- a/mediagoblin/gmg_commands/batchaddmedia.py
+++ b/mediagoblin/gmg_commands/batchaddmedia.py
@@ -19,6 +19,7 @@ from __future__ import print_function
import codecs
import csv
import os
+import sys
import requests
import six
@@ -28,8 +29,7 @@ from six.moves.urllib.parse import urlparse
from mediagoblin.db.models import LocalUser
from mediagoblin.gmg_commands import util as commands_util
from mediagoblin.submit.lib import (
- submit_media, get_upload_file_limits,
- FileUploadLimit, UserUploadLimit, UserPastUploadLimit)
+ submit_media, FileUploadLimit, UserUploadLimit, UserPastUploadLimit)
from mediagoblin.tools.metadata import compact_and_validate
from mediagoblin.tools.translate import pass_to_ugettext as _
from jsonschema.exceptions import ValidationError
@@ -73,8 +73,6 @@ def batchaddmedia(args):
username=args.username)))
return
- temp_files = []
-
if os.path.isfile(args.metadata_path):
metadata_path = args.metadata_path
@@ -99,7 +97,7 @@ def batchaddmedia(args):
contents = all_metadata.read()
media_metadata = parse_csv_file(contents)
- for media_id, file_metadata in media_metadata.iteritems():
+ for media_id, file_metadata in media_metadata.items():
files_attempted += 1
# In case the metadata was not uploaded initialize an empty dictionary.
json_ld_metadata = compact_and_validate({})
@@ -113,6 +111,7 @@ def batchaddmedia(args):
title = file_metadata.get('title') or file_metadata.get('dc:title')
description = (file_metadata.get('description') or
file_metadata.get('dc:description'))
+ collection_slug = file_metadata.get('collection-slug')
license = file_metadata.get('license')
try:
@@ -141,7 +140,7 @@ Metadata was not uploaded.""".format(
file_path = os.path.join(abs_metadata_dir, path)
file_abs_path = os.path.abspath(file_path)
try:
- media_file = file(file_abs_path, 'r')
+ media_file = open(file_abs_path, 'rb')
except IOError:
print(_(u"""\
FAIL: Local file {filename} could not be accessed.
@@ -155,6 +154,7 @@ FAIL: Local file {filename} could not be accessed.
filename=filename,
title=maybe_unicodeify(title),
description=maybe_unicodeify(description),
+ collection_slug=maybe_unicodeify(collection_slug),
license=maybe_unicodeify(license),
metadata=json_ld_metadata,
tags_string=u"")
@@ -203,7 +203,12 @@ def parse_csv_file(file_contents):
# Build a dictionary
for index, line in enumerate(lines):
if line.isspace() or line == u'': continue
- values = unicode_csv_reader([line]).next()
+ if (sys.version_info[0] == 3):
+ # Python 3's csv.py supports Unicode out of the box.
+ reader = csv.reader([line])
+ else:
+ reader = unicode_csv_reader([line])
+ values = next(reader)
line_dict = dict([(key[i], val)
for i, val in enumerate(values)])
media_id = line_dict.get('id') or index
diff --git a/mediagoblin/init/config.py b/mediagoblin/init/config.py
index a9189e8d..fe469156 100644
--- a/mediagoblin/init/config.py
+++ b/mediagoblin/init/config.py
@@ -123,6 +123,7 @@ def read_mediagoblin_config(config_path, config_spec_path=CONFIG_SPEC_PATH):
config = ConfigObj(
config_path,
configspec=config_spec,
+ encoding="UTF8",
interpolation="ConfigParser")
_setup_defaults(config, config_path, mainconfig_defaults)
diff --git a/mediagoblin/media_types/blog/views.py b/mediagoblin/media_types/blog/views.py
index f1d5c49d..2bf2e5be 100644
--- a/mediagoblin/media_types/blog/views.py
+++ b/mediagoblin/media_types/blog/views.py
@@ -376,7 +376,9 @@ def blog_about_view(request):
user = request.db.LocalUser.query.filter(
LocalUser.username==url_user
).first()
- blog = get_blog_by_slug(request, blog_slug, author=user.id)
+
+ if user:
+ blog = get_blog_by_slug(request, blog_slug, author=user.id)
if not user or not blog:
return render_404(request)
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
index c377d100..012ba352 100644
--- a/mediagoblin/media_types/video/processing.py
+++ b/mediagoblin/media_types/video/processing.py
@@ -81,7 +81,17 @@ def sniffer(media_file):
return MEDIA_TYPE
+EXCLUDED_EXTS = ["nef", "svg"]
+
def sniff_handler(media_file, filename):
+ name, ext = os.path.splitext(filename)
+ clean_ext = ext.lower()[1:]
+
+ if clean_ext in EXCLUDED_EXTS:
+ # We don't handle this filetype, though gstreamer might think we can
+ _log.info('Refused to process {0} due to excluded extension'.format(filename))
+ return None
+
try:
return sniffer(media_file)
except:
@@ -108,10 +118,13 @@ def get_tags(stream_info):
# TODO: handle timezone info; gst.get_time_zone_offset +
# python's tzinfo should help
dt = tags['datetime']
- tags['datetime'] = datetime.datetime(
- dt.get_year(), dt.get_month(), dt.get_day(), dt.get_hour(),
- dt.get_minute(), dt.get_second(),
- dt.get_microsecond()).isoformat()
+ try:
+ tags['datetime'] = datetime.datetime(
+ dt.get_year(), dt.get_month(), dt.get_day(), dt.get_hour(),
+ dt.get_minute(), dt.get_second(),
+ dt.get_microsecond()).isoformat()
+ except:
+ tags['datetime'] = None
for k, v in tags.copy().items():
# types below are accepted by json; others must not present
if not isinstance(v, (dict, list, six.string_types, int, float, bool,
diff --git a/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html.orig b/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html.orig
deleted file mode 100644
index 2bd1a14c..00000000
--- a/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html.orig
+++ /dev/null
@@ -1,60 +0,0 @@
-{#
-# GNU MediaGoblin -- federated, autonomous media hosting
-# Copyright (C) 2011, 2012 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/>.
-#}
-
-<<<<<<< HEAD:mediagoblin/templates/mediagoblin/utils/metadata_table.html
-{%- macro render_table(request, media_entry, format_predicate) %}
- {%- set metadata=media_entry.media_metadata %}
- {%- set metadata_context=metadata['@context'] %}
- {%- if metadata %}
- <h3>{% trans %}Metadata Information{% endtrans %}</h3>
- <table class="metadata_info">
- {%- for key, value in metadata.iteritems() if (
- not key=='@context' and value) %}
- <tr {% if loop.index%2 == 1 %}class="highlight"{% endif %}>
- <th>{{ format_predicate(key) }}</th>
- <td property="{{ key }}">
- {{ value }}</td>
- </tr>
- {%- endfor %}
- </table>
- {% endif %}
- {% if request.user and request.user.has_privilege('admin') %}
- <a href="{{ request.urlgen('mediagoblin.edit.metadata',
- user=media_entry.get_uploader.username,
- media_id=media_entry.id) }}">
- {% trans %}Edit Metadata{% endtrans %}</a>
- {% endif %}
-{%- endmacro %}
-=======
-{%- set metadata=media.media_metadata %}
-{%- set metadata_context=metadata['@context'] %}
-{%- if metadata %}
- {#- NOTE: In some smart future where the context is more extensible,
- we will need to add to the prefix here-#}
- <table>
- {%- for key, value in metadata.iteritems() if not key=='@context' %}
- {% if value -%}
- <tr>
- <td>{{ rdfa_to_readable(key) }}</td>
- <td property="{{ key }}">{{ value }}</td>
- </tr>
- {%- endif -%}
- {%- endfor %}
- </table>
-{% endif %}
->>>>>>> acfcaf6366bd4695c1c37c7aa8ff5a176b412e2a:mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html
diff --git a/mediagoblin/plugins/subtitles/__init__.py b/mediagoblin/plugins/subtitles/__init__.py
new file mode 100644
index 00000000..78f207dc
--- /dev/null
+++ b/mediagoblin/plugins/subtitles/__init__.py
@@ -0,0 +1,50 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2016 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 import pluginapi
+import os
+
+PLUGIN_DIR = os.path.dirname(__file__)
+
+def setup_plugin():
+ config = pluginapi.get_config('mediagoblin.plugins.subtitles')
+
+ routes = [
+ ('mediagoblin.plugins.subtitles.customize',
+ '/u/<string:user>/m/<int:media_id>/customize/<int:id>',
+ 'mediagoblin.plugins.subtitles.views:custom_subtitles'),
+ ('mediagoblin.plugins.subtitles.subtitles',
+ '/u/<string:user>/m/<int:media_id>/subtitles/',
+ 'mediagoblin.plugins.subtitles.views:edit_subtitles'),
+ ('mediagoblin.plugins.subtitles.delete_subtitles',
+ '/u/<string:user>/m/<int:media_id>/delete/<int:id>',
+ 'mediagoblin.plugins.subtitles.views:delete_subtitles')]
+
+ pluginapi.register_routes(routes)
+
+ # Register the template path.
+ pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates'))
+
+ pluginapi.register_template_hooks(
+ {"customize_subtitles": "mediagoblin/plugins/subtitles/custom_subtitles.html",
+ "add_subtitles": "mediagoblin/plugins/subtitles/subtitles.html",
+ "subtitle_sidebar": "mediagoblin/plugins/subtitles/subtitle_media_block.html"})
+
+
+
+hooks = {
+ 'setup': setup_plugin
+ }
diff --git a/mediagoblin/plugins/subtitles/forms.py b/mediagoblin/plugins/subtitles/forms.py
new file mode 100644
index 00000000..de8ffbcd
--- /dev/null
+++ b/mediagoblin/plugins/subtitles/forms.py
@@ -0,0 +1,29 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2016 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 wtforms
+
+class CustomizeSubtitlesForm(wtforms.Form):
+ subtitle = wtforms.TextAreaField(
+ ('Subtitle'),
+ [wtforms.validators.Optional()],
+ description=(""))
+
+class EditSubtitlesForm(wtforms.Form):
+ subtitle_language = wtforms.StringField(
+ 'Language')
+ subtitle_file = wtforms.FileField(
+ 'File')
diff --git a/mediagoblin/plugins/subtitles/models.py b/mediagoblin/plugins/subtitles/models.py
new file mode 100644
index 00000000..f71fb173
--- /dev/null
+++ b/mediagoblin/plugins/subtitles/models.py
@@ -0,0 +1,49 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2016 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 sqlalchemy import Column, Integer, Unicode, ForeignKey
+from sqlalchemy.orm import relationship
+
+from mediagoblin.db.models import User
+from mediagoblin.db.base import Base,MediaEntry
+
+class MediaSubtitleFile(Base):
+ __tablename__ = "core__subtitle_files"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id),
+ nullable=False)
+ name = Column(Unicode, nullable=False)
+ filepath = Column(PathTupleWithSlashes)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+
+ @property
+ def dict_view(self):
+ """A dict like view on this object"""
+ return DictReadAttrProxy(self)
+
+ subtitle_files_helper = relationship("MediaSubtitleFile",
+ cascade="all, delete-orphan",
+ order_by="MediaSubtitleFile.created"
+ )
+ subtitle_files = association_proxy("subtitle_files_helper", "dict_view",
+ creator=lambda v: MediaSubtitleFile(
+ name=v["name"], filepath=v["filepath"])
+ )
+
+MODELS = [
+ MediaSubtitleFile
+]
diff --git a/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/custom_subtitles.html b/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/custom_subtitles.html
new file mode 100644
index 00000000..a62325ca
--- /dev/null
+++ b/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/custom_subtitles.html
@@ -0,0 +1,48 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2016 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/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+{%- endblock %}
+
+
+{% block mediagoblin_content %}
+<link href="{{
+ request.staticdirect('/css/subtitles.css') }}"
+ rel="stylesheet">
+
+ <form action="{{ request.urlgen('mediagoblin.plugins.subtitles.customize',
+ user=media.get_actor.username,
+ media_id=media.id,
+ id=id) }}" method="POST" enctype="multipart/form-data">
+ <div class="form_box edit_box">
+ {{ wtforms_util.render_divs(form) }}
+ <div class="form_submit_buttons">
+ {% set delete_url = request.urlgen('mediagoblin.plugins.subtitles.delete_subtitles',
+ user= media.get_actor.username,
+ media_id=media.id,
+ id=id) %}
+ <a class="button_action button_warning" href="{{ delete_url }}">{% trans %}Delete Subtitle{% endtrans %}</a>
+ <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitle_media_block.html b/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitle_media_block.html
new file mode 100644
index 00000000..34c9c16f
--- /dev/null
+++ b/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitle_media_block.html
@@ -0,0 +1,50 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2016 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/>.
+#}
+
+{% block subtitle_block %}
+{% if "video.html" in media.media_manager.display_template or "audio.html" in media.media_manager.display_template %}
+ {%- if media.subtitle_files|count %}
+ <h3>{% trans %}Subtitles{% endtrans %}</h3>
+ <ul>
+ {%- for subtitle in media.subtitle_files %}
+ <li>
+ <a href="{{ request.urlgen('mediagoblin.plugins.subtitles.customize',
+ user=media.get_actor.username,
+ media_id=media.id,
+ id=subtitle.id ) }}">
+ {{- subtitle.name -}}
+ </li>
+ {%- endfor %}
+ </ul>
+ {%- endif %}
+ {%- if request.user
+ and (media.actor == request.user.id
+ or request.user.has_privilege('admin')) %}
+ {%- if not media.subtitle_files|count %}
+ <h3>{% trans %}Subtitles{% endtrans %}</h3>
+ {%- endif %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.plugins.subtitles.subtitles',
+ user=media.get_actor.username,
+ media_id=media.id) }}">
+ {%- trans %}Add subtitle {% endtrans -%}
+ </a>
+ </p>
+ {%- endif %}
+ {% endif %}
+{% endblock %}
diff --git a/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitles.html b/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitles.html
new file mode 100644
index 00000000..5b249403
--- /dev/null
+++ b/mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitles.html
@@ -0,0 +1,69 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2016 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/>.
+#}
+{%- extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+ {% trans media_title=media.title -%}
+ Editing subtitles for {{ media_title }}
+ {%- endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.plugins.subtitles.subtitles',
+ user= media.get_actor.username,
+ media_id=media.id) }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>
+ {%- trans media_title=media.title -%}
+ Editing subtitles for {{ media_title }}
+ {%- endtrans -%}
+ </h1>
+ <div style="text-align: center;" >
+ <img src="{{ media.thumb_url }}" />
+ </div>
+
+ {% if media.subtitle_files|count %}
+ <h2>{% trans %}subtitles{% endtrans %}</h2>
+ <ul>
+ {%- for subtitle in media.subtitle_files %}
+ <li>
+ <a target="_blank" href="{{ request.app.public_store.file_url(
+ subtitle['filepath']) }}">
+ {{- subtitle.name -}}
+ </a>
+ </li>
+ {%- endfor %}
+ </ul>
+ {% endif %}
+
+ <h2>{% trans %}Add subtitle{% endtrans %}</h2>
+ {{- wtforms_util.render_divs(form) }}
+ <div class="form_submit_buttons">
+ <a class="button_action" href="{{ media.url_for_self(request.urlgen) }}">
+ {%- trans %}Cancel{% endtrans -%}
+ </a>
+ <input type="submit" value="{% trans %}Save changes{% endtrans %}"
+ class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/plugins/subtitles/tools.py b/mediagoblin/plugins/subtitles/tools.py
new file mode 100644
index 00000000..43f5bd6a
--- /dev/null
+++ b/mediagoblin/plugins/subtitles/tools.py
@@ -0,0 +1,42 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2016 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 import mg_globals
+import os
+
+def open_subtitle(path):
+ status = True
+ subtitle_public_filepath = path
+ try:
+ with mg_globals.public_store.get_file(
+ subtitle_public_filepath, 'rb') as subtitle_public_file:
+ text = subtitle_public_file.read().decode('utf-8','ignore')
+ return (text,status)
+ except:
+ status = False
+ return ('',status)
+
+def save_subtitle(path,text):
+ status = True
+ subtitle_public_filepath = path
+ try:
+ with mg_globals.public_store.get_file(
+ subtitle_public_filepath, 'wb') as subtitle_public_file:
+ subtitle_public_file.write(text.encode('utf-8','ignore'))
+ return status
+ except:
+ status = False
+ return (status)
diff --git a/mediagoblin/plugins/subtitles/views.py b/mediagoblin/plugins/subtitles/views.py
new file mode 100644
index 00000000..46b844af
--- /dev/null
+++ b/mediagoblin/plugins/subtitles/views.py
@@ -0,0 +1,186 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2016 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 six
+
+from datetime import datetime
+
+from itsdangerous import BadSignature
+from werkzeug.exceptions import Forbidden
+from werkzeug.utils import secure_filename
+
+from mediagoblin import messages
+from mediagoblin import mg_globals
+
+from mediagoblin.plugins.subtitles import forms
+from mediagoblin.decorators import (require_active_login, active_user_from_url,
+ get_media_entry_by_id, user_may_delete_media)
+from mediagoblin.tools.metadata import (compact_and_validate, DEFAULT_CHECKER,
+ DEFAULT_SCHEMA)
+from mediagoblin.tools.response import (render_to_response,
+ redirect, redirect_obj, render_404)
+
+import mimetypes
+
+from mediagoblin.plugins.subtitles.tools import open_subtitle,save_subtitle
+
+UNSAFE_MIMETYPES = [
+ 'text/html',
+ 'text/svg+xml']
+
+@get_media_entry_by_id
+@user_may_delete_media
+@require_active_login
+def edit_subtitles(request, media):
+ allowed_extensions = ['aqt','gsub','jss','sub','ttxt','pjs','psb',
+ 'rt','smi','stl','ssf','srt','ssa','ass','usf','vtt','lrc']
+ form = forms.EditSubtitlesForm(request.form)
+
+ # Add any subtitles
+ if 'subtitle_file' in request.files \
+ and request.files['subtitle_file']:
+ if mimetypes.guess_type(
+ request.files['subtitle_file'].filename)[0] in \
+ UNSAFE_MIMETYPES:
+ public_filename = secure_filename('{0}.notsafe'.format(
+ request.files['subtitle_file'].filename))
+ else:
+ public_filename = secure_filename(
+ request.files['subtitle_file'].filename)
+ filepath = request.files['subtitle_file'].filename
+ if filepath.split('.')[-1] not in allowed_extensions :
+ messages.add_message(
+ request,
+ messages.ERROR,
+ ("Invalid subtitle file"))
+
+ return redirect(request,
+ location=media.url_for_self(request.urlgen))
+ subtitle_public_filepath = mg_globals.public_store.get_unique_filepath(
+ ['media_entries', six.text_type(media.id), 'subtitle',
+ public_filename])
+
+ with mg_globals.public_store.get_file(
+ subtitle_public_filepath, 'wb') as subtitle_public_file:
+ subtitle_public_file.write(
+ request.files['subtitle_file'].stream.read())
+ request.files['subtitle_file'].stream.close()
+
+ media.subtitle_files.append(dict(
+ name=form.subtitle_language.data \
+ or request.files['subtitle_file'].filename,
+ filepath=subtitle_public_filepath,
+ created=datetime.utcnow(),
+ ))
+
+ media.save()
+
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ ("You added the subtitle %s!") %
+ (form.subtitle_language.data or
+ request.files['subtitle_file'].filename))
+
+ return redirect(request,
+ location=media.url_for_self(request.urlgen))
+ return render_to_response(
+ request,
+ 'mediagoblin/plugins/subtitles/subtitles.html',
+ {'media': media,
+ 'form': form})
+
+
+@require_active_login
+@get_media_entry_by_id
+@user_may_delete_media
+def custom_subtitles(request,media,id=None):
+ id = request.matchdict['id']
+ path = ""
+ for subtitle in media.subtitle_files:
+ if subtitle["id"] == id:
+ path = subtitle["filepath"]
+ text = ""
+ value = open_subtitle(path)
+ text, status = value[0], value[1]
+ if status == True :
+ form = forms.CustomizeSubtitlesForm(request.form,
+ subtitle=text)
+ if request.method == 'POST' and form.validate():
+ subtitle_data = form.subtitle.data
+ status = save_subtitle(path,subtitle_data)
+ if status == True:
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ ("Subtitle file changed!!!"))
+ return redirect(request,
+ location=media.url_for_self(request.urlgen))
+ else :
+ messages.add_message(
+ request,
+ messages.ERROR,
+ ("Couldn't edit the subtitles!!!"))
+ return redirect(request,
+ location=media.url_for_self(request.urlgen))
+
+ return render_to_response(
+ request,
+ "mediagoblin/plugins/subtitles/custom_subtitles.html",
+ {"id": id,
+ "media": media,
+ "form": form })
+ else:
+ index = 0
+ for subtitle in media.subtitle_files:
+ if subtitle["id"] == id:
+ delete_container = index
+ media.subtitle_files.pop(delete_container)
+ media.save()
+ break
+ index += 1
+ messages.add_message(
+ request,
+ messages.ERROR,
+ ("File link broken! Upload the subtitle again"))
+ return redirect(request,
+ location=media.url_for_self(request.urlgen))
+
+
+@require_active_login
+@get_media_entry_by_id
+@user_may_delete_media
+def delete_subtitles(request,media):
+ id = request.matchdict['id']
+ delete_container = None
+ index = 0
+ for subtitle in media.subtitle_files:
+ if subtitle["id"] == id:
+ path = subtitle["filepath"]
+ mg_globals.public_store.delete_file(path)
+ delete_container = index
+ media.subtitle_files.pop(delete_container)
+ media.save()
+ break
+ index += 1
+
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ ("Subtitle file deleted!!!"))
+
+ return redirect(request,
+ location=media.url_for_self(request.urlgen))
diff --git a/mediagoblin/static/css/lightbox.css b/mediagoblin/static/css/lightbox.css
new file mode 100644
index 00000000..e4fa4c48
--- /dev/null
+++ b/mediagoblin/static/css/lightbox.css
@@ -0,0 +1,21 @@
+body {
+ height: 100%;
+}
+.overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ z-index: 50;
+ cursor: pointer;
+}
+.box {
+ position: absolute;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ left: -9999em;
+ z-index: 51;
+}
diff --git a/mediagoblin/static/css/subtitles.css b/mediagoblin/static/css/subtitles.css
new file mode 100644
index 00000000..73dcbc7d
--- /dev/null
+++ b/mediagoblin/static/css/subtitles.css
@@ -0,0 +1,4 @@
+textarea#subtitle {
+ height: 500px;
+ border: 3px solid #cccccc;
+} \ No newline at end of file
diff --git a/mediagoblin/static/js/lightbox.js b/mediagoblin/static/js/lightbox.js
new file mode 100644
index 00000000..8d7bf31f
--- /dev/null
+++ b/mediagoblin/static/js/lightbox.js
@@ -0,0 +1,70 @@
+$(document).ready(function() {
+ $(".lightbox").click(function() {
+ overlayLink = $(this).attr("href"); //Getting the link for the media
+ window.startOverlay(overlayLink);
+ return false;
+ });
+});
+
+function startOverlay(overlayLink) {
+
+ // Adding elements to the page
+ $("body")
+ .append('<div class="overlay"></div><div class="box"></div>')
+ .css({
+ "overflow-y": "hidden"
+ });
+
+ // To create the lightbox effect
+ $(".container").animate({
+ "opacity": "0.2"
+ }, 300, "linear");
+
+ var imgWidth = $(".box img").width();
+ var imgHeight = $(".box img").height();
+
+ //adding the image to the box
+
+ $(".box").html('<img height=100% width=100% src="' + overlayLink + '" alt="" />');
+ //Position
+ $(".box img").load(function() {
+ var imgWidth = $(".box img").width();
+ var imgHeight = $(".box img").height();
+ if (imgHeight > screen.height - 170) imgHeight = screen.height - 170;
+ if (imgWidth > screen.width - 300) imgWidth = screen.width - 300;
+ $(".box")
+ .css({
+ "position": "absolute",
+ "top": "50%",
+ "left": "50%",
+ "height": imgHeight + 10,
+ "width": imgWidth + 10,
+ "border": "5px solid white",
+ "margin-top": -(imgHeight / 2),
+ "margin-left": -(imgWidth / 2) //to position it in the middle
+ })
+ .animate({
+ "opacity": "1"
+ }, 400, "linear");
+
+ //To remove
+ window.closeOverlay();
+ });
+}
+
+function closeOverlay() {
+ // allow users to be able to close the lightbox
+ $(".overlay").click(function() {
+ $(".box, .overlay").animate({
+ "opacity": "0"
+ }, 200, "linear", function() {
+ $(".box, .overlay").remove();
+ });
+ $(".container").animate({
+ "opacity": "1"
+ }, 200, "linear");
+ $("body").css({
+ "overflow-y": "scroll"
+ });
+ });
+}
diff --git a/mediagoblin/templates/mediagoblin/federation/host-meta.xml b/mediagoblin/templates/mediagoblin/api/host-meta.xml
index 7970a0d2..7970a0d2 100644
--- a/mediagoblin/templates/mediagoblin/federation/host-meta.xml
+++ b/mediagoblin/templates/mediagoblin/api/host-meta.xml
diff --git a/mediagoblin/templates/mediagoblin/media_displays/video.html b/mediagoblin/templates/mediagoblin/media_displays/video.html
index 61baf11c..31825bfd 100644
--- a/mediagoblin/templates/mediagoblin/media_displays/video.html
+++ b/mediagoblin/templates/mediagoblin/media_displays/video.html
@@ -65,6 +65,10 @@
type="{{ media.media_manager['default_webm_type'] }}"
{% endif %}
label="{{ each_media_path[0] }}" res="{{ each_media_path[1][1] }}" />
+ {%- for subtitle in media.subtitle_files %}
+ <track src="{{ request.app.public_store.file_url(subtitle.filepath) }}"
+ label="{{ subtitle.name }}" kind="subtitles">
+ {%- endfor %}
{% endfor %}
<div class="no_html5">
{%- trans -%}Sorry, this video will not work because
diff --git a/mediagoblin/templates/mediagoblin/moderation/media_panel.html b/mediagoblin/templates/mediagoblin/moderation/media_panel.html
index 94d4a1a0..e60f5e98 100644
--- a/mediagoblin/templates/mediagoblin/moderation/media_panel.html
+++ b/mediagoblin/templates/mediagoblin/moderation/media_panel.html
@@ -35,6 +35,7 @@
{% if processing_entries.count() %}
<table class="media_panel processing">
<tr>
+ <th>{% trans %}Thumbnail{% endtrans %}</th>
<th>{% trans %}ID{% endtrans %}</th>
<th>{% trans %}User{% endtrans %}</th>
<th>{% trans %}Title{% endtrans %}</th>
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html
index ce19717f..b93da06e 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/media.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/media.html
@@ -233,6 +233,7 @@
</a>
</p>
{%- endif %}
+ {% template_hook("subtitle_sidebar") %}
{% block mediagoblin_sidebar %}
{% endblock %}
diff --git a/mediagoblin/tests/.gitignore b/mediagoblin/tests/.gitignore
new file mode 100644
index 00000000..16d3c4db
--- /dev/null
+++ b/mediagoblin/tests/.gitignore
@@ -0,0 +1 @@
+.cache
diff --git a/mediagoblin/tests/fake_carrot_conf_good.ini b/mediagoblin/tests/fake_carrot_conf_good.ini
index 1377907b..8dc32525 100644
--- a/mediagoblin/tests/fake_carrot_conf_good.ini
+++ b/mediagoblin/tests/fake_carrot_conf_good.ini
@@ -7,7 +7,7 @@ num_carrots = 88
encouragement_phrase = "I'd love it if you eat your carrots!"
# Something extra!
-blah_blah = "blah!"
+blah_blah = "blæh!"
[celery]
EAT_CELERY_WITH_CARROTS = False
diff --git a/mediagoblin/tests/resources.py b/mediagoblin/tests/resources.py
index 480f6d9a..38406d62 100644
--- a/mediagoblin/tests/resources.py
+++ b/mediagoblin/tests/resources.py
@@ -41,3 +41,4 @@ GOOD_JPG = resource_exif('good.jpg')
EMPTY_JPG = resource_exif('empty.jpg')
BAD_JPG = resource_exif('bad.jpg')
GPS_JPG = resource_exif('has-gps.jpg')
+BAD_GPS_JPG = resource_exif('bad-gps.jpg')
diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py
index 90873cb9..f4741fd1 100644
--- a/mediagoblin/tests/test_api.py
+++ b/mediagoblin/tests/test_api.py
@@ -25,11 +25,11 @@ from webtest import AppError
from .resources import GOOD_JPG
from mediagoblin import mg_globals
-from mediagoblin.db.models import User, Activity, MediaEntry, TextComment
-from mediagoblin.tools.routing import extract_url_arguments
+from mediagoblin.db.models import User, MediaEntry, TextComment
from mediagoblin.tests.tools import fixture_add_user
from mediagoblin.moderation.tools import take_away_privileges
+
class TestAPI(object):
""" Test mediagoblin's pump.io complient APIs """
@@ -38,7 +38,8 @@ class TestAPI(object):
self.test_app = test_app
self.db = mg_globals.database
- self.user = fixture_add_user(privileges=[u'active', u'uploader', u'commenter'])
+ self.user = fixture_add_user(privileges=[u'active', u'uploader',
+ u'commenter'])
self.other_user = fixture_add_user(
username="otheruser",
privileges=[u'active', u'uploader', u'commenter']
@@ -61,7 +62,7 @@ class TestAPI(object):
return response, json.loads(response.body.decode())
- def _upload_image(self, test_app, image):
+ def _upload_image(self, test_app, image, custom_filename=None):
""" Uploads and image to MediaGoblin via pump.io API """
data = open(image, "rb").read()
headers = {
@@ -69,6 +70,8 @@ class TestAPI(object):
"Content-Length": str(len(data))
}
+ if custom_filename is not None:
+ headers["X-File-Name"] = custom_filename
with self.mock_oauth():
response = test_app.post(
@@ -126,9 +129,48 @@ class TestAPI(object):
assert image["objectType"] == "image"
# Check that we got the response we're expecting
- response, _ = self._post_image_to_feed(test_app, image)
+ response, data = self._post_image_to_feed(test_app, image)
+ assert response.status_code == 200
+ assert data["object"]["fullImage"]["url"].endswith("unknown.jpe")
+ assert data["object"]["image"]["url"].endswith("unknown.thumbnail.jpe")
+
+ def test_can_post_image_custom_filename(self, test_app):
+ """ Tests an image can be posted to the API with custom filename """
+ # First request we need to do is to upload the image
+ response, image = self._upload_image(test_app, GOOD_JPG,
+ custom_filename="hello.jpg")
+
+ # I should have got certain things back
+ assert response.status_code == 200
+
+ assert "id" in image
+ assert "fullImage" in image
+ assert "url" in image["fullImage"]
+ assert "url" in image
+ assert "author" in image
+ assert "published" in image
+ assert "updated" in image
+ assert image["objectType"] == "image"
+
+ # Check that we got the response we're expecting
+ response, data = self._post_image_to_feed(test_app, image)
+ assert response.status_code == 200
+ assert data["object"]["fullImage"]["url"].endswith("hello.jpg")
+ assert data["object"]["image"]["url"].endswith("hello.thumbnail.jpg")
+
+ def test_can_post_image_tags(self, test_app):
+ """ Tests that an image can be posted to the API """
+ # First request we need to do is to upload the image
+ response, image = self._upload_image(test_app, GOOD_JPG)
assert response.status_code == 200
+ image["tags"] = ["hello", "world"]
+
+ # Check that we got the response we're expecting
+ response, data = self._post_image_to_feed(test_app, image)
+ assert response.status_code == 200
+ assert data["object"]["tags"] == ["hello", "world"]
+
def test_unable_to_upload_as_someone_else(self, test_app):
""" Test that can't upload as someoen else """
data = open(GOOD_JPG, "rb").read()
@@ -172,7 +214,7 @@ class TestAPI(object):
assert "403 FORBIDDEN" in excinfo.value.args[0]
def test_only_able_to_update_own_image(self, test_app):
- """ Test's that the uploader is the only person who can update an image """
+ """ Test uploader is the only person who can update an image """
response, data = self._upload_image(test_app, GOOD_JPG)
response, data = self._post_image_to_feed(test_app, data)
@@ -186,13 +228,16 @@ class TestAPI(object):
}
# Lets change the image uploader to be self.other_user, this is easier
- # than uploading the image as someone else as the way self.mocked_oauth_required
- # and self._upload_image.
- media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first()
+ # than uploading the image as someone else as the way
+ # self.mocked_oauth_required and self._upload_image.
+ media = MediaEntry.query \
+ .filter_by(public_id=data["object"]["id"]) \
+ .first()
media.actor = self.other_user.id
media.save()
- # Now lets try and edit the image as self.user, this should produce a 403 error.
+ # Now lets try and edit the image as self.user, this should produce a
+ # 403 error.
with self.mock_oauth():
with pytest.raises(AppError) as excinfo:
test_app.post(
@@ -242,7 +287,6 @@ class TestAPI(object):
assert image["content"] == description
assert image["license"] == license
-
def test_only_uploaders_post_image(self, test_app):
""" Test that only uploaders can upload images """
# Remove uploader permissions from user
@@ -288,12 +332,15 @@ class TestAPI(object):
image = json.loads(request.body.decode())
entry = MediaEntry.query.filter_by(public_id=image["id"]).first()
+ assert entry is not None
+
assert request.status_code == 200
assert "image" in image
assert "fullImage" in image
assert "pump_io" in image
assert "links" in image
+ assert "tags" in image
def test_post_comment(self, test_app):
""" Tests that I can post an comment media """
@@ -316,7 +363,9 @@ class TestAPI(object):
assert response.status_code == 200
# Find the objects in the database
- media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first()
+ media = MediaEntry.query \
+ .filter_by(public_id=data["object"]["id"]) \
+ .first()
comment = media.get_comments()[0].comment()
# Tests that it matches in the database
@@ -378,7 +427,9 @@ class TestAPI(object):
response, comment_data = self._activity_to_feed(test_app, activity)
# change who uploaded the comment as it's easier than changing
- comment = TextComment.query.filter_by(public_id=comment_data["object"]["id"]).first()
+ comment = TextComment.query \
+ .filter_by(public_id=comment_data["object"]["id"]) \
+ .first()
comment.actor = self.other_user.id
comment.save()
@@ -432,7 +483,7 @@ class TestAPI(object):
def test_whoami_without_login(self, test_app):
""" Test that whoami endpoint returns error when not logged in """
with pytest.raises(AppError) as excinfo:
- response = test_app.get("/api/whoami")
+ test_app.get("/api/whoami")
assert "401 UNAUTHORIZED" in excinfo.value.args[0]
@@ -621,8 +672,11 @@ class TestAPI(object):
delete = self._activity_to_feed(test_app, activity)[1]
# Verify the comment no longer exists
- assert TextComment.query.filter_by(public_id=comment["object"]["id"]).first() is None
- comment_id = comment["object"]["id"]
+ assert TextComment.query \
+ .filter_by(public_id=comment["object"]["id"]) \
+ .first() is None
+
+ assert "id" in comment["object"]
# Check we've got a delete activity back
assert "id" in delete
@@ -662,6 +716,8 @@ class TestAPI(object):
comment = self._activity_to_feed(test_app, activity)[1]
# Verify the comment reflects the changes
- model = TextComment.query.filter_by(public_id=comment["object"]["id"]).first()
+ model = TextComment.query \
+ .filter_by(public_id=comment["object"]["id"]) \
+ .first()
assert model.content == activity["object"]["content"]
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index 618d02b6..9cf5ccb0 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -101,7 +101,7 @@ def test_register_views(test_app):
'password': 'iamsohappy',
'email': 'easter@egg.com'})
- ## At this point there should on user in the database
+ ## At this point there should be one user in the database
assert User.query.count() == 1
# Successful register
diff --git a/mediagoblin/tests/test_config.py b/mediagoblin/tests/test_config.py
index b13adae6..c3527418 100644
--- a/mediagoblin/tests/test_config.py
+++ b/mediagoblin/tests/test_config.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+#
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
@@ -47,7 +49,7 @@ def test_read_mediagoblin_config():
assert this_conf['carrotapp']['num_carrots'] == 88
assert this_conf['carrotapp']['encouragement_phrase'] == \
"I'd love it if you eat your carrots!"
- assert this_conf['carrotapp']['blah_blah'] == "blah!"
+ assert this_conf['carrotapp']['blah_blah'] == u"blæh!"
assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == False
# A bad file
diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py
index d0495a7a..ad771cca 100644
--- a/mediagoblin/tests/test_exif.py
+++ b/mediagoblin/tests/test_exif.py
@@ -24,7 +24,7 @@ from collections import OrderedDict
from mediagoblin.tools.exif import exif_fix_image_orientation, \
extract_exif, clean_exif, get_gps_data, get_useful
-from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG
+from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG, BAD_GPS_JPG
def assert_in(a, b):
@@ -437,3 +437,18 @@ def test_exif_gps_data():
'direction': 25.674046740467404,
'altitude': 37.64365671641791,
'longitude': 18.016166666666667}
+
+
+def test_exif_bad_gps_data():
+ '''
+ Test extraction of GPS data from an image with bad GPS data
+ '''
+ result = extract_exif(BAD_GPS_JPG)
+ gps = get_gps_data(result)
+ print(gps)
+
+ assert gps == {
+ 'latitude': 0.0,
+ 'direction': 0.0,
+ 'altitude': 0.0,
+ 'longitude': 0.0}
diff --git a/mediagoblin/tests/test_exif/bad-gps.jpg b/mediagoblin/tests/test_exif/bad-gps.jpg
new file mode 100644
index 00000000..bd6c7bf2
--- /dev/null
+++ b/mediagoblin/tests/test_exif/bad-gps.jpg
Binary files differ
diff --git a/mediagoblin/tests/test_subtitles.py b/mediagoblin/tests/test_subtitles.py
new file mode 100644
index 00000000..4e884d07
--- /dev/null
+++ b/mediagoblin/tests/test_subtitles.py
@@ -0,0 +1,68 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 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.tests import tools
+from mediagoblin import mg_globals
+from mediagoblin.db.models import User, MediaEntry
+from mediagoblin.db.base import Session
+from mediagoblin.tools.testing import _activate_testing
+from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry
+from mediagoblin.plugins.subtitles.tools import open_subtitle, save_subtitle
+
+# Checking if the subtitle entry is working
+
+def test_add_subtitle_entry(test_app):
+ user_a = fixture_add_user(u"test_user")
+
+ media = fixture_media_entry(uploader=user_a.id, save=False, expunge=False)
+ media.subtitle_files.append(dict(
+ name=u"some name",
+ filepath=[u"does", u"not", u"exist"],
+ ))
+ Session.add(media)
+ Session.flush()
+
+ MediaEntry.query.get(media.id).delete()
+ User.query.get(user_a.id).delete()
+
+# Checking the tools written for subtitles
+
+def test_read_write_file(test_app):
+ test_filepath = ['test']
+
+ status = save_subtitle(test_filepath,"Testing!!!")
+ text = open_subtitle(test_filepath)[0]
+
+ assert status == True
+ assert text == "Testing!!!"
+
+ mg_globals.public_store.delete_file(test_filepath)
+
+# Checking the customize exceptions
+
+def test_customize_subtitle(test_app):
+ user_a = fixture_add_user(u"test_user")
+
+ media = fixture_media_entry(uploader=user_a.id, save=False, expunge=False)
+ media.subtitle_files.append(dict(
+ name=u"some name",
+ filepath=[u"does", u"not", u"exist"],
+ ))
+ Session.add(media)
+ Session.flush()
+
+ for subtitle in media.subtitle_files:
+ assert '' == open_subtitle(subtitle['filepath'])[0]
diff --git a/mediagoblin/themes/airy/assets/css/airy.css b/mediagoblin/themes/airy/assets/css/airy.css
index 7539997e..047e02dc 100644
--- a/mediagoblin/themes/airy/assets/css/airy.css
+++ b/mediagoblin/themes/airy/assets/css/airy.css
@@ -52,6 +52,10 @@ footer {
border-top: 1px solid #E4E4E4;
}
+table.admin_panel th {
+ color: #4a4a4a;
+}
+
.button_action, .button_action_highlight, .button_form {
color: #4a4a4a;
background-color: #fff;
diff --git a/mediagoblin/tools/exif.py b/mediagoblin/tools/exif.py
index fafd987d..2215fb0c 100644
--- a/mediagoblin/tools/exif.py
+++ b/mediagoblin/tools/exif.py
@@ -19,6 +19,11 @@ import six
from exifread import process_file
from exifread.utils import Ratio
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
from mediagoblin.processing import BadMediaFail
from mediagoblin.tools.translate import pass_to_ugettext as _
@@ -61,12 +66,12 @@ def exif_fix_image_orientation(im, exif_tags):
# Rotate image
if 'Image Orientation' in exif_tags:
rotation_map = {
- 3: 180,
- 6: 270,
- 8: 90}
+ 3: Image.ROTATE_180,
+ 6: Image.ROTATE_270,
+ 8: Image.ROTATE_90}
orientation = exif_tags['Image Orientation'].values[0]
if orientation in rotation_map:
- im = im.rotate(
+ im = im.transpose(
rotation_map[orientation])
return im
@@ -175,18 +180,14 @@ def get_gps_data(tags):
pass
try:
- gps_data['direction'] = (
- lambda d:
- float(d.num) / float(d.den)
- )(tags['GPS GPSImgDirection'].values[0])
+ direction = tags['GPS GPSImgDirection'].values[0]
+ gps_data['direction'] = safe_gps_ratio_divide(direction)
except KeyError:
pass
try:
- gps_data['altitude'] = (
- lambda a:
- float(a.num) / float(a.den)
- )(tags['GPS GPSAltitude'].values[0])
+ altitude = tags['GPS GPSAltitude'].values[0]
+ gps_data['altitude'] = safe_gps_ratio_divide(altitude)
except KeyError:
pass
diff --git a/mediagoblin/tools/files.py b/mediagoblin/tools/files.py
index 2c486ac8..0509a387 100644
--- a/mediagoblin/tools/files.py
+++ b/mediagoblin/tools/files.py
@@ -41,5 +41,12 @@ def delete_media_files(media):
except OSError:
no_such_files.append("/".join(attachment['filepath']))
+ for subtitle in media.subtitle_files:
+ try:
+ mg_globals.public_store.delete_file(
+ subtitle['filepath'])
+ except OSError:
+ no_such_files.append("/".join(subtitle['filepath']))
+
if no_such_files:
raise OSError(", ".join(no_such_files))
diff --git a/mediagoblin/tools/licenses.py b/mediagoblin/tools/licenses.py
index a964980e..2aff7f20 100644
--- a/mediagoblin/tools/licenses.py
+++ b/mediagoblin/tools/licenses.py
@@ -20,28 +20,45 @@ License = namedtuple("License", ["abbreviation", "name", "uri"])
SORTED_LICENSES = [
License("All rights reserved", "No license specified", ""),
+ License("CC BY 4.0", "Creative Commons Attribution 4.0 International",
+ "https://creativecommons.org/licenses/by/4.0/"),
+ License("CC BY-SA 4.0",
+ "Creative Commons Attribution-ShareAlike 4.0 International",
+ "https://creativecommons.org/licenses/by-sa/4.0/"),
+ License("CC BY-ND 4.0",
+ "Creative Commons Attribution-NoDerivs 4.0 International",
+ "https://creativecommons.org/licenses/by-nd/4.0/"),
+ License("CC BY-NC 4.0",
+ "Creative Commons Attribution-NonCommercial 4.0 International",
+ "https://creativecommons.org/licenses/by-nc/4.0/"),
+ License("CC BY-NC-SA 4.0",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International",
+ "https://creativecommons.org/licenses/by-nc-sa/4.0/"),
+ License("CC BY-NC-ND 4.0",
+ "Creative Commons Attribution-NonCommercial-NoDerivs 4.0 International",
+ "https://creativecommons.org/licenses/by-nc-nd/4.0/"),
License("CC BY 3.0", "Creative Commons Attribution Unported 3.0",
- "http://creativecommons.org/licenses/by/3.0/"),
+ "https://creativecommons.org/licenses/by/3.0/"),
License("CC BY-SA 3.0",
- "Creative Commons Attribution-ShareAlike Unported 3.0",
- "http://creativecommons.org/licenses/by-sa/3.0/"),
+ "Creative Commons Attribution-ShareAlike 3.0 Unported",
+ "https://creativecommons.org/licenses/by-sa/3.0/"),
License("CC BY-ND 3.0",
"Creative Commons Attribution-NoDerivs 3.0 Unported",
- "http://creativecommons.org/licenses/by-nd/3.0/"),
+ "https://creativecommons.org/licenses/by-nd/3.0/"),
License("CC BY-NC 3.0",
- "Creative Commons Attribution-NonCommercial Unported 3.0",
- "http://creativecommons.org/licenses/by-nc/3.0/"),
+ "Creative Commons Attribution-NonCommercial 3.0 Unported",
+ "https://creativecommons.org/licenses/by-nc/3.0/"),
License("CC BY-NC-SA 3.0",
"Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported",
- "http://creativecommons.org/licenses/by-nc-sa/3.0/"),
+ "https://creativecommons.org/licenses/by-nc-sa/3.0/"),
License("CC BY-NC-ND 3.0",
"Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported",
- "http://creativecommons.org/licenses/by-nc-nd/3.0/"),
+ "https://creativecommons.org/licenses/by-nc-nd/3.0/"),
License("CC0 1.0",
"Creative Commons CC0 1.0 Universal",
- "http://creativecommons.org/publicdomain/zero/1.0/"),
+ "https://creativecommons.org/publicdomain/zero/1.0/"),
License("Public Domain","Public Domain",
- "http://creativecommons.org/publicdomain/mark/1.0/"),
+ "https://creativecommons.org/publicdomain/mark/1.0/"),
]
# dict {uri: License,...} to enable fast license lookup by uri. Ideally,
diff --git a/mediagoblin/tools/subtitles.py b/mediagoblin/tools/subtitles.py
new file mode 100644
index 00000000..efafbeec
--- /dev/null
+++ b/mediagoblin/tools/subtitles.py
@@ -0,0 +1,20 @@
+import os
+
+def get_path(path):
+ temp = ['user_dev','media','public']
+ path = list(eval(path))
+ file_path = os.path.abspath(__file__).split('/') # Path of current file as dictionary
+ subtitle_path = file_path[:-3] + temp + path # Creating the absolute path for the subtitle file
+ subtitle_path = "/" + os.path.join(*subtitle_path)
+ return subtitle_path
+
+def open_subtitle(path):
+ subtitle_path = get_path(path)
+ subtitle = open(subtitle_path,"r") # Opening the file using the absolute path
+ text = subtitle.read()
+ return text
+
+def save_subtitle(path,text):
+ subtitle_path = get_path(path)
+ subtitle = open(subtitle_path,"w") # Opening the file using the absolute path
+ subtitle.write(text) \ No newline at end of file
diff --git a/mediagoblin/tools/validator.py b/mediagoblin/tools/validator.py
index 03598f9c..93296eab 100644
--- a/mediagoblin/tools/validator.py
+++ b/mediagoblin/tools/validator.py
@@ -14,21 +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/>.
-from wtforms.validators import Email, URL
+from six.moves.urllib.parse import urlparse
def validate_email(email):
- """
- Validates an email
-
- Returns True if valid and False if invalid
"""
+ Validates an email
- email_re = Email().regex
- result = email_re.match(email)
- if result is None:
- return False
- else:
- return result.string
+ Returns True if valid and False if invalid
+ """
+ return '@' in email
def validate_url(url):
"""
@@ -36,11 +30,9 @@ def validate_url(url):
Returns True if valid and False if invalid
"""
-
- url_re = URL().regex
- result = url_re.match(url)
- if result is None:
+ try:
+ urlparse(url)
+ return True
+ except Exception as e:
return False
- else:
- return result.string
diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py
index 68cb0a3b..73371b6d 100644
--- a/mediagoblin/user_pages/routing.py
+++ b/mediagoblin/user_pages/routing.py
@@ -113,4 +113,4 @@ add_route('mediagoblin.edit.attachments',
add_route('mediagoblin.edit.metadata',
'/u/<string:user>/m/<int:media_id>/metadata/',
- 'mediagoblin.edit.views:edit_metadata')
+ 'mediagoblin.edit.views:edit_metadata') \ No newline at end of file
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
index 5e629575..62a4f151 100644
--- a/mediagoblin/user_pages/views.py
+++ b/mediagoblin/user_pages/views.py
@@ -179,6 +179,10 @@ def media_post_comment(request, media):
if not request.method == 'POST':
raise MethodNotAllowed()
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
comment = request.db.TextComment()
comment.actor = request.user.id
comment.content = six.text_type(request.form['comment_content'])
@@ -231,6 +235,10 @@ def media_preview_comment(request):
def media_collect(request, media):
"""Add media to collection submission"""
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
form = user_forms.MediaCollectForm(request.form)
# A user's own collections:
form.collection.query = Collection.query.filter_by(
@@ -288,12 +296,6 @@ def media_collect(request, media):
collection = None
# Make sure the user actually selected a collection
- item = CollectionItem.query.filter_by(collection=collection.id)
- item = item.join(CollectionItem.object_helper).filter_by(
- model_type=media.__tablename__,
- obj_pk=media.id
- ).first()
-
if not collection:
messages.add_message(
request,
@@ -303,8 +305,14 @@ def media_collect(request, media):
user=media.get_actor.username,
media_id=media.id)
+ item = CollectionItem.query.filter_by(collection=collection.id)
+ item = item.join(CollectionItem.object_helper).filter_by(
+ model_type=media.__tablename__,
+ obj_pk=media.id
+ ).first()
+
# Check whether media already exists in collection
- elif item is not None:
+ if item is not None:
messages.add_message(
request,
messages.ERROR,