1 /*
2  * Copyright (C) 2013-2014 Tim Mayberry <mojofunk@gmail.com>
3  * Copyright (C) 2013-2017 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
5  * Copyright (C) 2013 John Emmas <john@creativepost.co.uk>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 #include <stdio.h>
22 #include <string.h>
23 #include <sstream>
24 #include <sys/types.h>
25 
26 #include "pbd/error.h"
27 #include "pbd/convert.h"
28 #include "pbd/file_utils.h"
29 #include "gui_thread.h"
30 
31 #include "ardour/filesystem_paths.h"
32 
33 #include "transcode_ffmpeg.h"
34 #include "utils_videotl.h"
35 
36 #include "pbd/i18n.h"
37 
38 using namespace PBD;
39 using namespace VideoUtils;
40 
TranscodeFfmpeg(std::string f)41 TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
42 	: infile(f)
43 {
44 	probeok = false;
45 	ffexecok = false;
46 	m_duration = 0;
47 	m_avoffset = m_lead_in = m_lead_out = 0;
48 	m_width = m_height = 0;
49 	m_aspect = m_fps = 0;
50 	m_sar = "";
51 #if 1 /* tentative debug mode */
52 	debug_enable = false;
53 #endif
54 
55 	if (!ARDOUR::ArdourVideoToolPaths::transcoder_exe(ffmpeg_exe, ffprobe_exe)) {
56 		warning << string_compose(
57 				_(
58 					"ffmpeg installation was not found on this system.\n"
59 					"%1 requires ffmpeg and ffprobe from ffmpeg.org - version 1.1 or newer.\n"
60 					"Video import and export is not possible until you install tools.\n"
61 					"\n"
62 					"The tools are included with the %1 releases from ardour.org "
63 					"and also available with the video-server at http://x42.github.com/harvid/\n"
64 					"\n"
65 					"Important: the files need to be installed in $PATH and named ffmpeg_harvid and ffprobe_harvid.\n"
66 					"If you already have a suitable ffmpeg installation on your system, we recommend creating "
67 					"symbolic links from ffmpeg to ffmpeg_harvid and from ffprobe to ffprobe_harvid.\n"
68 					"\n"
69 					"see also http://manual.ardour.org/video-timeline/setup/"
70 				 ), PROGRAM_NAME) << endmsg;
71 		return;
72 	}
73 	ffexecok = true;
74 
75 	if (infile.empty() || !probe()) {
76 		return;
77 	}
78 	probeok = true;
79 }
80 
~TranscodeFfmpeg()81 TranscodeFfmpeg::~TranscodeFfmpeg ()
82 {
83   ;
84 }
85 
86 bool
probe()87 TranscodeFfmpeg::probe ()
88 {
89 	ffoutput = "";
90 	char **argp;
91 	argp=(char**) calloc(7,sizeof(char*));
92 	argp[0] = strdup(ffprobe_exe.c_str());
93 	argp[1] = strdup("-print_format");
94 	argp[2] = strdup("csv=nk=0");
95 	argp[3] = strdup("-show_format");
96 	argp[4] = strdup("-show_streams");
97 	argp[5] = strdup(infile.c_str());
98 	argp[6] = 0;
99 	ffcmd = new ARDOUR::SystemExec(ffprobe_exe, argp);
100 	ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffprobeparse, this, _1 ,_2));
101 	ffcmd->Terminated.connect (*this, invalidator (*this), boost::bind (&TranscodeFfmpeg::ffexit, this), gui_context());
102 	if (ffcmd->start (SystemExec::IgnoreAndClose)) {
103 		ffexit();
104 		return false;
105 	}
106 
107 	/* wait for ffprobe process to exit */
108 	ffcmd->wait();
109 
110 	/* wait for interposer thread to copy all data.
111 	 * SystemExec::Terminated is emitted and ffcmd set to NULL */
112 	int timeout = 300; // 1.5 sec
113 	while (ffcmd && --timeout > 0) {
114 		Glib::usleep(5000);
115 		ARDOUR::GUIIdle();
116 	}
117 	if (timeout == 0 || ffoutput.empty()) {
118 		return false;
119 	}
120 
121 	/* parse */
122 
123 	std::vector<std::vector<std::string> > lines;
124 	ParseCSV(ffoutput, lines);
125 	double timebase = 0;
126 	m_width = m_height = 0;
127 	m_fps = m_aspect = 0;
128 	m_duration = 0;
129 	m_sar.clear();
130 	m_codec.clear();
131 	m_audio.clear();
132 
133 #define PARSE_FRACTIONAL_FPS(VAR) \
134 	{ \
135 		std::string::size_type pos; \
136 		VAR = atof(value); \
137 		pos = value.find_first_of('/'); \
138 		if (pos != std::string::npos) { \
139 			VAR = atof(value.substr(0, pos)) / atof(value.substr(pos+1)); \
140 		} \
141 	}
142 
143 	std::string duration_from_format;
144 
145 	for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
146 		if (i->at(0) == X_("format")) {
147 			/* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
148 			for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
149 				const size_t kvsep = kv->find('=');
150 				if(kvsep == std::string::npos) continue;
151 				std::string key = kv->substr(0, kvsep);
152 				std::string value = kv->substr(kvsep + 1);
153 				if (key == X_("duration")) {
154 					duration_from_format = value;
155 				}
156 			}
157 		} else
158 		if (i->at(0) == X_("stream")) {
159 			if (i->at(5) == X_("codec_type=video") && m_width == 0) {
160 
161 				for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
162 					const size_t kvsep = kv->find('=');
163 					if(kvsep == std::string::npos) continue;
164 					std::string key = kv->substr(0, kvsep);
165 					std::string value = kv->substr(kvsep + 1);
166 
167 					if (key == X_("index")) {
168 						m_videoidx = atoi(value);
169 					} else if (key == X_("width")) {
170 						m_width = atoi(value);
171 					} else if (key == X_("height")) {
172 						m_height = atoi(value);
173 					} else if (key == X_("codec_name")) {
174 						if (!m_codec.empty()) m_codec += " ";
175 						m_codec += value;
176 					} else if (key == X_("codec_long_name")) {
177 						if (!m_codec.empty()) m_codec += " ";
178 						m_codec += "[" + value + "]";
179 					} else if (key == X_("codec_tag_string")) {
180 						if (!m_codec.empty()) m_codec += " ";
181 						m_codec += "(" + value + ")";
182 					} else if (key == X_("r_frame_rate")) {
183 						PARSE_FRACTIONAL_FPS(m_fps)
184 					} else if (key == X_("avg_frame_rate") && m_fps == 0) {
185 						PARSE_FRACTIONAL_FPS(m_fps)
186 					} else if (key == X_("time_base")) {
187 						PARSE_FRACTIONAL_FPS(timebase)
188 					} else if (key == X_("timecode") && m_duration == 0 && m_fps > 0) {
189 						int h,m,s; char f[32];
190 						if (sscanf(i->at(16).c_str(), "%d:%d:%d:%32s",&h,&m,&s,f) == 4) {
191 							m_duration = (ARDOUR::samplecnt_t) floor(m_fps * (
192 									h * 3600.0
193 								+ m * 60.0
194 								+ s * 1.0
195 								+ atoi(f) / pow((double)10, (int)strlen(f))
196 							));
197 						}
198 					} else if (key == X_("duration_ts") && m_fps == 0 && timebase !=0 ) {
199 						m_duration = atof(value) * m_fps * timebase;
200 					} else if (key == X_("duration") && m_fps != 0 && m_duration == 0) {
201 						m_duration = atof(value) * m_fps;
202 					} else if (key == X_("sample_aspect_ratio")) {
203 						std::string::size_type pos;
204 						pos = value.find_first_of(':');
205 						if (pos != std::string::npos && atof(value.substr(pos+1)) != 0) {
206 							m_sar = value;
207 							m_sar.replace(pos, 1, "/");
208 						}
209 					} else if (key == X_("display_aspect_ratio")) {
210 						std::string::size_type pos;
211 						pos = value.find_first_of(':');
212 						if (pos != std::string::npos && atof(value.substr(pos+1)) != 0) {
213 							m_aspect = atof(value.substr(0, pos)) / atof(value.substr(pos+1));
214 						}
215 					}
216 				}
217 
218 				if (m_aspect == 0) {
219 					m_aspect = (double)m_width / (double)m_height;
220 				}
221 
222 			} else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
223 				FFAudioStream as;
224 				for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
225 					const size_t kvsep = kv->find('=');
226 					if(kvsep == std::string::npos) continue;
227 					std::string key = kv->substr(0, kvsep);
228 					std::string value = kv->substr(kvsep + 1);
229 
230 					if (key == X_("channels")) {
231 						as.channels   = atoi(value);
232 					} else if (key == X_("index")) {
233 						as.stream_id  = value;
234 					} else if (key == X_("codec_long_name")) {
235 						if (!as.name.empty()) as.name += " ";
236 						as.name += value;
237 					} else if (key == X_("codec_name")) {
238 						if (!as.name.empty()) as.name += " ";
239 						as.name += value;
240 					} else if (key == X_("sample_fmt")) {
241 						if (!as.name.empty()) as.name += " ";
242 						as.name += "FMT:" + value;
243 					} else if (key == X_("sample_rate")) {
244 						if (!as.name.empty()) as.name += " ";
245 						as.name += "SR:" + value;
246 					}
247 
248 				}
249 				m_audio.push_back(as);
250 			}
251 		}
252 	}
253 	/* end parse */
254 
255 	if (m_duration == 0 && !duration_from_format.empty() && m_fps > 0) {
256 		warning << "using video-duration from format (container)." << endmsg;
257 		m_duration = atof(duration_from_format) * m_fps;
258 	}
259 
260 #if 0 /* DEBUG */
261 	printf("FPS: %f\n", m_fps);
262 	printf("Duration: %lu frames\n",(unsigned long)m_duration);
263 	printf("W/H: %ix%i\n",m_width, m_height);
264 	printf("aspect: %f\n",m_aspect);
265 	printf("codec: %s\n",m_codec.c_str());
266 	if (m_audio.size() > 0) {
267 		for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
268 			printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
269 		}
270 	} else {
271 	  printf("audio: no audio streams in file.\n");
272 	}
273 #endif
274 
275 	return true;
276 }
277 
278 TranscodeFfmpeg::FFSettings
default_encoder_settings()279 TranscodeFfmpeg::default_encoder_settings ()
280 {
281 	TranscodeFfmpeg::FFSettings ffs;
282 	ffs.clear();
283 	ffs["-vcodec"] = "mpeg4";
284 	ffs["-acodec"] = "ac3";
285 	ffs["-b:v"] = "5000k";
286 	ffs["-b:a"] = "160k";
287 	return ffs;
288 }
289 
290 TranscodeFfmpeg::FFSettings
default_meta_data()291 TranscodeFfmpeg::default_meta_data ()
292 {
293 	TranscodeFfmpeg::FFSettings ffm;
294 	ffm.clear();
295 	ffm["comment"] = "Created with " PROGRAM_NAME;
296 	return ffm;
297 }
298 
299 
300 bool
encode(std::string outfile,std::string inf_a,std::string inf_v,TranscodeFfmpeg::FFSettings ffs,TranscodeFfmpeg::FFSettings meta,bool map)301 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, TranscodeFfmpeg::FFSettings ffs, TranscodeFfmpeg::FFSettings meta, bool map)
302 {
303 #define MAX_FFMPEG_ENCODER_ARGS (100)
304 	char **argp;
305 	int a=0;
306 
307 	argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
308 	argp[a++] = strdup(ffmpeg_exe.c_str());
309 	if (m_avoffset < 0 || m_avoffset > 0) {
310 		std::ostringstream osstream; osstream << m_avoffset;
311 		argp[a++] = strdup("-itsoffset");
312 		argp[a++] = strdup(osstream.str().c_str());
313 	}
314 	argp[a++] = strdup("-i");
315 	argp[a++] = strdup(inf_v.c_str());
316 
317 	argp[a++] = strdup("-i");
318 	argp[a++] = strdup(inf_a.c_str());
319 
320 	for(TranscodeFfmpeg::FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
321 		argp[a++] = strdup(it->first.c_str());
322 		argp[a++] = strdup(it->second.c_str());
323 	}
324 	for(TranscodeFfmpeg::FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
325 		argp[a++] = strdup("-metadata");
326 		argp[a++] = SystemExec::format_key_value_parameter (it->first.c_str(), it->second.c_str());
327 	}
328 
329 	if (m_fps > 0) {
330 		m_lead_in  = rint (m_lead_in * m_fps) / m_fps;
331 		m_lead_out = rint (m_lead_out * m_fps) / m_fps;
332 	}
333 
334 	if (m_lead_in != 0 && m_lead_out != 0) {
335 		std::ostringstream osstream;
336 		argp[a++] = strdup("-vf");
337 		osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in;
338 		if (!m_sar.empty()) osstream << X_(":sar=") << m_sar;
339 		osstream << X_(" [pre]; ");
340 		osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out;
341 		if (!m_sar.empty()) osstream << X_(":sar=") << m_sar;
342 		osstream << X_(" [post]; ");
343 		osstream << X_("[pre] [in] [post] concat=n=3");
344 		argp[a++] = strdup(osstream.str().c_str());
345 	} else if (m_lead_in != 0) {
346 		std::ostringstream osstream;
347 		argp[a++] = strdup("-vf");
348 		osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in;
349 		if (!m_sar.empty()) osstream << X_(":sar=") << m_sar;
350 		osstream << X_(" [pre]; ");
351 		osstream << X_("[pre] [in] concat=n=2");
352 		argp[a++] = strdup(osstream.str().c_str());
353 	} else if (m_lead_out != 0) {
354 		std::ostringstream osstream;
355 		argp[a++] = strdup("-vf");
356 		osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out;
357 		if (!m_sar.empty()) osstream << X_(":sar=") << m_sar;
358 		osstream << 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 ARDOUR::SystemExec(ffmpeg_exe, argp);
388 	ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
389 	ffcmd->Terminated.connect (*this, invalidator (*this), boost::bind (&TranscodeFfmpeg::ffexit, this), gui_context());
390 	if (ffcmd->start (SystemExec::MergeWithStdin)) {
391 		ffexit();
392 		return false;
393 	}
394 	return true;
395 }
396 
397 bool
extract_audio(std::string outfile,ARDOUR::samplecnt_t,unsigned int stream)398 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::samplecnt_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 ARDOUR::SystemExec(ffmpeg_exe, argp);
436 	ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
437 	ffcmd->Terminated.connect (*this, invalidator (*this), boost::bind (&TranscodeFfmpeg::ffexit, this), gui_context());
438 	if (ffcmd->start (SystemExec::MergeWithStdin)) {
439 		ffexit();
440 		return false;
441 	}
442 	return true;
443 }
444 
445 
446 bool
transcode(std::string outfile,const int outw,const int outh,const int kbitps)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 ARDOUR::SystemExec(ffmpeg_exe, argp);
496 	ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
497 	ffcmd->Terminated.connect (*this, invalidator (*this), boost::bind (&TranscodeFfmpeg::ffexit, this), gui_context());
498 	if (ffcmd->start (SystemExec::MergeWithStdin)) {
499 		ffexit();
500 		return false;
501 	}
502 	return true;
503 }
504 
505 void
cancel()506 TranscodeFfmpeg::cancel ()
507 {
508 	if (!ffcmd || !ffcmd->is_running()) { return;}
509 	ffcmd->write_to_stdin("q");
510 #ifdef PLATFORM_WINDOWS
511 	Sleep(1000);
512 #else
513 	sleep (1);
514 #endif
515 	if (ffcmd) {
516 	  ffcmd->terminate();
517 	}
518 }
519 
520 void
ffexit()521 TranscodeFfmpeg::ffexit ()
522 {
523 	delete ffcmd;
524 	ffcmd=0;
525 	Finished(); /* EMIT SIGNAL */
526 }
527 
528 void
ffprobeparse(std::string d,size_t)529 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
530 {
531 	ffoutput+=d;
532 }
533 
534 void
ffmpegparse_a(std::string d,size_t)535 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
536 {
537 	const char *t;
538 	int h,m,s; char f[7];
539 	ARDOUR::samplecnt_t p = -1;
540 
541 	if (!(t=strstr(d.c_str(), "time="))) { return; }
542 
543 	if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
544 		p = (ARDOUR::samplecnt_t) floor( 100.0 * (
545 		      h * 3600.0
546 		    + m * 60.0
547 		    + s * 1.0
548 		    + atoi(f) / pow((double)10, (int)strlen(f))
549 		));
550 		p = p * m_fps / 100.0;
551 		if (p > m_duration ) { p = m_duration; }
552 		Progress(p, m_duration); /* EMIT SIGNAL */
553 	} else {
554 		Progress(0, 0); /* EMIT SIGNAL */
555 	}
556 }
557 
558 void
ffmpegparse_v(std::string d,size_t)559 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
560 {
561 	if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
562 		warning << "ffmpeg-error: " << d << endmsg;
563 	}
564 	if (strncmp(d.c_str(), "frame=",6)) {
565 #if 1 /* DEBUG */
566 		if (debug_enable) {
567 			d.erase(d.find_last_not_of(" \t\r\n") + 1);
568 		  printf("ffmpeg: '%s'\n", d.c_str());
569 		}
570 #endif
571 		return;
572 	}
573 	ARDOUR::samplecnt_t f = atol(d.substr(6));
574 	if (f == 0) {
575 		Progress(0, 0); /* EMIT SIGNAL */
576 	} else {
577 		Progress(f, m_duration); /* EMIT SIGNAL */
578 	}
579 }
580