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.
20 #ifdef WITH_VIDEOTIMELINE
25 #include <sys/types.h>
27 #include "pbd/error.h"
28 #include "pbd/file_utils.h"
29 #include "pbd/file_utils.h"
30 #include "gui_thread.h"
32 #include "transcode_ffmpeg.h"
33 #include "utils_videotl.h"
37 TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
45 m_avoffset = m_lead_in = m_lead_out = 0;
46 m_width = m_height = 0;
48 #if 1 /* tentative debug mode */
52 std::string ff_file_path;
53 if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffmpeg_harvid"), ff_file_path)) { ffmpeg_exe = ff_file_path; }
54 else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
55 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
57 else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
58 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
61 if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffprobe_harvid"), ff_file_path)) { ffprobe_exe = ff_file_path; }
62 else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
63 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
65 else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
66 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
69 if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
71 "No ffprobe or ffmpeg executables could be found on this system.\n"
72 "Video import and export is not possible until you install those tools.\n"
73 "Ardour requires ffmpeg and ffprobe from ffmpeg.org - version 1.1 or newer.\n"
75 "The tools are included with the Ardour releases from ardour.org "
76 "and also available with the video-server at http://x42.github.com/harvid/\n"
78 "Important: the files need to be installed in $PATH and named ffmpeg_harvid and ffprobe_harvid.\n"
79 "If you already have a suitable ffmpeg installation on your system, we recommend creating "
80 "symbolic links from ffmpeg to ffmpeg_harvid and from ffprobe to ffprobe_harvid.\n"
86 if (infile.empty() || !probe()) {
92 TranscodeFfmpeg::~TranscodeFfmpeg ()
98 TranscodeFfmpeg::probe ()
102 argp=(char**) calloc(7,sizeof(char*));
103 argp[0] = strdup(ffprobe_exe.c_str());
104 argp[1] = strdup("-print_format");
105 argp[2] = strdup("csv=nk=0");
106 argp[3] = strdup("-show_format");
107 argp[4] = strdup("-show_streams");
108 argp[5] = strdup(infile.c_str());
110 ffcmd = new SystemExec(ffprobe_exe, argp);
111 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffprobeparse, this, _1 ,_2));
112 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
113 if (ffcmd->start(1)) {
121 std::vector<std::vector<std::string> > lines;
122 ParseCSV(ffoutput, lines);
124 m_width = m_height = 0;
125 m_fps = m_aspect = 0;
130 #define PARSE_FRACTIONAL_FPS(VAR) \
132 std::string::size_type pos; \
133 VAR = atof(value.c_str()); \
134 pos = value.find_first_of('/'); \
135 if (pos != std::string::npos) { \
136 VAR = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str()); \
140 for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
141 if (i->at(0) == X_("format")) {
142 /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
144 if (i->at(0) == X_("stream")) {
145 if (i->at(5) == X_("codec_type=video") && m_width == 0) {
147 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
148 const size_t kvsep = kv->find('=');
149 if(kvsep == std::string::npos) continue;
150 std::string key = kv->substr(0, kvsep);
151 std::string value = kv->substr(kvsep + 1);
153 if (key == X_("index")) {
154 m_videoidx = atoi(value.c_str());
155 } else if (key == X_("width")) {
156 m_width = atoi(value.c_str());
157 } else if (key == X_("height")) {
158 m_height = atoi(value.c_str());
159 } else if (key == X_("codec_name")) {
160 if (!m_codec.empty()) m_codec += " ";
162 } else if (key == X_("codec_long_name")) {
163 if (!m_codec.empty()) m_codec += " ";
164 m_codec += "[" + value + "]";
165 } else if (key == X_("codec_tag_string")) {
166 if (!m_codec.empty()) m_codec += " ";
167 m_codec += "(" + value + ")";
168 } else if (key == X_("r_frame_rate")) {
169 PARSE_FRACTIONAL_FPS(m_fps)
170 } else if (key == X_("avg_frame_rate") && m_fps == 0) {
171 PARSE_FRACTIONAL_FPS(m_fps)
172 } else if (key == X_("time_base")) {
173 PARSE_FRACTIONAL_FPS(timebase)
174 } else if (key == X_("timecode") && m_duration == 0) {
175 int h,m,s; char f[7];
176 if (sscanf(i->at(16).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
177 m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
181 + atoi(f) / pow(10, strlen(f))
184 } else if (key == X_("duration_ts") && m_fps == 0 && timebase !=0 ) {
185 m_duration = atof(value.c_str()) * m_fps * timebase;
186 } else if (key == X_("duration") && m_fps != 0 && m_duration == 0) {
187 m_duration = atof(value.c_str()) * m_fps;
188 } else if (key == X_("display_aspect_ratio")) {
189 std::string::size_type pos;
190 pos = value.find_first_of(':');
191 if (pos != std::string::npos && atof(value.substr(pos+1).c_str()) != 0) {
192 m_aspect = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str());
198 m_aspect = (double)m_width / (double)m_height;
201 } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
203 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
204 const size_t kvsep = kv->find('=');
205 if(kvsep == std::string::npos) continue;
206 std::string key = kv->substr(0, kvsep);
207 std::string value = kv->substr(kvsep + 1);
209 if (key == X_("channels")) {
210 as.channels = atoi(value.c_str());
211 } else if (key == X_("index")) {
212 as.stream_id = value;
213 } else if (key == X_("codec_long_name")) {
214 if (!as.name.empty()) as.name += " ";
216 } else if (key == X_("codec_name")) {
217 if (!as.name.empty()) as.name += " ";
219 } else if (key == X_("sample_fmt")) {
220 if (!as.name.empty()) as.name += " ";
221 as.name += "FMT:" + value;
222 } else if (key == X_("sample_rate")) {
223 if (!as.name.empty()) as.name += " ";
224 as.name += "SR:" + value;
228 m_audio.push_back(as);
236 while (ffcmd && --timeout) usleep (1000); // wait until 'ffprobe' terminated.
237 if (timeout == 0) return false;
240 printf("FPS: %f\n", m_fps);
241 printf("Duration: %lu frames\n",(unsigned long)m_duration);
242 printf("W/H: %ix%i\n",m_width, m_height);
243 printf("aspect: %f\n",m_aspect);
244 printf("codec: %s\n",m_codec.c_str());
245 if (m_audio.size() > 0) {
246 for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
247 printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
250 printf("audio: no audio streams in file.\n");
258 TranscodeFfmpeg::default_encoder_settings ()
262 ffs["-vcodec"] = "mpeg4";
263 ffs["-acodec"] = "ac3";
264 ffs["-b:v"] = "5000k";
265 ffs["-b:a"] = "160k";
270 TranscodeFfmpeg::default_meta_data ()
274 ffm["comment"] = "Created with ardour";
279 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
281 size_t start_pos = 0;
282 std::string v1 = value;
283 while((start_pos = v1.find_first_not_of(
284 "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
285 start_pos)) != std::string::npos)
287 v1.replace(start_pos, 1, "_");
292 while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
293 v1.replace(start_pos, 1, "\\\"");
297 size_t len = key.length() + v1.length() + 4;
298 char *mds = (char*) calloc(len, sizeof(char));
299 snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
304 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map)
306 #define MAX_FFMPEG_ENCODER_ARGS (100)
310 argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
311 argp[a++] = strdup(ffmpeg_exe.c_str());
312 if (m_avoffset < 0 || m_avoffset > 0) {
313 std::ostringstream osstream; osstream << m_avoffset;
314 argp[a++] = strdup("-itsoffset");
315 argp[a++] = strdup(osstream.str().c_str());
317 argp[a++] = strdup("-i");
318 argp[a++] = strdup(inf_v.c_str());
320 argp[a++] = strdup("-i");
321 argp[a++] = strdup(inf_a.c_str());
323 for(FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
324 argp[a++] = strdup(it->first.c_str());
325 argp[a++] = strdup(it->second.c_str());
327 for(FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
328 argp[a++] = strdup("-metadata");
329 argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
331 if (m_lead_in != 0 && m_lead_out != 0) {
332 std::ostringstream osstream;
333 argp[a++] = strdup("-vf");
334 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in << X_(" [pre]; ");
335 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << X_(" [post]; ");
336 osstream << X_("[pre] [in] [post] concat=n=3");
337 argp[a++] = strdup(osstream.str().c_str());
338 } else if (m_lead_in != 0) {
339 std::ostringstream osstream;
340 argp[a++] = strdup("-vf");
341 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in << X_(" [pre]; ");
342 osstream << X_("[pre] [in] concat=n=2");
343 argp[a++] = strdup(osstream.str().c_str());
344 } else if (m_lead_out != 0) {
345 std::ostringstream osstream;
346 argp[a++] = strdup("-vf");
347 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << X_(" [post]; ");
348 osstream << X_("[in] [post] concat=n=2");
349 argp[a++] = strdup(osstream.str().c_str());
353 std::ostringstream osstream;
354 argp[a++] = strdup("-map");
355 osstream << X_("0:") << m_videoidx;
356 argp[a++] = strdup(osstream.str().c_str());
357 argp[a++] = strdup("-map");
358 argp[a++] = strdup("1:0");
361 argp[a++] = strdup("-y");
362 argp[a++] = strdup(outfile.c_str());
364 assert(a<MAX_FFMPEG_ENCODER_ARGS);
365 /* Note: these are free()d in ~SystemExec */
367 if (debug_enable) { /* tentative debug mode */
368 printf("EXPORT ENCODE:\n");
369 for (int i=0; i< a; ++i) {
370 printf("%s ", argp[i]);
376 ffcmd = new SystemExec(ffmpeg_exe, argp);
377 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
378 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
379 if (ffcmd->start(2)) {
387 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
389 if (!probeok) return false;
390 if (stream >= m_audio.size()) return false;
395 argp=(char**) calloc(15,sizeof(char*));
396 argp[i++] = strdup(ffmpeg_exe.c_str());
397 argp[i++] = strdup("-i");
398 argp[i++] = strdup(infile.c_str());
399 #if 0 /* ffmpeg write original samplerate, use a3/SRC to resample */
400 argp[i++] = strdup("-ar");
401 argp[i] = (char*) calloc(7,sizeof(char)); snprintf(argp[i++], 7, "%"PRId64, samplerate);
403 argp[i++] = strdup("-ac");
404 argp[i] = (char*) calloc(3,sizeof(char)); snprintf(argp[i++], 3, "%i", m_audio.at(stream).channels);
405 argp[i++] = strdup("-map");
406 argp[i] = (char*) calloc(8,sizeof(char)); snprintf(argp[i++], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
407 argp[i++] = strdup("-vn");
408 argp[i++] = strdup("-acodec");
409 argp[i++] = strdup("pcm_f32le");
410 argp[i++] = strdup("-y");
411 argp[i++] = strdup(outfile.c_str());
412 argp[i++] = (char *)0;
413 /* Note: argp is free()d in ~SystemExec */
415 if (debug_enable) { /* tentative debug mode */
416 printf("EXTRACT AUDIO:\n");
417 for (int i=0; i< 14; ++i) {
418 printf("%s ", argp[i]);
424 ffcmd = new SystemExec(ffmpeg_exe, argp);
425 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
426 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
427 if (ffcmd->start(2)) {
436 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
438 if (!probeok) return false;
441 int bitrate = kbitps;
445 if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
446 if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
449 const double bitperpixel = .7; /* avg quality */
450 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
452 bitrate = bitrate / 10;
454 if (bitrate < 10) bitrate = 10;
455 if (bitrate > 1000) bitrate = 1000;
457 argp=(char**) calloc(16,sizeof(char*));
458 argp[0] = strdup(ffmpeg_exe.c_str());
459 argp[1] = strdup("-i");
460 argp[2] = strdup(infile.c_str());
461 argp[3] = strdup("-b:v");
462 argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
463 argp[5] = strdup("-s");
464 argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
465 argp[7] = strdup("-y");
466 argp[8] = strdup("-vcodec");
467 argp[9] = strdup("mpeg4");
468 argp[10] = strdup("-an");
469 argp[11] = strdup("-intra");
470 argp[12] = strdup("-g");
471 argp[13] = strdup("1");
472 argp[14] = strdup(outfile.c_str());
473 argp[15] = (char *)0;
474 /* Note: these are free()d in ~SystemExec */
476 if (debug_enable) { /* tentative debug mode */
477 printf("TRANSCODE VIDEO:\n");
478 for (int i=0; i< 15; ++i) {
479 printf("%s ", argp[i]);
484 ffcmd = new SystemExec(ffmpeg_exe, argp);
485 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
486 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
487 if (ffcmd->start(2)) {
495 TranscodeFfmpeg::cancel ()
497 if (!ffcmd || !ffcmd->is_running()) { return;}
498 ffcmd->write_to_stdin("q");
506 TranscodeFfmpeg::ffexit ()
510 Finished(); /* EMIT SIGNAL */
514 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
520 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
523 int h,m,s; char f[7];
524 ARDOUR::framecnt_t p = -1;
526 if (!(t=strstr(d.c_str(), "time="))) { return; }
528 if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
529 p = (ARDOUR::framecnt_t) floor( 100.0 * (
533 + atoi(f) / pow(10, strlen(f))
535 p = p * m_fps / 100.0;
536 if (p > m_duration ) { p = m_duration; }
537 Progress(p, m_duration); /* EMIT SIGNAL */
539 Progress(0, 0); /* EMIT SIGNAL */
544 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
546 if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
547 PBD::warning << "ffmpeg-error: " << d << endmsg;
549 if (strncmp(d.c_str(), "frame=",6)) {
552 d.erase(d.find_last_not_of(" \t\r\n") + 1);
553 printf("ffmpeg: '%s'\n", d.c_str());
556 Progress(0, 0); /* EMIT SIGNAL */
559 ARDOUR::framecnt_t f = atol(d.substr(6).c_str());
561 Progress(0, 0); /* EMIT SIGNAL */
563 Progress(f, m_duration); /* EMIT SIGNAL */
567 #endif /* WITH_VIDEOTIMELINE */