def from_bytes(cls, b):
		"""Create an APNG from raw bytes.
		
		:arg bytes b: The raw bytes of the APNG file.
		:rtype: APNG
		"""
		hdr = None
		head_chunks = []
		end = ("IEND", make_chunk("IEND", b""))
		
		frame_chunks = []
		frames = []
		num_plays = 0
		frame_has_head_chunks = False
		
		control = None
		
		for type_, data in parse_chunks(b):
			if type_ == "IHDR":
				hdr = data
				frame_chunks.append((type_, data))
			elif type_ == "acTL":
				_num_frames, num_plays = struct.unpack("!II", data[8:-4])
				continue
			elif type_ == "fcTL":
				if any(type_ == "IDAT" for type_, data in frame_chunks):
					# IDAT inside chunk, go to next frame
					frame_chunks.append(end)
					frames.append((PNG.from_chunks(frame_chunks), control))
					frame_has_head_chunks = False
					control = FrameControl.from_bytes(data[12:-4])
					# https://github.com/PyCQA/pylint/issues/2072
					# pylint: disable=typecheck
					hdr = make_chunk("IHDR", struct.pack("!II", control.width, control.height) + hdr[16:-4])
					frame_chunks = [("IHDR", hdr)]
				else:
					control = FrameControl.from_bytes(data[12:-4])
			elif type_ == "IDAT":
				if not frame_has_head_chunks:
					frame_chunks.extend(head_chunks)
					frame_has_head_chunks = True
				frame_chunks.append((type_, data))
			elif type_ == "fdAT":
				# convert to IDAT
				if not frame_has_head_chunks:
					frame_chunks.extend(head_chunks)
					frame_has_head_chunks = True
				frame_chunks.append(("IDAT", make_chunk("IDAT", data[12:-4])))
			elif type_ == "IEND":
				# end
				frame_chunks.append(end)
				frames.append((PNG.from_chunks(frame_chunks), control))
				break
			elif type_ in CHUNK_BEFORE_IDAT:
				head_chunks.append((type_, data))
			else:
				frame_chunks.append((type_, data))
				
		o = cls()
		o.frames = frames
		o.num_plays = num_plays
		return o