OpenShot Library | libopenshot  0.5.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 }
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::CacheBase::needs_range_processing
bool needs_range_processing
Something has changed, and the range data needs to be re-calculated.
Definition: CacheBase.h:40
openshot::CacheBase::max_bytes
int64_t max_bytes
This is the max number of bytes to cache (0 = no limit)
Definition: CacheBase.h:38
openshot::CacheBase::ordered_frame_numbers
std::vector< int64_t > ordered_frame_numbers
Ordered list of frame numbers used by cache.
Definition: CacheBase.h:42
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
openshot::CacheDisk::Contains
bool Contains(int64_t frame_number)
Check if frame is already contained in cache.
Definition: CacheDisk.cpp:148
openshot::CacheDisk::Touch
void Touch(int64_t frame_number)
Move frame to front of queue (so it lasts longer)
Definition: CacheDisk.cpp:338
openshot::CacheBase::json_ranges
std::string json_ranges
JSON ranges of frame numbers.
Definition: CacheBase.h:41
CacheDisk.h
Header file for CacheDisk class.
QtUtilities.h
Header file for QtUtilities (compatibiity overlay)
openshot::CacheDisk::Remove
void Remove(int64_t frame_number)
Remove a specific frame.
Definition: CacheDisk.cpp:283
openshot::CacheDisk::Clear
void Clear()
Clear the cache of all frames.
Definition: CacheDisk.cpp:364
openshot::CacheBase
All cache managers in libopenshot are based on this CacheBase class.
Definition: CacheBase.h:34
openshot::CacheBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: CacheBase.cpp:111
openshot::CacheDisk::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: CacheDisk.cpp:468
openshot::CacheDisk::Count
int64_t Count()
Count the frames in the queue.
Definition: CacheDisk.cpp:387
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:217
openshot::CacheDisk::Json
std::string Json()
Generate JSON string of this object.
Definition: CacheDisk.cpp:417
openshot::CacheDisk::CacheDisk
CacheDisk(std::string cache_path, std::string format, float quality, float scale)
Default constructor, no max bytes.
Definition: CacheDisk.cpp:27
openshot::CacheDisk::GetFrame
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)
Get a frame from the cache.
Definition: CacheDisk.cpp:157
openshot::CacheBase::JsonValue
virtual Json::Value JsonValue()=0
Generate Json::Value for this object.
Definition: CacheBase.cpp:100
openshot::CacheBase::CalculateRanges
void CalculateRanges()
Calculate ranges of frames.
Definition: CacheBase.cpp:36
openshot::CacheDisk::GetBytes
int64_t GetBytes()
Gets the maximum bytes value.
Definition: CacheDisk.cpp:267
Frame.h
Header file for Frame class.
openshot::CacheBase::cache_type
std::string cache_type
This is a friendly type name of the derived cache instance.
Definition: CacheBase.h:37
openshot::CacheDisk::GetFrames
std::vector< std::shared_ptr< openshot::Frame > > GetFrames()
Get an array of all Frames.
Definition: CacheDisk.cpp:227
openshot::CacheDisk::JsonValue
Json::Value JsonValue()
Generate Json::Value for this object.
Definition: CacheDisk.cpp:424
openshot::CacheDisk::GetSmallestFrame
std::shared_ptr< openshot::Frame > GetSmallestFrame()
Get the smallest frame number.
Definition: CacheDisk.cpp:244
openshot::CacheDisk::SetJson
void SetJson(const std::string value)
Load JSON string into this object.
Definition: CacheDisk.cpp:451
openshot::CacheDisk::Add
void Add(std::shared_ptr< openshot::Frame > frame)
Add a Frame to the cache.
Definition: CacheDisk.cpp:89
openshot::ChannelLayout
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...
Definition: ChannelLayouts.h:28
openshot::CacheDisk::~CacheDisk
~CacheDisk()
Definition: CacheDisk.cpp:80
openshot::CacheBase::range_version
int64_t range_version
The version of the JSON range data (incremented with each change)
Definition: CacheBase.h:44
Exceptions.h
Header file for all Exception classes.
openshot::CacheBase::cacheMutex
std::recursive_mutex * cacheMutex
Mutex for multiple threads.
Definition: CacheBase.h:47