       readNext()
 
     df.promise()
+
+  ns.replaceJpegChunk = (blob, marker, chunks) ->
+    df = $.Deferred()
+    oldChunkPos = []
+    oldChunkLength = []
+
+    op = ns.readJpegChunks(blob)
+    op.fail(df.reject)
+    op.progress (pos, length, oldMarker) ->
+      if oldMarker == marker
+        oldChunkPos.push(pos)
+        oldChunkLength.push(length)
+    op.done ->
+      newChunks = [blob.slice(0, 2)]
+      for chunk in chunks
+        intro = new DataView(new ArrayBuffer(4))
+        intro.setUint16(0, 0xff00 + marker)
+        intro.setUint16(2, chunk.byteLength + 2)
+        newChunks.push(intro.buffer)
+        newChunks.push(chunk)
+
+      pos = 2
+      for i in [0...oldChunkPos.length]
+        if oldChunkPos[i] > pos
+          newChunks.push(blob.slice(pos, oldChunkPos[i]))
+        pos = oldChunkPos[i] + oldChunkLength[i] + 4
+      newChunks.push(blob.slice(pos, blob.size))
+
+      df.resolve(new Blob(newChunks, {type: blob.type}))
+
+    df.promise()

    # out <- blob
    df = $.Deferred()
    exif = null
    exifTags = null

    if not (URL and DataView)
      return df.reject('support')

    op = ns.readJpegChunks(file)
    op.progress (marker, view) ->
      if marker == 0xe1
        if view.byteLength >= 14
          if view.getUint32(0) == 0x45786966 and view.getUint16(4) == 0
            exif = view.buffer

        readToView file.slice(pos, pos += length), (view) ->
          if view.byteLength != length
            return df.reject('corrupted')
          df.notify(startPos, length, marker, view)
          readNext()

    df = $.Deferred()
