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,25 @@
1
+ module M4S::Binary
2
+ class DefaultSampleFlags < BinData::Record
3
+ endian :big
4
+
5
+ bit2 :is_leading
6
+ bit2 :sample_depends_on
7
+ bit2 :sample_is_depended_on
8
+ bit2 :sample_has_redundancy
9
+ bit3 :sample_padding_value
10
+ bit1 :sample_is_sync
11
+ bit16 :sample_degradation_priority
12
+ end
13
+
14
+ class Trex < BinData::Record
15
+ endian :big
16
+
17
+ uint8 :version
18
+ bit24 :flags
19
+ uint32 :track_id
20
+ uint32 :default_sample_description_index
21
+ uint32 :default_sample_duration
22
+ uint32 :default_sample_size
23
+ default_sample_flags :default_sample_flags
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module M4S
2
+ module Binary
3
+ class Trun < BinData::Record
4
+ endian :big
5
+
6
+ FLAGS = {
7
+ data_offset: 0x000001,
8
+ first_sample_flags: 0x000004,
9
+ sample_duration: 0x000100,
10
+ sample_size: 0x000200,
11
+ sample_flags: 0x000400,
12
+ sample_composition_time_offset: 0x000800,
13
+ }.freeze
14
+
15
+ uint8 :version
16
+ bit24 :flags
17
+ uint32 :sample_count
18
+
19
+ int32 :data_offset, onlyif: -> { flags & FLAGS[:data_offset] != 0 }
20
+ uint32 :first_sample_flags, onlyif: -> { flags & FLAGS[:first_sample_flags] != 0 }
21
+
22
+ array :samples, initial_length: :sample_count do
23
+ uint32 :sample_duration, onlyif: -> { parent.flags & FLAGS[:sample_duration] != 0 }
24
+ uint32 :sample_size, onlyif: -> { parent.flags & FLAGS[:sample_size] != 0 }
25
+ uint32 :sample_flags, onlyif: -> { parent.flags & FLAGS[:sample_flags] != 0 }
26
+ int32 :sample_composition_time_offset, onlyif: -> { parent.flags & FLAGS[:sample_composition_time_offset] != 0 }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,149 @@
1
+ module M4S::Dash
2
+ module Manifest
3
+ extend self
4
+
5
+ VIDEO_TRACK = 'video/mp4'.freeze
6
+ AUDIO_TRACK = 'audio/mp4'.freeze
7
+ TRACKS_PRIORITY = { VIDEO_TRACK => 0, AUDIO_TRACK => 1 }.freeze
8
+ SCHEMA_LOCATION = 'urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd'.freeze
9
+ PROFILE = 'urn:mpeg:dash:profile:isoff-live:2011'.freeze
10
+
11
+ def generate(mp4, init_segment_path:, segment_path:)
12
+ media_presentation_duration = calculate_media_presentation_duration(mp4)
13
+ max_segment_duration = calculate_max_segment_duration(mp4)
14
+
15
+ init_template = join_path(init_segment_path, 'init-$RepresentationID$.mp4')
16
+ media_template = join_path(segment_path, 'chunk-$RepresentationID$-$Number%05d$.m4s')
17
+
18
+ builder = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
19
+ xml.MPD(
20
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
21
+ 'xmlns' => 'urn:mpeg:dash:schema:mpd:2011',
22
+ 'xmlns:xlink' => 'http://www.w3.org/1999/xlink',
23
+ 'xsi:schemaLocation' => SCHEMA_LOCATION,
24
+ 'profiles' => PROFILE,
25
+ 'type' => 'static',
26
+ 'mediaPresentationDuration' => format_duration(media_presentation_duration),
27
+ 'maxSegmentDuration' => format_duration(max_segment_duration),
28
+ 'minBufferTime' => format_duration(max_segment_duration * 2.0),
29
+ ) do
30
+ xml.ProgramInformation
31
+ xml.ServiceDescription(id: '0')
32
+ xml.Period(id: '0', start: 'PT0.0S') do
33
+ sorted_tracks(mp4).each_with_index do |track, index|
34
+ render_adaptation_set(xml, mp4, track, index, init_template, media_template)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ builder.to_xml
41
+ end
42
+
43
+ def calculate_max_segment_duration(mp4)
44
+ track = primary_track(mp4)
45
+ max = mp4.max_segment_duration_ticks(track.fetch('track_id')).to_f
46
+ max / track.fetch('timescale').to_f
47
+ end
48
+
49
+ def calculate_media_presentation_duration(mp4)
50
+ track = primary_track(mp4)
51
+ mp4.track_sample_delta_sum(track.fetch('track_id')).to_f / track.fetch('timescale')
52
+ end
53
+
54
+ def format_duration(float_seconds)
55
+ total_seconds = float_seconds.to_f
56
+ minutes = (total_seconds / 60).to_i
57
+ seconds = total_seconds % 60
58
+
59
+ if minutes.positive?
60
+ seconds.positive? ? "PT#{minutes}M#{seconds.round(1)}S" : "PT#{minutes}M"
61
+ else
62
+ "PT#{seconds.round(1)}S"
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def sorted_tracks(mp4)
69
+ mp4.tracks.sort_by { |t| TRACKS_PRIORITY.fetch(t['mime_type'], TRACKS_PRIORITY.size) }
70
+ end
71
+
72
+ def primary_track(mp4)
73
+ mp4.tracks.find { |t| t.fetch('handler_type') == 'vide' } ||
74
+ mp4.tracks.find { |t| t.fetch('handler_type') == 'soun' }
75
+ end
76
+
77
+ def render_adaptation_set(xml, mp4, track, index, init_template, media_template)
78
+ xml.AdaptationSet(adaptation_set_options(track, index)) do
79
+ xml.Representation(representation_options(track, index)) do
80
+ if track.fetch('mime_type') == AUDIO_TRACK
81
+ xml.AudioChannelConfiguration(
82
+ schemeIdUri: 'urn:mpeg:dash:23003:3:audio_channel_configuration:2011',
83
+ value: track.fetch('channel_count') || 2,
84
+ )
85
+ end
86
+
87
+ xml.SegmentTemplate(
88
+ timescale: track.fetch('timescale'),
89
+ initialization: init_template,
90
+ media: media_template,
91
+ startNumber: '1',
92
+ ) do
93
+ xml.SegmentTimeline do
94
+ mp4.segment_duration_ticks_by_index(track.fetch('track_id')).each_with_index do |segment, seg_i|
95
+ if seg_i.zero?
96
+ xml.S(t: '0', d: segment.fetch('duration'))
97
+ else
98
+ xml.S(d: segment.fetch('duration'))
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def adaptation_set_options(track, index)
108
+ opts = { id: index.to_s, startWithSAP: '1', segmentAlignment: 'true', bitstreamSwitching: 'true' }
109
+ if track.fetch('mime_type') == VIDEO_TRACK
110
+ opts.merge!(
111
+ contentType: 'video',
112
+ maxWidth: track.fetch('width').to_s,
113
+ maxHeight: track.fetch('height').to_s,
114
+ par: track.fetch('par'),
115
+ )
116
+ else
117
+ opts.merge!(contentType: 'audio')
118
+ end
119
+ opts
120
+ end
121
+
122
+ def representation_options(track, index)
123
+ opts = {
124
+ id: index.to_s,
125
+ mimeType: track.fetch('mime_type'),
126
+ codecs: track.fetch('codec'),
127
+ bandwidth: bandwidth(track),
128
+ }
129
+ if track.fetch('mime_type') == VIDEO_TRACK
130
+ opts.merge!(width: track.fetch('width'), height: track.fetch('height'), sar: track.fetch('sar'))
131
+ elsif track.fetch('mime_type') == AUDIO_TRACK
132
+ opts.merge!(audioSamplingRate: track.fetch('sample_rate'))
133
+ end
134
+ opts
135
+ end
136
+
137
+ def bandwidth(track)
138
+ value = track.fetch('avg_bitrate').to_i
139
+ value = track.fetch('max_bitrate').to_i if value.zero?
140
+ value.positive? ? value : 128_000
141
+ end
142
+
143
+ def join_path(prefix, tail)
144
+ return tail if prefix.to_s.empty?
145
+
146
+ [prefix, tail].join('/')
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,44 @@
1
+ module M4S::Layout
2
+ class Atom
3
+ attr_accessor :cache, :track, :segment_index, :track_id
4
+
5
+ def self.generate(cache, opts = {})
6
+ new(cache, opts).generate
7
+ end
8
+
9
+ def initialize(cache, opts = {})
10
+ @cache = cache
11
+ @track = opts.fetch(:track, nil)
12
+ @track_id = track.fetch('track_id') if track
13
+ @segment_index = opts.fetch(:segment_index, nil)
14
+ end
15
+
16
+ def generate
17
+ [
18
+ *header,
19
+ binary_content,
20
+ ].join
21
+ end
22
+
23
+ private
24
+
25
+ def header
26
+ [
27
+ BinData::Uint32be.new(size).to_binary_s,
28
+ BinData::String.new(name).to_binary_s,
29
+ ]
30
+ end
31
+
32
+ def name
33
+ raise NotImplementedError.new(message: "#{__callee__} not implemented")
34
+ end
35
+
36
+ def size
37
+ ::MP4::Constants::GENERIC_HEADER_SIZE + binary_content.size
38
+ end
39
+
40
+ def binary_content
41
+ raise NotImplementedError.new(message: "#{__callee__} not implemented")
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ module M4S::Layout
2
+ module Builder
3
+ module_function
4
+
5
+ def call(cache, source, track_id, segment_index)
6
+ track = cache.track(track_id)
7
+
8
+ container = Container.new(root: true)
9
+
10
+ container.add_atom Styp.generate(cache)
11
+ container.add_atom Sidx.generate(cache, track: track, segment_index: segment_index)
12
+
13
+ container.size
14
+ container.add_container :moof do |moof|
15
+ moof.add_atom Mfhd.generate(cache, segment_index: segment_index)
16
+
17
+ moof.add_container :traf do |traf|
18
+ traf.add_atom Tfhd.generate(cache, track: track, segment_index: segment_index)
19
+ traf.add_atom Tfdt.generate(cache, track: track, segment_index: segment_index)
20
+ trun_boilerplate = Trun.generate(cache, track: track, segment_index: segment_index)
21
+
22
+ data_offset = moof.size + traf.size + trun_boilerplate.size + ::MP4::Constants::GENERIC_HEADER_SIZE
23
+ traf.add_atom Trun.generate(cache, track: track, segment_index: segment_index, data_offset: data_offset)
24
+ end
25
+ end
26
+
27
+ container.add_atom Mdat.generate(cache, source: source, track: track, segment_index: segment_index)
28
+
29
+ container
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,59 @@
1
+ module M4S::Layout
2
+ class Container
3
+ # include ::Layout::Base
4
+
5
+ attr_accessor :root, :name, :descendants
6
+
7
+ def initialize(root: false, name: nil)
8
+ @root = root
9
+ @name = name
10
+ @descendants ||= []
11
+ end
12
+
13
+ def generate
14
+ return binary_descendants if root
15
+
16
+ [
17
+ BinData::Uint32be.new(size).to_binary_s,
18
+ BinData::String.new(name).to_binary_s,
19
+ binary_descendants,
20
+ ].join
21
+ end
22
+
23
+ def binary_descendants
24
+ descendants.join
25
+ end
26
+
27
+ def size
28
+ binary_descendants.size + (root ? 0 : ::MP4::Constants::GENERIC_HEADER_SIZE)
29
+ end
30
+
31
+ def add_container(name, &block)
32
+ container = self.class.new(name: name)
33
+
34
+ block.call(container)
35
+
36
+ @descendants << container.generate
37
+
38
+ container
39
+ end
40
+
41
+ def add_atom(atom)
42
+ @descendants << atom
43
+ atom
44
+ end
45
+
46
+ def add_mdat(mp4, source)
47
+ @descendants << ::Layout::Mdat.new(mp4: mp4, source: source)
48
+ end
49
+
50
+ def pack
51
+ return descendants.map(&:pack).join if root
52
+
53
+ [
54
+ *header,
55
+ descendants.map(&:pack),
56
+ ].join
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,20 @@
1
+ module M4S
2
+ module Layout
3
+ class Ctts < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'ctts'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Ctts.new(
14
+ entry_count: 0,
15
+ entries_list: [],
16
+ ).to_binary_s
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module M4S
2
+ module Layout
3
+ class Dinf < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'dinf'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Dinf.new(content: cache.dinf_content(track_id)).to_binary_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module M4S
2
+ module Layout
3
+ class Elst < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'elst'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Elst.new(
14
+ version: 0,
15
+ flags: [0, 0, 0],
16
+ entries_count: matrix.size,
17
+ matrix: matrix,
18
+ ).to_binary_s
19
+ end
20
+
21
+ def matrix
22
+ @matrix ||= cache.elst_entries(track_id).map do |entry|
23
+ {
24
+ segment_duration: 0,
25
+ media_time: entry.fetch('media_time'),
26
+ media_rate: entry.fetch('media_rate'),
27
+ media_rate_fraction: entry.fetch('media_rate_fraction'),
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ module M4S::Layout
2
+ class Ftyp < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ def name
6
+ 'ftyp'
7
+ end
8
+
9
+ def binary_content
10
+ @binary_content ||= MP4::Binary::Ftyp.new(
11
+ major_brand: memoized_ftyp.fetch('major_brand'),
12
+ minor_version: memoized_ftyp.fetch('minor_version'),
13
+ compatible_brands: memoized_ftyp.fetch('compatible_brands'),
14
+ ).to_binary_s
15
+ end
16
+
17
+ def memoized_ftyp
18
+ @memoized_ftyp ||= cache.ftyp
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ module M4S
2
+ module Layout
3
+ class Hdlr < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'hdlr'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Hdlr.new(
14
+ version: 0,
15
+ flags: [0, 0, 0],
16
+ handler_type: track.fetch('handler_type'),
17
+ handler_name: track.fetch('handler_name'),
18
+ ).to_binary_s
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ module M4S::Layout
2
+ module InitMp4Builder
3
+ module_function
4
+
5
+ def call(cache, track_id)
6
+ # binding.pry
7
+ track = cache.track(track_id)
8
+
9
+ container = Container.new(root: true)
10
+
11
+ container.add_atom Ftyp.generate(cache)
12
+
13
+ container.add_container :moov do |moov|
14
+ moov.add_atom Mvhd.generate(cache)
15
+
16
+ moov.add_container :trak do |trak|
17
+ trak.add_atom Tkhd.generate(cache, track: track)
18
+
19
+ trak.add_container :edts do |edts|
20
+ edts.add_atom Elst.generate(cache, track: track)
21
+ end
22
+
23
+ trak.add_container :mdia do |mdia|
24
+ # if track.fetch('handler_type') == 'vide'
25
+ mdia.add_atom Mdhd.generate(cache, track: track)
26
+ mdia.add_atom Hdlr.generate(cache, track: track)
27
+
28
+ mdia.add_container :minf do |minf|
29
+ minf.add_atom VSmhd.generate(cache, track: track)
30
+ minf.add_atom Dinf.generate(cache, track: track)
31
+
32
+ minf.add_container :stbl do |stbl|
33
+ stbl.add_atom Stsd.generate(cache, track: track)
34
+ stbl.add_atom Stts.generate(cache, track: track)
35
+
36
+ if track['handler_type'] == 'vide'
37
+ stbl.add_atom Stss.generate(cache, track: track)
38
+ stbl.add_atom Ctts.generate(cache, track: track) if track['have_composition_time'].nonzero?
39
+ end
40
+
41
+ stbl.add_atom Stsc.generate(cache, track: track)
42
+ stbl.add_atom Stsz.generate(cache, track: track)
43
+ stbl.add_atom Stco.generate(cache, track: track)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ moov.add_container :mvex do |mvex|
50
+ mvex.add_atom Trex.generate(cache)
51
+ end
52
+ end
53
+
54
+ container
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,61 @@
1
+ module M4S::Layout
2
+ class Mdat < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ attr_reader :source, :track_id
6
+
7
+ def initialize(cache, opts = {})
8
+ super(cache, opts)
9
+ @source = opts.fetch(:source)
10
+ end
11
+
12
+ private
13
+
14
+ def name
15
+ 'mdat'
16
+ end
17
+
18
+ def binary_content
19
+ return @binary_content if defined?(@binary_content)
20
+
21
+ buffer = StringIO.new
22
+
23
+ source.reset_seek
24
+
25
+ samples = cache.segment_bytes_sizes_offsets(track.fetch('track_id'), segment_index)
26
+
27
+ if source.respond_to?(:fetch)
28
+ range(buffer, samples)
29
+ else
30
+ progressive(buffer, samples)
31
+ end
32
+
33
+ buffer.seek(0)
34
+
35
+ @binary_content = BinData::String.new(buffer.read).to_binary_s
36
+ end
37
+
38
+ def range(buffer, samples)
39
+ range_from = samples.first.fetch('offset')
40
+ range_to = samples.last.fetch('offset') + samples.last.fetch('size')
41
+ bytes = StringIO.new(source.fetch(range_from, range_to))
42
+
43
+ samples.each do |chunk|
44
+ offset = chunk.fetch('offset') - range_from
45
+ bytes.seek(offset)
46
+ chunk_bytes = bytes.read(chunk.fetch('size'))
47
+
48
+ buffer.write chunk_bytes
49
+ end
50
+ end
51
+
52
+ def progressive(buffer, samples)
53
+ samples.each do |chunk|
54
+ source.set_seek chunk.fetch('offset')
55
+ chunk_bytes = source.read(chunk.fetch('size'))
56
+
57
+ buffer.write chunk_bytes
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ module M4S
2
+ module Layout
3
+ class Mdhd < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'mdhd'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Mdhd.new(
14
+ version: 0,
15
+ flags: [0, 0, 0],
16
+ creation_time: 0,
17
+ modification_time: 0,
18
+ timescale: track.fetch('timescale'),
19
+ duration: 0,
20
+ language: [0, 0, 0],
21
+ ).to_binary_s
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module M4S::Layout
2
+ class Mfhd < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ def name
6
+ 'mfhd'
7
+ end
8
+
9
+ def binary_content
10
+ @binary_content ||= M4S::Binary::Mfhd.new(
11
+ version: 0,
12
+ flags: 0,
13
+ sequence_number: segment_index,
14
+ ).to_binary_s
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ module M4S::Layout
2
+ class Mvhd < Atom
3
+ DEFAULT_TIMESCALE = 1000
4
+
5
+ using ::MP4::Ext::Refinements
6
+
7
+ private
8
+
9
+ def name
10
+ 'mvhd'
11
+ end
12
+
13
+ def binary_content
14
+ @binary_content ||= MP4::Binary::Mvhd.new(
15
+ version: 0,
16
+ flags: [0, 0, 0],
17
+ creation_time: 0,
18
+ modification_time: 0,
19
+ timescale: DEFAULT_TIMESCALE,
20
+ duration: 0,
21
+ rate: 1.0,
22
+ volume: 1.0,
23
+ matrix: [65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824],
24
+ next_track_id: calculate_next_track_id,
25
+ ).to_binary_s
26
+ end
27
+
28
+ def calculate_next_track_id
29
+ cache.next_track_id
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ module M4S::Layout
2
+ class Sidx < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ def name
6
+ 'sidx'
7
+ end
8
+
9
+ def binary_content
10
+ @binary_content ||= M4S::Binary::Sidx.new(
11
+ version: 1,
12
+ flags: 0,
13
+ reference_id: 1,
14
+ timescale: track.fetch('timescale'),
15
+ earliest_presentation_time: earliest_presentation_time,
16
+ first_offset: 0,
17
+ reference_count: 1,
18
+ references: [
19
+ { reference_type: 0, referenced_size: segment_size, subsegment_duration: segment_duration, starts_with_sap: 1, sap_type: 0, sap_delta_time: 0 },
20
+ ],
21
+ ).to_binary_s
22
+ end
23
+
24
+ def segment_duration
25
+ cache.segment_duration_ticks(track.fetch('track_id'), segment_index)
26
+ end
27
+
28
+ def earliest_presentation_time
29
+ cache.segment_earliest_presentation_ticks(track.fetch('track_id'), segment_index)
30
+ end
31
+
32
+ def segment_size
33
+ cache.segment_size_bytes(track.fetch('track_id'), segment_index)
34
+ end
35
+ end
36
+ end