/* === S Y N F I G ========================================================= */
/*! \file mptr_ffmpeg.cpp
** \brief ppm Target Module
**
** $Id$
**
** \legal
** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
** Copyright (c) 2007 Chris Moore
**
** This package is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
** published by the Free Software Foundation; either version 2 of
** the License, or (at your option) any later version.
**
** This package is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
** \endlegal
**
** === N O T E S ===========================================================
**
** ========================================================================= */
/* === H E A D E R S ======================================================= */
#ifdef USING_PCH
# include "pch.h"
#else
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <ETL/stringf>
#include "mptr_ffmpeg.h"
#include <cstdio>
#include <sys/types.h>
#include <synfig/general.h>
#include <synfig/localization.h>
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#if HAVE_IO_H
#include <io.h>
#endif
#if HAVE_PROCESS_H
#include <process.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <unistd.h>
#include <iostream>
#include <algorithm>
#include <functional>
#include <ETL/stringf>
#endif
/* === M A C R O S ========================================================= */
using namespace synfig;
using namespace std;
using namespace etl;
#if defined(HAVE_FORK) && defined(HAVE_PIPE) && defined(HAVE_WAITPID)
#define UNIX_PIPE_TO_PROCESSES
#else
#define WIN32_PIPE_TO_PROCESSES
#endif
/* === G L O B A L S ======================================================= */
SYNFIG_IMPORTER_INIT(ffmpeg_mptr);
SYNFIG_IMPORTER_SET_NAME(ffmpeg_mptr,"ffmpeg");
SYNFIG_IMPORTER_SET_EXT(ffmpeg_mptr,"avi"); // not working look at the main.cpp for list opf available extensions
SYNFIG_IMPORTER_SET_VERSION(ffmpeg_mptr,"0.1");
SYNFIG_IMPORTER_SET_CVS_ID(ffmpeg_mptr,"$Id$");
SYNFIG_IMPORTER_SET_SUPPORTS_FILE_SYSTEM_WRAPPER(ffmpeg_mptr, false);
/* === M E T H O D S ======================================================= */
bool ffmpeg_mptr::is_animated()
{
return true;
}
bool
ffmpeg_mptr::seek_to(const Time& time)
{
//if(frame<cur_frame || !file)
//{
if(file)
{
#if defined(WIN32_PIPE_TO_PROCESSES)
pclose(file);
#elif defined(UNIX_PIPE_TO_PROCESSES)
fclose(file);
int status;
waitpid(pid,&status,0);
#endif
}
// FIXME: 24 fps is hardcoded now, but in fact we have to get it from canvas
//float position = (frame+1)/24; // ffmpeg didn't work with 0 frame
//float position = 1000/24; // ffmpeg didn't work with 0 frame
const char* position = time.get_string(Time::FORMAT_NORMAL).c_str();
#if defined(WIN32_PIPE_TO_PROCESSES)
string command;
String binary_path = synfig::get_binary_path("");
if (binary_path != "")
binary_path = etl::dirname(binary_path)+ETL_DIRECTORY_SEPARATOR;
binary_path += "ffmpeg.exe";
command=strprintf("\"%s\" -ss %s -i \"%s\" -vframes 1 -an -f image2pipe -vcodec ppm -\n", binary_path.c_str(), position, identifier.filename.c_str());
// This covers the dumb cmd.exe behavior.
// See: http://eli.thegreenplace.net/2011/01/28/on-spaces-in-the-paths-of-programs-and-files-on-windows/
command = "\"" + command + "\"";
file=popen(command.c_str(),POPEN_BINARY_READ_TYPE);
#elif defined(UNIX_PIPE_TO_PROCESSES)
int p[2];
if (pipe(p)) {
cerr<<"Unable to open pipe to ffmpeg (no pipe)"<<endl;
return false;
};
pid = fork();
if (pid == -1) {
cerr<<"Unable to open pipe to ffmpeg (pid == -1)"<<endl;
return false;
}
if (pid == 0){
// Child process
// Close pipein, not needed
close(p[0]);
// Dup pipein to stdout
if( dup2( p[1], STDOUT_FILENO ) == -1 ){
cerr<<"Unable to open pipe to ffmpeg (dup2( p[1], STDOUT_FILENO ) == -1)"<<endl;
return false;
}
// Close the unneeded pipein
close(p[1]);
/*std::string command = strprintf("\"%s\" -ss '%s' -i \"%s\" -vframes 1 -an -f image2pipe -vcodec ppm -\n", "ffmpeg", position2, identifier.filename.c_str());
synfig::warning("ffmpeg command: '%s'", command.c_str());*/
execlp("ffmpeg", "ffmpeg", "-ss", position, "-i", identifier.filename.c_str(), "-vframes", "1","-an", "-f", "image2pipe", "-vcodec", "ppm", "-", (const char *)NULL);
// We should never reach here unless the exec failed
cerr<<"Unable to open pipe to ffmpeg (exec failed)"<<endl;
_exit(1);
} else {
// Parent process
// Close pipeout, not needed
close(p[1]);
// Save pipein to file handle, will read from it later
file = fdopen(p[0], "rb");
}
#else
#error There are no known APIs for creating child processes
#endif
if(!file)
{
cerr<<"Unable to open pipe to ffmpeg"<<endl;
return false;
}
cur_frame=-1;
//}
//while(cur_frame<frame-1)
//{
// cerr<<"Seeking to..."<<frame<<'('<<cur_frame<<')'<<endl;
// if(!grab_frame())
// return false;
//}
return true;
}
bool
ffmpeg_mptr::grab_frame(void)
{
if(!file)
{
cerr<<"unable to open "<<identifier.filename.c_str()<<endl;
return false;
}
int w,h;
float divisor;
char cookie[2];
cookie[0]=fgetc(file);
if(feof(file))
return false;
cookie[1]=fgetc(file);
if(cookie[0]!='P' || cookie[1]!='6')
{
cerr<<"stream not in PPM format \""<<cookie[0]<<cookie[1]<<'"'<<endl;
return false;
}
fgetc(file);
fscanf(file,"%d %d\n",&w,&h);
fscanf(file,"%f",&divisor);
fgetc(file);
if(feof(file))
return false;
frame.set_wh(w, h);
const ColorReal k = 1/255.0;
for(int y = 0; y < frame.get_h(); ++y)
for(int x = 0; x < frame.get_w(); ++x)
{
if(feof(file))
return false;
ColorReal r = k*(unsigned char)fgetc(file);
ColorReal g = k*(unsigned char)fgetc(file);
ColorReal b = k*(unsigned char)fgetc(file);
frame[y][x] = Color(r, g, b);
}
cur_frame++;
return true;
}
ffmpeg_mptr::ffmpeg_mptr(const synfig::FileSystem::Identifier &identifier):
synfig::Importer(identifier)
{
pid=-1;
#ifdef HAVE_TERMIOS_H
tcgetattr (0, &oldtty);
#endif
file=NULL;
fps=23.98;
cur_frame=-1;
}
ffmpeg_mptr::~ffmpeg_mptr()
{
if(file)
{
#if defined(WIN32_PIPE_TO_PROCESSES)
pclose(file);
#elif defined(UNIX_PIPE_TO_PROCESSES)
fclose(file);
int status;
waitpid(pid,&status,0);
#endif
}
#ifdef HAVE_TERMIOS_H
tcsetattr(0,TCSANOW,&oldtty);
#endif
}
bool
ffmpeg_mptr::get_frame(synfig::Surface &surface, const synfig::RendDesc &/*renddesc*/, Time time, synfig::ProgressCallback *)
{
synfig::warning("time: %f", (float)time);
if(!seek_to(time))
return false;
if(!grab_frame())
return false;
surface=frame;
return true;
}