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,87 @@
|
|
|
1
|
+
module MP4::Constants
|
|
2
|
+
GENERIC_HEADER_SIZE = 8
|
|
3
|
+
|
|
4
|
+
TRAK_ATOMS = [
|
|
5
|
+
TKHD_ATOM = 'tkhd'.freeze,
|
|
6
|
+
TREF_ATOM = 'tref'.freeze,
|
|
7
|
+
TRGR_ATOM = 'trgr'.freeze,
|
|
8
|
+
ELST_ATOM = 'elst'.freeze,
|
|
9
|
+
META_ATOM = 'meta'.freeze,
|
|
10
|
+
MDHD_ATOM = 'mdhd'.freeze,
|
|
11
|
+
HDLR_ATOM = 'hdlr'.freeze,
|
|
12
|
+
ELNG_ATOM = 'elng'.freeze,
|
|
13
|
+
# minf
|
|
14
|
+
VMHD_ATOM = 'vmhd'.freeze,
|
|
15
|
+
SMHD_ATOM = 'smhd'.freeze,
|
|
16
|
+
HMHD_ATOM = 'hmhd'.freeze,
|
|
17
|
+
STHD_ATOM = 'sthd'.freeze,
|
|
18
|
+
NMHD_ATOM = 'nmhd'.freeze,
|
|
19
|
+
DINF_ATOM = 'dinf'.freeze, # TODO
|
|
20
|
+
# 'dref', # TODO
|
|
21
|
+
# stbl
|
|
22
|
+
STSD_ATOM = 'stsd'.freeze,
|
|
23
|
+
STTS_ATOM = 'stts'.freeze,
|
|
24
|
+
CTTS_ATOM = 'ctts'.freeze,
|
|
25
|
+
CSLG_ATOM = 'cslg'.freeze,
|
|
26
|
+
STSC_ATOM = 'stsc'.freeze,
|
|
27
|
+
STSZ_ATOM = 'stsz'.freeze,
|
|
28
|
+
STZ2_ATOM = 'stz2'.freeze,
|
|
29
|
+
STCO_ATOM = 'stco'.freeze,
|
|
30
|
+
CO64_ATOM = 'co64'.freeze,
|
|
31
|
+
STSS_ATOM = 'stss'.freeze,
|
|
32
|
+
STSH_ATOM = 'stsh'.freeze,
|
|
33
|
+
PADB_ATOM = 'padb'.freeze,
|
|
34
|
+
STDP_ATOM = 'stdp'.freeze,
|
|
35
|
+
SDTP_ATOM = 'sdtp'.freeze,
|
|
36
|
+
SBGP_ATOM = 'sbgp'.freeze,
|
|
37
|
+
# 'sgpd',
|
|
38
|
+
SUBS_ATOM = 'subs'.freeze,
|
|
39
|
+
SAIZ_ATOM = 'saiz'.freeze,
|
|
40
|
+
SAIO_ATOM = 'saio'.freeze,
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
CACHED_ATOMS = %w[dinf stbl stsd].freeze
|
|
44
|
+
|
|
45
|
+
PARSERS = {
|
|
46
|
+
'mvhd' => '::MP4::Binary::Mvhd',
|
|
47
|
+
'iods' => '::MP4::Binary::Iods',
|
|
48
|
+
'tkhd' => '::MP4::Binary::Tkhd',
|
|
49
|
+
'tref' => '::MP4::Binary::Tref',
|
|
50
|
+
'trgr' => '::MP4::Binary::Trgr',
|
|
51
|
+
'elst' => '::MP4::Binary::Elst',
|
|
52
|
+
'meta' => '::MP4::Binary::Meta',
|
|
53
|
+
'mdhd' => '::MP4::Binary::Mdhd',
|
|
54
|
+
'hdlr' => '::MP4::Binary::Hdlr',
|
|
55
|
+
'elng' => '::MP4::Binary::Elng',
|
|
56
|
+
# minf
|
|
57
|
+
'vmhd' => '::MP4::Binary::Vmhd',
|
|
58
|
+
'smhd' => '::MP4::Binary::Smhd',
|
|
59
|
+
'hmhd' => '::MP4::Binary::Hmhd',
|
|
60
|
+
'sthd' => '::MP4::Binary::Sthd',
|
|
61
|
+
'nmhd' => '::MP4::Binary::Nmhd',
|
|
62
|
+
'dinf' => '::MP4::Binary::Dinf',
|
|
63
|
+
# stbl
|
|
64
|
+
'stsd' => '::MP4::Binary::Stsd',
|
|
65
|
+
'stts' => '::MP4::Binary::Stts',
|
|
66
|
+
'ctts' => '::MP4::Binary::Ctts',
|
|
67
|
+
'cslg' => '::MP4::Binary::Cslg',
|
|
68
|
+
'stsc' => '::MP4::Binary::Stsc',
|
|
69
|
+
'stsz' => '::MP4::Binary::Stsz',
|
|
70
|
+
'stz2' => '::MP4::Binary::Stz2',
|
|
71
|
+
'stco' => '::MP4::Binary::Stco',
|
|
72
|
+
'co64' => '::MP4::Binary::Co64',
|
|
73
|
+
'stss' => '::MP4::Binary::Stss',
|
|
74
|
+
'stsh' => '::MP4::Binary::Stsh',
|
|
75
|
+
'padb' => '::MP4::Binary::Padb',
|
|
76
|
+
'stdp' => '::MP4::Binary::Stdp',
|
|
77
|
+
'sdtp' => '::MP4::Binary::Sdtp',
|
|
78
|
+
'gmhd' => '::MP4::Binary::Gmhd',
|
|
79
|
+
'sgpd' => '::MP4::Binary::Sgpd', # TODO: add support
|
|
80
|
+
'sbgp' => '::MP4::Binary::Sbgp', # TODO: add support
|
|
81
|
+
'subs' => '::MP4::Binary::Subs',
|
|
82
|
+
'saiz' => '::MP4::Binary::Saiz',
|
|
83
|
+
'saio' => '::MP4::Binary::Saio',
|
|
84
|
+
'senc' => '::MP4::Binary::Senc',
|
|
85
|
+
'udta' => '::MP4::Binary::Udta',
|
|
86
|
+
}.freeze
|
|
87
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module MP4::Ext
|
|
2
|
+
module Refinements
|
|
3
|
+
refine Hash do
|
|
4
|
+
def dig!(*keys)
|
|
5
|
+
val = self
|
|
6
|
+
keys.each { |key| val = val.fetch(key) }
|
|
7
|
+
val
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
refine Array do
|
|
12
|
+
def dig!(*keys)
|
|
13
|
+
val = self
|
|
14
|
+
keys.each { |key| val = val.fetch(key) }
|
|
15
|
+
val
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Atom
|
|
3
|
+
attr_accessor :cache, :media_cache, :track_id
|
|
4
|
+
|
|
5
|
+
def self.generate(cache, media_cache, opts = {})
|
|
6
|
+
new(cache, media_cache, opts).generate
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(cache, media_cache, opts = {})
|
|
10
|
+
@cache = cache
|
|
11
|
+
@media_cache = media_cache
|
|
12
|
+
@track_id = opts.fetch(:track_id, nil)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def generate
|
|
16
|
+
[
|
|
17
|
+
*header,
|
|
18
|
+
binary_content,
|
|
19
|
+
].join
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def header
|
|
25
|
+
[
|
|
26
|
+
BinData::Uint32be.new(size).to_binary_s,
|
|
27
|
+
BinData::String.new(name).to_binary_s,
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def name
|
|
32
|
+
raise NotImplementedError.new(message: "#{__callee__} not implemented")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def size
|
|
36
|
+
::MP4::Constants::GENERIC_HEADER_SIZE + binary_content.size
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def binary_content
|
|
40
|
+
raise NotImplementedError.new(message: "#{__callee__} not implemented")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def track
|
|
44
|
+
@track ||= media_cache.track(track_id)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
module Builder
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
def call(cache, media_cache, source)
|
|
6
|
+
container = Container.new(root: true)
|
|
7
|
+
|
|
8
|
+
container.add_atom Ftyp.generate(cache, media_cache)
|
|
9
|
+
container.add_atom Free.generate(cache, media_cache, size: container.size)
|
|
10
|
+
container.add_atom Mdat.generate(cache, media_cache, source: source)
|
|
11
|
+
container.add_atom Moov.generate(cache, media_cache)
|
|
12
|
+
|
|
13
|
+
container
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module MP4::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
|
+
::MP4::Constants::GENERIC_HEADER_SIZE + binary_descendants.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,35 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Ctts < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'ctts'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Ctts.new(
|
|
13
|
+
entry_count: generate_entries_list.size,
|
|
14
|
+
entries_list: generate_entries_list,
|
|
15
|
+
).to_binary_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate_entries_list
|
|
19
|
+
return @generate_entries_list if defined?(@generate_entries_list)
|
|
20
|
+
|
|
21
|
+
buffer = []
|
|
22
|
+
media_cache.sample_c_delta_pairs(track_id).each do |row|
|
|
23
|
+
c_delta = row.fetch('c_delta')
|
|
24
|
+
latest = buffer.last
|
|
25
|
+
if latest && latest[:composition_offset] == c_delta
|
|
26
|
+
latest[:sample_count] += 1
|
|
27
|
+
else
|
|
28
|
+
buffer << { sample_count: 1, composition_offset: c_delta }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@generate_entries_list = buffer
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Dinf < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'dinf'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Dinf.new(content: cache.dinf_content(track_id)).to_binary_s
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Elst < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'elst'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Elst.new(
|
|
13
|
+
version: 0,
|
|
14
|
+
flags: [0, 0, 0],
|
|
15
|
+
entries_count: 1,
|
|
16
|
+
matrix: [compile_matrix],
|
|
17
|
+
).to_binary_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def compile_matrix
|
|
21
|
+
{
|
|
22
|
+
segment_duration: calculate_duration,
|
|
23
|
+
media_time: 0,
|
|
24
|
+
media_rate: 1,
|
|
25
|
+
}
|
|
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
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Free < Atom
|
|
3
|
+
|
|
4
|
+
def initialize(_cache, _media_cache, opts = {})
|
|
5
|
+
@size = opts.fetch(:size)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def name
|
|
9
|
+
'free'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def binary_content
|
|
13
|
+
@binary_content ||= Array.new(50 - @size - MP4::Constants::GENERIC_HEADER_SIZE) { "\x00" }.join
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def memoized_ftyp
|
|
17
|
+
@memoized_ftyp ||= @cache.ftyp
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module MP4::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,20 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Hdlr < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'hdlr'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Hdlr.new(
|
|
13
|
+
version: 0,
|
|
14
|
+
flags: [0, 0, 0],
|
|
15
|
+
handler_type: track.fetch('handler_type'),
|
|
16
|
+
handler_name: track.fetch('handler_name'),
|
|
17
|
+
).to_binary_s
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Mdat < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
attr_reader :source
|
|
6
|
+
|
|
7
|
+
def initialize(cache, media_cache, opts = {})
|
|
8
|
+
super(cache, media_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
|
+
source.reset_seek
|
|
23
|
+
|
|
24
|
+
media_cache.each_sample_ordered_across_tracks do |chunk|
|
|
25
|
+
source.set_seek chunk.fetch('offset')
|
|
26
|
+
buffer.write source.read(chunk.fetch('size'))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
buffer.seek(0)
|
|
30
|
+
@binary_content = BinData::String.new(buffer.read).to_binary_s
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Mdhd < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'mdhd'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Mdhd.new(
|
|
13
|
+
version: 0,
|
|
14
|
+
flags: [0, 0, 0],
|
|
15
|
+
creation_time: 0,
|
|
16
|
+
modification_time: 0,
|
|
17
|
+
timescale: track.fetch('timescale'),
|
|
18
|
+
duration: calculate_duration,
|
|
19
|
+
language: [0, 0, 0],
|
|
20
|
+
).to_binary_s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def calculate_duration
|
|
24
|
+
media_cache.track_sample_delta_sum(track_id)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
module Moov
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
def generate(cache, media_cache)
|
|
6
|
+
moov = Container.new(name: 'moov')
|
|
7
|
+
moov.add_atom Mvhd.generate(cache, media_cache)
|
|
8
|
+
|
|
9
|
+
media_cache.tracks_asc.each do |track|
|
|
10
|
+
moov.add_container(:trak) { |trak| build_trak(cache, media_cache, trak, track) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
moov.generate
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_trak(cache, media_cache, trak, track)
|
|
19
|
+
track_id = track.fetch('track_id')
|
|
20
|
+
|
|
21
|
+
trak.add_atom Tkhd.generate(cache, media_cache, track_id: track_id)
|
|
22
|
+
trak.add_container(:edts) { |edts| edts.add_atom Elst.generate(cache, media_cache, track_id: track_id) }
|
|
23
|
+
trak.add_container(:mdia) { |mdia| build_mdia(cache, media_cache, mdia, track) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def build_mdia(cache, media_cache, mdia, track)
|
|
27
|
+
track_id = track.fetch('track_id')
|
|
28
|
+
|
|
29
|
+
mdia.add_atom Mdhd.generate(cache, media_cache, track_id: track_id)
|
|
30
|
+
mdia.add_atom Hdlr.generate(cache, media_cache, track_id: track_id)
|
|
31
|
+
mdia.add_container(:minf) { |minf| build_minf(cache, media_cache, minf, track) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build_minf(cache, media_cache, minf, track)
|
|
35
|
+
track_id = track.fetch('track_id')
|
|
36
|
+
|
|
37
|
+
minf.add_atom VSmhd.generate(cache, media_cache, track_id: track_id)
|
|
38
|
+
minf.add_atom Dinf.generate(cache, media_cache, track_id: track_id)
|
|
39
|
+
minf.add_container(:stbl) { |stbl| build_stbl(cache, media_cache, stbl, track) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def build_stbl(cache, media_cache, stbl, track)
|
|
43
|
+
track_id = track.fetch('track_id')
|
|
44
|
+
|
|
45
|
+
stbl.add_atom Stsd.generate(cache, media_cache, track_id: track_id)
|
|
46
|
+
stbl.add_atom Stts.generate(cache, media_cache, track_id: track_id)
|
|
47
|
+
|
|
48
|
+
if track['handler_type'] == 'vide'
|
|
49
|
+
stbl.add_atom Stss.generate(cache, media_cache, track_id: track_id)
|
|
50
|
+
stbl.add_atom Ctts.generate(cache, media_cache, track_id: track_id) if track['have_composition_time'].to_i.nonzero?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
stbl.add_atom Stsc.generate(cache, media_cache, track_id: track_id)
|
|
54
|
+
stbl.add_atom Stsz.generate(cache, media_cache, track_id: track_id)
|
|
55
|
+
stbl.add_atom Stco.generate(cache, media_cache, track_id: track_id)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module MP4::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: calculate_timescale,
|
|
20
|
+
duration: calculate_duration,
|
|
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_timescale
|
|
29
|
+
DEFAULT_TIMESCALE
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def calculate_duration
|
|
33
|
+
durations = media_cache.tracks.map do |tr|
|
|
34
|
+
tid = tr.fetch('track_id')
|
|
35
|
+
track_timescale = cache.track(tid).fetch('timescale').to_f
|
|
36
|
+
seconds = media_cache.track_sample_delta_sum(tid) / track_timescale
|
|
37
|
+
(seconds.round(3) * calculate_timescale).to_i
|
|
38
|
+
end
|
|
39
|
+
durations.max.to_i.nonzero? || DEFAULT_TIMESCALE
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def calculate_next_track_id
|
|
43
|
+
cache.next_track_id
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Stco < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'stco'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Stco.new(
|
|
13
|
+
chunks_count: generate_offsets_list.size,
|
|
14
|
+
offsets: generate_offsets_list,
|
|
15
|
+
).to_binary_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate_offsets_list
|
|
19
|
+
return @generate_offsets_list if defined?(@generate_offsets_list)
|
|
20
|
+
|
|
21
|
+
offset_counter = 50
|
|
22
|
+
buffer = []
|
|
23
|
+
|
|
24
|
+
media_cache.each_sample_ordered_across_tracks do |sample|
|
|
25
|
+
buffer << offset_counter if sample.fetch('track_id') == track_id
|
|
26
|
+
offset_counter += sample.fetch('size')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@generate_offsets_list = buffer
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def offset_start_point
|
|
33
|
+
@offset_start_point ||= media_cache.min_sample_offset
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
# 794164
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Stsc < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'stsc'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Stsc.new(
|
|
13
|
+
chunk_count: sample_to_chunk_table.size,
|
|
14
|
+
table: sample_to_chunk_table,
|
|
15
|
+
).to_binary_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def sample_to_chunk_table
|
|
19
|
+
return @sample_to_chunk_table if defined?(@sample_to_chunk_table)
|
|
20
|
+
|
|
21
|
+
chunk_indices = media_cache.sample_chunk_indices(track_id)
|
|
22
|
+
@sample_to_chunk_table = chunk_indices.each_with_index.map do |_chunk_index, i|
|
|
23
|
+
{ first_chunk: i + 1, samples_per_chunk: 1, sample_description_index: 1 }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Stsd < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'stsd'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Stsd.new(content: cache.stsd_content(track_id)).to_binary_s
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module MP4::Layout
|
|
2
|
+
class Stss < Atom
|
|
3
|
+
using ::MP4::Ext::Refinements
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
'stss'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def binary_content
|
|
12
|
+
@binary_content ||= MP4::Binary::Stss.new(
|
|
13
|
+
sync_samples_count: generate_sync_sample_list.size,
|
|
14
|
+
sync_sample: generate_sync_sample_list,
|
|
15
|
+
).to_binary_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate_sync_sample_list
|
|
19
|
+
@generate_sync_sample_list ||= media_cache.sync_sample_indices(track_id)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|