Commit | Line | Data |
---|---|---|
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 | ||
17 | import Image | |
41f446f4 | 18 | |
2e5ea6b9 | 19 | from celery.task import Task |
4a477e24 | 20 | from celery import registry |
41f446f4 | 21 | |
4a477e24 CAW |
22 | from mediagoblin.db.util import ObjectId |
23 | from mediagoblin import mg_globals as mgg | |
24 | from mediagoblin.process_media.errors import BaseProcessingFail, BadMediaFail | |
4b860cb8 | 25 | |
41f446f4 | 26 | |
24eaf0fd | 27 | THUMB_SIZE = 180, 180 |
93214d8e | 28 | MEDIUM_SIZE = 640, 640 |
41f446f4 CAW |
29 | |
30 | ||
180bdbde | 31 | def 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 | ||
42 | class 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 | 77 | process_media = registry.tasks[ProcessMedia.name] |
4a477e24 CAW |
78 | |
79 | ||
6788b412 | 80 | def 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 | ||
115 | def 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() |