Class: MP4::Cache::Memory

Inherits:
Object
  • Object
show all
Defined in:
lib/mp4/cache/memory.rb

Overview

Drop-in metadata cache backed by Ruby data structures.

Implements the method-style API (add_track, tracks, chunks, segments, add_sample, each_sample, add_ftyp/ftyp, add_dinf_content/dinf_content, add_stsd_content/stsd_content, add_elst_list_item/elst_entries, add_segment_sample, segment_samples).

Deliberately does NOT emulate raw SQL: #query and #execute raise — see docs/CACHE_MIGRATION.md for the list of call sites (layout writers, Nolan segmentation, DASH manifest) that still speak SQL against MP4::Cache::SQLite and need method-API replacements before this backend can drive the full pipeline.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(_location = nil) ⇒ Memory

Returns a new instance of Memory.



15
16
17
18
19
20
21
22
23
24
# File 'lib/mp4/cache/memory.rb', line 15

def initialize(_location = nil)
  @ftyp            = nil
  @tracks          = {}
  @chunks          = Hash.new { |h, k| h[k] = [] }
  @samples         = Hash.new { |h, k| h[k] = [] }
  @segment_samples = Hash.new { |h, k| h[k] = [] }
  @elst            = Hash.new { |h, k| h[k] = [] }
  @dinf            = {}
  @stsd            = {}
end

Instance Attribute Details

#ftypObject (readonly)

Returns the value of attribute ftyp.



45
46
47
# File 'lib/mp4/cache/memory.rb', line 45

def ftyp
  @ftyp
end

Instance Method Details

#add_chunk(track_id:, chunk_index:, samples_per_chunk:, sample_counter:, description_index:, offset:) ⇒ Object



83
84
85
86
87
88
89
90
91
92
# File 'lib/mp4/cache/memory.rb', line 83

def add_chunk(track_id:, chunk_index:, samples_per_chunk:, sample_counter:, description_index:, offset:)
  @chunks[track_id] << {
    'track_id'          => track_id,
    'chunk_index'       => chunk_index,
    'samples_per_chunk' => samples_per_chunk,
    'sample_counter'    => sample_counter,
    'description_index' => description_index,
    'offset'            => offset,
  }
end

#add_dinf_content(track_id:, content:) ⇒ Object



162
163
164
# File 'lib/mp4/cache/memory.rb', line 162

def add_dinf_content(track_id:, content:)
  @dinf[track_id.to_i] = content
end

#add_elst_list_item(track_id:, track_duration:, media_time:, media_rate:, media_rate_fraction:) ⇒ Object



148
149
150
151
152
153
154
155
156
# File 'lib/mp4/cache/memory.rb', line 148

def add_elst_list_item(track_id:, track_duration:, media_time:, media_rate:, media_rate_fraction:)
  @elst[track_id] << {
    'track_id'            => track_id,
    'track_duration'      => track_duration,
    'media_time'          => media_time,
    'media_rate'          => media_rate,
    'media_rate_fraction' => media_rate_fraction,
  }
end

#add_ftyp(major_brand:, minor_version:, compatible_brands:) ⇒ Object



37
38
39
40
41
42
43
# File 'lib/mp4/cache/memory.rb', line 37

def add_ftyp(major_brand:, minor_version:, compatible_brands:)
  @ftyp = {
    'major_brand'       => major_brand.to_s,
    'minor_version'     => minor_version.to_i,
    'compatible_brands' => compatible_brands.to_s,
  }
end

#add_sample(track_id:, chunk_index:, sample_index:, chunk_offset:, sample_delta:, delta_sum:, absolute_delta_sum:, composition_offset:, sample_size:, sample_offset: 0, original_chunk_offset: 0, is_keyframe: 'FALSE') ⇒ Object



102
103
104
105
106
107
108
109
110
111
# File 'lib/mp4/cache/memory.rb', line 102

def add_sample(track_id:, chunk_index:, sample_index:, chunk_offset:, sample_delta:, delta_sum:, absolute_delta_sum:, composition_offset:, sample_size:, sample_offset: 0, original_chunk_offset: 0, is_keyframe: 'FALSE')
  @samples[track_id] << sample_row(
    track_id: track_id, chunk_index: chunk_index, sample_index: sample_index,
    sample_size: sample_size, sample_offset: sample_offset,
    chunk_offset: chunk_offset, original_chunk_offset: original_chunk_offset,
    sample_delta: sample_delta, delta_sum: delta_sum,
    absolute_delta_sum: absolute_delta_sum,
    composition_offset: composition_offset, is_keyframe: is_keyframe,
  )
