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