OpenShot Library | libopenshot 0.6.0
Loading...
Searching...
No Matches
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
23using namespace std;
24using namespace openshot;
25
26// Default constructor, no max bytes
27CacheDisk::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;
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
43CacheDisk::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;
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
58void 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
89void 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);
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
148bool 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)
157std::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
227std::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)
244std::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
283void CacheDisk::Remove(int64_t frame_number)
284{
285 Remove(frame_number, frame_number);
286}
287
288// Remove range of frames
289void 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)
335}
336
337// Move frame to front of queue (so it lasts longer)
338void 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();
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
397void 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
417std::string CacheDisk::Json() {
418
419 // Return formatted string
420 return JsonValue().toStyledString();
421}
422
423// Generate Json::Value for this object
424Json::Value CacheDisk::JsonValue() {
425
426 // Process range data (if anything has changed)
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
451void 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
468void 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.
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.
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.
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)
Get a frame from the cache.
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.
void Touch(int64_t frame_number)
Move frame to front of queue (so it lasts longer)
std::string Json()
Generate JSON string of this object.
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.
Json::Value JsonValue()
Generate Json::Value for this object.
void Clear()
Clear the cache of all frames.
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
void SetJson(const std::string value)
Load JSON string into this object.
int64_t GetBytes()
Gets the maximum bytes value.
int64_t Count()
Count the frames in the queue.
void Remove(int64_t frame_number)
Remove a specific frame.
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