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/>.
17 from tempfile
import NamedTemporaryFile
21 from mediagoblin
import mg_globals
as mgg
22 from mediagoblin
.processing
import \
23 create_pub_filepath
, FilenameBuilder
, BaseProcessingFail
, ProgressCallback
24 from mediagoblin
.tools
.translate
import lazy_pass_to_ugettext
as _
26 from . import transcoders
27 from .util
import skip_transcode
29 _log
= logging
.getLogger(__name__
)
30 _log
.setLevel(logging
.DEBUG
)
33 class VideoTranscodingFail(BaseProcessingFail
):
35 Error raised if video transcoding fails
37 general_message
= _(u
'Video transcoding failed')
40 def sniff_handler(media_file
, **kw
):
41 transcoder
= transcoders
.VideoTranscoder()
42 data
= transcoder
.discover(media_file
.name
)
44 _log
.debug('Discovered: {0}'.format(data
))
47 _log
.error('Could not discover {0}'.format(
51 if data
['is_video'] == True:
57 def process_video(proc_state
):
59 Process a video entry, transcode the queued media files (originals) and
60 create a thumbnail for the entry.
62 A Workbench() represents a local tempory dir. It is automatically
63 cleaned up when this function exits.
65 entry
= proc_state
.entry
66 workbench
= proc_state
.workbench
67 video_config
= mgg
.global_config
['media_type:mediagoblin.media_types.video']
69 queued_filepath
= entry
.queued_media_file
70 queued_filename
= proc_state
.get_queued_filename()
71 name_builder
= FilenameBuilder(queued_filename
)
73 medium_filepath
= create_pub_filepath(
74 entry
, name_builder
.fill('{basename}-640p.webm'))
76 thumbnail_filepath
= create_pub_filepath(
77 entry
, name_builder
.fill('{basename}.thumbnail.jpg'))
79 # Create a temporary file for the video destination (cleaned up with workbench)
80 tmp_dst
= NamedTemporaryFile(dir=workbench
.dir, delete
=False)
82 # Transcode queued file to a VP8/vorbis file that fits in a 640x640 square
83 progress_callback
= ProgressCallback(entry
)
86 mgg
.global_config
['media:medium']['max_width'],
87 mgg
.global_config
['media:medium']['max_height'])
89 # Extract metadata and keep a record of it
90 metadata
= transcoders
.VideoTranscoder().discover(queued_filename
)
91 store_metadata(entry
, metadata
)
93 # Figure out whether or not we need to transcode this video or
95 if skip_transcode(metadata
):
96 _log
.debug('Skipping transcoding')
98 dst_dimensions
= metadata
['videowidth'], metadata
['videoheight']
100 # Push original file to public storage
101 _log
.debug('Saving original...')
102 proc_state
.copy_original(queued_filepath
[-1])
104 did_transcode
= False
106 transcoder
= transcoders
.VideoTranscoder()
108 transcoder
.transcode(queued_filename
, tmp_dst
.name
,
109 vp8_quality
=video_config
['vp8_quality'],
110 vp8_threads
=video_config
['vp8_threads'],
111 vorbis_quality
=video_config
['vorbis_quality'],
112 progress_callback
=progress_callback
,
113 dimensions
=dimensions
)
115 dst_dimensions
= transcoder
.dst_data
.videowidth
,\
116 transcoder
.dst_data
.videoheight
118 # Push transcoded video to public storage
119 _log
.debug('Saving medium...')
120 mgg
.public_store
.copy_local_to_storage(tmp_dst
.name
, medium_filepath
)
121 _log
.debug('Saved medium')
123 entry
.media_files
['webm_640'] = medium_filepath
127 # Save the width and height of the transcoded video
128 entry
.media_data_init(
129 width
=dst_dimensions
[0],
130 height
=dst_dimensions
[1])
132 # Temporary file for the video thumbnail (cleaned up with workbench)
133 tmp_thumb
= NamedTemporaryFile(dir=workbench
.dir, suffix
='.jpg', delete
=False)
136 # Create a thumbnail.jpg that fits in a 180x180 square
137 transcoders
.VideoThumbnailerMarkII(
142 # Push the thumbnail to public storage
143 _log
.debug('Saving thumbnail...')
144 mgg
.public_store
.copy_local_to_storage(tmp_thumb
.name
, thumbnail_filepath
)
145 entry
.media_files
['thumb'] = thumbnail_filepath
147 # save the original... but only if we did a transcoding
148 # (if we skipped transcoding and just kept the original anyway as the main
149 # media, then why would we save the original twice?)
150 if video_config
['keep_original'] and did_transcode
:
151 # Push original file to public storage
152 _log
.debug('Saving original...')
153 proc_state
.copy_original(queued_filepath
[-1])
155 # Remove queued media file from storage and database
156 proc_state
.delete_queue_file()
159 def store_metadata(media_entry
, metadata
):
161 Store metadata from this video for this media entry.
163 # Let's pull out the easy, not having to be converted ones first
164 stored_metadata
= dict(
165 [(key
, metadata
[key
])
167 "videoheight", "videolength", "videowidth",
168 "audiorate", "audiolength", "audiochannels", "audiowidth",
172 # We have to convert videorate into a sequence because it's a
173 # special type normally..
175 if "videorate" in metadata
:
176 videorate
= metadata
["videorate"]
177 stored_metadata
["videorate"] = [videorate
.num
, videorate
.denom
]
179 # Also make a whitelist conversion of the tags.
180 if "tags" in metadata
:
181 tags_metadata
= metadata
['tags']
183 # we don't use *all* of these, but we know these ones are
186 [(key
, tags_metadata
[key
])
188 "application-name", "artist", "audio-codec", "bitrate",
189 "container-format", "copyright", "encoder",
190 "encoder-version", "license", "nominal-bitrate", "title",
192 if key
in tags_metadata
])
193 if 'date' in tags_metadata
:
194 date
= tags_metadata
['date']
195 tags
['date'] = "%s-%s-%s" % (
196 date
.year
, date
.month
, date
.day
)
198 # TODO: handle timezone info; gst.get_time_zone_offset +
199 # python's tzinfo should help
200 if 'datetime' in tags_metadata
:
201 dt
= tags_metadata
['datetime']
202 tags
['datetime'] = datetime
.datetime(
203 dt
.get_year(), dt
.get_month(), dt
.get_day(), dt
.get_hour(),
204 dt
.get_minute(), dt
.get_second(),
205 dt
.get_microsecond()).isoformat()
207 metadata
['tags'] = tags
209 # Only save this field if there's something to save
210 if len(stored_metadata
):
211 media_entry
.media_data_init(
212 orig_metadata
=stored_metadata
)