Skip to content

Commit

Permalink
Merge pull request #74 from kiwilan/develop
Browse files Browse the repository at this point in the history
v2.4.8
  • Loading branch information
ewilan-riviere authored May 15, 2024
2 parents e17eb3c + 9a2b46b commit 172fd00
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 96 deletions.
19 changes: 9 additions & 10 deletions .github/workflows/run-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,6 @@ jobs:
runs-on: macos-latest

steps:
- name: Install
run: |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew update
brew install p7zip
brew install rar
brew install ghostscript
brew install imagemagick
shell: bash

- name: Checkout code
uses: actions/checkout@v4

Expand All @@ -27,6 +17,15 @@ jobs:
extensions: imagick, zip, fileinfo, intl
coverage: pcov

- name: Install
continue-on-error: true
run: |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew update
brew install p7zip rar ghostscript
brew install imagemagick
shell: bash

- name: Add rar extension
run: |
git clone https://github.com/cataphract/php-rar
Expand Down
37 changes: 22 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ This package was built for [`bookshelves-project/bookshelves`](https://github.co
- eBooks: `EPUB` v2 and v3 from [IDPF](https://idpf.org/) with `calibre:series` from [Calibre](https://calibre-ebook.com/) | `MOBI` from Mobipocket (and derivatives) | `FB2` from [FictionBook](https://en.wikipedia.org/wiki/FictionBook)
- Comics: `CBAM` (Comic Book Archive Metadata) : `ComicInfo.xml` format from _ComicRack_ and maintained by [`anansi-project`](https://github.com/anansi-project/comicinfo)
- `PDF` with [`smalot/pdfparser`](https://github.com/smalot/pdfparser)
- Audiobooks: `ID3`, `vorbis` and `flac` tags with [`kiwilan/php-audio`](https://github.com/kiwilan/php-audio) (not included)
- Audiobooks: `ID3`, `vorbis` and `flac` tags with [`kiwilan/php-audio`](https://github.com/kiwilan/php-audio) (not included), based on [audiobookshelf specifications](https://www.audiobookshelf.org/docs#book-audio-metadata)
- 🔖 Chapters extraction (`EPUB` only)
- 📦 `EPUB` and `CBZ` creation supported
<!-- - 📝 `EPUB` and `CBZ` metadata update supported -->
Expand All @@ -77,7 +77,6 @@ This package was built for [`bookshelves-project/bookshelves`](https://github.co
- [ ] Add better handling of MOBI files: [`libmobi`](https://github.com/bfabiszewski/libmobi) and [`ebook-convert`](https://manual.calibre-ebook.com/generated/en/ebook-convert.html) from Calibre (fallback is available)
- [ ] Add support of [`ebook-convert`](https://manual.calibre-ebook.com/generated/en/ebook-convert.html) from Calibre
- [ ] Add suport for DJVU: [`djvulibre`](https://djvu.sourceforge.net/)
- [ ] Support FB2 archive

## Installation

Expand Down Expand Up @@ -245,21 +244,29 @@ $cover->getContents(bool $toBase64 = false); // ?string => content of cover, if

For audiobooks, you have to install seperately [`kiwilan/php-audio`](https://github.com/kiwilan/php-audio).

Specifications are based on [audiobookshelf](https://www.audiobookshelf.org/docs#book-audio-metadata) and [ID3](https://id3.org/ID3v2.4.0) tags. Metadata on audio files will be mapped as follows (second tag after "/" is a fallback):

Properties of `Audio::class` are:

| **Ebook** | **Audio** |
| ------------- | ------------------------ |
| `title` | `title` |
| `author` | `artist` |
| `description` | `description` |
| `publisher` | `albumArtist` |
| `series` | `album` |
| `volume` | `trackNumber` |
| `publishDate` | `artist` |
| `copyright` | `year` or `creationDate` |
| `copyright` | `encodingBy` |
| `tags` | `genre` |
| `language` | `language` |
| **ID3 Tag (case-insensitive) ** | **eBook** |
| ------------------------------- | -------------------------- |
| `artist` / `album-artist` | Authors\* |
| `album` / `title` | Title |
| `subtitle` | Extra property `subtitle` |
| `publisher` | Publisher |
| `year` | Publish Year |
| `composer` | Extra property `narrators` |
| `description` | Description |
| `genre` | Tags\*\* |
| `series` / `mvnm` | Series |
| `series-part` / `mvin` | Volume |
| `language` / `lang` | Language |
| `isbn` | Identifiers `isbn` |
| `asin` / `audible_asin` | Identifiers `asin` |
| Overdrive MediaMarkers | Extra property `chapters` |

- \* Authors naming as well as multiple authors separated by `,`, `;`, `&` or `and`.
- \*\* Tags can include multiple tags separated by `/`, `//`, or `;`. e.g. "Science Fiction/Fiction/Fantasy"

You can find all metadata into `getExtras()` array of `Ebook::class`.

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "kiwilan/php-ebook",
"description": "PHP package to read metadata and extract covers from eBooks, comics and audiobooks.",
"version": "2.3.8",
"version": "2.4.8",
"keywords": [
"php",
"ebook",
Expand Down
216 changes: 154 additions & 62 deletions src/Formats/Audio/AudiobookModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Kiwilan\Ebook\EbookCover;
use Kiwilan\Ebook\Formats\EbookModule;
use Kiwilan\Ebook\Models\BookAuthor;
use Kiwilan\Ebook\Models\BookIdentifier;

class AudiobookModule extends EbookModule
{
Expand Down Expand Up @@ -34,24 +35,50 @@ private function create(): self
{
$audio = $this->ebook->getAudio();

$authors = $audio->getArtist() ?? $audio->getAlbumArtist();
$genres = $this->parseGenres($audio->getGenre());
$series = $audio->getTag('series') ?? $audio->getTag('mvnm');
$series_part = $audio->getTag('series-part') ?? $audio->getTag('mvin');
$series_part = $this->parseTag($series_part);
$language = $audio->getTag('language') ?? $audio->getTag('lang');
$narrators = $audio->getComposer();

$chapters = [];
$quicktime = $audio->toArray()['quicktime'] ?? [];
if (array_key_exists('chapters', $quicktime)) {
$chapters = $quicktime['chapters'];
}

$this->audio = [
'title' => $audio->getTitle(),
'artist' => $audio->getArtist(),
'albumArtist' => $audio->getAlbumArtist(),
'album' => $audio->getAlbum(),
'genre' => $audio->getGenre(),
'year' => $audio->getYear(),
'trackNumber' => $audio->getTrackNumber(),
'description' => $audio->getDescription(),
'comment' => $audio->getComment(),
'creationDate' => $audio->getCreationDate(),
'composer' => $audio->getComposer(),
'discNumber' => $audio->getDiscNumber(),
'isCompilation' => $audio->isCompilation(),
'authors' => $this->parseAuthors($authors),
'title' => $audio->getAlbum() ?? $audio->getTitle(),
'subtitle' => $this->parseTag($audio->getTag('subtitle'), false),
'publisher' => $audio->getTag('encoded_by'),
'publish_year' => $audio->getYear(),
'narrators' => $this->parseAuthors($narrators),
'description' => $this->parseTag($audio->getDescription(), false),
'lyrics' => $this->parseTag($audio->getLyrics()),
'comment' => $this->parseTag($audio->getComment()),
'synopsis' => $this->parseTag($audio->getTag('description_long')),
'genres' => $genres,
'series' => $this->parseTag($series),
'series_sequence' => $series_part ? intval($series_part) : null,
'language' => $this->parseTag($language),
'isbn' => $this->parseTag($audio->getTag('isbn')),
'asin' => $this->parseTag($audio->getTag('asin') ?? $audio->getTag('audible_asin')),
'chapters' => $chapters,
'date' => $audio->getCreationDate() ?? $audio->getTag('origyear'),
'is_compilation' => $audio->isCompilation(),
'encoding' => $audio->getEncoding(),
'lyrics' => $audio->getLyrics(),
'track_number' => $audio->getTrackNumber(),
'disc_number' => $audio->getDiscNumber(),
'copyright' => $this->parseTag($audio->getTag('copyright')),
'stik' => $audio->getStik(),
'duration' => $audio->getDuration(),
'audio_title' => $audio->getTitle(),
'audio_artist' => $audio->getArtist(),
'audio_album' => $audio->getAlbum(),
'audio_album_artist' => $audio->getAlbumArtist(),
];

return $this;
Expand All @@ -64,60 +91,57 @@ public function getAudio(): array

public function toEbook(): Ebook
{
$audio = $this->ebook->getAudio();

$author = new BookAuthor($audio->getArtist());

$date = null;
if ($audio->getCreationDate()) {
$date = new DateTime($audio->getCreationDate());
} elseif ($audio->getYear()) {
$date = new DateTime("{$audio->getYear()}-01-01");
$authors = [];
foreach ($this->audio['authors'] as $author) {
$authors[] = new BookAuthor($author, 'author');
}

$description = trim($audio->getDescription() ?? '');

$this->ebook->setTitle($audio->getTitle());
$this->ebook->setAuthors([$author]);
$this->ebook->setPublisher($audio->getAlbumArtist());
$this->ebook->setDescription($description);

$tags = $audio->getGenre();
if (str_contains($tags, ';') || str_contains($tags, ',')) {
$tags = preg_split('/[;,]/', $tags);
} else {
$tags = [$tags];
$identifiers = [];
if ($this->audio['isbn']) {
$identifiers[] = ['type' => 'isbn', 'value' => $this->audio['isbn']];
}

$tags = array_map('trim', $tags);
$tags = array_map('ucfirst', $tags);
$this->ebook->setTags($tags);
$date = $this->audio['date'] ? new DateTime(str_replace('/', '-', $this->audio['date'])) : null;

$this->ebook->setAuthors($authors);
$this->ebook->setTitle($this->audio['title']);
$this->ebook->setPublisher($this->audio['publisher']);
$this->ebook->setDescription($this->audio['description']);
$this->ebook->setDescriptionHtml("<p>{$this->audio['description']}</p>");
$this->ebook->setTags($this->audio['genres']);
$this->ebook->setSeries($this->audio['series']);
$this->ebook->setVolume($this->audio['series_sequence']);
$this->ebook->setLanguage($this->audio['language']);
if ($this->audio['isbn']) {
$this->ebook->setIdentifier(new BookIdentifier($this->audio['isbn'], 'isbn', false));
}
if ($this->audio['asin']) {
$this->ebook->setIdentifier(new BookIdentifier($this->audio['asin'], 'asin', false));
}
if ($date instanceof DateTime) {
$this->ebook->setPublishDate($date);
}
$this->ebook->setCopyright($this->audio['copyright']);

$this->ebook->setSeries($audio->getAlbum());
$this->ebook->setVolume($audio->getTrackNumber());
$this->ebook->setPublishDate($date);
$this->ebook->setCopyright($audio->getEncodingBy());
$this->ebook->setLanguage($audio->getLanguage());
$this->ebook->setExtras([
'title' => $audio->getTitle(),
'artist' => $audio->getArtist(),
'albumArtist' => $audio->getAlbumArtist(),
'album' => $audio->getAlbum(),
'genre' => $audio->getGenre(),
'year' => $audio->getYear(),
'trackNumber' => $audio->getTrackNumber(),
'description' => $audio->getDescription(),
'comment' => $audio->getComment(),
'creationDate' => $audio->getCreationDate(),
'composer' => $audio->getComposer(),
'discNumber' => $audio->getDiscNumber(),
'isCompilation' => $audio->isCompilation(),
'encoding' => $audio->getEncoding(),
'podcastDescription' => $audio->getPodcastDescription(),
'language' => $audio->getLanguage(),
'lyrics' => $audio->getLyrics(),
'stik' => $audio->getStik(),
'duration' => $audio->getDuration(),
'subtitle' => $this->audio['subtitle'],
'publish_year' => $this->audio['publish_year'],
'authors' => $this->audio['authors'],
'narrators' => $this->audio['narrators'],
'lyrics' => $this->audio['lyrics'],
'comment' => $this->audio['comment'],
'synopsis' => $this->audio['synopsis'],
'chapters' => $this->audio['chapters'],
'is_compilation' => $this->audio['is_compilation'],
'encoding' => $this->audio['encoding'],
'track_number' => $this->audio['track_number'],
'disc_number' => $this->audio['disc_number'],
'stik' => $this->audio['stik'],
'duration' => $this->audio['duration'],
'audio_title' => $this->audio['audio_title'],
'audio_artist' => $this->audio['audio_artist'],
'audio_album' => $this->audio['audio_album'],
'audio_album_artist' => $this->audio['audio_album_artist'],
]);

$this->ebook->setHasParser(true);
Expand Down Expand Up @@ -158,4 +182,72 @@ public function __toString(): string
{
return $this->toJson();
}

/**
* @return string[]
*/
private function parseGenres(?string $genres): array
{
if (! $genres) {
return [];
}

$items = [];
if (str_contains($genres, ';')) {
$items = explode(';', $genres);
} elseif (str_contains($genres, '/')) {
$items = explode('/', $genres);
} elseif (str_contains($genres, '//')) {
$items = explode('//', $genres);
} elseif (str_contains($genres, ',')) {
$items = explode(',', $genres);
} else {
$items = [$genres];
}

$items = array_map('trim', $items);
$items = array_map('ucfirst', $items);

return $items;
}

/**
* @return string[]
*/
private function parseAuthors(?string $authors): array
{
if (! $authors) {
return [];
}

$items = [];
if (str_contains($authors, ',')) {
$items = explode(',', $authors);
} elseif (str_contains($authors, ';')) {
$items = explode(';', $authors);
} elseif (str_contains($authors, '&')) {
$items = explode('&', $authors);
} elseif (str_contains($authors, 'and')) {
$items = explode('and', $authors);
} else {
$items = [$authors];
}

return array_map('trim', $items);
}

private function parseTag(?string $tag, bool $flat = true): ?string
{
if (! $tag) {
return null;
}

$tag = html_entity_decode($tag);
if ($flat) {
$tag = preg_replace('/\s+/', ' ', $tag);
}
$tag = trim($tag);

return $tag;
}
}
Loading

0 comments on commit 172fd00

Please sign in to comment.