Now store metadata info from processing into the media type.
[mediagoblin.git] / mediagoblin / media_types / video / processing.py
CommitLineData
93bdab9d 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
93bdab9d
JW
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
25e39842 17from tempfile import NamedTemporaryFile
e9c1b938 18import logging
93bdab9d 19
93bdab9d 20from mediagoblin import mg_globals as mgg
196a5181 21from mediagoblin.processing import \
64712915 22 create_pub_filepath, FilenameBuilder, BaseProcessingFail, ProgressCallback
51eb0267
JW
23from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
24
26729e02 25from . import transcoders
5c754fda
JW
26from .util import skip_transcode
27
1f255101 28
8e5f9746
JW
29_log = logging.getLogger(__name__)
30_log.setLevel(logging.DEBUG)
93bdab9d
JW
31
32
51eb0267
JW
33class VideoTranscodingFail(BaseProcessingFail):
34 '''
35 Error raised if video transcoding fails
36 '''
37 general_message = _(u'Video transcoding failed')
38
39
ec4261a4 40def sniff_handler(media_file, **kw):
10085b77 41 transcoder = transcoders.VideoTranscoder()
4f4f2531 42 data = transcoder.discover(media_file.name)
10085b77 43
4f4f2531 44 _log.debug('Discovered: {0}'.format(data))
10085b77 45
4f4f2531
JW
46 if not data:
47 _log.error('Could not discover {0}'.format(
10085b77 48 kw.get('media')))
4f4f2531 49 return False
26729e02 50
4f4f2531
JW
51 if data['is_video'] == True:
52 return True
26729e02 53
ec4261a4 54 return False
93bdab9d 55
bfd68cce 56
fb46fa66 57def process_video(proc_state):
93bdab9d 58 """
5a34a80d
JW
59 Process a video entry, transcode the queued media files (originals) and
60 create a thumbnail for the entry.
45ab3e07
SS
61
62 A Workbench() represents a local tempory dir. It is automatically
63 cleaned up when this function exits.
93bdab9d 64 """
fb46fa66 65 entry = proc_state.entry
bfd68cce 66 workbench = proc_state.workbench
23caf305
CAW
67 video_config = mgg.global_config['media_type:mediagoblin.media_types.video']
68
8545cfc9 69 queued_filepath = entry.queued_media_file
bfd68cce 70 queued_filename = proc_state.get_queued_filename()
28f364bd 71 name_builder = FilenameBuilder(queued_filename)
93bdab9d 72
e9c1b938 73 medium_filepath = create_pub_filepath(
28f364bd 74 entry, name_builder.fill('{basename}-640p.webm'))
93bdab9d 75
e9c1b938 76 thumbnail_filepath = create_pub_filepath(
28f364bd 77 entry, name_builder.fill('{basename}.thumbnail.jpg'))
93bdab9d 78
c00e18fe
SS
79 # Create a temporary file for the video destination (cleaned up with workbench)
80 tmp_dst = NamedTemporaryFile(dir=workbench.dir, delete=False)
e9c1b938
JW
81 with tmp_dst:
82 # Transcode queued file to a VP8/vorbis file that fits in a 640x640 square
64712915 83 progress_callback = ProgressCallback(entry)
93bdab9d 84
5c754fda
JW
85 dimensions = (
86 mgg.global_config['media:medium']['max_width'],
87 mgg.global_config['media:medium']['max_height'])
88
29adab46 89 # Extract metadata and keep a record of it
5c754fda 90 metadata = transcoders.VideoTranscoder().discover(queued_filename)
29adab46 91 store_metadata(entry, metadata)
5c754fda 92
29adab46
CAW
93 # Figure out whether or not we need to transcode this video or
94 # if we can skip it
5c754fda
JW
95 if skip_transcode(metadata):
96 _log.debug('Skipping transcoding')
97 # Just push the submitted file to the tmp_dst
98 open(tmp_dst.name, 'wb').write(open(queued_filename, 'rb').read())
99
100 dst_dimensions = metadata['videowidth'], metadata['videoheight']
fd693e36
CAW
101
102 # Push original file to public storage
103 _log.debug('Saving original...')
104 proc_state.copy_original(queued_filepath[-1])
105
106 did_transcode = False
5c754fda
JW
107 else:
108 transcoder = transcoders.VideoTranscoder()
109
110 transcoder.transcode(queued_filename, tmp_dst.name,
111 vp8_quality=video_config['vp8_quality'],
112 vp8_threads=video_config['vp8_threads'],
113 vorbis_quality=video_config['vorbis_quality'],
114 progress_callback=progress_callback,
115 dimensions=dimensions)
116
117 dst_dimensions = transcoder.dst_data.videowidth,\
118 transcoder.dst_data.videoheight
119
fd693e36
CAW
120 # Push transcoded video to public storage
121 _log.debug('Saving medium...')
122 mgg.public_store.copy_local_to_storage(tmp_dst.name, medium_filepath)
123 _log.debug('Saved medium')
124
125 entry.media_files['webm_640'] = medium_filepath
e9c1b938 126
fd693e36 127 did_transcode = True
e9c1b938 128
5c754fda
JW
129 # Save the width and height of the transcoded video
130 entry.media_data_init(
131 width=dst_dimensions[0],
132 height=dst_dimensions[1])
93bdab9d 133
c00e18fe
SS
134 # Temporary file for the video thumbnail (cleaned up with workbench)
135 tmp_thumb = NamedTemporaryFile(dir=workbench.dir, suffix='.jpg', delete=False)
93bdab9d 136
e9c1b938
JW
137 with tmp_thumb:
138 # Create a thumbnail.jpg that fits in a 180x180 square
e4a1b6d2
JW
139 transcoders.VideoThumbnailerMarkII(
140 queued_filename,
141 tmp_thumb.name,
142 180)
1f255101 143
5c754fda
JW
144 # Push the thumbnail to public storage
145 _log.debug('Saving thumbnail...')
146 mgg.public_store.copy_local_to_storage(tmp_thumb.name, thumbnail_filepath)
147 entry.media_files['thumb'] = thumbnail_filepath
93bdab9d 148
fd693e36
CAW
149 # save the original... but only if we did a transcoding
150 # (if we skipped transcoding and just kept the original anyway as the main
151 # media, then why would we save the original twice?)
152 if video_config['keep_original'] and did_transcode:
23caf305 153 # Push original file to public storage
c00e18fe 154 _log.debug('Saving original...')
715ea495 155 proc_state.copy_original(queued_filepath[-1])
93bdab9d 156
bfd68cce
E
157 # Remove queued media file from storage and database
158 proc_state.delete_queue_file()
29adab46
CAW
159
160
161def store_metadata(media_entry, metadata):
162 """
163 Store metadata from this video for this media entry.
164 """
165 # Let's pull out the easy, not having to be converted ones first
166 stored_metadata = dict(
167 [(key, metadata[key])
168 for key in [
169 "videoheight", "videolength", "videowidth",
170 "audiorate", "audiolength", "audiochannels", "audiowidth",
171 "mimetype", "tags"]
172 if key in metadata])
173
174 # We have to convert videorate into a sequence because it's a
175 # special type normally..
176
177 if "videorate" in metadata:
178 videorate = metadata["videorate"]
179 stored_metadata["videorate"] = [videorate.num, videorate.denom]
180
181 media_entry.media_data_init(
182 orig_metadata=stored_metadata)