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.
- checksums.yaml +7 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +149 -0
- data/README.md +195 -0
- data/Rakefile +8 -0
- data/bin/build_mp4_xlsx.rb +35 -0
- data/bin/divide_mp4_to_segments.rb +52 -0
- data/lib/m4s/binary/ftyp.rb +11 -0
- data/lib/m4s/binary/mdat.rb +0 -0
- data/lib/m4s/binary/mfhd.rb +18 -0
- data/lib/m4s/binary/mvhd.rb +39 -0
- data/lib/m4s/binary/sidx.rb +38 -0
- data/lib/m4s/binary/styp.rb +17 -0
- data/lib/m4s/binary/tfdt.rb +25 -0
- data/lib/m4s/binary/tfhd.rb +38 -0
- data/lib/m4s/binary/trex.rb +25 -0
- data/lib/m4s/binary/trun.rb +30 -0
- data/lib/m4s/dash/manifest.rb +149 -0
- data/lib/m4s/layout/atom.rb +44 -0
- data/lib/m4s/layout/builder.rb +32 -0
- data/lib/m4s/layout/container.rb +59 -0
- data/lib/m4s/layout/ctts.rb +20 -0
- data/lib/m4s/layout/dinf.rb +17 -0
- data/lib/m4s/layout/elst.rb +33 -0
- data/lib/m4s/layout/ftyp.rb +21 -0
- data/lib/m4s/layout/hdlr.rb +22 -0
- data/lib/m4s/layout/init_mp4_builder.rb +57 -0
- data/lib/m4s/layout/mdat.rb +61 -0
- data/lib/m4s/layout/mdhd.rb +25 -0
- data/lib/m4s/layout/mfhd.rb +17 -0
- data/lib/m4s/layout/mvhd.rb +32 -0
- data/lib/m4s/layout/sidx.rb +36 -0
- data/lib/m4s/layout/stco.rb +21 -0
- data/lib/m4s/layout/stsc.rb +20 -0
- data/lib/m4s/layout/stsd.rb +17 -0
- data/lib/m4s/layout/stss.rb +20 -0
- data/lib/m4s/layout/stsz.rb +19 -0
- data/lib/m4s/layout/stts.rb +20 -0
- data/lib/m4s/layout/styp.rb +17 -0
- data/lib/m4s/layout/tfdt.rb +21 -0
- data/lib/m4s/layout/tfhd.rb +17 -0
- data/lib/m4s/layout/tkhd.rb +34 -0
- data/lib/m4s/layout/trex.rb +33 -0
- data/lib/m4s/layout/trun.rb +89 -0
- data/lib/m4s/layout/vsmhd.rb +51 -0
- data/lib/m4s/m3u8/general_playlist.rb +52 -0
- data/lib/m4s/m3u8/media_playlist.rb +32 -0
- data/lib/mp4/binary/avc1.rb +98 -0
- data/lib/mp4/binary/box_head.rb +8 -0
- data/lib/mp4/binary/co64.rb +11 -0
- data/lib/mp4/binary/codec.rb +8 -0
- data/lib/mp4/binary/ctts.rb +14 -0
- data/lib/mp4/binary/ctts_reader.rb +5 -0
- data/lib/mp4/binary/dinf.rb +5 -0
- data/lib/mp4/binary/dref.rb +5 -0
- data/lib/mp4/binary/elst.rb +16 -0
- data/lib/mp4/binary/fixed_point16.rb +19 -0
- data/lib/mp4/binary/fixed_point8.rb +19 -0
- data/lib/mp4/binary/ftyp.rb +9 -0
- data/lib/mp4/binary/gmhd.rb +5 -0
- data/lib/mp4/binary/hdlr.rb +13 -0
- data/lib/mp4/binary/hev1.rb +73 -0
- data/lib/mp4/binary/hmhd.rb +14 -0
- data/lib/mp4/binary/iods.rb +5 -0
- data/lib/mp4/binary/mdhd.rb +18 -0
- data/lib/mp4/binary/mp4a.rb +76 -0
- data/lib/mp4/binary/mvhd.rb +39 -0
- data/lib/mp4/binary/nmhd.rb +8 -0
- data/lib/mp4/binary/saio.rb +25 -0
- data/lib/mp4/binary/saiz.rb +17 -0
- data/lib/mp4/binary/sbgp.rb +5 -0
- data/lib/mp4/binary/sdtp.rb +18 -0
- data/lib/mp4/binary/senc.rb +12 -0
- data/lib/mp4/binary/sgpd.rb +5 -0
- data/lib/mp4/binary/smhd.rb +11 -0
- data/lib/mp4/binary/stco.rb +13 -0
- data/lib/mp4/binary/stco_reader.rb +5 -0
- data/lib/mp4/binary/sthd.rb +8 -0
- data/lib/mp4/binary/stsc.rb +15 -0
- data/lib/mp4/binary/stsc_reader.rb +5 -0
- data/lib/mp4/binary/stsd.rb +5 -0
- data/lib/mp4/binary/stsd_atom.rb +11 -0
- data/lib/mp4/binary/stsd_reader.rb +5 -0
- data/lib/mp4/binary/stss.rb +13 -0
- data/lib/mp4/binary/stss_reader.rb +5 -0
- data/lib/mp4/binary/stsz.rb +18 -0
- data/lib/mp4/binary/stsz_reader.rb +5 -0
- data/lib/mp4/binary/stts.rb +14 -0
- data/lib/mp4/binary/stts_reader.rb +5 -0
- data/lib/mp4/binary/tkhd.rb +46 -0
- data/lib/mp4/binary/udta.rb +5 -0
- data/lib/mp4/binary/vmhd.rb +11 -0
- data/lib/mp4/cache/memory.rb +328 -0
- data/lib/mp4/cache/sqlite.rb +348 -0
- data/lib/mp4/cache.rb +13 -0
- data/lib/mp4/constants.rb +87 -0
- data/lib/mp4/ext/refinements.rb +19 -0
- data/lib/mp4/layout/atom.rb +47 -0
- data/lib/mp4/layout/base.rb +10 -0
- data/lib/mp4/layout/builder.rb +16 -0
- data/lib/mp4/layout/container.rb +59 -0
- data/lib/mp4/layout/ctts.rb +35 -0
- data/lib/mp4/layout/dinf.rb +15 -0
- data/lib/mp4/layout/elst.rb +33 -0
- data/lib/mp4/layout/free.rb +20 -0
- data/lib/mp4/layout/ftyp.rb +21 -0
- data/lib/mp4/layout/hdlr.rb +20 -0
- data/lib/mp4/layout/mdat.rb +33 -0
- data/lib/mp4/layout/mdhd.rb +27 -0
- data/lib/mp4/layout/moov.rb +58 -0
- data/lib/mp4/layout/mvhd.rb +46 -0
- data/lib/mp4/layout/stco.rb +37 -0
- data/lib/mp4/layout/stsc.rb +27 -0
- data/lib/mp4/layout/stsd.rb +15 -0
- data/lib/mp4/layout/stss.rb +22 -0
- data/lib/mp4/layout/stsz.rb +39 -0
- data/lib/mp4/layout/stts.rb +35 -0
- data/lib/mp4/layout/tkhd.rb +45 -0
- data/lib/mp4/layout/vsmhd.rb +49 -0
- data/lib/mp4/layout.rb +3 -0
- data/lib/mp4/metadata/memoize/ftyp.rb +13 -0
- data/lib/mp4/metadata/memoize/stbl/binary_stsd.rb +13 -0
- data/lib/mp4/metadata/memoize/stbl/chunks.rb +46 -0
- data/lib/mp4/metadata/memoize/stbl/codec.rb +82 -0
- data/lib/mp4/metadata/memoize/stbl/composition_offset_calculator.rb +31 -0
- data/lib/mp4/metadata/memoize/stbl/delta_calculator.rb +29 -0
- data/lib/mp4/metadata/memoize/stbl/sample_to_chunk_calculator.rb +63 -0
- data/lib/mp4/metadata/memoize/stbl/samples.rb +80 -0
- data/lib/mp4/metadata/memoize/track.rb +98 -0
- data/lib/mp4/metadata/memoize/tracks.rb +11 -0
- data/lib/mp4/metadata/service.rb +30 -0
- data/lib/mp4/metadata/summary.rb +11 -0
- data/lib/mp4/nolan/service/sound.rb +36 -0
- data/lib/mp4/nolan/service/track.rb +9 -0
- data/lib/mp4/nolan/service/video.rb +35 -0
- data/lib/mp4/nolan/service.rb +84 -0
- data/lib/mp4/nolan.rb +3 -0
- data/lib/mp4/parser/main_boxes.rb +43 -0
- data/lib/mp4/parser/moov.rb +40 -0
- data/lib/mp4/parser/service.rb +47 -0
- data/lib/mp4/parser/track.rb +56 -0
- data/lib/mp4/rb/version.rb +7 -0
- data/lib/mp4/rb.rb +10 -0
- data/lib/mp4/source/base.rb +20 -0
- data/lib/mp4/source/local.rb +79 -0
- data/lib/mp4/source/s3.rb +118 -0
- data/lib/mp4/source/url.rb +130 -0
- data/lib/mp4/source.rb +23 -0
- data/lib/mp4/structure/service.rb +13 -0
- data/lib/mp4/xlsx/builder.rb +44 -0
- data/lib/mp4.rb +222 -0
- data/mp4-rb.gemspec +68 -0
- data/sig/mp4/rb.rbs +6 -0
- data/todo.txt +4 -0
- 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,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,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,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
|