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"
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 (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 (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)) {
118 /* wait for ffprobe process to exit */
121 /* wait for interposer thread to copy all data.
122 * SystemExec::Terminated is emitted and ffcmd set to NULL */
123 int timeout = 300; // 1.5 sec
124 while (ffcmd && --timeout > 0) {
127 if (timeout == 0 || ffoutput.empty()) {
133 std::vector<std::vector<std::string> > lines;
134 ParseCSV(ffoutput, lines);
136 m_width = m_height = 0;
137 m_fps = m_aspect = 0;
142 #define PARSE_FRACTIONAL_FPS(VAR) \
144 std::string::size_type pos; \
146 pos = value.find_first_of('/'); \
147 if (pos != std::string::npos) { \
148 VAR = atof(value.substr(0, pos)) / atof(value.substr(pos+1)); \
152 for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
153 if (i->at(0) == X_("format")) {
154 /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
156 if (i->at(0) == X_("stream")) {
157 if (i->at(5) == X_("codec_type=video") && m_width == 0) {
159 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
160 const size_t kvsep = kv->find('=');
161 if(kvsep == std::string::npos) continue;
162 std::string key = kv->substr(0, kvsep);
163 std::string value = kv->substr(kvsep + 1);
165 if (key == X_("index")) {
166 m_videoidx = atoi(value);
167 } else if (key == X_("width")) {
168 m_width = atoi(value);
169 } else if (key == X_("height")) {
170 m_height = atoi(value);
171 } else if (key == X_("codec_name")) {
172 if (!m_codec.empty()) m_codec += " ";
174 } else if (key == X_("codec_long_name")) {
175 if (!m_codec.empty()) m_codec += " ";
176 m_codec += "[" + value + "]";
177 } else if (key == X_("codec_tag_string")) {
178 if (!m_codec.empty()) m_codec += " ";
179 m_codec += "(" + value + ")";
180 } else if (key == X_("r_frame_rate")) {
181 PARSE_FRACTIONAL_FPS(m_fps)
182 } else if (key == X_("avg_frame_rate") && m_fps == 0) {
183 PARSE_FRACTIONAL_FPS(m_fps)
184 } else if (key == X_("time_base")) {
185 PARSE_FRACTIONAL_FPS(timebase)
186 } else if (key == X_("timecode") && m_duration == 0) {
187 int h,m,s; char f[7];
188 if (sscanf(i->at(16).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
189 m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
193 + atoi(f) / pow(10, strlen(f))
196 } else if (key == X_("duration_ts") && m_fps == 0 && timebase !=0 ) {
197 m_duration = atof(value) * m_fps * timebase;
198 } else if (key == X_("duration") && m_fps != 0 && m_duration == 0) {
199 m_duration = atof(value) * m_fps;
200 } else if (key == X_("display_aspect_ratio")) {
201 std::string::size_type pos;
202 pos = value.find_first_of(':');
203 if (pos != std::string::npos && atof(value.substr(pos+1)) != 0) {
204 m_aspect = atof(value.substr(0, pos)) / atof(value.substr(pos+1));
210 m_aspect = (double)m_width / (double)m_height;
213 } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
215 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
216 const size_t kvsep = kv->find('=');
217 if(kvsep == std::string::npos) continue;
218 std::string key = kv->substr(0, kvsep);
219 std::string value = kv->substr(kvsep + 1);
221 if (key == X_("channels")) {
222 as.channels = atoi(value);
223 } else if (key == X_("index")) {
224 as.stream_id = value;
225 } else if (key == X_("codec_long_name")) {
226 if (!as.name.empty()) as.name += " ";
228 } else if (key == X_("codec_name")) {
229 if (!as.name.empty()) as.name += " ";
231 } else if (key == X_("sample_fmt")) {
232 if (!as.name.empty()) as.name += " ";
233 as.name += "FMT:" + value;
234 } else if (key == X_("sample_rate")) {
235 if (!as.name.empty()) as.name += " ";
236 as.name += "SR:" + value;
240 m_audio.push_back(as);
247 printf("FPS: %f\n", m_fps);
248 printf("Duration: %lu frames\n",(unsigned long)m_duration);
249 printf("W/H: %ix%i\n",m_width, m_height);
250 printf("aspect: %f\n",m_aspect);
251 printf("codec: %s\n",m_codec.c_str());
252 if (m_audio.size() > 0) {
253 for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
254 printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
257 printf("audio: no audio streams in file.\n");
265 TranscodeFfmpeg::default_encoder_settings ()
269 ffs["-vcodec"] = "mpeg4";
270 ffs["-acodec"] = "ac3";
271 ffs["-b:v"] = "5000k";
272 ffs["-b:a"] = "160k";
277 TranscodeFfmpeg::default_meta_data ()
281 ffm["comment"] = "Created with ardour";
286 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
288 size_t start_pos = 0;
289 std::string v1 = value;
290 while((start_pos = v1.find_first_not_of(
291 "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
292 start_pos)) != std::string::npos)
294 v1.replace(start_pos, 1, "_");
299 while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
300 v1.replace(start_pos, 1, "\\\"");
304 size_t len = key.length() + v1.length() + 4;
305 char *mds = (char*) calloc(len, sizeof(char));
306 snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
311 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map)
313 #define MAX_FFMPEG_ENCODER_ARGS (100)
317 argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
318 argp[a++] = strdup(ffmpeg_exe.c_str());
319 if (m_avoffset < 0 || m_avoffset > 0) {
320 std::ostringstream osstream; osstream << m_avoffset;
321 argp[a++] = strdup("-itsoffset");
322 argp[a++] = strdup(osstream.str().c_str());
324 argp[a++] = strdup("-i");
325 argp[a++] = strdup(inf_v.c_str());
327 argp[a++] = strdup("-i");
328 argp[a++] = strdup(inf_a.c_str());
330 for(FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
331 argp[a++] = strdup(it->first.c_str());
332 argp[a++] = strdup(it->second.c_str());
334 for(FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
335 argp[a++] = strdup("-metadata");
336 argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
338 if (m_lead_in != 0 && m_lead_out != 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_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << 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 << X_(" [pre]; ");
349 osstream << X_("[pre] [in] concat=n=2");
350 argp[a++] = strdup(osstream.str().c_str());
351 } else if (m_lead_out != 0) {
352 std::ostringstream osstream;
353 argp[a++] = strdup("-vf");
354 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << X_(" [post]; ");
355 osstream << X_("[in] [post] concat=n=2");
356 argp[a++] = strdup(osstream.str().c_str());
360 std::ostringstream osstream;
361 argp[a++] = strdup("-map");
362 osstream << X_("0:") << m_videoidx;
363 argp[a++] = strdup(osstream.str().c_str());
364 argp[a++] = strdup("-map");
365 argp[a++] = strdup("1:0");
368 argp[a++] = strdup("-y");
369 argp[a++] = strdup(outfile.c_str());
371 assert(a<MAX_FFMPEG_ENCODER_ARGS);
372 /* Note: these are free()d in ~SystemExec */
374 if (debug_enable) { /* tentative debug mode */
375 printf("EXPORT ENCODE:\n");
376 for (int i=0; i< a; ++i) {
377 printf("%s ", argp[i]);
383 ffcmd = new SystemExec(ffmpeg_exe, argp);
384 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
385 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
386 if (ffcmd->start(2)) {
394 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
396 if (!probeok) return false;
397 if (stream >= m_audio.size()) return false;
402 argp=(char**) calloc(15,sizeof(char*));
403 argp[i++] = strdup(ffmpeg_exe.c_str());
404 argp[i++] = strdup("-i");
405 argp[i++] = strdup(infile.c_str());
406 #if 0 /* ffmpeg write original samplerate, use a3/SRC to resample */
407 argp[i++] = strdup("-ar");
408 argp[i] = (char*) calloc(7,sizeof(char)); snprintf(argp[i++], 7, "%"PRId64, samplerate);
410 argp[i++] = strdup("-ac");
411 argp[i] = (char*) calloc(3,sizeof(char)); snprintf(argp[i++], 3, "%i", m_audio.at(stream).channels);
412 argp[i++] = strdup("-map");
413 argp[i] = (char*) calloc(8,sizeof(char)); snprintf(argp[i++], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
414 argp[i++] = strdup("-vn");
415 argp[i++] = strdup("-acodec");
416 argp[i++] = strdup("pcm_f32le");
417 argp[i++] = strdup("-y");
418 argp[i++] = strdup(outfile.c_str());
419 argp[i++] = (char *)0;
420 /* Note: argp is free()d in ~SystemExec */
422 if (debug_enable) { /* tentative debug mode */
423 printf("EXTRACT AUDIO:\n");
424 for (int i=0; i< 14; ++i) {
425 printf("%s ", argp[i]);
431 ffcmd = new SystemExec(ffmpeg_exe, argp);
432 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
433 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
434 if (ffcmd->start(2)) {
443 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
445 if (!probeok) return false;
448 int bitrate = kbitps;
452 if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
453 if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
456 const double bitperpixel = .7; /* avg quality */
457 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
459 bitrate = bitrate / 10;
461 if (bitrate < 10) bitrate = 10;
462 if (bitrate > 1000) bitrate = 1000;
464 argp=(char**) calloc(16,sizeof(char*));
465 argp[0] = strdup(ffmpeg_exe.c_str());
466 argp[1] = strdup("-i");
467 argp[2] = strdup(infile.c_str());
468 argp[3] = strdup("-b:v");
469 argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
470 argp[5] = strdup("-s");
471 argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
472 argp[7] = strdup("-y");
473 argp[8] = strdup("-vcodec");
474 argp[9] = strdup("mjpeg");
475 argp[10] = strdup("-an");
476 argp[11] = strdup("-intra");
477 argp[12] = strdup("-g");
478 argp[13] = strdup("1");
479 argp[14] = strdup(outfile.c_str());
480 argp[15] = (char *)0;
481 /* Note: these are free()d in ~SystemExec */
483 if (debug_enable) { /* tentative debug mode */
484 printf("TRANSCODE VIDEO:\n");
485 for (int i=0; i< 15; ++i) {
486 printf("%s ", argp[i]);
491 ffcmd = new SystemExec(ffmpeg_exe, argp);
492 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
493 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
494 if (ffcmd->start(2)) {
502 TranscodeFfmpeg::cancel ()
504 if (!ffcmd || !ffcmd->is_running()) { return;}
505 ffcmd->write_to_stdin("q");
513 TranscodeFfmpeg::ffexit ()
517 Finished(); /* EMIT SIGNAL */
521 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
527 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
530 int h,m,s; char f[7];
531 ARDOUR::framecnt_t p = -1;
533 if (!(t=strstr(d.c_str(), "time="))) { return; }
535 if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
536 p = (ARDOUR::framecnt_t) floor( 100.0 * (
540 + atoi(f) / pow(10, strlen(f))
542 p = p * m_fps / 100.0;
543 if (p > m_duration ) { p = m_duration; }
544 Progress(p, m_duration); /* EMIT SIGNAL */
546 Progress(0, 0); /* EMIT SIGNAL */
551 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
553 if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
554 warning << "ffmpeg-error: " << d << endmsg;
556 if (strncmp(d.c_str(), "frame=",6)) {
559 d.erase(d.find_last_not_of(" \t\r\n") + 1);
560 printf("ffmpeg: '%s'\n", d.c_str());
563 Progress(0, 0); /* EMIT SIGNAL */
566 ARDOUR::framecnt_t f = atol(d.substr(6));
568 Progress(0, 0); /* EMIT SIGNAL */
570 Progress(f, m_duration); /* EMIT SIGNAL */