end

#add_segment_sample(track_id:, segment_index:, chunk_index:, sample_index:, chunk_offset:, sample_delta:, delta_sum:, absolute_delta_sum:, composition_offset:, sample_size:, sample_offset: 0, original_chunk_offset: 0, is_keyframe: 'FALSE') ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/mp4/cache/memory.rb', line 121

def add_segment_sample(track_id:, segment_index:, chunk_index:, sample_index:, chunk_offset:, sample_delta:, delta_sum:, absolute_delta_sum:, composition_offset:, sample_size:, sample_offset: 0, original_chunk_offset: 0, is_keyframe: 'FALSE')
  row = sample_row(
    track_id: track_id, chunk_index: chunk_index, sample_index: sample_index,
    sample_size: sample_size, sample_offset: sample_offset,
    chunk_offset: chunk_offset, original_chunk_offset: original_chunk_offset,
    sample_delta: sample_delta, delta_sum: delta_sum,
    absolute_delta_sum: absolute_delta_sum,
    composition_offset: composition_offset, is_keyframe: is_keyframe,
  )
  row['segment_index'] = segment_index
  @segment_samples[track_id] << row
end

#add_stsd_content(track_id:, content:) ⇒ Object



170
171
172
# File 'lib/mp4/cache/memory.rb', line 170

def add_stsd_content(track_id:, content:)
  @stsd[track_id.to_i] = content
end

#add_track(**opts) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/mp4/cache/memory.rb', line 47

def add_track(**opts)
  row = {
    'track_id'              => opts.fetch(:track_id),
    'handler_type'          => opts.fetch(:handler_type),
    'handler_name'          => opts.fetch(:handler_name),
    'have_composition_time' => opts.fetch(:have_composition_time),
    'timescale'             => opts.fetch(:timescale),
    'creation_time'         => opts.fetch(:creation_time),
    'width'                 => opts.fetch(:width),
    'height'                => opts.fetch(:height),
    'volume'                => opts.fetch(:volume),
    'channel_count'         => opts.fetch(:channel_count),
    'par'                   => opts.fetch(:par),
    'sar'                   => opts.fetch(:sar),
    'codec'                 => opts.fetch(:codec),
    'max_bitrate'           => opts.fetch(:max_bitrate),
    'avg_bitrate'           => opts.fetch(:avg_bitrate),
    'sample_rate'           => opts.fetch(:sample_rate),
    'mime_type'             => opts.fetch(:mime_type),
    'alternate_group'       => opts.fetch(:alternate_group),
  }
  @tracks[row['track_id']] = row
end

#chunks(track_id) ⇒ Object



94
95
96
# File 'lib/mp4/cache/memory.rb', line 94

def chunks(track_id)
  @chunks[track_id]
end

#dinf_content(track_id) ⇒ Object



166
167
168
# File 'lib/mp4/cache/memory.rb', line 166

def dinf_content(track_id)
  @dinf[track_id.to_i]
end

#each_sample(track_id, &block) ⇒ Object



113
114
115
# File 'lib/mp4/cache/memory.rb', line 113

def each_sample(track_id, &block)
  @samples[track_id].each(&block)
end

#each_sample_ordered_across_tracksObject



264
265
266
267
# File 'lib/mp4/cache/memory.rb', line 264

def each_sample_ordered_across_tracks
  rows = @samples.values.flatten.sort_by { |s| [s['sample_index'], s['track_id']] }
  rows.each { |s| yield({ 'track_id' => s['track_id'], 'size' => s['size'], 'offset' => s['offset'] }) }
end

#each_track(&block) ⇒ Object



98
99
100
# File 'lib/mp4/cache/memory.rb', line 98

def each_track(&block)
  @chunks.keys.uniq.each(&block)
end

#elst_entries(track_id) ⇒ Object



158
159
160
# File 'lib/mp4/cache/memory.rb', line 158

def elst_entries(track_id)
  @elst[track_id]
end

#first_sample(track_id) ⇒ Object



191
192
193
# File 'lib/mp4/cache/memory.rb', line 191

def first_sample(track_id)
  @samples[track_id].min_by { |s| s['sample_index'] }
end

#first_sample_size(track_id) ⇒ Object



247
248
249
250
# File 'lib/mp4/cache/memory.rb', line 247

def first_sample_size(track_id)
  first = first_sample(track_id)
  first && first['size']
end

#first_video_track_idObject



178
179
180
# File 'lib/mp4/cache/memory.rb', line 178

def first_video_track_id
  @tracks.values.select { |t| t['handler_type'] == 'vide' }.map { |t| t['track_id'] }.min
