mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-07 08:37:13 +00:00
Merge pull request #532 from paperless-ngx/feature-barcode-splitter
Feature barcode splitter
This commit is contained in:
27 files changed
+778
-1
No files matched your search
@@ -1,6 +1,12 @@
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from typing import List # for type hinting. Can be removed, if only Python >3.8 is used
|
||||
|
||||
import tqdm
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from django.conf import settings
|
||||
from django.db.models.signals import post_save
|
||||
from documents import index
|
||||
@@ -14,8 +20,12 @@ from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import Tag
|
||||
from documents.sanity_checker import SanityCheckFailedException
|
||||
from pdf2image import convert_from_path
|
||||
from pikepdf import Pdf
|
||||
from pyzbar import pyzbar
|
||||
from whoosh.writing import AsyncWriter
|
||||
|
||||
|
||||
logger = logging.getLogger("paperless.tasks")
|
||||
|
||||
|
||||
@@ -62,6 +72,115 @@ def train_classifier():
|
||||
logger.warning("Classifier error: " + str(e))
|
||||
|
||||
|
||||
def barcode_reader(image) -> List[str]:
|
||||
"""
|
||||
Read any barcodes contained in image
|
||||
Returns a list containing all found barcodes
|
||||
"""
|
||||
barcodes = []
|
||||
# Decode the barcode image
|
||||
detected_barcodes = pyzbar.decode(image)
|
||||
|
||||
if detected_barcodes:
|
||||
# Traverse through all the detected barcodes in image
|
||||
for barcode in detected_barcodes:
|
||||
if barcode.data:
|
||||
decoded_barcode = barcode.data.decode("utf-8")
|
||||
barcodes.append(decoded_barcode)
|
||||
logger.debug(
|
||||
f"Barcode of type {str(barcode.type)} found: {decoded_barcode}",
|
||||
)
|
||||
return barcodes
|
||||
|
||||
|
||||
def scan_file_for_separating_barcodes(filepath: str) -> List[int]:
|
||||
"""
|
||||
Scan the provided file for page separating barcodes
|
||||
Returns a list of pagenumbers, which separate the file
|
||||
"""
|
||||
separator_page_numbers = []
|
||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||
# use a temporary directory in case the file os too big to handle in memory
|
||||
with tempfile.TemporaryDirectory() as path:
|
||||
pages_from_path = convert_from_path(filepath, output_folder=path)
|
||||
for current_page_number, page in enumerate(pages_from_path):
|
||||
current_barcodes = barcode_reader(page)
|
||||
if separator_barcode in current_barcodes:
|
||||
separator_page_numbers.append(current_page_number)
|
||||
return separator_page_numbers
|
||||
|
||||
|
||||
def separate_pages(filepath: str, pages_to_split_on: List[int]) -> List[str]:
|
||||
"""
|
||||
Separate the provided file on the pages_to_split_on.
|
||||
The pages which are defined by page_numbers will be removed.
|
||||
Returns a list of (temporary) filepaths to consume.
|
||||
These will need to be deleted later.
|
||||
"""
|
||||
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
|
||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||
fname = os.path.splitext(os.path.basename(filepath))[0]
|
||||
pdf = Pdf.open(filepath)
|
||||
document_paths = []
|
||||
logger.debug(f"Temp dir is {str(tempdir)}")
|
||||
if not pages_to_split_on:
|
||||
logger.warning("No pages to split on!")
|
||||
else:
|
||||
# go from the first page to the first separator page
|
||||
dst = Pdf.new()
|
||||
for n, page in enumerate(pdf.pages):
|
||||
if n < pages_to_split_on[0]:
|
||||
dst.pages.append(page)
|
||||
output_filename = "{}_document_0.pdf".format(fname)
|
||||
savepath = os.path.join(tempdir, output_filename)
|
||||
with open(savepath, "wb") as out:
|
||||
dst.save(out)
|
||||
document_paths = [savepath]
|
||||
|
||||
# iterate through the rest of the document
|
||||
for count, page_number in enumerate(pages_to_split_on):
|
||||
logger.debug(f"Count: {str(count)} page_number: {str(page_number)}")
|
||||
dst = Pdf.new()
|
||||
try:
|
||||
next_page = pages_to_split_on[count + 1]
|
||||
except IndexError:
|
||||
next_page = len(pdf.pages)
|
||||
# skip the first page_number. This contains the barcode page
|
||||
for page in range(page_number + 1, next_page):
|
||||
logger.debug(
|
||||
f"page_number: {str(page_number)} next_page: {str(next_page)}",
|
||||
)
|
||||
dst.pages.append(pdf.pages[page])
|
||||
output_filename = "{}_document_{}.pdf".format(fname, str(count + 1))
|
||||
logger.debug(f"pdf no:{str(count)} has {str(len(dst.pages))} pages")
|
||||
savepath = os.path.join(tempdir, output_filename)
|
||||
with open(savepath, "wb") as out:
|
||||
dst.save(out)
|
||||
document_paths.append(savepath)
|
||||
logger.debug(f"Temp files are {str(document_paths)}")
|
||||
return document_paths
|
||||
|
||||
|
||||
def save_to_dir(
|
||||
filepath: str,
|
||||
newname: str = None,
|
||||
target_dir: str = settings.CONSUMPTION_DIR,
|
||||
):
|
||||
"""
|
||||
Copies filepath to target_dir.
|
||||
Optionally rename the file.
|
||||
"""
|
||||
if os.path.isfile(filepath) and os.path.isdir(target_dir):
|
||||
dst = shutil.copy(filepath, target_dir)
|
||||
logging.debug(f"saved {str(filepath)} to {str(dst)}")
|
||||
if newname:
|
||||
dst_new = os.path.join(target_dir, newname)
|
||||
logger.debug(f"moving {str(dst)} to {str(dst_new)}")
|
||||
os.rename(dst, dst_new)
|
||||
else:
|
||||
logger.warning(f"{str(filepath)} or {str(target_dir)} don't exist.")
|
||||
|
||||
|
||||
def consume_file(
|
||||
path,
|
||||
override_filename=None,
|
||||
@@ -72,6 +191,48 @@ def consume_file(
|
||||
task_id=None,
|
||||
):
|
||||
|
||||
# check for separators in current document
|
||||
if settings.CONSUMER_ENABLE_BARCODES:
|
||||
separators = []
|
||||
document_list = []
|
||||
separators = scan_file_for_separating_barcodes(path)
|
||||
if separators:
|
||||
logger.debug(f"Pages with separators found in: {str(path)}")
|
||||
document_list = separate_pages(path, separators)
|
||||
if document_list:
|
||||
for n, document in enumerate(document_list):
|
||||
# save to consumption dir
|
||||
# rename it to the original filename with number prefix
|
||||
if override_filename:
|
||||
newname = f"{str(n)}_" + override_filename
|
||||
else:
|
||||
newname = None
|
||||
save_to_dir(document, newname=newname)
|
||||
# if we got here, the document was successfully split
|
||||
# and can safely be deleted
|
||||
logger.debug("Deleting file {}".format(path))
|
||||
os.unlink(path)
|
||||
# notify the sender, otherwise the progress bar
|
||||
# in the UI stays stuck
|
||||
payload = {
|
||||
"filename": override_filename,
|
||||
"task_id": task_id,
|
||||
"current_progress": 100,
|
||||
"max_progress": 100,
|
||||
"status": "SUCCESS",
|
||||
"message": "finished",
|
||||
}
|
||||
try:
|
||||
async_to_sync(get_channel_layer().group_send)(
|
||||
"status_updates",
|
||||
{"type": "status_update", "data": payload},
|
||||
)
|
||||
except OSError as e:
|
||||
logger.warning("OSError. It could be, the broker cannot be reached.")
|
||||
logger.warning(str(e))
|
||||
return "File successfully split"
|
||||
|
||||
# continue with consumption if no barcode was found
|
||||
document = Consumer().try_consume_file(
|
||||
path,
|
||||
override_filename=override_filename,
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 836 B |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 891 B |
@@ -0,0 +1,243 @@
|
||||
%PDF-1.6
|
||||
%äüöß
|
||||
2 0 obj
|
||||
<</Length 3 0 R/Filter/FlateDecode>>
|
||||
stream
|
||||
xś]QËjÄ0Ľű+t.Ä+ŮVl�1äµĐŢ=”žú:mË_ŮަˇbŤ<Ł‘Ôßę¨ŃŕČÚx†ŕH‡žŕú¦ďŕł1ä\?Ô¸*"ÔĽëuÖW8 D˝ľ?%¤L ␍ZtČŘŁÇ�;ÉÄl7ŕpĚ6á„s!3.ą
|
||||
ĺ¸×ćçőA-«:ýk€Ĺ<âźż2żţ¦ú—ËĺΤZj}3ŠĹzŘ(ŽąóL␍ÎŇ•–zB �Č “-ˇ#.Bęë<2sHäk&Ü
|
||||
NYR±¦ˇ·Ç±~'ń§6:-%.Ő['‚;n+Ůś·%śŕDQ›ÁYÖV¦7ÎÉčNď˙•ä—uŐn"®g#ĽśŐáţě`ţ‚RôŇćmx
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
284
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<</Type/XObject/Subtype/Image/Width 828/Height 236/BitsPerComponent 8/Length 5 0 R
|
||||
/Filter/FlateDecode/ColorSpace/DeviceRGB
|
||||
>>
|
||||
stream
|
||||
xœíÛÁn`IŽdÑüÿŸîYÌ¢„`”ñòÝA8—¢é+½Mý÷ßÿÎÿüeþûËÏü÷—i~æ2j˜þÞü¾ô®¡ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~º£Ù<õ½Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO
ô
‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´ñ7¶d·±mõj2‰ü^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w
}·l´“¶5þÆ–ì6¶^M&‘¿ÕËlK2
~Line truncated
|
||||
g2Éďýy+Ďśţüď~˛OűyëÍ›7oŢĽył;?˙Íý˙•řÉü_§żý篔żĺl©ţöŰ“Ý?˙őň'§Î7oŢĽyóćÍÍĽo¶dŢ7Űß2߼yóćÍ›7»łő-´ű“śóć›íożýßůÉźç}ď˝yóćÍ›7Óyßlď›í}ł˝yóćÍ›7ÎůŰ_ŢßýEţöŻüď~űźßi»ßlľűŐdřfë˙K{óćÍ›7oţµyßlď›íň·˙ĽűľŮŢĽyóćÍ›źçż?ćĎÍsňýŰoüÝo1|łý|ÉĆŮüäÍoOş˙™đ»˙ŢĽyóćÍ›s~ţëI|üů{§Úć·˙í{#Éů9?ůůťłůɛߞt˙3áw˙␍ĽyóćÍ›7˙ňüî#˙Éä_óżőÍooś˝ęo_8^ ů™©™¸Ňď.ÖďľyóćÍ›7˙ćüî[(˙Éä_“ßő;çďľFňÉU?‰ýéüůg¦fâJż»XżűćÍ›7oŢüks˙=@ä_#ůÜ|ý®ű˝3—üíżş7oŢĽyóćÍźóľŮ~ç¤űűf{óćÍ›7oŢüßůŰ_^âo÷ÖWÓżüÍö·÷o˙7š^ěw˙Ą˝yóćÍ›7˙ňĽo¶iÎď¶Ţ7[˛űľŮŢĽyóćÍ›d.ż…ţťo¶˙żľŻ’źś6úyŢwÚ›7oŢĽy3ť÷Í–ükŻr~‰ĺ?ůľŮŢĽyóćÍóü÷—ąůÉ?·gţÝ’Ď´uňۧÎÝË÷i?o˝yóćÍ›7o�Ůý�ţäź[Ť3IřŮĐtÉűÔą{ů>íç7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢĽyóćÍ›7oŢü;ó | ||||