2 * abby Copyright (C) 2009 Toni Gundogdu.
3 * This file is part of abby.
5 * abby is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * abby is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include <QMainWindow>
20 #include <QCloseEvent>
21 #include <QMessageBox>
22 #include <QFileDialog>
31 MainWindow::MainWindow():
35 The word "English" is not meant to be translated literally.
36 Replace the word "English" with target language, e.g. Suomi
37 or Deutsch. Note that the only purpose of this string is to
38 list it as an available language in the Preferences dialog.
40 const QString lang
= tr("English");
45 setWindowIcon(QIcon(":/rc/abby.png"));
47 connect(&process
, SIGNAL(started()),
48 this, SLOT(onProcStarted()));
49 connect(&process
, SIGNAL(error(QProcess::ProcessError
)),
50 this, SLOT(onProcError(QProcess::ProcessError
)));
52 // NOTE: We read both channels stdout and stderr.
53 connect(&process
, SIGNAL(readyReadStandardOutput()),
54 this, SLOT(onProcStdoutReady()));
55 connect(&process
, SIGNAL(readyReadStandardError()),
56 this, SLOT(onProcStdoutReady()));
58 connect(&process
, SIGNAL(finished(int,QProcess::ExitStatus
)),
59 this, SLOT(onProcFinished(int,QProcess::ExitStatus
)));
61 prefs
= new PreferencesDialog(this);
66 MainWindow::isCclive(const QString
& path
, QString
& output
) {
68 process
.setProcessChannelMode(QProcess::MergedChannels
);
69 process
.start(path
, QStringList() << "--version");
72 if (!process
.waitForFinished())
73 qDebug() << path
<< ": " << process
.errorString();
75 output
= QString::fromLocal8Bit(process
.readAll());
76 QStringList lst
= output
.split(" ", QString::SkipEmptyParts
);
77 state
= lst
[0] == "cclive";
83 MainWindow::ccliveSupports(const QString
& buildOption
) {
84 QString output
, path
= prefs
->ccliveEdit
->text();
85 const bool _isCclive
= isCclive(path
,output
);
87 if (!_isCclive
&& buildOption
== "--with-perl")
88 return false; // To keep the titleBox hidden always
90 return output
.contains(buildOption
);
94 MainWindow::updateWidgets() {
95 // Enable widgets based on preferences and other settings.
98 s
= prefs
->streamEdit
->text();
99 streamBox
->setEnabled(!s
.isEmpty());
101 streamBox
->setCheckState(Qt::Unchecked
);
103 s
= prefs
->commandEdit
->text();
104 commandBox
->setEnabled(!s
.isEmpty());
106 commandBox
->setCheckState(Qt::Unchecked
);
108 if (ccliveSupports("--with-perl")) {
111 titleBox
->setCheckState(Qt::Unchecked
);
117 MainWindow::closeEvent(QCloseEvent
*event
) {
123 MainWindow::writeSettings() {
125 s
.beginGroup("MainWindow");
126 s
.setValue("size",size());
127 s
.setValue("pos",pos());
128 s
.setValue("titleBox",titleBox
->checkState());
133 MainWindow::readSettings() {
135 s
.beginGroup("MainWindow");
136 resize(s
.value("size",QSize(525,265)).toSize());
137 move(s
.value("pos",QPoint(200,200)).toPoint());
138 titleBox
->setCheckState(
139 s
.value("titleBox").toBool()
146 MainWindow::updateLog(const QString
& newText
) {
147 QString text
= logEdit
->toPlainText() + newText
;
148 logEdit
->setPlainText(text
);
152 MainWindow::updateFormats() {
154 // Alter widgets dynamically based on the video URL.
156 QString url
= urlEdit
->toPlainText();
165 static const struct lookup_s lookup
[] = {
166 {"youtube.com", "fmt17|fmt18|fmt22|fmt35"},
167 // {"video.google.", "mp4"}, // missing in cclive 0.4.3+
168 {"dailymotion.com", "spak-mini|vp6-hq|vp6-hd|vp6|h264"},
171 const int c
= sizeof(lookup
)/sizeof(struct lookup_s
);
174 formats
<< "flv" << "best";
176 for (register int i
=0; i
<c
; ++i
) {
177 if (url
.contains(lookup
[i
].host
)) {
178 QString s
= lookup
[i
].formats
;
179 formats
<< s
.split("|");
184 QString last
= formatCombo
->currentText();
186 formatCombo
->clear();
187 formatCombo
->addItems(formats
);
189 int n
= formatCombo
->findText(last
);
191 formatCombo
->setCurrentIndex(n
);
198 MainWindow::onPreferences() {
204 MainWindow::onStreamStateChanged(int state
) {
205 streamSpin
->setEnabled(state
!= 0);
209 MainWindow::onStart() {
212 QString url
= urlEdit
->toPlainText();
215 statusBar()->showMessage(tr("Enter a video link."));
221 if (!url
.startsWith("http://",Qt::CaseInsensitive
))
222 url
.insert(0,"http://");
226 QString path
= prefs
->ccliveEdit
->text();
227 if (path
.isEmpty()) {
228 QMessageBox::information(this,QCoreApplication::applicationName(),
229 tr("Path to cclive (or clive) command undefined. "
230 "See preferences."));
236 const bool _isCclive
= isCclive(path
,output
);
238 // Check video save directory
240 // NOTE: cclive itself does not support this concept so we
241 // work around it by changing the working directory to the
244 QString savedir
= prefs
->savedirEdit
->text();
245 if (savedir
.isEmpty()) {
246 QMessageBox::information(this,QCoreApplication::applicationName(),
247 tr("Save directory undefined. See preferences."));
252 // clive can use this same approach even if --savedir option exists.
253 process
.setWorkingDirectory(savedir
);
255 // Construct cclive/clive args
261 args
<< "--print-fname";
264 // Set environment variables for clive
265 env
<< "COLUMNS=80" << "LINES=24" // Term::ReadKey
266 << QString("HOME=%1").arg(QDir::homePath()); // $env{HOME}
269 QString s
= prefs
->additionalEdit
->text();
273 s
= prefs
->streamEdit
->text();
274 if (!s
.isEmpty() && streamBox
->isChecked()) {
275 args
<< QString("--stream-exec=%1").arg(s
);
276 args
<< QString("--stream=%1").arg(streamSpin
->value());
279 s
= prefs
->commandEdit
->text();
280 if (!s
.isEmpty() && commandBox
->isChecked()) {
281 if (!s
.endsWith(";"))
283 args
<< QString("--exec=%1").arg(s
);
286 if (prefs
->proxyCombo
->currentIndex() == 0)
287 args
<< "--no-proxy";
289 s
= prefs
->proxyEdit
->text();
291 args
<< QString("--proxy=%1").arg(s
);
294 if (prefs
->limitBox
->checkState()) {
295 int n
= prefs
->limitSpin
->value();
296 args
<< QString("--limit-rate=%1").arg(n
);
299 if (prefs
->timeoutBox
->checkState()) {
300 int n
= prefs
->timeoutSpin
->value();
301 if (!prefs
->socksBox
->checkState())
302 args
<< QString("--connect-timeout=%1").arg(n
);
304 args
<< QString("--connect-timeout-socks=%1").arg(n
);
307 #ifdef MAKE_CONTINUE_CONDITIONAL
308 if (continueBox
->isChecked())
309 args
<< "--continue";
311 args
<< "--continue";
314 if (_isCclive
) { // clive defaults to this
315 if (titleBox
->isChecked()) {
317 s
= prefs
->cclassEdit
->text();
319 args
<< QString("--cclass=%1").arg(s
);
321 } else { // this clive can use
322 s
= prefs
->cclassEdit
->text();
324 args
<< QString("--cclass=%1").arg(s
);
327 s
= formatCombo
->currentText();
331 args
<< QString("--format=%1").arg(s
);
332 args
<< QString("%1").arg(url
);
337 updateLog("% " +path
+ " " +args
.join(" ")+ "\n");
339 // And finally start the process
342 process
.setEnvironment(env
);
343 process
.setProcessChannelMode(QProcess::MergedChannels
);
344 process
.start(path
,args
);
348 MainWindow::onCancel() {
354 MainWindow::onAbout() {
355 AboutDialog
about(this,prefs
->ccliveEdit
->text());
360 MainWindow::onURLEditingFinished() {
365 MainWindow::onURLReturnPressed() {
370 MainWindow::onFormatStateChanged(int) {
371 QString url
= urlEdit
->toPlainText();
376 #ifdef FLV_CANNOT_RESUME
378 // Disable --continue for the specified hosts if format is "flv".
379 // Make changes based on the video URL.
384 static const struct lookup_s lookup
[] = {
389 const int c
= sizeof(lookup
)/sizeof(struct lookup_s
);
392 for (register int i
=0; i
<c
; ++i
) {
393 if (url
.contains(lookup
[i
].host
)) {
394 if (formatCombo
->currentText() == "flv") {
401 continueBox
->setEnabled(enable
);
403 if (continueBox
->isChecked() && !enable
)
404 continueBox
->setCheckState(Qt::Unchecked
);
405 #endif // FLV_CANNOT_RESUME
409 MainWindow::onPasteURL() {
410 QClipboard
*cb
= QApplication::clipboard();
411 urlEdit
->setText(cb
->text());
416 MainWindow::onProcStarted() {
417 statusBar() ->clearMessage();
418 fileLabel
->setText("-");
419 sizeLabel
->setText("-- / --");
420 rateLabel
->setText("--.-");
421 etaLabel
->setText("--:--");
422 progressBar
->setValue(0);
424 startButton
->setEnabled(false);
425 cancelButton
->setEnabled(true);
427 errorOccurred
= false;
431 MainWindow::onProcError(QProcess::ProcessError err
) {
432 if (err
== QProcess::FailedToStart
) {
433 QString msg
= tr("error: failed to start process");
434 statusBar()->showMessage(msg
);
440 MainWindow::onProcStdoutReady() {
441 // NOTE: We read both channels stdout and stderr.
443 QString::fromLocal8Bit(process
.readAll());
445 QStringList tmp
= newText
.split("\n", QString::SkipEmptyParts
);
449 QString status
, last
= tmp
.last();
452 if (last
.startsWith("fetch"))
453 status
= tr("Fetching link...");
454 else if (last
.startsWith("verify") || last
.startsWith("query length") )
455 status
= tr("Verifying video link...");
456 else if (last
.startsWith("error:"))
457 errorOccurred
= true;
459 if (newText
.contains("file:")) {
460 QStringList tmp
= newText
.split(" ", QString::SkipEmptyParts
);
461 fileLabel
->setText(tmp
[1].remove("\n"));
462 status
= tr("Extracting video...");
465 if (!status
.isEmpty())
466 statusBar()->showMessage(status
.remove("\n"));
468 if (last
.contains("%")) {
469 newText
.replace("\n", " ");
471 QStringList tmp
= newText
.split(" ", QString::SkipEmptyParts
);
472 QString percent
= tmp
[1].remove(QChar('%'));
473 QString now
= tmp
[2];
474 QString expected
= tmp
[4];
475 QString rate
= tmp
[5];
476 QString eta
= tmp
[6];
478 sizeLabel
->setText(now
+" / "+ expected
);
479 progressBar
->setValue(percent
.toInt());
480 rateLabel
->setText(rate
);
481 etaLabel
->setText(eta
);
488 MainWindow::onProcFinished(int exitCode
, QProcess::ExitStatus exitStatus
) {
490 if (!errorOccurred
) {
491 if (exitStatus
== QProcess::NormalExit
) {
492 status
= exitCode
!= 0
493 ? tr("Process exited with an error; see log")
494 : tr("Process exited normally");
497 ? tr("Process terminated")
498 : tr("Process crashed; see log");
500 updateLog(status
+ ".");
503 status
= tr("error: see log for details");
505 statusBar()->showMessage(status
);
507 startButton
->setEnabled(true);
508 cancelButton
->setEnabled(false);