I need to decode rtsp stream from Ip camera using ffmpeg, below is the code for decoder
ffmpeg_decoder.h
class ffmpeg_decoder
{
public:
ffmpeg_decoder();
int initial(QString & url);
int h264Decodec();
void close_stream();
virtual ~ffmpeg_decoder();
AVPicture picture;
int width;
int height;
QMutex mutex;
QImage imageDecoded;
private:
AVFormatContext *pFormatCtx;
AVCodecContext *pCodecCtx;
AVFrame *pFrame;
AVPacket packet;
SwsContext * pSwsCtx;
int videoStream;
QString rtspURL;
};
ffmpeg_decoder.cpp
ffmpeg_decoder::ffmpeg_decoder()
{
pCodecCtx = NULL;
videoStream=-1;
}
ffmpeg_decoder::~ffmpeg_decoder()
{
sws_freeContext(pSwsCtx);
}
int ffmpeg_decoder::initial(QString & url)
{
int err;
rtspURL=url;
AVCodec *pCodec;
av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
pFrame = av_frame_alloc();
err = avformat_open_input(&pFormatCtx, rtspURL.toStdString().c_str(), NULL,
NULL);
if (err < 0)
{
printf("Can not open this file");
return -1;
}
if (avformat_find_stream_info(pFormatCtx,NULL) < 0)
{
printf("Unable to get stream info");
return -1;
}
int i = 0;
videoStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
break;
}
}
if (videoStream == -1)
{
printf("Unable to find video stream");
return -1;
}
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
width=pCodecCtx->width;
height=pCodecCtx->height;
avpicture_alloc(&picture,PIX_FMT_RGB24,pCodecCtx->width,pCodecCtx->height);
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
pSwsCtx = sws_getContext(width, height, PIX_FMT_YUV420P, width,
height, PIX_FMT_RGB24,
SWS_BICUBIC, 0, 0, 0);
if (pCodec == NULL)
{
printf("Unsupported codec");
return -1;
}
printf("video size : width=%d height=%d \n", pCodecCtx->width,
pCodecCtx->height);
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("Unable to open codec");
return -1;
}
printf("initial successfully");
return 0;
}
int ffmpeg_decoder::h264Decodec()
{
int frameFinished=0;
// while (av_read_frame(pFormatCtx, &packet) >= 0)
if(av_read_frame(pFormatCtx, &packet) >= 0)
{
if(packet.stream_index==videoStream)
{
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if (frameFinished)
{
printf("***************ffmpeg decodec*******************\n");
mutex.lock();
int rs = sws_scale(pSwsCtx, (const uint8_t* const *) pFrame->data,
pFrame->linesize, 0,
height, picture.data, picture.linesize);
imageDecoded = QImage();
imageDecoded= QImage(this->picture.data[0],this->width,this->height,QImage::Format_RGB888);
//imageDecoded = imageDecoded.copy();
mutex.unlock();
if (rs == -1)
{
printf("__________Can open to change to des imag_____________e\n");
return -1;
}
}
}
}
av_free_packet(&packet);
av_frame_unref(pFrame);
av_packet_unref(&packet);
avpicture_free(&picture);
return 1;
}
void ffmpeg_decoder::close_stream(){
/*if (pFrame)
av_free(&pFrame);*/
if (pCodecCtx)
avcodec_close(pCodecCtx);
if (pSwsCtx)
sws_freeContext(pSwsCtx);
avpicture_free(&picture);
if (pFormatCtx)
avformat_close_input(&pFormatCtx);
}
Below is the main thread which do the decoding.
I am using Qt for creating thread and do decoding
ffmpeg_decoder * ffmpeg = new ffmpeg_decoder();;
if(ffmpeg->initial(rtspURL)==0){
while (1) {
ffmpeg->h264Decodec();
//get frame and do processing right now it disabled, and still see the memory leak.
.......
if(stopFlg.load()==1)
break;
}
//close stream if break
ffmpeg->close_stream();
}
else {
ffmpeg->close_stream();
}
When I run 36 thread with different URL I can see the memory usage of the program increase over time.
I have used valgrind to detect the leak, and here is the relevant part of the log
This is the first memory leak location
=14402== by 0x692017F: av_malloc (in /usr/lib/x86_64-linux-gnu/libavutil-ffmpeg.so.54.31.100)
==14402== by 0x692048D: av_mallocz (in /usr/lib/x86_64-linux-gnu/libavutil-ffmpeg.so.54.31.100)
==14402== by 0x691915E: av_frame_alloc (in /usr/lib/x86_64-linux-gnu/libavutil-ffmpeg.so.54.31.100)
==14402== by 0x419663: ffmpeg_decoder::initial(QString&) (ffmpeg_decoder.cpp:24)
==14402== by 0x41ABEC: RTSP_Player_Object::run() (rtsp_player_object.cpp:15)
Another
==14402== 2,176 bytes in 16 blocks are indirectly lost in loss record 23,113 of 23,379
==14402== at 0x4C2E0EF: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14402== by 0x7780A4E: QImageData::create(unsigned char*, int, int, int, QImage::Format, bool, void (*)(void*), void*) (in /home/vapplica/Qt5.11.1/5.11.1/gcc_64/lib/libQt5Gui.so.5.11.1)
==14402== by 0x7780C30: QImage::QImage(unsigned char*, int, int, QImage::Format, void (*)(void*), void*) (in /home/vapplica/Qt5.11.1/5.11.1/gcc_64/lib/libQt5Gui.so.5.11.1)
==14402== by 0x419B21: ffmpeg_decoder::h264Decodec() (ffmpeg_decoder.cpp:96)
I have check the documentation and sample on ffmpeg site, and I think I am releasing the allocated memory, but still I can see the memory leak when I run the program.
Trying my hands on libgphoto2 library examples and while going through simple-capture.c file. Can i download foo.jpg captured image to a specified folder on my computer?
As far as i understood, in capture_to_file() camera_file_path.folder is the folder in which the file can be found on the camera. So open() should specify the host(computer) location. But nothing worked, i get following error:
You need to specify a folder starting with /store_xxxxxxxxx/
Am i missing something here? Any help would be appreciated, thanks!
I got this working.
Written small application for multiple cameras. Including main() for about question.
int main(int argc, char **argv)
{
CameraList *list;
Camera **cams;
int retval, count, i;
GPContext *context;
FILE *f;
char *data;
unsigned long size;
const char *name, *value;
/*
* Create context
*/
context = sample_create_context();
/*
* Setup Images DB directory.
*/
char* home = getenv("HOME");
if (home == NULL)
{
printf("Error: Unable to fetch home env! \n");
exit(1);
}
char* path = "/Desktop/mw/";
size_t len = strlen(home) + strlen(path) + 1;
char* imgdb = malloc(len);
if (imgdb == NULL)
{
printf("Error: Unable to malloc(). \n");
exit(1);
}
strcpy(imgdb, home);
strcat(imgdb, path);
directory_exists_or_create(imgdb);
/*
* Logs
*/
gp_log_add_func(GP_LOG_ERROR, errordumper, NULL);
/*
* Detect all the cameras that can be autodetected
*/
retval = gp_list_new(&list);
if (retval < GP_OK)
{
printf("Unable to create camera list.\n");
return 1;
}
count = sample_autodetect(list, context);
if (count < GP_OK)
{
printf("No cameras detected.\n");
return 1;
}
/*
* Now open all the cameras we autodetected for usage.
*/
printf("Number of cameras: %d\n", count);
cams = calloc(sizeof(Camera*), count);
for (i = 0; i < count; i++)
{
gp_list_get_name(list, i, &name);
gp_list_get_value(list, i, &value);
retval = sample_open_camera(&cams[i], name, value, context);
if (retval < GP_OK)
{
fprintf(stderr, "Camera %s on port %s failed to open\n", name, value);
}
}
if (argc > 0)
{
while ((++argv)[0])
{
if (argv[0][0] == '-')
{
switch (argv[0][1])
{
case 'h':
case 'H':
{
/* Now call a simple function in each of those cameras. */
for (i = 0; i < count; i++)
{
CameraText text;
char *owner;
retval = gp_camera_get_summary (cams[i], &text, context);
if (retval < GP_OK)
{
fprintf (stderr, "Failed to get summary.\n");
continue;
}
gp_list_get_name (list, i, &name);
gp_list_get_value (list, i, &value);
printf("%-30s %-16s\n", name, value);
printf("Summary:\n%s\n", text.text);
/* Query a simple string configuration variable. */
retval = get_config_value_string (cams[i], "owner", &owner, context);
if (retval >= GP_OK)
{
printf("Owner: %s\n", owner);
free (owner);
}
else
{
printf("Owner: No owner found.\n");
}
}
}
/* Graceful exit from the program */
goto exit_;;
default:
printf("Unknown option -%c\n\n", argv[0][1]);
break;
}
}
}
}
/* When I set GP_LOG_DEBUG instead of GP_LOG_ERROR above, I noticed that the
* init function seems to traverse the entire filesystem on the camera. This
* is partly why it takes so long.
* (Marcus: the ptp2 driver does this by default currently.)
*/
printf("Cameras init. Takes about 10 seconds each.\n");
for (i = 0; i < count; i++)
{
retval = gp_camera_init(cams[i], context);
if (retval != GP_OK)
{
printf(" Camera [%d] init failed with retval %d\n", i, retval);
exit (1);
}
}
printf(" ----------------\n");
printf(" Sampler is ready \n");
printf(" ----------------\n");
printf("Usage : \n");
printf(" ESC - Exit the program\n");
printf(" i/I - Insert new product barcode manually\n");
#if defined(BARCODE_ENABLED)
printf(" b/B - Insert new product barcode using barcode-scanner\n");
#endif
char get_key;
char exit_key = 0;
char bcr_buf[128] = {0};
int hemispheres_counts = 0;
int rotar_steps = 0;
do
{
get_key = getchar();
switch (get_key)
{
// Gracefull Exit
case _ESC_:
exit_key = 1;
break;
// Manual insert mode
case 'i':
case 'I':
printf("ACTION: Type in the name.\n");
scanf("%128s", bcr_buf);
process:
press_enter();
printf("ACTION: Shall we start? press return key.\n");
press_enter();
hemispheres_counts = 0;
rotar_steps = 0;
char product_filename[256] = {0};
strcpy(product_filename, imgdb);
strcat(product_filename, bcr_buf);
if (directory_exists_or_create(product_filename))
{
printf("\n\n!!! ATTENTION: The product already exists !!!\n\n");
printf("\nEnter options:\n");
printf(" ESC - Exit the program\n");
printf(" i/I - Insert new product barcode manually\n");
#if defined(BARCODE_ENABLED)
printf(" b/B - Insert new product barcode using barcode-scanner\n");
#endif
break;
}
while (hemispheres_counts < MAX_HEMISPHERES)
{
while (rotar_steps < MAX_ROTAR_STEPS)
{
for (i = 0; i < count; i++)
{
capture_to_memory(cams[i], context, (const char**)&data, &size);
char fname[64] = {0};
char mk_filename[256] = {0};
strcpy(mk_filename, product_filename);
snprintf(fname, sizeof(fname), "/%d-%d-%d.jpg", i, hemispheres_counts, rotar_steps);
strcat(mk_filename, fname);
printf("file name %s\n", mk_filename);
f = fopen(mk_filename, "wb");
if (f)
{
retval = fwrite (data, size, 1, f);
if (retval != size)
{
printf(" fwrite size %ld, written %d\n", size, retval);
}
fclose(f);
}
else
{
printf(" fopen *.jpg failed. %s\n", strerror(errno));
}
usleep(500*1000);
}
rotar_steps++;
}
rotar_steps = 0;
hemispheres_counts++;
if (hemispheres_counts < MAX_HEMISPHERES)
{
printf("Flip the product and hit 'RETURN' key\n");
press_enter(); // This expect some input from user, thats it.
printf("Started capturing other hemisphere!\n");
} else {
printf("Sampling Done for barcode: %s\n", bcr_buf);
printf(" -------------------------------------\n");
printf("\nEnter options:\n");
printf(" ESC - Exit the program\n");
printf(" i/I - Insert new product barcode manually\n");
#if defined(BARCODE_ENABLED)
printf(" b/B - Insert new product barcode using barcode-scanner\n");
#endif
break;
}
}
break;
}
} while (exit_key != 1);
exit_:
/*
* Release all the resources.
*/
printf("\nReleasing all the resources ... \n");
for (i = 0; i < count; i++)
{
gp_camera_exit(cams[i], context);
}
if (cams) {
free(cams);
}
free(imgdb);
#if defined(BARCODE_ENABLED)
close_bcr();
#endif
printf("Done.\n");
return 0;
}
I have updated the latest code from SVN and complie CutyCapt with Qt SDK 4.8.
And I have static-linked libeay32.dll / ssleay32.dll in the same location of the binary.
It works fine if I launch the CutyCapt process in command window.
It works fine if I launch the CutyCapt process from ASP.NET (w3wp) to capture HTTP web page.
it does NOT work if I launch the CutyCapt process from ASP.NET (w3wp) to capture HTTPS web page, always generates a blank image.
I doubted it was because the libeay32.dll / ssleay32.dll dependencies are not loaded, so I copied these 2 dll to system32 / SysWOW64, this does not resolve my problem.
Then I monitor the process by ProcessMonitor, it shows me that libeay32.dll / ssleay32.dll are loaded successfully.
So, the dependencies are not the reason.
Here is my C# code to launch the CutyCapt,
using (Process process = new Process())
{
process.StartInfo.FileName = ti.ExeFile;
process.StartInfo.ErrorDialog = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(ti.ExeFile);
process.StartInfo.UseShellExecute = false;
process.StartInfo.Arguments = string.Format(#"--min-width=1024 --delay=1500 --javascript=off --auto-load-images=on --plugins=off --java=off --url={0} --out={1}"
, ti.Url
, destFile
);
process.Start();
process.WaitForExit();
process.Close();
}
Does anyone knows how to allow CutyCapt works from w3wp?
SOLUTION:
I have resolved this problem by modifying the CutyCapt source.
The idea is that, get the HTML of the url then set the HTML to the WebKit.
CutyCapt.hpp
#include <QtWebKit>
class CutyCapt;
class CutyPage : public QWebPage {
Q_OBJECT
public:
void setAttribute(QWebSettings::WebAttribute option, const QString& value);
void setUserAgent(const QString& userAgent);
void setAlertString(const QString& alertString);
void setPrintAlerts(bool printAlerts);
void setCutyCapt(CutyCapt* cutyCapt);
QString getAlertString();
protected:
QString chooseFile(QWebFrame *frame, const QString& suggestedFile);
void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID);
bool javaScriptPrompt(QWebFrame* frame, const QString& msg, const QString& defaultValue, QString* result);
void javaScriptAlert(QWebFrame* frame, const QString& msg);
bool javaScriptConfirm(QWebFrame* frame, const QString& msg);
QString userAgentForUrl(const QUrl& url) const;
QString mUserAgent;
QString mAlertString;
bool mPrintAlerts;
CutyCapt* mCutyCapt;
};
class CutyCapt : public QObject {
Q_OBJECT
public:
QString mUrl;
// TODO: This should really be elsewhere and be named differently
enum OutputFormat { SvgFormat, PdfFormat, PsFormat, InnerTextFormat, HtmlFormat,
RenderTreeFormat, PngFormat, JpegFormat, MngFormat, TiffFormat, GifFormat,
BmpFormat, PpmFormat, XbmFormat, XpmFormat, OtherFormat };
CutyCapt(CutyPage* page,
const QString& output,
int delay,
OutputFormat format,
const QString& scriptProp,
const QString& scriptCode);
private slots:
void HtmlDownloadFinished(QNetworkReply * reply);
void HtmlDownloadError(QNetworkReply::NetworkError code);
void DocumentComplete(bool ok);
void InitialLayoutCompleted();
void JavaScriptWindowObjectCleared();
void Timeout();
void Delayed();
private:
void TryDelayedRender();
void saveSnapshot();
bool mSawInitialLayout;
bool mSawDocumentComplete;
protected:
QString mOutput;
int mDelay;
CutyPage* mPage;
OutputFormat mFormat;
QObject* mScriptObj;
QString mScriptProp;
QString mScriptCode;
};
CutyCapt.cpp
////////////////////////////////////////////////////////////////////
//
// CutyCapt - A Qt WebKit Web Page Rendering Capture Utility
//
// Copyright (C) 2003-2010 Bjoern Hoehrmann <bjoern#hoehrmann.de>
//
// This program 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 program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This program 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.
//
// $Id: CutyCapt.cpp 7 2011-11-30 10:56:34Z hoehrmann $
//
////////////////////////////////////////////////////////////////////
#include <QApplication>
#include <QtWebKit>
#include <QtGui>
#include <QSvgGenerator>
#include <QPrinter>
#include <QTimer>
#include <QByteArray>
#include <QNetworkRequest>
#include <QtCore>
#include "CutyCapt.hpp"
#if QT_VERSION >= 0x040600 && 0
#define CUTYCAPT_SCRIPT 1
#endif
#ifdef STATIC_PLUGINS
Q_IMPORT_PLUGIN(qjpeg)
Q_IMPORT_PLUGIN(qgif)
Q_IMPORT_PLUGIN(qtiff)
Q_IMPORT_PLUGIN(qsvg)
Q_IMPORT_PLUGIN(qmng)
Q_IMPORT_PLUGIN(qico)
#endif
static struct _CutyExtMap {
CutyCapt::OutputFormat id;
const char* extension;
const char* identifier;
} const CutyExtMap[] = {
{ CutyCapt::SvgFormat, ".svg", "svg" },
{ CutyCapt::PdfFormat, ".pdf", "pdf" },
{ CutyCapt::PsFormat, ".ps", "ps" },
{ CutyCapt::InnerTextFormat, ".txt", "itext" },
{ CutyCapt::HtmlFormat, ".html", "html" },
{ CutyCapt::RenderTreeFormat, ".rtree", "rtree" },
{ CutyCapt::JpegFormat, ".jpeg", "jpeg" },
{ CutyCapt::PngFormat, ".png", "png" },
{ CutyCapt::MngFormat, ".mng", "mng" },
{ CutyCapt::TiffFormat, ".tiff", "tiff" },
{ CutyCapt::GifFormat, ".gif", "gif" },
{ CutyCapt::BmpFormat, ".bmp", "bmp" },
{ CutyCapt::PpmFormat, ".ppm", "ppm" },
{ CutyCapt::XbmFormat, ".xbm", "xbm" },
{ CutyCapt::XpmFormat, ".xpm", "xpm" },
{ CutyCapt::OtherFormat, "", "" }
};
QString
CutyPage::chooseFile(QWebFrame* /*frame*/, const QString& /*suggestedFile*/) {
return QString::null;
}
bool
CutyPage::javaScriptConfirm(QWebFrame* /*frame*/, const QString& /*msg*/) {
return true;
}
bool
CutyPage::javaScriptPrompt(QWebFrame* /*frame*/,
const QString& /*msg*/,
const QString& /*defaultValue*/,
QString* /*result*/) {
return true;
}
void
CutyPage::javaScriptConsoleMessage(const QString& /*message*/,
int /*lineNumber*/,
const QString& /*sourceID*/) {
// noop
}
void
CutyPage::javaScriptAlert(QWebFrame* /*frame*/, const QString& msg) {
if (mPrintAlerts)
qDebug() << "[alert]" << msg;
if (mAlertString == msg) {
QTimer::singleShot(10, mCutyCapt, SLOT(Delayed()));
}
}
QString
CutyPage::userAgentForUrl(const QUrl& url) const {
if (!mUserAgent.isNull())
return mUserAgent;
return QWebPage::userAgentForUrl(url);
}
void
CutyPage::setUserAgent(const QString& userAgent) {
mUserAgent = userAgent;
}
void
CutyPage::setAlertString(const QString& alertString) {
mAlertString = alertString;
}
QString
CutyPage::getAlertString() {
return mAlertString;
}
void
CutyPage::setCutyCapt(CutyCapt* cutyCapt) {
mCutyCapt = cutyCapt;
}
void
CutyPage::setPrintAlerts(bool printAlerts) {
mPrintAlerts = printAlerts;
}
void
CutyPage::setAttribute(QWebSettings::WebAttribute option,
const QString& value) {
if (value == "on")
settings()->setAttribute(option, true);
else if (value == "off")
settings()->setAttribute(option, false);
else
(void)0; // TODO: ...
}
// TODO: Consider merging some of main() and CutyCap
CutyCapt::CutyCapt(CutyPage* page, const QString& output, int delay, OutputFormat format,
const QString& scriptProp, const QString& scriptCode) {
mPage = page;
mOutput = output;
mDelay = delay;
mSawInitialLayout = false;
mSawDocumentComplete = false;
mFormat = format;
mScriptProp = scriptProp;
mScriptCode = scriptCode;
mScriptObj = new QObject();
// This is not really nice, but some restructuring work is
// needed anyway, so this should not be that bad for now.
mPage->setCutyCapt(this);
}
void
CutyCapt::InitialLayoutCompleted() {
mSawInitialLayout = true;
if (mSawInitialLayout && mSawDocumentComplete)
TryDelayedRender();
}
void
CutyCapt::DocumentComplete(bool /*ok*/) {
mSawDocumentComplete = true;
if (mSawInitialLayout && mSawDocumentComplete)
TryDelayedRender();
}
void
CutyCapt::JavaScriptWindowObjectCleared() {
if (!mScriptProp.isEmpty()) {
QVariant var = mPage->mainFrame()->evaluateJavaScript(mScriptProp);
QObject* obj = var.value<QObject*>();
if (obj == mScriptObj)
return;
mPage->mainFrame()->addToJavaScriptWindowObject(mScriptProp, mScriptObj);
}
mPage->mainFrame()->evaluateJavaScript(mScriptCode);
}
void CutyCapt::HtmlDownloadFinished(QNetworkReply * reply)
{
QVariant statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
qDebug() << statusCode;
if ( !statusCode.isValid() )
{
QApplication::exit();
return;
}
int status = statusCode.toInt();
if( status == 301 || status == 302 )
{
QVariant loc = reply->header(QNetworkRequest::LocationHeader);
qDebug() << "Location :" << loc.toString();
QApplication::exit();
return;
}
if ( status != 200 )
{
QString reason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();
qDebug() << reason;
QApplication::exit();
return;
}
QString html=QString::fromUtf8(reply->readAll());
mPage->mainFrame()->setHtml( html, QUrl(mUrl) );
}
void CutyCapt::HtmlDownloadError(QNetworkReply::NetworkError code)
{
QApplication::exit();
}
void
CutyCapt::TryDelayedRender() {
if (!mPage->getAlertString().isEmpty())
return;
if (mDelay > 0) {
QTimer::singleShot(mDelay, this, SLOT(Delayed()));
return;
}
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::Timeout() {
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::Delayed() {
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::saveSnapshot() {
QWebFrame *mainFrame = mPage->mainFrame();
QPainter painter;
const char* format = NULL;
for (int ix = 0; CutyExtMap[ix].id != OtherFormat; ++ix)
if (CutyExtMap[ix].id == mFormat)
format = CutyExtMap[ix].identifier; //, break;
// TODO: sometimes contents/viewport can have size 0x0
// in which case saving them will fail. This is likely
// the result of the method being called too early. So
// far I've been unable to find a workaround, except
// using --delay with some substantial wait time. I've
// tried to resize multiple time, make a fake render,
// check for other events... This is primarily a problem
// under my Ubuntu virtual machine.
mPage->setViewportSize( mainFrame->contentsSize() );
switch (mFormat) {
case SvgFormat: {
QSvgGenerator svg;
svg.setFileName(mOutput);
svg.setSize(mPage->viewportSize());
painter.begin(&svg);
mainFrame->render(&painter);
painter.end();
break;
}
case PdfFormat:
case PsFormat: {
QPrinter printer;
printer.setPageSize(QPrinter::A4);
printer.setOutputFileName(mOutput);
// TODO: change quality here?
mainFrame->print(&printer);
break;
}
case RenderTreeFormat:
case InnerTextFormat:
case HtmlFormat: {
QFile file(mOutput);
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream s(&file);
s.setCodec("utf-8");
s << (mFormat == RenderTreeFormat ? mainFrame->renderTreeDump() :
mFormat == InnerTextFormat ? mainFrame->toPlainText() :
mFormat == HtmlFormat ? mainFrame->toHtml() :
"bug");
break;
}
default: {
QImage image(mPage->viewportSize(), QImage::Format_ARGB32);
painter.begin(&image);
mainFrame->render(&painter);
painter.end();
// TODO: add quality
image.save(mOutput, format);
}
};
}
void
CaptHelp(void) {
printf("%s",
" -----------------------------------------------------------------------------\n"
" Usage: CutyCapt --url=http://www.example.org/ --out=localfile.png \n"
" -----------------------------------------------------------------------------\n"
" --help Print this help page and exit \n"
" --url=<url> The URL to capture (http:...|file:...|...) \n"
" --out=<path> The target file (.png|pdf|ps|svg|jpeg|...) \n"
" --out-format=<f> Like extension in --out, overrides heuristic \n"
// " --out-quality=<int> Output format quality from 1 to 100 \n"
" --min-width=<int> Minimal width for the image (default: 800) \n"
" --min-height=<int> Minimal height for the image (default: 600) \n"
" --max-wait=<ms> Don't wait more than (default: 90000, inf: 0)\n"
" --delay=<ms> After successful load, wait (default: 0) \n"
// " --user-styles=<url> Location of user style sheet (deprecated) \n"
" --user-style-path=<path> Location of user style sheet file, if any \n"
" --user-style-string=<css> User style rules specified as text \n"
" --header=<name>:<value> request header; repeatable; some can't be set\n"
" --method=<get|post|put> Specifies the request method (default: get) \n"
" --body-string=<string> Unencoded request body (default: none) \n"
" --body-base64=<base64> Base64-encoded request body (default: none) \n"
" --app-name=<name> appName used in User-Agent; default is none \n"
" --app-version=<version> appVers used in User-Agent; default is none \n"
" --user-agent=<string> Override the User-Agent header Qt would set \n"
" --javascript=<on|off> JavaScript execution (default: on) \n"
" --java=<on|off> Java execution (default: unknown) \n"
" --plugins=<on|off> Plugin execution (default: unknown) \n"
" --private-browsing=<on|off> Private browsing (default: unknown) \n"
" --auto-load-images=<on|off> Automatic image loading (default: on) \n"
" --js-can-open-windows=<on|off> Script can open windows? (default: unknown) \n"
" --js-can-access-clipboard=<on|off> Script clipboard privs (default: unknown)\n"
#if QT_VERSION >= 0x040500
" --print-backgrounds=<on|off> Backgrounds in PDF/PS output (default: off) \n"
" --zoom-factor=<float> Page zoom factor (default: no zooming) \n"
" --zoom-text-only=<on|off> Whether to zoom only the text (default: off) \n"
" --http-proxy=<url> Address for HTTP proxy server (default: none)\n"
#endif
#if CUTYCAPT_SCRIPT
" --inject-script=<path> JavaScript that will be injected into pages \n"
" --script-object=<string> Property to hold state for injected script \n"
" --expect-alert=<string> Try waiting for alert(string) before capture \n"
" --debug-print-alerts Prints out alert(...) strings for debugging. \n"
#endif
" -----------------------------------------------------------------------------\n"
" <f> is svg,ps,pdf,itext,html,rtree,png,jpeg,mng,tiff,gif,bmp,ppm,xbm,xpm \n"
" -----------------------------------------------------------------------------\n"
#if CUTYCAPT_SCRIPT
" The `inject-script` option can be used to inject script code into loaded web \n"
" pages. The code is called whenever the `javaScriptWindowObjectCleared` signal\n"
" is received. When `script-object` is set, an object under the specified name \n"
" will be available to the script to maintain state across page loads. When the\n"
" `expect-alert` option is specified, the shot will be taken when a script in- \n"
" vokes alert(string) with the string if that happens before `max-wait`. These \n"
" options effectively allow you to remote control the browser and the web page.\n"
" This an experimental and easily abused and misused feature. Use with caution.\n"
" -----------------------------------------------------------------------------\n"
#endif
" http://cutycapt.sf.net - (c) 2003-2010 Bjoern Hoehrmann - bjoern#hoehrmann.de\n"
"");
}
int
main(int argc, char *argv[]) {
int argHelp = 0;
int argDelay = 0;
int argSilent = 0;
int argMinWidth = 800;
int argMinHeight = 600;
int argMaxWait = 90000;
int argVerbosity = 0;
const char* argUrl = NULL;
const char* argUserStyle = NULL;
const char* argUserStylePath = NULL;
const char* argUserStyleString = NULL;
const char* argIconDbPath = NULL;
const char* argInjectScript = NULL;
const char* argScriptObject = NULL;
QString argOut;
CutyCapt::OutputFormat format = CutyCapt::OtherFormat;
QApplication app(argc, argv, true);
CutyPage page;
QNetworkAccessManager::Operation method =
QNetworkAccessManager::GetOperation;
QByteArray body;
QNetworkRequest req;
QNetworkAccessManager manager;
// Parse command line parameters
for (int ax = 1; ax < argc; ++ax) {
size_t nlen;
const char* s = argv[ax];
const char* value;
// boolean options
if (strcmp("--silent", s) == 0) {
argSilent = 1;
continue;
} else if (strcmp("--help", s) == 0) {
argHelp = 1;
break;
} else if (strcmp("--verbose", s) == 0) {
argVerbosity++;
continue;
#if CUTYCAPT_SCRIPT
} else if (strcmp("--debug-print-alerts", s) == 0) {
page.setPrintAlerts(true);
continue;
#endif
}
value = strchr(s, '=');
if (value == NULL) {
// TODO: error
argHelp = 1;
break;
}
nlen = value++ - s;
// --name=value options
if (strncmp("--url", s, nlen) == 0) {
argUrl = value;
} else if (strncmp("--min-width", s, nlen) == 0) {
// TODO: add error checking here?
argMinWidth = (unsigned int)atoi(value);
} else if (strncmp("--min-height", s, nlen) == 0) {
// TODO: add error checking here?
argMinHeight = (unsigned int)atoi(value);
} else if (strncmp("--delay", s, nlen) == 0) {
// TODO: see above
argDelay = (unsigned int)atoi(value);
} else if (strncmp("--max-wait", s, nlen) == 0) {
// TODO: see above
argMaxWait = (unsigned int)atoi(value);
} else if (strncmp("--out", s, nlen) == 0) {
argOut = value;
if (format == CutyCapt::OtherFormat)
for (int ix = 0; CutyExtMap[ix].id != CutyCapt::OtherFormat; ++ix)
if (argOut.endsWith(CutyExtMap[ix].extension))
format = CutyExtMap[ix].id; //, break;
} else if (strncmp("--user-styles", s, nlen) == 0) {
// This option is provided for backwards-compatibility only
argUserStyle = value;
} else if (strncmp("--user-style-path", s, nlen) == 0) {
argUserStylePath = value;
} else if (strncmp("--user-style-string", s, nlen) == 0) {
argUserStyleString = value;
} else if (strncmp("--icon-database-path", s, nlen) == 0) {
argIconDbPath = value;
} else if (strncmp("--auto-load-images", s, nlen) == 0) {
page.setAttribute(QWebSettings::AutoLoadImages, value);
} else if (strncmp("--javascript", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptEnabled, value);
} else if (strncmp("--java", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavaEnabled, value);
} else if (strncmp("--plugins", s, nlen) == 0) {
page.setAttribute(QWebSettings::PluginsEnabled, value);
} else if (strncmp("--private-browsing", s, nlen) == 0) {
page.setAttribute(QWebSettings::PrivateBrowsingEnabled, value);
} else if (strncmp("--js-can-open-windows", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptCanOpenWindows, value);
} else if (strncmp("--js-can-access-clipboard", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptCanAccessClipboard, value);
} else if (strncmp("--developer-extras", s, nlen) == 0) {
page.setAttribute(QWebSettings::DeveloperExtrasEnabled, value);
} else if (strncmp("--links-included-in-focus-chain", s, nlen) == 0) {
page.setAttribute(QWebSettings::LinksIncludedInFocusChain, value);
#if QT_VERSION >= 0x040500
} else if (strncmp("--print-backgrounds", s, nlen) == 0) {
page.setAttribute(QWebSettings::PrintElementBackgrounds, value);
} else if (strncmp("--zoom-factor", s, nlen) == 0) {
page.mainFrame()->setZoomFactor(QString(value).toFloat());
} else if (strncmp("--zoom-text-only", s, nlen) == 0) {
page.setAttribute(QWebSettings::ZoomTextOnly, value);
} else if (strncmp("--http-proxy", s, nlen) == 0) {
QUrl p = QUrl::fromEncoded(value);
QNetworkProxy proxy = QNetworkProxy(QNetworkProxy::HttpProxy,
p.host(), p.port(80), p.userName(), p.password());
manager.setProxy(proxy);
page.setNetworkAccessManager(&manager);
#endif
#if CUTYCAPT_SCRIPT
} else if (strncmp("--inject-script", s, nlen) == 0) {
argInjectScript = value;
} else if (strncmp("--script-object", s, nlen) == 0) {
argScriptObject = value;
} else if (strncmp("--expect-alert", s, nlen) == 0) {
page.setAlertString(value);
#endif
} else if (strncmp("--app-name", s, nlen) == 0) {
app.setApplicationName(value);
} else if (strncmp("--app-version", s, nlen) == 0) {
app.setApplicationVersion(value);
} else if (strncmp("--body-base64", s, nlen) == 0) {
body = QByteArray::fromBase64(value);
} else if (strncmp("--body-string", s, nlen) == 0) {
body = QByteArray(value);
} else if (strncmp("--user-agent", s, nlen) == 0) {
page.setUserAgent(value);
} else if (strncmp("--out-format", s, nlen) == 0) {
for (int ix = 0; CutyExtMap[ix].id != CutyCapt::OtherFormat; ++ix)
if (strcmp(value, CutyExtMap[ix].identifier) == 0)
format = CutyExtMap[ix].id; //, break;
if (format == CutyCapt::OtherFormat) {
// TODO: error
argHelp = 1;
break;
}
} else if (strncmp("--header", s, nlen) == 0) {
const char* hv = strchr(value, ':');
if (hv == NULL) {
// TODO: error
argHelp = 1;
break;
}
req.setRawHeader(QByteArray(value, hv - value), hv + 1);
} else if (strncmp("--method", s, nlen) == 0) {
if (strcmp("value", "get") == 0)
method = QNetworkAccessManager::GetOperation;
else if (strcmp("value", "put") == 0)
method = QNetworkAccessManager::PutOperation;
else if (strcmp("value", "post") == 0)
method = QNetworkAccessManager::PostOperation;
else if (strcmp("value", "head") == 0)
method = QNetworkAccessManager::HeadOperation;
else
(void)0; // TODO: ...
} else {
// TODO: error
argHelp = 1;
}
}
if (argUrl == NULL || argOut == NULL || argHelp) {
CaptHelp();
return EXIT_FAILURE;
}
// This used to use QUrl(argUrl) but that escapes %hh sequences
// even though it should not, as URLs can assumed to be escaped.
req.setUrl( QUrl::fromEncoded(argUrl) );
QString scriptProp(argScriptObject);
QString scriptCode;
if (argInjectScript) {
QFile file(argInjectScript);
if (file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream.setAutoDetectUnicode(true);
scriptCode = stream.readAll();
file.close();
}
}
CutyCapt main(&page, argOut, argDelay, format, scriptProp, scriptCode);
main.mUrl = argUrl;
app.connect(&page,
SIGNAL(loadFinished(bool)),
&main,
SLOT(DocumentComplete(bool)));
app.connect(page.mainFrame(),
SIGNAL(initialLayoutCompleted()),
&main,
SLOT(InitialLayoutCompleted()));
if (argMaxWait > 0) {
// TODO: Should this also register one for the application?
QTimer::singleShot(argMaxWait, &main, SLOT(Timeout()));
}
if (argUserStyle != NULL)
// TODO: does this need any syntax checking?
page.settings()->setUserStyleSheetUrl( QUrl::fromEncoded(argUserStyle) );
if (argUserStylePath != NULL) {
page.settings()->setUserStyleSheetUrl( QUrl::fromLocalFile(argUserStylePath) );
}
if (argUserStyleString != NULL) {
QUrl data("data:text/css;charset=utf-8;base64," +
QByteArray(argUserStyleString).toBase64());
page.settings()->setUserStyleSheetUrl( data );
}
if (argIconDbPath != NULL)
// TODO: does this need any syntax checking?
page.settings()->setIconDatabasePath(argUserStyle);
// The documentation does not say, but it seems the mainFrame
// will never change, so we can set this here. Otherwise we'd
// have to set this in snapshot and trigger an update, which
// is not currently possible (Qt 4.4.0) as far as I can tell.
page.mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
page.mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
page.setViewportSize( QSize(argMinWidth, argMinHeight) );
#if CUTYCAPT_SCRIPT
// javaScriptWindowObjectCleared does not get called on the
// initial load unless some JavaScript has been executed.
page.mainFrame()->evaluateJavaScript(QString(""));
app.connect(page.mainFrame(),
SIGNAL(javaScriptWindowObjectCleared()),
&main,
SLOT(JavaScriptWindowObjectCleared()));
#endif
app.connect( &manager,SIGNAL(finished(QNetworkReply *)), &main,SLOT(HtmlDownloadFinished(QNetworkReply *)));
QNetworkReply *reply = manager.get(req);
app.connect( reply,SIGNAL(error(QNetworkReply::NetworkError)), &main,SLOT(HtmlDownloadError(QNetworkReply::NetworkError)));
return app.exec();
}