508. Updates copyright/license information
[mediagoblin.git] / mediagoblin / process_media / __init__.py
CommitLineData
41f446f4 1# GNU MediaGoblin -- federated, autonomous media hosting
12a100e4 2# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
41f446f4
CAW
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import Image
41f446f4 18
851c51a3 19from contextlib import contextmanager
2e5ea6b9 20from celery.task import Task
4a477e24 21from celery import registry
41f446f4 22
4a477e24
CAW
23from mediagoblin.db.util import ObjectId
24from mediagoblin import mg_globals as mgg
25from mediagoblin.process_media.errors import BaseProcessingFail, BadMediaFail
4b860cb8 26
41f446f4 27
24eaf0fd 28THUMB_SIZE = 180, 180
93214d8e 29MEDIUM_SIZE = 640, 640
41f446f4
CAW
30
31
180bdbde 32def create_pub_filepath(entry, filename):
48a7ba1e 33 return mgg.public_store.get_unique_filepath(
180bdbde
E
34 ['media_entries',
35 unicode(entry['_id']),
36 filename])
37
4a477e24 38
851c51a3
JW
39@contextmanager
40def closing(callback):
41 try:
42 yield callback
43 finally:
44 pass
180bdbde 45
ca030ab6 46
4a477e24
CAW
47################################
48# Media processing initial steps
49################################
50
51class ProcessMedia(Task):
52 """
53 Pass this entry off for processing.
54 """
55 def run(self, media_id):
56 """
57 Pass the media entry off to the appropriate processing function
58 (for now just process_image...)
59 """
60 entry = mgg.database.MediaEntry.one(
61 {'_id': ObjectId(media_id)})
6788b412
CAW
62
63 # Try to process, and handle expected errors.
64 try:
65 process_image(entry)
66 except BaseProcessingFail, exc:
67 mark_entry_failed(entry[u'_id'], exc)
68 return
69
4a477e24
CAW
70 entry['state'] = u'processed'
71 entry.save()
72
73 def on_failure(self, exc, task_id, args, kwargs, einfo):
74 """
75 If the processing failed we should mark that in the database.
76
77 Assuming that the exception raised is a subclass of BaseProcessingFail,
78 we can use that to get more information about the failure and store that
79 for conveying information to users about the failure, etc.
80 """
6788b412
CAW
81 entry_id = args[0]
82 mark_entry_failed(entry_id, exc)
4a477e24 83
4a477e24 84
6788b412 85process_media = registry.tasks[ProcessMedia.name]
4a477e24
CAW
86
87
6788b412 88def mark_entry_failed(entry_id, exc):
2e5ea6b9
CAW
89 """
90 Mark a media entry as having failed in its conversion.
91
92 Uses the exception that was raised to mark more information. If the
93 exception is a derivative of BaseProcessingFail then we can store extra
94 information that can be useful for users telling them why their media failed
95 to process.
96
97 Args:
98 - entry_id: The id of the media entry
99
100 """
6788b412
CAW
101 # Was this a BaseProcessingFail? In other words, was this a
102 # type of error that we know how to handle?
103 if isinstance(exc, BaseProcessingFail):
104 # Looks like yes, so record information about that failure and any
105 # metadata the user might have supplied.
106 mgg.database['media_entries'].update(
107 {'_id': entry_id},
108 {'$set': {u'state': u'failed',
109 u'fail_error': exc.exception_path,
110 u'fail_metadata': exc.metadata}})
111 else:
112 # Looks like no, so just mark it as failed and don't record a
113 # failure_error (we'll assume it wasn't handled) and don't record
114 # metadata (in fact overwrite it if somehow it had previous info
115 # here)
116 mgg.database['media_entries'].update(
117 {'_id': entry_id},
118 {'$set': {u'state': u'failed',
119 u'fail_error': None,
120 u'fail_metadata': {}}})
4a477e24
CAW
121
122
123def process_image(entry):
124 """
125 Code to process an image
126 """
127 workbench = mgg.workbench_manager.create_workbench()
41f446f4 128
fa7f9c61 129 queued_filepath = entry['queued_media_file']
52426ae0
E
130 queued_filename = workbench.localized_file(
131 mgg.queue_store, queued_filepath,
ca030ab6
CAW
132 'source')
133
4b860cb8
CAW
134 try:
135 thumb = Image.open(queued_filename)
136 except IOError:
137 raise BadMediaFail()
138
93214d8e
JW
139 thumb.thumbnail(THUMB_SIZE, Image.ANTIALIAS)
140 # ensure color mode is compatible with jpg
141 if thumb.mode != "RGB":
142 thumb = thumb.convert("RGB")
41f446f4 143
93214d8e 144 thumb_filepath = create_pub_filepath(entry, 'thumbnail.jpg')
93214d8e 145 thumb_file = mgg.public_store.get_file(thumb_filepath, 'w')
b5e7b967 146
851c51a3 147 with closing(thumb_file):
899d8916 148 thumb.save(thumb_file, "JPEG", quality=90)
93214d8e 149
ff520ff5
CAW
150 # If the size of the original file exceeds the specified size of a `medium`
151 # file, a `medium.jpg` files is created and later associated with the media
152 # entry.
93214d8e 153 medium = Image.open(queued_filename)
2c9e635a 154 medium_processed = False
93214d8e 155
2c9e635a
JW
156 if medium.size[0] > MEDIUM_SIZE[0] or medium.size[1] > MEDIUM_SIZE[1]:
157 medium.thumbnail(MEDIUM_SIZE, Image.ANTIALIAS)
41f446f4 158
2c9e635a
JW
159 if medium.mode != "RGB":
160 medium = medium.convert("RGB")
41f446f4 161
2c9e635a 162 medium_filepath = create_pub_filepath(entry, 'medium.jpg')
2c9e635a 163 medium_file = mgg.public_store.get_file(medium_filepath, 'w')
b5e7b967 164
851c51a3 165 with closing(medium_file):
5f72a4c3 166 medium.save(medium_file, "JPEG", quality=90)
2c9e635a 167 medium_processed = True
41f446f4 168
fa7f9c61
CAW
169 # we have to re-read because unlike PIL, not everything reads
170 # things in string representation :)
ca030ab6 171 queued_file = file(queued_filename, 'rb')
fa7f9c61
CAW
172
173 with queued_file:
2c9e635a 174 original_filepath = create_pub_filepath(entry, queued_filepath[-1])
fa7f9c61 175
851c51a3 176 with closing(mgg.public_store.get_file(original_filepath, 'wb')) as original_file:
2c9e635a 177 original_file.write(queued_file.read())
fa7f9c61 178
300c34e8 179 mgg.queue_store.delete_file(queued_filepath)
180bdbde 180 entry['queued_media_file'] = []
fa7f9c61
CAW
181 media_files_dict = entry.setdefault('media_files', {})
182 media_files_dict['thumb'] = thumb_filepath
2c9e635a
JW
183 media_files_dict['original'] = original_filepath
184 if medium_processed:
185 media_files_dict['medium'] = medium_filepath
ca030ab6
CAW
186
187 # clean up workbench
b67a983a 188 workbench.destroy_self()