end

#keyframes_grouped_by_second(track_id) ⇒ Object



222
223
224
225
226
227
228
229
230
231
# File 'lib/mp4/cache/memory.rb', line 222

def keyframes_grouped_by_second(track_id)
  buckets = {}
  @samples[track_id].each do |s|
    next unless s['keyframe'] == 'TRUE'

    bucket = s['delta_sum_millisec'].to_i / 1000
    buckets[bucket] ||= { 'sample_index' => s['sample_index'], 'delta_sum' => s['delta_sum'] }
  end
  buckets.values
end

#last_sample(track_id) ⇒ Object



195
196
197
# File 'lib/mp4/cache/memory.rb', line 195

def last_sample(track_id)
  @samples[track_id].max_by { |s| s['sample_index'] }
end

#max_segment_duration_ticks(track_id) ⇒ Object



297
298
299
300
# File 'lib/mp4/cache/memory.rb', line 297

def max_segment_duration_ticks(track_id)
  by_seg = segment_duration_ticks_by_index(track_id)
  by_seg.map { |r| r['duration'] }.max
end

#min_sample_offsetObject



269
270
271
# File 'lib/mp4/cache/memory.rb', line 269

def min_sample_offset
  @samples.values.flatten.map { |s| s['offset'] }.min
end

#next_track_idObject



182
183
184
185
# File 'lib/mp4/cache/memory.rb', line 182

def next_track_id
  max = @tracks.keys.max
  max && max + 1
end

#query(*_args) ⇒ Object Also known as: execute

Raises:

  • (NotImplementedError)


30
31
32
33
34
# File 'lib/mp4/cache/memory.rb', line 30

def query(*_args)
  raise NotImplementedError,
        'MP4::Cache::Memory does not support raw SQL queries. ' \
        'Use the method-style API instead (see docs/CACHE_MIGRATION.md).'
end

#sample_at(track_id, sample_index) ⇒ Object



199
200
201
# File 'lib/mp4/cache/memory.rb', line 199

def sample_at(track_id, sample_index)
  @samples[track_id].find { |s| s['sample_index'] == sample_index }
end

#sample_c_delta_pairs(track_id) ⇒ Object



256
257
258
# File 'lib/mp4/cache/memory.rb', line 256

def sample_c_delta_pairs(track_id)
  @samples[track_id].sort_by { |s| s['sample_index'] }.map { |s| { 'sample_index' => s['sample_index'], 'c_delta' => s['c_delta'] } }
end

#sample_chunk_indices(track_id) ⇒ Object



260
261
262
# File 'lib/mp4/cache/memory.rb', line 260

def sample_chunk_indices(track_id)
  @samples[track_id].sort_by { |s| s['sample_index'] }.map { |s| s['chunk_index'] }
end

#sample_count(track_id) ⇒ Object



187
188
189
# File 'lib/mp4/cache/memory.rb', line 187

def sample_count(track_id)
  @samples[track_id].size
end

#sample_delta_pairs(track_id) ⇒ Object



252
253
254
# File 'lib/mp4/cache/memory.rb', line 252

def sample_delta_pairs(track_id)
  @samples[track_id].sort_by { |s| s['sample_index'] }.map { |s| { 'sample_index' => s['sample_index'], 'delta' => s['delta'] } }
end

#sample_delta_sum_ms(track_id, sample_index) ⇒ Object



203
204
205
206
# File 'lib/mp4/cache/memory.rb', line 203

def sample_delta_sum_ms(track_id, sample_index)
  row = sample_at(track_id, sample_index)
  row && row['delta_sum_millisec']
end

#sample_size_list(track_id) ⇒ Object



239
240
241
# File 'lib/mp4/cache/memory.rb', line 239

def sample_size_list(track_id)
  @samples[track_id].sort_by { |s| s['sample_index'] }.map { |s| s['size'] }
end

#samples(track_id) ⇒ Object



117
118
119
# File 'lib/mp4/cache/memory.rb', line 117

def samples(track_id)
  @samples[track_id]
end

#samples_between_ms(track_id, from_ms, to_ms) ⇒ Object



233
234
235
236
237
# File 'lib/mp4/cache/memory.rb', line 233

def samples_between_ms(track_id, from_ms, to_ms)
  @samples[track_id]
    .select { |s| s['delta_sum_millisec'] >= from_ms && s['delta_sum_millisec'] <= to_ms }
    .sort_by { |s| s['sample_index'] }
end

#segment_bytes_sizes_offsets(track_id, segment_index) ⇒ Object



302
303
304
305
306
307
# File 'lib/mp4/cache/memory.rb', line 302

