OpenShot Library | libopenshot  0.7.0
CacheDisk.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2019 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "CacheDisk.h"
14 #include "Exceptions.h"
15 #include "Frame.h"
16 #include "QtUtilities.h"
17 
18 #include <sstream>
19 #include <Qt>
20 #include <QString>
21 #include <QTextStream>
22 
23 using namespace std;
24 using namespace openshot;
25 
26 // Default constructor, no max bytes
27 CacheDisk::CacheDisk(std::string cache_path, std::string format, float quality, float scale) : CacheBase(0) {
28  // Set cache type name
29  cache_type = "CacheDisk";
30  range_version = 0;
31  needs_range_processing = false;
32  frame_size_bytes = 0;
33  image_format = format;
34  image_quality = quality;
35  image_scale = scale;
36  max_bytes = 0;
37 
38  // Init path directory
39  InitPath(cache_path);
40 }
41 
42 // Constructor that sets the max bytes to cache
43 CacheDisk::CacheDisk(std::string cache_path, std::string format, float quality, float scale, int64_t max_bytes) : CacheBase(max_bytes) {
44  // Set cache type name
45  cache_type = "CacheDisk";
46  range_version = 0;
47  needs_range_processing = false;
48  frame_size_bytes = 0;
49  image_format = format;
50  image_quality = quality;
51  image_scale = scale;
52 
53  // Init path directory
54  InitPath(cache_path);
55 }
56 
57 // Initialize cache directory
58 void CacheDisk::InitPath(std::string cache_path) {
59  QString qpath;
60 
61  if (!cache_path.empty()) {
62  // Init QDir with cache directory
63  qpath = QString(cache_path.c_str());
64 
65  } else {
66  // Init QDir with user's temp directory
67  qpath = QDir::tempPath() + QString("/preview-cache/");
68  }
69 
70  // Init QDir with cache directory
71  path = QDir(qpath);
72 
73  // Check if cache directory exists
74  if (!path.exists())
75  // Create
76  path.mkpath(qpath);
77 }
78 
79 // Default destructor
81 {
82  Clear();
83 
84  // remove mutex
85  delete cacheMutex;
86 }
87 
88 // Add a Frame to the cache
89 void CacheDisk::Add(std::shared_ptr<Frame> frame)
90 {
91  // Create a scoped lock, to protect the cache from multiple threads
92  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
93  int64_t frame_number = frame->number;
94 
95  // Freshen frame if it already exists
96  if (frames.count(frame_number))
97  // Move frame to front of queue
98  Touch(frame_number);
99 
100  else
101  {
102  // Add frame to queue and map
103  frames[frame_number] = frame_number;
104  frame_numbers.push_front(frame_number);
105  ordered_frame_numbers.push_back(frame_number);
106  needs_range_processing = true;
107 
108  // Save image to disk (if needed)
109  QString frame_path(path.path() + "/" + QString("%1.").arg(frame_number) + QString(image_format.c_str()).toLower());
110  frame->Save(frame_path.toStdString(), image_scale, image_format, image_quality);
111  if (frame_size_bytes == 0) {
112  // Get compressed size of frame image (to correctly apply max size against)
113  QFile image_file(frame_path);
114  frame_size_bytes = image_file.size();
115  }
116 
117  // Save audio data (if needed)
118  if (frame->has_audio_data) {
119  QString audio_path(path.path() + "/" + QString("%1").arg(frame_number) + ".audio");
120  QFile audio_file(audio_path);
121 
122  if (audio_file.open(QIODevice::WriteOnly)) {
123  QTextStream audio_stream(&audio_file);
124  audio_stream << frame->SampleRate() << Qt::endl;
125  audio_stream << frame->GetAudioChannelsCount() << Qt::endl;
126  audio_stream << frame->GetAudioSamplesCount() << Qt::endl;
127  audio_stream << frame->ChannelsLayout() << Qt::endl;
128 
129  // Loop through all samples
130  for (int channel = 0; channel < frame->GetAudioChannelsCount(); channel++)
131  {
132  // Get audio for this channel
133  float *samples = frame->GetAudioSamples(channel);
134  for (int sample = 0; sample < frame->GetAudioSamplesCount(); sample++)
135  audio_stream << samples[sample] << Qt::endl;
136  }
137 
138  }
139 
140  }
141 
142  // Clean up old frames
143  CleanUp();
144  }
145 }
146 
147 // Check if frame is already contained in cache
148 bool CacheDisk::Contains(int64_t frame_number) {
149  if (frames.count(frame_number) > 0) {
150  return true;
151  } else {
152  return false;
153  }
154 }
155 
156 // Get a frame from the cache (or NULL shared_ptr if no frame is found)
157 std::shared_ptr<Frame> CacheDisk::GetFrame(int64_t frame_number)
158 {
159  // Create a scoped lock, to protect the cache from multiple threads
160  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
161 
162  // Does frame exists in cache?
163  if (frames.count(frame_number)) {
164  // Does frame exist on disk
165  QString frame_path(path.path() + "/" + QString("%1.").arg(frame_number) + QString(image_format.c_str()).toLower());
166  if (path.exists(frame_path)) {
167 
168  // Load image file
169  auto image = std::make_shared<QImage>();
170  image->load(frame_path);
171 
172  // Set pixel formatimage->
173  image = std::make_shared<QImage>(image->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
174 
175  // Create frame object
176  auto frame = std::make_shared<Frame>();
177  frame->number = frame_number;
178  frame->AddImage(image);
179 
180  // Get audio data (if found)
181  QString audio_path(path.path() + "/" + QString("%1").arg(frame_number) + ".audio");
182  QFile audio_file(audio_path);
183  if (audio_file.exists()) {
184  // Open audio file
185  QTextStream in(&audio_file);
186  if (audio_file.open(QIODevice::ReadOnly)) {
187  int sample_rate = in.readLine().toInt();
188  int channels = in.readLine().toInt();
189  int sample_count = in.readLine().toInt();
190  int channel_layout = in.readLine().toInt();
191 
192  // Set basic audio properties
193  frame->ResizeAudio(channels, sample_count, sample_rate, (ChannelLayout) channel_layout);
194 
195  // Loop through audio samples and add to frame
196  int current_channel = 0;
197  int current_sample = 0;
198  float *channel_samples = new float[sample_count];
199  while (!in.atEnd()) {
200  // Add sample to channel array
201  channel_samples[current_sample] = in.readLine().toFloat();
202  current_sample++;
203 
204  if (current_sample == sample_count) {
205  // Add audio to frame
206  frame->AddAudio(true, current_channel, 0, channel_samples, sample_count, 1.0);
207 
208  // Increment channel, and reset sample position
209  current_channel++;
210  current_sample = 0;
211  }
212 
213  }
214  }
215  }
216 
217  // return the Frame object
218  return frame;
219  }
220  }
221 
222  // no Frame found
223  return std::shared_ptr<Frame>();
224 }
225 
226 // @brief Get an array of all Frames
227 std::vector<std::shared_ptr<openshot::Frame>> CacheDisk::GetFrames()
228 {
229  // Create a scoped lock, to protect the cache from multiple threads
230  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
231 
232  std::vector<std::shared_ptr<openshot::Frame>> all_frames;
233  std::vector<int64_t>::iterator itr_ordered;
234  for(itr_ordered = ordered_frame_numbers.begin(); itr_ordered != ordered_frame_numbers.end(); ++itr_ordered)
235  {
236  int64_t frame_number = *itr_ordered;
237  all_frames.push_back(GetFrame(frame_number));
238  }
239 
240  return all_frames;
241 }
242 
243 // Get the smallest frame number (or NULL shared_ptr if no frame is found)
244 std::shared_ptr<Frame> CacheDisk::GetSmallestFrame()
245 {
246  // Create a scoped lock, to protect the cache from multiple threads
247  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
248 
249  // Loop through frame numbers
250  std::deque<int64_t>::iterator itr;
251  int64_t smallest_frame = -1;
252  for(itr = frame_numbers.begin(); itr != frame_numbers.end(); ++itr)
253  {
254  if (*itr < smallest_frame || smallest_frame == -1)
255  smallest_frame = *itr;
256  }
257 
258  // Return frame (if any)
259  if (smallest_frame != -1) {
260  return GetFrame(smallest_frame);
261  } else {
262  return NULL;
263  }
264 }
265 
266 // Gets the maximum bytes value
268 {
269  // Create a scoped lock, to protect the cache from multiple threads
270  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
271 
272  int64_t total_bytes = 0;
273 
274  // Loop through frames, and calculate total bytes
275  std::deque<int64_t>::reverse_iterator itr;
276  for(itr = frame_numbers.rbegin(); itr != frame_numbers.rend(); ++itr)
277  total_bytes += frame_size_bytes;
278 
279  return total_bytes;
280 }
281 
282 // Remove a specific frame
283 void CacheDisk::Remove(int64_t frame_number)
284 {
285  Remove(frame_number, frame_number);
286 }
287 
288 // Remove range of frames
289 void CacheDisk::Remove(int64_t start_frame_number, int64_t end_frame_number)
290 {
291  // Create a scoped lock, to protect the cache from multiple threads
292  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
293 
294  // Loop through frame numbers
295  std::deque<int64_t>::iterator itr;
296  for(itr = frame_numbers.begin(); itr != frame_numbers.end();)
297  {
298  //deque<int64_t>::iterator current = itr++;
299  if (*itr >= start_frame_number && *itr <= end_frame_number)
300  {
301  // erase frame number
302  itr = frame_numbers.erase(itr);
303  } else
304  itr++;
305  }
306 
307  // Loop through ordered frame numbers
308  std::vector<int64_t>::iterator itr_ordered;
309  for(itr_ordered = ordered_frame_numbers.begin(); itr_ordered != ordered_frame_numbers.end();)
310  {
311  if (*itr_ordered >= start_frame_number && *itr_ordered <= end_frame_number)
312  {
313  // erase frame number
314  frames.erase(*itr_ordered);
315 
316  // Remove the image file (if it exists)
317  QString frame_path(path.path() + "/" + QString("%1.").arg(*itr_ordered) + QString(image_format.c_str()).toLower());
318  QFile image_file(frame_path);
319  if (image_file.exists())
320  image_file.remove();
321 
322  // Remove audio file (if it exists)
323  QString audio_path(path.path() + "/" + QString("%1").arg(*itr_ordered) + ".audio");
324  QFile audio_file(audio_path);
325  if (audio_file.exists())
326  audio_file.remove();
327 
328  itr_ordered = ordered_frame_numbers.erase(itr_ordered);
329  } else
330  itr_ordered++;
331  }
332 
333  // Needs range processing (since cache has changed)
334  needs_range_processing = true;
335 }
336 
337 // Move frame to front of queue (so it lasts longer)
338 void CacheDisk::Touch(int64_t frame_number)
339 {
340  // Does frame exists in cache?
341  if (frames.count(frame_number))
342  {
343  // Create a scoped lock, to protect the cache from multiple threads
344  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
345 
346  // Loop through frame numbers
347  std::deque<int64_t>::iterator itr;
348  for(itr = frame_numbers.begin(); itr != frame_numbers.end(); ++itr)
349  {
350  if (*itr == frame_number)
351  {
352  // erase frame number
353  frame_numbers.erase(itr);
354 
355  // add frame number to 'front' of queue
356  frame_numbers.push_front(frame_number);
357  break;
358  }
359  }
360  }
361 }
362 
363 // Clear the cache of all frames
365 {
366  // Create a scoped lock, to protect the cache from multiple threads
367  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
368 
369  // Clear all containers
370  frames.clear();
371  frame_numbers.clear();
372  frame_numbers.shrink_to_fit();
373  ordered_frame_numbers.clear();
374  ordered_frame_numbers.shrink_to_fit();
375  needs_range_processing = true;
376  frame_size_bytes = 0;
377 
378  // Delete cache directory, and recreate it
379  QString current_path = path.path();
380  path.removeRecursively();
381 
382  // Re-init folder
383  InitPath(current_path.toStdString());
384 }
385 
386 // Count the frames in the queue
388 {
389  // Create a scoped lock, to protect the cache from multiple threads
390  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
391 
392  // Return the number of frames in the cache
393  return frames.size();
394 }
395 
396 // Clean up cached frames that exceed the number in our max_bytes variable
397 void CacheDisk::CleanUp()
398 {
399  // Do we auto clean up?
400  if (max_bytes > 0)
401  {
402  // Create a scoped lock, to protect the cache from multiple threads
403  const std::lock_guard<std::recursive_mutex> lock(*cacheMutex);
404 
405  while (GetBytes() > max_bytes && frame_numbers.size() > 20)
406  {
407  // Get the oldest frame number.
408  int64_t frame_to_remove = frame_numbers.back();
409 
410  // Remove frame_number and frame
411  Remove(frame_to_remove);
412  }
413  }
414 }
415 
416 // Generate JSON string of this object
417 std::string CacheDisk::Json() {
418 
419  // Return formatted string
420  return JsonValue().toStyledString();
421 }
422 
423 // Generate Json::Value for this object
424 Json::Value CacheDisk::JsonValue() {
425 
426  // Process range data (if anything has changed)
427  CalculateRanges();
428 
429  // Create root json object
430  Json::Value root = CacheBase::JsonValue(); // get parent properties
431  root["type"] = cache_type;
432  root["path"] = path.path().toStdString();
433 
434  Json::Value version;
435  std::stringstream range_version_str;
436  range_version_str << range_version;
437  root["version"] = range_version_str.str();
438 
439  // Parse and append range data (if any)
440  // Parse and append range data (if any)
441  try {
442  const Json::Value ranges = openshot::stringToJson(json_ranges);
443  root["ranges"] = ranges;
444  } catch (...) { }
445 
446  // return JsonValue
447  return root;
448 }
449 
450 // Load JSON string into this object
451 void CacheDisk::SetJson(const std::string value) {
452 
453  // Parse JSON string into JSON objects
454  try
455  {
456  const Json::Value root = openshot::stringToJson(value);
457  // Set all values that match
458  SetJsonValue(root);
459  }
460  catch (const std::exception& e)
461  {
462  // Error parsing JSON (or missing keys)
463  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
464  }
465 }
466 
467 // Load Json::Value into this object
468 void CacheDisk::SetJsonValue(const Json::Value root) {
469 
470  // Close timeline before we do anything (this also removes all open and closing clips)
471  Clear();
472 
473  // Set parent data
475 
476  if (!root["type"].isNull())
477  cache_type = root["type"].asString();
478  if (!root["path"].isNull())
479  // Update duration of timeline
480  InitPath(root["path"].asString());
481 }
Header file for CacheDisk class.
Header file for all Exception classes.
Header file for Frame class.
Header file for QtUtilities (compatibiity overlay)
All cache managers in libopenshot are based on this CacheBase class.
Definition: CacheBase.h:35
int64_t range_version
The version of the JSON range data (incremented with each change)
Definition: CacheBase.h:44
virtual Json::Value JsonValue()=0
Generate Json::Value for this object.
Definition: CacheBase.cpp:100
std::string cache_type
This is a friendly type name of the derived cache instance.
Definition: CacheBase.h:37
void CalculateRanges()
Calculate ranges of frames.
Definition: CacheBase.cpp:36
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: CacheBase.cpp:111
bool needs_range_processing
Something has changed, and the range data needs to be re-calculated.
Definition: CacheBase.h:40
int64_t max_bytes
This is the max number of bytes to cache (0 = no limit)
Definition: CacheBase.h:38
std::recursive_mutex * cacheMutex
Mutex for multiple threads.
Definition: CacheBase.h:47
std::string json_ranges
JSON ranges of frame numbers.
Definition: CacheBase.h:41
std::vector< int64_t > ordered_frame_numbers
Ordered list of frame numbers used by cache.
Definition: CacheBase.h:42
std::vector< std::shared_ptr< openshot::Frame > > GetFrames()
Get an array of all Frames.
Definition: CacheDisk.cpp:227
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)
Get a frame from the cache.
Definition: CacheDisk.cpp:157
CacheDisk(std::string cache_path, std::string format, float quality, float scale)
Default constructor, no max bytes.
Definition: CacheDisk.cpp:27
bool Contains(int64_t frame_number)
Check if frame is already contained in cache.
Definition: CacheDisk.cpp:148
void Touch(int64_t frame_number)
Move frame to front of queue (so it lasts longer)
Definition: CacheDisk.cpp:338
std::string Json()
Generate JSON string of this object.
Definition: CacheDisk.cpp:417
void Add(std::shared_ptr< openshot::Frame > frame)
Add a Frame to the cache.
Definition: CacheDisk.cpp:89
std::shared_ptr< openshot::Frame > GetSmallestFrame()
Get the smallest frame number.
Definition: CacheDisk.cpp:244
Json::Value JsonValue()
Generate Json::Value for this object.
Definition: CacheDisk.cpp:424
void Clear()
Clear the cache of all frames.
Definition: CacheDisk.cpp:364
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: CacheDisk.cpp:468
void SetJson(const std::string value)
Load JSON string into this object.
Definition: CacheDisk.cpp:451
int64_t GetBytes()
Gets the maximum bytes value.
Definition: CacheDisk.cpp:267
int64_t Count()
Count the frames in the queue.
Definition: CacheDisk.cpp:387
void Remove(int64_t frame_number)
Remove a specific frame.
Definition: CacheDisk.cpp:283
Exception for invalid JSON.
Definition: Exceptions.h:224
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:29
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16