Menu

Reading kid3-cli output via QProcess

Help
Vegeta
2019-08-28
2019-09-04
  • Vegeta

    Vegeta - 2019-08-28

    I use kid3-cli to quickly and easily read the tags via a QProcess. The communication runs via qProcess->write() and the readout via connection with readyReadStandardOutput. This ran smoothly until the new version 3.8.0. The new version no longer delivers data that can be read via readyReadStandardOutput. Maybe qProcess->write() doesn't work anymore, I can't say that due to lack of feedback from kid3-cli.
    Is there any way to solve the problem?

     
  • Urs Fleisch

    Urs Fleisch - 2019-08-28

    The thing which has changed is that kid3-cli now detects if its input is not a tty and then uses normal standard input instead of readline. I did this to make things easier, together with support for JSON input and output. I just made a small program and it seems to work, so maybe you need some changes in your code. You could use straceto see what is going on.

    #include <QCoreApplication>
    #include <QProcess>
    #include <QTimer>
    
    class CliController : public QObject {
    public:
      CliController() : m_process(new QProcess(this)) {
        connect(m_process, &QProcess::readyReadStandardOutput, this,
                [this]() {
          auto out = m_process->readAllStandardOutput();
          qDebug("out: %s", out.constData());
          if (out.startsWith("import")) {
            sendPwd();
          } else {
            sendExit();
          }
        });
        connect(m_process, QOverload<int, QProcess::ExitStatus>::of(
                  &QProcess::finished),
              [=](int, QProcess::ExitStatus) {
          qDebug("finished");
          qApp->quit();
        });
    
        m_process->start("/usr/bin/kid3-cli");
      }
    
      void sendHelp() {
        m_process->write("help import\n");
      }
    
      void sendPwd() {
        m_process->write("pwd\n");
      }
    
      void sendExit() {
        m_process->write("exit\n");
      }
    
    private:
      QProcess* m_process;
    };
    
    int main(int argc, char *argv[])
    {
      QCoreApplication a(argc, argv);
      CliController cliCtl;
      QTimer::singleShot(0, &cliCtl, &CliController::sendHelp);
      return a.exec();
    }
    

    And the output I get is as expected

    $ ./cliprocess
    out: import P S [T]  Import from file
                    P S = File path Format name | tags Source Extraction
    
    out: /home/urs/src
    
    finished
    $
    
     
  • Vegeta

    Vegeta - 2019-08-28

    Thank you for the quick answer and the example. I tried it and it works, sorry that I didn't add an example code. The following sample program worked with the previous version, but there is no output with the new version. I'll have a closer look tomorrow.

    const QString KID3_BINARY = "/usr/bin/kid3-cli";
    
    MainWindow::MainWindow( QWidget *parent )
        : QMainWindow( parent )
        , ui( new Ui::MainWindow )
    {
        ui->setupUi( this );
    
        c = new QProcess( this );
    
        connect( c, &QProcess::readyReadStandardOutput, [&] { qDebug() << c->readAll(); } );
    
        c->start( KID3_BINARY );
        c->waitForStarted( 1000 );
        c->write( "cd '/path/to/music folder'\n" );
        c->write( "select first\n" );
        c->write( "get all 12\n" );
    }
    
     
  • Urs Fleisch

    Urs Fleisch - 2019-08-31

    If this is the whole application, using Qt seems to be an overkill, this could be done with a simple call kid3-cli -c "select first" -c "get all 12" "/path/to/music folder" or a series of calls in a shell script.
    If you want to use Qt you should use an asynchronous event-based style with signals and slots and avoid blocking calls like waitForStarted. In your example, the three write calls are probably called in sequence without yielding the current thread and if the program is terminated afterwards, the readyReadStandardOutput will never be signalled.
    If you use the standard command line interface, you should wait for the output after writing to the process standard input, otherwise it will be difficult to find out which output corresponds to which input. Using JSON you could use idfields to associate requests and responses, but also in this case it is probably better to handle responses before sending the next request.

     
  • Vegeta

    Vegeta - 2019-08-31

    Hi,
    no this is not the whole application, but a newly started test project to debug the actual problem quickly and not have to deal with the whole application. Lambda connections are also used there. ;)
    I haven't had time to check it out yet, but the problem is that this little test program works fine with kid3-cli 3.7.1 and you get the tags of the first file and absolutely nothing happens with kid3-cli 3.8.0.

    Even if you just start the kid3-cli binary without any commands via QProcess->write(), i.e. only QProcess->start( "/usr/bin/kid3-cli" );, with the old version you get the response "kid3-cli> " via QProcess::readyReadStandardOutput, with the new version nothing happens.
    So the described problem cannot be caused by QProcess->write(), because even a normal call of kid3-cli behaves quite differently than before.

     
  • Urs Fleisch

    Urs Fleisch - 2019-09-01

    The prompt will only be written if isatty() returns 1, i.e. kid3-cli is connected to a real terminal. You could try the following to make it think that it is connected to a real terminal and thus get the old behavior: Instead of QProcess->start("/usr/bin/kid3-cli") run it with script using for example

    QProcess->start("/usr/bin/script -eqc /usr/bin/kid3-cli /dev/null")
    
     
  • Vegeta

    Vegeta - 2019-09-04

    Thanks a lot that works, but unfortunately my program still doesn't work properly with the new version of kid3-cli. There must be more changes under the hood that cause problems. As soon as I have more time, I'll take a closer look at it, so long I will still use the old version.