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 #if 1 /* tentative debug mode */
49 std::string ff_file_path;
50 if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffmpeg_harvid"), ff_file_path)) { ffmpeg_exe = ff_file_path; }
51 else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
52 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
54 else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
55 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
58 if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffprobe_harvid"), ff_file_path)) { ffprobe_exe = ff_file_path; }
59 else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
60 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
62 else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
63 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
66 if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
68 "No ffprobe or ffmpeg executables could be found on this system.\n"
69 "Video import and export is not possible until you install those tools.\n"
70 "Ardour requires ffmpeg and ffprobe from ffmpeg.org - version 1.1 or newer.\n"
72 "The tools are included with the Ardour releases from ardour.org "
73 "and also available with the video-server at http://x42.github.com/harvid/\n"
75 "Important: the files need to be installed in $PATH and named ffmpeg_harvid and ffprobe_harvid.\n"
76 "If you already have a suitable ffmpeg installation on your system, we recommend creating "
77 "symbolic links from ffmpeg to ffmpeg_harvid and from ffprobe to ffprobe_harvid.\n"
83 if (infile.empty() || !probe()) {
89 TranscodeFfmpeg::~TranscodeFfmpeg ()
95 TranscodeFfmpeg::probe ()
99 argp=(char**) calloc(7,sizeof(char*));
100 argp[0] = strdup(ffprobe_exe.c_str());
101 argp[1] = strdup("-print_format");
102 argp[2] = strdup("csv=nk=0");
103 argp[3] = strdup("-show_format");
104 argp[4] = strdup("-show_streams");
105 argp[5] = strdup(infile.c_str());
107 ffcmd = new SystemExec(ffprobe_exe, argp);
108 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffprobeparse, this, _1 ,_2));
109 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
110 if (ffcmd->start(1)) {
118 std::vector<std::vector<std::string> > lines;
119 ParseCSV(ffoutput, lines);
120 m_width = m_height = 0;
121 m_fps = m_aspect = 0;
126 #define PARSE_FRACTIONAL_FPS \
128 std::string::size_type pos; \
129 m_fps = atof(value.c_str()); \
130 pos = value.find_first_of('/'); \
131 if (pos != std::string::npos) { \
132 m_fps = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str()); \
136 for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
137 if (i->at(0) == X_("format")) {
138 /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
140 if (i->at(0) == X_("stream")) {
141 if (i->at(5) == X_("codec_type=video") && m_width == 0) {
143 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
144 const size_t kvsep = kv->find('=');
145 if(kvsep == std::string::npos) continue;
146 std::string key = kv->substr(0, kvsep);
147 std::string value = kv->substr(kvsep + 1);
149 if (key == X_("width")) {
150 m_width = atoi(value.c_str());
151 } else if (key == X_("height")) {
152 m_height = atoi(value.c_str());
153 } else if (key == X_("codec_name")) {
154 if (!m_codec.empty()) m_codec += " ";
156 } else if (key == X_("codec_long_name")) {
157 if (!m_codec.empty()) m_codec += " ";
158 m_codec += "[" + value + "]";
159 } else if (key == X_("codec_tag_string")) {
160 if (!m_codec.empty()) m_codec += " ";
161 m_codec += "(" + value + ")";
162 } else if (key == X_("r_frame_rate")) {
164 } else if (key == X_("time_base") && m_fps == 0) {
166 } else if (key == X_("timecode") && m_duration == 0) {
167 int h,m,s; char f[7];
168 if (sscanf(i->at(16).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
169 m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
173 + atoi(f) / pow(10, strlen(f))
176 } else if (key == X_("duration_ts")) {
177 m_duration = atof(value.c_str());
178 } else if (key == X_("duration") && m_duration == 0 && m_fps != 0) {
179 m_duration = atof(value.c_str()) * m_fps;
180 } else if (key == X_("display_aspect_ratio")) {
181 std::string::size_type pos;
182 pos = value.find_first_of(':');
183 if (pos != std::string::npos && atof(value.substr(pos+1).c_str()) != 0) {
184 m_aspect = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str());
190 m_aspect = (double)m_width / (double)m_height;
193 } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
195 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
196 const size_t kvsep = kv->find('=');
197 if(kvsep == std::string::npos) continue;
198 std::string key = kv->substr(0, kvsep);
199 std::string value = kv->substr(kvsep + 1);
201 if (key == X_("channels")) {
202 as.channels = atoi(value.c_str());
203 } else if (key == X_("index")) {
204 as.stream_id = value;
205 } else if (key == X_("codec_long_name")) {
206 if (!as.name.empty()) as.name += " ";
208 } else if (key == X_("codec_name")) {
209 if (!as.name.empty()) as.name += " ";
211 } else if (key == X_("sample_fmt")) {
212 if (!as.name.empty()) as.name += " ";
213 as.name += "FMT:" + value;
214 } else if (key == X_("sample_rate")) {
215 if (!as.name.empty()) as.name += " ";
216 as.name += "SR:" + value;
220 m_audio.push_back(as);
228 while (ffcmd && --timeout) usleep (1000); // wait until 'ffprobe' terminated.
229 if (timeout == 0) return false;
232 printf("FPS: %f\n", m_fps);
233 printf("Duration: %lu frames\n",(unsigned long)m_duration);
234 printf("W/H: %ix%i\n",m_width, m_height);
235 printf("aspect: %f\n",m_aspect);
236 printf("codec: %s\n",m_codec.c_str());
237 if (m_audio.size() > 0) {
238 for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
239 printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
242 printf("audio: no audio streams in file.\n");
250 TranscodeFfmpeg::default_encoder_settings ()
254 ffs["-vcodec"] = "mpeg4";
255 ffs["-acodec"] = "ac3";
262 TranscodeFfmpeg::default_meta_data ()
266 ffm["comment"] = "Created with ardour";
271 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
273 size_t start_pos = 0;
274 std::string v1 = value;
275 while((start_pos = v1.find_first_not_of(
276 "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
277 start_pos)) != std::string::npos)
279 v1.replace(start_pos, 1, "_");
284 while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
285 v1.replace(start_pos, 1, "\\\"");
289 size_t len = key.length() + v1.length() + 4;
290 char *mds = (char*) calloc(len, sizeof(char));
291 snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
296 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map)
298 #define MAX_FFMPEG_ENCODER_ARGS (100)
302 argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
303 argp[a++] = strdup(ffmpeg_exe.c_str());
304 if (m_avoffset < 0 || m_avoffset > 0) {
305 std::ostringstream osstream; osstream << m_avoffset;
306 argp[a++] = strdup("-itsoffset");
307 argp[a++] = strdup(osstream.str().c_str());
309 argp[a++] = strdup("-i");
310 argp[a++] = strdup(inf_v.c_str());
312 argp[a++] = strdup("-i");
313 argp[a++] = strdup(inf_a.c_str());
314 for(FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
315 argp[a++] = strdup(it->first.c_str());
316 argp[a++] = strdup(it->second.c_str());
318 for(FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
319 argp[a++] = strdup("-metadata");
320 argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
323 argp[a++] = strdup("-map");
324 argp[a++] = strdup("0:0");
325 argp[a++] = strdup("-map");
326 argp[a++] = strdup("1:0");
328 argp[a++] = strdup("-y");
329 argp[a++] = strdup(outfile.c_str());
331 assert(a<MAX_FFMPEG_ENCODER_ARGS);
332 /* Note: these are free()d in ~SystemExec */
334 if (debug_enable) { /* tentative debug mode */
335 printf("EXPORT ENCODE:\n");
336 for (int i=0; i< a; ++i) {
337 printf("%s ", argp[i]);
343 ffcmd = new SystemExec(ffmpeg_exe, argp);
344 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
345 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
346 if (ffcmd->start(2)) {
354 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
356 if (!probeok) return false;
357 if (stream >= m_audio.size()) return false;
361 argp=(char**) calloc(15,sizeof(char*));
362 argp[0] = strdup(ffmpeg_exe.c_str());
363 argp[1] = strdup("-i");
364 argp[2] = strdup(infile.c_str());
365 argp[3] = strdup("-ar");
366 argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%"PRId64, samplerate);
367 argp[5] = strdup("-ac");
368 argp[6] = (char*) calloc(3,sizeof(char)); snprintf(argp[6], 3, "%i", m_audio.at(stream).channels);
369 argp[7] = strdup("-map");
370 argp[8] = (char*) calloc(8,sizeof(char)); snprintf(argp[8], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
371 argp[9] = strdup("-vn");
372 argp[10] = strdup("-acodec");
373 argp[11] = strdup("pcm_f32le");
374 argp[12] = strdup("-y");
375 argp[13] = strdup(outfile.c_str());
376 argp[14] = (char *)0;
377 /* Note: argp is free()d in ~SystemExec */
379 if (debug_enable) { /* tentative debug mode */
380 printf("EXTRACT AUDIO:\n");
381 for (int i=0; i< 14; ++i) {
382 printf("%s ", argp[i]);
388 ffcmd = new SystemExec(ffmpeg_exe, argp);
389 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
390 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
391 if (ffcmd->start(2)) {
400 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
402 if (!probeok) return false;
405 int bitrate = kbitps;
409 if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
410 if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
413 const double bitperpixel = .7; /* avg quality */
414 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
416 bitrate = bitrate / 10;
418 if (bitrate < 10) bitrate = 10;
419 if (bitrate > 1000) bitrate = 1000;
421 argp=(char**) calloc(16,sizeof(char*));
422 argp[0] = strdup(ffmpeg_exe.c_str());
423 argp[1] = strdup("-i");
424 argp[2] = strdup(infile.c_str());
425 argp[3] = strdup("-b");
426 argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
427 argp[5] = strdup("-s");
428 argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
429 argp[7] = strdup("-y");
430 argp[8] = strdup("-vcodec");
431 argp[9] = strdup("mjpeg");
432 argp[10] = strdup("-an");
433 argp[11] = strdup("-intra");
434 argp[12] = strdup("-g");
435 argp[13] = strdup("1");
436 argp[14] = strdup(outfile.c_str());
437 argp[15] = (char *)0;
438 /* Note: these are free()d in ~SystemExec */
440 if (debug_enable) { /* tentative debug mode */
441 printf("TRANSCODE VIDEO:\n");
442 for (int i=0; i< 15; ++i) {
443 printf("%s ", argp[i]);
448 ffcmd = new SystemExec(ffmpeg_exe, argp);
449 ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
450 ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
451 if (ffcmd->start(2)) {
459 TranscodeFfmpeg::cancel ()
461 if (!ffcmd || !ffcmd->is_running()) { return;}
462 ffcmd->write_to_stdin("q");
470 TranscodeFfmpeg::ffexit ()
474 Finished(); /* EMIT SIGNAL */
478 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
484 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
487 if (!(t=strstr(d.c_str(), "time="))) { return; }
488 ARDOUR::framecnt_t f = (ARDOUR::framecnt_t) floorf (atof(t+5) * m_fps);
489 if (f > m_duration ) { f = m_duration; }
490 Progress(f, m_duration); /* EMIT SIGNAL */
494 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
496 if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
497 PBD::warning << "ffmpeg-error: " << d << endmsg;
499 if (strncmp(d.c_str(), "frame=",6)) {
502 d.erase(d.find_last_not_of(" \t\r\n") + 1);
503 printf("ffmpeg: '%s'\n", d.c_str());
508 ARDOUR::framecnt_t f = atol(d.substr(6).c_str());
509 Progress(f, m_duration); /* EMIT SIGNAL */
512 #endif /* WITH_VIDEOTIMELINE */