Merge remote-tracking branch 'gsoc2016/Subtitle-1'
authorBoris Bobrov <breton@cynicmansion.ru>
Tue, 10 Jul 2018 16:29:30 +0000 (18:29 +0200)
committerBoris Bobrov <breton@cynicmansion.ru>
Tue, 10 Jul 2018 16:29:30 +0000 (18:29 +0200)
22 files changed:
mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py [new file with mode: 0644]
mediagoblin/db/models.py
mediagoblin/edit/routing.py
mediagoblin/edit/views.py
mediagoblin/media_types/blog/views.py
mediagoblin/plugins/subtitles/__init__.py [new file with mode: 0644]
mediagoblin/plugins/subtitles/forms.py [new file with mode: 0644]
mediagoblin/plugins/subtitles/models.py [new file with mode: 0644]
mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/custom_subtitles.html [new file with mode: 0644]
mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitle_media_block.html [new file with mode: 0644]
mediagoblin/plugins/subtitles/templates/mediagoblin/plugins/subtitles/subtitles.html [new file with mode: 0644]
mediagoblin/plugins/subtitles/tools.py [new file with mode: 0644]
mediagoblin/plugins/subtitles/views.py [new file with mode: 0644]
mediagoblin/static/css/lightbox.css [new file with mode: 0644]
mediagoblin/static/css/subtitles.css [new file with mode: 0644]
mediagoblin/static/js/lightbox.js [new file with mode: 0644]
mediagoblin/templates/mediagoblin/media_displays/video.html
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/tests/test_subtitles.py [new file with mode: 0644]
mediagoblin/tools/files.py
mediagoblin/tools/subtitles.py [new file with mode: 0644]
mediagoblin/user_pages/routing.py

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 (file)
index 0000000..565d486
--- /dev/null
@@ -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 ###
index 3b85b07aa9405cf965e79ac44c6eb11337cd6137..c19fe4dade6f390721084435bba0173494489836 100644 (file)
@@ -574,6 +574,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
         )
@@ -907,6 +916,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"
@@ -1618,7 +1643,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]
index b349975d397eabd8bc7e21b04e82cda0adcd6af8..d3ae5465ae3cb4da53bc46f86b81ac45f8d57243 100644 (file)
@@ -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
index a296a18416099ef31b8643dfbd3f4c6ae9eda67a..717241e8b63ef48989c13a1ef36963b904bb4fd5 100644 (file)
@@ -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)
@@ -538,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
index 2bf2e5be67eda19607ebb23e8d631337eb1c4fcc..288a47ae08451239447a76e7c20dcda0f066b6d2 100644 (file)
@@ -24,7 +24,7 @@ import six
 from werkzeug.exceptions import Forbidden
 from mediagoblin.tools import pluginapi
 
-from mediagoblin import messages, mg_globals
+from mediagoblin import mg_globals
 
 from mediagoblin.media_types.blog import forms as blog_forms
 from mediagoblin.media_types.blog.models import Blog, BlogPostData
diff --git a/mediagoblin/plugins/subtitles/__init__.py b/mediagoblin/plugins/subtitles/__init__.py
new file mode 100644 (file)
index 0000000..78f207d
--- /dev/null
@@ -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 (file)
index 0000000..de8ffbc
--- /dev/null
@@ -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 (file)
index 0000000..f71fb17
--- /dev/null
@@ -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 (file)
index 0000000..a62325c
--- /dev/null
@@ -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 (file)
index 0000000..34c9c16
--- /dev/null
@@ -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 (file)
index 0000000..5b24940
--- /dev/null
@@ -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 (file)
index 0000000..43f5bd6
--- /dev/null
@@ -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 (file)
index 0000000..7d41a36
--- /dev/null
@@ -0,0 +1,195 @@
+# 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.count('.') != 1: # Not allowing double extensions or no extensions
+            messages.add_message(
+            request,
+            messages.ERROR,
+            ("Check the filename"))
+
+            return redirect(request,
+                            location=media.url_for_self(request.urlgen))
+        elif 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 (file)
index 0000000..e4fa4c4
--- /dev/null
@@ -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 (file)
index 0000000..73dcbc7
--- /dev/null
@@ -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 (file)
index 0000000..8d7bf31
--- /dev/null
@@ -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"
+        });
+    });
+}
index 5aac3529bca43b61fb839bec39892dbeafdbd5aa..8e3a202ff9c88c313f254e76f3dc11c900e58407 100644 (file)
             {% else %}
               type="{{ media.media_manager['default_webm_type'] }}"
             {% endif %} />
+    {%- for subtitle in media.subtitle_files %}
+            <track src="{{ request.app.public_store.file_url(subtitle.filepath) }}"
+              label = "{{ subtitle.name }}" kind="subtitles" >
+    {%- endfor %}
     <div class="no_html5">
       {%- trans -%}Sorry, this video will not work because
       your web browser does not support HTML5 
index ce19717f8db15ec6e4885771ef3e0931efc9d413..b93da06ee0c232fd8bce1cae503f5e797c346bc9 100644 (file)
         </a>
       </p>
     {%- endif %}
+    {% template_hook("subtitle_sidebar") %}
 
     {% block mediagoblin_sidebar %}
     {% endblock %}
diff --git a/mediagoblin/tests/test_subtitles.py b/mediagoblin/tests/test_subtitles.py
new file mode 100644 (file)
index 0000000..4e884d0
--- /dev/null
@@ -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]
index 2c486ac88c418098ef3a31a4890b62e3fad7ac10..0509a387c4809b4ade8b786d5c072e0134dadc84 100644 (file)
@@ -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/subtitles.py b/mediagoblin/tools/subtitles.py
new file mode 100644 (file)
index 0000000..efafbee
--- /dev/null
@@ -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
index 68cb0a3bd892cf4b5a9c52731f42131e5360904d..73371b6d2e053772c506a25d0a2561f128c6ab30 100644 (file)
@@ -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