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
110 orig_filepath
= self
.entry
.media_files
['original']
112 orig_filename
= self
.workbench
.localized_file(
113 mgg
.queue_store
, orig_filepath
,
115 self
.orig_filename
= orig_filename
118 def copy_original(self
, target_name
, keyname
=u
"original"):
119 self
.store_public(keyname
, self
.get_orig_filename(), target_name
)
121 def store_public(self
, keyname
, local_file
, target_name
=None):
122 if target_name
is None:
123 target_name
= os
.path
.basename(local_file
)
124 target_filepath
= create_pub_filepath(self
.entry
, target_name
)
125 if keyname
in self
.entry
.media_files
:
126 _log
.warn("store_public: keyname %r already used for file %r, "
127 "replacing with %r", keyname
,
128 self
.entry
.media_files
[keyname
], target_filepath
)
129 mgg
.public_store
.copy_local_to_storage(local_file
, target_filepath
)
130 self
.entry
.media_files
[keyname
] = target_filepath
132 def delete_queue_file(self
):
133 # Remove queued media file from storage and database.
134 # queued_filepath is in the task_id directory which should
135 # be removed too, but fail if the directory is not empty to be on
136 # the super-safe side.
137 queued_filepath
= self
.entry
.queued_media_file
138 mgg
.queue_store
.delete_file(queued_filepath
) # rm file
139 mgg
.queue_store
.delete_dir(queued_filepath
[:-1]) # rm dir
140 self
.entry
.queued_media_file
= []
143 def mark_entry_failed(entry_id
, exc
):
145 Mark a media entry as having failed in its conversion.
147 Uses the exception that was raised to mark more information. If
148 the exception is a derivative of BaseProcessingFail then we can
149 store extra information that can be useful for users telling them
150 why their media failed to process.
153 - entry_id: The id of the media entry
156 # Was this a BaseProcessingFail? In other words, was this a
157 # type of error that we know how to handle?
158 if isinstance(exc
, BaseProcessingFail
):
159 # Looks like yes, so record information about that failure and any
160 # metadata the user might have supplied.
161 atomic_update(mgg
.database
.MediaEntry
,
163 {u
'state': u
'failed',
164 u
'fail_error': unicode(exc
.exception_path
),
165 u
'fail_metadata': exc
.metadata
})
167 _log
.warn("No idea what happened here, but it failed: %r", exc
)
168 # Looks like no, so just mark it as failed and don't record a
169 # failure_error (we'll assume it wasn't handled) and don't record
170 # metadata (in fact overwrite it if somehow it had previous info
172 atomic_update(mgg
.database
.MediaEntry
,
174 {u
'state': u
'failed',
176 u
'fail_metadata': {}})
179 class BaseProcessingFail(Exception):
181 Base exception that all other processing failure messages should
184 You shouldn't call this itself; instead you should subclass it
185 and provid the exception_path and general_message applicable to
188 general_message
= u
''
191 def exception_path(self
):
193 self
.__class
__.__module
__, self
.__class
__.__name
__)
195 def __init__(self
, **metadata
):
196 self
.metadata
= metadata
or {}
199 class BadMediaFail(BaseProcessingFail
):
201 Error that should be raised when an inappropriate file was given
202 for the media type specified.
204 general_message
= _(u
'Invalid file given for media type.')