Skip to content

Commit

Permalink
Allow to recheck all covers
Browse files Browse the repository at this point in the history
  • Loading branch information
SergioMendolia committed Sep 19, 2023
1 parent 45f2906 commit c103e9d
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 45 deletions.
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"doctrine/orm": "^2.14",
"easycorp/easyadmin-bundle": "^4.7",
"gedmo/doctrine-extensions": "^3.11",
"gemorroj/archive7z": "^5.6",
"google/apiclient": "^2.15",
"kiwilan/php-ebook": "^2.0",
"knplabs/knp-markdown-bundle": "^1.10",
Expand Down Expand Up @@ -60,7 +61,8 @@
"symfony/webpack-encore-bundle": "^2.0",
"symfony/yaml": "^6.2",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
"twig/twig": "^2.12|^3.0",
"ext-imagick": "*"
},
"config": {
"allow-plugins": {
Expand Down
54 changes: 53 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3'

services:
biblioteca:
image: ghcr.io/biblioverse/biblioteca-docker:1.0.4
image: ghcr.io/biblioverse/biblioteca-docker:1.0.6
ports:
- 48480:8080
depends_on:
Expand Down
79 changes: 79 additions & 0 deletions src/Command/BooksExtractCoverCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace App\Command;

use App\Entity\Book;
use App\Repository\BookRepository;
use App\Service\BookFileSystemManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;

#[AsCommand(
name: 'books:extract-cover',
description: 'Add a short description for your command',
)]
class BooksExtractCoverCommand extends Command
{
public function __construct(private BookFileSystemManager $fileSystemManager, private BookRepository $bookRepository, private EntityManagerInterface $entityManager)
{
parent::__construct();
}

protected function configure(): void
{
$this
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force the extraction of the cover')
->addArgument('book-id', InputOption::VALUE_REQUIRED, 'Which book to extract the cover from, use "all" for all books')
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$bookId = $input->getArgument('book-id');
$force = $input->getOption('force');
if ($bookId === 'all') {
$books = $this->bookRepository->findAll();
} else {
$books = $this->bookRepository->findBy(['id' => $bookId]);
}

if (count($books) === 0) {
$io->error('No books found');

return Command::FAILURE;
}

$io->note(sprintf('Processing: %s book(s)', count($books)));

$progressBar = new ProgressBar($output, count($books));
$progressBar->start();
$fs = new Filesystem();
foreach ($books as $book) {
/* @var Book $book */
$progressBar->advance();

if ($force === true || $book->getImageFilename() === null || !$fs->exists($book->getImagePath().$book->getImageFilename())) {
try {
$book = $this->fileSystemManager->extractCover($book);
$this->entityManager->persist($book);
} catch (\Exception $e) {
$io->error($e->getMessage());
continue;
}
}
}
$this->entityManager->flush();

$progressBar->finish();

return Command::SUCCESS;
}
}
126 changes: 113 additions & 13 deletions src/Service/BookFileSystemManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace App\Service;

