Class: Ffmprb::File

Inherits:
File
  • Object
show all
Includes:
Util::ProcVis::Node
Defined in:
lib/ffmprb/file.rb,
lib/ffmprb/file/sample.rb,
lib/ffmprb/file/threaded_buffered.rb

Constant Summary collapse

AUDIO_SAMPLE_MIN =
0.5

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes included from Util::ProcVis::Node

#_proc_vis

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Util::ProcVis::Node

#proc_vis_edge, #proc_vis_name, #proc_vis_node

Constructor Details

#initialize(path:, mode:) ⇒ File

Returns a new instance of File.



96
97
98
99
100
101
102
103
# File 'lib/ffmprb/file.rb', line 96

def initialize(path:, mode:)
  @mode = mode.to_sym
  fail Error, "Open for read, create for write, ??? for #{@mode}"  unless
    %i[read write].include?(@mode)
  @path = path
  @path.close  if @path && @path.respond_to?(:close)  # NOTE we operate on closed files
  path!  # NOTE early (exception) raiser
end

Class Attribute Details

.image_extname_regexObject

NOTE careful when subclassing, it doesn’t inherit the attr values



13
14
15
# File 'lib/ffmprb/file.rb', line 13

def image_extname_regex
  @image_extname_regex
end

.movie_extname_regexObject

NOTE careful when subclassing, it doesn’t inherit the attr values



13
14
15
# File 'lib/ffmprb/file.rb', line 13

def movie_extname_regex
  @movie_extname_regex
end

.sound_extname_regexObject

NOTE careful when subclassing, it doesn’t inherit the attr values



13
14
15
# File 'lib/ffmprb/file.rb', line 13

def sound_extname_regex
  @sound_extname_regex
end

Instance Attribute Details

#modeObject (readonly)

Returns the value of attribute mode.



94
95
96
# File 'lib/ffmprb/file.rb', line 94

def mode
  @mode
end

Class Method Details

.access(path) ⇒ Object



31
32
33
34
35
# File 'lib/ffmprb/file.rb', line 31

def access(path)
  new(path: path, mode: :read).tap do |file|
    Ffmprb.logger.debug{"Accessed file with path: #{file.path}"}
  end
end

.create(path) ⇒ Object



25
26
27
28
29
# File 'lib/ffmprb/file.rb', line 25

def create(path)
  new(path: path, mode: :write).tap do |file|
    Ffmprb.logger.debug{"Created file with path: #{file.path}"}
  end
end

.image?(extname) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/ffmprb/file.rb', line 80

def image?(extname)
  !!(extname =~ image_extname_regex)
end

.movie?(extname) ⇒ Boolean

Returns:

  • (Boolean)


88
89
90
# File 'lib/ffmprb/file.rb', line 88

def movie?(extname)
  !!(extname =~ movie_extname_regex)
end

.opener(file, mode = nil) ⇒ Object

NOTE must be timeout-safe



16
17
18
19
20
21
22
23
# File 'lib/ffmprb/file.rb', line 16

def opener(file, mode=nil)
  ->{
    path = file.respond_to?(:path)? file.path : file
    mode ||= file.respond_to?(mode)? file.mode.to_s[0] : 'r'
    Ffmprb.logger.debug{"Trying to open #{path} (for #{mode}-buffering or something)"}
    ::File.open path, mode
  }
end

.sound?(extname) ⇒ Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/ffmprb/file.rb', line 84

def sound?(extname)
  !!(extname =~ sound_extname_regex)
end

.temp(extname) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ffmprb/file.rb', line 37

def temp(extname)
  file = create(Tempfile.new(['', extname]))
  path = file.path
  Ffmprb.logger.debug{"Created temp file with path: #{path}"}

  return file  unless block_given?

  begin
    yield file
  ensure
    begin
      file.unlink
    rescue
      Ffmprb.logger.warn "#{$!.class.name} removing temp file with path #{path}: #{$!.message}"
    end
    Ffmprb.logger.debug{"Removed temp file with path: #{path}"}
  end
end

