mp4-rb 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.
Files changed (155) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +24 -0
  3. data/Gemfile.lock +149 -0
  4. data/README.md +195 -0
  5. data/Rakefile +8 -0
  6. data/bin/build_mp4_xlsx.rb +35 -0
  7. data/bin/divide_mp4_to_segments.rb +52 -0
  8. data/lib/m4s/binary/ftyp.rb +11 -0
  9. data/lib/m4s/binary/mdat.rb +0 -0
  10. data/lib/m4s/binary/mfhd.rb +18 -0
  11. data/lib/m4s/binary/mvhd.rb +39 -0
  12. data/lib/m4s/binary/sidx.rb +38 -0
  13. data/lib/m4s/binary/styp.rb +17 -0
  14. data/lib/m4s/binary/tfdt.rb +25 -0
  15. data/lib/m4s/binary/tfhd.rb +38 -0
  16. data/lib/m4s/binary/trex.rb +25 -0
  17. data/lib/m4s/binary/trun.rb +30 -0
  18. data/lib/m4s/dash/manifest.rb +149 -0
  19. data/lib/m4s/layout/atom.rb +44 -0
  20. data/lib/m4s/layout/builder.rb +32 -0
  21. data/lib/m4s/layout/container.rb +59 -0
  22. data/lib/m4s/layout/ctts.rb +20 -0
  23. data/lib/m4s/layout/dinf.rb +17 -0
  24. data/lib/m4s/layout/elst.rb +33 -0
  25. data/lib/m4s/layout/ftyp.rb +21 -0
  26. data/lib/m4s/layout/hdlr.rb +22 -0
  27. data/lib/m4s/layout/init_mp4_builder.rb +57 -0
  28. data/lib/m4s/layout/mdat.rb +61 -0
  29. data/lib/m4s/layout/mdhd.rb +25 -0
  30. data/lib/m4s/layout/mfhd.rb +17 -0
  31. data/lib/m4s/layout/mvhd.rb +32 -0
  32. data/lib/m4s/layout/sidx.rb +36 -0
  33. data/lib/m4s/layout/stco.rb +21 -0
  34. data/lib/m4s/layout/stsc.rb +20 -0
  35. data/lib/m4s/layout/stsd.rb +17 -0
  36. data/lib/m4s/layout/stss.rb +20 -0
  37. data/lib/m4s/layout/stsz.rb +19 -0
  38. data/lib/m4s/layout/stts.rb +20 -0
  39. data/lib/m4s/layout/styp.rb +17 -0
  40. data/lib/m4s/layout/tfdt.rb +21 -0
  41. data/lib/m4s/layout/tfhd.rb +17 -0
  42. data/lib/m4s/layout/tkhd.rb +34 -0
  43. data/lib/m4s/layout/trex.rb +33 -0
  44. data/lib/m4s/layout/trun.rb +89 -0
  45. data/lib/m4s/layout/vsmhd.rb +51 -0
  46. data/lib/m4s/m3u8/general_playlist.rb +52 -0
  47. data/lib/m4s/m3u8/media_playlist.rb +32 -0
  48. data/lib/mp4/binary/avc1.rb +98 -0
  49. data/lib/mp4/binary/box_head.rb +8 -0
  50. data/lib/mp4/binary/co64.rb +11 -0
  51. data/lib/mp4/binary/codec.rb +8 -0
  52. data/lib/mp4/binary/ctts.rb +14 -0
  53. data/lib/mp4/binary/ctts_reader.rb +5 -0
  54. data/lib/mp4/binary/dinf.rb +5 -0
  55. data/lib/mp4/binary/dref.rb +5 -0
  56. data/lib/mp4/binary/elst.rb +16 -0
  57. data/lib/mp4/binary/fixed_point16.rb +19 -0
  58. data/lib/mp4/binary/fixed_point8.rb +19 -0
  59. data/lib/mp4/binary/ftyp.rb +9 -0
  60. data/lib/mp4/binary/gmhd.rb +5 -0
  61. data/lib/mp4/binary/hdlr.rb +13 -0
  62. data/lib/mp4/binary/hev1.rb +73 -0
  63. data/lib/mp4/binary/hmhd.rb +14 -0
  64. data/lib/mp4/binary/iods.rb +5 -0
  65. data/lib/mp4/binary/mdhd.rb +18 -0
  66. data/lib/mp4/binary/mp4a.rb +76 -0
  67. data/lib/mp4/binary/mvhd.rb +39 -0
  68. data/lib/mp4/binary/nmhd.rb +8 -0
  69. data/lib/mp4/binary/saio.rb +25 -0
  70. data/lib/mp4/binary/saiz.rb +17 -0
  71. data/lib/mp4/binary/sbgp.rb +5 -0
  72. data/lib/mp4/binary/sdtp.rb +18 -0
  73. data/lib/mp4/binary/senc.rb +12 -0
  74. data/lib/mp4/binary/sgpd.rb +5 -0
  75. data/lib/mp4/binary/smhd.rb +11 -0
  76. data/lib/mp4/binary/stco.rb +13 -0
  77. data/lib/mp4/binary/stco_reader.rb +5 -0
  78. data/lib/mp4/binary/sthd.rb +8 -0
  79. data/lib/mp4/binary/stsc.rb +15 -0
  80. data/lib/mp4/binary/stsc_reader.rb +5 -0
  81. data/lib/mp4/binary/stsd.rb +5 -0
  82. data/lib/mp4/binary/stsd_atom.rb +11 -0
  83. data/lib/mp4/binary/stsd_reader.rb +5 -0
  84. data/lib/mp4/binary/stss.rb +13 -0
  85. data/lib/mp4/binary/stss_reader.rb +5 -0
  86. data/lib/mp4/binary/stsz.rb +18 -0
  87. data/lib/mp4/binary/stsz_reader.rb +5 -0
  88. data/lib/mp4/binary/stts.rb +14 -0
  89. data/lib/mp4/binary/stts_reader.rb +5 -0
  90. data/lib/mp4/binary/tkhd.rb +46 -0
  91. data/lib/mp4/binary/udta.rb +5 -0
  92. data/lib/mp4/binary/vmhd.rb +11 -0
  93. data/lib/mp4/cache/memory.rb +328 -0
  94. data/lib/mp4/cache/sqlite.rb +348 -0
  95. data/lib/mp4/cache.rb +13 -0
  96. data/lib/mp4/constants.rb +87 -0
  97. data/lib/mp4/ext/refinements.rb +19 -0
  98. data/lib/mp4/layout/atom.rb +47 -0
  99. data/lib/mp4/layout/base.rb +10 -0
  100. data/lib/mp4/layout/builder.rb +16 -0
  101. data/lib/mp4/layout/container.rb +59 -0
  102. data/lib/mp4/layout/ctts.rb +35 -0
  103. data/lib/mp4/layout/dinf.rb +15 -0
  104. data/lib/mp4/layout/elst.rb +33 -0
  105. data/lib/mp4/layout/free.rb +20 -0
  106. data/lib/mp4/layout/ftyp.rb +21 -0
  107. data/lib/mp4/layout/hdlr.rb +20 -0
  108. data/lib/mp4/layout/mdat.rb +33 -0
  109. data/lib/mp4/layout/mdhd.rb +27 -0
  110. data/lib/mp4/layout/moov.rb +58 -0
  111. data/lib/mp4/layout/mvhd.rb +46 -0
  112. data/lib/mp4/layout/stco.rb +37 -0
  113. data/lib/mp4/layout/stsc.rb +27 -0
  114. data/lib/mp4/layout/stsd.rb +15 -0
  115. data/lib/mp4/layout/stss.rb +22 -0
  116. data/lib/mp4/layout/stsz.rb +39 -0
  117. data/lib/mp4/layout/stts.rb +35 -0
  118. data/lib/mp4/layout/tkhd.rb +45 -0
  119. data/lib/mp4/layout/vsmhd.rb +49 -0
  120. data/lib/mp4/layout.rb +3 -0
  121. data/lib/mp4/metadata/memoize/ftyp.rb +13 -0
  122. data/lib/mp4/metadata/memoize/stbl/binary_stsd.rb +13 -0
  123. data/lib/mp4/metadata/memoize/stbl/chunks.rb +46 -0
  124. data/lib/mp4/metadata/memoize/stbl/codec.rb +82 -0
  125. data/lib/mp4/metadata/memoize/stbl/composition_offset_calculator.rb +31 -0
  126. data/lib/mp4/metadata/memoize/stbl/delta_calculator.rb +29 -0
  127. data/lib/mp4/metadata/memoize/stbl/sample_to_chunk_calculator.rb +63 -0
  128. data/lib/mp4/metadata/memoize/stbl/samples.rb +80 -0
  129. data/lib/mp4/metadata/memoize/track.rb +98 -0
  130. data/lib/mp4/metadata/memoize/tracks.rb +11 -0
  131. data/lib/mp4/metadata/service.rb +30 -0
  132. data/lib/mp4/metadata/summary.rb +11 -0
  133. data/lib/mp4/nolan/service/sound.rb +36 -0
  134. data/lib/mp4/nolan/service/track.rb +9 -0
  135. data/lib/mp4/nolan/service/video.rb +35 -0
  136. data/lib/mp4/nolan/service.rb +84 -0
  137. data/lib/mp4/nolan.rb +3 -0
  138. data/lib/mp4/parser/main_boxes.rb +43 -0
  139. data/lib/mp4/parser/moov.rb +40 -0
  140. data/lib/mp4/parser/service.rb +47 -0
  141. data/lib/mp4/parser/track.rb +56 -0
  142. data/lib/mp4/rb/version.rb +7 -0
  143. data/lib/mp4/rb.rb +10 -0
  144. data/lib/mp4/source/base.rb +20 -0
  145. data/lib/mp4/source/local.rb +79 -0
  146. data/lib/mp4/source/s3.rb +118 -0
  147. data/lib/mp4/source/url.rb +130 -0
  148. data/lib/mp4/source.rb +23 -0
  149. data/lib/mp4/structure/service.rb +13 -0
  150. data/lib/mp4/xlsx/builder.rb +44 -0
  151. data/lib/mp4.rb +222 -0
  152. data/mp4-rb.gemspec +68 -0
  153. data/sig/mp4/rb.rbs +6 -0
  154. data/todo.txt +4 -0
  155. metadata +323 -0
