MythTV  master
imagethumbs.cpp
Go to the documentation of this file.
1 #include "imagethumbs.h"
2 
3 #include <QDir>
4 #include <QStringList>
5 
6 #include "mythlogging.h"
7 #include "mythcorecontext.h" // for events
8 #include "mythsystemlegacy.h" // for previewgen
9 #include "mythdirs.h" // for previewgen
10 #include "exitcodes.h" // for previewgen
11 #include "mythimage.h"
12 
13 #include "imagemetadata.h"
14 
18 template <class DBFS>
20 {
21  cancel();
22  wait();
23 }
24 
25 
29 template <class DBFS>
31 {
32  // Clear all queues
33  QMutexLocker locker(&m_mutex);
34  m_requestQ.clear();
35  m_backgroundQ.clear();
36 }
37 
38 
43 template <class DBFS>
45 {
46  if (task)
47  {
48  bool background = task->m_priority > kBackgroundPriority;
49 
50  QMutexLocker locker(&m_mutex);
51  if (background)
52  m_backgroundQ.insert(task->m_priority, task);
53  else
54  m_requestQ.insert(task->m_priority, task);
55 
56  // restart if not already running
57  if ((m_doBackground || !background) && !this->isRunning())
58  this->start();
59  }
60 }
61 
62 
67 template <class DBFS>
68 void ThumbThread<DBFS>::AbortDevice(int devId, const QString &action)
69 {
70  if (action == "DEVICE CLOSE ALL" || action == "DEVICE CLEAR ALL")
71  {
72  if (isRunning())
73  LOG(VB_FILE, LOG_INFO,
74  QString("Aborting all thumbnails %1").arg(action));
75 
76  // Abort thumbnail generation for all devices
77  cancel();
78  return;
79  }
80 
81  LOG(VB_FILE, LOG_INFO, QString("Aborting thumbnails (%1) for '%2'")
82  .arg(action).arg(m_dbfs.DeviceName(devId)));
83 
84  // Remove all tasks for specific device from every queue
85  QMutexLocker locker(&m_mutex);
86  RemoveTasks(m_requestQ, devId);
87  RemoveTasks(m_backgroundQ, devId);
88  if (isRunning())
89  // Wait until current task is complete - it may be using the device
90  m_taskDone.wait(&m_mutex, 3000);
91 }
92 
93 
97 template <class DBFS>
99 {
100  QMutableMapIterator<int, TaskPtr> it(queue);
101  while (it.hasNext())
102  {
103  it.next();
104  TaskPtr task = it.value();
105  // All thumbs in a task come from same device
106  if (task && !task->m_images.isEmpty()
107  && task->m_images.at(0)->m_device == devId)
108  it.remove();
109  }
110 }
111 
112 
119 template <class DBFS>
121 {
122  RunProlog();
123 
124  setPriority(QThread::LowestPriority);
125 
126  while (true)
127  {
128  // Signal previous task is complete (its files have been closed)
129  m_taskDone.wakeAll();
130 
131  // Do all we can to run in background
132  QThread::yieldCurrentThread();
133 
134  // process next highest-priority task
135  TaskPtr task;
136  {
137  QMutexLocker locker(&m_mutex);
138  if (!m_requestQ.isEmpty())
139  task = m_requestQ.take(m_requestQ.constBegin().key());
140  else if (m_doBackground && !m_backgroundQ.isEmpty())
141  task = m_backgroundQ.take(m_backgroundQ.constBegin().key());
142  else
143  // quit when both queues exhausted
144  break;
145  }
146 
147  // Shouldn't receive empty requests
148  if (task->m_images.isEmpty())
149  continue;
150 
151  if (task->m_action == "CREATE")
152  {
153  ImagePtrK im = task->m_images.at(0);
154 
155  QString err = CreateThumbnail(im, task->m_priority);
156 
157  if (!err.isEmpty())
158  {
159  LOG(VB_GENERAL, LOG_ERR, QString("%1").arg(err));
160  }
161  else if (task->m_notify)
162  {
163  // notify clients when done
164  m_dbfs.Notify("THUMB_AVAILABLE",
165  QStringList(QString::number(im->m_id)));
166  }
167  }
168  else if (task->m_action == "DELETE")
169  {
170  for (const auto& im : qAsConst(task->m_images))
171  {
172  QString thumbnail = im->m_thumbPath;
173  if (!QDir::root().remove(thumbnail))
174  {
175  LOG(VB_FILE, LOG_WARNING,
176  QString("Failed to delete thumbnail %1").arg(thumbnail));
177  continue;
178  }
179  LOG(VB_FILE, LOG_DEBUG,
180  QString("Deleted thumbnail %1").arg(thumbnail));
181 
182  // Clean up empty dirs
183  QString path = QFileInfo(thumbnail).path();
184  if (QDir::root().rmpath(path))
185  LOG(VB_FILE, LOG_DEBUG,
186  QString("Cleaned up path %1").arg(path));
187  }
188  }
189  else if (task->m_action == "MOVE")
190  {
191  for (const auto& im : qAsConst(task->m_images))
192  {
193  // Build new thumb path
194  QString newThumbPath =
195  m_dbfs.GetAbsThumbPath(m_dbfs.ThumbDir(im->m_device),
196  m_dbfs.ThumbPath(*im.data()));
197 
198  // Ensure path exists
199  if (QDir::root().mkpath(QFileInfo(newThumbPath).path())
200  && QFile::rename(im->m_thumbPath, newThumbPath))
201  {
202  LOG(VB_FILE, LOG_DEBUG, QString("Moved thumbnail %1 -> %2")
203  .arg(im->m_thumbPath, newThumbPath));
204  }
205  else
206  {
207  LOG(VB_FILE, LOG_WARNING,
208  QString("Failed to rename thumbnail %1 -> %2")
209  .arg(im->m_thumbPath, newThumbPath));
210  continue;
211  }
212 
213  // Clean up empty dirs
214  QString path = QFileInfo(im->m_thumbPath).path();
215  if (QDir::root().rmpath(path))
216  LOG(VB_FILE, LOG_DEBUG,
217  QString("Cleaned up path %1").arg(path));
218  }
219  }
220  else
221  LOG(VB_GENERAL, LOG_ERR,
222  QString("Unknown task %1").arg(task->m_action));
223  }
224 
225  RunEpilog();
226 }
227 
228 
234 template <class DBFS>
235 QString ThumbThread<DBFS>::CreateThumbnail(ImagePtrK im, int thumbPriority)
236 {
237  if (QDir::root().exists(im->m_thumbPath))
238  {
239  LOG(VB_FILE, LOG_DEBUG, QString("[%3] %2 already exists")
240  .arg(im->m_thumbPath).arg(thumbPriority));
241  return QString(); // Notify anyway
242  }
243 
244  // Local filenames are always absolute
245  // Remote filenames are absolute from the scanner only
246  // UI requests (derived from Db) are relative
247  QString imagePath = m_dbfs.GetAbsFilePath(im);
248  if (imagePath.isEmpty())
249  return QString("Empty image path: %1").arg(im->m_filePath);
250 
251  // Ensure path exists
252  QDir::root().mkpath(QFileInfo(im->m_thumbPath).path());
253 
254  QImage image;
255  if (im->m_type == kImageFile)
256  {
257  if (!image.load(imagePath))
258  return QString("Failed to open image %1").arg(imagePath);
259 
260  // Resize to optimise load/display time by FE's
261  image = image.scaled(QSize(240,180), Qt::KeepAspectRatio, Qt::SmoothTransformation);
262  }
263  else if (im->m_type == kVideoFile)
264  {
265  // Run Preview Generator in foreground
266  QString cmd = GetAppBinDir() + MYTH_APPNAME_MYTHPREVIEWGEN;
267  QStringList args;
268  args << "--size 320x240"; // Video thumbnails are also shown in slideshow
269  args << QString("--infile '%1'").arg(imagePath);
270  args << QString("--outfile '%1'").arg(im->m_thumbPath);
271 
272  MythSystemLegacy ms(cmd, args,
273  kMSRunShell |
279  ms.SetNice(10);
280  ms.SetIOPrio(7);
281  ms.Run(30);
282  if (ms.Wait() != GENERIC_EXIT_OK)
283  {
284  LOG(VB_GENERAL, LOG_ERR, QString("Failed to run %2 %3")
285  .arg(cmd, args.join(" ")));
286  return QString("Preview Generator failed for %1").arg(imagePath);
287  }
288 
289  if (!image.load(im->m_thumbPath))
290  return QString("Failed to open preview %1").arg(im->m_thumbPath);
291  }
292  else
293  return QString("Can't create thumbnail for type %1 (image %2)")
294  .arg(im->m_type).arg(imagePath);
295 
296  // Compensate for any Qt auto-orientation
297  int orientBy = Orientation(im->m_orientation)
298  .GetCurrent(im->m_type == kImageFile);
299 
300  // Orientate now to optimise load/display time - no orientation
301  // is required when displaying thumbnails
302  image = MythImage::ApplyExifOrientation(image, orientBy);
303 
304  // Create the thumbnail
305  if (!image.save(im->m_thumbPath))
306  return QString("Failed to create thumbnail %1").arg(im->m_thumbPath);
307 
308  LOG(VB_FILE, LOG_INFO, QString("[%2] Created %1")
309  .arg(im->m_thumbPath).arg(thumbPriority));
310  return QString();
311 }
312 
313 
317 template <class DBFS>
319 {
320  QMutexLocker locker(&m_mutex);
321  m_doBackground = !pause;
322 
323  // restart if not already running
324  if (m_doBackground && !this->isRunning())
325  this->start();
326 }
327 
328 
332 template <class DBFS>
334  : m_dbfs(*dbfs),
335  m_imageThread(new ThumbThread<DBFS>("ImageThumbs", dbfs)),
336  m_videoThread(new ThumbThread<DBFS>("VideoThumbs", dbfs))
337 {}
338 
339 
343 template <class DBFS>
345 {
346  delete m_imageThread;
347  delete m_videoThread;
348  m_imageThread = m_videoThread = nullptr;
349 }
350 
351 
358 template <class DBFS>
359 void ImageThumb<DBFS>::ClearThumbs(int devId, const QString &action)
360 {
361  // Cancel pending requests for the device
362  // Waits for current generator task to complete
363  if (m_imageThread)
364  m_imageThread->AbortDevice(devId, action);
365  if (m_videoThread)
366  m_videoThread->AbortDevice(devId, action);
367 
368  // Remove devices now they are not in use
369  QStringList mountPaths = m_dbfs.CloseDevices(devId, action);
370 // if (mountPaths.isEmpty())
371 // return;
372 
373  // Generate file & thumbnail urls (as per image cache) of mountpoints
374  QStringList mesg;
375  for (const auto& mount : qAsConst(mountPaths))
376  mesg << m_dbfs.MakeFileUrl(mount)
377  << m_dbfs.MakeThumbUrl(mount);
378 
379  // Notify clients to clear cache
380  m_dbfs.Notify("IMAGE_DEVICE_CHANGED", mesg);
381 }
382 
383 
389 template <class DBFS>
391 {
392  // Determine affected images and redundant images/thumbnails
393  QStringList ids;
394 
395  // Pictures & videos are deleted by their own threads
396  ImageListK pics;
397  ImageListK videos;
398  for (const auto& im : qAsConst(images))
399  {
400  if (im->m_type == kVideoFile)
401  videos.append(im);
402  else
403  pics.append(im);
404 
405  ids << QString::number(im->m_id);
406  }
407 
408  if (!pics.isEmpty() && m_imageThread)
409  m_imageThread->Enqueue(TaskPtr(new ThumbTask("DELETE", pics)));
410  if (!videos.isEmpty() && m_videoThread)
411  m_videoThread->Enqueue(TaskPtr(new ThumbTask("DELETE", videos)));
412  return ids.join(",");
413 }
414 
415 
422 template <class DBFS>
423 void ImageThumb<DBFS>::CreateThumbnail(const ImagePtrK &im, int priority,
424  bool notify)
425 {
426  if (!im)
427  return;
428 
429  // Allocate task a (hopefully) unique priority
430  if (priority == kBackgroundPriority)
431  priority = Priority(*im);
432 
433  TaskPtr task(new ThumbTask("CREATE", im, priority, notify));
434 
435  if (im->m_type == kImageFile && m_imageThread)
436  {
437  m_imageThread->Enqueue(task);
438  }
439  else if (im->m_type == kVideoFile && m_videoThread)
440  {
441  m_videoThread->Enqueue(task);
442  }
443  else
444  {
445  LOG(VB_FILE, LOG_INFO, QString("Ignoring create thumbnail %1, type %2")
446  .arg(im->m_id).arg(im->m_type));
447  }
448 }
449 
450 
455 template <class DBFS>
457 {
458  if (!im)
459  return;
460 
461  TaskPtr task(new ThumbTask("MOVE", im));
462 
463  if (im->m_type == kImageFile && m_imageThread)
464  {
465  m_imageThread->Enqueue(task);
466  }
467  else if (im->m_type == kVideoFile && m_videoThread)
468  {
469  m_videoThread->Enqueue(task);
470  }
471  else
472  {
473  LOG(VB_FILE, LOG_INFO, QString("Ignoring move thumbnail %1, type %2")
474  .arg(im->m_id).arg(im->m_type));
475  }
476 }
477 
478 
482 template <class DBFS>
484 {
485  LOG(VB_FILE, LOG_INFO, QString("Paused %1").arg(pause));
486 
487  if (m_imageThread)
488  m_imageThread->PauseBackground(pause);
489  if (m_videoThread)
490  m_videoThread->PauseBackground(pause);
491 }
492 
493 
494 // Must define the valid template implementations to generate code for the
495 // instantiations (as they are defined in the cpp rather than header).
496 // Otherwise the linker will fail with undefined references...
497 #include "imagemanager.h"
498 template class ImageThumb<ImageDbLocal>;
499 template class ImageThumb<ImageDbSg>;
ImagePtrK
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:164
build_compdb.args
args
Definition: build_compdb.py:11
ImageThumb::PauseBackground
void PauseBackground(bool pause)
Pauses or restarts processing of background tasks (scanner requests)
Definition: imagethumbs.cpp:483
GENERIC_EXIT_OK
#define GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:10
root
QDomElement root
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:653
kVideoFile
@ kVideoFile
A video.
Definition: imagetypes.h:39
ImageThumb::CreateThumbnail
void CreateThumbnail(const ImagePtrK &im, int priority=kBackgroundPriority, bool notify=false)
Creates a thumbnail.
Definition: imagethumbs.cpp:423
kMSDontBlockInputDevs
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:34
MythSystemLegacy
Definition: mythsystemlegacy.h:68
ThumbThread::~ThumbThread
~ThumbThread() override
Destructor.
Definition: imagethumbs.cpp:19
imagemanager.h
Manages a collection of images.
MythSystemLegacy::SetIOPrio
bool SetIOPrio(int prio)
Definition: mythsystemlegacy.cpp:202
arg
arg(title).arg(filename).arg(doDelete))
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
ThumbThread::RemoveTasks
static void RemoveTasks(ThumbQueue &queue, int devId)
Definition: imagethumbs.cpp:98
MYTH_APPNAME_MYTHPREVIEWGEN
#define MYTH_APPNAME_MYTHPREVIEWGEN
Definition: mythcorecontext.h:23
MythSystemLegacy::Run
void Run(time_t timeout=0)
Runs a command inside the /bin/sh shell. Returns immediately.
Definition: mythsystemlegacy.cpp:212
mythdirs.h
ThumbThread< ImageDbSg >::ThumbQueue
QMultiMap< int, TaskPtr > ThumbQueue
A priority queue where 0 is highest priority.
Definition: imagethumbs.h:111
Orientation::GetCurrent
int GetCurrent(bool compensate)
Determines orientation required for an image.
Definition: imagemetadata.cpp:85
ImageThumb::DeleteThumbs
QString DeleteThumbs(const ImageList &images)
Remove specific thumbnails.
Definition: imagethumbs.cpp:390
mythsystemlegacy.h
ImageThumb< ImageDbLocal >
ImageThumb::~ImageThumb
~ImageThumb()
Destructor .
Definition: imagethumbs.cpp:344
ThumbThread::PauseBackground
void PauseBackground(bool pause)
Pauses or restarts processing of background tasks (scanner requests)
Definition: imagethumbs.cpp:318
mythlogging.h
MythImage::ApplyExifOrientation
static QImage ApplyExifOrientation(QImage &image, int orientation)
Definition: mythimage.cpp:99
ImageList
QList< ImagePtr > ImageList
Definition: imagetypes.h:159
ThumbThread::CreateThumbnail
QString CreateThumbnail(ImagePtrK im, int thumbPriority)
Generate thumbnail for an image.
Definition: imagethumbs.cpp:235
ImageListK
QList< ImagePtrK > ImageListK
Definition: imagetypes.h:165
ThumbThread::cancel
void cancel()
Clears all queues so that the thread will terminate.
Definition: imagethumbs.cpp:30
ThumbThread
A generator worker thread.
Definition: imagethumbs.h:87
kMSPropagateLogs
@ kMSPropagateLogs
add arguments for MythTV log propagation
Definition: mythsystem.h:50
ThumbThread::AbortDevice
void AbortDevice(int devId, const QString &action)
Clears thumbnail request queue.
Definition: imagethumbs.cpp:68
kMSRunShell
@ kMSRunShell
run process through shell
Definition: mythsystem.h:41
kMSAutoCleanup
@ kMSAutoCleanup
automatically delete if backgrounded
Definition: mythsystem.h:43
ThumbTask
A generator request that is queued.
Definition: imagethumbs.h:41
mythimage.h
isRunning
static bool isRunning(const char *program)
Returns true if a program containing the specified string is running on this machine.
Definition: mythtv/programs/mythshutdown/main.cpp:203
kImageFile
@ kImageFile
A picture.
Definition: imagetypes.h:38
mythcorecontext.h
TaskPtr
QSharedPointer< ThumbTask > TaskPtr
Definition: imagethumbs.h:82
kMSProcessEvents
@ kMSProcessEvents
process events while waiting
Definition: mythsystem.h:37
imagemetadata.h
Handles Exif/FFMpeg metadata tags for images.
ImageThumb::ClearThumbs
void ClearThumbs(int devId, const QString &action)
Clears thumbnails for a device.
Definition: imagethumbs.cpp:359
imagethumbs.h
Creates and manages thumbnails.
MythSystemLegacy::SetNice
bool SetNice(int nice)
Definition: mythsystemlegacy.cpp:193
GetAppBinDir
QString GetAppBinDir(void)
Definition: mythdirs.cpp:221
ThumbThread::Enqueue
void Enqueue(const TaskPtr &task)
Queues a Create request.
Definition: imagethumbs.cpp:44
build_compdb.action
action
Definition: build_compdb.py:9
ImageThumb::MoveThumbnail
void MoveThumbnail(const ImagePtrK &im)
Renames a thumbnail.
Definition: imagethumbs.cpp:456
kBackgroundPriority
@ kBackgroundPriority
Scanner background request.
Definition: imagethumbs.h:36
ImageThumb::ImageThumb
ImageThumb(DBFS *dbfs)
Constructor.
Definition: imagethumbs.cpp:333
kMSDontDisableDrawing
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:35
exitcodes.h
ThumbThread::run
void run() override
Handles thumbnail requests by priority.
Definition: imagethumbs.cpp:120
Priority
Definition: channelsettings.cpp:191
Orientation
Encapsulates Exif orientation processing.
Definition: imagemetadata.h:62
MythSystemLegacy::Wait
uint Wait(time_t timeout=0)
Definition: mythsystemlegacy.cpp:242