Example #1
0
QString MetadataDownload::getMXMLPath(QString filename)
{
    QString ret;
    QString xmlname;
    QUrl qurl(filename);
    QString ext = QFileInfo(qurl.path()).suffix();
    xmlname = filename.left(filename.size() - ext.size()) + "mxml";
    QUrl xurl(xmlname);

    if (xmlname.startsWith("myth://"))
    {
        if (qurl.host().toLower() != gCoreContext->GetHostName().toLower() &&
            (qurl.host() != gCoreContext->GetSettingOnHost("BackendServerIP",
                                               gCoreContext->GetHostName())))
        {
            if (RemoteFile::Exists(xmlname))
                ret = xmlname;
        }
        else
        {
            StorageGroup sg;
            QString fn = sg.FindFile(xurl.path());
            if (!fn.isEmpty() && QFile::exists(fn))
                ret = xmlname;
        }
    }
    else
    {
        if (QFile::exists(xmlname))
            ret = xmlname;
    }

    return ret;
}
Example #2
0
QString MetadataDownload::getNFOPath(QString filename)
{
    QString ret;
    QString nfoname;
    QUrl qurl(filename);
    QString ext = QFileInfo(qurl.path()).suffix();
    nfoname = filename.left(filename.size() - ext.size()) + "nfo";
    QUrl nurl(nfoname);

    if (nfoname.startsWith("myth://"))
    {
        if (qurl.host().toLower() != gCoreContext->GetHostName().toLower() &&
            (!gCoreContext->IsThisHost(qurl.host())))
        {
            if (RemoteFile::Exists(nfoname))
                ret = nfoname;
        }
        else
        {
            StorageGroup sg;
            QString fn = sg.FindFile(nurl.path());
            if (!fn.isEmpty() && QFile::exists(fn))
                ret = nfoname;
        }
    }
    else
    {
        if (QFile::exists(nfoname))
            ret = nfoname;
    }

    return ret;
}
Example #3
0
QString StorageGroup::FindFileDir(QString filename)
{
    QString result = "";
    QFileInfo checkFile("");

    int curDir = 0;
    while (curDir < m_dirlist.size())
    {
        QString testFile = m_dirlist[curDir] + "/" + filename;
        LOG(VB_FILE, LOG_DEBUG, LOC +
            QString("FindFileDir: Checking '%1' for '%2'")
                .arg(m_dirlist[curDir]).arg(testFile));
        checkFile.setFile(testFile);
        if (checkFile.exists() || checkFile.isSymLink())
        {
            QString tmp = m_dirlist[curDir];
            tmp.detach();
            return tmp;
        }

        curDir++;
    }

    if (m_groupname.isEmpty() || (m_allowFallback == false))
    {
        // Not found in any dir, so try RecordFilePrefix if it exists
        QString tmpFile =
            gCoreContext->GetSetting("RecordFilePrefix") + "/" + filename;
        checkFile.setFile(tmpFile);
        if (checkFile.exists() || checkFile.isSymLink())
            result = tmpFile;
    }
    else if (m_groupname != "Default")
    {
        // Not found in current group so try Default
        StorageGroup sgroup("Default");
        QString tmpFile = sgroup.FindFileDir(filename);
        result = (tmpFile.isEmpty()) ? result : tmpFile;
    }
    else
    {
        // Not found in Default so try any dir
        StorageGroup sgroup;
        QString tmpFile = sgroup.FindFileDir(filename);
        result = (tmpFile.isEmpty()) ? result : tmpFile;
    }

    result.detach();
    return result;
}
Example #4
0
/** \fn     ImageUtils::GetStorageDirs()
 *  \brief  Gets the available storage groups
 *  \return List of all available storage groups
 */
