5ce9281b584ed334e5e8707ca4dc607eb7745fd6
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
20 from mediagoblin
.db
.util
import atomic_update
21 from mediagoblin
import mg_globals
as mgg
23 from mediagoblin
.tools
.translate
import lazy_pass_to_ugettext
as _
25 _log
= logging
.getLogger(__name__
)
28 class ProgressCallback(object):
29 def __init__(self
, entry
):
32 def __call__(self
, progress
):
34 self
.entry
.transcoding_progress
= progress
38 def create_pub_filepath(entry
, filename
):
39 return mgg
.public_store
.get_unique_filepath(
45 class FilenameBuilder(object):
46 """Easily slice and dice filenames.
48 Initialize this class with an original file path, then use the fill()
49 method to create new filenames based on the original.
52 MAX_FILENAME_LENGTH
= 255 # VFAT's maximum filename length
54 def __init__(self
, path
):
55 """Initialize a builder from an original file path."""
56 self
.dirpath
, self
.basename
= os
.path
.split(path
)
57 self
.basename
, self
.ext
= os
.path
.splitext(self
.basename
)
58 self
.ext
= self
.ext
.lower()
60 def fill(self
, fmtstr
):
61 """Build a new filename based on the original.
63 The fmtstr argument can include the following:
64 {basename} -- the original basename, with the extension removed
65 {ext} -- the original extension, always lowercase
67 If necessary, {basename} will be truncated so the filename does not
68 exceed this class' MAX_FILENAME_LENGTH in length.
71 basename_len
= (self
.MAX_FILENAME_LENGTH
-
72 len(fmtstr
.format(basename
='', ext
=self
.ext
)))
73 return fmtstr
.format(basename
=self
.basename
[:basename_len
],
77 class ProcessingState(object):
79 The first and only argument to the "processor" of a media type
81 This could be thought of as a "request" to the processor
82 function. It has the main info for the request (media entry)
83 and a bunch of tools for the request on it.
84 It can get more fancy without impacting old media types.
86 def __init__(self
, entry
):
89 self
.orig_filename
= None
91 def set_workbench(self
, wb
):
94 def get_orig_filename(self
):
96 Get the a filename for the original, on local storage
98 If the media entry has a queued_media_file, use that, otherwise
101 In the future, this will return the highest quality file available
102 if neither the original or queued file are available
104 if self
.orig_filename
is not None:
105 return self
.orig_filename
107 if self
.entry
.queued_media_file
:
108 orig_filepath
= self
.entry
.queued_media_file
109 storage
= mgg
.queue_store
111 orig_filepath
= self
.entry
.media_files
['original']
112 storage
= mgg
.public_store
114 orig_filename
= self
.workbench
.localized_file(
115 storage
, orig_filepath
,
117 self
.orig_filename
= orig_filename
120 def copy_original(self
, target_name
, keyname
=u
"original"):
121 self
.store_public(keyname
, self
.get_orig_filename(), target_name
)
123 def store_public(self
, keyname
, local_file
, target_name
=None):
124 if target_name
is None:
125 target_name
= os
.path
.basename(local_file
)
126 target_filepath
= create_pub_filepath(self
.entry
, target_name
)
127 if keyname
in self
.entry
.media_files
:
128 _log
.warn("store_public: keyname %r already used for file %r, "
129 "replacing with %r", keyname
,
130 self
.entry
.media_files
[keyname
], target_filepath
)
131 mgg
.public_store
.copy_local_to_storage(local_file
, target_filepath
)
132 self
.entry
.media_files
[keyname
] = target_filepath
134 def delete_queue_file(self
):
135 # Remove queued media file from storage and database.
136 # queued_filepath is in the task_id directory which should
137 # be removed too, but fail if the directory is not empty to be on
138 # the super-safe side.
139 queued_filepath
= self
.entry
.queued_media_file
140 mgg
.queue_store
.delete_file(queued_filepath
) # rm file
141 mgg
.queue_store
.delete_dir(queued_filepath
[:-1]) # rm dir
142 self
.entry
.queued_media_file
= []
145 def mark_entry_failed(entry_id
, exc
):
147 Mark a media entry as having failed in its conversion.
149 Uses the exception that was raised to mark more information. If
150 the exception is a derivative of BaseProcessingFail then we can
151 store extra information that can be useful for users telling them
152 why their media failed to process.
155 - entry_id: The id of the media entry
158 # Was this a BaseProcessingFail? In other words, was this a
159 # type of error that we know how to handle?
160 if isinstance(exc
, BaseProcessingFail
):
161 # Looks like yes, so record information about that failure and any
162 # metadata the user might have supplied.
163 atomic_update(mgg
.database
.MediaEntry
,
165 {u
'state': u
'failed',
166 u
'fail_error': unicode(exc
.exception_path
),
167 u
'fail_metadata': exc
.metadata
})
169 _log
.warn("No idea what happened here, but it failed: %r", exc
)
170 # Looks like no, so just mark it as failed and don't record a
171 # failure_error (we'll assume it wasn't handled) and don't record
172 # metadata (in fact overwrite it if somehow it had previous info
174 atomic_update(mgg
.database
.MediaEntry
,
176 {u
'state': u
'failed',
178 u
'fail_metadata': {}})
181 class BaseProcessingFail(Exception):
183 Base exception that all other processing failure messages should
186 You shouldn't call this itself; instead you should subclass it
187 and provid the exception_path and general_message applicable to
190 general_message
= u
''
193 def exception_path(self
):
195 self
.__class
__.__module
__, self
.__class
__.__name
__)
197 def __init__(self
, **metadata
):
198 self
.metadata
= metadata
or {}
201 class BadMediaFail(BaseProcessingFail
):
203 Error that should be raised when an inappropriate file was given
204 for the media type specified.
206 general_message
= _(u
'Invalid file given for media type.')