/* smplayer, GUI front-end for mplayer. Copyright (C) 2007 Ricardo Villalba 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 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "mplayerprocess.h" #include #include #include #include "global.h" #include "preferences.h" #include "config.h" #if DONT_USE_SIGNALS #include #endif MplayerProcess::MplayerProcess(QObject * parent, const char * name) : QProcess(parent,name) { setCommunication( QProcess::Stdin | QProcess::Stdout | QProcess::Stderr ); #if DONT_USE_SIGNALS timer = new QTimer( this ); connect( timer, SIGNAL(timeout()), this, SLOT(read()) ); #else connect( this, SIGNAL(readyReadStdout()), this, SLOT(read()) ); #endif connect( this, SIGNAL(readyReadStderr()), this, SLOT(readFromStderr()) ); connect( this, SIGNAL(processExited()), this, SLOT(stopReading()) ); connect( this, SIGNAL(lineAvailable(QString)), this, SLOT(parseLine(QString)) ); incomplete_line = ""; notified_mplayer_is_running = FALSE; last_sub_id = -1; init_rx(); } MplayerProcess::~MplayerProcess() { } bool MplayerProcess::start( QStringList * env ) { init_rx(); // Update configurable regular expressions md.reset(); notified_mplayer_is_running = FALSE; incomplete_line = ""; last_sub_id = -1; #if !DONT_USE_SIGNALS return QProcess::start(env); #else int r = QProcess::start(env); QTimer * t = new QTimer(this); connect( t, SIGNAL(timeout()), this, SLOT(startReading()) ); t->start(100, TRUE); return r; #endif } void MplayerProcess::startReading() { #if DONT_USE_SIGNALS qDebug("MplayerProcess::startReading"); timer->start(40); #endif } void MplayerProcess::stopReading() { #if DONT_USE_SIGNALS qDebug("MplayerProcess::stopReading"); timer->stop(); #endif } void MplayerProcess::readFromStderr() { QString line; while (canReadLineStderr()) { line = "stderr: " + readLineStderr(); emit lineAvailable(line); } } #if DONT_USE_SIGNALS void MplayerProcess::read() { //qDebug("MplayerProcess::read"); QByteArray ba; ba = readStdout(); if ( !ba.isEmpty() ) { read( ba ); } } #else void MplayerProcess::read() { //qDebug("MplayerProcess::read"); read( readStdout() ); } #endif void MplayerProcess::read(const QByteArray & new_text) { QString line; QString l = incomplete_line; l += new_text; l = l.replace(0x0D, '\n'); //int n=0; int pos = l.find('\n'); while (pos > -1) { line = l.left(pos); emit lineAvailable( QString::fromLocal8Bit(line) ); l = l.mid(pos+1); #ifdef Q_OS_WIN // If line starts with \n, remove it if (l.startsWith("\n")) l = l.mid(1); #endif pos = l.find('\n'); /* n++; if (n > 10) { n=0; qApp->processEvents(); } */ } incomplete_line = l; //qDebug("incomplete_line: '%s'", incomplete_line.utf8().data()); } static QRegExp rx_av("^[AV]: *([0-9,:.-]+)"); static QRegExp rx_frame("^[AV]:.* (\\d+)\\/.\\d+");// [0-9,.]+"); static QRegExp rx("^(.*)=(.*)"); static QRegExp rx_audio_mat("^ID_AID_(\\d+)_(LANG|NAME)=(.*)"); static QRegExp rx_title("^ID_DVD_TITLE_(\\d+)_(LENGTH|CHAPTERS|ANGLES)=(.*)"); static QRegExp rx_winresolution("^VO: \\[(.*)\\] (\\d+)x(\\d+) => (\\d+)x(\\d+)"); static QRegExp rx_ao("^AO: \\[(.*)\\]"); static QRegExp rx_paused("^ID_PAUSED"); static QRegExp rx_novideo("^Video: no video"); static QRegExp rx_cache("^Cache fill:.*"); static QRegExp rx_create_index("^Generating Index:.*"); static QRegExp rx_play("^Starting playback..."); static QRegExp rx_connecting("^Connecting to .*"); static QRegExp rx_resolving("^Resolving .*"); static QRegExp rx_screenshot("^\\*\\*\\* screenshot '(.*)'"); static QRegExp rx_endoffile("^Exiting... \\(End of file\\)"); static QRegExp rx_mkvchapters("\\[mkv\\] Chapter (\\d+) from"); static QRegExp rx_aspect2("^Movie-Aspect is ([0-9,.]+):1"); // VCD static QRegExp rx_vcd("^ID_VCD_TRACK_(\\d+)_MSF=(.*)"); // Audio CD static QRegExp rx_cdda("^ID_CDDA_TRACK_(\\d+)_MSF=(.*)"); //Subtitles #if SUBTITLES_BY_INDEX static QRegExp rx_subtitle("^ID_(SUBTITLE|FILE_SUB|VOBSUB)_ID=(\\d+)"); static QRegExp rx_sid("^ID_(SID|VSID)_(\\d+)_(LANG|NAME)=(.*)"); static QRegExp rx_subtitle_file("^ID_FILE_SUB_FILENAME=(.*)"); #else static QRegExp rx_subs("^ID_SID_(\\d+)_(LANG|NAME)=(.*)"); #endif //Clip info static QRegExp rx_clip_name("^ (name|title): (.*)", false); static QRegExp rx_clip_artist("^ artist: (.*)", false); static QRegExp rx_clip_author("^ author: (.*)", false); static QRegExp rx_clip_album("^ album: (.*)", false); static QRegExp rx_clip_genre("^ genre: (.*)", false); static QRegExp rx_clip_date("^ (creation date|year): (.*)", false); static QRegExp rx_clip_track("^ track: (.*)", false); static QRegExp rx_clip_copyright("^ copyright: (.*)", false); static QRegExp rx_clip_comment("^ comment: (.*)", false); static QRegExp rx_clip_software("^ software: (.*)", false); static QRegExp rx_stream_title("^.* StreamTitle='(.*)';StreamUrl='(.*)';"); void MplayerProcess::init_rx() { qDebug("MplayerProcess::init_rx"); if (!pref->rx_endoffile.isEmpty()) rx_endoffile.setPattern(pref->rx_endoffile); if (!pref->rx_novideo.isEmpty()) rx_novideo.setPattern(pref->rx_novideo); } void MplayerProcess::parseLine(QString line) { //qDebug("MplayerProcess::parseLine: '%s'", line.utf8().data() ); QString tag; QString value; // Parse A: V: line //qDebug("%s", line.utf8().data()); if (rx_av.search(line) > -1) { double sec = rx_av.cap(1).toDouble(); //qDebug("cap(1): '%s'", rx_av.cap(1).utf8().data() ); //qDebug("sec: %f", sec); if (!notified_mplayer_is_running) { qDebug("MplayerProcess::parseLine: starting sec: %f", sec); emit receivedStartingTime(sec); emit mplayerFullyLoaded(); notified_mplayer_is_running = TRUE; } emit receivedCurrentSec( sec ); // Check for frame if (rx_frame.search(line) > -1) { int frame = rx_frame.cap(1).toInt(); //qDebug(" frame: %d", frame); emit receivedCurrentFrame(frame); } } else { // Parse other things qDebug("MplayerProcess::parseLine: '%s'", line.utf8().data() ); // Screenshot if (rx_screenshot.search(line) > -1) { QString shot = rx_screenshot.cap(1); qDebug("MplayerProcess::parseLine: screenshot: '%s'", shot.utf8().data()); emit receivedScreenshot( shot ); } else // End of file if (rx_endoffile.search(line) > -1) { qDebug("MplayerProcess::parseLine: detected end of file"); // In case of playing VCDs or DVDs, maybe the first title // is not playable, so the GUI doesn't get the info about // available titles. So if we received the end of file // first let's pretend the file has started so the GUI can have // the data. if ( !notified_mplayer_is_running) { emit mplayerFullyLoaded(); } emit receivedEndOfFile(); } else // Window resolution if (rx_winresolution.search(line) > -1) { /* md.win_width = rx_winresolution.cap(4).toInt(); md.win_height = rx_winresolution.cap(5).toInt(); md.video_aspect = (double) md.win_width / md.win_height; */ int w = rx_winresolution.cap(4).toInt(); int h = rx_winresolution.cap(5).toInt(); emit receivedVO( rx_winresolution.cap(1) ); emit receivedWindowResolution( w, h ); //emit mplayerFullyLoaded(); } else // No video if (rx_novideo.search(line) > -1) { md.novideo = TRUE; emit receivedNoVideo(); //emit mplayerFullyLoaded(); } else // Pause if (rx_paused.search(line) > -1) { emit receivedPause(); } // Stream title if (rx_stream_title.search(line) > -1) { QString s = rx_stream_title.cap(1); QString url = rx_stream_title.cap(2); qDebug("MplayerProcess::parseLine: stream_title: '%s'", s.utf8().data()); qDebug("MplayerProcess::parseLine: stream_url: '%s'", url.utf8().data()); md.stream_title = s; md.stream_url = url; emit receivedStreamTitleAndUrl( s, url ); } // The following things are not sent when the file has started to play // (or if sent, smplayer will ignore anyway...) // So not process anymore, if video is playing to save some time if (notified_mplayer_is_running) { return; } #if SUBTITLES_BY_INDEX if (rx_subtitle.search(line) > -1) { md.subs.process(line); } else if (rx_sid.search(line) > -1) { md.subs.process(line); } else if (rx_subtitle_file.search(line) > -1) { md.subs.process(line); } #endif // AO if (rx_ao.search(line) > -1) { emit receivedAO( rx_ao.cap(1) ); } else #if !SUBTITLES_BY_INDEX // Matroska subtitles if (rx_subs.search(line) > -1) { int ID = rx_subs.cap(1).toInt(); QString lang = rx_subs.cap(3); QString t = rx_subs.cap(2); qDebug("MplayerProcess::parseLine: Subs: ID: %d, Lang: '%s' Type: '%s'", ID, lang.utf8().data(), t.utf8().data()); if ( t == "NAME" ) md.subtitles.addName(ID, lang); else md.subtitles.addLang(ID, lang); } else #endif // Matroska audio if (rx_audio_mat.search(line) > -1) { int ID = rx_audio_mat.cap(1).toInt(); QString lang = rx_audio_mat.cap(3); QString t = rx_audio_mat.cap(2); qDebug("MplayerProcess::parseLine: Audio: ID: %d, Lang: '%s' Type: '%s'", ID, lang.utf8().data(), t.utf8().data()); if ( t == "NAME" ) md.audios.addName(ID, lang); else md.audios.addLang(ID, lang); } else // Matroshka chapters if (rx_mkvchapters.search(line)!=-1) { int c = rx_mkvchapters.cap(1).toInt(); qDebug("MplayerProcess::parseLine: mkv chapters: %d", c); if (c > md.mkv_chapters) { md.mkv_chapters = c; qDebug("MplayerProcess::parseLine: mkv_chapters set to: %d", c); } } else // VCD titles if (rx_vcd.search(line) > -1 ) { int ID = rx_vcd.cap(1).toInt(); QString length = rx_vcd.cap(2); //md.titles.addID( ID ); md.titles.addName( ID, length ); } else // Audio CD titles if (rx_cdda.search(line) > -1 ) { int ID = rx_cdda.cap(1).toInt(); QString length = rx_cdda.cap(2); double duration = 0; QRegExp r("(\\d+):(\\d+):(\\d+)"); if ( r.search(length) > -1 ) { duration = r.cap(1).toInt() * 60; duration += r.cap(2).toInt(); } md.titles.addID( ID ); /* QString name = QString::number(ID) + " (" + length + ")"; md.titles.addName( ID, name ); */ md.titles.addDuration( ID, duration ); } else // DVD titles if (rx_title.search(line) > -1) { int ID = rx_title.cap(1).toInt(); QString t = rx_title.cap(2); if (t=="LENGTH") { double length = rx_title.cap(3).toDouble(); qDebug("MplayerProcess::parseLine: Title: ID: %d, Length: '%f'", ID, length); md.titles.addDuration(ID, length); } else if (t=="CHAPTERS") { int chapters = rx_title.cap(3).toInt(); qDebug("MplayerProcess::parseLine: Title: ID: %d, Chapters: '%d'", ID, chapters); md.titles.addChapters(ID, chapters); } else if (t=="ANGLES") { int angles = rx_title.cap(3).toInt(); qDebug("MplayerProcess::parseLine: Title: ID: %d, Angles: '%d'", ID, angles); md.titles.addAngles(ID, angles); } } else // Catch cache messages if (rx_cache.search(line) > -1) { emit receivedCacheMessage(line); } else // Creating index if (rx_create_index.search(line) > -1) { emit receivedCreatingIndex(line); } else // Catch connecting message if (rx_connecting.search(line) > -1) { emit receivedConnectingToMessage(line); } else // Catch resolving message if (rx_resolving.search(line) > -1) { emit receivedResolvingMessage(line); } else // Aspect ratio for old versions of mplayer if (rx_aspect2.search(line) > -1) { md.video_aspect = rx_aspect2.cap(1).toDouble(); qDebug("MplayerProcess::parseLine: md.video_aspect set to %f", md.video_aspect); } else // Clip info // Name if (rx_clip_name.search(line) > -1) { QString s = rx_clip_name.cap(2); qDebug("MplayerProcess::parseLine: clip_name: '%s'", s.utf8().data()); md.clip_name = s; } else // Artist if (rx_clip_artist.search(line) > -1) { QString s = rx_clip_artist.cap(1); qDebug("MplayerProcess::parseLine: clip_artist: '%s'", s.utf8().data()); md.clip_artist = s; } else // Author if (rx_clip_author.search(line) > -1) { QString s = rx_clip_author.cap(1); qDebug("MplayerProcess::parseLine: clip_author: '%s'", s.utf8().data()); md.clip_author = s; } else // Album if (rx_clip_album.search(line) > -1) { QString s = rx_clip_album.cap(1); qDebug("MplayerProcess::parseLine: clip_album: '%s'", s.utf8().data()); md.clip_album = s; } else // Genre if (rx_clip_genre.search(line) > -1) { QString s = rx_clip_genre.cap(1); qDebug("MplayerProcess::parseLine: clip_genre: '%s'", s.utf8().data()); md.clip_genre = s; } else // Date if (rx_clip_date.search(line) > -1) { QString s = rx_clip_date.cap(2); qDebug("MplayerProcess::parseLine: clip_date: '%s'", s.utf8().data()); md.clip_date = s; } else // Track if (rx_clip_track.search(line) > -1) { QString s = rx_clip_track.cap(1); qDebug("MplayerProcess::parseLine: clip_track: '%s'", s.utf8().data()); md.clip_track = s; } else // Copyright if (rx_clip_copyright.search(line) > -1) { QString s = rx_clip_copyright.cap(1); qDebug("MplayerProcess::parseLine: clip_copyright: '%s'", s.utf8().data()); md.clip_copyright = s; } else // Comment if (rx_clip_comment.search(line) > -1) { QString s = rx_clip_comment.cap(1); qDebug("MplayerProcess::parseLine: clip_comment: '%s'", s.utf8().data()); md.clip_comment = s; } else // Software if (rx_clip_software.search(line) > -1) { QString s = rx_clip_software.cap(1); qDebug("MplayerProcess::parseLine: clip_software: '%s'", s.utf8().data()); md.clip_software = s; } else // Catch starting message /* pos = rx_play.search(line); if (pos > -1) { emit mplayerFullyLoaded(); } */ //Generic things if (rx.search(line) > -1) { tag = rx.cap(1); value = rx.cap(2); //qDebug("MplayerProcess::parseLine: tag: %s, value: %s", tag.utf8().data(), value.utf8().data()); // Generic audio if (tag == "ID_AUDIO_ID") { int ID = value.toInt(); qDebug("MplayerProcess::parseLine: ID_AUDIO_ID: %d", ID); md.audios.addID( ID ); } else #if !SUBTITLES_BY_INDEX // Generic subtitle if (tag == "ID_SUBTITLE_ID") { int ID = value.toInt(); qDebug("MplayerProcess::parseLine: ID_SUBTITLE_ID: %d", ID); md.subtitles.addID( ID ); } else // Avi subs (srt, sub...) if (tag == "ID_FILE_SUB_ID") { int ID = value.toInt(); qDebug("MplayerProcess::parseLine: SUB_ID: %d", ID); md.subtitles.addID( ID ); last_sub_id = ID; } else if (tag == "ID_FILE_SUB_FILENAME") { QString name = value; qDebug("MplayerProcess::parseLine: SUB_FILENAME: %s", name.utf8().data() ); if (last_sub_id != -1) md.subtitles.addFilename( last_sub_id, name ); /* if (!md.subtitles.existsFilename(name)) { int new_id = md.subtitles.lastID() + 1; qDebug("MplayerProcess::parseLine: creating new id for file sub: %d", new_id); md.subtitles.addFilename( new_id, value ); } */ } #endif if (tag == "ID_LENGTH") { md.duration = value.toDouble(); qDebug("MplayerProcess::parseLine: md.duration set to %f", md.duration); } else if (tag == "ID_VIDEO_WIDTH") { md.video_width = value.toInt(); qDebug("MplayerProcess::parseLine: md.video_width set to %d", md.video_width); } else if (tag == "ID_VIDEO_HEIGHT") { md.video_height = value.toInt(); qDebug("MplayerProcess::parseLine: md.video_height set to %d", md.video_height); } else if (tag == "ID_VIDEO_ASPECT") { md.video_aspect = value.toDouble(); if ( md.video_aspect == 0.0 ) { // I hope width & height are already set. md.video_aspect = (double) md.video_width / md.video_height; } qDebug("MplayerProcess::parseLine: md.video_aspect set to %f", md.video_aspect); } else if (tag == "ID_DVD_DISC_ID") { md.dvd_id = value; qDebug("MplayerProcess::parseLine: md.dvd_id set to '%s'", md.dvd_id.utf8().data()); } else if (tag == "ID_DEMUXER") { md.demuxer = value; } else if (tag == "ID_VIDEO_FORMAT") { md.video_format = value; } else if (tag == "ID_AUDIO_FORMAT") { md.audio_format = value; } else if (tag == "ID_VIDEO_BITRATE") { md.video_bitrate = value.toInt(); } else if (tag == "ID_VIDEO_FPS") { md.video_fps = value; } else if (tag == "ID_AUDIO_BITRATE") { md.audio_bitrate = value.toInt(); } else if (tag == "ID_AUDIO_RATE") { md.audio_rate = value.toInt(); } else if (tag == "ID_AUDIO_NCH") { md.audio_nch = value.toInt(); } else if (tag == "ID_VIDEO_CODEC") { md.video_codec = value; } else if (tag == "ID_AUDIO_CODEC") { md.audio_codec = value; } } } }