Whitespace and formatting cleanup.
[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
2e5ea6b9 19from celery.task import Task
4a477e24 20from celery import registry
41f446f4 21
4a477e24
CAW
22from mediagoblin.db.util import ObjectId
23from mediagoblin import mg_globals as mgg
24from mediagoblin.process_media.errors import BaseProcessingFail, BadMediaFail
4b860cb8 25
41f446f4 26
24eaf0fd 27THUMB_SIZE = 180, 180
93214d8e 28MEDIUM_SIZE = 640, 640
41f446f4
CAW
29
30
180bdbde 31def create_pub_filepath(entry, filename):
48a7ba1e 32 return mgg.public_store.get_unique_filepath(
180bdbde
E
33 ['media_entries',
34 unicode(entry['_id']),
35 filename])
36
4a477e24 37
4a477e24
CAW
38################################
39# Media processing initial steps
40################################
41
42class ProcessMedia(Task):
43 """
44 Pass this entry off for processing.
45 """
46 def run(self, media_id):
47 """
48 Pass the media entry off to the appropriate processing function
49 (for now just process_image...)
50 """
51 entry = mgg.database.MediaEntry.one(
52 {'_id': ObjectId(media_id)})
6788b412
CAW
53
54 # Try to process, and handle expected errors.
55 try:
56 process_image(entry)
57 except BaseProcessingFail, exc:
58 mark_entry_failed(entry[u'_id'], exc)
59 return
c2b862d1 60
4a477e24
CAW
61 entry['state'] = u'processed'
62 entry.save()
63
64 def on_failure(self, exc, task_id, args, kwargs, einfo):
65 """
66 If the processing failed we should mark that in the database.
67
243c3843
NY
68 Assuming that the exception raised is a subclass of
69 BaseProcessingFail, we can use that to get more information
70 about the failure and store that for conveying information to
71 users about the failure, etc.
4a477e24 72 """
6788b412
CAW
73 entry_id = args[0]
74 mark_entry_failed(entry_id, exc)
4a477e24 75
4a477e24 76
6788b412 77process_media = registry.tasks[ProcessMedia.name]
4a477e24
CAW
78
79
6788b412 80def mark_entry_failed(entry_id, exc):
2e5ea6b9
CAW
81 """
82 Mark a media entry as having failed in its conversion.
83
243c3843
NY
84 Uses the exception that was raised to mark more information. If
85 the exception is a derivative of BaseProcessingFail then we can
86 store extra information that can be useful for users telling them
87 why their media failed to process.
2e5ea6b9
CAW
88
89 Args:
90 - entry_id: The id of the media entry
91
92 """
6788b412
CAW
93 # Was this a BaseProcessingFail? In other words, was this a
94 # type of error that we know how to handle?
95 if isinstance(exc, BaseProcessingFail):
96 # Looks like yes, so record information about that failure and any
97 # metadata the user might have supplied.
98 mgg.database['media_entries'].update(
99 {'_id': entry_id},
100 {'$set': {u'state': u'failed',
101 u'fail_error': exc.exception_path,
102 u'fail_metadata': exc.metadata}})
103 else:
104 # Looks like no, so just mark it as failed and don't record a
105 # failure_error (we'll assume it wasn't handled) and don't record
106 # metadata (in fact overwrite it if somehow it had previous info
107 # here)
108 mgg.database['media_entries'].update(
109 {'_id': entry_id},
110 {'$set': {u'state': u'failed',
111 u'fail_error': None,
112 u'fail_metadata': {}}})
4a477e24
CAW
113
114
115def process_image(entry):
116 """
117 Code to process an image
118 """
119 workbench = mgg.workbench_manager.create_workbench()
41f446f4 120
fa7f9c61 121 queued_filepath = entry['queued_media_file']
52426ae0
E
122 queued_filename = workbench.localized_file(
123 mgg.queue_store, queued_filepath,
ca030ab6
CAW
124 'source')
125
4b860cb8
CAW
126 try:
127 thumb = Image.open(queued_filename)
128 except IOError:
129 raise BadMediaFail()
130
93214d8e
JW
131 thumb.thumbnail(THUMB_SIZE, Image.ANTIALIAS)
132 # ensure color mode is compatible with jpg
133 if thumb.mode != "RGB":
134 thumb = thumb.convert("RGB")
41f446f4 135
93214d8e 136 thumb_filepath = create_pub_filepath(entry, 'thumbnail.jpg')
93214d8e 137 thumb_file = mgg.public_store.get_file(thumb_filepath, 'w')
b5e7b967 138
c2b862d1 139 with thumb_file:
899d8916 140 thumb.save(thumb_file, "JPEG", quality=90)
93214d8e 141
ff520ff5
CAW
142 # If the size of the original file exceeds the specified size of a `medium`
143 # file, a `medium.jpg` files is created and later associated with the media
144 # entry.
93214d8e 145 medium = Image.open(queued_filename)
2c9e635a 146 medium_processed = False
93214d8e 147
2c9e635a
JW
148 if medium.size[0] > MEDIUM_SIZE[0] or medium.size[1] > MEDIUM_SIZE[1]:
149 medium.thumbnail(MEDIUM_SIZE, Image.ANTIALIAS)
41f446f4 150
2c9e635a
JW
151 if medium.mode != "RGB":
152 medium = medium.convert("RGB")
41f446f4 153
2c9e635a 154 medium_filepath = create_pub_filepath(entry, 'medium.jpg')
2c9e635a 155 medium_file = mgg.public_store.get_file(medium_filepath, 'w')
b5e7b967 156
c2b862d1 157 with medium_file:
5f72a4c3 158 medium.save(medium_file, "JPEG", quality=90)
2c9e635a 159 medium_processed = True
41f446f4 160
fa7f9c61
CAW
161 # we have to re-read because unlike PIL, not everything reads
162 # things in string representation :)
ca030ab6 163 queued_file = file(queued_filename, 'rb')
fa7f9c61
CAW
164
165 with queued_file:
2c9e635a 166 original_filepath = create_pub_filepath(entry, queued_filepath[-1])
c2b862d1 167
243c3843
NY
168 with mgg.public_store.get_file(original_filepath, 'wb') \
169 as original_file:
2c9e635a 170 original_file.write(queued_file.read())
fa7f9c61 171
300c34e8 172 mgg.queue_store.delete_file(queued_filepath)
180bdbde 173 entry['queued_media_file'] = []
fa7f9c61
CAW
174 media_files_dict = entry.setdefault('media_files', {})
175 media_files_dict['thumb'] = thumb_filepath
2c9e635a
JW
176 media_files_dict['original'] = original_filepath
177 if medium_processed:
178 media_files_dict['medium'] = medium_filepath
ca030ab6
CAW
179
180 # clean up workbench
b67a983a 181 workbench.destroy_self()