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,21 @@
1
+ module M4S
2
+ module Layout
3
+ class Stco < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'stco'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Stco.new(
14
+ chunks_count: 0,
15
+ offsets: [],
16
+ ).to_binary_s
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module M4S
2
+ module Layout
3
+ class Stsc < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'stsc'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Stsc.new(
14
+ chunk_count: 0,
15
+ table: [],
16
+ ).to_binary_s
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module M4S
2
+ module Layout
3
+ class Stsd < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'stsd'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Stsd.new(content: cache.stsd_content(track_id)).to_binary_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module M4S
2
+ module Layout
3
+ class Stss < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'stss'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= MP4::Binary::Stss.new(
14
+ sync_samples_count: 0,
15
+ sync_sample: [],
16
+ ).to_binary_s
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module M4S
2
+ module Layout
3
+ class Stsz < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'stsz'
10
+ end
11
+
12
+ def binary_content
13
+ return @binary_content if defined?(@binary_content)
14
+
15
+ @binary_content = ::MP4::Binary::Stsz.new(sample_size: 0, sample_count: 0).to_binary_s
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module M4S
2
+ module Layout
3
+ class Stts < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'stts'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::MP4::Binary::Stts.new(
14
+ entries_count: 0,
15
+ time_to_sample: [],
16
+ ).to_binary_s
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module M4S::Layout
2
+ class Styp < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ def name
6
+ 'styp'
7
+ end
8
+
9
+ def binary_content
10
+ @binary_content ||= M4S::Binary::Styp.new(
11
+ major_brand: 'msdh',
12
+ minor_version: 0,
13
+ compatible_brands: %w[msdh msix],
14
+ ).to_binary_s
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module M4S::Layout
2
+ class Tfdt < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ def name
6
+ 'tfdt'
7
+ end
8
+
9
+ def binary_content
10
+ @binary_content ||= M4S::Binary::Tfdt.new(
11
+ version: M4S::Binary::Tfdt::VERSION_1,
12
+ flags: 0,
13
+ base_media_decode_time: earliest_presentation_time,
14
+ ).to_binary_s
15
+ end
16
+
17
+ def earliest_presentation_time
18
+ cache.segment_earliest_presentation_ticks(track.fetch('track_id'), segment_index)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module M4S::Layout
2
+ class Tfhd < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ def name
6
+ 'tfhd'
7
+ end
8
+
9
+ def binary_content
10
+ @binary_content ||= M4S::Binary::Tfhd.new(
11
+ version: 0,
12
+ flags: 0,
13
+ track_id: 1,
14
+ ).to_binary_s
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ module M4S::Layout
2
+ class Tkhd < Atom
3
+ DEFAULT_TIMESCALE = 1000
4
+
5
+ using ::MP4::Ext::Refinements
6
+
7
+ private
8
+
9
+ def name
10
+ 'tkhd'
11
+ end
12
+
13
+ def binary_content
14
+ @binary_content ||= ::MP4::Binary::Tkhd.new(
15
+ version: 0,
16
+ flags: [0, 0, 3],
17
+ creation_time: 0,
18
+ modification_time: 0,
19
+ track_id: 1,
20
+ duration: 0,
21
+ layer: 0,
22
+ alternate_group: track.fetch('alternate_group'),
23
+ volume: track.fetch('volume'),
24
+ matrix: [65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824],
25
+ width: track.fetch('width'),
26
+ height: track.fetch('height'),
27
+ ).to_binary_s
28
+ end
29
+
30
+ def calculate_timescale
31
+ DEFAULT_TIMESCALE
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ module M4S
2
+ module Layout
3
+ class Trex < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ 'trex'
10
+ end
11
+
12
+ def binary_content
13
+ @binary_content ||= ::M4S::Binary::Trex.new(
14
+ version: 0,
15
+ flags: 0,
16
+ track_id: 1,
17
+ default_sample_description_index: 1,
18
+ default_sample_duration: 0,
19
+ default_sample_size: 0,
20
+ default_sample_flags: {
21
+ is_leading: 0,
22
+ sample_depends_on: 0,
23
+ sample_is_depended_on: 0,
24
+ sample_has_redundancy: 0,
25
+ sample_padding_value: 0,
26
+ sample_is_sync: 1,
27
+ sample_degradation_priority: 0,
28
+ },
29
+ ).to_binary_s
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,89 @@
1
+ module M4S::Layout
2
+ class Trun < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ attr_accessor :data_offset
6
+
7
+ def initialize(cache, opts = {})
8
+ super(cache, opts)
9
+
10
+ @data_offset = opts.fetch(:data_offset, 0)
11
+ end
12
+
13
+ def name
14
+ 'trun'
15
+ end
16
+
17
+ def binary_content
18
+ flags = M4S::Binary::Trun::FLAGS
19
+
20
+ @binary_content ||= M4S::Binary::Trun.new(
21
+ version: 0,
22
+ flags: flags[:data_offset] | flags[:sample_duration] | flags[:sample_size] | flags[:sample_flags] | flags[:sample_composition_time_offset],
23
+ sample_count: entries_list.size,
24
+ data_offset: data_offset,
25
+ first_sample_flags: 0,
26
+ samples: entries_list,
27
+ ).to_binary_s
28
+ end
29
+
30
+ def entries_list
31
+ @entries_list ||= generate_entries_list
32
+ end
33
+
34
+ def generate_entries_list
35
+ buffer = []
36
+
37
+ cache.segment_samples(track.fetch('track_id'), segment_index).each do |sample|
38
+ sample_flags =
39
+ if sample.fetch('keyframe') == 'TRUE'
40
+ {
41
+ sample_degradation_priority: 0,
42
+ sample_depends_on: 2, # Сэмпл не зависит от других сэмплов
43
+ sample_is_depended_on: 0, # Другие сэмплы зависят от этого сэмпла (ключевой кадр)
44
+ sample_has_redundancy: 0,
45
+ sample_padding_value: 0,
46
+ sample_is_difference_sample: 1,
47
+ }
48
+ else
49
+ {
50
+ sample_degradation_priority: 0,
51
+ sample_depends_on: 1, # Сэмпл не зависит от других сэмплов
52
+ sample_is_depended_on: 1, # Другие сэмплы зависят от этого сэмпла (ключевой кадр)
53
+ sample_has_redundancy: 0,
54
+ sample_padding_value: 0,
55
+ sample_is_difference_sample: 0,
56
+ }
57
+ end
58
+
59
+ buffer << {
60
+ sample_duration: sample.fetch('delta'),
61
+ sample_size: sample.fetch('size'),
62
+ sample_flags: encode_sample_flags(sample_flags),
63
+ sample_composition_time_offset: sample.fetch('c_delta'),
64
+ }
65
+ end
66
+
67
+ buffer
68
+ end
69
+
70
+ def encode_sample_flags(flags_hash)
71
+ sample_degradation_priority = flags_hash[:sample_degradation_priority] & 0xFFFF
72
+ sample_depends_on = flags_hash[:sample_depends_on] & 0x3
73
+ sample_is_depended_on = flags_hash[:sample_is_depended_on] & 0x3
74
+ sample_has_redundancy = flags_hash[:sample_has_redundancy] & 0x3
75
+ sample_padding_value = flags_hash[:sample_padding_value] & 0x7
76
+ sample_is_difference_sample = flags_hash[:sample_is_difference_sample] & 0x1
77
+
78
+ flags = 0
79
+ flags |= (sample_degradation_priority << 16)
80
+ flags |= (sample_depends_on << 14)
81
+ flags |= (sample_is_depended_on << 12)
82
+ flags |= (sample_has_redundancy << 10)
83
+ flags |= (sample_padding_value << 7)
84
+ flags |= (sample_is_difference_sample << 6)
85
+
86
+ flags
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,51 @@
1
+ module M4S
2
+ module Layout
3
+ class VSmhd < Atom
4
+ using ::MP4::Ext::Refinements
5
+
6
+ private
7
+
8
+ def name
9
+ return 'vmhd' if video?
10
+ return 'smhd' if sound?
11
+
12
+ raise NotImplementedError, ''
13
+ end
14
+
15
+ def binary_content
16
+ return @binary_content if defined?(@binary_content)
17
+
18
+ serializer_klass = if video?
19
+ ::MP4::Binary::Vmhd
20
+ elsif sound?
21
+ ::MP4::Binary::Smhd
22
+ else
23
+ raise "Unknown track handler type #{track.fetch('handler_type')}"
24
+ end
25
+
26
+ @binary_content = serializer_klass.new(handler_attrs).to_binary_s
27
+ end
28
+
29
+ def handler_attrs
30
+ if video?
31
+ {
32
+ flags: [0, 0, 0],
33
+ opcolor: [0, 0, 0],
34
+ }
35
+ elsif sound?
36
+ {
37
+ flags: [0, 0, 0],
38
+ }
39
+ end
40
+ end
41
+
42
+ def video?
43
+ @video ||= track.fetch('handler_type') == 'vide'
44
+ end
45
+
46
+ def sound?
47
+ @sound ||= track.fetch('handler_type') == 'soun'
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ module M4S::M3U8
2
+ module GeneralPlaylist
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
+ AUDIO_GROUP_ID = 'audio'.freeze
9
+
10
+ def generate(mp4, base_path = nil)
11
+ video_tracks = mp4.tracks.select { |t| t['mime_type'] == VIDEO_TRACK }
12
+ audio_tracks = mp4.tracks.select { |t| t['mime_type'] == AUDIO_TRACK }
13
+
14
+ lines = ['#EXTM3U', '#EXT-X-VERSION:7', '#EXT-X-INDEPENDENT-SEGMENTS']
15
+
16
+ audio_tracks.each do |track|
17
+ lines << audio_media_line(track, base_path)
18
+ end
19
+
20
+ video_tracks.each do |track|
21
+ lines << stream_inf_line(track, audio_tracks.any?)
22
+ lines << playlist_uri(track, base_path)
23
+ end
24
+
25
+ "#{lines.join("\n")}\n"
26
+ end
27
+
28
+ private
29
+
30
+ def audio_media_line(track, base_path)
31
+ %(#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="#{AUDIO_GROUP_ID}",NAME="default",DEFAULT=YES,AUTOSELECT=YES,URI="#{playlist_uri(track, base_path)}")
32
+ end
33
+
34
+ def stream_inf_line(track, has_audio)
35
+ bandwidth = bandwidth_for(track)
36
+ resolution = "#{track.fetch('width')}x#{track.fetch('height')}"
37
+ codecs = track.fetch('codec')
38
+ base = %(#EXT-X-STREAM-INF:BANDWIDTH=#{bandwidth},RESOLUTION=#{resolution},CODECS="#{codecs}")
39
+ has_audio ? base + %(,AUDIO="#{AUDIO_GROUP_ID}") : base
40
+ end
41
+
42
+ def bandwidth_for(track)
43
+ value = track.fetch('avg_bitrate').to_i
44
+ value = track.fetch('max_bitrate').to_i if value.zero?
45
+ value.positive? ? value : 500_000
46
+ end
47
+
48
+ def playlist_uri(track, base_path)
49
+ [base_path, "playlist-#{track.fetch('track_id').pred}.m3u8"].compact.join('/')
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ module M4S::M3U8
2
+ module MediaPlaylist
3
+ module_function
4
+
5
+ def generate(mp4, track, init_segment_path:, segment_path:)
6
+ track_id = track.fetch('track_id')
7
+ timescale = track.fetch('timescale').to_f
8
+ init_file = [init_segment_path, "init-#{track_id.pred}.mp4"].compact.join
9
+
10
+ segment_durations_seconds = mp4.segment_duration_ticks_by_index(track_id).map { |s| s.fetch('duration') / timescale }
11
+ target_duration = segment_durations_seconds.max.to_f.ceil.clamp(1, nil)
12
+
13
+ lines = [
14
+ '#EXTM3U',
15
+ "#EXT-X-TARGETDURATION:#{target_duration}",
16
+ '#EXT-X-VERSION:7',
17
+ '#EXT-X-MEDIA-SEQUENCE:0',
18
+ '#EXT-X-PLAYLIST-TYPE:VOD',
19
+ '#EXT-X-INDEPENDENT-SEGMENTS',
20
+ %(#EXT-X-MAP:URI="#{init_file}"),
21
+ ]
22
+
23
+ segment_durations_seconds.each_with_index do |duration, index|
24
+ lines << "#EXTINF:#{format('%.6f', duration)},"
25
+ lines << [segment_path, "chunk-#{track_id.pred}-#{format('%05d', index.next)}.m4s"].compact.join('/')
26
+ end
27
+
28
+ lines << '#EXT-X-ENDLIST'
29
+ "#{lines.join("\n")}\n"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,98 @@
1
+ require 'bindata'
2
+
3
+ module MP4::Binary
4
+ class BtrtAtom < BinData::Record
5
+ endian :big
6
+ uint32 :a_size
7
+ string :a_type, read_length: 4, assert: -> { value == "btrt" }
8
+ uint32 :buffer_size_db
9
+ uint32 :max_bitrate
10
+ uint32 :avg_bitrate
11
+ end
12
+
13
+ class PaspAtom < BinData::Record
14
+ endian :big
15
+
16
+ uint32 :a_size
17
+ string :a_type, read_length: 4, assert: -> { value == "pasp" }
18
+ uint32 :h_spacing
19
+ uint32 :v_spacing
20
+
21
+ def sample_aspect_ratio
22
+ "#{h_spacing}:#{v_spacing}"
23
+ end
24
+ end
25
+
26
+ class Avc1 < BinData::Record
27
+ endian :big
28
+ array :reserved1, type: :uint8, initial_length: 6
29
+ uint16 :data_reference_index
30
+ uint16 :pre_defined
31
+ uint16 :reserved2
32
+ array :pre_defined2, type: :uint32, initial_length: 3
33
+ uint16 :width
34
+ uint16 :height
35
+ uint32 :horizresolution
36
+ uint32 :vertresolution
37
+ uint32 :reserved3
38
+ uint16 :frame_count
39
+ string :compressorname, read_length: 32
40
+ uint16 :depth
41
+ int16 :pre_defined3
42
+
43
+ struct :avcC do
44
+ endian :big
45
+ uint32 :a_size
46
+ string :a_type, read_length: 4, assert: -> { value == "avcC" }
47
+ uint8 :configuration_version
48
+ uint8 :avc_profile_indication
49
+ uint8 :profile_compatibility
50
+ uint8 :avc_level_indication
51
+ bit2 :reserved
52
+ bit6 :length_size_minus_one
53
+ bit3 :reserved_2
54
+ bit5 :num_of_sps
55
+
56
+ array :sps, initial_length: :num_of_sps do
57
+ uint16 :sps_length
58
+ string :sps_data, read_length: :sps_length
59
+ end
60
+
61
+ uint8 :num_of_pps
62
+ array :pps, initial_length: :num_of_pps do
63
+ uint16 :pps_length
64
+ string :pps_data, read_length: :pps_length
65
+ end
66
+ end
67
+
68
+ rest :all_rest
69
+
70
+ def bitrate
71
+ return @bitrate if defined?(@bitrate)
72
+
73
+ index_of_btrt = all_rest.index('btrt')
74
+
75
+ return unless index_of_btrt
76
+
77
+ atom_start_pos = index_of_btrt - 4
78
+ atom_size = BinData::Uint32be.read(all_rest[atom_start_pos..atom_start_pos + 4]).to_i
79
+ atom_bytes = all_rest[atom_start_pos..atom_start_pos + 4 + atom_size]
80
+
81
+ @bitrate = BtrtAtom.read(atom_bytes)
82
+ end
83
+
84
+ def pasp
85
+ return @pasp if defined?(@pasp)
86
+
87
+ index_of_pasp = all_rest.index('pasp')
88
+
89
+ return unless index_of_pasp
90
+
91
+ atom_start_pos = index_of_pasp - 4
92
+ atom_size = BinData::Uint32be.read(all_rest[atom_start_pos..atom_start_pos + 4]).to_i
93
+ atom_bytes = all_rest[atom_start_pos..atom_start_pos + 4 + atom_size]
94
+
95
+ @pasp = PaspAtom.read(atom_bytes)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,8 @@
1
+ module MP4::Binary
2
+ class BoxHead < BinData::Record
3
+ endian :big
4
+
5
+ uint32 :box_size
6
+ string :box_name, :read_length => 4
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module MP4::Binary
2
+ class Co64 < BinData::Record
3
+ endian :big
4
+
5
+ uint8 :version, value: 0
6
+ array :flags, initial_length: 3, type: :uint8, value: [0, 0, 0]
7
+
8
+ uint32 :chunks_count
9
+ array :offsets, initial_length: :chunks_count, type: :uint64
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module MP4::Binary
2
+ class Codec < BinData::Record
3
+ endian :big
4
+ uint32 :content_size
5
+ string :codec_type, read_length: 4
6
+ rest :content
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ module MP4::Binary
2
+ class Ctts < BinData::Record
3
+ endian :big
4
+
5
+ bit1 :version, value: 0
6
+ array :flags, initial_length: 3, type: :uint8, value: [0, 0, 0]
7
+
8
+ uint32 :entry_count
9
+ array :entries_list, initial_length: :entry_count do
10
+ uint32 :sample_count
11
+ int32 :composition_offset
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module MP4::Binary
2
+ class CttsReader < BinData::Record
3
+ rest :content
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module MP4::Binary
2
+ class Dinf < BinData::Record
3
+ rest :content
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module MP4::Binary
2
+ class Dref < BinData::Record
3
+ endian :big
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ module MP4::Binary
2
+ class Elst < BinData::Record
3
+ endian :big
4
+
5
+ bit1 :version, value: 0
6
+ array :flags, initial_length: 3, type: :uint8
7
+
8
+ uint32 :entries_count
9
+ array :matrix, initial_length: :entries_count do
10
+ uint32 :segment_duration
11
+ int32 :media_time
12
+ int16 :media_rate
13
+ int16 :media_rate_fraction, value: 0
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module MP4::Binary
2
+ class FixedPoint16 < BinData::BasePrimitive
3
+ def value_to_binary_string(value)
4
+ value = (value * (2.0**16)).to_i
5
+
6
+ BinData::Int32be.new(value).to_binary_s
7
+ end
8
+
9
+ def read_and_return_value(io)
10
+ raw_value = BinData::Int32be.read read_uint32(io)
11
+
12
+ (raw_value / (2.0**16)).to_i
13
+ end
14
+
15
+ def read_uint32(io)
16
+ io.readbytes(4)
17
+ end
18
+ end
19
+ end