diff options
22 files changed, 794 insertions, 6 deletions
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 3b85b07a..c19fe4da 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -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] 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 a296a184..717241e8 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -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 diff --git a/mediagoblin/media_types/blog/views.py b/mediagoblin/media_types/blog/views.py index 2bf2e5be..288a47ae 100644 --- a/mediagoblin/media_types/blog/views.py +++ b/mediagoblin/media_types/blog/views.py @@ -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 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 %} — {{ 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..7d41a36d --- /dev/null +++ b/mediagoblin/plugins/subtitles/views.py @@ -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 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/media_displays/video.html b/mediagoblin/templates/mediagoblin/media_displays/video.html index 5aac3529..8e3a202f 100644 --- a/mediagoblin/templates/mediagoblin/media_displays/video.html +++ b/mediagoblin/templates/mediagoblin/media_displays/video.html @@ -60,6 +60,10 @@ {% 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 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/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/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/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/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 |