QStringList ImageUtils::GetStorageDirs()
{
    QStringList sgDirList;

    // The name that shall be used for the images storage group. It must be
    // specified because its not part of the default storage group names
    QString sgName = gCoreContext->GetSetting("GalleryStorageGroupName");

    if (!sgName.isEmpty())
    {
        QString host = gCoreContext->GetHostName();

        // Search for the specified dirs in the defined storage group.
        // If there is no such storage group then don't use the fallback
        // and don't get the default storage group name of "/mnt/store".
        // The list will be empty. The user has to check the settings.
        StorageGroup sg;
        sg.Init(sgName, host, false);
        sgDirList = sg.GetDirList();
    }

    return sgDirList;
}
QString getLocalStorageGroupPath(VideoArtworkType type, QString host)
{
    QString path;

    StorageGroup sg;

    if (type == kArtworkCoverart)
        sg.Init("Coverart", host);
    else if (type == kArtworkFanart)
        sg.Init("Fanart", host);
    else if (type == kArtworkBanner)
        sg.Init("Banners", host);
    else if (type == kArtworkScreenshot)
        sg.Init("Screenshots", host);
    else
        sg.Init("Default", host);

    path = sg.FindNextDirMostFree();

    return path;
}
Example #6
0
QString FileServerHandler::LocalFilePath(const QUrl &url,
                                           const QString &wantgroup)
{
    QString lpath = url.path();

    if (lpath.section('/', -2, -2) == "channels")
    {
        // This must be an icon request. Check channel.icon to be safe.
        QString querytext;

        QString file = lpath.section('/', -1);
        lpath = "";

        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("SELECT icon FROM channel WHERE icon LIKE :FILENAME ;");
        query.bindValue(":FILENAME", QString("%/") + file);

        if (query.exec() && query.next())
        {
            lpath = query.value(0).toString();
        }
        else
        {
            MythDB::DBError("Icon path", query);
        }
    }
    else
    {
        lpath = lpath.section('/', -1);

        QString fpath = lpath;
        if (fpath.right(4) == ".png")
            fpath = fpath.left(fpath.length() - 4);

        ProgramInfo pginfo(fpath);
        if (pginfo.GetChanID())
        {
            QString pburl = GetPlaybackURL(&pginfo);
            if (pburl.left(1) == "/")
            {
                lpath = pburl.section('/', 0, -2) + "/" + lpath;
                LOG(VB_FILE, LOG_INFO,
                    QString("Local file path: %1").arg(lpath));
            }
            else
            {
                LOG(VB_GENERAL, LOG_ERR,
                        QString("LocalFilePath unable to find local "
                                "path for '%1', found '%2' instead.")
                                .arg(lpath).arg(pburl));
                lpath = "";
            }
        }
        else if (!lpath.isEmpty())
        {
            // For securities sake, make sure filename is really the pathless.
            QString opath = lpath;
            StorageGroup sgroup;

            if (!wantgroup.isEmpty())
            {
                sgroup.Init(wantgroup);
                lpath = url.toString();
            }
            else
            {
                lpath = QFileInfo(lpath).fileName();
            }

            QString tmpFile = sgroup.FindFile(lpath);
            if (!tmpFile.isEmpty())
            {
                lpath = tmpFile;
                LOG(VB_FILE, LOG_INFO,
                        QString("LocalFilePath(%1 '%2'), found through "
                                "exhaustive search at '%3'")
                            .arg(url.toString()).arg(opath).arg(lpath));
            }
            else
            {
                LOG(VB_GENERAL, LOG_ERR, QString("LocalFilePath unable to "
                                                 "find local path for '%1'.")
                                .arg(opath));
                lpath = "";
            }

        }
        else
        {
            lpath = "";
        }
    }

    return lpath;
}
void MetadataImageDownload::run()
{
    RunProlog();

    // Always handle thumbnails first, they're higher priority.
    ThumbnailData *thumb;
    while ((thumb = moreThumbs()) != NULL)
    {
        QString sFilename = getDownloadFilename(thumb->title, thumb->url);

        bool exists = QFile::exists(sFilename);
        if (!exists && !thumb->url.isEmpty())
        {
            if (!GetMythDownloadManager()->download(thumb->url, sFilename))
            {
                LOG(VB_GENERAL, LOG_ERR,
                    QString("MetadataImageDownload: failed to download thumbnail from: %1")
                    .arg(thumb->url));

                delete thumb;
                continue;
            }
        }

        // inform parent we have thumbnail ready for it
        if (QFile::exists(sFilename) && m_parent)
        {
            LOG(VB_GENERAL, LOG_DEBUG,
                    QString("Threaded Image Thumbnail Download: %1")
                    .arg(sFilename));
            thumb->url = sFilename;
            QCoreApplication::postEvent(m_parent,
                           new ThumbnailDLEvent(thumb));
        }
        else
            delete thumb;
    }

    MetadataLookup *lookup;
    while ((lookup = moreDownloads()) != NULL)
    {
        DownloadMap downloads = lookup->GetDownloads();
        DownloadMap downloaded;

        for (DownloadMap::iterator i = downloads.begin();
                i != downloads.end(); ++i)
        {
            VideoArtworkType type = i.key();
            ArtworkInfo info = i.value();
            QString filename = getDownloadFilename( type, lookup,
                                   info.url );
            if (lookup->GetHost().isEmpty())
            {
                QString path = getLocalWritePath(lookup->GetType(), type);
                QDir dirPath(path);
                if (!dirPath.exists())
                    if (!dirPath.mkpath(path))
                    {
                        LOG(VB_GENERAL, LOG_ERR,
                            QString("Metadata Image Download: Unable to create "
                                    "path %1, aborting download.").arg(path));
                        QCoreApplication::postEvent(m_parent,
                                    new ImageDLFailureEvent(lookup));
                        continue;
                    }
                QString finalfile = path + "/" + filename;
                QString oldurl = info.url;
                info.url = finalfile;
                if (!QFile::exists(finalfile) || lookup->GetAllowOverwrites())
                {
                    QFile dest_file(finalfile);
                    if (dest_file.exists())
                    {
                        QFileInfo fi(finalfile);
                        GetMythUI()->RemoveFromCacheByFile(fi.fileName());
                        dest_file.remove();
                    }

                    LOG(VB_GENERAL, LOG_INFO,
                        QString("Metadata Image Download: %1 ->%2")
                         .arg(oldurl).arg(finalfile));
                    QByteArray *download = new QByteArray();
                    GetMythDownloadManager()->download(oldurl, download);

                    QImage testImage;
                    bool didLoad = testImage.loadFromData(*download);
                    if (!didLoad)
                    {
                        LOG(VB_GENERAL, LOG_ERR,
                            QString("Tried to write %1, but it appears to be "
                                    "an HTML redirect (filesize %2).")
                                .arg(oldurl).arg(download->size()));
                        delete download;
                        download = NULL;
                        QCoreApplication::postEvent(m_parent,
                                    new ImageDLFailureEvent(lookup));
                        continue;
                    }

                    if (dest_file.open(QIODevice::WriteOnly))
                    {
                        off_t size = dest_file.write(*download,
                                                     download->size());
                        if (size != download->size())
                        {
                            LOG(VB_GENERAL, LOG_ERR,
                                QString("Image Download: Error Writing Image "
                                        "to file: %1").arg(finalfile));
                            QCoreApplication::postEvent(m_parent,
                                        new ImageDLFailureEvent(lookup));
                        }
                        else
                            downloaded.insert(type, info);
                    }

                    delete download;
                }
                else
                    downloaded.insert(type, info);
            }
            else
            {
                QString path = getStorageGroupURL(type, lookup->GetHost());
                QString finalfile = path + filename;
                QString oldurl = info.url;
                info.url = finalfile;
                bool exists = false;
                bool onMaster = false;
                QString resolvedFN;
                if ((lookup->GetHost().toLower() == gCoreContext->GetHostName().toLower()) ||
                    (gCoreContext->IsThisHost(lookup->GetHost())))
                {
                    StorageGroup sg;
                    resolvedFN = sg.FindFile(filename);
                    exists = QFile::exists(resolvedFN);
                    if (!exists)
                    {
                        resolvedFN = getLocalStorageGroupPath(type,
                                                 lookup->GetHost()) + "/" + filename;
                    }
                    onMaster = true;
                }
                else
                    exists = RemoteFile::Exists(finalfile);

                if (!exists || lookup->GetAllowOverwrites())
                {

                    if (exists && !onMaster)
                    {
                        QFileInfo fi(finalfile);
                        GetMythUI()->RemoveFromCacheByFile(fi.fileName());
                        RemoteFile::DeleteFile(finalfile);
                    }
                    else if (exists)
                        QFile::remove(resolvedFN);

                    LOG(VB_GENERAL, LOG_INFO,
                        QString("Metadata Image Download: %1 -> %2")
                            .arg(oldurl).arg(finalfile));
                    QByteArray *download = new QByteArray();
                    GetMythDownloadManager()->download(oldurl, download);

                    QImage testImage;
                    bool didLoad = testImage.loadFromData(*download);
                    if (!didLoad)
                    {
                        LOG(VB_GENERAL, LOG_ERR,
                            QString("Tried to write %1, but it appears to be "
                                    "an HTML redirect or corrupt file "
                                    "(filesize %2).")
                                .arg(oldurl).arg(download->size()));
                        delete download;
                        download = NULL;
                        QCoreApplication::postEvent(m_parent,
                                    new ImageDLFailureEvent(lookup));
                        continue;
                    }

                    if (!onMaster)
                    {
                        RemoteFile *outFile = new RemoteFile(finalfile, true);
                        if (!outFile->isOpen())
                        {
                            LOG(VB_GENERAL, LOG_ERR,
                                QString("Image Download: Failed to open "
                                        "remote file (%1) for write.  Does "
                                        "Storage Group Exist?")
                                        .arg(finalfile));
                            delete outFile;
                            outFile = NULL;
                            QCoreApplication::postEvent(m_parent,
                                        new ImageDLFailureEvent(lookup));
                        }
                        else
                        {
                            off_t written = outFile->Write(*download,
                                                           download->size());
                            if (written != download->size())
                            {
                                LOG(VB_GENERAL, LOG_ERR,
                                    QString("Image Download: Error Writing Image "
                                            "to file: %1").arg(finalfile));
                                QCoreApplication::postEvent(m_parent,
                                        new ImageDLFailureEvent(lookup));
                            }
                            else
                                downloaded.insert(type, info);
                            delete outFile;
                            outFile = NULL;
                        }
                    }
                    else
                    {
                        QFile dest_file(resolvedFN);
                        if (dest_file.open(QIODevice::WriteOnly))
                        {
                            off_t size = dest_file.write(*download,
                                                         download->size());
                            if (size != download->size())
                            {
                                LOG(VB_GENERAL, LOG_ERR,
                                    QString("Image Download: Error Writing Image "
                                            "to file: %1").arg(finalfile));
                                QCoreApplication::postEvent(m_parent,
                                            new ImageDLFailureEvent(lookup));
                            }
                            else
                                downloaded.insert(type, info);
                        }
                    }

                    delete download;
                }
                else
                    downloaded.insert(type, info);
            }
        }
        lookup->SetDownloads(downloaded);
        QCoreApplication::postEvent(m_parent, new ImageDLEvent(lookup));
    }

    RunEpilog();
}
Example #8
0
bool PreviewGenerator::Run(void)
{
    bool ok = false;
    if (!IsLocal())
    {
        if (!localOnly)
        {
            ok = RemotePreviewRun();
        }
        else
        {
            VERBOSE(VB_IMPORTANT, LOC_ERR +
                    QString("Run() file not local: '%1'")
                    .arg(pathname));
        }
    }
    else
    {
        // This is where we fork and run mythbackend to actually make preview
        QString command = gContext->GetInstallPrefix() +
                                    "/bin/mythbackend --generate-preview ";
        command += QString("%1x%2")
            .arg(outSize.width()).arg(outSize.height());
        if (captureTime >= 0)
            command += QString("@%1%2")
                .arg(captureTime).arg(timeInSeconds ? "s" : "f");
        command += " ";
        command += QString("--chanid %1 ").arg(programInfo.chanid);
        command += QString("--starttime %1 ")
            .arg(programInfo.recstartts.toString("yyyyMMddhhmmss"));
        if (!outFileName.isEmpty())
            command += QString("--outfile \"%1\" ").arg(outFileName);

        int ret = myth_system(command);
        if (ret)
        {
            VERBOSE(VB_IMPORTANT, LOC_ERR + "Encountered problems running " +
                    QString("'%1'").arg(command));
        }
        else
        {
            VERBOSE(VB_PLAYBACK, LOC + "Preview process returned 0.");
            QString outname = (!outFileName.isEmpty()) ?
                outFileName : (pathname + ".png");

            QString lpath = QFileInfo(outname).fileName();
            if (lpath == outname)
            {
                StorageGroup sgroup;
                QString tmpFile = sgroup.FindRecordingFile(lpath);
                outname = (tmpFile.isEmpty()) ? outname : tmpFile;
            }

            QFileInfo fi(outname);
            ok = (fi.exists() && fi.isReadable() && fi.size());
            if (ok)
                VERBOSE(VB_PLAYBACK, LOC + "Preview process ran ok.");
            else
            {
                VERBOSE(VB_IMPORTANT, LOC_ERR + "Preview process not ok." +
                        QString("\n\t\t\tfileinfo(%1)").arg(outname)
                        <<" exists: "<<fi.exists()
                        <<" readable: "<<fi.isReadable()
                        <<" size: "<<fi.size());
            }
        }
    }

    if (ok)
    {
        QMutexLocker locker(&previewLock);
        emit previewReady(&programInfo);
    }

    return ok;
}
Example #9
0
bool PreviewGenerator::Run(void)
{
    QString msg;
    QDateTime dtm = QDateTime::currentDateTime();
    QTime tm = QTime::currentTime();
    bool ok = false;
    QString command = GetInstallPrefix() + "/bin/mythpreviewgen";
    bool local_ok = ((IsLocal() || !!(mode & kForceLocal)) &&
                     (!!(mode & kLocal)) &&
                     QFileInfo(command).isExecutable());
    if (!local_ok)
    {
        if (!!(mode & kRemote))
        {
            ok = RemotePreviewRun();
            if (ok)
            {
                msg =
                    QString("Generated remotely in %1 seconds, starting at %2")
                    .arg(tm.elapsed()*0.001)
                    .arg(tm.toString(Qt::ISODate));
            }
        }
        else
        {
            LOG(VB_GENERAL, LOG_ERR, LOC +
                QString("Run() cannot generate preview locally for: '%1'")
                    .arg(pathname));
            msg = "Failed, local preview requested for remote file.";
        }
    }
    else
    {
        // This is where we fork and run mythpreviewgen to actually make preview
        command += QString(" --size %1x%2")
            .arg(outSize.width()).arg(outSize.height());
        if (captureTime >= 0)
        {
            if (timeInSeconds)
                command += QString(" --seconds %1").arg(captureTime);
            else
                command += QString(" --frame %1").arg(captureTime);
        }
        command += QString(" --chanid %1").arg(programInfo.GetChanID());
        command += QString(" --starttime %1")
            .arg(programInfo.GetRecordingStartTime(MythDate));

        if (!outFileName.isEmpty())
            command += QString(" --outfile \"%1\"").arg(outFileName);

        command += logPropagateArgs;
        if (!logPropagateQuiet())
            command += " --quiet";

        // Timeout in 30s
        uint ret = myth_system(command, kMSDontBlockInputDevs |
                                        kMSDontDisableDrawing |
                                        kMSProcessEvents, 30);
        if (ret != GENERIC_EXIT_OK)
        {
            LOG(VB_GENERAL, LOG_ERR, LOC + 
                QString("Encountered problems running '%1' (%2)")
                    .arg(command) .arg(ret));
        }
        else
        {
            LOG(VB_PLAYBACK, LOG_INFO, LOC + "Preview process returned 0.");
            QString outname = (!outFileName.isEmpty()) ?
                outFileName : (pathname + ".png");

            QString lpath = QFileInfo(outname).fileName();
            if (lpath == outname)
            {
                StorageGroup sgroup;
                QString tmpFile = sgroup.FindFile(lpath);
                outname = (tmpFile.isEmpty()) ? outname : tmpFile;
            }

            QFileInfo fi(outname);
            ok = (fi.exists() && fi.isReadable() && fi.size());
            if (ok)
            {
                LOG(VB_PLAYBACK, LOG_INFO, LOC + "Preview process ran ok.");
                msg = QString("Generated on %1 in %2 seconds, starting at %3")
                    .arg(gCoreContext->GetHostName())
                    .arg(tm.elapsed()*0.001)
                    .arg(tm.toString(Qt::ISODate));
            }
            else
            {
                LOG(VB_GENERAL, LOG_ERR, LOC + "Preview process not ok." +
                    QString("\n\t\t\tfileinfo(%1)").arg(outname) +
                    QString(" exists: %1").arg(fi.exists()) +
                    QString(" readable: %1").arg(fi.isReadable()) +
                    QString(" size: %1").arg(fi.size()));
                LOG(VB_GENERAL, LOG_ERR, LOC +
                    QString("Despite command '%1' returning success")
                        .arg(command));
                msg = QString("Failed to read preview image despite "
                              "preview process returning success.");
            }
        }
    }

    QMutexLocker locker(&previewLock);

    // Backdate file to start of preview time in case a bookmark was made
    // while we were generating the preview.
    QString output_fn = outFileName.isEmpty() ?
        (programInfo.GetPathname()+".png") : outFileName;

    QDateTime dt;
    if (ok)
    {
        QFileInfo fi(output_fn);
        if (fi.exists())
            dt = fi.lastModified();
    }

    QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
    if (listener)
    {
        QStringList list;
        list.push_back(programInfo.MakeUniqueKey());
        list.push_back(outFileName.isEmpty() ?
                       (programInfo.GetPathname()+".png") : outFileName);
        list.push_back(msg);
        list.push_back(dt.isValid()?dt.toString(Qt::ISODate):"");
        list.push_back(token);
        QCoreApplication::postEvent(listener, new MythEvent(message, list));
    }

    return ok;
}
Example #10
0
bool PreviewGenerator::Run(void)
{
    QString msg;
    QDateTime dtm = MythDate::current();
    QTime tm = QTime::currentTime();
    bool ok = false;
    QString command = GetAppBinDir() + "mythpreviewgen";
    bool local_ok = ((IsLocal() || !!(m_mode & kForceLocal)) &&
                     (!!(m_mode & kLocal)) &&
                     QFileInfo(command).isExecutable());
    if (!local_ok)
    {
        if (!!(m_mode & kRemote))
        {
            ok = RemotePreviewRun();
            if (ok)
            {
                msg =
                    QString("Generated remotely in %1 seconds, starting at %2")
                    .arg(tm.elapsed()*0.001)
                    .arg(tm.toString(Qt::ISODate));
            }
        }
        else
        {
            LOG(VB_GENERAL, LOG_ERR, LOC +
                QString("Run() cannot generate preview locally for: '%1'")
                    .arg(m_pathname));
            msg = "Failed, local preview requested for remote file.";
        }
    }
    else
    {
        // This is where we fork and run mythpreviewgen to actually make preview
        QStringList cmdargs;

        cmdargs << "--size"
                << QString("%1x%2").arg(m_outSize.width()).arg(m_outSize.height());
        if (m_captureTime >= 0)
        {
            if (m_timeInSeconds)
                cmdargs << "--seconds";
            else
                cmdargs << "--frame";
            cmdargs << QString::number(m_captureTime);
        }
        cmdargs << "--chanid"
                << QString::number(m_programInfo.GetChanID())
                << "--starttime"
                << m_programInfo.GetRecordingStartTime(MythDate::kFilename);

        if (!m_outFileName.isEmpty())
            cmdargs << "--outfile" << m_outFileName;

        // Timeout in 30s
        MythSystemLegacy *ms = new MythSystemLegacy(command, cmdargs,
                                        kMSDontBlockInputDevs |
                                        kMSDontDisableDrawing |
                                        kMSProcessEvents      |
                                        kMSAutoCleanup        |
                                        kMSPropagateLogs);
        ms->SetNice(10);
        ms->SetIOPrio(7);

        ms->Run(30);
        uint ret = ms->Wait();
        delete ms;

        if (ret != GENERIC_EXIT_OK)
        {
            LOG(VB_GENERAL, LOG_ERR, LOC +
                QString("Encountered problems running '%1 %2' - (%3)")
                    .arg(command).arg(cmdargs.join(" ")).arg(ret));
        }
        else
        {
            LOG(VB_PLAYBACK, LOG_INFO, LOC + "Preview process returned 0.");
            QString outname = (!m_outFileName.isEmpty()) ?
                m_outFileName : (m_pathname + ".png");

            QString lpath = QFileInfo(outname).fileName();
            if (lpath == outname)
            {
                StorageGroup sgroup;
                QString tmpFile = sgroup.FindFile(lpath);
                outname = (tmpFile.isEmpty()) ? outname : tmpFile;
            }

            QFileInfo fi(outname);
            ok = (fi.exists() && fi.isReadable() && fi.size());
            if (ok)
            {
                LOG(VB_PLAYBACK, LOG_INFO, LOC + "Preview process ran ok.");
                msg = QString("Generated on %1 in %2 seconds, starting at %3")
                    .arg(gCoreContext->GetHostName())
                    .arg(tm.elapsed()*0.001)
                    .arg(tm.toString(Qt::ISODate));
            }
            else
            {
                LOG(VB_GENERAL, LOG_ERR, LOC + "Preview process not ok." +
                    QString("\n\t\t\tfileinfo(%1)").arg(outname) +
                    QString(" exists: %1").arg(fi.exists()) +
                    QString(" readable: %1").arg(fi.isReadable()) +
                    QString(" size: %1").arg(fi.size()));
                LOG(VB_GENERAL, LOG_ERR, LOC +
                    QString("Despite command '%1' returning success")
                        .arg(command));
                msg = QString("Failed to read preview image despite "
                              "preview process returning success.");
            }
        }
    }

    QMutexLocker locker(&m_previewLock);

    // Backdate file to start of preview time in case a bookmark was made
    // while we were generating the preview.
    QString output_fn = m_outFileName.isEmpty() ?
        (m_programInfo.GetPathname()+".png") : m_outFileName;

    QDateTime dt;
    if (ok)
    {
        QFileInfo fi(output_fn);
        if (fi.exists())
            dt = fi.lastModified();
    }

    QString message = (ok) ? "PREVIEW_SUCCESS" : "PREVIEW_FAILED";
    if (m_listener)
    {
        QStringList list;
        list.push_back(QString::number(m_programInfo.GetRecordingID()));
        list.push_back(output_fn);
        list.push_back(msg);
        list.push_back(dt.isValid()?dt.toUTC().toString(Qt::ISODate):"");
        list.push_back(m_token);
        QCoreApplication::postEvent(m_listener, new MythEvent(message, list));
    }

    return ok;
}