Created
November 2, 2009 06:12
-
-
Save zunda/223991 to your computer and use it in GitHub Desktop.
A ruby script to show segments in jpeg files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
# | |
# usage: ruby inspect-jpeg.rb jpeg-file ... | |
# | |
# Copyright (C) 2009 zunda <zunda at freeshell.org> | |
# | |
# This program is free software: you can redistribute it and/or | |
# modify it under the terms of the GNU General Public License as | |
# published by the Free Software Foundation, either version 3 of | |
# the License, or (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
# General Public License for more details. | |
# | |
class JpegError < StandardError; end | |
class Jpeg | |
def Jpeg::read_onebyte(io) | |
io.read(1).unpack('C')[0] | |
end | |
def Jpeg::read_twobytes(io) | |
io.read(2).unpack('n')[0] | |
end | |
def Jpeg::read_segment_length(io) | |
io.read(2).unpack('n')[0] - 2 | |
end | |
# reads data until next marker | |
def Jpeg::read_data(io) | |
data = '' | |
while !io.eof? | |
t = io.read(1) | |
if "\xFF" == t | |
u = io.read(1) | |
unless "\x00" == u | |
# Marker for next segment | |
io.seek(-2, IO::SEEK_CUR) | |
break | |
end | |
end | |
data += t | |
end | |
return data | |
end | |
class MarkerBase | |
@@markers = Array.new | |
def length(io); 0; end # length of the payload not including two bytes for length data itself | |
def self.second_byte; raise NotImplementedError; end | |
def self.name; 'To be implemented'; end | |
def self.has_data?; false; end | |
def MarkerBase.inherited(subclass) | |
@@markers << subclass | |
end | |
def self.marker_hash | |
r = Hash.new | |
@@markers.each do |marker| | |
r[marker.second_byte] = marker | |
end | |
return r | |
end | |
end | |
class Marker | |
# http://en.wikipedia.org/wiki/JPEG | |
class SOI < MarkerBase | |
def length(io); 0; end | |
def self.second_byte; 0xD8; end | |
def self.name; 'Start Of Image'; end | |
end | |
class SOF0 < MarkerBase | |
def self.second_byte; 0xC0; end | |
def self.name; 'Start Of Frame (Baseline DCT)'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
end | |
class SOF2 < MarkerBase | |
def self.second_byte; 0xC2; end | |
def self.name; 'Start Of Frame (Progressive DCT)'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
end | |
class DHT < MarkerBase | |
def self.second_byte; 0xC4; end | |
def self.name; 'Define Huffman Table(s)'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
end | |
class DQT < MarkerBase | |
def self.second_byte; 0xD8; end | |
def self.name; 'Define Quantization Table(s)'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
end | |
class DRI < MarkerBase | |
def self.second_byte; 0xDD; end | |
def self.name; 'Define Restart Interval'; end | |
def length(io); 2; end | |
end | |
class SOS < MarkerBase | |
def self.second_byte; 0xDA; end | |
def self.name; 'Start Of Scan'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
def self.has_data?; true; end | |
end | |
0.upto(7) do |i| | |
eval <<-_END | |
class RST#{i} < MarkerBase | |
def self.second_byte; #{0xD0+i}; end | |
def self.name; 'Restart'; end | |
end | |
_END | |
end | |
1.upto(0xf) do |i| | |
eval <<-_END | |
class APP#{i} < MarkerBase | |
def self.second_byte; #{0xE0+i}; end | |
def self.name; 'Application-specific'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
end | |
_END | |
end | |
class COM < MarkerBase | |
def self.second_byte; 0xFE; end | |
def self.name; 'Comment'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
end | |
class EOI < MarkerBase | |
def self.second_byte; 0xD9; end | |
def self.name; 'End Of Image'; end | |
end | |
# http://www.obrador.com/essentialjpeg/headerinfo.htm | |
class JFIF < MarkerBase | |
def self.second_byte; 0xE0; end | |
def self.name; 'JFIF marker'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
end | |
class DQT < MarkerBase | |
def self.second_byte; 0xDB; end | |
def self.name; 'Define Quantization Table'; end | |
def length(io); Jpeg::read_segment_length(io); end | |
end | |
@@markers = MarkerBase.marker_hash | |
def self.with(second_byte) | |
klass = @@markers[second_byte] | |
unless klass | |
raise JpegError, "Unknown marker with second byte 0x%02X" % second_byte | |
end | |
klass.new | |
end | |
end | |
class Segment | |
attr_reader :marker | |
attr_reader :length # length of the payload not including two bytes for length data itself | |
attr_reader :payload | |
attr_reader :data | |
attr_reader :pos | |
def initialize(marker, io) | |
@marker = marker | |
@pos = io.pos - 2 | |
@length = marker.length(io) | |
@payload = io.read(@length) | |
if @marker.class.has_data? | |
@data = Jpeg::read_data(io) | |
else | |
@data = nil | |
end | |
end | |
def to_s | |
r = "#{"%-4.4s" % @marker.class.to_s.split(/::/)[-1]} pos:#{@pos} len:#{@length}" | |
r += " data:#{@data.length}" if @data | |
return r | |
end | |
end | |
def initialize(io) | |
@io = io | |
end | |
def each_segment | |
while not @io.eof? | |
t = Jpeg.read_onebyte(@io) | |
unless 0xFF == t | |
@io.seek(-1, IO::SEEK_CUR) | |
raise JpegError, "Segment starts with #{"0x%02X" % t} instead of 0xFF at byte #{@io.pos - 1} in #{@io.path.inspect}" | |
end | |
yield Segment.new(Jpeg::Marker::with(Jpeg.read_onebyte(@io)), @io) | |
end | |
end | |
def seek_to_segment | |
Jpeg::read_data(@io) | |
end | |
def eof? | |
@io.eof? | |
end | |
def pos | |
@io.pos | |
end | |
end | |
if __FILE__ == $0 | |
ARGV.each do |path| | |
puts path | |
File.open(path) do |f| | |
image = Jpeg.new(f) | |
while !image.eof? | |
begin | |
image.each_segment do |segment| | |
puts " #{segment}" | |
end | |
rescue JpegError | |
pos = image.pos | |
len = image.seek_to_segment.length | |
puts " Skipping #{len} bytes from pos:#{pos}" | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment