Skip to content

Commit 89a0964

Browse files
author
pytorchbot
committed
2025-11-19 nightly release (1ea235a)
1 parent 1da1dcd commit 89a0964

File tree

5 files changed

+63
-28
lines changed

5 files changed

+63
-28
lines changed

src/torchcodec/_core/Encoder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ void tryToValidateCodecOption(
607607
"] for this codec. For more details, run 'ffmpeg -h encoder=",
608608
avCodec.name,
609609
"'");
610-
} catch (const std::invalid_argument& e) {
610+
} catch (const std::invalid_argument&) {
611611
TORCH_CHECK(
612612
false,
613613
"Option ",

src/torchcodec/_core/custom_ops.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -613,14 +613,14 @@ void encode_video_to_file(
613613
const at::Tensor& frames,
614614
int64_t frame_rate,
615615
std::string_view file_name,
616-
std::optional<std::string> codec = std::nullopt,
616+
std::optional<std::string_view> codec = std::nullopt,
617617
std::optional<std::string_view> pixel_format = std::nullopt,
618618
std::optional<double> crf = std::nullopt,
619619
std::optional<std::string_view> preset = std::nullopt,
620620
std::optional<std::vector<std::string>> extra_options = std::nullopt) {
621621
VideoStreamOptions videoStreamOptions;
622-
videoStreamOptions.codec = codec;
623-
videoStreamOptions.pixelFormat = pixel_format;
622+
videoStreamOptions.codec = std::move(codec);
623+
videoStreamOptions.pixelFormat = std::move(pixel_format);
624624
videoStreamOptions.crf = crf;
625625
videoStreamOptions.preset = preset;
626626

@@ -641,15 +641,15 @@ at::Tensor encode_video_to_tensor(
641641
const at::Tensor& frames,
642642
int64_t frame_rate,
643643
std::string_view format,
644-
std::optional<std::string> codec = std::nullopt,
644+
std::optional<std::string_view> codec = std::nullopt,
645645
std::optional<std::string_view> pixel_format = std::nullopt,
646646
std::optional<double> crf = std::nullopt,
647647
std::optional<std::string_view> preset = std::nullopt,
648648
std::optional<std::vector<std::string>> extra_options = std::nullopt) {
649649
auto avioContextHolder = std::make_unique<AVIOToTensorContext>();
650650
VideoStreamOptions videoStreamOptions;
651-
videoStreamOptions.codec = codec;
652-
videoStreamOptions.pixelFormat = pixel_format;
651+
videoStreamOptions.codec = std::move(codec);
652+
videoStreamOptions.pixelFormat = std::move(pixel_format);
653653
videoStreamOptions.crf = crf;
654654
videoStreamOptions.preset = preset;
655655

@@ -672,7 +672,7 @@ void _encode_video_to_file_like(
672672
int64_t frame_rate,
673673
std::string_view format,
674674
int64_t file_like_context,
675-
std::optional<std::string> codec = std::nullopt,
675+
std::optional<std::string_view> codec = std::nullopt,
676676
std::optional<std::string_view> pixel_format = std::nullopt,
677677
std::optional<double> crf = std::nullopt,
678678
std::optional<std::string_view> preset = std::nullopt,
@@ -684,8 +684,8 @@ void _encode_video_to_file_like(
684684
std::unique_ptr<AVIOFileLikeContext> avioContextHolder(fileLikeContext);
685685

686686
VideoStreamOptions videoStreamOptions;
687-
videoStreamOptions.codec = codec;
688-
videoStreamOptions.pixelFormat = pixel_format;
687+
videoStreamOptions.codec = std::move(codec);
688+
videoStreamOptions.pixelFormat = std::move(pixel_format);
689689
videoStreamOptions.crf = crf;
690690
videoStreamOptions.preset = preset;
691691

src/torchcodec/_core/ops.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def encode_video_to_file_abstract(
331331
frames: torch.Tensor,
332332
frame_rate: int,
333333
filename: str,
334-
codec: Optional[str],
334+
codec: Optional[str] = None,
335335
pixel_format: Optional[str] = None,
336336
preset: Optional[str] = None,
337337
crf: Optional[Union[int, float]] = None,
@@ -345,7 +345,7 @@ def encode_video_to_tensor_abstract(
345345
frames: torch.Tensor,
346346
frame_rate: int,
347347
format: str,
348-
codec: Optional[str],
348+
codec: Optional[str] = None,
349349
pixel_format: Optional[str] = None,
350350
preset: Optional[str] = None,
351351
crf: Optional[Union[int, float]] = None,
@@ -360,7 +360,7 @@ def _encode_video_to_file_like_abstract(
360360
frame_rate: int,
361361
format: str,
362362
file_like_context: int,
363-
codec: Optional[str],
363+
codec: Optional[str] = None,
364364
pixel_format: Optional[str] = None,
365365
preset: Optional[str] = None,
366366
crf: Optional[Union[int, float]] = None,

src/torchcodec/encoders/_video_encoder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ def __init__(self, frames: Tensor, *, frame_rate: int):
3535
def to_file(
3636
self,
3737
dest: Union[str, Path],
38-
extra_options: Optional[Dict[str, Any]] = None,
3938
*,
4039
codec: Optional[str] = None,
4140
pixel_format: Optional[str] = None,
4241
crf: Optional[Union[int, float]] = None,
4342
preset: Optional[Union[str, int]] = None,
43+
extra_options: Optional[Dict[str, Any]] = None,
4444
) -> None:
4545
"""Encode frames into a file.
4646

test/test_encoders.py

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,10 @@ def test_against_to_file(self, tmp_path, format, method):
919919
{"pixel_format": "yuv420p", "crf": None, "preset": None},
920920
],
921921
)
922-
def test_video_encoder_against_ffmpeg_cli(self, tmp_path, format, encode_params):
922+
@pytest.mark.parametrize("method", ("to_file", "to_tensor", "to_file_like"))
923+
def test_video_encoder_against_ffmpeg_cli(
924+
self, tmp_path, format, encode_params, method
925+
):
923926
ffmpeg_version = get_ffmpeg_major_version()
924927
if format == "webm" and (
925928
ffmpeg_version == 4 or (IS_WINDOWS and ffmpeg_version in (6, 7))
@@ -967,26 +970,45 @@ def test_video_encoder_against_ffmpeg_cli(self, tmp_path, format, encode_params)
967970
# Output path must be last
968971
ffmpeg_cmd.append(ffmpeg_encoded_path)
969972
subprocess.run(ffmpeg_cmd, check=True)
973+
ffmpeg_frames = self.decode(ffmpeg_encoded_path).data
970974

971975
# Encode with our video encoder
972-
encoder_output_path = str(tmp_path / f"encoder_output.{format}")
973976
encoder = VideoEncoder(frames=source_frames, frame_rate=frame_rate)
974-
encoder.to_file(
975-
dest=encoder_output_path,
976-
pixel_format=pixel_format,
977-
crf=crf,
978-
preset=preset,
979-
)
977+
encoder_output_path = str(tmp_path / f"encoder_output.{format}")
980978

981-
ffmpeg_frames = self.decode(ffmpeg_encoded_path).data
982-
encoder_frames = self.decode(encoder_output_path).data
979+
if method == "to_file":
980+
encoder.to_file(
981+
dest=encoder_output_path,
982+
pixel_format=pixel_format,
983+
crf=crf,
984+
preset=preset,
985+
)
986+
encoder_frames = self.decode(encoder_output_path).data
987+
elif method == "to_tensor":
988+
encoded_output = encoder.to_tensor(
989+
format=format,
990+
pixel_format=pixel_format,
991+
crf=crf,
992+
preset=preset,
993+
)
994+
encoder_frames = self.decode(encoded_output).data
995+
elif method == "to_file_like":
996+
file_like = io.BytesIO()
997+
encoder.to_file_like(
998+
file_like=file_like,
999+
format=format,
1000+
pixel_format=pixel_format,
1001+
crf=crf,
1002+
preset=preset,
1003+
)
1004+
encoder_frames = self.decode(file_like.getvalue()).data
1005+
else:
1006+
raise ValueError(f"Unknown method: {method}")
9831007

9841008
assert ffmpeg_frames.shape[0] == encoder_frames.shape[0]
9851009

986-
# If FFmpeg selects a codec or pixel format that uses qscale (not crf),
987-
# the VideoEncoder outputs *slightly* different frames.
988-
# There may be additional subtle differences in the encoder.
989-
percentage = 94 if ffmpeg_version == 6 or format == "avi" else 99
1010+
# MPEG codec used for avi format does not accept CRF
1011+
percentage = 94 if format == "avi" else 99
9901012

9911013
# Check that PSNR between both encoded versions is high
9921014
for ff_frame, enc_frame in zip(ffmpeg_frames, encoder_frames):
@@ -996,6 +1018,19 @@ def test_video_encoder_against_ffmpeg_cli(self, tmp_path, format, encode_params)
9961018
ff_frame, enc_frame, percentage=percentage, atol=2
9971019
)
9981020

1021+
# Check that video metadata is the same
1022+
if method == "to_file":
1023+
fields = ["duration", "duration_ts", "r_frame_rate", "nb_frames"]
1024+
ffmpeg_metadata = self._get_video_metadata(
1025+
ffmpeg_encoded_path,
1026+
fields=fields,
1027+
)
1028+
encoder_metadata = self._get_video_metadata(
1029+
encoder_output_path,
1030+
fields=fields,
1031+
)
1032+
assert ffmpeg_metadata == encoder_metadata
1033+
9991034
def test_to_file_like_custom_file_object(self):
10001035
"""Test to_file_like with a custom file-like object that implements write and seek."""
10011036

0 commit comments

Comments
 (0)