undef SearchPath where needed as it is defined via windows.h
[ardour.git] / gtk2_ardour / transcode_ffmpeg.cc
1 /*
2     Copyright (C) 2010-2013 Paul Davis
3     Author: Robin Gareus <robin@gareus.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20 #include <stdio.h>
21 #include <string.h>
22 #include <sstream>
23 #include <sys/types.h>
24
25 #include "pbd/error.h"
26 #include "pbd/convert.h"
27 #include "pbd/file_utils.h"
28 #include "gui_thread.h"
29
30 #include "transcode_ffmpeg.h"
31 #include "utils_videotl.h"
32
33 #include "i18n.h"
34
35 #ifdef SearchPath
36 #undef SearchPath
37 #endif
38
39 using namespace PBD;
40
41 TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
42         : infile(f)
43 {
44         probeok = false;
45         ffexecok = false;
46         ffmpeg_exe = "";
47         ffprobe_exe = "";
48         m_duration = 0;
49         m_avoffset = m_lead_in = m_lead_out = 0;
50         m_width = m_height = 0;
51         m_aspect = m_fps = 0;
52 #if 1 /* tentative debug mode */
53         debug_enable = false;
54 #endif
55
56         std::string ff_file_path;
57         if (find_file_in_search_path (SearchPath(Glib::getenv("PATH")), X_("ffmpeg_harvid"), ff_file_path)) { ffmpeg_exe = ff_file_path; }
58         else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
59                 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
60         }
61         else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
62                 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
63         }
64
65         if (find_file_in_search_path (SearchPath(Glib::getenv("PATH")), X_("ffprobe_harvid"), ff_file_path)) { ffprobe_exe = ff_file_path; }
66         else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
67                 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
68         }
69         else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
70                 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
71         }
72
73         if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
74                 warning << _(
75                                 "No ffprobe or ffmpeg executables could be found on this system.\n"
76                                 "Video import and export is not possible until you install those tools.\n"
77                                 "Ardour requires ffmpeg and ffprobe from ffmpeg.org - version 1.1 or newer.\n"
78                                 "\n"
79                                 "The tools are included with the Ardour releases from ardour.org "
80                                 "and also available with the video-server at http://x42.github.com/harvid/\n"
81                                 "\n"
82                                 "Important: the files need to be installed in $PATH and named ffmpeg_harvid and ffprobe_harvid.\n"
83                                 "If you already have a suitable ffmpeg installation on your system, we recommend creating "
84                                 "symbolic links from ffmpeg to ffmpeg_harvid and from ffprobe to ffprobe_harvid.\n"
85                                 ) << endmsg;
86                 return;
87         }
88         ffexecok = true;
89
90         if (infile.empty() || !probe()) {
91                 return;
92         }
93         probeok = true;
94 }
95
96 TranscodeFfmpeg::~TranscodeFfmpeg ()
97 {
98   ;
99 }
100
101 bool
102 TranscodeFfmpeg::probe ()
103 {
104         ffoutput = "";
105         char **argp;
106         argp=(char**) calloc(7,sizeof(char*));
107         argp[0] = strdup(ffprobe_exe.c_str());
108         argp[1] = strdup("-print_format");
109         argp[2] = strdup("csv=nk=0");
110         argp[3] = strdup("-show_format");
111         argp[4] = strdup("-show_streams");
112         argp[5] = strdup(infile.c_str());
113         argp[6] = 0;
114         ffcmd = new SystemExec(ffprobe_exe, argp);
115         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffprobeparse, this, _1 ,_2));
116         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
117         if (ffcmd->start(1)) {
118                 ffexit();
119                 return false;
120         }
121
122         /* wait for ffprobe process to exit */
123         ffcmd->wait();
124
125         /* wait for interposer thread to copy all data.
126          * SystemExec::Terminated is emitted and ffcmd set to NULL */
127         int timeout = 300; // 1.5 sec
128         while (ffcmd && --timeout > 0) {
129                 usleep(5000);
130         }
131         if (timeout == 0 || ffoutput.empty()) {
132                 return false;
133         }
134
135         /* parse */
136
137         std::vector<std::vector<std::string> > lines;
138         ParseCSV(ffoutput, lines);
139         double timebase = 0;
140         m_width = m_height = 0;
141         m_fps = m_aspect = 0;
142         m_duration = 0;
143         m_codec.clear();
144         m_audio.clear();
145
146 #define PARSE_FRACTIONAL_FPS(VAR) \
147         { \
148                 std::string::size_type pos; \
149                 VAR = atof(value); \
150                 pos = value.find_first_of('/'); \
151                 if (pos != std::string::npos) { \
152                         VAR = atof(value.substr(0, pos)) / atof(value.substr(pos+1)); \
153                 } \
154         }
155
156         for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
157                 if (i->at(0) == X_("format")) {
158                         /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
159                 } else
160                 if (i->at(0) == X_("stream")) {
161                         if (i->at(5) == X_("codec_type=video") && m_width == 0) {
162
163                                 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
164                                         const size_t kvsep = kv->find('=');
165                                         if(kvsep == std::string::npos) continue;
166                                         std::string key = kv->substr(0, kvsep);
167                                         std::string value = kv->substr(kvsep + 1);
168
169                                         if (key == X_("index")) {
170                                                 m_videoidx = atoi(value);
171                                         } else if (key == X_("width")) {
172                                                 m_width = atoi(value);
173                                         } else if (key == X_("height")) {
174                                                 m_height = atoi(value);
175                                         } else if (key == X_("codec_name")) {
176                                                 if (!m_codec.empty()) m_codec += " ";
177                                                 m_codec += value;
178                                         } else if (key == X_("codec_long_name")) {
179                                                 if (!m_codec.empty()) m_codec += " ";
180                                                 m_codec += "[" + value + "]";
181                                         } else if (key == X_("codec_tag_string")) {
182                                                 if (!m_codec.empty()) m_codec += " ";
183                                                 m_codec += "(" + value + ")";
184                                         } else if (key == X_("r_frame_rate")) {
185                                                 PARSE_FRACTIONAL_FPS(m_fps)
186                                         } else if (key == X_("avg_frame_rate") && m_fps == 0) {
187                                                 PARSE_FRACTIONAL_FPS(m_fps)
188                                         } else if (key == X_("time_base")) {
189                                                 PARSE_FRACTIONAL_FPS(timebase)
190                                         } else if (key == X_("timecode") && m_duration == 0) {
191                                                 int h,m,s; char f[7];
192                                                 if (sscanf(i->at(16).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
193                                                         m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
194                                                                         h * 3600.0
195                                                                 + m * 60.0
196                                                                 + s * 1.0
197                                                                 + atoi(f) / pow(10, strlen(f))
198                                                         ));
199                                                 }
200                                         } else if (key == X_("duration_ts") && m_fps == 0 && timebase !=0 ) {
201                                                 m_duration = atof(value) * m_fps * timebase;
202                                         } else if (key == X_("duration") && m_fps != 0 && m_duration == 0) {
203                                                 m_duration = atof(value) * m_fps;
204                                         } else if (key == X_("display_aspect_ratio")) {
205                                                 std::string::size_type pos;
206                                                 pos = value.find_first_of(':');
207                                                 if (pos != std::string::npos && atof(value.substr(pos+1)) != 0) {
208                                                         m_aspect = atof(value.substr(0, pos)) / atof(value.substr(pos+1));
209                                                 }
210                                         }
211                                 }
212
213                                 if (m_aspect == 0) {
214                                         m_aspect = (double)m_width / (double)m_height;
215                                 }
216
217                         } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
218                                 FFAudioStream as;
219                                 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
220                                         const size_t kvsep = kv->find('=');
221                                         if(kvsep == std::string::npos) continue;
222                                         std::string key = kv->substr(0, kvsep);
223                                         std::string value = kv->substr(kvsep + 1);
224
225                                         if (key == X_("channels")) {
226                                                 as.channels   = atoi(value);
227                                         } else if (key == X_("index")) {
228                                                 as.stream_id  = value;
229                                         } else if (key == X_("codec_long_name")) {
230                                                 if (!as.name.empty()) as.name += " ";
231                                                 as.name += value;
232                                         } else if (key == X_("codec_name")) {
233                                                 if (!as.name.empty()) as.name += " ";
234                                                 as.name += value;
235                                         } else if (key == X_("sample_fmt")) {
236                                                 if (!as.name.empty()) as.name += " ";
237                                                 as.name += "FMT:" + value;
238                                         } else if (key == X_("sample_rate")) {
239                                                 if (!as.name.empty()) as.name += " ";
240                                                 as.name += "SR:" + value;
241                                         }
242
243                                 }
244                                 m_audio.push_back(as);
245                         }
246                 }
247         }
248         /* end parse */
249
250 #if 0 /* DEBUG */
251         printf("FPS: %f\n", m_fps);
252         printf("Duration: %lu frames\n",(unsigned long)m_duration);
253         printf("W/H: %ix%i\n",m_width, m_height);
254         printf("aspect: %f\n",m_aspect);
255         printf("codec: %s\n",m_codec.c_str());
256         if (m_audio.size() > 0) {
257                 for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
258                         printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
259                 }
260         } else {
261           printf("audio: no audio streams in file.\n");
262         }
263 #endif
264
265         return true;
266 }
267
268 TranscodeFfmpeg::FFSettings
269 TranscodeFfmpeg::default_encoder_settings ()
270 {
271         TranscodeFfmpeg::FFSettings ffs;
272         ffs.clear();
273         ffs["-vcodec"] = "mpeg4";
274         ffs["-acodec"] = "ac3";
275         ffs["-b:v"] = "5000k";
276         ffs["-b:a"] = "160k";
277         return ffs;
278 }
279
280 TranscodeFfmpeg::FFSettings
281 TranscodeFfmpeg::default_meta_data ()
282 {
283         TranscodeFfmpeg::FFSettings ffm;
284         ffm.clear();
285         ffm["comment"] = "Created with ardour";
286         return ffm;
287 }
288
289 char *
290 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
291 {
292         size_t start_pos = 0;
293         std::string v1 = value;
294         while((start_pos = v1.find_first_not_of(
295                         "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
296                         start_pos)) != std::string::npos)
297         {
298                 v1.replace(start_pos, 1, "_");
299                 start_pos += 1;
300         }
301
302         start_pos = 0;
303         while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
304                 v1.replace(start_pos, 1, "\\\"");
305                 start_pos += 2;
306         }
307
308         size_t len = key.length() + v1.length() + 4;
309         char *mds = (char*) calloc(len, sizeof(char));
310         snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
311         return mds;
312 }
313
314 bool
315 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, TranscodeFfmpeg::FFSettings ffs, TranscodeFfmpeg::FFSettings meta, bool map)
316 {
317 #define MAX_FFMPEG_ENCODER_ARGS (100)
318         char **argp;
319         int a=0;
320
321         argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
322         argp[a++] = strdup(ffmpeg_exe.c_str());
323         if (m_avoffset < 0 || m_avoffset > 0) {
324                 std::ostringstream osstream; osstream << m_avoffset;
325                 argp[a++] = strdup("-itsoffset");
326                 argp[a++] = strdup(osstream.str().c_str());
327         }
328         argp[a++] = strdup("-i");
329         argp[a++] = strdup(inf_v.c_str());
330
331         argp[a++] = strdup("-i");
332         argp[a++] = strdup(inf_a.c_str());
333
334         for(TranscodeFfmpeg::FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
335                 argp[a++] = strdup(it->first.c_str());
336                 argp[a++] = strdup(it->second.c_str());
337         }
338         for(TranscodeFfmpeg::FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
339                 argp[a++] = strdup("-metadata");
340                 argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
341         }
342         if (m_lead_in != 0 && m_lead_out != 0) {
343                 std::ostringstream osstream;
344                 argp[a++] = strdup("-vf");
345                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in << X_(" [pre]; ");
346                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << X_(" [post]; ");
347                 osstream << X_("[pre] [in] [post] concat=n=3");
348                 argp[a++] = strdup(osstream.str().c_str());
349         } else if (m_lead_in != 0) {
350                 std::ostringstream osstream;
351                 argp[a++] = strdup("-vf");
352                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in << X_(" [pre]; ");
353                 osstream << X_("[pre] [in] concat=n=2");
354                 argp[a++] = strdup(osstream.str().c_str());
355         } else if (m_lead_out != 0) {
356                 std::ostringstream osstream;
357                 argp[a++] = strdup("-vf");
358                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << X_(" [post]; ");
359                 osstream << X_("[in] [post] concat=n=2");
360                 argp[a++] = strdup(osstream.str().c_str());
361         }
362
363         if (map) {
364                 std::ostringstream osstream;
365                 argp[a++] = strdup("-map");
366                 osstream << X_("0:") << m_videoidx;
367                 argp[a++] = strdup(osstream.str().c_str());
368                 argp[a++] = strdup("-map");
369                 argp[a++] = strdup("1:0");
370         }
371
372         argp[a++] = strdup("-y");
373         argp[a++] = strdup(outfile.c_str());
374         argp[a] = (char *)0;
375         assert(a<MAX_FFMPEG_ENCODER_ARGS);
376         /* Note: these are free()d in ~SystemExec */
377 #if 1 /* DEBUG */
378         if (debug_enable) { /* tentative debug mode */
379         printf("EXPORT ENCODE:\n");
380         for (int i=0; i< a; ++i) {
381           printf("%s ", argp[i]);
382         }
383         printf("\n");
384         }
385 #endif
386
387         ffcmd = new SystemExec(ffmpeg_exe, argp);
388         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
389         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
390         if (ffcmd->start(2)) {
391                 ffexit();
392                 return false;
393         }
394         return true;
395 }
396
397 bool
398 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
399 {
400         if (!probeok) return false;
401   if (stream >= m_audio.size()) return false;
402
403         char **argp;
404         int i = 0;
405
406         argp=(char**) calloc(15,sizeof(char*));
407         argp[i++] = strdup(ffmpeg_exe.c_str());
408         argp[i++] = strdup("-i");
409         argp[i++] = strdup(infile.c_str());
410 #if 0 /* ffmpeg write original samplerate, use a3/SRC to resample */
411         argp[i++] = strdup("-ar");
412         argp[i] = (char*) calloc(7,sizeof(char)); snprintf(argp[i++], 7, "%"PRId64, samplerate);
413 #endif
414         argp[i++] = strdup("-ac");
415         argp[i] = (char*) calloc(3,sizeof(char)); snprintf(argp[i++], 3, "%i", m_audio.at(stream).channels);
416         argp[i++] = strdup("-map");
417         argp[i] = (char*) calloc(8,sizeof(char)); snprintf(argp[i++], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
418         argp[i++] = strdup("-vn");
419         argp[i++] = strdup("-acodec");
420         argp[i++] = strdup("pcm_f32le");
421         argp[i++] = strdup("-y");
422         argp[i++] = strdup(outfile.c_str());
423         argp[i++] = (char *)0;
424         /* Note: argp is free()d in ~SystemExec */
425 #if 1 /* DEBUG */
426         if (debug_enable) { /* tentative debug mode */
427         printf("EXTRACT AUDIO:\n");
428         for (int i=0; i< 14; ++i) {
429           printf("%s ", argp[i]);
430         }
431         printf("\n");
432         }
433 #endif
434
435         ffcmd = new SystemExec(ffmpeg_exe, argp);
436         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
437         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
438         if (ffcmd->start(2)) {
439                 ffexit();
440                 return false;
441         }
442         return true;
443 }
444
445
446 bool
447 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
448 {
449         if (!probeok) return false;
450
451         char **argp;
452         int bitrate = kbitps;
453         int width = outw;
454         int height = outh;
455
456         if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
457         if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
458
459         if (bitrate == 0) {
460                 const double bitperpixel = .7; /* avg quality */
461                 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
462         } else {
463                 bitrate = bitrate / 10;
464         }
465         if (bitrate < 10)  bitrate = 10;
466         if (bitrate > 1000) bitrate = 1000;
467
468         argp=(char**) calloc(16,sizeof(char*));
469         argp[0] = strdup(ffmpeg_exe.c_str());
470         argp[1] = strdup("-i");
471         argp[2] = strdup(infile.c_str());
472         argp[3] = strdup("-b:v");
473         argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
474         argp[5] = strdup("-s");
475         argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
476         argp[7] = strdup("-y");
477         argp[8] = strdup("-vcodec");
478         argp[9] = strdup("mjpeg");
479         argp[10] = strdup("-an");
480         argp[11] = strdup("-intra");
481         argp[12] = strdup("-g");
482         argp[13] = strdup("1");
483         argp[14] = strdup(outfile.c_str());
484         argp[15] = (char *)0;
485         /* Note: these are free()d in ~SystemExec */
486 #if 1 /* DEBUG */
487         if (debug_enable) { /* tentative debug mode */
488         printf("TRANSCODE VIDEO:\n");
489         for (int i=0; i< 15; ++i) {
490           printf("%s ", argp[i]);
491         }
492         printf("\n");
493         }
494 #endif
495         ffcmd = new SystemExec(ffmpeg_exe, argp);
496         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
497         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
498         if (ffcmd->start(2)) {
499                 ffexit();
500                 return false;
501         }
502         return true;
503 }
504
505 void
506 TranscodeFfmpeg::cancel ()
507 {
508         if (!ffcmd || !ffcmd->is_running()) { return;}
509         ffcmd->write_to_stdin("q");
510         sleep (1);
511         if (ffcmd) {
512           ffcmd->terminate();
513         }
514 }
515
516 void
517 TranscodeFfmpeg::ffexit ()
518 {
519         delete ffcmd;
520         ffcmd=0;
521         Finished(); /* EMIT SIGNAL */
522 }
523
524 void
525 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
526 {
527         ffoutput+=d;
528 }
529
530 void
531 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
532 {
533         const char *t;
534         int h,m,s; char f[7];
535         ARDOUR::framecnt_t p = -1;
536
537         if (!(t=strstr(d.c_str(), "time="))) { return; }
538
539         if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
540                 p = (ARDOUR::framecnt_t) floor( 100.0 * (
541                       h * 3600.0
542                     + m * 60.0
543                     + s * 1.0
544                     + atoi(f) / pow(10, strlen(f))
545                 ));
546                 p = p * m_fps / 100.0;
547                 if (p > m_duration ) { p = m_duration; }
548                 Progress(p, m_duration); /* EMIT SIGNAL */
549         } else {
550                 Progress(0, 0); /* EMIT SIGNAL */
551         }
552 }
553
554 void
555 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
556 {
557         if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
558                 warning << "ffmpeg-error: " << d << endmsg;
559         }
560         if (strncmp(d.c_str(), "frame=",6)) {
561 #if 1 /* DEBUG */
562                 if (debug_enable) {
563                         d.erase(d.find_last_not_of(" \t\r\n") + 1);
564                   printf("ffmpeg: '%s'\n", d.c_str());
565                 }
566 #endif
567                 Progress(0, 0); /* EMIT SIGNAL */
568                 return;
569         }
570         ARDOUR::framecnt_t f = atol(d.substr(6));
571         if (f == 0) {
572                 Progress(0, 0); /* EMIT SIGNAL */
573         } else {
574                 Progress(f, m_duration); /* EMIT SIGNAL */
575         }
576 }