m3u8 1.4.0 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a4ef59c0103a519f686f38d7eb24e99286f698bfbee43d7e8ad48827dfd00e5
4
- data.tar.gz: b776355294c670836ecc6598ad169d71eedc40e017a686c28042828bf724e805
3
+ metadata.gz: df37c1a4cc89e0bd918c6137c5bc86682d61638b407697240e76f40acb8983e7
4
+ data.tar.gz: e19fd10c1b3f732057285247f08bb235573fa6434085295b53a76be22f1a53aa
5
5
  SHA512:
6
- metadata.gz: e2f761adbf62244f9f175fc28f7f00f0d77d78acb5af19906f70fff656777e97bbbe787454ce47accd0e72f4e702baeda20dde763a72ac87174cb9ccd24dca15
7
- data.tar.gz: bf19a4de08cc5216b939c77eedc147e93e134bc284a90464f138f3a31f0d679d88e5bd6e306db63dbf0e26ce7a40fba618bb67d41c15443899a2824b6fd4776c
6
+ metadata.gz: c4ff8a727525925718fcb0ff7fed0b8bb758556d8e262c97ebacaca4fe96550b4494817ce5d8f4a0525d961a274a1905b16af6b5a85b8ac57235254f54392f8a
7
+ data.tar.gz: 19ae07aa8cd202846254983b823973c4b7d0a9d4bde50e5cc8be7e1e4d91182205bec84731f628298f2659085434716120135a074656e1c9c2aa2c1988415b4f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ **1.5.0**
2
+
3
+ * Added `Playlist#freeze` for deep-freezing playlists, items, nested objects, and playlist-level objects. `Playlist.build` and `Playlist.read` now return frozen playlists. `Playlist.new` remains mutable until `freeze` is called explicitly.
4
+
5
+ ***
6
+
1
7
  **1.4.0**
2
8
 
3
9
  * Added `Playlist#errors` method returning an array of validation error messages. `Playlist#valid?` now delegates to `errors.empty?`. Validates mixed item types, target duration, segment items, playlist items, media items, encryption keys, session keys, session data, and LL-HLS part items.