@@ -0,0 +1,130 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module MP4::Source
5
+ class Url
6
+ include Base
7
+
8
+ attr_reader :url
9
+
10
+ def self.from_uri(uri)
11
+ unless uri.start_with?('http://') || uri.start_with?('https://')
12
+ raise ArgumentError, "expected http(s):// URI, got #{uri.inspect}"
13
+ end
14
+
15
+ new(uri)
16
+ end
17
+
18
+ def initialize(url)
19
+ @url = url
20
+ @position = 0
21
+ @eof = false
22
+ @file_size = fetch_file_size
23
+ end
24
+
25
+ def uri
26
+ @url
27
+ end
28
+
29
+ def read(n)
30
+ return '' if eof?
31
+
32
+ range_header = "bytes=#{@position}-#{@position + n - 1}"
33
+ response = http_get(range_header)
34
+
35
+ if response.is_a?(Net::HTTPSuccess)
36
+ data = response.body
37
+ @position += data.size
38
+ data
39
+ else
40
+ @eof = true
41
+ ''
42
+ end
43
+ end
44
+
45
+ def fetch(from, to)
46
+ range = "bytes=#{from}-#{to}"
47
+ response = http_get(range)
48
+
49
+ if response.is_a?(Net::HTTPSuccess)
50
+ response.body
51
+ else
52
+ raise "Unable to fetch range"
53
+ end
54
+ end
55
+
56
+ def set_seek(n)
57
+ @position = n
58
+ @eof = false if n < @file_size
59
+ end
60
+
61
+ def reset_seek
62
+ @position = 0
63
+ @eof = false
64
+ end
65
+
66
+ def open!
67
+ self
68
+ end
69
+
70
+ def eof?
71
+ @position >= @file_size
72
+ end
73
+
74
+ def pos
75
+ @position
76
+ end
77
+
78
+ def fetch_ranges(ranges)
79
+ range_header = "bytes=#{ranges.join(',')}"
80
+ response = http_get(range_header)
81
+
82
+ if response.is_a?(Net::HTTPSuccess)
83
+ parse_multipart_response(response.body, response['Content-Type'])
84
+ else
85
+ raise "Unable to fetch ranges"
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def fetch_file_size
92
+ uri = URI.parse(@url)
93
+ request = Net::HTTP::Get.new(uri)
94
+ request['Range'] = 'bytes=0-0'
95
+
96
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
97
+ response = http.request(request)
98
+ if response.is_a?(Net::HTTPSuccess)
99
+ content_range = response['Content-Range']
100
+ if content_range
101
+ return content_range.split('/').last.to_i
102
+ else
103
+ raise "Unable to fetch file size"
104
+ end
105
+ else
106
+ raise "Unable to fetch file size"
107
+ end
108
+ end
109
+ end
110
+
111
+ def http_get(range_header)
112
+ uri = URI.parse(@url)
113
+ request = Net::HTTP::Get.new(uri)
114
+ request['Range'] = range_header
115
+
116
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
117
+ http.request(request)
118
+ end
119
+ end
120
+
121
+ def parse_multipart_response(body, content_type)
122
+ boundary = content_type.match(/boundary=(.*)$/)[1]
123
+ parts = body.split("--#{boundary}\r\n").reject { |part| part.strip.empty? || part == "--" }
124
+ parts.map do |part|
125
+ _, content = part.split("\r\n\r\n", 2)
126
+ content.split("\r\n--")[0] # Remove any trailing boundary marker
127
+ end
128
+ end
129
+ end
130
+ end
data/lib/mp4/source.rb ADDED
@@ -0,0 +1,23 @@
1
+ module MP4::Source
2
+ SCHEMES = {
3
+ 'local' => 'MP4::Source::Local',
4
+ 'http' => 'MP4::Source::Url',
5
+ 'https' => 'MP4::Source::Url',
6
+ 's3' => 'MP4::Source::S3',
7
+ }.freeze
8
+
9
+ def self.build(uri, base_dir: nil)
10
+ raise ArgumentError, 'uri must be a String' unless uri.is_a?(String)
11
+
12
+ scheme = uri.split(':', 2).first
13
+ klass_name = SCHEMES[scheme]
14
+ raise ArgumentError, "unknown source scheme: #{scheme.inspect}" unless klass_name
15
+
16
+ klass = Object.const_get(klass_name)
17
+ if scheme == 'local'
18
+ klass.from_uri(uri, base_dir: base_dir)
19
+ else
20
+ klass.from_uri(uri)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ module MP4::Structure
2
+ class Service
3
+ attr_reader :mp4
4
+
5
+ def initialize(mp4)
6
+ @mp4 = mp4
7
+ end
8
+
9
+ def next_track_id
10
+ mp4.cache.next_track_id
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ module MP4::XLSX
2
+ module Builder
3
+ module_function
4
+
5
+ def call(mp4:, output_path:)
6
+ require 'axlsx'
7
+
8
+ package = Axlsx::Package.new
9
+
10
+ package.workbook do |workbook|
11
+ mp4.cache.each_track do |track_id|
12
+ add_track_worksheet(workbook, track_id, mp4)
13
+ end
14
+ end
15
+
16
+ package.serialize output_path
17
+ end
18
+
19
+ def add_track_worksheet(workbook, track_id, mp4)
20
+ track = mp4.cache.track(track_id)
21
+ track_timescale = track.fetch('timescale')
22
+
23
+ workbook.add_worksheet name: "trak_#{track_id}" do |sheet|
24
+ sheet.add_row(['Chunk ID', 'Chunk Offset', 'Sample Number', 'Sample Size', 'Sample Offset', 'Sample Delta', 'Sample C Delta', 'delta sum', 'delta_sum_millsec', 'timestamp', 'Keyframe'])
25
+
26
+ mp4.cache.each_sample(track_id) do |row|
27
+ sheet.add_row([
28
+ row['chunk_index'],
29
+ row['chunk_offset'],
30
+ row['sample_index'],
31
+ row['size'],
32
+ row['offset'],
33
+ row['delta'],
34
+ row['c_delta'],
35
+ row['delta_sum'],
36
+ row['delta_sum_millisec'],
37
+ Time.at(row['delta_sum'].to_f / track_timescale).utc.strftime('%H:%M:%S,%L'),
38
+ row['keyframe'],
39
+ ])
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/mp4.rb ADDED
@@ -0,0 +1,222 @@
1
+ require 'logger'
2
+ require 'stringio'
3
+ require 'bindata'
4
+ require 'nokogiri'
5
+
6
+ class MP4
7
+ attr_reader :source, :cache_location, :cache_backend
8
+
9
+ LOG_LEVELS = {
10
+ 'silent' => Logger::UNKNOWN + 1,
11
+ 'error' => Logger::ERROR,
12
+ 'warn' => Logger::WARN,
13
+ 'info' => Logger::INFO,
14
+ 'debug' => Logger::DEBUG,
15
+ 'verbose' => Logger::DEBUG,
16
+ }.freeze
17
+
18
+ def self.logger
19
+ @logger ||= begin
20
+ log = Logger.new($stdout)
21
+ log.level = LOG_LEVELS.fetch(ENV.fetch('MP4_LOG_LEVEL', 'info').downcase, Logger::INFO)
22
+ log.formatter = proc do |severity, _time, _prog, msg|
23
+ "[mp4-rb #{severity.ljust(5)}] #{msg}\n"
24
+ end
25
+ log
26
+ end
27
+ end
28
+
29
+ def self.verbose?
30
+ ENV.fetch('MP4_LOG_LEVEL', 'info').downcase == 'verbose'
31
+ end
32
+
33
+ def self.root
34
+ Pathname.new(__dir__).join('..')
35
+ end
36
+
37
+ def initialize(path: nil, url: nil, source: nil, cache_location: nil, cache_backend: nil)
38
+ @source = source
39
+ @source ||= MP4::Source::Local.new(path) if path
40
+ @source ||= MP4::Source::Url.new(url) if url
41
+
42
+ raise 'source is not defined' unless @source
43
+
44
+ @cache_location = cache_location
45
+ @cache_backend = cache_backend || (cache_location ? :sqlite : :memory)
46
+
47
+ return if @cache_location && File.exist?(@cache_location.to_s)
48
+
49
+ memoize_mp4
50
+ segmentize_mp4
51
+ end
52
+
53
+ def build_xslx(output_path)
54
+ ::MP4::XLSX::Builder.call(mp4: self, output_path: output_path)
55
+ end
56
+
57
+ def cache
58
+ @cache ||= Cache.build(cache_backend, cache_location)
59
+ end
60
+
61
+ def save_full_to(output_path)
62
+ t = Layout::Builder.call(cache, cache, source)
63
+
64
+ File.open(output_path, 'wb') { |file| file.write(t.generate) }
65
+ end
66
+
67
+ def compute_segments
68
+ MP4::Nolan::Service.compute_segments(cache)
69
+ end
70
+
71
+ def save_segments_to(output_path)
72
+ base_path = Pathname.new(output_path)
73
+
74
+ FileUtils.mkdir_p(base_path)
75
+
76
+ cached_segments = MP4::Nolan::Service.divide_into_segments(cache)
77
+
78
+ cached_segments.each_with_index do |cached_part, index|
79
+ t = Layout::Builder.call(cache, cached_part, source)
80
+
81
+ File.open(base_path.join("#{index}.mp4"), 'wb') do |file|
82
+ file.write(t.generate)
83
+ end
84
+ end
85
+ end
86
+
87
+ def build_dash_manifest_xml(init_segment_path:, segment_path:)
88
+ M4S::Dash::Manifest.generate(cache, init_segment_path: init_segment_path, segment_path: segment_path)
89
+ end
90
+
91
+ def build_dash_init_segment_binary(track_id)
92
+ M4S::Layout::InitMp4Builder.call(cache, track_id).generate
93
+ end
94
+
95
+ def build_dash_segment_binary(track_id, segment_index)
96
+ M4S::Layout::Builder.call(cache, source, track_id, segment_index).generate
97
+ end
98
+
99
+ def save_fmp4_segments_to(output_path)
100
+ base_path = Pathname.new(output_path)
101
+ FileUtils.mkdir_p(base_path)
102
+
103
+ cache.tracks.each_with_index do |track, index|
104
+ t = M4S::Layout::InitMp4Builder.call(cache, track.fetch('track_id'))
105
+
106
+ file_name = "init-#{index}.mp4"
107
+ file_path = base_path.join(file_name)
108
+ File.open(file_path, 'wb') { |file| file.write(t.generate) }
109
+ end
110
+
111
+ cache.tracks.each do |track|
112
+ track_id = track.fetch('track_id')
113
+
114
+ cache.segments(track_id).each do |segment|
115
+ segment_index = segment.fetch('segment_index')
116
+
117
+ t = M4S::Layout::Builder.call(cache, source, track_id, segment.fetch('segment_index'))
118
+ file_name = "chunk-#{track_id.pred}-#{format('%05d', segment_index.next)}.m4s"
119
+ file_path = base_path.join(file_name)
120
+
121
+ MP4.logger.debug { "writing #{file_path}" }
122
+
123
+ File.open(file_path, 'wb') { |file| file.write(t.generate) }
124
+ end
125
+ end
126
+
127
+ File.open(base_path.join('manifest.mpd'), 'w') do |file|
128
+ file.write(M4S::Dash::Manifest.generate(cache, init_segment_path: nil, segment_path: nil))
129
+ end
130
+
131
+ File.open(base_path.join('general_playlist.m3u8'), 'w') do |file|
132
+ file.write(M4S::M3U8::GeneralPlaylist.generate(cache))
133
+ end
134
+
135
+ cache.tracks.each do |track|
136
+ content = M4S::M3U8::MediaPlaylist.generate(cache, track, init_segment_path: nil, segment_path: nil)
137
+ file_name = "playlist-#{track.fetch('track_id').pred}.m3u8"
138
+ File.open(base_path.join(file_name), 'w') do |file|
139
+ file.write(content)
140
+ end
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def memoize_mp4
147
+ @memoize_mp4 ||= Metadata::Service.call(cache: cache, parsed_mp4: parsed_mp4)
148
+ end
149
+
150
+ def segmentize_mp4
151
+ @segmentize_mp4 ||= MP4::Nolan::Service.divide_into_segments(cache)
152
+ end
153
+
154
+ def parsed_mp4
155
+ Parser::Service.new(source: source).call
156
+ end
157
+ end
158
+
159
+ require_relative './mp4/ext/refinements'
160
+ require_relative './mp4/constants'
161
+ require_relative './mp4/binary/fixed_point8'
162
+ require_relative './mp4/binary/fixed_point16'
163
+ Dir["#{File.dirname(__FILE__)}/mp4/binary/*.rb"].sort.each {|file| require file }
164
+ Dir["#{File.dirname(__FILE__)}/m4s/binary/*.rb"].sort.each {|file| require file }
165
+ Dir["#{File.dirname(__FILE__)}/m4s/dash/*.rb"].sort.each {|file| require file }
166
+ Dir["#{File.dirname(__FILE__)}/m4s/m3u8/*.rb"].sort.each {|file| require file }
167
+ Dir["#{File.dirname(__FILE__)}/m4s/layout/*.rb"].sort.each {|file| require file }
168
+ require_relative './m4s/local_stream'
169
+ require_relative './mp4/cache'
170
+ require_relative './mp4/cache/sqlite'
171
+ require_relative './mp4/cache/memory'
172
+ require_relative './mp4/source'
173
+ require_relative './mp4/source/base'
174
+ require_relative './mp4/source/local'
175
+ require_relative './mp4/source/url'
176
+ require_relative './mp4/source/s3'
177
+ require_relative './mp4/parser/service'
178
+ require_relative './mp4/parser/moov'
179
+ require_relative './mp4/parser/track'
180
+ require_relative './mp4/parser/main_boxes'
181
+ require_relative './mp4/metadata/service'
182
+ require_relative './mp4/metadata/summary'
183
+ require_relative './mp4/metadata/memoize/ftyp'
184
+ require_relative './mp4/metadata/memoize/tracks'
185
+ require_relative './mp4/metadata/memoize/track'
186
+ require_relative './mp4/metadata/memoize/stbl/chunks'
187
+ require_relative './mp4/metadata/memoize/stbl/sample_to_chunk_calculator'
188
+ require_relative './mp4/metadata/memoize/stbl/composition_offset_calculator'
189
+ require_relative './mp4/metadata/memoize/stbl/delta_calculator'
190
+ require_relative './mp4/metadata/memoize/stbl/samples'
191
+ require_relative './mp4/metadata/memoize/stbl/binary_stsd'
192
+ require_relative './mp4/metadata/memoize/stbl/codec'
193
+ require_relative './mp4/structure/service'
194
+ require_relative './mp4/xlsx/builder'
195
+ require_relative './mp4/nolan'
196
+ require_relative './mp4/nolan/service'
197
+ require_relative './mp4/nolan/service/track'
198
+ require_relative './mp4/nolan/service/sound'
199
+ require_relative './mp4/nolan/service/video'
200
+ require_relative './mp4/layout'
201
+ require_relative './mp4/layout/atom'
202
+ require_relative './mp4/layout/base'
203
+ require_relative './mp4/layout/ftyp'
204
+ require_relative './mp4/layout/free'
205
+ require_relative './mp4/layout/elst'
206
+ require_relative './mp4/layout/mdhd'
207
+ require_relative './mp4/layout/hdlr'
208
+ require_relative './mp4/layout/tkhd'
209
+ require_relative './mp4/layout/mvhd'
210
+ require_relative './mp4/layout/vsmhd'
211
+ require_relative './mp4/layout/dinf'
212
+ require_relative './mp4/layout/stsd'
213
+ require_relative './mp4/layout/stts'
214
+ require_relative './mp4/layout/stss'
215
+ require_relative './mp4/layout/ctts'
216
+ require_relative './mp4/layout/stsc'
217
+ require_relative './mp4/layout/stsz'
218
+ require_relative './mp4/layout/stco'
219
+ require_relative './mp4/layout/mdat'
220
+ require_relative './mp4/layout/container'
221
+ require_relative './mp4/layout/moov'
222
+ require_relative './mp4/layout/builder'
data/mp4-rb.gemspec ADDED
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/mp4/rb/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'mp4-rb'
7
+ spec.version = Mp4::Rb::VERSION
8
+ spec.authors = ['Edgars Meinarts']
9
+ spec.email = ['edgars@meinarts.name']
10
+
11
+ spec.summary = 'Ruby MP4 parser + fMP4 segmenter with DASH and HLS manifest generation.'
12
+ spec.description = <<~DESC
13
+ A learning-oriented Ruby library for parsing ISO/IEC 14496-12 MP4 containers
14
+ and turning them into adaptive-streaming assets. Parses every atom with BinData,
15
+ memoises the sample table into a pluggable cache (memory or SQLite), cuts each
16
+ track into keyframe-aligned fMP4 segments, and emits both DASH (MPD) manifests
17
+ and HLS (m3u8) master + media playlists that reference the same segment bytes.
18
+ Also includes a full MP4 remux path (regenerated moov from the cache) and an
19
+ XLSX per-sample analysis workbook. Read the code, run the demo, learn how MP4
20
+ works under the hood. Not production software.
21
+ DESC
22
+
23
+ spec.homepage = 'https://github.com/edgarMeinart/mp4-rb'
24
+ spec.license = 'MIT'
25
+ spec.required_ruby_version = '>= 2.6.0'
26
+
27
+ spec.metadata['homepage_uri'] = spec.homepage
28
+ spec.metadata['source_code_uri'] = 'https://github.com/edgarMeinart/mp4-rb'
29
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/edgarMeinart/mp4-rb/issues'
30
+ spec.metadata['documentation_uri'] = 'https://github.com/edgarMeinart/mp4-rb#readme'
31
+ spec.metadata['rubygems_mfa_required'] = 'true'
32
+
33
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
34
+ `git ls-files -z`.split("\x0").reject do |f|
35
+ f.match?(%r{
36
+ \A(?:
37
+ # dev-only / build artefacts
38
+ \.github/|\.rubocop\.yml|\.gitignore|\.rspec|
39
+ # spec suite
40
+ spec/|
41
+ # docker + demo scaffolding (not part of the library)
42
+ Dockerfile|Dockerfile\.encoder|docker-compose\.yml|Justfile|Makefile|
43
+ # sinatra-based demo server — excluded per user scope
44
+ lib/mp4/server/|lib/m4s/local_stream\.rb|
45
+ # scratch / example scripts and large binary fixtures
46
+ example\.rb|example_v2\.rb|example_v3\.rb|
47
+ examples/|migrations/|out/|tmp/|
48
+ # bootstrap scripts specific to the working repo
49
+ bin/console|bin/setup|bin/encode\.sh
50
+ )
51
+ }x)
52
+ end
53
+ end
54
+
55
+ spec.bindir = 'bin'
56
+ spec.executables = %w[divide_mp4_to_segments.rb build_mp4_xlsx.rb]
57
+ spec.require_paths = ['lib']
58
+
59
+ spec.add_dependency 'axlsx', '~> 2.0.1'
60
+ spec.add_dependency 'bindata', '~> 2.4'
61
+ spec.add_dependency 'nokogiri', '~> 1.15'
62
+ spec.add_dependency 'sqlite3', '~> 1.6.0'
63
+
64
+ spec.add_development_dependency 'pry', '~> 0.14'
65
+ spec.add_development_dependency 'rake', '~> 13.0'
66
+ spec.add_development_dependency 'rspec', '~> 3.12'
67
+ spec.add_development_dependency 'sinatra', '~> 3.0'
68
+ end
data/sig/mp4/rb.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Mp4
2
+ module Rb
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
data/todo.txt ADDED
@@ -0,0 +1,4 @@
1
+ 08.03.2023
2
+ - Calculate duration for TKHD
3
+ - Calculate duration for MDHD
4
+ - Recompile a full video