diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f52a0765ff..6ac27fe580 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,8 +17,8 @@ on: type: boolean default: false required: false - schedule: - - cron: '0 12 * * *' +# schedule: +# - cron: '0 12 * * *' env: DOCKER_BUILDKIT: 1 diff --git a/addins/4.4.sh b/addins/4.4.sh index 6eec69523e..414585a891 100644 --- a/addins/4.4.sh +++ b/addins/4.4.sh @@ -1,2 +1,3 @@ #!/bin/bash GIT_BRANCH="release/4.4" +PATCHES_DIR="patches/ffmpeg/older" \ No newline at end of file diff --git a/addins/5.0.sh b/addins/5.0.sh index 4a1b864c08..3f50232161 100644 --- a/addins/5.0.sh +++ b/addins/5.0.sh @@ -1,2 +1,3 @@ #!/bin/bash GIT_BRANCH="release/5.0" +PATCHES_DIR="patches/ffmpeg/older" \ No newline at end of file diff --git a/addins/5.1.sh b/addins/5.1.sh index a58b318414..79bd2b3bdd 100644 --- a/addins/5.1.sh +++ b/addins/5.1.sh @@ -1,2 +1,3 @@ #!/bin/bash GIT_BRANCH="release/5.1" +PATCHES_DIR="patches/ffmpeg/older" \ No newline at end of file diff --git a/addins/6.0.sh b/addins/6.0.sh index 720a9cd0a7..507b90a45a 100644 --- a/addins/6.0.sh +++ b/addins/6.0.sh @@ -1,2 +1,3 @@ #!/bin/bash GIT_BRANCH="release/6.0" +PATCHES_DIR="patches/ffmpeg/older" \ No newline at end of file diff --git a/addins/6.1.sh b/addins/6.1.sh index a50844a2b6..a13dcfc4ac 100644 --- a/addins/6.1.sh +++ b/addins/6.1.sh @@ -1,2 +1,3 @@ #!/bin/bash GIT_BRANCH="release/6.1" +PATCHES_DIR="patches/ffmpeg/older" \ No newline at end of file diff --git a/addins/7.0.sh b/addins/7.0.sh index 312d7d155e..b622e8c997 100644 --- a/addins/7.0.sh +++ b/addins/7.0.sh @@ -1,2 +1,3 @@ #!/bin/bash GIT_BRANCH="release/7.0" +PATCHES_DIR="patches/ffmpeg/v7" \ No newline at end of file diff --git a/addins/7.1.sh b/addins/7.1.sh index 0ddb8340a1..39ad97cb10 100644 --- a/addins/7.1.sh +++ b/addins/7.1.sh @@ -1,2 +1,3 @@ #!/bin/bash -GIT_BRANCH="release/7.1" +GIT_BRANCH="descript/7.1" +PATCHES_DIR="patches/ffmpeg/v7.1" diff --git a/build.sh b/build.sh index 03a62de527..e4840eac75 100755 --- a/build.sh +++ b/build.sh @@ -19,10 +19,11 @@ fi rm -rf ffbuild mkdir ffbuild -FFMPEG_REPO="${FFMPEG_REPO:-https://github.com/FFmpeg/FFmpeg.git}" +FFMPEG_REPO="${FFMPEG_REPO:-https://github.com/descriptinc/FFmpeg.git}" FFMPEG_REPO="${FFMPEG_REPO_OVERRIDE:-$FFMPEG_REPO}" GIT_BRANCH="${GIT_BRANCH:-master}" GIT_BRANCH="${GIT_BRANCH_OVERRIDE:-$GIT_BRANCH}" +PATCHES_DIR="$PWD/${PATCHES_DIR:-patches/ffmpeg/v7}" BUILD_SCRIPT="$(mktemp)" trap "rm -f -- '$BUILD_SCRIPT'" EXIT @@ -46,7 +47,7 @@ EOF [[ -t 1 ]] && TTY_ARG="-t" || TTY_ARG="" -docker run --rm -i $TTY_ARG "${UIDARGS[@]}" -v "$PWD/ffbuild":/ffbuild -v "$BUILD_SCRIPT":/build.sh "$IMAGE" bash /build.sh +docker run --rm -i $TTY_ARG "${UIDARGS[@]}" -v "$PWD/ffbuild":/ffbuild -v "$BUILD_SCRIPT":/build.sh -v "$PATCHES_DIR":/patches "$IMAGE" bash /build.sh if [[ -n "$FFBUILD_OUTPUT_DIR" ]]; then mkdir -p "$FFBUILD_OUTPUT_DIR" diff --git a/patches/ffmpeg/older/001-ffmpeg-add-force-cfr-flag.patch b/patches/ffmpeg/older/001-ffmpeg-add-force-cfr-flag.patch new file mode 100644 index 0000000000..c2f05b932d --- /dev/null +++ b/patches/ffmpeg/older/001-ffmpeg-add-force-cfr-flag.patch @@ -0,0 +1,80 @@ +diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h +index 25604e05a5..fedd4d4308 100644 +--- a/fftools/ffmpeg.h ++++ b/fftools/ffmpeg.h +@@ -282,6 +282,9 @@ typedef struct OptionsContext { + int nb_enc_stats_post_fmt; + SpecifierOpt *mux_stats_fmt; + int nb_mux_stats_fmt; ++ SpecifierOpt *force_cfr; ++ int nb_force_cfr; ++ + } OptionsContext; + + typedef struct InputFilter { +@@ -416,6 +419,7 @@ typedef struct InputFile { + int64_t ts_offset; + int64_t start_time; /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */ + int64_t recording_time; ++ double force_cfr; + + /* streams that ffmpeg is aware of; + * there may be extra streams in ctx that are not mapped to an InputStream +diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c +index 350f233ab7..2c1e1a1d3e 100644 +--- a/fftools/ffmpeg_demux.c ++++ b/fftools/ffmpeg_demux.c +@@ -1589,6 +1589,9 @@ int ifile_open(const OptionsContext *o, const char *filename) + f->input_ts_offset = o->input_ts_offset; + f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); + f->accurate_seek = o->accurate_seek; ++ if (o->force_cfr) { ++ f->force_cfr = o->force_cfr[o->nb_force_cfr-1].u.dbl; ++ } + d->loop = o->loop; + d->duration = 0; + d->time_base = (AVRational){ 1, 1 }; +diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c +index c738fc3397..b4527aba09 100644 +--- a/fftools/ffmpeg_filter.c ++++ b/fftools/ffmpeg_filter.c +@@ -1483,13 +1483,23 @@ static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter, + return ret; + } + +- snprintf(name, sizeof(name), "trim_in_%d_%d", +- ist->file_index, ist->index); + if (copy_ts) { + tsoffset = f->start_time == AV_NOPTS_VALUE ? 0 : f->start_time; + if (!start_at_zero && f->ctx->start_time != AV_NOPTS_VALUE) + tsoffset += f->ctx->start_time; + } ++ ++ if (f->force_cfr > 0) { ++ char fps_buf[20]; ++ snprintf(fps_buf,sizeof(fps_buf),"%0.4f",f->force_cfr); ++ ret = insert_filter(&last_filter, &pad_idx, "fps", fps_buf); ++ if (ret < 0) ++ goto fail; ++ } ++ ++ snprintf(name, sizeof(name), "trim_in_%d_%d", ++ ist->file_index, ist->index); ++ + ret = insert_trim(((f->start_time == AV_NOPTS_VALUE) || !f->accurate_seek) ? + AV_NOPTS_VALUE : tsoffset, f->recording_time, + &last_filter, &pad_idx, name); +diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c +index 304471dd03..0a8f461493 100644 +--- a/fftools/ffmpeg_opt.c ++++ b/fftools/ffmpeg_opt.c +@@ -1850,6 +1850,9 @@ const OptionDef options[] = { + "initialise hardware device", "args" }, + { "filter_hw_device", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_hw_device }, + "set hardware device used when filtering", "device" }, ++ { "force_cfr", OPT_VIDEO | HAS_ARG | OPT_DOUBLE | OPT_SPEC | ++ OPT_INPUT, { .off = OFFSET(force_cfr) }, ++ "set frame rate (Hz value, fraction or abbreviation)", "rate" }, + + { NULL, }, + }; diff --git a/patches/ffmpeg/v7.1/001-ffmpeg-add-force-cfr-flag.patch b/patches/ffmpeg/v7.1/001-ffmpeg-add-force-cfr-flag.patch new file mode 100644 index 0000000000..64be6b5167 --- /dev/null +++ b/patches/ffmpeg/v7.1/001-ffmpeg-add-force-cfr-flag.patch @@ -0,0 +1,106 @@ +diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h +index 733d551fa4..06474f1b3c 100644 +--- a/fftools/ffmpeg.h ++++ b/fftools/ffmpeg.h +@@ -176,6 +176,7 @@ typedef struct OptionsContext { + SpecifierOptList hwaccel_output_formats; + SpecifierOptList autorotate; + SpecifierOptList apply_cropping; ++ SpecifierOptList force_cfr; + + /* output options */ + StreamMap *stream_maps; +@@ -276,6 +277,9 @@ typedef struct InputFilterOptions { + * accurate */ + AVRational framerate; + ++ /* convert input stream to CFR at this framerate before inserting additional filters */ ++ AVRational force_cfr; ++ + unsigned crop_top; + unsigned crop_bottom; + unsigned crop_left; +@@ -451,6 +455,9 @@ typedef struct InputStream { + + /* framerate forced with -r */ + AVRational framerate; ++ ++ /* convert input stream to CFR at this framerate before inserting additional filters */ ++ AVRational force_cfr; + #if FFMPEG_OPT_TOP + int top_field_first; + #endif +diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c +index 13aef15eab..9c500b7936 100644 +--- a/fftools/ffmpeg_demux.c ++++ b/fftools/ffmpeg_demux.c +@@ -1032,6 +1032,10 @@ int ist_filter_add(InputStream *ist, InputFilter *ifilter, int is_simple, + (opts->crop_top | opts->crop_bottom | opts->crop_left | opts->crop_right)) + opts->flags |= IFILTER_FLAG_CROP; + } ++ if (ist->force_cfr.num > 0 && ist->force_cfr.den > 0) { ++ opts->force_cfr = ist->force_cfr; ++ opts->flags |= IFILTER_FLAG_CFR; ++ } + } else if (ist->par->codec_type == AVMEDIA_TYPE_SUBTITLE) { + /* Compute the size of the canvas for the subtitles stream. + If the subtitles codecpar has set a size, use it. Otherwise use the +@@ -1241,7 +1245,7 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona + AVCodecParameters *par = st->codecpar; + DemuxStream *ds; + InputStream *ist; +- const char *framerate = NULL, *hwaccel_device = NULL; ++ const char *framerate = NULL, *hwaccel_device = NULL, *forcecfr = NULL; + const char *hwaccel = NULL; + const char *apply_cropping = NULL; + const char *hwaccel_output_format = NULL; +@@ -1437,6 +1441,15 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona + } + } + ++ opt_match_per_stream_str(ist, &o->force_cfr, ic, st, &forcecfr); ++ if (forcecfr) { ++ ret = av_parse_video_rate(&ist->force_cfr, forcecfr); ++ if (ret < 0) { ++ av_log(ist, AV_LOG_ERROR, "Error parsing framerate %s.\n", forcecfr); ++ return ret; ++ } ++ } ++ + #if FFMPEG_OPT_TOP + ist->top_field_first = -1; + opt_match_per_stream_int(ist, &o->top_field_first, ic, st, &ist->top_field_first); +diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c +index 38c7676a7e..62dd23ebe4 100644 +--- a/fftools/ffmpeg_filter.c ++++ b/fftools/ffmpeg_filter.c +@@ -1781,6 +1781,15 @@ static int configure_input_video_filter(FilterGraph *fg, AVFilterGraph *graph, + ifp->displaymatrix_applied = 1; + } + ++ if (ifp->opts.force_cfr.num > 0 && ifp->opts.force_cfr.den > 0) { ++ char force_cfr_buf[64]; ++ snprintf(force_cfr_buf, sizeof(force_cfr_buf), "%d/%d", ++ ifp->opts.force_cfr.num, ifp->opts.force_cfr.den); ++ ret = insert_filter(&last_filter, &pad_idx, "fps", force_cfr_buf); ++ if (ret < 0) ++ return ret; ++ } ++ + snprintf(name, sizeof(name), "trim_in_%s", ifp->opts.name); + ret = insert_trim(ifp->opts.trim_start_us, ifp->opts.trim_end_us, + &last_filter, &pad_idx, name); +diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c +index f639a1cf0a..ce5aabcb59 100644 +--- a/fftools/ffmpeg_opt.c ++++ b/fftools/ffmpeg_opt.c +@@ -2033,5 +2033,9 @@ const OptionDef options[] = { + "set video sync method globally; deprecated, use -fps_mode", "" }, + #endif + ++ { "force_cfr", OPT_TYPE_STRING, OPT_VIDEO | OPT_EXPERT | OPT_PERSTREAM | OPT_INPUT, ++ { .off = OFFSET(force_cfr) }, ++ "set frame rate (Hz value, fraction or abbreviation)", "force_cfr" }, ++ + { NULL, }, + }; diff --git a/patches/ffmpeg/v7.1/002-Adding-option-to-safely-seek-fragmented-MP4-files.patch b/patches/ffmpeg/v7.1/002-Adding-option-to-safely-seek-fragmented-MP4-files.patch new file mode 100644 index 0000000000..48392f43a4 --- /dev/null +++ b/patches/ffmpeg/v7.1/002-Adding-option-to-safely-seek-fragmented-MP4-files.patch @@ -0,0 +1,47 @@ +diff --git a/libavformat/mov.c b/libavformat/mov.c +index ff32d4160e..38109d8eb5 100644 +--- a/libavformat/mov.c ++++ b/libavformat/mov.c +@@ -87,6 +87,7 @@ static int mov_read_mfra(MOVContext *c, AVIOContext *f); + static void mov_free_stream_context(AVFormatContext *s, AVStream *st); + static int64_t add_ctts_entry(MOVCtts** ctts_data, unsigned int* ctts_count, unsigned int* allocated_size, + int count, int duration); ++static int mov_switch_root(AVFormatContext *s, int64_t target, int index); + + static int mov_metadata_track_or_disc_number(MOVContext *c, AVIOContext *pb, + unsigned len, const char *key) +@@ -1638,6 +1639,7 @@ static int64_t get_frag_time(AVFormatContext *s, AVStream *dst_st, + MOVFragmentStreamInfo * frag_stream_info; + MOVStreamContext *sc = dst_st->priv_data; + int64_t timestamp; ++ int stream_present; + int i, j; + + // If the stream is referenced by any sidx, limit the search +@@ -1653,6 +1655,26 @@ static int64_t get_frag_time(AVFormatContext *s, AVStream *dst_st, + return frag_stream_info->sidx_pts; + } + ++ // Check if the requested stream is present in the fragment ++ stream_present = 0; ++ for (i = 0; i < frag_index->item[index].nb_stream_info; i++) { ++ if (dst_st->id != frag_index->item[index].stream_info[i].id) ++ continue; ++ if ( get_stream_info_time(&frag_index->item[index].stream_info[i]) != AV_NOPTS_VALUE) { ++ stream_present = 1; ++ break; ++ } ++ if ( mov_switch_root(s,-1,index) < 0) ++ return AV_NOPTS_VALUE; ++ if ( get_stream_info_time(&frag_index->item[index].stream_info[i]) != AV_NOPTS_VALUE) { ++ stream_present = 1; ++ break; ++ } ++ } ++ ++ if (!stream_present) ++ return AV_NOPTS_VALUE; ++ + for (i = 0; i < frag_index->item[index].nb_stream_info; i++) { + AVStream *frag_stream = NULL; + frag_stream_info = &frag_index->item[index].stream_info[i]; diff --git a/patches/ffmpeg/v7/001-ffmpeg-add-force-cfr-flag.patch b/patches/ffmpeg/v7/001-ffmpeg-add-force-cfr-flag.patch new file mode 100644 index 0000000000..3d673836bd --- /dev/null +++ b/patches/ffmpeg/v7/001-ffmpeg-add-force-cfr-flag.patch @@ -0,0 +1,117 @@ +diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h +index 6394cca1e7..dde2b742e9 100644 +--- a/fftools/ffmpeg.h ++++ b/fftools/ffmpeg.h +@@ -155,6 +155,7 @@ typedef struct OptionsContext { + SpecifierOptList hwaccel_devices; + SpecifierOptList hwaccel_output_formats; + SpecifierOptList autorotate; ++ SpecifierOptList force_cfr; + + /* output options */ + StreamMap *stream_maps; +@@ -254,6 +255,9 @@ typedef struct InputFilterOptions { + * accurate */ + AVRational framerate; + ++ /* convert input stream to CFR at this framerate before inserting additional filters */ ++ AVRational force_cfr; ++ + int sub2video_width; + int sub2video_height; + +@@ -364,6 +368,9 @@ typedef struct InputStream { + + /* framerate forced with -r */ + AVRational framerate; ++ ++ /* convert input stream to CFR at this framerate before inserting additional filters */ ++ AVRational force_cfr; + #if FFMPEG_OPT_TOP + int top_field_first; + #endif +diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c +index 47312c9fe1..7172b137f2 100644 +--- a/fftools/ffmpeg_demux.c ++++ b/fftools/ffmpeg_demux.c +@@ -1002,6 +1002,11 @@ int ist_filter_add(InputStream *ist, InputFilter *ifilter, int is_simple, + opts->flags |= IFILTER_FLAG_CFR; + } else + opts->framerate = av_guess_frame_rate(d->f.ctx, ist->st, NULL); ++ ++ if (ist->force_cfr.num > 0 && ist->force_cfr.den > 0) { ++ opts->force_cfr = ist->force_cfr; ++ opts->flags |= IFILTER_FLAG_CFR; ++ } + } else if (ist->par->codec_type == AVMEDIA_TYPE_SUBTITLE) { + /* Compute the size of the canvas for the subtitles stream. + If the subtitles codecpar has set a size, use it. Otherwise use the +@@ -1210,7 +1215,7 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st) + AVCodecParameters *par = st->codecpar; + DemuxStream *ds; + InputStream *ist; +- char *framerate = NULL, *hwaccel_device = NULL; ++ char *framerate = NULL, *hwaccel_device = NULL, *forcecfr = NULL; + const char *hwaccel = NULL; + char *hwaccel_output_format = NULL; + char *codec_tag = NULL; +@@ -1375,6 +1380,16 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st) + } + } + ++ MATCH_PER_STREAM_OPT(force_cfr, str, forcecfr, ic, st); ++ if (forcecfr) { ++ ret = av_parse_video_rate(&ist->force_cfr, forcecfr); ++ if (ret < 0) { ++ av_log(ist, AV_LOG_ERROR, "Error parsing forced_cfr %s.\n", ++ forcecfr); ++ return ret; ++ } ++ } ++ + #if FFMPEG_OPT_TOP + ist->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st); +diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c +index 171e47be9e..afb7b72a10 100644 +--- a/fftools/ffmpeg_filter.c ++++ b/fftools/ffmpeg_filter.c +@@ -1587,6 +1587,15 @@ static int configure_input_video_filter(FilterGraph *fg, AVFilterGraph *graph, + return ret; + } + ++ if (ifp->opts.force_cfr.num > 0 && ifp->opts.force_cfr.den > 0) { ++ char force_cfr_buf[64]; ++ snprintf(force_cfr_buf, sizeof(force_cfr_buf), "%d/%d", ++ ifp->opts.force_cfr.num, ifp->opts.force_cfr.den); ++ ret = insert_filter(&last_filter, &pad_idx, "fps", force_cfr_buf); ++ if (ret < 0) ++ return ret; ++ } ++ + snprintf(name, sizeof(name), "trim_in_%s", ifp->opts.name); + ret = insert_trim(ifp->opts.trim_start_us, ifp->opts.trim_end_us, + &last_filter, &pad_idx, name); +diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c +index 4b3f9789ba..45e8572030 100644 +--- a/fftools/ffmpeg_opt.c ++++ b/fftools/ffmpeg_opt.c +@@ -1421,7 +1421,7 @@ const OptionDef options[] = { + { "seek_timestamp", OPT_TYPE_INT, OPT_OFFSET | OPT_INPUT | OPT_EXPERT, + { .off = OFFSET(seek_timestamp) }, + "enable/disable seeking by timestamp with -ss" }, +- { "accurate_seek", OPT_TYPE_BOOL, OPT_OFFSET | OPT_EXPERT | OPT_INPUT, ++ { "accurate_seek", OPT_TYPE_BOOL, OPT_OFFSET | OPT_INPUT | OPT_EXPERT, + { .off = OFFSET(accurate_seek) }, + "enable/disable accurate seeking with -ss" }, + { "isync", OPT_TYPE_INT, OPT_OFFSET | OPT_EXPERT | OPT_INPUT, +@@ -1911,5 +1911,9 @@ const OptionDef options[] = { + "set video sync method globally; deprecated, use -fps_mode", "" }, + #endif + ++ { "force_cfr", OPT_TYPE_STRING, OPT_VIDEO | OPT_EXPERT |OPT_PERSTREAM | OPT_INPUT, ++ { .off = OFFSET(force_cfr) }, ++ "set frame rate (Hz value, fraction or abbreviation)", "force_cfr" }, ++ + { NULL, }, + };