@@ -570,10 +570,10 @@ AVPixelFormat validatePixelFormat(
570570 TORCH_CHECK (false , errorMsg.str ());
571571}
572572
573- void validateDoubleOption (
573+ void tryToValidateCodecOption (
574574 const AVCodec& avCodec,
575575 const char * optionName,
576- double value) {
576+ const std::string& value) {
577577 if (!avCodec.priv_class ) {
578578 return ;
579579 }
@@ -586,24 +586,60 @@ void validateDoubleOption(
586586 0 ,
587587 AV_OPT_SEARCH_FAKE_OBJ,
588588 nullptr );
589- // If the option was not found, let FFmpeg handle it later
589+ // If option is not found we cannot validate it , let FFmpeg handle it
590590 if (!option) {
591591 return ;
592592 }
593+ // Validate if option is defined as a numeric type
593594 if (option->type == AV_OPT_TYPE_INT || option->type == AV_OPT_TYPE_INT64 ||
594595 option->type == AV_OPT_TYPE_FLOAT || option->type == AV_OPT_TYPE_DOUBLE) {
595- TORCH_CHECK (
596- value >= option->min && value <= option->max ,
597- optionName,
598- " =" ,
599- value,
600- " is out of valid range [" ,
601- option->min ,
602- " , " ,
603- option->max ,
604- " ] for this codec. For more details, run 'ffmpeg -h encoder=" ,
605- avCodec.name ,
606- " '" );
596+ try {
597+ double numericValue = std::stod (value);
598+ TORCH_CHECK (
599+ numericValue >= option->min && numericValue <= option->max ,
600+ optionName,
601+ " =" ,
602+ numericValue,
603+ " is out of valid range [" ,
604+ option->min ,
605+ " , " ,
606+ option->max ,
607+ " ] for this codec. For more details, run 'ffmpeg -h encoder=" ,
608+ avCodec.name ,
609+ " '" );
610+ } catch (const std::invalid_argument&) {
611+ TORCH_CHECK (
612+ false ,
613+ " Option " ,
614+ optionName,
615+ " expects a numeric value but got '" ,
616+ value,
617+ " '" );
618+ }
619+ }
620+ }
621+
622+ void sortCodecOptions (
623+ const std::map<std::string, std::string>& extraOptions,
624+ AVDictionary** codecDict,
625+ AVDictionary** formatDict) {
626+ // Accepts a map of options as input, then sorts them into codec options and
627+ // format options. The sorted options are returned into two separate dicts.
628+ const AVClass* formatClass = avformat_get_class ();
629+ for (const auto & [key, value] : extraOptions) {
630+ const AVOption* fmtOpt = av_opt_find2 (
631+ &formatClass,
632+ key.c_str (),
633+ nullptr ,
634+ 0 ,
635+ AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ,
636+ nullptr );
637+ if (fmtOpt) {
638+ av_dict_set (formatDict, key.c_str (), value.c_str (), 0 );
639+ } else {
640+ // Default to codec option (includes AVCodecContext + encoder-private)
641+ av_dict_set (codecDict, key.c_str (), value.c_str (), 0 );
642+ }
607643 }
608644}
609645} // namespace
@@ -621,6 +657,7 @@ VideoEncoder::~VideoEncoder() {
621657 avFormatContext_->pb = nullptr ;
622658 }
623659 }
660+ av_dict_free (&avFormatOptions_);
624661}
625662
626663VideoEncoder::VideoEncoder (
@@ -760,21 +797,31 @@ void VideoEncoder::initializeEncoder(
760797 }
761798
762799 // Apply videoStreamOptions
763- AVDictionary* options = nullptr ;
800+ AVDictionary* avCodecOptions = nullptr ;
801+ if (videoStreamOptions.extraOptions .has_value ()) {
802+ for (const auto & [key, value] : videoStreamOptions.extraOptions .value ()) {
803+ tryToValidateCodecOption (*avCodec, key.c_str (), value);
804+ }
805+ sortCodecOptions (
806+ videoStreamOptions.extraOptions .value (),
807+ &avCodecOptions,
808+ &avFormatOptions_);
809+ }
810+
764811 if (videoStreamOptions.crf .has_value ()) {
765- validateDoubleOption (*avCodec, " crf" , videoStreamOptions.crf .value ());
766- av_dict_set (
767- &options,
768- " crf" ,
769- std::to_string (videoStreamOptions.crf .value ()).c_str (),
770- 0 );
812+ std::string crfValue = std::to_string (videoStreamOptions.crf .value ());
813+ tryToValidateCodecOption (*avCodec, " crf" , crfValue);
814+ av_dict_set (&avCodecOptions, " crf" , crfValue.c_str (), 0 );
771815 }
772816 if (videoStreamOptions.preset .has_value ()) {
773817 av_dict_set (
774- &options, " preset" , videoStreamOptions.preset .value ().c_str (), 0 );
818+ &avCodecOptions,
819+ " preset" ,
820+ videoStreamOptions.preset .value ().c_str (),
821+ 0 );
775822 }
776- int status = avcodec_open2 (avCodecContext_.get (), avCodec, &options );
777- av_dict_free (&options );
823+ int status = avcodec_open2 (avCodecContext_.get (), avCodec, &avCodecOptions );
824+ av_dict_free (&avCodecOptions );
778825
779826 TORCH_CHECK (
780827 status == AVSUCCESS,
@@ -799,7 +846,7 @@ void VideoEncoder::encode() {
799846 TORCH_CHECK (!encodeWasCalled_, " Cannot call encode() twice." );
800847 encodeWasCalled_ = true ;
801848
802- int status = avformat_write_header (avFormatContext_.get (), nullptr );
849+ int status = avformat_write_header (avFormatContext_.get (), &avFormatOptions_ );
803850 TORCH_CHECK (
804851 status == AVSUCCESS,
805852 " Error in avformat_write_header: " ,
0 commit comments