media_parser.go (2964B)
1 package jpegstructure 2 3 import ( 4 "bufio" 5 "bytes" 6 "image" 7 "io" 8 "os" 9 10 "image/jpeg" 11 12 "github.com/dsoprea/go-logging" 13 "github.com/dsoprea/go-utility/v2/image" 14 ) 15 16 // JpegMediaParser is a `riimage.MediaParser` that knows how to parse JPEG 17 // images. 18 type JpegMediaParser struct { 19 } 20 21 // NewJpegMediaParser returns a new JpegMediaParser. 22 func NewJpegMediaParser() *JpegMediaParser { 23 24 // TODO(dustin): Add test 25 26 return new(JpegMediaParser) 27 } 28 29 // Parse parses a JPEG uses an `io.ReadSeeker`. Even if it fails, it will return 30 // the list of segments encountered prior to the failure. 31 func (jmp *JpegMediaParser) Parse(rs io.ReadSeeker, size int) (ec riimage.MediaContext, err error) { 32 defer func() { 33 if state := recover(); state != nil { 34 err = log.Wrap(state.(error)) 35 } 36 }() 37 38 s := bufio.NewScanner(rs) 39 40 // Since each segment can be any size, our buffer must allowed to grow as 41 // large as the file. 42 buffer := []byte{} 43 s.Buffer(buffer, size) 44 45 js := NewJpegSplitter(nil) 46 s.Split(js.Split) 47 48 for s.Scan() != false { 49 } 50 51 // Always return the segments that were parsed, at least until there was an 52 // error. 53 ec = js.Segments() 54 55 log.PanicIf(s.Err()) 56 57 return ec, nil 58 } 59 60 // ParseFile parses a JPEG file. Even if it fails, it will return the list of 61 // segments encountered prior to the failure. 62 func (jmp *JpegMediaParser) ParseFile(filepath string) (ec riimage.MediaContext, err error) { 63 defer func() { 64 if state := recover(); state != nil { 65 err = log.Wrap(state.(error)) 66 } 67 }() 68 69 // TODO(dustin): Add test 70 71 f, err := os.Open(filepath) 72 log.PanicIf(err) 73 74 defer f.Close() 75 76 stat, err := f.Stat() 77 log.PanicIf(err) 78 79 size := stat.Size() 80 81 sl, err := jmp.Parse(f, int(size)) 82 83 // Always return the segments that were parsed, at least until there was an 84 // error. 85 ec = sl 86 87 log.PanicIf(err) 88 89 return ec, nil 90 } 91 92 // ParseBytes parses a JPEG byte-slice. Even if it fails, it will return the 93 // list of segments encountered prior to the failure. 94 func (jmp *JpegMediaParser) ParseBytes(data []byte) (ec riimage.MediaContext, err error) { 95 defer func() { 96 if state := recover(); state != nil { 97 err = log.Wrap(state.(error)) 98 } 99 }() 100 101 br := bytes.NewReader(data) 102 103 sl, err := jmp.Parse(br, len(data)) 104 105 // Always return the segments that were parsed, at least until there was an 106 // error. 107 ec = sl 108 109 log.PanicIf(err) 110 111 return ec, nil 112 } 113 114 // LooksLikeFormat indicates whether the data looks like a JPEG image. 115 func (jmp *JpegMediaParser) LooksLikeFormat(data []byte) bool { 116 if len(data) < 4 { 117 return false 118 } 119 120 l := len(data) 121 if data[0] != 0xff || data[1] != MARKER_SOI || data[l-2] != 0xff || data[l-1] != MARKER_EOI { 122 return false 123 } 124 125 return true 126 } 127 128 // GetImage returns an image.Image-compatible struct. 129 func (jmp *JpegMediaParser) GetImage(r io.Reader) (img image.Image, err error) { 130 img, err = jpeg.Decode(r) 131 log.PanicIf(err) 132 133 return img, nil 134 } 135 136 var ( 137 // Enforce interface conformance. 138 _ riimage.MediaParser = new(JpegMediaParser) 139 )