2 Copyright (C) 2010-2013 Paul Davis
3 Author: Robin Gareus <robin@gareus.org>
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.
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.
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.
23 #include <sys/types.h>
25 #include "pbd/error.h"
26 #include "pbd/convert.h"
27 #include "pbd/file_utils.h"
28 #include "gui_thread.h"
30 #include "transcode_ffmpeg.h"
31 #include "utils_videotl.h"
41 TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
49 m_avoffset = m_lead_in = m_lead_out = 0;
50 m_width = m_height = 0;
52 #if 1 /* tentative debug mode */
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");
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");
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");
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");
73 if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
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"
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"
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"
90 if (infile.empty() || !probe()) {
96 TranscodeFfmpeg::~TranscodeFfmpeg ()
102 TranscodeFfmpeg::probe ()
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());
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)) {
122 /* wait for ffprobe process to exit */
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) {
131 if (timeout == 0 || ffoutput.empty()) {
137 std::vector<std::vector<std::string> > lines;
138 ParseCSV(ffoutput, lines);
140 m_width = m_height = 0;
141 m_fps = m_aspect = 0;
146 #define PARSE_FRACTIONAL_FPS(VAR) \
148 std::string::size_type pos; \
150 pos = value.find_first_of('/'); \
151 if (pos != std::string::npos) { \
152 VAR = atof(value.substr(0, pos)) / atof(value.substr(pos+1)); \
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 */
160 if (i->at(0) == X_("stream")) {
161 if (i->at(5) == X_("codec_type=video") && m_width == 0) {
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);
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 += " ";
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 * (
197 + atoi(f) / pow(10, strlen(f))
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));
214 m_aspect = (double)m_width / (double)m_height;
217 } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
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);
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 += " ";
232 } else if (key == X_("codec_name")) {
233 if (!as.name.empty()) as.name += " ";
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;
244 m_audio.push_back(as);
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);
261 printf("audio: no audio streams in file.\n");
268 TranscodeFfmpeg::FFSettings
269 TranscodeFfmpeg::default_encoder_settings ()
271 TranscodeFfmpeg::FFSettings ffs;
273 ffs["-vcodec"] = "mpeg4";
274 ffs["-acodec"] = "ac3";
275 ffs["-b:v"] = "5000k";
276 ffs["-b:a"] = "160k";
280 TranscodeFfmpeg::FFSettings
281 TranscodeFfmpeg::default_meta_data ()
283 TranscodeFfmpeg::FFSettings ffm;
285 ffm["comment"] = "Created with ardour";
290 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
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)
298 v1.replace(start_pos, 1, "_");
303 while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
304 v1.replace(start_pos, 1, "\\\"");
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());
315 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, TranscodeFfmpeg::FFSettings ffs, TranscodeFfmpeg::FFSettings meta, bool map)
317 #define MAX_FFMPEG_ENCODER_ARGS (100)
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());
328 argp[a++] = strdup("-i");
329 argp[a++] = strdup(inf_v.c_str());
331 argp[a++] = strdup("-i");
332 argp[a++] = strdup(inf_a.c_str());
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());
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());
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());
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");
372 argp[a++] = strdup("-y");
373 argp[a++] = strdup(outfile.c_str());
375 assert(a<MAX_FFMPEG_ENCODER_ARGS);
376 /* Note: these are free()d in ~SystemExec */
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]);
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)) {
398 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
400 if (!probeok) return false;
401 if (stream >= m_audio.size()) return false;
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);
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 */
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]);
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)) {
447 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
449 if (!probeok) return false;
452 int bitrate = kbitps;
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); }
460 const double bitperpixel = .7; /* avg quality */
461 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
463 bitrate = bitrate / 10;
465 if (bitrate < 10) bitrate = 10;
466 if (bitrate > 1000) bitrate = 1000;
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 */
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]);
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)) {
506 TranscodeFfmpeg::cancel ()
508 if (!ffcmd || !ffcmd->is_running()) { return;}
509 ffcmd->write_to_stdin("q");
517 TranscodeFfmpeg::ffexit ()
521 Finished(); /* EMIT SIGNAL */
525 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
531 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
534 int h,m,s; char f[7];
535 ARDOUR::framecnt_t p = -1;
537 if (!(t=strstr(d.c_str(), "time="))) { return; }
539 if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
540 p = (ARDOUR::framecnt_t) floor( 100.0 * (
544 + atoi(f) / pow(10, strlen(f))
546 p = p * m_fps / 100.0;
547 if (p > m_duration ) { p = m_duration; }
548 Progress(p, m_duration); /* EMIT SIGNAL */
550 Progress(0, 0); /* EMIT SIGNAL */
555 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
557 if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
558 warning << "ffmpeg-error: " << d << endmsg;
560 if (strncmp(d.c_str(), "frame=",6)) {
563 d.erase(d.find_last_not_of(" \t\r\n") + 1);
564 printf("ffmpeg: '%s'\n", d.c_str());
567 Progress(0, 0); /* EMIT SIGNAL */
570 ARDOUR::framecnt_t f = atol(d.substr(6));
572 Progress(0, 0); /* EMIT SIGNAL */
574 Progress(f, m_duration); /* EMIT SIGNAL */