Codificar en base64, decodificar a, desde archivos en trozos con Python 2.7

He leído los documentos de python de base64 y he visto ejemplos aquí en SO y en otros lugares, pero todavía tengo un problema para decodificar base64 de nuevo a la representación binaria original.

No estoy recibiendo ninguna excepción, así que no creo que haya un problema de relleno o conjunto de caracteres. Acabo de obtener un archivo binario resultante que es más pequeño que el binario original.

Estoy incluyendo los pasos de encoding y deencoding de base64 en caso de que haya un problema con uno o ambos pasos.

El código debe ejecutarse con python 2.7.

A continuación se muestran los scripts que reproducen el problema.

b64_encode.py

#!/usr/bin/env python2.7 # # b64_encode.py - must run with python 2.7 # - must process data in chunks to limit memory consumption # - base64 data must be JSON compatible, ie # use base64 "modern" interface, # not base64.encodestring() which contains linefeeds # import sys, base64 def write_base64_file_from_file(src_fname, b64_fname, chunk_size=8192): with open(src_fname, 'rb') as fin, open(b64_fname, 'w') as fout: while True: bin_data = fin.read(chunk_size) if not bin_data: break print 'bin %s data len: %d' % (type(bin_data), len(bin_data)) b64_data = base64.b64encode(bin_data) print 'b64 %s data len: %d' % (type(b64_data), len(b64_data)) fout.write(b64_data) if len(sys.argv) != 2: print 'usage: %s ' % sys.argv[0] sys.exit() bin_fname = sys.argv[1] b64_fname = bin_fname + '.b64' write_base64_file_from_file(bin_fname, b64_fname) 

b64_decode.py

 #!/usr/bin/env python2.7 # # b64_decode.py - must run with python 2.7 # - must process data in chunks to limit memory consumption # import os, sys, base64 def write_file_from_base64_file(b64_fname, dst_fname, chunk_size=8192): with open(b64_fname, 'r') as fin, open(dst_fname, 'wb') as fout: while True: b64_data = fin.read(chunk_size) if not b64_data: break print 'b64 %s data len: %d' % (type(b64_data), len(b64_data)) bin_data = base64.b64decode(b64_data) print 'bin %s data len: %d' % (type(bin_data), len(bin_data)) fout.write(bin_data) if len(sys.argv) != 2: print 'usage: %s ' % sys.argv[0] sys.exit() b64_fname = sys.argv[1] bin_ext = os.path.splitext(os.path.splitext(b64_fname)[0])[1] bin_fname = os.path.splitext(b64_fname)[0] + bin_ext write_file_from_base64_file(b64_fname, bin_fname) 

Por ejemplo, mi salida para un archivo de imagen de 19k es:

 $ ./b64_encode.py img.jpg bin  data len: 8192 b64  data len: 10924 bin  data len: 8192 b64  data len: 10924 bin  data len: 2842 b64  data len: 3792 $ ./b64_decode.py img.jpg.b64 b64  data len: 8192 bin  data len: 6144 b64  data len: 8192 bin  data len: 2048 b64  data len: 8192 bin  data len: 4097 b64  data len: 1064 bin  data len: 796 $ ll 19226 Feb 5 14:24 img.jpg 25640 Mar 29 12:12 img.jpg.b64 13085 Mar 29 12:14 img.jpg.jpg 

Te encuentras con problemas de relleno:

 >>> open('pianoavatar.jpg').read(8192).encode('base64')[-5:] 'IIE=\n' 

La deencoding de Base64 se detiene cuando encuentra el marcador de relleno = . Su segunda lectura encuentra tal marcador en el 10924o carácter.

Debes ajustar el tamaño de tu fragmento para que sea divisible entre 3 en lugar de evitar el relleno en el medio de tu archivo de salida. Utilice un tamaño de trozo de 8190, por ejemplo.

Al leer, debe usar un tamaño de buffers que sea un múltiplo de 4 para evitar problemas de alineación. 8192 estaría bien allí, pero debe asegurarse de que esta restricción se cumpla en sus funciones. Sería mejor que el valor predeterminado sea el tamaño de la porción expandida de base64 para las porciones de entrada; 10920 para un tamaño de fragmento de encoding de 8190 (4 caracteres base64 por cada 3 bytes codificados).

Manifestación:

 >>> write_base64_file_from_file('pianoavatar.jpg', 'test.b64', 8190) bin  data len: 8190 b64  data len: 10920 bin  data len: 8190 b64  data len: 10920 bin  data len: 1976 b64  data len: 2636 

La lectura ahora funciona bien, incluso con su tamaño de fragmento original de 8192:

 >>> write_file_from_base64_file('test.b64', 'test.jpg', 8192) b64  data len: 8192 bin  data len: 6144 b64  data len: 8192 bin  data len: 6144 b64  data len: 8092 bin  data len: 6068 

Puede forzar que el tamaño de buffers se alinee en sus funciones con un módulo simple:

 def write_base64_file_from_file(src_fname, b64_fname, chunk_size=8190): chunk_size -= chunk_size % 3 # align to multiples of 3 # ... def write_file_from_base64_file(b64_fname, dst_fname, chunk_size=10920): chunk_size -= chunk_size % 4 # align to multiples of 4 # ...