@@ -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& e) {
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 (
@@ -687,9 +724,33 @@ VideoEncoder::VideoEncoder(
687724
688725void VideoEncoder::initializeEncoder (
689726 const VideoStreamOptions& videoStreamOptions) {
690- const AVCodec* avCodec =
691- avcodec_find_encoder (avFormatContext_->oformat ->video_codec );
692- TORCH_CHECK (avCodec != nullptr , " Video codec not found" );
727+ const AVCodec* avCodec = nullptr ;
728+ // If codec arg is provided, find codec using logic similar to FFmpeg:
729+ // https://github.com/FFmpeg/FFmpeg/blob/master/fftools/ffmpeg_opt.c#L804-L835
730+ if (videoStreamOptions.codec .has_value ()) {
731+ const std::string& codec = videoStreamOptions.codec .value ();
732+ // Try to find codec by name ("libx264", "libsvtav1")
733+ avCodec = avcodec_find_encoder_by_name (codec.c_str ());
734+ // Try to find by codec descriptor ("h264", "av1")
735+ if (!avCodec) {
736+ const AVCodecDescriptor* desc =
737+ avcodec_descriptor_get_by_name (codec.c_str ());
738+ if (desc) {
739+ avCodec = avcodec_find_encoder (desc->id );
740+ }
741+ }
742+ TORCH_CHECK (
743+ avCodec != nullptr ,
744+ " Video codec " ,
745+ codec,
746+ " not found. To see available codecs, run: ffmpeg -encoders" );
747+ } else {
748+ TORCH_CHECK (
749+ avFormatContext_->oformat != nullptr ,
750+ " Output format is null, unable to find default codec." );
751+ avCodec = avcodec_find_encoder (avFormatContext_->oformat ->video_codec );
752+ TORCH_CHECK (avCodec != nullptr , " Video codec not found" );
753+ }
693754
694755 AVCodecContext* avCodecContext = avcodec_alloc_context3 (avCodec);
695756 TORCH_CHECK (avCodecContext != nullptr , " Couldn't allocate codec context." );
@@ -736,17 +797,31 @@ void VideoEncoder::initializeEncoder(
736797 }
737798
738799 // Apply videoStreamOptions
739- 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+
740811 if (videoStreamOptions.crf .has_value ()) {
741- validateDoubleOption (*avCodec, " crf" , videoStreamOptions.crf .value ());
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 );
815+ }
816+ if (videoStreamOptions.preset .has_value ()) {
742817 av_dict_set (
743- &options ,
744- " crf " ,
745- std::to_string ( videoStreamOptions.crf .value () ).c_str (),
818+ &avCodecOptions ,
819+ " preset " ,
820+ videoStreamOptions.preset .value ().c_str (),
746821 0 );
747822 }
748- int status = avcodec_open2 (avCodecContext_.get (), avCodec, &options );
749- av_dict_free (&options );
823+ int status = avcodec_open2 (avCodecContext_.get (), avCodec, &avCodecOptions );
824+ av_dict_free (&avCodecOptions );
750825
751826 TORCH_CHECK (
752827 status == AVSUCCESS,
@@ -771,7 +846,7 @@ void VideoEncoder::encode() {
771846 TORCH_CHECK (!encodeWasCalled_, " Cannot call encode() twice." );
772847 encodeWasCalled_ = true ;
773848
774- int status = avformat_write_header (avFormatContext_.get (), nullptr );
849+ int status = avformat_write_header (avFormatContext_.get (), &avFormatOptions_ );
775850 TORCH_CHECK (
776851 status == AVSUCCESS,
777852 " Error in avformat_write_header: " ,
0 commit comments