use App\Entity\Book;
use Archive7z\Archive7z;
use Kiwilan\Ebook\Ebook;
use Psr\Log\LoggerInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
Expand Down Expand Up @@ -52,7 +54,9 @@ public function getAllBooksFiles(): \Iterator
public function getBookFile(Book $book): \SplFileInfo
{
$finder = new Finder();
$finder->files()->name($book->getBookFilename())->in($this->getBooksDirectory().$book->getBookPath());
$filename = str_replace(['[', ']'], '*', $book->getBookFilename());

$finder->files()->name($filename)->in($this->getBooksDirectory().$book->getBookPath());
$return = iterator_to_array($finder->getIterator());

if (0 === count($return)) {
Expand Down Expand Up @@ -120,7 +124,7 @@ private function calculateFilePath(Book $book): string
$author = mb_strtolower($main);
$title = mb_strtolower($this->slugger->slug($book->getTitle()));
$serie = null !== $book->getSerie() ? mb_strtolower($this->slugger->slug($book->getSerie())) : null;
$letter = $author[0];
$letter = substr($main, 0, 1);
$path = [$letter];

$path[] = $author;
Expand Down Expand Up @@ -207,20 +211,24 @@ public function removeEmptySubFolders(string $path = null): bool
}
$empty = true;

$files = glob($path.DIRECTORY_SEPARATOR.'{,.}[!.,!..]*', GLOB_MARK | GLOB_BRACE);
if (false !== $files && count($files) > 0) {
foreach ($files as $file) {
if (is_dir($file)) {
if (!$this->removeEmptySubFolders($file)) {
$empty = false;
}
} else {
$empty = false;
}
$finder = new Finder();

$files = $finder->in($path)->ignoreDotFiles(true)->files();
$directories = $finder->in($path)->ignoreDotFiles(true)->directories();

if ($files->count() > 0 || $directories->count() > 0) {
$empty = false;
}

foreach ($directories as $directory) {
if (!$this->removeEmptySubFolders($directory->getRealPath())) {
$empty = false;
}
}

if ($empty) {
rmdir($path);
$fs = new Filesystem();
$fs->remove($path);
}

return $empty;
Expand Down Expand Up @@ -311,4 +319,96 @@ public function deleteBookFiles(Book $book): void
$this->removeEmptySubFolders($this->getCoverDirectory().$book->getImagePath());
}
}

public function extractCover(Book $book): Book
{
$filesystem = new Filesystem();
$bookFile = $this->getBookFile($book);
switch ($book->getExtension()) {
case 'epub':
$ebook = Ebook::read($bookFile->getRealPath());
if ($ebook === null) {
break;
}
$cover = $ebook->getCover();

if ($cover === null || $cover->getPath() === null) {
break;
}
$coverContent = $cover->getContent();

$coverFileName = explode('/', $cover->getPath());
$coverFileName = end($coverFileName);
$ext = explode('.', $coverFileName);
$book->setImageExtension(end($ext));

$coverPath = $this->getCalculatedImagePath($book, true);
$coverFileName = $this->getCalculatedImageName($book);

$filesystem = new Filesystem();
$filesystem->mkdir($coverPath);

$coverFile = file_put_contents($coverPath.$coverFileName, $coverContent);

if (false !== $coverFile) {
$book->setImagePath($this->getCalculatedImagePath($book, false));
$book->setImageFilename($coverFileName);
}
break;
case 'cbr':
case 'cbz':
$archive = new Archive7z($bookFile->getRealPath());
$entries = [];
foreach ($archive->getEntries() as $entry) {
if (str_contains($entry->getPath(), '.jpg') || str_contains($entry->getPath(), '.jpeg')) {
$entries[] = $entry->getPath();
}
}
ksort($entries);
if (count($entries) === 0) {
break;
}

$archive->setOutputDirectory('/tmp')->extractEntry($entries[0]); // extract the archive

$filesystem->mkdir($this->getCalculatedImagePath($book, true));
$checksum = $this->getFileChecksum(new \SplFileInfo('/tmp/'.$entries[0]));
$filesystem->rename(
'/tmp/'.$entries[0],
$this->getCalculatedImagePath($book, true).$this->getCalculatedImageName($book, $checksum),
true);

$book->setImagePath($this->getCalculatedImagePath($book, false));
$book->setImageFilename($this->getCalculatedImageName($book, $checksum));
$book->setImageExtension('jpg');

break;
case 'pdf':
$checksum = md5(''.time());

try {
$im = new \Imagick($bookFile->getRealPath().'[0]'); // 0-first page, 1-second page
} catch (\Exception $e) {
$this->logger->error('Could not extract cover', ['book' => $bookFile->getRealPath(), 'exception' => $e->getMessage()]);
break;
}

$im->setImageColorspace(255); // prevent image colors from inverting
$im->setImageFormat('jpeg');
$im->thumbnailImage(300, 460); // width and height
$book->setImageExtension('jpg');
$filesystem->mkdir($this->getCalculatedImagePath($book, true));
$im->writeImage($this->getCalculatedImagePath($book, true).$this->getCalculatedImageName($book, $checksum));
$im->clear();
$im->destroy();
$book->setImagePath($this->getCalculatedImagePath($book, false));
$book->setImageFilename($this->getCalculatedImageName($book, $checksum));

break;
default:
throw new \RuntimeException('Extension not implemented');
}

return $book;
}
}
31 changes: 2 additions & 29 deletions src/Service/BookManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Kiwilan\Ebook\Ebook;
use Kiwilan\Ebook\EbookCover;
use Kiwilan\Ebook\Tools\BookAuthor;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\KernelInterface;

/**
Expand Down Expand Up @@ -62,34 +61,8 @@ public function createBook(\SplFileInfo $file): Book

$book->setBookPath('');
$book->setBookFilename('');
$book = $this->updateBookLocation($book, $file);

/** @var ?EbookCover $cover */
$cover = $extractedMetadata['cover'];

if (null !== $cover && null !== $cover->getPath()) {
$coverContent = $cover->getContent();

$coverFileName = explode('/', $cover->getPath());
$coverFileName = end($coverFileName);
$ext = explode('.', $coverFileName);
$book->setImageExtension(end($ext));

$coverPath = $this->fileSystemManager->getCalculatedImagePath($book, true);
$coverFileName = $this->fileSystemManager->getCalculatedImageName($book);

$filesystem = new Filesystem();
$filesystem->mkdir($coverPath);

$coverFile = file_put_contents($coverPath.$coverFileName, $coverContent);

if (false !== $coverFile) {
$book->setImagePath($this->fileSystemManager->getCalculatedImagePath($book, false));
$book->setImageFilename($coverFileName);
}
}

return $book;
return $this->updateBookLocation($book, $file);
}

public function updateBookLocation(Book $book, \SplFileInfo $file): Book
Expand Down Expand Up @@ -138,7 +111,7 @@ public function extractEbookMetadata(\SplFileInfo $file): array
}

return [
'title' => $ebook->getTitle() ?? $file->getFilename(), // string
'title' => $ebook->getTitle() ?? $file->getBasename('.'.$file->getExtension()), // string
'authors' => $ebook->getAuthors(), // BookAuthor[] (`name`: string, `role`: string)
'main_author' => $ebook->getAuthorMain(), // ?BookAuthor => First BookAuthor (`name`: string, `role`: string)
'description' => $ebook->getDescription(), // ?string
Expand Down

0 comments on commit c103e9d

Please sign in to comment.