def segment_bytes_sizes_offsets(track_id, segment_index)
  @segment_samples[track_id]
    .select { |s| s['segment_index'] == segment_index }
    .sort_by { |s| s['sample_index'] }
    .map { |s| { 'size' => s['size'], 'offset' => s['offset'] } }
end

#segment_duration_ticks(track_id, segment_index) ⇒ Object



273
274
275
276
277
# File 'lib/mp4/cache/memory.rb', line 273

def segment_duration_ticks(track_id, segment_index)
  @segment_samples[track_id]
    .select { |s| s['segment_index'] == segment_index }
    .sum { |s| s['delta'].to_i }
end

#segment_duration_ticks_by_index(track_id) ⇒ Object



292
293
294
295
# File 'lib/mp4/cache/memory.rb', line 292

def segment_duration_ticks_by_index(track_id)
  grouped = @segment_samples[track_id].group_by { |s| s['segment_index'] }
  grouped.map { |i, ss| { 'segment_index' => i, 'duration' => ss.sum { |s| s['delta'].to_i } } }.sort_by { |r| r['segment_index'] }
end

#segment_earliest_presentation_ticks(track_id, segment_index) ⇒ Object



279
280
281
282
283
284
# File 'lib/mp4/cache/memory.rb', line 279

def segment_earliest_presentation_ticks(track_id, segment_index)
  @segment_samples[track_id]
    .select { |s| s['segment_index'] == segment_index }
    .map { |s| s['delta_sum'] }
    .min
end

#segment_samples(track_id, segment_index) ⇒ Object



142
143
144
145
146
# File 'lib/mp4/cache/memory.rb', line 142

def segment_samples(track_id, segment_index)
  @segment_samples[track_id]
    .select { |s| s['segment_index'] == segment_index }
    .sort_by { |s| s['sample_index'] }
end

#segment_size_bytes(track_id, segment_index) ⇒ Object



286
287
288
289
290
# File 'lib/mp4/cache/memory.rb', line 286

def segment_size_bytes(track_id, segment_index)
  @segment_samples[track_id]
    .select { |s| s['segment_index'] == segment_index }
    .sum { |s| s['size'].to_i }
end

#segments(track_id) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/mp4/cache/memory.rb', line 134

def segments(track_id)
  @segment_samples[track_id]
    .map { |s| s['segment_index'] }
    .uniq
    .sort
    .map { |i| { 'segment_index' => i } }
end

#stsd_content(track_id) ⇒ Object



174
175
176
# File 'lib/mp4/cache/memory.rb', line 174

def stsd_content(track_id)
  @stsd[track_id.to_i]
end

#sum_delta_between(track_id, from_sample_index, to_sample_index) ⇒ Object



208
209
210
211
212
# File 'lib/mp4/cache/memory.rb', line 208

def sum_delta_between(track_id, from_sample_index, to_sample_index)
  @samples[track_id]
    .select { |s| s['sample_index'] >= from_sample_index && s['sample_index'] <= to_sample_index }
    .sum { |s| s['delta'].to_i }
end

#sync_sample_indices(track_id) ⇒ Object



218
219
220
# File 'lib/mp4/cache/memory.rb', line 218

def sync_sample_indices(track_id)
  @samples[track_id].select { |s| s['keyframe'] == 'TRUE' }.map { |s| s['sample_index'] }.sort
end

#track(track_id) ⇒ Object



79
80
81
# File 'lib/mp4/cache/memory.rb', line 79

def track(track_id)
  @tracks.fetch(track_id)
end

#track_sample_delta_sum(track_id) ⇒ Object



214
215
216
# File 'lib/mp4/cache/memory.rb', line 214

def track_sample_delta_sum(track_id)
  @samples[track_id].sum { |s| s['delta'].to_i }
end

#tracksObject



71
72
73
# File 'lib/mp4/cache/memory.rb', line 71

def tracks
  @tracks.values.sort_by { |t| t['handler_type'].to_s }.reverse
end

#tracks_ascObject



75
76
77
# File 'lib/mp4/cache/memory.rb', line 75

def tracks_asc
  @tracks.values.sort_by { |t| t['track_id'] }
end

#transactionObject



26
27
28
# File 'lib/mp4/cache/memory.rb', line 26

def transaction
  yield
end

#uniform_sample_size?(track_id) ⇒ Boolean

Returns:

  • (Boolean)


243
244
245
# File 'lib/mp4/cache/memory.rb', line 243

def uniform_sample_size?(track_id)
  @samples[track_id].map { |s| s['size'] }.uniq.size == 1
end