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>
32 MainWindow::MainWindow():
36 The word "English" is not meant to be translated literally.
37 Replace the word "English" with target language, e.g. Suomi
38 or Deutsch. Note that the only purpose of this string is to
39 list it as an available language in the Preferences dialog.
41 const QString lang
= tr("English");
46 setWindowIcon(QIcon(":/rc/abby.png"));
48 connect(&process
, SIGNAL(started()),
49 this, SLOT(onProcStarted()));
50 connect(&process
, SIGNAL(error(QProcess::ProcessError
)),
51 this, SLOT(onProcError(QProcess::ProcessError
)));
53 // NOTE: We read both channels stdout and stderr.
54 connect(&process
, SIGNAL(readyReadStandardOutput()),
55 this, SLOT(onProcStdoutReady()));
56 connect(&process
, SIGNAL(readyReadStandardError()),
57 this, SLOT(onProcStdoutReady()));
59 connect(&process
, SIGNAL(finished(int,QProcess::ExitStatus
)),
60 this, SLOT(onProcFinished(int,QProcess::ExitStatus
)));
62 prefs
= new PreferencesDialog(this);
67 MainWindow::isCclive(const QString
& path
, QString
& output
) {
69 process
.setProcessChannelMode(QProcess::MergedChannels
);
70 process
.start(path
, QStringList() << "--version");
73 if (!process
.waitForFinished())
74 qDebug() << path
<< ": " << process
.errorString();
76 output
= QString::fromLocal8Bit(process
.readAll());
77 QStringList lst
= output
.split(" ", QString::SkipEmptyParts
);
78 state
= lst
[0] == "cclive";
84 MainWindow::ccliveSupports(const QString
& buildOption
) {
85 QString output
, path
= prefs
->ccliveEdit
->text();
86 const bool _isCclive
= isCclive(path
,output
);
88 if (!_isCclive
&& buildOption
== "--with-perl")
89 return false; // To keep the titleBox hidden always
91 return output
.contains(buildOption
);
95 MainWindow::updateWidgets() {
96 // Enable widgets based on preferences and other settings.
99 s
= prefs
->streamEdit
->text();
100 streamBox
->setEnabled(!s
.isEmpty());
102 streamBox
->setCheckState(Qt::Unchecked
);
104 s
= prefs
->commandEdit
->text();
105 commandBox
->setEnabled(!s
.isEmpty());
107 commandBox
->setCheckState(Qt::Unchecked
);
109 if (ccliveSupports("--with-perl")) {
112 titleBox
->setCheckState(Qt::Unchecked
);
118 MainWindow::closeEvent(QCloseEvent
*event
) {
124 MainWindow::writeSettings() {
126 s
.beginGroup("MainWindow");
127 s
.setValue("size",size());
128 s
.setValue("pos",pos());
129 s
.setValue("titleBox",titleBox
->checkState());
134 MainWindow::readSettings() {
136 s
.beginGroup("MainWindow");
137 resize(s
.value("size",QSize(525,265)).toSize());
138 move(s
.value("pos",QPoint(200,200)).toPoint());
139 titleBox
->setCheckState(
140 s
.value("titleBox").toBool()
147 MainWindow::updateLog(const QString
& newText
) {
148 QString text
= logEdit
->toPlainText() + newText
;
149 logEdit
->setPlainText(text
);
153 MainWindow::updateFormats() {
155 // Alter widgets dynamically based on the video URL.
157 QString url
= urlEdit
->text();
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::onSaveasStateChanged(int state
) {
205 saveasEdit
->setEnabled(state
!= 0);
206 saveasButton
->setEnabled(state
!= 0);
208 if (state
!= 0 && titleBox
->isChecked())
209 titleBox
->setCheckState(Qt::Unchecked
);
211 titleBox
->setEnabled(state
== 0);
215 MainWindow::onStreamStateChanged(int state
) {
216 streamSpin
->setEnabled(state
!= 0);
220 MainWindow::onSaveasBrowse() {
221 QString fname
= QFileDialog::getSaveFileName(this,tr("Save as"));
222 if (!fname
.isEmpty())
223 saveasEdit
->setText(fname
);
227 MainWindow::onStart() {
230 QString url
= urlEdit
->text();
233 statusBar()->showMessage(tr("Enter a video link."));
239 if (!url
.startsWith("http://",Qt::CaseInsensitive
))
240 url
.insert(0,"http://");
244 QString path
= prefs
->ccliveEdit
->text();
245 if (path
.isEmpty()) {
246 QMessageBox::information(this,QCoreApplication::applicationName(),
247 tr("Path to cclive (or clive) command undefined. "
248 "See preferences."));
254 const bool _isCclive
= isCclive(path
,output
);
256 // Check video save directory
258 // NOTE: cclive itself does not support this concept so we
259 // work around it by changing the working directory to the
262 QString savedir
= prefs
->savedirEdit
->text();
263 if (savedir
.isEmpty()) {
264 QMessageBox::information(this,QCoreApplication::applicationName(),
265 tr("Save directory undefined. See preferences."));
270 // clive can use this same approach even if --savedir option exists.
271 process
.setWorkingDirectory(savedir
);
273 // Construct cclive/clive args
279 args
<< "--print-fname";
281 args
<< "--renew" << "--stderr";
282 // Set environment variables for clive
283 env
<< "COLUMNS=80" << "LINES=24" // Term::ReadKey
284 << QString("HOME=%1").arg(QDir::homePath()); // $env{HOME}
287 QString s
= prefs
->additionalEdit
->text();
291 s
= prefs
->streamEdit
->text();
292 if (!s
.isEmpty() && streamBox
->isChecked()) {
293 args
<< QString("--stream-exec=%1").arg(s
);
294 args
<< QString("--stream=%1").arg(streamSpin
->value());
297 s
= prefs
->commandEdit
->text();
298 if (!s
.isEmpty() && commandBox
->isChecked()) {
299 if (!s
.endsWith(";"))
301 args
<< QString("--exec=%1").arg(s
);
304 if (prefs
->proxyCombo
->currentIndex() == 0)
305 args
<< "--no-proxy";
307 s
= prefs
->proxyEdit
->text();
309 args
<< QString("--proxy=%1").arg(s
);
312 if (prefs
->limitBox
->checkState()) {
313 int n
= prefs
->limitSpin
->value();
314 args
<< QString("--limit-rate=%1").arg(n
);
317 if (prefs
->timeoutBox
->checkState()) {
318 int n
= prefs
->timeoutSpin
->value();
319 if (!prefs
->socksBox
->checkState())
320 args
<< QString("--connect-timeout=%1").arg(n
);
322 args
<< QString("--connect-timeout-socks=%1").arg(n
);
325 if (prefs
->youtubeGroup
->isChecked()) {
326 QString user
= prefs
->ytuserEdit
->text();
327 QString pass
= prefs
->ytpassEdit
->text();
328 if (pass
.isEmpty()) {
329 PasswordDialog
pwd(this);
331 pass
= pwd
.passEdit
->text();
333 if (!user
.isEmpty() && !pass
.isEmpty()) {
334 args
<< QString("--youtube-user=%1").arg(user
);
335 args
<< QString("--youtube-pass=%1").arg(pass
);
339 s
= saveasEdit
->text();
340 if (!s
.isEmpty() && saveasBox
->isChecked())
341 args
<< QString("--output-video=%1").arg(s
);
343 if (continueBox
->isChecked())
344 args
<< "--continue";
346 if (_isCclive
) { // clive defaults to this
347 if (titleBox
->isChecked()) {
349 s
= prefs
->cclassEdit
->text();
351 args
<< QString("--title-cclass=%1").arg(s
);
353 } else { // this clive can use
354 s
= prefs
->cclassEdit
->text();
356 args
<< QString("--cclass=%1").arg(s
);
358 s
= formatCombo
->currentText();
361 args
<< QString("--%1=%2").arg("format").arg(s
);
362 args
<< QString("%1").arg(url
);
367 updateLog("% " +path
+ " " +args
.join(" ")+ "\n");
369 // And finally start the process
372 process
.setEnvironment(env
);
373 process
.setProcessChannelMode(QProcess::MergedChannels
);
374 process
.start(path
,args
);
378 MainWindow::onCancel() {
384 MainWindow::onAbout() {
385 AboutDialog
about(this,prefs
->ccliveEdit
->text());
390 MainWindow::onURLEditingFinished() {
395 MainWindow::onURLReturnPressed() {
400 MainWindow::onFormatStateChanged(int) {
402 // Disable --continue for the specified hosts if format is "flv".
403 // Make changes based on the video URL.
405 QString url
= urlEdit
->text();
412 static const struct lookup_s lookup
[] = {
417 const int c
= sizeof(lookup
)/sizeof(struct lookup_s
);
420 for (register int i
=0; i
<c
; ++i
) {
421 if (url
.contains(lookup
[i
].host
)) {
422 if (formatCombo
->currentText() == "flv") {
429 continueBox
->setEnabled(enable
);
431 if (continueBox
->isChecked() && !enable
)
432 continueBox
->setCheckState(Qt::Unchecked
);
436 MainWindow::onPasteURL() {
437 QClipboard
*cb
= QApplication::clipboard();
438 urlEdit
->setText(cb
->text());
443 MainWindow::onProcStarted() {
444 statusBar() ->clearMessage();
445 fileLabel
->setText("-");
446 sizeLabel
->setText("-- / --");
447 rateLabel
->setText("--.-");
448 etaLabel
->setText("--:--");
449 progressBar
->setValue(0);
451 startButton
->setEnabled(false);
452 cancelButton
->setEnabled(true);
454 errorOccurred
= false;
458 MainWindow::onProcError(QProcess::ProcessError err
) {
459 if (err
== QProcess::FailedToStart
) {
460 QString msg
= tr("error: failed to start process");
461 statusBar()->showMessage(msg
);
467 MainWindow::onProcStdoutReady() {
468 // NOTE: We read both channels stdout and stderr.
470 QString::fromLocal8Bit(process
.readAll());
472 QStringList tmp
= newText
.split("\n", QString::SkipEmptyParts
);
476 QString status
, last
= tmp
.last();
479 if (last
.startsWith("fetch"))
480 status
= tr("Fetching link...");
481 else if (last
.startsWith("verify") || last
.startsWith("query length") )
482 status
= tr("Verifying video link...");
483 else if (last
.startsWith("error:"))
484 errorOccurred
= true;
486 if (newText
.contains("file:")) {
487 QStringList tmp
= newText
.split(" ", QString::SkipEmptyParts
);
488 fileLabel
->setText(tmp
[1].remove("\n"));
489 status
= tr("Extracting video...");
492 if (!status
.isEmpty())
493 statusBar()->showMessage(status
.remove("\n"));
495 if (last
.contains("%")) {
496 newText
.replace("\n", " ");
498 QStringList tmp
= newText
.split(" ", QString::SkipEmptyParts
);
499 QString percent
= tmp
[1].remove(QChar('%'));
500 QString now
= tmp
[2];
501 QString expected
= tmp
[4];
502 QString rate
= tmp
[5];
503 QString eta
= tmp
[6];
505 sizeLabel
->setText(now
+" / "+ expected
);
506 progressBar
->setValue(percent
.toInt());
507 rateLabel
->setText(rate
);
508 etaLabel
->setText(eta
);
515 MainWindow::onProcFinished(int exitCode
, QProcess::ExitStatus exitStatus
) {
517 if (!errorOccurred
) {
518 if (exitStatus
== QProcess::NormalExit
) {
519 status
= exitCode
!= 0
520 ? tr("Process exited with an error; see log")
521 : tr("Process exited normally");
524 ? tr("Process terminated")
525 : tr("Process crashed; see log");
527 updateLog(status
+ ".");
530 status
= tr("error: see log for details");
532 statusBar()->showMessage(status
);
534 startButton
->setEnabled(true);
535 cancelButton
->setEnabled(false);