aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitmodules6
-rw-r--r--AUTHORS2
-rw-r--r--MANIFEST.in9
-rw-r--r--docs/source/siteadmin/commandline-upload.rst3
-rw-r--r--docs/source/siteadmin/configuration.rst24
-rw-r--r--docs/source/siteadmin/deploying.rst17
-rw-r--r--docs/source/siteadmin/media-types.rst20
-rw-r--r--docs/source/siteadmin/production-deployments.rst2
-rw-r--r--docs/source/siteadmin/relnotes.rst8
-rw-r--r--docs/source/siteadmin/theming.rst2
-rwxr-xr-xlazystarter.sh4
-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.ini (renamed from alembic.ini)2
-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
59 files changed, 1085 insertions, 216 deletions
diff --git a/.gitignore b/.gitignore
index afe010c0..acb9a3b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,7 @@
*~
*.swp
*.mo
+*.patch
# The legacy of buildout
.installed.cfg
diff --git a/.gitmodules b/.gitmodules
index 562ad4e4..d9171995 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,12 +1,12 @@
[submodule "pdf.js"]
path = pdf.js
- url = git://github.com/mozilla/pdf.js.git
+ url = https://github.com/mozilla/pdf.js.git
[submodule "extlib/pdf.js"]
path = extlib/pdf.js
- url = git://github.com/mozilla/pdf.js.git
+ url = https://github.com/mozilla/pdf.js.git
[submodule "extlib/skeleton"]
path = extlib/skeleton
- url = git://github.com/dhg/Skeleton.git
+ url = https://github.com/dhg/Skeleton.git
[submodule "extlib/sandyseventiesspeedboat"]
path = extlib/sandyseventiesspeedboat
url = https://github.com/jpope777/sandyseventiesspeedboat-mg.git
diff --git a/AUTHORS b/AUTHORS
index 0804efd5..1808fa4a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -32,6 +32,7 @@ Thank you!
* Corey Farwell
* Chris Moylan
* Christopher Allan Webber
+* chrysn
* Dan Callahan
* David Thompson
* Daniel Krol
@@ -94,6 +95,7 @@ Thank you!
* Robert Smith
* Rodney Ewing
* Rodrigo Rodrigues da Silva
+* Romain Porte
* Runar Petursson
* Sacha De'Angeli
* Sam Clegg
diff --git a/MANIFEST.in b/MANIFEST.in
index 675c8081..dd2fd23a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,8 +3,17 @@ recursive-include mediagoblin *.js *.css *.png *.svg *.ico
recursive-include mediagoblin *.ini
recursive-include mediagoblin *.html *.txt
recursive-include docs *.rst *.html
+recursive-include mediagoblin/db/migrations/versions *.py
+recursive-include mediagoblin/media_types/ascii/migrations *.py
+recursive-include mediagoblin/media_types/audio/migrations *.py
+recursive-include mediagoblin/media_types/blog/migrations *.py
+recursive-include mediagoblin/media_types/image/migrations *.py
+recursive-include mediagoblin/media_types/pdf/migrations *.py
+recursive-include mediagoblin/media_types/stl/migrations *.py
+recursive-include mediagoblin/media_types/video/migrations *.py
include mediagoblin.example.ini mediagoblin/config_spec.ini paste.ini
include mediagoblin/config_spec.ini
+include mediagoblin/db/migrations/alembic.ini
graft extlib
graft licenses
graft devtools
diff --git a/docs/source/siteadmin/commandline-upload.rst b/docs/source/siteadmin/commandline-upload.rst
index ef597a44..756f5fa8 100644
--- a/docs/source/siteadmin/commandline-upload.rst
+++ b/docs/source/siteadmin/commandline-upload.rst
@@ -58,7 +58,7 @@ it is a bit more complex.
This is an example of what a script may look like. The important part here is
that you have to create the 'metadata.csv' file.::
- media:location,dcterms:title,dcterms:creator,dcterms:type
+ location,dcterms:title,dcterms:creator,dcterms:type
"http://www.example.net/path/to/nap.png","Goblin taking a nap",,"Image"
"http://www.example.net/path/to/snore.ogg","Goblin Snoring","Me","Audio"
@@ -87,6 +87,7 @@ just as easy to provide this information through the correct metadata columns.
- **license** is used to set a license for your piece a media for MediaGoblin's use. This must be a URI.
- **title** will set the title displayed to MediaGoblin users.
- **description** will set a description of your media.
+- **collection-slug** will add the media to a collection, if a collection with the given slug exists.
Metadata columns
----------------
diff --git a/docs/source/siteadmin/configuration.rst b/docs/source/siteadmin/configuration.rst
index 6b8cd225..1b8dc9bb 100644
--- a/docs/source/siteadmin/configuration.rst
+++ b/docs/source/siteadmin/configuration.rst
@@ -54,30 +54,6 @@ mediagoblin/config_spec.ini
option that we didn't tell you about. :)
-Making local copies
-===================
-
-Let's assume you're doing the virtualenv setup described elsewhere in this
-manual, and you need to make local tweaks to the config files. How do you do
-that? Let's see.
-
-To make changes to mediagoblin.ini ::
-
- cp mediagoblin.ini mediagoblin_local.ini
-
-To make changes to paste.ini ::
-
- cp paste.ini paste_local.ini
-
-From here you should be able to make direct adjustments to the files,
-and most of the commands described elsewhere in this manual will "notice"
-your local config files and use those instead of the non-local version.
-
-.. note::
-
- Note that all commands provide a way to pass in a specific config
- file also, usually by a ``-cf`` flag.
-
Common changes
==============
diff --git a/docs/source/siteadmin/deploying.rst b/docs/source/siteadmin/deploying.rst
index c7cc2403..42fe1772 100644
--- a/docs/source/siteadmin/deploying.rst
+++ b/docs/source/siteadmin/deploying.rst
@@ -65,7 +65,7 @@ MediaGoblin has the following core dependencies:
- `virtualenv <http://www.virtualenv.org/>`_
- `nodejs <https://nodejs.org>`_
-On a DEB-based system (e.g Debian, gNewSense, Trisquel, *buntu, and
+On a DEB-based system (e.g Debian, gNewSense, Trisquel, \*buntu, and
derivatives) issue the following command::
sudo apt-get install git-core python python-dev python-lxml \
@@ -240,7 +240,7 @@ Change to the MediaGoblin directory that you just created::
Clone the MediaGoblin repository and set up the git submodules::
- $ git clone git://git.savannah.gnu.org/mediagoblin.git -b stable
+ $ git clone https://git.savannah.gnu.org/git/mediagoblin.git -b stable
$ cd mediagoblin
$ git submodule init && git submodule update
@@ -250,7 +250,7 @@ Clone the MediaGoblin repository and set up the git submodules::
gitorious.org shut down, we had to move. We are presently on
Savannah. You may need to update your git repository location::
- $ git remote set-url origin git://git.savannah.gnu.org/mediagoblin.git
+ $ git remote set-url origin https://git.savannah.gnu.org/git/mediagoblin.git
Set up the hacking environment::
@@ -319,13 +319,16 @@ Edit site configuration
~~~~~~~~~~~~~~~~~~~~~~~
A few basic properties must be set before MediaGoblin will work. First
-make a copy of ``mediagoblin.ini`` and ``paste.ini`` for editing so the original
+make a copy of ``paste.ini`` for editing so the original
config files aren't lost (you likely won't need to edit the paste configuration,
but we'll make a local copy of it just in case)::
- $ cp -av mediagoblin.ini mediagoblin_local.ini && cp -av paste.ini paste_local.ini
+ $ cp -av paste.ini paste_local.ini
+
+``mediagoblin.ini`` does not need to be copied, because original config is
+stored in ``mediagoblin.example.ini``.
-Then edit mediagoblin_local.ini:
+Then edit ``mediagoblin.ini``:
- Set ``email_sender_address`` to the address you wish to be used as
the sender for system-generated emails
- Edit ``direct_remote_path``, ``base_dir``, and ``base_url`` if
@@ -337,7 +340,7 @@ Configure MediaGoblin to use the PostgreSQL database
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are using PostgreSQL, edit the ``[mediagoblin]`` section in your
-``mediagoblin_local.ini`` and put in::
+``mediagoblin.ini`` and put in::
sql_engine = postgresql:///mediagoblin
diff --git a/docs/source/siteadmin/media-types.rst b/docs/source/siteadmin/media-types.rst
index 146e1aae..8f9239be 100644
--- a/docs/source/siteadmin/media-types.rst
+++ b/docs/source/siteadmin/media-types.rst
@@ -30,17 +30,14 @@ Enabling Media Types
.. note::
Media types are now plugins
-Media types are enabled in your MediaGoblin configuration file, typically it is
-created by copying ``mediagoblin.ini`` to ``mediagoblin_local.ini`` and then
-applying your changes to ``mediagoblin_local.ini``. If you don't already have a
-``mediagoblin_local.ini``, create one in the way described.
+Media types are enabled in your MediaGoblin configuration file.
Most media types have additional dependencies that you will have to install.
You will find descriptions on how to satisfy the requirements of each media type
on this page.
To enable a media type, add the the media type under the ``[plugins]`` section
-in you ``mediagoblin_local.ini``. For example, if your system supported image
+in you ``mediagoblin.ini``. For example, if your system supported image
and video media types, then it would look like this::
[plugins]
@@ -99,7 +96,7 @@ good/bad/ugly). On Debianoid systems
Add ``[[mediagoblin.media_types.video]]`` under the ``[plugins]`` section in
-your ``mediagoblin_local.ini`` and restart MediaGoblin.
+your ``mediagoblin.ini`` and restart MediaGoblin.
Run
@@ -139,7 +136,7 @@ Then install ``scikits.audiolab`` for the spectrograms::
./bin/pip install scikits.audiolab
Add ``[[mediagoblin.media_types.audio]]`` under the ``[plugins]`` section in your
-``mediagoblin_local.ini`` and restart MediaGoblin.
+``mediagoblin.ini`` and restart MediaGoblin.
Run
@@ -160,7 +157,7 @@ To enable raw image you need to install pyexiv2. On Debianoid systems
sudo apt-get install python-pyexiv2
Add ``[[mediagoblin.media_types.raw_image]]`` under the ``[plugins]``
-section in your ``mediagoblin_local.ini`` and restart MediaGoblin.
+section in your ``mediagoblin.ini`` and restart MediaGoblin.
Run
@@ -184,8 +181,7 @@ library, which is necessary for creating thumbnails of ASCII art
./bin/easy_install chardet
-Next, modify (and possibly copy over from ``mediagoblin.ini``) your
-``mediagoblin_local.ini``. In the ``[plugins]`` section, add
+Next, modify your ``mediagoblin.ini``. In the ``[plugins]`` section, add
``[[mediagoblin.media_types.ascii]]``.
Run
@@ -207,7 +203,7 @@ It may work on some earlier versions, but that is not guaranteed (and
is surely not to work prior to Blender 2.5X).
Add ``[[mediagoblin.media_types.stl]]`` under the ``[plugins]`` section in your
-``mediagoblin_local.ini`` and restart MediaGoblin.
+``mediagoblin.ini`` and restart MediaGoblin.
Run
@@ -256,7 +252,7 @@ This feature has been tested on Fedora with:
It may work on some earlier versions, but that is not guaranteed.
Add ``[[mediagoblin.media_types.pdf]]`` under the ``[plugins]`` section in your
-``mediagoblin_local.ini`` and restart MediaGoblin.
+``mediagoblin.ini`` and restart MediaGoblin.
Run
diff --git a/docs/source/siteadmin/production-deployments.rst b/docs/source/siteadmin/production-deployments.rst
index ee915573..3d11f022 100644
--- a/docs/source/siteadmin/production-deployments.rst
+++ b/docs/source/siteadmin/production-deployments.rst
@@ -76,7 +76,7 @@ modify it to suit your environment's setup:
ExecStartPre=/bin/mkdir -p /run/mediagoblin
ExecStartPre=/bin/chown -hR mediagoblin:mediagoblin /run/mediagoblin
# Celery process will run as the `mediagoblin` user after start.
- Environment=MEDIAGOBLIN_CONFIG=/srv/mediagoblin.example.org/mediagoblin/mediagoblin_local.ini \
+ Environment=MEDIAGOBLIN_CONFIG=/srv/mediagoblin.example.org/mediagoblin/mediagoblin.ini \
CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_celery
ExecStart=/srv/mediagoblin.example.org/mediagoblin/bin/celery worker \
--logfile=/var/log/mediagoblin/celery.log \
diff --git a/docs/source/siteadmin/relnotes.rst b/docs/source/siteadmin/relnotes.rst
index f32ca792..1c15f249 100644
--- a/docs/source/siteadmin/relnotes.rst
+++ b/docs/source/siteadmin/relnotes.rst
@@ -36,7 +36,7 @@ carefully, or at least skim over it.
gitorious.org shut down, we had to move. We are presently on
Savannah. You may need to update your git repository location::
- git remote set-url origin git://git.savannah.gnu.org/mediagoblin.git
+ git remote set-url origin https://git.savannah.gnu.org/git/mediagoblin.git
0.9.0
@@ -49,7 +49,7 @@ Python 3, which is pretty cool!
**Do this to upgrade**
0. If you haven't already, switch the git remote URL:
- ``git remote set-url origin git://git.savannah.gnu.org/mediagoblin.git``
+ ``git remote set-url origin https://git.savannah.gnu.org/git/mediagoblin.git``
1. Update to the latest release. If checked out from git, run:
``git fetch && git checkout -q v0.9.0``
2. Run
@@ -89,7 +89,7 @@ soon as possible.
**Do this to upgrade**
0. If you haven't already, switch the git remote URL:
- ``git remote set-url origin git://git.savannah.gnu.org/mediagoblin.git``
+ ``git remote set-url origin https://git.savannah.gnu.org/git/mediagoblin.git``
1. Update to the latest release. If checked out from git, run:
``git fetch && git checkout -q v0.8.1``
2. Run
@@ -143,7 +143,7 @@ trouble, consider pinging the MediaGoblin list or IRC channel.
**Do this to upgrade**
0. If you haven't already, switch the git remote URL:
- ``git remote set-url origin git://git.savannah.gnu.org/mediagoblin.git``
+ ``git remote set-url origin https://git.savannah.gnu.org/git/mediagoblin.git``
1. If you don't have node.js installed, you'll need it for handling
MediaGoblin's static web dependencies. Install this via your
distribution! (In the glorious future MediaGoblin will be simply
diff --git a/docs/source/siteadmin/theming.rst b/docs/source/siteadmin/theming.rst
index 9c01a5b3..24f23235 100644
--- a/docs/source/siteadmin/theming.rst
+++ b/docs/source/siteadmin/theming.rst
@@ -43,7 +43,7 @@ want to install this theme! Don't worry, it's fairly painless.
3. ``tar -xzvf <tar-archive>``
4. Open your configuration file (probably named
- ``mediagoblin_local.ini``) and set the theme name::
+ ``mediagoblin.ini``) and set the theme name::
[mediagoblin]
# ...
diff --git a/lazystarter.sh b/lazystarter.sh
index 0ed22fd8..b531b068 100755
--- a/lazystarter.sh
+++ b/lazystarter.sh
@@ -25,7 +25,7 @@ case "$selfname" in
ini_prefix=paste
;;
lazycelery.sh)
- starter_cmd=celeryd
+ starter_cmd=celery
ini_prefix=mediagoblin
;;
*)
@@ -87,7 +87,7 @@ case "$selfname" in
lazycelery.sh)
MEDIAGOBLIN_CONFIG="${ini_file}" \
CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_celery \
- $starter -B "$@"
+ $starter worker -B "$@"
;;
*) exit 1 ;;
esac
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/alembic.ini b/mediagoblin/db/migrations/alembic.ini
index 7ae94f9f..4f7fc115 100644
--- a/alembic.ini
+++ b/mediagoblin/db/migrations/alembic.ini
@@ -2,7 +2,7 @@
[alembic]
# path to migration scripts
-script_location = %(here)s/mediagoblin/db/migrations
+script_location = %(here)s
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)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,