aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.dockerignore3
-rw-r--r--Dockerfile-python2102
-rw-r--r--Dockerfile-python3100
-rw-r--r--docker-compose.yml33
-rw-r--r--docs/source/siteadmin/commandline-upload.rst2
-rw-r--r--docs/source/siteadmin/media-types.rst5
-rw-r--r--extlib/freesound/audioprocessing.py25
-rw-r--r--guix-env.scm104
-rw-r--r--mediagoblin/gmg_commands/batchaddmedia.py136
-rw-r--r--mediagoblin/init/config.py9
-rw-r--r--mediagoblin/plugins/metadata_display/static/css/metadata_display.css4
-rw-r--r--mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html2
-rw-r--r--mediagoblin/static/css/base.css4
13 files changed, 404 insertions, 125 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..12ef3383
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+# This helps to reduce the size of the Docker build context.
+user_dev
+node_modules
diff --git a/Dockerfile-python2 b/Dockerfile-python2
new file mode 100644
index 00000000..849ca8ae
--- /dev/null
+++ b/Dockerfile-python2
@@ -0,0 +1,102 @@
+# A Dockerfile for MediaGoblin hacking.
+
+# docker build -t mediagoblin-python2 -f Dockerfile-python2 .
+# docker build -t mediagoblin
+# docker run -it -p 6543:6543 -v ~/ws/mediagoblin/mediagoblin:/opt/mediagoblin/mediagoblin -v ~/ws/mediagoblin/extlib:/opt/mediagoblin/extlib mediagoblin-python2
+# docker stop [container-name/id]
+# docker start [container-name/id]
+# docker kill [container-name/id]
+
+FROM debian:buster
+
+# Install bootstrap and configure dependencies. Currently requires virtualenv
+# rather than the more modern python3-venv (should be fixed).
+RUN apt-get update && apt-get install -y \
+automake \
+git \
+nodejs \
+npm \
+python-dev \
+virtualenv
+
+# Install make and runtime dependencies.
+RUN apt-get install -y \
+python-alembic \
+python-celery \
+python-jsonschema \
+python-kombu \
+python-lxml \
+python-migrate \
+python-mock \
+python-py \
+python-pytest \
+python-pytest-xdist \
+python-six \
+python-sphinx \
+python-webtest
+
+# Install audio dependencies.
+RUN apt-get install -y \
+gstreamer1.0-libav \
+gstreamer1.0-plugins-bad \
+gstreamer1.0-plugins-base \
+gstreamer1.0-plugins-good \
+gstreamer1.0-plugins-ugly \
+libsndfile1-dev \
+python-gst-1.0 \
+python-numpy \
+python-scipy
+
+# Install video dependencies.
+RUN apt-get install -y \
+gir1.2-gst-plugins-base-1.0 \
+gir1.2-gstreamer-1.0 \
+gstreamer1.0-tools \
+python-gi
+
+# Create working directory.
+RUN mkdir /opt/mediagoblin
+RUN chown -R www-data:www-data /opt/mediagoblin
+WORKDIR /opt/mediagoblin
+
+# Create /var/www because Bower writes some cache files into /var/www during
+# make, failing if it doesn't exist.
+RUN mkdir /var/www
+RUN chown root:www-data /var/www
+RUN chmod g+w /var/www
+
+USER www-data
+
+# Clone MediaGoblin for use during the install. Could alternately copy across
+# just the files needed to run bootstrap/configure/make.
+RUN git clone git://git.savannah.gnu.org/mediagoblin.git -b master .
+RUN git submodule init && git submodule update
+
+RUN ./bootstrap.sh
+RUN VIRTUALENV_FLAGS='--system-site-packages' ./configure
+RUN make
+
+# Re-run installation of Python dependencies - seems to install more things that
+# didn't get installed with make. That shouldn't happen.
+RUN ./bin/python setup.py develop --upgrade
+
+# Only supported on Python 2.
+RUN ./bin/pip install scikits.audiolab
+
+# Patch to fix the config defaults that are failing at runtime. Needed here
+# since we're running `dbupdate` during the Docker build.
+COPY mediagoblin/init/config.py /opt/mediagoblin/mediagoblin/init/config.py
+
+RUN echo '[[mediagoblin.media_types.audio]]' >> mediagoblin.ini
+RUN echo '[[mediagoblin.media_types.video]]' >> mediagoblin.ini
+
+RUN cat mediagoblin.ini
+
+# Using default sqlite database for now.
+RUN ./bin/gmg dbupdate
+
+RUN ./bin/gmg adduser --username admin --password a --email admin@example.com
+RUN ./bin/gmg makeadmin admin
+
+# You can change this to /bin/bash if you'd prefer a shell.
+CMD ["./lazyserver.sh", "--server-name=broadcast"]
diff --git a/Dockerfile-python3 b/Dockerfile-python3
new file mode 100644
index 00000000..c617e205
--- /dev/null
+++ b/Dockerfile-python3
@@ -0,0 +1,100 @@
+# A Dockerfile for MediaGoblin hacking.
+
+# docker build -t mediagoblin-python3 -f Dockerfile-python3 .
+# docker run -it -p 6543:6543 -v ~/ws/mediagoblin/mediagoblin:/opt/mediagoblin/mediagoblin -v ~/ws/mediagoblin/extlib:/opt/mediagoblin/extlib mediagoblin-python3
+# docker stop [container-name/id]
+# docker start [container-name/id]
+# docker kill [container-name/id]
+
+FROM debian:buster
+
+# Install bootstrap and configure dependencies. Currently requires virtualenv
+# rather than the more modern python3-venv (should be fixed).
+RUN apt-get update && apt-get install -y \
+automake \
+git \
+nodejs \
+npm \
+python3-dev \
+virtualenv
+
+# Install make and runtime dependencies.
+RUN apt-get install -y \
+python3-alembic \
+python3-celery \
+python3-jsonschema \
+python3-kombu \
+python3-lxml \
+python3-migrate \
+python3-py \
+python3-pytest \
+python3-pytest-xdist \
+python3-six \
+python3-sphinx \
+python3-webtest
+
+# Install audio dependencies.
+RUN apt-get install -y \
+gstreamer1.0-libav \
+gstreamer1.0-plugins-bad \
+gstreamer1.0-plugins-base \
+gstreamer1.0-plugins-good \
+gstreamer1.0-plugins-ugly \
+libsndfile1-dev \
+python3-gst-1.0 \
+python3-numpy \
+python3-scipy
+
+# Install video dependencies.
+RUN apt-get install -y \
+gir1.2-gst-plugins-base-1.0 \
+gir1.2-gstreamer-1.0 \
+gstreamer1.0-tools \
+python3-gi
+
+# Create working directory.
+RUN mkdir /opt/mediagoblin
+RUN chown -R www-data:www-data /opt/mediagoblin
+WORKDIR /opt/mediagoblin
+
+# Create /var/www because Bower writes some cache files into /var/www during
+# make, failing if it doesn't exist.
+RUN mkdir /var/www
+RUN chown root:www-data /var/www
+RUN chmod g+w /var/www
+
+USER www-data
+
+# Clone MediaGoblin for use during the install. Could alternately copy across
+# just the files needed to run bootstrap/configure/make.
+RUN git clone git://git.savannah.gnu.org/mediagoblin.git -b master .
+RUN git submodule init && git submodule update
+
+RUN ./bootstrap.sh
+RUN VIRTUALENV_FLAGS='--system-site-packages' ./configure --with-python3
+RUN make
+
+# Re-run installation of Python dependencies - seems to install more things that
+# didn't get installed with make. That shouldn't happen.
+RUN ./bin/python setup.py develop --upgrade
+
+# Only supported on Python 2.
+# RUN ./bin/pip install scikits.audiolab
+
+# Patch to fix the config defaults that are failing at runtime. Needed here
+# since we're running `dbupdate` during the Docker build.
+COPY mediagoblin/init/config.py /opt/mediagoblin/mediagoblin/init/config.py
+
+RUN echo '[[mediagoblin.media_types.audio]]' >> mediagoblin.ini
+RUN echo '[[mediagoblin.media_types.video]]' >> mediagoblin.ini
+
+RUN cat mediagoblin.ini
+
+# Using default sqlite database for now.
+RUN ./bin/gmg dbupdate
+
+RUN ./bin/gmg adduser --username admin --password a --email admin@example.com
+RUN ./bin/gmg makeadmin admin
+
+# You can change this to /bin/bash if you'd prefer a shell.
+CMD ["./lazyserver.sh", "--server-name=broadcast"]
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..ac11257b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,33 @@
+# A docker-compose recipe for MediaGoblin hacking.
+#
+# Tested on Trisquel 8. Currently runs Python 3 and works for photos and video.
+# Audio raises an exception "NameError: name 'audiolab' is not defined".
+
+# docker-compose up --build
+# docker-compose run --rm web bin/python
+# docker-compose start [service]
+# docker-compose stop [service]
+# docker-compose down
+
+version: '2'
+
+services:
+ web:
+ build:
+ context: .
+ dockerfile: Dockerfile-python3
+ # Is user required here, or does it just pick up from the last USER in Dockerfile?
+ user: www-data
+ # Consider running dbupdate here (at runtime), rather than in Dockerfile.
+ command: ./lazyserver.sh --server-name=broadcast
+ volumes:
+ # Mount your local copy of the source for hecking on MediaGoblin.
+ - ./mediagoblin:/opt/mediagoblin/mediagoblin
+
+ # Mount your local media/secrets. Requires some initial setup:
+ #
+ # $ mkdir user_dev/media user_dev/crypto
+ # $ chmod 777 user_dev/media user_dev_crypto
+ - ./user_dev:/opt/mediagoblin/user_dev
+ ports:
+ - "6543:6543"
diff --git a/docs/source/siteadmin/commandline-upload.rst b/docs/source/siteadmin/commandline-upload.rst
index 756f5fa8..af4fd1bd 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.::
- location,dcterms:title,dcterms:creator,dcterms:type
+ location,dc:title,dc:creator,dc: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"
diff --git a/docs/source/siteadmin/media-types.rst b/docs/source/siteadmin/media-types.rst
index 8f9239be..e06739ec 100644
--- a/docs/source/siteadmin/media-types.rst
+++ b/docs/source/siteadmin/media-types.rst
@@ -131,10 +131,13 @@ To install these on Debianoid systems, run::
not compile it with alsa support. Alsa support is not necessary for the GNU
MediaGoblin application.
-Then install ``scikits.audiolab`` for the spectrograms::
+If you're running Python 2, install ``scikits.audiolab`` for the spectrograms::
./bin/pip install scikits.audiolab
+Audio spectrograms are currently not available on Python 3, since scikits.audiolab
+does not provide Python 3 support.
+
Add ``[[mediagoblin.media_types.audio]]`` under the ``[plugins]`` section in your
``mediagoblin.ini`` and restart MediaGoblin.
diff --git a/extlib/freesound/audioprocessing.py b/extlib/freesound/audioprocessing.py
index 7ef8d5d4..b9a96a97 100644
--- a/extlib/freesound/audioprocessing.py
+++ b/extlib/freesound/audioprocessing.py
@@ -44,6 +44,31 @@ try:
import scikits.audiolab as audiolab
except ImportError:
print("WARNING: audiolab is not installed so wav2png will not work")
+
+ # Hack to prevent errors when uploading audio files. The issue is that
+ # scikits.audiolab does not support Python 3. By replacing it with a mock
+ # implementation here, we can accept audio files, but we won't get the nice
+ # waveform image.
+ import six
+ if six.PY3:
+ class MockSndfile(object):
+ def __init__(self, *args, **kwargs):
+ self.nframes = 0
+ self.channels = 1
+ self.samplerate = 44100
+
+ def read_frames(self, *args):
+ return []
+
+ def seek(self, *args):
+ return
+
+ def close(self):
+ return
+ import unittest.mock as mock
+ audiolab = mock.Mock()
+ audiolab.Sndfile = MockSndfile
+
import subprocess
class AudioProcessingException(Exception):
diff --git a/guix-env.scm b/guix-env.scm
index d56f8539..acff8886 100644
--- a/guix-env.scm
+++ b/guix-env.scm
@@ -1,6 +1,7 @@
;;; GNU MediaGoblin -- federated, autonomous media hosting
;;; Copyright © 2015, 2016 David Thompson <davet@gnu.org>
;;; Copyright © 2016 Christopher Allan Webber <cwebber@dustycloud.org>
+;;; Copyright © 2019 Ben Sturmfels <ben@sturm.com.au>
;;;
;;; This program is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
@@ -19,7 +20,7 @@
;;; also borrows some code directly from Guix.
;;;
;;; ========================================
-;;;
+;;;
;;; With `guix environment' you can use guix as kind of a universal
;;; virtualenv, except a universal virtualenv with magical time traveling
;;; properties and also, not just for Python.
@@ -28,11 +29,19 @@
;;; Then do:
;;; guix environment -l guix-env.scm --pure
;;;
-;;; And the first time you use it:
+;;; You'll need to run the above command every time you close your terminal or
+;;; restart your system, so a handy way to save having to remember is to install
+;;; "direnv" an then create a ".envrc" file in your current directory containing
+;;; the following and then run "direnv allow" when prompted:
+;;; use guix -l guix-env.scm --pure
+;;;
+;;; To set things up for the first time, you'll also need to run:
+;;; git submodule init
+;;; git submodule update
;;; ./bootstrap.sh
;;; ./configure --with-python3 --without-virtualenv
;;; make
-;;; virtualenv . && ./bin/python setup.py develop --no-deps
+;;; python3 -m venv --system-site-packages . && bin/python setup.py develop --no-deps
;;;
;;; ... wait whaaat, what's that last line! I thought you said this
;;; was a reasonable virtualenv replacement! Well it is and it will
@@ -41,6 +50,14 @@
;;; for certain things to run, so we have a virtualenv with nothing
;;; in it but this project itself.
;;;
+;;; The devtools/update_extlib.sh script won't run on Guix due to missing
+;;; "/usr/bin/env", so then run:
+;;; node node_modules/.bin/bower install
+;;; ./devtools/update_extlib.sh
+;;; bin/gmg dbupdate
+;;; bin/gmg adduser --username admin --password a --email admin@example.com
+;;; ./lazyserver.sh
+;;;
;;; So anyway, now you can do:
;;; PYTHONPATH="${PYTHONPATH}:$(pwd)" ./runtests.sh
;;;
@@ -49,6 +66,9 @@
;;; the virtualenv and path-hacking stuff unnecessary.
;;;
;;; Have fun!
+;;;
+;;; Known issues:
+;;; - currently fails to upload h264 source video: "GStreamer: missing H.264 decoder"
(use-modules (ice-9 match)
(srfi srfi-1)
@@ -61,11 +81,19 @@
(gnu packages)
(gnu packages autotools)
(gnu packages base)
+ (gnu packages certs)
+ (gnu packages check)
+ (gnu packages databases)
(gnu packages python)
+ (gnu packages python-crypto)
+ (gnu packages python-web)
+ (gnu packages python-xyz)
+ (gnu packages sphinx)
(gnu packages gstreamer)
(gnu packages glib)
(gnu packages rsync)
(gnu packages ssh)
+ (gnu packages time)
(gnu packages version-control)
((guix licenses) #:select (expat zlib) #:prefix license:))
@@ -75,43 +103,28 @@
;; ourselves to...
;; =================================================================
-(define python-sqlalchemy-0.9.10
+(define python-pytest-forked
(package
- (inherit python-sqlalchemy)
- (version "0.9.10")
- (source
- (origin
- (method url-fetch)
- (uri (string-append "https://pypi.python.org/packages/source/S/"
- "SQLAlchemy/SQLAlchemy-" version ".tar.gz"))
- (sha256
- (base32
- "0fqnssf7pxvc7dvd5l83vnqz2wfvpq7y01kcl1537f9nbqnvlp24"))))
-
- ;; Temporarily skipping tests. It's the stuff that got fixed in
- ;; the recent sqlalchemy release we struggled with on-list. The
- ;; patch would have to be backported here to 0.9.10.
- (arguments
- '(#:tests? #f))))
-
-(define python-alembic-0.6.6
- (package
- (inherit python-alembic)
- (version "0.6.6")
- (source
- (origin
- (method url-fetch)
- (uri (pypi-uri "alembic" version))
- (sha256
- (base32
- "0i3nic56blq079vj1iskkmllwjp980vnvvx898d3bm5qa416crcn"))))
- (native-inputs
- `(("python-nose" ,python-nose)
- ,@(package-native-inputs python-alembic)))
- (propagated-inputs
- `(("python-sqlalchemy" ,python-sqlalchemy-0.9.10)
- ("python-mako" ,python-mako)
- ("python-editor" ,python-editor)))))
+ (name "python-pytest-forked")
+ (version "1.0.2")
+ (source
+ (origin
+ (method url-fetch)
+ (uri (pypi-uri "pytest-forked" version))
+ (sha256
+ (base32
+ "0f4y1jhcg70xhm220pdb8r24n01knhn749aqlr14vmgbsb7allnk"))))
+ (build-system python-build-system)
+ (propagated-inputs
+ `(("python-pytest" ,python-pytest)
+ ("python-setuptools-scm" ,python-setuptools-scm)))
+ (home-page
+ "https://github.com/pytest-dev/pytest-forked")
+ (synopsis
+ "run tests in isolated forked subprocesses")
+ (description
+ "run tests in isolated forked subprocesses")
+ (license license:expat)))
;; =================================================================
@@ -127,11 +140,16 @@
(base32
"0p2gj4z351166d1zqmmd8wc9bzb69w0fjm8qq1fs8dw2yhcg2wwv"))))
(build-system python-build-system)
+ (arguments
+ ;; Complains about missing gunicorn. Not sure where that comes from.
+ '(#:tests? #f))
(native-inputs
- `(("python-pytest" ,python-pytest)))
+ `(("python-pytest" ,python-pytest)
+ ("nss-certs" ,nss-certs)))
(propagated-inputs
`(("python-alembic" ,python-alembic)
("python-pytest-xdist" ,python-pytest-xdist)
+ ("python-pytest-forked" ,python-pytest-forked)
("python-celery" ,python-celery)
("python-kombu" ,python-kombu)
("python-webtest" ,python-webtest)
@@ -141,7 +159,7 @@
("python-translitcodec" ,python-translitcodec)
("python-babel" ,python-babel)
("python-configobj" ,python-configobj)
- ("python-dateutil-2" ,python-dateutil-2)
+ ("python-dateutil" ,python-dateutil)
("python-itsdangerous" ,python-itsdangerous)
("python-jinja2" ,python-jinja2)
("python-jsonschema" ,python-jsonschema)
@@ -175,8 +193,10 @@ media.")
(inputs
`(;;; audio/video stuff
("gstreamer" ,gstreamer)
+ ("gst-libav" ,gst-plugins-base)
("gst-plugins-base" ,gst-plugins-base)
("gst-plugins-good" ,gst-plugins-good)
+ ("gst-plugins-bad" ,gst-plugins-bad)
("gst-plugins-ugly" ,gst-plugins-ugly)
("gobject-introspection" ,gobject-introspection)
;; useful to have!
@@ -185,7 +205,7 @@ media.")
("which" ,which)
("git" ,git)
("automake" ,automake)
- ("autoconf" ,(autoconf-wrapper))
+ ("autoconf" ,autoconf)
,@(package-inputs mediagoblin)))
(propagated-inputs
`(("python" ,python)
diff --git a/mediagoblin/gmg_commands/batchaddmedia.py b/mediagoblin/gmg_commands/batchaddmedia.py
index 55ed865b..88fa3e5a 100644
--- a/mediagoblin/gmg_commands/batchaddmedia.py
+++ b/mediagoblin/gmg_commands/batchaddmedia.py
@@ -14,19 +14,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import print_function
+from __future__ import print_function, unicode_literals
-import codecs
import csv
import os
-import sys
+import shutil
+import tempfile
import requests
import six
-
from six.moves.urllib.parse import urlparse
-from mediagoblin.db.models import LocalUser
+from mediagoblin.db.models import LocalUser, MediaEntry
from mediagoblin.gmg_commands import util as commands_util
from mediagoblin.submit.lib import (
submit_media, FileUploadLimit, UserUploadLimit, UserPastUploadLimit)
@@ -38,21 +37,21 @@ from jsonschema.exceptions import ValidationError
def parser_setup(subparser):
subparser.description = """\
This command allows the administrator to upload many media files at once."""
- subparser.epilog = _(u"""For more information about how to properly run this
+ subparser.epilog = _("""For more information about how to properly run this
script (and how to format the metadata csv file), read the MediaGoblin
documentation page on command line uploading
<http://docs.mediagoblin.org/siteadmin/commandline-upload.html>""")
subparser.add_argument(
'username',
- help=_(u"Name of user these media entries belong to"))
+ help=_("Name of user these media entries belong to"))
subparser.add_argument(
'metadata_path',
help=_(
-u"""Path to the csv file containing metadata information."""))
+"""Path to the csv file containing metadata information."""))
subparser.add_argument(
'--celery',
action='store_true',
- help=_(u"Don't process eagerly, pass off to celery"))
+ help=_("Don't process eagerly, pass off to celery"))
def batchaddmedia(args):
@@ -69,7 +68,7 @@ def batchaddmedia(args):
LocalUser.username==args.username.lower()
).first()
if user is None:
- print(_(u"Sorry, no user by username '{username}' exists".format(
+ print(_("Sorry, no user by username '{username}' exists".format(
username=args.username)))
return
@@ -77,7 +76,7 @@ def batchaddmedia(args):
metadata_path = args.metadata_path
else:
- error = _(u'File at {path} not found, use -h flag for help'.format(
+ error = _('File at {path} not found, use -h flag for help'.format(
path=args.metadata_path))
print(error)
return
@@ -85,19 +84,12 @@ def batchaddmedia(args):
abs_metadata_filename = os.path.abspath(metadata_path)
abs_metadata_dir = os.path.dirname(abs_metadata_filename)
- def maybe_unicodeify(some_string):
- # this is kinda terrible
- if some_string is None:
- return None
- else:
- return six.text_type(some_string)
-
- with codecs.open(
- abs_metadata_filename, 'r', encoding='utf-8') as all_metadata:
- contents = all_metadata.read()
- media_metadata = parse_csv_file(contents)
+ all_metadata = open(abs_metadata_filename, 'r')
+ media_metadata = csv.DictReader(all_metadata)
+ for index, file_metadata in enumerate(media_metadata):
+ if six.PY2:
+ file_metadata = {k.decode('utf-8'): v.decode('utf-8') for k, v in file_metadata.items()}
- 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({})
@@ -108,6 +100,7 @@ def batchaddmedia(args):
### Pull the important media information for mediagoblin from the
### metadata, if it is provided.
+ slug = file_metadata.get('slug')
title = file_metadata.get('title') or file_metadata.get('dc:title')
description = (file_metadata.get('description') or
file_metadata.get('dc:description'))
@@ -117,7 +110,8 @@ def batchaddmedia(args):
try:
json_ld_metadata = compact_and_validate(file_metadata)
except ValidationError as exc:
- error = _(u"""Error with media '{media_id}' value '{error_path}': {error_msg}
+ media_id = file_metadata.get('id') or index
+ error = _("""Error with media '{media_id}' value '{error_path}': {error_msg}
Metadata was not uploaded.""".format(
media_id=media_id,
error_path=exc.path[0],
@@ -125,12 +119,36 @@ Metadata was not uploaded.""".format(
print(error)
continue
+ if slug and MediaEntry.query.filter_by(actor=user.id, slug=slug).count():
+ # Avoid re-importing media from a previous batch run. Note that this
+ # check isn't quite robust enough, since it requires that a slug is
+ # specified. Probably needs to be based on "location" since this is
+ # the only required field.
+ error = '{}: {}'.format(
+ slug, _('An entry with that slug already exists for this user.'))
+ print(error)
+ continue
+
url = urlparse(original_location)
filename = url.path.split()[-1]
- if url.scheme == 'http':
+ if url.scheme.startswith('http'):
res = requests.get(url.geturl(), stream=True)
- media_file = res.raw
+ if res.headers.get('content-encoding'):
+ # The requests library's "raw" method does not deal with content
+ # encoding. Alternative could be to use iter_content(), and
+ # write chunks to the temporary file.
+ raise NotImplementedError('URL-based media with content-encoding (eg. gzip) are not currently supported.')
+
+ # To avoid loading the media into memory all at once, we write it to
+ # a file before importing. This currently requires free space up to
+ # twice the size of the media file. Memory use can be tested by
+ # running something like `ulimit -Sv 200000` before running
+ # `batchaddmedia` to upload a file larger than 200MB.
+ media_file = tempfile.TemporaryFile()
+ shutil.copyfileobj(res.raw, media_file)
+ if six.PY2:
+ media_file.seek(0)
elif url.scheme == '':
path = url.path
@@ -142,76 +160,42 @@ Metadata was not uploaded.""".format(
try:
media_file = open(file_abs_path, 'rb')
except IOError:
- print(_(u"""\
+ print(_("""\
FAIL: Local file {filename} could not be accessed.
{filename} will not be uploaded.""".format(filename=filename)))
continue
try:
- submit_media(
+ entry = submit_media(
mg_app=app,
user=user,
submitted_file=media_file,
filename=filename,
- title=maybe_unicodeify(title),
- description=maybe_unicodeify(description),
- collection_slug=maybe_unicodeify(collection_slug),
- license=maybe_unicodeify(license),
+ title=title,
+ description=description,
+ collection_slug=collection_slug,
+ license=license,
metadata=json_ld_metadata,
- tags_string=u"")
- print(_(u"""Successfully submitted {filename}!
+ tags_string="")
+ if slug:
+ # Slug is automatically set by submit_media, so overwrite it
+ # with the desired slug.
+ entry.slug = slug
+ entry.save()
+ print(_("""Successfully submitted {filename}!
Be sure to look at the Media Processing Panel on your website to be sure it
uploaded successfully.""".format(filename=filename)))
files_uploaded += 1
except FileUploadLimit:
print(_(
-u"FAIL: This file is larger than the upload limits for this site."))
+"FAIL: This file is larger than the upload limits for this site."))
except UserUploadLimit:
print(_(
"FAIL: This file will put this user past their upload limits."))
except UserPastUploadLimit:
print(_("FAIL: This user is already past their upload limits."))
+ finally:
+ media_file.close()
print(_(
"{files_uploaded} out of {files_attempted} files successfully submitted".format(
files_uploaded=files_uploaded,
files_attempted=files_attempted)))
-
-
-def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs):
- # csv.py doesn't do Unicode; encode temporarily as UTF-8:
- # TODO: this probably won't be necessary in Python 3
- csv_reader = csv.reader(utf_8_encoder(unicode_csv_data),
- dialect=dialect, **kwargs)
- for row in csv_reader:
- # decode UTF-8 back to Unicode, cell by cell:
- yield [six.text_type(cell, 'utf-8') for cell in row]
-
-def utf_8_encoder(unicode_csv_data):
- for line in unicode_csv_data:
- yield line.encode('utf-8')
-
-def parse_csv_file(file_contents):
- """
- The helper function which converts the csv file into a dictionary where each
- item's key is the provided value 'id' and each item's value is another
- dictionary.
- """
- list_of_contents = file_contents.split('\n')
- key, lines = (list_of_contents[0].split(','),
- list_of_contents[1:])
- objects_dict = {}
-
- # Build a dictionary
- for index, line in enumerate(lines):
- if line.isspace() or line == u'': continue
- 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
- objects_dict[media_id] = (line_dict)
-
- return objects_dict
diff --git a/mediagoblin/init/config.py b/mediagoblin/init/config.py
index fe469156..2e22083a 100644
--- a/mediagoblin/init/config.py
+++ b/mediagoblin/init/config.py
@@ -84,6 +84,15 @@ def read_mediagoblin_config(config_path, config_spec_path=CONFIG_SPEC_PATH):
config_spec_path,
encoding="UTF8", list_values=False, _inspec=True)
+ # HACK to get MediaGoblin running under Docker/Python 3. Without this line,
+ # `./bin/gmg dbupdate` fails as the configuration under 'DEFAULT' in
+ # config_spec still had %(here)s markers in it, when these should have been
+ # replaced with actual paths, resulting in
+ # "configobj.MissingInterpolationOption: missing option "here" in
+ # interpolation". This issue doesn't seem to appear when running on Guix,
+ # but adding this line also doesn't appear to cause problems on Guix.
+ _setup_defaults(config_spec, config_path)
+
# Set up extra defaults that will be pushed into the rest of the
# configs. This is a combined extrapolation of defaults based on
mainconfig_defaults = copy.copy(config_spec.get("DEFAULT", {}))
diff --git a/mediagoblin/plugins/metadata_display/static/css/metadata_display.css b/mediagoblin/plugins/metadata_display/static/css/metadata_display.css
index e4612b02..dd787e94 100644
--- a/mediagoblin/plugins/metadata_display/static/css/metadata_display.css
+++ b/mediagoblin/plugins/metadata_display/static/css/metadata_display.css
@@ -1,6 +1,6 @@
table.metadata_info {
font-size:85%;
- margin-left:10px;
+ margin: 8px 0 16px 8px;
}
table.metadata_info th {
@@ -8,7 +8,7 @@ table.metadata_info th {
border-spacing: 10px;
text-align: left;
}
+
table.metadata_info td {
padding: 4px 8px;
}
-
diff --git a/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html b/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html
index 15ea1536..6fc46212 100644
--- a/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html
+++ b/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html
@@ -23,7 +23,7 @@
{#- NOTE: In some smart future where the context is more extensible,
we will need to add to the prefix here-#}
<table class="metadata_info">
- {%- for key, value in metadata.iteritems() if not key=='@context' %}
+ {%- for key, value in metadata.items() if key != '@context' %}
{% if value -%}
<tr>
<th>{{ rdfa_to_readable(key) }}</th>
diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css
index 6da19f94..11558fe5 100644
--- a/mediagoblin/static/css/base.css
+++ b/mediagoblin/static/css/base.css
@@ -142,7 +142,7 @@ header {
.header_right {
width: 47%;
- margin: 8px 8px 4px 0;
+ margin: 8px 8px 8px 0;
display: inline-block;
float: right;
text-align: right;
@@ -195,7 +195,7 @@ a.logo {
.logo img {
vertical-align: middle;
- margin: 6px 8px 6px 0;
+ margin: 8px 8px 6px 0;
}
.welcomeimage {