Files
test2/source/blender/sequencer/intern/strip_add.cc
Sergey Sharybin 93be12fde0 ImBuf: Linearize float buffers from FFmpeg
Blender expects float buffers to be in scene linear space, which was
violated bu the 1012bit movie reading code. While such image buffers
can be displayed correctly, performing operations in various areas
of Blender might lead to unexpected results.

The non-linear colorspace for ImBuf is expected to be "internal-only"
to a specific area, like VSE.

This is only done for Image and MovieClip data-blocks, sequencer still
reads movie files in their original colorspace as it helps performance and
sequencer can not be referenced from places where linear colorspace
for float buffer is really important.

Pull Request: https://projects.blender.org/blender/blender/pulls/141603
2025-07-10 17:39:00 +02:00

756 lines
24 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <algorithm>
#include <cmath>
#include <cstring>
#include "BLI_math_base.hh"
#include "MEM_guardedalloc.h"
#include "DNA_mask_types.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "DNA_sound_types.h"
#include "BLI_listbase.h"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BKE_image.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_mask.h"
#include "BKE_movieclip.h"
#include "BKE_scene.hh"
#include "BKE_sound.h"
#include "DEG_depsgraph_query.hh"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_metadata.hh"
#include "MOV_read.hh"
#include "SEQ_add.hh"
#include "SEQ_edit.hh"
#include "SEQ_effects.hh"
#include "SEQ_relations.hh"
#include "SEQ_render.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_time.hh"
#include "SEQ_transform.hh"
#include "SEQ_utils.hh"
#include "multiview.hh"
#include "proxy.hh"
#include "sequencer.hh"
#include "strip_time.hh"
namespace blender::seq {
void add_load_data_init(LoadData *load_data,
const char *name,
const char *path,
const int start_frame,
const int channel)
{
memset(load_data, 0, sizeof(LoadData));
if (name != nullptr) {
STRNCPY(load_data->name, name);
}
if (path != nullptr) {
STRNCPY(load_data->path, path);
}
load_data->start_frame = start_frame;
load_data->channel = channel;
}
static void strip_add_generic_update(Scene *scene, Strip *strip)
{
strip_unique_name_set(scene, &scene->ed->seqbase, strip);
relations_invalidate_cache(scene, strip);
strip_lookup_invalidate(scene->ed);
strip_time_effect_range_set(scene, strip);
time_update_meta_strip_range(scene, lookup_meta_by_strip(scene->ed, strip));
}
static void strip_add_set_name(Scene *scene, Strip *strip, LoadData *load_data)
{
if (load_data->name[0] != '\0') {
edit_strip_name_set(scene, strip, load_data->name);
}
else {
if (strip->type == STRIP_TYPE_SCENE) {
edit_strip_name_set(scene, strip, load_data->scene->id.name + 2);
}
else if (strip->type == STRIP_TYPE_MOVIECLIP) {
edit_strip_name_set(scene, strip, load_data->clip->id.name + 2);
}
else if (strip->type == STRIP_TYPE_MASK) {
edit_strip_name_set(scene, strip, load_data->mask->id.name + 2);
}
else if ((strip->type & STRIP_TYPE_EFFECT) != 0) {
edit_strip_name_set(scene, strip, strip_give_name(strip));
}
else { /* Image, sound and movie. */
edit_strip_name_set(scene, strip, load_data->name);
}
}
}
static void strip_add_set_view_transform(Scene *scene, Strip *strip, LoadData *load_data)
{
const char *strip_colorspace = strip->data->colorspace_settings.name;
if (load_data->flags & SEQ_LOAD_SET_VIEW_TRANSFORM) {
const char *role_colorspace_byte;
role_colorspace_byte = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE);
if (STREQ(strip_colorspace, role_colorspace_byte)) {
const ColorManagedDisplay *display = IMB_colormanagement_display_get_named(
scene->display_settings.display_device);
const char *default_view_transform =
IMB_colormanagement_display_get_default_view_transform_name(display);
STRNCPY(scene->view_settings.view_transform, default_view_transform);
}
}
}
Strip *add_scene_strip(Scene *scene, ListBase *seqbase, LoadData *load_data)
{
Strip *strip = strip_alloc(
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_SCENE);
strip->scene = load_data->scene;
strip->len = load_data->scene->r.efra - load_data->scene->r.sfra + 1;
id_us_ensure_real((ID *)load_data->scene);
strip_add_set_name(scene, strip, load_data);
strip_add_generic_update(scene, strip);
return strip;
}
Strip *add_movieclip_strip(Scene *scene, ListBase *seqbase, LoadData *load_data)
{
Strip *strip = strip_alloc(
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_MOVIECLIP);
strip->clip = load_data->clip;
strip->len = BKE_movieclip_get_duration(load_data->clip);
id_us_ensure_real((ID *)load_data->clip);
strip_add_set_name(scene, strip, load_data);
strip_add_generic_update(scene, strip);
return strip;
}
Strip *add_mask_strip(Scene *scene, ListBase *seqbase, LoadData *load_data)
{
Strip *strip = strip_alloc(seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_MASK);
strip->mask = load_data->mask;
strip->len = BKE_mask_get_duration(load_data->mask);
id_us_ensure_real((ID *)load_data->mask);
strip_add_set_name(scene, strip, load_data);
strip_add_generic_update(scene, strip);
return strip;
}
Strip *add_effect_strip(Scene *scene, ListBase *seqbase, LoadData *load_data)
{
Strip *strip = strip_alloc(
seqbase, load_data->start_frame, load_data->channel, load_data->effect.type);
strip->flag |= SEQ_USE_EFFECT_DEFAULT_FADE;
EffectHandle sh = strip_effect_handle_get(strip);
sh.init(strip);
if (seq::effect_get_num_inputs(strip->type) != 0) {
strip->input1 = load_data->effect.input1;
strip->input2 = load_data->effect.input2;
}
if (effect_get_num_inputs(strip->type) == 1) {
strip->blend_mode = strip->input1->blend_mode;
strip->blend_opacity = strip->input1->blend_opacity;
}
if (strip->input1 == nullptr) {
strip->len = 1; /* Effect is generator, set non zero length. */
strip->flag |= SEQ_SINGLE_FRAME_CONTENT;
time_right_handle_frame_set(scene, strip, load_data->effect.end_frame);
}
strip_add_set_name(scene, strip, load_data);
strip_add_generic_update(scene, strip);
return strip;
}
void add_image_set_directory(Strip *strip, const char *dirpath)
{
STRNCPY(strip->data->dirpath, dirpath);
}
void add_image_load_file(Scene *scene, Strip *strip, size_t strip_frame, const char *filename)
{
StripElem *se = render_give_stripelem(scene, strip, time_start_frame_get(strip) + strip_frame);
STRNCPY(se->filename, filename);
}
void add_image_init_alpha_mode(Strip *strip)
{
if (strip->data && strip->data->stripdata) {
char filepath[FILE_MAX];
ImBuf *ibuf;
BLI_path_join(
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
BLI_path_abs(filepath, BKE_main_blendfile_path_from_global());
/* Initialize input color space. */
if (strip->type == STRIP_TYPE_IMAGE) {
ibuf = IMB_load_image_from_filepath(filepath,
IB_test | IB_multilayer | IB_alphamode_detect,
strip->data->colorspace_settings.name);
/* Byte images are default to straight alpha, however sequencer
* works in premul space, so mark strip to be premultiplied first.
*/
strip->alpha_mode = SEQ_ALPHA_STRAIGHT;
if (ibuf) {
if (ibuf->flags & IB_alphamode_premul) {
strip->alpha_mode = IMA_ALPHA_PREMUL;
}
IMB_freeImBuf(ibuf);
}
}
}
}
Strip *add_image_strip(Main *bmain, Scene *scene, ListBase *seqbase, LoadData *load_data)
{
Strip *strip = strip_alloc(
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_IMAGE);
strip->len = load_data->image.len;
StripData *data = strip->data;
data->stripdata = MEM_calloc_arrayN<StripElem>(load_data->image.len, "stripelem");
if (strip->len == 1) {
strip->flag |= SEQ_SINGLE_FRAME_CONTENT;
}
/* Multiview settings. */
if (load_data->use_multiview) {
strip->flag |= SEQ_USE_VIEWS;
strip->views_format = load_data->views_format;
}
if (load_data->stereo3d_format) {
strip->stereo3d_format = load_data->stereo3d_format;
}
/* Set initial scale based on load_data->fit_method. */
char file_path[FILE_MAX];
STRNCPY(file_path, load_data->path);
BLI_path_abs(file_path, BKE_main_blendfile_path(bmain));
ImBuf *ibuf = IMB_load_image_from_filepath(
file_path, IB_byte_data | IB_multilayer, strip->data->colorspace_settings.name);
if (ibuf != nullptr) {
/* Set image resolution. Assume that all images in sequence are same size. This fields are only
* informative. */
StripElem *strip_elem = data->stripdata;
for (int i = 0; i < load_data->image.len; i++) {
strip_elem->orig_width = ibuf->x;
strip_elem->orig_height = ibuf->y;
strip_elem++;
}
set_scale_to_fit(strip, ibuf->x, ibuf->y, scene->r.xsch, scene->r.ysch, load_data->fit_method);
IMB_freeImBuf(ibuf);
}
/* Set Last active directory. */
STRNCPY(scene->ed->act_imagedir, strip->data->dirpath);
strip_add_set_view_transform(scene, strip, load_data);
strip_add_set_name(scene, strip, load_data);
strip_add_generic_update(scene, strip);
return strip;
}
#ifdef WITH_AUDASPACE
void add_sound_av_sync(Main *bmain, Scene *scene, Strip *strip, LoadData *load_data)
{
SoundStreamInfo sound_stream;
if (!BKE_sound_stream_info_get(bmain, load_data->path, 0, &sound_stream)) {
return;
}
const double av_stream_offset = sound_stream.start - load_data->r_video_stream_start;
const int frame_offset = av_stream_offset * FPS;
/* Set sub-frame offset. */
strip->sound->offset_time = (double(frame_offset) / FPS) - av_stream_offset;
transform_translate_strip(scene, strip, frame_offset);
}
Strip *add_sound_strip(Main *bmain, Scene *scene, ListBase *seqbase, LoadData *load_data)
{
bSound *sound = BKE_sound_new_file(bmain, load_data->path); /* Handles relative paths. */
SoundInfo info;
bool sound_loaded = BKE_sound_info_get(bmain, sound, &info);
if (!sound_loaded && !load_data->allow_invalid_file) {
BKE_id_free(bmain, sound);
return nullptr;
}
if (info.specs.channels == SOUND_CHANNELS_INVALID && !load_data->allow_invalid_file) {
BKE_id_free(bmain, sound);
return nullptr;
}
Strip *strip = strip_alloc(
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_SOUND_RAM);
strip->sound = sound;
strip->scene_sound = nullptr;
/* We round the frame duration as the audio sample lengths usually does not
* line up with the video frames. Therefore we round this number to the
* nearest frame as the audio track usually overshoots or undershoots the
* end frame of the video by a little bit.
* See #47135 for under shoot example. */
strip->len = std::max(1, int(round((info.length - sound->offset_time) * FPS)));
StripData *data = strip->data;
/* We only need 1 element to store the filename. */
StripElem *se = data->stripdata = MEM_callocN<StripElem>("stripelem");
BLI_path_split_dir_file(
load_data->path, data->dirpath, sizeof(data->dirpath), se->filename, sizeof(se->filename));
if (strip->sound != nullptr) {
if (load_data->flags & SEQ_LOAD_SOUND_MONO) {
strip->sound->flags |= SOUND_FLAGS_MONO;
}
if (load_data->flags & SEQ_LOAD_SOUND_CACHE) {
if (strip->sound) {
strip->sound->flags |= SOUND_FLAGS_CACHING;
}
}
/* Turn on Display Waveform by default. */
strip->flag |= SEQ_AUDIO_DRAW_WAVEFORM;
}
/* Set Last active directory. */
BLI_strncpy(scene->ed->act_sounddir, data->dirpath, FILE_MAXDIR);
strip_add_set_name(scene, strip, load_data);
strip_add_generic_update(scene, strip);
return strip;
}
#else // WITH_AUDASPACE
void add_sound_av_sync(Main * /*bmain*/,
Scene * /*scene*/,
Strip * /*strip*/,
LoadData * /*load_data*/)
{
}
Strip *add_sound_strip(Main * /*bmain*/,
Scene * /*scene*/,
ListBase * /*seqbase*/,
LoadData * /*load_data*/)
{
return nullptr;
}
#endif // WITH_AUDASPACE
Strip *add_meta_strip(Scene *scene, ListBase *seqbase, LoadData *load_data)
{
/* Allocate strip. */
Strip *strip_meta = strip_alloc(
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_META);
/* Set name. */
strip_add_set_name(scene, strip_meta, load_data);
/* Set frames start and length. */
strip_meta->start = load_data->start_frame;
strip_meta->len = 1;
strip_add_generic_update(scene, strip_meta);
return strip_meta;
}
Strip *add_movie_strip(Main *bmain, Scene *scene, ListBase *seqbase, LoadData *load_data)
{
char filepath[sizeof(load_data->path)];
STRNCPY(filepath, load_data->path);
BLI_path_abs(filepath, BKE_main_blendfile_path(bmain));
char colorspace[64] = "\0"; /* MAX_COLORSPACE_NAME */
bool is_multiview_loaded = false;
const int totfiles = seq_num_files(scene, load_data->views_format, load_data->use_multiview);
MovieReader **anim_arr = MEM_calloc_arrayN<MovieReader *>(totfiles, "Video files");
int i;
int orig_width = 0;
int orig_height = 0;
if (load_data->use_multiview && (load_data->views_format == R_IMF_VIEWS_INDIVIDUAL)) {
char prefix[FILE_MAX];
const char *ext = nullptr;
size_t j = 0;
BKE_scene_multiview_view_prefix_get(scene, filepath, prefix, &ext);
if (prefix[0] != '\0') {
for (i = 0; i < totfiles; i++) {
char filepath_view[FILE_MAX];
seq_multiview_name(scene, i, prefix, ext, filepath_view, sizeof(filepath_view));
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
* kept unchanged for the performance reasons. */
anim_arr[j] = openanim(filepath_view, IB_byte_data, 0, true, colorspace);
if (anim_arr[j]) {
seq_anim_add_suffix(scene, anim_arr[j], i);
j++;
}
}
is_multiview_loaded = true;
}
}
if (is_multiview_loaded == false) {
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
* kept unchanged for the performance reasons. */
anim_arr[0] = openanim(filepath, IB_byte_data, 0, true, colorspace);
}
if (anim_arr[0] == nullptr && !load_data->allow_invalid_file) {
MEM_freeN(anim_arr);
return nullptr;
}
float video_fps = 0.0f;
load_data->r_video_stream_start = 0.0;
if (anim_arr[0] != nullptr) {
short fps_num;
float fps_denom;
bool have_fps = MOV_get_fps_num_denom(anim_arr[0], fps_num, fps_denom);
if (have_fps) {
video_fps = fps_num / fps_denom;
}
/* Adjust scene's frame rate settings to match. */
if (have_fps && (load_data->flags & SEQ_LOAD_MOVIE_SYNC_FPS)) {
scene->r.frs_sec = fps_num;
scene->r.frs_sec_base = fps_denom;
DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_FPS | ID_RECALC_SEQUENCER_STRIPS);
}
load_data->r_video_stream_start = MOV_get_start_offset_seconds(anim_arr[0]);
}
Strip *strip = strip_alloc(
seqbase, load_data->start_frame, load_data->channel, STRIP_TYPE_MOVIE);
/* Multiview settings. */
if (load_data->use_multiview) {
strip->flag |= SEQ_USE_VIEWS;
strip->views_format = load_data->views_format;
}
if (load_data->stereo3d_format) {
strip->stereo3d_format = load_data->stereo3d_format;
}
for (i = 0; i < totfiles; i++) {
if (anim_arr[i]) {
StripAnim *sanim = MEM_mallocN<StripAnim>("Strip Anim");
BLI_addtail(&strip->anims, sanim);
sanim->anim = anim_arr[i];
}
else {
break;
}
}
if (anim_arr[0] != nullptr) {
strip->len = MOV_get_duration_frames(anim_arr[0], IMB_TC_RECORD_RUN);
MOV_load_metadata(anim_arr[0]);
/* Set initial scale based on load_data->fit_method. */
orig_width = MOV_get_image_width(anim_arr[0]);
orig_height = MOV_get_image_height(anim_arr[0]);
set_scale_to_fit(
strip, orig_width, orig_height, scene->r.xsch, scene->r.ysch, load_data->fit_method);
float fps = MOV_get_fps(anim_arr[0]);
if (fps > 0.0f) {
strip->media_playback_rate = fps;
}
}
strip->len = std::max(1, strip->len);
if (load_data->adjust_playback_rate) {
strip->flag |= SEQ_AUTO_PLAYBACK_RATE;
}
STRNCPY(strip->data->colorspace_settings.name, colorspace);
StripData *data = strip->data;
/* We only need 1 element for MOVIE strips. */
StripElem *se;
data->stripdata = se = MEM_callocN<StripElem>("stripelem");
data->stripdata->orig_width = orig_width;
data->stripdata->orig_height = orig_height;
data->stripdata->orig_fps = video_fps;
BLI_path_split_dir_file(
load_data->path, data->dirpath, sizeof(data->dirpath), se->filename, sizeof(se->filename));
strip_add_set_view_transform(scene, strip, load_data);
strip_add_set_name(scene, strip, load_data);
strip_add_generic_update(scene, strip);
MEM_freeN(anim_arr);
return strip;
}
void add_reload_new_file(Main *bmain, Scene *scene, Strip *strip, const bool lock_range)
{
int prev_startdisp = 0, prev_enddisp = 0;
/* NOTE: don't rename the strip, will break animation curves. */
if (ELEM(strip->type,
STRIP_TYPE_MOVIE,
STRIP_TYPE_IMAGE,
STRIP_TYPE_SOUND_RAM,
STRIP_TYPE_SCENE,
STRIP_TYPE_META,
STRIP_TYPE_MOVIECLIP,
STRIP_TYPE_MASK) == 0)
{
return;
}
if (lock_range) {
/* keep so we don't have to move the actual start and end points (only the data) */
prev_startdisp = time_left_handle_frame_get(scene, strip);
prev_enddisp = time_right_handle_frame_get(scene, strip);
}
switch (strip->type) {
case STRIP_TYPE_IMAGE: {
/* Hack? */
size_t olen = MEM_allocN_len(strip->data->stripdata) / sizeof(StripElem);
strip->len = olen;
strip->len -= strip->anim_startofs;
strip->len -= strip->anim_endofs;
strip->len = std::max(strip->len, 0);
break;
}
case STRIP_TYPE_MOVIE: {
char filepath[FILE_MAX];
StripAnim *sanim;
bool is_multiview_loaded = false;
const bool is_multiview = (strip->flag & SEQ_USE_VIEWS) != 0 &&
(scene->r.scemode & R_MULTIVIEW) != 0;
BLI_path_join(
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
BLI_path_abs(filepath, BKE_main_blendfile_path_from_global());
relations_strip_free_anim(strip);
if (is_multiview && (strip->views_format == R_IMF_VIEWS_INDIVIDUAL)) {
char prefix[FILE_MAX];
const char *ext = nullptr;
const int totfiles = seq_num_files(scene, strip->views_format, true);
int i = 0;
BKE_scene_multiview_view_prefix_get(scene, filepath, prefix, &ext);
if (prefix[0] != '\0') {
for (i = 0; i < totfiles; i++) {
MovieReader *anim;
char filepath_view[FILE_MAX];
seq_multiview_name(scene, i, prefix, ext, filepath_view, sizeof(filepath_view));
/* Sequencer takes care of colorspace conversion of the result. The input is the best
* to be kept unchanged for the performance reasons. */
anim = openanim(filepath_view,
IB_byte_data | ((strip->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
strip->streamindex,
true,
strip->data->colorspace_settings.name);
if (anim) {
seq_anim_add_suffix(scene, anim, i);
sanim = MEM_mallocN<StripAnim>("Strip Anim");
BLI_addtail(&strip->anims, sanim);
sanim->anim = anim;
}
}
is_multiview_loaded = true;
}
}
if (is_multiview_loaded == false) {
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
* kept unchanged for the performance reasons. */
MovieReader *anim = openanim(filepath,
IB_byte_data |
((strip->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
strip->streamindex,
true,
strip->data->colorspace_settings.name);
if (anim) {
sanim = MEM_mallocN<StripAnim>("Strip Anim");
BLI_addtail(&strip->anims, sanim);
sanim->anim = anim;
}
}
/* use the first video as reference for everything */
sanim = static_cast<StripAnim *>(strip->anims.first);
if ((!sanim) || (!sanim->anim)) {
return;
}
MOV_load_metadata(sanim->anim);
strip->len = MOV_get_duration_frames(
sanim->anim,
IMB_Timecode_Type(strip->data->proxy ? IMB_Timecode_Type(strip->data->proxy->tc) :
IMB_TC_RECORD_RUN));
strip->len -= strip->anim_startofs;
strip->len -= strip->anim_endofs;
strip->len = std::max(strip->len, 0);
break;
}
case STRIP_TYPE_MOVIECLIP:
if (strip->clip == nullptr) {
return;
}
strip->len = BKE_movieclip_get_duration(strip->clip);
strip->len -= strip->anim_startofs;
strip->len -= strip->anim_endofs;
strip->len = std::max(strip->len, 0);
break;
case STRIP_TYPE_MASK:
if (strip->mask == nullptr) {
return;
}
strip->len = BKE_mask_get_duration(strip->mask);
strip->len -= strip->anim_startofs;
strip->len -= strip->anim_endofs;
strip->len = std::max(strip->len, 0);
break;
case STRIP_TYPE_SOUND_RAM:
#ifdef WITH_AUDASPACE
if (!strip->sound) {
return;
}
strip->len = ceil(double(BKE_sound_get_length(bmain, strip->sound)) * FPS);
strip->len -= strip->anim_startofs;
strip->len -= strip->anim_endofs;
strip->len = std::max(strip->len, 0);
#else
UNUSED_VARS(bmain);
return;
#endif
break;
case STRIP_TYPE_SCENE: {
strip->len = (strip->scene) ? strip->scene->r.efra - strip->scene->r.sfra + 1 : 0;
strip->len -= strip->anim_startofs;
strip->len -= strip->anim_endofs;
strip->len = std::max(strip->len, 0);
break;
}
}
free_strip_proxy(strip);
if (lock_range) {
time_left_handle_frame_set(scene, strip, prev_startdisp);
time_right_handle_frame_set(scene, strip, prev_enddisp);
}
relations_invalidate_cache_raw(scene, strip);
}
void add_movie_reload_if_needed(
Main *bmain, Scene *scene, Strip *strip, bool *r_was_reloaded, bool *r_can_produce_frames)
{
BLI_assert_msg(strip->type == STRIP_TYPE_MOVIE,
"This function is only implemented for movie strips.");
bool must_reload = false;
/* The Sequence struct allows for multiple anim structs to be associated with one strip.
* This function will return true only if there is at least one 'anim' AND all anims can
* produce frames. */
if (BLI_listbase_is_empty(&strip->anims)) {
/* No anim present, so reloading is always necessary. */
must_reload = true;
}
else {
LISTBASE_FOREACH (StripAnim *, sanim, &strip->anims) {
if (!MOV_is_initialized_and_valid(sanim->anim)) {
/* Anim cannot produce frames, try reloading. */
must_reload = true;
break;
}
};
}
if (!must_reload) {
/* There are one or more anims, and all can produce frames. */
*r_was_reloaded = false;
*r_can_produce_frames = true;
return;
}
add_reload_new_file(bmain, scene, strip, true);
*r_was_reloaded = true;
if (BLI_listbase_is_empty(&strip->anims)) {
/* No anims present after reloading => no frames can be produced. */
*r_can_produce_frames = false;
return;
}
/* Check if there are still anims that cannot produce frames. */
LISTBASE_FOREACH (StripAnim *, sanim, &strip->anims) {
if (!MOV_is_initialized_and_valid(sanim->anim)) {
/* There still is an anim that cannot produce frames. */
*r_can_produce_frames = false;
return;
}
};
/* There are one or more anims, and all can produce frames. */
*r_can_produce_frames = true;
}
} // namespace blender::seq