data/README.md CHANGED
@@ -344,6 +344,27 @@ options = { width: 1920, height: 1080, codecs: 'avc1.66.30,mp4a.40.2',
344
344
  item = M3u8::PlaylistItem.new(options)
345
345
  ```
346
346
 
347
+ ## Frozen playlists
348
+
349
+ Playlists returned by `Playlist.build` and `Playlist.read` are frozen (deeply immutable). Items, nested objects, and the items array are all frozen, preventing accidental mutation after construction:
350
+
351
+ ```ruby
352
+ playlist = M3u8::Playlist.read(File.open('master.m3u8'))
353
+ playlist.frozen? # => true
354
+ playlist.items.frozen? # => true
355
+ playlist.items.first.frozen? # => true
356
+ ```
357
+
358
+ Playlists created with `Playlist.new` remain mutable. Call `freeze` explicitly when ready:
359
+
360
+ ```ruby
361
+ playlist = M3u8::Playlist.new
362
+ playlist.items << M3u8::SegmentItem.new(duration: 10.0, segment: 'test.ts')
363
+ playlist.freeze
364
+ ```
365
+
366
+ Frozen playlists still support `to_s` and `write` for output.
367
+
347
368
  ## Validation
348
369
 
349
370
  Check whether a playlist is valid and inspect specific errors:
data/lib/m3u8/playlist.rb CHANGED
@@ -22,7 +22,7 @@ module M3u8
22
22
  else
23
23
  builder.instance_eval(&block)
24
24
  end
25
- playlist
25
+ playlist.freeze
26
26
  end
27
27
 
28
28
  def self.codecs(options = {})
@@ -53,6 +53,14 @@ module M3u8
53
53
  playlist_size.positive?
54
54
  end
55
55
 
56
+ def freeze
57
+ items.each { |item| freeze_item(item) }
58
+ items.freeze
59
+ part_inf&.freeze
60
+ server_control&.freeze
61
+ super
62
+ end
63
+
56
64
  def to_s
57
65
  output = StringIO.open
58
66
  write(output)
@@ -119,6 +127,13 @@ module M3u8
119
127
 
120
128
  private
121
129
 
130
+ def freeze_item(item)
131
+ item.byterange&.freeze if item.respond_to?(:byterange)
132
+ item.program_date_time&.freeze if item.respond_to?(:program_date_time)
133
+ item.client_attributes&.freeze if item.respond_to?(:client_attributes)
134
+ item.freeze
135
+ end
136
+
122
137
  def assign_options(options)
123
138
  options = defaults.merge(options)
124
139
 
data/lib/m3u8/reader.rb CHANGED
@@ -25,7 +25,7 @@ module M3u8
25
25
  parse_line(line)
26
26
  end
27
27
  playlist.live = !@has_endlist unless master
28
- playlist
28
+ playlist.freeze
29
29
  end
30
30
 
31
31
  private
data/lib/m3u8/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  # M3u8 provides parsing, generation, and validation of m3u8 playlists
4
4
  module M3u8
5
- VERSION = '1.4.0'
5
+ VERSION = '1.5.0'
6
6
  end
@@ -191,6 +191,20 @@ describe M3u8::Builder do
191
191
  expect(playlist.target).to eq(12)
192
192
  end
193
193
 
194
+ it 'returns a frozen playlist' do
195
+ playlist = M3u8::Playlist.build do
196
+ segment duration: 10.0, segment: 'test.ts'
197
+ end
198
+ expect(playlist).to be_frozen
199
+ end
200
+
201
+ it 'returns a frozen playlist with yielded form' do
202
+ playlist = M3u8::Playlist.build do |b|
203
+ b.segment duration: 10.0, segment: 'test.ts'
204
+ end
205
+ expect(playlist).to be_frozen
206
+ end
207
+
194
208
  it 'supports yielded builder form' do
195
209
  files = %w[seg1.ts seg2.ts]
196
210
  playlist = M3u8::Playlist.build(version: 4) do |b|
@@ -654,6 +654,108 @@ describe M3u8::Playlist do
654
654
  end
655
655
  end
656
656
 
657
+ describe '#freeze' do
658
+ it 'freezes the playlist' do
659
+ playlist.freeze
660
+ expect(playlist).to be_frozen
661
+ end
662
+
663
+ it 'freezes the items array' do
664
+ playlist.freeze
665
+ expect(playlist.items).to be_frozen
666
+ end
667
+
668
+ it 'freezes each item' do
669
+ item = M3u8::SegmentItem.new(duration: 10.0, segment: 'test.ts')
670
+ playlist.items << item
671
+ playlist.freeze
672
+ expect(item).to be_frozen
673
+ end
674
+
675
+ it 'freezes nested byterange on items' do
676
+ item = M3u8::SegmentItem.new(
677
+ duration: 10.0, segment: 'test.ts',
678
+ byterange: { length: 4500, start: 600 }
679
+ )
680
+ playlist.items << item
681
+ playlist.freeze
682
+ expect(item.byterange).to be_frozen
683
+ end
684
+
685
+ it 'freezes nested program_date_time on items' do
686
+ time = M3u8::TimeItem.new(time: '2024-06-01T12:00:00Z')
687
+ item = M3u8::SegmentItem.new(
688
+ duration: 10.0, segment: 'test.ts',
689
+ program_date_time: time
690
+ )
691
+ playlist.items << item
692
+ playlist.freeze
693
+ expect(item.program_date_time).to be_frozen
694
+ end
695
+
696
+ it 'freezes nested client_attributes on items' do
697
+ item = M3u8::DateRangeItem.new(
698
+ id: 'ad-1', start_date: '2024-01-01T00:00:00Z',
699
+ client_attributes: { 'X-AD-ID' => '"foo"' }
700
+ )
701
+ playlist.items << item
702
+ playlist.freeze
703
+ expect(item.client_attributes).to be_frozen
704
+ end
705
+
706
+ it 'freezes part_inf' do
707
+ playlist.part_inf = M3u8::PartInfItem.new(part_target: 0.5)
708
+ playlist.freeze
709
+ expect(playlist.part_inf).to be_frozen
710
+ end
711
+
712
+ it 'freezes server_control' do
713
+ playlist.server_control = M3u8::ServerControlItem.new(
714
+ can_skip_until: 24.0
715
+ )
716
+ playlist.freeze
717
+ expect(playlist.server_control).to be_frozen
718
+ end
719
+
720
+ it 'raises FrozenError on attribute set' do
721
+ playlist.freeze
722
+ expect { playlist.version = 7 }
723
+ .to raise_error(FrozenError)
724
+ end
725
+
726
+ it 'raises FrozenError on items append' do
727
+ playlist.freeze
728
+ item = M3u8::SegmentItem.new(
729
+ duration: 10.0, segment: 'test.ts'
730
+ )
731
+ expect { playlist.items << item }
732
+ .to raise_error(FrozenError)
733
+ end
734
+
735
+ it 'raises FrozenError on item mutation' do
736
+ item = M3u8::SegmentItem.new(
737
+ duration: 10.0, segment: 'test.ts'
738
+ )
739
+ playlist.items << item
740
+ playlist.freeze
741
+ expect { item.duration = 5.0 }
742
+ .to raise_error(FrozenError)
743
+ end
744
+
745
+ it 'still supports to_s' do
746
+ item = M3u8::SegmentItem.new(
747
+ duration: 10.0, segment: 'test.ts'
748
+ )
749
+ playlist.items << item
750
+ playlist.freeze
751
+ expect(playlist.to_s).to include('#EXTM3U')
752
+ end
753
+
754
+ it 'returns self' do
755
+ expect(playlist.freeze).to equal(playlist)
756
+ end
757
+ end
758
+
657
759
  describe '#write' do
658
760
  context 'when playlist is valid' do
659
761
  it 'returns playlist text' do
@@ -590,6 +590,20 @@ describe M3u8::Reader do
590
590
  expect(item.segment).to eq('segment0.ts')
591
591
  end
592
592
 
593
+ it 'returns a frozen master playlist' do
594
+ playlist = reader.read(
595
+ File.read('spec/fixtures/master.m3u8')
596
+ )
597
+ expect(playlist).to be_frozen
598
+ end
599
+
600
+ it 'returns a frozen media playlist' do
601
+ playlist = reader.read(
602
+ File.read('spec/fixtures/playlist.m3u8')
603
+ )
604
+ expect(playlist).to be_frozen
605
+ end
606
+
593
607
  context 'when playlist source is invalid' do
594
608
  it 'raises error with message' do
595
609
  message = 'Playlist must start with a #EXTM3U tag, line read ' \
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: m3u8
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth Deckard