Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(110)

Side by Side Diff: media/capture/video/win/video_capture_device_win.cc

Issue 2824773002: Rename ScopedComPtr::get() to ScopedComPtr::Get() (Closed)
Patch Set: Update to 5293966 Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "media/capture/video/win/video_capture_device_win.h" 5 #include "media/capture/video/win/video_capture_device_win.h"
6 6
7 #include <ks.h> 7 #include <ks.h>
8 #include <ksmedia.h> 8 #include <ksmedia.h>
9 9
10 #include <algorithm> 10 #include <algorithm>
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
125 // Finds an IPin on an IBaseFilter given the direction, Category and/or Major 125 // Finds an IPin on an IBaseFilter given the direction, Category and/or Major
126 // Type. If either |category| or |major_type| are GUID_NULL, they are ignored. 126 // Type. If either |category| or |major_type| are GUID_NULL, they are ignored.
127 // static 127 // static
128 ScopedComPtr<IPin> VideoCaptureDeviceWin::GetPin(IBaseFilter* filter, 128 ScopedComPtr<IPin> VideoCaptureDeviceWin::GetPin(IBaseFilter* filter,
129 PIN_DIRECTION pin_dir, 129 PIN_DIRECTION pin_dir,
130 REFGUID category, 130 REFGUID category,
131 REFGUID major_type) { 131 REFGUID major_type) {
132 ScopedComPtr<IPin> pin; 132 ScopedComPtr<IPin> pin;
133 ScopedComPtr<IEnumPins> pin_enum; 133 ScopedComPtr<IEnumPins> pin_enum;
134 HRESULT hr = filter->EnumPins(pin_enum.Receive()); 134 HRESULT hr = filter->EnumPins(pin_enum.Receive());
135 if (pin_enum.get() == NULL) 135 if (pin_enum.Get() == NULL)
136 return pin; 136 return pin;
137 137
138 // Get first unconnected pin. 138 // Get first unconnected pin.
139 hr = pin_enum->Reset(); // set to first pin 139 hr = pin_enum->Reset(); // set to first pin
140 while ((hr = pin_enum->Next(1, pin.Receive(), NULL)) == S_OK) { 140 while ((hr = pin_enum->Next(1, pin.Receive(), NULL)) == S_OK) {
141 PIN_DIRECTION this_pin_dir = static_cast<PIN_DIRECTION>(-1); 141 PIN_DIRECTION this_pin_dir = static_cast<PIN_DIRECTION>(-1);
142 hr = pin->QueryDirection(&this_pin_dir); 142 hr = pin->QueryDirection(&this_pin_dir);
143 if (pin_dir == this_pin_dir) { 143 if (pin_dir == this_pin_dir) {
144 if ((category == GUID_NULL || PinMatchesCategory(pin.get(), category)) && 144 if ((category == GUID_NULL || PinMatchesCategory(pin.Get(), category)) &&
145 (major_type == GUID_NULL || 145 (major_type == GUID_NULL ||
146 PinMatchesMajorType(pin.get(), major_type))) { 146 PinMatchesMajorType(pin.Get(), major_type))) {
147 return pin; 147 return pin;
148 } 148 }
149 } 149 }
150 pin.Reset(); 150 pin.Reset();
151 } 151 }
152 152
153 DCHECK(!pin.get()); 153 DCHECK(!pin.Get());
154 return pin; 154 return pin;
155 } 155 }
156 156
157 // static 157 // static
158 VideoPixelFormat 158 VideoPixelFormat
159 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat( 159 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
160 const GUID& sub_type) { 160 const GUID& sub_type) {
161 static struct { 161 static struct {
162 const GUID& sub_type; 162 const GUID& sub_type;
163 VideoPixelFormat format; 163 VideoPixelFormat format;
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
227 227
228 VideoCaptureDeviceWin::VideoCaptureDeviceWin( 228 VideoCaptureDeviceWin::VideoCaptureDeviceWin(
229 const VideoCaptureDeviceDescriptor& device_descriptor) 229 const VideoCaptureDeviceDescriptor& device_descriptor)
230 : device_descriptor_(device_descriptor), state_(kIdle) { 230 : device_descriptor_(device_descriptor), state_(kIdle) {
231 // TODO(mcasas): Check that CoInitializeEx() has been called with the 231 // TODO(mcasas): Check that CoInitializeEx() has been called with the
232 // appropriate Apartment model, i.e., Single Threaded. 232 // appropriate Apartment model, i.e., Single Threaded.
233 } 233 }
234 234
235 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() { 235 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() {
236 DCHECK(thread_checker_.CalledOnValidThread()); 236 DCHECK(thread_checker_.CalledOnValidThread());
237 if (media_control_.get()) 237 if (media_control_.Get())
238 media_control_->Stop(); 238 media_control_->Stop();
239 239
240 if (graph_builder_.get()) { 240 if (graph_builder_.Get()) {
241 if (sink_filter_.get()) { 241 if (sink_filter_.get()) {
242 graph_builder_->RemoveFilter(sink_filter_.get()); 242 graph_builder_->RemoveFilter(sink_filter_.get());
243 sink_filter_ = NULL; 243 sink_filter_ = NULL;
244 } 244 }
245 245
246 if (capture_filter_.get()) 246 if (capture_filter_.Get())
247 graph_builder_->RemoveFilter(capture_filter_.get()); 247 graph_builder_->RemoveFilter(capture_filter_.Get());
248 } 248 }
249 249
250 if (capture_graph_builder_.get()) 250 if (capture_graph_builder_.Get())
251 capture_graph_builder_.Reset(); 251 capture_graph_builder_.Reset();
252 } 252 }
253 253
254 bool VideoCaptureDeviceWin::Init() { 254 bool VideoCaptureDeviceWin::Init() {
255 DCHECK(thread_checker_.CalledOnValidThread()); 255 DCHECK(thread_checker_.CalledOnValidThread());
256 HRESULT hr; 256 HRESULT hr;
257 257
258 hr = GetDeviceFilter(device_descriptor_.device_id, capture_filter_.Receive()); 258 hr = GetDeviceFilter(device_descriptor_.device_id, capture_filter_.Receive());
259 DLOG_IF_FAILED_WITH_HRESULT("Failed to create capture filter", hr); 259 DLOG_IF_FAILED_WITH_HRESULT("Failed to create capture filter", hr);
260 if (!capture_filter_.get()) 260 if (!capture_filter_.Get())
261 return false; 261 return false;
262 262
263 output_capture_pin_ = GetPin(capture_filter_.get(), PINDIR_OUTPUT, 263 output_capture_pin_ = GetPin(capture_filter_.Get(), PINDIR_OUTPUT,
264 PIN_CATEGORY_CAPTURE, GUID_NULL); 264 PIN_CATEGORY_CAPTURE, GUID_NULL);
265 if (!output_capture_pin_.get()) { 265 if (!output_capture_pin_.Get()) {
266 DLOG(ERROR) << "Failed to get capture output pin"; 266 DLOG(ERROR) << "Failed to get capture output pin";
267 return false; 267 return false;
268 } 268 }
269 269
270 // Create the sink filter used for receiving Captured frames. 270 // Create the sink filter used for receiving Captured frames.
271 sink_filter_ = new SinkFilter(this); 271 sink_filter_ = new SinkFilter(this);
272 if (sink_filter_.get() == NULL) { 272 if (sink_filter_.get() == NULL) {
273 DLOG(ERROR) << "Failed to create sink filter"; 273 DLOG(ERROR) << "Failed to create sink filter";
274 return false; 274 return false;
275 } 275 }
276 276
277 input_sink_pin_ = sink_filter_->GetPin(0); 277 input_sink_pin_ = sink_filter_->GetPin(0);
278 278
279 hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL, 279 hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL,
280 CLSCTX_INPROC_SERVER); 280 CLSCTX_INPROC_SERVER);
281 DLOG_IF_FAILED_WITH_HRESULT("Failed to create capture filter", hr); 281 DLOG_IF_FAILED_WITH_HRESULT("Failed to create capture filter", hr);
282 if (FAILED(hr)) 282 if (FAILED(hr))
283 return false; 283 return false;
284 284
285 hr = capture_graph_builder_.CreateInstance(CLSID_CaptureGraphBuilder2, NULL, 285 hr = capture_graph_builder_.CreateInstance(CLSID_CaptureGraphBuilder2, NULL,
286 CLSCTX_INPROC); 286 CLSCTX_INPROC);
287 DLOG_IF_FAILED_WITH_HRESULT("Failed to create the Capture Graph Builder", hr); 287 DLOG_IF_FAILED_WITH_HRESULT("Failed to create the Capture Graph Builder", hr);
288 if (FAILED(hr)) 288 if (FAILED(hr))
289 return false; 289 return false;
290 290
291 hr = capture_graph_builder_->SetFiltergraph(graph_builder_.get()); 291 hr = capture_graph_builder_->SetFiltergraph(graph_builder_.Get());
292 DLOG_IF_FAILED_WITH_HRESULT("Failed to give graph to capture graph builder", 292 DLOG_IF_FAILED_WITH_HRESULT("Failed to give graph to capture graph builder",
293 hr); 293 hr);
294 if (FAILED(hr)) 294 if (FAILED(hr))
295 return false; 295 return false;
296 296
297 hr = graph_builder_.QueryInterface(media_control_.Receive()); 297 hr = graph_builder_.QueryInterface(media_control_.Receive());
298 DLOG_IF_FAILED_WITH_HRESULT("Failed to create media control builder", hr); 298 DLOG_IF_FAILED_WITH_HRESULT("Failed to create media control builder", hr);
299 if (FAILED(hr)) 299 if (FAILED(hr))
300 return false; 300 return false;
301 301
302 hr = graph_builder_->AddFilter(capture_filter_.get(), NULL); 302 hr = graph_builder_->AddFilter(capture_filter_.Get(), NULL);
303 DLOG_IF_FAILED_WITH_HRESULT("Failed to add the capture device to the graph", 303 DLOG_IF_FAILED_WITH_HRESULT("Failed to add the capture device to the graph",
304 hr); 304 hr);
305 if (FAILED(hr)) 305 if (FAILED(hr))
306 return false; 306 return false;
307 307
308 hr = graph_builder_->AddFilter(sink_filter_.get(), NULL); 308 hr = graph_builder_->AddFilter(sink_filter_.get(), NULL);
309 DLOG_IF_FAILED_WITH_HRESULT("Failed to add the sink filter to the graph", hr); 309 DLOG_IF_FAILED_WITH_HRESULT("Failed to add the sink filter to the graph", hr);
310 if (FAILED(hr)) 310 if (FAILED(hr))
311 return false; 311 return false;
312 312
313 // The following code builds the upstream portions of the graph, for example 313 // The following code builds the upstream portions of the graph, for example
314 // if a capture device uses a Windows Driver Model (WDM) driver, the graph may 314 // if a capture device uses a Windows Driver Model (WDM) driver, the graph may
315 // require certain filters upstream from the WDM Video Capture filter, such as 315 // require certain filters upstream from the WDM Video Capture filter, such as
316 // a TV Tuner filter or an Analog Video Crossbar filter. We try using the more 316 // a TV Tuner filter or an Analog Video Crossbar filter. We try using the more
317 // prevalent MEDIATYPE_Interleaved first. 317 // prevalent MEDIATYPE_Interleaved first.
318 base::win::ScopedComPtr<IAMStreamConfig> stream_config; 318 base::win::ScopedComPtr<IAMStreamConfig> stream_config;
319 319
320 hr = capture_graph_builder_->FindInterface( 320 hr = capture_graph_builder_->FindInterface(
321 &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, capture_filter_.get(), 321 &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, capture_filter_.Get(),
322 IID_IAMStreamConfig, (void**)stream_config.Receive()); 322 IID_IAMStreamConfig, (void**)stream_config.Receive());
323 if (FAILED(hr)) { 323 if (FAILED(hr)) {
324 hr = capture_graph_builder_->FindInterface( 324 hr = capture_graph_builder_->FindInterface(
325 &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, capture_filter_.get(), 325 &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, capture_filter_.Get(),
326 IID_IAMStreamConfig, (void**)stream_config.Receive()); 326 IID_IAMStreamConfig, (void**)stream_config.Receive());
327 DLOG_IF_FAILED_WITH_HRESULT("Failed to find CapFilter:IAMStreamConfig", hr); 327 DLOG_IF_FAILED_WITH_HRESULT("Failed to find CapFilter:IAMStreamConfig", hr);
328 } 328 }
329 329
330 return CreateCapabilityMap(); 330 return CreateCapabilityMap();
331 } 331 }
332 332
333 void VideoCaptureDeviceWin::AllocateAndStart( 333 void VideoCaptureDeviceWin::AllocateAndStart(
334 const VideoCaptureParams& params, 334 const VideoCaptureParams& params,
335 std::unique_ptr<VideoCaptureDevice::Client> client) { 335 std::unique_ptr<VideoCaptureDevice::Client> client) {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
390 if (FAILED(hr)) { 390 if (FAILED(hr)) {
391 SetErrorState(FROM_HERE, "Failed to set capture device output format", hr); 391 SetErrorState(FROM_HERE, "Failed to set capture device output format", hr);
392 return; 392 return;
393 } 393 }
394 394
395 SetAntiFlickerInCaptureFilter(params); 395 SetAntiFlickerInCaptureFilter(params);
396 396
397 if (media_type->subtype == kMediaSubTypeHDYC) { 397 if (media_type->subtype == kMediaSubTypeHDYC) {
398 // HDYC pixel format, used by the DeckLink capture card, needs an AVI 398 // HDYC pixel format, used by the DeckLink capture card, needs an AVI
399 // decompressor filter after source, let |graph_builder_| add it. 399 // decompressor filter after source, let |graph_builder_| add it.
400 hr = graph_builder_->Connect(output_capture_pin_.get(), 400 hr = graph_builder_->Connect(output_capture_pin_.Get(),
401 input_sink_pin_.get()); 401 input_sink_pin_.Get());
402 } else { 402 } else {
403 hr = graph_builder_->ConnectDirect(output_capture_pin_.get(), 403 hr = graph_builder_->ConnectDirect(output_capture_pin_.Get(),
404 input_sink_pin_.get(), NULL); 404 input_sink_pin_.Get(), NULL);
405 } 405 }
406 406
407 if (FAILED(hr)) { 407 if (FAILED(hr)) {
408 SetErrorState(FROM_HERE, "Failed to connect the Capture graph.", hr); 408 SetErrorState(FROM_HERE, "Failed to connect the Capture graph.", hr);
409 return; 409 return;
410 } 410 }
411 411
412 hr = media_control_->Pause(); 412 hr = media_control_->Pause();
413 if (FAILED(hr)) { 413 if (FAILED(hr)) {
414 SetErrorState(FROM_HERE, "Failed to pause the Capture device", hr); 414 SetErrorState(FROM_HERE, "Failed to pause the Capture device", hr);
(...skipping 15 matching lines...) Expand all
430 DCHECK(thread_checker_.CalledOnValidThread()); 430 DCHECK(thread_checker_.CalledOnValidThread());
431 if (state_ != kCapturing) 431 if (state_ != kCapturing)
432 return; 432 return;
433 433
434 HRESULT hr = media_control_->Stop(); 434 HRESULT hr = media_control_->Stop();
435 if (FAILED(hr)) { 435 if (FAILED(hr)) {
436 SetErrorState(FROM_HERE, "Failed to stop the capture graph.", hr); 436 SetErrorState(FROM_HERE, "Failed to stop the capture graph.", hr);
437 return; 437 return;
438 } 438 }
439 439
440 graph_builder_->Disconnect(output_capture_pin_.get()); 440 graph_builder_->Disconnect(output_capture_pin_.Get());
441 graph_builder_->Disconnect(input_sink_pin_.get()); 441 graph_builder_->Disconnect(input_sink_pin_.Get());
442 442
443 client_.reset(); 443 client_.reset();
444 state_ = kIdle; 444 state_ = kIdle;
445 } 445 }
446 446
447 void VideoCaptureDeviceWin::TakePhoto(TakePhotoCallback callback) { 447 void VideoCaptureDeviceWin::TakePhoto(TakePhotoCallback callback) {
448 DCHECK(thread_checker_.CalledOnValidThread()); 448 DCHECK(thread_checker_.CalledOnValidThread());
449 // DirectShow has other means of capturing still pictures, e.g. connecting a 449 // DirectShow has other means of capturing still pictures, e.g. connecting a
450 // SampleGrabber filter to a PIN_CATEGORY_STILL of |capture_filter_|. This 450 // SampleGrabber filter to a PIN_CATEGORY_STILL of |capture_filter_|. This
451 // way, however, is not widespread and proves too cumbersome, so we just grab 451 // way, however, is not widespread and proves too cumbersome, so we just grab
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
518 if (format.pixel_format == PIXEL_FORMAT_UNKNOWN) 518 if (format.pixel_format == PIXEL_FORMAT_UNKNOWN)
519 continue; 519 continue;
520 520
521 VIDEOINFOHEADER* h = 521 VIDEOINFOHEADER* h =
522 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat); 522 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
523 format.frame_size.SetSize(h->bmiHeader.biWidth, h->bmiHeader.biHeight); 523 format.frame_size.SetSize(h->bmiHeader.biWidth, h->bmiHeader.biHeight);
524 524
525 // Try to get a better |time_per_frame| from IAMVideoControl. If not, use 525 // Try to get a better |time_per_frame| from IAMVideoControl. If not, use
526 // the value from VIDEOINFOHEADER. 526 // the value from VIDEOINFOHEADER.
527 REFERENCE_TIME time_per_frame = h->AvgTimePerFrame; 527 REFERENCE_TIME time_per_frame = h->AvgTimePerFrame;
528 if (video_control.get()) { 528 if (video_control.Get()) {
529 ScopedCoMem<LONGLONG> max_fps; 529 ScopedCoMem<LONGLONG> max_fps;
530 LONG list_size = 0; 530 LONG list_size = 0;
531 const SIZE size = {format.frame_size.width(), 531 const SIZE size = {format.frame_size.width(),
532 format.frame_size.height()}; 532 format.frame_size.height()};
533 hr = video_control->GetFrameRateList(output_capture_pin_.get(), 533 hr = video_control->GetFrameRateList(output_capture_pin_.Get(),
534 stream_index, size, &list_size, 534 stream_index, size, &list_size,
535 &max_fps); 535 &max_fps);
536 // Can't assume the first value will return the max fps. 536 // Can't assume the first value will return the max fps.
537 // Sometimes |list_size| will be > 0, but max_fps will be NULL. Some 537 // Sometimes |list_size| will be > 0, but max_fps will be NULL. Some
538 // drivers may return an HRESULT of S_FALSE which SUCCEEDED() translates 538 // drivers may return an HRESULT of S_FALSE which SUCCEEDED() translates
539 // into success, so explicitly check S_OK. See http://crbug.com/306237. 539 // into success, so explicitly check S_OK. See http://crbug.com/306237.
540 if (hr == S_OK && list_size > 0 && max_fps) { 540 if (hr == S_OK && list_size > 0 && max_fps) {
541 time_per_frame = 541 time_per_frame =
542 *std::min_element(max_fps.get(), max_fps.get() + list_size); 542 *std::min_element(max_fps.get(), max_fps.get() + list_size);
543 } 543 }
(...skipping 15 matching lines...) Expand all
559 void VideoCaptureDeviceWin::SetAntiFlickerInCaptureFilter( 559 void VideoCaptureDeviceWin::SetAntiFlickerInCaptureFilter(
560 const VideoCaptureParams& params) { 560 const VideoCaptureParams& params) {
561 const PowerLineFrequency power_line_frequency = GetPowerLineFrequency(params); 561 const PowerLineFrequency power_line_frequency = GetPowerLineFrequency(params);
562 if (power_line_frequency != media::PowerLineFrequency::FREQUENCY_50HZ && 562 if (power_line_frequency != media::PowerLineFrequency::FREQUENCY_50HZ &&
563 power_line_frequency != media::PowerLineFrequency::FREQUENCY_60HZ) { 563 power_line_frequency != media::PowerLineFrequency::FREQUENCY_60HZ) {
564 return; 564 return;
565 } 565 }
566 ScopedComPtr<IKsPropertySet> ks_propset; 566 ScopedComPtr<IKsPropertySet> ks_propset;
567 DWORD type_support = 0; 567 DWORD type_support = 0;
568 HRESULT hr; 568 HRESULT hr;
569 if (SUCCEEDED(hr = ks_propset.QueryFrom(capture_filter_.get())) && 569 if (SUCCEEDED(hr = ks_propset.QueryFrom(capture_filter_.Get())) &&
570 SUCCEEDED(hr = ks_propset->QuerySupported( 570 SUCCEEDED(hr = ks_propset->QuerySupported(
571 PROPSETID_VIDCAP_VIDEOPROCAMP, 571 PROPSETID_VIDCAP_VIDEOPROCAMP,
572 KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY, 572 KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY,
573 &type_support)) && 573 &type_support)) &&
574 (type_support & KSPROPERTY_SUPPORT_SET)) { 574 (type_support & KSPROPERTY_SUPPORT_SET)) {
575 KSPROPERTY_VIDEOPROCAMP_S data = {}; 575 KSPROPERTY_VIDEOPROCAMP_S data = {};
576 data.Property.Set = PROPSETID_VIDCAP_VIDEOPROCAMP; 576 data.Property.Set = PROPSETID_VIDCAP_VIDEOPROCAMP;
577 data.Property.Id = KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY; 577 data.Property.Id = KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY;
578 data.Property.Flags = KSPROPERTY_TYPE_SET; 578 data.Property.Flags = KSPROPERTY_TYPE_SET;
579 data.Value = 579 data.Value =
(...skipping 10 matching lines...) Expand all
590 void VideoCaptureDeviceWin::SetErrorState( 590 void VideoCaptureDeviceWin::SetErrorState(
591 const tracked_objects::Location& from_here, 591 const tracked_objects::Location& from_here,
592 const std::string& reason, 592 const std::string& reason,
593 HRESULT hr) { 593 HRESULT hr) {
594 DCHECK(thread_checker_.CalledOnValidThread()); 594 DCHECK(thread_checker_.CalledOnValidThread());
595 DLOG_IF_FAILED_WITH_HRESULT(reason, hr); 595 DLOG_IF_FAILED_WITH_HRESULT(reason, hr);
596 state_ = kError; 596 state_ = kError;
597 client_->OnError(from_here, reason); 597 client_->OnError(from_here, reason);
598 } 598 }
599 } // namespace media 599 } // namespace media
OLDNEW
« no previous file with comments | « media/capture/video/win/video_capture_device_mf_win.cc ('k') | media/gpu/d3d11_h264_accelerator.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698