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