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/file_utils.h"
27 #include "pbd/file_utils.h"
28 #include "gui_thread.h"
30 #include "transcode_ffmpeg.h"
31 #include "utils_videotl.h"
35 TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
43 m_avoffset = m_lead_in = m_lead_out = 0;
44 m_width = m_height = 0;
46 #if 1 /* tentative debug mode */
50 std::string ff_file_path;
51 if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffmpeg_harvid"), ff_file_path)) { ffmpeg_exe = ff_file_path; }
52 else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
53 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
55 else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
56 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
59 if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffprobe_harvid"), ff_file_path)) { ffprobe_exe = ff_file_path; }
60 else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
61 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
63 else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
64 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
67 if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
69 "No ffprobe or ffmpeg executables could be found on this system.\n"
70 "Video import and export is not possible until you install those tools.\n"
71 "Ardour requires ffmpeg and ffprobe from ffmpeg.org - version 1.1 or newer.\n"
73 "The tools are included with the Ardour releases from ardour.org "
74 "and also available with the video-server at http://x42.github.com/harvid/\n"
76 "Important: the files need to be installed in $PATH and named ffmpeg_harvid and ffprobe_harvid.\n"
77 "If you already have a suitable ffmpeg installation on your system, we recommend creating "
78 "symbolic links from ffmpeg to ffmpeg_harvid and from ffprobe to ffprobe_harvid.\n"
84 if (infile.empty() || !probe()) {
90 TranscodeFfmpeg::~TranscodeFfmpeg ()
96 TranscodeFfmpeg::probe ()
100 argp=(char**) calloc(7,sizeof(char*));
101 argp[0] = strdup(ffprobe_exe.c_str());
102 argp[1] = strdup("-print_format");
103 argp[2] = strdup("csv=nk=0");
104 argp[3] = strdup("-show_format");
105 argp[4] = strdup("-show_streams");
106 argp[5] = strdup(infile.c_str());
108 ffcmd = new SystemExec(ffprobe_exe, argp);
109 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffprobeparse, this, _1 ,_2));
110 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
111 if (ffcmd->start(1)) {
119 std::vector<std::vector<std::string> > lines;
120 ParseCSV(ffoutput, lines);
122 m_width = m_height = 0;
123 m_fps = m_aspect = 0;
128 #define PARSE_FRACTIONAL_FPS(VAR) \
130 std::string::size_type pos; \
131 VAR = atof(value.c_str()); \
132 pos = value.find_first_of('/'); \
133 if (pos != std::string::npos) { \
134 VAR = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str()); \
138 for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
139 if (i->at(0) == X_("format")) {
140 /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
142 if (i->at(0) == X_("stream")) {
143 if (i->at(5) == X_("codec_type=video") && m_width == 0) {
145 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
146 const size_t kvsep = kv->find('=');
147 if(kvsep == std::string::npos) continue;
148 std::string key = kv->substr(0, kvsep);
149 std::string value = kv->substr(kvsep + 1);
151 if (key == X_("index")) {
152 m_videoidx = atoi(value.c_str());
153 } else if (key == X_("width")) {
154 m_width = atoi(value.c_str());
155 } else if (key == X_("height")) {
156 m_height = atoi(value.c_str());
157 } else if (key == X_("codec_name")) {
158 if (!m_codec.empty()) m_codec += " ";
160 } else if (key == X_("codec_long_name")) {
161 if (!m_codec.empty()) m_codec += " ";
162 m_codec += "[" + value + "]";
163 } else if (key == X_("codec_tag_string")) {
164 if (!m_codec.empty()) m_codec += " ";
165 m_codec += "(" + value + ")";
166 } else if (key == X_("r_frame_rate")) {
167 PARSE_FRACTIONAL_FPS(m_fps)
168 } else if (key == X_("avg_frame_rate") && m_fps == 0) {
169 PARSE_FRACTIONAL_FPS(m_fps)
170 } else if (key == X_("time_base")) {
171 PARSE_FRACTIONAL_FPS(timebase)
172 } else if (key == X_("timecode") && m_duration == 0) {
173 int h,m,s; char f[7];
174 if (sscanf(i->at(16).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
175 m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
179 + atoi(f) / pow(10, strlen(f))
182 } else if (key == X_("duration_ts") && m_fps == 0 && timebase !=0 ) {
183 m_duration = atof(value.c_str()) * m_fps * timebase;
184 } else if (key == X_("duration") && m_fps != 0 && m_duration == 0) {
185 m_duration = atof(value.c_str()) * m_fps;
186 } else if (key == X_("display_aspect_ratio")) {
187 std::string::size_type pos;
188 pos = value.find_first_of(':');
189 if (pos != std::string::npos && atof(value.substr(pos+1).c_str()) != 0) {
190 m_aspect = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str());
196 m_aspect = (double)m_width / (double)m_height;
199 } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
201 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
202 const size_t kvsep = kv->find('=');
203 if(kvsep == std::string::npos) continue;
204 std::string key = kv->substr(0, kvsep);
205 std::string value = kv->substr(kvsep + 1);
207 if (key == X_("channels")) {
208 as.channels = atoi(value.c_str());
209 } else if (key == X_("index")) {
210 as.stream_id = value;
211 } else if (key == X_("codec_long_name")) {
212 if (!as.name.empty()) as.name += " ";
214 } else if (key == X_("codec_name")) {
215 if (!as.name.empty()) as.name += " ";
217 } else if (key == X_("sample_fmt")) {
218 if (!as.name.empty()) as.name += " ";
219 as.name += "FMT:" + value;
220 } else if (key == X_("sample_rate")) {
221 if (!as.name.empty()) as.name += " ";
222 as.name += "SR:" + value;
226 m_audio.push_back(as);
234 while (ffcmd && --timeout) usleep (1000); // wait until 'ffprobe' terminated.
235 if (timeout == 0) return false;
238 printf("FPS: %f\n", m_fps);
239 printf("Duration: %lu frames\n",(unsigned long)m_duration);
240 printf("W/H: %ix%i\n",m_width, m_height);
241 printf("aspect: %f\n",m_aspect);
242 printf("codec: %s\n",m_codec.c_str());
243 if (m_audio.size() > 0) {
244 for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
245 printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
248 printf("audio: no audio streams in file.\n");
256 TranscodeFfmpeg::default_encoder_settings ()
260 ffs["-vcodec"] = "mpeg4";
261 ffs["-acodec"] = "ac3";
262 ffs["-b:v"] = "5000k";
263 ffs["-b:a"] = "160k";
268 TranscodeFfmpeg::default_meta_data ()
272 ffm["comment"] = "Created with ardour";
277 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
279 size_t start_pos = 0;
280 std::string v1 = value;
281 while((start_pos = v1.find_first_not_of(
282 "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
283 start_pos)) != std::string::npos)
285 v1.replace(start_pos, 1, "_");
290 while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
291 v1.replace(start_pos, 1, "\\\"");
295 size_t len = key.length() + v1.length() + 4;
296 char *mds = (char*) calloc(len, sizeof(char));
297 snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
302 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map)
304 #define MAX_FFMPEG_ENCODER_ARGS (100)
308 argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
309 argp[a++] = strdup(ffmpeg_exe.c_str());
310 if (m_avoffset < 0 || m_avoffset > 0) {
311 std::ostringstream osstream; osstream << m_avoffset;
312 argp[a++] = strdup("-itsoffset");
313 argp[a++] = strdup(osstream.str().c_str());
315 argp[a++] = strdup("-i");
316 argp[a++] = strdup(inf_v.c_str());
318 argp[a++] = strdup("-i");
319 argp[a++] = strdup(inf_a.c_str());
321 for(FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
322 argp[a++] = strdup(it->first.c_str());
323 argp[a++] = strdup(it->second.c_str());
325 for(FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
326 argp[a++] = strdup("-metadata");
327 argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
329 if (m_lead_in != 0 && m_lead_out != 0) {
330 std::ostringstream osstream;
331 argp[a++] = strdup("-vf");
332 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in << X_(" [pre]; ");
333 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << X_(" [post]; ");
334 osstream << X_("[pre] [in] [post] concat=n=3");
335 argp[a++] = strdup(osstream.str().c_str());
336 } else if (m_lead_in != 0) {
337 std::ostringstream osstream;
338 argp[a++] = strdup("-vf");
339 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in << X_(" [pre]; ");
340 osstream << X_("[pre] [in] concat=n=2");
341 argp[a++] = strdup(osstream.str().c_str());
342 } else if (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_out << X_(" [post]; ");
346 osstream << X_("[in] [post] concat=n=2");
347 argp[a++] = strdup(osstream.str().c_str());
351 std::ostringstream osstream;
352 argp[a++] = strdup("-map");
353 osstream << X_("0:") << m_videoidx;
354 argp[a++] = strdup(osstream.str().c_str());
355 argp[a++] = strdup("-map");
356 argp[a++] = strdup("1:0");
359 argp[a++] = strdup("-y");
360 argp[a++] = strdup(outfile.c_str());
362 assert(a<MAX_FFMPEG_ENCODER_ARGS);
363 /* Note: these are free()d in ~SystemExec */
365 if (debug_enable) { /* tentative debug mode */
366 printf("EXPORT ENCODE:\n");
367 for (int i=0; i< a; ++i) {
368 printf("%s ", argp[i]);
374 ffcmd = new SystemExec(ffmpeg_exe, argp);
375 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
376 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
377 if (ffcmd->start(2)) {
385 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
387 if (!probeok) return false;
388 if (stream >= m_audio.size()) return false;
393 argp=(char**) calloc(15,sizeof(char*));
394 argp[i++] = strdup(ffmpeg_exe.c_str());
395 argp[i++] = strdup("-i");
396 argp[i++] = strdup(infile.c_str());
397 #if 0 /* ffmpeg write original samplerate, use a3/SRC to resample */
398 argp[i++] = strdup("-ar");
399 argp[i] = (char*) calloc(7,sizeof(char)); snprintf(argp[i++], 7, "%"PRId64, samplerate);
401 argp[i++] = strdup("-ac");
402 argp[i] = (char*) calloc(3,sizeof(char)); snprintf(argp[i++], 3, "%i", m_audio.at(stream).channels);
403 argp[i++] = strdup("-map");
404 argp[i] = (char*) calloc(8,sizeof(char)); snprintf(argp[i++], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
405 argp[i++] = strdup("-vn");
406 argp[i++] = strdup("-acodec");
407 argp[i++] = strdup("pcm_f32le");
408 argp[i++] = strdup("-y");
409 argp[i++] = strdup(outfile.c_str());
410 argp[i++] = (char *)0;
411 /* Note: argp is free()d in ~SystemExec */
413 if (debug_enable) { /* tentative debug mode */
414 printf("EXTRACT AUDIO:\n");
415 for (int i=0; i< 14; ++i) {
416 printf("%s ", argp[i]);
422 ffcmd = new SystemExec(ffmpeg_exe, argp);
423 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
424 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
425 if (ffcmd->start(2)) {
434 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
436 if (!probeok) return false;
439 int bitrate = kbitps;
443 if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
444 if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
447 const double bitperpixel = .7; /* avg quality */
448 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
450 bitrate = bitrate / 10;
452 if (bitrate < 10) bitrate = 10;
453 if (bitrate > 1000) bitrate = 1000;
455 argp=(char**) calloc(16,sizeof(char*));
456 argp[0] = strdup(ffmpeg_exe.c_str());
457 argp[1] = strdup("-i");
458 argp[2] = strdup(infile.c_str());
459 argp[3] = strdup("-b:v");
460 argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
461 argp[5] = strdup("-s");
462 argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
463 argp[7] = strdup("-y");
464 argp[8] = strdup("-vcodec");
465 argp[9] = strdup("mpeg4");
466 argp[10] = strdup("-an");
467 argp[11] = strdup("-intra");
468 argp[12] = strdup("-g");
469 argp[13] = strdup("1");
470 argp[14] = strdup(outfile.c_str());
471 argp[15] = (char *)0;
472 /* Note: these are free()d in ~SystemExec */
474 if (debug_enable) { /* tentative debug mode */
475 printf("TRANSCODE VIDEO:\n");
476 for (int i=0; i< 15; ++i) {
477 printf("%s ", argp[i]);
482 ffcmd = new SystemExec(ffmpeg_exe, argp);
483 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
484 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
485 if (ffcmd->start(2)) {
493 TranscodeFfmpeg::cancel ()
495 if (!ffcmd || !ffcmd->is_running()) { return;}
496 ffcmd->write_to_stdin("q");
504 TranscodeFfmpeg::ffexit ()
508 Finished(); /* EMIT SIGNAL */
512 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
518 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
521 int h,m,s; char f[7];
522 ARDOUR::framecnt_t p = -1;
524 if (!(t=strstr(d.c_str(), "time="))) { return; }
526 if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
527 p = (ARDOUR::framecnt_t) floor( 100.0 * (
531 + atoi(f) / pow(10, strlen(f))
533 p = p * m_fps / 100.0;
534 if (p > m_duration ) { p = m_duration; }
535 Progress(p, m_duration); /* EMIT SIGNAL */
537 Progress(0, 0); /* EMIT SIGNAL */
542 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
544 if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
545 PBD::warning << "ffmpeg-error: " << d << endmsg;
547 if (strncmp(d.c_str(), "frame=",6)) {
550 d.erase(d.find_last_not_of(" \t\r\n") + 1);
551 printf("ffmpeg: '%s'\n", d.c_str());
554 Progress(0, 0); /* EMIT SIGNAL */
557 ARDOUR::framecnt_t f = atol(d.substr(6).c_str());
559 Progress(0, 0); /* EMIT SIGNAL */
561 Progress(f, m_duration); /* EMIT SIGNAL */