torchcodec 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +29 -0
- data/README.md +69 -0
- data/ext/torchcodec/AVIOContextHolder.cpp +60 -0
- data/ext/torchcodec/AVIOContextHolder.h +64 -0
- data/ext/torchcodec/AVIOTensorContext.cpp +130 -0
- data/ext/torchcodec/AVIOTensorContext.h +44 -0
- data/ext/torchcodec/CpuDeviceInterface.cpp +509 -0
- data/ext/torchcodec/CpuDeviceInterface.h +141 -0
- data/ext/torchcodec/DeviceInterface.cpp +117 -0
- data/ext/torchcodec/DeviceInterface.h +191 -0
- data/ext/torchcodec/Encoder.cpp +1054 -0
- data/ext/torchcodec/Encoder.h +192 -0
- data/ext/torchcodec/FFMPEGCommon.cpp +684 -0
- data/ext/torchcodec/FFMPEGCommon.h +314 -0
- data/ext/torchcodec/FilterGraph.cpp +159 -0
- data/ext/torchcodec/FilterGraph.h +59 -0
- data/ext/torchcodec/Frame.cpp +47 -0
- data/ext/torchcodec/Frame.h +72 -0
- data/ext/torchcodec/Metadata.cpp +124 -0
- data/ext/torchcodec/Metadata.h +92 -0
- data/ext/torchcodec/SingleStreamDecoder.cpp +1586 -0
- data/ext/torchcodec/SingleStreamDecoder.h +391 -0
- data/ext/torchcodec/StableABICompat.h +185 -0
- data/ext/torchcodec/StreamOptions.h +70 -0
- data/ext/torchcodec/Transform.cpp +128 -0
- data/ext/torchcodec/Transform.h +86 -0
- data/ext/torchcodec/ValidationUtils.cpp +35 -0
- data/ext/torchcodec/ValidationUtils.h +21 -0
- data/ext/torchcodec/custom_ops.cpp +913 -0
- data/ext/torchcodec/ext.cpp +12 -0
- data/ext/torchcodec/extconf.rb +73 -0
- data/lib/torchcodec/core/metadata.rb +41 -0
- data/lib/torchcodec/decoders/audio_decoder.rb +88 -0
- data/lib/torchcodec/decoders/decoder_utils.rb +11 -0
- data/lib/torchcodec/version.rb +3 -0
- data/lib/torchcodec.rb +28 -0
- metadata +90 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#include <rice/rice.hpp>
|
|
2
|
+
|
|
3
|
+
void init_core(Rice::Module m);
|
|
4
|
+
|
|
5
|
+
extern "C"
|
|
6
|
+
void Init_ext() {
|
|
7
|
+
auto rb_mTorchCodec = Rice::define_module("TorchCodec");
|
|
8
|
+
|
|
9
|
+
auto rb_mCore = Rice::define_module_under(rb_mTorchCodec, "Core");
|
|
10
|
+
|
|
11
|
+
init_core(rb_mCore);
|
|
12
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "mkmf-rice"
|
|
2
|
+
|
|
3
|
+
$CXXFLAGS += " -std=c++17 $(optflags)"
|
|
4
|
+
|
|
5
|
+
paths = []
|
|
6
|
+
if RbConfig::CONFIG["host_os"] =~ /darwin/i
|
|
7
|
+
if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i
|
|
8
|
+
paths << "/opt/homebrew"
|
|
9
|
+
else
|
|
10
|
+
paths << "/usr/local"
|
|
11
|
+
end
|
|
12
|
+
elsif !Gem.win_platform?
|
|
13
|
+
paths << "/home/linuxbrew/.linuxbrew"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
inc, lib = dir_config("torch")
|
|
17
|
+
inc ||= paths.map { |v| "#{v}/opt/pytorch/include" }.find { |v| Dir.exist?("#{v}/torch") }
|
|
18
|
+
lib ||= paths.map { |v| "#{v}/opt/pytorch/lib" }.find { |v| Dir["#{v}/*torch_cpu*"].any? }
|
|
19
|
+
|
|
20
|
+
unless inc && lib
|
|
21
|
+
abort "LibTorch not found"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
cuda_inc, cuda_lib = dir_config("cuda")
|
|
25
|
+
cuda_lib ||= "/usr/local/cuda/lib64"
|
|
26
|
+
|
|
27
|
+
cudnn_inc, cudnn_lib = dir_config("cudnn")
|
|
28
|
+
cudnn_lib ||= "/usr/local/cuda/lib"
|
|
29
|
+
|
|
30
|
+
$LDFLAGS += " -L#{lib}" if Dir.exist?(lib)
|
|
31
|
+
abort "LibTorch not found" unless have_library("torch")
|
|
32
|
+
|
|
33
|
+
have_library("mkldnn")
|
|
34
|
+
have_library("nnpack")
|
|
35
|
+
|
|
36
|
+
with_cuda = false
|
|
37
|
+
if Dir["#{lib}/*torch_cuda*"].any?
|
|
38
|
+
$LDFLAGS += " -L#{cuda_lib}" if Dir.exist?(cuda_lib)
|
|
39
|
+
$LDFLAGS += " -L#{cudnn_lib}" if Dir.exist?(cudnn_lib) && cudnn_lib != cuda_lib
|
|
40
|
+
with_cuda = have_library("cuda") && have_library("cudnn")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
$INCFLAGS += " -I#{inc}"
|
|
44
|
+
$INCFLAGS += " -I#{inc}/torch/csrc/api/include"
|
|
45
|
+
|
|
46
|
+
$LDFLAGS += " -Wl,-rpath,#{lib}"
|
|
47
|
+
$LDFLAGS += ":#{cuda_lib}/stubs:#{cuda_lib}" if with_cuda
|
|
48
|
+
|
|
49
|
+
# https://github.com/pytorch/pytorch/blob/v2.9.0/torch/utils/cpp_extension.py#L1351-L1364
|
|
50
|
+
$LDFLAGS += " -lc10 -ltorch_cpu -ltorch"
|
|
51
|
+
if with_cuda
|
|
52
|
+
$LDFLAGS += " -lcuda -lnvrtc"
|
|
53
|
+
$LDFLAGS += " -lnvToolsExt" if File.exist?("#{cuda_lib}/libnvToolsExt.so")
|
|
54
|
+
$LDFLAGS += " -lcudart -lc10_cuda -ltorch_cuda -lcufft -lcurand -lcublas -lcudnn"
|
|
55
|
+
# TODO figure out why this is needed
|
|
56
|
+
$LDFLAGS += " -Wl,--no-as-needed,#{lib}/libtorch.so"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
ffmpeg_inc, ffmpeg_lib = dir_config("ffmpeg")
|
|
60
|
+
ffmpeg_inc ||= paths.map { |v| "#{v}/opt/ffmpeg/include" }.find { |v| File.exist?("#{v}/libavcodec/avcodec.h") }
|
|
61
|
+
ffmpeg_lib ||= paths.map { |v| "#{v}/opt/ffmpeg/lib" }.find { |v| Dir["#{v}/*libavcodec*"].any? }
|
|
62
|
+
|
|
63
|
+
ffmpeg_incs = ffmpeg_inc ? [ffmpeg_inc] : []
|
|
64
|
+
ffmpeg_libs = ffmpeg_lib ? [ffmpeg_lib] : []
|
|
65
|
+
|
|
66
|
+
abort "libavcodec not found" unless find_header("libavcodec/avcodec.h", *ffmpeg_incs)
|
|
67
|
+
abort "libavcodec not found" unless find_library("avcodec", nil, *ffmpeg_libs)
|
|
68
|
+
abort "libavdevice not found" unless find_library("avdevice", nil, *ffmpeg_libs)
|
|
69
|
+
abort "libavfilter not found" unless find_library("avfilter", nil, *ffmpeg_libs)
|
|
70
|
+
abort "libavutil not found" unless find_library("avutil", nil, *ffmpeg_libs)
|
|
71
|
+
|
|
72
|
+
# create makefile
|
|
73
|
+
create_makefile("torchcodec/ext")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module TorchCodec
|
|
2
|
+
module Core
|
|
3
|
+
def self.get_container_metadata(decoder)
|
|
4
|
+
container_dict = JSON.parse(_get_container_json_metadata(decoder))
|
|
5
|
+
streams_metadata = []
|
|
6
|
+
|
|
7
|
+
container_dict["numStreams"].times do |stream_index|
|
|
8
|
+
stream_dict = JSON.parse(_get_stream_json_metadata(decoder, stream_index))
|
|
9
|
+
common_meta = {
|
|
10
|
+
duration_seconds_from_header: stream_dict["durationSecondsFromHeader"],
|
|
11
|
+
duration_seconds: stream_dict["durationSeconds"],
|
|
12
|
+
bit_rate: stream_dict["bitRate"],
|
|
13
|
+
begin_stream_seconds_from_header: stream_dict["beginStreamSecondsFromHeader"],
|
|
14
|
+
begin_stream_seconds: stream_dict["beginStreamSeconds"],
|
|
15
|
+
codec: stream_dict["codec"],
|
|
16
|
+
stream_index: stream_index
|
|
17
|
+
}
|
|
18
|
+
if stream_dict["mediaType"] == "video"
|
|
19
|
+
raise Todo
|
|
20
|
+
elsif stream_dict["mediaType"] == "audio"
|
|
21
|
+
streams_metadata << {
|
|
22
|
+
sample_rate: stream_dict["sampleRate"],
|
|
23
|
+
num_channels: stream_dict["numChannels"],
|
|
24
|
+
sample_format: stream_dict["sampleFormat"],
|
|
25
|
+
**common_meta
|
|
26
|
+
}
|
|
27
|
+
else
|
|
28
|
+
raise Todo
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
duration_seconds_from_header: container_dict["durationSecondsFromHeader"],
|
|
34
|
+
bit_rate_from_header: container_dict["bitRate"],
|
|
35
|
+
best_video_stream_index: container_dict["bestVideoStreamIndex"],
|
|
36
|
+
best_audio_stream_index: container_dict["bestAudioStreamIndex"],
|
|
37
|
+
streams: streams_metadata
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module TorchCodec
|
|
2
|
+
module Decoders
|
|
3
|
+
class AudioDecoder
|
|
4
|
+
attr_reader :metadata
|
|
5
|
+
|
|
6
|
+
def initialize(
|
|
7
|
+
source,
|
|
8
|
+
stream_index: nil,
|
|
9
|
+
sample_rate: nil,
|
|
10
|
+
num_channels: nil
|
|
11
|
+
)
|
|
12
|
+
@decoder = Decoders.create_decoder(source, "approximate")
|
|
13
|
+
|
|
14
|
+
container_metadata = Core.get_container_metadata(@decoder)
|
|
15
|
+
@stream_index =
|
|
16
|
+
stream_index.nil? ? container_metadata[:best_audio_stream_index] : stream_index
|
|
17
|
+
if @stream_index.nil?
|
|
18
|
+
raise ArgumentError, "The best audio stream is unknown and there is no specified stream."
|
|
19
|
+
end
|
|
20
|
+
if @stream_index >= container_metadata[:streams].length
|
|
21
|
+
raise ArgumentError, "The stream at index #{@stream_index} is not a valid stream."
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@metadata = container_metadata[:streams][@stream_index]
|
|
25
|
+
if !@metadata.key?(:sample_rate)
|
|
26
|
+
raise ArgumentError, "The stream at index #{@stream_index} is not an audio stream."
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@desired_sample_rate =
|
|
30
|
+
!sample_rate.nil? ? sample_rate : @metadata[:sample_rate]
|
|
31
|
+
|
|
32
|
+
Core.add_audio_stream(
|
|
33
|
+
@decoder,
|
|
34
|
+
stream_index,
|
|
35
|
+
sample_rate,
|
|
36
|
+
num_channels
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_all_samples
|
|
41
|
+
get_samples_played_in_range
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_samples_played_in_range(start_seconds: 0.0, stop_seconds: nil)
|
|
45
|
+
if !stop_seconds.nil? && !(start_seconds <= stop_seconds)
|
|
46
|
+
raise ArgumentError, "Invalid start seconds: #{start_seconds}. It must be less than or equal to stop seconds (#{stop_seconds})."
|
|
47
|
+
end
|
|
48
|
+
frames, first_pts = Core.get_frames_by_pts_in_range_audio(
|
|
49
|
+
@decoder,
|
|
50
|
+
start_seconds,
|
|
51
|
+
stop_seconds
|
|
52
|
+
)
|
|
53
|
+
first_pts = first_pts.item
|
|
54
|
+
|
|
55
|
+
sample_rate = @desired_sample_rate
|
|
56
|
+
# TODO: metadata's sample_rate should probably not be Optional
|
|
57
|
+
raise if sample_rate.nil?
|
|
58
|
+
|
|
59
|
+
if first_pts < start_seconds
|
|
60
|
+
offset_beginning = ((start_seconds - first_pts) * sample_rate).round
|
|
61
|
+
output_pts_seconds = start_seconds
|
|
62
|
+
else
|
|
63
|
+
# In normal cases we'll have first_pts <= start_pts, but in some
|
|
64
|
+
# edge cases it's possible to have first_pts > start_seconds,
|
|
65
|
+
# typically if the stream's first frame's pts isn't exactly 0.
|
|
66
|
+
offset_beginning = 0
|
|
67
|
+
output_pts_seconds = first_pts
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
num_samples = frames.shape[1]
|
|
71
|
+
last_pts = first_pts + num_samples / sample_rate
|
|
72
|
+
if !stop_seconds.nil? && stop_seconds < last_pts
|
|
73
|
+
offset_end = num_samples - ((last_pts - stop_seconds) * sample_rate).round
|
|
74
|
+
else
|
|
75
|
+
offset_end = num_samples
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
data = frames[0.., offset_beginning...offset_end]
|
|
79
|
+
{
|
|
80
|
+
data: data,
|
|
81
|
+
pts_seconds: output_pts_seconds,
|
|
82
|
+
duration_seconds: data.shape[1] / sample_rate.to_f,
|
|
83
|
+
sample_rate: sample_rate
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/torchcodec.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# dependencies
|
|
2
|
+
require "torch"
|
|
3
|
+
|
|
4
|
+
# ext
|
|
5
|
+
require "torchcodec/ext"
|
|
6
|
+
|
|
7
|
+
# stdlib
|
|
8
|
+
require "json"
|
|
9
|
+
|
|
10
|
+
# modules
|
|
11
|
+
require_relative "torchcodec/version"
|
|
12
|
+
|
|
13
|
+
# core
|
|
14
|
+
require_relative "torchcodec/core/metadata"
|
|
15
|
+
|
|
16
|
+
# decoders
|
|
17
|
+
require_relative "torchcodec/decoders/audio_decoder"
|
|
18
|
+
require_relative "torchcodec/decoders/decoder_utils"
|
|
19
|
+
|
|
20
|
+
module TorchCodec
|
|
21
|
+
class Error < StandardError; end
|
|
22
|
+
|
|
23
|
+
class Todo < Error
|
|
24
|
+
def message
|
|
25
|
+
"not implemented yet"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: torchcodec
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andrew Kane
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: torch-rb
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.23'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.23'
|
|
26
|
+
email: andrew@ankane.org
|
|
27
|
+
executables: []
|
|
28
|
+
extensions:
|
|
29
|
+
- ext/torchcodec/extconf.rb
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- CHANGELOG.md
|
|
33
|
+
- LICENSE.txt
|
|
34
|
+
- README.md
|
|
35
|
+
- ext/torchcodec/AVIOContextHolder.cpp
|
|
36
|
+
- ext/torchcodec/AVIOContextHolder.h
|
|
37
|
+
- ext/torchcodec/AVIOTensorContext.cpp
|
|
38
|
+
- ext/torchcodec/AVIOTensorContext.h
|
|
39
|
+
- ext/torchcodec/CpuDeviceInterface.cpp
|
|
40
|
+
- ext/torchcodec/CpuDeviceInterface.h
|
|
41
|
+
- ext/torchcodec/DeviceInterface.cpp
|
|
42
|
+
- ext/torchcodec/DeviceInterface.h
|
|
43
|
+
- ext/torchcodec/Encoder.cpp
|
|
44
|
+
- ext/torchcodec/Encoder.h
|
|
45
|
+
- ext/torchcodec/FFMPEGCommon.cpp
|
|
46
|
+
- ext/torchcodec/FFMPEGCommon.h
|
|
47
|
+
- ext/torchcodec/FilterGraph.cpp
|
|
48
|
+
- ext/torchcodec/FilterGraph.h
|
|
49
|
+
- ext/torchcodec/Frame.cpp
|
|
50
|
+
- ext/torchcodec/Frame.h
|
|
51
|
+
- ext/torchcodec/Metadata.cpp
|
|
52
|
+
- ext/torchcodec/Metadata.h
|
|
53
|
+
- ext/torchcodec/SingleStreamDecoder.cpp
|
|
54
|
+
- ext/torchcodec/SingleStreamDecoder.h
|
|
55
|
+
- ext/torchcodec/StableABICompat.h
|
|
56
|
+
- ext/torchcodec/StreamOptions.h
|
|
57
|
+
- ext/torchcodec/Transform.cpp
|
|
58
|
+
- ext/torchcodec/Transform.h
|
|
59
|
+
- ext/torchcodec/ValidationUtils.cpp
|
|
60
|
+
- ext/torchcodec/ValidationUtils.h
|
|
61
|
+
- ext/torchcodec/custom_ops.cpp
|
|
62
|
+
- ext/torchcodec/ext.cpp
|
|
63
|
+
- ext/torchcodec/extconf.rb
|
|
64
|
+
- lib/torchcodec.rb
|
|
65
|
+
- lib/torchcodec/core/metadata.rb
|
|
66
|
+
- lib/torchcodec/decoders/audio_decoder.rb
|
|
67
|
+
- lib/torchcodec/decoders/decoder_utils.rb
|
|
68
|
+
- lib/torchcodec/version.rb
|
|
69
|
+
homepage: https://github.com/ankane/torchcodec-ruby
|
|
70
|
+
licenses:
|
|
71
|
+
- BSD-3-Clause
|
|
72
|
+
metadata: {}
|
|
73
|
+
rdoc_options: []
|
|
74
|
+
require_paths:
|
|
75
|
+
- lib
|
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '3.2'
|
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
requirements: []
|
|
87
|
+
rubygems_version: 4.0.3
|
|
88
|
+
specification_version: 4
|
|
89
|
+
summary: Media encoding and decoding for Torch.rb
|
|
90
|
+
test_files: []
|