28#include <QRegularExpression>
29#include <unordered_map>
36Timeline::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),
path(
""), max_time(0.0), cache_epoch(0)
83 info.width, info.height, info.fps, info.sample_rate,
84 info.channels, info.channel_layout) {}
88 is_open(false), auto_map_clips(true), managed_cache(true),
path(projectPath), max_time(0.0), cache_epoch(0) {
107 QFileInfo filePath(QString::fromStdString(path));
108 if (!filePath.exists()) {
109 throw InvalidFile(
"Timeline project file could not be opened.", path);
115 if (!openshotPath.exists()) {
118 QDir openshotTransPath(openshotPath.filePath(
"transitions"));
119 if (!openshotTransPath.exists()) {
120 throw InvalidFile(
"PATH_OPENSHOT_INSTALL/transitions could not be found.", openshotTransPath.path().toStdString());
124 QString asset_name = filePath.baseName().left(30) +
"_assets";
125 QDir asset_folder(filePath.dir().filePath(asset_name));
126 if (!asset_folder.exists()) {
128 asset_folder.mkpath(
".");
132 QFile projectFile(QString::fromStdString(path));
133 projectFile.open(QFile::ReadOnly);
134 QString projectContents = QString::fromUtf8(projectFile.readAll());
137 if (convert_absolute_paths) {
141 QRegularExpression allPathsRegex(QStringLiteral(
"\"(image|path)\":.*?\"(.*?)\""));
142 std::vector<QRegularExpressionMatch> matchedPositions;
143 QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(projectContents);
144 while (i.hasNext()) {
145 QRegularExpressionMatch match = i.next();
146 if (match.hasMatch()) {
148 matchedPositions.push_back(match);
153 std::vector<QRegularExpressionMatch>::reverse_iterator itr;
154 for (itr = matchedPositions.rbegin(); itr != matchedPositions.rend(); itr++) {
155 QRegularExpressionMatch match = *itr;
156 QString relativeKey = match.captured(1);
157 QString relativePath = match.captured(2);
158 QString absolutePath =
"";
161 if (relativePath.startsWith(
"@assets")) {
162 absolutePath = QFileInfo(asset_folder.absoluteFilePath(relativePath.replace(
"@assets",
"."))).canonicalFilePath();
163 }
else if (relativePath.startsWith(
"@transitions")) {
164 absolutePath = QFileInfo(openshotTransPath.absoluteFilePath(relativePath.replace(
"@transitions",
"."))).canonicalFilePath();
166 absolutePath = QFileInfo(filePath.absoluteDir().absoluteFilePath(relativePath)).canonicalFilePath();
170 if (!absolutePath.isEmpty()) {
171 projectContents.replace(match.capturedStart(0), match.capturedLength(0),
"\"" + relativeKey +
"\": \"" + absolutePath +
"\"");
175 matchedPositions.clear();
179 SetJson(projectContents.toStdString());
183 float calculated_duration = 0.0;
184 for (
auto clip : clips)
187 if (clip_last_frame > calculated_duration)
188 calculated_duration = clip_last_frame;
189 if (
clip->Reader() &&
clip->Reader()->info.has_audio)
191 if (
clip->Reader() &&
clip->Reader()->info.has_video)
224 if (managed_cache && final_cache) {
234 auto iterator = tracked_objects.find(trackedObject->Id());
236 if (iterator != tracked_objects.end()){
238 iterator->second = trackedObject;
242 tracked_objects[trackedObject->Id()] = trackedObject;
252 auto iterator = tracked_objects.find(
id);
254 if (iterator != tracked_objects.end()){
256 std::shared_ptr<openshot::TrackedObjectBase> trackedObject = iterator->second;
257 return trackedObject;
269 std::list<std::string> trackedObjects_ids;
272 for (
auto const& it: tracked_objects){
274 trackedObjects_ids.push_back(it.first);
277 return trackedObjects_ids;
285 Json::Value trackedObjectJson;
288 auto iterator = tracked_objects.find(
id);
290 if (iterator != tracked_objects.end())
293 std::shared_ptr<TrackedObjectBBox> trackedObject = std::static_pointer_cast<TrackedObjectBBox>(iterator->second);
296 if (trackedObject->ExactlyContains(frame_number)){
297 BBox box = trackedObject->GetBox(frame_number);
298 float x1 = box.
cx - (box.
width/2);
300 float x2 = box.
cx + (box.
width/2);
302 float rotation = box.
angle;
304 trackedObjectJson[
"x1"] = x1;
305 trackedObjectJson[
"y1"] = y1;
306 trackedObjectJson[
"x2"] = x2;
307 trackedObjectJson[
"y2"] = y2;
308 trackedObjectJson[
"rotation"] = rotation;
311 BBox box = trackedObject->BoxVec.begin()->second;
312 float x1 = box.
cx - (box.
width/2);
314 float x2 = box.
cx + (box.
width/2);
316 float rotation = box.
angle;
318 trackedObjectJson[
"x1"] = x1;
319 trackedObjectJson[
"y1"] = y1;
320 trackedObjectJson[
"x2"] = x2;
321 trackedObjectJson[
"y2"] = y2;
322 trackedObjectJson[
"rotation"] = rotation;
328 trackedObjectJson[
"x1"] = 0;
329 trackedObjectJson[
"y1"] = 0;
330 trackedObjectJson[
"x2"] = 0;
331 trackedObjectJson[
"y2"] = 0;
332 trackedObjectJson[
"rotation"] = 0;
335 return trackedObjectJson.toStyledString();
343 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
349 if (
clip->Reader() &&
clip->Reader()->GetCache())
350 clip->Reader()->GetCache()->Clear();
353 if (auto_map_clips) {
355 apply_mapper_to_clip(
clip);
358 InvalidateCacheForClip(
clip);
361 clips.push_back(
clip);
371 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
377 effects.push_back(effect);
387 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
389 effects.remove(effect);
392 if (allocated_effects.count(effect)) {
393 allocated_effects.erase(effect);
406 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
411 if (allocated_clips.count(
clip)) {
412 allocated_clips.erase(
clip);
425 for (
const auto&
clip : clips) {
437 for (
const auto& effect : effects) {
438 if (effect->Id() ==
id) {
448 for (
const auto&
clip : clips) {
449 const auto e =
clip->GetEffect(
id);
461 std::list<EffectBase*> timelineEffectsList;
464 for (
const auto&
clip : clips) {
467 std::list<EffectBase*> clipEffectsList =
clip->Effects();
470 timelineEffectsList.insert(timelineEffectsList.end(), clipEffectsList.begin(), clipEffectsList.end());
473 return timelineEffectsList;
486 const double frames = t * fps;
487 constexpr double frame_boundary_epsilon = 1e-4;
490 if (frames > 0.0 && frames < frame_boundary_epsilon)
492 return static_cast<int64_t
>(std::ceil(frames - frame_boundary_epsilon));
500 return static_cast<int64_t
>(std::floor(t * fps)) + 1;
510void Timeline::apply_mapper_to_clip(
Clip* clip)
513 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
517 if (
clip->Reader()->Name() ==
"FrameMapper")
530 allocated_frame_mappers.insert(mapper);
535 clip->Reader(clip_reader);
545 for (
auto clip : clips)
548 apply_mapper_to_clip(
clip);
553double Timeline::calculate_time(int64_t number,
Fraction rate)
556 double raw_fps = rate.
ToFloat();
559 return double(number - 1) / raw_fps;
567 "Timeline::apply_effects",
568 "frame->number", frame->number,
569 "timeline_frame_number", timeline_frame_number,
573 for (
auto effect : effects)
577 int64_t effect_start_position =
static_cast<int64_t
>(std::llround(effect->Position() * fpsD)) + 1;
578 int64_t effect_end_position =
static_cast<int64_t
>(std::llround((effect->Position() + effect->Duration()) * fpsD));
580 bool does_effect_intersect = (effect_start_position <= timeline_frame_number && effect_end_position >= timeline_frame_number && effect->Layer() == layer);
583 if (does_effect_intersect)
586 int64_t effect_start_frame =
static_cast<int64_t
>(std::llround(effect->Start() * fpsD)) + 1;
587 int64_t effect_frame_number = timeline_frame_number - effect_start_position + effect_start_frame;
597 "Timeline::apply_effects (Process Effect)",
598 "effect_frame_number", effect_frame_number,
599 "does_effect_intersect", does_effect_intersect);
602 frame = effect->ProcessFrame(frame, effect_frame_number);
612std::shared_ptr<Frame> Timeline::GetOrCreateFrame(std::shared_ptr<Frame> background_frame,
Clip* clip, int64_t number,
openshot::TimelineInfoStruct* options)
614 std::shared_ptr<Frame> new_frame;
622 "Timeline::GetOrCreateFrame (from reader)",
624 "samples_in_frame", samples_in_frame);
627 new_frame = std::shared_ptr<Frame>(
clip->
GetFrame(background_frame, number, options));
640 "Timeline::GetOrCreateFrame (create blank)",
642 "samples_in_frame", samples_in_frame);
649void Timeline::add_layer(std::shared_ptr<Frame> new_frame,
Clip* source_clip, int64_t clip_frame_number,
bool is_top_clip,
float max_volume)
657 std::shared_ptr<Frame> source_frame;
658 source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number, &options);
666 "Timeline::add_layer",
667 "new_frame->number", new_frame->number,
668 "clip_frame_number", clip_frame_number);
671 if (source_clip->
Reader()->info.has_audio) {
674 "Timeline::add_layer (Copy Audio)",
675 "source_clip->Reader()->info.has_audio", source_clip->
Reader()->info.has_audio,
676 "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(),
678 "clip_frame_number", clip_frame_number);
683 if (new_frame->GetAudioSamplesCount() != source_frame->GetAudioSamplesCount()){
688 const auto transition_audio_gains = ResolveTransitionAudioGains(source_clip, new_frame->number, is_top_clip);
690 for (
int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++)
693 float previous_volume = source_clip->
volume.
GetValue(clip_frame_number - 1);
695 previous_volume *= transition_audio_gains.first;
696 volume *= transition_audio_gains.second;
703 previous_volume = previous_volume / max_volume;
704 volume = volume / max_volume;
708 previous_volume = previous_volume * 0.77;
709 volume = volume * 0.77;
713 if (channel_filter != -1 && channel_filter != channel)
717 if (previous_volume == 0.0 && volume == 0.0)
721 if (channel_mapping == -1)
722 channel_mapping = channel;
725 if (!isEqual(previous_volume, 1.0) || !isEqual(volume, 1.0))
726 source_frame->ApplyGainRamp(channel_mapping, 0, source_frame->GetAudioSamplesCount(), previous_volume, volume);
730 new_frame->AddAudio(
false, channel_mapping, 0, source_frame->GetAudioSamples(channel), source_frame->GetAudioSamplesCount(), 1.0);
736 "Timeline::add_layer (No Audio Copied - Wrong # of Channels)",
737 "source_clip->Reader()->info.has_audio",
738 source_clip->
Reader()->info.has_audio,
739 "source_frame->GetAudioChannelsCount()",
740 source_frame->GetAudioChannelsCount(),
742 "clip_frame_number", clip_frame_number);
747 "Timeline::add_layer (Transform: Composite Image Layer: Completed)",
748 "source_frame->number", source_frame->number,
749 "new_frame->GetImage()->width()", new_frame->GetWidth(),
750 "new_frame->GetImage()->height()", new_frame->GetHeight());
754void Timeline::update_open_clips(
Clip *clip,
bool does_clip_intersect)
757 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
760 "Timeline::update_open_clips (before)",
761 "does_clip_intersect", does_clip_intersect,
762 "closing_clips.size()", closing_clips.size(),
763 "open_clips.size()", open_clips.size());
766 bool clip_found = open_clips.count(
clip);
773 if (clip_found && does_clip_intersect)
775 bool clip_reader_open =
false;
777 clip_reader_open =
clip->Reader() &&
clip->Reader()->IsOpen();
780 clip_reader_open =
false;
782 if (!
clip->IsOpen() || !clip_reader_open)
784 open_clips.erase(
clip);
790 if (clip_found && !does_clip_intersect)
793 open_clips.erase(
clip);
798 else if (!clip_found && does_clip_intersect)
813 "Timeline::update_open_clips (after)",
814 "does_clip_intersect", does_clip_intersect,
815 "clip_found", clip_found,
816 "closing_clips.size()", closing_clips.size(),
817 "open_clips.size()", open_clips.size());
821void Timeline::calculate_max_duration() {
822 double last_clip = 0.0;
823 double last_effect = 0.0;
824 double first_clip = std::numeric_limits<double>::max();
825 double first_effect = std::numeric_limits<double>::max();
828 if (!clips.empty()) {
830 const auto max_clip = std::max_element(
832 last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
835 const auto min_clip = std::min_element(
837 return lhs->Position() < rhs->Position();
839 first_clip = (*min_clip)->Position();
843 if (!effects.empty()) {
845 const auto max_effect = std::max_element(
847 last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
850 const auto min_effect = std::min_element(
852 return lhs->Position() < rhs->Position();
854 first_effect = (*min_effect)->Position();
858 max_time = std::max(last_clip, last_effect);
859 min_time = std::min(first_clip, first_effect);
862 if (clips.empty() && effects.empty()) {
869void Timeline::sort_clips()
872 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
876 "Timeline::SortClips",
877 "clips.size()", clips.size());
883 calculate_max_duration();
887void Timeline::sort_effects()
890 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
896 calculate_max_duration();
905 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
908 for (
auto clip : clips)
910 update_open_clips(
clip,
false);
913 bool allocated = allocated_clips.count(
clip);
920 allocated_clips.clear();
923 for (
auto effect : effects)
926 bool allocated = allocated_effects.count(effect);
933 allocated_effects.clear();
936 for (
auto mapper : allocated_frame_mappers)
942 allocated_frame_mappers.clear();
951 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
954 for (
auto clip : clips)
957 update_open_clips(
clip,
false);
974bool Timeline::isEqual(
double a,
double b)
976 return fabs(a - b) < 0.000001;
983 if (requested_frame < 1)
986 const bool past_timeline_end = (max_frame > 0 && requested_frame > max_frame);
990 std::shared_ptr<Frame> frame;
992 frame = final_cache->
GetFrame(requested_frame);
996 "Timeline::GetFrame (Cached frame found)",
997 "requested_frame", requested_frame);
1005 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1011 std::shared_ptr<Frame> frame;
1012 if (!past_timeline_end)
1013 frame = final_cache->
GetFrame(requested_frame);
1017 "Timeline::GetFrame (Cached frame found on 2nd check)",
1018 "requested_frame", requested_frame);
1025 std::vector<Clip *> nearby_clips;
1026 nearby_clips = find_intersecting_clips(requested_frame, 1,
true);
1030 "Timeline::GetFrame (processing frame)",
1031 "requested_frame", requested_frame,
1032 "omp_get_thread_num()", omp_get_thread_num());
1039 new_frame->AddAudioSilence(samples_in_frame);
1045 "Timeline::GetFrame (Adding solid color)",
1046 "requested_frame", requested_frame,
1058 "Timeline::GetFrame (Loop through clips)",
1059 "requested_frame", requested_frame,
1060 "clips.size()", clips.size(),
1061 "nearby_clips.size()", nearby_clips.size());
1068 int64_t start_frame;
1069 int64_t frame_number;
1072 std::vector<ClipInfo> clip_infos;
1073 clip_infos.reserve(nearby_clips.size());
1076 for (
auto clip : nearby_clips) {
1077 int64_t start_pos =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1079 bool intersects = (start_pos <= requested_frame && end_pos >= requested_frame);
1080 int64_t start_frame =
static_cast<int64_t
>(std::llround(
clip->
Start() * fpsD)) + 1;
1081 int64_t frame_number = requested_frame - start_pos + start_frame;
1082 clip_infos.push_back({
clip, start_pos, end_pos, start_frame, frame_number, intersects});
1086 std::unordered_map<int, int64_t> top_start_for_layer;
1087 std::unordered_map<int, Clip*> top_clip_for_layer;
1088 for (
const auto& ci : clip_infos) {
1089 if (!ci.intersects)
continue;
1090 const int layer = ci.clip->Layer();
1091 auto it = top_start_for_layer.find(layer);
1093 if (it == top_start_for_layer.end() || ci.start_pos >= it->second) {
1094 top_start_for_layer[layer] = ci.start_pos;
1095 top_clip_for_layer[layer] = ci.clip;
1100 float max_volume_sum = 0.0f;
1101 for (
const auto& ci : clip_infos) {
1102 if (!ci.intersects)
continue;
1103 if (ci.clip->Reader() && ci.clip->Reader()->info.has_audio &&
1104 ci.clip->has_audio.GetInt(ci.frame_number) != 0) {
1105 max_volume_sum +=
static_cast<float>(ci.clip->volume.GetValue(ci.frame_number));
1110 for (
const auto& ci : clip_infos) {
1113 "Timeline::GetFrame (Does clip intersect)",
1114 "requested_frame", requested_frame,
1115 "clip->Position()", ci.clip->Position(),
1116 "clip->Duration()", ci.clip->Duration(),
1117 "does_clip_intersect", ci.intersects);
1120 if (ci.intersects) {
1122 bool is_top_clip =
false;
1123 const int layer = ci.clip->Layer();
1124 auto top_it = top_clip_for_layer.find(layer);
1125 if (top_it != top_clip_for_layer.end())
1126 is_top_clip = (top_it->second == ci.clip);
1129 int64_t clip_frame_number = ci.frame_number;
1133 "Timeline::GetFrame (Calculate clip's frame #)",
1134 "clip->Position()", ci.clip->Position(),
1135 "clip->Start()", ci.clip->Start(),
1137 "clip_frame_number", clip_frame_number);
1140 add_layer(new_frame, ci.clip, clip_frame_number, is_top_clip, max_volume_sum);
1145 "Timeline::GetFrame (clip does not intersect)",
1146 "requested_frame", requested_frame,
1147 "does_clip_intersect", ci.intersects);
1154 "Timeline::GetFrame (Add frame to cache)",
1155 "requested_frame", requested_frame,
1160 new_frame->SetFrameNumber(requested_frame);
1163 if (!past_timeline_end &&
CacheEpoch() == decode_epoch
1165 final_cache->
Add(new_frame);
1174std::vector<Clip*> Timeline::find_intersecting_clips(int64_t requested_frame,
int number_of_frames,
bool include)
1177 std::vector<Clip*> matching_clips;
1180 const int64_t min_requested_frame = requested_frame;
1181 const int64_t max_requested_frame = requested_frame + (number_of_frames - 1);
1184 matching_clips.reserve(clips.size());
1186 for (
auto clip : clips)
1189 int64_t clip_start_position =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1192 bool does_clip_intersect =
1193 (clip_start_position <= min_requested_frame || clip_start_position <= max_requested_frame) &&
1194 (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame);
1198 "Timeline::find_intersecting_clips (Is clip near or intersecting)",
1199 "requested_frame", requested_frame,
1200 "min_requested_frame", min_requested_frame,
1201 "max_requested_frame", max_requested_frame,
1203 "does_clip_intersect", does_clip_intersect);
1206 update_open_clips(
clip, does_clip_intersect);
1209 if (does_clip_intersect && include)
1211 matching_clips.push_back(
clip);
1213 else if (!does_clip_intersect && !include)
1215 matching_clips.push_back(
clip);
1220 return matching_clips;
1226 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1229 if (managed_cache && final_cache) {
1232 managed_cache =
false;
1236 final_cache = new_cache;
1251 root[
"type"] =
"Timeline";
1256 root[
"path"] = path;
1259 root[
"clips"] = Json::Value(Json::arrayValue);
1262 for (
const auto existing_clip : clips)
1264 root[
"clips"].append(existing_clip->JsonValue());
1268 root[
"effects"] = Json::Value(Json::arrayValue);
1271 for (
const auto existing_effect: effects)
1273 root[
"effects"].append(existing_effect->JsonValue());
1284 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1293 catch (
const std::exception& e)
1296 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
1304 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1307 bool was_open = is_open;
1314 if (!root[
"path"].isNull())
1315 path = root[
"path"].asString();
1317 if (!root[
"clips"].isNull()) {
1322 for (
const Json::Value existing_clip : root[
"clips"]) {
1324 if (existing_clip.isNull()) {
1332 allocated_clips.insert(c);
1349 if (!root[
"effects"].isNull()) {
1354 for (
const Json::Value existing_effect :root[
"effects"]) {
1356 if (existing_effect.isNull()) {
1363 if (!existing_effect[
"type"].isNull()) {
1365 if ( (e =
EffectInfo().CreateEffect(existing_effect[
"type"].asString())) ) {
1368 allocated_effects.insert(e);
1380 if (!root[
"duration"].isNull()) {
1406 const std::lock_guard<std::recursive_mutex> lock(
getFrameMutex);
1412 const uint64_t initial_cache_epoch =
CacheEpoch();
1415 for (
auto& change : root) {
1416 std::string change_key = change[
"key"][(uint)0].asString();
1419 if (change_key ==
"clips")
1421 apply_json_to_clips(std::move(change));
1423 else if (change_key ==
"effects")
1425 apply_json_to_effects(std::move(change));
1429 apply_json_to_timeline(std::move(change));
1434 if (!root.empty() &&
CacheEpoch() == initial_cache_epoch) {
1438 catch (
const std::exception& e)
1441 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
1445void Timeline::BumpCacheEpoch() {
1446 cache_epoch.fetch_add(1, std::memory_order_relaxed);
1449void Timeline::InvalidateCacheForClip(
const Clip* clip) {
1450 if (!
clip || !final_cache) {
1455 const int64_t starting_frame =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1457 final_cache->
Remove(starting_frame - 8, ending_frame + 8);
1462void Timeline::apply_json_to_clips(Json::Value change) {
1465 std::string change_type = change[
"type"].asString();
1466 std::string clip_id =
"";
1467 Clip *existing_clip = NULL;
1470 for (
auto key_part : change[
"key"]) {
1472 if (key_part.isObject()) {
1474 if (!key_part[
"id"].isNull()) {
1476 clip_id = key_part[
"id"].asString();
1479 for (
auto c : clips)
1481 if (c->Id() == clip_id) {
1493 if (existing_clip && change[
"key"].size() == 4 && change[
"key"][2] ==
"effects")
1496 Json::Value key_part = change[
"key"][3];
1498 if (key_part.isObject()) {
1500 if (!key_part[
"id"].isNull())
1503 std::string effect_id = key_part[
"id"].asString();
1506 std::list<EffectBase*> effect_list = existing_clip->
Effects();
1507 for (
auto e : effect_list)
1509 if (e->Id() == effect_id) {
1511 apply_json_to_effects(std::move(change), e);
1521 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1531 if (change_type ==
"insert") {
1537 allocated_clips.insert(
clip);
1549 }
else if (change_type ==
"update") {
1552 if (existing_clip) {
1558 existing_clip->
SetJsonValue(std::move(change[
"value"]));
1565 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1566 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1569 if (auto_map_clips) {
1570 apply_mapper_to_clip(existing_clip);
1574 }
else if (change_type ==
"delete") {
1577 if (existing_clip) {
1584 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1594void Timeline::apply_json_to_effects(Json::Value change) {
1597 std::string change_type = change[
"type"].asString();
1601 for (
auto key_part : change[
"key"]) {
1603 if (key_part.isObject()) {
1605 if (!key_part[
"id"].isNull())
1608 std::string effect_id = key_part[
"id"].asString();
1611 for (
auto e : effects)
1613 if (e->Id() == effect_id) {
1614 existing_effect = e;
1624 if (existing_effect || change_type ==
"insert") {
1626 apply_json_to_effects(std::move(change), existing_effect);
1631void Timeline::apply_json_to_effects(Json::Value change,
EffectBase* existing_effect) {
1634 std::string change_type = change[
"type"].asString();
1637 if (!change[
"value"].isArray() && !change[
"value"][
"position"].isNull()) {
1638 int64_t new_starting_frame = (change[
"value"][
"position"].asDouble() *
info.
fps.
ToDouble()) + 1;
1639 int64_t new_ending_frame = ((change[
"value"][
"position"].asDouble() + change[
"value"][
"end"].asDouble() - change[
"value"][
"start"].asDouble()) *
info.
fps.
ToDouble()) + 1;
1640 final_cache->
Remove(new_starting_frame - 8, new_ending_frame + 8);
1644 if (change_type ==
"insert") {
1647 std::string effect_type = change[
"value"][
"type"].asString();
1656 allocated_effects.insert(e);
1665 }
else if (change_type ==
"update") {
1668 if (existing_effect) {
1673 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1676 existing_effect->
SetJsonValue(std::move(change[
"value"]));
1679 }
else if (change_type ==
"delete") {
1682 if (existing_effect) {
1687 final_cache->
Remove(old_starting_frame - 8, old_ending_frame + 8);
1700void Timeline::apply_json_to_timeline(Json::Value change) {
1701 bool cache_dirty =
true;
1704 std::string change_type = change[
"type"].asString();
1705 std::string root_key = change[
"key"][(uint)0].asString();
1706 std::string sub_key =
"";
1707 if (change[
"key"].size() >= 2)
1708 sub_key = change[
"key"][(uint)1].asString();
1711 if (change_type ==
"insert" || change_type ==
"update") {
1715 if (root_key ==
"color")
1718 else if (root_key ==
"viewport_scale")
1721 else if (root_key ==
"viewport_x")
1724 else if (root_key ==
"viewport_y")
1727 else if (root_key ==
"duration") {
1733 cache_dirty =
false;
1735 else if (root_key ==
"width") {
1740 else if (root_key ==
"height") {
1745 else if (root_key ==
"fps" && sub_key ==
"" && change[
"value"].isObject()) {
1747 if (!change[
"value"][
"num"].isNull())
1748 info.
fps.
num = change[
"value"][
"num"].asInt();
1749 if (!change[
"value"][
"den"].isNull())
1750 info.
fps.
den = change[
"value"][
"den"].asInt();
1752 else if (root_key ==
"fps" && sub_key ==
"num")
1755 else if (root_key ==
"fps" && sub_key ==
"den")
1758 else if (root_key ==
"display_ratio" && sub_key ==
"" && change[
"value"].isObject()) {
1760 if (!change[
"value"][
"num"].isNull())
1762 if (!change[
"value"][
"den"].isNull())
1765 else if (root_key ==
"display_ratio" && sub_key ==
"num")
1768 else if (root_key ==
"display_ratio" && sub_key ==
"den")
1771 else if (root_key ==
"pixel_ratio" && sub_key ==
"" && change[
"value"].isObject()) {
1773 if (!change[
"value"][
"num"].isNull())
1775 if (!change[
"value"][
"den"].isNull())
1778 else if (root_key ==
"pixel_ratio" && sub_key ==
"num")
1781 else if (root_key ==
"pixel_ratio" && sub_key ==
"den")
1785 else if (root_key ==
"sample_rate")
1788 else if (root_key ==
"channels")
1791 else if (root_key ==
"channel_layout")
1796 throw InvalidJSONKey(
"JSON change key is invalid", change.toStyledString());
1799 }
else if (change[
"type"].asString() ==
"delete") {
1803 if (root_key ==
"color") {
1809 else if (root_key ==
"viewport_scale")
1811 else if (root_key ==
"viewport_x")
1813 else if (root_key ==
"viewport_y")
1817 throw InvalidJSONKey(
"JSON change key is invalid", change.toStyledString());
1831 cache_clear_requested.fetch_add(1);
1838 const std::lock_guard<std::recursive_mutex> guard(
getFrameMutex);
1841 const uint64_t clear_request = cache_clear_requested.load();
1845 final_cache->
Clear();
1850 for (
const auto clip : clips) {
1852 if (
clip->Reader()) {
1853 if (
auto rc =
clip->Reader()->GetCache())
1857 if (deep &&
clip->Reader()->Name() ==
"FrameMapper") {
1859 if (nested_reader->
Reader()) {
1867 if (
auto cc =
clip->GetCache())
1876 cache_clear_completed.store(clear_request);
1889 display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio);
1902std::pair<float, float> Timeline::ResolveTransitionAudioGains(
Clip* source_clip, int64_t timeline_frame_number,
bool is_top_clip)
const
1904 constexpr double half_pi = 1.57079632679489661923;
1907 return {1.0f, 1.0f};
1910 Mask* active_mask =
nullptr;
1911 int64_t effect_start_position = 0;
1912 int64_t effect_end_position = 0;
1915 for (
auto effect : effects) {
1916 if (effect->Layer() != source_clip->
Layer())
1919 auto* mask =
dynamic_cast<Mask*
>(effect);
1920 if (!mask || !mask->fade_audio_hint)
1923 const int64_t start_pos =
static_cast<int64_t
>(std::llround(effect->Position() * fpsD)) + 1;
1924 const int64_t end_pos =
static_cast<int64_t
>(std::llround((effect->Position() + effect->Duration()) * fpsD));
1925 if (start_pos > timeline_frame_number || end_pos < timeline_frame_number)
1929 return {1.0f, 1.0f};
1932 effect_start_position = start_pos;
1933 effect_end_position = end_pos;
1937 return {1.0f, 1.0f};
1939 struct AudibleClipInfo {
1945 std::vector<AudibleClipInfo> audible_clips;
1946 audible_clips.reserve(2);
1949 for (
auto clip : clips) {
1952 if (!
clip->Reader() || !
clip->Reader()->info.has_audio)
1955 const int64_t clip_start_pos =
static_cast<int64_t
>(std::llround(
clip->
Position() * fpsD)) + 1;
1957 if (clip_start_pos > timeline_frame_number || clip_end_pos < timeline_frame_number)
1960 const int64_t clip_start_frame =
static_cast<int64_t
>(std::llround(
clip->
Start() * fpsD)) + 1;
1961 const int64_t clip_frame_number = timeline_frame_number - clip_start_pos + clip_start_frame;
1962 if (
clip->has_audio.GetInt(clip_frame_number) == 0)
1965 audible_clips.push_back({
clip, clip_start_pos, clip_end_pos});
1966 if (audible_clips.size() > 2)
1967 return {1.0f, 1.0f};
1970 if (audible_clips.empty())
1971 return {1.0f, 1.0f};
1974 const auto source_it = std::find_if(
1975 audible_clips.begin(),
1976 audible_clips.end(),
1977 [source_clip](
const AudibleClipInfo&
info) {
1978 return info.clip == source_clip;
1980 if (source_it == audible_clips.end())
1981 return {1.0f, 1.0f};
1984 if (audible_clips.size() == 2) {
1986 Clip* top_clip = audible_clips.back().
clip;
1987 if ((is_top_clip && source_clip != top_clip) || (!is_top_clip && source_clip == top_clip))
1988 return {1.0f, 1.0f};
1992 const int64_t left_distance = std::llabs(effect_start_position - source_it->start_pos);
1993 const int64_t right_distance = std::llabs(effect_end_position - source_it->end_pos);
1994 const bool clip_fades_in = left_distance <= right_distance;
1997 const auto compute_gain = [&](int64_t frame_number) ->
float {
1998 if (effect_end_position <= effect_start_position)
2001 const double span =
static_cast<double>(effect_end_position - effect_start_position);
2002 double t =
static_cast<double>(frame_number - effect_start_position) / span;
2008 return static_cast<float>(clip_fades_in ? std::sin(t * half_pi) : std::cos(t * half_pi));
2011 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 std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
Get a frame from the cache.
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.
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)
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...
std::string Id() const
Get the Id of this clip 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)
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)
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
openshot::CacheMemory * GetCache() override
Get the cache object (always return NULL for this reader)
openshot::Keyframe has_audio
An optional override to determine if this clip has audio (-1=undefined, 0=no, 1=yes)
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
std::list< openshot::EffectBase * > Effects()
Return the list of effects on the timeline.
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.
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)
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)
static Logger * Instance()
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 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.
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
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.
void RequestClearAllCache()
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.
bool CacheRefreshPending() const
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)
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%)
QSize AlignPreviewSize(const QSize &size)
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)