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