require 'tempfile'
require 'fileutils'
require 'readline'
require 'pathname'
module AviGlitch
# Base is the object that provides interfaces mainly used.
# To glitch, and save file. The instance returned through AviGlitch#open.
#
class Base
SAFE_FRAMES_COUNT = 150000 # :nodoc:
# AviGlitch::Frames object generated from the +file+.
attr_reader :frames
# The input file (copied tempfile).
attr_reader :file
##
# Create a new instance of AviGlitch::Base, open the file and
# make it ready to manipulate.
# It requires +path+ as Pathname.
def initialize path
File.open(path) do |f|
# copy as tempfile
@file = Tempfile.open 'aviglitch'
f.rewind
while d = f.read(1024) do
@file.print d
end
end
unless AviGlitch::Base.surely_formatted? @file
raise 'Unsupported file passed.'
end
unless safe_frames_count? @file
close
exit
end
@frames = Frames.new @file
# I believe Ruby's GC to close and remove the Tempfile..
end
##
# Output the glitched file to +path+, and close the file.
def output path
FileUtils.cp @file.path, path
close
end
##
# An explicit file close.
def close
@file.close!
end
##
# Glitch each frame data.
# It is a convent method to iterate each frame.
#
# The argument +target+ takes symbols listed below:
# [:keyframe or :iframe] select video key frames (aka I-frame)
# [:deltaframe or :pframe] select video delta frames (difference frames)
# [:videoframe] select both of keyframe and deltaframe
# [:audioframe] select audio frames
# [:all] select all frames
#
# It also requires a block. In the block, you take the frame data
# as a String parameter.
# To modify the data, simply return a modified data.
def glitch target = :all, &block # :yield: data
frames.each do |frame|
if valid_target? target, frame
frame.data = yield frame.data
end
end
end
##
# Do glitch with index.
def glitch_with_index target = :all, &block # :yield: data, index
i = 0
frames.each do |frame|
if valid_target? target, frame
frame.data = yield(frame.data, i)
i += 1
end
end
end
alias :write :output
def valid_target? target, frame # :nodoc:
return true if target == :all
begin
frame.send "is_#{target.to_s}?"
rescue
false
end
end
def safe_frames_count? io # :nodoc:
r = true
io.pos = 12
while io.read(4) != 'idx1' do
s = io.read(4).unpack('V').first
io.pos += s
end
s = io.read(4).unpack('V').first
fc = s / 16
if fc >= SAFE_FRAMES_COUNT
trap(:INT) do
close
exit
end
m = ["WARNING: The passed file has too many frames (#{fc}).\n",
"It may use a large memory to process. ",
"We recommend to chop the movie to smaller chunks before you glitch.\n",
"Do you want to continue anyway? [yN] "].join('')
a = Readline.readline m
r = a == 'y'
end
r
end
private_instance_methods [:valid_target?, :is_safe_frames_count?]
class << self
##
# Check if the +file+ is a correctly formetted AVI file.
# +file+ can be String or Pathname or IO.
def surely_formatted? file, debug = false
answer = true
is_io = file.respond_to?(:seek) # Probably IO.
file = File.open(file) unless is_io
begin
file.seek 0, IO::SEEK_END
eof = file.pos
file.rewind
unless file.read(4) == 'RIFF'
answer = false
warn 'RIFF sign is not found' if debug
end
len = file.read(4).unpack('V').first
unless file.read(4) == 'AVI '
answer = false
warn 'AVI sign is not found' if debug
end
while file.read(4) =~ /^(?:LIST|JUNK)$/ do
s = file.read(4).unpack('V').first
file.pos += s
end
file.pos -= 4
# we require idx1
unless file.read(4) == 'idx1'
answer = false
warn 'idx1 is not found' if debug
end
s = file.read(4).unpack('V').first
file.pos += s
rescue => err
warn err.message if debug
answer = false
ensure
file.close unless is_io
end
answer
end
end
end
end