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