00001
00002 #include <cmath>
00003
00004
00005 #include <sys/types.h>
00006 #include <sys/stat.h>
00007 #include <unistd.h>
00008 #include <sys/time.h>
00009 #include <sys/types.h>
00010 #include <sys/stat.h>
00011 #include <fcntl.h>
00012 #include <unistd.h>
00013
00014
00015 #include <qfileinfo.h>
00016 #include <qimage.h>
00017 #include <qurl.h>
00018
00019
00020 #include "RingBuffer.h"
00021 #include "NuppelVideoPlayer.h"
00022 #include "previewgenerator.h"
00023 #include "tv_rec.h"
00024 #include "mythsocket.h"
00025 #include "remotefile.h"
00026 #include "storagegroup.h"
00027 #include "util.h"
00028
00029 #define LOC QString("Preview: ")
00030 #define LOC_ERR QString("Preview Error: ")
00031 #define LOC_WARN QString("Preview Warning: ")
00032
00033 const char *PreviewGenerator::kInUseID = "preview_generator";
00034
00066 PreviewGenerator::PreviewGenerator(const ProgramInfo *pginfo,
00067 bool local_only)
00068 : programInfo(*pginfo), localOnly(local_only), isConnected(false),
00069 createSockets(false), serverSock(NULL), pathname(pginfo->pathname),
00070 timeInSeconds(true), captureTime(-1), outFileName(QString::null),
00071 outSize(0,0)
00072 {
00073 if (IsLocal())
00074 return;
00075
00076
00077 QString localFN = programInfo.GetPlaybackURL(false, true);
00078 QString localFNdir = QFileInfo(localFN).dirPath();
00079 if (!(localFN.left(1) == "/" &&
00080 QFileInfo(localFN).exists() &&
00081 QFileInfo(localFNdir).isWritable()))
00082 return;
00083
00084
00085 QString msg = QString(
00086 "'%1' is not local, "
00087 "\n\t\t\treplacing with '%2', which is local.")
00088 .arg(pathname).arg(localFN);
00089 VERBOSE(VB_RECORD, LOC + msg);
00090 pathname = localFN;
00091 }
00092
00093 PreviewGenerator::~PreviewGenerator()
00094 {
00095 TeardownAll();
00096 }
00097
00098 void PreviewGenerator::SetOutputFilename(const QString &fileName)
00099 {
00100 outFileName = QDeepCopy<QString>(fileName);
00101 }
00102
00103 void PreviewGenerator::TeardownAll(void)
00104 {
00105 if (!isConnected)
00106 return;
00107
00108 const QString filename = programInfo.pathname + ".png";
00109
00110 MythTimer t;
00111 t.start();
00112 for (bool done = false; !done;)
00113 {
00114 previewLock.lock();
00115 if (isConnected)
00116 emit previewThreadDone(filename, done);
00117 else
00118 done = true;
00119 previewLock.unlock();
00120 usleep(5000);
00121 }
00122 VERBOSE(VB_PLAYBACK, LOC + "previewThreadDone took "<<t.elapsed()<<"ms");
00123 disconnectSafe();
00124 }
00125
00126 void PreviewGenerator::deleteLater()
00127 {
00128 TeardownAll();
00129 QObject::deleteLater();
00130 }
00131
00132 void PreviewGenerator::AttachSignals(QObject *obj)
00133 {
00134 QMutexLocker locker(&previewLock);
00135 connect(this, SIGNAL(previewThreadDone(const QString&,bool&)),
00136 obj, SLOT( previewThreadDone(const QString&,bool&)));
00137 connect(this, SIGNAL(previewReady(const ProgramInfo*)),
00138 obj, SLOT( previewReady(const ProgramInfo*)));
00139 isConnected = true;
00140 }
00141
00146 void PreviewGenerator::disconnectSafe(void)
00147 {
00148 QMutexLocker locker(&previewLock);
00149 QObject::disconnect(this, NULL, NULL, NULL);
00150 isConnected = false;
00151 }
00152
00156 void PreviewGenerator::Start(void)
00157 {
00158 pthread_create(&previewThread, NULL, PreviewRun, this);
00159
00160 pthread_detach(previewThread);
00161 }
00162
00166 bool PreviewGenerator::RunReal(void)
00167 {
00168 bool ok = false;
00169 bool is_local = IsLocal();
00170 if (is_local && LocalPreviewRun())
00171 {
00172 ok = true;
00173 }
00174 else if (!localOnly)
00175 {
00176 if (is_local)
00177 {
00178 VERBOSE(VB_IMPORTANT, LOC_WARN + "Failed to save preview."
00179 "\n\t\t\tYou may need to check user and group ownership on"
00180 "\n\t\t\tyour frontend and backend for quicker previews.\n"
00181 "\n\t\t\tAttempting to regenerate preview on backend.\n");
00182 }
00183 ok = RemotePreviewRun();
00184 }
00185 else
00186 {
00187 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Run() file not local: '%1'")
00188 .arg(pathname));
00189 }
00190
00191 return ok;
00192 }
00193
00194 bool PreviewGenerator::Run(void)
00195 {
00196 bool ok = false;
00197 if (!IsLocal())
00198 {
00199 if (!localOnly)
00200 {
00201 ok = RemotePreviewRun();
00202 }
00203 else
00204 {
00205 VERBOSE(VB_IMPORTANT, LOC_ERR +
00206 QString("Run() file not local: '%1'")
00207 .arg(pathname));
00208 }
00209 }
00210 else
00211 {
00212
00213 QString command = gContext->GetInstallPrefix() +
00214 "/bin/mythbackend --generate-preview ";
00215 command += QString("%1x%2")
00216 .arg(outSize.width()).arg(outSize.height());
00217 if (captureTime >= 0)
00218 command += QString("@%1%2")
00219 .arg(captureTime).arg(timeInSeconds ? "s" : "f");
00220 command += " ";
00221 command += QString("--chanid %1 ").arg(programInfo.chanid);
00222 command += QString("--starttime %1 ")
00223 .arg(programInfo.recstartts.toString("yyyyMMddhhmmss"));
00224 if (!outFileName.isEmpty())
00225 command += QString("--outfile \"%1\" ").arg(outFileName);
00226
00227 int ret = myth_system(command);
00228 if (ret)
00229 {
00230 VERBOSE(VB_IMPORTANT, LOC_ERR + "Encountered problems running " +
00231 QString("'%1'").arg(command));
00232 }
00233 else
00234 {
00235 VERBOSE(VB_PLAYBACK, LOC + "Preview process returned 0.");
00236 QString outname = (!outFileName.isEmpty()) ?
00237 outFileName : (pathname + ".png");
00238
00239 QString lpath = QFileInfo(outname).fileName();
00240 if (lpath == outname)
00241 {
00242 StorageGroup sgroup;
00243 QString tmpFile = sgroup.FindRecordingFile(lpath);
00244 outname = (tmpFile.isEmpty()) ? outname : tmpFile;
00245 }
00246
00247 QFileInfo fi(outname);
00248 ok = (fi.exists() && fi.isReadable() && fi.size());
00249 if (ok)
00250 VERBOSE(VB_PLAYBACK, LOC + "Preview process ran ok.");
00251 else
00252 {
00253 VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview process not ok." +
00254 QString("\n\t\t\tfileinfo(%1)").arg(outname)
00255 <<" exists: "<<fi.exists()
00256 <<" readable: "<<fi.isReadable()
00257 <<" size: "<<fi.size());
00258 }
00259 }
00260 }
00261
00262 if (ok)
00263 {
00264 QMutexLocker locker(&previewLock);
00265 emit previewReady(&programInfo);
00266 }
00267
00268 return ok;
00269 }
00270
00271 void *PreviewGenerator::PreviewRun(void *param)
00272 {
00273
00274 if (setpriority(PRIO_PROCESS, 0, 9))
00275 VERBOSE(VB_IMPORTANT, LOC + "Setting priority failed." + ENO);
00276 PreviewGenerator *gen = (PreviewGenerator*) param;
00277 gen->createSockets = true;
00278 gen->Run();
00279 gen->deleteLater();
00280 return NULL;
00281 }
00282
00283 bool PreviewGenerator::RemotePreviewSetup(void)
00284 {
00285 QString server = gContext->GetSetting("MasterServerIP", "localhost");
00286 int port = gContext->GetNumSetting("MasterServerPort", 6543);
00287
00288 serverSock = gContext->ConnectServer(NULL, server, port);
00289 return serverSock;
00290 }
00291
00292 bool PreviewGenerator::RemotePreviewRun(void)
00293 {
00294 QStringList strlist = "QUERY_GENPIXMAP";
00295 programInfo.ToStringList(strlist);
00296 strlist.push_back(timeInSeconds ? "s" : "f");
00297 encodeLongLong(strlist, captureTime);
00298 if (outFileName.isEmpty())
00299 {
00300 strlist.push_back("<EMPTY>");
00301 }
00302 else
00303 {
00304 QFileInfo fi(outFileName);
00305 strlist.push_back(fi.fileName());
00306 }
00307 strlist.push_back(QString::number(outSize.width()));
00308 strlist.push_back(QString::number(outSize.height()));
00309
00310 bool ok = false;
00311
00312 if (createSockets)
00313 {
00314 if (!RemotePreviewSetup())
00315 {
00316 VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to open sockets.");
00317 return false;
00318 }
00319
00320 if (serverSock)
00321 {
00322 serverSock->writeStringList(strlist);
00323 ok = serverSock->readStringList(strlist, false);
00324 }
00325
00326 RemotePreviewTeardown();
00327 }
00328 else
00329 {
00330 ok = gContext->SendReceiveStringList(strlist);
00331 }
00332
00333 if (!ok || strlist.empty() || (strlist[0] != "OK"))
00334 {
00335 if (!ok)
00336 {
00337 VERBOSE(VB_IMPORTANT, LOC_ERR +
00338 "Remote Preview failed due to communications error.");
00339 }
00340 else if (strlist.size() > 1)
00341 {
00342 VERBOSE(VB_IMPORTANT, LOC_ERR +
00343 "Remote Preview failed, reason given: " <<strlist[1]);
00344 }
00345 else
00346 {
00347 VERBOSE(VB_IMPORTANT, LOC_ERR +
00348 "Remote Preview failed due to an uknown error.");
00349 }
00350 return false;
00351 }
00352
00353 if (outFileName.isEmpty())
00354 return true;
00355
00356
00357
00358 QString url = QString::null;
00359 QString fn = QFileInfo(outFileName).fileName();
00360 QByteArray data;
00361 ok = false;
00362
00363 QStringList fileNames;
00364 fileNames.push_back(CreateAccessibleFilename(programInfo.pathname, fn));
00365 fileNames.push_back(CreateAccessibleFilename(programInfo.pathname, ""));
00366
00367 QStringList::const_iterator it = fileNames.begin();
00368 for ( ; it != fileNames.end() && (!ok || data.isEmpty()); ++it)
00369 {
00370 data.resize(0);
00371 url = *it;
00372 RemoteFile *rf = new RemoteFile(url, false, 0);
00373 ok = rf->SaveAs(data);
00374 delete rf;
00375 }
00376
00377 if (ok && data.size())
00378 {
00379 QFile file(outFileName);
00380 ok = file.open(IO_Raw|IO_WriteOnly);
00381 if (!ok)
00382 {
00383 VERBOSE(VB_IMPORTANT, QString("Failed to open: '%1'")
00384 .arg(outFileName));
00385 }
00386
00387 off_t offset = 0;
00388 size_t remaining = (ok) ? data.size() : 0;
00389 uint failure_cnt = 0;
00390 while ((remaining > 0) && (failure_cnt < 5))
00391 {
00392 ssize_t written = file.writeBlock(data.data() + offset, remaining);
00393 if (written < 0)
00394 {
00395 failure_cnt++;
00396 usleep(50000);
00397 continue;
00398 }
00399
00400 failure_cnt = 0;
00401 offset += written;
00402 remaining -= written;
00403 }
00404 if (ok && !remaining)
00405 {
00406 VERBOSE(VB_PLAYBACK, QString("Saved: '%1'")
00407 .arg(outFileName));
00408 }
00409 }
00410
00411 return ok && data.size();
00412 }
00413
00414 void PreviewGenerator::RemotePreviewTeardown(void)
00415 {
00416 if (serverSock)
00417 {
00418 serverSock->DownRef();
00419 serverSock = NULL;
00420 }
00421 }
00422
00423 bool PreviewGenerator::SavePreview(QString filename,
00424 const unsigned char *data,
00425 uint width, uint height, float aspect,
00426 int desired_width, int desired_height)
00427 {
00428 if (!data || !width || !height)
00429 return false;
00430
00431 const QImage img((unsigned char*) data,
00432 width, height, 32, NULL, 65536 * 65536,
00433 QImage::LittleEndian);
00434
00435 float ppw = max(desired_width, 0);
00436 float pph = max(desired_height, 0);
00437 bool desired_size_exactly_specified = true;
00438 if ((ppw < 1.0f) && (pph < 1.0f))
00439 {
00440 ppw = gContext->GetNumSetting("PreviewPixmapWidth", 320);
00441 pph = gContext->GetNumSetting("PreviewPixmapHeight", 240);
00442 desired_size_exactly_specified = false;
00443 }
00444
00445 aspect = (aspect <= 0.0f) ? ((float) width) / height : aspect;
00446 pph = (pph < 1.0f) ? (ppw / aspect) : pph;
00447 ppw = (ppw < 1.0f) ? (pph * aspect) : ppw;
00448
00449 if (!desired_size_exactly_specified)
00450 {
00451 if (aspect > ppw / pph)
00452 pph = (ppw / aspect);
00453 else
00454 ppw = (pph * aspect);
00455 }
00456
00457 ppw = max(1.0f, ppw);
00458 pph = max(1.0f, pph);;
00459
00460 QImage small_img = img.smoothScale((int) ppw, (int) pph);
00461
00462 if (small_img.save(filename, "PNG"))
00463 {
00464 chmod(filename.ascii(), 0666);
00465
00466 VERBOSE(VB_PLAYBACK, LOC +
00467 QString("Saved preview '%0' %1x%2")
00468 .arg(filename).arg((int) ppw).arg((int) pph));
00469
00470 return true;
00471 }
00472
00473
00474 QString newfile = filename + ".new";
00475 if (QFileInfo(filename.ascii()).exists() &&
00476 small_img.save(newfile.ascii(), "PNG"))
00477 {
00478 chmod(newfile.ascii(), 0666);
00479 rename(newfile.ascii(), filename.ascii());
00480
00481 VERBOSE(VB_PLAYBACK, LOC +
00482 QString("Saved preview '%0' %1x%2")
00483 .arg(filename).arg((int) ppw).arg((int) pph));
00484
00485 return true;
00486 }
00487
00488
00489 return false;
00490 }
00491
00492 bool PreviewGenerator::LocalPreviewRun(void)
00493 {
00494 programInfo.MarkAsInUse(true, kInUseID);
00495
00496 float aspect = 0;
00497 int len, width, height, sz;
00498 long long captime = captureTime;
00499 if (captime < 0)
00500 {
00501 timeInSeconds = true;
00502 captime = (gContext->GetNumSetting("PreviewPixmapOffset", 64) +
00503 gContext->GetNumSetting("RecordPreRoll", 0));
00504 }
00505
00506 len = width = height = sz = 0;
00507 unsigned char *data = (unsigned char*)
00508 GetScreenGrab(&programInfo, pathname,
00509 captime, timeInSeconds,
00510 sz, width, height, aspect);
00511
00512 QString outname = CreateAccessibleFilename(pathname, outFileName);
00513
00514 int dw = (outSize.width() < 0) ? width : outSize.width();
00515 int dh = (outSize.height() < 0) ? height : outSize.height();
00516
00517 bool ok = SavePreview(outname, data, width, height, aspect, dw, dh);
00518
00519 if (data)
00520 delete[] data;
00521
00522 programInfo.MarkAsInUse(false);
00523
00524 return ok;
00525 }
00526
00527 QString PreviewGenerator::CreateAccessibleFilename(
00528 const QString &pathname, const QString &outFileName)
00529 {
00530 QString outname = pathname + ".png";
00531
00532 if (outFileName.isEmpty())
00533 return QDeepCopy<QString>(outname);
00534
00535 outname = outFileName;
00536 QFileInfo fi(outname);
00537 if (outname == fi.fileName())
00538 {
00539 QString dir = QString::null;
00540 if (pathname.contains(":"))
00541 {
00542 QUrl uinfo(pathname);
00543 uinfo.setPath("");
00544 dir = uinfo.toString();
00545 }
00546 else
00547 {
00548 dir = QFileInfo(pathname).dirPath();
00549 }
00550 outname = dir + "/" + fi.fileName();
00551 VERBOSE(VB_IMPORTANT, LOC + QString("outfile '%1' -> '%2'")
00552 .arg(outFileName).arg(outname));
00553 }
00554
00555 return QDeepCopy<QString>(outname);
00556 }
00557
00558 bool PreviewGenerator::IsLocal(void) const
00559 {
00560 QString pathdir = QFileInfo(pathname).dirPath();
00561 return (QFileInfo(pathname).exists() && QFileInfo(pathdir).isWritable());
00562 }
00563
00580 char *PreviewGenerator::GetScreenGrab(
00581 const ProgramInfo *pginfo, const QString &filename,
00582 long long seektime, bool time_in_secs,
00583 int &bufferlen,
00584 int &video_width, int &video_height, float &video_aspect)
00585 {
00586 (void) pginfo;
00587 (void) filename;
00588 (void) seektime;
00589 (void) time_in_secs;
00590 (void) bufferlen;
00591 (void) video_width;
00592 (void) video_height;
00593 char *retbuf = NULL;
00594 bufferlen = 0;
00595 #ifdef USING_FRONTEND
00596 if (!MSqlQuery::testDBConnection())
00597 {
00598 VERBOSE(VB_IMPORTANT, LOC_ERR + "Previewer could not connect to DB.");
00599 return NULL;
00600 }
00601
00602
00603 if (filename.left(1)=="/")
00604 {
00605 QFileInfo info(filename);
00606 bool invalid = !info.exists() || !info.isReadable() || !info.isFile();
00607 if (!invalid)
00608 {
00609
00610 unsigned long long fsize =
00611 myth_get_approximate_large_file_size(filename);
00612 invalid = (fsize < 8*1024);
00613 }
00614 if (invalid)
00615 {
00616 VERBOSE(VB_IMPORTANT, LOC_ERR + "Previewer file " +
00617 QString("'%1'").arg(filename) + " is not valid.");
00618 return NULL;
00619 }
00620 }
00621
00622 RingBuffer *rbuf = new RingBuffer(filename, false, false, 0);
00623 if (!rbuf->IsOpen())
00624 {
00625 VERBOSE(VB_IMPORTANT, LOC_ERR + "Previewer could not open file: " +
00626 QString("'%1'").arg(filename));
00627 delete rbuf;
00628 return NULL;
00629 }
00630
00631 NuppelVideoPlayer *nvp = new NuppelVideoPlayer(kInUseID, pginfo);
00632 nvp->SetRingBuffer(rbuf);
00633
00634 if (time_in_secs)
00635 retbuf = nvp->GetScreenGrab(seektime, bufferlen,
00636 video_width, video_height, video_aspect);
00637 else
00638 retbuf = nvp->GetScreenGrabAtFrame(
00639 seektime, true, bufferlen,
00640 video_width, video_height, video_aspect);
00641
00642 delete nvp;
00643 delete rbuf;
00644
00645 #else // USING_FRONTEND
00646 QString msg = "Backend compiled without USING_FRONTEND !!!!";
00647 VERBOSE(VB_IMPORTANT, LOC_ERR + msg);
00648 #endif // USING_FRONTEND
00649
00650 if (retbuf)
00651 {
00652 VERBOSE(VB_GENERAL, LOC +
00653 QString("Grabbed preview '%0' %1x%2@%3%4")
00654 .arg(filename).arg(video_width).arg(video_height)
00655 .arg((Q_LLONG)seektime).arg((time_in_secs) ? "s" : "f"));
00656 }
00657
00658 return retbuf;
00659 }
00660
00661