.temp_fifo(extname = '.tmp', &blk) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/ffmprb/file.rb', line 56

def temp_fifo(extname='.tmp', &blk)
  path = temp_fifo_path(extname)
  mkfifo path
  fifo_file = create(path)

  return fifo_file  unless block_given?

  path = fifo_file.path
  begin
    yield fifo_file
  ensure
    begin
      fifo_file.unlink
    rescue
      Ffmprb.logger.warn "#{$!.class.name} removing temp file with path #{path}: #{$!.message}"
    end
    Ffmprb.logger.debug{"Removed temp file with path: #{path}"}
  end
end

.temp_fifo_path(extname) ⇒ Object



76
77
78
# File 'lib/ffmprb/file.rb', line 76

def temp_fifo_path(extname)
  join Dir.tmpdir, "#{rand(2**222)}p#{extname}"
end

.threaded_buffered_fifo(extname = '.tmp', reader_open_on_writer_idle_limit: nil, proc_vis: nil) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/ffmprb/file/threaded_buffered.rb', line 7

def threaded_buffered_fifo(extname='.tmp', reader_open_on_writer_idle_limit: nil, proc_vis: nil)
  input_fifo_file = temp_fifo(extname)
  output_fifo_file = temp_fifo(extname)
  Ffmprb.logger.debug{"Opening #{input_fifo_file.path}>#{output_fifo_file.path} for buffering"}
  Util::Thread.new do
    begin
      io_buff = Util::ThreadedIoBuffer.new(opener(input_fifo_file, 'r'), opener(output_fifo_file, 'w'), keep_outputs_open_on_input_idle_limit: reader_open_on_writer_idle_limit)
      if proc_vis
        proc_vis.proc_vis_edge input_fifo_file, io_buff
        proc_vis.proc_vis_edge io_buff, output_fifo_file
      end
      begin
        # yield input_fifo_file, output_fifo_file, io_buff  if block_given?
      ensure
        Util::Thread.join_children!
      end
      Ffmprb.logger.debug{"IoBuffering from #{input_fifo_file.path} to #{output_fifo_file.path} ended"}
    ensure
      input_fifo_file.unlink  if input_fifo_file
      output_fifo_file.unlink  if output_fifo_file
    end
  end
  Ffmprb.logger.debug{"IoBuffering from #{input_fifo_file.path} to #{output_fifo_file.path} started"}

  [input_fifo_file, output_fifo_file]
end

Instance Method Details

#basenameObject



119
120
121
# File 'lib/ffmprb/file.rb', line 119

def basename
  @basename ||= File.basename(path)
end

#channel?(medium) ⇒ Boolean

Returns:

  • (Boolean)


127
128
129
130
131
132
133
134
# File 'lib/ffmprb/file.rb', line 127

def channel?(medium)
  case medium
  when :video
    self.class.image?(extname) || self.class.movie?(extname)
  when :audio
    self.class.sound?(extname) || self.class.movie?(extname)
  end
end

#creation_time(force = false) ⇒ Object



161
162
163
# File 'lib/ffmprb/file.rb', line 161

def creation_time(force=false)
  Time.parse probe(force)['format']['tags']['creation_time']
end

#exist?Boolean

Info

Returns:

  • (Boolean)


115
116
117
# File 'lib/ffmprb/file.rb', line 115

def exist?
  File.exist? path
end

#extnameObject



123
124
125
# File 'lib/ffmprb/file.rb', line 123

def extname
  @extname ||= File.extname(path)
end

#fps(force = false) ⇒ Object



156
157
158
159
# File 'lib/ffmprb/file.rb', line 156

def fps(force=false)
  v_stream = probe(force)['streams'].first
  Rational v_stream['r_frame_rate'] || v_stream['avg_frame_rate']
end

#labelObject



105
106
107
# File 'lib/ffmprb/file.rb', line 105

def label
  basename
end

#length(force = false) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ffmprb/file.rb', line 136

