From 432ed4e14c01d66fecf4f333994875f2edea38cb Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Thu, 6 Jan 2022 16:13:51 +0800 Subject: [PATCH] feat: get file size for pages during analysis --- .../V20220106143755__page_file_size.sql | 2 + .../org/gotson/komga/domain/model/BookPage.kt | 1 + .../komga/domain/model/MediaContainerEntry.kt | 1 + .../komga/domain/service/BookAnalyzer.kt | 2 +- .../komga/infrastructure/jooq/MediaDao.kt | 5 +- .../mediacontainer/EpubExtractor.kt | 7 ++- .../mediacontainer/PdfExtractor.kt | 5 +- .../mediacontainer/RarExtractor.kt | 3 +- .../mediacontainer/ZipExtractor.kt | 4 +- .../interfaces/api/rest/BookController.kt | 11 ++++- .../api/rest/TransientBooksController.kt | 1 + .../komga/interfaces/api/rest/dto/PageDto.kt | 4 ++ .../komga/infrastructure/jooq/MediaDaoTest.kt | 13 +++++ .../mediacontainer/EpubExtractorTest.kt | 1 + .../mediacontainer/PdfExtractorTest.kt | 40 ++++++++++++++++ .../mediacontainer/RarExtractorTest.kt | 45 ++++++++++++++++++ .../mediacontainer/ZipExtractorTest.kt | 45 ++++++++++++++++++ komga/src/test/resources/pdf/komga.pdf | Bin 0 -> 20693 bytes 18 files changed, 178 insertions(+), 12 deletions(-) create mode 100644 komga/src/flyway/resources/db/migration/sqlite/V20220106143755__page_file_size.sql create mode 100644 komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractorTest.kt create mode 100644 komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractorTest.kt create mode 100644 komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractorTest.kt create mode 100644 komga/src/test/resources/pdf/komga.pdf diff --git a/komga/src/flyway/resources/db/migration/sqlite/V20220106143755__page_file_size.sql b/komga/src/flyway/resources/db/migration/sqlite/V20220106143755__page_file_size.sql new file mode 100644 index 000000000..20024299e --- /dev/null +++ b/komga/src/flyway/resources/db/migration/sqlite/V20220106143755__page_file_size.sql @@ -0,0 +1,2 @@ +alter table media_page + add column FILE_SIZE int8 NULL; diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookPage.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookPage.kt index b010844e8..dbd80229d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookPage.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookPage.kt @@ -5,4 +5,5 @@ data class BookPage( val mediaType: String, val dimension: Dimension? = null, val fileHash: String = "", + val fileSize: Long? = null, ) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaContainerEntry.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaContainerEntry.kt index 678f1a8de..294084e1f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaContainerEntry.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaContainerEntry.kt @@ -5,4 +5,5 @@ data class MediaContainerEntry( val mediaType: String? = null, val comment: String? = null, val dimension: Dimension? = null, + val fileSize: Long? = null, ) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt index e94b11369..82b6c52d6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt @@ -60,7 +60,7 @@ class BookAnalyzer( entry.mediaType?.let { contentDetector.isImage(it) } ?: false }.let { (images, others) -> Pair( - images.map { BookPage(it.name, it.mediaType!!, it.dimension) }, + images.map { BookPage(fileName = it.name, mediaType = it.mediaType!!, dimension = it.dimension, fileSize = it.fileSize) }, others, ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/MediaDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/MediaDao.kt index 84d163269..b11b4cf8e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/MediaDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/MediaDao.kt @@ -131,7 +131,8 @@ class MediaDao( p.WIDTH, p.HEIGHT, p.FILE_HASH, - ).values(null as String?, null, null, null, null, null, null), + p.FILE_SIZE + ).values(null as String?, null, null, null, null, null, null, null), ).also { step -> chunk.forEach { media -> media.pages.forEachIndexed { index, page -> @@ -143,6 +144,7 @@ class MediaDao( page.dimension?.width, page.dimension?.height, page.fileHash, + page.fileSize, ) } } @@ -233,5 +235,6 @@ class MediaDao( mediaType = mediaType, dimension = if (width != null && height != null) Dimension(width, height) else null, fileHash = fileHash, + fileSize = fileSize ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt index 264b8af7f..a10a9e549 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt @@ -1,6 +1,7 @@ package org.gotson.komga.infrastructure.mediacontainer import mu.KotlinLogging +import org.apache.commons.compress.archivers.ArchiveEntry import org.apache.commons.compress.archivers.zip.ZipFile import org.gotson.komga.domain.model.MediaContainerEntry import org.gotson.komga.domain.model.MediaUnsupportedException @@ -56,11 +57,13 @@ class EpubExtractor( val mediaType = manifest.values.first { it.href == (opfDir?.relativize(image) ?: image).invariantSeparatorsPathString }.mediaType + val zipEntry = zip.getEntry(name) val dimension = if (analyzeDimensions && contentDetector.isImage(mediaType)) - zip.getInputStream(zip.getEntry(name)).use { imageAnalyzer.getDimension(it) } + zip.getInputStream(zipEntry).use { imageAnalyzer.getDimension(it) } else null - MediaContainerEntry(name = name, mediaType = mediaType, dimension = dimension) + val fileSize = if (zipEntry.size == ArchiveEntry.SIZE_UNKNOWN) null else zipEntry.size + MediaContainerEntry(name = name, mediaType = mediaType, dimension = dimension, fileSize = fileSize) } } catch (e: Exception) { logger.error(e) { "File is not a proper Epub, treating it as a zip file" } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractor.kt index c5ad65427..5072155b8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractor.kt @@ -8,7 +8,6 @@ import org.apache.pdfbox.rendering.ImageType import org.apache.pdfbox.rendering.PDFRenderer import org.gotson.komga.domain.model.Dimension import org.gotson.komga.domain.model.MediaContainerEntry -import org.gotson.komga.infrastructure.image.ImageAnalyzer import org.springframework.stereotype.Service import java.io.ByteArrayOutputStream import java.nio.file.Path @@ -19,9 +18,7 @@ import kotlin.math.roundToInt private val logger = KotlinLogging.logger {} @Service -class PdfExtractor( - private val imageAnalyzer: ImageAnalyzer, -) : MediaContainerExtractor { +class PdfExtractor : MediaContainerExtractor { private val mediaType = "image/jpeg" private val imageIOFormat = "jpeg" diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractor.kt index d230ed458..92b8e481d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractor.kt @@ -36,7 +36,8 @@ class RarExtractor( buffer.inputStream().use { imageAnalyzer.getDimension(it) } else null - MediaContainerEntry(name = entry.fileName, mediaType = mediaType, dimension = dimension) + val fileSize = entry.fullUnpackSize + MediaContainerEntry(name = entry.fileName, mediaType = mediaType, dimension = dimension, fileSize = fileSize) } catch (e: Exception) { logger.warn(e) { "Could not analyze entry: ${entry.fileName}" } MediaContainerEntry(name = entry.fileName, comment = e.message) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractor.kt index 33161b042..002429343 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractor.kt @@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.mediacontainer import com.github.benmanes.caffeine.cache.Caffeine import mu.KotlinLogging import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator +import org.apache.commons.compress.archivers.ArchiveEntry import org.apache.commons.compress.archivers.zip.ZipFile import org.gotson.komga.domain.model.MediaContainerEntry import org.gotson.komga.infrastructure.image.ImageAnalyzer @@ -40,7 +41,8 @@ class ZipExtractor( imageAnalyzer.getDimension(stream) else null - MediaContainerEntry(name = entry.name, mediaType = mediaType, dimension = dimension) + val fileSize = if (entry.size == ArchiveEntry.SIZE_UNKNOWN) null else entry.size + MediaContainerEntry(name = entry.name, mediaType = mediaType, dimension = dimension, fileSize = fileSize) } } catch (e: Exception) { logger.warn(e) { "Could not analyze entry: ${entry.name}" } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt index f084b883f..7bf43adfc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt @@ -432,8 +432,15 @@ class BookController( ) Media.Status.ERROR -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed") Media.Status.UNSUPPORTED -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book format is not supported") - Media.Status.READY -> media.pages.mapIndexed { index, s -> - PageDto(index + 1, s.fileName, s.mediaType, s.dimension?.width, s.dimension?.height) + Media.Status.READY -> media.pages.mapIndexed { index, bookPage -> + PageDto( + number = index + 1, + fileName = bookPage.fileName, + mediaType = bookPage.mediaType, + width = bookPage.dimension?.width, + height = bookPage.dimension?.height, + sizeBytes = bookPage.fileSize, + ) } } } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TransientBooksController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TransientBooksController.kt index ec333adef..50dced5b6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TransientBooksController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TransientBooksController.kt @@ -96,6 +96,7 @@ private fun BookWithMedia.toDto() = mediaType = bookPage.mediaType, width = bookPage.dimension?.width, height = bookPage.dimension?.height, + sizeBytes = bookPage.fileSize, ) }, files = media.files, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageDto.kt index a2ceb4b8b..856de0ccc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageDto.kt @@ -1,9 +1,13 @@ package org.gotson.komga.interfaces.api.rest.dto +import com.jakewharton.byteunits.BinaryByteUnit + data class PageDto( val number: Int, val fileName: String, val mediaType: String, val width: Int?, val height: Int?, + val sizeBytes: Long?, + val size: String = sizeBytes?.let { BinaryByteUnit.format(it) } ?: "", ) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt index 654b2b6c8..c0446713f 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/MediaDaoTest.kt @@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.catchThrowable import org.gotson.komga.domain.model.BookPage +import org.gotson.komga.domain.model.Dimension import org.gotson.komga.domain.model.Media import org.gotson.komga.domain.model.makeBook import org.gotson.komga.domain.model.makeLibrary @@ -68,6 +69,9 @@ class MediaDaoTest( BookPage( fileName = "1.jpg", mediaType = "image/jpeg", + dimension = Dimension(10, 10), + fileHash = "hashed", + fileSize = 10, ), ), files = listOf("ComicInfo.xml"), @@ -88,6 +92,9 @@ class MediaDaoTest( with(created.pages.first()) { assertThat(fileName).isEqualTo(media.pages.first().fileName) assertThat(mediaType).isEqualTo(media.pages.first().mediaType) + assertThat(dimension).isEqualTo(media.pages.first().dimension) + assertThat(fileHash).isEqualTo(media.pages.first().fileHash) + assertThat(fileSize).isEqualTo(media.pages.first().fileSize) } assertThat(created.files).hasSize(1) assertThat(created.files.first()).isEqualTo(media.files.first()) @@ -135,6 +142,9 @@ class MediaDaoTest( BookPage( fileName = "2.png", mediaType = "image/png", + dimension = Dimension(10, 10), + fileHash = "hashed", + fileSize = 10, ), ), files = listOf("id.txt"), @@ -155,6 +165,9 @@ class MediaDaoTest( assertThat(modified.comment).isEqualTo(updated.comment) assertThat(modified.pages.first().fileName).isEqualTo(updated.pages.first().fileName) assertThat(modified.pages.first().mediaType).isEqualTo(updated.pages.first().mediaType) + assertThat(modified.pages.first().dimension).isEqualTo(updated.pages.first().dimension) + assertThat(modified.pages.first().fileHash).isEqualTo(updated.pages.first().fileHash) + assertThat(modified.pages.first().fileSize).isEqualTo(updated.pages.first().fileSize) assertThat(modified.files.first()).isEqualTo(updated.files.first()) } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractorTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractorTest.kt index c05609362..d71fbd137 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractorTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractorTest.kt @@ -26,6 +26,7 @@ class EpubExtractorTest { assertThat(name).isEqualTo("cover.jpeg") assertThat(mediaType).isEqualTo("image/jpeg") assertThat(dimension).isEqualTo(Dimension(461, 616)) + assertThat(fileSize).isEqualTo(56756) } } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractorTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractorTest.kt new file mode 100644 index 000000000..6e643722d --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/PdfExtractorTest.kt @@ -0,0 +1,40 @@ +package org.gotson.komga.infrastructure.mediacontainer + +import org.assertj.core.api.Assertions +import org.gotson.komga.domain.model.Dimension +import org.junit.jupiter.api.Test +import org.springframework.core.io.ClassPathResource + +class PdfExtractorTest { + private val pdfExtractor = PdfExtractor() + + @Test + fun `given pdf file when parsing for entries then returns all images`() { + val fileResource = ClassPathResource("pdf/komga.pdf") + + val entries = pdfExtractor.getEntries(fileResource.file.toPath(), true) + + Assertions.assertThat(entries).hasSize(1) + with(entries.first()) { + Assertions.assertThat(name).isEqualTo("0") + Assertions.assertThat(mediaType).isEqualTo("image/jpeg") + Assertions.assertThat(dimension).isEqualTo(Dimension(1536, 1536)) + Assertions.assertThat(fileSize).isNull() + } + } + + @Test + fun `given pdf file when parsing for entries without analyzing dimensions then returns all images without dimensions`() { + val fileResource = ClassPathResource("pdf/komga.pdf") + + val entries = pdfExtractor.getEntries(fileResource.file.toPath(), false) + + Assertions.assertThat(entries).hasSize(1) + with(entries.first()) { + Assertions.assertThat(name).isEqualTo("0") + Assertions.assertThat(mediaType).isEqualTo("image/jpeg") + Assertions.assertThat(dimension).isNull() + Assertions.assertThat(fileSize).isNull() + } + } +} diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractorTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractorTest.kt new file mode 100644 index 000000000..caade7bed --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/RarExtractorTest.kt @@ -0,0 +1,45 @@ +package org.gotson.komga.infrastructure.mediacontainer + +import org.apache.tika.config.TikaConfig +import org.assertj.core.api.Assertions +import org.gotson.komga.domain.model.Dimension +import org.gotson.komga.infrastructure.image.ImageAnalyzer +import org.junit.jupiter.api.Test +import org.springframework.core.io.ClassPathResource + +class RarExtractorTest { + + private val contentDetector = ContentDetector(TikaConfig()) + private val imageAnalyzer = ImageAnalyzer() + private val rarExtractor = RarExtractor(contentDetector, imageAnalyzer) + + @Test + fun `given rar file when parsing for entries then returns all images`() { + val fileResource = ClassPathResource("archives/rar4.rar") + + val entries = rarExtractor.getEntries(fileResource.file.toPath(), true) + + Assertions.assertThat(entries).hasSize(3) + with(entries.first()) { + Assertions.assertThat(name).isEqualTo("komga-1.png") + Assertions.assertThat(mediaType).isEqualTo("image/png") + Assertions.assertThat(dimension).isEqualTo(Dimension(48, 48)) + Assertions.assertThat(fileSize).isEqualTo(3108) + } + } + + @Test + fun `given rar file when parsing for entries without analyzing dimensions then returns all images without dimensions`() { + val fileResource = ClassPathResource("archives/rar4.rar") + + val entries = rarExtractor.getEntries(fileResource.file.toPath(), false) + + Assertions.assertThat(entries).hasSize(3) + with(entries.first()) { + Assertions.assertThat(name).isEqualTo("komga-1.png") + Assertions.assertThat(mediaType).isEqualTo("image/png") + Assertions.assertThat(dimension).isNull() + Assertions.assertThat(fileSize).isEqualTo(3108) + } + } +} diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractorTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractorTest.kt new file mode 100644 index 000000000..823ef7e52 --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/ZipExtractorTest.kt @@ -0,0 +1,45 @@ +package org.gotson.komga.infrastructure.mediacontainer + +import org.apache.tika.config.TikaConfig +import org.assertj.core.api.Assertions.assertThat +import org.gotson.komga.domain.model.Dimension +import org.gotson.komga.infrastructure.image.ImageAnalyzer +import org.junit.jupiter.api.Test +import org.springframework.core.io.ClassPathResource + +class ZipExtractorTest { + + private val contentDetector = ContentDetector(TikaConfig()) + private val imageAnalyzer = ImageAnalyzer() + private val zipExtractor = ZipExtractor(contentDetector, imageAnalyzer) + + @Test + fun `given zip file when parsing for entries then returns all images`() { + val fileResource = ClassPathResource("archives/zip.zip") + + val entries = zipExtractor.getEntries(fileResource.file.toPath(), true) + + assertThat(entries).hasSize(1) + with(entries.first()) { + assertThat(name).isEqualTo("komga.png") + assertThat(mediaType).isEqualTo("image/png") + assertThat(dimension).isEqualTo(Dimension(48, 48)) + assertThat(fileSize).isEqualTo(3108) + } + } + + @Test + fun `given zip file when parsing for entries without analyzing dimensions then returns all images without dimensions`() { + val fileResource = ClassPathResource("archives/zip.zip") + + val entries = zipExtractor.getEntries(fileResource.file.toPath(), false) + + assertThat(entries).hasSize(1) + with(entries.first()) { + assertThat(name).isEqualTo("komga.png") + assertThat(mediaType).isEqualTo("image/png") + assertThat(dimension).isNull() + assertThat(fileSize).isEqualTo(3108) + } + } +} diff --git a/komga/src/test/resources/pdf/komga.pdf b/komga/src/test/resources/pdf/komga.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c61ae3295f5d67532b32e31a6866c5b16ee7f834 GIT binary patch literal 20693 zcmbrmbyQr>wk`^U5TtQy+=ELa!5xCThT!h*u8q3}cMtCF?rsSl+=2#uoqc}$oc+dq zZ`?b^{i}PdRaI-wuWZ(w^D8PvvCoVkCU#Op5Gf0(ouMTnA0HyKqJf!-6Dcd?i83Oy zs)xM^DYK}7vw^jp8KQsyqKU2X--3T%|GUC}mHAgSL}nQaV<*zDq-=kyljBE{;YJN&d>DsbFYnV&n|@Ps%K1!$QjSujV1* zFe^IR8L5~!lYaW|Cvqmn76u}A?xbH?AkR2JASQ0ke}4YT<7j99|Nqj?))^v$6Dj-O z)*#xOyVw|#g8rhT{2vWKC?%oXtr&{$0e$+0n$n2GIfWpAY|s41fO`*&zN) z0Wmv7CB(mA{!{)RSpPQjw`DfYe*^tjufI>ek}^Yji?7td9WR^FufpjwjSw!Z~7S_%tj*u>7{%mdF zY$9f2WM^#hwy=dFEFIdoShDOFufEgmoF9d(N$c3lfeot zg^{hE&B@eY>i+F#>fY#-∾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 P8y0~SgTcsF6tVvRfE_Nb literal 0 HcmV?d00001