MythTV  master
imagethumbs.cpp
Go to the documentation of this file.
1 #include "imagethumbs.h"
2 
3 #include <QDir>
4 #include <QStringList>
5 
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  int orientBy = Orientation(im->m_orientation).GetCurrent();
304 
305  // Orientate now to optimise load/display time - no orientation
306  // is required when displaying thumbnails
307  image = MythImage::ApplyExifOrientation(image, orientBy);
308 
309  // Create the thumbnail
310  if (!image.save(im->m_thumbPath))
311  return QString("Failed to create thumbnail %1").arg(im->m_thumbPath);
312 
313  LOG(VB_FILE, LOG_INFO, QString("[%2] Created %1")
314  .arg(im->m_thumbPath).arg(thumbPriority));
315  return {};
316 }
317 
318 
322 template <class DBFS>
324 {
325  QMutexLocker locker(&m_mutex);
326  m_doBackground = !pause;
327 
328  // restart if not already running
329  if (m_doBackground && !this->isRunning())
330  this->start();
331 }
332 
333 
337 template <class DBFS>
339  : m_dbfs(*dbfs),
340  m_imageThread(new ThumbThread<DBFS>("ImageThumbs", dbfs)),
341  m_videoThread(new ThumbThread<DBFS>("VideoThumbs", dbfs))
342 {}
343 
344 
348 template <class DBFS>
350 {
351  delete m_imageThread;
352  delete m_videoThread;
353  m_imageThread = m_videoThread = nullptr;
354 }
355 
356 
363 template <class DBFS>
364 void ImageThumb<DBFS>::ClearThumbs(int devId, const QString &action)
365 {
366  // Cancel pending requests for the device
367  // Waits for current generator task to complete
368  if (m_imageThread)
369  m_imageThread->AbortDevice(devId, action);
370  if (m_videoThread)
371  m_videoThread->AbortDevice(devId, action);
372 
373  // Remove devices now they are not in use
374  QStringList mountPaths = m_dbfs.CloseDevices(devId, action);
375 // if (mountPaths.isEmpty())
376 // return;
377 
378  // Generate file & thumbnail urls (as per image cache) of mountpoints
379  QStringList mesg;
380  for (const auto& mount : std::as_const(mountPaths))
381  mesg << m_dbfs.MakeFileUrl(mount)
382  << m_dbfs.MakeThumbUrl(mount);
383 
384  // Notify clients to clear cache
385  m_dbfs.Notify("IMAGE_DEVICE_CHANGED", mesg);
386 }
387 
388 
394 template <class DBFS>
396 {
397  // Determine affected images and redundant images/thumbnails
398  QStringList ids;
399 
400  // Pictures & videos are deleted by their own threads
401  ImageListK pics;
402  ImageListK videos;
403  for (const auto& im : std::as_const(images))
404  {
405  if (im->m_type == kVideoFile)
406  videos.append(im);
407  else
408  pics.append(im);
409 
410  ids << QString::number(im->m_id);
411  }
412 
413  if (!pics.isEmpty() && m_imageThread)
414  m_imageThread->Enqueue(TaskPtr(new ThumbTask("DELETE", pics)));
415  if (!videos.isEmpty() && m_videoThread)
416  m_videoThread->Enqueue(TaskPtr(new ThumbTask("DELETE", videos)));
417  return ids.join(",");
418 }
419 
420 
427 template <class DBFS>
428 void ImageThumb<DBFS>::CreateThumbnail(const ImagePtrK &im, int priority,
429  bool notify)
430 {
431  if (!im)
432  return;
433 
434  // Allocate task a (hopefully) unique priority
435  if (priority == kBackgroundPriority)
436  priority = Priority(*im);
437 
438  TaskPtr task(new ThumbTask("CREATE", im, priority, notify));
439 
440  if (im->m_type == kImageFile && m_imageThread)
441  {
442  m_imageThread->Enqueue(task);
443  }
444  else if (im->m_type == kVideoFile && m_videoThread)
445  {
446  m_videoThread->Enqueue(task);
447  }
448  else
449  {
450  LOG(VB_FILE, LOG_INFO, QString("Ignoring create thumbnail %1, type %2")
451  .arg(im->m_id).arg(im->m_type));
452  }
453 }
454 
455 
460 template <class DBFS>
462 {
463  if (!im)
464  return;
465 
466  TaskPtr task(new ThumbTask("MOVE", im));
467 
468  if (im->m_type == kImageFile && m_imageThread)
469  {
470  m_imageThread->Enqueue(task);
471  }
472  else if (im->m_type == kVideoFile && m_videoThread)
473  {
474  m_videoThread->Enqueue(task);
475  }
476  else
477  {
478  LOG(VB_FILE, LOG_INFO, QString("Ignoring move thumbnail %1, type %2")
479  .arg(im->m_id).arg(im->m_type));
480  }
481 }
482 
483 
487 template <class DBFS>
489 {
490  LOG(VB_FILE, LOG_INFO, QString("Paused %1").arg(pause));
491 
492  if (m_imageThread)
493  m_imageThread->PauseBackground(pause);
494  if (m_videoThread)
495  m_videoThread->PauseBackground(pause);
496 }
497 
498 
499 // Must define the valid template implementations to generate code for the
500 // instantiations (as they are defined in the cpp rather than header).
501 // Otherwise the linker will fail with undefined references...
502 #include "imagemanager.h"
503 template class ImageThumb<ImageDbLocal>;
504 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:488
ImageThumb::CreateThumbnail
void CreateThumbnail(const ImagePtrK &im, int priority=kBackgroundPriority, bool notify=false)
Creates a thumbnail.
Definition: imagethumbs.cpp:428
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
Orientation::GetCurrent
int GetCurrent() const
Determines orientation required for an image.
Definition: imagemetadata.cpp:40
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
ImageThumb::DeleteThumbs
QString DeleteThumbs(const ImageList &images)
Remove specific thumbnails.
Definition: imagethumbs.cpp:395
mythsystemlegacy.h
ImageThumb< ImageDbLocal >
ImageThumb::~ImageThumb
~ImageThumb()
Destructor .
Definition: imagethumbs.cpp:349
ThumbThread::PauseBackground
void PauseBackground(bool pause)
Pauses or restarts processing of background tasks (scanner requests)
Definition: imagethumbs.cpp:323
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:209
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
MYTH_APPNAME_MYTHPREVIEWGEN
static constexpr const char * MYTH_APPNAME_MYTHPREVIEWGEN
Definition: mythappname.h:11
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
mythappname.h
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
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:364
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:461
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:338
kMSDontDisableDrawing
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:37
ThumbThread::run
void run() override
Handles thumbnail requests by priority.
Definition: imagethumbs.cpp:123
Priority
Definition: channelsettings.cpp:248
Orientation
Encapsulates Exif orientation processing.
Definition: imagemetadata.h:62