def length(force=false)
  @duration = nil  if force
  return @duration  if @duration

  # NOTE first attempt
  @duration = probe(force)['format']['duration']
  @duration &&= @duration.to_f
  return @duration  if @duration

  # NOTE a harder try
  @duration = probe(true)['frames'].reduce(0) do |sum, frame|
    sum + frame['pkt_duration_time'].to_f
  end
end

#pathObject



109
110
111
# File 'lib/ffmprb/file.rb', line 109

def path
  path!
end

#readObject

Manipulation



168
169
170
# File 'lib/ffmprb/file.rb', line 168

def read
  File.read path
end

#resolution(force = false) ⇒ Object



151
152
153
154
# File 'lib/ffmprb/file.rb', line 151

def resolution(force=false)
  v_stream = probe(force)['streams'].first
  "#{v_stream['width']}x#{v_stream['height']}"
end

#sample(at: 0.01, duration: 0, video: true, audio: true, &blk) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ffmprb/file/sample.rb', line 7

def sample(
  at: 0.01,
  duration: 0,
  video: true,
  audio: true,
  &blk
)
  audio = File.temp('.wav')  if audio == true
  video = File.temp('.png')  if video == true

  Ffmprb.logger.debug{"Snap shooting files, video: #{video && video.path}, audio: #{audio && audio.path}"}

  fail Error, "Incorrect output extname (must be image)"  unless
    !video || video.channel?(:video) && !video.channel?(:audio)
  fail Error, "Incorrect audio extname (must be sound)"  unless
    !audio || audio.channel?(:audio) && !audio.channel?(:video)
  fail Error, "Can sample either video OR audio UNLESS a block is given"  unless
    block_given? || !!audio != !!video
  fail Error, "Can sample video just for 0 sec (an image snapshot)"  unless
    !video || duration == 0

  cmd = %W[-i #{path}]
  cmd.concat %W[-deinterlace -an -ss #{at} -vframes 1 #{video.path}]  if video
  audio_duration = [AUDIO_SAMPLE_MIN, duration].max
  audio_at = [0, at - audio_duration / 2].max
  cmd.concat %W[-vn -ss #{audio_at} -t #{audio_duration} #{audio.path}]  if audio
  Util.ffmpeg *cmd

  return video || audio  unless block_given?

  begin
    yield *[video || nil, audio || nil].compact
  ensure
    begin
      video.unlink  if video
      audio.unlink  if audio
      Ffmprb.logger.debug{"Removed sample files"}
    rescue
      Ffmprb.logger.warn "#{$!.class.name} removing sample files: #{$!.message}"
    end
  end
end

#sample_audio(*audio, at: 0.01, &blk) ⇒ Object



52
53
54
# File 'lib/ffmprb/file/sample.rb', line 52

def sample_audio(*audio, at: 0.01, &blk)
  sample at: at, video: false, audio: (audio.first || true), &blk
end

#sample_video(*video, at: 0.01, &blk) ⇒ Object



49
50
51
# File 'lib/ffmprb/file/sample.rb', line 49

def sample_video(*video, at: 0.01, &blk)
  sample at: at, video: (video.first || true), audio: false, &blk
end

#threaded_buffered_copy_to(*dsts) ⇒ Object



36
37
38
39
40
41
42
43
44
# File 'lib/ffmprb/file/threaded_buffered.rb', line 36

def threaded_buffered_copy_to(*dsts)
  Util::ThreadedIoBuffer.new(
    self.class.opener(self, 'r'),
    *dsts.map{|io| self.class.opener io, 'w'}
  ).tap do |io_buff|
    proc_vis_edge self, io_buff
    dsts.each{ |dst| proc_vis_edge io_buff, dst }
  end
end


175
176
177
178
179
180
181
182
183
# File 'lib/ffmprb/file.rb', line 175

def unlink
  if path.respond_to? :unlink
    path.unlink
  else
    FileUtils.remove_entry path
  end
  Ffmprb.logger.debug{"Removed file with path: #{path}"}
  @path = nil
end

#write(s) ⇒ Object



171
172
173
# File 'lib/ffmprb/file.rb', line 171

def write(s)
  File.write path, s
end