3

I am a total beginner in Qt. I need to call a command-line program from a button in my desktop app. The program downloads a YouTube video. I also need to read standard error from it. I wrote the following code:

void YoutubeDL::on_downloadButton_clicked()
{
    [...]

    QProcess p;
    p.startDetached("youtube-dl -f " + get + " " + ui->urlBox->text());
    QString perr = p.readAllStandardError();
    if (perr.length())
        ui->descBox->setText("Error during download.\n" + perr);
    else
        ui->descBox->setText("Download completed!");
}

However the stderr read does not take place.

On the other hand if I use non-detached p.start() and then waitForFinished(-1) then I can read the stderr, but the GUI freezes until the download completes.

How can this be fixed?

A related problem: I would also like some way to be able to read the outputs of the download process in real-time, so that I can display it in the GUI. youtube-dl gives progress reports like this:

[download]   0.0% of 2.00MiB at 173.22KiB/s ETA 00:12
[download]   0.1% of 2.00MiB at 105.01KiB/s ETA 00:19
[download]   0.3% of 2.00MiB at 96.86KiB/s ETA 00:21
[download]   0.7% of 2.00MiB at 105.23KiB/s ETA 00:19
[download]   1.5% of 2.00MiB at 100.29KiB/s ETA 00:20
...

I'd like to be able to read these as and when they are generated.

Aakash Jain
  • 1,963
  • 17
  • 29
  • I suggest you lookup more into QThread instead of running another process. QThread allows you to do parallel computation without freezing the GUI. – Cool_Coder Mar 22 '14 at 08:21
  • I did try looking into QThread but there seems to be no consensus on what the best way to use them is. My research only left me thoroughly confused. Could you point me in the right direction? – Aakash Jain Mar 22 '14 at 08:23
  • Do you know what threads are? – Cool_Coder Mar 22 '14 at 08:28
  • Yes, and I have used pthreads in C. – Aakash Jain Mar 22 '14 at 08:30
  • 2
    OK great, then search 'voidrealms' on Youtube. That guy has made about 100 tutorials on Qt. It contains 5-6 tutorials on QThread. I strongly recommend you watch each of these videos at least 3 times. Because there are 2 techniques of using QThread one of which is absolutely incorrect but still prevails on the internet. – Cool_Coder Mar 22 '14 at 08:36
  • possible duplicate of [How to get STDOUT from a QProcess?](http://stackoverflow.com/questions/3852587/how-to-get-stdout-from-a-qprocess) – Cool_Coder Mar 22 '14 at 08:38
  • No, I can read the output with the method in that thread but it won't help my purpose. I will watch those videos, thanks! – Aakash Jain Mar 22 '14 at 08:41

3 Answers3

7

All the answers above are incorrect due to the nature of the question. @aakashjain asks for a detached process. What you guys suggest works only in the case the started process is still attached.

QProcess p
p.startDetached(...)

is the same as

QProcess::startDetached(...)

QProcess::startDetached() is a static method and it is NOT part of any object or returns one. Once you call this method and it succeeds the process will no longer be attached in any way to the process that has spawned it (your application).

I suggest you look first at the official documentation on this method and then read more about interprocess communication as well how to pipe the output of one process to another (in this case detached process to the terminal).

I stumbled upon the issue even of basic controll using QProcess::startDetached() in PyQt4 where I did exaclty what the @DmitrySokolov suggested. For my surprise the state was always zero, nothing could be set or retrieved using the QProcess non-static method. After some digging and asking around it was pointed to me exactly what I've written at the beginning of my answer - if you use QProcess::startDetached(), the control that follows has to be through the system tools (such as the kill command, pipes etc.) because you have no object you can actually work with.

QProcess::startDetached() offers two important return values:

  • the return value of the method itself - a boolean, which tells you if the starting of the process has succeeded (==true) or failed (==false)
  • the qint64 * pid pointer argument of the function - if the process has been started successfully, the pid will contain the PID (Process Identifier) of the detached process

You can use this PID to interact with the process however you like (and the process itself allows you to of course ;)) - you can terminate it, pipe its output, put it to sleep etc.

Now back to your problem. There are three solutions here:

  • Start a child process - the process will be contained in a QProcess object, which you can interact with as @DmitrySokolov has described. However this will block you main thread, which handles the UI until the child process has finished its task and has stopped (main thread runs inside the process of your Qt app). I doubt you want your UI to freeze (as you have noticed by experimenting on your own) especially since we are talking about downloading possibly large contents of video data...
  • Start a detached process - what I've described before this list of solutions. It requires however a more in debt understanding of interprocess communication. If you are up for it and want to spend some time working on this topic (I can only encourage you to do that!), do it
  • Start a thread and a child process in it - not only you will have a really neat control over the download task but you will also be able to give great feedback via slots and signals to the UI, which will improve the user experience a lot. This is the most widely used way for such task and I can also see in your comment that you want to output the progress in ui->descBox (you can also add a QProgressbar to make the output more user-friendly and do the stdout/stderr thing in the background just for you to see). I haven't tested this to be honest but it should work.
Sevle
  • 3,109
  • 2
  • 19
  • 31
rbaleksandar
  • 8,713
  • 7
  • 76
  • 161
1

Before starting a process connect QProcess signals to your slots:

void finished(int exitCode, QProcess::ExitStatus exitStatus)
void readyReadStandardError()
void readyReadStandardOutput()

When the finished() signal is triggered you can read all sdterr output with the readAllStandardError() method or you can read portions of stderr data when readyReadStandardError() is triggered.

As I understood the youtube-dl outputs progress data to stdout. So you can read it and parse when the readyReadStandardOutput signal is triggered.

Update

class YoutubeDL
{
...
private:
    QProcess p;
    void on_process_finished(int exitCode, QProcess::ExitStatus exitStatus);
    void on_process_readyReadStandardOutput();
};

void YoutubeDL::on_downloadButton_clicked()
{
    ...

    p.connect(&p, &QProcess::readyReadStandardOutput,
              this, &YoutubeDL::on_process_readyReadStandardOutput);
    p.connect(&p, (void (QProcess::*)(int,QProcess::ExitStatus))&QProcess::finished,
              this, &YoutubeDL::on_process_finished);

    p.start("youtube-dl -f " + get + " " + ui->urlBox->text());
}

void YoutubeDL::on_process_finished(int exitCode, QProcess::ExitStatus exitStatus)
{
    QString perr = p.readAllStandardError();
    if (perr.length())
        ui->descBox->setText("Error during download.\n" + perr);
    else
        ui->descBox->setText("Download completed!");
}

void YoutubeDL::on_process_readyReadStandardOutput()
{
    p.setReadChannel(QProcess::StandardOutput);
    QTextStream stream(&p);
    while (!stream.atEnd()) {
        QString line = stream.readLine();
        // extract progress info from line and etc.
        ...
    }
}
Dmitry Sokolov
  • 3,118
  • 1
  • 30
  • 35
0

Don't wait for finished. Instead of this use QProcess signal readyReadStandardError to be notified that you can read anything.

Bogdan
  • 984
  • 8
  • 16