pass
+@BuildEbook.register('cover_clean')
+@task(ignore_result=True)
+class BuildCoverClean(BuildCover):
+ @classmethod
+ def transform(cls, wldoc, fieldfile):
+ from librarian.cover import WLCover
+ return WLCover(wldoc.book_info, width=240).output_file()
+
+
@BuildEbook.register('cover_thumb')
@task(ignore_result=True)
class BuildCoverThumb(BuildCover):
--- /dev/null
+# Generated by Django 2.2.25 on 2022-03-10 11:51
+
+import catalogue.fields
+import catalogue.models.book
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0033_auto_20220128_1409'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='book',
+ name='cover_clean',
+ field=catalogue.fields.EbookField('cover_clean', blank=True, max_length=255, null=True, upload_to=catalogue.models.book.UploadToPath('book/cover_clean/%s.jpg'), verbose_name='clean cover'),
+ ),
+ migrations.AddField(
+ model_name='book',
+ name='cover_clean_etag',
+ field=models.CharField(db_index=True, default='', editable=False, max_length=255),
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.25 on 2022-03-10 12:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0034_auto_20220310_1251'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='bookmedia',
+ name='duration',
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.25 on 2022-03-10 14:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0035_bookmedia_duration'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='book',
+ name='toc',
+ field=models.TextField(blank=True, verbose_name='toc'),
+ ),
+ ]
import os.path
import re
from urllib.request import urlretrieve
+from django.apps import apps
from django.conf import settings
from django.db import connection, models, transaction
import django.dispatch
from django.utils.translation import ugettext_lazy as _, get_language
from django.utils.deconstruct import deconstructible
from fnpdjango.storage import BofhFileSystemStorage
-
+from lxml import html
from librarian.cover import WLCover
from librarian.html import transform_abstrakt
from newtagging import managers
_cover_upload_to = UploadToPath('book/cover/%s.jpg')
+_cover_clean_upload_to = UploadToPath('book/cover_clean/%s.jpg')
_cover_thumb_upload_to = UploadToPath('book/cover_thumb/%s.jpg')
_cover_api_thumb_upload_to = UploadToPath('book/cover_api_thumb/%s.jpg')
_simple_cover_upload_to = UploadToPath('book/cover_simple/%s.jpg')
language = models.CharField(_('language code'), max_length=3, db_index=True, default=app_settings.DEFAULT_LANGUAGE)
description = models.TextField(_('description'), blank=True)
abstract = models.TextField(_('abstract'), blank=True)
+ toc = models.TextField(_('toc'), blank=True)
created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
changed_at = models.DateTimeField(_('change date'), auto_now=True, db_index=True)
parent_number = models.IntegerField(_('parent number'), default=0)
storage=bofh_storage, max_length=255)
cover_etag = models.CharField(max_length=255, editable=False, default='', db_index=True)
# Cleaner version of cover for thumbs
+ cover_clean = EbookField(
+ 'cover_clean', _('clean cover'),
+ null=True, blank=True,
+ upload_to=_cover_clean_upload_to,
+ max_length=255
+ )
+ cover_clean_etag = models.CharField(max_length=255, editable=False, default='', db_index=True)
cover_thumb = EbookField(
'cover_thumb', _('cover thumbnail'),
null=True, blank=True,
return sibling.get_first_text()
return self.parent.get_next_text()
+ def get_child_audiobook(self):
+ BookMedia = apps.get_model('catalogue', 'BookMedia')
+ if not BookMedia.objects.filter(book__ancestor=self).exists():
+ return None
+ for child in self.children.all():
+ if child.has_mp3_file():
+ return child
+ child_sub = child.get_child_audiobook()
+ if child_sub is not None:
+ return child_sub
+
def get_siblings(self):
if not self.parent:
return []
audiobooks = []
projects = set()
+ total_duration = 0
for mp3 in self.media.filter(type='mp3').iterator():
# ogg files are always from the same project
meta = mp3.get_extra_info_json()
project = 'CzytamySłuchając'
projects.add((project, meta.get('funded_by', '')))
+ total_duration += mp3.duration or 0
media = {'mp3': mp3}
audiobooks.append(media)
projects = sorted(projects)
- return audiobooks, projects
+ total_duration = '%d:%02d' % (
+ total_duration // 60,
+ total_duration % 60
+ )
+ return audiobooks, projects, total_duration
def wldocument(self, parse_dublincore=True, inherit=True):
from catalogue.import_utils import ORMDocProvider
else:
self.abstract = ''
+ def load_toc(self):
+ self.toc = ''
+ if self.html_file:
+ parser = html.HTMLParser(encoding='utf-8')
+ tree = html.parse(self.html_file.path, parser=parser)
+ toc = tree.find('//div[@id="toc"]/ol')
+ if toc is None or not len(toc):
+ return
+ html_link = reverse('book_text', args=[self.slug])
+ for a in toc.findall('.//a'):
+ a.attrib['href'] = html_link + a.attrib['href']
+ self.toc = html.tostring(toc, encoding='unicode')
+ # div#toc
+
@classmethod
def from_xml_file(cls, xml_file, **kwargs):
from django.core.files import File
book.common_slug = book.slug
book.extra_info = json.dumps(book_info.to_dict())
book.load_abstract()
+ book.load_toc()
book.save()
meta_tags = Tag.tags_from_info(book_info)
# Build cover.
if 'cover' not in dont_build:
book.cover.build_delay()
+ book.cover_clean.build_delay()
book.cover_thumb.build_delay()
book.cover_api_thumb.build_delay()
book.simple_cover.build_delay()
if not self.cover_info(inherit=False):
if 'cover' not in app_settings.DONT_BUILD:
self.cover.build_delay()
+ self.cover_clean.build_delay()
self.cover_thumb.build_delay()
self.cover_api_thumb.build_delay()
self.simple_cover.build_delay()
+ self.cover_ebookpoint.build_delay()
for format_ in constants.EBOOK_FORMATS_WITH_COVERS:
if format_ not in app_settings.DONT_BUILD:
getattr(self, '%s_file' % format_).build_delay()
from django.db import models
from django.utils.translation import ugettext_lazy as _
from slugify import slugify
-from mutagen import MutagenError
+import mutagen
+from mutagen import id3
from catalogue.fields import OverwriteStorage
part_name = models.CharField(_('part name'), default='', blank=True, max_length=512)
index = models.IntegerField(_('index'), default=0)
file = models.FileField(_('file'), max_length=600, upload_to=_file_upload_to, storage=OverwriteStorage())
+ duration = models.IntegerField(null=True, blank=True)
uploaded_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False, db_index=True)
project_description = models.CharField(max_length=2048, blank=True)
project_icon = models.CharField(max_length=2048, blank=True)
extra_info.update(self.read_meta())
self.extra_info = json.dumps(extra_info)
self.source_sha1 = self.read_source_sha1(self.file.path, self.type)
+ self.duration = self.read_duration()
return super(BookMedia, self).save(*args, **kwargs)
+ def read_duration(self):
+ try:
+ return mutagen.File(self.file.path).info.length
+ except:
+ return None
+
def read_meta(self):
"""
Reads some metadata from the audiobook.
"""
- import mutagen
- from mutagen import id3
-
artist_name = director_name = project = funded_by = license = ''
if self.type == 'mp3':
try:
funded_by = ", ".join([
t.data.decode('utf-8') for t in audio.getall('PRIV')
if t.owner == 'wolnelektury.pl?funded_by'])
- except MutagenError:
+ except mutagen.MutagenError:
pass
elif self.type == 'ogg':
try:
license = ', '.join(audio.get('license', []))
project = ", ".join(audio.get('project', []))
funded_by = ", ".join(audio.get('funded_by', []))
- except (MutagenError, AttributeError):
+ except (mutagen.MutagenError, AttributeError):
pass
else:
return {}
"""
Reads source file SHA1 from audiobok metadata.
"""
- import mutagen
- from mutagen import id3
-
if filetype == 'mp3':
try:
audio = id3.ID3(filepath)
return [t.data.decode('utf-8') for t in audio.getall('PRIV')
if t.owner == 'wolnelektury.pl?flac_sha1'][0]
- except (MutagenError, IndexError):
+ except (mutagen.MutagenError, IndexError):
return None
elif filetype == 'ogg':
try:
audio = mutagen.File(filepath)
return audio.get('flac_sha1', [None])[0]
- except (MutagenError, AttributeError, IndexError):
+ except (mutagen.MutagenError, AttributeError, IndexError):
return None
else:
return None
--- /dev/null
+from django.db import models
+
+
+class Edition(models.Model):
+ book = models.ForeignKey('Book', models.CASCADE)
+ identifier = models.CharField(max_length=32)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+
+
<section class="l-section">
<aside class="l-aside">
<figure>
- <img src="{% if book.cover_thumb %}{{ book.cover_thumb.url }}{% endif %}" alt="{{ book.pretty_title }}" width="238">
+ <img src="{% if book.cover_clean %}{{ book.cover_clean.url }}{% endif %}" alt="{{ book.pretty_title }}" width="240">
</figure>
<ul class="l-aside__info">
<li><span>Epoka:</span> {% for tag in book.epochs %}<a href="{{ tag.get_absolute_url }}">{{ tag.name|lower }}</a> {% endfor %}</li>
{% if book.has_mp3_file %}
{% include 'catalogue/snippets/2022_jplayer.html' %}
+ {% else %}
+ {% with ch=book.get_child_audiobook %}
+ {% if ch %}
+ {% include 'catalogue/snippets/2022_jplayer_link.html' with book=ch %}
+ {% endif %}
+ {% endwith %}
+
{% endif %}
<h3>Opis</h3>
{{ book.abstract|safe }}
- <h4>Spis treści:</h4>
- <ul>
- <li>Dziady. Poema</li>
- <li>Przedmowa</li>
- <li>Upiór</li>
- <li>Dziady, część II</li>
- <li>Dziady, część IV</li>
- <li>Dziady, część III</li>
- <li>Dziady. Widowisko, część I</li>
- </ul>
+ {% if book.toc %}
+ <h4>Spis treści:</h4>
+ {{ book.toc|safe }}
+ {% endif %}
</div>
<button class="l-article__read-more" aria-label="Kliknij aby rozwinąć" data-label="Czytaj więcej" data-action="Zwiń tekst">Czytaj więcej</button>
</article>
- </div>
- </section>
- <section class="l-section">
- <div class="c-support">
- <h2>Ta książka jest dostępna dla tysięcy dzieciaków dzięki darowiznom od osób takich jak Ty!</h2>
- <figure>
- <img src="{% static '2022/images/img-1.jpg' %}" alt="Dorzuć się!">
- <a href="/towarzystwo/">Dorzuć się!</a>
- </figure>
+ <div class="c-support">
+ <h2>Ta książka jest dostępna dla tysięcy dzieciaków dzięki darowiznom od osób takich jak Ty!</h2>
+ <figure>
+ <img src="{% static '2022/images/img-1.jpg' %}" alt="Dorzuć się!">
+ <a href="/towarzystwo/">Dorzuć się!</a>
+ </figure>
+ </div>
</div>
</section>
</div>
</div>
- <div class="row">
- <div class="l-author__quotes">
- <div class="l-author__quotes__slider">
- {% choose_cites book 3 as cites %}
- {% for fragment in cites %}
- <div class="l-author__quotes__slider__item">
- <em>
- {{ fragment.short_text|safe }}
- </em>
- <p>{{ fragment.book.pretty_title }}</p>
- </div>
- {% endfor %}
+ {% choose_cites book 3 as cites %}
+ {% if cites %}
+ <div class="row">
+ <div class="l-author__quotes">
+ <div class="l-author__quotes__slider">
+ {% for fragment in cites %}
+ <div class="l-author__quotes__slider__item">
+ <em>
+ {{ fragment.short_text|safe }}
+ </em>
+ <p>{{ fragment.book.pretty_title }}</p>
+ </div>
+ {% endfor %}
+ </div>
</div>
</div>
- </div>
+ {% endif %}
</div>
</section>
{% endfor %}
</div>
<script src="{% static '2022/scripts/vendor.js' %}"></script>
- <script src="{% static '2022/scripts/main.js' %}"></script>
+ <script src="{% static '2021/scripts/main.js' %}"></script>
{% javascript '2022' %}
{% javascript '2022_player' %}
{% load i18n catalogue_tags %}
-<div class="c-media__player" id="jp_container_{{ book.pk }}">
- <div class="jp-jplayer" data-player="jp_container_{{ book.pk }}"
- data-supplied="oga,mp3"></div>
+{% with audiobooks=book.get_audiobooks %}
+ <div class="c-media__player" id="jp_container_{{ book.pk }}">
+ <div class="jp-jplayer" data-player="jp_container_{{ book.pk }}"
+ data-supplied="oga,mp3"></div>
- <div class="c-player">
- <button class="c-player__btn jp-play">
- <i class="icon icon-play"></i>
- </button>
- <button class="c-player__btn jp-pause">
- <i class="icon icon-play"></i>
- </button>
- <div class="c-player__timeline">
- <div class="c-player__title">
- </div>
- <div class="c-player__info"></div>
- <span>
- <span class="jp-seek-bar">
- <span class="jp-play-bar"></span>
- </span>
- </span>
- <time class="c-player__length">
- <span class="jp-current-time"></span>
+ <div class="c-player">
+ <button class="c-player__btn jp-play">
+ <i class="icon icon-play"></i>
+ </button>
+ <button class="c-player__btn jp-pause">
+ <i class="icon icon-pause"></i>
+ </button>
+ <div class="c-player__timeline">
+ <div class="c-player__title">
+ </div>
+ <div class="c-player__info"></div>
<span>
- <span class="jp-duration"></span>
+ <span class="jp-seek-bar">
+ <span class="jp-play-bar"></span>
+ </span>
</span>
- </time>
+ <time class="c-player__length">
+ <span class="jp-current-time"></span>
+ <span>
+ <span class="jp-duration"></span>
+ {% if audiobooks.0|length > 1 %}
+ / {{ audiobooks.2 }}
+ {% endif %}
+ </span>
+ </time>
+ </div>
+ </div>
+ <div class="c-media__caption">
+ <div class="license"></div>
+ <div class="project-logo"></div>
+ <div class="content"></div>
</div>
- </div>
- <div class="c-media__caption">
- <div class="license"></div>
- <div class="project-logo"></div>
- <div class="content"></div>
- </div>
- <ul class="jp-playlist" style="display: none;">
- {% for i in book.get_audiobooks.0 %}
- <li
- data-mp3='{{ i.mp3.file.url }}'
- data-ogg='{{ i.ogg.file.url }}'
- data-media-id="{{ i.mp3.id }}"
- >
- {% with extra_info=i.mp3.get_extra_info_json %}
- <span class="title">
- {{ i.mp3.part_name }}
- </span>
- <span class="attribution">
- {% trans "Artist:" %} <span class='artist'>{{ extra_info.artist_name }}</span>,
- {% trans "director:" %} <span class='director'>{{ extra_info.director_name }}</span>
- </span>
- <span class="license">
- {% if extra_info.license %}{% license_icon extra_info.license %}{% endif %}
- </span>
- <span class="project-icon">
- {% if i.mp3.project_icon %}<img class="project-icon" src="{{ i.mp3.project_icon }}">{% endif %}
- </span>
- <span class="project-description">
- {% if i.mp3.project_description %}{{ i.mp3.project_description }}{% else %}
- {% with fb=extra_info.funded_by %}
- {% if fb %}Dofinansowano ze środków: {{ fb }}.{% endif %}
- {% endwith %}
- {% endif %}
- </span>
- {% endwith %}
- </li>
- {% endfor %}
- </ul>
+ <ul class="jp-playlist" style="display: none;">
+ {% for i in audiobooks.0 %}
+ <li
+ data-mp3='{{ i.mp3.file.url }}'
+ data-ogg='{{ i.ogg.file.url }}'
+ data-media-id="{{ i.mp3.id }}"
+ >
+ {% with extra_info=i.mp3.get_extra_info_json %}
+ <span class="title">
+ {{ i.mp3.part_name }}
+ </span>
+ <span class="attribution">
+ {% trans "Artist:" %} <span class='artist'>{{ extra_info.artist_name }}</span>,
+ {% trans "director:" %} <span class='director'>{{ extra_info.director_name }}</span>
+ </span>
+ <span class="license">
+ {% if extra_info.license %}{% license_icon extra_info.license %}{% endif %}
+ </span>
+ <span class="project-icon">
+ {% if i.mp3.project_icon %}<img class="project-icon" src="{{ i.mp3.project_icon }}">{% endif %}
+ </span>
+ <span class="project-description">
+ {% if i.mp3.project_description %}{{ i.mp3.project_description }}{% else %}
+ {% with fb=extra_info.funded_by %}
+ {% if fb %}Dofinansowano ze środków: {{ fb }}.{% endif %}
+ {% endwith %}
+ {% endif %}
+ </span>
+ {% endwith %}
+ </li>
+ {% endfor %}
+ </ul>
-</div>
+ </div>
+{% endwith %}
--- /dev/null
+{% load i18n catalogue_tags %}
+
+<div class="c-media__player" id="jp_container_{{ book.pk }}">
+ <div class="jp-jplayer"></div>
+
+ <div class="c-player">
+ <a class="c-player__btn" href="{{ book.get_absolute_url }}">
+ <i class="icon icon-next">⏭</i>
+ </a>
+ <div class="c-player__timeline">
+ <div class="c-player__title">
+ {{ book.title }}
+ </div>
+ <span>
+ <span class="jp-seek-bar">
+ <span class="jp-play-bar"></span>
+ </span>
+ </span>
+ </div>
+ </div>
+ </div>
height: 10px;
width: 100%;
display: block;
- background: #FFFFFF;
+ background: #F2F2F2;
border-radius: 5px;
position: relative;
overflow: hidden;
- span {
- width: 40%;
+ span.jp-seek-bar {
+ height: 100%;
+ position: absolute;
+ border-radius: 5px;
+ background-color: #FFFFFF;
+ top: 0; left: 0;
+ }
+
+ span.jp-play-bar {
height: 100%;
position: absolute;
border-radius: 5px;
}
}
+
+.c-player__title {
+ top: -27px;
+ left: 0;
+ position: absolute;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 12px;
+ line-height: 140%;
+ letter-spacing: 0.05em;
+ color: #083F4D;
+}
+
+
.c-player__info {
top: -27px;
right: 0;
display: block;
position: relative;
- span {
+ > span {
position: absolute;
padding-top: 10px;
font-style: normal;
font-weight: bolder;
}
- ul {
+ ol {
padding: 0;
list-style: none;
margin-top: 0.25rem;
--- /dev/null
+(function($){
+ $(function() {
+
+
+ if (Modernizr.localstorage) {
+ try {
+ audiobooks = JSON.parse(localStorage["audiobook-history"]);
+ } catch {
+ audiobooks = {};
+ }
+
+ latest = [];
+ Object.keys(audiobooks).forEach(function(slug) {
+ [ts, media_id, time] = audiobooks[slug];
+ latest.push([ts, slug]);
+ });
+ latest.sort().reverse().forEach(function(item) {
+ [ts, slug] = item;
+ $newitem = $('<div style="display:inline-block;"></div>'); // remove from history
+ $("#last-audiobooks").append($newitem);
+ (function($box) {
+ $.get(
+ "/katalog/lektura/" + slug + "/mini_box.html",
+ function(data) {
+ console.log(data);
+ $box.html(data);
+ $("#personal-history").slideDown("slow");
+ }).fail(function() {
+ $box.remove();
+ });
+ })($newitem);
+ });
+ }
+ });
+})(jQuery);