end support for mobi
parent
e4a514566f
commit
88669d7a35
|
@ -64,7 +64,6 @@ class BooXtream(object):
|
|||
# fake it, so you can test other functions without hitting booxtream
|
||||
boox = Boox.objects.create(
|
||||
download_link_epub='https://github.com/eshellman/42_ebook/blob/master/download/42.epub?raw=true&extra=download.booxtream.com/',
|
||||
download_link_mobi='https://github.com/eshellman/42_ebook/blob/master/download/42.mobi?raw=true',
|
||||
referenceid= kwargs.get('referenceid', '42'),
|
||||
downloads_remaining=kwargs.get('downloadlimit', 10),
|
||||
expirydays=kwargs.get('expirydays', 30),
|
||||
|
@ -81,12 +80,8 @@ class BooXtream(object):
|
|||
download_link_epub = doc.find('.//DownloadLink[@type="epub"]')
|
||||
if download_link_epub is not None:
|
||||
download_link_epub = download_link_epub.text
|
||||
download_link_mobi = doc.find('.//DownloadLink[@type="mobi"]')
|
||||
if download_link_mobi is not None:
|
||||
download_link_mobi = download_link_mobi.text
|
||||
boox = Boox.objects.create(
|
||||
download_link_epub=download_link_epub,
|
||||
download_link_mobi=download_link_mobi,
|
||||
referenceid=kwargs.get('referenceid'),
|
||||
downloads_remaining=kwargs.get('downloadlimit'),
|
||||
expirydays=kwargs.get('expirydays'),
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.29 on 2022-07-28 06:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('booxtream', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='boox',
|
||||
name='download_link_mobi',
|
||||
),
|
||||
]
|
|
@ -7,7 +7,6 @@ class Boox(models.Model):
|
|||
keeps a record of a file that's been watermarked
|
||||
"""
|
||||
download_link_epub = models.URLField(null=True)
|
||||
download_link_mobi = models.URLField(null=True)
|
||||
referenceid = models.CharField(max_length=32)
|
||||
downloads_remaining = models.PositiveSmallIntegerField(default=0)
|
||||
expirydays = models.PositiveSmallIntegerField()
|
||||
|
@ -20,7 +19,5 @@ class Boox(models.Model):
|
|||
def download_link(self, format):
|
||||
if format == 'epub':
|
||||
return self.download_link_epub
|
||||
elif format == 'mobi':
|
||||
return self.download_link_mobi
|
||||
return None
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ class EbookFileAdmin(ModelAdmin):
|
|||
list_display = ('created', 'format', 'ebook_link', 'asking')
|
||||
date_hierarchy = 'created'
|
||||
ordering = ('edition__work',)
|
||||
fields = ('file', 'format', 'edition', 'edition_link', 'ebook', 'ebook_link', 'source', 'mobied')
|
||||
fields = ('file', 'format', 'edition', 'edition_link', 'ebook', 'ebook_link', 'source')
|
||||
readonly_fields = ('file', 'edition_link', 'ebook_link', 'source')
|
||||
def edition_link(self, obj):
|
||||
if obj.edition:
|
||||
|
|
|
@ -107,7 +107,7 @@ class FormatFacetGroup(FacetGroup):
|
|||
def __init__(self):
|
||||
super(FacetGroup,self).__init__()
|
||||
self.title = 'Format'
|
||||
self.facets = ['pdf', 'epub', 'mobi']
|
||||
self.facets = ['pdf', 'epub']
|
||||
self.label = '{} is ...'.format(self.title)
|
||||
|
||||
def get_facet_class(self, facet_name):
|
||||
|
|
|
@ -283,7 +283,7 @@ def load_doab_edition(title, doab_id, url, format, rights,
|
|||
work.selected_edition = edition
|
||||
work.save()
|
||||
|
||||
if format in ('pdf', 'epub', 'mobi', 'html', 'online') and rights:
|
||||
if format in ('pdf', 'epub', 'html', 'online') and rights:
|
||||
ebook = models.Ebook()
|
||||
ebook.format = format
|
||||
ebook.provider = provider
|
||||
|
|
|
@ -618,7 +618,7 @@ def harvest_frontiersin(ebook):
|
|||
logger.warning('couldn\'t get any dl_url for %s', ebook.url)
|
||||
return harvested, num
|
||||
|
||||
SPRINGERDL = re.compile(r'(EPUB|PDF|MOBI)')
|
||||
SPRINGERDL = re.compile(r'(EPUB|PDF)')
|
||||
|
||||
def harvest_springerlink(ebook):
|
||||
def selector(doc):
|
||||
|
@ -910,7 +910,7 @@ def harvest_doi_coaccess(ebook):
|
|||
|
||||
# a new ebook
|
||||
format = loader.type_for_url(url)
|
||||
if format in ('pdf', 'epub', 'mobi', 'html', 'online'):
|
||||
if format in ('pdf', 'epub', 'html', 'online'):
|
||||
new_ebook = models.Ebook()
|
||||
new_ebook.format = format
|
||||
new_ebook.url = url
|
||||
|
@ -1000,7 +1000,7 @@ def harvest_fulcrum(ebook):
|
|||
|
||||
def harvest_ubiquity(ebook):
|
||||
def selector(doc):
|
||||
return doc.find_all('a', attrs={'data-category': re.compile('(epub|mobi|pdf) download')})
|
||||
return doc.find_all('a', attrs={'data-category': re.compile('(epub|pdf) download')})
|
||||
return harvest_multiple_generic(ebook, selector)
|
||||
|
||||
def harvest_orkana(ebook):
|
||||
|
|
|
@ -11,7 +11,7 @@ class PressbooksScraper(BaseScraper):
|
|||
can_scrape_strings = ['pressbooks']
|
||||
|
||||
def get_downloads(self):
|
||||
for dl_type in ['epub', 'mobi', 'pdf']:
|
||||
for dl_type in ['epub', 'pdf']:
|
||||
download_el = self.doc.select_one('.{}'.format(dl_type))
|
||||
value = None
|
||||
if download_el and download_el.find_parent():
|
||||
|
|
|
@ -199,8 +199,7 @@ class BaseScraper(object):
|
|||
'''return a dict of edition keys and ISBNs'''
|
||||
isbns = {}
|
||||
isbn_cleaner = identifier_cleaner('isbn', quiet=True)
|
||||
label_map = {'epub': 'EPUB', 'mobi': 'Mobi',
|
||||
'paper': 'Paperback', 'pdf':'PDF', 'hard':'Hardback'}
|
||||
label_map = {'epub': 'EPUB', 'paper': 'Paperback', 'pdf':'PDF', 'hard':'Hardback'}
|
||||
for key in label_map.keys():
|
||||
isbn_key = 'isbn_{}'.format(key)
|
||||
value = self.check_metas(['citation_isbn'], type=label_map[key])
|
||||
|
@ -332,7 +331,7 @@ class BaseScraper(object):
|
|||
self.set('covers', [{'image_url': image_url}])
|
||||
|
||||
def get_downloads(self):
|
||||
for dl_type in ['epub', 'mobi', 'pdf']:
|
||||
for dl_type in ['epub', 'pdf']:
|
||||
dl_meta = 'citation_{}_url'.format(dl_type)
|
||||
value = self.check_metas([dl_meta])
|
||||
if value:
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.core.management.base import BaseCommand
|
|||
from regluit.core.loaders.harvest import archive_dl, RateLimiter, DONT_HARVEST
|
||||
from regluit.core.models import Ebook
|
||||
from regluit.core.parameters import GOOD_PROVIDERS
|
||||
DOWNLOADABLE = ['pdf', 'epub', 'mobi']
|
||||
DOWNLOADABLE = ['pdf', 'epub']
|
||||
|
||||
DONT_CHECK = list(GOOD_PROVIDERS) + DONT_HARVEST
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from regluit.core.models import Work, EbookFile
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "generate mobi ebooks where needed and possible."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('max', nargs='?', type=int, default=1, help="maximum mobis to make")
|
||||
parser.add_argument('--reset', '-r', action='store_true', help="reset failed mobi conversions")
|
||||
|
||||
|
||||
def handle(self, max=None, **options):
|
||||
maxbad = 10
|
||||
if options['reset']:
|
||||
bads = EbookFile.objects.filter(mobied__lt=0)
|
||||
for bad in bads:
|
||||
bad.mobied = 0
|
||||
bad.save()
|
||||
|
||||
epubs = Work.objects.filter(editions__ebooks__format='epub').distinct().order_by('-id')
|
||||
|
||||
i = 0
|
||||
n_bad = 0
|
||||
for work in epubs:
|
||||
if not work.ebooks().filter(format="mobi").exists():
|
||||
for ebook in work.ebooks().filter(format="epub"):
|
||||
ebf = ebook.get_archive_ebf()
|
||||
if ebf and ebf.mobied >= 0:
|
||||
try:
|
||||
self.stdout.write(u'making mobi for {}'.format(work.title))
|
||||
if ebf.make_mobi():
|
||||
self.stdout.write('made mobi')
|
||||
i += 1
|
||||
break
|
||||
else:
|
||||
self.stdout.write('failed to make mobi')
|
||||
n_bad += 1
|
||||
|
||||
except:
|
||||
self.stdout.write('failed to make mobi')
|
||||
n_bad += 1
|
||||
if i >= max or n_bad >= maxbad:
|
||||
break
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.29 on 2022-07-28 06:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0024_auto_20210503_1717'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='ebookfile',
|
||||
name='mobied',
|
||||
),
|
||||
]
|
30
core/mobi.py
30
core/mobi.py
|
@ -1,30 +0,0 @@
|
|||
import requests
|
||||
from django.conf import settings
|
||||
|
||||
mobigen_url = settings.MOBIGEN_URL
|
||||
mobigen_user_id = settings.MOBIGEN_USER_ID
|
||||
mobigen_password = settings.MOBIGEN_PASSWORD
|
||||
|
||||
|
||||
|
||||
def convert_to_mobi(input_url, input_format="application/epub+zip"):
|
||||
|
||||
"""
|
||||
return a string with the output of mobigen computation
|
||||
|
||||
"""
|
||||
|
||||
# using verify=False since at the moment, using a self-signed SSL cert.
|
||||
|
||||
payload = requests.get(input_url).content
|
||||
|
||||
headers = {'Content-Type': input_format}
|
||||
r = requests.post(mobigen_url, auth=(mobigen_user_id, mobigen_password),
|
||||
data=payload, headers=headers)
|
||||
|
||||
# if HTTP reponse code is ok, the output is the mobi file; else error message
|
||||
if r.status_code == 200:
|
||||
return r.content
|
||||
else:
|
||||
raise Exception("{0}: {1}".format(r.status_code, r.content))
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
"""
|
||||
Utilities for calling mobigen for management. do not use in application
|
||||
|
||||
"""
|
||||
|
||||
from itertools import islice
|
||||
import uuid
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.files.base import ContentFile, File
|
||||
|
||||
from regluit.core.models import (Campaign, Ebook)
|
||||
from regluit.core import parameters
|
||||
from regluit.core.mobi import convert_to_mobi
|
||||
|
||||
|
||||
# compute whether we can apply mobigen to a given edition to produce a mobi file
|
||||
# need to have an ebook in epub or pdf format
|
||||
# possible return values: already has a mobi file / can generate a mobi file / not possible
|
||||
|
||||
def edition_mobi_status(edition):
|
||||
"""
|
||||
for a given edition, return:
|
||||
* 1 if there is already a mobi ebook
|
||||
* 0 if there is none but we have an epub or html to convert from
|
||||
* -1 for no epub/html to convert from
|
||||
"""
|
||||
formats = set([ebook.format for ebook in edition.work.ebooks()])
|
||||
if 'mobi' in formats:
|
||||
return 1
|
||||
elif ('epub' in formats) or ('html' in formats):
|
||||
return 0
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
def write_file_to_storage(file_object, content_type, path):
|
||||
"""
|
||||
write file_object to the default_storage at given path
|
||||
"""
|
||||
file_s3 = ContentFile(file_object)
|
||||
file_s3.content_type = content_type
|
||||
|
||||
default_storage.save(path, file_s3)
|
||||
return file_s3
|
||||
|
||||
|
||||
# generator for editions to add mobi to
|
||||
# campaigns that can have mobi files but don't yet.
|
||||
|
||||
def editions_to_convert():
|
||||
for campaign in Campaign.objects.filter(edition__ebooks__isnull=False).distinct():
|
||||
# need to make sure campaign type is not B2U because kindlegen is for books we give awy free of charge
|
||||
if (edition_mobi_status(campaign.edition) == 0) and (campaign.type != parameters.BUY2UNGLUE): # possible to generate mobi
|
||||
yield campaign.edition
|
||||
|
||||
|
||||
def generate_mobi_ebook_for_edition(edition):
|
||||
|
||||
# pull out the sister edition to convert from
|
||||
sister_ebook = edition.ebooks.filter(format__in=['epub', 'html'])[0]
|
||||
|
||||
# run the conversion process
|
||||
|
||||
output = convert_to_mobi(sister_ebook.url)
|
||||
#output = open("/Users/raymondyee/Downloads/hello.mobi").read()
|
||||
|
||||
file_ = write_file_to_storage(output,
|
||||
"application/x-mobipocket-ebook",
|
||||
"/ebf/{0}.mobi".format(uuid.uuid4().hex))
|
||||
|
||||
# create a path for the ebookfile: IS THIS NECESSARY?
|
||||
# https://github.com/Gluejar/regluit/blob/25dcb06f464dc11b5e589ab6859dfcc487f8f3ef/core/models.py#L1771
|
||||
|
||||
#ebfile = EbookFile(edition=edition, file=file_, format='mobi')
|
||||
#ebfile.save()
|
||||
|
||||
# maybe need to create an ebook pointing to ebookFile ?
|
||||
# copy metadata from sister ebook
|
||||
|
||||
ebfile_url = default_storage.url(file_.name)
|
||||
|
||||
ebook = Ebook(url=ebfile_url,
|
||||
format="mobi",
|
||||
provider="Unglue.it",
|
||||
rights=sister_ebook.rights,
|
||||
edition=edition)
|
||||
ebook.save()
|
||||
|
||||
return ebook
|
|
@ -65,7 +65,6 @@ from regluit.core.parameters import (
|
|||
)
|
||||
from regluit.core.epub import personalize, ungluify, ask_epub
|
||||
from regluit.core.pdf import ask_pdf, pdf_append
|
||||
from regluit.core import mobi
|
||||
from regluit.core.signals import (
|
||||
successful_campaign,
|
||||
unsuccessful_campaign,
|
||||
|
@ -213,8 +212,8 @@ class Acq(models.Model):
|
|||
|
||||
class mock_ebook(object):
|
||||
def __init__(self, acq):
|
||||
self.url = acq.get_mobi_url()
|
||||
self.format = 'mobi'
|
||||
self.url = acq.get_epub_url()
|
||||
self.format = 'epub'
|
||||
self.filesize = 0
|
||||
def save(self):
|
||||
return True
|
||||
|
@ -247,10 +246,6 @@ class Acq(models.Model):
|
|||
else:
|
||||
return self.expires < datetime.now()
|
||||
|
||||
def get_mobi_url(self):
|
||||
if self.expired:
|
||||
return ''
|
||||
return self.get_watermarked().download_link_mobi
|
||||
|
||||
def get_epub_url(self):
|
||||
if self.expired:
|
||||
|
@ -887,17 +882,6 @@ class Campaign(models.Model):
|
|||
def latest_ending(cls):
|
||||
return timedelta(days=int(settings.UNGLUEIT_LONGEST_DEADLINE)) + now()
|
||||
|
||||
def make_mobis(self):
|
||||
# make archive files for ebooks, make mobi files for epubs
|
||||
versions = set()
|
||||
for ebook in self.work.ebooks().filter(provider__in=GOOD_PROVIDERS, format='mobi'):
|
||||
versions.add(ebook.version_label)
|
||||
for ebook in self.work.ebooks_all().exclude(provider='Unglue.it').filter(provider__in=GOOD_PROVIDERS, format='epub'):
|
||||
if not ebook.version_label in versions:
|
||||
# now make the mobi file
|
||||
ebf = ebook.get_archive_ebf()
|
||||
ebf.make_mobi()
|
||||
|
||||
def add_ask_to_ebfs(self, position=0):
|
||||
if not self.use_add_ask or self.type != THANKS:
|
||||
return
|
||||
|
@ -948,17 +932,6 @@ class Campaign(models.Model):
|
|||
new_epub_ebf.version = version
|
||||
new_ebfs.append(new_epub_ebf)
|
||||
|
||||
# now make the mobi file
|
||||
new_mobi_ebf = EbookFile.objects.create(edition=edition, format='mobi', asking=True)
|
||||
try:
|
||||
new_mobi_file = ContentFile(mobi.convert_to_mobi(new_epub_ebf.file.url))
|
||||
except Exception as e:
|
||||
logger.error("error making mobi for %s" % (new_epub_ebf.file.url))
|
||||
raise e
|
||||
new_mobi_ebf.file.save(path_for_file('ebf', None), new_mobi_file)
|
||||
new_mobi_ebf.save()
|
||||
new_mobi_ebf.version = version
|
||||
new_ebfs.append(new_mobi_ebf)
|
||||
except Exception as e:
|
||||
logger.error("error making epub ask or mobi %s" % (e))
|
||||
for ebf in new_ebfs:
|
||||
|
@ -1047,7 +1020,6 @@ class Campaign(models.Model):
|
|||
ungluified.file_obj.seek(0)
|
||||
watermarked = watermarker.platform(epubfile=ungluified.file_obj, **params)
|
||||
self.make_unglued_ebf('epub', watermarked)
|
||||
self.make_unglued_ebf('mobi', watermarked)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ from regluit.marc.models import MARCRecord as NewMARC
|
|||
from questionnaire.models import Landing
|
||||
|
||||
from regluit.bisac.models import interpret_notation
|
||||
from regluit.core import mobi
|
||||
import regluit.core.cc as cc
|
||||
from regluit.core.covers import (get_thumbnail,
|
||||
DEFAULT_COVER, DEFAULT_COVER_LARGE, DEFAULT_COVER_SMALL)
|
||||
|
@ -1091,7 +1090,6 @@ class EbookFile(models.Model):
|
|||
asking = models.BooleanField(default=False)
|
||||
ebook = models.ForeignKey('Ebook', on_delete=models.CASCADE, related_name='ebook_files', null=True)
|
||||
source = models.URLField(max_length=1024, null=True, blank=True)
|
||||
mobied = models.IntegerField(default=0) #-1 indicates a failed conversion attempt
|
||||
version = None
|
||||
def check_file(self):
|
||||
if self.format == 'epub':
|
||||
|
@ -1105,43 +1103,6 @@ class EbookFile(models.Model):
|
|||
except:
|
||||
return False
|
||||
|
||||
def make_mobi(self):
|
||||
if not self.format == 'epub' or not settings.MOBIGEN_URL:
|
||||
return False
|
||||
if self.mobied < 0:
|
||||
return False
|
||||
try:
|
||||
mobi_cf = ContentFile(mobi.convert_to_mobi(self.file.url))
|
||||
except:
|
||||
self.mobied = -1
|
||||
self.save()
|
||||
return False
|
||||
new_mobi_ebf = EbookFile.objects.create(
|
||||
edition=self.edition,
|
||||
format='mobi',
|
||||
asking=self.asking,
|
||||
source=self.file.url,
|
||||
)
|
||||
|
||||
new_mobi_ebf.file.save(path_for_file(new_mobi_ebf, None), mobi_cf)
|
||||
new_mobi_ebf.save()
|
||||
if self.ebook:
|
||||
new_ebook = Ebook.objects.create(
|
||||
edition=self.edition,
|
||||
format='mobi',
|
||||
provider='Unglue.it',
|
||||
url=new_mobi_ebf.file.url,
|
||||
rights=self.ebook.rights,
|
||||
version_label=self.ebook.version_label,
|
||||
version_iter=self.ebook.version_iter,
|
||||
filesize=mobi_cf.size,
|
||||
)
|
||||
new_mobi_ebf.ebook = new_ebook
|
||||
new_mobi_ebf.save()
|
||||
self.mobied = 1
|
||||
self.save()
|
||||
return True
|
||||
|
||||
send_to_kindle_limit = 7492232
|
||||
|
||||
class Ebook(models.Model):
|
||||
|
|
|
@ -29,8 +29,7 @@ from regluit.core import (
|
|||
bookloader,
|
||||
covers,
|
||||
models,
|
||||
librarything,
|
||||
mobigen
|
||||
librarything
|
||||
)
|
||||
from regluit.core.models import Acq, Campaign, EbookFile, Gift, UserProfile, Work
|
||||
from regluit.core.signals import deadline_impending
|
||||
|
@ -157,18 +156,8 @@ def process_ebfs(campaign_id):
|
|||
campaign.add_ask_to_ebfs()
|
||||
else:
|
||||
campaign.revert_asks()
|
||||
campaign.make_mobis()
|
||||
|
||||
|
||||
@task
|
||||
def make_mobi(ebookfile_id):
|
||||
try:
|
||||
ebookfile = EbookFile.objects.get(ebookfile_id)
|
||||
except EbookFile.DoesNotExist as e:
|
||||
logger.error("error getting EbookFile %s", ebookfile_id)
|
||||
return False
|
||||
return ebookfile.make_mobi()
|
||||
|
||||
@task
|
||||
def refresh_acqs():
|
||||
in_10_min = now() + timedelta(minutes=10)
|
||||
|
@ -194,10 +183,6 @@ def refresh_acqs():
|
|||
else:
|
||||
acq.refreshed = True
|
||||
|
||||
@task
|
||||
def convert_to_mobi(input_url, input_format="application/epub+zip"):
|
||||
return mobigen.convert_to_mobi(input_url, input_format)
|
||||
|
||||
@task
|
||||
def ml_subscribe_task(profile_id, **kwargs):
|
||||
try:
|
||||
|
|
|
@ -943,23 +943,14 @@ class DownloadPageTest(TestCase):
|
|||
eb1.edition = e1
|
||||
eb1.format = 'epub'
|
||||
|
||||
eb2 = models.Ebook()
|
||||
eb2.url = "https://example2.com"
|
||||
eb2.edition = e2
|
||||
eb2.format = 'mobi'
|
||||
|
||||
eb1.save()
|
||||
eb2.save()
|
||||
|
||||
anon_client = Client()
|
||||
response = anon_client.get("/work/%s/download/" % w.id, follow=True)
|
||||
self.assertContains(response, "/download_ebook/%s/"% eb1.id, count=11)
|
||||
self.assertContains(response, "/download_ebook/%s/"% eb2.id, count=5)
|
||||
self.assertContains(response, "/download_ebook/%s/"% eb1.id, count=12)
|
||||
self.assertTrue(eb1.edition.work.is_free)
|
||||
eb1.delete()
|
||||
self.assertTrue(eb2.edition.work.is_free)
|
||||
eb2.delete()
|
||||
self.assertFalse(eb2.edition.work.is_free)
|
||||
|
||||
class MailingListTests(TestCase):
|
||||
#mostly to check that MailChimp account is setp correctly
|
||||
|
@ -1057,7 +1048,7 @@ class EbookFileTests(TestCase):
|
|||
#flip the campaign to success
|
||||
c.cc_date_initial = datetime(2012, 1, 1)
|
||||
c.update_status()
|
||||
self.assertEqual(c.work.ebooks().count(), 2)
|
||||
self.assertEqual(c.work.ebooks().count(), 1)
|
||||
c.do_watermark = False
|
||||
c.save()
|
||||
url = acq.get_watermarked().download_link_epub
|
||||
|
@ -1122,15 +1113,12 @@ class EbookFileTests(TestCase):
|
|||
ebf.ebook = eb
|
||||
ebf.save()
|
||||
temp_file.close()
|
||||
ebf.make_mobi()
|
||||
finally:
|
||||
# make sure we get rid of temp file
|
||||
os.remove(temp.name)
|
||||
#test the ask-appender
|
||||
c.add_ask_to_ebfs()
|
||||
self.assertTrue(c.work.ebookfiles().filter(asking=True, format='epub').count() > 0)
|
||||
if settings.MOBIGEN_URL:
|
||||
self.assertTrue(c.work.ebookfiles().filter(asking=True, format='mobi').count() > 0)
|
||||
self.assertTrue(c.work.ebookfiles().filter(asking=True, ebook__active=True).count() > 0)
|
||||
self.assertTrue(c.work.ebookfiles().filter(asking=False, ebook__active=True).count() == 0)
|
||||
#test the unasker
|
||||
|
@ -1154,24 +1142,11 @@ class EbookFileTests(TestCase):
|
|||
ebf = EbookFile(format='epub', edition=e, file=dj_file)
|
||||
ebf.save()
|
||||
temp_file.close()
|
||||
ebf.make_mobi()
|
||||
finally:
|
||||
# make sure we get rid of temp file
|
||||
os.remove(temp.name)
|
||||
self.assertTrue(ebf.mobied < 0)
|
||||
|
||||
|
||||
class MobigenTests(TestCase):
|
||||
def test_convert_to_mobi(self):
|
||||
"""
|
||||
check the size of the mobi output of a Moby Dick epub
|
||||
"""
|
||||
from regluit.core.mobigen import convert_to_mobi
|
||||
if settings.TEST_INTEGRATION:
|
||||
output = convert_to_mobi(
|
||||
"https://github.com/GITenberg/Moby-Dick--Or-The-Whale_2701/releases/download/0.2.0/Moby-Dick-Or-The-Whale.epub"
|
||||
)
|
||||
self.assertTrue(len(output) > 2207877)
|
||||
|
||||
@override_settings(LOCAL_TEST=True)
|
||||
class LibTests(TestCase):
|
||||
|
@ -1225,11 +1200,11 @@ class GitHubTests(TestCase):
|
|||
)
|
||||
expected_set = set([
|
||||
('epub', u'Adventures-of-Huckleberry-Finn.epub'),
|
||||
('mobi', u'Adventures-of-Huckleberry-Finn.mobi'),
|
||||
('pdf', u'Adventures-of-Huckleberry-Finn.pdf')
|
||||
])
|
||||
|
||||
self.assertEqual(set(ebooks), expected_set)
|
||||
self.assertTrue(('epub', 'Adventures-of-Huckleberry-Finn.epub') in set(ebooks))
|
||||
self.assertTrue(('pdf', 'Adventures-of-Huckleberry-Finn.pdf') in set(ebooks))
|
||||
|
||||
class OnixLoaderTests(TestCase):
|
||||
fixtures = ['initial_data.json']
|
||||
|
|
|
@ -13,7 +13,6 @@ from django.forms import ValidationError
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pyepub import EPUB
|
||||
from regluit.mobi import Mobi
|
||||
from .isbn import ISBN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -112,14 +111,6 @@ def test_file(the_file, fformat):
|
|||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise ValidationError(_('Are you sure this is an EPUB file?: %s' % e))
|
||||
elif fformat == 'mobi':
|
||||
try:
|
||||
book = Mobi(the_file.file)
|
||||
book.parse()
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
#raise ValidationError(_('Are you sure this is a MOBI file?: %s' % e))
|
||||
raise e
|
||||
elif fformat == 'pdf':
|
||||
try:
|
||||
PdfFileReader(the_file.file)
|
||||
|
|
|
@ -109,7 +109,7 @@ class EbookFileForm(forms.ModelForm):
|
|||
self.fields['format'].widget = forms.HiddenInput()
|
||||
if campaign_type == THANKS:
|
||||
self.fields['format'].widget = forms.Select(
|
||||
choices = (('pdf', 'PDF'), ('epub', 'EPUB'), ('mobi', 'MOBI'))
|
||||
choices = (('pdf', 'PDF'), ('epub', 'EPUB'))
|
||||
)
|
||||
|
||||
def clean_version_label(self):
|
||||
|
|
|
@ -183,12 +183,8 @@ $j(document).ready(function() {
|
|||
<h3>Download your ebook{% if acq.lib_acq %}{% if acq.on_reserve %}, on reserve for you at{% else %}, on loan to you at{% endif %} {{ acq.lib_acq.user.library }}{% endif %}</h3>
|
||||
<div class="ebook_download">
|
||||
<a href="{{ formats.epub }}"><img src="/static/images/epub32.png" height="32" alt="epub" title="epub" /></a>
|
||||
<a href="{{ formats.epub }}">EPUB</a> (for iBooks, Nook, Kobo)
|
||||
<a href="{{ formats.epub }}">EPUB</a> (for Kindle, iBooks, Nook, Kobo)
|
||||
<a class="dropbox-saver" href="{{ xfer_url }}" data-filename="unglueit-{{ work.id }}.epub"></a>
|
||||
<br /><br />
|
||||
<a href="{{ formats.mobi }}"><img src="/static/images/mobi32.png" height="32" alt="mobi" title="mobi" /></a>
|
||||
<a href="{{ formats.mobi }}">MOBI</a> (for Kindle)
|
||||
<a class="dropbox-saver" href="{{ kindle_url }}" data-filename="unglueit-{{ work.id }}.mobi"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
@ -211,7 +207,7 @@ $j(document).ready(function() {
|
|||
Looks like you're using an embedded browser inside an iOS app. (Maybe you followed a link in Twitter or Facebook?)
|
||||
</p>
|
||||
<p>
|
||||
{% if formats.epub or formats.mobi %}
|
||||
{% if formats.epub %}
|
||||
To read this ebook you should open this page in safari, or use one of the "One-click" buttons, above. <br />
|
||||
{% if iphone %}<img width="357" height="156" src="/static/images/clickhere.png" alt="how to open in safari" />{% else %}<img width="500" height="403" src="/static/images/open_safari.png" alt="how to open in safari" />{% endif %}<br clear="left" />
|
||||
{% endif %}
|
||||
|
@ -271,10 +267,6 @@ $j(document).ready(function() {
|
|||
<p>
|
||||
Download the <a href="{{ formats.text }}">text version</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
This ebook is only available in .mobi. Your best bet is to install the free Amazon Kindle app from the Apple Store and then use the Send-to-Kindle option above.
|
||||
</p>
|
||||
{% endif %}
|
||||
<p class="other_instructions_paragraph">
|
||||
Not on iOS? Try the instructions for <a class="android other_instructions">Android</a>, <a class="desktop other_instructions">desktop computers</a>, or <a class="ereader other_instructions">ereaders (Kindle, Nook, Kobo, etc.)</a>.
|
||||
|
@ -284,6 +276,7 @@ $j(document).ready(function() {
|
|||
<div id="android_div"{% if android %} class="active"{% endif %}>
|
||||
<h4>Android devices</h4>
|
||||
{% if formats.epub %}
|
||||
You can send EPUB files to your Amazon Kindle using the Send-to-Kindle option above.
|
||||
<p>
|
||||
You may already have an app which reads ebooks. Download the <a href="{{ formats.epub }}">epub file</a> and see if you're offered an option for opening the file. If so, you're done! If not...
|
||||
</p>
|
||||
|
@ -313,10 +306,6 @@ $j(document).ready(function() {
|
|||
<p>
|
||||
Download the <a href="{{ formats.text }}">text version</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
This ebook is only available in .mobi. Your best bet is to install the free Amazon Kindle app from Google Play and then use the Send-to-Kindle option above.
|
||||
</p>
|
||||
{% endif %}{% endif %}{% endif %}{% endif %}
|
||||
<p class="other_instructions_paragraph">
|
||||
Not on Android? Try the instructions for <a class="ios other_instructions">iPhone/iPad</a>, <a class="desktop other_instructions">desktop computers</a>, or <a class="ereader other_instructions">ereaders (Kindle, Nook, Kobo, etc.)</a>.
|
||||
|
@ -344,14 +333,6 @@ $j(document).ready(function() {
|
|||
<li>Open the file in Calibre.</li>
|
||||
<li>You can <a href="http://blog.marvinapp.com/post/53438723356">use a Calibre plugin</a> to manage files on reader apps.
|
||||
</ul>
|
||||
{% elif formats.mobi %}
|
||||
<p class="ebook_download logo"><img src="/static/images/calibre_logo.png" alt="Calibre Logo" />Calibre</p>
|
||||
<ul>
|
||||
<li><a href="http://calibre-ebook.com/download">Download the free Calibre app.</a></li>
|
||||
<li>Download the <a href="{{ formats.mobi }}">mobi file</a>.</li>
|
||||
<li>Open the file in Calibre.</li>
|
||||
<li>You can <a href="http://blog.marvinapp.com/post/53438723356">use a Calibre plugin</a> to manage files on reader apps.
|
||||
</ul>
|
||||
{% elif formats.html %}
|
||||
<p>
|
||||
You can read the <a href="{{ formats.html }}">HTML version</a> right here in your browser.
|
||||
|
@ -372,8 +353,8 @@ $j(document).ready(function() {
|
|||
{% if formats.mobi or formats.pdf or formats.epub %}
|
||||
<ul>
|
||||
<li>
|
||||
{% if formats.mobi %}
|
||||
<b>Kindle</b>: download the <a href="{{ formats.mobi }}">mobi file</a> to your computer, or use the <i>Send To Kindle</i> button above.
|
||||
{% if formats.mobi or formats.epub %}
|
||||
<b>Kindle</b>: download the <a href="{{ formats.epub }}">epub file</a> to your computer, or use the <i>Send To Kindle</i> button above.
|
||||
{% elif formats.pdf %}
|
||||
<b>Kindle</b>: download the <a href="{{ formats.pdf }}">pdf file</a> to your computer, or use the <i>Send To Kindle</i> button above.
|
||||
{% else %}
|
||||
|
|
|
@ -44,7 +44,6 @@
|
|||
<p> Reference id: <b>{{watermarked.referenceid}}</b></p>
|
||||
<ul>
|
||||
<li><a href="{{watermarked.download_link_epub}}">Processed epub for testing</a></li>
|
||||
<li><a href="{{watermarked.download_link_mobi}}">Processed mobi (kindle) for testing</a></li>
|
||||
</ul>
|
||||
{% else %}{% if upload_error %}
|
||||
<p>
|
||||
|
@ -65,7 +64,7 @@
|
|||
{{ upload_error }}
|
||||
<h2>Upload Ebook files</h2>
|
||||
{% ifequal edition.work.last_campaign.type 2 %}
|
||||
<p>At this time, we accept only EPUB files for "Buy to Unglue" campaigns. Files for Kindle will be autogenerated.
|
||||
<p>At this time, we accept only EPUB files for "Buy to Unglue" campaigns.
|
||||
{% endifequal %}
|
||||
{% ifequal edition.work.last_campaign.type 3 %}
|
||||
<p>You can upload PDF, EPUB and MOBI files for "Thanks for Ungluing" campaigns.
|
||||
|
|
|
@ -2,10 +2,7 @@
|
|||
<p style="">
|
||||
<img src="/static/images/{{ facet.facet_name }}32.png" height="32" alt="{{ facet.facet_name }}" title="{{ facet.facet_name }}" />
|
||||
{% if facet.facet_name == 'epub' %}
|
||||
These books are available in EPUB format - good for iBooks, Nook, Kobo.
|
||||
{% endif %}
|
||||
{% if facet.facet_name == 'mobi' %}
|
||||
These books are available in MOBI format - good for Kindle.
|
||||
These books are available in EPUB format - good for Kindle, iBooks, Nook, Kobo.
|
||||
{% endif %}
|
||||
{% if facet.facet_name == 'pdf' %}
|
||||
These books are available in PDF format - good for desktops, printing.
|
||||
|
|
|
@ -269,7 +269,7 @@ What does this mean for you? If you're a book lover, you can read unglued ebook
|
|||
|
||||
<dd>All unglued ebooks are released under a Creative Commons or other free license. The rights holder chooses which license to apply. Books that we distribute in a Buy-to-Unglue Campaign have creative commons licenses, but the effective date of these licenses is set in the future.<br /><br />
|
||||
|
||||
Creative Commons licenses mean that once the license is effective you <b>can</b>: make copies; keep them for as long as you like; shift them to other formats (like .mobi or PDF); share them with friends or on the internet; download them for free.<br /><br />
|
||||
Creative Commons licenses mean that once the license is effective you <b>can</b>: make copies; keep them for as long as you like; shift them to other formats (like PDF); share them with friends or on the internet; download them for free.<br /><br />
|
||||
|
||||
Under NC (non-commercial) licenses, you <b>cannot</b>: sell unglued ebooks, or otherwise use them commercially, without permission from the rights holder.<br /><br />
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ Reward the courageous creators who've made their ebooks free to all.
|
|||
<dd>There’s no charge creators to join. Unglue.it charge $0.25 + 8%, which includes credit card fees. So if you pay $1 for a book, 67 cents goes to the creator (or whoever holds commercial rights), and 33 cents, the entirety of our fee, goes to pay bank network fees. If you pay $10 for a book, the split is $8.95 for the creator, $0.60 for the bank network, and $0.35 to support unglue.it. Unglue.it doesn't process contributions less than $1.</dd>
|
||||
|
||||
<dt>What file formats are supported?</dt>
|
||||
<dd>The website is designed to work with pdf, epub, and mobi.</dd>
|
||||
<dd>The website is designed to work with pdf, epub.</dd>
|
||||
|
||||
<dt>Why should libraries support Thanks-for-Ungluing books?</dt>
|
||||
<dd>Libraries that participate in Thanks-for-Ungluing campaigns make these books more accessible to their users. In addition:
|
||||
|
|
|
@ -164,20 +164,14 @@ Please fix the following before launching your campaign:
|
|||
{% endif %}
|
||||
{% if campaign.work.epubfiles.0 %}
|
||||
{% for ebf in campaign.work.epubfiles %}
|
||||
<p>{% if ebf.active %}<span class="yikes">ACTIVE</span> {% elif ebf.ebook.active %} MIRROR {% endif %}EPUB file: <a href="{{ebf.file.url}}">{{ebf.file}}</a> <br />created {{ebf.created}} for edition <a href="#edition_{{ebf.edition_id}}">{{ebf.edition_id}}</a> {% if ebf.asking %}(This file has had the campaign 'ask' added.){% endif %}<br />{% if ebf.active %}{% if action == 'mademobi' %}<span class="yikes">A MOBI file is being generated. </span> (Takes a minute or two.) {% else %}You can <a href="{% url 'makemobi' campaign.id ebf.id %}">generate a MOBI file.</a> {% endif %}{% endif %}</p>
|
||||
<p>{% if ebf.active %}<span class="yikes">ACTIVE</span> {% elif ebf.ebook.active %} MIRROR {% endif %}EPUB file: <a href="{{ebf.file.url}}">{{ebf.file}}</a> <br />created {{ebf.created}} for edition <a href="#edition_{{ebf.edition_id}}">{{ebf.edition_id}}</a> {% if ebf.asking %}(This file has had the campaign 'ask' added.){% endif %}</p>
|
||||
{% endfor %}
|
||||
{% if campaign.work.test_acqs.0 %}
|
||||
<ul>
|
||||
<li><a href="{{campaign.work.test_acqs.0.watermarked.download_link_epub}}">Processed epub for testing</a></li>
|
||||
<li><a href="{{campaign.work.test_acqs.0.watermarked.download_link_mobi}}">Processed mobi (kindle) for testing</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if campaign.work.mobifiles.0 %}
|
||||
{% for ebf in campaign.work.mobifiles %}
|
||||
<p>{% if ebf.active %}<span class="yikes">ACTIVE</span> {% endif %}MOBI file: <a href="{{ebf.file.url}}">{{ebf.file}}</a> <br />created {{ebf.created}} for edition <a href="#edition_{{ebf.edition_id}}">{{ebf.edition_id}}</a> {% if ebf.asking %}(This file has had the campaign 'ask' added.){% endif %}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if campaign.work.pdffiles.0 %}
|
||||
{% for ebf in campaign.work.pdffiles %}
|
||||
<p>{% if ebf.active %}<span class="yikes">ACTIVE</span> {% endif %}PDF file: <a href="{{ebf.file.url}}">{{ebf.file}}</a> <br />created {{ebf.created}} for edition <a href="#edition_{{ebf.edition_id}}">{{ebf.edition_id}}</a> {% if ebf.asking %}(This file has had the campaign 'ask' added.){% endif %}</p>
|
||||
|
|
|
@ -18,9 +18,6 @@ onload = function(){
|
|||
else if(urlInput.value.endsWith('.epub')){
|
||||
formatInput.value = 'epub'
|
||||
}
|
||||
else if(urlInput.value.endsWith('.mobi')){
|
||||
formatInput.value = 'mobi'
|
||||
}
|
||||
else if(urlInput.value.endsWith('.html')){
|
||||
formatInput.value = 'html'
|
||||
};
|
||||
|
@ -32,9 +29,6 @@ onload = function(){
|
|||
else if(fileInput.value.endsWith('.epub')){
|
||||
formatInput.value = 'epub'
|
||||
}
|
||||
else if(fileInput.value.endsWith('.mobi')){
|
||||
formatInput.value = 'mobi'
|
||||
}
|
||||
else if(fileInput.value.endsWith('.html')){
|
||||
formatInput.value = 'html'
|
||||
};
|
||||
|
|
|
@ -61,8 +61,7 @@
|
|||
<div class="find-book">
|
||||
<h4>Available formats...</h4>
|
||||
<ul>
|
||||
<li><span class="format_display"><img src="/static/images/mobi32.png" height="32" alt="mobi" title="mobi" /> (for Kindle) </span></li>
|
||||
<li><span class="format_display"><img src="/static/images/epub32.png" height="32" alt="epub" title="epub" /> (for iBooks, Nook, Kobo) </span></li>
|
||||
<li><span class="format_display"><img src="/static/images/epub32.png" height="32" alt="epub" title="epub" /> (for Kindle, iBooks, Nook, Kobo) </span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -31,8 +31,6 @@ urlpatterns = [
|
|||
url(r"^rightsholders/agree/submitted$", TemplateView.as_view(template_name='agreed.html'), name="agreed"),
|
||||
url(r"^rightsholders/campaign/(?P<id>\d+)/$", views.manage_campaign, name="manage_campaign"),
|
||||
url(r"^rightsholders/campaign/(?P<id>\d+)/results/$", views.manage_campaign, {'action': 'results'}, name="campaign_results"),
|
||||
url(r"^rightsholders/campaign/(?P<id>\d+)/(?P<ebf>\d+)/makemobi/$", views.manage_campaign, {'action': 'makemobi'}, name="makemobi"),
|
||||
url(r"^rightsholders/campaign/(?P<id>\d+)/mademobi/$", views.manage_campaign, {'action': 'mademobi'}, name="mademobi"),
|
||||
url(r"^rightsholders/edition/(?P<work_id>\d*)/(?P<edition_id>\d*)$", views.edit_edition, {'by': 'rh'}, name="rh_edition"),
|
||||
url(r"^rightsholders/edition/(?P<edition_id>\d*)/upload/$", views.edition_uploads, name="edition_uploads"),
|
||||
url(r"^rightsholders/claim/$", login_required(views.claim), name="claim"),
|
||||
|
|
|
@ -2523,7 +2523,7 @@ class DownloadView(PurchaseView):
|
|||
|
||||
unglued_ebooks = work.ebooks().filter(edition__unglued=True)
|
||||
other_ebooks = work.ebooks().filter(edition__unglued=False)
|
||||
xfer_url = kindle_url = None
|
||||
xfer_url = None
|
||||
acq = None
|
||||
formats = {} # a dict of format name and url
|
||||
for ebook in work.ebooks().all():
|
||||
|
@ -2546,9 +2546,7 @@ class DownloadView(PurchaseView):
|
|||
watermark_acq.delay(an_acq.id)
|
||||
acq = an_acq
|
||||
formats['epub'] = reverse('download_acq', kwargs={'nonce':acq.nonce, 'format':'epub'})
|
||||
formats['mobi'] = reverse('download_acq', kwargs={'nonce':acq.nonce, 'format':'mobi'})
|
||||
xfer_url = settings.BASE_URL_SECURE + formats['epub']
|
||||
kindle_url = settings.BASE_URL_SECURE + formats['mobi']
|
||||
can_kindle = True
|
||||
break
|
||||
|
||||
|
@ -2560,7 +2558,7 @@ class DownloadView(PurchaseView):
|
|||
#send to kindle
|
||||
|
||||
try:
|
||||
kindle_ebook = non_google_ebooks.filter(format='mobi')[0]
|
||||
kindle_ebook = non_google_ebooks.filter(format='epub')[0]
|
||||
can_kindle = kindle_ebook.kindle_sendable()
|
||||
except IndexError:
|
||||
try:
|
||||
|
@ -2584,7 +2582,6 @@ class DownloadView(PurchaseView):
|
|||
'other_ebooks': other_ebooks,
|
||||
'formats': formats,
|
||||
'xfer_url': xfer_url,
|
||||
'kindle_url': kindle_url,
|
||||
'dropbox_key': settings.DROPBOX_KEY,
|
||||
'can_kindle': can_kindle,
|
||||
'base_url': settings.BASE_URL_SECURE,
|
||||
|
@ -2699,8 +2696,6 @@ def download_acq(request, nonce, format):
|
|||
acq.borrow()
|
||||
if format == 'epub':
|
||||
return HttpResponseRedirect(acq.get_epub_url())
|
||||
else:
|
||||
return HttpResponseRedirect(acq.get_mobi_url())
|
||||
|
||||
def about(request, facet):
|
||||
template = "about_" + facet + ".html"
|
||||
|
@ -2903,7 +2898,7 @@ def send_to_kindle(request, work_id, javascript='0'):
|
|||
else:
|
||||
non_google_ebooks = work.ebooks().exclude(provider='Google Books')
|
||||
try:
|
||||
ebook = non_google_ebooks.filter(format='mobi')[0]
|
||||
ebook = non_google_ebooks.filter(format='epub')[0]
|
||||
except IndexError:
|
||||
try:
|
||||
ebook = non_google_ebooks.filter(format='pdf')[0]
|
||||
|
|
|
@ -281,16 +281,6 @@ def manage_campaign(request, id, ebf=None, action='manage'):
|
|||
new_premium_form = CustomPremiumForm(data={'campaign': campaign})
|
||||
activetab = '#2'
|
||||
else:
|
||||
if action == 'makemobi':
|
||||
try:
|
||||
ebookfile = get_object_or_404(models.EbookFile, id=ebf)
|
||||
except ValueError:
|
||||
raise Http404
|
||||
|
||||
tasks.make_mobi.delay(ebookfile.id)
|
||||
return HttpResponseRedirect(reverse('mademobi', args=[campaign.id]))
|
||||
elif action == 'mademobi':
|
||||
alerts.append('A MOBI file is being generated')
|
||||
form = ManageCampaignForm(
|
||||
instance=campaign,
|
||||
initial={'work_description':campaign.work.description}
|
||||
|
|
297
mobi/__init__.py
297
mobi/__init__.py
|
@ -1,297 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Mobi.py
|
||||
|
||||
Created by Elliot Kroo on 2009-12-25.
|
||||
Copyright (c) 2009 Elliot Kroo. All rights reserved.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import unittest
|
||||
from struct import *
|
||||
from pprint import pprint
|
||||
from . import utils
|
||||
from .lz77 import uncompress_lz77
|
||||
|
||||
class Mobi:
|
||||
def parse(self):
|
||||
""" reads in the file, then parses record tables"""
|
||||
self.contents = self.f.read();
|
||||
self.header = self.parseHeader();
|
||||
self.records = self.parseRecordInfoList();
|
||||
self.readRecord0()
|
||||
|
||||
def readRecord(self, recordnum, disable_compression=False):
|
||||
if self.config:
|
||||
if self.config['palmdoc']['Compression'] == 1 or disable_compression:
|
||||
return self.contents[self.records[recordnum]['record Data Offset']:self.records[recordnum+1]['record Data Offset']];
|
||||
elif self.config['palmdoc']['Compression'] == 2:
|
||||
result = uncompress_lz77(self.contents[self.records[recordnum]['record Data Offset']:self.records[recordnum+1]['record Data Offset']-self.config['mobi']['extra bytes']])
|
||||
return result
|
||||
|
||||
def readImageRecord(self, imgnum):
|
||||
if self.config:
|
||||
recordnum = self.config['mobi']['First Image index'] + imgnum;
|
||||
return self.readRecord(recordnum, disable_compression=True);
|
||||
|
||||
def author(self):
|
||||
"Returns the author of the book"
|
||||
return str(self.config['exth']['records'][100], 'utf-8')
|
||||
|
||||
def title(self):
|
||||
"Returns the title of the book"
|
||||
return str(self.config['mobi']['Full Name'], 'utf-8')
|
||||
|
||||
########### Private API ###########################
|
||||
|
||||
def __init__(self, filename):
|
||||
try:
|
||||
if isinstance(filename, str):
|
||||
self.f = open(filename, "rb");
|
||||
else:
|
||||
self.f = filename;
|
||||
except IOError as e:
|
||||
sys.stderr.write("Could not open %s! " % filename);
|
||||
raise e;
|
||||
self.offset = 0;
|
||||
|
||||
def __iter__(self):
|
||||
if not self.config: return;
|
||||
for record in range(1, self.config['mobi']['First Non-book index'] - 1):
|
||||
yield self.readRecord(record);
|
||||
|
||||
def parseRecordInfoList(self):
|
||||
records = {};
|
||||
# read in all records in info list
|
||||
for recordID in range(self.header['number of records']):
|
||||
headerfmt = '>II'
|
||||
headerlen = calcsize(headerfmt)
|
||||
fields = [
|
||||
"record Data Offset",
|
||||
"UniqueID",
|
||||
]
|
||||
# create tuple with info
|
||||
results = zip(fields, unpack(headerfmt, self.contents[self.offset:self.offset+headerlen]))
|
||||
|
||||
# increment offset into file
|
||||
self.offset += headerlen
|
||||
|
||||
# convert tuple to dictionary
|
||||
resultsDict = utils.toDict(results);
|
||||
|
||||
# futz around with the unique ID record, as the uniqueID's top 8 bytes are
|
||||
# really the "record attributes":
|
||||
resultsDict['record Attributes'] = (resultsDict['UniqueID'] & 0xFF000000) >> 24;
|
||||
resultsDict['UniqueID'] = resultsDict['UniqueID'] & 0x00FFFFFF;
|
||||
|
||||
# store into the records dict
|
||||
records[resultsDict['UniqueID']] = resultsDict;
|
||||
|
||||
return records;
|
||||
|
||||
def parseHeader(self):
|
||||
headerfmt = '>32shhIIIIII4s4sIIH'
|
||||
headerlen = calcsize(headerfmt)
|
||||
fields = [
|
||||
"name",
|
||||
"attributes",
|
||||
"version",
|
||||
"created",
|
||||
"modified",
|
||||
"backup",
|
||||
"modnum",
|
||||
"appInfoId",
|
||||
"sortInfoID",
|
||||
"type",
|
||||
"creator",
|
||||
"uniqueIDseed",
|
||||
"nextRecordListID",
|
||||
"number of records"
|
||||
]
|
||||
|
||||
# unpack header, zip up into list of tuples
|
||||
results = zip(fields, unpack(headerfmt, self.contents[self.offset:self.offset+headerlen]))
|
||||
|
||||
# increment offset into file
|
||||
self.offset += headerlen
|
||||
|
||||
# convert tuple array to dictionary
|
||||
resultsDict = utils.toDict(results);
|
||||
|
||||
return resultsDict
|
||||
|
||||
def readRecord0(self):
|
||||
palmdocHeader = self.parsePalmDOCHeader();
|
||||
MobiHeader = self.parseMobiHeader();
|
||||
exthHeader = None
|
||||
if MobiHeader['Has EXTH Header']:
|
||||
exthHeader = self.parseEXTHHeader();
|
||||
|
||||
self.config = {
|
||||
'palmdoc': palmdocHeader,
|
||||
'mobi' : MobiHeader,
|
||||
'exth' : exthHeader
|
||||
}
|
||||
|
||||
def parseEXTHHeader(self):
|
||||
headerfmt = '>III'
|
||||
headerlen = calcsize(headerfmt)
|
||||
|
||||
fields = [
|
||||
'identifier',
|
||||
'header length',
|
||||
'record Count'
|
||||
]
|
||||
|
||||
# unpack header, zip up into list of tuples
|
||||
results = zip(fields, unpack(headerfmt, self.contents[self.offset:self.offset+headerlen]))
|
||||
|
||||
# convert tuple array to dictionary
|
||||
resultsDict = utils.toDict(results);
|
||||
|
||||
self.offset += headerlen;
|
||||
resultsDict['records'] = {};
|
||||
for record in range(resultsDict['record Count']):
|
||||
recordType, recordLen = unpack(">II", self.contents[self.offset:self.offset+8]);
|
||||
recordData = self.contents[self.offset+8:self.offset+recordLen];
|
||||
resultsDict['records'][recordType] = recordData;
|
||||
self.offset += recordLen;
|
||||
|
||||
return resultsDict;
|
||||
|
||||
def parseMobiHeader(self):
|
||||
headerfmt = '> IIII II 40s III IIIII IIII I 36s IIII 8s HHIIIII'
|
||||
headerlen = calcsize(headerfmt)
|
||||
|
||||
fields = [
|
||||
"identifier",
|
||||
"header length",
|
||||
"Mobi type",
|
||||
"text Encoding",
|
||||
|
||||
"Unique-ID",
|
||||
"Generator version",
|
||||
|
||||
"-Reserved",
|
||||
|
||||
"First Non-book index",
|
||||
"Full Name Offset",
|
||||
"Full Name Length",
|
||||
|
||||
"Language",
|
||||
"Input Language",
|
||||
"Output Language",
|
||||
"Format version",
|
||||
"First Image index",
|
||||
|
||||
"First Huff Record",
|
||||
"Huff Record Count",
|
||||
"First DATP Record",
|
||||
"DATP Record Count",
|
||||
|
||||
"EXTH flags",
|
||||
|
||||
"-36 unknown bytes, if Mobi is long enough",
|
||||
|
||||
"DRM Offset",
|
||||
"DRM Count",
|
||||
"DRM Size",
|
||||
"DRM Flags",
|
||||
|
||||
"-Usually Zeros, unknown 8 bytes",
|
||||
|
||||
"-Unknown",
|
||||
"Last Image Record",
|
||||
"-Unknown",
|
||||
"FCIS record",
|
||||
"-Unknown",
|
||||
"FLIS record",
|
||||
"Unknown"
|
||||
]
|
||||
|
||||
# unpack header, zip up into list of tuples
|
||||
results = zip(fields, unpack(headerfmt, self.contents[self.offset:self.offset+headerlen]))
|
||||
|
||||
# convert tuple array to dictionary
|
||||
resultsDict = utils.toDict(results);
|
||||
|
||||
resultsDict['Start Offset'] = self.offset;
|
||||
|
||||
resultsDict['Full Name'] = (self.contents[
|
||||
self.records[0]['record Data Offset'] + resultsDict['Full Name Offset'] :
|
||||
self.records[0]['record Data Offset'] + resultsDict['Full Name Offset'] + resultsDict['Full Name Length']])
|
||||
|
||||
resultsDict['Has DRM'] = resultsDict['DRM Offset'] != 0xFFFFFFFF;
|
||||
|
||||
resultsDict['Has EXTH Header'] = (resultsDict['EXTH flags'] & 0x40) != 0;
|
||||
|
||||
self.offset += resultsDict['header length'];
|
||||
|
||||
def onebits(x, width=16):
|
||||
return len(list(filter(lambda x: x == "1", (str((x >> i) & 1) for i in range(width-1, -1, -1)))));
|
||||
|
||||
resultsDict['extra bytes'] = 2*onebits(unpack(">H", self.contents[self.offset-2:self.offset])[0] & 0xFFFE)
|
||||
|
||||
return resultsDict;
|
||||
|
||||
def parsePalmDOCHeader(self):
|
||||
headerfmt = '>HHIHHHH'
|
||||
headerlen = calcsize(headerfmt)
|
||||
fields = [
|
||||
"Compression",
|
||||
"Unused",
|
||||
"text length",
|
||||
"record count",
|
||||
"record size",
|
||||
"Encryption Type",
|
||||
"Unknown"
|
||||
]
|
||||
offset = self.records[0]['record Data Offset'];
|
||||
# create tuple with info
|
||||
results = zip(fields, unpack(headerfmt, self.contents[offset:offset+headerlen]))
|
||||
|
||||
# convert tuple array to dictionary
|
||||
resultsDict = utils.toDict(results);
|
||||
|
||||
self.offset = offset+headerlen;
|
||||
return resultsDict
|
||||
|
||||
TESTDIR = os.path.join(os.path.dirname(__file__), '../test/')
|
||||
MOBIFILE = os.path.join(TESTDIR, 'CharlesDarwin.mobi')
|
||||
|
||||
class MobiTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mobitest = Mobi(MOBIFILE);
|
||||
|
||||
def testParse(self):
|
||||
self.mobitest.parse();
|
||||
#pprint (self.mobitest.config)
|
||||
|
||||
def testRead(self):
|
||||
self.mobitest.parse();
|
||||
content = b''
|
||||
for i in range(1,5):
|
||||
content += self.mobitest.readRecord(i);
|
||||
|
||||
def testImage(self):
|
||||
self.mobitest.parse();
|
||||
#pprint (self.mobitest.records);
|
||||
for record in range(4):
|
||||
f = open("imagerecord%d.jpg" % record, 'wb')
|
||||
f.write(self.mobitest.readImageRecord(record));
|
||||
f.close();
|
||||
for record in range(4):
|
||||
f = os.remove("imagerecord%d.jpg" % record)
|
||||
|
||||
def testAuthorTitle(self):
|
||||
self.mobitest.parse()
|
||||
self.assertEqual(self.mobitest.author(), 'Charles Darwin')
|
||||
self.assertEqual(self.mobitest.title(), 'The Origin of Species by means '+
|
||||
'of Natural Selection, 6th Edition')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
85
mobi/lz77.py
85
mobi/lz77.py
|
@ -1,85 +0,0 @@
|
|||
import struct
|
||||
# ported directly from the PalmDoc Perl library
|
||||
# http://kobesearch.cpan.org/htdocs/EBook-Tools/EBook/Tools/PalmDoc.pm.html
|
||||
# ... and reported to py 3
|
||||
|
||||
def uncompress_lz77(data):
|
||||
length = len(data);
|
||||
offset = 0; # Current offset into data
|
||||
# char; # Character being examined
|
||||
# ord; # Ordinal of $char
|
||||
# lz77; # 16-bit Lempel-Ziv 77 length-offset pair
|
||||
# lz77offset; # LZ77 offset
|
||||
# lz77length; # LZ77 length
|
||||
# lz77pos; # Position inside $lz77length
|
||||
text = b''; # Output (uncompressed) text
|
||||
# textlength; # Length of uncompressed text during LZ77 pass
|
||||
# textpos; # Position inside $text during LZ77 pass
|
||||
|
||||
while offset < length:
|
||||
# char = substr($data, $offset++, 1);
|
||||
ord_ = data[offset];
|
||||
byte = ord_.to_bytes(1, byteorder='big')
|
||||
offset += 1;
|
||||
|
||||
# The long if-elsif chain is the best logic for $ord handling
|
||||
## no critic (Cascading if-elsif chain)
|
||||
if (ord_ == 0):
|
||||
# Nulls are literal
|
||||
text += byte;
|
||||
elif (ord_ <= 8):
|
||||
# Next $ord bytes are literal
|
||||
text += data[offset:offset + ord_] # text .=substr($data, $offset, ord);
|
||||
offset += ord_;
|
||||
elif (ord_ <= 0x7f):
|
||||
# Values from 0x09 through 0x7f are literal
|
||||
text += byte;
|
||||
elif (ord_ <= 0xbf):
|
||||
# Data is LZ77-compressed
|
||||
|
||||
# From Wikipedia:
|
||||
# "A length-distance pair is always encoded by a two-byte
|
||||
# sequence. Of the 16 bits that make up these two bytes,
|
||||
# 11 bits go to encoding the distance, 3 go to encoding
|
||||
# the length, and the remaining two are used to make sure
|
||||
# the decoder can identify the first byte as the beginning
|
||||
# of such a two-byte sequence."
|
||||
|
||||
offset += 1;
|
||||
if (offset > len(data)):
|
||||
print("WARNING: offset to LZ77 bits is outside of the data: %d" % offset);
|
||||
return text;
|
||||
|
||||
lz77, = struct.unpack('>H', data[offset-2:offset])
|
||||
|
||||
# Leftmost two bits are ID bits and need to be dropped
|
||||
lz77 &= 0x3fff;
|
||||
|
||||
# Length is rightmost 3 bits + 3
|
||||
lz77length = (lz77 & 0x0007) + 3;
|
||||
|
||||
# Remaining 11 bits are offset
|
||||
lz77offset = lz77 >> 3;
|
||||
if (lz77offset < 1):
|
||||
print("WARNING: LZ77 decompression offset is invalid!");
|
||||
return text;
|
||||
|
||||
# Getting text from the offset is a little tricky, because
|
||||
# in theory you can be referring to characters you haven't
|
||||
# actually decompressed yet. You therefore have to check
|
||||
# the reference one character at a time.
|
||||
textlength = len(text);
|
||||
for lz77pos in range(lz77length): # for($lz77pos = 0; $lz77pos < $lz77length; $lz77pos++)
|
||||
textpos = textlength - lz77offset;
|
||||
if (textpos < 0):
|
||||
print("WARNING: LZ77 decompression reference is before"+
|
||||
" beginning of text! %x" % lz77);
|
||||
return;
|
||||
|
||||
text += text[textpos:textpos + 1]; #text .= substr($text, $textpos, 1);
|
||||
textlength += 1;
|
||||
else:
|
||||
# 0xc0 - 0xff are single characters (XOR 0x80) preceded by
|
||||
# a space
|
||||
text += b' ' + (ord_ ^ 0x80).to_bytes(1, byteorder='big');
|
||||
return text;
|
|
@ -1,20 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
utils.py
|
||||
|
||||
Created by Elliot Kroo on 2009-12-25.
|
||||
Copyright (c) 2009 Elliot Kroo. All rights reserved.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import unittest
|
||||
|
||||
|
||||
def toDict(tuples):
|
||||
resultsDict = {}
|
||||
for field, value in tuples:
|
||||
if len(field) > 0 and field[0] != "-":
|
||||
resultsDict[field] = value
|
||||
return resultsDict;
|
|
@ -8,9 +8,6 @@ DROPBOX_KEY = os.environ.get('DROPBOX_KEY', '012345678901234')
|
|||
GITHUB_PUBLIC_TOKEN = os.environ.get('GITHUB_PUBLIC_TOKEN', None) # 40 chars; null has lower limit
|
||||
MAILCHIMP_API_KEY = os.environ.get('MAILCHIMP_API_KEY', '-us2') # [32chars]-xx#
|
||||
MAILCHIMP_NEWS_ID = os.environ.get('MAILCHIMP_NEWS_ID', '0123456789')
|
||||
MOBIGEN_PASSWORD = os.environ.get('MOBIGEN_PASSWORD', '012345678901234')
|
||||
MOBIGEN_URL = os.environ.get('MOBIGEN_URL', '') # https://host/mobigen
|
||||
MOBIGEN_USER_ID = os.environ.get('MOBIGEN_USER_ID', 'user')
|
||||
STRIPE_PK = os.environ.get('STRIPE_PK', 'user')
|
||||
STRIPE_SK = os.environ.get('STRIPE_SK', 'user')
|
||||
|
||||
|
|
Loading…
Reference in New Issue