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 : std::as_const(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 : std::as_const(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  {
225  LOG(VB_GENERAL, LOG_ERR,
226  QString("Unknown task %1").arg(task->m_action));
227  }
228  }
229 
230  RunEpilog();
231 }
232 
233 
239 template <class DBFS>
240 QString ThumbThread<DBFS>::CreateThumbnail(const ImagePtrK &im, int thumbPriority)
241 {
242  if (QDir::root().exists(im->m_thumbPath))
243  {
244  LOG(VB_FILE, LOG_DEBUG, QString("[%3] %2 already exists")
245  .arg(im->m_thumbPath).arg(thumbPriority));
246  return {}; // Notify anyway
247  }
248 
249  // Local filenames are always absolute
250  // Remote filenames are absolute from the scanner only
251  // UI requests (derived from Db) are relative
252  QString imagePath = m_dbfs.GetAbsFilePath(im);
253  if (imagePath.isEmpty())
254  return QString("Empty image path: %1").arg(im->m_filePath);
255 
256  // Ensure path exists
257  QDir::root().mkpath(QFileInfo(im->m_thumbPath).path());
258 
259  QImage image;
260  if (im->m_type == kImageFile)
261  {
262  if (!image.load(imagePath))
263  return QString("Failed to open image %1").arg(imagePath);
264 
265  // Resize to optimise load/display time by FE's
266  image = image.scaled(QSize(240,180), Qt::KeepAspectRatio, Qt::SmoothTransformation);
267  }
268  else if (im->m_type == kVideoFile)
269  {
270  // Run Preview Generator in foreground
271  QString cmd = GetAppBinDir() + MYTH_APPNAME_MYTHPREVIEWGEN;
272  QStringList args;
273  args << "--size 320x240"; // Video thumbnails are also shown in slideshow
274  args << QString("--infile '%1'").arg(imagePath);
275  args << QString("--outfile '%1'").arg(im->m_thumbPath);
276 
277  MythSystemLegacy ms(cmd, args,
278  kMSRunShell |
284  ms.SetNice(10);
285  ms.SetIOPrio(7);
286  ms.Run(30s);
287  if (ms.Wait() != GENERIC_EXIT_OK)
288  {
289  LOG(VB_GENERAL, LOG_ERR, QString("Failed to run %2 %3")
290  .arg(cmd, args.join(" ")));
291  return QString("Preview Generator failed for %1").arg(imagePath);
292  }
293 
294  if (!image.load(im->m_thumbPath))
295  return QString("Failed to open preview %1").arg(im->m_thumbPath);
296  }
297  else
298  {
299  return QString("Can't create thumbnail for type %1 (image %2)")
300  .arg(im->m_type).arg(imagePath);
301  }
302 
303  // Compensate for any Qt auto-orientation
304  int orientBy = Orientation(im->m_orientation)
305  .GetCurrent(im->m_type == kImageFile);
306 
307  // Orientate now to optimise load/display time - no orientation
308  // is required when displaying thumbnails
309  image = MythImage::ApplyExifOrientation(image, orientBy);
310 
311  // Create the thumbnail
312  if (!image.save(im->m_thumbPath))
313  return QString("Failed to create thumbnail %1").arg(im->m_thumbPath);
314 
315  LOG(VB_FILE, LOG_INFO, QString("[%2] Created %1")
316  .arg(im->m_thumbPath).arg(thumbPriority));
317  return {};
318 }
319 
320 
324 template <class DBFS>
326 {
327  QMutexLocker locker(&m_mutex);
328  m_doBackground = !pause;
329 
330  // restart if not already running
331  if (m_doBackground && !this->isRunning())
332  this->start();
333 }
334 
335 
339 template <class DBFS>
341  : m_dbfs(*dbfs),
342  m_imageThread(new ThumbThread<DBFS>("ImageThumbs", dbfs)),
343  m_videoThread(new ThumbThread<DBFS>("VideoThumbs", dbfs))
344 {}
345 
346 
350 template <class DBFS>
352 {
353  delete m_imageThread;
354  delete m_videoThread;
355  m_imageThread = m_videoThread = nullptr;
356 }
357 
358 
365 template <class DBFS>
366 void ImageThumb<DBFS>::ClearThumbs(int devId, const QString &action)
367 {
368  // Cancel pending requests for the device
369  // Waits for current generator task to complete
370  if (m_imageThread)
371  m_imageThread->AbortDevice(devId, action);
372  if (m_videoThread)
373  m_videoThread->AbortDevice(devId, action);
374 
375  // Remove devices now they are not in use
376  QStringList mountPaths = m_dbfs.CloseDevices(devId, action);
377 // if (mountPaths.isEmpty())
378 // return;
379 
380  // Generate file & thumbnail urls (as per image cache) of mountpoints
381  QStringList mesg;
382  for (const auto& mount : std::as_const(mountPaths))
383  mesg << m_dbfs.MakeFileUrl(mount)
384  << m_dbfs.MakeThumbUrl(mount);
385 
386  // Notify clients to clear cache
387  m_dbfs.Notify("IMAGE_DEVICE_CHANGED", mesg);
388 }
389 
390 
396 template <class DBFS>
398 {
399  // Determine affected images and redundant images/thumbnails
400  QStringList ids;
401 
402  // Pictures & videos are deleted by their own threads
403  ImageListK pics;
404  ImageListK videos;
405  for (const auto& im : std::as_const(images))
406  {
407  if (im->m_type == kVideoFile)
408  videos.append(im);
409  else
410  pics.append(im);
411 
412  ids << QString::number(im->m_id);
413  }
414 
415  if (!pics.isEmpty() && m_imageThread)
416  m_imageThread->Enqueue(TaskPtr(new ThumbTask("DELETE", pics)));
417  if (!videos.isEmpty() && m_videoThread)
418  m_videoThread->Enqueue(TaskPtr(new ThumbTask("DELETE", videos)));
419  return ids.join(",");
420 }
421 
422 
429 template <class DBFS>
430 void ImageThumb<DBFS>::CreateThumbnail(const ImagePtrK &im, int priority,
431  bool notify)
432 {
433  if (!im)
434  return;
435 
436  // Allocate task a (hopefully) unique priority
437  if (priority == kBackgroundPriority)
438  priority = Priority(*im);
439 
440  TaskPtr task(new ThumbTask("CREATE", im, priority, notify));
441 
442  if (im->m_type == kImageFile && m_imageThread)
443  {
444  m_imageThread->Enqueue(task);
445  }
446  else if (im->m_type == kVideoFile && m_videoThread)
447  {
448  m_videoThread->Enqueue(task);
449  }
450  else
451  {
452  LOG(VB_FILE, LOG_INFO, QString("Ignoring create thumbnail %1, type %2")
453  .arg(im->m_id).arg(im->m_type));
454  }
455 }
456 
457 
462 template <class DBFS>
464 {
465  if (!im)
466  return;
467 
468  TaskPtr task(new ThumbTask("MOVE", im));
469 
470  if (im->m_type == kImageFile && m_imageThread)
471  {
472  m_imageThread->Enqueue(task);
473  }
474  else if (im->m_type == kVideoFile && m_videoThread)
475  {
476  m_videoThread->Enqueue(task);
477  }
478  else
479  {
480  LOG(VB_FILE, LOG_INFO, QString("Ignoring move thumbnail %1, type %2")
481  .arg(im->m_id).arg(im->m_type));
482  }
483 }
484 
485 
489 template <class DBFS>
491 {
492  LOG(VB_FILE, LOG_INFO, QString("Paused %1").arg(pause));
493 
494  if (m_imageThread)
495  m_imageThread->PauseBackground(pause);
496  if (m_videoThread)
497  m_videoThread->PauseBackground(pause);
498 }
499 
500 
501 // Must define the valid template implementations to generate code for the
502 // instantiations (as they are defined in the cpp rather than header).
503 // Otherwise the linker will fail with undefined references...
504 #include "imagemanager.h"
505 template class ImageThumb<ImageDbLocal>;
506 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:490
ImageThumb::CreateThumbnail
void CreateThumbnail(const ImagePtrK &im, int priority=kBackgroundPriority, bool notify=false)
Creates a thumbnail.
Definition: imagethumbs.cpp:430
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
xbmcvfs.exists
bool exists(str path)
Definition: xbmcvfs.py:51
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
kVideoFile
@ kVideoFile
A video.
Definition: imagetypes.h:40
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:397
mythsystemlegacy.h
ImageThumb< ImageDbLocal >
ImageThumb::~ImageThumb
~ImageThumb()
Destructor .
Definition: imagethumbs.cpp:351
ThumbThread::PauseBackground
void PauseBackground(bool pause)
Pauses or restarts processing of background tasks (scanner requests)
Definition: imagethumbs.cpp:325
mythlogging.h
kImageFile
@ kImageFile
A picture.
Definition: imagetypes.h:39
MythImage::ApplyExifOrientation
static QImage ApplyExifOrientation(QImage &image, int orientation)
Definition: mythimage.cpp:97
GENERIC_EXIT_OK
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
isRunning
static bool isRunning(const char *program)
Returns true if a program containing the specified string is running on this machine.
Definition: mythshutdown.cpp:208
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
kBackgroundPriority
@ kBackgroundPriority
Scanner background request.
Definition: imagethumbs.h:36
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:366
imagethumbs.h
Creates and manages thumbnails.
MythSystemLegacy::SetNice
bool SetNice(int nice)
Definition: mythsystemlegacy.cpp:194
GetAppBinDir
QString GetAppBinDir(void)
Definition: mythdirs.cpp:260
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:463
ThumbThread::CreateThumbnail
QString CreateThumbnail(const ImagePtrK &im, int thumbPriority)
Generate thumbnail for an image.
Definition: imagethumbs.cpp:240
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:340
kMSDontDisableDrawing
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:37
MYTH_APPNAME_MYTHPREVIEWGEN
static constexpr const char * MYTH_APPNAME_MYTHPREVIEWGEN
Definition: mythcorecontext.h:26
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