84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
#
|
|
# Copyright (C) 2026 Frédéric Tronel
|
|
|
|
|
|
import logging
|
|
import re
|
|
from math import ceil, log
|
|
from os import write
|
|
from typing import IO
|
|
from io import BytesIO
|
|
|
|
|
|
from typeguard import typechecked
|
|
|
|
@typechecked
|
|
def dump_ppm(pictures: bytes, prefix: str, temporaries: list[IO[bytes]]) -> None:
|
|
"""
|
|
Dump PPM pictures from a bytes buffer to files.
|
|
|
|
This function takes a bytes buffer containing PPM pictures, a prefix for the output file names,
|
|
and a list of temporary files.
|
|
It extracts each PPM picture from the buffer, checks its validity, and writes it to a file.
|
|
The output files are named according to the prefix and a zero-padded three-digit number.
|
|
|
|
Args:
|
|
pictures (bytes): The bytes buffer containing the PPM pictures.
|
|
prefix (str): The prefix for the output file names.
|
|
temporaries (list[IO[bytes]]): A list of temporary files that will be used to store
|
|
the output files.
|
|
|
|
Returns:
|
|
None
|
|
|
|
Raises:
|
|
None, but logs errors if:
|
|
- the PPM picture is not valid (e.g. wrong magic number, dimensions, or color encoding)
|
|
- an I/O error occurs while creating or writing to an output file
|
|
"""
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# "P6\nWIDTH HEIGHT\n255\n"
|
|
pos = 0
|
|
picture = 0
|
|
|
|
logger.debug('Dumping %d pictures: %s', len(pictures),prefix)
|
|
|
|
while pos<len(pictures):
|
|
filename = f'{prefix}-{picture:03d}.ppm'
|
|
header = BytesIO(pictures[pos:])
|
|
magic = header.readline().decode('utf8')
|
|
dimensions = header.readline().decode('utf8')
|
|
max_value = int(header.readline().decode('utf8'))
|
|
if magic == 'P6\n':
|
|
pattern = re.compile('^(?P<width>[0-9]+) (?P<height>[0-9]+)\n$')
|
|
m = pattern.match(dimensions)
|
|
if m is not None:
|
|
width = int(m['width'])
|
|
height = int(m['height'])
|
|
else:
|
|
logger.error('Impossible to parse dimensions of picture')
|
|
return
|
|
else:
|
|
logger.error('Not a PPM picture')
|
|
return
|
|
|
|
if max_value != 255:
|
|
logger.error('Not a valid PPM picture. Color are not encoded on byte. Max value: %d',
|
|
max_value)
|
|
|
|
header_len=2+1+ceil(log(width, 10))+1+ceil(log(height, 10))+1+3+1
|
|
try:
|
|
with open(filename, 'wb') as out:
|
|
temporaries.append(out)
|
|
outfd = out.fileno()
|
|
length=header_len+3*width*height
|
|
nb_bytes = 0
|
|
while nb_bytes < length:
|
|
nb_bytes+=write(outfd, pictures[pos+nb_bytes:pos+length])
|
|
pos+=length
|
|
picture+=1
|
|
except OSError:
|
|
logger.error('Impossible to create file: %s', filename)
|