Merge branch '1.0' into 1.0-multiple-selection
[dcpomatic.git] / src / lib / player.cc
1 /*
2     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 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 General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <stdint.h>
21 #include "player.h"
22 #include "film.h"
23 #include "ffmpeg_decoder.h"
24 #include "ffmpeg_content.h"
25 #include "still_image_decoder.h"
26 #include "still_image_content.h"
27 #include "moving_image_decoder.h"
28 #include "moving_image_content.h"
29 #include "sndfile_decoder.h"
30 #include "sndfile_content.h"
31 #include "subtitle_content.h"
32 #include "playlist.h"
33 #include "job.h"
34 #include "image.h"
35 #include "ratio.h"
36 #include "resampler.h"
37 #include "log.h"
38 #include "scaler.h"
39
40 using std::list;
41 using std::cout;
42 using std::min;
43 using std::max;
44 using std::vector;
45 using std::pair;
46 using std::map;
47 using boost::shared_ptr;
48 using boost::weak_ptr;
49 using boost::dynamic_pointer_cast;
50
51 class Piece
52 {
53 public:
54         Piece (shared_ptr<Content> c)
55                 : content (c)
56                 , video_position (c->position ())
57                 , audio_position (c->position ())
58                 , repeat_to_do (0)
59                 , repeat_done (0)
60         {}
61         
62         Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
63                 : content (c)
64                 , decoder (d)
65                 , video_position (c->position ())
66                 , audio_position (c->position ())
67         {}
68
69         /** Set this piece to repeat a video frame a given number of times */
70         void set_repeat (IncomingVideo video, int num)
71         {
72                 repeat_video = video;
73                 repeat_to_do = num;
74                 repeat_done = 0;
75         }
76
77         void reset_repeat ()
78         {
79                 repeat_video.image.reset ();
80                 repeat_to_do = 0;
81                 repeat_done = 0;
82         }
83
84         bool repeating () const
85         {
86                 return repeat_done != repeat_to_do;
87         }
88
89         void repeat (Player* player)
90         {
91                 player->process_video (
92                         repeat_video.weak_piece,
93                         repeat_video.image,
94                         repeat_video.eyes,
95                         repeat_done > 0,
96                         repeat_video.frame,
97                         (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
98                         );
99
100                 ++repeat_done;
101         }
102         
103         shared_ptr<Content> content;
104         shared_ptr<Decoder> decoder;
105         /** Time of the last video we emitted relative to the start of the DCP */
106         Time video_position;
107         /** Time of the last audio we emitted relative to the start of the DCP */
108         Time audio_position;
109
110         IncomingVideo repeat_video;
111         int repeat_to_do;
112         int repeat_done;
113 };
114
115 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
116         : _film (f)
117         , _playlist (p)
118         , _video (true)
119         , _audio (true)
120         , _have_valid_pieces (false)
121         , _video_position (0)
122         , _audio_position (0)
123         , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
124         , _last_emit_was_black (false)
125 {
126         _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
127         _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
128         _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
129         set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ()));
130 }
131
132 void
133 Player::disable_video ()
134 {
135         _video = false;
136 }
137
138 void
139 Player::disable_audio ()
140 {
141         _audio = false;
142 }
143
144 bool
145 Player::pass ()
146 {
147         if (!_have_valid_pieces) {
148                 setup_pieces ();
149                 _have_valid_pieces = true;
150         }
151
152         Time earliest_t = TIME_MAX;
153         shared_ptr<Piece> earliest;
154         enum {
155                 VIDEO,
156                 AUDIO
157         } type = VIDEO;
158
159         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
160                 if ((*i)->decoder->done ()) {
161                         continue;
162                 }
163
164                 shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
165                 shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
166
167                 if (_video && vd) {
168                         if ((*i)->video_position < earliest_t) {
169                                 earliest_t = (*i)->video_position;
170                                 earliest = *i;
171                                 type = VIDEO;
172                         }
173                 }
174
175                 if (_audio && ad && ad->has_audio ()) {
176                         if ((*i)->audio_position < earliest_t) {
177                                 earliest_t = (*i)->audio_position;
178                                 earliest = *i;
179                                 type = AUDIO;
180                         }
181                 }
182         }
183
184         if (!earliest) {
185                 flush ();
186                 return true;
187         }
188
189         switch (type) {
190         case VIDEO:
191                 if (earliest_t > _video_position) {
192                         emit_black ();
193                 } else {
194                         if (earliest->repeating ()) {
195                                 earliest->repeat (this);
196                         } else {
197                                 earliest->decoder->pass ();
198                         }
199                 }
200                 break;
201
202         case AUDIO:
203                 if (earliest_t > _audio_position) {
204                         emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
205                 } else {
206                         earliest->decoder->pass ();
207
208                         if (earliest->decoder->done()) {
209                                 shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
210                                 assert (ac);
211                                 shared_ptr<Resampler> re = resampler (ac, false);
212                                 if (re) {
213                                         shared_ptr<const AudioBuffers> b = re->flush ();
214                                         if (b->frames ()) {
215                                                 process_audio (earliest, b, ac->audio_length ());
216                                         }
217                                 }
218                         }
219                 }
220                 break;
221         }
222
223         if (_audio) {
224                 boost::optional<Time> audio_done_up_to;
225                 for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
226                         if ((*i)->decoder->done ()) {
227                                 continue;
228                         }
229
230                         if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
231                                 audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
232                         }
233                 }
234
235                 if (audio_done_up_to) {
236                         TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
237                         Audio (tb.audio, tb.time);
238                         _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
239                 }
240         }
241                 
242         return false;
243 }
244
245 /** @param extra Amount of extra time to add to the content frame's time (for repeat) */
246 void
247 Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
248 {
249         /* Keep a note of what came in so that we can repeat it if required */
250         _last_incoming_video.weak_piece = weak_piece;
251         _last_incoming_video.image = image;
252         _last_incoming_video.eyes = eyes;
253         _last_incoming_video.same = same;
254         _last_incoming_video.frame = frame;
255         _last_incoming_video.extra = extra;
256         
257         shared_ptr<Piece> piece = weak_piece.lock ();
258         if (!piece) {
259                 return;
260         }
261
262         shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
263         assert (content);
264
265         FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
266         if (frc.skip && (frame % 2) == 1) {
267                 return;
268         }
269
270         Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
271         if (content->trimmed (relative_time)) {
272                 return;
273         }
274
275         /* Convert to RGB first, as FFmpeg doesn't seem to like handling YUV images with odd widths */
276         shared_ptr<Image> work_image = image->scale (image->size (), _film->scaler(), PIX_FMT_RGB24, true);
277
278         work_image = work_image->crop (content->crop(), true);
279
280         float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
281         libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
282         
283         work_image = work_image->scale (image_size, _film->scaler(), PIX_FMT_RGB24, true);
284
285         Time time = content->position() + relative_time + extra - content->trim_start ();
286             
287         if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
288                 work_image->alpha_blend (_out_subtitle.image, _out_subtitle.position);
289         }
290
291         if (image_size != _video_container_size) {
292                 assert (image_size.width <= _video_container_size.width);
293                 assert (image_size.height <= _video_container_size.height);
294                 shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
295                 im->make_black ();
296                 im->copy (work_image, Position<int> ((_video_container_size.width - image_size.width) / 2, (_video_container_size.height - image_size.height) / 2));
297                 work_image = im;
298         }
299
300 #ifdef DCPOMATIC_DEBUG
301         _last_video = piece->content;
302 #endif
303
304         Video (work_image, eyes, content->colour_conversion(), same, time);
305
306         time += TIME_HZ / _film->video_frame_rate();
307         _last_emit_was_black = false;
308         _video_position = piece->video_position = time;
309
310         if (frc.repeat > 1 && !piece->repeating ()) {
311                 piece->set_repeat (_last_incoming_video, frc.repeat - 1);
312         }
313 }
314
315 void
316 Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
317 {
318         shared_ptr<Piece> piece = weak_piece.lock ();
319         if (!piece) {
320                 return;
321         }
322
323         shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
324         assert (content);
325
326         /* Gain */
327         if (content->audio_gain() != 0) {
328                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
329                 gain->apply_gain (content->audio_gain ());
330                 audio = gain;
331         }
332
333         /* Resample */
334         if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
335                 shared_ptr<Resampler> r = resampler (content, true);
336                 pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
337                 audio = ro.first;
338                 frame = ro.second;
339         }
340         
341         Time const relative_time = _film->audio_frames_to_time (frame);
342
343         if (content->trimmed (relative_time)) {
344                 return;
345         }
346
347         Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
348         
349         /* Remap channels */
350         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
351         dcp_mapped->make_silent ();
352         list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
353         for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
354                 if (i->first < audio->channels() && i->second < dcp_mapped->channels()) {
355                         dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
356                 }
357         }
358
359         audio = dcp_mapped;
360
361         /* We must cut off anything that comes before the start of all time */
362         if (time < 0) {
363                 int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
364                 if (frames >= audio->frames ()) {
365                         return;
366                 }
367
368                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
369                 trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
370
371                 audio = trimmed;
372                 time = 0;
373         }
374
375         _audio_merger.push (audio, time);
376         piece->audio_position += _film->audio_frames_to_time (audio->frames ());
377 }
378
379 void
380 Player::flush ()
381 {
382         TimedAudioBuffers<Time> tb = _audio_merger.flush ();
383         if (tb.audio) {
384                 Audio (tb.audio, tb.time);
385                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
386         }
387
388         while (_video_position < _audio_position) {
389                 emit_black ();
390         }
391
392         while (_audio_position < _video_position) {
393                 emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
394         }
395         
396 }
397
398 /** Seek so that the next pass() will yield (approximately) the requested frame.
399  *  Pass accurate = true to try harder to get close to the request.
400  *  @return true on error
401  */
402 void
403 Player::seek (Time t, bool accurate)
404 {
405         if (!_have_valid_pieces) {
406                 setup_pieces ();
407                 _have_valid_pieces = true;
408         }
409
410         if (_pieces.empty ()) {
411                 return;
412         }
413
414         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
415                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
416                 if (!vc) {
417                         continue;
418                 }
419
420                 /* s is the offset of t from the start position of this content */
421                 Time s = t - vc->position ();
422                 s = max (static_cast<Time> (0), s);
423                 s = min (vc->length_after_trim(), s);
424
425                 /* Hence set the piece positions to the `global' time */
426                 (*i)->video_position = (*i)->audio_position = vc->position() + s;
427
428                 /* And seek the decoder */
429                 dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
430                         vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
431                         );
432
433                 (*i)->reset_repeat ();
434         }
435
436         _video_position = _audio_position = t;
437
438         /* XXX: don't seek audio because we don't need to... */
439 }
440
441 void
442 Player::setup_pieces ()
443 {
444         list<shared_ptr<Piece> > old_pieces = _pieces;
445
446         _pieces.clear ();
447
448         ContentList content = _playlist->content ();
449         sort (content.begin(), content.end(), ContentSorter ());
450
451         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
452
453                 shared_ptr<Piece> piece (new Piece (*i));
454
455                 /* XXX: into content? */
456
457                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
458                 if (fc) {
459                         shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
460                         
461                         fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
462                         fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
463                         fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
464
465                         fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
466                         piece->decoder = fd;
467                 }
468                 
469                 shared_ptr<const StillImageContent> ic = dynamic_pointer_cast<const StillImageContent> (*i);
470                 if (ic) {
471                         shared_ptr<StillImageDecoder> id;
472                         
473                         /* See if we can re-use an old StillImageDecoder */
474                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
475                                 shared_ptr<StillImageDecoder> imd = dynamic_pointer_cast<StillImageDecoder> ((*j)->decoder);
476                                 if (imd && imd->content() == ic) {
477                                         id = imd;
478                                 }
479                         }
480
481                         if (!id) {
482                                 id.reset (new StillImageDecoder (_film, ic));
483                                 id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
484                         }
485
486                         piece->decoder = id;
487                 }
488
489                 shared_ptr<const MovingImageContent> mc = dynamic_pointer_cast<const MovingImageContent> (*i);
490                 if (mc) {
491                         shared_ptr<MovingImageDecoder> md;
492
493                         if (!md) {
494                                 md.reset (new MovingImageDecoder (_film, mc));
495                                 md->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
496                         }
497
498                         piece->decoder = md;
499                 }
500
501                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
502                 if (sc) {
503                         shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
504                         sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
505
506                         piece->decoder = sd;
507                 }
508
509                 _pieces.push_back (piece);
510         }
511 }
512
513 void
514 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
515 {
516         shared_ptr<Content> c = w.lock ();
517         if (!c) {
518                 return;
519         }
520
521         if (
522                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
523                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END
524                 ) {
525                 
526                 _have_valid_pieces = false;
527                 Changed (frequent);
528
529         } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
530
531                 update_subtitle ();
532                 Changed (frequent);
533
534         } else if (
535                 property == VideoContentProperty::VIDEO_FRAME_TYPE || property == VideoContentProperty::VIDEO_CROP ||
536                 property == VideoContentProperty::VIDEO_RATIO
537                 ) {
538                 
539                 Changed (frequent);
540
541         } else if (property == ContentProperty::PATH) {
542
543                 Changed (frequent);
544         }
545 }
546
547 void
548 Player::playlist_changed ()
549 {
550         _have_valid_pieces = false;
551         Changed (false);
552 }
553
554 void
555 Player::set_video_container_size (libdcp::Size s)
556 {
557         _video_container_size = s;
558         _black_frame.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
559         _black_frame->make_black ();
560 }
561
562 shared_ptr<Resampler>
563 Player::resampler (shared_ptr<AudioContent> c, bool create)
564 {
565         map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
566         if (i != _resamplers.end ()) {
567                 return i->second;
568         }
569
570         if (!create) {
571                 return shared_ptr<Resampler> ();
572         }
573
574         _film->log()->log (
575                 String::compose (
576                         "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
577                         )
578                 );
579         
580         shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
581         _resamplers[c] = r;
582         return r;
583 }
584
585 void
586 Player::emit_black ()
587 {
588 #ifdef DCPOMATIC_DEBUG
589         _last_video.reset ();
590 #endif
591
592         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
593         _video_position += _film->video_frames_to_time (1);
594         _last_emit_was_black = true;
595 }
596
597 void
598 Player::emit_silence (OutputAudioFrame most)
599 {
600         if (most == 0) {
601                 return;
602         }
603         
604         OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
605         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
606         silence->make_silent ();
607         Audio (silence, _audio_position);
608         _audio_position += _film->audio_frames_to_time (N);
609 }
610
611 void
612 Player::film_changed (Film::Property p)
613 {
614         /* Here we should notice Film properties that affect our output, and
615            alert listeners that our output now would be different to how it was
616            last time we were run.
617         */
618
619         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER) {
620                 Changed (false);
621         }
622 }
623
624 void
625 Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
626 {
627         _in_subtitle.piece = weak_piece;
628         _in_subtitle.image = image;
629         _in_subtitle.rect = rect;
630         _in_subtitle.from = from;
631         _in_subtitle.to = to;
632
633         update_subtitle ();
634 }
635
636 void
637 Player::update_subtitle ()
638 {
639         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
640         if (!piece) {
641                 return;
642         }
643
644         if (!_in_subtitle.image) {
645                 _out_subtitle.image.reset ();
646                 return;
647         }
648
649         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
650         assert (sc);
651
652         dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
653         libdcp::Size scaled_size;
654
655         in_rect.y += sc->subtitle_offset ();
656
657         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
658         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
659         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
660
661         /* Then we need a corrective translation, consisting of two parts:
662          *
663          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
664          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
665          *
666          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
667          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
668          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
669          *
670          * Combining these two translations gives these expressions.
671          */
672         
673         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
674         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
675         
676         _out_subtitle.image = _in_subtitle.image->scale (
677                 scaled_size,
678                 Scaler::from_id ("bicubic"),
679                 _in_subtitle.image->pixel_format (),
680                 true
681                 );
682         _out_subtitle.from = _in_subtitle.from + piece->content->position ();
683         _out_subtitle.to = _in_subtitle.to + piece->content->position ();
684 }
685
686 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
687  *  @return false if this could not be done.
688  */
689 bool
690 Player::repeat_last_video ()
691 {
692         if (!_last_incoming_video.image) {
693                 return false;
694         }
695
696         process_video (
697                 _last_incoming_video.weak_piece,
698                 _last_incoming_video.image,
699                 _last_incoming_video.eyes,
700                 _last_incoming_video.same,
701                 _last_incoming_video.frame,
702                 _last_incoming_video.extra
703                 );
704
705         return true;
706 }