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);
357 clips.push_back(
clip);
367 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
373 effects.push_back(effect);
383 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
385 effects.remove(effect);
388 if (allocated_effects.count(effect)) {
389 allocated_effects.erase(effect);
402 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
407 if (allocated_clips.count(
clip)) {
408 allocated_clips.erase(
clip);
421 for (
const auto&
clip : clips) {
433 for (
const auto& effect : effects) {
434 if (effect->Id() ==
id) {
444 for (
const auto&
clip : clips) {
445 const auto e =
clip->GetEffect(
id);
457 std::list<EffectBase*> timelineEffectsList;
460 for (
const auto&
clip : clips) {
463 std::list<EffectBase*> clipEffectsList =
clip->Effects();
466 timelineEffectsList.insert(timelineEffectsList.end(), clipEffectsList.begin(), clipEffectsList.end());
469 return timelineEffectsList;
483 return static_cast<int64_t
>(std::ceil(t * fps));
491 return static_cast<int64_t
>(std::floor(t * fps)) + 1;
501 void Timeline::apply_mapper_to_clip(
Clip* clip)
504 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
508 if (
clip->Reader()->Name() ==
"FrameMapper")
521 allocated_frame_mappers.insert(mapper);
526 clip->Reader(clip_reader);
536 for (
auto clip : clips)
539 apply_mapper_to_clip(
clip);
544 double Timeline::calculate_time(int64_t number,
Fraction rate)
547 double raw_fps = rate.
ToFloat();
550 return double(number - 1) / raw_fps;
558 "Timeline::apply_effects",
559 "frame->number", frame->number,
560 "timeline_frame_number", timeline_frame_number,
564 for (
auto effect : effects)
568 int64_t effect_start_position =
static_cast<int64_t
>(std::llround(effect->Position() * fpsD)) + 1;
569 int64_t effect_end_position =
static_cast<int64_t
>(std::llround((effect->Position() + effect->Duration()) * fpsD));
571 bool does_effect_intersect = (effect_start_position <= timeline_frame_number && effect_end_position >= timeline_frame_number && effect->Layer() == layer);
574 if (does_effect_intersect)
577 int64_t effect_start_frame =
static_cast<int64_t
>(std::llround(effect->Start() * fpsD)) + 1;
578 int64_t effect_frame_number = timeline_frame_number - effect_start_position + effect_start_frame;
588 "Timeline::apply_effects (Process Effect)",
589 "effect_frame_number", effect_frame_number,
590 "does_effect_intersect", does_effect_intersect);
593 frame = effect->ProcessFrame(frame, effect_frame_number);
603 std::shared_ptr<Frame> Timeline::GetOrCreateFrame(std::shared_ptr<Frame> background_frame,
Clip* clip, int64_t number,
openshot::TimelineInfoStruct* options)
605 std::shared_ptr<Frame> new_frame;
613 "Timeline::GetOrCreateFrame (from reader)",
615 "samples_in_frame", samples_in_frame);
618 new_frame = std::shared_ptr<Frame>(
clip->
GetFrame(background_frame, number, options));
631 "Timeline::GetOrCreateFrame (create blank)",
633 "samples_in_frame", samples_in_frame);
640 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)
648 std::shared_ptr<Frame> source_frame;
649 source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number, &options);
657 "Timeline::add_layer",
658 "new_frame->number", new_frame->number,
659 "clip_frame_number", clip_frame_number);
662 if (source_clip->
Reader()->info.has_audio) {
665 "Timeline::add_layer (Copy Audio)",
666 "source_clip->Reader()->info.has_audio", source_clip->
Reader()->info.has_audio,
667 "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(),
669 "clip_frame_number", clip_frame_number);
674 if (new_frame->GetAudioSamplesCount() != source_frame->GetAudioSamplesCount()){
679 const auto transition_audio_gains = ResolveTransitionAudioGains(source_clip, new_frame->number, is_top_clip);
681 for (
int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++)
684 float previous_volume = source_clip->
volume.
GetValue(clip_frame_number - 1);
686 previous_volume *= transition_audio_gains.first;
687 volume *= transition_audio_gains.second;
694 previous_volume = previous_volume / max_volume;
695 volume = volume / max_volume;
699 previous_volume = previous_volume * 0.77;
700 volume = volume * 0.77;
704 if (channel_filter != -1 && channel_filter != channel)
708 if (previous_volume == 0.0 && volume == 0.0)
712 if (channel_mapping == -1)
713 channel_mapping = channel;
716 if (!isEqual(previous_volume, 1.0) || !isEqual(volume, 1.0))
717 source_frame->ApplyGainRamp(channel_mapping, 0, source_frame->GetAudioSamplesCount(), previous_volume, volume);
721 new_frame->AddAudio(
false, channel_mapping, 0, source_frame->GetAudioSamples(channel), source_frame->GetAudioSamplesCount(), 1.0);
727 "Timeline::add_layer (No Audio Copied - Wrong # of Channels)",
728 "source_clip->Reader()->info.has_audio",
729 source_clip->
Reader()->info.has_audio,
730 "source_frame->GetAudioChannelsCount()",
731 source_frame->GetAudioChannelsCount(),
733 "clip_frame_number", clip_frame_number);
738 "Timeline::add_layer (Transform: Composite Image Layer: Completed)",
739 "source_frame->number", source_frame->number,
740 "new_frame->GetImage()->width()", new_frame->GetWidth(),
741 "new_frame->GetImage()->height()", new_frame->GetHeight());
745 void Timeline::update_open_clips(
Clip *clip,
bool does_clip_intersect)
748 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
751 "Timeline::update_open_clips (before)",
752 "does_clip_intersect", does_clip_intersect,
753 "closing_clips.size()", closing_clips.size(),
754 "open_clips.size()", open_clips.size());
757 bool clip_found = open_clips.count(
clip);
759 if (clip_found && !does_clip_intersect)
762 open_clips.erase(
clip);
767 else if (!clip_found && does_clip_intersect)
783 "Timeline::update_open_clips (after)",
784 "does_clip_intersect", does_clip_intersect,
785 "clip_found", clip_found,
786 "closing_clips.size()", closing_clips.size(),
787 "open_clips.size()", open_clips.size());
791 void Timeline::calculate_max_duration() {
792 double last_clip = 0.0;
793 double last_effect = 0.0;
794 double first_clip = std::numeric_limits<double>::max();
795 double first_effect = std::numeric_limits<double>::max();
798 if (!clips.empty()) {
800 const auto max_clip = std::max_element(
802 last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
805 const auto min_clip = std::min_element(
807 return lhs->Position() < rhs->Position();
809 first_clip = (*min_clip)->Position();
813 if (!effects.empty()) {
815 const auto max_effect = std::max_element(
817 last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
820 const auto min_effect = std::min_element(
822 return lhs->Position() < rhs->Position();
824 first_effect = (*min_effect)->Position();
828 max_time = std::max(last_clip, last_effect);
829 min_time = std::min(first_clip, first_effect);
832 if (clips.empty() && effects.empty()) {
839 void Timeline::sort_clips()
842 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
846 "Timeline::SortClips",
847 "clips.size()", clips.size());
853 calculate_max_duration();
857 void Timeline::sort_effects()
860 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
866 calculate_max_duration();
875 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
878 for (
auto clip : clips)
880 update_open_clips(
clip,
false);
883 bool allocated = allocated_clips.count(
clip);
890 allocated_clips.clear();
893 for (
auto effect : effects)
896 bool allocated = allocated_effects.count(effect);
903 allocated_effects.clear();
906 for (
auto mapper : allocated_frame_mappers)
912 allocated_frame_mappers.clear();
921 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
924 for (
auto clip : clips)
927 update_open_clips(
clip,
false);
944 bool Timeline::isEqual(
double a,
double b)
946 return fabs(a - b) < 0.000001;
953 if (requested_frame < 1)
956 const bool past_timeline_end = (max_frame > 0 && requested_frame > max_frame);
959 std::shared_ptr<Frame> frame;
960 if (!past_timeline_end)
961 frame = final_cache->
GetFrame(requested_frame);
965 "Timeline::GetFrame (Cached frame found)",
966 "requested_frame", requested_frame);
974 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
977 std::shared_ptr<Frame> frame;
978 if (!past_timeline_end)
979 frame = final_cache->
GetFrame(requested_frame);
983 "Timeline::GetFrame (Cached frame found on 2nd check)",
984 "requested_frame", requested_frame);
991 std::vector<Clip *> nearby_clips;
992 nearby_clips = find_intersecting_clips(requested_frame, 1,
true);
996 "Timeline::GetFrame (processing frame)",
997 "requested_frame", requested_frame,
998 "omp_get_thread_num()", omp_get_thread_num());
1005 new_frame->AddAudioSilence(samples_in_frame);
1011 "Timeline::GetFrame (Adding solid color)",
1012 "requested_frame", requested_frame,
1024 "Timeline::GetFrame (Loop through clips)",
1025 "requested_frame", requested_frame,
1026 "clips.size()", clips.size(),
1027 "nearby_clips.size()", nearby_clips.size());
1034 int64_t start_frame;
1035 int64_t frame_number;
1038 std::vector<ClipInfo> clip_infos;
1039 clip_infos.reserve(nearby_clips.size());
1042 for (
auto clip : nearby_clips) {
1043 int64_t start_pos =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1045 bool intersects = (start_pos <= requested_frame && end_pos >= requested_frame);
1046 int64_t start_frame =
static_cast<int64_t
>(std::llround(
clip->
Start() * fpsD)) + 1;
1047 int64_t frame_number = requested_frame - start_pos + start_frame;
1048 clip_infos.push_back({
clip, start_pos, end_pos, start_frame, frame_number, intersects});
1052 std::unordered_map<int, int64_t> top_start_for_layer;
1053 std::unordered_map<int, Clip*> top_clip_for_layer;
1054 for (
const auto& ci : clip_infos) {
1055 if (!ci.intersects)
continue;
1056 const int layer = ci.clip->Layer();
1057 auto it = top_start_for_layer.find(layer);
1058 if (it == top_start_for_layer.end() || ci.start_pos > it->second) {
1059 top_start_for_layer[layer] = ci.start_pos;
1060 top_clip_for_layer[layer] = ci.clip;
1065 float max_volume_sum = 0.0f;
1066 for (
const auto& ci : clip_infos) {
1067 if (!ci.intersects)
continue;
1068 if (ci.clip->Reader() && ci.clip->Reader()->info.has_audio &&
1069 ci.clip->has_audio.GetInt(ci.frame_number) != 0) {
1070 max_volume_sum +=
static_cast<float>(ci.clip->volume.GetValue(ci.frame_number));
1075 for (
const auto& ci : clip_infos) {
1078 "Timeline::GetFrame (Does clip intersect)",
1079 "requested_frame", requested_frame,
1080 "clip->Position()", ci.clip->Position(),
1081 "clip->Duration()", ci.clip->Duration(),
1082 "does_clip_intersect", ci.intersects);
1085 if (ci.intersects) {
1087 bool is_top_clip =
false;
1088 const int layer = ci.clip->Layer();
1089 auto top_it = top_clip_for_layer.find(layer);
1090 if (top_it != top_clip_for_layer.end())
1091 is_top_clip = (top_it->second == ci.clip);
1094 int64_t clip_frame_number = ci.frame_number;
1098 "Timeline::GetFrame (Calculate clip's frame #)",
1099 "clip->Position()", ci.clip->Position(),
1100 "clip->Start()", ci.clip->Start(),
1102 "clip_frame_number", clip_frame_number);
1105 add_layer(new_frame, ci.clip, clip_frame_number, is_top_clip, max_volume_sum);
1110 "Timeline::GetFrame (clip does not intersect)",
1111 "requested_frame", requested_frame,
1112 "does_clip_intersect", ci.intersects);
1119 "Timeline::GetFrame (Add frame to cache)",
1120 "requested_frame", requested_frame,
1125 new_frame->SetFrameNumber(requested_frame);
1128 if (!past_timeline_end)
1129 final_cache->
Add(new_frame);
1138 std::vector<Clip*> Timeline::find_intersecting_clips(int64_t requested_frame,
int number_of_frames,
bool include)
1141 std::vector<Clip*> matching_clips;
1144 const int64_t min_requested_frame = requested_frame;
1145 const int64_t max_requested_frame = requested_frame + (number_of_frames - 1);
1148 matching_clips.reserve(clips.size());
1150 for (
auto clip : clips)
1153 int64_t clip_start_position =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1156 bool does_clip_intersect =
1157 (clip_start_position <= min_requested_frame || clip_start_position <= max_requested_frame) &&
1158 (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame);
1162 "Timeline::find_intersecting_clips (Is clip near or intersecting)",
1163 "requested_frame", requested_frame,
1164 "min_requested_frame", min_requested_frame,
1165 "max_requested_frame", max_requested_frame,
1167 "does_clip_intersect", does_clip_intersect);
1170 update_open_clips(
clip, does_clip_intersect);
1173 if (does_clip_intersect && include)
1175 matching_clips.push_back(
clip);
1177 else if (!does_clip_intersect && !include)
1179 matching_clips.push_back(
clip);
1184 return matching_clips;
1190 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1193 if (managed_cache && final_cache) {
1196 managed_cache =
false;
1200 final_cache = new_cache;
1215 root[
"type"] =
"Timeline";
1220 root[
"path"] = path;
1223 root[
"clips"] = Json::Value(Json::arrayValue);
1226 for (
const auto existing_clip : clips)
1228 root[
"clips"].append(existing_clip->JsonValue());
1232 root[
"effects"] = Json::Value(Json::arrayValue);
1235 for (
const auto existing_effect: effects)
1237 root[
"effects"].append(existing_effect->JsonValue());
1248 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1257 catch (
const std::exception& e)
1260 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
1268 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1271 bool was_open = is_open;
1278 if (!root[
"path"].isNull())
1279 path = root[
"path"].asString();
1281 if (!root[
"clips"].isNull()) {
1286 for (
const Json::Value existing_clip : root[
"clips"]) {
1288 if (existing_clip.isNull()) {
1296 allocated_clips.insert(c);
1313 if (!root[
"effects"].isNull()) {
1318 for (
const Json::Value existing_effect :root[
"effects"]) {
1320 if (existing_effect.isNull()) {
1327 if (!existing_effect[
"type"].isNull()) {
1329 if ( (e =
EffectInfo().CreateEffect(existing_effect[
"type"].asString())) ) {
1332 allocated_effects.insert(e);
1344 if (!root[
"duration"].isNull()) {
1370 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1377 for (
const Json::Value change : root) {
1378 std::string change_key = change[
"key"][(uint)0].asString();
1381 if (change_key ==
"clips")
1383 apply_json_to_clips(change);
1385 else if (change_key ==
"effects")
1387 apply_json_to_effects(change);
1391 apply_json_to_timeline(change);
1396 if (!root.empty()) {
1400 catch (
const std::exception& e)
1403 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
1407 void Timeline::BumpCacheEpoch() {
1408 cache_epoch.fetch_add(1, std::memory_order_relaxed);
1412 void Timeline::apply_json_to_clips(Json::Value change) {
1415 std::string change_type = change[
"type"].asString();
1416 std::string clip_id =
"";
1417 Clip *existing_clip = NULL;
1420 for (
auto key_part : change[
"key"]) {
1422 if (key_part.isObject()) {
1424 if (!key_part[
"id"].isNull()) {
1426 clip_id = key_part[
"id"].asString();
1429 for (
auto c : clips)
1431 if (c->Id() == clip_id) {
1443 if (existing_clip && change[
"key"].size() == 4 && change[
"key"][2] ==
"effects")
1446 Json::Value key_part = change[
"key"][3];
1448 if (key_part.isObject()) {
1450 if (!key_part[
"id"].isNull())
1453 std::string effect_id = key_part[
"id"].asString();
1456 std::list<EffectBase*> effect_list = existing_clip->
Effects();
1457 for (
auto e : effect_list)
1459 if (e->Id() == effect_id) {
1461 apply_json_to_effects(change, e);
1471 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1481 if (change_type ==
"insert") {
1487 allocated_clips.insert(
clip);
1498 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1500 }
else if (change_type ==
"update") {
1503 if (existing_clip) {
1516 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1517 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1520 if (auto_map_clips) {
1521 apply_mapper_to_clip(existing_clip);
1525 }
else if (change_type ==
"delete") {
1528 if (existing_clip) {
1535 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1545 void Timeline::apply_json_to_effects(Json::Value change) {
1548 std::string change_type = change[
"type"].asString();
1552 for (
auto key_part : change[
"key"]) {
1554 if (key_part.isObject()) {
1556 if (!key_part[
"id"].isNull())
1559 std::string effect_id = key_part[
"id"].asString();
1562 for (
auto e : effects)
1564 if (e->Id() == effect_id) {
1565 existing_effect = e;
1575 if (existing_effect || change_type ==
"insert") {
1577 apply_json_to_effects(change, existing_effect);
1582 void Timeline::apply_json_to_effects(Json::Value change,
EffectBase* existing_effect) {
1585 std::string change_type = change[
"type"].asString();
1588 if (!change[
"value"].isArray() && !change[
"value"][
"position"].isNull()) {
1589 int64_t new_starting_frame = (change[
"value"][
"position"].asDouble() *
info.
fps.
ToDouble()) + 1;
1590 int64_t new_ending_frame = ((change[
"value"][
"position"].asDouble() + change[
"value"][
"end"].asDouble() - change[
"value"][
"start"].asDouble()) *
info.
fps.
ToDouble()) + 1;
1591 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1595 if (change_type ==
"insert") {
1598 std::string effect_type = change[
"value"][
"type"].asString();
1607 allocated_effects.insert(e);
1616 }
else if (change_type ==
"update") {
1619 if (existing_effect) {
1624 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1630 }
else if (change_type ==
"delete") {
1633 if (existing_effect) {
1638 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1651 void Timeline::apply_json_to_timeline(Json::Value change) {
1652 bool cache_dirty =
true;
1655 std::string change_type = change[
"type"].asString();
1656 std::string root_key = change[
"key"][(uint)0].asString();
1657 std::string sub_key =
"";
1658 if (change[
"key"].size() >= 2)
1659 sub_key = change[
"key"][(uint)1].asString();
1662 if (change_type ==
"insert" || change_type ==
"update") {
1666 if (root_key ==
"color")
1669 else if (root_key ==
"viewport_scale")
1672 else if (root_key ==
"viewport_x")
1675 else if (root_key ==
"viewport_y")
1678 else if (root_key ==
"duration") {
1684 cache_dirty =
false;
1686 else if (root_key ==
"width") {
1691 else if (root_key ==
"height") {
1696 else if (root_key ==
"fps" && sub_key ==
"" && change[
"value"].isObject()) {
1698 if (!change[
"value"][
"num"].isNull())
1699 info.
fps.
num = change[
"value"][
"num"].asInt();
1700 if (!change[
"value"][
"den"].isNull())
1701 info.
fps.
den = change[
"value"][
"den"].asInt();
1703 else if (root_key ==
"fps" && sub_key ==
"num")
1706 else if (root_key ==
"fps" && sub_key ==
"den")
1709 else if (root_key ==
"display_ratio" && sub_key ==
"" && change[
"value"].isObject()) {
1711 if (!change[
"value"][
"num"].isNull())
1713 if (!change[
"value"][
"den"].isNull())
1716 else if (root_key ==
"display_ratio" && sub_key ==
"num")
1719 else if (root_key ==
"display_ratio" && sub_key ==
"den")
1722 else if (root_key ==
"pixel_ratio" && sub_key ==
"" && change[
"value"].isObject()) {
1724 if (!change[
"value"][
"num"].isNull())
1726 if (!change[
"value"][
"den"].isNull())
1729 else if (root_key ==
"pixel_ratio" && sub_key ==
"num")
1732 else if (root_key ==
"pixel_ratio" && sub_key ==
"den")
1736 else if (root_key ==
"sample_rate")
1739 else if (root_key ==
"channels")
1742 else if (root_key ==
"channel_layout")
1747 throw InvalidJSONKey(
"JSON change key is invalid", change.toStyledString());
1750 }
else if (change[
"type"].asString() ==
"delete") {
1754 if (root_key ==
"color") {
1760 else if (root_key ==
"viewport_scale")
1762 else if (root_key ==
"viewport_x")
1764 else if (root_key ==
"viewport_y")
1768 throw InvalidJSONKey(
"JSON change key is invalid", change.toStyledString());
1781 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
1785 final_cache->
Clear();
1790 for (
const auto clip : clips) {
1792 if (
clip->Reader()) {
1793 if (
auto rc =
clip->Reader()->GetCache())
1797 if (deep &&
clip->Reader()->Name() ==
"FrameMapper") {
1799 if (nested_reader->
Reader()) {
1807 if (
auto cc =
clip->GetCache())
1826 display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio);
1834 std::pair<float, float> Timeline::ResolveTransitionAudioGains(
Clip* source_clip, int64_t timeline_frame_number,
bool is_top_clip)
const
1836 constexpr
double half_pi = 1.57079632679489661923;
1839 return {1.0f, 1.0f};
1842 Mask* active_mask =
nullptr;
1843 int64_t effect_start_position = 0;
1844 int64_t effect_end_position = 0;
1847 for (
auto effect : effects) {
1848 if (effect->Layer() != source_clip->
Layer())
1851 auto* mask =
dynamic_cast<Mask*
>(effect);
1852 if (!mask || !mask->fade_audio_hint)
1855 const int64_t start_pos =
static_cast<int64_t
>(std::llround(effect->Position() * fpsD)) + 1;
1856 const int64_t end_pos =
static_cast<int64_t
>(std::llround((effect->Position() + effect->Duration()) * fpsD));
1857 if (start_pos > timeline_frame_number || end_pos < timeline_frame_number)
1861 return {1.0f, 1.0f};
1864 effect_start_position = start_pos;
1865 effect_end_position = end_pos;
1869 return {1.0f, 1.0f};
1871 struct AudibleClipInfo {
1877 std::vector<AudibleClipInfo> audible_clips;
1878 audible_clips.reserve(2);
1881 for (
auto clip : clips) {
1884 if (!
clip->Reader() || !
clip->Reader()->info.has_audio)
1887 const int64_t clip_start_pos =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1889 if (clip_start_pos > timeline_frame_number || clip_end_pos < timeline_frame_number)
1892 const int64_t clip_start_frame =
static_cast<int64_t
>(std::llround(
clip->
Start() * fpsD)) + 1;
1893 const int64_t clip_frame_number = timeline_frame_number - clip_start_pos + clip_start_frame;
1894 if (
clip->has_audio.GetInt(clip_frame_number) == 0)
1897 audible_clips.push_back({
clip, clip_start_pos, clip_end_pos});
1898 if (audible_clips.size() > 2)
1899 return {1.0f, 1.0f};
1902 if (audible_clips.empty())
1903 return {1.0f, 1.0f};
1906 const auto source_it = std::find_if(
1907 audible_clips.begin(),
1908 audible_clips.end(),
1909 [source_clip](
const AudibleClipInfo&
info) {
1910 return info.clip == source_clip;
1912 if (source_it == audible_clips.end())
1913 return {1.0f, 1.0f};
1916 if (audible_clips.size() == 2) {
1917 auto top_it = std::max_element(
1918 audible_clips.begin(),
1919 audible_clips.end(),
1920 [](
const AudibleClipInfo& lhs,
const AudibleClipInfo& rhs) {
1921 if (lhs.start_pos != rhs.start_pos)
1922 return lhs.start_pos < rhs.start_pos;
1923 return std::less<Clip*>()(lhs.clip, rhs.clip);
1925 if ((is_top_clip && source_clip != top_it->clip) || (!is_top_clip && source_clip == top_it->clip))
1926 return {1.0f, 1.0f};
1930 const int64_t left_distance = std::llabs(effect_start_position - source_it->start_pos);
1931 const int64_t right_distance = std::llabs(effect_end_position - source_it->end_pos);
1932 const bool clip_fades_in = left_distance <= right_distance;
1935 const auto compute_gain = [&](int64_t frame_number) ->
float {
1936 if (effect_end_position <= effect_start_position)
1939 const double span =
static_cast<double>(effect_end_position - effect_start_position);
1940 double t =
static_cast<double>(frame_number - effect_start_position) / span;
1946 return static_cast<float>(clip_fades_in ? std::sin(t * half_pi) : std::cos(t * half_pi));
1949 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)
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)