@@ -34,7 +34,7 @@ using namespace openshot;
3434
3535// Default Constructor for the timeline (which sets the canvas width and height)
3636Timeline::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,107 @@ 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.makeAbsolute ();
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 (does not support relative paths with ../)
129+ // In otherwords, assets and files must be located in a child/sub-folder (and not from outside this folder)
130+ if (convert_absolute_paths) {
131+ // Convert all paths into absolute (if requested)
132+ QRegularExpression pathRegex (QStringLiteral (" \" (image|path)\" :.*?\" (?:\\ ./)?(?!@assets|@transitions+)(.*?)\" " ));
133+ projectContents.replace (pathRegex, " \"\\ 1\" : \" " + filePath.absoluteDir ().absoluteFilePath (" \\ 2" ) + " \" " );
134+
135+ // Convert all transitions paths into absolute (if requested)
136+ QRegularExpression transRegex (QStringLiteral (" \" (image|path)\" :.*?\" @transitions/*(.*?)\" " ));
137+ projectContents.replace (transRegex, " \"\\ 1\" : \" " + openshotTransPath.absoluteFilePath (" \\ 2" ) + " \" " );
138+
139+ // Convert all assets paths into absolute
140+ QRegularExpression assetRegex (QStringLiteral (" \" (image|path)\" :.*?\" @assets/*(.*?)\" " ));
141+ projectContents.replace (assetRegex, " \"\\ 1\" : \" " + asset_folder.absoluteFilePath (" \\ 2" ) + " \" " );
142+ }
143+
144+ // Set JSON of project
145+ SetJson (projectContents.toStdString ());
146+
147+ // Calculate valid duration and set has_audio and has_video
148+ // based on content inside this Timeline's clips.
149+ float calculated_duration = 0.0 ;
150+ for (auto clip : clips)
151+ {
152+ float clip_last_frame = clip->Position () + clip->Duration ();
153+ if (clip_last_frame > calculated_duration)
154+ calculated_duration = clip_last_frame;
155+ if (clip->Reader () && clip->Reader ()->info .has_audio )
156+ info.has_audio = true ;
157+ if (clip->Reader () && clip->Reader ()->info .has_video )
158+ info.has_video = true ;
159+
160+ }
161+ info.video_length = calculated_duration * info.fps .ToFloat ();
162+ info.duration = calculated_duration;
163+
164+ // Init FileInfo settings
165+ info.acodec = " openshot::timeline" ;
166+ info.vcodec = " openshot::timeline" ;
167+ info.video_timebase = info.fps .Reciprocal ();
168+ info.has_video = true ;
169+ info.has_audio = true ;
170+
171+ // Init max image size
172+ SetMaxSize (info.width , info.height );
173+
174+ // Init cache
175+ final_cache = new CacheMemory ();
176+ final_cache->SetMaxBytesFromInfo (OPEN_MP_NUM_PROCESSORS * 2 , info.width , info.height , info.sample_rate , info.channels );
177+ }
178+
76179Timeline::~Timeline () {
77180 if (is_open)
78181 // Auto Close if not already
@@ -706,7 +809,8 @@ void Timeline::Close()
706809 is_open = false ;
707810
708811 // Clear cache
709- final_cache->Clear ();
812+ if (final_cache)
813+ final_cache->Clear ();
710814}
711815
712816// Open the reader (and start consuming resources)
@@ -984,6 +1088,7 @@ Json::Value Timeline::JsonValue() const {
9841088 root[" viewport_x" ] = viewport_x.JsonValue ();
9851089 root[" viewport_y" ] = viewport_y.JsonValue ();
9861090 root[" color" ] = color.JsonValue ();
1091+ root[" path" ] = path;
9871092
9881093 // Add array of clips
9891094 root[" clips" ] = Json::Value (Json::arrayValue);
@@ -1037,6 +1142,10 @@ void Timeline::SetJsonValue(const Json::Value root) {
10371142 // Set parent data
10381143 ReaderBase::SetJsonValue (root);
10391144
1145+ // Set data from Json (if key is found)
1146+ if (!root[" path" ].isNull ())
1147+ path = root[" path" ].asString ();
1148+
10401149 if (!root[" clips" ].isNull ()) {
10411150 // Clear existing clips
10421151 clips.clear ();
0 commit comments