From 7d6f8dd73aff7046fdc57b39790ae88bfb46955d Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 25 Sep 2009 22:48:34 -0400 Subject: Finished framework and added job stubs. --- AnyRip.pro | 18 +++++++-- dvdimagejob.cpp | 15 ++++++-- dvdimagejob.h | 6 ++- dvdimagejobgui.cpp | 33 +++++++++++++++++ dvdimagejobgui.h | 24 ++++++++++++ encodemp4job.cpp | 23 ++++++++++++ encodemp4job.h | 21 +++++++++++ imagegui.cpp | 48 ------------------------ imagegui.h | 25 ------------- job.h | 3 +- jobqueue.cpp | 31 ++++++++++++++++ jobqueue.h | 23 ++++++++++++ main.cpp | 3 -- titleloadjob.cpp | 23 ++++++++++++ titleloadjob.h | 21 +++++++++++ uploadjob.cpp | 22 +++++++++++ uploadjob.h | 20 ++++++++++ video.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++----------- video.h | 31 +++++++++++++--- videoqueue.cpp | 32 ++++++++++++++++ videoqueue.h | 22 +++++++++++ 21 files changed, 436 insertions(+), 115 deletions(-) create mode 100644 dvdimagejobgui.cpp create mode 100644 dvdimagejobgui.h create mode 100644 encodemp4job.cpp create mode 100644 encodemp4job.h delete mode 100644 imagegui.cpp delete mode 100644 imagegui.h create mode 100644 jobqueue.cpp create mode 100644 jobqueue.h create mode 100644 titleloadjob.cpp create mode 100644 titleloadjob.h create mode 100644 uploadjob.cpp create mode 100644 uploadjob.h create mode 100644 videoqueue.cpp create mode 100644 videoqueue.h diff --git a/AnyRip.pro b/AnyRip.pro index a84415f..db8f7a5 100644 --- a/AnyRip.pro +++ b/AnyRip.pro @@ -1,14 +1,24 @@ SOURCES += main.cpp \ dvdimagejob.cpp \ - imagegui.cpp \ + dvdimagejobgui.cpp \ job.cpp \ video.cpp \ - dvddrive.cpp + dvddrive.cpp \ + jobqueue.cpp \ + videoqueue.cpp \ + encodemp4job.cpp \ + uploadjob.cpp \ + titleloadjob.cpp HEADERS += dvdimagejob.h \ - imagegui.h \ + dvdimagejobgui.h \ job.h \ video.h \ - dvddrive.h + dvddrive.h \ + jobqueue.h \ + videoqueue.h \ + encodemp4job.h \ + uploadjob.h \ + titleloadjob.h LIBS += -ldvdcss \ -ldvdread QT += dbus diff --git a/dvdimagejob.cpp b/dvdimagejob.cpp index 98fe537..54e82ca 100644 --- a/dvdimagejob.cpp +++ b/dvdimagejob.cpp @@ -1,5 +1,6 @@ #include "dvdimagejob.h" #include "dvddrive.h" +#include "dvdimagejobgui.h" #include #include #include @@ -10,9 +11,10 @@ #include #include -DVDImageJob::DVDImageJob(Video *video, DVDDrive *dvdDrive) +DVDImageJob::DVDImageJob(Video *video, QString defaultPath) : Job(video), - m_dvdDrive(dvdDrive) + m_dvdDrive(new DVDDrive(this)), //TODO: get from static singleton instance + m_defaultPath(defaultPath) { } @@ -30,10 +32,10 @@ int DVDImageJob::cmpvob(const void *p1, const void *p2) bool DVDImageJob::executeJob() { - return saveImageToPath(QLatin1String("image.iso")); //Fix up + return saveImageToPath(m_defaultPath); } -Video::Jobs DVDImageJob::jobType() +Video::Jobs DVDImageJob::jobType() const { return Video::DVDImage; } @@ -197,3 +199,8 @@ bool DVDImageJob::saveImageToDevice(QIODevice &out) qDebug() << "Success:" << blkno << "blocks copied (" << (long long)blkno * DVDCSS_BLOCK_SIZE << ") of" << discend << "expected"; return true; } + +QWidget* DVDImageJob::gui() +{ + return new DVDImageJobGui(this); +} diff --git a/dvdimagejob.h b/dvdimagejob.h index 4a99f16..d70d414 100644 --- a/dvdimagejob.h +++ b/dvdimagejob.h @@ -9,10 +9,11 @@ class DVDImageJob : public Job { Q_OBJECT public: - DVDImageJob(Video *video, DVDDrive *dvdDrive); + DVDImageJob(Video *video, QString defaultPath); bool saveImageToDevice(QIODevice &out); bool saveImageToPath(const QString &path); - Video::Jobs jobType(); + Video::Jobs jobType() const; + QWidget* gui(); private: static int cmpvob(const void *p1, const void *p2); @@ -20,6 +21,7 @@ private: int32_t start, end; } vobfile; DVDDrive *m_dvdDrive; + QString m_defaultPath; protected: bool executeJob(); diff --git a/dvdimagejobgui.cpp b/dvdimagejobgui.cpp new file mode 100644 index 0000000..7db1dd6 --- /dev/null +++ b/dvdimagejobgui.cpp @@ -0,0 +1,33 @@ +#include "dvdimagejobgui.h" +#include "dvdimagejob.h" +#include "dvddrive.h" +#include +#include +#include +#include +#include + +DVDImageJobGui::DVDImageJobGui(DVDImageJob *job) : + m_first(true) +{ + connect(job, SIGNAL(extractProgress(int,int)), this, SLOT(extractProgress(int,int))); +} +void DVDImageJobGui::extractProgress(int current, int maximum) +{ + if (m_first) { + m_startTime.start(); + m_first = false; + } else { + long long currentBytes = (long long)current * DVDCSS_BLOCK_SIZE; + long long maximumBytes = (long long)maximum * DVDCSS_BLOCK_SIZE; + double bytesPerMillisecond = (double)currentBytes / (double)m_startTime.elapsed(); + long long millisecondsRemaining = (long long)((maximumBytes - currentBytes) / bytesPerMillisecond); + setWindowTitle(QString("%1 of %2 megabytes (%3 mb/s, %4 remaining)") + .arg(QString::number((double)currentBytes / 1024.0 / 1024.0, 'f', 2)) + .arg(QString::number((double)maximumBytes / 1024.0 / 1024.0, 'f', 2)) + .arg(QString::number(bytesPerMillisecond * (1000.0 / 1024.0 / 1024.0), 'f', 2)) + .arg(QTime().addMSecs(millisecondsRemaining).toString())); + } + setMaximum(maximum); + setValue(current); +} diff --git a/dvdimagejobgui.h b/dvdimagejobgui.h new file mode 100644 index 0000000..27aafe5 --- /dev/null +++ b/dvdimagejobgui.h @@ -0,0 +1,24 @@ +#ifndef DVDIMAGEJOBGUI_H +#define DVDIMAGEJOBGUI_H + +#include +#include +class DVDImageJob; + +class DVDImageJobGui : public QProgressBar +{ + Q_OBJECT + +public: + DVDImageJobGui(DVDImageJob *job); + +private: + QTime m_startTime; + bool m_first; + DVDImageJob *m_job; + +private slots: + void extractProgress(int current, int maximum); +}; + +#endif // DVDIMAGEJOBGUI_H diff --git a/encodemp4job.cpp b/encodemp4job.cpp new file mode 100644 index 0000000..84408d3 --- /dev/null +++ b/encodemp4job.cpp @@ -0,0 +1,23 @@ +#include "encodemp4job.h" +#include + +EncodeMP4Job::EncodeMP4Job(Video *video, QString encodePath, QString imagePath) : + Job(video), + m_encodePath(encodePath), + m_imagePath(imagePath) +{ +} +Video::Jobs EncodeMP4Job::jobType() const +{ + return Video::EncodeMP4; +} +bool EncodeMP4Job::executeJob() +{ + //TODO: encode the actual mp4! + return true; +} +QWidget* EncodeMP4Job::gui() +{ + //TODO: make a gui + return new QWidget; +} diff --git a/encodemp4job.h b/encodemp4job.h new file mode 100644 index 0000000..58ecd51 --- /dev/null +++ b/encodemp4job.h @@ -0,0 +1,21 @@ +#ifndef ENCODEMP4JOB_H +#define ENCODEMP4JOB_H + +#include "job.h" +#include "video.h" + +class EncodeMP4Job : public Job +{ + Q_OBJECT +public: + EncodeMP4Job(Video *video, QString encodePath, QString imagePath); + Video::Jobs jobType() const; + QWidget* gui(); +protected: + bool executeJob(); +private: + QString m_encodePath; + QString m_imagePath; +}; + +#endif // ENCODEMP4JOB_H diff --git a/imagegui.cpp b/imagegui.cpp deleted file mode 100644 index 52c5790..0000000 --- a/imagegui.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "imagegui.h" -#include "dvdimagejob.h" -#include "dvddrive.h" -#include -#include -#include -#include -#include -#include - -ImageGui::ImageGui() -{ - m_dvdDrive = new DVDDrive(this); - if (m_dvdDrive->dvdInserted()) { - startImaging(); - } else { - setWindowTitle(tr("Please insert DVD...")); - connect(m_dvdDrive, SIGNAL(dvdAdded()), this, SLOT(startImaging())); - } -} -void ImageGui::startImaging() -{ - setWindowTitle(tr("Starting imaging...")); - Video *video = new Video(m_dvdDrive); - Job *job = video->nextJob(); - m_first = true; - connect(job, SIGNAL(extractProgress(int,int)), this, SLOT(extractProgress(int,int))); - job->runJob(); -} -void ImageGui::extractProgress(int current, int maximum) -{ - if (m_first) { - m_startTime.start(); - m_first = false; - } else { - long long currentBytes = (long long)current * DVDCSS_BLOCK_SIZE; - long long maximumBytes = (long long)maximum * DVDCSS_BLOCK_SIZE; - double bytesPerMillisecond = (double)currentBytes / (double)m_startTime.elapsed(); - long long millisecondsRemaining = (long long)((maximumBytes - currentBytes) / bytesPerMillisecond); - setWindowTitle(QString("AnyRip: %1 of %2 megabytes (%3 mb/s, %4 remaining)") - .arg(QString::number((double)currentBytes / 1024.0 / 1024.0, 'f', 2)) - .arg(QString::number((double)maximumBytes / 1024.0 / 1024.0, 'f', 2)) - .arg(QString::number(bytesPerMillisecond * (1000.0 / 1024.0 / 1024.0), 'f', 2)) - .arg(QTime().addMSecs(millisecondsRemaining).toString())); - } - setMaximum(maximum); - setValue(current); -} diff --git a/imagegui.h b/imagegui.h deleted file mode 100644 index ddd731e..0000000 --- a/imagegui.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef IMAGEGUI_H -#define IMAGEGUI_H - -#include -#include -class DVDDrive; - -class ImageGui : public QProgressBar -{ - Q_OBJECT - -public: - ImageGui(); - -private: - QTime m_startTime; - bool m_first; - DVDDrive *m_dvdDrive; - -private slots: - void extractProgress(int current, int maximum); - void startImaging(); -}; - -#endif // IMAGEGUI_H diff --git a/job.h b/job.h index c0b1750..7717143 100644 --- a/job.h +++ b/job.h @@ -10,7 +10,8 @@ class Job : public QObject Q_OBJECT public: void runJob(); - virtual Video::Jobs jobType() = 0; + virtual Video::Jobs jobType() const = 0; + virtual QWidget* gui() = 0; protected: Job(QObject *parent = 0); virtual bool executeJob() = 0; diff --git a/jobqueue.cpp b/jobqueue.cpp new file mode 100644 index 0000000..c18ffee --- /dev/null +++ b/jobqueue.cpp @@ -0,0 +1,31 @@ +#include "jobqueue.h" +#include "job.h" + +#include + +JobQueue::JobQueue(QObject *parent) : + QObject(parent), + m_jobIsRunning(false) +{ +} +void JobQueue::addJob(Job *job) +{ + m_queue.enqueue(job); + if (!m_jobIsRunning) + runNextJob(); +} +void JobQueue::runNextJob() +{ + if (m_queue.isEmpty()) + return; + m_jobIsRunning = true; + Job *job = m_queue.dequeue(); + qDebug() << "running job" << job->jobType() << "for video" << qobject_cast(job->parent())->title(); + connect(job, SIGNAL(completed(bool)), this, SLOT(jobCompleted())); + job->runJob(); +} +void JobQueue::jobCompleted() +{ + m_jobIsRunning = false; + runNextJob(); +} diff --git a/jobqueue.h b/jobqueue.h new file mode 100644 index 0000000..09fa73f --- /dev/null +++ b/jobqueue.h @@ -0,0 +1,23 @@ +#ifndef JOBQUEUE_H +#define JOBQUEUE_H + +#include +#include +class Job; + +class JobQueue : public QObject +{ + Q_OBJECT + +public: + JobQueue(QObject *parent = 0); + void addJob(Job *job); +private: + QQueue m_queue; + bool m_jobIsRunning; + void runNextJob(); +private slots: + void jobCompleted(); +}; + +#endif // JOBQUEUE_H diff --git a/main.cpp b/main.cpp index 9e8963d..ef28e50 100644 --- a/main.cpp +++ b/main.cpp @@ -1,4 +1,3 @@ -#include "imagegui.h" #include "dvddrive.h" #include @@ -11,7 +10,5 @@ int main(int argc, char *argv[]) a.setApplicationName(QLatin1String("AnyRip")); a.setOrganizationName(QLatin1String("AnyClip")); a.setOrganizationDomain(QLatin1String("anyclip.com")); - ImageGui i; - i.show(); return a.exec(); } diff --git a/titleloadjob.cpp b/titleloadjob.cpp new file mode 100644 index 0000000..f54c9b2 --- /dev/null +++ b/titleloadjob.cpp @@ -0,0 +1,23 @@ +#include "titleloadjob.h" +#include + +TitleLoadJob::TitleLoadJob(Video *video, QString subtitlePath, QString posterPath) : + Job(video), + m_subtitlePath(subtitlePath), + m_posterPath(posterPath) +{ +} +Video::Jobs TitleLoadJob::jobType() const +{ + return Video::TitleLoad; +} +bool TitleLoadJob::executeJob() +{ + //TODO: title load the whole thing + return true; +} +QWidget* TitleLoadJob::gui() +{ + //TODO: make a gui + return new QWidget; +} diff --git a/titleloadjob.h b/titleloadjob.h new file mode 100644 index 0000000..d4ff9ab --- /dev/null +++ b/titleloadjob.h @@ -0,0 +1,21 @@ +#ifndef TITLELOADJOB_H +#define TITLELOADJOB_H + +#include "job.h" +#include "video.h" + +class TitleLoadJob : public Job +{ + Q_OBJECT +public: + TitleLoadJob(Video *video, QString subtitlePath, QString posterPath); + Video::Jobs jobType() const; + QWidget* gui(); +protected: + bool executeJob(); +private: + QString m_subtitlePath; + QString m_posterPath; +}; + +#endif // TITLELOADJOB_H diff --git a/uploadjob.cpp b/uploadjob.cpp new file mode 100644 index 0000000..776db6b --- /dev/null +++ b/uploadjob.cpp @@ -0,0 +1,22 @@ +#include "uploadjob.h" +#include + +UploadJob::UploadJob(Video *video, QString encodePath) : + Job(video), + m_encodePath(encodePath) +{ +} +Video::Jobs UploadJob::jobType() const +{ + return Video::Upload; +} +bool UploadJob::executeJob() +{ + //TODO: upload the mp4! + return true; +} +QWidget* UploadJob::gui() +{ + //TODO: make a gui + return new QWidget; +} diff --git a/uploadjob.h b/uploadjob.h new file mode 100644 index 0000000..ff77e6b --- /dev/null +++ b/uploadjob.h @@ -0,0 +1,20 @@ +#ifndef UPLOADJOB_H +#define UPLOADJOB_H + +#include "job.h" +#include "video.h" + +class UploadJob : public Job +{ + Q_OBJECT +public: + UploadJob(Video *video, QString encodePath); + Video::Jobs jobType() const; + QWidget* gui(); +protected: + bool executeJob(); +private: + QString m_encodePath; +}; + +#endif // UPLOADJOB_H diff --git a/video.cpp b/video.cpp index 9a8527e..a4240b9 100644 --- a/video.cpp +++ b/video.cpp @@ -1,41 +1,104 @@ #include "video.h" #include "job.h" #include "dvdimagejob.h" -#include "dvddrive.h" +#include "encodemp4job.h" +#include "uploadjob.h" +#include "titleloadjob.h" -Video::Video(DVDDrive *dvdDrive) : - m_jobsCompleted(QBitArray(5)), - m_dvdDrive(dvdDrive) +#include + +Video::Video(QString title, QObject *parent) : + QObject(parent), + m_jobsCompleted(QBitArray(6)), + m_jobsInProgress(QBitArray(6)) { - m_title = dvdDrive->dvdName(); + m_title = title; + m_rootPath = QString("%1/AnyRip/%2").arg(QDir::homePath()).arg(m_title); + m_imagePath = QString("%1/%2 - Image.iso").arg(m_rootPath).arg(m_title); + m_encodePath = QString("%1/%2 - Encode.mp4").arg(m_rootPath).arg(m_title); + m_subtitlePath = QString("%1/%2 - Subtitles.srt").arg(m_rootPath).arg(m_title); + m_posterPath = QString("%1/%2 - Poster.jpg").arg(m_rootPath).arg(m_title); } void Video::completedJob(bool success) { Job *job = qobject_cast(sender()); qDebug() << "Job code" << job->jobType() << "completed" << success; m_jobsCompleted.setBit(job->jobType(), success); + m_jobsInProgress.setBit(job->jobType(), false); + emit jobCompleted(job->jobType(), success); delete job; - - if (success) { - //queue next job function - ; - } else { - //failure mechanism - ; +} +DVDImageJob* Video::dvdImageJob() +{ + if (!m_jobsInProgress.at(Video::DVDImage) && !m_jobsCompleted.at(Video::DVDImage)) { + DVDImageJob *job = new DVDImageJob(this, m_imagePath); + connect(job, SIGNAL(completed(bool)), this, SLOT(completedJob(bool))); + m_jobsInProgress.setBit(Video::DVDImage, true); + return job; } + return 0; } -Job* Video::nextJob() +EncodeMP4Job* Video::encodeMP4Job() { - Job *job; - if (!m_jobsCompleted.at(Video::DVDImage)) { - job = new DVDImageJob(this, m_dvdDrive); - } else { - job = 0; //Other jobs... + if (!m_jobsInProgress.at(Video::EncodeMP4) && !m_jobsCompleted.at(Video::EncodeMP4) && m_jobsCompleted.at(Video::DVDImage)) { + EncodeMP4Job *job = new EncodeMP4Job(this, m_encodePath, m_imagePath); + connect(job, SIGNAL(completed(bool)), this, SLOT(completedJob(bool))); + m_jobsInProgress.setBit(Video::EncodeMP4, true); + return job; + } + return 0; +} +UploadJob* Video::uploadJob() +{ + if (!m_jobsInProgress.at(Video::Upload) && !m_jobsCompleted.at(Video::Upload) && m_jobsCompleted.at(Video::EncodeMP4)) { + UploadJob *job = new UploadJob(this, m_encodePath); + connect(job, SIGNAL(completed(bool)), this, SLOT(completedJob(bool))); + m_jobsInProgress.setBit(Video::Upload, true); + return job; + } + return 0; +} +TitleLoadJob* Video::titleLoadJob() +{ + if (!m_jobsInProgress.at(Video::TitleLoad) && !m_jobsCompleted.at(Video::TitleLoad) && m_jobsCompleted.at(Video::Upload) && m_jobsCompleted.at(Video::Subtitle) && m_jobsCompleted.at(Video::Poster)) { + TitleLoadJob *job = new TitleLoadJob(this, m_subtitlePath, m_posterPath); + connect(job, SIGNAL(completed(bool)), this, SLOT(completedJob(bool))); + m_jobsInProgress.setBit(Video::TitleLoad, true); + return job; } - connect(job, SIGNAL(completed(bool)), this, SLOT(completedJob(bool))); - return job; + return 0; +} +QList Video::availableJobs() +{ + QList jobs; + Job *job; + if ((job = dvdImageJob())) + jobs.append(job); + if ((job = encodeMP4Job())) + jobs.append(job); + if ((job = uploadJob())) + jobs.append(job); + if ((job = titleLoadJob())) + jobs.append(job); + return jobs; +} +void Video::setSubtitle(const QIODevice &input) +{ + m_jobsInProgress.setBit(Video::Subtitle, true); + //TODO: save input to subtitle file + m_jobsCompleted.setBit(Video::Subtitle, true); + m_jobsInProgress.setBit(Video::Subtitle, false); + emit jobCompleted(Video::Subtitle, true); +} +void Video::setPoster(const QIODevice &input) +{ + m_jobsInProgress.setBit(Video::Poster, true); + //TODO: save input to subtitle file + m_jobsCompleted.setBit(Video::Poster, true); + m_jobsInProgress.setBit(Video::Poster, false); + emit jobCompleted(Video::Poster, true); } -bool Video::isJobComplete(Jobs job) const +QString Video::title() const { - return m_jobsCompleted.at(job); + return m_title; } diff --git a/video.h b/video.h index 4efee93..0defb42 100644 --- a/video.h +++ b/video.h @@ -3,23 +3,42 @@ #include #include +#include +class QIODevice; +class DVDImageJob; +class EncodeMP4Job; +class UploadJob; +class TitleLoadJob; class Job; -class DVDDrive; + class Video : public QObject { Q_OBJECT public: - Video(DVDDrive *dvdDrive); - enum Jobs { DVDImage, EncodeMP4, Upload, Subtitle, Poster }; - Job* nextJob(); - bool isJobComplete(Jobs job) const; + Video(QString title, QObject *parent = 0); + enum Jobs { DVDImage, EncodeMP4, Upload, TitleLoad, Subtitle, Poster }; + void setSubtitle(const QIODevice &input); + void setPoster(const QIODevice &input); + DVDImageJob* dvdImageJob(); + EncodeMP4Job* encodeMP4Job(); + UploadJob* uploadJob(); + TitleLoadJob* titleLoadJob(); + QList availableJobs(); + QString title() const; private: QBitArray m_jobsCompleted; + QBitArray m_jobsInProgress; QString m_title; - DVDDrive *m_dvdDrive; + QString m_rootPath; + QString m_imagePath; + QString m_encodePath; + QString m_subtitlePath; + QString m_posterPath; private slots: void completedJob(bool success); +signals: + void jobCompleted(Video::Jobs job, bool success); }; #endif // VIDEO_H diff --git a/videoqueue.cpp b/videoqueue.cpp new file mode 100644 index 0000000..f7a5991 --- /dev/null +++ b/videoqueue.cpp @@ -0,0 +1,32 @@ +#include "videoqueue.h" +#include "video.h" +#include "jobqueue.h" +#include "job.h" + +#include + +VideoQueue::VideoQueue(QObject *parent) : + QObject(parent), + m_jobQueues(QVector(4)) +{ + m_jobQueues.insert(Video::DVDImage, new JobQueue(this)); + m_jobQueues.insert(Video::EncodeMP4, new JobQueue(this)); + m_jobQueues.insert(Video::Upload, new JobQueue(this)); + m_jobQueues.insert(Video::TitleLoad, new JobQueue(this)); +} +void VideoQueue::newVideo(Video *video) +{ + connect(video, SIGNAL(jobCompleted(Video::Jobs,bool)), this, SLOT(videoChanged())); + enqueueVideo(video); +} +void VideoQueue::enqueueVideo(Video *video) +{ + foreach(Job *job, video->availableJobs()) { + qDebug() << "enqueueing job type" << job->jobType() << "for video" << video->title(); + m_jobQueues.at(job->jobType())->addJob(job); + } +} +void VideoQueue::videoChanged() +{ + enqueueVideo(qobject_cast(sender())); +} diff --git a/videoqueue.h b/videoqueue.h new file mode 100644 index 0000000..a587c23 --- /dev/null +++ b/videoqueue.h @@ -0,0 +1,22 @@ +#ifndef VIDEOQUEUE_H +#define VIDEOQUEUE_H + +#include +#include +class JobQueue; +class Video; + +class VideoQueue : public QObject +{ + Q_OBJECT +public: + VideoQueue(QObject *parent = 0); + void newVideo(Video *video); +private: + QVector m_jobQueues; + void enqueueVideo(Video *video); +private slots: + void videoChanged(); +}; + +#endif // VIDEOQUEUE_H -- cgit v1.2.3-59-g8ed1b