26 #include <QRegularExpression>
27 #include <unordered_map>
35 is_open(false), auto_map_clips(true), managed_cache(true),
path(
""), max_time(0.0), cache_epoch(0)
81 info.width, info.height, info.fps, info.sample_rate,
82 info.channels, info.channel_layout) {}
86 is_open(false), auto_map_clips(true), managed_cache(true),
path(projectPath), max_time(0.0), cache_epoch(0) {
105 QFileInfo filePath(QString::fromStdString(path));
106 if (!filePath.exists()) {
107 throw InvalidFile(
"Timeline project file could not be opened.", path);
113 if (!openshotPath.exists()) {
116 QDir openshotTransPath(openshotPath.filePath(
"transitions"));
117 if (!openshotTransPath.exists()) {
118 throw InvalidFile(
"PATH_OPENSHOT_INSTALL/transitions could not be found.", openshotTransPath.path().toStdString());
122 QString asset_name = filePath.baseName().left(30) +
"_assets";
123 QDir asset_folder(filePath.dir().filePath(asset_name));
124 if (!asset_folder.exists()) {
126 asset_folder.mkpath(
".");
130 QFile projectFile(QString::fromStdString(path));
131 projectFile.open(QFile::ReadOnly);
132 QString projectContents = QString::fromUtf8(projectFile.readAll());
135 if (convert_absolute_paths) {
139 QRegularExpression allPathsRegex(QStringLiteral(
"\"(image|path)\":.*?\"(.*?)\""));
140 std::vector<QRegularExpressionMatch> matchedPositions;
141 QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(projectContents);
142 while (i.hasNext()) {
143 QRegularExpressionMatch match = i.next();
144 if (match.hasMatch()) {
146 matchedPositions.push_back(match);
151 std::vector<QRegularExpressionMatch>::reverse_iterator itr;
152 for (itr = matchedPositions.rbegin(); itr != matchedPositions.rend(); itr++) {
153 QRegularExpressionMatch match = *itr;
154 QString relativeKey = match.captured(1);
155 QString relativePath = match.captured(2);
156 QString absolutePath =
"";
159 if (relativePath.startsWith(
"@assets")) {
160 absolutePath = QFileInfo(asset_folder.absoluteFilePath(relativePath.replace(
"@assets",
"."))).canonicalFilePath();
161 }
else if (relativePath.startsWith(
"@transitions")) {
162 absolutePath = QFileInfo(openshotTransPath.absoluteFilePath(relativePath.replace(
"@transitions",
"."))).canonicalFilePath();
164 absolutePath = QFileInfo(filePath.absoluteDir().absoluteFilePath(relativePath)).canonicalFilePath();
168 if (!absolutePath.isEmpty()) {
169 projectContents.replace(match.capturedStart(0), match.capturedLength(0),
"\"" + relativeKey +
"\": \"" + absolutePath +
"\"");
173 matchedPositions.clear();
177 SetJson(projectContents.toStdString());
181 float calculated_duration = 0.0;
182 for (
auto clip : clips)
185 if (clip_last_frame > calculated_duration)
186 calculated_duration = clip_last_frame;
187 if (
clip->Reader() &&
clip->Reader()->info.has_audio)
189 if (
clip->Reader() &&
clip->Reader()->info.has_video)
222 if (managed_cache && final_cache) {
232 auto iterator = tracked_objects.find(trackedObject->Id());
234 if (iterator != tracked_objects.end()){
236 iterator->second = trackedObject;
240 tracked_objects[trackedObject->Id()] = trackedObject;
250 auto iterator = tracked_objects.find(
id);
252 if (iterator != tracked_objects.end()){
254 std::shared_ptr<openshot::TrackedObjectBase> trackedObject = iterator->second;
255 return trackedObject;
267 std::list<std::string> trackedObjects_ids;
270 for (
auto const& it: tracked_objects){
272 trackedObjects_ids.push_back(it.first);
275 return trackedObjects_ids;
283 Json::Value trackedObjectJson;
286 auto iterator = tracked_objects.find(
id);
288 if (iterator != tracked_objects.end())
291 std::shared_ptr<TrackedObjectBBox> trackedObject = std::static_pointer_cast<TrackedObjectBBox>(iterator->second);
294 if (trackedObject->ExactlyContains(frame_number)){
295 BBox box = trackedObject->GetBox(frame_number);
296 float x1 = box.
cx - (box.
width/2);
298 float x2 = box.
cx + (box.
width/2);
300 float rotation = box.
angle;
302 trackedObjectJson[
"x1"] = x1;
303 trackedObjectJson[
"y1"] = y1;
304 trackedObjectJson[
"x2"] = x2;
305 trackedObjectJson[
"y2"] = y2;
306 trackedObjectJson[
"rotation"] = rotation;
309 BBox box = trackedObject->BoxVec.begin()->second;
310 float x1 = box.
cx - (box.
width/2);
312 float x2 = box.
cx + (box.
width/2);
314 float rotation = box.
angle;
316 trackedObjectJson[
"x1"] = x1;
317 trackedObjectJson[
"y1"] = y1;
318 trackedObjectJson[
"x2"] = x2;
319 trackedObjectJson[
"y2"] = y2;
320 trackedObjectJson[
"rotation"] = rotation;
326 trackedObjectJson[
"x1"] = 0;
327 trackedObjectJson[
"y1"] = 0;
328 trackedObjectJson[
"x2"] = 0;
329 trackedObjectJson[
"y2"] = 0;
330 trackedObjectJson[
"rotation"] = 0;
333 return trackedObjectJson.toStyledString();
341 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
347 if (
clip->Reader() &&
clip->Reader()->GetCache())
348 clip->Reader()->GetCache()->Clear();
351 if (auto_map_clips) {
353 apply_mapper_to_clip(
clip);
356 InvalidateCacheForClip(
clip);
359 clips.push_back(
clip);
369 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
375 effects.push_back(effect);
385 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
387 effects.remove(effect);
390 if (allocated_effects.count(effect)) {
391 allocated_effects.erase(effect);
404 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
409 if (allocated_clips.count(
clip)) {
410 allocated_clips.erase(
clip);
423 for (
const auto&
clip : clips) {
435 for (
const auto& effect : effects) {
436 if (effect->Id() ==
id) {
446 for (
const auto&
clip : clips) {
447 const auto e =
clip->GetEffect(
id);
459 std::list<EffectBase*> timelineEffectsList;
462 for (
const auto&
clip : clips) {
465 std::list<EffectBase*> clipEffectsList =
clip->Effects();
468 timelineEffectsList.insert(timelineEffectsList.end(), clipEffectsList.begin(), clipEffectsList.end());
471 return timelineEffectsList;
484 const double frames = t * fps;
485 constexpr
double frame_boundary_epsilon = 1e-4;
488 if (frames > 0.0 && frames < frame_boundary_epsilon)
490 return static_cast<int64_t
>(std::ceil(frames - frame_boundary_epsilon));
498 return static_cast<int64_t
>(std::floor(t * fps)) + 1;
508 void Timeline::apply_mapper_to_clip(
Clip* clip)
511 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
515 if (
clip->Reader()->Name() ==
"FrameMapper")
528 allocated_frame_mappers.insert(mapper);
533 clip->Reader(clip_reader);
543 for (
auto clip : clips)
546 apply_mapper_to_clip(
clip);
551 double Timeline::calculate_time(int64_t number,
Fraction rate)
554 double raw_fps = rate.
ToFloat();
557 return double(number - 1) / raw_fps;
565 "Timeline::apply_effects",
566 "frame->number", frame->number,
567 "timeline_frame_number", timeline_frame_number,
571 for (
auto effect : effects)
575 int64_t effect_start_position =
static_cast<int64_t
>(std::llround(effect->Position() * fpsD)) + 1;
576 int64_t effect_end_position =
static_cast<int64_t
>(std::llround((effect->Position() + effect->Duration()) * fpsD));
578 bool does_effect_intersect = (effect_start_position <= timeline_frame_number && effect_end_position >= timeline_frame_number && effect->Layer() == layer);
581 if (does_effect_intersect)
584 int64_t effect_start_frame =
static_cast<int64_t
>(std::llround(effect->Start() * fpsD)) + 1;
585 int64_t effect_frame_number = timeline_frame_number - effect_start_position + effect_start_frame;
595 "Timeline::apply_effects (Process Effect)",
596 "effect_frame_number", effect_frame_number,
597 "does_effect_intersect", does_effect_intersect);
600 frame = effect->ProcessFrame(frame, effect_frame_number);
610 std::shared_ptr<Frame> Timeline::GetOrCreateFrame(std::shared_ptr<Frame> background_frame,
Clip* clip, int64_t number,
openshot::TimelineInfoStruct* options)
612 std::shared_ptr<Frame> new_frame;
620 "Timeline::GetOrCreateFrame (from reader)",
622 "samples_in_frame", samples_in_frame);
625 new_frame = std::shared_ptr<Frame>(
clip->
GetFrame(background_frame, number, options));
638 "Timeline::GetOrCreateFrame (create blank)",
640 "samples_in_frame", samples_in_frame);
647 void Timeline::add_layer(std::shared_ptr<Frame> new_frame,
Clip* source_clip, int64_t clip_frame_number,
bool is_top_clip,
float max_volume)
655 std::shared_ptr<Frame> source_frame;
656 source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number, &options);
664 "Timeline::add_layer",
665 "new_frame->number", new_frame->number,
666 "clip_frame_number", clip_frame_number);
669 if (source_clip->
Reader()->info.has_audio) {
672 "Timeline::add_layer (Copy Audio)",
673 "source_clip->Reader()->info.has_audio", source_clip->
Reader()->info.has_audio,
674 "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(),
676 "clip_frame_number", clip_frame_number);
681 if (new_frame->GetAudioSamplesCount() != source_frame->GetAudioSamplesCount()){
686 const auto transition_audio_gains = ResolveTransitionAudioGains(source_clip, new_frame->number, is_top_clip);
688 for (
int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++)
691 float previous_volume = source_clip->
volume.
GetValue(clip_frame_number - 1);
693 previous_volume *= transition_audio_gains.first;
694 volume *= transition_audio_gains.second;
701 previous_volume = previous_volume / max_volume;
702 volume = volume / max_volume;
706 previous_volume = previous_volume * 0.77;
707 volume = volume * 0.77;
711 if (channel_filter != -1 && channel_filter != channel)
715 if (previous_volume == 0.0 && volume == 0.0)
719 if (channel_mapping == -1)
720 channel_mapping = channel;
723 if (!isEqual(previous_volume, 1.0) || !isEqual(volume, 1.0))
724 source_frame->ApplyGainRamp(channel_mapping, 0, source_frame->GetAudioSamplesCount(), previous_volume, volume);
728 new_frame->AddAudio(
false, channel_mapping, 0, source_frame->GetAudioSamples(channel), source_frame->GetAudioSamplesCount(), 1.0);
734 "Timeline::add_layer (No Audio Copied - Wrong # of Channels)",
735 "source_clip->Reader()->info.has_audio",
736 source_clip->
Reader()->info.has_audio,
737 "source_frame->GetAudioChannelsCount()",
738 source_frame->GetAudioChannelsCount(),
740 "clip_frame_number", clip_frame_number);
745 "Timeline::add_layer (Transform: Composite Image Layer: Completed)",
746 "source_frame->number", source_frame->number,
747 "new_frame->GetImage()->width()", new_frame->GetWidth(),
748 "new_frame->GetImage()->height()", new_frame->GetHeight());
752 void Timeline::update_open_clips(
Clip *clip,
bool does_clip_intersect)
755 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
758 "Timeline::update_open_clips (before)",
759 "does_clip_intersect", does_clip_intersect,
760 "closing_clips.size()", closing_clips.size(),
761 "open_clips.size()", open_clips.size());
764 bool clip_found = open_clips.count(
clip);
771 if (clip_found && does_clip_intersect)
773 bool clip_reader_open =
false;
775 clip_reader_open =
clip->Reader() &&
clip->Reader()->IsOpen();
778 clip_reader_open =
false;
780 if (!
clip->IsOpen() || !clip_reader_open)
782 open_clips.erase(
clip);
788 if (clip_found && !does_clip_intersect)
791 open_clips.erase(
clip);
796 else if (!clip_found && does_clip_intersect)
811 "Timeline::update_open_clips (after)",
812 "does_clip_intersect", does_clip_intersect,
813 "clip_found", clip_found,
814 "closing_clips.size()", closing_clips.size(),
815 "open_clips.size()", open_clips.size());
819 void Timeline::calculate_max_duration() {
820 double last_clip = 0.0;
821 double last_effect = 0.0;
822 double first_clip = std::numeric_limits<double>::max();
823 double first_effect = std::numeric_limits<double>::max();
826 if (!clips.empty()) {
828 const auto max_clip = std::max_element(
830 last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
833 const auto min_clip = std::min_element(
835 return lhs->Position() < rhs->Position();
837 first_clip = (*min_clip)->Position();
841 if (!effects.empty()) {
843 const auto max_effect = std::max_element(
845 last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
848 const auto min_effect = std::min_element(
850 return lhs->Position() < rhs->Position();
852 first_effect = (*min_effect)->Position();
856 max_time = std::max(last_clip, last_effect);
857 min_time = std::min(first_clip, first_effect);
860 if (clips.empty() && effects.empty()) {
867 void Timeline::sort_clips()
870 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
874 "Timeline::SortClips",
875 "clips.size()", clips.size());
881 calculate_max_duration();
885 void Timeline::sort_effects()
888 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
894 calculate_max_duration();
903 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
906 for (
auto clip : clips)
908 update_open_clips(
clip,
false);
911 bool allocated = allocated_clips.count(
clip);
918 allocated_clips.clear();
921 for (
auto effect : effects)
924 bool allocated = allocated_effects.count(effect);
931 allocated_effects.clear();
934 for (
auto mapper : allocated_frame_mappers)
940 allocated_frame_mappers.clear();
949 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
952 for (
auto clip : clips)
955 update_open_clips(
clip,
false);
972 bool Timeline::isEqual(
double a,
double b)
974 return fabs(a - b) < 0.000001;
981 if (requested_frame < 1)
984 const bool past_timeline_end = (max_frame > 0 && requested_frame > max_frame);
987 std::shared_ptr<Frame> frame;
988 if (!past_timeline_end)
989 frame = final_cache->
GetFrame(requested_frame);
993 "Timeline::GetFrame (Cached frame found)",
994 "requested_frame", requested_frame);
1002 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1005 std::shared_ptr<Frame> frame;
1006 if (!past_timeline_end)
1007 frame = final_cache->
GetFrame(requested_frame);
1011 "Timeline::GetFrame (Cached frame found on 2nd check)",
1012 "requested_frame", requested_frame);
1019 std::vector<Clip *> nearby_clips;
1020 nearby_clips = find_intersecting_clips(requested_frame, 1,
true);
1024 "Timeline::GetFrame (processing frame)",
1025 "requested_frame", requested_frame,
1026 "omp_get_thread_num()", omp_get_thread_num());
1033 new_frame->AddAudioSilence(samples_in_frame);
1039 "Timeline::GetFrame (Adding solid color)",
1040 "requested_frame", requested_frame,
1052 "Timeline::GetFrame (Loop through clips)",
1053 "requested_frame", requested_frame,
1054 "clips.size()", clips.size(),
1055 "nearby_clips.size()", nearby_clips.size());
1062 int64_t start_frame;
1063 int64_t frame_number;
1066 std::vector<ClipInfo> clip_infos;
1067 clip_infos.reserve(nearby_clips.size());
1070 for (
auto clip : nearby_clips) {
1071 int64_t start_pos =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1073 bool intersects = (start_pos <= requested_frame && end_pos >= requested_frame);
1074 int64_t start_frame =
static_cast<int64_t
>(std::llround(
clip->
Start() * fpsD)) + 1;
1075 int64_t frame_number = requested_frame - start_pos + start_frame;
1076 clip_infos.push_back({
clip, start_pos, end_pos, start_frame, frame_number, intersects});
1080 std::unordered_map<int, int64_t> top_start_for_layer;
1081 std::unordered_map<int, Clip*> top_clip_for_layer;
1082 for (
const auto& ci : clip_infos) {
1083 if (!ci.intersects)
continue;
1084 const int layer = ci.clip->Layer();
1085 auto it = top_start_for_layer.find(layer);
1086 if (it == top_start_for_layer.end() || ci.start_pos > it->second) {
1087 top_start_for_layer[layer] = ci.start_pos;
1088 top_clip_for_layer[layer] = ci.clip;
1093 float max_volume_sum = 0.0f;
1094 for (
const auto& ci : clip_infos) {
1095 if (!ci.intersects)
continue;
1096 if (ci.clip->Reader() && ci.clip->Reader()->info.has_audio &&
1097 ci.clip->has_audio.GetInt(ci.frame_number) != 0) {
1098 max_volume_sum +=
static_cast<float>(ci.clip->volume.GetValue(ci.frame_number));
1103 for (
const auto& ci : clip_infos) {
1106 "Timeline::GetFrame (Does clip intersect)",
1107 "requested_frame", requested_frame,
1108 "clip->Position()", ci.clip->Position(),
1109 "clip->Duration()", ci.clip->Duration(),
1110 "does_clip_intersect", ci.intersects);
1113 if (ci.intersects) {
1115 bool is_top_clip =
false;
1116 const int layer = ci.clip->Layer();
1117 auto top_it = top_clip_for_layer.find(layer);
1118 if (top_it != top_clip_for_layer.end())
1119 is_top_clip = (top_it->second == ci.clip);
1122 int64_t clip_frame_number = ci.frame_number;
1126 "Timeline::GetFrame (Calculate clip's frame #)",
1127 "clip->Position()", ci.clip->Position(),
1128 "clip->Start()", ci.clip->Start(),
1130 "clip_frame_number", clip_frame_number);
1133 add_layer(new_frame, ci.clip, clip_frame_number, is_top_clip, max_volume_sum);
1138 "Timeline::GetFrame (clip does not intersect)",
1139 "requested_frame", requested_frame,
1140 "does_clip_intersect", ci.intersects);
1147 "Timeline::GetFrame (Add frame to cache)",
1148 "requested_frame", requested_frame,
1153 new_frame->SetFrameNumber(requested_frame);
1156 if (!past_timeline_end)
1157 final_cache->
Add(new_frame);
1166 std::vector<Clip*> Timeline::find_intersecting_clips(int64_t requested_frame,
int number_of_frames,
bool include)
1169 std::vector<Clip*> matching_clips;
1172 const int64_t min_requested_frame = requested_frame;
1173 const int64_t max_requested_frame = requested_frame + (number_of_frames - 1);
1176 matching_clips.reserve(clips.size());
1178 for (
auto clip : clips)
1181 int64_t clip_start_position =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1184 bool does_clip_intersect =
1185 (clip_start_position <= min_requested_frame || clip_start_position <= max_requested_frame) &&
1186 (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame);
1190 "Timeline::find_intersecting_clips (Is clip near or intersecting)",
1191 "requested_frame", requested_frame,
1192 "min_requested_frame", min_requested_frame,
1193 "max_requested_frame", max_requested_frame,
1195 "does_clip_intersect", does_clip_intersect);
1198 update_open_clips(
clip, does_clip_intersect);
1201 if (does_clip_intersect && include)
1203 matching_clips.push_back(
clip);
1205 else if (!does_clip_intersect && !include)
1207 matching_clips.push_back(
clip);
1212 return matching_clips;
1218 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1221 if (managed_cache && final_cache) {
1224 managed_cache =
false;
1228 final_cache = new_cache;
1243 root[
"type"] =
"Timeline";
1248 root[
"path"] = path;
1251 root[
"clips"] = Json::Value(Json::arrayValue);
1254 for (
const auto existing_clip : clips)
1256 root[
"clips"].append(existing_clip->JsonValue());
1260 root[
"effects"] = Json::Value(Json::arrayValue);
1263 for (
const auto existing_effect: effects)
1265 root[
"effects"].append(existing_effect->JsonValue());
1276 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1285 catch (
const std::exception& e)
1288 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
1296 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1299 bool was_open = is_open;
1306 if (!root[
"path"].isNull())
1307 path = root[
"path"].asString();
1309 if (!root[
"clips"].isNull()) {
1314 for (
const Json::Value existing_clip : root[
"clips"]) {
1316 if (existing_clip.isNull()) {
1324 allocated_clips.insert(c);
1341 if (!root[
"effects"].isNull()) {
1346 for (
const Json::Value existing_effect :root[
"effects"]) {
1348 if (existing_effect.isNull()) {
1355 if (!existing_effect[
"type"].isNull()) {
1357 if ( (e =
EffectInfo().CreateEffect(existing_effect[
"type"].asString())) ) {
1360 allocated_effects.insert(e);
1372 if (!root[
"duration"].isNull()) {
1398 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1404 const uint64_t initial_cache_epoch =
CacheEpoch();
1406 for (
const Json::Value change : root) {
1407 std::string change_key = change[
"key"][(uint)0].asString();
1410 if (change_key ==
"clips")
1412 apply_json_to_clips(change);
1414 else if (change_key ==
"effects")
1416 apply_json_to_effects(change);
1420 apply_json_to_timeline(change);
1425 if (!root.empty() &&
CacheEpoch() == initial_cache_epoch) {
1429 catch (
const std::exception& e)
1432 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
1436 void Timeline::BumpCacheEpoch() {
1437 cache_epoch.fetch_add(1, std::memory_order_relaxed);
1440 void Timeline::InvalidateCacheForClip(
const Clip* clip) {
1441 if (!
clip || !final_cache) {
1446 const int64_t starting_frame =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1448 final_cache->
Remove(starting_frame - 8, ending_frame + 8);
1453 void Timeline::apply_json_to_clips(Json::Value change) {
1456 std::string change_type = change[
"type"].asString();
1457 std::string clip_id =
"";
1458 Clip *existing_clip = NULL;
1461 for (
auto key_part : change[
"key"]) {
1463 if (key_part.isObject()) {
1465 if (!key_part[
"id"].isNull()) {
1467 clip_id = key_part[
"id"].asString();
1470 for (
auto c : clips)
1472 if (c->Id() == clip_id) {
1484 if (existing_clip && change[
"key"].size() == 4 && change[
"key"][2] ==
"effects")
1487 Json::Value key_part = change[
"key"][3];
1489 if (key_part.isObject()) {
1491 if (!key_part[
"id"].isNull())
1494 std::string effect_id = key_part[
"id"].asString();
1497 std::list<EffectBase*> effect_list = existing_clip->
Effects();
1498 for (
auto e : effect_list)
1500 if (e->Id() == effect_id) {
1502 apply_json_to_effects(change, e);
1512 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1522 if (change_type ==
"insert") {
1528 allocated_clips.insert(
clip);
1540 }
else if (change_type ==
"update") {
1543 if (existing_clip) {
1556 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1557 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1560 if (auto_map_clips) {
1561 apply_mapper_to_clip(existing_clip);
1565 }
else if (change_type ==
"delete") {
1568 if (existing_clip) {
1575 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1585 void Timeline::apply_json_to_effects(Json::Value change) {
1588 std::string change_type = change[
"type"].asString();
1592 for (
auto key_part : change[
"key"]) {
1594 if (key_part.isObject()) {
1596 if (!key_part[
"id"].isNull())
1599 std::string effect_id = key_part[
"id"].asString();
1602 for (
auto e : effects)
1604 if (e->Id() == effect_id) {
1605 existing_effect = e;
1615 if (existing_effect || change_type ==
"insert") {
1617 apply_json_to_effects(change, existing_effect);
1622 void Timeline::apply_json_to_effects(Json::Value change,
EffectBase* existing_effect) {
1625 std::string change_type = change[
"type"].asString();
1628 if (!change[
"value"].isArray() && !change[
"value"][
"position"].isNull()) {
1629 int64_t new_starting_frame = (change[
"value"][
"position"].asDouble() *
info.
fps.
ToDouble()) + 1;
1630 int64_t new_ending_frame = ((change[
"value"][
"position"].asDouble() + change[
"value"][
"end"].asDouble() - change[
"value"][
"start"].asDouble()) *
info.
fps.
ToDouble()) + 1;
1631 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1635 if (change_type ==
"insert") {
1638 std::string effect_type = change[
"value"][
"type"].asString();
1647 allocated_effects.insert(e);
1656 }
else if (change_type ==
"update") {
1659 if (existing_effect) {
1664 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1670 }
else if (change_type ==
"delete") {
1673 if (existing_effect) {
1678 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1691 void Timeline::apply_json_to_timeline(Json::Value change) {
1692 bool cache_dirty =
true;
1695 std::string change_type = change[
"type"].asString();
1696 std::string root_key = change[
"key"][(uint)0].asString();
1697 std::string sub_key =
"";
1698 if (change[
"key"].size() >= 2)
1699 sub_key = change[
"key"][(uint)1].asString();
1702 if (change_type ==
"insert" || change_type ==
"update") {
1706 if (root_key ==
"color")
1709 else if (root_key ==
"viewport_scale")
1712 else if (root_key ==
"viewport_x")
1715 else if (root_key ==
"viewport_y")
1718 else if (root_key ==
"duration") {
1724 cache_dirty =
false;
1726 else if (root_key ==
"width") {
1731 else if (root_key ==
"height") {
1736 else if (root_key ==
"fps" && sub_key ==
"" && change[
"value"].isObject()) {
1738 if (!change[
"value"][
"num"].isNull())
1739 info.
fps.
num = change[
"value"][
"num"].asInt();
1740 if (!change[
"value"][
"den"].isNull())
1741 info.
fps.
den = change[
"value"][
"den"].asInt();
1743 else if (root_key ==
"fps" && sub_key ==
"num")
1746 else if (root_key ==
"fps" && sub_key ==
"den")
1749 else if (root_key ==
"display_ratio" && sub_key ==
"" && change[
"value"].isObject()) {
1751 if (!change[
"value"][
"num"].isNull())
1753 if (!change[
"value"][
"den"].isNull())
1756 else if (root_key ==
"display_ratio" && sub_key ==
"num")
1759 else if (root_key ==
"display_ratio" && sub_key ==
"den")
1762 else if (root_key ==
"pixel_ratio" && sub_key ==
"" && change[
"value"].isObject()) {
1764 if (!change[
"value"][
"num"].isNull())
1766 if (!change[
"value"][
"den"].isNull())
1769 else if (root_key ==
"pixel_ratio" && sub_key ==
"num")
1772 else if (root_key ==
"pixel_ratio" && sub_key ==
"den")
1776 else if (root_key ==
"sample_rate")
1779 else if (root_key ==
"channels")
1782 else if (root_key ==
"channel_layout")
1787 throw InvalidJSONKey(
"JSON change key is invalid", change.toStyledString());
1790 }
else if (change[
"type"].asString() ==
"delete") {
1794 if (root_key ==
"color") {
1800 else if (root_key ==
"viewport_scale")
1802 else if (root_key ==
"viewport_x")
1804 else if (root_key ==
"viewport_y")
1808 throw InvalidJSONKey(
"JSON change key is invalid", change.toStyledString());
1821 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
1825 final_cache->
Clear();
1830 for (
const auto clip : clips) {
1832 if (
clip->Reader()) {
1833 if (
auto rc =
clip->Reader()->GetCache())
1837 if (deep &&
clip->Reader()->Name() ==
"FrameMapper") {
1839 if (nested_reader->
Reader()) {
1847 if (
auto cc =
clip->GetCache())
1866 display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio);
1874 std::pair<float, float> Timeline::ResolveTransitionAudioGains(
Clip* source_clip, int64_t timeline_frame_number,
bool is_top_clip)
const
1876 constexpr
double half_pi = 1.57079632679489661923;
1879 return {1.0f, 1.0f};
1882 Mask* active_mask =
nullptr;
1883 int64_t effect_start_position = 0;
1884 int64_t effect_end_position = 0;
1887 for (
auto effect : effects) {
1888 if (effect->Layer() != source_clip->
Layer())
1891 auto* mask =
dynamic_cast<Mask*
>(effect);
1892 if (!mask || !mask->fade_audio_hint)
1895 const int64_t start_pos =
static_cast<int64_t
>(std::llround(effect->Position() * fpsD)) + 1;
1896 const int64_t end_pos =
static_cast<int64_t
>(std::llround((effect->Position() + effect->Duration()) * fpsD));
1897 if (start_pos > timeline_frame_number || end_pos < timeline_frame_number)
1901 return {1.0f, 1.0f};
1904 effect_start_position = start_pos;
1905 effect_end_position = end_pos;
1909 return {1.0f, 1.0f};
1911 struct AudibleClipInfo {
1917 std::vector<AudibleClipInfo> audible_clips;
1918 audible_clips.reserve(2);
1921 for (
auto clip : clips) {
1924 if (!
clip->Reader() || !
clip->Reader()->info.has_audio)
1927 const int64_t clip_start_pos =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1929 if (clip_start_pos > timeline_frame_number || clip_end_pos < timeline_frame_number)
1932 const int64_t clip_start_frame =
static_cast<int64_t
>(std::llround(
clip->
Start() * fpsD)) + 1;
1933 const int64_t clip_frame_number = timeline_frame_number - clip_start_pos + clip_start_frame;
1934 if (
clip->has_audio.GetInt(clip_frame_number) == 0)
1937 audible_clips.push_back({
clip, clip_start_pos, clip_end_pos});
1938 if (audible_clips.size() > 2)
1939 return {1.0f, 1.0f};
1942 if (audible_clips.empty())
1943 return {1.0f, 1.0f};
1946 const auto source_it = std::find_if(
1947 audible_clips.begin(),
1948 audible_clips.end(),
1949 [source_clip](
const AudibleClipInfo&
info) {
1950 return info.clip == source_clip;
1952 if (source_it == audible_clips.end())
1953 return {1.0f, 1.0f};
1956 if (audible_clips.size() == 2) {
1957 auto top_it = std::max_element(
1958 audible_clips.begin(),
1959 audible_clips.end(),
1960 [](
const AudibleClipInfo& lhs,
const AudibleClipInfo& rhs) {
1961 if (lhs.start_pos != rhs.start_pos)
1962 return lhs.start_pos < rhs.start_pos;
1963 return std::less<Clip*>()(lhs.clip, rhs.clip);
1965 if ((is_top_clip && source_clip != top_it->clip) || (!is_top_clip && source_clip == top_it->clip))
1966 return {1.0f, 1.0f};
1970 const int64_t left_distance = std::llabs(effect_start_position - source_it->start_pos);
1971 const int64_t right_distance = std::llabs(effect_end_position - source_it->end_pos);
1972 const bool clip_fades_in = left_distance <= right_distance;
1975 const auto compute_gain = [&](int64_t frame_number) ->
float {
1976 if (effect_end_position <= effect_start_position)
1979 const double span =
static_cast<double>(effect_end_position - effect_start_position);
1980 double t =
static_cast<double>(frame_number - effect_start_position) / span;
1986 return static_cast<float>(clip_fades_in ? std::sin(t * half_pi) : std::cos(t * half_pi));
1989 return {compute_gain(timeline_frame_number - 1), compute_gain(timeline_frame_number)};
Header file for CacheBase class.
Header file for CacheDisk class.
Header file for CacheMemory class.
Header file for CrashHandler class.
Header file for all Exception classes.
Header file for the FrameMapper class.
Header file for Mask class.
#define OPEN_MP_NUM_PROCESSORS
Header file for Timeline class.
All cache managers in libopenshot are based on this CacheBase class.
virtual void Clear()=0
Clear the cache of all frames.
virtual void Remove(int64_t frame_number)=0
Remove a specific frame.
virtual void Add(std::shared_ptr< openshot::Frame > frame)=0
Add a Frame to the cache.
void SetMaxBytesFromInfo(int64_t number_of_frames, int width, int height, int sample_rate, int channels)
Set maximum bytes to a different amount based on a ReaderInfo struct.
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
Get a frame from the cache.
This class is a memory-based cache manager for Frame objects.
void Clear()
Clear the cache of all frames.
float Start() const
Get start position (in seconds) of clip (trim start of video)
float Duration() const
Get the length of this clip (in seconds)
std::string Id() const
Get the Id of this clip object.
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
float Position() const
Get position on timeline (in seconds)
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
This class represents a clip (used to arrange readers on the timeline)
std::list< openshot::EffectBase * > Effects()
Return the list of effects on the timeline.
openshot::VolumeMixType mixing
What strategy should be followed when mixing audio with other clips.
openshot::Keyframe channel_filter
A number representing an audio channel to filter (clears all other channels)
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
openshot::Keyframe has_audio
An optional override to determine if this clip has audio (-1=undefined, 0=no, 1=yes)
openshot::Keyframe volume
Curve representing the volume (0 to 1)
openshot::Keyframe channel_mapping
A number representing an audio channel to output (only works when filtering a channel)
void Reader(openshot::ReaderBase *new_reader)
Set the current reader.
openshot::CacheMemory * GetCache() override
Get the cache object (always return NULL for this reader)
This class represents a color (used on the timeline and clips)
std::string GetColorHex(int64_t frame_number)
Get the HEX value of a color at a specific frame.
openshot::Keyframe blue
Curve representing the red value (0 - 255)
openshot::Keyframe red
Curve representing the red value (0 - 255)
openshot::Keyframe green
Curve representing the green value (0 - 255)
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Json::Value JsonValue() const
Generate Json::Value for this object.
static CrashHandler * Instance()
This abstract class is the base class, used by all effects in libopenshot.
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
This class returns a listing of all effects supported by libopenshot.
EffectBase * CreateEffect(std::string effect_type)
Create an instance of an effect (factory style)
This class represents a fraction.
int num
Numerator for the fraction.
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
void Reduce()
Reduce this fraction (i.e. 640/480 = 4/3)
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
int den
Denominator for the fraction.
This class creates a mapping between 2 different frame rates, applying a specific pull-down technique...
void ChangeMapping(Fraction target_fps, PulldownType pulldown, int target_sample_rate, int target_channels, ChannelLayout target_channel_layout)
Change frame rate or audio mapping details.
ReaderBase * Reader()
Get the current reader.
void Close() override
Close the openshot::FrameMapper and internal reader.
int GetSamplesPerFrame(openshot::Fraction fps, int sample_rate, int channels)
Calculate the # of samples per video frame (for the current frame number)
Exception for files that can not be found or opened.
Exception for missing JSON Change key.
Exception for invalid JSON.
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
double GetValue(int64_t index) const
Get the value at a specific index.
Json::Value JsonValue() const
Generate Json::Value for this object.
int64_t GetCount() const
Get the number of points (i.e. # of points)
This class uses the image libraries to apply alpha (or transparency) masks to any frame....
Exception for frames that are out of bounds.
This abstract class is the base class, used by all readers in libopenshot.
openshot::ReaderInfo info
Information about the current media file.
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
Exception when a reader is closed, and a frame is requested.
This class is contains settings used by libopenshot (and can be safely toggled at any point)
std::string PATH_OPENSHOT_INSTALL
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
This class represents a timeline.
void AddTrackedObject(std::shared_ptr< openshot::TrackedObjectBase > trackedObject)
Add to the tracked_objects map a pointer to a tracked object (TrackedObjectBBox)
Json::Value JsonValue() const override
Generate Json::Value for this object.
openshot::Keyframe viewport_scale
Curve representing the scale of the viewport (0 to 100)
void ApplyJsonDiff(std::string value)
Apply a special formatted JSON object, which represents a change to the timeline (add,...
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
void AddClip(openshot::Clip *clip)
Add an openshot::Clip to the timeline.
std::list< openshot::EffectBase * > ClipEffects() const
Return the list of effects on all clips.
std::list< std::string > GetTrackedObjectsIds() const
Return the ID's of the tracked objects as a list of strings.
std::string Json() const override
Generate JSON string of this object.
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
double GetMinTime()
Look up the position/start time of the first timeline element.
std::shared_ptr< openshot::Frame > GetFrame(int64_t requested_frame) override
void ApplyMapperToClips()
Apply the timeline's framerate and samplerate to all clips.
openshot::Color color
Background color of timeline canvas.
std::string GetTrackedObjectValues(std::string id, int64_t frame_number) const
Return the trackedObject's properties as a JSON string.
Timeline(int width, int height, openshot::Fraction fps, int sample_rate, int channels, openshot::ChannelLayout channel_layout)
Constructor for the timeline (which configures the default frame properties)
std::shared_ptr< openshot::TrackedObjectBase > GetTrackedObject(std::string id) const
Return tracked object pointer by it's id.
int64_t GetMinFrame()
Look up the start frame number of the first element on the timeline (first frame is 1)
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
openshot::Clip * GetClip(const std::string &id)
Look up a single clip by ID.
void ClearAllCache(bool deep=false)
void AddEffect(openshot::EffectBase *effect)
Add an effect to the timeline.
void SetCache(openshot::CacheBase *new_cache)
uint64_t CacheEpoch() const
Return the current cache invalidation epoch.
void Clear()
Clear all clips, effects, and frame mappers from timeline (and free memory)
openshot::Keyframe viewport_x
Curve representing the x coordinate for the viewport.
void RemoveClip(openshot::Clip *clip)
Remove an openshot::Clip from the timeline.
void SetMaxSize(int width, int height)
double GetMaxTime()
Look up the end time of the latest timeline element.
void RemoveEffect(openshot::EffectBase *effect)
Remove an effect from the timeline.
std::shared_ptr< openshot::Frame > apply_effects(std::shared_ptr< openshot::Frame > frame, int64_t timeline_frame_number, int layer, TimelineInfoStruct *options)
Apply global/timeline effects to the source frame (if any)
void Open() override
Open the reader (and start consuming resources)
void SetJson(const std::string value) override
Load JSON string into this object.
openshot::Keyframe viewport_y
Curve representing the y coordinate for the viewport.
void Close() override
Close the timeline reader (and any resources it was consuming)
void AppendDebugMethod(std::string method_name, std::string arg1_name="", float arg1_value=-1.0, std::string arg2_name="", float arg2_value=-1.0, std::string arg3_name="", float arg3_value=-1.0, std::string arg4_name="", float arg4_value=-1.0, std::string arg5_name="", float arg5_value=-1.0, std::string arg6_name="", float arg6_value=-1.0)
Append debug information.
static ZmqLogger * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
This namespace is the default namespace for all code in the openshot library.
@ PULLDOWN_NONE
Do not apply pull-down techniques, just repeat or skip entire frames.
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...
@ VOLUME_MIX_AVERAGE
Evenly divide the overlapping clips volume keyframes, so that the sum does not exceed 100%.
@ VOLUME_MIX_REDUCE
Reduce volume by about %25, and then mix (louder, but could cause pops if the sum exceeds 100%)
const Json::Value stringToJson(const std::string value)
This struct holds the information of a bounding-box.
float cy
y-coordinate of the bounding box center
float height
bounding box height
float cx
x-coordinate of the bounding box center
float width
bounding box width
float angle
bounding box rotation angle [degrees]
Like CompareClipEndFrames, but for effects.
This struct contains info about a media file, such as height, width, frames per second,...
float duration
Length of time (in seconds)
int width
The width of the video (in pixesl)
int channels
The number of audio channels used in the audio stream.
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
openshot::Fraction display_ratio
The ratio of width to height of the video stream (i.e. 640x480 has a ratio of 4/3)
int height
The height of the video (in pixels)
int64_t video_length
The number of frames in the video stream.
std::string acodec
The name of the audio codec used to encode / decode the video stream.
std::string vcodec
The name of the video codec used to encode / decode the video stream.
openshot::Fraction pixel_ratio
The pixel ratio of the video stream as a fraction (i.e. some pixels are not square)
openshot::ChannelLayout channel_layout
The channel layout (mono, stereo, 5 point surround, etc...)
bool has_video
Determines if this file has a video stream.
bool has_audio
Determines if this file has an audio stream.
openshot::Fraction video_timebase
The video timebase determines how long each frame stays on the screen.
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
This struct contains info about the current Timeline clip instance.
bool is_before_clip_keyframes
Is this before clip keyframes are applied.
bool is_top_clip
Is clip on top (if overlapping another clip)