Class: HLS::State

Inherits:
Object
  • Object
show all
Defined in:
lib/hls/state.rb

Overview

Sidecar state for an encoded bundle. Records the input digest, rendition list, and per-file upload status so a re-run can skip work that’s already done and a crashed run can resume from the last successful upload.

Lives at ‘<output>/.hls-state.json` next to the encoded bundle.

Defined Under Namespace

Classes: CorruptError

Constant Summary collapse

FILENAME =
".hls-state.json"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:, data:) ⇒ State

Returns a new instance of State.



51
52
53
54
# File 'lib/hls/state.rb', line 51

def initialize(path:, data:)
  @path = Pathname.new(path)
  @data = data
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



49
50
51
# File 'lib/hls/state.rb', line 49

def path
  @path
end

Class Method Details

.default_dataObject



38
39
40
41
42
43
44
45
46
47
# File 'lib/hls/state.rb', line 38

def self.default_data
  {
    input_digest: nil,
    config_digest: nil,
    profile: nil,
    renditions: [],
    encoded_at: nil,
    uploads: {}
  }
end

.load(output_dir) ⇒ Object



19
20
21
22
# File 'lib/hls/state.rb', line 19

def self.load(output_dir)
  path = Pathname.new(output_dir).join(FILENAME)
  new(path: path, data: read(path))
end

.read(path) ⇒ Object

Reads state JSON from disk. Returns ‘default_data` for a missing file (the common first-run case). Raises CorruptError when the file exists but isn’t parseable — silently re-encoding on corruption would be expensive and surprising; explicit failure lets the caller decide whether to delete the sidecar and retry.



29
30
31
32
33
34
35
36
# File 'lib/hls/state.rb', line 29

def self.read(path)
  return default_data unless path.exist?

  raw = JSON.parse(path.read, symbolize_names: true)
  default_data.merge(raw)
rescue JSON::ParserError => e
  raise CorruptError, "state file at #{path} is not valid JSON: #{e.message}"
end

Instance Method Details

#config_digestObject



57
# File 'lib/hls/state.rb', line 57

def config_digest = @data[:config_digest]

#encoded?(input_digest:, config_digest:) ⇒ Boolean

Has this output already been encoded for the given input AND profile config? A change to either invalidates the encode —bumping ‘audio_bitrate` or adding a rendition has to re-run ffmpeg even if the input file is byte-identical.

Returns:

  • (Boolean)


67
68
69
70
71
# File 'lib/hls/state.rb', line 67

def encoded?(input_digest:, config_digest:)
  !@data[:encoded_at].nil? &&
    @data[:input_digest]  == input_digest &&
    @data[:config_digest] == config_digest
end

#encoded_atObject



60
# File 'lib/hls/state.rb', line 60

def encoded_at    = @data[:encoded_at]

#input_digestObject



56
# File 'lib/hls/state.rb', line 56

def input_digest  = @data[:input_digest]

#profileObject



58
# File 'lib/hls/state.rb', line 58

def profile       = @data[:profile]

#record_encode(input_digest:, config_digest:, profile:, renditions:) ⇒ Object



79
80
81
82
83
84
85
86
87
# File 'lib/hls/state.rb', line 79

def record_encode(input_digest:, config_digest:, profile:, renditions:)
  @data[:input_digest]  = input_digest
  @data[:config_digest] = config_digest
  @data[:profile]       = profile
  @data[:renditions]    = renditions
  @data[:encoded_at]    = Time.now.utc.iso8601
  # Content has changed; previous upload records are stale.
  @data[:uploads]       = {}
end

#record_upload(relative_key:, digest:, etag: nil) ⇒ Object



89
90
91
92
93
94
95
# File 'lib/hls/state.rb', line 89

def record_upload(relative_key:, digest:, etag: nil)
  @data[:uploads][relative_key.to_sym] = {
    digest: digest,
    etag: etag,
    uploaded_at: Time.now.utc.iso8601
  }
end

#renditionsObject



59
# File 'lib/hls/state.rb', line 59

def renditions    = @data[:renditions]

#saveObject



97
98
99
100
# File 'lib/hls/state.rb', line 97

def save
  path.parent.mkpath
  path.write(JSON.pretty_generate(@data))
end

#to_hObject



102
103
104
# File 'lib/hls/state.rb', line 102

def to_h
  @data.dup
end

#uploaded?(relative_key:, digest:) ⇒ Boolean

Has the file at relative_key already been uploaded with the given digest?

Returns:

  • (Boolean)


74
75
76
77
# File 'lib/hls/state.rb', line 74

def uploaded?(relative_key:, digest:)
  upload = @data[:uploads][relative_key.to_sym]
  !upload.nil? && upload[:digest] == digest
end

#uploadsObject



61
# File 'lib/hls/state.rb', line 61

def uploads       = @data[:uploads]