mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Merge from trunk
This commit is contained in:
commit
e7df9e742c
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
blic.rs
|
blic.rs
|
||||||
'''
|
'''
|
||||||
@ -21,21 +21,53 @@ class Blic(BasicNewsRecipe):
|
|||||||
masthead_url = 'http://www.blic.rs/resources/images/header/header_back.png'
|
masthead_url = 'http://www.blic.rs/resources/images/header/header_back.png'
|
||||||
language = 'sr'
|
language = 'sr'
|
||||||
publication_type = 'newspaper'
|
publication_type = 'newspaper'
|
||||||
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Georgia, serif1, serif} .article_description{font-family: Arial, sans1, sans-serif} .img_full{float: none} img{margin-bottom: 0.8em} '
|
extra_css = """
|
||||||
|
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
|
||||||
|
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||||
|
body{font-family: Georgia, serif1, serif}
|
||||||
|
.articledescription,#nadnaslov,.article_info{font-family: Arial, sans1, sans-serif}
|
||||||
|
.img_full{float: none}
|
||||||
|
#nadnaslov{font-size: small}
|
||||||
|
#article_lead{font-size: 1.5em}
|
||||||
|
h1{color: red}
|
||||||
|
.potpis{font-size: x-small; color: gray}
|
||||||
|
.article_info{font-size: small}
|
||||||
|
img{margin-bottom: 0.8em; margin-top: 0.8em; display: block}
|
||||||
|
"""
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
'comment' : description
|
'comment' : description
|
||||||
, 'tags' : category
|
, 'tags' : category
|
||||||
, 'publisher': publisher
|
, 'publisher': publisher
|
||||||
, 'language' : language
|
, 'language' : language
|
||||||
|
, 'linearize_tables' : True
|
||||||
}
|
}
|
||||||
|
|
||||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||||
remove_tags_before = dict(name='div', attrs={'id':'article_info'})
|
remove_tags_before = dict(name='div', attrs={'id':'article_info'})
|
||||||
remove_tags = [dict(name=['object','link'])]
|
remove_tags = [dict(name=['object','link','meta','base','object','embed'])]
|
||||||
remove_attributes = ['width','height']
|
remove_attributes = ['width','height','m_id','m_ext','mlg_id','poll_id','v_id']
|
||||||
|
|
||||||
feeds = [(u'Danasnje Vesti', u'http://www.blic.rs/rss/danasnje-vesti')]
|
feeds = [
|
||||||
|
(u'Politika' , u'http://www.blic.rs/rss/Vesti/Politika')
|
||||||
|
,(u'Tema Dana' , u'http://www.blic.rs/rss/Vesti/Tema-Dana')
|
||||||
|
,(u'Svet' , u'http://www.blic.rs/rss/Vesti/Svet')
|
||||||
|
,(u'Drustvo' , u'http://www.blic.rs/rss/Vesti/Drustvo')
|
||||||
|
,(u'Ekonomija' , u'http://www.blic.rs/rss/Vesti/Ekonomija')
|
||||||
|
,(u'Hronika' , u'http://www.blic.rs/rss/Vesti/Hronika')
|
||||||
|
,(u'Beograd' , u'http://www.blic.rs/rss/Vesti/Beograd')
|
||||||
|
,(u'Srbija' , u'http://www.blic.rs/rss/Vesti/Srbija')
|
||||||
|
,(u'Vojvodina' , u'http://www.blic.rs/rss/Vesti/Vojvodina')
|
||||||
|
,(u'Republika Srpska' , u'http://www.blic.rs/rss/Vesti/Republika-Srpska')
|
||||||
|
,(u'Reportaza' , u'http://www.blic.rs/rss/Vesti/Reportaza')
|
||||||
|
,(u'Dodatak' , u'http://www.blic.rs/rss/Vesti/Dodatak')
|
||||||
|
,(u'Zabava' , u'http://www.blic.rs/rss/Zabava')
|
||||||
|
,(u'Kultura' , u'http://www.blic.rs/rss/Kultura')
|
||||||
|
,(u'Slobodno Vreme' , u'http://www.blic.rs/rss/Slobodno-vreme')
|
||||||
|
,(u'IT' , u'http://www.blic.rs/rss/IT')
|
||||||
|
,(u'Komentar' , u'http://www.blic.rs/rss/Komentar')
|
||||||
|
,(u'Intervju' , u'http://www.blic.rs/rss/Intervju')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
@ -44,4 +76,4 @@ class Blic(BasicNewsRecipe):
|
|||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
for item in soup.findAll(style=True):
|
for item in soup.findAll(style=True):
|
||||||
del item['style']
|
del item['style']
|
||||||
return self.adeify_images(soup)
|
return soup
|
||||||
|
64
resources/recipes/gulfnews.recipe
Normal file
64
resources/recipes/gulfnews.recipe
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
'''
|
||||||
|
gulfnews.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class GulfNews(BasicNewsRecipe):
|
||||||
|
title = 'Gulf News'
|
||||||
|
__author__ = 'Darko Miletic'
|
||||||
|
description = 'News from United Arab Emirrates, persian gulf and rest of the world'
|
||||||
|
publisher = 'Al Nisr Publishing LLC'
|
||||||
|
category = 'news, politics, UAE, world'
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 200
|
||||||
|
no_stylesheets = True
|
||||||
|
encoding = 'utf8'
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'en'
|
||||||
|
remove_empty_feeds = True
|
||||||
|
publication_type = 'newsportal'
|
||||||
|
masthead_url = 'http://gulfnews.com/media/img/gulf_news_logo.jpg'
|
||||||
|
extra_css = """
|
||||||
|
body{font-family: Arial,Helvetica,sans-serif }
|
||||||
|
img{margin-bottom: 0.4em; display:block}
|
||||||
|
h1{font-family: Georgia, 'Times New Roman', Times, serif}
|
||||||
|
ol,ul{list-style: none}
|
||||||
|
.synopsis{font-size: small}
|
||||||
|
.details{font-size: x-small}
|
||||||
|
.image{font-size: xx-small}
|
||||||
|
"""
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comment' : description
|
||||||
|
, 'tags' : category
|
||||||
|
, 'publisher' : publisher
|
||||||
|
, 'language' : language
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name=['meta','link','object','embed'])
|
||||||
|
,dict(attrs={'class':['quickLinks','ratings']})
|
||||||
|
,dict(attrs={'id':'imageSelector'})
|
||||||
|
]
|
||||||
|
remove_attributes=['lang']
|
||||||
|
keep_only_tags=[
|
||||||
|
dict(name='h1')
|
||||||
|
,dict(attrs={'class':['synopsis','details','image','article']})
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'UAE News' , u'http://gulfnews.com/cmlink/1.446094')
|
||||||
|
,(u'Business' , u'http://gulfnews.com/cmlink/1.446098')
|
||||||
|
,(u'Entertainment' , u'http://gulfnews.com/cmlink/1.446095')
|
||||||
|
,(u'Sport' , u'http://gulfnews.com/cmlink/1.446096')
|
||||||
|
,(u'Life' , u'http://gulfnews.com/cmlink/1.446097')
|
||||||
|
]
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for item in soup.findAll(style=True):
|
||||||
|
del item['style']
|
||||||
|
return soup
|
@ -3,12 +3,17 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
|||||||
class AdvancedUserRecipe1274742400(BasicNewsRecipe):
|
class AdvancedUserRecipe1274742400(BasicNewsRecipe):
|
||||||
|
|
||||||
title = u'Las Vegas Review Journal'
|
title = u'Las Vegas Review Journal'
|
||||||
__author__ = 'Joel'
|
__author__ = 'Kovid Goyal'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
|
|
||||||
oldest_article = 7
|
oldest_article = 7
|
||||||
|
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
|
keep_only_tags = [dict(id='content-main')]
|
||||||
|
remove_tags = [dict(id=['right-col-content', 'trending-topics']),
|
||||||
|
{'class':['ppy-outer']}
|
||||||
|
]
|
||||||
|
no_stylesheets = True
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
(u'News', u'http://www.lvrj.com/news.rss'),
|
(u'News', u'http://www.lvrj.com/news.rss'),
|
||||||
|
@ -241,7 +241,7 @@ def get_parsed_proxy(typ='http', debug=True):
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def browser(honor_time=True, max_time=2, mobile_browser=False):
|
def browser(honor_time=True, max_time=2, mobile_browser=False, user_agent=None):
|
||||||
'''
|
'''
|
||||||
Create a mechanize browser for web scraping. The browser handles cookies,
|
Create a mechanize browser for web scraping. The browser handles cookies,
|
||||||
refresh requests and ignores robots.txt. Also uses proxy if avaialable.
|
refresh requests and ignores robots.txt. Also uses proxy if avaialable.
|
||||||
@ -253,8 +253,10 @@ def browser(honor_time=True, max_time=2, mobile_browser=False):
|
|||||||
opener = Browser()
|
opener = Browser()
|
||||||
opener.set_handle_refresh(True, max_time=max_time, honor_time=honor_time)
|
opener.set_handle_refresh(True, max_time=max_time, honor_time=honor_time)
|
||||||
opener.set_handle_robots(False)
|
opener.set_handle_robots(False)
|
||||||
opener.addheaders = [('User-agent', ' Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016' if mobile_browser else \
|
if user_agent is None:
|
||||||
'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13')]
|
user_agent = ' Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016' if mobile_browser else \
|
||||||
|
'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13'
|
||||||
|
opener.addheaders = [('User-agent', user_agent)]
|
||||||
http_proxy = get_proxies().get('http', None)
|
http_proxy = get_proxies().get('http', None)
|
||||||
if http_proxy:
|
if http_proxy:
|
||||||
opener.set_proxies({'http':http_proxy})
|
opener.set_proxies({'http':http_proxy})
|
||||||
|
@ -21,7 +21,7 @@ class ANDROID(USBMS):
|
|||||||
# HTC
|
# HTC
|
||||||
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9
|
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9
|
||||||
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
|
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
|
||||||
0xc92 : [0x100], 0xc97: [0x226]},
|
0xc92 : [0x100], 0xc97: [0x226], 0xc99 : [0x0100]},
|
||||||
|
|
||||||
# Eken
|
# Eken
|
||||||
0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] },
|
0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] },
|
||||||
@ -54,7 +54,7 @@ class ANDROID(USBMS):
|
|||||||
0x1004 : { 0x61cc : [0x100] },
|
0x1004 : { 0x61cc : [0x100] },
|
||||||
|
|
||||||
# Archos
|
# Archos
|
||||||
0x0e79 : { 0x1420 : [0x0216]},
|
0x0e79 : { 0x1419: [0x0216], 0x1420 : [0x0216]},
|
||||||
|
|
||||||
}
|
}
|
||||||
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books']
|
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books']
|
||||||
@ -70,10 +70,10 @@ class ANDROID(USBMS):
|
|||||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
||||||
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
|
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
|
||||||
'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE',
|
'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE',
|
||||||
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID']
|
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT']
|
||||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||||
'A70S']
|
'A70S', 'A101IT']
|
||||||
|
|
||||||
OSX_MAIN_MEM = 'Android Device Main Memory'
|
OSX_MAIN_MEM = 'Android Device Main Memory'
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ class PDNOVEL(USBMS):
|
|||||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE'
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE'
|
||||||
THUMBNAIL_HEIGHT = 130
|
THUMBNAIL_HEIGHT = 130
|
||||||
|
|
||||||
EBOOK_DIR_MAIN = 'eBooks'
|
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'eBooks'
|
||||||
SUPPORTS_SUB_DIRS = False
|
SUPPORTS_SUB_DIRS = False
|
||||||
DELETE_EXTS = ['.jpg', '.jpeg', '.png']
|
DELETE_EXTS = ['.jpg', '.jpeg', '.png']
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ class Dehyphenator(object):
|
|||||||
lookupword = self.removesuffixes.sub('', dehyphenated)
|
lookupword = self.removesuffixes.sub('', dehyphenated)
|
||||||
else:
|
else:
|
||||||
lookupword = dehyphenated
|
lookupword = dehyphenated
|
||||||
if len(firsthalf) > 3 and self.prefixes.match(firsthalf) is None:
|
if len(firsthalf) > 4 and self.prefixes.match(firsthalf) is None:
|
||||||
lookupword = self.removeprefix.sub('', lookupword)
|
lookupword = self.removeprefix.sub('', lookupword)
|
||||||
if self.verbose > 2:
|
if self.verbose > 2:
|
||||||
self.log("lookup word is: "+str(lookupword)+", orig is: " + str(hyphenated))
|
self.log("lookup word is: "+str(lookupword)+", orig is: " + str(hyphenated))
|
||||||
@ -224,6 +224,10 @@ class Dehyphenator(object):
|
|||||||
return firsthalf+u'\u2014'+wraptags+secondhalf
|
return firsthalf+u'\u2014'+wraptags+secondhalf
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
if self.format == 'individual_words' and len(firsthalf) + len(secondhalf) <= 6:
|
||||||
|
if self.verbose > 2:
|
||||||
|
self.log("too short, returned hyphenated word: " + str(hyphenated))
|
||||||
|
return hyphenated
|
||||||
if len(firsthalf) <= 2 and len(secondhalf) <= 2:
|
if len(firsthalf) <= 2 and len(secondhalf) <= 2:
|
||||||
if self.verbose > 2:
|
if self.verbose > 2:
|
||||||
self.log("too short, returned hyphenated word: " + str(hyphenated))
|
self.log("too short, returned hyphenated word: " + str(hyphenated))
|
||||||
|
@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
Fetch cover from LibraryThing.com based on ISBN number.
|
Fetch cover from LibraryThing.com based on ISBN number.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import sys, socket, os, re
|
import sys, socket, os, re, random
|
||||||
|
|
||||||
from lxml import html
|
from lxml import html
|
||||||
import mechanize
|
import mechanize
|
||||||
@ -16,13 +16,26 @@ from calibre.ebooks.chardet import strip_encoding_declarations
|
|||||||
|
|
||||||
OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false'
|
OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false'
|
||||||
|
|
||||||
|
def get_ua():
|
||||||
|
choices = [
|
||||||
|
'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11'
|
||||||
|
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
|
||||||
|
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)'
|
||||||
|
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)'
|
||||||
|
'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16'
|
||||||
|
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.2.153.1 Safari/525.19'
|
||||||
|
'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11'
|
||||||
|
]
|
||||||
|
return choices[random.randint(0, len(choices)-1)]
|
||||||
|
|
||||||
|
|
||||||
class HeadRequest(mechanize.Request):
|
class HeadRequest(mechanize.Request):
|
||||||
|
|
||||||
def get_method(self):
|
def get_method(self):
|
||||||
return 'HEAD'
|
return 'HEAD'
|
||||||
|
|
||||||
def check_for_cover(isbn, timeout=5.):
|
def check_for_cover(isbn, timeout=5.):
|
||||||
br = browser()
|
br = browser(user_agent=get_ua())
|
||||||
br.set_handle_redirect(False)
|
br.set_handle_redirect(False)
|
||||||
try:
|
try:
|
||||||
br.open_novisit(HeadRequest(OPENLIBRARY%isbn), timeout=timeout)
|
br.open_novisit(HeadRequest(OPENLIBRARY%isbn), timeout=timeout)
|
||||||
@ -51,7 +64,7 @@ def login(br, username, password, force=True):
|
|||||||
|
|
||||||
def cover_from_isbn(isbn, timeout=5., username=None, password=None):
|
def cover_from_isbn(isbn, timeout=5., username=None, password=None):
|
||||||
src = None
|
src = None
|
||||||
br = browser()
|
br = browser(user_agent=get_ua())
|
||||||
try:
|
try:
|
||||||
return br.open(OPENLIBRARY%isbn, timeout=timeout).read(), 'jpg'
|
return br.open(OPENLIBRARY%isbn, timeout=timeout).read(), 'jpg'
|
||||||
except:
|
except:
|
||||||
@ -100,7 +113,7 @@ def get_social_metadata(title, authors, publisher, isbn, username=None,
|
|||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
mi = MetaInformation(title, authors)
|
mi = MetaInformation(title, authors)
|
||||||
if isbn:
|
if isbn:
|
||||||
br = browser()
|
br = browser(user_agent=get_ua())
|
||||||
if username and password:
|
if username and password:
|
||||||
try:
|
try:
|
||||||
login(br, username, password, force=False)
|
login(br, username, password, force=False)
|
||||||
|
@ -71,21 +71,41 @@ class TXTInput(InputFormatPlugin):
|
|||||||
txt = txt.decode(ienc, 'replace')
|
txt = txt.decode(ienc, 'replace')
|
||||||
|
|
||||||
txt = _ent_pat.sub(xml_entity_to_unicode, txt)
|
txt = _ent_pat.sub(xml_entity_to_unicode, txt)
|
||||||
|
|
||||||
|
# Normalize line endings
|
||||||
|
txt = normalize_line_endings(txt)
|
||||||
|
|
||||||
|
if options.formatting_type == 'auto':
|
||||||
|
options.formatting_type = detect_formatting_type(txt)
|
||||||
|
|
||||||
|
if options.formatting_type == 'heuristic':
|
||||||
|
setattr(options, 'enable_heuristics', True)
|
||||||
|
setattr(options, 'markup_chapter_headings', True)
|
||||||
|
setattr(options, 'italicize_common_cases', True)
|
||||||
|
setattr(options, 'fix_indents', True)
|
||||||
|
setattr(options, 'preserve_spaces', True)
|
||||||
|
setattr(options, 'delete_blank_paragraphs', True)
|
||||||
|
setattr(options, 'format_scene_breaks', True)
|
||||||
|
setattr(options, 'dehyphenate', True)
|
||||||
|
|
||||||
|
# Determine the paragraph type of the document.
|
||||||
|
if options.paragraph_type == 'auto':
|
||||||
|
options.paragraph_type = detect_paragraph_type(txt)
|
||||||
|
if options.paragraph_type == 'unknown':
|
||||||
|
log.debug('Could not reliably determine paragraph type using block')
|
||||||
|
options.paragraph_type = 'block'
|
||||||
|
else:
|
||||||
|
log.debug('Auto detected paragraph type as %s' % options.paragraph_type)
|
||||||
|
|
||||||
# Preserve spaces will replace multiple spaces to a space
|
# Preserve spaces will replace multiple spaces to a space
|
||||||
# followed by the entity.
|
# followed by the entity.
|
||||||
if options.preserve_spaces:
|
if options.preserve_spaces:
|
||||||
txt = preserve_spaces(txt)
|
txt = preserve_spaces(txt)
|
||||||
|
|
||||||
# Normalize line endings
|
|
||||||
txt = normalize_line_endings(txt)
|
|
||||||
|
|
||||||
# Get length for hyphen removal and punctuation unwrap
|
# Get length for hyphen removal and punctuation unwrap
|
||||||
docanalysis = DocAnalysis('txt', txt)
|
docanalysis = DocAnalysis('txt', txt)
|
||||||
length = docanalysis.line_length(.5)
|
length = docanalysis.line_length(.5)
|
||||||
|
|
||||||
if options.formatting_type == 'auto':
|
|
||||||
options.formatting_type = detect_formatting_type(txt)
|
|
||||||
|
|
||||||
if options.formatting_type == 'markdown':
|
if options.formatting_type == 'markdown':
|
||||||
log.debug('Running text though markdown conversion...')
|
log.debug('Running text though markdown conversion...')
|
||||||
try:
|
try:
|
||||||
@ -96,16 +116,8 @@ class TXTInput(InputFormatPlugin):
|
|||||||
elif options.formatting_type == 'textile':
|
elif options.formatting_type == 'textile':
|
||||||
log.debug('Running text though textile conversion...')
|
log.debug('Running text though textile conversion...')
|
||||||
html = convert_textile(txt)
|
html = convert_textile(txt)
|
||||||
else:
|
|
||||||
# Determine the paragraph type of the document.
|
|
||||||
if options.paragraph_type == 'auto':
|
|
||||||
options.paragraph_type = detect_paragraph_type(txt)
|
|
||||||
if options.paragraph_type == 'unknown':
|
|
||||||
log.debug('Could not reliably determine paragraph type using block')
|
|
||||||
options.paragraph_type = 'block'
|
|
||||||
else:
|
|
||||||
log.debug('Auto detected paragraph type as %s' % options.paragraph_type)
|
|
||||||
|
|
||||||
|
else:
|
||||||
# Dehyphenate
|
# Dehyphenate
|
||||||
dehyphenator = Dehyphenator(options.verbose, log=self.log)
|
dehyphenator = Dehyphenator(options.verbose, log=self.log)
|
||||||
txt = dehyphenator(txt,'txt', length)
|
txt = dehyphenator(txt,'txt', length)
|
||||||
@ -129,15 +141,6 @@ class TXTInput(InputFormatPlugin):
|
|||||||
flow_size = getattr(options, 'flow_size', 0)
|
flow_size = getattr(options, 'flow_size', 0)
|
||||||
html = convert_basic(txt, epub_split_size_kb=flow_size)
|
html = convert_basic(txt, epub_split_size_kb=flow_size)
|
||||||
|
|
||||||
if options.formatting_type == 'heuristic':
|
|
||||||
setattr(options, 'enable_heuristics', True)
|
|
||||||
setattr(options, 'markup_chapter_headings', True)
|
|
||||||
setattr(options, 'italicize_common_cases', True)
|
|
||||||
setattr(options, 'fix_indents', True)
|
|
||||||
setattr(options, 'delete_blank_paragraphs', True)
|
|
||||||
setattr(options, 'format_scene_breaks', True)
|
|
||||||
setattr(options, 'dehyphenate', True)
|
|
||||||
|
|
||||||
from calibre.customize.ui import plugin_for_input_format
|
from calibre.customize.ui import plugin_for_input_format
|
||||||
html_input = plugin_for_input_format('html')
|
html_input = plugin_for_input_format('html')
|
||||||
for opt in html_input.options:
|
for opt in html_input.options:
|
||||||
|
@ -175,9 +175,9 @@ def detect_formatting_type(txt):
|
|||||||
# Block quote.
|
# Block quote.
|
||||||
textile_count += len(re.findall(r'(?mu)^bq\.', txt))
|
textile_count += len(re.findall(r'(?mu)^bq\.', txt))
|
||||||
# Images
|
# Images
|
||||||
textile_count += len(re.findall(r'\![^\s]+(:[^\s]+)*', txt))
|
textile_count += len(re.findall(r'\![^\s]+(?=.*?/)(:[^\s]+)*', txt))
|
||||||
# Links
|
# Links
|
||||||
textile_count += len(re.findall(r'"(\(.+?\))*[^\(]+?(\(.+?\))*":[^\s]+', txt))
|
textile_count += len(re.findall(r'"(?=".*?\()(\(.+?\))*[^\(]+?(\(.+?\))*":[^\s]+', txt))
|
||||||
|
|
||||||
if markdown_count > 5 or textile_count > 5:
|
if markdown_count > 5 or textile_count > 5:
|
||||||
if markdown_count > textile_count:
|
if markdown_count > textile_count:
|
||||||
|
@ -8,11 +8,12 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os
|
import os
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QInputDialog, QPixmap, QMenu
|
from PyQt4.Qt import QPixmap, QMenu
|
||||||
|
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog, choose_files, \
|
from calibre.gui2 import error_dialog, choose_files, \
|
||||||
choose_dir, warning_dialog, info_dialog
|
choose_dir, warning_dialog, info_dialog
|
||||||
|
from calibre.gui2.dialogs.add_empty_book import AddEmptyBookDialog
|
||||||
from calibre.gui2.widgets import IMAGE_EXTENSIONS
|
from calibre.gui2.widgets import IMAGE_EXTENSIONS
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
@ -42,7 +43,7 @@ class AddAction(InterfaceAction):
|
|||||||
'ebook file is a different book)'), self.add_recursive_multiple)
|
'ebook file is a different book)'), self.add_recursive_multiple)
|
||||||
self.add_menu.addSeparator()
|
self.add_menu.addSeparator()
|
||||||
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
||||||
'formats)'), self.add_empty)
|
'formats)'), self.add_empty, _('Shift+Ctrl+E'))
|
||||||
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
|
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
|
||||||
self.qaction.setMenu(self.add_menu)
|
self.qaction.setMenu(self.add_menu)
|
||||||
self.qaction.triggered.connect(self.add_books)
|
self.qaction.triggered.connect(self.add_books)
|
||||||
@ -83,12 +84,21 @@ class AddAction(InterfaceAction):
|
|||||||
Add an empty book item to the library. This does not import any formats
|
Add an empty book item to the library. This does not import any formats
|
||||||
from a book file.
|
from a book file.
|
||||||
'''
|
'''
|
||||||
num, ok = QInputDialog.getInt(self.gui, _('How many empty books?'),
|
author = None
|
||||||
_('How many empty books should be added?'), 1, 1, 100)
|
index = self.gui.library_view.currentIndex()
|
||||||
if ok:
|
if index.isValid():
|
||||||
|
raw = index.model().db.authors(index.row())
|
||||||
|
if raw:
|
||||||
|
authors = [a.strip().replace('|', ',') for a in raw.split(',')]
|
||||||
|
if authors:
|
||||||
|
author = authors[0]
|
||||||
|
dlg = AddEmptyBookDialog(self.gui, self.gui.library_view.model().db, author)
|
||||||
|
if dlg.exec_() == dlg.Accepted:
|
||||||
|
num = dlg.qty_to_add
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
for x in xrange(num):
|
for x in xrange(num):
|
||||||
self.gui.library_view.model().db.import_book(MetaInformation(None), [])
|
mi = MetaInformation(_('Unknown'), dlg.selected_authors)
|
||||||
|
self.gui.library_view.model().db.import_book(mi, [])
|
||||||
self.gui.library_view.model().books_added(num)
|
self.gui.library_view.model().books_added(num)
|
||||||
|
|
||||||
def add_isbns(self, books, add_tags=[]):
|
def add_isbns(self, books, add_tags=[]):
|
||||||
|
@ -32,7 +32,7 @@ class LibraryUsageStats(object): # {{{
|
|||||||
locs = list(self.stats.keys())
|
locs = list(self.stats.keys())
|
||||||
locs.sort(cmp=lambda x, y: cmp(self.stats[x], self.stats[y]),
|
locs.sort(cmp=lambda x, y: cmp(self.stats[x], self.stats[y]),
|
||||||
reverse=True)
|
reverse=True)
|
||||||
for key in locs[15:]:
|
for key in locs[25:]:
|
||||||
self.stats.pop(key)
|
self.stats.pop(key)
|
||||||
gprefs.set('library_usage_stats', self.stats)
|
gprefs.set('library_usage_stats', self.stats)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Sections to include in catalog. All catalogs include 'Books by Author'.</string>
|
<string>Sections to include in catalog.</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Included sections</string>
|
<string>Included sections</string>
|
||||||
@ -79,13 +79,13 @@
|
|||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QCheckBox" name="generate_authors">
|
<widget class="QCheckBox" name="generate_authors">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Books by Author</string>
|
<string>Books by Author</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="checked">
|
<property name="checked">
|
||||||
<bool>true</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -379,7 +379,8 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
|
|||||||
w = bulk_widgets[type](db, col, parent)
|
w = bulk_widgets[type](db, col, parent)
|
||||||
else:
|
else:
|
||||||
w = widgets[type](db, col, parent)
|
w = widgets[type](db, col, parent)
|
||||||
w.initialize(book_id)
|
if book_id is not None:
|
||||||
|
w.initialize(book_id)
|
||||||
return w
|
return w
|
||||||
x = db.custom_column_num_map
|
x = db.custom_column_num_map
|
||||||
cols = list(x)
|
cols = list(x)
|
||||||
|
85
src/calibre/gui2/dialogs/add_empty_book.py
Normal file
85
src/calibre/gui2/dialogs/add_empty_book.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
|
||||||
|
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
|
||||||
|
QApplication, QSpinBox, QToolButton, QIcon
|
||||||
|
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
||||||
|
from calibre.gui2.widgets import CompleteComboBox
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
|
||||||
|
class AddEmptyBookDialog(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent, db, author):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
self.setWindowTitle(_('How many empty books?'))
|
||||||
|
|
||||||
|
self._layout = QGridLayout(self)
|
||||||
|
self.setLayout(self._layout)
|
||||||
|
|
||||||
|
self.qty_label = QLabel(_('How many empty books should be added?'))
|
||||||
|
self._layout.addWidget(self.qty_label, 0, 0, 1, 2)
|
||||||
|
|
||||||
|
self.qty_spinbox = QSpinBox(self)
|
||||||
|
self.qty_spinbox.setRange(1, 10000)
|
||||||
|
self.qty_spinbox.setValue(1)
|
||||||
|
self._layout.addWidget(self.qty_spinbox, 1, 0, 1, 2)
|
||||||
|
|
||||||
|
self.author_label = QLabel(_('Set the author of the new books to:'))
|
||||||
|
self._layout.addWidget(self.author_label, 2, 0, 1, 2)
|
||||||
|
|
||||||
|
self.authors_combo = CompleteComboBox(self)
|
||||||
|
self.authors_combo.setSizeAdjustPolicy(
|
||||||
|
self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
|
||||||
|
self.authors_combo.setEditable(True)
|
||||||
|
self._layout.addWidget(self.authors_combo, 3, 0, 1, 1)
|
||||||
|
self.initialize_authors(db, author)
|
||||||
|
|
||||||
|
self.clear_button = QToolButton(self)
|
||||||
|
self.clear_button.setIcon(QIcon(I('trash.png')))
|
||||||
|
self.clear_button.setToolTip(_('Reset author to Unknown'))
|
||||||
|
self.clear_button.clicked.connect(self.reset_author)
|
||||||
|
self._layout.addWidget(self.clear_button, 3, 1, 1, 1)
|
||||||
|
|
||||||
|
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
button_box.accepted.connect(self.accept)
|
||||||
|
button_box.rejected.connect(self.reject)
|
||||||
|
self._layout.addWidget(button_box)
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
|
def reset_author(self, *args):
|
||||||
|
self.authors_combo.setEditText(_('Unknown'))
|
||||||
|
|
||||||
|
def initialize_authors(self, db, author):
|
||||||
|
all_authors = db.all_authors()
|
||||||
|
all_authors.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
for i in all_authors:
|
||||||
|
id, name = i
|
||||||
|
name = [name.strip().replace('|', ',') for n in name.split(',')]
|
||||||
|
self.authors_combo.addItem(authors_to_string(name))
|
||||||
|
|
||||||
|
au = author
|
||||||
|
if not au:
|
||||||
|
au = _('Unknown')
|
||||||
|
self.authors_combo.setEditText(au.replace('|', ','))
|
||||||
|
|
||||||
|
self.authors_combo.set_separator('&')
|
||||||
|
self.authors_combo.set_space_before_sep(True)
|
||||||
|
self.authors_combo.update_items_cache(db.all_author_names())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def qty_to_add(self):
|
||||||
|
return self.qty_spinbox.value()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected_authors(self):
|
||||||
|
return string_to_authors(unicode(self.authors_combo.text()))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QApplication([])
|
||||||
|
d = AddEmptyBookDialog()
|
||||||
|
d.exec_()
|
@ -724,7 +724,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
au = _('Unknown')
|
au = _('Unknown')
|
||||||
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
|
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
|
||||||
self.authors.setEditText(au)
|
self.authors.setEditText(au)
|
||||||
|
|
||||||
self.authors.set_separator('&')
|
self.authors.set_separator('&')
|
||||||
self.authors.set_space_before_sep(True)
|
self.authors.set_space_before_sep(True)
|
||||||
self.authors.update_items_cache(self.db.all_author_names())
|
self.authors.update_items_cache(self.db.all_author_names())
|
||||||
@ -775,7 +775,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.original_tags = unicode(self.tags.text())
|
self.original_tags = unicode(self.tags.text())
|
||||||
else:
|
else:
|
||||||
self.tags.setText(self.original_tags)
|
self.tags.setText(self.original_tags)
|
||||||
d = TagEditor(self, self.db, self.row)
|
d = TagEditor(self, self.db, self.id)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
tag_string = ', '.join(d.tags)
|
tag_string = ', '.join(d.tags)
|
||||||
|
@ -10,13 +10,13 @@ from calibre.utils.icu import sort_key
|
|||||||
|
|
||||||
class TagEditor(QDialog, Ui_TagEditor):
|
class TagEditor(QDialog, Ui_TagEditor):
|
||||||
|
|
||||||
def __init__(self, window, db, index=None):
|
def __init__(self, window, db, id_=None):
|
||||||
QDialog.__init__(self, window)
|
QDialog.__init__(self, window)
|
||||||
Ui_TagEditor.__init__(self)
|
Ui_TagEditor.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
self.db = db
|
self.db = db
|
||||||
self.index = index
|
self.index = db.row(id_)
|
||||||
if self.index is not None:
|
if self.index is not None:
|
||||||
tags = self.db.tags(self.index)
|
tags = self.db.tags(self.index)
|
||||||
else:
|
else:
|
||||||
|
@ -356,6 +356,13 @@ class %(classname)s(%(base_class)s):
|
|||||||
self.populate_options(AutomaticNewsRecipe)
|
self.populate_options(AutomaticNewsRecipe)
|
||||||
self.source_code.setText('')
|
self.source_code.setText('')
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
if question_dialog(self, _('Are you sure?'),
|
||||||
|
_('You will lose any unsaved changes. To save your'
|
||||||
|
' changes, click the Add/Update recipe button.'
|
||||||
|
' Continue?'), show_copy_button=False):
|
||||||
|
ResizableDialog.reject(self)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
from calibre.gui2 import is_ok_to_use_qt
|
||||||
is_ok_to_use_qt()
|
is_ok_to_use_qt()
|
||||||
|
1014
src/calibre/gui2/metadata/basic_widgets.py
Normal file
1014
src/calibre/gui2/metadata/basic_widgets.py
Normal file
File diff suppressed because it is too large
Load Diff
474
src/calibre/gui2/metadata/single.py
Normal file
474
src/calibre/gui2/metadata/single.py
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import os
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from PyQt4.Qt import Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, \
|
||||||
|
QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, \
|
||||||
|
QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem, \
|
||||||
|
QSizePolicy
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
||||||
|
from calibre.gui2 import ResizableDialog, error_dialog, gprefs
|
||||||
|
from calibre.gui2.metadata.basic_widgets import TitleEdit, AuthorsEdit, \
|
||||||
|
AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, ISBNEdit, \
|
||||||
|
RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, \
|
||||||
|
BuddyLabel, DateEdit, PubdateEdit
|
||||||
|
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||||
|
from calibre.utils.config import tweaks
|
||||||
|
|
||||||
|
class MetadataSingleDialog(ResizableDialog):
|
||||||
|
|
||||||
|
view_format = pyqtSignal(object)
|
||||||
|
|
||||||
|
def __init__(self, db, parent=None):
|
||||||
|
self.db = db
|
||||||
|
self.changed = set([])
|
||||||
|
ResizableDialog.__init__(self, parent)
|
||||||
|
|
||||||
|
def setupUi(self, *args): # {{{
|
||||||
|
self.resize(990, 650)
|
||||||
|
|
||||||
|
self.button_box = QDialogButtonBox(
|
||||||
|
QDialogButtonBox.Ok|QDialogButtonBox.Cancel, Qt.Horizontal,
|
||||||
|
self)
|
||||||
|
self.button_box.accepted.connect(self.accept)
|
||||||
|
self.button_box.rejected.connect(self.reject)
|
||||||
|
self.next_button = QPushButton(QIcon(I('forward.png')), _('Next'),
|
||||||
|
self)
|
||||||
|
self.next_button.clicked.connect(partial(self.do_one, delta=1))
|
||||||
|
self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'),
|
||||||
|
self)
|
||||||
|
self.button_box.addButton(self.prev_button, self.button_box.ActionRole)
|
||||||
|
self.button_box.addButton(self.next_button, self.button_box.ActionRole)
|
||||||
|
self.prev_button.clicked.connect(partial(self.do_one, delta=-1))
|
||||||
|
|
||||||
|
self.scroll_area = QScrollArea(self)
|
||||||
|
self.scroll_area.setFrameShape(QScrollArea.NoFrame)
|
||||||
|
self.scroll_area.setWidgetResizable(True)
|
||||||
|
self.central_widget = QTabWidget(self)
|
||||||
|
self.scroll_area.setWidget(self.central_widget)
|
||||||
|
|
||||||
|
self.l = QVBoxLayout(self)
|
||||||
|
self.setLayout(self.l)
|
||||||
|
self.l.setMargin(0)
|
||||||
|
self.l.addWidget(self.scroll_area)
|
||||||
|
self.l.addWidget(self.button_box)
|
||||||
|
|
||||||
|
self.setWindowIcon(QIcon(I('edit_input.png')))
|
||||||
|
self.setWindowTitle(_('Edit Meta Information'))
|
||||||
|
|
||||||
|
self.create_basic_metadata_widgets()
|
||||||
|
|
||||||
|
if len(self.db.custom_column_label_map) == 0:
|
||||||
|
self.central_widget.tabBar().setVisible(False)
|
||||||
|
else:
|
||||||
|
self.create_custom_metadata_widgets()
|
||||||
|
|
||||||
|
|
||||||
|
self.do_layout()
|
||||||
|
geom = gprefs.get('metasingle_window_geometry3', None)
|
||||||
|
if geom is not None:
|
||||||
|
self.restoreGeometry(bytes(geom))
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def create_basic_metadata_widgets(self): # {{{
|
||||||
|
self.basic_metadata_widgets = []
|
||||||
|
|
||||||
|
self.title = TitleEdit(self)
|
||||||
|
self.title.textChanged.connect(self.update_window_title)
|
||||||
|
self.deduce_title_sort_button = QToolButton(self)
|
||||||
|
self.deduce_title_sort_button.setToolTip(
|
||||||
|
_('Automatically create the title sort entry based on the current '
|
||||||
|
'title entry.\nUsing this button to create title sort will '
|
||||||
|
'change title sort from red to green.'))
|
||||||
|
self.deduce_title_sort_button.setWhatsThis(
|
||||||
|
self.deduce_title_sort_button.toolTip())
|
||||||
|
self.title_sort = TitleSortEdit(self, self.title,
|
||||||
|
self.deduce_title_sort_button)
|
||||||
|
self.basic_metadata_widgets.extend([self.title, self.title_sort])
|
||||||
|
|
||||||
|
self.authors = AuthorsEdit(self)
|
||||||
|
self.deduce_author_sort_button = QToolButton(self)
|
||||||
|
self.deduce_author_sort_button.setToolTip(_(
|
||||||
|
'Automatically create the author sort entry based on the current'
|
||||||
|
' author entry.\n'
|
||||||
|
'Using this button to create author sort will change author sort from'
|
||||||
|
' red to green.'))
|
||||||
|
self.author_sort = AuthorSortEdit(self, self.authors,
|
||||||
|
self.deduce_author_sort_button, db)
|
||||||
|
self.basic_metadata_widgets.extend([self.authors, self.author_sort])
|
||||||
|
|
||||||
|
self.swap_title_author_button = QToolButton(self)
|
||||||
|
self.swap_title_author_button.setIcon(QIcon(I('swap.png')))
|
||||||
|
self.swap_title_author_button.setToolTip(_(
|
||||||
|
'Swap the author and title'))
|
||||||
|
self.swap_title_author_button.clicked.connect(self.swap_title_author)
|
||||||
|
|
||||||
|
self.series = SeriesEdit(self)
|
||||||
|
self.remove_unused_series_button = QToolButton(self)
|
||||||
|
self.remove_unused_series_button.setToolTip(
|
||||||
|
_('Remove unused series (Series that have no books)') )
|
||||||
|
self.remove_unused_series_button.clicked.connect(self.remove_unused_series)
|
||||||
|
self.series_index = SeriesIndexEdit(self, self.series)
|
||||||
|
self.basic_metadata_widgets.extend([self.series, self.series_index])
|
||||||
|
|
||||||
|
self.formats_manager = FormatsManager(self)
|
||||||
|
self.basic_metadata_widgets.append(self.formats_manager)
|
||||||
|
self.formats_manager.metadata_from_format_button.clicked.connect(
|
||||||
|
self.metadata_from_format)
|
||||||
|
self.formats_manager.cover_from_format_button.clicked.connect(
|
||||||
|
self.cover_from_format)
|
||||||
|
self.cover = Cover(self)
|
||||||
|
self.basic_metadata_widgets.append(self.cover)
|
||||||
|
|
||||||
|
self.comments = CommentsEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.comments)
|
||||||
|
|
||||||
|
self.rating = RatingEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.rating)
|
||||||
|
|
||||||
|
self.tags = TagsEdit(self)
|
||||||
|
self.tags_editor_button = QToolButton(self)
|
||||||
|
self.tags_editor_button.setToolTip(_('Open Tag Editor'))
|
||||||
|
self.tags_editor_button.setIcon(QIcon(I('chapters.png')))
|
||||||
|
self.tags_editor_button.clicked.connect(self.tags_editor)
|
||||||
|
self.basic_metadata_widgets.append(self.tags)
|
||||||
|
|
||||||
|
self.isbn = ISBNEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.isbn)
|
||||||
|
|
||||||
|
self.publisher = PublisherEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.publisher)
|
||||||
|
|
||||||
|
self.timestamp = DateEdit(self)
|
||||||
|
self.pubdate = PubdateEdit(self)
|
||||||
|
self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])
|
||||||
|
|
||||||
|
self.fetch_metadata_button = QPushButton(
|
||||||
|
_('&Fetch metadata from server'), self)
|
||||||
|
self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
|
||||||
|
font = self.fmb_font = QFont()
|
||||||
|
font.setBold(True)
|
||||||
|
self.fetch_metadata_button.setFont(font)
|
||||||
|
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def create_custom_metadata_widgets(self): # {{{
|
||||||
|
self.custom_metadata_widgets_parent = w = QWidget(self)
|
||||||
|
layout = QGridLayout()
|
||||||
|
w.setLayout(layout)
|
||||||
|
self.custom_metadata_widgets, self.__cc_spacers = \
|
||||||
|
populate_metadata_page(layout, self.db, None, parent=w, bulk=False,
|
||||||
|
two_column=tweaks['metadata_single_use_2_cols_for_custom_fields'])
|
||||||
|
self.__custom_col_layouts = [layout]
|
||||||
|
ans = self.custom_metadata_widgets
|
||||||
|
for i in range(len(ans)-1):
|
||||||
|
if len(ans[i+1].widgets) == 2:
|
||||||
|
w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[1])
|
||||||
|
else:
|
||||||
|
w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[0])
|
||||||
|
for c in range(2, len(ans[i].widgets), 2):
|
||||||
|
w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1])
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def do_layout(self): # {{{
|
||||||
|
self.central_widget.clear()
|
||||||
|
self.tabs = []
|
||||||
|
self.labels = []
|
||||||
|
self.tabs.append(QWidget(self))
|
||||||
|
self.central_widget.addTab(self.tabs[0], _("&Basic metadata"))
|
||||||
|
self.tabs[0].l = l = QVBoxLayout()
|
||||||
|
self.tabs[0].tl = tl = QGridLayout()
|
||||||
|
self.tabs[0].setLayout(l)
|
||||||
|
w = getattr(self, 'custom_metadata_widgets_parent', None)
|
||||||
|
if w is not None:
|
||||||
|
self.tabs.append(w)
|
||||||
|
self.central_widget.addTab(w, _('&Custom metadata'))
|
||||||
|
l.addLayout(tl)
|
||||||
|
l.addItem(QSpacerItem(10, 15, QSizePolicy.Expanding,
|
||||||
|
QSizePolicy.Fixed))
|
||||||
|
|
||||||
|
sto = QWidget.setTabOrder
|
||||||
|
sto(self.button_box, self.fetch_metadata_button)
|
||||||
|
sto(self.fetch_metadata_button, self.title)
|
||||||
|
|
||||||
|
def create_row(row, one, two, three, col=1, icon='forward.png'):
|
||||||
|
ql = BuddyLabel(one)
|
||||||
|
tl.addWidget(ql, row, col+0, 1, 1)
|
||||||
|
self.labels.append(ql)
|
||||||
|
tl.addWidget(one, row, col+1, 1, 1)
|
||||||
|
if two is not None:
|
||||||
|
tl.addWidget(two, row, col+2, 1, 1)
|
||||||
|
two.setIcon(QIcon(I(icon)))
|
||||||
|
ql = BuddyLabel(three)
|
||||||
|
tl.addWidget(ql, row, col+3, 1, 1)
|
||||||
|
self.labels.append(ql)
|
||||||
|
tl.addWidget(three, row, col+4, 1, 1)
|
||||||
|
sto(one, two)
|
||||||
|
sto(two, three)
|
||||||
|
|
||||||
|
tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1)
|
||||||
|
|
||||||
|
create_row(0, self.title, self.deduce_title_sort_button, self.title_sort)
|
||||||
|
sto(self.title_sort, self.authors)
|
||||||
|
create_row(1, self.authors, self.deduce_author_sort_button, self.author_sort)
|
||||||
|
sto(self.author_sort, self.series)
|
||||||
|
create_row(2, self.series, self.remove_unused_series_button,
|
||||||
|
self.series_index, icon='trash.png')
|
||||||
|
sto(self.series_index, self.swap_title_author_button)
|
||||||
|
|
||||||
|
tl.addWidget(self.formats_manager, 0, 6, 3, 1)
|
||||||
|
|
||||||
|
self.splitter = QSplitter(Qt.Horizontal, self)
|
||||||
|
self.splitter.addWidget(self.cover)
|
||||||
|
l.addWidget(self.splitter)
|
||||||
|
self.tabs[0].gb = gb = QGroupBox(_('Change cover'), self)
|
||||||
|
gb.l = l = QGridLayout()
|
||||||
|
gb.setLayout(l)
|
||||||
|
sto(self.swap_title_author_button, self.cover.buttons[0])
|
||||||
|
for i, b in enumerate(self.cover.buttons[:3]):
|
||||||
|
l.addWidget(b, 0, i, 1, 1)
|
||||||
|
sto(b, self.cover.buttons[i+1])
|
||||||
|
gb.hl = QHBoxLayout()
|
||||||
|
for b in self.cover.buttons[3:]:
|
||||||
|
gb.hl.addWidget(b)
|
||||||
|
sto(self.cover.buttons[-2], self.cover.buttons[-1])
|
||||||
|
l.addLayout(gb.hl, 1, 0, 1, 3)
|
||||||
|
self.tabs[0].middle = w = QWidget(self)
|
||||||
|
w.l = l = QGridLayout()
|
||||||
|
w.setLayout(w.l)
|
||||||
|
l.setMargin(0)
|
||||||
|
self.splitter.addWidget(w)
|
||||||
|
def create_row2(row, widget, button=None):
|
||||||
|
row += 1
|
||||||
|
ql = BuddyLabel(widget)
|
||||||
|
l.addWidget(ql, row, 0, 1, 1)
|
||||||
|
l.addWidget(widget, row, 1, 1, 2 if button is None else 1)
|
||||||
|
if button is not None:
|
||||||
|
l.addWidget(button, row, 2, 1, 1)
|
||||||
|
if button is not None:
|
||||||
|
sto(widget, button)
|
||||||
|
|
||||||
|
l.addWidget(gb, 0, 0, 1, 3)
|
||||||
|
self.tabs[0].spc_one = QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||||
|
QSizePolicy.Expanding)
|
||||||
|
l.addItem(self.tabs[0].spc_one, 1, 0, 1, 3)
|
||||||
|
sto(self.cover.buttons[-1], self.rating)
|
||||||
|
create_row2(1, self.rating)
|
||||||
|
sto(self.rating, self.tags)
|
||||||
|
create_row2(2, self.tags, self.tags_editor_button)
|
||||||
|
sto(self.tags_editor_button, self.isbn)
|
||||||
|
create_row2(3, self.isbn)
|
||||||
|
sto(self.isbn, self.timestamp)
|
||||||
|
create_row2(4, self.timestamp, self.timestamp.clear_button)
|
||||||
|
sto(self.timestamp.clear_button, self.pubdate)
|
||||||
|
create_row2(5, self.pubdate, self.pubdate.clear_button)
|
||||||
|
sto(self.pubdate.clear_button, self.publisher)
|
||||||
|
create_row2(6, self.publisher)
|
||||||
|
self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||||
|
QSizePolicy.Expanding)
|
||||||
|
l.addItem(self.tabs[0].spc_two, 8, 0, 1, 3)
|
||||||
|
l.addWidget(self.fetch_metadata_button, 9, 0, 1, 3)
|
||||||
|
|
||||||
|
self.tabs[0].gb2 = gb = QGroupBox(_('Co&mments'), self)
|
||||||
|
gb.l = l = QVBoxLayout()
|
||||||
|
gb.setLayout(l)
|
||||||
|
l.addWidget(self.comments)
|
||||||
|
self.splitter.addWidget(gb)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def __call__(self, id_):
|
||||||
|
self.book_id = id_
|
||||||
|
for widget in self.basic_metadata_widgets:
|
||||||
|
widget.initialize(self.db, id_)
|
||||||
|
for widget in self.custom_metadata_widgets:
|
||||||
|
widget.initialize(id_)
|
||||||
|
# Commented out as it doesn't play nice with Next, Prev buttons
|
||||||
|
#self.fetch_metadata_button.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
|
|
||||||
|
def update_window_title(self, *args):
|
||||||
|
title = self.title.current_val
|
||||||
|
if len(title) > 50:
|
||||||
|
title = title[:50] + u'\u2026'
|
||||||
|
self.setWindowTitle(_('Edit Meta Information') + ' - ' +
|
||||||
|
title)
|
||||||
|
|
||||||
|
def swap_title_author(self, *args):
|
||||||
|
title = self.title.current_val
|
||||||
|
self.title.current_val = authors_to_string(self.authors.current_val)
|
||||||
|
self.authors.current_val = string_to_authors(title)
|
||||||
|
self.title_sort.auto_generate()
|
||||||
|
self.author_sort.auto_generate()
|
||||||
|
|
||||||
|
def remove_unused_series(self, *args):
|
||||||
|
self.db.remove_unused_series()
|
||||||
|
idx = self.series.current_val
|
||||||
|
self.series.clear()
|
||||||
|
self.series.initialize(self.db, self.book_id)
|
||||||
|
if idx:
|
||||||
|
for i in range(self.series.count()):
|
||||||
|
if unicode(self.series.itemText(i)) == idx:
|
||||||
|
self.series.setCurrentIndex(i)
|
||||||
|
break
|
||||||
|
|
||||||
|
def tags_editor(self, *args):
|
||||||
|
self.tags.edit(self.db, self.book_id)
|
||||||
|
|
||||||
|
def metadata_from_format(self, *args):
|
||||||
|
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
|
||||||
|
self.book_id)
|
||||||
|
if mi is not None:
|
||||||
|
self.update_from_mi(mi)
|
||||||
|
|
||||||
|
def cover_from_format(self, *args):
|
||||||
|
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
|
||||||
|
self.book_id)
|
||||||
|
if mi is None:
|
||||||
|
return
|
||||||
|
cdata = None
|
||||||
|
if mi.cover and os.access(mi.cover, os.R_OK):
|
||||||
|
cdata = open(mi.cover).read()
|
||||||
|
elif mi.cover_data[1] is not None:
|
||||||
|
cdata = mi.cover_data[1]
|
||||||
|
if cdata is None:
|
||||||
|
error_dialog(self, _('Could not read cover'),
|
||||||
|
_('Could not read cover from %s format')%ext).exec_()
|
||||||
|
return
|
||||||
|
orig = self.cover.current_val
|
||||||
|
self.cover.current_val = cdata
|
||||||
|
if self.cover.current_val is None:
|
||||||
|
self.cover.current_val = orig
|
||||||
|
return error_dialog(self, _('Could not read cover'),
|
||||||
|
_('The cover in the %s format is invalid')%ext,
|
||||||
|
show=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
def update_from_mi(self, mi):
|
||||||
|
if not mi.is_null('title'):
|
||||||
|
self.title.current_val = mi.title
|
||||||
|
if not mi.is_null('authors'):
|
||||||
|
self.authors.current_val = mi.authors
|
||||||
|
if not mi.is_null('author_sort'):
|
||||||
|
self.author_sort.current_val = mi.author_sort
|
||||||
|
if not mi.is_null('rating'):
|
||||||
|
try:
|
||||||
|
self.rating.current_val = mi.rating
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not mi.is_null('publisher'):
|
||||||
|
self.publisher.current_val = mi.publisher
|
||||||
|
if not mi.is_null('tags'):
|
||||||
|
self.tags.current_val = mi.tags
|
||||||
|
if not mi.is_null('isbn'):
|
||||||
|
self.isbn.current_val = mi.isbn
|
||||||
|
if not mi.is_null('pubdate'):
|
||||||
|
self.pubdate.current_val = mi.pubdate
|
||||||
|
if not mi.is_null('series') and mi.series.strip():
|
||||||
|
self.series.current_val = mi.series
|
||||||
|
if mi.series_index is not None:
|
||||||
|
self.series_index.current_val = float(mi.series_index)
|
||||||
|
if mi.comments and mi.comments.strip():
|
||||||
|
self.comments.current_val = mi.comments
|
||||||
|
|
||||||
|
def fetch_metadata(self, *args):
|
||||||
|
pass # TODO: fetch metadata
|
||||||
|
|
||||||
|
def apply_changes(self):
|
||||||
|
self.changed.add(self.book_id)
|
||||||
|
for widget in self.basic_metadata_widgets:
|
||||||
|
try:
|
||||||
|
if not widget.commit(self.db, self.book_id):
|
||||||
|
return False
|
||||||
|
except IOError, err:
|
||||||
|
if err.errno == 13: # Permission denied
|
||||||
|
import traceback
|
||||||
|
fname = err.filename if err.filename else 'file'
|
||||||
|
error_dialog(self, _('Permission denied'),
|
||||||
|
_('Could not open %s. Is it being used by another'
|
||||||
|
' program?')%fname, det_msg=traceback.format_exc(),
|
||||||
|
show=True)
|
||||||
|
return False
|
||||||
|
raise
|
||||||
|
for widget in getattr(self, 'custom_metadata_widgets', []):
|
||||||
|
widget.commit(self.book_id)
|
||||||
|
|
||||||
|
self.db.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
self.save_state()
|
||||||
|
if not self.apply_changes():
|
||||||
|
return
|
||||||
|
ResizableDialog.accept(self)
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
self.save_state()
|
||||||
|
ResizableDialog.reject(self)
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
gprefs['metasingle_window_geometry3'] = bytearray(self.saveGeometry())
|
||||||
|
|
||||||
|
def start(self, row_list, current_row, view_slot=None):
|
||||||
|
self.row_list = row_list
|
||||||
|
self.current_row = current_row
|
||||||
|
if view_slot is not None:
|
||||||
|
self.view_format.connect(view_slot)
|
||||||
|
self.do_one()
|
||||||
|
ret = self.exec_()
|
||||||
|
self.break_cycles()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def do_one(self, delta=0):
|
||||||
|
self.current_row += delta
|
||||||
|
prev = next_ = None
|
||||||
|
if self.current_row > 0:
|
||||||
|
prev = self.db.title(self.row_list[self.current_row-1])
|
||||||
|
if self.current_row < len(self.row_list) - 1:
|
||||||
|
next_ = self.db.title(self.row_list[self.current_row+1])
|
||||||
|
|
||||||
|
if next_ is not None:
|
||||||
|
tip = _('Save changes and edit the metadata of %s')%next_
|
||||||
|
self.next_button.setToolTip(tip)
|
||||||
|
self.next_button.setVisible(next_ is not None)
|
||||||
|
if prev is not None:
|
||||||
|
tip = _('Save changes and edit the metadata of %s')%prev
|
||||||
|
self.prev_button.setToolTip(tip)
|
||||||
|
self.prev_button.setVisible(prev is not None)
|
||||||
|
self(self.db.id(self.row_list[self.current_row]))
|
||||||
|
|
||||||
|
def break_cycles(self):
|
||||||
|
# Break any reference cycles that could prevent python
|
||||||
|
# from garbage collecting this dialog
|
||||||
|
def disconnect(signal):
|
||||||
|
try:
|
||||||
|
signal.disconnect()
|
||||||
|
except:
|
||||||
|
pass # Fails if view format was never connected
|
||||||
|
disconnect(self.view_format)
|
||||||
|
for b in ('next_button', 'prev_button'):
|
||||||
|
x = getattr(self, b, None)
|
||||||
|
if x is not None:
|
||||||
|
disconnect(x.clicked)
|
||||||
|
|
||||||
|
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None):
|
||||||
|
d = MetadataSingleDialog(db, parent)
|
||||||
|
d.start(row_list, current_row, view_slot=view_slot)
|
||||||
|
return d.changed
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from PyQt4.Qt import QApplication
|
||||||
|
app = QApplication([])
|
||||||
|
from calibre.library import db
|
||||||
|
db = db()
|
||||||
|
row_list = list(range(len(db.data)))
|
||||||
|
edit_metadata(db, row_list, 0)
|
||||||
|
|
@ -449,7 +449,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
|
|
||||||
def set_window_title(self):
|
def set_window_title(self):
|
||||||
self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name())
|
self.setWindowTitle(__appname__ + u' - || %s ||'%self.iactions['Choose Library'].library_name())
|
||||||
|
|
||||||
def location_selected(self, location):
|
def location_selected(self, location):
|
||||||
'''
|
'''
|
||||||
|
@ -123,6 +123,8 @@ IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp']
|
|||||||
|
|
||||||
class FormatList(QListWidget):
|
class FormatList(QListWidget):
|
||||||
DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS
|
DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS
|
||||||
|
formats_dropped = pyqtSignal(object, object)
|
||||||
|
delete_format = pyqtSignal()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def paths_from_event(cls, event):
|
def paths_from_event(cls, event):
|
||||||
@ -146,15 +148,14 @@ class FormatList(QListWidget):
|
|||||||
def dropEvent(self, event):
|
def dropEvent(self, event):
|
||||||
paths = self.paths_from_event(event)
|
paths = self.paths_from_event(event)
|
||||||
event.setDropAction(Qt.CopyAction)
|
event.setDropAction(Qt.CopyAction)
|
||||||
self.emit(SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'),
|
self.formats_dropped.emit(event, paths)
|
||||||
event, paths)
|
|
||||||
|
|
||||||
def dragMoveEvent(self, event):
|
def dragMoveEvent(self, event):
|
||||||
event.acceptProposedAction()
|
event.acceptProposedAction()
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.key() == Qt.Key_Delete:
|
if event.key() == Qt.Key_Delete:
|
||||||
self.emit(SIGNAL('delete_format()'))
|
self.delete_format.emit()
|
||||||
else:
|
else:
|
||||||
return QListWidget.keyPressEvent(self, event)
|
return QListWidget.keyPressEvent(self, event)
|
||||||
|
|
||||||
@ -162,6 +163,7 @@ class FormatList(QListWidget):
|
|||||||
class ImageView(QWidget):
|
class ImageView(QWidget):
|
||||||
|
|
||||||
BORDER_WIDTH = 1
|
BORDER_WIDTH = 1
|
||||||
|
cover_changed = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
@ -201,8 +203,7 @@ class ImageView(QWidget):
|
|||||||
if not pmap.isNull():
|
if not pmap.isNull():
|
||||||
self.setPixmap(pmap)
|
self.setPixmap(pmap)
|
||||||
event.accept()
|
event.accept()
|
||||||
self.emit(SIGNAL('cover_changed(PyQt_PyObject)'), open(path,
|
self.cover_changed.emit(open(path, 'rb').read())
|
||||||
'rb').read())
|
|
||||||
break
|
break
|
||||||
|
|
||||||
def dragMoveEvent(self, event):
|
def dragMoveEvent(self, event):
|
||||||
@ -271,7 +272,7 @@ class ImageView(QWidget):
|
|||||||
pmap = cb.pixmap(cb.Selection)
|
pmap = cb.pixmap(cb.Selection)
|
||||||
if not pmap.isNull():
|
if not pmap.isNull():
|
||||||
self.setPixmap(pmap)
|
self.setPixmap(pmap)
|
||||||
self.emit(SIGNAL('cover_changed(PyQt_PyObject)'),
|
self.cover_changed.emit(
|
||||||
pixmap_to_data(pmap))
|
pixmap_to_data(pmap))
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ FIELDS = ['all', 'author_sort', 'authors', 'comments',
|
|||||||
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
|
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
|
||||||
'uuid']
|
'uuid']
|
||||||
|
|
||||||
|
|
||||||
#Allowed fields for template
|
#Allowed fields for template
|
||||||
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
|
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
|
||||||
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
|
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
|
||||||
@ -581,7 +580,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
"pipeline to the specified "
|
"pipeline to the specified "
|
||||||
"directory. Useful if you are unsure at which stage "
|
"directory. Useful if you are unsure at which stage "
|
||||||
"of the conversion process a bug is occurring.\n"
|
"of the conversion process a bug is occurring.\n"
|
||||||
"Default: '%default'None\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: ePub, MOBI output formats")),
|
"Applies to: ePub, MOBI output formats")),
|
||||||
Option('--exclude-book-marker',
|
Option('--exclude-book-marker',
|
||||||
default=':',
|
default=':',
|
||||||
@ -605,43 +604,42 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
"Default: '%default'\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: ePub, MOBI output formats")),
|
"Applies to: ePub, MOBI output formats")),
|
||||||
Option('--generate-authors',
|
Option('--generate-authors',
|
||||||
default=True,
|
default=False,
|
||||||
dest='generate_authors',
|
dest='generate_authors',
|
||||||
action = 'store_true',
|
action = 'store_true',
|
||||||
help=_("Include 'Authors' section in catalog."
|
help=_("Include 'Authors' section in catalog.\n"
|
||||||
"This switch is ignored - Books By Author section is always generated."
|
|
||||||
"Default: '%default'\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: ePub, MOBI output formats")),
|
"Applies to: ePub, MOBI output formats")),
|
||||||
Option('--generate-descriptions',
|
Option('--generate-descriptions',
|
||||||
default=True,
|
default=False,
|
||||||
dest='generate_descriptions',
|
dest='generate_descriptions',
|
||||||
action = 'store_true',
|
action = 'store_true',
|
||||||
help=_("Include book descriptions in catalog.\n"
|
help=_("Include 'Descriptions' section in catalog.\n"
|
||||||
"Default: '%default'\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: ePub, MOBI output formats")),
|
"Applies to: ePub, MOBI output formats")),
|
||||||
Option('--generate-genres',
|
Option('--generate-genres',
|
||||||
default=True,
|
default=False,
|
||||||
dest='generate_genres',
|
dest='generate_genres',
|
||||||
action = 'store_true',
|
action = 'store_true',
|
||||||
help=_("Include 'Genres' section in catalog.\n"
|
help=_("Include 'Genres' section in catalog.\n"
|
||||||
"Default: '%default'\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: ePub, MOBI output formats")),
|
"Applies to: ePub, MOBI output formats")),
|
||||||
Option('--generate-titles',
|
Option('--generate-titles',
|
||||||
default=True,
|
default=False,
|
||||||
dest='generate_titles',
|
dest='generate_titles',
|
||||||
action = 'store_true',
|
action = 'store_true',
|
||||||
help=_("Include 'Titles' section in catalog.\n"
|
help=_("Include 'Titles' section in catalog.\n"
|
||||||
"Default: '%default'\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: ePub, MOBI output formats")),
|
"Applies to: ePub, MOBI output formats")),
|
||||||
Option('--generate-series',
|
Option('--generate-series',
|
||||||
default=True,
|
default=False,
|
||||||
dest='generate_series',
|
dest='generate_series',
|
||||||
action = 'store_true',
|
action = 'store_true',
|
||||||
help=_("Include 'Series' section in catalog.\n"
|
help=_("Include 'Series' section in catalog.\n"
|
||||||
"Default: '%default'\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: ePub, MOBI output formats")),
|
"Applies to: ePub, MOBI output formats")),
|
||||||
Option('--generate-recently-added',
|
Option('--generate-recently-added',
|
||||||
default=True,
|
default=False,
|
||||||
dest='generate_recently_added',
|
dest='generate_recently_added',
|
||||||
action = 'store_true',
|
action = 'store_true',
|
||||||
help=_("Include 'Recently Added' section in catalog.\n"
|
help=_("Include 'Recently Added' section in catalog.\n"
|
||||||
@ -976,7 +974,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.__thumbWidth = 0
|
self.__thumbWidth = 0
|
||||||
self.__thumbHeight = 0
|
self.__thumbHeight = 0
|
||||||
self.__title = opts.catalog_title
|
self.__title = opts.catalog_title
|
||||||
self.__totalSteps = 8.0
|
self.__totalSteps = 6.0
|
||||||
self.__useSeriesPrefixInTitlesSection = False
|
self.__useSeriesPrefixInTitlesSection = False
|
||||||
self.__verbose = opts.verbose
|
self.__verbose = opts.verbose
|
||||||
|
|
||||||
@ -1014,17 +1012,21 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
(self.__archive_path, float(cached_thumb_width)))
|
(self.__archive_path, float(cached_thumb_width)))
|
||||||
|
|
||||||
# Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX
|
# Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX
|
||||||
|
incremental_jobs = 0
|
||||||
|
if self.opts.generate_authors:
|
||||||
|
incremental_jobs += 2
|
||||||
if self.opts.generate_titles:
|
if self.opts.generate_titles:
|
||||||
self.__totalSteps += 2
|
incremental_jobs += 2
|
||||||
if self.opts.generate_recently_added:
|
if self.opts.generate_recently_added:
|
||||||
self.__totalSteps += 2
|
incremental_jobs += 2
|
||||||
if self.generateRecentlyRead:
|
if self.generateRecentlyRead:
|
||||||
self.__totalSteps += 2
|
incremental_jobs += 2
|
||||||
if self.opts.generate_series:
|
if self.opts.generate_series:
|
||||||
self.__totalSteps += 2
|
incremental_jobs += 2
|
||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
# +1 thumbs
|
# +1 thumbs
|
||||||
self.__totalSteps += 3
|
incremental_jobs += 3
|
||||||
|
self.__totalSteps += incremental_jobs
|
||||||
|
|
||||||
# Load section list templates
|
# Load section list templates
|
||||||
templates = []
|
templates = []
|
||||||
@ -1358,13 +1360,23 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
self.generateThumbnails()
|
self.generateThumbnails()
|
||||||
self.generateHTMLDescriptions()
|
self.generateHTMLDescriptions()
|
||||||
self.generateHTMLByAuthor()
|
if self.opts.generate_authors:
|
||||||
|
self.generateHTMLByAuthor()
|
||||||
if self.opts.generate_titles:
|
if self.opts.generate_titles:
|
||||||
self.generateHTMLByTitle()
|
self.generateHTMLByTitle()
|
||||||
if self.opts.generate_series:
|
if self.opts.generate_series:
|
||||||
self.generateHTMLBySeries()
|
self.generateHTMLBySeries()
|
||||||
if self.opts.generate_genres:
|
if self.opts.generate_genres:
|
||||||
self.generateHTMLByTags()
|
self.generateHTMLByTags()
|
||||||
|
# If this is the only Section, and there are no genres, bail
|
||||||
|
if self.opts.section_list == ['Genres'] and not self.genres:
|
||||||
|
error_msg = _("No enabled genres found to catalog.\n")
|
||||||
|
if not self.opts.cli_environment:
|
||||||
|
error_msg += "Check 'Excluded genres'\nin E-book options.\n"
|
||||||
|
self.opts.log.error(error_msg)
|
||||||
|
self.error.append(_('No books available to catalog'))
|
||||||
|
self.error.append(error_msg)
|
||||||
|
return False
|
||||||
if self.opts.generate_recently_added:
|
if self.opts.generate_recently_added:
|
||||||
self.generateHTMLByDateAdded()
|
self.generateHTMLByDateAdded()
|
||||||
if self.generateRecentlyRead:
|
if self.generateRecentlyRead:
|
||||||
@ -1372,7 +1384,8 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
|
|
||||||
self.generateOPF()
|
self.generateOPF()
|
||||||
self.generateNCXHeader()
|
self.generateNCXHeader()
|
||||||
self.generateNCXByAuthor("Authors")
|
if self.opts.generate_authors:
|
||||||
|
self.generateNCXByAuthor("Authors")
|
||||||
if self.opts.generate_titles:
|
if self.opts.generate_titles:
|
||||||
self.generateNCXByTitle("Titles")
|
self.generateNCXByTitle("Titles")
|
||||||
if self.opts.generate_series:
|
if self.opts.generate_series:
|
||||||
@ -1508,7 +1521,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
for tag in exclude_tags:
|
for tag in exclude_tags:
|
||||||
search_terms.append("tag:=%s" % tag)
|
search_terms.append("tag:=%s" % tag)
|
||||||
search_phrase = "not (%s)" % " or ".join(search_terms)
|
search_phrase = "not (%s)" % " or ".join(search_terms)
|
||||||
|
|
||||||
# If a list of ids are provided, don't use search_text
|
# If a list of ids are provided, don't use search_text
|
||||||
if self.opts.ids:
|
if self.opts.ids:
|
||||||
self.opts.search_text = search_phrase
|
self.opts.search_text = search_phrase
|
||||||
@ -1879,7 +1891,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Link to author
|
# Link to author
|
||||||
emTag = Tag(soup, "em")
|
emTag = Tag(soup, "em")
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author']))
|
if self.opts.generate_authors:
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author']))
|
||||||
aTag.insert(0, NavigableString(book['author']))
|
aTag.insert(0, NavigableString(book['author']))
|
||||||
emTag.insert(0,aTag)
|
emTag.insert(0,aTag)
|
||||||
pBookTag.insert(ptc, emTag)
|
pBookTag.insert(ptc, emTag)
|
||||||
@ -2149,7 +2162,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
pAuthorTag = Tag(soup, "p")
|
pAuthorTag = Tag(soup, "p")
|
||||||
pAuthorTag['class'] = "author_index"
|
pAuthorTag['class'] = "author_index"
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['name'] = "%s" % self.generateAuthorAnchor(current_author)
|
if self.opts.generate_authors:
|
||||||
|
aTag['name'] = "%s" % self.generateAuthorAnchor(current_author)
|
||||||
aTag.insert(0,NavigableString(current_author))
|
aTag.insert(0,NavigableString(current_author))
|
||||||
pAuthorTag.insert(0,aTag)
|
pAuthorTag.insert(0,aTag)
|
||||||
divTag.insert(dtc,pAuthorTag)
|
divTag.insert(dtc,pAuthorTag)
|
||||||
@ -2276,7 +2290,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Link to author
|
# Link to author
|
||||||
emTag = Tag(soup, "em")
|
emTag = Tag(soup, "em")
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
|
if self.opts.generate_authors:
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
|
||||||
aTag.insert(0, NavigableString(new_entry['author']))
|
aTag.insert(0, NavigableString(new_entry['author']))
|
||||||
emTag.insert(0,aTag)
|
emTag.insert(0,aTag)
|
||||||
pBookTag.insert(ptc, emTag)
|
pBookTag.insert(ptc, emTag)
|
||||||
@ -2425,7 +2440,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Link to author
|
# Link to author
|
||||||
emTag = Tag(soup, "em")
|
emTag = Tag(soup, "em")
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
|
if self.opts.generate_authors:
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
|
||||||
aTag.insert(0, NavigableString(new_entry['author']))
|
aTag.insert(0, NavigableString(new_entry['author']))
|
||||||
emTag.insert(0,aTag)
|
emTag.insert(0,aTag)
|
||||||
pBookTag.insert(ptc, emTag)
|
pBookTag.insert(ptc, emTag)
|
||||||
@ -2473,7 +2489,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Link to author
|
# Link to author
|
||||||
emTag = Tag(soup, "em")
|
emTag = Tag(soup, "em")
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
|
if self.opts.generate_authors:
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
|
||||||
aTag.insert(0, NavigableString(new_entry['author']))
|
aTag.insert(0, NavigableString(new_entry['author']))
|
||||||
emTag.insert(0,aTag)
|
emTag.insert(0,aTag)
|
||||||
pBookTag.insert(ptc, emTag)
|
pBookTag.insert(ptc, emTag)
|
||||||
@ -2692,7 +2709,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
|
|
||||||
# Link to author
|
# Link to author
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
|
if self.opts.generate_authors:
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
|
||||||
self.generateAuthorAnchor(escape(' & '.join(book['authors']))))
|
self.generateAuthorAnchor(escape(' & '.join(book['authors']))))
|
||||||
aTag.insert(0, NavigableString(' & '.join(book['authors'])))
|
aTag.insert(0, NavigableString(' & '.join(book['authors'])))
|
||||||
pBookTag.insert(ptc, aTag)
|
pBookTag.insert(ptc, aTag)
|
||||||
@ -2776,14 +2794,16 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
genre_list.append(tag_list)
|
genre_list.append(tag_list)
|
||||||
|
|
||||||
if self.opts.verbose:
|
if self.opts.verbose:
|
||||||
self.opts.log.info(" Genre summary: %d active genre tags used in generating catalog with %d titles" %
|
if len(genre_list):
|
||||||
|
self.opts.log.info(" Genre summary: %d active genre tags used in generating catalog with %d titles" %
|
||||||
(len(genre_list), len(self.booksByTitle)))
|
(len(genre_list), len(self.booksByTitle)))
|
||||||
|
|
||||||
for genre in genre_list:
|
for genre in genre_list:
|
||||||
for key in genre:
|
for key in genre:
|
||||||
self.opts.log.info(" %s: %d %s" % (self.getFriendlyGenreTag(key),
|
self.opts.log.info(" %s: %d %s" % (self.getFriendlyGenreTag(key),
|
||||||
len(genre[key]),
|
len(genre[key]),
|
||||||
'titles' if len(genre[key]) > 1 else 'title'))
|
'titles' if len(genre[key]) > 1 else 'title'))
|
||||||
|
|
||||||
|
|
||||||
# Write the results
|
# Write the results
|
||||||
# genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...]
|
# genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...]
|
||||||
@ -3074,10 +3094,36 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
textTag.insert(0, NavigableString(self.title))
|
textTag.insert(0, NavigableString(self.title))
|
||||||
navLabelTag.insert(0, textTag)
|
navLabelTag.insert(0, textTag)
|
||||||
navPointTag.insert(0, navLabelTag)
|
navPointTag.insert(0, navLabelTag)
|
||||||
contentTag = Tag(soup, 'content')
|
|
||||||
#contentTag['src'] = "content/book_%d.html" % int(self.booksByTitle[0]['id'])
|
if self.opts.generate_authors:
|
||||||
contentTag['src'] = "content/ByAlphaAuthor.html"
|
contentTag = Tag(soup, 'content')
|
||||||
navPointTag.insert(1, contentTag)
|
contentTag['src'] = "content/ByAlphaAuthor.html"
|
||||||
|
navPointTag.insert(1, contentTag)
|
||||||
|
elif self.opts.generate_titles:
|
||||||
|
contentTag = Tag(soup, 'content')
|
||||||
|
contentTag['src'] = "content/ByAlphaTitle.html"
|
||||||
|
navPointTag.insert(1, contentTag)
|
||||||
|
elif self.opts.generate_series:
|
||||||
|
contentTag = Tag(soup, 'content')
|
||||||
|
contentTag['src'] = "content/BySeries.html"
|
||||||
|
navPointTag.insert(1, contentTag)
|
||||||
|
elif self.opts.generate_genres:
|
||||||
|
contentTag = Tag(soup, 'content')
|
||||||
|
#contentTag['src'] = "content/ByGenres.html"
|
||||||
|
contentTag['src'] = "%s" % self.genres[0]['file']
|
||||||
|
navPointTag.insert(1, contentTag)
|
||||||
|
elif self.opts.generate_recently_added:
|
||||||
|
contentTag = Tag(soup, 'content')
|
||||||
|
contentTag['src'] = "content/ByDateAdded.html"
|
||||||
|
navPointTag.insert(1, contentTag)
|
||||||
|
else:
|
||||||
|
# Descriptions only
|
||||||
|
sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \
|
||||||
|
else self.booksByTitle
|
||||||
|
contentTag = Tag(soup, 'content')
|
||||||
|
contentTag['src'] = "content/book_%d.html" % int(sort_descriptions_by[0]['id'])
|
||||||
|
navPointTag.insert(1, contentTag)
|
||||||
|
|
||||||
cmiTag = Tag(soup, '%s' % 'calibre:meta-img')
|
cmiTag = Tag(soup, '%s' % 'calibre:meta-img')
|
||||||
cmiTag['name'] = "mastheadImage"
|
cmiTag['name'] = "mastheadImage"
|
||||||
cmiTag['src'] = "images/mastheadImage.gif"
|
cmiTag['src'] = "images/mastheadImage.gif"
|
||||||
@ -3085,7 +3131,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
navMapTag.insert(0,navPointTag)
|
navMapTag.insert(0,navPointTag)
|
||||||
|
|
||||||
ncx.insert(0,navMapTag)
|
ncx.insert(0,navMapTag)
|
||||||
|
|
||||||
self.ncxSoup = soup
|
self.ncxSoup = soup
|
||||||
|
|
||||||
def generateNCXDescriptions(self, tocTitle):
|
def generateNCXDescriptions(self, tocTitle):
|
||||||
@ -3871,7 +3916,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Add this section to the body
|
# Add this section to the body
|
||||||
body.insert(btc, navPointTag)
|
body.insert(btc, navPointTag)
|
||||||
btc += 1
|
btc += 1
|
||||||
|
|
||||||
self.ncxSoup = ncx_soup
|
self.ncxSoup = ncx_soup
|
||||||
|
|
||||||
def writeNCX(self):
|
def writeNCX(self):
|
||||||
@ -4015,12 +4059,34 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Remove the special marker tags from the database's tag list,
|
# Remove the special marker tags from the database's tag list,
|
||||||
# return sorted list of normalized genre tags
|
# return sorted list of normalized genre tags
|
||||||
|
|
||||||
|
def format_tag_list(tags, indent=5, line_break=70, header='Tag list'):
|
||||||
|
def next_tag(sorted_tags):
|
||||||
|
for (i, tag) in enumerate(sorted_tags):
|
||||||
|
if i < len(tags) - 1:
|
||||||
|
yield tag + ", "
|
||||||
|
else:
|
||||||
|
yield tag
|
||||||
|
|
||||||
|
ans = '%s%d %s:\n' % (' ' * indent, len(tags), header)
|
||||||
|
ans += ' ' * (indent + 1)
|
||||||
|
out_str = ''
|
||||||
|
sorted_tags = sorted(tags)
|
||||||
|
for tag in next_tag(sorted_tags):
|
||||||
|
out_str += tag
|
||||||
|
if len(out_str) >= line_break:
|
||||||
|
ans += out_str + '\n'
|
||||||
|
out_str = ' ' * (indent + 1)
|
||||||
|
return ans + out_str
|
||||||
|
|
||||||
normalized_tags = []
|
normalized_tags = []
|
||||||
friendly_tags = []
|
friendly_tags = []
|
||||||
|
excluded_tags = []
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
if tag[0] in self.markerTags:
|
if tag in self.markerTags:
|
||||||
|
excluded_tags.append(tag)
|
||||||
continue
|
continue
|
||||||
if re.search(self.opts.exclude_genre, tag):
|
if re.search(self.opts.exclude_genre, tag):
|
||||||
|
excluded_tags.append(tag)
|
||||||
continue
|
continue
|
||||||
if tag == ' ':
|
if tag == ' ':
|
||||||
continue
|
continue
|
||||||
@ -4039,32 +4105,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
if genre_tags_dict[key] == normalized:
|
if genre_tags_dict[key] == normalized:
|
||||||
self.opts.log.warn(" %s" % key)
|
self.opts.log.warn(" %s" % key)
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
def next_tag(tags):
|
self.opts.log.info('%s' % format_tag_list(genre_tags_dict, header="enabled genre tags in database"))
|
||||||
for (i, tag) in enumerate(tags):
|
self.opts.log.info('%s' % format_tag_list(excluded_tags, header="excluded genre tags"))
|
||||||
if i < len(tags) - 1:
|
|
||||||
yield tag + ", "
|
|
||||||
else:
|
|
||||||
yield tag
|
|
||||||
|
|
||||||
self.opts.log.info(u' %d genre tags in database (excluding genres matching %s):' % \
|
|
||||||
(len(genre_tags_dict), self.opts.exclude_genre))
|
|
||||||
|
|
||||||
# Display friendly/normalized genres
|
|
||||||
# friendly => normalized
|
|
||||||
if False:
|
|
||||||
sorted_tags = ['%s => %s' % (key, genre_tags_dict[key]) for key in sorted(genre_tags_dict.keys())]
|
|
||||||
for tag in next_tag(sorted_tags):
|
|
||||||
self.opts.log(u' %s' % tag)
|
|
||||||
else:
|
|
||||||
sorted_tags = ['%s' % (key) for key in sorted(genre_tags_dict.keys())]
|
|
||||||
out_str = ''
|
|
||||||
line_break = 70
|
|
||||||
for tag in next_tag(sorted_tags):
|
|
||||||
out_str += tag
|
|
||||||
if len(out_str) >= line_break:
|
|
||||||
self.opts.log.info(' %s' % out_str)
|
|
||||||
out_str = ''
|
|
||||||
self.opts.log.info(' %s' % out_str)
|
|
||||||
|
|
||||||
return genre_tags_dict
|
return genre_tags_dict
|
||||||
|
|
||||||
@ -4140,7 +4182,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
pAuthorTag = Tag(soup, "p")
|
pAuthorTag = Tag(soup, "p")
|
||||||
pAuthorTag['class'] = "author_index"
|
pAuthorTag['class'] = "author_index"
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author']))
|
if self.opts.generate_authors:
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author']))
|
||||||
aTag.insert(0, book['author'])
|
aTag.insert(0, book['author'])
|
||||||
pAuthorTag.insert(0,aTag)
|
pAuthorTag.insert(0,aTag)
|
||||||
divTag.insert(dtc,pAuthorTag)
|
divTag.insert(dtc,pAuthorTag)
|
||||||
@ -4371,7 +4414,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
|
|
||||||
# Insert the author link (always)
|
# Insert the author link (always)
|
||||||
aTag = body.find('a', attrs={'class':'author'})
|
aTag = body.find('a', attrs={'class':'author'})
|
||||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
|
if self.opts.generate_authors:
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
|
||||||
self.generateAuthorAnchor(book['author']))
|
self.generateAuthorAnchor(book['author']))
|
||||||
|
|
||||||
if publisher == ' ':
|
if publisher == ' ':
|
||||||
@ -4860,6 +4904,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
|
|
||||||
opts.basename = "Catalog"
|
opts.basename = "Catalog"
|
||||||
opts.cli_environment = not hasattr(opts,'sync')
|
opts.cli_environment = not hasattr(opts,'sync')
|
||||||
|
|
||||||
|
# Hard-wired to always sort descriptions by author, with series after non-series
|
||||||
opts.sort_descriptions_by_author = True
|
opts.sort_descriptions_by_author = True
|
||||||
|
|
||||||
build_log = []
|
build_log = []
|
||||||
@ -4898,14 +4944,13 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
if opts_dict['ids']:
|
if opts_dict['ids']:
|
||||||
build_log.append(" book count: %d" % len(opts_dict['ids']))
|
build_log.append(" book count: %d" % len(opts_dict['ids']))
|
||||||
|
|
||||||
'''
|
|
||||||
sections_list = []
|
sections_list = []
|
||||||
if opts.generate_authors:
|
if opts.generate_authors:
|
||||||
sections_list.append('Authors')
|
sections_list.append('Authors')
|
||||||
'''
|
|
||||||
sections_list = ['Authors']
|
|
||||||
if opts.generate_titles:
|
if opts.generate_titles:
|
||||||
sections_list.append('Titles')
|
sections_list.append('Titles')
|
||||||
|
if opts.generate_series:
|
||||||
|
sections_list.append('Series')
|
||||||
if opts.generate_genres:
|
if opts.generate_genres:
|
||||||
sections_list.append('Genres')
|
sections_list.append('Genres')
|
||||||
if opts.generate_recently_added:
|
if opts.generate_recently_added:
|
||||||
@ -4913,7 +4958,27 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
if opts.generate_descriptions:
|
if opts.generate_descriptions:
|
||||||
sections_list.append('Descriptions')
|
sections_list.append('Descriptions')
|
||||||
|
|
||||||
build_log.append(u" Sections: %s" % ', '.join(sections_list))
|
if not sections_list:
|
||||||
|
if opts.cli_environment:
|
||||||
|
opts.log.warn('*** No Section switches specified, enabling all Sections ***')
|
||||||
|
opts.generate_authors = True
|
||||||
|
opts.generate_titles = True
|
||||||
|
opts.generate_series = True
|
||||||
|
opts.generate_genres = True
|
||||||
|
opts.generate_recently_added = True
|
||||||
|
opts.generate_descriptions = True
|
||||||
|
sections_list = ['Authors','Titles','Series','Genres','Recently Added','Descriptions']
|
||||||
|
else:
|
||||||
|
opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***')
|
||||||
|
return ["No Included Sections","No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"]
|
||||||
|
if opts.fmt == 'mobi' and sections_list == ['Descriptions']:
|
||||||
|
warning = _("\n*** Adding 'By Authors' Section required for MOBI output ***")
|
||||||
|
opts.log.warn(warning)
|
||||||
|
sections_list.insert(0,'Authors')
|
||||||
|
opts.generate_authors = True
|
||||||
|
|
||||||
|
opts.log(u" Sections: %s" % ', '.join(sections_list))
|
||||||
|
opts.section_list = sections_list
|
||||||
|
|
||||||
# Limit thumb_width to 1.0" - 2.0"
|
# Limit thumb_width to 1.0" - 2.0"
|
||||||
try:
|
try:
|
||||||
@ -4948,6 +5013,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
|
|
||||||
# Launch the Catalog builder
|
# Launch the Catalog builder
|
||||||
catalog = self.CatalogBuilder(db, opts, self, report_progress=notification)
|
catalog = self.CatalogBuilder(db, opts, self, report_progress=notification)
|
||||||
|
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
log.info(" Begin catalog source generation")
|
log.info(" Begin catalog source generation")
|
||||||
catalog.createDirectoryStructure()
|
catalog.createDirectoryStructure()
|
||||||
@ -4959,7 +5025,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
if catalog_source_built:
|
if catalog_source_built:
|
||||||
log.info(" Completed catalog source generation\n")
|
log.info(" Completed catalog source generation\n")
|
||||||
else:
|
else:
|
||||||
log.warn(" *** Errors during catalog generation, check log for details ***")
|
log.error(" *** Terminated catalog generation, check log for details ***")
|
||||||
|
|
||||||
if catalog_source_built:
|
if catalog_source_built:
|
||||||
recommendations = []
|
recommendations = []
|
||||||
|
@ -260,11 +260,11 @@ The Output profile also controls the screen size. This will cause, for example,
|
|||||||
Heuristic Processing
|
Heuristic Processing
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Heuristic Processing provides a variety of functions which can be used that try to detect and correct
|
Heuristic Processing provides a variety of functions which can be used to try and detect and correct
|
||||||
common problems in poorly formatted input documents. Use these functions if your input document suffers
|
common problems in poorly formatted input documents. Use these functions if your input document suffers
|
||||||
from bad formatting. Because these functions rely on common patterns, be aware that in some cases an
|
from poor formatting. Because these functions rely on common patterns, be aware that in some cases an
|
||||||
option may lead to worse results, so use with care. As an example, several of these options will
|
option may lead to worse results, so use with care. As an example, several of these options will
|
||||||
remove all non-breaking-space entities.
|
remove all non-breaking-space entities, or may include false positive matches relating to the function.
|
||||||
|
|
||||||
:guilabel:`Enable heuristic processing`
|
:guilabel:`Enable heuristic processing`
|
||||||
This option activates |app|'s Heuristic Processing stage of the conversion pipeline.
|
This option activates |app|'s Heuristic Processing stage of the conversion pipeline.
|
||||||
@ -283,7 +283,7 @@ remove all non-breaking-space entities.
|
|||||||
correction, then this value should be reduced to somewhere between 0.1 and 0.2.
|
correction, then this value should be reduced to somewhere between 0.1 and 0.2.
|
||||||
|
|
||||||
:guilabel:`Detect and markup unformatted chapter headings and sub headings`
|
:guilabel:`Detect and markup unformatted chapter headings and sub headings`
|
||||||
If your document does not have Chapter Markers and titles formatted differently from the rest of the text,
|
If your document does not have chapter headings and titles formatted differently from the rest of the text,
|
||||||
|app| can use this option to attempt detection them and surround them with heading tags. <h2> tags are used
|
|app| can use this option to attempt detection them and surround them with heading tags. <h2> tags are used
|
||||||
for chapter headings; <h3> tags are used for any titles that are detected.
|
for chapter headings; <h3> tags are used for any titles that are detected.
|
||||||
|
|
||||||
@ -331,21 +331,23 @@ remove all non-breaking-space entities.
|
|||||||
Some documents use a convention of defining text indents using non-breaking space entities. When this option is enabled |app| will
|
Some documents use a convention of defining text indents using non-breaking space entities. When this option is enabled |app| will
|
||||||
attempt to detect this sort of formatting and convert them to a 3% text indent using css.
|
attempt to detect this sort of formatting and convert them to a 3% text indent using css.
|
||||||
|
|
||||||
.. search-replace:
|
.. _search-replace:
|
||||||
|
|
||||||
Search & Replace
|
Search & Replace
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
These options are useful primarily for conversion of PDF documents. Often, the conversion leaves
|
These options are useful primarily for conversion of PDF documents or OCR conversions, though they can
|
||||||
behind page headers and footers in the text. These options use regular expressions to try and detect
|
also be used to fix many document specific problems. As an example, some conversions can leaves behind page
|
||||||
the headers and footers and remove them. Remember that they operate on the intermediate XHTML produced
|
headers and footers in the text. These options use regular expressions to try and detect headers, footers,
|
||||||
by the conversion pipeline. There is also a wizard to help you customize the regular expressions for
|
or other arbitrary text and remove or replace them. Remember that they operate on the intermediate XHTML produced
|
||||||
your document. These options can also be used for generic search and replace of any content by additionally
|
by the conversion pipeline. There is a wizard to help you customize the regular expressions for
|
||||||
specifying a replacement expression.
|
your document. Click the magic wand beside the expression box, and click the 'Test' button after composing
|
||||||
|
your search expression. Successful matches will be highlighted in Yellow.
|
||||||
|
|
||||||
The search works by using a python regular expression. All matched text is simply removed from
|
The search works by using a python regular expression. All matched text is simply removed from
|
||||||
the document or replaced using the replacement pattern. You can learn more about regular expressions and
|
the document or replaced using the replacement pattern. The replacement pattern is optional, if left blank
|
||||||
their syntax at :ref:`regexptutorial`.
|
then text matching the search pattern will be deleted from the document. You can learn more about regular expressions
|
||||||
|
and their syntax at :ref:`regexptutorial`.
|
||||||
|
|
||||||
.. _structure-detection:
|
.. _structure-detection:
|
||||||
|
|
||||||
|
@ -108,8 +108,8 @@ Follow these steps to find the problem:
|
|||||||
|
|
||||||
* Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time.
|
* Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time.
|
||||||
* Make sure you are running the latest version of |app|. The latest version can always be downloaded from `the calibre website <http://calibre-ebook.com/download>`_.
|
* Make sure you are running the latest version of |app|. The latest version can always be downloaded from `the calibre website <http://calibre-ebook.com/download>`_.
|
||||||
* Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is
|
* Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is.
|
||||||
* In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled.
|
* In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled, the plugin icon next to it should be green when it is enabled.
|
||||||
* If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `the calibre bug tracker <http://bugs.calibre-ebook.com>`_.
|
* If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `the calibre bug tracker <http://bugs.calibre-ebook.com>`_.
|
||||||
|
|
||||||
How does |app| manage collections on my SONY reader?
|
How does |app| manage collections on my SONY reader?
|
||||||
@ -441,7 +441,7 @@ menu, choose "Validate fonts".
|
|||||||
I downloaded the installer, but it is not working?
|
I downloaded the installer, but it is not working?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Downloading from the internet can sometimes result in a corrupted download. If the |app| installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from `an alternate location <http://sourceforge.net/projects/calibre/files/>`_. If the installer still doesn't work, then something on your computer is preventing it from running. Best place to ask for more help is in the `forums <http://www.mobileread.com/forums/usercp.php>`_.
|
Downloading from the internet can sometimes result in a corrupted download. If the |app| installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from `an alternate location <http://sourceforge.net/projects/calibre/files/>`_. If the installer still doesn't work, then something on your computer is preventing it from running. Try rebooting your computer and running a registry cleaner like `Wise registry cleaner <http://www.wisecleaner.com>`_. Best place to ask for more help is in the `forums <http://www.mobileread.com/forums/usercp.php>`_.
|
||||||
|
|
||||||
My antivirus program claims |app| is a virus/trojan?
|
My antivirus program claims |app| is a virus/trojan?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -104,12 +104,12 @@ class cmd_commit(_cmd_commit):
|
|||||||
|
|
||||||
def close_bug(self, bug, action, url, config):
|
def close_bug(self, bug, action, url, config):
|
||||||
print 'Closing bug #%s'% bug
|
print 'Closing bug #%s'% bug
|
||||||
nick = config.get_nickname()
|
#nick = config.get_nickname()
|
||||||
suffix = config.get_user_option('bug_close_comment')
|
suffix = config.get_user_option('bug_close_comment')
|
||||||
if suffix is None:
|
if suffix is None:
|
||||||
suffix = 'The fix will be in the next release.'
|
suffix = 'The fix will be in the next release.'
|
||||||
action = action+'ed'
|
action = action+'ed'
|
||||||
msg = '%s in branch %s. %s'%(action, nick, suffix)
|
msg = '%s in branch %s. %s'%(action, 'lp:calibre', suffix)
|
||||||
msg = msg.replace('Fixesed', 'Fixed')
|
msg = msg.replace('Fixesed', 'Fixed')
|
||||||
server = xmlrpclib.ServerProxy(url)
|
server = xmlrpclib.ServerProxy(url)
|
||||||
server.ticket.update(int(bug), msg,
|
server.ticket.update(int(bug), msg,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user