Blender V5.0
movie_proxy_indexer.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011 Peter Schlaile <peter [at] schlaile [dot] de>.
2 * SPDX-FileCopyrightText: 2024 Blender Authors
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later */
5
9
10#include <cstdlib>
11
12#include "MEM_guardedalloc.h"
13
14#include "BLI_endian_defines.h"
15#include "BLI_endian_switch.h"
16#include "BLI_fileops.h"
17#include "BLI_math_base.h"
18#include "BLI_math_base.hh"
19#include "BLI_path_utils.hh"
20#include "BLI_string.h"
21#include "BLI_string_utils.hh"
22#include "BLI_threads.h"
23#include "BLI_time.h"
24#include "BLI_utildefines.h"
25
26#include "CLG_log.h"
27
28#include "MOV_read.hh"
29
30#include "ffmpeg_swscale.hh"
32#include "movie_read.hh"
33#include "movie_util.hh"
34
35static CLG_LogRef LOG = {"video.proxy"};
36
37#ifdef WITH_FFMPEG
38extern "C" {
39# include "ffmpeg_compat.h"
40# include <libavutil/imgutils.h>
41}
42
43static const char temp_ext[] = "_part";
44#endif
45
46static const char binary_header_str[] = "BlenMIdx";
47
50static const float proxy_fac[] = {0.25, 0.50, 0.75, 1.00};
51
52#define INDEX_FILE_VERSION 2
53
54/* ----------------------------------------------------------------------
55 * - time code index functions
56 * ---------------------------------------------------------------------- */
57
58#ifdef WITH_FFMPEG
59
60struct MovieIndexBuilder {
61 FILE *fp;
62 char filepath[FILE_MAX];
63 char filepath_temp[FILE_MAX];
64};
65
66static MovieIndexBuilder *index_builder_create(const char *filepath)
67{
68 MovieIndexBuilder *rv = MEM_callocN<MovieIndexBuilder>("index builder");
69
70 STRNCPY(rv->filepath, filepath);
71
72 STRNCPY(rv->filepath_temp, filepath);
73 BLI_string_join(rv->filepath_temp, sizeof(rv->filepath_temp), filepath, temp_ext);
74
75 BLI_file_ensure_parent_dir_exists(rv->filepath_temp);
76
77 rv->fp = BLI_fopen(rv->filepath_temp, "wb");
78
79 if (!rv->fp) {
81 "Failed to build index for '%s': could not open '%s' for writing",
82 filepath,
83 rv->filepath_temp);
84 MEM_freeN(rv);
85 return nullptr;
86 }
87
88 fprintf(rv->fp,
89 "%s%c%.3d",
91 /* NOTE: this is endianness-sensitive.
92 * On Big Endian system 'V' must be used instead of 'v'. */
93 'v',
95
96 return rv;
97}
98
99static void index_builder_add_entry(
100 MovieIndexBuilder *fp, int frameno, uint64_t seek_pos_pts, uint64_t seek_pos_dts, uint64_t pts)
101{
102 uint64_t pad = 0;
103 fwrite(&frameno, sizeof(int), 1, fp->fp);
104 fwrite(&pad, sizeof(uint64_t), 1, fp->fp);
105 fwrite(&seek_pos_pts, sizeof(uint64_t), 1, fp->fp);
106 fwrite(&seek_pos_dts, sizeof(uint64_t), 1, fp->fp);
107 fwrite(&pts, sizeof(uint64_t), 1, fp->fp);
108}
109
110static void index_builder_finish(MovieIndexBuilder *fp, bool rollback)
111{
112 fclose(fp->fp);
113
114 if (rollback) {
115 BLI_delete(fp->filepath_temp, false, false);
116 }
117 else {
118 BLI_rename_overwrite(fp->filepath_temp, fp->filepath);
119 }
120
121 MEM_freeN(fp);
122}
123
124#endif
125
126static MovieIndex *movie_index_open(const char *filepath)
127{
128 FILE *fp = BLI_fopen(filepath, "rb");
129 if (!fp) {
130 return nullptr;
131 }
132
133 constexpr int64_t header_size = 12;
134 char header[header_size + 1];
135 if (fread(header, header_size, 1, fp) != 1) {
136 CLOG_ERROR(&LOG, "Couldn't read indexer file: %s", filepath);
137 fclose(fp);
138 return nullptr;
139 }
140
141 header[header_size] = 0;
142
143 if (memcmp(header, binary_header_str, 8) != 0) {
144 CLOG_ERROR(&LOG, "Error reading %s: Binary file type string mismatch", filepath);
145 fclose(fp);
146 return nullptr;
147 }
148
149 if (atoi(header + 9) != INDEX_FILE_VERSION) {
150 CLOG_ERROR(&LOG, "Error reading %s: File version mismatch", filepath);
151 fclose(fp);
152 return nullptr;
153 }
154
155 MovieIndex *idx = MEM_new<MovieIndex>("MovieIndex");
156
157 STRNCPY(idx->filepath, filepath);
158
159 fseek(fp, 0, SEEK_END);
160
161 constexpr int64_t entry_size = sizeof(int) + /* framepos */
162 sizeof(uint64_t) + /* _pad */
163 sizeof(uint64_t) + /* seek_pos_pts */
164 sizeof(uint64_t) + /* seek_pos_dts */
165 sizeof(uint64_t); /* pts */
166
167 int64_t num_entries = (ftell(fp) - header_size) / entry_size;
168 fseek(fp, header_size, SEEK_SET);
169
170 idx->entries.resize(num_entries);
171
172 int64_t items_read = 0;
174 for (int64_t i = 0; i < num_entries; i++) {
175 items_read += fread(&idx->entries[i].frameno, sizeof(int), 1, fp);
176 items_read += fread(&pad, sizeof(uint64_t), 1, fp);
177 items_read += fread(&idx->entries[i].seek_pos_pts, sizeof(uint64_t), 1, fp);
178 items_read += fread(&idx->entries[i].seek_pos_dts, sizeof(uint64_t), 1, fp);
179 items_read += fread(&idx->entries[i].pts, sizeof(uint64_t), 1, fp);
180 }
181
182 if (items_read != num_entries * 5) {
183 CLOG_ERROR(&LOG, "Error: Element data size mismatch in: %s", filepath);
184 MEM_delete(idx);
185 fclose(fp);
186 return nullptr;
187 }
188
189 /* NOTE: this is endianness-sensitive. */
190 BLI_assert(ELEM(header[8], 'v', 'V'));
191 const int16_t file_endianness = (header[8] == 'v') ? L_ENDIAN : B_ENDIAN;
192 if (file_endianness == B_ENDIAN) {
193 for (int64_t i = 0; i < num_entries; i++) {
194 BLI_endian_switch_int32(&idx->entries[i].frameno);
195 BLI_endian_switch_uint64(&idx->entries[i].seek_pos_pts);
196 BLI_endian_switch_uint64(&idx->entries[i].seek_pos_dts);
198 }
199 }
200
201 fclose(fp);
202
203 return idx;
204}
205
207{
208 frame_index = blender::math::clamp<int>(frame_index, 0, this->entries.size() - 1);
209 return this->entries[frame_index].seek_pos_pts;
210}
211
213{
214 frame_index = blender::math::clamp<int>(frame_index, 0, this->entries.size() - 1);
215 return this->entries[frame_index].seek_pos_dts;
216}
217
218int MovieIndex::get_frame_index(int frameno) const
219{
220 int len = int(this->entries.size());
221 int first = 0;
222
223 /* Binary-search (lower bound) the right index. */
224 while (len > 0) {
225 int half = len >> 1;
226 int middle = first + half;
227
228 if (this->entries[middle].frameno < frameno) {
229 first = middle;
230 first++;
231 len = len - half - 1;
232 }
233 else {
234 len = half;
235 }
236 }
237
238 if (first == this->entries.size()) {
239 return int(this->entries.size()) - 1;
240 }
241
242 return first;
243}
244
245uint64_t MovieIndex::get_pts(int frame_index) const
246{
247 frame_index = blender::math::clamp<int>(frame_index, 0, this->entries.size() - 1);
248 return this->entries[frame_index].pts;
249}
250
252{
253 if (this->entries.is_empty()) {
254 return 0;
255 }
256 return this->entries.last().frameno + 1;
257}
258
260{
261 MEM_delete(idx);
262}
263
265{
266 switch (pr_size) {
267 case IMB_PROXY_NONE:
268 return -1;
269 case IMB_PROXY_25:
270 return 0;
271 case IMB_PROXY_50:
272 return 1;
273 case IMB_PROXY_75:
274 return 2;
275 case IMB_PROXY_100:
276 return 3;
277 default:
278 BLI_assert_msg(0, "Unhandled proxy size enum!");
279 return -1;
280 }
281}
282
283/* ----------------------------------------------------------------------
284 * - rebuild helper functions
285 * ---------------------------------------------------------------------- */
286
287static void get_index_dir(const MovieReader *anim, char *index_dir, size_t index_dir_maxncpy)
288{
289 if (!anim->index_dir[0]) {
290 char filename[FILE_MAXFILE];
291 char dirname[FILE_MAXDIR];
292 BLI_path_split_dir_file(anim->filepath, dirname, sizeof(dirname), filename, sizeof(filename));
293 BLI_path_join(index_dir, index_dir_maxncpy, dirname, "BL_proxy", filename);
294 }
295 else {
296 BLI_strncpy(index_dir, anim->index_dir, index_dir_maxncpy);
297 }
298}
299
300static bool get_proxy_filepath(const MovieReader *anim,
301 IMB_Proxy_Size preview_size,
302 char *filepath,
303 bool temp)
304{
305 char index_dir[FILE_MAXDIR];
306 int i = proxy_size_to_array_index(preview_size);
307
308 BLI_assert(i >= 0);
309
310 char proxy_name[FILE_MAXFILE];
311 char stream_suffix[20];
312 const char *name = (temp) ? "proxy_%d%s_part.avi" : "proxy_%d%s.avi";
313
314 stream_suffix[0] = 0;
315
316 if (anim->streamindex > 0) {
317 SNPRINTF(stream_suffix, "_st%d", anim->streamindex);
318 }
319
320 SNPRINTF(proxy_name, name, int(proxy_fac[i] * 100), stream_suffix, anim->suffix);
321
322 get_index_dir(anim, index_dir, sizeof(index_dir));
323
324 if (BLI_path_ncmp(anim->filepath, index_dir, FILE_MAXDIR) == 0) {
325 return false;
326 }
327
328 BLI_path_join(filepath, FILE_MAXFILE + FILE_MAXDIR, index_dir, proxy_name);
329 return true;
330}
331
332static void get_tc_filepath(MovieReader *anim, IMB_Timecode_Type tc, char *filepath)
333{
334 char index_dir[FILE_MAXDIR];
335 int i = tc == IMB_TC_RECORD_RUN_NO_GAPS ? 1 : 0;
336
337 const char *index_names[] = {
338 "record_run%s%s.blen_tc",
339 "record_run_no_gaps%s%s.blen_tc",
340 };
341
342 char stream_suffix[20];
343 char index_name[256];
344
345 stream_suffix[0] = 0;
346
347 if (anim->streamindex > 0) {
348 SNPRINTF(stream_suffix, "_st%d", anim->streamindex);
349 }
350
351 SNPRINTF(index_name, index_names[i], stream_suffix, anim->suffix);
352
353 get_index_dir(anim, index_dir, sizeof(index_dir));
354
355 BLI_path_join(filepath, FILE_MAXFILE + FILE_MAXDIR, index_dir, index_name);
356}
357
358/* ----------------------------------------------------------------------
359 * - ffmpeg rebuilder
360 * ---------------------------------------------------------------------- */
361
362#ifdef WITH_FFMPEG
363
364struct proxy_output_ctx {
365 AVFormatContext *of;
366 AVStream *st;
367 AVCodecContext *c;
368 const AVCodec *codec;
369 SwsContext *sws_ctx;
370 AVFrame *frame;
371 int cfra;
372 IMB_Proxy_Size proxy_size;
373 int orig_height;
374 MovieReader *anim;
375};
376
377static proxy_output_ctx *alloc_proxy_output_ffmpeg(MovieReader *anim,
378 AVCodecContext *codec_ctx,
379 AVStream *st,
380 IMB_Proxy_Size proxy_size,
381 int width,
382 int height,
383 int quality)
384{
385 proxy_output_ctx *rv = MEM_callocN<proxy_output_ctx>("alloc_proxy_output");
386
387 char filepath[FILE_MAX];
388
389 rv->proxy_size = proxy_size;
390 rv->anim = anim;
391
392 get_proxy_filepath(rv->anim, rv->proxy_size, filepath, true);
393 if (!BLI_file_ensure_parent_dir_exists(filepath)) {
394 MEM_freeN(rv);
395 return nullptr;
396 }
397
398 rv->of = avformat_alloc_context();
399 /* Note: we keep on using .avi extension for proxies,
400 * but actual container can not be AVI, since it does not support
401 * video rotation metadata. */
402 rv->of->oformat = av_guess_format("mp4", nullptr, nullptr);
403
404 rv->of->url = av_strdup(filepath);
405
406 rv->st = avformat_new_stream(rv->of, nullptr);
407 rv->st->id = 0;
408
409 rv->codec = avcodec_find_encoder(AV_CODEC_ID_H264);
410
411 rv->c = avcodec_alloc_context3(rv->codec);
412
413 if (!rv->codec) {
414 CLOG_ERROR(&LOG, "Could not build proxy '%s': failed to create video encoder", filepath);
415 avcodec_free_context(&rv->c);
416 avformat_free_context(rv->of);
417 MEM_freeN(rv);
418 return nullptr;
419 }
420
421 rv->c->width = width;
422 rv->c->height = height;
423 rv->c->gop_size = 10;
424 rv->c->max_b_frames = 0;
425
426 const enum AVPixelFormat *pix_fmts = ffmpeg_get_pix_fmts(rv->c, rv->codec);
427 if (pix_fmts) {
428 rv->c->pix_fmt = pix_fmts[0];
429 }
430 else {
431 rv->c->pix_fmt = AV_PIX_FMT_YUVJ420P;
432 }
433
434 rv->c->sample_aspect_ratio = rv->st->sample_aspect_ratio = st->sample_aspect_ratio;
435
436 rv->c->time_base.den = 25;
437 rv->c->time_base.num = 1;
438 rv->st->time_base = rv->c->time_base;
439 rv->st->avg_frame_rate = av_inv_q(rv->c->time_base);
440
441 /* This range matches #eFFMpegCrf. `crf_range_min` corresponds to lowest quality,
442 * `crf_range_max` to highest quality. */
443 const int crf_range_min = 32;
444 const int crf_range_max = 17;
445 int crf = round_fl_to_int((quality / 100.0f) * (crf_range_max - crf_range_min) + crf_range_min);
446
447 AVDictionary *codec_opts = nullptr;
448 /* High quality preset value. */
449 av_dict_set_int(&codec_opts, "crf", crf, 0);
450 /* Prefer smaller file-size. Presets from `veryslow` to `veryfast` produce output with very
451 * similar file-size, but there is big difference in performance.
452 * In some cases `veryfast` preset will produce smallest file-size. */
453 av_dict_set(&codec_opts, "preset", "veryfast", 0);
454 av_dict_set(&codec_opts, "tune", "fastdecode", 0);
455
456 if (rv->codec->capabilities & AV_CODEC_CAP_OTHER_THREADS) {
457 rv->c->thread_count = 0;
458 }
459 else {
460 rv->c->thread_count = MOV_thread_count();
461 }
462
463 if (rv->codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
464 rv->c->thread_type = FF_THREAD_FRAME;
465 }
466 else if (rv->codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
467 rv->c->thread_type = FF_THREAD_SLICE;
468 }
469
470 if (rv->of->oformat->flags & AVFMT_GLOBALHEADER) {
471 rv->c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
472 }
473
474 rv->c->color_range = codec_ctx->color_range;
475 rv->c->color_primaries = codec_ctx->color_primaries;
476 rv->c->color_trc = codec_ctx->color_trc;
477 rv->c->colorspace = codec_ctx->colorspace;
478
479 int ret = avio_open(&rv->of->pb, filepath, AVIO_FLAG_WRITE);
480
481 if (ret < 0) {
482 char error_str[AV_ERROR_MAX_STRING_SIZE];
483 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
484
486 "Could not build proxy '%s': failed to create output file (%s)",
487 filepath,
488 error_str);
489 avcodec_free_context(&rv->c);
490 avformat_free_context(rv->of);
491 MEM_freeN(rv);
492 return nullptr;
493 }
494
495 ret = avcodec_open2(rv->c, rv->codec, &codec_opts);
496 if (ret < 0) {
497 char error_str[AV_ERROR_MAX_STRING_SIZE];
498 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
499
501 &LOG, "Could not build proxy '%s': failed to open video codec (%s)", filepath, error_str);
502 avcodec_free_context(&rv->c);
503 avformat_free_context(rv->of);
504 MEM_freeN(rv);
505 return nullptr;
506 }
507
508 avcodec_parameters_from_context(rv->st->codecpar, rv->c);
509 ffmpeg_copy_display_matrix(st, rv->st);
510
511 rv->orig_height = st->codecpar->height;
512
513 if (st->codecpar->width != width || st->codecpar->height != height ||
514 st->codecpar->format != rv->c->pix_fmt)
515 {
516 const size_t align = ffmpeg_get_buffer_alignment();
517 rv->frame = av_frame_alloc();
518 rv->frame->format = rv->c->pix_fmt;
519 rv->frame->width = width;
520 rv->frame->height = height;
521 av_frame_get_buffer(rv->frame, align);
522
523 rv->sws_ctx = ffmpeg_sws_get_context(st->codecpar->width,
524 rv->orig_height,
525 AVPixelFormat(st->codecpar->format),
526 codec_ctx->color_range == AVCOL_RANGE_JPEG,
527 -1,
528 width,
529 height,
530 rv->c->pix_fmt,
531 codec_ctx->color_range == AVCOL_RANGE_JPEG,
532 -1,
533 SWS_FAST_BILINEAR);
534 }
535
536 ret = avformat_write_header(rv->of, nullptr);
537 if (ret < 0) {
538 char error_str[AV_ERROR_MAX_STRING_SIZE];
539 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
540
542 &LOG, "Could not build proxy '%s': failed to write header (%s)", filepath, error_str);
543
544 if (rv->frame) {
545 av_frame_free(&rv->frame);
546 }
547
548 avcodec_free_context(&rv->c);
549 avformat_free_context(rv->of);
550 MEM_freeN(rv);
551 return nullptr;
552 }
553
554 return rv;
555}
556
557static void add_to_proxy_output_ffmpeg(proxy_output_ctx *ctx, AVFrame *frame)
558{
559 if (!ctx) {
560 return;
561 }
562
563 if (ctx->sws_ctx && frame &&
564 (frame->data[0] || frame->data[1] || frame->data[2] || frame->data[3]))
565 {
566 ffmpeg_sws_scale_frame(ctx->sws_ctx, ctx->frame, frame);
567 }
568
569 frame = ctx->sws_ctx ? (frame ? ctx->frame : nullptr) : frame;
570
571 if (frame) {
572 frame->pts = ctx->cfra++;
573 }
574
575 int ret = avcodec_send_frame(ctx->c, frame);
576 if (ret < 0) {
577 /* Can't send frame to encoder. This shouldn't happen. */
578 char error_str[AV_ERROR_MAX_STRING_SIZE];
579 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
580
582 &LOG, "Building proxy '%s': failed to send video frame (%s)", ctx->of->url, error_str);
583 return;
584 }
585 AVPacket *packet = av_packet_alloc();
586
587 while (ret >= 0) {
588 ret = avcodec_receive_packet(ctx->c, packet);
589
590 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
591 /* No more packets to flush. */
592 break;
593 }
594 if (ret < 0) {
595 char error_str[AV_ERROR_MAX_STRING_SIZE];
596 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
597
599 "Building proxy '%s': error encoding frame #%i (%s)",
600 ctx->of->url,
601 ctx->cfra - 1,
602 error_str);
603 break;
604 }
605
606 packet->stream_index = ctx->st->index;
607 av_packet_rescale_ts(packet, ctx->c->time_base, ctx->st->time_base);
608# ifdef FFMPEG_USE_DURATION_WORKAROUND
609 my_guess_pkt_duration(ctx->of, ctx->st, packet);
610# endif
611
612 int write_ret = av_interleaved_write_frame(ctx->of, packet);
613 if (write_ret != 0) {
614 char error_str[AV_ERROR_MAX_STRING_SIZE];
615 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, write_ret);
616
618 "Building proxy '%s': error writing frame #%i (%s)",
619 ctx->of->url,
620 ctx->cfra - 1,
621 error_str);
622 break;
623 }
624 }
625
626 av_packet_free(&packet);
627}
628
629static void free_proxy_output_ffmpeg(proxy_output_ctx *ctx, int rollback)
630{
631 char filepath[FILE_MAX];
632 char filepath_tmp[FILE_MAX];
633
634 if (!ctx) {
635 return;
636 }
637
638 if (!rollback) {
639 /* Flush the remaining packets. */
640 add_to_proxy_output_ffmpeg(ctx, nullptr);
641 }
642
643 av_write_trailer(ctx->of);
644
645 if (ctx->of->oformat) {
646 if (!(ctx->of->oformat->flags & AVFMT_NOFILE)) {
647 avio_close(ctx->of->pb);
648 }
649 }
650 avcodec_free_context(&ctx->c);
651 avformat_free_context(ctx->of);
652
653 if (ctx->sws_ctx) {
654 ffmpeg_sws_release_context(ctx->sws_ctx);
655 ctx->sws_ctx = nullptr;
656 }
657 if (ctx->frame) {
658 av_frame_free(&ctx->frame);
659 }
660
661 get_proxy_filepath(ctx->anim, ctx->proxy_size, filepath_tmp, true);
662
663 if (rollback) {
664 BLI_delete(filepath_tmp, false, false);
665 }
666 else {
667 get_proxy_filepath(ctx->anim, ctx->proxy_size, filepath, false);
668 BLI_rename_overwrite(filepath_tmp, filepath);
669 }
670
671 MEM_freeN(ctx);
672}
673
676
677struct MovieProxyBuilder {
678
679 AVFormatContext *iFormatCtx;
680 AVCodecContext *iCodecCtx;
681 const AVCodec *iCodec;
682 AVStream *iStream;
683 int videoStream;
684
685 int num_proxy_sizes;
686
687 proxy_output_ctx *proxy_ctx[IMB_PROXY_MAX_SLOT];
688 MovieIndexBuilder *indexer[IMB_TC_NUM_TYPES];
689
690 int tcs_in_use;
691 int proxy_sizes_in_use;
692
693 uint64_t seek_pos_pts;
694 uint64_t seek_pos_dts;
695 uint64_t last_seek_pos_pts;
696 uint64_t last_seek_pos_dts;
697 uint64_t start_pts;
698 double frame_rate;
699 double pts_time_base;
700 int frameno, frameno_gapless;
701 int start_pts_set;
702
703 bool build_only_on_bad_performance;
704 bool building_cancelled;
705};
706
707static MovieProxyBuilder *index_ffmpeg_create_context(MovieReader *anim,
708 int tcs_in_use,
709 int proxy_sizes_in_use,
710 int quality,
711 bool build_only_on_bad_performance)
712{
713 /* Never build proxies for un-seekable single frame files. */
714 if (anim->never_seek_decode_one_frame) {
715 return nullptr;
716 }
717
718 MovieProxyBuilder *context = MEM_callocN<MovieProxyBuilder>("FFmpeg index builder context");
719 int num_proxy_sizes = IMB_PROXY_MAX_SLOT;
720 int i, streamcount;
721
722 context->tcs_in_use = tcs_in_use;
723 context->proxy_sizes_in_use = proxy_sizes_in_use;
724 context->num_proxy_sizes = IMB_PROXY_MAX_SLOT;
725 context->build_only_on_bad_performance = build_only_on_bad_performance;
726
727 memset(context->proxy_ctx, 0, sizeof(context->proxy_ctx));
728 memset(context->indexer, 0, sizeof(context->indexer));
729
730 if (avformat_open_input(&context->iFormatCtx, anim->filepath, nullptr, nullptr) != 0) {
731 MEM_freeN(context);
732 return nullptr;
733 }
734
735 if (avformat_find_stream_info(context->iFormatCtx, nullptr) < 0) {
736 avformat_close_input(&context->iFormatCtx);
737 MEM_freeN(context);
738 return nullptr;
739 }
740
741 streamcount = anim->streamindex;
742
743 /* Find the video stream */
744 context->videoStream = -1;
745 for (i = 0; i < context->iFormatCtx->nb_streams; i++) {
746 if (context->iFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
747 if (streamcount > 0) {
748 streamcount--;
749 continue;
750 }
751 context->videoStream = i;
752 break;
753 }
754 }
755
756 if (context->videoStream == -1) {
757 avformat_close_input(&context->iFormatCtx);
758 MEM_freeN(context);
759 return nullptr;
760 }
761
762 context->iStream = context->iFormatCtx->streams[context->videoStream];
763
764 context->iCodec = avcodec_find_decoder(context->iStream->codecpar->codec_id);
765
766 if (context->iCodec == nullptr) {
767 avformat_close_input(&context->iFormatCtx);
768 MEM_freeN(context);
769 return nullptr;
770 }
771
772 context->iCodecCtx = avcodec_alloc_context3(nullptr);
773 avcodec_parameters_to_context(context->iCodecCtx, context->iStream->codecpar);
774 context->iCodecCtx->workaround_bugs = FF_BUG_AUTODETECT;
775
776 if (context->iCodec->capabilities & AV_CODEC_CAP_OTHER_THREADS) {
777 context->iCodecCtx->thread_count = 0;
778 }
779 else {
780 context->iCodecCtx->thread_count = MOV_thread_count();
781 }
782
783 if (context->iCodec->capabilities & AV_CODEC_CAP_FRAME_THREADS) {
784 context->iCodecCtx->thread_type = FF_THREAD_FRAME;
785 }
786 else if (context->iCodec->capabilities & AV_CODEC_CAP_SLICE_THREADS) {
787 context->iCodecCtx->thread_type = FF_THREAD_SLICE;
788 }
789
790 if (avcodec_open2(context->iCodecCtx, context->iCodec, nullptr) < 0) {
791 avformat_close_input(&context->iFormatCtx);
792 avcodec_free_context(&context->iCodecCtx);
793 MEM_freeN(context);
794 return nullptr;
795 }
796
797 for (i = 0; i < num_proxy_sizes; i++) {
798 if (proxy_sizes_in_use & proxy_sizes[i]) {
799 int width = context->iCodecCtx->width * proxy_fac[i];
800 int height = context->iCodecCtx->height * proxy_fac[i];
801 width += width % 2;
802 height += height % 2;
803 context->proxy_ctx[i] = alloc_proxy_output_ffmpeg(
804 anim, context->iCodecCtx, context->iStream, proxy_sizes[i], width, height, quality);
805 if (!context->proxy_ctx[i]) {
806 proxy_sizes_in_use &= ~int(proxy_sizes[i]);
807 }
808 }
809 }
810
811 if (context->proxy_ctx[0] == nullptr && context->proxy_ctx[1] == nullptr &&
812 context->proxy_ctx[2] == nullptr && context->proxy_ctx[3] == nullptr)
813 {
814 avformat_close_input(&context->iFormatCtx);
815 avcodec_free_context(&context->iCodecCtx);
816 MEM_freeN(context);
817 return nullptr; /* Nothing to transcode. */
818 }
819
820 for (i = 0; i < IMB_TC_NUM_TYPES; i++) {
821 if (tcs_in_use & tc_types[i]) {
822 char filepath[FILE_MAX];
823
824 get_tc_filepath(anim, tc_types[i], filepath);
825
826 context->indexer[i] = index_builder_create(filepath);
827 if (!context->indexer[i]) {
828 tcs_in_use &= ~int(tc_types[i]);
829 }
830 }
831 }
832
833 return context;
834}
835
836static void index_rebuild_ffmpeg_finish(MovieProxyBuilder *context, const bool stop)
837{
838 int i;
839
840 const bool do_rollback = stop || context->building_cancelled;
841
842 for (i = 0; i < IMB_TC_NUM_TYPES; i++) {
843 if (context->tcs_in_use & tc_types[i]) {
844 index_builder_finish(context->indexer[i], do_rollback);
845 }
846 }
847
848 for (i = 0; i < context->num_proxy_sizes; i++) {
849 if (context->proxy_sizes_in_use & proxy_sizes[i]) {
850 free_proxy_output_ffmpeg(context->proxy_ctx[i], do_rollback);
851 }
852 }
853
854 avcodec_free_context(&context->iCodecCtx);
855 avformat_close_input(&context->iFormatCtx);
856
857 MEM_freeN(context);
858}
859
860static void index_rebuild_ffmpeg_proc_decoded_frame(MovieProxyBuilder *context, AVFrame *in_frame)
861{
862 int i;
863 uint64_t s_pts = context->seek_pos_pts;
864 uint64_t s_dts = context->seek_pos_dts;
865 uint64_t pts = av_get_pts_from_frame(in_frame);
866
867 for (i = 0; i < context->num_proxy_sizes; i++) {
868 add_to_proxy_output_ffmpeg(context->proxy_ctx[i], in_frame);
869 }
870
871 if (!context->start_pts_set) {
872 context->start_pts = pts;
873 context->start_pts_set = true;
874 }
875
876 context->frameno = floor(
877 (pts - context->start_pts) * context->pts_time_base * context->frame_rate + 0.5);
878
879 int64_t seek_pos_pts = timestamp_from_pts_or_dts(context->seek_pos_pts, context->seek_pos_dts);
880
881 if (pts < seek_pos_pts) {
882 /* Decoding starts *always* on I-Frames. In this case our position is
883 * before our seek I-Frame. So we need to pick the previous available
884 * I-Frame to be able to decode this one properly.
885 */
886 s_pts = context->last_seek_pos_pts;
887 s_dts = context->last_seek_pos_dts;
888 }
889
890 for (i = 0; i < IMB_TC_NUM_TYPES; i++) {
891 if (context->tcs_in_use & tc_types[i]) {
892 int tc_frameno = context->frameno;
893
894 if (tc_types[i] == IMB_TC_RECORD_RUN_NO_GAPS) {
895 tc_frameno = context->frameno_gapless;
896 }
897
898 index_builder_add_entry(context->indexer[i], tc_frameno, s_pts, s_dts, pts);
899 }
900 }
901
902 context->frameno_gapless++;
903}
904
905static int index_rebuild_ffmpeg(MovieProxyBuilder *context,
906 const bool *stop,
907 bool *do_update,
908 float *progress)
909{
910 AVFrame *in_frame = av_frame_alloc();
911 AVPacket *next_packet = av_packet_alloc();
912 uint64_t stream_size;
913
914 stream_size = avio_size(context->iFormatCtx->pb);
915
916 context->frame_rate = av_q2d(
917 av_guess_frame_rate(context->iFormatCtx, context->iStream, nullptr));
918 context->pts_time_base = av_q2d(context->iStream->time_base);
919
920 while (av_read_frame(context->iFormatCtx, next_packet) >= 0) {
921 float next_progress =
922 float(int(floor(double(next_packet->pos) * 100 / double(stream_size) + 0.5))) / 100;
923
924 if (*progress != next_progress) {
925 *progress = next_progress;
926 *do_update = true;
927 }
928
929 if (*stop) {
930 break;
931 }
932
933 if (next_packet->stream_index == context->videoStream) {
934 int ret = avcodec_send_packet(context->iCodecCtx, next_packet);
935 while (ret >= 0) {
936 ret = avcodec_receive_frame(context->iCodecCtx, in_frame);
937
938 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
939 /* No more frames to flush. */
940 break;
941 }
942 if (ret < 0) {
943 char error_str[AV_ERROR_MAX_STRING_SIZE];
944 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
945 CLOG_ERROR(&LOG, "Error decoding proxy frame: %s", error_str);
946 break;
947 }
948
949 if (next_packet->flags & AV_PKT_FLAG_KEY) {
950 context->last_seek_pos_pts = context->seek_pos_pts;
951 context->last_seek_pos_dts = context->seek_pos_dts;
952
953 context->seek_pos_pts = in_frame->pts;
954 context->seek_pos_dts = in_frame->pkt_dts;
955 }
956
957 index_rebuild_ffmpeg_proc_decoded_frame(context, in_frame);
958 }
959 }
960 av_packet_unref(next_packet);
961 }
962
963 /* process pictures still stuck in decoder engine after EOF
964 * according to ffmpeg docs using nullptr packets.
965 *
966 * At least, if we haven't already stopped... */
967
968 if (!*stop) {
969 int ret = avcodec_send_packet(context->iCodecCtx, nullptr);
970
971 while (ret >= 0) {
972 ret = avcodec_receive_frame(context->iCodecCtx, in_frame);
973
974 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
975 /* No more frames to flush. */
976 break;
977 }
978 if (ret < 0) {
979 char error_str[AV_ERROR_MAX_STRING_SIZE];
980 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
981 CLOG_ERROR(&LOG, "Error flushing proxy frame: %s", error_str);
982 break;
983 }
984 index_rebuild_ffmpeg_proc_decoded_frame(context, in_frame);
985 }
986 }
987
988 av_packet_free(&next_packet);
989 av_free(in_frame);
990
991 return 1;
992}
993
994/* Get number of frames, that can be decoded in specified time period. */
995static int indexer_performance_get_decode_rate(MovieProxyBuilder *context,
996 const double time_period)
997{
998 AVFrame *in_frame = av_frame_alloc();
999 AVPacket *packet = av_packet_alloc();
1000
1001 const double start = BLI_time_now_seconds();
1002 int frames_decoded = 0;
1003
1004 while (av_read_frame(context->iFormatCtx, packet) >= 0) {
1005 if (packet->stream_index != context->videoStream) {
1006 av_packet_unref(packet);
1007 continue;
1008 }
1009
1010 int ret = avcodec_send_packet(context->iCodecCtx, packet);
1011 while (ret >= 0) {
1012 ret = avcodec_receive_frame(context->iCodecCtx, in_frame);
1013
1014 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
1015 break;
1016 }
1017
1018 if (ret < 0) {
1019 char error_str[AV_ERROR_MAX_STRING_SIZE];
1020 av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret);
1021 CLOG_ERROR(&LOG, "Error decoding proxy frame: %s", error_str);
1022 break;
1023 }
1024 frames_decoded++;
1025 }
1026
1027 const double end = BLI_time_now_seconds();
1028
1029 if (end > start + time_period) {
1030 break;
1031 }
1032 av_packet_unref(packet);
1033 }
1034
1035 av_packet_free(&packet);
1036 av_frame_free(&in_frame);
1037
1038 avcodec_flush_buffers(context->iCodecCtx);
1039 av_seek_frame(context->iFormatCtx, -1, 0, AVSEEK_FLAG_BACKWARD);
1040 return frames_decoded;
1041}
1042
1043/* Read up to 10k movie packets and return max GOP size detected.
1044 * Number of packets is arbitrary. It should be as large as possible, but processed within
1045 * reasonable time period, so detected GOP size is as close to real as possible. */
1046static int indexer_performance_get_max_gop_size(MovieProxyBuilder *context)
1047{
1048 AVPacket *packet = av_packet_alloc();
1049
1050 const int packets_max = 10000;
1051 int packet_index = 0;
1052 int max_gop = 0;
1053 int cur_gop = 0;
1054
1055 while (av_read_frame(context->iFormatCtx, packet) >= 0) {
1056 if (packet->stream_index != context->videoStream) {
1057 av_packet_unref(packet);
1058 continue;
1059 }
1060 packet_index++;
1061 cur_gop++;
1062
1063 if (packet->flags & AV_PKT_FLAG_KEY) {
1064 max_gop = max_ii(max_gop, cur_gop);
1065 cur_gop = 0;
1066 }
1067
1068 if (packet_index > packets_max) {
1069 break;
1070 }
1071 av_packet_unref(packet);
1072 }
1073
1074 av_packet_free(&packet);
1075
1076 av_seek_frame(context->iFormatCtx, -1, 0, AVSEEK_FLAG_BACKWARD);
1077 return max_gop;
1078}
1079
1080/* Assess scrubbing performance of provided file. This function is not meant to be very exact.
1081 * It compares number of frames decoded in reasonable time with largest detected GOP size.
1082 * Because seeking happens in single GOP, it means, that maximum seek time can be detected this
1083 * way.
1084 * Since proxies use GOP size of 10 frames, skip building if detected GOP size is less or
1085 * equal.
1086 */
1087static bool indexer_need_to_build_proxy(MovieProxyBuilder *context)
1088{
1089 if (!context->build_only_on_bad_performance) {
1090 return true;
1091 }
1092
1093 /* Make sure, that file is not cold read. */
1094 indexer_performance_get_decode_rate(context, 0.1);
1095 /* Get decode rate per 100ms. This is arbitrary, but seems to be good baseline cadence of
1096 * seeking. */
1097 const int decode_rate = indexer_performance_get_decode_rate(context, 0.1);
1098 const int max_gop_size = indexer_performance_get_max_gop_size(context);
1099
1100 if (max_gop_size <= 10 || max_gop_size < decode_rate) {
1102 "Skipping proxy building for %s: Decoding performance is already good.",
1103 context->iFormatCtx->url);
1104 context->building_cancelled = true;
1105 return false;
1106 }
1107
1108 return true;
1109}
1110
1111#endif
1112
1113/* ----------------------------------------------------------------------
1114 * - public API
1115 * ---------------------------------------------------------------------- */
1116
1117MovieProxyBuilder *MOV_proxy_builder_start(MovieReader *anim,
1118 IMB_Timecode_Type tcs_in_use,
1119 int proxy_sizes_in_use,
1120 int quality,
1121 const bool overwrite,
1122 blender::Set<std::string> *processed_paths,
1123 bool build_only_on_bad_performance)
1124{
1125 int proxy_sizes_to_build = proxy_sizes_in_use;
1126
1127 /* Check which proxies are going to be generated in this session already. */
1128 if (processed_paths != nullptr) {
1129 for (int i = 0; i < IMB_PROXY_MAX_SLOT; i++) {
1130 IMB_Proxy_Size proxy_size = proxy_sizes[i];
1131 if ((proxy_size & proxy_sizes_to_build) == 0) {
1132 continue;
1133 }
1134 char filepath[FILE_MAX];
1135 if (!get_proxy_filepath(anim, proxy_size, filepath, false)) {
1136 return nullptr;
1137 }
1138 if (!processed_paths->add(filepath)) {
1139 proxy_sizes_to_build &= ~int(proxy_size);
1140 }
1141 }
1142 }
1143
1144 /* When not overwriting existing proxies, skip the ones that already exist. */
1145 if (!overwrite) {
1146 int built_proxies = MOV_get_existing_proxies(anim);
1147 if (built_proxies != 0) {
1148 for (int i = 0; i < IMB_PROXY_MAX_SLOT; i++) {
1149 IMB_Proxy_Size proxy_size = proxy_sizes[i];
1150 if (proxy_size & built_proxies) {
1151 char filepath[FILE_MAX];
1152 if (!get_proxy_filepath(anim, proxy_size, filepath, false)) {
1153 return nullptr;
1154 }
1155 CLOG_INFO_NOCHECK(&LOG, "Skipping proxy: %s", filepath);
1156 }
1157 }
1158 }
1159 proxy_sizes_to_build &= ~built_proxies;
1160 }
1161
1162 if (proxy_sizes_to_build == 0) {
1163 return nullptr;
1164 }
1165
1166 MovieProxyBuilder *context = nullptr;
1167#ifdef WITH_FFMPEG
1168 if (anim->state == MovieReader::State::Valid) {
1169 context = index_ffmpeg_create_context(
1170 anim, tcs_in_use, proxy_sizes_to_build, quality, build_only_on_bad_performance);
1171 }
1172#else
1173 UNUSED_VARS(build_only_on_bad_performance);
1174#endif
1175
1176 return context;
1177
1178 UNUSED_VARS(tcs_in_use, proxy_sizes_in_use, quality);
1179}
1180
1181void MOV_proxy_builder_process(MovieProxyBuilder *context,
1182 /* NOLINTNEXTLINE: readability-non-const-parameter. */
1183 bool *stop,
1184 /* NOLINTNEXTLINE: readability-non-const-parameter. */
1185 bool *do_update,
1186 /* NOLINTNEXTLINE: readability-non-const-parameter. */
1187 float *progress)
1188{
1189#ifdef WITH_FFMPEG
1190 if (context != nullptr) {
1191 if (indexer_need_to_build_proxy(context)) {
1192 index_rebuild_ffmpeg(context, stop, do_update, progress);
1193 }
1194 }
1195#endif
1196 UNUSED_VARS(context, stop, do_update, progress);
1197}
1198
1199void MOV_proxy_builder_finish(MovieProxyBuilder *context, const bool stop)
1200{
1201#ifdef WITH_FFMPEG
1202 if (context != nullptr) {
1203 index_rebuild_ffmpeg_finish(context, stop);
1204 }
1205#endif
1206 /* static defined at top of the file */
1207 UNUSED_VARS(context, stop, proxy_sizes);
1208}
1209
1211{
1212 if (anim == nullptr) {
1213 return;
1214 }
1215
1216 for (int i = 0; i < IMB_PROXY_MAX_SLOT; i++) {
1217 if (anim->proxy_anim[i]) {
1218 MOV_close(anim->proxy_anim[i]);
1219 anim->proxy_anim[i] = nullptr;
1220 }
1221 }
1222
1223 if (anim->record_run) {
1225 anim->record_run = nullptr;
1226 }
1227 if (anim->no_gaps) {
1229 anim->no_gaps = nullptr;
1230 }
1231
1232 anim->proxies_tried = 0;
1233 anim->indices_tried = 0;
1234}
1235
1236void MOV_set_custom_proxy_dir(MovieReader *anim, const char *dir)
1237{
1238 if (STREQ(anim->index_dir, dir)) {
1239 return;
1240 }
1241 STRNCPY(anim->index_dir, dir);
1242
1243 MOV_close_proxies(anim);
1244}
1245
1247{
1248 char filepath[FILE_MAX];
1249 int i = proxy_size_to_array_index(preview_size);
1250
1251 if (i < 0) {
1252 return nullptr;
1253 }
1254
1255 if (anim->proxy_anim[i]) {
1256 return anim->proxy_anim[i];
1257 }
1258
1259 if (anim->proxies_tried & preview_size) {
1260 return nullptr;
1261 }
1262
1263 get_proxy_filepath(anim, preview_size, filepath, false);
1264
1265 /* Proxies are generated in the same color space as animation itself.
1266 *
1267 * Also skip any colorspace conversion to the color pipeline design as it helps performance and
1268 * the image buffers from the proxy builder are not used anywhere else in Blender. */
1269 anim->proxy_anim[i] = MOV_open_file(filepath, 0, 0, true, anim->colorspace);
1270
1271 anim->proxies_tried |= preview_size;
1272
1273 return anim->proxy_anim[i];
1274}
1275
1277{
1278 char filepath[FILE_MAX];
1279
1280 MovieIndex **index = nullptr;
1281
1282 if (tc == IMB_TC_RECORD_RUN) {
1283 index = &anim->record_run;
1284 }
1285 else if (tc == IMB_TC_RECORD_RUN_NO_GAPS) {
1286 index = &anim->no_gaps;
1287 }
1288
1289 if (anim->indices_tried & tc) {
1290 return nullptr;
1291 }
1292 if (index == nullptr) {
1293 return nullptr;
1294 }
1295
1296 get_tc_filepath(anim, tc, filepath);
1297
1298 *index = movie_index_open(filepath);
1299
1300 anim->indices_tried |= tc;
1301
1302 return *index;
1303}
1304
1306{
1307 const MovieIndex *idx = movie_open_index(anim, tc);
1308
1309 if (!idx) {
1310 return position;
1311 }
1312
1313 return idx->get_frame_index(position);
1314}
1315
1317{
1318 const int num_proxy_sizes = IMB_PROXY_MAX_SLOT;
1319 int existing = IMB_PROXY_NONE;
1320 for (int i = 0; i < num_proxy_sizes; i++) {
1321 IMB_Proxy_Size proxy_size = proxy_sizes[i];
1322 char filepath[FILE_MAX];
1323 get_proxy_filepath(anim, proxy_size, filepath, false);
1324 if (BLI_exists(filepath)) {
1325 existing |= int(proxy_size);
1326 }
1327 }
1328 return existing;
1329}
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define B_ENDIAN
#define L_ENDIAN
BLI_INLINE void BLI_endian_switch_uint64(uint64_t *val) ATTR_NONNULL(1)
BLI_INLINE void BLI_endian_switch_int32(int *val) ATTR_NONNULL(1)
File and directory operations.
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:360
FILE * BLI_fopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL()
int BLI_rename_overwrite(const char *from, const char *to) ATTR_NONNULL()
Definition fileops_c.cc:528
bool BLI_file_ensure_parent_dir_exists(const char *filepath) ATTR_NONNULL(1)
Definition fileops_c.cc:452
MINLINE int round_fl_to_int(float a)
MINLINE int max_ii(int a, int b)
#define BLI_path_ncmp
#define FILE_MAXFILE
#define FILE_MAX
#define BLI_path_join(...)
void BLI_path_split_dir_file(const char *filepath, char *dir, size_t dir_maxncpy, char *file, size_t file_maxncpy) ATTR_NONNULL(1
#define FILE_MAXDIR
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:604
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define BLI_string_join(...)
Platform independent time functions.
double BLI_time_now_seconds(void)
Definition time.cc:113
#define UNUSED_VARS(...)
#define ELEM(...)
#define STREQ(a, b)
const char * dirname(char *path)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:188
#define CLOG_INFO_NOCHECK(clg_ref, format,...)
Definition CLG_log.h:204
IMB_Proxy_Size
@ IMB_PROXY_100
@ IMB_PROXY_MAX_SLOT
@ IMB_PROXY_75
@ IMB_PROXY_50
@ IMB_PROXY_25
@ IMB_PROXY_NONE
Read Guarded memory(de)allocation.
IMB_Timecode_Type
Definition MOV_enums.hh:44
@ IMB_TC_RECORD_RUN_NO_GAPS
Definition MOV_enums.hh:61
@ IMB_TC_NUM_TYPES
Definition MOV_enums.hh:62
@ IMB_TC_RECORD_RUN
Definition MOV_enums.hh:54
int pad[32 - sizeof(int)]
long long int int64_t
unsigned long long int uint64_t
void resize(const int64_t new_size)
bool add(const Key &key)
Definition BLI_set.hh:248
Definition half.h:41
nullptr float
FFMPEG_INLINE size_t ffmpeg_get_buffer_alignment()
FFMPEG_INLINE int64_t timestamp_from_pts_or_dts(int64_t pts, int64_t dts)
FFMPEG_INLINE int64_t av_get_pts_from_frame(AVFrame *picture)
FFMPEG_INLINE enum AVPixelFormat * ffmpeg_get_pix_fmts(struct AVCodecContext *context, const AVCodec *codec)
FFMPEG_INLINE void my_guess_pkt_duration(AVFormatContext *s, AVStream *st, AVPacket *pkt)
FFMPEG_INLINE void ffmpeg_copy_display_matrix(const AVStream *src, AVStream *dst)
#define floor
#define LOG(level)
Definition log.h:97
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
void MOV_set_custom_proxy_dir(MovieReader *anim, const char *dir)
MovieProxyBuilder * MOV_proxy_builder_start(MovieReader *anim, IMB_Timecode_Type tcs_in_use, int proxy_sizes_in_use, int quality, const bool overwrite, blender::Set< std::string > *processed_paths, bool build_only_on_bad_performance)
void MOV_proxy_builder_process(MovieProxyBuilder *context, bool *stop, bool *do_update, float *progress)
#define INDEX_FILE_VERSION
const MovieIndex * movie_open_index(MovieReader *anim, IMB_Timecode_Type tc)
static const IMB_Proxy_Size proxy_sizes[]
static int proxy_size_to_array_index(IMB_Proxy_Size pr_size)
static const char binary_header_str[]
void MOV_close_proxies(MovieReader *anim)
static const float proxy_fac[]
static MovieIndex * movie_index_open(const char *filepath)
static void get_tc_filepath(MovieReader *anim, IMB_Timecode_Type tc, char *filepath)
int MOV_get_existing_proxies(const MovieReader *anim)
int MOV_calc_frame_index_with_timecode(MovieReader *anim, IMB_Timecode_Type tc, int position)
static void movie_index_free(MovieIndex *idx)
void MOV_proxy_builder_finish(MovieProxyBuilder *context, const bool stop)
static void get_index_dir(const MovieReader *anim, char *index_dir, size_t index_dir_maxncpy)
MovieReader * movie_open_proxy(MovieReader *anim, IMB_Proxy_Size preview_size)
static bool get_proxy_filepath(const MovieReader *anim, IMB_Proxy_Size preview_size, char *filepath, bool temp)
void MOV_close(MovieReader *anim)
Definition movie_read.cc:66
MovieReader * MOV_open_file(const char *filepath, const int ib_flags, const int streamindex, const bool keep_original_colorspace, char colorspace[IM_MAX_SPACE])
int context(const bContext *C, const char *member, bContextDataResult *result)
T clamp(const T &a, const T &min, const T &max)
const char * name
return ret
int get_duration() const
blender::Vector< MovieIndexFrame > entries
char filepath[1024]
int get_frame_index(int frameno) const
uint64_t get_seek_pos_dts(int frame_index) const
uint64_t get_pts(int frame_index) const
uint64_t get_seek_pos_pts(int frame_index) const
char colorspace[64]
Definition movie_read.hh:93
MovieReader * proxy_anim[IMB_PROXY_MAX_SLOT]
Definition movie_read.hh:89
MovieIndex * record_run
Definition movie_read.hh:90
MovieIndex * no_gaps
Definition movie_read.hh:91
char index_dir[768]
Definition movie_read.hh:84
char filepath[1024]
Definition movie_read.hh:47
int indices_tried
Definition movie_read.hh:87
char suffix[64]
Definition movie_read.hh:95
int proxies_tried
Definition movie_read.hh:86
i
Definition text_draw.cc:230
uint len