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,39 @@
1
+ module MP4::Layout
2
+ class Stsz < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ private
6
+
7
+ def name
8
+ 'stsz'
9
+ end
10
+
11
+ def binary_content
12
+ return @binary_content if defined?(@binary_content)
13
+
14
+ binary_content = if all_sizes_equeal?
15
+ MP4::Binary::Stsz.new(sample_size: sample_size, sample_count: sample_count)
16
+ else
17
+ MP4::Binary::Stsz.new(sample_count: sample_size_list.size, sample_sizes: sample_size_list)
18
+ end
19
+
20
+ @binary_content = binary_content.to_binary_s
21
+ end
22
+
23
+ def sample_size_list
24
+ @sample_size_list ||= media_cache.sample_size_list(track_id)
25
+ end
26
+
27
+ def all_sizes_equeal?
28
+ media_cache.uniform_sample_size?(track_id)
29
+ end
30
+
31
+ def sample_size
32
+ media_cache.first_sample_size(track_id)
33
+ end
34
+
35
+ def sample_count
36
+ media_cache.sample_count(track_id)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ module MP4::Layout
2
+ class Stts < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ private
6
+
7
+ def name
8
+ 'stts'
9
+ end
10
+
11
+ def binary_content
12
+ @binary_content ||= MP4::Binary::Stts.new(
13
+ entries_count: generate_time_to_sample_list.size,
14
+ time_to_sample: generate_time_to_sample_list,
15
+ ).to_binary_s
16
+ end
17
+
18
+ def generate_time_to_sample_list
19
+ return @generate_time_to_sample_list if defined?(@generate_time_to_sample_list)
20
+
21
+ buffer = []
22
+ media_cache.sample_delta_pairs(track_id).each do |row|
23
+ delta = row.fetch('delta')
24
+ latest = buffer.last
25
+ if latest && latest[:sample_delta] == delta
26
+ latest[:sample_count] += 1
27
+ else
28
+ buffer << { sample_count: 1, sample_delta: delta }
29
+ end
30
+ end
31
+
32
+ @generate_time_to_sample_list = buffer
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ module MP4::Layout
2
+ class Tkhd < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ private
6
+
7
+ def name
8
+ 'tkhd'
9
+ end
10
+
11
+ def binary_content
12
+ @binary_content ||= MP4::Binary::Tkhd.new(
13
+ version: 0,
14
+ flags: [0, 0, 3],
15
+ creation_time: 0,
16
+ modification_time: 0,
17
+ track_id: track_id,
18
+ duration: calculate_duration,
19
+ layer: 0,
20
+ alternate_group: 0,
21
+ volume: get_volume,
22
+ matrix: [65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824],
23
+ width: get_width,
24
+ height: get_height,
25
+ ).to_binary_s
26
+ end
27
+
28
+ def calculate_duration
29
+ seconds = media_cache.track_sample_delta_sum(track_id) / track.fetch('timescale').to_f
30
+ (seconds.round(3) * MP4::Layout::Mvhd::DEFAULT_TIMESCALE).to_i
31
+ end
32
+
33
+ def get_volume
34
+ track.fetch('volume').to_f
35
+ end
36
+
37
+ def get_width
38
+ track.fetch('width').to_f
39
+ end
40
+
41
+ def get_height
42
+ track.fetch('height').to_f
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ module MP4::Layout
2
+ class VSmhd < Atom
3
+ using ::MP4::Ext::Refinements
4
+
5
+ private
6
+
7
+ def name
8
+ return 'vmhd' if video?
9
+ return 'smhd' if sound?
10
+
11
+ raise NotImplementedError, ''
12
+ end
13
+
14
+ def binary_content
15
+ return @binary_content if defined?(@binary_content)
16
+
17
+ serializer_klass = if video?
18
+ ::MP4::Binary::Vmhd
19
+ elsif sound?
20
+ ::MP4::Binary::Smhd
21
+ else
22
+ raise "Unknown track handler type #{track.fetch('handler_type')}"
23
+ end
24
+
25
+ @binary_content = serializer_klass.new(handler_attrs).to_binary_s
26
+ end
27
+
28
+ def handler_attrs
29
+ if video?
30
+ {
31
+ flags: [0, 0, 0],
32
+ opcolor: [0, 0, 0],
33
+ }
34
+ elsif sound?
35
+ {
36
+ flags: [0, 0, 0],
37
+ }
38
+ end
39
+ end
40
+
41
+ def video?
42
+ @video ||= track.fetch('handler_type') == 'vide'
43
+ end
44
+
45
+ def sound?
46
+ @sound ||= track.fetch('handler_type') == 'soun'
47
+ end
48
+ end
49
+ end
data/lib/mp4/layout.rb ADDED
@@ -0,0 +1,3 @@
1
+ module MP4::Layout
2
+ # Namespace module
3
+ end
@@ -0,0 +1,13 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Ftyp
4
+ def self.call(cache, ftyp)
5
+ cache.add_ftyp(
6
+ major_brand: ftyp.major_brand.to_s,
7
+ minor_version: ftyp.minor_version.to_i,
8
+ compatible_brands: ftyp.compatible_brands.to_s,
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Stbl
4
+ module BinaryStsd
5
+ module_function
6
+
7
+ def call(track_id, cache, stsd_cache)
8
+ cache.add_stsd_content(track_id: track_id.to_i, content: stsd_cache.to_s)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Stbl
4
+ module Chunks
5
+ module_function
6
+
7
+ def call(track_id, cache, stco, stsc)
8
+ each_chunk(stsc, stco) do |chunk_index, samples_per_chunk, offset, description_index, sample_counter|
9
+ cache.add_chunk(
10
+ track_id: track_id,
11
+ chunk_index: chunk_index,
12
+ samples_per_chunk: samples_per_chunk,
13
+ sample_counter: sample_counter,
14
+ description_index: description_index,
15
+ offset: offset,
16
+ )
17
+ end
18
+ end
19
+
20
+ def each_chunk(stsc, stco)
21
+ sample_counter = 0
22
+
23
+ stsc.chunk_count.times do |i|
24
+ first_chunk = stsc.table[i].first_chunk.to_i
25
+ samples_per_chunk = stsc.table[i].samples_per_chunk.to_i
26
+ description_index = stsc.table[i].sample_description_index.to_i
27
+
28
+ last_chunk = i.next < stsc.chunk_count ? stsc.table[i.next].first_chunk - 1 : stco.chunks_count
29
+
30
+ (first_chunk..last_chunk).each do |chunk_index|
31
+ sample_counter += samples_per_chunk
32
+
33
+ yield(
34
+ chunk_index,
35
+ samples_per_chunk,
36
+ stco.offsets[chunk_index.pred].to_i,
37
+ description_index,
38
+ sample_counter
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,82 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Stbl
4
+ module Codec
5
+ module_function
6
+
7
+ def parse(stsd_cache)
8
+ stsd = MP4::Binary::StsdAtom.read(stsd_cache)
9
+
10
+ memoize_codecs(stsd.codecs)
11
+ end
12
+
13
+ def memoize_codecs(codecs)
14
+ codecs.map { extract_codec(_1) }
15
+ end
16
+
17
+ def extract_codec(codec)
18
+ case codec.codec_type
19
+ when 'avc1'
20
+ avc1_data = MP4::Binary::Avc1.read(codec.content)
21
+ avc1(avc1_data)
22
+ when 'mp4a'
23
+ mp4a_data = MP4::Binary::Mp4a.read(codec.content)
24
+ mp4a(mp4a_data)
25
+ when 'hev1'
26
+ hev1_data = MP4::Binary::Hev1.read(codec.content)
27
+ hev1(hev1_data)
28
+ else
29
+ raise NotImplementedError, "Unable to memoize `#{codec.codec_type}' codec, track #{track_id}"
30
+ end
31
+ end
32
+
33
+ def avc1(avc1_data)
34
+ avc_profile_indication = avc1_data.avcC.avc_profile_indication
35
+ profile_compatibility = avc1_data.avcC.profile_compatibility
36
+ avc_level_indication = avc1_data.avcC.avc_level_indication
37
+
38
+ {
39
+ par: calculate_aspect_ratio(avc1_data.width.to_i, avc1_data.height.to_i),
40
+ sar: "#{avc1_data.pasp.h_spacing}:#{avc1_data.pasp.v_spacing}",
41
+ codec: format("avc1.%02X%02X%02X", avc_profile_indication, profile_compatibility, avc_level_indication).downcase,
42
+ max_bitrate: avc1_data.bitrate.max_bitrate.to_i,
43
+ avg_bitrate: avc1_data.bitrate.avg_bitrate.to_i,
44
+ }
45
+ end
46
+
47
+ def mp4a(mp4a_data)
48
+ object_type_indication = mp4a_data.esds.es_descriptor.dec_config_desc.object_type_indication
49
+ audio_object_type_byte = mp4a_data.esds.es_descriptor.dec_config_desc.dec_specific_info.info.bytes[0]
50
+ audio_object_type = (audio_object_type_byte >> 3) & 0x1F
51
+
52
+ {
53
+ codec: format("mp4a.%02x.%d", object_type_indication, audio_object_type),
54
+ channel_count: mp4a_data.channel_count,
55
+ sample_rate: (mp4a_data.sample_rate >> 16).to_i,
56
+ max_bitrate: mp4a_data.esds.es_descriptor.dec_config_desc.max_bitrate.to_i,
57
+ avg_bitrate: mp4a_data.esds.es_descriptor.dec_config_desc.avg_bitrate.to_i,
58
+ }
59
+ end
60
+
61
+ def hev1(hev1_data)
62
+ {
63
+ par: calculate_aspect_ratio(hev1_data[:width].to_i, hev1_data[:height].to_i),
64
+ sar: "#{hev1_data[:pasp].h_spacing}:#{hev1_data[:pasp].v_spacing}",
65
+ codec: 'hev1',
66
+ max_bitrate: hev1_data[:bitrate].max_bitrate.to_i,
67
+ avg_bitrate: hev1_data[:bitrate].avg_bitrate.to_i,
68
+ }
69
+ end
70
+
71
+ private_class_method def calculate_aspect_ratio(width, height)
72
+ gcd = width.gcd(height)
73
+
74
+ aspect_ratio_width = width / gcd
75
+ aspect_ratio_height = height / gcd
76
+
77
+ "#{aspect_ratio_width}:#{aspect_ratio_height}"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,31 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Stbl
4
+ class CompositionOffsetCalculator
5
+ def initialize(ctts_table)
6
+ @ctts_table = ctts_table.dup
7
+ @ctts_table_dup = ctts_table.dup
8
+ @cached_total_samples = 0
9
+ @cached_composition_offset = nil
10
+ end
11
+
12
+ def find_composition_offset(sample_number)
13
+ return nil if @ctts_table.nil?
14
+
15
+ return @cached_composition_offset if sample_number <= @cached_total_samples
16
+
17
+ while (entry = @ctts_table.shift)
18
+ @cached_total_samples += entry.sample_count
19
+
20
+ if sample_number <= @cached_total_samples
21
+ @cached_composition_offset = entry.fetch(:composition_offset)
22
+ return @cached_composition_offset
23
+ end
24
+ end
25
+
26
+ raise "Sample Composition offset not found for sample index: #{sample_number}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Stbl
4
+ class DeltaCalculator
5
+ def initialize(stts_table)
6
+ @stts_table = stts_table.dup
7
+ @stts_table_dup = stts_table.dup
8
+ @samples_processed_count = 0
9
+ @cached_delta = nil
10
+ end
11
+
12
+ def find_delta(sample_number)
13
+ return @cached_delta if sample_number <= @samples_processed_count
14
+
15
+ while (entry = @stts_table.shift)
16
+ @samples_processed_count += entry.sample_count
17
+
18
+ if sample_number <= @samples_processed_count
19
+ @cached_delta = entry.fetch(:sample_delta)
20
+ return @cached_delta
21
+ end
22
+ end
23
+
24
+ raise "Delta not found for sample index: #{sample_number}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Stbl
4
+ class SampleToChunkCalculator
5
+ def initialize(stsc_table, total_chunks_count)
6
+ @stsc_table = stsc_table.dup
7
+ @total_chunks_count = total_chunks_count
8
+
9
+ @current_item = nil
10
+ @next_item = nil
11
+ @prev_item = nil
12
+ @counter = 0
13
+ @chunk_samples_counter = 0
14
+ @buffer = []
15
+ @intermediary_chunks_count = 0
16
+ end
17
+
18
+ def count_hidden(sample_number)
19
+ (@total_chunks_count - @counter).times do
20
+ @counter += 1
21
+ @chunk_samples_counter += @current_item[:samples_per_chunk]
22
+
23
+ return @counter if sample_number <= @chunk_samples_counter
24
+ end
25
+
26
+ nil
27
+ end
28
+
29
+ def search_in_buffer(sample_number)
30
+ @buffer.each do |samples_per_chunk|
31
+ @chunk_samples_counter += samples_per_chunk
32
+ @counter += 1
33
+ @buffer.shift
34
+
35
+ return @counter if sample_number <= @chunk_samples_counter
36
+ end
37
+
38
+ nil
39
+ end
40
+
41
+ def find_chunk_number(sample_number)
42
+ return @counter if sample_number <= @chunk_samples_counter
43
+
44
+ return count_hidden(sample_number) if @stsc_table.empty?
45
+
46
+ return search_in_buffer(sample_number) unless @buffer.empty?
47
+
48
+ @current_item = @stsc_table.shift
49
+ @next_item = @stsc_table.first || { samples_per_chunk: @current_item[:samples_per_chunk], first_chunk: @total_chunks_count }
50
+
51
+ chunks_between = [@next_item[:first_chunk] - @current_item[:first_chunk], 1].max
52
+ @buffer = Array.new(chunks_between) { @current_item[:samples_per_chunk] }
53
+
54
+ value_in_buffer = search_in_buffer(sample_number)
55
+
56
+ return value_in_buffer unless value_in_buffer.nil?
57
+
58
+ raise "Chunk not found for sample #{sample_number}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,80 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Stbl
4
+ module Samples
5
+ module_function
6
+
7
+ def call(track_id, cache, stsc, stco, stss, stts, ctts, stsz)
8
+ sample_sizes = expand_stsz(stsz)
9
+ track_timescale = cache.track(track_id).fetch('timescale').to_i
10
+ stsc_calculator = SampleToChunkCalculator.new(stsc.table, stco.offsets.size)
11
+ ctts_calculator = CompositionOffsetCalculator.new(ctts&.entries_list)
12
+ delta_calculator = DeltaCalculator.new(stts.time_to_sample)
13
+
14
+ delta_sum_counter = 0
15
+ sample_offset_counter = 0
16
+ prev_sample_size = 0
17
+ prev_chunk_index = 0
18
+
19
+ cache.transaction do
20
+ stsz.sample_count.times do |index|
21
+ sample_index = index.next
22
+ sample_delta = delta_calculator.find_delta(sample_index)
23
+
24
+ chunk_index = stsc_calculator.find_chunk_number(sample_index)
25
+
26
+ chunk_offset = stco.offsets.fetch(chunk_index - 1)
27
+ is_keyframe = stss&.sync_sample&.include?(sample_index) ? 'TRUE' : 'FALSE'
28
+
29
+ if prev_chunk_index.zero? || prev_chunk_index != chunk_index
30
+ sample_offset_counter = chunk_offset
31
+ else
32
+ sample_offset_counter += prev_sample_size
33
+ end
34
+
35
+ prev_sample_size = sample_sizes[index].to_i
36
+
37
+ cache.add_sample(
38
+ track_id: track_id.to_i,
39
+ chunk_index: chunk_index.to_i,
40
+ sample_index: sample_index.to_i,
41
+ sample_size: sample_sizes[index].to_i,
42
+ sample_offset: sample_offset_counter,
43
+ chunk_offset: chunk_offset,
44
+ original_chunk_offset: chunk_offset,
45
+ sample_delta: sample_delta.to_i,
46
+ delta_sum: delta_sum_counter,
47
+ absolute_delta_sum: (delta_sum_counter.to_f / track_timescale * 1000.0).ceil,
48
+ composition_offset: ctts_calculator.find_composition_offset(sample_index),
49
+ is_keyframe: is_keyframe,
50
+ )
51
+
52
+ delta_sum_counter += sample_delta
53
+ prev_chunk_index = chunk_index
54
+ end
55
+ end
56
+ end
57
+
58
+ def expand_stsz(stsz)
59
+ return Hash.new(stsz.sample_size) if stsz.sample_size.to_i.nonzero?
60
+
61
+ stsz.sample_sizes
62
+ end
63
+
64
+ def fetch_composition_offset_by_sample_index(ctts, sample_number)
65
+ return nil if ctts.nil?
66
+
67
+ total_samples = 0
68
+
69
+ ctts.entries_list.each do |entry|
70
+ total_samples += entry.sample_count
71
+
72
+ return entry.fetch(:composition_offset) if total_samples >= sample_number
73
+ end
74
+
75
+ raise "Sample Composition offset not found for sample index: #{sample_index}"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,98 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Track
4
+ module_function
5
+
6
+ using ::MP4::Ext::Refinements
7
+
8
+ def call(cache, track)
9
+ current_track_id = get_track_id(track)
10
+ ::MP4.logger.debug { "track #{current_track_id}: memoize started" }
11
+
12
+ memoize_overall_track_information(current_track_id, cache, track)
13
+ memoize_elst(current_track_id, cache, track)
14
+ memoize_dinf(current_track_id, cache, track)
15
+ memoize_stbl(current_track_id, cache, track)
16
+ end
17
+
18
+ def memoize_overall_track_information(track_id, cache, track)
19
+ codec_data = Stbl::Codec.parse(track.dig!(:mdia, :minf, :stbl, :stsd, :content))
20
+ handler_type = track.dig!(:mdia, :hdlr, :handler_type).to_s
21
+
22
+ cache.add_track(
23
+ track_id: track_id,
24
+ handler_type: handler_type,
25
+ handler_name: track.dig!(:mdia, :hdlr, :handler_name).to_s,
26
+ have_composition_time: track.dig!(:mdia, :minf, :stbl).fetch(:ctts, nil) ? 1 : 0,
27
+ timescale: track.dig!(:mdia, :mdhd, :timescale).to_i,
28
+ creation_time: track.dig!(:mdia, :mdhd, :creation_time).to_i,
29
+ width: track.dig!(:tkhd, :width).to_i,
30
+ height: track.dig!(:tkhd, :height).to_i,
31
+ volume: track.dig!(:tkhd, :volume).to_i,
32
+ channel_count: codec_data.dig(0, :channel_count),
33
+ par: codec_data.dig(0, :par),
34
+ sar: codec_data.dig(0, :sar),
35
+ codec: codec_data.dig(0, :codec),
36
+ sample_rate: codec_data.dig(0, :sample_rate),
37
+ max_bitrate: codec_data.dig(0, :max_bitrate),
38
+ avg_bitrate: codec_data.dig(0, :avg_bitrate),
39
+ mime_type: { 'vide' => 'video/mp4', 'soun' => 'audio/mp4' }.fetch(handler_type),
40
+ alternate_group: track.dig!(:tkhd, :alternate_group),
41
+ )
42
+
43
+ ::MP4.logger.debug { "track #{track_id}: metadata memoized (#{handler_type}, #{codec_data.dig(0, :codec)})" }
44
+ end
45
+
46
+ def memoize_dinf(track_id, cache, track)
47
+ cache.add_dinf_content(
48
+ track_id: track_id,
49
+ content: track.dig!(:mdia, :minf, :dinf, :content),
50
+ )
51
+ end
52
+
53
+ def memoize_stbl(current_track_id, cache, track)
54
+ stbl_start = Time.now
55
+
56
+ Stbl::BinaryStsd.call(current_track_id, cache, track.dig!(:mdia, :minf, :stbl, :stsd, :content))
57
+
58
+ Stbl::Chunks.call(
59
+ current_track_id, cache,
60
+ track.dig!(:mdia, :minf, :stbl, :stco),
61
+ track.dig!(:mdia, :minf, :stbl, :stsc),
62
+ )
63
+
64
+ Stbl::Samples.call(
65
+ current_track_id, cache,
66
+ track.dig!(:mdia, :minf, :stbl).fetch(:stsc, nil),
67
+ track.dig!(:mdia, :minf, :stbl).fetch(:stco, nil),
68
+ track.dig!(:mdia, :minf, :stbl).fetch(:stss, nil),
69
+ track.dig!(:mdia, :minf, :stbl).fetch(:stts, nil),
70
+ track.dig!(:mdia, :minf, :stbl).fetch(:ctts, nil),
71
+ track.dig!(:mdia, :minf, :stbl).fetch(:stsz, nil),
72
+ )
73
+
74
+ ::MP4.logger.debug { "track #{current_track_id}: stbl memoized in #{(Time.now - stbl_start).round(2)} s" }
75
+ end
76
+
77
+ def memoize_elst(current_track_id, cache, track)
78
+ elst_matrix = track.dig(:edts, :elst, :matrix)
79
+
80
+ return if elst_matrix.nil?
81
+
82
+ elst_matrix.each do |matrix|
83
+ cache.add_elst_list_item(
84
+ track_id: current_track_id,
85
+ track_duration: matrix.fetch(:segment_duration),
86
+ media_time: matrix.fetch(:media_time),
87
+ media_rate: matrix.fetch(:media_rate),
88
+ media_rate_fraction: matrix.fetch(:media_rate_fraction),
89
+ )
90
+ end
91
+ end
92
+
93
+ def get_track_id(track)
94
+ track.fetch(:tkhd).fetch(:track_id).to_i
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,11 @@
1
+ module MP4::Metadata
2
+ module Memoize
3
+ module Tracks
4
+ def self.call(cache, moov)
5
+ moov.tracks.each do |track|
6
+ Track.call(cache, track)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module MP4::Metadata
2
+ class Service
3
+ def self.call(cache:, parsed_mp4:)
4
+ new(cache).memoize!(parsed_mp4)
5
+ end
6
+
7
+ def initialize(cache)
8
+ @cache = cache
9
+ end
10
+
11
+ def memoize!(parsed_mp4)
12
+ started_at = Time.now
13
+ ::MP4.logger.info { 'memoize: starting' }
14
+
15
+ memoize_ftyp(parsed_mp4)
16
+ memoize_tracks(parsed_mp4)
17
+
18
+ ::MP4.logger.info { "memoize: done in #{(Time.now - started_at).round(2)} s" }
19
+ nil
20
+ end
21
+
22
+ def memoize_ftyp(parsed_mp4)
23
+ Memoize::Ftyp.call(@cache, parsed_mp4.ftyp)
24
+ end
25
+
26
+ def memoize_tracks(parsed_mp4)
27
+ Memoize::Tracks.call(@cache, parsed_mp4.moov)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ module MP4::Metadata
2
+ module Summary
3
+ module_function
4
+
5
+ def generate(_parsed_mp4, _cache)
6
+ {
7
+ # mdat_offset:
8
+ }
9
+ end
10
+ end
11
+ end