Skip to content

Commit 629517f

Browse files
authored
Merge pull request #459 from OpenShot/timeline-reader-improvements
Adding ability for a Clip to auto-detect and instantiate a Timeline Reader
2 parents 4058dde + 4ea3623 commit 629517f

File tree

4 files changed

+168
-5
lines changed

4 files changed

+168
-5
lines changed

include/Settings.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ namespace openshot {
124124
/// The audio device name to use during playback
125125
std::string PLAYBACK_AUDIO_DEVICE_NAME = "";
126126

127+
/// The current install path of OpenShot (needs to be set when using Timeline(path), since certain
128+
/// paths depend on the location of OpenShot transitions and files)
129+
std::string PATH_OPENSHOT_INSTALL = "";
130+
127131
/// Create or get an instance of this logger singleton (invoke the class with this method)
128132
static Settings * Instance();
129133
};

include/Timeline.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <set>
3737
#include <QtGui/QImage>
3838
#include <QtGui/QPainter>
39+
#include <QtCore/QRegularExpression>
3940
#include "CacheBase.h"
4041
#include "CacheDisk.h"
4142
#include "CacheMemory.h"
@@ -156,6 +157,7 @@ namespace openshot {
156157
CacheBase *final_cache; ///<Final cache of timeline frames
157158
std::set<FrameMapper*> allocated_frame_mappers; ///< all the frame mappers we allocated and must free
158159
bool managed_cache; ///< Does this timeline instance manage the cache object
160+
std::string path; ///< Optional path of loaded UTF-8 OpenShot JSON project file
159161

160162
/// Process a new layer of video or audio
161163
void add_layer(std::shared_ptr<Frame> new_frame, Clip* source_clip, int64_t clip_frame_number, int64_t timeline_frame_number, bool is_top_clip, float max_volume);
@@ -209,6 +211,11 @@ namespace openshot {
209211
/// @param channel_layout The channel layout (i.e. mono, stereo, 3 point surround, etc...)
210212
Timeline(int width, int height, Fraction fps, int sample_rate, int channels, ChannelLayout channel_layout);
211213

214+
/// @brief Constructor for the timeline (which loads a JSON structure from a file path, and initializes a timeline)
215+
/// @param projectPath The path of the UTF-8 *.osp project file (JSON contents). Contents will be loaded automatically.
216+
/// @param convert_absolute_paths Should all paths be converted to absolute paths (based on the folder of the path provided)
217+
Timeline(std::string projectPath, bool convert_absolute_paths);
218+
212219
virtual ~Timeline();
213220

214221
/// @brief Add an openshot::Clip to the timeline

src/Clip.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "../include/QtImageReader.h"
3939
#include "../include/ChunkReader.h"
4040
#include "../include/DummyReader.h"
41+
#include "../include/Timeline.h"
4142

4243
using namespace openshot;
4344

@@ -159,7 +160,7 @@ Clip::Clip(std::string path) : resampler(NULL), audio_cache(NULL), reader(NULL),
159160

160161
// Get file extension (and convert to lower case)
161162
std::string ext = get_file_extension(path);
162-
transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
163+
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
163164

164165
// Determine if common video formats
165166
if (ext=="avi" || ext=="mov" || ext=="mkv" || ext=="mpg" || ext=="mpeg" || ext=="mp3" || ext=="mp4" || ext=="mts" ||
@@ -172,6 +173,16 @@ Clip::Clip(std::string path) : resampler(NULL), audio_cache(NULL), reader(NULL),
172173

173174
} catch(...) { }
174175
}
176+
if (ext=="osp")
177+
{
178+
try
179+
{
180+
// Open common video format
181+
reader = new Timeline(path, true);
182+
183+
} catch(...) { }
184+
}
185+
175186

176187
// If no video found, try each reader
177188
if (!reader)
@@ -319,12 +330,10 @@ std::shared_ptr<Frame> Clip::GetFrame(int64_t requested_frame)
319330

320331
// Now that we have re-mapped what frame number is needed, go and get the frame pointer
321332
std::shared_ptr<Frame> original_frame;
322-
#pragma omp critical (Clip_GetFrame)
323333
original_frame = GetOrCreateFrame(new_frame_number);
324334

325335
// Create a new frame
326336
std::shared_ptr<Frame> frame(new Frame(new_frame_number, 1, 1, "#000000", original_frame->GetAudioSamplesCount(), original_frame->GetAudioChannelsCount()));
327-
#pragma omp critical (Clip_GetFrame)
328337
{
329338
frame->SampleRate(original_frame->SampleRate());
330339
frame->ChannelsLayout(original_frame->ChannelsLayout());
@@ -789,6 +798,8 @@ Json::Value Clip::JsonValue() const {
789798

790799
if (reader)
791800
root["reader"] = reader->JsonValue();
801+
else
802+
root["reader"] = Json::Value(Json::objectValue);
792803

793804
// return JsonValue
794805
return root;
@@ -964,6 +975,12 @@ void Clip::SetJsonValue(const Json::Value root) {
964975
// Create new reader
965976
reader = new DummyReader();
966977
reader->SetJsonValue(root["reader"]);
978+
979+
} else if (type == "Timeline") {
980+
981+
// Create new reader (always load from file again)
982+
// This prevents FrameMappers from being loaded on accident
983+
reader = new Timeline(root["reader"]["path"].asString(), true);
967984
}
968985

969986
// mark as managed reader and set parent

src/Timeline.cpp

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ using namespace openshot;
3434

3535
// Default Constructor for the timeline (which sets the canvas width and height)
3636
Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int channels, ChannelLayout channel_layout) :
37-
is_open(false), auto_map_clips(true), managed_cache(true)
37+
is_open(false), auto_map_clips(true), managed_cache(true), path("")
3838
{
3939
// Create CrashHandler and Attach (incase of errors)
4040
CrashHandler::Instance();
@@ -64,6 +64,8 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha
6464
info.display_ratio = openshot::Fraction(width, height);
6565
info.display_ratio.Reduce();
6666
info.pixel_ratio = openshot::Fraction(1, 1);
67+
info.acodec = "openshot::timeline";
68+
info.vcodec = "openshot::timeline";
6769

6870
// Init max image size
6971
SetMaxSize(info.width, info.height);
@@ -73,6 +75,133 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha
7375
final_cache->SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels);
7476
}
7577

78+
// Constructor for the timeline (which loads a JSON structure from a file path, and initializes a timeline)
79+
Timeline::Timeline(std::string projectPath, bool convert_absolute_paths) :
80+
is_open(false), auto_map_clips(true), managed_cache(true), path(projectPath) {
81+
82+
// Create CrashHandler and Attach (incase of errors)
83+
CrashHandler::Instance();
84+
85+
// Init final cache as NULL (will be created after loading json)
86+
final_cache = NULL;
87+
88+
// Init viewport size (curve based, because it can be animated)
89+
viewport_scale = Keyframe(100.0);
90+
viewport_x = Keyframe(0.0);
91+
viewport_y = Keyframe(0.0);
92+
93+
// Init background color
94+
color.red = Keyframe(0.0);
95+
color.green = Keyframe(0.0);
96+
color.blue = Keyframe(0.0);
97+
98+
// Check if path exists
99+
QFileInfo filePath(QString::fromStdString(path));
100+
if (!filePath.exists()) {
101+
throw InvalidFile("File could not be opened.", path);
102+
}
103+
104+
// Check OpenShot Install Path exists
105+
Settings *s = Settings::Instance();
106+
QDir openshotPath(QString::fromStdString(s->PATH_OPENSHOT_INSTALL));
107+
if (!openshotPath.exists()) {
108+
throw InvalidFile("PATH_OPENSHOT_INSTALL could not be found.", s->PATH_OPENSHOT_INSTALL);
109+
}
110+
QDir openshotTransPath(openshotPath.filePath("transitions"));
111+
if (!openshotTransPath.exists()) {
112+
throw InvalidFile("PATH_OPENSHOT_INSTALL/transitions could not be found.", openshotTransPath.path().toStdString());
113+
}
114+
115+
// Determine asset path
116+
QString asset_name = filePath.baseName().left(30) + "_assets";
117+
QDir asset_folder(filePath.dir().filePath(asset_name));
118+
if (!asset_folder.exists()) {
119+
// Create directory if needed
120+
asset_folder.mkpath(".");
121+
}
122+
123+
// Load UTF-8 project file into QString
124+
QFile projectFile(QString::fromStdString(path));
125+
projectFile.open(QFile::ReadOnly);
126+
QString projectContents = QString::fromUtf8(projectFile.readAll());
127+
128+
// Convert all relative paths into absolute paths (if requested)
129+
if (convert_absolute_paths) {
130+
131+
// Find all "image" or "path" references in JSON (using regex). Must loop through match results
132+
// due to our path matching needs, which are not possible with the QString::replace() function.
133+
QRegularExpression allPathsRegex(QStringLiteral("\"(image|path)\":.*?\"(.*?)\""));
134+
std::vector<QRegularExpressionMatch> matchedPositions;
135+
QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(projectContents);
136+
while (i.hasNext()) {
137+
QRegularExpressionMatch match = i.next();
138+
if (match.hasMatch()) {
139+
// Push all match objects into a vector (so we can reverse them later)
140+
matchedPositions.push_back(match);
141+
}
142+
}
143+
144+
// Reverse the matches (bottom of file to top, so our replacements don't break our match positions)
145+
std::vector<QRegularExpressionMatch>::reverse_iterator itr;
146+
for (itr = matchedPositions.rbegin(); itr != matchedPositions.rend(); itr++) {
147+
QRegularExpressionMatch match = *itr;
148+
QString relativeKey = match.captured(1); // image or path
149+
QString relativePath = match.captured(2); // relative file path
150+
QString absolutePath = "";
151+
152+
// Find absolute path of all path, image (including special replacements of @assets and @transitions)
153+
if (relativePath.startsWith("@assets")) {
154+
absolutePath = QFileInfo(asset_folder.absoluteFilePath(relativePath.replace("@assets", "."))).canonicalFilePath();
155+
} else if (relativePath.startsWith("@transitions")) {
156+
absolutePath = QFileInfo(openshotTransPath.absoluteFilePath(relativePath.replace("@transitions", "."))).canonicalFilePath();
157+
} else {
158+
absolutePath = QFileInfo(filePath.absoluteDir().absoluteFilePath(relativePath)).canonicalFilePath();
159+
}
160+
161+
// Replace path in JSON content, if an absolute path was successfully found
162+
if (!absolutePath.isEmpty()) {
163+
projectContents.replace(match.capturedStart(0), match.capturedLength(0), "\"" + relativeKey + "\": \"" + absolutePath + "\"");
164+
}
165+
}
166+
// Clear matches
167+
matchedPositions.clear();
168+
}
169+
170+
// Set JSON of project
171+
SetJson(projectContents.toStdString());
172+
173+
// Calculate valid duration and set has_audio and has_video
174+
// based on content inside this Timeline's clips.
175+
float calculated_duration = 0.0;
176+
for (auto clip : clips)
177+
{
178+
float clip_last_frame = clip->Position() + clip->Duration();
179+
if (clip_last_frame > calculated_duration)
180+
calculated_duration = clip_last_frame;
181+
if (clip->Reader() && clip->Reader()->info.has_audio)
182+
info.has_audio = true;
183+
if (clip->Reader() && clip->Reader()->info.has_video)
184+
info.has_video = true;
185+
186+
}
187+
info.video_length = calculated_duration * info.fps.ToFloat();
188+
info.duration = calculated_duration;
189+
190+
// Init FileInfo settings
191+
info.acodec = "openshot::timeline";
192+
info.vcodec = "openshot::timeline";
193+
info.video_timebase = info.fps.Reciprocal();
194+
info.has_video = true;
195+
info.has_audio = true;
196+
197+
// Init max image size
198+
SetMaxSize(info.width, info.height);
199+
200+
// Init cache
201+
final_cache = new CacheMemory();
202+
final_cache->SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels);
203+
}
204+
76205
Timeline::~Timeline() {
77206
if (is_open)
78207
// Auto Close if not already
@@ -706,7 +835,8 @@ void Timeline::Close()
706835
is_open = false;
707836

708837
// Clear cache
709-
final_cache->Clear();
838+
if (final_cache)
839+
final_cache->Clear();
710840
}
711841

712842
// Open the reader (and start consuming resources)
@@ -984,6 +1114,7 @@ Json::Value Timeline::JsonValue() const {
9841114
root["viewport_x"] = viewport_x.JsonValue();
9851115
root["viewport_y"] = viewport_y.JsonValue();
9861116
root["color"] = color.JsonValue();
1117+
root["path"] = path;
9871118

9881119
// Add array of clips
9891120
root["clips"] = Json::Value(Json::arrayValue);
@@ -1037,6 +1168,10 @@ void Timeline::SetJsonValue(const Json::Value root) {
10371168
// Set parent data
10381169
ReaderBase::SetJsonValue(root);
10391170

1171+
// Set data from Json (if key is found)
1172+
if (!root["path"].isNull())
1173+
path = root["path"].asString();
1174+
10401175
if (!root["clips"].isNull()) {
10411176
// Clear existing clips
10421177
clips.clear();

0 commit comments

Comments
 (0)